Compare commits

...

3170 Commits

Author SHA1 Message Date
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: 390986dc4729975aadb25018b857063e79649f6c
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: cea32a4bdaca0a0e8f21c4bd734d7bae787b0c98
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: 282dc9264d4157c78959c626bbe6f33ea364def5
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: b42a3c28b4395bd8a253c7728080f09100d02f42
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: 64ad5563f7ae321b749d5a52bc0b477d666dc6be
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: e1c7d72833afff6ef33e8d32cdd395190742dc08
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: 095e645ea58b2259b25c923aeaf11bbcb2990c8f
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: 10d9770aa61d864686e4ba20fbcffb8a8dd68903
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: d708ddb222abda2c8d5396bbf4ce9ee5c4549fe3
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: fa50b635800f20b0732d4f34c6da404db4dbc95f
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: e8e99997cb83b8f88cc89abb7fc0b96570ef313f
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: c776a12d15d029e4a4858ba12653ba9bafcf6949
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: ece9226e886004f1e0536dd5de3ddc2946fc118c
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: 6b672f36cf0bd4296d3bb2d1b2e6e50d1bb601f1
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: 37090daad4b99171a55962101d9998fd452e2739
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: 7a2740b18a336bc3a58c23cff08953a5c06a6078
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: 7f64fd3ee17c3a7e5b7f26e618816497e94c5243
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: 545ea60fa26bb742376237ca83c65665133bcf7c
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: 25412dbc2ca91876882de1854da1f0e9c0640543
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: 1c43dbff399853fd0bd4132886c3394d6659e85b
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: 3369cf2c35cbf03bc4008d123e69f43f1cc083e9
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: fab2ef4c1f8fa4922bcf76fc34f04ad5786c56e4
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: faefa4089d0fdf153961d4dbf6feda58d6b6a29a
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: 978b0cd6c79064f4c67e5f347655263a6d1cfbdf
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: 6d7b32b1849cae1001e581eb6f53b79594dff9b4
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: 3db47be907decd78116603c6ab4a48ff91eb2c25
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: 7ce6bd834e8653561f5389b8756bfab7664ab9f3
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: e04c6fb47d1ad2623121c907b2e8dcaff62b9ad7
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: 11e85f865148f6c6216aaf00fc5b0ef78238070a
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: ffa45893e0ea72bc21b48d0ea227253ba207eec0
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: c702d98bd63f4de4059df54f8aaea1ff56c01a34
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: dbf2edd6f4fdc89aea865bec631f7a289a4dcbd1
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: dc1d4d075af7afdf4c143f1639cd49e129969f6c
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: 19661b8fb1e3aea20e438b28e8323d7f42fe01d6
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: 825a402f0fe7d521902291e7dd6dbb288699224e
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: b772432c1ff17f49fdfc747ba88e7ed297b08465
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: ec789c66ec14fb4f23ead56f4c4ad87743ca89eb
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: 44742652c9ac2eec82a6d958fd77f84ba1d29c0a
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: 06c82586d96392dfd49a6944971d854f5890dc1c
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
Ayaan Zaidi
64e412e57e
fix(android): lazy-init node runtime after onboarding 2026-03-16 18:54:51 +05:30
Ayaan Zaidi
ac66d383e7
test: mock telegram native command reply pipeline 2026-03-16 18:54:50 +05:30
Ayaan Zaidi
e2b8ef369d
test: update discord subagent hook mocks 2026-03-16 18:54:50 +05:30
Ayaan Zaidi
7178a0d3cb
fix: normalize discord commands allowFrom auth 2026-03-16 18:54:50 +05:30
Val Alexander
0b055303f5 fix(local-storage): improve VITEST environment check for localStorage access 2026-03-16 08:21:44 -05:00
Radek Sienkiewicz
7deb543624
Browser: support non-Chrome existing-session profiles via userDataDir (#48170)
Merged via squash.

Prepared head SHA: e490035a24a3a7f0c17f681250b7ffe2b0dcd3d3
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-16 14:21:22 +01:00
Ayaan Zaidi
3e360ec8cb
fix(android): shrink chat image attachments 2026-03-16 18:47:09 +05:30
Ayaan Zaidi
a41be2585f
fix(android): preserve chat message identity on refresh 2026-03-16 18:42:25 +05:30
Ayaan Zaidi
56e23a887f
fix(android): reduce chat recomposition churn 2026-03-16 18:42:20 +05:30
Ayaan Zaidi
3009e689bc
test: remove stale synology zod mock 2026-03-16 18:41:29 +05:30
Ayaan Zaidi
5f78057ffa
fix: align telegram probe test mock 2026-03-16 18:35:03 +05:30
Ayaan Zaidi
1b31ede435
fix: bypass telegram runtime proxy during health checks 2026-03-16 18:27:05 +05:30
Gustavo Madeira Santana
55253e2a9d
Plugins: avoid booting bundled providers for catalog hooks 2026-03-16 12:56:48 +00:00
Gustavo Madeira Santana
8ad8069854
Tests: fix green check typing regressions 2026-03-16 12:54:01 +00:00
Yauheni Shauchenka
80bef826f8
fix(slack): harden bolt import interop (#45953)
* fix(slack): harden bolt import interop

* fix(slack): simplify bolt interop resolver

* fix(slack): harden startup bolt interop

* fix(slack): place changelog entry at section end

---------

Co-authored-by: Ubuntu <ubuntu@vps-1c82b947.vps.ovh.net>
Co-authored-by: Altay <altay@uinaf.dev>
2026-03-16 15:49:24 +03:00
Gustavo Madeira Santana
7d4ccee717
Plugin SDK: update entrypoint metadata 2026-03-16 12:46:23 +00:00
Gustavo Madeira Santana
841025da66
Plugin SDK: add narrow setup subpaths 2026-03-16 12:46:04 +00:00
Gustavo Madeira Santana
77566a1448
Providers: scope compat resolution to owning plugins 2026-03-16 12:45:56 +00:00
Gustavo Madeira Santana
c186176ca3
Plugin SDK: keep root alias reflection lazy 2026-03-16 12:35:13 +00:00
Gustavo Madeira Santana
ad18866bcc
Tests: align Docker cache checks with non-root images 2026-03-16 12:31:51 +00:00
Gustavo Madeira Santana
467dae53cf
Secrets: honor caller env during runtime validation 2026-03-16 12:31:44 +00:00
Gustavo Madeira Santana
e5282e6bda
Plugin SDK: update entrypoint metadata 2026-03-16 12:22:21 +00:00
Gustavo Madeira Santana
b7f99a57bf
Plugins: decouple bundled web search discovery 2026-03-16 12:19:32 +00:00
Gustavo Madeira Santana
c08f2aa21a
Providers: centralize setup defaults and helper boundaries 2026-03-16 12:06:32 +00:00
Gustavo Madeira Santana
9fc6c1929a
Plugin SDK: split setup and sandbox subpaths 2026-03-16 12:06:32 +00:00
Ayaan Zaidi
e78b51baea
test(telegram): cover shared parsing without registry 2026-03-16 17:25:27 +05:30
Ayaan Zaidi
55f6d2d1ad
fix(channels): parse bundled targets without plugin registry 2026-03-16 17:25:27 +05:30
huntharo
092afc850d
Bootstrap: report nested entry import misses 2026-03-16 07:54:12 -04:00
Gustavo Madeira Santana
4c8853122a
Plugins: preserve lazy runtime provider resolution 2026-03-16 11:52:50 +00:00
Gustavo Madeira Santana
5e4851ae2b
Tests: align media auth fixture with selection checks 2026-03-16 11:52:49 +00:00
Gustavo Madeira Santana
d6aa9b516e
Cron: isolate active-model delivery tests 2026-03-16 11:52:49 +00:00
Ayaan Zaidi
ccba943738
test(gateway): restore agent request route mock 2026-03-16 17:17:03 +05:30
ImJarvis by LukeF
8b438a308b
fix(telegram): keep silent error fallback replies quiet 2026-03-16 22:44:10 +11:00
郑耀宏
fba394c56b fix(ui): auto load Usage tab data on navigation 2026-03-16 06:28:49 -05:00
Myeongwon Choi
6a8f5bc12f
feat(telegram): add configurable silent error replies (#19776)
Port and complete #19776 on top of the current Telegram extension layout.

Adds a default-off `channels.telegram.silentErrorReplies` setting. When enabled, Telegram bot replies marked as errors are delivered silently across the regular bot reply flow, native/slash command replies, and fallback sends.

Thanks @auspic7 

Co-authored-by: Myeongwon Choi <36367286+auspic7@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
2026-03-16 22:18:34 +11:00
Gustavo Madeira Santana
fdfa98cda8
Tests: isolate bundle surface fixtures 2026-03-16 11:03:17 +00:00
Gustavo Madeira Santana
d61c08efbb
Tests: scope Codex bundle loader fixture 2026-03-16 10:48:42 +00:00
Gustavo Madeira Santana
6e65066616
Media: avoid slow auth misses in auto-detect 2026-03-16 10:45:56 +00:00
Gustavo Madeira Santana
8cd1bdd345
Status: stabilize startup memory probes 2026-03-16 10:27:44 +00:00
Gustavo Madeira Santana
1cf544ffbc
Channels: fix surface contract plugin lookup 2026-03-16 10:07:55 +00:00
Gustavo Madeira Santana
296083a49a
Plugin SDK: consolidate shared channel exports 2026-03-16 10:05:40 +00:00
Gustavo Madeira Santana
92700940d9
Plugin SDK: restore scoped imports for bundled channels 2026-03-16 09:51:36 +00:00
Vincent Koc
e1f759f4f1 BlueBubbles: lazy-load channel runtime paths 2026-03-16 02:35:43 -07:00
Vincent Koc
5336c4e945 CI: add changed extension test lane 2026-03-16 02:29:46 -07:00
Vincent Koc
303f690dd9 Docs: add extension test workflow 2026-03-16 02:29:46 -07:00
Vincent Koc
2ee20a6072 Tests: cover changed extension detection 2026-03-16 02:29:46 -07:00
Vincent Koc
d68645d47f Tests: detect changed extensions 2026-03-16 02:29:46 -07:00
Vincent Koc
898d6840dc Runtime: lazy-load Telegram and Slack channel ops 2026-03-16 02:21:57 -07:00
Vincent Koc
1447e2e384 Release: trim generated docs from npm pack 2026-03-16 02:10:04 -07:00
Vincent Koc
3832f938fd Docs: use placeholders for marketplace plugin examples 2026-03-16 02:09:20 -07:00
Vincent Koc
abb21d9163 Runtime: lazy-load Discord channel ops 2026-03-16 02:07:13 -07:00
Vincent Koc
d572188f61 Tests: add extension test runner 2026-03-16 02:06:21 -07:00
Vincent Koc
65f05d7c09 Tests: harden WhatsApp inbound contract cleanup 2026-03-16 02:06:21 -07:00
Vincent Koc
a8970963cd Tests: add contract runner 2026-03-16 02:06:21 -07:00
Vincent Koc
70aa9204c0 Channels: centralize inbound context contracts 2026-03-16 02:06:21 -07:00
Vincent Koc
79a8905fa4 Channels: centralize group policy contracts 2026-03-16 02:06:21 -07:00
Vincent Koc
4aae0d4c9d Channels: centralize outbound payload contracts 2026-03-16 02:06:21 -07:00
Vincent Koc
429144d9f1 Channels: add contract surface coverage 2026-03-16 02:06:21 -07:00
Vincent Koc
5cd206f780 Channels: expand contract suites 2026-03-16 02:06:21 -07:00
Vincent Koc
d896d8e0cd Docs: add Claude marketplace plugin install guidance 2026-03-16 02:04:05 -07:00
Nimrod Gutman
2a85fa7db1
fix(macos): restore debug build helpers (#48046) 2026-03-16 10:57:08 +02:00
Peter Steinberger
6f5369c7e8 fix: split browser-safe thinking helpers 2026-03-16 08:51:31 +00:00
Peter Steinberger
43c156e43b docs: reorder unreleased changelog entries 2026-03-16 08:50:58 +00:00
Vincent Koc
c9423dce1e Docs: refresh generated config baseline 2026-03-16 01:49:41 -07:00
Vincent Koc
c06101b8ad Infra: restore check after gaxios compat 2026-03-16 01:49:41 -07:00
Vincent Koc
30c31d4efd UI: keep thinking helpers browser-safe 2026-03-16 01:49:41 -07:00
Vincent Koc
ff2e864c98
Plugins: add Claude marketplace registry installs (#48058)
* Changelog: note Claude marketplace plugin support

* Plugins: add Claude marketplace installs

* E2E: cover marketplace plugin installs in Docker
2026-03-16 01:46:07 -07:00
Vincent Koc
9ee0fb52e9 Gateway: cover lazy channel runtime resolution 2026-03-16 01:43:47 -07:00
Vincent Koc
776e5d8a08 Gateway: lazily resolve channel runtime 2026-03-16 01:43:47 -07:00
Peter Steinberger
77b1f240fd fix: retry runtime postbuild skill copy races 2026-03-16 08:42:50 +00:00
Peter Steinberger
09e8d1e96f docs: add frontmatter to parallels discord skill 2026-03-16 08:42:50 +00:00
Peter Steinberger
f49fc633ac fix: restore effective setup wizard lazy import 2026-03-16 08:36:43 +00:00
Peter Steinberger
4c8678c0b4
refactor: add private channel sdk bridges 2026-03-16 01:34:35 -07:00
Peter Steinberger
7e74adef91
refactor: shrink public channel plugin sdk surfaces 2026-03-16 01:34:22 -07:00
Peter Steinberger
94a01c9789 fix: keep gaxios compat off the package root (#47914) (thanks @pdd-cli) 2026-03-16 08:22:39 +00:00
Prompt Driven
1aabce78e7 fix(infra): also wire gaxios-fetch-compat shim into src/index.ts (gateway entry) 2026-03-16 01:22:08 -07:00
Prompt Driven
e575f419a5 fix(infra): wire gaxios-fetch-compat shim to prevent node-fetch crash on Node.js 25 2026-03-16 01:22:08 -07:00
Peter Steinberger
7cc5789202
refactor(plugins): finish provider auth boundary cleanup 2026-03-16 01:20:56 -07:00
Peter Steinberger
a73d6620b3
refactor: route remaining channel imports through plugin sdk 2026-03-16 01:17:13 -07:00
Peter Steinberger
f11589b311
refactor: tighten plugin sdk channel seams 2026-03-16 01:05:51 -07:00
Vincent Koc
7a09255361 Runtime: lazy-load channel runtime singletons 2026-03-16 01:02:19 -07:00
Peter Steinberger
7c2863d401 fix: harden bonjour retry recovery 2026-03-16 07:59:15 +00:00
Vincent Koc
83ddb0fb4c Plugins: restore routing seams and discovery fixtures 2026-03-16 00:56:40 -07:00
Vincent Koc
ced20e7997 Plugins: add auth choice contracts 2026-03-16 00:55:03 -07:00
Peter Steinberger
3a2c24e598
refactor: route shared channel sdk imports through plugin seams 2026-03-16 00:48:53 -07:00
Peter Steinberger
0ed64f124d fix: mount CLI auth dirs in docker live tests 2026-03-16 07:44:15 +00:00
Vincent Koc
78f24dcaa2 Tests: type auth contract prompt mocks 2026-03-16 00:41:55 -07:00
Vincent Koc
4f8c066680 Plugins: cover catalog discovery providers 2026-03-16 00:41:37 -07:00
Peter Steinberger
8fe08df2eb
refactor(plugins): derive compat provider ids from manifests 2026-03-16 00:41:05 -07:00
Peter Steinberger
74d0c39b32
refactor: move session lifecycle and outbound fallbacks into plugins 2026-03-16 00:40:43 -07:00
Peter Steinberger
49251def61
docs: codify macOS parallels discord smoke 2026-03-16 00:38:20 -07:00
Vincent Koc
67b886b725 Plugins: extend provider discovery contracts 2026-03-16 00:35:16 -07:00
Peter Steinberger
045a879acf fix: stop bonjour before re-advertising 2026-03-16 07:32:34 +00:00
Vincent Koc
a6eda07316 Plugins: add provider discovery contracts 2026-03-16 00:29:46 -07:00
Vincent Koc
209f1a08d7 Plugins: dedupe routing imports in channel adapters 2026-03-16 00:29:02 -07:00
Vincent Koc
bbf3b4acf2 Plugins: add provider auth contracts 2026-03-16 00:25:51 -07:00
Peter Steinberger
b3025e6d8e
refactor(plugin-sdk): clean shared core imports 2026-03-16 00:25:32 -07:00
Peter Steinberger
7964563299
refactor: finish plugin-owned channel runtime seams 2026-03-16 00:25:19 -07:00
Peter Steinberger
e90c1d9add fix: unblock docs and registry checks 2026-03-16 07:23:43 +00:00
Vincent Koc
320b4bcb07 Plugins: add provider wizard contracts 2026-03-16 00:22:11 -07:00
Peter Steinberger
cec10703dc fix: unblock ci gates 2026-03-16 07:19:54 +00:00
Peter Steinberger
99c501a9a7
refactor(plugin-sdk): use scoped core imports for bundled channels 2026-03-16 00:19:31 -07:00
Vincent Koc
3c62ab5c89 Plugins: narrow provider runtime contracts 2026-03-16 00:18:10 -07:00
Vincent Koc
79a67a5e08 Plugins: add provider catalog contracts 2026-03-16 00:18:00 -07:00
Vincent Koc
95b761a2e1 Firecrawl: drop local registration contract test 2026-03-16 00:15:33 -07:00
Vincent Koc
947b548870 Plugins: cover Firecrawl tool ownership 2026-03-16 00:15:33 -07:00
Vincent Koc
6644783052 Plugins: capture tool registrations in test registry 2026-03-16 00:15:33 -07:00
Peter Steinberger
36f0f216ce
fix: accept sandbox plugin id hints 2026-03-16 00:14:57 -07:00
Peter Steinberger
e3ab0e174c
style(core): normalize rebase fallout 2026-03-16 00:12:43 -07:00
Peter Steinberger
0ca1b18517
fix(core): restore outbound fallbacks and gate checks 2026-03-16 00:12:43 -07:00
Vincent Koc
e7eb410dd1 Qwen Portal: move runtime tests to provider contracts 2026-03-16 00:11:06 -07:00
Vincent Koc
7dab66c89e OpenAI: move runtime tests to provider contracts 2026-03-16 00:11:05 -07:00
Vincent Koc
182a00cc49 Google: move runtime tests to provider contracts 2026-03-16 00:11:05 -07:00
Vincent Koc
62de7e02ea Anthropic: move runtime tests to provider contracts 2026-03-16 00:11:05 -07:00
Vincent Koc
25535b571a Z.ai: move runtime tests to provider contracts 2026-03-16 00:11:05 -07:00
Vincent Koc
a9a9cf4257 GitHub Copilot: move runtime tests to provider contracts 2026-03-16 00:11:05 -07:00
Vincent Koc
3fe3a53dd9 Plugins: add provider runtime contracts 2026-03-16 00:11:05 -07:00
Peter Steinberger
85b7bc7edf
refactor: remove dock shim and move session routing into plugins 2026-03-16 00:09:38 -07:00
Vincent Koc
5ca26bcae0 Tests: add plugin loader contract suite 2026-03-16 00:05:23 -07:00
Vincent Koc
c59e2dde47 Tests: tighten provider wizard contracts 2026-03-16 00:05:23 -07:00
Peter Steinberger
00ef214d59 docs: regenerate zh-CN onboarding references 2026-03-16 07:03:19 +00:00
Peter Steinberger
edab939f4d fix: make docs i18n use gpt-5.4 overrides 2026-03-16 07:03:19 +00:00
Tak Hoffman
3c6a49b27e
feishu: harden media support and align capability docs (#47968)
* feishu: harden media support and action surface

* feishu: format media action changes

* feishu: fix review follow-ups

* fix: scope Feishu target aliases to Feishu (#47968) (thanks @Takhoffman)
2026-03-16 02:02:48 -05:00
Vincent Koc
476d948732
!refactor(browser): remove Chrome extension path and add MCP doctor migration (#47893)
* Browser: replace extension path with Chrome MCP

* Browser: clarify relay stub and doctor checks

* Docs: mark browser MCP migration as breaking

* Browser: reject unsupported profile drivers

* Browser: accept clawd alias on profile create

* Doctor: narrow legacy browser driver migration
2026-03-15 23:56:08 -07:00
Vincent Koc
10cd276641 Tests: relax provider auth hint contract 2026-03-15 23:55:10 -07:00
Vincent Koc
d7ab1a6c7c Tests: add provider registry contract suite 2026-03-15 23:55:10 -07:00
Peter Steinberger
a8367bb0ec fix: stabilize ci gate 2026-03-16 06:51:18 +00:00
Vincent Koc
9b73673313 Tests: add global web search contract suite 2026-03-15 23:50:48 -07:00
Vincent Koc
0f502726e1 Tests: add global provider contract suite 2026-03-15 23:50:48 -07:00
Vincent Koc
a8878be0fd Tests: add provider contract registry 2026-03-15 23:50:48 -07:00
Vincent Koc
d410debd01 Tests: add provider contract suites 2026-03-15 23:50:48 -07:00
ObitaBot
5ece9afa8b fix: scope localStorage settings key by basePath to prevent cross-deployment conflicts
- Add settingsKeyForGateway() function similar to tokenSessionKeyForGateway()
- Use scoped key format: openclaw.control.settings.v1:https://example.com/gateway-a
- Add migration from legacy static key on load
- Fixes #47481
2026-03-15 23:50:00 -07:00
Peter Steinberger
7cdd8a84a6
refactor: add plugin-owned outbound adapters 2026-03-15 23:47:43 -07:00
Peter Steinberger
2054cb9431
refactor: move remaining channel seams into plugins 2026-03-15 23:47:30 -07:00
Peter Steinberger
ae60094fb5
refactor(plugins): move onboarding auth metadata to manifests 2026-03-15 23:47:16 -07:00
Vincent Koc
f5ef936615 Tests: replace local channel contracts 2026-03-15 23:46:45 -07:00
Vincent Koc
9df7e8bec4 Tests: add global status contract suite 2026-03-15 23:46:45 -07:00
Vincent Koc
acf7e83ac4 Tests: add global setup contract suite 2026-03-15 23:46:45 -07:00
Vincent Koc
c5d61b9677 Tests: add global actions contract suite 2026-03-15 23:46:45 -07:00
Vincent Koc
910d039ea7 Tests: add global plugin contract suite 2026-03-15 23:46:45 -07:00
Vincent Koc
6043e733a6 Tests: add plugin contract registry 2026-03-15 23:46:45 -07:00
Vincent Koc
3105a1284a Tests: add plugin contract suites 2026-03-15 23:46:45 -07:00
Peter Steinberger
fb47777d38 fix: address bot nit on session route preservation (#47797) (thanks @brokemac79) 2026-03-15 23:37:59 -07:00
brokemac79
623ba14031 fix(session): preserve external channel route when webchat views session (#47745)
When a Telegram/WhatsApp/iMessage session was viewed or messaged from the
dashboard/webchat, resolveLastChannelRaw() unconditionally returned 'webchat'
for any isDirectSessionKey() or isMainSessionKey() match, overwriting the
persisted external delivery route.

This caused subagent completion events to be delivered to the webchat/dashboard
instead of the original channel (Telegram, WhatsApp, etc.), silently dropping
messages for the channel user.

Fix: only allow webchat to own routing when no external delivery route has been
established (no persisted external lastChannel, no external channel hint in the
session key). If an external route exists, webchat is treated as admin/monitoring
access and must not mutate the delivery route.

Updated/added tests to document the correct behaviour.

Fixes #47745
2026-03-15 23:37:59 -07:00
Vincent Koc
3838ef9b2a Tests: add Discord channel contract suite 2026-03-15 23:32:13 -07:00
Vincent Koc
4fc3492da5 Tests: add Telegram channel contract suite 2026-03-15 23:32:13 -07:00
Vincent Koc
13090da3ac Tests: add Mattermost channel contract suite 2026-03-15 23:32:13 -07:00
Vincent Koc
4ae80407a6 Tests: add Slack channel contract suite 2026-03-15 23:32:13 -07:00
Vincent Koc
c01515672f Tests: add channel plugin contract helper 2026-03-15 23:32:13 -07:00
Vincent Koc
bd67f33364 Tests: add channel actions contract helper 2026-03-15 23:32:13 -07:00
Vincent Koc
c7137270d1 Security: split audit runtime surfaces 2026-03-15 23:30:34 -07:00
Peter Steinberger
d163278e9c
refactor: move channel delivery and ACP seams into plugins 2026-03-15 23:25:20 -07:00
Vincent Koc
d5b12f505c Status: lazy-load security audit commands 2026-03-15 23:24:25 -07:00
Vincent Koc
a608d09552 Status: lazy-load summary session helpers 2026-03-15 23:24:25 -07:00
Peter Steinberger
4ab016a9bd fix: preserve loopback gateway scopes for local auth 2026-03-16 06:22:15 +00:00
Peter Steinberger
130b575c21 fix: recover bonjour advertiser from ciao announce loops 2026-03-16 06:21:46 +00:00
Vincent Koc
7b2a7da549 Gateway: import normalizeAgentId in hooks 2026-03-15 23:20:11 -07:00
Vincent Koc
853d8c0d8e Tests: cover plugin capability matrix 2026-03-15 23:17:58 -07:00
Vincent Koc
81d3c6c909 Tests: fix Feishu full registration mock 2026-03-15 23:13:45 -07:00
Vincent Koc
ed82c7e57b Status: lazy-load tailscale and memory scan deps 2026-03-15 23:12:27 -07:00
Peter Steinberger
f0f934556e build: remove land gate script 2026-03-16 06:08:41 +00:00
Tak Hoffman
fa62231afc
feishu: add structured card actions and interactive approval flows (#47873)
* feishu: add structured card actions and interactive approval flows

* feishu: address review fixes and test-gate regressions

* feishu: hold inflight card dedup until completion

* feishu: restore fire-and-forget bot menu handling

* feishu: format card interaction helpers

* Feishu: add changelog entry for card interactions

* Feishu: add changelog entry for ACP session binding
2026-03-16 01:07:09 -05:00
Peter Steinberger
aa97368f7d
test: add openshell sandbox e2e smoke 2026-03-15 23:02:36 -07:00
Peter Steinberger
ddd34b6cc3
refactor(plugins): simplify provider auth choice metadata 2026-03-15 23:01:12 -07:00
Vincent Koc
c4b18ab3c9 Status: split lightweight gateway agent list 2026-03-15 22:55:27 -07:00
Vincent Koc
d47fc009de Config: keep native command defaults off heavy channel registry 2026-03-15 22:55:27 -07:00
Vincent Koc
5f42389d8d Security: lazy-load audit config snapshot IO 2026-03-15 22:55:26 -07:00
Vincent Koc
a2119efe1c Security: lazy-load deep skill audit helpers 2026-03-15 22:55:26 -07:00
Vincent Koc
4cb46f223c Security: trim audit policy import surfaces 2026-03-15 22:55:26 -07:00
Vincent Koc
ebfd32efc3 Status: split heartbeat summary helpers 2026-03-15 22:55:26 -07:00
Peter Steinberger
0a6f22a694 docs: sync config baseline 2026-03-16 05:54:58 +00:00
Peter Steinberger
465567b1eb test: fix setup wizard smoke mocks 2026-03-16 05:54:58 +00:00
Peter Steinberger
2852eab323 build: add land gate parity script 2026-03-16 05:54:16 +00:00
Peter Steinberger
ecaafb6a4f refactor: unify telegram interactive button resolution 2026-03-16 05:54:16 +00:00
Peter Steinberger
ff558862f0 refactor: extract discord shared interactive mapper 2026-03-16 05:54:16 +00:00
Peter Steinberger
7bea559166 refactor: unify reply content checks 2026-03-16 05:54:16 +00:00
Peter Steinberger
3963408871 refactor: split plugin interactive dispatch adapters 2026-03-16 05:53:35 +00:00
Peter Steinberger
9cd9c7a488 refactor: split slack block action handling 2026-03-16 05:53:35 +00:00
Peter Steinberger
2580b81bd2
refactor: move channel capability diagnostics into plugins 2026-03-15 22:53:03 -07:00
Peter Steinberger
f9e185887f docs: restore onboard docs references 2026-03-16 05:50:57 +00:00
Peter Steinberger
2acbea0da7 docs: restore onboard as canonical setup command 2026-03-16 05:50:57 +00:00
Peter Steinberger
55cbfb6e6a
refactor(plugins): move provider onboarding auth into plugins 2026-03-15 22:43:10 -07:00
Peter Steinberger
0b58a1cc13
fix: stabilize windows parallels smoke harness 2026-03-15 22:41:35 -07:00
Peter Steinberger
ad97c581e2
refactor: move channel messaging hooks into plugins 2026-03-15 22:39:00 -07:00
Peter Steinberger
680eff63fb fix: land SIGUSR1 orphan recovery regressions (#47719) (thanks @joeykrug) 2026-03-15 22:32:36 -07:00
bot_apk
98f6ec50aa fix: address 6 review comments on PR #47719
1. [P1] Treat remap failures as resume failures — if replaceSubagentRunAfterSteer
   returns false, do NOT clear abortedLastRun, increment failed count.

2. [P2] Count scan-level exceptions as retryable failures — set result.failed > 0
   in the outer catch block so scheduleOrphanRecovery retry logic triggers.

3. [P2] Persist resumed-session dedupe across recovery retries — accept
   resumedSessionKeys as a parameter; scheduleOrphanRecovery lifts the Set to
   its own scope and passes it through retries.

4. [Greptile] Use typed config accessors instead of raw structural cast for TLS
   check in lifecycle.ts.

5. [Greptile] Forward gateway.reload.deferralTimeoutMs to deferGatewayRestartUntilIdle
   in scheduleGatewaySigusr1Restart so user-configured value is not silently ignored.

6. [Greptile] Same as #4 — already addressed by the typed config fix.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-03-15 22:32:36 -07:00
Joey Krug
c780b6a6ab fix: address all review comments on PR #47719 + implement resume context and config idempotency guard 2026-03-15 22:32:36 -07:00
Joey Krug
44304ba24a fix: add retry with exponential backoff for orphan recovery
Addresses Codex review feedback — if recovery fails (e.g. gateway
still booting), retries up to 3 times with exponential backoff
(5s → 10s → 20s) before giving up.
2026-03-15 22:32:36 -07:00
Joey Krug
0311ff05d7 fix: address Greptile review feedback
- Remove unrelated pnpm-lock.yaml changes
- Move abortedLastRun flag clearing to AFTER successful resume
  (prevents permanent session loss on transient gateway failures)
- Use dynamic import for orphan recovery module to avoid startup
  memory overhead
- Add test assertion that flag is preserved on resume failure
2026-03-15 22:32:36 -07:00
Joey Krug
304703f165 fix: resume orphaned subagent sessions after SIGUSR1 reload
Closes #47711

After a SIGUSR1 gateway reload aborts in-flight subagent LLM calls, the gateway now scans for orphaned sessions and sends a synthetic resume message to restart their work. Also makes the deferral timeout configurable via gateway.reload.deferralTimeoutMs (default: 5 minutes, up from 90s).
2026-03-15 22:32:36 -07:00
Peter Steinberger
e627a5069f
refactor(plugins): move auth profile hooks into providers 2026-03-15 22:23:55 -07:00
Peter Steinberger
abe7ea4373 fix: accept schtasks Last Result key on Windows (#47844) (thanks @MoerAI) 2026-03-15 22:20:34 -07:00
MoerAI
3e8bc9f16a fix(daemon): accept 'Last Result' schtasks key variant on Windows (#47726)
Some Windows locales/versions emit 'Last Result' instead of 'Last Run Result' in schtasks output, causing gateway status to falsely report 'Runtime: unknown'. Fall back to the shorter key when the canonical key is absent.
2026-03-15 22:20:34 -07:00
Peter Steinberger
69c12c2b11 fix(plugins): resolve lazy runtime from package root 2026-03-16 05:12:30 +00:00
Peter Steinberger
d937b61fb3 fix: follow up shared interactive regressions (#47715) 2026-03-16 05:03:46 +00:00
Peter Steinberger
823039c000
docs: prefer setup wizard command 2026-03-15 22:01:04 -07:00
Peter Steinberger
f6f0045e0f
test: move setup surface coverage 2026-03-15 22:01:04 -07:00
Peter Steinberger
5c120cb36c
refactor: make setup the primary wizard surface 2026-03-15 22:01:04 -07:00
Vincent Koc
98877dc413 Slack: fail oversized merged block payloads 2026-03-15 21:55:45 -07:00
Vincent Koc
0277aa0159 Slack: fix review regressions 2026-03-15 21:55:45 -07:00
Vincent Koc
c7d31bae8a Channels: centralize shared interactive rendering 2026-03-15 21:55:45 -07:00
Vincent Koc
92bea9704e Channels: add message action capabilities 2026-03-15 21:55:45 -07:00
Vincent Koc
69a85325c3 Matrix: guard optional outbound handlers 2026-03-15 21:55:45 -07:00
Vincent Koc
e77aa26af6 Slack: test shared interactive renderer 2026-03-15 21:55:45 -07:00
Vincent Koc
6ed8ad1844 Discord: test shared interactive renderer 2026-03-15 21:55:45 -07:00
Vincent Koc
833a19f756 Plugins: update Slack interactive tests 2026-03-15 21:55:45 -07:00
Vincent Koc
d607d2e6d4 Plugins: register Slack interactive handlers correctly 2026-03-15 21:55:45 -07:00
Vincent Koc
52c90524c9 Slack: update shared interactive interaction tests 2026-03-15 21:55:45 -07:00
Vincent Koc
eb51ba5c1d Slack: fix shared interactive registration context 2026-03-15 21:55:45 -07:00
Vincent Koc
c66b994965 Cron: treat shared interactive payloads as deliverable 2026-03-15 21:55:45 -07:00
Vincent Koc
3a08f70151 Outbound: test shared interactive telegram delivery 2026-03-15 21:55:45 -07:00
Vincent Koc
0feb939cb3 Outbound: deliver shared interactive payloads 2026-03-15 21:55:45 -07:00
Vincent Koc
8f41001edf Reply: update shared interactive normalize tests 2026-03-15 21:55:45 -07:00
Vincent Koc
576ea84195 Reply: update shared interactive flow tests 2026-03-15 21:55:45 -07:00
Vincent Koc
14b7187c33 Reply: route shared interactive payloads outbound 2026-03-15 21:55:45 -07:00
Vincent Koc
38f61564ac Reply: keep shared interactive payloads during normalization 2026-03-15 21:55:45 -07:00
Vincent Koc
2d048980af Slack: map shared interactive sends in SDK actions 2026-03-15 21:55:45 -07:00
Vincent Koc
bdc91130fe Discord: map shared interactive sends in actions 2026-03-15 21:55:45 -07:00
Vincent Koc
474368d746 CLI: add shared interactive send flag 2026-03-15 21:55:45 -07:00
Vincent Koc
2eb2b0995d Outbound: accept shared interactive sends 2026-03-15 21:55:45 -07:00
Vincent Koc
04081d349e Outbound: parse shared interactive params 2026-03-15 21:55:45 -07:00
Vincent Koc
c1846000dd Message Tool: add shared interactive schema 2026-03-15 21:55:45 -07:00
Vincent Koc
f6d8a1129d Slack: advertise shared interactive support 2026-03-15 21:55:45 -07:00
Vincent Koc
59bcc9ee46 Discord: advertise shared interactive support 2026-03-15 21:55:45 -07:00
Vincent Koc
d5a7880de2 Telegram: advertise shared interactive support 2026-03-15 21:55:45 -07:00
Vincent Koc
1e54a4a6a3 Channels: test shared interactive support checks 2026-03-15 21:55:45 -07:00
Vincent Koc
8b6806ab5c Channels: expose shared interactive support checks 2026-03-15 21:55:45 -07:00
Vincent Koc
298832d170 Channels: add interactive message capability 2026-03-15 21:55:45 -07:00
Vincent Koc
6fd11f5496 Slack: add shared interactive renderer 2026-03-15 21:55:45 -07:00
Vincent Koc
f889219955 Discord: render shared interactive payloads outbound 2026-03-15 21:55:45 -07:00
Vincent Koc
59d355bc48 Discord: add shared interactive renderer 2026-03-15 21:55:45 -07:00
Vincent Koc
f327408fad Telegram: render shared interactive payloads outbound 2026-03-15 21:55:45 -07:00
Vincent Koc
e50545d767 Telegram: add shared interactive renderer 2026-03-15 21:55:45 -07:00
Vincent Koc
b1243bf15b Slack: render shared interactive payloads outbound 2026-03-15 21:55:45 -07:00
Vincent Koc
82f587fc82 Reply: compile Slack directives into shared interactions 2026-03-15 21:55:45 -07:00
Vincent Koc
5e093639d7 Plugins: centralize binding approval interactions 2026-03-15 21:55:45 -07:00
Vincent Koc
f3f0bdcb07 Outbound: preserve shared interactive payloads 2026-03-15 21:55:45 -07:00
Vincent Koc
7018412102 Reply: keep interactive payloads renderable 2026-03-15 21:55:45 -07:00
Vincent Koc
12f4dd9a05 Reply: expose shared interactive payloads 2026-03-15 21:55:45 -07:00
Vincent Koc
df2a6b1672 Interactive: add shared payload model 2026-03-15 21:55:45 -07:00
Vincent Koc
082383b40d Tests: cover Slack block-action shared dispatch 2026-03-15 21:55:45 -07:00
Vincent Koc
cc6f03ec6c Slack: route block actions through shared dispatcher 2026-03-15 21:55:45 -07:00
Vincent Koc
553cbccd40 Tests: cover Slack shared interactive dispatcher 2026-03-15 21:55:45 -07:00
Vincent Koc
f70d2624dc Plugins: add Slack shared interactive dispatcher 2026-03-15 21:55:45 -07:00
Vincent Koc
1c2a609d03 Plugins: add Slack interactive handler types 2026-03-15 21:55:45 -07:00
Vincent Koc
28de97356d Plugin SDK: export Slack interactive handler context 2026-03-15 21:55:45 -07:00
Peter Steinberger
a69f6190ab
fix(gateway): pin plugin webhook route registry (#47902) 2026-03-15 21:53:05 -07:00
Peter Steinberger
99a4594bde
fix(plugins): resolve rebase fallout in auth hooks 2026-03-15 21:52:29 -07:00
Peter Steinberger
0c2ae71366
fix(outbound): preserve channel registry during provider snapshots 2026-03-15 21:52:29 -07:00
Peter Steinberger
7a6be3d531
refactor(plugins): move auth and model policy to providers 2026-03-15 21:52:29 -07:00
Vincent Koc
3d8c29cc53 Build: unbundle LanceDB from published package 2026-03-15 21:51:42 -07:00
Vincent Koc
922ce15c65 Docs: refresh generated config baseline 2026-03-15 21:41:38 -07:00
Vincent Koc
09f607fa82 Hooks: tolerate hidden generated format targets 2026-03-15 21:41:02 -07:00
Peter Steinberger
5287ae3c06
docs: update setup wizard wording 2026-03-15 21:40:31 -07:00
Peter Steinberger
656848dcd7
refactor: rename setup wizard surfaces 2026-03-15 21:40:31 -07:00
Peter Steinberger
07d71d2b27
fix: drop stray a2ui bundle 2026-03-15 21:39:49 -07:00
Peter Steinberger
1beea52d8d
refactor: rename setup wizard surfaces 2026-03-15 21:39:49 -07:00
Peter Steinberger
0a2f95916b
test: expand ssh sandbox coverage and docs 2026-03-15 21:38:22 -07:00
Peter Steinberger
b8bb8510a2
feat: move ssh sandboxing into core 2026-03-15 21:35:30 -07:00
Peter Steinberger
33edb57e74
fix: keep provider resolution from clobbering channel plugins 2026-03-15 21:31:31 -07:00
Vincent Koc
7781f62d33 Status: restore lazy scan runtime typing 2026-03-15 21:28:56 -07:00
Vincent Koc
cb4a298961 CLI: route gateway status through daemon status 2026-03-15 21:15:04 -07:00
Peter Steinberger
7e8f5ca71b fix(ui): centralize control model ref handling 2026-03-16 04:13:43 +00:00
Vincent Koc
093e51f2b3 Security: lazy-load channel audit provider helpers 2026-03-15 21:09:41 -07:00
Peter Steinberger
c4a5fd8465
docs: update channel setup wording 2026-03-15 21:07:18 -07:00
Peter Steinberger
0f43dc4680
test: fix fetch mock typing 2026-03-15 21:07:05 -07:00
Peter Steinberger
53ccc78c63
refactor: rename setup helper surfaces 2026-03-15 21:06:55 -07:00
Vincent Koc
350b42d342 Status: lazy-load text scan helpers 2026-03-15 21:03:55 -07:00
Peter Steinberger
0218045818
test: silence vitest warning noise 2026-03-15 21:02:31 -07:00
Vincent Koc
522dda1971 Docs: refresh generated config baseline 2026-03-15 21:00:03 -07:00
Vincent Koc
270ba54c47 Status: lazy-load channel security and summaries 2026-03-15 21:00:03 -07:00
Vincent Koc
7d5e26b4a2 Tests: stabilize bundle MCP env on Windows 2026-03-15 21:00:03 -07:00
Vincent Koc
31e6cb0df6 Nostr: break setup-surface import cycle 2026-03-15 21:00:03 -07:00
Christopher Chamaletsos
d9fb50e777 fix: format default model label as 'model · provider' for consistency
The default option showed 'Default (openai/gpt-5.2)' while individual
options used the friendlier 'gpt-5.2 · openai' format.
2026-03-15 20:59:38 -07:00
Christopher Chamaletsos
01456f95bc fix: control UI sends correct provider prefix when switching models
The model selector was using just the model ID (e.g. "gpt-5.2") as the
option value. When sent to sessions.patch, the server would fall back to
the session's current provider ("anthropic") yielding "anthropic/gpt-5.2"
instead of "openai/gpt-5.2".

Now option values use "provider/model" format, and resolveModelOverrideValue
and resolveDefaultModelValue also return the full provider-prefixed key so
selected state stays consistent.
2026-03-15 20:59:38 -07:00
Peter Steinberger
a33caab280
refactor(plugins): move auth and model policy to providers 2026-03-15 20:59:06 -07:00
Vincent Koc
ca2f046668 Status: route JSON through lean command 2026-03-15 20:56:44 -07:00
Vincent Koc
1f50fed3b2 Agents: skip eager context warmup for status commands 2026-03-15 20:52:31 -07:00
Vincent Koc
92d5307074 Status: lazy-load channel summary helpers 2026-03-15 20:52:31 -07:00
Peter Steinberger
0eaf03f55b
fix: update feishu setup adapter import 2026-03-15 20:46:29 -07:00
Peter Steinberger
dfc237c319
docs: update channel setup docs 2026-03-15 20:44:26 -07:00
Peter Steinberger
98dcbd3e7e
build: add setup entrypoints for migrated channel plugins 2026-03-15 20:44:26 -07:00
Peter Steinberger
371366e9eb
feat: add synology chat setup wizard 2026-03-15 20:44:26 -07:00
Peter Steinberger
de503dbcbb
refactor: move setup fallback into setup registry 2026-03-15 20:44:25 -07:00
Peter Steinberger
77d0ff629c
refactor: rename channel setup flow seam 2026-03-15 20:44:25 -07:00
Vincent Koc
ca6dbc0f0a Gateway: lazy-load SSH status helpers 2026-03-15 20:40:22 -07:00
Peter Steinberger
aa28d1c711 feat: add firecrawl onboarding search plugin 2026-03-16 03:38:58 +00:00
Peter Steinberger
be8fef3840
docs: expand openshell sandbox docs 2026-03-15 20:35:56 -07:00
Peter Steinberger
ae7f18e503
feat: add remote openshell sandbox mode 2026-03-15 20:28:19 -07:00
Vincent Koc
3b26da4b82 CLI: route gateway status before program registration 2026-03-15 20:26:58 -07:00
Peter Steinberger
8ab01c5c93
refactor(core): land plugin auth and startup cleanup 2026-03-15 20:12:37 -07:00
Vincent Koc
f71f44576a Status: lazy-load read-only account inspectors 2026-03-15 20:10:43 -07:00
Vincent Koc
986b772a89 Status: scope JSON plugin preload to configured channels 2026-03-15 20:05:54 -07:00
Peter Steinberger
d8b927ee6a
feat: add openshell sandbox backend 2026-03-15 20:03:22 -07:00
Peter Steinberger
bc6ca4940b
fix: drop duplicate channel setup import 2026-03-15 19:58:22 -07:00
Peter Steinberger
46482a283a
feat: add nostr setup and unify channel setup discovery 2026-03-15 19:58:22 -07:00
Peter Steinberger
84c0326f4d
refactor: move group access into setup wizard 2026-03-15 19:58:22 -07:00
Vincent Koc
d8e138c743 Gateway: add presence-only probe mode for status 2026-03-15 19:56:08 -07:00
Josh Avant
a2cb81199e
secrets: harden read-only SecretRef command paths and diagnostics (#47794)
* secrets: harden read-only SecretRef resolution for status and audit

* CLI: add SecretRef degrade-safe regression coverage

* Docs: align SecretRef status and daemon probe semantics

* Security audit: close SecretRef review gaps

* Security audit: preserve source auth SecretRef configuredness

* changelog

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

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-15 21:55:24 -05:00
Peter Steinberger
3f12e90f3e
fix(ci): repair security and route test fixtures 2026-03-15 19:54:00 -07:00
Peter Steinberger
65ec4843e8 fix: tighten outbound channel/plugin resolution 2026-03-16 02:52:01 +00:00
Peter Steinberger
a97e1e1611
fix(plugins): tighten lazy setup typing 2026-03-15 19:47:58 -07:00
Vincent Koc
fdfefcaa11 Status: skip unused channel issue scan in JSON mode 2026-03-15 19:43:42 -07:00
Vincent Koc
dd203c8eee Zalouser: split setup adapter helpers 2026-03-15 19:39:38 -07:00
Peter Steinberger
b580d142cd
refactor(plugins): split lightweight channel setup modules 2026-03-15 19:38:19 -07:00
Vincent Koc
88b8151c52 Zalo: split setup adapter helpers 2026-03-15 19:37:25 -07:00
Tak Hoffman
b37085984d fixed main? 2026-03-15 21:36:56 -05:00
Vincent Koc
61bcdcca9c Feishu: split setup adapter helpers 2026-03-15 19:35:25 -07:00
Ayaan Zaidi
c08796b039 fix: add Telegram topic-edit action (#47798) 2026-03-16 08:03:22 +05:30
Ayaan Zaidi
ac5e97097e fix(telegram): normalize topic-edit targets 2026-03-16 08:03:22 +05:30
Ayaan Zaidi
a516141bda feat(telegram): add topic-edit action 2026-03-16 08:03:22 +05:30
Vincent Koc
0c9428a865 MSTeams: split setup adapter helpers 2026-03-15 19:32:48 -07:00
Vincent Koc
7212b5f01a Matrix: split setup adapter helpers 2026-03-15 19:31:11 -07:00
Vincent Koc
ecc688d205 Google Chat: split setup adapter helpers 2026-03-15 19:29:19 -07:00
Peter Steinberger
acae0b60c2
perf(plugins): lazy-load channel setup entrypoints 2026-03-15 19:27:55 -07:00
Peter Steinberger
bcdbd03579 docs: refresh zh-CN model providers 2026-03-16 02:26:45 +00:00
Peter Steinberger
47a9c1a893 refactor: merge minimax bundled plugins 2026-03-16 02:26:45 +00:00
Vincent Koc
6513749ef6 Mattermost: split setup adapter helpers 2026-03-15 19:26:13 -07:00
Peter Steinberger
c8576ec78b fix: resolve line setup rebase drift 2026-03-16 02:25:02 +00:00
Peter Steinberger
38abdea8ce fix: restore ci type checks 2026-03-16 02:23:44 +00:00
Vincent Koc
6a2efa541b LINE: split setup adapter helpers 2026-03-15 19:21:40 -07:00
Vincent Koc
c89527f389 Tlon: split setup adapter helpers 2026-03-15 19:19:28 -07:00
Peter Steinberger
c6950367fb fix: allow plugin package id hints 2026-03-16 02:19:02 +00:00
Vincent Koc
067215629f Telegram: split setup adapter helpers 2026-03-15 19:15:50 -07:00
Peter Steinberger
60bf58ddbc
refactor: trim onboarding sdk exports 2026-03-15 19:14:36 -07:00
Peter Steinberger
ec93398d7b
refactor: move line to setup wizard 2026-03-15 19:14:36 -07:00
Vincent Koc
9785b44307 IRC: split setup adapter helpers 2026-03-15 19:12:58 -07:00
Peter Steinberger
10f4a03de8 docs(google): remove stale plugin references 2026-03-16 02:11:19 +00:00
Peter Steinberger
2b57d3bb34 build(plugin-sdk): enforce export sync in check 2026-03-16 02:11:19 +00:00
Peter Steinberger
39aba198f1 fix(docs): run i18n through a local rpc client 2026-03-16 02:11:18 +00:00
Peter Steinberger
6987a3c8b5 docs(i18n): sync zh-CN google plugin references 2026-03-16 02:11:18 +00:00
Peter Steinberger
0a136f1b90 fix(docs): harden i18n prompt failures 2026-03-16 02:11:18 +00:00
Peter Steinberger
59940cb3ee refactor(plugin-sdk): centralize entrypoint manifest 2026-03-16 02:11:18 +00:00
Peter Steinberger
92e765cdee refactor(google): split oauth flow modules 2026-03-16 02:11:18 +00:00
Peter Steinberger
7c0cac2740 refactor(plugins): share bundled compat transforms 2026-03-16 02:11:18 +00:00
Peter Steinberger
bb76a90dd1 refactor(tests): share plugin registration helpers 2026-03-16 02:11:18 +00:00
Peter Steinberger
6b28668104 test(plugins): cover retired google auth compatibility 2026-03-16 02:11:18 +00:00
Vincent Koc
4ed30abc7a BlueBubbles: split setup adapter helpers 2026-03-15 19:10:54 -07:00
Peter Steinberger
70a6d40d37 fix: remove stale dist plugin dirs 2026-03-16 02:10:36 +00:00
Vincent Koc
7d2ddf70c1 Nextcloud Talk: split setup adapter helpers 2026-03-15 18:59:58 -07:00
Vincent Koc
413d2ff3da iMessage: lazy-load setup wizard surface 2026-03-15 18:53:58 -07:00
Vincent Koc
399b6f745a Signal: restore setup surface helper exports 2026-03-15 18:53:58 -07:00
Peter Steinberger
57a0534f93
fix(cli): repair preaction merge typo 2026-03-15 18:47:23 -07:00
Peter Steinberger
fb991e6f31
perf(plugins): lazy-load setup surfaces 2026-03-15 18:46:54 -07:00
Vincent Koc
de6666b895 Signal: lazy-load setup wizard surface 2026-03-15 18:44:59 -07:00
Vincent Koc
d663df7a74 Discord: lazy-load setup wizard surface 2026-03-15 18:36:57 -07:00
Vincent Koc
1c4f52d6a1 Feishu: drop stale runtime onboarding export 2026-03-15 18:36:41 -07:00
Vincent Koc
961f42e0cf Slack: lazy-load setup wizard surface 2026-03-15 18:29:40 -07:00
Peter Steinberger
1e196db49d fix: quiet discord startup logs 2026-03-16 01:27:09 +00:00
Peter Steinberger
26a8aee01c
refactor: drop channel onboarding fallback 2026-03-15 18:24:39 -07:00
Peter Steinberger
0958aea112
refactor: move matrix msteams twitch to setup wizard 2026-03-15 18:24:39 -07:00
Peter Steinberger
40be12db96
refactor: move feishu zalo zalouser to setup wizard 2026-03-15 18:24:39 -07:00
Peter Steinberger
71a69e5337
refactor: extend setup wizard account resolution 2026-03-15 18:23:40 -07:00
Peter Steinberger
9cca8a6de5
fix(matrix): assert outbound runtime hooks 2026-03-15 18:20:53 -07:00
Peter Steinberger
83ee5c0328
perf(status): defer heavy startup loading 2026-03-15 18:20:53 -07:00
Peter Steinberger
9c89a74f84
perf(cli): trim help startup imports 2026-03-15 18:20:52 -07:00
Peter Steinberger
74a57ace10
refactor(plugins): lazy load provider runtime shims 2026-03-15 18:20:52 -07:00
Peter Steinberger
b54e37c71f
feat(plugins): merge openai vendor seams into one plugin 2026-03-15 18:20:52 -07:00
Peter Steinberger
bc5054ce68 refactor(google): merge gemini auth into google plugin 2026-03-16 01:19:32 +00:00
Peter Steinberger
d56559bad7 fix: repair node24 ci type drift 2026-03-16 01:15:31 +00:00
Peter Steinberger
b8dbc12560 fix: align channel adapters with plugin sdk 2026-03-16 01:10:27 +00:00
Vincent Koc
7a93f7d9df WhatsApp: lazy-load setup wizard surface 2026-03-15 18:09:05 -07:00
Peter Steinberger
579d0ebe2b refactor(web-search): move providers into company plugins 2026-03-16 01:07:45 +00:00
Peter Steinberger
3aa5f2703c fix(web-search): restore build after plugin rebase 2026-03-16 01:07:45 +00:00
Peter Steinberger
e8156c8281 feat(web-search): add plugin-backed search providers 2026-03-16 01:07:44 +00:00
Peter Steinberger
59bcac472e fix: gate setup-only plugin side effects 2026-03-16 01:05:42 +00:00
Vincent Koc
ae6ee73097 Google Chat: lazy-load runtime-heavy channel paths 2026-03-15 18:02:27 -07:00
Vincent Koc
66a8c257b9 Feishu: lazy-load runtime-heavy channel paths 2026-03-15 18:01:43 -07:00
Peter Steinberger
a78b83472e
refactor: expose setup wizard sdk surfaces 2026-03-15 17:57:04 -07:00
Peter Steinberger
18e4e4677c
refactor: move googlechat to setup wizard 2026-03-15 17:57:04 -07:00
Peter Steinberger
8c71b36acb
refactor: move tlon to setup wizard 2026-03-15 17:57:04 -07:00
Peter Steinberger
a8bee6fb6c
refactor: move irc to setup wizard 2026-03-15 17:57:04 -07:00
Peter Steinberger
0da588d2d2
refactor: move whatsapp to setup wizard 2026-03-15 17:57:03 -07:00
Peter Steinberger
33495f32e9
refactor: expand setup wizard flow 2026-03-15 17:57:03 -07:00
Vincent Koc
da4f82503f MSTeams: lazy-load runtime-heavy channel paths 2026-03-15 17:50:35 -07:00
Vincent Koc
c0e0115b31 CI: add CLI startup memory regression check 2026-03-15 17:42:48 -07:00
Vincent Koc
a782358c9b Matrix: lazy-load runtime-heavy channel paths 2026-03-15 17:38:39 -07:00
Vincent Koc
f87e7be55e CLI: restore lightweight root help and scoped status plugin preload 2026-03-15 17:38:39 -07:00
Peter Steinberger
c455cccd3d
refactor: move nextcloud talk to setup wizard 2026-03-15 17:34:36 -07:00
Peter Steinberger
bad65f130e
refactor: move bluebubbles to setup wizard 2026-03-15 17:34:36 -07:00
Peter Steinberger
cbb8c43f60
refactor: tighten setup wizard onboarding bridge 2026-03-15 17:34:36 -07:00
Peter Steinberger
eb97535a35 build: suppress protobufjs eval warning in tsdown 2026-03-16 00:29:39 +00:00
Peter Steinberger
dd96be4e95
chore: raise plugin registry cache cap 2026-03-15 17:29:17 -07:00
Peter Steinberger
c156f7c7e3 fix: reduce plugin and discord warning noise 2026-03-16 00:24:44 +00:00
Peter Steinberger
a9317a4c28 test(discord): cover startup phase logging 2026-03-16 00:11:26 +00:00
Peter Steinberger
0537f3e597 fix: repair onboarding setup-wizard imports 2026-03-16 00:10:46 +00:00
Peter Steinberger
ee7ecb2dd4
feat(plugins): move anthropic and openai vendors to plugins 2026-03-15 17:07:28 -07:00
Peter Steinberger
e42d86afa9
docs: document richer setup wizard prompts 2026-03-15 17:06:42 -07:00
Peter Steinberger
1f37203f88
refactor: move signal imessage mattermost to setup wizard 2026-03-15 17:06:42 -07:00
Peter Steinberger
c6239bf253
refactor: expand setup wizard input flow 2026-03-15 17:06:42 -07:00
Peter Steinberger
70a228cdaa fix: repair onboarding adapter registry imports 2026-03-16 00:06:28 +00:00
Peter Steinberger
1f68e6e89c
docs(plugins): unify bundle format explainer 2026-03-15 16:58:28 -07:00
Peter Steinberger
c05cfccc17
docs(plugins): document provider runtime usage hooks 2026-03-15 16:57:32 -07:00
Peter Steinberger
8e2a1d0941
feat(plugins): move bundled providers behind plugin hooks 2026-03-15 16:57:24 -07:00
Peter Steinberger
e7555724af
feat(plugins): add provider usage runtime hooks 2026-03-15 16:57:16 -07:00
Mason
f4cc93dc7d
fix(onboarding): use scoped plugin snapshots to prevent OOM on low-memory hosts (#46763)
* fix(onboarding): use scoped plugin snapshots to prevent OOM on low-memory hosts

Onboarding and channel-add flows previously loaded the full plugin registry,
which caused OOM crashes on memory-constrained hosts. This patch introduces
scoped, non-activating plugin registry snapshots that load only the selected
channel plugin without replacing the running gateway's global state.

Key changes:
- Add onlyPluginIds and activate options to loadOpenClawPlugins for scoped loads
- Add suppressGlobalCommands to plugin registry to avoid leaking commands
- Replace full registry reloads in onboarding with per-channel scoped snapshots
- Validate command definitions in snapshot loads without writing global registry
- Preload configured external plugins via scoped discovery during onboarding

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

* fix(test): add return type annotation to hoisted mock to resolve TS2322

* fix(plugins): enforce cache:false invariant for non-activating snapshot loads

* Channels: preserve lazy scoped snapshot import after rebase

* Onboarding: scope channel snapshots by plugin id

* Catalog: trust manifest ids for channel plugin mapping

* Onboarding: preserve scoped setup channel loading

* Onboarding: restore built-in adapter fallback

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-15 16:52:08 -07:00
Peter Steinberger
a058bf918d
feat(plugins): test bundle MCP end to end 2026-03-15 16:51:13 -07:00
Peter Steinberger
c3ed3ba310
docs: update setup wizard capabilities 2026-03-15 16:48:43 -07:00
Peter Steinberger
5a68e8261e
refactor: drop onboarding adapter sdk exports 2026-03-15 16:48:43 -07:00
Peter Steinberger
bb160ebe89
refactor: move discord and slack to setup wizard 2026-03-15 16:48:43 -07:00
Peter Steinberger
6e047eb683
refactor: expand setup wizard flow 2026-03-15 16:48:43 -07:00
Vincent Koc
c74042ba04
Commands: lazy-load auth choice plugin provider runtime (#47692)
* Commands: lazy-load auth choice plugin provider runtime

* Tests: cover auth choice plugin provider runtime
2026-03-15 16:40:51 -07:00
Peter Steinberger
fd7e283ac5
fix: tighten setup wizard typing 2026-03-15 16:26:09 -07:00
Peter Steinberger
d040d48af4
docs: describe channel setup wizard surface 2026-03-15 16:26:09 -07:00
Peter Steinberger
a4047bf148
refactor: move telegram onboarding to setup wizard 2026-03-15 16:26:09 -07:00
Peter Steinberger
74c762beb0
refactor: decouple channel setup discovery 2026-03-15 16:26:09 -07:00
Vincent Koc
963237a18f Changelog: note plugin agent integrations 2026-03-15 16:10:59 -07:00
Peter Steinberger
9eed6e674b
fix(plugins): restore provider compatibility fallbacks 2026-03-15 16:09:40 -07:00
Peter Steinberger
684e5ea249
build(plugins): add bundled provider plugin packages 2026-03-15 16:09:40 -07:00
Peter Steinberger
4adcfa3256
feat(plugins): move provider runtimes into bundled plugins 2026-03-15 16:09:40 -07:00
Peter Steinberger
dd40741e18
feat(plugins): add compatible bundle support 2026-03-15 16:08:50 -07:00
Harold Hunt
aa1454d1a8
Plugins: broaden plugin surface for Codex App Server (#45318)
* Plugins: add inbound claim and Telegram interaction seams

* Plugins: add Discord interaction surface

* Chore: fix formatting after plugin rebase

* fix(hooks): preserve observers after inbound claim

* test(hooks): cover claimed inbound observer delivery

* fix(plugins): harden typing lease refreshes

* fix(discord): pass real auth to plugin interactions

* fix(plugins): remove raw session binding runtime exposure

* fix(plugins): tighten interactive callback handling

* Plugins: gate conversation binding with approvals

* Plugins: migrate legacy plugin binding records

* Plugins/phone-control: update test command context

* Plugins: migrate legacy binding ids

* Plugins: migrate legacy codex session bindings

* Discord: fix plugin interaction handling

* Discord: support direct plugin conversation binds

* Plugins: preserve Discord command bind targets

* Tests: fix plugin binding and interactive fallout

* Discord: stabilize directory lookup tests

* Discord: route bound DMs to plugins

* Discord: restore plugin bindings after restart

* Telegram: persist detached plugin bindings

* Plugins: limit binding APIs to Telegram and Discord

* Plugins: harden bound conversation routing

* Plugins: fix extension target imports

* Plugins: fix Telegram runtime extension imports

* Plugins: format rebased binding handlers

* Discord: bind group DM interactions by channel

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-15 16:06:11 -07:00
Peter Steinberger
4eee827dce
Channels: use owned helper imports 2026-03-15 15:59:12 -07:00
Peter Steinberger
8b001d6e4d
Channels: move onboarding adapters into extensions 2026-03-15 15:59:12 -07:00
Peter Steinberger
392ddb56e2
build(plugins): add bundled provider plugin manifests 2026-03-15 15:18:32 -07:00
Peter Steinberger
4a0f72866b
feat(plugins): move provider runtimes into bundled plugins 2026-03-15 15:18:32 -07:00
Gustavo Madeira Santana
14137bef22
Plugins: clean stale bundled skill outputs 2026-03-15 21:48:09 +00:00
Gustavo Madeira Santana
50a6902a9a
Plugins: skip nested node_modules in bundled skills 2026-03-15 21:43:13 +00:00
Gustavo Madeira Santana
1839bc0b1a
Plugins: relocate bundled skill assets 2026-03-15 21:42:02 +00:00
Vincent Koc
b810e94a17
Commands: lazy-load non-interactive plugin provider runtime (#47593)
* Commands: lazy-load non-interactive plugin provider runtime

* Tests: cover non-interactive plugin provider ordering

* Update src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.runtime.ts

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-15 14:37:41 -07:00
Nimrod Gutman
50c8934231
fix(dev): align gateway watch with tsdown wrapper (#47636) 2026-03-15 23:28:57 +02:00
Vincent Koc
5a7aba94a2
CLI: support package-manager installs from GitHub main (#47630)
* CLI: resolve package-manager main install specs

* CLI: skip registry resolution for raw package specs

* CLI: support main package target updates

* CLI: document package update specs in help

* Tests: cover package install spec resolution

* Tests: cover npm main-package updates

* Tests: cover update --tag main

* Installer: support main package targets

* Installer: support main package targets on Windows

* Docs: document package-manager main updates

* Docs: document installer main targets

* Docs: document npm and pnpm main installs

* Docs: document update --tag main

* Changelog: note package-manager main installs

* Update src/infra/update-global.test.ts

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-15 14:18:12 -07:00
Vincent Koc
3735156766
fix(ci): restore config baseline release-check output (#47629)
* Docs: regenerate config baseline

* Chore: ignore generated config baseline

* Update .prettierignore

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-15 14:14:30 -07:00
Nimrod Gutman
47fd8558cd
fix(plugins): fix bundled plugin roots and skill assets (#47601)
* fix(acpx): resolve bundled plugin root correctly

* fix(plugins): copy bundled plugin skill assets

* fix(plugins): tolerate missing bundled skill paths
2026-03-15 23:00:30 +02:00
Vincent Koc
7931f06c00 Plugins: harden context engine ownership 2026-03-15 13:51:15 -07:00
Gustavo Madeira Santana
4fb0160309
Gateway: sync runtime post-build artifacts 2026-03-15 20:44:15 +00:00
Vincent Koc
b795ba1d02 Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  Plugins: reserve context engine ownership (#47595)
  fix(release): block oversized npm packs that regress low-memory startup (#46850)
  Scripts: rebuild on extension and tsdown config changes (#47571)
  Docs: move release runbook to maintainer repo (#47532)
  docs(zalo): document current Marketplace bot behavior (openclaw#47552)
2026-03-15 13:42:21 -07:00
Vincent Koc
85dd0ab2f8
Plugins: reserve context engine ownership (#47595)
* Plugins: reserve context engine ownership

* Update src/context-engine/registry.ts

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-15 13:33:37 -07:00
Ted Li
07f890fa45
fix(release): block oversized npm packs that regress low-memory startup (#46850)
* fix(release): guard npm pack size regressions

* fix(release): fail closed when npm omits pack size
2026-03-15 21:31:30 +01:00
Gustavo Madeira Santana
594920f8cc
Scripts: rebuild on extension and tsdown config changes (#47571)
Merged via squash.

Prepared head SHA: edd8ed825469128bbe85f86e2e1341f6c57687d7
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-15 16:19:27 -04:00
Onur Solmaz
a2080421a1
Docs: move release runbook to maintainer repo (#47532)
* Docs: redact private release setup

* Docs: tighten release order

* Docs: move release runbook to maintainer repo

* Docs: delete public mac release page

* Docs: remove zh-CN mac release page

* Docs: turn release checklist into release policy

* Docs: point release policy to private docs

* Docs: regenerate zh-CN release policy pages

* Docs: preserve Doctor in zh-CN hubs

* Docs: fix zh-CN polls label

* Docs: tighten docs i18n term guardrails

* Docs: enforce zh-CN glossary coverage
2026-03-15 20:42:39 +01:00
Tomáš Dinh
4a7fbe090a
docs(zalo): document current Marketplace bot behavior (openclaw#47552)
Verified:
- pnpm check:docs

Co-authored-by: Tomáš Dinh <82420070+No898@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-15 14:40:35 -05:00
Vincent Koc
51631e5797 Plugins: reserve context engine ownership 2026-03-15 12:27:29 -07:00
peizhe.chen
42837a04bf
fix(models): preserve stream usage compat opt-ins (#45733)
Preserves explicit `supportsUsageInStreaming` overrides from built-in provider
catalogs and user config instead of unconditionally forcing `false` on non-native
openai-completions endpoints.

Adds `applyNativeStreamingUsageCompat()` to set `supportsUsageInStreaming: true`
on ModelStudio (DashScope) and Moonshot models at config build time so their
native streaming usage works out of the box.

Closes #46142

Co-authored-by: pezy <peizhe.chen@vbot.cn>
2026-03-15 20:21:11 +01:00
Nimrod Gutman
e2dac5d5cb
fix(plugins): load bundled extensions from dist (#47560) 2026-03-15 21:16:27 +02:00
xiaoyi
bbb0c3e5d7
CLI/completion: fix generator OOM and harden plugin registries (#45537)
* fix: avoid OOM during completion script generation

* CLI/completion: fix PowerShell nested command paths

* CLI/completion: cover generated shell scripts

* Changelog: note completion generator follow-up

* Plugins: reserve shared registry names

---------

Co-authored-by: Xiaoyi <xiaoyi@example.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-15 12:14:30 -07:00
Vincent Koc
dd2eb29038
Commands: split static onboard auth choice help (#47545)
* Commands: split static onboard auth choice help

* Tests: cover static onboard auth choice help

* Changelog: note static onboard auth choice help
2026-03-15 12:11:55 -07:00
Vincent Koc
c9a8b6f82f chore(fmt): format changes and broken types 2026-03-15 12:03:35 -07:00
Vincent Koc
438991b6a4
Commands: lazy-load model picker provider runtime (#47536)
* Commands: lazy-load model picker provider runtime

* Tests: cover model picker runtime boundary
2026-03-15 10:54:46 -07:00
Vincent Koc
630958749c
Changelog: note CLI OOM startup fixes (#47525) 2026-03-15 10:54:21 -07:00
Vincent Koc
fc2d29ea92
Gateway: tighten forwarded client and pairing guards (#46800)
* Gateway: tighten forwarded client and pairing guards

* Gateway: make device approval scope checks atomic

* Gateway: preserve device approval baseDir compatibility
2026-03-15 10:50:49 -07:00
Vincent Koc
132e459009 fix(ci): config drift found and documented 2026-03-15 10:43:03 -07:00
Vincent Koc
756d9b5782
CLI: lazy-load auth choice provider fallback (#47495)
* CLI: lazy-load auth choice provider fallback

* CLI: cover lazy auth choice provider fallback
2026-03-15 10:29:31 -07:00
Nimrod Gutman
d88da9f5f8
fix(config): avoid failing startup on implicit memory slot (#47494)
* fix(config): avoid failing on implicit memory slot

* fix(config): satisfy build for memory slot guard

* docs(changelog): note implicit memory slot startup fix (#47494)
2026-03-15 19:28:50 +02:00
Vincent Koc
f0202264d0
Gateway: scrub credentials from endpoint snapshots (#46799)
* Gateway: scrub credentials from endpoint snapshots

* Gateway: scrub raw endpoint credentials in snapshots

* Gateway: preserve config redaction round-trips

* Gateway: restore redacted endpoint URLs on apply
2026-03-15 10:28:15 -07:00
Sally O'Malley
d37e3d582f
Scope Control UI sessions per gateway (#47453)
* Scope Control UI sessions per gateway

Signed-off-by: sallyom <somalley@redhat.com>

* Add changelog for Control UI session scoping

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
2026-03-15 13:08:37 -04:00
Vincent Koc
13e256ac9d
CLI: trim onboarding provider startup imports (#47467) 2026-03-15 09:47:56 -07:00
Vincent Koc
8e97b752d0
Tools: revalidate workspace-only patch targets (#46803)
* Tools: revalidate workspace-only patch targets

* Tests: narrow apply-patch delete-path assertion
2026-03-15 09:45:58 -07:00
Vincent Koc
5e78c8bc95
Webhooks: tighten pre-auth body handling (#46802)
* Webhooks: tighten pre-auth body handling

* Webhooks: clean up request body guards
2026-03-15 09:45:18 -07:00
Vincent Koc
7679eb3752
Subagents: restrict follow-up messaging scope (#46801)
* Subagents: restrict follow-up messaging scope

* Subagents: cover foreign-session follow-up sends

* Update CHANGELOG.md
2026-03-15 09:44:51 -07:00
Vincent Koc
9e2eed211c Changelog: add more unreleased PR numbers 2026-03-15 09:36:53 -07:00
Vincent Koc
a493f01a90 Changelog: add missing PR credits 2026-03-15 09:33:47 -07:00
Vincent Koc
229426a257
ACP: require admin scope for mutating internal actions (#46789)
* ACP: require admin scope for mutating internal actions

* ACP: cover operator admin mutating actions

* ACP: gate internal status behind admin scope
2026-03-15 09:28:44 -07:00
Vincent Koc
a47722de7e
Integrations: tighten inbound callback and allowlist checks (#46787)
* Integrations: harden inbound callback and allowlist handling

* Integrations: address review follow-ups

* Update CHANGELOG.md

* Mattermost: avoid command-gating open button callbacks
2026-03-15 09:24:24 -07:00
Vincent Koc
67b2d1b8e8
CLI: reduce channels add startup memory (#46784)
* CLI: lazy-load channel subcommand handlers

* Channels: defer add command dependencies

* CLI: skip status JSON plugin preload

* CLI: cover status JSON route preload

* Status: trim JSON security audit path

* Status: update JSON fast-path tests

* CLI: cover root help fast path

* CLI: fast-path root help

* Status: keep JSON security parity

* Status: restore JSON security tests

* CLI: document status plugin preload

* Channels: reuse Telegram account import
2026-03-15 09:10:40 -07:00
Vincent Koc
8d44b16b7c
Plugins: preserve scoped ids and reserve bundled duplicates (#47413)
* Plugins: preserve scoped ids and reserve bundled duplicates

* Changelog: add plugin scoped id note

* Plugins: harden scoped install ids

* Plugins: reserve scoped install dirs

* Plugins: migrate legacy scoped update ids
2026-03-15 09:07:10 -07:00
Peter Steinberger
0c7ae04262
style: format imported model helpers 2026-03-15 09:05:45 -07:00
Peter Steinberger
7c0a849ed7
fix: harden device token rotation denial paths 2026-03-15 09:05:45 -07:00
Vincent Koc
a60fd3feed Nodes tests: prove pull-time policy revalidation 2026-03-15 09:05:22 -07:00
Aditya Chaudhary
f5cd7c390d
added a fix for memory leak on 2gb ram (#46522) 2026-03-15 09:01:31 -07:00
Peter Steinberger
87c4ae36b4
refactor: drop deprecated whatsapp mention pattern sdk helper 2026-03-15 08:50:31 -07:00
Vincent Koc
ec2c6d83b9
Nodes: recheck queued actions before delivery (#46815)
* Nodes: recheck queued actions before delivery

* Nodes tests: cover pull-time policy recheck

* Nodes tests: type node policy mocks explicitly
2026-03-15 08:47:17 -07:00
Peter Steinberger
ff61343d76
fix: harden mention pattern regex compilation 2026-03-15 08:44:12 -07:00
Vincent Koc
e4c61723cd
ACP: fail closed on conflicting tool identity hints (#46817)
* ACP: fail closed on conflicting tool identity hints

* ACP: restore rawInput fallback for safe tool resolution

* ACP tests: cover rawInput-only safe tool approval
2026-03-15 08:39:49 -07:00
Tak Hoffman
89e3969d64
feat(feishu): add ACP and subagent session binding (#46819)
* feat(feishu): add ACP session support

* fix(feishu): preserve sender-scoped ACP rebinding

* fix(feishu): recover sender scope from bound ACP sessions

* fix(feishu): support DM ACP binding placement

* feat(feishu): add current-conversation session binding

* fix(feishu): avoid DM parent binding fallback

* fix(feishu): require canonical topic sender ids

* fix(feishu): honor sender-scoped ACP bindings

* fix(feishu): allow user-id ACP DM bindings

* fix(feishu): recover user-id ACP DM bindings
2026-03-15 10:33:49 -05:00
Peter Steinberger
a472f988d8
fix: harden remote cdp probes 2026-03-15 08:23:01 -07:00
Harold Hunt
53462b990d
chore(gateway): ignore .test.ts changes in gateway:watch (#36211) 2026-03-15 11:14:28 -04:00
Peter Steinberger
b2e9221a8c
test(whatsapp): fix stale append inbox expectation 2026-03-15 07:57:26 -07:00
Ayaan Zaidi
c4265a5f16
fix: preserve Telegram word boundaries when rechunking HTML (#47274)
* fix: preserve Telegram chunk word boundaries

* fix: address Telegram chunking review feedback

* fix: preserve Telegram retry separators

* fix: preserve Telegram chunking boundaries (#47274)
2026-03-15 18:10:49 +05:30
Andrew Demczuk
26e0a3ee9a
fix(gateway): skip Control UI pairing when auth.mode=none (closes #42931) (#47148)
When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the #43478 regression where a top-level OR disabled pairing for ALL
websocket clients.
2026-03-15 13:03:39 +01:00
助爪
5c5c64b612
Deduplicate repeated tool call IDs for OpenAI-compatible APIs (#40996)
Merged via squash.

Prepared head SHA: 38d80483592de63866b07cd61edc7f41ffd56021
Co-authored-by: xaeon2026 <264572156+xaeon2026@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-15 19:46:07 +08:00
Jason
9d3e653ec9
fix(web): handle 515 Stream Error during WhatsApp QR pairing (#27910)
* fix(web): handle 515 Stream Error during WhatsApp QR pairing

getStatusCode() never unwrapped the lastDisconnect wrapper object,
so login.errorStatus was always undefined and the 515 restart path
in restartLoginSocket was dead code.

- Add err.error?.output?.statusCode fallback to getStatusCode()
- Export waitForCredsSaveQueue() so callers can await pending creds
- Await creds flush in restartLoginSocket before creating new socket

Fixes #3942

* test: update session mock for getStatusCode unwrap + waitForCredsSaveQueue

Mirror the getStatusCode fix (err.error?.output?.statusCode fallback)
in the test mock and export waitForCredsSaveQueue so restartLoginSocket
tests work correctly.

* fix(web): scope creds save queue per-authDir to avoid cross-account blocking

The credential save queue was a single global promise chain shared by all
WhatsApp accounts. In multi-account setups, a slow save on one account
blocked credential writes and 515 restart recovery for unrelated accounts.

Replace the global queue with a per-authDir Map so each account's creds
serialize independently. waitForCredsSaveQueue() now accepts an optional
authDir to wait on a single account's queue, or waits on all when omitted.

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

* test: use real Baileys v7 error shape in 515 restart test

The test was using { output: { statusCode: 515 } } which was already
handled before the fix. Updated to use the actual Baileys v7 shape
{ error: { output: { statusCode: 515 } } } to cover the new fallback
path in getStatusCode.

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

* fix(web): bound credential-queue wait during 515 restart

Prevents restartLoginSocket from blocking indefinitely if a queued
saveCreds() promise stalls (e.g. hung filesystem write).

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

* fix: clear flush timeout handle and assert creds queue in test

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

* fix: evict settled credsSaveQueues entries to prevent unbounded growth

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

* fix: share WhatsApp 515 creds flush handling (#27910) (thanks @asyncjason)

---------

Co-authored-by: Jason Separovic <jason@wilma.dog>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-15 17:00:07 +05:30
Ted Li
843e3c1efb
fix(whatsapp): restore append recency filter lost in extensions refactor, handle Long timestamps (#42588)
Merged via squash.

Prepared head SHA: 8ce59bb7153c1717dad4022e1cfd94857be53324
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
2026-03-15 03:03:31 -07:00
Ace Lee
d7ac16788e
fix(android): support android node calllog.search (#44073)
* fix(android): support android node  `calllog.search`

* fix(android): support android node calllog.search

* fix(android): wire callLog through shared surfaces

* fix: land Android callLog support (#44073) (thanks @lxk7280)

---------

Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-15 14:54:32 +05:30
Frank Yang
4bb8a65edd
fix: forward forceDocument through sendPayload path (follow-up to #45111) (#47119)
Merged via squash.

Prepared head SHA: d791190f8303c664cea8737046eb653c0514e939
Co-authored-by: thepagent <262003297+thepagent@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-15 17:23:53 +08:00
Sahan
9616d1e8ba
fix: Disable strict mode tools for non-native openai-completions compatible APIs (#45497)
Merged via squash.

Prepared head SHA: 20fe05fe747821455c020521e5c2072b368713d8
Co-authored-by: sahancava <57447079+sahancava@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-15 16:36:52 +08:00
Onur Solmaz
a2d73be3a4
Docs: switch README logo to SVG assets (#47049) 2026-03-15 08:58:45 +01:00
SkunkWorks0x
c33375f843
docs: replace outdated Clawdbot references with OpenClaw in skill docs (#41563)
Update 5 references to the old "Clawdbot" name in
skills/apple-reminders/SKILL.md and skills/imsg/SKILL.md.

Co-authored-by: imanisynapse <imanisynapse@gmail.com>
2026-03-15 08:29:19 +01:00
Praveen K Singh
d230bd9c38
Docs: fix stale Clawdbot branding in agent workflow file (#46963)
Co-authored-by: webdevpraveen <webdevpraveen@users.noreply.github.com>
2026-03-15 08:01:03 +01:00
Ayaan Zaidi
6a458ef29e
fix: harden compaction timeout follow-ups 2026-03-15 12:13:23 +05:30
Jason
f77a684131
feat: make compaction timeout configurable via agents.defaults.compaction.timeoutSeconds (#46889)
* feat: make compaction timeout configurable via agents.defaults.compaction.timeoutSeconds

The hardcoded 5-minute (300s) compaction timeout causes large sessions
to enter a death spiral where compaction repeatedly fails and the
session grows indefinitely. This adds agents.defaults.compaction.timeoutSeconds
to allow operators to override the compaction safety timeout.

Default raised to 900s (15min) which is sufficient for sessions up to
~400k tokens. The resolved timeout is also used for the session write
lock duration so locks don't expire before compaction completes.

Fixes #38233

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

* test: add resolveCompactionTimeoutMs tests

Cover config resolution edge cases: undefined config, missing
compaction section, valid seconds, fractional values, zero,
negative, NaN, and Infinity.

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

* fix: add timeoutSeconds to compaction Zod schema

The compaction object schema uses .strict(), so setting the new
timeoutSeconds config option would fail validation at startup.

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

* fix: enforce integer constraint on compaction timeoutSeconds schema

Prevents sub-second values like 0.5 which would floor to 0ms and
cause immediate compaction timeout. Matches pattern of other
integer timeout fields in the schema.

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

* fix: clamp compaction timeout to Node timer-safe maximum

Values above ~2.1B ms overflow Node's setTimeout to 1ms, causing
immediate timeout. Clamp to MAX_SAFE_TIMEOUT_MS matching the
pattern in agents/timeout.ts.

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

* fix: add FIELD_LABELS entry for compaction timeoutSeconds

Maintains label/help parity invariant enforced by
schema.help.quality.test.ts.

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

* fix: align compaction timeouts with abort handling

* fix: land compaction timeout handling (#46889) (thanks @asyncjason)

---------

Co-authored-by: Jason Separovic <jason@wilma.dog>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-15 12:04:48 +05:30
Vincent Koc
8e04d1fe15
macOS: restrict canvas agent actions to trusted surfaces (#46790)
* macOS: restrict canvas agent actions to trusted surfaces

* Changelog: note trusted macOS canvas actions

* macOS: encode allowed canvas schemes as JSON
2026-03-14 23:26:19 -07:00
Vincent Koc
3cbf932413
Tlon: honor explicit empty allowlists and defer cite expansion (#46788)
* Tlon: fail closed on explicit empty allowlists

* Tlon: preserve cited content for owner DMs
2026-03-14 23:24:53 -07:00
Vincent Koc
d1e4ee03ff fix(context): skip eager warmup for non-model CLI commands 2026-03-14 23:20:15 -07:00
Jinhao Dong
8e4a1d87e2
fix(openrouter): silently dropped images for new OpenRouter models — runtime capability detection (#45824)
* fix: fetch OpenRouter model capabilities at runtime for unknown models

When an OpenRouter model is not in the built-in static snapshot from
pi-ai, the fallback hardcodes input: ["text"], silently dropping images.

Query the OpenRouter API at runtime to detect actual capabilities
(image support, reasoning, context window) for models not in the
built-in list. Results are cached in memory for 1 hour. On API
failure/timeout, falls back to text-only (no regression).

* feat(openrouter): add disk cache for OpenRouter model capabilities

Persist the OpenRouter model catalog to ~/.openclaw/cache/openrouter-models.json
so it survives process restarts. Cache lookup order:

1. In-memory Map (instant)
2. On-disk JSON file (avoids network on restart)
3. OpenRouter API fetch (populates both layers)

Also triggers a background refresh when a model is not found in the cache,
in case it was newly added to OpenRouter.

* refactor(openrouter): remove pre-warm, use pure lazy-load with disk cache

- Remove eager ensureOpenRouterModelCache() from run.ts
- Remove TTL — model capabilities are stable, no periodic re-fetching
- Cache lookup: in-memory → disk → API fetch (only when needed)
- API is only called when no cache exists or a model is not found
- Disk cache persists across gateway restarts

* fix(openrouter): address review feedback

- Fix timer leak: move clearTimeout to finally block
- Fix modality check: only check input side of "->" separator to avoid
  matching image-generation models (text->image)
- Use resolveStateDir() instead of hardcoded homedir()/.openclaw
- Separate cache dir and filename constants
- Add utf-8 encoding to writeFileSync for consistency
- Add data validation when reading disk cache

* ci: retrigger checks

* fix: preload unknown OpenRouter model capabilities before resolve

* fix: accept top-level OpenRouter max token metadata

* fix: update changelog for OpenRouter runtime capability lookup (#45824) (thanks @DJjjjhao)

* fix: avoid redundant OpenRouter refetches and preserve suppression guards

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-15 11:48:39 +05:30
Vincent Koc
a97b9014a2
External content: sanitize wrapped metadata (#46816) 2026-03-14 23:06:30 -07:00
Peter Steinberger
8851d06429
docs: reorder unreleased changelog 2026-03-14 22:16:41 -07:00
Ayaan Zaidi
37c79f84ba
fix(android): theme popup surfaces 2026-03-15 09:48:08 +05:30
Sebastian Schubotz
db20141993
feat(android): add dark theme (#46249)
* Android: add mobile dark theme

* Android: fix remaining dark mode card surfaces

* Android: address dark mode review comments

* fix(android): theme onboarding flow

* fix: add Android dark theme coverage (#46249) (thanks @sibbl)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-15 08:35:04 +05:30
Tak Hoffman
29fec8bb9f
fix(gateway): harden health monitor account gating (#46749)
* gateway: harden health monitor account gating

* gateway: tighten health monitor account-id guard
2026-03-14 21:58:28 -05:00
Vincent Koc
8aaafa045a
docker: add lsof to runtime image (#46636) 2026-03-14 19:40:29 -07:00
rstar327
ba6064cc22
feat(gateway): make health monitor stale threshold and max restarts configurable (openclaw#42107)
Verified:
- pnpm exec vitest --run src/config/config-misc.test.ts -t "gateway.channelHealthCheckMinutes"
- pnpm exec vitest --run src/gateway/server-channels.test.ts -t "health monitor"
- pnpm exec vitest --run src/gateway/channel-health-monitor.test.ts src/gateway/server/readiness.test.ts
- pnpm exec vitest --run extensions/feishu/src/outbound.test.ts
- pnpm exec tsc --noEmit

Co-authored-by: rstar327 <114364448+rstar327@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-14 21:21:56 -05:00
Tak Hoffman
f00db91590
fix(plugins): prefer explicit installs over bundled duplicates (#46722)
* fix(plugins): prefer explicit installs over bundled duplicates

* test(feishu): mock structured card sends in outbound tests

* fix(plugins): align duplicate diagnostics with loader precedence
2026-03-14 21:08:32 -05:00
Radek Sienkiewicz
e3b7ff2f1f
Docs: fix MDX markers blocking page refreshes (#46695)
Merged via squash.

Prepared head SHA: 56b25a9fb3acc1a3befbf33c28a6d27df8aca8ef
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-15 02:58:59 +01:00
songlei
df3a247db2
feat(feishu): structured cards with identity header, note footer, and streaming enhancements (openclaw#29938)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: nszhsl <512639+nszhsl@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-14 20:31:46 -05:00
Tak Hoffman
f4dbd78afd
Add Feishu reactions and card action support (#46692)
* Add Feishu reactions and card action support

* Tighten Feishu action handling
2026-03-14 20:25:02 -05:00
Hiago Silva
946c24d674
fix: validate edge tts output file is non-empty before reporting success (#43385) thanks @Huntterxx
Merged after review.\n\nSmall, scoped fix: treat 0-byte Edge TTS output as failure so provider fallback can continue.
2026-03-14 20:22:09 -05:00
Tomsun28
c57b750be4
feat(provider): support new model zai glm-5-turbo, performs better for openclaw (openclaw#46670)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: tomsun28 <24788200+tomsun28@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-14 20:19:41 -05:00
Radek Sienkiewicz
4c6a7f84a4
docs: remove dead security README nav entry (#46675)
Merged via squash.

Prepared head SHA: 63331a54b8a6d50950a6ca85774fa1d915cd4e8d
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-15 01:40:00 +01:00
Tak Hoffman
774b40467b
fix(zalouser): stop inheriting dm allowlist for groups (#46663) 2026-03-14 19:10:11 -05:00
nmccready
f4aff83c51
feat(webchat): add toggle to hide tool calls and thinking blocks (#20317) thanks @nmccready
Merged via maintainer override after review.\n\nRed required checks are unrelated to this PR; local inspection found no blocker in the diff.
2026-03-14 19:03:04 -05:00
Tak Hoffman
e5a42c0bec
fix(feishu): keep sender-scoped thread bootstrap across id types (#46651) 2026-03-14 18:47:05 -05:00
Andrew Demczuk
92fc8065e9
fix(gateway): remove re-introduced auth.mode=none pairing bypass
The revert of #43478 (commit 39b4185d0b) was silently undone by
3704293e6f which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
2026-03-15 00:46:24 +01:00
Tomáš Dinh
b5b589d99d
fix(zalo): use plugin-sdk export for webhook client IP resolution (openclaw#46549)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Tomáš Dinh <82420070+No898@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-14 18:37:56 -05:00
Gugu-sugar
c1a0196826
Fix Codex CLI auth profile sync (#45353)
Merged via squash.

Prepared head SHA: e5432ec4e1685a78ca7251bc71f26c1f17355a15
Co-authored-by: Gugu-sugar <201366873+Gugu-sugar@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-14 16:36:09 -07:00
Andrew Demczuk
b202ac2ad1
revert: restore supportsUsageInStreaming=false default for non-native endpoints
Reverts #46500. Breaks Ollama, LM Studio, TGI, LocalAI, Mistral API -
these backends reject stream_options with 400/422.

This reverts commit bb06dc7cc9e71fbac29d7888d64323db2acec7ca.
2026-03-15 00:34:04 +01:00
George Zhang
2806f2b878
Heartbeat: add isolatedSession option for fresh session per heartbeat run (#46634)
Reuses the cron isolated session pattern (resolveCronSession with forceNew)
to give each heartbeat a fresh session with no prior conversation history.
Reduces per-heartbeat token cost from ~100K to ~2-5K tokens.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 16:28:01 -07:00
day253
9e8df16732
feat(feishu): add reasoning stream support to streaming cards (openclaw#46029)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: day253 <9634619+day253@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-14 18:23:03 -05:00
ufhy
3928b4872a
fix: persist context-engine auto-compaction counts (#42629)
Merged via squash.

Prepared head SHA: df8f292039e27edec45b8ed2ad65ab0ac7f56194
Co-authored-by: uf-hy <41638541+uf-hy@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-14 16:22:10 -07:00
Brian Qu
8a607d7553
fix(feishu): fetch thread context so AI can see bot replies in topic threads (#45254)
* fix(feishu): fetch thread context so AI can see bot replies in topic threads

When a user replies in a Feishu topic thread, the AI previously could only
see the quoted parent message but not the bot's own prior replies in the
thread. This made multi-turn conversations in threads feel broken.

- Add `threadId` (omt_xxx) to `FeishuMessageInfo` and `getMessageFeishu`
- Add `listFeishuThreadMessages()` using `container_id_type=thread` API
  to fetch all messages in a thread including bot replies
- In `handleFeishuMessage`, fetch ThreadStarterBody and ThreadHistoryBody
  for topic session modes and pass them to the AI context
- Reuse quoted message result when rootId === parentId to avoid redundant
  API calls; exclude root message from thread history to prevent duplication
- Fall back to inbound ctx.threadId when rootId is absent or API fails
- Fetch newest messages first (ByCreateTimeDesc + reverse) so long threads
  keep the most recent turns instead of the oldest

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

* fix(feishu): skip redundant thread context injection on subsequent turns

Only inject ThreadHistoryBody on the first turn of a thread session.
On subsequent turns the session already contains prior context, so
re-injecting thread history (and starter) would waste tokens.

The heuristic checks whether the current user has already sent a
non-root message in the thread — if so, the session has prior turns
and thread context injection is skipped entirely.

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

* fix(feishu): handle thread_id-only events in prior-turn detection

When ctx.rootId is undefined (thread_id-only events), the starter
message exclusion check `msg.messageId !== ctx.rootId` was always
true, causing the first follow-up to be misclassified as a prior
turn. Fall back to the first message in the chronologically-sorted
thread history as the starter.

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

* fix(feishu): bootstrap topic thread context via session state

* test(memory): pin remote embedding hostnames in offline suites

* fix(feishu): use plugin-safe session runtime for thread bootstrap

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-14 18:01:59 -05:00
George Zhang
3704293e6f
browser: drop headless/remote MCP attach modes, simplify existing-session to autoConnect-only (#46628) 2026-03-14 15:54:22 -07:00
Josh Lehman
2f7e548a57
chore: regenerate config baseline (#46598) 2026-03-14 15:44:13 -07:00
George Zhang
b1d8737017
browser: drop chrome-relay auto-creation, simplify to user profile only (#46596)
Merged via squash.

Prepared head SHA: 74becc8f7dac245a345d2c7d549f604344df33fd
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Co-authored-by: odysseus0 <8635094+odysseus0@users.noreply.github.com>
Reviewed-by: @odysseus0
2026-03-14 15:40:02 -07:00
Vincent Koc
39b4185d0b revert: 9bffa3422c4dc13f5c72ab5d2813cc287499cc14 2026-03-14 15:09:22 -07:00
Vincent Koc
173fe3cb54
feat(browser): add headless existing-session MCP support esp for Linux/Docker/VPS (#45769)
* fix(browser): prefer managed default profile in headless mode

* test(browser): cover headless default profile fallback

* feat(browser): support headless MCP profile resolution

* feat(browser): add headless and target-url Chrome MCP modes

* feat(browser): allow MCP target URLs in profile creation

* docs(browser): document headless MCP existing-session flows

* fix(browser): restore playwright browser act helpers

* fix(browser): preserve strict selector actions

* docs(changelog): add existing-session MCP note
2026-03-14 14:59:30 -07:00
Vincent Koc
92834c8440 fix(deps): update package yauzl 2026-03-14 14:35:17 -07:00
Vincent Koc
39377b7a20
UI: surface gateway restart reasons in dashboard disconnect state (#46580)
* UI: surface gateway shutdown reason

* UI: add gateway restart disconnect tests

* Changelog: add dashboard restart reason fix

* UI: cover reconnect shutdown state
2026-03-14 14:31:26 -07:00
Vincent Koc
cbec476b6b
Docs: add config drift baseline statefile (#45891)
* Docs: add config drift statefile generator

* Docs: generate config drift baseline

* CI: move config docs drift runner into workflow sanity

* Docs: emit config drift baseline json

* Docs: commit config drift baseline json

* Docs: wire config baseline into release checks

* Config: fix baseline drift walker coverage

* Docs: regenerate config drift baselines
2026-03-14 14:23:30 -07:00
Vincent Koc
432ea11248
Security: add secops ownership for sensitive paths (#46440)
* Meta: add secops ownership for sensitive paths

* Docs: restrict Codeowners-managed security edits

* Meta: guide agents away from secops-owned paths

* Meta: broaden secops CODEOWNERS coverage

* Meta: narrow secops workflow ownership
2026-03-14 14:16:14 -07:00
Tak Hoffman
e81442ac80 Fix full local gate on main 2026-03-14 15:52:11 -05:00
Andrew Demczuk
678ea77dcf
style(gateway): fix oxfmt formatting and remove unused test helper 2026-03-14 21:46:53 +01:00
Andrew Demczuk
747609d7d5
fix(node): remove debug console.log on node host startup
Fixes #46411

Fixes #46411
2026-03-14 21:17:48 +01:00
Tak Hoffman
b49e1386d0 Fix test environment regressions on main 2026-03-14 14:26:22 -05:00
Andrew Demczuk
bb06dc7cc9
fix(agents): restore usage tracking for non-native openai-completions providers
Fixes #46142

Stop forcing supportsUsageInStreaming=false on non-native openai-completions
endpoints. Most OpenAI-compatible APIs (DashScope, DeepSeek, Groq, Together,
etc.) handle stream_options: { include_usage: true } correctly. The blanket
disable broke usage/cost tracking for all non-OpenAI providers.

supportsDeveloperRole is still forced off for non-native endpoints since
the developer message role is genuinely OpenAI-specific.

Users on backends that reject stream_options can opt out with
compat.supportsUsageInStreaming: false in their model config.

Fixes #46142
2026-03-14 19:41:21 +01:00
Onur
d33f3f843a
ci: allow fallback npm correction tags (#46486) 2026-03-14 19:38:14 +01:00
Sally O'Malley
8db6fcca77
fix(gateway/cli): relax local backend self-pairing and harden launchd restarts (#46290)
Signed-off-by: sallyom <somalley@redhat.com>
2026-03-14 14:27:52 -04:00
scoootscooob
ac29edf6c3
fix(ci): update vitest configs after channel move to extensions/ (openclaw#46066)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-14 13:23:25 -05:00
Andrew Demczuk
e490f450f3
fix(auth): clear stale lockout state when user re-authenticates
Fixes #43057

* fix(auth): clear stale lockout on re-login

Clear stale `auth_permanent` and `billing` disabled state for all
profiles matching the target provider when `openclaw models auth login`
is invoked, so users locked out by expired or revoked OAuth tokens can
recover by re-authenticating instead of waiting for the cooldown timer.

Uses the agent-scoped store (`loadAuthProfileStoreForRuntime`) for
correct multi-agent profile resolution and wraps the housekeeping in
try/catch so corrupt store files never block re-authentication.

Fixes #43057

* test(auth): remove unnecessary non-null assertions

oxlint no-unnecessary-type-assertion: invocationCallOrder[0]
already returns number, not number | undefined.
2026-03-14 19:20:12 +01:00
Andrew Demczuk
9bffa3422c
fix(gateway): skip device pairing when auth.mode=none
Fixes #42931

When gateway.auth.mode is set to "none", authentication succeeds with
method "none" but sharedAuthOk remains false because the auth-context
only recognises token/password/trusted-proxy methods. This causes all
pairing-skip conditions to fail, so Control UI browser connections get
closed with code 1008 "pairing required" despite auth being disabled.

Short-circuit the skipPairing check: if the operator explicitly
disabled authentication, device pairing (which is itself an auth
mechanism) must also be bypassed.

Fixes #42931
2026-03-14 19:17:39 +01:00
Andrew Demczuk
c6e32835d4
fix(feishu): clear stale streamingStartPromise on card creation failure
Fixes #43322

* fix(feishu): clear stale streamingStartPromise on card creation failure

When FeishuStreamingSession.start() throws (HTTP 400), the catch block
sets streaming = null but leaves streamingStartPromise dangling. The
guard in startStreaming() checks streamingStartPromise first, so all
future deliver() calls silently skip streaming - the session locks
permanently.

Clear streamingStartPromise in the catch block so subsequent messages
can retry streaming instead of dropping all future replies.

Fixes #43322

* test(feishu): wrap push override in try/finally for cleanup safety
2026-03-14 19:15:49 +01:00
Andrew Demczuk
d9bc1920ed
docs: add ademczuk to maintainers list 2026-03-14 19:12:47 +01:00
Vincent Koc
c30cabcca4
Docs: sweep recent user-facing updates (#46424)
* Docs: document Telegram force-document sends

* Docs: note Telegram document send behavior

* Docs: clarify memory file precedence

* Docs: align default AGENTS memory guidance

* Docs: update workspace FAQ memory note

* Docs: document gateway status require-rpc

* Docs: add require-rpc to gateway CLI index
2026-03-14 10:20:44 -07:00
Nimrod Gutman
0e893347f6
docs(nav): move btw to end of built-in tools (#46416) 2026-03-14 19:16:57 +02:00
Vincent Koc
d039add663
Slack: preserve interactive reply blocks in DMs (#45890)
* Slack: forward reply blocks in DM delivery

* Slack: preserve reply blocks in preview finalization

* Slack: cover block-only DM replies

* Changelog: note Slack interactive reply fix
2026-03-14 10:03:06 -07:00
Nimrod Gutman
133cce23ce
fix(btw): stop persisting side questions (#46328)
* fix(btw): stop persisting side questions

* docs(btw): document side-question behavior
2026-03-14 19:01:13 +02:00
scoootscooob
d9c285e930
Fix configure startup stalls from outbound send-deps imports (#46301)
* fix: avoid configure startup plugin stalls

* fix: credit configure startup changelog entry
2026-03-14 09:58:03 -07:00
Onur
62afc4b514
ci: add manual backfill support to Docker release (#46269)
* ci: add docker release backfill workflow

* ci: add manual backfill support to docker release

* ci: keep docker latest tags off manual backfills
2026-03-14 16:36:20 +01:00
Nimrod Gutman
9aac55d306
Add /btw side questions (#45444)
* feat(agent): add /btw side questions

* fix(agent): gate and log /btw reviews

* feat(btw): isolate side-question delivery

* test(reply): update route reply runtime mocks

* fix(btw): complete side-result delivery across clients

* fix(gateway): handle streamed btw side results

* fix(telegram): unblock btw side questions

* fix(reply): make external btw replies explicit

* fix(chat): keep btw side results ephemeral in internal history

* fix(btw): address remaining review feedback

* fix(chat): preserve btw history on mobile refresh

* fix(acp): keep btw replies out of prompt history

* refactor(btw): narrow side questions to live channels

* fix(btw): preserve channel typing indicators

* fix(btw): keep side questions isolated in chat

* fix(outbound): restore typed channel send deps

* fix(btw): avoid blocking replies on transcript persistence

* fix(btw): keep side questions fast

* docs(commands): document btw slash command

* docs(changelog): add btw side questions entry

* test(outbound): align session transcript mocks
2026-03-14 17:27:54 +02:00
Onur
b5ba2101c7
ci: move Docker release to GitHub-hosted runners (#46247)
* ci: move docker release to GitHub-hosted runners

* ci: annotate docker release runner guardrails
2026-03-14 15:54:06 +01:00
Onur Solmaz
c08317203d ci: enforce calver freshness on npm publish 2026-03-14 13:45:40 +01:00
Onur Solmaz
5c9fae5adc chore: add code owners for npm release paths 2026-03-14 13:45:40 +01:00
Onur Solmaz
00891dee90 ci: switch npm release workflow to trusted publishing 2026-03-14 13:45:40 +01:00
Onur Solmaz
61a7f2e7c3 docs: clarify npm release preview and publish flow 2026-03-14 13:45:40 +01:00
Onur Solmaz
02a86da23a ci: preserve manual npm release approval delays 2026-03-14 13:45:40 +01:00
Onur Solmaz
2eea93982f ci: make npm release preview more verbose 2026-03-14 13:45:40 +01:00
Onur Solmaz
78d2bfc4d8 ci: add dry-run gate to npm release workflow 2026-03-14 13:45:40 +01:00
Radek Sienkiewicz
2fad7b823e
Update CONTRIBUTING.md 2026-03-14 12:43:53 +01:00
thepagent
0ee11d3321
feat: add --force-document to message.send for Telegram (bypass sendPhoto + image optimizer) (#45111)
* feat: add --force-document to message.send for Telegram

Adds --force-document CLI flag to bypass sendPhoto and use sendDocument
instead, avoiding Telegram image compression for PNG/image files.

- TelegramSendOpts: add forceDocument field
- send.ts: skip sendPhoto when forceDocument=true (mediaSender pattern)
- ChannelOutboundContext: add forceDocument field
- telegramOutbound.sendMedia: pass forceDocument to sendMessageTelegram
- ChannelHandlerParams / DeliverOutboundPayloadsCoreParams: add forceDocument
- createChannelOutboundContextBase: propagate forceDocument
- outbound-send-service.ts: add forceDocument to executeSendAction params
- message-action-runner.ts: read forceDocument from params
- message.ts: add forceDocument to MessageSendParams
- register.send.ts: add --force-document CLI option

* fix: pass forceDocument through telegram action dispatch path

The actual send path goes through dispatchChannelMessageAction ->
telegramMessageActions.handleAction -> handleTelegramAction, not
deliverOutboundPayloads. forceDocument was not being read in
readTelegramSendParams or passed to sendMessageTelegram.

* fix: apply forceDocument to GIF branch to avoid sendAnimation

* fix: add disable_content_type_detection=true to sendDocument for --force-document

* fix: add forceDocument to buildSendSchema for agent discoverability

* fix: scope telegram force-document detection

* test: fix heartbeat target helper typing

* fix: skip image optimization when forceDocument is set

* fix: persist forceDocument in WAL queue for crash-recovery replay

* test: tighten heartbeat target test entry typing

---------

Co-authored-by: thepagent <thepagent@users.noreply.github.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-03-14 19:43:49 +08:00
luzhidong
40c81e9cd3
fix(ui): session dropdown shows label instead of key (#45130)
Merged via squash.

Prepared head SHA: 0255e3971b06b3641e6b26590eaa22a900079517
Co-authored-by: luzhidong <15848762+luzhidong@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-14 14:36:46 +03:00
Ayaan Zaidi
64e6df7eea
docs: mark memory bootstrap change as breaking 2026-03-14 16:55:32 +05:30
Ayaan Zaidi
c79c4ffbfb
fix(zai): align explicit coding endpoint setup with detected model defaults (#45969)
* fix: align Z.AI coding onboarding with endpoint docs

* fix: align Z.AI coding onboarding with endpoint docs (#45969)
2026-03-14 16:20:37 +05:30
scoootscooob
439c21e078
refactor: remove channel shim directories, point all imports to extensions (#45967)
* refactor: remove channel shim directories, point all imports to extensions

Delete the 6 backward-compat shim directories (src/telegram, src/discord,
src/slack, src/signal, src/imessage, src/web) that were re-exporting from
extensions. Update all 112+ source files to import directly from
extensions/{channel}/src/ instead of through the shims.

Also:
- Move src/channels/telegram/ (allow-from, api) to extensions/telegram/src/
- Fix outbound adapters to use resolveOutboundSendDep (fixes 5 pre-existing TS errors)
- Update cross-extension imports (src/web/media.js → extensions/whatsapp/src/media.js)
- Update vitest, tsdown, knip, labeler, and script configs for new paths
- Update guard test allowlists for extension paths

After this, src/ has zero channel-specific implementation code — only the
generic plugin framework remains.

* fix: update raw-fetch guard allowlist line numbers after shim removal

* refactor: document direct extension channel imports

* test: mock transcript module in delivery helpers
2026-03-14 03:43:07 -07:00
scoootscooob
5682ec37fa
refactor: move Discord channel implementation to extensions/ (#45660)
* refactor: move Discord channel implementation to extensions/discord/src/

Move all Discord source files from src/discord/ to extensions/discord/src/,
following the extension migration pattern. Source files in src/discord/ are
replaced with re-export shims. Channel-plugin files from
src/channels/plugins/*/discord* are similarly moved and shimmed.

- Copy all .ts source files preserving subdirectory structure (monitor/, voice/)
- Move channel-plugin files (actions, normalize, onboarding, outbound, status-issues)
- Fix all relative imports to use correct paths from new location
- Create re-export shims at original locations for backward compatibility
- Delete test files from shim locations (tests live in extension now)
- Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to accommodate
  extension files outside src/
- Update write-plugin-sdk-entry-dts.ts to match new declaration output paths

* fix: add importOriginal to thread-bindings session-meta mock for extensions test

* style: fix formatting in thread-bindings lifecycle test
2026-03-14 02:53:57 -07:00
scoootscooob
e5bca0832f
refactor: move Telegram channel implementation to extensions/ (#45635)
* refactor: move Telegram channel implementation to extensions/telegram/src/

Move all Telegram channel code (123 files + 10 bot/ files + 8 channel plugin
files) from src/telegram/ and src/channels/plugins/*/telegram.ts to
extensions/telegram/src/. Leave thin re-export shims at original locations so
cross-cutting src/ imports continue to resolve.

- Fix all relative import paths in moved files (../X/ -> ../../../src/X/)
- Fix vi.mock paths in 60 test files
- Fix inline typeof import() expressions
- Update tsconfig.plugin-sdk.dts.json rootDir to "." for cross-directory DTS
- Update write-plugin-sdk-entry-dts.ts for new rootDir structure
- Move channel plugin files with correct path remapping

* fix: support keyed telegram send deps

* fix: sync telegram extension copies with latest main

* fix: correct import paths and remove misplaced files in telegram extension

* fix: sync outbound-adapter with main (add sendTelegramPayloadMessages) and fix delivery.test import path
2026-03-14 02:50:17 -07:00
scoootscooob
8746362f5e
refactor(slack): move Slack channel code to extensions/slack/src/ (#45621)
Move all Slack channel implementation files from src/slack/ to
extensions/slack/src/ and replace originals with shim re-exports.
This follows the extension migration pattern for channel plugins.

- Copy all .ts files to extensions/slack/src/ (preserving directory
  structure: monitor/, http/, monitor/events/, monitor/message-handler/)
- Transform import paths: external src/ imports use relative paths
  back to src/, internal slack imports stay relative within extension
- Replace all src/slack/ files with shim re-exports pointing to
  the extension copies
- Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." so
  the DTS build can follow shim chains into extensions/
- Update write-plugin-sdk-entry-dts.ts re-export path accordingly
- Preserve extensions/slack/index.ts, package.json, openclaw.plugin.json,
  src/channel.ts, src/runtime.ts, src/channel.test.ts (untouched)
2026-03-14 02:47:04 -07:00
scoootscooob
16505718e8
refactor: move WhatsApp channel implementation to extensions/ (#45725)
* refactor: move WhatsApp channel from src/web/ to extensions/whatsapp/

Move all WhatsApp implementation code (77 source/test files + 9 channel
plugin files) from src/web/ and src/channels/plugins/*/whatsapp* to
extensions/whatsapp/src/.

- Leave thin re-export shims at all original locations so cross-cutting
  imports continue to resolve
- Update plugin-sdk/whatsapp.ts to only re-export generic framework
  utilities; channel-specific functions imported locally by the extension
- Update vi.mock paths in 15 cross-cutting test files
- Rename outbound.ts -> send.ts to match extension naming conventions
  and avoid false positive in cfg-threading guard test
- Widen tsconfig.plugin-sdk.dts.json rootDir to support shim->extension
  cross-directory references

Part of the core-channels-to-extensions migration (PR 6/10).

* style: format WhatsApp extension files

* fix: correct stale import paths in WhatsApp extension tests

Fix vi.importActual, test mock, and hardcoded source paths that weren't
updated during the file move:
- media.test.ts: vi.importActual path
- onboarding.test.ts: vi.importActual path
- test-helpers.ts: test/mocks/baileys.js path
- monitor-inbox.test-harness.ts: incomplete media/store mock
- login.test.ts: hardcoded source file path
- message-action-runner.media.test.ts: vi.mock/importActual path
2026-03-14 02:44:55 -07:00
scoootscooob
0ce23dc62d
refactor: move iMessage channel to extensions/imessage (#45539) 2026-03-14 02:44:23 -07:00
scoootscooob
4540c6b3bc
refactor(signal): move Signal channel code to extensions/signal/src/ (#45531)
Move all Signal channel implementation files from src/signal/ to
extensions/signal/src/ and replace originals with re-export shims.
This continues the channel plugin migration pattern used by other
extensions, keeping backward compatibility via shims while the real
code lives in the extension.

- Copy 32 .ts files (source + tests) to extensions/signal/src/
- Transform all relative import paths for the new location
- Create 2-line re-export shims in src/signal/ for each moved file
- Preserve existing extension files (channel.ts, runtime.ts, etc.)
- Change tsconfig.plugin-sdk.dts.json rootDir from "src" to "."
  to support cross-boundary re-exports from extensions/
2026-03-14 02:42:48 -07:00
scoootscooob
7764f717e9
refactor: make OutboundSendDeps dynamic with channel-ID keys (#45517)
* refactor: make OutboundSendDeps dynamic with channel-ID keys

Replace hardcoded per-channel send fields (sendTelegram, sendDiscord,
etc.) with a dynamic index-signature type keyed by channel ID. This
unblocks moving channel implementations to extensions without breaking
the outbound dispatch contract.

- OutboundSendDeps and CliDeps are now { [channelId: string]: unknown }
- Each outbound adapter resolves its send fn via bracket access with cast
- Lazy-loading preserved via createLazySender with module cache
- Delete 6 deps-send-*.runtime.ts one-liner re-export files
- Harden guardrail scan against deleted-but-tracked files


* fix: preserve outbound send-deps compatibility

* style: fix formatting issues (import order, extra bracket, trailing whitespace)



* fix: resolve type errors from dynamic OutboundSendDeps in tests and extension

* fix: remove unused OutboundSendDeps import from deliver.test-helpers
2026-03-14 02:42:21 -07:00
Teconomix
0c926a2c5e
fix(mattermost): carry thread context to non-inbound reply paths (#44283)
Merged via squash.

Prepared head SHA: 2846a6cfa959019d3ed811ccafae6b757db3bdf3
Co-authored-by: teconomix <6959299+teconomix@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-14 12:23:23 +05:30
Peter Steinberger
17cb60080a test(ci): isolate cron heartbeat delivery cases 2026-03-14 06:28:58 +00:00
Darshil
61bf7b8536 fix: annotate shared failover mocks (openclaw#39820) thanks @lupuletic 2026-03-13 23:25:04 -07:00
Darshil
dd6ecd5bfa fix: tighten runner failover test types (openclaw#39820) thanks @lupuletic 2026-03-13 23:25:04 -07:00
Darshil
105dcd69e7 style: format probe regression test (openclaw#39820) thanks @lupuletic 2026-03-13 23:25:04 -07:00
Darshil
e403ed6546 fix: harden wrapped rate-limit failover (openclaw#39820) thanks @lupuletic 2026-03-13 23:25:04 -07:00
Catalin Lupuleti
c1c74f9952 fix: move cause-chain traversal before timeout heuristic (review feedback) 2026-03-13 23:25:04 -07:00
Catalin Lupuleti
dac220bd88 fix(agents): normalize abort-wrapped RESOURCE_EXHAUSTED into failover errors (#11972) 2026-03-13 23:25:04 -07:00
Peter Steinberger
2f5d3b6574
build: refresh lockfile for plugin sync 2026-03-14 06:10:06 +00:00
Peter Steinberger
49a2ff7d01
build: sync plugins for 2026.3.14 2026-03-14 06:05:39 +00:00
Peter Steinberger
be8fc3399e
build: prepare 2026.3.14 cycle 2026-03-14 06:02:01 +00:00
Peter Steinberger
6e251dcf68
test: harden parallels beta smoke flows 2026-03-14 05:54:49 +00:00
kkhomej33-netizen
e7d9648fba
feat(cron): support custom session IDs and auto-bind to current session (#16511)
feat(cron): support persistent session targets for cron jobs (#9765)

Add support for `sessionTarget: "current"` and `session:<id>` so cron jobs can
bind to the creating session or a persistent named session instead of only
`main` or ephemeral `isolated` sessions.

Also:
- preserve custom session targets across reloads and restarts
- update gateway validation and normalization for the new target forms
- add cron coverage for current/custom session targets and fallback behavior
- fix merged CI regressions in Discord and diffs tests
- add a changelog entry for the new cron session behavior

Co-authored-by: kkhomej33-netizen <kkhomej33-netizen@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
2026-03-14 16:48:46 +11:00
Peter Steinberger
61d171ab0b
fix(browser): restore batch playwright dispatch 2026-03-14 05:34:37 +00:00
Peter Steinberger
32dcae9d01
chore: update appcast for 2026.3.13 release 2026-03-14 05:34:37 +00:00
Ayaan Zaidi
2ae8837987
fix: keep android canvas home visible after restart 2026-03-14 11:03:02 +05:30
Peter Steinberger
f6e5b6758e
build: prepare 2026.3.13 release 2026-03-14 05:19:23 +00:00
Vincent Koc
a6bdf2dfd0 Revert "Browser: scope nested batch failures in switch"
This reverts commit aaeb348bb7cbbaebe14a471776909bff129499a3.
2026-03-13 22:17:57 -07:00
Vincent Koc
aa0cb4ef01 Merge remote-tracking branch 'origin/main'
* origin/main:
  fix(gateway): bound unanswered client requests (#45689)
2026-03-13 22:14:51 -07:00
Vincent Koc
81ecae9d7a Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw: (640 commits)
  ci: add npm token fallback for npm releases
  build: prepare 2026.3.13-beta.1
  docs: reorder unreleased changelog by impact
  fix: keep windows onboarding logs ascii-safe
  test: harden parallels all-os smoke harness
  chore: bump pi to 0.58.0
  fix(browser): prefer user profile over chrome relay
  build: upload Android native debug symbols
  Gateway: treat scope-limited probe RPC as degraded reachability (#45622)
  build: shrink Android app release bundle
  fix: keep exec summaries inline
  docs: fix changelog formatting
  test(discord): align rate limit error mock with carbon
  build(android): strip unused dnsjava resolver service before R8
  build(android): add auto-bump signed aab release script
  fix(browser): add browser session selection
  fix(models): apply Gemini model-id normalization to google-vertex provider (#42435)
  fix(feishu): add early event-level dedup to prevent duplicate replies (#43762)
  fix: unblock discord startup on deploy rate limits
  fix: default Android TLS setup codes to port 443
  ...

# Conflicts:
#	src/browser/pw-tools-core.interactions.batch.test.ts
#	src/browser/pw-tools-core.interactions.ts
2026-03-13 22:13:33 -07:00
Tak Hoffman
5fc43ff0ec
fix(gateway): bound unanswered client requests (#45689)
* fix(gateway): bound unanswered client requests

* fix(gateway): skip default timeout for expectFinal requests

* fix(gateway): preserve gateway call timeouts

* fix(gateway): localize request timeout policy

* fix(gateway): clamp explicit request timeouts

* fix(gateway): clamp default request timeout
2026-03-14 00:12:43 -05:00
Peter Steinberger
bc3319207c
ci: add npm token fallback for npm releases 2026-03-14 05:08:19 +00:00
Peter Steinberger
94a292686c
build: prepare 2026.3.13-beta.1 2026-03-14 04:56:02 +00:00
Peter Steinberger
4f3ed8f4ab
docs: reorder unreleased changelog by impact 2026-03-14 04:50:36 +00:00
Peter Steinberger
ad65778818
fix: keep windows onboarding logs ascii-safe 2026-03-14 04:46:47 +00:00
Peter Steinberger
7e41ba4cbb
test: harden parallels all-os smoke harness 2026-03-14 04:46:47 +00:00
Peter Steinberger
2ce6b77205
chore: bump pi to 0.58.0 2026-03-14 04:33:37 +00:00
Peter Steinberger
b6d1d0d72d
fix(browser): prefer user profile over chrome relay 2026-03-14 04:15:34 +00:00
Ayaan Zaidi
1f9cc647f8
build: upload Android native debug symbols 2026-03-14 09:44:31 +05:30
Josh Avant
f4fef64fc1
Gateway: treat scope-limited probe RPC as degraded reachability (#45622)
* Gateway: treat scope-limited probe RPC as degraded

* Docs: clarify gateway probe degraded scope output

* test: fix CI type regressions in gateway and outbound suites

* Tests: fix Node24 diffs theme loading and Windows assertions

* Tests: fix extension typing after main rebase

* Tests: fix Windows CI regressions after rebase

* Tests: normalize executable path assertions on Windows

* Tests: remove duplicate gateway daemon result alias

* Tests: stabilize Windows approval path assertions

* Tests: fix Discord rate-limit startup fixture typing

* Tests: use Windows-friendly relative exec fixtures

---------

Co-authored-by: Mainframe <mainframe@MainfraacStudio.localdomain>
2026-03-13 23:13:33 -05:00
Ayaan Zaidi
f251e7e2c2
build: shrink Android app release bundle 2026-03-14 09:39:33 +05:30
Peter Steinberger
70459e7fec
fix: keep exec summaries inline 2026-03-14 04:08:00 +00:00
Muhammed Mukhthar CM
a142853032 docs: fix changelog formatting 2026-03-14 04:03:33 +00:00
Muhammed Mukhthar CM
a4a5fdcd98 test(discord): align rate limit error mock with carbon 2026-03-14 04:01:42 +00:00
Ayaan Zaidi
f1d9fcd407
build(android): strip unused dnsjava resolver service before R8 2026-03-14 09:25:17 +05:30
Ayaan Zaidi
3fb629219e
build(android): add auto-bump signed aab release script 2026-03-14 09:25:17 +05:30
Peter Steinberger
5c40c1c78a
fix(browser): add browser session selection 2026-03-14 03:46:44 +00:00
scoootscooob
b857a8d8bc
fix(models): apply Gemini model-id normalization to google-vertex provider (#42435)
* fix(models): apply Gemini model-id normalization to google-vertex provider

The existing normalizeGoogleModelId() (which maps e.g. gemini-3.1-flash-lite
to gemini-3.1-flash-lite-preview) was only applied when the provider was
"google". Users configuring google-vertex/gemini-3.1-flash-lite would get
a "missing" model because the -preview suffix was never appended.

Extend the normalization to google-vertex in both model-selection
(parseModelRef path) and normalizeProviders (config normalization path).

Ref: https://github.com/openclaw/openclaw/issues/36838
Ref: https://github.com/openclaw/openclaw/pull/36918#issuecomment-4032732959


* fix(models): normalize google-vertex flash-lite

* fix(models): place unreleased changelog entry last

* fix(models): place unreleased changelog entry before releases
2026-03-13 20:45:34 -07:00
yunweibang
f4a2bbe0c9
fix(feishu): add early event-level dedup to prevent duplicate replies (#43762)
* fix(feishu): add early event-level dedup to prevent duplicate replies

Add synchronous in-memory dedup at EventDispatcher handler level using
message_id as key with 5-minute TTL and 2000-entry cap.

This catches duplicate events immediately when they arrive from the Lark
SDK — before the inbound debouncer or processing queue — preventing the
race condition where two concurrent dispatches enter the pipeline before
either records the messageId in the downstream dedup layer.

Fixes the root cause reported in #42687.

* fix(feishu): correct inverted dedup condition

check() returns false on first call (new key) and true on subsequent
calls (duplicate). The previous `!check()` guard was inverted —
dropping every first delivery and passing all duplicates.

Remove the negation so the guard correctly drops duplicates.

* fix(feishu): simplify eventDedup key — drop redundant accountId prefix

eventDedup is already scoped per account (one instance per
registerEventHandlers call), so the accountId prefix in the cache key
is redundant. Use `evt:${messageId}` instead.

* fix(feishu): share inbound processing claim dedupe

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-13 22:37:40 -05:00
Peter Steinberger
2659fc6c97 fix: unblock discord startup on deploy rate limits 2026-03-14 03:31:50 +00:00
Ayaan Zaidi
df765f602b
fix: default Android TLS setup codes to port 443 2026-03-14 08:54:01 +05:30
Peter Steinberger
8bc163d15f fix(ci): repair helper typing regressions 2026-03-14 03:22:53 +00:00
George Zhang
eee5d7c6b0
fix(browser): harden existing-session driver validation and session lifecycle (#45682)
* fix(browser): harden existing-session driver validation, session lifecycle, and code quality

Fix config validation rejecting existing-session profiles that lack
cdpPort/cdpUrl (they use Chrome MCP auto-connect instead). Fix callTool
tearing down the MCP session on tool-level errors (element not found,
script error), which caused expensive npx re-spawns. Skip unnecessary
CDP port allocation for existing-session profiles. Remove redundant
ensureChromeMcpAvailable call in isReachable.

Extract shared ARIA role sets (INTERACTIVE_ROLES, CONTENT_ROLES,
STRUCTURAL_ROLES) into snapshot-roles.ts so both the Playwright and
Chrome MCP snapshot paths stay in sync. Add usesChromeMcp capability
flag and replace ~20 scattered driver === "existing-session" string
checks with the centralized flag.

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

* fix(browser): harden existing-session driver validation and session lifecycle (#45682) (thanks @odysseus0)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 20:21:47 -07:00
Frank Yang
01674c575e
fix(agents): preserve blank local custom-provider API keys after onboarding
Co-authored-by: Xinhua Gu <xinhua.gu@gmail.com>
2026-03-14 11:08:19 +08:00
Luke
bed661609e
fix(macos): align minimum Node.js version with runtime guard (22.16.0) (#45640)
* macOS: align minimum Node.js version with runtime guard

* macOS: add boundary and failure-message coverage for RuntimeLocator

* docs: add changelog note for the macOS runtime locator fix

* credit: original fix direction from @sumleo, cleaned up and rebased in #45640 by @ImLukeF
2026-03-14 13:43:21 +11:00
Peter Steinberger
66e02b296f test: share memory search config helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
c5d905871f test: share oauth profile fixtures 2026-03-14 02:40:28 +00:00
Peter Steinberger
6720bf5be0 refactor: share exec host approval helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
3bc9d9177d test: share workspace skill test helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
6ad675c1e9 test: share subagent announce timeout helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
95b4132674 test: share provider discovery auth fixtures 2026-03-14 02:40:28 +00:00
Peter Steinberger
e474ac882e test: share model selection config helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
0e6f150c3b test: share timeout failover assertions 2026-03-14 02:40:28 +00:00
Peter Steinberger
dfcc2fae9f test: share context lookup helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
f0179d3b4a test: share workspace skills snapshot helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
8622395c8b test: share models config merge helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
7aedb6d442 test: share subagent gateway mock setup 2026-03-14 02:40:28 +00:00
Peter Steinberger
013ad58f3c test: share sandbox fs bridge seeded workspace 2026-03-14 02:40:28 +00:00
Peter Steinberger
6a61d5504c refactor: share extension deferred and runtime helpers 2026-03-14 02:40:28 +00:00
Peter Steinberger
1ac4bac8b1 refactor: share extension monitor runtime setup 2026-03-14 02:40:28 +00:00
Peter Steinberger
6decaebcf2 test: share plugin api test harness 2026-03-14 02:40:27 +00:00
Peter Steinberger
c3e78908c7 test: share feishu startup mock modules 2026-03-14 02:40:27 +00:00
Peter Steinberger
97dc493e2a refactor: share extension channel status summaries 2026-03-14 02:40:27 +00:00
Peter Steinberger
e885f1999f refactor: reduce extension channel setup duplication 2026-03-14 02:40:27 +00:00
Peter Steinberger
74e50d3be3 test: share send cfg threading helpers 2026-03-14 02:40:27 +00:00
Peter Steinberger
55ebdce9c3 refactor: share open allowFrom config checks 2026-03-14 02:40:27 +00:00
Peter Steinberger
38b09866b8 test: share directory runtime helpers 2026-03-14 02:40:27 +00:00
Ayaan Zaidi
8410d5a050
feat: add node-connect skill 2026-03-14 07:54:11 +05:30
Vincent Koc
bcbfbb831e
Plugins: fail fast on channel and binding collisions (#45628)
* Plugins: reject duplicate channel ids

* Bindings: reject duplicate adapter registration

* Plugins: fail on export id mismatch
2026-03-13 19:13:35 -07:00
Peter Steinberger
27e863ce40
chore: update dependencies 2026-03-14 02:09:53 +00:00
Peter Steinberger
10afde99c1 fix: harden discord guild allowlist resolution 2026-03-14 02:09:19 +00:00
2233admin
5c73ed62d5
fix(sessions): create transcript file on chat.inject when missing (#36645)
`chat.inject` called `appendAssistantTranscriptMessage` with
`createIfMissing: false`, causing a hard error when the transcript
file did not exist on disk despite having a valid `transcriptPath`
in session metadata. This commonly happens with ACP oneshot/run
sessions where the session entry is created but the transcript file
is not yet materialized.

The fix is a one-character change: `createIfMissing: true`. The
`ensureTranscriptFile` helper already handles directory creation
and file initialization safely.

Fixes #36170

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 03:00:24 +01:00
Peter Steinberger
d925b0113f
test: add parallels linux smoke harness 2026-03-14 01:56:24 +00:00
Peter Steinberger
965bdb2d2d
fix: harden gateway status rpc smoke 2026-03-14 01:56:24 +00:00
ImLukeF
200625b340
docs(changelog): note voice wake crash fix 2026-03-14 12:48:51 +11:00
ImLukeF
17bd36bf4d
refactor(voicewake): mark transcript parameter unused 2026-03-14 12:48:12 +11:00
ImLukeF
66cb015bb4
fix(voicewake): avoid crash on foreign transcript ranges 2026-03-14 12:48:12 +11:00
Vincent Koc
8b82a0124d Changelog: credit embedded runner queue deadlock fix 2026-03-13 18:47:47 -07:00
Peter Steinberger
9cfc2d4618 refactor: share request url resolution 2026-03-14 01:41:17 +00:00
Peter Steinberger
757077d028 test: share memory tool helpers 2026-03-14 01:41:17 +00:00
Peter Steinberger
42d6e35cb4 refactor: share session tool context setup 2026-03-14 01:41:17 +00:00
Peter Steinberger
d9a604f15f test: share web fetch header helpers 2026-03-14 01:41:17 +00:00
Peter Steinberger
231589ef66 fix: restore imessage control command flag 2026-03-14 01:41:17 +00:00
Peter Steinberger
258945d4d0 test: share status issue assertion helpers 2026-03-14 01:41:17 +00:00
Peter Steinberger
0acd1f63fc test: share startup account lifecycle helpers 2026-03-14 01:41:17 +00:00
Peter Steinberger
b61bc4948e refactor: share dual text command gating 2026-03-14 01:41:17 +00:00
Peter Steinberger
91d9573b55 refactor: declone model picker model ref parsing 2026-03-14 01:41:17 +00:00
Peter Steinberger
c0831927b0 refactor: share allowlist wildcard matching 2026-03-14 01:41:17 +00:00
Peter Steinberger
f4094ab19e refactor: share slack text truncation 2026-03-14 01:41:17 +00:00
Peter Steinberger
d886ca6474 fix: widen telegram reply progress typing 2026-03-14 01:41:17 +00:00
Peter Steinberger
5b53481d1d refactor: share daemon install cli setup 2026-03-14 01:41:17 +00:00
Peter Steinberger
5197171d7a refactor: share telegram reply chunk threading 2026-03-14 01:41:17 +00:00
Peter Steinberger
66de7311c7 test: share whatsapp outbound poll fixtures 2026-03-14 01:41:17 +00:00
Peter Steinberger
1ec6b012f8 refactor: share zalo status issue helpers 2026-03-14 01:41:17 +00:00
Peter Steinberger
7285e04ead refactor: share whatsapp outbound adapter base 2026-03-14 01:41:17 +00:00
Peter Steinberger
d4b193b581 test: share embedded workspace attempt helpers 2026-03-14 01:41:17 +00:00
Peter Steinberger
fb93acb046 test: share compaction retry timer helpers 2026-03-14 01:41:16 +00:00
Peter Steinberger
88de4769de refactor: share agent tool fixture helpers 2026-03-14 01:41:16 +00:00
Peter Steinberger
6e3f0f9fcb refactor: share tool result char estimation 2026-03-14 01:41:16 +00:00
Peter Steinberger
0db62fc6c5 refactor: share pinned sandbox entry finalization 2026-03-14 01:41:16 +00:00
Peter Steinberger
414e9c87cb refactor: share browser console result formatting 2026-03-14 01:41:16 +00:00
Peter Steinberger
997256d370 refactor: share memory tool builders 2026-03-14 01:41:16 +00:00
Peter Steinberger
d7637d3a19 refactor: share session send context lines 2026-03-14 01:41:16 +00:00
Peter Steinberger
4e055d8df2 refactor: share gateway timeout parsing 2026-03-14 01:41:16 +00:00
Peter Steinberger
d1fda7b8f2 refactor: share tts request setup 2026-03-14 01:41:16 +00:00
Peter Steinberger
f7f5c24786 refactor: share terminal note wrapping 2026-03-14 01:41:16 +00:00
Peter Steinberger
827b166bbc refactor: share zalo send context validation 2026-03-14 01:41:16 +00:00
Peter Steinberger
d55fa78e40 refactor: share delimited channel entry parsing 2026-03-14 01:41:16 +00:00
Peter Steinberger
e8a80cfbd8 refactor: share onboarding diagnostics type 2026-03-14 01:41:16 +00:00
Peter Steinberger
487e188112 test: share outbound delivery helpers 2026-03-14 01:41:16 +00:00
Peter Steinberger
81ea997d40 refactor: share self hosted provider plugin helpers 2026-03-14 01:40:41 +00:00
Peter Steinberger
66aabf5eaa test: share telegram monitor startup helpers 2026-03-14 01:40:41 +00:00
Peter Steinberger
3850ea1e0f test: share outbound action runner helpers 2026-03-14 01:40:41 +00:00
Peter Steinberger
8de2f7339c test: fix current ci regressions 2026-03-14 01:29:04 +00:00
Jaehoon You
2bfe188510
fix(macos): prevent PortGuard from killing Docker Desktop in remote mode (#13798)
fix(macos): prevent PortGuardian from killing Docker Desktop in remote mode (#6755)

PortGuardian.sweep() was killing non-SSH processes holding the gateway
port in remote mode. When the gateway runs in a Docker container,
`com.docker.backend` owns the port-forward, so this could shut down
Docker Desktop entirely.

Changes:
- accept any process on the gateway port in remote mode
- add a defense-in-depth guard to skip kills in remote mode
- update remote-mode port diagnostics/reporting to match
- add regression coverage for Docker and local-mode behavior
- add a changelog entry for the fix

Co-Authored-By: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
2026-03-14 12:26:09 +11:00
Sally O'Malley
e5fe818a74
fix(gateway/ui): restore control-ui auth bypass and classify connect failures (#45512)
Merged via squash.

Prepared head SHA: 42b5595edec71897b479b3bbaa94bcb4ac6fab17
Co-authored-by: sallyom <11166065+sallyom@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev
2026-03-13 20:13:35 -05:00
Peter Steinberger
19edeb1aeb test: tighten node shell platform normalization 2026-03-14 01:05:46 +00:00
Peter Steinberger
e3637253ef test: tighten target error hint trimming 2026-03-14 01:05:04 +00:00
Peter Steinberger
604203c179 fix: tighten pairing token blank handling 2026-03-14 01:04:18 +00:00
Peter Steinberger
5ef458ca56 test: tighten openclaw exec env coverage 2026-03-14 01:03:24 +00:00
Val Alexander
40ab39b5ea
fix(ui): keep oversized chat replies readable (#45559)
* fix(ui): keep oversized chat replies readable

* Update ui/src/ui/markdown.ts

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

* fix(ui): preserve oversized markdown whitespace

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-13 20:03:19 -05:00
Peter Steinberger
89e52d6178 test: tighten hostname normalization coverage 2026-03-14 01:02:20 +00:00
Peter Steinberger
2351caa9cf test: tighten prototype key matching 2026-03-14 01:01:27 +00:00
Peter Steinberger
0146345b88 fix: tighten target error hint coverage 2026-03-14 01:00:51 +00:00
Steven
25f458a907
macOS: respect exec-approvals.json settings in gateway prompter (#13707)
Fix macOS gateway exec approvals to respect exec-approvals.json.

This updates the macOS gateway prompter to resolve per-agent exec approval policy before deciding whether to show UI, use agentId for policy lookup, honor askFallback when prompts cannot be presented, and resolve no-prompt decisions from the configured security policy instead of hardcoded allow-once behavior. It also adds regression coverage for ask-policy and allowlist-fallback behavior, plus a changelog entry for the fix.

Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
2026-03-14 12:00:15 +11:00
Peter Steinberger
1aca4c7b87 test: tighten outbound session context coverage 2026-03-14 00:59:56 +00:00
Peter Steinberger
cbd264f33d test: tighten outbound identity normalization 2026-03-14 00:59:03 +00:00
Peter Steinberger
8dab4a48c4 test: tighten package tag prefix matching 2026-03-14 00:58:12 +00:00
Peter Steinberger
4d523f4e19 test: tighten node list parse fallback coverage 2026-03-14 00:57:29 +00:00
Peter Steinberger
91f725a998 test: tighten update channel display precedence 2026-03-14 00:56:42 +00:00
Peter Steinberger
9050aa9efd test: tighten channel activity account isolation 2026-03-14 00:55:32 +00:00
Peter Steinberger
a23a23ba69 test: tighten bonjour ciao coverage 2026-03-14 00:54:42 +00:00
Peter Steinberger
9fbb7eb2e1
docs(changelog): note upcoming security fixes 2026-03-14 00:54:19 +00:00
Peter Steinberger
70d6217dbe test: tighten backoff abort coverage 2026-03-14 00:53:47 +00:00
Peter Steinberger
e794417623 fix: resolve current ci regressions 2026-03-14 00:51:12 +00:00
Peter Steinberger
17eaa59a7a test: tighten json file helper coverage 2026-03-14 00:44:12 +00:00
Peter Steinberger
958a2f31da test: tighten is-main helper coverage 2026-03-14 00:43:19 +00:00
fabiaodemianyang
983fecc106
fix(feishu): preserve non-ASCII filenames in file uploads (#33912) (#34262)
* fix(feishu): preserve non-ASCII filenames in file uploads (#33912)

* style(feishu): format media test file

* fix(feishu): preserve UTF-8 filenames in file uploads (openclaw#34262) thanks @fabiaodemianyang

---------

Co-authored-by: Robin Waslander <r.waslander@gmail.com>
2026-03-14 01:42:46 +01:00
Peter Steinberger
2083b0581d test: tighten system run command normalization coverage 2026-03-14 00:42:13 +00:00
Peter Steinberger
576134ec73 test: tighten wsl detection coverage 2026-03-14 00:41:22 +00:00
Peter Steinberger
4eb279036a test: tighten warning filter coverage 2026-03-14 00:40:23 +00:00
Peter Steinberger
9984e83d1e test: tighten path guard helper coverage 2026-03-14 00:39:27 +00:00
Peter Steinberger
7621589ba2 test: tighten proxy fetch helper coverage 2026-03-14 00:38:43 +00:00
Peter Steinberger
482fdd8c05
docs: reorder changelog highlights by user impact 2026-03-14 00:37:56 +00:00
Peter Steinberger
226c1be964 fix: tighten bonjour whitespace error coverage 2026-03-14 00:36:31 +00:00
Peter Steinberger
701bed85f8 test: share models list forward compat fixtures 2026-03-14 00:35:07 +00:00
Peter Steinberger
a6385091e0 test: share gateway status auth fixtures 2026-03-14 00:35:07 +00:00
Peter Steinberger
dcbc574a27 test: share browser route test helpers 2026-03-14 00:35:07 +00:00
Peter Steinberger
4523260dda test: share gateway route auth helpers 2026-03-14 00:35:07 +00:00
Peter Steinberger
727fc79ed2
fix: force-stop lingering gateway client sockets 2026-03-14 00:33:39 +00:00
Peter Steinberger
4dbab064f0
test: add parallels windows smoke harness 2026-03-14 00:33:39 +00:00
Peter Steinberger
767609532f test: tighten system run command coverage 2026-03-14 00:30:20 +00:00
Peter Steinberger
f806b07208 refactor: share cli install helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
97aa786dd5 refactor: share browser route helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
614844c9fe refactor: share plugin directory helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
5eaa14687f test: share channel health helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
944a2c93e3 refactor: share gateway connection auth options 2026-03-14 00:30:14 +00:00
Peter Steinberger
42f9737e59 refactor: share gateway chat text normalization 2026-03-14 00:30:14 +00:00
Peter Steinberger
1886fe5fd9 test: share gateway chat history setup 2026-03-14 00:30:14 +00:00
Peter Steinberger
8225b9edbb test: share gateway hook and cron helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
b64466953a test: share plugin http auth helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
b72ac7936a test: share gateway reload helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
320de5ecdd test: share startup auth token fixtures 2026-03-14 00:30:14 +00:00
Peter Steinberger
5f87b1eba5 test: share schtasks gateway script fixture 2026-03-14 00:30:14 +00:00
Peter Steinberger
49cbcea429 refactor: share daemon launchd and path helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
2d39c50ee6 refactor: share daemon lifecycle restart helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
f8efa30305 test: share gateway chat run helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
54999be326 test: share qr cli setup code helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
c90b10b02f test: share daemon cli service helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
68a507ab31 test: share lifecycle config guard helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
6e7e82e5e7 test: share restart health helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
d07c6c0bc6 test: share config-only channel status helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
ed14682d63 test: share heartbeat scheduler helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
1243927cfb test: share line webhook gating helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
fbdea7f3ba test: share telegram account helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
d78b7b3dcf test: share telegram draft stream helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
903cb0679d test: share sanitize session usage helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
91b9c47dad test: share embedded compaction hook helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
34a552383f test: share telegram sticky fetch helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
2da384e110 test: share outbound media fallback helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
ba1d7b272a test: share lane delivery final helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
e91a5c72de test: share scheduled task stop helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
4fe59edd84 test: share systemd service test helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
26578a18c8 test: share agent acp turn helpers 2026-03-14 00:30:14 +00:00
Peter Steinberger
2d1134be23 test: share cron telegram delivery failure assertions 2026-03-14 00:30:14 +00:00
Peter Steinberger
6d06c582e3 test: share config pruning defaults setup 2026-03-14 00:30:14 +00:00
Peter Steinberger
9442260a20 test: share browser loopback auth error assertions 2026-03-14 00:30:14 +00:00
Peter Steinberger
a0fb5c7c41 test: share venice model response fixtures 2026-03-14 00:30:14 +00:00
Peter Steinberger
403e35e6b0 test: share cli help version assertions 2026-03-14 00:30:14 +00:00
Peter Steinberger
0a50eb0343 refactor: share models command helpers 2026-03-14 00:30:13 +00:00
Peter Steinberger
a9194f7a67 test: tighten path prepend casing coverage 2026-03-14 00:28:34 +00:00
Peter Steinberger
3920c444cb test: tighten json file lock coverage 2026-03-14 00:27:40 +00:00
Peter Steinberger
56798bd811 test: add home relative path coverage 2026-03-14 00:26:57 +00:00
Peter Steinberger
285b50c549 fix: support bun lockfile detection 2026-03-14 00:26:03 +00:00
Peter Steinberger
6ad2f793af fix: tighten runtime status detail coverage 2026-03-14 00:24:59 +00:00
Peter Steinberger
70489cbed0 fix: tighten package tag and channel summary coverage 2026-03-14 00:23:57 +00:00
Peter Steinberger
766f13d37a test: expand browser existing-session coverage 2026-03-14 00:22:45 +00:00
Peter Steinberger
3c70e50af5 fix: harden bootstrap and transport ready coverage 2026-03-14 00:22:19 +00:00
Frank Yang
7a53eb7ea8
fix: retry Telegram inbound media downloads over IPv4 fallback (#45327)
* fix: retry telegram inbound media downloads over ipv4

* fix: preserve telegram media retry errors

* fix: redact telegram media fetch errors
2026-03-14 08:21:31 +08:00
Peter Steinberger
060f3e5f9a test: tighten fetch and channel summary coverage 2026-03-14 00:20:47 +00:00
Val Alexander
0e8672af87
fix(ui): stop dashboard chat history reload storm (#45541)
* UI: stop dashboard chat history reload storm

* Changelog: add PR number for chat reload fix

* fix: resolve branch typecheck regressions
2026-03-13 19:19:53 -05:00
Peter Steinberger
4f1195f5ab test: tighten apns send coverage 2026-03-14 00:19:04 +00:00
Peter Steinberger
6ae66a8cbc test: add state migration coverage 2026-03-14 00:17:20 +00:00
Peter Steinberger
6e32daa4da test: add device bootstrap coverage 2026-03-14 00:14:26 +00:00
Peter Steinberger
e268e7a726 test: extract apns store coverage 2026-03-14 00:13:07 +00:00
Peter Steinberger
d4f36fe0be test: extract apns auth helper coverage 2026-03-14 00:11:03 +00:00
Peter Steinberger
60f2aba40d test: extract apns relay coverage 2026-03-14 00:09:15 +00:00
Peter Steinberger
7709e4a219 test: extract archive helper coverage 2026-03-14 00:06:47 +00:00
Peter Steinberger
2235511849 test: add gateway tls helper coverage 2026-03-14 00:04:18 +00:00
Peter Steinberger
2d0b9ee53c test: extract fingerprint helper coverage 2026-03-14 00:01:33 +00:00
Peter Steinberger
816ffb9379 test: extract provider usage load coverage 2026-03-13 23:59:31 +00:00
Peter Steinberger
b7ca9082ef test: tighten fetch helper coverage 2026-03-13 23:57:24 +00:00
Peter Steinberger
4357cf4e37 fix: harden browser existing-session flows 2026-03-13 23:56:48 +00:00
Peter Steinberger
fa05947225 fix: tighten duration formatter coverage 2026-03-13 23:56:24 +00:00
Peter Steinberger
71a3dd80e7 fix: tighten safe bin runtime policy coverage 2026-03-13 23:55:07 +00:00
Peter Steinberger
699ac5ab12 test: tighten safe bin policy coverage 2026-03-13 23:54:12 +00:00
Peter Steinberger
2e409da274 test: tighten channel summary coverage 2026-03-13 23:53:20 +00:00
Peter Steinberger
a5a2e487c7 test: tighten transport ready coverage 2026-03-13 23:51:44 +00:00
Val Alexander
4c77c3a7bb
UI: fix chat context notice icon sizing (#45533)
* UI: fix chat context notice icon sizing

* Update ui/src/ui/views/chat.browser.test.ts

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

* UI: tighten chat context notice regression test

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-13 18:51:06 -05:00
Peter Steinberger
e8c300c353 fix: tighten device identity helper coverage 2026-03-13 23:50:15 +00:00
Peter Steinberger
8240fc519a test: add archive staging helper coverage 2026-03-13 23:49:07 +00:00
Peter Steinberger
fffe587e27 test: tighten brew helper coverage 2026-03-13 23:47:22 +00:00
Peter Steinberger
3b9989bd90 test: tighten tmp dir fallback coverage 2026-03-13 23:46:45 +00:00
Peter Steinberger
1ae2163413 test: tighten install safe path coverage 2026-03-13 23:45:36 +00:00
Peter Steinberger
98716bc0d7 test: tighten gateway process argv coverage 2026-03-13 23:44:44 +00:00
Peter Steinberger
f8b13e5b70 fix: tighten machine name coverage 2026-03-13 23:43:06 +00:00
Peter Steinberger
47a15d7a9a fix: tighten package tag coverage 2026-03-13 23:42:01 +00:00
Peter Steinberger
369032c256 fix: tighten bonjour error coverage 2026-03-13 23:40:45 +00:00
Peter Steinberger
4d16d1390a fix: tighten package json coverage 2026-03-13 23:39:44 +00:00
Peter Steinberger
50c4e89aeb fix: tighten runtime status coverage 2026-03-13 23:38:47 +00:00
Robin Waslander
a54bf71b4c
fix(imessage): sanitize SCP remote path to prevent shell metacharacter injection
References GHSA-g2f6-pwvx-r275.
2026-03-14 00:38:14 +01:00
Peter Steinberger
ff6636ed5b fix: tighten path guard coverage 2026-03-13 23:37:37 +00:00
Tak Hoffman
bff340c1ca
test: preserve wrapper behavior for targeted runs FIX OOM issues(#45518)
* test: preserve wrapper behavior for targeted runs

* test: tighten targeted wrapper routing
2026-03-13 18:36:38 -05:00
Peter Steinberger
0da9a25818 test: share pairing setup resolution assertions 2026-03-13 23:35:28 +00:00
Peter Steinberger
a56e620777 test: simplify mattermost token summary fixtures 2026-03-13 23:35:28 +00:00
Peter Steinberger
a474a9c45d test: reuse feishu streaming merge helper 2026-03-13 23:35:28 +00:00
Peter Steinberger
b6c297af8c test: share matrix sdk test mocks 2026-03-13 23:35:28 +00:00
Peter Steinberger
4df8722edf test: share feishu monitor startup mocks 2026-03-13 23:35:28 +00:00
Peter Steinberger
0f8531dea6 test: share synology channel harness 2026-03-13 23:35:28 +00:00
Peter Steinberger
9b0e333f2c refactor: share bluebubbles multipart helpers 2026-03-13 23:35:28 +00:00
Peter Steinberger
d7aa3cc1c3 test: share zalouser test helpers 2026-03-13 23:35:28 +00:00
Peter Steinberger
66979bcc2f refactor: share self hosted provider auth flow 2026-03-13 23:35:28 +00:00
Peter Steinberger
46d4fe2fa1 refactor: share embedded run and discord test helpers 2026-03-13 23:35:28 +00:00
Peter Steinberger
0201f3ff7b refactor: share auto reply helper fixtures 2026-03-13 23:35:28 +00:00
Peter Steinberger
fd5243c27e refactor: share discord exec approval helpers 2026-03-13 23:35:28 +00:00
Peter Steinberger
fd340a88d6 test: dedupe discord preflight helpers 2026-03-13 23:35:28 +00:00
Peter Steinberger
6a44ca9f76 test: dedupe discord queue preflight setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
a7c293b8ef test: dedupe discord bound slash dispatch setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
6cabcf3fd2 test: dedupe session idle timeout assertions 2026-03-13 23:35:27 +00:00
Peter Steinberger
f15abb657a test: dedupe discord listener deferred setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
58a51e2746 refactor: share discord preflight shared fields 2026-03-13 23:35:27 +00:00
Peter Steinberger
801113b46a refactor: share session entry persistence update 2026-03-13 23:35:27 +00:00
Peter Steinberger
f8ee528174 refactor: share discord channel override config type 2026-03-13 23:35:27 +00:00
Peter Steinberger
809785dcd7 test: dedupe discord provider account config harness 2026-03-13 23:35:27 +00:00
Peter Steinberger
aed626ed96 test: dedupe discord gateway proxy register flow 2026-03-13 23:35:27 +00:00
Peter Steinberger
ee80b4be69 test: dedupe discord retry delivery setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
3eb039c554 test: dedupe discord forwarded media assertions 2026-03-13 23:35:27 +00:00
Peter Steinberger
cad1c95405 test: dedupe inline action skip assertions 2026-03-13 23:35:27 +00:00
Peter Steinberger
8cd48c2896 test: dedupe model info reply setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
c59ae1527c refactor: share discord trailing media delivery 2026-03-13 23:35:27 +00:00
Peter Steinberger
1b91fa9358 test: dedupe discord route fixture setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
97ce1503fd refactor: share discord binding update loop 2026-03-13 23:35:27 +00:00
Peter Steinberger
301594b448 refactor: share discord auto thread params 2026-03-13 23:35:27 +00:00
Peter Steinberger
0f9e16ca46 refactor: share provider chunk context resolution 2026-03-13 23:35:27 +00:00
Peter Steinberger
da51e40638 refactor: share auth label suffix formatting 2026-03-13 23:35:27 +00:00
Peter Steinberger
bd758bb438 refactor: share abort target apply params 2026-03-13 23:35:27 +00:00
Peter Steinberger
aaea0b2f28 test: dedupe directive auth ref label setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
07b3f5233e test: dedupe post compaction legacy fallback checks 2026-03-13 23:35:27 +00:00
Peter Steinberger
91c94c8b95 test: dedupe elevated permission assertions 2026-03-13 23:35:27 +00:00
Peter Steinberger
b9e5f23914 test: dedupe route reply slack no-op cases 2026-03-13 23:35:27 +00:00
Peter Steinberger
36e9a811cc test: dedupe discord auto thread harness 2026-03-13 23:35:27 +00:00
Peter Steinberger
7b70fa26e6 test: dedupe discord thread starter setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
bbb52087ed test: dedupe llm task embedded run setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
a5671ea3d8 test: dedupe discord delivery target setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
22e976574c test: dedupe inbound main scope fixtures 2026-03-13 23:35:27 +00:00
Peter Steinberger
ccd763aef7 test: dedupe gemini oauth fallback checks 2026-03-13 23:35:27 +00:00
Peter Steinberger
b4719455bc test: dedupe gemini oauth project assertions 2026-03-13 23:35:27 +00:00
Peter Steinberger
1d99401b8b refactor: share telegram voice send path 2026-03-13 23:35:27 +00:00
Peter Steinberger
41fa63a49e refactor: share anthropic compat flag checks 2026-03-13 23:35:27 +00:00
Peter Steinberger
088d6432a4 test: dedupe diffs file artifact assertions 2026-03-13 23:35:27 +00:00
Peter Steinberger
f7b9cfebe1 test: dedupe diffs http local get setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
07900303f4 refactor: share outbound poll and signal route helpers 2026-03-13 23:35:27 +00:00
Peter Steinberger
86caf454f4 refactor: share device pair ipv4 parsing 2026-03-13 23:35:27 +00:00
Peter Steinberger
9b24f890b2 refactor: share voice call message actions 2026-03-13 23:35:27 +00:00
Peter Steinberger
c5dc61e795 test: share session target and outbound mirror helpers 2026-03-13 23:35:27 +00:00
Peter Steinberger
017c0dce32 test: dedupe msteams attachment redirects 2026-03-13 23:35:27 +00:00
Peter Steinberger
0229246f3b test: share wake failure assertions 2026-03-13 23:35:27 +00:00
Peter Steinberger
fd58268f04 test: dedupe bluebubbles normalize fixtures 2026-03-13 23:35:27 +00:00
Peter Steinberger
a4a7958678 refactor: share outbound base session setup 2026-03-13 23:35:27 +00:00
Peter Steinberger
2ebc7e3ded test: dedupe msteams revoked thread context 2026-03-13 23:35:27 +00:00
Peter Steinberger
40b0cbd713 test: dedupe thread ownership send checks 2026-03-13 23:35:27 +00:00
Peter Steinberger
8ca510a669 test: dedupe feishu media account setup 2026-03-13 23:35:26 +00:00
Peter Steinberger
b213348665 test: dedupe feishu signed webhook posts 2026-03-13 23:35:26 +00:00
Peter Steinberger
4d1fcc1df2 test: share memory lancedb temp config harness 2026-03-13 23:35:26 +00:00
Peter Steinberger
1ea5bba848 test: dedupe feishu startup preflight waits 2026-03-13 23:35:26 +00:00
Peter Steinberger
5af8322ff5 refactor: share tlon channel put requests 2026-03-13 23:35:26 +00:00
Peter Steinberger
7ca8804a33 test: share feishu schema and reaction assertions 2026-03-13 23:35:26 +00:00
Peter Steinberger
a7e5925ec1 test: dedupe feishu account resolution fixtures 2026-03-13 23:35:26 +00:00
Peter Steinberger
9a14696f30 test: dedupe feishu config schema checks 2026-03-13 23:35:26 +00:00
Peter Steinberger
854df8352c refactor: share net and slack input helpers 2026-03-13 23:35:26 +00:00
Peter Steinberger
b5eb329f94 test: dedupe feishu outbound setup 2026-03-13 23:35:26 +00:00
Peter Steinberger
2cf6e2e4f6 test: dedupe matrix target resolution cases 2026-03-13 23:35:26 +00:00
Peter Steinberger
1dc8e17371 refactor: share line outbound media loop 2026-03-13 23:35:26 +00:00
Peter Steinberger
407d0d296d refactor: share tlon outbound send context 2026-03-13 23:35:26 +00:00
Peter Steinberger
a57c590a71 refactor: share telegram outbound send options 2026-03-13 23:35:26 +00:00
Val Alexander
868fd32ee7
fix(config): avoid Anthropic startup crash (#45520)
Co-authored-by: Val Alexander <bunsthedev@gmail.com>
2026-03-13 18:28:33 -05:00
Jacob Tomlinson
63802c1112
docker: add apt-get upgrade to all Dockerfiles (#45384)
* docker: add apt-get upgrade to patch base-image vulnerabilities

Closes #45159

* docker: add DEBIAN_FRONTEND and --no-install-recommends to apt-get upgrade

Prevents debconf hangs during Docker builds and avoids pulling in
recommended packages that silently grow the image.

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

* Revert "docker: add DEBIAN_FRONTEND and --no-install-recommends to apt-get upgrade"

This reverts commit 6fc3839cb56d4eb08cb43764fcbe7bd72e9bc50a.

* docker: add DEBIAN_FRONTEND and --no-install-recommends to apt-get upgrade

Prevents debconf hangs during Docker builds and avoids pulling in
recommended packages that silently grow the image.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 16:23:02 -07:00
Robin Waslander
1803d16d5c
fix(auth): make device bootstrap tokens single-use to prevent scope escalation
Refs: GHSA-63f5-hhc7-cx6p
2026-03-13 23:58:45 +01:00
Vincent Koc
aaeb348bb7 Browser: scope nested batch failures in switch 2026-03-13 15:51:08 -07:00
Peter Steinberger
ae1a1fccfe fix: stabilize browser existing-session control 2026-03-13 22:41:17 +00:00
Vincent Koc
e82ba71911
fix(browser): follow up batch failure and limit handling (#45506)
* fix(browser): propagate nested batch failures

* fix(browser): validate top-level batch limits

* test(browser): cover nested batch failures

* test(browser): cover top-level batch limits
2026-03-13 15:39:28 -07:00
Robin Waslander
7e49e98f79
fix(telegram): validate webhook secret before reading request body
Refs: GHSA-jq3f-vjww-8rq7
2026-03-13 23:21:48 +01:00
Eyal En Gad
1ef0aa443b
docs(android): note that app is not publicly released yet (#23051)
Co-authored-by: Eyal <eyal.engad@gmail.com>
2026-03-13 15:14:53 -07:00
Vincent Koc
f59b2b1db3
fix(browser): normalize batch act dispatch for selector and batch support (#45457)
* feat(browser): add batch actions, CSS selector support, and click delayMs

Adds three improvements to the browser act tool:

1. CSS selector support: All element-targeting actions (click, type,
   hover, drag, scrollIntoView, select) now accept an optional
   'selector' parameter alongside 'ref'. When selector is provided,
   Playwright's page.locator() is used directly, skipping the need
   for a snapshot to obtain refs. This reduces roundtrips for agents
   that already know the DOM structure.

2. Click delay (delayMs): The click action now accepts an optional
   'delayMs' parameter. When set, the element is hovered first, then
   after the specified delay, clicked. This enables human-like
   hover-before-click in a single tool call instead of three
   (hover + wait + click).

3. Batch actions: New 'batch' action kind that accepts an array of
   actions to execute sequentially in a single tool call. Supports
   'stopOnError' (default true) to control whether execution halts
   on first failure. Results are returned as an array. This eliminates
   the AI inference roundtrip between each action, dramatically
   reducing latency and token cost for multi-step flows.

Addresses: #44431, #38844

* fix(browser): address security review — batch evaluateEnabled guard, input validation, recursion limit

Fixes all 4 issues raised by Greptile review:

1. Security: batch actions now respect evaluateEnabled flag.
   executeSingleAction and batchViaPlaywright accept evaluateEnabled
   param. evaluate and wait-with-fn inside batches are rejected
   when evaluateEnabled=false, matching the direct route guards.

2. Security: batch input validation. Each action in body.actions
   is validated as a plain object with a known kind string before
   dispatch. Applies same normalization as direct action handlers.

3. Perf: SELECTOR_ALLOWED_KINDS moved to module scope as a
   ReadonlySet<string> constant (was re-created on every request).

4. Security: max batch nesting depth of 5. Nested batch actions
   track depth and throw if MAX_BATCH_DEPTH exceeded, preventing
   call stack exhaustion from crafted payloads.

* fix(browser): normalize batch act dispatch

* fix(browser): tighten existing-session act typing

* fix(browser): preserve batch type text

* fix(browser): complete batch action execution

* test(browser): cover batch route normalization

* test(browser): cover batch interaction dispatch

* fix(browser): bound batch route action inputs

* fix(browser): harden batch interaction limits

* test(browser): cover batch security guardrails

---------

Co-authored-by: Diwakar <diwakarrankawat@gmail.com>
2026-03-13 15:10:55 -07:00
Peter Steinberger
d0337a18b6
fix: clear typecheck backlog 2026-03-13 22:09:06 +00:00
Peter Steinberger
a66a0852bb
test: cover plugin-sdk subpath imports 2026-03-13 22:09:06 +00:00
Vincent Koc
65f92fd839
Guard updater service refresh against missing invocation cwd (#45486)
* Update: capture a stable cwd for service refresh env

* Test: cover service refresh when cwd disappears
2026-03-13 18:09:01 -04:00
Peter Steinberger
fac754041c fix: tighten executable path coverage 2026-03-13 22:07:14 +00:00
Peter Steinberger
0826feb94d test: tighten path prepend helper coverage 2026-03-13 22:06:01 +00:00
Peter Steinberger
56e5b8b9e8 test: tighten secret file error coverage 2026-03-13 22:04:54 +00:00
Peter Steinberger
c04ea0eac5 test: tighten tmp dir security coverage 2026-03-13 22:03:17 +00:00
Peter Steinberger
cb99a23d84 test: tighten shell env helper coverage 2026-03-13 22:02:18 +00:00
Peter Steinberger
fb4aa7eaba fix: tighten shared chat envelope coverage 2026-03-13 22:00:22 +00:00
Peter Steinberger
2fe4c4f8e5 test: tighten shared auth store coverage 2026-03-13 21:59:35 +00:00
Peter Steinberger
6a9e141c7a test: tighten shared config eval helper coverage 2026-03-13 21:58:23 +00:00
Peter Steinberger
b7ff8256ef test: guard plugin-sdk shared-bundle regression (#45426) (thanks @TarasShyn) 2026-03-13 21:57:43 +00:00
Taras Shynkarenko
ccced29b46 perf(build): deduplicate plugin-sdk chunks to fix ~2x memory regression
Bundle all plugin-sdk entries in a single tsdown build pass instead of
38 separate builds. The separate builds prevented the bundler from
sharing common chunks, causing massive duplication (e.g. 20 copies of
query-expansion, 14 copies of fetch, 11 copies of logger).

Measured impact:
- dist/ size: 190MB → 64MB (-66%)
- plugin-sdk/ size: 142MB → 16MB (-89%)
- JS files: 1,395 → 789 (-43%)
- 5MB+ files: 27 → 7 (-74%)
- Plugin-SDK heap cost: +1,309MB → +63MB (-95%)
- Total heap (all chunks loaded): 1,926MB → 711MB (-63%)
2026-03-13 21:57:43 +00:00
Peter Steinberger
592d93211f test: tighten shared manifest metadata coverage 2026-03-13 21:57:16 +00:00
Peter Steinberger
25e900f64a test: tighten shared requirements coverage 2026-03-13 21:55:40 +00:00
Peter Steinberger
a9d8518e7c test: dedupe msteams consent auth fixtures 2026-03-13 21:54:39 +00:00
Peter Steinberger
110eeec5b8 test: dedupe twitch access control checks 2026-03-13 21:54:39 +00:00
Peter Steinberger
0530d1c530 test: dedupe twitch access control assertions 2026-03-13 21:54:39 +00:00
Peter Steinberger
f2300f4522 test: dedupe msteams policy route fixtures 2026-03-13 21:54:39 +00:00
Peter Steinberger
b23bfef8cc test: dedupe feishu probe fixtures 2026-03-13 21:54:39 +00:00
Peter Steinberger
5b51d92f3e test: dedupe synology channel account fixtures 2026-03-13 21:54:39 +00:00
Peter Steinberger
d964c15040 test: dedupe synology webhook request helpers 2026-03-13 21:54:39 +00:00
Peter Steinberger
8896a477df test: dedupe bluebubbles local media send cases 2026-03-13 21:54:39 +00:00
Peter Steinberger
168394980f refactor: share slack allowlist target mapping 2026-03-13 21:54:39 +00:00
Peter Steinberger
f0d0ad39c4 test: dedupe nostr profile http assertions 2026-03-13 21:54:39 +00:00
Peter Steinberger
58baf22230 refactor: share zalo monitor processing context 2026-03-13 21:54:39 +00:00
Peter Steinberger
b9f0effd55 test: dedupe synology chat client timer setup 2026-03-13 21:54:39 +00:00
Peter Steinberger
853999fd7f refactor: dedupe synology chat client webhook payloads 2026-03-13 21:54:39 +00:00
Peter Steinberger
f5b9095108 refactor: share zalo send result handling 2026-03-13 21:54:39 +00:00
Val Alexander
158d970e2b
[codex] Polish sidebar status, agent skills, and chat rendering (#45451)
* style: update chat layout and spacing for improved UI consistency

- Adjusted margin and padding for .chat-thread and .content--chat to enhance layout.
- Consolidated CSS selectors for better readability and maintainability.
- Introduced new test for log parsing functionality to ensure accurate message extraction.

* UI: polish agent skills, chat images, and sidebar status

* test: stabilize vitest helper export types

* UI: address review feedback on agents refresh and chat styles

* test: update outbound gateway client fixture values

* test: narrow shared ip fixtures to IPv4
2026-03-13 16:53:40 -05:00
Peter Steinberger
52900b48ad test: tighten shared policy helper coverage 2026-03-13 21:53:11 +00:00
Peter Steinberger
4de268587c test: tighten shared tailscale fallback coverage 2026-03-13 21:52:01 +00:00
Peter Steinberger
e665888a45 test: tighten shared usage aggregate coverage 2026-03-13 21:51:01 +00:00
Peter Steinberger
fbcea506ba test: tighten shared gateway bind and avatar coverage 2026-03-13 21:49:50 +00:00
Peter Steinberger
daca6c9df2 test: tighten small shared helper coverage 2026-03-13 21:48:40 +00:00
Peter Steinberger
9b590c9f67 test: tighten shared reasoning tag coverage 2026-03-13 21:47:33 +00:00
Peter Steinberger
ae5563dd18 test: tighten shared join and message content coverage 2026-03-13 21:46:20 +00:00
Peter Steinberger
2d7a061161 test: tighten shared ip parsing coverage 2026-03-13 21:45:30 +00:00
Peter Steinberger
e7863d7fdd
test: add parallels macos smoke harness 2026-03-13 21:44:29 +00:00
Peter Steinberger
c659f6c959
fix: improve onboarding install diagnostics 2026-03-13 21:44:29 +00:00
Peter Steinberger
eea41f308e fix: tighten shared subagent format coverage 2026-03-13 21:44:11 +00:00
Peter Steinberger
dd54b6f4c7 test: tighten shared node match coverage 2026-03-13 21:43:01 +00:00
Peter Steinberger
73c2edbc0c test: tighten shared code region coverage 2026-03-13 21:42:07 +00:00
Peter Steinberger
fa04e62201 test: tighten shared tailscale and sample coverage 2026-03-13 21:40:59 +00:00
Peter Steinberger
a6375a2094 refactor: share zalouser account resolution 2026-03-13 21:40:54 +00:00
Peter Steinberger
7235ee55c6 test: share APNs direct send fixtures 2026-03-13 21:40:54 +00:00
Peter Steinberger
29bc011ec7 test: share heartbeat retry fixtures 2026-03-13 21:40:54 +00:00
Peter Steinberger
ed3dd6a1a0 test: share install flow failure harness 2026-03-13 21:40:54 +00:00
Peter Steinberger
84a50acb55 refactor: share portable env entry normalization 2026-03-13 21:40:54 +00:00
Peter Steinberger
ef15600b3e refactor: share request body chunk accounting 2026-03-13 21:40:54 +00:00
Peter Steinberger
8f852ef82f refactor: share system run success delivery 2026-03-13 21:40:54 +00:00
Peter Steinberger
a2fcaf9774 test: share plugin install path fixtures 2026-03-13 21:40:54 +00:00
Peter Steinberger
f06ae90884 test: share process respawn launchd assertions 2026-03-13 21:40:54 +00:00
Peter Steinberger
25eb3d5209 refactor: share openclaw root package parsing 2026-03-13 21:40:54 +00:00
Peter Steinberger
95f8b91c8a test: share memory search manager fixtures 2026-03-13 21:40:54 +00:00
Peter Steinberger
7eb38e8f7b test: share temporal decay vector fixtures 2026-03-13 21:40:54 +00:00
Peter Steinberger
a879ad7547 test: share node host credential assertions 2026-03-13 21:40:54 +00:00
Peter Steinberger
4ec0a120df test: share zalo api request assertion 2026-03-13 21:40:54 +00:00
Peter Steinberger
ba34266e89 test: dedupe cron config setup 2026-03-13 21:40:53 +00:00
Peter Steinberger
83571fdb93 refactor: dedupe agent list filtering 2026-03-13 21:40:53 +00:00
Peter Steinberger
fa1ce9fd19 test: trim acp translator import duplication 2026-03-13 21:40:53 +00:00
Peter Steinberger
7119ab1d98 refactor: dedupe home relative path resolution 2026-03-13 21:40:53 +00:00
Peter Steinberger
e4924a0134 test: dedupe acp translator cancel scoping tests 2026-03-13 21:40:53 +00:00
Peter Steinberger
77d2f9a354 refactor: share snake case param lookup 2026-03-13 21:40:53 +00:00
Peter Steinberger
467a7bae3f refactor: share session conversation normalization 2026-03-13 21:40:53 +00:00
Peter Steinberger
0f637b5e30 refactor: share acp conversation text normalization 2026-03-13 21:40:53 +00:00
Peter Steinberger
9b6790e3a6 refactor: share acp binding resolution helper 2026-03-13 21:40:53 +00:00
Peter Steinberger
94531fa237 test: reduce docker setup e2e duplication 2026-03-13 21:40:53 +00:00
Peter Steinberger
d9fb1e0e45 test: dedupe acp startup test harness 2026-03-13 21:40:53 +00:00
Peter Steinberger
1301462a1b refactor: share acp persistent binding fixtures 2026-03-13 21:40:53 +00:00
Peter Steinberger
4269ea4e8d test: share slack config snapshot helper 2026-03-13 21:40:53 +00:00
Peter Steinberger
71639d1744 test: share lobster windows spawn assertions 2026-03-13 21:40:53 +00:00
Peter Steinberger
12432ca138 test: share googlechat webhook routing helpers 2026-03-13 21:40:53 +00:00
Peter Steinberger
d4d0091760 test: share msteams safe fetch assertions 2026-03-13 21:40:53 +00:00
Peter Steinberger
9ecd1898d0 refactor: share telegram channel test harnesses 2026-03-13 21:40:53 +00:00
Peter Steinberger
3ffb9f19cb test: reduce feishu reply dispatcher duplication 2026-03-13 21:40:53 +00:00
Peter Steinberger
d347a4426d refactor: share twitch outbound target assertions 2026-03-13 21:40:53 +00:00
Peter Steinberger
aa551e5a9c refactor: share acpx process env test helper 2026-03-13 21:40:53 +00:00
Peter Steinberger
65cf2cea9d refactor: share matrix monitor test helpers 2026-03-13 21:40:53 +00:00
Peter Steinberger
67f7d1e65f test: dedupe slack message event tests 2026-03-13 21:40:53 +00:00
Peter Steinberger
c8898034f9 refactor: share agent wait dedupe cleanup 2026-03-13 21:40:53 +00:00
Peter Steinberger
b5010719d6 test: dedupe telnyx webhook test fixtures 2026-03-13 21:40:53 +00:00
Peter Steinberger
d5d2fe1b0e test: reduce webhook auth test duplication 2026-03-13 21:40:53 +00:00
Peter Steinberger
de9ea76b6c refactor: dedupe feishu send reply fallback helpers 2026-03-13 21:40:53 +00:00
Peter Steinberger
0159269a51 refactor: share openclaw tool sandbox config 2026-03-13 21:40:53 +00:00
Peter Steinberger
4674fbf923 refactor: share handshake auth helper builders 2026-03-13 21:40:53 +00:00
Peter Steinberger
6ecc184637 refactor: share googlechat api fetch handling 2026-03-13 21:40:53 +00:00
Peter Steinberger
e64cc907ff test: share cron run fallback helpers 2026-03-13 21:40:53 +00:00
Peter Steinberger
0574ac23d0 test: share delivery target session helpers 2026-03-13 21:40:53 +00:00
Peter Steinberger
d7f9035e80 test: share sandbox default assertions 2026-03-13 21:40:53 +00:00
Peter Steinberger
7fd21b6bc6 refactor: share subagent followup reply helpers 2026-03-13 21:40:53 +00:00
Peter Steinberger
d3f46fa7fa test: share session transcript store setup 2026-03-13 21:40:53 +00:00
Peter Steinberger
eca22c0cc7 test: share bluebubbles attachment fixtures 2026-03-13 21:40:53 +00:00
Peter Steinberger
89e0e80db3 test: share bluebubbles removal reaction helper 2026-03-13 21:40:53 +00:00
Peter Steinberger
8ddb531346 test: share discord auto presence assertions 2026-03-13 21:40:53 +00:00
Peter Steinberger
143ae5a5b0 refactor: share feishu chunked reply delivery 2026-03-13 21:40:53 +00:00
Peter Steinberger
6756e376f3 refactor: share bluebubbles response and tapback helpers 2026-03-13 21:40:53 +00:00
Peter Steinberger
867dc6a185 test: share twitch send success mocks 2026-03-13 21:40:53 +00:00
Peter Steinberger
a8508f2b31 test: share voice webhook reaper harness 2026-03-13 21:40:53 +00:00
Peter Steinberger
534e4b1418 refactor: share synology chat raw config fields 2026-03-13 21:40:52 +00:00
Peter Steinberger
1cea43d349 test: share zalouser group policy resolver 2026-03-13 21:40:52 +00:00
Peter Steinberger
6d0e4c76ac refactor: share cron model formatting assertions 2026-03-13 21:40:52 +00:00
Peter Steinberger
0836bf844b refactor: share global update test harness 2026-03-13 21:40:52 +00:00
Peter Steinberger
cdde51c608 test: tighten shared text chunking coverage 2026-03-13 21:40:01 +00:00
Peter Steinberger
56299effe9 test: tighten shared metadata and node resolve coverage 2026-03-13 21:39:11 +00:00
Peter Steinberger
4ecdd7907a test: tighten shared auth and identity coverage 2026-03-13 21:38:28 +00:00
Peter Steinberger
4fd8b98b10 test: tighten shared message and ipv4 coverage 2026-03-13 21:37:48 +00:00
Peter Steinberger
80569babd3 test: tighten shared chat envelope coverage 2026-03-13 21:37:10 +00:00
Peter Steinberger
fe55622205 test: tighten shared process map and model coverage 2026-03-13 21:36:32 +00:00
Peter Steinberger
2f82ade66f test: tighten assistant scaffolding coverage 2026-03-13 21:35:31 +00:00
Peter Steinberger
3a59d40109 test: tighten shared pid and node parsing coverage 2026-03-13 21:30:35 +00:00
Peter Steinberger
783d320547 test: tighten shared requirements coverage 2026-03-13 21:29:07 +00:00
Peter Steinberger
330631a0eb test: tighten shared config eval coverage 2026-03-13 21:28:17 +00:00
Peter Steinberger
5a9d3abc10 test: tighten shared ip helper coverage 2026-03-13 21:27:15 +00:00
Vincent Koc
e56e0cc913
Fix updater refresh cwd for service reinstall (#45452)
* Fix updater refresh cwd for service reinstall

* Update: preserve relative env overrides during service refresh

* Test: cover updater service refresh env rebasing
2026-03-13 17:27:12 -04:00
Peter Steinberger
090c0c4b5d test: tighten shared text normalization coverage 2026-03-13 21:26:15 +00:00
Peter Steinberger
0c79c86b40 test: tighten shared singleton and sample coverage 2026-03-13 21:25:20 +00:00
Peter Steinberger
42ccee658d test: tighten shared avatar and scope coverage 2026-03-13 21:24:38 +00:00
Peter Steinberger
e8addf2ac2 test: add message action spec coverage 2026-03-13 21:23:10 +00:00
Peter Steinberger
256c91ca6d test: tighten message action normalization coverage 2026-03-13 21:22:15 +00:00
Peter Steinberger
651ccf9901 test: tighten channel selection coverage 2026-03-13 21:20:26 +00:00
Vincent Koc
28b0d8e8bd
fix(cron): prevent isolated cron nested lane deadlocks (#45459)
* fix(cron): resolve isolated session deadlock (#44805)

Map cron lane to nested in resolveGlobalLane to prevent deadlock when
isolated cron jobs trigger inner operations (e.g. compaction). Outer
execution holds the cron lane slot; inner work now uses nested lane.

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

* docs(changelog): add cron isolated deadlock note

---------

Co-authored-by: zhujian <zhujianxyz@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:19:40 -07:00
Peter Steinberger
3e6c8376fb test: tighten outbound send service coverage 2026-03-13 21:19:15 +00:00
Peter Steinberger
d062252522 test: dedupe exec approvals analysis coverage 2026-03-13 21:18:13 +00:00
Vincent Koc
8c7bdbe4d1
Docs: describe Slack interactive replies (#45463) 2026-03-13 17:16:14 -04:00
Peter Steinberger
c2a9c5699d test: extract outbound delivery lifecycle coverage 2026-03-13 21:13:52 +00:00
Peter Steinberger
c355b8a671 test: extract message action context coverage 2026-03-13 21:10:37 +00:00
Peter Steinberger
9c08312121 test: add message action poll validation coverage 2026-03-13 21:08:34 +00:00
Vincent Koc
a976cc2e95
Slack: add opt-in interactive reply directives (#44607)
* Reply: add Slack interactive directive parser

* Reply: wire Slack directives into normalization

* Reply: cover Slack directive parsing

* Reply: test Slack directive normalization

* Slack: hint interactive reply directives

* Config: add Slack interactive reply capability type

* Config: validate Slack interactive reply capability

* Reply: gate Slack directives behind capability

* Slack: gate interactive reply hints by capability

* Tests: cover Slack interactive reply capability gating

* Changelog: note opt-in Slack interactive replies

* Slack: fix interactive reply review findings

* Slack: harden interactive reply routing and limits

* Slack: harden interactive reply trust and validation
2026-03-13 14:08:04 -07:00
Peter Steinberger
1f4b8c4eea test: extract message action media coverage 2026-03-13 21:06:40 +00:00
MoerAI
9da06d918f fix(windows): add windowsHide to detached spawn calls to suppress console windows (#44693)
The restart helper and taskkill spawn calls were missing windowsHide: true,
causing visible command prompt windows to flash on screen during gateway
restart and process cleanup on Windows.
2026-03-13 21:06:33 +00:00
Peter Steinberger
9044a10c5f test: extract message action plugin dispatch coverage 2026-03-13 21:04:08 +00:00
Peter Steinberger
a9fd34058f test: fix daemon status env typing 2026-03-13 21:02:19 +00:00
Peter Steinberger
b84c7037de fix: repair ci audit and type drift 2026-03-13 21:02:19 +00:00
Peter Steinberger
cfc9a21957 test: extract exec approvals shell analysis coverage 2026-03-13 21:01:35 +00:00
Peter Steinberger
4d686b47f0
fix: bind macOS skill trust to resolved paths 2026-03-13 21:00:59 +00:00
Peter Steinberger
fc140bb02b test: extract outbound delivery queue coverage 2026-03-13 20:58:52 +00:00
Vincent Koc
ffee3dfef0 Plugins: resolve local openclaw peer for audits 2026-03-13 13:55:28 -07:00
Peter Steinberger
d537904abb test: extract outbound session route coverage 2026-03-13 20:54:41 +00:00
Peter Steinberger
6b49a604b4
fix: harden macos shell continuation parsing 2026-03-13 20:54:10 +00:00
Peter Steinberger
cd72fa6e77 test: extract outbound policy coverage 2026-03-13 20:51:52 +00:00
Peter Steinberger
9747da8682
fix: honor gateway command env in status reads 2026-03-13 20:50:48 +00:00
Peter Steinberger
377b42c92b test: extract outbound payload coverage 2026-03-13 20:50:31 +00:00
Peter Steinberger
e1fedd4388
fix: harden macos env wrapper resolution 2026-03-13 20:49:17 +00:00
Peter Steinberger
0643c0d15a test: extract outbound format and cache coverage 2026-03-13 20:47:29 +00:00
Peter Steinberger
bde038527c test: extract exec approvals policy coverage 2026-03-13 20:43:54 +00:00
Peter Steinberger
8b05cd4074 test: add exec approvals store helper coverage 2026-03-13 20:43:08 +00:00
Peter Steinberger
5f0e97b22a test: extract exec approval session target coverage 2026-03-13 20:40:19 +00:00
Peter Steinberger
8dcee1f6b2 test: fix fresh infra type drift 2026-03-13 20:38:24 +00:00
Peter Steinberger
75c7c169e1 test: re-enable Node 24 vmForks fast lane 2026-03-13 20:38:24 +00:00
Peter Steinberger
5c07207dd1 ci: trim PR critical path 2026-03-13 20:38:24 +00:00
Peter Steinberger
8c21284c1c refactor: share stale pid polling fixtures 2026-03-13 20:37:54 +00:00
Peter Steinberger
bf631b5872 refactor: share voice restore test setup 2026-03-13 20:37:53 +00:00
Peter Steinberger
eec1b3a512 refactor: share system run deny cases 2026-03-13 20:37:53 +00:00
Peter Steinberger
9dafcd417d refactor: share cron restart catchup harness 2026-03-13 20:37:53 +00:00
Peter Steinberger
e762a57d62 refactor: share secrets audit model fixtures 2026-03-13 20:37:53 +00:00
Peter Steinberger
ec31948bcc refactor: share gemini embedding test setup 2026-03-13 20:37:53 +00:00
Peter Steinberger
ba2d57d024 refactor: share mattermost test harnesses 2026-03-13 20:37:53 +00:00
Peter Steinberger
48853f875b refactor: share test request helpers 2026-03-13 20:37:53 +00:00
Peter Steinberger
28a49aaa34
fix: harden powershell wrapper detection 2026-03-13 20:37:38 +00:00
Peter Steinberger
e2fa47f5f2 fix: tighten exec approval reply coverage 2026-03-13 20:37:21 +00:00
Peter Steinberger
f568bd23d8 test: extract exec allowlist matching coverage 2026-03-13 20:34:25 +00:00
Peter Steinberger
3957f29e2f test: extract exec command resolution coverage 2026-03-13 20:33:18 +00:00
Peter Steinberger
fca6b57037 test: add gateway process helper coverage 2026-03-13 20:31:20 +00:00
Peter Steinberger
7fe5cd26b5 test: add entry status and ipv4 helper coverage 2026-03-13 20:29:40 +00:00
Peter Steinberger
b7afc7bf40
fix: harden external content marker sanitization 2026-03-13 20:28:45 +00:00
Peter Steinberger
9666188da8 test: add shared chat helper coverage 2026-03-13 20:28:22 +00:00
Peter Steinberger
d291148e93 test: add shared node and usage helper coverage 2026-03-13 20:26:22 +00:00
Peter Steinberger
2192bb7eb5 test: add shared text and identity helper coverage 2026-03-13 20:24:35 +00:00
Peter Steinberger
8dd454530d test: add frontmatter and node match coverage 2026-03-13 20:23:26 +00:00
Peter Steinberger
341d3e3493 test: add shared helper coverage 2026-03-13 20:21:43 +00:00
Peter Steinberger
35cf3d0ce5 test: add device auth store coverage 2026-03-13 20:20:27 +00:00
Peter Steinberger
e7fb2fea5c build: ignore generated docs and changelogs in jscpd 2026-03-13 20:19:39 +00:00
Peter Steinberger
784020f71e docs: trim duplicated plugin and open prose guides 2026-03-13 20:19:39 +00:00
Peter Steinberger
1ea97fddd7 docs: share docker vm runtime guidance 2026-03-13 20:19:39 +00:00
Peter Steinberger
5225667e25 docs: trim duplicated wizard and gateway api examples 2026-03-13 20:19:39 +00:00
Peter Steinberger
fff514c7f2 refactor: share cron and ollama test helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
8473a29da7 refactor: share exec approval session target routing 2026-03-13 20:19:39 +00:00
Peter Steinberger
c74e5210f6 refactor: share embedded runner e2e fixtures 2026-03-13 20:19:39 +00:00
Peter Steinberger
92dbb59b79 refactor: share stream payload patch helper 2026-03-13 20:19:39 +00:00
Peter Steinberger
e08dc6f0af refactor: share onboard provider merge helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
7d69579634 refactor: share windows daemon test fixtures 2026-03-13 20:19:39 +00:00
Peter Steinberger
4e05357c45 refactor: share backup coverage assertions 2026-03-13 20:19:39 +00:00
Peter Steinberger
95818a7c32 refactor: share embedding batch retry helper 2026-03-13 20:19:39 +00:00
Peter Steinberger
1a5a3fecf3 refactor: share ollama setup test helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
d53d4dc22f refactor: share zalouser group gating helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
ea82458290 refactor: share launchd bootstrap assertions 2026-03-13 20:19:39 +00:00
Peter Steinberger
806e3c12dc refactor: share doctor state migration helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
420b0672e4 refactor: share allow-always test helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
2dd180472f refactor: share mattermost interaction test helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
e731974da1 refactor: share approval id test helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
df2bda63c6 refactor: share compact hook success harness 2026-03-13 20:19:39 +00:00
Peter Steinberger
e6a26e82ca refactor: share memory ssrf test helper 2026-03-13 20:19:39 +00:00
Peter Steinberger
d904f37f1c refactor: share embedding retry waits 2026-03-13 20:19:39 +00:00
Peter Steinberger
da1ec45505 refactor: share plugin temp dir helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
5a255809b9 refactor: share backup invalid config fixture 2026-03-13 20:19:39 +00:00
Peter Steinberger
5cc751386d refactor: share web secret unresolved helpers 2026-03-13 20:19:39 +00:00
Peter Steinberger
855748a1a2 refactor: share secret fallback diagnostics 2026-03-13 20:19:39 +00:00
Peter Steinberger
cba07a400a refactor: share launchd bootstrap ordering assertions 2026-03-13 20:19:39 +00:00
Peter Steinberger
feba7ea8fd refactor: share shared auth scope assertion 2026-03-13 20:19:39 +00:00
Peter Steinberger
3a21f8b1e3 refactor: share discord proxy fetch failure helper 2026-03-13 20:19:39 +00:00
Peter Steinberger
1fe261f92f refactor: share chat abort auth invocation 2026-03-13 20:19:39 +00:00
Peter Steinberger
f201bad372 refactor: share telegram dispatch failure harness 2026-03-13 20:19:39 +00:00
Peter Steinberger
2cd1a4b8dd refactor: share telegram named account dm fixtures 2026-03-13 20:19:39 +00:00
Peter Steinberger
c61f3f4ede refactor: share logging console spies 2026-03-13 20:19:39 +00:00
Peter Steinberger
0625547800 refactor: share approval unavailable fixtures 2026-03-13 20:19:38 +00:00
Peter Steinberger
ad52724d9a refactor: share fallback skip assertions 2026-03-13 20:19:38 +00:00
Peter Steinberger
0652b885df refactor: share gemini preview model fixtures 2026-03-13 20:19:38 +00:00
Peter Steinberger
14c052a256 refactor: share host env git exploit helpers 2026-03-13 20:19:38 +00:00
Peter Steinberger
95ed44ce71 refactor: share memory watcher test setup 2026-03-13 20:19:38 +00:00
Peter Steinberger
5067d06f55 refactor: share session status sandbox helpers 2026-03-13 20:19:38 +00:00
Peter Steinberger
ae7121d534 refactor: share memory concurrency config 2026-03-13 20:19:38 +00:00
Peter Steinberger
a5f0f66427 refactor: share zalouser group auth setup 2026-03-13 20:19:38 +00:00
Peter Steinberger
44e1c6cc21 refactor: share exec approval script fixtures 2026-03-13 20:19:38 +00:00
Peter Steinberger
8ddaca1763 refactor: share migration and tts test helpers 2026-03-13 20:19:38 +00:00
Peter Steinberger
fd656ed3b0 refactor: share ollama setup prompts 2026-03-13 20:19:38 +00:00
Peter Steinberger
94e748086c refactor: share auth overview and fetch test helpers 2026-03-13 20:19:38 +00:00
Peter Steinberger
985be2a864 refactor: share acpx ensure install checks 2026-03-13 20:19:38 +00:00
Peter Steinberger
4ec0fcf1b6 refactor: share plugin test fixtures 2026-03-13 20:19:38 +00:00
Peter Steinberger
5f34391f75 refactor: share gateway client auth retry helpers 2026-03-13 20:19:38 +00:00
Peter Steinberger
60dc46ad10 refactor: share telegram native command auth harness 2026-03-13 20:19:38 +00:00
Peter Steinberger
b1b6c7a982 refactor: share models config envvar fixtures 2026-03-13 20:19:38 +00:00
Peter Steinberger
9780e999e9 refactor: share lane delivery test flows 2026-03-13 20:19:38 +00:00
Peter Steinberger
44cd3674dd refactor: share ollama stream test assertions 2026-03-13 20:19:38 +00:00
Peter Steinberger
07e5fc19bd refactor: share system run plan test fixtures 2026-03-13 20:19:38 +00:00
Peter Steinberger
c2096897bb refactor: share backup verify fixtures 2026-03-13 20:19:38 +00:00
Peter Steinberger
d2a36d0a98 refactor: share small test harness helpers 2026-03-13 20:19:38 +00:00
Peter Steinberger
f95c09b6f2 test: add diagnostic and port format helper coverage 2026-03-13 20:18:50 +00:00
Peter Steinberger
1a319b7847 test: add dedupe and boundary file helper coverage 2026-03-13 20:16:57 +00:00
Peter Steinberger
fdbfdec341 test: add channel resolution helper coverage 2026-03-13 20:13:53 +00:00
Peter Steinberger
c3fadff0ce test: add socket and ssh helper coverage 2026-03-13 20:12:04 +00:00
Peter Steinberger
dbef3dfef0
docs(voice-call): clarify allowlist caller ID limits 2026-03-13 20:11:42 +00:00
Peter Steinberger
593964560b
feat(browser): add chrome MCP existing-session support 2026-03-13 20:10:08 +00:00
Peter Steinberger
9c52e1b7de test: add machine and adapter helper coverage 2026-03-13 20:09:49 +00:00
Peter Steinberger
146cba46ca test: add target normalization helper coverage 2026-03-13 20:06:14 +00:00
Peter Steinberger
bf6da81028 test: dedupe is-main and shell coverage 2026-03-13 20:04:35 +00:00
Peter Steinberger
7771444725 test: dedupe outbound envelope coverage 2026-03-13 20:03:09 +00:00
Peter Steinberger
ddfa6e66c8 test: dedupe voicewake and target helper coverage 2026-03-13 20:00:43 +00:00
Peter Steinberger
6d159a45a8 test: add outbound and hardlink helper coverage 2026-03-13 19:59:11 +00:00
Peter Steinberger
7c95a25df7 test: add exec helper coverage 2026-03-13 19:57:49 +00:00
Peter Steinberger
7c58de294e test: dedupe activity and diagnostic coverage 2026-03-13 19:56:04 +00:00
Peter Steinberger
1fefd4e67f test: add install and pairing helper coverage 2026-03-13 19:54:16 +00:00
Peter Steinberger
60d308cff0 test: fix CI type regressions 2026-03-13 19:53:40 +00:00
Peter Steinberger
d17490ff54 ci: speed up scoped workflow lanes 2026-03-13 19:53:40 +00:00
Peter Steinberger
a423b1d936 test: add direct infra helper coverage 2026-03-13 19:51:59 +00:00
Peter Steinberger
3e77263b4c
docs: tighten parallels macos retest notes 2026-03-13 19:51:05 +00:00
Peter Steinberger
c84c76ee66 test: add clipboard and package helper coverage 2026-03-13 19:50:27 +00:00
Peter Steinberger
ba9fb4d994
fix: persist auth profile env refs for daemon install 2026-03-13 19:50:13 +00:00
Peter Steinberger
549cb65ba4 test: add executable path and backup summary coverage 2026-03-13 19:48:06 +00:00
Peter Steinberger
c351b49aed test: add package manager and deps status coverage 2026-03-13 19:46:52 +00:00
Peter Steinberger
b8a2b1b5cc test: expand npm install and update check coverage 2026-03-13 19:45:37 +00:00
Peter Steinberger
cda4e904cd test: tighten runtime status and usage formatting coverage 2026-03-13 19:44:09 +00:00
Peter Steinberger
413c8d189c test: add update global and migration fs coverage 2026-03-13 19:42:47 +00:00
Peter Steinberger
0386dcb63f test: add small infra helper coverage 2026-03-13 19:39:07 +00:00
Peter Steinberger
12cbaddade test: expand runtime guard and path prepend coverage 2026-03-13 19:37:49 +00:00
Peter Steinberger
eb32f42b53 test: harden restart sentinel and host env coverage 2026-03-13 19:36:49 +00:00
Peter Steinberger
a9b5fe4099 test: tighten system event and lsof helper coverage 2026-03-13 19:35:27 +00:00
Peter Steinberger
bb84e5e82e test: add tailnet and os summary helper coverage 2026-03-13 19:34:10 +00:00
Peter Steinberger
cda9eacada test: add json file and canvas host helper coverage 2026-03-13 19:32:59 +00:00
Peter Steinberger
7817eb0117 test: add gemini auth and pairing helper coverage 2026-03-13 19:31:59 +00:00
Peter Steinberger
3442acbae1 test: add normalization and backoff helper coverage 2026-03-13 19:30:46 +00:00
Peter Steinberger
cab2f891e7 test: tighten port and map helper coverage 2026-03-13 19:30:06 +00:00
Peter Steinberger
d2bebfb253 test: add direct error helper coverage 2026-03-13 19:28:43 +00:00
Peter Steinberger
47b0ee36ff test: expand message action helper coverage 2026-03-13 19:27:04 +00:00
Peter Steinberger
4a6020c574 test: tighten numeric parsing and path safety coverage 2026-03-13 19:24:22 +00:00
Peter Steinberger
cf43951abc test: tighten claude usage fallback coverage 2026-03-13 19:23:25 +00:00
Peter Steinberger
348f8e8f28 test: tighten codex usage coverage 2026-03-13 19:22:21 +00:00
Peter Steinberger
0ece3834f8 test: expand approval binding helper coverage 2026-03-13 19:21:17 +00:00
Peter Steinberger
03d076283c test: tighten small helper edge coverage 2026-03-13 19:18:45 +00:00
Peter Steinberger
4f78d8542d test: tighten secure token and system mark coverage 2026-03-13 19:17:24 +00:00
Peter Steinberger
9270c03665 test: expand state dir identity coverage 2026-03-13 19:16:38 +00:00
Peter Steinberger
54728c60d5 fix: harden zai and ssh helper coverage 2026-03-13 19:15:25 +00:00
Peter Steinberger
da2f85ae2b test: tighten copilot and shared usage coverage 2026-03-13 19:13:51 +00:00
Peter Steinberger
09fd72bc5b test: expand approval context and gemini usage coverage 2026-03-13 19:13:01 +00:00
Peter Steinberger
dfcbfcfcc9 test: tighten proxy env and conversation id coverage 2026-03-13 19:11:10 +00:00
Peter Steinberger
5189ba851c
fix: stop windows startup fallback gateways 2026-03-13 19:10:57 +00:00
Peter Steinberger
5024fd0908 test: expand dns zone and runner coverage 2026-03-13 19:10:11 +00:00
Peter Steinberger
f155d8febc fix: harden format time fallback handling 2026-03-13 19:08:26 +00:00
Peter Steinberger
b4a3e5324b test: expand exec wrapper helper coverage 2026-03-13 19:06:22 +00:00
Peter Steinberger
e6213b2fc7 test: tighten fetch helper and package root coverage 2026-03-13 19:04:41 +00:00
Peter Steinberger
6bbf2d486c test: expand presence and maintenance warning coverage 2026-03-13 19:03:01 +00:00
AstroHan
96c48f5566
fix(ui): restore chat-new-messages class on scroll pill button (#44856)
Merged via squash.

Prepared head SHA: 621ef634a49bc55ff52e0d0adbcdd1d23c5949fa
Co-authored-by: Astro-Han <255364436+Astro-Han@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 22:03:00 +03:00
Peter Steinberger
e928f55537 test: tighten warning and npm integrity coverage 2026-03-13 19:01:16 +00:00
Peter Steinberger
2622b05c0b test: tighten plugin install path warning coverage 2026-03-13 18:58:53 +00:00
Peter Steinberger
0f48556241 test: expand install safe path coverage 2026-03-13 18:58:22 +00:00
Peter Steinberger
6a545c04eb test: clarify system presence version precedence 2026-03-13 18:56:54 +00:00
Peter Steinberger
e895d4d1a8 test: tighten shared fetch helper coverage 2026-03-13 18:56:37 +00:00
Peter Steinberger
3e9243817e test: harden secret file helper coverage 2026-03-13 18:54:43 +00:00
Peter Steinberger
d4d7174773 test: expand npm registry spec coverage 2026-03-13 18:54:21 +00:00
Peter Steinberger
cf39c03801 test: tighten git root helper coverage 2026-03-13 18:53:02 +00:00
Peter Steinberger
30dbd1a598 test: tighten shared usage helper coverage 2026-03-13 18:52:52 +00:00
0xffee
5ba1bfdb7b
refactor: remove redundant ?? undefined in Slack probe (#44775)
Merged via squash.

Prepared head SHA: ecc73fe47c5ba8be28005e8752cff85eb01ae643
Co-authored-by: Cafexss <13113185+Cafexss@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 21:52:15 +03:00
Peter Steinberger
9e28f5aac2 test: tighten abort signal coverage 2026-03-13 18:51:21 +00:00
Peter Steinberger
690f7bba97 test: expand env helper coverage 2026-03-13 18:51:02 +00:00
Peter Steinberger
5dd9389c25 test: tighten fixed window limiter coverage 2026-03-13 18:49:41 +00:00
Peter Steinberger
8f86cb92ac test: harden device identity state dir coverage 2026-03-13 18:49:21 +00:00
Peter Steinberger
ec2663ee5d test: simplify heartbeat reason coverage 2026-03-13 18:48:12 +00:00
Peter Steinberger
a6b4294bfd test: expand remote skill eligibility coverage 2026-03-13 18:48:03 +00:00
Peter Steinberger
6cb8729952
fix: harden windows gateway stop cleanup 2026-03-13 18:47:35 +00:00
Peter Steinberger
51fe0bf663 test: tighten secure random coverage 2026-03-13 18:46:31 +00:00
Peter Steinberger
0db1c31103 test: tighten install mode and allowlist coverage 2026-03-13 18:46:11 +00:00
xingsy97
1bf56e711a
fix(docker): exclude .env from Docker build context (#44956) 2026-03-13 11:42:11 -07:00
Peter Steinberger
897910ea4f test: expand wide-area dns helper coverage 2026-03-13 18:38:36 +00:00
Peter Steinberger
7eeefb3813 test: harden safe open coverage 2026-03-13 18:38:21 +00:00
Peter Steinberger
389de66b25 refactor: share browser auth test helpers 2026-03-13 18:38:12 +00:00
Peter Steinberger
03c2814124 refactor: share line webhook test helpers 2026-03-13 18:38:12 +00:00
Peter Steinberger
565dc0d17b refactor: share exec approval registration context 2026-03-13 18:38:12 +00:00
Peter Steinberger
e003038261 refactor: share agent snapshot and scope test fixtures 2026-03-13 18:38:12 +00:00
Peter Steinberger
05a1b0c3ae refactor: share telegram network test helpers 2026-03-13 18:38:12 +00:00
Peter Steinberger
41c9e3ead0 refactor: share cron and zalo monitor test helpers 2026-03-13 18:38:12 +00:00
Peter Steinberger
99b274592d refactor: share doctor cron store fixtures 2026-03-13 18:38:12 +00:00
Peter Steinberger
a3ece09d19 refactor: share control ui hardlink asset setup 2026-03-13 18:38:12 +00:00
Peter Steinberger
6a1ba52ad5 refactor: share gateway probe auth warnings 2026-03-13 18:38:12 +00:00
Peter Steinberger
07dacec904 refactor: share embedded attempt test harness 2026-03-13 18:38:12 +00:00
Peter Steinberger
6cc86ad211 refactor: share gateway credential secretref assertions 2026-03-13 18:38:12 +00:00
Peter Steinberger
2f58647033 refactor: share plugin route auth test harness 2026-03-13 18:38:12 +00:00
Peter Steinberger
7cb6553ce8 fix: pass injected config to session tools 2026-03-13 18:38:12 +00:00
Peter Steinberger
198c2482ee refactor: share gateway session store migration 2026-03-13 18:38:12 +00:00
Peter Steinberger
6464149031 refactor: share feishu webhook monitor harness 2026-03-13 18:38:12 +00:00
Peter Steinberger
88b87d893d refactor: share temp dir test helper 2026-03-13 18:38:12 +00:00
Peter Steinberger
b5349f7563 refactor: share startup auth token assertions 2026-03-13 18:38:12 +00:00
Peter Steinberger
8633d2e0a9 refactor: share system presence version checks 2026-03-13 18:38:12 +00:00
Peter Steinberger
b697c05354 refactor: share discord allowlist name matching 2026-03-13 18:38:12 +00:00
Peter Steinberger
3bf3ebf514 refactor: share exec approval dm route checks 2026-03-13 18:38:12 +00:00
Peter Steinberger
31c8bb9167 refactor: share agent wait dedupe test entries 2026-03-13 18:38:12 +00:00
Peter Steinberger
db9c755045 refactor: share readiness test harness 2026-03-13 18:38:11 +00:00
Peter Steinberger
06bdfc403e refactor: share system run command resolution 2026-03-13 18:38:11 +00:00
Keelan Fadden-Hopper
fc408bba37
Fix incorrect rendering of brave costs in docs (#44989)
Merged via squash.

Prepared head SHA: 8c69de822273938cb57cf73371101fcc6e10ece2
Co-authored-by: keelanfh <19519457+keelanfh@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 21:37:39 +03:00
Peter Steinberger
5b63f6486f
docs: note preferred fresh parallels macos snapshot 2026-03-13 18:37:00 +00:00
Peter Steinberger
0bf930bdc7 test: harden agent event bus coverage 2026-03-13 18:36:23 +00:00
Peter Steinberger
6a9285d1f5 test: tighten byte count and file identity coverage 2026-03-13 18:35:55 +00:00
Peter Steinberger
54998a1042 test: expand exec wrapper helper coverage 2026-03-13 18:34:42 +00:00
Peter Steinberger
c1b3a49320 test: expand heartbeat event filter coverage 2026-03-13 18:34:13 +00:00
Peter Steinberger
5ea03efe92
fix: harden windows gateway lifecycle 2026-03-13 18:33:59 +00:00
Peter Steinberger
84a2a289e6 test: tighten scp host coverage 2026-03-13 18:32:45 +00:00
Peter Steinberger
9c343fb3db test: tighten small infra helper coverage 2026-03-13 18:31:59 +00:00
Peter Steinberger
8cef6f2120 test: tighten cli root option coverage 2026-03-13 18:30:36 +00:00
Peter Steinberger
f0a266cb86 test: expand archive path helper coverage 2026-03-13 18:29:56 +00:00
Peter Steinberger
bc9a9cf972 test: expand update channel helper coverage 2026-03-13 18:29:09 +00:00
Peter Steinberger
cc3846d1b5 test: simplify numeric parsing coverage 2026-03-13 18:28:05 +00:00
Peter Steinberger
f5ab0c1d32 test: tighten retry helper coverage 2026-03-13 18:26:42 +00:00
Vincent Koc
cc5168b5c3 Fix plugin update dependency failures and dedupe warnings 2026-03-13 11:26:14 -07:00
Peter Steinberger
1d300c416d test: simplify home dir coverage 2026-03-13 18:25:54 +00:00
Peter Steinberger
fbc06f1926 test: simplify tailscale helper coverage 2026-03-13 18:24:02 +00:00
Peter Steinberger
e1b9250dea test: simplify method scope coverage 2026-03-13 18:21:48 +00:00
Peter Steinberger
5aa79f1ba4 test: harden guarded fetch redirect coverage 2026-03-13 18:21:02 +00:00
Peter Steinberger
f3d4bb4103 test: simplify ssrf hostname coverage 2026-03-13 18:20:08 +00:00
Peter Steinberger
3e8d9bc6ea test: refine telegram token coverage 2026-03-13 18:19:27 +00:00
Peter Steinberger
431463dec2 test: simplify config patch validation coverage 2026-03-13 18:15:30 +00:00
Peter Steinberger
584e3c2916 test: refine http body limit coverage 2026-03-13 18:13:39 +00:00
Peter Steinberger
29b9e21b7b test: simplify auth rate limit coverage 2026-03-13 18:12:24 +00:00
Peter Steinberger
2920d61f18 test: tighten minimax usage coverage 2026-03-13 18:10:40 +00:00
Peter Steinberger
4ed3b62f01 test: refine telegram network and install source coverage 2026-03-13 18:08:48 +00:00
Peter Steinberger
bec76be592 test: simplify talk config and path env coverage 2026-03-13 18:06:53 +00:00
Peter Steinberger
8f4e77e72f test: tighten channel auth and network coverage 2026-03-13 18:06:39 +00:00
Peter Steinberger
572df97179 test: simplify provider auth normalization coverage 2026-03-13 18:05:46 +00:00
Peter Steinberger
91d4f5cd2f test: simplify control ui http coverage 2026-03-13 18:03:35 +00:00
Frank Yang
987c254eea
test: annotate chat abort helper exports (#45346) 2026-03-14 02:03:14 +08:00
Peter Steinberger
1f85c9af68 test: simplify runtime config coverage 2026-03-13 18:00:03 +00:00
Peter Steinberger
e25fa446e8 test: refine gateway auth helper coverage 2026-03-13 17:58:28 +00:00
Peter Steinberger
91f1894372 test: tighten server method helper coverage 2026-03-13 17:57:05 +00:00
Peter Steinberger
981062a94e test: simplify outbound channel coverage 2026-03-13 17:55:55 +00:00
Peter Steinberger
a68caaf719 test: dedupe infra runtime and heartbeat coverage 2026-03-13 17:54:38 +00:00
Peter Steinberger
118abfbdb7 test: simplify trusted proxy coverage 2026-03-13 17:52:49 +00:00
Peter Steinberger
87c447ed46 test: tighten failover classifier coverage 2026-03-13 17:51:36 +00:00
Peter Steinberger
f5b006f6a1 test: simplify model ref normalization coverage 2026-03-13 17:49:32 +00:00
Peter Steinberger
2d32cf2839 test: harden infra formatter and retry coverage 2026-03-13 17:47:47 +00:00
Peter Steinberger
4aec20d365 test: tighten gateway helper coverage 2026-03-13 17:45:21 +00:00
Peter Steinberger
9b5000057e ci: remove Android Node 20 action warnings 2026-03-13 17:41:58 +00:00
Frank Yang
7778627b71
fix(ollama): hide native reasoning-only output (#45330) Thanks @xi7ang
Co-authored-by: xi7ang <266449609+xi7ang@users.noreply.github.com>
Co-authored-by: Frank Yang <vibespecs@gmail.com>
2026-03-14 01:38:06 +08:00
Peter Steinberger
ee1d4eb29d test: align chat abort helpers with gateway handler types 2026-03-13 17:33:03 +00:00
Peter Steinberger
644fb76960 refactor: share node pending test client 2026-03-13 17:29:59 +00:00
Peter Steinberger
8de94abfbc refactor: share chat abort test helpers 2026-03-13 17:29:59 +00:00
Peter Steinberger
4a00cefe63 refactor: share outbound plugin test results 2026-03-13 17:29:59 +00:00
Peter Steinberger
369430f9ab refactor: share tlon upload test mocks 2026-03-13 17:29:59 +00:00
Peter Steinberger
6a812b621d ci: modernize GitHub Actions workflow versions 2026-03-13 16:57:23 +00:00
Peter Steinberger
e358d57fb5 refactor: share feishu reply fallback flow 2026-03-13 16:51:59 +00:00
Peter Steinberger
a14a32695d refactor: share feishu reaction client setup 2026-03-13 16:51:59 +00:00
Peter Steinberger
49f3fbf726 fix: restore cron manual run type narrowing 2026-03-13 16:51:59 +00:00
Peter Steinberger
acfb95e2c6 refactor: share tlon channel put requests 2026-03-13 16:51:59 +00:00
Peter Steinberger
e351a86290 refactor: share node wake test apns fixtures 2026-03-13 16:51:59 +00:00
Peter Steinberger
3ccf5f9dc8 refactor: share imessage inbound test fixtures 2026-03-13 16:51:59 +00:00
Peter Steinberger
592dd35ce9 refactor: share directory config helpers 2026-03-13 16:51:59 +00:00
Peter Steinberger
b6b5e5caac refactor: deduplicate push test fixtures 2026-03-13 16:51:59 +00:00
Peter Steinberger
fb40b09157 refactor: share feishu media client setup 2026-03-13 16:51:59 +00:00
Peter Steinberger
6b04ab1e35 refactor: share teams drive upload flow 2026-03-13 16:51:59 +00:00
Peter Steinberger
e94ac57f80 refactor: reuse gateway talk provider schema fields 2026-03-13 16:51:59 +00:00
Peter Steinberger
7b8e48ffb6 refactor: share cron manual run preflight 2026-03-13 16:51:59 +00:00
Peter Steinberger
1ff8de3a8a test: deduplicate session target discovery cases 2026-03-13 16:51:59 +00:00
Peter Steinberger
a4525b721e refactor: deduplicate nextcloud send context 2026-03-13 16:51:59 +00:00
Peter Steinberger
6b07604d64 refactor: share nextcloud target normalization 2026-03-13 16:51:59 +00:00
Peter Steinberger
ef8cc3d0fb refactor: share tlon inline text rendering 2026-03-13 16:51:59 +00:00
Peter Steinberger
966653e174 ci: suppress expected zizmor pull_request_target findings 2026-03-13 16:48:34 +00:00
Peter Steinberger
41718404a1 ci: opt workflows into Node 24 action runtime 2026-03-13 16:41:22 +00:00
Peter Steinberger
261a40dae1 fix: narrow acpx health failure handling 2026-03-13 16:30:27 +00:00
Peter Steinberger
3f37afd18c refactor: extract acpx event builders 2026-03-13 16:30:27 +00:00
Peter Steinberger
501837058c refactor: share outbound media payload sequencing 2026-03-13 16:30:27 +00:00
Peter Steinberger
a37e25fa21 refactor: deduplicate media store writes 2026-03-13 16:30:27 +00:00
Peter Steinberger
f4ed317083 refactor: deduplicate acpx availability checks 2026-03-13 16:30:27 +00:00
Peter Steinberger
202765c810
fix: quiet local windows gateway auth noise 2026-03-13 16:22:13 +00:00
Peter Steinberger
394fd87c2c
fix: clarify gated core tool warnings 2026-03-13 15:38:07 +00:00
Max aka Mosheh
55e79adf69
fix: resolve target agent workspace for cross-agent subagent spawns (#40176)
Merged via squash.

Prepared head SHA: 2378e40383f194557c582b8e28976e57dfe03e8a
Co-authored-by: moshehbenavraham <17122072+moshehbenavraham@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-03-13 12:09:51 -03:00
Val Alexander
ca414735b9
ui: mobile navigation drawer, theme variant refinements & skills fix (#45107) thanks @BunsDev
## Summary

- Mobile navigation drawer with slide-over behavior at ≤1100px
- Topnav & sidebar shell restructure with brand eyebrow
- Chat model selection picker with optimistic caching + rollback
- Nav breakpoint gap fix (769–1100px toggle visibility)
- Skills page autofill pollution fix (autocomplete=off)
- Delete confirm popover positioning (left/right by role)
- Effective collapsed state propagation to nav items in drawer mode
- Duplicate CSS selector consolidation
- Session key race condition fixes in async model patching
- 2 new test files + expanded test coverage (23 tests)

Co-authored-by: Nova <nova@openclaw.ai>
2026-03-13 09:44:05 -05:00
정우용
72b6a11a83
fix: preserve persona and language continuity in compaction summaries (#10456)
Merged via squash.

Prepared head SHA: 4518fb20e1037f87493e3668621cb1a45ab8233e
Co-authored-by: keepitmello <71975659+keepitmello@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-13 07:40:32 -07:00
Peter Steinberger
80e7da92ce
fix: stabilize macos daemon onboarding 2026-03-13 13:47:09 +00:00
Radek Sienkiewicz
0a3b9a9a09
fix(ui): keep shared auth on insecure control-ui connects (#45088)
Merged via squash.

Prepared head SHA: 99eb3fd9281549a4e012b63eb9608dc47455ad03
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-13 14:25:31 +01:00
Peter Steinberger
3cf06f7939
docs(plugins): clarify workspace shadowing 2026-03-13 13:15:46 +00:00
Peter Steinberger
be8d51c301
fix(node-host): harden perl approval binding 2026-03-13 13:09:36 +00:00
Peter Steinberger
2f03de029c
fix(node-host): harden pnpm approval binding 2026-03-13 12:59:55 +00:00
ingyukoh
af4731aa5f
fix(discovery): add missing domain to wideArea Zod config schema (#35615)
Merged via squash.

Prepared head SHA: d81d3321b6aaf4ca4f3c63989b6b9ac431b60fbb
Co-authored-by: ingyukoh <6015960+ingyukoh@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 15:52:54 +03:00
Sovtoshi
e9b1e856a0
chore(gitignore): add docker-compose override (#42879) 2026-03-13 15:25:48 +03:00
Nimrod Gutman
496176d738
feat(ios): add onboarding welcome pager (#45054)
* feat(ios): add onboarding welcome pager

* feat(ios): add onboarding welcome pager (#45054) (thanks @ngutman)
2026-03-13 14:24:15 +02:00
Alex Zaytsev
61429230b2
fix(signal): add groups config to Signal channel schema (#27199)
Merged via squash.

Prepared head SHA: 4ba4a39ddf10eaa3d6ad3f2975c088547f5373e5
Co-authored-by: unisone <32521398+unisone@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 15:14:30 +03:00
stim64045-spec
4e68684bd2
fix: restore web fetch firecrawl config in runtime zod schema (#42583)
Merged via squash.

Prepared head SHA: e37f965b8ef5370f07e1492499c6a87aa26a178a
Co-authored-by: stim64045-spec <259352523+stim64045-spec@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 14:56:26 +03:00
Ayaan Zaidi
45721d5dec fix: polish Android QR scanner onboarding (#45021) 2026-03-13 17:13:54 +05:30
Ayaan Zaidi
b934cb49c7 fix(android): use Google Code Scanner for onboarding QR 2026-03-13 17:13:54 +05:30
atian8179
b72c87712d
fix(config): add missing params field to agents.list[] validation schema (#41171)
Merged via squash.

Prepared head SHA: 9522761cf1d5f5318b6b44abfb1292384acd9c37
Co-authored-by: atian8179 <255488364+atian8179@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 14:29:36 +03:00
Ayaan Zaidi
f9ea879729
docs(contributing): update Android app ownership 2026-03-13 15:19:16 +05:30
xingsy97
2c39cd0953
fix(agents): rephrase session reset prompt to avoid Azure content filter (#43403)
* fix(agents): rephrase session reset prompt to avoid Azure content filter

Azure OpenAI's content filter flags the phrase 'Execute your Session
Startup sequence now' as potentially harmful, causing /new and /reset
to return 400 for all Azure-hosted deployments.

Replace 'Execute ... now' with 'Run your Session Startup sequence' in
session-reset-prompt.ts and post-compaction-context.ts. The semantics
are identical but the softer phrasing avoids the false-positive.

Closes #42769

* ci: retrigger checks (windows shard timeout)

* fix: add changelog for Azure startup prompt fix (#43403) (thanks @xingsy97)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-13 15:07:03 +05:30
xingsy97
b28a2257f7 test(config): cover requiresOpenAiAnthropicToolPayload in compat schema fixture
Adds the missing requiresOpenAiAnthropicToolPayload field to the
model-compat schema acceptance test, guarding against regressions
like #43339 where onboarding fails with "Unrecognized key".

Closes #43339
2026-03-13 15:02:28 +05:30
cheapestinference
60cb1d683c
fix(agents): respect explicit user compat overrides for non-native openai-completions (#44432)
Reviewed-by: @frankekn
2026-03-13 17:30:24 +08:00
Kaneki
84428bbba6
Android: fix HttpURLConnection leak in TalkModeVoiceResolver (#43780)
* Android: fix HttpURLConnection leak in TalkModeVoiceResolver.listVoices

* fix null errorStream NPE and preserve HTTP keep-alive

* fix: restore voice resolver disconnect cleanup

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-13 14:59:21 +05:30
Jealous
4d3a2f674b
Docker: add OPENCLAW_TZ timezone support (#34119)
* Docker: add OPENCLAW_TZ timezone support

* fix: validate docker timezone names

* fix: support Docker timezone override (#34119) (thanks @Lanfei)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-13 14:51:55 +05:30
Jealous
a3eed2b70f
fix(agents): avoid injecting memory file twice on case-insensitive mounts (#26054)
* fix(agents): avoid injecting memory file twice on case-insensitive mounts

On case-insensitive file systems mounted into Docker from macOS, both
MEMORY.md and memory.md pass fs.access() even when they are the same
underlying file. The previous dedup via fs.realpath() failed in this
scenario because realpath does not normalise case through the Docker
mount layer, so both paths were treated as distinct entries and the
same content was injected into the bootstrap context twice, wasting
tokens.

Fix by replacing the collect-then-dedup approach with an early-exit:
try MEMORY.md first; fall back to memory.md only when MEMORY.md is
absent. This makes the function return at most one entry regardless
of filesystem case-sensitivity.

* docs: clarify singular memory bootstrap fallback

* fix: note memory bootstrap fallback docs and changelog (#26054) (thanks @Lanfei)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-03-13 14:39:51 +05:30
Ayaan Zaidi
7638052178 fix: note android chat settings redesign (#44894) 2026-03-13 14:31:39 +05:30
Ayaan Zaidi
c04544891d feat(android): consolidate Settings into grouped card sections
Remove header bloat, merge Node info into a single Device card,
group permissions into Media/Notifications/Data Access cards with
internal dividers, and combine Screen+Debug into Preferences.
Sections reduced from 9 to 6.
2026-03-13 14:31:39 +05:30
Ayaan Zaidi
8b0e16a1c8 feat(android): soften chat role labels and deduplicate session header
Rename role labels to You/OpenClaw/System, update streaming label to
OpenClaw · Live, and remove the redundant SESSION row + Connected pill
since the top bar and chip row already convey both.
2026-03-13 14:31:39 +05:30
Ayaan Zaidi
c761b5b8a8 feat(android): compact chat composer layout
Remove MESSAGE label and divider, let text field auto-size instead
of fixed 92dp, and merge Detail/Attach into the bottom action row.
2026-03-13 14:31:39 +05:30
Ayaan Zaidi
720b9d2c45 feat(android): add speaker label and status pill to Voice tab
Add text label under speaker toggle, balance layout with matching
spacer column, and wrap status text in a colored pill.
2026-03-13 14:31:39 +05:30
Ayaan Zaidi
beff0cf02c feat(android): redesign Connect tab with unified status cards
Merge endpoint and status into a single grouped card with icons.
Split connect/disconnect into context-aware buttons.
2026-03-13 14:31:39 +05:30
Jealous
e986aa175f
docs: fix session key :dm: → :direct (#26506) 2026-03-13 14:28:33 +05:30
Frank Yang
5ca0233db0
fix(agents): drop Anthropic thinking blocks on replay (#44843)
* agents: drop Anthropic thinking blocks on replay

* fix: extend anthropic replay sanitization openclaw#44429 thanks @jmcte

* fix: extend anthropic replay sanitization openclaw#44843 thanks @jmcte

* test: add bedrock replay sanitization coverage openclaw#44843

* test: cover anthropic provider drop-thinking hints openclaw#44843

---------

Co-authored-by: johnmteneyckjr <john.m.teneyck@gmail.com>
2026-03-13 16:57:56 +08:00
Frank Yang
0705225274
docs: fix changelog credit for xhigh help (#44874) 2026-03-13 16:40:53 +08:00
Frank Yang
4e27c9b958
CLI: align xhigh thinking help text (#44819)
Merged via squash.

Prepared head SHA: ff1f12717692d7745b34c5aef7c1dde3c6a4500d
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-13 16:37:11 +08:00
Frank Yang
f07033ed3f
fix: address delivery dedupe review follow-ups (#44666)
Merged via squash.

Prepared head SHA: 8e6d254cc4781df66ee02b683c4ad72b5a633502
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-13 16:18:01 +08:00
Jonatan
5b06619c67
Updated default model from openai-codex/gpt-5.3-codex to openai-codex/gpt-5.4 in tests. (#44367)
Merged via squash.

Prepared head SHA: c372ba691b9964dc986c3a4880a7413ab8fb23f7
Co-authored-by: jrrcdev <19454127+jrrcdev@users.noreply.github.com>
Co-authored-by: dvrshil <81693876+dvrshil@users.noreply.github.com>
Reviewed-by: @dvrshil
2026-03-13 00:13:54 -07:00
Ayaan Zaidi
d40a4e343c fix: add gateway session reset routing coverage (#44773) (thanks @Lanfei) 2026-03-13 12:39:44 +05:30
Jealous
3066607037 fix(session): preserve lastAccountId and lastThreadId on session reset 2026-03-13 12:39:44 +05:30
Ayaan Zaidi
aae75b5e57
feat(android): redesign onboarding flow UI
- Welcome: replace bullet list with icon+subtitle feature cards
- Gateway: simplify to single instruction line, collapse advanced by default, remove verbose developer text
- Permissions: group into System/Media/Personal Data sections, rewrite subtitles to plain English, style "Not granted" with warning color
- Review: replace plain text fields with icon cards matching Welcome style, add colored status cards for connect/pairing states
- Remove redundant "FIRST RUN" label, "Step X of 4" text, and StepRailWrap dividers
2026-03-13 12:25:39 +05:30
Frank Yang
80e6701959 test: stabilize sanitize session history smoke checks 2026-03-13 14:50:03 +08:00
Frank Yang
fa6ff39b9b fix: recover outbound plugins from the active registry 2026-03-13 14:32:07 +08:00
Ayaan Zaidi
402f2556b9
fix(android): clip CommandBlock accent bar to rounded container bounds 2026-03-13 11:39:23 +05:30
Josh Lehman
93e7fcaa73
docs: move post-release changelog entries to Unreleased (#44691)
4 entries were added to the 2026.3.12 section after the v2026.3.12
tag was cut. Move them to ## Unreleased where they belong.

Verified: 2026.3.12 section now matches the 74 entries present at
the v2026.3.12 release tag (28d64c48e).
2026-03-12 22:42:06 -07:00
Peter Steinberger
a0f09a4589
test: fix windows startup fallback mock typing 2026-03-13 05:00:55 +00:00
Peter Steinberger
32d8ec9482
fix: harden windows gateway fallback launch 2026-03-13 04:58:35 +00:00
Josh Lehman
6d0939d84e
fix: handle Discord gateway metadata fetch failures (#44397)
Merged via squash.

Prepared head SHA: edd17c0effe4f90887ac94ce549f44a69fe19eb2
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-12 21:52:17 -07:00
Ayaan Zaidi
8023f4c701
fix(telegram): thread media transport policy into SSRF (#44639)
* fix(telegram): preserve media download transport policy

* refactor(telegram): thread media transport policy

* fix(telegram): sync fallback media policy

* fix: note telegram media transport fix (#44639)
2026-03-13 10:11:43 +05:30
Peter Steinberger
c38e7b0270 test(utils): await temp dir cleanup in async tests 2026-03-13 04:38:46 +00:00
Peter Steinberger
16ececf0a6
chore: bump version to 2026.3.13 2026-03-13 04:38:32 +00:00
Efe Büken
771066d122
fix(compaction): use full-session token count for post-compaction sanity check (#28347)
Merged via squash.

Prepared head SHA: cf4eab1c51e6b8890e23c2d7172313c40cd2fe04
Co-authored-by: efe-arv <259833796+efe-arv@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 21:26:30 -07:00
Peter Steinberger
70d7a0854c
chore: update appcast for 2026.3.12 release 2026-03-13 04:26:20 +00:00
Peter Steinberger
fc2b796f02 test(proxy): make env proxy tests windows-safe 2026-03-13 04:17:10 +00:00
Peter Steinberger
6472949f25 fix(plugins): normalize bundled provider ids 2026-03-13 04:10:06 +00:00
Cypherm
61d219cb39
feat: show status reaction during context compaction (#35474)
Merged via squash.

Prepared head SHA: 145a7b7c4e1939718c41a300899ae813bd9c511b
Co-authored-by: Cypherm <28184436+Cypherm@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 21:06:15 -07:00
Peter Steinberger
4e872521f0
fix(ui): restore native web /status 2026-03-13 04:04:09 +00:00
Peter Steinberger
0c8ea8d987 test(ui): add jsdom runtime for vitest dom suites 2026-03-13 03:50:52 +00:00
Peter Steinberger
bffce8ea4f
fix(ui): harden avatar fallback regressions 2026-03-13 03:46:30 +00:00
Peter Steinberger
4656317770
fix(ui): resolve control chat avatar fallback 2026-03-13 03:42:11 +00:00
Vincent Koc
7509c4a057 UI: fix mounted avatar meta fallback 2026-03-12 23:39:53 -04:00
Peter Steinberger
c52f23f794 test(qmd): make windows cli fixtures explicit 2026-03-13 03:37:41 +00:00
Vincent Koc
fd07132389 UI: fix control chat logo fallback 2026-03-12 23:36:17 -04:00
Peter Steinberger
f803215474 fix(ci): restore full gate 2026-03-13 03:34:47 +00:00
scoootscooob
255414032f changelog: move ACP final-snapshot entry to active 2026.3.12 section 2026-03-12 20:31:03 -07:00
Peter Steinberger
0f290fe6d6
fix: narrow Slack outbound blocks opt type 2026-03-13 03:29:00 +00:00
scoootscooob
17c954c46e
fix(acp): preserve final assistant message snapshot before end_turn (#44597)
Process messageData via handleDeltaEvent for both delta and final states
before resolving the turn, so ACP clients no longer drop the last visible
assistant text when the gateway sends the final message body on the
terminal chat event.

Closes #15377
Based on #17615

Co-authored-by: PJ Eby <3527052+pjeby@users.noreply.github.com>
2026-03-12 20:23:57 -07:00
Peter Steinberger
2201d533fd fix: enable fast mode for isolated cron runs 2026-03-13 03:21:57 +00:00
Vincent Koc
42efd98ff8
Slack: support Block Kit payloads in agent replies (#44592)
* Slack: route reply blocks through outbound adapter

* Slack: cover Block Kit outbound payloads

* Changelog: add Slack Block Kit agent reply entry
2026-03-12 23:18:59 -04:00
Peter Steinberger
433e65711f
fix: fall back to a startup entry for windows gateway install 2026-03-13 03:18:17 +00:00
Peter Steinberger
a60a4b4b5e test(gateway): avoid hoisted reply mock tdz 2026-03-13 03:17:51 +00:00
Peter Steinberger
0979264ed5 test(qmd): make windows cli fixtures explicit 2026-03-13 03:13:56 +00:00
Peter Steinberger
496ca3a637 fix(feishu): fail closed on webhook signature checks 2026-03-13 03:13:56 +00:00
Peter Steinberger
ec3c20d96d
test: harden plugin fixture permissions on macos 2026-03-13 03:13:25 +00:00
Peter Steinberger
fb9984a774 fix(memory): stop forcing Windows qmd cmd shims 2026-03-13 03:09:14 +00:00
Ayaan Zaidi
ff2368af57 fix: stop false cron payload-kind warnings in doctor (#44012) (thanks @shuicici) 2026-03-13 08:38:52 +05:30
shuicici
42613b9baa fix(cron): compare raw value not trimmed in normalizePayloadKind 2026-03-13 08:38:52 +05:30
shuicici
3e2c776aaf fix(cron): avoid false legacy payload kind migrations 2026-03-13 08:38:52 +05:30
Peter Steinberger
21fa50f564
test: harden plugin env-scoped fixtures 2026-03-13 03:01:47 +00:00
Peter Steinberger
08da1b47ba fix: use build-stage image for docker live tests 2026-03-13 02:59:36 +00:00
Peter Steinberger
6b14e6b55b test(commands): align slash-command config persistence coverage 2026-03-13 02:51:55 +00:00
Peter Steinberger
7dc447f79f fix(gateway): strip unbound scopes for shared-auth connects 2026-03-13 02:51:55 +00:00
Peter Steinberger
b858d6c3a9
fix: clarify windows onboarding gateway health 2026-03-13 02:40:40 +00:00
Dinakar Sarbada
23c7fc745f
refactor(agents): replace console.warn with SubsystemLogger in compaction-safeguard.ts (#9974)
Merged via squash.

Prepared head SHA: 35dcc5ba354ad7f058d796846bda9d1f8a416e04
Co-authored-by: dinakars777 <250428393+dinakars777@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 19:34:55 -07:00
Peter Steinberger
c8439f6587 fix: import oauth types from the oauth entrypoint 2026-03-13 02:17:00 +00:00
Peter Steinberger
296a106f49 test: stabilize hooks loader log assertion on Windows 2026-03-13 02:17:00 +00:00
Peter Steinberger
4fb3b88e57
docs: reorder latest release changelog 2026-03-13 02:11:50 +00:00
Peter Steinberger
d6d01f853f
fix: align Ollama onboarding docs before landing (#43473) (thanks @BruceMacD)
(cherry picked from commit 19fa274343a102ca85c7679ec28c5a3503a99f55)
2026-03-13 02:03:54 +00:00
Bruce MacDonald
f906bf58db
docs(ollama): update onboarding flow
Co-Authored-By: Jeffrey Morgan <jmorganca@gmail.com>
(cherry picked from commit e8ca2ff4e522f2d971801a537b3c4fdfecde0711)
2026-03-13 02:03:54 +00:00
Peter Steinberger
0068f55dd8
fix(memory): fail closed for Windows qmd wrappers 2026-03-13 01:56:20 +00:00
Peter Steinberger
ddeb423944 fix: quiet Telegram command overflow retry logs 2026-03-13 01:45:56 +00:00
Peter Steinberger
de3e6a8c5b fix(routing): require ids for slack and msteams allowlists 2026-03-13 01:44:42 +00:00
Peter Steinberger
f36d8c09f1 feat(zalouser): audit mutable group allowlists 2026-03-13 01:44:42 +00:00
Peter Steinberger
88244c0942 refactor(zalouser): reuse shared name matching helper 2026-03-13 01:44:42 +00:00
Peter Steinberger
c25e46a433
chore: prepare 2026.3.12 release 2026-03-13 01:38:20 +00:00
Peter Steinberger
72ba05504e
test: resolve rebase conflicts in gateway coverage 2026-03-13 01:38:19 +00:00
Nachx639
e951a42bcb
fix(mac): adopt canonical session key and add reset triggers (#10898)
Add shared native chat handling for /new, /reset, and /clear.

This also aligns main session key handling in the shared chat UI and includes follow-up test and CI fixes needed to keep the branch mergeable.

Co-authored-by: Nachx639 <71144023+Nachx639@users.noreply.github.com>
Co-authored-by: Luke <92253590+ImLukeF@users.noreply.github.com>
2026-03-13 12:35:39 +11:00
Peter Steinberger
268a8592de fix: avoid ineffective dynamic imports 2026-03-13 01:33:37 +00:00
Peter Steinberger
b14a5c6713 fix(zalouser): require ids for group allowlist auth 2026-03-13 01:31:17 +00:00
Peter Steinberger
c80da4e72f refactor: validate provider plugin metadata 2026-03-13 01:19:35 +00:00
Peter Steinberger
87ad1ce9b1 refactor: add non-interactive provider plugin setup 2026-03-13 01:19:35 +00:00
Vincent Koc
bcbf429d6b Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  build: sync bundled plugin versions
2026-03-12 21:06:55 -04:00
Vincent Koc
fed24a1311 build: sync bundled plugin versions 2026-03-12 21:06:12 -04:00
Vincent Koc
f12cd92bb3 Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  build: update deps and fix vitest 4 regressions
2026-03-12 21:04:02 -04:00
Peter Steinberger
4dd4e36450
build: update deps and fix vitest 4 regressions 2026-03-13 01:02:00 +00:00
Vincent Koc
8661c271e9 Gateway: preserve trusted-proxy browser scopes 2026-03-12 21:00:43 -04:00
Vincent Koc
b2e21e3792 fix(security): strip Mongolian selectors in exec obfuscation detector 2026-03-12 21:00:00 -04:00
Peter Steinberger
9bbdb5ca94
test(live): add codex instructions to spark probe 2026-03-13 00:53:21 +00:00
Peter Steinberger
d5b3f2ed71
fix(models): keep codex spark codex-only 2026-03-13 00:53:21 +00:00
Vincent Koc
d4f535b203
fix(hooks): fail closed on unreadable loader paths (#44437)
* Hooks: fail closed on unreadable loader paths

* Changelog: note hooks loader hardening

* Tests: cover sanitized hook loader logs

* Hooks: use realpath containment for legacy loaders

* Hooks: sanitize unreadable workspace log path
2026-03-12 20:47:30 -04:00
Vincent Koc
2649c03cdb
fix(hooks): dedupe repeated agent deliveries by idempotency key (#44438)
* Hooks: add hook idempotency key resolution

* Hooks: dedupe repeated agent deliveries by idempotency key

* Tests: cover hook idempotency dedupe

* Changelog: note hook idempotency dedupe

* Hooks: cap hook idempotency key length

* Gateway: hash hook replay cache keys

* Tests: cover hook replay key hardening
2026-03-12 20:43:38 -04:00
Peter Steinberger
d96069f0df
feat: add windows update package spec override 2026-03-12 23:56:48 +00:00
Peter Steinberger
91b701e183
fix: harden windows native updates 2026-03-12 23:42:14 +00:00
Peter Steinberger
35aafd7ca8
feat: add Anthropic fast mode support 2026-03-12 23:39:03 +00:00
Josh Lehman
52e2a7747a
Revert "feat: add --no-test flag to prepare-gates"
This reverts commit ee6bdb3bab26f2796943c6cac03d8f62a5664937.
2026-03-12 16:37:50 -07:00
Peter Steinberger
d5bffcdeab
feat: add fast mode toggle for OpenAI models 2026-03-12 23:31:31 +00:00
Peter Steinberger
ddcaec89e9
fix(node-host): fail closed on ruby approval preload flags 2026-03-12 23:23:54 +00:00
Josh Lehman
ee6bdb3bab
feat: add --no-test flag to prepare-gates
Allows skipping the full test suite during prepare phase.
Testing is deferred to the dedicated Test phase in the pipeline.
2026-03-12 16:22:37 -07:00
Peter Steinberger
86a3149b2e
fix: harden windows npm runtime path 2026-03-12 23:03:19 +00:00
Vincent Koc
92191fcd68 deps: bump openclaw to 2026.3.11
Raise internal OpenClaw constraints to 2026.3.11 and regenerate pnpm lockfile to remove the vulnerable 2026.3.8 resolution.
2026-03-12 19:00:49 -04:00
Peter Steinberger
212afb6950
refactor: clarify pairing setup auth labels 2026-03-12 22:46:28 +00:00
Peter Steinberger
01e4845f6d
refactor: extract websocket handshake auth helpers 2026-03-12 22:46:28 +00:00
Peter Steinberger
1c7ca391a8
refactor: trim bootstrap token metadata 2026-03-12 22:46:28 +00:00
Peter Steinberger
589aca0e6d
refactor: unify gateway connect auth selection 2026-03-12 22:46:28 +00:00
Peter Steinberger
2c8f31135b test: cover provider plugin boundaries 2026-03-12 22:43:55 +00:00
Peter Steinberger
300a093121 refactor: split simple api-key auth providers 2026-03-12 22:38:58 +00:00
Peter Steinberger
fd2b06d463 refactor: split non-interactive auth choice providers 2026-03-12 22:38:58 +00:00
Peter Steinberger
21d1032ca4 refactor: remove legacy provider apply shims 2026-03-12 22:38:58 +00:00
Peter Steinberger
7fd4dea1af refactor: share openai-compatible local discovery 2026-03-12 22:38:58 +00:00
Peter Steinberger
9692dc7668
fix(security): harden nodes owner-only tool gating 2026-03-12 22:27:52 +00:00
Josh Lehman
2622d2453b
fix(ci): restore generated protocol swift outputs (#44411)
Regenerate the Swift protocol models so PushTestResult keeps the transport field required by the current gateway schema, and update protocol:check to diff both generated Swift destinations because the generator writes both files.

Regeneration-Prompt: |
  Investigate the protocol CI failure on current origin/main rather than assuming the earlier fix still held. Confirm whether the generated Swift outputs drifted from the TypeScript gateway schema, identify whether the regression was reintroduced by a later commit, and keep the patch minimal: restore the generated Swift outputs from the existing schema and tighten the protocol check so it verifies every Swift file the generator writes.
2026-03-12 15:25:38 -07:00
Peter Steinberger
319766639a docs: explain plugin architecture 2026-03-12 22:24:35 +00:00
Peter Steinberger
d83491e751 feat: modularize provider plugin architecture 2026-03-12 22:24:35 +00:00
Peter Steinberger
bf89947a8e
fix: switch pairing setup codes to bootstrap tokens 2026-03-12 22:23:07 +00:00
ToToKr
9cd54ea882
fix: skip cache-ttl append after compaction to prevent double compaction (#28548)
Merged via squash.

Prepared head SHA: a4114a52bcff6ed4057cc54d3c629bd723f3d420
Co-authored-by: MoerAI <26067127+MoerAI@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 15:17:18 -07:00
jnMetaCode
7332e6d609
fix(failover): classify HTTP 422 as format and OpenRouter credits as billing (#43823)
Merged via squash.

Prepared head SHA: 4f48e977fe06c5662753d3900fe94f1835cc2dce
Co-authored-by: jnMetaCode <12096460+jnMetaCode@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 00:50:28 +03:00
Peter Steinberger
268e036172
refactor(test): share hook request handler fixtures 2026-03-12 21:44:58 +00:00
Peter Steinberger
eece586747
refactor(security): reuse hook agent routing normalization 2026-03-12 21:44:06 +00:00
Peter Steinberger
445ff0242e
refactor(gateway): cache hook proxy config in runtime state 2026-03-12 21:43:36 +00:00
Peter Steinberger
1d986f1c01
refactor(gateway): move request client ip resolution to net 2026-03-12 21:41:51 +00:00
Peter Steinberger
904db27019
fix(security): audit unrestricted hook agent routing 2026-03-12 21:36:19 +00:00
Peter Steinberger
4da617e178
fix(gateway): honor trusted proxy hook auth rate limits 2026-03-12 21:35:57 +00:00
Rodrigo Uroz
143e593ab8
Compaction Runner: wire post-compaction memory sync (#25561)
Merged via squash.

Prepared head SHA: 6d2bc02cc16429a19b041acd353c08dd2404335f
Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 14:24:29 -07:00
bwjoke
fd568c4f74
fix(failover): classify ZenMux quota-refresh 402 as rate_limit (#43917)
Merged via squash.

Prepared head SHA: 1d58a36a774d06b1493971e8f14f9abc806be6b0
Co-authored-by: bwjoke <1284814+bwjoke@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 00:06:43 +03:00
Wayne
d93db0fc13
fix(failover): classify z.ai network_error stop reason as retryable timeout (#43884)
Merged via squash.

Prepared head SHA: 9660f6cd5bcb8d073fc5575bbba2bf3792b29de3
Co-authored-by: hougangdev <105773686+hougangdev@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-13 00:00:44 +03:00
Andrew Demczuk
3700279b14
docs: codify American English spelling convention (#44159) 2026-03-12 14:45:17 -05:00
Josh Lehman
50cc375c11
feat(context-engine): plumb sessionKey into all ContextEngine methods (#44157)
Merged via squash.

Prepared head SHA: 0b341f6f4ce487055d8bc0c0d335c42577941592
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-12 12:43:36 -07:00
Marcus Castro
e525957b4f
fix(sandbox): restore spawned workspace handoff (#44307) 2026-03-12 16:12:08 -03:00
Vincent Koc
4ca84acf24
fix(runtime): duplicate messages, share singleton state across bundled chunks (#43683)
* Tests: add fresh module import helper

* Process: share command queue runtime state

* Agents: share embedded run runtime state

* Reply: share followup queue runtime state

* Reply: share followup drain callback state

* Reply: share queued message dedupe state

* Reply: share inbound dedupe state

* Tests: cover shared command queue runtime state

* Tests: cover shared embedded run runtime state

* Tests: cover shared followup queue runtime state

* Tests: cover shared inbound dedupe state

* Tests: cover shared Slack thread participation state

* Slack: share sent thread participation state

* Tests: document fresh import helper

* Telegram: share draft stream runtime state

* Tests: cover shared Telegram draft stream state

* Telegram: share sent message cache state

* Tests: cover shared Telegram sent message cache

* Telegram: share thread binding runtime state

* Tests: cover shared Telegram thread binding state

* Tests: avoid duplicate shared queue reset

* refactor(runtime): centralize global singleton access

* refactor(runtime): preserve undefined global singleton values

* test(runtime): cover undefined global singleton values

---------

Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
2026-03-12 14:59:27 -04:00
Vincent Koc
08aa57a3de
Commands: require owner for /config and /debug (#44305)
* Commands: add non-owner gate helper

* Commands: enforce owner-only config and debug

* Commands/test: cover owner-only config and debug

* Changelog: add owner-only config debug entry

* Commands/test: split config owner gating section

* Commands: redact sender ids in verbose command logs

* Commands: preserve internal read-only config access

* Commands/test: keep operator.write config show coverage non-owner
2026-03-12 14:58:14 -04:00
Josh Lehman
fda4965818
fix: format CSS files for oxfmt (#44313) 2026-03-12 11:58:00 -07:00
Vincent Koc
5e389d5e7c
Gateway/ws: clear unbound scopes for shared-token auth (#44306)
* Gateway/ws: clear unbound shared-auth scopes

* Gateway/auth: cover shared-token scope stripping

* Changelog: add shared-token scope stripping entry

* Gateway/ws: preserve allowed control-ui scopes

* Gateway/auth: assert control-ui admin scopes survive allowed device-less auth

* Gateway/auth: cover shared-password scope stripping
2026-03-12 14:52:24 -04:00
liyuan97
55f47e5ce6
onboard(minimax): flatten auth to 4 direct choices, unify CN/Global under single provider (#44284)
Replace the multi-step MiniMax onboarding wizard with 4 flat options:
- MiniMax Global — OAuth (minimax.io)
- MiniMax Global — API Key (minimax.io)
- MiniMax CN — OAuth (minimaxi.com)
- MiniMax CN — API Key (minimaxi.com)

Storage changes:
- Unify CN and Global under provider "minimax" (baseUrl distinguishes region)
- Profiles: minimax:global / minimax:cn (both regions can coexist)
- Model ref: minimax/MiniMax-M2.5 (no more minimax-cn/ prefix)
- Remove LM Studio local mode and Lightning/Highspeed choice

Backward compatibility:
- Keep minimax-cn in provider-env-vars for existing configs
- Accept minimax-cn as legacy tokenProvider in CI pipelines
- Error with migration hint for removed auth choices in non-interactive mode
- Warn when dual-profile overwrites shared provider baseUrl

Made-with: Cursor
2026-03-12 11:23:42 -07:00
Vincent Koc
1492ad20a9
Ollama/Kimi: apply Moonshot payload compatibility (#44274)
* Runner: extend Moonshot payload compat to Ollama Kimi

* Changelog: note Ollama Kimi tool routing

* Tests: cover Ollama Kimi payload compat

* Runner: narrow Ollama Kimi payload compat
2026-03-12 14:17:01 -04:00
Val Alexander
2d42588a18
chore(changelog): update CHANGELOG.md to include new features in dashboard-v2, highlighting the refreshed gateway dashboard with modular views and enhanced chat tools (#41503) 2026-03-12 12:56:24 -05:00
Josh Lehman
9cb0fa58c2
fix: restore protocol outputs and stabilize Windows path CI (#44266)
* fix(ci): restore protocol outputs and stabilize Windows path test

Regenerate the Swift protocol models so protocol:check stops failing on main.
Align the session target test helper with the sync production realpath behavior so Windows does not compare runneradmin and RUNNER~1 spellings for the same file.

Regeneration-Prompt: |
  Investigate the failing checks from merged PR #34485 and confirm whether they still affect current main before changing code. Keep the fix tight: do not alter runtime behavior beyond what is required to clear the reproduced CI regressions. Commit the generated Swift protocol outputs for the PushTestResult transport field because protocol:check was failing from stale generated files on main. Also fix the Windows-only session target test by making its helper use the same synchronous realpath behavior as production discovery, so path spelling differences like runneradmin versus RUNNER~1 do not cause a false assertion failure.

* fix(ci): align session target realpath behavior on Windows

Use native realpath for sync session target discovery so it matches the async path on Windows, and update the session target test helper to assert against the same canonical path form.

Regeneration-Prompt: |
  After opening the follow-up PR for the CI regressions from merged PR #34485, inspect the new failing Windows shard instead of assuming the first fix covered every case. Keep scope limited to the session target path mismatch exposed by CI. Fix the inconsistency at the source by making sync session target discovery use the same native realpath canonicalization as the async discovery path on Windows, then update the test helper to match that shared behavior and verify the touched file with targeted tests and file-scoped lint/format checks.

* test: make merge config fixtures satisfy provider type

After rebasing the PR onto current origin/main, the merge helper test fixtures no longer satisfied ProviderConfig because the anthropic provider examples were missing required provider and model fields. Add a shared fully-typed model fixture and explicit anthropic baseUrl values so the test keeps full type coverage under tsgo.

Regeneration-Prompt: |
  Rebase the PR branch for #44266 onto the current origin/main because the failing CI error only reproduced on the merge ref. Re-run the type-check path and inspect src/agents/models-config.merge.test.ts at the exact compiler lines instead of weakening types globally. Keep the fix test-only: make the anthropic ProviderConfig fixtures structurally valid by supplying the required baseUrl and full model definition fields, and keep the shared fixture typed so tsgo accepts it without unknown casts.

* fix: align Windows session store test expectations
2026-03-12 10:55:29 -07:00
Val Alexander
f76a3c5225
feat(ui): dashboard-v2 views refactor (slice 3/3 of dashboard-v2) (#41503)
* feat(ui): add chat infrastructure modules (slice 1 of dashboard-v2)

New self-contained chat modules extracted from dashboard-v2-structure:

- chat/slash-commands.ts: slash command definitions and completions
- chat/slash-command-executor.ts: execute slash commands via gateway RPC
- chat/slash-command-executor.node.test.ts: test coverage
- chat/speech.ts: speech-to-text (STT) support
- chat/input-history.ts: per-session input history navigation
- chat/pinned-messages.ts: pinned message management
- chat/deleted-messages.ts: deleted message tracking
- chat/export.ts: shared exportChatMarkdown helper
- chat-export.ts: re-export shim for backwards compat

Gateway fix:
- Restore usage/cost stripping in chat.history sanitization
- Add test coverage for sanitization behavior

These modules are additive and tree-shaken — no existing code
imports them yet. They will be wired in subsequent slices.

* feat(ui): add utilities, theming, and i18n updates (slice 2 of dashboard-v2)

UI utilities and theming improvements extracted from dashboard-v2-structure:

Icons & formatting:
- icons.ts: expanded icon set for new dashboard views
- format.ts: date/number formatting helpers
- tool-labels.ts: human-readable tool name mappings

Theming:
- theme.ts: enhanced theme resolution and system theme support
- theme-transition.ts: simplified transition logic
- storage.ts: theme parsing improvements for settings persistence

Navigation & types:
- navigation.ts: extended tab definitions for dashboard-v2
- app-view-state.ts: expanded view state management
- types.ts: new type definitions (HealthSummary, ModelCatalogEntry, etc.)

Components:
- components/dashboard-header.ts: reusable header component

i18n:
- Updated en, pt-BR, zh-CN, zh-TW locales with new dashboard strings

All changes are additive or backwards-compatible. Build passes.
Part of #36853.

* feat(ui): dashboard-v2 views refactor (slice 3 of dashboard-v2)

Complete views refactor from dashboard-v2-structure, building on
slice 1 (chat infra, #41497) and slice 2 (utilities/theming, #41500).

Core app wiring:
- app.ts: updated host component with new state properties
- app-render.ts: refactored render pipeline for new dashboard layout
- app-render.helpers.ts: extracted render helpers
- app-settings.ts: theme listener lifecycle fix, cron runs on tab load
- app-gateway.ts: refactored chat event handling
- app-chat.ts: slash command integration

New views:
- views/command-palette.ts: command palette (Cmd+K)
- views/login-gate.ts: authentication gate
- views/bottom-tabs.ts: mobile tab navigation
- views/overview-*.ts: modular overview dashboard (cards, attention,
  event log, hints, log tail, quick actions)
- views/agents-panels-overview.ts: agent overview panel

Refactored views:
- views/chat.ts: major refactor with STT, slash commands, search,
  export, pinned messages, input history
- views/config.ts: restructured config management
- views/agents.ts: streamlined agent management
- views/overview.ts: modular composition from sub-views
- views/sessions.ts: enhanced session management

Controllers:
- controllers/health.ts: new health check controller
- controllers/models.ts: new model catalog controller
- controllers/agents.ts: tools catalog improvements
- controllers/config.ts: config form enhancements

Tests & infrastructure:
- Updated test helpers, browser tests, node tests
- vite.config.ts: build configuration updates
- markdown.ts: rendering improvements

Build passes  | 44 files | +6,626/-1,499
Part of #36853. Depends on #41497 and #41500.

* UI: fix chat review follow-ups

* fix(ui): repair chat clear and attachment regressions

* fix(ui): address remaining chat review comments

* fix(ui): address review follow-ups

* fix(ui): replay queued local slash commands

* fix(ui): repair control-ui type drift

* fix(ui): restore control UI styling

* feat(ui): enhance layout and styling for config and topbar components

- Updated grid layout for the config layout to allow full-width usage.
- Introduced new styles for top tabs and search components to improve usability.
- Added theme mode toggle styling for better visual integration.
- Implemented tests for layout and theme mode components to ensure proper rendering and functionality.

* feat(ui): add config file opening functionality and enhance styles

- Implemented a new handler to open the configuration file using the default application based on the operating system.
- Updated various CSS styles across components for improved visual consistency and usability, including adjustments to padding, margins, and font sizes.
- Introduced new styles for the data table and sidebar components to enhance layout and interaction.
- Added tests for the collapsed navigation rail to ensure proper functionality in different states.

* refactor(ui): update CSS styles for improved layout and consistency

- Simplified font-body declaration in base.css for cleaner code.
- Adjusted transition properties in components.css for better readability.
- Added new .workspace-link class in components.css for enhanced link styling.
- Changed config layout from grid to flex in config.css for better responsiveness.
- Updated related tests to reflect layout changes in config-layout.browser.test.ts.

* feat(ui): enhance theme handling and loading states in chat interface

- Updated CSS to support new theme mode attributes for better styling consistency across light and dark themes.
- Introduced loading skeletons in the chat view to improve user experience during data fetching.
- Refactored command palette to manage focus more effectively, enhancing accessibility.
- Added tests for the appearance theme picker and loading states to ensure proper rendering and functionality.

* refactor(ui): streamline ephemeral state management in chat and config views

- Introduced interfaces for ephemeral state in chat and config views to encapsulate related variables.
- Refactored state management to utilize a single object for better organization and maintainability.
- Removed legacy state variables and updated related functions to reference the new state structure.
- Enhanced readability and consistency across the codebase by standardizing state handling.

* chore: remove test files to reduce PR scope

* fix(ui): resolve type errors in debug props and chat search

* refactor(ui): remove stream mode functionality across various components

- Eliminated stream mode related translations and CSS styles to streamline the user interface.
- Updated multiple components to remove references to stream mode, enhancing code clarity and maintainability.
- Adjusted rendering logic in views to ensure consistent behavior without stream mode.
- Improved overall readability by cleaning up unused variables and props.

* fix(ui): add msg-meta CSS and fix rebase type errors

* fix(ui): add CSS for chat footer action buttons (TTS, delete) and msg-meta

* feat(ui): add delete confirmation with remember-decision checkbox

* fix(ui): delete confirmation with remember, attention icon sizing

* fix(ui): open delete confirm popover to the left (not clipped)

* fix(ui): show all nav items in collapsed sidebar, remove gap

* fix(ui): address P1/P2 review feedback — session queue clear, kill scope, palette guard, stop button

* fix(ui): address Greptile re-review — kill scope, queue flush, idle handling, parallel fetch

- SECURITY: /kill <target> now enforces session tree scope (not just /kill all)
- /kill reports idle sessions gracefully instead of throwing
- Queue continues draining after local slash commands
- /model fetches sessions.list + models.list in parallel (perf fix)

* fix(ui): style update banner close button — SVG stroke + sizing

* fix(ui): update layout styles for sidebar and content spacing

* UI: restore colon slash command parsing

* UI: restore slash command session queries

* Refactor thinking resolution: Introduce resolveThinkingDefaultForModel function and update model-selection to utilize it. Add tests for new functionality in thinking.test.ts.

* fix(ui): constrain welcome state logo size, add missing CSS for new session view

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-12 12:46:19 -05:00
Vincent Koc
86135d5889
Kimi Coding: set default subscription user agent (#44248)
* Providers: set default Kimi coding user agent

* Tests: cover Kimi coding header overrides

* Changelog: note Kimi coding user agent

* Tests: satisfy Kimi provider fixture type

* Update CHANGELOG.md

* Providers: preserve Kimi headers through models merge
2026-03-12 13:30:07 -04:00
Vincent Koc
33ba3ce951
fix(node-host): harden ambiguous approval operand binding (#44247)
* fix(node-host): harden approval operand binding

* test(node-host): cover approval parser hardening

* docs(changelog): note approval hardening GHSA cluster

* Update CHANGELOG.md

* fix(node-host): remove dead approval parser entries

* test(node-host): cover bunx approval wrapper

* fix(node-host): unwrap pnpm shim exec forms

* test(node-host): cover pnpm shim wrappers
2026-03-12 13:28:35 -04:00
Peter Steinberger
136adb4c02
docs: reorder unreleased changelog 2026-03-12 17:11:31 +00:00
Gustavo Madeira Santana
60c1577860
Gateway: preserve discovered session store paths 2026-03-12 17:08:55 +00:00
yuweuii
b3e6f92fd2
runner: infer names from malformed toolCallId variants (#34485)
Merged via squash.

Prepared head SHA: 150ea1a7c90de3232f72498d851719c4dfb00b43
Co-authored-by: yuweuii <82372187+yuweuii@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 09:58:23 -07:00
Peter Steinberger
0b34671de3
fix: canonicalize openrouter native model keys 2026-03-12 16:51:00 +00:00
Peter Steinberger
115f24819e
fix: make node-llama-cpp optional for npm installs 2026-03-12 16:45:59 +00:00
Peter Steinberger
9f08af1f06
fix(ci): harden docker builds and unblock config docs 2026-03-12 16:45:29 +00:00
Gustavo Madeira Santana
46f0bfc55b
Gateway: harden custom session-store discovery (#44176)
Merged via squash.

Prepared head SHA: 52ebbf5188b47386f2a78ac4715993bc082e911b
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-12 16:44:46 +00:00
Peter Steinberger
dc3bb1890b
docs: clarify gateway HTTP trust boundary 2026-03-12 16:40:36 +00:00
Vincent Koc
f96ba87f03
Zalo: rate limit invalid webhook secret guesses before auth (#44173)
* Zalo: rate limit webhook guesses before auth

* Tests: cover pre-auth Zalo webhook rate limiting

* Changelog: note Zalo pre-auth rate limiting

* Zalo: preserve auth-before-content-type response ordering

* Tests: cover auth-before-content-type webhook ordering

* Zalo: split auth and unauth webhook rate-limit buckets

* Tests: cover auth bucket split for Zalo webhook rate limiting

* Zalo: use trusted proxy client IP for webhook rate limiting

* Tests: cover trusted proxy client IP rate limiting for Zalo
2026-03-12 12:30:50 -04:00
Nimrod Gutman
96fb423528 fix(ios): add live activity horizontal padding 2026-03-12 18:20:44 +02:00
Nimrod Gutman
b77b7485e0 feat(push): add iOS APNs relay gateway (#43369)
* feat(push): add ios apns relay gateway

* fix(shared): avoid oslog string concatenation

# Conflicts:
#	apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift

* fix(push): harden relay validation and invalidation

* fix(push): persist app attest state before relay registration

* fix(push): harden relay invalidation and url handling

* feat(push): use scoped relay send grants

* feat(push): configure ios relay through gateway config

* feat(push): bind relay registration to gateway identity

* fix(push): tighten ios relay trust flow

* fix(push): bound APNs registration fields (#43369) (thanks @ngutman)
2026-03-12 18:15:35 +02:00
2233admin
9342739d71
fix(providers): respect user-configured baseUrl for kimi-coding (#36647)
* fix(providers): respect user-configured baseUrl for kimi-coding

The kimi-coding provider was built exclusively from
`buildKimiCodingProvider()` defaults, ignoring any user-specified
`baseUrl` or other overrides in `openclaw.json` providers config.
This caused 404 errors when users configured a custom endpoint.

Now merge `explicitProviders["kimi-coding"]` on top of defaults,
matching the pattern used by ollama/vllm. User's `baseUrl`, `api`,
and `models` take precedence; env/profile API key still wins.

Fixes #36353

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

* Tests: use Kimi implicit provider harness

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-12 12:14:07 -04:00
Vincent Koc
3e28e10c2f
Plugins: require explicit trust for workspace-discovered plugins (#44174)
* Plugins: disable implicit workspace plugin auto-load

* Tests: cover workspace plugin trust gating

* Changelog: note workspace plugin trust hardening

* Plugins: keep workspace trust gate ahead of memory slot defaults

* Tests: cover workspace memory-slot trust bypass
2026-03-12 12:12:41 -04:00
chengzhichao-xydt
0a8fa0e001
Moonshot: respect explicit baseUrl for CN endpoint so platform.moonshot.cn keys authenticate (#33637) (#33696)
* Moonshot: respect explicit baseUrl for CN endpoint so platform.moonshot.cn keys authenticate (#33637)

* Moonshot: address review - remove dead constant, import canonical URLs (#33696)
2026-03-12 12:10:38 -04:00
Jacob Riff
3fa91cd69d
feat: add sessions_yield tool for cooperative turn-ending (#36537)
Merged via squash.

Prepared head SHA: 75d9204c863792226389a4d33eeb40c4e842528d
Co-authored-by: jriff <50276+jriff@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 08:46:47 -07:00
Gustavo Madeira Santana
e6897c800b
Plugins: fix env-aware root resolution and caching (#44046)
Merged via squash.

Prepared head SHA: 6e8852a188b0eaa4d6cf0bb71829023e0e0ed82b
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-12 15:31:31 +00:00
Rodrigo Uroz
688e3f0863 Compaction Runner: emit transcript updates post-compact (#25558)
Merged via squash.

Prepared head SHA: 8a858436ed31805124a9d096bd93ab90e5423672
Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 08:22:12 -07:00
Josh Lehman
8525fd94ea
docs: sync Feishu secretref credential matrix
## Summary

- Problem: `src/secrets/target-registry.test.ts` fails on latest `main` because the runtime registry includes Feishu `encryptKey` paths that the docs matrix and surface reference omit.
- Why it matters: the docs/runtime sync guard currently blocks prep and merge work for unrelated PRs, including `#25558`.
- What changed: regenerated the secretref credential matrix and updated the surface reference to include both Feishu `encryptKey` paths.
- What did NOT change (scope boundary): no runtime registry behavior, config semantics, or channel handling changed.

## Change Type (select all)

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [x] Docs
- [ ] Security hardening
- [ ] Chore/infra

## Scope (select all touched areas)

- [ ] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [x] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra

## Linked Issue/PR

- Closes #
- Related #25558

## User-visible / Behavior Changes

None.

## Security Impact (required)

- New permissions/capabilities? `No`
- Secrets/tokens handling changed? `No`
- New/changed network calls? `No`
- Command/tool execution surface changed? `No`
- Data access scope changed? `No`
- If any `Yes`, explain risk + mitigation:

## Repro + Verification

### Environment

- OS: macOS
- Runtime/container: Node.js repo checkout
- Model/provider: N/A
- Integration/channel (if any): Feishu docs/runtime registry sync
- Relevant config (redacted): none

### Steps

1. Check out latest `main` before this change.
2. Run `./node_modules/.bin/vitest run --config vitest.unit.config.ts src/secrets/target-registry.test.ts`.
3. Apply this docs-only sync change and rerun the same command.

### Expected

- The target registry stays in sync with the generated docs matrix and the test passes.

### Actual

- Before this change, the test failed because `channels.feishu.encryptKey` and `channels.feishu.accounts.*.encryptKey` were missing from the docs artifacts.

## Evidence

Attach at least one:

- [x] Failing test/log before + passing after
- [ ] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers (if relevant)

## Human Verification (required)

What you personally verified (not just CI), and how:

- Verified scenarios: confirmed the failure on plain latest `main`, applied only these docs entries in a clean bootstrapped worktree, and reran `./node_modules/.bin/vitest run --config vitest.unit.config.ts src/secrets/target-registry.test.ts` to green.
- Edge cases checked: verified both top-level Feishu `encryptKey` and account-scoped `encryptKey` paths are present in the matrix and surface reference.
- What you did **not** verify: full repo test suite and CI beyond the targeted regression.

## Review Conversations

- [x] I replied to or resolved every bot review conversation I addressed in this PR.
- [x] I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

## Compatibility / Migration

- Backward compatible? `Yes`
- Config/env changes? `No`
- Migration needed? `No`
- If yes, exact upgrade steps:

## Failure Recovery (if this breaks)

- How to disable/revert this change quickly: revert this commit.
- Files/config to restore: `docs/reference/secretref-user-supplied-credentials-matrix.json` and `docs/reference/secretref-credential-surface.md`
- Known bad symptoms reviewers should watch for: the target-registry docs sync test failing again for missing Feishu `encryptKey` entries.

## Risks and Mitigations

- Risk: the markdown surface reference could drift from the generated matrix again in a later credential-shape change.
  - Mitigation: `src/secrets/target-registry.test.ts` continues to guard docs/runtime sync.
2026-03-12 08:18:13 -07:00
Vincent Koc
8ad0ca309e
Subagents: stop retrying external completion timeouts (#41235) (#43847)
* Changelog: add subagent announce timeout note

* Tests: cover subagent completion timeout no-retry

* Subagents: stop retrying external completion timeouts

* Config: update subagent announce timeout default docs

* Tests: use fake timers for subagent timeout retry guard
2026-03-12 11:03:06 -04:00
Vincent Koc
7844bc89a1
Security: require Feishu webhook encrypt key (#44087)
* Feishu: require webhook encrypt key in schema

* Feishu: cover encrypt key webhook validation

* Feishu: enforce encrypt key at startup

* Feishu: add webhook forgery regression test

* Feishu: collect encrypt key during onboarding

* Docs: require Feishu webhook encrypt key

* Changelog: note Feishu webhook hardening

* Docs: clarify Feishu encrypt key screenshot

* Feishu: treat webhook encrypt key as secret input

* Feishu: resolve encrypt key only in webhook mode
2026-03-12 11:01:00 -04:00
Vincent Koc
99170e2408
Hardening: normalize Unicode command obfuscation detection (#44091)
* Exec: cover unicode obfuscation cases

* Exec: normalize unicode obfuscation detection

* Changelog: note exec detection hardening

* Exec: strip unicode tag character obfuscation

* Exec: harden unicode suppression and length guards

* Exec: require path boundaries for safe URL suppressions
2026-03-12 10:57:49 -04:00
Vincent Koc
eff0d5a947
Hardening: tighten preauth WebSocket handshake limits (#44089)
* Gateway: tighten preauth handshake limits

* Changelog: note WebSocket preauth hardening

* Gateway: count preauth frame bytes accurately

* Gateway: cap WebSocket payloads before auth
2026-03-12 10:55:41 -04:00
Vincent Koc
3e730c0332
Security: preserve Feishu reaction chat type (#44088)
* Feishu: preserve looked-up chat type

* Feishu: fail closed on ambiguous reaction chats

* Feishu: cover reaction chat type fallback

* Changelog: note Feishu reaction hardening

* Feishu: fail closed without resolved chat type

* Feishu: normalize reaction chat type at runtime
2026-03-12 10:53:40 -04:00
Vincent Koc
48cbfdfac0
Hardening: require LINE webhook signatures (#44090)
* LINE: require webhook signatures in express handler

* LINE: require webhook signatures in node handler

* LINE: update express signature tests

* LINE: update node signature tests

* Changelog: note LINE webhook hardening

* LINE: validate signatures before parsing webhook bodies

* LINE: reject missing signatures before body reads
2026-03-12 10:50:36 -04:00
Lyle
c965049dc6
fix(mattermost): pass mediaLocalRoots through reply delivery (#44021)
Merged via squash.

Prepared head SHA: 856f11f129f7d6a4bc8f23e8d13c786ecb871f52
Co-authored-by: LyleLiu666 <31182860+LyleLiu666@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-12 20:13:51 +05:30
Altay
797b6fe614 ci: tighten cache docs and node22 gate 2026-03-12 20:07:44 +05:30
Altay
e1d054547e ci: restore PR pnpm cache fallback 2026-03-12 20:07:44 +05:30
Altay
29b36f8e4a ci: harden pnpm sticky cache on PRs 2026-03-12 20:07:44 +05:30
Altay
b0f717aa02 build: align Node 22 guidance with 22.16 minimum 2026-03-12 20:07:44 +05:30
Altay
0a8d2b6200 build: raise Node 22 compatibility floor to 22.16 2026-03-12 20:07:44 +05:30
Altay
deada7edd3 build: default to Node 24 and keep Node 22 compat 2026-03-12 20:07:44 +05:30
Vincent Koc
2f037f0930 Agents: adapt pi-ai oauth and payload hooks 2026-03-12 10:19:14 -04:00
0x4C33
f3be1c828c
fix(status): resolve context window by provider-qualified key, prefer max on bare-id collision, solve #35976 (#36389)
Merged via squash.

Prepared head SHA: f8cf752c59708fb388fd200276115277e8b217d6
Co-authored-by: haoruilee <60883781+haoruilee@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 07:00:36 -07:00
rabsef-bicrym
ff47876e61
fix: carry observed overflow token counts into compaction (#40357)
Merged via squash.

Prepared head SHA: b99eed4329bda45083cdedc2386c2c4041c034be
Co-authored-by: rabsef-bicrym <52549148+rabsef-bicrym@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-12 06:58:42 -07:00
avirweb
f2e28fc30f
fix(telegram): allow fallback models in /model validation (#40105)
Merged via squash.

Prepared head SHA: de07585e03cba06897d50c1d79fbe09d326c6ac9
Co-authored-by: avirweb <257412074+avirweb@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-12 13:55:51 +01:00
Teconomix
171d2df9e0
feat(mattermost): add replyToMode support (off | first | all) (#29587)
Merged via squash.

Prepared head SHA: 4a67791f53b1109959082738429471b7a5bc93b8
Co-authored-by: teconomix <6959299+teconomix@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-12 18:03:12 +05:30
Sally O'Malley
8e0e4f736a
docs: add Kubernetes install guide, setup script, and manifests (#34492)
* add docs and manifests for k8s install

Signed-off-by: sallyom <somalley@redhat.com>

* changelog

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
2026-03-12 07:28:21 -04:00
Nimrod Gutman
4f620bebe5
fix(doctor): canonicalize gateway service entrypoint paths (#43882)
Merged via squash.

Prepared head SHA: 9f530d2a86b5d30822d4419db7cffae6a560ddca
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-03-12 12:39:22 +02:00
Ayaan Zaidi
783a0d540f
fix: add zalouser outbound chunker 2026-03-12 15:47:12 +05:30
Ayaan Zaidi
8582cb08b5 fix: stop main-session UI replies inheriting channel routes (#43918) 2026-03-12 15:39:34 +05:30
Ayaan Zaidi
5acf6cae8e fix: stop main-session UI replies inheriting channel routes 2026-03-12 15:39:34 +05:30
glitch
8ea79b64d0
fix: preserve sandbox write payload stdin (#43876)
Merged via squash.

Prepared head SHA: a10fd4b21c78ec57411e6a4f387f16b1441660c2
Co-authored-by: glitch418x <189487110+glitch418x@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-12 12:42:57 +03:00
jnMetaCode
f640326e31
fix(failover): add missing network errno patterns to text-based timeout classifier (#42830)
Merged via squash.

Prepared head SHA: 91761487e8825c0fd6582a762d04bba04f726a85
Co-authored-by: jnMetaCode <12096460+jnMetaCode@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-12 12:34:44 +03:00
darkamenosa
a6711afdc2
feat(zalouser): add markdown-to-Zalo text style parsing (#43324)
* feat(zalouser): add markdown-to-Zalo text style parsing

Parse markdown formatting (bold, italic, strikethrough, headings, lists,
code blocks, blockquotes, custom color/style tags) into Zalo native
TextStyle ranges so outbound messages render with rich formatting.

- Add text-styles.ts with parseZalouserTextStyles() converter
- Wire markdown mode into send pipeline (sendMessageZalouser)
- Export TextStyle enum and Style type from zca-client
- Add textMode/textStyles to ZaloSendOptions
- Pass textStyles through sendZaloTextMessage to zca-js API
- Enable textMode:"markdown" in outbound sendText/sendMedia and monitor
- Add comprehensive tests for parsing, send, and channel integration

* fix(zalouser): harden markdown text parsing

* fix(zalouser): mirror zca-js text style types

* fix(zalouser): support tilde fenced code blocks

* fix(zalouser): handle quoted fenced code blocks

* fix(zalouser): preserve literal quote lines in code fences

* fix(zalouser): support indented quoted fences

* fix(zalouser): preserve quoted markdown blocks

* fix(zalouser): rechunk formatted messages

* fix(zalouser): preserve markdown structure across chunks

* fix(zalouser): honor chunk limits and CRLF fences
2026-03-12 16:24:15 +07:00
Vincent Koc
7c889e7113
Refactor: trim duplicate gateway/onboarding helpers and dead utils (#43871)
* Gateway: share input provenance schema

* Onboarding: dedupe top-level channel patching

* Utils: remove unused path helpers

* Protocol: refresh generated gateway models
2026-03-12 05:04:31 -04:00
Vincent Koc
cb7b38105f Merge remote-tracking branch 'origin/vincentkoc-code/fix-terminal-table-width'
* origin/vincentkoc-code/fix-terminal-table-width:
  Terminal: consume unsupported escape bytes in tables
  Skills: normalize emoji presentation across outputs
  Changelog: note terminal skills table fixes
  Skills: use Terminal-safe emoji in list output
  Terminal: stop shrinking CLI tables by one column
  Terminal: refine table wrapping and width handling
  Update CHANGELOG.md
  Deps: patch file-type and hono
  Tests: cover emoji table alignment
  Terminal: wrap table cells by grapheme width
  Terminal: measure grapheme display width
  Tests: cover grapheme terminal width
  Changelog: add unreleased March 9 entries

# Conflicts:
#	CHANGELOG.md
#	package.json
#	pnpm-lock.yaml
#	src/cli/skills-cli.format.ts
#	src/terminal/table.test.ts
2026-03-12 04:56:21 -04:00
Vincent Koc
1dfc35fc28 Merge branch 'vincentkoc-code/fix-terminal-table-width' of https://github.com/openclaw/openclaw into vincentkoc-code/fix-terminal-table-width
* 'vincentkoc-code/fix-terminal-table-width' of https://github.com/openclaw/openclaw:
  Update CHANGELOG.md
2026-03-12 04:51:56 -04:00
Luke
62a71361a9
Docs: clarify llm-task thinking presets 2026-03-12 19:27:07 +11:00
Val Alexander
46cb73da37
feat(ui): utilities, theming, and i18n updates (slice 2/3 of dashboard-v2) (#41500)
* feat(ui): add utilities, theming, and i18n updates (slice 2 of dashboard-v2)

UI utilities and theming improvements extracted from dashboard-v2-structure:

Icons & formatting:
- icons.ts: expanded icon set for new dashboard views
- format.ts: date/number formatting helpers
- tool-labels.ts: human-readable tool name mappings

Theming:
- theme.ts: enhanced theme resolution and system theme support
- theme-transition.ts: simplified transition logic
- storage.ts: theme parsing improvements for settings persistence

Navigation & types:
- navigation.ts: extended tab definitions for dashboard-v2
- app-view-state.ts: expanded view state management
- types.ts: new type definitions (HealthSummary, ModelCatalogEntry, etc.)

Components:
- components/dashboard-header.ts: reusable header component

i18n:
- Updated en, pt-BR, zh-CN, zh-TW locales with new dashboard strings

All changes are additive or backwards-compatible. Build passes.
Part of #36853.

* ui: fix theme and locale review regressions

* ui: fix review follow-ups for dashboard tabs

* ui: allowlist locale password placeholder false positives

* ui: fix theme mode and locale regressions

* Vincentkoc code/pr 41500 route fix (#43829)

* UI: keep unfinished settings routes hidden

* UI: normalize light theme data token

* UI: restore cron type compatibility

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-12 04:26:39 -04:00
Xaden Ryan
658bd54ecf
feat(llm-task): add thinking override
Co-authored-by: Xaden Ryan <165437834+xadenryan@users.noreply.github.com>
2026-03-12 19:21:35 +11:00
Vincent Koc
f37815b323
Gateway: block profile mutations via browser.request (#43800)
* Gateway: block profile mutations via browser.request

* Changelog: note GHSA-vmhq browser request fix

* Gateway: normalize browser.request profile guard paths
2026-03-12 04:21:03 -04:00
Vincent Koc
46a332385d
Gateway: keep spawned workspace overrides internal (#43801)
* Gateway: keep spawned workspace overrides internal

* Changelog: note GHSA-2rqg agent boundary fix

* Gateway: persist spawned workspace inheritance in sessions

* Agents: clean failed lineage spawn state

* Tests: cover lineage attachment cleanup

* Tests: cover lineage thread cleanup
2026-03-12 04:20:00 -04:00
Vincent Koc
97683071b5 Tests: extend exec allowlist glob coverage 2026-03-12 04:01:49 -04:00
Vincent Koc
9aeaa19e9e
Agents: clear invalidated Kimi tool arg repair (#43824) 2026-03-12 03:53:06 -04:00
Val Alexander
c5ea6134d0
feat(ui): add chat infrastructure modules (slice 1/3 of dashboard-v2) (#41497)
* feat(ui): add chat infrastructure modules (slice 1 of dashboard-v2)

New self-contained chat modules extracted from dashboard-v2-structure:

- chat/slash-commands.ts: slash command definitions and completions
- chat/slash-command-executor.ts: execute slash commands via gateway RPC
- chat/slash-command-executor.node.test.ts: test coverage
- chat/speech.ts: speech-to-text (STT) support
- chat/input-history.ts: per-session input history navigation
- chat/pinned-messages.ts: pinned message management
- chat/deleted-messages.ts: deleted message tracking
- chat/export.ts: shared exportChatMarkdown helper
- chat-export.ts: re-export shim for backwards compat

Gateway fix:
- Restore usage/cost stripping in chat.history sanitization
- Add test coverage for sanitization behavior

These modules are additive and tree-shaken — no existing code
imports them yet. They will be wired in subsequent slices.

* Update ui/src/ui/chat/export.ts

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

* fix(ui): address review feedback on chat infra slice

- export.ts: handle array content blocks (Claude API format) instead
  of silently exporting empty strings
- slash-command-executor.ts: restrict /kill all to current session's
  subagent subtree instead of all sessions globally
- slash-command-executor.ts: only count truly aborted runs (check
  aborted !== false) in /kill summary

* fix: scope /kill <id> to current session subtree and preserve usage.cost in chat.history

- Restrict /kill <id> matching to only subagents belonging to the current
  session's agent subtree (P1 review feedback)
- Preserve nested usage.cost in chat.history sanitization so cost badges
  remain available (P2 review feedback)

* fix(ui): tighten slash kill scoping

* fix(ui): support legacy slash kill scopes

* fix(ci): repair pr branch checks

* Gateway: harden chat abort and export

* UI: align slash commands with session tree scope

* UI: resolve session aliases for slash command lookups

* Update .gitignore

* Cron: use shared nested lane resolver

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-12 03:48:58 -04:00
Ayaan Zaidi
ed0ec57a7b
fix: scope telegram polling restart to telegram errors (#43799)
* fix: scope telegram polling restart to telegram errors

* fix: make telegram error tagging best-effort

* fix: scope telegram polling restart to telegram errors (#43799)
2026-03-12 13:14:17 +05:30
Vincent Koc
82e3ac21ee
Infra: tighten exec allowlist glob matching (#43798)
* Infra: tighten exec allowlist glob matching

* Changelog: note GHSA-f8r2 exec allowlist fix
2026-03-12 03:33:50 -04:00
Vincent Koc
d8ee97c466
Agents: recover malformed Anthropic-compatible tool call args (#42835)
* Agents: recover malformed anthropic tool call args

* Agents: add malformed tool call regression test

* Changelog: note Kimi tool call arg recovery

* Agents: repair toolcall end message snapshots

* Agents: narrow Kimi tool call arg repair
2026-03-12 03:28:22 -04:00
Vincent Koc
4dfd8eea90 BlueBubbles: require confirmed outbound for self-chat cache 2026-03-12 03:22:57 -04:00
Josh Avant
0bcb95e8fa
Models: enforce source-managed SecretRef markers in models.json (#43759)
Merged via squash.

Prepared head SHA: 4a065ef5d849273756ceb0dd241ca24ca9e621ca
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
Reviewed-by: @joshavant
2026-03-12 02:22:52 -05:00
Mathias Nagler
e8a162d3d8
fix(mattermost): prevent duplicate messages when block streaming + threading are active (#41362)
* fix(mattermost): prevent duplicate messages when block streaming + threading are active

Remove replyToId from createBlockReplyPayloadKey so identical content is
deduplicated regardless of threading target. Add explicit threading dock
to the Mattermost plugin with resolveReplyToMode reading from config
(default "all"), and add replyToMode to the Mattermost config schema.

Fixes #41219

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

* fix(mattermost): address PR review — per-account replyToMode and test clarity

Read replyToMode from the merged per-account config via
resolveMattermostAccount so account-level overrides are honored in
multi-account setups. Add replyToMode to MattermostAccountConfig type.
Rename misleading test to clarify it exercises shouldDropFinalPayloads
short-circuit, not payload key dedup.

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

* Replies: keep block-pipeline reply targets distinct

* Tests: cover block reply target-aware dedupe

* Update CHANGELOG.md

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-12 03:15:17 -04:00
Vincent Koc
241e8cc553
fix(bluebubbles): dedupe reflected self-chat duplicates (#38442)
* BlueBubbles: drop reflected self-chat duplicates

* Changelog: add BlueBubbles self-chat echo dedupe entry

* BlueBubbles: gate self-chat cache and expand coverage

* BlueBubbles: require explicit sender ids for self-chat dedupe

* BlueBubbles: harden self-chat cache

* BlueBubbles: move self-chat cache identity into cache

* BlueBubbles: gate self-chat cache to confirmed outbound sends

* Update CHANGELOG.md

* BlueBubbles: bound self-chat cache input work

* Tests: cover BlueBubbles cache cap under cleanup throttle

* BlueBubbles: canonicalize self-chat DM scope

* Tests: cover BlueBubbles mixed self-chat scope aliases
2026-03-12 03:11:43 -04:00
wangchunyue(王春跃)
6c196c913f
fix(cron): prevent duplicate proactive delivery on transient retry (#40646)
* fix(cron): prevent duplicate proactive delivery on transient retry

* refactor: scope skipQueue to retryTransient path only

Non-retrying direct delivery (structured content / thread) keeps the
write-ahead queue so recoverPendingDeliveries can replay after a crash.

Addresses review feedback from codex-connector.

* fix: preserve write-ahead queue on initial delivery attempt

The first call through retryTransientDirectCronDelivery now keeps the
write-ahead queue entry so recoverPendingDeliveries can replay after a
crash.  Only subsequent retry attempts set skipQueue to prevent
duplicate sends.

Addresses second codex-connector review on ea5ae5c.

* ci: retrigger checks

* Cron: bypass write-ahead queue for direct isolated delivery

* Tests: assert isolated cron skipQueue invariants

* Changelog: add cron duplicate-delivery fix entry

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-12 03:01:19 -04:00
lisitan
f3c00fce15
fix: prevent duplicate assistant messages in TUI (fixes #35278) (#35364)
* fix: prevent duplicate assistant messages in TUI (fixes #35278)

When startAssistant() is called multiple times with the same runId,
it was creating duplicate AssistantMessageComponent instances instead
of reusing the existing one. This caused messages to appear twice in
the terminal UI.

The fix checks if a component already exists for the runId before
creating a new one. If it exists, we update its text instead of
appending a duplicate component.

Test coverage includes verification that:
- Only one component is created when startAssistant is called twice
- The second text replaces the first
- Component count remains 1 (prevents regression)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* Changelog: add TUI duplicate-render fix entry

---------

Co-authored-by: 沐沐 <mumu@example.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-12 02:59:42 -04:00
Vincent Koc
99ec687d7a
fix(agents): enforce sandboxed session_status visibility (#43754)
* agents: guard sandboxed session_status access

* test(agents): cover sandboxed session_status scope

* docs(changelog): credit session_status hardening

* agents: preflight sandboxed session_status checks

* test(agents): cover session_status existence oracle

* agents: preserve legacy session_status tree keys

* test(agents): cover legacy session_status tree keys

* Update CHANGELOG.md
2026-03-12 02:54:25 -04:00
Vincent Koc
12dc299cde
fix(imessage): dedupe reflected self-chat duplicates (#38440)
* iMessage: drop reflected self-chat duplicates

* Changelog: add iMessage self-chat echo dedupe entry

* iMessage: keep self-chat dedupe scoped to final group identity

* iMessage: harden self-chat cache

* iMessage: sanitize self-chat duplicate logs

* iMessage: scope group self-chat dedupe by sender

* iMessage: move self-chat cache identity into cache

* iMessage: hash full self-chat text

* Update CHANGELOG.md
2026-03-12 02:27:35 -04:00
Luke
8baf55d8ed
Changelog: note Reminders permission fix 2026-03-12 17:01:42 +11:00
Dinakar Sarbada
cee8717020
fix(macos): add NSRemindersUsageDescription for apple-reminders skill
Fixes #5090

Without this plist key, macOS silently denies Reminders access when
running through OpenClaw.app, preventing the apple-reminders skill
from requesting permission.

(cherry picked from commit e5774471c851b773dd2bffd51dd5d28d95a8a7ca)
2026-03-12 17:01:38 +11:00
Ayaan Zaidi
f7416da905
style: format changelog 2026-03-12 11:28:27 +05:30
Vincent Koc
d8d8dc7421 Infra: fail closed without device scope baseline 2026-03-12 01:42:12 -04:00
Vincent Koc
276ee259ca Tests: clean up temp git helper directory 2026-03-12 01:42:12 -04:00
Vincent Koc
99a5a3c16a
Update CHANGELOG.md 2026-03-12 01:37:33 -04:00
Vincent Koc
672924b01e
Update CHANGELOG.md 2026-03-12 01:36:16 -04:00
Vincent Koc
4f462facda
Infra: cap device tokens to approved scopes (#43686)
* Infra: cap device tokens to approved scopes

* Changelog: note device token hardening
2026-03-12 01:25:52 -04:00
Vincent Koc
2504cb6a1e
Security: escape invisible exec approval format chars (#43687)
* Infra: escape invisible exec approval chars

* Gateway: sanitize exec approval display text

* Tests: cover sanitized exec approval payloads

* Tests: cover sanitized exec approval forwarding

* Changelog: note exec approval prompt hardening
2026-03-12 01:20:04 -04:00
Vincent Koc
1dcef7b644
Infra: block GIT_EXEC_PATH in host env sanitizer (#43685)
* Infra: block GIT_EXEC_PATH in host env sanitizer

* Changelog: note host env hardening
2026-03-12 01:16:03 -04:00
Vincent Koc
18f15850e6
fix(browser): restore proxy attachment media size cap (#43684)
* browser: honor shared proxy file size cap

* test(browser): cover proxy file size cap

* docs(changelog): note browser proxy size cap fix
2026-03-12 01:04:31 -04:00
Peter Steinberger
29dc65403f
build: prepare 2026.3.11 release 2026-03-12 05:01:07 +00:00
Neerav Makwana
c65390cbde
docs: update Raspberry Pi dashboard access instructions (#43584)
* docs(pi): update dashboard access instructions

* docs(i18n): refresh raspberry pi source hash

* docs: clarify Raspberry Pi dashboard access

* fix: clarify Raspberry Pi dashboard access (#43584) (thanks @neeravmakwana)

---------

Co-authored-by: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <zaidi@uplause.io>
2026-03-12 10:04:44 +05:30
Peter Steinberger
b125c3ba06
build: bump openclaw to 2026.3.11-beta.1 2026-03-12 04:08:19 +00:00
Ayaan Zaidi
fbc1bd6f8e fix: clear telegram polling cleanup timers 2026-03-12 09:36:04 +05:30
Huang X
70abee69e9 fix(telegram): avoid polling restart hang after stall detection 2026-03-12 09:36:04 +05:30
Peter Steinberger
ce5dd742f8
build: sync versions to 2026.3.11 2026-03-12 04:01:57 +00:00
Peter Steinberger
96485701a7
docs: update 2026.3.11 release examples 2026-03-12 04:01:56 +00:00
Toven
ade748176f
OpenRouter: surface free Hunter and Healer stealth models for the next week (#43642)
* Models: add temporary Hunter and Healer alpha to OpenRouter catalog

* Add temporary OpenRouter stealth catalog entries

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-11 22:58:48 -05:00
Peter Steinberger
1fcee52a5c
docs: reorder unreleased changelog by user impact 2026-03-12 03:42:39 +00:00
David Rudduck
f01c41b27a
fix(context-engine): guard compact() throw + fire hooks for ownsCompaction engines (#41361)
Merged via squash.

Prepared head SHA: 0957b32dc63b16d710403565953b77bfbd2bd987
Co-authored-by: davidrudduck <47308254+davidrudduck@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-11 20:19:20 -07:00
Frank Yang
5231277163
fix(acp): rehydrate restarted main ACP sessions (#43285)
Merged via squash.

Prepared head SHA: f06318e58fe3e3fedd70426ca7eeecf6d71bb604
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-12 11:05:09 +08:00
Peter Steinberger
5ca780fa78
feat: expose runtime version in gateway status 2026-03-12 02:55:31 +00:00
Robin Waslander
e95f2dcd6e
fix(sandbox): anchor fs-bridge writeFile commit to canonical parent path
Refs: GHSA-xvx8-77m6-gwg6
2026-03-12 03:52:24 +01:00
Peter Steinberger
43a10677ed
fix: isolate plugin discovery env from global state 2026-03-12 02:46:29 +00:00
Peter Steinberger
17fd46ab66
test: fix websocket tool shape coverage 2026-03-12 02:16:56 +00:00
Robin Waslander
487a3ba8ce
fix(discord): enforce users/roles allowlist in reaction ingress
References GHSA-9vvh-2768-c8vp.
2026-03-12 03:13:46 +01:00
Peter Steinberger
980619b9be
fix: harden openai websocket replay 2026-03-12 02:13:06 +00:00
Peter Steinberger
607c158a75
test(cli): update daemon coverage restart contract 2026-03-12 01:43:27 +00:00
Peter Steinberger
b31836317a
fix(cli): handle scheduled gateway restarts consistently 2026-03-12 01:38:39 +00:00
Robin Waslander
841ee24340
fix(daemon): address clanker review findings for kickstart restart
Bug 1 (high): replace fixed sleep 1 with caller-PID polling in both
kickstart and start-after-exit handoff modes. The helper now waits until
kill -0 $caller_pid fails before issuing launchctl kickstart -k.

Bug 2 (medium): gate enable+bootstrap fallback on isLaunchctlNotLoaded().
Only attempt re-registration when kickstart -k fails because the job is
absent; all other kickstart failures now re-throw the original error.

Follows up on 3c0fd3dffe.
Fixes #43311, #43406, #43035, #43049
2026-03-12 02:16:24 +01:00
Robin Waslander
b7a37c2023
fix(node-host): extend script-runner set and add fail-closed guard for mutable-file approval
tsx, jiti, ts-node, ts-node-esm, vite-node, and esno were not recognized
as interpreter-style script runners in invoke-system-run-plan.ts. These
runners produced mutableFileOperand: null, causing invoke-system-run.ts
to skip revalidation entirely. A mutated script payload would execute
without the approval binding check that node ./run.js already enforced.

Two-part fix:
- Add tsx, jiti, and related TypeScript/ESM loaders to the known script
  runner set so they produce a valid mutableFileOperand from the planner
- Add a fail-closed runtime guard in invoke-system-run.ts that denies
  execution when a script run should have a mutable-file binding but the
  approval plan is missing it, preventing unknown future runners from
  silently bypassing revalidation

Fixes GHSA-qc36-x95h-7j53
2026-03-12 01:34:35 +01:00
Luke
a5ceb62d44
fix(whatsapp): trim leading whitespace in direct outbound sends (#43539)
Trim leading whitespace from direct WhatsApp text and media caption sends.

Also guard empty text-only web sends after trimming.
2026-03-12 11:32:04 +11:00
Peter Steinberger
7e3787517f fix: harden state dir permissions during onboard 2026-03-12 00:26:36 +00:00
Robin Waslander
ebed3bbde1
fix(gateway): enforce browser origin check regardless of proxy headers
In trusted-proxy mode, enforceOriginCheckForAnyClient was set to false
whenever proxy headers were present. This allowed browser-originated
WebSocket connections from untrusted origins to bypass origin validation
entirely, as the check only ran for control-ui and webchat client types.

An attacker serving a page from an untrusted origin could connect through
a trusted reverse proxy, inherit proxy-injected identity, and obtain
operator.admin access via the sharedAuthOk / roleCanSkipDeviceIdentity
path without any origin restriction.

Remove the hasProxyHeaders exemption so origin validation runs for all
browser-originated connections regardless of how the request arrived.

Fixes GHSA-5wcw-8jjv-m286
2026-03-12 01:16:52 +01:00
Robin Waslander
3c0fd3dffe
fix(daemon): replace bootout with kickstart -k for launchd restarts on macOS
On macOS, launchctl bootout permanently unloads the LaunchAgent plist.
Even with KeepAlive: true, launchd cannot respawn a service whose plist
has been removed from its registry. This left users with a dead gateway
requiring manual 'openclaw gateway install' to recover.

Affected trigger paths:
- openclaw gateway restart from an agent session (#43311)
- SIGTERM on config reload (#43406)
- Gateway self-restart via SIGTERM (#43035)
- Hot reload on channel config change (#43049)

Switch restartLaunchAgent() to launchctl kickstart -k, which force-kills
and restarts the service without unloading the plist. When the restart
originates from inside the launchd-managed process tree, delegate to a
new detached handoff helper (launchd-restart-handoff.ts) to avoid the
caller being killed mid-command. Self-restart paths in process-respawn.ts
now schedule the detached start-after-exit handoff before exiting instead
of relying on exit/KeepAlive timing.

Fixes #43311, #43406, #43035, #43049
2026-03-12 01:16:49 +01:00
Peter Steinberger
e11be576fb fix: repair bundled plugin dirs after npm install 2026-03-11 23:53:50 +00:00
Vincent Koc
b6d83749c8
fix(terminal): sanitize skills JSON and fallback on legacy Windows (#43520)
* Terminal: use ASCII borders on legacy Windows consoles

* Skills: sanitize JSON output for control bytes

* Changelog: credit terminal follow-up fixes

* Update CHANGELOG.md

* Update CHANGELOG.md

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

* Skills: strip remaining escape sequences from JSON output

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-11 19:53:07 -04:00
Peter Steinberger
0e397e62b7 chore: bump version to 2026.3.10 2026-03-11 23:29:53 +00:00
Brian Yu
cced1e0f76 preserve openai phase param 2026-03-11 23:15:52 +00:00
Gustavo Madeira Santana
da6f97a3f6 Memory: revalidate multimodal files before indexing 2026-03-11 22:51:34 +00:00
zhoulf1006
453c8d7c1b
fix(hooks): add missing trigger and channelId to agent_end, llm_input, and llm_output hook contexts (#42362)
Merged via squash.

Prepared head SHA: e6d7b7e31aa6ae12813b4609adb6e569a4084d08
Co-authored-by: zhoulf1006 <35586967+zhoulf1006@users.noreply.github.com>
Co-authored-by: hydro13 <6640526+hydro13@users.noreply.github.com>
Reviewed-by: @hydro13
2026-03-11 23:40:13 +01:00
Gustavo Madeira Santana
d79ca52960
Memory: add multimodal image and audio indexing (#43460)
Merged via squash.

Prepared head SHA: a994c07190a2062322f459c928b6cd74f9803d88
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-11 22:28:34 +00:00
Harold Hunt
20d097ac2f
Gateway/Dashboard: surface config validation issues (#42664)
Merged via squash.

Prepared head SHA: 43f66cdcf04a14d5381a5a2f14e291a52a8b7389
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-11 17:32:41 -04:00
Altay
4eccea9f7f
test(gateway): widen before tool hook mock typing (#43476)
* test(gateway): widen before tool hook mock typing

* chore: update pnpm.lock
2026-03-12 00:17:03 +03:00
Peter Steinberger
8cc0c9baf2 fix(gateway): run before_tool_call for HTTP tools 2026-03-11 20:18:24 +00:00
Peter Steinberger
c8dd06cba2 fix(ws): preserve payload overrides 2026-03-11 20:11:51 +00:00
Peter Steinberger
bdd9ed238a test: align pi-ai oauth mocks 2026-03-11 20:11:51 +00:00
Peter Steinberger
5e324cf785 docs(ollama): align onboarding guidance with code 2026-03-11 20:11:51 +00:00
Peter Steinberger
e65011dc29 fix(onboard): default custom Ollama URL to native API 2026-03-11 20:11:51 +00:00
Peter Steinberger
620bae4ec7 fix(ollama): share model context discovery 2026-03-11 20:11:51 +00:00
Peter Steinberger
9329a0ab24 test(agents): cover openai responses phase replay 2026-03-11 20:10:55 +00:00
Peter Steinberger
9c81c31232 chore: refresh dependencies except carbon 2026-03-11 20:10:33 +00:00
Tak Hoffman
4133edb395
fix: restore web tools to coding profile (#43436)
* fix: restore web tools to coding profile

* fix: tighten tool catalog regression assertion
2026-03-11 15:07:17 -05:00
Squabble9
128e5bc317
fix: recognize Venice 402 billing errors for model fallback (#43205)
Merged via squash.

Prepared head SHA: 1f6b10b9d934235e71f279f888292139c4a85aa6
Co-authored-by: Squabble9 <194720422+Squabble9@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-11 22:15:32 +03:00
Gustavo Madeira Santana
01ffc5db24
memory: normalize Gemini embeddings (#43409)
Merged via squash.

Prepared head SHA: 70613e022540b4d923d55c3a6ca51715cc6f46c4
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-11 15:06:21 -04:00
ingyukoh
2a18cbb110
fix(agents): prevent false billing error replacing valid response text (#40616)
Merged via squash.

Prepared head SHA: 05179362b439ff3b0330df3eb6a28aa05396ea9d
Co-authored-by: ingyukoh <6015960+ingyukoh@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-11 22:00:11 +03:00
ingyukoh
78b9384aa7
fix(discord): add missing autoThread to DiscordGuildChannelConfig type (#35608)
Merged via squash.

Prepared head SHA: e62b88bb01bf13ba2759c51095be07c3a47536dc
Co-authored-by: ingyukoh <6015960+ingyukoh@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-11 21:54:49 +03:00
VibhorGautam
4473242b4f
fix: use unknown instead of rate_limit as default cooldown reason (#42911)
Merged via squash.

Prepared head SHA: bebf6704d7b02b9a32935c0006eac2d76694fec0
Co-authored-by: VibhorGautam <55019395+VibhorGautam@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-11 21:34:14 +03:00
Bill Chirico
60aed95346
feat(memory): add gemini-embedding-2-preview support (#42501)
Merged via squash.

Prepared head SHA: c57b1f8ba2ca65f4946afe94a9137ee8c05c8c64
Co-authored-by: BillChirico <13951316+BillChirico@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-11 14:28:53 -04:00
ademczuk
58634c9c65
fix(agents): check billing errors before context overflow heuristics (#40409)
Merged via squash.

Prepared head SHA: c88f89c462d87957a4c6c51a23ab997fd307059d
Co-authored-by: ademczuk <5212682+ademczuk@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-11 21:08:55 +03:00
ingyukoh
f417d78eef
fix(config): add missing editMessage and createForumTopic to Telegram actions schema (#35498)
Merged via squash.

Prepared head SHA: 631fc14832402c5a6659fdb1a24a37ae67832ed0
Co-authored-by: ingyukoh <6015960+ingyukoh@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-11 20:59:27 +03:00
ingyukoh
a84bcf734c
fix(signal): add missing accountUuid to Zod config schema (#35578)
Merged via squash.

Prepared head SHA: 39e8e9ad62f65fa70e1a660e65f19d9452ae0412
Co-authored-by: ingyukoh <6015960+ingyukoh@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-11 20:57:07 +03:00
ademczuk
8618a711ff
fix(voice-call): add speed and instructions to OpenAI TTS config schema (#39226)
Merged via squash.

Prepared head SHA: 775e3063b58d4629f59021798ab1c7222ff069d9
Co-authored-by: ademczuk <5212682+ademczuk@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-11 23:15:48 +05:30
Ayaan Zaidi
daf8afc954
fix(telegram): clear stale retain before transient final fallback (#41763)
Merged via squash.

Prepared head SHA: c0940838bce6c4ed052579a4773b934c08826e0a
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-11 21:36:43 +05:30
Tak Hoffman
87876a3e36
Fix env proxy bootstrap for model traffic (#43248)
* Fix env proxy bootstrap for model traffic

* Address proxy dispatcher review followups

* Fix proxy env precedence for empty lowercase vars
2026-03-11 10:21:35 -05:00
Peter Steinberger
1435fce2de fix: tighten Ollama onboarding cloud handling (#41529) (thanks @BruceMacD) 2026-03-11 14:52:55 +00:00
Bruce MacDonald
d6108a6f72 Onboard: add Ollama auth flow and improve model defaults
Add Ollama as a auth provider in onboarding with Cloud + Local mode
selection, browser-based sign-in via /api/me, smart model suggestions
per mode, and graceful fallback when the default model is unavailable.

- Extract shared ollama-models.ts
- Auto-pull missing models during onboarding
- Non-interactive mode support for CI/automation

Closes #8239
Closes #3494

Co-Authored-By: Jeffrey Morgan <jmorganca@gmail.com>
2026-03-11 14:52:55 +00:00
Robin Waslander
62d5df28dc
fix(agents): add nodes to owner-only tool policy fallbacks
The nodes tool was missing from OWNER_ONLY_TOOL_NAME_FALLBACKS in
tool-policy.ts. applyOwnerOnlyToolPolicy() correctly removed gateway
and cron for non-owners but kept nodes, which internally issues
privileged gateway calls: node.pair.approve (operator.pairing) and
node.invoke (operator.write).

A non-owner sender could approve pending node pairings and invoke
arbitrary node commands, extending to system.run on paired nodes.

Add nodes to the fallback owner-only set. Non-owners no longer receive
the nodes tool after policy application; owners retain it.

Fixes GHSA-r26r-9hxr-r792
2026-03-11 14:17:03 +01:00
Robin Waslander
a1520d70ff
fix(gateway): propagate real gateway client into plugin subagent runtime
Plugin subagent dispatch used a hardcoded synthetic client carrying
operator.admin, operator.approvals, and operator.pairing for all
runtime.subagent.* calls. Plugin HTTP routes with auth:"plugin" require
no gateway auth by design, so an unauthenticated external request could
drive admin-only gateway methods (sessions.delete, agent.run) through
the subagent runtime.

Propagate the real gateway client into the plugin runtime request scope
when one is available. Plugin HTTP routes now run inside a scoped
runtime client: auth:"plugin" routes receive a non-admin synthetic
operator.write client; gateway-authenticated routes retain admin-capable
scopes. The security boundary is enforced at the HTTP handler level.

Fixes GHSA-xw77-45gv-p728
2026-03-11 14:17:01 +01:00
Robin Waslander
dafd61b5c1
fix(gateway): enforce caller-scope subsetting in device.token.rotate
device.token.rotate accepted attacker-controlled scopes and forwarded
them to rotateDeviceToken without verifying the caller held those
scopes. A pairing-scoped token could rotate up to operator.admin on
any already-paired device whose approvedScopes included admin.

Add a caller-scope subsetting check before rotateDeviceToken: the
requested scopes must be a subset of client.connect.scopes via the
existing roleScopesAllow helper. Reject with missing scope: <scope>
if not.

Also add server.device-token-rotate-authz.test.ts covering both the
priv-esc path and the admin-to-node-invoke chain.

Fixes GHSA-4jpw-hj22-2xmc
2026-03-11 14:16:59 +01:00
Vincent Koc
04e103d10e
fix(terminal): stabilize skills table width across Terminal.app and iTerm (#42849)
* Terminal: measure grapheme display width

* Tests: cover grapheme terminal width

* Terminal: wrap table cells by grapheme width

* Tests: cover emoji table alignment

* Terminal: refine table wrapping and width handling

* Terminal: stop shrinking CLI tables by one column

* Skills: use Terminal-safe emoji in list output

* Changelog: note terminal skills table fixes

* Skills: normalize emoji presentation across outputs

* Terminal: consume unsupported escape bytes in tables
2026-03-11 09:13:10 -04:00
Vincent Koc
361f3109a5 Terminal: consume unsupported escape bytes in tables 2026-03-11 09:11:25 -04:00
Vincent Koc
accabda65c Skills: normalize emoji presentation across outputs 2026-03-11 09:11:20 -04:00
Vincent Koc
ad7db1cc06 Changelog: note terminal skills table fixes 2026-03-11 09:05:20 -04:00
Andyliu
10e6e27451
fix(models): guard optional model input capabilities (#42096)
Merged via squash.

Prepared head SHA: d398fa0222b7045b549fd3592d469c079ca3efb6
Co-authored-by: andyliu <2377291+andyliu@users.noreply.github.com>
Co-authored-by: hydro13 <6640526+hydro13@users.noreply.github.com>
Reviewed-by: @hydro13
2026-03-11 13:43:59 +01:00
Nimrod Gutman
144c1b802b
macOS/onboarding: prompt for remote gateway auth tokens (#43100)
Merged via squash.

Prepared head SHA: 00e2ad847b5a47c34e72e9df1574c0d069b7c671
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-03-11 13:53:19 +02:00
Luke
f063e57d4b
fix(macos): use foundationValue when serializing browser proxy POST body (#43069)
Merged via squash.

Prepared head SHA: 04c33fa0615a12ff70daae0ff874d578a3bff91e
Co-authored-by: ImLukeF <1272861+Effet@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-11 19:14:01 +08:00
Nimrod Gutman
2d91284fdb
feat(ios): add local beta release flow (#42991)
Merged via squash.

Prepared head SHA: 82b38fe93b71e7a06252fb33b8559cebc6c81548
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-03-11 12:32:28 +02:00
Frank Yang
665f677265 docs(changelog): update context pruning PR reference 2026-03-11 18:07:37 +08:00
Frank Yang
d68d4362ee fix(context-pruning): cover image-only tool-result pruning 2026-03-11 18:07:37 +08:00
MoerAI
a78674f115 fix(context-pruning): prune image-containing tool results instead of skipping them (#41789) 2026-03-11 18:07:37 +08:00
ademczuk
dc4441322f
fix(agents): include azure-openai in Responses API store override (#42934)
Merged via squash.

Prepared head SHA: d3285fef41001bb25a8d1cb47a37ee9a132ffb9e
Co-authored-by: ademczuk <5212682+ademczuk@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-11 16:16:10 +08:00
Ayaan Zaidi
a2e30824e6 fix(telegram): fall back on ambiguous first preview sends 2026-03-11 11:23:10 +05:30
Wayne
e37e1ed24e
fix(telegram): prevent duplicate messages with slow LLM providers (#41932)
Merged via squash.

Prepared head SHA: 2f50c51d5acd40a0899ba4c9b557cca377cb47d2
Co-authored-by: hougangdev <105773686+hougangdev@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-11 11:19:55 +05:30
Vincent Koc
f46913b834 Skills: use Terminal-safe emoji in list output 2026-03-11 01:40:15 -04:00
Vincent Koc
209decf25c Terminal: stop shrinking CLI tables by one column 2026-03-11 01:40:01 -04:00
Vincent Koc
c58fffdab6 Terminal: refine table wrapping and width handling 2026-03-11 01:39:43 -04:00
Luke
7761e7626f
Providers: add Opencode Go support (#42313)
* feat(providers): add opencode-go provider support and onboarding

* Onboard: unify OpenCode auth handling openclaw#42313 thanks @ImLukeF

* Docs: merge OpenCode Zen and Go docs openclaw#42313 thanks @ImLukeF

* Update CHANGELOG.md

---------

Co-authored-by: Ubuntu <ubuntu@vps-90352893.vps.ovh.ca>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-11 01:31:06 -04:00
Vincent Koc
0d7db6c652
Update CHANGELOG.md 2026-03-11 01:04:18 -04:00
Vincent Koc
bd33a340fb
fix(sandbox): sanitize Docker env before marking OPENCLAW_CLI (#42256)
* Sandbox: sanitize Docker env before exec marker injection

* Sandbox: add regression test for Docker exec marker env

* Sandbox: disable Windows shell fallback for Docker

* Sandbox: cover Windows Docker wrapper rejection

* Sandbox: test strict env sanitization through Docker args
2026-03-11 00:59:36 -04:00
Luke
061b8258bc
macOS: add chat model selector and persist thinking (#42314)
* feat(macos): add chat model selector and thinking persistence UX

* Chat UI: carry session model providers

* Docs: add macOS model selector changelog

* macOS: persist extended thinking levels

* Chat UI: keep model picker state in sync

* Chat UI tests: cover model selection races

---------

Co-authored-by: Ubuntu <ubuntu@vps-90352893.vps.ovh.ca>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-11 00:43:04 -04:00
Ayaan Zaidi
bf70a333fa fix: clear pnpm prod audit vulnerabilities 2026-03-11 09:33:45 +05:30
Peter Steinberger
0aa79fc4d3 fix(build): restore full gate 2026-03-11 02:52:55 +00:00
Peter Steinberger
c91d1622d5 fix(gateway): split conversation reset from admin reset 2026-03-11 02:50:44 +00:00
Peter Steinberger
0ab8d20917 docs(changelog): note interpreter approval hardening 2026-03-11 02:45:10 +00:00
Josh Avant
0125ce1f44
Gateway: fail closed unresolved local auth SecretRefs (#42672)
* Gateway: fail closed unresolved local auth SecretRefs

* Docs: align node-host gateway auth precedence

* CI: resolve rebase breakages in checks lanes

* Tests: isolate LOCAL_REMOTE_FALLBACK_TOKEN env state

* Gateway: remove stale remote.enabled auth-surface semantics

* Changelog: note gateway SecretRef fail-closed fix
2026-03-10 21:41:56 -05:00
Peter Steinberger
a52104c235 test: restore fs bridge helper export 2026-03-11 02:38:00 +00:00
Peter Steinberger
a0d5462571 fix(security): pin staged writes and fs mutations 2026-03-11 02:38:00 +00:00
Peter Steinberger
daaf211e20 fix(node-host): fail closed on unbound interpreter approvals 2026-03-11 02:36:38 +00:00
Vincent Koc
f7f75519ad Deps: patch file-type and hono 2026-03-10 22:30:44 -04:00
Peter Steinberger
72b0e00eab refactor: unify sandbox fs bridge mutations 2026-03-11 02:10:23 +00:00
Shadow
841f3b4af5
Switch to org-wide funding.yml file 2026-03-10 20:55:08 -05:00
Peter Steinberger
aad014c7c1 fix: harden subagent control boundaries 2026-03-11 01:44:38 +00:00
Peter Steinberger
68c674d37c refactor(security): simplify system.run approval model 2026-03-11 01:43:06 +00:00
Peter Steinberger
5716e52417 refactor: unify gateway credential planning 2026-03-11 01:37:25 +00:00
Peter Steinberger
3a39dc4e18 refactor(security): unify config write target policy 2026-03-11 01:35:04 +00:00
Peter Steinberger
7289c19f1a fix(security): bind system.run approvals to exact argv text 2026-03-11 01:25:31 +00:00
Peter Steinberger
8eac939417 fix(security): enforce target account configWrites 2026-03-11 01:24:36 +00:00
Peter Steinberger
11924a7026 fix(sandbox): pin fs-bridge staged writes 2026-03-11 01:15:47 +00:00
Peter Steinberger
702f6f3305 fix: fail closed for unresolved local gateway auth refs 2026-03-11 01:14:06 +00:00
Peter Steinberger
ecdbd8aa52 fix(security): restrict leaf subagent control scope 2026-03-11 01:12:22 +00:00
Gustavo Madeira Santana
3ba6491659 Infra: extract backup and plugin path helpers 2026-03-10 20:16:35 -04:00
Peter Steinberger
f4a4b50cd5 refactor: compile allowlist matchers 2026-03-11 00:07:47 +00:00
Peter Steinberger
fa0329c340 test: cover cron nested lane selection 2026-03-11 00:02:00 +00:00
Peter Steinberger
f604cbedf3 fix: remove stale allowlist matcher cache 2026-03-11 00:00:04 +00:00
Peter Steinberger
825a435709 fix: avoid cron embedded lane deadlock 2026-03-10 23:56:21 +00:00
Peter Steinberger
8901032007 Merge remote-tracking branch 'origin/main' 2026-03-10 23:55:30 +00:00
Josh Avant
36d2ae2a22 SecretRef: harden custom/provider secret persistence and reuse (#42554)
* Models: gate custom provider keys by usable secret semantics

* Config: project runtime writes onto source snapshot

* Models: prevent stale apiKey preservation for marker-managed providers

* Runner: strip SecretRef marker headers from resolved models

* Secrets: scan active agent models.json path in audit

* Config: guard runtime-source projection for unrelated configs

* Extensions: fix onboarding type errors in CI

* Tests: align setup helper account-enabled expectation

* Secrets audit: harden models.json file reads

* fix: harden SecretRef custom/provider secret persistence (#42554) (thanks @joshavant)
2026-03-10 23:55:10 +00:00
Peter Steinberger
20237358d9 refactor: clarify archive staging intent 2026-03-10 23:54:12 +00:00
Peter Steinberger
0bac47de51 refactor: split tar.bz2 extraction helpers 2026-03-10 23:53:32 +00:00
Peter Steinberger
9c64508822 refactor: rename tar archive preflight checker 2026-03-10 23:52:51 +00:00
Peter Steinberger
6565ae1857 refactor: extract archive staging helpers 2026-03-10 23:52:31 +00:00
Peter Steinberger
658cf4bd94 fix: harden archive extraction destinations 2026-03-10 23:49:35 +00:00
Josh Avant
fbc66324ee
SecretRef: harden custom/provider secret persistence and reuse (#42554)
* Models: gate custom provider keys by usable secret semantics

* Config: project runtime writes onto source snapshot

* Models: prevent stale apiKey preservation for marker-managed providers

* Runner: strip SecretRef marker headers from resolved models

* Secrets: scan active agent models.json path in audit

* Config: guard runtime-source projection for unrelated configs

* Extensions: fix onboarding type errors in CI

* Tests: align setup helper account-enabled expectation

* Secrets audit: harden models.json file reads

* fix: harden SecretRef custom/provider secret persistence (#42554) (thanks @joshavant)
2026-03-10 18:46:47 -05:00
Peter Steinberger
201420a7ee fix: harden secret-file readers 2026-03-10 23:40:10 +00:00
Peter Steinberger
208fb1aa35 test: share runtime group policy fallback cases 2026-03-10 22:20:19 +00:00
Peter Steinberger
344b2286aa refactor: share windows command shim resolution 2026-03-10 22:18:04 +00:00
Peter Steinberger
1df78202b9 refactor: share approval gateway client setup 2026-03-10 22:18:04 +00:00
Peter Steinberger
bc1cc2e50f refactor: share telegram payload send flow 2026-03-10 22:18:04 +00:00
Peter Steinberger
a455c0cc3d refactor: share passive account lifecycle helpers 2026-03-10 22:18:04 +00:00
Peter Steinberger
50ded5052f refactor: share channel config schema fragments 2026-03-10 22:18:04 +00:00
Peter Steinberger
4a8e039a5f refactor: share channel config security scaffolding 2026-03-10 22:18:04 +00:00
Peter Steinberger
725958c66f refactor: share onboarding secret prompt flows 2026-03-10 22:18:03 +00:00
Peter Steinberger
00170f8e1a refactor: share scoped account config patching 2026-03-10 22:18:03 +00:00
David Guttman
b517dc089a
feat(discord): add autoArchiveDuration config option (#35065)
* feat(discord): add autoArchiveDuration config option

Add config option to control auto-archive duration for auto-created threads:

- autoArchiveDuration: 60 (default), 1440, 4320, or 10080
  - Sets archive duration in minutes (1hr/1day/3days/1week)
  - Accepts both string and numeric values
  - Discord's default was 60 minutes (hardcoded)

Example config:
```yaml
channels:
  discord:
    guilds:
      GUILD_ID:
        channels:
          CHANNEL_ID:
            autoThread: true
            autoArchiveDuration: 10080  # 1 week
```

* feat(discord): add autoArchiveDuration changelog entry (#35065) (thanks @davidguttman)

---------

Co-authored-by: Onur <onur@textcortex.com>
2026-03-10 23:13:24 +01:00
Josh Avant
a76e810193
fix(gateway): harden token fallback/reconnect behavior and docs (#42507)
* fix(gateway): harden token fallback and auth reconnect handling

* docs(gateway): clarify auth retry and token-drift recovery

* fix(gateway): tighten auth reconnect gating across clients

* fix: harden gateway token retry (#42507) (thanks @joshavant)
2026-03-10 17:05:57 -05:00
Rodrigo Uroz
ff2e7a2945
fix(acp): strip provider auth env for child ACP processes (openclaw#42250)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-10 16:50:10 -05:00
Matt Van Horn
5ed96da990
fix(browser): surface 429 rate limit errors with actionable hints (#40491)
Merged via squash.

Prepared head SHA: 13839c2dbd0396d8a582aa2c5c206d2efaff1b07
Co-authored-by: mvanhorn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-11 00:49:31 +03:00
Pejman Pour-Moezzi
7c76acafd6
fix(acp): scope cancellation and event routing by runId (#41331) 2026-03-10 22:37:21 +01:00
Onur
c00117aff2
docs: require codex review in contributing guide (#42503) 2026-03-10 22:15:00 +01:00
PonyX-lab
53374394fb
Fix stale runtime model reuse on session reset (#41173)
Merged via squash.

Prepared head SHA: d8a04a466a3b110aa7d608cc1425a66fa65e326b
Co-authored-by: PonyX-lab <266766228+PonyX-lab@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-10 14:02:43 -07:00
Shadow
0c17e7c225
docs: document r: spam auto-close label 2026-03-10 16:00:34 -05:00
Shadow
b16ee34c34
fix(ci): auto-close and lock r: spam items 2026-03-10 15:58:24 -05:00
David Guttman
9f5dee32f6
fix(acp): implicit streamToParent for mode=run without thread (#42404)
* fix(acp): implicit streamToParent for mode=run without thread

When spawning ACP sessions with mode=run and no thread binding,
automatically route output to parent session instead of Discord.
This enables agent-to-agent supervision patterns where the spawning
agent wants results returned programmatically, not posted as chat.

The change makes sessions_spawn with runtime=acp and thread=false
behave like direct acpx invocation - output goes to the spawning
session, not to Discord.

Fixes the issue where mode=run without thread still posted to Discord
because hasDeliveryTarget was true when called from a Discord context.

* fix: use resolved spawnMode instead of params.mode

Move implicit streamToParent check to after resolveSpawnMode so that
both explicit mode="run" and omitted mode (which defaults to "run"
when thread is false) correctly trigger parent routing.

This fixes the issue where callers that rely on default mode selection
would not get the intended parent streaming behavior.

* fix: tighten implicit ACP parent relay gating (#42404) (thanks @davidguttman)

---------

Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
2026-03-10 21:42:15 +01:00
Peter Steinberger
f209a9be80 test: extract sendpayload outbound contract suite 2026-03-10 20:35:03 +00:00
Peter Steinberger
158a3b49a7 test: deduplicate cli option collision fixtures 2026-03-10 20:34:54 +00:00
Peter Steinberger
283570de4d fix: normalize stale openai completions transport 2026-03-10 20:23:03 +00:00
Peter Steinberger
0976317f96 test: deduplicate diffs extension fixtures 2026-03-10 20:22:56 +00:00
Peter Steinberger
23cd997526 fix: make install smoke docker-driver safe 2026-03-10 20:02:26 +00:00
Peter Steinberger
6d4241cbd9 fix: wire modelstudio env discovery (#40634) (thanks @pomelo-nwu) 2026-03-10 19:58:43 +00:00
pomelo-nwu
95eaa08781 refactor: rename bailian to modelstudio and fix review issues
- Rename provider ID, constants, functions, CLI flags, and types from
  "bailian" to "modelstudio" to match the official English name
  "Alibaba Cloud Model Studio".
- Fix P2 bug: global endpoint variant now always overwrites baseUrl
  instead of silently preserving a stale CN URL.
- Fix P1 bug: add modelstudio entry to PROVIDER_ENV_VARS so
  secret-input-mode=ref no longer throws.
- Move Model Studio imports to top of onboard-auth.config-core.ts.
- Remove unused BAILIAN_BASE_URL export.

Made-with: Cursor
2026-03-10 19:58:43 +00:00
pomelo-nwu
77a35025e8 feat: integrate Alibaba Bailian Coding Plan into onboarding wizard 2026-03-10 19:58:43 +00:00
Nimrod Gutman
c2e41c57c9 fix(ios): make pairing instructions generic 2026-03-10 21:44:00 +02:00
Nimrod Gutman
6bcf89b09b feat(ios): refresh home canvas toolbar 2026-03-10 21:44:00 +02:00
Mariano Belinky
67746a12de iOS: add welcome home canvas 2026-03-10 21:44:00 +02:00
Onur
8ba1b6eff1
ci: add npm release workflow and CalVer checks (#42414) (thanks @onutc) 2026-03-10 20:09:25 +01:00
Altay
0ff184397d
docs(telegram): clarify group and sender allowlists (#42451)
Merged via squash.

Prepared head SHA: f30cacafb326a1ed0ef996424f049ae7b36ff1a6
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 21:56:30 +03:00
Josh Avant
b205de6154
Docs: add changelog entry for SecretRef traversal (#42455) 2026-03-10 13:52:50 -05:00
Josh Avant
d30dc28b8c
Secrets: reject exec SecretRef traversal ids across schema/runtime/gateway (#42370)
* Secrets: harden exec SecretRef validation and reload LKG coverage

* Tests: harden exec fast-exit stdin regression case

* Tests: align lifecycle daemon test formatting with oxfmt 0.36
2026-03-10 13:45:37 -05:00
Josh Avant
0687e04760
fix: thread runtime config through Discord/Telegram sends (#42352) (thanks @joshavant) (#42352) 2026-03-10 13:30:57 -05:00
Yufeng He
c2d9386796
fix: log auth profile resolution failures instead of swallowing silently (#41271)
Merged via squash.

Prepared head SHA: 049d1e119a4df88ae00870353a9e7134261fe9dd
Co-authored-by: he-yufeng <40085740+he-yufeng@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 20:38:49 +03:00
JiangNan
e9e8b81939
fix(failover): classify Gemini MALFORMED_RESPONSE as retryable timeout (#42292)
Merged via squash.

Prepared head SHA: 68f106ff49fc7a28a806601bc8ca1e5e77c6e8c6
Co-authored-by: jnMetaCode <12096460+jnMetaCode@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 20:34:32 +03:00
jiarung
bc9b35d6ce
fix(logging): include model and provider in overload/error log (#41236)
Merged via squash.

Prepared head SHA: bb16fecbf7173dbd8f49adacb6147635bad00c56
Co-authored-by: jiarung <16461359+jiarung@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 20:32:14 +03:00
Ayaan Zaidi
3b582f1d54
fix(telegram): chunk long html outbound messages (#42240)
Merged via squash.

Prepared head SHA: 4d79c41ddf33f44749355641936f8c425224ec6f
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-10 22:53:04 +05:30
CryUshio
8bf64f219a
fix: recognize Poe 402 'used up your points' as billing for fallback (#42278)
Merged via squash.

Prepared head SHA: f3cdfa76dd9afcb023504eef723036e826e6ebc5
Co-authored-by: CryUshio <30655354+CryUshio@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 20:17:36 +03:00
Pejman Pour-Moezzi
466cc816a8
docs(acp): document resumeSessionId for session resume (#42280)
* docs(acp): document resumeSessionId for session resume

* docs: clarify ACP resumeSessionId thread/mode behavior (#42280) (thanks @pejmanjohn)

---------

Co-authored-by: Onur <onur@textcortex.com>
2026-03-10 18:06:09 +01:00
sline
bfeea5d23f
fix(agents): prevent /v1beta duplication in Gemini PDF URL (#34369)
Strip trailing /v1beta from baseUrl before appending the version
segment, so callers that already include /v1beta in their base URL
(e.g. subagent-registry) no longer produce /v1beta/v1beta/models/…
which results in a 404 from the Gemini API.

Closes #34312

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 12:52:49 -04:00
Ayaan Zaidi
936607ca22 ci: drop detect-secrets check 2026-03-10 20:35:23 +05:30
Ayaan Zaidi
ac88a39acc fix: align pi-ai 0.57.1 oauth imports and payload hooks 2026-03-10 20:29:03 +05:30
Vincent Koc
a7a5e01c4c Tests: cover emoji table alignment 2026-03-10 10:50:21 -04:00
Vincent Koc
1ec49e33f3 Terminal: wrap table cells by grapheme width 2026-03-10 10:50:11 -04:00
Vincent Koc
4efe7a4dcd Terminal: measure grapheme display width 2026-03-10 10:50:01 -04:00
Vincent Koc
7a8316706c Tests: cover grapheme terminal width 2026-03-10 10:49:39 -04:00
George Zhang
f50fc2966b
docs: add #42173 to CHANGELOG — strip leaked model control tokens (#42216)
Thanks @imwyvern.
2026-03-10 07:19:13 -07:00
joshavant
59bc3c6630
Agents: align onPayload callback and OAuth imports 2026-03-10 08:50:30 -05:00
George Zhang
3508b4821b
docs: add Tengji (George) Zhang to maintainer table (#42190) 2026-03-10 06:46:12 -07:00
George Zhang
309162f9a2
fix: strip leaked model control tokens from user-facing text (#42173)
Models like GLM-5 and DeepSeek sometimes emit internal delimiter tokens in their responses. Uses generic pattern in the text extraction pipeline, following the same architecture as stripMinimaxToolCallXml.

Closes #40020
Supersedes #40573

Co-authored-by: imwyvern <100903837+imwyvern@users.noreply.github.com>
2026-03-10 06:27:59 -07:00
Vincent Koc
208b636414 Changelog: add unreleased March 9 entries 2026-03-10 08:51:12 -04:00
Vincent Koc
ccc7003360 Changelog: add unreleased March 9 entries 2026-03-10 08:50:30 -04:00
smysle
d340ea92d1
chore: add .dev-state to .gitignore (#41848)
Merged via squash.

Prepared head SHA: 85c4eb7d261271faa36cffa36a859d218af0378e
Co-authored-by: smysle <207193754+smysle@users.noreply.github.com>
Co-authored-by: hydro13 <6640526+hydro13@users.noreply.github.com>
Reviewed-by: @hydro13
2026-03-10 13:35:04 +01:00
Charles Dusek
048e25c2b2
fix(agents): avoid duplicate same-provider cooldown probes in fallback runs (#41711)
Merged via squash.

Prepared head SHA: 8be8967bcb4be81f6abc5ff078644ec4efcfe7a0
Co-authored-by: cgdusek <38732970+cgdusek@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 15:26:47 +03:00
Echo
bda63c3c7f
fix(mattermost): preserve markdown formatting and native tables (#18655)
Merged via squash.

Prepared head SHA: d30fff1776ba94da0b68e5610248829c05450572
Co-authored-by: echo931 <259437483+echo931@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-10 17:40:01 +05:30
Pejman Pour-Moezzi
aca216bfcf
feat(acp): add resumeSessionId to sessions_spawn for ACP session resume (#41847)
* feat(acp): add resumeSessionId to sessions_spawn for ACP session resume

Thread resumeSessionId through the ACP session spawn pipeline so agents
can resume existing sessions (e.g. a prior Codex conversation) instead
of starting fresh.

Flow: sessions_spawn tool → spawnAcpDirect → initializeSession →
ensureSession → acpx --resume-session flag → agent session/load

- Add resumeSessionId param to sessions-spawn-tool schema with
  description so agents can discover and use it
- Thread through SpawnAcpParams → AcpInitializeSessionInput →
  AcpRuntimeEnsureInput → acpx extension runtime
- Pass as --resume-session flag to acpx CLI
- Error hard (exit 4) on non-existent session, no silent fallback
- All new fields optional for backward compatibility

Depends on acpx >= 0.1.16 (openclaw/acpx#85, merged, pending release).

Tests: 26/26 pass (runtime + tool schema)
Verified e2e: Discord → sessions_spawn(resumeSessionId) → Codex
resumed session and recalled stored secret.

🤖 AI-assisted

* fix: guard resumeSessionId against non-ACP runtime

Add early-return error when resumeSessionId is passed without
runtime="acp" (mirrors existing streamTo guard). Without this,
the parameter is silently ignored and the agent gets a fresh
session instead of resuming.

Also update schema description to note the runtime=acp requirement.

Addresses Greptile review feedback.

* ACP: add changelog entry for session resume (#41847) (thanks @pejmanjohn)

---------

Co-authored-by: Pejman Pour-Moezzi <481729+pejmanjohn@users.noreply.github.com>
Co-authored-by: Onur <onur@textcortex.com>
2026-03-10 10:36:13 +01:00
Bob
c2eb12bbc5
ACPX: bump bundled acpx to 0.1.16 (#41975)
* ACPX: bump bundled acpx to 0.1.16

* fix: bump acpx pin to 0.1.16 (#41975) (thanks @dutifulbob)

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-10 10:18:09 +01:00
Teconomix
6d0547dc2e
mattermost: fix DM media upload for unprefixed user IDs (#29925)
Merged via squash.

Prepared head SHA: 5cffcb072cc82394fe4c93d6c1c0c520325180b7
Co-authored-by: teconomix <6959299+teconomix@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-10 14:22:24 +05:30
Brad Groux
568b0a22bb
fix(msteams): use General channel conversation ID as team key for Bot Framework compatibility (#41838)
* fix(msteams): use General channel conversation ID as team key for Bot Framework compatibility

Bot Framework sends `activity.channelData.team.id` as the General channel's
conversation ID (e.g. `19:abc@thread.tacv2`), not the Graph API group GUID
(e.g. `fa101332-cf00-431b-b0ea-f701a85fde81`). The startup resolver was
storing the Graph GUID as the team config key, so runtime matching always
failed and every channel message was silently dropped.

Fix: always call `listChannelsForTeam` during resolution to find the General
channel, then use its conversation ID as the stored `teamId`. When a specific
channel is also configured, reuse the same channel list rather than issuing a
second API call. Falls back to the Graph GUID if the General channel cannot
be found (renamed/deleted edge case).

Fixes #41390

* fix(msteams): handle listChannelsForTeam failure gracefully

* fix(msteams): trim General channel ID and guard against empty string

* fix: document MS Teams allowlist team-key fix (#41838) (thanks @BradGroux)

---------

Co-authored-by: bradgroux <bradgroux@users.noreply.github.com>
Co-authored-by: Onur <onur@textcortex.com>
2026-03-10 09:13:41 +01:00
Daniel Hnyk
450d49ea52
fix(mattermost): read replyTo param in plugin handleAction send (#41176)
Merged via squash.

Prepared head SHA: 33cac4c33f24d12a53189c4de01a39d0a6c2ada1
Co-authored-by: hnykda <2741256+hnykda@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-10 13:19:54 +05:30
Daniel Reis
3495563cfe
fix(sandbox): pass real workspace to sessions_spawn when workspaceAccess is ro (#40757)
Merged via squash.

Prepared head SHA: 0e8b27bf80e41fcce77db8298ac74205c7b3b2c3
Co-authored-by: dsantoreis <66363641+dsantoreis@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-03-10 04:12:50 -03:00
Austin
9d403fd415
fix(ui): replace Manual RPC text input with sorted method dropdown (#14967)
Merged via squash.

Prepared head SHA: 1bb49b2e64675d37882d0975eb19f8fafd3c6fe9
Co-authored-by: rixau <112558420+rixau@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev
2026-03-10 01:30:31 -05:00
Val Alexander
5296147c20
CI: select Swift 6.2 toolchain for CodeQL (#41787)
Merged via squash.

Prepared head SHA: 8abc6c16571661450a6b932de17b74607ecacb8e
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev
2026-03-10 01:22:41 -05:00
Frank Yang
8306eabf85
fix(agents): forward memory flush write path (#41761)
Merged via squash.

Prepared head SHA: 0a8ebf8e5b426c5b402adc34509830f46e4bb849
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-10 14:18:41 +08:00
Eugene
45b74fb56c
fix(telegram): move network fallback to resolver-scoped dispatchers (#40740)
Merged via squash.

Prepared head SHA: a4456d48b42d6c588b2858831a2391d015260a9b
Co-authored-by: sircrumpet <4436535+sircrumpet@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-10 11:28:51 +05:30
Urian Paul Danut
d1a59557b5
fix(security): harden replaceMarkers() to catch space/underscore boundary marker variants (#35983)
Merged via squash.

Prepared head SHA: ff07dc45a9c9665c0a88c9898684a5c97f76473b
Co-authored-by: urianpaul94 <33277984+urianpaul94@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-10 13:54:23 +08:00
Laurie Luo
cf9db91b61
fix(web-search): recover OpenRouter Perplexity citations from message annotations (#40881)
Merged via squash.

Prepared head SHA: 66c8bb2c6a4bbc95a5d23661c185f1e551c2929e
Co-authored-by: laurieluo <89195476+laurieluo@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-10 10:37:44 +05:30
futuremind2026
382287026b
cron: record lastErrorReason in job state (#14382)
Merged via squash.

Prepared head SHA: baa6b5d566a41950dea0a214881eef48697326d8
Co-authored-by: futuremind2026 <258860756+futuremind2026@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev
2026-03-10 00:01:45 -05:00
Wayne
da4fec6641
fix(telegram): prevent duplicate messages when preview edit times out (#41662)
Merged via squash.

Prepared head SHA: 2780e62d070d7b4c4d7447e966ca172e33e44ad4
Co-authored-by: hougangdev <105773686+hougangdev@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-10 10:17:39 +05:30
Frank Yang
96e4975922
fix: protect bootstrap files during memory flush (#38574)
Merged via squash.

Prepared head SHA: a0b9a02e2ef1a6f5480621ccb799a8b35a10ce48
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-10 12:44:33 +08:00
Benji Peng
989ee21b24
ui: fix sessions table collapse on narrow widths (#12175)
Merged via squash.

Prepared head SHA: b1fcfba868fcfb7b9ee3496725921f3f38f58ac4
Co-authored-by: benjipeng <11394934+benjipeng@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Reviewed-by: @BunsDev
2026-03-09 23:14:07 -05:00
Tak Hoffman
705c6a422d
Add provider routing details to bug report form (#41712) 2026-03-09 23:01:55 -05:00
Josh Avant
f0eb67923c
fix(secrets): resolve web tool SecretRefs atomically at runtime 2026-03-09 22:57:03 -05:00
Ayaan Zaidi
93c44e3dad
ci: drop gha cache from docker release (#41692) 2026-03-10 09:14:57 +05:30
Shadow
e42c4f4513
docs: harden PR review gates against unsubstantiated fixes 2026-03-09 22:43:56 -05:00
Ayane
391f9430ca
fix(feishu): pass mediaLocalRoots in sendText local-image auto-convert shim (openclaw#40623)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: ayanesakura <40628300+ayanesakura@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-09 22:26:06 -05:00
Ayaan Zaidi
e74666cd0a build: raise extension openclaw peer floor 2026-03-10 08:47:56 +05:30
Ayaan Zaidi
731f1aa906 test: avoid detect-secrets churn in observation fixtures 2026-03-10 08:43:19 +05:30
Harold Hunt
de49a8b72c
Telegram: exec approvals for OpenCode/Codex (#37233)
Merged via squash.

Prepared head SHA: f2433790941841ade0efe6292ff4909b2edd6f18
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-09 23:04:35 -04:00
Ayaan Zaidi
9432a8bb3f test: allowlist detect-secrets fixture strings 2026-03-10 08:14:35 +05:30
Zhe Liu
25c2facc2b
fix(agents): fix Brave llm-context empty snippets (#41387)
Merged via squash.

Prepared head SHA: 1e6f1d9d51607a115e4bf912f53149a26a5cdd82
Co-authored-by: zheliu2 <15888718+zheliu2@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-10 08:09:57 +05:30
Shadow
1720174757
fix: auto-close no-ci PR label and document triage labels 2026-03-09 21:30:47 -05:00
Neerav Makwana
5decb00e9d
fix(swiftformat): sync GatewayModels exclusions with OpenClawProtocol (#41242)
Co-authored-by: Shadow <shadow@openclaw.ai>
2026-03-09 20:42:54 -05:00
Val Alexander
6b87489890
Revert "feat(ui): add chat infrastructure modules (slice 1 of dashboard-v2)"
This reverts commit 5a659b0b61dbfa1645fdfa28bf9bffee03a8c9bc.
2026-03-09 18:47:44 -05:00
Val Alexander
9f0a64f855
Revert "Update ui/src/ui/chat/export.ts"
This reverts commit d648dd7643dc1232cc1a9071391fad0587097ca8.
2026-03-09 18:47:40 -05:00
Val Alexander
8e412bad0e
Revert "fix(ui): address review feedback on chat infra slice"
This reverts commit 8a6cd808a138ea73e53b7498bc3fd5dcfd565a7a.
2026-03-09 18:47:37 -05:00
Val Alexander
8a6cd808a1
fix(ui): address review feedback on chat infra slice
- export.ts: handle array content blocks (Claude API format) instead
  of silently exporting empty strings
- slash-command-executor.ts: restrict /kill all to current session's
  subagent subtree instead of all sessions globally
- slash-command-executor.ts: only count truly aborted runs (check
  aborted !== false) in /kill summary
2026-03-09 18:34:47 -05:00
Val Alexander
d648dd7643
Update ui/src/ui/chat/export.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-09 18:34:47 -05:00
Val Alexander
5a659b0b61
feat(ui): add chat infrastructure modules (slice 1 of dashboard-v2)
New self-contained chat modules extracted from dashboard-v2-structure:

- chat/slash-commands.ts: slash command definitions and completions
- chat/slash-command-executor.ts: execute slash commands via gateway RPC
- chat/slash-command-executor.node.test.ts: test coverage
- chat/speech.ts: speech-to-text (STT) support
- chat/input-history.ts: per-session input history navigation
- chat/pinned-messages.ts: pinned message management
- chat/deleted-messages.ts: deleted message tracking
- chat/export.ts: shared exportChatMarkdown helper
- chat-export.ts: re-export shim for backwards compat

Gateway fix:
- Restore usage/cost stripping in chat.history sanitization
- Add test coverage for sanitization behavior

These modules are additive and tree-shaken — no existing code
imports them yet. They will be wired in subsequent slices.
2026-03-09 18:34:47 -05:00
Julia Barth
c0cba7fb72
Fix one-shot exit hangs by tearing down cached memory managers (#40389)
Merged via squash.

Prepared head SHA: 0e600e89cf10f5086ab9d93f445587373a54dcec
Co-authored-by: Julbarth <72460857+Julbarth@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn
2026-03-10 07:34:46 +08:00
Vincent Koc
b48291e01e
Exec: mark child command env with OPENCLAW_CLI (#41411) 2026-03-09 19:14:08 -04:00
Xinhua Gu
4790e40ac6
fix(plugins): expose model auth API to context-engine plugins (#41090)
Merged via squash.

Prepared head SHA: ee96e96bb984cc3e1e152d17199357a8f6db312d
Co-authored-by: xinhuagu <562450+xinhuagu@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-09 16:07:26 -07:00
alan blount
c9a6c542ef
Add HTTP 499 to transient error codes for model fallback (#41468)
Merged via squash.

Prepared head SHA: 0053bae14038e6df9264df364d1c9aa83d5b698e
Co-authored-by: zeroasterisk <23422+zeroasterisk@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 01:55:10 +03:00
Altay
de4c3db3e3
Logging: harden probe suppression for observations (#41338)
Merged via squash.

Prepared head SHA: d18356cb8062935090466d4e142ce202381d4ef2
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 01:40:15 +03:00
Hermione
64746c150c
fix(discord): apply effective maxLinesPerMessage in live replies (#40133)
Merged via squash.

Prepared head SHA: 031d0325347abd11892fbd5f44328f6b3c043902
Co-authored-by: rbutera <6047293+rbutera@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 01:30:24 +03:00
Mariano
56f787e3c0
build(protocol): regenerate Swift models after pending node work schemas (#41477)
Merged via squash.

Prepared head SHA: cae0aaf1c2f23deb65ad797b482d6c212236b18f
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-09 23:22:09 +01:00
Altay
531e8362b1
Agents: add fallback error observations (#41337)
Merged via squash.

Prepared head SHA: 852469c82ff28fb0e1be7f1019f5283e712c4283
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 01:12:10 +03:00
Mariano
3c3474360b
acp: harden follow-up reliability and attachments (#41464)
Merged via squash.

Prepared head SHA: 7d167dff54ab975f90224feb3fe697a5e508e895
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-09 23:03:50 +01:00
Altay
0669b0ddc2
fix(agents): probe single-provider billing cooldowns (#41422)
Merged via squash.

Prepared head SHA: bbc4254b94559f95c34e11734a679cbe852aba52
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-10 00:58:51 +03:00
Mariano
0c7f07818f
acp: add regression coverage and smoke-test docs (#41456)
Merged via squash.

Prepared head SHA: 514d5873520683efcca1542cbca1ee6ec645582b
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-09 22:40:14 +01:00
Mariano
4aebff78bc
acp: forward attachments into ACP runtime sessions (#41427)
Merged via squash.

Prepared head SHA: f2ac51df2c4c84a7c3f7150cb736b087d592ac94
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-09 22:32:32 +01:00
Mariano
8e3f3bc3cf
acp: enrich streaming updates for ide clients (#41442)
Merged via squash.

Prepared head SHA: 0764368e805403edda43c88418f322509bfc5c68
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-09 22:26:46 +01:00
Altay
30340d6835
Sandbox: import STATE_DIR from paths directly (#41439) 2026-03-10 00:18:41 +03:00
Mariano
d346f2d9ce
acp: restore session context and controls (#41425)
Merged via squash.

Prepared head SHA: fcabdf7c31e33bbbd3ef82bdee92755eb0f62c82
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-09 22:17:19 +01:00
Mariano
e6e4169e82
acp: fail honestly in bridge mode (#41424)
Merged via squash.

Prepared head SHA: b5e6e13afe917f47e0bb303159430930591c0c87
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-09 22:01:30 +01:00
Mariano
1bc59cc09d
Gateway: tighten node pending drain semantics (#41429)
Merged via squash.

Prepared head SHA: 361c2eb5c84e3b532862d843536ca68b21336fb2
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-09 21:56:00 +01:00
Mariano
ef95975411
Gateway: add pending node work primitives (#41409)
Merged via squash.

Prepared head SHA: a6d7ca90d71a33c6d634a6396d1e7ae40545ea66
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-09 21:42:57 +01:00
zerone0x
5f90883ad3
fix(auth): reset cooldown error counters on expiry to prevent infinite escalation (#41028)
Merged via squash.

Prepared head SHA: 89bd83f09a141f68c0cd715a3652559ad04be7c6
Co-authored-by: zerone0x <39543393+zerone0x@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-09 23:40:11 +03:00
Robin Waslander
2b2e5e2038
fix(cron): do not misclassify empty/NO_REPLY as interim acknowledgement (#41401)
* fix(cron): do not misclassify empty/NO_REPLY as interim acknowledgement

When a cron task's agent returns NO_REPLY, the payload filter strips the
silent token, leaving an empty text string. isLikelyInterimCronMessage()
previously returned true for empty input, causing the cron runner to
inject a forced rerun prompt ('Your previous response was only an
acknowledgement...').

Change the empty-string branch to return false: empty text after payload
filtering means the agent deliberately chose silent completion, not that
it sent an interim 'on it' message.

Fixes #41246

* fix(cron): do not misclassify empty/NO_REPLY as interim acknowledgement

Fixes #41246. (#41383) thanks @jackal092927.

---------

Co-authored-by: xaeon2026 <xaeon2026@gmail.com>
2026-03-09 21:16:28 +01:00
Mariano
0bcddb3d4f
iOS: reconnect gateway on foreground return (#41384)
Merged via squash.

Prepared head SHA: 0e2e0dcc36fb90e92342430198f82f9594c8caf3
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-09 21:12:23 +01:00
Vincent Koc
d86647d7db
Doctor: fix non-interactive cron repair gating (#41386) 2026-03-09 12:35:31 -07:00
Altay
87d939be79
Agents: add embedded error observations (#41336)
Merged via squash.

Prepared head SHA: 490004229862129ceb21939e382658714e23bd68
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-09 22:27:05 +03:00
Mariano
d4e59a3666
Cron: enforce cron-owned delivery contract (#40998)
Merged via squash.

Prepared head SHA: 5877389e33d5b3a518925b5793a6f6294cb3fb3d
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-09 20:12:37 +01:00
Vincent Koc
7b88249c9e
fix(telegram): bridge direct delivery to internal message:sent hooks (#40185)
* telegram: bridge direct delivery message hooks

* telegram: align sent hooks with command session
2026-03-09 11:21:19 -07:00
Vincent Koc
12702e11a5
plugins: harden global hook runner state (#40184) 2026-03-09 11:20:33 -07:00
Pejman Pour-Moezzi
14bbcad169
fix(acp): propagate setSessionMode gateway errors to client (#41185)
* fix(acp): propagate setSessionMode gateway errors to client

* fix: add changelog entry for ACP setSessionMode propagation (#41185) (thanks @pejmanjohn)

---------

Co-authored-by: Pejman Pour-Moezzi <481729+pejmanjohn@users.noreply.github.com>
Co-authored-by: Onur <onur@textcortex.com>
2026-03-09 17:50:38 +01:00
Pejman Pour-Moezzi
eab39c721b
fix(acp): map error states to end_turn instead of unconditional refusal (#41187)
* fix(acp): map error states to end_turn instead of unconditional refusal

* fix: map ACP error stop reason to end_turn (#41187) (thanks @pejmanjohn)

---------

Co-authored-by: Pejman Pour-Moezzi <481729+pejmanjohn@users.noreply.github.com>
Co-authored-by: Onur <onur@textcortex.com>
2026-03-09 17:37:33 +01:00
Radek Sienkiewicz
4815dc0603
Update CONTRIBUTING.md 2026-03-09 17:27:29 +01:00
Robin Waslander
2cce45962f Add Robin Waslander to maintainers 2026-03-09 17:23:56 +01:00
Radek Sienkiewicz
258b7902a4
Update CONTRIBUTING.md 2026-03-09 17:13:16 +01:00
xaeon2026
425bd89b48
Allow ACP sessions.patch lineage fields on ACP session keys (#40995)
Merged via squash.

Prepared head SHA: c1191edc08618dec1826c57b75556c4e35ccccaf
Co-authored-by: xaeon2026 <264572156+xaeon2026@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-03-09 17:08:11 +01:00
Charles Dusek
54be30ef89
fix(agents): bound compaction retry wait and drain embedded runs on restart (#40324)
Merged via squash.

Prepared head SHA: cfd99562d686b21b9239d3d536c6f6aadc518138
Co-authored-by: cgdusek <38732970+cgdusek@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-09 08:27:29 -07:00
Daniel Reis
fbf5d56366
test(context-engine): add bundle chunk isolation tests for registry (#40460)
Merged via squash.

Prepared head SHA: 44622abfbc83120912060abb1059cbca8a20be83
Co-authored-by: dsantoreis <220753637+dsantoreis@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-09 08:15:35 -07:00
Joshua Lelon Mitchell
98ea71aca5
fix(swiftformat): exclude HostEnvSecurityPolicy.generated.swift from formatters (#39969) 2026-03-09 07:30:43 -07:00
opriz
51bae75120
fix(kimi-coding): fix kimi tool format: use native Anthropic tool schema instead of OpenAI … (openclaw#40008)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: opriz <51957849+opriz@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-09 08:28:47 -05:00
Radek Sienkiewicz
f2f561fab1
fix(ui): preserve control-ui auth across refresh (#40892)
Merged via squash.

Prepared head SHA: f9b2375892485e91c838215b8880d54970179153
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-09 12:50:47 +01:00
Peter Steinberger
f6d0712f50 build: sync plugin versions for 2026.3.9 2026-03-09 08:39:52 +00:00
Peter Steinberger
6c579d7842 fix: stabilize launchd paths and appcast secret scan 2026-03-09 08:37:37 +00:00
Peter Steinberger
f9706fde6a build: bump unreleased version to 2026.3.9 2026-03-09 08:33:58 +00:00
Peter Steinberger
7217b97658 fix(onboard): avoid persisting talk fallback on fresh setup 2026-03-09 08:33:58 +00:00
Peter Steinberger
ce9e91fdfc fix(launchd): harden macOS launchagent install permissions 2026-03-09 08:14:46 +00:00
Peter Steinberger
3caab9260c test: narrow gateway loop signal harness 2026-03-09 07:42:15 +00:00
Peter Steinberger
d0847ee322 chore: prepare 2026.3.8 npm release 2026-03-09 07:37:50 +00:00
Peter Steinberger
1d3dde8d21 fix(update): re-enable launchd service before updater bootstrap 2026-03-09 07:27:11 +00:00
Peter Steinberger
cc0f30f5fb test: fix windows runtime and restart loop harnesses 2026-03-09 07:22:23 +00:00
Peter Steinberger
250d3c949e chore: update appcast for 2026.3.8-beta.1 2026-03-09 07:20:08 +00:00
Peter Steinberger
5fca4c0de0 chore: prepare 2026.3.8-beta.1 release 2026-03-09 07:09:37 +00:00
Peter Steinberger
66c581c64c fix: normalize windows runtime shim executables 2026-03-09 07:01:42 +00:00
Peter Steinberger
912aa8744a test: fix Windows fake runtime bin fixtures 2026-03-09 06:50:52 +00:00
Peter Steinberger
8d2d6db9ad test: fix Node 24+ test runner and subagent registry mocks 2026-03-09 06:45:13 +00:00
Peter Steinberger
2d55ad05f3 docs: move 2026.3.8 entries back to unreleased 2026-03-09 06:34:53 +00:00
Peter Steinberger
9631f4665c chore: refresh secrets baseline 2026-03-09 06:31:35 +00:00
Peter Steinberger
e2a1a4a3db build: sync pnpm lockfile 2026-03-09 06:25:01 +00:00
Peter Steinberger
f82931ba8b docs: reorder 2026.3.8 changelog by impact 2026-03-09 06:24:29 +00:00
Peter Steinberger
17599a8ea2 refactor: flatten supervisor marker hints 2026-03-09 06:19:30 +00:00
Peter Steinberger
e86b38f09d refactor: split cron startup catch-up flow 2026-03-09 06:19:10 +00:00
Peter Steinberger
1d301f74a6 refactor: extract telegram polling session 2026-03-09 06:18:07 +00:00
Peter Steinberger
2e79d82198 build: update app deps except carbon 2026-03-09 06:09:33 +00:00
Peter Steinberger
96d17f3cb1 fix: stagger missed cron jobs on restart (#18925) (thanks @rexlunae) 2026-03-09 06:07:43 +00:00
rexlunae
79853aca9c fix(cron): stagger missed jobs on restart to prevent gateway overload
When the gateway restarts with many overdue cron jobs, they are now
executed with staggered delays to prevent overwhelming the gateway.

- Add missedJobStaggerMs config (default 5s between jobs)
- Add maxMissedJobsPerRestart limit (default 5 jobs immediately)
- Prioritize most overdue jobs by sorting by nextRunAtMs
- Reschedule deferred jobs to fire gradually via normal timer

Fixes #18892
2026-03-09 06:07:43 +00:00
Peter Steinberger
2d5e70f3e7 fix: abort telegram getupdates on shutdown (#23950) (thanks @Gkinthecodeland) 2026-03-09 06:03:46 +00:00
George Kalogirou
6186f620d2 fix(telegram): use manual signal forwarding to avoid cross-realm AbortSignal
AbortSignal.any() fails in Node.js when signals come from different module
contexts (grammY's internal signal vs local AbortController), producing:
"The signals[0] argument must be an instance of AbortSignal. Received an
instance of AbortSignal".

Replace with manual event forwarding that works across all realms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 06:03:46 +00:00
George Kalogirou
2767907abf fix(telegram): abort in-flight getUpdates fetch on shutdown
When the gateway receives SIGTERM, runner.stop() stops the grammY polling
loop but does not abort the in-flight getUpdates HTTP request. That request
hangs for up to 30 seconds (the Telegram API timeout). If a new gateway
instance starts polling during that window, Telegram returns a 409 Conflict
error, causing message loss and requiring exponential backoff recovery.

This is especially problematic with service managers (launchd, systemd)
that restart the process immediately after SIGTERM.

Wire an AbortController into the fetch layer so every Telegram API request
(especially the long-polling getUpdates) aborts immediately on shutdown:

- bot.ts: Accept optional fetchAbortSignal in TelegramBotOptions; wrap
  the grammY fetch with AbortSignal.any() to merge the shutdown signal.
- monitor.ts: Create a per-iteration AbortController, pass its signal to
  createTelegramBot, and abort it from the SIGTERM handler, force-restart
  path, and finally block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 06:03:46 +00:00
Peter Steinberger
9abf014f35 fix(skills): pin validated download roots 2026-03-09 06:00:50 +00:00
Peter Steinberger
cf3a479bd1 fix(node-host): bind bun and deno approval scripts 2026-03-09 05:59:32 +00:00
Peter Steinberger
fd902b0651 fix: detect launchd supervision via xpc service name (#20555) (thanks @dimat) 2026-03-09 05:57:35 +00:00
dimatu
cf796e2a22 fix(gateway): detect launchd supervision via XPC_SERVICE_NAME
On macOS, launchd sets XPC_SERVICE_NAME on managed processes but does
not set LAUNCH_JOB_LABEL or LAUNCH_JOB_NAME. Without checking
XPC_SERVICE_NAME, isLikelySupervisedProcess() returns false for
launchd-managed gateways, causing restartGatewayProcessWithFreshPid()
to fork a detached child instead of returning "supervised". The
detached child holds the gateway lock while launchd simultaneously
respawns the original process (KeepAlive=true), leading to an infinite
lock-timeout / restart loop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 05:57:35 +00:00
merlin
f84adcbe88 fix: release gateway lock on restart failure + reply to Codex reviews
- Release gateway lock when in-process restart fails, so daemon
  restart/stop can still manage the process (Codex P2)
- P1 (env mismatch) already addressed: best-effort by design, documented
  in JSDoc
2026-03-09 05:53:52 +00:00
merlin
f184e7811c fix: move config pre-flight before onNotLoaded in runServiceRestart (Codex P2)
The config check was positioned after onNotLoaded, which could send
SIGUSR1 to an unmanaged process before config was validated.
2026-03-09 05:53:52 +00:00
merlin
c79a0dbdb4 fix: address bot review feedback on #35862
- Remove dead 'return false' in runServiceStart (Greptile)
- Include stack trace in run-loop crash guard error log (Greptile)
- Only catch startup errors on subsequent restarts, not initial start (Codex P1)
- Add JSDoc note about env var false positive edge case (Codex P1)
2026-03-09 05:53:52 +00:00
merlin
335223af32 test: add runServiceStart config pre-flight tests (#35862)
Address Greptile review: add test coverage for runServiceStart path.
The error message copy-paste issue was already fixed in the DRY refactor
(uses params.serviceNoun instead of hardcoded 'restart').
2026-03-09 05:53:52 +00:00
merlin
6740cdf160 fix(gateway): catch startup failure in run loop to prevent process exit (#35862)
When an in-process restart (SIGUSR1) triggers a config-triggered restart
and the new config is invalid, params.start() throws and the while loop
exits, killing the process. On macOS this loses TCC permissions.

Wrap params.start() in try/catch: on failure, set server=null, log the
error, and wait for the next SIGUSR1 instead of crashing.
2026-03-09 05:53:52 +00:00
merlin
eea925b12b fix(gateway): validate config before restart to prevent crash + macOS permission loss (#35862)
When 'openclaw gateway restart' is run with an invalid config, the new
process crashes on startup due to config validation failure. On macOS,
this causes Full Disk Access (TCC) permissions to be lost because the
respawned process has a different PID.

Add getConfigValidationError() helper and pre-flight config validation
in both runServiceRestart() and runServiceStart(). If config is invalid,
abort with a clear error message instead of crashing.

The config watcher's hot-reload path already had this guard
(handleInvalidSnapshot), but the CLI restart/start commands did not.

AI-assisted (OpenClaw agent, fully tested)
2026-03-09 05:53:52 +00:00
Peter Steinberger
88aee9161e fix(msteams): enforce sender allowlists with route allowlists 2026-03-09 05:52:19 +00:00
Peter Steinberger
03a6e3b460 test(cron): cover owner-only tool availability 2026-03-09 05:52:04 +00:00
Peter Steinberger
41e023a80b fix(cron): restore owner-only tools for isolated runs 2026-03-09 05:49:20 +00:00
Peter Steinberger
93775ef6a4 fix(browser): enforce redirect-hop SSRF checks 2026-03-09 05:41:36 +00:00
Peter Steinberger
31402b8542 fix: add changelog for restart timeout recovery (#40380) (thanks @dsantoreis) 2026-03-09 05:38:54 +00:00
DevMac
4bb8104810 test(secrets): skip ACL-dependent runtime snapshot tests on windows 2026-03-09 05:38:54 +00:00
Daniel dos Santos Reis
1d6a2d0165 fix(gateway): exit non-zero on restart shutdown timeout
When a config-change restart hits the force-exit timeout, exit with
code 1 instead of 0 so launchd/systemd treats it as a failure and
triggers a clean process restart. Stop-timeout stays at exit(0)
since graceful stops should not cause supervisor recovery.

Closes #36822
2026-03-09 05:38:54 +00:00
scoootscooob
44beb7be1f fix(daemon): also enable LaunchAgent in repairLaunchAgentBootstrap
The repair/recovery path had the same missing `enable` guard as
`restartLaunchAgent`.  If launchd persists a "disabled" state after a
previous `bootout`, the `bootstrap` call in `repairLaunchAgentBootstrap`
fails silently, leaving the gateway unloaded in the recovery flow.

Add the same `enable` guard before `bootstrap` that was already applied
to `installLaunchAgent` and (in this PR) `restartLaunchAgent`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 05:36:27 +00:00
scoootscooob
69cd376e3b fix(daemon): enable LaunchAgent before bootstrap on restart
restartLaunchAgent was missing the launchctl enable call that
installLaunchAgent already performs. launchd can persist a "disabled"
state after bootout, causing bootstrap to silently fail and leaving the
gateway unloaded until a manual reinstall.

Fixes #39211

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 05:36:27 +00:00
Peter Steinberger
41eef15cdc test: fix windows secrets runtime ci 2026-03-09 05:24:09 +00:00
GazeKingNuWu
41450187dd
fix: clear plugin discovery cache after plugin installation (openclaw#39752)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: GazeKingNuWu <264914544+GazeKingNuWu@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-09 00:16:25 -05:00
Ayaan Zaidi
a40c29b11a
Fix cron text announce delivery for Telegram targets (#40575)
Merged via squash.

Prepared head SHA: 54b1513c78613bddd8cae16ab2d617788a0dacb6
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-09 10:26:17 +05:30
Bronko
d4a960fcca
fix(matrix): restore robust DM routing without the memberCount heuristic (#19736)
* fix(matrix): remove memberCount heuristic from DM detection

The memberCount === 2 check in isDirectMessage() misclassifies 2-person
group rooms (admin channels, monitoring rooms) as DMs, routing them to
the main session instead of their room-specific session.

Matrix already distinguishes DMs from groups at the protocol level via
m.direct account data and is_direct member state flags. Both are already
checked by client.dms.isDm() and hasDirectFlag(). The memberCount
heuristic only adds false positives for 2-person groups.

Move resolveMemberCount() below the protocol-level checks so it is only
reached for rooms not matched by m.direct or is_direct. This narrows its
role to diagnostic logging for confirmed group rooms.

Refs: #19739

* fix(matrix): add conservative fallback for broken DM flags

Some homeservers (notably Continuwuity) have broken m.direct account
data or never set is_direct on invite events. With the memberCount
heuristic removed, these DMs are no longer detected.

Add a conservative fallback that requires two signals before classifying
as DM: memberCount === 2 AND no explicit m.room.name. Group rooms almost
always have explicit names; DMs almost never do.

Error handling distinguishes M_NOT_FOUND (missing state event, expected
for unnamed rooms) from network/auth errors. Non-404 errors fall through
to group classification rather than guessing.

This is independently revertable — removing this commit restores pure
protocol-based detection without any heuristic fallback.

* fix(matrix): add parentPeer for DM room binding support

Add parentPeer to DM routes so conversations are bindable by room ID
while preserving DM trust semantics (secure 1:1, no group restrictions).

Suggested by @KirillShchetinin.

* fix(matrix): override DM detection for explicitly configured rooms

Builds on @robertcorreiro's config-driven approach from #9106.

Move resolveMatrixRoomConfig() before the DM check. If a room matches
a non-wildcard config entry (matchSource === "direct") and was
classified as DM, override the classification to group. This gives users
a deterministic escape hatch for misclassified rooms.

Wildcards are excluded from the override to avoid breaking DM routing
when a "*" catch-all exists. roomConfig is gated behind isRoom so DMs
never inherit group settings (skills, systemPrompt, autoReply).

This commit is independently droppable if the scope is too broad.

* test(matrix): add DM detection and config override tests

- 15 unit tests for direct.ts: all detection paths, priority order,
  M_NOT_FOUND vs network error handling, edge cases (whitespace names,
  API failures)
- 8 unit tests for rooms.ts: matchSource classification, wildcard
  safety for DM override, direct match priority over wildcard

* Changelog: note matrix DM routing follow-up

* fix(matrix): preserve DM fallback and room bindings

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-08 23:26:48 -05:00
Ayaan Zaidi
26e76f9a61
fix: dedupe inbound Telegram DM replies per agent (#40519)
Merged via squash.

Prepared head SHA: 6e235e7d1f7a00ef43455a9240b62e24dbc4ef94
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-09 09:31:05 +05:30
Peter Steinberger
8befd88119 build(protocol): sync generated swift models 2026-03-09 03:49:50 +00:00
Peter Steinberger
99cbda83a2 fix(media): accept reader read result type 2026-03-09 03:49:50 +00:00
Peter Steinberger
e8775cda93 fix(agents): re-expose configured tools under restrictive profiles 2026-03-09 03:49:50 +00:00
Tak Hoffman
ef36cb8cbc
chore(acpx): move runtime test fixtures to test-utils (openclaw#40548)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini
2026-03-08 22:47:04 -05:00
Ayaan Zaidi
f114a5c638 test: fix android talk config contract fixture 2026-03-09 09:15:49 +05:30
Kyle
a438ff4397
fix(plugin-sdk): remove remaining bundled plugin src imports (openclaw#39638)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Kyle <3477429+kyledh@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-08 22:32:45 -05:00
Kesku
adec8b28bb
alphabetize web search providers (#40259)
Merged via squash.

Prepared head SHA: be6350e5ae1c0610f813483dca0a5c98813a7f8e
Co-authored-by: kesku <62210496+kesku@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-09 08:54:54 +05:30
Mariano
e3df94365b
ACP: add optional ingress provenance receipts (#40473)
Merged via squash.

Prepared head SHA: b63e46dd94479de611dab68868340aa18bdaff2f
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-09 04:19:03 +01:00
Tyson Cung
4d501e4ccf
fix(telegram): add download timeout to prevent polling loop hang (#40098)
Merged via squash.

Prepared head SHA: abdfa1a35f41615aaa52e06b5ba9581eeb8f8a6a
Co-authored-by: tysoncung <45380903+tysoncung@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-03-09 08:29:21 +05:30
yuweuii
f6243916b5
fix(models): use 1M context for openai-codex gpt-5.4 (#37876)
Merged via squash.

Prepared head SHA: c41020779ee4f5a77cab932b5fcfb973d3e6a53d
Co-authored-by: yuweuii <82372187+yuweuii@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-08 18:23:49 -07:00
Radek Sienkiewicz
b34158086a
docs(changelog): correct Control UI contributor credit (#40420)
Merged via squash.

Prepared head SHA: e4295fe18b05aee751b2e86579348c14db7c9d55
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-09 02:18:30 +01:00
Vincent Koc
eabda6e3a4 fix(tests): correct security check failure 2026-03-08 18:13:35 -07:00
Vincent Koc
6d5e142b93
Docker: improve build cache reuse (#40351)
* Docker: improve build cache reuse

* Tests: cover Docker build cache layout

* Docker: fix sandbox cache mount continuations

* Docker: document qr-import manifest scope

* Docker: narrow e2e install inputs

* CI: cache Docker builds in workflows

* CI: route sandbox smoke through setup script

* CI: keep sandbox smoke on script path
2026-03-08 17:57:46 -07:00
Radek Sienkiewicz
4f42c03a49
gateway: fix global Control UI 404s for symlinked wrappers and bundled package roots (#40385)
Merged via squash.

Prepared head SHA: 567b3ed68434220bb319a940fa1b834a2f520ff5
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-09 01:50:42 +01:00
Peter Steinberger
13bd3db307 chore(docs): drop refactor cleanup tracker 2026-03-09 00:26:20 +00:00
Peter Steinberger
ff4745fc3f refactor(models): split provider discovery helpers 2026-03-09 00:26:20 +00:00
Peter Steinberger
c29b098744 refactor(models): split models.json planning from writes 2026-03-09 00:26:20 +00:00
Peter Steinberger
24b53fcf47 refactor(agents): extract provider model normalization 2026-03-09 00:26:20 +00:00
Peter Steinberger
dfc18b7a2b refactor(models): extract list row builders 2026-03-09 00:26:20 +00:00
Peter Steinberger
141738f717 refactor: harden browser runtime profile handling 2026-03-09 00:25:43 +00:00
bbblending
4ff4ed7ec9
fix(config): refresh runtime snapshot from disk after write. Fixes #37175 (#37313)
Merged via squash.

Prepared head SHA: 69e1861abf97d20c787a790d37e68c9e3ae2cb1d
Co-authored-by: bbblending <122739024+bbblending@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-08 19:49:15 -04:00
Peter Steinberger
362248e559 refactor: harden browser relay CDP flows 2026-03-08 23:46:10 +00:00
Peter Steinberger
d47aa6bae8 docs(changelog): remove rebase marker 2026-03-08 23:39:03 +00:00
Peter Steinberger
661af2acd3 fix(agents): bootstrap runtime plugins before context-engine resolution 2026-03-08 23:38:38 +00:00
Peter Steinberger
936ac22ec2 refactor: share channel config adapter base 2026-03-08 23:38:24 +00:00
Peter Steinberger
bf601db3fc test: dedupe brave llm-context rejection cases 2026-03-08 23:38:24 +00:00
Peter Steinberger
5845b5bfba refactor: share multi-account config schema fragments 2026-03-08 23:38:24 +00:00
Peter Steinberger
52a253f18c refactor: reuse broadcast route key construction 2026-03-08 23:38:24 +00:00
Peter Steinberger
3f2f007c9a refactor: extract gateway port diagnostics helper 2026-03-08 23:38:24 +00:00
Peter Steinberger
32a6eae576 refactor: share gateway argv parsing 2026-03-08 23:38:24 +00:00
Peter Steinberger
8d7778d1d6 refactor: dedupe plugin runtime stores 2026-03-08 23:38:24 +00:00
Peter Steinberger
3e70109cb2 docs: add refactor cluster backlog 2026-03-08 23:38:24 +00:00
0xsline
024857050a fix: normalize openai-codex gpt-5.4 transport overrides 2026-03-08 23:35:21 +00:00
Doruk Ardahan
3da8882a02 test(models): refresh list assertions after main sync 2026-03-08 23:30:58 +00:00
Doruk Ardahan
b2b99f0325 fix(models): keep --all aligned with synthetic catalog rows 2026-03-08 23:30:58 +00:00
Vincent Koc
a3dc4b5a57
fix(tui): improve color contrast for light-background terminals (#40345)
* fix(tui): improve colour contrast for light-background terminals (#38636)

Detect light terminal backgrounds via COLORFGBG and apply a WCAG
AA-compliant light palette. Adds OPENCLAW_THEME=light|dark env var
override for terminals without auto-detection.

Uses proper sRGB linearisation and WCAG 2.1 contrast ratios to pick
whichever text palette (dark or light) has higher contrast against
the detected background colour.

Co-authored-by: ademczuk <ademczuk@users.noreply.github.com>

* Update CHANGELOG.md

---------

Co-authored-by: ademczuk <andrew.demczuk@gmail.com>
Co-authored-by: ademczuk <ademczuk@users.noreply.github.com>
2026-03-08 16:17:28 -07:00
Vincent Koc
211f68f8ad
docs(changelog): move post-2026.3.8 entries to unreleased (#40342)
* docs(changelog): move post-2026.3.8 entries to unreleased

* Update CHANGELOG.md
2026-03-08 16:11:53 -07:00
Vincent Koc
3f3f66a5f7
Docker: trim runtime image payload (#40307)
* Docker: shrink runtime image payload

* Docker: add runtime pnpm opt-in

* Docker: collapse helper entrypoint chmod layers

* Docker: restore bundled pnpm runtime

* Update CHANGELOG.md
2026-03-08 16:07:04 -07:00
langdon
bd1fe4d8b4
fix(run-openclaw-podman): add SELinux :Z mount option on enforcing/permissive hosts (#39449)
* fix(run-openclaw-podman): add SELinux :Z mount option on Linux with enforcing/permissive SELinux

* fix(quadlet): add SELinux :Z label to openclaw.container.in volume mount

* fix(podman): add SELinux :Z mount option for Fedora/RHEL hosts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:53:09 -04:00
Vincent Koc
3ea3a1c0ca
Update CHANGELOG.md 2026-03-08 15:35:13 -07:00
Vincent Koc
da6592b681
Update CHANGELOG.md 2026-03-08 15:34:56 -07:00
Mariano
abb8f63107
iOS: auto-load the scoped gateway canvas with safe fallback (#40282)
Merged via squash.

- mb-server validation: `swift test --package-path apps/shared/OpenClawKit --filter GatewayNodeSessionTests`
- mb-server validation: `pnpm build`
- Scope note: top-level `RootTabs` shell change was intentionally removed from this PR before merge
2026-03-08 22:47:39 +01:00
Mariano
e806c479f5
Gateway/iOS: replay queued foreground actions safely after resume (#40281)
Merged via squash.

- Local validation: `pnpm exec vitest run --config vitest.gateway.config.ts src/gateway/server-methods/nodes.invoke-wake.test.ts`
- Local validation: `pnpm build`
- mb-server validation: `pnpm exec vitest run --config vitest.gateway.config.ts src/gateway/server-methods/nodes.invoke-wake.test.ts`
- mb-server validation: `pnpm build`
- mb-server validation: `pnpm protocol:check`
2026-03-08 22:46:54 +01:00
Tyler Yust
38543d8196
fix(cron): consolidate announce delivery, fire-and-forget trigger, and minimal prompt mode (#40204)
* fix(cron): consolidate announce delivery and detach manual runs

* fix: queue detached cron runs (#40204)
2026-03-08 14:46:33 -07:00
langdon
7dfd77abeb
fix(setup-podman): cd to TMPDIR before podman load to avoid cwd permission error (#39435)
* fix(setup-podman): cd to TMPDIR before podman load to avoid inherited cwd permission error

* fix(podman): safe cwd in run_as_user to prevent chdir errors

Co-Authored-By: Claude Opus 4.6  <noreply@anthropic.com>
Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 17:32:08 -04:00
Gustavo Madeira Santana
5889a2e98e fix(plugin-sdk): lazily load legacy root alias 2026-03-08 17:13:46 -04:00
Gustavo Madeira Santana
09acbe6528 fix: harden backup verify path validation 2026-03-08 16:53:44 -04:00
Nimrod Gutman
64dd23eade fix(ci): refresh detect-secrets baseline 2026-03-08 22:44:05 +02:00
Nimrod Gutman
dadd7f99cd fix(ci): scope secrets scan to branch changes 2026-03-08 22:21:49 +02:00
shichangs
0ecfd37b44
feat: add local backup CLI (#40163)
Merged via squash.

Prepared head SHA: ed46625ae20c71e5f46d51aa801cefe4ef1c92e3
Co-authored-by: shichangs <46870204+shichangs@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-08 16:21:20 -04:00
Peter Steinberger
a075baba84 refactor(browser): scope CDP sessions and harden stale target recovery 2026-03-08 19:52:33 +00:00
Nimrod Gutman
a6131438ea
fix(macos): improve tailscale gateway discovery (#40167)
Sanitized test tailnet hostnames and re-ran the targeted macOS gateway discovery test suite before merge.
2026-03-08 21:49:42 +02:00
Nimrod Gutman
92726d9863 docs(changelog): credit macos remote token author 2026-03-08 21:28:17 +02:00
Nimrod Gutman
3d3e8fe78c fix(macos): preserve unsupported remote gateway tokens 2026-03-08 21:28:17 +02:00
Charles Dusek
3b7a72bffb tests: document remote token persistence across mode toggle 2026-03-08 21:28:17 +02:00
Charles Dusek
37e0b01684 macos: add mode-toggle remote token sync coverage 2026-03-08 21:28:17 +02:00
Charles Dusek
bd0e6a6efd macos: clarify remote token placeholder text 2026-03-08 21:28:17 +02:00
Charles Dusek
6b338dd283 macos: add remote gateway token field for remote mode 2026-03-08 21:28:17 +02:00
Peter Steinberger
9d467d1620 docs: add WSL2 + Windows remote Chrome CDP troubleshooting (#39407) (thanks @Owlock) 2026-03-08 19:21:42 +00:00
Peter Steinberger
d3111fbbcb fix: make browser relay bind address configurable (#39364) (thanks @mvanhorn) 2026-03-08 19:15:21 +00:00
Matt Van Horn
e883d0b556 fix(browser): add IP validation, fix upgrade handler for non-loopback bind
- Zod schema: validate relayBindHost with ipv4/ipv6 instead of bare string
- Upgrade handler: allow non-loopback connections when bindHost is explicitly
  non-loopback (e.g. 0.0.0.0 for WSL2), keeping loopback-only default
- Test: verify actual bind address via relay.bindHost instead of just checking
  reachability on 127.0.0.1 which passes regardless
- Expose bindHost on ChromeExtensionRelayServer type for inspection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:15:21 +00:00
Matt Van Horn
436ae8a07c fix(infra): make browser relay bind address configurable
Add browser.relayBindHost config option so the Chrome extension relay
server can bind to a non-loopback address (e.g. 0.0.0.0 for WSL2).
Defaults to 127.0.0.1 when unset, preserving current behavior.

Closes #39214

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:15:21 +00:00
Peter Steinberger
0692f71c6f fix: wait for extension relay tab reconnects (#32461) (thanks @AaronWander) 2026-03-08 19:11:58 +00:00
AaronWander
bcb0d1b8b4 fix(browser): wait for extension tabs after relay drop (#32331) 2026-03-08 19:11:58 +00:00
Peter Steinberger
dcdce83da7 fix: normalize wildcard remote CDP websocket URLs (#17760) (thanks @joeharouni) 2026-03-08 19:07:23 +00:00
Joe Harouni
dfa3605bee fix(browser): rewrite 0.0.0.0 and [::] wildcard addresses in CDP WebSocket URLs
Containerized browsers (e.g. browserless in Docker) report
`ws://0.0.0.0:<internal-port>` in their `/json/version` response.
`normalizeCdpWsUrl` rewrites loopback WS hosts to the external
CDP host:port, but `0.0.0.0` and `[::]` were not treated as
addresses needing rewriting, causing OpenClaw to try connecting
to `ws://0.0.0.0:3000` literally — which always fails.

Fixes #17752

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:07:23 +00:00
Josh Lehman
4bfa800cc7
fix: share context engine registry across bundled chunks (#40115)
Merged via squash.

Prepared head SHA: 6af4820b7d0ea64d96f2f894ef3b0e5750b776aa
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-08 11:56:01 -07:00
Peter Steinberger
9914b48c57 fix: preserve loopback ws cdp tab ops (#31085) (thanks @shrey150) 2026-03-08 18:48:51 +00:00
Shrey Pandya
4d904e7b7d style(browser): fix oxfmt formatting in config.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Shrey Pandya
7b58507224 chore: remove vendor-specific references from code comments 2026-03-08 18:48:10 +00:00
Shrey Pandya
c1f6edf48b fix(browser): preserve wss:// cdpUrl in legacy default profile resolution 2026-03-08 18:48:10 +00:00
shrey150
8b2f40f5f6 fix(browser): update existing tests for ws/wss protocol support
Two pre-existing tests still expected ws:// URLs to be rejected by
parseHttpUrl, which now accepts them. Switch the invalid-protocol
fixture to ftp:// and tighten the assertion to match the full
"must be http(s) or ws(s)" error message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
shrey150
f9c220e261 test+docs: comprehensive coverage and generic framing
- Add 12 new tests covering: isWebSocketUrl detection, parseHttpUrl WSS
  acceptance/rejection, direct WS target creation with query params,
  SSRF enforcement on WS URLs, WS reachability probing bypasses HTTP
- Reframe docs section as generic "Direct WebSocket CDP providers" with
  Browserbase as one example — any WSS-based provider works
- Update security tips to mention WSS alongside HTTPS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
shrey150
75602014db feat(browser): support direct WebSocket CDP URLs for Browserbase
Browserbase uses direct WebSocket connections (wss://) rather than the
standard HTTP-based /json/version CDP discovery flow used by Browserless.
This change teaches the browser tool to accept ws:// and wss:// URLs as
cdpUrl values: when a WebSocket URL is detected, OpenClaw connects
directly instead of attempting HTTP discovery.

Changes:
- config.ts: accept ws:// and wss:// in cdpUrl validation
- cdp.helpers.ts: add isWebSocketUrl() helper
- cdp.ts: skip /json/version when cdpUrl is already a WebSocket URL
- chrome.ts: probe WSS endpoints via WebSocket handshake instead of HTTP
- cdp.test.ts: add test for direct WebSocket target creation
- docs/tools/browser.md: update Browserbase section with correct URL
  format and notes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Shrey Pandya
3cf75f760c docs: simplify Browserbase section, drop pricing details
Restore platform-level feature description (CAPTCHA solving, stealth
mode, proxies) without plan-specific pricing gating. Keep free tier
note brief.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Shrey Pandya
ae39a152d8 docs: fact-check Browserbase section against official docs
- Fix CAPTCHA/stealth/proxy claims: these are Developer plan+ only,
  not available on free tier
- Fix free tier limits: 1 browser hour, 15-min session duration
  (not "60 minutes of monthly usage")
- Add link to pricing page for paid plan details
- Simplify structure to match Browserless section format
- Remove sub-headings to match Browserless section style

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Shrey Pandya
efa1204183 docs: restore direct wss://connect.browserbase.com URL
Browserbase exposes a direct WebSocket connect endpoint that
auto-creates a session, similar to how Browserless works. Simplified
the section to use this static URL pattern instead of requiring
manual session creation via the API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Shrey Pandya
9a4610c641 docs: fix Browserbase section to match official docs
Browserbase requires creating a session via their API to get a CDP
connect URL, unlike Browserless which uses a static endpoint. Updated
to show the correct curl-based session creation flow, removed
unverified static WebSocket URL, and added the 5-minute connect
timeout note from official docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Shrey Pandya
c0a988f692 docs: fix duplicate heading lint error
Rename "Configuration" sub-heading to "Profile setup" to avoid
MD024/no-duplicate-heading conflict with the existing top-level
"Configuration" heading.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Shrey Pandya
641e1bacb4 docs: add Browserbase as hosted remote CDP option
Add Browserbase documentation section alongside the existing Browserless
section in the browser docs. Includes signup instructions, CDP connection
configuration, and environment variable setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Shrey Pandya
0252bdc837 Revert "docs: add Browserbase as hosted remote CDP option"
This reverts commit c469657c97848c7a3e1e5135bf4ce735d07d6614.
2026-03-08 18:48:10 +00:00
Shrey Pandya
885199dcaa docs: add Browserbase as hosted remote CDP option
Add Browserbase documentation section alongside the existing Browserless
section in the browser docs. Includes signup instructions, CDP connection
configuration, and environment variable setup for both English and Chinese
(zh-CN) translations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-08 18:48:10 +00:00
Peter Steinberger
3ada30e670 fix: restore gate after rebase 2026-03-08 18:40:15 +00:00
Peter Steinberger
c5095153b0 refactor: extract qmd process runner 2026-03-08 18:40:15 +00:00
Peter Steinberger
68775745d2 fix: restore acp session meta narrowing 2026-03-08 18:40:15 +00:00
Peter Steinberger
f399a818ef refactor: extract ios watch reply coordinator 2026-03-08 18:40:15 +00:00
Peter Steinberger
6bd5735519 refactor: split doctor config analysis helpers 2026-03-08 18:40:15 +00:00
Peter Steinberger
11be305609 refactor: neutralize context engine runtime bridge 2026-03-08 18:40:15 +00:00
Peter Steinberger
f6cb77134c refactor: centralize acp session resolution guards 2026-03-08 18:40:14 +00:00
Peter Steinberger
25d0aa7296 refactor: simplify plugin sdk compatibility aliases 2026-03-08 18:40:14 +00:00
Peter Steinberger
dd7470730d test: isolate git commit resolution fallbacks 2026-03-08 18:40:14 +00:00
Peter Steinberger
c70151e873 test: isolate legacy plugin-sdk root import check 2026-03-08 18:40:14 +00:00
Peter Steinberger
a007bed375 test: isolate plugin loader from mocked module cache 2026-03-08 18:40:14 +00:00
Peter Steinberger
fa580e33c1 refactor: split android talk voice resolution 2026-03-08 18:40:14 +00:00
Peter Steinberger
371c53b282 test: expand talk config contract fixtures 2026-03-08 18:40:14 +00:00
Peter Steinberger
cee2f3e8b4 refactor: dedupe android talk config parsing 2026-03-08 18:40:14 +00:00
Peter Steinberger
2ed644f5d3 fix: require talk resolved payload 2026-03-08 18:40:14 +00:00
Mariano
404b1527e6
fix(acp): persist spawned child session history (#40137)
Merged via squash.

Prepared head SHA: 62de5d56691e1fd185cc617bb5e7283a14ce90b0
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-08 19:37:00 +01:00
Peter Steinberger
72ebaf97c3 test: add implicit provider matrix coverage 2026-03-08 18:26:36 +00:00
Peter Steinberger
8ab762c005 test: standardize hermetic provider env snapshots 2026-03-08 18:26:36 +00:00
Peter Steinberger
d307a7ca1a refactor: extract bundled extension manifest parser 2026-03-08 18:26:36 +00:00
Peter Steinberger
52bc809143 refactor: extract provider stream wrappers 2026-03-08 18:26:36 +00:00
Peter Steinberger
6094035054 refactor: extract static provider builders 2026-03-08 18:26:36 +00:00
Peter Steinberger
f493b03202 refactor: validate bundled extension release metadata 2026-03-08 18:26:36 +00:00
Peter Steinberger
e53d840fed refactor: extract openai stream wrappers 2026-03-08 18:26:36 +00:00
Peter Steinberger
f66bd105a4 refactor: decompose implicit provider resolution 2026-03-08 18:26:36 +00:00
Peter Steinberger
ef2541ceb3 refactor: centralize transcript provider quirks 2026-03-08 18:26:35 +00:00
Peter Steinberger
8a18e2598f refactor: split models registry loading from persistence 2026-03-08 18:26:35 +00:00
Peter Steinberger
749eb4efea refactor: thread config runtime env through models config 2026-03-08 18:26:35 +00:00
Peter Steinberger
64d4d9aabb refactor: move bundled extension gap allowlists into manifests 2026-03-08 18:26:35 +00:00
Peter Steinberger
e5c06dd64a refactor: use model compat for anthropic tool payload normalization 2026-03-08 18:26:35 +00:00
Vincent Koc
efcca3d2ea Tests: format daemon lifecycle CLI coverage 2026-03-08 11:22:41 -07:00
Vincent Koc
0b452a5665 CLI: set local gateway mode in setup 2026-03-08 11:17:29 -07:00
Vincent Koc
4c71176c9f Chore: refresh detect-secrets baseline for Feishu docs 2026-03-08 11:16:03 -07:00
Vincent Koc
c5bba6628e Chore: refresh detect-secrets baseline after final scan 2026-03-08 11:16:03 -07:00
Vincent Koc
3b68d3fded Chore: refresh detect-secrets baseline after docs line changes 2026-03-08 11:16:03 -07:00
Vincent Koc
7856f5730c Web search: allowlist Perplexity auth source type name 2026-03-08 11:16:03 -07:00
Vincent Koc
aebfce7a36 Chore: refresh detect-secrets baseline 2026-03-08 11:16:03 -07:00
Vincent Koc
e19b3679d1 Chore: widen xxxxx detect-secrets allowlist 2026-03-08 11:16:03 -07:00
Vincent Koc
d23d36a2f9 Tests: lower entropy git commit fixtures 2026-03-08 11:16:03 -07:00
Vincent Koc
2ae58542a0 Fixtures: normalize talk config API key placeholder 2026-03-08 11:16:03 -07:00
Vincent Koc
55465d86d9 Docs: use placeholder OpenRouter key in web tool docs 2026-03-08 11:16:03 -07:00
Vincent Koc
615466bdf4 Docs: use placeholder OpenRouter key in Perplexity guide 2026-03-08 11:16:03 -07:00
Vincent Koc
6f4de3cc23 Web search: rename Perplexity auth source helper 2026-03-08 11:16:03 -07:00
Vincent Koc
f19761cefa Tests: reduce web search secret-scan noise 2026-03-08 11:16:03 -07:00
Vincent Koc
5387faa718 CI: satisfy provider merge fixture typing 2026-03-08 11:15:48 -07:00
Tak Hoffman
bdf9739e59 Add too-many-prs override label handling 2026-03-08 13:13:53 -05:00
Rémi
2970d72554
docs: update Brave Search API docs for Feb 2026 plan restructuring (#40111)
Merged via squash.

Prepared head SHA: c651f07855a2e57bb2a25ee42ec62b4bad5b6d8c
Co-authored-by: remusao <1299873+remusao@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-08 14:06:21 -04:00
Tak Hoffman
74624e619d
fix: prefer bundled channel plugins over npm duplicates (#40094)
* fix: prefer bundled channel plugins over npm duplicates

* fix: tighten bundled plugin review follow-ups

* fix: address check gate follow-ups

* docs: add changelog for bundled plugin install fix

* fix: align lifecycle test formatting with CI oxfmt
2026-03-08 13:00:24 -05:00
yuweuii
6c9b49a10b
fix(sessions): clear stale contextTokens on model switch (#38044)
Merged via squash.

Prepared head SHA: bac2df4b7f920ce271f0a15f1db9ed99b35300f3
Co-authored-by: yuweuii <82372187+yuweuii@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-08 10:59:16 -07:00
GitBuck
caf1b84822
feat: allow compaction model override via config (#38753)
Merged via squash.

Prepared head SHA: a3d6d6c845c9ef492370c4cc12ea790ca92123f0
Co-authored-by: starbuck100 <25417736+starbuck100@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-08 10:47:34 -07:00
Vincent Koc
b6520d7172 CI: scope CodeQL JavaScript analysis 2026-03-08 10:29:56 -07:00
Ayaan Zaidi
d4ab731746 fix(telegram): use message previews in DMs 2026-03-08 21:59:43 +05:30
Peter Steinberger
95dff166cb refactor: fold implicit provider injection into resolver 2026-03-08 16:22:52 +00:00
Peter Steinberger
1ec1f0f1f2 refactor: scope prep push results to env artifacts 2026-03-08 16:22:52 +00:00
Peter Steinberger
bce9d93fb5 fix: publish models.json atomically 2026-03-08 16:22:52 +00:00
Peter Steinberger
bec3c0b71d refactor: reuse one models.json read per write 2026-03-08 16:22:52 +00:00
Peter Steinberger
b41bcb08a2 refactor: expand provider capability registry 2026-03-08 16:22:52 +00:00
Peter Steinberger
75e1521660 refactor: extract pure models config merge helpers 2026-03-08 16:22:52 +00:00
Peter Steinberger
79c5c660bb fix: treat model api drift as baseUrl refresh 2026-03-08 16:22:52 +00:00
Peter Steinberger
fa00b1d0ca refactor: dedupe prep branch push flow 2026-03-08 16:22:52 +00:00
Peter Steinberger
032778fb2e refactor: avoid checkout during prep head verification 2026-03-08 16:22:52 +00:00
Peter Steinberger
16a5f0b006 refactor: split talk gateway config loaders 2026-03-08 16:22:48 +00:00
Peter Steinberger
dc5645d459 test: add talk config contract fixtures 2026-03-08 16:22:48 +00:00
Peter Steinberger
8d3d742c6a refactor: require canonical talk resolved payload 2026-03-08 16:22:48 +00:00
Peter Steinberger
87640f9a61 fix: align talk config secret schemas 2026-03-08 16:22:48 +00:00
Peter Steinberger
b7ad8fd661 fix: fail closed talk provider selection 2026-03-08 16:22:48 +00:00
Altay
ca5e352c53
CLI: include commit hash in --version output (#39712)
* CLI: include commit hash in --version output

* fix(version): harden commit SHA resolution and keep output consistent

* CLI: keep install checks compatible with commit-tagged version output

* fix(cli): include commit hash in root version fast path

* test(cli): allow null commit-hash mocks

* Installer: share version parser across install scripts

* Installer: avoid sourcing helpers from stdin cwd

* CLI: note commit-tagged version output

* CLI: anchor commit hash resolution to module root

* CLI: harden commit hash resolution

* CLI: fix commit hash lookup edge cases

* CLI: prefer live git metadata in dev builds

* CLI: keep git lookup inside package root

* Infra: tolerate invalid moduleUrl hints

* CLI: cache baked commit metadata fallbacks

* CLI: align changelog attribution with prep gate

* CLI: restore changelog contributor credit

---------

Co-authored-by: echoVic <echovic@163.com>
Co-authored-by: echoVic <echoVic@users.noreply.github.com>
2026-03-08 19:10:48 +03:00
Hermione
c942655451
fix(hooks): use resolveAgentIdFromSessionKey in runBeforeReset (#39875)
Merged via squash.

Prepared head SHA: 00a2b241df1cf4fa7089d6ee98b5d7e2c7acf105
Co-authored-by: rbutera <6047293+rbutera@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-08 19:07:28 +03:00
Tak Hoffman
fa83010b17
fix(plugins): ship Feishu bundled runtime dependency (#39990)
* fix: ship feishu bundled runtime dependency

* test: align feishu bundled dependency specs
2026-03-08 10:36:41 -05:00
darkamenosa
67b2e81360
Zalo: fix provider lifecycle restarts (#39892)
* Zalo: fix provider lifecycle restarts

* Zalo: add typing indicators, smart webhook cleanup, and API type fixes

* fix review

* add allow list test secrect

* Zalo: bound webhook cleanup during shutdown

* Zalo: bound typing chat action timeout

* Zalo: use plugin-safe abort helper import
2026-03-08 22:33:18 +07:00
Ayaan Zaidi
28e46d04e5
fix(web-search): restore OpenRouter compatibility for Perplexity (#39937) (#39937) 2026-03-08 20:37:54 +05:30
Tak Hoffman
d9e8e8ac15
fix: resolve live config paths in status and gateway metadata (#39952)
* fix: resolve live config paths in status and gateway metadata

* fix: resolve remaining runtime config path references

* test: cover gateway config.set config path response
2026-03-08 09:59:32 -05:00
Peter Steinberger
da3cccb212 test: decouple ios talk parsing coverage 2026-03-08 14:58:29 +00:00
Peter Steinberger
e8ad80afc7 test: cover invalid talk config inputs 2026-03-08 14:58:29 +00:00
Peter Steinberger
b4c8950417 refactor: centralize talk silence timeout defaults 2026-03-08 14:58:29 +00:00
Peter Steinberger
4e2290b841 refactor: add canonical talk config payload 2026-03-08 14:58:29 +00:00
Peter Steinberger
4f482d2a2b refactor: share Apple talk config parsing 2026-03-08 14:58:29 +00:00
Peter Steinberger
eba9dcc67a
Refactor release hardening follow-ups (#39959)
* build: fail fast on stale host-env swift policy

* build: sync generated host env swift policy

* build: guard bundled extension root dependency gaps

* refactor: centralize provider capability quirks

* test: table-drive provider regression coverage

* fix: block merge when prep branch has unpushed commits

* refactor: simplify models config merge preservation
2026-03-08 14:49:58 +00:00
Tak Hoffman
27558806b5
docs: clarify bot review conversation ownership (#39942)
* docs: clarify bot review conversations
2026-03-08 09:39:39 -05:00
Peter Steinberger
0af3118d08 fix: harden talk silence timeout parsing (#39607) (thanks @danodoesdesign)
Co-authored-by: dano does design <dano.does.design@gmail.com>
2026-03-08 14:30:25 +00:00
dano does design
6ff7e8f42e talk: add configurable silence timeout 2026-03-08 14:30:25 +00:00
Varun Chopra
097c588a6b transcript-policy: use named Set for anthropic signature-excluded providers 2026-03-08 14:16:21 +00:00
Varun Chopra
2bf53c2cb6 transcript-policy: don't preserve thinking signatures for kimi-coding (#39798) 2026-03-08 14:16:21 +00:00
Peter Steinberger
e2c07f8a47 fix: land mac universal release defaults (#33891) (thanks @cgdusek) 2026-03-08 14:14:36 +00:00
Charles Dusek
1a364cd066 Docs: clarify notarization handoff in mac release flow 2026-03-08 14:14:36 +00:00
Charles Dusek
9ce79bba34 Docs: mark basic mac dist example as non-notarized 2026-03-08 14:14:36 +00:00
Charles Dusek
047f4acacf Docs: clarify release build arch defaults for mac packaging 2026-03-08 14:14:36 +00:00
Charles Dusek
64760614aa macOS: default release app builds to universal binaries 2026-03-08 14:14:36 +00:00
GeekCheyun
76e4b8277f fix(issue-39839): address tool-call extra params parsing for kimi anthropic-messages 2026-03-08 14:14:06 +00:00
Peter Steinberger
6dadfaa18c docs: use alphabetical provider ordering 2026-03-08 14:10:36 +00:00
Peter Steinberger
d5b305b250 fix: follow up #39321 and #38445 landings 2026-03-08 13:58:13 +00:00
Peter Steinberger
ba2d580c4e docs: note /landpr merge process 2026-03-08 13:57:50 +00:00
Peter Steinberger
acac7e3132 fix: land Brave llm-context gaps (#33383) (thanks @thirumaleshp) 2026-03-08 13:57:12 +00:00
Thirumalesh
8a1015f1aa feat: add Brave Search LLM Context API mode for web_search
Add support for Brave's LLM Context API endpoint (/res/v1/llm/context)
as an optional mode for the web_search tool. When configured with
tools.web.search.brave.mode set to llm-context, the tool returns
pre-extracted page content optimized for LLM grounding instead of
standard URL/snippet results.

The llm-context cache key excludes count and ui_lang parameters that
the LLM Context API does not accept, preventing unnecessary cache
misses.

Closes #14992

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:57:12 +00:00
Ayane
38f4ac5e3c fix(feishu): restore @larksuiteoapi/node-sdk in root dependencies
The bundled Feishu extension fails to load after npm global install because
`@larksuiteoapi/node-sdk` was removed from the root package.json in
e1503349c ("scope extension runtime deps to plugin manifests").

Bundled extensions shipped inside the npm package resolve modules through
the root node_modules tree.  Since `.gitignore` excludes nested
`node_modules/` directories, the extension-level `node_modules/` is
never published, so the module is unreachable at runtime.

Other bundled channel dependencies (e.g. `@discordjs/voice`,
`@slack/bolt`) remain in the root manifest for the same reason.

Re-add the entry — matching the version already declared in
`extensions/feishu/package.json` — so that both global npm installs and
the bundled extension path can locate the SDK.

Closes #39733
2026-03-08 13:56:46 +00:00
Peter Steinberger
d91d24e41d refactor: tighten codex inline api fallback follow-up 2026-03-08 13:54:21 +00:00
Dmitri
d2347ed825 macOS: set speech recognition taskHint for Talk Mode mic capture
Add taskHint = .dictation to Talk Mode's SFSpeechAudioBufferRecognitionRequest,
matching what Voice Wake already sets. Without this hint the recognizer may not
properly initialize audio capture, causing Talk Mode to appear unresponsive.

Co-Authored-By: dmiv <dmiv@users.noreply.github.com>
2026-03-08 13:52:08 +00:00
justinhuangcode
6e086a5b3b chore: update secrets baseline line numbers 2026-03-08 13:51:37 +00:00
justinhuangcode
c9f2d6b761 fix(agents): let forward-compat resolve api when inline model omits it
When a user configures `models.providers.openai-codex` with a models
array but omits the `api` field, `buildInlineProviderModels` produces
an entry with `api: undefined`.  The inline-match early return then
hands this incomplete model straight to the caller, skipping the
forward-compat resolver that would supply the correct
`openai-codex-responses` api — causing a crash loop.

Let the inline match fall through to forward-compat when `api` is
absent so the resolver chain can fill it in.

Fixes #39682
2026-03-08 13:51:37 +00:00
Kros Dai
e9d51d874b Models: fix codex follow-up CI issues 2026-03-08 13:48:13 +00:00
Kros Dai
ec75643a09 Models: scope implicit codex baseUrl override 2026-03-08 13:48:13 +00:00
Kros Dai
374001c4a0 fix: add implicit openai-codex provider snapshot 2026-03-08 13:48:13 +00:00
Felix Hellström
58ae5582f4 macOS: fix VoiceWakeOverlayController exclusivity violation #39275 2026-03-08 13:47:27 +00:00
Peter Steinberger
eebee84093 fix(models): discover Vercel AI Gateway catalog 2026-03-08 13:44:10 +00:00
Peter Steinberger
386b811ddd test(cron): relax concurrent start race timeout 2026-03-08 13:44:10 +00:00
Peter Steinberger
f66cc886d3 test(agents): normalize live model not-found skips 2026-03-08 13:44:10 +00:00
daymade
f930fcbd3f Add regression test and CHANGELOG entry
- Add test ensuring launchd path never returns "failed" status
- Add CHANGELOG.md entry documenting the fix with issue/PR references
- Reference ThrottleInterval evolution (#27650#29078 → current 1s)
2026-03-08 13:42:50 +00:00
daymade
03aea082d0 chore: condense inline comments per code review
Remove redundant rationale from test body (test names already convey it)
and trim the production comment to what/consequence/link (mechanism
details live in #39760).
2026-03-08 13:42:50 +00:00
daymade
5f45e76d61 fix(darwin): remove self-kickstart from launchd gateway restart; rely on KeepAlive
When the gateway needs a config-triggered restart under launchd, calling
`launchctl kickstart -k` from within the service itself races with
launchd's async bootout state machine:

1. `kickstart -k` initiates a launchd bootout → SIGTERM to self
2. Gateway ignores SIGTERM during shutdown → process doesn't exit
3. 2s `spawnSync` timeout kills the launchctl child, but launchd
   continues the bootout asynchronously
4. Fallback `launchctl bootstrap` fails with EIO (service mid-bootout)
5. In-process restart runs on the same PID that launchd will SIGKILL
6. LaunchAgent is permanently unloaded — no auto-restart

Fix: on darwin/launchd, skip `triggerOpenClawRestart()` entirely.
The caller already calls `exitProcess(0)` for supervised mode, and
`KeepAlive=true` (always set in the plist template) restarts the
service within ~1 second.

The schtasks (Windows) path is unchanged — Windows doesn't have an
equivalent KeepAlive mechanism.
2026-03-08 13:42:50 +00:00
5796 changed files with 539352 additions and 141569 deletions

View File

@ -1,8 +1,8 @@
---
description: Update Clawdbot from upstream when branch has diverged (ahead/behind)
description: Update OpenClaw from upstream when branch has diverged (ahead/behind)
---
# Clawdbot Upstream Sync Workflow
# OpenClaw Upstream Sync Workflow
Use this workflow when your fork has diverged from upstream (e.g., "18 commits ahead, 29 commits behind").
@ -132,16 +132,16 @@ pnpm mac:package
```bash
# Kill running app
pkill -x "Clawdbot" || true
pkill -x "OpenClaw" || true
# Move old version
mv /Applications/Clawdbot.app /tmp/Clawdbot-backup.app
mv /Applications/OpenClaw.app /tmp/OpenClaw-backup.app
# Install new build
cp -R dist/Clawdbot.app /Applications/
cp -R dist/OpenClaw.app /Applications/
# Launch
open /Applications/Clawdbot.app
open /Applications/OpenClaw.app
```
---
@ -235,7 +235,7 @@ If upstream introduced new model configurations:
# Check for OpenRouter API key requirements
grep -r "openrouter\|OPENROUTER" src/ --include="*.ts" --include="*.js"
# Update clawdbot.json with fallback chains
# Update openclaw.json with fallback chains
# Add model fallback configurations as needed
```

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,58 @@
---
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.
## macOS flow
- Preferred entrypoint: `pnpm test:parallels:macos`
- Target the snapshot closest to `macOS 26.3.1 fresh`.
- `prlctl exec` is fine for deterministic repo commands, but use the guest Terminal or `prlctl enter` when installer parity or shell-sensitive behavior matters.
- 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.
- Keep onboarding and status output ASCII-clean in logs; fancy punctuation becomes mojibake in current capture paths.
## Linux flow
- Preferred entrypoint: `pnpm test:parallels:linux`
- Use the snapshot closest to fresh `Ubuntu 24.04.3 ARM64`.
- 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.
## 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,74 @@
---
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.
- Use the private maintainer release docs for the actual runbook and `docs/reference/RELEASING.md` for public policy.
## 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
- “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.
## 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
```
## Use the right auth flow
- Core `openclaw` publish uses GitHub trusted publishing.
- Do not use `NPM_TOKEN` or the plugin OTP flow for core 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.
## 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

@ -0,0 +1,62 @@
---
name: parallels-discord-roundtrip
description: Run the macOS Parallels smoke harness with Discord end-to-end roundtrip verification, including guest send, host verification, host reply, and guest readback.
---
# Parallels Discord Roundtrip
Use when macOS Parallels smoke must prove Discord two-way delivery end to end.
## Goal
Cover:
- install on fresh macOS snapshot
- onboard + gateway health
- guest `message send` to Discord
- host sees that message on Discord
- host posts a new Discord message
- guest `message read` sees that new message
## Inputs
- host env var with Discord bot token
- Discord guild ID
- Discord channel ID
- `OPENAI_API_KEY`
## Preferred run
```bash
export OPENCLAW_PARALLELS_DISCORD_TOKEN="$(
ssh peters-mac-studio-1 'jq -r ".channels.discord.token" ~/.openclaw/openclaw.json' | tr -d '\n'
)"
pnpm test:parallels:macos \
--discord-token-env OPENCLAW_PARALLELS_DISCORD_TOKEN \
--discord-guild-id 1456350064065904867 \
--discord-channel-id 1456744319972282449 \
--json
```
## 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`
- If roundtrip flakes, inspect `fresh.discord-roundtrip.log` and `discord-last-readback.json` in the run dir first.
## Pass criteria
- fresh lane or upgrade lane requested passes
- summary reports `discord=pass` for that lane
- guest outbound nonce appears in channel history
- host inbound nonce appears in `openclaw message read` output

View File

@ -41,3 +41,5 @@ pattern = grep -q 'N[O]DE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bash
pattern = env: \{ MISTRAL_API_K[E]Y: "sk-\.\.\." \},
pattern = "ap[i]Key": "xxxxx",
pattern = ap[i]Key: "A[I]za\.\.\.",
# Sparkle appcast signatures are release metadata, not credentials.
pattern = sparkle:edSignature="[A-Za-z0-9+/=]+"

View File

@ -1,5 +1,11 @@
.git
.worktrees
# 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.*
.bun-cache
.bun
.tmp

54
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,54 @@
# Protect the ownership rules themselves.
/.github/CODEOWNERS @steipete
# WARNING: GitHub CODEOWNERS uses last-match-wins semantics.
# If you add overlapping rules below the secops block, include @openclaw/secops
# on those entries too or you can silently remove required secops review.
# Security-sensitive code, config, and docs require secops review.
/SECURITY.md @openclaw/secops
/.github/dependabot.yml @openclaw/secops
/.github/codeql/ @openclaw/secops
/.github/workflows/codeql.yml @openclaw/secops
/src/security/ @openclaw/secops
/src/secrets/ @openclaw/secops
/src/config/*secret*.ts @openclaw/secops
/src/config/**/*secret*.ts @openclaw/secops
/src/gateway/*auth*.ts @openclaw/secops
/src/gateway/**/*auth*.ts @openclaw/secops
/src/gateway/*secret*.ts @openclaw/secops
/src/gateway/**/*secret*.ts @openclaw/secops
/src/gateway/security-path*.ts @openclaw/secops
/src/gateway/resolve-configured-secret-input-string*.ts @openclaw/secops
/src/gateway/protocol/**/*secret*.ts @openclaw/secops
/src/gateway/server-methods/secrets*.ts @openclaw/secops
/src/agents/*auth*.ts @openclaw/secops
/src/agents/**/*auth*.ts @openclaw/secops
/src/agents/auth-profiles*.ts @openclaw/secops
/src/agents/auth-health*.ts @openclaw/secops
/src/agents/auth-profiles/ @openclaw/secops
/src/agents/sandbox.ts @openclaw/secops
/src/agents/sandbox-*.ts @openclaw/secops
/src/agents/sandbox/ @openclaw/secops
/src/infra/secret-file*.ts @openclaw/secops
/src/cron/stagger.ts @openclaw/secops
/src/cron/service/jobs.ts @openclaw/secops
/docs/security/ @openclaw/secops
/docs/gateway/authentication.md @openclaw/secops
/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md @openclaw/secops
/docs/gateway/sandboxing.md @openclaw/secops
/docs/gateway/secrets-plan-contract.md @openclaw/secops
/docs/gateway/secrets.md @openclaw/secops
/docs/gateway/security/ @openclaw/secops
/docs/cli/approvals.md @openclaw/secops
/docs/cli/sandbox.md @openclaw/secops
/docs/cli/security.md @openclaw/secops
/docs/cli/secrets.md @openclaw/secops
/docs/reference/secretref-credential-surface.md @openclaw/secops
/docs/reference/secretref-user-supplied-credentials-matrix.json @openclaw/secops
# Release workflow and its supporting release-path checks.
/.github/workflows/openclaw-npm-release.yml @openclaw/openclaw-release-managers
/docs/reference/RELEASING.md @openclaw/openclaw-release-managers
/scripts/openclaw-npm-publish.sh @openclaw/openclaw-release-managers
/scripts/openclaw-npm-release-check.ts @openclaw/openclaw-release-managers
/scripts/release-check.ts @openclaw/openclaw-release-managers

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
custom: ["https://github.com/sponsors/steipete"]

View File

@ -7,7 +7,8 @@ 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`.
- type: dropdown
id: bug_type
attributes:
@ -23,35 +24,35 @@ body:
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
@ -76,31 +77,57 @@ body:
label: Install method
description: How OpenClaw was installed or launched.
placeholder: npm global / pnpm dev / docker / mac app
- type: input
id: model
attributes:
label: Model
description: Effective model under test.
placeholder: minimax/text-01 / openrouter/anthropic/claude-opus-4.1 / anthropic/claude-sonnet-4.5
validations:
required: true
- type: input
id: provider_chain
attributes:
label: Provider / routing chain
description: Effective request path through gateways, proxies, providers, or model routers.
placeholder: openclaw -> cloudflare-ai-gateway -> minimax
validations:
required: true
- type: textarea
id: provider_setup_details
attributes:
label: Additional provider/model setup details
description: Optional. Include redacted routing details, per-agent overrides, auth-profile interactions, env/config context, or anything else needed to explain the effective provider/model setup. Do not include API keys, tokens, or passwords.
placeholder: |
Default route is openclaw -> cloudflare-ai-gateway -> minimax.
Previous setup was openclaw -> cloudflare-ai-gateway -> openrouter -> minimax.
Relevant config lives in ~/.openclaw/openclaw.json under models.providers.minimax and models.providers.cloudflare-ai-gateway.
- type: textarea
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

@ -1,12 +1,16 @@
name: Setup Node environment
description: >
Initialize submodules with retry, install Node 22, pnpm, optionally Bun,
Initialize submodules with retry, 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.
required: false
default: "22.x"
default: "24.x"
cache-key-suffix:
description: Suffix appended to the pnpm store cache key.
required: false
default: "node24"
pnpm-version:
description: pnpm version for corepack.
required: false
@ -16,7 +20,7 @@ inputs:
required: false
default: "true"
use-sticky-disk:
description: Use Blacksmith sticky disks for pnpm store caching.
description: Request Blacksmith sticky-disk pnpm caching on trusted runs; pull_request runs fall back to actions/cache.
required: false
default: "false"
install-deps:
@ -45,7 +49,7 @@ runs:
exit 1
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
check-latest: false
@ -54,12 +58,12 @@ runs:
uses: ./.github/actions/setup-pnpm-store-cache
with:
pnpm-version: ${{ inputs.pnpm-version }}
cache-key-suffix: "node22"
cache-key-suffix: ${{ inputs.cache-key-suffix }}
use-sticky-disk: ${{ inputs.use-sticky-disk }}
- name: Setup Bun
if: inputs.install-bun == 'true'
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@v2.1.3
with:
bun-version: "1.3.9"

View File

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

View File

@ -0,0 +1,18 @@
name: openclaw-codeql-javascript-typescript
paths:
- src
- extensions
- ui/src
- skills
paths-ignore:
- apps
- dist
- docs
- "**/node_modules"
- "**/coverage"
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.e2e.test.ts"
- "**/*.e2e.test.tsx"

97
.github/labeler.yml vendored
View File

@ -6,7 +6,6 @@
"channel: discord":
- changed-files:
- any-glob-to-any-file:
- "src/discord/**"
- "extensions/discord/**"
- "docs/channels/discord.md"
"channel: irc":
@ -28,7 +27,6 @@
"channel: imessage":
- changed-files:
- any-glob-to-any-file:
- "src/imessage/**"
- "extensions/imessage/**"
- "docs/channels/imessage.md"
"channel: line":
@ -64,19 +62,16 @@
"channel: signal":
- changed-files:
- any-glob-to-any-file:
- "src/signal/**"
- "extensions/signal/**"
- "docs/channels/signal.md"
"channel: slack":
- changed-files:
- any-glob-to-any-file:
- "src/slack/**"
- "extensions/slack/**"
- "docs/channels/slack.md"
"channel: telegram":
- changed-files:
- any-glob-to-any-file:
- "src/telegram/**"
- "extensions/telegram/**"
- "docs/channels/telegram.md"
"channel: tlon":
@ -96,7 +91,6 @@
"channel: whatsapp-web":
- changed-files:
- any-glob-to-any-file:
- "src/web/**"
- "extensions/whatsapp/**"
- "docs/channels/whatsapp.md"
"channel: zalo":
@ -171,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"
@ -204,14 +201,6 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/diagnostics-otel/**"
"extensions: google-antigravity-auth":
- changed-files:
- any-glob-to-any-file:
- "extensions/google-antigravity-auth/**"
"extensions: google-gemini-cli-auth":
- changed-files:
- any-glob-to-any-file:
- "extensions/google-gemini-cli-auth/**"
"extensions: llm-task":
- changed-files:
- any-glob-to-any-file:
@ -244,15 +233,95 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/acpx/**"
"extensions: byteplus":
- changed-files:
- any-glob-to-any-file:
- "extensions/byteplus/**"
"extensions: anthropic":
- changed-files:
- any-glob-to-any-file:
- "extensions/anthropic/**"
"extensions: cloudflare-ai-gateway":
- changed-files:
- any-glob-to-any-file:
- "extensions/cloudflare-ai-gateway/**"
"extensions: minimax-portal-auth":
- changed-files:
- any-glob-to-any-file:
- "extensions/minimax-portal-auth/**"
"extensions: huggingface":
- changed-files:
- any-glob-to-any-file:
- "extensions/huggingface/**"
"extensions: kilocode":
- changed-files:
- any-glob-to-any-file:
- "extensions/kilocode/**"
"extensions: openai":
- changed-files:
- any-glob-to-any-file:
- "extensions/openai/**"
"extensions: kimi-coding":
- changed-files:
- any-glob-to-any-file:
- "extensions/kimi-coding/**"
"extensions: minimax":
- changed-files:
- any-glob-to-any-file:
- "extensions/minimax/**"
"extensions: modelstudio":
- changed-files:
- any-glob-to-any-file:
- "extensions/modelstudio/**"
"extensions: moonshot":
- changed-files:
- any-glob-to-any-file:
- "extensions/moonshot/**"
"extensions: nvidia":
- changed-files:
- any-glob-to-any-file:
- "extensions/nvidia/**"
"extensions: phone-control":
- changed-files:
- any-glob-to-any-file:
- "extensions/phone-control/**"
"extensions: qianfan":
- changed-files:
- any-glob-to-any-file:
- "extensions/qianfan/**"
"extensions: synthetic":
- 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:
- "extensions/talk-voice/**"
"extensions: together":
- changed-files:
- any-glob-to-any-file:
- "extensions/together/**"
"extensions: venice":
- changed-files:
- any-glob-to-any-file:
- "extensions/venice/**"
"extensions: vercel-ai-gateway":
- changed-files:
- any-glob-to-any-file:
- "extensions/vercel-ai-gateway/**"
"extensions: volcengine":
- changed-files:
- any-glob-to-any-file:
- "extensions/volcengine/**"
"extensions: xiaomi":
- changed-files:
- any-glob-to-any-file:
- "extensions/xiaomi/**"
"extensions: fal":
- changed-files:
- any-glob-to-any-file:
- "extensions/fal/**"

View File

@ -11,7 +11,7 @@ Describe the problem and fix in 25 bullets:
- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Refactor required for the fix
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
@ -87,6 +87,13 @@ What you personally verified (not just CI), and how:
- Edge cases checked:
- What you did **not** verify:
## Review Conversations
- [ ] I replied to or resolved every bot review conversation I addressed in this PR.
- [ ] I left unresolved only the conversations that still need reviewer or maintainer judgment.
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
## Compatibility / Migration
- Backward compatible? (`Yes/No`)

View File

@ -5,9 +5,12 @@ on:
types: [opened, edited, labeled]
issue_comment:
types: [created]
pull_request_target:
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned label automation; no untrusted checkout or code execution
types: [labeled]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
permissions: {}
jobs:
@ -17,20 +20,20 @@ jobs:
pull-requests: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Handle labeled items
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@ -51,6 +54,7 @@ jobs:
},
{
label: "r: no-ci-pr",
close: true,
message:
"Please don't make PRs for test failures on main.\n\n" +
"The team is aware of those and will handle them directly on the codebase, not only fixing the tests but also investigating what the root cause is. Having to sift through test-fix-PRs (including some that have been out of date for weeks...) on top of that doesn't help. There are already way too many PRs for humans to manage; please don't make the flood worse.\n\n" +
@ -261,6 +265,8 @@ jobs:
};
const triggerLabel = "trigger-response";
const activePrLimitLabel = "r: too-many-prs";
const activePrLimitOverrideLabel = "r: too-many-prs-override";
const target = context.payload.issue ?? context.payload.pull_request;
if (!target) {
return;
@ -390,6 +396,7 @@ jobs:
}
const invalidLabel = "invalid";
const spamLabel = "r: spam";
const dirtyLabel = "dirty";
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.";
@ -426,6 +433,21 @@ jobs:
});
return;
}
if (labelSet.has(spamLabel)) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
state: "closed",
});
await github.rest.issues.lock({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
lock_reason: "spam",
});
return;
}
if (labelSet.has(invalidLabel)) {
await github.rest.issues.update({
owner: context.repo.owner,
@ -437,6 +459,23 @@ jobs:
}
}
if (issue && labelSet.has(spamLabel)) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: "closed",
state_reason: "not_planned",
});
await github.rest.issues.lock({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
lock_reason: "spam",
});
return;
}
if (issue && labelSet.has(invalidLabel)) {
await github.rest.issues.update({
owner: context.repo.owner,
@ -448,6 +487,10 @@ jobs:
return;
}
if (pullRequest && labelSet.has(activePrLimitOverrideLabel)) {
labelSet.delete(activePrLimitLabel);
}
const rule = rules.find((item) => labelSet.has(item.label));
if (!rule) {
return;

View File

@ -7,7 +7,10 @@ on:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
cancel-in-progress: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
# Detect docs-only changes to skip heavy jobs (test, build, Windows, macOS, Android).
@ -19,7 +22,7 @@ jobs:
docs_changed: ${{ steps.check.outputs.docs_changed }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 1
fetch-tags: false
@ -35,9 +38,8 @@ jobs:
id: check
uses: ./.github/actions/detect-docs-changes
# Detect which heavy areas are touched so PRs can skip unrelated expensive jobs.
# Push to main keeps broad coverage, but this job still needs to run so
# downstream jobs that list it in `needs` are not skipped.
# Detect which heavy areas are touched so CI can skip unrelated expensive jobs.
# Fail-safe: if detection fails, downstream jobs run.
changed-scope:
needs: [docs-scope]
if: needs.docs-scope.outputs.docs_only != 'true'
@ -50,7 +52,7 @@ jobs:
run_windows: ${{ steps.scope.outputs.run_windows }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 1
fetch-tags: false
@ -76,14 +78,58 @@ jobs:
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
changed-extensions:
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
outputs:
has_changed_extensions: ${{ steps.changed.outputs.has_changed_extensions }}
changed_extensions_matrix: ${{ steps.changed.outputs.changed_extensions_matrix }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 1
fetch-tags: false
submodules: false
- name: Ensure changed-extensions 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: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
install-deps: "false"
use-sticky-disk: "false"
- name: Detect changed extensions
id: changed
env:
BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
run: |
node --input-type=module <<'EOF'
import { appendFileSync } from "node:fs";
import { listChangedExtensionIds } from "./scripts/test-extension.mjs";
const extensionIds = listChangedExtensionIds({ base: process.env.BASE_SHA, head: "HEAD" });
const matrix = JSON.stringify({ include: extensionIds.map((extension) => ({ extension })) });
appendFileSync(process.env.GITHUB_OUTPUT, `has_changed_extensions=${extensionIds.length > 0}\n`, "utf8");
appendFileSync(process.env.GITHUB_OUTPUT, `changed_extensions_matrix=${matrix}\n`, "utf8");
EOF
# Build dist once for Node-relevant changes and share it with downstream jobs.
build-artifacts:
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
if: github.event_name == 'push' && needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -98,13 +144,13 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "true"
use-sticky-disk: "false"
- name: Build dist
run: pnpm build
- name: Upload dist artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: dist-build
path: dist/
@ -117,7 +163,7 @@ jobs:
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -125,10 +171,10 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "true"
use-sticky-disk: "false"
- name: Download dist artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: dist-build
path: dist/
@ -138,7 +184,7 @@ jobs:
checks:
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
strategy:
fail-fast: false
@ -146,55 +192,88 @@ jobs:
include:
- runtime: node
task: test
shard_index: 1
shard_count: 2
command: pnpm canvas:a2ui:bundle && pnpm test
- runtime: node
task: test
shard_index: 2
shard_count: 2
command: pnpm canvas:a2ui:bundle && pnpm test
- runtime: node
task: extensions
command: pnpm test:extensions
- runtime: node
task: channels
command: pnpm test:channels
- runtime: node
task: contracts
command: pnpm test:contracts
- runtime: node
task: protocol
command: pnpm protocol:check
- runtime: bun
task: test
command: pnpm canvas:a2ui:bundle && bunx vitest run --config vitest.unit.config.ts
- runtime: node
task: compat-node22
node_version: "22.x"
cache_key_suffix: "node22"
command: |
pnpm build
pnpm test
node scripts/stage-bundled-plugin-runtime-deps.mjs
node --import tsx scripts/release-check.ts
steps:
- name: Skip bun lane on push
if: github.event_name == 'push' && matrix.runtime == 'bun'
run: echo "Skipping bun test lane on push events."
- name: Skip compatibility lanes on pull requests
if: github.event_name == 'pull_request' && (matrix.runtime == 'bun' || matrix.task == 'compat-node22')
run: echo "Skipping push-only lane on pull requests."
- name: Checkout
if: github.event_name != 'push' || matrix.runtime != 'bun'
uses: actions/checkout@v4
if: github.event_name != 'pull_request' || (matrix.runtime != 'bun' && matrix.task != 'compat-node22')
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
if: matrix.runtime != 'bun' || github.event_name != 'push'
if: github.event_name != 'pull_request' || (matrix.runtime != 'bun' && matrix.task != 'compat-node22')
uses: ./.github/actions/setup-node-env
with:
node-version: "${{ matrix.node_version || '24.x' }}"
cache-key-suffix: "${{ matrix.cache_key_suffix || 'node24' }}"
install-bun: "${{ matrix.runtime == 'bun' }}"
use-sticky-disk: "true"
use-sticky-disk: "false"
- name: Configure Node test resources
if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node'
if: (github.event_name != 'pull_request' || (matrix.runtime != 'bun' && matrix.task != 'compat-node22')) && matrix.runtime == 'node' && (matrix.task == 'test' || matrix.task == 'compat-node22')
env:
SHARD_COUNT: ${{ matrix.shard_count || '' }}
SHARD_INDEX: ${{ matrix.shard_index || '' }}
run: |
# `pnpm test` runs `scripts/test-parallel.mjs`, which spawns multiple Node processes.
# Default heap limits have been too low on Linux CI (V8 OOM near 4GB).
echo "OPENCLAW_TEST_WORKERS=2" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144" >> "$GITHUB_ENV"
if [ -n "$SHARD_COUNT" ] && [ -n "$SHARD_INDEX" ]; then
echo "OPENCLAW_TEST_SHARDS=$SHARD_COUNT" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
fi
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
if: matrix.runtime != 'bun' || github.event_name != 'push'
if: github.event_name != 'pull_request' || (matrix.runtime != 'bun' && matrix.task != 'compat-node22')
run: ${{ matrix.command }}
# Types, lint, and format check.
check:
name: "check"
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
extension-fast:
name: "extension-fast"
needs: [docs-scope, changed-scope, changed-extensions]
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' && needs.changed-extensions.outputs.has_changed_extensions == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.changed-extensions.outputs.changed_extensions_matrix) }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -202,7 +281,30 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "true"
use-sticky-disk: "false"
- name: Run changed extension tests
env:
OPENCLAW_CHANGED_EXTENSION: ${{ matrix.extension }}
run: pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION"
# Types, lint, and format check.
check:
name: "check"
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
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: Check types and lint and oxfmt
run: pnpm check
@ -210,9 +312,121 @@ jobs:
- name: Strict TS build smoke
run: pnpm build:strict-smoke
check-additional:
name: "check-additional"
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
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: Run plugin extension boundary guard
id: plugin_extension_boundary
continue-on-error: true
run: pnpm run lint:plugins:no-extension-imports
- name: Run web search provider boundary guard
id: web_search_provider_boundary
continue-on-error: true
run: pnpm run lint:web-search-provider-boundaries
- name: Run extension src boundary guard
id: extension_src_outside_plugin_sdk_boundary
continue-on-error: true
run: pnpm run lint:extensions:no-src-outside-plugin-sdk
- name: Run extension plugin-sdk-internal guard
id: extension_plugin_sdk_internal_boundary
continue-on-error: true
run: pnpm run lint:extensions:no-plugin-sdk-internal
- name: Enforce safe external URL opening policy
id: no_raw_window_open
continue-on-error: true
run: pnpm lint:ui:no-raw-window-open
- name: Run gateway watch regression harness
id: gateway_watch_regression
continue-on-error: true
run: pnpm test:gateway:watch-regression
- name: Upload gateway watch regression artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: gateway-watch-regression
path: .local/gateway-watch-regression/
retention-days: 7
- name: Fail if any additional check failed
if: always()
env:
PLUGIN_EXTENSION_BOUNDARY_OUTCOME: ${{ steps.plugin_extension_boundary.outcome }}
WEB_SEARCH_PROVIDER_BOUNDARY_OUTCOME: ${{ steps.web_search_provider_boundary.outcome }}
EXTENSION_SRC_OUTSIDE_PLUGIN_SDK_BOUNDARY_OUTCOME: ${{ steps.extension_src_outside_plugin_sdk_boundary.outcome }}
EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME: ${{ steps.extension_plugin_sdk_internal_boundary.outcome }}
NO_RAW_WINDOW_OPEN_OUTCOME: ${{ steps.no_raw_window_open.outcome }}
GATEWAY_WATCH_REGRESSION_OUTCOME: ${{ steps.gateway_watch_regression.outcome }}
run: |
failures=0
for result in \
"plugin-extension-boundary|$PLUGIN_EXTENSION_BOUNDARY_OUTCOME" \
"web-search-provider-boundary|$WEB_SEARCH_PROVIDER_BOUNDARY_OUTCOME" \
"extension-src-outside-plugin-sdk-boundary|$EXTENSION_SRC_OUTSIDE_PLUGIN_SDK_BOUNDARY_OUTCOME" \
"extension-plugin-sdk-internal-boundary|$EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME" \
"lint:ui:no-raw-window-open|$NO_RAW_WINDOW_OPEN_OUTCOME" \
"gateway-watch-regression|$GATEWAY_WATCH_REGRESSION_OUTCOME"; do
name="${result%%|*}"
outcome="${result#*|}"
if [ "$outcome" != "success" ]; then
echo "::error title=${name} failed::${name} outcome: ${outcome}"
failures=1
fi
done
exit "$failures"
build-smoke:
name: "build-smoke"
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
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 dist
run: pnpm build
- name: Smoke test CLI launcher help
run: node openclaw.mjs --help
- name: Smoke test CLI launcher status json
run: node openclaw.mjs status --json --timeout 1
- name: Smoke test built bundled plugin singleton
run: pnpm test:build:singleton
- name: Check CLI startup memory
run: pnpm test:startup:memory
# Validate docs (format, lint, broken links) only when docs files changed.
check-docs:
needs: [docs-scope]
@ -220,7 +434,7 @@ jobs:
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -228,23 +442,23 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "true"
use-sticky-disk: "false"
- name: Check docs
run: pnpm check:docs
skills-python:
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true' || needs.changed-scope.outputs.run_skills_python == 'true')
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_skills_python == 'true')
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
@ -263,10 +477,16 @@ jobs:
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
- name: Ensure secrets 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: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
@ -276,7 +496,7 @@ jobs:
- name: Setup Python
id: setup-python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: "pip"
@ -286,7 +506,7 @@ jobs:
.github/workflows/ci.yml
- name: Restore pre-commit cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
@ -296,53 +516,34 @@ jobs:
python -m pip install --upgrade pip
python -m pip install pre-commit
- name: Detect secrets
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "push" ]; then
echo "Running full detect-secrets scan on push."
pre-commit run --all-files detect-secrets
exit 0
fi
BASE="${{ github.event.pull_request.base.sha }}"
changed_files=()
if git rev-parse --verify "$BASE^{commit}" >/dev/null 2>&1; then
while IFS= read -r path; do
[ -n "$path" ] || continue
[ -f "$path" ] || continue
changed_files+=("$path")
done < <(git diff --name-only --diff-filter=ACMR "$BASE" HEAD)
fi
if [ "${#changed_files[@]}" -gt 0 ]; then
echo "Running detect-secrets on ${#changed_files[@]} changed file(s)."
pre-commit run detect-secrets --files "${changed_files[@]}"
else
echo "Falling back to full detect-secrets scan."
pre-commit run --all-files detect-secrets
fi
- name: Detect committed private keys
run: pre-commit run --all-files detect-private-key
- name: Audit changed GitHub workflows with zizmor
env:
BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "push" ]; then
BASE="${{ github.event.before }}"
else
BASE="${{ github.event.pull_request.base.sha }}"
if [ -z "${BASE_SHA:-}" ] || [ "${BASE_SHA}" = "0000000000000000000000000000000000000000" ]; then
echo "No usable base SHA detected; skipping zizmor."
exit 0
fi
mapfile -t workflow_files < <(git diff --name-only "$BASE" HEAD -- '.github/workflows/*.yml' '.github/workflows/*.yaml')
if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then
echo "Base SHA ${BASE_SHA} is unavailable; skipping zizmor."
exit 0
fi
mapfile -t workflow_files < <(
git diff --name-only "${BASE_SHA}" HEAD -- '.github/workflows/*.yml' '.github/workflows/*.yaml'
)
if [ "${#workflow_files[@]}" -eq 0 ]; then
echo "No workflow changes detected; skipping zizmor."
exit 0
fi
printf 'Auditing workflow files:\n%s\n' "${workflow_files[@]}"
pre-commit run zizmor --files "${workflow_files[@]}"
- name: Audit production dependencies
@ -350,7 +551,7 @@ jobs:
checks-windows:
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_windows == 'true')
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_windows == 'true'
runs-on: blacksmith-32vcpu-windows-2025
timeout-minutes: 45
env:
@ -397,7 +598,7 @@ jobs:
command: pnpm test
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -421,16 +622,16 @@ jobs:
}
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@v6
with:
node-version: 22.x
node-version: 24.x
check-latest: false
- name: Setup pnpm + cache store
uses: ./.github/actions/setup-pnpm-store-cache
with:
pnpm-version: "10.23.0"
cache-key-suffix: "node22"
cache-key-suffix: "node24"
# Sticky disk mount currently retries/fails on every shard and adds ~50s
# before install while still yielding zero pnpm store reuse.
# Try exact-key actions/cache restores instead to recover store reuse
@ -483,7 +684,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -519,7 +720,7 @@ jobs:
swiftformat --lint apps/macos/Sources --config .swiftformat
- name: Cache SwiftPM
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/Library/Caches/org.swift.swiftpm
key: ${{ runner.os }}-swiftpm-${{ hashFiles('apps/macos/Package.resolved') }}
@ -555,7 +756,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -712,43 +913,61 @@ jobs:
android:
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_android == 'true')
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_android == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
strategy:
fail-fast: false
matrix:
include:
- task: test
command: ./gradlew --no-daemon :app:testDebugUnitTest
- task: build
command: ./gradlew --no-daemon :app:assembleDebug
- task: test-play
command: ./gradlew --no-daemon :app:testPlayDebugUnitTest
- task: test-third-party
command: ./gradlew --no-daemon :app:testThirdPartyDebugUnitTest
- task: build-play
command: ./gradlew --no-daemon :app:assemblePlayDebug
- task: build-third-party
command: ./gradlew --no-daemon :app:assembleThirdPartyDebug
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
# setup-android's sdkmanager currently crashes on JDK 21 in CI.
# Keep sdkmanager on the stable JDK path for Linux CI runners.
java-version: 17
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
accept-android-sdk-licenses: false
- name: Setup Android SDK cmdline-tools
run: |
set -euo pipefail
ANDROID_SDK_ROOT="$HOME/.android-sdk"
CMDLINE_TOOLS_VERSION="12266719"
ARCHIVE="commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip"
URL="https://dl.google.com/android/repository/${ARCHIVE}"
mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools"
curl -fsSL "$URL" -o "/tmp/${ARCHIVE}"
rm -rf "$ANDROID_SDK_ROOT/cmdline-tools/latest"
unzip -q "/tmp/${ARCHIVE}" -d "$ANDROID_SDK_ROOT/cmdline-tools"
mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest"
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
echo "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" >> "$GITHUB_PATH"
echo "$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_PATH"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v5
with:
gradle-version: 8.11.1
- name: Install Android SDK packages
run: |
yes | sdkmanager --licenses >/dev/null
sdkmanager --install \
yes | sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --licenses >/dev/null
sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --install \
"platform-tools" \
"platforms;android-36" \
"build-tools;36.0.0"

View File

@ -7,6 +7,9 @@ concurrency:
group: codeql-${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
permissions:
actions: read
contents: read
@ -28,6 +31,7 @@ jobs:
needs_swift_tools: false
needs_manual_build: false
needs_autobuild: false
config_file: ./.github/codeql/codeql-javascript-typescript.yml
- language: actions
runs_on: blacksmith-16vcpu-ubuntu-2404
needs_node: false
@ -36,6 +40,7 @@ jobs:
needs_swift_tools: false
needs_manual_build: false
needs_autobuild: false
config_file: ""
- language: python
runs_on: blacksmith-16vcpu-ubuntu-2404
needs_node: false
@ -44,6 +49,7 @@ jobs:
needs_swift_tools: false
needs_manual_build: false
needs_autobuild: false
config_file: ""
- language: java-kotlin
runs_on: blacksmith-16vcpu-ubuntu-2404
needs_node: false
@ -52,6 +58,7 @@ jobs:
needs_swift_tools: false
needs_manual_build: true
needs_autobuild: false
config_file: ""
- language: swift
runs_on: macos-latest
needs_node: false
@ -60,9 +67,10 @@ jobs:
needs_swift_tools: true
needs_manual_build: true
needs_autobuild: false
config_file: ""
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -71,30 +79,35 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "true"
use-sticky-disk: "false"
- name: Setup Python
if: matrix.needs_python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Setup Java
if: matrix.needs_java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- name: Setup Swift build tools
if: matrix.needs_swift_tools
run: brew install xcodegen swiftlint swiftformat
run: |
sudo xcode-select -s /Applications/Xcode_26.1.app
xcodebuild -version
brew install xcodegen swiftlint swiftformat
swift --version
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
queries: security-and-quality
config-file: ${{ matrix.config_file || '' }}
- name: Autobuild
if: matrix.needs_autobuild
@ -103,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

@ -12,19 +12,65 @@ on:
- "**/*.mdx"
- ".agents/**"
- "skills/**"
workflow_dispatch:
inputs:
tag:
description: Existing release tag to backfill (for example v2026.3.13)
required: true
type: string
concurrency:
group: docker-release-${{ github.workflow }}-${{ github.ref }}
group: docker-release-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
validate_manual_backfill:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-24.04
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]*)?$ ]]; then
echo "Invalid release tag: ${RELEASE_TAG}"
exit 1
fi
- name: Checkout selected tag
uses: actions/checkout@v6
with:
ref: refs/tags/${{ inputs.tag }}
fetch-depth: 0
approve_manual_backfill:
if: github.event_name == 'workflow_dispatch'
needs: validate_manual_backfill
# WARNING: KEEP MANUAL BACKFILLS GATED BY THE docker-release ENVIRONMENT.
runs-on: ubuntu-24.04
environment: docker-release
steps:
- name: Approve Docker backfill
env:
RELEASE_TAG: ${{ inputs.tag }}
run: echo "Approved Docker backfill for $RELEASE_TAG"
# KEEP THIS WORKFLOW ON GITHUB-HOSTED RUNNERS.
# DO NOT MOVE IT BACK TO BLACKSMITH WITHOUT RE-VALIDATING TAG BUILDS AND BACKFILLS.
# Build amd64 images (default + slim share the build stage cache)
build-amd64:
runs-on: blacksmith-16vcpu-ubuntu-2404
needs: [approve_manual_backfill]
if: ${{ always() && (github.event_name != 'workflow_dispatch' || needs.approve_manual_backfill.result == 'success') }}
# WARNING: DO NOT REVERT THIS TO A BLACKSMITH RUNNER WITHOUT RE-VALIDATING TAG BACKFILLS.
runs-on: ubuntu-24.04
permissions:
packages: write
contents: read
@ -33,13 +79,16 @@ jobs:
slim-digest: ${{ steps.build-slim.outputs.digest }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
fetch-depth: 0
- name: Set up Docker Builder
uses: useblacksmith/setup-docker-builder@v1
uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
@ -50,21 +99,22 @@ jobs:
shell: bash
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
run: |
set -euo pipefail
tags=()
slim_tags=()
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
tags+=("${IMAGE}:main-amd64")
slim_tags+=("${IMAGE}:main-slim-amd64")
fi
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
version="${GITHUB_REF#refs/tags/v}"
if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then
version="${SOURCE_REF#refs/tags/v}"
tags+=("${IMAGE}:${version}-amd64")
slim_tags+=("${IMAGE}:${version}-slim-amd64")
fi
if [[ ${#tags[@]} -eq 0 ]]; then
echo "::error::No amd64 tags resolved for ref ${GITHUB_REF}"
echo "::error::No amd64 tags resolved for ref ${SOURCE_REF}"
exit 1
fi
{
@ -81,19 +131,22 @@ jobs:
- name: Resolve OCI labels (amd64)
id: labels
shell: bash
env:
SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
run: |
set -euo pipefail
version="${GITHUB_SHA}"
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
source_sha="$(git rev-parse HEAD)"
version="${source_sha}"
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
version="main"
fi
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
version="${GITHUB_REF#refs/tags/v}"
if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then
version="${SOURCE_REF#refs/tags/v}"
fi
created="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
{
echo "value<<EOF"
echo "org.opencontainers.image.revision=${GITHUB_SHA}"
echo "org.opencontainers.image.revision=${source_sha}"
echo "org.opencontainers.image.version=${version}"
echo "org.opencontainers.image.created=${created}"
echo "EOF"
@ -101,7 +154,8 @@ jobs:
- name: Build and push amd64 image
id: build
uses: useblacksmith/build-push-action@v2
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
@ -112,7 +166,8 @@ jobs:
- name: Build and push amd64 slim image
id: build-slim
uses: useblacksmith/build-push-action@v2
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
@ -125,7 +180,10 @@ jobs:
# Build arm64 images (default + slim share the build stage cache)
build-arm64:
runs-on: blacksmith-16vcpu-ubuntu-2404-arm
needs: [approve_manual_backfill]
if: ${{ always() && (github.event_name != 'workflow_dispatch' || needs.approve_manual_backfill.result == 'success') }}
# WARNING: DO NOT REVERT THIS TO A BLACKSMITH RUNNER WITHOUT RE-VALIDATING TAG BACKFILLS.
runs-on: ubuntu-24.04-arm
permissions:
packages: write
contents: read
@ -134,13 +192,16 @@ jobs:
slim-digest: ${{ steps.build-slim.outputs.digest }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
fetch-depth: 0
- name: Set up Docker Builder
uses: useblacksmith/setup-docker-builder@v1
uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
@ -151,21 +212,22 @@ jobs:
shell: bash
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
run: |
set -euo pipefail
tags=()
slim_tags=()
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
tags+=("${IMAGE}:main-arm64")
slim_tags+=("${IMAGE}:main-slim-arm64")
fi
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
version="${GITHUB_REF#refs/tags/v}"
if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then
version="${SOURCE_REF#refs/tags/v}"
tags+=("${IMAGE}:${version}-arm64")
slim_tags+=("${IMAGE}:${version}-slim-arm64")
fi
if [[ ${#tags[@]} -eq 0 ]]; then
echo "::error::No arm64 tags resolved for ref ${GITHUB_REF}"
echo "::error::No arm64 tags resolved for ref ${SOURCE_REF}"
exit 1
fi
{
@ -182,19 +244,22 @@ jobs:
- name: Resolve OCI labels (arm64)
id: labels
shell: bash
env:
SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
run: |
set -euo pipefail
version="${GITHUB_SHA}"
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
source_sha="$(git rev-parse HEAD)"
version="${source_sha}"
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
version="main"
fi
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
version="${GITHUB_REF#refs/tags/v}"
if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then
version="${SOURCE_REF#refs/tags/v}"
fi
created="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
{
echo "value<<EOF"
echo "org.opencontainers.image.revision=${GITHUB_SHA}"
echo "org.opencontainers.image.revision=${source_sha}"
echo "org.opencontainers.image.version=${version}"
echo "org.opencontainers.image.created=${created}"
echo "EOF"
@ -202,7 +267,8 @@ jobs:
- name: Build and push arm64 image
id: build
uses: useblacksmith/build-push-action@v2
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
@ -213,7 +279,8 @@ jobs:
- name: Build and push arm64 slim image
id: build-slim
uses: useblacksmith/build-push-action@v2
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
@ -226,17 +293,22 @@ jobs:
# Create multi-platform manifests
create-manifest:
runs-on: blacksmith-16vcpu-ubuntu-2404
needs: [approve_manual_backfill, build-amd64, build-arm64]
if: ${{ always() && needs.build-amd64.result == 'success' && needs.build-arm64.result == 'success' && (github.event_name != 'workflow_dispatch' || needs.approve_manual_backfill.result == 'success') }}
# WARNING: DO NOT REVERT THIS TO A BLACKSMITH RUNNER WITHOUT RE-VALIDATING TAG BACKFILLS.
runs-on: ubuntu-24.04
permissions:
packages: write
contents: read
needs: [build-amd64, build-arm64]
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
fetch-depth: 0
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
@ -247,25 +319,28 @@ jobs:
shell: bash
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
IS_MANUAL_BACKFILL: ${{ github.event_name == 'workflow_dispatch' && '1' || '0' }}
run: |
set -euo pipefail
tags=()
slim_tags=()
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
tags+=("${IMAGE}:main")
slim_tags+=("${IMAGE}:main-slim")
fi
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
version="${GITHUB_REF#refs/tags/v}"
if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then
version="${SOURCE_REF#refs/tags/v}"
tags+=("${IMAGE}:${version}")
slim_tags+=("${IMAGE}:${version}-slim")
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]]; then
# Manual backfills should only republish the requested version tags.
if [[ "${IS_MANUAL_BACKFILL}" != "1" && "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]]; then
tags+=("${IMAGE}:latest")
slim_tags+=("${IMAGE}:slim")
fi
fi
if [[ ${#tags[@]} -eq 0 ]]; then
echo "::error::No manifest tags resolved for ref ${GITHUB_REF}"
echo "::error::No manifest tags resolved for ref ${SOURCE_REF}"
exit 1
fi
{

View File

@ -10,6 +10,9 @@ concurrency:
group: install-smoke-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
docs-scope:
runs-on: blacksmith-16vcpu-ubuntu-2404
@ -17,7 +20,7 @@ jobs:
docs_only: ${{ steps.check.outputs.docs_only }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 1
fetch-tags: false
@ -38,11 +41,13 @@ jobs:
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout CLI
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Docker Builder
uses: useblacksmith/setup-docker-builder@v1
uses: docker/setup-buildx-action@v4
# Blacksmith can fall back to the local docker driver, which rejects gha
# cache export/import. Keep smoke builds driver-agnostic.
- name: Build root Dockerfile smoke image
uses: useblacksmith/build-push-action@v2
with:
@ -52,33 +57,70 @@ jobs:
load: true
push: false
provenance: false
cache-from: type=gha,scope=install-smoke-root-dockerfile
cache-to: type=gha,mode=max,scope=install-smoke-root-dockerfile
- name: Run root Dockerfile CLI smoke
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_EXTENSIONS=matrix
tags: openclaw-ext-smoke:local
load: true
push: false
provenance: false
cache-from: type=gha,scope=install-smoke-root-dockerfile-ext
cache-to: type=gha,mode=max,scope=install-smoke-root-dockerfile-ext
- 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
@ -89,8 +131,6 @@ jobs:
load: true
push: false
provenance: false
cache-from: type=gha,scope=install-smoke-installer-root
cache-to: type=gha,mode=max,scope=install-smoke-installer-root
- name: Build installer non-root image
if: github.event_name != 'pull_request'
@ -102,8 +142,6 @@ jobs:
load: true
push: false
provenance: false
cache-from: type=gha,scope=install-smoke-installer-nonroot
cache-to: type=gha,mode=max,scope=install-smoke-installer-nonroot
- name: Run installer docker tests
env:

View File

@ -1,7 +1,7 @@
name: Labeler
on:
pull_request_target:
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned triage workflow; no untrusted checkout or PR code execution
types: [opened, synchronize, reopened]
issues:
types: [opened]
@ -16,6 +16,9 @@ on:
required: false
default: "50"
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
permissions: {}
jobs:
@ -25,25 +28,25 @@ jobs:
pull-requests: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5
- uses: actions/labeler@v6
with:
configuration-path: .github/labeler.yml
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
sync-labels: true
- name: Apply PR size label
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@ -132,7 +135,7 @@ jobs:
labels: [targetSizeLabel],
});
- name: Apply maintainer or trusted-contributor label
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@ -203,7 +206,7 @@ jobs:
// });
// }
- name: Apply too-many-prs label
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@ -213,6 +216,7 @@ jobs:
}
const activePrLimitLabel = "r: too-many-prs";
const activePrLimitOverrideLabel = "r: too-many-prs-override";
const activePrLimit = 10;
const labelColor = "B60205";
const labelDescription = `Author has more than ${activePrLimit} active PRs in this repo`;
@ -221,12 +225,37 @@ jobs:
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 labelNames = new Set(
(pullRequest.labels ?? [])
currentLabels
.map((label) => (typeof label === "string" ? label : label?.name))
.filter((name) => typeof name === "string"),
);
if (labelNames.has(activePrLimitOverrideLabel)) {
if (labelNames.has(activePrLimitLabel)) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
name: activePrLimitLabel,
});
} catch (error) {
if (error?.status !== 404) {
throw error;
}
}
}
return;
}
const ensureLabelExists = async () => {
try {
await github.rest.issues.getLabel({
@ -355,20 +384,20 @@ jobs:
pull-requests: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Backfill PR labels
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
@ -603,20 +632,20 @@ jobs:
issues: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Apply maintainer or trusted-contributor label
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |

View File

@ -0,0 +1,195 @@
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)
required: true
type: string
concurrency:
group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.x"
PNPM_VERSION: "10.23.0"
jobs:
preview_openclaw_npm:
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
with:
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: 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_MAIN_REF: origin/main
run: |
set -euxo 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
# for older tagged commits that are still contained in main.
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
pnpm release:openclaw:npm:check
- name: Ensure version is not already published
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euxo 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."
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
- name: Check
run: |
set -euxo pipefail
pnpm check
- name: Build
run: |
set -euxo pipefail
pnpm build
- name: Verify release contents
run: |
set -euxo pipefail
pnpm release:check
- name: Preview publish command
run: bash scripts/openclaw-npm-publish.sh --dry-run
publish_openclaw_npm:
if: github.event_name == 'workflow_dispatch'
# npm trusted publishing + provenance requires a GitHub-hosted runner.
runs-on: ubuntu-latest
environment: npm-release
permissions:
contents: read
id-token: write
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
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: 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
# Fetch the full main ref so merge-base ancestry checks keep working
# for older tagged commits that are still contained in main.
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
pnpm release:openclaw:npm:check
- name: Ensure version is not already published
run: |
set -euo pipefail
PACKAGE_VERSION=$(node -p "require('./package.json').version")
if npm view "openclaw@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
echo "openclaw@${PACKAGE_VERSION} is already published on npm."
exit 1
fi
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

@ -17,17 +17,20 @@ concurrency:
group: sandbox-common-smoke-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
sandbox-common-smoke:
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
- name: Set up Docker Builder
uses: useblacksmith/setup-docker-builder@v1
uses: docker/setup-buildx-action@v4
- name: Build minimal sandbox base (USER sandbox)
shell: bash

View File

@ -5,6 +5,9 @@ on:
- cron: "17 3 * * *"
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
permissions: {}
jobs:
@ -14,13 +17,13 @@ jobs:
pull-requests: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token
continue-on-error: true
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token-fallback
continue-on-error: true
with:
@ -29,7 +32,7 @@ jobs:
- name: Mark stale issues and pull requests (primary)
id: stale-primary
continue-on-error: true
uses: actions/stale@v9
uses: actions/stale@v10
with:
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
days-before-issue-stale: 7
@ -62,7 +65,7 @@ jobs:
- name: Check stale state cache
id: stale-state
if: always()
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token-fallback.outputs.token || steps.app-token.outputs.token }}
script: |
@ -85,7 +88,7 @@ jobs:
}
- name: Mark stale issues and pull requests (fallback)
if: (steps.stale-primary.outcome == 'failure' || steps.stale-state.outputs.has_state == 'true') && steps.app-token-fallback.outputs.token != ''
uses: actions/stale@v9
uses: actions/stale@v10
with:
repo-token: ${{ steps.app-token-fallback.outputs.token }}
days-before-issue-stale: 7
@ -121,13 +124,13 @@ jobs:
issues: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: "2729701"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Lock closed issues after 48h of no comments
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |

View File

@ -4,17 +4,22 @@ on:
pull_request:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: workflow-sanity-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
no-tabs:
if: github.event_name != 'workflow_dispatch'
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Fail on tabs in workflow files
run: |
@ -42,10 +47,11 @@ jobs:
PY
actionlint:
if: github.event_name != 'workflow_dispatch'
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install actionlint
shell: bash
@ -65,3 +71,19 @@ jobs:
- name: Disallow direct inputs interpolation in composite run blocks
run: python3 scripts/check-composite-action-input-interpolation.py
config-docs-drift:
if: github.event_name == 'workflow_dispatch'
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Check config docs drift statefile
run: pnpm config:docs:check

20
.gitignore vendored
View File

@ -1,12 +1,15 @@
node_modules
**/node_modules/
.env
docker-compose.override.yml
docker-compose.extra.yml
dist
dist-runtime
pnpm-lock.yaml
bun.lock
bun.lockb
coverage
__openclaw_vitest__/
__pycache__/
*.pyc
.tsbuildinfo
@ -28,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
@ -81,6 +85,7 @@ apps/ios/*.mobileprovision
# Local untracked files
.local/
docs/.local/
tmp/
IDENTITY.md
USER.md
.tgz
@ -96,8 +101,6 @@ USER.md
/local/
package-lock.json
.claude/
.agents/
.agents
.agent/
skills-lock.json
@ -121,3 +124,16 @@ dist/protocol.schema.json
# Synthing
**/.stfolder/
.dev-state
docs/superpowers/plans/2026-03-10-collapsed-side-nav.md
docs/superpowers/specs/2026-03-10-collapsed-side-nav-design.md
.gitignore
test/config-form.analyze.telegram.test.ts
ui/src/ui/theme-variants.browser.test.ts
ui/src/ui/__screenshots__
ui/src/ui/views/__screenshots__
ui/.vitest-attachments
docs/superpowers
# Deprecated changelog fragment workflow
changelog/fragments/

16
.jscpd.json Normal file
View File

@ -0,0 +1,16 @@
{
"gitignore": true,
"noSymlinks": true,
"ignore": [
"**/node_modules/**",
"**/dist/**",
"dist/**",
"**/.git/**",
"**/coverage/**",
"**/build/**",
"**/.build/**",
"**/.artifacts/**",
"docs/zh-CN/**",
"**/CHANGELOG.md"
]
}

2
.npmignore Normal file
View File

@ -0,0 +1,2 @@
**/node_modules/
docs/.generated/

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

View File

@ -9,7 +9,19 @@ Input
- If ambiguous: ask.
Do (review-only)
Goal: produce a thorough review and a clear recommendation (READY for /landpr vs NEEDS WORK). Do NOT merge, do NOT push, do NOT make changes in the repo as part of this command.
Goal: produce a thorough review and a clear recommendation (READY FOR /landpr vs NEEDS WORK vs INVALID CLAIM). Do NOT merge, do NOT push, do NOT make changes in the repo as part of this command.
0. Truthfulness + reality gate (required for bug-fix claims)
- Do not trust the issue text or PR summary by default; verify in code and evidence.
- If the PR claims to fix a bug linked to an issue, confirm the bug exists now (repro steps, logs, failing test, or clear code-path proof).
- Prove root cause with exact location (`path/file.ts:line` + explanation of why behavior is wrong).
- Verify fix targets the same code path as the root cause.
- Require a regression test when feasible (fails before fix, passes after fix). If not feasible, require explicit justification + manual verification evidence.
- Hallucination/BS red flags (treat as BLOCKER until disproven):
- claimed behavior not present in repo,
- issue/PR says "fixes #..." but changed files do not touch implicated path,
- only docs/comments changed for a runtime bug claim,
- vague AI-generated rationale without concrete evidence.
1. Identify PR meta + context
@ -56,6 +68,7 @@ Goal: produce a thorough review and a clear recommendation (READY for /landpr vs
- Any deprecations, docs, types, or lint rules we should adjust?
8. Key questions to answer explicitly
- Is the core claim substantiated by evidence, or is it likely invalid/hallucinated?
- Can we fix everything ourselves in a follow-up, or does the contributor need to update this PR?
- Any blocking concerns (must-fix before merge)?
- Is this PR ready to land, or does it need work?
@ -65,18 +78,32 @@ Goal: produce a thorough review and a clear recommendation (READY for /landpr vs
A) TL;DR recommendation
- One of: READY FOR /landpr | NEEDS WORK | NEEDS DISCUSSION
- One of: READY FOR /landpr | NEEDS WORK | INVALID CLAIM (issue/bug not substantiated) | NEEDS DISCUSSION
- 13 sentence rationale.
B) What changed
B) Claim verification matrix (required)
- Fill this table:
| Field | Evidence |
| ----------------------------------------------- | -------- |
| Claimed problem | ... |
| Evidence observed (repro/log/test/code) | ... |
| Root cause location (`path:line`) | ... |
| Why this fix addresses that root cause | ... |
| Regression coverage (test name or manual proof) | ... |
- If any row is missing/weak, default to `NEEDS WORK` or `INVALID CLAIM`.
C) What changed
- Brief bullet summary of the diff/behavioral changes.
C) What's good
D) What's good
- Bullets: correctness, simplicity, tests, docs, ergonomics, etc.
D) Concerns / questions (actionable)
E) Concerns / questions (actionable)
- Numbered list.
- Mark each item as:
@ -84,17 +111,19 @@ D) Concerns / questions (actionable)
- IMPORTANT (should fix before merge)
- NIT (optional)
- For each: point to the file/area and propose a concrete fix or alternative.
- If evidence for the core bug claim is missing, add a `BLOCKER` explicitly.
E) Tests
F) Tests
- What exists.
- What's missing (specific scenarios).
- State clearly whether there is a regression test for the claimed bug.
F) Follow-ups (optional)
G) Follow-ups (optional)
- Non-blocking refactors/tickets to open later.
G) Suggested PR comment (optional)
H) Suggested PR comment (optional)
- Offer: "Want me to draft a PR comment to the author?"
- If yes, provide a ready-to-paste comment summarizing the above, with clear asks.

View File

@ -66,9 +66,13 @@ repos:
- --exclude-lines
- 'env: \{ MISTRAL_API_K[E]Y: "sk-\.\.\." \},'
- --exclude-lines
- '"ap[i]Key": "xxxxx",'
- '"ap[i]Key": "xxxxx"(,)?'
- --exclude-lines
- 'ap[i]Key: "A[I]za\.\.\.",'
- --exclude-lines
- '"ap[i]Key": "(resolved|normalized|legacy)-key"(,)?'
- --exclude-lines
- 'sparkle:edSignature="[A-Za-z0-9+/=]+"'
# Shell script linting
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.11.0

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
docs/.generated/

View File

@ -151,8 +151,10 @@
"export CUSTOM_API_K[E]Y=\"your-key\"",
"grep -q 'N[O]DE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bashrc \\|\\| cat >> ~/.bashrc <<'EOF'",
"env: \\{ MISTRAL_API_K[E]Y: \"sk-\\.\\.\\.\" \\},",
"\"ap[i]Key\": \"xxxxx\",",
"ap[i]Key: \"A[I]za\\.\\.\\.\","
"\"ap[i]Key\": \"xxxxx\"(,)?",
"ap[i]Key: \"A[I]za\\.\\.\\.\",",
"\"ap[i]Key\": \"(resolved|normalized|legacy)-key\"(,)?",
"sparkle:edSignature=\"[A-Za-z0-9+/=]+\""
]
},
{
@ -179,29 +181,6 @@
"line_number": 15
}
],
"appcast.xml": [
{
"type": "Base64 High Entropy String",
"filename": "appcast.xml",
"hashed_secret": "7afea670e53d801f1f881c99c40aa177e3395bfa",
"is_verified": false,
"line_number": 365
},
{
"type": "Base64 High Entropy String",
"filename": "appcast.xml",
"hashed_secret": "6e1ba26139ac4e73427e68a7eec2abf96bcf1fd4",
"is_verified": false,
"line_number": 584
},
{
"type": "Base64 High Entropy String",
"filename": "appcast.xml",
"hashed_secret": "c0baa9660a8d3b11874c63a535d8369f4a8fa8fa",
"is_verified": false,
"line_number": 723
}
],
"apps/android/app/src/test/java/ai/openclaw/android/node/AppUpdateHandlerTest.kt": [
{
"type": "Hex High Entropy String",
@ -226,7 +205,7 @@
"filename": "apps/macos/Sources/OpenClawProtocol/GatewayModels.swift",
"hashed_secret": "7990585255d25249fb1e6eac3d2bd6c37429b2cd",
"is_verified": false,
"line_number": 1749
"line_number": 1859
}
],
"apps/macos/Tests/OpenClawIPCTests/AnthropicAuthResolverTests.swift": [
@ -251,7 +230,7 @@
"filename": "apps/macos/Tests/OpenClawIPCTests/GatewayEndpointStoreTests.swift",
"hashed_secret": "19dad5cecb110281417d1db56b60e1b006d55bb4",
"is_verified": false,
"line_number": 66
"line_number": 81
}
],
"apps/macos/Tests/OpenClawIPCTests/GatewayLaunchAgentManagerTests.swift": [
@ -287,7 +266,7 @@
"filename": "apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift",
"hashed_secret": "7990585255d25249fb1e6eac3d2bd6c37429b2cd",
"is_verified": false,
"line_number": 1749
"line_number": 1859
}
],
"docs/.i18n/zh-CN.tm.jsonl": [
@ -9619,14 +9598,14 @@
"filename": "docs/channels/feishu.md",
"hashed_secret": "b60d121b438a380c343d5ec3c2037564b82ffef3",
"is_verified": false,
"line_number": 189
"line_number": 187
},
{
"type": "Secret Keyword",
"filename": "docs/channels/feishu.md",
"hashed_secret": "186154712b2d5f6791d85b9a0987b98fa231779c",
"is_verified": false,
"line_number": 501
"line_number": 499
}
],
"docs/channels/irc.md": [
@ -9795,63 +9774,63 @@
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "1188d5a8ed7edcff5144a9472af960243eacf12e",
"is_verified": false,
"line_number": 1612
"line_number": 1614
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "bde4db9b4c3be4049adc3b9a69851d7c35119770",
"is_verified": false,
"line_number": 1628
"line_number": 1630
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "7f8aaf142ce0552c260f2e546dda43ddd7c9aef3",
"is_verified": false,
"line_number": 1813
"line_number": 1817
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "22af290a1a3d5e941193a41a3d3a9e4ca8da5e27",
"is_verified": false,
"line_number": 1986
"line_number": 1990
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "ec3810e10fb78db55ce38b9c18d1c3eb1db739e0",
"is_verified": false,
"line_number": 2042
"line_number": 2046
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "c1e6ee547fd492df1441ac492e8bb294974712bd",
"is_verified": false,
"line_number": 2274
"line_number": 2278
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "45d676e7c6ab44cf4b8fa366ef2d8fccd3e6d6e6",
"is_verified": false,
"line_number": 2402
"line_number": 2408
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "a219d7693c25cd2d93313512e200ff3eb374d281",
"is_verified": false,
"line_number": 2655
"line_number": 2661
},
{
"type": "Secret Keyword",
"filename": "docs/gateway/configuration-reference.md",
"hashed_secret": "b6f56e5e92078ed7c078c46fbfeedcbe5719bc25",
"is_verified": false,
"line_number": 2657
"line_number": 2663
}
],
"docs/gateway/configuration.md": [
@ -9972,7 +9951,7 @@
"filename": "docs/perplexity.md",
"hashed_secret": "6b26c117c66a0c030e239eef595c1e18865132a8",
"is_verified": false,
"line_number": 29
"line_number": 43
}
],
"docs/plugins/voice-call.md": [
@ -10198,21 +10177,21 @@
"filename": "docs/tools/web.md",
"hashed_secret": "6b26c117c66a0c030e239eef595c1e18865132a8",
"is_verified": false,
"line_number": 90
"line_number": 135
},
{
"type": "Secret Keyword",
"filename": "docs/tools/web.md",
"hashed_secret": "491d458f895b9213facb2ee9375b1b044eaea3ac",
"is_verified": false,
"line_number": 179
"line_number": 228
},
{
"type": "Secret Keyword",
"filename": "docs/tools/web.md",
"hashed_secret": "674397e2c0c2faaa85961c708d2a96a7cc7af217",
"is_verified": false,
"line_number": 277
"line_number": 332
}
],
"docs/tts.md": [
@ -10255,14 +10234,14 @@
"filename": "docs/zh-CN/channels/feishu.md",
"hashed_secret": "b60d121b438a380c343d5ec3c2037564b82ffef3",
"is_verified": false,
"line_number": 195
"line_number": 191
},
{
"type": "Secret Keyword",
"filename": "docs/zh-CN/channels/feishu.md",
"hashed_secret": "186154712b2d5f6791d85b9a0987b98fa231779c",
"is_verified": false,
"line_number": 509
"line_number": 505
}
],
"docs/zh-CN/channels/line.md": [
@ -11481,7 +11460,7 @@
"filename": "src/agents/models-config.e2e-harness.ts",
"hashed_secret": "7cf31e8b6cda49f70c31f1f25af05d46f924142d",
"is_verified": false,
"line_number": 130
"line_number": 157
}
],
"src/agents/models-config.fills-missing-provider-apikey-from-env-var.e2e.test.ts": [
@ -11515,14 +11494,14 @@
"filename": "src/agents/models-config.providers.nvidia.test.ts",
"hashed_secret": "3acfb2c2b433c0ea7ff107e33df91b18e52f960f",
"is_verified": false,
"line_number": 13
"line_number": 14
},
{
"type": "Secret Keyword",
"filename": "src/agents/models-config.providers.nvidia.test.ts",
"hashed_secret": "be1a7be9d4d5af417882b267f4db6dddc08507bd",
"is_verified": false,
"line_number": 22
"line_number": 23
}
],
"src/agents/models-config.providers.ollama.e2e.test.ts": [
@ -11583,7 +11562,7 @@
"filename": "src/agents/pi-embedded-runner/model.ts",
"hashed_secret": "e774aaeac31c6272107ba89080295e277050fa7c",
"is_verified": false,
"line_number": 267
"line_number": 279
}
],
"src/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.ts": [
@ -11680,7 +11659,7 @@
"filename": "src/agents/tools/web-search.ts",
"hashed_secret": "dfba7aade0868074c2861c98e2a9a92f3178a51b",
"is_verified": false,
"line_number": 254
"line_number": 291
}
],
"src/agents/tools/web-tools.enabled-defaults.e2e.test.ts": [
@ -11746,7 +11725,7 @@
"filename": "src/auto-reply/status.test.ts",
"hashed_secret": "3acfb2c2b433c0ea7ff107e33df91b18e52f960f",
"is_verified": false,
"line_number": 36
"line_number": 37
}
],
"src/browser/bridge-server.auth.test.ts": [
@ -11764,14 +11743,14 @@
"filename": "src/browser/browser-utils.test.ts",
"hashed_secret": "4e126c049580d66ca1549fa534d95a7263f27f46",
"is_verified": false,
"line_number": 43
"line_number": 47
},
{
"type": "Basic Auth Credentials",
"filename": "src/browser/browser-utils.test.ts",
"hashed_secret": "9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684",
"is_verified": false,
"line_number": 164
"line_number": 171
}
],
"src/browser/cdp.test.ts": [
@ -11780,7 +11759,7 @@
"filename": "src/browser/cdp.test.ts",
"hashed_secret": "9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684",
"is_verified": false,
"line_number": 243
"line_number": 318
}
],
"src/channels/plugins/plugins-channel.test.ts": [
@ -12100,21 +12079,21 @@
"filename": "src/config/config.env-vars.test.ts",
"hashed_secret": "a24ef9c1a27cac44823571ceef2e8262718eee36",
"is_verified": false,
"line_number": 13
"line_number": 17
},
{
"type": "Secret Keyword",
"filename": "src/config/config.env-vars.test.ts",
"hashed_secret": "29d5f92e9ee44d4854d6dfaeefc3dc27d779fdf3",
"is_verified": false,
"line_number": 19
"line_number": 23
},
{
"type": "Secret Keyword",
"filename": "src/config/config.env-vars.test.ts",
"hashed_secret": "1672b6a1e7956c6a70f45d699aa42a351b1f8b80",
"is_verified": false,
"line_number": 27
"line_number": 31
}
],
"src/config/config.irc.test.ts": [
@ -12335,14 +12314,14 @@
"filename": "src/config/schema.help.ts",
"hashed_secret": "9f4cda226d3868676ac7f86f59e4190eb94bd208",
"is_verified": false,
"line_number": 649
"line_number": 657
},
{
"type": "Secret Keyword",
"filename": "src/config/schema.help.ts",
"hashed_secret": "01822c8bbf6a8b136944b14182cb885100ec2eae",
"is_verified": false,
"line_number": 680
"line_number": 690
}
],
"src/config/schema.irc.ts": [
@ -12381,14 +12360,14 @@
"filename": "src/config/schema.labels.ts",
"hashed_secret": "e73c9fcad85cd4eecc74181ec4bdb31064d68439",
"is_verified": false,
"line_number": 216
"line_number": 219
},
{
"type": "Secret Keyword",
"filename": "src/config/schema.labels.ts",
"hashed_secret": "2eda7cd978f39eebec3bf03e4410a40e14167fff",
"is_verified": false,
"line_number": 324
"line_number": 328
}
],
"src/config/slack-http-config.test.ts": [
@ -12932,14 +12911,14 @@
"filename": "src/telegram/monitor.test.ts",
"hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4",
"is_verified": false,
"line_number": 450
"line_number": 497
},
{
"type": "Secret Keyword",
"filename": "src/telegram/monitor.test.ts",
"hashed_secret": "5934c4d4a4fa5d66ddb3d3fc0bba84996c17a5b7",
"is_verified": false,
"line_number": 641
"line_number": 688
}
],
"src/telegram/webhook.test.ts": [
@ -13012,7 +12991,7 @@
"filename": "ui/src/i18n/locales/en.ts",
"hashed_secret": "de0ff6b974d6910aca8d6b830e1b761f076d8fe6",
"is_verified": false,
"line_number": 61
"line_number": 74
}
],
"ui/src/i18n/locales/pt-BR.ts": [
@ -13021,7 +13000,7 @@
"filename": "ui/src/i18n/locales/pt-BR.ts",
"hashed_secret": "ef7b6f95faca2d7d3a5aa5a6434c89530c6dd243",
"is_verified": false,
"line_number": 61
"line_number": 73
}
],
"vendor/a2ui/README.md": [
@ -13034,5 +13013,5 @@
}
]
},
"generated_at": "2026-03-08T05:05:36Z"
"generated_at": "2026-03-10T03:11:06Z"
}

View File

@ -48,4 +48,4 @@
--allman false
# Exclusions
--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,Swabble,apps/android,apps/ios,apps/shared,apps/macos/Sources/MoltbotProtocol
--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,Swabble,apps/android,apps/ios,apps/shared,apps/macos/Sources/OpenClawProtocol,apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift

View File

@ -18,7 +18,9 @@ excluded:
- coverage
- "*.playground"
# Generated (protocol-gen-swift.ts)
- apps/macos/Sources/MoltbotProtocol/GatewayModels.swift
- apps/macos/Sources/OpenClawProtocol/GatewayModels.swift
# Generated (generate-host-env-security-policy-swift.mjs)
- apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
analyzer_rules:
- unused_declaration

165
AGENTS.md
View File

@ -2,20 +2,17 @@
- 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).
- 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.
## 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.
- 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/`
@ -28,6 +25,7 @@
- Docs are hosted on Mintlify (docs.openclaw.ai).
- Internal doc links in `docs/**/*.md`: root-relative, no `.md`/`.mdx` (example: `[Config](/configuration)`).
- When working with documentation, read the mintlify skill.
- 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).
@ -39,6 +37,8 @@
- `docs/zh-CN/**` is generated; do not edit unless the user explicitly asks.
- Pipeline: update English docs → adjust glossary (`docs/.i18n/glossary.zh-CN.json`) → run `scripts/docs-i18n` → apply targeted fixes only if instructed.
- Before rerunning `scripts/docs-i18n`, add glossary entries for any new technical terms, page titles, or short nav labels that must stay in English or use a fixed translation (for example `Doctor` or `Polls`).
- `pnpm docs:check-i18n-glossary` enforces glossary coverage for changed English doc titles and short internal doc labels before translation reruns.
- Translation memory: `docs/.i18n/zh-CN.tm.jsonl` (generated).
- See `docs/.i18n/README.md`.
- The pipeline can be slow/inefficient; if its dragging, ping @jospalmbier on Discord instead of hacking around it.
@ -64,21 +64,31 @@
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
- Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`.
- Node remains supported for running built output (`dist/*`) and production installs.
- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. Release checklist: `docs/platforms/mac/release.md`.
- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch.
- Type-check/build: `pnpm build`
- TypeScript checks: `pnpm tsgo`
- Lint/format: `pnpm check`
- Format check: `pnpm format` (oxfmt --check)
- Format fix: `pnpm format:fix` (oxfmt --write)
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
- 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.
@ -86,23 +96,26 @@
- Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`.
- Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability.
- Naming: use **OpenClaw** for product/app/docs headings; use `openclaw` for CLI command, package/binary, paths, and config keys.
- Written English: use American spelling and grammar in code, comments, docs, and UI strings (e.g. "color" not "colour", "behavior" not "behaviour", "analyze" not "analyse").
## 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`.
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
- 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.
- Do not switch CI `pnpm test` lanes back to Vitest `vmForks` by default without fresh green evidence on current `main`; keep CI on `forks` unless explicitly re-validated.
- 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`.
- 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.
@ -111,70 +124,41 @@
## 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.
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
- Group related changes; avoid bundling unrelated refactors.
- PR submission template (canonical): `.github/pull_request_template.md`
- Issue submission templates (canonical): `.github/ISSUE_TEMPLATE/`
## 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: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them.
- 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".
- 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.**
@ -182,14 +166,27 @@
- If shared guardrails are available locally, review them; otherwise follow this repo's guidance.
- SwiftUI state management (iOS/macOS): prefer the `Observation` framework (`@Observable`, `@Bindable`) over `ObservableObject`/`@StateObject`; dont introduce new `ObservableObject` unless required for compatibility, and migrate existing usages when touching related code.
- Connection providers: when adding a new connection, update every UI surface and docs (macOS app, web UI, mobile if applicable, onboarding/overview docs) and add matching status + configuration forms so provider lists and settings stay in sync.
- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/Sources/Info.plist` + `apps/ios/Tests/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `apps/macos/Sources/OpenClaw/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `docs/install/updating.md` (pinned npm version), `docs/platforms/mac/release.md` (APP_VERSION/APP_BUILD examples), Peekaboo Xcode projects/Info.plists (MARKETING_VERSION/CURRENT_PROJECT_VERSION).
- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/Sources/Info.plist` + `apps/ios/Tests/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `apps/macos/Sources/OpenClaw/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `docs/install/updating.md` (pinned npm version), and Peekaboo Xcode projects/Info.plists (MARKETING_VERSION/CURRENT_PROJECT_VERSION).
- "Bump version everywhere" means all version locations above **except** `appcast.xml` (only touch appcast when cutting a new macOS Sparkle release).
- **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch.
- **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators.
- 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 keys are managed outside the repo; follow internal release docs.
- Notary auth env vars (`APP_STORE_CONNECT_ISSUER_ID`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_API_KEY_P8`) are expected in your environment (per internal release docs).
- 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.
@ -200,64 +197,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.
## NPM + 1Password (publish/verify)
- Use the 1password skill; all `op` commands must run inside a fresh tmux session.
- Correct 1Password path for npm release auth: `op://Private/Npmjs` (use that item; OTP stays `op://Private/Npmjs/one-time password?attribute=otp`).
- Sign in: `eval "$(op signin --account my.1password.com)"` (app unlocked + integration on).
- OTP: `op read 'op://Private/Npmjs/one-time password?attribute=otp'`.
- Publish: `npm publish --access public --otp="<otp>"` (run from the package dir).
- Verify without local npmrc side effects: `npm view <pkg> version --userconfig "$(mktemp)"`.
- Kill the tmux session after publish.
## Plugin Release Fast Path (no core `openclaw` publish)
- Release only already-on-npm plugins. Source list is in `docs/reference/RELEASING.md` under "Current npm plugin list".
- Run all CLI `op` calls and `npm publish` inside tmux to avoid hangs/interruption:
- `tmux new -d -s release-plugins-$(date +%Y%m%d-%H%M%S)`
- `eval "$(op signin --account my.1password.com)"`
- 1Password helpers:
- password used by `npm login`:
`op item get Npmjs --format=json | jq -r '.fields[] | select(.id=="password").value'`
- OTP:
`op read 'op://Private/Npmjs/one-time password?attribute=otp'`
- Fast publish loop (local helper script in `/tmp` is fine; keep repo clean):
- compare local plugin `version` to `npm view <name> version`
- only run `npm publish --access public --otp="<otp>"` when versions differ
- skip if package is missing on npm or version already matches.
- Keep `openclaw` untouched: never run publish from repo root unless explicitly requested.
- Post-check for each release:
- per-plugin: `npm view @openclaw/<name> version --userconfig "$(mktemp)"` should be `2026.2.17`
- core guard: `npm view openclaw version --userconfig "$(mktemp)"` should stay at previous version unless explicitly requested.
## 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

@ -23,7 +23,7 @@ Welcome to the lobster tank! 🦞
- **Jos** - Telegram, API, Nix mode
- GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes)
- **Ayaan Zaidi** - Telegram subsystem, iOS app
- **Ayaan Zaidi** - Telegram subsystem, Android app
- GitHub: [@obviyus](https://github.com/obviyus) · X: [@0bviyus](https://x.com/0bviyus)
- **Tyler Yust** - Agents/subagents, cron, BlueBubbles, macOS app
@ -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
@ -57,24 +57,69 @@ Welcome to the lobster tank! 🦞
- GitHub: [@joshavant](https://github.com/joshavant) · X: [@joshavant](https://x.com/joshavant)
- **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)
- 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_)
- GitHub [@jalehman](https://github.com/jalehman) · X: [@jlehman\_](https://x.com/jlehman_)
- **Radek Sienkiewicz** - Docs, Control UI
- GitHub [@velvet-shark](https://github.com/velvet-shark) · X: [@velvet_shark](https://twitter.com/velvet_shark)
- **Muhammed Mukhthar** - Mattermost, CLI
- GitHub [@mukhtharcm](https://github.com/mukhtharcm) · X: [@mukhtharcm](https://x.com/mukhtharcm)
- **Altay** - Agents, CLI, error handling
- GitHub [@altaywtf](https://github.com/altaywtf) · X: [@altaywtf](https://x.com/altaywtf)
- **Robin Waslander** - Security, PR triage, bug fixes
- GitHub: [@hydro13](https://github.com/hydro13) · X: [@Robin_waslander](https://x.com/Robin_waslander)
- **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
- Test locally with your OpenClaw instance
- Run tests: `pnpm build && pnpm check && pnpm test`
- For extension/plugin changes, run the fast local lane first:
- `pnpm test:extension <extension-name>`
- `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
- Reply to or resolve bot review conversations you addressed before asking for review again
- **Include screenshots** — one showing the problem/before, one showing the fix/after (for UI or visual changes)
- Use American English spelling and grammar in code, comments, docs, and UI strings
- Do not edit files covered by `CODEOWNERS` security ownership unless a listed owner explicitly asked for the change or is already reviewing it with you. Treat those paths as restricted review surfaces, not opportunistic cleanup targets.
## Review Conversations Are Author-Owned
If a review bot leaves review conversations on your PR, you are expected to handle the follow-through:
- Resolve the conversation yourself once the code or explanation fully addresses the bot's concern
- Reply and leave it open only when you need maintainer or reviewer judgment
- Do not leave "fixed" bot review conversations for maintainers to clean up for you
- If Codex leaves comments, address every relevant one or resolve it with a short explanation when it is not applicable to your change
- If GitHub Codex review does not trigger for some reason, run `codex review --base origin/main` locally anyway and treat that output as required review work
This applies to both human-authored and AI-assisted PRs.
## Control UI Decorators
@ -101,8 +146,10 @@ Please include in your PR:
- [ ] Note the degree of testing (untested / lightly tested / fully tested)
- [ ] Include prompts or session logs if possible (super helpful!)
- [ ] Confirm you understand what the code does
- [ ] If you have access to Codex, run `codex review --base origin/main` locally and address the findings before asking for review
- [ ] Resolve or reply to bot review conversations after you address them
AI PRs are first-class citizens here. We just want transparency so reviewers know what to look for.
AI PRs are first-class citizens here. We just want transparency so reviewers know what to look for. If you are using an LLM coding agent, instruct it to resolve bot review conversations it has addressed instead of leaving them for maintainers.
## Current Focus & Roadmap 🗺

View File

@ -1,3 +1,5 @@
# syntax=docker/dockerfile:1.7
# Opt-in extension dependencies at build time (space-separated directory names).
# Example: docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel matrix" .
#
@ -12,14 +14,14 @@
# Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim .
ARG OPENCLAW_EXTENSIONS=""
ARG OPENCLAW_VARIANT=default
ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:22-bookworm@sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9"
ARG OPENCLAW_NODE_BOOKWORM_DIGEST="sha256:b501c082306a4f528bc4038cbf2fbb58095d583d0419a259b2114b5ac53d12e9"
ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:22-bookworm-slim@sha256:9c2c405e3ff9b9afb2873232d24bb06367d649aa3e6259cbe314da59578e81e9"
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:9c2c405e3ff9b9afb2873232d24bb06367d649aa3e6259cbe314da59578e81e9"
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"
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"
# Base images are pinned to SHA256 digests for reproducible builds.
# Trade-off: digests must be updated manually when upstream tags move.
# To update, run: docker manifest inspect node:22-bookworm (or podman)
# To update, run: docker buildx imagetools inspect node:24-bookworm (or podman)
# and replace the digest below with the current multi-arch manifest list entry.
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps
@ -37,8 +39,18 @@ RUN mkdir -p /out && \
# ── Stage 2: Build ──────────────────────────────────────────────
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build
# Install Bun (required for build scripts)
RUN curl -fsSL https://bun.sh/install | bash
# Install Bun (required for build scripts). Retry the whole bootstrap flow to
# tolerate transient 5xx failures from bun.sh/GitHub during CI image builds.
RUN set -eux; \
for attempt in 1 2 3 4 5; do \
if curl --retry 5 --retry-all-errors --retry-delay 2 -fsSL https://bun.sh/install | bash; then \
break; \
fi; \
if [ "$attempt" -eq 5 ]; then \
exit 1; \
fi; \
sleep $((attempt * 2)); \
done
ENV PATH="/root/.bun/bin:${PATH}"
RUN corepack enable
@ -48,16 +60,25 @@ WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY patches ./patches
COPY scripts ./scripts
COPY --from=ext-deps /out/ ./extensions/
# Reduce OOM risk on low-memory hosts during dependency installation.
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
COPY . .
# Normalize extension paths now so runtime COPY preserves safe modes
# without adding a second full extensions layer.
RUN for dir in /app/extensions /app/.agent /app/.agents; do \
if [ -d "$dir" ]; then \
find "$dir" -type d -exec chmod 755 {} +; \
find "$dir" -type f -exec chmod 644 {} +; \
fi; \
done
# A2UI bundle may fail under QEMU cross-compilation (e.g. building amd64
# on Apple Silicon). CI builds natively per-arch so this is a no-op there.
# Stub it so local cross-arch builds still succeed.
@ -67,20 +88,26 @@ RUN pnpm canvas:a2ui:bundle || \
echo "/* A2UI bundle unavailable in this build */" > src/canvas-host/a2ui/a2ui.bundle.js && \
echo "stub" > src/canvas-host/a2ui/.bundle.hash && \
rm -rf vendor/a2ui apps/shared/OpenClawKit/Tools/CanvasA2UI)
RUN pnpm build
RUN pnpm build:docker
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
ENV OPENCLAW_PREFER_PNPM=1
RUN pnpm ui:build
# Prune dev dependencies and strip build-only metadata before copying
# runtime assets into the final image.
FROM build AS runtime-assets
RUN CI=true pnpm prune --prod && \
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete
# ── Runtime base images ─────────────────────────────────────────
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS base-default
ARG OPENCLAW_NODE_BOOKWORM_DIGEST
LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm" \
LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm" \
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_DIGEST}"
FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-slim
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST
LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm-slim" \
LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm-slim" \
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST}"
# ── Stage 3: Runtime ────────────────────────────────────────────
@ -102,36 +129,52 @@ 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).
RUN apt-get update && \
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 && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
procps hostname curl git openssl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
procps hostname curl git lsof openssl
RUN chown node:node /app
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/package.json .
COPY --from=build --chown=node:node /app/openclaw.mjs .
COPY --from=build --chown=node:node /app/extensions ./extensions
COPY --from=build --chown=node:node /app/skills ./skills
COPY --from=build --chown=node:node /app/docs ./docs
COPY --from=runtime-assets --chown=node:node /app/dist ./dist
COPY --from=runtime-assets --chown=node:node /app/node_modules ./node_modules
COPY --from=runtime-assets --chown=node:node /app/package.json .
COPY --from=runtime-assets --chown=node:node /app/openclaw.mjs .
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
# Docker live-test runners invoke `pnpm` inside the runtime image.
# Activate the exact pinned package manager now so the container does not
# rely on a first-run network fetch or missing shims under the non-root user.
RUN corepack enable && \
corepack prepare "$(node -p "require('./package.json').packageManager")" --activate
# 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.
ENV COREPACK_HOME=/usr/local/share/corepack
RUN install -d -m 0755 "$COREPACK_HOME" && \
corepack enable && \
for attempt in 1 2 3 4 5; do \
if corepack prepare "$(node -p "require('./package.json').packageManager")" --activate; then \
break; \
fi; \
if [ "$attempt" -eq 5 ]; then \
exit 1; \
fi; \
sleep $((attempt * 2)); \
done && \
chmod -R a+rX "$COREPACK_HOME"
# Install additional system packages needed by your skills or extensions.
# Example: docker build --build-arg OPENCLAW_DOCKER_APT_PACKAGES="python3 wget" .
ARG OPENCLAW_DOCKER_APT_PACKAGES=""
RUN if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
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 \
if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES; \
fi
# Optionally install Chromium and Xvfb for browser automation.
@ -139,15 +182,15 @@ RUN if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
# Adds ~300MB but eliminates the 60-90s Playwright install on every container start.
# Must run after node_modules COPY so playwright-core is available.
ARG OPENCLAW_INSTALL_BROWSER=""
RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
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 \
if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends xvfb && \
mkdir -p /home/node/.cache/ms-playwright && \
PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright \
node /app/node_modules/playwright-core/cli.js install --with-deps chromium && \
chown -R node:node /home/node/.cache/ms-playwright && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
chown -R node:node /home/node/.cache/ms-playwright; \
fi
# Optionally install Docker CLI for sandbox container management.
@ -156,7 +199,9 @@ RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
# Required for agents.defaults.sandbox to function in Docker deployments.
ARG OPENCLAW_INSTALL_DOCKER_CLI=""
ARG OPENCLAW_DOCKER_GPG_FINGERPRINT="9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
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 \
if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl gnupg && \
@ -177,20 +222,9 @@ RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
"$(dpkg --print-architecture)" > /etc/apt/sources.list.d/docker.list && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
docker-ce-cli docker-compose-plugin && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
docker-ce-cli docker-compose-plugin; \
fi
# Normalize extension paths so plugin safety checks do not reject
# world-writable directories inherited from source file modes.
RUN for dir in /app/extensions /app/.agent /app/.agents; do \
if [ -d "$dir" ]; then \
find "$dir" -type d -exec chmod 755 {} +; \
find "$dir" -type f -exec chmod 644 {} +; \
fi; \
done
# Expose the CLI binary without requiring npm global writes as non-root.
RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
&& chmod 755 /app/openclaw.mjs
@ -198,7 +232,7 @@ RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
ENV NODE_ENV=production
# Security hardening: Run as non-root user
# The node:22-bookworm image includes a 'node' user (uid 1000)
# The node:24-bookworm image includes a 'node' user (uid 1000)
# This reduces the attack surface by preventing container escape via root privileges
USER node

View File

@ -1,8 +1,13 @@
# syntax=docker/dockerfile:1.7
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
apt-get update \
&& apt-get upgrade -y --no-install-recommends \
&& apt-get install -y --no-install-recommends \
bash \
ca-certificates \
@ -10,8 +15,7 @@ RUN apt-get update \
git \
jq \
python3 \
ripgrep \
&& rm -rf /var/lib/apt/lists/*
ripgrep
RUN useradd --create-home --shell /bin/bash sandbox
USER sandbox

View File

@ -1,8 +1,13 @@
# syntax=docker/dockerfile:1.7
FROM debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
apt-get update \
&& apt-get upgrade -y --no-install-recommends \
&& apt-get install -y --no-install-recommends \
bash \
ca-certificates \
@ -17,11 +22,9 @@ RUN apt-get update \
socat \
websockify \
x11vnc \
xvfb \
&& rm -rf /var/lib/apt/lists/*
xvfb
COPY scripts/sandbox-browser-entrypoint.sh /usr/local/bin/openclaw-sandbox-browser
RUN chmod +x /usr/local/bin/openclaw-sandbox-browser
COPY --chmod=755 scripts/sandbox-browser-entrypoint.sh /usr/local/bin/openclaw-sandbox-browser
RUN useradd --create-home --shell /bin/bash sandbox
USER sandbox

View File

@ -1,3 +1,5 @@
# syntax=docker/dockerfile:1.7
ARG BASE_IMAGE=openclaw-sandbox:bookworm-slim
FROM ${BASE_IMAGE}
@ -19,9 +21,11 @@ ENV HOMEBREW_CELLAR=${BREW_INSTALL_DIR}/Cellar
ENV HOMEBREW_REPOSITORY=${BREW_INSTALL_DIR}/Homebrew
ENV PATH=${BUN_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/sbin:${PATH}
RUN apt-get update \
&& apt-get install -y --no-install-recommends ${PACKAGES} \
&& rm -rf /var/lib/apt/lists/*
RUN --mount=type=cache,id=openclaw-sandbox-common-apt-cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=openclaw-sandbox-common-apt-lists,target=/var/lib/apt,sharing=locked \
apt-get update \
&& apt-get upgrade -y --no-install-recommends \
&& apt-get install -y --no-install-recommends ${PACKAGES}
RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm; fi
@ -42,4 +46,3 @@ fi
# Default is sandbox, but allow BASE_IMAGE overrides to select another final user.
USER ${FINAL_USER}

View File

@ -2,8 +2,8 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text-dark.png">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text.png" alt="OpenClaw" width="500">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text-dark.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text.svg" alt="OpenClaw" width="500">
</picture>
</p>
@ -23,10 +23,10 @@ It answers you on the channels you already use (WhatsApp, Telegram, Slack, Disco
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)
@ -103,7 +103,7 @@ pnpm build
pnpm openclaw onboard --install-daemon
# Dev loop (auto-reload on TS changes)
# Dev loop (auto-reload on source/config changes)
pnpm gateway:watch
```
@ -132,7 +132,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- **[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,7 +143,7 @@ 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).
@ -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
@ -422,7 +422,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

@ -37,6 +37,7 @@ For fastest triage, include all of the following:
- Exact vulnerable path (`file`, function, and line range) on a current revision.
- Tested version details (OpenClaw version and/or commit SHA).
- Reproducible PoC against latest `main` or latest released version.
- If the claim targets a released version, evidence from the shipped tag and published artifact/package for that exact version (not only `main`).
- Demonstrated impact tied to OpenClaw's documented trust boundaries.
- For exposed-secret reports: proof the credential is OpenClaw-owned (or grants access to OpenClaw-operated infrastructure/services).
- Explicit statement that the report does not rely on adversarial operators sharing one gateway host/config.
@ -55,6 +56,7 @@ These are frequently reported but are typically closed with no code change:
- Authorized user-triggered local actions presented as privilege escalation. Example: an allowlisted/owner sender running `/export-session /absolute/path.html` to write on the host. In this trust model, authorized user actions are trusted host actions unless you demonstrate an auth/sandbox/boundary bypass.
- Reports that only show a malicious plugin executing privileged actions after a trusted operator installs/enables it.
- Reports that assume per-user multi-tenant authorization on a shared gateway host/config.
- Reports that treat the Gateway HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) as if they implemented scoped operator auth (`operator.write` vs `operator.admin`). These endpoints authenticate the shared Gateway bearer secret/password and are documented full operator-access surfaces, not per-user/per-scope boundaries.
- Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries.
- ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass.
- Archive/install extraction claims that require pre-existing local filesystem priming in trusted state (for example planting symlink/hardlink aliases under destination directories such as skills/tools paths) without showing an untrusted path that can create/control that primitive.
@ -65,6 +67,7 @@ These are frequently reported but are typically closed with no code change:
- Discord inbound webhook signature findings for paths not used by this repo's Discord integration.
- Claims that Microsoft Teams `fileConsent/invoke` `uploadInfo.uploadUrl` is attacker-controlled without demonstrating one of: auth boundary bypass, a real authenticated Teams/Bot Framework event carrying attacker-chosen URL, or compromise of the Microsoft/Bot trust path.
- Scanner-only claims against stale/nonexistent paths, or claims without a working repro.
- Reports that restate an already-fixed issue against later released versions without showing the vulnerable path still exists in the shipped tag or published artifact for that later version.
### Duplicate Report Handling
@ -90,6 +93,7 @@ When patching a GHSA via `gh api`, include `X-GitHub-Api-Version: 2022-11-28` (o
OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boundary.
- Authenticated Gateway callers are treated as trusted operators for that gateway instance.
- The HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) are in that same trusted-operator bucket. Passing Gateway bearer auth there is equivalent to operator access for that gateway; they do not implement a narrower `operator.write` vs `operator.admin` trust split.
- Session identifiers (`sessionKey`, session IDs, labels) are routing controls, not per-user authorization boundaries.
- If one operator can view data from another operator on the same gateway, that is expected in this trust model.
- OpenClaw can technically run multiple gateway instances on one machine, but recommended operations are clean separation by trust boundary.
@ -125,6 +129,7 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
- Any report whose only claim is that an operator-enabled `dangerous*`/`dangerously*` config option weakens defaults (these are explicit break-glass tradeoffs by design)
- Reports that depend on trusted operator-supplied configuration values to trigger availability impact (for example custom regex patterns). These may still be fixed as defense-in-depth hardening, but are not security-boundary bypasses.
- Reports whose only claim is heuristic/parity drift in command-risk detection (for example obfuscation-pattern checks) across exec surfaces, without a demonstrated trust-boundary bypass. These are hardening-only findings and are not vulnerabilities; triage may close them as `invalid`/`no-action` or track them separately as low/informational hardening.
- Reports whose only claim is that exec approvals do not semantically model every interpreter/runtime loader form, subcommand, flag combination, package script, or transitive module/config import. Exec approvals bind exact request context and best-effort direct local file operands; they are not a complete semantic model of everything a runtime may load.
- Exposed secrets that are third-party/user-controlled credentials (not OpenClaw-owned and not granting access to OpenClaw-operated infrastructure/services) without demonstrated OpenClaw impact
- Reports whose only claim is host-side exec when sandbox runtime is disabled/unavailable (documented default behavior in the trusted-operator model), without a boundary bypass.
- Reports whose only claim is that a platform-provided upload destination URL is untrusted (for example Microsoft Teams `fileConsent/invoke` `uploadInfo.uploadUrl`) without proving attacker control in an authenticated production flow.
@ -144,6 +149,7 @@ OpenClaw security guidance assumes:
OpenClaw's security model is "personal assistant" (one trusted operator, potentially many agents), not "shared multi-tenant bus."
- If multiple people can message the same tool-enabled agent (for example a shared Slack workspace), they can all steer that agent within its granted permissions.
- Non-owner sender status only affects owner-only tools/commands. If a non-owner can still access a non-owner-only tool on that same agent (for example `canvas`), that is within the granted tool boundary unless the report demonstrates an auth, policy, allowlist, approval, or sandbox bypass.
- Session or memory scoping reduces context bleed, but does **not** create per-user host authorization boundaries.
- For mixed-trust or adversarial users, isolate by OS user/host/gateway and use separate credentials per boundary.
- A company-shared agent can be a valid setup when users are in the same trust boundary and the agent is strictly business-only.
@ -165,6 +171,7 @@ OpenClaw separates routing from execution, but both remain inside the same opera
- **Gateway** is the control plane. If a caller passes Gateway auth, they are treated as a trusted operator for that Gateway.
- **Node** is an execution extension of the Gateway. Pairing a node grants operator-level remote capability on that node.
- **Exec approvals** (allowlist/ask UI) are operator guardrails to reduce accidental command execution, not a multi-tenant authorization boundary.
- Exec approvals bind exact command/cwd/env context and, when OpenClaw can identify one concrete local script/file operand, that file snapshot too. This is best-effort integrity hardening, not a complete semantic model of every interpreter/runtime loader path.
- Differences in command-risk warning heuristics between exec surfaces (`gateway`, `node`, `sandbox`) do not, by themselves, constitute a security-boundary bypass.
- For untrusted-user isolation, split by trust boundary: separate gateways and separate OS users/hosts per boundary.

View File

@ -101,25 +101,19 @@ public enum WakeWordGate {
}
public static func commandText(
transcript: String,
transcript _: String,
segments: [WakeWordSegment],
triggerEndTime: TimeInterval)
-> String {
let threshold = triggerEndTime + 0.001
var commandWords: [String] = []
commandWords.reserveCapacity(segments.count)
for segment in segments where segment.start >= threshold {
if normalizeToken(segment.text).isEmpty { continue }
if let range = segment.range {
let slice = transcript[range.lowerBound...]
return String(slice).trimmingCharacters(in: Self.whitespaceAndPunctuation)
}
break
let normalized = normalizeToken(segment.text)
if normalized.isEmpty { continue }
commandWords.append(segment.text)
}
let text = segments
.filter { $0.start >= threshold && !normalizeToken($0.text).isEmpty }
.map(\.text)
.joined(separator: " ")
return text.trimmingCharacters(in: Self.whitespaceAndPunctuation)
return commandWords.joined(separator: " ").trimmingCharacters(in: Self.whitespaceAndPunctuation)
}
public static func matchesTextOnly(text: String, triggers: [String]) -> Bool {

View File

@ -46,6 +46,25 @@ import Testing
let match = WakeWordGate.match(transcript: transcript, segments: segments, config: config)
#expect(match?.command == "do it")
}
@Test func commandTextHandlesForeignRangeIndices() {
let transcript = "hey clawd do thing"
let other = "do thing"
let foreignRange = other.range(of: "do")
let segments = [
WakeWordSegment(text: "hey", start: 0.0, duration: 0.1, range: transcript.range(of: "hey")),
WakeWordSegment(text: "clawd", start: 0.2, duration: 0.1, range: transcript.range(of: "clawd")),
WakeWordSegment(text: "do", start: 0.9, duration: 0.1, range: foreignRange),
WakeWordSegment(text: "thing", start: 1.1, duration: 0.1, range: nil),
]
let command = WakeWordGate.commandText(
transcript: transcript,
segments: segments,
triggerEndTime: 0.3)
#expect(command == "do thing")
}
}
private func makeSegments(

View File

@ -3,725 +3,246 @@
<channel>
<title>OpenClaw</title>
<item>
<title>2026.3.7</title>
<pubDate>Sun, 08 Mar 2026 04:42:35 +0000</pubDate>
<title>2026.3.13</title>
<pubDate>Sat, 14 Mar 2026 05:19:48 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026030790</sparkle:version>
<sparkle:shortVersionString>2026.3.7</sparkle:shortVersionString>
<sparkle:version>2026031390</sparkle:version>
<sparkle:shortVersionString>2026.3.13</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.7</h2>
<description><![CDATA[<h2>OpenClaw 2026.3.13</h2>
<h3>Changes</h3>
<ul>
<li>Agents/context engine plugin interface: add <code>ContextEngine</code> plugin slot with full lifecycle hooks (<code>bootstrap</code>, <code>ingest</code>, <code>assemble</code>, <code>compact</code>, <code>afterTurn</code>, <code>prepareSubagentSpawn</code>, <code>onSubagentEnded</code>), slot-based registry with config-driven resolution, <code>LegacyContextEngine</code> wrapper preserving existing compaction behavior, scoped subagent runtime for plugin runtimes via <code>AsyncLocalStorage</code>, and <code>sessions.get</code> gateway method. Enables plugins like <code>lossless-claw</code> to provide alternative context management strategies without modifying core compaction logic. Zero behavior change when no context engine plugin is configured. (#22201) thanks @jalehman.</li>
<li>ACP/persistent channel bindings: add durable Discord channel and Telegram topic binding storage, routing resolution, and CLI/docs support so ACP thread targets survive restarts and can be managed consistently. (#34873) Thanks @dutifulbob.</li>
<li>Telegram/ACP topic bindings: accept Telegram Mac Unicode dash option prefixes in <code>/acp spawn</code>, support Telegram topic thread binding (<code>--thread here|auto</code>), route bound-topic follow-ups to ACP sessions, add actionable Telegram approval buttons with prefixed approval-id resolution, and pin successful bind confirmations in-topic. (#36683) Thanks @huntharo.</li>
<li>Telegram/topic agent routing: support per-topic <code>agentId</code> overrides in forum groups and DM topics so topics can route to dedicated agents with isolated sessions. (#33647; based on #31513) Thanks @kesor and @Sid-Qin.</li>
<li>Web UI/i18n: add Spanish (<code>es</code>) locale support in the Control UI, including locale detection, lazy loading, and language picker labels across supported locales. (#35038) Thanks @DaoPromociones.</li>
<li>Onboarding/web search: add provider selection step and full provider list in configure wizard, with SecretRef ref-mode support during onboarding. (#34009) Thanks @kesku and @thewilloftheshadow.</li>
<li>Tools/Web search: switch Perplexity provider to Search API with structured results plus new language/region/time filters. (#33822) Thanks @kesku.</li>
<li>Gateway: add SecretRef support for gateway.auth.token with auth-mode guardrails. (#35094) Thanks @joshavant.</li>
<li>Docker/Podman extension dependency baking: add <code>OPENCLAW_EXTENSIONS</code> so container builds can preinstall selected bundled extension npm dependencies into the image for faster and more reproducible startup in container deployments. (#32223) Thanks @sallyom.</li>
<li>Plugins/before_prompt_build system-context fields: add <code>prependSystemContext</code> and <code>appendSystemContext</code> so static plugin guidance can be placed in system prompt space for provider caching and lower repeated prompt token cost. (#35177) thanks @maweibin.</li>
<li>Plugins/hook policy: add <code>plugins.entries.<id>.hooks.allowPromptInjection</code>, validate unknown typed hook names at runtime, and preserve legacy <code>before_agent_start</code> model/provider overrides while stripping prompt-mutating fields when prompt injection is disabled. (#36567) thanks @gumadeiras.</li>
<li>Hooks/Compaction lifecycle: emit <code>session:compact:before</code> and <code>session:compact:after</code> internal events plus plugin compaction callbacks with session/count metadata, so automations can react to compaction runs consistently. (#16788) thanks @vincentkoc.</li>
<li>Agents/compaction post-context configurability: add <code>agents.defaults.compaction.postCompactionSections</code> so deployments can choose which <code>AGENTS.md</code> sections are re-injected after compaction, while preserving legacy fallback behavior when the documented default pair is configured in any order. (#34556) thanks @efe-arv.</li>
<li>TTS/OpenAI-compatible endpoints: add <code>messages.tts.openai.baseUrl</code> config support with config-over-env precedence, endpoint-aware directive validation, and OpenAI TTS request routing to the resolved base URL. (#34321) thanks @RealKai42.</li>
<li>Slack/DM typing feedback: add <code>channels.slack.typingReaction</code> so Socket Mode DMs can show reaction-based processing status even when Slack native assistant typing is unavailable. (#19816) Thanks @dalefrieswthat.</li>
<li>Discord/allowBots mention gating: add <code>allowBots: "mentions"</code> to only accept bot-authored messages that mention the bot. Thanks @thewilloftheshadow.</li>
<li>Agents/tool-result truncation: preserve important tail diagnostics by using head+tail truncation for oversized tool results while keeping configurable truncation options. (#20076) thanks @jlwestsr.</li>
<li>Cron/job snapshot persistence: skip backup during normalization persistence in <code>ensureLoaded</code> so <code>jobs.json.bak</code> keeps the pre-edit snapshot for recovery, while preserving backup creation on explicit user-driven writes. (#35234) Thanks @0xsline.</li>
<li>CLI: make read-only SecretRef status flows degrade safely (#37023) thanks @joshavant.</li>
<li>Tools/Diffs guidance: restore a short system-prompt hint for enabled diffs while keeping the detailed instructions in the companion skill, so diffs usage guidance stays out of user-prompt space. (#36904) thanks @gumadeiras.</li>
<li>Tools/Diffs guidance loading: move diffs usage guidance from unconditional prompt-hook injection to the plugin companion skill path, reducing unrelated-turn prompt noise while keeping diffs tool behavior unchanged. (#32630) thanks @sircrumpet.</li>
<li>Docs/Web search: remove outdated Brave free-tier wording and replace prescriptive AI ToS guidance with neutral compliance language in Brave setup docs. (#26860) Thanks @HenryLoenwind.</li>
<li>Config/Compaction safeguard tuning: expose <code>agents.defaults.compaction.recentTurnsPreserve</code> and quality-guard retry knobs through the validated config surface and embedded-runner wiring, with regression coverage for real config loading and schema metadata. (#25557) thanks @rodrigouroz.</li>
<li>iOS/App Store Connect release prep: align iOS bundle identifiers under <code>ai.openclaw.client</code>, refresh Watch app icons, add Fastlane metadata/screenshot automation, and support Keychain-backed ASC auth for uploads. (#38936) Thanks @ngutman.</li>
<li>Mattermost/model picker: add Telegram-style interactive provider/model browsing for <code>/oc_model</code> and <code>/oc_models</code>, fix picker callback updates, and emit a normal confirmation reply when a model is selected. (#38767) thanks @mukhtharcm.</li>
<li>Docker/multi-stage build: restructure Dockerfile as a multi-stage build to produce a minimal runtime image without build tools, source code, or Bun; add <code>OPENCLAW_VARIANT=slim</code> build arg for a bookworm-slim variant. (#38479) Thanks @sallyom.</li>
<li>Google/Gemini 3.1 Flash-Lite: add first-class <code>google/gemini-3.1-flash-lite-preview</code> support across model-id normalization, default aliases, media-understanding image lookups, Google Gemini CLI forward-compat fallback, and docs.</li>
</ul>
<h3>Breaking</h3>
<ul>
<li><strong>BREAKING:</strong> Gateway auth now requires explicit <code>gateway.auth.mode</code> when both <code>gateway.auth.token</code> and <code>gateway.auth.password</code> are configured (including SecretRefs). Set <code>gateway.auth.mode</code> to <code>token</code> or <code>password</code> before upgrade to avoid startup/pairing/TUI failures. (#35094) Thanks @joshavant.</li>
<li>Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus.</li>
<li>iOS/onboarding: add a first-run welcome pager before gateway setup, stop auto-opening the QR scanner, and show <code>/pair qr</code> instructions on the connect step. (#45054) Thanks @ngutman.</li>
<li>Browser/existing-session: add an official Chrome DevTools MCP attach mode for signed-in live Chrome sessions, with docs for <code>chrome://inspect/#remote-debugging</code> enablement and direct backlinks to Chromes own setup guides.</li>
<li>Browser/agents: add built-in <code>profile="user"</code> for the logged-in host browser and <code>profile="chrome-relay"</code> for the extension relay, so agent browser calls can prefer the real signed-in browser without the extra <code>browserSession</code> selector.</li>
<li>Browser/act automation: add batched actions, selector targeting, and delayed clicks for browser act requests with normalized batch dispatch. Thanks @vincentkoc.</li>
<li>Docker/timezone override: add <code>OPENCLAW_TZ</code> so <code>docker-setup.sh</code> can pin gateway and CLI containers to a chosen IANA timezone instead of inheriting the daemon default. (#34119) Thanks @Lanfei.</li>
<li>Dependencies/pi: bump <code>@mariozechner/pi-agent-core</code>, <code>@mariozechner/pi-ai</code>, <code>@mariozechner/pi-coding-agent</code>, and <code>@mariozechner/pi-tui</code> to <code>0.58.0</code>.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Models/MiniMax: stop advertising removed <code>MiniMax-M2.5-Lightning</code> in built-in provider catalogs, onboarding metadata, and docs; keep the supported fast-tier model as <code>MiniMax-M2.5-highspeed</code>.</li>
<li>Security/Config: fail closed when <code>loadConfig()</code> hits validation or read errors so invalid configs cannot silently fall back to permissive runtime defaults. (#9040) Thanks @joetomasone.</li>
<li>Memory/Hybrid search: preserve negative FTS5 BM25 relevance ordering in <code>bm25RankToScore()</code> so stronger keyword matches rank above weaker ones instead of collapsing or reversing scores. (#33757) Thanks @lsdcc01.</li>
<li>LINE/<code>requireMention</code> group gating: align inbound and reply-stage LINE group policy resolution across raw, <code>group:</code>, and <code>room:</code> keys (including account-scoped group config), preserve plugin-backed reply-stage fallback behavior, and add regression coverage for prefixed-only group/room config plus reply-stage policy resolution. (#35847) Thanks @kirisame-wang.</li>
<li>Onboarding/local setup: default unset local <code>tools.profile</code> to <code>coding</code> instead of <code>messaging</code>, restoring file/runtime tools for fresh local installs while preserving explicit user-set profiles. (from #38241, overlap with #34958) Thanks @cgdusek.</li>
<li>Gateway/Telegram stale-socket restart guard: only apply stale-socket restarts to channels that publish event-liveness timestamps, preventing Telegram providers from being misclassified as stale solely due to long uptime and avoiding restart/pairing storms after upgrade. (openclaw#38464)</li>
<li>Onboarding/headless Linux daemon probe hardening: treat <code>systemctl --user is-enabled</code> probe failures as non-fatal during daemon install flow so onboarding no longer crashes on SSH/headless VPS environments before showing install guidance. (#37297) Thanks @acarbajal-web.</li>
<li>Memory/QMD mcporter Windows spawn hardening: when <code>mcporter.cmd</code> launch fails with <code>spawn EINVAL</code>, retry via bare <code>mcporter</code> shell resolution so QMD recall can continue instead of falling back to builtin memory search. (#27402) Thanks @i0ivi0i.</li>
<li>Tools/web_search Brave language-code validation: align <code>search_lang</code> handling with Brave-supported codes (including <code>zh-hans</code>, <code>zh-hant</code>, <code>en-gb</code>, and <code>pt-br</code>), map common alias inputs (<code>zh</code>, <code>ja</code>) to valid Brave values, and reject unsupported codes before upstream requests to prevent 422 failures. (#37260) Thanks @heyanming.</li>
<li>Models/openai-completions streaming compatibility: force <code>compat.supportsUsageInStreaming=false</code> for non-native OpenAI-compatible endpoints during model normalization, preventing usage-only stream chunks from triggering <code>choices[0]</code> parser crashes in provider streams. (#8714) Thanks @nonanon1.</li>
<li>Tools/xAI native web-search collision guard: drop OpenClaw <code>web_search</code> from tool registration when routing to xAI/Grok model providers (including OpenRouter <code>x-ai/*</code>) to avoid duplicate tool-name request failures against provider-native <code>web_search</code>. (#14749) Thanks @realsamrat.</li>
<li>TUI/token copy-safety rendering: treat long credential-like mixed alphanumeric tokens (including quoted forms) as copy-sensitive in render sanitization so formatter hard-wrap guards no longer inject visible spaces into auth-style values before display. (#26710) Thanks @jasonthane.</li>
<li>WhatsApp/self-chat response prefix fallback: stop forcing <code>"[openclaw]"</code> as the implicit outbound response prefix when no identity name or response prefix is configured, so blank/default prefix settings no longer inject branding text unexpectedly in self-chat flows. (#27962) Thanks @ecanmor.</li>
<li>Memory/QMD search result decoding: accept <code>qmd search</code> hits that only include <code>file</code> URIs (for example <code>qmd://collection/path.md</code>) without <code>docid</code>, resolve them through managed collection roots, and keep multi-collection results keyed by file fallback so valid QMD hits no longer collapse to empty <code>memory_search</code> output. (#28181) Thanks @0x76696265.</li>
<li>Memory/QMD collection-name conflict recovery: when <code>qmd collection add</code> fails because another collection already occupies the same <code>path + pattern</code>, detect the conflicting collection from <code>collection list</code>, remove it, and retry add so agent-scoped managed collections are created deterministically instead of being silently skipped; also add warning-only fallback when qmd metadata is unavailable to avoid destructive guesses. (#25496) Thanks @Ramsbaby.</li>
<li>Slack/app_mention race dedupe: when <code>app_mention</code> dispatch wins while same-<code>ts</code> <code>message</code> prepare is still in-flight, suppress the later message dispatch so near-simultaneous Slack deliveries do not produce duplicate replies; keep single-retry behavior and add regression coverage for both dropped and successful message-prepare outcomes. (#37033) Thanks @Takhoffman.</li>
<li>Gateway/chat streaming tool-boundary text retention: merge assistant delta segments into per-run chat buffers so pre-tool text is preserved in live chat deltas/finals when providers emit post-tool assistant segments as non-prefix snapshots. (#36957) Thanks @Datyedyeguy.</li>
<li>TUI/model indicator freshness: prevent stale session snapshots from overwriting freshly patched model selection (and reset per-session freshness when switching session keys) so <code>/model</code> updates reflect immediately instead of lagging by one or more commands. (#21255) Thanks @kowza.</li>
<li>TUI/final-error rendering fallback: when a chat <code>final</code> event has no renderable assistant content but includes envelope <code>errorMessage</code>, render the formatted error text instead of collapsing to <code>"(no output)"</code>, preserving actionable failure context in-session. (#14687) Thanks @Mquarmoc.</li>
<li>TUI/session-key alias event matching: treat chat events whose session keys are canonical aliases (for example <code>agent:<id>:main</code> vs <code>main</code>) as the same session while preserving cross-agent isolation, so assistant replies no longer disappear or surface in another terminal window due to strict key-form mismatch. (#33937) Thanks @yjh1412.</li>
<li>OpenAI Codex OAuth/login parity: keep <code>openclaw models auth login --provider openai-codex</code> on the built-in path even without provider plugins, preserve Pi-generated authorize URLs without local scope rewriting, and stop validating successful Codex sign-ins against the public OpenAI Responses API after callback. (#37558; follow-up to #36660 and #24720) Thanks @driesvints, @Skippy-Gunboat, and @obviyus.</li>
<li>Agents/config schema lookup: add <code>gateway</code> tool action <code>config.schema.lookup</code> so agents can inspect one config path at a time before edits without loading the full schema into prompt context. (#37266) Thanks @gumadeiras.</li>
<li>Onboarding/API key input hardening: strip non-Latin1 Unicode artifacts from normalized secret input (while preserving Latin-1 content and internal spaces) so malformed copied API keys cannot trigger HTTP header <code>ByteString</code> construction crashes; adds regression coverage for shared normalization and MiniMax auth header usage. (#24496) Thanks @fa6maalassaf.</li>
<li>Kimi Coding/Anthropic tools compatibility: normalize <code>anthropic-messages</code> tool payloads to OpenAI-style <code>tools[].function</code> + compatible <code>tool_choice</code> when targeting Kimi Coding endpoints, restoring tool-call workflows that regressed after v2026.3.2. (#37038) Thanks @mochimochimochi-hub.</li>
<li>Heartbeat/workspace-path guardrails: append explicit workspace <code>HEARTBEAT.md</code> path guidance (and <code>docs/heartbeat.md</code> avoidance) to heartbeat prompts so heartbeat runs target workspace checklists reliably across packaged install layouts. (#37037) Thanks @stofancy.</li>
<li>Subagents/kill-complete announce race: when a late <code>subagent-complete</code> lifecycle event arrives after an earlier kill marker, clear stale kill suppression/cleanup flags and re-run announce cleanup so finished runs no longer get silently swallowed. (#37024) Thanks @cmfinlan.</li>
<li>Agents/tool-result cleanup timeout hardening: on embedded runner teardown idle timeouts, clear pending tool-call state without persisting synthetic <code>missing tool result</code> entries, preventing timeout cleanups from poisoning follow-up turns; adds regression coverage for timeout clear-vs-flush behavior. (#37081) Thanks @Coyote-Den.</li>
<li>Agents/openai-completions stream timeout hardening: ensure runtime undici global dispatchers use extended streaming body/header timeouts (including env-proxy dispatcher mode) before embedded runs, reducing forced mid-stream <code>terminated</code> failures on long generations; adds regression coverage for dispatcher selection and idempotent reconfiguration. (#9708) Thanks @scottchguard.</li>
<li>Agents/fallback cooldown probe execution: thread explicit rate-limit cooldown probe intent from model fallback into embedded runner auth-profile selection so same-provider fallback attempts can actually run when all profiles are cooldowned for <code>rate_limit</code> (instead of failing pre-run as <code>No available auth profile</code>), while preserving default cooldown skip behavior and adding regression tests at both fallback and runner layers. (#13623) Thanks @asfura.</li>
<li>Cron/OpenAI Codex OAuth refresh hardening: when <code>openai-codex</code> token refresh fails specifically on account-id extraction, reuse the cached access token instead of failing the run immediately, with regression coverage to keep non-Codex and unrelated refresh failures unchanged. (#36604) Thanks @laulopezreal.</li>
<li>TUI/session isolation for <code>/new</code>: make <code>/new</code> allocate a unique <code>tui-<uuid></code> session key instead of resetting the shared agent session, so multiple TUI clients on the same agent stop receiving each others replies; also sanitize <code>/new</code> and <code>/reset</code> failure text before rendering in-terminal. Landed from contributor PR #39238 by @widingmarcus-cyber. Thanks @widingmarcus-cyber.</li>
<li>Synology Chat/rate-limit env parsing: honor <code>SYNOLOGY_RATE_LIMIT=0</code> as an explicit value while still falling back to the default limit for malformed env values instead of partially parsing them. Landed from contributor PR #39197 by @scoootscooob. Thanks @scoootscooob.</li>
<li>Voice-call/OpenAI Realtime STT config defaults: honor explicit <code>vadThreshold: 0</code> and <code>silenceDurationMs: 0</code> instead of silently replacing them with defaults. Landed from contributor PR #39196 by @scoootscooob. Thanks @scoootscooob.</li>
<li>Voice-call/OpenAI TTS speed config: honor explicit <code>speed: 0</code> instead of silently replacing it with the default speed. Landed from contributor PR #39318 by @ql-wade. Thanks @ql-wade.</li>
<li>launchd/runtime PID parsing: reject <code>pid <= 0</code> from <code>launchctl print</code> so the daemon state parser no longer treats kernel/non-running sentinel values as real process IDs. Landed from contributor PR #39281 by @mvanhorn. Thanks @mvanhorn.</li>
<li>Cron/file permission hardening: enforce owner-only (<code>0600</code>) cron store/backup/run-log files and harden cron store + run-log directories to <code>0700</code>, including pre-existing directories from older installs. (#36078) Thanks @aerelune.</li>
<li>Gateway/remote WS break-glass hostname support: honor <code>OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1</code> for <code>ws://</code> hostname URLs (not only private IP literals) across onboarding validation and runtime gateway connection checks, while still rejecting public IP literals and non-unicast IPv6 endpoints. (#36930) Thanks @manju-rn.</li>
<li>Routing/binding lookup scalability: pre-index route bindings by channel/account and avoid full binding-list rescans on channel-account cache rollover, preventing multi-second <code>resolveAgentRoute</code> stalls in large binding configurations. (#36915) Thanks @songchenghao.</li>
<li>Browser/session cleanup: track browser tabs opened by session-scoped browser tool runs and close tracked tabs during <code>sessions.reset</code>/<code>sessions.delete</code> runtime cleanup, preventing orphaned tabs and unbounded browser memory growth after session teardown. (#36666) Thanks @Harnoor6693.</li>
<li>Plugin/hook install rollback hardening: stage installs under the canonical install base, validate and run dependency installs before publish, and restore updates by rename instead of deleting the target path, reducing partial-replace and symlink-rebind risk during install failures.</li>
<li>Slack/local file upload allowlist parity: propagate <code>mediaLocalRoots</code> through the Slack send action pipeline so workspace-rooted attachments pass <code>assertLocalMediaAllowed</code> checks while non-allowlisted paths remain blocked. (synthesis: #36656; overlap considered from #36516, #36496, #36493, #36484, #32648, #30888) Thanks @2233admin.</li>
<li>Agents/compaction safeguard pre-check: skip embedded compaction before entering the Pi SDK when a session has no real conversation messages, avoiding unnecessary LLM API calls on idle sessions. (#36451) thanks @Sid-Qin.</li>
<li>Config/schema cache key stability: build merged schema cache keys with incremental hashing to avoid large single-string serialization and prevent <code>RangeError: Invalid string length</code> on high-cardinality plugin/channel metadata. (#36603) Thanks @powermaster888.</li>
<li>iMessage/cron completion announces: strip leaked inline reply tags (for example <code>[[reply_to:6100]]</code>) from user-visible completion text so announcement deliveries do not expose threading metadata. (#24600) Thanks @vincentkoc.</li>
<li>Control UI/iMessage duplicate reply routing: keep internal webchat turns on dispatcher delivery (instead of origin-channel reroute) so Control UI chats do not duplicate replies into iMessage, while preserving webchat-provider relayed routing for external surfaces. Fixes #33483. Thanks @alicexmolt.</li>
<li>Sessions/daily reset transcript archival: archive prior transcript files during stale-session scheduled/daily resets by capturing the previous session entry before rollover, preventing orphaned transcript files on disk. (#35493) Thanks @byungsker.</li>
<li>Feishu/group slash command detection: normalize group mention wrappers before command-authorization probing so mention-prefixed commands (for example <code>@Bot/model</code> and <code>@Bot /reset</code>) are recognized as gateway commands instead of being forwarded to the agent. (#35994) Thanks @liuxiaopai-ai.</li>
<li>Control UI/auth token separation: keep the shared gateway token in browser auth validation while reserving cached device tokens for signed device payloads, preventing false <code>device token mismatch</code> disconnects after restart/rotation. Landed from contributor PR #37382 by @FradSer. Thanks @FradSer.</li>
<li>Gateway/browser auth reconnect hardening: stop counting missing token/password submissions as auth rate-limit failures, and stop auto-reconnecting Control UI clients on non-recoverable auth errors so misconfigured browser tabs no longer lock out healthy sessions. Landed from contributor PR #38725 by @ademczuk. Thanks @ademczuk.</li>
<li>Gateway/service token drift repair: stop persisting shared auth tokens into installed gateway service units, flag stale embedded service tokens for reinstall, and treat tokenless service env as canonical so token rotation/reboot flows stay aligned with config/env resolution. Landed from contributor PR #28428 by @l0cka. Thanks @l0cka.</li>
<li>Control UI/agents-page selection: keep the edited agent selected after saving agent config changes and reloading the agents list, so <code>/agents</code> no longer snaps back to the default agent. Landed from contributor PR #39301 by @MumuTW. Thanks @MumuTW.</li>
<li>Gateway/auth follow-up hardening: preserve systemd <code>EnvironmentFile=</code> precedence/source provenance in daemon audits and doctor repairs, block shared-password override flows from piggybacking cached device tokens, and fail closed when config-first gateway SecretRefs cannot resolve. Follow-up to #39241.</li>
<li>Agents/context pruning: guard assistant thinking/text char estimation against malformed blocks (missing <code>thinking</code>/<code>text</code> strings or null entries) so pruning no longer crashes with malformed provider content. (openclaw#35146) thanks @Sid-Qin.</li>
<li>Agents/transcript policy: set <code>preserveSignatures</code> to Anthropic-only handling in <code>resolveTranscriptPolicy</code> so Anthropic thinking signatures are preserved while non-Anthropic providers remain unchanged. (#32813) thanks @Sid-Qin.</li>
<li>Agents/schema cleaning: detect Venice + Grok model IDs as xAI-proxied targets so unsupported JSON Schema keywords are stripped before requests, preventing Venice/Grok <code>Invalid arguments</code> failures. (openclaw#35355) thanks @Sid-Qin.</li>
<li>Skills/native command deduplication: centralize skill command dedupe by canonical <code>skillName</code> in <code>listSkillCommandsForAgents</code> so duplicate suffixed variants (for example <code>_2</code>) are no longer surfaced across interfaces outside Discord. (#27521) thanks @shivama205.</li>
<li>Agents/xAI tool-call argument decoding: decode HTML-entity encoded xAI/Grok tool-call argument values (<code>&amp;</code>, <code>&quot;</code>, <code>&lt;</code>, <code>&gt;</code>, numeric entities) before tool execution so commands with shell operators and quotes no longer fail with parse errors. (#35276) Thanks @Sid-Qin.</li>
<li>Linux/WSL2 daemon install hardening: add regression coverage for WSL environment detection, WSL-specific systemd guidance, and <code>systemctl --user is-enabled</code> failure paths so WSL2/headless onboarding keeps treating bus-unavailable probes as non-fatal while preserving real permission errors. Related: #36495. Thanks @vincentkoc.</li>
<li>Linux/systemd status and degraded-session handling: treat degraded-but-reachable <code>systemctl --user status</code> results as available, preserve early errors for truly unavailable user-bus cases, and report externally managed running services as running instead of <code>not installed</code>. Thanks @vincentkoc.</li>
<li>Agents/thinking-tag promotion hardening: guard <code>promoteThinkingTagsToBlocks</code> against malformed assistant content entries (<code>null</code>/<code>undefined</code>) before <code>block.type</code> reads so malformed provider payloads no longer crash session processing while preserving pass-through behavior. (#35143) thanks @Sid-Qin.</li>
<li>Gateway/Control UI version reporting: align runtime and browser client version metadata to avoid <code>dev</code> placeholders, wait for bootstrap version before first UI websocket connect, and only forward bootstrap <code>serverVersion</code> to same-origin gateway targets to prevent cross-target version leakage. (from #35230, #30928, #33928) Thanks @Sid-Qin, @joelnishanth, and @MoerAI.</li>
<li>Control UI/markdown parser crash fallback: catch <code>marked.parse()</code> failures and fall back to escaped plain-text <code><pre></code> rendering so malformed recursive markdown no longer crashes Control UI session rendering on load. (#36445) Thanks @BinHPdev.</li>
<li>Control UI/markdown fallback regression coverage: add explicit regression assertions for parser-error fallback behavior so malformed markdown no longer risks reintroducing hard-crash rendering paths in future markdown/parser upgrades. (#36445) Thanks @BinHPdev.</li>
<li>Web UI/config form: treat <code>additionalProperties: true</code> object schemas as editable map entries instead of unsupported fields so Accounts-style maps stay editable in form mode. (#35380, supersedes #32072) Thanks @stakeswky and @liuxiaopai-ai.</li>
<li>Feishu/streaming card delivery synthesis: unify snapshot and delta streaming merge semantics, apply overlap-aware final merge, suppress duplicate final text delivery (including text+media final packets), prefer topic-thread <code>message.reply</code> routing when a reply target exists, and tune card print cadence to avoid duplicate incremental rendering. (from #33245, #32896, #33840) Thanks @rexl2018, @kcinzgg, and @aerelune.</li>
<li>Feishu/group mention detection: carry startup-probed bot display names through monitor dispatch so <code>requireMention</code> checks compare against current bot identity instead of stale config names, fixing missed <code>@bot</code> handling in groups while preserving multi-bot false-positive guards. (#36317, #34271) Thanks @liuxiaopai-ai.</li>
<li>Security/dependency audit: patch transitive Hono vulnerabilities by pinning <code>hono</code> to <code>4.12.5</code> and <code>@hono/node-server</code> to <code>1.19.10</code> in production resolution paths. Thanks @shakkernerd.</li>
<li>Security/dependency audit: bump <code>tar</code> to <code>7.5.10</code> (from <code>7.5.9</code>) to address the high-severity hardlink path traversal advisory (<code>GHSA-qffp-2rhf-9h96</code>). Thanks @shakkernerd.</li>
<li>Cron/announce delivery robustness: bypass pending-descendant announce guards for cron completion sends, ensure named-agent announce routes have outbound session entries, and fall back to direct delivery only when an announce send was actually attempted and failed. (from #35185, #32443, #34987) Thanks @Sid-Qin, @scoootscooob, and @bmendonca3.</li>
<li>Cron/announce best-effort fallback: run direct outbound fallback after attempted announce failures even when delivery is configured as best-effort, so Telegram cron sends are not left as attempted-but-undelivered after <code>cron announce delivery failed</code> warnings.</li>
<li>Auto-reply/system events: restore runtime system events to the message timeline (<code>System:</code> lines), preserve think-hint parsing with prepended events, and carry events into deferred followup/collect/steer-backlog prompts to keep cache behavior stable without dropping queued metadata. (#34794) Thanks @anisoptera.</li>
<li>Security/audit account handling: avoid prototype-chain account IDs in audit validation by using own-property checks for <code>accounts</code>. (#34982) Thanks @HOYALIM.</li>
<li>Cron/restart catch-up semantics: replay interrupted recurring jobs and missed immediate cron slots on startup without replaying interrupted one-shot jobs, with guarded missed-slot probing to avoid malformed-schedule startup aborts and duplicate-trigger drift after restart. (from #34466, #34896, #34625, #33206) Thanks @dunamismax, @dsantoreis, @Octane0411, and @Sid-Qin.</li>
<li>Venice/provider onboarding hardening: align per-model Venice completion-token limits with discovery metadata, clamp untrusted discovery values to safe bounds, sync the static Venice fallback catalog with current live model metadata, and disable tool wiring for Venice models that do not support function calling so default Venice setups no longer fail with <code>max_completion_tokens</code> or unsupported-tools 400s. Fixes #38168. Thanks @Sid-Qin, @powermaster888 and @vincentkoc.</li>
<li>Agents/session usage tracking: preserve accumulated usage metadata on embedded Pi runner error exits so failed turns still update session <code>totalTokens</code> from real usage instead of stale prior values. (#34275) thanks @RealKai42.</li>
<li>Slack/reaction thread context routing: carry Slack native DM channel IDs through inbound context and threading tool resolution so reaction targets resolve consistently for DM <code>To=user:*</code> sessions (including <code>toolContext.currentChannelId</code> fallback behavior). (from #34831; overlaps #34440, #34502, #34483, #32754) Thanks @dunamismax.</li>
<li>Subagents/announce completion scoping: scope nested direct-child completion aggregation to the current requester run window, harden frozen completion capture for deterministic descendant synthesis, and route completion announce delivery through parent-agent announce turns with provenance-aware internal events. (#35080) Thanks @tyler6204.</li>
<li>Nodes/system.run approval hardening: use explicit argv-mutation signaling when regenerating prepared <code>rawCommand</code>, and cover the <code>system.run.prepare -> system.run</code> handoff so direct PATH-based <code>nodes.run</code> commands no longer fail with <code>rawCommand does not match command</code>. (#33137) thanks @Sid-Qin.</li>
<li>Models/custom provider headers: propagate <code>models.providers.<name>.headers</code> across inline, fallback, and registry-found model resolution so header-authenticated proxies consistently receive configured request headers. (#27490) thanks @Sid-Qin.</li>
<li>Ollama/remote provider auth fallback: synthesize a local runtime auth key for explicitly configured <code>models.providers.ollama</code> entries that omit <code>apiKey</code>, so remote Ollama endpoints run without requiring manual dummy-key setup while preserving env/profile/config key precedence and missing-config failures. (#11283) Thanks @cpreecs.</li>
<li>Ollama/custom provider headers: forward resolved model headers into native Ollama stream requests so header-authenticated Ollama proxies receive configured request headers. (#24337) thanks @echoVic.</li>
<li>Ollama/compaction and summarization: register custom <code>api: "ollama"</code> handling for compaction, branch-style internal summarization, and TTS text summarization on current <code>main</code>, so native Ollama models no longer fail with <code>No API provider registered for api: ollama</code> outside the main run loop. Thanks @JaviLib.</li>
<li>Daemon/systemd install robustness: treat <code>systemctl --user is-enabled</code> exit-code-4 <code>not-found</code> responses as not-enabled by combining stderr/stdout detail parsing, so Ubuntu fresh installs no longer fail with <code>systemctl is-enabled unavailable</code>. (#33634) Thanks @Yuandiaodiaodiao.</li>
<li>Slack/system-event session routing: resolve reaction/member/pin/interaction system-event session keys through channel/account bindings (with sender-aware DM routing) so inbound Slack events target the correct agent session in multi-account setups instead of defaulting to <code>agent:main</code>. (#34045) Thanks @paulomcg, @daht-mad and @vincentkoc.</li>
<li>Slack/native streaming markdown conversion: stop pre-normalizing text passed to Slack native <code>markdown_text</code> in streaming start/append/stop paths to prevent Markdown style corruption from double conversion. (#34931)</li>
<li>Gateway/HTTP tools invoke media compatibility: preserve raw media payload access for direct <code>/tools/invoke</code> clients by allowing media <code>nodes</code> invoke commands only in HTTP tool context, while keeping agent-context media invoke blocking to prevent base64 prompt bloat. (#34365) Thanks @obviyus.</li>
<li>Security/archive ZIP hardening: extract ZIP entries via same-directory temp files plus atomic rename, then re-open and reject post-rename hardlink alias races outside the destination root.</li>
<li>Agents/Nodes media outputs: add dedicated <code>photos_latest</code> action handling, block media-returning <code>nodes invoke</code> commands, keep metadata-only <code>camera.list</code> invoke allowed, and normalize empty <code>photos_latest</code> results to a consistent response shape to prevent base64 context bloat. (#34332) Thanks @obviyus.</li>
<li>TUI/session-key canonicalization: normalize <code>openclaw tui --session</code> values to lowercase so uppercase session names no longer drop real-time streaming updates due to gateway/TUI key mismatches. (#33866, #34013) thanks @lynnzc.</li>
<li>iMessage/echo loop hardening: strip leaked assistant-internal scaffolding from outbound iMessage replies, drop reflected assistant-content messages before they re-enter inbound processing, extend echo-cache text retention for delayed reflections, and suppress repeated loop traffic before it amplifies into queue overflow. (#33295) Thanks @joelnishanth.</li>
<li>Skills/workspace boundary hardening: reject workspace and extra-dir skill roots or <code>SKILL.md</code> files whose realpath escapes the configured source root, and skip syncing those escaped skills into sandbox workspaces.</li>
<li>Outbound/send config threading: pass resolved SecretRef config through outbound adapters and helper send paths so send flows do not reload unresolved runtime config. (#33987) Thanks @joshavant.</li>
<li>gateway: harden shared auth resolution across systemd, discord, and node host (#39241) Thanks @joshavant.</li>
<li>Secrets/models.json persistence hardening: keep SecretRef-managed api keys + headers from persisting in generated models.json, expand audit/apply coverage, and harden marker handling/serialization. (#38955) Thanks @joshavant.</li>
<li>Sessions/subagent attachments: remove <code>attachments[].content.maxLength</code> from <code>sessions_spawn</code> schema to avoid llama.cpp GBNF repetition overflow, and preflight UTF-8 byte size before buffer allocation while keeping runtime file-size enforcement unchanged. (#33648) Thanks @anisoptera.</li>
<li>Runtime/tool-state stability: recover from dangling Anthropic <code>tool_use</code> after compaction, serialize long-running Discord handler runs without blocking new inbound events, and prevent stale busy snapshots from suppressing stuck-channel recovery. (from #33630, #33583) Thanks @kevinWangSheng and @theotarr.</li>
<li>ACP/Discord startup hardening: clean up stuck ACP worker children on gateway restart, unbind stale ACP thread bindings during Discord startup reconciliation, and add per-thread listener watchdog timeouts so wedged turns cannot block later messages. (#33699) Thanks @dutifulbob.</li>
<li>Extensions/media local-root propagation: consistently forward <code>mediaLocalRoots</code> through extension <code>sendMedia</code> adapters (Google Chat, Slack, iMessage, Signal, WhatsApp), preserving non-local media behavior while restoring local attachment resolution from configured roots. Synthesis of #33581, #33545, #33540, #33536, #33528. Thanks @bmendonca3.</li>
<li>Gateway/plugin HTTP auth hardening: require gateway auth when any overlapping matched route needs it, block mixed-auth fallthrough at dispatch, and reject mixed-auth exact/prefix route overlaps during plugin registration.</li>
<li>Feishu/video media send contract: keep mp4-like outbound payloads on <code>msg_type: "media"</code> (including reply and reply-in-thread paths) so videos render as media instead of degrading to file-link behavior, while preserving existing non-video file subtype handling. (from #33720, #33808, #33678) Thanks @polooooo, @dingjianrui, and @kevinWangSheng.</li>
<li>Gateway/security default response headers: add <code>Permissions-Policy: camera=(), microphone=(), geolocation=()</code> to baseline gateway HTTP security headers for all responses. (#30186) thanks @habakan.</li>
<li>Plugins/startup loading: lazily initialize plugin runtime, split startup-critical plugin SDK imports into <code>openclaw/plugin-sdk/core</code> and <code>openclaw/plugin-sdk/telegram</code>, and preserve <code>api.runtime</code> reflection semantics for plugin compatibility. (#28620) thanks @hmemcpy.</li>
<li>Plugins/startup performance: reduce bursty plugin discovery/manifest overhead with short in-process caches, skip importing bundled memory plugins that are disabled by slot selection, and speed legacy root <code>openclaw/plugin-sdk</code> compatibility via runtime root-alias routing while preserving backward compatibility. Thanks @gumadeiras.</li>
<li>Build/lazy runtime boundaries: replace ineffective dynamic import sites with dedicated lazy runtime boundaries across Slack slash handling, Telegram audit, CLI send deps, memory fallback, and outbound delivery paths while preserving behavior. (#33690) thanks @gumadeiras.</li>
<li>Gateway/password CLI hardening: add <code>openclaw gateway run --password-file</code>, warn when inline <code>--password</code> is used because it can leak via process listings, and document env/file-backed password input as the preferred startup path. Fixes #27948. Thanks @vibewrk and @vincentkoc.</li>
<li>Config/heartbeat legacy-path handling: auto-migrate top-level <code>heartbeat</code> into <code>agents.defaults.heartbeat</code> (with merge semantics that preserve explicit defaults), and keep startup failures on non-migratable legacy entries in the detailed invalid-config path instead of generic migration-failed errors. (#32706) thanks @xiwan.</li>
<li>Plugins/SDK subpath parity: expand plugin SDK subpaths across bundled channels/extensions (Discord, Slack, Signal, iMessage, WhatsApp, LINE, and bundled companion plugins), with build/export/type/runtime wiring so scoped imports resolve consistently in source and dist while preserving compatibility. (#33737) thanks @gumadeiras.</li>
<li>Google/Gemini Flash model selection: switch built-in <code>gemini-flash</code> defaults and docs/examples from the nonexistent <code>google/gemini-3.1-flash-preview</code> ID to the working <code>google/gemini-3-flash-preview</code>, while normalizing legacy OpenClaw config that still uses the old Flash 3.1 alias.</li>
<li>Plugins/bundled scoped-import migration: migrate bundled plugins from monolithic <code>openclaw/plugin-sdk</code> imports to scoped subpaths (or <code>openclaw/plugin-sdk/core</code>) across registration and startup-sensitive runtime files, add CI/release guardrails to prevent regressions, and keep root <code>openclaw/plugin-sdk</code> support for external/community plugins. Thanks @gumadeiras.</li>
<li>Routing/session duplicate suppression synthesis: align shared session delivery-context inheritance, channel-paired route-field merges, and reply-surface target matching so dmScope=main turns avoid cross-surface duplicate replies while thread-aware forwarding keeps intended routing semantics. (from #33629, #26889, #17337, #33250) Thanks @Yuandiaodiaodiao, @kevinwildenradt, @Glucksberg, and @bmendonca3.</li>
<li>Routing/legacy session route inheritance: preserve external route metadata inheritance for legacy channel session keys (<code>agent:<agent>:<channel>:<peer></code> and <code>...:thread:<id></code>) so <code>chat.send</code> does not incorrectly fall back to webchat when valid delivery context exists. Follow-up to #33786.</li>
<li>Routing/legacy route guard tightening: require legacy session-key channel hints to match the saved delivery channel before inheriting external routing metadata, preventing custom namespaced keys like <code>agent:<agent>:work:<ticket></code> from inheriting stale non-webchat routes.</li>
<li>Gateway/internal client routing continuity: prevent webchat/TUI/UI turns from inheriting stale external reply routes by requiring explicit <code>deliver: true</code> for external delivery, keeping main-session external inheritance scoped to non-Webchat/UI clients, and honoring configured <code>session.mainKey</code> when identifying main-session continuity. (from #35321, #34635, #35356) Thanks @alexyyyander and @Octane0411.</li>
<li>Security/auth labels: remove token and API-key snippets from user-facing auth status labels so <code>/status</code> and <code>/models</code> do not expose credential fragments. (#33262) thanks @cu1ch3n.</li>
<li>Models/MiniMax portal vision routing: add <code>MiniMax-VL-01</code> to the <code>minimax-portal</code> provider, route portal image understanding through the MiniMax VLM endpoint, and align media auto-selection plus Telegram sticker description with the shared portal image provider path. (#33953) Thanks @tars90percent.</li>
<li>Auth/credential semantics: align profile eligibility + probe diagnostics with SecretRef/expiry rules and harden browser download atomic writes. (#33733) thanks @joshavant.</li>
<li>Security/audit denyCommands guidance: suggest likely exact node command IDs for unknown <code>gateway.nodes.denyCommands</code> entries so ineffective denylist entries are easier to correct. (#29713) thanks @liquidhorizon88-bot.</li>
<li>Agents/overload failover handling: classify overloaded provider failures separately from rate limits/status timeouts, add short overload backoff before retry/failover, record overloaded prompt/assistant failures as transient auth-profile cooldowns (with probeable same-provider fallback) instead of treating them like persistent auth/billing failures, and keep one-shot cron retry classification aligned so overloaded fallback summaries still count as transient retries.</li>
<li>Docs/security hardening guidance: document Docker <code>DOCKER-USER</code> + UFW policy and add cross-linking from Docker install docs for VPS/public-host setups. (#27613) thanks @dorukardahan.</li>
<li>Docs/security threat-model links: replace relative <code>.md</code> links with Mintlify-compatible root-relative routes in security docs to prevent broken internal navigation. (#27698) thanks @clawdoo.</li>
<li>Plugins/Update integrity drift: avoid false integrity drift prompts when updating npm-installed plugins from unpinned specs, while keeping drift checks for exact pinned versions. (#37179) Thanks @vincentkoc.</li>
<li>iOS/Voice timing safety: guard system speech start/finish callbacks to the active utterance to avoid misattributed start events during rapid stop/restart cycles. (#33304) thanks @mbelinky; original implementation direction by @ngutman.</li>
<li>Gateway/chat.send command scopes: require <code>operator.admin</code> for persistent <code>/config set|unset</code> writes routed through gateway chat clients while keeping <code>/config show</code> available to normal write-scoped operator clients, preserving messaging-channel config command behavior without widening RPC write scope into admin config mutation. Thanks @tdjackey for reporting.</li>
<li>iOS/Talk incremental speech pacing: allow long punctuation-free assistant chunks to start speaking at safe whitespace boundaries so voice responses begin sooner instead of waiting for terminal punctuation. (#33305) thanks @mbelinky; original implementation by @ngutman.</li>
<li>iOS/Watch reply reliability: make watch session activation waiters robust under concurrent requests so status/send calls no longer hang intermittently, and align delegate callbacks with Swift 6 actor safety. (#33306) thanks @mbelinky; original implementation by @Rocuts.</li>
<li>Docs/tool-loop detection config keys: align <code>docs/tools/loop-detection.md</code> examples and field names with the current <code>tools.loopDetection</code> schema to prevent copy-paste validation failures from outdated keys. (#33182) Thanks @Mylszd.</li>
<li>Gateway/session agent discovery: include disk-scanned agent IDs in <code>listConfiguredAgentIds</code> even when <code>agents.list</code> is configured, so disk-only/ACP agent sessions remain visible in gateway session aggregation and listings. (#32831) thanks @Sid-Qin.</li>
<li>Discord/inbound debouncer: skip bot-own MESSAGE_CREATE events before they reach the debounce queue to avoid self-triggered slowdowns in busy servers. Thanks @thewilloftheshadow.</li>
<li>Discord/Agent-scoped media roots: pass <code>mediaLocalRoots</code> through Discord monitor reply delivery (message + component interaction paths) so local media attachments honor per-agent workspace roots instead of falling back to default global roots. Thanks @thewilloftheshadow.</li>
<li>Discord/slash command handling: intercept text-based slash commands in channels, register plugin commands as native, and send fallback acknowledgments for empty slash runs so interactions do not hang. Thanks @thewilloftheshadow.</li>
<li>Discord/thread session lifecycle: reset thread-scoped sessions when a thread is archived so reopening a thread starts fresh without deleting transcript history. Thanks @thewilloftheshadow.</li>
<li>Discord/presence defaults: send an online presence update on ready when no custom presence is configured so bots no longer appear offline by default. Thanks @thewilloftheshadow.</li>
<li>Discord/typing cleanup: stop typing indicators after silent/NO_REPLY runs by marking the run complete before dispatch idle cleanup. Thanks @thewilloftheshadow.</li>
<li>ACP/sandbox spawn parity: block <code>/acp spawn</code> from sandboxed requester sessions with the same host-runtime guard already enforced for <code>sessions_spawn({ runtime: "acp" })</code>, preserving non-sandbox ACP flows while closing the command-path policy gap. Thanks @patte.</li>
<li>Discord/config SecretRef typing: align Discord account token config typing with SecretInput so SecretRef tokens typecheck. (#32490) Thanks @scoootscooob.</li>
<li>Discord/voice messages: request upload slots with JSON fetch calls so voice message uploads no longer fail with content-type errors. Thanks @thewilloftheshadow.</li>
<li>Discord/voice decoder fallback: drop the native Opus dependency and use opusscript for voice decoding to avoid native-opus installs. Thanks @thewilloftheshadow.</li>
<li>Discord/auto presence health signal: add runtime availability-driven presence updates plus connected-state reporting to improve health monitoring and operator visibility. (#33277) Thanks @thewilloftheshadow.</li>
<li>HEIC image inputs: accept HEIC/HEIF <code>input_image</code> sources in Gateway HTTP APIs, normalize them to JPEG before provider delivery, and document the expanded default MIME allowlist. Thanks @vincentkoc.</li>
<li>Gateway/HEIC input follow-up: keep non-HEIC <code>input_image</code> MIME handling unchanged, make HEIC tests hermetic, and enforce chat-completions <code>maxTotalImageBytes</code> against post-normalization image payload size. Thanks @vincentkoc.</li>
<li>Telegram/draft-stream boundary stability: materialize DM draft previews at assistant-message/tool boundaries, serialize lane-boundary callbacks before final delivery, and scope preview cleanup to the active preview so multi-step Telegram streams no longer lose, overwrite, or leave stale preview bubbles. (#33842) Thanks @ngutman.</li>
<li>Telegram/DM draft finalization reliability: require verified final-text draft emission before treating preview finalization as delivered, and fall back to normal payload send when final draft delivery is not confirmed (preventing missing final responses and preserving media/button delivery). (#32118) Thanks @OpenCils.</li>
<li>Telegram/DM draft final delivery: materialize text-only <code>sendMessageDraft</code> previews into one permanent final message and skip duplicate final payload sends, while preserving fallback behavior when materialization fails. (#34318) Thanks @Brotherinlaw-13.</li>
<li>Telegram/DM draft duplicate display: clear stale DM draft previews after materializing the real final message, including threadless fallback when DM topic lookup fails, so partial streaming no longer briefly shows duplicate replies. (#36746) Thanks @joelnishanth.</li>
<li>Telegram/draft preview boundary + silent-token reliability: stabilize answer-lane message boundaries across late-partial/message-start races, preserve/reset finalized preview state at the correct boundaries, and suppress <code>NO_REPLY</code> lead-fragment leaks without broad heartbeat-prefix false positives. (#33169) Thanks @obviyus.</li>
<li>Telegram/native commands <code>commands.allowFrom</code> precedence: make native Telegram commands honor <code>commands.allowFrom</code> as the command-specific authorization source, including group chats, instead of falling back to channel sender allowlists. (#28216) Thanks @toolsbybuddy and @vincentkoc.</li>
<li>Telegram/<code>groupAllowFrom</code> sender-ID validation: restore sender-only runtime validation so negative chat/group IDs remain invalid entries instead of appearing accepted while still being unable to authorize group access. (#37134) Thanks @qiuyuemartin-max and @vincentkoc.</li>
<li>Telegram/native group command auth: authorize native commands in groups and forum topics against <code>groupAllowFrom</code> and per-group/topic sender overrides, while keeping auth rejection replies in the originating topic thread. (#39267) Thanks @edwluo.</li>
<li>Telegram/named-account DMs: restore non-default-account DM routing when a named Telegram account falls back to the default agent by keeping groups fail-closed but deriving a per-account session key for DMs, including identity-link canonicalization and regression coverage for account isolation. (from #32426; fixes #32351) Thanks @chengzhichao-xydt.</li>
<li>Discord/audit wildcard warnings: ignore "\*" wildcard keys when counting unresolved guild channels so doctor/status no longer warns on allow-all configs. (#33125) Thanks @thewilloftheshadow.</li>
<li>Discord/channel resolution: default bare numeric recipients to channels, harden allowlist numeric ID handling with safe fallbacks, and avoid inbound WS heartbeat stalls. (#33142) Thanks @thewilloftheshadow.</li>
<li>Discord/chunk delivery reliability: preserve chunk ordering when using a REST client and retry chunk sends on 429/5xx using account retry settings. (#33226) Thanks @thewilloftheshadow.</li>
<li>Discord/mention handling: add id-based mention formatting + cached rewrites, resolve inbound mentions to display names, and add optional ignoreOtherMentions gating (excluding @everyone/@here). (#33224) Thanks @thewilloftheshadow.</li>
<li>Discord/media SSRF allowlist: allow Discord CDN hostnames (including wildcard domains) in inbound media SSRF policy to prevent proxy/VPN fake-ip blocks. (#33275) Thanks @thewilloftheshadow.</li>
<li>Telegram/device pairing notifications: auto-arm one-shot notify on <code>/pair qr</code>, auto-ping on new pairing requests, and add manual fallback via <code>/pair approve latest</code> if the ping does not arrive. (#33299) thanks @mbelinky.</li>
<li>Exec heartbeat routing: scope exec-triggered heartbeat wakes to agent session keys so unrelated agents are no longer awakened by exec events, while preserving legacy unscoped behavior for non-canonical session keys. (#32724) thanks @altaywtf</li>
<li>macOS/Tailscale remote gateway discovery: add a Tailscale Serve fallback peer probe path (<code>wss://<peer>.ts.net</code>) when Bonjour and wide-area DNS-SD discovery return no gateways, and refresh both discovery paths from macOS onboarding. (#32860) Thanks @ngutman.</li>
<li>iOS/Gateway keychain hardening: move gateway metadata and TLS fingerprints to device keychain storage with safer migration behavior and rollback-safe writes to reduce credential loss risk during upgrades. (#33029) thanks @mbelinky.</li>
<li>iOS/Concurrency stability: replace risky shared-state access in camera and gateway connection paths with lock-protected access patterns to reduce crash risk under load. (#33241) thanks @mbelinky.</li>
<li>iOS/Security guardrails: limit production API-key sourcing to app config and make deep-link confirmation prompts safer by coalescing queued requests instead of silently dropping them. (#33031) thanks @mbelinky.</li>
<li>iOS/TTS playback fallback: keep voice playback resilient by switching from PCM to MP3 when provider format support is unavailable, while avoiding sticky fallback on generic local playback errors. (#33032) thanks @mbelinky.</li>
<li>Plugin outbound/text-only adapter compatibility: allow direct-delivery channel plugins that only implement <code>sendText</code> (without <code>sendMedia</code>) to remain outbound-capable, gracefully fall back to text delivery for media payloads when <code>sendMedia</code> is absent, and fail explicitly for media-only payloads with no text fallback. (#32788) thanks @liuxiaopai-ai.</li>
<li>Telegram/multi-account default routing clarity: warn only for ambiguous (2+) account setups without an explicit default, add <code>openclaw doctor</code> warnings for missing/invalid multi-account defaults across channels, and document explicit-default guidance for channel routing and Telegram config. (#32544) thanks @Sid-Qin.</li>
<li>Telegram/plugin outbound hook parity: run <code>message_sending</code> + <code>message_sent</code> in Telegram reply delivery, include reply-path hook metadata (<code>mediaUrls</code>, <code>threadId</code>), and report <code>message_sent.success=false</code> when hooks blank text and no outbound message is delivered. (#32649) Thanks @KimGLee.</li>
<li>CLI/Coding-agent reliability: switch default <code>claude-cli</code> non-interactive args to <code>--permission-mode bypassPermissions</code>, auto-normalize legacy <code>--dangerously-skip-permissions</code> backend overrides to the modern permission-mode form, align coding-agent + live-test docs with the non-PTY Claude path, and emit session system-event heartbeat notices when CLI watchdog no-output timeouts terminate runs. (#28610, #31149, #34055). Thanks @niceysam, @cryptomaltese and @vincentkoc.</li>
<li>Gateway/OpenAI chat completions: parse active-turn <code>image_url</code> content parts (including parameterized data URIs and guarded URL sources), forward them as multimodal <code>images</code>, accept image-only user turns, enforce per-request image-part/byte budgets, default URL-based image fetches to disabled unless explicitly enabled by config, and redact image base64 data in cache-trace/provider payload diagnostics. (#17685) Thanks @vincentkoc</li>
<li>ACP/ACPX session bootstrap: retry with <code>sessions new</code> when <code>sessions ensure</code> returns no session identifiers so ACP spawns avoid <code>NO_SESSION</code>/<code>ACP_TURN_FAILED</code> failures on affected agents. (#28786, #31338, #34055). Thanks @Sid-Qin and @vincentkoc.</li>
<li>ACP/sessions_spawn parent stream visibility: add <code>streamTo: "parent"</code> for <code>runtime: "acp"</code> to forward initial child-run progress/no-output/completion updates back into the requester session as system events (instead of direct child delivery), and emit a tail-able session-scoped relay log (<code><sessionId>.acp-stream.jsonl</code>, returned as <code>streamLogPath</code> when available), improving orchestrator visibility for blocked or long-running harness turns. (#34310, #29909; reopened from #34055). Thanks @vincentkoc.</li>
<li>Agents/bootstrap truncation warning handling: unify bootstrap budget/truncation analysis across embedded + CLI runtime, <code>/context</code>, and <code>openclaw doctor</code>; add <code>agents.defaults.bootstrapPromptTruncationWarning</code> (<code>off|once|always</code>, default <code>once</code>) and persist warning-signature metadata so truncation warnings are consistent and deduped across turns. (#32769) Thanks @gumadeiras.</li>
<li>Agents/Skills runtime loading: propagate run config into embedded attempt and compaction skill-entry loading so explicitly enabled bundled companion skills are discovered consistently when skill snapshots do not already provide resolved entries. Thanks @gumadeiras.</li>
<li>Agents/Session startup date grounding: substitute <code>YYYY-MM-DD</code> placeholders in startup/post-compaction AGENTS context and append runtime current-time lines for <code>/new</code> and <code>/reset</code> prompts so daily-memory references resolve correctly. (#32381) Thanks @chengzhichao-xydt.</li>
<li>Agents/Compaction template heading alignment: update AGENTS template section names to <code>Session Startup</code>/<code>Red Lines</code> and keep legacy <code>Every Session</code>/<code>Safety</code> fallback extraction so post-compaction context remains intact across template versions. (#25098) thanks @echoVic.</li>
<li>Agents/Compaction continuity: expand staged-summary merge instructions to preserve active task status, batch progress, latest user request, and follow-up commitments so compaction handoffs retain in-flight work context. (#8903) thanks @joetomasone.</li>
<li>Agents/Compaction safeguard structure hardening: require exact fallback summary headings, sanitize untrusted compaction instruction text before prompt embedding, and keep structured sections when preserving all turns. (#25555) thanks @rodrigouroz.</li>
<li>Gateway/status self version reporting: make Gateway self version in <code>openclaw status</code> prefer runtime <code>VERSION</code> (while preserving explicit <code>OPENCLAW_VERSION</code> override), preventing stale post-upgrade app version output. (#32655) thanks @liuxiaopai-ai.</li>
<li>Memory/QMD index isolation: set <code>QMD_CONFIG_DIR</code> alongside <code>XDG_CONFIG_HOME</code> so QMD config state stays per-agent despite upstream XDG handling bugs, preventing cross-agent collection indexing and excess disk/CPU usage. (#27028) thanks @HenryLoenwind.</li>
<li>Memory/QMD collection safety: stop destructive collection rebinds when QMD <code>collection list</code> only reports names without path metadata, preventing <code>memory search</code> from dropping existing collections if re-add fails. (#36870) Thanks @Adnannnnnnna.</li>
<li>Memory/QMD duplicate-document recovery: detect <code>UNIQUE constraint failed: documents.collection, documents.path</code> update failures, rebuild managed collections once, and retry update so periodic QMD syncs recover instead of failing every run; includes regression coverage to avoid over-matching unrelated unique constraints. (#27649) Thanks @MiscMich.</li>
<li>Memory/local embedding initialization hardening: add regression coverage for transient initialization retry and mixed <code>embedQuery</code> + <code>embedBatch</code> concurrent startup to lock single-flight initialization behavior. (#15639) thanks @SubtleSpark.</li>
<li>CLI/Coding-agent reliability: switch default <code>claude-cli</code> non-interactive args to <code>--permission-mode bypassPermissions</code>, auto-normalize legacy <code>--dangerously-skip-permissions</code> backend overrides to the modern permission-mode form, align coding-agent + live-test docs with the non-PTY Claude path, and emit session system-event heartbeat notices when CLI watchdog no-output timeouts terminate runs. Related to #28261. Landed from contributor PRs #28610 and #31149. Thanks @niceysam, @cryptomaltese and @vincentkoc.</li>
<li>ACP/ACPX session bootstrap: retry with <code>sessions new</code> when <code>sessions ensure</code> returns no session identifiers so ACP spawns avoid <code>NO_SESSION</code>/<code>ACP_TURN_FAILED</code> failures on affected agents. Related to #28786. Landed from contributor PR #31338. Thanks @Sid-Qin and @vincentkoc.</li>
<li>LINE/auth boundary hardening synthesis: enforce strict LINE webhook authn/z boundary semantics across pairing-store account scoping, DM/group allowlist separation, fail-closed webhook auth/runtime behavior, and replay/duplication controls (including in-flight replay reservation and post-success dedupe marking). (from #26701, #26683, #25978, #17593, #16619, #31990, #26047, #30584, #18777) Thanks @bmendonca3, @davidahmann, @harshang03, @haosenwang1018, @liuxiaopai-ai, @coygeek, and @Takhoffman.</li>
<li>LINE/media download synthesis: fix file-media download handling and M4A audio classification across overlapping LINE regressions. (from #26386, #27761, #27787, #29509, #29755, #29776, #29785, #32240) Thanks @kevinWangSheng, @loiie45e, @carrotRakko, @Sid-Qin, @codeafridi, and @bmendonca3.</li>
<li>LINE/context and routing synthesis: fix group/room peer routing and command-authorization context propagation, and keep processing later events in mixed-success webhook batches. (from #21955, #24475, #27035, #28286) Thanks @lailoo, @mcaxtr, @jervyclaw, @Glucksberg, and @Takhoffman.</li>
<li>LINE/status/config/webhook synthesis: fix status false positives from snapshot/config state and accept LINE webhook HEAD probes for compatibility. (from #10487, #25726, #27537, #27908, #31387) Thanks @BlueBirdBack, @stakeswky, @loiie45e, @puritysb, and @mcaxtr.</li>
<li>LINE cleanup/test follow-ups: fold cleanup/test learnings into the synthesis review path while keeping runtime changes focused on regression fixes. (from #17630, #17289) Thanks @Clawborn and @davidahmann.</li>
<li>Mattermost/interactive buttons: add interactive button send/callback support with directory-based channel/user target resolution, and harden callbacks via account-scoped HMAC verification plus sender-scoped DM routing. (#19957) thanks @tonydehnke.</li>
<li>Feishu/groupPolicy legacy alias compatibility: treat legacy <code>groupPolicy: "allowall"</code> as <code>open</code> in both schema parsing and runtime policy checks so intended open-group configs no longer silently drop group messages when <code>groupAllowFrom</code> is empty. (from #36358) Thanks @Sid-Qin.</li>
<li>Mattermost/plugin SDK import policy: replace remaining monolithic <code>openclaw/plugin-sdk</code> imports in Mattermost mention-gating paths/tests with scoped subpaths (<code>openclaw/plugin-sdk/compat</code> and <code>openclaw/plugin-sdk/mattermost</code>) so <code>pnpm check</code> passes <code>lint:plugins:no-monolithic-plugin-sdk-entry-imports</code> on baseline. (#36480) Thanks @Takhoffman.</li>
<li>Telegram/polls: add Telegram poll action support to channel action discovery and tool/CLI poll flows, with multi-account discoverability gated to accounts that can actually execute polls (<code>sendMessage</code> + <code>poll</code>). (#36547) thanks @gumadeiras.</li>
<li>Agents/failover cooldown classification: stop treating generic <code>cooling down</code> text as provider <code>rate_limit</code> so healthy models no longer show false global cooldown/rate-limit warnings while explicit <code>model_cooldown</code> markers still trigger failover. (#32972) thanks @stakeswky.</li>
<li>Agents/failover service-unavailable handling: stop treating bare proxy/CDN <code>service unavailable</code> errors as provider overload while keeping them retryable via the timeout/failover path, so transient outages no longer show false rate-limit warnings or block fallback. (#36646) thanks @jnMetaCode.</li>
<li>Plugins/HTTP route migration diagnostics: rewrite legacy <code>api.registerHttpHandler(...)</code> loader failures into actionable migration guidance so doctor/plugin diagnostics point operators to <code>api.registerHttpRoute(...)</code> or <code>registerPluginHttpRoute(...)</code>. (#36794) Thanks @vincentkoc</li>
<li>Doctor/Heartbeat upgrade diagnostics: warn when heartbeat delivery is configured with an implicit <code>directPolicy</code> so upgrades pin direct/DM behavior explicitly instead of relying on the current default. (#36789) Thanks @vincentkoc.</li>
<li>Agents/current-time UTC anchor: append a machine-readable UTC suffix alongside local <code>Current time:</code> lines in shared cron-style prompt contexts so agents can compare UTC-stamped workspace timestamps without doing timezone math. (#32423) thanks @jriff.</li>
<li>Ollama/local model handling: preserve explicit lower <code>contextWindow</code> / <code>maxTokens</code> overrides during merge refresh, and keep native Ollama streamed replies from surfacing fallback <code>thinking</code> / <code>reasoning</code> text once real content starts streaming. (#39292) Thanks @vincentkoc.</li>
<li>TUI/webchat command-owner scope alignment: treat internal-channel gateway sessions with <code>operator.admin</code> as owner-authorized in command auth, restoring cron/gateway/connector tool access for affected TUI/webchat sessions while keeping external channels on identity-based owner checks. (from #35666, #35673, #35704) Thanks @Naylenv, @Octane0411, and @Sid-Qin.</li>
<li>Discord/inbound timeout isolation: separate inbound worker timeout tracking from listener timeout budgets so queued Discord replies are no longer dropped when listener watchdog windows expire mid-run. (#36602) Thanks @dutifulbob.</li>
<li>Memory/doctor SecretRef handling: treat SecretRef-backed memory-search API keys as configured, and fail embedding setup with explicit unresolved-secret errors instead of crashing. (#36835) Thanks @joshavant.</li>
<li>Memory/flush default prompt: ban timestamped variant filenames during default memory flush runs so durable notes stay in the canonical daily <code>memory/YYYY-MM-DD.md</code> file. (#34951) thanks @zerone0x.</li>
<li>Agents/reply delivery timing: flush embedded Pi block replies before waiting on compaction retries so already-generated assistant replies reach channels before compaction wait completes. (#35489) thanks @Sid-Qin.</li>
<li>Agents/gateway config guidance: stop exposing <code>config.schema</code> through the agent <code>gateway</code> tool, remove prompt/docs guidance that told agents to call it, and keep agents on <code>config.get</code> plus <code>config.patch</code>/<code>config.apply</code> for config changes. (#7382) thanks @kakuteki.</li>
<li>Provider/KiloCode: Keep duplicate models after malformed discovery rows, and strip legacy <code>reasoning_effort</code> when proxy reasoning injection is skipped. (#32352) Thanks @pandemicsyn and @vincentkoc.</li>
<li>Agents/failover: classify periodic provider limit exhaustion text (for example <code>Weekly/Monthly Limit Exhausted</code>) as <code>rate_limit</code> while keeping explicit <code>402 Payment Required</code> variants in billing, so failover continues without misclassifying billing-wrapped quota errors. (#33813) thanks @zhouhe-xydt.</li>
<li>Mattermost/interactive button callbacks: allow external callback base URLs and stop requiring loopback-origin requests so button clicks work when Mattermost reaches the gateway over Tailscale, LAN, or a reverse proxy. (#37543) thanks @mukhtharcm.</li>
<li>Gateway/chat.send route inheritance: keep explicit external delivery for channel-scoped sessions while preventing shared-main and other channel-agnostic webchat sessions from inheriting stale external routes, so Control UI replies stay on webchat without breaking selected channel-target sessions. (#34669) Thanks @vincentkoc.</li>
<li>Telegram/Discord media upload caps: make outbound uploads honor channel <code>mediaMaxMb</code> config, raise Telegram's default media cap to 100MB, and remove MIME fallback limits that kept some Telegram uploads at 16MB. Thanks @vincentkoc.</li>
<li>Skills/nano-banana-pro resolution override: respect explicit <code>--resolution</code> values during image editing and only auto-detect output size from input images when the flag is omitted. (#36880) Thanks @shuofengzhang and @vincentkoc.</li>
<li>Skills/openai-image-gen CLI validation: validate <code>--background</code> and <code>--style</code> inputs early, normalize supported values, and warn when those flags are ignored for incompatible models. (#36762) Thanks @shuofengzhang and @vincentkoc.</li>
<li>Skills/openai-image-gen output formats: validate <code>--output-format</code> values early, normalize aliases like <code>jpg -> jpeg</code>, and warn when the flag is ignored for incompatible models. (#36648) Thanks @shuofengzhang and @vincentkoc.</li>
<li>ACP/skill env isolation: strip skill-injected API keys from ACP harness child-process environments so tools like Codex CLI keep their own auth flow instead of inheriting billed provider keys from active skills. (#36316) Thanks @taw0002 and @vincentkoc.</li>
<li>WhatsApp media upload caps: make outbound media sends and auto-replies honor <code>channels.whatsapp.mediaMaxMb</code> with per-account overrides so inbound and outbound limits use the same channel config. Thanks @vincentkoc.</li>
<li>Windows/Plugin install: when OpenClaw runs on Windows via Bun and <code>npm-cli.js</code> is not colocated with the runtime binary, fall back to <code>npm.cmd</code>/<code>npx.cmd</code> through the existing <code>cmd.exe</code> wrapper so <code>openclaw plugins install</code> no longer fails with <code>spawn EINVAL</code>. (#38056) Thanks @0xlin2023.</li>
<li>Telegram/send retry classification: retry grammY <code>Network request ... failed after N attempts</code> envelopes in send flows without reclassifying plain <code>Network request ... failed!</code> wrappers as transient, restoring the intended retry path while keeping broad send-context message matching tight. (#38056) Thanks @0xlin2023.</li>
<li>Gateway/probes: keep <code>/health</code>, <code>/healthz</code>, <code>/ready</code>, and <code>/readyz</code> reachable when the Control UI is mounted at <code>/</code>, preserve plugin-owned route precedence on those paths, and make <code>/ready</code> and <code>/readyz</code> report channel-backed readiness with startup grace plus <code>503</code> on disconnected managed channels, while <code>/health</code> and <code>/healthz</code> stay shallow liveness probes. (#18446) Thanks @vibecodooor, @mahsumaktas, and @vincentkoc.</li>
<li>Feishu/media downloads: drop invalid timeout fields from SDK method calls now that client-level <code>httpTimeoutMs</code> applies to requests. (#38267) Thanks @ant1eicher and @thewilloftheshadow.</li>
<li>PI embedded runner/Feishu docs: propagate sender identity into embedded attempts so Feishu doc auto-grant restores requester access for embedded-runner executions. (#32915) thanks @cszhouwei.</li>
<li>Agents/usage normalization: normalize missing or partial assistant usage snapshots before compaction accounting so <code>openclaw agent --json</code> no longer crashes when provider payloads omit <code>totalTokens</code> or related usage fields. (#34977) thanks @sp-hk2ldn.</li>
<li>Venice/default model refresh: switch the built-in Venice default to <code>kimi-k2-5</code>, update onboarding aliasing, and refresh Venice provider docs/recommendations to match the current private and anonymized catalog. (from #12964) Fixes #20156. Thanks @sabrinaaquino and @vincentkoc.</li>
<li>Agents/skill API write pacing: add a global prompt guardrail that treats skill-driven external API writes as rate-limited by default, so runners prefer batched writes, avoid tight request loops, and respect <code>429</code>/<code>Retry-After</code>. Thanks @vincentkoc.</li>
<li>Google Chat/multi-account webhook auth fallback: when <code>channels.googlechat.accounts.default</code> carries shared webhook audience/path settings (for example after config normalization), inherit those defaults for named accounts while preserving top-level and per-account overrides, so inbound webhook verification no longer fails silently for named accounts missing duplicated audience fields. Fixes #38369.</li>
<li>Models/tool probing: raise the tool-capability probe budget from 32 to 256 tokens so reasoning models that spend tokens on thinking before returning a required tool call are less likely to be misclassified as not supporting tools. (#7521) Thanks @jakobdylanc.</li>
<li>Gateway/transient network classification: treat wrapped <code>...: fetch failed</code> transport messages as transient while avoiding broad matches like <code>Web fetch failed (404): ...</code>, preventing Discord reconnect wrappers from crashing the gateway without suppressing non-network tool failures. (#38530) Thanks @xinhuagu.</li>
<li>ACP/console silent reply suppression: filter ACP <code>NO_REPLY</code> lead fragments and silent-only finals before <code>openclaw agent</code> logging/delivery so console-backed ACP sessions no longer leak <code>NO</code>/<code>NO_REPLY</code> placeholders. (#38436) Thanks @ql-wade.</li>
<li>Feishu/reply delivery reliability: disable block streaming in Feishu reply options so plain-text auto-render replies are no longer silently dropped before final delivery. (#38258) Thanks @xinhuagu.</li>
<li>Agents/reply MEDIA delivery: normalize local assistant <code>MEDIA:</code> paths before block/final delivery, keep media dedupe aligned with message-tool sends, and contain malformed media normalization failures so generated files send reliably instead of falling back to empty responses. (#38572) Thanks @obviyus.</li>
<li>Sessions/bootstrap cache rollover invalidation: clear cached workspace bootstrap snapshots whenever an existing <code>sessionKey</code> rolls to a new <code>sessionId</code> across auto-reply, command, and isolated cron session resolvers, so <code>AGENTS.md</code>/<code>MEMORY.md</code>/<code>USER.md</code> updates are reloaded after daily, idle, or forced session resets instead of staying stale until gateway restart. (#38494) Thanks @LivingInDrm.</li>
<li>Gateway/Telegram polling health monitor: skip stale-socket restarts for Telegram long-polling channels and thread channel identity through shared health evaluation so polling connections are not restarted on the WebSocket stale-socket heuristic. (#38395) Thanks @ql-wade and @Takhoffman.</li>
<li>Daemon/systemd fresh-install probe: check for OpenClaw's managed user unit before running <code>systemctl --user is-enabled</code>, so first-time Linux installs no longer fail on generic missing-unit probe errors. (#38819) Thanks @adaHubble.</li>
<li>Gateway/container lifecycle: allow <code>openclaw gateway stop</code> to SIGTERM unmanaged gateway listeners and <code>openclaw gateway restart</code> to SIGUSR1 a single unmanaged listener when no service manager is installed, so container and supervisor-based deployments are no longer blocked by <code>service disabled</code> no-op responses. Fixes #36137. Thanks @vincentkoc.</li>
<li>Gateway/Windows restart supervision: relaunch task-managed gateways through Scheduled Task with quoted helper-script command paths, distinguish restart-capable supervisors per platform, and stop orphaned Windows gateway children during self-restart. (#38825) Thanks @obviyus.</li>
<li>Telegram/native topic command routing: resolve forum-topic native commands through the same conversation route as inbound messages so topic <code>agentId</code> overrides and bound topic sessions target the active session instead of the default topic-parent session. (#38871) Thanks @obviyus.</li>
<li>Markdown/assistant image hardening: flatten remote markdown images to plain text across the Control UI, exported HTML, and shared Swift chat while keeping inline <code>data:image/...</code> markdown renderable, so model output no longer triggers automatic remote image fetches. (#38895) Thanks @obviyus.</li>
<li>Config/compaction safeguard settings: regression-test <code>agents.defaults.compaction.recentTurnsPreserve</code> through <code>loadConfig()</code> and cover the new help metadata entry so the exposed preserve knob stays wired through schema validation and config UX. (#25557) thanks @rodrigouroz.</li>
<li>iOS/Quick Setup presentation: skip automatic Quick Setup when a gateway is already configured (active connect config, last-known connection, preferred gateway, or manual host), so reconnecting installs no longer get prompted to connect again. (#38964) Thanks @ngutman.</li>
<li>CLI/Docs memory help accuracy: clarify <code>openclaw memory status --deep</code> behavior and align memory command examples/docs with the current search options. (#31803) Thanks @JasonOA888 and @Avi974.</li>
<li>Auto-reply/allowlist store account scoping: keep <code>/allowlist ... --store</code> writes scoped to the selected account and clear legacy unscoped entries when removing default-account store access, preventing cross-account default allowlist bleed-through from legacy pairing-store reads. Thanks @tdjackey for reporting and @vincentkoc for the fix.</li>
<li>Security/Nostr: harden profile mutation/import loopback guards by failing closed on non-loopback forwarded client headers (<code>x-forwarded-for</code> / <code>x-real-ip</code>) and rejecting <code>sec-fetch-site: cross-site</code>; adds regression coverage for proxy-forwarded and browser cross-site mutation attempts.</li>
<li>CLI/bootstrap Node version hint maintenance: replace hardcoded nvm <code>22</code> instructions in <code>openclaw.mjs</code> with <code>MIN_NODE_MAJOR</code> interpolation so future minimum-Node bumps keep startup guidance in sync automatically. (#39056) Thanks @onstash.</li>
<li>Discord/native slash command auth: honor <code>commands.allowFrom.discord</code> (and <code>commands.allowFrom["*"]</code>) in guild slash-command pre-dispatch authorization so allowlisted senders are no longer incorrectly rejected as unauthorized. (#38794) Thanks @jskoiz and @thewilloftheshadow.</li>
<li>Outbound/message target normalization: ignore empty legacy <code>to</code>/<code>channelId</code> fields when explicit <code>target</code> is provided so valid target-based sends no longer fail legacy-param validation; includes regression coverage. (#38944) Thanks @Narcooo.</li>
<li>Models/auth token prompts: guard cancelled manual token prompts so <code>Symbol(clack:cancel)</code> values cannot be persisted into auth profiles; adds regression coverage for cancelled <code>models auth paste-token</code>. (#38951) Thanks @MumuTW.</li>
<li>Gateway/loopback announce URLs: treat <code>http://</code> and <code>https://</code> aliases with the same loopback/private-network policy as websocket URLs so loopback cron announce delivery no longer fails secure URL validation. (#39064) Thanks @Narcooo.</li>
<li>Models/default provider fallback: when the hardcoded default provider is removed from <code>models.providers</code>, resolve defaults from configured providers instead of reporting stale removed-provider defaults in status output. (#38947) Thanks @davidemanuelDEV.</li>
<li>Agents/cache-trace stability: guard stable stringify against circular references in trace payloads so near-limit payloads no longer crash with <code>Maximum call stack size exceeded</code>; adds regression coverage. (#38935) Thanks @MumuTW.</li>
<li>Extensions/diffs CI stability: add <code>headers</code> to the <code>localReq</code> test helper in <code>extensions/diffs/index.test.ts</code> so forwarding-hint checks no longer crash with <code>req.headers</code> undefined. (supersedes #39063) Thanks @Shennng.</li>
<li>Agents/compaction thresholding: apply <code>agents.defaults.contextTokens</code> cap to the model passed into embedded run and <code>/compact</code> session creation so auto-compaction thresholds use the effective context window, not native model max context. (#39099) Thanks @MumuTW.</li>
<li>Models/merge mode provider precedence: when <code>models.mode: "merge"</code> is active and config explicitly sets a provider <code>baseUrl</code>, keep config as source of truth instead of preserving stale runtime <code>models.json</code> <code>baseUrl</code> values; includes normalized provider-key coverage. (#39103) Thanks @BigUncle.</li>
<li>UI/Control chat tool streaming: render tool events live in webchat without requiring refresh by enabling <code>tool-events</code> capability, fixing stream/event correlation, and resetting/reloading stream state around tool results and terminal events. (#39104) Thanks @jakepresent.</li>
<li>Models/provider apiKey persistence hardening: when a provider <code>apiKey</code> value equals a known provider env var value, persist the canonical env var name into <code>models.json</code> instead of resolved plaintext secrets. (#38889) Thanks @gambletan.</li>
<li>Discord/model picker persistence check: add a short post-dispatch settle delay before reading back session model state so picker confirmations stop reporting false mismatch warnings after successful model switches. (#39105) Thanks @akropp.</li>
<li>Agents/OpenAI WS compat store flag: omit <code>store</code> from <code>response.create</code> payloads when model compat sets <code>supportsStore: false</code>, preventing strict OpenAI-compatible providers from rejecting websocket requests with unknown-field errors. (#39113) Thanks @scoootscooob.</li>
<li>Config/validation log sanitization: sanitize config-validation issue paths/messages before logging so control characters and ANSI escape sequences cannot inject misleading terminal output from crafted config content. (#39116) Thanks @powermaster888.</li>
<li>Agents/compaction counter accuracy: count successful overflow-triggered auto-compactions (<code>willRetry=true</code>) in the compaction counter while still excluding aborted/no-result events, so <code>/status</code> reflects actual safeguard compaction activity. (#39123) Thanks @MumuTW.</li>
<li>Gateway/chat delta ordering: flush buffered assistant deltas before emitting tool <code>start</code> events so pre-tool text is delivered to Control UI before tool cards, avoiding transient text/tool ordering artifacts in streaming. (#39128) Thanks @0xtangping.</li>
<li>Voice-call plugin schema parity: add missing manifest <code>configSchema</code> fields (<code>webhookSecurity</code>, <code>streaming.preStartTimeoutMs|maxPendingConnections|maxPendingConnectionsPerIp|maxConnections</code>, <code>staleCallReaperSeconds</code>) so gateway AJV validation accepts already-supported runtime config instead of failing with <code>additionalProperties</code> errors. (#38892) Thanks @giumex.</li>
<li>Agents/OpenAI WS reconnect retry accounting: avoid double retry scheduling when reconnect failures emit both <code>error</code> and <code>close</code>, so retry budgets track actual reconnect attempts instead of exhausting early. (#39133) Thanks @scoootscooob.</li>
<li>Daemon/Windows schtasks runtime detection: use locale-invariant <code>Last Run Result</code> running codes (<code>0x41301</code>/<code>267009</code>) as the primary running signal so <code>openclaw node status</code> no longer misreports active tasks as stopped on non-English Windows locales. (#39076) Thanks @ademczuk.</li>
<li>Usage/token count formatting: round near-million token counts to millions (<code>1.0m</code>) instead of <code>1000k</code>, with explicit boundary coverage for <code>999_499</code> and <code>999_500</code>. (#39129) Thanks @CurryMessi.</li>
<li>Gateway/session bootstrap cache invalidation ordering: clear bootstrap snapshots only after active embedded-run shutdown wait completes, preventing dying runs from repopulating stale cache between <code>/new</code>/<code>sessions.reset</code> turns. (#38873) Thanks @MumuTW.</li>
<li>Browser/dispatcher error clarity: preserve dispatcher-side failure context in browser fetch errors while still appending operator guidance and explicit no-retry model hints, preventing misleading <code>"Can't reach service"</code> wrapping and avoiding LLM retry loops. (#39090) Thanks @NewdlDewdl.</li>
<li>Telegram/polling offset safety: confirm persisted offsets before polling startup while validating stored <code>lastUpdateId</code> values as non-negative safe integers (with overflow guards) so malformed offset state cannot cause update skipping/dropping. (#39111) Thanks @MumuTW.</li>
<li>Telegram/status SecretRef read-only resolution: resolve env-backed bot-token SecretRefs in config-only/status inspection while respecting provider source/defaults and env allowlists, so status no longer crashes or reports false-ready tokens for disallowed providers. (#39130) Thanks @neocody.</li>
<li>Agents/OpenAI WS max-token zero forwarding: treat <code>maxTokens: 0</code> as an explicit value in websocket <code>response.create</code> payloads (instead of dropping it as falsy), with regression coverage for zero-token forwarding. (#39148) Thanks @scoootscooob.</li>
<li>Podman/.env gateway bind precedence: evaluate <code>OPENCLAW_GATEWAY_BIND</code> after sourcing <code>.env</code> in <code>run-openclaw-podman.sh</code> so env-file overrides are honored. (#38785) Thanks @majinyu666.</li>
<li>Models/default alias refresh: bump <code>gpt</code> to <code>openai/gpt-5.4</code> and Gemini defaults to <code>gemini-3.1</code> preview aliases (including normalization/default wiring) to track current model IDs. (#38638) Thanks @ademczuk.</li>
<li>Config/env substitution degraded mode: convert missing <code>${VAR}</code> resolution in config reads from hard-fail to warning-backed degraded behavior, while preventing unresolved placeholders from being accepted as gateway credentials. (#39050) Thanks @akz142857.</li>
<li>Discord inbound listener non-blocking dispatch: make <code>MESSAGE_CREATE</code> listener handoff asynchronous (no per-listener queue blocking), so long runs no longer stall unrelated incoming events. (#39154) Thanks @yaseenkadlemakki.</li>
<li>Daemon/Windows PATH freeze fix: stop persisting install-time <code>PATH</code> snapshots into Scheduled Task scripts so runtime tool lookup follows current host PATH updates; also refresh local TUI history on silent local finals. (#39139) Thanks @Narcooo.</li>
<li>Gateway/systemd service restart hardening: clear stale gateway listeners by explicit run-port before service bind, add restart stale-pid port-override support, tune systemd start/stop/exit handling, and disable detached child mode only in service-managed runtime so cgroup stop semantics clean up descendants reliably. (#38463) Thanks @spirittechie.</li>
<li>Discord/plugin native command aliases: let plugins declare provider-specific slash names so native Discord registration can avoid built-in command collisions; the bundled Talk voice plugin now uses <code>/talkvoice</code> natively on Discord while keeping text <code>/voice</code>.</li>
<li>Daemon/Windows schtasks status normalization: derive runtime state from locale-neutral numeric <code>Last Run Result</code> codes only (without language string matching) and surface unknown when numeric result data is unavailable, preventing locale-specific misclassification drift. (#39153) Thanks @scoootscooob.</li>
<li>Telegram/polling conflict recovery: reset the polling <code>webhookCleared</code> latch on <code>getUpdates</code> 409 conflicts so webhook cleanup re-runs on restart cycles and polling avoids infinite conflict loops. (#39205) Thanks @amittell.</li>
<li>Heartbeat/requests-in-flight scheduling: stop advancing <code>nextDueMs</code> and avoid immediate <code>scheduleNext()</code> timer overrides on requests-in-flight skips, so wake-layer retry cooldowns are honored and heartbeat cadence no longer drifts under sustained contention. (#39182) Thanks @MumuTW.</li>
<li>Memory/SQLite contention resilience: re-apply <code>PRAGMA busy_timeout</code> on every sync-store and QMD connection open so process restarts/reopens no longer revert to immediate <code>SQLITE_BUSY</code> failures under lock contention. (#39183) Thanks @MumuTW.</li>
<li>Gateway/webchat route safety: block webchat/control-ui clients from inheriting stored external delivery routes on channel-scoped sessions (while preserving route inheritance for UI/TUI clients), preventing cross-channel leakage from scoped chats. (#39175) Thanks @widingmarcus-cyber.</li>
<li>Telegram error-surface resilience: return a user-visible fallback reply when dispatch/debounce processing fails instead of going silent, while preserving draft-stream cleanup and best-effort thread-scoped fallback delivery. (#39209) Thanks @riftzen-bit.</li>
<li>Gateway/password auth startup diagnostics: detect unresolved provider-reference objects in <code>gateway.auth.password</code> and fail with a specific bootstrap-secrets error message instead of generic misconfiguration output. (#39230) Thanks @ademczuk.</li>
<li>Agents/OpenAI-responses compatibility: strip unsupported <code>store</code> payload fields when <code>supportsStore=false</code> (including OpenAI-compatible non-OpenAI providers) while preserving server-compaction payload behavior. (#39219) Thanks @ademczuk.</li>
<li>Agents/model fallback visibility: warn when configured model IDs cannot be resolved and fallback is applied, with log-safe sanitization of model text to prevent control-sequence injection in warning output. (#39215) Thanks @ademczuk.</li>
<li>Outbound delivery replay safety: use two-phase delivery ACK markers (<code>.json</code> -> <code>.delivered</code> -> unlink) and startup marker cleanup so crash windows between send and cleanup do not replay already-delivered messages. (#38668) Thanks @Gundam98.</li>
<li>Nodes/system.run approval binding: carry prepared approval plans through gateway forwarding and bind interpreter-style script operands across approval to execution, so post-approval script rewrites are denied while unchanged approved script runs keep working. Thanks @tdjackey for reporting.</li>
<li>Nodes/system.run PowerShell wrapper parsing: treat <code>pwsh</code>/<code>powershell</code> <code>-EncodedCommand</code> forms as shell-wrapper payloads so allowlist mode still requires approval instead of falling back to plain argv analysis. Thanks @tdjackey for reporting.</li>
<li>Control UI/auth error reporting: map generic browser <code>Fetch failed</code> websocket close errors back to actionable gateway auth messages (<code>gateway token mismatch</code>, <code>authentication failed</code>, <code>retry later</code>) so dashboard disconnects stop hiding credential problems. Landed from contributor PR #28608 by @KimGLee. Thanks @KimGLee.</li>
<li>Media/mime unknown-kind handling: return <code>undefined</code> (not <code>"unknown"</code>) for missing/unrecognized MIME kinds and use document-size fallback caps for unknown remote media, preventing phantom <code><media:unknown></code> Signal events from being treated as real messages. (#39199) Thanks @nicolasgrasset.</li>
<li>Nodes/system.run allow-always persistence: honor shell comment semantics during allowlist analysis so <code>#</code>-tailed payloads that never execute are not persisted as trusted follow-up commands. Thanks @tdjackey for reporting.</li>
<li>Signal/inbound attachment fan-in: forward all successfully fetched inbound attachments through <code>MediaPaths</code>/<code>MediaUrls</code>/<code>MediaTypes</code> (instead of only the first), and improve multi-attachment placeholder summaries in mention-gated pending history. (#39212) Thanks @joeykrug.</li>
<li>Nodes/system.run dispatch-wrapper boundary: keep shell-wrapper approval classification active at the depth boundary so <code>env</code> wrapper stacks cannot reach <code>/bin/sh -c</code> execution without the expected approval gate. Thanks @tdjackey for reporting.</li>
<li>Docker/token persistence on reconfigure: reuse the existing <code>.env</code> gateway token during <code>docker-setup.sh</code> reruns and align compose token env defaults, so Docker installs stop silently rotating tokens and breaking existing dashboard sessions. Landed from contributor PR #33097 by @chengzhichao-xydt. Thanks @chengzhichao-xydt.</li>
<li>Agents/strict OpenAI turn ordering: apply assistant-first transcript bootstrap sanitization to strict OpenAI-compatible providers (for example vLLM/Gemma via <code>openai-completions</code>) without adding Google-specific session markers, preventing assistant-first history rejections. (#39252) Thanks @scoootscooob.</li>
<li>Discord/exec approvals gateway auth: pass resolved shared gateway credentials into the Discord exec-approvals gateway client so token-auth installs stop failing approvals with <code>gateway token mismatch</code>. Related to #38179. Thanks @0riginal-claw for the adjacent PR #35147 investigation.</li>
<li>Subagents/workspace inheritance: propagate parent workspace directory to spawned subagent runs so child sessions reliably inherit workspace-scoped instructions (<code>AGENTS.md</code>, <code>SOUL.md</code>, etc.) without exposing workspace override through tool-call arguments. (#39247) Thanks @jasonQin6.</li>
<li>Exec approvals/gateway-node policy: honor explicit <code>ask=off</code> from <code>exec-approvals.json</code> even when runtime defaults are stricter, so trusted full/off setups stop re-prompting on gateway and node exec paths. Landed from contributor PR #26789 by @pandego. Thanks @pandego.</li>
<li>Exec approvals/config fallback: inherit <code>ask</code> from <code>exec-approvals.json</code> when <code>tools.exec.ask</code> is unset, so local full/off defaults no longer fall back to <code>on-miss</code> for exec tool and <code>nodes run</code>. Landed from contributor PR #29187 by @Bartok9. Thanks @Bartok9.</li>
<li>Exec approvals/allow-always shell scripts: persist and match script paths for wrapper invocations like <code>bash scripts/foo.sh</code> while still blocking <code>-c</code>/<code>-s</code> wrapper bypasses. Landed from contributor PR #35137 by @yuweuii. Thanks @yuweuii.</li>
<li>Queue/followup dedupe across drain restarts: dedupe queued redelivery <code>message_id</code> values after queue recreation so busy-session followups no longer duplicate on replayed inbound events. Landed from contributor PR #33168 by @rylena. Thanks @rylena.</li>
<li>Telegram/preview-final edit idempotence: treat <code>message is not modified</code> errors during preview finalization as delivered so partial-stream final replies do not fall back to duplicate sends. Landed from contributor PR #34983 by @HOYALIM. Thanks @HOYALIM.</li>
<li>Telegram/DM streaming transport parity: use message preview transport for all DM streaming lanes so final delivery can edit the active preview instead of sending duplicate finals. Landed from contributor PR #38906 by @gambletan. Thanks @gambletan.</li>
<li>Telegram/DM draft streaming restoration: restore native <code>sendMessageDraft</code> preview transport for DM answer streaming while keeping reasoning on message transport, with regression coverage to keep draft finalization from sending duplicate finals. (#39398) Thanks @obviyus.</li>
<li>Telegram/send retry safety: retry non-idempotent send paths only for pre-connect failures and make custom retry predicates strict, preventing ambiguous reconnect retries from sending duplicate messages. Landed from contributor PR #34238 by @hal-crackbot. Thanks @hal-crackbot.</li>
<li>ACP/run spawn delivery bootstrap: stop reusing requester inline delivery targets for one-shot <code>mode: "run"</code> ACP spawns, so fresh run-mode workers bootstrap in isolation instead of inheriting thread-bound session delivery behavior. (#39014) Thanks @lidamao633.</li>
<li>Discord/DM session-key normalization: rewrite legacy <code>discord:dm:*</code> and phantom direct-message <code>discord:channel:<user></code> session keys to <code>discord:direct:*</code> when the sender matches, so multi-agent Discord DMs stop falling into empty channel-shaped sessions and resume replying correctly.</li>
<li>Discord/native slash session fallback: treat empty configured bound-session keys as missing so <code>/status</code> and other native commands fall back to the routed slash session and routed channel session instead of blanking Discord session keys in normal channel bindings.</li>
<li>Agents/tool-call dispatch normalization: normalize provider-prefixed tool names before dispatch across <code>toolCall</code>, <code>toolUse</code>, and <code>functionCall</code> blocks, while preserving multi-segment tool suffixes when stripping provider wrappers so malformed-but-recoverable tool names no longer fail with <code>Tool not found</code>. (#39328) Thanks @vincentkoc.</li>
<li>Agents/parallel tool-call compatibility: honor <code>parallel_tool_calls</code> / <code>parallelToolCalls</code> extra params only for <code>openai-completions</code> and <code>openai-responses</code> payloads, preserve higher-precedence alias overrides across config and runtime layers, and ignore invalid non-boolean values so single-tool-call providers like NVIDIA-hosted Kimi stop failing on forced parallel tool-call payloads. (#37048) Thanks @vincentkoc.</li>
<li>Config/invalid-load fail-closed: stop converting <code>INVALID_CONFIG</code> into an empty runtime config, keep valid settings available only through explicit best-effort diagnostic reads, and route read-only CLI diagnostics through that path so unknown keys no longer silently drop security-sensitive config. (#28140) Thanks @bobsahur-robot and @vincentkoc.</li>
<li>Agents/codex-cli sandbox defaults: switch the built-in Codex backend from <code>read-only</code> to <code>workspace-write</code> so spawned coding runs can edit files out of the box. Landed from contributor PR #39336 by @0xtangping. Thanks @0xtangping.</li>
<li>Gateway/health-monitor restart reason labeling: report <code>disconnected</code> instead of <code>stuck</code> for clean channel disconnect restarts, so operator logs distinguish socket drops from genuinely stuck channels. (#36436) Thanks @Sid-Qin.</li>
<li>Control UI/agents-page overrides: auto-create minimal per-agent config entries when editing inherited agents, so model/tool/skill changes enable Save and inherited model fallbacks can be cleared by writing a primary-only override. Landed from contributor PR #39326 by @dunamismax. Thanks @dunamismax.</li>
<li>Gateway/Telegram webhook-mode recovery: add <code>webhookCertPath</code> to re-upload self-signed certificates during webhook registration and skip stale-socket detection for webhook-mode channels, so Telegram webhook setups survive health-monitor restarts. Landed from contributor PR #39313 by @fellanH. Thanks @fellanH.</li>
<li>Discord/config schema parity: add <code>channels.discord.agentComponents</code> to the strict Zod config schema so valid <code>agentComponents.enabled</code> settings (root and account-scoped) no longer fail with unrecognized-key validation errors. Landed from contributor PR #39378 by @gambletan. Thanks @gambletan and @thewilloftheshadow.</li>
<li>ACPX/MCP session bootstrap: inject configured MCP servers into ACP <code>session/new</code> and <code>session/load</code> for acpx-backed sessions, restoring Canva and other external MCP tools. Landed from contributor PR #39337. Thanks @goodspeed-apps.</li>
<li>Control UI/Telegram sender labels: preserve inbound sender labels in sanitized chat history so dashboard user-message groups split correctly and show real group-member names instead of <code>You</code>. (#39414) Thanks @obviyus.</li>
<li>Dashboard/chat UI: stop reloading full chat history on every live tool result in dashboard v2 so tool-heavy runs no longer trigger UI freeze/re-render storms while the final event still refreshes persisted history. (#45541) Thanks @BunsDev.</li>
<li>Gateway/client requests: reject unanswered gateway RPC calls after a bounded timeout and clear their pending state, so stalled connections no longer leak hanging <code>GatewayClient.request()</code> promises indefinitely.</li>
<li>Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn.</li>
<li>Ollama/reasoning visibility: stop promoting native <code>thinking</code> and <code>reasoning</code> fields into final assistant text so local reasoning models no longer leak internal thoughts in normal replies. (#45330) Thanks @xi7ang.</li>
<li>Android/onboarding QR scan: switch setup QR scanning to Google Code Scanner so onboarding uses a more reliable scanner instead of the legacy embedded ZXing flow. (#45021) Thanks @obviyus.</li>
<li>Browser/existing-session: harden driver validation and session lifecycle so transport errors trigger reconnects while tool-level errors preserve the session, and extract shared ARIA role sets to deduplicate Playwright and Chrome MCP snapshot paths. (#45682) Thanks @odysseus0.</li>
<li>Browser/existing-session: accept text-only <code>list_pages</code> and <code>new_page</code> responses from Chrome DevTools MCP so live-session tab discovery and new-tab open flows keep working when the server omits structured page metadata.</li>
<li>Control UI/insecure auth: preserve explicit shared token and password auth on plain-HTTP Control UI connects so LAN and reverse-proxy sessions no longer drop shared auth before the first WebSocket handshake. (#45088) Thanks @velvet-shark.</li>
<li>Gateway/session reset: preserve <code>lastAccountId</code> and <code>lastThreadId</code> across gateway session resets so replies keep routing back to the same account and thread after <code>/reset</code>. (#44773) Thanks @Lanfei.</li>
<li>macOS/onboarding: avoid self-restarting freshly bootstrapped launchd gateways and give new daemon installs longer to become healthy, so <code>openclaw onboard --install-daemon</code> no longer false-fails on slower Macs and fresh VM snapshots.</li>
<li>Gateway/status: add <code>openclaw gateway status --require-rpc</code> and clearer Linux non-interactive daemon-install failure reporting so automation can fail hard on probe misses instead of treating a printed RPC error as green.</li>
<li>macOS/exec approvals: respect per-agent exec approval settings in the gateway prompter, including allowlist fallback when the native prompt cannot be shown, so gateway-triggered <code>system.run</code> requests follow configured policy instead of always prompting or denying unexpectedly. (#13707) Thanks @sliekens.</li>
<li>Telegram/media downloads: thread the same direct or proxy transport policy into SSRF-guarded file fetches so inbound attachments keep working when Telegram falls back between env-proxy and direct networking. (#44639) Thanks @obviyus.</li>
<li>Telegram/inbound media IPv4 fallback: retry SSRF-guarded Telegram file downloads once with the same IPv4 fallback policy as Bot API calls so fresh installs on IPv6-broken hosts no longer fail to download inbound images.</li>
<li>Windows/gateway install: bound <code>schtasks</code> calls and fall back to the Startup-folder login item when task creation hangs, so native <code>openclaw gateway install</code> fails fast instead of wedging forever on broken Scheduled Task setups.</li>
<li>Windows/gateway stop: resolve Startup-folder fallback listeners from the installed <code>gateway.cmd</code> port, so <code>openclaw gateway stop</code> now actually kills fallback-launched gateway processes before restart.</li>
<li>Windows/gateway status: reuse the installed service command environment when reading runtime status, so startup-fallback gateways keep reporting the configured port and running state in <code>gateway status --json</code> instead of falling back to <code>gateway port unknown</code>.</li>
<li>Windows/gateway auth: stop attaching device identity on local loopback shared-token and password gateway calls, so native Windows agent replies no longer log stale <code>device signature expired</code> fallback noise before succeeding.</li>
<li>Discord/gateway startup: treat plain-text and transient <code>/gateway/bot</code> metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman.</li>
<li>Slack/probe: keep <code>auth.test()</code> bot and team metadata mapping stable while simplifying the probe result path. (#44775) Thanks @Cafexss.</li>
<li>Dashboard/chat UI: render oversized plain-text replies as normal paragraphs instead of capped gray code blocks, so long desktop chat responses stay readable without tab-switching refreshes.</li>
<li>Dashboard/chat UI: restore the <code>chat-new-messages</code> class on the New messages scroll pill so the button uses its existing compact styling instead of rendering as a full-screen SVG overlay. (#44856) Thanks @Astro-Han.</li>
<li>Gateway/Control UI: restore the operator-only device-auth bypass and classify browser connect failures so origin and device-identity problems no longer show up as auth errors in the Control UI and web chat. (#45512) thanks @sallyom.</li>
<li>macOS/voice wake: stop crashing wake-word command extraction when speech segment ranges come from a different transcript instance.</li>
<li>Discord/allowlists: honor raw <code>guild_id</code> when hydrated guild objects are missing so allowlisted channels and threads like <code>#maintainers</code> no longer get false-dropped before channel allowlist checks.</li>
<li>macOS/runtime locator: require Node >=22.16.0 during macOS runtime discovery so the app no longer accepts Node versions that the main runtime guard rejects later. Thanks @sumleo.</li>
<li>Agents/custom providers: preserve blank API keys for loopback OpenAI-compatible custom providers by clearing the synthetic Authorization header at runtime, while keeping explicit apiKey and oauth/token config from silently downgrading into fake bearer auth. (#45631) Thanks @xinhuagu.</li>
<li>Models/google-vertex Gemini flash-lite normalization: apply existing bare-ID preview normalization to <code>google-vertex</code> model refs and provider configs so <code>google-vertex/gemini-3.1-flash-lite</code> resolves as <code>gemini-3.1-flash-lite-preview</code>. (#42435) thanks @scoootscooob.</li>
<li>iMessage/remote attachments: reject unsafe remote attachment paths before spawning SCP, so sender-controlled filenames can no longer inject shell metacharacters into remote media staging. Thanks @lintsinghua.</li>
<li>Telegram/webhook auth: validate the Telegram webhook secret before reading or parsing request bodies, so unauthenticated requests are rejected immediately instead of consuming up to 1 MB first. Thanks @space08.</li>
<li>Security/device pairing: make bootstrap setup codes single-use so pending device pairing requests cannot be silently replayed and widened to admin before approval. Thanks @tdjackey.</li>
<li>Security/external content: strip zero-width and soft-hyphen marker-splitting characters during boundary sanitization so spoofed <code>EXTERNAL_UNTRUSTED_CONTENT</code> markers fall back to the existing hardening path instead of bypassing marker normalization.</li>
<li>Security/exec approvals: unwrap more <code>pnpm</code> runtime forms during approval binding, including <code>pnpm --reporter ... exec</code> and direct <code>pnpm node</code> file runs, with matching regression coverage and docs updates.</li>
<li>Security/exec approvals: fail closed for Perl <code>-M</code> and <code>-I</code> approval flows so preload and load-path module resolution stays outside approval-backed runtime execution unless the operator uses a broader explicit trust path.</li>
<li>Security/exec approvals: recognize PowerShell <code>-File</code> and <code>-f</code> wrapper forms during inline-command extraction so approval and command-analysis paths treat file-based PowerShell launches like the existing <code>-Command</code> variants.</li>
<li>Security/exec approvals: unwrap <code>env</code> dispatch wrappers inside shell-segment allowlist resolution on macOS so <code>env FOO=bar /path/to/bin</code> resolves against the effective executable instead of the wrapper token.</li>
<li>Security/exec approvals: treat backslash-newline as shell line continuation during macOS shell-chain parsing so line-continued <code>$(</code> substitutions fail closed instead of slipping past command-substitution checks.</li>
<li>Security/exec approvals: bind macOS skill auto-allow trust to both executable name and resolved path so same-basename binaries no longer inherit trust from unrelated skill bins.</li>
<li>Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn.</li>
<li>Cron/isolated sessions: route nested cron-triggered embedded runner work onto the nested lane so isolated cron jobs no longer deadlock when compaction or other queued inner work runs. Thanks @vincentkoc.</li>
<li>Agents/OpenAI-compatible compat overrides: respect explicit user <code>models[].compat</code> opt-ins for non-native <code>openai-completions</code> endpoints so usage-in-streaming capability overrides no longer get forced off when the endpoint actually supports them. (#44432) Thanks @cheapestinference.</li>
<li>Agents/Azure OpenAI startup prompts: rephrase the built-in <code>/new</code>, <code>/reset</code>, and post-compaction startup instruction so Azure OpenAI deployments no longer hit HTTP 400 false positives from the content filter. (#43403) Thanks @xingsy97.</li>
<li>Agents/memory bootstrap: load only one root memory file, preferring <code>MEMORY.md</code> and using <code>memory.md</code> as a fallback, so case-insensitive Docker mounts no longer inject duplicate memory context. (#26054) Thanks @Lanfei.</li>
<li>Agents/compaction: compare post-compaction token sanity checks against full-session pre-compaction totals and skip the check when token estimation fails, so sessions with large bootstrap context keep real token counts instead of falling back to unknown. (#28347) thanks @efe-arv.</li>
<li>Agents/compaction: preserve safeguard compaction summary language continuity via default and configurable custom instructions so persona drift is reduced after auto-compaction. (#10456) Thanks @keepitmello.</li>
<li>Agents/tool warnings: distinguish gated core tools like <code>apply_patch</code> from plugin-only unknown entries in <code>tools.profile</code> warnings, so unavailable core tools now report current runtime/provider/model/config gating instead of suggesting a missing plugin.</li>
<li>Config/validation: accept documented <code>agents.list[].params</code> per-agent overrides in strict config validation so <code>openclaw config validate</code> no longer rejects runtime-supported <code>cacheRetention</code>, <code>temperature</code>, and <code>maxTokens</code> settings. (#41171) Thanks @atian8179.</li>
<li>Config/web fetch: restore runtime validation for documented <code>tools.web.fetch.readability</code> and <code>tools.web.fetch.firecrawl</code> settings so valid web fetch configs no longer fail with unrecognized-key errors. (#42583) Thanks @stim64045-spec.</li>
<li>Signal/config validation: add <code>channels.signal.groups</code> schema support so per-group <code>requireMention</code>, <code>tools</code>, and <code>toolsBySender</code> overrides no longer get rejected during config validation. (#27199) Thanks @unisone.</li>
<li>Config/discovery: accept <code>discovery.wideArea.domain</code> in strict config validation so unicast DNS-SD gateway configs no longer fail with an unrecognized-key error. (#35615) Thanks @ingyukoh.</li>
<li>Telegram/media errors: redact Telegram file URLs before building media fetch errors so failed inbound downloads do not leak bot tokens into logs. Thanks @space08.</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.7/OpenClaw-2026.3.7.zip" length="23263833" type="application/octet-stream" sparkle:edSignature="SO0zedZMzrvSDltLkuaSVQTWFPPPe1iu/enS4TGGb5EGckhqRCmNJWMKNID5lKwFC8vefTbfG9JTlSrZedP4Bg=="/>
<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.2</title>
<pubDate>Tue, 03 Mar 2026 04:30:29 +0000</pubDate>
<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>2026030290</sparkle:version>
<sparkle:shortVersionString>2026.3.2</sparkle:shortVersionString>
<sparkle:version>2026031290</sparkle:version>
<sparkle:shortVersionString>2026.3.12</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.3.2</h2>
<description><![CDATA[<h2>OpenClaw 2026.3.12</h2>
<h3>Changes</h3>
<ul>
<li>Secrets/SecretRef coverage: expand SecretRef support across the full supported user-supplied credential surface (64 targets total), including runtime collectors, <code>openclaw secrets</code> planning/apply/audit flows, onboarding SecretInput UX, and related docs; unresolved refs now fail fast on active surfaces while inactive surfaces report non-blocking diagnostics. (#29580) Thanks @joshavant.</li>
<li>Tools/PDF analysis: add a first-class <code>pdf</code> tool with native Anthropic and Google PDF provider support, extraction fallback for non-native models, configurable defaults (<code>agents.defaults.pdfModel</code>, <code>pdfMaxBytesMb</code>, <code>pdfMaxPages</code>), and docs/tests covering routing, validation, and registration. (#31319) Thanks @tyler6204.</li>
<li>Outbound adapters/plugins: add shared <code>sendPayload</code> support across direct-text-media, Discord, Slack, WhatsApp, Zalo, and Zalouser with multi-media iteration and chunk-aware text fallback. (#30144) Thanks @nohat.</li>
<li>Models/MiniMax: add first-class <code>MiniMax-M2.5-highspeed</code> support across built-in provider catalogs, onboarding flows, and MiniMax OAuth plugin defaults, while keeping legacy <code>MiniMax-M2.5-Lightning</code> compatibility for existing configs.</li>
<li>Sessions/Attachments: add inline file attachment support for <code>sessions_spawn</code> (subagent runtime only) with base64/utf8 encoding, transcript content redaction, lifecycle cleanup, and configurable limits via <code>tools.sessions_spawn.attachments</code>. (#16761) Thanks @napetrov.</li>
<li>Telegram/Streaming defaults: default <code>channels.telegram.streaming</code> to <code>partial</code> (from <code>off</code>) so new Telegram setups get live preview streaming out of the box, with runtime fallback to message-edit preview when native drafts are unavailable.</li>
<li>Telegram/DM streaming: use <code>sendMessageDraft</code> for private preview streaming, keep reasoning/answer preview lanes separated in DM reasoning-stream mode. (#31824) Thanks @obviyus.</li>
<li>Telegram/voice mention gating: add optional <code>disableAudioPreflight</code> on group/topic config to skip mention-detection preflight transcription for inbound voice notes where operators want text-only mention checks. (#23067) Thanks @yangnim21029.</li>
<li>CLI/Config validation: add <code>openclaw config validate</code> (with <code>--json</code>) to validate config files before gateway startup, and include detailed invalid-key paths in startup invalid-config errors. (#31220) thanks @Sid-Qin.</li>
<li>Tools/Diffs: add PDF file output support and rendering quality customization controls (<code>fileQuality</code>, <code>fileScale</code>, <code>fileMaxWidth</code>) for generated diff artifacts, and document PDF as the preferred option when messaging channels compress images. (#31342) Thanks @gumadeiras.</li>
<li>Memory/Ollama embeddings: add <code>memorySearch.provider = "ollama"</code> and <code>memorySearch.fallback = "ollama"</code> support, honor <code>models.providers.ollama</code> settings for memory embedding requests, and document Ollama embedding usage. (#26349) Thanks @nico-hoff.</li>
<li>Zalo Personal plugin (<code>@openclaw/zalouser</code>): rebuilt channel runtime to use native <code>zca-js</code> integration in-process, removing external CLI transport usage and keeping QR/login + send/listen flows fully inside OpenClaw.</li>
<li>Plugin SDK/channel extensibility: expose <code>channelRuntime</code> on <code>ChannelGatewayContext</code> so external channel plugins can access shared runtime helpers (reply/routing/session/text/media/commands) without internal imports. (#25462) Thanks @guxiaobo.</li>
<li>Plugin runtime/STT: add <code>api.runtime.stt.transcribeAudioFile(...)</code> so extensions can transcribe local audio files through OpenClaw's configured media-understanding audio providers. (#22402) Thanks @benthecarman.</li>
<li>Plugin hooks/session lifecycle: include <code>sessionKey</code> in <code>session_start</code>/<code>session_end</code> hook events and contexts so plugins can correlate lifecycle callbacks with routing identity. (#26394) Thanks @tempeste.</li>
<li>Hooks/message lifecycle: add internal hook events <code>message:transcribed</code> and <code>message:preprocessed</code>, plus richer outbound <code>message:sent</code> context (<code>isGroup</code>, <code>groupId</code>) for group-conversation correlation and post-transcription automations. (#9859) Thanks @Drickon.</li>
<li>Media understanding/audio echo: add optional <code>tools.media.audio.echoTranscript</code> + <code>echoFormat</code> to send a pre-agent transcript confirmation message to the originating chat, with echo disabled by default. (#32150) Thanks @AytuncYildizli.</li>
<li>Plugin runtime/system: expose <code>runtime.system.requestHeartbeatNow(...)</code> so extensions can wake targeted sessions immediately after enqueueing system events. (#19464) Thanks @AustinEral.</li>
<li>Plugin runtime/events: expose <code>runtime.events.onAgentEvent</code> and <code>runtime.events.onSessionTranscriptUpdate</code> for extension-side subscriptions, and isolate transcript-listener failures so one faulty listener cannot break the entire update fanout. (#16044) Thanks @scifantastic.</li>
<li>CLI/Banner taglines: add <code>cli.banner.taglineMode</code> (<code>random</code> | <code>default</code> | <code>off</code>) to control funny tagline behavior in startup output, with docs + FAQ guidance and regression tests for config override behavior.</li>
</ul>
<h3>Breaking</h3>
<ul>
<li><strong>BREAKING:</strong> Onboarding now defaults <code>tools.profile</code> to <code>messaging</code> for new local installs (interactive + non-interactive). New setups no longer start with broad coding/system tools unless explicitly configured.</li>
<li><strong>BREAKING:</strong> ACP dispatch now defaults to enabled unless explicitly disabled (<code>acp.dispatch.enabled=false</code>). If you need to pause ACP turn routing while keeping <code>/acp</code> controls, set <code>acp.dispatch.enabled=false</code>. Docs: https://docs.openclaw.ai/tools/acp-agents</li>
<li><strong>BREAKING:</strong> Plugin SDK removed <code>api.registerHttpHandler(...)</code>. Plugins must register explicit HTTP routes via <code>api.registerHttpRoute({ path, auth, match, handler })</code>, and dynamic webhook lifecycles should use <code>registerPluginHttpRoute(...)</code>.</li>
<li><strong>BREAKING:</strong> Zalo Personal plugin (<code>@openclaw/zalouser</code>) no longer depends on external <code>zca</code>-compatible CLI binaries (<code>openzca</code>, <code>zca-cli</code>) for runtime send/listen/login; operators should use <code>openclaw channels login --channel zalouser</code> after upgrade to refresh sessions in the new JS-native path.</li>
<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>Plugin command/runtime hardening: validate and normalize plugin command name/description at registration boundaries, and guard Telegram native menu normalization paths so malformed plugin command specs cannot crash startup (<code>trim</code> on undefined). (#31997) Fixes #31944. Thanks @liuxiaopai-ai.</li>
<li>Telegram: guard duplicate-token checks and gateway startup token normalization when account tokens are missing, preventing <code>token.trim()</code> crashes during status/start flows. (#31973) Thanks @ningding97.</li>
<li>Discord/lifecycle startup status: push an immediate <code>connected</code> status snapshot when the gateway is already connected before lifecycle debug listeners attach, with abort-guarding to avoid contradictory status flips during pre-aborted startup. (#32336) Thanks @mitchmcalister.</li>
<li>Feishu/LINE group system prompts: forward per-group <code>systemPrompt</code> config into inbound context <code>GroupSystemPrompt</code> for Feishu and LINE group/room events so configured group-specific behavior actually applies at dispatch time. (#31713) Thanks @whiskyboy.</li>
<li>Mentions/Slack formatting hardening: add null-safe guards for runtime text normalization paths so malformed/undefined text payloads do not crash mention stripping or mrkdwn conversion. (#31865) Thanks @stone-jin.</li>
<li>Feishu/Plugin sdk compatibility: add safe webhook default fallbacks when loading Feishu monitor state so mixed-version installs no longer crash if older <code>openclaw/plugin-sdk</code> builds omit webhook default constants. (#31606)</li>
<li>Feishu/group broadcast dispatch: add configurable multi-agent group broadcast dispatch with observer-session isolation, cross-account dedupe safeguards, and non-mention history buffering rules that avoid duplicate replay in broadcast/topic workflows. (#29575) Thanks @ohmyskyhigh.</li>
<li>Gateway/Subagent TLS pairing: allow authenticated local <code>gateway-client</code> backend self-connections to skip device pairing while still requiring pairing for non-local/direct-host paths, restoring <code>sessions_spawn</code> with <code>gateway.tls.enabled=true</code> in Docker/LAN setups. Fixes #30740. Thanks @Sid-Qin and @vincentkoc.</li>
<li>Browser/CDP startup diagnostics: include Chrome stderr output and a Linux no-sandbox hint in startup timeout errors so failed launches are easier to diagnose. (#29312) Thanks @veast.</li>
<li>Synology Chat/webhook ingress hardening: enforce bounded body reads (size + timeout) via shared request-body guards to prevent unauthenticated slow-body hangs before token validation. (#25831) Thanks @bmendonca3.</li>
<li>Feishu/Dedup restart resilience: warm persistent dedup state into memory on monitor startup so retry events after gateway restart stay suppressed without requiring initial on-disk probe misses. (#31605)</li>
<li>Voice-call/runtime lifecycle: prevent <code>EADDRINUSE</code> loops by resetting failed runtime promises, making webhook <code>start()</code> idempotent with the actual bound port, and fully cleaning up webhook/tunnel/tailscale resources after startup failures. (#32395) Thanks @scoootscooob.</li>
<li>Gateway/Security hardening: tie loopback-origin dev allowance to actual local socket clients (not Host header claims), add explicit warnings/metrics when <code>gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback</code> accepts websocket origins, harden safe-regex detection for quantified ambiguous alternation patterns (for example <code>(a|aa)+</code>), and bound large regex-evaluation inputs for session-filter and log-redaction paths.</li>
<li>Gateway/Plugin HTTP hardening: require explicit <code>auth</code> for plugin route registration, add route ownership guards for duplicate <code>path+match</code> registrations, centralize plugin path matching/auth logic into dedicated modules, and share webhook target-route lifecycle wiring across channel monitors to avoid stale or conflicting registrations. Thanks @tdjackey for reporting.</li>
<li>Browser/Profile defaults: prefer <code>openclaw</code> profile over <code>chrome</code> in headless/no-sandbox environments unless an explicit <code>defaultProfile</code> is configured. (#14944) Thanks @BenediktSchackenberg.</li>
<li>Gateway/WS security: keep plaintext <code>ws://</code> loopback-only by default, with explicit break-glass private-network opt-in via <code>OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1</code>; align onboarding/client/call validation and tests to this strict-default policy. (#28670) Thanks @dashed, @vincentkoc.</li>
<li>OpenAI Codex OAuth/TLS prerequisites: add an OAuth TLS cert-chain preflight with actionable remediation for cert trust failures, and gate doctor TLS prerequisite probing to OpenAI Codex OAuth-configured installs (or explicit <code>doctor --deep</code>) to avoid unconditional outbound probe latency. (#32051) Thanks @alexfilatov.</li>
<li>Security/Webhook request hardening: enforce auth-before-body parsing for BlueBubbles and Google Chat webhook handlers, add strict pre-auth body/time budgets for webhook auth paths (including LINE signature verification), and add shared in-flight/request guardrails plus regression tests/lint checks to prevent reintroducing unauthenticated slow-body DoS patterns. Thanks @GCXWLP for reporting.</li>
<li>CLI/Config validation and routing hardening: dedupe <code>openclaw config validate</code> failures to a single authoritative report, expose allowed-values metadata/hints across core Zod and plugin AJV validation (including <code>--json</code> fields), sanitize terminal-rendered validation text, and make command-path parsing root-option-aware across preaction/route/lazy registration (including routed <code>config get/unset</code> with split root options). Thanks @gumadeiras.</li>
<li>Browser/Extension relay reconnect tolerance: keep <code>/json/version</code> and <code>/cdp</code> reachable during short MV3 worker disconnects when attached targets still exist, and retain clients across reconnect grace windows. (#30232) Thanks @Sid-Qin.</li>
<li>CLI/Browser start timeout: honor <code>openclaw browser --timeout <ms> start</code> and stop by removing the fixed 15000ms override so slower Chrome startups can use caller-provided timeouts. (#22412, #23427) Thanks @vincentkoc.</li>
<li>Synology Chat/gateway lifecycle: keep <code>startAccount</code> pending until abort for inactive and active account paths to prevent webhook route restart loops under gateway supervision. (#23074) Thanks @druide67.</li>
<li>Exec approvals/allowlist matching: escape regex metacharacters in path-pattern literals (while preserving glob wildcards), preventing crashes on allowlisted executables like <code>/usr/bin/g++</code> and correctly matching mixed wildcard/literal token paths. (#32162) Thanks @stakeswky.</li>
<li>Synology Chat/webhook compatibility: accept JSON and alias payload fields, allow token resolution from body/query/header sources, and ACK webhook requests with <code>204</code> to avoid persistent <code>Processing...</code> states in Synology Chat clients. (#26635) Thanks @memphislee09-source.</li>
<li>Voice-call/Twilio signature verification: retry signature validation across deterministic URL port variants (with/without port) to handle mixed Twilio signing behavior behind reverse proxies and non-standard ports. (#25140) Thanks @drvoss.</li>
<li>Slack/Bolt startup compatibility: remove invalid <code>message.channels</code> and <code>message.groups</code> event registrations so Slack providers no longer crash on startup with Bolt 4.6+; channel/group traffic continues through the unified <code>message</code> handler (<code>channel_type</code>). (#32033) Thanks @mahopan.</li>
<li>Slack/socket auth failure handling: fail fast on non-recoverable auth errors (<code>account_inactive</code>, <code>invalid_auth</code>, etc.) during startup and reconnect instead of retry-looping indefinitely, including <code>unable_to_socket_mode_start</code> error payload propagation. (#32377) Thanks @scoootscooob.</li>
<li>Gateway/macOS LaunchAgent hardening: write <code>Umask=077</code> in generated gateway LaunchAgent plists so npm upgrades preserve owner-only default file permissions for gateway-created state files. (#31919) Fixes #31905. Thanks @liuxiaopai-ai.</li>
<li>macOS/LaunchAgent security defaults: write <code>Umask=63</code> (octal <code>077</code>) into generated gateway launchd plists so post-update service reinstalls keep owner-only file permissions by default instead of falling back to system <code>022</code>. (#32022) Fixes #31905. Thanks @liuxiaopai-ai.</li>
<li>Media understanding/provider HTTP proxy routing: pass a proxy-aware fetch function from <code>HTTPS_PROXY</code>/<code>HTTP_PROXY</code> env vars into audio/video provider calls (with graceful malformed-proxy fallback) so transcription/video requests honor configured outbound proxies. (#27093) Thanks @mcaxtr.</li>
<li>Sandbox/workspace mount permissions: make primary <code>/workspace</code> bind mounts read-only whenever <code>workspaceAccess</code> is not <code>rw</code> (including <code>none</code>) across both core sandbox container and sandbox browser create flows. (#32227) Thanks @guanyu-zhang.</li>
<li>Tools/fsPolicy propagation: honor <code>tools.fs.workspaceOnly</code> for image/pdf local-root allowlists so non-sandbox media paths outside workspace are rejected when workspace-only mode is enabled. (#31882) Thanks @justinhuangcode.</li>
<li>Daemon/Homebrew runtime pinning: resolve Homebrew Cellar Node paths to stable Homebrew-managed symlinks (including versioned formulas like <code>node@22</code>) so gateway installs keep the intended runtime across brew upgrades. (#32185) Thanks @scoootscooob.</li>
<li>Browser/Security output boundary hardening: replace check-then-rename output commits with root-bound fd-verified writes, unify install/skills canonical path-boundary checks, and add regression coverage for symlink-rebind race paths across browser output and shared fs-safe write flows. Thanks @tdjackey for reporting.</li>
<li>Gateway/Security canonicalization hardening: decode plugin route path variants to canonical fixpoint (with bounded depth), fail closed on canonicalization anomalies, and enforce gateway auth for deeply encoded <code>/api/channels/*</code> variants to prevent alternate-path auth bypass through plugin handlers. Thanks @tdjackey for reporting.</li>
<li>Browser/Gateway hardening: preserve env credentials for <code>OPENCLAW_GATEWAY_URL</code> / <code>CLAWDBOT_GATEWAY_URL</code> while treating explicit <code>--url</code> as override-only auth, and make container browser hardening flags optional with safer defaults for Docker/LXC stability. (#31504) Thanks @vincentkoc.</li>
<li>Gateway/Control UI basePath webhook passthrough: let non-read methods under configured <code>controlUiBasePath</code> fall through to plugin routes (instead of returning Control UI 405), restoring webhook handlers behind basePath mounts. (#32311) Thanks @ademczuk.</li>
<li>Control UI/Legacy browser compatibility: replace <code>toSorted</code>-dependent cron suggestion sorting in <code>app-render</code> with a compatibility helper so older browsers without <code>Array.prototype.toSorted</code> no longer white-screen. (#31775) Thanks @liuxiaopai-ai.</li>
<li>macOS/PeekabooBridge: add compatibility socket symlinks for legacy <code>clawdbot</code>, <code>clawdis</code>, and <code>moltbot</code> Application Support socket paths so pre-rename clients can still connect. (#6033) Thanks @lumpinif and @vincentkoc.</li>
<li>Gateway/message tool reliability: avoid false <code>Unknown channel</code> failures when <code>message.*</code> actions receive platform-specific channel ids by falling back to <code>toolContext.currentChannelProvider</code>, and prevent health-monitor restart thrash for channels that just (re)started by adding a per-channel startup-connect grace window. (from #32367) Thanks @MunemHashmi.</li>
<li>Windows/Spawn canonicalization: unify non-core Windows spawn handling across ACP client, QMD/mcporter memory paths, and sandbox Docker execution using the shared wrapper-resolution policy, with targeted regression coverage for <code>.cmd</code> shim unwrapping and shell fallback behavior. (#31750) Thanks @Takhoffman.</li>
<li>Security/ACP sandbox inheritance: enforce fail-closed runtime guardrails for <code>sessions_spawn</code> with <code>runtime="acp"</code> by rejecting ACP spawns from sandboxed requester sessions and rejecting <code>sandbox="require"</code> for ACP runtime, preventing sandbox-boundary bypass via host-side ACP initialization. (#32254) Thanks @tdjackey for reporting, and @dutifulbob for the fix.</li>
<li>Security/Web tools SSRF guard: keep DNS pinning for untrusted <code>web_fetch</code> and citation-redirect URL checks when proxy env vars are set, and require explicit dangerous opt-in before env-proxy routing can bypass pinned dispatch for trusted/operator-controlled endpoints. Thanks @tdjackey for reporting.</li>
<li>Gemini schema sanitization: coerce malformed JSON Schema <code>properties</code> values (<code>null</code>, arrays, primitives) to <code>{}</code> before provider validation, preventing downstream strict-validator crashes on invalid plugin/tool schemas. (#32332) Thanks @webdevtodayjason.</li>
<li>Media understanding/malformed attachment guards: harden attachment selection and decision summary formatting against non-array or malformed attachment payloads to prevent runtime crashes on invalid inbound metadata shapes. (#28024) Thanks @claw9267.</li>
<li>Browser/Extension navigation reattach: preserve debugger re-attachment when relay is temporarily disconnected by deferring relay attach events until reconnect/re-announce, reducing post-navigation tab loss. (#28725) Thanks @stone-jin.</li>
<li>Browser/Extension relay stale tabs: evict stale cached targets from <code>/json/list</code> when extension targets are destroyed/crashed or commands fail with missing target/session errors. (#6175) Thanks @vincentkoc.</li>
<li>Browser/CDP startup readiness: wait for CDP websocket readiness after launching Chrome and cleanly stop/reset when readiness never arrives, reducing follow-up <code>PortInUseError</code> races after <code>browser start</code>/<code>open</code>. (#29538) Thanks @AaronWander.</li>
<li>OpenAI/Responses WebSocket tool-call id hygiene: normalize blank/whitespace streamed tool-call ids before persistence, and block empty <code>function_call_output.call_id</code> payloads in the WS conversion path to avoid OpenAI 400 errors (<code>Invalid 'input[n].call_id': empty string</code>), with regression coverage for both inbound stream normalization and outbound payload guards.</li>
<li>Security/Nodes camera URL downloads: bind node <code>camera.snap</code>/<code>camera.clip</code> URL payload downloads to the resolved node host, enforce fail-closed behavior when node <code>remoteIp</code> is unavailable, and use SSRF-guarded fetch with redirect host/protocol checks to prevent off-node fetch pivots. Thanks @tdjackey for reporting.</li>
<li>Config/backups hardening: enforce owner-only (<code>0600</code>) permissions on rotated config backups and clean orphan <code>.bak.*</code> files outside the managed backup ring, reducing credential leakage risk from stale or permissive backup artifacts. (#31718) Thanks @YUJIE2002.</li>
<li>Telegram/inbound media filenames: preserve original <code>file_name</code> metadata for document/audio/video/animation downloads (with fetch/path fallbacks), so saved inbound attachments keep sender-provided names instead of opaque Telegram file paths. (#31837) Thanks @Kay-051.</li>
<li>Gateway/OpenAI chat completions: honor <code>x-openclaw-message-channel</code> when building <code>agentCommand</code> input for <code>/v1/chat/completions</code>, preserving caller channel identity instead of forcing <code>webchat</code>. (#30462) Thanks @bmendonca3.</li>
<li>Plugin SDK/runtime hardening: add package export verification in CI/release checks to catch missing runtime exports before publish-time regressions. (#28575) Thanks @Glucksberg.</li>
<li>Media/MIME normalization: normalize parameterized/case-variant MIME strings in <code>kindFromMime</code> (for example <code>Audio/Ogg; codecs=opus</code>) so WhatsApp voice notes are classified as audio and routed through transcription correctly. (#32280) Thanks @Lucenx9.</li>
<li>Discord/audio preflight mentions: detect audio attachments via Discord <code>content_type</code> and gate preflight transcription on typed text (not media placeholders), so guild voice-note mentions are transcribed and matched correctly. (#32136) Thanks @jnMetaCode.</li>
<li>Feishu/topic session routing: use <code>thread_id</code> as topic session scope fallback when <code>root_id</code> is absent, keep first-turn topic keys stable across thread creation, and force thread replies when inbound events already carry topic/thread context. (#29788) Thanks @songyaolun.</li>
<li>Gateway/Webchat NO_REPLY streaming: suppress assistant lead-fragment deltas that are prefixes of <code>NO_REPLY</code> and keep final-message buffering in sync, preventing partial <code>NO</code> leaks on silent-response runs while preserving legitimate short replies. (#32073) Thanks @liuxiaopai-ai.</li>
<li>Telegram/models picker callbacks: keep long model buttons selectable by falling back to compact callback payloads and resolving provider ids on selection (with provider re-prompt on ambiguity), avoiding Telegram 64-byte callback truncation failures. (#31857) Thanks @bmendonca3.</li>
<li>Context-window metadata warmup: add exponential config-load retry backoff (1s -> 2s -> 4s, capped at 60s) so transient startup failures recover automatically without hot-loop retries.</li>
<li>Voice-call/Twilio external outbound: auto-register webhook-first <code>outbound-api</code> calls (initiated outside OpenClaw) so media streams are accepted and call direction metadata stays accurate. (#31181) Thanks @scoootscooob.</li>
<li>Feishu/topic root replies: prefer <code>root_id</code> as outbound <code>replyTargetMessageId</code> when present, and parse millisecond <code>message_create_time</code> values correctly so topic replies anchor to the root message in grouped thread flows. (#29968) Thanks @bmendonca3.</li>
<li>Feishu/DM pairing reply target: send pairing challenge replies to <code>chat:<chat_id></code> instead of <code>user:<sender_open_id></code> so Lark/Feishu private chats with user-id-only sender payloads receive pairing messages reliably. (#31403) Thanks @stakeswky.</li>
<li>Feishu/Lark private DM routing: treat inbound <code>chat_type: "private"</code> as direct-message context for pairing/mention-forward/reaction synthetic handling so Lark private chats behave like Feishu p2p DMs. (#31400) Thanks @stakeswky.</li>
<li>Signal/message actions: allow <code>react</code> to fall back to <code>toolContext.currentMessageId</code> when <code>messageId</code> is omitted, matching Telegram behavior and unblocking agent-initiated reactions on inbound turns. (#32217) Thanks @dunamismax.</li>
<li>Discord/message actions: allow <code>react</code> to fall back to <code>toolContext.currentMessageId</code> when <code>messageId</code> is omitted, matching Telegram/Signal reaction ergonomics in inbound turns.</li>
<li>Synology Chat/reply delivery: resolve webhook usernames to Chat API <code>user_id</code> values for outbound chatbot replies, avoiding mismatches between webhook user IDs and <code>method=chatbot</code> recipient IDs in multi-account setups. (#23709) Thanks @druide67.</li>
<li>Slack/thread context payloads: only inject thread starter/history text on first thread turn for new sessions while preserving thread metadata, reducing repeated context-token bloat on long-lived thread sessions. (#32133) Thanks @sourman.</li>
<li>Slack/session routing: keep top-level channel messages in one shared session when <code>replyToMode=off</code>, while preserving thread-scoped keys for true thread replies and non-off modes. (#32193) Thanks @bmendonca3.</li>
<li>Voice-call/webhook routing: require exact webhook path matches (instead of prefix matches) so lookalike paths cannot reach provider verification/dispatch logic. (#31930) Thanks @afurm.</li>
<li>Zalo/Pairing auth tests: add webhook regression coverage asserting DM pairing-store reads/writes remain account-scoped, preventing cross-account authorization bleed in multi-account setups. (#26121) Thanks @bmendonca3.</li>
<li>Zalouser/Pairing auth tests: add account-scoped DM pairing-store regression coverage (<code>monitor.account-scope.test.ts</code>) to prevent cross-account allowlist bleed in multi-account setups. (#26672) Thanks @bmendonca3.</li>
<li>Feishu/Send target prefixes: normalize explicit <code>group:</code>/<code>dm:</code> send targets and preserve explicit receive-id routing hints when resolving outbound Feishu targets. (#31594) Thanks @liuxiaopai-ai.</li>
<li>Webchat/Feishu session continuation: preserve routable <code>OriginatingChannel</code>/<code>OriginatingTo</code> metadata from session delivery context in <code>chat.send</code>, and prefer provider-normalized channel when deciding cross-channel route dispatch so Webchat replies continue on the selected Feishu session instead of falling back to main/internal session routing. (#31573)</li>
<li>Telegram/implicit mention forum handling: exclude Telegram forum system service messages (<code>forum_topic_*</code>, <code>general_forum_topic_*</code>) from reply-chain implicit mention detection so <code>requireMention</code> does not get bypassed inside bot-created topic lifecycle events. (#32262) Thanks @scoootscooob.</li>
<li>Slack/inbound debounce routing: isolate top-level non-DM message debounce keys by message timestamp to avoid cross-thread collisions, preserve DM batching, and flush pending top-level buffers before immediate non-debounce follow-ups to keep ordering stable. (#31951) Thanks @scoootscooob.</li>
<li>Feishu/Duplicate replies: suppress same-target reply dispatch when message-tool sends use generic provider metadata (<code>provider: "message"</code>) and normalize <code>lark</code>/<code>feishu</code> provider aliases during duplicate-target checks, preventing double-delivery in Feishu sessions. (#31526)</li>
<li>Webchat/silent token leak: filter assistant <code>NO_REPLY</code>-only transcript entries from <code>chat.history</code> responses and add client-side defense-in-depth guards in the chat controller so internal silent tokens never render as visible chat bubbles. (#32015) Consolidates overlap from #32183, #32082, #32045, #32052, #32172, and #32112. Thanks @ademczuk, @liuxiaopai-ai, @ningding97, @bmendonca3, and @x4v13r1120.</li>
<li>Doctor/local memory provider checks: stop false-positive local-provider warnings when <code>provider=local</code> and no explicit <code>modelPath</code> is set by honoring default local model fallback while still warning when gateway probe reports local embeddings not ready. (#32014) Fixes #31998. Thanks @adhishthite.</li>
<li>Media understanding/parakeet CLI output parsing: read <code>parakeet-mlx</code> transcripts from <code>--output-dir/<media-basename>.txt</code> when txt output is requested (or default), with stdout fallback for non-txt formats. (#9177) Thanks @mac-110.</li>
<li>Media understanding/audio transcription guard: skip tiny/empty audio files (<1024 bytes) before provider/CLI transcription to avoid noisy invalid-audio failures and preserve clean fallback behavior. (#8388) Thanks @Glucksberg.</li>
<li>Gateway/Plugin HTTP route precedence: run explicit plugin HTTP routes before the Control UI SPA catch-all so registered plugin webhook/custom paths remain reachable, while unmatched paths still fall through to Control UI handling. (#31885) Thanks @Sid-Qin.</li>
<li>Gateway/Node browser proxy routing: honor <code>profile</code> from <code>browser.request</code> JSON body when query params omit it, while preserving query-profile precedence when both are present. (#28852) Thanks @Sid-Qin.</li>
<li>Gateway/Control UI basePath POST handling: return 405 for <code>POST</code> on exact basePath routes (for example <code>/openclaw</code>) instead of redirecting, and add end-to-end regression coverage that root-mounted webhook POST paths still pass through to plugin handlers. (#31349) Thanks @Sid-Qin.</li>
<li>Browser/default profile selection: default <code>browser.defaultProfile</code> behavior now prefers <code>openclaw</code> (managed standalone CDP) when no explicit default is configured, while still auto-provisioning the <code>chrome</code> relay profile for explicit opt-in use. (#32031) Fixes #31907. Thanks @liuxiaopai-ai.</li>
<li>Sandbox/mkdirp boundary checks: allow existing in-boundary directories to pass mkdirp boundary validation when directory open probes return platform-specific I/O errors, with regression coverage for directory-safe fallback behavior. (#31547) Thanks @stakeswky.</li>
<li>Models/config env propagation: apply <code>config.env.vars</code> before implicit provider discovery in models bootstrap so config-scoped credentials are visible to implicit provider resolution paths. (#32295) Thanks @hsiaoa.</li>
<li>Models/Codex usage labels: infer weekly secondary usage windows from reset cadence when API window seconds are ambiguously reported as 24h, so <code>openclaw models status</code> no longer mislabels weekly limits as daily. (#31938) Thanks @bmendonca3.</li>
<li>Gateway/Heartbeat model reload: treat <code>models.*</code> and <code>agents.defaults.model</code> config updates as heartbeat hot-reload triggers so heartbeat picks up model changes without a full gateway restart. (#32046) Thanks @stakeswky.</li>
<li>Memory/LanceDB embeddings: forward configured <code>embedding.dimensions</code> into OpenAI embeddings requests so vector size and API output dimensions stay aligned when dimensions are explicitly configured. (#32036) Thanks @scotthuang.</li>
<li>Gateway/Control UI method guard: allow POST requests to non-UI routes to fall through when no base path is configured, and add POST regression coverage for fallthrough and base-path 405 behavior. (#23970) Thanks @tyler6204.</li>
<li>Browser/CDP status accuracy: require a successful <code>Browser.getVersion</code> response over the CDP websocket (not just socket-open) before reporting <code>cdpReady</code>, so stale idle command channels are surfaced as unhealthy. (#23427) Thanks @vincentkoc.</li>
<li>Daemon/systemd checks in containers: treat missing <code>systemctl</code> invocations (including <code>spawn systemctl ENOENT</code>/<code>EACCES</code>) as unavailable service state during <code>is-enabled</code> checks, preventing container flows from failing with <code>Gateway service check failed</code> before install/status handling can continue. (#26089) Thanks @sahilsatralkar and @vincentkoc.</li>
<li>Security/Node exec approvals: revalidate approval-bound <code>cwd</code> identity immediately before execution/forwarding and fail closed with an explicit denial when <code>cwd</code> drifts after approval hardening.</li>
<li>Security audit/skills workspace hardening: add <code>skills.workspace.symlink_escape</code> warning in <code>openclaw security audit</code> when workspace <code>skills/**/SKILL.md</code> resolves outside the workspace root (for example symlink-chain drift), plus docs coverage in the security glossary.</li>
<li>Security/Node exec approvals: preserve shell/dispatch-wrapper argv semantics during approval hardening so approved wrapper commands (for example <code>env sh -c ...</code>) cannot drift into a different runtime command shape, and add regression coverage for both approval-plan generation and approved runtime execution paths. Thanks @tdjackey for reporting.</li>
<li>Security/fs-safe write hardening: make <code>writeFileWithinRoot</code> use same-directory temp writes plus atomic rename, add post-write inode/hardlink revalidation with security warnings on boundary drift, and avoid truncating existing targets when final rename fails.</li>
<li>Security/Skills archive extraction: unify tar extraction safety checks across tar.gz and tar.bz2 install flows, enforce tar compressed-size limits, and fail closed if tar.bz2 archives change between preflight and extraction to prevent bypasses of entry-type/size guardrails. Thanks @GCXWLP for reporting.</li>
<li>Security/Prompt spoofing hardening: stop injecting queued runtime events into user-role prompt text, route them through trusted system-prompt context, and neutralize inbound spoof markers like <code>[System Message]</code> and line-leading <code>System:</code> in untrusted message content. (#30448)</li>
<li>Sandbox/Docker setup command parsing: accept <code>agents.*.sandbox.docker.setupCommand</code> as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai.</li>
<li>Sandbox/Bootstrap context boundary hardening: reject symlink/hardlink alias bootstrap seed files that resolve outside the source workspace and switch post-compaction <code>AGENTS.md</code> context reads to boundary-verified file opens, preventing host file content from being injected via workspace aliasing. Thanks @tdjackey for reporting.</li>
<li>Agents/Sandbox workdir mapping: map container workdir paths (for example <code>/workspace</code>) back to the host workspace before sandbox path validation so exec requests keep the intended directory in containerized runs instead of falling back to an unavailable host path. (#31841) Thanks @liuxiaopai-ai.</li>
<li>Docker/Sandbox bootstrap hardening: make <code>OPENCLAW_SANDBOX</code> opt-in parsing explicit (<code>1|true|yes|on</code>), support custom Docker socket paths via <code>OPENCLAW_DOCKER_SOCKET</code>, defer docker.sock exposure until sandbox prerequisites pass, and reset/roll back persisted sandbox mode to <code>off</code> when setup is skipped or partially fails to avoid stale broken sandbox state. (#29974) Thanks @jamtujest and @vincentkoc.</li>
<li>Hooks/webhook ACK compatibility: return <code>200</code> (instead of <code>202</code>) for successful <code>/hooks/agent</code> requests so providers that require <code>200</code> (for example Forward Email) accept dispatched agent hook deliveries. (#28204) Thanks @Glucksberg.</li>
<li>Feishu/Run channel fallback: prefer <code>Provider</code> over <code>Surface</code> when inferring queued run <code>messageProvider</code> fallback (when <code>OriginatingChannel</code> is missing), preventing Feishu turns from being mislabeled as <code>webchat</code> in mixed relay metadata contexts. (#31880) Fixes #31859. Thanks @liuxiaopai-ai.</li>
<li>Skills/sherpa-onnx-tts: run the <code>sherpa-onnx-tts</code> bin under ESM (replace CommonJS <code>require</code> imports) and add regression coverage to prevent <code>require is not defined in ES module scope</code> startup crashes. (#31965) Thanks @bmendonca3.</li>
<li>Inbound metadata/direct relay context: restore direct-channel conversation metadata blocks for external channels (for example WhatsApp) while preserving webchat-direct suppression, so relay agents recover sender/message identifiers without reintroducing internal webchat metadata noise. (#31969) Fixes #29972. Thanks @Lucenx9.</li>
<li>Slack/Channel message subscriptions: register explicit <code>message.channels</code> and <code>message.groups</code> monitor handlers (alongside generic <code>message</code>) so channel/group event subscriptions are consumed even when Slack dispatches typed message event names. Fixes #31674.</li>
<li>Hooks/session-scoped memory context: expose ephemeral <code>sessionId</code> in embedded plugin tool contexts and <code>before_tool_call</code>/<code>after_tool_call</code> hook contexts (including compaction and client-tool wiring) so plugins can isolate per-conversation state across <code>/new</code> and <code>/reset</code>. Related #31253 and #31304. Thanks @Sid-Qin and @Servo-AIpex.</li>
<li>Voice-call/Twilio inbound greeting: run answered-call initial notify greeting for Twilio instead of skipping the manager speak path, with regression coverage for both Twilio and Plivo notify flows. (#29121) Thanks @xinhuagu.</li>
<li>Voice-call/stale call hydration: verify active calls with the provider before loading persisted in-progress calls so stale locally persisted records do not block or misroute new call handling after restarts. (#4325) Thanks @garnetlyx.</li>
<li>Feishu/File upload filenames: percent-encode non-ASCII/special-character <code>file_name</code> values in Feishu multipart uploads so Chinese/symbol-heavy filenames are sent as proper attachments instead of plain text links. (#31179) Thanks @Kay-051.</li>
<li>Media/MIME channel parity: route Telegram/Signal/iMessage media-kind checks through normalized <code>kindFromMime</code> so mixed-case/parameterized MIME values classify consistently across message channels.</li>
<li>WhatsApp/inbound self-message context: propagate inbound <code>fromMe</code> through the web inbox pipeline and annotate direct self messages as <code>(self)</code> in envelopes so agents can distinguish owner-authored turns from contact turns. (#32167) Thanks @scoootscooob.</li>
<li>Webchat/stream finalization: persist streamed assistant text when final events omit <code>message</code>, while keeping final payload precedence and skipping empty stream buffers to prevent disappearing replies after tool turns. (#31920) Thanks @Sid-Qin.</li>
<li>Feishu/Inbound ordering: serialize message handling per chat while preserving cross-chat concurrency to avoid same-chat race drops under bursty inbound traffic. (#31807)</li>
<li>Feishu/Typing notification suppression: skip typing keepalive reaction re-adds when the indicator is already active, preventing duplicate notification pings from repeated identical emoji adds. (#31580)</li>
<li>Feishu/Probe failure backoff: cache API and timeout probe failures for one minute per account key while preserving abort-aware probe timeouts, reducing repeated health-check retries during transient credential/network outages. (#29970)</li>
<li>Feishu/Streaming block fallback: preserve markdown block stream text as final streaming-card content when final payload text is missing, while still suppressing non-card internal block chunk delivery. (#30663)</li>
<li>Feishu/Bitable API errors: unify Feishu Bitable tool error handling with structured <code>LarkApiError</code> responses and consistent API/context attribution across wiki/base metadata, field, and record operations. (#31450)</li>
<li>Feishu/Missing-scope grant URL fix: rewrite known invalid scope aliases (<code>contact:contact.base:readonly</code>) to valid scope names in permission grant links, so remediation URLs open with correct Feishu consent scopes. (#31943)</li>
<li>BlueBubbles/Message metadata: harden send response ID extraction, include sender identity in DM context, and normalize inbound <code>message_id</code> selection to avoid duplicate ID metadata. (#23970) Thanks @tyler6204.</li>
<li>WebChat/markdown tables: ensure GitHub-flavored markdown table parsing is explicitly enabled at render time and add horizontal overflow handling for wide tables, with regression coverage for table-only and mixed text+table content. (#32365) Thanks @BlueBirdBack.</li>
<li>Feishu/default account resolution: always honor explicit <code>channels.feishu.defaultAccount</code> during outbound account selection (including top-level-credential setups where the preferred id is not present in <code>accounts</code>), instead of silently falling back to another account id. (#32253) Thanks @bmendonca3.</li>
<li>Feishu/Sender lookup permissions: suppress user-facing grant prompts for stale non-existent scope errors (<code>contact:contact.base:readonly</code>) during best-effort sender-name resolution so inbound messages continue without repeated false permission notices. (#31761)</li>
<li>Discord/dispatch + Slack formatting: restore parallel outbound dispatch across Discord channels with per-channel queues while preserving in-channel ordering, and run Slack preview/stream update text through mrkdwn normalization for consistent formatting. (#31927) Thanks @Sid-Qin.</li>
<li>Feishu/Inbound debounce: debounce rapid same-chat sender bursts into one ordered dispatch turn, skip already-processed retries when composing merged text, and preserve bot-mention intent across merged entries to reduce duplicate or late inbound handling. (#31548)</li>
<li>Tests/Sandbox + archive portability: use junction-compatible directory-link setup on Windows and explicit file-symlink platform guards in symlink escape tests where unprivileged file symlinks are unavailable, reducing false Windows CI failures while preserving traversal checks on supported paths. (#28747) Thanks @arosstale.</li>
<li>Browser/Extension re-announce reliability: keep relay state in <code>connecting</code> when re-announce forwarding fails and extend debugger re-attach retries after navigation to reduce false attached states and post-nav disconnect loops. (#27630) Thanks @markmusson.</li>
<li>Browser/Act request compatibility: accept legacy flattened <code>action="act"</code> params (<code>kind/ref/text/...</code>) in addition to <code>request={...}</code> so browser act calls no longer fail with <code>request required</code>. (#15120) Thanks @vincentkoc.</li>
<li>OpenRouter/x-ai compatibility: skip <code>reasoning.effort</code> injection for <code>x-ai/*</code> models (for example Grok) so OpenRouter requests no longer fail with invalid-arguments errors on unsupported reasoning params. (#32054) Thanks @scoootscooob.</li>
<li>Models/openai-completions developer-role compatibility: force <code>supportsDeveloperRole=false</code> for non-native endpoints, treat unparseable <code>baseUrl</code> values as non-native, and add regression coverage for empty/malformed baseUrl plus explicit-true override behavior. (#29479) thanks @akramcodez.</li>
<li>Browser/Profile attach-only override: support <code>browser.profiles.<name>.attachOnly</code> (fallback to global <code>browser.attachOnly</code>) so loopback proxy profiles can skip local launch/port-ownership checks without forcing attach-only mode for every profile. (#20595) Thanks @unblockedgamesstudio and @vincentkoc.</li>
<li>Sessions/Lock recovery: detect recycled Linux PIDs by comparing lock-file <code>starttime</code> with <code>/proc/<pid>/stat</code> starttime, so stale <code>.jsonl.lock</code> files are reclaimed immediately in containerized PID-reuse scenarios while preserving compatibility for older lock files. (#26443) Fixes #27252. Thanks @HirokiKobayashi-R and @vincentkoc.</li>
<li>Cron/isolated delivery target fallback: remove early unresolved-target return so cron delivery can flow through shared outbound target resolution (including per-channel <code>resolveDefaultTo</code> fallback) when <code>delivery.to</code> is omitted. (#32364) Thanks @hclsys.</li>
<li>OpenAI media capabilities: include <code>audio</code> in the OpenAI provider capability list so audio transcription models are eligible in media-understanding provider selection. (#12717) Thanks @openjay.</li>
<li>Browser/Managed tab cap: limit loopback managed <code>openclaw</code> page tabs to 8 via best-effort cleanup after tab opens to reduce long-running renderer buildup while preserving attach-only and remote profile behavior. (#29724) Thanks @pandego.</li>
<li>Docker/Image health checks: add Dockerfile <code>HEALTHCHECK</code> that probes gateway <code>GET /healthz</code> so container runtimes can mark unhealthy instances without requiring auth credentials in the probe command. (#11478) Thanks @U-C4N and @vincentkoc.</li>
<li>Gateway/Node dangerous-command parity: include <code>sms.send</code> in default onboarding node <code>denyCommands</code>, share onboarding deny defaults with the gateway dangerous-command source of truth, and include <code>sms.send</code> in phone-control <code>/phone arm writes</code> handling so SMS follows the same break-glass flow as other dangerous node commands. Thanks @zpbrent.</li>
<li>Pairing/AllowFrom account fallback: handle omitted <code>accountId</code> values in <code>readChannelAllowFromStore</code> and <code>readChannelAllowFromStoreSync</code> as <code>default</code>, while preserving legacy unscoped allowFrom merges for default-account flows. Thanks @Sid-Qin and @vincentkoc.</li>
<li>Browser/Remote CDP ownership checks: skip local-process ownership errors for non-loopback remote CDP profiles when HTTP is reachable but the websocket handshake fails, and surface the remote websocket attach/retry path instead. (#15582) Landed from contributor (#28780) Thanks @stubbi, @bsormagec, @unblockedgamesstudio and @vincentkoc.</li>
<li>Browser/CDP proxy bypass: force direct loopback agent paths and scoped <code>NO_PROXY</code> expansion for localhost CDP HTTP/WS connections when proxy env vars are set, so browser relay/control still works behind global proxy settings. (#31469) Thanks @widingmarcus-cyber.</li>
<li>Sessions/idle reset correctness: preserve existing <code>updatedAt</code> during inbound metadata-only writes so idle-reset boundaries are not unintentionally refreshed before actual user turns. (#32379) Thanks @romeodiaz.</li>
<li>Sessions/lock recovery: reclaim orphan legacy same-PID lock files missing <code>starttime</code> when no in-process lock ownership exists, avoiding false lock timeouts after PID reuse while preserving active lock safety checks. (#32081) Thanks @bmendonca3.</li>
<li>Sessions/store cache invalidation: reload cached session stores when file size changes within the same mtime tick by keying cache validation on a single file-stat snapshot (<code>mtimeMs</code> + <code>sizeBytes</code>), with regression coverage for same-tick rewrites. (#32191) Thanks @jalehman.</li>
<li>Agents/Subagents <code>sessions_spawn</code>: reject malformed <code>agentId</code> inputs before normalization (for example error-message/path-like strings) to prevent unintended synthetic agent IDs and ghost workspace/session paths; includes strict validation regression coverage. (#31381) Thanks @openperf.</li>
<li>CLI/installer Node preflight: enforce Node.js <code>v22.12+</code> consistently in both <code>openclaw.mjs</code> runtime bootstrap and installer active-shell checks, with actionable nvm recovery guidance for mismatched shell PATH/defaults. (#32356) Thanks @jasonhargrove.</li>
<li>Web UI/config form: support SecretInput string-or-secret-ref unions in map <code>additionalProperties</code>, so provider API key fields stay editable instead of being marked unsupported. (#31866) Thanks @ningding97.</li>
<li>Auto-reply/inline command cleanup: preserve newline structure when stripping inline <code>/status</code> and extracting inline slash commands by collapsing only horizontal whitespace, preventing paragraph flattening in multi-line replies. (#32224) Thanks @scoootscooob.</li>
<li>Config/raw redaction safety: preserve non-sensitive literals during raw redaction round-trips, scope SecretRef redaction to secret IDs (not structural fields like <code>source</code>/<code>provider</code>), and fall back to structured raw redaction when text replacement cannot restore the original config shape. (#32174) Thanks @bmendonca3.</li>
<li>Hooks/runtime stability: keep the internal hook handler registry on a <code>globalThis</code> singleton so hook registration/dispatch remains consistent when bundling emits duplicate module copies. (#32292) Thanks @Drickon.</li>
<li>Hooks/after_tool_call: include embedded session context (<code>sessionKey</code>, <code>agentId</code>) and fire the hook exactly once per tool execution by removing duplicate adapter-path dispatch in embedded runs. (#32201) Thanks @jbeno, @scoootscooob, @vincentkoc.</li>
<li>Hooks/tool-call correlation: include <code>runId</code> and <code>toolCallId</code> in plugin tool hook payloads/context and scope tool start/adjusted-param tracking by run to prevent cross-run collisions in <code>before_tool_call</code> and <code>after_tool_call</code>. (#32360) Thanks @vincentkoc.</li>
<li>Plugins/install diagnostics: reject legacy plugin package shapes without <code>openclaw.extensions</code> and return an explicit upgrade hint with troubleshooting docs for repackaging. (#32055) Thanks @liuxiaopai-ai.</li>
<li>Hooks/plugin context parity: ensure <code>llm_input</code> hooks in embedded attempts receive the same <code>trigger</code> and <code>channelId</code>-aware <code>hookCtx</code> used by the other hook phases, preserving channel/trigger-scoped plugin behavior. (#28623) Thanks @davidrudduck and @vincentkoc.</li>
<li>Plugins/hardlink install compatibility: allow bundled plugin manifests and entry files to load when installed via hardlink-based package managers (<code>pnpm</code>, <code>bun</code>) while keeping hardlink rejection enabled for non-bundled plugin sources. (#32119) Fixes #28175, #28404, #29455. Thanks @markfietje.</li>
<li>Cron/session reaper reliability: move cron session reaper sweeps into <code>onTimer</code> <code>finally</code> and keep pruning active even when timer ticks fail early (for example cron store parse failures), preventing stale isolated run sessions from accumulating indefinitely. (#31996) Fixes #31946. Thanks @scoootscooob.</li>
<li>Cron/HEARTBEAT_OK summary leak: suppress fallback main-session enqueue for heartbeat/internal ack summaries in isolated announce mode so <code>HEARTBEAT_OK</code> noise never appears in user chat while real summaries still forward. (#32093) Thanks @scoootscooob.</li>
<li>Authentication: classify <code>permission_error</code> as <code>auth_permanent</code> for profile fallback. (#31324) Thanks @Sid-Qin.</li>
<li>Agents/host edit reliability: treat host edit-tool throws as success only when on-disk post-check confirms replacement likely happened (<code>newText</code> present and <code>oldText</code> absent), preventing false failure reports while avoiding pre-write false positives. (#32383) Thanks @polooooo.</li>
<li>Plugins/install fallback safety: resolve bare install specs to bundled plugin ids before npm lookup (for example <code>diffs</code> -> bundled <code>@openclaw/diffs</code>), keep npm fallback limited to true package-not-found errors, and continue rejecting non-plugin npm packages that fail manifest validation. (#32096) Thanks @scoootscooob.</li>
<li>Web UI/inline code copy fidelity: disable forced mid-token wraps on inline <code><code></code> spans so copied UUID/hash/token strings preserve exact content instead of inserting line-break spaces. (#32346) Thanks @hclsys.</li>
<li>Restart sentinel formatting: avoid duplicate <code>Reason:</code> lines when restart message text already matches <code>stats.reason</code>, keeping restart notifications concise for users and downstream parsers. (#32083) Thanks @velamints2.</li>
<li>Auto-reply/followup queue: avoid stale callback reuse across idle-window restarts by caching the followup runner only when a drain actually starts, preserving enqueue ordering after empty-finalize paths. (#31902) Thanks @Lanfei.</li>
<li>Agents/tool-result guard: always clear pending tool-call state on interruptions even when synthetic tool results are disabled, preventing orphaned tool-use transcripts that cause follow-up provider request failures. (#32120) Thanks @jnMetaCode.</li>
<li>Failover/error classification: treat HTTP <code>529</code> (provider overloaded, common with Anthropic-compatible APIs) as <code>rate_limit</code> so model failover can engage instead of misclassifying the error path. (#31854) Thanks @bugkill3r.</li>
<li>Logging: use local time for logged timestamps instead of UTC, aligning log output with documented local timezone behavior and avoiding confusion during local diagnostics. (#28434) Thanks @liuy.</li>
<li>Agents/Subagent announce cleanup: keep completion-message runs pending while descendants settle, add a 30 minute hard-expiry backstop to avoid indefinite pending state, and keep retry bookkeeping resumable across deferred wakes. (#23970) Thanks @tyler6204.</li>
<li>Secrets/exec resolver timeout defaults: use provider <code>timeoutMs</code> as the default inactivity (<code>noOutputTimeoutMs</code>) watchdog for exec secret providers, preventing premature no-output kills for resolvers that start producing output after 2s. (#32235) Thanks @bmendonca3.</li>
<li>Auto-reply/reminder guard note suppression: when a turn makes reminder-like commitments but schedules no new cron jobs, suppress the unscheduled-reminder warning note only if an enabled cron already exists for the same session; keep warnings for unrelated sessions, disabled jobs, or unreadable cron store paths. (#32255) Thanks @scoootscooob.</li>
<li>Cron/isolated announce heartbeat suppression: treat multi-payload runs as skippable when any payload is a heartbeat ack token and no payload has media, preventing internal narration + trailing <code>HEARTBEAT_OK</code> from being delivered to users. (#32131) Thanks @adhishthite.</li>
<li>Cron/store migration: normalize legacy cron jobs with string <code>schedule</code> and top-level <code>command</code>/<code>timeout</code> fields into canonical schedule/payload/session-target shape on load, preventing schedule-error loops on old persisted stores. (#31926) Thanks @bmendonca3.</li>
<li>Tests/Windows backup rotation: skip chmod-only backup permission assertions on Windows while retaining compose/rotation/prune coverage across platforms to avoid false CI failures from Windows non-POSIX mode semantics. (#32286) Thanks @jalehman.</li>
<li>Tests/Subagent announce: set <code>OPENCLAW_TEST_FAST=1</code> before importing <code>subagent-announce</code> format suites so module-level fast-mode constants are captured deterministically on Windows CI, preventing timeout flakes in nested completion announce coverage. (#31370) Thanks @zwffff.</li>
<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.2/OpenClaw-2026.3.2.zip" length="23181513" type="application/octet-stream" sparkle:edSignature="THMgkcoMgz2vv5zse3Po3K7l3Or2RhBKurXZIi8iYVXN76yJy1YXAY6kXi6ovD+dbYn68JKYDIKA1Ya78bO7BQ=="/>
<!-- pragma: allowlist secret -->
<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.1</title>
<pubDate>Mon, 02 Mar 2026 04:40:59 +0000</pubDate>
<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>2026030190</sparkle:version>
<sparkle:shortVersionString>2026.3.1</sparkle:shortVersionString>
<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.1</h2>
<description><![CDATA[<h2>OpenClaw 2026.3.8-beta.1</h2>
<h3>Changes</h3>
<ul>
<li>Agents/Thinking defaults: set <code>adaptive</code> as the default thinking level for Anthropic Claude 4.6 models (including Bedrock Claude 4.6 refs) while keeping other reasoning-capable models at <code>low</code> unless explicitly configured.</li>
<li>Gateway/Container probes: add built-in HTTP liveness/readiness endpoints (<code>/health</code>, <code>/healthz</code>, <code>/ready</code>, <code>/readyz</code>) for Docker/Kubernetes health checks, with fallback routing so existing handlers on those paths are not shadowed. (#31272) Thanks @vincentkoc.</li>
<li>Android/Nodes: add <code>camera.list</code>, <code>device.permissions</code>, <code>device.health</code>, and <code>notifications.actions</code> (<code>open</code>/<code>dismiss</code>/<code>reply</code>) on Android nodes, plus first-class node-tool actions for the new device/notification commands. (#28260) Thanks @obviyus.</li>
<li>Discord/Thread bindings: replace fixed TTL lifecycle with inactivity (<code>idleHours</code>, default 24h) plus optional hard <code>maxAgeHours</code> lifecycle controls, and add <code>/session idle</code> + <code>/session max-age</code> commands for focused thread-bound sessions. (#27845) Thanks @osolmaz.</li>
<li>Telegram/DM topics: add per-DM <code>direct</code> + topic config (allowlists, <code>dmPolicy</code>, <code>skills</code>, <code>systemPrompt</code>, <code>requireTopic</code>), route DM topics as distinct inbound/outbound sessions, and enforce topic-aware authorization/debounce for messages, callbacks, commands, and reactions. Landed from contributor PR #30579 by @kesor. Thanks @kesor.</li>
<li>Web UI/Cron i18n: localize cron page labels, filters, form help text, and validation/error messaging in English and zh-CN. (#29315) Thanks @BUGKillerKing.</li>
<li>OpenAI/Streaming transport: make <code>openai</code> Responses WebSocket-first by default (<code>transport: "auto"</code> with SSE fallback), add shared OpenAI WS stream/connection runtime wiring with per-session cleanup, and preserve server-side compaction payload mutation (<code>store</code> + <code>context_management</code>) on the WS path.</li>
<li>Android/Gateway capability refresh: add live Android capability integration coverage and node canvas capability refresh wiring, plus runtime hardening for A2UI readiness retries, scoped canvas URL normalization, debug diagnostics JSON, and JavaScript MIME delivery. (#28388) Thanks @obviyus.</li>
<li>Android/Nodes parity: add <code>system.notify</code>, <code>photos.latest</code>, <code>contacts.search</code>/<code>contacts.add</code>, <code>calendar.events</code>/<code>calendar.add</code>, and <code>motion.activity</code>/<code>motion.pedometer</code>, with motion sensor-aware command gating and improved activity sampling reliability. (#29398) Thanks @obviyus.</li>
<li>CLI/Config: add <code>openclaw config file</code> to print the active config file path resolved from <code>OPENCLAW_CONFIG_PATH</code> or the default location. (#26256) thanks @cyb1278588254.</li>
<li>Feishu/Docx tables + uploads: add <code>feishu_doc</code> actions for Docx table creation/cell writing (<code>create_table</code>, <code>write_table_cells</code>, <code>create_table_with_values</code>) and image/file uploads (<code>upload_image</code>, <code>upload_file</code>) with stricter create/upload error handling for missing <code>document_id</code> and placeholder cleanup failures. (#20304) Thanks @xuhao1.</li>
<li>Feishu/Reactions: add inbound <code>im.message.reaction.created_v1</code> handling, route verified reactions through synthetic inbound turns, and harden verification with timeout + fail-closed filtering so non-bot or unverified reactions are dropped. (#16716) Thanks @schumilin.</li>
<li>Feishu/Chat tooling: add <code>feishu_chat</code> tool actions for chat info and member queries, with configurable enablement under <code>channels.feishu.tools.chat</code>. (#14674) Thanks @liuweifly.</li>
<li>Feishu/Doc permissions: support optional owner permission grant fields on <code>feishu_doc</code> create and report permission metadata only when the grant call succeeds, with regression coverage for success/failure/omitted-owner paths. (#28295) Thanks @zhoulongchao77.</li>
<li>Web UI/i18n: add German (<code>de</code>) locale support and auto-render language options from supported locale constants in Overview settings. (#28495) thanks @dsantoreis.</li>
<li>Tools/Diffs: add a new optional <code>diffs</code> plugin tool for read-only diff rendering from before/after text or unified patches, with gateway viewer URLs for canvas and PNG image output. Thanks @gumadeiras.</li>
<li>Memory/LanceDB: support custom OpenAI <code>baseUrl</code> and embedding dimensions for LanceDB memory. (#17874) Thanks @rish2jain and @vincentkoc.</li>
<li>ACP/ACPX streaming: pin ACPX plugin support to <code>0.1.15</code>, add configurable ACPX command/version probing, and streamline ACP stream delivery (<code>final_only</code> default + reduced tool-event noise) with matching runtime and test updates. (#30036) Thanks @osolmaz.</li>
<li>Shell env markers: set <code>OPENCLAW_SHELL</code> across shell-like runtimes (<code>exec</code>, <code>acp</code>, <code>acp-client</code>, <code>tui-local</code>) so shell startup/config rules can target OpenClaw contexts consistently, and document the markers in env/exec/acp/TUI docs. Thanks @vincentkoc.</li>
<li>Cron/Heartbeat light bootstrap context: add opt-in lightweight bootstrap mode for automation runs (<code>--light-context</code> for cron agent turns and <code>agents.*.heartbeat.lightContext</code> for heartbeat), keeping only <code>HEARTBEAT.md</code> for heartbeat runs and skipping bootstrap-file injection for cron lightweight runs. (#26064) Thanks @jose-velez.</li>
<li>OpenAI/WebSocket warm-up: add optional OpenAI Responses WebSocket warm-up (<code>response.create</code> with <code>generate:false</code>), enable it by default for <code>openai/*</code>, and expose <code>params.openaiWsWarmup</code> for per-model enable/disable control.</li>
<li>Agents/Subagents runtime events: replace ad-hoc subagent completion system-message handoff with typed internal completion events (<code>task_completion</code>) that are rendered consistently across direct and queued announce paths, with gateway/CLI plumbing for structured <code>internalEvents</code>.</li>
</ul>
<h3>Breaking</h3>
<ul>
<li><strong>BREAKING:</strong> Node exec approval payloads now require <code>systemRunPlan</code>. <code>host=node</code> approval requests without that plan are rejected.</li>
<li><strong>BREAKING:</strong> Node <code>system.run</code> execution now pins path-token commands to the canonical executable path (<code>realpath</code>) in both allowlist and approval execution flows. Integrations/tests that asserted token-form argv (for example <code>tr</code>) must now accept canonical paths (for example <code>/usr/bin/tr</code>).</li>
<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>Android/Nodes reliability: reject <code>facing=both</code> when <code>deviceId</code> is set to avoid mislabeled duplicate captures, allow notification <code>open</code>/<code>reply</code> on non-clearable entries while still gating dismiss, trigger listener rebind before notification actions, and scale invoke-result ack timeout to invoke budget for large clip payloads. (#28260) Thanks @obviyus.</li>
<li>Windows/Plugin install: avoid <code>spawn EINVAL</code> on Windows npm/npx invocations by resolving to <code>node</code> + npm CLI scripts instead of spawning <code>.cmd</code> directly. Landed from contributor PR #31147 by @codertony. Thanks @codertony.</li>
<li>LINE/Voice transcription: classify M4A voice media as <code>audio/mp4</code> (not <code>video/mp4</code>) by checking the MPEG-4 <code>ftyp</code> major brand (<code>M4A </code> / <code>M4B </code>), restoring voice transcription for LINE voice messages. Landed from contributor PR #31151 by @scoootscooob. Thanks @scoootscooob.</li>
<li>Slack/Announce target account routing: enable session-backed announce-target lookup for Slack so multi-account announces resolve the correct <code>accountId</code> instead of defaulting to bot-token context. Landed from contributor PR #31028 by @taw0002. Thanks @taw0002.</li>
<li>Android/Voice screen TTS: stream assistant speech via ElevenLabs WebSocket in Talk Mode, stop cleanly on speaker mute/barge-in, and ignore stale out-of-order stream events. (#29521) Thanks @gregmousseau.</li>
<li>Android/Photos permissions: declare Android 14+ selected-photo access permission (<code>READ_MEDIA_VISUAL_USER_SELECTED</code>) and align Android permission/settings paths with current minSdk behavior for more reliable permission state handling.</li>
<li>Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.</li>
<li>Cron/Delivery: disable the agent messaging tool when <code>delivery.mode</code> is <code>"none"</code> so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.</li>
<li>CLI/Cron: clarify <code>cron list</code> output by renaming <code>Agent</code> to <code>Agent ID</code> and adding a <code>Model</code> column for isolated agent-turn jobs. (#26259) Thanks @openperf.</li>
<li>Feishu/Reply media attachments: send Feishu reply <code>mediaUrl</code>/<code>mediaUrls</code> payloads as attachments alongside text/streamed replies in the reply dispatcher, including legacy fallback when <code>mediaUrls</code> is empty. (#28959) Thanks @icesword0760.</li>
<li>Slack/User-token resolution: normalize Slack account user-token sourcing through resolved account metadata (<code>SLACK_USER_TOKEN</code> env + config) so monitor reads, Slack actions, directory lookups, onboarding allow-from resolution, and capabilities probing consistently use the effective user token. (#28103) Thanks @Glucksberg.</li>
<li>Feishu/Outbound session routing: stop assuming bare <code>oc_</code> identifiers are always group chats, honor explicit <code>dm:</code>/<code>group:</code> prefixes for <code>oc_</code> chat IDs, and default ambiguous bare <code>oc_</code> targets to direct routing to avoid DM session misclassification. (#10407) Thanks @Bermudarat.</li>
<li>Feishu/Group session routing: add configurable group session scopes (<code>group</code>, <code>group_sender</code>, <code>group_topic</code>, <code>group_topic_sender</code>) with legacy <code>topicSessionMode=enabled</code> compatibility so Feishu group conversations can isolate sessions by sender/topic as configured. (#17798) Thanks @yfge.</li>
<li>Feishu/Reply-in-thread routing: add <code>replyInThread</code> config (<code>disabled|enabled</code>) for group replies, propagate <code>reply_in_thread</code> across text/card/media/streaming sends, and align topic-scoped session routing so newly created reply threads stay on the same session root. (#27325) Thanks @kcinzgg.</li>
<li>Feishu/Probe status caching: cache successful <code>probeFeishu()</code> bot-info results for 10 minutes (bounded cache with per-account keying) to reduce repeated status/onboarding probe API calls, while bypassing cache for failures and exceptions. (#28907) Thanks @Glucksberg.</li>
<li>Feishu/Opus media send type: send <code>.opus</code> attachments with <code>msg_type: "audio"</code> (instead of <code>"media"</code>) so Feishu voice messages deliver correctly while <code>.mp4</code> remains <code>msg_type: "media"</code> and documents remain <code>msg_type: "file"</code>. (#28269) Thanks @Glucksberg.</li>
<li>Feishu/Mobile video media type: treat inbound <code>message_type: "media"</code> as video-equivalent for media key extraction, placeholder inference, and media download resolution so mobile-app video sends ingest correctly. (#25502) Thanks @4ier.</li>
<li>Feishu/Inbound sender fallback: fall back to <code>sender_id.user_id</code> when <code>sender_id.open_id</code> is missing on inbound events, and use ID-type-aware sender lookup so mobile-delivered messages keep stable sender identity/routing. (#26703) Thanks @NewdlDewdl.</li>
<li>Feishu/Reply context metadata: include inbound <code>parent_id</code> and <code>root_id</code> as <code>ReplyToId</code>/<code>RootMessageId</code> in inbound context, and parse interactive-card quote bodies into readable text when fetching replied messages. (#18529) Thanks @qiangu.</li>
<li>Feishu/Post embedded media: extract <code>media</code> tags from inbound rich-text (<code>post</code>) messages and download embedded video/audio files alongside existing embedded-image handling, with regression coverage. (#21786) Thanks @laopuhuluwa.</li>
<li>Feishu/Local media sends: propagate <code>mediaLocalRoots</code> through Feishu outbound media sending into <code>loadWebMedia</code> so local path attachments work with post-CVE local-root enforcement. (#27884) Thanks @joelnishanth.</li>
<li>Feishu/Group wildcard policy fallback: honor <code>channels.feishu.groups["*"]</code> when no explicit group match exists so unmatched groups inherit wildcard reply-policy settings instead of falling back to global defaults. (#29456) Thanks @WaynePika.</li>
<li>Feishu/Inbound media regression coverage: add explicit tests for message resource type mapping (<code>image</code> stays <code>image</code>, non-image maps to <code>file</code>) to prevent reintroducing unsupported Feishu <code>type=audio</code> fetches. (#16311, #8746) Thanks @Yaxuan42.</li>
<li>TTS/Voice bubbles: use opus output and enable <code>audioAsVoice</code> routing for Feishu and WhatsApp (in addition to Telegram) so supported channels receive voice-bubble playback instead of file-style audio attachments. (#27366) Thanks @smthfoxy.</li>
<li>Telegram/Reply media context: include replied media files in inbound context when replying to media, defer reply-media downloads to debounce flush, gate reply-media fetch behind DM authorization, and preserve replied media when non-vision sticker fallback runs (including cached-sticker paths). (#28488) Thanks @obviyus.</li>
<li>Android/Nodes notification wake flow: enable Android <code>system.notify</code> default allowlist, emit <code>notifications.changed</code> events for posted/removed notifications (excluding OpenClaw app-owned notifications), canonicalize notification session keys before enqueue/wake routing, and skip heartbeat wakes when consecutive notification summaries dedupe. (#29440) Thanks @obviyus.</li>
<li>Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.</li>
<li>Feishu/Multi-account + reply reliability: add <code>channels.feishu.defaultAccount</code> outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as <code>msg_type: "file"</code>, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.</li>
<li>Cron/Delivery: disable the agent messaging tool when <code>delivery.mode</code> is <code>"none"</code> so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.</li>
<li>Feishu/Inbound rich-text parsing: preserve <code>share_chat</code> payload summaries when available and add explicit parsing for rich-text <code>code</code>/<code>code_block</code>/<code>pre</code> tags so forwarded and code-heavy messages keep useful context in agent input. (#28591) Thanks @kevinWangSheng.</li>
<li>Feishu/Post markdown parsing: parse rich-text <code>post</code> payloads through a shared markdown-aware parser with locale-wrapper support, preserved mention/image metadata extraction, and inline/fenced code fidelity for agent input rendering. (#12755) Thanks @WilsonLiu95.</li>
<li>Telegram/Outbound chunking: route oversize splitting through the shared outbound pipeline (including subagents), retry Telegram sends when escaped HTML exceeds limits, and preserve boundary whitespace when retry re-splitting rendered chunks so plain-text/transcript fidelity is retained. (#29342, #27317; follow-up to #27461) Thanks @obviyus.</li>
<li>Slack/Native commands: register Slack native status as <code>/agentstatus</code> (Slack-reserved <code>/status</code>) so manifest slash command registration stays valid while text <code>/status</code> still works. Landed from contributor PR #29032 by @maloqab. Thanks @maloqab.</li>
<li>Android/Camera clip: remove <code>camera.clip</code> HTTP-upload fallback to base64 so clip transport is deterministic and fail-loud, and reject non-positive <code>maxWidth</code> values so invalid inputs fall back to the safe resize default. (#28229) Thanks @obviyus.</li>
<li>Android/Gateway canvas capability refresh: send <code>node.canvas.capability.refresh</code> with object <code>params</code> (<code>{}</code>) from Android node runtime so gateway object-schema validation accepts refresh retries and A2UI host recovery works after scoped capability expiry. (#28413) Thanks @obviyus.</li>
<li>Gateway/Control UI origins: honor <code>gateway.controlUi.allowedOrigins: ["*"]</code> wildcard entries (including trimmed values) and lock behavior with regression tests. Landed from contributor PR #31058 by @byungsker. Thanks @byungsker.</li>
<li>Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.</li>
<li>Agents/Sessions list transcript paths: handle missing/non-string/relative <code>sessions.list.path</code> values and per-agent <code>{agentId}</code> templates when deriving <code>transcriptPath</code>, so cross-agent session listings resolve to concrete agent session files instead of workspace-relative paths. (#24775) Thanks @martinfrancois.</li>
<li>Gateway/Control UI CSP: allow required Google Fonts origins in Control UI CSP. (#29279) Thanks @Glucksberg and @vincentkoc.</li>
<li>CLI/Install: add an npm-link fallback to fix CLI startup <code>Permission denied</code> failures (<code>exit 127</code>) on affected installs. (#17151) Thanks @sskyu and @vincentkoc.</li>
<li>Onboarding/Custom providers: improve verification reliability for slower local endpoints (for example Ollama) during setup. (#27380) Thanks @Sid-Qin.</li>
<li>Plugins/NPM spec install: fix npm-spec plugin installs when <code>npm pack</code> output is empty by detecting newly created <code>.tgz</code> archives in the pack directory. (#21039) Thanks @graysurf and @vincentkoc.</li>
<li>Plugins/Install: clear stale install errors when an npm package is not found so follow-up install attempts report current state correctly. (#25073) Thanks @dalefrieswthat.</li>
<li>Security/Feishu webhook ingress: bound unauthenticated webhook rate-limit state with stale-window pruning and a hard key cap to prevent unbounded pre-auth memory growth from rotating source keys. (#26050) Thanks @bmendonca3.</li>
<li>Gateway/macOS supervised restart: actively <code>launchctl kickstart -k</code> during intentional supervised restarts to bypass LaunchAgent <code>ThrottleInterval</code> delays, and fall back to in-process restart when kickstart fails. Landed from contributor PR #29078 by @cathrynlavery. Thanks @cathrynlavery.</li>
<li>Daemon/macOS TLS certs: default LaunchAgent service env <code>NODE_EXTRA_CA_CERTS</code> to <code>/etc/ssl/cert.pem</code> (while preserving explicit overrides) so HTTPS clients no longer fail with local-issuer errors under launchd. (#27915) Thanks @Lukavyi.</li>
<li>Discord/Components wildcard handlers: use distinct internal registration sentinel IDs and parse those sentinels as wildcard keys so select/user/role/channel/mentionable/modal interactions are not dropped by raw customId dedupe paths. Landed from contributor PR #29459 by @Sid-Qin. Thanks @Sid-Qin.</li>
<li>Feishu/Reaction notifications: add <code>channels.feishu.reactionNotifications</code> (<code>off | own | all</code>, default <code>own</code>) so operators can disable reaction ingress or allow all verified reaction events (not only bot-authored message reactions). (#28529) Thanks @cowboy129.</li>
<li>Feishu/Typing backoff: re-throw Feishu typing add/remove rate-limit and quota errors (<code>429</code>, <code>99991400</code>, <code>99991403</code>) and detect SDK non-throwing backoff responses so the typing keepalive circuit breaker can stop retries instead of looping indefinitely. (#28494) Thanks @guoqunabc.</li>
<li>Feishu/Zalo runtime logging: replace direct <code>console.log/error</code> usage in Feishu typing-indicator paths and Zalo monitor paths with runtime-gated logger calls so verbosity controls are respected while preserving typing backoff behavior. (#18841) Thanks @Clawborn.</li>
<li>Feishu/Group sender allowlist fallback: add global <code>channels.feishu.groupSenderAllowFrom</code> sender authorization for group chats, with per-group <code>groups.<id>.allowFrom</code> precedence and regression coverage for allow/block/precedence behavior. (#29174) Thanks @1MoreBuild.</li>
<li>Feishu/Docx append/write ordering: insert converted Docx blocks sequentially (single-block creates) so Feishu append/write preserves markdown block order instead of returning shuffled sections in asynchronous batch inserts. (#26172, #26022) Thanks @echoVic.</li>
<li>Feishu/Docx convert fallback chunking: recursively split oversized markdown chunks (including long no-heading sections) when <code>document.convert</code> hits content limits, while keeping fenced-code-aware split boundaries whenever possible. (#14402) Thanks @lml2468.</li>
<li>Feishu/API quota controls: add <code>typingIndicator</code> and <code>resolveSenderNames</code> config flags (top-level and per-account) so operators can disable typing reactions and sender-name lookup requests while keeping default behavior unchanged. (#10513) Thanks @BigUncle.</li>
<li>Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted <code>System:</code> context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.</li>
<li>Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.</li>
<li>Sessions/Internal routing: preserve established external <code>lastTo</code>/<code>lastChannel</code> routes for internal/non-deliverable turns, with added coverage for no-fallback internal routing behavior. Landed from contributor PR #30941 by @graysurf. Thanks @graysurf.</li>
<li>Control UI/Debug log layout: render Debug Event Log payloads at full width to prevent payload JSON from being squeezed into a narrow side column. Landed from contributor PR #30978 by @stozo04. Thanks @stozo04.</li>
<li>Auto-reply/NO_REPLY: strip <code>NO_REPLY</code> token from mixed-content messages instead of leaking raw control text to end users. Landed from contributor PR #31080 by @scoootscooob. Thanks @scoootscooob.</li>
<li>Install/npm: fix npm global install deprecation warnings. (#28318) Thanks @vincentkoc.</li>
<li>Update/Global npm: fallback to <code>--omit=optional</code> when global <code>npm update</code> fails so optional dependency install failures no longer abort update flows. (#24896) Thanks @xinhuagu and @vincentkoc.</li>
<li>Inbound metadata/Multi-account routing: include <code>account_id</code> in trusted inbound metadata so multi-account channel sessions can reliably disambiguate the receiving account in prompt context. Landed from contributor PR #30984 by @Stxle2. Thanks @Stxle2.</li>
<li>Model directives/Auth profiles: split <code>/model</code> profile suffixes at the first <code>@</code> after the last slash so email-based auth profile IDs (for example OAuth profile IDs) resolve correctly. Landed from contributor PR #30932 by @haosenwang1018. Thanks @haosenwang1018.</li>
<li>Cron/Delivery mode none: send explicit <code>delivery: { mode: "none" }</code> from cron editor for both add and update flows so previous announce delivery is actually cleared. Landed from contributor PR #31145 by @byungsker. Thanks @byungsker.</li>
<li>Cron editor viewport: make the sticky cron edit form independently scrollable with viewport-bounded height so lower fields/actions are reachable on shorter screens. Landed from contributor PR #31133 by @Sid-Qin. Thanks @Sid-Qin.</li>
<li>Agents/Thinking fallback: when providers reject unsupported thinking levels without enumerating alternatives, retry with <code>think=off</code> to avoid hard failure during model/provider fallback chains. Landed from contributor PR #31002 by @yfge. Thanks @yfge.</li>
<li>Ollama/Embedded runner base URL precedence: prioritize configured provider <code>baseUrl</code> over model defaults for embedded Ollama runs so Docker and remote-host setups avoid localhost fetch failures. (#30964) Thanks @stakeswky.</li>
<li>Agents/Failover reason classification: avoid false rate-limit classification from incidental <code>tpm</code> substrings by matching TPM as a standalone token/phrase and keeping auth-context errors on the auth path. Landed from contributor PR #31007 by @HOYALIM. Thanks @HOYALIM.</li>
<li>CLI/Cron: clarify <code>cron list</code> output by renaming <code>Agent</code> to <code>Agent ID</code> and adding a <code>Model</code> column for isolated agent-turn jobs. (#26259) Thanks @openperf.</li>
<li>Gateway/WS: close repeated post-handshake <code>unauthorized role:*</code> request floods per connection and sample duplicate rejection logs, preventing a single misbehaving client from degrading gateway responsiveness. (#20168) Thanks @acy103, @vibecodooor, and @vincentkoc.</li>
<li>Gateway/Auth: improve device-auth v2 migration diagnostics so operators get clearer guidance when legacy clients connect. (#28305) Thanks @vincentkoc.</li>
<li>CLI/Ollama config: allow <code>config set</code> for Ollama <code>apiKey</code> without predeclared provider config. (#29299) Thanks @vincentkoc.</li>
<li>Ollama/Autodiscovery: harden autodiscovery and warning behavior. (#29201) Thanks @marcodelpin and @vincentkoc.</li>
<li>Ollama/Context window: unify context window handling across discovery, merge, and OpenAI-compatible transport paths. (#29205) Thanks @Sid-Qin, @jimmielightner, and @vincentkoc.</li>
<li>Agents/Ollama: demote empty-discovery logging from <code>warn</code> to <code>debug</code> to reduce noisy warnings in normal edge-case discovery flows. (#26379) Thanks @byungsker.</li>
<li>fix(model): preserve reasoning in provider fallback resolution. (#29285) Fixes #25636. Thanks @vincentkoc.</li>
<li>Docker/Image permissions: normalize <code>/app/extensions</code>, <code>/app/.agent</code>, and <code>/app/.agents</code> to directory mode <code>755</code> and file mode <code>644</code> during image build so plugin discovery does not block inherited world-writable paths. (#30191) Fixes #30139. Thanks @edincampara.</li>
<li>OpenAI Responses/Compaction: rewrite and unify the OpenAI Responses store patches to treat empty <code>baseUrl</code> as non-direct, honor <code>compat.supportsStore=false</code>, and auto-inject server-side compaction <code>context_management</code> for compatible direct OpenAI models (with per-model opt-out/threshold overrides). Landed from contributor PRs #16930 (@OiPunk), #22441 (@EdwardWu7), and #25088 (@MoerAI). Thanks @OiPunk, @EdwardWu7, and @MoerAI.</li>
<li>Sandbox/Browser Docker: pass <code>OPENCLAW_BROWSER_NO_SANDBOX=1</code> to sandbox browser containers and bump sandbox browser security hash epoch so existing containers are recreated and pick up the env on upgrade. (#29879) Thanks @Lukavyi.</li>
<li>Usage normalization: clamp negative prompt/input token values to zero (including <code>prompt_tokens</code> alias inputs) so <code>/usage</code> and TUI usage displays cannot show nonsensical negative counts. Landed from contributor PR #31211 by @scoootscooob. Thanks @scoootscooob.</li>
<li>Secrets/Auth profiles: normalize inline SecretRef <code>token</code>/<code>key</code> values to canonical <code>tokenRef</code>/<code>keyRef</code> before persistence, and keep explicit <code>keyRef</code> precedence when inline refs are also present. Landed from contributor PR #31047 by @minupla. Thanks @minupla.</li>
<li>Tools/Edit workspace boundary errors: preserve the real <code>Path escapes workspace root</code> failure path instead of surfacing a misleading access/file-not-found error when editing outside workspace roots. Landed from contributor PR #31015 by @haosenwang1018. Thanks @haosenwang1018.</li>
<li>Browser/Open & navigate: accept <code>url</code> as an alias parameter for <code>open</code> and <code>navigate</code>. (#29260) Thanks @vincentkoc.</li>
<li>Codex/Usage window: label weekly usage window as <code>Week</code> instead of <code>Day</code>. (#26267) Thanks @Sid-Qin.</li>
<li>Signal/Sync message null-handling: treat <code>syncMessage</code> presence (including <code>null</code>) as sync envelope traffic so replayed sentTranscript payloads cannot bypass loop guards after daemon restart. Landed from contributor PR #31138 by @Sid-Qin. Thanks @Sid-Qin.</li>
<li>Infra/fs-safe: sanitize directory-read failures so raw <code>EISDIR</code> text never leaks to messaging surfaces, with regression tests for both root-scoped and direct safe reads. Landed from contributor PR #31205 by @polooooo. Thanks @polooooo.</li>
<li>Sandbox/mkdirp boundary checks: allow directory-safe boundary validation for existing in-boundary subdirectories, preventing false <code>cannot create directories</code> failures in sandbox write mode. (#30610) Thanks @glitch418x.</li>
<li>Security/Compaction audit: remove the post-compaction audit injection message. (#28507) Thanks @fuller-stack-dev and @vincentkoc.</li>
<li>Web tools/RFC2544 fake-IP compatibility: allow RFC2544 benchmark range (<code>198.18.0.0/15</code>) for trusted web-tool fetch endpoints so proxy fake-IP networking modes do not trigger false SSRF blocks. Landed from contributor PR #31176 by @sunkinux. Thanks @sunkinux.</li>
<li>Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.</li>
<li>Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted <code>System:</code> context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.</li>
<li>Feishu/Multi-account + reply reliability: add <code>channels.feishu.defaultAccount</code> outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as <code>msg_type: "file"</code>, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.</li>
<li>Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.</li>
<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.1/OpenClaw-2026.3.1.zip" length="12804155" type="application/octet-stream" sparkle:edSignature="TF1otD4Vk3pG0iViX7mvix5DQEgAsk4JkSFvH7opjf9aawV16f29SUa2wRmiCFU6HEgyNgnGI/078O+A27eXCA=="/>
<!-- pragma: allowlist secret -->
<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>

View File

@ -27,9 +27,33 @@ 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
```
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
@ -172,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

@ -1,5 +1,7 @@
import com.android.build.api.variant.impl.VariantOutputImpl
val dnsjavaInetAddressResolverService = "META-INF/services/java.net.spi.InetAddressResolverProvider"
val androidStoreFile = providers.gradleProperty("OPENCLAW_ANDROID_STORE_FILE").orNull?.takeIf { it.isNotBlank() }
val androidStorePassword = providers.gradleProperty("OPENCLAW_ANDROID_STORE_PASSWORD").orNull?.takeIf { it.isNotBlank() }
val androidKeyAlias = providers.gradleProperty("OPENCLAW_ANDROID_KEY_ALIAS").orNull?.takeIf { it.isNotBlank() }
@ -63,14 +65,29 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 202603081
versionName = "2026.3.8"
versionCode = 2026032000
versionName = "2026.3.20"
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) {
@ -78,6 +95,9 @@ android {
}
isMinifyEnabled = true
isShrinkResources = true
ndk {
debugSymbolLevel = "SYMBOL_TABLE"
}
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
debug {
@ -104,6 +124,10 @@ android {
"/META-INF/LICENSE*.txt",
"DebugProbesKt.bin",
"kotlin-tooling-metadata.json",
"org/bouncycastle/pqc/crypto/picnic/lowmcL1.bin.properties",
"org/bouncycastle/pqc/crypto/picnic/lowmcL3.bin.properties",
"org/bouncycastle/pqc/crypto/picnic/lowmcL5.bin.properties",
"org/bouncycastle/x509/CertPathReviewerMessages*.properties",
)
}
}
@ -131,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
}
}
@ -168,7 +197,6 @@ dependencies {
// material-icons-extended pulled in full icon set (~20 MB DEX). Only ~18 icons used.
// R8 will tree-shake unused icons when minify is enabled on release builds.
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.navigation:navigation-compose:2.9.7")
debugImplementation("androidx.compose.ui:ui-tooling")
@ -193,8 +221,7 @@ dependencies {
implementation("androidx.camera:camera-camera2:1.5.2")
implementation("androidx.camera:camera-lifecycle:1.5.2")
implementation("androidx.camera:camera-video:1.5.2")
implementation("androidx.camera:camera-view:1.5.2")
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
implementation("com.google.android.gms:play-services-code-scanner:16.1.0")
// Unicast DNS-SD (Wide-Area Bonjour) for tailnet discovery domains.
implementation("dnsjava:dnsjava:3.6.4")
@ -211,3 +238,45 @@ dependencies {
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
val stripReleaseDnsjavaServiceDescriptor =
tasks.register("stripReleaseDnsjavaServiceDescriptor") {
val mergedJar =
layout.buildDirectory.file(
"intermediates/merged_java_res/release/mergeReleaseJavaResource/base.jar",
)
inputs.file(mergedJar)
outputs.file(mergedJar)
doLast {
val jarFile = mergedJar.get().asFile
if (!jarFile.exists()) {
return@doLast
}
val unpackDir = temporaryDir.resolve("merged-java-res")
delete(unpackDir)
copy {
from(zipTree(jarFile))
into(unpackDir)
exclude(dnsjavaInetAddressResolverService)
}
delete(jarFile)
ant.invokeMethod(
"zip",
mapOf(
"destfile" to jarFile.absolutePath,
"basedir" to unpackDir.absolutePath,
),
)
}
}
tasks.matching { it.name == "stripReleaseDnsjavaServiceDescriptor" }.configureEach {
dependsOn("mergeReleaseJavaResource")
}
tasks.matching { it.name == "minifyReleaseWithR8" }.configureEach {
dependsOn(stripReleaseDnsjavaServiceDescriptor)
}

View File

@ -1,26 +1,6 @@
# ── App classes ───────────────────────────────────────────────────
-keep class ai.openclaw.app.** { *; }
# ── Bouncy Castle ─────────────────────────────────────────────────
-keep class org.bouncycastle.** { *; }
-dontwarn org.bouncycastle.**
# ── CameraX ───────────────────────────────────────────────────────
-keep class androidx.camera.** { *; }
# ── kotlinx.serialization ────────────────────────────────────────
-keep class kotlinx.serialization.** { *; }
-keepclassmembers class * {
@kotlinx.serialization.Serializable *;
}
-keepattributes *Annotation*, InnerClasses
# ── OkHttp ────────────────────────────────────────────────────────
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class okhttp3.internal.platform.** { *; }
# ── Misc suppressions ────────────────────────────────────────────
-dontwarn com.sun.jna.**
-dontwarn javax.naming.**
-dontwarn lombok.Generated

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
@ -19,6 +20,7 @@
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />

View File

@ -18,14 +18,13 @@ import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
private lateinit var permissionRequester: PermissionRequester
private var didAttachRuntimeUi = false
private var didStartNodeService = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
permissionRequester = PermissionRequester(this)
viewModel.camera.attachLifecycleOwner(this)
viewModel.camera.attachPermissionRequester(permissionRequester)
viewModel.sms.attachPermissionRequester(permissionRequester)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -39,6 +38,20 @@ class MainActivity : ComponentActivity() {
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.runtimeInitialized.collect { ready ->
if (!ready || didAttachRuntimeUi) return@collect
viewModel.attachRuntimeUi(owner = this@MainActivity, permissionRequester = permissionRequester)
didAttachRuntimeUi = true
if (!didStartNodeService) {
NodeForegroundService.start(this@MainActivity)
didStartNodeService = true
}
}
}
}
setContent {
OpenClawTheme {
Surface(modifier = Modifier) {
@ -46,9 +59,6 @@ class MainActivity : ComponentActivity() {
}
}
}
// Keep startup path lean: start foreground service after first frame.
window.decorView.post { NodeForegroundService.start(this) }
}
override fun onStart() {

View File

@ -2,201 +2,274 @@ package ai.openclaw.app
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import ai.openclaw.app.gateway.GatewayEndpoint
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.viewModelScope
import ai.openclaw.app.chat.ChatMessage
import ai.openclaw.app.chat.ChatPendingToolCall
import ai.openclaw.app.chat.ChatSessionEntry
import ai.openclaw.app.chat.OutgoingAttachment
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.node.CameraCaptureManager
import ai.openclaw.app.node.CanvasController
import ai.openclaw.app.node.SmsManager
import ai.openclaw.app.voice.VoiceConversationEntry
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
@OptIn(ExperimentalCoroutinesApi::class)
class MainViewModel(app: Application) : AndroidViewModel(app) {
private val runtime: NodeRuntime = (app as NodeApp).runtime
private val nodeApp = app as NodeApp
private val prefs = nodeApp.prefs
private val runtimeRef = MutableStateFlow<NodeRuntime?>(null)
private var foreground = true
val canvas: CanvasController = runtime.canvas
val canvasCurrentUrl: StateFlow<String?> = runtime.canvas.currentUrl
val canvasA2uiHydrated: StateFlow<Boolean> = runtime.canvasA2uiHydrated
val canvasRehydratePending: StateFlow<Boolean> = runtime.canvasRehydratePending
val canvasRehydrateErrorText: StateFlow<String?> = runtime.canvasRehydrateErrorText
val camera: CameraCaptureManager = runtime.camera
val sms: SmsManager = runtime.sms
private fun ensureRuntime(): NodeRuntime {
runtimeRef.value?.let { return it }
val runtime = nodeApp.ensureRuntime()
runtime.setForeground(foreground)
runtimeRef.value = runtime
return runtime
}
val gateways: StateFlow<List<GatewayEndpoint>> = runtime.gateways
val discoveryStatusText: StateFlow<String> = runtime.discoveryStatusText
private fun <T> runtimeState(
initial: T,
selector: (NodeRuntime) -> StateFlow<T>,
): StateFlow<T> =
runtimeRef
.flatMapLatest { runtime -> runtime?.let(selector) ?: flowOf(initial) }
.stateIn(viewModelScope, SharingStarted.Eagerly, initial)
val isConnected: StateFlow<Boolean> = runtime.isConnected
val isNodeConnected: StateFlow<Boolean> = runtime.nodeConnected
val statusText: StateFlow<String> = runtime.statusText
val serverName: StateFlow<String?> = runtime.serverName
val remoteAddress: StateFlow<String?> = runtime.remoteAddress
val pendingGatewayTrust: StateFlow<NodeRuntime.GatewayTrustPrompt?> = runtime.pendingGatewayTrust
val isForeground: StateFlow<Boolean> = runtime.isForeground
val seamColorArgb: StateFlow<Long> = runtime.seamColorArgb
val mainSessionKey: StateFlow<String> = runtime.mainSessionKey
val runtimeInitialized: StateFlow<Boolean> =
runtimeRef
.flatMapLatest { runtime -> flowOf(runtime != null) }
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
val cameraHud: StateFlow<CameraHudState?> = runtime.cameraHud
val cameraFlashToken: StateFlow<Long> = runtime.cameraFlashToken
val canvasCurrentUrl: StateFlow<String?> = runtimeState(initial = null) { it.canvas.currentUrl }
val canvasA2uiHydrated: StateFlow<Boolean> = runtimeState(initial = false) { it.canvasA2uiHydrated }
val canvasRehydratePending: StateFlow<Boolean> = runtimeState(initial = false) { it.canvasRehydratePending }
val canvasRehydrateErrorText: StateFlow<String?> = runtimeState(initial = null) { it.canvasRehydrateErrorText }
val instanceId: StateFlow<String> = runtime.instanceId
val displayName: StateFlow<String> = runtime.displayName
val cameraEnabled: StateFlow<Boolean> = runtime.cameraEnabled
val locationMode: StateFlow<LocationMode> = runtime.locationMode
val locationPreciseEnabled: StateFlow<Boolean> = runtime.locationPreciseEnabled
val preventSleep: StateFlow<Boolean> = runtime.preventSleep
val micEnabled: StateFlow<Boolean> = runtime.micEnabled
val micCooldown: StateFlow<Boolean> = runtime.micCooldown
val micStatusText: StateFlow<String> = runtime.micStatusText
val micLiveTranscript: StateFlow<String?> = runtime.micLiveTranscript
val micIsListening: StateFlow<Boolean> = runtime.micIsListening
val micQueuedMessages: StateFlow<List<String>> = runtime.micQueuedMessages
val micConversation: StateFlow<List<VoiceConversationEntry>> = runtime.micConversation
val micInputLevel: StateFlow<Float> = runtime.micInputLevel
val micIsSending: StateFlow<Boolean> = runtime.micIsSending
val speakerEnabled: StateFlow<Boolean> = runtime.speakerEnabled
val manualEnabled: StateFlow<Boolean> = runtime.manualEnabled
val manualHost: StateFlow<String> = runtime.manualHost
val manualPort: StateFlow<Int> = runtime.manualPort
val manualTls: StateFlow<Boolean> = runtime.manualTls
val gatewayToken: StateFlow<String> = runtime.gatewayToken
val onboardingCompleted: StateFlow<Boolean> = runtime.onboardingCompleted
val canvasDebugStatusEnabled: StateFlow<Boolean> = runtime.canvasDebugStatusEnabled
val gateways: StateFlow<List<GatewayEndpoint>> = runtimeState(initial = emptyList()) { it.gateways }
val discoveryStatusText: StateFlow<String> = runtimeState(initial = "Searching…") { it.discoveryStatusText }
val chatSessionKey: StateFlow<String> = runtime.chatSessionKey
val chatSessionId: StateFlow<String?> = runtime.chatSessionId
val chatMessages = runtime.chatMessages
val chatError: StateFlow<String?> = runtime.chatError
val chatHealthOk: StateFlow<Boolean> = runtime.chatHealthOk
val chatThinkingLevel: StateFlow<String> = runtime.chatThinkingLevel
val chatStreamingAssistantText: StateFlow<String?> = runtime.chatStreamingAssistantText
val chatPendingToolCalls = runtime.chatPendingToolCalls
val chatSessions = runtime.chatSessions
val pendingRunCount: StateFlow<Int> = runtime.pendingRunCount
val isConnected: StateFlow<Boolean> = runtimeState(initial = false) { it.isConnected }
val isNodeConnected: StateFlow<Boolean> = runtimeState(initial = false) { it.nodeConnected }
val statusText: StateFlow<String> = runtimeState(initial = "Offline") { it.statusText }
val serverName: StateFlow<String?> = runtimeState(initial = null) { it.serverName }
val remoteAddress: StateFlow<String?> = runtimeState(initial = null) { it.remoteAddress }
val pendingGatewayTrust: StateFlow<NodeRuntime.GatewayTrustPrompt?> = runtimeState(initial = null) { it.pendingGatewayTrust }
val seamColorArgb: StateFlow<Long> = runtimeState(initial = 0xFF0EA5E9) { it.seamColorArgb }
val mainSessionKey: StateFlow<String> = runtimeState(initial = "main") { it.mainSessionKey }
val cameraHud: StateFlow<CameraHudState?> = runtimeState(initial = null) { it.cameraHud }
val cameraFlashToken: StateFlow<Long> = runtimeState(initial = 0L) { it.cameraFlashToken }
val instanceId: StateFlow<String> = prefs.instanceId
val displayName: StateFlow<String> = prefs.displayName
val cameraEnabled: StateFlow<Boolean> = prefs.cameraEnabled
val locationMode: StateFlow<LocationMode> = prefs.locationMode
val locationPreciseEnabled: StateFlow<Boolean> = prefs.locationPreciseEnabled
val preventSleep: StateFlow<Boolean> = prefs.preventSleep
val manualEnabled: StateFlow<Boolean> = prefs.manualEnabled
val manualHost: StateFlow<String> = prefs.manualHost
val manualPort: StateFlow<Int> = prefs.manualPort
val manualTls: StateFlow<Boolean> = prefs.manualTls
val gatewayToken: StateFlow<String> = prefs.gatewayToken
val onboardingCompleted: StateFlow<Boolean> = prefs.onboardingCompleted
val canvasDebugStatusEnabled: StateFlow<Boolean> = prefs.canvasDebugStatusEnabled
val speakerEnabled: StateFlow<Boolean> = prefs.speakerEnabled
val micEnabled: StateFlow<Boolean> = prefs.talkEnabled
val micCooldown: StateFlow<Boolean> = runtimeState(initial = false) { it.micCooldown }
val micStatusText: StateFlow<String> = runtimeState(initial = "Mic off") { it.micStatusText }
val micLiveTranscript: StateFlow<String?> = runtimeState(initial = null) { it.micLiveTranscript }
val micIsListening: StateFlow<Boolean> = runtimeState(initial = false) { it.micIsListening }
val micQueuedMessages: StateFlow<List<String>> = runtimeState(initial = emptyList()) { it.micQueuedMessages }
val micConversation: StateFlow<List<VoiceConversationEntry>> = runtimeState(initial = emptyList()) { it.micConversation }
val micInputLevel: StateFlow<Float> = runtimeState(initial = 0f) { it.micInputLevel }
val micIsSending: StateFlow<Boolean> = runtimeState(initial = false) { it.micIsSending }
val chatSessionKey: StateFlow<String> = runtimeState(initial = "main") { it.chatSessionKey }
val chatSessionId: StateFlow<String?> = runtimeState(initial = null) { it.chatSessionId }
val chatMessages: StateFlow<List<ChatMessage>> = runtimeState(initial = emptyList()) { it.chatMessages }
val chatError: StateFlow<String?> = runtimeState(initial = null) { it.chatError }
val chatHealthOk: StateFlow<Boolean> = runtimeState(initial = false) { it.chatHealthOk }
val chatThinkingLevel: StateFlow<String> = runtimeState(initial = "off") { it.chatThinkingLevel }
val chatStreamingAssistantText: StateFlow<String?> = runtimeState(initial = null) { it.chatStreamingAssistantText }
val chatPendingToolCalls: StateFlow<List<ChatPendingToolCall>> = runtimeState(initial = emptyList()) { it.chatPendingToolCalls }
val chatSessions: StateFlow<List<ChatSessionEntry>> = runtimeState(initial = emptyList()) { it.chatSessions }
val pendingRunCount: StateFlow<Int> = runtimeState(initial = 0) { it.pendingRunCount }
init {
if (prefs.onboardingCompleted.value) {
ensureRuntime()
}
}
val canvas: CanvasController
get() = ensureRuntime().canvas
val camera: CameraCaptureManager
get() = ensureRuntime().camera
val sms: SmsManager
get() = ensureRuntime().sms
fun attachRuntimeUi(owner: LifecycleOwner, permissionRequester: PermissionRequester) {
val runtime = runtimeRef.value ?: return
runtime.camera.attachLifecycleOwner(owner)
runtime.camera.attachPermissionRequester(permissionRequester)
runtime.sms.attachPermissionRequester(permissionRequester)
}
fun setForeground(value: Boolean) {
runtime.setForeground(value)
foreground = value
val runtime =
if (value && prefs.onboardingCompleted.value) {
ensureRuntime()
} else {
runtimeRef.value
}
runtime?.setForeground(value)
}
fun setDisplayName(value: String) {
runtime.setDisplayName(value)
prefs.setDisplayName(value)
}
fun setCameraEnabled(value: Boolean) {
runtime.setCameraEnabled(value)
prefs.setCameraEnabled(value)
}
fun setLocationMode(mode: LocationMode) {
runtime.setLocationMode(mode)
prefs.setLocationMode(mode)
}
fun setLocationPreciseEnabled(value: Boolean) {
runtime.setLocationPreciseEnabled(value)
prefs.setLocationPreciseEnabled(value)
}
fun setPreventSleep(value: Boolean) {
runtime.setPreventSleep(value)
prefs.setPreventSleep(value)
}
fun setManualEnabled(value: Boolean) {
runtime.setManualEnabled(value)
prefs.setManualEnabled(value)
}
fun setManualHost(value: String) {
runtime.setManualHost(value)
prefs.setManualHost(value)
}
fun setManualPort(value: Int) {
runtime.setManualPort(value)
prefs.setManualPort(value)
}
fun setManualTls(value: Boolean) {
runtime.setManualTls(value)
prefs.setManualTls(value)
}
fun setGatewayToken(value: String) {
runtime.setGatewayToken(value)
prefs.setGatewayToken(value)
}
fun setGatewayBootstrapToken(value: String) {
prefs.setGatewayBootstrapToken(value)
}
fun setGatewayPassword(value: String) {
runtime.setGatewayPassword(value)
prefs.setGatewayPassword(value)
}
fun setOnboardingCompleted(value: Boolean) {
runtime.setOnboardingCompleted(value)
if (value) {
ensureRuntime()
}
prefs.setOnboardingCompleted(value)
}
fun setCanvasDebugStatusEnabled(value: Boolean) {
runtime.setCanvasDebugStatusEnabled(value)
prefs.setCanvasDebugStatusEnabled(value)
}
fun setVoiceScreenActive(active: Boolean) {
runtime.setVoiceScreenActive(active)
ensureRuntime().setVoiceScreenActive(active)
}
fun setMicEnabled(enabled: Boolean) {
runtime.setMicEnabled(enabled)
ensureRuntime().setMicEnabled(enabled)
}
fun setSpeakerEnabled(enabled: Boolean) {
runtime.setSpeakerEnabled(enabled)
ensureRuntime().setSpeakerEnabled(enabled)
}
fun refreshGatewayConnection() {
runtime.refreshGatewayConnection()
ensureRuntime().refreshGatewayConnection()
}
fun connect(endpoint: GatewayEndpoint) {
runtime.connect(endpoint)
ensureRuntime().connect(endpoint)
}
fun connectManual() {
runtime.connectManual()
ensureRuntime().connectManual()
}
fun disconnect() {
runtime.disconnect()
runtimeRef.value?.disconnect()
}
fun acceptGatewayTrustPrompt() {
runtime.acceptGatewayTrustPrompt()
runtimeRef.value?.acceptGatewayTrustPrompt()
}
fun declineGatewayTrustPrompt() {
runtime.declineGatewayTrustPrompt()
runtimeRef.value?.declineGatewayTrustPrompt()
}
fun handleCanvasA2UIActionFromWebView(payloadJson: String) {
runtime.handleCanvasA2UIActionFromWebView(payloadJson)
ensureRuntime().handleCanvasA2UIActionFromWebView(payloadJson)
}
fun requestCanvasRehydrate(source: String = "screen_tab") {
runtime.requestCanvasRehydrate(source = source, force = true)
ensureRuntime().requestCanvasRehydrate(source = source, force = true)
}
fun refreshHomeCanvasOverviewIfConnected() {
ensureRuntime().refreshHomeCanvasOverviewIfConnected()
}
fun loadChat(sessionKey: String) {
runtime.loadChat(sessionKey)
ensureRuntime().loadChat(sessionKey)
}
fun refreshChat() {
runtime.refreshChat()
ensureRuntime().refreshChat()
}
fun refreshChatSessions(limit: Int? = null) {
runtime.refreshChatSessions(limit = limit)
ensureRuntime().refreshChatSessions(limit = limit)
}
fun setChatThinkingLevel(level: String) {
runtime.setChatThinkingLevel(level)
ensureRuntime().setChatThinkingLevel(level)
}
fun switchChatSession(sessionKey: String) {
runtime.switchChatSession(sessionKey)
ensureRuntime().switchChatSession(sessionKey)
}
fun abortChat() {
runtime.abortChat()
ensureRuntime().abortChat()
}
fun sendChat(message: String, thinking: String, attachments: List<OutgoingAttachment>) {
runtime.sendChat(message = message, thinking = thinking, attachments = attachments)
ensureRuntime().sendChat(message = message, thinking = thinking, attachments = attachments)
}
}

View File

@ -4,7 +4,18 @@ import android.app.Application
import android.os.StrictMode
class NodeApp : Application() {
val runtime: NodeRuntime by lazy { NodeRuntime(this) }
val prefs: SecurePrefs by lazy { SecurePrefs(this) }
@Volatile private var runtimeInstance: NodeRuntime? = null
fun ensureRuntime(): NodeRuntime {
runtimeInstance?.let { return it }
return synchronized(this) {
runtimeInstance ?: NodeRuntime(this, prefs).also { runtimeInstance = it }
}
}
fun peekRuntime(): NodeRuntime? = runtimeInstance
override fun onCreate() {
super.onCreate()

View File

@ -28,7 +28,11 @@ class NodeForegroundService : Service() {
val initial = buildNotification(title = "OpenClaw Node", text = "Starting…")
startForegroundWithTypes(notification = initial)
val runtime = (application as NodeApp).runtime
val runtime = (application as NodeApp).peekRuntime()
if (runtime == null) {
stopSelf()
return
}
notificationJob =
scope.launch {
combine(
@ -59,7 +63,7 @@ class NodeForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_STOP -> {
(application as NodeApp).runtime.disconnect()
(application as NodeApp).peekRuntime()?.disconnect()
stopSelf()
return START_NOT_STICKY
}

View File

@ -33,6 +33,8 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
@ -41,11 +43,12 @@ import kotlinx.serialization.json.buildJsonObject
import java.util.UUID
import java.util.concurrent.atomic.AtomicLong
class NodeRuntime(context: Context) {
class NodeRuntime(
context: Context,
val prefs: SecurePrefs = SecurePrefs(context.applicationContext),
) {
private val appContext = context.applicationContext
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
val prefs = SecurePrefs(appContext)
private val deviceAuthStore = DeviceAuthStore(prefs)
val canvas = CanvasController()
val camera = CameraCaptureManager(appContext)
@ -86,6 +89,8 @@ class NodeRuntime(context: Context) {
private val deviceHandler: DeviceHandler = DeviceHandler(
appContext = appContext,
smsEnabled = BuildConfig.OPENCLAW_ENABLE_SMS,
callLogEnabled = BuildConfig.OPENCLAW_ENABLE_CALL_LOG,
)
private val notificationsHandler: NotificationsHandler = NotificationsHandler(
@ -108,6 +113,10 @@ class NodeRuntime(context: Context) {
appContext = appContext,
)
private val callLogHandler: CallLogHandler = CallLogHandler(
appContext = appContext,
)
private val motionHandler: MotionHandler = MotionHandler(
appContext = appContext,
)
@ -130,7 +139,9 @@ class NodeRuntime(context: Context) {
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 },
)
@ -149,10 +160,13 @@ class NodeRuntime(context: Context) {
smsHandler = smsHandlerImpl,
a2uiHandler = a2uiHandler,
debugHandler = debugHandler,
callLogHandler = callLogHandler,
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 = {
@ -210,7 +224,8 @@ class NodeRuntime(context: Context) {
private val _isForeground = MutableStateFlow(true)
val isForeground: StateFlow<Boolean> = _isForeground.asStateFlow()
private var lastAutoA2uiUrl: String? = null
private var gatewayDefaultAgentId: String? = null
private var gatewayAgents: List<GatewayAgentSummary> = emptyList()
private var didAutoRequestCanvasRehydrate = false
private val canvasRehydrateSeq = AtomicLong(0)
private var operatorConnected = false
@ -232,7 +247,7 @@ class NodeRuntime(context: Context) {
updateStatus()
micCapture.onGatewayConnectionChanged(true)
scope.launch {
refreshBrandingFromGateway()
refreshHomeCanvasOverviewIfConnected()
if (voiceReplySpeakerLazy.isInitialized()) {
voiceReplySpeaker.refreshConfig()
}
@ -270,7 +285,7 @@ class NodeRuntime(context: Context) {
_canvasRehydratePending.value = false
_canvasRehydrateErrorText.value = null
updateStatus()
maybeNavigateToA2uiOnConnect()
showLocalCanvasOnConnect()
},
onDisconnected = { message ->
_nodeConnected.value = false
@ -396,6 +411,7 @@ class NodeRuntime(context: Context) {
_mainSessionKey.value = trimmed
talkMode.setMainSessionKey(trimmed)
chat.applyMainSessionKey(trimmed)
updateHomeCanvasState()
}
private fun updateStatus() {
@ -415,6 +431,7 @@ class NodeRuntime(context: Context) {
operator.isNotBlank() && operator != "Offline" -> operator
else -> node
}
updateHomeCanvasState()
}
private fun resolveMainSessionKey(): String {
@ -422,23 +439,31 @@ class NodeRuntime(context: Context) {
return if (trimmed.isEmpty()) "main" else trimmed
}
private fun maybeNavigateToA2uiOnConnect() {
val a2uiUrl = a2uiHandler.resolveA2uiHostUrl() ?: return
val current = canvas.currentUrl()?.trim().orEmpty()
if (current.isEmpty() || current == lastAutoA2uiUrl) {
lastAutoA2uiUrl = a2uiUrl
canvas.navigate(a2uiUrl)
}
}
private fun showLocalCanvasOnDisconnect() {
lastAutoA2uiUrl = null
private fun showLocalCanvasOnConnect() {
_canvasA2uiHydrated.value = false
_canvasRehydratePending.value = false
_canvasRehydrateErrorText.value = null
canvas.navigate("")
}
private fun showLocalCanvasOnDisconnect() {
_canvasA2uiHydrated.value = false
_canvasRehydratePending.value = false
_canvasRehydrateErrorText.value = null
canvas.navigate("")
}
fun refreshHomeCanvasOverviewIfConnected() {
if (!operatorConnected) {
updateHomeCanvasState()
return
}
scope.launch {
refreshBrandingFromGateway()
refreshAgentsFromGateway()
}
}
fun requestCanvasRehydrate(source: String = "manual", force: Boolean = true) {
scope.launch {
if (!_nodeConnected.value) {
@ -503,6 +528,7 @@ class NodeRuntime(context: Context) {
val gatewayToken: StateFlow<String> = prefs.gatewayToken
val onboardingCompleted: StateFlow<Boolean> = prefs.onboardingCompleted
fun setGatewayToken(value: String) = prefs.setGatewayToken(value)
fun setGatewayBootstrapToken(value: String) = prefs.setGatewayBootstrapToken(value)
fun setGatewayPassword(value: String) = prefs.setGatewayPassword(value)
fun setOnboardingCompleted(value: Boolean) = prefs.setOnboardingCompleted(value)
val lastDiscoveredStableId: StateFlow<String> = prefs.lastDiscoveredStableId
@ -546,43 +572,8 @@ class NodeRuntime(context: Context) {
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()
}
}
@ -601,15 +592,59 @@ class NodeRuntime(context: Context) {
canvas.setDebugStatus(status, server ?: remote)
}
}
updateHomeCanvasState()
}
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)
}
@ -698,10 +733,25 @@ class NodeRuntime(context: Context) {
operatorStatusText = "Connecting…"
updateStatus()
val token = prefs.loadGatewayToken()
val bootstrapToken = prefs.loadGatewayBootstrapToken()
val password = prefs.loadGatewayPassword()
val tls = connectionManager.resolveTlsParams(endpoint)
operatorSession.connect(endpoint, token, password, connectionManager.buildOperatorConnectOptions(), tls)
nodeSession.connect(endpoint, token, password, connectionManager.buildNodeConnectOptions(), tls)
operatorSession.connect(
endpoint,
token,
bootstrapToken,
password,
connectionManager.buildOperatorConnectOptions(),
tls,
)
nodeSession.connect(
endpoint,
token,
bootstrapToken,
password,
connectionManager.buildNodeConnectOptions(),
tls,
)
operatorSession.reconnect()
nodeSession.reconnect()
}
@ -726,9 +776,24 @@ class NodeRuntime(context: Context) {
nodeStatusText = "Connecting…"
updateStatus()
val token = prefs.loadGatewayToken()
val bootstrapToken = prefs.loadGatewayBootstrapToken()
val password = prefs.loadGatewayPassword()
operatorSession.connect(endpoint, token, password, connectionManager.buildOperatorConnectOptions(), tls)
nodeSession.connect(endpoint, token, password, connectionManager.buildNodeConnectOptions(), tls)
operatorSession.connect(
endpoint,
token,
bootstrapToken,
password,
connectionManager.buildOperatorConnectOptions(),
tls,
)
nodeSession.connect(
endpoint,
token,
bootstrapToken,
password,
connectionManager.buildNodeConnectOptions(),
tls,
)
}
fun acceptGatewayTrustPrompt() {
@ -897,11 +962,177 @@ class NodeRuntime(context: Context) {
val parsed = parseHexColorArgb(raw)
_seamColorArgb.value = parsed ?: DEFAULT_SEAM_COLOR_ARGB
updateHomeCanvasState()
} catch (_: Throwable) {
// ignore
}
}
private suspend fun refreshAgentsFromGateway() {
if (!operatorConnected) return
try {
val res = operatorSession.request("agents.list", "{}")
val root = json.parseToJsonElement(res).asObjectOrNull() ?: return
val defaultAgentId = root["defaultId"].asStringOrNull()?.trim().orEmpty()
val mainKey = normalizeMainKey(root["mainKey"].asStringOrNull())
val agents =
(root["agents"] as? JsonArray)?.mapNotNull { item ->
val obj = item.asObjectOrNull() ?: return@mapNotNull null
val id = obj["id"].asStringOrNull()?.trim().orEmpty()
if (id.isEmpty()) return@mapNotNull null
val name = obj["name"].asStringOrNull()?.trim()
val emoji = obj["identity"].asObjectOrNull()?.get("emoji").asStringOrNull()?.trim()
GatewayAgentSummary(
id = id,
name = name?.takeIf { it.isNotEmpty() },
emoji = emoji?.takeIf { it.isNotEmpty() },
)
} ?: emptyList()
gatewayDefaultAgentId = defaultAgentId.ifEmpty { null }
gatewayAgents = agents
applyMainSessionKey(mainKey)
updateHomeCanvasState()
} catch (_: Throwable) {
// ignore
}
}
private fun updateHomeCanvasState() {
val payload =
try {
json.encodeToString(makeHomeCanvasPayload())
} catch (_: Throwable) {
null
}
canvas.updateHomeCanvasState(payload)
}
private fun makeHomeCanvasPayload(): HomeCanvasPayload {
val state = resolveHomeCanvasGatewayState()
val gatewayName = normalized(_serverName.value)
val gatewayAddress = normalized(_remoteAddress.value)
val gatewayLabel = gatewayName ?: gatewayAddress ?: "Gateway"
val activeAgentId = resolveActiveAgentId()
val agents = homeCanvasAgents(activeAgentId)
return when (state) {
HomeCanvasGatewayState.Connected ->
HomeCanvasPayload(
gatewayState = "connected",
eyebrow = "Connected to $gatewayLabel",
title = "Your agents are ready",
subtitle =
"This phone stays dormant until the gateway needs it, then wakes, syncs, and goes back to sleep.",
gatewayLabel = gatewayLabel,
activeAgentName = resolveActiveAgentName(activeAgentId),
activeAgentBadge = agents.firstOrNull { it.isActive }?.badge ?: "OC",
activeAgentCaption = "Selected on this phone",
agentCount = agents.size,
agents = agents.take(6),
footer = "The overview refreshes on reconnect and when this screen opens.",
)
HomeCanvasGatewayState.Connecting ->
HomeCanvasPayload(
gatewayState = "connecting",
eyebrow = "Reconnecting",
title = "OpenClaw is syncing back up",
subtitle =
"The gateway session is coming back online. Agent shortcuts should settle automatically in a moment.",
gatewayLabel = gatewayLabel,
activeAgentName = resolveActiveAgentName(activeAgentId),
activeAgentBadge = "OC",
activeAgentCaption = "Gateway session in progress",
agentCount = agents.size,
agents = agents.take(4),
footer = "If the gateway is reachable, reconnect should complete without intervention.",
)
HomeCanvasGatewayState.Error, HomeCanvasGatewayState.Offline ->
HomeCanvasPayload(
gatewayState = if (state == HomeCanvasGatewayState.Error) "error" else "offline",
eyebrow = "Welcome to OpenClaw",
title = "Your phone stays quiet until it is needed",
subtitle =
"Pair this device to your gateway to wake it only for real work, keep a live agent overview handy, and avoid battery-draining background loops.",
gatewayLabel = gatewayLabel,
activeAgentName = "Main",
activeAgentBadge = "OC",
activeAgentCaption = "Connect to load your agents",
agentCount = agents.size,
agents = agents.take(4),
footer = "When connected, the gateway can wake the phone with a silent push instead of holding an always-on session.",
)
}
}
private fun resolveHomeCanvasGatewayState(): HomeCanvasGatewayState {
val lower = _statusText.value.trim().lowercase()
return when {
_isConnected.value -> HomeCanvasGatewayState.Connected
lower.contains("connecting") || lower.contains("reconnecting") -> HomeCanvasGatewayState.Connecting
lower.contains("error") || lower.contains("failed") -> HomeCanvasGatewayState.Error
else -> HomeCanvasGatewayState.Offline
}
}
private fun resolveActiveAgentId(): String {
val mainKey = _mainSessionKey.value.trim()
if (mainKey.startsWith("agent:")) {
val agentId = mainKey.removePrefix("agent:").substringBefore(':').trim()
if (agentId.isNotEmpty()) return agentId
}
return gatewayDefaultAgentId?.trim().orEmpty()
}
private fun resolveActiveAgentName(activeAgentId: String): String {
if (activeAgentId.isNotEmpty()) {
gatewayAgents.firstOrNull { it.id == activeAgentId }?.let { agent ->
return normalized(agent.name) ?: agent.id
}
return activeAgentId
}
return gatewayAgents.firstOrNull()?.let { normalized(it.name) ?: it.id } ?: "Main"
}
private fun homeCanvasAgents(activeAgentId: String): List<HomeCanvasAgentCard> {
val defaultAgentId = gatewayDefaultAgentId?.trim().orEmpty()
return gatewayAgents
.map { agent ->
val isActive = activeAgentId.isNotEmpty() && agent.id == activeAgentId
val isDefault = defaultAgentId.isNotEmpty() && agent.id == defaultAgentId
HomeCanvasAgentCard(
id = agent.id,
name = normalized(agent.name) ?: agent.id,
badge = homeCanvasBadge(agent),
caption =
when {
isActive -> "Active on this phone"
isDefault -> "Default agent"
else -> "Ready"
},
isActive = isActive,
)
}.sortedWith(compareByDescending<HomeCanvasAgentCard> { it.isActive }.thenBy { it.name.lowercase() })
}
private fun homeCanvasBadge(agent: GatewayAgentSummary): String {
val emoji = normalized(agent.emoji)
if (emoji != null) return emoji
val initials =
(normalized(agent.name) ?: agent.id)
.split(' ', '-', '_')
.filter { it.isNotBlank() }
.take(2)
.mapNotNull { token -> token.firstOrNull()?.uppercaseChar()?.toString() }
.joinToString("")
return if (initials.isNotEmpty()) initials else "OC"
}
private fun normalized(value: String?): String? {
val trimmed = value?.trim().orEmpty()
return trimmed.ifEmpty { null }
}
private fun triggerCameraFlash() {
// Token is used as a pulse trigger; value doesn't matter as long as it changes.
_cameraFlashToken.value = SystemClock.elapsedRealtimeNanos()
@ -920,3 +1151,40 @@ class NodeRuntime(context: Context) {
}
}
private enum class HomeCanvasGatewayState {
Connected,
Connecting,
Error,
Offline,
}
private data class GatewayAgentSummary(
val id: String,
val name: String?,
val emoji: String?,
)
@Serializable
private data class HomeCanvasPayload(
val gatewayState: String,
val eyebrow: String,
val title: String,
val subtitle: String,
val gatewayLabel: String,
val activeAgentName: String,
val activeAgentBadge: String,
val activeAgentCaption: String,
val agentCount: Int,
val agents: List<HomeCanvasAgentCard>,
val footer: String,
)
@Serializable
private data class HomeCanvasAgentCard(
val id: String,
val name: String,
val badge: String,
val caption: String,
val isActive: Boolean,
)

View File

@ -15,7 +15,10 @@ import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonPrimitive
import java.util.UUID
class SecurePrefs(context: Context) {
class SecurePrefs(
context: Context,
private val securePrefsOverride: SharedPreferences? = null,
) {
companion object {
val defaultWakeWords: List<String> = listOf("openclaw", "claude")
private const val displayNameKey = "node.displayName"
@ -35,7 +38,7 @@ class SecurePrefs(context: Context) {
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
}
private val securePrefs: SharedPreferences by lazy { createSecurePrefs(appContext, securePrefsName) }
private val securePrefs: SharedPreferences by lazy { securePrefsOverride ?: createSecurePrefs(appContext, securePrefsName) }
private val _instanceId = MutableStateFlow(loadOrCreateInstanceId())
val instanceId: StateFlow<String> = _instanceId
@ -76,6 +79,9 @@ class SecurePrefs(context: Context) {
private val _gatewayToken = MutableStateFlow("")
val gatewayToken: StateFlow<String> = _gatewayToken
private val _gatewayBootstrapToken = MutableStateFlow("")
val gatewayBootstrapToken: StateFlow<String> = _gatewayBootstrapToken
private val _onboardingCompleted =
MutableStateFlow(plainPrefs.getBoolean("onboarding.completed", false))
val onboardingCompleted: StateFlow<Boolean> = _onboardingCompleted
@ -165,6 +171,10 @@ class SecurePrefs(context: Context) {
saveGatewayPassword(value)
}
fun setGatewayBootstrapToken(value: String) {
saveGatewayBootstrapToken(value)
}
fun setOnboardingCompleted(value: Boolean) {
plainPrefs.edit { putBoolean("onboarding.completed", value) }
_onboardingCompleted.value = value
@ -193,6 +203,26 @@ class SecurePrefs(context: Context) {
securePrefs.edit { putString(key, token.trim()) }
}
fun loadGatewayBootstrapToken(): String? {
val key = "gateway.bootstrapToken.${_instanceId.value}"
val stored =
_gatewayBootstrapToken.value.trim().ifEmpty {
val persisted = securePrefs.getString(key, null)?.trim().orEmpty()
if (persisted.isNotEmpty()) {
_gatewayBootstrapToken.value = persisted
}
persisted
}
return stored.takeIf { it.isNotEmpty() }
}
fun saveGatewayBootstrapToken(token: String) {
val key = "gateway.bootstrapToken.${_instanceId.value}"
val trimmed = token.trim()
securePrefs.edit { putString(key, trimmed) }
_gatewayBootstrapToken.value = trimmed
}
fun loadGatewayPassword(): String? {
val key = "gateway.password.${_instanceId.value}"
val stored = securePrefs.getString(key, null)?.trim()

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()
@ -265,13 +267,15 @@ class ChatController(
}
val historyJson = session.request("chat.history", """{"sessionKey":"$key"}""")
val history = parseHistory(historyJson, sessionKey = key)
val history = parseHistory(historyJson, sessionKey = key, previousMessages = _messages.value)
_messages.value = history.messages
_sessionId.value = history.sessionId
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
}
@ -336,7 +340,7 @@ class ChatController(
try {
val historyJson =
session.request("chat.history", """{"sessionKey":"${_sessionKey.value}"}""")
val history = parseHistory(historyJson, sessionKey = _sessionKey.value)
val history = parseHistory(historyJson, sessionKey = _sessionKey.value, previousMessages = _messages.value)
_messages.value = history.messages
_sessionId.value = history.sessionId
history.thinkingLevel?.trim()?.takeIf { it.isNotEmpty() }?.let { _thinkingLevel.value = it }
@ -450,7 +454,11 @@ class ChatController(
}
}
private fun parseHistory(historyJson: String, sessionKey: String): ChatHistory {
private fun parseHistory(
historyJson: String,
sessionKey: String,
previousMessages: List<ChatMessage>,
): ChatHistory {
val root = json.parseToJsonElement(historyJson).asObjectOrNull() ?: return ChatHistory(sessionKey, null, null, emptyList())
val sid = root["sessionId"].asStringOrNull()
val thinkingLevel = root["thinkingLevel"].asStringOrNull()
@ -470,7 +478,12 @@ class ChatController(
)
}
return ChatHistory(sessionKey = sessionKey, sessionId = sid, thinkingLevel = thinkingLevel, messages = messages)
return ChatHistory(
sessionKey = sessionKey,
sessionId = sid,
thinkingLevel = thinkingLevel,
messages = reconcileMessageIds(previous = previousMessages, incoming = messages),
)
}
private fun parseMessageContent(el: JsonElement): ChatMessageContent? {
@ -519,6 +532,47 @@ class ChatController(
}
}
internal fun reconcileMessageIds(previous: List<ChatMessage>, incoming: List<ChatMessage>): List<ChatMessage> {
if (previous.isEmpty() || incoming.isEmpty()) return incoming
val idsByKey = LinkedHashMap<String, ArrayDeque<String>>()
for (message in previous) {
val key = messageIdentityKey(message) ?: continue
idsByKey.getOrPut(key) { ArrayDeque() }.addLast(message.id)
}
return incoming.map { message ->
val key = messageIdentityKey(message) ?: return@map message
val ids = idsByKey[key] ?: return@map message
val reusedId = ids.removeFirstOrNull() ?: return@map message
if (ids.isEmpty()) {
idsByKey.remove(key)
}
if (reusedId == message.id) return@map message
message.copy(id = reusedId)
}
}
internal fun messageIdentityKey(message: ChatMessage): String? {
val role = message.role.trim().lowercase()
if (role.isEmpty()) return null
val timestamp = message.timestampMs?.toString().orEmpty()
val contentFingerprint =
message.content.joinToString(separator = "\u001E") { part ->
listOf(
part.type.trim().lowercase(),
part.text?.trim().orEmpty(),
part.mimeType?.trim()?.lowercase().orEmpty(),
part.fileName?.trim().orEmpty(),
part.base64?.hashCode()?.toString().orEmpty(),
).joinToString(separator = "\u001F")
}
if (timestamp.isEmpty() && contentFingerprint.isEmpty()) return null
return listOf(role, timestamp, contentFingerprint).joinToString(separator = "|")
}
private fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject
private fun JsonElement?.asArrayOrNull(): JsonArray? = this as? JsonArray

View File

@ -5,6 +5,7 @@ import ai.openclaw.app.SecurePrefs
interface DeviceAuthTokenStore {
fun loadToken(deviceId: String, role: String): String?
fun saveToken(deviceId: String, role: String, token: String)
fun clearToken(deviceId: String, role: String)
}
class DeviceAuthStore(private val prefs: SecurePrefs) : DeviceAuthTokenStore {
@ -18,7 +19,7 @@ class DeviceAuthStore(private val prefs: SecurePrefs) : DeviceAuthTokenStore {
prefs.putString(key, token.trim())
}
fun clearToken(deviceId: String, role: String) {
override fun clearToken(deviceId: String, role: String) {
val key = tokenKey(deviceId, role)
prefs.remove(key)
}

View File

@ -52,6 +52,33 @@ data class GatewayConnectOptions(
val userAgent: String? = null,
)
private enum class GatewayConnectAuthSource {
DEVICE_TOKEN,
SHARED_TOKEN,
BOOTSTRAP_TOKEN,
PASSWORD,
NONE,
}
data class GatewayConnectErrorDetails(
val code: String?,
val canRetryWithDeviceToken: Boolean,
val recommendedNextStep: String?,
)
private data class SelectedConnectAuth(
val authToken: String?,
val authBootstrapToken: String?,
val authDeviceToken: String?,
val authPassword: String?,
val signatureToken: String?,
val authSource: GatewayConnectAuthSource,
val attemptedDeviceTokenRetry: Boolean,
)
private class GatewayConnectFailure(val gatewayError: GatewaySession.ErrorShape) :
IllegalStateException(gatewayError.message)
class GatewaySession(
private val scope: CoroutineScope,
private val identityStore: DeviceIdentityStore,
@ -83,7 +110,11 @@ class GatewaySession(
}
}
data class ErrorShape(val code: String, val message: String)
data class ErrorShape(
val code: String,
val message: String,
val details: GatewayConnectErrorDetails? = null,
)
private val json = Json { ignoreUnknownKeys = true }
private val writeLock = Mutex()
@ -95,6 +126,7 @@ class GatewaySession(
private data class DesiredConnection(
val endpoint: GatewayEndpoint,
val token: String?,
val bootstrapToken: String?,
val password: String?,
val options: GatewayConnectOptions,
val tls: GatewayTlsParams?,
@ -103,15 +135,22 @@ class GatewaySession(
private var desired: DesiredConnection? = null
private var job: Job? = null
@Volatile private var currentConnection: Connection? = null
@Volatile private var pendingDeviceTokenRetry = false
@Volatile private var deviceTokenRetryBudgetUsed = false
@Volatile private var reconnectPausedForAuthFailure = false
fun connect(
endpoint: GatewayEndpoint,
token: String?,
bootstrapToken: String?,
password: String?,
options: GatewayConnectOptions,
tls: GatewayTlsParams? = null,
) {
desired = DesiredConnection(endpoint, token, password, options, tls)
desired = DesiredConnection(endpoint, token, bootstrapToken, password, options, tls)
pendingDeviceTokenRetry = false
deviceTokenRetryBudgetUsed = false
reconnectPausedForAuthFailure = false
if (job == null) {
job = scope.launch(Dispatchers.IO) { runLoop() }
}
@ -119,6 +158,9 @@ class GatewaySession(
fun disconnect() {
desired = null
pendingDeviceTokenRetry = false
deviceTokenRetryBudgetUsed = false
reconnectPausedForAuthFailure = false
currentConnection?.closeQuietly()
scope.launch(Dispatchers.IO) {
job?.cancelAndJoin()
@ -130,6 +172,7 @@ class GatewaySession(
}
fun reconnect() {
reconnectPausedForAuthFailure = false
currentConnection?.closeQuietly()
}
@ -219,6 +262,7 @@ class GatewaySession(
private inner class Connection(
private val endpoint: GatewayEndpoint,
private val token: String?,
private val bootstrapToken: String?,
private val password: String?,
private val options: GatewayConnectOptions,
private val tls: GatewayTlsParams?,
@ -344,15 +388,48 @@ class GatewaySession(
private suspend fun sendConnect(connectNonce: String) {
val identity = identityStore.loadOrCreate()
val storedToken = deviceAuthStore.loadToken(identity.deviceId, options.role)
val trimmedToken = token?.trim().orEmpty()
// QR/setup/manual shared token must take precedence; stale role tokens can survive re-onboarding.
val authToken = if (trimmedToken.isNotBlank()) trimmedToken else storedToken.orEmpty()
val payload = buildConnectParams(identity, connectNonce, authToken, password?.trim())
val storedToken = deviceAuthStore.loadToken(identity.deviceId, options.role)?.trim()
val selectedAuth =
selectConnectAuth(
endpoint = endpoint,
tls = tls,
role = options.role,
explicitGatewayToken = token?.trim()?.takeIf { it.isNotEmpty() },
explicitBootstrapToken = bootstrapToken?.trim()?.takeIf { it.isNotEmpty() },
explicitPassword = password?.trim()?.takeIf { it.isNotEmpty() },
storedToken = storedToken?.takeIf { it.isNotEmpty() },
)
if (selectedAuth.attemptedDeviceTokenRetry) {
pendingDeviceTokenRetry = false
}
val payload =
buildConnectParams(
identity = identity,
connectNonce = connectNonce,
selectedAuth = selectedAuth,
)
val res = request("connect", payload, timeoutMs = CONNECT_RPC_TIMEOUT_MS)
if (!res.ok) {
val msg = res.error?.message ?: "connect failed"
throw IllegalStateException(msg)
val error = res.error ?: ErrorShape("UNAVAILABLE", "connect failed")
val shouldRetryWithDeviceToken =
shouldRetryWithStoredDeviceToken(
error = error,
explicitGatewayToken = token?.trim()?.takeIf { it.isNotEmpty() },
storedToken = storedToken?.takeIf { it.isNotEmpty() },
attemptedDeviceTokenRetry = selectedAuth.attemptedDeviceTokenRetry,
endpoint = endpoint,
tls = tls,
)
if (shouldRetryWithDeviceToken) {
pendingDeviceTokenRetry = true
deviceTokenRetryBudgetUsed = true
} else if (
selectedAuth.attemptedDeviceTokenRetry &&
shouldClearStoredDeviceTokenAfterRetry(error)
) {
deviceAuthStore.clearToken(identity.deviceId, options.role)
}
throw GatewayConnectFailure(error)
}
handleConnectSuccess(res, identity.deviceId)
connectDeferred.complete(Unit)
@ -361,6 +438,9 @@ class GatewaySession(
private fun handleConnectSuccess(res: RpcResponse, deviceId: String) {
val payloadJson = res.payloadJson ?: throw IllegalStateException("connect failed: missing payload")
val obj = json.parseToJsonElement(payloadJson).asObjectOrNull() ?: throw IllegalStateException("connect failed")
pendingDeviceTokenRetry = false
deviceTokenRetryBudgetUsed = false
reconnectPausedForAuthFailure = false
val serverName = obj["server"].asObjectOrNull()?.get("host").asStringOrNull()
val authObj = obj["auth"].asObjectOrNull()
val deviceToken = authObj?.get("deviceToken").asStringOrNull()
@ -380,8 +460,7 @@ class GatewaySession(
private fun buildConnectParams(
identity: DeviceIdentity,
connectNonce: String,
authToken: String,
authPassword: String?,
selectedAuth: SelectedConnectAuth,
): JsonObject {
val client = options.client
val locale = Locale.getDefault().toLanguageTag()
@ -397,16 +476,20 @@ class GatewaySession(
client.modelIdentifier?.let { put("modelIdentifier", JsonPrimitive(it)) }
}
val password = authPassword?.trim().orEmpty()
val authJson =
when {
authToken.isNotEmpty() ->
selectedAuth.authToken != null ->
buildJsonObject {
put("token", JsonPrimitive(authToken))
put("token", JsonPrimitive(selectedAuth.authToken))
selectedAuth.authDeviceToken?.let { put("deviceToken", JsonPrimitive(it)) }
}
password.isNotEmpty() ->
selectedAuth.authBootstrapToken != null ->
buildJsonObject {
put("password", JsonPrimitive(password))
put("bootstrapToken", JsonPrimitive(selectedAuth.authBootstrapToken))
}
selectedAuth.authPassword != null ->
buildJsonObject {
put("password", JsonPrimitive(selectedAuth.authPassword))
}
else -> null
}
@ -420,7 +503,7 @@ class GatewaySession(
role = options.role,
scopes = options.scopes,
signedAtMs = signedAtMs,
token = if (authToken.isNotEmpty()) authToken else null,
token = selectedAuth.signatureToken,
nonce = connectNonce,
platform = client.platform,
deviceFamily = client.deviceFamily,
@ -483,7 +566,16 @@ class GatewaySession(
frame["error"]?.asObjectOrNull()?.let { obj ->
val code = obj["code"].asStringOrNull() ?: "UNAVAILABLE"
val msg = obj["message"].asStringOrNull() ?: "request failed"
ErrorShape(code, msg)
val detailObj = obj["details"].asObjectOrNull()
val details =
detailObj?.let {
GatewayConnectErrorDetails(
code = it["code"].asStringOrNull(),
canRetryWithDeviceToken = it["canRetryWithDeviceToken"].asBooleanOrNull() == true,
recommendedNextStep = it["recommendedNextStep"].asStringOrNull(),
)
}
ErrorShape(code, msg, details)
}
pending.remove(id)?.complete(RpcResponse(id, ok, payloadJson, error))
}
@ -607,6 +699,10 @@ class GatewaySession(
delay(250)
continue
}
if (reconnectPausedForAuthFailure) {
delay(250)
continue
}
try {
onDisconnected(if (attempt == 0) "Connecting…" else "Reconnecting…")
@ -615,6 +711,13 @@ class GatewaySession(
} catch (err: Throwable) {
attempt += 1
onDisconnected("Gateway error: ${err.message ?: err::class.java.simpleName}")
if (
err is GatewayConnectFailure &&
shouldPauseReconnectAfterAuthFailure(err.gatewayError)
) {
reconnectPausedForAuthFailure = true
continue
}
val sleepMs = minOf(8_000L, (350.0 * Math.pow(1.7, attempt.toDouble())).toLong())
delay(sleepMs)
}
@ -622,7 +725,15 @@ class GatewaySession(
}
private suspend fun connectOnce(target: DesiredConnection) = withContext(Dispatchers.IO) {
val conn = Connection(target.endpoint, target.token, target.password, target.options, target.tls)
val conn =
Connection(
target.endpoint,
target.token,
target.bootstrapToken,
target.password,
target.options,
target.tls,
)
currentConnection = conn
try {
conn.connect()
@ -698,6 +809,100 @@ class GatewaySession(
if (host == "0.0.0.0" || host == "::") return true
return host.startsWith("127.")
}
private fun selectConnectAuth(
endpoint: GatewayEndpoint,
tls: GatewayTlsParams?,
role: String,
explicitGatewayToken: String?,
explicitBootstrapToken: String?,
explicitPassword: String?,
storedToken: String?,
): SelectedConnectAuth {
val shouldUseDeviceRetryToken =
pendingDeviceTokenRetry &&
explicitGatewayToken != null &&
storedToken != null &&
isTrustedDeviceRetryEndpoint(endpoint, tls)
val authToken =
explicitGatewayToken
?: if (
explicitPassword == null &&
(explicitBootstrapToken == null || storedToken != null)
) {
storedToken
} else {
null
}
val authDeviceToken = if (shouldUseDeviceRetryToken) storedToken else null
val authBootstrapToken = if (authToken == null) explicitBootstrapToken else null
val authSource =
when {
authDeviceToken != null || (explicitGatewayToken == null && authToken != null) ->
GatewayConnectAuthSource.DEVICE_TOKEN
authToken != null -> GatewayConnectAuthSource.SHARED_TOKEN
authBootstrapToken != null -> GatewayConnectAuthSource.BOOTSTRAP_TOKEN
explicitPassword != null -> GatewayConnectAuthSource.PASSWORD
else -> GatewayConnectAuthSource.NONE
}
return SelectedConnectAuth(
authToken = authToken,
authBootstrapToken = authBootstrapToken,
authDeviceToken = authDeviceToken,
authPassword = explicitPassword,
signatureToken = authToken ?: authBootstrapToken,
authSource = authSource,
attemptedDeviceTokenRetry = shouldUseDeviceRetryToken,
)
}
private fun shouldRetryWithStoredDeviceToken(
error: ErrorShape,
explicitGatewayToken: String?,
storedToken: String?,
attemptedDeviceTokenRetry: Boolean,
endpoint: GatewayEndpoint,
tls: GatewayTlsParams?,
): Boolean {
if (deviceTokenRetryBudgetUsed) return false
if (attemptedDeviceTokenRetry) return false
if (explicitGatewayToken == null || storedToken == null) return false
if (!isTrustedDeviceRetryEndpoint(endpoint, tls)) return false
val detailCode = error.details?.code
val recommendedNextStep = error.details?.recommendedNextStep
return error.details?.canRetryWithDeviceToken == true ||
recommendedNextStep == "retry_with_device_token" ||
detailCode == "AUTH_TOKEN_MISMATCH"
}
private fun shouldPauseReconnectAfterAuthFailure(error: ErrorShape): Boolean {
return when (error.details?.code) {
"AUTH_TOKEN_MISSING",
"AUTH_BOOTSTRAP_TOKEN_INVALID",
"AUTH_PASSWORD_MISSING",
"AUTH_PASSWORD_MISMATCH",
"AUTH_RATE_LIMITED",
"PAIRING_REQUIRED",
"CONTROL_UI_DEVICE_IDENTITY_REQUIRED",
"DEVICE_IDENTITY_REQUIRED" -> true
"AUTH_TOKEN_MISMATCH" -> deviceTokenRetryBudgetUsed && !pendingDeviceTokenRetry
else -> false
}
}
private fun shouldClearStoredDeviceTokenAfterRetry(error: ErrorShape): Boolean {
return error.details?.code == "AUTH_DEVICE_TOKEN_MISMATCH"
}
private fun isTrustedDeviceRetryEndpoint(
endpoint: GatewayEndpoint,
tls: GatewayTlsParams?,
): Boolean {
if (isLoopbackHost(endpoint.host)) {
return true
}
return tls?.expectedFingerprint?.trim()?.isNotEmpty() == true
}
}
private fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject

View File

@ -0,0 +1,247 @@
package ai.openclaw.app.node
import android.Manifest
import android.content.Context
import android.provider.CallLog
import androidx.core.content.ContextCompat
import ai.openclaw.app.gateway.GatewaySession
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.put
private const val DEFAULT_CALL_LOG_LIMIT = 25
internal data class CallLogRecord(
val number: String?,
val cachedName: String?,
val date: Long,
val duration: Long,
val type: Int,
)
internal data class CallLogSearchRequest(
val limit: Int, // Number of records to return
val offset: Int, // Offset value
val cachedName: String?, // Search by contact name
val number: String?, // Search by phone number
val date: Long?, // Search by time (timestamp, deprecated, use dateStart/dateEnd)
val dateStart: Long?, // Query start time (timestamp)
val dateEnd: Long?, // Query end time (timestamp)
val duration: Long?, // Search by duration (seconds)
val type: Int?, // Search by call log type
)
internal interface CallLogDataSource {
fun hasReadPermission(context: Context): Boolean
fun search(context: Context, request: CallLogSearchRequest): List<CallLogRecord>
}
private object SystemCallLogDataSource : CallLogDataSource {
override fun hasReadPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_CALL_LOG
) == android.content.pm.PackageManager.PERMISSION_GRANTED
}
override fun search(context: Context, request: CallLogSearchRequest): List<CallLogRecord> {
val resolver = context.contentResolver
val projection = arrayOf(
CallLog.Calls.NUMBER,
CallLog.Calls.CACHED_NAME,
CallLog.Calls.DATE,
CallLog.Calls.DURATION,
CallLog.Calls.TYPE,
)
// Build selection and selectionArgs for filtering
val selections = mutableListOf<String>()
val selectionArgs = mutableListOf<String>()
request.cachedName?.let {
selections.add("${CallLog.Calls.CACHED_NAME} LIKE ?")
selectionArgs.add("%$it%")
}
request.number?.let {
selections.add("${CallLog.Calls.NUMBER} LIKE ?")
selectionArgs.add("%$it%")
}
// Support time range query
if (request.dateStart != null && request.dateEnd != null) {
selections.add("${CallLog.Calls.DATE} >= ? AND ${CallLog.Calls.DATE} <= ?")
selectionArgs.add(request.dateStart.toString())
selectionArgs.add(request.dateEnd.toString())
} else if (request.dateStart != null) {
selections.add("${CallLog.Calls.DATE} >= ?")
selectionArgs.add(request.dateStart.toString())
} else if (request.dateEnd != null) {
selections.add("${CallLog.Calls.DATE} <= ?")
selectionArgs.add(request.dateEnd.toString())
} else if (request.date != null) {
// Compatible with the old date parameter (exact match)
selections.add("${CallLog.Calls.DATE} = ?")
selectionArgs.add(request.date.toString())
}
request.duration?.let {
selections.add("${CallLog.Calls.DURATION} = ?")
selectionArgs.add(it.toString())
}
request.type?.let {
selections.add("${CallLog.Calls.TYPE} = ?")
selectionArgs.add(it.toString())
}
val selection = if (selections.isNotEmpty()) selections.joinToString(" AND ") else null
val selectionArgsArray = if (selectionArgs.isNotEmpty()) selectionArgs.toTypedArray() else null
val sortOrder = "${CallLog.Calls.DATE} DESC"
resolver.query(
CallLog.Calls.CONTENT_URI,
projection,
selection,
selectionArgsArray,
sortOrder,
).use { cursor ->
if (cursor == null) return emptyList()
val numberIndex = cursor.getColumnIndex(CallLog.Calls.NUMBER)
val cachedNameIndex = cursor.getColumnIndex(CallLog.Calls.CACHED_NAME)
val dateIndex = cursor.getColumnIndex(CallLog.Calls.DATE)
val durationIndex = cursor.getColumnIndex(CallLog.Calls.DURATION)
val typeIndex = cursor.getColumnIndex(CallLog.Calls.TYPE)
// Skip offset rows
if (request.offset > 0 && cursor.moveToPosition(request.offset - 1)) {
// Successfully moved to offset position
}
val out = mutableListOf<CallLogRecord>()
var count = 0
while (cursor.moveToNext() && count < request.limit) {
out += CallLogRecord(
number = cursor.getString(numberIndex),
cachedName = cursor.getString(cachedNameIndex),
date = cursor.getLong(dateIndex),
duration = cursor.getLong(durationIndex),
type = cursor.getInt(typeIndex),
)
count++
}
return out
}
}
}
class CallLogHandler private constructor(
private val appContext: Context,
private val dataSource: CallLogDataSource,
) {
constructor(appContext: Context) : this(appContext = appContext, dataSource = SystemCallLogDataSource)
fun handleCallLogSearch(paramsJson: String?): GatewaySession.InvokeResult {
if (!dataSource.hasReadPermission(appContext)) {
return GatewaySession.InvokeResult.error(
code = "CALL_LOG_PERMISSION_REQUIRED",
message = "CALL_LOG_PERMISSION_REQUIRED: grant Call Log permission",
)
}
val request = parseSearchRequest(paramsJson)
?: return GatewaySession.InvokeResult.error(
code = "INVALID_REQUEST",
message = "INVALID_REQUEST: expected JSON object",
)
return try {
val callLogs = dataSource.search(appContext, request)
GatewaySession.InvokeResult.ok(
buildJsonObject {
put(
"callLogs",
buildJsonArray {
callLogs.forEach { add(callLogJson(it)) }
},
)
}.toString(),
)
} catch (err: Throwable) {
GatewaySession.InvokeResult.error(
code = "CALL_LOG_UNAVAILABLE",
message = "CALL_LOG_UNAVAILABLE: ${err.message ?: "call log query failed"}",
)
}
}
private fun parseSearchRequest(paramsJson: String?): CallLogSearchRequest? {
if (paramsJson.isNullOrBlank()) {
return CallLogSearchRequest(
limit = DEFAULT_CALL_LOG_LIMIT,
offset = 0,
cachedName = null,
number = null,
date = null,
dateStart = null,
dateEnd = null,
duration = null,
type = null,
)
}
val params = try {
Json.parseToJsonElement(paramsJson).asObjectOrNull()
} catch (_: Throwable) {
null
} ?: return null
val limit = ((params["limit"] as? JsonPrimitive)?.content?.toIntOrNull() ?: DEFAULT_CALL_LOG_LIMIT)
.coerceIn(1, 200)
val offset = ((params["offset"] as? JsonPrimitive)?.content?.toIntOrNull() ?: 0)
.coerceAtLeast(0)
val cachedName = (params["cachedName"] as? JsonPrimitive)?.content?.takeIf { it.isNotBlank() }
val number = (params["number"] as? JsonPrimitive)?.content?.takeIf { it.isNotBlank() }
val date = (params["date"] as? JsonPrimitive)?.content?.toLongOrNull()
val dateStart = (params["dateStart"] as? JsonPrimitive)?.content?.toLongOrNull()
val dateEnd = (params["dateEnd"] as? JsonPrimitive)?.content?.toLongOrNull()
val duration = (params["duration"] as? JsonPrimitive)?.content?.toLongOrNull()
val type = (params["type"] as? JsonPrimitive)?.content?.toIntOrNull()
return CallLogSearchRequest(
limit = limit,
offset = offset,
cachedName = cachedName,
number = number,
date = date,
dateStart = dateStart,
dateEnd = dateEnd,
duration = duration,
type = type,
)
}
private fun callLogJson(callLog: CallLogRecord): JsonObject {
return buildJsonObject {
put("number", JsonPrimitive(callLog.number))
put("cachedName", JsonPrimitive(callLog.cachedName))
put("date", JsonPrimitive(callLog.date))
put("duration", JsonPrimitive(callLog.duration))
put("type", JsonPrimitive(callLog.type))
}
}
companion object {
internal fun forTesting(
appContext: Context,
dataSource: CallLogDataSource,
): CallLogHandler = CallLogHandler(appContext = appContext, dataSource = dataSource)
}
}

View File

@ -34,6 +34,7 @@ class CanvasController {
@Volatile private var debugStatusEnabled: Boolean = false
@Volatile private var debugStatusTitle: String? = null
@Volatile private var debugStatusSubtitle: String? = null
@Volatile private var homeCanvasStateJson: String? = null
private val _currentUrl = MutableStateFlow<String?>(null)
val currentUrl: StateFlow<String?> = _currentUrl.asStateFlow()
@ -56,6 +57,7 @@ class CanvasController {
this.webView = webView
reload()
applyDebugStatus()
applyHomeCanvasState()
}
fun detach(webView: WebView) {
@ -88,6 +90,12 @@ class CanvasController {
fun onPageFinished() {
applyDebugStatus()
applyHomeCanvasState()
}
fun updateHomeCanvasState(json: String?) {
homeCanvasStateJson = json
applyHomeCanvasState()
}
private inline fun withWebViewOnMain(crossinline block: (WebView) -> Unit) {
@ -142,6 +150,22 @@ class CanvasController {
}
}
private fun applyHomeCanvasState() {
val payload = homeCanvasStateJson ?: "null"
withWebViewOnMain { wv ->
val js = """
(() => {
try {
const api = globalThis.__openclaw;
if (!api || typeof api.renderHome !== 'function') return;
api.renderHome($payload);
} catch (_) {}
})();
""".trimIndent()
wv.evaluateJavascript(js, null)
}
}
suspend fun eval(javaScript: String): String =
withContext(Dispatchers.Main) {
val wv = webView ?: throw IllegalStateException("no webview")

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

@ -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(
@ -212,6 +214,13 @@ class DeviceHandler(
promptableWhenDenied = true,
),
)
put(
"callLog",
permissionStateJson(
granted = callLogEnabled && hasPermission(Manifest.permission.READ_CALL_LOG),
promptableWhenDenied = callLogEnabled,
),
)
put(
"motion",
permissionStateJson(

View File

@ -5,6 +5,7 @@ import ai.openclaw.app.protocol.OpenClawCanvasA2UICommand
import ai.openclaw.app.protocol.OpenClawCanvasCommand
import ai.openclaw.app.protocol.OpenClawCameraCommand
import ai.openclaw.app.protocol.OpenClawCapability
import ai.openclaw.app.protocol.OpenClawCallLogCommand
import ai.openclaw.app.protocol.OpenClawContactsCommand
import ai.openclaw.app.protocol.OpenClawDeviceCommand
import ai.openclaw.app.protocol.OpenClawLocationCommand
@ -17,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,
@ -28,7 +31,9 @@ enum class InvokeCommandAvailability {
Always,
CameraEnabled,
LocationEnabled,
SmsAvailable,
SendSmsAvailable,
ReadSmsAvailable,
CallLogAvailable,
MotionActivityAvailable,
MotionPedometerAvailable,
DebugBuild,
@ -39,6 +44,7 @@ enum class NodeCapabilityAvailability {
CameraEnabled,
LocationEnabled,
SmsAvailable,
CallLogAvailable,
VoiceWakeEnabled,
MotionAvailable,
}
@ -84,6 +90,10 @@ object InvokeCommandRegistry {
name = OpenClawCapability.Motion.rawValue,
availability = NodeCapabilityAvailability.MotionAvailable,
),
NodeCapabilitySpec(
name = OpenClawCapability.CallLog.rawValue,
availability = NodeCapabilityAvailability.CallLogAvailable,
),
)
val all: List<InvokeCommandSpec> =
@ -185,7 +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",
@ -208,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
}
@ -223,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

@ -5,6 +5,7 @@ import ai.openclaw.app.protocol.OpenClawCalendarCommand
import ai.openclaw.app.protocol.OpenClawCanvasA2UICommand
import ai.openclaw.app.protocol.OpenClawCanvasCommand
import ai.openclaw.app.protocol.OpenClawCameraCommand
import ai.openclaw.app.protocol.OpenClawCallLogCommand
import ai.openclaw.app.protocol.OpenClawContactsCommand
import ai.openclaw.app.protocol.OpenClawDeviceCommand
import ai.openclaw.app.protocol.OpenClawLocationCommand
@ -27,10 +28,13 @@ class InvokeDispatcher(
private val smsHandler: SmsHandler,
private val a2uiHandler: A2UIHandler,
private val debugHandler: DebugHandler,
private val callLogHandler: CallLogHandler,
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,
@ -160,6 +164,10 @@ class InvokeDispatcher(
// SMS command
OpenClawSmsCommand.Send.rawValue -> smsHandler.handleSmsSend(paramsJson)
OpenClawSmsCommand.Search.rawValue -> smsHandler.handleSmsSearch(paramsJson)
// CallLog command
OpenClawCallLogCommand.Search.rawValue -> callLogHandler.handleCallLogSearch(paramsJson)
// Debug commands
"debug.ed25519" -> debugHandler.handleEd25519()
@ -251,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(
@ -260,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

@ -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

@ -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

@ -13,6 +13,7 @@ enum class OpenClawCapability(val rawValue: String) {
Contacts("contacts"),
Calendar("calendar"),
Motion("motion"),
CallLog("callLog"),
}
enum class OpenClawCanvasCommand(val rawValue: String) {
@ -52,6 +53,7 @@ enum class OpenClawCameraCommand(val rawValue: String) {
enum class OpenClawSmsCommand(val rawValue: String) {
Send("sms.send"),
Search("sms.search"),
;
companion object {
@ -137,3 +139,12 @@ enum class OpenClawMotionCommand(val rawValue: String) {
const val NamespacePrefix: String = "motion."
}
}
enum class OpenClawCallLogCommand(val rawValue: String) {
Search("callLog.search"),
;
companion object {
const val NamespacePrefix: String = "callLog."
}
}

View File

@ -25,7 +25,7 @@ import ai.openclaw.app.MainViewModel
@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) }
@ -45,6 +45,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
@ -127,6 +128,16 @@ fun CanvasScreen(viewModel: MainViewModel, modifier: Modifier = Modifier) {
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()
}
},
)
}

View File

@ -1,13 +1,14 @@
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
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@ -18,8 +19,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape
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
import androidx.compose.material.icons.filled.PowerSettingsNew
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@ -45,8 +50,10 @@ 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
private enum class ConnectInputMode {
SetupCode,
@ -55,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()
@ -87,20 +95,28 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
val prompt = pendingTrust!!
AlertDialog(
onDismissRequest = { viewModel.declineGatewayTrustPrompt() },
title = { Text("Trust this gateway?") },
containerColor = mobileCardSurface,
title = { Text("Trust this gateway?", style = mobileHeadline, color = mobileText) },
text = {
Text(
"First-time TLS connection.\n\nVerify this SHA-256 fingerprint before trusting:\n${prompt.fingerprintSha256}",
style = mobileCallout,
color = mobileText,
)
},
confirmButton = {
TextButton(onClick = { viewModel.acceptGatewayTrustPrompt() }) {
TextButton(
onClick = { viewModel.acceptGatewayTrustPrompt() },
colors = ButtonDefaults.textButtonColors(contentColor = mobileAccent),
) {
Text("Trust and continue")
}
},
dismissButton = {
TextButton(onClick = { viewModel.declineGatewayTrustPrompt() }) {
TextButton(
onClick = { viewModel.declineGatewayTrustPrompt() },
colors = ButtonDefaults.textButtonColors(contentColor = mobileTextSecondary),
) {
Text("Cancel")
}
},
@ -121,100 +137,190 @@ 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),
verticalArrangement = Arrangement.spacedBy(14.dp),
) {
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text("Connection Control", style = mobileCaption1.copy(fontWeight = FontWeight.Bold), color = mobileAccent)
Text("Gateway Connection", style = mobileTitle1, color = mobileText)
Text(
"One primary action. Open advanced controls only when needed.",
if (isConnected) "Your gateway is active and ready." else "Connect to your gateway to get started.",
style = mobileCallout,
color = mobileTextSecondary,
)
}
// Status cards in a unified card group
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp),
color = mobileSurface,
color = mobileCardSurface,
border = BorderStroke(1.dp, mobileBorder),
) {
Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text("Active endpoint", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
Text(activeEndpoint, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText)
Column {
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Surface(
shape = RoundedCornerShape(10.dp),
color = mobileAccentSoft,
) {
Icon(
imageVector = Icons.Default.Link,
contentDescription = null,
modifier = Modifier.padding(8.dp).size(18.dp),
tint = mobileAccent,
)
}
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text("Endpoint", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
Text(activeEndpoint, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText)
}
}
HorizontalDivider(color = mobileBorder)
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Surface(
shape = RoundedCornerShape(10.dp),
color = if (isConnected) mobileSuccessSoft else mobileSurface,
) {
Icon(
imageVector = Icons.Default.Cloud,
contentDescription = null,
modifier = Modifier.padding(8.dp).size(18.dp),
tint = if (isConnected) mobileSuccess else mobileTextTertiary,
)
}
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text("Status", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
Text(statusText, style = mobileBody, color = if (isConnected) mobileSuccess else mobileText)
}
}
}
}
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp),
color = mobileSurface,
border = BorderStroke(1.dp, mobileBorder),
) {
Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text("Gateway state", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
Text(statusText, style = mobileBody, color = mobileText)
}
}
Button(
onClick = {
if (isConnected) {
if (isConnected) {
// Outlined secondary button when connected — don't scream "danger"
Button(
onClick = {
viewModel.disconnect()
validationText = null
return@Button
}
if (statusText.contains("operator offline", ignoreCase = true)) {
},
modifier = Modifier.fillMaxWidth().height(48.dp),
shape = RoundedCornerShape(14.dp),
colors =
ButtonDefaults.buttonColors(
containerColor = mobileCardSurface,
contentColor = mobileDanger,
),
border = BorderStroke(1.dp, mobileDanger.copy(alpha = 0.4f)),
) {
Icon(Icons.Default.PowerSettingsNew, contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("Disconnect", style = mobileHeadline.copy(fontWeight = FontWeight.SemiBold))
}
} else {
Button(
onClick = {
if (statusText.contains("operator offline", ignoreCase = true)) {
validationText = null
viewModel.refreshGatewayConnection()
return@Button
}
val config =
resolveGatewayConnectConfig(
useSetupCode = inputMode == ConnectInputMode.SetupCode,
setupCode = setupCode,
manualHost = manualHostInput,
manualPort = manualPortInput,
manualTls = manualTlsInput,
fallbackToken = gatewayToken,
fallbackPassword = passwordInput,
)
if (config == null) {
validationText =
if (inputMode == ConnectInputMode.SetupCode) {
"Paste a valid setup code to connect."
} else {
"Enter a valid manual host and port to connect."
}
return@Button
}
validationText = null
viewModel.refreshGatewayConnection()
return@Button
}
viewModel.setManualEnabled(true)
viewModel.setManualHost(config.host)
viewModel.setManualPort(config.port)
viewModel.setManualTls(config.tls)
viewModel.setGatewayBootstrapToken(config.bootstrapToken)
if (config.token.isNotBlank()) {
viewModel.setGatewayToken(config.token)
} else if (config.bootstrapToken.isNotBlank()) {
viewModel.setGatewayToken("")
}
viewModel.setGatewayPassword(config.password)
viewModel.connectManual()
},
modifier = Modifier.fillMaxWidth().height(52.dp),
shape = RoundedCornerShape(14.dp),
colors =
ButtonDefaults.buttonColors(
containerColor = mobileAccent,
contentColor = Color.White,
),
) {
Text("Connect Gateway", style = mobileHeadline.copy(fontWeight = FontWeight.Bold))
}
}
val config =
resolveGatewayConnectConfig(
useSetupCode = inputMode == ConnectInputMode.SetupCode,
setupCode = setupCode,
manualHost = manualHostInput,
manualPort = manualPortInput,
manualTls = manualTlsInput,
fallbackToken = gatewayToken,
fallbackPassword = passwordInput,
)
if (config == null) {
validationText =
if (inputMode == ConnectInputMode.SetupCode) {
"Paste a valid setup code to connect."
} else {
"Enter a valid manual host and port to connect."
}
return@Button
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))
}
}
validationText = null
viewModel.setManualEnabled(true)
viewModel.setManualHost(config.host)
viewModel.setManualPort(config.port)
viewModel.setManualTls(config.tls)
if (config.token.isNotBlank()) {
viewModel.setGatewayToken(config.token)
}
viewModel.setGatewayPassword(config.password)
viewModel.connectManual()
},
modifier = Modifier.fillMaxWidth().height(52.dp),
shape = RoundedCornerShape(14.dp),
colors =
ButtonDefaults.buttonColors(
containerColor = if (isConnected) mobileDanger else mobileAccent,
contentColor = Color.White,
),
) {
Text(primaryLabel, style = mobileHeadline.copy(fontWeight = FontWeight.Bold))
}
}
Surface(
@ -245,7 +351,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp),
color = Color.White,
color = mobileCardSurface,
border = BorderStroke(1.dp, mobileBorder),
) {
Column(
@ -427,7 +533,7 @@ private fun MethodChip(label: String, active: Boolean, onClick: () -> Unit) {
containerColor = if (active) mobileAccent else mobileSurface,
contentColor = if (active) Color.White else mobileText,
),
border = BorderStroke(1.dp, if (active) Color(0xFF184DAF) else mobileBorderStrong),
border = BorderStroke(1.dp, if (active) mobileAccentBorderStrong else mobileBorderStrong),
) {
Text(label, style = mobileCaption1.copy(fontWeight = FontWeight.Bold))
}
@ -456,10 +562,10 @@ private fun CommandBlock(command: String) {
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
color = mobileCodeBg,
border = BorderStroke(1.dp, Color(0xFF2B2E35)),
border = BorderStroke(1.dp, mobileCodeBorder),
) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Box(modifier = Modifier.width(3.dp).height(42.dp).background(Color(0xFF3FC97A)))
Box(modifier = Modifier.width(3.dp).height(42.dp).background(mobileCodeAccent))
Text(
text = command,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),

View File

@ -1,8 +1,8 @@
package ai.openclaw.app.ui
import androidx.core.net.toUri
import java.util.Base64
import java.util.Locale
import java.net.URI
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
@ -18,6 +18,7 @@ internal data class GatewayEndpointConfig(
internal data class GatewaySetupCode(
val url: String,
val bootstrapToken: String?,
val token: String?,
val password: String?,
)
@ -26,6 +27,7 @@ internal data class GatewayConnectConfig(
val host: String,
val port: Int,
val tls: Boolean,
val bootstrapToken: String,
val token: String,
val password: String,
)
@ -44,12 +46,26 @@ internal fun resolveGatewayConnectConfig(
if (useSetupCode) {
val setup = decodeGatewaySetupCode(setupCode) ?: return null
val parsed = parseGatewayEndpoint(setup.url) ?: return null
val setupBootstrapToken = setup.bootstrapToken?.trim().orEmpty()
val sharedToken =
when {
!setup.token.isNullOrBlank() -> setup.token.trim()
setupBootstrapToken.isNotEmpty() -> ""
else -> fallbackToken.trim()
}
val sharedPassword =
when {
!setup.password.isNullOrBlank() -> setup.password.trim()
setupBootstrapToken.isNotEmpty() -> ""
else -> fallbackPassword.trim()
}
return GatewayConnectConfig(
host = parsed.host,
port = parsed.port,
tls = parsed.tls,
token = setup.token ?: fallbackToken.trim(),
password = setup.password ?: fallbackPassword.trim(),
bootstrapToken = setupBootstrapToken,
token = sharedToken,
password = sharedPassword,
)
}
@ -59,6 +75,7 @@ internal fun resolveGatewayConnectConfig(
host = parsed.host,
port = parsed.port,
tls = parsed.tls,
bootstrapToken = "",
token = fallbackToken.trim(),
password = fallbackPassword.trim(),
)
@ -69,7 +86,7 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
if (raw.isEmpty()) return null
val normalized = if (raw.contains("://")) raw else "https://$raw"
val uri = normalized.toUri()
val uri = runCatching { URI(normalized) }.getOrNull() ?: return null
val host = uri.host?.trim().orEmpty()
if (host.isEmpty()) return null
@ -80,7 +97,7 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
"wss", "https" -> true
else -> true
}
val port = uri.port.takeIf { it in 1..65535 } ?: 18789
val port = uri.port.takeIf { it in 1..65535 } ?: if (tls) 443 else 18789
val displayUrl = "${if (tls) "https" else "http"}://$host:$port"
return GatewayEndpointConfig(host = host, port = port, tls = tls, displayUrl = displayUrl)
@ -104,9 +121,10 @@ internal fun decodeGatewaySetupCode(rawInput: String): GatewaySetupCode? {
val obj = parseJsonObject(decoded) ?: return null
val url = jsonField(obj, "url").orEmpty()
if (url.isEmpty()) return null
val bootstrapToken = jsonField(obj, "bootstrapToken")
val token = jsonField(obj, "token")
val password = jsonField(obj, "password")
GatewaySetupCode(url = url, token = token, password = password)
GatewaySetupCode(url = url, bootstrapToken = bootstrapToken, token = token, password = password)
} catch (_: IllegalArgumentException) {
null
}

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

@ -1,5 +1,7 @@
package ai.openclaw.app.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
@ -9,32 +11,147 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import ai.openclaw.app.R
internal val mobileBackgroundGradient =
Brush.verticalGradient(
listOf(
Color(0xFFFFFFFF),
Color(0xFFF7F8FA),
Color(0xFFEFF1F5),
),
// ---------------------------------------------------------------------------
// MobileColors semantic color tokens with light + dark variants
// ---------------------------------------------------------------------------
internal data class MobileColors(
val surface: Color,
val surfaceStrong: Color,
val cardSurface: Color,
val border: Color,
val borderStrong: Color,
val text: Color,
val textSecondary: Color,
val textTertiary: Color,
val accent: Color,
val accentSoft: Color,
val accentBorderStrong: Color,
val success: Color,
val successSoft: Color,
val warning: Color,
val warningSoft: Color,
val danger: Color,
val dangerSoft: Color,
val codeBg: Color,
val codeText: Color,
val codeBorder: Color,
val codeAccent: Color,
val chipBorderConnected: Color,
val chipBorderConnecting: Color,
val chipBorderWarning: Color,
val chipBorderError: Color,
)
internal fun lightMobileColors() =
MobileColors(
surface = Color(0xFFF6F7FA),
surfaceStrong = Color(0xFFECEEF3),
cardSurface = Color(0xFFFFFFFF),
border = Color(0xFFE5E7EC),
borderStrong = Color(0xFFD6DAE2),
text = Color(0xFF17181C),
textSecondary = Color(0xFF5D6472),
textTertiary = Color(0xFF99A0AE),
accent = Color(0xFF1D5DD8),
accentSoft = Color(0xFFECF3FF),
accentBorderStrong = Color(0xFF184DAF),
success = Color(0xFF2F8C5A),
successSoft = Color(0xFFEEF9F3),
warning = Color(0xFFC8841A),
warningSoft = Color(0xFFFFF8EC),
danger = Color(0xFFD04B4B),
dangerSoft = Color(0xFFFFF2F2),
codeBg = Color(0xFF15171B),
codeText = Color(0xFFE8EAEE),
codeBorder = Color(0xFF2B2E35),
codeAccent = Color(0xFF3FC97A),
chipBorderConnected = Color(0xFFCFEBD8),
chipBorderConnecting = Color(0xFFD5E2FA),
chipBorderWarning = Color(0xFFEED8B8),
chipBorderError = Color(0xFFF3C8C8),
)
internal val mobileSurface = Color(0xFFF6F7FA)
internal val mobileSurfaceStrong = Color(0xFFECEEF3)
internal val mobileBorder = Color(0xFFE5E7EC)
internal val mobileBorderStrong = Color(0xFFD6DAE2)
internal val mobileText = Color(0xFF17181C)
internal val mobileTextSecondary = Color(0xFF5D6472)
internal val mobileTextTertiary = Color(0xFF99A0AE)
internal val mobileAccent = Color(0xFF1D5DD8)
internal val mobileAccentSoft = Color(0xFFECF3FF)
internal val mobileSuccess = Color(0xFF2F8C5A)
internal val mobileSuccessSoft = Color(0xFFEEF9F3)
internal val mobileWarning = Color(0xFFC8841A)
internal val mobileWarningSoft = Color(0xFFFFF8EC)
internal val mobileDanger = Color(0xFFD04B4B)
internal val mobileDangerSoft = Color(0xFFFFF2F2)
internal val mobileCodeBg = Color(0xFF15171B)
internal val mobileCodeText = Color(0xFFE8EAEE)
internal fun darkMobileColors() =
MobileColors(
surface = Color(0xFF1A1C20),
surfaceStrong = Color(0xFF24262B),
cardSurface = Color(0xFF1E2024),
border = Color(0xFF2E3038),
borderStrong = Color(0xFF3A3D46),
text = Color(0xFFE4E5EA),
textSecondary = Color(0xFFA0A6B4),
textTertiary = Color(0xFF6B7280),
accent = Color(0xFF6EA8FF),
accentSoft = Color(0xFF1A2A44),
accentBorderStrong = Color(0xFF5B93E8),
success = Color(0xFF5FBB85),
successSoft = Color(0xFF152E22),
warning = Color(0xFFE8A844),
warningSoft = Color(0xFF2E2212),
danger = Color(0xFFE87070),
dangerSoft = Color(0xFF2E1616),
codeBg = Color(0xFF111317),
codeText = Color(0xFFE8EAEE),
codeBorder = Color(0xFF2B2E35),
codeAccent = Color(0xFF3FC97A),
chipBorderConnected = Color(0xFF1E4A30),
chipBorderConnecting = Color(0xFF1E3358),
chipBorderWarning = Color(0xFF3E3018),
chipBorderError = Color(0xFF3E1E1E),
)
internal val LocalMobileColors = staticCompositionLocalOf { lightMobileColors() }
internal object MobileColorsAccessor {
val current: MobileColors
@Composable get() = LocalMobileColors.current
}
// ---------------------------------------------------------------------------
// Backward-compatible top-level accessors (composable getters)
// ---------------------------------------------------------------------------
// These allow existing call sites to keep using `mobileSurface`, `mobileText`, etc.
// without converting every file at once. Each resolves to the themed value.
internal val mobileSurface: Color @Composable get() = LocalMobileColors.current.surface
internal val mobileSurfaceStrong: Color @Composable get() = LocalMobileColors.current.surfaceStrong
internal val mobileCardSurface: Color @Composable get() = LocalMobileColors.current.cardSurface
internal val mobileBorder: Color @Composable get() = LocalMobileColors.current.border
internal val mobileBorderStrong: Color @Composable get() = LocalMobileColors.current.borderStrong
internal val mobileText: Color @Composable get() = LocalMobileColors.current.text
internal val mobileTextSecondary: Color @Composable get() = LocalMobileColors.current.textSecondary
internal val mobileTextTertiary: Color @Composable get() = LocalMobileColors.current.textTertiary
internal val mobileAccent: Color @Composable get() = LocalMobileColors.current.accent
internal val mobileAccentSoft: Color @Composable get() = LocalMobileColors.current.accentSoft
internal val mobileAccentBorderStrong: Color @Composable get() = LocalMobileColors.current.accentBorderStrong
internal val mobileSuccess: Color @Composable get() = LocalMobileColors.current.success
internal val mobileSuccessSoft: Color @Composable get() = LocalMobileColors.current.successSoft
internal val mobileWarning: Color @Composable get() = LocalMobileColors.current.warning
internal val mobileWarningSoft: Color @Composable get() = LocalMobileColors.current.warningSoft
internal val mobileDanger: Color @Composable get() = LocalMobileColors.current.danger
internal val mobileDangerSoft: Color @Composable get() = LocalMobileColors.current.dangerSoft
internal val mobileCodeBg: Color @Composable get() = LocalMobileColors.current.codeBg
internal val mobileCodeText: Color @Composable get() = LocalMobileColors.current.codeText
internal val mobileCodeBorder: Color @Composable get() = LocalMobileColors.current.codeBorder
internal val mobileCodeAccent: Color @Composable get() = LocalMobileColors.current.codeAccent
// Background gradient light fades white→gray, dark fades near-black→dark-gray
internal val mobileBackgroundGradient: Brush
@Composable get() {
val colors = LocalMobileColors.current
return Brush.verticalGradient(
listOf(
colors.surface,
colors.surfaceStrong,
colors.surfaceStrong,
),
)
}
// ---------------------------------------------------------------------------
// Typography tokens (theme-independent)
// ---------------------------------------------------------------------------
internal val mobileFontFamily =
FontFamily(
@ -44,6 +161,15 @@ internal val mobileFontFamily =
Font(resId = R.font.manrope_700_bold, weight = FontWeight.Bold),
)
internal val mobileDisplay =
TextStyle(
fontFamily = mobileFontFamily,
fontWeight = FontWeight.Bold,
fontSize = 34.sp,
lineHeight = 40.sp,
letterSpacing = (-0.8).sp,
)
internal val mobileTitle1 =
TextStyle(
fontFamily = mobileFontFamily,

View File

@ -5,6 +5,7 @@ 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.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
@ -13,8 +14,11 @@ fun OpenClawTheme(content: @Composable () -> Unit) {
val context = LocalContext.current
val isDark = isSystemInDarkTheme()
val colorScheme = if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
val mobileColors = if (isDark) darkMobileColors() else lightMobileColors()
MaterialTheme(colorScheme = colorScheme, content = content)
CompositionLocalProvider(LocalMobileColors provides mobileColors) {
MaterialTheme(colorScheme = colorScheme, content = content)
}
}
@Composable

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,46 +167,20 @@ 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()
val isNodeConnected by viewModel.isNodeConnected.collectAsState()
val canvasUrl by viewModel.canvasCurrentUrl.collectAsState()
val canvasA2uiHydrated by viewModel.canvasA2uiHydrated.collectAsState()
val canvasRehydratePending by viewModel.canvasRehydratePending.collectAsState()
val canvasRehydrateErrorText by viewModel.canvasRehydrateErrorText.collectAsState()
val isA2uiUrl = canvasUrl?.contains("/__openclaw__/a2ui/") == true
val showRestoreCta = isConnected && isNodeConnected && (canvasUrl.isNullOrBlank() || (isA2uiUrl && !canvasA2uiHydrated))
val restoreCtaText =
when {
canvasRehydratePending -> "Restore requested. Waiting for agent…"
!canvasRehydrateErrorText.isNullOrBlank() -> canvasRehydrateErrorText!!
else -> "Canvas reset. Tap to restore dashboard."
}
var refreshedForCurrentConnection by rememberSaveable(isConnected) { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxSize()) {
CanvasScreen(viewModel = viewModel, modifier = Modifier.fillMaxSize())
if (showRestoreCta) {
Surface(
onClick = {
if (canvasRehydratePending) return@Surface
viewModel.requestCanvasRehydrate(source = "screen_tab_cta")
},
modifier = Modifier.align(Alignment.TopCenter).padding(horizontal = 16.dp, vertical = 16.dp),
shape = RoundedCornerShape(12.dp),
color = mobileSurface.copy(alpha = 0.9f),
border = BorderStroke(1.dp, mobileBorder),
shadowElevation = 4.dp,
) {
Text(
text = restoreCtaText,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
style = mobileCallout.copy(fontWeight = FontWeight.Medium),
color = mobileText,
)
}
LaunchedEffect(isConnected, visible, refreshedForCurrentConnection) {
if (visible && isConnected && !refreshedForCurrentConnection) {
viewModel.refreshHomeCanvasOverviewIfConnected()
refreshedForCurrentConnection = true
}
}
Box(modifier = modifier.fillMaxSize()) {
CanvasScreen(viewModel = viewModel, visible = visible, modifier = Modifier.fillMaxSize())
}
}
@Composable
@ -188,28 +197,28 @@ private fun TopStatusBar(
mobileSuccessSoft,
mobileSuccess,
mobileSuccess,
Color(0xFFCFEBD8),
LocalMobileColors.current.chipBorderConnected,
)
StatusVisual.Connecting ->
listOf(
mobileAccentSoft,
mobileAccent,
mobileAccent,
Color(0xFFD5E2FA),
LocalMobileColors.current.chipBorderConnecting,
)
StatusVisual.Warning ->
listOf(
mobileWarningSoft,
mobileWarning,
mobileWarning,
Color(0xFFEED8B8),
LocalMobileColors.current.chipBorderWarning,
)
StatusVisual.Error ->
listOf(
mobileDangerSoft,
mobileDanger,
mobileDanger,
Color(0xFFF3C8C8),
LocalMobileColors.current.chipBorderError,
)
StatusVisual.Offline ->
listOf(
@ -278,7 +287,7 @@ private fun BottomTabBar(
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = Color.White.copy(alpha = 0.97f),
color = mobileCardSurface.copy(alpha = 0.97f),
shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
border = BorderStroke(1.dp, mobileBorder),
shadowElevation = 6.dp,
@ -299,7 +308,7 @@ private fun BottomTabBar(
modifier = Modifier.weight(1f).heightIn(min = 58.dp),
shape = RoundedCornerShape(16.dp),
color = if (active) mobileAccentSoft else Color.Transparent,
border = if (active) BorderStroke(1.dp, Color(0xFFD5E2FA)) else null,
border = if (active) BorderStroke(1.dp, LocalMobileColors.current.chipBorderConnecting) else null,
shadowElevation = 0.dp,
) {
Column(

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
@ -218,6 +220,18 @@ fun SettingsSheet(viewModel: MainViewModel) {
calendarPermissionGranted = readOk && writeOk
}
var callLogPermissionGranted by
remember {
mutableStateOf(
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) ==
PackageManager.PERMISSION_GRANTED,
)
}
val callLogPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
callLogPermissionGranted = granted
}
var motionPermissionGranted by
remember {
mutableStateOf(
@ -235,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()
}
@ -266,12 +284,17 @@ fun SettingsSheet(viewModel: MainViewModel) {
PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR) ==
PackageManager.PERMISSION_GRANTED
callLogPermissionGranted =
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALL_LOG) ==
PackageManager.PERMISSION_GRANTED
motionPermissionGranted =
!motionPermissionRequired ||
ContextCompat.checkSelfPermission(context, Manifest.permission.ACTIVITY_RECOGNITION) ==
PackageManager.PERMISSION_GRANTED
smsPermissionGranted =
ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) ==
PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) ==
PackageManager.PERMISSION_GRANTED
}
}
@ -345,179 +368,90 @@ fun SettingsSheet(viewModel: MainViewModel) {
contentPadding = PaddingValues(horizontal = 20.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
item {
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text(
"SETTINGS",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
Text("Device Configuration", style = mobileTitle2, color = mobileText)
Text(
"Manage capabilities, permissions, and diagnostics.",
style = mobileCallout,
color = mobileTextSecondary,
)
}
}
item { HorizontalDivider(color = mobileBorder) }
// Order parity: Node → Voice → Camera → Messaging → Location → Screen.
// ── Node ──
item {
Text(
"NODE",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
OutlinedTextField(
value = displayName,
onValueChange = viewModel::setDisplayName,
label = { Text("Name", style = mobileCaption1, color = mobileTextSecondary) },
modifier = Modifier.fillMaxWidth(),
textStyle = mobileBody.copy(color = mobileText),
colors = settingsTextFieldColors(),
)
}
item { Text("Instance ID: $instanceId", style = mobileCallout.copy(fontFamily = FontFamily.Monospace), color = mobileTextSecondary) }
item { Text("Device: $deviceModel", style = mobileCallout, color = mobileTextSecondary) }
item { Text("Version: $appVersion", style = mobileCallout, color = mobileTextSecondary) }
item { HorizontalDivider(color = mobileBorder) }
// Voice
item {
Text(
"VOICE",
"DEVICE",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Microphone permission", style = mobileHeadline) },
supportingContent = {
Column(modifier = Modifier.settingsRowModifier()) {
OutlinedTextField(
value = displayName,
onValueChange = viewModel::setDisplayName,
label = { Text("Name", style = mobileCaption1, color = mobileTextSecondary) },
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 10.dp),
textStyle = mobileBody.copy(color = mobileText),
colors = settingsTextFieldColors(),
)
HorizontalDivider(color = mobileBorder)
Column(
modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text("$deviceModel · $appVersion", style = mobileCallout, color = mobileTextSecondary)
Text(
if (micPermissionGranted) {
"Granted. Use the Voice tab mic button to capture transcript while the app is open."
} else {
"Required for foreground Voice tab transcription."
},
style = mobileCallout,
instanceId.take(8) + "",
style = mobileCaption1.copy(fontFamily = FontFamily.Monospace),
color = mobileTextTertiary,
)
},
trailingContent = {
Button(
onClick = {
if (micPermissionGranted) {
openAppSettings(context)
} else {
audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (micPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
}
item {
Text(
"Voice wake and talk modes were removed. Voice now uses one mic on/off flow in the Voice tab while the app is open.",
style = mobileCallout,
color = mobileTextSecondary,
)
}
item { HorizontalDivider(color = mobileBorder) }
// Camera
item {
Text(
"CAMERA",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Allow Camera", style = mobileHeadline) },
supportingContent = { Text("Allows the gateway to request photos or short video clips (foreground only).", style = mobileCallout) },
trailingContent = { Switch(checked = cameraEnabled, onCheckedChange = ::setCameraEnabledChecked) },
)
}
item {
Text(
"Tip: grant Microphone permission for video clips with audio.",
style = mobileCallout,
color = mobileTextSecondary,
)
}
item { HorizontalDivider(color = mobileBorder) }
// Messaging
item {
Text(
"MESSAGING",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
val buttonLabel =
when {
!smsPermissionAvailable -> "Unavailable"
smsPermissionGranted -> "Manage"
else -> "Grant"
}
}
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("SMS Permission", style = mobileHeadline) },
supportingContent = {
Text(
if (smsPermissionAvailable) {
"Allow the gateway to send SMS from this device."
} else {
"SMS requires a device with telephony hardware."
}
// ── Media ──
item {
Text(
"MEDIA",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
Column(modifier = Modifier.settingsRowModifier()) {
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Microphone", style = mobileHeadline) },
supportingContent = {
Text(
if (micPermissionGranted) "Granted" else "Required for voice transcription.",
style = mobileCallout,
)
},
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = {
if (!smsPermissionAvailable) return@Button
if (smsPermissionGranted) {
openAppSettings(context)
} else {
smsPermissionLauncher.launch(Manifest.permission.SEND_SMS)
trailingContent = {
Button(
onClick = {
if (micPermissionGranted) {
openAppSettings(context)
} else {
audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (micPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
enabled = smsPermissionAvailable,
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(buttonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold))
}
},
)
}
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Camera", style = mobileHeadline) },
supportingContent = { Text("Photos and video clips (foreground only).", style = mobileCallout) },
trailingContent = { Switch(checked = cameraEnabled, onCheckedChange = ::setCameraEnabledChecked) },
)
}
}
item { HorizontalDivider(color = mobileBorder) }
// Notifications
// ── Notifications & Messaging ──
item {
Text(
"NOTIFICATIONS",
@ -526,67 +460,87 @@ fun SettingsSheet(viewModel: MainViewModel) {
)
}
item {
val buttonLabel =
if (notificationsPermissionGranted) {
"Manage"
} else {
"Grant"
}
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("System Notifications", style = mobileHeadline) },
supportingContent = {
Text(
"Required for `system.notify` and Android foreground service alerts.",
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = {
if (notificationsPermissionGranted || Build.VERSION.SDK_INT < 33) {
openAppSettings(context)
} else {
notificationsPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
Column(modifier = Modifier.settingsRowModifier()) {
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("System Notifications", style = mobileHeadline) },
supportingContent = {
Text("Alerts and foreground service.", style = mobileCallout)
},
trailingContent = {
Button(
onClick = {
if (notificationsPermissionGranted || Build.VERSION.SDK_INT < 33) {
openAppSettings(context)
} else {
notificationsPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (notificationsPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Notification Listener", style = mobileHeadline) },
supportingContent = {
Text("Read and interact with notifications.", style = mobileCallout)
},
trailingContent = {
Button(
onClick = { openNotificationListenerSettings(context) },
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (notificationListenerEnabled) "Manage" else "Enable",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
if (smsPermissionAvailable) {
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("SMS", style = mobileHeadline) },
supportingContent = {
Text("Send and search SMS from this device.", style = mobileCallout)
},
trailingContent = {
Button(
onClick = {
if (smsPermissionGranted) {
openAppSettings(context)
} else {
smsPermissionLauncher.launch(arrayOf(Manifest.permission.SEND_SMS, Manifest.permission.READ_SMS))
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (smsPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(buttonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold))
}
},
)
}
item {
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Notification Listener Access", style = mobileHeadline) },
supportingContent = {
Text(
"Required for `notifications.list` and `notifications.actions`.",
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = { openNotificationListenerSettings(context) },
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (notificationListenerEnabled) "Manage" else "Enable",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
}
}
}
item { HorizontalDivider(color = mobileBorder) }
// Data access
// ── Data Access ──
item {
Text(
"DATA ACCESS",
@ -595,142 +549,142 @@ fun SettingsSheet(viewModel: MainViewModel) {
)
}
item {
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Photos Permission", style = mobileHeadline) },
supportingContent = {
Text(
"Required for `photos.latest`.",
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = {
if (photosPermissionGranted) {
openAppSettings(context)
} else {
photosPermissionLauncher.launch(photosPermission)
Column(modifier = Modifier.settingsRowModifier()) {
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Photos", style = mobileHeadline) },
supportingContent = { Text("Access recent photos.", style = mobileCallout) },
trailingContent = {
Button(
onClick = {
if (photosPermissionGranted) {
openAppSettings(context)
} else {
photosPermissionLauncher.launch(photosPermission)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (photosPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Contacts", style = mobileHeadline) },
supportingContent = { Text("Search and add contacts.", style = mobileCallout) },
trailingContent = {
Button(
onClick = {
if (contactsPermissionGranted) {
openAppSettings(context)
} else {
contactsPermissionLauncher.launch(arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS))
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (contactsPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Calendar", style = mobileHeadline) },
supportingContent = { Text("Read and create events.", style = mobileCallout) },
trailingContent = {
Button(
onClick = {
if (calendarPermissionGranted) {
openAppSettings(context)
} else {
calendarPermissionLauncher.launch(arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR))
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (calendarPermissionGranted) "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),
)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (photosPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
}
item {
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Contacts Permission", style = mobileHeadline) },
supportingContent = {
Text(
"Required for `contacts.search` and `contacts.add`.",
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = {
if (contactsPermissionGranted) {
openAppSettings(context)
} else {
contactsPermissionLauncher.launch(arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS))
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (contactsPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
}
item {
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Calendar Permission", style = mobileHeadline) },
supportingContent = {
Text(
"Required for `calendar.events` and `calendar.add`.",
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = {
if (calendarPermissionGranted) {
openAppSettings(context)
} else {
calendarPermissionLauncher.launch(arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR))
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(
if (calendarPermissionGranted) "Manage" else "Grant",
style = mobileCallout.copy(fontWeight = FontWeight.Bold),
)
}
},
)
}
item {
val motionButtonLabel =
when {
!motionAvailable -> "Unavailable"
!motionPermissionRequired -> "Manage"
motionPermissionGranted -> "Manage"
else -> "Grant"
}
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Motion Permission", style = mobileHeadline) },
supportingContent = {
Text(
if (!motionAvailable) {
"This device does not expose accelerometer or step-counter motion sensors."
} else {
"Required for `motion.activity` and `motion.pedometer`."
},
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = {
if (!motionAvailable) return@Button
if (!motionPermissionRequired || motionPermissionGranted) {
openAppSettings(context)
} else {
motionPermissionLauncher.launch(Manifest.permission.ACTIVITY_RECOGNITION)
if (motionAvailable) {
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Motion", style = mobileHeadline) },
supportingContent = { Text("Track steps and activity.", style = mobileCallout) },
trailingContent = {
val motionButtonLabel =
when {
!motionPermissionRequired -> "Manage"
motionPermissionGranted -> "Manage"
else -> "Grant"
}
Button(
onClick = {
if (!motionPermissionRequired || motionPermissionGranted) {
openAppSettings(context)
} else {
motionPermissionLauncher.launch(Manifest.permission.ACTIVITY_RECOGNITION)
}
},
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(motionButtonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold))
}
},
enabled = motionAvailable,
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(motionButtonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold))
}
},
)
)
}
}
}
item { HorizontalDivider(color = mobileBorder) }
// Location
// ── Location ──
item {
Text(
"LOCATION",
@ -739,7 +693,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
)
}
item {
Column(modifier = Modifier.settingsRowModifier(), verticalArrangement = Arrangement.spacedBy(0.dp)) {
Column(modifier = Modifier.settingsRowModifier()) {
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
@ -781,50 +735,39 @@ fun SettingsSheet(viewModel: MainViewModel) {
)
}
}
item { HorizontalDivider(color = mobileBorder) }
// Screen
// ── Preferences ──
item {
Text(
"SCREEN",
"PREFERENCES",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Prevent Sleep", style = mobileHeadline) },
supportingContent = { Text("Keeps the screen awake while OpenClaw is open.", style = mobileCallout) },
trailingContent = { Switch(checked = preventSleep, onCheckedChange = viewModel::setPreventSleep) },
)
}
item { HorizontalDivider(color = mobileBorder) }
// Debug
item {
Text(
"DEBUG",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
ListItem(
modifier = Modifier.settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Debug Canvas Status", style = mobileHeadline) },
supportingContent = { Text("Show status text in the canvas when debug is enabled.", style = mobileCallout) },
trailingContent = {
Switch(
checked = canvasDebugStatusEnabled,
onCheckedChange = viewModel::setCanvasDebugStatusEnabled,
Column(modifier = Modifier.settingsRowModifier()) {
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Prevent Sleep", style = mobileHeadline) },
supportingContent = { Text("Keep screen awake while open.", style = mobileCallout) },
trailingContent = { Switch(checked = preventSleep, onCheckedChange = viewModel::setPreventSleep) },
)
},
)
}
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Debug Canvas", style = mobileHeadline) },
supportingContent = { Text("Show status overlay on canvas.", style = mobileCallout) },
trailingContent = {
Switch(
checked = canvasDebugStatusEnabled,
onCheckedChange = viewModel::setCanvasDebugStatusEnabled,
)
},
)
}
}
item { Spacer(modifier = Modifier.height(24.dp)) }
}
@ -843,11 +786,12 @@ private fun settingsTextFieldColors() =
cursorColor = mobileAccent,
)
@Composable
private fun Modifier.settingsRowModifier() =
this
.fillMaxWidth()
.border(width = 1.dp, color = mobileBorder, shape = RoundedCornerShape(14.dp))
.background(Color.White, RoundedCornerShape(14.dp))
.background(mobileCardSurface, RoundedCornerShape(14.dp))
@Composable
private fun settingsPrimaryButtonColors() =
@ -888,7 +832,7 @@ private fun openNotificationListenerSettings(context: Context) {
private fun hasNotificationsPermission(context: Context): Boolean {
if (Build.VERSION.SDK_INT < 33) return true
return ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED
PackageManager.PERMISSION_GRANTED
}
private fun isNotificationListenerEnabled(context: Context): Boolean {
@ -898,5 +842,5 @@ private fun isNotificationListenerEnabled(context: Context): Boolean {
private fun hasMotionCapabilities(context: Context): Boolean {
val sensorManager = context.getSystemService(SensorManager::class.java) ?: return false
return sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null ||
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) != null
sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) != null
}

View File

@ -17,10 +17,12 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
@ -212,19 +214,26 @@ fun VoiceTabScreen(viewModel: MainViewModel) {
verticalAlignment = Alignment.CenterVertically,
) {
// Speaker toggle
IconButton(
onClick = { viewModel.setSpeakerEnabled(!speakerEnabled) },
modifier = Modifier.size(48.dp),
colors =
IconButtonDefaults.iconButtonColors(
containerColor = if (speakerEnabled) mobileSurface else mobileDangerSoft,
),
) {
Icon(
imageVector = if (speakerEnabled) Icons.AutoMirrored.Filled.VolumeUp else Icons.AutoMirrored.Filled.VolumeOff,
contentDescription = if (speakerEnabled) "Mute speaker" else "Unmute speaker",
modifier = Modifier.size(22.dp),
tint = if (speakerEnabled) mobileTextSecondary else mobileDanger,
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp)) {
IconButton(
onClick = { viewModel.setSpeakerEnabled(!speakerEnabled) },
modifier = Modifier.size(48.dp),
colors =
IconButtonDefaults.iconButtonColors(
containerColor = if (speakerEnabled) mobileSurface else mobileDangerSoft,
),
) {
Icon(
imageVector = if (speakerEnabled) Icons.AutoMirrored.Filled.VolumeUp else Icons.AutoMirrored.Filled.VolumeOff,
contentDescription = if (speakerEnabled) "Mute speaker" else "Unmute speaker",
modifier = Modifier.size(22.dp),
tint = if (speakerEnabled) mobileTextSecondary else mobileDanger,
)
}
Text(
if (speakerEnabled) "Speaker" else "Muted",
style = mobileCaption2,
color = if (speakerEnabled) mobileTextTertiary else mobileDanger,
)
}
@ -278,8 +287,12 @@ fun VoiceTabScreen(viewModel: MainViewModel) {
}
}
// Invisible spacer to balance the row (same size as speaker button)
Box(modifier = Modifier.size(48.dp))
// Invisible spacer to balance the row (matches speaker column width)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.size(48.dp))
Spacer(modifier = Modifier.height(4.dp))
Text("", style = mobileCaption2)
}
}
// Status + labels
@ -292,11 +305,24 @@ fun VoiceTabScreen(viewModel: MainViewModel) {
micEnabled -> "Listening"
else -> "Mic off"
}
Text(
"$gatewayStatus · $stateText",
style = mobileCaption1,
color = mobileTextSecondary,
)
val stateColor =
when {
micEnabled -> mobileSuccess
micIsSending -> mobileAccent
else -> mobileTextSecondary
}
Surface(
shape = RoundedCornerShape(999.dp),
color = if (micEnabled) mobileSuccessSoft else mobileSurface,
border = BorderStroke(1.dp, if (micEnabled) mobileSuccess.copy(alpha = 0.3f) else mobileBorder),
) {
Text(
"$gatewayStatus · $stateText",
style = mobileCallout.copy(fontWeight = FontWeight.SemiBold),
color = stateColor,
modifier = Modifier.padding(horizontal = 14.dp, vertical = 6.dp),
)
}
if (!hasMicPermission) {
val showRationale =
@ -337,7 +363,7 @@ private fun VoiceTurnBubble(entry: VoiceConversationEntry) {
Surface(
modifier = Modifier.fillMaxWidth(0.90f),
shape = RoundedCornerShape(12.dp),
color = if (isUser) mobileAccentSoft else Color.White,
color = if (isUser) mobileAccentSoft else mobileCardSurface,
border = BorderStroke(1.dp, if (isUser) mobileAccent else mobileBorderStrong),
) {
Column(
@ -365,7 +391,7 @@ private fun VoiceThinkingBubble() {
Surface(
modifier = Modifier.fillMaxWidth(0.68f),
shape = RoundedCornerShape(12.dp),
color = Color.White,
color = mobileCardSurface,
border = BorderStroke(1.dp, mobileBorderStrong),
) {
Row(

View File

@ -1,7 +1,5 @@
package ai.openclaw.app.ui.chat
import android.graphics.BitmapFactory
import android.util.Base64
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -28,8 +26,7 @@ internal fun rememberBase64ImageState(base64: String): Base64ImageState {
image =
withContext(Dispatchers.Default) {
try {
val bytes = Base64.decode(base64, Base64.DEFAULT)
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return@withContext null
val bitmap = decodeBase64Bitmap(base64) ?: return@withContext null
bitmap.asImageBitmap()
} catch (_: Throwable) {
null

View File

@ -26,7 +26,6 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
@ -47,11 +46,13 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ai.openclaw.app.ui.mobileAccent
import ai.openclaw.app.ui.mobileAccentBorderStrong
import ai.openclaw.app.ui.mobileAccentSoft
import ai.openclaw.app.ui.mobileBorder
import ai.openclaw.app.ui.mobileBorderStrong
import ai.openclaw.app.ui.mobileCallout
import ai.openclaw.app.ui.mobileCaption1
import ai.openclaw.app.ui.mobileCardSurface
import ai.openclaw.app.ui.mobileHeadline
import ai.openclaw.app.ui.mobileSurface
import ai.openclaw.app.ui.mobileText
@ -78,65 +79,15 @@ fun ChatComposer(
val sendBusy = pendingRunCount > 0
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Box(modifier = Modifier.weight(1f)) {
Surface(
onClick = { showThinkingMenu = true },
shape = RoundedCornerShape(14.dp),
color = mobileAccentSoft,
border = BorderStroke(1.dp, mobileBorderStrong),
) {
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = "Thinking: ${thinkingLabel(thinkingLevel)}",
style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold),
color = mobileText,
)
Icon(Icons.Default.ArrowDropDown, contentDescription = "Select thinking level", tint = mobileTextSecondary)
}
}
DropdownMenu(expanded = showThinkingMenu, onDismissRequest = { showThinkingMenu = false }) {
ThinkingMenuItem("off", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false }
ThinkingMenuItem("low", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false }
ThinkingMenuItem("medium", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false }
ThinkingMenuItem("high", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false }
}
}
SecondaryActionButton(
label = "Attach",
icon = Icons.Default.AttachFile,
enabled = true,
onClick = onPickImages,
)
}
if (attachments.isNotEmpty()) {
AttachmentsStrip(attachments = attachments, onRemoveAttachment = onRemoveAttachment)
}
HorizontalDivider(color = mobileBorder)
Text(
text = "MESSAGE",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 0.9.sp),
color = mobileTextSecondary,
)
OutlinedTextField(
value = input,
onValueChange = { input = it },
modifier = Modifier.fillMaxWidth().height(92.dp),
placeholder = { Text("Type a message", style = mobileBodyStyle(), color = mobileTextTertiary) },
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("Type a message…", style = mobileBodyStyle(), color = mobileTextTertiary) },
minLines = 2,
maxLines = 5,
textStyle = mobileBodyStyle().copy(color = mobileText),
@ -155,26 +106,70 @@ fun ChatComposer(
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
SecondaryActionButton(
label = "Refresh",
icon = Icons.Default.Refresh,
enabled = true,
compact = true,
onClick = onRefresh,
)
Box {
Surface(
onClick = { showThinkingMenu = true },
shape = RoundedCornerShape(14.dp),
color = mobileCardSurface,
border = BorderStroke(1.dp, mobileBorderStrong),
) {
Row(
modifier = Modifier.padding(horizontal = 10.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = thinkingLabel(thinkingLevel),
style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold),
color = mobileTextSecondary,
)
Icon(Icons.Default.ArrowDropDown, contentDescription = "Select thinking level", modifier = Modifier.size(18.dp), tint = mobileTextTertiary)
}
}
SecondaryActionButton(
label = "Abort",
icon = Icons.Default.Stop,
enabled = pendingRunCount > 0,
compact = true,
onClick = onAbort,
)
DropdownMenu(
expanded = showThinkingMenu,
onDismissRequest = { showThinkingMenu = false },
shape = RoundedCornerShape(16.dp),
containerColor = mobileCardSurface,
tonalElevation = 0.dp,
shadowElevation = 8.dp,
border = BorderStroke(1.dp, mobileBorder),
) {
ThinkingMenuItem("off", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false }
ThinkingMenuItem("low", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false }
ThinkingMenuItem("medium", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false }
ThinkingMenuItem("high", thinkingLevel, onSetThinkingLevel) { showThinkingMenu = false }
}
}
SecondaryActionButton(
label = "Attach",
icon = Icons.Default.AttachFile,
enabled = true,
compact = true,
onClick = onPickImages,
)
SecondaryActionButton(
label = "Refresh",
icon = Icons.Default.Refresh,
enabled = true,
compact = true,
onClick = onRefresh,
)
SecondaryActionButton(
label = "Abort",
icon = Icons.Default.Stop,
enabled = pendingRunCount > 0,
compact = true,
onClick = onAbort,
)
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = {
val text = input
@ -182,8 +177,9 @@ fun ChatComposer(
onSend(text)
},
enabled = canSend,
modifier = Modifier.weight(1f).height(48.dp),
modifier = Modifier.height(44.dp),
shape = RoundedCornerShape(14.dp),
contentPadding = PaddingValues(horizontal = 20.dp),
colors =
ButtonDefaults.buttonColors(
containerColor = mobileAccent,
@ -191,14 +187,14 @@ fun ChatComposer(
disabledContainerColor = mobileBorderStrong,
disabledContentColor = mobileTextTertiary,
),
border = BorderStroke(1.dp, if (canSend) Color(0xFF154CAD) else mobileBorderStrong),
border = BorderStroke(1.dp, if (canSend) mobileAccentBorderStrong else mobileBorderStrong),
) {
if (sendBusy) {
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = Color.White)
} else {
Icon(Icons.AutoMirrored.Filled.Send, contentDescription = null, modifier = Modifier.size(16.dp))
}
Spacer(modifier = Modifier.width(8.dp))
Spacer(modifier = Modifier.width(6.dp))
Text(
text = "Send",
style = mobileHeadline.copy(fontWeight = FontWeight.Bold),
@ -225,9 +221,9 @@ private fun SecondaryActionButton(
shape = RoundedCornerShape(14.dp),
colors =
ButtonDefaults.buttonColors(
containerColor = Color.White,
containerColor = mobileCardSurface,
contentColor = mobileTextSecondary,
disabledContainerColor = Color.White,
disabledContainerColor = mobileCardSurface,
disabledContentColor = mobileTextTertiary,
),
border = BorderStroke(1.dp, mobileBorderStrong),
@ -317,7 +313,7 @@ private fun AttachmentChip(fileName: String, onRemove: () -> Unit) {
Surface(
onClick = onRemove,
shape = RoundedCornerShape(999.dp),
color = Color.White,
color = mobileCardSurface,
border = BorderStroke(1.dp, mobileBorderStrong),
) {
Text(

View File

@ -0,0 +1,150 @@
package ai.openclaw.app.ui.chat
import android.content.ContentResolver
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Base64
import android.util.LruCache
import androidx.core.graphics.scale
import ai.openclaw.app.node.JpegSizeLimiter
import java.io.ByteArrayOutputStream
import kotlin.math.max
import kotlin.math.roundToInt
private const val CHAT_ATTACHMENT_MAX_WIDTH = 1600
private const val CHAT_ATTACHMENT_MAX_BASE64_CHARS = 300 * 1024
private const val CHAT_ATTACHMENT_START_QUALITY = 85
private const val CHAT_DECODE_MAX_DIMENSION = 1600
private const val CHAT_IMAGE_CACHE_BYTES = 16 * 1024 * 1024
private val decodedBitmapCache =
object : LruCache<String, Bitmap>(CHAT_IMAGE_CACHE_BYTES) {
override fun sizeOf(key: String, value: Bitmap): Int = value.byteCount.coerceAtLeast(1)
}
internal fun loadSizedImageAttachment(resolver: ContentResolver, uri: Uri): PendingImageAttachment {
val fileName = normalizeAttachmentFileName((uri.lastPathSegment ?: "image").substringAfterLast('/'))
val bitmap = decodeScaledBitmap(resolver, uri, maxDimension = CHAT_ATTACHMENT_MAX_WIDTH)
if (bitmap == null) {
throw IllegalStateException("unsupported attachment")
}
val maxBytes = (CHAT_ATTACHMENT_MAX_BASE64_CHARS / 4) * 3
val encoded =
JpegSizeLimiter.compressToLimit(
initialWidth = bitmap.width,
initialHeight = bitmap.height,
startQuality = CHAT_ATTACHMENT_START_QUALITY,
maxBytes = maxBytes,
minSize = 240,
encode = { width, height, quality ->
val working =
if (width == bitmap.width && height == bitmap.height) {
bitmap
} else {
bitmap.scale(width, height, true)
}
try {
val out = ByteArrayOutputStream()
if (!working.compress(Bitmap.CompressFormat.JPEG, quality, out)) {
throw IllegalStateException("attachment encode failed")
}
out.toByteArray()
} finally {
if (working !== bitmap) {
working.recycle()
}
}
},
)
val base64 = Base64.encodeToString(encoded.bytes, Base64.NO_WRAP)
return PendingImageAttachment(
id = uri.toString() + "#" + System.currentTimeMillis().toString(),
fileName = fileName,
mimeType = "image/jpeg",
base64 = base64,
)
}
internal fun decodeBase64Bitmap(base64: String, maxDimension: Int = CHAT_DECODE_MAX_DIMENSION): Bitmap? {
val cacheKey = "$maxDimension:${base64.length}:${base64.hashCode()}"
decodedBitmapCache.get(cacheKey)?.let { return it }
val bytes = Base64.decode(base64, Base64.DEFAULT)
if (bytes.isEmpty()) return null
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bounds)
if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null
val bitmap =
BitmapFactory.decodeByteArray(
bytes,
0,
bytes.size,
BitmapFactory.Options().apply {
inSampleSize = computeInSampleSize(bounds.outWidth, bounds.outHeight, maxDimension)
inPreferredConfig = Bitmap.Config.RGB_565
},
) ?: return null
decodedBitmapCache.put(cacheKey, bitmap)
return bitmap
}
internal fun computeInSampleSize(width: Int, height: Int, maxDimension: Int): Int {
if (width <= 0 || height <= 0 || maxDimension <= 0) return 1
var sample = 1
var longestEdge = max(width, height)
while (longestEdge > maxDimension && sample < 64) {
sample *= 2
longestEdge = max(width / sample, height / sample)
}
return sample.coerceAtLeast(1)
}
internal fun normalizeAttachmentFileName(raw: String): String {
val trimmed = raw.trim()
if (trimmed.isEmpty()) return "image.jpg"
val stem = trimmed.substringBeforeLast('.', missingDelimiterValue = trimmed).ifEmpty { "image" }
return "$stem.jpg"
}
private fun decodeScaledBitmap(
resolver: ContentResolver,
uri: Uri,
maxDimension: Int,
): Bitmap? {
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
resolver.openInputStream(uri).use { input ->
if (input == null) return null
BitmapFactory.decodeStream(input, null, bounds)
}
if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null
val decoded =
resolver.openInputStream(uri).use { input ->
if (input == null) return null
BitmapFactory.decodeStream(
input,
null,
BitmapFactory.Options().apply {
inSampleSize = computeInSampleSize(bounds.outWidth, bounds.outHeight, maxDimension)
inPreferredConfig = Bitmap.Config.ARGB_8888
},
)
} ?: return null
val longestEdge = max(decoded.width, decoded.height)
if (longestEdge <= maxDimension) return decoded
val scale = maxDimension.toDouble() / longestEdge.toDouble()
val targetWidth = max(1, (decoded.width * scale).roundToInt())
val targetHeight = max(1, (decoded.height * scale).roundToInt())
val scaled = decoded.scale(targetWidth, targetHeight, true)
if (scaled !== decoded) {
decoded.recycle()
}
return scaled
}

View File

@ -94,7 +94,7 @@ private val markdownParser: Parser by lazy {
@Composable
fun ChatMarkdown(text: String, textColor: Color) {
val document = remember(text) { markdownParser.parse(text) as Document }
val inlineStyles = InlineStyles(inlineCodeBg = mobileCodeBg, inlineCodeColor = mobileCodeText)
val inlineStyles = InlineStyles(inlineCodeBg = mobileCodeBg, inlineCodeColor = mobileCodeText, linkColor = mobileAccent, baseCallout = mobileCallout)
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
RenderMarkdownBlocks(
@ -124,7 +124,7 @@ private fun RenderMarkdownBlocks(
val headingText = remember(current) { buildInlineMarkdown(current.firstChild, inlineStyles) }
Text(
text = headingText,
style = headingStyle(current.level),
style = headingStyle(current.level, inlineStyles.baseCallout),
color = textColor,
)
}
@ -231,7 +231,7 @@ private fun RenderParagraph(
Text(
text = annotated,
style = mobileCallout,
style = inlineStyles.baseCallout,
color = textColor,
)
}
@ -315,7 +315,7 @@ private fun RenderListItem(
) {
Text(
text = marker,
style = mobileCallout.copy(fontWeight = FontWeight.SemiBold),
style = inlineStyles.baseCallout.copy(fontWeight = FontWeight.SemiBold),
color = textColor,
modifier = Modifier.width(24.dp),
)
@ -360,7 +360,7 @@ private fun RenderTableBlock(
val cell = row.cells.getOrNull(index) ?: AnnotatedString("")
Text(
text = cell,
style = if (row.isHeader) mobileCaption1.copy(fontWeight = FontWeight.SemiBold) else mobileCallout,
style = if (row.isHeader) mobileCaption1.copy(fontWeight = FontWeight.SemiBold) else inlineStyles.baseCallout,
color = textColor,
modifier = Modifier
.border(1.dp, mobileTextSecondary.copy(alpha = 0.22f))
@ -417,6 +417,7 @@ private fun buildInlineMarkdown(start: Node?, inlineStyles: InlineStyles): Annot
node = start,
inlineCodeBg = inlineStyles.inlineCodeBg,
inlineCodeColor = inlineStyles.inlineCodeColor,
linkColor = inlineStyles.linkColor,
)
}
}
@ -425,6 +426,7 @@ private fun AnnotatedString.Builder.appendInlineNode(
node: Node?,
inlineCodeBg: Color,
inlineCodeColor: Color,
linkColor: Color,
) {
var current = node
while (current != null) {
@ -445,27 +447,27 @@ private fun AnnotatedString.Builder.appendInlineNode(
}
is Emphasis -> {
withStyle(SpanStyle(fontStyle = FontStyle.Italic)) {
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor)
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor)
}
}
is StrongEmphasis -> {
withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) {
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor)
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor)
}
}
is Strikethrough -> {
withStyle(SpanStyle(textDecoration = TextDecoration.LineThrough)) {
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor)
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor)
}
}
is Link -> {
withStyle(
SpanStyle(
color = mobileAccent,
color = linkColor,
textDecoration = TextDecoration.Underline,
),
) {
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor)
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor)
}
}
is MarkdownImage -> {
@ -482,7 +484,7 @@ private fun AnnotatedString.Builder.appendInlineNode(
}
}
else -> {
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor)
appendInlineNode(current.firstChild, inlineCodeBg = inlineCodeBg, inlineCodeColor = inlineCodeColor, linkColor = linkColor)
}
}
current = current.next
@ -519,19 +521,21 @@ private fun parseDataImageDestination(destination: String?): ParsedDataImage? {
return ParsedDataImage(mimeType = "image/$subtype", base64 = base64)
}
private fun headingStyle(level: Int): TextStyle {
private fun headingStyle(level: Int, baseCallout: TextStyle): TextStyle {
return when (level.coerceIn(1, 6)) {
1 -> mobileCallout.copy(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold)
2 -> mobileCallout.copy(fontSize = 20.sp, lineHeight = 26.sp, fontWeight = FontWeight.Bold)
3 -> mobileCallout.copy(fontSize = 18.sp, lineHeight = 24.sp, fontWeight = FontWeight.SemiBold)
4 -> mobileCallout.copy(fontSize = 16.sp, lineHeight = 22.sp, fontWeight = FontWeight.SemiBold)
else -> mobileCallout.copy(fontWeight = FontWeight.SemiBold)
1 -> baseCallout.copy(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.Bold)
2 -> baseCallout.copy(fontSize = 20.sp, lineHeight = 26.sp, fontWeight = FontWeight.Bold)
3 -> baseCallout.copy(fontSize = 18.sp, lineHeight = 24.sp, fontWeight = FontWeight.SemiBold)
4 -> baseCallout.copy(fontSize = 16.sp, lineHeight = 22.sp, fontWeight = FontWeight.SemiBold)
else -> baseCallout.copy(fontWeight = FontWeight.SemiBold)
}
}
private data class InlineStyles(
val inlineCodeBg: Color,
val inlineCodeColor: Color,
val linkColor: Color,
val baseCallout: TextStyle,
)
private data class TableRenderRow(

View File

@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@ -19,6 +21,7 @@ import ai.openclaw.app.chat.ChatMessage
import ai.openclaw.app.chat.ChatPendingToolCall
import ai.openclaw.app.ui.mobileBorder
import ai.openclaw.app.ui.mobileCallout
import ai.openclaw.app.ui.mobileCardSurface
import ai.openclaw.app.ui.mobileHeadline
import ai.openclaw.app.ui.mobileText
import ai.openclaw.app.ui.mobileTextSecondary
@ -33,11 +36,19 @@ fun ChatMessageListCard(
modifier: Modifier = Modifier,
) {
val listState = rememberLazyListState()
val displayMessages = remember(messages) { messages.asReversed() }
val stream = streamingAssistantText?.trim()
// With reverseLayout the newest item is at index 0 (bottom of screen).
LaunchedEffect(messages.size, pendingRunCount, pendingToolCalls.size, streamingAssistantText) {
// New list items/tool rows should animate into view, but token streaming should not restart
// that animation on every delta.
LaunchedEffect(messages.size, pendingRunCount, pendingToolCalls.size) {
listState.animateScrollToItem(index = 0)
}
LaunchedEffect(stream) {
if (!stream.isNullOrEmpty()) {
listState.scrollToItem(index = 0)
}
}
Box(modifier = modifier.fillMaxWidth()) {
LazyColumn(
@ -49,8 +60,6 @@ fun ChatMessageListCard(
) {
// With reverseLayout = true, index 0 renders at the BOTTOM.
// So we emit newest items first: streaming → tools → typing → messages (newest→oldest).
val stream = streamingAssistantText?.trim()
if (!stream.isNullOrEmpty()) {
item(key = "stream") {
ChatStreamingAssistantBubble(text = stream)
@ -69,8 +78,8 @@ fun ChatMessageListCard(
}
}
items(count = messages.size, key = { idx -> messages[messages.size - 1 - idx].id }) { idx ->
ChatMessageBubble(message = messages[messages.size - 1 - idx])
items(items = displayMessages, key = { it.id }) { message ->
ChatMessageBubble(message = message)
}
}
@ -85,7 +94,7 @@ private fun EmptyChatHint(modifier: Modifier = Modifier, healthOk: Boolean) {
Surface(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp),
color = androidx.compose.ui.graphics.Color.White.copy(alpha = 0.9f),
color = mobileCardSurface.copy(alpha = 0.9f),
border = androidx.compose.foundation.BorderStroke(1.dp, mobileBorder),
) {
androidx.compose.foundation.layout.Column(

View File

@ -36,7 +36,9 @@ import ai.openclaw.app.ui.mobileBorderStrong
import ai.openclaw.app.ui.mobileCallout
import ai.openclaw.app.ui.mobileCaption1
import ai.openclaw.app.ui.mobileCaption2
import ai.openclaw.app.ui.mobileCardSurface
import ai.openclaw.app.ui.mobileCodeBg
import ai.openclaw.app.ui.mobileCodeBorder
import ai.openclaw.app.ui.mobileCodeText
import ai.openclaw.app.ui.mobileHeadline
import ai.openclaw.app.ui.mobileText
@ -151,7 +153,7 @@ fun ChatPendingToolsBubble(toolCalls: List<ChatPendingToolCall>) {
ChatBubbleContainer(
style = bubbleStyle("assistant"),
roleLabel = "TOOLS",
roleLabel = "Tools",
) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text("Running tools...", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
@ -188,12 +190,13 @@ fun ChatPendingToolsBubble(toolCalls: List<ChatPendingToolCall>) {
fun ChatStreamingAssistantBubble(text: String) {
ChatBubbleContainer(
style = bubbleStyle("assistant").copy(borderColor = mobileAccent),
roleLabel = "ASSISTANT · LIVE",
roleLabel = "OpenClaw · Live",
) {
ChatMarkdown(text = text, textColor = mobileText)
}
}
@Composable
private fun bubbleStyle(role: String): ChatBubbleStyle {
return when (role) {
"user" ->
@ -215,7 +218,7 @@ private fun bubbleStyle(role: String): ChatBubbleStyle {
else ->
ChatBubbleStyle(
alignEnd = false,
containerColor = Color.White,
containerColor = mobileCardSurface,
borderColor = mobileBorderStrong,
roleColor = mobileTextSecondary,
)
@ -224,9 +227,9 @@ private fun bubbleStyle(role: String): ChatBubbleStyle {
private fun roleLabel(role: String): String {
return when (role) {
"user" -> "USER"
"system" -> "SYSTEM"
else -> "ASSISTANT"
"user" -> "You"
"system" -> "System"
else -> "OpenClaw"
}
}
@ -239,7 +242,7 @@ private fun ChatBase64Image(base64: String, mimeType: String?) {
Surface(
shape = RoundedCornerShape(10.dp),
border = BorderStroke(1.dp, mobileBorder),
color = Color.White,
color = mobileCardSurface,
modifier = Modifier.fillMaxWidth(),
) {
Image(
@ -277,7 +280,7 @@ fun ChatCodeBlock(code: String, language: String?) {
Surface(
shape = RoundedCornerShape(8.dp),
color = mobileCodeBg,
border = BorderStroke(1.dp, Color(0xFF2B2E35)),
border = BorderStroke(1.dp, mobileCodeBorder),
modifier = Modifier.fillMaxWidth(),
) {
Column(modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {

View File

@ -1,8 +1,5 @@
package ai.openclaw.app.ui.chat
import android.content.ContentResolver
import android.net.Uri
import android.util.Base64
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
@ -36,19 +33,17 @@ import ai.openclaw.app.MainViewModel
import ai.openclaw.app.chat.ChatSessionEntry
import ai.openclaw.app.chat.OutgoingAttachment
import ai.openclaw.app.ui.mobileAccent
import ai.openclaw.app.ui.mobileAccentBorderStrong
import ai.openclaw.app.ui.mobileBorder
import ai.openclaw.app.ui.mobileBorderStrong
import ai.openclaw.app.ui.mobileCallout
import ai.openclaw.app.ui.mobileCardSurface
import ai.openclaw.app.ui.mobileCaption1
import ai.openclaw.app.ui.mobileCaption2
import ai.openclaw.app.ui.mobileDanger
import ai.openclaw.app.ui.mobileSuccess
import ai.openclaw.app.ui.mobileSuccessSoft
import ai.openclaw.app.ui.mobileDangerSoft
import ai.openclaw.app.ui.mobileText
import ai.openclaw.app.ui.mobileTextSecondary
import ai.openclaw.app.ui.mobileWarning
import ai.openclaw.app.ui.mobileWarningSoft
import java.io.ByteArrayOutputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -68,7 +63,6 @@ fun ChatSheetContent(viewModel: MainViewModel) {
LaunchedEffect(mainSessionKey) {
viewModel.loadChat(mainSessionKey)
viewModel.refreshChatSessions(limit = 200)
}
val context = LocalContext.current
@ -84,7 +78,7 @@ fun ChatSheetContent(viewModel: MainViewModel) {
val next =
uris.take(8).mapNotNull { uri ->
try {
loadImageAttachment(resolver, uri)
loadSizedImageAttachment(resolver, uri)
} catch (_: Throwable) {
null
}
@ -106,7 +100,6 @@ fun ChatSheetContent(viewModel: MainViewModel) {
sessionKey = sessionKey,
sessions = sessions,
mainSessionKey = mainSessionKey,
healthOk = healthOk,
onSelectSession = { key -> viewModel.switchChatSession(key) },
)
@ -160,77 +153,37 @@ private fun ChatThreadSelector(
sessionKey: String,
sessions: List<ChatSessionEntry>,
mainSessionKey: String,
healthOk: Boolean,
onSelectSession: (String) -> Unit,
) {
val sessionOptions = resolveSessionChoices(sessionKey, sessions, mainSessionKey = mainSessionKey)
val currentSessionLabel =
friendlySessionName(sessionOptions.firstOrNull { it.key == sessionKey }?.displayName ?: sessionKey)
val sessionOptions =
remember(sessionKey, sessions, mainSessionKey) {
resolveSessionChoices(sessionKey, sessions, mainSessionKey = mainSessionKey)
}
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically,
) {
Text(
text = "SESSION",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 0.8.sp),
color = mobileTextSecondary,
)
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
Row(
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
for (entry in sessionOptions) {
val active = entry.key == sessionKey
Surface(
onClick = { onSelectSession(entry.key) },
shape = RoundedCornerShape(14.dp),
color = if (active) mobileAccent else mobileCardSurface,
border = BorderStroke(1.dp, if (active) mobileAccentBorderStrong else mobileBorderStrong),
tonalElevation = 0.dp,
shadowElevation = 0.dp,
) {
Text(
text = currentSessionLabel,
style = mobileCallout.copy(fontWeight = FontWeight.SemiBold),
color = mobileText,
text = friendlySessionName(entry.displayName ?: entry.key),
style = mobileCaption1.copy(fontWeight = if (active) FontWeight.Bold else FontWeight.SemiBold),
color = if (active) Color.White else mobileText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
)
ChatConnectionPill(healthOk = healthOk)
}
}
Row(
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
for (entry in sessionOptions) {
val active = entry.key == sessionKey
Surface(
onClick = { onSelectSession(entry.key) },
shape = RoundedCornerShape(14.dp),
color = if (active) mobileAccent else Color.White,
border = BorderStroke(1.dp, if (active) Color(0xFF154CAD) else mobileBorderStrong),
tonalElevation = 0.dp,
shadowElevation = 0.dp,
) {
Text(
text = friendlySessionName(entry.displayName ?: entry.key),
style = mobileCaption1.copy(fontWeight = if (active) FontWeight.Bold else FontWeight.SemiBold),
color = if (active) Color.White else mobileText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
)
}
}
}
}
}
@Composable
private fun ChatConnectionPill(healthOk: Boolean) {
Surface(
shape = RoundedCornerShape(999.dp),
color = if (healthOk) mobileSuccessSoft else mobileWarningSoft,
border = BorderStroke(1.dp, if (healthOk) mobileSuccess.copy(alpha = 0.35f) else mobileWarning.copy(alpha = 0.35f)),
) {
Text(
text = if (healthOk) "Connected" else "Offline",
style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold),
color = if (healthOk) mobileSuccess else mobileWarning,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp),
)
}
}
@ -238,7 +191,7 @@ private fun ChatConnectionPill(healthOk: Boolean) {
private fun ChatErrorRail(errorText: String) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = androidx.compose.ui.graphics.Color.White,
color = mobileDangerSoft,
shape = RoundedCornerShape(12.dp),
border = androidx.compose.foundation.BorderStroke(1.dp, mobileDanger),
) {
@ -259,24 +212,3 @@ data class PendingImageAttachment(
val mimeType: String,
val base64: String,
)
private suspend fun loadImageAttachment(resolver: ContentResolver, uri: Uri): PendingImageAttachment {
val mimeType = resolver.getType(uri) ?: "image/*"
val fileName = (uri.lastPathSegment ?: "image").substringAfterLast('/')
val bytes =
withContext(Dispatchers.IO) {
resolver.openInputStream(uri)?.use { input ->
val out = ByteArrayOutputStream()
input.copyTo(out)
out.toByteArray()
} ?: ByteArray(0)
}
if (bytes.isEmpty()) throw IllegalStateException("empty attachment")
val base64 = Base64.encodeToString(bytes, Base64.NO_WRAP)
return PendingImageAttachment(
id = uri.toString() + "#" + System.currentTimeMillis().toString(),
fileName = fileName,
mimeType = mimeType,
base64 = base64,
)
}

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

@ -0,0 +1,5 @@
package ai.openclaw.app.voice
internal object TalkDefaults {
const val defaultSilenceTimeoutMs = 700L
}

View File

@ -0,0 +1,50 @@
package ai.openclaw.app.voice
import ai.openclaw.app.normalizeMainKey
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.contentOrNull
internal data class TalkModeGatewayConfigState(
val mainSessionKey: String,
val interruptOnSpeech: Boolean?,
val silenceTimeoutMs: Long,
)
internal object TalkModeGatewayConfigParser {
fun parse(config: JsonObject?): TalkModeGatewayConfigState {
val talk = config?.get("talk").asObjectOrNull()
val sessionCfg = config?.get("session").asObjectOrNull()
return TalkModeGatewayConfigState(
mainSessionKey = normalizeMainKey(sessionCfg?.get("mainKey").asStringOrNull()),
interruptOnSpeech = talk?.get("interruptOnSpeech").asBooleanOrNull(),
silenceTimeoutMs = resolvedSilenceTimeoutMs(talk),
)
}
fun resolvedSilenceTimeoutMs(talk: JsonObject?): Long {
val fallback = TalkDefaults.defaultSilenceTimeoutMs
val primitive = talk?.get("silenceTimeoutMs") as? JsonPrimitive ?: return fallback
if (primitive.isString) return fallback
val timeout = primitive.content.toDoubleOrNull() ?: return fallback
if (timeout <= 0 || timeout % 1.0 != 0.0 || timeout > Long.MAX_VALUE.toDouble()) {
return fallback
}
return timeout.toLong()
}
}
private fun JsonElement?.asStringOrNull(): String? =
this?.let { element ->
element as? JsonPrimitive
}?.contentOrNull
private fun JsonElement?.asBooleanOrNull(): Boolean? {
val primitive = this as? JsonPrimitive ?: return null
return primitive.booleanOrNull
}
private fun JsonElement?.asObjectOrNull(): JsonObject? =
this as? JsonObject

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.OpenClawNode" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">false</item>
</style>
</resources>

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

@ -20,4 +20,19 @@ class SecurePrefsTest {
assertEquals(LocationMode.WhileUsing, prefs.locationMode.value)
assertEquals("whileUsing", plainPrefs.getString("location.enabledMode", null))
}
@Test
fun saveGatewayBootstrapToken_persistsSeparatelyFromSharedToken() {
val context = RuntimeEnvironment.getApplication()
val securePrefs = context.getSharedPreferences("openclaw.node.secure.test", Context.MODE_PRIVATE)
securePrefs.edit().clear().commit()
val prefs = SecurePrefs(context, securePrefsOverride = securePrefs)
prefs.setGatewayToken("shared-token")
prefs.setGatewayBootstrapToken("bootstrap-token")
assertEquals("shared-token", prefs.loadGatewayToken())
assertEquals("bootstrap-token", prefs.loadGatewayBootstrapToken())
assertEquals("bootstrap-token", prefs.gatewayBootstrapToken.value)
}
}

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