Compare commits

..

3955 Commits

Author SHA1 Message Date
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: edd8ed8254
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: 38d8048359
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: 8ce59bb715
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: d791190f83
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: 20fe05fe74
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: 56b25a9fb3
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: 63331a54b8
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: e5432ec4e1
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 bb06dc7cc9.
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: df8f292039
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: 74becc8f7d
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: 9bffa3422c 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: 0255e3971b
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: 2846a6cfa9
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 aaeb348bb7.
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: 42b5595ede
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 6fc3839cb5.

* 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: 621ef634a4
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: ecc73fe47c
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: 8c69de8222
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: 2378e40383
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: 4518fb20e1
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: 99eb3fd928
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: d81d3321b6
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: 4ba4a39ddf
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: e37f965b8e
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: 9522761cf1
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: ff1f127176
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: 8e6d254cc4
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: c372ba691b
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: edd17c0eff
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: cf4eab1c51
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: 145a7b7c4e
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: 35dcc5ba35
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 ee6bdb3bab.
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: a4114a52bc
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: 4f48e977fe
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: 6d2bc02cc1
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: 1d58a36a77
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: 9660f6cd5b
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: 0b341f6f4c
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: 150ea1a7c9
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: 52ebbf5188
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: 75d9204c86
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: 6e8852a188
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: 8a858436ed
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: 856f11f129
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: f8cf752c59
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: b99eed4329
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: de07585e03
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: 4a67791f53
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: 9f530d2a86
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: a10fd4b21c
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: 91761487e8
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: 4a065ef5d8
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 e5774471c8)
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: 0957b32dc6
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: f06318e58f
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: e6d7b7e31a
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: a994c07190
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: 43f66cdcf0
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: 1f6b10b9d9
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: 70613e0225
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: 05179362b4
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: e62b88bb01
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: bebf6704d7
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: c57b1f8ba2
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: c88f89c462
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: 631fc14832
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: 39e8e9ad62
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: 775e3063b5
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: c0940838bc
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: d398fa0222
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: 00e2ad847b
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: 04c33fa061
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: 82b38fe93b
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: d3285fef41
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: 2f50c51d5a
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: 13839c2dbd
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: d8a04a466a
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: f30cacafb3
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: 049d1e119a
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: 68f106ff49
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: bb16fecbf7
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: 4d79c41ddf
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: f3cdfa76dd
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: 85c4eb7d26
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: 8be8967bcb
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: d30fff1776
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: 5cffcb072c
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: 33cac4c33f
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: 0e8b27bf80
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: 1bb49b2e64
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: 8abc6c1657
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: 0a8ebf8e5b
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: a4456d48b4
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: ff07dc45a9
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: 66c8bb2c6a
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: baa6b5d566
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: 2780e62d07
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: a0b9a02e2e
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: b1fcfba868
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: f243379094
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: 1e6f1d9d51
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 5a659b0b61.
2026-03-09 18:47:44 -05:00
Val Alexander 9f0a64f855
Revert "Update ui/src/ui/chat/export.ts"
This reverts commit d648dd7643.
2026-03-09 18:47:40 -05:00
Val Alexander 8e412bad0e
Revert "fix(ui): address review feedback on chat infra slice"
This reverts commit 8a6cd808a1.
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: 0e600e89cf
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: ee96e96bb9
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: 0053bae140
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: d18356cb80
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: 031d032534
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: cae0aaf1c2
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: 852469c82f
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: 7d167dff54
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: bbc4254b94
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: 514d587352
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: f2ac51df2c
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: 0764368e80
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: fcabdf7c31
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: b5e6e13afe
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: 361c2eb5c8
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: a6d7ca90d7
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: 89bd83f09a
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: 0e2e0dcc36
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: 4900042298
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: 5877389e33
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: c1191edc08
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: cfd99562d6
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: 44622abfbc
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: f9b2375892
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: 54b1513c78
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: 6e235e7d1f
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: be6350e5ae
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: b63e46dd94
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: abdfa1a35f
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: c41020779e
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: e4295fe18b
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: 567b3ed684
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: 69e1861abf
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: ed46625ae2
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: 6af4820b7d
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: 62de5d5669
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: c651f07855
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: bac2df4b7f
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: a3d6d6c845
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: 00a2b241df
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
Peter Steinberger 53fb317e7f fix(macos): clean swiftformat pass and sendable warning 2026-03-08 13:22:46 +00:00
Ayaan Zaidi eb0758e172 docs(changelog): note Android Play policy cutovers 2026-03-08 16:25:49 +05:30
Ayaan Zaidi 04b4b48077 fix(android): persist legacy location mode migration 2026-03-08 16:25:49 +05:30
Ayaan Zaidi 709e11ea70 build(android): bump release version code 2026-03-08 16:25:49 +05:30
Ayaan Zaidi 46145fde19 fix(android): remove mic and screen foreground services 2026-03-08 16:25:49 +05:30
Ayaan Zaidi 1230cefe25 fix(android): remove background location mode 2026-03-08 16:25:49 +05:30
Ayaan Zaidi 0f9566b0b5 fix(android): remove self-update install flow 2026-03-08 16:25:49 +05:30
arceus77-7 492fe679a7
feat(tui): infer workspace agent when launching TUI (#39591)
Merged via squash.

Prepared head SHA: 23533e24c4
Co-authored-by: arceus77-7 <261276524+arceus77-7@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-08 13:31:11 +03:00
Altay f4c4856254
docs(changelog): add #39377 failover note (#39704) 2026-03-08 13:09:26 +03:00
gambletan 8a20f51460
fix: add rate limit patterns for 'too many tokens' and 'tokens per day' (#39377)
Merged via squash.

Prepared head SHA: 132a457286
Co-authored-by: gambletan <266203672+gambletan@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-08 13:03:33 +03:00
Farhoud Cheraghi aedf3ee68f
fix(skills): expand skill-creator description to cover edit/audit/review triggers (#39158)
Merged via squash.

Prepared head SHA: 13997c1ee5
Co-authored-by: haynzz <1236319+haynzz@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-08 12:13:00 +03:00
J. Campbell b38f371630
fix: add @tloncorp/api to pnpm onlyBuiltDependencies allowlist (#39027)
Merged via squash.

Prepared head SHA: e149350260
Co-authored-by: apexfork <363026+apexfork@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-08 12:07:10 +03:00
gambletan e5fdfec9dc
fix(config): accept "openclaw" as browser profile driver in Zod schema (#39374)
Merged via squash.

Prepared head SHA: 0eba5ab939
Co-authored-by: gambletan <266203672+gambletan@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-08 12:04:49 +03:00
Altay f73778e9b2
fix: remove redundant root strip-ansi dependency (#39652) 2026-03-08 12:04:46 +03:00
Nutchanon (Ben) Ninyawee c1b914026d
fix: add missing strip-ansi dep for pi-coding-agent (#38999)
Merged via squash.

Prepared head SHA: dd03a6aaaf
Co-authored-by: ninyawee <8089231+ninyawee@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-08 12:00:17 +03:00
Daniel Hnyk 9425209602
fix(mattermost): pass payload.replyToId as root_id for threaded replies (#27744)
Merged via squash.

Prepared head SHA: e029079872
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-08 14:13:13 +05:30
Ayaan Zaidi 4db634964b chore(secrets): sync appcast baseline 2026-03-08 13:29:26 +05:30
Ayaan Zaidi 6477da623f chore(secrets): sync detect-secrets baseline 2026-03-08 13:25:01 +05:30
Ayaan Zaidi d3c3d0e730 style(android): update app icon 2026-03-08 13:25:01 +05:30
Peter Lee 92648f9ba9
fix(agents): broaden 402 temporary-limit detection and allow billing cooldown probe (#38533)
Merged via squash.

Prepared head SHA: 282b9186c6
Co-authored-by: xialonglee <22994703+xialonglee@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-08 10:27:01 +03:00
Peter Steinberger d15b6af77b fix: land contributor PR #39516 from @Imhermes1
macOS app/chat/browser/cron/permissions fixes.

Co-authored-by: ImHermes1 <lukeforn@gmail.com>
2026-03-08 06:11:20 +00:00
Peter Steinberger 05217845a7 build: bump version to 2026.3.8 2026-03-08 05:59:04 +00:00
Peter Steinberger 389647157d build: update stable appcast release URL 2026-03-08 05:53:19 +00:00
Ayaan Zaidi c217237a36 style(daemon-cli): format lifecycle test 2026-03-08 11:22:57 +05:30
Peter Steinberger 42a1394c5c build: prepare 2026.3.7 release 2026-03-08 05:42:26 +00:00
Vincent Koc c3810346f9
CLI: avoid false update restart failures without listener attribution (#39508) 2026-03-07 21:42:25 -08:00
Peter Steinberger e0f80cf0e9 fix(ui): align control-ui device auth token signing 2026-03-08 05:41:03 +00:00
Peter Steinberger 5d22bd0297 fix: add google flash-lite forward compat 2026-03-08 05:22:38 +00:00
Peter Steinberger 59102a1ff7 fix: add gemini 3.1 flash-lite support 2026-03-08 05:12:48 +00:00
Peter Steinberger 06ffef8465 fix(ci): repair zalouser CI failures 2026-03-08 05:09:12 +00:00
Peter Steinberger c6a8ab69c6 build: refresh beta appcast asset signature 2026-03-08 04:53:53 +00:00
Peter Steinberger fcdc1a13e1 fix: land #33992 from @darkamenosa
Co-authored-by: Tom <hxtxmu@gmail.com>
2026-03-08 04:49:04 +00:00
Peter Steinberger d9670093cb style: format daemon lifecycle test 2026-03-08 04:44:08 +00:00
Peter Steinberger 3596a46868 build: prepare 2026.3.7-beta.1 release 2026-03-08 04:44:08 +00:00
Peter Steinberger dd8fd98ad4 build: reduce build log noise 2026-03-08 04:12:32 +00:00
Peter Steinberger a035a3ce48 fix: drop removed minimax lightning model 2026-03-08 04:06:26 +00:00
Peter Steinberger 21df014d56 fix: stage docker live tests from mounted source 2026-03-08 04:06:26 +00:00
Peter Steinberger 1b3d8ee250 docs: note npmjs 1password path for releases 2026-03-08 04:03:25 +00:00
Peter Steinberger dc78725d47 test: stabilize exec resolver timeout fixture 2026-03-08 03:50:41 +00:00
Ayaan Zaidi 5214859c52 chore: add changelog and format fix for #39414 2026-03-08 09:17:02 +05:30
Ayaan Zaidi 930caeaafb fix(chat): preserve sender labels in dashboard history 2026-03-08 09:17:02 +05:30
Peter Steinberger c743fd9c4c docs: clean up latest changelog sections 2026-03-08 03:34:53 +00:00
Peter Steinberger 75a44dee8f docs: dedupe changelog contributor attribution 2026-03-08 03:34:53 +00:00
Peter Steinberger f2a4bdf069 fix(ci): resolve current gate regressions 2026-03-08 03:34:36 +00:00
Peter Steinberger ed437434af refactor(voice-call): share tts deep merge 2026-03-08 03:22:55 +00:00
Peter Steinberger 5659d7f985 fix: land #39337 by @goodspeed-apps for acpx MCP bootstrap
Co-authored-by: Goodspeed App Studio <goodspeed-apps@users.noreply.github.com>
2026-03-08 03:16:26 +00:00
Peter Steinberger f72114173c fix(ci): resolve type regressions on main 2026-03-08 03:11:24 +00:00
gambletan 9c8e34da9d
fix: document discord agentComponents schema parity (#39378) (thanks @gambletan) (#39378)
Co-authored-by: Shadow <hi@shadowing.dev>
2026-03-07 21:11:12 -06:00
Shadow d902bae554
fix(discord): validate agentComponents config 2026-03-07 21:08:36 -06:00
Peter Steinberger 7d2b146d8d test: cover daemon probe auth seam 2026-03-08 03:02:25 +00:00
Peter Steinberger f6c7ff3e0e refactor: preserve explicit mock voice-call values 2026-03-08 03:02:25 +00:00
Peter Steinberger bd413263b2 refactor: register gateway service adapters 2026-03-08 03:02:25 +00:00
Peter Steinberger 380eb1c072 refactor: reuse shared gateway probe auth 2026-03-08 03:02:25 +00:00
Peter Steinberger fd1e481624 refactor: split daemon status gathering 2026-03-08 03:02:25 +00:00
Peter Steinberger 2646739d23 refactor: centralize strict numeric parsing 2026-03-08 03:02:25 +00:00
Peter Steinberger 3087893ef9 refactor: normalize voice-call runtime defaults 2026-03-08 03:02:25 +00:00
Peter Steinberger 5759b93dda fix(ci): pin multi-arch docker base digests 2026-03-08 02:55:15 +00:00
Ayaan Zaidi 722c5e5d33 docs: add changelog for Telegram DM draft restore (#39398) 2026-03-08 08:23:25 +05:30
Ayaan Zaidi e45fcc57ed fix(telegram): restore DM draft streaming 2026-03-08 08:23:25 +05:30
Peter Steinberger 56cd0084d9 test: fix gate regressions 2026-03-08 02:45:08 +00:00
Peter Steinberger 7f44bc5e94 fix: reject launchd pid sentinel values
Landed from contributor PR #39281 by @mvanhorn.

Co-authored-by: Matt Van Horn <mvanhorn@gmail.com>
2026-03-08 02:44:02 +00:00
Vincent Koc 244aabb0cb Voice Call: read realtime STT internals in tests 2026-03-07 18:42:17 -08:00
Vincent Koc b1f7cf46d8 Voice Call: read TTS internals in tests 2026-03-07 18:42:15 -08:00
Vincent Koc b8b65692c0 Voice Call: allowlist realtime STT api key fixtures 2026-03-07 18:39:39 -08:00
Vincent Koc 14916fbc70 Secrets: refresh baseline for model provider docs 2026-03-07 18:39:39 -08:00
Peter Steinberger 442f2c36b3 fix: honor explicit OpenAI TTS speed values
Landed from contributor PR #39318 by @ql-wade.

Co-authored-by: ql-wade <wade@openclaw.ai>
2026-03-08 02:38:44 +00:00
Peter Steinberger 28b72e5cb0 fix: honor zero-valued voice-call STT settings
Landed from contributor PR #39196 by @scoootscooob.

Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-03-08 02:36:41 +00:00
Peter Steinberger a8c67affd8 test: cover gemini flash compat normalization 2026-03-08 02:34:49 +00:00
Peter Steinberger af9d76b79a fix: honor explicit Synology Chat rate-limit env values
Landed from contributor PR #39197 by @scoootscooob.

Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-03-08 02:34:19 +00:00
Vincent Koc 6cb889da8c TUI: type setSession test mocks 2026-03-07 18:33:46 -08:00
Peter Steinberger 100da9f45c fix: correct gemini flash model id 2026-03-08 02:32:58 +00:00
Peter Steinberger 46008178d1 fix: isolate TUI /new sessions per client
Landed from contributor PR #39238 by @widingmarcus-cyber.

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
2026-03-08 02:31:15 +00:00
Vincent Koc 76a028a50a Gateway CLI: allowlist password-file fixture 2026-03-07 18:28:18 -08:00
Peter Steinberger 9d7d961db8 fix: restore Telegram webhook-mode health after restarts
Landed from contributor PR #39313 by @fellanH.

Co-authored-by: Felix Hellström <30758862+fellanH@users.noreply.github.com>
2026-03-08 02:27:18 +00:00
Peter Steinberger 1ef8d6a01b test: accept ACP token-file inspect errors 2026-03-08 02:27:18 +00:00
Vincent Koc 0125bd9639 Agents UI: complete config state test fixture 2026-03-07 18:24:41 -08:00
Vincent Koc 96f4f50f51 Agents UI: compose save state from config state 2026-03-07 18:24:41 -08:00
Vincent Koc c6ff137a6f CI: make CodeQL manual only 2026-03-07 18:23:21 -08:00
Peter Steinberger c0a7c302f3 fix: preserve agents-page selection after config save
Landed from contributor PR #39301 by @MumuTW.

Co-authored-by: MumuTW <clothl47364@gmail.com>
2026-03-08 02:20:48 +00:00
Vincent Koc 1e3daa6373 CI: fix CodeQL concurrency 2026-03-07 18:20:32 -08:00
Vincent Koc bf9c362129
Gateway: stop and restart unmanaged listeners (#39355)
* Daemon: allow unmanaged gateway lifecycle fallback

* Status: fix service summary formatting

* Changelog: note unmanaged gateway lifecycle fallback

* Tests: cover unmanaged gateway lifecycle fallback

* Daemon: split unmanaged restart health checks

* Daemon: harden unmanaged gateway signaling

* Daemon: reject unmanaged restarts when disabled
2026-03-07 18:20:29 -08:00
Vincent Koc 4062aa5e5d
Gateway: add safer password-file input for gateway run (#39067)
* CLI: add gateway password-file option

* Docs: document safer gateway password input

* Update src/cli/gateway-cli/run.ts

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

* Tests: clean up gateway password temp dirs

* CLI: restore gateway password warning flow

* Security: harden secret file reads

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-07 18:20:17 -08:00
Vincent Koc 31564bed1d CI: fix CodeQL manual builds 2026-03-07 18:18:53 -08:00
Peter Steinberger c2e1ae68a9 refactor(telegram): split bot message context helpers 2026-03-08 02:16:03 +00:00
Peter Steinberger a679049c38 refactor(doctor): type legacy migration fixtures 2026-03-08 02:16:03 +00:00
Peter Steinberger 44e7c1142e refactor(doctor): model legacy file copies as plans 2026-03-08 02:16:03 +00:00
Peter Steinberger 01cff3a7a6 refactor(pairing): share allowFrom path resolution 2026-03-08 02:16:03 +00:00
Peter Steinberger e7056272bc refactor(telegram): centralize text parsing helpers 2026-03-08 02:16:03 +00:00
Peter Steinberger 6a8081a7f3 refactor(routing): centralize inbound last-route policy 2026-03-08 02:16:03 +00:00
Vincent Koc b2f8f5e4dd CI: add CodeQL workflow 2026-03-07 18:15:06 -08:00
Peter Steinberger 49261b0d82 fix: auto-create inherited agent override entries
Landed from contributor PR #39326 by @dunamismax.

Co-authored-by: dunamismax <dunamismax@tutamail.com>
2026-03-08 02:12:33 +00:00
Peter Steinberger 1e05f14f3a fix: land health-monitor disconnected reason label (#36436) (thanks @Sid-Qin) 2026-03-08 02:02:19 +00:00
SidQin-cyber 066d589b8a fix(gateway): distinguish disconnected from stuck in health-monitor restart reason
resolveChannelRestartReason did not handle the "disconnected" evaluation
reason explicitly, so it fell through to "stuck". This conflates a clean
WebSocket drop (e.g. Discord 1006) with a genuinely stuck channel, making
logs misleading and preventing future policy differentiation.

Add "disconnected" to ChannelRestartReason and handle it before the
catch-all "stuck" return.

Closes #36404
2026-03-08 02:02:19 +00:00
Vincent Koc 0018f47661 Secrets: refresh baseline for tts line drift 2026-03-07 18:00:13 -08:00
Vincent Koc f494e46ea0 Ollama: allowlist test api keys 2026-03-07 18:00:13 -08:00
Vincent Koc ae15e3fd60 Daemon CLI: format lifecycle core imports 2026-03-07 18:00:13 -08:00
Peter Steinberger 5b257c65d5 fix: default codex-cli sandbox to workspace-write
Landed from contributor PR #39336 by @0xtangping.

Co-authored-by: john <john.j@min123.net>
2026-03-08 01:58:34 +00:00
Peter Steinberger 1b9e4800eb test: fix gateway register option collision mock 2026-03-08 01:58:33 +00:00
Vincent Koc daecd2d8c3
Pi Runner: gate parallel_tool_calls to compatible APIs (#39356)
* Pi Runner: gate parallel_tool_calls payload injection

* Pi Runner: cover parallel_tool_calls alias precedence

* Changelog: note parallel_tool_calls compatibility fix

* Update CHANGELOG.md

* Pi Runner: clarify null parallel_tool_calls override logging
2026-03-07 17:57:53 -08:00
Vincent Koc 2c7fb54956
Config: fail closed invalid config loads (#39071)
* Config: fail closed invalid config loads

* CLI: keep diagnostics on explicit best-effort config

* Tests: cover invalid config best-effort diagnostics

* Changelog: note invalid config fail-closed fix

* Status: pass best-effort config through status-all gateway RPCs

* CLI: pass config through gateway secret RPC

* CLI: skip plugin loading from invalid config

* Tests: align daemon token drift env precedence
2026-03-07 17:48:13 -08:00
Vincent Koc 1831dbb63f Status: format service summary 2026-03-07 17:46:24 -08:00
Vincent Koc 7e946b3c6c
fix(ollama): register custom api for compaction and summarization (#39332)
* fix(agents): add custom api registry helper

* fix(ollama): register native api for embedded runs

* fix(ollama): register custom api before compaction

* fix(tts): register custom api before summarization

* changelog: note ollama compaction registration fix

* fix(ollama): honor resolved base urls in custom api paths
2026-03-07 17:40:34 -08:00
lidamao633 01833c5111
fix(acp): avoid inline delivery for oneshot run spawns (#39014)
* fix(acp): scope inline delivery to session spawns

* test(acp): cover run and session delivery behavior

* Changelog: add ACP run delivery bootstrap fix

---------

Co-authored-by: 徐善 <samxu633@gmail.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-07 17:37:22 -08:00
Vincent Koc 5b30c9d3d7 Changelog: move #39328 credit to section end 2026-03-07 17:36:11 -08:00
Vincent Koc 2ec478cf68 Changelog: credit #39328 to @vincentkoc 2026-03-07 17:35:29 -08:00
Vincent Koc 69a6c0a9dd
Runner: normalize malformed tool call names before dispatch (#39328)
* Runner: normalize malformed tool call names before dispatch

* Runner: tighten prefixed tool name normalization
2026-03-07 17:34:27 -08:00
Vincent Koc ad80ecd445 Discord: fix native command context test args 2026-03-07 17:34:11 -08:00
Vincent Koc 556a74d259
Daemon: handle degraded systemd status checks (#39325)
* Daemon: handle degraded systemd status checks

* Changelog: note systemd status handling

* Update src/commands/status.service-summary.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-07 17:30:48 -08:00
Vincent Koc c22a4450ee
fix(telegram): honor commands.allowFrom in native command auth (#39310)
* telegram: honor commands.allowFrom in native auth

* test(telegram): cover native commands.allowFrom precedence

* changelog: note telegram native commands allowFrom fix

* Update CHANGELOG.md

* telegram: preserve group policy in native command auth

* test(telegram): keep commands.allowFrom under group gating
2026-03-07 17:28:47 -08:00
Peter Steinberger 8cc477b873 refactor(sessions): simplify provider normalizer matching 2026-03-08 01:27:05 +00:00
Peter Steinberger e381ab630e refactor(channels): share native command session targets 2026-03-08 01:27:05 +00:00
Peter Steinberger 6016e22cc0 refactor(discord): compose native command routes 2026-03-08 01:27:05 +00:00
Peter Steinberger 547436bca7 refactor(discord): extract inbound context helpers 2026-03-08 01:27:05 +00:00
Peter Steinberger 08597e817d fix(ci): stabilize detect-secrets baseline 2026-03-08 01:25:15 +00:00
Peter Steinberger eb9e78d6d0 fix(discord): default missing native command args 2026-03-08 01:17:59 +00:00
Peter Steinberger ad7399b6e6 refactor(sessions): add provider key normalizers 2026-03-08 01:17:06 +00:00
Peter Steinberger 8f719e541a refactor(discord): extract native command session targets 2026-03-08 01:15:56 +00:00
Peter Steinberger 9d10697227 refactor(discord): extract native command context builder 2026-03-08 01:15:29 +00:00
Peter Steinberger 189cd99377 refactor(discord): require explicit outbound target hints 2026-03-08 01:15:29 +00:00
Peter Steinberger 74e3c071b2 refactor(discord): extract session key normalization 2026-03-08 01:15:29 +00:00
Peter Steinberger c1d07b09ce refactor(discord): extract route resolution helpers 2026-03-08 01:15:29 +00:00
Peter Steinberger 269cc22b61 refactor(telegram): split lane delivery modules 2026-03-08 01:14:16 +00:00
Peter Steinberger 1135b7f12f refactor(telegram): precompute dm preview transport flag 2026-03-08 01:14:16 +00:00
Peter Steinberger 3987ca4099 refactor(retry): simplify telegram shouldRetry composition 2026-03-08 01:14:16 +00:00
Peter Steinberger 7b9a34939a refactor(telegram): share error graph traversal helper 2026-03-08 01:14:16 +00:00
Peter Steinberger f866e57de3 refactor(telegram): dedupe non-idempotent request setup 2026-03-08 01:14:16 +00:00
Peter Steinberger 7e59803df2 refactor(queue): use stable tuple key for recent message dedupe 2026-03-08 01:14:16 +00:00
Peter Steinberger bebde34b98 refactor(sandbox): clarify fs bridge read and shell plans 2026-03-08 01:14:07 +00:00
Peter Steinberger da88d92099 fix(gateway): fail closed for config-first secretrefs 2026-03-08 01:13:28 +00:00
Peter Steinberger f236742dc1 fix(gateway): block cached device token override fallback 2026-03-08 01:13:28 +00:00
Peter Steinberger a2cb80b9c4 fix(daemon): preserve envfile auth provenance 2026-03-08 01:13:28 +00:00
Peter Steinberger ad052d661b docs: note gateway auth follow-up hardening 2026-03-08 01:13:28 +00:00
Peter Steinberger 99cfd271d0 fix(sandbox): pin fs bridge readfile handles 2026-03-08 01:09:05 +00:00
Peter Steinberger bc91ae9ca0 fix(discord): preserve native command session keys 2026-03-08 01:06:09 +00:00
Peter Steinberger cf1c2cc208 fix(discord): normalize DM session keys 2026-03-08 01:06:09 +00:00
Peter Steinberger 6337666ac0 fix(telegram): restore named-account DM fallback routing (from #32426)
Rebased and landed contributor work from @chengzhichao-xydt for the
Telegram multi-account DM regression in #32351.

Co-authored-by: Zhichao Cheng <cheng.zhichao@xydigit.com>
2026-03-08 01:05:08 +00:00
Peter Steinberger 40dfba85d8 refactor(sandbox): split fs bridge path safety 2026-03-08 01:01:40 +00:00
Peter Steinberger eb09d8dd71 fix(telegram): land #34238 from @hal-crackbot
Landed from contributor PR #34238 by @hal-crackbot.

Co-authored-by: Hal Crackbot <hal@crackbot.dev>
2026-03-08 00:56:58 +00:00
Peter Steinberger 09cfcf9dd5 fix(sandbox): anchor fs-bridge mkdirp 2026-03-08 00:55:34 +00:00
Peter Steinberger a505be78ab fix(telegram): land #38906 from @gambletan
Landed from contributor PR #38906 by @gambletan.

Co-authored-by: gambletan <ethanchang32@gmail.com>
2026-03-08 00:54:49 +00:00
Peter Steinberger 4869e24915 fix(telegram): land #34983 from @HOYALIM
Landed from contributor PR #34983 by @HOYALIM.

Co-authored-by: Ho Lim <subhoya@gmail.com>
2026-03-08 00:53:19 +00:00
Vincent Koc d6d04f361e
fix(ollama): preserve local limits and native thinking fallback (#39292)
* fix(ollama): support thinking field fallback in native stream

* fix(models): honor explicit lower token limits in merge mode

* fix(ollama): prefer streamed content over fallback thinking

* changelog: note Ollama local model fixes
2026-03-07 16:53:02 -08:00
Peter Steinberger 5edcab2eee fix(queue): land #33168 from @rylena
Landed from contributor PR #33168 by @rylena.

Co-authored-by: Rylen Anil <rylen.anil@gmail.com>
2026-03-08 00:51:11 +00:00
Peter Steinberger 149ae45bad fix(cron): preserve manual timeoutSeconds on add 2026-03-08 00:48:57 +00:00
Peter Steinberger e66c418c45 refactor(cron): normalize legacy delivery at ingress 2026-03-08 00:48:57 +00:00
Peter Steinberger 9b99787c31 refactor(cron): extract delivery tool policy helpers 2026-03-08 00:48:57 +00:00
Peter Steinberger 45d3e62f50 refactor(cron): extract agent defaults merge helpers 2026-03-08 00:48:56 +00:00
Peter Steinberger 6b18ec479c refactor(cron): centralize initial delivery defaults 2026-03-08 00:48:56 +00:00
Peter Steinberger e758d49361 refactor(plugins): extract alias candidate resolution 2026-03-08 00:48:56 +00:00
Peter Steinberger 7ac7b39eff refactor(daemon): extract gateway token drift helper 2026-03-08 00:48:56 +00:00
Edward 02eef1d45a
fix(telegram): use group allowlist for native command auth in groups (#39267)
* fix(telegram): use group allowlist for native command auth in groups

Native slash commands (/status, /model, etc.) in Telegram supergroups
and forum topics reject authorized senders with "not authorized" even
when the sender is in groupAllowFrom.

The bug is in resolveTelegramCommandAuth — the final commandAuthorized
check only passes DM allowFrom as an authorizer, so senders who are
authorized via groupAllowFrom get rejected. Regular messages don't have
this problem because they go through evaluateTelegramGroupPolicyAccess
which correctly uses effectiveGroupAllow.

Add effectiveGroupAllow as a second authorizer when the message comes
from a group. resolveCommandAuthorizedFromAuthorizers uses .some(), so
either DM or group allowlist matching is sufficient.

Fixes #28216
Fixes #29135
Fixes #30234

* fix(test): resolve TS2769 type errors in group-auth test

Remove explicit tuple type annotations on mock.calls.filter() callbacks
that conflicted with vitest's mock call types.

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

* test(telegram): cover topic auth rejection routing

* changelog: note telegram native group command auth fix

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-07 16:47:57 -08:00
Vincent Koc 0d66834f94 Daemon: scope relaxed systemd probes to install flows 2026-03-07 16:45:18 -08:00
Vincent Koc fcb990e369 Node Host: allowlist password precedence labels 2026-03-07 16:43:22 -08:00
Vincent Koc ac02529844 Gateway Auth: allowlist connection auth precedence fixtures 2026-03-07 16:43:22 -08:00
Vincent Koc 83290c5cef Discord: format exec approval tests 2026-03-07 16:43:22 -08:00
Vincent Koc 60441c8ced Systemd: allowlist environment file fixtures 2026-03-07 16:43:21 -08:00
Vincent Koc a56841b98c
Daemon: harden WSL2 systemctl install checks (#39294)
* Daemon: harden WSL2 systemctl install checks

* Changelog: note WSL2 daemon install hardening

* Daemon: tighten systemctl failure classification
2026-03-07 16:43:19 -08:00
Peter Steinberger f195af0b22 fix(sandbox): anchor fs-bridge destructive ops 2026-03-08 00:41:12 +00:00
Peter Steinberger 9d2b292998 fix(exec-approvals): honor allow-always for bash script invocations
Landed from contributor PR #35137 by @yuweuii.

Co-authored-by: yuweuii <82372187+yuweuii@users.noreply.github.com>
2026-03-08 00:39:54 +00:00
Vincent Koc ca37a4e82e changelog: note telegram groupAllowFrom sender validation fix 2026-03-07 16:36:16 -08:00
Peter Steinberger c6575891c7 fix(exec): inherit ask from exec-approvals.json when tools.exec.ask unset
Landed from contributor PR #29187 by @Bartok9.

Co-authored-by: Bartok9 <259807879+Bartok9@users.noreply.github.com>
2026-03-08 00:35:50 +00:00
Vincent Koc 240b143bde test(telegram): cover sender-only groupAllowFrom normalization 2026-03-07 16:34:42 -08:00
Vincent Koc 13ed6afe60 telegram: restore sender-only allowFrom validation 2026-03-07 16:34:21 -08:00
Peter Steinberger 173132165d fix(exec): honor exec-approvals ask=off for gateway/node runs
Landed from contributor PR #26789 by @pandego.

Co-authored-by: Miguel Miranda Dias <7780875+pandego@users.noreply.github.com>
2026-03-08 00:29:34 +00:00
Peter Steinberger 79e3d1f956 fix: retry git lock in committer 2026-03-08 00:28:37 +00:00
Josh Avant 25252ab5ab
gateway: harden shared auth resolution across systemd, discord, and node host 2026-03-07 18:28:32 -06:00
Martin-Max a7f6e0a921
fix(telegram): support negative IDs in groupAllowFrom (#36753) (#37134)
* fix(telegram): support negative IDs in groupAllowFrom for group/channel whitelist (#36753)

When configuring Telegram group restrictions with groupAllowFrom,
negative group/channel IDs (e.g., -1001234567890) are rejected with
'authorization requires numeric Telegram sender IDs only' error,
even though the field name suggests it should accept group IDs.

Root cause:
- normalizeAllowFrom() uses regex /^\d+$/ to validate IDs
- Telegram group/channel IDs are negative integers
- Regex only matches positive integers, rejecting all group IDs

Impact:
- Users cannot whitelist specific groups using groupAllowFrom
- Workaround requires groupPolicy: "open" (security risk)
- Field name is misleading (suggests group IDs, but only accepts user IDs)

Fix:
- Change regex from /^\d+$/ to /^-?\d+$/ (support optional minus sign)
- Apply to both invalidEntries filter and ids filter
- Add comment explaining negative ID support for groups/channels

Testing:
- Positive user IDs (745123456) →  still work
- Negative group IDs (-1001234567890) →  now accepted
- Invalid entries (@username) → ⚠️  still warned

Fixes #36753

* test(telegram): add signed ID runtime regression

---------

Co-authored-by: Martin Qiu <qiuyuemartin@gmail.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-07 19:27:25 -05:00
Vincent Koc 73e510cdf4 Gateway UI: allowlist device key fixtures 2026-03-07 16:27:00 -08:00
Vincent Koc 27b6b0c119 Gateway Secrets: allowlist unresolved secret ref label 2026-03-07 16:27:00 -08:00
Vincent Koc a7c605ec4a Gateway Credentials: allowlist precedence fixtures 2026-03-07 16:27:00 -08:00
Vincent Koc ace64831e0 Gateway Credentials: allowlist password fixtures 2026-03-07 16:27:00 -08:00
Vincent Koc 889a60e122 Gateway Auth: allowlist bootstrap password references 2026-03-07 16:27:00 -08:00
Vincent Koc 475b0cb49a Docker Setup: allowlist dotenv token fixtures 2026-03-07 16:27:00 -08:00
Vincent Koc d83f2c145a Zalo User: use scoped plugin SDK imports 2026-03-07 16:27:00 -08:00
Vincent Koc 5b0fa341fb Zalo: use scoped plugin SDK imports 2026-03-07 16:27:00 -08:00
Vincent Koc b46ac250d1 WhatsApp: use scoped plugin SDK imports 2026-03-07 16:27:00 -08:00
Vincent Koc e9cf3506fd Telegram: use scoped plugin SDK imports 2026-03-07 16:27:00 -08:00
Vincent Koc d899990b44 Slack: use scoped plugin SDK imports 2026-03-07 16:27:00 -08:00
Vincent Koc 4bcef8631c Signal: use scoped plugin SDK imports 2026-03-07 16:27:00 -08:00
Vincent Koc c7c5c0edaa Nextcloud Talk: use scoped plugin SDK imports 2026-03-07 16:26:59 -08:00
Vincent Koc 6035677545 Teams: use scoped plugin SDK allowlist imports 2026-03-07 16:26:59 -08:00
Vincent Koc 6b2adf663e Teams: use scoped plugin SDK channel imports 2026-03-07 16:26:59 -08:00
Vincent Koc 4cc619f06c Mattermost: use scoped plugin SDK imports 2026-03-07 16:26:59 -08:00
Vincent Koc 4b0d55dadf Matrix: use scoped plugin SDK resolve-target imports 2026-03-07 16:26:59 -08:00
Vincent Koc 4b02a4eacf Matrix: use scoped plugin SDK channel imports 2026-03-07 16:26:59 -08:00
Vincent Koc 43fd45f038 LINE: use scoped plugin SDK imports 2026-03-07 16:26:59 -08:00
Vincent Koc 7980dc59e3 IRC: use scoped plugin SDK imports 2026-03-07 16:26:59 -08:00
Vincent Koc 4cd81b0c7b iMessage: use scoped plugin SDK imports 2026-03-07 16:26:59 -08:00
Vincent Koc 566f30828d Google Chat: use scoped plugin SDK imports 2026-03-07 16:26:59 -08:00
Vincent Koc 1b034f08e0 Feishu: scope plugin SDK directory imports 2026-03-07 16:26:59 -08:00
Vincent Koc 2a5158295e Feishu: scope plugin SDK channel imports 2026-03-07 16:26:59 -08:00
Vincent Koc e47b63acaa Discord: use scoped plugin SDK imports 2026-03-07 16:26:59 -08:00
Vincent Koc 8f40b132f9 BlueBubbles: use scoped plugin SDK imports 2026-03-07 16:26:59 -08:00
Peter Steinberger 9856d8432d chore(scripts): remove changelog fragment workflow helpers 2026-03-08 00:24:49 +00:00
Peter Steinberger efdff9c738 fix(scripts): enforce changelog.md and post clickable SHA links 2026-03-08 00:23:45 +00:00
Peter Steinberger eed403dc74 refactor(agents): unify spawned metadata and extract attachments service 2026-03-08 00:23:45 +00:00
Peter Steinberger 61000b8e4d fix(acp): block sandboxed slash spawns 2026-03-08 00:23:07 +00:00
Peter Steinberger bda035768f fix(plugins): fall back to src plugin-sdk aliases 2026-03-08 00:18:45 +00:00
Peter Steinberger 4e07bdbdfd fix(cron): restore isolated delivery defaults 2026-03-08 00:18:45 +00:00
Peter Steinberger 8a469a12b2 test(exec): dedupe wrapper boundary regressions 2026-03-08 00:12:08 +00:00
Peter Steinberger 5f50823abf refactor(exec): share wrapper depth classification 2026-03-08 00:12:08 +00:00
Vincent Koc 168c65aa26 Allowlists: type test runtime mocks 2026-03-07 16:09:57 -08:00
Vincent Koc 3b1be1a08c Plugin SDK: align allowFrom helper test input 2026-03-07 16:08:39 -08:00
Vincent Koc d15a3d3454 Telegram: coerce route allowlist warning flag 2026-03-07 16:08:38 -08:00
Vincent Koc 9d3469c914 Nextcloud Talk: coerce route allowlist warning flag 2026-03-07 16:08:17 -08:00
Vincent Koc a4ffebbef4 Mattermost: default unknown media kind 2026-03-07 16:07:41 -08:00
Vincent Koc 7c5d6c3dc9 Matrix: default missing media kind to unknown 2026-03-07 16:07:41 -08:00
Vincent Koc 3800f6700a Feishu: narrow directory entry types 2026-03-07 16:07:41 -08:00
Peter Steinberger 990fc36cbd refactor: share sampled entry summary formatting 2026-03-08 00:05:24 +00:00
Peter Steinberger cc03c097c5 refactor: share provider group-policy warning collectors 2026-03-08 00:05:24 +00:00
Peter Steinberger 566a821e5d refactor: share missing-sender matched allowlist evaluation 2026-03-08 00:05:24 +00:00
Peter Steinberger 2b54070526 refactor: share allowlist provider warning resolution 2026-03-08 00:05:24 +00:00
Peter Steinberger 846ec320e2 refactor: share account-scoped config adapter accessors 2026-03-08 00:05:24 +00:00
Peter Steinberger b6318d4df4 fix: narrow dm shared group policy typing 2026-03-08 00:05:24 +00:00
Peter Steinberger b0d9246768 refactor: share matched group policy evaluation 2026-03-08 00:05:24 +00:00
Peter Steinberger f319ec2dac refactor: share onboarding allowlist entry parsing 2026-03-08 00:05:24 +00:00
Vincent Koc 766d76ef9a Wizard: type-safe onboarding install plan assertions 2026-03-07 16:02:37 -08:00
Vincent Koc 029fdd4208 Daemon CLI: type-safe install plan assertions 2026-03-07 16:02:27 -08:00
Vincent Koc c5fb661742 Daemon CLI: resolve token drift from gateway credentials 2026-03-07 16:02:18 -08:00
Vincent Koc 936f0a7f22 Update gateway-status.test.ts 2026-03-07 15:59:11 -08:00
Vincent Koc 3ae61d57a3 Gateway Status: allowlist missing token test fixture 2026-03-07 15:58:01 -08:00
Vincent Koc 81140a778b Secrets: refresh baseline line numbers 2026-03-07 15:58:01 -08:00
Vincent Koc d5803cc4ee CI: remove Knip dead-code report job 2026-03-07 15:58:01 -08:00
Vincent Koc 07cccfc926 CI: drop duplicate strict smoke build check 2026-03-07 15:58:01 -08:00
Peter Steinberger ab54532c8f fix(agents): land #39247 from @jasonQin6 (subagent workspace inheritance)
Propagate parent workspace directories into spawned subagent runs, keep workspace override internal-only, and add regression tests for forwarding boundaries.

Co-authored-by: jasonQin6 <991262382@qq.com>
2026-03-07 23:56:37 +00:00
Peter Steinberger eeba93d63d fix(discord): pass gateway auth to exec approvals
Pass resolved gateway token/password into the Discord exec approvals GatewayClient startup path so token-auth installs stop failing approvals with gateway token mismatch.

Fixes #38179
Adjacent investigation: #35147 by @0riginal-claw
Co-authored-by: 0riginal-claw <0rginal_claw@0rginal-claws-Mac-mini.local>
2026-03-07 23:47:48 +00:00
Peter Steinberger f304ca09b1 fix(agents): sanitize strict openai-compatible turn ordering from #39252 (thanks @scoootscooob)
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-03-07 23:42:19 +00:00
Peter Steinberger ada4ee08d9 fix(docker): land #33097 from @chengzhichao-xydt
Landed from contributor PR #33097 by @chengzhichao-xydt.

Co-authored-by: Zhichao Cheng <cheng.zhichao@xydigit.com>
2026-03-07 23:41:57 +00:00
Peter Steinberger 2fc95a7cfc fix(exec): close dispatch-wrapper boundary drift 2026-03-07 23:40:38 +00:00
Peter Steinberger adf4eb487b fix(signal): forward all inbound attachments from #39212 (thanks @joeykrug)
Co-authored-by: Joey Krug <joeykrug@gmail.com>
2026-03-07 23:35:55 +00:00
Peter Steinberger 939b18475d fix(exec): honor shell comments in allow-always analysis 2026-03-07 23:31:25 +00:00
Peter Steinberger 1aaca517e3 fix(media): harden unknown mime handling from #39199 (thanks @nicolasgrasset)
Co-authored-by: Nicolas Grasset <nicolas.grasset@gmail.com>
2026-03-07 23:30:32 +00:00
Peter Steinberger dc92f2e19d refactor: share nextcloud onboarding allowFrom lookup 2026-03-07 23:27:51 +00:00
Peter Steinberger 4956271da1 refactor: share provider allowlist input normalization 2026-03-07 23:27:51 +00:00
Peter Steinberger c9128e1f3f refactor: share trimmed list normalization in provider helpers 2026-03-07 23:27:51 +00:00
Peter Steinberger c5bd84309a refactor: share allowFrom stringification helpers 2026-03-07 23:27:51 +00:00
Peter Steinberger 99d14a820a refactor: share route policy evaluation in chat monitors 2026-03-07 23:27:51 +00:00
Peter Steinberger 8c15b8600c refactor: share sender group policy evaluation 2026-03-07 23:27:51 +00:00
Peter Steinberger d228a62143 refactor: share trimmed string entry normalization 2026-03-07 23:27:51 +00:00
Peter Steinberger 6647d02846 refactor: share dock config adapter helper scaffolding 2026-03-07 23:27:51 +00:00
Peter Steinberger 556aa8a702 refactor: share config adapter allowFrom and defaultTo helpers 2026-03-07 23:27:51 +00:00
Peter Steinberger feac26c3b7 refactor: share allowFrom formatter scaffolding 2026-03-07 23:27:51 +00:00
Peter Steinberger c91bfa830a refactor: share route-level group gating decisions 2026-03-07 23:27:51 +00:00
Peter Steinberger 5bbca5be91 refactor: share sender-scoped group policy derivation 2026-03-07 23:27:51 +00:00
Peter Steinberger 621063a956 style: format plugin helper tests 2026-03-07 23:27:51 +00:00
Peter Steinberger b7d03ea1f5 refactor: centralize open group-policy warning flow collectors 2026-03-07 23:27:51 +00:00
Peter Steinberger b456649974 refactor: unify account-scoped dm security policy resolver 2026-03-07 23:27:51 +00:00
Peter Steinberger 7230b96cc7 refactor: unify extension allowlist resolver and directory scaffolding 2026-03-07 23:27:51 +00:00
Peter Steinberger 8e0e76697a refactor: unify channel open-group-policy warning builders 2026-03-07 23:27:51 +00:00
Peter Steinberger 4b61779a46 refactor: unify extension webhook request lifecycle scaffolding 2026-03-07 23:27:51 +00:00
Peter Steinberger 27dad962fe refactor: normalize runtime group sender gating decisions 2026-03-07 23:27:51 +00:00
Peter Steinberger 5eba663c38 refactor: unify onboarding secret-input prompt state wiring 2026-03-07 23:27:51 +00:00
Peter Steinberger 6b1c82c4f1 refactor: unify onboarding dm/group policy scaffolding 2026-03-07 23:27:51 +00:00
Peter Steinberger fecca6fd8d refactor: unify gateway SecretRef auth resolution paths 2026-03-07 23:27:50 +00:00
Peter Steinberger 5f26970200 fix(ui): land #28608 from @KimGLee
Landed from contributor PR #28608 by @KimGLee.

Co-authored-by: Kim <150593189+KimGLee@users.noreply.github.com>
2026-03-07 23:26:09 +00:00
Peter Steinberger 1d1757b16f fix(exec): recognize PowerShell encoded commands 2026-03-07 23:15:46 +00:00
Peter Steinberger 5b27b0cecf refactor(outbound,agents): extract shared payload and queue helpers 2026-03-07 23:07:16 +00:00
Peter Steinberger 7ab49a7fb7 test(regression): cover recent landed fix paths 2026-03-07 23:07:16 +00:00
Peter Steinberger c76d29208b fix(node-host): bind approved script operands 2026-03-07 23:04:00 +00:00
Altay bfbe80ab7d
test(ui): reduce gateway client test mocking (#39251) 2026-03-08 01:58:44 +03:00
Peter Steinberger 708187f28c fix(outbound): prevent replay after ack crash windows (#38668, thanks @Gundam98)
Co-authored-by: Gundam98 <huhanwen98@gmail.com>
2026-03-07 22:53:27 +00:00
Peter Steinberger 3ca023bf44 chore(test): normalize install assertion formatting 2026-03-07 22:51:08 +00:00
Peter Steinberger 265367d99b fix(gateway): land #28428 from @l0cka
Landed from contributor PR #28428 by @l0cka.

Co-authored-by: Daniel Alkurdi <danielalkurdi@gmail.com>
2026-03-07 22:51:08 +00:00
Peter Steinberger e83094e63f fix(agents): warn clearly on unresolved model ids (#39215, thanks @ademczuk)
Co-authored-by: ademczuk <andrew.demczuk@gmail.com>
2026-03-07 22:50:27 +00:00
Peter Steinberger 3a761fbcf8 fix(agents): strip unsupported responses store payloads (#39219, thanks @ademczuk)
Co-authored-by: ademczuk <andrew.demczuk@gmail.com>
2026-03-07 22:47:41 +00:00
Peter Steinberger ab704b7aca fix(gateway): explain provider-object password bootstrap errors (#39230, thanks @ademczuk)
Co-authored-by: ademczuk <andrew.demczuk@gmail.com>
2026-03-07 22:44:44 +00:00
Peter Steinberger e45d62ba26 fix(memory): preserve BM25 relevance ordering (#33757, thanks @lsdcc01)
Land #33757 by @lsdcc01 without the unrelated dependency bump. Preserve negative FTS5 BM25 ordering in hybrid scoring and add changelog coverage for #5767.

Co-authored-by: 丁春才0668000523 <ding.chuncai1@xydigit.com>
2026-03-07 22:41:48 +00:00
Peter Steinberger 99de6515a0 fix(telegram): surface fallback on dispatch failures (#39209, thanks @riftzen-bit)
Co-authored-by: riftzen-bit <binb53339@gmail.com>
2026-03-07 22:41:09 +00:00
Peter Steinberger f53e10e3fd fix(config): fail closed on invalid config load (#9040, thanks @joetomasone)
Land #9040 by @joetomasone. Add fail-closed config loading, compat coverage, and changelog entry for #5052.

Co-authored-by: Joe Tomasone <joe@tomasone.com>
2026-03-07 22:39:26 +00:00
Peter Steinberger 3a74dc00bf fix(gateway): land #38725 from @ademczuk
Source: #38725 / 533ff3e70b by @ademczuk.
Thanks @ademczuk.

Co-authored-by: ademczuk <andrew.demczuk@gmail.com>
2026-03-07 22:35:38 +00:00
Peter Steinberger 8ca326caa9 fix(ui): land #37382 from @FradSer
Separate shared gateway auth from cached device-token signing in Control UI browser auth. Preserves shared-token validation while keeping cached device tokens scoped to signed device payloads.

Co-authored-by: Frad LEE <fradser@gmail.com>
2026-03-07 22:33:24 +00:00
Peter Steinberger b4bac484e3 fix(gateway): stop webchat route inheritance on channel sessions (#39175, thanks @widingmarcus-cyber)
Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
2026-03-07 22:22:23 +00:00
Peter Steinberger 3a2fdc5136 fix(memory): restore sqlite busy_timeout on reopen (#39183, thanks @MumuTW)
Co-authored-by: MumuTW <clothl47364@gmail.com>
2026-03-07 22:17:55 +00:00
Peter Steinberger 733f7af92b fix(heartbeat): keep requests-in-flight retries from drifting schedule (#39182, thanks @MumuTW)
Co-authored-by: MumuTW <clothl47364@gmail.com>
2026-03-07 22:10:51 +00:00
Peter Steinberger 42bf4998d3 fix(telegram): reset webhook cleanup latch after polling 409 conflicts (#39205, thanks @amittell)
Co-authored-by: amittell <mittell@me.com>
2026-03-07 22:08:41 +00:00
Peter Steinberger c934dd51c0 fix(daemon): normalize schtasks runtime from numeric result only (#39153, thanks @scoootscooob)
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-03-07 22:06:20 +00:00
Peter Steinberger be9ea991de fix(discord): avoid native plugin command collisions 2026-03-07 21:59:44 +00:00
Peter Steinberger 4dcd930923 fix(test): strip windows drive prefix from darwin hints 2026-03-07 21:46:34 +00:00
Peter Steinberger eb616b709f fix(test): normalize darwin runtime hint paths 2026-03-07 21:40:52 +00:00
Peter Steinberger e20f445099 fix(supervisor): keep service-managed children attached (#38463, thanks @spirittechie)
Co-authored-by: Jesse Paul <drzin69@gmail.com>
2026-03-07 21:36:24 +00:00
Peter Steinberger cc7e61612a fix(gateway): harden service-mode stale process cleanup (#38463, thanks @spirittechie)
Co-authored-by: Jesse Paul <drzin69@gmail.com>
2026-03-07 21:36:24 +00:00
Peter Steinberger 1835d5808f fix(test): align feishu pairing assertion 2026-03-07 21:36:04 +00:00
Peter Steinberger 6181fe22c7 fix(ci): refresh detect-secrets allowlists and baseline 2026-03-07 21:30:04 +00:00
Peter Steinberger a617cd7b79 fix(test): restore long dep for full vitest gate 2026-03-07 21:23:06 +00:00
Peter Steinberger e3c21c913d fix(ci): refresh secret baseline and UI state types 2026-03-07 21:17:57 +00:00
Peter Steinberger b9dd6e99b6 fix(daemon): avoid freezing Windows PATH in task scripts (#39139, thanks @Narcooo)
Co-authored-by: majx_mac <mjxnarco@pku.edu.cn>
2026-03-07 21:15:01 +00:00
Peter Steinberger f51cac277c fix(discord): make message listener non-blocking (#39154, thanks @yaseenkadlemakki)
Co-authored-by: Yaseen Kadlemakki <yaseen82@gmail.com>
2026-03-07 21:13:47 +00:00
Peter Steinberger 7649712356 fix(config): degrade gracefully on missing env vars (#39050, thanks @akz142857)
Co-authored-by: ziy <ziyang.liu@wahool.com>
2026-03-07 21:12:26 +00:00
Peter Steinberger 92f5a2e252 fix(models): refresh gpt/gemini alias defaults (#38638, thanks @ademczuk)
Co-authored-by: ademczuk <andrew.demczuk@gmail.com>
2026-03-07 21:10:58 +00:00
Peter Steinberger a3db68f9ab fix(telegram): guard null persisted update id normalization 2026-03-07 21:10:58 +00:00
Peter Steinberger c35f529fac refactor: share daemon install plan runtime scaffolding 2026-03-07 21:09:27 +00:00
Peter Steinberger dfe8cd028e refactor: share discord allowlist resolver scaffolding 2026-03-07 21:09:27 +00:00
Peter Steinberger 804d989b29 refactor: share slack allowlist resolver scaffolding 2026-03-07 21:09:27 +00:00
Peter Steinberger b955ba1688 refactor: consolidate daemon runtime and start hints 2026-03-07 21:09:26 +00:00
Peter Steinberger a91731a831 refactor: centralize gateway auth env credential readers 2026-03-07 21:09:26 +00:00
Peter Steinberger f0b05869fc refactor: share onboarding account id resolution prelude 2026-03-07 21:09:26 +00:00
Peter Steinberger 168e4159ad fix(podman): honor OPENCLAW_GATEWAY_BIND env-file override (#38785, thanks @majinyu666)
Co-authored-by: majinyu666 <majy14miles@gmail.com>
2026-03-07 21:08:15 +00:00
Peter Steinberger c0c2f82147 docs(agents): require clickable commit SHAs in PR landing comments 2026-03-07 21:07:40 +00:00
Peter Steinberger f2a92e7c84 fix(agents): forward websocket maxTokens=0 correctly
Landed from #39148 by @scoootscooob.

Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-03-07 20:51:26 +00:00
Peter Steinberger 330579ef96 fix(telegram): resolve status SecretRefs with provider-safe env checks
Landed from #39130 by @neocody.

Co-authored-by: Cody <25426121+neocody@users.noreply.github.com>
2026-03-07 20:50:07 +00:00
Peter Steinberger 2015ab3194 fix(telegram): harden persisted offset confirmation and stall recovery
Landed from #39111 by @MumuTW.

Co-authored-by: MumuTW <clothl47364@gmail.com>
2026-03-07 20:47:33 +00:00
Peter Steinberger 9b4a114eb6 fix(browser): keep dispatcher context with no-retry hints
Landed from #39090 by @NewdlDewdl.

Co-authored-by: NewdlDewdl <rohin.agrawal@gmail.com>
2026-03-07 20:45:06 +00:00
Peter Steinberger f17f2f918c fix(gateway): order bootstrap cache clear after embedded run wait
Landed from #38873 by @MumuTW.

Co-authored-by: MumuTW <clothl47364@gmail.com>
2026-03-07 20:42:33 +00:00
Peter Steinberger 3ec81709d7 refactor: unify shared utility normalization helpers 2026-03-07 20:33:50 +00:00
Peter Steinberger 30d091b2fb refactor: share thread binding id parser 2026-03-07 20:33:50 +00:00
Peter Steinberger 95fe282a17 refactor: unify channel status snapshot base fields 2026-03-07 20:33:50 +00:00
Peter Steinberger b9e7521463 refactor: unify directory config entry extraction 2026-03-07 20:33:50 +00:00
Peter Steinberger b0ac284dae refactor: share setup account config patch helper 2026-03-07 20:33:50 +00:00
Peter Steinberger 2ee8b807f8 refactor: dedupe discord account inspect config merge 2026-03-07 20:33:50 +00:00
Peter Steinberger 7242777d63 refactor: unify account list/default scaffolding 2026-03-07 20:33:50 +00:00
Peter Steinberger 2bcd56cfac refactor: unify DM pairing challenge flows 2026-03-07 20:33:50 +00:00
Tars dab0e97c22
fix(models): support minimax-portal coding plan vlm routing for image tool (openclaw#33953)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: tars90percent <252094836+tars90percent@users.noreply.github.com>
2026-03-07 14:30:53 -06:00
Tyler Yust e554c59aac
fix(cron): eliminate double-announce and replace delivery polling with push-based flow (#39089)
* fix(cron): eliminate double-announce and replace delivery polling with push-based flow

- Set deliveryAttempted=true in announce early-return paths (active-subagent
  suppression and stale-interim suppression) so the heartbeat timer no longer
  fires a redundant enqueueSystemEvent fallback (double-announce bug).

- Refactor waitForDescendantSubagentSummary to use event-based agent.wait RPC
  calls instead of a 500ms busy-poll loop.  Each active descendant run is now
  awaited concurrently via Promise.allSettled, and only a short bounded grace
  period (5s) remains to capture the cron agent's post-orchestration synthesis.
  Eliminates O(n*timeoutMs/500ms) gateway calls and wasted wall-clock time.

- Add FAST_TEST_MODE (OPENCLAW_TEST_FAST=1) to subagent-followup.ts to keep
  the grace-period tests instant in CI.

- Add comprehensive tests for the new waitForDescendantSubagentSummary behaviour
  (push-based wait, error resilience, NO_REPLY handling, multi-descendant waits).

* fix: prep cron double-announce followup tests (#39089) (thanks @tyler6204)
2026-03-07 12:13:37 -08:00
Altay 97f9e25525
fix(ci): restore strip-ansi and typecheck fixtures (#39146)
* fix: restore strip-ansi and typecheck fixtures

* test: normalize windows install path assertions
2026-03-07 23:13:13 +03:00
Yi-Cheng Wang 4682f3cace
Fix/Complete LINE `requireMention` gating behavior (#35847)
* fix(line): enforce requireMention gating in group message handler

* fix(line): scope canDetectMention to text messages, pass hasAnyMention

* fix(line): fix TS errors in mentionees type and test casts

* feat(line): register LINE in DOCKS and CHAT_CHANNEL_ORDER

- Add "line" to CHAT_CHANNEL_ORDER and CHAT_CHANNEL_META in registry.ts
- Export resolveLineGroupRequireMention and resolveLineGroupToolPolicy
  in group-mentions.ts using the generic resolveChannelGroupRequireMention
  and resolveChannelGroupToolsPolicy helpers (same pattern as iMessage)
- Add "line" entry to DOCKS in dock.ts so resolveGroupRequireMention
  in the reply stage can correctly read LINE group config

Fixes the third layer of the requireMention bug: previously
getChannelDock("line") returned undefined, causing the reply-stage
resolveGroupRequireMention to fall back to true unconditionally.

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

* fix(line): pending history, requireMention default, mentionPatterns fallback

- Default requireMention to true (consistent with other channels)
- Add mentionPatterns regex fallback alongside native isSelf/@all detection
- Record unmentioned group messages via recordPendingHistoryEntryIfEnabled
- Inject pending history context in buildLineMessageContext when bot is mentioned

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

* test(line): update tests for requireMention default and pending history

- Add requireMention: false to 6 group tests unrelated to mention gating
  (allowlist, replay dedup, inflight dedup, error retry) to preserve
  their original intent after the default changed from false to true
- Add test: skips group messages by default when requireMention not configured
- Add test: records unmentioned group messages as pending history

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

* fix(line): use undefined instead of empty string as historyKey sentinel

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

* fix(line): deliver pending history via InboundHistory, not Body mutation

- Remove post-hoc ctxPayload.Body injection (BodyForAgent takes priority
  in the prompt pipeline, so Body was never reached)
- Pass InboundHistory array to finalizeInboundContext instead, matching
  the Telegram pattern rendered by buildInboundUserContextPrefix

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

* fix(line): pass agentId to buildMentionRegexes for per-agent mentionPatterns

- Resolve route before mention gating to obtain agentId
- Pass agentId to buildMentionRegexes, matching Telegram behavior

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

* fix(line): clear pending history after handled group turn

- Call clearHistoryEntriesIfEnabled after processMessage for group messages
- Prevents stale skipped messages from replaying on subsequent mentions
- Matches Discord, Signal, Slack, iMessage behavior

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

* style(line): fix import order and merge orphaned JSDoc in bot-handlers

- Move resolveAgentRoute import from ./local group to ../routing group
- Merge duplicate JSDoc blocks above getLineMentionees into one

Addresses Greptile review comments r2888826724 and r2888826840 on PR #35847.

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

* fix(line): read historyLimit from config and guard clear with has()

- bot.ts: resolve historyLimit from cfg.messages.groupChat.historyLimit
  with fallback to DEFAULT_GROUP_HISTORY_LIMIT, so setting historyLimit: 0
  actually disables pending history accumulation
- bot-handlers.ts: add groupHistories.has(historyKey) guard before
  clearHistoryEntriesIfEnabled to prevent writing empty buckets for
  groups that have never accumulated pending history (memory leak)

Addresses Codex review comments r2888829146 and r2888829152 on PR #35847.

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

* style(line): apply oxfmt formatting to bot-handlers and bot

Auto-formatted by oxfmt to fix CI format:check failure on PR #35847.

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

* fix(line): add shouldLogVerbose to globals mock in bot-handlers test

resolveAgentRoute calls shouldLogVerbose() from globals.js; the mock
was missing this export, causing 13 test failures.

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

* Address review findings for #35847

---------

Co-authored-by: Kaiyi <me@kaiyi.cool>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Yi-Cheng Wang <yicheng.wang@heph-ai.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-07 14:06:07 -06:00
Peter Steinberger d6f28a3da7 fix(usage): format near-million token counts as millions (#39129)
Co-authored-by: CurryMessi <curry-messi@users.noreply.github.com>
2026-03-07 19:59:12 +00:00
Peter Steinberger 80a6eb3131 fix(daemon): use locale-invariant schtasks running code detection (#39076)
Co-authored-by: ademczuk <andrew.demczuk@gmail.com>
2026-03-07 19:56:47 +00:00
Peter Steinberger 3c1176110a fix(agents): avoid double websocket retry accounting on reconnect failures (#39133)
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-03-07 19:55:28 +00:00
Peter Steinberger ac86deccee fix(gateway): harden plugin HTTP route auth 2026-03-07 19:55:06 +00:00
Peter Steinberger cf290e31bd fix(voice-call): align plugin manifest schema with runtime config fields (#38892)
Co-authored-by: giumex <giuliano.messina@gmail.com>
2026-03-07 19:49:58 +00:00
Peter Steinberger 43b36bfe8c fix(gateway): flush chat delta before tool-start events (#39128)
Co-authored-by: john <john.j@min123.net>
2026-03-07 19:46:04 +00:00
Peter Steinberger e4497234c7 fix(agents): increment compaction counter on overflow-triggered compaction (#39123)
Co-authored-by: MumuTW <clothl47364@gmail.com>
2026-03-07 19:44:06 +00:00
Peter Steinberger 4c2cb73055 fix(config): sanitize validation log output to prevent control character injection (#39116)
Co-authored-by: Bill <gsamzn@gmail.com>
2026-03-07 19:41:59 +00:00
Peter Steinberger 0e4603ac71 fix(agents): respect compat.supportsStore in WebSocket stream path (#39113)
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-03-07 19:40:34 +00:00
Peter Steinberger 5f8f58ae25 fix(gateway): require admin for chat config writes 2026-03-07 19:38:49 +00:00
Peter Steinberger 724d2d58fa fix(discord): avoid false model picker mismatch warning (#39105)
Land #39105 by @akropp.

Co-authored-by: Adam Kropp <adam@thekropp.com>
2026-03-07 19:32:35 +00:00
Peter Steinberger 17ab46aedd fix(models): prevent plaintext apiKey writes to models state (#38889)
Land #38889 by @gambletan.

Co-authored-by: gambletan <ethanchang32@gmail.com>
2026-03-07 19:29:46 +00:00
Peter Steinberger de2ccffec1 fix(ui): stream tool events live in control chat (#39104)
Land #39104 by @jakepresent.

Co-authored-by: Jake Present <jakepresent@microsoft.com>
2026-03-07 19:27:17 +00:00
Sally O'Malley 499c1ee6e3
reduce image size, offer slim image (#38479)
Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 14:26:29 -05:00
Peter Steinberger d72734946a fix(security): harden install base drift cleanup 2026-03-07 19:23:01 +00:00
Peter Steinberger c06014d50c fix(agents): respect explicit provider baseUrl in merge mode (#39103)
Land #39103 by @BigUncle.

Co-authored-by: BigUncle <biguncle2017@gmail.com>
2026-03-07 19:22:21 +00:00
Peter Steinberger 537c97cce9 fix(agents): apply contextTokens cap for compaction threshold (#39099)
Land #39099 by @MumuTW.

Co-authored-by: MumuTW <clothl47364@gmail.com>
2026-03-07 19:19:03 +00:00
Peter Steinberger e27bbe4982 fix(exec): block dangerous override-only env pivots 2026-03-07 19:18:05 +00:00
Peter Steinberger 6aa80844b8 fix(security): stage installs before publish 2026-03-07 19:11:07 +00:00
ademczuk 70be8ce15c
fix(daemon): normalise whitespace in checkTokenDrift to prevent false-positive warning (#39108) 2026-03-07 14:10:54 -05:00
Peter Steinberger 74ecdec9ba fix(security): harden fs-safe copy writes 2026-03-07 19:10:27 +00:00
Peter Steinberger 6bfae2714f refactor: dedupe bluebubbles webhook auth test setup 2026-03-07 19:02:01 +00:00
Peter Steinberger acf3ff91e4 refactor: dedupe discord native command test scaffolding 2026-03-07 19:02:01 +00:00
Peter Steinberger 0848a47c97 refactor: dedupe anthropic probe target test setup 2026-03-07 19:02:01 +00:00
Peter Steinberger 8928aba7ee refactor: dedupe minimax provider auth test setup 2026-03-07 19:02:01 +00:00
Peter Steinberger 143eca8e86 refactor: dedupe runtime snapshot test fixtures 2026-03-07 19:02:01 +00:00
Peter Steinberger 31acad4e8f fix: harden zip extraction writes 2026-03-07 19:01:35 +00:00
Peter Steinberger 0f53177971 fix(tests): stabilize diffs localReq headers (supersedes #39063)
Co-authored-by: Shennng <Shennng@users.noreply.github.com>
2026-03-07 18:57:35 +00:00
Peter Steinberger 253e159700 fix: harden workspace skill path containment 2026-03-07 18:56:15 +00:00
Peter Steinberger 5effa6043e fix(agents): land #38935 from @MumuTW
Co-authored-by: MumuTW <MumuTW@users.noreply.github.com>
2026-03-07 18:55:49 +00:00
Peter Steinberger 231c1fa37a fix(models): land #38947 from @davidemanuelDEV
Co-authored-by: davidemanuelDEV <davidemanuelDEV@users.noreply.github.com>
2026-03-07 18:54:12 +00:00
Peter Steinberger 2f59a3cff3 fix(gateway): land #39064 from @Narcooo
Co-authored-by: Narcooo <Narcooo@users.noreply.github.com>
2026-03-07 18:52:42 +00:00
Peter Steinberger 2ada1b71b6 fix(models-auth): land #38951 from @MumuTW
Co-authored-by: MumuTW <MumuTW@users.noreply.github.com>
2026-03-07 18:51:17 +00:00
Peter Steinberger 02f99c0ff3 docs: clarify agent owner trust defaults 2026-03-07 18:48:27 +00:00
Peter Steinberger 729ee165ed docs(gateway): clarify trusted operator HTTP endpoints 2026-03-07 18:48:17 +00:00
Peter Steinberger 8bd0eb5424 fix(outbound): land #38944 from @Narcooo
Co-authored-by: Narcooo <Narcooo@users.noreply.github.com>
2026-03-07 18:46:48 +00:00
Tak Hoffman 52e7d4295e
fix(gateway): clear stale Slack socket state after disconnect (#39083)
* fix(gateway): restore stale-socket recovery

* test(slack): cover clean socket disconnect status
2026-03-07 12:37:32 -06:00
Peter Steinberger fbb9bb08c5 style(test): format gateway auth token coverage 2026-03-07 18:33:30 +00:00
Peter Steinberger 10d0e3f3ca fix(dashboard): keep gateway tokens out of URL storage 2026-03-07 18:33:30 +00:00
Vincent Koc f966dde476
tests: fix detect-secrets false positives (#39084)
* Tests: rename gateway status env token fixture

* Tests: allowlist feishu onboarding fixtures

* Tests: allowlist Google Chat private key fixture

* Docs: allowlist Brave API key example

* Tests: allowlist pairing password env fixtures

* Chore: refresh detect-secrets baseline
2026-03-07 13:21:29 -05:00
Vincent Koc 3acf46ed45 Tests: fix doctor gateway auth token formatting 2026-03-07 10:18:52 -08:00
Vincent Koc 5290d97574 Docs: fix web tools MDX links 2026-03-07 10:15:22 -08:00
Vincent Koc 912f7a5525 CI: enable Windows pnpm side-effects cache 2026-03-07 10:11:52 -08:00
Vincent Koc de7848e227 CI: cache Python and Windows pnpm stores 2026-03-07 10:11:51 -08:00
Vincent Koc 61273c072c Docs: remove MDX-breaking secret markers 2026-03-07 10:09:00 -08:00
Vincent Koc e4d80ed556
CI: restore main detect-secrets scan (#38438)
* Tests: stabilize detect-secrets fixtures

* Tests: fix rebased detect-secrets false positives

* Docs: keep snippets valid under detect-secrets

* Tests: finalize detect-secrets false-positive fixes

* Tests: reduce detect-secrets false positives

* Tests: keep detect-secrets pragmas inline

* Tests: remediate next detect-secrets batch

* Tests: tighten detect-secrets allowlists

* Tests: stabilize detect-secrets formatter drift
2026-03-07 10:06:35 -08:00
Peter Steinberger 46e324e269 docs(changelog): credit hook auth throttling report 2026-03-07 18:05:11 +00:00
Peter Steinberger 44820dcead fix(hooks): gate methods before auth lockout accounting 2026-03-07 18:05:09 +00:00
jsk 262fef6ac8
fix(discord): honor commands.allowFrom in guild slash auth (#38794)
* fix(discord): honor commands.allowFrom in guild slash auth

* Update native-command.ts

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

* Update native-command.commands-allowfrom.test.ts

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

* fix(discord): address slash auth review feedback

* test(discord): add slash auth coverage for allowFrom variants

* fix: add changelog entry for discord slash auth fix (#38794) (thanks @jskoiz)

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Shadow <hi@shadowing.dev>
2026-03-07 12:03:52 -06:00
Peter Steinberger 278e5220ec test: narrow pairing setup helper token type 2026-03-07 17:58:31 +00:00
Peter Steinberger 9dc759023b refactor(agents): share skill plugin fixture writer in tests 2026-03-07 17:58:31 +00:00
Peter Steinberger 7eb48d3cf8 refactor(auto-reply): share discord auth registry test fixture 2026-03-07 17:58:31 +00:00
Peter Steinberger ce9719c654 refactor(test-utils): share direct channel plugin test fixture 2026-03-07 17:58:31 +00:00
Peter Steinberger 5f56333016 refactor(commands): dedupe config-only channel status fixtures 2026-03-07 17:58:31 +00:00
Peter Steinberger bcb587a3bc refactor(commands): dedupe channel plugin test fixture builders 2026-03-07 17:58:31 +00:00
Peter Steinberger 66de964c59 refactor(tui): dedupe mode-specific exec secret fixtures 2026-03-07 17:58:31 +00:00
Peter Steinberger e60b28fd1f refactor(tui): dedupe gateway token resolution path 2026-03-07 17:58:31 +00:00
Peter Steinberger a96ef12061 refactor(memory): dedupe local embedding init concurrency fixtures 2026-03-07 17:58:31 +00:00
Peter Steinberger 98ed7f57c6 refactor(feishu): dedupe non-streaming reply dispatcher setup 2026-03-07 17:58:31 +00:00
Peter Steinberger 6b0785028f refactor(feishu): dedupe accounts env secret-ref checks 2026-03-07 17:58:31 +00:00
Peter Steinberger 7fddb357cb refactor(feishu): dedupe client timeout assertion scaffolding 2026-03-07 17:58:31 +00:00
Peter Steinberger ac5f018877 refactor(feishu): dedupe onboarding status env setup tests 2026-03-07 17:58:31 +00:00
Peter Steinberger 72df4bd624 refactor(web): dedupe self-chat response-prefix tests 2026-03-07 17:58:31 +00:00
Peter Steinberger 7e94dec679 refactor(pairing): dedupe inferred auth token fixtures 2026-03-07 17:58:31 +00:00
Peter Steinberger 19245dd547 refactor(gateway): dedupe blocked chat reply mock setup 2026-03-07 17:58:31 +00:00
Peter Steinberger 4cdf867cb1 refactor(gateway): dedupe maintenance timer test setup 2026-03-07 17:58:31 +00:00
Peter Steinberger 0de6778f13 refactor(gateway): dedupe legacy migration validation assertions 2026-03-07 17:58:31 +00:00
Peter Steinberger f7a7f08e15 refactor(gateway): dedupe probe route assertion loops 2026-03-07 17:58:31 +00:00
Peter Steinberger 25efbdafce refactor(gateway): dedupe missing-local-token fixture tests 2026-03-07 17:58:31 +00:00
Peter Steinberger 49df8ab7b6 refactor(gateway): dedupe invalid image request assertions 2026-03-07 17:58:31 +00:00
Peter Steinberger b7733d6f5c refactor(agents): dedupe oauth token env setup tests 2026-03-07 17:58:31 +00:00
Peter Steinberger ca49372a8d refactor(agents): dedupe anthropic turn validation fixtures 2026-03-07 17:58:31 +00:00
Peter Steinberger 02b3e85eac refactor(agents): dedupe embedded fallback e2e helpers 2026-03-07 17:58:31 +00:00
Peter Steinberger 2d4a0c79a3 refactor(agents): dedupe nodes photos_latest camera tests 2026-03-07 17:58:31 +00:00
Peter Steinberger 2891c6c93c refactor(agents): dedupe model fallback probe failure tests 2026-03-07 17:58:31 +00:00
Peter Steinberger e41613f6ec refactor(agents): dedupe kilocode fetch-path tests 2026-03-07 17:58:31 +00:00
Peter Steinberger 53c1ae229f refactor(agents): dedupe minimax api-key normalization tests 2026-03-07 17:58:31 +00:00
Peter Steinberger 4e8fcc1d3d refactor(cli): dedupe command secret gateway env fixtures 2026-03-07 17:58:31 +00:00
Peter Steinberger c1a8f8150e refactor(commands): dedupe gateway status token secret fixtures 2026-03-07 17:58:31 +00:00
Peter Steinberger 4113a0f39e refactor(gateway): dedupe readiness healthy snapshot fixtures 2026-03-07 17:58:31 +00:00
Peter Steinberger 3d18c6ecec refactor(googlechat): dedupe outbound media runtime fixture setup 2026-03-07 17:58:31 +00:00
Peter Steinberger 6b778c4048 refactor(zalouser): reuse shared QR temp file writer 2026-03-07 17:58:31 +00:00
Peter Steinberger c5bb6db85b refactor(cron): share isolated-agent turn core test setup 2026-03-07 17:58:31 +00:00
Peter Steinberger 41e0c35b61 refactor(cron): reuse cron job builder in issue-13992 tests 2026-03-07 17:58:31 +00:00
Peter Steinberger d918fe3ecf refactor(mattermost): dedupe interaction callback test flows 2026-03-07 17:58:31 +00:00
Peter Steinberger 90a41aa1f7 refactor(discord): dedupe resolve channels fallback tests 2026-03-07 17:58:31 +00:00
Peter Steinberger 1fc11ea7d8 refactor(daemon): dedupe systemd restart test scaffolding 2026-03-07 17:58:30 +00:00
Peter Steinberger a31d3cad96 refactor(fetch-guard): clarify cross-origin redirect header filtering 2026-03-07 17:58:05 +00:00
Peter Steinberger c6472c189f chore: land #39056 Node version hint sync (thanks @onstash)
Land contributor change from #39056 and append changelog credit for @onstash.

Co-authored-by: Santosh Venkatraman <santosh.venk@gmail.com>
2026-03-07 17:51:54 +00:00
Byungsker 7735a0b85c
fix(security): use icacls /sid for locale-independent Windows ACL audit (#38900)
* fix(security): use icacls /sid for locale-independent Windows ACL audit

On non-English Windows editions (Russian, Chinese, etc.) icacls prints
account names in the system locale.  When Node.js reads the output in a
different code page the strings are garbled (e.g. "NT AUTHORITY\???????"
for "NT AUTHORITY\СИСТЕМА"), causing summarizeWindowsAcl to classify SYSTEM
and Administrators as untrusted and flag the config files as "others
writable" — a false-positive security alert.

Fix:
1. Pass /sid to icacls so it outputs security identifiers (*S-1-5-X-...)
   instead of locale-dependent account names.
2. Extend SID_RE to accept the leading * that icacls prepends to SIDs in
   /sid mode: /^\*?s-\d+-\d+(-\d+)+$/i
3. Strip the * before looking up the bare SID in TRUSTED_SIDS / the
   per-user USERSID set so *S-1-5-18 is correctly classified as SYSTEM
   (trusted) and *S-1-5-32-544 as Administrators (trusted).

Tests:
- Update the inspectWindowsAcl "returns parsed ACL entries" assertion to
  expect the /sid flag in the icacls call.
- Add "classifies *S-1-5-18 (icacls /sid prefix form of SYSTEM) as trusted"
  SID classification test.
- Add "classifies *S-1-5-32-544 (icacls /sid Administrators) as trusted".
- Add inspectWindowsAcl end-to-end test with /sid-format mock output
  (*S-1-5-18, *S-1-5-32-544, user SID) — all three classified as trusted.

Fixes #35834

* fix(security): classify world-equivalent SIDs as 'world' when using icacls /sid

When icacls is invoked with /sid, world-equivalent principals like
Everyone, Authenticated Users, and BUILTIN\Users are emitted as raw
SIDs (*S-1-1-0, *S-1-5-11, *S-1-5-32-545). classifyPrincipal() had
no SID-based mapping for these, so they fell through to the generic
'group' category instead of 'world', silently downgrading security
findings that should trigger world-write/world-readable alerts.

Fix: add a WORLD_SIDS constant and check it before falling back to
'group'. Add three regression tests to lock in the behaviour.

* Security: resolve owner SID fallback for Windows ACL audit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-07 12:49:33 -05:00
Peter Steinberger 4de697f8fa fix(ci): refresh detect-secrets baseline offsets 2026-03-07 17:42:17 +00:00
Peter Steinberger 46715371b0 fix(security): strip custom auth headers on cross-origin redirects 2026-03-07 17:34:42 +00:00
Peter Steinberger 630485ac98 fix(ci): harden diffs viewer request guard and secret scan baseline 2026-03-07 17:32:30 +00:00
Josh Avant 8e20dd22d8
Secrets: harden SecretRef-safe models.json persistence (#38955) 2026-03-07 11:28:39 -06:00
Peter Steinberger b08337b902 docs(changelog): credit allowlist scoping report 2026-03-07 17:09:28 +00:00
Peter Steinberger 6f3990ddca refactor(commands): dedupe onboard search perplexity test setup 2026-03-07 17:05:23 +00:00
Peter Steinberger 8e6acded82 refactor(commands): dedupe message command secret-config tests 2026-03-07 17:05:23 +00:00
Peter Steinberger 0a73328053 refactor(cli): dedupe restart health probe setup tests 2026-03-07 17:05:23 +00:00
Peter Steinberger 8fd043abac refactor(cron): dedupe interim retry fallback assertions 2026-03-07 17:05:23 +00:00
Peter Steinberger d103918891 refactor(commands): dedupe model probe target test fixtures 2026-03-07 17:05:23 +00:00
Peter Steinberger bffec0f5d5 refactor(discord): dedupe message preflight test runners 2026-03-07 17:05:23 +00:00
Peter Steinberger 9849ee8390 refactor(discord): share message handler test scaffolding 2026-03-07 17:05:23 +00:00
Peter Steinberger 3381efc5c1 refactor(discord): dedupe native command ACP routing test setup 2026-03-07 17:05:23 +00:00
Peter Steinberger 949beca0c2 refactor(slack): dedupe app mention in-flight race setup 2026-03-07 17:05:23 +00:00
Peter Steinberger d33efeef10 refactor(slack): reuse shared prepare test scaffolding 2026-03-07 17:05:23 +00:00
Peter Steinberger 08aae60dc9 refactor(plugin-sdk): extract shared channel prelude exports 2026-03-07 17:05:23 +00:00
Peter Steinberger 969b9029c0 refactor(slack): dedupe app mention race test setup 2026-03-07 17:05:23 +00:00
Peter Steinberger 5d37139ee5 refactor(line): dedupe replay webhook test fixtures 2026-03-07 17:05:23 +00:00
Peter Steinberger 4575bbbb69 refactor(telegram): dedupe topic agent routing tests 2026-03-07 17:05:23 +00:00
Peter Steinberger c1eb973e32 refactor(telegram): dedupe native command session-meta fixtures 2026-03-07 17:05:23 +00:00
Peter Steinberger a82df52753 refactor(extensions): share secret input schema builder 2026-03-07 17:05:23 +00:00
Peter Steinberger 134c1e23d3 refactor(commands): dedupe ACP stream test scaffolding 2026-03-07 17:05:23 +00:00
Peter Steinberger e51bad0c3a refactor(discord): dedupe preflight test builders 2026-03-07 17:05:23 +00:00
Peter Steinberger b3fd537740 refactor(line): share command authorization gate logic 2026-03-07 17:05:23 +00:00
Peter Steinberger f7fef07725 refactor(slack): share account surface field types 2026-03-07 17:05:23 +00:00
Peter Steinberger d02ef9efc2 refactor(telegram): share account config helpers 2026-03-07 17:05:23 +00:00
Peter Steinberger 398bf51659 refactor(slack): reuse shared account merge helper 2026-03-07 17:05:23 +00:00
Peter Steinberger d01cb7b65f refactor(cron): share cron schedule resolver 2026-03-07 17:05:23 +00:00
Peter Steinberger 4204c96105 refactor(gateway): share input allowlist normalizer 2026-03-07 17:05:23 +00:00
Vincent Koc 70da80bcb5
Auto-reply: scope allowlist store writes by account (#39015)
* Auto-reply: scope allowlist store writes

* Tests: cover allowlist store account scoping

* Changelog: note allowlist store scoping hardening
2026-03-07 08:51:20 -08:00
Peter Steinberger 74912037dc perf: harden chunking against quadratic scans 2026-03-07 16:50:35 +00:00
Peter Steinberger b393b9e8ff refactor(synology-chat): thread command authorization from webhook gate 2026-03-07 16:48:42 +00:00
Peter Steinberger 44881b0222 fix(diffs): harden proxied local viewer detection 2026-03-07 16:46:02 +00:00
Peter Steinberger 3a50e46cbf fix(nostr): harden profile mutation proxy guards 2026-03-07 16:44:21 +00:00
Peter Steinberger 1dd4f92ea2 fix: default local onboarding tools profile to coding 2026-03-07 16:41:27 +00:00
Vincent Koc f03f305ade
Mattermost: fix interaction action lookup sentinel (#38992) 2026-03-07 08:20:13 -08:00
Muhammed Mukhthar CM 4f08dcccfd
Mattermost: add interactive model picker (#38767)
Merged via squash.

Prepared head SHA: 0883654e88
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-07 21:45:29 +05:30
Florian Hines 33e7394861
fix(providers): make all models available in kilocode provider (#32352)
* kilocode: dynamic model discovery, kilo/auto default, cooldown exemption

- Replace 9-model hardcoded catalog with dynamic discovery from
  GET /api/gateway/models (Venice-like pattern with static fallback)
- Default model changed from anthropic/claude-opus-4.6 to kilo/auto
  (smart routing model)
- Add createKilocodeWrapper for X-KILOCODE-FEATURE header injection
  and reasoning.effort handling (skip for kilo/auto)
- Add kilocode to cooldown-exempt providers (proxy like OpenRouter)
- Keep sync buildKilocodeProvider for onboarding, add async
  buildKilocodeProviderWithDiscovery for implicit provider resolution
- Per-token gateway pricing converted to per-1M-token for cost fields

* kilocode: skip reasoning injection for x-ai models, harden discovery loop

* fix(kilocode): keep valid discovered duplicates (openclaw#32352, thanks @pandemicsyn)

* refactor(proxy): normalize reasoning payload guards (openclaw#32352, thanks @pandemicsyn)

* chore(changelog): note kilocode hardening (openclaw#32352, thanks @pandemicsyn and @vincentkoc)

* chore(changelog): fix kilocode note format (openclaw#32352, thanks @pandemicsyn and @vincentkoc)

* test(kilocode): support auto-model override cases (openclaw#32352, thanks @pandemicsyn)

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-07 08:14:06 -08:00
Jason 786ec21b5a
docs(cli): improve memory command examples (#31803)
Merged via squash.

Prepared head SHA: 15dcda3027
Co-authored-by: JasonOA888 <101583541+JasonOA888@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-07 19:03:23 +03:00
Nimrod Gutman 1eb7198bad
fix(ios): skip quick setup when a gateway is configured (#38964)
* fix(ios): hide quick setup when gateway is configured

* fix: note ios quick setup gating for configured gateways (#38964) (thanks @ngutman)
2026-03-07 17:46:16 +02:00
Nimrod Gutman 0bac6e4d67 fix: add changelog note for ios app store connect release prep (#38936) (thanks @ngutman) 2026-03-07 17:21:07 +02:00
Nimrod Gutman 43ab4f33ad feat(ios): prepare app store connect release assets 2026-03-07 17:21:07 +02:00
Rodrigo Uroz 4c0b873a4d
Config/Compaction: expose safeguard preserve and quality settings (#25557)
Merged via squash.

Prepared head SHA: ea9904039a
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-07 07:13:13 -08:00
Ayaan Zaidi bdd0f74188 docs: add changelog for markdown image hardening (#38895) 2026-03-07 19:46:41 +05:30
Ayaan Zaidi d25b493c7f fix: address markdown image review feedback 2026-03-07 19:46:41 +05:30
Ayaan Zaidi 4bf902de58 fix: flatten remote markdown images 2026-03-07 19:46:41 +05:30
Peter Steinberger 53a7e3b6e5 docs(security): clarify trusted operator control surfaces 2026-03-07 13:52:22 +00:00
Ayaan Zaidi 9e1de97a69
fix(telegram): route native topic commands to the active session (#38871)
* fix(telegram): resolve session entry for /stop in forum topics

Fixes #38675

- Export normalizeStoreSessionKey from store.ts for reuse
- Use it in resolveSessionEntryForKey so topic session keys (lowercase
  in store) are found when handling /stop
- Add test for forum topic session key lookup

* fix(telegram): share native topic routing with inbound messages

* fix: land telegram topic routing follow-up (#38871)

---------

Co-authored-by: xialonglee <li.xialong@xydigit.com>
2026-03-07 19:01:16 +05:30
Ayaan Zaidi bfc36cc86d test: cover telegram ACP slash session namespace (#38680) 2026-03-07 18:15:30 +05:30
john e8f419c4e0 fix(telegram): namespace slash SessionKey by agent
Fixes openclaw/openclaw#38648
2026-03-07 18:15:30 +05:30
Ayaan Zaidi 05c240fad6
fix: restart Windows gateway via Scheduled Task (#38825) (#38825) 2026-03-07 18:00:38 +05:30
Ayaan Zaidi 26c9796736 fix: check managed systemd unit before is-enabled (#38819) 2026-03-07 17:11:07 +05:30
Peter Steinberger addd290f88 fix(ci): stabilize tests and detect-secrets after dep updates 2026-03-07 11:14:04 +00:00
Ayaan Zaidi ac63f30cd2 test(nodes): type wrapped prepare coverage mock 2026-03-07 16:39:43 +05:30
Ayaan Zaidi 9d99370027 test(nodes): cover wrapped system.run prepare 2026-03-07 16:39:43 +05:30
Felipe 3efafab21b fix(nodes): remove redundant rawCommand from system.run.prepare
The nodes tool was passing rawCommand: formatExecCommand(command) to
system.run.prepare, which produced the full formatted argv string
(e.g. 'powershell -Command "echo hello"'). However,
validateSystemRunCommandConsistency() recognizes shell wrappers like
powershell/bash and extracts the inner command as the 'inferred' value
(e.g. 'echo hello'). This caused a rawCommand vs inferred mismatch,
breaking all nodes run commands with shell wrappers.

The fix removes the explicit rawCommand parameter, letting the
validation correctly infer the command text from the argv array.

Fixes #33080
2026-03-07 16:39:43 +05:30
Peter Steinberger 8db5d67768 chore: update dependencies except carbon 2026-03-07 10:55:18 +00:00
Peter Steinberger b85005194e test(memory): make mcporter EINVAL retry test deterministic 2026-03-07 10:49:03 +00:00
Peter Steinberger 1aa77e4603 refactor(extensions): reuse shared helper primitives 2026-03-07 10:41:05 +00:00
Peter Steinberger 3c71e2bd48 refactor(core): extract shared dedup helpers 2026-03-07 10:41:05 +00:00
Ayaan Zaidi 14c61bb33f fix(ci): re-enable detect-secrets on main 2026-03-07 16:09:12 +05:30
Peter Steinberger f358c6f2fb docs: reorder 2026.3.7 changelog highlights 2026-03-07 10:10:42 +00:00
Peter Steinberger 997a9f5b9e chore: bump version to 2026.3.7 2026-03-07 10:09:02 +00:00
Ayaan Zaidi 84f5d7dc1d fix(android): align run command with app id 2026-03-07 14:58:51 +05:30
Ayaan Zaidi 2018d8aa99 docs: add changelog entry for Android package rename (#38712) 2026-03-07 14:51:03 +05:30
Ayaan Zaidi 5568b393a8 fix(android): rename app package to ai.openclaw.app 2026-03-07 14:51:03 +05:30
Tak Hoffman 8873e13f1e
fix(gateway): stop stale-socket restarts before first event (#38643)
* fix(gateway): guard stale-socket restarts by event liveness

* fix(gateway): centralize connect-time liveness tracking

* fix(web): apply connected status patch atomically

* fix(gateway): require active socket for stale checks

* fix(gateway): ignore inherited stale event timestamps
2026-03-07 00:58:08 -06:00
ql-wade a5c07fa115
fix(gateway): skip stale-socket restarts for Telegram polling (openclaw#38405)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: ql-wade <262266039+ql-wade@users.noreply.github.com>
2026-03-07 00:20:34 -06:00
拐爷&&老拐瘦 2e31aead39
fix(gateway): invalidate bootstrap cache on session rollover (openclaw#38535)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: yfge <1186273+yfge@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-06 23:46:02 -06:00
Ayaan Zaidi e802840b62 docs: update changelog for reply media delivery (#38572) 2026-03-07 10:52:16 +05:30
Ayaan Zaidi c943747d6b fix: contain final reply media normalization failures 2026-03-07 10:52:16 +05:30
Ayaan Zaidi 059aedeb08 fix: contain block reply media failures 2026-03-07 10:52:16 +05:30
Ayaan Zaidi 77ef672468 fix: normalize reply media paths 2026-03-07 10:52:16 +05:30
Vincent Koc 15a5e39da2
Fix owner-only auth and overlapping skill env regressions (#38548) 2026-03-06 23:33:42 -05:00
Xinhua Gu 024af2b738
fix(feishu): disable block streaming to prevent silent reply drops (openclaw#38422)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: xinhuagu <562450+xinhuagu@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-06 22:33:30 -06:00
ql-wade e309a15d73
fix: suppress ACP NO_REPLY fragments in console output (#38436) 2026-03-07 09:34:45 +05:30
Vincent Koc 6017b738b1
Web: add HEIC media regression and doc fix (#38294)
* Web: add HEIC media normalization regression

* Docs: list HEIC input_image MIME types

* Update src/web/media.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-06 22:49:38 -05:00
Xinhua Gu 1a022a31de
fix(gateway): classify wrapped "fetch failed" messages as transient network errors (openclaw#38530)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: xinhuagu <562450+xinhuagu@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-06 21:47:32 -06:00
Jakob fa69f836c4
fix: increase maxTokens for tool probe to support reasoning models
Closes #7521
2026-03-06 22:27:28 -05:00
Tak Hoffman a01978ba96
fix(googlechat): inherit shared defaults for multi-account webhook auth (#38492)
* fix(googlechat): inherit shared defaults from accounts.default

* fix(googlechat): do not inherit default enabled state

* fix(googlechat): avoid inheriting default credentials

* fix(googlechat): keep dangerous auth flags account-local
2026-03-06 21:11:55 -06:00
Vincent Koc ba9eaf2ee2
fix(media): retain inbound media with recursive cleanup TTL (#38292)
* Config: add media retention TTL setting

* Media: recurse persisted media cleanup

* Gateway: add persisted media cleanup timer

* Media: harden retention cleanup sweep

* Media: make recursive retention cleanup opt-in

* Media: retry writes after empty-dir cleanup race
2026-03-06 22:06:09 -05:00
Vincent Koc 563a125c66
fix(gateway): stop shared-main chat.send from inheriting stale external routes (#38418)
* fix(gateway): prevent webchat messages from cross-routing to external channels

chat.send always originates from the webchat/control-UI surface.  Previously,
channel-scoped session keys (e.g. agent:main:slack:direct:U…) caused
OriginatingChannel to inherit the session's stored external route, so the
reply dispatcher would route responses to Slack/Telegram instead of back to
the gateway connection.  Remove the route-inheritance logic from chat.send and
always set OriginatingChannel to INTERNAL_MESSAGE_CHANNEL ("webchat").

Closes #34647

Made-with: Cursor

* Gateway: preserve configured-main connect gating

* Gateway: cover connect-without-client routing

* Gateway: add chat.send session key length limit

* Gateway: cap chat.send session key schema

* Gateway: bound chat.send session key parsing

* Gateway: cover oversized chat.send session keys

* Update CHANGELOG.md

---------

Co-authored-by: SidQin-cyber <sidqin0410@gmail.com>
2026-03-06 21:59:08 -05:00
Vincent Koc bf623a580b
Agents: add skill API rate-limit guardrail (#38452)
* Agents: add rate-limit guardrail for skill API writes

* Changelog: note skill API rate-limit awareness
2026-03-06 20:20:00 -05:00
Vincent Koc 75981b05c3
Dependencies: remove unused extension packages (#38317)
* Dependencies: drop unused extension packages

* Dependencies: drop unused tlon http-api package

* Dependencies: keep bundled acpx package
2026-03-06 19:55:41 -05:00
Vincent Koc 2d52c88dad
fix(podman): stop assuming /tmp is disk-backed (#38296)
* Podman: avoid hardcoding /tmp for image staging

* Docs: clarify container storage paths

* Podman: secure staged image import

* Podman: clarify streamed image handoff
2026-03-06 19:55:26 -05:00
Vincent Koc 74959fc1fd
Dependencies: remove unused core and UI packages (#38316)
* Dependencies: drop unused root runtime packages

* Dependencies: trim unused UI package deps

* Dependencies: keep UI build deps and stable git lock resolution

* Lockfile: fix UI vitest browser snapshot key
2026-03-06 19:53:22 -05:00
Vincent Koc 063b9aabe2 fix: xxxxx 2026-03-06 19:46:38 -05:00
Vincent Koc 42e3d8d693
Secrets: add inline allowlist review set (#38314)
* Secrets: add inline allowlist review set

* Secrets: narrow detect-secrets file exclusions

* Secrets: exclude Docker fingerprint false positive

* Secrets: allowlist test and docs false positives

* Secrets: refresh baseline after allowlist updates

* Secrets: fix gateway chat fixture pragma

* Secrets: format pre-commit config

* Android: keep talk mode fixture JSON valid

* Feishu: rely on client timeout injection

* Secrets: allowlist provider auth test fixtures

* Secrets: allowlist onboard search fixtures

* Secrets: allowlist onboard mode fixture

* Secrets: allowlist gateway auth mode fixture

* Secrets: allowlist APNS wake test key

* Secrets: allowlist gateway reload fixtures

* Secrets: allowlist moonshot video fixture

* Secrets: allowlist auto audio fixture

* Secrets: allowlist tiny audio fixture

* Secrets: allowlist embeddings fixtures

* Secrets: allowlist resolve fixtures

* Secrets: allowlist target registry pattern fixtures

* Secrets: allowlist gateway chat env fixture

* Secrets: refresh baseline after fixture allowlists

* Secrets: reapply gateway chat env allowlist

* Secrets: reapply gateway chat env allowlist

* Secrets: stabilize gateway chat env allowlist

* Secrets: allowlist runtime snapshot save fixture

* Secrets: allowlist oauth profile fixtures

* Secrets: allowlist compaction identifier fixture

* Secrets: allowlist model auth fixture

* Secrets: allowlist model status fixtures

* Secrets: allowlist custom onboarding fixture

* Secrets: allowlist mattermost token summary fixtures

* Secrets: allowlist gateway auth suite fixtures

* Secrets: allowlist channel summary fixture

* Secrets: allowlist provider usage auth fixtures

* Secrets: allowlist media proxy fixture

* Secrets: allowlist secrets audit fixtures

* Secrets: refresh baseline after final fixture allowlists

* Feishu: prefer explicit client timeout

* Feishu: test direct timeout precedence
2026-03-06 19:35:26 -05:00
Vincent Koc 3070fafec1
fix(venice): switch default model to kimi-k2-5 (#38423)
* Docs: refresh Venice default model guidance

* Venice: switch default model to Kimi K2.5

* Changelog: credit Venice default refresh
2026-03-06 19:31:07 -05:00
OfflynAI adb9234d03
fix(imessage): prevent echo loop from leaking internal metadata and amplifying NO_REPLY into queue overflow (#33295)
* fix(imessage): prevent echo loop from leaking internal metadata and amplifying NO_REPLY into queue overflow

- Add outbound sanitization at channel boundary (sanitize-outbound.ts):
  strips thinking/reasoning tags, relevant-memories tags, model-specific
  separators (+#+#), and assistant role markers before iMessage delivery

- Add inbound reflection guard (reflection-guard.ts): detects and drops
  messages containing assistant-internal markers that indicate a reflected
  outbound message, preventing recursive echo amplification

- Harden echo cache: increase text TTL from 5s to 30s to catch delayed
  reflections that previously expired before the echo could be detected

- Add loop rate limiter (loop-rate-limiter.ts): per-conversation rapid-fire
  detection that suppresses conversations exceeding threshold within a
  time window, acting as a safety net against amplification

Closes #33281

* fix(imessage): address review — stricter reflection regex, loop-aware rate limiter

- Reflection guard: require closing > bracket on thinking/final/memory
  tag patterns to prevent false-positives on user phrases like
  '<final answer>' or '<thought experiment>' (#33295 review)

- Rate limiter: only record echo/reflection/from-me drops instead of
  all dispatches, so the limiter acts as a loop-specific escalation
  mechanism rather than a general throttle on normal conversation
  velocity (#33295 review)

* Changelog: add iMessage echo-loop hardening entry

* iMessage: restore short echo-text TTL

* iMessage: ignore reflection markers in code

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 19:19:57 -05:00
Vincent Koc 5320ee7731
fix(venice): harden discovery limits and tool support (#38306)
* Config: add supportsTools compat flag

* Agents: add model tool support helper

* Venice: sync discovery and fallback metadata

* Agents: skip tools for unsupported models

* Changelog: note Venice provider hardening

* Update CHANGELOG.md

* Venice: cap degraded discovery metadata

* Apply suggestion from @greptile-apps[bot]

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

* Venice: tolerate partial discovery capabilities

* Venice: tolerate missing discovery specs

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-03-06 19:07:11 -05:00
SP 942c53e7f0
fix(agents): prevent totalTokens crash when assistant usage is missing (#34977)
Merged via squash.

Prepared head SHA: 1c14094f3f
Co-authored-by: sp-hk2ldn <8068616+sp-hk2ldn@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-06 15:59:16 -08:00
Marcus Widing 48b3c4a043
fix(auth): treat unconfigured-owner sessions as owner for ownerOnly tools (#26331)
Merged via squash.

Prepared head SHA: 1fbe1c7651
Co-authored-by: widingmarcus-cyber <245375637+widingmarcus-cyber@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-06 15:37:07 -08:00
Drew Wagner ae96a81916
fix: strip skill-injected env vars from ACP harness spawn env (#36280) (#36316)
* fix: strip skill-injected env vars from ACP harness spawn env

Skill apiKey entries (e.g., openai-image-gen with primaryEnv=OPENAI_API_KEY)
are set on process.env during agent runs and only reverted after the run
completes. ACP harnesses like Codex CLI inherit these vars, causing them
to silently use API billing instead of their own auth (e.g., OAuth).

The fix tracks which env vars are actively injected by skill overrides in
a module-level Set (activeSkillEnvKeys) and strips them in
resolveAcpClientSpawnEnv() before spawning ACP child processes.

Fixes #36280

* ACP: type spawn env for stripped keys

* Skills: cover active env key lifecycle

* Changelog: note ACP skill env isolation

* ACP: preserve shell marker after env stripping

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 18:18:13 -05:00
Efe Büken 03b9abab84
feat(compaction): make post-compaction context sections configurable (#34556)
Merged via squash.

Prepared head SHA: 491bb28544
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-06 14:57:15 -08:00
Vincent Koc 455430a6f8
Dead code: remove unused helper modules (#38318)
* Dead code: remove unused provider runtime policy helper

* Dead code: remove unused shared env writer

* Dead code: remove unused auth store path collector
2026-03-06 17:53:02 -05:00
Vincent Koc a190220967 Tests: serialize low-memory test runner lanes 2026-03-06 17:45:44 -05:00
Altay 6e962d8b9e
fix(agents): handle overloaded failover separately (#38301)
* fix(agents): skip auth-profile failure on overload

* fix(agents): note overload auth-profile fallback fix

* fix(agents): classify overloaded failures separately

* fix(agents): back off before overload failover

* fix(agents): tighten overload probe and backoff state

* fix(agents): persist overloaded cooldown across runs

* fix(agents): tighten overloaded status handling

* test(agents): add overload regression coverage

* fix(agents): restore runner imports after rebase

* test(agents): add overload fallback integration coverage

* fix(agents): harden overloaded failover abort handling

* test(agents): tighten overload classifier coverage

* test(agents): cover all-overloaded fallback exhaustion

* fix(cron): retry overloaded fallback summaries

* fix(cron): treat HTTP 529 as overloaded retry
2026-03-07 01:42:11 +03:00
Vincent Koc 110ca23bab Feishu: update media timeout tests 2026-03-06 17:34:41 -05:00
Wei Zhou e601bf2d8e
fix(pi-embedded-runner): propagate sender identity to fix Feishu doc create auto-grant (#32915)
Merged via squash.

Prepared head SHA: efb2293075
Co-authored-by: cszhouwei <1811726+cszhouwei@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-06 14:31:15 -08:00
Shadow 91494b2596
fix: repair auto-response workflow YAML 2026-03-06 16:24:50 -06:00
Shadow c301c5d083
fix: add no-ci-pr auto-response label 2026-03-06 15:53:59 -06:00
Shadow 864a1ecae7
docs: add changelog entry for Feishu timeouts (#38356) 2026-03-06 15:53:10 -06:00
Anton Eicher 20db7afd5f
fix(feishu): remove invalid timeout properties from SDK method calls (#38267)
The `timeout` property is not part of the Lark SDK method signatures,
causing TS2353 errors. The client-level `httpTimeoutMs` already applies
the timeout to all requests.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:50:34 -06:00
AngryBird 7ce79c8972
docs: fix broken dashboard image on i18n pages (#38031)
The dashboard screenshot uses a relative path `src="whatsapp-openclaw.jpg"`
which resolves correctly on the English root page but produces 404 on
zh-CN and ja-JP pages because Mintlify prepends the language subdirectory
to the CDN path.

Change to absolute path `/whatsapp-openclaw.jpg` in all three index files,
consistent with other images on the same page that already use absolute
paths (e.g. `/assets/openclaw-logo-text-dark.png`).
2026-03-07 00:22:19 +03:00
Vincent Koc ab5fcfcc01
feat(gateway): add channel-backed readiness probes (#38285)
* Changelog: add channel-backed readiness probe entry

* Gateway: add channel-backed readiness probes

* Docs: describe readiness probe behavior

* Gateway: add readiness probe regression tests

* Changelog: dedupe gateway probe entries

* Docs: fix readiness startup grace description

* Changelog: remove stale readiness entry

* Gateway: cover readiness hardening

* Gateway: harden readiness probes
2026-03-06 15:15:23 -05:00
Vincent Koc b17baca871 CI: enable report-only Knip deadcode job 2026-03-06 15:15:16 -05:00
Vincent Koc b70d3c4af3 Tooling: wire deadcode scripts to Knip 2026-03-06 15:15:16 -05:00
Vincent Koc 768736dc19 Tooling: add Knip workspace config 2026-03-06 15:15:16 -05:00
Vincent Koc 9c55299a82 CI: skip detect-secrets on main temporarily 2026-03-06 15:00:46 -05:00
Vincent Koc 82eebc905d Install Smoke: fetch docs base on demand 2026-03-06 14:45:37 -05:00
Vincent Koc 9c464c274c CI: fetch base history on demand 2026-03-06 14:45:34 -05:00
Vincent Koc e9919ead49 CI: add base-commit fetch helper 2026-03-06 14:45:30 -05:00
Vincent Koc 042b2c867d Docs: clarify main secret scan behavior 2026-03-06 14:41:23 -05:00
Vincent Koc 66112980aa CI: keep full secret scans on main 2026-03-06 14:41:20 -05:00
Vincent Koc b529b7c6b7 Docs: update secret scan reproduction steps 2026-03-06 14:34:46 -05:00
Vincent Koc ec3df0dd8f CI: scope secret scans to changed files 2026-03-06 14:34:46 -05:00
Vincent Koc 084dfd2ecc
Media: reject spoofed input_image MIME payloads (#38289)
* Media: reject spoofed input image MIME types

* Media: cover spoofed input image MIME regressions

* Changelog: note input image MIME hardening
2026-03-06 14:34:28 -05:00
Vincent Koc 38f46e80b0
chore: code/dead tests cleanup (#38286)
* Discord: assert bot-self filter queue guard

* Tests: remove dead gateway SIGTERM placeholder
2026-03-06 14:27:02 -05:00
Vincent Koc 5e05a9cb79 Install Smoke: cache docker smoke builds 2026-03-06 14:23:04 -05:00
Vincent Koc 60d20f9daf Install Smoke: allow reusing prebuilt test images 2026-03-06 14:23:00 -05:00
Vincent Koc afdbc472a4 Install Smoke: shallow docs-scope checkout 2026-03-06 14:15:15 -05:00
Vincent Koc 067ec4f0f9 CI: shallow scope checkouts 2026-03-06 14:15:15 -05:00
Kesku 3d7bc5958d
feat(onboarding): add web search to onboarding flow (#34009)
* add web search to onboarding flow

* remove post onboarding step (now redundant)

* post-onboarding nudge if no web search set up

* address comments

* fix test mocking

* add enabled: false assertion to the no-key test

* --skip-search cli flag

* use provider that a user has a key for

* add assertions, replace the duplicated switch blocks

* test for quickstart fast-path with existing config key

* address comments

* cover quickstart falls through to key test

* bring back key source

* normalize secret inputs instead of direct string trimming

* preserve enabled: false if it's already set

* handle missing API keys in flow

* doc updates

* hasExistingKey to detect both plaintext strings and SecretRef objects

* preserve enabled state only on the "keep current" paths

* add test for preserving

* better gate flows

* guard against invalid provider values in config

* Update src/commands/configure.wizard.ts

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

* format fix

* only mentions env var when it's actually available

* search apiKey fields now typed as SecretInput

* if no provider check if any search provider key is detectable

* handle both kimi keys

* remove .filter(Boolean)

* do not disable web_search after user enables it

* update resolveSearchProvider

* fix(onboarding): skip search key prompt in ref mode

* fix: add onboarding web search step (#34009) (thanks @kesku)

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Shadow <hi@shadowing.dev>
2026-03-06 13:09:00 -06:00
Shadow 9a1a63a667
chore: disable contributor labels 2026-03-06 12:37:14 -06:00
Shadow 8f834ff87d
chore: update X handle 2026-03-06 12:29:44 -06:00
Shadow 3e967cbc22
fix: add stale workflow fallback run 2026-03-06 12:15:28 -06:00
Shadow b782538743
fix: tune stale workflow limits 2026-03-06 12:08:53 -06:00
Vincent Koc e3390bfb70
CI: add Barnacle r: too-many-prs guard
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 11:37:00 -06:00
Sally O'Malley 57f19f0d5c
container builds: opt-in extension deps via OPENCLAW_EXTENSIONS build arg (#32223)
* Docker: opt-in extension deps via OPENCLAW_EXTENSIONS build arg

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

* CI: clarify extension smoke scope

* Tests: allow digest-pinned multi-stage FROM lines

* Changelog: note container extension preinstall option

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 12:18:42 -05:00
Vincent Koc d070c44091
fix(gateway): keep probe routes reachable with root-mounted control ui (#38199)
* fix(gateway): keep probe routes reachable with root-mounted control ui

* Changelog: add root-mounted probe precedence fix entry

* Update CHANGELOG.md
2026-03-06 12:13:20 -05:00
Ayaan Zaidi 4ed5febc38 chore(extensions): sync plugin versions 2026-03-06 22:26:15 +05:30
Ayaan Zaidi b12733395e fix(feishu): restore explicit media request timeouts 2026-03-06 22:26:15 +05:30
Vincent Koc 9521e61a22
Gateway: follow up HEIC input image handling (#38146)
* Media: scope HEIC MIME sniffing

* Media: hermeticize HEIC input tests

* Gateway: fix HEIC image budget accounting

* Gateway: add HEIC image budget regression test

* Changelog: note HEIC follow-up fix
2026-03-06 11:53:59 -05:00
Ayaan Zaidi f9d86b9256 chore: prep #38056 for landing (thanks @0xlin2023) 2026-03-06 22:19:16 +05:30
Ayaan Zaidi 59895f9c5a fix: narrow Telegram failed-after retry match 2026-03-06 22:19:16 +05:30
0xlin2023 e6bf69b366 fix: Telegram API requests fail with Network request failed after
Fixes #28835
2026-03-06 22:19:16 +05:30
0xlin2023 d000316d19 fix: Windows: openclaw plugins install fails with spawn EINVAL
Fixes #7631
2026-03-06 22:19:16 +05:30
Vincent Koc 6a9deb21b8 CI: cover skill and extension tests 2026-03-06 11:21:03 -05:00
Vincent Koc 9aceb51379
Gateway: normalize HEIC input_image sources (#38122)
* Media: normalize HEIC input images

* Gateway: accept HEIC image input schema

* Media: add HEIC input normalization tests

* Gateway: cover HEIC input schema parity

* Docs: document HEIC input image support

* Changelog: note HEIC input image fix
2026-03-06 11:19:36 -05:00
Mark Zhang 81f22ae109
openai-image-gen: validate and normalize --output-format (#36648)
* openai-image-gen: validate and normalize output format

* Skills/openai-image-gen: cover output-format edge cases

* Changelog: note openai image output format validation

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 11:14:30 -05:00
Vincent Koc f392b81e95
Infra: require explicit opt-in for prerelease npm installs (#38117)
* Infra: tighten npm registry spec parsing

* Infra: block implicit prerelease npm installs

* Plugins: cover prerelease install policy

* Infra: add npm registry spec tests

* Hooks: cover prerelease install policy

* Docs: clarify plugin guide version policy

* Docs: clarify plugin install version policy

* Docs: clarify hooks install version policy

* Docs: clarify hook pack version policy
2026-03-06 11:13:30 -05:00
Vincent Koc a274ef929f
Mattermost: harden interaction callback binding (#38057) 2026-03-06 11:08:45 -05:00
Vincent Koc 222d635aee
WhatsApp: honor outbound mediaMaxMb (#38097)
* WhatsApp: add media cap helper

* WhatsApp: cap outbound media loads

* WhatsApp: align auto-reply media caps

* WhatsApp: add outbound media cap test

* WhatsApp: update auto-reply cap tests

* Docs: update WhatsApp media caps

* Changelog: note WhatsApp media cap fix
2026-03-06 11:08:15 -05:00
Mark Zhang 20038fb955
openai-image-gen: validate --background and --style options (#36762)
* openai-image-gen: validate --background and --style inputs

* Skills/openai-image-gen: warn on ignored background and style flags

* Skills/openai-image-gen: cover empty and warning cases

* Changelog: note openai image flag validation

* Skills/openai-image-gen: fix Python import order

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 11:04:25 -05:00
Vincent Koc 7c45d918bf Docs: align BlueBubbles media cap wording 2026-03-06 10:59:05 -05:00
Vincent Koc 9c1786bdd6
Telegram/Discord: honor outbound mediaMaxMb uploads (#38065)
* Telegram: default media cap to 100MB

* Telegram: honor outbound mediaMaxMb

* Discord: add shared media upload cap

* Discord: pass mediaMaxMb to outbound sends

* Telegram: cover outbound media cap sends

* Discord: cover media upload cap config

* Docs: update Telegram media cap guide

* Docs: update Telegram config reference

* Changelog: note media upload cap fix

* Docs: note Discord upload cap behavior
2026-03-06 10:53:06 -05:00
Vincent Koc 9917a3fb77 CI: run changed-scope on main pushes 2026-03-06 10:51:32 -05:00
Vincent Koc 05c2cbf0e9
Skills/nano-banana-pro: clarify MEDIA token comment (#38063) 2026-03-06 10:51:11 -05:00
Mark Zhang 37a3fb0f86
nano-banana-pro: respect explicit --resolution when editing images (#36880)
* nano-banana-pro: respect explicit --resolution when editing images

* Changelog: note nano banana resolution fix

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 10:44:54 -05:00
Vincent Koc a820c63912 CI: drop unused install-smoke bootstrap 2026-03-06 10:40:41 -05:00
ABFS Tech 86a89d96d7
fix(nano-banana-pro): remove space after MEDIA: token in generate_image.py (#18706)
The MEDIA: output token must appear at line start with no space after
the colon for OpenClaw's splitMediaFromOutput parser to extract the
file path and auto-attach media on outbound chat channels (Discord,
Telegram, WhatsApp, etc.).

The script was printing 'MEDIA: /path' (with space), which while
tolerated by the regex, does not match the canonical 'MEDIA:/path'
format used by all other skills (e.g. openai-image-gen) and tested
in the codebase (pi-embedded-subscribe.tools.media.test.ts,
media/parse.test.ts).

Also updated the comment to clarify the format constraint.
2026-03-06 10:29:06 -05:00
Vincent Koc 151f26070b docs: context engine 2026-03-06 08:55:58 -05:00
Vincent Koc 5470337b1c docs(config): list the context engine plugin slot 2026-03-06 08:53:30 -05:00
Vincent Koc 7cc3376f07 docs(plugins): add context-engine manifest kind example 2026-03-06 08:53:30 -05:00
Vincent Koc eb2eebae22 docs(plugins): document context engine slots and registration 2026-03-06 08:53:30 -05:00
Vincent Koc f788ba142a docs(protocol): document slash-delimited schema lookup plugin ids 2026-03-06 08:53:29 -05:00
Vincent Koc e88f6605ec docs(tools): document slash-delimited config schema lookup paths 2026-03-06 08:53:29 -05:00
Vincent Koc 9fed9f1302
fix(session): tighten direct-session webchat routing matching (#37867)
* fix(session): require strict direct key routing shapes

* test(session): cover direct route poisoning cases
2026-03-06 08:53:16 -05:00
Josh Lehman fee91fefce
feature(context): extend plugin system to support custom context management (#22201)
* feat(context-engine): add ContextEngine interface and registry

Introduce the pluggable ContextEngine abstraction that allows external
plugins to register custom context management strategies.

- ContextEngine interface with lifecycle methods: bootstrap, ingest,
  ingestBatch, afterTurn, assemble, compact, prepareSubagentSpawn,
  onSubagentEnded, dispose
- Module-level singleton registry with registerContextEngine() and
  resolveContextEngine() (config-driven slot selection)
- LegacyContextEngine: pass-through implementation wrapping existing
  compaction behavior for 100% backward compatibility
- ensureContextEnginesInitialized() guard for safe one-time registration
- 19 tests covering contract, registry, resolution, and legacy parity

* feat(plugins): add context-engine slot and registerContextEngine API

Wire the ContextEngine abstraction into the plugin system so external
plugins can register context engines via the standard plugin API.

- Add 'context-engine' to PluginKind union type
- Add 'contextEngine' slot to PluginSlotsConfig (default: 'legacy')
- Wire registerContextEngine() through OpenClawPluginApi
- Export ContextEngine types from plugin-sdk for external consumers
- Restore proper slot-based resolution in registry

* feat(context-engine): wire ContextEngine into agent run lifecycle

Integrate the ContextEngine abstraction into the core agent run path:

- Resolve context engine once per run (reused across retries)
- Bootstrap: hydrate canonical store from session file on first run
- Assemble: route context assembly through pluggable engine
- Auto-compaction guard: disable built-in auto-compaction when
  the engine declares ownsCompaction (prevents double-compaction)
- AfterTurn: post-turn lifecycle hook for ingest + background
  compaction decisions
- Overflow compaction: route through contextEngine.compact()
- Dispose: clean up engine resources in finally block
- Notify context engine on subagent lifecycle events

Legacy engine: all lifecycle methods are pass-through/no-op, preserving
100% backward compatibility for users without a context engine plugin.

* feat(plugins): add scoped subagent methods and gateway request scope

Expose runtime.subagent.{run, waitForRun, getSession, deleteSession}
so external plugins can spawn sub-agent sessions without raw gateway
dispatch access.

Uses AsyncLocalStorage request-scope bridge to dispatch internally via
handleGatewayRequest with a synthetic operator client. Methods are only
available during gateway request handling.

- Symbol.for-backed global singleton for cross-module-reload safety
- Fallback gateway context for non-WS dispatch paths (Telegram/WhatsApp)
- Set gateway request scope for all handlers, not just plugin handlers
- 3 staleness tests for fallback context hardening

* feat(context-engine): route /compact and sessions.get through context engine

Wire the /compact command and sessions.get handler through the pluggable
ContextEngine interface.

- Thread tokenBudget and force parameters to context engine compact
- Route /compact through contextEngine.compact() when registered
- Wire sessions.get as runtime alias for plugin subagent dispatch
- Add .pebbles/ to .gitignore

* style: format with oxfmt 0.33.0

Fix duplicate import (ControlUiRootState in server.impl.ts) and
import ordering across all changed files.

* fix: update extension test mocks for context-engine types

Add missing subagent property to bluebubbles PluginRuntime mock.
Add missing registerContextEngine to lobster OpenClawPluginApi mock.

* fix(subagents): keep deferred delete cleanup retryable

* style: format run attempt for CI

* fix(rebase): remove duplicate embedded-run imports

* test: add missing gateway context mock export

* fix: pass resolved auth profile into afterTurn compaction

Ensure the embedded runner forwards resolved auth profile context into
legacy context-engine compaction params on the normal afterTurn path,
matching overflow compaction behavior. This allows downstream LCM
summarization to use the intended provider auth/profile consistently.

Also fix strict TS typing in external-link token dedupe and align an
attempt unit test reasoningLevel value with the current ReasoningLevel
enum.

Regeneration-Prompt: |
  We were debugging context-engine compaction where downstream summary
  calls were missing the right auth/profile context in normal afterTurn
  flow, while overflow compaction already propagated it. Preserve current
  behavior and keep changes additive: thread the resolved authProfileId
  through run -> attempt -> legacy compaction param builder without
  broad refactors.

  Add tests that prove the auth profile is included in afterTurn legacy
  params and that overflow compaction still passes it through run
  attempts. Keep existing APIs stable, and only adjust small type issues
  needed for strict compilation.

* fix: remove duplicate imports from rebase

* feat: add context-engine system prompt additions

* fix(rebase): dedupe attempt import declarations

* test: fix fetch mock typing in ollama autodiscovery

* fix(test): add registerContextEngine to diffs extension mock APIs

* test(windows): use path.delimiter in ios-team-id fixture PATH

* test(cron): add model formatting and precedence edge case tests

Covers:
- Provider/model string splitting (whitespace, nested paths, empty segments)
- Provider normalization (casing, aliases like bedrock→amazon-bedrock)
- Anthropic model alias normalization (opus-4.5→claude-opus-4-5)
- Precedence: job payload > session override > config default
- Sequential runs with different providers (CI flake regression pattern)
- forceNew session preserving stored model overrides
- Whitespace/empty model string edge cases
- Config model as string vs object format

* test(cron): fix model formatting test config types

* test(phone-control): add registerContextEngine to mock API

* fix: re-export ChannelKind from config-reload-plan

* fix: add subagent mock to plugin-runtime-mock test util

* docs: add changelog fragment for context engine PR #22201
2026-03-06 05:31:59 -08:00
Gustavo Madeira Santana fa6c0e1b40 Gateway: allow slash-delimited schema lookup paths 2026-03-06 06:57:19 -05:00
Muhammed Mukhthar CM 4a80d48ea9
fix(mattermost): allow reachable interaction callback URLs (#37543)
Merged via squash.

Prepared head SHA: 4d593731be
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-06 15:27:47 +05:30
Xinhua Gu 01b20172b8
fix(failover): classify HTTP 402 as rate_limit when payload indicates usage limit (#30484) (#36802)
* fix(failover): classify HTTP 402 as rate_limit when payload indicates usage limit (#30484)

Some providers (notably Anthropic Claude Max plan) surface temporary
usage/rate-limit failures as HTTP 402 instead of 429. Before this change,
all 402s were unconditionally mapped to 'billing', which produced a
misleading 'run out of credits' warning for Max plan users who simply
hit their usage window.

This follows the same pattern introduced for HTTP 400 in #36783: check
the error message for an explicit rate-limit signal before falling back
to the default status-code classification.

- classifyFailoverReasonFromHttpStatus now returns 'rate_limit' for 402
  when isRateLimitErrorMessage matches the payload text
- Added regression tests covering both the rate-limit and billing paths
  on 402

* fix: narrow 402 rate-limit matcher to prevent billing misclassification

The original implementation used isRateLimitErrorMessage(), which matches
phrases like 'quota exceeded' that legitimately appear in billing errors.

This commit replaces it with a narrow, 402-specific matcher that requires
BOTH retry language (try again/retry/temporary/cooldown) AND limit
terminology (usage limit/rate limit/organization usage).

Prevents misclassification of errors like:
'HTTP 402: exceeded quota, please add credits' -> billing (not rate_limit)

Added regression test for the ambiguous case.

---------

Co-authored-by: Val Alexander <bunsthedev@gmail.com>
2026-03-06 03:45:36 -06:00
Ayaan Zaidi ae56597f08 docs(changelog): add codex oauth pr reference (#37558) 2026-03-06 15:07:34 +05:30
Ayaan Zaidi f051c14325 docs(changelog): fold codex oauth fix notes 2026-03-06 15:07:34 +05:30
Ayaan Zaidi bdd368533f fix(auth): remove bogus codex oauth responses probe 2026-03-06 15:07:34 +05:30
Vignesh cbb96d9fe7
Update CHANGELOG.md 2026-03-06 01:19:07 -08:00
Vignesh Natarajan a4a490bae7 fix(openai-codex-oauth): stop mutating authorize url scopes 2026-03-06 01:13:12 -08:00
zhouhe-xydt a65d70f84b
Fix failover for zhipuai 1310 Weekly/Monthly Limit Exhausted (#33813)
Merged via squash.

Prepared head SHA: 3dc441e58d
Co-authored-by: zhouhe-xydt <265407618+zhouhe-xydt@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-06 12:04:09 +03:00
Altay ee6f7b1bf0
fix(ci): restore protocol and schema checks (#37470) 2026-03-06 11:46:17 +03:00
Gustavo Madeira Santana 5b03ce77f5 docs(changelog): add pr entry 2026-03-06 02:53:51 -05:00
Gustavo Madeira Santana ff97195500
Gateway: add path-scoped config schema lookup (#37266)
Merged via squash.

Prepared head SHA: 0c4d187f6f
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-06 02:50:48 -05:00
Vignesh Natarajan c5828cbc08 fix(onboarding): guard daemon status probe on headless linux 2026-03-05 22:51:58 -08:00
Vignesh Natarajan 30c0f7e89f fix(memory): retry mcporter after Windows EINVAL spawn 2026-03-05 22:27:37 -08:00
Vincent Koc 44ec3e4111 Cron: stabilize runs-one-shot migration tests 2026-03-06 01:27:23 -05:00
Vincent Koc a622aee45a Cron: migrate legacy provider delivery hints 2026-03-06 01:27:23 -05:00
Vincent Koc ff334600d5 Gateway: discriminate input sources 2026-03-06 01:27:23 -05:00
Frank Yang 5fdcef7cbe
fix(session): prefer webchat routes for direct ui turns (#37135) 2026-03-06 01:14:13 -05:00
Octane 777af476cb
Respect source channel for agent event surfacing (#36030) 2026-03-06 01:14:00 -05:00
Vignesh Natarajan dfe23b9cc4 fix(web_search): align brave language codes with API 2026-03-05 22:12:57 -08:00
Vincent Koc a939a15607 Gateway: coerce chat deliverable route boolean 2026-03-06 01:05:56 -05:00
Vincent Koc 9dab154519 Gateway: normalize OpenAI stream chunk text 2026-03-06 01:05:56 -05:00
Vignesh Natarajan 726ef48c2a fix(tui): accept canonical session-key aliases in chat event routing 2026-03-05 22:01:06 -08:00
aerelune 0e2bc588c4
fix: enforce 600 perms for cron store and run logs (#36078)
* fix: enforce secure permissions for cron store and run logs

* fix(cron): enforce dir perms and gate posix tests on windows

* Cron store tests: cover existing directory permission hardening

* Cron run-log tests: cover existing directory permission hardening

* Changelog: note cron file permission hardening

---------

Co-authored-by: linhey <linhey@mini.local>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 00:48:35 -05:00
Vincent Koc 6c39616ecd
Fix Control UI duplicate iMessage replies for internal webchat turns (#36151)
* Auto-reply: avoid routing external replies from internal webchat turns

* Auto-reply tests: cover internal webchat non-routing with external origin metadata

* Changelog: add Control UI iMessage duplicate-reply fix note

* Auto-reply context: track explicit deliver routes

* Gateway chat: mark explicit external deliver routes in context

* Auto-reply: preserve explicit deliver routes for internal webchat turns

* Auto-reply tests: cover explicit deliver routes from internal webchat turns

* Gateway chat tests: assert explicit deliver route context tagging
2026-03-06 00:47:57 -05:00
Ayaan Zaidi 8c2633a46f fix: clear Telegram DM draft after materialize (#36746) (thanks @joelnishanth) 2026-03-06 11:16:01 +05:30
Vignesh Natarajan e11a0775e7 fix(agents): avoid xAI web_search tool-name collisions 2026-03-05 21:37:47 -08:00
Vincent Koc 9c86a9fd23
fix(gateway): support image_url in OpenAI chat completions (#34068)
* fix(gateway): parse image_url in openai chat completions

* test(gateway): cover openai chat completions image_url flows

* docs(changelog): note openai image_url chat completions fix (#17685)

* fix(gateway): harden openai image_url parsing and limits

* test(gateway): add openai image_url regression coverage

* docs(changelog): expand #17685 openai chat completions note

* Gateway: make OpenAI image_url URL fetch opt-in and configurable

* Diagnostics: redact image base64 payload data in trace logs

* Changelog: note OpenAI image_url hardening follow-ups

* Gateway: enforce OpenAI image_url total budget incrementally

* Gateway: scope OpenAI image_url extraction to the active turn

* Update CHANGELOG.md
2026-03-06 00:35:50 -05:00
Brenner Spear 36e2e04a32
feat(nano-banana-pro): add --aspect-ratio flag to generate_image.py (#28159)
* feat(nano-banana-pro): add --aspect-ratio flag to generate_image.py

* Nano Banana: allow all supported aspect ratios

* Docs: expand nano banana aspect ratio options

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-06 00:26:55 -05:00
Vignesh Natarajan 2671f04865 fix(agents): disable usage streaming chunks on non-native openai-completions 2026-03-05 21:23:25 -08:00
joshavant ca8091491d
chore(changelog): update for #37023
Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-03-05 23:09:28 -06:00
Josh Avant 0e4245063f
CLI: make read-only SecretRef status flows degrade safely (#37023)
* CLI: add read-only SecretRef inspection

* CLI: fix read-only SecretRef status regressions

* CLI: preserve read-only SecretRef status fallbacks

* Docs: document read-only channel inspection hook

* CLI: preserve audit coverage for read-only SecretRefs

* CLI: fix read-only status account selection

* CLI: fix targeted gateway fallback analysis

* CLI: fix Slack HTTP read-only inspection

* CLI: align audit credential status checks

* CLI: restore Telegram read-only fallback semantics
2026-03-05 23:07:13 -06:00
Vignesh Natarajan 8d4a2f2c59 fix(tui): preserve credential-like tokens in render sanitization 2026-03-05 21:06:07 -08:00
dorukardahan 5d4b04040d
feat(openai): add gpt-5.4 support for API and Codex OAuth (#36590)
* feat(openai): add gpt-5.4 support and priority processing

* feat(openai-codex): add gpt-5.4 oauth support

* fix(openai): preserve provider overrides in gpt-5.4 fallback

* fix(openai-codex): keep xhigh for gpt-5.4 default

* fix(models): preserve configured overrides in list output

* fix(models): close gpt-5.4 integration gaps

* fix(openai): scope service tier to public api

* fix(openai): complete prep followups for gpt-5.4 support (#36590) (thanks @dorukardahan)

---------

Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
2026-03-05 21:01:37 -08:00
Hinata Kaga (samon) 8c85ad540a
fix: remove config.schema from agent gateway tool (#7382)
Merged via squash.

Prepared head SHA: f34a778069
Co-authored-by: kakuteki <61647657+kakuteki@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-05 23:53:08 -05:00
Vignesh Natarajan 4d9134fe9c fix(whatsapp): remove implicit [openclaw] self-chat prefix 2026-03-05 20:49:56 -08:00
Vincent Koc 10fe82fef1
Update CHANGELOG.md 2026-03-05 23:47:21 -05:00
Vincent Koc 8f69e07eb3
Delete changelog/fragments directory 2026-03-05 23:46:55 -05:00
Vincent Koc 9881a74e25 Changelog: add #37179 release note 2026-03-05 23:46:11 -05:00
Vincent Koc 428d1761b4
Plugins: avoid false integrity drift prompts on unpinned updates (#37179)
* Plugins: skip drift prompts for unpinned updates

* Plugins: cover unpinned integrity update behavior
2026-03-05 23:43:35 -05:00
Vignesh Natarajan 91aed291dd fix(memory): handle qmd search results without docid 2026-03-05 20:39:26 -08:00
Vignesh Natarajan 16f9f4dd22 fix(memory): repair qmd collection name conflicts during ensure 2026-03-05 20:31:01 -08:00
Vincent Koc d4021f4b92
Plugins: clarify registerHttpHandler migration errors (#36794)
* Changelog: note plugin HTTP route migration diagnostics

* Tests: cover registerHttpHandler migration diagnostics

* Plugins: clarify registerHttpHandler migration errors

* Tests: cover registerHttpHandler diagnostic edge cases

* Plugins: tighten registerHttpHandler migration hint
2026-03-05 23:23:24 -05:00
Vincent Koc e5481ac79f
Doctor: warn on implicit heartbeat directPolicy (#36789)
* Changelog: note heartbeat directPolicy doctor warning

* Tests: cover heartbeat directPolicy doctor warning

* Doctor: warn on implicit heartbeat directPolicy

* Tests: cover per-agent heartbeat directPolicy warning

* Update CHANGELOG.md
2026-03-05 23:22:39 -05:00
Vignesh Natarajan 87e38da826 fix(memory): recover qmd updates from duplicate document constraints 2026-03-05 20:20:25 -08:00
Vignesh Natarajan 36afd1b2b0 fix(agents): allow configured ollama endpoints without dummy api keys 2026-03-05 20:13:26 -08:00
Vignesh Natarajan d45353f95b fix(agents): honor explicit rate-limit cooldown probes in fallback runs 2026-03-05 20:03:06 -08:00
Tak Hoffman ce71fac7d6
fix(slack): record app_mention retry key before dedupe check (#37033)
- Prime app_mention retry allowance before dedupe so near-simultaneous message/app_mention races do not drop valid mentions.
- Prevent duplicate dispatch when app_mention wins the race and message prepare later succeeds.
- Prune dispatched mention keys and add regression coverage for both dropped and successful in-flight message outcomes.

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 21:47:52 -06:00
Vignesh Natarajan 05fb16d151 fix(agent): harden undici stream timeouts for long openai-completions runs 2026-03-05 19:44:11 -08:00
Vignesh Natarajan 4daaea1190 fix(agents): avoid synthetic tool-result writes on idle-timeout cleanup 2026-03-05 19:29:18 -08:00
Tyler Yust 81b93b9ce0
fix(subagents): announce delivery with descendant gating, frozen result refresh, and cron retry (#35080)
Thanks @tyler6204
2026-03-05 19:20:24 -08:00
Vignesh Natarajan fa3fafdde5 fix(auth): harden openai-codex oauth refresh fallback 2026-03-05 19:17:58 -08:00
Vincent Koc 71ec42127d
feat(hooks): emit compaction lifecycle hooks (#16788) 2026-03-05 19:08:26 -08:00
Vignesh Natarajan 2f86ae71d5 fix(subagents): recover announce cleanup after kill/complete race 2026-03-05 19:03:56 -08:00
Vignesh Natarajan 604f22c42a fix(heartbeat): pin HEARTBEAT.md reads to workspace path 2026-03-05 18:52:39 -08:00
dunamismax 1efa7a88c4
fix(slack): thread channel ID through inbound context for reactions (#34831)
Slack reaction/thread context routing fixes via canonical synthesis of #34831.

Co-authored-by: Tak <tak@users.noreply.github.com>
2026-03-05 20:47:31 -06:00
Vignesh Natarajan 909f26a26b fix(kimi-coding): normalize anthropic tool payload format 2026-03-05 18:43:15 -08:00
littleben b39ca7eccb
fix(slack): remove double mrkdwn conversion in native streaming path
Remove redundant text normalization from Slack native streaming markdown_text flow so Markdown formatting is preserved.

Synthesis context: overlaps reviewed from #34931, #34759, #34716, #34682, #34814.

Co-authored-by: littleben <1573829+littleben@users.noreply.github.com>
Co-authored-by: dunamismax <dunamismax@tutamail.com>
Co-authored-by: Octane <wdznb1@gmail.com>
Co-authored-by: Mitsuyuki Osabe <24588751+carrotRakko@users.noreply.github.com>
Co-authored-by: Kai <me@kaiyi.cool>
Co-authored-by: OpenClaw Agent <agent@openclaw.ai>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 20:34:43 -06:00
Vignesh Natarajan 1ab9393212 fix(secrets): harden api key normalization for ByteString headers 2026-03-05 18:31:45 -08:00
Sid 7a22b3fa0b
feat(agents): flush reply pipeline before compaction wait (#35489)
Merged via squash.

Prepared head SHA: 7dbbcc510b
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-05 18:22:19 -08:00
Vignesh Natarajan 6084c26d00 fix(tui): render final event error when assistant output is empty (#14687) 2026-03-05 18:16:43 -08:00
zerone0x 94fdee2eac
fix(memory-flush): ban timestamped variant files in default flush prompt (#34951)
Merged via squash.

Prepared head SHA: efadda4988
Co-authored-by: zerone0x <39543393+zerone0x@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-05 18:15:13 -08:00
Vignesh Natarajan 8088218f46 fix(openai-codex): request required oauth api scopes (#24720) 2026-03-05 18:10:03 -08:00
Josh Avant fb289b7a79
Memory: handle SecretRef keys in doctor embeddings (#36835)
Merged via squash.

Prepared head SHA: c1a3d0caae
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-05 20:05:59 -06:00
Vignesh Natarajan cec5535096 fix(tui): prevent stale model indicator after /model 2026-03-05 17:39:19 -08:00
Vignesh Natarajan d326861eb4 fix(gateway): preserve streamed prefixes across tool boundaries 2026-03-05 17:28:22 -08:00
Harold Hunt d58dafae88
feat(telegram/acp): Topic Binding, Pin Binding Message, Fix Spawn Param Parsing (#36683)
* fix(acp): normalize unicode flags and Telegram topic binding

* feat(telegram/acp): restore topic-bound ACP and session bindings

* fix(acpx): clarify permission-denied guidance

* feat(telegram/acp): pin spawn bind notice in topics

* docs(telegram): document ACP topic thread binding behavior

* refactor(reply): share Telegram conversation-id resolver

* fix(telegram/acp): preserve bound session routing semantics

* fix(telegram): respect binding persistence and expiry reporting

* refactor(telegram): simplify binding lifecycle persistence

* fix(telegram): bind acp spawns in direct messages

* fix: document telegram ACP topic binding changelog (#36683) (thanks @huntharo)

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-06 02:17:50 +01:00
Vignesh Natarajan 92b4892127 fix(auth): harden openai-codex oauth login path 2026-03-05 17:16:34 -08:00
vignesh07 3cd4978a09 fix(llm-task): load runEmbeddedPiAgent from dist/extensionAPI in installs 2026-03-05 17:16:14 -08:00
Vignesh Natarajan d86a12eb62 fix(gateway): honor insecure ws override for remote hostnames 2026-03-05 17:04:26 -08:00
Vignesh Natarajan c260e207b2 fix(routing): avoid full binding rescans in resolveAgentRoute (#36915) 2026-03-05 16:49:29 -08:00
Gustavo Madeira Santana 1a67cf57e3
Diffs: restore system prompt guidance (#36904)
Merged via squash.

Prepared head SHA: 1b3be3c879
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-05 19:46:39 -05:00
Vignesh Natarajan 06a229f98f fix(browser): close tracked tabs on session cleanup (#36666) 2026-03-05 16:40:52 -08:00
Gustavo Madeira Santana 6dfd39c32f
Harden Telegram poll gating and schema consistency (#36547)
Merged via squash.

Prepared head SHA: f77824419e
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-05 19:24:43 -05:00
Vignesh Natarajan f771ba8de9 fix(memory): avoid destructive qmd collection rebinds 2026-03-05 16:04:22 -08:00
Gustavo Madeira Santana 688b72e158
plugins: enforce prompt hook policy with runtime validation (#36567)
Merged via squash.

Prepared head SHA: 6b9d883b6a
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-05 18:15:54 -05:00
Bob 063e493d3d
fix: decouple Discord inbound worker timeout from listener timeout (#36602) (thanks @dutifulbob) (#36602)
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
2026-03-06 00:09:14 +01:00
joshavant 97ea9df57f
README: add algal to contributors list (#2046) 2026-03-05 17:07:03 -06:00
littleben b9a20dc97f
fix(slack): preserve dedupe while recovering dropped app_mention (#34937)
This PR fixes Slack mention loss without reintroducing duplicate dispatches.

- Preserve seen-message dedupe at ingress to prevent duplicate processing.
- Allow a one-time app_mention retry only when the paired message event was previously dropped before dispatch.
- Add targeted race tests for both recovery and duplicate-prevention paths.

Co-authored-by: littleben <1573829+littleben@users.noreply.github.com>
Co-authored-by: OpenClaw Agent <agent@openclaw.ai>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 17:00:05 -06:00
2233admin 7830366f3c
fix(slack): propagate mediaLocalRoots through Slack send path
Restore Slack local file upload parity with CVE-era local media allowlist enforcement by threading `mediaLocalRoots` through the Slack send call chain.

- pass `ctx.mediaLocalRoots` from Slack channel action adapter into `handleSlackAction`
- add and forward `mediaLocalRoots` in Slack action context/send path
- pass `mediaLocalRoots` into `sendMessageSlack` for upload allowlist enforcement
- add changelog entry with attribution for this behavior fix

Co-authored-by: 2233admin <1497479966@qq.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 16:52:49 -06:00
Bill a0b731e2ce
fix(config): prevent RangeError in merged schema cache key generation
Fix merged schema cache key generation for high-cardinality plugin/channel metadata by hashing incrementally instead of serializing one large aggregate string.

Includes changelog entry for the user-visible regression fix.

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Bill <gsamzn@gmail.com>
2026-03-05 16:45:07 -06:00
Sid 60d33637d9
fix(auth): grant senderIsOwner for internal channels with operator.admin scope (openclaw#35704)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Naylenv <45486779+Naylenv@users.noreply.github.com>
Co-authored-by: Octane0411 <88922959+Octane0411@users.noreply.github.com>
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 16:32:42 -06:00
Jacob Riff aad372e15f
feat: append UTC time alongside local time in shared Current time lines (#32423)
Merged via squash.

Prepared head SHA: 9e8ec13933
Co-authored-by: jriff <50276+jriff@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-06 01:26:34 +03:00
Altay 49acb07f9f
fix(agents): classify insufficient_quota 400s as billing (#36783) 2026-03-06 01:17:48 +03:00
Vincent Koc 0c08e3f55f
UI: hoist lifecycle connect test mocks (#36788) 2026-03-05 17:15:31 -05:00
Vincent Koc 999b7e4edf
fix(ui): bump dompurify to 3.3.2 (#36781)
* UI: bump dompurify to 3.3.2

* Deps: refresh dompurify lockfile
2026-03-05 17:08:42 -05:00
Vincent Koc 98aecab7bd Docs: cover heartbeat, cron, and plugin route updates 2026-03-05 17:05:21 -05:00
Vincent Koc 2b45eb0e52 Docs: document Control UI locale support 2026-03-05 16:57:59 -05:00
Vincent Koc 6b2c115167 Docs: clarify OpenAI-compatible TTS endpoints 2026-03-05 16:57:51 -05:00
Vincent Koc 1d3962a000 Docs: update gateway config reference for Slack and TTS 2026-03-05 16:57:40 -05:00
Vincent Koc 837b7b4b94 Docs: add Slack typing reaction fallback 2026-03-05 16:57:31 -05:00
Altay 6859619e98
test(agents): add provider-backed failover regressions (#36735)
* test(agents): add provider-backed failover fixtures

* test(agents): cover more provider error docs

* test(agents): tighten provider doc fixtures
2026-03-06 00:42:59 +03:00
Rodrigo Uroz 036c329716
Compaction/Safeguard: add summary quality audit retries (#25556)
Merged via squash.

Prepared head SHA: be473efd16
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-05 13:39:25 -08:00
jiangnan 029c473727
fix(failover): narrow service-unavailable to require overload indicator (#32828) (#36646)
Merged via squash.

Prepared head SHA: 46fb430612
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-06 00:01:57 +03:00
Altay f014e255df
refactor(agents): share failover HTTP status classification (#36615)
* fix(agents): classify transient failover statuses consistently

* fix(agents): preserve legacy failover status mapping
2026-03-05 23:50:36 +03:00
不做了睡大觉 8ac7ce73b3
fix: avoid false global rate-limit classification from generic cooldown text (#32972)
Merged via squash.

Prepared head SHA: 813c16f5af
Co-authored-by: stakeswky <64798754+stakeswky@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-05 22:58:21 +03:00
Sid 591264ef52
fix(agents): set preserveSignatures to isAnthropic in resolveTranscriptPolicy (#32813)
Merged via squash.

Prepared head SHA: f522d21ca5
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-05 11:55:06 -08:00
Byungsker 709dc671e4
fix(session): archive old transcript on daily/scheduled reset to prevent orphaned files (#35493)
Merged via squash.

Prepared head SHA: 0d95549d75
Co-authored-by: byungsker <72309817+byungsker@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-05 11:52:23 -08:00
Bin Deng edc386e9a5
fix(ui): catch marked.js parse errors to prevent Control UI crash (#36445)
- Prevent Control UI session render crashes when `marked.parse()` encounters pathological recursive markdown by safely falling back to escaped `<pre>` output.
- Tighten markdown fallback regression coverage and keep changelog attribution in sync for this crash-hardening path.

Co-authored-by: Bin Deng <dengbin@romangic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 13:46:49 -06:00
Sid 6c0376145f
fix(agents): skip compaction API call when session has no real messages (#36451)
Merged via squash.

Prepared head SHA: 52dd631789
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-05 11:40:25 -08:00
Kai 60a6d11116
fix(embedded): classify model_context_window_exceeded as context overflow, trigger compaction (#35934)
Merged via squash.

Prepared head SHA: 20fa77289c
Co-authored-by: RealKai42 <44634134+RealKai42@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-05 11:30:24 -08:00
Josh Avant 72cf9253fc
Gateway: add SecretRef support for gateway.auth.token with auth-mode guardrails (#35094) 2026-03-05 12:53:56 -06:00
Tak Hoffman bc66a8fa81
fix(feishu): avoid media regressions from global HTTP timeout (#36500)
* fix(feishu): avoid media regressions from global http timeout

* fix(feishu): source HTTP timeout from config

* fix(feishu): apply media timeout override to image uploads

* fix(feishu): invalidate cached client when timeout changes

* fix(feishu): clamp timeout values and cover image download
2026-03-05 12:13:40 -06:00
maweibin 09c68f8f0e
add prependSystemContext and appendSystemContext to before_prompt_build (fixes #35131) (#35177)
Merged via squash.

Prepared head SHA: d9a2869ad6
Co-authored-by: maweibin <18023423+maweibin@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-05 13:06:59 -05:00
Liu Xiaopai 174eeea76c
Feishu: normalize group slash command probing
- Feishu/group slash command detection: normalize group mention wrappers before command-authorization probing so mention-prefixed commands are recognized in group routing.\n- Source PR: #36011\n- Contributor: @liuxiaopai-ai\n\nCo-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>\nCo-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
2026-03-05 11:56:59 -06:00
Sid 995ae73d5f
synthesis: fix Feishu group mention slash parsing
## Summary\n\nFeishu group slash command parsing is fixed for mentions and command probes across authorization paths.\n\nThis includes:\n- Normalizing bot mention text in group context for reliable slash detection in message parsing.\n- Adding command-probe normalization for group slash invocations.\n\nCo-authored-by: Sid Qin <sidqin0410@gmail.com>\nCo-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 11:34:08 -06:00
Sid 2972d6fa79
fix(feishu): accept groupPolicy "allowall" as alias for "open" (#36358)
* fix(feishu): accept groupPolicy "allowall" as alias for "open"

When users configure groupPolicy: "allowall" in Feishu channel config,
the Zod schema rejects the value and the runtime policy check falls
through to the allowlist path.  With an empty allowFrom array, all group
messages are silently dropped despite the intended "allow all" semantics.

Accept "allowall" at the schema level (transform to "open") and add a
runtime guard in isFeishuGroupAllowed so the value is handled even if it
bypasses schema validation.

Closes #36312

Made-with: Cursor

* Feishu: tighten allowall alias handling and coverage

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 11:32:01 -06:00
Tak Hoffman 89b303c553
Mattermost: switch plugin-sdk imports to scoped subpaths (openclaw#36480)
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 11:28:16 -06:00
StingNing 627b37e34f
Feishu: honor bot mentions by ID despite aliases (Fixes #36317) (#36333) 2026-03-05 11:00:27 -06:00
Liu Xiaopai b9f3f8d737
fix(feishu): use probed botName for mention checks (#36391) 2026-03-05 10:55:04 -06:00
Ayane ba223c7766
fix(feishu): add HTTP timeout to prevent per-chat queue deadlocks (#36430)
When the Feishu API hangs or responds slowly, the sendChain never settles,
causing the per-chat queue to remain in a processing state forever and
blocking all subsequent messages in that thread. This adds a 30-second
default timeout to all Feishu HTTP requests by providing a timeout-aware
httpInstance to the Lark SDK client.

Closes #36412

Co-authored-by: Ayane <wangruofei@soulapp.cn>
2026-03-05 10:46:10 -06:00
Sid 8d48235d3a
fix(browser): remove deprecated --disable-blink-features=AutomationControlled flag
- Removes OpenClaw's default `--disable-blink-features=AutomationControlled` Chrome launch switch to avoid unsupported-flag warnings in newer Chrome (#35721).
- Preserves compatibility for older Chrome via `browser.extraArgs` override behavior (source analysis: #35770, #35728, #35727, #35885).
- Synthesis attribution: thanks @Sid-Qin, @kevinWangSheng, @ningding97, @Naylenv, @clawbie.

Source PR refs: #35734, #35770, #35728, #35727, #35885

Co-authored-by: Sid-Qin <Sid-Qin@users.noreply.github.com>
Co-authored-by: kevinWangSheng <kevinWangSheng@users.noreply.github.com>
Co-authored-by: ningding97 <ningding97@users.noreply.github.com>
Co-authored-by: Naylenv <Naylenv@users.noreply.github.com>
Co-authored-by: clawbie <clawbie@users.noreply.github.com>
Co-authored-by: Takhoffman <Takhoffman@users.noreply.github.com>
2026-03-05 09:22:47 -06:00
Tony Dehnke 136ca87f7b
feat(mattermost): add interactive buttons support (#19957)
Merged via squash.

Prepared head SHA: 8a25e60872
Co-authored-by: tonydehnke <36720180+tonydehnke@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-05 20:14:57 +05:30
Tak Hoffman 9741e91a64
test(cron): add cross-channel announce fallback regression coverage (openclaw#36197)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on pre-existing origin/main lint debt in extensions/mattermost imports)
- pnpm test:macmini

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 07:37:37 -06:00
Tak Hoffman 544abc927f
fix(cron): restore direct fallback after announce failure in best-effort mode (openclaw#36177)
Verified:
- pnpm build
- pnpm check (fails on pre-existing origin/main lint debt in extensions/mattermost imports)
- pnpm test:macmini

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 07:25:24 -06:00
Vincent Koc 4dc0c66399
fix(subagents): strip leaked [[reply_to]] tags from completion announces (#34503)
* fix(subagents): strip reply tags from completion delivery text

* test(subagents): cover reply-tag stripping in cron completion sends

* changelog: note iMessage reply-tag stripping in completion announces

* Update CHANGELOG.md

* 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-05 07:50:55 -05:00
Joseph Turian e5b6a4e19d
Mattermost: honor onmessage mention override and add gating diagnostics tests (#27160)
Merged via squash.

Prepared head SHA: 6cefb1d5bf
Co-authored-by: turian <65918+turian@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-05 17:59:54 +05:30
Sid 06ff25cce4
fix(feishu): check response.ok before calling response.json() in streaming card (#35628)
Merged via squash.

Prepared head SHA: 62c3fec80d
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-05 01:58:21 -08:00
青雲 c522154771
docs(telegram): recommend allowlist for single-user DM policy (#34841)
* docs(telegram): recommend allowlist for single-user bots

* docs(telegram): condense single-user allowlist note

---------

Co-authored-by: echoVic <echovic@163.com>
2026-03-05 11:39:19 +03:00
Bob 6a705a37f2
ACP: add persistent Discord channel and Telegram topic bindings (#34873)
* docs: add ACP persistent binding experiment plan

* docs: align ACP persistent binding spec to channel-local config

* docs: scope Telegram ACP bindings to forum topics only

* docs: lock bound /new and /reset behavior to in-place ACP reset

* ACP: add persistent discord/telegram conversation bindings

* ACP: fix persistent binding reuse and discord thread parent context

* docs: document channel-specific persistent ACP bindings

* ACP: split persistent bindings and share conversation id helpers

* ACP: defer configured binding init until preflight passes

* ACP: fix discord thread parent fallback and explicit disable inheritance

* ACP: keep bound /new and /reset in-place

* ACP: honor configured bindings in native command flows

* ACP: avoid configured fallback after runtime bind failure

* docs: refine ACP bindings experiment config examples

* acp: cut over to typed top-level persistent bindings

* ACP bindings: harden reset recovery and native command auth

* Docs: add ACP bound command auth proposal

* Tests: normalize i18n registry zh-CN assertion encoding

* ACP bindings: address review findings for reset and fallback routing

* ACP reset: gate hooks on success and preserve /new arguments

* ACP bindings: fix auth and binding-priority review findings

* Telegram ACP: gate ensure on auth and accepted messages

* ACP bindings: fix session-key precedence and unavailable handling

* ACP reset/native commands: honor fallback targets and abort on bootstrap failure

* Config schema: validate ACP binding channel and Telegram topic IDs

* Discord ACP: apply configured DM bindings to native commands

* ACP reset tails: dispatch through ACP after command handling

* ACP tails/native reset auth: fix target dispatch and restore full auth

* ACP reset detection: fallback to active ACP keys for DM contexts

* Tests: type runTurn mock input in ACP dispatch test

* ACP: dedup binding route bootstrap and reset target resolution

* reply: align ACP reset hooks with bound session key

* docs: replace personal discord ids with placeholders

* fix: add changelog entry for ACP persistent bindings (#34873) (thanks @dutifulbob)

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-05 09:38:12 +01:00
Kai 2c8ee593b9
TTS: add baseUrl support to OpenAI TTS config (#34321)
Merged via squash.

Prepared head SHA: e9a10cf81d
Co-authored-by: RealKai42 <44634134+RealKai42@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-05 07:25:04 +00:00
Shakker 60849f3335
chore(pr): enforce changelog placement and reduce merge sync churn 2026-03-05 06:37:53 +00:00
Sid 3a6b412f00
fix(gateway): pass actual version to Control UI client instead of dev (#35230)
* fix(gateway): pass actual version to Control UI client instead of "dev"

The GatewayClient, CLI WS client, and browser Control UI all sent
"dev" as their clientVersion during handshake, making it impossible
to distinguish builds in gateway logs and health snapshots.

- GatewayClient and CLI WS client now use the resolved VERSION constant
- Control UI reads serverVersion from the bootstrap endpoint and
  forwards it when connecting
- Bootstrap contract extended with serverVersion field

Closes #35209

* Gateway: fix control-ui version version-reporting consistency

* Control UI: guard deferred bootstrap connect after disconnect

* fix(ui): accept same-origin http and relative gateway URLs for client version

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-05 00:01:34 -06:00
alexyyyander c4dab17ca9
fix(gateway): prevent internal route leakage in chat.send
Synthesis of routing fixes from #35321, #34635, and #35356 for internal-client reply safety.

- Require explicit `deliver: true` before inheriting any external delivery route.
- Keep webchat/TUI/UI-origin traffic on internal routing by default.
- Allow configured-main session inheritance only for non-Webchat/UI clients, and honor `session.mainKey`.
- Add regression tests for UI no-inherit, configured-main CLI inherit, and deliver-flag behavior.

Co-authored-by: alexyyyander <alexyyyander@users.noreply.github.com>
Co-authored-by: Octane0411 <88922959+Octane0411@users.noreply.github.com>
Co-authored-by: Linux2010 <35169750+Linux2010@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-04 23:57:35 -06:00
Sid 463fd4735e
fix(agents): guard context pruning against malformed thinking blocks (#35146)
Merged via squash.

Prepared head SHA: a196a565b1
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-05 05:52:24 +00:00
不做了睡大觉 8891e1e48d
fix(web-ui): render Accounts schema node properly (#35380)
Co-authored-by: stakeswky <64798754+stakeswky@users.noreply.github.com>
Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-04 23:50:18 -06:00
Sid d9b69a6145
fix(agents): guard promoteThinkingTagsToBlocks against malformed content entries (#35143)
Merged via squash.

Prepared head SHA: 3971122f5f
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-05 05:37:33 +00:00
Sid ce0c13191f
fix(agents): decode HTML entities in xAI/Grok tool call arguments (#35276)
Merged via squash.

Prepared head SHA: c4445d2938
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-05 05:32:39 +00:00
Sid 987e473364
fix(agents): detect Venice provider proxying xAI/Grok models for schema cleaning (#35355)
Merged via squash.

Prepared head SHA: 8bfdec257b
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-05 05:29:25 +00:00
Shakker 1805735c63 chore(changelog): add dedupe note openclaw#27521 thanks @shivama205 2026-03-05 05:11:06 +00:00
Shakker b5a94d274b style(skills): align formatting cleanup for dedupe changes 2026-03-05 05:11:06 +00:00
Shivam fb4f52b710 style: fix formatting in skill-commands.test.ts and provider.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 05:11:06 +00:00
Shivam 48decefbf4 fix(skills): deduplicate slash commands by skillName across all interfaces
Move skill-command deduplication by skillName from the Discord-only
`dedupeSkillCommandsForDiscord` into `listSkillCommandsForAgents` so
every interface (TUI, Slack, text) consistently sees a clean command
list without platform-specific workarounds.

When multiple agents share a skill with the same name the old code
emitted `github` + `github_2` and relied on Discord to collapse them.
Now `listSkillCommandsForAgents` returns only the first registration
per skillName, and the Discord-specific wrapper is removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 05:11:06 +00:00
Shakker 5d5fa0dac8
fix(pr): make review claim step required 2026-03-05 04:53:32 +00:00
Harold Hunt 4bd3469324
refactor(telegram): remove unused webhook callback helper (#27816) 2026-03-05 10:10:09 +05:30
Tak Hoffman cc5dad81bc
cron: unify stale-run recovery and preserve manual-run every anchors (#35363)
* cron: unify stale-run recovery and preserve manual every anchors

* cron: address unresolved review threads on recovery paths

* cron: remove duplicate timestamp helper after rebase
2026-03-04 22:12:32 -06:00
Tak Hoffman 28dc2e8a40
cron: narrow startup replay backoff guard (#35391) 2026-03-04 22:11:11 -06:00
Tak Hoffman 79d00ae398
fix(cron): stabilize restart catch-up replay semantics (#35351)
* Cron: stabilize restart catch-up replay semantics

* Cron: respect backoff in startup missed-run replay
2026-03-04 21:50:16 -06:00
sline 1059b406a8
fix: cron backup should preserve pre-edit snapshot (#35195) (#35234)
* fix(cron): avoid overwriting .bak during normalization

Fixes openclaw/openclaw#35195

* test(cron): preserve pre-edit bak snapshot in normalization path

---------

Co-authored-by: 0xsline <sline@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-04 21:46:27 -06:00
rexl2018 3bf6ed181e
Feishu: harden streaming merge semantics and final reply dedupe (#33245)
* Feishu: close duplicate final gap and cover routing precedence

* Feishu: resolve reviewer duplicate-final and routing feedback

* Feishu: tighten streaming send-mode option typing

* Feishu: fix reverse-overlap streaming merge ordering

* Feishu: align streaming final dedupe test expectation

* Feishu: allow distinct streaming finals while deduping repeats

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-04 21:32:35 -06:00
Sid 8b8167d547
fix(agents): bypass pendingDescendantRuns guard for cron announce delivery (#35185)
* fix(agents): bypass pendingDescendantRuns guard for cron announce delivery

Standalone cron job completions were blocked from direct channel delivery
when the cron run had spawned subagents that were still registered as
pending. The pendingDescendantRuns guard exists for live orchestration
coordination and should not apply to fire-and-forget cron announce sends.

Thread the announceType through the delivery chain and skip both the
child-descendant and requester-descendant pending-run guards when the
announce originates from a cron job.

Closes #34966

* fix: ensure outbound session entry for cron announce with named agents (#32432)

Named agents may not have a session entry for their delivery target,
causing the announce flow to silently fail (delivered=false, no error).

Two fixes:
1. Call ensureOutboundSessionEntry when resolving the cron announce
   session key so downstream delivery can find channel metadata.
2. Fall back to direct outbound delivery when announce delivery fails
   to ensure cron output reaches the target channel.

Closes #32432

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

* fix: guard announce direct-delivery fallback against suppression leaks (#32432)

The `!delivered` fallback condition was too broad — it caught intentional
suppressions (active subagents, interim messages, SILENT_REPLY_TOKEN) in
addition to actual announce delivery failures.  Add an
`announceDeliveryWasAttempted` flag so the direct-delivery fallback only
fires when `runSubagentAnnounceFlow` was actually called and failed.

Also remove the redundant `if (route)` guard in
`resolveCronAnnounceSessionKey` since `resolved` being truthy guarantees
`route` is non-null.

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

* fix(cron): harden announce synthesis follow-ups

---------

Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-04 21:31:33 -06:00
Nhj 68e68bfb57
fix(feishu): use msg_type media for mp4 video (fixes #33674) (#33720)
* fix(feishu): use msg_type media for mp4 video (fixes #33674)

* Feishu: harden streaming merge semantics and final reply dedupe

Use explicit streaming update semantics in the Feishu reply dispatcher:
treat onPartialReply payloads as snapshot updates and block fallback payloads
as delta chunks, then merge final text with the shared overlap-aware
mergeStreamingText helper before closing the stream.

Prevent duplicate final text delivery within the same dispatch cycle, and add
regression tests covering overlap snapshot merge, duplicate final suppression,
and block-as-delta behavior to guard against repeated/truncated output.

* fix(feishu): prefer message.reply for streaming cards in topic threads

* fix: reduce Feishu streaming card print_step to avoid duplicate rendering

Fixes openclaw/openclaw#33751

* Feishu: preserve media sends on duplicate finals and add media synthesis changelog

* Feishu: only dedupe exact duplicate final replies

* Feishu: use scoped plugin-sdk import in streaming-card tests

---------

Co-authored-by: 倪汉杰0668001185 <ni.hanjie@xydigit.com>
Co-authored-by: zhengquanliu <zhengquanliu@bytedance.com>
Co-authored-by: nick <nickzj@qq.com>
Co-authored-by: linhey <linhey@mini.local>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-04 20:39:44 -06:00
Madoka 63ce7c74bd
fix(feishu): comprehensive reply mechanism — outbound replyToId forwarding + topic-aware reply targeting (#33789)
* fix(feishu): comprehensive reply mechanism fix — outbound replyToId forwarding + topic-aware reply targeting

- Forward replyToId from ChannelOutboundContext through sendText/sendMedia
  to sendMessageFeishu/sendMarkdownCardFeishu/sendMediaFeishu, enabling
  reply-to-message via the message tool.

- Fix group reply targeting: use ctx.messageId (triggering message) in
  normal groups to prevent silent topic thread creation (#32980). Preserve
  ctx.rootId targeting for topic-mode groups (group_topic/group_topic_sender)
  and groups with explicit replyInThread config.

- Add regression tests for both fixes.

Fixes #32980
Fixes #32958
Related #19784

* fix: normalize Feishu delivery.to before comparing with messaging tool targets

- Add normalizeDeliveryTarget helper to strip user:/chat: prefixes for Feishu
- Apply normalization in matchesMessagingToolDeliveryTarget before comparison
- This ensures cron duplicate suppression works when session uses prefixed targets
  (user:ou_xxx) but messaging tool extract uses normalized bare IDs (ou_xxx)

Fixes review comment on PR #32755

(cherry picked from commit fc20106f16)

* fix(feishu): catch thrown SDK errors for withdrawn reply targets

The Feishu Lark SDK can throw exceptions (SDK errors with .code or
AxiosErrors with .response.data.code) for withdrawn/deleted reply
targets, in addition to returning error codes in the response object.

Wrap reply calls in sendMessageFeishu and sendCardFeishu with
try-catch to handle thrown withdrawn/not-found errors (230011,
231003) and fall back to client.im.message.create, matching the
existing response-level fallback behavior.

Also extract sendFallbackDirect helper to deduplicate the
direct-send fallback block across both functions.

Closes #33496

(cherry picked from commit ad0901aec1)

* feishu: forward outbound reply target context

(cherry picked from commit c129a691fcf552a1cebe1e8a22ea8611ffc3b377)

* feishu extension: tighten reply target fallback semantics

(cherry picked from commit f85ec610f267020b66713c09e648ec004b2e26f1)

* fix(feishu): align synthesized fallback typing and changelog attribution

* test(feishu): cover group_topic_sender reply targeting

---------

Co-authored-by: Xu Zimo <xuzimojimmy@163.com>
Co-authored-by: Munem Hashmi <munem.hashmi@gmail.com>
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-04 20:32:28 -06:00
Isis Anisoptera 432e0222dd
fix: restore auto-reply system events timeline (#34794) (thanks @anisoptera) (#34794)
Co-authored-by: Ayaan Zaidi <zaidi@uplause.io>
2026-03-05 07:56:14 +05:30
Shakker 498948581a
docs(changelog): document dependency security fixes 2026-03-05 02:05:16 +00:00
Shakker 4d06c909d2
fix(deps): bump tar to 7.5.10 2026-03-05 02:00:18 +00:00
Ho Lim da0e245db6
fix(security): avoid prototype-chain account path checks (#34982)
Merged via squash.

Prepared head SHA: f89cc6a649
Co-authored-by: HOYALIM <166576253+HOYALIM@users.noreply.github.com>
Co-authored-by: dvrshil <81693876+dvrshil@users.noreply.github.com>
Reviewed-by: @dvrshil
2026-03-04 17:38:09 -08:00
Shakker 809f9513ac
fix(deps): patch hono transitive audit vulnerabilities 2026-03-04 23:34:36 +00:00
Darshil ed05810d68 fix: add spanish locale support (#35038) (thanks @DaoPromociones) 2026-03-04 15:29:52 -08:00
Darshil b3fb881a73 fix: finalize spanish locale support 2026-03-04 15:29:52 -08:00
Vincent Koc 9c6847074d Changelog: add gateway restart health entry (#34874) 2026-03-04 15:44:02 -05:00
Vincent Koc 8c5692ac4a Changelog: add daemon systemd user-bus fallback entry (#34884) 2026-03-04 15:44:02 -05:00
青雲 96021a2b17
fix: align AGENTS.md template section names with post-compaction extraction (#25029) (#25098)
Merged via squash.

Prepared head SHA: 8cd6cc8049
Co-authored-by: echoVic <16428813+echoVic@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-04 12:16:00 -08:00
Kai 4242c5152f
agents: preserve totalTokens on request failure instead of using contextWindow (#34275)
Merged via squash.

Prepared head SHA: f9d111d0a7
Co-authored-by: RealKai42 <44634134+RealKai42@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-04 12:02:22 -08:00
Vincent Koc 53b2479eed
Fix Linux daemon install checks when systemd user bus env is missing (#34884)
* daemon(systemd): fall back to machine user scope when user bus is missing

* test(systemd): cover machine scope fallback for user-bus errors

* test(systemd): reset execFile mock state across cases

* test(systemd): make machine-user fallback assertion portable

* fix(daemon): keep root sudo path on direct user scope

* test(systemd): cover sudo root user-scope behavior

* ci: use resolvable bun version in setup-node-env
2026-03-04 11:54:03 -08:00
Rodrigo Uroz df0f2e349f
Compaction/Safeguard: require structured summary headings (#25555)
Merged via squash.

Prepared head SHA: 0b1df34806
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-04 10:54:42 -08:00
Vincent Koc 2b98cb6d8b
Fix gateway restart false timeouts on Debian/systemd (#34874)
* daemon(systemd): target sudo caller user scope

* test(systemd): cover sudo user scope commands

* infra(ports): fall back to ss when lsof missing

* test(ports): verify ss fallback listener detection

* cli(gateway): use probe fallback for restart health

* test(gateway): cover restart-health probe fallback
2026-03-04 10:52:33 -08:00
Shakker 4cc293d084
fix(review): enforce behavioral sweep validation 2026-03-04 18:49:36 +00:00
Shakker 2123265c09 chore(changelog): clarify outbound media-only fallback openclaw#32788 thanks @liuxiaopai-ai 2026-03-04 18:42:21 +00:00
Shakker 698c200eba fix(outbound): fail media-only text-only adapter fallback 2026-03-04 18:42:21 +00:00
Shakker a970cae2da chore(changelog): align outbound adapter entry openclaw#32788 thanks @liuxiaopai-ai 2026-03-04 18:42:21 +00:00
liuxiaopai-ai bb07b2b93a Outbound: avoid empty multi-media fallback sends 2026-03-04 18:42:21 +00:00
liuxiaopai-ai efdf2ca0d7 Outbound: allow text-only plugin adapters 2026-03-04 18:42:21 +00:00
Shakker e6f0203ef3 chore(changelog): add PR entry openclaw#24337 thanks @echoVic 2026-03-04 16:39:54 +00:00
Shakker 7531a3e30a test(ollama): add default header precedence coverage 2026-03-04 16:39:54 +00:00
echoVic 7597fc556c fix(ollama): pass provider headers to Ollama stream function (#24285)
createOllamaStreamFn() only accepted baseUrl, ignoring custom headers
configured in models.providers.<provider>.headers. This caused 403
errors when Ollama endpoints are behind reverse proxies that require
auth headers (e.g. X-OLLAMA-KEY via HAProxy).

Add optional defaultHeaders parameter to createOllamaStreamFn() and
merge them into every fetch request. Provider headers from config are
now passed through at the call site in the embedded runner.

Fixes #24285
2026-03-04 16:39:54 +00:00
Gustavo Madeira Santana 76bfd9b5e6 Agents: add generic poll-vote action support 2026-03-04 11:36:14 -05:00
Sid c8ebd48e0f
fix(node-host): sync rawCommand with hardened argv after executable path pinning (#33137)
Merged via squash.

Prepared head SHA: a7987905f7
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-04 11:30:33 -05:00
a 4fb40497d4
fix(daemon): handle systemctl is-enabled exit 4 (not-found) on Ubuntu (#33634)
Merged via squash.

Prepared head SHA: 67dffc3ee2
Co-authored-by: Yuandiaodiaodiao <33371662+Yuandiaodiaodiao@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-04 16:13:45 +00:00
Sid 3fa43ec221
fix(model): propagate custom provider headers to model objects (#27490)
Merged via squash.

Prepared head SHA: e4183b398f
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-04 16:02:29 +00:00
huangcj dc8253a84d
fix(memory): serialize local embedding initialization to avoid duplicate model loads (#15639)
Merged via squash.

Prepared head SHA: a085fc21a8
Co-authored-by: SubtleSpark <43933609+SubtleSpark@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-04 10:09:03 -05:00
Vincent Koc 88ee57124e
Delete changelog/fragments directory 2026-03-04 09:41:10 -05:00
Vincent Koc c1bb07bd16
fix(slack): route system events to bound agent sessions (#34045)
* fix(slack): route system events via binding-aware session keys

* fix(slack): pass sender to system event session resolver

* fix(slack): include sender context for interaction session routing

* fix(slack): include modal submitter in session routing

* test(slack): cover binding-aware system event routing

* test(slack): update interaction session key assertions

* test(slack): assert reaction session routing carries sender

* docs(changelog): note slack system event routing fix

* Update CHANGELOG.md
2026-03-04 08:44:07 -05:00
Ayaan Zaidi 7b5e64ef2e fix: preserve raw media invoke for HTTP tool clients (#34365) 2026-03-04 17:17:39 +05:30
Ayaan Zaidi ef4fa43df8 fix: prevent nodes media base64 context bloat (#34332) 2026-03-04 16:53:30 +05:30
Ayaan Zaidi ed8e0a8146 docs(changelog): credit @Brotherinlaw-13 for #34318 2026-03-04 16:27:48 +05:30
Ayaan Zaidi 3cc1d5a92f fix(telegram): materialize dm draft final to avoid duplicates 2026-03-04 16:27:48 +05:30
Bob 257e2f5338
fix: relay ACP sessions_spawn parent streaming (#34310) (thanks @vincentkoc) (#34310)
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
2026-03-04 11:44:20 +01:00
Bob 61f7cea48b
fix: kill stuck ACP child processes on startup and harden sessions in discord threads (#33699)
* Gateway: resolve agent.wait for chat.send runs

* Discord: harden ACP thread binding + listener timeout

* ACPX: handle already-exited child wait

* Gateway/Discord: address PR review findings

* Discord: keep ACP error-state thread bindings on startup

* gateway: make agent.wait dedupe bridge event-driven

* discord: harden ACP probe classification and cap startup fan-out

* discord: add cooperative timeout cancellation

* discord: fix startup probe concurrency helper typing

* plugin-sdk: avoid Windows root-alias shard timeout

* plugin-sdk: keep root alias reflection path non-blocking

* discord+gateway: resolve remaining PR review findings

* gateway+discord: fix codex review regressions

* Discord/Gateway: address Codex review findings

* Gateway: keep agent.wait lifecycle active with shared run IDs

* Discord: clean up status reactions on aborted runs

* fix: add changelog note for ACP/Discord startup hardening (#33699) (thanks @dutifulbob)

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-04 10:52:28 +01:00
Mariano bd25182d5a
feat(ios): add Live Activity connection status + stale cleanup (#33591)
* feat(ios): add live activity connection status and cleanup

Add lock-screen/Dynamic Island connection health states and prune duplicate/stale activities before reuse. This intentionally excludes AI/title generation and heavier UX rewrites from #27488.

Co-authored-by: leepokai <1663017+leepokai@users.noreply.github.com>

* fix(ios): treat ended live activities as inactive

* chore(changelog): add PR reference and author thanks

---------

Co-authored-by: leepokai <1663017+leepokai@users.noreply.github.com>
2026-03-04 07:44:42 +00:00
Gustavo Madeira Santana 6a40f69d4d chore(docs): add plugins refactor changelog entry 2026-03-04 02:39:11 -05:00
Gustavo Madeira Santana ad9ceafec2 Chore: remove accidental .DS_Store artifact 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana 5c4ab999b0 Plugins/zalouser: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana e9c7bb6e15 Plugins/zalo: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana d25bf0d0ca Plugins/whatsapp: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana bbf29201b8 Plugins/voice-call: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana a9af933486 Plugins/twitch: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana 72e774431c Plugins/tlon: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana 7c96d82112 Plugins/thread-ownership: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana 8377dbba30 Plugins/test-utils: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana f006c5f5c1 Plugins/talk-voice: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana 65ffa676a5 Plugins/synology-chat: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana 6521965e40 Plugins/qwen-portal-auth: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana 71e62a77e8 Plugins/phone-control: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana c1c1af9d7b Plugins/open-prose: migrate to scoped plugin-sdk imports 2026-03-04 02:35:13 -05:00
Gustavo Madeira Santana 3dda4aaf08 Plugins/nostr: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 20ed90f1ba Plugins/nextcloud-talk: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana adb400f9b1 Plugins/msteams: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana e42d345aee Plugins/minimax-portal-auth: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 6b19b7f37a Plugins/memory-lancedb: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 61a2a3417f Plugins/memory-core: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana b192276283 Plugins/mattermost: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana b69b2a7ae0 Plugins/matrix: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana a5f56e8b4e Plugins/lobster: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana ccd2d7dc27 Plugins/llm-task: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 7b8e36583f Plugins/irc: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana a1e21bc02d Plugins/googlechat: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 5174b38626 Plugins/google-gemini-cli-auth: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 3e1ca111af Plugins/feishu: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana ed85754722 Plugins/diffs: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 54d78bb423 Plugins/diagnostics-otel: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 04385a61b7 Plugins/device-pair: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 04ff4a0c26 Plugins/copilot-proxy: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 9cfec9c05e Plugins/bluebubbles: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana c7c25c8902 Plugins/acpx: migrate to scoped plugin-sdk imports 2026-03-04 02:35:12 -05:00
Gustavo Madeira Santana 7a2f5a0098 Plugin SDK: add full bundled subpath wiring 2026-03-04 02:35:12 -05:00
Lynn 9d941949c9
fix(tui): normalize session key to lowercase to match gateway canonicalization (#34013)
Merged via squash.

Prepared head SHA: cfe06ca131
Co-authored-by: lynnzc <6257996+lynnzc@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-04 09:53:38 +03:00
Gustavo Madeira Santana 26e014311f Extensions: migrate acpx plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana 37a8caee42 Extensions: migrate zalouser plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana dda86af866 Extensions: migrate zalo plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana b361cac753 Extensions: migrate voice-call plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana 9d102b762e Extensions: migrate twitch plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana b0bca8d6e9 Extensions: migrate tlon plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana 9bf08c926b Extensions: migrate test-utils plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana 7a9754c927 Extensions: migrate telegram plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana 96b0fce27c Extensions: migrate synology-chat plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana de05186ad7 Extensions: migrate qwen-portal-auth plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana 612ca670da Extensions: migrate nostr plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana ed29472af6 Extensions: migrate nextcloud-talk plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana 10bd6ae3c8 Extensions: migrate msteams plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana b2188092a1 Extensions: migrate minimax-portal-auth plugin-sdk imports 2026-03-04 01:21:30 -05:00
Gustavo Madeira Santana 009d4d115a Extensions: migrate mattermost plugin-sdk imports 2026-03-04 01:21:21 -05:00
Gustavo Madeira Santana 15f7e329c2 Extensions: migrate matrix plugin-sdk imports 2026-03-04 01:20:49 -05:00
Gustavo Madeira Santana b7df821372 Extensions: migrate lobster plugin-sdk imports 2026-03-04 01:20:49 -05:00
Gustavo Madeira Santana d9b8ec5afa Extensions: migrate llm-task plugin-sdk imports 2026-03-04 01:20:49 -05:00
Gustavo Madeira Santana 9b6101e382 Extensions: migrate irc plugin-sdk imports 2026-03-04 01:20:49 -05:00
Gustavo Madeira Santana 39a55844bc Extensions: migrate googlechat plugin-sdk imports 2026-03-04 01:20:49 -05:00
Gustavo Madeira Santana b4f60d900b Extensions: migrate google-gemini-cli-auth plugin-sdk imports 2026-03-04 01:20:48 -05:00
Gustavo Madeira Santana 1ebd1fdb2d Extensions: migrate feishu plugin-sdk imports 2026-03-04 01:20:48 -05:00
Gustavo Madeira Santana 73de1d038e Extensions: migrate diffs plugin-sdk imports 2026-03-04 01:20:48 -05:00
Gustavo Madeira Santana 56d98a50cf Extensions: migrate diagnostics-otel plugin-sdk imports 2026-03-04 01:20:48 -05:00
Gustavo Madeira Santana 2bb63868c6 Extensions: migrate device-pair plugin-sdk imports 2026-03-04 01:20:48 -05:00
Gustavo Madeira Santana ff38bc7649 Extensions: migrate bluebubbles plugin-sdk imports 2026-03-04 01:20:48 -05:00
Gustavo Madeira Santana 802b9f6b19 Plugins: add root-alias shim and cache/docs updates 2026-03-04 01:20:48 -05:00
Josh Avant 646817dd80
fix(outbound): unify resolved cfg threading across send paths (#33987) 2026-03-04 00:20:44 -06:00
Vincent Koc 4d183af0cf
fix: code/cli acpx reliability 20260304 (#34020)
* agents: switch claude-cli defaults to bypassPermissions

* agents: add claude-cli default args coverage

* agents: emit watchdog stall system event for cli runs

* agents: test cli watchdog stall system event

* acpx: fallback to sessions new when ensure returns no ids

* acpx tests: mock sessions new fallback path

* acpx tests: cover ensure-empty fallback flow

* skills: clarify claude print mode without pty

* docs: update cli-backends claude default args

* docs: refresh cli live test default args

* gateway tests: align live claude args defaults

* changelog: credit claude/acpx reliability fixes

* Agents: normalize legacy Claude permission flag overrides

* Tests: cover legacy Claude permission override normalization

* Changelog: note legacy Claude permission flag auto-normalization

* ACPX: fail fast when ensure/new return no session IDs

* ACPX tests: support empty sessions new fixture output

* ACPX tests: assert ensureSession failure when IDs missing

* CLI runner: scope watchdog heartbeat wake to session

* CLI runner tests: assert session-scoped watchdog wake

* Update CHANGELOG.md
2026-03-04 01:15:28 -05:00
Vincent Koc dfb4cb87f9
plugins: avoid peer auto-install dependency bloat (#34017)
* plugins/install: omit peer deps during plugin npm install

* tests: assert plugin install omits peer deps

* extensions/googlechat: mark openclaw peer optional

* extensions/memory-core: mark openclaw peer optional
2026-03-03 22:00:15 -08:00
Dale Yarborough a95a0be133
feat(slack): add typingReaction config for DM typing indicator fallback (#19816)
* feat(slack): add typingReaction config for DM typing indicator fallback

Adds a reaction-based typing indicator for Slack DMs that works without
assistant mode. When `channels.slack.typingReaction` is set (e.g.
"hourglass_flowing_sand"), the emoji is added to the user's message when
processing starts and removed when the reply is sent.

Addresses #19809

* test(slack): add typingReaction to createSlackMonitorContext test callers

* test(slack): add typingReaction to test context callers

* test(slack): add typingReaction to context fixture

* docs(changelog): credit Slack typingReaction feature

* test(slack): align existing-thread history expectation

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-03 21:07:17 -08:00
Kesku 230fea1ca6
feat(web-search): switch Perplexity to native Search API (#33822)
* feat: Add Perplexity Search API as web_search provider

* docs fixes

* domain_filter validation

* address comments

* provider-specific options in cache key

* add validation for unsupported date filters

* legacy fields

* unsupported_language guard

* cache key matches the request's precedence order

* conflicting_time_filters guard

* unsupported_country guard

* invalid_date_range guard

* pplx validate for ISO 639-1 format

* docs: add Perplexity Search API changelog entry

* unsupported_domain_filter guard

---------

Co-authored-by: Shadow <hi@shadowing.dev>
2026-03-03 22:57:19 -06:00
Ayaan Zaidi d5a7a32826 docs(changelog): credit #31513 in #33647 entry 2026-03-04 10:20:59 +05:30
Tak Hoffman b4e4e25e74
fix(gateway): narrow legacy route inheritance for custom session keys (openclaw#33932) thanks @Takhoffman
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-03 22:45:46 -06:00
Vincent Koc 4bc466422f
Deps: fix pnpm audit vulnerabilities in Google extension path (#33939)
* extensions/googlechat: require openclaw 2026.3.2+

* extensions/memory-core: require openclaw 2026.3.2+

* deps: bump fast-xml-parser override to 5.3.8

* deps: refresh lockfile for audit vulnerability fixes
2026-03-03 20:44:05 -08:00
Ayaan Zaidi 6962d2d79f fix: harden sessions_spawn attachment schema landing (#33648) (thanks @anisoptera) 2026-03-04 10:05:41 +05:30
Isis Anisoptera 965ce31d84 fix(sessions-spawn): remove maxLength from attachment content schema to fix llama.cpp GBNF grammar overflow 2026-03-04 10:05:41 +05:30
Tak Hoffman 8a7d1aa973
fix(gateway): preserve route inheritance for legacy channel session keys (openclaw#33919) thanks @Takhoffman
Verified:
- pnpm build
- pnpm check
- pnpm test src/gateway/server-methods/chat.directive-tags.test.ts
- pnpm test:macmini

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-03 22:27:36 -06:00
Ayaan Zaidi f74a04e4ba fix: tighten telegram topic-agent docs + fallback tests (#33647) (thanks @kesor) 2026-03-04 09:35:53 +05:30
Evgeny Zislis 8eeb049683 fix(telegram): address PR review comments
- Export pickFirstExistingAgentId and use it to validate topic agentId
- Properly update mainSessionKey when overriding route agent
- Fix docs example showing incorrect session key for topic 3

Fixes issue where non-existent agentId would create orphaned sessions.
Fixes issue where DM topic replies would route to wrong agent.
2026-03-04 09:35:53 +05:30
Evgeny Zislis 58bc9a241b feat(telegram): add per-topic agent routing for forum groups [AI-assisted]
This feature allows different topics within a Telegram forum supergroup to route
to different agents, each with isolated workspace, memory, and sessions.

Key changes:
- Add agentId field to TelegramTopicConfig type for per-topic routing
- Add zod validation for agentId in topic config schema
- Implement routing logic to re-derive session key with topic's agent
- Add debug logging for topic agent overrides
- Add unit tests for routing behavior (forum topics + DM topics)
- Add config validation tests
- Document feature in docs/channels/telegram.md

This builds on the approach from PR #31513 by @Sid-Qin with additional fixes
for security (preserved account fail-closed guard) and test coverage.

Closes #31473
2026-03-04 09:35:53 +05:30
Tak Hoffman 7f2708a8c3
fix(routing): unify session delivery invariants for duplicate suppression (#33786)
* Routing: unify session delivery invariants

* Routing: address PR review feedback

* Routing: tighten topic and session-scope suppression

* fix(chat): inherit routes for per-account channel-peer sessions
2026-03-03 21:40:38 -06:00
Tak Hoffman 1be39d4250
fix(gateway): synthesize lifecycle robustness for restart and startup probes (#33831)
* fix(gateway): correct launchctl command sequence for gateway restart (closes #20030)

* fix(restart): expand HOME and escape label in launchctl plist path

* fix(restart): poll port free after SIGKILL to prevent EADDRINUSE restart loop

When cleanStaleGatewayProcessesSync() kills a stale gateway process,
the kernel may not immediately release the TCP port. Previously the
function returned after a fixed 500ms sleep (300ms SIGTERM + 200ms
SIGKILL), allowing triggerOpenClawRestart() to hand off to systemd
before the port was actually free. The new systemd process then raced
the dying socket for port 18789, hit EADDRINUSE, and exited with
status 1, causing systemd to retry indefinitely — the zombie restart
loop reported in #33103.

Fix: add waitForPortFreeSync() that polls lsof at 50ms intervals for
up to 2 seconds after SIGKILL. cleanStaleGatewayProcessesSync() now
blocks until the port is confirmed free (or the budget expires with a
warning) before returning. The increased SIGTERM/SIGKILL wait budgets
(600ms / 400ms) also give slow processes more time to exit cleanly.

Fixes #33103
Related: #28134

* fix: add EADDRINUSE retry and TIME_WAIT port-bind checks for gateway startup

* fix(ports): treat EADDRNOTAVAIL as non-retryable and fix flaky test

* fix(gateway): hot-reload agents.defaults.models allowlist changes

The reload plan had a rule for `agents.defaults.model` (singular) but
not `agents.defaults.models` (plural — the allowlist array).  Because
`agents.defaults.models` does not prefix-match `agents.defaults.model.`,
it fell through to the catch-all `agents` tail rule (kind=none), so
allowlist edits in openclaw.json were silently ignored at runtime.

Add a dedicated reload rule so changes to the models allowlist trigger
a heartbeat restart, which re-reads the config and serves the updated
list to clients.

Fixes #33600

Co-authored-by: HCL <chenglunhu@gmail.com>
Signed-off-by: HCL <chenglunhu@gmail.com>

* test(restart): 100% branch coverage — audit round 2

Audit findings fixed:
- remove dead guard: terminateStaleProcessesSync pids.length===0 check was
  unreachable (only caller cleanStaleGatewayProcessesSync already guards)
- expose __testing.callSleepSyncRaw so sleepSync's real Atomics.wait path
  can be unit-tested directly without going through the override
- fix broken sleepSync Atomics.wait test: previous test set override=null
  but cleanStaleGatewayProcessesSync returned before calling sleepSync —
  replaced with direct callSleepSyncRaw calls that actually exercise L36/L42-47
- fix pid collision: two tests used process.pid+304 (EPERM + dead-at-SIGTERM);
  EPERM test changed to process.pid+305
- fix misindented tests: 'deduplicates pids' and 'lsof status 1 container
  edge case' were outside their intended describe blocks; moved to correct
  scopes (findGatewayPidsOnPortSync and pollPortOnce respectively)
- add missing branch tests:
  - status 1 + non-empty stdout with zero openclaw pids → free:true (L145)
  - mid-loop non-openclaw cmd in &&-chain (L67)
  - consecutive p-lines without c-line between them (L67)
  - invalid PID in p-line (p0 / pNaN) — ternary false branch (L67)
  - unknown lsof output line (else-if false branch L69)

Coverage: 100% stmts / 100% branch / 100% funcs / 100% lines (36 tests)

* test(restart): fix stale-pid test typing for tsgo

* fix(gateway): address lifecycle review findings

* test(update): make restart-helper path assertions windows-safe

---------

Signed-off-by: HCL <chenglunhu@gmail.com>
Co-authored-by: Glucksberg <markuscontasul@gmail.com>
Co-authored-by: Efe Büken <efe@arven.digital>
Co-authored-by: Riccardo Marino <rmarino@apple.com>
Co-authored-by: HCL <chenglunhu@gmail.com>
2026-03-03 21:31:12 -06:00
Tak Hoffman 87e6ce7c3a
fix(extensions): synthesize mediaLocalRoots propagation across sendMedia adapters
Restore deterministic mediaLocalRoots propagation through extension sendMedia adapters and add coverage for local/remote media handling in Google Chat.

Synthesis of #33581, #33545, #33540, #33536, #33528.

Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
2026-03-03 21:30:41 -06:00
Tak Hoffman 9889c6da53
Runtime: stabilize tool/run state transitions under compaction and backpressure
Synthesize runtime state transition fixes for compaction tool-use integrity and long-running handler backpressure.

Sources: #33630, #33583

Co-authored-by: Kevin Shenghui <shenghuikevin@gmail.com>
Co-authored-by: Theo Tarr <theodore@tarr.com>
2026-03-03 21:25:32 -06:00
Ayaan Zaidi 575bd77196 fix: stabilize telegram draft boundary previews (#33842) (thanks @ngutman) 2026-03-04 08:55:27 +05:30
Gustavo Madeira Santana 5ce53095c5 fix(tlon): use HTTPS git URL for api-beta 2026-03-03 22:14:37 -05:00
Gustavo Madeira Santana 1278ee9248 plugin-sdk: add channel subpaths and migrate bundled plugins 2026-03-03 22:07:03 -05:00
Josh Avant 1c200ca7ae
follow-up: align ingress, atomic paths, and channel tests with credential semantics (#33733)
Merged via squash.

Prepared head SHA: c290c2ab6a
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-03 20:29:46 -06:00
Gustavo Madeira Santana 6842877b2e build: prevent mixed static/dynamic pi-model-discovery imports 2026-03-03 21:27:14 -05:00
Gustavo Madeira Santana b10f438221 Config: harden legacy heartbeat key migration 2026-03-03 20:42:35 -05:00
wan.xi caa748b969
fix(config): detect top-level heartbeat as invalid config path (#30894) (#32706)
Merged via squash.

Prepared head SHA: 1714ffe6fc
Co-authored-by: xiwan <931632+xiwan@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-03 20:27:04 -05:00
LiaoyuanNing b7589b32a8
fix(feishu): support SecretRef-style env credentials in account resolver (#30903)
Merged via squash.

Prepared head SHA: d3d0a18f17
Co-authored-by: LiaoyuanNing <259494737+LiaoyuanNing@users.noreply.github.com>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
Reviewed-by: @joshavant
2026-03-03 19:22:50 -06:00
Gustavo Madeira Santana 21e8d88c1d
build: fix ineffective dynamic imports with lazy boundaries (#33690)
Merged via squash.

Prepared head SHA: 38b3c23d6f
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-03 20:14:41 -05:00
Igal Tabachnik a4850b1b8f
fix(plugins): lazily initialize runtime and split plugin-sdk startup imports (#28620)
Merged via squash.

Prepared head SHA: 8bd7d6c13b
Co-authored-by: hmemcpy <601206+hmemcpy@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-03 19:58:48 -05:00
habakan 4b17d6d882
feat(gateway): add Permissions-Policy header to default security headers (#30186)
Merged via squash.

Prepared head SHA: 0dac89283f
Co-authored-by: habakan <12531644+habakan@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-03 16:25:39 -08:00
Gustavo Madeira Santana 0d97101665 Agents: preserve bootstrap warning dedupe across followup runs 2026-03-03 18:56:11 -05:00
liquidhorizon88-bot d95cf256e7
Security audit: suggest valid gateway.nodes.denyCommands entries (#29713)
Merged via squash.

Prepared head SHA: db23298f98
Co-authored-by: liquidhorizon88-bot <257047709+liquidhorizon88-bot@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-03 15:47:57 -08:00
Cui Chen e8cb0484ce
fix(security): strip partial API token from status labels (#33262)
Merged via squash.

Prepared head SHA: 5fe81704e6
Co-authored-by: cu1ch3n <80438676+cu1ch3n@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-03 15:11:49 -08:00
Clawdoo b1a735829d
docs: fix Mintlify-incompatible links in security docs (#27698)
Merged via squash.

Prepared head SHA: 6078cd94ba
Co-authored-by: clawdoo <65667097+clawdoo@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-03 14:51:28 -08:00
Mariano 2a733a8444
fix(ios): harden watch messaging activation concurrency (#33306)
Merged via squash.

Prepared head SHA: d40f8c4afb
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-03 22:38:54 +00:00
Mariano 4c6dec84a6
Telegram/device-pair: auto-arm one-shot notify on /pair qr with manual fallback (#33299)
Merged via squash.

Prepared head SHA: 0986691fd4
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-03 22:36:45 +00:00
Mariano a36ccf4156
fix(ios): start incremental speech at soft boundaries (#33305)
Merged via squash.

Prepared head SHA: d1acf72317
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-03 22:36:40 +00:00
Mariano 22e33ddda9
fix(ios): guard talk TTS callbacks to active utterance (#33304)
Merged via squash.

Prepared head SHA: dd88886e41
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-03 22:34:09 +00:00
13otKmdr a8dd9ffea1
security: add X-Content-Type-Options nosniff header to media route (#30356)
Merged via squash.

Prepared head SHA: b14f9ad7ca
Co-authored-by: 13otKmdr <154699144+13otKmdr@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-03 13:35:46 -08:00
wangchunyue bcd58c26d3
fix(logging ): use local timezone for console log timestamps (#25970)
Merged via squash.

Prepared head SHA: 30123265b7
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-04 00:31:41 +03:00
Gustavo Madeira Santana e4b4486a96
Agent: unify bootstrap truncation warning handling (#32769)
Merged via squash.

Prepared head SHA: 5d6d4ddfa6
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-03 16:28:38 -05:00
Sid 3ad3a90db3
fix(gateway): include disk-scanned agent IDs in listConfiguredAgentIds (#32831)
Merged via squash.

Prepared head SHA: 2aa58f6afd
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-03 21:19:18 +00:00
Shakker b02a07655d
fix: harden pr review artifact validation 2026-03-03 21:14:37 +00:00
joshavant a9969e641a
docs: fix secretref marker rendering in credential surface 2026-03-03 15:08:41 -06:00
scoootscooob ff96e41c38
fix(discord): align DiscordAccountConfig.token type with SecretInput (#32490)
Merged via squash.

Prepared head SHA: 233aa032f1
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
Reviewed-by: @joshavant
2026-03-03 14:59:57 -06:00
Robin Waslander 44162e7ba5
docs(contributing): require before/after screenshots for UI PRs (#32206)
Merged via squash.

Prepared head SHA: d7f0914873
Co-authored-by: hydro13 <6640526+hydro13@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-03 23:45:19 +03:00
dorukardahan 2cd3be896d
docs(security): document Docker UFW hardening via DOCKER-USER (#27613)
Merged via squash.

Prepared head SHA: 31ddd43326
Co-authored-by: dorukardahan <35905596+dorukardahan@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06
2026-03-03 12:28:35 -08:00
joshavant 490670128b
fix(docs): avoid MDX regex markers in secretref page 2026-03-03 14:00:09 -06:00
joshavant 70c6bc8581
fix(docs): use MDX-safe secretref markers 2026-03-03 13:54:03 -06:00
Shadow 65816657c2
feat(discord): add allowBots mention gating 2026-03-03 12:47:25 -06:00
Shadow b0bcea03db
fix: drop discord opus dependency 2026-03-03 12:23:19 -06:00
Shadow 16ebbd24b5
fix(discord): reset thread sessions on archive 2026-03-03 11:32:59 -06:00
Shadow b8b1eeb052
fix(discord): harden slash command routing 2026-03-03 11:32:05 -06:00
Shadow 0eef7a367d
fix(discord): honor agent media roots in replies 2026-03-03 11:29:58 -06:00
Shadow 548b15d8e0
fix(discord): skip bot messages before debounce 2026-03-03 11:29:58 -06:00
Shadow 05446d6b6b
docs: document discord ignoreOtherMentions 2026-03-03 11:26:20 -06:00
Shadow e28ff1215c
fix: discord auto presence health signal (#33277) (thanks @thewilloftheshadow) (#33277) 2026-03-03 11:20:59 -06:00
Ayaan Zaidi 3d998828b9
fix: stabilize Telegram draft boundaries and suppress NO_REPLY lead leaks (#33169)
* fix: stabilize telegram draft stream message boundaries

* fix: suppress NO_REPLY lead-fragment leaks

* fix: keep underscore guard for non-NO_REPLY prefixes

* fix: skip assistant-start rotation only after real lane rotation

* fix: preserve finalized state when pre-rotation does not force

* fix: reset finalized preview state on message-start boundary

* fix: document Telegram draft boundary + NO_REPLY reliability updates (#33169) (thanks @obviyus)
2026-03-03 22:49:33 +05:30
Shadow a7a9a3d3c8
fix: allowlist Discord CDN hostnames for SSRF media (#33275) (thanks @thewilloftheshadow) (#33275) 2026-03-03 11:17:27 -06:00
Mariano bf7061092a
iOS Security Stack 4/5: TTS PCM->MP3 Fallback (#30885) (#33032)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: f77e3d7644
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-03 16:33:55 +00:00
Shadow d493861c16
fix: discord mention handling (#33224) (thanks @thewilloftheshadow) (#33224) 2026-03-03 10:32:22 -06:00
Mariano a3112d6c5f
iOS Security Stack 3/5: Runtime Security Guards (#33031)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 9917165401
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-03 16:30:27 +00:00
Mariano 6df57d9633
iOS Security Stack 2/5: Concurrency Locks (#33241)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: b99ad804fb
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-03 16:28:27 +00:00
Shadow 3ee8528b17
test(discord): align bound-thread target kind 2026-03-03 10:22:52 -06:00
Shadow 3b3738e41e
fix(discord): use fetch for voice upload slots 2026-03-03 10:22:28 -06:00
Shadow 66d06beec6
fix(discord): stop typing after silent runs 2026-03-03 10:22:27 -06:00
Shadow 5d16d45b20
fix(discord): default presence online when unconfigured 2026-03-03 10:22:27 -06:00
Shadow 6593a57607
fix: improve discord chunk delivery (#33226) (thanks @thewilloftheshadow) (#33226) 2026-03-03 10:17:33 -06:00
Mariano ec0eb9f8c3
iOS Security Stack 1/5: Keychain Migrations + Tests (#33029)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: da2f8f6141
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-03 16:15:20 +00:00
Jason L. West, Sr. 606cd0d591
feat(tool-truncation): use head+tail strategy to preserve errors during truncation (#20076)
Merged via squash.

Prepared head SHA: 6edebf22b1
Co-authored-by: jlwestsr <52389+jlwestsr@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-03 08:11:14 -08:00
Mylszd d89e1e40f9
docs(loop-detection): fix config keys to match schema (#33182)
Merged via squash.

Prepared head SHA: 612ecc00d3
Co-authored-by: Mylszd <23611557+Mylszd@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-03 11:02:30 -05:00
Shadow ca307c3fdf
fix: harden Discord channel resolution (#33142) (thanks @thewilloftheshadow) (#33142) 2026-03-03 09:31:26 -06:00
Shadow 4abf398a17
fix: Discord acp inline actions + bound-thread filter (#33136) (thanks @thewilloftheshadow) (#33136) 2026-03-03 09:30:21 -06:00
Shadow 8e2e4b2ed5
fix: ignore discord wildcard audit keys (#33125) (thanks @thewilloftheshadow) (#33125) 2026-03-03 09:28:30 -06:00
Rodrigo Uroz c8b45a4c5c
Compaction/Safeguard: preserve recent turns verbatim (#25554)
Merged via squash.

Prepared head SHA: 7fb33c411c
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-03 07:00:49 -08:00
Shadow 171f305c3d
chore: note about pagination 2026-03-03 08:35:29 -06:00
chengzhichao-xydt 53727c72f4
fix: substitute YYYY-MM-DD at session startup and post-compaction (#32363) (#32381)
Merged via squash.

Prepared head SHA: aee998a2c1
Co-authored-by: chengzhichao-xydt <264300353+chengzhichao-xydt@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-03 06:21:26 -08:00
OpenCils 3fe4c19305
fix(telegram): prevent duplicate messages in DM draft streaming mode (#32118)
* fix(telegram): prevent duplicate messages in DM draft streaming mode

When using sendMessageDraft for DM streaming (streaming: 'partial'),
the draft bubble auto-converts to the final message. The code was
incorrectly falling through to sendPayload() after the draft was
finalized, causing a duplicate message.

This fix checks if we're in draft preview mode with hasStreamedMessage
and skips the sendPayload call, returning "preview-finalized" directly.

Key changes:
- Use hasStreamedMessage flag instead of previewRevision comparison
- Avoids double stopDraftLane calls by returning early
- Prevents duplicate messages when final text equals last streamed text

Root cause: In lane-delivery.ts, the final message handling logic
did not properly handle the DM draft flow where sendMessageDraft
creates a transient bubble that doesn't need a separate final send.

* fix(telegram): harden DM draft finalization path

* fix(telegram): require emitted draft preview for unchanged finals

* fix(telegram): require final draft text emission before finalize

* fix: update changelog for telegram draft finalization (#32118) (thanks @OpenCils)

---------

Co-authored-by: Ayaan Zaidi <zaidi@uplause.io>
2026-03-03 17:34:46 +05:30
Altay 627813aba4
fix(heartbeat): scope exec wake dispatch to session key (#32724)
Merged via squash.

Prepared head SHA: 563fee0e65
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-03 14:47:40 +03:00
Ayaan Zaidi 1ded5cc9a9 fix: guard malformed Telegram replies and pass hook accountId 2026-03-03 17:01:04 +05:30
Ayaan Zaidi 5f95f46070 docs: update changelog for telegram message_sent fix (#32649) 2026-03-03 16:56:20 +05:30
Ayaan Zaidi 5b8fc68ea2 fix(telegram): include reply hook metadata 2026-03-03 16:56:20 +05:30
KimGLee 9830b7c298 fix(telegram): mark message_sent success only when delivery occurred 2026-03-03 16:56:20 +05:30
KimGLee 6d118ab815 fix(telegram): run outbound message hooks in reply delivery path 2026-03-03 16:56:20 +05:30
Nimrod Gutman 4aa548cf7d
macOS: add tailscale serve discovery fallback for remote gateways (#32860)
* feat(macos): add tailscale serve gateway discovery fallback

* fix: add changelog note for tailscale serve discovery fallback (#32860) (thanks @ngutman)
2026-03-03 13:25:36 +02:00
Sid 4ffe15c6b2
fix(telegram): warn when accounts.default is missing in multi-account setup (#32544)
Merged via squash.

Prepared head SHA: 7ebc3f65b2
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-03 03:27:19 -05:00
Gustavo Madeira Santana 2370ea5d1b agents: propagate config for embedded skill loading 2026-03-03 02:44:56 -05:00
Liu Xiaopai ae29842158
Gateway: fix stale self version in status output (#32655)
Merged via squash.

Prepared head SHA: b9675d1f90
Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-03 02:41:52 -05:00
Muhammed Mukhthar CM b1b41eb443
feat(mattermost): add native slash command support (refresh) (#32467)
Merged via squash.

Prepared head SHA: 989126574e
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm
2026-03-03 12:39:18 +05:30
Eugene 5341b5c71c
Diffs: Migrate tool usage guidance from before_prompt_build to a plugin skill (#32630)
Merged via squash.

Prepared head SHA: 585697a4e1
Co-authored-by: sircrumpet <4436535+sircrumpet@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-03 01:50:59 -05:00
Henry Loenwind 997197c6c9
bug: Workaround for QMD upstream bug (#27028)
Merged via squash.

Prepared head SHA: 939f9f4574
Co-authored-by: HenryLoenwind <1485873+HenryLoenwind@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-03 01:48:43 -05:00
JT de9031da22
fix: improve compaction summary instructions to preserve active work (#8903)
fix: improve compaction summary instructions to preserve active work

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.

Co-authored-by: joetomasone <56984887+joetomasone@users.noreply.github.com>
Co-authored-by: Josh Lehman <josh@martian.engineering>
2026-03-02 22:36:19 -08:00
Henry Loenwind 75775f2fe6
chore: Updated Brave documentation (#26860)
Merged via squash.

Prepared head SHA: f8fc4bf01e
Co-authored-by: HenryLoenwind <1485873+HenryLoenwind@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-03 01:34:15 -05:00
Tak Hoffman dbccc73d7a
security(line): synthesize strict LINE auth boundary hardening
LINE auth boundary hardening synthesis for inbound webhook authn/z/authz:
- account-scoped pairing-store access
- strict DM/group allowlist boundary separation
- fail-closed webhook auth/runtime behavior
- replay and duplicate handling with in-flight continuity for concurrent redeliveries

Source PRs: #26701, #26683, #25978, #17593, #16619, #31990, #26047, #30584, #18777
Related continuity context: #21955

Co-authored-by: bmendonca3 <208517100+bmendonca3@users.noreply.github.com>
Co-authored-by: davidahmann <46606159+davidahmann@users.noreply.github.com>
Co-authored-by: harshang03 <58983401+harshang03@users.noreply.github.com>
Co-authored-by: haosenwang1018 <167664334+haosenwang1018@users.noreply.github.com>
Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: coygeek <65363919+coygeek@users.noreply.github.com>
Co-authored-by: lailoo <20536249+lailoo@users.noreply.github.com>
2026-03-03 00:21:15 -06:00
Peter Steinberger fe92113472 test(e2e): isolate module mocks across harnesses 2026-03-03 05:52:14 +00:00
Peter Steinberger 1d7a287cf6 fix(telegram): debounce forwarded media-only bursts 2026-03-03 05:52:14 +00:00
Peter Steinberger 094140bdb1 test(live): harden gateway model profile probes 2026-03-03 05:52:14 +00:00
Peter Steinberger b52c9f2575 fix(ci): handle disabled systemd units in docker doctor flow 2026-03-03 05:52:14 +00:00
Peter Steinberger de62ccbf81 fix(test): stabilize appcast version assertion 2026-03-03 05:51:50 +00:00
Tak Hoffman 9a5bfb1fe5
fix(line): synthesize media/auth/routing webhook regressions (openclaw#32546) thanks @Takhoffman
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 23:47:56 -06:00
Viz 0b3bbfec06
fix(gateway+acp): thread stopReason through final event to ACP bridge (#24867)
Complete the stop reason propagation chain so ACP clients can
distinguish end_turn from max_tokens:

- server-chat.ts: emitChatFinal accepts optional stopReason param,
  includes it in the final payload, reads it from lifecycle event data
- translator.ts: read stopReason from the final payload instead of
  hardcoding end_turn

Chain: LLM API → run.ts (meta.stopReason) → agent.ts (lifecycle event)
→ server-chat.ts (final payload) → ACP translator (PromptResponse)
2026-03-03 00:40:54 -05:00
Peter Steinberger b34530a05d docs(changelog): reattribute duplicated PR credits 2026-03-03 05:40:05 +00:00
Peter Steinberger e1503349c3 fix: scope extension runtime deps to plugin manifests 2026-03-03 05:33:12 +00:00
Shadow 2a888c5703
ci: enable stale workflow 2026-03-02 23:21:34 -06:00
Peter Steinberger 786ff6afca chore(release): bump to 2026.3.3 and seed changelog 2026-03-03 05:12:23 +00:00
Peter Steinberger 2d67c9b2a0 fix: repair Feishu reset hook typing and stabilize secret resolver timeout 2026-03-03 05:06:08 +00:00
Viz a9ec75fe81
fix(gateway): flush throttled delta before emitChatFinal (#24856)
* fix(gateway): flush throttled delta before emitChatFinal

The 150ms throttle in emitChatDelta can suppress the last text chunk
before emitChatFinal fires, causing streaming clients (e.g. ACP) to
receive truncated responses. The final event carries the complete text,
but clients that build responses incrementally from deltas miss the
tail end.

Flush one last unthrottled delta with the complete buffered text
immediately before sending the final event. This ensures all streaming
consumers have the full response without needing to reconcile deltas
against the final payload.

* fix(gateway): avoid duplicate delta flush when buffer unchanged

Track the text length at the time of the last broadcast. The flush in
emitChatFinal now only sends a delta if the buffer has grown since the
last broadcast, preventing duplicate sends when the final delta passed
the 150ms throttle and was already broadcast.

* fix(gateway): honor heartbeat suppression in final delta flush

* test(gateway): add final delta flush and dedupe coverage

* fix(gateway): skip final flush for silent lead fragments

* docs(changelog): note gateway final-delta flush fix credits

---------

Co-authored-by: Jonathan Taylor <visionik@pobox.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-02 23:45:46 -05:00
dongdong 0566845b71
fix(feishu): validate outbound renderMode routing with tests (#31562)
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 22:41:01 -06:00
Jealous 9083a3f2e3
fix(feishu): normalize all mentions in inbound agent context (#30252)
* fix(feishu): normalize all mentions in inbound agent context

Convert Feishu mention placeholders to explicit <at user_id="..."> tags (including bot mentions), add mention semantics hints for the model, and remove unused mentionMessageBody parsing to keep context handling consistent.

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

* fix(feishu): use replacer callback and escape only < > in normalizeMentions

Switch String.replace to a function replacer to prevent $ sequences in
display names from being interpolated as replacement patterns. Narrow
escaping to < and > only — & does not need escaping in LLM prompt tag
bodies and escaping it degrades readability (e.g. R&D → R&amp;D).

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

* fix(feishu): only use open_id in normalizeMentions tag, drop user_id fallback

When a mention has no open_id, degrade to @name instead of emitting
<at user_id="uid_...">. This keeps the tag user_id space exclusively
open_id, so the bot self-reference hint (which uses botOpenId) is
always consistent with what appears in the tags.

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

* fix(feishu): register mention strip pattern for <at> tags in channel dock

Add mentions.stripPatterns to feishuPlugin so that normalizeCommandBody
receives a slash-clean string after normalizeMentions replaces Feishu
placeholders with <at user_id="...">name</at> tags. Without this,
group slash commands like @Bot /help had their leading / obscured by
the tag prefix and no longer triggered command handlers.

Pattern mirrors the approach used by Slack (<@[^>]+>) and Discord (<@!?\d+>).

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

* fix(feishu): strip bot mention in p2p to preserve DM slash commands

In p2p messages the bot mention is a pure addressing prefix; converting
it to <at user_id="..."> breaks slash commands because buildCommandContext
skips stripMentions for DMs. Extend normalizeMentions with a stripKeys
set and populate it with bot mention keys in p2p, so @Bot /help arrives
as /help. Non-bot mentions (mention-forward targets) are still normalized
to <at> tags in both p2p and group contexts.

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

* Changelog: note Feishu inbound mention normalization

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 22:40:17 -06:00
Peter Steinberger 85377a2817 chore(release): cut 2026.3.2 2026-03-03 04:35:46 +00:00
Vincent Koc d45aa68ae8 CI: disable flaky sticky disk mount for Windows pnpm setup 2026-03-02 20:34:10 -08:00
Vincent Koc be5de30de5 CI: start push test lanes earlier and drop check gating 2026-03-02 20:29:06 -08:00
挨踢小茶 406e7aba75
fix(feishu): guard against false-positive @mentions in multi-app groups (#30315)
* fix(feishu): guard against false-positive @mentions in multi-app groups

When multiple Feishu bot apps share a group chat, Feishu's WebSocket
event delivery remaps the open_id in mentions[] per-app. This causes
checkBotMentioned() to return true for ALL bots when only one was
actually @mentioned, making requireMention ineffective.

Add a botName guard: if the mention's open_id matches this bot but the
mention's display name differs from this bot's configured botName, treat
it as a false positive and skip.

botName is already available via account.config.botName (set during
onboarding).

Closes #24249

* fix(feishu): support @all mention in multi-bot groups

When a user sends @all (@_all in Feishu message content), treat it as
mentioning every bot so all agents respond when requireMention is true.

Feishu's @all does not populate the mentions[] array, so this needs
explicit content-level detection.

* fix(feishu): auto-fetch bot display name from API for reliable mention matching

Instead of relying on the manually configured botName (which may differ
from the actual Feishu bot display name), fetch the bot's display name
from the Feishu API at startup via probeFeishu().

This ensures checkBotMentioned() always compares against the correct
display name, even when the config botName doesn't match (e.g. config
says 'Wanda' but Feishu shows '绯红女巫').

Changes:
- monitor.ts: fetchBotOpenId → fetchBotInfo (returns both openId and name)
- monitor.ts: store botNames map, pass botName to handleFeishuMessage
- bot.ts: accept botName from params, prefer it over config fallback

* Changelog: note Feishu multi-app mention false-positive guard

---------

Co-authored-by: Teague Xiao <teaguexiao@TeaguedeMac-mini.local>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 22:27:35 -06:00
Andy Tien cad06faafe
fix: add session-memory hook support for Feishu provider (#31437)
* fix: add session-memory hook support for Feishu provider

Issue #31275: Session-memory hook not triggered when using /new command in Feishu

- Added command handler to Feishu provider
- Integrated with OpenClaw's before_reset hook system
- Ensures session memory is saved when /new or /reset commands are used

* Changelog: note Feishu session-memory hook parity

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 22:19:24 -06:00
Huaqing.Hao a5a7239182
fix(feishu): non-blocking WS ACK and preserve full streaming card content (#29616)
* fix(feishu): non-blocking ws ack and preserve streaming card full content

* fix(feishu): preserve fragmented streaming text without newline artifacts

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 22:17:15 -06:00
Vincent Koc a5a6952bf2 CI: reduce critical path for check build and windows jobs 2026-03-02 20:11:28 -08:00
Vincent Koc d28fa50f8b CI: make node deps install optional in setup action 2026-03-02 20:11:28 -08:00
Vincent Koc 5ef04d2822 CI: speed up Windows dependency warmup 2026-03-02 20:11:12 -08:00
Peter Steinberger bb5796265b docs(changelog): remove docs-only 2026.3.2 entries 2026-03-03 04:07:40 +00:00
Tian Wei 7c179f9288
feishu, line: pass per-group systemPrompt to inbound context (#31713)
* feishu: pass per-group systemPrompt to inbound context

The Feishu extension schema supports systemPrompt in per-group config
(channels.feishu.accounts.<id>.groups.<groupId>.systemPrompt) but the
value was never forwarded to the inbound context as GroupSystemPrompt.

This means per-group system prompts configured for Feishu had no effect,
unlike IRC, Discord, Slack, Telegram, Matrix, and other channels that
already pass this field correctly.

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

* line: pass per-group systemPrompt to inbound context

Same issue as feishu: the Line config schema defines systemPrompt in
per-group config but the value was never forwarded as GroupSystemPrompt
in the inbound context payload.

Added resolveLineGroupSystemPrompt helper that mirrors the existing
resolveLineGroupConfig lookup logic (groupId > roomId > wildcard).

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

* Changelog: note Feishu and LINE group systemPrompt propagation

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 22:07:35 -06:00
Peter Steinberger d9d604c6ad docs: add dedicated pdf tool docs page 2026-03-03 04:07:04 +00:00
Tak Hoffman 6cdfd2eaaa
fix(feishu): correct invalid scope name in permission grant URL (#32509)
* fix(feishu): correct invalid scope name in permission grant URL

The Feishu API returns error code 99991672 with an authorization URL
containing the non-existent scope `contact:contact.base:readonly`
when the `contact.user.get` endpoint is called without the correct
permission. The valid scope is `contact:user.base:readonly`.

Add a scope correction map that replaces known incorrect scope names
in the extracted grant URL before presenting it to the user/agent,
so the authorization link actually works.

Closes #31761

* chore(changelog): note feishu scope correction

---------

Co-authored-by: SidQin-cyber <sidqin0410@gmail.com>
2026-03-02 22:06:42 -06:00
Peter Steinberger b3b4fd30c3 chore(release): update appcast for 2026.3.2-beta.1 2026-03-03 03:58:35 +00:00
Vincent Koc a951ecdd7b CI: shard Windows tests into sixths and skip cache restore 2026-03-02 19:54:52 -08:00
Vincent Koc c6634b4083 CI: add toggle to skip pnpm actions cache restore 2026-03-02 19:54:52 -08:00
Peter Steinberger 524fb16619 fix(gateway): skip google rate limits in live suite 2026-03-03 03:48:09 +00:00
青雲 1fdc20a24f
refactor(feishu): unify Lark SDK error handling with LarkApiError (#31450)
* refactor(feishu): unify Lark SDK error handling with LarkApiError

- Add LarkApiError class with code, api, and context fields for better diagnostics
- Add ensureLarkSuccess helper to replace 9 duplicate error check patterns
- Update tool registration layer to return structured error info (code, api, context)

This improves:
- Observability: errors now include API name and request context for easier debugging
- Maintainability: single point of change for error handling logic
- Extensibility: foundation for retry strategies, error classification, etc.

Affected APIs:
- wiki.space.getNode
- bitable.app.get
- bitable.app.create
- bitable.appTableField.list
- bitable.appTableField.create
- bitable.appTableRecord.list
- bitable.appTableRecord.get
- bitable.appTableRecord.create
- bitable.appTableRecord.update

* Changelog: note Feishu bitable error handling unification

---------

Co-authored-by: echoVic <echovic@163.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 21:44:40 -06:00
Vincent Koc 925da0fe99
Delete changelog/fragments directory 2026-03-02 19:43:23 -08:00
Peter Steinberger 99ae722e57 fix(ci): complete feishu route mock typing in broadcast tests 2026-03-03 03:42:30 +00:00
Peter Steinberger eb8a8840d6 chore(release): prepare 2026.3.2-beta.1 2026-03-03 03:38:49 +00:00
Runkun Miao 7c6f8bfe73
feat(feishu): add broadcast support for multi-agent groups (#29575)
* feat(feishu): add broadcast support for multi-agent group observation

When multiple agents share a Feishu group chat, only the @mentioned
agent receives the message. This prevents observer agents from building
session memory of group activity they weren't directly addressed in.

Adds broadcast support (reusing the same cfg.broadcast schema as
WhatsApp) so all configured agents receive every group message in their
session transcripts. Only the @mentioned agent responds on Feishu;
observer agents process silently via no-op dispatchers.

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

* fix(feishu): guard sequential broadcast dispatch against single-agent failure

Wrap each dispatchForAgent() call in the sequential loop with try/catch
so one agent's dispatch failure doesn't abort delivery to remaining agents.

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

* fix(feishu): avoid duplicate messages in broadcast observer mode and normalize agent IDs

- Skip recordPendingHistoryEntryIfEnabled for broadcast groups when not
  mentioned, since the message is dispatched directly to all agents.
  Previously the message appeared twice in the agent prompt.
- Normalize agent IDs with toLowerCase() before membership checks so
  config casing mismatches don't silently skip valid agents.

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

* fix(feishu): set WasMentioned per-agent and normalize broadcast IDs

- buildCtxPayloadForAgent now takes a wasMentioned parameter so active
  agents get WasMentioned=true and observers get false (P1 fix)
- Normalize broadcastAgents to lowercase at resolution time and
  lowercase activeAgentId so all comparisons and session key generation
  use canonical IDs regardless of config casing (P2 fix)

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

* fix(feishu): canonicalize broadcast agent IDs with normalizeAgentId

* fix(feishu): match ReplyDispatcher sync return types for noop dispatcher

The upstream ReplyDispatcher changed sendToolResult/sendBlockReply/
sendFinalReply to synchronous (returning boolean). Update the broadcast
observer noop dispatcher to match.

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

* fix(feishu): deduplicate broadcast agent IDs after normalization

Config entries like "Main" and "main" collapse to the same canonical ID
after normalizeAgentId but were dispatched multiple times. Use Set to
deduplicate after normalization.

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

* fix(feishu): honor requireMention=false when selecting broadcast responder

When requireMention is false, the routed agent should be active (reply
on Feishu) even without an explicit @mention. Previously activeAgentId
was null whenever ctx.mentionedBot was false, so all agents got the
noop dispatcher and no reply was sent — silently breaking groups that
disabled mention gating.

Hoist requireMention out of the if(isGroup) block so it's accessible
in the dispatch code.

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

* fix(feishu): cross-account broadcast dedup to prevent duplicate dispatches

In multi-account Feishu setups, the same message event is delivered to
every bot account in a group. Without cross-account dedup, each account
independently dispatches broadcast agents, causing 2×N dispatches instead
of N (where N = number of broadcast agents).

Two changes:
1. requireMention=true + bot not mentioned: return early instead of
   falling through to broadcast. The mentioned bot's handler will
   dispatch for all agents. Non-mentioned handlers record to history.
2. Add cross-account broadcast dedup using a shared 'broadcast' namespace
   (tryRecordMessagePersistent). The first handler to reach the broadcast
   block claims the message; subsequent accounts skip. This handles the
   requireMention=false multi-account case.

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

* fix(feishu): strip CommandAuthorized from broadcast observer contexts

Broadcast observer agents inherited CommandAuthorized from the sender,
causing slash commands (e.g. /reset) to silently execute on every observer
session. Now only the active agent retains CommandAuthorized; observers
have it stripped before dispatch.

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

* fix(feishu): use actual mention state for broadcast WasMentioned

The active broadcast agent's WasMentioned was set to true whenever
requireMention=false, even when the bot was not actually @mentioned.
Now uses ctx.mentionedBot && agentId === activeAgentId, consistent
with the single-agent path which passes ctx.mentionedBot directly.

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

* fix(feishu): skip history buffer for broadcast accounts and log parallel failures

1. In requireMention groups with broadcast, non-mentioned accounts no
   longer buffer pending history — the mentioned handler's broadcast
   dispatch already writes turns into all agent sessions. Buffering
   caused duplicate replay via buildPendingHistoryContextFromMap.

2. Parallel broadcast dispatch now inspects Promise.allSettled results
   and logs rejected entries, matching the sequential path's per-agent
   error logging.

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

* Changelog: note Feishu multi-agent broadcast dispatch

* Changelog: restore author credit for Feishu broadcast entry

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 21:38:46 -06:00
Peter Steinberger 92c4a2a29e fix(gateway): retry exec-read live tool probe 2026-03-03 03:36:55 +00:00
Peter Steinberger 70ab91500a test(ci): add changed-scope shell-injection regression 2026-03-03 03:34:51 +00:00
Peter Steinberger f175a5d6d3 fix(ci): avoid shell interpolation in changed-scope git diff 2026-03-03 03:34:46 +00:00
xbsheng 02d26ced98
docs(feishu): Feishu docs – add verificationToken and align zh-CN with EN (openclaw#31555) thanks @xbsheng
Verified:
- pnpm build
- pnpm test:macmini
- pnpm check (blocked locally by pre-existing mainline lint issue in src/scripts/ci-changed-scope.test.ts unrelated to this PR)

Co-authored-by: xbsheng <56357338+xbsheng@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 21:33:41 -06:00
Vincent Koc 99a48aad08 CI: increase checks-windows test shards to 4 2026-03-02 19:32:46 -08:00
Vincent Koc 8b80848ae9 CI: increase checks-windows test shards to 3 2026-03-02 19:31:27 -08:00
Vincent Koc 153a4f55db CI: reduce pre-test Windows setup latency 2026-03-02 19:30:29 -08:00
Vincent Koc 578a7a82be CI: add exact-key mode for pnpm cache restore 2026-03-02 19:30:29 -08:00
Sid e6f34b25aa
fix(feishu): preserve block streaming text when final payload is missing (#30663)
* fix(feishu): preserve block streaming text when final payload is missing

When Feishu card streaming receives block payloads without matching final/partial
callbacks, keep block text in stream state so onIdle close still publishes the
reply instead of an empty message. Add a regression test for block-only streaming.

Closes #30628

* Feishu: preserve streaming block fallback when final text is missing

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 21:26:21 -06:00
Peter Steinberger 17bb87f432 fix(venice): retry model discovery on transient fetch failures 2026-03-03 03:21:00 +00:00
joshavant 85a320de54
docs(changelog): add SecretRef note for #29580 2026-03-02 21:19:17 -06:00
Peter Steinberger 46b62c53f0 fix(ci): restore scope-test require import and sync host policy 2026-03-03 03:18:45 +00:00
Peter Steinberger ca1b50908f chore(gitignore): ignore android kotlin cache 2026-03-03 03:13:23 +00:00
Vincent Koc 05aa16c040 CI: allow blacksmith 32 vCPU Windows runner in actionlint 2026-03-02 19:13:14 -08:00
Vincent Koc 2c6616b830
CI: gate Windows checks by windows-relevant scope (#32456)
* CI: add windows scope output for changed-scope

* Test: cover windows scope gating in changed-scope

* CI: gate checks-windows by windows scope

* Docs: update CI windows scope and runner label

* CI: move checks-windows to 32 vCPU runner

* Docs: align CI windows runner with workflow
2026-03-02 19:10:58 -08:00
Peter Steinberger 80efcb75c7 style(swift): apply lint and format cleanup 2026-03-03 03:07:55 +00:00
Peter Steinberger ba50dfaae3 refactor(macos): simplify pairing alert and host helper paths 2026-03-03 03:07:54 +00:00
Peter Steinberger 04a8f97c57 fix(swift): align async helper callsites across iOS and macOS 2026-03-03 03:07:54 +00:00
Peter Steinberger 5cba9a6bab test: load ci changed-scope script via esm import 2026-03-03 03:06:22 +00:00
Peter Steinberger da6e6fb900 test: fix strict runtime mock types in channel tests 2026-03-03 03:06:22 +00:00
Peter Steinberger 805de8537c fix(telegram): move unchanged command-sync log to verbose 2026-03-03 03:05:39 +00:00
Peter Steinberger f7f0caa5c7 fix(ci): tighten type signatures in gateway params validation 2026-03-03 03:04:13 +00:00
Peter Steinberger 7fd4328854 fix(e2e): include shared tool display resource in onboard docker build 2026-03-03 03:02:27 +00:00
Peter Steinberger 7bad42910b docs: reorder unreleased changelog by user interest 2026-03-03 03:00:37 +00:00
Vincent Koc f2c37e543e CI: optimize Windows lane by splitting bundle and dropping duplicate lanes 2026-03-02 18:58:43 -08:00
Josh Avant 806803b7ef
feat(secrets): expand SecretRef coverage across user-supplied credentials (#29580)
* feat(secrets): expand secret target coverage and gateway tooling

* docs(secrets): align gateway and CLI secret docs

* chore(protocol): regenerate swift gateway models for secrets methods

* fix(config): restore talk apiKey fallback and stabilize runner test

* ci(windows): reduce test worker count for shard stability

* ci(windows): raise node heap for test shard stability

* test(feishu): make proxy env precedence assertion windows-safe

* fix(gateway): resolve auth password SecretInput refs for clients

* fix(gateway): resolve remote SecretInput credentials for clients

* fix(secrets): skip inactive refs in command snapshot assignments

* fix(secrets): scope gateway.remote refs to effective auth surfaces

* fix(secrets): ignore memory defaults when enabled agents disable search

* fix(secrets): honor Google Chat serviceAccountRef inheritance

* fix(secrets): address tsgo errors in command and gateway collectors

* fix(secrets): avoid auth-store load in providers-only configure

* fix(gateway): defer local password ref resolution by precedence

* fix(secrets): gate telegram webhook secret refs by webhook mode

* fix(secrets): gate slack signing secret refs to http mode

* fix(secrets): skip telegram botToken refs when tokenFile is set

* fix(secrets): gate discord pluralkit refs by enabled flag

* fix(secrets): gate discord voice tts refs by voice enabled

* test(secrets): make runtime fixture modes explicit

* fix(cli): resolve local qr password secret refs

* fix(cli): fail when gateway leaves command refs unresolved

* fix(gateway): fail when local password SecretRef is unresolved

* fix(gateway): fail when required remote SecretRefs are unresolved

* fix(gateway): resolve local password refs only when password can win

* fix(cli): skip local password SecretRef resolution on qr token override

* test(gateway): cast SecretRef fixtures to OpenClawConfig

* test(secrets): activate mode-gated targets in runtime coverage fixture

* fix(cron): support SecretInput webhook tokens safely

* fix(bluebubbles): support SecretInput passwords across config paths

* fix(msteams): make appPassword SecretInput-safe in onboarding/token paths

* fix(bluebubbles): align SecretInput schema helper typing

* fix(cli): clarify secrets.resolve version-skew errors

* refactor(secrets): return structured inactive paths from secrets.resolve

* refactor(gateway): type onboarding secret writes as SecretInput

* chore(protocol): regenerate swift models for secrets.resolve

* feat(secrets): expand extension credential secretref support

* fix(secrets): gate web-search refs by active provider

* fix(onboarding): detect SecretRef credentials in extension status

* fix(onboarding): allow keeping existing ref in secret prompt

* fix(onboarding): resolve gateway password SecretRefs for probe and tui

* fix(onboarding): honor secret-input-mode for local gateway auth

* fix(acp): resolve gateway SecretInput credentials

* fix(secrets): gate gateway.remote refs to remote surfaces

* test(secrets): cover pattern matching and inactive array refs

* docs(secrets): clarify secrets.resolve and remote active surfaces

* fix(bluebubbles): keep existing SecretRef during onboarding

* fix(tests): resolve CI type errors in new SecretRef coverage

* fix(extensions): replace raw fetch with SSRF-guarded fetch

* test(secrets): mark gateway remote targets active in runtime coverage

* test(infra): normalize home-prefix expectation across platforms

* fix(cli): only resolve local qr password refs in password mode

* test(cli): cover local qr token mode with unresolved password ref

* docs(cli): clarify local qr password ref resolution behavior

* refactor(extensions): reuse sdk SecretInput helpers

* fix(wizard): resolve onboarding env-template secrets before plaintext

* fix(cli): surface secrets.resolve diagnostics in memory and qr

* test(secrets): repair post-rebase runtime and fixtures

* fix(gateway): skip remote password ref resolution when token wins

* fix(secrets): treat tailscale remote gateway refs as active

* fix(gateway): allow remote password fallback when token ref is unresolved

* fix(gateway): ignore stale local password refs for none and trusted-proxy

* fix(gateway): skip remote secret ref resolution on local call paths

* test(cli): cover qr remote tailscale secret ref resolution

* fix(secrets): align gateway password active-surface with auth inference

* fix(cli): resolve inferred local gateway password refs in qr

* fix(gateway): prefer resolvable remote password over token ref pre-resolution

* test(gateway): cover none and trusted-proxy stale password refs

* docs(secrets): sync qr and gateway active-surface behavior

* fix: restore stability blockers from pre-release audit

* Secrets: fix collector/runtime precedence contradictions

* docs: align secrets and web credential docs

* fix(rebase): resolve integration regressions after main rebase

* fix(node-host): resolve gateway secret refs for auth

* fix(secrets): harden secretinput runtime readers

* gateway: skip inactive auth secretref resolution

* cli: avoid gateway preflight for inactive secret refs

* extensions: allow unresolved refs in onboarding status

* tests: fix qr-cli module mock hoist ordering

* Security: align audit checks with SecretInput resolution

* Gateway: resolve local-mode remote fallback secret refs

* Node host: avoid resolving inactive password secret refs

* Secrets runtime: mark Slack appToken inactive for HTTP mode

* secrets: keep inactive gateway remote refs non-blocking

* cli: include agent memory secret targets in runtime resolution

* docs(secrets): sync docs with active-surface and web search behavior

* fix(secrets): keep telegram top-level token refs active for blank account tokens

* fix(daemon): resolve gateway password secret refs for probe auth

* fix(secrets): skip IRC NickServ ref resolution when NickServ is disabled

* fix(secrets): align token inheritance and exec timeout defaults

* docs(secrets): clarify active-surface notes in cli docs

* cli: require secrets.resolve gateway capability

* gateway: log auth secret surface diagnostics

* secrets: remove dead provider resolver module

* fix(secrets): restore gateway auth precedence and fallback resolution

* fix(tests): align plugin runtime mock typings

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-03 02:58:20 +00:00
Peter Steinberger f212351aed refactor(telegram): dedupe monitor retry test helpers 2026-03-03 02:53:14 +00:00
Peter Steinberger 6408b7f81c refactor(agents): dedupe steer restart test replacement flow 2026-03-03 02:53:14 +00:00
Peter Steinberger 1538813096 refactor(agents): dedupe ollama provider test scaffolding 2026-03-03 02:53:14 +00:00
Peter Steinberger 55c128ddc2 refactor(memory): dedupe readonly recovery test scenarios 2026-03-03 02:53:14 +00:00
Peter Steinberger 3ff0cf262d refactor(infra): dedupe update startup test setup 2026-03-03 02:53:14 +00:00
Peter Steinberger a50dd0bb06 refactor(infra): dedupe ssrf fetch guard test fixtures 2026-03-03 02:53:13 +00:00
Peter Steinberger 8b4cdbb21d refactor(infra): dedupe exec approval allowlist evaluation flow 2026-03-03 02:53:13 +00:00
Peter Steinberger b8181e5944 refactor(gateway): dedupe agents server-method handlers 2026-03-03 02:53:13 +00:00
Peter Steinberger 7a8232187b refactor(config): dedupe session store save error handling 2026-03-03 02:53:13 +00:00
Peter Steinberger 1a0036283d refactor(security): dedupe telegram allowlist validation loops 2026-03-03 02:53:13 +00:00
Peter Steinberger 4fb6da2b32 refactor(tests): dedupe canvas host server setup 2026-03-03 02:53:13 +00:00
Peter Steinberger 4a59d0ad98 refactor(tests): dedupe session store route fixtures 2026-03-03 02:53:13 +00:00
Peter Steinberger d068fc9f9d refactor(tests): dedupe agent handler test scaffolding 2026-03-03 02:53:13 +00:00
Peter Steinberger 369646a513 refactor(tests): dedupe openresponses http fixtures 2026-03-03 02:53:13 +00:00
Peter Steinberger 3460aa4dee refactor(browser): dedupe playwright interaction helpers 2026-03-03 02:53:13 +00:00
Peter Steinberger e290f4ca41 refactor(config): dedupe repeated zod schema shapes 2026-03-03 02:53:13 +00:00
Peter Steinberger 884ca65dc7 refactor(acp): dedupe runtime option command plumbing 2026-03-03 02:53:13 +00:00
Peter Steinberger 1a52d943ed refactor(tests): dedupe model compat assertions 2026-03-03 02:53:13 +00:00
Peter Steinberger 7897ffb72f refactor(memory): dedupe openai batch fetch flows 2026-03-03 02:53:13 +00:00
Peter Steinberger 5c18ba6f65 refactor(tests): dedupe gateway chat history fixtures 2026-03-03 02:53:13 +00:00
Peter Steinberger 25a2fe2bea refactor(tests): dedupe control ui auth pairing fixtures 2026-03-03 02:53:13 +00:00
Peter Steinberger fa4ff5f3d2 refactor(acp): extract install hint resolver 2026-03-03 02:51:24 +00:00
Peter Steinberger ac318be405 refactor(voice-call): unify runtime cleanup lifecycle 2026-03-03 02:51:17 +00:00
Peter Steinberger c85bd2646a refactor(cli): extract plugin install plan helper 2026-03-03 02:51:11 +00:00
Peter Steinberger 6472e03412 refactor(agents): share failover error matchers 2026-03-03 02:51:00 +00:00
Vincent Koc 24fd6c8278 CI: use Blacksmith docker builder in sandbox smoke 2026-03-02 18:48:18 -08:00
Vincent Koc 5cffbbda32 CI: use Blacksmith docker builder in install smoke 2026-03-02 18:48:18 -08:00
Vincent Koc 85d17fd429 CI: migrate docker release build cache to Blacksmith 2026-03-02 18:48:18 -08:00
Vincent Koc 96d56a9721 CI: enable sticky-disk pnpm cache on Linux CI jobs 2026-03-02 18:48:18 -08:00
Vincent Koc ffd3ad032a CI: add sticky-disk mode to pnpm cache action 2026-03-02 18:48:18 -08:00
Vincent Koc 8a463af823 CI: add sticky-disk toggle to setup node action 2026-03-02 18:48:18 -08:00
Peter Steinberger 6bf1abf603 ci: use valid Blacksmith Windows runner label 2026-03-03 02:47:06 +00:00
Josh Lehman 3a8133d587
fix(scripts/pr): SSH-first prhead remote with GraphQL fallback for fork PRs (#32126)
Co-authored-by: Shakker <shakkerdroid@gmail.com>
2026-03-03 02:46:01 +00:00
Peter Steinberger 8ac924c769 refactor(security): centralize audit execution context 2026-03-03 02:42:43 +00:00
Peter Steinberger 2d033d2aa8 refactor(agents): split tool-result char estimator 2026-03-03 02:42:43 +00:00
Peter Steinberger 1ec9673cc5 refactor(telegram): split lane preview target helpers 2026-03-03 02:42:43 +00:00
Peter Steinberger fdb0bf804f refactor(test): dedupe telegram draft-stream fixtures 2026-03-03 02:42:43 +00:00
Peter Steinberger 40f2e2b8a6 ci: scale Windows CI runner and test workers 2026-03-03 02:42:32 +00:00
Ayaan Zaidi 87977d7a19 fix: unblock build type errors 2026-03-03 08:11:51 +05:30
Peter Steinberger 9f691099db fix(voice-call): harden webhook lifecycle cleanup and retries (#32395) (thanks @scoootscooob) 2026-03-03 02:39:50 +00:00
scoootscooob e707c97ca6 fix(voice-call): prevent EADDRINUSE by guarding webhook server lifecycle
Three issues caused the port to remain bound after partial failures:

1. VoiceCallWebhookServer.start() had no idempotency guard — calling it
   while the server was already listening would create a second server on
   the same port.

2. createVoiceCallRuntime() did not clean up the webhook server if a step
   after webhookServer.start() failed (e.g. manager.initialize). The
   server kept the port bound while the runtime promise rejected.

3. ensureRuntime() cached the rejected promise forever, so subsequent
   calls would re-throw the same error without ever retrying. Combined
   with (2), the port stayed orphaned until gateway restart.

Fixes #32387

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 02:39:50 +00:00
Peter Steinberger 0750fc2de1 test: consolidate extension runtime mocks and split bluebubbles webhook auth suite 2026-03-03 02:37:23 +00:00
Peter Steinberger 59567a8c5d ci: move changed-scope logic into tested script 2026-03-03 02:37:23 +00:00
Peter Steinberger 8ee357fc76 refactor: extract session init helpers 2026-03-03 02:37:23 +00:00
Peter Steinberger 9702d94196 refactor: split plugin runtime type contracts 2026-03-03 02:37:23 +00:00
AI南柯(KingMo) 30ab9b2068
fix(agents): recognize connection errors as retryable timeout failures (#31697)
* fix(agents): recognize connection errors as retryable timeout failures

## Problem

When a model endpoint becomes unreachable (e.g., local proxy down,
relay server offline), the failover system fails to switch to the
next candidate model. Errors like "Connection error." are not
classified as retryable, causing the session to hang on a broken
endpoint instead of falling back to healthy alternatives.

## Root Cause

Connection/network errors are not recognized by the current failover
classifier:
- Text patterns like "Connection error.", "fetch failed", "network error"
- Error codes like ECONNREFUSED, ENOTFOUND, EAI_AGAIN (in message text)

While `failover-error.ts` handles these as error codes (err.code),
it misses them when they appear as plain text in error messages.

## Solution

Extend timeout error patterns to include connection/network failures:

**In `errors.ts` (ERROR_PATTERNS.timeout):**
- Text: "connection error", "network error", "fetch failed", etc.
- Regex: /\beconn(?:refused|reset|aborted)\b/i, /\benotfound\b/i, /\beai_again\b/i

**In `failover-error.ts` (TIMEOUT_HINT_RE):**
- Same patterns for non-assistant error paths

## Testing

Added test cases covering:
- "Connection error."
- "fetch failed"
- "network error: ECONNREFUSED"
- "ENOTFOUND" / "EAI_AGAIN" in message text

## Impact

- **Compatibility:** High - only expands retryable error detection
- **Behavior:** Connection failures now trigger automatic fallback
- **Risk:** Low - changes are additive and well-tested

* style: fix code formatting for test file
2026-03-03 02:37:23 +00:00
riftzen-bit 5e1a2ea019 chore: remove unreachable "LINUX" from resolvePlatform return type
Address review feedback: since resolvePlatform() no longer returns
"LINUX", remove it from the union type to prevent future confusion.
2026-03-03 02:36:01 +00:00
riftzen-bit 008e4804a6 fix(gemini-cli-auth): use PLATFORM_UNSPECIFIED for Linux in loadCodeAssist
Google's loadCodeAssist API rejects "LINUX" as an invalid Platform enum
value, causing OAuth setup to fail with 400 Bad Request on Linux systems.

The pi-ai runtime already uses "PLATFORM_UNSPECIFIED" for this field.
This aligns the extension's discoverProject() with that approach by
returning "PLATFORM_UNSPECIFIED" for Linux (and other non-Windows/macOS
platforms) instead of "LINUX".

Also fixes the original resolvePlatform() which incorrectly fell through
to "MACOS" as default instead of explicitly checking for "darwin".
2026-03-03 02:36:01 +00:00
AaronWander 4c32411bee fix(exec): suggest increasing timeout on timeouts 2026-03-03 02:35:10 +00:00
Gustavo Madeira Santana 91cdb703bd Agents: add context metadata warmup retry backoff 2026-03-02 21:34:55 -05:00
john 04ac688dff fix(acp): use publishable acpx install hint 2026-03-03 02:34:07 +00:00
苏敏童0668001043 b29e913efe fix(docker): correct awk quoting in Docker GPG fingerprint check (#32153) 2026-03-03 02:32:46 +00:00
Peter Steinberger 895abc5a64 perf(security): allow audit snapshot and summary cache reuse 2026-03-03 02:32:13 +00:00
Peter Steinberger 62582fc088 perf(agents): cache per-pass context char estimates 2026-03-03 02:32:13 +00:00
Peter Steinberger 57336203d5 test(telegram): move preview-finalization cases to lane unit tests 2026-03-03 02:32:13 +00:00
Peter Steinberger 1929151103 refactor(telegram): extract sequential key module 2026-03-03 02:32:13 +00:00
Peter Steinberger 6ab9e00e17 fix: resolve pi-tools typing regressions 2026-03-03 02:27:59 +00:00
Peter Steinberger 2380c1b5fd refactor(ui): dedupe inline code wrap rules 2026-03-03 02:19:34 +00:00
Peter Steinberger 493b560dfd refactor(runtime): unify node version guard parsing 2026-03-03 02:19:34 +00:00
Peter Steinberger 1dd77e4106 refactor(slack): extract socket reconnect policy helpers 2026-03-03 02:19:34 +00:00
Peter Steinberger 4d52dfe85b refactor(sessions): add explicit merge activity policies 2026-03-03 02:19:34 +00:00
Peter Steinberger d380ed710d refactor(agents): split pi-tools param and host-edit wrappers 2026-03-03 02:19:34 +00:00
Peter Steinberger 03755f8463 test(telegram): dedupe streaming cases and tighten sequential key checks 2026-03-03 02:14:15 +00:00
Peter Steinberger 7fdbf1202e test(security): reduce audit fixture setup overhead 2026-03-03 02:14:15 +00:00
Peter Steinberger 70db52de71 test(agents): centralize AgentMessage fixtures and remove unsafe casts 2026-03-03 02:14:15 +00:00
Gustavo Madeira Santana 15a0455d04 CLI: unify routed config positional parsing 2026-03-02 21:11:53 -05:00
Peter Steinberger d3c637d193 fix: recover host edit success after post-write upstream throw (#32383) (thanks @polooooo) 2026-03-03 02:06:59 +00:00
倪汉杰0668001185 0fb3f188b2 fix(agents): only recover edit when oldText no longer in file (review feedback) 2026-03-03 02:06:59 +00:00
倪汉杰0668001185 bf6aa7ca67 fix(agents): treat host edit tool as success when file contains newText after upstream throw (fixes #32333) 2026-03-03 02:06:59 +00:00
Peter Steinberger 0fd77c9856 refactor: modularize plugin runtime and test hooks 2026-03-03 02:06:58 +00:00
Peter Steinberger f77f1d3800 fix: preserve inline code copy fidelity in web ui (#32346) (thanks @hclsys) 2026-03-03 02:05:45 +00:00
HCL 7c90ef7c52 fix(webui): prevent inline code from breaking mid-token on copy/paste
The parent `.chat-text` applies `overflow-wrap: anywhere; word-break: break-word;`
which forces long tokens (UUIDs, hashes) inside inline `<code>` to break across
visual lines. When copied, the browser injects spaces at those break points,
corrupting the pasted value.

Override with `overflow-wrap: normal; word-break: keep-all;` on inline `<code>`
selectors so tokens stay intact.

Fixes #32230

Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-03 02:05:37 +00:00
Peter Steinberger 7dadd5027b fix: enforce node v22.12+ preflight for installer and runtime (#32356) (thanks @jasonhargrove) 2026-03-03 02:03:45 +00:00
Jason Hargrove f8ed48293c fix(cli): align Node 22.12 preflight checks and clean runtime guard output
Tighten installer/runtime consistency so users on Node 22.0-22.11 are blocked before install/runtime drift, with cleaner CLI guidance.

- Enforce Node >=22.12 in scripts/install.sh preflight checks
- Align installer messages to the same 22.12+ runtime floor
- Replace openclaw.mjs thrown version error with stderr+exit to avoid noisy stack traces
2026-03-03 02:03:45 +00:00
Jason Hargrove 96a38d5aa4 fix(cli): fail fast on unsupported Node versions in install and runtime paths
Surface a clear Node 22.12+ requirement before npm/install bootstrap work so users avoid misleading downstream errors.

- Add installer shell preflight to block active Node <22 and suggest NVM recovery commands
- Add openclaw.mjs runtime preflight for npm/npx usage with explicit Node version guidance
- Keep messaging actionable for both NVM and non-NVM environments
2026-03-03 02:03:45 +00:00
Peter Steinberger c7ec237089 fix: fail fast on non-recoverable slack auth errors (#32377) (thanks @scoootscooob) 2026-03-03 01:59:47 +00:00
scoootscooob 1ae82be55a fix(slack): fail fast on non-recoverable auth errors instead of retry loop
When a Slack bot is removed from a workspace while still configured in
OpenClaw, the gateway enters an infinite retry loop on account_inactive
or invalid_auth errors, making the entire gateway unresponsive.

Add isNonRecoverableSlackAuthError() to detect permanent credential
failures (account_inactive, invalid_auth, token_revoked, etc.) and
throw immediately instead of retrying.  This mirrors how the Telegram
provider already distinguishes recoverable network errors from fatal
auth errors via isRecoverableTelegramNetworkError().

The check is applied in both the startup catch block and the disconnect
reconnect path so stale credentials always fail fast with a clear error
message.

Closes #32366

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:59:47 +00:00
Peter Steinberger fd782d811e fix: preserve idle reset timestamp on inbound metadata writes (#32379) (thanks @romeodiaz) 2026-03-03 01:57:53 +00:00
romeodiaz a467517b2b fix(sessions): preserve idle reset timestamp on inbound metadata 2026-03-03 01:57:53 +00:00
nico-hoff 3eec79bd6c
feat(memory): add Ollama embedding provider (#26349)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: ac41386543
Co-authored-by: nico-hoff <43175972+nico-hoff@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-02 20:56:40 -05:00
Peter Steinberger 4ba5937ef9 refactor(tests): dedupe tools invoke http request helpers 2026-03-03 01:54:28 +00:00
Peter Steinberger 6fc3f504d6 refactor(tests): dedupe media transcript echo config setup 2026-03-03 01:54:28 +00:00
Peter Steinberger b17687b775 refactor(tests): dedupe security fix scenario helpers 2026-03-03 01:54:27 +00:00
Peter Steinberger eca242b971 refactor(tests): dedupe manifest registry link fixture setup 2026-03-03 01:54:27 +00:00
Peter Steinberger 4494844d17 refactor(tests): dedupe discord monitor e2e fixtures 2026-03-03 01:54:27 +00:00
Peter Steinberger 5193189953 refactor(tests): dedupe cron store migration setup 2026-03-03 01:54:27 +00:00
Peter Steinberger fbb88d5063 refactor(tests): dedupe isolated agent cron turn assertions 2026-03-03 01:54:27 +00:00
Peter Steinberger c0715db3c8 fix: add session hook context regression tests (#26394) (thanks @tempeste) 2026-03-03 01:48:46 +00:00
tempeste 20c15ccc63 Plugins: add sessionKey to session lifecycle hooks 2026-03-03 01:48:46 +00:00
Peter Steinberger 16fd604219 fix(security): pin tlon api source and secure hold music url 2026-03-03 01:45:24 +00:00
Peter Steinberger 61f29830bc fix(test): resolve upstream typing drift in feishu and cron suites 2026-03-03 01:44:21 +00:00
Peter Steinberger 47736e3432 refactor(test): extract cron issue-regression harness and frozen-time helper 2026-03-03 01:44:21 +00:00
Peter Steinberger 39520ad21b test(agents): tighten pi message typing and dedupe malformed tool-call cases 2026-03-03 01:44:21 +00:00
Sk Akram bd8c3230e8
fix: force supportsDeveloperRole=false for non-native OpenAI endpoints (#29479)
Merged via squash.

Prepared head SHA: 1416c584ac
Co-authored-by: akramcodez <179671552+akramcodez@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-02 20:43:49 -05:00
Peter Steinberger ebbb572639 fix: add requestHeartbeatNow runtime coverage (#19464) (thanks @AustinEral) 2026-03-03 01:40:31 +00:00
Austin Eral 3b9877dee7 fix: add requestHeartbeatNow to bluebubbles test mock 2026-03-03 01:40:31 +00:00
Austin Eral 40e5c6a18d feat(plugins): expose requestHeartbeatNow on plugin runtime
Add requestHeartbeatNow to PluginRuntime.system so extensions can
trigger an immediate heartbeat wake without importing internal modules.

This enables extensions to inject a system event and wake the agent
in one step — useful for inbound message handlers that use the
heartbeat model (e.g. agent-to-agent DMs via Nostr).

Changes:
- src/plugins/runtime/types.ts: add RequestHeartbeatNow type alias
  and requestHeartbeatNow to PluginRuntime.system
- src/plugins/runtime/index.ts: import and wire requestHeartbeatNow
  into createPluginRuntime()
2026-03-03 01:40:31 +00:00
David Rudduck 11e1363d2d
feat(hooks): add trigger and channelId to plugin hook agent context (#28623)
* feat(hooks): add trigger and channelId to plugin hook agent context

Adds `trigger` and `channelId` fields to `PluginHookAgentContext` so
plugins can determine what initiated the agent run and which channel
it originated from, without session-key parsing or Redis bridging.

trigger values: "user", "heartbeat", "cron", "memory"
channelId values: "telegram", "discord", "whatsapp", etc.

Both fields are threaded through run.ts and attempt.ts hookCtx so all
hook phases receive them (before_model_resolve, before_prompt_build,
before_agent_start, llm_input, llm_output, agent_end).

channelId falls back from messageChannel to messageProvider when the
former is not set. followup-runner passes originatingChannel so queued
followup runs also carry channel context.

* docs(changelog): note hook context parity fix for #28623

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-02 17:39:20 -08:00
Peter Steinberger ee646dae82 fix: add runtime.events regression tests (#16044) (thanks @scifantastic) 2026-03-03 01:37:56 +00:00
SciFantastic 85f01cd9eb Fix styles 2026-03-03 01:37:56 +00:00
SciFantastic bab5d994bc docs: expand JSDoc for onSessionTranscriptUpdate params and return 2026-03-03 01:37:56 +00:00
SciFantastic 2365c6c86a docs: add JSDoc to onSessionTranscriptUpdate 2026-03-03 01:37:56 +00:00
SciFantastic 53ada1e9b9 fix: add missing events property to bluebubbles PluginRuntime mock 2026-03-03 01:37:56 +00:00
SciFantastic b91a22a3fb style: fix indentation in transcript-events 2026-03-03 01:37:56 +00:00
SciFantastic 2aab6dff76 fix: wrap transcript event listeners in try/catch to prevent throw propagation 2026-03-03 01:37:56 +00:00
SciFantastic 980388fcf0 plugin-sdk: expose onAgentEvent + onSessionTranscriptUpdate via PluginRuntime.events 2026-03-03 01:37:56 +00:00
Peter Steinberger 3e6451f2d8 refactor(feishu): expose default-account selection source 2026-03-03 01:37:39 +00:00
Peter Steinberger 2f6718b8e7 refactor(gateway): extract channel health policy and timing aliases 2026-03-03 01:37:39 +00:00
Peter Steinberger b5350bf46f refactor(outbound): unify channel selection and action input normalization 2026-03-03 01:37:39 +00:00
Peter Steinberger 0f5f20ee6b refactor(tests): dedupe cron delivered status assertions 2026-03-03 01:37:12 +00:00
Peter Steinberger 6b6af1a64f refactor(tests): dedupe web fetch and embedded tool hook fixtures 2026-03-03 01:37:12 +00:00
Peter Steinberger c1b37f29f0 refactor(tests): dedupe browser and telegram tool test fixtures 2026-03-03 01:37:12 +00:00
Peter Steinberger a3b674cc98 refactor(tests): dedupe agent lock and loop detection fixtures 2026-03-03 01:37:12 +00:00
Brian Mendonca cdc1ef85e8
Feishu: cache failing probes (#29970)
* Feishu: cache failing probes

* Changelog: add Feishu probe failure backoff note

---------

Co-authored-by: bmendonca3 <208517100+bmendonca3@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 19:37:07 -06:00
Peter Steinberger 1ca69c8fd7 fix: add channelRuntime regression coverage (#25462) (thanks @guxiaobo) 2026-03-03 01:34:50 +00:00
Gu XiaoBo 469cd5b464 feat(plugin-sdk): Add channelRuntime support for external channel plugins
## Overview

This PR enables external channel plugins (loaded via Plugin SDK) to access
advanced runtime features like AI response dispatching, which were previously
only available to built-in channels.

## Changes

### src/gateway/server-channels.ts
- Import PluginRuntime type
- Add optional channelRuntime parameter to ChannelManagerOptions
- Pass channelRuntime to channel startAccount calls via conditional spread
- Ensures backward compatibility (field is optional)

### src/gateway/server.impl.ts
- Import createPluginRuntime from plugins/runtime
- Create and pass channelRuntime to channel manager

### src/channels/plugins/types.adapters.ts
- Import PluginRuntime type
- Add comprehensive documentation for channelRuntime field
- Document available features, use cases, and examples
- Improve type safety (use imported PluginRuntime type vs inline import)

## Benefits

External channel plugins can now:
- Generate AI-powered responses using dispatchReplyWithBufferedBlockDispatcher
- Access routing, text processing, and session management utilities
- Use command authorization and group policy resolution
- Maintain feature parity with built-in channels

## Backward Compatibility

- channelRuntime field is optional in ChannelGatewayContext
- Conditional spread ensures it's only passed when explicitly provided
- Existing channels without channelRuntime support continue to work unchanged
- No breaking changes to channel plugin API

## Testing

- Email channel plugin successfully uses channelRuntime for AI responses
- All existing built-in channels (slack, discord, telegram, etc.) work unchanged
- Gateway loads and runs without errors when channelRuntime is provided
2026-03-03 01:34:50 +00:00
Peter Steinberger 666073ee46 test: fix tsgo baseline test compatibility 2026-03-03 01:24:20 +00:00
Vincent Koc 747902a26a
fix(hooks): propagate run/tool IDs for tool hook correlation (#32360)
* Plugin SDK: add run and tool call fields to tool hooks

* Agents: propagate runId and toolCallId in before_tool_call

* Agents: thread runId through tool wrapper context

* Runner: pass runId into tool hook context

* Compaction: pass runId into tool hook context

* Agents: scope after_tool_call start data by run

* Tests: cover run and tool IDs in before_tool_call hooks

* Tests: add run-scoped after_tool_call collision coverage

* Hooks: scope adjusted tool params by run

* Tests: cover run-scoped adjusted param collisions

* Hooks: preserve active tool start metadata until end

* Changelog: add tool-hook correlation note
2026-03-02 17:23:08 -08:00
Peter Steinberger 61adcea68e fix(test): tighten tool result typing in context pruning tests 2026-03-03 01:18:29 +00:00
Peter Steinberger 5ee6ca13b7 docs(changelog): add landed notes for #32336 and #32364 2026-03-03 01:18:05 +00:00
Peter Steinberger 71cd337137 fix(gateway): harden message action channel fallback and startup grace
Take the safe, tested subset from #32367:\n- per-channel startup connect grace in health monitor\n- tool-context channel-provider fallback for message actions\n\nCo-authored-by: Munem Hashmi <munem.hashmi@gmail.com>
2026-03-03 01:17:27 +00:00
Peter Steinberger 4d04e1a41f fix(test): harden discord lifecycle status sink typing 2026-03-03 01:15:16 +00:00
Peter Steinberger 67e3eb85d7 refactor(tests): dedupe browser and config cli test setup 2026-03-03 01:15:09 +00:00
Peter Steinberger 1b4062defd refactor(tests): dedupe pi embedded test harness 2026-03-03 01:15:09 +00:00
Peter Steinberger 3e4dd84511 fix: webchat gfm table rendering and overflow (#32365) (thanks @BlueBirdBack) 2026-03-03 01:14:30 +00:00
Ash (Bug Lab) 5084621f43 fix(ui): ensure GFM tables render in WebChat markdown (#20410)
- Pass gfm:true + breaks:true explicitly to marked.parse() so table
  support is guaranteed even if global setOptions() is bypassed or
  reset by a future refactor (defense-in-depth)
- Add display:block + overflow-x:auto to .chat-text table so wide
  multi-column tables scroll horizontally instead of being clipped
  by the parent overflow-x:hidden chat container
- Add regression tests for GFM table rendering in markdown.test.ts
2026-03-03 01:14:30 +00:00
Peter Steinberger 346d3590fb fix(discord): harden voice ffmpeg path and opus fast-path 2026-03-03 01:14:15 +00:00
Peter Steinberger 687ef2e00f refactor(media): add shared ffmpeg helpers 2026-03-03 01:14:14 +00:00
Peter Steinberger 1187464041 fix: feishu default account outbound resolution (#32253) (thanks @bmendonca3) 2026-03-03 01:13:18 +00:00
bmendonca3 4e4a100038 Feishu: honor configured default account 2026-03-03 01:13:18 +00:00
Peter Steinberger ddd71bc9f6 fix: guard gemini schema null properties (#32332) (thanks @webdevtodayjason) 2026-03-03 01:12:06 +00:00
webdevtodayjason 1a7a18d0bc fix(agents): guard gemini tool schema properties against null 2026-03-03 01:12:06 +00:00
Peter Steinberger 4e4d94cd38 fix(test): satisfy auth profile secret ref typing in runtime tests 2026-03-03 01:12:01 +00:00
Peter Steinberger f0640b0100 fix(test): align gateway and session spawn hook typings 2026-03-03 01:12:01 +00:00
dongdong 46df7e2421
fix(feishu): skip typing indicator keepalive re-adds to prevent notification spam (#31580)
* fix(feishu): skip typing indicator keepalive re-adds to prevent notification spam

The typing keepalive loop calls addTypingIndicator() every 3 seconds,
which creates a new messageReaction.create API call each time. Feishu
treats each re-add as a new reaction event and fires a push notification,
causing users to receive repeated notifications while waiting for a
response.

Unlike Telegram/Discord where typing status expires after a few seconds,
Feishu reactions persist until explicitly removed. Skip the keepalive
re-add when a reaction already exists (reactionId is set) since there
is no need to refresh it.

Closes #28660

* Changelog: note Feishu typing keepalive suppression

---------

Co-authored-by: yuxh1996 <yuxh1996@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 19:11:47 -06:00
Peter Steinberger 42626648d7 docs(models): clarify moonshot thinking and failover stop-reason errors 2026-03-03 01:11:29 +00:00
Mitch McAlister 17b40c4a59 fix: guard isConnected check against already-aborted signal
When abortSignal is already aborted at lifecycle start, onAbort() fires
synchronously and pushes connected: false. Without a lifecycleStopping
guard, the subsequent gateway.isConnected check could push a spurious
connected: true, contradicting the shutdown.

Adds !lifecycleStopping to the isConnected guard and a test verifying
no connected: true is emitted when the signal is pre-aborted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:10:56 +00:00
Mitch McAlister d9119f0791 fix(discord): push connected status when gateway is already connected at lifecycle start
When the Discord gateway completes its READY handshake before
`runDiscordGatewayLifecycle` registers its debug event listener, the
initial "WebSocket connection opened" event is missed. This leaves
`connected` as undefined in the channel runtime, causing the health
monitor to treat the channel as "stuck" and restart it every check
cycle.

Check `gateway.isConnected` immediately after registering the debug
listener and push the initial connected status if the gateway is
already connected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:10:56 +00:00
HCL 586f057c24 fix(cron): let resolveOutboundTarget handle missing delivery target fallback
The cron delivery path short-circuits with an error when `toCandidate` is
falsy (line 151), before reaching `resolveOutboundTarget()` which provides
the `plugin.config.resolveDefaultTo()` fallback. The direct send path in
`targets.ts` already uses this fallback correctly.

Remove the early `!toCandidate` exit so that `resolveOutboundTarget()`
can attempt the plugin-provided default. Guard the WhatsApp allowFrom
override against falsy `toCandidate` to maintain existing behavior when
a target IS resolved.

Fixes #32355

Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-03 01:09:47 +00:00
Peter Steinberger 90d8b40808 perf(test): simplify plugin install fixture archive loading 2026-03-03 01:09:07 +00:00
Peter Steinberger d7bafae387 perf(test): trim fixture and serialization overhead in integration suites 2026-03-03 01:09:07 +00:00
Peter Steinberger 588fbd5b68 perf(test): reduce temp fixture churn in guardrail-heavy suites 2026-03-03 01:09:07 +00:00
Peter Steinberger ef920f2f39 refactor(channels): dedupe monitor message test flows 2026-03-03 01:06:00 +00:00
Peter Steinberger 57e1534df8 refactor(tests): consolidate repeated setup helpers 2026-03-03 01:06:00 +00:00
Peter Steinberger a48a3dbdda refactor(tests): dedupe tool, projector, and delivery fixtures 2026-03-03 01:06:00 +00:00
Peter Steinberger c3d5159121 refactor(hooks): dedupe install parameter wiring 2026-03-03 01:06:00 +00:00
Peter Steinberger 1bd20dbdb6 fix(failover): treat stop reason error as timeout 2026-03-03 01:05:24 +00:00
Peter Steinberger a2fdc3415f fix(failover): handle unhandled stop reason error 2026-03-03 01:05:24 +00:00
Peter Steinberger ced267c5cb fix(moonshot): apply native thinking payload compatibility 2026-03-03 01:05:24 +00:00
Peter Steinberger 287606e445 feat(acp): add kimi harness support surfaces 2026-03-03 01:05:24 +00:00
Gustavo Madeira Santana f26853f14c CLI: dedupe config validate errors and expose allowed values 2026-03-02 20:05:12 -05:00
AytuncYildizli a44843507f
fix(slack): restore persistent per-channel session routing (#32320)
Top-level channel messages were creating isolated per-message sessions because roomThreadId fell through to threadContext.messageTs whenever replyToMode was not off.

Introduced in #10686, every new channel message got its own session key (agent:...🧵<messageTs>), breaking conversation continuity.

Fix: only derive thread-specific session keys for actual thread replies. Top-level channel messages stay on the per-channel session key regardless of replyToMode.

Fixes #32285
2026-03-03 01:00:49 +00:00
scoootscooob de09ca149f
fix(telegram): use retry logic for sticker getFile calls (#32349)
The sticker code path called ctx.getFile() directly without retry,
unlike the non-sticker media path which uses resolveTelegramFileWithRetry
(3 attempts with jitter). This made sticker downloads vulnerable to
transient Telegram API failures, particularly in group topics where
file availability can be delayed.

Refs #32326

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:00:31 +00:00
hcl 503d395780
fix(memoryFlush): guard transcript-size forced flush against repeated runs (#32358)
The `forceFlushTranscriptBytes` path (introduced in d729ab21) bypasses the
`memoryFlushCompactionCount` guard that prevents repeated flushes within the
same compaction cycle. Once the session transcript exceeds 2 MB, memory flush
fires on every single message — even when token count is well under the
compaction threshold.

Extract `hasAlreadyFlushedForCurrentCompaction()` from the inline guard in
`shouldRunMemoryFlush` and apply it to both the token-based and the
transcript-size trigger paths.

Fixes #32317

Signed-off-by: HCL <chenglunhu@gmail.com>
2026-03-03 01:00:18 +00:00
Shawn 924d9e34ef
fix(discord): resample audio to 48kHz for voice messages (#32298)
Fixes #32293: Discord voice message plays at ~0.5x speed with 24kHz TTS source

When TTS providers (like mlx-audio Qwen3-TTS) output audioHz,
Discord voice at 24k messages play at half speed because Discord expects 48kHz.

This fix adds explicit sample rate conversion to 48kHz in the ensureOggOpus
function, ensuring voice messages always play at correct speed regardless
of the input audio's sample rate.

Co-authored-by: Kevin Shenghui <shenghuikevin@gmail.com>
2026-03-03 01:00:04 +00:00
Peter Steinberger f3e6578e6c fix(test): tighten websocket and runner fixture typing 2026-03-03 00:55:01 +00:00
Peter Steinberger e930517154 fix(ci): resolve docs lint and test typing regressions 2026-03-03 00:55:01 +00:00
Peter Steinberger 47083460ea refactor: unify inbound debounce policy and split gateway/models helpers 2026-03-03 00:54:33 +00:00
Peter Steinberger 7de4204e57 docs(acp): document sandbox limitation 2026-03-03 00:52:09 +00:00
Peter Steinberger 36dfd462a8 feat(acp): enable dispatch by default 2026-03-03 00:47:35 +00:00
Peter Steinberger 6649c22471 fix(agents): harden openai ws tool call id handling 2026-03-03 00:43:48 +00:00
Peter Steinberger 596621919c chore(test): add vitest hotspot reporter script 2026-03-03 00:43:01 +00:00
Peter Steinberger 9657ded2e1 test(perf): trim slack, hook, and plugin-validation test overhead 2026-03-03 00:43:01 +00:00
Peter Steinberger 282b107e99 test(perf): speed up cron, memory, and secrets hotspots 2026-03-03 00:43:01 +00:00
Peter Steinberger 86090b0ff2 docs(models): refresh minimax kimi glm provider docs 2026-03-03 00:40:15 +00:00
Peter Steinberger 77ecef1fde feat(models): support minimax highspeed across onboarding 2026-03-03 00:40:15 +00:00
ademczuk 53fd7f8163
fix(test): resolve Feishu hoisted mock export syntax error (#32128)
- Remove vi.hoisted() wrapper from exported mock in shared module
  (Vitest cannot export hoisted variables)
- Inline vi.hoisted + vi.mock in startup test so Vitest's per-file
  hoisting registers mocks before production imports

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:34:16 +00:00
Peter Steinberger 1b5ac8b0b1 feat(cli): add configurable banner tagline mode 2026-03-03 00:31:51 +00:00
Peter Steinberger f6233cfa5c fix: dedupe restart sentinel reason output (#32083) (thanks @velamints2) 2026-03-03 00:30:34 +00:00
velamints2 61be533ad4 fix(restart): deduplicate reason line in restart sentinel message
When gateway.restart is triggered with a reason but no separate note,
the payload sets both message and stats.reason to the same text.
formatRestartSentinelMessage() then emits both the message line and a
redundant 'Reason: <same text>' line, doubling the restart reason in
the notification delivered to the agent session.

Skip the 'Reason:' line when stats.reason matches the already-emitted
message text. Add regression tests for both duplicate and distinct
reason scenarios.
2026-03-03 00:30:34 +00:00
Peter Steinberger d76ddd61ec fix(discord): add missing accountId to reaction routing params 2026-03-03 00:29:20 +00:00
Peter Steinberger 82101b152a test(voice-call): split call manager tests by scenario 2026-03-03 00:29:20 +00:00
Peter Steinberger 439a7732f4 refactor(voice-call): split webhook server and tailscale helpers 2026-03-03 00:29:20 +00:00
Peter Steinberger a96b3b406a refactor(voice-call): extract twilio twiml policy and status mapping 2026-03-03 00:29:20 +00:00
Peter Steinberger 68e982ec80 fix: stabilize internal hooks singleton registry (#32292) (thanks @Drickon) 2026-03-03 00:27:10 +00:00
Eric Lytle d0a3743abd refactor: use ??= operator for cleaner globalThis singleton init
Addresses greptile review: collapses the if-guard + assignment into
a single ??= expression so TypeScript can narrow the type without
a non-null assertion.
2026-03-03 00:27:10 +00:00
Eric Lytle 0d8beeb4e5 fix(hooks): use globalThis singleton for handler registry to survive bundle splitting
Without this fix, the bundler can emit multiple copies of internal-hooks
into separate chunks. registerInternalHook writes to one Map instance
while triggerInternalHook reads from another — resulting in hooks that
silently fire with zero handlers regardless of how many were registered.

Reproduce: load a hook via hooks.external.entries (loader reads one chunk),
then send a message:transcribed event (get-reply imports a different chunk).
The handler list is empty; the hook never runs.

Fix: use globalThis.__openclaw_internal_hook_handlers__ as a shared
singleton. All module copies check for and reuse the same Map, ensuring
registrations are always visible to triggers.
2026-03-03 00:27:10 +00:00
Peter Steinberger 1e8afa16f0 fix: apply config env vars before model discovery (#32295) (thanks @hsiaoa) 2026-03-03 00:25:24 +00:00
hsiaoa 65dc3ee76c models-config: apply config env vars before implicit provider discovery 2026-03-03 00:25:24 +00:00
Hunter Miller f4682742d9
feat: update tlon channel/plugin to be more fully featured (#21208)
* feat(tlon): sync with openclaw-tlon master

- Add tlon CLI tool registration with binary lookup
- Add approval, media, settings, foreigns, story, upload modules
- Add http-api wrapper for Urbit connection patching
- Update types for defaultAuthorizedShips support
- Fix type compatibility with core plugin SDK
- Stub uploadFile (API not yet available in @tloncorp/api-beta)
- Remove incompatible test files (security, sse-client, upload)

* chore(tlon): remove dead code

Remove unused Urbit channel client files:
- channel-client.ts
- channel-ops.ts
- context.ts

These were not imported anywhere in the extension.

* feat(tlon): add image upload support via @tloncorp/api

- Import configureClient and uploadFile from @tloncorp/api
- Implement uploadImageFromUrl using uploadFile
- Configure API client before media uploads
- Update dependency to github:tloncorp/api-beta#main

* fix(tlon): restore SSRF protection with event ack tracking

- Restore context.ts and channel-ops.ts for SSRF support
- Restore sse-client.ts with urbitFetch for SSRF-protected requests
- Add event ack tracking from openclaw-tlon (acks every 20 events)
- Pass ssrfPolicy through authenticate() and UrbitSSEClient
- Fixes security regression from sync with openclaw-tlon

* fix(tlon): restore buildTlonAccountFields for allowPrivateNetwork

The inlined payload building was missing allowPrivateNetwork field,
which would prevent the setting from being persisted to config.

* fix(tlon): restore SSRF protection in probeAccount

- Restore channel-client.ts for UrbitChannelClient
- Use UrbitChannelClient with ssrfPolicy in probeAccount
- Ensures account probe respects allowPrivateNetwork setting

* feat(tlon): add ownerShip to setup flow

ownerShip should always be set as it controls who receives
approval requests and can approve/deny actions.

* chore(tlon): remove unused http-api.ts

After restoring SSRF protection, probeAccount uses UrbitChannelClient
instead of @urbit/http-api. The http-api.ts wrapper is no longer needed.

* refactor(tlon): simplify probeAccount to direct /~/name request

No channel needed - just authenticate and GET /~/name.
Removes UrbitChannelClient, keeping only UrbitSSEClient for monitor.

* chore(tlon): add logging for event acks

* chore(tlon): lower ack threshold to 5 for testing

* fix(tlon): address security review issues

- Fix SSRF in upload.ts: use urbitFetch with SSRF protection
- Fix SSRF in media.ts: use urbitFetch with SSRF protection
- Add command whitelist to tlon tool to prevent command injection
- Add getDefaultSsrFPolicy() helper for uploads/downloads

* fix(tlon): restore auth retry and add reauth on SSE reconnect

- Add authenticateWithRetry() helper with exponential backoff (restores lost logic from #39)
- Add onReconnect callback to re-authenticate when SSE stream reconnects
- Add UrbitSSEClient.updateCookie() method for proper cookie normalization on reauth

* fix(tlon): add infinite reconnect with reset after max attempts

Instead of giving up after maxReconnectAttempts, wait 10 seconds then
reset the counter and keep trying. This ensures the monitor never
permanently disconnects due to temporary network issues.

* test(tlon): restore security, sse-client, and upload tests

- security.test.ts: DM allowlist, group invite, bot mention detection, ship normalization
- sse-client.test.ts: subscription handling, cookie updates, reconnection params
- upload.test.ts: image upload with SSRF protection, error handling

* fix(tlon): restore DM partner ship extraction for proper routing

- Add extractDmPartnerShip() to extract partner from 'whom' field
- Use partner ship for routing (more reliable than essay.author)
- Explicitly ignore bot's own outbound DM events
- Log mismatch between author and partner for debugging

* chore(tlon): restore ack threshold to 20

* chore(tlon): sync slash commands support from upstream

- Add stripBotMention for proper CommandBody parsing
- Add command authorization logic for owner-only slash commands
- Add CommandAuthorized and CommandSource to context payload

* fix(tlon): resolve TypeScript errors in tests and monitor

- Store validated account url/code before closure to fix type narrowing
- Fix test type annotations for mode rules
- Add proper Response type cast in sse-client mock
- Use optional chaining for init properties

* docs(tlon): update docs for new config options and capabilities

- Document ownerShip for approval system
- Document autoAcceptDmInvites and autoAcceptGroupInvites
- Update status to reflect rich text and image support
- Add bundled skill section
- Update notes with formatting and image details
- Fix pnpm-lock.yaml conflict

* docs(tlon): fix dmAllowlist description and improve allowPrivateNetwork docs

- Correct dmAllowlist: empty means no DMs allowed (not allow all)
- Promote allowPrivateNetwork to its own section with examples
- Add warning about SSRF protection implications

* docs(tlon): clarify ownerShip is auto-authorized everywhere

- Add ownerShip to minimal config example (recommended)
- Document that owner is automatically allowed for DMs and channels
- No need to add owner to dmAllowlist or defaultAuthorizedShips

* docs(tlon): add capabilities table, troubleshooting, and config reference

Align with Matrix docs format:
- Capabilities table for quick feature reference
- Troubleshooting section with common failures
- Configuration reference with all options

* docs(tlon): fix reactions status and expand bundled skill section

- Reactions ARE supported via bundled skill (not missing)
- Add link to skill GitHub repo
- List skill capabilities: contacts, channels, groups, DMs, reactions, settings

* fix(tlon): use crypto.randomUUID instead of Math.random for channel ID

Fixes security test failure - Math.random is flagged as weak randomness.

* docs: fix markdown lint - add blank line before </Step>

* fix: address PR review issues for tlon plugin

- upload.ts: Use fetchWithSsrFGuard directly instead of urbitFetch to
  preserve full URL path when fetching external images; add release() call
- media.ts: Same fix - use fetchWithSsrFGuard for external media downloads;
  add release() call to clean up resources
- channel.ts: Use urbitFetch for poke API to maintain consistent SSRF
  protection (DNS pinning + redirect handling)
- upload.test.ts: Update mocks to use fetchWithSsrFGuard instead of urbitFetch

Addresses blocking issues from jalehman's review:
1. Fixed incorrect URL being fetched (validateUrbitBaseUrl was stripping path)
2. Fixed missing release() calls that could leak resources
3. Restored guarded fetch semantics for poke operations

* docs: add tlon changelog fragment

* style: format tlon monitor

* fix: align tlon lockfile and sse id generation

* docs: fix onboarding markdown list spacing

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
2026-03-02 16:23:42 -08:00
Peter Steinberger d37ad9d866 test(perf): slim ios team-id harness and add perf budget guard 2026-03-03 00:20:46 +00:00
Peter Steinberger 4b3d9f4fb2 test(perf): trim fixture churn in install and cron suites 2026-03-03 00:20:46 +00:00
Peter Steinberger 6bf84ac28c perf(runtime): reduce hot-path config and routing overhead 2026-03-03 00:20:46 +00:00
Glucksberg 051b380d38
fix(hooks): return 200 instead of 202 for webhook responses (#28204)
* fix(hooks): return 200 instead of 202 for webhook responses (#22036)

* docs(webhook): document 200 status for hooks agent

* chore(changelog): add webhook ack note openclaw#28204 thanks @Glucksberg

---------

Co-authored-by: Shakker <shakkerdroid@gmail.com>
2026-03-03 00:19:31 +00:00
Hershey Goldberger dee7cda1ec feat(voice-call): add call-waiting queue for inbound Twilio calls 2026-03-03 00:17:21 +00:00
Peter Steinberger 8824565c2a chore(cli): refresh tagline set 2026-03-03 00:17:18 +00:00
Peter Steinberger d7dda4dd1a refactor: dedupe channel outbound and monitor tests 2026-03-03 00:15:15 +00:00
Peter Steinberger 6a42d09129 refactor: dedupe gateway config and infra flows 2026-03-03 00:15:14 +00:00
Peter Steinberger fd3ca8a34c refactor: dedupe agent and browser cli helpers 2026-03-03 00:15:00 +00:00
Peter Steinberger fe14be2352 Merge pull request #4325: fix(voice-call) verify stale calls with provider 2026-03-03 00:14:37 +00:00
Peter Steinberger e870cee542 fix: restore control-ui basePath webhook passthrough (#32311) (thanks @ademczuk) 2026-03-03 00:11:13 +00:00
ademczuk 3e9c8721fb fix(gateway): let non-GET requests fall through controlUi routing when basePath is set
When controlUiBasePath is set, classifyControlUiRequest returned
method-not-allowed (405) for all non-GET/HEAD requests under basePath,
blocking plugin webhook handlers (BlueBubbles, Mattermost, etc.) from
receiving POST requests. This is a 2026.3.1 regression.

Return not-control-ui instead, matching the empty-basePath behavior, so
requests fall through to plugin HTTP handlers. Remove the now-dead
method-not-allowed type variant, handler branch, and utility function.

Closes #31983
Closes #32275

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:11:13 +00:00
Peter Steinberger 11c397ef46 docs: strengthen prompt injection warning for weaker models 2026-03-03 00:06:39 +00:00
Peter Steinberger 4bfbf2dfff test(refactor): dedupe secret resolver posix fixtures and add registry cache regression 2026-03-03 00:05:39 +00:00
Peter Steinberger 1d0a4d1be2 refactor(runtime): harden channel-registry cache invalidation and split outbound delivery flow 2026-03-03 00:05:39 +00:00
Shakker d6491d8d71
fix: narrow webhook event provider call id typing 2026-03-03 00:05:03 +00:00
Peter Steinberger 6b85ec3022 docs: tighten subscription guidance and update MiniMax M2.5 refs 2026-03-03 00:02:37 +00:00
Peter Steinberger 3e1ec5ad8b fix: land Twilio signature port-variant verification (#25140) (thanks @drvoss) 2026-03-03 00:02:03 +00:00
drvoss c5ddba52d7 fix(voice-call): retry Twilio signature verification without port in URL
Twilio signs webhook requests using the URL without the port component,
even when the publicUrl config includes a non-standard port. Add a fallback
that strips the port from the verification URL when initial validation fails,
matching the behavior of Twilio's official helper library.

Closes #6334
2026-03-03 00:02:03 +00:00
Peter Steinberger 381bb867ac fix: land external Twilio outbound-api webhook calls (#31181) (thanks @scoootscooob) 2026-03-02 23:56:41 +00:00
scoootscooob 24dcd68f42 fix: rename createInboundCall → createWebhookCall, preserve event direction
Address Greptile review: externally-initiated outbound-api calls were
stored with hardcoded direction: "inbound". Now createWebhookCall accepts
a direction parameter so the CallRecord accurately reflects the event's
actual direction. Also skip inboundGreeting for outbound calls and add a
test asserting inbound direction is preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:56:41 +00:00
scoootscooob a1b4a0066b fix(voice-call): accept externally-initiated Twilio outbound-api calls
Fixes #30900 — Calls initiated directly via the Twilio REST API
(Direction=outbound-api) were rejected as "unknown call" because
processEvent only auto-registered calls with direction=inbound.
External outbound-api calls now get registered in the CallManager
so the media stream is accepted. Inbound policy checks still only
apply to true inbound calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:56:41 +00:00
Peter Steinberger a5b81d1c13 test(perf): remove long exec-delay sleep in secret resolver tests 2026-03-02 23:56:30 +00:00
Peter Steinberger d3dc4e54f7 perf(runtime): trim hot-path allocations and cache channel plugin lookups 2026-03-02 23:56:30 +00:00
Peter Steinberger dba47f349f fix: land Twilio inbound greeting for answered calls (#29121) (thanks @xinhuagu) 2026-03-02 23:54:54 +00:00
Xinhua Gu fe4c627432 fix(voice-call): speak inbound greeting for twilio answered calls 2026-03-02 23:54:54 +00:00
Peter Steinberger b8b8a5f314 fix(security): enforce explicit ingress owner context 2026-03-02 23:50:36 +00:00
Peter Steinberger ea3b7dfde5 fix(channels): normalize MIME kind parsing and reaction fallbacks 2026-03-02 23:48:11 +00:00
Peter Steinberger 32ecd6f579 refactor(auto-reply,telegram,config): extract guard and forum helpers 2026-03-02 23:48:11 +00:00
Peter Steinberger dc825e59f5 refactor: unify system.run approval cwd revalidation 2026-03-02 23:46:54 +00:00
Peter Steinberger 500d7cb107 fix: revalidate approval cwd before system.run execution 2026-03-02 23:42:10 +00:00
Brian Mendonca 1234cc4c31
Feishu: reply to topic roots (#29968)
* Feishu: reply to topic roots

* Changelog: note Feishu topic-root reply targeting

---------

Co-authored-by: bmendonca3 <208517100+bmendonca3@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 17:41:36 -06:00
Peter Steinberger abec8a4f0a test: preserve windows backup-rotation compose coverage (#32286) (thanks @jalehman) 2026-03-02 23:38:17 +00:00
Josh Lehman 41bdf2df41 test: skip chmod-dependent backup rotation tests on Windows
chmod is a no-op on Windows — file permissions always report 0o666
regardless of what was set, so asserting 0o600 will never pass.
2026-03-02 23:38:17 +00:00
Peter Steinberger c20ee11348 fix: harden fs-safe write boundary checks 2026-03-02 23:36:23 +00:00
ningding97 4d19dc8671 test(cron): assert embedded model on last call to avoid bun ordering flake
Bun runs can trigger multiple embedded agent invocations in a single cron
turn (e.g. retries/fallbacks), making assertions against call[0] flaky.
Assert against the last invocation instead.
2026-03-02 23:36:13 +00:00
Peter Steinberger 73e08ed7b0 test: expand reminder guard fail-closed coverage (#32255) (thanks @scoootscooob) 2026-03-02 23:35:14 +00:00
scoootscooob 5868344ade fix(reminder): do not suppress note when sessionKey is unavailable
Address Greptile review: when sessionKey is undefined the fallback
matched any enabled cron job, which could silently suppress the guard
note due to jobs from unrelated sessions.  Return false instead so the
note always appears when session scoping is not possible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:35:14 +00:00
scoootscooob abb0252a1a fix(reply): suppress unscheduled-reminder note when session already has active cron
Before appending the "I did not schedule a reminder" guard note, check the
cron store for enabled jobs matching the current session key.  This prevents
false positives when the agent references an existing cron created in a
prior turn (e.g. "I'll ping you when it's done" while a monitoring cron is
already running).

The check only fires on the rare path where the text matches commitment
patterns AND no cron was added in the current turn, so the added I/O is
negligible.

Closes #32228

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:35:14 +00:00
Mark L 55f04636f3
fix(feishu): suppress stale missing-scope grant notices (openclaw#31870) thanks @liuxiaopai-ai
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on unrelated baseline lint in src/browser/chrome.ts)

Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 17:34:11 -06:00
YolenSong f22fc17c78
feat(feishu): prefer thread_id for topic session routing (openclaw#29788) thanks @songyaolun
Verified:
- pnpm test -- extensions/feishu/src/bot.test.ts extensions/feishu/src/reply-dispatcher.test.ts
- pnpm build

Co-authored-by: songyaolun <26423459+songyaolun@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 17:33:08 -06:00
Peter Steinberger 28c88e9fa1 fix: harden telegram forum-service mention guard typing (#32262) (thanks @scoootscooob) 2026-03-02 23:32:53 +00:00
scoootscooob 58ad617e64 fix: detect forum service messages by field presence, not text absence
Stickers, voice notes, and captionless photos from the bot also lack
text and caption fields, so the previous check incorrectly classified
them as system messages and suppressed implicitMention.

Switch to checking for Telegram's forum_topic_* / general_forum_topic_*
service-message fields which only appear on actual service messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:32:53 +00:00
scoootscooob dc2aa1e21d fix(telegram): also check caption for bot media replies
Address Greptile review feedback: bot media messages (photo/video) use
caption instead of text, so they would be incorrectly classified as
system messages.  Add !caption guard to the system message check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:32:53 +00:00
scoootscooob 8fdd1d2f05 fix(telegram): exclude forum topic system messages from implicitMention
When a Telegram Forum topic is created by the bot, Telegram generates a
system message with from.id=botId and empty text.  Every subsequent user
message in that topic has reply_to_message pointing to this system
message, causing the implicitMention check to fire and bypassing
requireMention for every single message.

Add a guard that recognises system messages (is_bot=true with no text)
and excludes them from implicit mention detection, so that only genuine
replies to bot messages trigger the bypass.

Closes #32256

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:32:53 +00:00
Peter Steinberger bb60687b89 refactor(nodes): dedupe camera payload and node resolve helpers 2026-03-02 23:32:41 +00:00
Peter Steinberger a282b459b9 fix(ci): remove unused chrome ws type import 2026-03-02 23:31:42 +00:00
Peter Steinberger de77a36579 test: harden MIME normalization regression coverage (#32280) (thanks @Lucenx9) 2026-03-02 23:31:19 +00:00
Lucenx9 79e114a82f test(media): ensure WhatsApp scope rule is exercised in MIME regression 2026-03-02 23:31:19 +00:00
Lucenx9 7c7c22d66f test(media): use direct chatType in WhatsApp MIME regression case 2026-03-02 23:31:19 +00:00
Lucenx9 ec688d809f fix(media): normalize MIME kind detection for audio transcription 2026-03-02 23:31:19 +00:00
Sid 481da215b9
fix(feishu): persist dedup cache across gateway restarts via warmup (openclaw#31605) thanks @Sid-Qin
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini (fails on unrelated baseline test: src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts)

Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 17:30:40 -06:00
Peter Steinberger 132794fe74 feat(security): audit workspace skill symlink escapes 2026-03-02 23:28:54 +00:00
Peter Steinberger d4ec0ed3c7 docs(security): clarify trusted-local hardening-only cases 2026-03-02 23:28:54 +00:00
Peter Steinberger 2e0f5b73d1 fix(ci): stabilize cross-platform config test assertions 2026-03-02 23:28:24 +00:00
不做了睡大觉 66397c2855
fix(feishu): restore private chat pairing replies in Lark/Feishu (openclaw#31403) thanks @stakeswky
Verified:
- pnpm test -- extensions/feishu/src/bot.test.ts
- pnpm build

Co-authored-by: stakeswky <64798754+stakeswky@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 17:27:39 -06:00
Tak Hoffman e2483a5381 Browser: fix ws RawData type import for dts build 2026-03-02 17:24:34 -06:00
Peter Steinberger c703aa0fe9 fix(agents): align sandboxed ACP prompt guidance 2026-03-02 23:24:02 +00:00
Peter Steinberger 3bf19d6f40 fix(security): fail-close node camera URL downloads 2026-03-02 23:23:39 +00:00
Peter Steinberger 7365aefa19 fix(ci): resolve chrome websocket raw-data typing 2026-03-02 23:18:06 +00:00
Peter Steinberger 7066d5e192 refactor: extract shared sandbox and gateway plumbing 2026-03-02 23:16:47 +00:00
Sid 350d041eaf
fix(feishu): serialize message handling per chat to prevent skipped messages (openclaw#31807) thanks @Sid-Qin
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on unrelated pre-existing TypeScript error in src/browser/chrome.ts)

Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 17:14:00 -06:00
Onur Solmaz e05bcccde8
chore(pi): change wording in landpr slash prompt, prefer squash (#32279)
* chore(pi): remove rebase flow from landpr prompt

* chore(pi): prefer squash wording in landpr prompt
2026-03-03 00:13:11 +01:00
Vincent Koc 0954b6bf5f
fix(hooks): propagate ephemeral sessionId through embedded tool contexts (#32273)
* fix(plugins): expose ephemeral sessionId in tool contexts for per-conversation isolation

The plugin tool context (`OpenClawPluginToolContext`) and tool hook
context (`PluginHookToolContext`) only provided `sessionKey`, which
is a durable channel identifier that survives /new and /reset.
Plugins like mem0 that need per-conversation isolation (e.g. mapping
Mem0 `run_id`) had no way to distinguish between conversations,
causing session-scoped memories to persist unbounded across resets.

Add `sessionId` (ephemeral UUID regenerated on /new and /reset) to:
- `OpenClawPluginToolContext` (factory context for plugin tools)
- `PluginHookToolContext` (before_tool_call / after_tool_call hooks)
- Internal `HookContext` for tool call wrappers

Thread the value from the run attempt through createOpenClawCodingTools
→ createOpenClawTools → resolvePluginTools and through the tool hook
wrapper.

Closes #31253

Made-with: Cursor

* fix(agents): propagate embedded sessionId through tool hook context

* test(hooks): cover sessionId in embedded tool hook contexts

* docs(changelog): add sessionId hook context follow-up note

* test(hooks): avoid toolCallId collision in after_tool_call e2e

---------

Co-authored-by: SidQin-cyber <sidqin0410@gmail.com>
2026-03-02 15:11:51 -08:00
Berton 3b3e47e15d
Feishu: wire inbound message debounce (openclaw#31548) thanks @bertonhan
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on unrelated pre-existing lint in ui/src/ui/views/agents-utils.ts and src/pairing/pairing-store.ts)
- pnpm test:macmini (previous run passed before rebase)

Co-authored-by: bertonhan <60309291+bertonhan@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 17:10:47 -06:00
Vincent Koc 8f3eb0f7b4
fix(browser): use CDP command probe for cdpReady health (#31421)
* fix(browser): validate cdp command channel health

* test(browser): cover stale cdp command channel readiness

* changelog: note cdp command-channel readiness check

* browser(cdp): detach ws message listener on health-probe cleanup
2026-03-02 15:10:28 -08:00
Peter Steinberger 0e16749f00 ci: fix lint and audit regressions on main 2026-03-02 23:08:23 +00:00
Peter Steinberger 7eda632324 refactor: split slack/discord/session maintenance helpers 2026-03-02 23:07:20 +00:00
不做了睡大觉 3043e68dfa
fix(feishu): support Lark private chats as direct messages (openclaw#31400) thanks @stakeswky
Verified:
- pnpm test -- extensions/feishu/src/bot.checkBotMentioned.test.ts
- pnpm build
- pnpm check (blocked by unrelated baseline lint errors in untouched files)
- pnpm test:macmini (not run after pnpm check blocked)

Co-authored-by: stakeswky <64798754+stakeswky@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 17:04:42 -06:00
Peter Steinberger 36c6b63ea6 style(telegram): apply formatter to draft-stream warning 2026-03-02 23:04:13 +00:00
Peter Steinberger fc1787fd4b feat(telegram): default streaming preview to partial 2026-03-02 23:04:12 +00:00
Peter Steinberger 2287d1ec13 test: micro-optimize slow suites and CLI command setup 2026-03-02 23:00:49 +00:00
Peter Steinberger ba5ae5b4f1 perf(routing): cache route and mention regex resolution 2026-03-02 23:00:49 +00:00
Altay a81704e622
fix(skills): scope skill-command APIs to respect agent allowlists (#32155)
* refactor(skills): use explicit skill-command scope APIs

* test(skills): cover scoped listing and telegram allowlist

* fix(skills): add mergeSkillFilters edge-case tests and simplify dead code

Cover unrestricted-co-tenant and empty-allowlist merge paths in
skill-commands tests. Remove dead ternary in bot-handlers pagination.
Add clarifying comments on undefined vs [] filter semantics.

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

* refactor(skills): collapse scope functions into single listSkillCommandsForAgents

Replace listSkillCommandsForAgentIds, listSkillCommandsForAllAgents, and
the deprecated listSkillCommandsForAgents with a single function that
accepts optional agentIds and falls back to all agents when omitted.

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

* fix(skills): harden realpathSync race and add missing test coverage

- Wrap fs.realpathSync in try-catch to gracefully skip workspaces that
  disappear between existsSync and realpathSync (TOCTOU race).
- Log verbose diagnostics for missing/unresolvable workspace paths.
- Add test for overlapping allowlists deduplication on shared workspaces.
- Add test for graceful skip of missing workspaces.
- Add test for pagination callback without agent suffix (default agent).
- Clean up temp directories in skill-commands tests.

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

* fix(telegram): warn when nativeSkillsEnabled but no agent route is bound

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

* fix: use runtime.log instead of nonexistent runtime.warn

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 02:00:05 +03:00
Peter Steinberger 02eeb08e04 fix: enforce sandbox workspace mount mode (#32227) (thanks @guanyu-zhang) 2026-03-02 22:59:11 +00:00
Evan 7cbcbbc642 fix(sandbox): same fix for browser.ts - make /workspace bind mount read-only when workspaceAccess is not rw
The identical buggy logic from docker.ts also exists in browser.ts.
Applying the same fix here.
2026-03-02 22:58:09 +00:00
Evan 903e4dff35 fix(sandbox): make /workspace bind mount read-only when workspaceAccess is not rw
This ensures that when workspaceAccess is set to 'ro' or 'none', the
sandbox workspace (/workspace inside the container) is mounted as
read-only, matching the documented behavior.

Previously, the condition was:
  workspaceAccess === 'ro' && workspaceDir === agentWorkspaceDir

This was always false in 'ro' mode because workspaceDir equals
sandboxWorkspaceDir, not agentWorkspaceDir.

Now the logic is simplified:
  - 'rw': /workspace is writable
  - 'ro': /workspace is read-only
  - 'none': /workspace is read-only
2026-03-02 22:58:09 +00:00
12 905c3357eb
fix(feishu): encode non-ASCII filenames in file uploads (openclaw#31328) thanks @Kay-051
Verified:
- pnpm test extensions/feishu/src/media.test.ts

Co-authored-by: Kay-051 <210470990+Kay-051@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 16:56:57 -06:00
dunamismax f431f20c48 fix(followup): pass currentChannelId to queued message agent runs
The followup runner (which processes queued messages) was calling
runEmbeddedPiAgent without currentChannelId or currentThreadTs.
This meant the message tool's toolContext had no channel routing
info, causing reactions (and other target-inferred actions) to
fail with 'Action react requires a target' on queued messages.

Pass originatingTo as currentChannelId so the message tool can
infer the reaction target from context, matching the behavior
of the initial (non-queued) agent run.
2026-03-02 22:53:04 +00:00
dunamismax d9fdec12ab fix(signal): fall back to toolContext.currentMessageId for reactions
Signal reactions required an explicit messageId parameter, unlike
Telegram which already fell back to toolContext.currentMessageId.
This made agent-initiated reactions fail on Signal because the
inbound message ID was available in tool context but never used.

- Destructure toolContext in Signal action handler
- Fall back to toolContext.currentMessageId when messageId omitted
- Update reaction schema descriptions (not Telegram-specific)
- Add tests for fallback and missing-messageId rejection

Closes #17651
2026-03-02 22:53:04 +00:00
Peter Steinberger f25be781c4 fix: honor chat completions message-channel header (#30462) (thanks @bmendonca3) 2026-03-02 22:51:32 +00:00
bmendonca3 0d8f14fed3 gateway: cover default message-channel fallback 2026-03-02 22:51:32 +00:00
bmendonca3 842a79cf99 Gateway: honor message-channel header for chat completions 2026-03-02 22:51:32 +00:00
Peter Steinberger caae34cbaf refactor: unify message hook mapping and async dispatch 2026-03-02 22:51:28 +00:00
Mark L fa47f74c0f
Feishu: normalize group announce targets to chat ids (openclaw#31546) thanks @liuxiaopai-ai
Verified:
- pnpm build
- pnpm check (fails on unrelated existing main-branch lint violations in ui/src/ui/views/agents-utils.ts and src/pairing/pairing-store.ts)
- pnpm test:macmini

Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 16:50:55 -06:00
Bob ac11f0af73
Security: enforce ACP sandbox inheritance for sessions_spawn (#32254)
* Security: enforce ACP sandbox inheritance in sessions_spawn

* fix: add changelog attribution for ACP sandbox inheritance (#32254) (thanks @dutifulbob)

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-02 23:50:38 +01:00
Peter Steinberger a78ec81ae6 fix: align exec no-output timeout defaults (#32235) (thanks @bmendonca3) 2026-03-02 22:47:03 +00:00
bmendonca3 be578b43d3 secrets: default exec no-output timeout to timeoutMs 2026-03-02 22:47:03 +00:00
Peter Steinberger 0b5d8e5b47 fix: harden discord audio preflight mention detection (#32136) (thanks @jnMetaCode) 2026-03-02 22:45:41 +00:00
jiangnan b9b47f5002 fix(discord): use correct content_type property for audio attachment detection
The preflight audio transcription detection used camelCase `contentType`
but Discord's APIAttachment type uses snake_case `content_type`. This
caused `hasAudioAttachment` to always be false, preventing voice message
transcription from triggering in guild channels where mention detection
requires audio preflight.

Fixes #30034

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:45:41 +00:00
Peter Steinberger 319b7c68a1 fix: preserve inline-status newlines (#32224) (thanks @scoootscooob) 2026-03-02 22:43:10 +00:00
scoootscooob 6200e242b2 fix(auto-reply): preserve newlines in stripInlineStatus and extractInlineSimpleCommand
The /\s+/g whitespace normalizer collapsed newlines along with spaces/tabs,
destroying paragraph structure in multi-line messages before they reached
the LLM. Use /[^\S\n]+/g to only collapse horizontal whitespace while
preserving line breaks.

Closes #32216

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:43:10 +00:00
Mark L 5b5ccb0769
fix(ui): avoid toSorted in cron suggestions (#31775)
* Control UI: avoid toSorted in cron suggestions

* Control UI: make sortLocaleStrings legacy-safe

* fix(ui): use sort fallback in locale string helper

* fix(ui): remove toSorted from locale helper

* fix(ui): remove toSorted from locale helper

* fix(ui): remove toSorted from locale helper

* fix(ui): remove toSorted from locale helper

* fix(ui): remove toSorted from locale helper

* fix(ui): avoid sort in locale helper for browser compatibility

* ui: avoid unnecessary assertions in locale sort

* changelog: credit browser-compat cron fix PR

* fix(ui): use native locale sort in compatibility helper

* ui: use compat merge-sort for locale strings

* style: format locale sort helper

* style: fix oxfmt ordering in agents utils

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-02 14:41:01 -08:00
ademczuk 0743463b88
fix(webchat): suppress NO_REPLY token in chat transcript rendering (#32183)
* fix(types): resolve pre-existing TS errors in agent-components and pairing-store

- agent-components.ts: normalizeDiscordAllowList returns {allowAll, ids, names},
  not an array — use ids.values().next().value instead of [0] indexing
- pairing-store.ts: add non-null assertions for stat after cache-miss guard
  (resolveAllowFromReadCacheOrMissing returns early when stat is null)

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

* fix(webchat): suppress NO_REPLY token in chat transcript rendering

Filter assistant NO_REPLY-only entries from chat.history responses at
the gateway API boundary and add client-side defense-in-depth guards in
the UI chat controller so internal silent tokens never render as visible
chat bubbles.

Two-layer fix:
1. Gateway: extractAssistantTextForSilentCheck + isSilentReplyText
   filter in sanitizeChatHistoryMessages (entry.text takes precedence
   over entry.content to avoid dropping messages with real text)
2. UI: isAssistantSilentReply + isSilentReplyStream guards on all 5
   message insertion points in handleChatEvent and loadChatHistory

Fixes #32015

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

* fix(webchat): align isAssistantSilentReply text/content precedence with gateway

* webchat: tighten NO_REPLY transcript and delta filtering

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 16:39:08 -06:00
Peter Steinberger 48155729fc fix: document Homebrew stable node path resolution (#32185) (thanks @scoootscooob) 2026-03-02 22:37:09 +00:00
scoootscooob 163f5184b3 fix(daemon): handle versioned node@XX Homebrew formulas in Cellar resolution
Address review feedback: versioned Homebrew formulas (node@22, node@20)
use keg-only paths where the stable symlink is at <prefix>/opt/<formula>/bin/node,
not <prefix>/bin/node. Updated resolveStableNodePath to:

1. Try <prefix>/opt/<formula>/bin/node first (works for both default + versioned)
2. Fall back to <prefix>/bin/node for the default "node" formula
3. Return the original Cellar path if neither stable path exists

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:37:09 +00:00
scoootscooob 8950c59581 fix(daemon): resolve Homebrew Cellar path to stable symlink for gateway install
When `openclaw gateway install` runs under Homebrew Node, `process.execPath`
resolves to the versioned Cellar path (e.g. /opt/homebrew/Cellar/node/25.7.0/bin/node).
This path breaks when Homebrew upgrades Node, silently killing the gateway daemon.

Resolve Cellar paths to the stable Homebrew symlink (/opt/homebrew/bin/node)
which Homebrew updates automatically during upgrades.

Closes #32182

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:37:09 +00:00
Peter Steinberger 29dde80c3e fix: harden message hook session/group context and add integration coverage (#9859) (thanks @Drickon) 2026-03-02 22:34:43 +00:00
Eric Lytle b5102ba4f9 fix(hooks): add isGroup and groupId to message:sent context
Adds group context fields to MessageSentHookContext so hooks can
correlate sent events with received events for the same conversation.

Previously, message:received included isGroup/groupId but message:sent
did not, forcing hooks to use mismatched identifiers (e.g. groupId vs
numeric chat ID) when tracking conversations.

Fields are derived from MsgContext in dispatch-from-config and threaded
through route-reply and deliver via the mirror parameter.

Addresses feedback from matskevich (production user, 550+ events)
reported on PR #6797.
2026-03-02 22:34:43 +00:00
Eric Lytle 7ad6a04058 fix(hooks): resolve type/lint errors from CI check failure
Arrow function passed to registerInternalHook was implicitly returning
the number from Array.push(), which is not assignable to void | Promise<void>.
Use block body to discard the return value.
2026-03-02 22:34:43 +00:00
Eric Lytle e0b8b80067 feat(hooks): add message:transcribed and message:preprocessed internal hooks
Adds two new internal hook events that fire after media/link processing:

- message:transcribed: fires when audio has been transcribed, providing
  the transcript text alongside the original body and media metadata.
  Useful for logging, analytics, or routing based on spoken content.

- message:preprocessed: fires for every message after all media + link
  understanding completes. Gives hooks access to the fully enriched body
  (transcripts, image descriptions, link summaries) before the agent sees it.

Both hooks are added in get-reply.ts, after applyMediaUnderstanding and
applyLinkUnderstanding. message:received and message:sent are already
in upstream (f07bb8e8) and are not duplicated here.

Typed contexts (MessageTranscribedHookContext, MessagePreprocessedHookContext)
and type guards (isMessageTranscribedEvent, isMessagePreprocessedEvent) added
to internal-hooks.ts alongside the existing received/sent types.

Test coverage in src/hooks/message-hooks.test.ts.
2026-03-02 22:34:43 +00:00
Vincent Koc 44183c6eb1
fix(hooks): consolidate after_tool_call context + single-fire behavior (#32201)
* fix(hooks): deduplicate after_tool_call hook in embedded runs

(cherry picked from commit c129a1a74b)

* fix(hooks): propagate sessionKey in after_tool_call context

The after_tool_call hook in handleToolExecutionEnd was passing
`sessionKey: undefined` in the ToolContext, even though the value is
available on ctx.params. This broke plugins that need session context
in after_tool_call handlers (e.g., for per-session audit trails or
security logging).

- Add `sessionKey` to the `ToolHandlerParams` Pick type
- Pass `ctx.params.sessionKey` through to the hook context
- Add test assertion to prevent regression

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit b7117384fc)

* fix(hooks): thread agentId through to after_tool_call hook context

Follow-up to #30511 — the after_tool_call hook context was passing
`agentId: undefined` because SubscribeEmbeddedPiSessionParams did not
carry the agent identity. This threads sessionAgentId (resolved in
attempt.ts) through the session params into the tool handler context,
giving plugins accurate agent-scoped context for both before_tool_call
and after_tool_call hooks.

Changes:
- Add `agentId?: string` to SubscribeEmbeddedPiSessionParams
- Add "agentId" to ToolHandlerParams Pick type
- Pass `agentId: sessionAgentId` at the subscribeEmbeddedPiSession()
  call site in attempt.ts
- Wire ctx.params.agentId into the after_tool_call hook context
- Update tests to assert agentId propagation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit aad01edd3e)

* changelog: credit after_tool_call hook contributors

* Update CHANGELOG.md

* agents: preserve adjusted params until tool end

* agents: emit after_tool_call with adjusted args

* tests: cover adjusted after_tool_call params

* tests: align adapter after_tool_call expectation

---------

Co-authored-by: jbeno <jim@jimbeno.net>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:33:37 -08:00
Peter Steinberger f9cbcfca0d refactor: modularize slack/config/cron/daemon internals 2026-03-02 22:30:21 +00:00
Peter Steinberger 5d3032b293 fix: align gateway and zalouser typing imports 2026-03-02 22:29:18 +00:00
Peter Steinberger 11adaa15a8 test: isolate high-variance suites in parallel scheduler 2026-03-02 22:29:13 +00:00
Peter Steinberger 3cb851be90 test: micro-optimize heavy gateway/browser/telegram suites 2026-03-02 22:29:04 +00:00
Peter Steinberger 1fa2488db1 fix: wire telegram disableAudioPreflight config validation and precedence tests (#23067) (thanks @yangnim21029) 2026-03-02 22:26:52 +00:00
gemini-3-flash d3cb85eaf5 feat(telegram): add disableAudioPreflight config for groups and topics 2026-03-02 22:26:52 +00:00
Peter Steinberger d89c25d69e fix: support parakeet-mlx output-dir transcript parsing (#9177) (thanks @mac-110) 2026-03-02 22:22:17 +00:00
Alessandro Rodi f257818ea5
fix(sandbox): prevent Windows PATH from poisoning docker exec (#13873)
* fix(sandbox): prevent Windows PATH from poisoning docker exec shell lookup

On Windows hosts, `buildDockerExecArgs` passes the host PATH env var
(containing Windows paths like `C:\Windows\System32`) to `docker exec -e
PATH=...`. Docker uses this PATH to resolve the executable argument
(`sh`), which fails because Windows paths don't exist in the Linux
container — producing `exec: "sh": executable file not found in $PATH`.

Two changes:
- Skip PATH in the `-e` env loop (it's already handled separately via
  OPENCLAW_PREPEND_PATH + shell export)
- Use absolute `/bin/sh` instead of bare `sh` to eliminate PATH
  dependency entirely

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

* style: add braces around continue to satisfy linter

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

* fix(test): update assertion to match /bin/sh in buildDockerExecArgs

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 16:17:33 -06:00
magos-minor 350ac0d824 fix(daemon): default NODE_USE_SYSTEM_CA=1 on macOS 2026-03-02 22:17:14 +00:00
Peter Steinberger 19fafed11d refactor(zalouser): extract policy and message helpers 2026-03-02 22:16:47 +00:00
Peter Steinberger 7253e91300 fix: strengthen cron heartbeat multi-payload suppression (#32131) (thanks @adhishthite) 2026-03-02 22:16:18 +00:00
Adhish 2330c71b63 fix(cron): suppress delivery when multi-payload response contains HEARTBEAT_OK
When a cron agent emits multiple text payloads (narration + tool
summaries) followed by a final HEARTBEAT_OK, the delivery suppression
check `isHeartbeatOnlyResponse` fails because it uses `.every()` —
requiring ALL payloads to be heartbeat tokens. In practice, agents
narrate their work before signaling nothing needs attention.

Fix: check if ANY payload contains HEARTBEAT_OK (`.some()`) while
preserving the media delivery exception (if any payload has media,
always deliver). This matches the semantic intent: HEARTBEAT_OK is
the agent's explicit signal that nothing needs user attention.

Real-world example: heartbeat agent returns 3 payloads:
1. "It's 12:49 AM — quiet hours. Let me run the checks quickly."
2. "Emails: Just 2 calendar invites. Not urgent."
3. "HEARTBEAT_OK"

Previously: all 3 delivered to Telegram. Now: correctly suppressed.

Related: #32013 (fixed a different HEARTBEAT_OK leak path via system
events in timer.ts)
2026-03-02 22:16:18 +00:00
Maple778 477de545f9
fix(feishu): suppress reasoning/thinking block payloads from delivery (#31723)
* fix(extensions/feishu/src/reply-dispatcher.ts): missing privacy check / data leak

Pattern from PR #24969

The fix addresses the critical race condition by placing the 'block' filter check at the very top of the `deliver` function. This ensures that for internal 'block' reasoning chunks, the function returns immediately, preventing any text processing (lines 195-203) and, crucially, preventing the initialization of the streaming state for these payloads (lines 212-216). This ensures that the `streaming` object is not initialized with empty data, and subsequent 'final' payloads will correctly initialize and stream only the final content. The fix also addresses the 'incomplete' validation issue by using `info?.kind !== 'block'`. While the contract likely ensures `info` is present, this defensive approach ensures that if `info` is missing (and the payload is unrelated to internal blocking), the message is still delivered to the user, preventing a 'silent failure' bug. The validation logic at line 205 (`!hasText && !hasMedia`) ensures we do not send empty messages.

* Fix indentation: remove extra 4 spaces from deliver function body

The deliver function is inside the createReplyDispatcherWithTyping call,
so it should be indented at 2 levels (8 spaces), not 3 levels (12 spaces).

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

* test(feishu): cover block payload suppression in reply dispatcher

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 16:15:45 -06:00
Peter Steinberger bd4a082b73 fix: land config raw redaction collision guard (#32174) (thanks @bmendonca3) 2026-03-02 22:14:35 +00:00
Tak Hoffman cbd2e8eea8 Config: consolidate raw redaction overlap and SecretRef safety 2026-03-02 22:14:35 +00:00
bmendonca3 807c600ad1 config: avoid raw redaction collisions on round-trip 2026-03-02 22:14:35 +00:00
Zico a1ee605494 fix(slack): prevent duplicate DM processing from app_mention events
Fixes duplicate message processing in Slack DMs where both message.im
and app_mention events fire for the same message, causing:
- 2x token/credit usage per message
- 2x API calls
- Duplicate agent invocations with same runId

Root cause: app_mention events should only fire for channel mentions,
not DMs. Added channel_type check to skip im/mpim in app_mention handler.

Evidence of bug (from production logs):
- Same runId firing twice within 200-300ms
- Example: runId 13cd482c... at 20:32:42.699Z and 20:32:42.954Z

After fix:
- One message = one runId = one processing run
- 50% reduction in duplicate processing
2026-03-02 22:12:45 +00:00
OliYeet 923ff17ff3 fix(slack): filter inherited parent files from thread replies (#32203)
Slack's Events API includes the parent message's files array in every
thread reply event payload. This caused OpenClaw to re-download and
attach the parent's files to every text-only thread reply, creating
ghost media attachments.

The fix filters out files that belong to the thread starter by comparing
file IDs. The resolveSlackThreadStarter result is already cached, so
this adds no extra API calls.

Closes #32203
2026-03-02 22:11:07 +00:00
markfietje 49687d313c
fix(plugins): allow hardlinks for bundled plugins (fixes #28175, #28404) (openclaw#32119) thanks @markfietje
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: markfietje <4325889+markfietje@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 16:10:31 -06:00
Peter Steinberger 11dcf96628 fix: add changelog for session-store cache invalidation (#32191) (thanks @jalehman) 2026-03-02 22:09:36 +00:00
Josh Lehman 21a1db78b3 test: stabilize bun cache invalidation fixtures 2026-03-02 22:09:36 +00:00
Josh Lehman 175c770171 fix: address session-store cache review feedback 2026-03-02 22:09:36 +00:00
Josh Lehman 1212328c8d fix: refresh session-store cache when file size changes within same mtime tick
The session-store cache used only mtime for invalidation. In fast CI
runs (especially under bun), test writes to the session store can
complete within the same filesystem mtime granularity (~1s on HFS+/ext4),
so the cache returns stale data. This caused non-deterministic failures
in model precedence tests where a session override written to disk was
not observed by the next loadSessionStore() call.

Fix: add file size as a secondary cache invalidation signal. The cache
now checks both mtimeMs and sizeBytes — if either differs from the
cached values, it reloads from disk.

Changes:
- cache-utils.ts: add getFileSizeBytes() helper
- sessions/store.ts: extend SessionStoreCacheEntry with sizeBytes field,
  check size in cache-hit path, populate size on cache writes
- sessions.cache.test.ts: add regression test for same-mtime rewrite
2026-03-02 22:09:36 +00:00
Peter Steinberger f9025c3f55 feat(zalouser): add reactions, group context, and receipt acks 2026-03-02 22:08:11 +00:00
bmendonca3 317075ef3d telegram: route dm sessions by sender id 2026-03-02 22:08:07 +00:00
Peter Steinberger 2c39731846 fix: keep slack off-mode top-level turns in one session (#32193) (thanks @bmendonca3) 2026-03-02 22:05:25 +00:00
bmendonca3 29342c37b5 slack: keep top-level off-mode channel turns in one session 2026-03-02 22:05:25 +00:00
Peter Steinberger cc18e43832 docs(media): clarify audio echo defaults and proxy env 2026-03-02 22:01:24 +00:00
Peter Steinberger 6545317a2c refactor(media): split audio helpers and attachment cache 2026-03-02 22:01:24 +00:00
Peter Steinberger 9bde7f4fde perf: cache allowlist and account-id normalization 2026-03-02 21:58:35 +00:00
Peter Steinberger 3beb1b9da9 test: speed up heavy suites with shared fixtures 2026-03-02 21:58:35 +00:00
Peter Steinberger 6358aae024 refactor(infra): share windows path normalization helper 2026-03-02 21:55:12 +00:00
Peter Steinberger 55a2d12f40 refactor: split inbound and reload pipelines into staged modules 2026-03-02 21:55:01 +00:00
Peter Steinberger 99a3db6ba9 fix(zalouser): enforce group mention gating and typing 2026-03-02 21:53:54 +00:00
Peter Steinberger e5597a8dd4 refactor(media): dedupe tiny-audio test setup and normalize guards formatting 2026-03-02 21:50:54 +00:00
Peter Steinberger 8e259b8310 fix: keep audio transcript echo off-by-default and tiny-audio-safe (#32150) 2026-03-02 21:48:08 +00:00
AytuncYildizli 8f995dfc7a fix(audio): add echoTranscript/echoFormat to Zod config schema 2026-03-02 21:47:09 +00:00
AytuncYildizli 1b61269eec feat(audio): auto-echo transcription to chat before agent processing
When echoTranscript is enabled in tools.media.audio config, the
transcription text is sent back to the originating chat immediately
after successful audio transcription — before the agent processes it.
This lets users verify what was heard from their voice note.

Changes:
- config/types.tools.ts: add echoTranscript (bool) and echoFormat
  (string template) to MediaUnderstandingConfig
- media-understanding/apply.ts: sendTranscriptEcho() helper that
  resolves channel/to from ctx, guards on isDeliverableMessageChannel,
  and calls deliverOutboundPayloads best-effort
- config/schema.help.ts: help text for both new fields
- config/schema.labels.ts: labels for both new fields
- media-understanding/apply.echo-transcript.test.ts: 10 vitest cases
  covering disabled/enabled/custom-format/no-audio/failed-transcription/
  non-deliverable-channel/missing-from/OriginatingTo/delivery-failure

Default echoFormat: '📝 "{transcript}"'

Closes #32102
2026-03-02 21:47:09 +00:00
Shawn ef89b48785
fix(agents): normalize windows workspace path boundary checks (#30766)
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 15:47:02 -06:00
Peter Steinberger a183656f8f fix: apply missed media/runtime follow-ups from merged PRs 2026-03-02 21:45:39 +00:00
Peter Steinberger f2b37f0aa9 refactor(media): dedupe runner proxy and video test fixtures 2026-03-02 21:44:52 +00:00
benthecarman faa4ffec03 Add runtime.stt.transcribeAudioFile for plugin STT access
Expose audio transcription through the PluginRuntime so external
plugins (e.g. marmot) can use openclaw's media-understanding provider
framework without importing unexported internal modules.

The new transcribeAudioFile() wraps runCapability({capability: "audio"})
and reads provider/model/apiKey from tools.media.audio in the config,
matching the pattern used by the Discord VC implementation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-02 21:43:01 +00:00
Glucksberg f7b0378ccb fix(test): update media-understanding tests for whisper skip empty audio
Increase test audio file sizes to meet MIN_AUDIO_FILE_BYTES (1024) threshold
introduced by the skip-empty-audio feature. Fix localPathRoots in skip-tiny-audio
tests so temp files pass path validation. Remove undefined loadApply() call
in apply.test.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:41:09 +00:00
Glucksberg 5f19112217 fix(test): use strict assertion instead of optional chaining 2026-03-02 21:41:09 +00:00
Glucksberg 8039ef7dba test: add URL-only audio skip test for tiny remote attachments 2026-03-02 21:41:09 +00:00
Glucksberg 43f94e3ab8 fix: strengthen test assertions - assert array lengths before indexing 2026-03-02 21:41:09 +00:00
Glucksberg 8b70ba6ab8 fix(#8127): auto-skip tiny/empty audio files in whisper transcription
Add a minimum file size guard (MIN_AUDIO_FILE_BYTES = 1024) before
sending audio to transcription APIs. Files below this threshold are
almost certainly empty or corrupt and would cause unhelpful errors
from Whisper/Deepgram/Groq providers.

Changes:
- Add 'tooSmall' skip reason to MediaUnderstandingSkipError
- Add MIN_AUDIO_FILE_BYTES constant (1024 bytes) to defaults
- Guard both provider and CLI audio paths in runner.ts
- Add comprehensive tests for tiny, empty, and valid audio files
- Update existing test fixtures to use audio files above threshold
2026-03-02 21:41:09 +00:00
Peter Steinberger 036bd18e2a docs(changelog): fix 2026.3.1 split and dedupe entries 2026-03-02 21:40:57 +00:00
Clawrence 9c9ab891c2 fix(media-understanding): guard malformed attachments arrays 2026-03-02 21:39:57 +00:00
Peter Steinberger f7c658efb9 fix(core): resolve post-rebase type errors 2026-03-02 21:39:43 +00:00
Marcus Castro 58cde87436 fix: warn when proxy env var is set but agent creation fails 2026-03-02 21:37:36 +00:00
Marcus Castro 8c1e9949b3 fix: pass proxy-aware fetchFn to media understanding providers
runProviderEntry now calls resolveProxyFetchFromEnv() and passes the
result as fetchFn to transcribeAudio/describeVideo, so media provider
API calls respect HTTPS_PROXY/HTTP_PROXY behind corporate proxies.
2026-03-02 21:37:36 +00:00
Marcus Castro ba3fa44c5b refactor: extract shared proxy-fetch utility from Telegram module
Move makeProxyFetch to src/infra/net/proxy-fetch.ts and add
resolveProxyFetchFromEnv which reads standard proxy env vars
(HTTPS_PROXY, HTTP_PROXY, and lowercase variants) and returns a
proxy-aware fetch via undici's EnvHttpProxyAgent. Telegram re-exports
from the shared location to avoid duplication.
2026-03-02 21:37:36 +00:00
Peter Steinberger 5897eed6e9 refactor(core): dedupe final pairing and sandbox media clones 2026-03-02 21:36:23 +00:00
Peter Steinberger 453a1c179d fix: restore release-check control flow after export guard merge 2026-03-02 21:35:12 +00:00
openjay 76d6514ff5 fix: add "audio" to openai provider capabilities
The openai provider implements transcribeAudio via
transcribeOpenAiCompatibleAudio (Whisper API), but its capabilities
array only declared ["image"]. This caused the media-understanding
runner to skip the openai provider when processing inbound audio
messages, resulting in raw audio files being passed to agents
instead of transcribed text.

Fix: Add "audio" to the capabilities array so the runner correctly
selects the openai provider for audio transcription.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-03-02 21:33:54 +00:00
Peter Steinberger 6a425d189e refactor(channels): dedupe slack telegram and web monitor tests 2026-03-02 21:32:11 +00:00
Peter Steinberger 34daed1d1e refactor(core): dedupe infra, media, pairing, and plugin helpers 2026-03-02 21:32:11 +00:00
Peter Steinberger 91dd89313a refactor(core): dedupe command, hook, and cron fixtures 2026-03-02 21:31:36 +00:00
Peter Steinberger 5f0cbd0edc refactor(gateway): dedupe auth and discord monitor suites 2026-03-02 21:31:36 +00:00
Peter Steinberger ab8b8dae70 refactor(agents): dedupe model and tool test helpers 2026-03-02 21:31:36 +00:00
Peter Steinberger 067855e623 refactor(browser): dedupe browser and cli command wiring 2026-03-02 21:31:36 +00:00
Glucksberg 58e9ca2fb6 fix(release-check): add 4 missing plugin-sdk exports to align with check script 2026-03-02 21:30:44 +00:00
Glucksberg 61d14e8a8a fix(plugin-sdk): add export verification tests and release guard (#27569) 2026-03-02 21:30:44 +00:00
Peter Steinberger 2438fde6d9 fix: trim repeated slack thread context payloads (#32133) (thanks @sourman) 2026-03-02 21:29:36 +00:00
Ahmed Mansour 7a99027ef6 fix(slack): reduce token bloat by skipping thread context on existing sessions
Thread history and thread starter were being fetched and included on
every message in a Slack thread, causing unnecessary token bloat. The
session transcript already contains the full conversation history, so
re-fetching and re-injecting thread history on each turn is redundant.

Now thread history is only fetched for new thread sessions
(!threadSessionPreviousTimestamp). Existing sessions rely on their
transcript for context.

Fixes #32121
2026-03-02 21:29:36 +00:00
Peter Steinberger 42e402dfba fix: clear pending tool-call state across provider modes (#32120) (thanks @jnMetaCode) 2026-03-02 21:28:02 +00:00
jiangnan 11aa18b525 fix(agents): clear pending tool call state on interruption regardless of provider
When `allowSyntheticToolResults` is false (OpenAI, OpenRouter, and most
third-party providers), the guard never cleared its pending tool call map
when a user message arrived during in-flight tool execution. This left
orphaned tool_use blocks in the transcript with no matching tool_result,
causing the provider API to reject all subsequent requests with 400 errors
and permanently breaking the session.

The fix removes the `allowSyntheticToolResults` gate around the flush
calls. `flushPendingToolResults()` already handles both cases correctly:
it only inserts synthetic results when allowed, and always clears the
pending map. The gate was preventing the map from being cleared at all
for providers that disable synthetic results.

Fixes #32098

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:28:02 +00:00
Peter Steinberger 21d6d878ce fix: harden exec allowlist regex literal handling (#32162) (thanks @stakeswky) 2026-03-02 21:26:24 +00:00
User 8da8756f76 fix(exec): escape regex literals in allowlist path matching 2026-03-02 21:26:24 +00:00
George Pickett a4927ed8ee fix: OpenAI OAuth TLS preflight gating (#32051) (thanks @alexfilatov) 2026-03-02 13:24:49 -08:00
George Pickett 1f24323583 Auth: gate OpenAI OAuth TLS preflight in doctor 2026-03-02 13:24:49 -08:00
Alex Filatov dc8a56c857 Fix TLS cert preflight classification false positive 2026-03-02 13:24:49 -08:00
Alex Filatov f181b7dbe6 Add OpenAI OAuth TLS preflight and doctor prerequisite check 2026-03-02 13:24:49 -08:00
scoootscooob 0f1388fa15 fix(gateway): hot-reload channelHealthCheckMinutes without full restart
The health monitor was created once at startup and never touched by
applyHotReload(), so changing channelHealthCheckMinutes only took
effect after a full gateway restart.

Wire up a "restart-health-monitor" reload action so hot-reload can
stop the old monitor and (re)create one with the updated interval —
or disable it entirely when set to 0.

Closes #32105

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:23:20 +00:00
Peter Steinberger b782ecb7eb refactor: harden plugin install flow and main DM route pinning 2026-03-02 21:22:38 +00:00
Peter Steinberger af637deed1 fix: propagate whatsapp inbound fromMe context (#32167) (thanks @scoootscooob) 2026-03-02 21:20:21 +00:00
scoootscooob 73e6dc361e fix(whatsapp): propagate fromMe through inbound message pipeline
The `fromMe` flag from Baileys' WAMessage.key was only used for
access-control filtering and then discarded.  This meant agents
could not distinguish owner-sent messages from contact messages
in DM conversations (everything appeared as from the contact).

Add `fromMe` to `WebInboundMessage`, store it during message
construction, and thread it through `buildInboundLine` →
`formatInboundEnvelope` so DM transcripts prefix owner messages
with `(self):`.

Closes #32061

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:20:21 +00:00
Peter Steinberger 866bd91c65 refactor: harden msteams lifecycle and attachment flows 2026-03-02 21:19:23 +00:00
Peter Steinberger d98a61a977 fix(config): move sensitive-schema hint warnings to debug 2026-03-02 21:13:58 +00:00
Peter Steinberger d01e04bcec test(perf): reduce heavy fixture and guardrail overhead 2026-03-02 21:07:52 +00:00
Peter Steinberger 5a32a66aa8 perf(core): speed up routing, pairing, slack, and security scans 2026-03-02 21:07:52 +00:00
Peter Steinberger 3a08e69a05 refactor: unify queueing and normalize telegram slack flows 2026-03-02 20:55:15 +00:00
Peter Steinberger 320920d523 fix: harden bundled plugin install fallback semantics (#32096) (thanks @scoootscooob) 2026-03-02 20:49:50 +00:00
Peter Steinberger ad12d1fbce fix(plugins): prefer bundled plugin ids over bare npm specs 2026-03-02 20:49:50 +00:00
scoootscooob bfb6c6290f fix: distinguish warning message for non-OpenClaw vs missing npm package
Address Greptile review: show "not a valid OpenClaw plugin" when the
npm package was found but lacks openclaw.extensions, instead of the
misleading "npm package unavailable" message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:49:50 +00:00
scoootscooob da8a17d8de fix(plugins): fall back to bundled plugin when npm spec resolves to non-OpenClaw package (#32019)
When `openclaw plugins install diffs` downloads the unrelated npm
package `diffs@0.1.1` (which lacks `openclaw.extensions`), the install
fails without trying the bundled `@openclaw/diffs` plugin.

Two fixes:
1. Broaden the bundled-fallback trigger to also fire on
   "missing openclaw.extensions" errors (not just npm 404s)
2. Match bundled plugins by pluginId in addition to npmSpec so
   unscoped names like "diffs" resolve to `@openclaw/diffs`

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:49:50 +00:00
Peter Steinberger 089a8785b9 fix: harden msteams revoked-context fallback delivery (#27224) (thanks @openperf) 2026-03-02 20:49:03 +00:00
root e0b91067e3 fix(msteams): add proactive fallback for revoked turn context
Fixes #27189

When an inbound message is debounced, the Bot Framework turn context is
revoked before the debouncer flushes and the reply is dispatched. Any
attempt to use the revoked context proxy throws a TypeError, causing the
reply to fail silently.

This commit fixes the issue by adding a fallback to proactive messaging
when the turn context is revoked:

- `isRevokedProxyError()`: New error utility to reliably detect when a
  proxy has been revoked.

- `reply-dispatcher.ts`: `sendTypingIndicator` now catches revoked proxy
  errors and falls back to sending the typing indicator via
  `adapter.continueConversation`.

- `messenger.ts`: `sendMSTeamsMessages` now catches revoked proxy errors
  when `replyStyle` is `thread` and falls back to proactive messaging.

This ensures that replies are delivered reliably even when the inbound
message was debounced, resolving the core issue where the bot appeared
to ignore messages.
2026-03-02 20:49:03 +00:00
Peter Steinberger d2bb04b436 fix: document msteams auth redirect scoping hardening (#25045) (thanks @bmendonca3) 2026-03-02 20:45:09 +00:00
bmendonca3 4a414c5e53 fix(msteams): scope auth across media redirects 2026-03-02 20:45:09 +00:00
bmendonca3 da22a9113c test(msteams): cover auth stripping on graph redirect hops 2026-03-02 20:45:09 +00:00
bmendonca3 8937c10f1f fix(msteams): scope graph auth redirects 2026-03-02 20:45:09 +00:00
Peter Steinberger 259f6543b4 fix: harden config backup permissions and cleanup (#31718) (thanks @YUJIE2002) 2026-03-02 20:40:15 +00:00
YUJIE2002 3c0ec76e8e fix(config): harden backup file permissions and clean orphan .bak files
Addresses #31699 — config .bak files persist with sensitive data.

Changes:
- Explicitly chmod 0o600 on all .bak files after creation, instead of
  relying on copyFile to preserve source permissions (not guaranteed on
  all platforms, e.g. Windows, NFS mounts).
- Clean up orphan .bak files that fall outside the managed 5-deep
  rotation ring (e.g. PID-stamped leftovers from interrupted writes,
  manual backups like .bak.before-marketing).
- Add tests for permission hardening and orphan cleanup.

The backup ring itself is preserved — it's a valuable recovery mechanism.
This PR hardens the security surface by ensuring backup files are
always owner-only and stale copies don't accumulate indefinitely.
2026-03-02 20:40:15 +00:00
Peter Steinberger d80144f572 fix: keep long Telegram model callbacks selectable (#31857) (thanks @bmendonca3) 2026-03-02 20:38:43 +00:00
bmendonca3 54eb13893f Telegram: support compact model callback fallback 2026-03-02 20:38:43 +00:00
bmendonca3 c582a54554 fix(msteams): preserve guarded dispatcher redirects 2026-03-02 20:37:47 +00:00
bmendonca3 cceecc8bd4 msteams: enforce guarded redirect ownership in safeFetch 2026-03-02 20:37:47 +00:00
Jason Separovic 00347bda75 fix(tools): strip xAI-unsupported JSON Schema keywords from tool definitions
xAI rejects minLength, maxLength, minItems, maxItems, minContains, and
maxContains in tool schemas with a 502 error instead of ignoring them.
This causes all requests to fail when any tool definition includes these
validation-constraint keywords (e.g. sessions_spawn uses maxLength and
maxItems on its attachment fields).

Add stripXaiUnsupportedKeywords() in schema/clean-for-xai.ts, mirroring
the existing cleanSchemaForGemini() pattern. Apply it in normalizeToolParameters()
when the provider is xai directly, or openrouter with an x-ai/* model id.

Fixes tool calls for x-ai/grok-* models both direct and via OpenRouter.
2026-03-02 20:37:07 +00:00
Kay-051 da05395c2a fix(telegram): preserve original filename from Telegram document/audio/video uploads
The downloadAndSaveTelegramFile inner function only used the server-side
file path (e.g. "documents/file_42.pdf") or the Content-Disposition
header (which Telegram doesn't send) to derive the saved filename.
The original filename provided by Telegram via msg.document.file_name,
msg.audio.file_name, msg.video.file_name, and msg.animation.file_name
was never passed through, causing all inbound files to lose their
user-provided names.

Now downloadAndSaveTelegramFile accepts an optional telegramFileName
parameter that takes priority over the fetched/server-side name.
The resolveMedia call site extracts the original name from the message
and passes it through.

Closes #31768

Made-with: Cursor
2026-03-02 20:36:39 +00:00
Altay e45d26b9ed
chore(gitignore): add .claude folder to gitignore (#32141) 2026-03-02 12:35:56 -08:00
bmendonca3 16e7fc2563 fix(models): infer codex weekly usage labels from reset cadence 2026-03-02 20:35:45 +00:00
SidQin-cyber 479095bcfb fix(discord): use per-channel message queues to restore parallel agent dispatch
Replace the single per-account messageQueue Promise chain in
DiscordMessageListener with per-channel queues. This restores parallel
processing for channel-bound agents that regressed in 2026.3.1.

Messages within the same channel remain serialized to preserve ordering,
while messages to different channels now proceed independently. Completed
queue entries are cleaned up to prevent memory accumulation.

Closes #31530
2026-03-02 20:34:41 +00:00
SidQin-cyber 5b63417fec fix(slack): apply mrkdwn conversion in streaming and preview paths
The native streaming path (chatStream) and preview final edit path
(chat.update) send raw Markdown text without converting to Slack
mrkdwn format. This causes **bold** to appear as literal asterisks
instead of rendered bold text.

Apply markdownToSlackMrkdwn() in streaming.ts (start/append/stop) and
in dispatch.ts (preview final edit via chat.update) to match the
non-streaming delivery path behavior.

Closes #31892
2026-03-02 20:34:41 +00:00
bmendonca3 6945ba189d msteams: harden webhook ingress timeouts 2026-03-02 20:34:05 +00:00
webdevtodayjason ab0b2c21f3 WhatsApp: guard main DM last-route to single owner 2026-03-02 20:33:59 +00:00
Mitch McAlister f534ea9906 fix: prevent reasoning text leak through handleMessageEnd fallback
When enforceFinalTag is active (Google providers), stripBlockTags
correctly returns empty for text without <final> tags. However, the
handleMessageEnd fallback recovered raw text, bypassing this protection
and leaking internal reasoning (e.g. "**Applying single-bot mention
rule**NO_REPLY") to Discord.

Guard the fallback with enforceFinalTag check: if the provider is
supposed to use <final> tags and none were seen, the text is treated
as leaked reasoning and suppressed.

Also harden stripSilentToken regex to allow bold markdown (**) as
separator before NO_REPLY, matching the pattern Gemini Flash Lite
produces.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:32:01 +00:00
chilu18 15677133c1 test(msteams): remove tuple-unsafe spread in lifecycle mocks 2026-03-02 20:31:26 +00:00
chilu18 c9d0e345cb fix(msteams): keep monitor alive until shutdown 2026-03-02 20:31:26 +00:00
liuxiaopai-ai bf0653846e Gateway: suppress NO_REPLY lead-fragment chat leaks 2026-03-02 20:27:49 +00:00
Peter Steinberger 3de7768b11 perf(routing): cache normalized agent-id lookups 2026-03-02 20:19:10 +00:00
Peter Steinberger 2937fe0351 perf(config): skip redundant schema and session-store work 2026-03-02 20:19:10 +00:00
Peter Steinberger fb5d8a9cd1 perf(slack): memoize allow-from and mention paths 2026-03-02 20:19:10 +00:00
Peter Steinberger 2f352306fe perf(security): cache scanner directory walks 2026-03-02 20:19:10 +00:00
Peter Steinberger f7765bc151 perf(cron): cache schedule evaluators and stagger offsets 2026-03-02 20:19:10 +00:00
Jean-Marc b52561bfa3
fix(synology-chat): prevent restart loop in startAccount (#23074)
* fix(synology-chat): prevent restart loop in startAccount

startAccount must return a Promise that stays pending while the channel
is running. The gateway wraps the return value in Promise.resolve(), and
when it resolves, the gateway thinks the channel crashed and auto-restarts
with exponential backoff (5s → 10s → 20s..., up to 10 attempts).

Replace the synchronous { stop } return with a Promise<void> that resolves
only when ctx.abortSignal fires, keeping the channel alive until shutdown.

Tested on Synology DS923+ with DSM 7.2 — single startup, no restart loop.

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

* fix(synology-chat): add type guards for startAccount return value

startAccount returns `void | { stop: () => void }` — TypeScript requires
a type guard before accessing .stop on the union type. Added proper checks
in both integration and unit tests.

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

* fix(synology-chat): use Readable stream in integration test for Windows compat

Replace EventEmitter + process.nextTick with Readable stream for
request body simulation. The process.nextTick approach caused the test
to hang on Windows CI (120s timeout) because events were not reliably
delivered to readBody() listeners.

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

* fix: stabilize synology gateway account lifecycle (#23074) (thanks @druide67)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 20:06:16 +00:00
Peter Steinberger 4b50018406 fix: restore helper imports and plugin hook test exports 2026-03-02 19:57:33 +00:00
Peter Steinberger 7003615972 fix: resolve rebase conflict markers 2026-03-02 19:57:33 +00:00
Peter Steinberger eb816e0551 refactor: dedupe extension and ui helpers 2026-03-02 19:57:33 +00:00
Peter Steinberger b1c30f0ba9 refactor: dedupe cli config cron and install flows 2026-03-02 19:57:33 +00:00
Peter Steinberger 9d30159fcd refactor: dedupe channel and gateway surfaces 2026-03-02 19:57:33 +00:00
Peter Steinberger 9617ac9dd5 refactor: dedupe agent and reply runtimes 2026-03-02 19:57:33 +00:00
Peter Steinberger 8768487aee refactor(shared): dedupe protocol schema typing and session/media helpers 2026-03-02 19:57:33 +00:00
Peter Steinberger ee0d7ba6d6 chore: normalize changelog credit for #31841 (thanks @liuxiaopai-ai) 2026-03-02 19:56:18 +00:00
liuxiaopai-ai c48a0621ff fix(agents): map sandbox workdir from container path 2026-03-02 19:56:18 +00:00
Peter Steinberger b1cc8ffe9e fix: migrate legacy cron store shapes (#31926) (thanks @bmendonca3) 2026-03-02 19:55:19 +00:00
bmendonca3 4cd04e4652 fix(cron): migrate legacy string schedule and command jobs 2026-03-02 19:55:19 +00:00
Peter Steinberger c424836fbe refactor: harden outbound, matrix bootstrap, and plugin entry resolution 2026-03-02 19:55:09 +00:00
Peter Steinberger a351ab2481 fix: persist webchat stream-only finals (#31920) (thanks @Sid-Qin) 2026-03-02 19:54:26 +00:00
SidQin-cyber 15226b0b83 fix(gateway): persist streamed text when webchat final event lacks message
When an agent streams text and then immediately runs tool calls, the
webchat UI drops the streamed content: the "final" event arrives with
message: undefined (buffer consumed by sub-run), and the client clears
chatStream without saving it to chatMessages.

Before clearing chatStream on a "final" event, check whether the stream
buffer has content. If no finalMessage was provided but the stream is
non-empty, synthesize an assistant message from the buffered text —
mirroring the existing "aborted" handler's preservation logic.

Closes #31895
2026-03-02 19:54:26 +00:00
Peter Steinberger 0cf533ac61 fix: recover orphan same-pid session locks (#32081) (thanks @bmendonca3) 2026-03-02 19:53:41 +00:00
bmendonca3 4985c561df sessions: reclaim orphan self-pid lock files 2026-03-02 19:53:41 +00:00
Peter Steinberger 160dad56c4 fix: suppress HEARTBEAT_OK fallback leak (#32093) (thanks @scoootscooob) 2026-03-02 19:51:51 +00:00
scoootscooob a3c5d21b4d fix(cron): suppress HEARTBEAT_OK summary from leaking into main session (#32013)
When an isolated cron agent returns HEARTBEAT_OK (nothing to announce),
the direct delivery is correctly skipped, but the fallback path in
timer.ts still enqueues the summary as a system event to the main
session. Filter out heartbeat-only summaries using isCronSystemEvent
before enqueuing, so internal ack tokens never reach user conversations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:51:51 +00:00
Jean-Marc 9a3800d8e6
fix(synology-chat): resolve Chat API user_id for reply delivery (#23709)
* fix(synology-chat): resolve Chat API user_id for reply delivery

Synology Chat outgoing webhooks use a per-integration user_id that
differs from the global Chat API user_id required by method=chatbot.
This caused reply messages to fail silently when the IDs diverged.

Changes:
- Add fetchChatUsers() and resolveChatUserId() to resolve the correct
  Chat API user_id via the user_list endpoint (cached 5min)
- Use resolved user_id for all sendMessage() calls in webhook handler
  and channel dispatcher
- Add Provider field to MsgContext so the agent runner correctly
  identifies the message channel (was "unknown", now "synology-chat")
- Log warnings when user_list API fails or when falling back to
  unresolved webhook user_id
- Add 5 tests for user_id resolution (nickname, username, case,
  not-found, URL rewrite)

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

* fix(synology-chat): use Readable stream in integration test for Windows compat

Replace EventEmitter + process.nextTick with Readable stream for
request body simulation. The process.nextTick approach caused the test
to hang on Windows CI (120s timeout) because events were not reliably
delivered to readBody() listeners.

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

* fix: harden synology reply user resolution and cache scope (#23709) (thanks @druide67)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 19:50:58 +00:00
Peter Steinberger 39afcee864 test(perf): trim cron and audit fixture overhead 2026-03-02 19:48:02 +00:00
Peter Steinberger d979eeda9f perf(runtime): reduce slack prep and qmd cache-key overhead 2026-03-02 19:48:02 +00:00
Peter Steinberger 8e48f7e353 fix(tui): honor explicit gateway auth for url overrides 2026-03-02 19:48:02 +00:00
Peter Steinberger 2a2e2c3630 fix: land synology webhook payload compatibility ACK (#26635) (thanks @memphislee09-source) 2026-03-02 19:45:55 +00:00
memphislee09-source 92bf77d9a0 fix(synology-chat): accept JSON/aliases and ACK webhook with 204 2026-03-02 19:45:55 +00:00
Peter Steinberger a3bb7a5ee5 fix: land synology webhook bounded body reads (#25831) (thanks @bmendonca3) 2026-03-02 19:42:56 +00:00
bmendonca3 2b088ca125 test(synology-chat): use real plugin-sdk helper exports 2026-03-02 19:42:56 +00:00
bmendonca3 aeeb0474c6 test(synology-chat): match request destroy typing 2026-03-02 19:42:56 +00:00
bmendonca3 6df36a8b35 fix(synology-chat): bound webhook body read time 2026-03-02 19:42:56 +00:00
Mark L fbd1210ec2
fix(plugins): support legacy install entry fallback (#32055)
* fix(plugins): fallback install entrypoints for legacy manifests

* Voice Call: enforce exact webhook path match

* Tests: isolate webhook path suite and reset cron auth state

* chore: keep #31930 scoped to voice webhook path fix

* fix: add changelog for exact voice webhook path match (#31930) (thanks @afurm)

* fix: handle HTTP 529 (Anthropic overloaded) in failover error classification

Classify Anthropic's 529 status code as "rate_limit" so model fallback
triggers reliably without depending on fragile message-based detection.

Closes #28502

* fix: add changelog for HTTP 529 failover classification (#31854) (thanks @bugkill3r)

* fix(slack): guard against undefined text in includes calls during mention handling

* fix: add changelog for mentions/slack null-safe guards (#31865) (thanks @stone-jin)

* fix(memory-lancedb): pass dimensions to embedding API call

- Add dimensions parameter to Embeddings constructor
- Pass dimensions to OpenAI embeddings.create() API call
- Fixes dimension mismatch when using custom embedding models like DashScope text-embedding-v4

* fix: add regression for memory-lancedb dimensions pass-through (#32036) (thanks @scotthuang)

* fix(telegram): guard malformed native menu specs

* fix: harden plugin command registration + telegram menu guard (#31997) (thanks @liuxiaopai-ai)

* fix(gateway): restart heartbeat on model config changes

* fix: add changelog credit for heartbeat model reload (#32046) (thanks @stakeswky)

* test(process): replace no-output timer subprocess with spawn mock

* test(perf): trim repeated setup in cron memory and config suites

* test(perf): reduce per-case setup in script and git-hook tests

* fix(slack): scope debounce key by message timestamp to prevent cross-thread collisions

Top-level channel messages from the same sender shared a bare channel
debounce key, causing concurrent messages in different threads to merge
into a single reply on the wrong thread. Now the debounce key includes
the message timestamp for top-level messages, matching how the downstream
session layer already scopes by canonicalThreadId.

Extracted buildSlackDebounceKey() for testability.

Closes #31935

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

* fix: harden slack debounce key routing and ordering (#31951) (thanks @scoootscooob)

* fix(openrouter): skip reasoning.effort injection for x-ai/grok models

x-ai/grok models on OpenRouter do not support the reasoning.effort
parameter and reject payloads containing it with "Invalid arguments
passed to the model." Skip reasoning injection for these models, the
same way we already skip it for the dynamic "auto" routing model.

Closes #32039

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

* fix: add changelog credit for openrouter x-ai reasoning guard (#32054) (thanks @scoootscooob)

* fix(agents): scope volcengine-plan/byteplus-plan auth lookup to profile resolution

The configure flow stores auth credentials under `provider: "volcengine"`,
but the coding model uses `volcengine-plan` as its provider. Add a scoped
`normalizeProviderIdForAuth` function used only by `listProfilesForProvider`
so coding-plan variants resolve to their base provider for auth credential
lookup without affecting global provider routing.

Closes #31731

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

* fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots

PR #28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.

When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.

Relates to #31716

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

* fix: add changelog credit for fsPolicy image/pdf propagation (#31882) (thanks @justinhuangcode)

* fix: skip Telegram command sync when menu is unchanged (#32017)

Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.

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

* fix(telegram): scope command-sync hash cache by bot identity (#32059)

* fix: normalize coding-plan providers in auth order validation

* feat(security): Harden Docker browser container chromium flags (#23889) (#31504)

* Gateway: honor OPENCLAW_GATEWAY_URL override for remote/local calls

* Agents: fix sandbox sessionKey usage for PI embedded subagent calls

* Sandbox: tighten browser container Chromium runtime flags

* fix: add sandbox browser defaults for container hardening

* docs: expand sandbox browser default flags list

* fix: make sandbox browser flags optional and preserve gateway env auth overrides

* docs: scope PR 31504 changelog entry

* style: format gateway call override handling

* fix: dedupe sandbox browser chrome args

* fix: preserve remote tls fingerprint for env gateway override

* fix: enforce auth for env gateway URL override

* chore: document gateway override auth security expectations

* fix(delivery): strip HTML tags for plain-text messaging surfaces

Models occasionally produce HTML tags in their output. While these render
fine on web surfaces, they appear as literal text on WhatsApp, Signal,
SMS, IRC, and Telegram.

Add sanitizeForPlainText() utility that converts common inline HTML to
lightweight-markup equivalents and strips remaining tags. Applied in the
outbound delivery pipeline for non-HTML surfaces only.

Closes #31884
See also: #18558

* fix(outbound): harden plain-text HTML sanitization paths (#32034)

* fix(security): harden file installs and race-path tests

* matrix: bootstrap crypto runtime when npm scripts are skipped

* fix(matrix): keep plugin register sync while bootstrapping crypto runtime (#31989)

* perf(runtime): reduce cron persistence and logger overhead

* test(perf): use prebuilt plugin install archive fixtures

* test(perf): increase guardrail scan read concurrency

* fix(queue): restart drain when message enqueued after idle window

After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES.  If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.

Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts).  enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately.  clearSessionQueues cleans up the callback
cache alongside the queue state.

* fix: avoid stale followup drain callbacks (#31902) (thanks @Lanfei)

* fix(synology-chat): read cfg from outbound context so incomingUrl resolves

* fix: require openclaw.extensions for plugin installs (#32055) (thanks @liuxiaopai-ai)

---------

Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Saurabh <skmishra1991@gmail.com>
Co-authored-by: stone-jin <1520006273@qq.com>
Co-authored-by: scotthuang <scotthuang@tencent.com>
Co-authored-by: User <user@example.com>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: justinhuangcode <justinhuangcode@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: AytuncYildizli <cryptosquanch@gmail.com>
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
Co-authored-by: Jealous <CooLanfei@163.com>
Co-authored-by: white-rm <zhang.xujin@xydigit.com>
2026-03-02 19:41:05 +00:00
xtao 26b8a70a52 fix(synology-chat): use finalizeInboundContext for proper normalization 2026-03-02 19:39:14 +00:00
xtao e391646043 fix(synology-chat): add missing context fields for message delivery 2026-03-02 19:39:14 +00:00
white-rm e513714103 fix(synology-chat): read cfg from outbound context so incomingUrl resolves 2026-03-02 19:38:14 +00:00
Peter Steinberger b645654923 fix: avoid stale followup drain callbacks (#31902) (thanks @Lanfei) 2026-03-02 19:38:08 +00:00
Jealous 60130203e1 fix(queue): restart drain when message enqueued after idle window
After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES.  If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.

Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts).  enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately.  clearSessionQueues cleans up the callback
cache alongside the queue state.
2026-03-02 19:38:08 +00:00
Peter Steinberger c4511df283 test(perf): increase guardrail scan read concurrency 2026-03-02 19:34:04 +00:00
Peter Steinberger 64abf9a925 test(perf): use prebuilt plugin install archive fixtures 2026-03-02 19:34:04 +00:00
Peter Steinberger 1616113170 perf(runtime): reduce cron persistence and logger overhead 2026-03-02 19:34:04 +00:00
Peter Steinberger fcec2e364d fix(matrix): keep plugin register sync while bootstrapping crypto runtime (#31989) 2026-03-02 19:33:22 +00:00
bmendonca3 66c1da45d4 matrix: bootstrap crypto runtime when npm scripts are skipped 2026-03-02 19:33:22 +00:00
Peter Steinberger dbbd41a2ed fix(security): harden file installs and race-path tests 2026-03-02 19:30:02 +00:00
Peter Steinberger e1bc5cad25 fix(outbound): harden plain-text HTML sanitization paths (#32034) 2026-03-02 19:28:47 +00:00
AytuncYildizli 62d0cfeee7 fix(delivery): strip HTML tags for plain-text messaging surfaces
Models occasionally produce HTML tags in their output. While these render
fine on web surfaces, they appear as literal text on WhatsApp, Signal,
SMS, IRC, and Telegram.

Add sanitizeForPlainText() utility that converts common inline HTML to
lightweight-markup equivalents and strips remaining tags. Applied in the
outbound delivery pipeline for non-HTML surfaces only.

Closes #31884
See also: #18558
2026-03-02 19:28:47 +00:00
Vincent Koc a19a7f5e6e
feat(security): Harden Docker browser container chromium flags (#23889) (#31504)
* Gateway: honor OPENCLAW_GATEWAY_URL override for remote/local calls

* Agents: fix sandbox sessionKey usage for PI embedded subagent calls

* Sandbox: tighten browser container Chromium runtime flags

* fix: add sandbox browser defaults for container hardening

* docs: expand sandbox browser default flags list

* fix: make sandbox browser flags optional and preserve gateway env auth overrides

* docs: scope PR 31504 changelog entry

* style: format gateway call override handling

* fix: dedupe sandbox browser chrome args

* fix: preserve remote tls fingerprint for env gateway override

* fix: enforce auth for env gateway URL override

* chore: document gateway override auth security expectations
2026-03-02 11:28:27 -08:00
Peter Steinberger ea1fe77c83 fix: normalize coding-plan providers in auth order validation 2026-03-02 19:26:09 +00:00
Peter Steinberger d486b0a925 fix(telegram): scope command-sync hash cache by bot identity (#32059) 2026-03-02 19:25:19 +00:00
scoootscooob 10fb632c9e fix: skip Telegram command sync when menu is unchanged (#32017)
Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:25:19 +00:00
Peter Steinberger 4a2329e0af fix: add changelog credit for fsPolicy image/pdf propagation (#31882) (thanks @justinhuangcode) 2026-03-02 19:24:33 +00:00
justinhuangcode 14baadda2c fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots
PR #28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.

When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.

Relates to #31716

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:24:33 +00:00
justinhuangcode aab87ec880 fix(agents): scope volcengine-plan/byteplus-plan auth lookup to profile resolution
The configure flow stores auth credentials under `provider: "volcengine"`,
but the coding model uses `volcengine-plan` as its provider. Add a scoped
`normalizeProviderIdForAuth` function used only by `listProfilesForProvider`
so coding-plan variants resolve to their base provider for auth credential
lookup without affecting global provider routing.

Closes #31731

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:22:19 +00:00
Peter Steinberger a71b8d23be fix: add changelog credit for openrouter x-ai reasoning guard (#32054) (thanks @scoootscooob) 2026-03-02 19:20:11 +00:00
scoootscooob 6c7d012320 fix(openrouter): skip reasoning.effort injection for x-ai/grok models
x-ai/grok models on OpenRouter do not support the reasoning.effort
parameter and reject payloads containing it with "Invalid arguments
passed to the model." Skip reasoning injection for these models, the
same way we already skip it for the dynamic "auto" routing model.

Closes #32039

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:20:11 +00:00
Peter Steinberger 0956b599e1 fix: harden slack debounce key routing and ordering (#31951) (thanks @scoootscooob) 2026-03-02 19:18:25 +00:00
scoootscooob d4b20f5295 fix(slack): scope debounce key by message timestamp to prevent cross-thread collisions
Top-level channel messages from the same sender shared a bare channel
debounce key, causing concurrent messages in different threads to merge
into a single reply on the wrong thread. Now the debounce key includes
the message timestamp for top-level messages, matching how the downstream
session layer already scopes by canonicalThreadId.

Extracted buildSlackDebounceKey() for testability.

Closes #31935

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:18:25 +00:00
Peter Steinberger 07eaeb7350 test(perf): reduce per-case setup in script and git-hook tests 2026-03-02 19:16:46 +00:00
Peter Steinberger 83ec545bed test(perf): trim repeated setup in cron memory and config suites 2026-03-02 19:16:46 +00:00
Peter Steinberger 6add2bcc15 test(process): replace no-output timer subprocess with spawn mock 2026-03-02 19:16:46 +00:00
Peter Steinberger fbb343ab30 fix: add changelog credit for heartbeat model reload (#32046) (thanks @stakeswky) 2026-03-02 19:13:57 +00:00
User e1e93d932f fix(gateway): restart heartbeat on model config changes 2026-03-02 19:13:57 +00:00
Peter Steinberger ee68fa86b5 fix: harden plugin command registration + telegram menu guard (#31997) (thanks @liuxiaopai-ai) 2026-03-02 19:04:56 +00:00
liuxiaopai-ai 0958d11478 fix(telegram): guard malformed native menu specs 2026-03-02 19:04:56 +00:00
Peter Steinberger ed55b63684 fix: add regression for memory-lancedb dimensions pass-through (#32036) (thanks @scotthuang) 2026-03-02 19:02:11 +00:00
scotthuang 31bc2cc202 fix(memory-lancedb): pass dimensions to embedding API call
- Add dimensions parameter to Embeddings constructor
- Pass dimensions to OpenAI embeddings.create() API call
- Fixes dimension mismatch when using custom embedding models like DashScope text-embedding-v4
2026-03-02 19:02:11 +00:00
Peter Steinberger c146748d7a fix: add changelog for mentions/slack null-safe guards (#31865) (thanks @stone-jin) 2026-03-02 19:00:08 +00:00
stone-jin 2a98fd3d0b fix(slack): guard against undefined text in includes calls during mention handling 2026-03-02 19:00:08 +00:00
Peter Steinberger ce4faedad6 fix: add changelog for HTTP 529 failover classification (#31854) (thanks @bugkill3r) 2026-03-02 18:59:10 +00:00
Saurabh 1ef9a2a8ea fix: handle HTTP 529 (Anthropic overloaded) in failover error classification
Classify Anthropic's 529 status code as "rate_limit" so model fallback
triggers reliably without depending on fragile message-based detection.

Closes #28502
2026-03-02 18:59:10 +00:00
Peter Steinberger 84d9b64326 fix: add changelog for exact voice webhook path match (#31930) (thanks @afurm) 2026-03-02 18:57:46 +00:00
Peter Steinberger 99392f9868 chore: keep #31930 scoped to voice webhook path fix 2026-03-02 18:57:46 +00:00
Andrii Furmanets 662f389f45 Tests: isolate webhook path suite and reset cron auth state 2026-03-02 18:57:46 +00:00
Andrii Furmanets 3bd0505433 Voice Call: enforce exact webhook path match 2026-03-02 18:57:46 +00:00
SidQin-cyber dde43121c0 fix(deps): add strip-ansi runtime dependency
Add strip-ansi as an explicit root dependency so pi-coding-agent runtime imports do not fail with ERR_MODULE_NOT_FOUND in strict pnpm installs.
2026-03-02 18:49:17 +00:00
Peter Steinberger 6a5041f3ff test(exec): deflake no-output timeout heartbeat scenario 2026-03-02 18:41:59 +00:00
Peter Steinberger bcb1eb2f03 perf(test): speed up setup and config path resolution 2026-03-02 18:41:58 +00:00
Peter Steinberger 842087319b perf(logging): skip config/fs work in default silent test path 2026-03-02 18:41:58 +00:00
Lucenx9 5c1eb071ca
fix(whatsapp): restore direct inbound metadata for relay agents (#31969)
* fix(whatsapp): restore direct inbound metadata for relay agents

* fix(auto-reply): use shared inbound channel resolver for direct metadata

* chore(ci): retrigger checks after base update

* fix: add changelog attribution for inbound metadata relay fix (#31969) (thanks @Lucenx9)

---------

Co-authored-by: Simone <simone@example.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 18:40:04 +00:00
scoootscooob 4030de6c73
fix(cron): move session reaper to finally block so it runs reliably (#31996)
* fix(cron): move session reaper to finally block so it runs reliably

The cron session reaper was placed inside the try block of onTimer(),
after job execution and state updates. If the locked persist section
threw, the reaper was skipped — causing isolated cron run sessions to
accumulate indefinitely in sessions.json.

Move the reaper into the finally block so it always executes after a
timer tick, regardless of whether job execution succeeded. The reaper
is already self-throttled (MIN_SWEEP_INTERVAL_MS = 5 min) so calling
it more reliably has no performance impact.

Closes #31946

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

* fix: strengthen cron reaper failure-path coverage and changelog (#31996) (thanks @scoootscooob)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 18:38:59 +00:00
liuxiaopai-ai c9558cdcd7 fix(launchd): set restrictive umask in gateway plist 2026-03-02 18:38:56 +00:00
liuxiaopai-ai 740bb77c8c fix(reply): prefer provider over surface for run channel fallback 2026-03-02 18:37:00 +00:00
Adhish Thite 63734df3b0
fix(doctor): resolve false positive for local memory search when no explicit modelPath (#32014)
* fix(doctor): resolve false positive for local memory search when no explicit modelPath

When memorySearch.provider is 'local' (or 'auto') and no explicit
local.modelPath is configured, the runtime auto-resolves to
DEFAULT_LOCAL_MODEL (embeddinggemma-300m via HuggingFace). However,
the doctor's hasLocalEmbeddings() check only inspected the config
value and returned false when modelPath was empty, triggering a
misleading warning.

Fix: fall back to DEFAULT_LOCAL_MODEL in hasLocalEmbeddings(), matching
the runtime behavior in createLocalEmbeddingProvider().

Closes #31998

* fix: scope DEFAULT_LOCAL_MODEL fallback to explicit provider:local only

Address review feedback: canAutoSelectLocal() in the runtime skips
local for empty/hf: model paths in auto mode. The DEFAULT_LOCAL_MODEL
fallback should only apply when provider is explicitly 'local', not
when provider is 'auto' — otherwise users with no local file and no
API keys would get a clean doctor report but no working embeddings.

Add useDefaultFallback parameter to hasLocalEmbeddings() to
distinguish the two code paths.

* fix: preserve gateway probe warning for local provider with default model

When hasLocalEmbeddings returns true via DEFAULT_LOCAL_MODEL fallback,
also check the gateway memory probe if available. If the probe reports
not-ready (e.g. node-llama-cpp missing or model download failed),
emit a warning instead of silently reporting healthy.

Addresses review feedback about bypassing probe-based validation.

* fix: add changelog attribution for doctor local fallback fix (#32014) (thanks @adhishthite)

---------

Co-authored-by: Adhish <adhishthite@Adhishs-MacBook-Pro.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 18:35:40 +00:00
Peter Steinberger 534168a7a7 fix: add changelog entry for config-form secret union (#31866) (thanks @ningding97) 2026-03-02 18:35:15 +00:00
ningding97 9c1312b5e4 fix(ui): handle SecretInput union in config form analyzer
The config form marks models.providers as unsupported because
SecretInputSchema creates a oneOf union that the form analyzer
cannot handle. Add detection for secret-ref union variants and
normalize them to plain string inputs for form display.

Closes #31490
2026-03-02 18:35:15 +00:00
Mark L 1727279598
fix(browser): default to openclaw profile when unspecified (#32031) 2026-03-02 18:34:37 +00:00
Peter Steinberger d52e5e1d85 fix: add regression tests for telegram token guard (#31973) (thanks @ningding97) 2026-03-02 18:33:49 +00:00
ningding97 c1c20491da fix(telegram): guard token.trim() against undefined to prevent startup crash
When account.token is undefined (e.g. missing botToken config),
calling .trim() directly throws "Cannot read properties of undefined".
Use nullish coalescing to fall back to empty string before trimming.

Closes #31944
2026-03-02 18:33:49 +00:00
Maho d21cf44452
fix(slack): remove message.channels/message.groups handlers that crash Bolt 4.6 (#32033)
* fix(slack): remove message.channels/message.groups handlers that crash Bolt 4.6

Bolt 4.6 rejects app.event() calls with event names starting with
"message." (e.g. "message.channels", "message.groups"), throwing
AppInitializationError on startup. These handlers were added in #31701
based on the incorrect assumption that Slack dispatches typed event
names to Bolt. In reality, Slack always delivers events with
type:"message" regardless of the Event Subscription name; the
channel_type field distinguishes the source.

The generic app.event("message") handler already receives all channel,
group, IM, and MPIM messages. The additional typed handlers were
unreachable even if Bolt allowed them, since no event payload ever
carries type:"message.channels".

This preserves the handleIncomingMessageEvent refactor from #31701
(extracting the handler into a named function) while removing only
the broken registrations.

Fixes the Slack provider crash loop affecting all accounts on
@slack/bolt >= 4.6.0.

Closes #31674 (original issue was not caused by missing handlers)

* fix: document Slack Bolt 4.6 startup handler fix (#32033) (thanks @mahopan)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 18:32:42 +00:00
bmendonca3 738f5d4533 skills: make sherpa-onnx-tts bin ESM-compatible 2026-03-02 18:30:42 +00:00
Peter Steinberger a8fe8b6bf8 test(guardrails): exclude suite files and harden auth temp identity naming 2026-03-02 18:21:13 +00:00
Peter Steinberger 82f01d6081 perf(runtime): reduce startup import overhead in logging and schema validation 2026-03-02 18:21:13 +00:00
Sid 41c8734afd
fix(gateway): move plugin HTTP routes before Control UI SPA catch-all (#31885)
* fix(gateway): move plugin HTTP routes before Control UI SPA catch-all

The Control UI handler (`handleControlUiHttpRequest`) acts as an SPA
catch-all that matches every path, returning HTML for GET requests and
405 for other methods.  Because it ran before `handlePluginRequest` in
the request chain, any plugin HTTP route that did not live under
`/plugins` or `/api` was unreachable — shadowed by the catch-all.

Reorder the handlers so plugin routes are evaluated first.  Core
built-in routes (hooks, tools, Slack, Canvas, etc.) still take
precedence because they are checked even earlier in the chain.
Unmatched plugin paths continue to fall through to Control UI as before.

Closes #31766

* fix: add changelog for plugin route precedence landing (#31885) (thanks @Sid-Qin)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 18:16:14 +00:00
Peter Steinberger cf5702233c docs(security)!: document messaging-only onboarding default and hook/model risk 2026-03-02 18:15:49 +00:00
Mark L 718d418b32
fix(daemon): harden launchd plist with umask 077 (#31919)
* fix(daemon): add launchd umask hardening

* fix: finalize launchd umask changelog + thanks (#31919) (thanks @liuxiaopai-ai)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 18:13:41 +00:00
Peter Steinberger 16df7ef4a9 feat(onboarding)!: default tools profile to messaging 2026-03-02 18:12:11 +00:00
Mark L 9b8e642475
Config: newline-join sandbox setupCommand arrays (#31953) 2026-03-02 18:11:32 +00:00
bmendonca3 8b27582509
fix(cli): apply --profile before dotenv bootstrap in runCli (#31950)
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
2026-03-02 18:09:45 +00:00
bmendonca3 a6489ab5e9
fix(agents): cap openai-completions tool call ids to provider-safe format (#31947)
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
2026-03-02 18:08:20 +00:00
Peter Steinberger 83c8406f01 refactor(security): split gateway auth suites and share safe write path checks 2026-03-02 18:07:03 +00:00
Peter Steinberger 602f6439bd test(memory): stabilize windows qmd spawn expectation 2026-03-02 18:06:12 +00:00
Peter Steinberger 1c9deeda97 refactor: split webhook ingress and policy guards 2026-03-02 18:02:21 +00:00
Peter Steinberger fc0d374390 test(perf): drop duplicate gateway config patch validation case 2026-03-02 18:00:11 +00:00
Peter Steinberger 0ebe0480fa test(perf): replace relay fixed sleeps with condition waits 2026-03-02 17:55:47 +00:00
Peter Steinberger 8ae8056622 test(perf): trim telegram webhook artificial delay windows 2026-03-02 17:48:36 +00:00
Peter Steinberger 54382a66b4 test(perf): bypass matrix send queue delay in send tests 2026-03-02 17:46:31 +00:00
Peter Steinberger d7ae61c412 test(gateway): fix trusted-proxy control-ui auth test origin config 2026-03-02 17:45:45 +00:00
Peter Steinberger b07589642d test(perf): remove redundant acpx healthy-command case 2026-03-02 17:41:51 +00:00
Peter Steinberger 26b8e6d510 test(perf): avoid cron min-refire delay in auto-run coverage 2026-03-02 17:41:51 +00:00
Peter Steinberger e339c75d5d style(docs): format faq markdown spacing 2026-03-02 17:38:11 +00:00
Peter Steinberger 7dac9b05dd fix(security): harden zip write race handling 2026-03-02 17:38:11 +00:00
Peter Steinberger eb35fb745d docs: remove provider recommendation language 2026-03-02 17:33:38 +00:00
Peter Steinberger b9e820b7ed test(perf): cut exec approval metadata test timeout 2026-03-02 17:33:06 +00:00
Peter Steinberger aee27d0e38 refactor(security): table-drive wrapper approval pinning tests 2026-03-02 17:30:48 +00:00
Peter Steinberger 34ff873a7e test(perf): trim fixed waits in relay and startup tests 2026-03-02 17:30:33 +00:00
Peter Steinberger 310dd24ce3 test(perf): clean acpx runtime fixtures at suite end 2026-03-02 17:30:33 +00:00
Peter Steinberger d4bf07d075 refactor(security): unify hardened install and fs write flows 2026-03-02 17:23:29 +00:00
Peter Steinberger d3e8b17aa6 fix: harden webhook auth-before-body handling 2026-03-02 17:21:09 +00:00
Peter Steinberger dded569626 fix(security): preserve system.run wrapper approval semantics 2026-03-02 17:20:52 +00:00
Peter Steinberger 104d32bb64 fix(security): unify root-bound write hardening 2026-03-02 17:12:33 +00:00
Peter Steinberger be3a62c5e0 test(perf): defer delivery queue fixture cleanup to suite end 2026-03-02 17:10:55 +00:00
Hiren Thakore 193ad2f4f0
fix: handle PowerShell execution policy on Windows install (#24794)
* fix: add Arch Linux support to install.sh (GH#8051)

* fix: handle PowerShell execution policy on Windows install (GH#24784)
2026-03-02 11:09:01 -06:00
Dalomeve a0e11e63fe
docs(faq): add Windows exec encoding troubleshooting (#30736)
Co-authored-by: dalomeve <dalomeve@users.noreply.github.com>
2026-03-02 11:08:26 -06:00
Peter Steinberger 07b16d5ad0 fix(security): harden workspace bootstrap boundary reads 2026-03-02 17:07:36 +00:00
Mark L 67b2dde7c5
Docs: add WSL2 boot auto-start guide (#31616) 2026-03-02 11:07:15 -06:00
Glucksberg 7a55a3ca07
fix(install): correct Windows PATH troubleshooting docs (#28102)
* fix(install): correct Windows PATH troubleshooting — no \bin suffix needed (closes #19921)

* fix(docs): apply same PATH fix to FAQ
2026-03-02 11:07:07 -06:00
Peter Steinberger 11562c452a test(perf): avoid unused heartbeat fixture file writes 2026-03-02 17:01:40 +00:00
Val Alexander eb2e20c994 fix(ui): preserve margin-top: 0 for onboarding mode
- Change margin from -12px -16px -32px to 0 -16px -32px
- Preserves zero top offset required for onboarding mode
- Prevents clipping of top edge/actions area when padding-top: 0
2026-03-02 11:01:27 -06:00
Val Alexander 24a13c05b3 fix(ui): add mobile responsive margins and overflow fallback
- Add margin: 0 for mobile viewports (<=600px, <=400px) to prevent clipping
- Add overflow: hidden fallback for older browsers (Safari <16, Firefox <81)
- Fixes mobile regression where negative margins over-cancel padding

Addresses issue where save button was clipped on mobile due to
hard-coded desktop negative margins not accounting for mobile's
smaller content padding (4px 4px 16px).
2026-03-02 11:01:27 -06:00
SidQin-cyber 20c36f7e84 fix(ui): prevent config page save button from being clipped by overflow
The config-layout used a uniform margin: -16px that did not match the
parent .content padding (12px 16px 32px), causing the right edge of the
actions bar—including the Save button—to extend into the overflow-hidden
region on systems with non-overlay scrollbars (e.g. Ubuntu/GTK).

Changes:
- Match negative margin to actual .content padding (-12px -16px -32px).
- Use overflow: clip instead of overflow: hidden on .config-main so it
  does not create a scroll container that shifts the stacking context.
- Add flex-shrink: 0 and position: relative on .config-actions to
  guarantee the actions bar is never collapsed or layered behind the
  scrollable content area.

Closes #31658
2026-03-02 11:01:27 -06:00
Peter Steinberger db7a8a6982 test(perf): reuse delivery queue suite temp root 2026-03-02 16:55:18 +00:00
Peter Steinberger 4a80311628 refactor(security): split sandbox media staging and stream safe copies 2026-03-02 16:53:14 +00:00
Peter Steinberger 7a7eee920a refactor(gateway): harden plugin http route contracts 2026-03-02 16:48:00 +00:00
Peter Steinberger 33e76db12a refactor(gateway): scope ws origin fallback metrics to runtime 2026-03-02 16:47:00 +00:00
Peter Steinberger 9a68590385 refactor(logging): extract bounded regex redaction util 2026-03-02 16:47:00 +00:00
Peter Steinberger 031bf0c6c0 refactor(security): split safe-regex parse and bounded matching 2026-03-02 16:47:00 +00:00
Peter Steinberger 8611fd67b5 test(perf): remove duplicate bundled memory slot loader case 2026-03-02 16:46:17 +00:00
Peter Steinberger 14c93d2646 docs(changelog): add skills archive extraction hardening note 2026-03-02 16:45:47 +00:00
Artale 1b462ed174
fix(test): use NTFS junctions and platform guards for symlink tests on Windows (openclaw#28747) thanks @arosstale
Verified:
- pnpm install --frozen-lockfile
- pnpm test src/agents/apply-patch.test.ts src/agents/sandbox/fs-bridge.test.ts src/agents/sandbox/validate-sandbox-security.test.ts src/infra/archive.test.ts

Co-authored-by: arosstale <117890364+arosstale@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 10:45:19 -06:00
Peter Steinberger 18f8393b6c fix: harden sandbox writes and centralize atomic file writes 2026-03-02 16:45:12 +00:00
Peter Steinberger 14e4575af5 docs(changelog): note gateway and regex hardening 2026-03-02 16:38:03 +00:00
Peter Steinberger b1592457fa perf(security): bound regex input in filters and redaction 2026-03-02 16:37:45 +00:00
Peter Steinberger 31c7637e0f fix(security): block quantified ambiguous alternation regex 2026-03-02 16:37:45 +00:00
Peter Steinberger d5ae4b8337 fix(gateway): require local client for loopback origin fallback 2026-03-02 16:37:45 +00:00
Peter Steinberger 0dbb92dd2b fix(security): harden tar archive extraction parity 2026-03-02 16:36:56 +00:00
Peter Steinberger 17ede52a4b fix(security): harden sandbox media staging destination writes 2026-03-02 16:35:08 +00:00
Gustavo Madeira Santana be65dc8acc docs(diffs): clarify file size limitations 2026-03-02 11:34:12 -05:00
zwffff 8828418111
test(subagent-announce): fix flaky Windows-only test failure (#31298) (openclaw#31370) thanks @zwffff
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on main baseline issues in extensions/googlechat and extensions/phone-control)
- pnpm test:e2e src/agents/subagent-announce.format.e2e.test.ts

Co-authored-by: zwffff <5809959+zwffff@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 10:33:07 -06:00
Peter Steinberger 4dd6c7a509 test(perf): avoid redundant root mkdir in hooks install tests 2026-03-02 16:33:00 +00:00
bboyyan d94de5c4a1
fix(cron): normalize topic-qualified target.to in messaging tool suppress check (#29480)
* fix(cron): pass job.delivery.accountId through to delivery target resolution

* fix(cron): normalize topic-qualified target.to in messaging tool suppress check

When a cron job targets a Telegram forum topic (e.g. delivery.to =
"-1003597428309:topic:462"), delivery.to is stripped to the chatId
only by resolveOutboundTarget. However, the agent's message tool may
pass the full topic-qualified address as its target, causing
matchesMessagingToolDeliveryTarget to fail the equality check and not
suppress the tool send.

Strip the :topic:NNN suffix from target.to before comparing so the
suppress check works correctly for topic-bound cron deliveries.
Without this, the agent's message tool fires separately using the
announce session's accountId (often "default"), hitting 403 when
default bot is not in the multi-account target group.

* fix(cron): remove duplicate accountId keys after rebase

---------

Co-authored-by: jaxpkm <jaxpkm@jaxpkmdeMac-mini.local>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 10:32:06 -06:00
Glucksberg 09f49cd921
fix(cron): accept delivery mode "none" for sessionTarget="main" (#27431) (#28871) 2026-03-02 10:32:00 -06:00
Ayaan Zaidi 87d05592ea docs(changelog): add telegram dm streaming note (#31824) 2026-03-02 21:59:19 +05:30
Peter Steinberger d74bc257d8 fix(line): mark webhook route as plugin-authenticated 2026-03-02 16:27:47 +00:00
Ayaan Zaidi 6edb512efa
feat(telegram): use sendMessageDraft for private chat streaming (#31824)
* feat(telegram): use sendMessageDraft for private stream previews

* test(telegram): cover DM draft id rotation race

* fix(telegram): keep DM reasoning updates in draft preview

* fix(telegram): split DM reasoning preview transport

* fix(telegram): harden DM draft preview fallback paths

* style(telegram): normalize draft preview formatting
2026-03-02 21:56:59 +05:30
Peter Steinberger c973b053a5 refactor(net): unify proxy env checks and guarded fetch modes 2026-03-02 16:24:26 +00:00
Peter Steinberger a229ae6c3e chore(lint): add registerHttpHandler usage guard script 2026-03-02 16:24:06 +00:00
Peter Steinberger 2fd8264ab0 refactor(gateway): hard-break plugin wildcard http handlers 2026-03-02 16:24:06 +00:00
Peter Steinberger b13d48987c refactor(gateway): unify control-ui and plugin webhook routing 2026-03-02 16:18:12 +00:00
Tak Hoffman 21708f58ce
fix(exec): resolve PATH key case-insensitively for Windows pathPrepend (#25399) (#31879)
Co-authored-by: Glucksberg <markuscontasul@gmail.com>
2026-03-02 10:14:38 -06:00
Tak Hoffman 1ea42ebe98
fix(tsgo): unblock baseline type errors (#31873) 2026-03-02 10:09:49 -06:00
Peter Steinberger 3e5762c288 fix(security): harden sms.send dangerous-node defaults 2026-03-02 16:06:52 +00:00
SidQin-cyber c4711a9b69 fix(gateway): let POST requests pass through root-mounted Control UI to plugin handlers
The Control UI handler checked HTTP method before path routing, causing
all POST requests (including plugin webhook endpoints like /bluebubbles-webhook)
to receive 405 Method Not Allowed.  Move the method check after path-based
exclusions so non-GET/HEAD requests reach plugin HTTP handlers.

Closes #31344

Made-with: Cursor
2026-03-02 16:06:48 +00:00
Peter Steinberger ea204e65a0 fix(browser): fail closed navigation guard with env proxy 2026-03-02 16:06:31 +00:00
Peter Steinberger 14fbd0e6b6 test(perf): reduce timer teardown overhead in cron issue regressions 2026-03-02 16:06:04 +00:00
Peter Steinberger 17c434f2f3 refactor: split browser context/actions and unify CDP timeout policy 2026-03-02 16:02:39 +00:00
Peter Steinberger 19f5d1345c test(perf): cache redact hints and tune guardrail scan concurrency 2026-03-02 16:01:41 +00:00
Peter Steinberger 64c443ac65 docs(changelog): credit sessions_spawn agentId validation fix (#31381) 2026-03-02 15:59:45 +00:00
Peter Steinberger b28e472fa5 fix(agents): validate sessions_spawn agentId format (#31381) 2026-03-02 15:59:45 +00:00
root 0c6db05cc0 fix(agents): add strict format validation to sessions_spawn for agentId
Implements a strict format validation for the agentId parameter in
sessions_spawn to fully resolve the ghost workspace creation bug reported
in #31311.

This fix introduces a regex format gate at the entry point to
immediately reject malformed agentId strings. This prevents error
messages (e.g., 'Agent not found: xyz') or path traversals from being
mangled by normalizeAgentId into seemingly valid IDs (e.g.,
'agent-not-found--xyz'), which was the root cause of the bug.

The validation is placed before normalization and does not interfere
with existing workflows, including delegating to agents that are
allowlisted but not globally configured.

New, non-redundant tests are added to
sessions-spawn.allowlist.test.ts to cover format validation and
ensure no regressions in allowlist behavior.

Fixes #31311
2026-03-02 15:59:45 +00:00
Liu Yuan ade46d8ab7
fix(logging): log timestamps use local time instead of UTC (#28434)
* fix(logging): log timestamps use local time instead of UTC

Problem: Log timestamps used UTC, but docs say they should use host local timezone

* test(logging): add test for logger timestamp format

Verify logger uses local time (not UTC) in file logs

* changelog: note logger timestamp local-time fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-02 07:57:03 -08:00
Peter Steinberger 82247f09a7 test(perf): remove redundant module reset in system presence version tests 2026-03-02 15:56:30 +00:00
Peter Steinberger d01e82d54a test(perf): avoid module reload churn in config guard tests 2026-03-02 15:56:30 +00:00
Peter Steinberger 93b0724025 fix(gateway): fail closed plugin auth path canonicalization 2026-03-02 15:55:32 +00:00
Peter Steinberger 44270c533b docs(changelog): credit sandbox mkdirp boundary fix (#31547) 2026-03-02 15:55:00 +00:00
Peter Steinberger dec2c9e74d fix(sandbox): allow mkdirp boundary checks on existing directories (#31547) 2026-03-02 15:55:00 +00:00
User 6135eb3353 fix(sandbox): allow mkdirp boundary check on existing directories 2026-03-02 15:55:00 +00:00
Peter Steinberger 345abf0b20 fix: preserve dns pinning for strict web SSRF fetches 2026-03-02 15:54:46 +00:00
Peter Steinberger a3d2021eea test(cron): stabilize model precedence mocks in bun runs (#31594) 2026-03-02 15:47:21 +00:00
Peter Steinberger e08ba063d8 test(android): fix GatewaySessionInvokeTest coroutine job typing (#31594) 2026-03-02 15:47:21 +00:00
Peter Steinberger 998d477f5e test: stabilize cross-platform regression suites (#31594) 2026-03-02 15:47:21 +00:00
Peter Steinberger a49afd25ea fix(secrets): ignore stdin EPIPE from fast-exit exec resolvers 2026-03-02 15:47:21 +00:00
Peter Steinberger d86c1a67e0 fix(slack): correct typed message event overloads (#31701) 2026-03-02 15:47:21 +00:00
Peter Steinberger 05b84e718b fix(feishu): preserve explicit target routing hints (#31594) (thanks @liuxiaopai-ai) 2026-03-02 15:47:21 +00:00
liuxiaopai-ai 07b419a0e7 Feishu: honor group/dm prefixes in target parsing 2026-03-02 15:47:21 +00:00
Gustavo Madeira Santana 12be9a08fe refactor(diffs): dedupe functions 2026-03-02 10:46:45 -05:00
Peter Steinberger ee1b147631 fix(zalouser): harden inbound sender id handling 2026-03-02 15:44:07 +00:00
Peter Steinberger 208a9b1ad1 docs(zalouser): document js-native migration and breaking change 2026-03-02 15:44:07 +00:00
Peter Steinberger 0f00110f5d test(zalouser): expand native runtime regression coverage 2026-03-02 15:44:07 +00:00
Peter Steinberger 174f2de447 feat(zalouser): migrate runtime to native zca-js 2026-03-02 15:44:07 +00:00
Peter Steinberger db3d8d82c1 test(perf): avoid module reset churn in daemon lifecycle tests 2026-03-02 15:43:20 +00:00
Peter Steinberger 3f2848433a test(perf): reuse suite temp-home fixture in config io write tests 2026-03-02 15:43:20 +00:00
Peter Steinberger 663c1858b8 refactor(browser): split server context and unify CDP transport 2026-03-02 15:43:05 +00:00
Peter Steinberger 729ddfd7c8 fix: add zalo webhook account-scope regression assertions (#26121) (thanks @bmendonca3) 2026-03-02 15:38:36 +00:00
bmendonca3 f39882d57e zalo: update pairing-store read assertion to scoped params object 2026-03-02 15:38:36 +00:00
bmendonca3 6b7d3fb011 security(zalo): scope pairing store by account 2026-03-02 15:38:36 +00:00
Peter Steinberger c63c179278 chore: add changelog note for adapter sendPayload rollout (#30144) (thanks @nohat) 2026-03-02 15:35:47 +00:00
David Friedland dd3f7d57ee sendPayload: add chunking, empty-payload guard, and tests 2026-03-02 15:35:47 +00:00
David Friedland 47ef180fb7 sendPayload: explicit text fallback in text-only path 2026-03-02 15:35:47 +00:00
David Friedland ebe54e6903 fix(adapters): restructure sendPayload media loop to avoid uninitialized lastResult 2026-03-02 15:35:47 +00:00
David Friedland d06ee86292 feat(adapters): add sendPayload to batch-d adapters 2026-03-02 15:35:47 +00:00
Peter Steinberger f1cab9c5e5 fix: stabilize zalouser account-scope regression hook (#26672) (thanks @bmendonca3) 2026-03-02 15:34:17 +00:00
bmendonca3 f4c3e483fe zalouser: update account-scope test for scoped store API 2026-03-02 15:34:17 +00:00
bmendonca3 6aa20e91d9 security(zalouser): scope pairing-store auth to accountId 2026-03-02 15:34:17 +00:00
Evgeny Zislis 4b4ea5df8b
feat(cron): add failure destination support to failed cron jobs (#31059)
* feat(cron): add failure destination support with webhook mode and bestEffort handling

Extends PR #24789 failure alerts with features from PR #29145:
- Add webhook delivery mode for failure alerts (mode: 'webhook')
- Add accountId support for multi-account channel configurations
- Add bestEffort handling to skip alerts when job has bestEffort=true
- Add separate failureDestination config (global + per-job in delivery)
- Add duplicate prevention (prevents sending to same as primary delivery)
- Add CLI flags: --failure-alert-mode, --failure-alert-account-id
- Add UI fields for new options in web cron editor

* fix(cron): merge failureAlert mode/accountId and preserve failureDestination on updates

- Fix mergeCronFailureAlert to merge mode and accountId fields
- Fix mergeCronDelivery to preserve failureDestination on updates
- Fix isSameDeliveryTarget to use 'announce' as default instead of 'none'
  to properly detect duplicates when delivery.mode is undefined

* fix(cron): validate webhook mode requires URL in resolveFailureDestination

When mode is 'webhook' but no 'to' URL is provided, return null
instead of creating an invalid plan that silently fails later.

* fix(cron): fail closed on webhook mode without URL and make failureDestination fields clearable

- sendCronFailureAlert: fail closed when mode is webhook but URL is missing
- mergeCronDelivery: use per-key presence checks so callers can clear
  nested failureDestination fields via cron.update

Note: protocol:check shows missing internalEvents in Swift models - this is
a pre-existing issue unrelated to these changes (upstream sync needed).

* fix(cron): use separate schema for failureDestination and fix type cast

- Create CronFailureDestinationSchema excluding after/cooldownMs fields
- Fix type cast in sendFailureNotificationAnnounce to use CronMessageChannel

* fix(cron): merge global failureDestination with partial job overrides

When job has partial failureDestination config, fall back to global
config for unset fields instead of treating it as a full override.

* fix(cron): avoid forcing announce mode and clear inherited to on mode change

- UI: only include mode in patch if explicitly set to non-default
- delivery.ts: clear inherited 'to' when job overrides mode, since URL
  semantics differ between announce and webhook modes

* fix(cron): preserve explicit to on mode override and always include mode in UI patches

- delivery.ts: preserve job-level explicit 'to' when overriding mode
- UI: always include mode in failureAlert patch so users can switch between announce/webhook

* fix(cron): allow clearing accountId and treat undefined global mode as announce

- UI: always include accountId in patch so users can clear it
- delivery.ts: treat undefined global mode as announce when comparing for clearing inherited 'to'

* Cron: harden failure destination routing and add regression coverage

* Cron: resolve failure destination review feedback

* Cron: drop unrelated timeout assertions from conflict resolution

* Cron: format cron CLI regression test

* Cron: align gateway cron test mock types

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 09:27:41 -06:00
Peter Steinberger a905b6dabc test(perf): merge duplicate one-shot retry regression paths 2026-03-02 15:23:58 +00:00
Peter Steinberger 44c50d9a73 fix(types): tighten shared helper typing contracts 2026-03-02 15:21:19 +00:00
Peter Steinberger ed21b63bb8 refactor(plugin-sdk): share auth, routing, and stream/account helpers 2026-03-02 15:21:19 +00:00
Peter Steinberger e9dd6121f2 refactor(core): dedupe embedding imports and env parsing 2026-03-02 15:21:19 +00:00
Peter Steinberger dcf8308c8f refactor(ui): share channel config extras and hint types 2026-03-02 15:21:19 +00:00
Peter Steinberger d212721df1 test(perf): merge forum-topic direct-delivery scenarios 2026-03-02 15:17:28 +00:00
Peter Steinberger a469d00345 test(perf): reuse cron heartbeat delivery temp homes per suite 2026-03-02 15:14:17 +00:00
Peter Steinberger 3fb0ab7435 test(perf): tighten cron issue-regression timeout windows 2026-03-02 15:11:14 +00:00
Peter Steinberger 64ac790aa8 test(perf): reuse temp-home root in cron announce delivery suite 2026-03-02 15:08:35 +00:00
Felix Lu f1cd3ea531
fix(app:macos): 【 OpenClaw ⇄ clawdbot 】- Peekaboo Bridge discovery after the OpenClaw rename (#6033)
* fix(mac): keep OpenClaw bridge socket and harden legacy symlink

* fix(mac): add clawdis legacy Peekaboo bridge symlink

* macos: include moltbot in PeekabooBridge legacy socket paths

* changelog: note peekaboo legacy socket compatibility paths

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-02 07:00:30 -08:00
Peter Steinberger c5f1cf3c3b test(perf): reuse isolated-agent temp home root per suite 2026-03-02 15:00:08 +00:00
Peter Steinberger 87bd6226bd test(perf): merge overlapping preaction hook scenarios 2026-03-02 14:52:38 +00:00
Robin Waslander 9f98d2766a
fix(logs): respect TZ env var for timestamp display, fix Windows timezone (#21859) 2026-03-02 08:44:37 -06:00
StingNing 944abe0a6c
fix(security): recognize localized Windows SYSTEM account in ACL audit (#29698)
* fix(security): recognize localized Windows SYSTEM account in ACL audit

On non-English Windows (e.g. French "AUTORITE NT\Système"), the security
audit falsely reports fs.config.perms_writable because the localized
SYSTEM account name is not recognized as trusted.

Changes:
- Add common localized SYSTEM principal names (French, German, Spanish,
  Portuguese) to TRUSTED_BASE
- Add diacritics-stripping fallback in classifyPrincipal for unhandled
  locales
- Use well-known SID *S-1-5-18 in icacls reset commands instead of
  hardcoded "SYSTEM" string for locale independence

Fixes #29681

* style: format windows acl files

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 08:38:56 -06:00
Peter Steinberger dbc78243f4 refactor(scripts): share guard runners and paged select UI 2026-03-02 14:36:41 +00:00
Peter Steinberger e41f9998f7 refactor(test): extract shared gateway hook and vitest scoped config helpers 2026-03-02 14:36:41 +00:00
Peter Steinberger 741e74972b refactor(plugin-sdk): share boolean action param parsing 2026-03-02 14:36:41 +00:00
Peter Steinberger 693f61404d refactor(shared): centralize assistant identity and usage timeseries types 2026-03-02 14:36:41 +00:00
Peter Steinberger 3efd224ec6 refactor(commands): dedupe session target resolution and fs tool test setup 2026-03-02 14:36:41 +00:00
Peter Steinberger b85facfb5d refactor(android): share node JSON param parsing helpers 2026-03-02 14:36:41 +00:00
Ajay Elika e23b6fb2ba
fix(gateway): add Windows-compatible port detection using netstat fallback (openclaw#29239) thanks @ajay99511
Verified:
- pnpm vitest src/cli/program.force.test.ts
- pnpm check
- pnpm build

Co-authored-by: ajay99511 <73169130+ajay99511@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 08:33:59 -06:00
tda d145518f94
fix(cli): wait for process exit before restarting gateway on Windows (openclaw#27913) thanks @tda1017
Verified:
- pnpm vitest src/cli/update-cli/restart-helper.test.ts
- pnpm check
- pnpm build

Co-authored-by: tda1017 <95275462+tda1017@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 08:31:03 -06:00
Peter Steinberger cd18472405 test(perf): trim redundant cron regression setup coverage 2026-03-02 14:25:49 +00:00
Tak Hoffman 2a11a20fe2
test(windows): stabilize exec wrapper mock assertions (#31771) 2026-03-02 08:24:49 -06:00
Peter Steinberger 2a2a9902d9 test(perf): merge isolated-agent model precedence cases 2026-03-02 14:24:32 +00:00
Peter Steinberger 5561a6b659 test(perf): dedupe isolated-agent delivery announce cases 2026-03-02 14:24:32 +00:00
Peter Machona c2d41dc473
fix(daemon): recover Windows restarts from unknown stale listeners (openclaw#24734) thanks @chilu18
Verified:
- pnpm vitest src/cli/daemon-cli/restart-health.test.ts src/cli/gateway-cli.coverage.test.ts
- pnpm oxfmt --check src/cli/daemon-cli/restart-health.ts src/cli/daemon-cli/restart-health.test.ts
- pnpm check (fails on unrelated repo baseline tsgo errors in extensions/* and src/process/exec.windows.test.ts)

Co-authored-by: chilu18 <7957943+chilu18@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 08:24:25 -06:00
Peter Steinberger a05b8f47b1 test(perf): tighten cron regression timeout windows 2026-03-02 14:20:31 +00:00
Peter Steinberger 7d600ff4e2 test(perf): dedupe plugin validation scenarios 2026-03-02 14:20:21 +00:00
Peter Steinberger 38bdb0d271 test(perf): prune redundant preaction command-path cases 2026-03-02 14:14:02 +00:00
Peter Steinberger 32475448eb test(perf): trim ios team-id fixture setup 2026-03-02 14:12:26 +00:00
Fologan 8421b2e848
fix(gateway): avoid stale running status from Windows Scheduled Task (openclaw#19504) thanks @Fologan
Verified:
- pnpm vitest src/daemon/schtasks.test.ts
- pnpm check
- pnpm build

Co-authored-by: Fologan <164580328+Fologan@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 08:12:24 -06:00
Peter Steinberger f2468feb86 test(perf): use shell resolver fixture in secrets audit 2026-03-02 14:10:53 +00:00
Tak Hoffman 1fe0f848df
fix(slack): type message.channels/group handlers (#31758) 2026-03-02 08:09:49 -06:00
Peter Steinberger 98e5851d8a test(perf): collapse overlapping preaction scenarios 2026-03-02 14:07:06 +00:00
Tak Hoffman cd653c55d7
windows: unify non-core spawn handling across acp qmd and docker (openclaw#31750) thanks @Takhoffman
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on pre-existing unrelated src/slack/monitor/events/messages.ts typing errors)
- pnpm vitest run src/acp/client.test.ts src/memory/qmd-manager.test.ts src/agents/sandbox/docker.execDockerRaw.enoent.test.ts src/agents/sandbox/docker.windows.test.ts extensions/acpx/src/runtime-internals/process.test.ts

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 08:05:39 -06:00
Peter Steinberger 32c7242974 test(perf): simplify ios team-id fixtures 2026-03-02 14:05:08 +00:00
Peter Steinberger 534f436d4e test(perf): reduce repeated cli program setup overhead 2026-03-02 14:02:47 +00:00
Peter Steinberger 234e07fcc0 refactor(process): extract command env resolution helper 2026-03-02 14:02:47 +00:00
Peter Steinberger 9eb70d2725 fix: add proxy-bypass regression + changelog (#31469) (thanks @widingmarcus-cyber) 2026-03-02 13:56:30 +00:00
Marcus Widing 2bec80cd97 fix: preserve user-configured NO_PROXY when loopback already covered
Only restore env vars when we actually modified them (noProxyDidModify
flag). Prevents silently deleting a user's NO_PROXY that already
contains loopback entries. Added regression test.
2026-03-02 13:56:30 +00:00
Marcus Widing dd8c76110f fix: remove isFirst guard from NO_PROXY restore, add reverse-exit test
Fix Greptile review: when call A exits before call B, the isFirst flag
on B is false, so the restore condition (refCount===0 && isFirst) was
never true and NO_PROXY leaked permanently.

Remove '&& isFirst' so any last exiter (refCount===0) restores the
original env vars. Added explicit reverse-exit-order regression test.
2026-03-02 13:56:30 +00:00
Marcus Widing 158709ff62 fix: make withNoProxyForLocalhost reentrant-safe, fix [::1] check
Address Greptile review feedback:
- Replace snapshot/restore pattern with reference counter to prevent
  permanent NO_PROXY env-var leak under concurrent async calls
- Include [::1] in the alreadyCoversLocalhost guard
- Add concurrency regression test
2026-03-02 13:56:30 +00:00
Marcus Widing c96234b51d fix: bypass proxy for CDP localhost connections (#31219)
When HTTP_PROXY / HTTPS_PROXY / ALL_PROXY environment variables are set,
CDP connections to localhost/127.0.0.1 can be incorrectly routed through
the proxy (e.g. via global-agent or undici proxy dispatcher), causing
browser control to fail.

Fix:
- New cdp-proxy-bypass module with utilities for direct localhost connections
- WebSocket (ws) CDP connections: pass explicit http.Agent to bypass any
  global proxy agent patching
- fetch-based CDP probes: wrap in withNoProxyForLocalhost() to temporarily
  set NO_PROXY for the duration of the call
- Playwright connectOverCDP: wrap in withNoProxyForLocalhost() since
  Playwright reads env vars internally
- 13 new tests covering getDirectAgentForCdp, hasProxyEnv, and
  withNoProxyForLocalhost (env save/restore, error recovery)
2026-03-02 13:56:30 +00:00
Peter Steinberger 1184d39e1d fix: extend managed-tab cap regressions + changelog (#29724) (thanks @pandego) 2026-03-02 13:55:09 +00:00
pandego e303b356ba fix(browser): detach tab-cap listing from openTab return path 2026-03-02 13:55:09 +00:00
pandego 22ec577d80 fix(browser): require managed runtime ownership for tab cap cleanup 2026-03-02 13:55:09 +00:00
pandego 9b938f2bf6 fix(browser): skip tab cap cleanup for attach-only profiles 2026-03-02 13:55:09 +00:00
pandego c7bf54b914 fix(browser): scope tab cap to local profile and detach cleanup closes 2026-03-02 13:55:09 +00:00
pandego c350dc8a7b fix(browser): keep tab-cap cleanup best-effort on list errors 2026-03-02 13:55:09 +00:00
pandego b47dc73b70 fix(browser): cap managed profile tabs to prevent renderer buildup 2026-03-02 13:55:09 +00:00
Keenan 050e928985
[codex] Fix main-session web UI reply routing to Telegram (openclaw#29328) thanks @BeeSting50
Verified:
- pnpm test src/auto-reply/reply/dispatch-from-config.test.ts src/gateway/server-methods/chat.directive-tags.test.ts
- pnpm exec oxfmt --check src/auto-reply/reply/dispatch-from-config.test.ts src/gateway/server-methods/chat.directive-tags.test.ts src/auto-reply/reply/dispatch-from-config.ts src/gateway/server-methods/chat.ts CHANGELOG.md
- CI note: non-required check "check" failed on unrelated src/slack/monitor/events/messages.ts TS errors outside this PR scope.

Co-authored-by: BeeSting50 <85285887+BeeSting50@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:54:16 -06:00
Peter Steinberger 99ee26d534 fix: add timeout cleanup regression for browser CDP readiness (#29538) (thanks @AaronWander) 2026-03-02 13:53:21 +00:00
AaronWander 8bccb0032a fix(browser): bound post-launch CDP wait by elapsed time (#21149) 2026-03-02 13:53:21 +00:00
AaronWander d06cc77f38 fix(browser): wait for CDP readiness after start (#21149) 2026-03-02 13:53:21 +00:00
Peter Steinberger 0d620a56e2 test(refactor): reuse shared program setup in preaction tests 2026-03-02 13:53:10 +00:00
Peter Steinberger 09748ab109 test(perf): speed up supervisor and exec process tests 2026-03-02 13:53:10 +00:00
Peter Steinberger 2d8b8a17ab test(android): dedupe node and gateway invoke tests 2026-03-02 13:52:36 +00:00
Rain120 6ea6aca5bd fix(ui): the header has been hidden by content in the config page 2026-03-02 07:52:26 -06:00
Sid 7b5a410b83
fix(node-host): decode Windows exec output with active code page (openclaw#30652) thanks @Sid-Qin
Verified:
- pnpm vitest run src/node-host/invoke.sanitize-env.test.ts src/node-host/invoke-system-run.test.ts

Co-authored-by: Sid-Qin <53659198+Sid-Qin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:50:17 -06:00
icesword0760 6e008e93be
Process: fix Windows .cmd spawn EINVAL (openclaw#29759) thanks @icesword0760
Verified:
- pnpm vitest run src/process/exec.test.ts src/process/exec.windows.test.ts

Co-authored-by: icesword0760 <123886211+icesword0760@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:49:54 -06:00
SidQin-cyber 732c4f3921 fix(browser): retry chrome act when target tab is stale
When a Chrome relay targetId becomes stale between snapshot and action,
the browser tool now retries once without targetId so the relay falls
back to the currently attached tab.

Drop the unknown recovered field from the test mock return value
to satisfy tsc strict checking against BrowserActResponse.
2026-03-02 13:49:33 +00:00
leotwang 910c654807 test(config): add schema regression tests for browser.extraArgs 2026-03-02 13:47:59 +00:00
leotwang 925117d277 config: add extraArgs to browser zod schema 2026-03-02 13:47:59 +00:00
Yasunori Morishima(盛島康徳) be8930d6f9
fix: clear stale runningAtMs in cron.run() before already-running check (#17949)
Add recomputeNextRunsForMaintenance() call in run() so that stale
runningAtMs markers (from a crashed Phase-1 persist) are cleared by the
existing normalizeJobTickState logic before the already-running guard.

Without this, a manual cron.run() could be blocked for up to
STUCK_RUN_MS (2 hours) even though no job was actually running.

Fixes #17554

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:47:36 -06:00
Peter Steinberger 60b8d645de test(perf): standardize loader fixtures to cjs 2026-03-02 13:43:55 +00:00
Mark L 097ad88f9d
fix(feishu): tolerate missing webhook defaults in older plugin-sdk (openclaw#31639) thanks @liuxiaopai-ai
Verified:
- pnpm test extensions/feishu/src/monitor.state.defaults.test.ts
- pnpm exec vitest run extensions/feishu/src/monitor.state.defaults.test.ts
- pnpm exec oxfmt --check extensions/feishu/src/monitor.state.ts extensions/feishu/src/monitor.state.defaults.test.ts CHANGELOG.md
- CI note: non-required check "check" failed on unrelated  TS errors outside this PR scope.

Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:42:16 -06:00
Kate Chapman 6df8bd9741
fix(cron): wrap computeJobNextRunAtMs in try-catch inside applyJobResult (#30905)
* fix(cron): wrap computeJobNextRunAtMs in try-catch inside applyJobResult

Without this guard, if the croner library throws during schedule
computation (timezone/expression edge cases), the exception propagates
out of applyJobResult and the entire state update is lost — runningAtMs
never clears, lastRunAtMs never advances, nextRunAtMs never recomputes.
After STUCK_RUN_MS (2h), stuck detection clears runningAtMs and the job
re-fires, creating a ~2h repeat cycle instead of the intended schedule.

The sibling function recomputeJobNextRunAtMs in jobs.ts already wraps
computeJobNextRunAtMs in try-catch; this was an oversight in the
applyJobResult call sites.

Changes:
- Error-backoff path: catch and fall back to backoff-only schedule
- Success path: catch and fall through to the MIN_REFIRE_GAP_MS safety net
- applyOutcomeToStoredJob: log a warning when job not found after forceReload

* fix(cron): use recordScheduleComputeError in applyJobResult catch blocks

Address review feedback: the original catch blocks only logged a warning,
which meant a persistent computeJobNextRunAtMs throw would cause a
MIN_REFIRE_GAP_MS (2s) hot loop on cron-kind jobs.

Now both catch blocks call recordScheduleComputeError (exported from
jobs.ts), which tracks consecutive schedule errors and auto-disables the
job after 3 failures — matching the existing behavior in
recomputeJobNextRunAtMs.

* test(cron): cover applyJobResult schedule-throw fallback paths

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:38:33 -06:00
Andrey 21e19e42a3
fix(cron): skip isError payloads when picking summary/delivery content (#21454)
* fix(cron): skip isError payloads when picking summary/delivery content

buildEmbeddedRunPayloads appends isError warnings as the last payload.
Three functions in helpers.ts iterate last-to-first and pick the error
over real agent output. Use two-pass selection: prefer non-error payloads,
fall back to error-only when no real content exists.

Fixes: pickSummaryFromPayloads, pickLastNonEmptyTextFromPayloads,
pickLastDeliverablePayload — all now accept and filter isError.

* Changelog: note cron payload isError filtering (#21454)

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:38:05 -06:00
Peter Steinberger 2c192a3795 test(perf): reduce cron overlap timer advance slack 2026-03-02 13:37:26 +00:00
Peter Steinberger 02bd7a2249 test(perf): use CJS fixtures in plugin loader tests 2026-03-02 13:36:17 +00:00
Jared Grimes aa5d173bec
fix(feishu): prevent duplicate delivery when message tool uses generic provider (openclaw#31538) thanks @jlgrimes
Verified:
- pnpm exec vitest run src/auto-reply/reply/agent-runner-payloads.test.ts src/auto-reply/reply/followup-runner.test.ts
- pnpm check (fails on unrelated baseline type errors outside PR scope)

Co-authored-by: jlgrimes <8084595+jlgrimes@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:35:58 -06:00
liuxiaopai-ai 06306501ab Slack: register typed channel/group message event handlers 2026-03-02 13:32:54 +00:00
Yuzuru Suzuki 6513c42d2d
fix(cron): treat announce delivery failure as ok when execution succeeded (#31082)
* cron: treat announce delivery failure as ok when agent execution succeeded

* fix: set delivered:false and error on announce delivery failure paths

* Changelog: note cron announce delivery status handling (#31082)

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:27:57 -06:00
Peter Steinberger 16e85360a1 perf(cli): cache preaction lazy module imports 2026-03-02 13:26:54 +00:00
Peter Steinberger 4d31c29a19 test(perf): skip shell profile loading in ios team-id script tests 2026-03-02 13:25:49 +00:00
Peter Steinberger 79cb5e2c9b test(perf): trim cron regression timeout windows 2026-03-02 13:25:49 +00:00
kleebaker b40d5817a2
fix(cron): avoid 30s timeout for cron run --expect-final (#29942)
* fix(cron): use longer default timeout for cron run --expect-final

* test(cron-cli): stabilize cron run timeout assertions with explicit run exits

---------

Co-authored-by: Kelly Baker <kelly@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-02 07:24:42 -06:00
Tak Hoffman 254bb7ceee
ui(cron): add advanced controls for run-if-due and routing (#31244)
* ui(cron): add advanced run controls and routing fields

* ui(cron): gate delivery account id to announce mode

* ui(cron): allow clearing delivery account id in editor

* cron: persist payload lightContext updates

* tests(cron): fix payload lightContext assertion typing
2026-03-02 07:24:33 -06:00
cygaar 127217612c
fix(CI/CD): use path.resolve in expandHomePrefix test for Windows compat (#30961)
Merged via squash.

Prepared head SHA: 26bc118517
Co-authored-by: cygaar <97691933+cygaar@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-02 14:18:11 +01:00
Peter Steinberger 0b762e9a02 fix(android): import remember for pending tools bubble 2026-03-02 13:11:08 +00:00
Peter Steinberger cb9bce902e fix(infra): accept cross-realm promises in boundary traversal 2026-03-02 13:00:21 +00:00
Peter Steinberger 848ade07da test(cli): fix gateway coverage mock signature 2026-03-02 13:00:21 +00:00
Peter Steinberger a9d572394e test(perf): tighten exec timeout slack in non-flaky cases 2026-03-02 12:58:00 +00:00
Peter Steinberger b02b94673f refactor: dedupe runtime and helper flows 2026-03-02 12:55:47 +00:00
Peter Steinberger 5d3f066bbd test(perf): reduce boundary-path fuzz setup churn 2026-03-02 12:54:59 +00:00
Peter Steinberger 6adc93cc92 test(perf): skip scheduler startup in cron delivery-plan tests 2026-03-02 12:54:53 +00:00
Peter Steinberger e99928f3f1 test(perf): use git ls-files fast path for guardrail source scan 2026-03-02 12:42:02 +00:00
Peter Steinberger afda085b39 test(perf): disable scheduler startup in manual-only cron regressions 2026-03-02 12:41:56 +00:00
Peter Steinberger 3980c315d1 test(perf): avoid real node startup in pre-commit hook integration 2026-03-02 12:41:51 +00:00
Peter Steinberger 7b38e8231e test(perf): stub expensive cli coverage integration paths 2026-03-02 12:41:45 +00:00
Peter Steinberger f94d6fb1f1 test(perf): stub pre-commit helpers in hook integration test 2026-03-02 12:27:37 +00:00
Peter Steinberger 5fed91e624 test(perf): avoid real python startup in ios team-id integration case 2026-03-02 12:26:38 +00:00
Peter Steinberger ba3957ad77 test(perf): bypass daemon install token-generation path in coverage test 2026-03-02 12:24:03 +00:00
Peter Steinberger 916b0e6609 test(perf): tighten cron regression timeout constants 2026-03-02 12:21:35 +00:00
Peter Steinberger 099b11fc7d test(perf): align media auto-detect no-key mock with scenario 2026-03-02 12:20:51 +00:00
Peter Steinberger f7b8e4be27 test(fix): stabilize exec no-output heartbeat timing case 2026-03-02 12:18:27 +00:00
Peter Steinberger 2cda78a0b0 test(perf): stub docker probes in filesystem audit cases 2026-03-02 12:18:27 +00:00
Peter Steinberger 87316e07d8 refactor(macos): share pairing and ui dedupe utilities 2026-03-02 12:13:45 +00:00
Peter Steinberger d85d3c88d5 refactor(agents): centralize tool display definitions 2026-03-02 12:13:45 +00:00
Peter Steinberger d977af5853 refactor(diffs): share artifact detail and screenshot test helpers 2026-03-02 12:13:45 +00:00
Peter Steinberger 7533015532 refactor(android): extract shared dedupe helpers for node and chat 2026-03-02 12:13:45 +00:00
Peter Steinberger f01862bce2 test(perf): clear concurrent-start timeout handle in cron regression test 2026-03-02 12:07:38 +00:00
Peter Steinberger 8e0ca219a4 test(perf): precreate plugin config validation fixtures 2026-03-02 12:06:48 +00:00
Peter Steinberger bdfd3bae6f test(perf): reuse cli programs in coverage tests 2026-03-02 12:00:28 +00:00
Peter Steinberger adf2ef88c6 test(perf): simplify temp-path guard scan loop 2026-03-02 11:59:24 +00:00
Peter Steinberger d95bc10425 test(perf): streamline deep code-safety audit assertions 2026-03-02 11:58:49 +00:00
Peter Steinberger d9ff3bf1af test(perf): tighten process exec and supervisor timing fixtures 2026-03-02 11:56:57 +00:00
Peter Steinberger 2b855704da test(perf): remove redundant ios team-id script invocation 2026-03-02 11:55:35 +00:00
Peter Steinberger c80a332def test(perf): cut cron retry waits and tighten tmp guard prefilter 2026-03-02 11:54:26 +00:00
Peter Steinberger d9a8d3853d test(perf): trim qmd manager fixture setup overhead 2026-03-02 11:54:21 +00:00
Peter Steinberger 94e480f64a test(refactor): dedupe preaction command coverage 2026-03-02 11:41:40 +00:00
Peter Steinberger 735216f7e4 test(perf): reduce security audit and guardrail overhead 2026-03-02 11:41:33 +00:00
Peter Steinberger 316875582a test(perf): speed up pre-commit integration setup 2026-03-02 11:36:16 +00:00
Peter Steinberger 43bffe7bdc test(perf): cache plugin fixtures and streamline shell tests 2026-03-02 11:35:13 +00:00
Peter Steinberger cf67e374c0 refactor(macos): dedupe UI, pairing, and runtime helpers 2026-03-02 11:32:20 +00:00
Peter Steinberger cd011897d0 refactor(ios): dedupe status, gateway, and service flows 2026-03-02 11:32:20 +00:00
Peter Steinberger 2ca5722221 refactor(shared): dedupe common OpenClawKit helpers 2026-03-02 11:32:20 +00:00
Peter Steinberger 3dd01c3361 test(perf): reuse shared temp root in plugin install tests 2026-03-02 11:27:04 +00:00
Peter Steinberger 79b649a25e test: fix signal-listener typing in exec bridge test 2026-03-02 11:22:26 +00:00
Peter Steinberger 0c2d85529a test(refactor): dedupe cli and ios script scenarios 2026-03-02 11:16:33 +00:00
Peter Steinberger 1b98879295 test(perf): reduce guardrail and media test overhead 2026-03-02 11:16:29 +00:00
Peter Steinberger bff785aecc test(perf): tighten process test timeouts and fs setup 2026-03-02 11:16:24 +00:00
Peter Steinberger 4dcb16d696 ci: fix install smoke docker helper path 2026-03-02 11:01:56 +00:00
Peter Steinberger 96ef6ea3cf test(perf): dedupe setup in cli/security script suites 2026-03-02 10:53:21 +00:00
Peter Steinberger 4a8ada662e test(perf): cache media fixtures and trim timeout waits 2026-03-02 10:52:58 +00:00
Peter Steinberger 8a1465c314 test(perf): trim timer-heavy suites and guardrail scanning 2026-03-02 10:28:39 +00:00
Peter Steinberger f5a265a51a test(sessions): normalize cross-agent path assertions 2026-03-02 10:08:52 +00:00
Peter Steinberger 033c731f19 fix(ci): annotate feishu hoisted mock type 2026-03-02 09:59:16 +00:00
Peter Steinberger c1a46301b6 fix(ci): align strict nullable typing across channels and ui 2026-03-02 09:56:14 +00:00
Peter Steinberger fc692d82fd refactor(tests): dedupe macos ipc smoke setup blocks 2026-03-02 09:55:46 +00:00
Peter Steinberger 8553d22428 refactor(tests): dedupe ios gateway and deeplink fixtures 2026-03-02 09:55:46 +00:00
Peter Steinberger 7d44b753ff refactor(tests): dedupe openclawkit chat test helpers 2026-03-02 09:55:46 +00:00
Peter Steinberger 04030ddf68 test(runtime): trim timer-heavy regression suites 2026-03-02 09:47:29 +00:00
Peter Steinberger fd4d157e45 test(config): reuse fixtures for faster validation 2026-03-02 09:47:29 +00:00
Peter Steinberger fcb956a0a2 test(cli): reduce update/program suite overhead 2026-03-02 09:46:27 +00:00
Peter Steinberger 500883775b refactor(tests): dedupe ios defaults and setup-code helpers 2026-03-02 09:39:45 +00:00
Peter Steinberger fd7774a79e refactor(tests): dedupe swift gateway and chat fixtures 2026-03-02 09:39:45 +00:00
Gustavo Madeira Santana 5f49a5da3c
Diffs: extend image quality configs and add PDF as a format option (#31342)
Merged via squash.

Prepared head SHA: cc12097851
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-02 04:38:50 -05:00
Peter Steinberger 756f9c9fef refactor(scripts): dedupe installer CLI verification 2026-03-02 08:59:33 +00:00
Peter Steinberger ad8d766f65 refactor(extensions): dedupe channel config, onboarding, and monitors 2026-03-02 08:54:20 +00:00
Peter Steinberger d358b3ac88 refactor(core): extract shared usage, auth, and display helpers 2026-03-02 08:54:20 +00:00
Peter Steinberger e427826fcf refactor(ui): dedupe state, views, and usage helpers 2026-03-02 08:54:20 +00:00
Peter Steinberger 00a2456b72 refactor(scripts): dedupe guard checks and smoke helpers 2026-03-02 08:54:20 +00:00
Vincent Koc 5d53b61d9e
fix(browser): honor profile attachOnly for loopback CDP (#31429)
* config(browser): allow profile attachOnly field

* config(schema): accept profile attachOnly

* browser(config): resolve per-profile attachOnly

* browser(runtime): honor profile attachOnly checks

* browser(routes): expose profile attachOnly in status

* config(labels): add browser profile attachOnly label

* config(help): document browser profile attachOnly

* test(config): cover profile attachOnly resolution

* test(browser): cover profile attachOnly runtime path

* test(config): include profile attachOnly help target

* changelog: note profile attachOnly override

* browser(runtime): prioritize attachOnly over loopback ownership error

* test(browser): cover attachOnly ws-failure ownership path
2026-03-02 00:49:57 -08:00
Vincent Koc 29c3ce9454
[AI-assisted] test: fix typing and test fixture issues (#31444)
* test: fix typing and test fixture issues

* Fix type-test harness issues from session routing and mock typing

* Add routing regression test for session.mainKey precedence
2026-03-02 00:41:21 -08:00
Gustavo Madeira Santana 1443bb9a84 chore(tsgo/lint): fix CI errors 2026-03-02 03:03:11 -05:00
Vincent Koc 22be0c5801
fix(browser): support configurable CDP auto-port range start (#31352)
* config(browser): add cdpPortRangeStart type

* config(schema): validate browser.cdpPortRangeStart

* config(labels): add browser.cdpPortRangeStart label

* config(help): document browser.cdpPortRangeStart

* browser(config): resolve custom cdp port range start

* browser(profiles): allocate ports from resolved CDP range

* test(browser): cover cdpPortRangeStart config behavior

* test(browser): cover cdpPortRangeStart profile allocation

* test(browser): include CDP range fields in remote tab harness

* test(browser): include CDP range fields in ensure-tab harness

* test(browser): include CDP range fields in bridge auth config

* build(browser): add resolved CDP range metadata

* fix(browser): fallback CDP port allocation to derived range

* test(browser): cover missing resolved CDP range fallback

* fix(browser): remove duplicate resolved CDP range fields

* fix(agents): provide resolved CDP range in sandbox browser config

* chore(browser): format sandbox bridge resolved config

* chore(browser): reformat sandbox imports to satisfy oxfmt
2026-03-01 23:50:50 -08:00
Vincent Koc c6e5026edf Docs: sort provider lists A-Z 2026-03-01 23:42:55 -08:00
Vincent Koc 7e8118a93e Docs: sort built-in tools links A-Z 2026-03-01 23:41:39 -08:00
Vincent Koc c977ac8d26 Docs: sort supported channels A-Z 2026-03-01 23:40:51 -08:00
Vincent Koc ee22a01ec9 Docs: remove dead concepts/sessions alias 2026-03-01 23:40:09 -08:00
Vincent Koc abe0edaba7 Docs: sort channels list by name across locales 2026-03-01 23:38:55 -08:00
Vincent Koc a969df4c00 Docs: remove quickstart from first steps nav 2026-03-01 23:36:38 -08:00
Vincent Koc fbc1585b3f
fix(pairing): handle missing accountId in allowFrom reads (#31369)
* pairing: honor default account in allowFrom read when accountId omitted

* changelog: credit pairing allowFrom fallback fix
2026-03-01 23:24:33 -08:00
Vincent Koc e055afd000
fix(browser): accept legacy flattened act params (#31359)
* fix(browser-tool): accept flattened act params

* schema(browser-tool): add flattened act fields

* test(browser-tool): cover flattened act compatibility

* changelog: note browser act compatibility fix

* fix(schema): align browser act request fields
2026-03-01 23:21:07 -08:00
Vincent Koc 5b55c23948
fix(browser): evict stale extension relay targets from cache (#31362)
* fix(browser): prune stale extension relay targets

* test(browser): cover relay stale target pruning

* changelog: note extension relay stale target fix
2026-03-01 23:18:49 -08:00
Vincent Koc db28dda120
fix(cli): let browser start honor --timeout (#31365)
* fix(cli): respect browser start timeout option

* test(cli): cover browser start timeout propagation

* changelog: note browser start timeout propagation fix
2026-03-01 23:16:23 -08:00
Vincent Koc f4785c1a7b
Docs: expand sandbox guide for common image and Docker bootstrap 2026-03-01 23:16:00 -08:00
Peter Steinberger c00d5837d3 style(agents): format pdf tool test after rebase 2026-03-02 07:13:11 +00:00
Peter Steinberger 45d77cac16 test(agents): dedupe remaining tool and lock test scaffolds 2026-03-02 07:13:11 +00:00
Peter Steinberger c3948800f4 refactor(agents): extract shared tool model helpers 2026-03-02 07:13:11 +00:00
Peter Steinberger 45888276a3 test(integration): dedupe messaging, secrets, and plugin test suites 2026-03-02 07:13:11 +00:00
Peter Steinberger d3e0c0b29c test(gateway): dedupe gateway and infra test scaffolds 2026-03-02 07:13:10 +00:00
Peter Steinberger cded1b960a test(commands): dedupe command and onboarding test cases 2026-03-02 07:13:10 +00:00
Peter Steinberger 7e29d604ba test(agents): dedupe agent and cron test scaffolds 2026-03-02 07:13:10 +00:00
Veast 281494ae52
fix(browser): include Chrome stderr and sandbox hint in CDP startup error (#29355)
* fix(browser): include Chrome stderr and sandbox hint in CDP startup error (#29312)

When Chrome fails to start and CDP times out, the error message previously
contained no diagnostic information, making it impossible to determine why
Chrome couldn't start (e.g. missing --no-sandbox in containers, GPU issues,
shared memory errors).

This change:
- Collects Chrome's stderr output and includes up to 2000 chars in the error
- On Linux, if noSandbox is not set, appends a hint to try browser.noSandbox: true

Closes #29312

* chore(browser): format chrome startup diagnostics

* fix(browser): detach stderr listener after Chrome starts to prevent memory leak

Named the anonymous listener so it can be removed via proc.stderr.off()
once CDP is confirmed reachable. Also clears the stderrChunks array on
success so the buffered data is eligible for GC.

Fixes the unbounded memory growth reported in code review: a long-lived
Chrome process emitting periodic warnings would keep appending to
stderrChunks indefinitely since the listener was never removed.

Addresses review comment from chatgpt-codex-connector on PR #29355.

* changelog: note cdp startup diagnostics improvement

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: 派尼尔 <painier@openclaw.ai>
2026-03-01 23:08:52 -08:00
jamtujest cb491dfde5
feat(docker): add opt-in sandbox support for Docker deployments (#29974)
* feat(docker): add opt-in sandbox support for Docker deployments

Enable Docker-based sandbox isolation via OPENCLAW_SANDBOX=1 env var
in docker-setup.sh. This is a prerequisite for agents.defaults.sandbox
to function in any Docker deployment (self-hosted, Hostinger, DigitalOcean).

Changes:
- Dockerfile: add OPENCLAW_INSTALL_DOCKER_CLI build arg (~50MB, opt-in)
- docker-compose.yml: add commented-out docker.sock mount with docs
- docker-setup.sh: auto-detect Docker socket, inject mount, detect GID,
  build sandbox image, configure sandbox defaults, add group_add

All changes are opt-in. Zero impact on existing deployments.

Usage: OPENCLAW_SANDBOX=1 ./docker-setup.sh

Closes #29933
Related: #7575, #7827, #28401, #10361, #12505, #28326

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

* fix: address code review feedback on sandbox support

- Persist OPENCLAW_SANDBOX, DOCKER_GID, OPENCLAW_INSTALL_DOCKER_CLI
  to .env via upsert_env so group_add survives re-runs
- Show config set errors instead of swallowing them silently;
  report partial failure when sandbox config is incomplete
- Warn when Dockerfile.sandbox is missing but sandbox config
  is still applied (sandbox image won't exist)
- Fix non-canonical whitespace in apt sources.list entry
  by using printf instead of echo with line continuation

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

* fix: remove `local` outside function and guard sandbox behind Docker CLI check

- Remove `local` keyword from top-level `sandbox_config_ok` assignment
  which caused script exit under `set -euo pipefail` (bash `local`
  outside a function is an error)
- Add Docker CLI prerequisite check for pre-built (non-local) images:
  runs `docker --version` inside the container and skips sandbox setup
  with a clear warning if the CLI is missing
- Split sandbox block so config is only applied after prerequisites pass

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

* fix: defer docker.sock mount until sandbox prerequisites pass

Move Docker socket mounting from the early setup phase (before image
build/pull) to a dedicated compose overlay created only after:
1. Docker CLI is verified inside the container image
2. /var/run/docker.sock exists on the host

Previously the socket was mounted optimistically at startup, leaving
the host Docker daemon exposed even when sandbox setup was later
skipped due to missing Docker CLI. Now the gateway starts without
the socket, and a docker-compose.sandbox.yml overlay is generated
only when all prerequisites pass. The gateway restart at the end of
sandbox setup picks up both the socket mount and sandbox config.

Also moves group_add from write_extra_compose() into the sandbox
overlay, keeping all sandbox-specific compose configuration together.

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

* docs(docker): fix sandbox docs URL in setup output

* Docker: harden sandbox setup fallback behavior

* Tests: cover docker-setup sandbox edge paths

* Docker: roll back sandbox mode on partial config failure

* Tests: assert sandbox mode rollback on partial setup

* Docs: document Docker sandbox bootstrap env controls

* Changelog: credit Docker sandbox bootstrap hardening

* Update CHANGELOG.md

* Docker: verify Docker apt signing key fingerprint

* Docker: avoid sandbox overlay deps during policy writes

* Tests: assert no-deps sandbox rollback gateway recreate

* Docs: mention OPENCLAW_INSTALL_DOCKER_CLI in Docker env vars

---------

Co-authored-by: Jakub Karwowski <jakubkarwowski@Mac.lan>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 23:06:10 -08:00
Tyler Yust f918b336d1
fix: agent-only announce path, BB message IDs, sender identity, SSRF allowlist (#23970)
* fix(agents): defer announces until descendant cleanup settles

* fix(bluebubbles): harden message metadata extraction

* feat(contributors): rank by composite score (commits, PRs, LOC, tenure)

* refactor(control-ui): move method guard after path checks to improve request handling

* fix subagent completion announce when only current run is pending

* fix(subagents): keep orchestrator runs active until descendants finish

* fix: prepare PR feedback follow-ups (#23970) (thanks @tyler6204)
2026-03-01 22:52:11 -08:00
Peter Steinberger cfba64c9db test: fix pdf-tool fetch/model config mock typings 2026-03-02 06:48:01 +00:00
Peter Steinberger e876c2c3b3 fix: finalize headless profile default landing (#14944) (thanks @BenediktSchackenberg) 2026-03-02 06:48:01 +00:00
Benedikt Schackenberg d03928bb69 test: Add tests for headless/noSandbox profile preference
Cover all cases requested in review:
1. headless=true → defaultProfile = 'openclaw'
2. noSandbox=true → defaultProfile = 'openclaw'
3. both false → defaultProfile = 'chrome' (existing behavior)
4. explicit defaultProfile config overrides preference logic
5. custom profiles work in headless mode

Fixes: #14895
2026-03-02 06:48:01 +00:00
Benedikt Schackenberg 3e3b49cb94 fix(browser): prefer openclaw profile in headless/noSandbox environments
In headless or noSandbox server environments (like Ubuntu Server), the
Chrome extension relay cannot work because there is no GUI browser to
attach to. Previously, the default profile was 'chrome' (extension relay)
which caused snapshot/screenshot operations to fail with:

  'Chrome extension relay is running, but no tab is connected...'

This fix prefers the 'openclaw' profile (Playwright native mode) when
browser.headless=true or browser.noSandbox=true, while preserving the
'chrome' default for GUI environments where extension relay works.

Fixes: https://github.com/openclaw/openclaw/issues/14895

🤖 AI-assisted (Claude), fully tested: pnpm build && pnpm check && pnpm test
2026-03-02 06:48:01 +00:00
Tyler Yust d0ac1b0195
feat: add PDF analysis tool with native provider support (#31319)
* feat: add PDF analysis tool with native provider support

New `pdf` tool for analyzing PDF documents with model-powered analysis.

Architecture:
- Native PDF path: sends raw PDF bytes directly to providers that support
  inline document input (Anthropic via DocumentBlockParam, Google Gemini
  via inlineData with application/pdf MIME type)
- Extraction fallback: for providers without native PDF support, extracts
  text via pdfjs-dist and rasterizes pages to images via @napi-rs/canvas,
  then sends through the standard vision/text completion path

Key features:
- Single PDF (`pdf` param) or multiple PDFs (`pdfs` array, up to 10)
- Page range selection (`pages` param, e.g. "1-5", "1,3,7-9")
- Model override (`model` param) and file size limits (`maxBytesMb`)
- Auto-detects provider capability and falls back gracefully
- Same security patterns as image tool (SSRF guards, sandbox support,
  local path roots, workspace-only policy)

Config (agents.defaults):
- pdfModel: primary/fallbacks (defaults to imageModel, then session model)
- pdfMaxBytesMb: max PDF file size (default: 10)
- pdfMaxPages: max pages to process (default: 20)

Model catalog:
- Extended ModelInputType to include "document" alongside "text"/"image"
- Added modelSupportsDocument() capability check

Files:
- src/agents/tools/pdf-tool.ts - main tool factory
- src/agents/tools/pdf-tool.helpers.ts - helpers (page range, config, etc.)
- src/agents/tools/pdf-native-providers.ts - direct API calls for Anthropic/Google
- src/agents/tools/pdf-tool.test.ts - 43 tests covering all paths
- Modified: model-catalog.ts, openclaw-tools.ts, config schema/types/labels/help

* fix: prepare pdf tool for merge (#31319) (thanks @tyler6204)
2026-03-01 22:39:12 -08:00
Peter Steinberger 31b6e58a1b docs: add relay reattach changelog attribution (#28725) (thanks @stone-jin) 2026-03-02 06:38:21 +00:00
stone-jin 04b3a51d3a fix(browser): preserve debugger attachment across relay disconnects during navigation reattach 2026-03-02 06:38:21 +00:00
Peter Steinberger 18cd77c8ce fix: cover relay reannounce minimal target path (#27630) (thanks @markmusson) 2026-03-02 06:33:28 +00:00
Mark Musson 591ff3c1c8 fix(browser-relay): fallback to cached targetId on target info lookup failure 2026-03-02 06:33:28 +00:00
Vincent Koc 3ae8e5ee91
Docs: add changelog entry for auth permission error (#31367)
* Docs: add changelog entry for auth permission error

* Update CHANGELOG.md
2026-03-01 22:30:47 -08:00
Peter Steinberger b3cf6e7d77 fix: harden relay reconnect grace coverage (#30232) (thanks @Sid-Qin) 2026-03-02 06:28:50 +00:00
SidQin-cyber f77f3fb839 fix(browser): tolerate brief extension relay disconnects on attached tabs
Keep extension relay tab metadata available across short extension worker drops and allow CDP clients to connect while waiting for reconnect. This prevents false "no tab connected" failures in environments where the extension worker disconnects transiently (e.g. WSLg/MV3).
2026-03-02 06:28:50 +00:00
Peter Steinberger 0eebae44f6 fix: test browser.request profile body fallback (#28852) (thanks @Sid-Qin) 2026-03-02 06:26:35 +00:00
SidQin-cyber fa875a6bf7 fix(gateway): honor browser profile from request body for node proxy calls
Gateway browser.request only read profile from query.profile before invoking
browser.proxy on nodes. Calls that passed profile in POST body silently fell
back to the default profile, which could switch users into chrome extension
mode even when they explicitly requested openclaw profile.

Use query profile first, then fall back to body.profile when present.

Closes #28687
2026-03-02 06:26:35 +00:00
Sid 40e078a567
fix(auth): classify permission_error as auth_permanent for profile fallback (#31324)
When an OAuth auth profile returns HTTP 403 with permission_error
(e.g. expired plan), the error was not matched by the authPermanent
patterns. This caused the profile to receive only a short cooldown
instead of being disabled, so the gateway kept retrying the same
broken profile indefinitely.

Add "permission_error" and "not allowed for this organization" to
the authPermanent error patterns so these errors trigger the longer
billing/auth_permanent disable window and proper profile rotation.

Closes #31306

Made-with: Cursor

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 22:26:05 -08:00
Timothy Jordan f2dbaf70fa
docs: add Vercel sponsorship (#29270)
* docs: add Vercel sponsorship

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

* docs: fix README formatting

* docs: resize Vercel sponsor logo to match other logos

* docs: scale down Vercel SVG viewBox to match other sponsor logos

* Fixed ordering.

* md error fix

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 06:25:46 +00:00
SidQin-cyber 821b7c80a6 fix(browser): avoid extension profile startup deadlock in browser start
browser start for driver=extension required websocket tab attachment during
ensureBrowserAvailable, which can deadlock startup because tabs can only
attach after relay startup succeeds.

For extension profiles, only require relay HTTP reachability in startup and
leave tab attachment checks to ensureTabAvailable when a concrete tab action
is requested.

Closes #28701
2026-03-02 06:19:36 +00:00
Peter Steinberger 5b8f492a48 fix(security): harden spoofed system marker handling 2026-03-02 06:19:16 +00:00
SidQin-cyber 7c9d2c1d48 fix(browser): retry relay navigation after frame detach
Retry browser navigate once after transient frame-detached/target-closed errors by forcing a clean Playwright reconnect, so extension-relay sessions stay controllable across navigation swaps.

Closes #29431
2026-03-02 06:14:52 +00:00
zerone0x 376a52a5ba
fix: use 0o644 for inbound media files to allow sandbox read access (#17943)
* fix: use 0o644 for inbound media files to allow sandbox read access

Inbound media files were saved with 0o600 permissions, making them
unreadable from Docker sandbox containers running as different users.

Change to 0o644 (world-readable) so sandboxed agents can access
downloaded attachments.

Fixes #17941

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

* test(media): assert URL-sourced inbound files use 0o644

* test(media): make redirect file-mode assertion platform-aware

* docs(media): clarify 0o644 is for sandbox UID compatibility

---------

Co-authored-by: zerone0x <zerone0x@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 22:14:39 -08:00
AaronWander 366374b4ff
Sandbox: add actionable error when docker missing (#28547)
Co-authored-by: AaronWander <siralonne@163.com>
2026-03-01 22:14:26 -08:00
garnetlyx ffa7c13c9b fix(voice-call): verify call status with provider before loading stale calls
On gateway restart, persisted non-terminal calls are now verified with
the provider (Twilio/Plivo/Telnyx) before being restored to memory.
This prevents phantom calls from blocking the concurrent call limit.

- Add getCallStatus() to VoiceCallProvider interface
- Implement for all providers with SSRF-guarded fetch
- Transient errors (5xx, network) keep the call with timer fallback
- 404/known-terminal statuses drop the call
- Restart max-duration timers for restored answered calls
- Skip calls older than maxDurationSeconds or without providerCallId
2026-03-01 22:13:24 -08:00
Peter Steinberger 3049ca840f docs: replace bare provider URLs with markdown links 2026-03-02 06:01:29 +00:00
Jannes Stubbemann 5bb26bf22a
fix(browser): skip port ownership check for remote CDP profiles (#28780)
* fix(browser): skip port ownership check for remote CDP profiles

When a browser profile has a non-loopback cdpUrl (e.g. Browserless,
Kubernetes sidecar, or any external CDP service), the port-ownership
check incorrectly fires because we don't "own" the remote process.
This causes "Port is in use but not by openclaw" even though the
remote CDP service is working and reachable.

Guard the ownership error with !remoteCdp so remote profiles fall
through to the WebSocket retry/attach logic instead.

Fixes #15582

* fix: add TypeScript null guard for profileState.running

* chore(changelog): note remote CDP ownership fix credits

Refs #15582

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 22:00:16 -08:00
Sahil Satralkar cda119b052
fix: handle missing systemctl in containers (#26089) (#26699)
* Daemon: handle missing systemctl in containers

* Daemon: harden missing-systemctl detection

* Daemon tests: cover systemctl spawn failure path

* Changelog: note container systemctl service-check fix

* Update CHANGELOG.md

* Daemon: fail closed on unknown systemctl is-enabled errors

* Daemon tests: cover is-enabled unknown-error path

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 21:48:06 -08:00
Peter Steinberger 5d78fcf1b5 docs: add missing message channels to readme 2026-03-02 05:46:57 +00:00
Peter Steinberger bc0288bcfb docs: clarify adaptive thinking and openai websocket docs 2026-03-02 05:46:57 +00:00
Sid e1e715c53d
fix(gateway): skip device pairing for local backend self-connections (#30801)
* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes #30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 21:46:33 -08:00
Sid 3002f13ca7
feat(config): add `openclaw config validate` and improve startup error messages (#31220)
Merged via squash.

Prepared head SHA: 4598f2a541
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-02 00:45:51 -05:00
Vincent Koc 5a2200b280
fix(sessions): harden recycled PID lock recovery follow-up (#31320)
* fix: detect PID recycling in session write lock staleness check

The session lock uses isPidAlive() to determine if a lock holder is
still running. In containers, PID recycling can cause a different
process to inherit the same PID, making the lock appear valid when
the original holder is dead.

Record the process start time (field 22 of /proc/pid/stat) in the
lock file and compare it during staleness checks. If the PID is alive
but its start time differs from the recorded value, the lock is
treated as stale and reclaimed immediately.

Backward compatible: lock files without starttime are handled with
the existing PID-alive + age-based logic. Non-Linux platforms skip
the starttime check entirely (getProcessStartTime returns null).

* shared: harden pid starttime parsing

* sessions: validate lock pid/starttime payloads

* changelog: note recycled PID lock recovery fix

* changelog: credit hiroki and vincent on lock recovery fix

---------

Co-authored-by: HirokiKobayashi-R <hiroki@rhems-japan.co.jp>
2026-03-01 21:42:22 -08:00
Ayaan Zaidi 548a502c69 docs: sync android node docs with current pairing and capabilities 2026-03-02 11:08:51 +05:30
Nikolay Petrov a9f1188785
sessions_spawn: inline attachments with redaction, lifecycle cleanup, and docs (#16761)
Add inline file attachment support for sessions_spawn (subagent runtime only):

- Schema: attachments[] (name, content, encoding, mimeType) and attachAs.mountPath hint
- Materialization: files written to .openclaw/attachments/<uuid>/ with manifest.json
- Validation: strict base64 decode, filename checks, size limits, duplicate detection
- Transcript redaction: sanitizeToolCallInputs redacts attachment content from persisted transcripts
- Lifecycle cleanup: safeRemoveAttachmentsDir with symlink-safe path containment check
- Config: tools.sessions_spawn.attachments (enabled, maxFiles, maxFileBytes, maxTotalBytes, retainOnSessionKeep)
- Registry: attachmentsDir/attachmentsRootDir/retainAttachmentsOnKeep on SubagentRunRecord
- ACP rejection: attachments rejected for runtime=acp with clear error message
- Docs: updated tools/index.md, concepts/session-tool.md, configuration-reference.md
- Tests: 85 new/updated tests across 5 test files

Fixes:
- Guard fs.rm in materialization catch block with try/catch (review concern #1)
- Remove unreachable fallback in safeRemoveAttachmentsDir (review concern #7)
- Move attachment cleanup out of retry path to avoid timing issues with announce loop

Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: napetrov <napetrov@users.noreply.github.com>
2026-03-01 21:33:51 -08:00
Peter Steinberger 842deefe5d test: split fast lane from channel and gateway suites 2026-03-02 05:33:07 +00:00
Peter Steinberger a13586619b test: move integration-heavy suites to e2e lane 2026-03-02 05:33:07 +00:00
Peter Steinberger 656121a12b test: micro-optimize hot unit test files 2026-03-02 05:33:07 +00:00
Peter Steinberger 1de3200973 refactor(infra): centralize boundary traversal and root path checks 2026-03-02 05:20:19 +00:00
Peter Steinberger 7fcec6ca3e refactor(streaming): share approval and stream message builders 2026-03-02 05:20:19 +00:00
Peter Steinberger 6b78544f82 refactor(commands): unify repeated ACP and routing flows 2026-03-02 05:20:19 +00:00
Peter Steinberger 2d31126e6a refactor(shared): extract reused path and normalization helpers 2026-03-02 05:20:19 +00:00
Peter Steinberger 264599cc1d refactor(core): share JSON utf8 byte counting helper 2026-03-02 05:20:19 +00:00
Peter Steinberger 4a1be98254 fix(diffs): harden viewer security and docs 2026-03-02 05:07:09 +00:00
Peter Steinberger 0ab2c82624 docs: dedupe 2026.3.1 changelog entries 2026-03-02 05:04:28 +00:00
Peter Steinberger 6ba7238ac6 build: bump versions to 2026.3.2 2026-03-02 04:55:53 +00:00
Umut CAN d2472af724
Chore: add Dockerfile HEALTHCHECK and debug-log silent catch blocks (#11478)
* Docker: add /healthz-based container HEALTHCHECK

* Docs/Docker: document built-in image HEALTHCHECK

* Changelog: note Dockerfile healthcheck probe

* Docs/Docker: explain HEALTHCHECK behavior in plain language

* Docker: relax HEALTHCHECK interval to 3m

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 20:52:14 -08:00
Peter Steinberger 2a8ac974e1 build: prepare 2026.3.1 latest release 2026-03-02 04:50:11 +00:00
Alberto Leal 449511484d
fix(gateway): allow ws:// to private network addresses (#28670)
* fix(gateway): allow ws:// to RFC 1918 private network addresses

resolve ws-private-network conflicts

* gateway: keep ws security strict-by-default with private opt-in

* gateway: apply private ws opt-in in connection detail guard

* gateway: apply private ws opt-in in websocket client

* onboarding: gate private ws urls behind explicit opt-in

* gateway tests: enforce strict ws defaults with private opt-in

* onboarding tests: validate private ws opt-in behavior

* gateway client tests: cover private ws env override

* gateway call tests: cover private ws env override

* changelog: add ws strict-default security entry for pr 28670

* docs(onboard): document private ws break-glass env

* docs(gateway): add private ws env to remote guide

* docs(docker): add private ws break-glass env var

* docs(security): add private ws break-glass guidance

* docs(config): document OPENCLAW_ALLOW_PRIVATE_WS

* Update CHANGELOG.md

* gateway: normalize private-ws host classification

* test(gateway): cover non-unicast ipv6 private-ws edges

* changelog: rename insecure private ws break-glass env

* docs(onboard): rename insecure private ws env

* docs(gateway): rename insecure private ws env in config reference

* docs(gateway): rename insecure private ws env in remote guide

* docs(security): rename insecure private ws env

* docs(docker): rename insecure private ws env

* test(onboard): rename insecure private ws env

* onboard: rename insecure private ws env

* test(gateway): rename insecure private ws env in call tests

* gateway: rename insecure private ws env in call flow

* test(gateway): rename insecure private ws env in client tests

* gateway: rename insecure private ws env in client

* docker: pass insecure private ws env to services

* docker-setup: persist insecure private ws env

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 20:49:45 -08:00
Peter Steinberger d76b224e20 docs: update appcast for 2026.3.1 2026-03-02 04:41:39 +00:00
Peter Steinberger 92ad89da00 build: prepare 2026.3.1-beta.1 release 2026-03-02 04:38:03 +00:00
Vincent Koc eeb72097ba
Gateway: add healthz/readyz probe endpoints for container checks (#31272)
* Gateway: add HTTP liveness/readiness probe routes

* Gateway tests: cover probe route auth bypass and methods

* Docker Compose: add gateway /healthz healthcheck

* Docs: document Docker probe endpoints

* Dockerfile: note built-in probe endpoints

* Gateway: make probe routes fallback-only to avoid shadowing

* Gateway tests: verify probe paths do not shadow plugin routes

* Changelog: note gateway container probe endpoints
2026-03-01 20:36:58 -08:00
Peter Steinberger 0a1eac6b0b fix(ios): eliminate voice wake and xcode build warnings 2026-03-02 04:36:49 +00:00
Peter Steinberger 7073f63610 fix(ios): enforce main-actor device status APIs 2026-03-02 04:36:49 +00:00
Peter Steinberger cb484f44e9 fix: resolve i18n merge conflict and test hoist failure 2026-03-02 04:36:11 +00:00
cyb1278588254 96ffbb5aaf
CLI: add config path subcommand to print active config file path (#26256)
Merged via squash.

Prepared head SHA: b11c593a34
Co-authored-by: cyb1278588254 <48212932+cyb1278588254@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-03-01 23:33:20 -05:00
Peter Steinberger dc2290aeb1 fix(ci): drop redundant env assertions in daemon status 2026-03-02 04:32:35 +00:00
Peter Steinberger 8b05e4091c fix(discord): prefer names in allowlist resolution logs 2026-03-02 04:31:10 +00:00
Vincent Koc b7615e0ce3
Exec/ACP: inject OPENCLAW_SHELL into child shell env (#31271)
* exec: mark runtime shell context in exec env

* tests(exec): cover OPENCLAW_SHELL in gateway exec

* tests(exec): cover OPENCLAW_SHELL in pty mode

* acpx: mark runtime shell context for spawned process

* tests(acpx): log OPENCLAW_SHELL in runtime fixture

* tests(acpx): assert OPENCLAW_SHELL in runtime prompt

* docs(env): document OPENCLAW_SHELL runtime markers

* docs(exec): describe OPENCLAW_SHELL exec marker

* docs(acp): document OPENCLAW_SHELL acp marker

* docs(gateway): note OPENCLAW_SHELL for background exec

* tui: tag local shell runs with OPENCLAW_SHELL

* tests(tui): assert OPENCLAW_SHELL in local shell runner

* acp client: tag spawned bridge env with OPENCLAW_SHELL

* tests(acp): cover acp client OPENCLAW_SHELL env helper

* docs(env): include acp-client and tui-local shell markers

* docs(acp): document acp-client OPENCLAW_SHELL marker

* docs(tui): document tui-local OPENCLAW_SHELL marker

* exec: keep shell runtime env string-only for docker args

* changelog: note OPENCLAW_SHELL runtime markers
2026-03-01 20:31:06 -08:00
Peter Steinberger aeb817353f style(changelog): apply oxfmt 2026-03-02 04:30:05 +00:00
Peter Steinberger 1c0d36eed0 fix(ci): resolve i18n typing and generated-policy drift 2026-03-02 04:29:18 +00:00
Peter Steinberger fa9148400e fix(android): align lint gates and photo permission handling 2026-03-02 04:28:17 +00:00
Peter Steinberger 37d036714e fix(thinking): default Claude 4.6 to adaptive 2026-03-02 04:27:26 +00:00
Sid 4691aab019
fix(cron): guard against year-rollback in croner nextRun (#30777)
* fix(cron): guard against year-rollback in croner nextRun

Croner can return a past-year timestamp for some timezone/date
combinations (e.g. Asia/Shanghai).  When nextRun returns a value at or
before nowMs, retry from the next whole second and, if still stale,
from midnight-tomorrow UTC before giving up.

Closes #30351

* googlechat: guard API calls with SSRF-safe fetch

* test: fix hoisted plugin context mock setup

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 22:22:59 -06:00
Peter Steinberger 6fc0787bf0 chore(deps): bump workspace dependencies 2026-03-02 04:22:33 +00:00
Peter Steinberger 8208f5e822 docs: reorder unreleased changelog by user impact 2026-03-02 04:19:56 +00:00
Peter Steinberger 619dfa88cb fix(discord): enrich allowlist resolution logs 2026-03-02 04:19:37 +00:00
Peter Steinberger d17f4432b3 chore: fix gate formatting and raw-fetch allowlist lines 2026-03-02 04:18:48 +00:00
Peter Steinberger 7b3f506e64 style(swift): apply swiftformat and swiftlint fixes 2026-03-02 04:15:43 +00:00
Peter Steinberger e1f3ded033 refactor: split telegram delivery and unify media/frontmatter/i18n pipelines 2026-03-02 04:14:06 +00:00
Peter Steinberger 706cfcd54f fix: isolate docker onboard e2e config env 2026-03-02 04:10:28 +00:00
Peter Steinberger f46bd2e0cc refactor(feishu): split monitor startup and transport concerns 2026-03-02 04:09:24 +00:00
Peter Steinberger c0bf42f2a8 refactor: centralize delivery/path/media/version lifecycle 2026-03-02 04:04:36 +00:00
Peter Steinberger f4f094fc3b test(mattermost): cover defaultAccount resolution 2026-03-02 04:03:55 +00:00
Peter Steinberger 41537e9303 fix(channels): add optional defaultAccount routing 2026-03-02 04:03:46 +00:00
Peter Steinberger 0437ac1a89 fix(gateway): raise health-monitor restart cap 2026-03-02 04:03:04 +00:00
Mark L 0f2dce0483
fix(agents): prioritize per-model thinking defaults (#30439)
* fix(agents): honor per-model thinking defaults

* fix(agents): preserve thinking fallback with model defaults

---------

Co-authored-by: Mark L <73659136+markliuyuxiang@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 04:00:02 +00:00
Peter Steinberger 3fc19ed7d7 fix: harden feishu startup probe sequencing (#29941) (thanks @bmendonca3) 2026-03-02 03:59:23 +00:00
bmendonca3 abc7b6fbec Feishu: skip duplicate bot-info retries after preflight 2026-03-02 03:59:23 +00:00
bmendonca3 bdca44693c Feishu: serialize startup bot-info probes 2026-03-02 03:59:23 +00:00
Peter Steinberger 02b1958760 fix(feishu): suppress stale replay typing indicators (#30709) (thanks @arkyu2077) 2026-03-02 03:53:24 +00:00
yuxh1996 7fbc40f821 fix(feishu): skip typing indicator on old messages after context compaction (#30418) 2026-03-02 03:53:24 +00:00
Mark L 5b06c8c6e3
fix(config): normalize gateway bind host aliases during migration (#30855)
* fix(config): normalize gateway bind host aliases during migration [AI-assisted]

* config(legacy): detect gateway.bind host aliases as legacy

* config(legacy): sanitize bind alias migration log output

* test(config): cover bind alias legacy detection and log escaping

* config(legacy): add source-literal gate to legacy rules

* config(legacy): make issue detection source-aware

* config(legacy): require source-literal gateway.bind alias detection

* config(io): pass parsed source to legacy issue detection

* test(config): cover resolved-only gateway.bind alias legacy detection

* changelog: format after #30855 rebase conflict resolution

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 19:53:00 -08:00
Sid c9f0d6ac8e
feat(agents): support `thinkingDefault: "adaptive"` for Anthropic models (#31227)
* feat(agents): support `thinkingDefault: "adaptive"` for Anthropic models

Anthropic's Opus 4.6 and Sonnet 4.6 support adaptive thinking where the
model dynamically decides when and how much to think.  This is now
Anthropic's recommended mode and `budget_tokens` is deprecated on these
models.

Add "adaptive" as a valid thinking level:
- Config: `agents.defaults.thinkingDefault: "adaptive"`
- CLI: `/think adaptive` or `/think auto`
- Pi SDK mapping: "adaptive" → "medium" effort at the pi-agent-core
  layer, which the Anthropic provider translates to
  `thinking.type: "adaptive"` with `output_config.effort: "medium"`
- Provider fallbacks: OpenRouter and Google map "adaptive" to their
  respective "medium" equivalents

Closes #30880

Made-with: Cursor

* style(changelog): format changelog with oxfmt

* test(types): fix strict typing in runtime/plugin-context tests

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 03:52:02 +00:00
Peter Steinberger ede944371f fix(telegram): land #31067 first-chunk voice-fallback reply refs (@xdanger)
Landed from contributor PR #31067 by @xdanger.

Co-authored-by: Kros Dai <xdanger@gmail.com>
2026-03-02 03:50:09 +00:00
Anandesh Sharma 61ef76edb5
docs(gateway): document Docker bridge networking and loopback bind caveat (#28001)
* docs(gateway): document Docker bridge networking and loopback bind caveat

The default loopback bind makes the gateway unreachable with Docker
bridge networking because port-forwarded traffic arrives on eth0, not
lo. Add a note in both the Dockerfile and the configuration reference
explaining the workarounds (--network host or bind: lan).

Fixes #27950

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

* docs(docker): note legacy gateway.bind alias migration

* docs(gateway): clarify legacy bind alias auto-migration

* docs(docker): require bind mode values in gateway.bind

* docs(gateway): avoid bind alias auto-migration claim

* changelog: add #28001 docker bind docs credit

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 19:45:27 -08:00
Charles Dusek 92199ac129
fix(agents): unblock gpt-5.3-codex API-key routing and replay (#31083)
* fix(agents): unblock gpt-5.3-codex API-key replay path

* fix(agents): scope OpenAI replay ID rewrites per turn

* test: fix nodes-tool mock typing and reformat telegram accounts
2026-03-02 03:45:12 +00:00
Peter Steinberger e1bf9591c3 fix(web-tools): land #31176 allow RFC2544 trusted fetch range (@sunkinux)
Landed from contributor PR #31176 by @sunkinux.

Co-authored-by: sunkinux <sunkinux@users.noreply.github.com>
2026-03-02 03:43:25 +00:00
Peter Steinberger 2a252a14cc fix(feishu): harden target routing, dedupe, and reply fallback 2026-03-02 03:41:53 +00:00
Clawborn 77ccd35e5e
Fix onboard ignoring OPENCLAW_GATEWAY_TOKEN env var (#22658)
* Fix onboard ignoring OPENCLAW_GATEWAY_TOKEN env var

When running onboard via docker-setup.sh, the QuickStart wizard
generates its own 48-char token instead of using the 64-char token
already set in OPENCLAW_GATEWAY_TOKEN. This causes a token mismatch
that breaks all CLI commands after setup.

Check process.env.OPENCLAW_GATEWAY_TOKEN before falling back to
randomToken() in both the interactive QuickStart path and the
non-interactive path.

Closes #22638

Co-authored-by: Clawborn <tianrun.yang103@gmail.com>

* Tests: cover quickstart env token fallback

* Changelog: note docker onboarding token parity fix

* Tests: restore env var after non-interactive token fallback test

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 19:40:40 -08:00
Gustavo Madeira Santana 8e69fd80e0 Gateway: harden control-ui vs plugin HTTP precedence 2026-03-01 22:38:14 -05:00
Gustavo Madeira Santana 6532757cdf Diffs: add viewer payload validation and presentation defaults 2026-03-01 22:38:14 -05:00
Peter Steinberger 0202d79df4 fix(inbound-meta): land #30984 include account_id context (@Stxle2)
Landed from contributor PR #30984 by @Stxle2.

Co-authored-by: Stxle2 <166609401+Stxle2@users.noreply.github.com>
2026-03-02 03:36:48 +00:00
Ayaan Zaidi c13b35b83d feat(telegram): improve DM topics support (#30579) (thanks @kesor) 2026-03-02 09:06:45 +05:30
Peter Steinberger aafc4d56e3 docs(changelog): credit fixes from PRs #31058 #31211 #30941 #31047 #31205 2026-03-02 03:35:49 +00:00
Peter Steinberger 00dcd931cb test(fs-safe): assert directory-read errors never leak EISDIR text 2026-03-02 03:35:20 +00:00
倪汉杰0668001185 6398a0ba8f fix(infra): avoid EISDIR leak to messaging when Read targets directory (Closes #31186) 2026-03-02 03:35:20 +00:00
Dale Babiy 8a4d8c889c
fix(secrets): normalize inline SecretRef token/key to tokenRef/keyRef in runtime snapshot (#31047)
* fix(secrets): normalize inline SecretRef token/key to tokenRef/keyRef in runtime snapshot

When auth-profiles.json uses an inline SecretRef as the token or key
value directly (e.g. `"token": {"source":"file",...}`), the resolved
plaintext was written back to disk on every updateAuthProfileStoreWithLock
call, overwriting the SecretRef.

Root cause: collectTokenProfileAssignment and collectApiKeyProfileAssignment
detected inline SecretRefs but did not promote them to the canonical
tokenRef/keyRef fields. saveAuthProfileStore only strips plaintext when
tokenRef/keyRef is set, so the inline case fell through and persisted
plaintext on every save.

Fix: when an inline SecretRef is detected and no explicit tokenRef/keyRef
exists, promote it to the canonical field and delete the inline form.
saveAuthProfileStore then correctly strips the resolved plaintext on write.

Fixes #29108

* fix test: cast inline SecretRef loadAuthStore mocks to AuthProfileStore

* fix(secrets): fix TypeScript type error in runtime test loadAuthStore lambda

* test(secrets): keep explicit keyRef precedence over inline key ref

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 03:34:23 +00:00
Peter Steinberger d446722f2f docs(changelog): merge post-v2026.2.26 release notes 2026-03-02 03:34:00 +00:00
Peter Steinberger edd9319552 fix(feishu): land #31209 prevent system preview leakage (@stakeswky)
Landed from contributor PR #31209 by @stakeswky.

Co-authored-by: stakeswky <stakeswky@users.noreply.github.com>
2026-03-02 03:33:48 +00:00
Peter Steinberger 072e1e9e38 test(session): cover internal route without external fallback 2026-03-02 03:33:12 +00:00
graysurf 95db5bb5e8 fix(session): preserve external lastTo routing for internal turns 2026-03-02 03:33:12 +00:00
Peter Steinberger 0fa5d6ed2e test(usage): cover negative prompt_tokens alias clamp 2026-03-02 03:31:47 +00:00
scoootscooob 20467d987d fix(usage): clamp negative input token counts to zero
Some OpenAI-format providers (via pi-ai) pre-subtract cached_tokens from
prompt_tokens upstream.  When cached_tokens exceeds prompt_tokens due to
provider inconsistencies the subtraction produces a negative input value
that flows through to the TUI status bar and /usage dashboard.

Clamp rawInput to 0 in normalizeUsage() so downstream consumers never
see nonsensical negative token counts.

Closes #30765

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 03:31:47 +00:00
Glucksberg 08c35eb13f
fix(cron): re-arm one-shot at-jobs when rescheduled after completion (openclaw#28915) thanks @Glucksberg
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 21:31:24 -06:00
lbo728 904016b7de fix(origin-check): honour "*" wildcard in gateway.controlUi.allowedOrigins
When gateway.controlUi.allowedOrigins is set to ["*"], the Control UI
WebSocket was still rejected with "origin not allowed" for any non-
loopback origin (e.g. Tailscale IPs, LAN addresses).

Root cause: checkBrowserOrigin() compared each allowedOrigins entry
against the parsed request origin via a literal Array#includes(). The
entry "*" never equals an actual origin string, so the wildcard was
silently ignored and all remote connections were blocked.

Fix: check for the literal "*" entry before the per-origin comparison
and return ok:true immediately when found.

Closes #30990
2026-03-02 03:30:20 +00:00
Peter Steinberger 08f8aea32e fix(signal): land #31138 syncMessage presence filtering (@Sid-Qin)
Landed from contributor PR #31138 by @Sid-Qin.

Co-authored-by: Sid-Qin <sidqin0410@gmail.com>
2026-03-02 03:28:25 +00:00
Peter Steinberger 22666034a0 docs(changelog): credit feishu fix contributors 2026-03-02 03:24:21 +00:00
Vincent Koc 1a42ea3abf
fix(auto-reply): normalize block-reply callback to Promise for timeout path (#31200)
* Auto-reply: wrap block reply callback in Promise.resolve for timeout safety

* Build: add strict smoke build script for CI regression gating

* CI: gate strict TS smoke build in check workflow

* docs(changelog): add auto-reply block reply timeout fix under Unreleased

* docs(changelog): credit original #19779 contributor and vincentkoc
2026-03-01 19:23:38 -08:00
Vincent Koc 030565b18c
Docker: add OCI base-image labels and document base-image metadata (#31196)
* Docker: add OCI base image labels

* Docs(Docker): document base image metadata context

* Changelog: note Docker base annotation docs update

* Changelog: add author credit for Docker base annotations

* Update docker.md

* Docker: add OCI source and docs labels

* CI(Docker): publish OCI revision/version labels

* Docs(Docker): list OCI image annotations

* Changelog: expand OCI annotation coverage note

* Docker: set OCI license annotation to MIT

* Docs(Docker): align OCI license annotation to MIT

* Docker: note docs sync path for OCI annotations

* Docker: normalize OCI label block indentation
2026-03-01 19:22:44 -08:00
Peter Steinberger 6ea3a47dae fix(feishu): harden routing, parsing, and media delivery 2026-03-02 03:22:07 +00:00
Peter Steinberger cdbed3c9b1 fix(googlechat): land #30965 thread reply option support (@novan)
Landed from contributor PR #30965 by @novan.

Co-authored-by: novan <novan@users.noreply.github.com>
2026-03-02 03:16:48 +00:00
Peter Steinberger 355b4c62bc fix(mattermost): land #30891 route private channels as group (@BlueBirdBack)
Landed from contributor PR #30891 by @BlueBirdBack.

Co-authored-by: BlueBirdBack <BlueBirdBack@users.noreply.github.com>
2026-03-02 03:14:17 +00:00
Peter Steinberger 6bea38b21f fix(models): land #31202 normalize custom provider keys (@stakeswky)
Landed from contributor PR #31202 by @stakeswky.

Co-authored-by: stakeswky <stakeswky@users.noreply.github.com>
2026-03-02 03:11:55 +00:00
Peter Steinberger 342bf4838e fix(cli): preserve json stdout while keeping doctor migration (#24368) (thanks @altaywtf) 2026-03-02 03:10:02 +00:00
Altay 67b98139b9 test(cli): avoid brittle mock call indexing in json-mode checks 2026-03-02 03:10:02 +00:00
Altay 9e4a366ee6 fix(cli): keep json preflight stdout machine-readable 2026-03-02 03:10:02 +00:00
Peter Steinberger 493ebb915b refactor: simplify telegram delivery and outbound session resolver flow 2026-03-02 03:09:40 +00:00
Peter Steinberger 166ae8f002 fix(matrix): land #31201 preserve room ID casing (@williamos-dev)
Landed from contributor PR #31201 by @williamos-dev.

Co-authored-by: williamos-dev <williamos-dev@users.noreply.github.com>
2026-03-02 03:09:23 +00:00
Peter Steinberger efd303dbc4 fix: normalize Discord wildcard sentinel parsing (#29459) (thanks @Sid-Qin) 2026-03-02 03:08:32 +00:00
SidQin-cyber 6210d2e238 fix(discord): prevent wildcard component registration collisions
Assign distinct sentinel registration ids to Discord wildcard handlers while preserving wildcard parser keys, so select/menu/modal handlers no longer get dropped on runtimes that dedupe by raw customId.
2026-03-02 03:08:32 +00:00
Peter Steinberger c869ca4bbf fix: harden discord agent cid parsing (#29013) (thanks @Jacky1n7) 2026-03-02 03:07:48 +00:00
李肖然 c14c17403e style: oxfmt for agent-components 2026-03-02 03:07:48 +00:00
李肖然 e95f96f77a fix(discord): guard cid decode to avoid URIError 2026-03-02 03:07:48 +00:00
李肖然 9aba8422ca fix(discord): accept cid in agent component interactions 2026-03-02 03:07:48 +00:00
Peter Steinberger 25b731c34a fix: harden discord media fallback regressions (#28906) (thanks @Sid-Qin) 2026-03-02 03:05:12 +00:00
SidQin-cyber 0a67033fe3 fix(discord): keep attachment metadata when media fetch is blocked
Preserve inbound attachment/sticker metadata in Discord message context when media download fails (for example due to SSRF blocking), so agents still see file references instead of silent drops.

Closes #28816
2026-03-02 03:05:12 +00:00
Peter Steinberger e4e5d9c98c fix(model): land #30932 auth-profile @ parsing for /model (@haosenwang1018)
Landed from contributor PR #30932 by @haosenwang1018.

Co-authored-by: haosenwang1018 <haosenwang1018@users.noreply.github.com>
2026-03-02 03:05:03 +00:00
Peter Steinberger 15c1c93a95 docs: add missing changelog entry for #31064 2026-03-02 03:04:10 +00:00
Hyup 9c03f8be08
telegram: retry media fetch with IPv4 fallback on connect errors (#30554)
* telegram: retry fetch once with IPv4 fallback on connect errors

* test(telegram): format fetch fallback test

* style(telegram): apply oxfmt for fetch test

* fix(telegram): retry ipv4 fallback per request

* test: harden telegram ipv4 fallback coverage (#30554)

---------

Co-authored-by: root <root@vultr.guest>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 03:00:33 +00:00
Peter Steinberger 31c4722e90 docs: credit telegram empty-final regression coverage (#30746) 2026-03-02 02:59:08 +00:00
Rylen Anil 48d369749c fix(telegram): skip null/undefined final text payloads 2026-03-02 02:59:08 +00:00
liuxiaopai-ai e6e3a7b497 fix(telegram): retry DM thread sends without message_thread_id [AI-assisted] 2026-03-02 02:58:15 +00:00
Peter Steinberger ef9085927b test: cover voice fallback first-chunk reply behavior (#31077) 2026-03-02 02:57:10 +00:00
scoootscooob 2a381e6d7b fix(telegram): replyToMode 'first' now only applies reply-to to first chunk
The `replyToMessageIdForPayload` was computed once outside the chunk
and media loops, so all chunks received the same reply-to ID even when
replyToMode was set to "first". This replaces the static binding with
a lazy `resolveReplyTo()` function that checks `hasReplied` at each
send site, and updates `hasReplied` immediately after the first
successful send.

Fixes #31039

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:57:10 +00:00
Brian Le f64d25bd3e
fix(telegram): scope DM topic thread keys by chat id (#31064)
* fix(telegram): scope DM topic thread keys by chat id

* test(telegram): update dm topic session-key expectation

* fix(telegram): parse scoped dm thread ids in outbound recovery

* chore(telegram): format accounts config merge block

* test(nodes): simplify mocked exports for ts tuple spreads
2026-03-02 02:54:45 +00:00
Tak Hoffman bbab94c1fe
security(feishu): bind doc create grants to trusted requester context (#31184)
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 20:51:45 -06:00
不做了睡大觉 e482da6682
fix(ollama): prioritize provider baseUrl for embedded runner (#30964)
* fix(ollama): honor provider baseUrl in embedded runner

* Embedded Ollama: clarify provider baseUrl precedence comment

* Changelog: note embedded Ollama baseUrl precedence fix

* Telegram: apply required formatter update in accounts config merge

* Revert "Telegram: apply required formatter update in accounts config merge"

This reverts commit d372b26975.

* Update CHANGELOG.md

---------

Co-authored-by: User <user@example.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 18:38:42 -08:00
Gustavo Madeira Santana fd341d0d3f docs(changelog): add diffs entry 2026-03-01 21:36:42 -05:00
Peter Steinberger b0c7f1ebe2 fix: harden sessions_spawn delivery params and telegram account routing (#31000, #31110) 2026-03-02 02:35:48 +00:00
Peter Steinberger 684ac44b71 fix(ui): land #31133 cron edit form viewport scrolling (@Sid-Qin)
Landed from contributor PR #31133 by @Sid-Qin.

Co-authored-by: Sid-Qin <Sid-Qin@users.noreply.github.com>
2026-03-02 02:34:43 +00:00
Peter Steinberger 8eac33d4e0 fix(ui): land #30978 debug event log full-width payloads (@stozo04)
Landed from contributor PR #30978 by @stozo04.

Co-authored-by: stozo04 <stozo04@users.noreply.github.com>
2026-03-02 02:32:18 +00:00
Vincent Koc 601d1ccd24
Docs(Docker): clarify official GHCR image usage and setup flow (#31180)
* Add pre built images to docker docs

* Docs(Docker): clarify official GHCR image guidance

* Changelog: document Docker docs image clarification

* Update CHANGELOG.md

---------

Co-authored-by: Ken <ken@ipl31.net>
2026-03-01 18:31:20 -08:00
Peter Steinberger 5850045df6 fix(cron): land #31145 explicit delivery none in editor (@byungsker)
Landed from contributor PR #31145 by @byungsker.

Co-authored-by: byungsker <byungsker@users.noreply.github.com>
2026-03-02 02:29:42 +00:00
Peter Steinberger 1da7906a5d fix(line): land #31151 M4A voice MIME detection (@scoootscooob)
Landed from contributor PR #31151 by @scoootscooob.

Co-authored-by: scoootscooob <scoootscooob@users.noreply.github.com>
2026-03-02 02:26:41 +00:00
Peter Steinberger a1a8ec6870 fix(windows): land #31147 plugin install spawn EINVAL (@codertony)
Landed from contributor PR #31147 by @codertony.

Co-authored-by: codertony <codertony@users.noreply.github.com>
2026-03-02 02:23:53 +00:00
Peter Steinberger 00d2df46c7 docs(changelog): note security audit and slack download scope hardening 2026-03-02 02:23:43 +00:00
Peter Steinberger 40fda40aa7 fix(slack): scope download-file to channel and thread context 2026-03-02 02:23:22 +00:00
Peter Steinberger 17bae93680 fix(security): warn on wildcard control-ui origins and feishu owner grants 2026-03-02 02:23:22 +00:00
Peter Steinberger cc0806dfab docs(discord): standardize eventQueue timeout guidance 2026-03-02 02:22:59 +00:00
Peter Steinberger 4f8c49e85b docs: backfill telegram changelog credits for merged PRs 2026-03-02 02:14:14 +00:00
Jose E Velez 0c8fa63b93
feat: lightweight bootstrap context mode for heartbeat/cron runs (openclaw#26064) thanks @jose-velez
Verified:
- pnpm build
- pnpm check (fails on pre-existing unrelated repo issues in extensions/diffs and src/agents/tools/nodes-tool.test.ts)
- pnpm vitest run src/agents/bootstrap-files.test.ts src/infra/heartbeat-runner.model-override.test.ts src/cli/cron-cli.test.ts
- pnpm test:macmini (fails on pre-existing extensions/diffs import errors; touched suites pass)

Co-authored-by: jose-velez <10926182+jose-velez@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 20:13:24 -06:00
Peter Steinberger 0a182bb4d1 docs(changelog): add entries for recent landed Discord PRs 2026-03-02 02:11:21 +00:00
liuxiaopai-ai 042d06a19b Telegram: stop bot on polling teardown 2026-03-02 02:09:52 +00:00
Phineas1500 666a4763ee Telegram: preserve proxy-aware global dispatcher 2026-03-02 02:09:49 +00:00
Peter Steinberger b3990ad58a fix: add changelog for #8805 (thanks @Arthur742Ramos) 2026-03-02 02:09:40 +00:00
Arthur Freitas Ramos 2dcd2f9094 fix: refresh Copilot token before expiry and retry on auth errors
GitHub Copilot API tokens expire after ~30 minutes. When OpenClaw spawns
a long-running subagent using Copilot as the provider, the token would
expire mid-session with no recovery mechanism, causing 401 auth errors.

This commit adds:
- Periodic token refresh scheduled 5 minutes before expiry
- Auth error detection with automatic token refresh and single retry
- Proper timer cleanup on session shutdown to prevent leaks

The implementation uses a per-attempt retry flag to ensure each auth
error can trigger one refresh+retry cycle without creating infinite
retry loops.

🤖 AI-assisted: This fix was developed with GitHub Copilot CLI assistance.
Testing: Fully tested with 3 new unit tests covering auth retry, retry
reset, and timer cleanup scenarios. All 11 auth rotation tests pass.
2026-03-02 02:09:40 +00:00
Peter Steinberger e54ddf6161 fix: add changelog for #19077 (thanks @ayanesakura) 2026-03-02 02:08:27 +00:00
Ayane 5b562e96cb test: add missing ENETRESET test case 2026-03-02 02:08:27 +00:00
Ayane 76ed274aad fix(agents): trigger model failover on connection-refused and network-unreachable errors
Previously, only ETIMEDOUT / ESOCKETTIMEDOUT / ECONNRESET / ECONNABORTED
were recognised as failover-worthy network errors. Connection-level
failures such as ECONNREFUSED (server down), ENETUNREACH / EHOSTUNREACH
(network disconnected), ENETRESET, and EAI_AGAIN (DNS failure) were
treated as unknown errors and did not advance the fallback chain.

This is particularly impactful when a local fallback model (e.g. Ollama)
is configured: if the remote provider is unreachable due to a network
outage, the gateway should fall back to the local model instead of
returning an error to the user.

Add the missing error codes to resolveFailoverReasonFromError() and
corresponding e2e tests.

Closes #18868
2026-03-02 02:08:27 +00:00
YUJIE2002 3b2ed8fe6f fix(telegram): prevent channel-level groups from leaking to all accounts in multi-account setups
In multi-account Telegram configurations, `mergeTelegramAccountConfig()`
performs a shallow merge of channel-level config onto each account. This
causes channel-level `groups` to be inherited by ALL accounts, including
those whose bots are not members of the configured groups.

When a secondary bot attempts to handle group messages for a group it is
not in, the failure disrupts message delivery for all accounts — causing
silent message loss with no errors in logs.

Fix: exclude `groups` from the base spread (like `accounts` already is)
and only apply channel-level groups as fallback in single-account setups
for backward compatibility. Multi-account setups must use account-level
groups config.

Added 5 test cases covering single-account inheritance, multi-account
isolation, account-level priority, and backward compatibility.

Fixes #30673
2026-03-02 02:08:11 +00:00
openperf 8247c25a32 fix(telegram): check chat allowlist before sender allowlist in group policy
When groupPolicy is "allowlist", the sender allowlist empty-entries
guard ran before the chat-level allowlist check. This caused groups
that were explicitly configured in the groups config to be silently
rejected when no allowFrom / groupAllowFrom entries existed.

Move the checkChatAllowlist block before the sender allowlist guard
and introduce a chatExplicitlyAllowed flag that distinguishes a
dedicated group entry (groupConfig is set) from a wildcard-only
match. When the chat is explicitly allowed and no sender entries
exist, skip the sender check entirely — the group ID itself acts
as the authorization.

Fixes #30613.
2026-03-02 02:08:09 +00:00
SidQin-cyber 60f8e832e0 fix(telegram): handle sendVoice caption-too-long by resending without caption
When TTS text exceeds Telegram's 1024-char caption limit, sendVoice
throws "message caption is too long" and the entire reply (voice +
text) is lost. Now catch this specific error, resend the voice note
without caption, then deliver the full text as a separate message.

Closes #30980

Made-with: Cursor
2026-03-02 02:07:57 +00:00
Glucksberg a262a3ea08
fix(docker): ensure agent directory permissions in docker-setup.sh (#28841)
* fix(docker): ensure agent directory permissions in docker-setup.sh

* fix(docker): restrict chown to config-dir mount, not workspace

The previous 'chown -R node:node /home/node/.openclaw' call crossed into
the workspace bind mount on Linux hosts, recursively rewriting ownership
of all user project files in the workspace directory.

Fix: use 'find -xdev' to restrict chown to the config-dir filesystem
only (won't cross bind-mount boundaries). Then separately chown only
the OpenClaw metadata subdirectory (.openclaw/) within the workspace,
leaving the user's project files untouched.

Addresses review comment on PR #28841.
2026-03-01 18:07:34 -08:00
Glucksberg a25a73e707 discord: expose EventQueue listenerTimeout as configurable option (fixes #24458) 2026-03-02 02:06:24 +00:00
dhananjai1729 8629b996a1 fix(discord): restrict token fallback to transport/timeout errors only
Address review feedback: only fall back to token-based ID extraction
on transport/timeout errors (catch block), not on HTTP auth failures
(401/403) which should fail fast to surface credential issues early.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:05:48 +00:00
dhananjai1729 3efd190aca test(discord): add unit tests for parseApplicationIdFromToken
Cover valid tokens, large snowflake IDs exceeding MAX_SAFE_INTEGER,
Bot-prefixed tokens, and various invalid/edge-case inputs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:05:48 +00:00
dhananjai1729 4b2e35ab95 fix(discord): add token-based fallback for application ID resolution
When the Discord API call to /oauth2/applications/@me fails (timeout,
network error), the bot fails to start with "Failed to resolve Discord
application id". Add a fallback that extracts the application ID by
base64-decoding the first segment of the bot token, keeping it as a
string to avoid precision loss for snowflake IDs exceeding
Number.MAX_SAFE_INTEGER (2^53 - 1).

Fixes #29608

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:05:48 +00:00
Pushkar Kathayat 7f4d1b7531 fix(discord): support applied_tags parameter for forum thread creation
Forum channels that require tags fail with "A tag is required" when
creating threads because there was no way to pass tag IDs. Add
appliedTags parameter to the thread-create action so forum posts can
include required tags from the channel's available_tags list.
2026-03-02 02:05:11 +00:00
Ash (Bug Lab) 5b64b96c6c fix(discord): add ackReactionScope channel override + off/none values (#28268) 2026-03-02 02:04:39 +00:00
haosenwang1018 60330e011b fix(discord): log ignored messages from non-allowlisted channels
Closes #30676

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:03:44 +00:00
zerone0x a5f0a9240f
fix(cron): retry rename on EBUSY and fall back to copyFile on Windows
Landed from contributor PR #16932 with additional changelog alignment and verification.
2026-03-01 20:02:24 -06:00
FlamesCN aaa7de45fa
fix(cron): prevent armTimer tight loop when job has stuck runningAtMs (openclaw#29853) thanks @FlamesCN
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: FlamesCN <12966659+FlamesCN@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 19:58:58 -06:00
Peter Steinberger ffe1937b92 fix(cli): set cron run exit code from run outcome (land #31121 by @Sid-Qin)
Landed-from: #31121
Contributor: @Sid-Qin
Co-authored-by: Sid <sidqin0410@gmail.com>
2026-03-02 01:58:39 +00:00
Mark L 9670ccfc41
Control UI/Cron: persist delivery mode none on edit (openclaw#31114) thanks @liuxiaopai-ai
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 19:58:13 -06:00
C. Liao 313a655d13
fix(cron): reject sessionTarget "main" for non-default agents at creation time (openclaw#30217) thanks @liaosvcaf
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: liaosvcaf <51533973+liaosvcaf@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 19:54:53 -06:00
Peter Steinberger e70fc5eb62 fix(nodes): cap screen_record duration to 5 minutes (land #31106 by @BlueBirdBack)
Landed-from: #31106
Contributor: @BlueBirdBack
Co-authored-by: BlueBirdBack <126304167+BlueBirdBack@users.noreply.github.com>
2026-03-02 01:53:20 +00:00
charo 757e09fe43
fix(cron): recover flat patch params for update action and fix schema (openclaw#23221) thanks @charojo
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: charojo <4084797+charojo@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 19:50:51 -06:00
Peter Steinberger a779c2ca6a fix(telegram): skip nullish final text sends (land #30969 by @haosenwang1018)
Landed-from: #30969
Contributor: @haosenwang1018
Co-authored-by: Sense_wang <167664334+haosenwang1018@users.noreply.github.com>
2026-03-02 01:50:25 +00:00
Peter Steinberger dba039f016 fix(doctor): use posix path semantics for linux sd detection 2026-03-02 01:48:14 +00:00
Peter Steinberger 70ee256ae0 fix(routing): treat group/channel peer.kind as equivalent (land #31135 by @Sid-Qin)
Landed-from: #31135
Contributor: @Sid-Qin
Co-authored-by: Sid <sidqin0410@gmail.com>
2026-03-02 01:47:02 +00:00
Mark L e076665e5e
test(cron): add Asia/Shanghai year-regression coverage [AI-assisted] (openclaw#30565) thanks @liuxiaopai-ai
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 19:46:06 -06:00
Peter Steinberger 65e13c7b6e fix(fs): honor unset tools.fs.workspaceOnly default (land #31128 by @SaucePackets)
Landed-from: #31128
Contributor: @SaucePackets
Co-authored-by: SaucePackets <33006469+SaucePackets@users.noreply.github.com>
2026-03-02 01:43:50 +00:00
Mark L f1354869bd
Node install: persist gateway token in service env (#31122)
* Node daemon: persist gateway token env

* changelog: add credits for node gateway token fix

* changelog: credit byungsker for node token service fix

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 17:35:24 -08:00
StingNing ca770622b3
Cron: fix 1/3 timeout on fresh isolated CLI runs (openclaw#30140) thanks @ningding97
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: ningding97 <17723822+ningding97@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 19:34:18 -06:00
Peter Steinberger 949200d7cb test(browser): fix windows download tmp path assertions 2026-03-02 01:32:28 +00:00
Peter Steinberger 68a8a98ab7 fix(acpx): default strict windows wrapper policy on windows 2026-03-02 01:31:32 +00:00
Peter Steinberger f8459ef46c docs(security): document sessions_spawn sandbox=require hardening 2026-03-02 01:29:19 +00:00
Peter Steinberger f53ea0b74b docs(changelog): add entries for PRs #31090 #31105 #31093 #31088 2026-03-02 01:28:58 +00:00
Beer van der Drift feefedfb83
fix: allow docker cli container to connect to gateway (#12504)
* Docker: route CLI through gateway network namespace

* Tests: assert Docker Compose CLI namespace wiring

* Changelog: add Docker Compose CLI connectivity fix

* Docker: pin docker setup gateway mode and bind

* Tests: cover docker setup mode and bind sync

* Docs: clarify Docker LAN vs loopback gateway targeting

* Changelog: expand Docker #12504 targeting note

* Docker: default optional CLAUDE compose vars to empty

* Docs(Docker): document non-interactive compose runs

* Changelog: note docker compose env-noise reduction

* Docker: restore onboarding Tailscale guidance

* Docker: simplify onboarding output and clarify Tailscale

* Docker: harden shared-namespace CLI container

* Docs(Docker): document shared-namespace trust boundary

* Changelog: note docker shared-namespace hardening

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 17:28:35 -08:00
Peter Steinberger 710004e011 fix(security): harden root-scoped writes against symlink races 2026-03-02 01:27:46 +00:00
Peter Steinberger bfeadb80b6 feat(agents): add sessions_spawn sandbox require mode 2026-03-02 01:27:34 +00:00
Peter Steinberger a6a742f3d0 fix(auto-reply): land #31080 from @scoootscooob
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
2026-03-02 01:17:42 +00:00
Peter Steinberger e7cd4bf1bd refactor(web): split trusted and strict web tool fetch paths 2026-03-02 01:14:06 +00:00
Vincent Koc e07c51b045
CLI: avoid plugin preload for health --json route (#31108)
* CLI routes: skip plugin preload for health --json

* CLI routes tests: cover health --json plugin preload
2026-03-01 17:13:58 -08:00
Peter Steinberger 155118751f refactor!: remove versioned system-run approval contract 2026-03-02 01:12:53 +00:00
Frank Yang 1636f7ff5f
fix(gateway): support wildcard in controlUi.allowedOrigins for remote access (#31088)
* fix(gateway): support wildcard in controlUi.allowedOrigins for remote access

* build: regenerate host env security policy swift

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 01:11:24 +00:00
Shawn 654f63e8f8
fix(signal): prevent sentTranscript sync messages from bypassing loop protection (#31093)
* fix(signal): prevent sentTranscript sync messages from bypassing loop protection

Issue: #31084

On daemon restart, sentTranscript sync messages could bypass loop protection
because the syncMessage check happened before the sender validation. This
reorganizes the checks to:

1. First resolve the sender (phone or UUID)
2. Check if the message is from our own account (both phone and UUID)
3. Only skip sync messages from other sources after confirming not own account

This ensures that sync messages from the own account are properly filtered
to prevent self-reply loops, while still allowing messages synced from other
devices to be processed.

Added optional accountUuid config field for UUID-based account identification.

* fix(signal): cover UUID-only own-message loop protection

* build: regenerate host env security policy swift

---------

Co-authored-by: Kevin Wang <kevin@example.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 01:11:22 +00:00
Peter Steinberger b9aa2d436b fix(security): enforce sandbox inheritance for sessions_spawn 2026-03-02 01:11:13 +00:00
不做了睡大觉 6a1eedf10b
fix: deliver subagent completion announces to Slack without invalid thread_ts (#31105)
* fix(subagent): avoid invalid Slack thread_ts for bound completion announces

* build: regenerate host env security policy swift

---------

Co-authored-by: User <user@example.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 01:11:08 +00:00
Frank Yang ed86252aa5
fix: handle CLI session expired errors gracefully instead of crashing gateway (#31090)
* fix: handle CLI session expired errors gracefully

- Add session_expired to FailoverReason type
- Add isCliSessionExpiredErrorMessage to detect expired CLI sessions
- Modify runCliAgent to retry with new session when session expires
- Update agentCommand to clear expired session IDs from session store
- Add proper error handling to prevent gateway crashes on expired sessions

Fixes #30986

* fix: add session_expired to AuthProfileFailureReason and missing log import

* fix: type cli-runner usage field to match EmbeddedPiAgentMeta

* fix: harden CLI session-expiry recovery handling

* build: regenerate host env security policy swift

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-03-02 01:11:05 +00:00
Peter Steinberger a95c8077e8 test(discord): type gateway stop mock params 2026-03-02 01:10:03 +00:00
Peter Steinberger 81ca309ee6 fix(agents): land #31002 from @yfge
Co-authored-by: yfge <geyunfei@gmail.com>
2026-03-02 01:08:58 +00:00
Peter Steinberger 250f9e15f5 fix(agents): land #31007 from @HOYALIM
Co-authored-by: Ho Lim <subhoya@gmail.com>
2026-03-02 01:06:00 +00:00
Peter Steinberger 085c23ce5a fix(security): block private-network web_search citation redirects 2026-03-02 01:05:20 +00:00
Peter Steinberger e1a9ba8400 docs(changelog): credit GHSA-6f6j reporter 2026-03-02 01:04:27 +00:00
Peter Steinberger c823a85302 fix: harden sandbox media reads against TOCTOU escapes 2026-03-02 01:04:01 +00:00
Peter Steinberger 4320cde91d fix(slack): land #31028 from @taw0002
Co-authored-by: taw0002 <webmaster@sodsolutions.com>
2026-03-02 01:03:39 +00:00
Peter Steinberger da80e22d89 fix(tools): land #31015 from @haosenwang1018
Co-authored-by: haosenwang1018 <1293965075@qq.com>
2026-03-02 01:01:02 +00:00
Vincent Koc ac3e1e769b
chore(format): swiftformat host env and exec approvals (#31115) 2026-03-01 17:00:17 -08:00
Shakker 81d600d55e
fix(protocol): regenerate swift gateway models for internalEvents 2026-03-02 00:55:35 +00:00
Peter Steinberger c80f34f0e0 test(discord): type gateway stop mock params 2026-03-02 00:49:27 +00:00
Shakker 4274374297
Tests: type Discord gateway lifecycle wait mock 2026-03-02 00:44:34 +00:00
Peter Steinberger cef5fae0a2 refactor(gateway): dedupe origin seeding and plugin route auth matching 2026-03-02 00:42:22 +00:00
Benedikt Johannes b81e1b902d
Fixes minor security vulnerability (#30948) (#30951)
Merged via squash.

Prepared head SHA: cfbe5fe830
Co-authored-by: benediktjohannes <253604130+benediktjohannes@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-03-02 00:38:01 +00:00
Peter Steinberger dc816b84ea refactor(matrix): unify startup + split monitor config flow 2026-03-02 00:37:09 +00:00
Vincent Koc f696b64b51
Doctor: warn when Linux state dir is on SD/eMMC mounts (#31033)
* Doctor state: warn on Linux SD or eMMC state mounts

* Doctor tests: cover Linux SD or eMMC state mount detection

* Docs doctor: document Linux SD or eMMC state warning

* Changelog: add Linux SD or eMMC doctor warning

* Update CHANGELOG.md

* Doctor: escape mountinfo control chars in SD warning

* Doctor tests: cover escaped mountinfo control chars
2026-03-01 16:36:01 -08:00
Peter Steinberger 412eabc42b fix(session): retire stale dm main route after dmScope migration (#31010) 2026-03-02 00:33:54 +00:00
Peter Steinberger 68832f203e refactor(diagnostics): hot-reload stuck warning threshold 2026-03-02 00:32:33 +00:00
Peter Steinberger fbd832d64f refactor(config): share byte-size parsing for memory flush 2026-03-02 00:32:33 +00:00
Peter Steinberger 9e727893ff refactor(session): consolidate transcript snapshot reads 2026-03-02 00:32:33 +00:00
Peter Steinberger 3a68c56264 refactor(security): unify webhook guardrails across channels 2026-03-02 00:31:42 +00:00
Peter Steinberger 58659b931b fix(gateway): enforce owner boundary for agent runs 2026-03-02 00:27:44 +00:00
Peter Steinberger 9005e8bc0a refactor(gateway): unify metadata canonicalization + platform rules 2026-03-02 00:26:36 +00:00
Peter Steinberger 0c0f556927 fix(discord): unify reconnect watchdog and land #31025/#30530
Landed follow-up intent from contributor PR #31025 (@theotarr) and PR #30530 (@liuxiaopai-ai).

Co-authored-by: theotarr <theotarr@users.noreply.github.com>
Co-authored-by: liuxiaopai-ai <liuxiaopai-ai@users.noreply.github.com>
2026-03-02 00:24:15 +00:00
Peter Steinberger 0eac494db7 fix(gateway): harden node metadata policy classification 2026-03-02 00:15:34 +00:00
Peter Steinberger 84d0a794ec fix: harden matrix startup errors + add regressions (#31023) (thanks @efe-arv) 2026-03-02 00:15:10 +00:00
efe-arv 235ed71e94 fix: handle late client.start() failures via single catch handler
The .catch() handler now covers both early and late failures:
- Within 2s: sets settled=true, startup throws to caller
- After 2s: sets params.state.started=false so subsequent
  resolveSharedMatrixClient() calls detect the dead client

Removed redundant second .catch() — single handler covers all cases.
2026-03-02 00:15:10 +00:00
efe-arv 4f9daf9821 fix: propagate client.start() errors to caller instead of swallowing
Codex review feedback: ensureSharedClientStarted now throws the error
from client.start() if it rejects during the 2s grace window, so
resolveSharedMatrixClient() properly reports failure (e.g. bad token,
unreachable homeserver) instead of leaving the provider in a
running-but-not-syncing state.
2026-03-02 00:15:10 +00:00
efe-arv 8884f99c92 fix: address review feedback — handle start failure, remove placeholder URL
- Don't mark client as started if client.start() errors during init
- Remove placeholder issue URL from comment
2026-03-02 00:15:10 +00:00
efe-arv f66f563c1a fix(matrix): fix multiple Conduit compatibility issues preventing message delivery
## Changes

### 1. Fix client.start() hanging forever (shared.ts)
The bot-sdk's `client.start()` returns a promise that never resolves
(infinite sync loop). The plugin awaited it, blocking the entire provider
startup — `logged in as` never printed, no messages were processed.

Fix: fire-and-forget with error handler + 2s initialization delay.

### 2. Fix DM false positive for 2-member rooms (direct.ts)
`memberCount === 2` heuristic misclassified explicitly configured group
rooms as DMs when only bot + one user were joined. Messages were routed
through DM policy and silently dropped.

Fix: remove member count heuristic; only trust `m.direct` account data
and `is_direct` room state flag.

Ref: #20145

### 3. Prevent duplicate event listener registration (events.ts)
When both bundled channel plugin and extension load, listeners were
registered twice on the same shared client, causing inconsistent state.

Fix: WeakSet guard to skip registration if client already has listeners.

Ref: #18330

### 4. Add startup grace period (index.ts)
`startupGraceMs = 0` dropped messages timestamped during async setup.
Especially problematic with Conduit which retries on `M_NOT_FOUND`
during filter creation.

Fix: 5-second grace period.

### 5. Fix room ID case sensitivity with Conduit (index.ts)
Room IDs (`!xyz`) without `:server` suffix failed the
`includes(':')` check and were sent to `resolveMatrixTargets`, which
called Conduit's `resolveRoom` — returning lowercased IDs. The bot-sdk
emits events with original-case IDs, causing config lookup mismatches
and reply delivery failures (`M_UNKNOWN: non-create event for room of
unknown version`).

Fix: treat `!`-prefixed entries as room IDs directly (skip resolution).
Only resolve `#alias:server` entries.

## Testing

Tested with Conduit homeserver (lightweight Rust Matrix server).
All fixes verified with gateway log tracing:
- `logged in as @arvi:matrix.local` — first successful login
- `room.message` events fire and reach handler
- Room config matching returns `allowed: true`
- Agent generates response and delivers it to Matrix room
2026-03-02 00:15:10 +00:00
Peter Steinberger 43cad8268d fix(security): harden webhook memory guards across channels 2026-03-02 00:12:05 +00:00
Peter Steinberger 1c8ae978d2 test(lobster): preserve execFile in child_process mock 2026-03-02 00:10:51 +00:00
Peter Steinberger 53d10f8688 fix(gateway): land access/auth/config migration cluster
Land #28960 by @Glucksberg (Tailscale origin auto-allowlist).
Land #29394 by @synchronic1 (allowedOrigins upgrade migration).
Land #29198 by @Mariana-Codebase (plugin HTTP auth guard + route precedence).
Land #30910 by @liuxiaopai-ai (tailscale bind/config.patch guard).

Co-authored-by: Glucksberg <markuscontasul@gmail.com>
Co-authored-by: synchronic1 <synchronic1@users.noreply.github.com>
Co-authored-by: Mariana Sinisterra <mariana.data@outlook.com>
Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
2026-03-02 00:10:51 +00:00
Peter Steinberger 8e6b3ade3e docs(changelog): record session lifecycle and diagnostics fixes 2026-03-02 00:07:47 +00:00
Peter Steinberger 41cc46bbb4 feat(diagnostics): add configurable stuck-session warning threshold 2026-03-02 00:07:29 +00:00
Peter Steinberger d729ab2150 fix(session): harden usage accounting and memory flush recovery 2026-03-02 00:07:29 +00:00
Vincent Koc ee96e1751e
docs(changelog): add missing contributor credits for 2026.3.1 (#31079)
* changelog: credit @BUGKillerKing for #29315

* changelog: credit @liuweifly for #14674

* changelog: credit @Sid-Qin for #29709

* changelog: credit @lailoo for #21808

* changelog: credit @openperf for #26259

* changelog: credit @icesword0760 for #28959

* changelog: credit @cowboy129 for #28529

* changelog: credit @yfge for #17798

* changelog: credit @kcinzgg for #27325

* changelog: credit @guoqunabc for #28494

* changelog: credit @WilsonLiu95 for #12755

* changelog: credit @qiangu for #18529

* changelog: credit @lailoo for unreleased #27616

* changelog: credit @tumf for unreleased #18642

* changelog: normalize unreleased #24789 credit handle

* changelog: fill unreleased #24435 credit

* changelog: fill unreleased #25090 credit

* changelog: fill unreleased #29098 credit (entry 1)

* changelog: fill unreleased #29098 credit (entry 2)

* changelog: credit @liuxiaopai-ai for unreleased #30567

* changelog: credit @graysurf for unreleased #23169

* changelog: credit @pablohrcarvalho for unreleased #10686

* changelog: credit @Glucksberg for unreleased #21715

* changelog: credit @liuxiaopai-ai for unreleased #30586

* changelog: add missing credits for 2026.2.26

* changelog: add missing credits for 2026.2.25

* changelog: add missing credits for 2026.2.24

* changelog: add missing credits for 2026.2.23

* changelog: add missing credits for 2026.2.22
2026-03-01 16:04:55 -08:00
Peter Steinberger a62d55b283 test(discord): cover DM command decision flow 2026-03-02 00:00:05 +00:00
Peter Steinberger 75596e9370 refactor(discord): unify DM command auth handling 2026-03-02 00:00:05 +00:00
Peter Steinberger 12c1257023 fix(acpx): share windows wrapper resolver and add strict hardening mode 2026-03-01 23:57:06 +00:00
Peter Steinberger 881ac62005 test(discord): stabilize model picker timeout assertions 2026-03-01 23:53:07 +00:00
Peter Steinberger ee03ade0d6 fix(agents): harden tool-name normalization and transcript repair
Landed from contributor PRs #30620 and #30735 by @Sid-Qin, plus #30881 by @liuxiaopai-ai.

Co-authored-by: SidQin-cyber <sidqin0410@gmail.com>
Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
2026-03-01 23:51:54 +00:00
Peter Steinberger 50e2674dfc fix(discord): unify dm command auth gating 2026-03-01 23:50:24 +00:00
Peter Steinberger 577becf1ad fix(plugins): prioritize bundled duplicates in auto-discovery
Landed from contributor PR #29710 by @Sid-Qin.

Co-authored-by: SidQin-cyber <sidqin0410@gmail.com>
2026-03-01 23:48:30 +00:00
Peter Steinberger 5056b6438d fix(discord): harden reconnect recovery and preserve message delivery
Landed from contributor PR #29508 by @cgdusek.

Co-authored-by: Charles Dusek <cgdusek@gmail.com>
2026-03-01 23:46:07 +00:00
Peter Steinberger a0d2f6e4fe docs(changelog): note skills security hardening 2026-03-01 23:45:41 +00:00
Peter Steinberger 23f434f98d fix(skills): constrain plugin skill paths 2026-03-01 23:45:41 +00:00
Peter Steinberger 4614222572 fix(skills): validate installer metadata specs 2026-03-01 23:45:41 +00:00
edincampara 577f2fa540
fix(docker): harden /app/extensions permissions to 755 (#30191)
* fix(docker): harden /app/extensions permissions to 755

Bundled extension directories shipped as world-writable (mode 777)
in the Docker image. The plugin security scanner blocks any world-
writable path with:

  WARN: blocked plugin candidate: world-writable path
        (/app/extensions/memory-core, mode=777)

Add chmod -R 755 /app/extensions in the final USER root RUN step so
all bundled extensions are readable but not world-writable. This runs
as root before switching back to the node user, matching the pattern
already used for chmod 755 /app/openclaw.mjs.

Fixes #30139

* fix(docker): normalize plugin and agent path permissions

* docs(changelog): add docker permissions entry for #30191

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-03-01 15:45:21 -08:00
Peter Steinberger 9e6e7a3d69 fix(acpx): harden windows cmd wrapper spawning 2026-03-01 23:44:36 +00:00
Peter Steinberger 13bb80df9d fix(agents): land #20840 cross-channel message-tool actions from @altaywtf
Include scoped cross-channel action/description behavior, regression tests, changelog note, and make Ollama discovery tests URL-scoped to avoid env-dependent fetch interference.

Co-authored-by: Altay <altay@hey.com>
2026-03-01 23:37:55 +00:00
Peter Steinberger 912ddba81e fix(macos): harden exec approvals socket path and permissions 2026-03-01 23:37:11 +00:00
Peter Steinberger 6c5633598e fix(security): harden clawlog command execution 2026-03-01 23:33:13 +00:00
Peter Steinberger ccb415b69a fix: align ACP permission docs defaults (#31044) (thanks @barronlroth) 2026-03-01 23:30:39 +00:00
Barron Roth bed1cb9600 docs(acp): add permission configuration section and troubleshooting entries
Document permissionMode and nonInteractivePermissions plugin config
keys for the acpx backend. Add troubleshooting entries for:
- Permission prompt errors in non-interactive ACP sessions
- Silent session failures from swallowed permission errors
- Stalled ACP sessions that never report completion

Relates to #29195

AI-assisted (lightly tested)
2026-03-01 23:30:39 +00:00
Peter Steinberger 6a80e9db05 fix(browser): harden writable output paths 2026-03-01 23:25:13 +00:00
Peter Steinberger 51bccaf988 chore(changelog): note internal events and ingress hardening 2026-03-01 23:12:09 +00:00
Peter Steinberger b99666a47a fix(security): harden inbound metadata sentinel stripping 2026-03-01 23:11:48 +00:00
Peter Steinberger 8e48520d74 fix(channels): align command-body parsing sources 2026-03-01 23:11:48 +00:00
Peter Steinberger 4c43fccb3e feat(agents): use structured internal completion events 2026-03-01 23:11:48 +00:00
Peter Steinberger 738dd9aa42 fix(agents): type openai websocket warmup passthrough 2026-03-01 23:10:08 +00:00
Vincent Koc eb20793550
Docs: add all unlisted docs routes to navigation (#31027)
* Docs: add missing platform pages to nav

* Docs: include all unlisted docs routes in nav

* Docs nav: classify routes by area and remove catch-all groups

* Docs nav: remove ja-JP AGENTS page entry

* Docs ja-JP: remove AGENTS translation workspace page

* Docs nav: remove refactor plans group

* Docs nav: remove .dev template pages

* Docs nav: remove operations hubs group
2026-03-01 15:09:35 -08:00
Peter Steinberger 0f5348acb2 test(config): reject discord open DM with empty allowFrom 2026-03-01 23:08:37 +00:00
Peter Steinberger d1615eb35f feat(openai): add websocket warm-up with configurable toggle 2026-03-01 22:45:03 +00:00
Agent bc9f357ad7 test: fix fetch mock typing casts 2026-03-01 22:44:28 +00:00
Agent 002539c01e fix(security): harden sandbox novnc observer flow 2026-03-01 22:44:28 +00:00
Peter Steinberger 4ab13eca4d test(agents): port OpenAI websocket coverage from #24911
Co-authored-by: Jonathan Jing <achillesjing@gmail.com>
2026-03-01 22:38:56 +00:00
Vincent Koc eee870576d
doctor: warn on macOS cloud-synced state directories (#31004)
* Doctor: detect macOS cloud-synced state directories

* Doctor tests: cover cloud-synced macOS state detection

* Docs: note cloud-synced state warning in doctor guide

* Docs: recommend local macOS state dir placement

* Changelog: add macOS cloud-synced state dir warning

* Changelog: credit macOS cloud state warning PR

* Doctor state: anchor cloud-sync roots to macOS home

* Doctor tests: cover OPENCLAW_HOME cloud-sync override

* Doctor state: prefer resolved target for cloud detection

* Doctor tests: cover local-target cloud symlink case
2026-03-01 14:35:46 -08:00
Agent 063c4f00ea docs: clarify Anthropic context1m long-context requirements 2026-03-01 22:35:26 +00:00
Agent a374325fc2 docs(security): clarify local link-priming reports as out-of-scope 2026-03-01 22:34:32 +00:00
Peter Steinberger 8da86f6995 chore(changelog): note openai websocket-first streaming 2026-03-01 22:33:21 +00:00
Peter Steinberger 7ced38b5ef feat(agents): make openai responses websocket-first with fallback 2026-03-01 22:32:37 +00:00
Vincent Koc 38da2d076c
CLI: add root --help fast path and lazy channel option resolution (#30975)
* CLI argv: add strict root help invocation guard

* Entry: add root help fast-path bootstrap bypass

* CLI context: lazily resolve channel options

* CLI context tests: cover lazy channel option resolution

* CLI argv tests: cover root help invocation detection

* Changelog: note additional startup path optimizations

* Changelog: split startup follow-up into #30975 entry

* CLI channel options: load precomputed startup metadata

* CLI channel options tests: cover precomputed metadata path

* Build: generate CLI startup metadata during build

* Build script: invoke CLI startup metadata generator

* CLI routes: preload plugins for routed health

* CLI routes tests: assert health plugin preload

* CLI: add experimental bundled entry and snapshot helper

* Tools: compare CLI startup entries in benchmark script

* Docs: add startup tuning notes for Pi and VM hosts

* CLI: drop bundled entry runtime toggle

* Build: remove bundled and snapshot scripts

* Tools: remove bundled-entry benchmark shortcut

* Docs: remove bundled startup bench examples

* Docs: remove Pi bundled entry mention

* Docs: remove VM bundled entry mention

* Changelog: remove bundled startup follow-up claims

* Build: remove snapshot helper script

* Build: remove CLI bundle tsdown config

* Doctor: add low-power startup optimization hints

* Doctor: run startup optimization hint checks

* Doctor tests: cover startup optimization host targeting

* Doctor tests: mock startup optimization note export

* CLI argv: require strict root-only help fast path

* CLI argv tests: cover mixed root-help invocations

* CLI channel options: merge metadata with runtime catalog

* CLI channel options tests: assert dynamic catalog merge

* Changelog: align #30975 startup follow-up scope

* Docs tests: remove secondary-entry startup bench note

* Docs Pi: add systemd recovery reference link

* Docs VPS: add systemd recovery reference link
2026-03-01 14:23:46 -08:00
Agent dcd19da425 refactor: simplify sandbox boundary open flow 2026-03-01 21:49:42 +00:00
Agent 3be1343e00 fix: tighten sandbox mkdirp boundary checks (#30610) (thanks @glitch418x) 2026-03-01 21:41:47 +00:00
glitch418x 687f5779d1 sandbox: allow directory boundary checks for mkdirp 2026-03-01 21:41:47 +00:00
Bob 4fc7ecf088
ACP: force sessions_spawn as the only harness thread creation path (#30957)
* ACP: enforce sessions_spawn-only thread creation for harness spawns

* skills(acpx): require acp-router preflight for ACP thread spawns

* fix: enforce ACP thread spawn via sessions_spawn only (#30957) (thanks @dutifulbob)

---------

Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
2026-03-01 22:41:06 +01:00
Agent e4d22fb07a fix(browser): fail closed browser auth bootstrap 2026-03-01 21:40:16 +00:00
Agent 3a93a7bb1e fix(security): enforce auth for abort triggers and models 2026-03-01 21:30:07 +00:00
Peter Steinberger c89836a251 test: harden flaky timeout and resolver specs 2026-03-01 21:30:07 +00:00
Sid c1428e8df9
fix(gateway): prevent /api/* routes from returning SPA HTML when basePath is empty (#30333)
Merged via squash.

Prepared head SHA: 12591f304e
Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-01 22:23:54 +01:00
Vincent Koc e6049345db
fix(telegram): preserve HTTP proxy env in global dispatcher workaround (#29940)
* fix(telegram): preserve HTTP proxy env in global dispatcher workaround

* telegram: document request-scoped proxy dispatcher constraint

* telegram: assert proxy path never mutates global dispatcher

* changelog: credit telegram proxy env regression fix

---------

Co-authored-by: Rylen Anil <rylen.anil@gmail.com>
2026-03-01 13:21:01 -08:00
Agent e7cafed424 chore(release): bump version to 2026.3.1 2026-03-01 21:14:17 +00:00
Vincent Koc 94a5d28d26
CI: remove Vitest JSON report artifacts (#30976)
* CI: remove vitest JSON report upload steps

* Tests: stop injecting vitest JSON reporter

* Tests: remove vitest slowest report script
2026-03-01 13:03:06 -08:00
Vincent Koc 79f818e8a2 Status scan: guard deferred promise rejections 2026-03-01 12:56:56 -08:00
Vincent Koc 125ea585dd CLI routes tests: assert status plugin preload 2026-03-01 12:56:56 -08:00
Vincent Koc 266084f4c8 CLI routes: preload plugins for status security parity 2026-03-01 12:56:56 -08:00
Vincent Koc 4b027927cf Changelog: credit startup performance reports 2026-03-01 12:56:56 -08:00
Vincent Koc 23c6e9836e Status scan: overlap non-JSON async checks 2026-03-01 12:56:56 -08:00
Vincent Koc c161e141f3 Docs tests: add CLI startup benchmark usage 2026-03-01 12:56:56 -08:00
Vincent Koc bdd59e0149 Scripts: add CLI startup benchmark harness 2026-03-01 12:56:56 -08:00
Vincent Koc 08ea7f0cf6 Docs VPS: add startup tuning for small hosts 2026-03-01 12:56:56 -08:00
Vincent Koc 86e4f3e7e2 Docs Pi: add startup tuning for compile cache 2026-03-01 12:56:56 -08:00
Vincent Koc 8c4071f36a Entry: enable Node compile cache on startup 2026-03-01 12:56:56 -08:00
Vincent Koc e4b4fd5ce8 Entry: avoid top-level return in version fast-path 2026-03-01 12:56:56 -08:00
Vincent Koc 7aa9267d00 Status scan: fix JSON channels result typing 2026-03-01 12:56:56 -08:00
Vincent Koc ba0aa3cfae Status scan: add parallel JSON fast path 2026-03-01 12:56:56 -08:00
Vincent Koc b0a73ae773 Status command: parallelize JSON security audit 2026-03-01 12:56:56 -08:00
Vincent Koc 07da843378 CLI argv: test root version fast-path detection 2026-03-01 12:56:56 -08:00
Vincent Koc 153adc4c8f Entry: fast-path root version command 2026-03-01 12:56:56 -08:00
Vincent Koc 86a91cc01a CLI argv: detect root-only version invocation 2026-03-01 12:56:56 -08:00
Vincent Koc 3c4cdf72c9 CLI routes: test conditional plugin preload behavior 2026-03-01 12:56:56 -08:00
Vincent Koc 22653c0e27 Status scan: skip channel table work in JSON mode 2026-03-01 12:56:56 -08:00
Vincent Koc af12e7bdec CLI route: support argv-aware plugin preloading 2026-03-01 12:56:56 -08:00
Vincent Koc 5e061fd8b9 CLI routes: skip plugin preload for health 2026-03-01 12:56:56 -08:00
Ben Gitter 5d7314db22
fix(control-ui): include basePath in default WebSocket URL (#30228)
Merged via squash.

Prepared head SHA: a56d8d441c
Co-authored-by: gittb <8284364+gittb@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-03-01 21:01:43 +01:00
Onur Solmaz 907c09e1d5 fix: add changelog entry for ACPX stream update (#30036) (thanks @osolmaz) 2026-03-01 20:39:24 +01:00
Onur b12c909ea2 ACPX: pin 0.1.15 and tolerate missing --version in health check 2026-03-01 20:39:24 +01:00
Onur f81c2e75d2 Tests: make acpx config path assertions cross-platform 2026-03-01 20:39:24 +01:00
Onur ac5d7ee4cd Tests: normalize HOME expansion assertion on Windows 2026-03-01 20:39:24 +01:00
Onur 18033d3962 Cron+Slack: fix cooldown omission and cache cap enforcement 2026-03-01 20:39:24 +01:00
Onur 8292401719 ACP: rename stream char limits to output/sessionUpdate 2026-03-01 20:39:24 +01:00
Onur 4664d13857 Docs: remove temp ACP planning files 2026-03-01 20:39:24 +01:00
Onur 053e5eb506 ACP: remove maxMetaEventsPerTurn limit 2026-03-01 20:39:24 +01:00
Onur 6c08652c8d Tests: use preferred tmp dir in acpx runtime fixtures 2026-03-01 20:39:24 +01:00
Onur ca31683ca3 Tests: fix dispatch-acp mock typings for tsgo 2026-03-01 20:39:24 +01:00
Onur 63e607db9b ACPX: pin plugin dependency to 0.1.14 2026-03-01 20:39:24 +01:00
Onur f4538b22f7 ACP: fix projector dedupe regressions 2026-03-01 20:39:24 +01:00
Onur be73eb28b3 ACP: improve live text batching readability 2026-03-01 20:39:24 +01:00
Onur dd2fcade3e ACP: make live mode flush incremental chunks 2026-03-01 20:39:24 +01:00
Onur 43c57005a6 ACP: start typing lifecycle at turn start and harden delivery 2026-03-01 20:39:24 +01:00
Onur c8b958e573 ACP: add hidden-boundary separator for hidden tool events 2026-03-01 20:39:24 +01:00
Onur acd6ddb829 ACP: hide tool_call tags by default 2026-03-01 20:39:24 +01:00
Onur 5232f96d59 Agents: use tool emoji for ACP tool_call summaries 2026-03-01 20:39:24 +01:00
Onur 4324d84edd Docs: add ACP thread tool-editing final-only implementation plan 2026-03-01 20:39:24 +01:00
Onur c3a1fe01ae ACP: make final_only defer all projected output 2026-03-01 20:39:24 +01:00
Onur 4a82012461 ACP: default stream delivery to final_only 2026-03-01 20:39:24 +01:00
Onur 4e2efaf659 ACP: simplify stream config to repeatSuppression 2026-03-01 20:39:24 +01:00
Onur 79fcc8404e Scripts: add openclaw driver mode to discord ACP smoke 2026-03-01 20:39:24 +01:00
Onur 752398a6ba Refactor: split ACP dispatch delivery and settings 2026-03-01 20:39:24 +01:00
Onur 54ed2efc20 Tests: complete ACP meta dedupe coverage 2026-03-01 20:39:24 +01:00
Onur 9cfc630be9 ACPX: sync main ACP parser changes onto configurable-command branch 2026-03-01 20:39:24 +01:00
Onur cf3e4d2aef Docs: restore ACP meta-event dedupe implementation plan 2026-03-01 20:39:24 +01:00
Onur 2466a9bb13 ACP: carry dedupe/projector updates onto configurable acpx branch 2026-03-01 20:39:24 +01:00
Onur f88bc09f85 ACPX: ignore replayed updates outside active prompt 2026-03-01 20:39:24 +01:00
Onur d669b27a45 ACPX extension: split ACP stream parser and test fixtures 2026-03-01 20:39:24 +01:00
Onur bdc355d0b0 ACPX extension: parse pure ACP JSON-RPC stream 2026-03-01 20:39:24 +01:00
Onur 9cae5107d1 ACPX extension: support acpx any-version probe via --help 2026-03-01 20:39:24 +01:00
Onur 921ebfb25e ACPX plugin: allow configurable command and expected version 2026-03-01 20:39:24 +01:00
Glucksberg 134296276a
fix(memory): discard stdout for qmd update/embed to prevent output cap failure (openclaw#28900) thanks @Glucksberg
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 12:16:50 -06:00
pablohrcarvalho 11d34700c0
fix(slack): use thread-level sessions for channels to prevent context mixing (#10686)
* fix(slack): use thread-level sessions for channels to prevent context mixing

All messages in a Slack channel share a single session, causing context from
different threads to mix together. When users have multiple conversations in
different threads of the same channel, the agent sees combined context from
all threads, leading to confused responses.

Session key was: `slack:channel:${channelId}` (no thread identifier)

1. **Thread-level session keys**: Each message in channels/groups now gets
   its own session based on thread_ts:
   - Thread replies: use the parent thread's ts
   - New messages: use the message's own ts (becomes thread root)
   - DMs: unchanged (no thread-level sessions needed)

   New session key format: `slack:channel:${channelId}🧵${threadTs}`

2. **Increased thread cache TTL**: Changed from 60 seconds to 6 hours.
   Users often pause conversations, and the short TTL caused unnecessary
   API calls and thread resolution failures.

3. **Increased cache size**: Changed from 500 to 10,000 entries to support
   busy workspaces with many active threads.

1. Create two threads in the same Slack channel
2. In Thread A: tell the bot your name is "Alice" and ask about "billing"
3. In Thread B: tell the bot your name is "Bob" and ask about "API"
4. Reply in Thread A and ask "what's my name?" - should say "Alice"
5. Check sessions: each thread should have a unique session key with 🧵 suffix

Fixes context bleed issues related to #758

* fix(slack): also update resolveSlackSystemEventSessionKey for thread-level sessions

The context.ts file has a separate function for resolving session keys for
system events (reactions, file uploads, etc.). This also needs to support
thread-level sessions to ensure all Slack events route to the correct
thread-specific session.

Added threadTs and messageTs parameters to resolveSlackSystemEventSessionKey
and updated the implementation to use thread-level keys for channels/groups.

* fix(slack): preserve DM thread sessions for thread replies

The previous change broke thread-level sessions for DMs that have threads.
DMs with parent_user_id should still get thread-level sessions.

- For channels/groups: always use thread-level sessions
- For DMs: use thread-level sessions only when isThreadReply is true

* fix(slack): use thread-level sessionKey for previousTimestamp

Fixes the bug where previousTimestamp was read from the base channel
session key (route.sessionKey) instead of the resolved thread-level
sessionKey. This caused the elapsed-time calculation in the inbound
envelope to always pull from the channel session rather than the
thread session.

Also adds regression tests for the thread-level session key behavior.

Co-authored-by: Tony Dehnke <tdehnke@gmail.com>

* fix(slack): narrow #10686 to surgical thread-session patch

* test(slack): satisfy context/account typing in thread-session tests

* docs(changelog): record surgical slack thread-session fix

---------

Co-authored-by: Pablo Carvalho <pablo@telnyx.com>
Co-authored-by: Tony Dehnke <tdehnke@gmail.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 12:04:57 -06:00
Tak Hoffman a179a0d371
Slack onboarding: improve token help note with manifest option (openclaw#30846) thanks @yzhong52
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: yzhong52 <3712071+yzhong52@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:57:30 -06:00
msvechla 2c5b898eea
feat(slack): add download-file action for on-demand file attachment access (#24723)
* feat(slack): add download-file action for on-demand file attachment access

Adds a new `download-file` message tool action that allows the agent to
download Slack file attachments by file ID on demand. This is a prerequisite
for accessing images posted in thread history, where file attachments are
not automatically resolved.

Changes:
- Add `files` field to `SlackMessageSummary` type so file IDs are
  visible in message read results
- Add `downloadSlackFile()` to fetch a file by ID via `files.info`
  and resolve it through the existing `resolveSlackMedia()` pipeline
- Register `download-file` in `CHANNEL_MESSAGE_ACTION_NAMES`,
  `MESSAGE_ACTION_TARGET_MODE`, and `listSlackMessageActions`
- Add `downloadFile` dispatch case in `handleSlackAction`
- Wire agent-facing `download-file` → internal `downloadFile` in
  `handleSlackMessageAction`

Closes #24681

* style: fix formatting in slack-actions and actions

* test(slack): cover download-file action path

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:45:05 -06:00
graysurf eddaf19478
fix(slack): guard allow-from store resolution in monitor auth (#21967) 2026-03-01 11:42:58 -06:00
Oleksandr Zakotyanskyi 2a409bbba0
fix(slack): replace files.uploadV2 with 3-step upload flow to fix missing_scope error (#17558)
* fix(slack): replace files.uploadV2 with 3-step upload flow

files.uploadV2 from @slack/web-api internally calls the deprecated
files.upload endpoint, which fails with missing_scope even when
files:write is correctly granted in the bot token scopes.

Replace with Slack's recommended 3-step upload flow:
1. files.getUploadURLExternal - get presigned URL + file_id
2. fetch(upload_url) - upload file content
3. files.completeUploadExternal - finalize & share to channel/thread

This preserves all existing behavior including thread replies via
thread_ts and caption via initial_comment.

* fix(slack): harden external upload flow and tests

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:37:18 -06:00
Sid 39a45121d9
fix(discord,slack): add SSRF policy for media downloads in proxy environments (#25475)
* fix(discord,slack): add SSRF policy for media downloads in proxy environments

Discord and Slack media downloads (attachments, stickers, forwarded
images) call fetchRemoteMedia without any ssrfPolicy. When running
behind a local transparent proxy (Clash, mihomo, Shadowrocket) in
fake-ip mode, DNS returns virtual IPs in the 198.18.0.0/15 range,
which the SSRF guard blocks.

Add per-channel SSRF policy constants—matching the pattern already
applied to Telegram on main—that allowlist known CDN hostnames and
set allowRfc2544BenchmarkRange: true.

Refs #25355, #25322

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(slack): keep raw-fetch allowlist line anchors stable

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:30:10 -06:00
Jc Miñarro b9e07ad7b4
docs(slack): add missing DM scopes to manifest (openclaw#29999) thanks @JcMinarro
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: JcMinarro <4047514+JcMinarro@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:25:52 -06:00
calder-sandy 93ac2b43fb
feat(slack): per-thread session isolation for DM auto-threading (#26849)
* feat(slack): create thread sessions for auto-threaded DM messages

When replyToMode="all", every top-level message starts a new Slack thread.
Previously, only subsequent replies in that thread got an isolated session
(via 🧵<threadTs> suffix). The initial message fell back to the base
DM session, mixing context across unrelated conversations.

Now, when replyToMode="all" and a message is not already a thread reply,
the message's own ts is used as the threadId for session key resolution.
This gives the initial message AND all subsequent thread replies the same
isolated session.

This enables per-thread session isolation for Slack DMs — each new message
starts its own thread and session, keeping conversations separate.

* Slack: fix auto-thread session key mode check and add changelog

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:24:45 -06:00
Jin Kim 746688ddc9
Slack: redact and cap interaction system events (#28982) 2026-03-01 11:24:43 -06:00
tumf e0571399ac
fix(slack): reject HTML responses when downloading media (#4665)
* fix(slack): reject HTML responses when downloading media

Slack sometimes returns HTML login pages instead of binary media when
authentication fails or URLs expire. This change detects HTML responses
by checking content-type header and buffer content, then skips to the
next available file URL.

* fix: format import order and add braces to continue statement

* chore: format Slack media tests

* chore: apply formatter to Slack media tests

* fix(slack): merge auth-header forwarding and html media guard

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:20:25 -06:00
Glucksberg 6dbbc58a8d
fix(slack): use SLACK_USER_TOKEN when connecting to Slack (#28103)
* fix(slack): use SLACK_USER_TOKEN when connecting to Slack (closes #26480)

* test(slack): fix account fixture typing for user token source

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:05:35 -06:00
dan bachelder 46da76e267
fix(slack): honor replyToModeByChatType when ThreadLabel exists (#26251)
* fix(slack): honor direct replyToMode when thread label exists

ThreadLabel is a session/conversation label, not a reliable indicator
of an actual Slack thread reply. Using it to force replyToMode="all"
overrides replyToModeByChatType.direct="off" in DMs.

Switch to MessageThreadId which indicates a real thread target is
available, preserving expected behavior: thread replies stay threaded,
normal DMs respect the configured mode.

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

* Slack: add changelog for threading tool context fix

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 11:02:06 -06:00
Dennis Rankin a28a4b1b61
feat: detect stale Slack sockets and auto-restart (#30153)
* feat: detect stale Slack sockets and auto-restart

Slack Socket Mode connections can silently stop delivering events while
still appearing connected (health checks pass, WebSocket stays open).
This "half-dead socket" problem causes messages to go unanswered.

This commit adds two layers of protection:

1. **Event liveness tracking**: Every inbound Slack event (messages,
   reactions, member joins/leaves, channel events, pins) now calls
   `setStatus({ lastEventAt, lastInboundAt })` to update the channel
   account snapshot with the timestamp of the last received event.

2. **Health monitor stale socket detection**: The channel health monitor
   now checks `lastEventAt` against a configurable threshold (default
   30 minutes). If a channel has been running longer than the threshold
   and hasn't received any events in that window, it is flagged as
   unhealthy and automatically restarted — the same way disconnected
   or crashed channels are already handled.

The restart reason is logged as "stale-socket" for observability, and
the existing cooldown/rate-limit logic (3 restarts/hour max) prevents
restart storms.

* Slack: gate liveness tracking to accepted events

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 10:58:21 -06:00
lailoo 43ddb41354
fix(slack): extract attachment text for bot messages with empty text (#27616) (#27642)
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini
2026-03-01 10:49:51 -06:00
Miguel Miranda Dias 949faff5ce
fix(slack): reconnect socket mode after disconnect (#27232)
* fix(slack): reconnect socket mode after disconnect

* fix(slack): avoid orphaned disconnect waiters on start failure

* docs(changelog): record slack socket reconnect reliability fix

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 10:42:45 -06:00
graysurf a54b85822c
Handle transient Slack request errors without crashing the gateway (openclaw#23787) thanks @graysurf
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: graysurf <10785178+graysurf@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 10:42:42 -06:00
Luis Conde bd78a74298
feat(slack): track thread participation for auto-reply without @mention (#29165)
* feat(slack): track thread participation for auto-reply without @mention

* fix(slack): scope thread participation cache by accountId and capture actual reply thread ts

* fix(slack): capture reply thread ts from all delivery paths and only after success

* Slack: add changelog for thread participation cache behavior

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 10:42:12 -06:00
Peter Machona dfbdab5a29
fix(slack): map legacy streaming=false to off (openclaw#26020) thanks @chilu18
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: chilu18 <7957943+chilu18@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 10:21:25 -06:00
dan bachelder 9ae94390b9
fix(slack): resolve replyToMode per-message using chat type (#24717)
* fix(slack): resolve replyToMode per-message using chat type

The Slack monitor resolved replyToMode once at startup from the
top-level config, ignoring replyToModeByChatType overrides. This caused
DM replies to be threaded even when replyToModeByChatType.direct was
set to "off".

Now the inbound message handler calls resolveSlackReplyToMode(account,
chatType) per-message — the same function already used by the outbound
dock and tool threading context — so per-chat-type overrides take
effect on the inbound path.

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

* Slack: add changelog for per-message replyToMode resolution

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 10:21:01 -06:00
Mark L 265b22c401
fix(slack): skip monitor startup for disabled accounts [AI-assisted] (openclaw#30592) thanks @liuxiaopai-ai
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 10:19:50 -06:00
François Martin 53d6e07a60
fix(sessions): set transcriptPath to agent sessions directory (openclaw#24775) thanks @martinfrancois
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: martinfrancois <14319020+martinfrancois@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 09:41:06 -06:00
Colin Johnson 0f36ee5a2e
Slack: harden slash and interactions ingress checks (openclaw#29091) thanks @Solvely-Colin
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Solvely-Colin <211764741+Solvely-Colin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 09:40:57 -06:00
Glucksberg 3aad6c8bdb
fix(slack): guard Socket Mode listeners access during startup (openclaw#28702) thanks @Glucksberg
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 09:29:18 -06:00
HouRong b3f60a68a0
fix(slack): thread agent identity through channel reply path (openclaw#27134) thanks @hou-rong
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: hou-rong <8758438+hou-rong@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 09:25:32 -06:00
pushkarsingh32 4ba0a4d4fb
fix(slack): wrap session key in backticks to prevent emoji shortcode parsing (openclaw#30266) thanks @pushkarsingh32
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: pushkarsingh32 <29558481+pushkarsingh32@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 09:23:50 -06:00
Tak Hoffman ff563eef0f
Issues: unify bug form and subtype auto-labeling (openclaw#30733) thanks @Takhoffman
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 09:20:57 -06:00
Ayaan Zaidi 201c6252ed test(slack): pass cfg to buildAccountSnapshot in tests 2026-03-01 20:36:05 +05:30
Ayaan Zaidi 08f98ac1c9 docs(changelog): note android notify auth-race fix (#30726) 2026-03-01 20:32:14 +05:30
Ayaan Zaidi 6f63fc288a fix(android): return NOT_AUTHORIZED when notify permission is lost 2026-03-01 20:32:14 +05:30
Ayaan Zaidi 0d672e43b9 chore(protocol): sync generated swift models 2026-03-01 20:32:14 +05:30
Ayaan Zaidi 759a0fc1b2 chore(android): remove deprecated AGP gradle flags 2026-03-01 20:32:14 +05:30
Ayaan Zaidi 9c2f7e2a9d style(android): format gradle kotlin scripts 2026-03-01 20:32:14 +05:30
Ayaan Zaidi 348a7dd5b3 fix(android): guard notification post permission 2026-03-01 20:32:14 +05:30
Ayaan Zaidi 7f9274b71d chore(android): add kotlin lint/format tooling 2026-03-01 20:32:14 +05:30
Mark L 4da4cc94c1
fix(slack): treat HTTP mode accounts as configured [AI-assisted] (openclaw#30571) thanks @liuxiaopai-ai
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 09:00:17 -06:00
Xu Gu e3ba59dc71
Control UI: add cron jobs schedule/status filters with reset (#9510)
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 08:49:11 -06:00
Ayaan Zaidi 59fd394bfe docs(changelog): add #29521 voice tts entry (thanks @gregmousseau) 2026-03-01 20:03:26 +05:30
Greg Mousseau ba430cc65b fix(android): drainingTts identity check, mark stopped on WebSocket failure
- Codex P2: drain coroutine now only clears drainingTts if it's the
  same instance (=== check), preventing a newer drain from being
  unreachable by stopTts.
- Codex P2: set stopped=true on WebSocket onFailure so subsequent
  sendText calls are rejected and stale state doesn't persist.
2026-03-01 20:03:26 +05:30
Greg Mousseau ccca99c472 fix(android): ignore stale out-of-order agent events in streaming TTS
Agent events arrive on multiple threads concurrently. A stale event
with shorter accumulated text was falsely triggering 'text diverged',
causing the streaming TTS to restart with a new WebSocket — resulting
in multiple simultaneous ElevenLabs connections (2-3 voices) and
eventual system TTS fallback when hasReceivedAudio was false.

Fix: if sentFullText.startsWith(fullText), the event is stale (we
already have this text), not diverged. Accept and ignore it.
2026-03-01 20:03:26 +05:30
Greg Mousseau a583261775 fix(android): wire speaker mute to TalkMode, release audio focus on stop
- Codex P1: setSpeakerEnabled now syncs talkMode.setPlaybackEnabled
  so muting the speaker works when ttsOnAllResponses is active.
- Codex P2: abandonAudioFocus() called in stopSpeaking to prevent
  audio focus leak after TTS completes or is interrupted.
2026-03-01 20:03:26 +05:30
Greg Mousseau 930841cd7c fix(android): wire MP3 fallback call, prevent double-speaking
- Codex P1: streamAndPlayMp3 was computed but never called after PCM
  failure. Now properly invoked as fallback.
- Codex P2: MicCaptureManager.speakAssistantReply now skipped when
  TalkModeManager.ttsOnAllResponses is active, preventing both
  pipelines from speaking the same assistant reply.
2026-03-01 20:03:26 +05:30
Greg Mousseau 587790e84a fix(android): talk mode stability — thread safety, TTS fallback, mic cooldown
Bug fixes:
- @Synchronized on ElevenLabsStreamingTts.sendText/finish to prevent
  sentFullText/sentTextLength races across OkHttp and caller threads
- Pre-set pendingRunId via onRunIdKnown callback before chat.send to
  eliminate race where gateway events arrive before runId is stored
- Track drain coroutine as Job; cancel prior on rapid mic toggle to
  prevent duplicate TTS and stale transcript sends
- Mic button disabled during 2s drain cooldown (micCooldown StateFlow)

Codex review fixes:
- Gate agent streaming TTS on sessionKey to prevent cross-session
  audio leaks (P1)
- Clear ElevenLabs credentials when talk.provider is not elevenlabs;
  gate streaming TTS on activeProviderIsElevenLabs (P2)

System TTS fallback fixes:
- Null streamingTts immediately in finishStreamingTts so next response
  gets a fresh TTS instance
- Add hasReceivedAudio flag to ElevenLabsStreamingTts to detect when
  WebSocket connects but returns no audio (invalid key, network error)
- Fall back to playTtsForText when streaming TTS produced no audio
- Track ttsJob to cleanly cancel prior playTtsForText on new response
- Re-throw CancellationException instead of cascading into fallback
  attempts that also get cancelled
2026-03-01 20:03:26 +05:30
Greg Mousseau 4748ba491d fix(android): chat history refresh and mic capture improvements for voice
ChatController:
- final/aborted/error run events now trigger a history refresh regardless of
  whether the runId is in pendingRuns; only delta events require the run to be
  tracked (prevents voice-initiated responses from being silently dropped)

MicCaptureManager:
- Don't auto-send on onResults silence detection — accumulate transcript
  segments and send when mic is toggled off, giving the recognizer time to
  finish processing buffered audio
- Capture any partial live transcript if no final segments arrived (2s drain
  window before stop)
- Join multi-segment transcripts with sentence-ending punctuation to avoid
  run-on text sent to the gateway
2026-03-01 20:03:26 +05:30
Greg Mousseau 68db055f1a feat(android): wire TalkModeManager into NodeRuntime for voice screen TTS
TalkModeManager is instantiated lazily in NodeRuntime and drives ElevenLabs
streaming TTS for all assistant responses when the voice screen is active.
MicCaptureManager continues to own STT and chat.send; TalkModeManager is
TTS-only (ttsOnAllResponses = true, setEnabled never called).

- talkMode.ttsOnAllResponses = true when mic is enabled or voice screen active
- Barge-in: tapping the mic button calls stopTts() before re-enabling mic
- Lifecycle: PostOnboardingTabs LaunchedEffect + VoiceTabScreen onDispose both
  call setVoiceScreenActive(false) so TTS stops cleanly on tab switch or
  app backgrounding
- applyMainSessionKey wires the session key into TalkModeManager so it
  subscribes to the correct chat session for TTS
2026-03-01 20:03:26 +05:30
Greg Mousseau f0fcecd7c1 feat(android): ElevenLabsStreamingTts — WebSocket streaming TTS with PCM playback
Streams text to the ElevenLabs WebSocket API and plays audio in real-time
via AudioTrack (PCM 24kHz). Key design points:

- sendText(fullText) takes the full accumulated text and only transmits the
  new suffix, detecting divergence for restart
- Chunks are queued if the WebSocket isn't yet connected; flushed in onOpen
- finish() sends EOS to ElevenLabs; deferred if called before onOpen fires
- sendText returns true (not false) when finished=true to avoid treating a
  normal end-of-stream as a diverge restart
- finishStreamingTts coroutine uses identity check before nulling streamingTts
  to prevent a mid-drain restart from orphaning a live TTS session
- eleven_v3 does NOT support WebSocket streaming; use eleven_flash_v2_5
2026-03-01 20:03:26 +05:30
Ian Derrington 266d320062
feat(ui): add hide-cron toggle to chat session selector (#26976)
* feat(ui): add hide-cron toggle to chat session selector

Adds a clock icon toggle button in the chat controls bar that filters
cron sessions out of the session dropdown. Default: hidden (true).

Why: cron sessions (key prefix `cron:`) accumulate fast — a job running
every 15 min produces 48 entries/day. They pollute the session selector
on small screens and devices like the Rabbit R1.

Changes:
- app-render.helpers.ts
  - isCronSessionKey() — exported helper (exported for tests)
  - countHiddenCronSessions() — counts filterable crons, skips active key
  - resolveSessionOptions() — new hideCron param; skips cron: keys
    unless that key is the currently active session (never drop it)
  - renderCronFilterIcon() — clock SVG with optional badge count
  - renderChatControls() — reads state.sessionsHideCron (default true),
    passes hideCron to resolveSessionOptions, adds toggle button at the
    end of the controls bar showing hidden count as a badge
- app-view-state.ts — adds sessionsHideCron: boolean to AppViewState
- app.ts — @state() sessionsHideCron = true (persists across re-renders)
- app-render.helpers.node.test.ts — tests for isCronSessionKey

* fix(ui): harden cron session filtering and i18n labels

---------

Co-authored-by: FLUX <flux@openclaw.ai>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 08:24:14 -06:00
0xbrak 4637b90c07
feat(cron): configurable failure alerts for repeated job errors (openclaw#24789) thanks @0xbrak
Verified:
- pnpm install --frozen-lockfile
- pnpm check
- pnpm test -- --run src/cron/service.failure-alert.test.ts src/cli/cron-cli.test.ts src/gateway/protocol/cron-validators.test.ts

Co-authored-by: 0xbrak <181251288+0xbrak@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 08:18:15 -06:00
yinghaosang f902697bd5
feat(cron): add payload.fallbacks for per-job model fallback override (#26120) (#26304)
Co-authored-by: yinghaosang <yinghaosang@users.noreply.github.com>
2026-03-01 08:11:03 -06:00
BUGKillerKing 8c98cf05b2
i18n: add zh-CN for cron page and validation errors (#29315)
* i18n: add zh-CN for cron page and validation errors

* cron: treat unexpected delivery statuses as unknown

* test(cron): align validation tests with i18n keys

---------

Co-authored-by: 周鹤0668001310 <zhou.he3@xydigit.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 08:05:51 -06:00
Aviral d0ca02e963
fix(cron): respect subagents.model in isolated cron sessions (#11474)
* fix(cron): respect subagents.model in isolated cron sessions

* fix(cron): enforce model allowlist for subagents.model

* Cron: fix isolated subagent model gate regressions

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 07:54:09 -06:00
wangchunyue cb6f993b4c
fix(cli): cron list Agent column shows agentId not model — add Model column (openclaw#26259) thanks @openperf
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 07:47:32 -06:00
Altay 98e30dc2a3
fix(cron): handle sessions list cron model override (openclaw#21279) thanks @altaywtf
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 07:32:20 -06:00
Sid f107347608
fix(ui-cron): include configured model suggestions for scheduled jobs (openclaw#29709) thanks @Sid-Qin
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 07:31:47 -06:00
Pierre 5784963608
fix cron store backup churn (#19484) 2026-03-01 07:10:53 -06:00
Aleksandrs Tihenko 0cc46589ac
Cron: drain pending writes before reading run log (#25416)
* Cron: drain pending writes before reading run log

* Retrigger CI
2026-03-01 07:04:04 -06:00
Sid 29a55948d6
fix(cron): guard list sorting against malformed legacy jobs (#28896)
* fix(cron): guard list sorting against malformed legacy jobs

Prevent list operations from crashing when old or corrupted cron entries are missing name/id fields by hardening sort comparators.

Closes #28862

* cron: format list sort guard test imports

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 07:01:36 -06:00
Glucksberg 645d963954
feat: expand ~ (tilde) to home directory in file tools (read/write/edit) (openclaw#29779) thanks @Glucksberg
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 07:00:52 -06:00
NIO ea3955cd78
fix(cron): add retry policy for one-shot jobs on transient errors (#24355) (openclaw#24435) thanks @hugenshen
Verified:
- pnpm install --frozen-lockfile
- pnpm check
- pnpm test -- --run src/cron/service.issue-regressions.test.ts src/config/config-misc.test.ts

Co-authored-by: hugenshen <16300669+hugenshen@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 06:58:03 -06:00
ToToKr 62a7683ce6
fix(cron): add audit logging for job create/update/remove (openclaw#25090) thanks @MoerAI
Verified:
- pnpm install --frozen-lockfile
- pnpm check
- pnpm test -- --run src/gateway/server-cron.test.ts src/gateway/server-methods/server-methods.test.ts src/gateway/protocol/cron-validators.test.ts

Co-authored-by: MoerAI <26067127+MoerAI@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 06:55:48 -06:00
StingNing 5b49cc4129
fix(cron): notify user when cron job is auto-disabled after repeated errors (openclaw#29098) thanks @ningding97
Verified:
- pnpm install --frozen-lockfile
- pnpm check
- pnpm test -- --run src/cron/service.runs-one-shot-main-job-disables-it.test.ts

Co-authored-by: ningding97 <17723822+ningding97@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 06:54:02 -06:00
Sid 504c1f3607
fix(cron): migrate legacy schedule cron fields on load (#28889)
Backfill legacy jobs that still use schedule.cron and jobId so upgraded instances keep firing existing cron schedules instead of failing silently.

Closes #28861
2026-03-01 06:53:39 -06:00
Sid d509a81a12
fix(cron): treat transient tool error payloads as recoverable (openclaw#29527) thanks @Sid-Qin
Verified:
- pnpm install --frozen-lockfile
- pnpm check
- pnpm test -- --run src/cron/isolated-agent.uses-last-non-empty-agent-text-as.test.ts

Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-03-01 06:52:15 -06:00
Ayaan Zaidi 635c78a177 docs: add changelog entry for session_status levels (#30129) 2026-03-01 14:45:12 +05:30
YuzuruS 310344b6e4 fix: read thinking/verbose/reasoning levels from session entry in status
buildStatusMessage resolved thinkLevel, verboseLevel, and reasoningLevel
without falling back to sessionEntry, unlike elevatedLevel which already
had this fallback. When session_status tool calls buildStatusMessage
without passing resolvedThink/resolvedVerbose/resolvedReasoning, the
levels always fell back to agent defaults or "off", ignoring the
runtime-set session values.

Add sessionEntry fallback for thinkingLevel, verboseLevel, and
reasoningLevel, consistent with how elevatedLevel already works.

Closes #30126

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 14:42:34 +05:30
Shadow 54c46b7c8c
temp disable stale workflow to help with ratelimits 2026-02-28 22:51:34 -06:00
Gustavo Madeira Santana 9257dfb5c0 fix(diffs): tighten rendering quality 2026-02-28 23:03:28 -05:00
Gustavo Madeira Santana 0f72000c96 fix(diffs): increase resolution scaling factor 2026-02-28 22:25:29 -05:00
Shadow 3685ccb536
chore: lock inactive closed issues 2026-02-28 20:48:02 -06:00
Gustavo Madeira Santana c0ce125512 fix(gateway): shorten manual reinstall/restart delays
LaunchAgent plist hardcodes ThrottleInterval to 60 in src/daemon/launchd-plist.ts

That means every restart/install path that terminates the launchd-managed gateway gets delayed by launchd’s one-minute relaunch throttle. The CLI restart path in src/daemon/launchd.ts is doing the expected supervisor actions, but the plist policy makes those actions look hung.

In src/daemon/launchd-plist.ts:
- added LAUNCH_AGENT_THROTTLE_INTERVAL_SECONDS
- reduced the LaunchAgent ThrottleInterval from 60 to 1
2026-02-28 20:46:11 -05:00
Gustavo Madeira Santana 39e09273ca docs(diffs): update docs for diffs plugin 2026-02-28 20:40:30 -05:00
Gustavo Madeira Santana 0abf47cfd5 plugin(diffs): optimize rendering for image/view modes 2026-02-28 20:19:15 -05:00
Jarvis fcb6859784
fix(memoryFlush): correct context token accounting for flush gating (#5343)
Merged via squash.

Prepared head SHA: afaa7bae3b
Co-authored-by: jarvis-medmatic <252428873+jarvis-medmatic@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-02-28 16:54:57 -08:00
Gustavo Madeira Santana 812a996b2f adding config layer 2026-02-28 19:20:07 -05:00
Gustavo Madeira Santana 1828fdee8b icons refined 2026-02-28 18:58:26 -05:00
Gustavo Madeira Santana 612ed5b3e1 diffs plugin 2026-02-28 18:38:00 -05:00
Vignesh Natarajan fca0467082 TUI: guard SIGTERM shutdown against setRawMode EBADF 2026-02-28 14:56:01 -08:00
Vignesh Natarajan 2050fd7539 Cron: preserve session scope for main-target reminders 2026-02-28 14:53:19 -08:00
Yassine Amjad 61989091a4
fix(reply): fix duplicate block replies by unblocking coalesced payloads (#5080)
Merged via squash.

Prepared head SHA: 399e1259cb
Co-authored-by: yassine20011 <59234686+yassine20011@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-02-28 14:51:43 -08:00
Vignesh Natarajan c58d2aa99e Sessions: fix sessions_list transcriptPath path resolution 2026-02-28 14:42:14 -08:00
Vignesh Natarajan f57b4669e1 Memory: keep keyword hits when hybrid vector misses 2026-02-28 14:18:24 -08:00
Vignesh Natarajan 0929c233d8 TUI: sync /model status immediately 2026-02-28 14:02:56 -08:00
Vignesh Natarajan a623c9c8d2 Onboarding: enforce custom model context minimum 2026-02-28 13:37:21 -08:00
Vignesh Natarajan e90429794a Web UI: strip relevant-memories scaffolding 2026-02-28 13:20:50 -08:00
Vignesh Natarajan ea4f5106ea chore(gateway): guard cron agent heartbeat type 2026-02-28 13:03:45 -08:00
Vignesh Natarajan 9868d5cd8b Gateway: allow control-ui session deletion 2026-02-28 13:01:10 -08:00
Vincent Koc 62179c861b Update server-cron.ts 2026-02-28 10:16:34 -08:00
Vincent Koc 6dae3c2ca6 Update models-config.providers.ts 2026-02-28 10:16:34 -08:00
Vincent Koc 8624f80649 Update models-config.providers.ollama.test.ts 2026-02-28 10:16:34 -08:00
Vincent Koc b8863fc813 Update CHANGELOG.md 2026-02-28 10:16:34 -08:00
Kansodata Spa. 81d215afa7 fix(cron): narrow agentEntry type for heartbeat merge 2026-02-28 10:16:34 -08:00
Kansodata Spa. 247ff6ff9e fix(ollama): default explicit-model provider api to native ollama 2026-02-28 10:16:34 -08:00
Kansodata Spa. 0331fc5199 test(ollama): type explicit models input union in provider test 2026-02-28 10:16:34 -08:00
damaozi 78d49b4c8e fix: remove readonly type constraint in test 2026-02-28 10:16:34 -08:00
damaozi deb9560a2b fix(agents): skip Ollama discovery when explicit models configured (#28762) 2026-02-28 10:16:34 -08:00
Vincent Koc be8a5b9d64 chore(changelog): add missing entry for #28827 2026-02-28 09:47:06 -08:00
Vincent Koc db25b26e33 chore(changelog): add missing entry for #25326 2026-02-28 09:47:06 -08:00
Vincent Koc 67a1584e82 chore(changelog): add missing entry for #26414 2026-02-28 09:47:06 -08:00
金炳 28c80689d4
fix(browser): resolve correct targetId in navigate response after renderer swap (#25326)
* fix(browser): resolve correct targetId in navigate response after renderer swap

When `navigateViaPlaywright` triggers a Chrome renderer-process swap
(e.g. navigating from chrome-extension:// to https://), the old
`tab.targetId` captured before navigation becomes stale. The `/navigate`
route previously returned this stale targetId in its response.

After navigation, re-resolve the current tab by matching against the
final URL via `profileCtx.listTabs()`. If the old target is already gone
but the new one is not yet visible (extension re-attach in progress),
retry after 800ms.

Follow-up to #19744 (67bac62c2) which fixed the extension-side stale
session cleanup.

* fix(browser): prefer non-stale targetId when multiple tabs share the same URL

When multiple tabs have the same URL after navigation, find() could pick
a pre-existing tab instead of the newly created one. Now only re-resolve
when the old target is gone (renderer swap detected), and prefer the tab
whose targetId differs from the old one.

* fix(browser): encapsulate targetId resolution logic after navigation

Introduced a new function `resolveTargetIdAfterNavigate` to handle the resolution of the correct targetId after a navigation event that may trigger a renderer swap. This refactor improves code clarity and reuses the logic for determining the current targetId, ensuring that the correct tab is identified even when multiple tabs share the same URL.

* refactor(tests): simplify listTabs initialization in agent snapshot tests

Updated the initialization of listTabs in the agent snapshot tests for better readability by removing unnecessary line breaks. This change enhances code clarity without altering the test logic.

* fix(ui): widen Set type to accept string tokens in external-link helper

* chore: retrigger CI (unrelated Windows flaky test)

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-28 09:23:24 -08:00
Charlie Niño 26db298d3e
fix: sed escaping and UID mismatch in Podman Quadlet setup (#26414)
* fix: sed escaping and UID mismatch in Podman Quadlet setup

Fix two bugs in the Podman/Quadlet installation path:

1. setup-podman.sh line 227: Remove `/` from sed escape character class.
   The sed substitution uses `|` as delimiter, so `/` doesn't need
   escaping. Including it causes paths like `/home/openclaw` to become
   `\/home\/openclaw`, which Podman rejects as invalid volume names.

2. openclaw.container.in: Add `User=%U:%G` after `UserNS=keep-id`.
   The Dockerfile sets `USER node` (UID 1000), but the `openclaw` system
   user created by setup-podman.sh may get a different UID (e.g., 1001).
   Without `User=%U:%G`, the container process runs as UID 1000 and
   cannot read config files owned by the openclaw user.

Closes #26400

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

* scripts: extract quadlet sed replacement escaping helper

* podman: document quadlet user mapping rationale

* scripts: correct sed replacement escaping for pipe delimiter

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-02-28 09:20:18 -08:00
Marcus Widing 8ae1987f2a
fix(cron): pass heartbeat target=last for main-session cron jobs (#28508) (#28583)
* fix(cron): pass heartbeat target=last for main-session cron jobs

When a cron job with sessionTarget=main and wakeMode=now fires, it
triggers a heartbeat via runHeartbeatOnce. Since e2362d35 changed the
default heartbeat target from "last" to "none", these cron-triggered
heartbeats silently discard their responses instead of delivering them
to the last active channel (e.g. Telegram).

Fix: pass heartbeat: { target: "last" } from the cron timer to
runHeartbeatOnce for main-session jobs, and wire the override through
the gateway cron service builder. This restores delivery for
sessionTarget=main cron jobs without reverting the intentional default
change for regular heartbeats.

Regression introduced in: e2362d35 (2026-02-25)

Fixes #28508

* Cron: align server-cron wake routing expectations for main-target jobs

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-28 11:14:24 -06:00
lailoo d7d3416b1d
fix(cron): disable messaging tool when delivery.mode is none (#21808) (#21896) 2026-02-28 11:12:17 -06:00
Mitsuyuki Osabe e1df1c60b8
fix: clear delivery routing state when creating isolated cron sessions (#27778)
* fix: clear delivery routing state when creating isolated cron sessions

When `resolveCronSession()` creates a new session (forceNew / isolated),
the `...entry` spread preserves `lastThreadId`, `lastTo`, `lastChannel`,
and `lastAccountId` from the prior session. This causes announce-mode
cron deliveries to post as thread replies instead of channel top-level
messages when `delivery.to` matches the channel of a prior conversation.

Clear delivery routing metadata on new session creation so isolated
cron sessions start with a clean delivery state.

Closes #27751

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)

* fix: also clear deliveryContext to prevent lastThreadId repopulation

normalizeSessionEntryDelivery (called on store writes) repopulates
lastThreadId from deliveryContext.threadId. Clearing only the last*
fields is insufficient — deliveryContext must also be cleared when
creating a new isolated session.

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
2026-02-28 11:09:12 -06:00
Sid daa418895e
fix(cron): avoid marking queued announce paths as delivered (#29716)
Cron announce flow treated queued/steered outcomes as delivered even when no direct outbound send was confirmed, which could report false-positive delivery state. This change keeps cron delivery strict: only direct-path announce results count as delivered.

Closes #29660
2026-02-28 11:09:09 -06:00
Dennis Goldfinger 3096837238
fix(cron): enable completion direct send for text-only announce delivery (#29151) 2026-02-28 11:09:07 -06:00
Sid fe9a7c4082
fix(cron): force main-target system events onto main session (#28898)
Ignore persisted sessionKey overrides for sessionTarget=main jobs so cron system events consistently route to the agent main session after upgrades.

Closes #28770
2026-02-28 11:08:53 -06:00
Anandesh Sharma 2851926314
fix(cron): condition requireExplicitMessageTarget on resolved delivery (#28017)
When a cron job's delivery target resolution fails (resolvedDelivery.ok
is false), the agent was still started with requireExplicitMessageTarget:
true. This caused "Action send requires a target" errors because the
agent's message tool demanded a target that was never resolved.

Condition the flag on both deliveryRequested AND resolvedDelivery.ok so
the agent can still use messaging tools freely when no valid delivery
target exists.

Fixes #27898

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 11:08:37 -06:00
Marvin 5e2ef0e883
feat(cron): add --account flag for multi-account delivery routing (#26284)
* feat(cron): add --account flag for multi-account delivery routing

Add support for explicit delivery account routing in cron jobs across CLI, normalization, delivery planning, and isolated delivery target resolution.

Highlights:
- Add --account <id> to cron add and cron edit
- Add optional delivery.accountId to cron types and delivery plan
- Normalize and trim delivery.accountId in cron create/update normalization
- Prefer explicit accountId over session lastAccountId and bindings fallback
- Thread accountId through isolated cron run delivery resolution
- Preserve cron edit --best-effort-deliver/--no-best-effort-deliver behavior by keeping implicit announce mode
- Expand tests for account passthrough/merge/precedence and CLI account flows

* cron: resolve rebase duplicate accountId fields

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-28 10:57:49 -06:00
Pierre e1c8094ad0
fix: schedule nextWakeAtMs for isolated sessionTarget cron jobs (#19541)
* fix(cron): repair isolated next wake scheduling

* cron: harden isolated next-wake timestamp guards

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-28 10:48:31 -06:00
Ayaan Zaidi 139271ad5a fix: sandbox browser docker no-sandbox rollout (#29879) (thanks @Lukavyi) 2026-02-28 21:43:56 +05:30
Tak Hoffman 7ae683194f
GitHub: add regression bug issue template and routing (openclaw#29864) thanks @Takhoffman
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-28 10:02:55 -06:00
刘苇 5209c48923
feat(feishu): add chat info/member tool (openclaw#14674)
* feat(feishu): add chat members/info tool support

* Feishu: harden chat tool schema and coverage

---------

Co-authored-by: Nereo <nereo@Nereos-Mac-mini.local>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-28 10:00:31 -06:00
Elarwei 0740fb83d7
feat(feishu): add markdown tables, positional insert, color_text, and table ops (#29411)
* feat(feishu): add markdown tables, insert, color_text, table ops, and image fixes

Extends feishu_doc on top of #20304 with capabilities that are not yet covered:

Markdown → native table rendering:
- write/append now use the Descendant API instead of Children API,
  enabling GFM markdown tables (block_type 31/32) to render as native
  Feishu tables automatically
- Adaptive column widths calculated from cell content (CJK chars 2x weight)
- Batch insertion for large documents (>1000 blocks, docx-batch-insert.ts)

New actions:
- insert: positional markdown insertion after a given block_id
- color_text: apply color/bold to a text block via [red]...[/red] markup
- insert_table_row / insert_table_column: add rows or columns to a table
- delete_table_rows / delete_table_columns: remove rows or columns
- merge_table_cells: merge a rectangular cell range

Image upload fixes (affects write, append, and upload_image):
- upload_image now accepts data URI and plain base64 in addition to
  url/file_path, covering DALL-E b64_json, canvas screenshots, etc.
- Fix: pass Buffer directly to drive.media.uploadAll instead of
  Readable.from(), which caused Content-Length mismatch for large images
- Fix: same Readable bug fixed in upload_file
- Fix: pass drive_route_token via extra field for correct multi-datacenter
  routing (per API docs: required when parent_node is a document block ID)

* fix(feishu): add documentBlockDescendant mock to docx.test.ts

write/append now use the Descendant API (documentBlockDescendant.create)
instead of Children API. The existing test mock was missing this SDK
method, causing processImages to never be reached and fetchRemoteMedia
to go uncalled.

Added blockDescendantCreateMock returning an image block so the
'skips image upload when markdown image URL is blocked' test flows
through processImages as expected.

* fix(feishu): address bot review feedback

- resolveUploadInput: remove length < 1024 guard on file path detection.
  Prefix patterns (isAbsolute / ~ / ./ / ../) already correctly distinguish
  file paths from base64 strings at any length. The old guard caused file
  paths ≥1024 chars to fall through to the base64 branch incorrectly.

- parseColorMarkup: add comment clarifying that mismatched closing tags
  (e.g. [red]text[/green]) are intentional — opening tag style is applied,
  closing tag is consumed regardless of name.

* fix(feishu): address second-round codex bot review feedback

P1 - Reject single oversized subtrees in batch insert (docx-batch-insert.ts):
  A first-level block whose descendant count exceeds BATCH_SIZE (1000) cannot
  be split atomically (e.g. a very large table). Previously such a block was
  silently added to the current batch and sent as an oversized request,
  violating the API limit. Now throws a descriptive error so callers know to
  reduce the content size.

P2 - Preserve unmatched brackets in color markup parser (docx-color-text.ts):
  Text like 'Revenue [Q1] up' contains a bracket pair with no matching '[/...]'
  closer. The original regex dropped the '[' character in this case, silently
  corrupting the text. Fixed by appending '|\[' to the plain-text alternative
  so any '[' that does not open a complete tag is captured as literal text.

* fix(feishu): address third-round codex bot review feedback

P2 - Throw ENOENT for non-existing absolute image paths (docx.ts):
  Previously a non-existing absolute path like /tmp/missing.png fell
  through to Buffer.from(..., 'base64') and uploaded garbage bytes.
  Now throws a descriptive ENOENT error and hints at data URI format
  for callers intending to pass JPEG binary data (which starts with /9j/).

P2 - Fail clearly when insert anchor block is not found (docx.ts):
  insertDoc previously set insertIndex to -1 (append) when after_block_id
  was absent from the parent's child list, silently inserting at the wrong
  position. Two fixes:
  1. Paginate through all children (documentBlockChildren.get returns up to
     200 per page) before searching for the anchor.
  2. Throw a descriptive error if after_block_id is still not found after
     full pagination, instead of silently falling back to append.

* fix(feishu): address fourth-round codex bot review feedback

- Enforce mutual exclusivity across all three upload sources (url, file_path,
  image): throw immediately when more than one is provided, instead of silently
  preferring the image branch and ignoring the others.
- Validate plain base64 payloads before decoding: reject strings that contain
  characters outside the standard base64 alphabet ([A-Za-z0-9+/=]) so that
  malformed inputs fail fast with a clear error rather than decoding to garbage
  bytes and producing an opaque Feishu API failure downstream.
  Also throw if the decoded buffer is empty.

* fix(feishu): address fifth-round codex bot review feedback

- parseColorMarkup: restrict opening tag regex to known colour/style names
  (bg:*, bold, red, orange, yellow, green, blue, purple, grey/gray) so that
  ordinary bracket tokens like [Q1] can no longer consume a subsequent real
  closing tag ([/red]) and corrupt the surrounding styled spans.  Unknown tags
  now fall through to the plain-text alternatives and are emitted literally.
- resolveUploadInput: estimate decoded byte count from base64 input length
  (ceil(len * 3 / 4)) BEFORE allocating the full Buffer, preventing oversized
  payloads from spiking memory before the maxBytes limit is enforced.  Applies
  to both the data-URI branch and the plain-base64 branch.

* fix(feishu): address sixth-round codex bot review feedback

- docx-table-ops: apply MIN/MAX_COLUMN_WIDTH clamping in the empty-table
  branch so tables with 15+ columns don't produce sub-50 widths that Feishu
  rejects as invalid column_width values.
- docx.ts (data URI branch): validate the ';base64' marker before decoding
  so plain/URL-encoded data URIs are rejected with a clear error; also validate
  the payload against the base64 alphabet (same guard already applied in the
  plain-base64 branch) so malformed inputs fail fast rather than producing
  opaque downstream Feishu errors.

* Feishu: align docx descendant insertion tests and changelog

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-28 09:58:56 -06:00
Chuan Liu 4ad49de89d
feat(feishu): add parent/root inbound context for quote support (openclaw#18529)
* feat(feishu): add parentId and rootId to inbound context

Add ParentMessageId and RootMessageId fields to Feishu inbound message context,
enabling agents to:
- Identify quoted/replied messages
- Fetch original message content via Feishu API
- Build proper message thread context

The parent_id and root_id fields already exist in FeishuMessageContext but were
not being passed to the agent's inbound context.

Fixes: Allows proper handling of quoted card messages and message thread reconstruction.

* feat(feishu): parse interactive card content in quoted messages

Add support for extracting readable text from interactive card messages
when fetching quoted/replied message content.

Previously, only text messages were parsed. Now interactive cards
(with div and markdown elements) are also converted to readable text.

* 更新 bot.ts

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

* fix(types): add RootMessageId to MsgContext type definition

* style: fix formatting in bot.ts

* ci: trigger rebuild

* ci: retry flaky tests

* Feishu: add reply-context and interactive-quote regressions

---------

Co-authored-by: qiangu <qiangu@qq.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: 牛牛 <niuniu@openclaw.ai>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-28 09:55:50 -06:00
Ayaan Zaidi 9b39490d6a fix: land android onboarding and voice reliability updates (#29796) 2026-02-28 20:05:59 +05:30
Ayaan Zaidi 1d7b76a90e fix(android-voice): rotate playback token per assistant reply 2026-02-28 20:05:59 +05:30
Ayaan Zaidi addc619087 fix(android-voice): retry talk config after transient failures 2026-02-28 20:05:59 +05:30
Ayaan Zaidi 930e94024a fix(android-voice): cancel in-flight speech when speaker muted 2026-02-28 20:05:59 +05:30
Ayaan Zaidi 727ae469cf perf(android): reduce mic conversation update churn 2026-02-28 20:05:59 +05:30
Ayaan Zaidi 3daed77ba9 fix(android): unify voice speaker gating and config refresh 2026-02-28 20:05:59 +05:30
Ayaan Zaidi 72e135083a feat(android-voice): add speaker toggle in voice tab 2026-02-28 20:05:59 +05:30
Ayaan Zaidi fb92a91ef7 fix(android): speak final voice replies in mic capture flow 2026-02-28 20:05:59 +05:30
Ayaan Zaidi fcf3e5b0a0 fix(android): expose talk-mode assistant speech entrypoint 2026-02-28 20:05:59 +05:30
Ayaan Zaidi eea081c709 fix(android): update onboarding pairing commands 2026-02-28 20:05:59 +05:30
Ayaan Zaidi 548a28a13f fix(android): request onboarding permissions per toggle 2026-02-28 20:05:59 +05:30
Ayaan Zaidi f0c86039c7 fix: clarify outside-workspace fs-safe errors (#29715) (thanks @YuzuruS) 2026-02-28 18:08:10 +05:30
Ayaan Zaidi 44220ef24a test: add outside-workspace error mapping coverage 2026-02-28 18:08:10 +05:30
YuzuruS d6552998e9 fix: handle outside-workspace error in media store
Address Greptile review: add explicit "outside-workspace" case to
toSaveMediaSourceError so it returns "Media path is outside workspace
root" instead of the generic "Media path is not safe to read".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:08:10 +05:30
YuzuruS f5c2be1910 fix: distinguish outside-workspace errors from not-found in fs-safe
When editing a file outside the workspace root, SafeOpenError previously
used the "invalid-path" code with the message "path escapes root". This
was indistinguishable from other invalid-path errors (hardlinks, symlinks,
non-files) and consumers often fell back to a generic "not found" message,
which was misleading.

Add a new "outside-workspace" error code with the message "file is outside
workspace root" so consumers can surface a clear, accurate error message.

- fs-safe.ts: add "outside-workspace" to SafeOpenErrorCode, use it for
  all path-escapes-root checks in openFileWithinRoot/writeFileWithinRoot
- pi-tools.read.ts: map "outside-workspace" to EACCES instead of rethrowing
- browser/paths.ts: return specific "File is outside {scopeLabel}" message
- media/server.ts: return 400 with descriptive message for outside-workspace
- fs-safe.test.ts: update traversal test expectations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:08:10 +05:30
Ayaan Zaidi 150c2093fa test: make feishu proxy precedence assertion cross-platform 2026-02-28 17:14:45 +05:30
Ayaan Zaidi 89e158fc96 fix: harden azure custom-provider verification coverage (#29421) (thanks @kunalk16) 2026-02-28 15:58:20 +05:30
Kunal Karmakar 720e1479b8 Remove temperature 2026-02-28 15:58:20 +05:30
Kunal Karmakar 2258e736b0 Reduce default max tokens 2026-02-28 15:58:20 +05:30
Kunal Karmakar 2fe5620763 Fix linting issue 2026-02-28 15:58:20 +05:30
Kunal Karmakar 4ed12c18a0 Conditional azure openai endpoint usage 2026-02-28 15:58:20 +05:30
Kunal Karmakar 06a3175cd1 Fix linting issue 2026-02-28 15:58:20 +05:30
Kunal Karmakar 955768d132 Fix default max tokens 2026-02-28 15:58:20 +05:30
Kunal Karmakar 978d9ae199 Fix azure openai endpoint validation 2026-02-28 15:58:20 +05:30
Tony Dehnke f1bf558685
fix(doctor): detect groupPolicy=allowlist with empty groupAllowFrom (#28477)
* fix(doctor): detect groupPolicy=allowlist with empty groupAllowFrom

The existing `detectEmptyAllowlistPolicy` check only covers
`dmPolicy="allowlist"` with empty `allowFrom`. After the .26 security
hardening (`resolveDmGroupAccessDecision` fails closed on empty
allowlists), `groupPolicy="allowlist"` without `groupAllowFrom` or
`allowFrom` silently drops all group/channel messages with only a
verbose-level log.

Add a parallel check: when `groupPolicy` is `"allowlist"` and neither
`groupAllowFrom` nor `allowFrom` has entries, surface a doctor warning
with remediation steps.

Closes #27552

* fix: align empty-array semantics with runtime resolveGroupAllowFromSources

The runtime treats groupAllowFrom: [] as unset and falls back to
allowFrom, but the doctor check used ?? which treats [] as authoritative.
This caused a false warning when groupAllowFrom was explicitly empty but
allowFrom had entries.

Match runtime behavior: treat empty groupAllowFrom arrays as unset
before falling back to allowFrom.

* fix: scope group allowlist check to sender-based channels only

* fix: align doctor group allowlist semantics (#28477) (thanks @tonydehnke)

---------

Co-authored-by: mukhtharcm <mukhtharcm@gmail.com>
2026-02-28 14:45:10 +05:30
Vincent Koc 5d51e99537
Changelog: add missing entries for #29279 and #29299 (#29579) 2026-02-28 00:03:44 -08:00
Vincent Koc d123ade0cb
fix(gateway): allow required Google Fonts origins in Control UI CSP (#29279)
* Gateway: allow Google Fonts stylesheet and font CDN in Control UI CSP

* Tests: assert Control UI CSP allows required Google Fonts origins

* Gateway: fix CSP comment for Google Fonts allowlist intent

* Tests: split dedicated Google Fonts CSP assertion
2026-02-27 23:58:51 -08:00
Vincent Koc f810932859
Feishu: fix locale-wrapper post parser test (#29576) 2026-02-27 23:57:27 -08:00
Vincent Koc b297bae027
fix(cli): allow Ollama apiKey config set without predeclared provider (#29299)
* CLI: seed Ollama provider on apiKey set

* Tests: cover Ollama apiKey config set path
2026-02-27 23:35:57 -08:00
Vincent Koc 7968c0f514 Changelog: add model fallback reasoning fix (#29285) 2026-02-27 23:30:27 -08:00
Ayaan Zaidi 3f056a7294 fix(android): block onboarding advance until special setup is complete 2026-02-28 12:29:52 +05:30
Ayaan Zaidi cd61edb0f3 fix(android): add missing capability setup surfaces 2026-02-28 12:29:52 +05:30
Ayaan Zaidi 3899c89805 docs(changelog): add #29440 android notification wake notes 2026-02-28 11:18:01 +05:30
Ayaan Zaidi 6a16e7bb31 fix(gateway): skip heartbeat wake on deduped notifications 2026-02-28 11:18:01 +05:30
Ayaan Zaidi a8bcad3db1 fix(gateway): canonicalize notification wake session 2026-02-28 11:18:01 +05:30
Ayaan Zaidi f1bb26642c fix(gateway): scope notification wakeups to session 2026-02-28 11:18:01 +05:30
Ayaan Zaidi 9d3ccf4754 feat(gateway): enable Android notify + notification events 2026-02-28 11:18:01 +05:30
smthfoxy 5350f5b035
fix(tts): use opus format and enable voice bubbles for feishu and whatsapp (#27366)
* fix(tts): use opus format and enable voice bubbles for feishu and whatsapp

Previously only Telegram received opus output and had `shouldVoice=true`.
Feishu and WhatsApp also support voice-bubble playback and require opus audio,
but were falling back to mp3 with `audioAsVoice=false`.

- Extract VOICE_BUBBLE_CHANNELS set (telegram, feishu, whatsapp)
- resolveOutputFormat: return TELEGRAM_OUTPUT (opus) for all voice-bubble channels
- shouldVoice: enable for all voice-bubble channels, not just telegram
- Update test to cover feishu and whatsapp cases

* Changelog: add TTS voice-bubble channel coverage note

---------

Co-authored-by: Ning Hu <ninghu@Nings-MacBook-Pro.local>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 23:41:22 -06:00
laopuhuluwa 53a2e72fcb
feat(feishu): extract embedded video/media from post (rich text) messages (#21786)
* feat(feishu): extract embedded video/media from post (rich text) messages

Previously, parsePostContent() only extracted embedded images (img tags)
from rich text posts, ignoring embedded video/audio (media tags). Users
sending post messages with embedded videos would not have the media
downloaded or forwarded to the agent.

Changes:
- Extend parsePostContent() to also collect media tags with file_key
- Return new mediaKeys array alongside existing imageKeys
- Update resolveFeishuMediaList() to download embedded media files
  from post messages using the messageResource API
- Add appropriate logging for embedded media discovery and download

* Feishu: keep embedded post media payloads type-safe

* Feishu: format post parser after media tag extraction

---------

Co-authored-by: laopuhuluwa <laopuhuluwa@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 23:39:24 -06:00
Jealous b0a8909a73
fix(feishu): fix group policy enforcement gaps (#25439)
- Respect groupConfig.enabled flag (was parsed but never enforced)
- Fix misleading log: group allowlist rejection now logs group ID and
  policy instead of sender open_id
2026-02-27 23:39:21 -06:00
WilsonLiu95 8818464f5f
feat(feishu): render post rich text as markdown (openclaw#12755)
* feat(feishu): parse post rich text as markdown

* chore: rerun ci

* Feishu: resolve post parser rebase conflicts and gate fixes

---------

Co-authored-by: Wilson Liu <wilson.liu@example.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 23:33:20 -06:00
Clawborn 49cf2bceb6
fix(feishu): handle card.action.trigger callbacks (openclaw#17863)
Co-authored-by: Kai <clawborn@users.noreply.github.com>
2026-02-27 23:24:11 -06:00
Tak Hoffman 60bf56517f
fix(feishu): honor wildcard group config for reply policy (#29456)
## Summary
- honor Feishu wildcard group policy fallback via `channels.feishu.groups["*"]` when no explicit group entry matches
- keep exact and case-insensitive explicit group matches higher precedence than wildcard fallback
- add changelog credit and TypeScript-safe test assertions

## Verification
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Wayne Pika <262095977+WaynePika@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 23:22:38 -06:00
songlei 8a2273e210
feat(feishu): support optional header in streaming cards (openclaw#22826)
Add an optional `header` parameter to `FeishuStreamingSession.start()`
so that streaming cards can display a colored title bar, matching the
appearance of non-streaming interactive cards.

The Card Kit API already supports `header` alongside `streaming_mode`,
but the current implementation omits it, producing headerless cards.

This change is fully backward-compatible: when `header` is not provided,
behavior is identical to before.

Closes #13267 (partial)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:21:22 -06:00
Colin Lee 0a23739c37
fix(feishu): pass proxy agent to WSClient for proxy environments (#26397)
* fix(feishu): pass proxy agent to WSClient for environments behind HTTPS proxy

The Lark SDK WSClient uses the `ws` library which does not automatically
respect https_proxy/HTTP_PROXY environment variables. This causes WebSocket
connection failures in proxy environments (e.g. WSL2 with a local proxy).

Detect proxy env vars and pass an HttpsProxyAgent to WSClient via the
existing `agent` constructor option.

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

* fix(feishu): add generic type parameter to HttpsProxyAgent return type

Fix TS2314: `HttpsProxyAgent<Uri>` requires a type argument.

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

* fix(feishu): wire ws proxy dependency and coverage

* chore(lockfile): resolve axios peer lock entry after rebase

---------

Co-authored-by: lirui <lirui@fxiaoke.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 23:15:11 -06:00
Menglin Li 4dc55ea88d
fix(feishu): chunk large documents for write/append to avoid API 400 errors (#14402)
* fix(feishu): chunk large documents for write/append to avoid API 400 errors

The Feishu API limits documentBlockChildren.create to 50 blocks per
request and document.convert has content size limits for large markdown.

Previously, writeDoc and appendDoc would send the entire content in a
single API call, causing HTTP 400 errors for long documents.

This commit adds:
- splitMarkdownByHeadings(): splits markdown at # or ## headings
- chunkedConvertMarkdown(): converts each chunk independently
- chunkedInsertBlocks(): batches blocks into groups of ≤50

Both writeDoc and appendDoc now use the chunked helpers while
preserving backward compatibility for small documents. Image
processing correctly receives all inserted blocks across batches.

* fix(feishu): skip heading detection inside fenced code blocks

Addresses review feedback: splitMarkdownByHeadings() now tracks
fenced code blocks (``` or ~~~) and skips heading-based splitting
when inside one, preventing corruption of code block content.

* Feishu/Docx: add convert fallback chunking + tests

---------

Co-authored-by: lml2468 <lml2468@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 23:11:12 -06:00
BigUncle 27882dc73e
feat(feishu): add quota optimization flags (openclaw#10513) thanks @BigUncle
Verified:
- pnpm build
- pnpm check
- pnpm vitest run --config vitest.extensions.config.ts extensions/feishu/src/config-schema.test.ts extensions/feishu/src/reply-dispatcher.test.ts extensions/feishu/src/bot.test.ts

Co-authored-by: BigUncle <9360607+BigUncle@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 23:05:54 -06:00
Rohin e0b1b48be3
feishu: fall back to user_id for inbound sender identity (openclaw#26703) thanks @NewdlDewdl
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: NewdlDewdl <230946873+NewdlDewdl@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 22:59:42 -06:00
Ayaan Zaidi f29c642c13 fix(release): enforce lane floor for calver appcast entries 2026-02-28 10:28:53 +05:30
Clawborn 10f1be1072
fix(feishu): replace console.log with runtime log for typing indicator errors (openclaw#18841) thanks @Clawborn
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Clawborn <135319479+Clawborn@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 22:57:16 -06:00
Shadow a5b1e86535
chore: add fallback GitHub App token 2026-02-27 22:49:58 -06:00
YAXUAN 8beb048a84
test(feishu): add regression for audio download resource type=file (openclaw#16311) thanks @Yaxuan42
Verified:
- pnpm build
- pnpm check
- pnpm vitest run --config vitest.extensions.config.ts extensions/feishu/src/bot.test.ts extensions/feishu/src/media.test.ts

Co-authored-by: Yaxuan42 <184813557+Yaxuan42@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 22:49:05 -06:00
青雲 b28344eacc
fix(feishu): insert document blocks sequentially to preserve order (#26022) (openclaw#26172) thanks @echoVic
Verified:
- pnpm build
- pnpm check
- pnpm vitest run --config vitest.extensions.config.ts extensions/feishu/src/docx.test.ts

Co-authored-by: echoVic <16428813+echoVic@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 22:48:14 -06:00
Ayaan Zaidi 83698bf13e fix(macos): derive canonical APP_BUILD after deps install 2026-02-28 10:04:25 +05:30
Ayaan Zaidi af9edc98e4 fix(release): unify sparkle build policy and defaults 2026-02-28 10:04:25 +05:30
Logan Pritchett 3e55cc5811 appcast: fix sparkle version for 2026.2.26 2026-02-28 10:04:25 +05:30
Logan Pritchett 84adedd1cb macos: treat empty APP_BUILD as fallback 2026-02-28 10:04:25 +05:30
Logan Pritchett 0332dce203 macos: parse calver month/day as decimal for Sparkle build 2026-02-28 10:04:25 +05:30
Logan Pritchett e4ee585b73 release-check: align appcast floor with Sparkle build lanes 2026-02-28 10:04:25 +05:30
Logan Pritchett 08fd579412 macos: make derived Sparkle build unique for same-day releases 2026-02-28 10:04:25 +05:30
Logan Pritchett 266f10d47d docs: clarify Sparkle build version policy 2026-02-28 10:04:25 +05:30
Logan Pritchett 3be12b9fc4 release-check: validate appcast sparkle version floor 2026-02-28 10:04:25 +05:30
Logan Pritchett 7237b4666b macos: make default Sparkle build version monotonic 2026-02-28 10:04:25 +05:30
longfros 6e645300a8
docs(feishu): clarify oc_ group allowlist vs ou_ command allowFrom for /reset (#26835)
* docs(feishu): clarify oc_* group allowlist vs ou_* command allowFrom

* docs(feishu): avoid direct edits to generated zh-CN docs

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 22:30:05 -06:00
Cassius0924 4f8a54eeaa
docs: add cardkit permissions to Feishu channel setup (#9410)
- Add cardkit:card:read and cardkit:card:write to tenant scopes
- Format user scopes array for better readability
- Update both English and Chinese documentation

Co-authored-by: hezhizhou.606 <hezhizhou.606@bytedance.com>
2026-02-27 22:29:54 -06:00
傅洋 e4cb6a88be
fix(feishu): handle message_type "media" for video downloads (openclaw#25502) thanks @4ier
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: 4ier <5648066+4ier@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 22:28:37 -06:00
Yihao d9230b13a4
feat(feishu): skip reply-to in DM conversations (#13211)
In DM (p2p) chats, use message.create instead of message.reply
so that bot responses don't show a 'Reply to' quote. Group chats
retain the reply-to behavior for context clarity.

The typing indicator (emoji reaction on the user's message) is
preserved in DMs — only the reply reference in sent messages is
removed.

Changes:
- Add skipReplyToInMessages param to createFeishuReplyDispatcher
- In bot.ts, set skipReplyToInMessages: !isGroup for both dispatch sites
- In reply-dispatcher.ts, use sendReplyToMessageId (undefined for DMs)
  for message sending while keeping replyToMessageId for typing indicator
2026-02-27 22:24:42 -06:00
neverland 6a8d83b6dd
fix(feishu): Remove incorrect oc_ prefix assumption in resolveFeishuSession (#10407)
* fix(feishu): remove incorrect oc_ prefix assumption in resolveFeishuSession

- Feishu oc_ is a generic chat_id that can represent both groups and DMs
- Must use chat_mode field from API to distinguish, not ID prefix
- Only ou_/on_ prefixes reliably indicate user IDs (always DM)
- Fixes session misrouting for DMs with oc_ chat IDs

This bug caused DM messages with oc_ chat_ids to be incorrectly
created as group sessions, breaking session isolation and routing.

* docs: update Feishu ID format comment to reflect oc_ ambiguity

The previous comment incorrectly stated oc_ is always a group chat.
This update clarifies that oc_ chat_ids can be either groups or DMs,
and explicit prefixes (dm:/group:) should be used to distinguish.

* feishu: add regression coverage for oc session routing

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 22:16:20 -06:00
Ayaan Zaidi 079bc24613 fix: add changelog entry for android capability parity (#29398) 2026-02-28 09:27:54 +05:30
Ayaan Zaidi 1bc9da8f9e fix(android): stabilize motion sampling and gate pedometer command 2026-02-28 09:27:54 +05:30
Ayaan Zaidi 18e7938dfd refactor(android): remove unreachable motion classify branch 2026-02-28 09:27:54 +05:30
Ayaan Zaidi 943dce37be feat(android): wire new device capabilities into runtime 2026-02-28 09:27:54 +05:30
Ayaan Zaidi b9e474deb4 feat(android): add motion activity and pedometer handlers 2026-02-28 09:27:54 +05:30
Ayaan Zaidi f75385981a feat(android): add calendar capability handlers 2026-02-28 09:27:54 +05:30
Ayaan Zaidi 81ebe7de46 feat(android): add contacts capability handlers 2026-02-28 09:27:54 +05:30
Ayaan Zaidi c8ad229776 feat(android): add photos latest handler 2026-02-28 09:27:54 +05:30
Ayaan Zaidi f637cbd246 feat(android): add system notification handler 2026-02-28 09:27:54 +05:30
Haitian 107be4e909
feat(feishu): add global groupSenderAllowFrom for sender-level group access control (openclaw#29174) thanks @1MoreBuild
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: 1MoreBuild <11406106+1MoreBuild@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 21:49:47 -06:00
Tak Hoffman aef5355102
fix(feishu): add reactionNotifications mode gating (openclaw#29388) thanks @Takhoffman
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 21:47:12 -06:00
TIHU 0e4c24ebe2
fix(feishu): auto-convert local image path text to image message in outbound (openclaw#29264) thanks @paceyw
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: paceyw <44923937+paceyw@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 21:29:11 -06:00
Ayaan Zaidi 3f06693e7d refactor(android): share node capability and command manifest 2026-02-28 08:46:50 +05:30
tsu-builds f53ef73a2b
feat(feishu): add support for merge_forward message parsing (openclaw#28707) thanks @tsu-builds
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: tsu-builds <264409075+tsu-builds@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 20:57:18 -06:00
Lin Z 8241145ada
feat(feishu): add reaction event support (created/deleted) (openclaw#16716) thanks @schumilin
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: schumilin <2003498+schumilin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 20:54:24 -06:00
Ayaan Zaidi afa7ac1f68 docs: update changelog for telegram outbound chunking (#29342) (thanks @obviyus) 2026-02-28 08:13:59 +05:30
Ayaan Zaidi 2bef2910f1 fix: preserve whitespace in telegram html retry chunking 2026-02-28 08:13:59 +05:30
Ayaan Zaidi 69c39368ec fix: enforce telegram shared outbound chunking 2026-02-28 08:13:59 +05:30
Sid 4221b5f809
fix: pass rootId to streaming card in Feishu topic groups (openclaw#28346) thanks @Sid-Qin
Verified:
- pnpm check
- pnpm test extensions/feishu/src/reply-dispatcher.test.ts

Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 20:20:53 -06:00
Shawn da00ead652
fix(feishu): parse code blocks and share_chat messages (openclaw#28591) thanks @kevinWangSheng
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: kevinWangSheng <118158941+kevinWangSheng@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 20:15:48 -06:00
kcinzgg 89669a33bd
feat(feishu): add replyInThread configuration for message replies (openclaw#27325) thanks @kcinzgg
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: kcinzgg <13964709+kcinzgg@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 19:53:02 -06:00
Vincent Koc 50aa6a43ed
fix(model): preserve reasoning in provider fallback resolution (#29285)
* fix(model): preserve reasoning in provider fallback resolution

* test(model): cover fallback reasoning propagation
2026-02-27 17:38:22 -08:00
Vincent Koc 8090cb4c5e
docs: missing changelog itesm (#29281)
* Changelog: add LanceDB custom baseUrl + dimensions entry (#17874)

* Changelog: add Ollama autodiscovery hardening entry (#29201)

* Changelog: add Ollama context-window unification entry (#29205)

* Changelog: add compaction audit injection removal entry (#28507)

* Changelog: add browser url alias entry (#29260)

* Changelog: add codex weekly usage label entry (#26267)
2026-02-27 17:31:09 -08:00
拐爷&&老拐瘦 36d69d05e2
feat(feishu): support sender/topic-scoped group session routing (openclaw#17798) thanks @yfge
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: yfge <1186273+yfge@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 19:26:36 -06:00
Vincent Koc ed51796d97
fix(browser): accept url alias for open and navigate (#29260)
* fix(browser): expose url alias in tool schema

* fix(browser): accept url alias for open and navigate

* test(browser): cover url alias for open and navigate
2026-02-27 17:25:59 -08:00
Sid e16d051d9f
fix: label Codex weekly usage window as "Week" instead of "Day" (#26267)
The secondary window label logic treated any window >= 24h as "Day",
but Codex plans can have a weekly (604800s / 168h) quota window.
The reset timer showed "resets 2d 4h" while the label said "Day",
which was confusing.

Now windows >= 168h are labeled "Week", >= 24h remain "Day", and
shorter windows show the hour count.

Closes #25812

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-27 17:23:01 -08:00
Vincent Koc f16ecd1dac
fix(ollama): unify context window handling across discovery, merge, and OpenAI-compat transport (#29205)
* fix(ollama): inject num_ctx for OpenAI-compatible transport

* fix(ollama): discover per-model context and preserve higher limits

* fix(agents): prefer matching provider model for fallback limits

* fix(types): require numeric token limits in provider model merge

* fix(types): accept unknown payload in ollama num_ctx wrapper

* fix(types): simplify ollama settled-result extraction

* config(models): add provider flag for Ollama OpenAI num_ctx injection

* config(schema): allow provider num_ctx injection flag

* config(labels): label provider num_ctx injection flag

* config(help): document provider num_ctx injection flag

* agents(ollama): gate OpenAI num_ctx injection with provider config

* tests(ollama): cover provider num_ctx injection flag behavior

* docs(config): list provider num_ctx injection option

* docs(ollama): document OpenAI num_ctx injection toggle

* docs(config): clarify merge token-limit precedence

* config(help): note merge uses higher model token limits

* fix(ollama): cap /api/show discovery concurrency

* fix(ollama): restrict num_ctx injection to OpenAI compat

* tests(ollama): cover ipv6 and compat num_ctx gating

* fix(ollama): detect remote compat endpoints for ollama-labeled providers

* fix(ollama): cap per-model /api/show lookups to bound discovery load
2026-02-27 17:20:47 -08:00
fuller-stack-dev 70a4f25ab1
fix(security): remove post-compaction audit injection message (#28507)
* fix: remove post-compaction audit injection (Layer 3)

Remove the post-compaction read audit that injects fake system messages
into conversations after context compaction. This audit:

- Hardcodes WORKFLOW_AUTO.md (a file that doesn't exist in standard
  workspaces) as a required read after every compaction
- Leaks raw regex syntax (memory\/\d{4}-\d{2}-\d{2}\.md) in
  user-facing warning messages
- Injects messages via enqueueSystemEvent that appear as user-role
  messages, tricking agents into reading attacker-controlled files
- Creates a persistent prompt injection vector (see #27697)

Layer 1 (compaction summary) and Layer 2 (workspace context refresh
from AGENTS.md via post-compaction-context.ts) remain intact and are
sufficient for post-compaction context recovery.

Deleted files:
- src/auto-reply/reply/post-compaction-audit.ts
- src/auto-reply/reply/post-compaction-audit.test.ts

Modified files:
- src/auto-reply/reply/agent-runner.ts (removed imports, audit map,
  flag setting, and Layer 3 audit block)

Fixes #27697, fixes #26851, fixes #20484, fixes #22339, fixes #25600
Relates to #26461

* fix: resolve lint failures from post-compaction audit removal

* Tests: add regression for removed post-compaction audit warnings

---------

Co-authored-by: Wilfred (OpenClaw Agent) <jay@openclaw.dev>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-02-27 17:15:59 -08:00
icesword0760 a509154be5
Feishu: send media payloads as attachments (openclaw#28959) thanks @icesword0760
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: icesword0760 <23316247+icesword0760@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 19:06:27 -06:00
Shadow 5cb2a3aa1b
Tests: validate discord slash command options 2026-02-27 18:41:16 -06:00
Madoka 32ee2f0109
fix(feishu): break infinite typing-indicator retry loop on rate-limit / quota errors (openclaw#28494) thanks @guoqunabc
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: guoqunabc <9532020+guoqunabc@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 18:41:08 -06:00
Glucksberg 0e755ad99a
fix(feishu): use msg_type "audio" for opus files instead of "media" (openclaw#28269) thanks @Glucksberg
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 18:23:19 -06:00
Glucksberg 60ef923051
fix(feishu): cache probeFeishu() results with 10-min TTL to reduce API calls (openclaw#28907) thanks @Glucksberg
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 18:15:28 -06:00
XuHao 56fa05838a
feat(feishu): support Docx table create/write + image/file upload actions in feishu_doc (#20304)
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 18:00:56 -06:00
大猫子 1725839720
fix(tools): honor tools.fs.workspaceOnly=false for host write/edit (#28822)
Merged via squash.

Prepared head SHA: 83d432961d
Co-authored-by: lailoo <20536249+lailoo@users.noreply.github.com>
Co-authored-by: velvet-shark <126378+velvet-shark@users.noreply.github.com>
Reviewed-by: @velvet-shark
2026-02-28 00:53:20 +01:00
OfflynAI ad804b0356
fix(feishu): propagate mediaLocalRoots for local file sends (#27884) (openclaw#27928) thanks @joelnishanth
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: joelnishanth <140015627+joelnishanth@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 17:43:57 -06:00
zhoulc777 bf9585d056
PR: Feishu Plugin - Auto-grant document permissions to requesting user (openclaw#28295) thanks @zhoulongchao77
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: zhoulongchao77 <65058500+zhoulongchao77@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 17:34:18 -06:00
Vincent Koc fa5e71d1ae
fix: harden Ollama autodiscovery and warning behavior (#29201)
* agents: auto-discover Ollama models without API key

* tests: cover Ollama autodiscovery warning behavior
2026-02-27 15:22:34 -08:00
Vincent Koc d17c083803
docs(ollama): clarify /v1 tool-calling guidance (#29204) 2026-02-27 15:21:13 -08:00
Agent de77497ea8 chore: add convex to sponsors table 2026-02-27 23:27:27 +01:00
Peter Steinberger 4aa2dc6857 fix(infra): land #29078 from @cathrynlavery with restart fallback
Co-authored-by: Cathryn Lavery <cathryn@littlemight.com>
2026-02-27 22:04:46 +00:00
Cathryn Lavery db67492a00 fix(infra): actively kickstart launchd on supervised gateway restart
When an agent triggers a gateway restart in supervised mode, the process
exits expecting launchd KeepAlive to respawn it. But ThrottleInterval
(default 10s, or 60s on older installs) can delay or prevent restart.

Now calls triggerOpenClawRestart() to issue an explicit launchctl
kickstart before exiting, ensuring immediate respawn. Falls back to
in-process restart if kickstart fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:02:05 +00:00
Sid ee2eaddeb3
fix(onboard): increase verification timeout and reduce max_tokens for custom provider probes (#27380)
* fix(onboard): increase verification timeout and reduce max_tokens for custom provider probes

The onboard wizard sends a chat-completion request to verify custom
providers.  With max_tokens: 1024 and a 10 s timeout, large local
models (e.g. Qwen3.5-27B on llama.cpp) routinely time out because
the server needs to load the model and generate up to 1024 tokens
before responding.

Changes:
- Raise VERIFY_TIMEOUT_MS from 10 s to 30 s
- Lower max_tokens from 1024 to 1 (verification only needs a single
  token to confirm the API is reachable and the model ID is valid)
- Add explicit stream: false to both OpenAI and Anthropic probes

Closes #27346

Made-with: Cursor

* Changelog: note custom-provider onboarding verification fix

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2026-02-27 22:51:58 +01:00
Shakker 46d9605ef8
merge-pr: use short squash merge banner 2026-02-27 21:41:24 +00:00
Philipp Spiess 12618c333c tests: complete openai-responses model fixture typing 2026-02-27 22:30:30 +01:00
bmendonca3 f943c76cde
security(feishu): bound unauthenticated webhook rate-limit state (openclaw#26050) thanks @bmendonca3
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: bmendonca3 <208517100+bmendonca3@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 13:22:24 -06:00
Bartok Moltbot 3882b8a5be ci: fix CONTRIBUTING.md oxfmt formatting
- Remove trailing blank line after Jonathan Taylor entry
- Escape underscore in @jlehman_ X handle

Fixes #29039
2026-02-27 11:12:04 -08:00
Peter Steinberger 8bc80fad47 fix(slack): land #29032 /agentstatus alias from @maloqab
Land contributor PR #29032 by @maloqab with Slack native alias docs, integration tests, and changelog entry.

Co-authored-by: maloqab <mitebaloqab@gmail.com>
2026-02-27 19:09:38 +00:00
Rodrigo Uroz 1867611733
fix(memory): readonly sync recovery (openclaw#25799) thanks @rodrigouroz
Verified:
- pnpm build
- pnpm check
- pnpm test:macmini (fails in this environment at src/daemon/launchd.integration.test.ts beforeAll hook timeout; merged with Tak override)

Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-27 12:26:43 -06:00
Josh Lehman 2916152f83 Add contributor Josh Lehman to CONTRIBUTING.md 2026-02-27 12:03:49 -06:00
Vincent Koc 15cf288d73 Update CHANGELOG.md 2026-02-27 09:11:58 -08:00
Peter Steinberger dede4089a6 docs(openai): add clear server compaction toggle examples 2026-02-27 16:21:08 +00:00
Peter Steinberger 645791c35e ci: add timeout for windows checks job 2026-02-27 16:20:02 +00:00
Peter Steinberger 8da3a9a92d fix(agents): auto-enable OpenAI Responses server-side compaction (#16930, #22441, #25088)
Landed from contributor PRs #16930, #22441, and #25088.

Co-authored-by: liweiguang <codingpunk@gmail.com>
Co-authored-by: EdwardWu7 <wuzhiyuan7@gmail.com>
Co-authored-by: MoerAI <friendnt@g.skku.edu>
2026-02-27 16:15:50 +00:00
Rishabh Jain 6675aacb5e
feat(memory-lancedb): Custom OpenAI BaseURL & Dimensions Support (#17874)
* feat(memory-lancedb): add custom baseUrl and dimensions support

* fix(memory-lancedb): strict model typing and safe dimension resolution

* style: fix formatting in memory-lancedb config

* fix(memory-lancedb): sync manifest schema with new embedding options

---------

Co-authored-by: OpenClaw Bot <bot@openclaw.ai>
2026-02-27 07:56:09 -08:00
Vincent Koc 62fa65ec85
Fix npm global install deprecation warnings (#28318)
* Dependencies: make @discordjs/opus an optional peer

* Dependencies: bump node-llama-cpp peer to 3.16.2

* Dependencies: pin Google auth deps to warning-free versions

* CI: reduce Dependabot cooldown to 2 days

* CI: fix invalid Dependabot npm registry config

* CI: restore Dependabot npm registry with token auth

* Dependencies: remove global Google auth pnpm overrides

* CI: make Dependabot updates daily

* Dependencies: restore optional install semantics for @discordjs/opus

* CI: keep Docker Dependabot interval weekly
2026-02-27 07:38:02 -08:00
Peter Steinberger fe807e4bed chore(release): bump 2026.2.27 and split changelog 2026-02-27 16:09:28 +01:00
Rodrigo Uroz 0fe6cf06b2
Compaction: preserve opaque identifiers in summaries (openclaw#25553) thanks @rodrigouroz
Verified:
- pnpm install --frozen-lockfile
- 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-02-27 08:14:05 -06:00
Daniel Reis 84a88b2ace
feat(i18n): add German (de) locale (#28495)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: e418326aaf
Co-authored-by: dsantoreis <220753637+dsantoreis@users.noreply.github.com>
Co-authored-by: Evizero <10854026+Evizero@users.noreply.github.com>
Reviewed-by: @Evizero
2026-02-27 11:44:09 +01:00
wangchunyue 6b317b1f17
fix(agents): normalize whitespace-padded tool call names before dispatch (#27094)
Fix tool-call lookup failures when models emit whitespace-padded names by normalizing
both transcript history and live streamed embedded-runner tool calls before dispatch.

Co-authored-by: wangchunyue <80630709+openperf@users.noreply.github.com>
Co-authored-by: Sid <sidqin0410@gmail.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2026-02-27 11:26:37 +01:00
Ayaan Zaidi aae90cb036
fix(telegram): include replied media files in reply context (#28488)
* fix(telegram): include replied media files in reply context

* fix(telegram): keep reply media fields nullable

* perf(telegram): defer reply-media fetch to debounce flush

* fix(telegram): gate and preserve reply media attachments

* fix(telegram): preserve cached-sticker reply media context

* fix: update changelog for telegram reply-media context fixes (#28488) (thanks @obviyus)
2026-02-27 15:16:21 +05:30
Onur Solmaz a7929abad8
Discord: thread bindings idle + max-age lifecycle (#27845) (thanks @osolmaz)
* refactor discord thread bindings to idle and max-age lifecycle

* fix: migrate legacy thread binding expiry and reduce hot-path disk writes

* refactor: remove remaining thread-binding ttl legacy paths

* fix: harden thread-binding lifecycle persistence

* Discord: fix thread binding types in message/reply paths

* Infra: handle win32 unknown inode in file identity checks

* Infra: relax win32 guarded-open identity checks

* Config: migrate threadBindings ttlHours to idleHours

* Revert "Infra: relax win32 guarded-open identity checks"

This reverts commit de94126771.

* Revert "Infra: handle win32 unknown inode in file identity checks"

This reverts commit 96fc5ddfb3.

* Discord: re-read live binding state before sweep unbind

* fix: add changelog note for thread binding lifecycle update (#27845) (thanks @osolmaz)

---------

Co-authored-by: Onur Solmaz <onur@textcortex.com>
2026-02-27 10:02:39 +01:00
Ayaan Zaidi 0fb7add7d6 fix: document canvas capability refresh params fix (#28413) (thanks @obviyus) 2026-02-27 13:26:42 +05:30
Ayaan Zaidi 3a35035512 fix(android): send object params for canvas capability refresh 2026-02-27 13:26:42 +05:30
Ayaan Zaidi 256021b8da fix: update changelog for android capability refresh land (#28388) (thanks @obviyus) 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 6222d6650b fix(android): avoid duplicate A2UI readiness probe on happy path 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 8187fbc571 fix(android): refresh scoped canvas URLs without trailing slash 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 4b37b7b6a9 fix(media): serve JavaScript assets with text/javascript 2026-02-27 12:16:36 +05:30
Ayaan Zaidi d53b24d185 fix(android): return valid debug.ed25519 diagnostics JSON 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 34486f8c10 fix(android): retry A2UI after canvas capability refresh 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 9b64ad30c4 docs(android): add integration test preconditions and pitfalls 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 72adf1e993 test(gateway): add live android capability integration suite 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 54eaf17327 feat(gateway): add node canvas capability refresh flow 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 0896bb09b0 feat(android): wire runtime canvas capability refresh 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 6ed00abc1e docs: document android capability sweep in testing guide 2026-02-27 12:16:36 +05:30
Ayaan Zaidi 7f6e822526 test: add android integration test script 2026-02-27 12:16:36 +05:30
Byungsker d911b0254d
fix(agents): demote Ollama empty-discovery log from warn to debug (#26379)
When Ollama responds successfully but returns zero models (e.g. on Linux
with the bundled `ollama-stub.service`), `discoverOllamaModels` was
logging at `warn` level:

  [agents/model-providers] No Ollama models found on local instance

This appeared on every agent invocation even when Ollama was not
intentionally configured, polluting production logs.  An empty model
list is a normal operational state — it warrants at most a debug
note, not a warning.

Fix: change `log.warn` → `log.debug` for the zero-models branch.
The error paths (HTTP failure, fetch exception) remain at `warn`
since those indicate genuine connectivity problems.

Closes #26354
2026-02-26 21:12:10 -08:00
Vincent Koc cb9374a2a1
Gateway: improve device-auth v2 migration diagnostics (#28305)
* Gateway: add device-auth detail code resolver

* Gateway: emit specific device-auth detail codes

* Gateway tests: cover nonce and signature detail codes

* Docs: add gateway device-auth migration diagnostics

* Docs: add device-auth v2 troubleshooting signatures
2026-02-26 21:05:43 -08:00
Vincent Koc 22ad7523f1
Docker: replace npm link with root CLI symlink (#28312) 2026-02-26 23:57:28 -05:00
Vincent Koc e8e673992a
CI: smoke test root Dockerfile openclaw CLI (#28308) 2026-02-26 23:54:17 -05:00
Yutaka Sasaki f5adb66bbc
fix: add npm link to fix CLI permission denied (exit 127) (#17151)
Co-authored-by: Yutaka Sasaki <sskyu@minio.local>
2026-02-26 23:47:45 -05:00
Ayaan Zaidi 2719398dd9 docs(changelog): note android node diagnostics and action updates (#28260) (thanks @obviyus) 2026-02-27 10:15:21 +05:30
Ayaan Zaidi 22d422a792 refactor(android-node): share battery snapshot parsing across device handlers 2026-02-27 10:15:21 +05:30
Ayaan Zaidi 284f75500c refactor(android-node): unify notifications snapshot rebind preflight 2026-02-27 10:15:21 +05:30
Ayaan Zaidi 1bf08ae7c9 refactor(nodes): map read actions to invoke commands 2026-02-27 10:15:21 +05:30
Ayaan Zaidi bbab0b005e fix(android): rebind listener before notification actions 2026-02-27 10:15:21 +05:30
Ayaan Zaidi 8807267bfd fix(android): allow open and reply on non-clearable notifications 2026-02-27 10:15:21 +05:30
Ayaan Zaidi b8373eaddc fix(nodes): reject facing=both when camera deviceId is set 2026-02-27 10:15:21 +05:30
Ayaan Zaidi 29f5da5b2a feat(nodes): expose device diagnostics and notification actions 2026-02-27 10:15:21 +05:30
Ayaan Zaidi d0ec3de588 feat(android): implement device diagnostics and notification actions 2026-02-27 10:15:21 +05:30
Ayaan Zaidi e99b323a6b feat(node): add device diagnostics and notification action commands 2026-02-27 10:15:21 +05:30
Ayaan Zaidi e48513d512 fix(android): scale invoke result ack timeout to invoke budget 2026-02-27 10:15:21 +05:30
Ayaan Zaidi c1e0f8cfb1 docs(nodes): document android camera list and device actions 2026-02-27 10:15:21 +05:30
Ayaan Zaidi 01f1d355a4 feat(nodes): add device status and info actions 2026-02-27 10:15:21 +05:30
Ayaan Zaidi 1f7b3c613d feat(android): add camera list and device selection 2026-02-27 10:15:21 +05:30
Vincent Koc c838a4dde0
Changelog: add missing npm update and plugin fix credits (#28257) 2026-02-26 22:52:50 -05:00
Ayaan Zaidi de885d260f fix: update changelog for android camera clip (#28229) (thanks @obviyus) 2026-02-27 09:10:10 +05:30
Ayaan Zaidi 0f7664fda3 fix(android): reject non-positive camera maxWidth 2026-02-27 09:10:10 +05:30
Ayaan Zaidi adb41e48ae test(android): cover camera clip payload size guard 2026-02-27 09:10:10 +05:30
Ayaan Zaidi fb34c46074 refactor(android): make camera clip transport deterministic 2026-02-27 09:10:10 +05:30
Ayaan Zaidi 120a7abbab test(android): cover camera clip upload URL JSON parsing 2026-02-27 09:10:10 +05:30
Ayaan Zaidi 67609cc16f fix(android): parse camera and screen invoke params as JSON 2026-02-27 09:10:10 +05:30
Vincent Koc 88a0d87490
Docs: align gateway config key paths with metadata (#28196)
* Docs: align gateway config key paths in reference

* Docs: expand config reference coverage for channels plugins and providers
2026-02-26 22:35:43 -05:00
Dale Yarborough efdba59e49
fix(plugins): clear error when npm package not found (Closes #24993) (#25073) 2026-02-26 22:16:28 -05:00
graysurf 7aa233790b
Fix npm-spec plugin installs when npm pack output is empty (#21039)
* fix(plugins): recover npm pack archive when stdout is empty

* test(plugins): create npm pack archive in metadata mock

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-02-26 22:00:24 -05:00
Ayaan Zaidi 9d52dcf1f4 fix: stabilize launchd CA env tests (#27915) (thanks @Lukavyi) 2026-02-27 08:11:16 +05:30
clawdbot 6b59c87570 fix: add missing closing brace in proxy env test 2026-02-27 08:11:16 +05:30
Clawborn d33f24c4e9 Fix NODE_EXTRA_CA_CERTS missing from LaunchAgent environment on macOS
launchd services do not inherit the shell environment, so Node's undici/fetch
cannot locate the macOS system CA bundle (/etc/ssl/cert.pem). This causes TLS
verification failures for all HTTPS requests (e.g. Telegram, webhooks) when the
gateway runs as a LaunchAgent, while the same gateway works fine in a terminal.

Add NODE_EXTRA_CA_CERTS defaulting to /etc/ssl/cert.pem on macOS in both
buildServiceEnvironment and buildNodeServiceEnvironment. User-supplied
NODE_EXTRA_CA_CERTS is always respected and takes precedence.

Fixes #22856

Co-authored-by: Clawborn <tianrun.yang103@gmail.com>
2026-02-27 08:11:16 +05:30
Xinhua Gu 7bbfb9de5e
fix(update): fallback to --omit=optional when global npm update fails (#24896)
* fix(update): fallback to --omit=optional when global npm update fails

* fix(update): add recovery hints and fallback for npm global update failures

* chore(update): align fallback progress step index ordering

* chore(update): label omit-optional retry step in progress output

* chore(update): avoid showing 1/2 when fallback path is not used

* chore(ci): retrigger after unrelated test OOM

* fix(update): scope recovery hints to npm failures

* test(update): cover non-npm hint suppression

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-02-26 21:35:13 -05:00
Ayaan Zaidi 418111adb9 docs(telegram): align channel docs with runtime behavior 2026-02-27 08:00:29 +05:30
Ayaan Zaidi 7149ba5574 docs: remove legacy grammy page 2026-02-27 08:00:29 +05:30
Ayaan Zaidi 035a2dbb40 docs: consolidate grammy links to telegram 2026-02-27 08:00:29 +05:30
Ayaan Zaidi 1f68010bd6 docs(telegram): clarify group auth boundary 2026-02-27 08:00:29 +05:30
Philipp Spiess 35e40f1139 ui: remove Google Fonts import blocked by CSP (style-src 'self' 'unsafe-inline'); fonts never loaded; closes #28038 2026-02-27 01:44:41 +01:00
Peter Steinberger 5c776be60b test: stabilize docker live model suites 2026-02-27 01:21:45 +01:00
Peter Steinberger bc50708057 chore(release): cut 2026.2.26 2026-02-27 00:58:16 +01:00
Sid e6be26ef1c
fix(provider): normalize bare gemini-3 Pro model IDs for google-antigravity (#24145)
* fix(provider): normalize bare gemini-3 Pro model IDs for google-antigravity

The Antigravity Cloud Code Assist API requires a thinking-tier suffix
(-low or -high) for all Gemini 3 Pro variants.  When a user configures
a bare model ID like `gemini-3.1-pro`, the API returns a 404 because it
only recognises `gemini-3.1-pro-low` or `gemini-3.1-pro-high`.

Add `normalizeAntigravityModelId()` that appends `-low` (the default
tier) to bare Pro model IDs, and apply it during provider normalisation
for `google-antigravity`.  Also refactor the per-provider model
normalisation into a shared `normalizeProviderModels()` helper.

Closes #24071

Co-authored-by: Cursor <cursoragent@cursor.com>

* Tests: cover antigravity model ID normalization

* Changelog: note antigravity pro tier normalization

* Tests: type antigravity model helper inputs

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-02-26 18:53:46 -05:00
Byungsker 17578d77e1
fix(agents): add forward-compat fallback for google-gemini-cli gemini-3.1-pro/flash-preview (#26570)
* fix(agents): add "google" provider to isReasoningTagProvider to prevent reasoning leak

The gemini-api-key auth flow creates a profile with provider "google"
(e.g. google/gemini-3-pro-preview), but isReasoningTagProvider only
matched "google-gemini-cli" (OAuth) and "google-generative-ai". As a
result:
- reasoningTagHint was false → system prompt omitted <think>/<final>
  formatting instructions
- enforceFinalTag was false → <final> tag filtering was skipped

Raw <think> reasoning output was delivered to the end user.

Fix: add the bare "google" provider string to the match list and cover
it with two new test cases (exact match + case-insensitive).

Fixes #26551

* fix(agents): add forward-compat fallback for google-gemini-cli gemini-3.1-pro/flash-preview

gemini-3.1-pro-preview and gemini-3.1-flash-preview are not yet present in
pi-ai's built-in google-gemini-cli model catalog (only gemini-3-pro-preview
and gemini-3-flash-preview are registered). When users configure these models
they get "Unknown model" errors even though Gemini CLI OAuth supports them.

The codebase already has isGemini31Model() in extra-params.ts, which proves
intent to support these models. Add a resolveGoogleGeminiCli31ForwardCompatModel
entry to resolveForwardCompatModel following the same clone-template pattern
used for zai/glm-5 and anthropic 4.6 models.

- gemini-3.1-pro-* clones gemini-3-pro-preview (with reasoning: true)
- gemini-3.1-flash-* clones gemini-3-flash-preview (with reasoning: true)

Also add test helpers and three test cases to model.forward-compat.test.ts.

Fixes #26524

* Changelog: credit Google Gemini provider fallback fixes

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-02-26 18:39:13 -05:00
Philipp Spiess d320b30b9b Docs: expand ACP first-use naming and link protocol site 2026-02-27 00:33:58 +01:00
Peter Steinberger 297cca0565 docs(cli): improve secrets command guide 2026-02-27 00:20:02 +01:00
Peter Steinberger 1d43202930 fix: repair Telegram allowlist DM migrations (#27936) (thanks @widingmarcus-cyber) 2026-02-26 22:53:13 +00:00
Vincent Koc 2c6b078ff0
Changelog: include Gemini OAuth PRs #16683 and #16684 (#27987) 2026-02-26 17:50:53 -05:00
Peter Steinberger 7dad7cc2ca fix(ci): align sync boundary realpath canonicalization 2026-02-26 23:48:38 +01:00
Peter Steinberger 5b62d5603d fix: unblock CI minimatch audit and host policy check 2026-02-26 22:48:09 +00:00
Peter Steinberger c35368c6dd fix(ios): eliminate Swift warnings and clean build logs 2026-02-26 22:42:23 +00:00
Peter Steinberger 22c74d416b chore(release): point appcast to beta tag 2026-02-26 23:38:20 +01:00
5514 changed files with 558726 additions and 116688 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

@ -7,10 +7,6 @@
[exclude-files]
# pnpm lockfiles contain lots of high-entropy package integrity blobs.
pattern = (^|/)pnpm-lock\.yaml$
# Generated output and vendored assets.
pattern = (^|/)(dist|vendor)/
# Local config file with allowlist patterns.
pattern = (^|/)\.detect-secrets\.cfg$
[exclude-lines]
# Fastlane checks for private key marker; not a real key.
@ -28,3 +24,22 @@ pattern = "talk\.apiKey"
pattern = === "string"
# specific optional-chaining password check that didn't match the line above.
pattern = typeof remote\?\.password === "string"
# Docker apt signing key fingerprint constant; not a secret.
pattern = OPENCLAW_DOCKER_GPG_FINGERPRINT=
# Credential matrix metadata field in docs JSON; not a secret value.
pattern = "secretShape": "(secret_input|sibling_ref)"
# Docs line describing API key rotation knobs; not a credential.
pattern = API key rotation \(provider-specific\): set `\*_API_KEYS`
# Docs line describing remote password precedence; not a credential.
pattern = passw[o]rd: `OPENCLAW_GATEWAY_PASSW[O]RD` -> `gateway\.auth\.passw[o]rd` -> `gateway\.remote\.passw[o]rd`
pattern = passw[o]rd: `OPENCLAW_GATEWAY_PASSW[O]RD` -> `gateway\.remote\.passw[o]rd` -> `gateway\.auth\.passw[o]rd`
# Test fixture starts a multiline fake private key; detector should ignore the header line.
pattern = const key = `-----BEGIN PRIVATE KEY-----
# Docs examples: literal placeholder API key snippets and shell heredoc helper.
pattern = export CUSTOM_API_K[E]Y="your-key"
pattern = grep -q 'N[O]DE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache' ~/.bashrc \|\| cat >> ~/.bashrc <<'EOF'
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 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
@ -51,6 +57,10 @@ vendor/
# Keep the rest of apps/ and vendor/ excluded to avoid a large build context.
!apps/shared/
!apps/shared/OpenClawKit/
!apps/shared/OpenClawKit/Sources/
!apps/shared/OpenClawKit/Sources/OpenClawKit/
!apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/
!apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json
!apps/shared/OpenClawKit/Tools/
!apps/shared/OpenClawKit/Tools/CanvasA2UI/
!apps/shared/OpenClawKit/Tools/CanvasA2UI/**

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

@ -1,5 +1,5 @@
name: Bug report
description: Report a defect or unexpected behavior in OpenClaw.
description: Report defects, including regressions, crashes, and behavior bugs.
title: "[Bug]: "
labels:
- bug
@ -8,6 +8,17 @@ body:
attributes:
value: |
Thanks for filing this report. Keep it concise, reproducible, and evidence-based.
- type: dropdown
id: bug_type
attributes:
label: Bug type
description: Choose the category that best matches this report.
options:
- Regression (worked before, now fails)
- Crash (process/app exits or hangs)
- Behavior bug (incorrect output/state without crash)
validations:
required: true
- type: textarea
id: summary
attributes:
@ -65,6 +76,37 @@ 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: input
id: config_location
attributes:
label: Config file / key location
description: Optional. Relevant config source or key path if this bug depends on overrides or custom provider setup. Redact secrets.
placeholder: ~/.openclaw/openclaw.json ; models.providers.cloudflare-ai-gateway.baseUrl ; ~/.openclaw/agents/<agentId>/agent/models.json
- type: textarea
id: provider_setup_details
attributes:
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:
@ -91,5 +133,5 @@ body:
id: additional_information
attributes:
label: Additional information
description: Add any context that helps triage but does not fit above.
placeholder: Regression started after upgrade from <previous-version>; temporary workaround is ...
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 ...

View File

@ -8,6 +8,7 @@ self-hosted-runner:
- blacksmith-8vcpu-windows-2025
- blacksmith-16vcpu-ubuntu-2404
- blacksmith-16vcpu-windows-2025
- blacksmith-32vcpu-windows-2025
- blacksmith-16vcpu-ubuntu-2404-arm
# Ignore patterns for known issues

View File

@ -0,0 +1,47 @@
name: Ensure base commit
description: Ensure a shallow checkout has enough history to diff against a base SHA.
inputs:
base-sha:
description: Base commit SHA to diff against.
required: true
fetch-ref:
description: Branch or ref to deepen/fetch from origin when base-sha is missing.
required: true
runs:
using: composite
steps:
- name: Ensure base commit is available
shell: bash
env:
BASE_SHA: ${{ inputs.base-sha }}
FETCH_REF: ${{ inputs.fetch-ref }}
run: |
set -euo pipefail
if [ -z "$BASE_SHA" ] || [[ "$BASE_SHA" =~ ^0+$ ]]; then
echo "No concrete base SHA available; skipping targeted fetch."
exit 0
fi
if git rev-parse --verify "$BASE_SHA^{commit}" >/dev/null 2>&1; then
echo "Base commit already present: $BASE_SHA"
exit 0
fi
for deepen_by in 25 100 300; do
echo "Base commit missing; deepening $FETCH_REF by $deepen_by."
git fetch --no-tags --deepen="$deepen_by" origin "$FETCH_REF" || true
if git rev-parse --verify "$BASE_SHA^{commit}" >/dev/null 2>&1; then
echo "Resolved base commit after deepening: $BASE_SHA"
exit 0
fi
done
echo "Base commit still missing; fetching full history for $FETCH_REF."
git fetch --no-tags origin "$FETCH_REF" || true
if git rev-parse --verify "$BASE_SHA^{commit}" >/dev/null 2>&1; then
echo "Resolved base commit after full ref fetch: $BASE_SHA"
exit 0
fi
echo "Base commit still unavailable after fetch attempts: $BASE_SHA"

View File

@ -1,12 +1,16 @@
name: Setup Node environment
description: >
Initialize submodules with retry, install Node 22, pnpm, optionally Bun,
and run pnpm install. Requires actions/checkout to run first.
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
@ -15,6 +19,14 @@ inputs:
description: Whether to install Bun alongside Node.
required: false
default: "true"
use-sticky-disk:
description: Request Blacksmith sticky-disk pnpm caching on trusted runs; pull_request runs fall back to actions/cache.
required: false
default: "false"
install-deps:
description: Whether to run pnpm install after environment setup.
required: false
default: "true"
frozen-lockfile:
description: Whether to use --frozen-lockfile for install.
required: false
@ -37,22 +49,23 @@ 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: true
check-latest: false
- name: Setup pnpm + cache store
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+cf6cdbbba"
bun-version: "1.3.9"
- name: Runtime versions
shell: bash
@ -63,10 +76,12 @@ runs:
if command -v bun &>/dev/null; then bun -v; fi
- name: Capture node path
if: inputs.install-deps == 'true'
shell: bash
run: echo "NODE_BIN=$(dirname "$(node -p "process.execPath")")" >> "$GITHUB_ENV"
- name: Install dependencies
if: inputs.install-deps == 'true'
shell: bash
env:
CI: "true"

View File

@ -8,7 +8,19 @@ 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 on trusted runs; pull_request runs fall back to actions/cache.
required: false
default: "false"
use-restore-keys:
description: Whether to use restore-keys fallback for actions/cache.
required: false
default: "true"
use-actions-cache:
description: Whether to restore/save pnpm store with actions/cache, including pull_request fallback when sticky disks are disabled.
required: false
default: "true"
runs:
using: composite
steps:
@ -38,8 +50,25 @@ runs:
shell: bash
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
- name: Restore pnpm store cache
uses: actions/cache@v4
- name: Mount pnpm store sticky disk
# Keep persistent sticky-disk state off untrusted PR runs.
if: inputs.use-sticky-disk == 'true' && github.event_name != 'pull_request'
uses: useblacksmith/stickydisk@v1
with:
key: ${{ github.repository }}-pnpm-store-${{ runner.os }}-${{ github.ref_name }}-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }}
path: ${{ steps.pnpm-store.outputs.path }}
- name: Restore pnpm store cache (exact key only)
# PRs that request sticky disks still need a safe cache restore path.
if: inputs.use-actions-cache == 'true' && (inputs.use-sticky-disk != 'true' || github.event_name == 'pull_request') && inputs.use-restore-keys != 'true'
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-pnpm-store-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Restore pnpm store cache (with fallback keys)
if: inputs.use-actions-cache == 'true' && (inputs.use-sticky-disk != 'true' || github.event_name == 'pull_request') && inputs.use-restore-keys == 'true'
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"

View File

@ -7,6 +7,7 @@ registries:
npm-npmjs:
type: npm-registry
url: https://registry.npmjs.org
token: ${{secrets.NPM_NPMJS_TOKEN}}
replaces-base: true
updates:
@ -14,9 +15,9 @@ updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
interval: daily
cooldown:
default-days: 7
default-days: 2
groups:
production:
dependency-type: production
@ -36,9 +37,9 @@ updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
interval: daily
cooldown:
default-days: 7
default-days: 2
groups:
actions:
patterns:
@ -52,9 +53,9 @@ updates:
- package-ecosystem: swift
directory: /apps/macos
schedule:
interval: weekly
interval: daily
cooldown:
default-days: 7
default-days: 2
groups:
swift-deps:
patterns:
@ -68,9 +69,9 @@ updates:
- package-ecosystem: swift
directory: /apps/shared/MoltbotKit
schedule:
interval: weekly
interval: daily
cooldown:
default-days: 7
default-days: 2
groups:
swift-deps:
patterns:
@ -84,9 +85,9 @@ updates:
- package-ecosystem: swift
directory: /Swabble
schedule:
interval: weekly
interval: daily
cooldown:
default-days: 7
default-days: 2
groups:
swift-deps:
patterns:
@ -100,9 +101,9 @@ updates:
- package-ecosystem: gradle
directory: /apps/android
schedule:
interval: weekly
interval: daily
cooldown:
default-days: 7
default-days: 2
groups:
android-deps:
patterns:
@ -118,7 +119,7 @@ updates:
schedule:
interval: weekly
cooldown:
default-days: 7
default-days: 2
groups:
docker-images:
patterns:

6
.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":

View File

@ -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,17 +20,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 }}
- name: Handle labeled items
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
- uses: actions/create-github-app-token@v2
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
github-token: ${{ steps.app-token.outputs.token }}
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Handle labeled items
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
// Labels prefixed with "r:" are auto-response triggers.
const activePrLimit = 10;
const rules = [
{
label: "r: skill",
@ -41,6 +52,21 @@ jobs:
message:
"Please use [our support server](https://discord.gg/clawd) and ask in #help or #users-helping-users to resolve this, or follow the stuck FAQ at https://docs.openclaw.ai/help/faq#im-stuck-whats-the-fastest-way-to-get-unstuck.",
},
{
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" +
"Thank you.",
},
{
label: "r: too-many-prs",
close: true,
message:
`Closing this PR because the author has more than ${activePrLimit} active PRs in this repo. ` +
"Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit.",
},
{
label: "r: testflight",
close: true,
@ -70,6 +96,116 @@ jobs:
const mentionRegex = /@([A-Za-z0-9-]+)/g;
const maintainerCache = new Map();
const normalizeLogin = (login) => login.toLowerCase();
const bugSubtypeLabelSpecs = {
regression: {
color: "D93F0B",
description: "Behavior that previously worked and now fails",
},
"bug:crash": {
color: "B60205",
description: "Process/app exits unexpectedly or hangs",
},
"bug:behavior": {
color: "D73A4A",
description: "Incorrect behavior without a crash",
},
};
const bugTypeToLabel = {
"Regression (worked before, now fails)": "regression",
"Crash (process/app exits or hangs)": "bug:crash",
"Behavior bug (incorrect output/state without crash)": "bug:behavior",
};
const bugSubtypeLabels = Object.keys(bugSubtypeLabelSpecs);
const extractIssueFormValue = (body, field) => {
if (!body) {
return "";
}
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(
`(?:^|\\n)###\\s+${escapedField}\\s*\\n([\\s\\S]*?)(?=\\n###\\s+|$)`,
"i",
);
const match = body.match(regex);
if (!match) {
return "";
}
for (const line of match[1].split("\n")) {
const trimmed = line.trim();
if (trimmed) {
return trimmed;
}
}
return "";
};
const ensureLabelExists = async (name, color, description) => {
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name,
});
} catch (error) {
if (error?.status !== 404) {
throw error;
}
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name,
color,
description,
});
}
};
const syncBugSubtypeLabel = async (issue, labelSet) => {
if (!labelSet.has("bug")) {
return;
}
const selectedBugType = extractIssueFormValue(issue.body ?? "", "Bug type");
const targetLabel = bugTypeToLabel[selectedBugType];
if (!targetLabel) {
return;
}
const targetSpec = bugSubtypeLabelSpecs[targetLabel];
await ensureLabelExists(targetLabel, targetSpec.color, targetSpec.description);
for (const subtypeLabel of bugSubtypeLabels) {
if (subtypeLabel === targetLabel) {
continue;
}
if (!labelSet.has(subtypeLabel)) {
continue;
}
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: subtypeLabel,
});
labelSet.delete(subtypeLabel);
} catch (error) {
if (error?.status !== 404) {
throw error;
}
}
}
if (!labelSet.has(targetLabel)) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: [targetLabel],
});
labelSet.add(targetLabel);
}
};
const isMaintainer = async (login) => {
if (!login) {
@ -129,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;
@ -194,6 +332,8 @@ jobs:
body: pingWarningMessage,
});
}
await syncBugSubtypeLabel(issue, labelSet);
}
}
@ -256,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.";
@ -292,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,
@ -303,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,
@ -314,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,17 +22,24 @@ jobs:
docs_changed: ${{ steps.check.outputs.docs_changed }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-depth: 1
fetch-tags: false
submodules: false
- name: Ensure docs-scope base commit
uses: ./.github/actions/ensure-base-commit
with:
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
- name: Detect docs-only changes
id: check
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.
# 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'
@ -38,13 +48,22 @@ jobs:
run_node: ${{ steps.scope.outputs.run_node }}
run_macos: ${{ steps.scope.outputs.run_macos }}
run_android: ${{ steps.scope.outputs.run_android }}
run_skills_python: ${{ steps.scope.outputs.run_skills_python }}
run_windows: ${{ steps.scope.outputs.run_windows }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-depth: 1
fetch-tags: false
submodules: false
- name: Ensure changed-scope base commit
uses: ./.github/actions/ensure-base-commit
with:
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
- name: Detect changed scopes
id: scope
shell: bash
@ -57,93 +76,37 @@ jobs:
BASE="${{ github.event.pull_request.base.sha }}"
fi
CHANGED="$(git diff --name-only "$BASE" HEAD 2>/dev/null || echo "UNKNOWN")"
if [ "$CHANGED" = "UNKNOWN" ] || [ -z "$CHANGED" ]; then
# Fail-safe: run broad checks if detection fails.
echo "run_node=true" >> "$GITHUB_OUTPUT"
echo "run_macos=true" >> "$GITHUB_OUTPUT"
echo "run_android=true" >> "$GITHUB_OUTPUT"
exit 0
fi
run_node=false
run_macos=false
run_android=false
has_non_docs=false
has_non_native_non_docs=false
while IFS= read -r path; do
[ -z "$path" ] && continue
case "$path" in
docs/*|*.md|*.mdx)
continue
;;
*)
has_non_docs=true
;;
esac
case "$path" in
# Generated protocol models are already covered by protocol:check and
# should not force the full native macOS lane.
apps/macos/Sources/OpenClawProtocol/*|apps/shared/OpenClawKit/Sources/OpenClawProtocol/*)
;;
apps/macos/*|apps/ios/*|apps/shared/*|Swabble/*)
run_macos=true
;;
esac
case "$path" in
apps/android/*|apps/shared/*)
run_android=true
;;
esac
case "$path" in
src/*|test/*|extensions/*|packages/*|scripts/*|ui/*|.github/*|openclaw.mjs|package.json|pnpm-lock.yaml|pnpm-workspace.yaml|tsconfig*.json|vitest*.ts|tsdown.config.ts|.oxlintrc.json|.oxfmtrc.jsonc)
run_node=true
;;
esac
case "$path" in
apps/android/*|apps/ios/*|apps/macos/*|apps/shared/*|Swabble/*|appcast.xml)
;;
*)
has_non_native_non_docs=true
;;
esac
done <<< "$CHANGED"
# If there are non-doc files outside native app trees, keep Node checks enabled.
if [ "$run_node" = false ] && [ "$has_non_docs" = true ] && [ "$has_non_native_non_docs" = true ]; then
run_node=true
fi
echo "run_node=${run_node}" >> "$GITHUB_OUTPUT"
echo "run_macos=${run_macos}" >> "$GITHUB_OUTPUT"
echo "run_android=${run_android}" >> "$GITHUB_OUTPUT"
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
# Build dist once for Node-relevant changes and share it with downstream jobs.
build-artifacts:
needs: [docs-scope, changed-scope, check]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
needs: [docs-scope, changed-scope]
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
- name: Ensure secrets base commit (PR fast path)
if: github.event_name == 'pull_request'
uses: ./.github/actions/ensure-base-commit
with:
base-sha: ${{ github.event.pull_request.base.sha }}
fetch-ref: ${{ github.event.pull_request.base.ref }}
- 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: Upload dist artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: dist-build
path: dist/
@ -156,7 +119,7 @@ jobs:
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -164,9 +127,10 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Download dist artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: dist-build
path: dist/
@ -175,8 +139,8 @@ jobs:
run: pnpm release:check
checks:
needs: [docs-scope, changed-scope, check]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
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
strategy:
fail-fast: false
@ -184,7 +148,20 @@ 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: protocol
command: pnpm protocol:check
@ -192,62 +169,51 @@ jobs:
task: test
command: pnpm canvas:a2ui:bundle && bunx vitest run --config vitest.unit.config.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 bun lane on pull requests
if: github.event_name == 'pull_request' && matrix.runtime == 'bun'
run: echo "Skipping Bun compatibility 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'
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
if: matrix.runtime != 'bun' || github.event_name != 'push'
if: matrix.runtime != 'bun' || github.event_name != 'pull_request'
uses: ./.github/actions/setup-node-env
with:
install-bun: "${{ matrix.runtime == 'bun' }}"
- name: Configure vitest JSON reports
if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node'
run: echo "OPENCLAW_VITEST_REPORT_DIR=$RUNNER_TEMP/vitest-reports" >> "$GITHUB_ENV"
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 == 'test' && matrix.runtime == 'node'
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: matrix.runtime != 'bun' || github.event_name != 'pull_request'
run: ${{ matrix.command }}
- name: Summarize slowest tests
if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node'
run: |
node scripts/vitest-slowest.mjs --dir "$OPENCLAW_VITEST_REPORT_DIR" --top 50 --out "$RUNNER_TEMP/vitest-slowest.md" > /dev/null
echo "Slowest test summary written to $RUNNER_TEMP/vitest-slowest.md"
- name: Upload vitest reports
if: (github.event_name != 'push' || matrix.runtime != 'bun') && matrix.task == 'test' && matrix.runtime == 'node'
uses: actions/upload-artifact@v4
with:
name: vitest-reports-${{ runner.os }}-${{ matrix.runtime }}
path: |
${{ env.OPENCLAW_VITEST_REPORT_DIR }}
${{ runner.temp }}/vitest-slowest.md
# Types, lint, and format check.
check:
name: "check"
needs: [docs-scope]
if: needs.docs-scope.outputs.docs_only != 'true'
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@v4
uses: actions/checkout@v6
with:
submodules: false
@ -255,52 +221,17 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Check types and lint and oxfmt
run: pnpm check
- name: Strict TS build smoke
run: pnpm build:strict-smoke
- name: Enforce safe external URL opening policy
run: pnpm lint:ui:no-raw-window-open
# Report-only dead-code scans. Runs after scope detection and stores machine-readable
# results as artifacts for later triage before we enable hard gates.
# Temporarily disabled in CI while we process initial findings.
deadcode:
name: dead-code report
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: false
runs-on: blacksmith-16vcpu-ubuntu-2404
strategy:
fail-fast: false
matrix:
include:
- tool: knip
command: pnpm deadcode:report:ci:knip
- tool: ts-prune
command: pnpm deadcode:report:ci:ts-prune
- tool: ts-unused-exports
command: pnpm deadcode:report:ci:ts-unused
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- name: Run ${{ matrix.tool }} dead-code scan
run: ${{ matrix.command }}
- name: Upload dead-code results
uses: actions/upload-artifact@v4
with:
name: dead-code-${{ matrix.tool }}-${{ github.run_id }}
path: .artifacts/deadcode
# Validate docs (format, lint, broken links) only when docs files changed.
check-docs:
needs: [docs-scope]
@ -308,7 +239,7 @@ jobs:
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -316,22 +247,57 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Check docs
run: pnpm check:docs
skills-python:
compat-node22:
name: "compat-node22"
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
- name: Setup Node 22 compatibility environment
uses: ./.github/actions/setup-node-env
with:
node-version: "22.x"
cache-key-suffix: "node22"
install-bun: "false"
use-sticky-disk: "false"
- name: Configure Node 22 test resources
run: |
# Keep the compatibility lane aligned with the default Node test lane.
echo "OPENCLAW_TEST_WORKERS=2" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144" >> "$GITHUB_ENV"
- name: Build under Node 22
run: pnpm build
- name: Run tests under Node 22
run: pnpm test
- name: Verify npm pack under Node 22
run: pnpm release:check
skills-python:
needs: [docs-scope, changed-scope]
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_skills_python == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout
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"
@ -350,31 +316,44 @@ 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:
install-bun: "false"
use-sticky-disk: "false"
install-deps: "false"
- name: Setup Python
uses: actions/setup-python@v5
id: setup-python
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: |
pyproject.toml
.pre-commit-config.yaml
.github/workflows/ci.yml
- name: Restore pre-commit cache
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') }}
- name: Install pre-commit
run: |
python -m pip install --upgrade pip
python -m pip install pre-commit detect-secrets==1.5.0
- name: Detect secrets
run: |
if ! detect-secrets scan --baseline .secrets.baseline; then
echo "::error::Secret scanning failed. See docs/gateway/security.md#secret-scanning-detect-secrets"
exit 1
fi
python -m pip install pre-commit
- name: Detect committed private keys
run: pre-commit run --all-files detect-private-key
@ -401,14 +380,15 @@ jobs:
run: pre-commit run --all-files pnpm-audit-prod
checks-windows:
needs: [docs-scope, changed-scope, build-artifacts, check]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_node == 'true')
runs-on: blacksmith-16vcpu-windows-2025
needs: [docs-scope, changed-scope]
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:
NODE_OPTIONS: --max-old-space-size=4096
# Keep total concurrency predictable on the 16 vCPU runner:
# `scripts/test-parallel.mjs` runs some vitest suites in parallel processes.
OPENCLAW_TEST_WORKERS: 2
NODE_OPTIONS: --max-old-space-size=6144
# Keep total concurrency predictable on the 32 vCPU runner.
# Windows shard 2 has shown intermittent instability at 2 workers.
OPENCLAW_TEST_WORKERS: 1
defaults:
run:
shell: bash
@ -416,29 +396,39 @@ jobs:
fail-fast: false
matrix:
include:
- runtime: node
task: lint
shard_index: 0
shard_count: 1
command: pnpm lint
- runtime: node
task: test
shard_index: 1
shard_count: 2
command: pnpm canvas:a2ui:bundle && pnpm test
shard_count: 6
command: pnpm test
- runtime: node
task: test
shard_index: 2
shard_count: 2
command: pnpm canvas:a2ui:bundle && pnpm test
shard_count: 6
command: pnpm test
- runtime: node
task: protocol
shard_index: 0
shard_count: 1
command: pnpm protocol:check
task: test
shard_index: 3
shard_count: 6
command: pnpm test
- runtime: node
task: test
shard_index: 4
shard_count: 6
command: pnpm test
- runtime: node
task: test
shard_index: 5
shard_count: 6
command: pnpm test
- runtime: node
task: test
shard_index: 6
shard_count: 6
command: pnpm test
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -461,31 +451,24 @@ jobs:
Write-Warning "Failed to apply Defender exclusions, continuing. $($_.Exception.Message)"
}
- name: Download dist artifact (lint lane)
if: matrix.task == 'lint'
uses: actions/download-artifact@v4
with:
name: dist-build
path: dist/
- name: Verify dist artifact (lint lane)
if: matrix.task == 'lint'
run: |
set -euo pipefail
test -s dist/index.js
test -s dist/plugin-sdk/index.js
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@v6
with:
node-version: 22.x
check-latest: true
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
# without the sticky-disk mount penalty.
use-sticky-disk: "false"
use-restore-keys: "false"
use-actions-cache: "true"
- name: Runtime versions
run: |
@ -504,7 +487,9 @@ jobs:
which node
node -v
pnpm -v
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
# Persist Windows-native postinstall outputs in the pnpm store so restored
# caches can skip repeated rebuild/download work on later shards/runs.
pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true || pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true
- name: Configure test shard (Windows)
if: matrix.task == 'test'
@ -512,28 +497,13 @@ jobs:
echo "OPENCLAW_TEST_SHARDS=${{ matrix.shard_count }}" >> "$GITHUB_ENV"
echo "OPENCLAW_TEST_SHARD_INDEX=${{ matrix.shard_index }}" >> "$GITHUB_ENV"
- name: Configure vitest JSON reports
- name: Build A2UI bundle (Windows)
if: matrix.task == 'test'
run: echo "OPENCLAW_VITEST_REPORT_DIR=$RUNNER_TEMP/vitest-reports" >> "$GITHUB_ENV"
run: pnpm canvas:a2ui:bundle
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
run: ${{ matrix.command }}
- name: Summarize slowest tests
if: matrix.task == 'test'
run: |
node scripts/vitest-slowest.mjs --dir "$OPENCLAW_VITEST_REPORT_DIR" --top 50 --out "$RUNNER_TEMP/vitest-slowest.md" > /dev/null
echo "Slowest test summary written to $RUNNER_TEMP/vitest-slowest.md"
- name: Upload vitest reports
if: matrix.task == 'test'
uses: actions/upload-artifact@v4
with:
name: vitest-reports-${{ runner.os }}-${{ matrix.runtime }}-shard${{ matrix.shard_index }}of${{ matrix.shard_count }}
path: |
${{ env.OPENCLAW_VITEST_REPORT_DIR }}
${{ runner.temp }}/vitest-slowest.md
# Consolidated macOS job: runs TS tests + Swift lint/build/test sequentially
# on a single runner. GitHub limits macOS concurrent jobs to 5 per org;
# running 4 separate jobs per PR (as before) starved the queue. One job
@ -544,7 +514,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -580,7 +550,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') }}
@ -616,7 +586,7 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: false
@ -772,8 +742,8 @@ jobs:
PY
android:
needs: [docs-scope, changed-scope, check]
if: needs.docs-scope.outputs.docs_only != 'true' && (github.event_name == 'push' || needs.changed-scope.outputs.run_android == 'true')
needs: [docs-scope, changed-scope]
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
@ -785,31 +755,45 @@ jobs:
command: ./gradlew --no-daemon :app:assembleDebug
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"

137
.github/workflows/codeql.yml vendored Normal file
View File

@ -0,0 +1,137 @@
name: CodeQL
on:
workflow_dispatch:
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
security-events: write
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ${{ matrix.runs_on }}
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
runs_on: blacksmith-16vcpu-ubuntu-2404
needs_node: true
needs_python: false
needs_java: false
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
needs_python: false
needs_java: false
needs_swift_tools: false
needs_manual_build: false
needs_autobuild: false
config_file: ""
- language: python
runs_on: blacksmith-16vcpu-ubuntu-2404
needs_node: false
needs_python: true
needs_java: false
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
needs_python: false
needs_java: true
needs_swift_tools: false
needs_manual_build: true
needs_autobuild: false
config_file: ""
- language: swift
runs_on: macos-latest
needs_node: false
needs_python: false
needs_java: false
needs_swift_tools: true
needs_manual_build: true
needs_autobuild: false
config_file: ""
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
- name: Setup Node environment
if: matrix.needs_node
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
use-sticky-disk: "false"
- name: Setup Python
if: matrix.needs_python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Setup Java
if: matrix.needs_java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"
- name: Setup Swift build tools
if: matrix.needs_swift_tools
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
uses: github/codeql-action/autobuild@v4
- name: Build Android for CodeQL
if: matrix.language == 'java-kotlin'
working-directory: apps/android
run: ./gradlew --no-daemon :app:assembleDebug
- name: Build Swift for CodeQL
if: matrix.language == 'swift'
run: |
set -euo pipefail
swift build --package-path apps/macos --configuration release
cd apps/ios
xcodegen generate
xcodebuild build \
-project OpenClaw.xcodeproj \
-scheme OpenClaw \
-destination "generic/platform=iOS Simulator" \
CODE_SIGNING_ALLOWED=NO
- name: Analyze
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"

View File

@ -12,33 +12,83 @@ 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:
# Build amd64 image
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
outputs:
image-digest: ${{ steps.build.outputs.digest }}
digest: ${{ steps.build.outputs.digest }}
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 Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Docker Builder
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 }}
@ -49,18 +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=()
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
slim_tags=()
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
{
@ -68,36 +122,86 @@ jobs:
printf "%s\n" "${tags[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
{
echo "slim<<EOF"
printf "%s\n" "${slim_tags[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- 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
source_sha="$(git rev-parse HEAD)"
version="${source_sha}"
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
version="main"
fi
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=${source_sha}"
echo "org.opencontainers.image.version=${version}"
echo "org.opencontainers.image.created=${created}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Build and push amd64 image
id: build
# 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
tags: ${{ steps.tags.outputs.value }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-cache:amd64
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-cache:amd64,mode=max
labels: ${{ steps.labels.outputs.value }}
provenance: false
push: true
# Build arm64 image
- name: Build and push amd64 slim image
id: build-slim
# 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
build-args: |
OPENCLAW_VARIANT=slim
tags: ${{ steps.tags.outputs.slim }}
labels: ${{ steps.labels.outputs.value }}
provenance: false
push: true
# 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
outputs:
image-digest: ${{ steps.build.outputs.digest }}
digest: ${{ steps.build.outputs.digest }}
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 Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Docker Builder
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 }}
@ -108,18 +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=()
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
slim_tags=()
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
{
@ -127,32 +235,80 @@ jobs:
printf "%s\n" "${tags[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
{
echo "slim<<EOF"
printf "%s\n" "${slim_tags[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- 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
source_sha="$(git rev-parse HEAD)"
version="${source_sha}"
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
version="main"
fi
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=${source_sha}"
echo "org.opencontainers.image.version=${version}"
echo "org.opencontainers.image.created=${created}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Build and push arm64 image
id: build
# 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
tags: ${{ steps.tags.outputs.value }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-cache:arm64
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-cache:arm64,mode=max
labels: ${{ steps.labels.outputs.value }}
provenance: false
push: true
# Create multi-platform manifest
- name: Build and push arm64 slim image
id: build-slim
# 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
build-args: |
OPENCLAW_VARIANT=slim
tags: ${{ steps.tags.outputs.slim }}
labels: ${{ steps.labels.outputs.value }}
provenance: false
push: true
# 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 }}
@ -163,21 +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=()
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
slim_tags=()
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}")
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]]; then
slim_tags+=("${IMAGE}:${version}-slim")
# 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
{
@ -185,8 +348,13 @@ jobs:
printf "%s\n" "${tags[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
{
echo "slim<<EOF"
printf "%s\n" "${slim_tags[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Create and push manifest
- name: Create and push default manifest
shell: bash
run: |
set -euo pipefail
@ -197,5 +365,19 @@ jobs:
args+=("-t" "$tag")
done
docker buildx imagetools create "${args[@]}" \
${{ needs.build-amd64.outputs.image-digest }} \
${{ needs.build-arm64.outputs.image-digest }}
${{ needs.build-amd64.outputs.digest }} \
${{ needs.build-arm64.outputs.digest }}
- name: Create and push slim manifest
shell: bash
run: |
set -euo pipefail
mapfile -t tags <<< "${{ steps.tags.outputs.slim }}"
args=()
for tag in "${tags[@]}"; do
[ -z "$tag" ] && continue
args+=("-t" "$tag")
done
docker buildx imagetools create "${args[@]}" \
${{ needs.build-amd64.outputs.slim-digest }} \
${{ needs.build-arm64.outputs.slim-digest }}

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,9 +20,16 @@ jobs:
docs_only: ${{ steps.check.outputs.docs_only }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-depth: 1
fetch-tags: false
- name: Ensure docs-scope base commit
uses: ./.github/actions/ensure-base-commit
with:
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
- name: Detect docs-only changes
id: check
@ -31,22 +41,66 @@ jobs:
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- name: Checkout CLI
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- name: Set up Docker Builder
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:
node-version: 22.x
check-latest: true
context: .
file: ./Dockerfile
tags: openclaw-dockerfile-smoke:local
load: true
push: false
provenance: false
- name: Setup pnpm + cache store
uses: ./.github/actions/setup-pnpm-store-cache
- 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.
- name: Build extension Dockerfile smoke image
uses: useblacksmith/build-push-action@v2
with:
pnpm-version: "10.23.0"
cache-key-suffix: "node22"
context: .
file: ./Dockerfile
build-args: |
OPENCLAW_EXTENSIONS=diagnostics-otel
tags: openclaw-ext-smoke:local
load: true
push: false
provenance: false
- name: Install pnpm deps (minimal)
run: pnpm install --ignore-scripts --frozen-lockfile
- name: Smoke test Dockerfile with extension build arg
run: |
docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc 'which openclaw && openclaw --version'
- name: Build installer smoke image
uses: useblacksmith/build-push-action@v2
with:
context: ./scripts/docker
file: ./scripts/docker/install-sh-smoke/Dockerfile
tags: openclaw-install-smoke:local
load: true
push: false
provenance: false
- name: Build installer non-root image
if: github.event_name != 'pull_request'
uses: useblacksmith/build-push-action@v2
with:
context: ./scripts/docker
file: ./scripts/docker/install-sh-nonroot/Dockerfile
tags: openclaw-install-nonroot:local
load: true
push: false
provenance: false
- name: Run installer docker tests
env:
@ -54,6 +108,8 @@ jobs:
CLAWDBOT_INSTALL_CLI_URL: https://openclaw.ai/install-cli.sh
CLAWDBOT_NO_ONBOARD: "1"
CLAWDBOT_INSTALL_SMOKE_SKIP_CLI: "1"
CLAWDBOT_INSTALL_SMOKE_SKIP_IMAGE_BUILD: "1"
CLAWDBOT_INSTALL_NONROOT_SKIP_IMAGE_BUILD: ${{ github.event_name == 'pull_request' && '0' || '1' }}
CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }}
CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
run: pnpm test:install:smoke
run: bash scripts/test-install-sh-docker.sh

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,20 +28,27 @@ 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/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5
- 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@v6
with:
configuration-path: .github/labeler.yml
repo-token: ${{ steps.app-token.outputs.token }}
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 }}
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
const pullRequest = context.payload.pull_request;
if (!pullRequest) {
@ -125,9 +135,9 @@ 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 }}
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
const login = context.payload.pull_request?.user?.login;
if (!login) {
@ -135,10 +145,10 @@ jobs:
}
const repo = `${context.repo.owner}/${context.repo.repo}`;
const trustedLabel = "trusted-contributor";
const experiencedLabel = "experienced-contributor";
const trustedThreshold = 4;
const experiencedThreshold = 10;
// const trustedLabel = "trusted-contributor";
// const experiencedLabel = "experienced-contributor";
// const trustedThreshold = 4;
// const experiencedThreshold = 10;
let isMaintainer = false;
try {
@ -163,36 +173,208 @@ jobs:
return;
}
const mergedQuery = `repo:${repo} is:pr is:merged author:${login}`;
let mergedCount = 0;
// trusted-contributor and experienced-contributor labels disabled.
// const mergedQuery = `repo:${repo} is:pr is:merged author:${login}`;
// let mergedCount = 0;
// try {
// const merged = await github.rest.search.issuesAndPullRequests({
// q: mergedQuery,
// per_page: 1,
// });
// mergedCount = merged?.data?.total_count ?? 0;
// } catch (error) {
// if (error?.status !== 422) {
// throw error;
// }
// core.warning(`Skipping merged search for ${login}; treating as 0.`);
// }
//
// if (mergedCount >= experiencedThreshold) {
// await github.rest.issues.addLabels({
// ...context.repo,
// issue_number: context.payload.pull_request.number,
// labels: [experiencedLabel],
// });
// return;
// }
//
// if (mergedCount >= trustedThreshold) {
// await github.rest.issues.addLabels({
// ...context.repo,
// issue_number: context.payload.pull_request.number,
// labels: [trustedLabel],
// });
// }
- name: Apply too-many-prs label
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
const pullRequest = context.payload.pull_request;
if (!pullRequest) {
return;
}
const 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`;
const authorLogin = pullRequest.user?.login;
if (!authorLogin) {
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(
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({
owner: context.repo.owner,
repo: context.repo.repo,
name: activePrLimitLabel,
});
} catch (error) {
if (error?.status !== 404) {
throw error;
}
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: activePrLimitLabel,
color: labelColor,
description: labelDescription,
});
}
};
const isPrivilegedAuthor = async () => {
if (pullRequest.author_association === "OWNER") {
return true;
}
let isMaintainer = false;
try {
const membership = await github.rest.teams.getMembershipForUserInOrg({
org: context.repo.owner,
team_slug: "maintainer",
username: authorLogin,
});
isMaintainer = membership?.data?.state === "active";
} catch (error) {
if (error?.status !== 404) {
throw error;
}
}
if (isMaintainer) {
return true;
}
try {
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: authorLogin,
});
const roleName = (permission?.data?.role_name ?? "").toLowerCase();
return roleName === "admin" || roleName === "maintain";
} catch (error) {
if (error?.status !== 404) {
throw error;
}
}
return false;
};
if (await isPrivilegedAuthor()) {
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;
}
let openPrCount = 0;
try {
const merged = await github.rest.search.issuesAndPullRequests({
q: mergedQuery,
const result = await github.rest.search.issuesAndPullRequests({
q: `repo:${context.repo.owner}/${context.repo.repo} is:pr is:open author:${authorLogin}`,
per_page: 1,
});
mergedCount = merged?.data?.total_count ?? 0;
openPrCount = result?.data?.total_count ?? 0;
} catch (error) {
if (error?.status !== 422) {
throw error;
}
core.warning(`Skipping merged search for ${login}; treating as 0.`);
core.warning(`Skipping open PR count for ${authorLogin}; treating as 0.`);
}
if (mergedCount >= experiencedThreshold) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: context.payload.pull_request.number,
labels: [experiencedLabel],
});
if (openPrCount > activePrLimit) {
await ensureLabelExists();
if (!labelNames.has(activePrLimitLabel)) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pullRequest.number,
labels: [activePrLimitLabel],
});
}
return;
}
if (mergedCount >= trustedThreshold) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: context.payload.pull_request.number,
labels: [trustedLabel],
});
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;
}
}
}
backfill-pr-labels:
@ -202,15 +384,22 @@ 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 }}
- name: Backfill PR labels
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
- uses: actions/create-github-app-token@v2
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
github-token: ${{ steps.app-token.outputs.token }}
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Backfill PR labels
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
@ -227,10 +416,10 @@ jobs:
const sizeLabels = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
const labelColor = "b76e79";
const trustedLabel = "trusted-contributor";
const experiencedLabel = "experienced-contributor";
const trustedThreshold = 4;
const experiencedThreshold = 10;
// const trustedLabel = "trusted-contributor";
// const experiencedLabel = "experienced-contributor";
// const trustedThreshold = 4;
// const experiencedThreshold = 10;
const contributorCache = new Map();
@ -280,27 +469,28 @@ jobs:
return "maintainer";
}
const mergedQuery = `repo:${repoFull} is:pr is:merged author:${login}`;
let mergedCount = 0;
try {
const merged = await github.rest.search.issuesAndPullRequests({
q: mergedQuery,
per_page: 1,
});
mergedCount = merged?.data?.total_count ?? 0;
} catch (error) {
if (error?.status !== 422) {
throw error;
}
core.warning(`Skipping merged search for ${login}; treating as 0.`);
}
// trusted-contributor and experienced-contributor labels disabled.
// const mergedQuery = `repo:${repoFull} is:pr is:merged author:${login}`;
// let mergedCount = 0;
// try {
// const merged = await github.rest.search.issuesAndPullRequests({
// q: mergedQuery,
// per_page: 1,
// });
// mergedCount = merged?.data?.total_count ?? 0;
// } catch (error) {
// if (error?.status !== 422) {
// throw error;
// }
// core.warning(`Skipping merged search for ${login}; treating as 0.`);
// }
let label = null;
if (mergedCount >= experiencedThreshold) {
label = experiencedLabel;
} else if (mergedCount >= trustedThreshold) {
label = trustedLabel;
}
const label = null;
// if (mergedCount >= experiencedThreshold) {
// label = experiencedLabel;
// } else if (mergedCount >= trustedThreshold) {
// label = trustedLabel;
// }
contributorCache.set(login, label);
return label;
@ -442,15 +632,22 @@ 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 }}
- name: Apply maintainer or trusted-contributor label
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
- uses: actions/create-github-app-token@v2
id: app-token-fallback
if: steps.app-token.outcome == 'failure'
with:
github-token: ${{ steps.app-token.outputs.token }}
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Apply maintainer or trusted-contributor label
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
script: |
const login = context.payload.issue?.user?.login;
if (!login) {
@ -458,10 +655,10 @@ jobs:
}
const repo = `${context.repo.owner}/${context.repo.repo}`;
const trustedLabel = "trusted-contributor";
const experiencedLabel = "experienced-contributor";
const trustedThreshold = 4;
const experiencedThreshold = 10;
// const trustedLabel = "trusted-contributor";
// const experiencedLabel = "experienced-contributor";
// const trustedThreshold = 4;
// const experiencedThreshold = 10;
let isMaintainer = false;
try {
@ -486,34 +683,35 @@ jobs:
return;
}
const mergedQuery = `repo:${repo} is:pr is:merged author:${login}`;
let mergedCount = 0;
try {
const merged = await github.rest.search.issuesAndPullRequests({
q: mergedQuery,
per_page: 1,
});
mergedCount = merged?.data?.total_count ?? 0;
} catch (error) {
if (error?.status !== 422) {
throw error;
}
core.warning(`Skipping merged search for ${login}; treating as 0.`);
}
if (mergedCount >= experiencedThreshold) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: context.payload.issue.number,
labels: [experiencedLabel],
});
return;
}
if (mergedCount >= trustedThreshold) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: context.payload.issue.number,
labels: [trustedLabel],
});
}
// trusted-contributor and experienced-contributor labels disabled.
// const mergedQuery = `repo:${repo} is:pr is:merged author:${login}`;
// let mergedCount = 0;
// try {
// const merged = await github.rest.search.issuesAndPullRequests({
// q: mergedQuery,
// per_page: 1,
// });
// mergedCount = merged?.data?.total_count ?? 0;
// } catch (error) {
// if (error?.status !== 422) {
// throw error;
// }
// core.warning(`Skipping merged search for ${login}; treating as 0.`);
// }
//
// if (mergedCount >= experiencedThreshold) {
// await github.rest.issues.addLabels({
// ...context.repo,
// issue_number: context.payload.issue.number,
// labels: [experiencedLabel],
// });
// return;
// }
//
// if (mergedCount >= trustedThreshold) {
// await github.rest.issues.addLabels({
// ...context.repo,
// issue_number: context.payload.issue.number,
// labels: [trustedLabel],
// });
// }

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

View File

@ -17,15 +17,21 @@ 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: docker/setup-buildx-action@v4
- name: Build minimal sandbox base (USER sandbox)
shell: bash
run: |

View File

@ -5,6 +5,9 @@ on:
- cron: "17 3 * * *"
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
permissions: {}
jobs:
@ -14,15 +17,24 @@ 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 }}
- name: Mark stale issues and pull requests
uses: actions/stale@v9
- uses: actions/create-github-app-token@v2
id: app-token-fallback
continue-on-error: true
with:
repo-token: ${{ steps.app-token.outputs.token }}
app-id: "2971289"
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
- name: Mark stale issues and pull requests (primary)
id: stale-primary
continue-on-error: true
uses: actions/stale@v10
with:
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
days-before-issue-stale: 7
days-before-issue-close: 5
days-before-pr-stale: 5
@ -31,7 +43,8 @@ jobs:
stale-pr-label: stale
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale
exempt-pr-labels: maintainer,no-stale
operations-per-run: 10000
operations-per-run: 2000
ascending: true
exempt-all-assignees: true
remove-stale-when-updated: true
stale-issue-message: |
@ -49,3 +62,156 @@ jobs:
Closing due to inactivity.
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.
- name: Check stale state cache
id: stale-state
if: always()
uses: actions/github-script@v8
with:
github-token: ${{ steps.app-token-fallback.outputs.token || steps.app-token.outputs.token }}
script: |
const cacheKey = "_state";
const { owner, repo } = context.repo;
try {
const { data } = await github.rest.actions.getActionsCacheList({
owner,
repo,
key: cacheKey,
});
const caches = data.actions_caches ?? [];
const hasState = caches.some(cache => cache.key === cacheKey);
core.setOutput("has_state", hasState ? "true" : "false");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
core.warning(`Failed to check stale state cache: ${message}`);
core.setOutput("has_state", "false");
}
- 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@v10
with:
repo-token: ${{ steps.app-token-fallback.outputs.token }}
days-before-issue-stale: 7
days-before-issue-close: 5
days-before-pr-stale: 5
days-before-pr-close: 3
stale-issue-label: stale
stale-pr-label: stale
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale
exempt-pr-labels: maintainer,no-stale
operations-per-run: 2000
ascending: true
exempt-all-assignees: true
remove-stale-when-updated: true
stale-issue-message: |
This issue has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.
stale-pr-message: |
This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.
close-issue-message: |
Closing due to inactivity.
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
If you are absolutely sure it still happens on the latest release, open a new issue with fresh repro steps.
close-issue-reason: not_planned
close-pr-message: |
Closing due to inactivity.
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.
lock-closed-issues:
permissions:
issues: write
runs-on: blacksmith-16vcpu-ubuntu-2404
steps:
- 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@v8
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const lockAfterHours = 48;
const lockAfterMs = lockAfterHours * 60 * 60 * 1000;
const perPage = 100;
const cutoffMs = Date.now() - lockAfterMs;
const { owner, repo } = context.repo;
let locked = 0;
let inspected = 0;
let page = 1;
while (true) {
const { data: issues } = await github.rest.issues.listForRepo({
owner,
repo,
state: "closed",
sort: "updated",
direction: "desc",
per_page: perPage,
page,
});
if (issues.length === 0) {
break;
}
for (const issue of issues) {
if (issue.pull_request) {
continue;
}
if (issue.locked) {
continue;
}
if (!issue.closed_at) {
continue;
}
inspected += 1;
const closedAtMs = Date.parse(issue.closed_at);
if (!Number.isFinite(closedAtMs)) {
continue;
}
if (closedAtMs > cutoffMs) {
continue;
}
let lastCommentMs = 0;
if (issue.comments > 0) {
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issue.number,
per_page: 1,
page: 1,
sort: "created",
direction: "desc",
});
if (comments.length > 0) {
lastCommentMs = Date.parse(comments[0].created_at);
}
}
const lastActivityMs = Math.max(closedAtMs, lastCommentMs || 0);
if (lastActivityMs > cutoffMs) {
continue;
}
await github.rest.issues.lock({
owner,
repo,
issue_number: issue.number,
lock_reason: "resolved",
});
locked += 1;
}
page += 1;
}
core.info(`Inspected ${inspected} closed issues; locked ${locked}.`);

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

15
.gitignore vendored
View File

@ -1,6 +1,7 @@
node_modules
**/node_modules/
.env
docker-compose.override.yml
docker-compose.extra.yml
dist
pnpm-lock.yaml
@ -27,6 +28,7 @@ mise.toml
apps/android/.gradle/
apps/android/app/build/
apps/android/.cxx/
apps/android/.kotlin/
# Bun build artifacts
*.bun-build
@ -80,6 +82,7 @@ apps/ios/*.mobileprovision
# Local untracked files
.local/
docs/.local/
tmp/
IDENTITY.md
USER.md
.tgz
@ -94,7 +97,7 @@ USER.md
!.agent/workflows/
/local/
package-lock.json
.claude/settings.local.json
.claude/
.agents/
.agents
.agent/
@ -120,3 +123,13 @@ 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

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"
]
}

1
.npmignore Normal file
View File

@ -0,0 +1 @@
**/node_modules/

View File

@ -6,15 +6,7 @@
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import {
Container,
Key,
matchesKey,
type SelectItem,
SelectList,
Text,
} from "@mariozechner/pi-tui";
import { showPagedSelectList } from "./ui/paged-select";
interface FileInfo {
status: string;
@ -108,87 +100,17 @@ export default function (pi: ExtensionAPI) {
}
};
// Show file picker with SelectList
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
const container = new Container();
// Top border
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
// Title
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to diff")), 0, 0));
// Build select items with colored status
const items: SelectItem[] = files.map((f) => {
let statusColor: string;
switch (f.status) {
case "M":
statusColor = theme.fg("warning", f.status);
break;
case "A":
statusColor = theme.fg("success", f.status);
break;
case "D":
statusColor = theme.fg("error", f.status);
break;
case "?":
statusColor = theme.fg("muted", f.status);
break;
default:
statusColor = theme.fg("dim", f.status);
}
return {
value: f,
label: `${statusColor} ${f.file}`,
};
});
const visibleRows = Math.min(files.length, 15);
let currentIndex = 0;
const selectList = new SelectList(items, visibleRows, {
selectedPrefix: (t) => theme.fg("accent", t),
selectedText: (t) => t, // Keep existing colors
description: (t) => theme.fg("muted", t),
scrollInfo: (t) => theme.fg("dim", t),
noMatch: (t) => theme.fg("warning", t),
});
selectList.onSelect = (item) => {
const items = files.map((file) => ({
value: file,
label: `${file.status} ${file.file}`,
}));
await showPagedSelectList({
ctx,
title: " Select file to diff",
items,
onSelect: (item) => {
void openSelected(item.value as FileInfo);
};
selectList.onCancel = () => done();
selectList.onSelectionChange = (item) => {
currentIndex = items.indexOf(item);
};
container.addChild(selectList);
// Help text
container.addChild(
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
);
// Bottom border
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
return {
render: (w) => container.render(w),
invalidate: () => container.invalidate(),
handleInput: (data) => {
// Add paging with left/right
if (matchesKey(data, Key.left)) {
// Page up - clamp to 0
currentIndex = Math.max(0, currentIndex - visibleRows);
selectList.setSelectedIndex(currentIndex);
} else if (matchesKey(data, Key.right)) {
// Page down - clamp to last
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
selectList.setSelectedIndex(currentIndex);
} else {
selectList.handleInput(data);
}
tui.requestRender();
},
};
},
});
},
});

View File

@ -6,15 +6,7 @@
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import {
Container,
Key,
matchesKey,
type SelectItem,
SelectList,
Text,
} from "@mariozechner/pi-tui";
import { showPagedSelectList } from "./ui/paged-select";
interface FileEntry {
path: string;
@ -113,82 +105,30 @@ export default function (pi: ExtensionAPI) {
}
};
// Show file picker with SelectList
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
const container = new Container();
// Top border
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
// Title
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to open")), 0, 0));
// Build select items with colored operations
const items: SelectItem[] = files.map((f) => {
const ops: string[] = [];
if (f.operations.has("read")) {
ops.push(theme.fg("muted", "R"));
}
if (f.operations.has("write")) {
ops.push(theme.fg("success", "W"));
}
if (f.operations.has("edit")) {
ops.push(theme.fg("warning", "E"));
}
const opsLabel = ops.join("");
return {
value: f,
label: `${opsLabel} ${f.path}`,
};
});
const visibleRows = Math.min(files.length, 15);
let currentIndex = 0;
const selectList = new SelectList(items, visibleRows, {
selectedPrefix: (t) => theme.fg("accent", t),
selectedText: (t) => t, // Keep existing colors
description: (t) => theme.fg("muted", t),
scrollInfo: (t) => theme.fg("dim", t),
noMatch: (t) => theme.fg("warning", t),
});
selectList.onSelect = (item) => {
void openSelected(item.value as FileEntry);
};
selectList.onCancel = () => done();
selectList.onSelectionChange = (item) => {
currentIndex = items.indexOf(item);
};
container.addChild(selectList);
// Help text
container.addChild(
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
);
// Bottom border
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
const items = files.map((file) => {
const ops: string[] = [];
if (file.operations.has("read")) {
ops.push("R");
}
if (file.operations.has("write")) {
ops.push("W");
}
if (file.operations.has("edit")) {
ops.push("E");
}
return {
render: (w) => container.render(w),
invalidate: () => container.invalidate(),
handleInput: (data) => {
// Add paging with left/right
if (matchesKey(data, Key.left)) {
// Page up - clamp to 0
currentIndex = Math.max(0, currentIndex - visibleRows);
selectList.setSelectedIndex(currentIndex);
} else if (matchesKey(data, Key.right)) {
// Page down - clamp to last
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
selectList.setSelectedIndex(currentIndex);
} else {
selectList.handleInput(data);
}
tui.requestRender();
},
value: file,
label: `${ops.join("")} ${file.path}`,
};
});
await showPagedSelectList({
ctx,
title: " Select file to open",
items,
onSelect: (item) => {
void openSelected(item.value as FileEntry);
},
});
},
});
}

View File

@ -114,6 +114,17 @@ export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
}
};
const renderPromptMatch = (ctx: ExtensionContext, match: PromptMatch) => {
setWidget(ctx, match);
applySessionName(ctx, match);
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
const title = meta?.title?.trim();
const authorText = formatAuthor(meta?.author);
setWidget(ctx, match, title, authorText);
applySessionName(ctx, match, title);
});
};
pi.on("before_agent_start", async (event, ctx) => {
if (!ctx.hasUI) {
return;
@ -123,14 +134,7 @@ export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
return;
}
setWidget(ctx, match);
applySessionName(ctx, match);
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
const title = meta?.title?.trim();
const authorText = formatAuthor(meta?.author);
setWidget(ctx, match, title, authorText);
applySessionName(ctx, match, title);
});
renderPromptMatch(ctx, match);
});
pi.on("session_switch", async (_event, ctx) => {
@ -177,14 +181,7 @@ export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
return;
}
setWidget(ctx, match);
applySessionName(ctx, match);
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
const title = meta?.title?.trim();
const authorText = formatAuthor(meta?.author);
setWidget(ctx, match, title, authorText);
applySessionName(ctx, match, title);
});
renderPromptMatch(ctx, match);
};
pi.on("session_start", async (_event, ctx) => {

View File

@ -0,0 +1,82 @@
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
import {
Container,
Key,
matchesKey,
type SelectItem,
SelectList,
Text,
} from "@mariozechner/pi-tui";
type CustomUiContext = {
ui: {
custom: <T>(
render: (
tui: { requestRender: () => void },
theme: {
fg: (tone: string, text: string) => string;
bold: (text: string) => string;
},
kb: unknown,
done: () => void,
) => {
render: (width: number) => string;
invalidate: () => void;
handleInput: (data: string) => void;
},
) => Promise<T>;
};
};
export async function showPagedSelectList(params: {
ctx: CustomUiContext;
title: string;
items: SelectItem[];
onSelect: (item: SelectItem) => void;
}): Promise<void> {
await params.ctx.ui.custom<void>((tui, theme, _kb, done) => {
const container = new Container();
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
container.addChild(new Text(theme.fg("accent", theme.bold(params.title)), 0, 0));
const visibleRows = Math.min(params.items.length, 15);
let currentIndex = 0;
const selectList = new SelectList(params.items, visibleRows, {
selectedPrefix: (text) => theme.fg("accent", text),
selectedText: (text) => text,
description: (text) => theme.fg("muted", text),
scrollInfo: (text) => theme.fg("dim", text),
noMatch: (text) => theme.fg("warning", text),
});
selectList.onSelect = (item) => params.onSelect(item);
selectList.onCancel = () => done();
selectList.onSelectionChange = (item) => {
currentIndex = params.items.indexOf(item);
};
container.addChild(selectList);
container.addChild(
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
);
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
return {
render: (width) => container.render(width),
invalidate: () => container.invalidate(),
handleInput: (data) => {
if (matchesKey(data, Key.left)) {
currentIndex = Math.max(0, currentIndex - visibleRows);
selectList.setSelectedIndex(currentIndex);
} else if (matchesKey(data, Key.right)) {
currentIndex = Math.min(params.items.length - 1, currentIndex + visibleRows);
selectList.setSelectedIndex(currentIndex);
} else {
selectList.handleInput(data);
}
tui.requestRender();
},
};
});
}

View File

@ -9,7 +9,7 @@ Input
- If ambiguous: ask.
Do (end-to-end)
Goal: PR must end in GitHub state = MERGED (never CLOSED). Use `gh pr merge` with `--rebase` or `--squash`.
Goal: PR must end in GitHub state = MERGED (never CLOSED). Prefer `gh pr merge --squash`; use `--rebase` only when preserving commit history is required.
1. Assign PR to self:
- `gh pr edit <PR> --add-assignee @me`
@ -37,8 +37,8 @@ Goal: PR must end in GitHub state = MERGED (never CLOSED). Use `gh pr merge` wit
- Implement fixes + add/adjust tests
- Update `CHANGELOG.md` and mention `#<PR>` + `@$contrib`
9. Decide merge strategy:
- Rebase if we want to preserve commit history
- Squash if we want a single clean commit
- Squash (preferred): use when we want a single clean commit
- Rebase: use only when we explicitly want to preserve commit history
- If unclear, ask
10. Full gate (BEFORE commit):
- `pnpm lint && pnpm build && pnpm test`
@ -54,8 +54,8 @@ Goal: PR must end in GitHub state = MERGED (never CLOSED). Use `gh pr merge` wit
```
13. Merge PR (must show MERGED on GitHub):
- Rebase: `gh pr merge <PR> --rebase`
- Squash: `gh pr merge <PR> --squash`
- Squash (preferred): `gh pr merge <PR> --squash`
- Rebase (history-preserving fallback): `gh pr merge <PR> --rebase`
- Never `gh pr close` (closing is wrong)
14. Sync main:
- `git checkout main`

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

@ -30,7 +30,7 @@ repos:
- --baseline
- .secrets.baseline
- --exclude-files
- '(^|/)(dist/|vendor/|pnpm-lock\.yaml$|\.detect-secrets\.cfg$)'
- '(^|/)pnpm-lock\.yaml$'
- --exclude-lines
- 'key_content\.include\?\("BEGIN PRIVATE KEY"\)'
- --exclude-lines
@ -47,6 +47,32 @@ repos:
- '=== "string"'
- --exclude-lines
- 'typeof remote\?\.password === "string"'
- --exclude-lines
- "OPENCLAW_DOCKER_GPG_FINGERPRINT="
- --exclude-lines
- '"secretShape": "(secret_input|sibling_ref)"'
- --exclude-lines
- 'API key rotation \(provider-specific\): set `\*_API_KEYS`'
- --exclude-lines
- 'password: `OPENCLAW_GATEWAY_PASSWORD` -> `gateway\.auth\.password` -> `gateway\.remote\.password`'
- --exclude-lines
- 'password: `OPENCLAW_GATEWAY_PASSWORD` -> `gateway\.remote\.password` -> `gateway\.auth\.password`'
- --exclude-files
- '^src/gateway/client\.watchdog\.test\.ts$'
- --exclude-lines
- 'export CUSTOM_API_K[E]Y="your-key"'
- --exclude-lines
- 'grep -q ''N[O]DE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache'' ~/.bashrc \|\| cat >> ~/.bashrc <<''EOF'''
- --exclude-lines
- 'env: \{ MISTRAL_API_K[E]Y: "sk-\.\.\." \},'
- --exclude-lines
- '"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/

File diff suppressed because it is too large Load Diff

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

123
AGENTS.md
View File

@ -5,7 +5,41 @@
- GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n".
- GitHub comment footgun: never use `gh issue/pr comment -b "..."` when body contains backticks or shell chars. Always use single-quoted heredoc (`-F - <<'EOF'`) so no command substitution/escaping corruption.
- GitHub linking footgun: dont wrap issue/PR refs like `#24643` in backticks when you want auto-linking. Use plain `#24643` (optionally add full URL).
- PR landing comments: always make commit SHAs clickable with full commit links (both landed SHA + source SHA when present).
- PR review conversations: if a bot leaves review conversations on your PR, address them and resolve those conversations yourself once fixed. Leave a conversation unresolved only when reviewer or maintainer judgment is still needed; do not leave bot-conversation cleanup to maintainers.
- GitHub searching footgun: don't limit yourself to the first 500 issues or PRs when wanting to search all. Unless you're supposed to look at the most recent, keep going until you've reached the last page in the search
- Security advisory analysis: before triage/severity decisions, read `SECURITY.md` to align with OpenClaw's trust model and design boundaries.
- Do not edit files covered by security-focused `CODEOWNERS` rules unless a listed owner explicitly asked for the change or is already reviewing it with you. Treat those paths as restricted surfaces, not drive-by cleanup.
## Auto-close labels (issues and PRs)
- If an issue/PR matches one of the reasons below, apply the label and let `.github/workflows/auto-response.yml` handle comment/close/lock.
- Do not manually close + manually comment for these reasons.
- Why: keeps wording consistent, preserves automation behavior (`state_reason`, locking), and keeps triage/reporting searchable by label.
- `r:*` labels can be used on both issues and PRs.
- `r: skill`: close with guidance to publish skills on Clawhub.
- `r: support`: close with redirect to Discord support + stuck FAQ.
- `r: no-ci-pr`: close test-fix-only PRs for failing `main` CI and post the standard explanation.
- `r: too-many-prs`: close when author exceeds active PR limit.
- `r: testflight`: close requests asking for TestFlight access/builds. OpenClaw does not provide TestFlight distribution yet, so use the standard response (“Not available, build from source.”) instead of ad-hoc replies.
- `r: third-party-extension`: close with guidance to ship as third-party plugin.
- `r: moltbook`: close + lock as off-topic (not affiliated).
- `r: spam`: close + lock as spam (`lock_reason: spam`).
- `invalid`: close invalid items (issues are closed as `not_planned`; PRs are closed).
- `dirty`: close PRs with too many unrelated/unexpected changes (PR-only label).
## PR truthfulness and bug-fix validation
- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale.
- Before `/landpr`, run `/reviewpr` and require explicit evidence for bug-fix claims.
- Minimum merge gate for bug-fix PRs:
1. symptom evidence (repro/log/failing test),
2. verified root cause in code with file/line,
3. fix touches the implicated code path,
4. regression test (fail before/pass after) when feasible; if not feasible, include manual verification proof and why no test was added.
- If claim is unsubstantiated or likely hallucinated/BS: do not merge. Request evidence/changes, or close with `invalid` when appropriate.
- If linked issue appears wrong/outdated, correct triage first; do not merge speculative fixes.
## Project Structure & Module Organization
@ -26,6 +60,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).
@ -37,6 +72,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.
@ -62,7 +99,7 @@
- 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`
@ -75,6 +112,8 @@
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
- Formatting/linting via Oxlint and Oxfmt; run `pnpm check` before commits.
- 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.
- 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.
@ -82,6 +121,7 @@
- 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)
@ -95,11 +135,14 @@
- 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.
- For targeted/local debugging, keep using the wrapper: `pnpm test -- <path-or-filter> [vitest args...]` (for example `pnpm test -- src/commands/onboard-search.test.ts -t "shows registered plugin providers"`); do not default to raw `pnpm vitest run ...` because it bypasses wrapper config/profile/pool routing.
- Do not set test workers above 16; tried already.
- If local Vitest runs cause memory pressure (common on non-Mac-Studio hosts), use `OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test` for land/gate runs.
- Live tests (real keys): `CLAWDBOT_LIVE_TEST=1 pnpm test:live` (OpenClaw-only) or `LIVE=1 pnpm test:live` (includes provider live tests). Docker: `pnpm test:docker:live-models`, `pnpm test:docker:live-gateway`. Onboarding Docker E2E: `pnpm test:docker:onboard`.
- Full kit + whats covered: `docs/testing.md`.
- Changelog: user-facing changes only; no internal/meta notes (version alignment, appcast reminders, release process).
- Changelog placement: in the active version block, append new entries to the end of the target section (`### Changes` or `### Fixes`); do not insert new entries at the top of a section.
- Changelog attribution: use at most one contributor mention per line; prefer `Thanks @author` and do not also add `by @author` on the same entry.
- Pure test additions/fixes generally do **not** need a changelog entry unless they alter user-facing behavior or the user asks for one.
- Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available.
@ -107,6 +150,7 @@
**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.
- `/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.
@ -137,7 +181,7 @@
- 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; use `docs/reference/RELEASING.md` for the public release policy.
## GHSA (Repo Advisory) Patch/Publish
@ -161,6 +205,44 @@
## Agent-Specific Notes
- Vocabulary: "makeup" = "mac app".
- Parallels macOS retests: use the snapshot most closely named like `macOS 26.3.1 fresh` when the user asks for a clean/fresh macOS rerun; avoid older Tahoe snapshots unless explicitly requested.
- Parallels beta smoke: use `--target-package-spec openclaw@<beta-version>` for the beta artifact, and pin the stable side with both `--install-version <stable-version>` and `--latest-version <stable-version>` for upgrade runs. npm dist-tags can move mid-run.
- Parallels beta smoke, Windows nuance: old stable `2026.3.12` still prints the Unicode Windows onboarding banner, so mojibake during the stable precheck log is expected there. Judge the beta package by the post-upgrade lane.
- Parallels macOS smoke playbook:
- `prlctl exec` is fine for deterministic repo commands, but it can misrepresent interactive shell behavior (`PATH`, `HOME`, `curl | bash`, shebang resolution). For installer parity or shell-sensitive repros, prefer the guest Terminal or `prlctl enter`.
- Fresh Tahoe snapshot current reality: `brew` exists, `node` may not be on `PATH` in noninteractive guest exec. Use absolute `/opt/homebrew/bin/node` for repo/CLI runs when needed.
- Preferred automation entrypoint: `pnpm test:parallels:macos`. It restores the snapshot most closely matching `macOS 26.3.1 fresh`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc`, not plain `--deep`, so probe failures go non-zero.
- Latest-release pre-upgrade diagnostics still need compatibility fallback: stable `2026.3.12` does not know `--require-rpc`, so precheck status dumps should fall back to plain `gateway status --deep` until the guest is upgraded.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-smoke.*`.
- All-OS parallel runs should share the host `dist` build via `/tmp/openclaw-parallels-build.lock` instead of rebuilding three times.
- Current expected outcome on latest stable pre-upgrade: `precheck=latest-ref-fail` is normal on `2026.3.12`; treat it as a baseline signal, not a regression, unless the post-upgrade `main` lane also fails.
- Fresh host-served tgz install: restore fresh snapshot, install tgz as guest root with `HOME=/var/root`, then run onboarding as the desktop user via `prlctl exec --current-user`.
- For `openclaw onboard --non-interactive --secret-input-mode ref --install-daemon`, expect env-backed auth-profile refs (for example `OPENAI_API_KEY`) to be copied into the service env at install time; this path was fixed and should stay green.
- Dont run local + gateway agent turns in parallel on the same fresh workspace/session; they can collide on the session lock. Run sequentially.
- Root-installed tarball smoke on Tahoe can still log plugin blocks for world-writable `extensions/*` under `/opt/homebrew/lib/node_modules/openclaw`; treat that as separate from onboarding/gateway health unless the task is plugin loading.
- Parallels Windows smoke playbook:
- Preferred automation entrypoint: `pnpm test:parallels:windows`. It restores the snapshot most closely matching `pre-openclaw-native-e2e-2026-03-12`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc`, not plain `--deep`, so probe failures go non-zero.
- Latest-release pre-upgrade diagnostics still need compatibility fallback: stable `2026.3.12` does not know `--require-rpc`, so precheck status dumps should fall back to plain `gateway status --deep` until the guest is upgraded.
- Always use `prlctl exec --current-user` for Windows guest runs; plain `prlctl exec` lands in `NT AUTHORITY\SYSTEM` and does not match the real desktop-user install path.
- Prefer explicit `npm.cmd` / `openclaw.cmd`. Bare `npm` / `openclaw` in PowerShell can hit the `.ps1` shim and fail under restrictive execution policy.
- Use PowerShell only as the transport (`powershell.exe -NoProfile -ExecutionPolicy Bypass`) and call the `.cmd` shims explicitly from inside it.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-windows.*`.
- Current expected outcome on latest stable pre-upgrade: `precheck=latest-ref-fail` is normal on `2026.3.12`; treat it as a baseline signal, not a regression, unless the post-upgrade `main` lane also fails.
- Keep Windows onboarding/status text ASCII-clean in logs. Fancy punctuation in banners shows up as mojibake through the current guest PowerShell capture path.
- Parallels Linux smoke playbook:
- Preferred automation entrypoint: `pnpm test:parallels:linux`. It restores the snapshot most closely matching `fresh` on `Ubuntu 24.04.3 ARM64`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Use plain `prlctl exec` on this snapshot. `--current-user` is not the right transport there.
- Fresh snapshot reality: `curl` is missing and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates` before testing installer paths.
- Fresh `main` tgz smoke on Linux still needs the latest-release installer first, because this snapshot has no Node/npm before bootstrap. The harness does stable bootstrap first, then overlays current `main`.
- This snapshot does not have a usable `systemd --user` session. Treat managed daemon install as unsupported here; use `--skip-health`, then verify with direct `openclaw gateway run --bind loopback --port 18789 --force`.
- Env-backed auth refs are still fine, but any direct shell launch (`openclaw gateway run`, `openclaw agent --local`, Linux `gateway status --deep` against that direct run) must inherit the referenced env vars in the same shell.
- `prlctl exec` reaps detached Linux child processes on this snapshot, so a background `openclaw gateway run` launched from automation is not a trustworthy smoke path. The harness verifies installer + `agent --local`; do direct gateway checks only from an interactive guest shell when needed.
- When you do run Linux gateway checks manually from an interactive guest shell, use `openclaw gateway status --deep --require-rpc` so an RPC miss is a hard failure.
- Prefer direct argv guest commands for fetch/install steps (`curl`, `npm install -g`, `openclaw ...`) over nested `bash -lc` quoting; Linux guest quoting through Parallels was the flaky part.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-linux.*`.
- Current expected outcome on Linux smoke: fresh + upgrade should pass installer and `agent --local`; gateway remains `skipped-no-detached-linux-gateway` on this snapshot and should not be treated as a regression by itself.
- Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`.
- 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`.
@ -176,14 +258,13 @@
- 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).
- **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.
@ -210,34 +291,12 @@
- 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)
## Release Auth
- Use the 1password skill; all `op` commands must run inside a fresh tmux session.
- 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.
- Core `openclaw` publish uses GitHub trusted publishing; do not use `NPM_TOKEN` or the plugin OTP flow for core releases.
- Separate `@openclaw/*` plugin publishes use a different maintainer-only auth flow.
- Plugin scope: only publish already-on-npm `@openclaw/*` plugins. Bundled disk-tree-only plugins stay out.
- Maintainers: private 1Password item names, tmux rules, plugin publish helpers, and local mac signing/notary setup live in the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md).
## Changelog Release Notes

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ Welcome to the lobster tank! 🦞
- GitHub: [@steipete](https://github.com/steipete) · X: [@steipete](https://x.com/steipete)
- **Shadow** - Discord subsystem, Discord admin, Clawhub, all community moderation
- GitHub: [@thewilloftheshadow](https://github.com/thewilloftheshadow) · X: [@4shad0wed](https://x.com/4shad0wed)
- GitHub: [@thewilloftheshadow](https://github.com/thewilloftheshadow) · X: [@4shadowed](https://x.com/4shadowed)
- **Vignesh** - Memory (QMD), formal modeling, TUI, IRC, and Lobster
- GitHub: [@vignesh07](https://github.com/vignesh07) · X: [@\_vgnsh](https://x.com/_vgnsh)
@ -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
@ -57,7 +57,27 @@ 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_)
- **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
@ -69,9 +89,26 @@ Welcome to the lobster tank! 🦞
- Test locally with your OpenClaw instance
- Run tests: `pnpm build && pnpm check && pnpm test`
- 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.
- 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
@ -98,8 +135,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,67 +1,249 @@
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
# syntax=docker/dockerfile:1.7
# Install Bun (required for build scripts)
RUN curl -fsSL https://bun.sh/install | bash
# Opt-in extension dependencies at build time (space-separated directory names).
# Example: docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel matrix" .
#
# Multi-stage build produces a minimal runtime image without build tools,
# source code, or Bun. Works with Docker, Buildx, and Podman.
# The ext-deps stage extracts only the package.json files we need from
# extensions/, so the main build layer is not invalidated by unrelated
# extension source changes.
#
# Two runtime variants:
# Default (bookworm): docker build .
# Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim .
ARG OPENCLAW_EXTENSIONS=""
ARG OPENCLAW_VARIANT=default
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 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
ARG OPENCLAW_EXTENSIONS
COPY extensions /tmp/extensions
# Copy package.json for opted-in extensions so pnpm resolves their deps.
RUN mkdir -p /out && \
for ext in $OPENCLAW_EXTENSIONS; do \
if [ -f "/tmp/extensions/$ext/package.json" ]; then \
mkdir -p "/out/$ext" && \
cp "/tmp/extensions/$ext/package.json" "/out/$ext/package.json"; \
fi; \
done
# ── Stage 2: Build ──────────────────────────────────────────────
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build
# 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
WORKDIR /app
RUN chown node:node /app
ARG OPENCLAW_DOCKER_APT_PACKAGES=""
RUN 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/*; \
fi
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY patches ./patches
COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY --chown=node:node ui/package.json ./ui/package.json
COPY --chown=node:node patches ./patches
COPY --chown=node:node scripts ./scripts
COPY --from=ext-deps /out/ ./extensions/
USER node
# 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.
RUN pnpm canvas:a2ui:bundle || \
(echo "A2UI bundle: creating stub (non-fatal)" && \
mkdir -p src/canvas-host/a2ui && \
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: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: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:24-bookworm-slim" \
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST}"
# ── Stage 3: Runtime ────────────────────────────────────────────
FROM base-${OPENCLAW_VARIANT}
ARG OPENCLAW_VARIANT
# OCI base-image metadata for downstream image consumers.
# If you change these annotations, also update:
# - docs/install/docker.md ("Base image metadata" section)
# - https://docs.openclaw.ai/install/docker
LABEL org.opencontainers.image.source="https://github.com/openclaw/openclaw" \
org.opencontainers.image.url="https://openclaw.ai" \
org.opencontainers.image.documentation="https://docs.openclaw.ai/install/docker" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.title="OpenClaw" \
org.opencontainers.image.description="OpenClaw gateway and CLI runtime container image"
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 --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 lsof openssl
RUN chown node:node /app
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
# 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 --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; \
fi
# Optionally install Chromium and Xvfb for browser automation.
# Build with: docker build --build-arg OPENCLAW_INSTALL_BROWSER=1 ...
# Adds ~300MB but eliminates the 60-90s Playwright install on every container start.
# Must run after pnpm install so playwright-core is available in node_modules.
USER root
# 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
USER node
COPY --chown=node:node . .
RUN pnpm build
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
ENV OPENCLAW_PREFER_PNPM=1
RUN pnpm ui:build
# Optionally install Docker CLI for sandbox container management.
# Build with: docker build --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1 ...
# Adds ~50MB. Only the CLI is installed — no Docker daemon.
# Required for agents.defaults.sandbox to function in Docker deployments.
ARG OPENCLAW_INSTALL_DOCKER_CLI=""
ARG OPENCLAW_DOCKER_GPG_FINGERPRINT="9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
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 && \
install -m 0755 -d /etc/apt/keyrings && \
# Verify Docker apt signing key fingerprint before trusting it as a root key.
# Update OPENCLAW_DOCKER_GPG_FINGERPRINT when Docker rotates release keys.
curl -fsSL https://download.docker.com/linux/debian/gpg -o /tmp/docker.gpg.asc && \
expected_fingerprint="$(printf '%s' "$OPENCLAW_DOCKER_GPG_FINGERPRINT" | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')" && \
actual_fingerprint="$(gpg --batch --show-keys --with-colons /tmp/docker.gpg.asc | awk -F: '$1 == "fpr" { print toupper($10); exit }')" && \
if [ -z "$actual_fingerprint" ] || [ "$actual_fingerprint" != "$expected_fingerprint" ]; then \
echo "ERROR: Docker apt key fingerprint mismatch (expected $expected_fingerprint, got ${actual_fingerprint:-<empty>})" >&2; \
exit 1; \
fi && \
gpg --dearmor -o /etc/apt/keyrings/docker.gpg /tmp/docker.gpg.asc && \
rm -f /tmp/docker.gpg.asc && \
chmod a+r /etc/apt/keyrings/docker.gpg && \
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable\n' \
"$(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; \
fi
# 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
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
# Start gateway server with default config.
# Binds to loopback (127.0.0.1) by default for security.
#
# For container platforms requiring external health checks:
# 1. Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD env var
# 2. Override CMD: ["node","openclaw.mjs","gateway","--allow-unconfigured","--bind","lan"]
# IMPORTANT: With Docker bridge networking (-p 18789:18789), loopback bind
# makes the gateway unreachable from the host. Either:
# - Use --network host, OR
# - Override --bind to "lan" (0.0.0.0) and set auth credentials
#
# Built-in probe endpoints for container health checks:
# - GET /healthz (liveness) and GET /readyz (readiness)
# - aliases: /health and /ready
# For external access from host/ingress, override bind to "lan" and set auth.
HEALTHCHECK --interval=3m --timeout=10s --start-period=15s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]

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}

142
README.md
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>
@ -19,7 +19,7 @@
</p>
**OpenClaw** is a _personal AI assistant_ you run on your own devices.
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, Microsoft Teams, WebChat), plus extension channels like BlueBubbles, Matrix, Zalo, and Zalo Personal. It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WebChat). It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
@ -32,15 +32,15 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
## Sponsors
| OpenAI | Blacksmith |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| [![OpenAI](docs/assets/sponsors/openai.svg)](https://openai.com/) | [![Blacksmith](docs/assets/sponsors/blacksmith.svg)](https://blacksmith.sh/) |
| OpenAI | Vercel | Blacksmith | Convex |
| ----------------------------------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [![OpenAI](docs/assets/sponsors/openai.svg)](https://openai.com/) | [![Vercel](docs/assets/sponsors/vercel.svg)](https://vercel.com/) | [![Blacksmith](docs/assets/sponsors/blacksmith.svg)](https://blacksmith.sh/) | [![Convex](docs/assets/sponsors/convex.svg)](https://www.convex.dev/) |
**Subscriptions (OAuth):**
- **[OpenAI](https://openai.com/)** (ChatGPT/Codex)
Model note: while any model is supported, I strongly recommend **Anthropic Pro/Max (100/200) + Opus 4.6** for longcontext strength and better promptinjection resistance. See [Onboarding](https://docs.openclaw.ai/start/onboarding).
Model note: while many providers/models are supported, for the best experience and lower prompt-injection risk use the strongest latest-generation model available to you. See [Onboarding](https://docs.openclaw.ai/start/onboarding).
## Models (selection + auth)
@ -74,7 +74,7 @@ openclaw gateway --port 18789 --verbose
# Send a message
openclaw message send --to +1234567890 --message "Hello from OpenClaw"
# Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/BlueBubbles/Microsoft Teams/Matrix/Zalo/Zalo Personal/WebChat)
# Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/BlueBubbles/IRC/Microsoft Teams/Matrix/Feishu/LINE/Mattermost/Nextcloud Talk/Nostr/Synology Chat/Tlon/Twitch/Zalo/Zalo Personal/WebChat)
openclaw agent --message "Ship checklist" --thinking high
```
@ -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
```
@ -126,9 +126,9 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
## Highlights
- **[Local-first Gateway](https://docs.openclaw.ai/gateway)** — single control plane for sessions, channels, tools, and events.
- **[Multi-channel inbox](https://docs.openclaw.ai/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, BlueBubbles (iMessage), iMessage (legacy), Microsoft Teams, Matrix, Zalo, Zalo Personal, WebChat, macOS, iOS/Android.
- **[Multi-channel inbox](https://docs.openclaw.ai/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, BlueBubbles (iMessage), iMessage (legacy), IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WebChat, macOS, iOS/Android.
- **[Multi-agent routing](https://docs.openclaw.ai/gateway/configuration)** — route inbound channels/accounts/peers to isolated agents (workspaces + per-agent sessions).
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — always-on speech for macOS/iOS/Android with ElevenLabs.
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — wake words on macOS/iOS and continuous voice on Android (ElevenLabs + system TTS fallback).
- **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
- **[First-class tools](https://docs.openclaw.ai/tools)** — browser, canvas, nodes, cron, sessions, and Discord/Slack actions.
- **[Companion apps](https://docs.openclaw.ai/platforms/macos)** — macOS menu bar app + iOS/Android [nodes](https://docs.openclaw.ai/nodes).
@ -150,14 +150,14 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
### Channels
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (iMessage, recommended), [iMessage](https://docs.openclaw.ai/channels/imessage) (legacy imsg), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) (extension), [Matrix](https://docs.openclaw.ai/channels/matrix) (extension), [Zalo](https://docs.openclaw.ai/channels/zalo) (extension), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser) (extension), [WebChat](https://docs.openclaw.ai/web/webchat).
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (iMessage, recommended), [iMessage](https://docs.openclaw.ai/channels/imessage) (legacy imsg), [IRC](https://docs.openclaw.ai/channels/irc), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams), [Matrix](https://docs.openclaw.ai/channels/matrix), [Feishu](https://docs.openclaw.ai/channels/feishu), [LINE](https://docs.openclaw.ai/channels/line), [Mattermost](https://docs.openclaw.ai/channels/mattermost), [Nextcloud Talk](https://docs.openclaw.ai/channels/nextcloud-talk), [Nostr](https://docs.openclaw.ai/channels/nostr), [Synology Chat](https://docs.openclaw.ai/channels/synology-chat), [Tlon](https://docs.openclaw.ai/channels/tlon), [Twitch](https://docs.openclaw.ai/channels/twitch), [Zalo](https://docs.openclaw.ai/channels/zalo), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser), [WebChat](https://docs.openclaw.ai/web/webchat).
- [Group routing](https://docs.openclaw.ai/channels/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.openclaw.ai/channels).
### Apps + nodes
- [macOS app](https://docs.openclaw.ai/platforms/macos): menu bar control plane, [Voice Wake](https://docs.openclaw.ai/nodes/voicewake)/PTT, [Talk Mode](https://docs.openclaw.ai/nodes/talk) overlay, [WebChat](https://docs.openclaw.ai/web/webchat), debug tools, [remote gateway](https://docs.openclaw.ai/gateway/remote) control.
- [iOS node](https://docs.openclaw.ai/platforms/ios): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Voice Wake](https://docs.openclaw.ai/nodes/voicewake), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, Bonjour pairing.
- [Android node](https://docs.openclaw.ai/platforms/android): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, optional SMS.
- [iOS node](https://docs.openclaw.ai/platforms/ios): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Voice Wake](https://docs.openclaw.ai/nodes/voicewake), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, Bonjour + device pairing.
- [Android node](https://docs.openclaw.ai/platforms/android): Connect tab (setup code/manual), chat sessions, voice tab, [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), camera/screen recording, and Android device commands (notifications/location/SMS/photos/contacts/calendar/motion/app update).
- [macOS node mode](https://docs.openclaw.ai/nodes): system.run/notify + canvas/camera exposure.
### Tools + automation
@ -185,7 +185,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
## How it works (short)
```
WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBubbles / Microsoft Teams / Matrix / Zalo / Zalo Personal / WebChat
WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBubbles / IRC / Microsoft Teams / Matrix / Feishu / LINE / Mattermost / Nextcloud Talk / Nostr / Synology Chat / Tlon / Twitch / Zalo / Zalo Personal / WebChat
┌───────────────────────────────┐
@ -207,7 +207,7 @@ WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBu
- **[Tailscale exposure](https://docs.openclaw.ai/gateway/tailscale)** — Serve/Funnel for the Gateway dashboard + WS (remote access: [Remote](https://docs.openclaw.ai/gateway/remote)).
- **[Browser control](https://docs.openclaw.ai/tools/browser)** — openclawmanaged Chrome/Chromium with CDP control.
- **[Canvas + A2UI](https://docs.openclaw.ai/platforms/mac/canvas)** — agentdriven visual workspace (A2UI host: [Canvas/A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui)).
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — alwayson speech and continuous conversation.
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — wake words on macOS/iOS plus continuous voice on Android.
- **[Nodes](https://docs.openclaw.ai/nodes)** — Canvas, camera snap/clip, screen record, `location.get`, notifications, plus macOSonly `system.run`/`system.notify`.
## Tailscale access (Gateway dashboard)
@ -297,7 +297,7 @@ Note: signed builds required for macOS permissions to stick across rebuilds (see
### iOS node (optional)
- Pairs as a node via the Bridge.
- Pairs as a node over the Gateway WebSocket (device pairing).
- Voice trigger forwarding + Canvas surface.
- Controlled via `openclaw nodes …`.
@ -305,8 +305,8 @@ Runbook: [iOS connect](https://docs.openclaw.ai/platforms/ios).
### Android node (optional)
- Pairs via the same Bridge + pairing flow as iOS.
- Exposes Canvas, Camera, and Screen capture commands.
- Pairs as a WS node via device pairing (`openclaw devices ...`).
- Exposes Connect/Chat/Voice tabs plus Canvas, Camera, Screen capture, and Android device command families.
- Runbook: [Android connect](https://docs.openclaw.ai/platforms/android).
## Agent workspace + skills
@ -502,54 +502,58 @@ Special thanks to Adam Doppelt for lobster.bot.
Thanks to all clawtributors:
<p align="left">
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/sktbrd"><img src="https://avatars.githubusercontent.com/u/116202536?v=4&s=48" width="48" height="48" alt="sktbrd" title="sktbrd"/></a> <a href="https://github.com/cpojer"><img src="https://avatars.githubusercontent.com/u/13352?v=4&s=48" width="48" height="48" alt="cpojer" title="cpojer"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/Takhoffman"><img src="https://avatars.githubusercontent.com/u/781889?v=4&s=48" width="48" height="48" alt="Takhoffman" title="Takhoffman"/></a> <a href="https://github.com/sebslight"><img src="https://avatars.githubusercontent.com/u/19554889?v=4&s=48" width="48" height="48" alt="sebslight" title="sebslight"/></a> <a href="https://github.com/tyler6204"><img src="https://avatars.githubusercontent.com/u/64381258?v=4&s=48" width="48" height="48" alt="tyler6204" title="tyler6204"/></a> <a href="https://github.com/quotentiroler"><img src="https://avatars.githubusercontent.com/u/40643627?v=4&s=48" width="48" height="48" alt="quotentiroler" title="quotentiroler"/></a> <a href="https://github.com/VeriteIgiraneza"><img src="https://avatars.githubusercontent.com/u/69280208?v=4&s=48" width="48" height="48" alt="Verite Igiraneza" title="Verite Igiraneza"/></a>
<a href="https://github.com/gumadeiras"><img src="https://avatars.githubusercontent.com/u/5599352?v=4&s=48" width="48" height="48" alt="gumadeiras" title="gumadeiras"/></a> <a href="https://github.com/bohdanpodvirnyi"><img src="https://avatars.githubusercontent.com/u/31819391?v=4&s=48" width="48" height="48" alt="bohdanpodvirnyi" title="bohdanpodvirnyi"/></a> <a href="https://github.com/vincentkoc"><img src="https://avatars.githubusercontent.com/u/25068?v=4&s=48" width="48" height="48" alt="vincentkoc" title="vincentkoc"/></a> <a href="https://github.com/iHildy"><img src="https://avatars.githubusercontent.com/u/25069719?v=4&s=48" width="48" height="48" alt="iHildy" title="iHildy"/></a> <a href="https://github.com/jaydenfyi"><img src="https://avatars.githubusercontent.com/u/213395523?v=4&s=48" width="48" height="48" alt="jaydenfyi" title="jaydenfyi"/></a> <a href="https://github.com/Glucksberg"><img src="https://avatars.githubusercontent.com/u/80581902?v=4&s=48" width="48" height="48" alt="Glucksberg" title="Glucksberg"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/rodrigouroz"><img src="https://avatars.githubusercontent.com/u/384037?v=4&s=48" width="48" height="48" alt="rodrigouroz" title="rodrigouroz"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/BunsDev"><img src="https://avatars.githubusercontent.com/u/68980965?v=4&s=48" width="48" height="48" alt="BunsDev" title="BunsDev"/></a>
<a href="https://github.com/MatthieuBizien"><img src="https://avatars.githubusercontent.com/u/173090?v=4&s=48" width="48" height="48" alt="MatthieuBizien" title="MatthieuBizien"/></a> <a href="https://github.com/MaudeBot"><img src="https://avatars.githubusercontent.com/u/255777700?v=4&s=48" width="48" height="48" alt="MaudeBot" title="MaudeBot"/></a> <a href="https://github.com/vignesh07"><img src="https://avatars.githubusercontent.com/u/1436853?v=4&s=48" width="48" height="48" alt="vignesh07" title="vignesh07"/></a> <a href="https://github.com/smartprogrammer93"><img src="https://avatars.githubusercontent.com/u/33181301?v=4&s=48" width="48" height="48" alt="smartprogrammer93" title="smartprogrammer93"/></a> <a href="https://github.com/advaitpaliwal"><img src="https://avatars.githubusercontent.com/u/66044327?v=4&s=48" width="48" height="48" alt="advaitpaliwal" title="advaitpaliwal"/></a> <a href="https://github.com/HenryLoenwind"><img src="https://avatars.githubusercontent.com/u/1485873?v=4&s=48" width="48" height="48" alt="HenryLoenwind" title="HenryLoenwind"/></a> <a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a> <a href="https://github.com/vrknetha"><img src="https://avatars.githubusercontent.com/u/20596261?v=4&s=48" width="48" height="48" alt="vrknetha" title="vrknetha"/></a> <a href="https://github.com/abdelsfane"><img src="https://avatars.githubusercontent.com/u/32418586?v=4&s=48" width="48" height="48" alt="abdelsfane" title="abdelsfane"/></a> <a href="https://github.com/radek-paclt"><img src="https://avatars.githubusercontent.com/u/50451445?v=4&s=48" width="48" height="48" alt="radek-paclt" title="radek-paclt"/></a>
<a href="https://github.com/joshavant"><img src="https://avatars.githubusercontent.com/u/830519?v=4&s=48" width="48" height="48" alt="joshavant" title="joshavant"/></a> <a href="https://github.com/christianklotz"><img src="https://avatars.githubusercontent.com/u/69443?v=4&s=48" width="48" height="48" alt="christianklotz" title="christianklotz"/></a> <a href="https://github.com/mudrii"><img src="https://avatars.githubusercontent.com/u/220262?v=4&s=48" width="48" height="48" alt="mudrii" title="mudrii"/></a> <a href="https://github.com/zerone0x"><img src="https://avatars.githubusercontent.com/u/39543393?v=4&s=48" width="48" height="48" alt="zerone0x" title="zerone0x"/></a> <a href="https://github.com/ranausmanai"><img src="https://avatars.githubusercontent.com/u/257128159?v=4&s=48" width="48" height="48" alt="ranausmanai" title="ranausmanai"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/heyhudson"><img src="https://avatars.githubusercontent.com/u/258693705?v=4&s=48" width="48" height="48" alt="heyhudson" title="heyhudson"/></a> <a href="https://github.com/czekaj"><img src="https://avatars.githubusercontent.com/u/1464539?v=4&s=48" width="48" height="48" alt="czekaj" title="czekaj"/></a> <a href="https://github.com/ethanpalm"><img src="https://avatars.githubusercontent.com/u/56270045?v=4&s=48" width="48" height="48" alt="ethanpalm" title="ethanpalm"/></a> <a href="https://github.com/yinghaosang"><img src="https://avatars.githubusercontent.com/u/261132136?v=4&s=48" width="48" height="48" alt="yinghaosang" title="yinghaosang"/></a>
<a href="https://github.com/nabbilkhan"><img src="https://avatars.githubusercontent.com/u/203121263?v=4&s=48" width="48" height="48" alt="nabbilkhan" title="nabbilkhan"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/aether-ai-agent"><img src="https://avatars.githubusercontent.com/u/261339948?v=4&s=48" width="48" height="48" alt="aether-ai-agent" title="aether-ai-agent"/></a> <a href="https://github.com/coygeek"><img src="https://avatars.githubusercontent.com/u/65363919?v=4&s=48" width="48" height="48" alt="coygeek" title="coygeek"/></a> <a href="https://github.com/Mrseenz"><img src="https://avatars.githubusercontent.com/u/101962919?v=4&s=48" width="48" height="48" alt="Mrseenz" title="Mrseenz"/></a> <a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/juanpablodlc"><img src="https://avatars.githubusercontent.com/u/92012363?v=4&s=48" width="48" height="48" alt="juanpablodlc" title="juanpablodlc"/></a> <a href="https://github.com/conroywhitney"><img src="https://avatars.githubusercontent.com/u/249891?v=4&s=48" width="48" height="48" alt="conroywhitney" title="conroywhitney"/></a>
<a href="https://github.com/buerbaumer"><img src="https://avatars.githubusercontent.com/u/44548809?v=4&s=48" width="48" height="48" alt="Harald Buerbaumer" title="Harald Buerbaumer"/></a> <a href="https://github.com/akoscz"><img src="https://avatars.githubusercontent.com/u/1360047?v=4&s=48" width="48" height="48" alt="akoscz" title="akoscz"/></a> <a href="https://github.com/Bridgerz"><img src="https://avatars.githubusercontent.com/u/24499532?v=4&s=48" width="48" height="48" alt="Bridgerz" title="Bridgerz"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a> <a href="https://github.com/openclaw-bot"><img src="https://avatars.githubusercontent.com/u/258178069?v=4&s=48" width="48" height="48" alt="openclaw-bot" title="openclaw-bot"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/JustasMonkev"><img src="https://avatars.githubusercontent.com/u/59362982?v=4&s=48" width="48" height="48" alt="JustasM" title="JustasM"/></a> <a href="https://github.com/Phineas1500"><img src="https://avatars.githubusercontent.com/u/41450967?v=4&s=48" width="48" height="48" alt="Phineas1500" title="Phineas1500"/></a> <a href="https://github.com/ENCHIGO"><img src="https://avatars.githubusercontent.com/u/38551565?v=4&s=48" width="48" height="48" alt="ENCHIGO" title="ENCHIGO"/></a>
<a href="https://github.com/patelhiren"><img src="https://avatars.githubusercontent.com/u/172098?v=4&s=48" width="48" height="48" alt="Hiren Patel" title="Hiren Patel"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/jonisjongithub"><img src="https://avatars.githubusercontent.com/u/86072337?v=4&s=48" width="48" height="48" alt="jonisjongithub" title="jonisjongithub"/></a> <a href="https://github.com/theonejvo"><img src="https://avatars.githubusercontent.com/u/125909656?v=4&s=48" width="48" height="48" alt="theonejvo" title="theonejvo"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/Ryan-Haines"><img src="https://avatars.githubusercontent.com/u/1855752?v=4&s=48" width="48" height="48" alt="Ryan Haines" title="Ryan Haines"/></a> <a href="https://github.com/Blakeshannon"><img src="https://avatars.githubusercontent.com/u/257822860?v=4&s=48" width="48" height="48" alt="Blakeshannon" title="Blakeshannon"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/Marvae"><img src="https://avatars.githubusercontent.com/u/11957602?v=4&s=48" width="48" height="48" alt="Marvae" title="Marvae"/></a>
<a href="https://github.com/arosstale"><img src="https://avatars.githubusercontent.com/u/117890364?v=4&s=48" width="48" height="48" alt="arosstale" title="arosstale"/></a> <a href="https://github.com/shakkernerd"><img src="https://avatars.githubusercontent.com/u/165377636?v=4&s=48" width="48" height="48" alt="shakkernerd" title="shakkernerd"/></a> <a href="https://github.com/gejifeng"><img src="https://avatars.githubusercontent.com/u/17561857?v=4&s=48" width="48" height="48" alt="gejifeng" title="gejifeng"/></a> <a href="https://github.com/divanoli"><img src="https://avatars.githubusercontent.com/u/12023205?v=4&s=48" width="48" height="48" alt="divanoli" title="divanoli"/></a> <a href="https://github.com/ryan-crabbe"><img src="https://avatars.githubusercontent.com/u/128659760?v=4&s=48" width="48" height="48" alt="ryan-crabbe" title="ryan-crabbe"/></a> <a href="https://github.com/nyanjou"><img src="https://avatars.githubusercontent.com/u/258645604?v=4&s=48" width="48" height="48" alt="nyanjou" title="nyanjou"/></a> <a href="https://github.com/theSamPadilla"><img src="https://avatars.githubusercontent.com/u/35386211?v=4&s=48" width="48" height="48" alt="Sam Padilla" title="Sam Padilla"/></a> <a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/SocialNerd42069"><img src="https://avatars.githubusercontent.com/u/118244303?v=4&s=48" width="48" height="48" alt="SocialNerd42069" title="SocialNerd42069"/></a> <a href="https://github.com/solstead"><img src="https://avatars.githubusercontent.com/u/168413654?v=4&s=48" width="48" height="48" alt="solstead" title="solstead"/></a>
<a href="https://github.com/natefikru"><img src="https://avatars.githubusercontent.com/u/10344644?v=4&s=48" width="48" height="48" alt="natefikru" title="natefikru"/></a> <a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a> <a href="https://github.com/xzq-xu"><img src="https://avatars.githubusercontent.com/u/53989315?v=4&s=48" width="48" height="48" alt="LeftX" title="LeftX"/></a> <a href="https://github.com/Yida-Dev"><img src="https://avatars.githubusercontent.com/u/92713555?v=4&s=48" width="48" height="48" alt="Yida-Dev" title="Yida-Dev"/></a> <a href="https://github.com/harhogefoo"><img src="https://avatars.githubusercontent.com/u/11906529?v=4&s=48" width="48" height="48" alt="Masataka Shinohara" title="Masataka Shinohara"/></a> <a href="https://github.com/lewiswigmore"><img src="https://avatars.githubusercontent.com/u/58551848?v=4&s=48" width="48" height="48" alt="Lewis" title="Lewis"/></a> <a href="https://github.com/riccardogiorato"><img src="https://avatars.githubusercontent.com/u/4527364?v=4&s=48" width="48" height="48" alt="riccardogiorato" title="riccardogiorato"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/adam91holt"><img src="https://avatars.githubusercontent.com/u/9592417?v=4&s=48" width="48" height="48" alt="adam91holt" title="adam91holt"/></a> <a href="https://github.com/mousberg"><img src="https://avatars.githubusercontent.com/u/57605064?v=4&s=48" width="48" height="48" alt="mousberg" title="mousberg"/></a>
<a href="https://github.com/BillChirico"><img src="https://avatars.githubusercontent.com/u/13951316?v=4&s=48" width="48" height="48" alt="BillChirico" title="BillChirico"/></a> <a href="https://github.com/shadril238"><img src="https://avatars.githubusercontent.com/u/63901551?v=4&s=48" width="48" height="48" alt="shadril238" title="shadril238"/></a> <a href="https://github.com/CharlieGreenman"><img src="https://avatars.githubusercontent.com/u/8540141?v=4&s=48" width="48" height="48" alt="CharlieGreenman" title="CharlieGreenman"/></a> <a href="https://github.com/hougangdev"><img src="https://avatars.githubusercontent.com/u/105773686?v=4&s=48" width="48" height="48" alt="hougangdev" title="hougangdev"/></a> <a href="https://github.com/Mellowambience"><img src="https://avatars.githubusercontent.com/u/40958792?v=4&s=48" width="48" height="48" alt="Mars" title="Mars"/></a> <a href="https://github.com/orlyjamie"><img src="https://avatars.githubusercontent.com/u/6668807?v=4&s=48" width="48" height="48" alt="orlyjamie" title="orlyjamie"/></a> <a href="https://github.com/mcrolly"><img src="https://avatars.githubusercontent.com/u/60803337?v=4&s=48" width="48" height="48" alt="McRolly NWANGWU" title="McRolly NWANGWU"/></a> <a href="https://github.com/PeterShanxin"><img src="https://avatars.githubusercontent.com/u/128674037?v=4&s=48" width="48" height="48" alt="LI SHANXIN" title="LI SHANXIN"/></a> <a href="https://github.com/simonemacario"><img src="https://avatars.githubusercontent.com/u/2116609?v=4&s=48" width="48" height="48" alt="Simone Macario" title="Simone Macario"/></a> <a href="https://github.com/durenzidu"><img src="https://avatars.githubusercontent.com/u/38130340?v=4&s=48" width="48" height="48" alt="durenzidu" title="durenzidu"/></a>
<a href="https://github.com/JustYannicc"><img src="https://avatars.githubusercontent.com/u/52761674?v=4&s=48" width="48" height="48" alt="JustYannicc" title="JustYannicc"/></a> <a href="https://github.com/Minidoracat"><img src="https://avatars.githubusercontent.com/u/11269639?v=4&s=48" width="48" height="48" alt="Minidoracat" title="Minidoracat"/></a> <a href="https://github.com/magendary"><img src="https://avatars.githubusercontent.com/u/30611068?v=4&s=48" width="48" height="48" alt="magendary" title="magendary"/></a> <a href="https://github.com/jessy2027"><img src="https://avatars.githubusercontent.com/u/89694096?v=4&s=48" width="48" height="48" alt="Jessy LANGE" title="Jessy LANGE"/></a> <a href="https://github.com/mteam88"><img src="https://avatars.githubusercontent.com/u/84196639?v=4&s=48" width="48" height="48" alt="mteam88" title="mteam88"/></a> <a href="https://github.com/brandonwise"><img src="https://avatars.githubusercontent.com/u/21148772?v=4&s=48" width="48" height="48" alt="brandonwise" title="brandonwise"/></a> <a href="https://github.com/hirefrank"><img src="https://avatars.githubusercontent.com/u/183158?v=4&s=48" width="48" height="48" alt="hirefrank" title="hirefrank"/></a> <a href="https://github.com/M00N7682"><img src="https://avatars.githubusercontent.com/u/170746674?v=4&s=48" width="48" height="48" alt="M00N7682" title="M00N7682"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a>
<a href="https://github.com/Harrington-bot"><img src="https://avatars.githubusercontent.com/u/261410808?v=4&s=48" width="48" height="48" alt="Harrington-bot" title="Harrington-bot"/></a> <a href="https://github.com/TSavo"><img src="https://avatars.githubusercontent.com/u/877990?v=4&s=48" width="48" height="48" alt="TSavo" title="TSavo"/></a> <a href="https://github.com/aerolalit"><img src="https://avatars.githubusercontent.com/u/17166039?v=4&s=48" width="48" height="48" alt="Lalit Singh" title="Lalit Singh"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/jscaldwell55"><img src="https://avatars.githubusercontent.com/u/111952840?v=4&s=48" width="48" height="48" alt="Jay Caldwell" title="Jay Caldwell"/></a> <a href="https://github.com/KirillShchetinin"><img src="https://avatars.githubusercontent.com/u/13061871?v=4&s=48" width="48" height="48" alt="Kirill Shchetynin" title="Kirill Shchetynin"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/bradleypriest"><img src="https://avatars.githubusercontent.com/u/167215?v=4&s=48" width="48" height="48" alt="bradleypriest" title="bradleypriest"/></a> <a href="https://github.com/TsekaLuk"><img src="https://avatars.githubusercontent.com/u/79151285?v=4&s=48" width="48" height="48" alt="TsekaLuk" title="TsekaLuk"/></a> <a href="https://github.com/benithors"><img src="https://avatars.githubusercontent.com/u/20652882?v=4&s=48" width="48" height="48" alt="benithors" title="benithors"/></a>
<a href="https://github.com/gut-puncture"><img src="https://avatars.githubusercontent.com/u/75851986?v=4&s=48" width="48" height="48" alt="Shailesh" title="Shailesh"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/jackheuberger"><img src="https://avatars.githubusercontent.com/u/7830838?v=4&s=48" width="48" height="48" alt="jackheuberger" title="jackheuberger"/></a> <a href="https://github.com/loiie45e"><img src="https://avatars.githubusercontent.com/u/15420100?v=4&s=48" width="48" height="48" alt="loiie45e" title="loiie45e"/></a> <a href="https://github.com/El-Fitz"><img src="https://avatars.githubusercontent.com/u/8971906?v=4&s=48" width="48" height="48" alt="El-Fitz" title="El-Fitz"/></a> <a href="https://github.com/benostein"><img src="https://avatars.githubusercontent.com/u/31802821?v=4&s=48" width="48" height="48" alt="benostein" title="benostein"/></a> <a href="https://github.com/pvtclawn"><img src="https://avatars.githubusercontent.com/u/258811507?v=4&s=48" width="48" height="48" alt="pvtclawn" title="pvtclawn"/></a> <a href="https://github.com/0xRaini"><img src="https://avatars.githubusercontent.com/u/190923101?v=4&s=48" width="48" height="48" alt="0xRaini" title="0xRaini"/></a> <a href="https://github.com/ruypang"><img src="https://avatars.githubusercontent.com/u/46941315?v=4&s=48" width="48" height="48" alt="ruypang" title="ruypang"/></a> <a href="https://github.com/xinhuagu"><img src="https://avatars.githubusercontent.com/u/562450?v=4&s=48" width="48" height="48" alt="xinhuagu" title="xinhuagu"/></a>
<a href="https://github.com/DrCrinkle"><img src="https://avatars.githubusercontent.com/u/62564740?v=4&s=48" width="48" height="48" alt="Taylor Asplund" title="Taylor Asplund"/></a> <a href="https://github.com/adhitShet"><img src="https://avatars.githubusercontent.com/u/131381638?v=4&s=48" width="48" height="48" alt="adhitShet" title="adhitShet"/></a> <a href="https://github.com/pvoo"><img src="https://avatars.githubusercontent.com/u/20116814?v=4&s=48" width="48" height="48" alt="Paul van Oorschot" title="Paul van Oorschot"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a> <a href="https://github.com/AI-Reviewer-QS"><img src="https://avatars.githubusercontent.com/u/255312808?v=4&s=48" width="48" height="48" alt="AI-Reviewer-QS" title="AI-Reviewer-QS"/></a> <a href="https://github.com/stefangalescu"><img src="https://avatars.githubusercontent.com/u/52995748?v=4&s=48" width="48" height="48" alt="Stefan Galescu" title="Stefan Galescu"/></a> <a href="https://github.com/WalterSumbon"><img src="https://avatars.githubusercontent.com/u/45062253?v=4&s=48" width="48" height="48" alt="WalterSumbon" title="WalterSumbon"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a>
<a href="https://github.com/rodbland2021"><img src="https://avatars.githubusercontent.com/u/86267410?v=4&s=48" width="48" height="48" alt="rodbland2021" title="rodbland2021"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a> <a href="https://github.com/fagemx"><img src="https://avatars.githubusercontent.com/u/117356295?v=4&s=48" width="48" height="48" alt="fagemx" title="fagemx"/></a> <a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/omair445"><img src="https://avatars.githubusercontent.com/u/32237905?v=4&s=48" width="48" height="48" alt="omair445" title="omair445"/></a> <a href="https://github.com/dorukardahan"><img src="https://avatars.githubusercontent.com/u/35905596?v=4&s=48" width="48" height="48" alt="dorukardahan" title="dorukardahan"/></a> <a href="https://github.com/leszekszpunar"><img src="https://avatars.githubusercontent.com/u/13106764?v=4&s=48" width="48" height="48" alt="leszekszpunar" title="leszekszpunar"/></a> <a href="https://github.com/Clawborn"><img src="https://avatars.githubusercontent.com/u/261310391?v=4&s=48" width="48" height="48" alt="Clawborn" title="Clawborn"/></a> <a href="https://github.com/davidrudduck"><img src="https://avatars.githubusercontent.com/u/47308254?v=4&s=48" width="48" height="48" alt="davidrudduck" title="davidrudduck"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a>
<a href="https://github.com/pycckuu"><img src="https://avatars.githubusercontent.com/u/1489583?v=4&s=48" width="48" height="48" alt="Igor Markelov" title="Igor Markelov"/></a> <a href="https://github.com/rrenamed"><img src="https://avatars.githubusercontent.com/u/87486610?v=4&s=48" width="48" height="48" alt="rrenamed" title="rrenamed"/></a> <a href="https://github.com/parkertoddbrooks"><img src="https://avatars.githubusercontent.com/u/585456?v=4&s=48" width="48" height="48" alt="Parker Todd Brooks" title="Parker Todd Brooks"/></a> <a href="https://github.com/AnonO6"><img src="https://avatars.githubusercontent.com/u/124311066?v=4&s=48" width="48" height="48" alt="AnonO6" title="AnonO6"/></a> <a href="https://github.com/CommanderCrowCode"><img src="https://avatars.githubusercontent.com/u/72845369?v=4&s=48" width="48" height="48" alt="Tanwa Arpornthip" title="Tanwa Arpornthip"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a> <a href="https://github.com/davidguttman"><img src="https://avatars.githubusercontent.com/u/431696?v=4&s=48" width="48" height="48" alt="davidguttman" title="davidguttman"/></a> <a href="https://github.com/sleontenko"><img src="https://avatars.githubusercontent.com/u/7135949?v=4&s=48" width="48" height="48" alt="sleontenko" title="sleontenko"/></a> <a href="https://github.com/denysvitali"><img src="https://avatars.githubusercontent.com/u/4939519?v=4&s=48" width="48" height="48" alt="denysvitali" title="denysvitali"/></a> <a href="https://github.com/tomron87"><img src="https://avatars.githubusercontent.com/u/126325152?v=4&s=48" width="48" height="48" alt="Tom Ron" title="Tom Ron"/></a>
<a href="https://github.com/popomore"><img src="https://avatars.githubusercontent.com/u/360661?v=4&s=48" width="48" height="48" alt="popomore" title="popomore"/></a> <a href="https://github.com/Patrick-Barletta"><img src="https://avatars.githubusercontent.com/u/67929313?v=4&s=48" width="48" height="48" alt="Patrick Barletta" title="Patrick Barletta"/></a> <a href="https://github.com/shayan919293"><img src="https://avatars.githubusercontent.com/u/60409704?v=4&s=48" width="48" height="48" alt="shayan919293" title="shayan919293"/></a> <a href="https://github.com/stakeswky"><img src="https://avatars.githubusercontent.com/u/64798754?v=4&s=48" width="48" height="48" alt="不做了睡大觉" title="不做了睡大觉"/></a> <a href="https://github.com/luijoc"><img src="https://avatars.githubusercontent.com/u/96428056?v=4&s=48" width="48" height="48" alt="Luis Conde" title="Luis Conde"/></a> <a href="https://github.com/Kepler2024"><img src="https://avatars.githubusercontent.com/u/166882517?v=4&s=48" width="48" height="48" alt="Harry Cui Kepler" title="Harry Cui Kepler"/></a> <a href="https://github.com/SidQin-cyber"><img src="https://avatars.githubusercontent.com/u/201593046?v=4&s=48" width="48" height="48" alt="SidQin-cyber" title="SidQin-cyber"/></a> <a href="https://github.com/L-U-C-K-Y"><img src="https://avatars.githubusercontent.com/u/14868134?v=4&s=48" width="48" height="48" alt="Lucky" title="Lucky"/></a> <a href="https://github.com/TinyTb"><img src="https://avatars.githubusercontent.com/u/5957298?v=4&s=48" width="48" height="48" alt="Michael Lee" title="Michael Lee"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a>
<a href="https://github.com/peschee"><img src="https://avatars.githubusercontent.com/u/63866?v=4&s=48" width="48" height="48" alt="peschee" title="peschee"/></a> <a href="https://github.com/dakshaymehta"><img src="https://avatars.githubusercontent.com/u/50276213?v=4&s=48" width="48" height="48" alt="dakshaymehta" title="dakshaymehta"/></a> <a href="https://github.com/davidiach"><img src="https://avatars.githubusercontent.com/u/28102235?v=4&s=48" width="48" height="48" alt="davidiach" title="davidiach"/></a> <a href="https://github.com/nonggialiang"><img src="https://avatars.githubusercontent.com/u/14367839?v=4&s=48" width="48" height="48" alt="nonggia.liang" title="nonggia.liang"/></a> <a href="https://github.com/seheepeak"><img src="https://avatars.githubusercontent.com/u/134766597?v=4&s=48" width="48" height="48" alt="seheepeak" title="seheepeak"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/danielwanwx"><img src="https://avatars.githubusercontent.com/u/144515713?v=4&s=48" width="48" height="48" alt="danielwanwx" title="danielwanwx"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/minupla"><img src="https://avatars.githubusercontent.com/u/42547246?v=4&s=48" width="48" height="48" alt="minupla" title="minupla"/></a> <a href="https://github.com/misterdas"><img src="https://avatars.githubusercontent.com/u/170702047?v=4&s=48" width="48" height="48" alt="misterdas" title="misterdas"/></a>
<a href="https://github.com/Shuai-DaiDai"><img src="https://avatars.githubusercontent.com/u/134567396?v=4&s=48" width="48" height="48" alt="Shuai-DaiDai" title="Shuai-DaiDai"/></a> <a href="https://github.com/dominicnunez"><img src="https://avatars.githubusercontent.com/u/43616264?v=4&s=48" width="48" height="48" alt="dominicnunez" title="dominicnunez"/></a> <a href="https://github.com/lploc94"><img src="https://avatars.githubusercontent.com/u/28453843?v=4&s=48" width="48" height="48" alt="lploc94" title="lploc94"/></a> <a href="https://github.com/sfo2001"><img src="https://avatars.githubusercontent.com/u/103369858?v=4&s=48" width="48" height="48" alt="sfo2001" title="sfo2001"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/dirbalak"><img src="https://avatars.githubusercontent.com/u/30323349?v=4&s=48" width="48" height="48" alt="dirbalak" title="dirbalak"/></a> <a href="https://github.com/cathrynlavery"><img src="https://avatars.githubusercontent.com/u/50469282?v=4&s=48" width="48" height="48" alt="cathrynlavery" title="cathrynlavery"/></a> <a href="https://github.com/Joly0"><img src="https://avatars.githubusercontent.com/u/13993216?v=4&s=48" width="48" height="48" alt="Joly0" title="Joly0"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/niceysam"><img src="https://avatars.githubusercontent.com/u/256747835?v=4&s=48" width="48" height="48" alt="niceysam" title="niceysam"/></a>
<a href="https://github.com/danielz1z"><img src="https://avatars.githubusercontent.com/u/235270390?v=4&s=48" width="48" height="48" alt="danielz1z" title="danielz1z"/></a> <a href="https://github.com/Iranb"><img src="https://avatars.githubusercontent.com/u/49674669?v=4&s=48" width="48" height="48" alt="Iranb" title="Iranb"/></a> <a href="https://github.com/carrotRakko"><img src="https://avatars.githubusercontent.com/u/24588751?v=4&s=48" width="48" height="48" alt="carrotRakko" title="carrotRakko"/></a> <a href="https://github.com/Oceanswave"><img src="https://avatars.githubusercontent.com/u/760674?v=4&s=48" width="48" height="48" alt="Oceanswave" title="Oceanswave"/></a> <a href="https://github.com/cdorsey"><img src="https://avatars.githubusercontent.com/u/12650570?v=4&s=48" width="48" height="48" alt="cdorsey" title="cdorsey"/></a> <a href="https://github.com/AdeboyeDN"><img src="https://avatars.githubusercontent.com/u/65312338?v=4&s=48" width="48" height="48" alt="AdeboyeDN" title="AdeboyeDN"/></a> <a href="https://github.com/j2h4u"><img src="https://avatars.githubusercontent.com/u/39818683?v=4&s=48" width="48" height="48" alt="j2h4u" title="j2h4u"/></a> <a href="https://github.com/Alg0rix"><img src="https://avatars.githubusercontent.com/u/53804949?v=4&s=48" width="48" height="48" alt="Alg0rix" title="Alg0rix"/></a> <a href="https://github.com/adao-max"><img src="https://avatars.githubusercontent.com/u/153898832?v=4&s=48" width="48" height="48" alt="Skyler Miao" title="Skyler Miao"/></a> <a href="https://github.com/peetzweg"><img src="https://avatars.githubusercontent.com/u/839848?v=4&s=48" width="48" height="48" alt="peetzweg/" title="peetzweg/"/></a>
<a href="https://github.com/papago2355"><img src="https://avatars.githubusercontent.com/u/68721273?v=4&s=48" width="48" height="48" alt="TideFinder" title="TideFinder"/></a> <a href="https://github.com/CornBrother0x"><img src="https://avatars.githubusercontent.com/u/101160087?v=4&s=48" width="48" height="48" alt="CornBrother0x" title="CornBrother0x"/></a> <a href="https://github.com/DukeDeSouth"><img src="https://avatars.githubusercontent.com/u/51200688?v=4&s=48" width="48" height="48" alt="DukeDeSouth" title="DukeDeSouth"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a> <a href="https://github.com/bsormagec"><img src="https://avatars.githubusercontent.com/u/965219?v=4&s=48" width="48" height="48" alt="bsormagec" title="bsormagec"/></a> <a href="https://github.com/Diaspar4u"><img src="https://avatars.githubusercontent.com/u/3605840?v=4&s=48" width="48" height="48" alt="Diaspar4u" title="Diaspar4u"/></a> <a href="https://github.com/evanotero"><img src="https://avatars.githubusercontent.com/u/13204105?v=4&s=48" width="48" height="48" alt="evanotero" title="evanotero"/></a> <a href="https://github.com/nk1tz"><img src="https://avatars.githubusercontent.com/u/12980165?v=4&s=48" width="48" height="48" alt="Nate" title="Nate"/></a> <a href="https://github.com/OscarMinjarez"><img src="https://avatars.githubusercontent.com/u/86080038?v=4&s=48" width="48" height="48" alt="OscarMinjarez" title="OscarMinjarez"/></a> <a href="https://github.com/webvijayi"><img src="https://avatars.githubusercontent.com/u/49924855?v=4&s=48" width="48" height="48" alt="webvijayi" title="webvijayi"/></a>
<a href="https://github.com/garnetlyx"><img src="https://avatars.githubusercontent.com/u/12513503?v=4&s=48" width="48" height="48" alt="garnetlyx" title="garnetlyx"/></a> <a href="https://github.com/miloudbelarebia"><img src="https://avatars.githubusercontent.com/u/136994453?v=4&s=48" width="48" height="48" alt="miloudbelarebia" title="miloudbelarebia"/></a> <a href="https://github.com/jlowin"><img src="https://avatars.githubusercontent.com/u/153965?v=4&s=48" width="48" height="48" alt="Jeremiah Lowin" title="Jeremiah Lowin"/></a> <a href="https://github.com/liebertar"><img src="https://avatars.githubusercontent.com/u/99405438?v=4&s=48" width="48" height="48" alt="liebertar" title="liebertar"/></a> <a href="https://github.com/rdev"><img src="https://avatars.githubusercontent.com/u/8418866?v=4&s=48" width="48" height="48" alt="Max" title="Max"/></a> <a href="https://github.com/rhuanssauro"><img src="https://avatars.githubusercontent.com/u/164682191?v=4&s=48" width="48" height="48" alt="rhuanssauro" title="rhuanssauro"/></a> <a href="https://github.com/joshrad-dev"><img src="https://avatars.githubusercontent.com/u/62785552?v=4&s=48" width="48" height="48" alt="joshrad-dev" title="joshrad-dev"/></a> <a href="https://github.com/adityashaw2"><img src="https://avatars.githubusercontent.com/u/41204444?v=4&s=48" width="48" height="48" alt="adityashaw2" title="adityashaw2"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a> <a href="https://github.com/taw0002"><img src="https://avatars.githubusercontent.com/u/42811278?v=4&s=48" width="48" height="48" alt="taw0002" title="taw0002"/></a>
<a href="https://github.com/asklee-klawd"><img src="https://avatars.githubusercontent.com/u/105007315?v=4&s=48" width="48" height="48" alt="asklee-klawd" title="asklee-klawd"/></a> <a href="https://github.com/h0tp-ftw"><img src="https://avatars.githubusercontent.com/u/141889580?v=4&s=48" width="48" height="48" alt="h0tp-ftw" title="h0tp-ftw"/></a> <a href="https://github.com/constansino"><img src="https://avatars.githubusercontent.com/u/65108260?v=4&s=48" width="48" height="48" alt="constansino" title="constansino"/></a> <a href="https://github.com/mcaxtr"><img src="https://avatars.githubusercontent.com/u/7562095?v=4&s=48" width="48" height="48" alt="mcaxtr" title="mcaxtr"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/ryancontent"><img src="https://avatars.githubusercontent.com/u/39743613?v=4&s=48" width="48" height="48" alt="ryan" title="ryan"/></a> <a href="https://github.com/unisone"><img src="https://avatars.githubusercontent.com/u/32521398?v=4&s=48" width="48" height="48" alt="unisone" title="unisone"/></a> <a href="https://github.com/artuskg"><img src="https://avatars.githubusercontent.com/u/11966157?v=4&s=48" width="48" height="48" alt="artuskg" title="artuskg"/></a> <a href="https://github.com/Solvely-Colin"><img src="https://avatars.githubusercontent.com/u/211764741?v=4&s=48" width="48" height="48" alt="Solvely-Colin" title="Solvely-Colin"/></a> <a href="https://github.com/pahdo"><img src="https://avatars.githubusercontent.com/u/12799392?v=4&s=48" width="48" height="48" alt="pahdo" title="pahdo"/></a>
<a href="https://github.com/kimitaka"><img src="https://avatars.githubusercontent.com/u/167225?v=4&s=48" width="48" height="48" alt="Kimitaka Watanabe" title="Kimitaka Watanabe"/></a> <a href="https://github.com/detecti1"><img src="https://avatars.githubusercontent.com/u/1622461?v=4&s=48" width="48" height="48" alt="Lilo" title="Lilo"/></a> <a href="https://github.com/18-RAJAT"><img src="https://avatars.githubusercontent.com/u/78920780?v=4&s=48" width="48" height="48" alt="Rajat Joshi" title="Rajat Joshi"/></a> <a href="https://github.com/yuting0624"><img src="https://avatars.githubusercontent.com/u/32728916?v=4&s=48" width="48" height="48" alt="Yuting Lin" title="Yuting Lin"/></a> <a href="https://github.com/neooriginal"><img src="https://avatars.githubusercontent.com/u/54811660?v=4&s=48" width="48" height="48" alt="Neo" title="Neo"/></a> <a href="https://github.com/wu-tian807"><img src="https://avatars.githubusercontent.com/u/61640083?v=4&s=48" width="48" height="48" alt="wu-tian807" title="wu-tian807"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/crimeacs"><img src="https://avatars.githubusercontent.com/u/35071559?v=4&s=48" width="48" height="48" alt="crimeacs" title="crimeacs"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a>
<a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/manikv12"><img src="https://avatars.githubusercontent.com/u/49544491?v=4&s=48" width="48" height="48" alt="Manik Vahsith" title="Manik Vahsith"/></a> <a href="https://github.com/alexgleason"><img src="https://avatars.githubusercontent.com/u/3639540?v=4&s=48" width="48" height="48" alt="alexgleason" title="alexgleason"/></a> <a href="https://github.com/nicholascyh"><img src="https://avatars.githubusercontent.com/u/188132635?v=4&s=48" width="48" height="48" alt="Nicholas" title="Nicholas"/></a> <a href="https://github.com/sbking"><img src="https://avatars.githubusercontent.com/u/3913213?v=4&s=48" width="48" height="48" alt="Stephen Brian King" title="Stephen Brian King"/></a> <a href="https://github.com/justinhuangcode"><img src="https://avatars.githubusercontent.com/u/252443740?v=4&s=48" width="48" height="48" alt="justinhuangcode" title="justinhuangcode"/></a> <a href="https://github.com/mahanandhi"><img src="https://avatars.githubusercontent.com/u/46371575?v=4&s=48" width="48" height="48" alt="mahanandhi" title="mahanandhi"/></a> <a href="https://github.com/andreesg"><img src="https://avatars.githubusercontent.com/u/810322?v=4&s=48" width="48" height="48" alt="andreesg" title="andreesg"/></a> <a href="https://github.com/connorshea"><img src="https://avatars.githubusercontent.com/u/2977353?v=4&s=48" width="48" height="48" alt="connorshea" title="connorshea"/></a> <a href="https://github.com/dinakars777"><img src="https://avatars.githubusercontent.com/u/250428393?v=4&s=48" width="48" height="48" alt="dinakars777" title="dinakars777"/></a>
<a href="https://github.com/Flash-LHR"><img src="https://avatars.githubusercontent.com/u/47357603?v=4&s=48" width="48" height="48" alt="Flash-LHR" title="Flash-LHR"/></a> <a href="https://github.com/divisonofficer"><img src="https://avatars.githubusercontent.com/u/41609506?v=4&s=48" width="48" height="48" alt="JINNYEONG KIM" title="JINNYEONG KIM"/></a> <a href="https://github.com/Protocol-zero-0"><img src="https://avatars.githubusercontent.com/u/257158451?v=4&s=48" width="48" height="48" alt="Protocol Zero" title="Protocol Zero"/></a> <a href="https://github.com/kyleok"><img src="https://avatars.githubusercontent.com/u/58307870?v=4&s=48" width="48" height="48" alt="kyleok" title="kyleok"/></a> <a href="https://github.com/Limitless2023"><img src="https://avatars.githubusercontent.com/u/127183162?v=4&s=48" width="48" height="48" alt="Limitless" title="Limitless"/></a> <a href="https://github.com/grp06"><img src="https://avatars.githubusercontent.com/u/1573959?v=4&s=48" width="48" height="48" alt="grp06" title="grp06"/></a> <a href="https://github.com/robbyczgw-cla"><img src="https://avatars.githubusercontent.com/u/239660374?v=4&s=48" width="48" height="48" alt="robbyczgw-cla" title="robbyczgw-cla"/></a> <a href="https://github.com/slonce70"><img src="https://avatars.githubusercontent.com/u/130596182?v=4&s=48" width="48" height="48" alt="slonce70" title="slonce70"/></a> <a href="https://github.com/JayMishra-source"><img src="https://avatars.githubusercontent.com/u/82963117?v=4&s=48" width="48" height="48" alt="JayMishra-source" title="JayMishra-source"/></a> <a href="https://github.com/ide-rea"><img src="https://avatars.githubusercontent.com/u/30512600?v=4&s=48" width="48" height="48" alt="ide-rea" title="ide-rea"/></a>
<a href="https://github.com/lailoo"><img src="https://avatars.githubusercontent.com/u/20536249?v=4&s=48" width="48" height="48" alt="lailoo" title="lailoo"/></a> <a href="https://github.com/badlogic"><img src="https://avatars.githubusercontent.com/u/514052?v=4&s=48" width="48" height="48" alt="badlogic" title="badlogic"/></a> <a href="https://github.com/echoVic"><img src="https://avatars.githubusercontent.com/u/16428813?v=4&s=48" width="48" height="48" alt="echoVic" title="echoVic"/></a> <a href="https://github.com/amitbiswal007"><img src="https://avatars.githubusercontent.com/u/108086198?v=4&s=48" width="48" height="48" alt="amitbiswal007" title="amitbiswal007"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a> <a href="https://github.com/John-Rood"><img src="https://avatars.githubusercontent.com/u/62669593?v=4&s=48" width="48" height="48" alt="John Rood" title="John Rood"/></a> <a href="https://github.com/dddabtc"><img src="https://avatars.githubusercontent.com/u/104875499?v=4&s=48" width="48" height="48" alt="dddabtc" title="dddabtc"/></a> <a href="https://github.com/JonathanWorks"><img src="https://avatars.githubusercontent.com/u/124476234?v=4&s=48" width="48" height="48" alt="Jonathan Works" title="Jonathan Works"/></a> <a href="https://github.com/roshanasingh4"><img src="https://avatars.githubusercontent.com/u/88576930?v=4&s=48" width="48" height="48" alt="roshanasingh4" title="roshanasingh4"/></a> <a href="https://github.com/tosh-hamburg"><img src="https://avatars.githubusercontent.com/u/58424326?v=4&s=48" width="48" height="48" alt="tosh-hamburg" title="tosh-hamburg"/></a>
<a href="https://github.com/dlauer"><img src="https://avatars.githubusercontent.com/u/757041?v=4&s=48" width="48" height="48" alt="dlauer" title="dlauer"/></a> <a href="https://github.com/ezhikkk"><img src="https://avatars.githubusercontent.com/u/105670095?v=4&s=48" width="48" height="48" alt="ezhikkk" title="ezhikkk"/></a> <a href="https://github.com/shivamraut101"><img src="https://avatars.githubusercontent.com/u/110457469?v=4&s=48" width="48" height="48" alt="Shivam Kumar Raut" title="Shivam Kumar Raut"/></a> <a href="https://github.com/cheeeee"><img src="https://avatars.githubusercontent.com/u/21245729?v=4&s=48" width="48" height="48" alt="Mykyta Bozhenko" title="Mykyta Bozhenko"/></a> <a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/j1philli"><img src="https://avatars.githubusercontent.com/u/3744255?v=4&s=48" width="48" height="48" alt="Josh Phillips" title="Josh Phillips"/></a> <a href="https://github.com/ThomsenDrake"><img src="https://avatars.githubusercontent.com/u/120344051?v=4&s=48" width="48" height="48" alt="ThomsenDrake" title="ThomsenDrake"/></a> <a href="https://github.com/Wangnov"><img src="https://avatars.githubusercontent.com/u/48670012?v=4&s=48" width="48" height="48" alt="Wangnov" title="Wangnov"/></a> <a href="https://github.com/akramcodez"><img src="https://avatars.githubusercontent.com/u/179671552?v=4&s=48" width="48" height="48" alt="akramcodez" title="akramcodez"/></a> <a href="https://github.com/jadilson12"><img src="https://avatars.githubusercontent.com/u/36805474?v=4&s=48" width="48" height="48" alt="jadilson12" title="jadilson12"/></a>
<a href="https://github.com/Whoaa512"><img src="https://avatars.githubusercontent.com/u/1581943?v=4&s=48" width="48" height="48" alt="Whoaa512" title="Whoaa512"/></a> <a href="https://github.com/apps/clawdinator"><img src="https://avatars.githubusercontent.com/in/2607181?v=4&s=48" width="48" height="48" alt="clawdinator[bot]" title="clawdinator[bot]"/></a> <a href="https://github.com/emonty"><img src="https://avatars.githubusercontent.com/u/95156?v=4&s=48" width="48" height="48" alt="emonty" title="emonty"/></a> <a href="https://github.com/kaizen403"><img src="https://avatars.githubusercontent.com/u/134706404?v=4&s=48" width="48" height="48" alt="kaizen403" title="kaizen403"/></a> <a href="https://github.com/chriseidhof"><img src="https://avatars.githubusercontent.com/u/5382?v=4&s=48" width="48" height="48" alt="chriseidhof" title="chriseidhof"/></a> <a href="https://github.com/Lukavyi"><img src="https://avatars.githubusercontent.com/u/1013690?v=4&s=48" width="48" height="48" alt="Lukavyi" title="Lukavyi"/></a> <a href="https://github.com/wangai-studio"><img src="https://avatars.githubusercontent.com/u/256938352?v=4&s=48" width="48" height="48" alt="wangai-studio" title="wangai-studio"/></a> <a href="https://github.com/ysqander"><img src="https://avatars.githubusercontent.com/u/80843820?v=4&s=48" width="48" height="48" alt="ysqander" title="ysqander"/></a> <a href="https://github.com/aj47"><img src="https://avatars.githubusercontent.com/u/8023513?v=4&s=48" width="48" height="48" alt="aj47" title="aj47"/></a> <a href="https://github.com/apps/google-labs-jules"><img src="https://avatars.githubusercontent.com/in/842251?v=4&s=48" width="48" height="48" alt="google-labs-jules[bot]" title="google-labs-jules[bot]"/></a>
<a href="https://github.com/hyf0-agent"><img src="https://avatars.githubusercontent.com/u/258783736?v=4&s=48" width="48" height="48" alt="hyf0-agent" title="hyf0-agent"/></a> <a href="https://github.com/17jmumford"><img src="https://avatars.githubusercontent.com/u/36290330?v=4&s=48" width="48" height="48" alt="Jeremy Mumford" title="Jeremy Mumford"/></a> <a href="https://github.com/kennyklee"><img src="https://avatars.githubusercontent.com/u/1432489?v=4&s=48" width="48" height="48" alt="Kenny Lee" title="Kenny Lee"/></a> <a href="https://github.com/superman32432432"><img src="https://avatars.githubusercontent.com/u/7228420?v=4&s=48" width="48" height="48" alt="superman32432432" title="superman32432432"/></a> <a href="https://github.com/widingmarcus-cyber"><img src="https://avatars.githubusercontent.com/u/245375637?v=4&s=48" width="48" height="48" alt="widingmarcus-cyber" title="widingmarcus-cyber"/></a> <a href="https://github.com/DylanWoodAkers"><img src="https://avatars.githubusercontent.com/u/253595314?v=4&s=48" width="48" height="48" alt="DylanWoodAkers" title="DylanWoodAkers"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/austinm911"><img src="https://avatars.githubusercontent.com/u/31991302?v=4&s=48" width="48" height="48" alt="austinm911" title="austinm911"/></a> <a href="https://github.com/boris721"><img src="https://avatars.githubusercontent.com/u/257853888?v=4&s=48" width="48" height="48" alt="boris721" title="boris721"/></a> <a href="https://github.com/damoahdominic"><img src="https://avatars.githubusercontent.com/u/4623434?v=4&s=48" width="48" height="48" alt="damoahdominic" title="damoahdominic"/></a>
<a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/doodlewind"><img src="https://avatars.githubusercontent.com/u/7312949?v=4&s=48" width="48" height="48" alt="doodlewind" title="doodlewind"/></a> <a href="https://github.com/GHesericsu"><img src="https://avatars.githubusercontent.com/u/60202455?v=4&s=48" width="48" height="48" alt="GHesericsu" title="GHesericsu"/></a> <a href="https://github.com/HeimdallStrategy"><img src="https://avatars.githubusercontent.com/u/223014405?v=4&s=48" width="48" height="48" alt="HeimdallStrategy" title="HeimdallStrategy"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a> <a href="https://github.com/mahmoudashraf93"><img src="https://avatars.githubusercontent.com/u/9130129?v=4&s=48" width="48" height="48" alt="mahmoudashraf93" title="mahmoudashraf93"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a>
<a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/sumleo"><img src="https://avatars.githubusercontent.com/u/29517764?v=4&s=48" width="48" height="48" alt="sumleo" title="sumleo"/></a> <a href="https://github.com/Yeom-JinHo"><img src="https://avatars.githubusercontent.com/u/81306489?v=4&s=48" width="48" height="48" alt="Yeom-JinHo" title="Yeom-JinHo"/></a> <a href="https://github.com/akyourowngames"><img src="https://avatars.githubusercontent.com/u/123736861?v=4&s=48" width="48" height="48" alt="akyourowngames" title="akyourowngames"/></a> <a href="https://github.com/aldoeliacim"><img src="https://avatars.githubusercontent.com/u/17973757?v=4&s=48" width="48" height="48" alt="aldoeliacim" title="aldoeliacim"/></a> <a href="https://github.com/Dithilli"><img src="https://avatars.githubusercontent.com/u/41286037?v=4&s=48" width="48" height="48" alt="Dithilli" title="Dithilli"/></a> <a href="https://github.com/dougvk"><img src="https://avatars.githubusercontent.com/u/401660?v=4&s=48" width="48" height="48" alt="dougvk" title="dougvk"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/fal3"><img src="https://avatars.githubusercontent.com/u/6484295?v=4&s=48" width="48" height="48" alt="fal3" title="fal3"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a>
<a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/mkbehr"><img src="https://avatars.githubusercontent.com/u/1285?v=4&s=48" width="48" height="48" alt="mkbehr" title="mkbehr"/></a> <a href="https://github.com/orenyomtov"><img src="https://avatars.githubusercontent.com/u/168856?v=4&s=48" width="48" height="48" alt="Oren" title="Oren"/></a> <a href="https://github.com/shtse8"><img src="https://avatars.githubusercontent.com/u/8020099?v=4&s=48" width="48" height="48" alt="shtse8" title="shtse8"/></a> <a href="https://github.com/sibbl"><img src="https://avatars.githubusercontent.com/u/866535?v=4&s=48" width="48" height="48" alt="sibbl" title="sibbl"/></a> <a href="https://github.com/thesomewhatyou"><img src="https://avatars.githubusercontent.com/u/162917831?v=4&s=48" width="48" height="48" alt="thesomewhatyou" title="thesomewhatyou"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a> <a href="https://github.com/chrisrodz"><img src="https://avatars.githubusercontent.com/u/2967620?v=4&s=48" width="48" height="48" alt="chrisrodz" title="chrisrodz"/></a> <a href="https://github.com/frankekn"><img src="https://avatars.githubusercontent.com/u/4488090?v=4&s=48" width="48" height="48" alt="frankekn" title="frankekn"/></a>
<a href="https://github.com/gabriel-trigo"><img src="https://avatars.githubusercontent.com/u/38991125?v=4&s=48" width="48" height="48" alt="gabriel-trigo" title="gabriel-trigo"/></a> <a href="https://github.com/ghsmc"><img src="https://avatars.githubusercontent.com/u/68118719?v=4&s=48" width="48" height="48" alt="ghsmc" title="ghsmc"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/ibrahimq21"><img src="https://avatars.githubusercontent.com/u/8392472?v=4&s=48" width="48" height="48" alt="ibrahimq21" title="ibrahimq21"/></a> <a href="https://github.com/irtiq7"><img src="https://avatars.githubusercontent.com/u/3823029?v=4&s=48" width="48" height="48" alt="irtiq7" title="irtiq7"/></a> <a href="https://github.com/jeann2013"><img src="https://avatars.githubusercontent.com/u/3299025?v=4&s=48" width="48" height="48" alt="jeann2013" title="jeann2013"/></a> <a href="https://github.com/jogelin"><img src="https://avatars.githubusercontent.com/u/954509?v=4&s=48" width="48" height="48" alt="jogelin" title="jogelin"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/itsjling"><img src="https://avatars.githubusercontent.com/u/2521993?v=4&s=48" width="48" height="48" alt="Justin Ling" title="Justin Ling"/></a> <a href="https://github.com/kelvinCB"><img src="https://avatars.githubusercontent.com/u/50544379?v=4&s=48" width="48" height="48" alt="kelvinCB" title="kelvinCB"/></a>
<a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a> <a href="https://github.com/ZetiMente"><img src="https://avatars.githubusercontent.com/u/76985631?v=4&s=48" width="48" height="48" alt="Matthew" title="Matthew"/></a> <a href="https://github.com/mattqdev"><img src="https://avatars.githubusercontent.com/u/115874885?v=4&s=48" width="48" height="48" alt="MattQ" title="MattQ"/></a> <a href="https://github.com/Milofax"><img src="https://avatars.githubusercontent.com/u/2537423?v=4&s=48" width="48" height="48" alt="Milofax" title="Milofax"/></a> <a href="https://github.com/mitsuhiko"><img src="https://avatars.githubusercontent.com/u/7396?v=4&s=48" width="48" height="48" alt="mitsuhiko" title="mitsuhiko"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a> <a href="https://github.com/pejmanjohn"><img src="https://avatars.githubusercontent.com/u/481729?v=4&s=48" width="48" height="48" alt="pejmanjohn" title="pejmanjohn"/></a> <a href="https://github.com/ProspectOre"><img src="https://avatars.githubusercontent.com/u/54486432?v=4&s=48" width="48" height="48" alt="ProspectOre" title="ProspectOre"/></a> <a href="https://github.com/rmorse"><img src="https://avatars.githubusercontent.com/u/853547?v=4&s=48" width="48" height="48" alt="rmorse" title="rmorse"/></a> <a href="https://github.com/rubyrunsstuff"><img src="https://avatars.githubusercontent.com/u/246602379?v=4&s=48" width="48" height="48" alt="rubyrunsstuff" title="rubyrunsstuff"/></a>
<a href="https://github.com/rybnikov"><img src="https://avatars.githubusercontent.com/u/7761808?v=4&s=48" width="48" height="48" alt="rybnikov" title="rybnikov"/></a> <a href="https://github.com/santiagomed"><img src="https://avatars.githubusercontent.com/u/30184543?v=4&s=48" width="48" height="48" alt="santiagomed" title="santiagomed"/></a> <a href="https://github.com/stevebot-alive"><img src="https://avatars.githubusercontent.com/u/261149299?v=4&s=48" width="48" height="48" alt="Steve (OpenClaw)" title="Steve (OpenClaw)"/></a> <a href="https://github.com/suminhthanh"><img src="https://avatars.githubusercontent.com/u/2907636?v=4&s=48" width="48" height="48" alt="suminhthanh" title="suminhthanh"/></a> <a href="https://github.com/svkozak"><img src="https://avatars.githubusercontent.com/u/31941359?v=4&s=48" width="48" height="48" alt="svkozak" title="svkozak"/></a> <a href="https://github.com/wes-davis"><img src="https://avatars.githubusercontent.com/u/16506720?v=4&s=48" width="48" height="48" alt="wes-davis" title="wes-davis"/></a> <a href="https://github.com/24601"><img src="https://avatars.githubusercontent.com/u/1157207?v=4&s=48" width="48" height="48" alt="24601" title="24601"/></a> <a href="https://github.com/AkashKobal"><img src="https://avatars.githubusercontent.com/u/98216083?v=4&s=48" width="48" height="48" alt="AkashKobal" title="AkashKobal"/></a> <a href="https://github.com/ameno-"><img src="https://avatars.githubusercontent.com/u/2416135?v=4&s=48" width="48" height="48" alt="ameno-" title="ameno-"/></a> <a href="https://github.com/awkoy"><img src="https://avatars.githubusercontent.com/u/13995636?v=4&s=48" width="48" height="48" alt="awkoy" title="awkoy"/></a>
<a href="https://github.com/battman21"><img src="https://avatars.githubusercontent.com/u/2656916?v=4&s=48" width="48" height="48" alt="battman21" title="battman21"/></a> <a href="https://github.com/BinHPdev"><img src="https://avatars.githubusercontent.com/u/219093083?v=4&s=48" width="48" height="48" alt="BinHPdev" title="BinHPdev"/></a> <a href="https://github.com/bonald"><img src="https://avatars.githubusercontent.com/u/12394874?v=4&s=48" width="48" height="48" alt="bonald" title="bonald"/></a> <a href="https://github.com/dashed"><img src="https://avatars.githubusercontent.com/u/139499?v=4&s=48" width="48" height="48" alt="dashed" title="dashed"/></a> <a href="https://github.com/dawondyifraw"><img src="https://avatars.githubusercontent.com/u/9797257?v=4&s=48" width="48" height="48" alt="dawondyifraw" title="dawondyifraw"/></a> <a href="https://github.com/dguido"><img src="https://avatars.githubusercontent.com/u/294844?v=4&s=48" width="48" height="48" alt="dguido" title="dguido"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/evalexpr"><img src="https://avatars.githubusercontent.com/u/23485511?v=4&s=48" width="48" height="48" alt="evalexpr" title="evalexpr"/></a> <a href="https://github.com/henrino3"><img src="https://avatars.githubusercontent.com/u/4260288?v=4&s=48" width="48" height="48" alt="henrino3" title="henrino3"/></a> <a href="https://github.com/humanwritten"><img src="https://avatars.githubusercontent.com/u/206531610?v=4&s=48" width="48" height="48" alt="humanwritten" title="humanwritten"/></a>
<a href="https://github.com/hyojin"><img src="https://avatars.githubusercontent.com/u/3413183?v=4&s=48" width="48" height="48" alt="hyojin" title="hyojin"/></a> <a href="https://github.com/joeykrug"><img src="https://avatars.githubusercontent.com/u/5925937?v=4&s=48" width="48" height="48" alt="joeykrug" title="joeykrug"/></a> <a href="https://github.com/larlyssa"><img src="https://avatars.githubusercontent.com/u/13128869?v=4&s=48" width="48" height="48" alt="larlyssa" title="larlyssa"/></a> <a href="https://github.com/liuy"><img src="https://avatars.githubusercontent.com/u/1192888?v=4&s=48" width="48" height="48" alt="liuy" title="liuy"/></a> <a href="https://github.com/liuxiaopai-ai"><img src="https://avatars.githubusercontent.com/u/73659136?v=4&s=48" width="48" height="48" alt="Mark Liu" title="Mark Liu"/></a> <a href="https://github.com/natedenh"><img src="https://avatars.githubusercontent.com/u/13399956?v=4&s=48" width="48" height="48" alt="natedenh" title="natedenh"/></a> <a href="https://github.com/odysseus0"><img src="https://avatars.githubusercontent.com/u/8635094?v=4&s=48" width="48" height="48" alt="odysseus0" title="odysseus0"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/pi0"><img src="https://avatars.githubusercontent.com/u/5158436?v=4&s=48" width="48" height="48" alt="pi0" title="pi0"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a>
<a href="https://github.com/tmchow"><img src="https://avatars.githubusercontent.com/u/517103?v=4&s=48" width="48" height="48" alt="tmchow" title="tmchow"/></a> <a href="https://github.com/uli-will-code"><img src="https://avatars.githubusercontent.com/u/49715419?v=4&s=48" width="48" height="48" alt="uli-will-code" title="uli-will-code"/></a> <a href="https://github.com/aaronveklabs"><img src="https://avatars.githubusercontent.com/u/225997828?v=4&s=48" width="48" height="48" alt="aaronveklabs" title="aaronveklabs"/></a> <a href="https://github.com/andreabadesso"><img src="https://avatars.githubusercontent.com/u/3586068?v=4&s=48" width="48" height="48" alt="andreabadesso" title="andreabadesso"/></a> <a href="https://github.com/BinaryMuse"><img src="https://avatars.githubusercontent.com/u/189606?v=4&s=48" width="48" height="48" alt="BinaryMuse" title="BinaryMuse"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/CJWTRUST"><img src="https://avatars.githubusercontent.com/u/235565898?v=4&s=48" width="48" height="48" alt="CJWTRUST" title="CJWTRUST"/></a> <a href="https://github.com/cordx56"><img src="https://avatars.githubusercontent.com/u/23298744?v=4&s=48" width="48" height="48" alt="cordx56" title="cordx56"/></a> <a href="https://github.com/danballance"><img src="https://avatars.githubusercontent.com/u/13839912?v=4&s=48" width="48" height="48" alt="danballance" title="danballance"/></a> <a href="https://github.com/Elarwei001"><img src="https://avatars.githubusercontent.com/u/168552401?v=4&s=48" width="48" height="48" alt="Elarwei001" title="Elarwei001"/></a>
<a href="https://github.com/EnzeD"><img src="https://avatars.githubusercontent.com/u/9866900?v=4&s=48" width="48" height="48" alt="EnzeD" title="EnzeD"/></a> <a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/Evizero"><img src="https://avatars.githubusercontent.com/u/10854026?v=4&s=48" width="48" height="48" alt="Evizero" title="Evizero"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/gildo"><img src="https://avatars.githubusercontent.com/u/133645?v=4&s=48" width="48" height="48" alt="gildo" title="gildo"/></a> <a href="https://github.com/Grynn"><img src="https://avatars.githubusercontent.com/u/212880?v=4&s=48" width="48" height="48" alt="Grynn" title="Grynn"/></a> <a href="https://github.com/huntharo"><img src="https://avatars.githubusercontent.com/u/5617868?v=4&s=48" width="48" height="48" alt="huntharo" title="huntharo"/></a> <a href="https://github.com/hydro13"><img src="https://avatars.githubusercontent.com/u/6640526?v=4&s=48" width="48" height="48" alt="hydro13" title="hydro13"/></a> <a href="https://github.com/itsjaydesu"><img src="https://avatars.githubusercontent.com/u/220390?v=4&s=48" width="48" height="48" alt="itsjaydesu" title="itsjaydesu"/></a> <a href="https://github.com/ivanrvpereira"><img src="https://avatars.githubusercontent.com/u/183991?v=4&s=48" width="48" height="48" alt="ivanrvpereira" title="ivanrvpereira"/></a>
<a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/kentaro"><img src="https://avatars.githubusercontent.com/u/3458?v=4&s=48" width="48" height="48" alt="kentaro" title="kentaro"/></a> <a href="https://github.com/loeclos"><img src="https://avatars.githubusercontent.com/u/116607327?v=4&s=48" width="48" height="48" alt="loeclos" title="loeclos"/></a> <a href="https://github.com/longmaba"><img src="https://avatars.githubusercontent.com/u/9361500?v=4&s=48" width="48" height="48" alt="longmaba" title="longmaba"/></a> <a href="https://github.com/MarvinCui"><img src="https://avatars.githubusercontent.com/u/130876763?v=4&s=48" width="48" height="48" alt="MarvinCui" title="MarvinCui"/></a> <a href="https://github.com/MisterGuy420"><img src="https://avatars.githubusercontent.com/u/255743668?v=4&s=48" width="48" height="48" alt="MisterGuy420" title="MisterGuy420"/></a> <a href="https://github.com/mjrussell"><img src="https://avatars.githubusercontent.com/u/1641895?v=4&s=48" width="48" height="48" alt="mjrussell" title="mjrussell"/></a> <a href="https://github.com/odnxe"><img src="https://avatars.githubusercontent.com/u/403141?v=4&s=48" width="48" height="48" alt="odnxe" title="odnxe"/></a> <a href="https://github.com/optimikelabs"><img src="https://avatars.githubusercontent.com/u/31423109?v=4&s=48" width="48" height="48" alt="optimikelabs" title="optimikelabs"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a>
<a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a> <a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/RamiNoodle733"><img src="https://avatars.githubusercontent.com/u/117773986?v=4&s=48" width="48" height="48" alt="RamiNoodle733" title="RamiNoodle733"/></a> <a href="https://github.com/RayBB"><img src="https://avatars.githubusercontent.com/u/921217?v=4&s=48" width="48" height="48" alt="Raymond Berger" title="Raymond Berger"/></a> <a href="https://github.com/robaxelsen"><img src="https://avatars.githubusercontent.com/u/13132899?v=4&s=48" width="48" height="48" alt="Rob Axelsen" title="Rob Axelsen"/></a> <a href="https://github.com/sauerdaniel"><img src="https://avatars.githubusercontent.com/u/81422812?v=4&s=48" width="48" height="48" alt="sauerdaniel" title="sauerdaniel"/></a> <a href="https://github.com/SleuthCo"><img src="https://avatars.githubusercontent.com/u/259695222?v=4&s=48" width="48" height="48" alt="SleuthCo" title="SleuthCo"/></a> <a href="https://github.com/T5-AndyML"><img src="https://avatars.githubusercontent.com/u/22801233?v=4&s=48" width="48" height="48" alt="T5-AndyML" title="T5-AndyML"/></a> <a href="https://github.com/TaKO8Ki"><img src="https://avatars.githubusercontent.com/u/41065217?v=4&s=48" width="48" height="48" alt="TaKO8Ki" title="TaKO8Ki"/></a> <a href="https://github.com/thejhinvirtuoso"><img src="https://avatars.githubusercontent.com/u/258521837?v=4&s=48" width="48" height="48" alt="thejhinvirtuoso" title="thejhinvirtuoso"/></a>
<a href="https://github.com/travisp"><img src="https://avatars.githubusercontent.com/u/165698?v=4&s=48" width="48" height="48" alt="travisp" title="travisp"/></a> <a href="https://github.com/yudshj"><img src="https://avatars.githubusercontent.com/u/16971372?v=4&s=48" width="48" height="48" alt="yudshj" title="yudshj"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a> <a href="https://github.com/0oAstro"><img src="https://avatars.githubusercontent.com/u/79555780?v=4&s=48" width="48" height="48" alt="0oAstro" title="0oAstro"/></a> <a href="https://github.com/8BlT"><img src="https://avatars.githubusercontent.com/u/162764392?v=4&s=48" width="48" height="48" alt="8BlT" title="8BlT"/></a> <a href="https://github.com/Abdul535"><img src="https://avatars.githubusercontent.com/u/54276938?v=4&s=48" width="48" height="48" alt="Abdul535" title="Abdul535"/></a> <a href="https://github.com/abhaymundhara"><img src="https://avatars.githubusercontent.com/u/62872231?v=4&s=48" width="48" height="48" alt="abhaymundhara" title="abhaymundhara"/></a> <a href="https://github.com/aduk059"><img src="https://avatars.githubusercontent.com/u/257603478?v=4&s=48" width="48" height="48" alt="aduk059" title="aduk059"/></a> <a href="https://github.com/afurm"><img src="https://avatars.githubusercontent.com/u/6375192?v=4&s=48" width="48" height="48" alt="afurm" title="afurm"/></a> <a href="https://github.com/aisling404"><img src="https://avatars.githubusercontent.com/u/211950534?v=4&s=48" width="48" height="48" alt="aisling404" title="aisling404"/></a>
<a href="https://github.com/akari-musubi"><img src="https://avatars.githubusercontent.com/u/259925157?v=4&s=48" width="48" height="48" alt="akari-musubi" title="akari-musubi"/></a> <a href="https://github.com/Alex-Alaniz"><img src="https://avatars.githubusercontent.com/u/88956822?v=4&s=48" width="48" height="48" alt="Alex-Alaniz" title="Alex-Alaniz"/></a> <a href="https://github.com/alexanderatallah"><img src="https://avatars.githubusercontent.com/u/1011391?v=4&s=48" width="48" height="48" alt="alexanderatallah" title="alexanderatallah"/></a> <a href="https://github.com/alexstyl"><img src="https://avatars.githubusercontent.com/u/1665273?v=4&s=48" width="48" height="48" alt="alexstyl" title="alexstyl"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a> <a href="https://github.com/araa47"><img src="https://avatars.githubusercontent.com/u/22760261?v=4&s=48" width="48" height="48" alt="araa47" title="araa47"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/Ayush10"><img src="https://avatars.githubusercontent.com/u/7945279?v=4&s=48" width="48" height="48" alt="Ayush10" title="Ayush10"/></a> <a href="https://github.com/bennewton999"><img src="https://avatars.githubusercontent.com/u/458991?v=4&s=48" width="48" height="48" alt="bennewton999" title="bennewton999"/></a> <a href="https://github.com/bguidolim"><img src="https://avatars.githubusercontent.com/u/987360?v=4&s=48" width="48" height="48" alt="bguidolim" title="bguidolim"/></a>
<a href="https://github.com/caelum0x"><img src="https://avatars.githubusercontent.com/u/130079063?v=4&s=48" width="48" height="48" alt="caelum0x" title="caelum0x"/></a> <a href="https://github.com/championswimmer"><img src="https://avatars.githubusercontent.com/u/1327050?v=4&s=48" width="48" height="48" alt="championswimmer" title="championswimmer"/></a> <a href="https://github.com/Chloe-VP"><img src="https://avatars.githubusercontent.com/u/257371598?v=4&s=48" width="48" height="48" alt="Chloe-VP" title="Chloe-VP"/></a> <a href="https://github.com/dario-github"><img src="https://avatars.githubusercontent.com/u/40749119?v=4&s=48" width="48" height="48" alt="dario-github" title="dario-github"/></a> <a href="https://github.com/DarwinsBuddy"><img src="https://avatars.githubusercontent.com/u/490836?v=4&s=48" width="48" height="48" alt="DarwinsBuddy" title="DarwinsBuddy"/></a> <a href="https://github.com/David-Marsh-Photo"><img src="https://avatars.githubusercontent.com/u/228404527?v=4&s=48" width="48" height="48" alt="David-Marsh-Photo" title="David-Marsh-Photo"/></a> <a href="https://github.com/dcantu96"><img src="https://avatars.githubusercontent.com/u/32658690?v=4&s=48" width="48" height="48" alt="dcantu96" title="dcantu96"/></a> <a href="https://github.com/dndodson"><img src="https://avatars.githubusercontent.com/u/5123985?v=4&s=48" width="48" height="48" alt="dndodson" title="dndodson"/></a> <a href="https://github.com/dvrshil"><img src="https://avatars.githubusercontent.com/u/81693876?v=4&s=48" width="48" height="48" alt="dvrshil" title="dvrshil"/></a> <a href="https://github.com/dxd5001"><img src="https://avatars.githubusercontent.com/u/1886046?v=4&s=48" width="48" height="48" alt="dxd5001" title="dxd5001"/></a>
<a href="https://github.com/dylanneve1"><img src="https://avatars.githubusercontent.com/u/31746704?v=4&s=48" width="48" height="48" alt="dylanneve1" title="dylanneve1"/></a> <a href="https://github.com/EmberCF"><img src="https://avatars.githubusercontent.com/u/258471336?v=4&s=48" width="48" height="48" alt="EmberCF" title="EmberCF"/></a> <a href="https://github.com/ephraimm"><img src="https://avatars.githubusercontent.com/u/2803669?v=4&s=48" width="48" height="48" alt="ephraimm" title="ephraimm"/></a> <a href="https://github.com/ereid7"><img src="https://avatars.githubusercontent.com/u/27597719?v=4&s=48" width="48" height="48" alt="ereid7" title="ereid7"/></a> <a href="https://github.com/eternauta1337"><img src="https://avatars.githubusercontent.com/u/550409?v=4&s=48" width="48" height="48" alt="eternauta1337" title="eternauta1337"/></a> <a href="https://github.com/foeken"><img src="https://avatars.githubusercontent.com/u/13864?v=4&s=48" width="48" height="48" alt="foeken" title="foeken"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></a> <a href="https://github.com/iamEvanYT"><img src="https://avatars.githubusercontent.com/u/47493765?v=4&s=48" width="48" height="48" alt="iamEvanYT" title="iamEvanYT"/></a> <a href="https://github.com/ikari-pl"><img src="https://avatars.githubusercontent.com/u/811702?v=4&s=48" width="48" height="48" alt="ikari-pl" title="ikari-pl"/></a>
<a href="https://github.com/kesor"><img src="https://avatars.githubusercontent.com/u/7056?v=4&s=48" width="48" height="48" alt="kesor" title="kesor"/></a> <a href="https://github.com/knocte"><img src="https://avatars.githubusercontent.com/u/331303?v=4&s=48" width="48" height="48" alt="knocte" title="knocte"/></a> <a href="https://github.com/MackDing"><img src="https://avatars.githubusercontent.com/u/19878893?v=4&s=48" width="48" height="48" alt="MackDing" title="MackDing"/></a> <a href="https://github.com/nobrainer-tech"><img src="https://avatars.githubusercontent.com/u/445466?v=4&s=48" width="48" height="48" alt="nobrainer-tech" title="nobrainer-tech"/></a> <a href="https://github.com/Noctivoro"><img src="https://avatars.githubusercontent.com/u/183974570?v=4&s=48" width="48" height="48" alt="Noctivoro" title="Noctivoro"/></a> <a href="https://github.com/Olshansk"><img src="https://avatars.githubusercontent.com/u/1892194?v=4&s=48" width="48" height="48" alt="Olshansk" title="Olshansk"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="Pratham Dubey" title="Pratham Dubey"/></a> <a href="https://github.com/Raikan10"><img src="https://avatars.githubusercontent.com/u/20675476?v=4&s=48" width="48" height="48" alt="Raikan10" title="Raikan10"/></a> <a href="https://github.com/SecondThread"><img src="https://avatars.githubusercontent.com/u/18317476?v=4&s=48" width="48" height="48" alt="SecondThread" title="SecondThread"/></a> <a href="https://github.com/Swader"><img src="https://avatars.githubusercontent.com/u/1430603?v=4&s=48" width="48" height="48" alt="Swader" title="Swader"/></a>
<a href="https://github.com/testingabc321"><img src="https://avatars.githubusercontent.com/u/8577388?v=4&s=48" width="48" height="48" alt="testingabc321" title="testingabc321"/></a> <a href="https://github.com/0xJonHoldsCrypto"><img src="https://avatars.githubusercontent.com/u/81202085?v=4&s=48" width="48" height="48" alt="0xJonHoldsCrypto" title="0xJonHoldsCrypto"/></a> <a href="https://github.com/aaronn"><img src="https://avatars.githubusercontent.com/u/1653630?v=4&s=48" width="48" height="48" alt="aaronn" title="aaronn"/></a> <a href="https://github.com/Alphonse-arianee"><img src="https://avatars.githubusercontent.com/u/254457365?v=4&s=48" width="48" height="48" alt="Alphonse-arianee" title="Alphonse-arianee"/></a> <a href="https://github.com/atalovesyou"><img src="https://avatars.githubusercontent.com/u/3534502?v=4&s=48" width="48" height="48" alt="atalovesyou" title="atalovesyou"/></a> <a href="https://github.com/carlulsoe"><img src="https://avatars.githubusercontent.com/u/34673973?v=4&s=48" width="48" height="48" alt="carlulsoe" title="carlulsoe"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jiulingyun"><img src="https://avatars.githubusercontent.com/u/126459548?v=4&s=48" width="48" height="48" alt="jiulingyun" title="jiulingyun"/></a>
<a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/odrobnik"><img src="https://avatars.githubusercontent.com/u/333270?v=4&s=48" width="48" height="48" alt="odrobnik" title="odrobnik"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/rhjoh"><img src="https://avatars.githubusercontent.com/u/105699450?v=4&s=48" width="48" height="48" alt="rhjoh" title="rhjoh"/></a>
<a href="https://github.com/ronak-guliani"><img src="https://avatars.githubusercontent.com/u/23518228?v=4&s=48" width="48" height="48" alt="ronak-guliani" title="ronak-guliani"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/thesash"><img src="https://avatars.githubusercontent.com/u/1166151?v=4&s=48" width="48" height="48" alt="thesash" title="thesash"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a>
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/vincentkoc"><img src="https://avatars.githubusercontent.com/u/25068?v=4&s=48" width="48" height="48" alt="vincentkoc" title="vincentkoc"/></a> <a href="https://github.com/vignesh07"><img src="https://avatars.githubusercontent.com/u/1436853?v=4&s=48" width="48" height="48" alt="vignesh07" title="vignesh07"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/sebslight"><img src="https://avatars.githubusercontent.com/u/19554889?v=4&s=48" width="48" height="48" alt="sebslight" title="sebslight"/></a> <a href="https://github.com/gumadeiras"><img src="https://avatars.githubusercontent.com/u/5599352?v=4&s=48" width="48" height="48" alt="gumadeiras" title="gumadeiras"/></a> <a href="https://github.com/Takhoffman"><img src="https://avatars.githubusercontent.com/u/781889?v=4&s=48" width="48" height="48" alt="Takhoffman" title="Takhoffman"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/cpojer"><img src="https://avatars.githubusercontent.com/u/13352?v=4&s=48" width="48" height="48" alt="cpojer" title="cpojer"/></a>
<a href="https://github.com/tyler6204"><img src="https://avatars.githubusercontent.com/u/64381258?v=4&s=48" width="48" height="48" alt="tyler6204" title="tyler6204"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/Glucksberg"><img src="https://avatars.githubusercontent.com/u/80581902?v=4&s=48" width="48" height="48" alt="Glucksberg" title="Glucksberg"/></a> <a href="https://github.com/mcaxtr"><img src="https://avatars.githubusercontent.com/u/7562095?v=4&s=48" width="48" height="48" alt="mcaxtr" title="mcaxtr"/></a> <a href="https://github.com/quotentiroler"><img src="https://avatars.githubusercontent.com/u/40643627?v=4&s=48" width="48" height="48" alt="quotentiroler" title="quotentiroler"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/Sid-Qin"><img src="https://avatars.githubusercontent.com/u/201593046?v=4&s=48" width="48" height="48" alt="Sid-Qin" title="Sid-Qin"/></a> <a href="https://github.com/joshavant"><img src="https://avatars.githubusercontent.com/u/830519?v=4&s=48" width="48" height="48" alt="joshavant" title="joshavant"/></a> <a href="https://github.com/shakkernerd"><img src="https://avatars.githubusercontent.com/u/165377636?v=4&s=48" width="48" height="48" alt="shakkernerd" title="shakkernerd"/></a> <a href="https://github.com/bmendonca3"><img src="https://avatars.githubusercontent.com/u/208517100?v=4&s=48" width="48" height="48" alt="bmendonca3" title="bmendonca3"/></a>
<a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/zerone0x"><img src="https://avatars.githubusercontent.com/u/39543393?v=4&s=48" width="48" height="48" alt="zerone0x" title="zerone0x"/></a> <a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/lailoo"><img src="https://avatars.githubusercontent.com/u/20536249?v=4&s=48" width="48" height="48" alt="lailoo" title="lailoo"/></a> <a href="https://github.com/arosstale"><img src="https://avatars.githubusercontent.com/u/117890364?v=4&s=48" width="48" height="48" alt="arosstale" title="arosstale"/></a> <a href="https://github.com/rodrigouroz"><img src="https://avatars.githubusercontent.com/u/384037?v=4&s=48" width="48" height="48" alt="rodrigouroz" title="rodrigouroz"/></a> <a href="https://github.com/robbyczgw-cla"><img src="https://avatars.githubusercontent.com/u/239660374?v=4&s=48" width="48" height="48" alt="robbyczgw-cla" title="robbyczgw-cla"/></a> <a href="https://github.com/0xRaini"><img src="https://avatars.githubusercontent.com/u/190923101?v=4&s=48" width="48" height="48" alt="Elonito" title="Elonito"/></a> <a href="https://github.com/Clawborn"><img src="https://avatars.githubusercontent.com/u/261310391?v=4&s=48" width="48" height="48" alt="Clawborn" title="Clawborn"/></a>
<a href="https://github.com/yinghaosang"><img src="https://avatars.githubusercontent.com/u/261132136?v=4&s=48" width="48" height="48" alt="yinghaosang" title="yinghaosang"/></a> <a href="https://github.com/BunsDev"><img src="https://avatars.githubusercontent.com/u/68980965?v=4&s=48" width="48" height="48" alt="BunsDev" title="BunsDev"/></a> <a href="https://github.com/christianklotz"><img src="https://avatars.githubusercontent.com/u/69443?v=4&s=48" width="48" height="48" alt="christianklotz" title="christianklotz"/></a> <a href="https://github.com/echoVic"><img src="https://avatars.githubusercontent.com/u/16428813?v=4&s=48" width="48" height="48" alt="echoVic" title="echoVic"/></a> <a href="https://github.com/coygeek"><img src="https://avatars.githubusercontent.com/u/65363919?v=4&s=48" width="48" height="48" alt="coygeek" title="coygeek"/></a> <a href="https://github.com/roshanasingh4"><img src="https://avatars.githubusercontent.com/u/88576930?v=4&s=48" width="48" height="48" alt="roshanasingh4" title="roshanasingh4"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/bohdanpodvirnyi"><img src="https://avatars.githubusercontent.com/u/31819391?v=4&s=48" width="48" height="48" alt="bohdanpodvirnyi" title="bohdanpodvirnyi"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a>
<a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/VeriteIgiraneza"><img src="https://avatars.githubusercontent.com/u/69280208?v=4&s=48" width="48" height="48" alt="Verite Igiraneza" title="Verite Igiraneza"/></a> <a href="https://github.com/widingmarcus-cyber"><img src="https://avatars.githubusercontent.com/u/245375637?v=4&s=48" width="48" height="48" alt="widingmarcus-cyber" title="widingmarcus-cyber"/></a> <a href="https://github.com/akramcodez"><img src="https://avatars.githubusercontent.com/u/179671552?v=4&s=48" width="48" height="48" alt="akramcodez" title="akramcodez"/></a> <a href="https://github.com/aether-ai-agent"><img src="https://avatars.githubusercontent.com/u/261339948?v=4&s=48" width="48" height="48" alt="aether-ai-agent" title="aether-ai-agent"/></a> <a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/MaudeBot"><img src="https://avatars.githubusercontent.com/u/255777700?v=4&s=48" width="48" height="48" alt="MaudeBot" title="MaudeBot"/></a> <a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/chilu18"><img src="https://avatars.githubusercontent.com/u/7957943?v=4&s=48" width="48" height="48" alt="chilu18" title="chilu18"/></a> <a href="https://github.com/byungsker"><img src="https://avatars.githubusercontent.com/u/72309817?v=4&s=48" width="48" height="48" alt="byungsker" title="byungsker"/></a>
<a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/JayMishra-source"><img src="https://avatars.githubusercontent.com/u/82963117?v=4&s=48" width="48" height="48" alt="JayMishra-source" title="JayMishra-source"/></a> <a href="https://github.com/iHildy"><img src="https://avatars.githubusercontent.com/u/25069719?v=4&s=48" width="48" height="48" alt="iHildy" title="iHildy"/></a> <a href="https://github.com/mudrii"><img src="https://avatars.githubusercontent.com/u/220262?v=4&s=48" width="48" height="48" alt="mudrii" title="mudrii"/></a> <a href="https://github.com/dlauer"><img src="https://avatars.githubusercontent.com/u/757041?v=4&s=48" width="48" height="48" alt="dlauer" title="dlauer"/></a> <a href="https://github.com/Solvely-Colin"><img src="https://avatars.githubusercontent.com/u/211764741?v=4&s=48" width="48" height="48" alt="Solvely-Colin" title="Solvely-Colin"/></a> <a href="https://github.com/czekaj"><img src="https://avatars.githubusercontent.com/u/1464539?v=4&s=48" width="48" height="48" alt="czekaj" title="czekaj"/></a> <a href="https://github.com/advaitpaliwal"><img src="https://avatars.githubusercontent.com/u/66044327?v=4&s=48" width="48" height="48" alt="advaitpaliwal" title="advaitpaliwal"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/grp06"><img src="https://avatars.githubusercontent.com/u/1573959?v=4&s=48" width="48" height="48" alt="grp06" title="grp06"/></a>
<a href="https://github.com/HenryLoenwind"><img src="https://avatars.githubusercontent.com/u/1485873?v=4&s=48" width="48" height="48" alt="HenryLoenwind" title="HenryLoenwind"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a> <a href="https://github.com/Lukavyi"><img src="https://avatars.githubusercontent.com/u/1013690?v=4&s=48" width="48" height="48" alt="Lukavyi" title="Lukavyi"/></a> <a href="https://github.com/vrknetha"><img src="https://avatars.githubusercontent.com/u/20596261?v=4&s=48" width="48" height="48" alt="vrknetha" title="vrknetha"/></a> <a href="https://github.com/brandonwise"><img src="https://avatars.githubusercontent.com/u/21148772?v=4&s=48" width="48" height="48" alt="brandonwise" title="brandonwise"/></a> <a href="https://github.com/conroywhitney"><img src="https://avatars.githubusercontent.com/u/249891?v=4&s=48" width="48" height="48" alt="conroywhitney" title="conroywhitney"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/davidrudduck"><img src="https://avatars.githubusercontent.com/u/47308254?v=4&s=48" width="48" height="48" alt="davidrudduck" title="davidrudduck"/></a> <a href="https://github.com/xinhuagu"><img src="https://avatars.githubusercontent.com/u/562450?v=4&s=48" width="48" height="48" alt="xinhuagu" title="xinhuagu"/></a> <a href="https://github.com/jaydenfyi"><img src="https://avatars.githubusercontent.com/u/213395523?v=4&s=48" width="48" height="48" alt="jaydenfyi" title="jaydenfyi"/></a>
<a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/heyhudson"><img src="https://avatars.githubusercontent.com/u/258693705?v=4&s=48" width="48" height="48" alt="heyhudson" title="heyhudson"/></a> <a href="https://github.com/MatthieuBizien"><img src="https://avatars.githubusercontent.com/u/173090?v=4&s=48" width="48" height="48" alt="MatthieuBizien" title="MatthieuBizien"/></a> <a href="https://github.com/huntharo"><img src="https://avatars.githubusercontent.com/u/5617868?v=4&s=48" width="48" height="48" alt="huntharo" title="huntharo"/></a> <a href="https://github.com/omair445"><img src="https://avatars.githubusercontent.com/u/32237905?v=4&s=48" width="48" height="48" alt="omair445" title="omair445"/></a> <a href="https://github.com/adam91holt"><img src="https://avatars.githubusercontent.com/u/9592417?v=4&s=48" width="48" height="48" alt="adam91holt" title="adam91holt"/></a> <a href="https://github.com/adhitShet"><img src="https://avatars.githubusercontent.com/u/131381638?v=4&s=48" width="48" height="48" alt="adhitShet" title="adhitShet"/></a> <a href="https://github.com/smartprogrammer93"><img src="https://avatars.githubusercontent.com/u/33181301?v=4&s=48" width="48" height="48" alt="smartprogrammer93" title="smartprogrammer93"/></a> <a href="https://github.com/radek-paclt"><img src="https://avatars.githubusercontent.com/u/50451445?v=4&s=48" width="48" height="48" alt="radek-paclt" title="radek-paclt"/></a> <a href="https://github.com/frankekn"><img src="https://avatars.githubusercontent.com/u/4488090?v=4&s=48" width="48" height="48" alt="frankekn" title="frankekn"/></a>
<a href="https://github.com/bradleypriest"><img src="https://avatars.githubusercontent.com/u/167215?v=4&s=48" width="48" height="48" alt="bradleypriest" title="bradleypriest"/></a> <a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a> <a href="https://github.com/shadril238"><img src="https://avatars.githubusercontent.com/u/63901551?v=4&s=48" width="48" height="48" alt="shadril238" title="shadril238"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/juanpablodlc"><img src="https://avatars.githubusercontent.com/u/92012363?v=4&s=48" width="48" height="48" alt="juanpablodlc" title="juanpablodlc"/></a> <a href="https://github.com/jonisjongithub"><img src="https://avatars.githubusercontent.com/u/86072337?v=4&s=48" width="48" height="48" alt="jonisjongithub" title="jonisjongithub"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a> <a href="https://github.com/stakeswky"><img src="https://avatars.githubusercontent.com/u/64798754?v=4&s=48" width="48" height="48" alt="stakeswky" title="stakeswky"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/MisterGuy420"><img src="https://avatars.githubusercontent.com/u/255743668?v=4&s=48" width="48" height="48" alt="MisterGuy420" title="MisterGuy420"/></a>
<a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/nabbilkhan"><img src="https://avatars.githubusercontent.com/u/203121263?v=4&s=48" width="48" height="48" alt="nabbilkhan" title="nabbilkhan"/></a> <a href="https://github.com/aldoeliacim"><img src="https://avatars.githubusercontent.com/u/17973757?v=4&s=48" width="48" height="48" alt="aldoeliacim" title="aldoeliacim"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/orlyjamie"><img src="https://avatars.githubusercontent.com/u/6668807?v=4&s=48" width="48" height="48" alt="orlyjamie" title="orlyjamie"/></a> <a href="https://github.com/Elarwei001"><img src="https://avatars.githubusercontent.com/u/168552401?v=4&s=48" width="48" height="48" alt="Elarwei001" title="Elarwei001"/></a> <a href="https://github.com/rubyrunsstuff"><img src="https://avatars.githubusercontent.com/u/246602379?v=4&s=48" width="48" height="48" alt="rubyrunsstuff" title="rubyrunsstuff"/></a> <a href="https://github.com/Phineas1500"><img src="https://avatars.githubusercontent.com/u/41450967?v=4&s=48" width="48" height="48" alt="Phineas1500" title="Phineas1500"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/sfo2001"><img src="https://avatars.githubusercontent.com/u/103369858?v=4&s=48" width="48" height="48" alt="sfo2001" title="sfo2001"/></a>
<a href="https://github.com/Marvae"><img src="https://avatars.githubusercontent.com/u/11957602?v=4&s=48" width="48" height="48" alt="Marvae" title="Marvae"/></a> <a href="https://github.com/liuy"><img src="https://avatars.githubusercontent.com/u/1192888?v=4&s=48" width="48" height="48" alt="liuy" title="liuy"/></a> <a href="https://github.com/shtse8"><img src="https://avatars.githubusercontent.com/u/8020099?v=4&s=48" width="48" height="48" alt="shtse8" title="shtse8"/></a> <a href="https://github.com/thebenignhacker"><img src="https://avatars.githubusercontent.com/u/32418586?v=4&s=48" width="48" height="48" alt="thebenignhacker" title="thebenignhacker"/></a> <a href="https://github.com/carrotRakko"><img src="https://avatars.githubusercontent.com/u/24588751?v=4&s=48" width="48" height="48" alt="carrotRakko" title="carrotRakko"/></a> <a href="https://github.com/ranausmanai"><img src="https://avatars.githubusercontent.com/u/257128159?v=4&s=48" width="48" height="48" alt="ranausmanai" title="ranausmanai"/></a> <a href="https://github.com/kevinWangSheng"><img src="https://avatars.githubusercontent.com/u/118158941?v=4&s=48" width="48" height="48" alt="kevinWangSheng" title="kevinWangSheng"/></a> <a href="https://github.com/gregmousseau"><img src="https://avatars.githubusercontent.com/u/5036458?v=4&s=48" width="48" height="48" alt="gregmousseau" title="gregmousseau"/></a> <a href="https://github.com/rrenamed"><img src="https://avatars.githubusercontent.com/u/87486610?v=4&s=48" width="48" height="48" alt="rrenamed" title="rrenamed"/></a> <a href="https://github.com/akoscz"><img src="https://avatars.githubusercontent.com/u/1360047?v=4&s=48" width="48" height="48" alt="akoscz" title="akoscz"/></a>
<a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/danielz1z"><img src="https://avatars.githubusercontent.com/u/235270390?v=4&s=48" width="48" height="48" alt="danielz1z" title="danielz1z"/></a> <a href="https://github.com/pandego"><img src="https://avatars.githubusercontent.com/u/7780875?v=4&s=48" width="48" height="48" alt="pandego" title="pandego"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a> <a href="https://github.com/graysurf"><img src="https://avatars.githubusercontent.com/u/10785178?v=4&s=48" width="48" height="48" alt="graysurf" title="graysurf"/></a> <a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a> <a href="https://github.com/nyanjou"><img src="https://avatars.githubusercontent.com/u/258645604?v=4&s=48" width="48" height="48" alt="nyanjou" title="nyanjou"/></a> <a href="https://github.com/sibbl"><img src="https://avatars.githubusercontent.com/u/866535?v=4&s=48" width="48" height="48" alt="sibbl" title="sibbl"/></a> <a href="https://github.com/gejifeng"><img src="https://avatars.githubusercontent.com/u/17561857?v=4&s=48" width="48" height="48" alt="gejifeng" title="gejifeng"/></a>
<a href="https://github.com/ide-rea"><img src="https://avatars.githubusercontent.com/u/30512600?v=4&s=48" width="48" height="48" alt="ide-rea" title="ide-rea"/></a> <a href="https://github.com/leszekszpunar"><img src="https://avatars.githubusercontent.com/u/13106764?v=4&s=48" width="48" height="48" alt="leszekszpunar" title="leszekszpunar"/></a> <a href="https://github.com/Yida-Dev"><img src="https://avatars.githubusercontent.com/u/92713555?v=4&s=48" width="48" height="48" alt="Yida-Dev" title="Yida-Dev"/></a> <a href="https://github.com/AI-Reviewer-QS"><img src="https://avatars.githubusercontent.com/u/255312808?v=4&s=48" width="48" height="48" alt="AI-Reviewer-QS" title="AI-Reviewer-QS"/></a> <a href="https://github.com/SocialNerd42069"><img src="https://avatars.githubusercontent.com/u/118244303?v=4&s=48" width="48" height="48" alt="SocialNerd42069" title="SocialNerd42069"/></a> <a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/hougangdev"><img src="https://avatars.githubusercontent.com/u/105773686?v=4&s=48" width="48" height="48" alt="hougangdev" title="hougangdev"/></a> <a href="https://github.com/Minidoracat"><img src="https://avatars.githubusercontent.com/u/11269639?v=4&s=48" width="48" height="48" alt="Minidoracat" title="Minidoracat"/></a> <a href="https://github.com/AnonO6"><img src="https://avatars.githubusercontent.com/u/124311066?v=4&s=48" width="48" height="48" alt="AnonO6" title="AnonO6"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a>
<a href="https://github.com/YuzuruS"><img src="https://avatars.githubusercontent.com/u/1485195?v=4&s=48" width="48" height="48" alt="YuzuruS" title="YuzuruS"/></a> <a href="https://github.com/riccardogiorato"><img src="https://avatars.githubusercontent.com/u/4527364?v=4&s=48" width="48" height="48" alt="riccardogiorato" title="riccardogiorato"/></a> <a href="https://github.com/Bridgerz"><img src="https://avatars.githubusercontent.com/u/24499532?v=4&s=48" width="48" height="48" alt="Bridgerz" title="Bridgerz"/></a> <a href="https://github.com/Mrseenz"><img src="https://avatars.githubusercontent.com/u/101962919?v=4&s=48" width="48" height="48" alt="Mrseenz" title="Mrseenz"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/peschee"><img src="https://avatars.githubusercontent.com/u/63866?v=4&s=48" width="48" height="48" alt="peschee" title="peschee"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a>
<a href="https://github.com/buerbaumer"><img src="https://avatars.githubusercontent.com/u/44548809?v=4&s=48" width="48" height="48" alt="Harald Buerbaumer" title="Harald Buerbaumer"/></a> <a href="https://github.com/taw0002"><img src="https://avatars.githubusercontent.com/u/42811278?v=4&s=48" width="48" height="48" alt="taw0002" title="taw0002"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/openperf"><img src="https://avatars.githubusercontent.com/u/80630709?v=4&s=48" width="48" height="48" alt="openperf" title="openperf"/></a> <a href="https://github.com/BUGKillerKing"><img src="https://avatars.githubusercontent.com/u/117326392?v=4&s=48" width="48" height="48" alt="BUGKillerKing" title="BUGKillerKing"/></a> <a href="https://github.com/Oceanswave"><img src="https://avatars.githubusercontent.com/u/760674?v=4&s=48" width="48" height="48" alt="Oceanswave" title="Oceanswave"/></a> <a href="https://github.com/patelhiren"><img src="https://avatars.githubusercontent.com/u/172098?v=4&s=48" width="48" height="48" alt="Hiren Patel" title="Hiren Patel"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a>
<a href="https://github.com/jadilson12"><img src="https://avatars.githubusercontent.com/u/36805474?v=4&s=48" width="48" height="48" alt="jadilson12" title="jadilson12"/></a> <a href="https://github.com/sumleo"><img src="https://avatars.githubusercontent.com/u/29517764?v=4&s=48" width="48" height="48" alt="sumleo" title="sumleo"/></a> <a href="https://github.com/Whoaa512"><img src="https://avatars.githubusercontent.com/u/1581943?v=4&s=48" width="48" height="48" alt="Whoaa512" title="Whoaa512"/></a> <a href="https://github.com/luijoc"><img src="https://avatars.githubusercontent.com/u/96428056?v=4&s=48" width="48" height="48" alt="luijoc" title="luijoc"/></a> <a href="https://github.com/niceysam"><img src="https://avatars.githubusercontent.com/u/256747835?v=4&s=48" width="48" height="48" alt="niceysam" title="niceysam"/></a> <a href="https://github.com/JustYannicc"><img src="https://avatars.githubusercontent.com/u/52761674?v=4&s=48" width="48" height="48" alt="JustYannicc" title="JustYannicc"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a> <a href="https://github.com/TsekaLuk"><img src="https://avatars.githubusercontent.com/u/79151285?v=4&s=48" width="48" height="48" alt="TsekaLuk" title="TsekaLuk"/></a> <a href="https://github.com/JustasMonkev"><img src="https://avatars.githubusercontent.com/u/59362982?v=4&s=48" width="48" height="48" alt="JustasM" title="JustasM"/></a> <a href="https://github.com/loiie45e"><img src="https://avatars.githubusercontent.com/u/15420100?v=4&s=48" width="48" height="48" alt="loiie45e" title="loiie45e"/></a>
<a href="https://github.com/davidguttman"><img src="https://avatars.githubusercontent.com/u/431696?v=4&s=48" width="48" height="48" alt="davidguttman" title="davidguttman"/></a> <a href="https://github.com/natefikru"><img src="https://avatars.githubusercontent.com/u/10344644?v=4&s=48" width="48" height="48" alt="natefikru" title="natefikru"/></a> <a href="https://github.com/dougvk"><img src="https://avatars.githubusercontent.com/u/401660?v=4&s=48" width="48" height="48" alt="dougvk" title="dougvk"/></a> <a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/mkbehr"><img src="https://avatars.githubusercontent.com/u/1285?v=4&s=48" width="48" height="48" alt="mkbehr" title="mkbehr"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a> <a href="https://github.com/simonemacario"><img src="https://avatars.githubusercontent.com/u/2116609?v=4&s=48" width="48" height="48" alt="Simone Macario" title="Simone Macario"/></a> <a href="https://github.com/openclaw-bot"><img src="https://avatars.githubusercontent.com/u/258178069?v=4&s=48" width="48" height="48" alt="openclaw-bot" title="openclaw-bot"/></a> <a href="https://github.com/ENCHIGO"><img src="https://avatars.githubusercontent.com/u/38551565?v=4&s=48" width="48" height="48" alt="ENCHIGO" title="ENCHIGO"/></a> <a href="https://github.com/mteam88"><img src="https://avatars.githubusercontent.com/u/84196639?v=4&s=48" width="48" height="48" alt="mteam88" title="mteam88"/></a>
<a href="https://github.com/Blakeshannon"><img src="https://avatars.githubusercontent.com/u/257822860?v=4&s=48" width="48" height="48" alt="Blakeshannon" title="Blakeshannon"/></a> <a href="https://github.com/gabriel-trigo"><img src="https://avatars.githubusercontent.com/u/38991125?v=4&s=48" width="48" height="48" alt="gabriel-trigo" title="gabriel-trigo"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a> <a href="https://github.com/pejmanjohn"><img src="https://avatars.githubusercontent.com/u/481729?v=4&s=48" width="48" height="48" alt="pejmanjohn" title="pejmanjohn"/></a> <a href="https://github.com/durenzidu"><img src="https://avatars.githubusercontent.com/u/38130340?v=4&s=48" width="48" height="48" alt="durenzidu" title="durenzidu"/></a> <a href="https://github.com/Ryan-Haines"><img src="https://avatars.githubusercontent.com/u/1855752?v=4&s=48" width="48" height="48" alt="Ryan Haines" title="Ryan Haines"/></a> <a href="https://github.com/hclsys"><img src="https://avatars.githubusercontent.com/u/7755017?v=4&s=48" width="48" height="48" alt="hcl" title="hcl"/></a> <a href="https://github.com/xuhao1"><img src="https://avatars.githubusercontent.com/u/5087930?v=4&s=48" width="48" height="48" alt="XuHao" title="XuHao"/></a> <a href="https://github.com/benithors"><img src="https://avatars.githubusercontent.com/u/20652882?v=4&s=48" width="48" height="48" alt="benithors" title="benithors"/></a> <a href="https://github.com/bitfoundry-ai"><img src="https://avatars.githubusercontent.com/u/239082898?v=4&s=48" width="48" height="48" alt="bitfoundry-ai" title="bitfoundry-ai"/></a>
<a href="https://github.com/HeMuling"><img src="https://avatars.githubusercontent.com/u/74801533?v=4&s=48" width="48" height="48" alt="HeMuling" title="HeMuling"/></a> <a href="https://github.com/markmusson"><img src="https://avatars.githubusercontent.com/u/4801649?v=4&s=48" width="48" height="48" alt="markmusson" title="markmusson"/></a> <a href="https://github.com/ameno-"><img src="https://avatars.githubusercontent.com/u/2416135?v=4&s=48" width="48" height="48" alt="ameno-" title="ameno-"/></a> <a href="https://github.com/battman21"><img src="https://avatars.githubusercontent.com/u/2656916?v=4&s=48" width="48" height="48" alt="battman21" title="battman21"/></a> <a href="https://github.com/BinHPdev"><img src="https://avatars.githubusercontent.com/u/219093083?v=4&s=48" width="48" height="48" alt="BinHPdev" title="BinHPdev"/></a> <a href="https://github.com/dguido"><img src="https://avatars.githubusercontent.com/u/294844?v=4&s=48" width="48" height="48" alt="dguido" title="dguido"/></a> <a href="https://github.com/evalexpr"><img src="https://avatars.githubusercontent.com/u/23485511?v=4&s=48" width="48" height="48" alt="evalexpr" title="evalexpr"/></a> <a href="https://github.com/guirguispierre"><img src="https://avatars.githubusercontent.com/u/22091706?v=4&s=48" width="48" height="48" alt="guirguispierre" title="guirguispierre"/></a> <a href="https://github.com/henrino3"><img src="https://avatars.githubusercontent.com/u/4260288?v=4&s=48" width="48" height="48" alt="henrino3" title="henrino3"/></a> <a href="https://github.com/joeykrug"><img src="https://avatars.githubusercontent.com/u/5925937?v=4&s=48" width="48" height="48" alt="joeykrug" title="joeykrug"/></a>
<a href="https://github.com/loganprit"><img src="https://avatars.githubusercontent.com/u/72722788?v=4&s=48" width="48" height="48" alt="loganprit" title="loganprit"/></a> <a href="https://github.com/odysseus0"><img src="https://avatars.githubusercontent.com/u/8635094?v=4&s=48" width="48" height="48" alt="odysseus0" title="odysseus0"/></a> <a href="https://github.com/dbachelder"><img src="https://avatars.githubusercontent.com/u/325706?v=4&s=48" width="48" height="48" alt="dbachelder" title="dbachelder"/></a> <a href="https://github.com/divanoli"><img src="https://avatars.githubusercontent.com/u/12023205?v=4&s=48" width="48" height="48" alt="Divanoli Mydeen Pitchai" title="Divanoli Mydeen Pitchai"/></a> <a href="https://github.com/liuxiaopai-ai"><img src="https://avatars.githubusercontent.com/u/73659136?v=4&s=48" width="48" height="48" alt="liuxiaopai-ai" title="liuxiaopai-ai"/></a> <a href="https://github.com/theSamPadilla"><img src="https://avatars.githubusercontent.com/u/35386211?v=4&s=48" width="48" height="48" alt="Sam Padilla" title="Sam Padilla"/></a> <a href="https://github.com/pvtclawn"><img src="https://avatars.githubusercontent.com/u/258811507?v=4&s=48" width="48" height="48" alt="pvtclawn" title="pvtclawn"/></a> <a href="https://github.com/seheepeak"><img src="https://avatars.githubusercontent.com/u/134766597?v=4&s=48" width="48" height="48" alt="seheepeak" title="seheepeak"/></a> <a href="https://github.com/TSavo"><img src="https://avatars.githubusercontent.com/u/877990?v=4&s=48" width="48" height="48" alt="TSavo" title="TSavo"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a>
<a href="https://github.com/misterdas"><img src="https://avatars.githubusercontent.com/u/170702047?v=4&s=48" width="48" height="48" alt="misterdas" title="misterdas"/></a> <a href="https://github.com/xzq-xu"><img src="https://avatars.githubusercontent.com/u/53989315?v=4&s=48" width="48" height="48" alt="LeftX" title="LeftX"/></a> <a href="https://github.com/badlogic"><img src="https://avatars.githubusercontent.com/u/514052?v=4&s=48" width="48" height="48" alt="badlogic" title="badlogic"/></a> <a href="https://github.com/Shuai-DaiDai"><img src="https://avatars.githubusercontent.com/u/134567396?v=4&s=48" width="48" height="48" alt="Shuai-DaiDai" title="Shuai-DaiDai"/></a> <a href="https://github.com/mousberg"><img src="https://avatars.githubusercontent.com/u/57605064?v=4&s=48" width="48" height="48" alt="mousberg" title="mousberg"/></a> <a href="https://github.com/harhogefoo"><img src="https://avatars.githubusercontent.com/u/11906529?v=4&s=48" width="48" height="48" alt="Masataka Shinohara" title="Masataka Shinohara"/></a> <a href="https://github.com/BillChirico"><img src="https://avatars.githubusercontent.com/u/13951316?v=4&s=48" width="48" height="48" alt="BillChirico" title="BillChirico"/></a> <a href="https://github.com/lewiswigmore"><img src="https://avatars.githubusercontent.com/u/58551848?v=4&s=48" width="48" height="48" alt="Lewis" title="Lewis"/></a> <a href="https://github.com/solstead"><img src="https://avatars.githubusercontent.com/u/168413654?v=4&s=48" width="48" height="48" alt="solstead" title="solstead"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a>
<a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/sahilsatralkar"><img src="https://avatars.githubusercontent.com/u/62758655?v=4&s=48" width="48" height="48" alt="sahilsatralkar" title="sahilsatralkar"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a> <a href="https://github.com/mahmoudashraf93"><img src="https://avatars.githubusercontent.com/u/9130129?v=4&s=48" width="48" height="48" alt="mahmoudashraf93" title="mahmoudashraf93"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/ryan-crabbe"><img src="https://avatars.githubusercontent.com/u/128659760?v=4&s=48" width="48" height="48" alt="ryan-crabbe" title="ryan-crabbe"/></a> <a href="https://github.com/miloudbelarebia"><img src="https://avatars.githubusercontent.com/u/136994453?v=4&s=48" width="48" height="48" alt="miloudbelarebia" title="miloudbelarebia"/></a> <a href="https://github.com/Mellowambience"><img src="https://avatars.githubusercontent.com/u/40958792?v=4&s=48" width="48" height="48" alt="Mars" title="Mars"/></a> <a href="https://github.com/El-Fitz"><img src="https://avatars.githubusercontent.com/u/8971906?v=4&s=48" width="48" height="48" alt="El-Fitz" title="El-Fitz"/></a> <a href="https://github.com/mcrolly"><img src="https://avatars.githubusercontent.com/u/60803337?v=4&s=48" width="48" height="48" alt="McRolly NWANGWU" title="McRolly NWANGWU"/></a>
<a href="https://github.com/carlulsoe"><img src="https://avatars.githubusercontent.com/u/34673973?v=4&s=48" width="48" height="48" alt="carlulsoe" title="carlulsoe"/></a> <a href="https://github.com/Dithilli"><img src="https://avatars.githubusercontent.com/u/41286037?v=4&s=48" width="48" height="48" alt="Dithilli" title="Dithilli"/></a> <a href="https://github.com/emonty"><img src="https://avatars.githubusercontent.com/u/95156?v=4&s=48" width="48" height="48" alt="emonty" title="emonty"/></a> <a href="https://github.com/fal3"><img src="https://avatars.githubusercontent.com/u/6484295?v=4&s=48" width="48" height="48" alt="fal3" title="fal3"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/benostein"><img src="https://avatars.githubusercontent.com/u/31802821?v=4&s=48" width="48" height="48" alt="benostein" title="benostein"/></a> <a href="https://github.com/PeterShanxin"><img src="https://avatars.githubusercontent.com/u/128674037?v=4&s=48" width="48" height="48" alt="LI SHANXIN" title="LI SHANXIN"/></a> <a href="https://github.com/magendary"><img src="https://avatars.githubusercontent.com/u/30611068?v=4&s=48" width="48" height="48" alt="magendary" title="magendary"/></a> <a href="https://github.com/mahanandhi"><img src="https://avatars.githubusercontent.com/u/46371575?v=4&s=48" width="48" height="48" alt="mahanandhi" title="mahanandhi"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a>
<a href="https://github.com/j2h4u"><img src="https://avatars.githubusercontent.com/u/39818683?v=4&s=48" width="48" height="48" alt="j2h4u" title="j2h4u"/></a> <a href="https://github.com/bsormagec"><img src="https://avatars.githubusercontent.com/u/965219?v=4&s=48" width="48" height="48" alt="bsormagec" title="bsormagec"/></a> <a href="https://github.com/jessy2027"><img src="https://avatars.githubusercontent.com/u/89694096?v=4&s=48" width="48" height="48" alt="Jessy LANGE" title="Jessy LANGE"/></a> <a href="https://github.com/aerolalit"><img src="https://avatars.githubusercontent.com/u/17166039?v=4&s=48" width="48" height="48" alt="Lalit Singh" title="Lalit Singh"/></a> <a href="https://github.com/hyf0-agent"><img src="https://avatars.githubusercontent.com/u/258783736?v=4&s=48" width="48" height="48" alt="hyf0-agent" title="hyf0-agent"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a> <a href="https://github.com/unisone"><img src="https://avatars.githubusercontent.com/u/32521398?v=4&s=48" width="48" height="48" alt="unisone" title="unisone"/></a> <a href="https://github.com/jeann2013"><img src="https://avatars.githubusercontent.com/u/3299025?v=4&s=48" width="48" height="48" alt="jeann2013" title="jeann2013"/></a> <a href="https://github.com/jogelin"><img src="https://avatars.githubusercontent.com/u/954509?v=4&s=48" width="48" height="48" alt="jogelin" title="jogelin"/></a> <a href="https://github.com/rmorse"><img src="https://avatars.githubusercontent.com/u/853547?v=4&s=48" width="48" height="48" alt="rmorse" title="rmorse"/></a>
<a href="https://github.com/scz2011"><img src="https://avatars.githubusercontent.com/u/9337506?v=4&s=48" width="48" height="48" alt="scz2011" title="scz2011"/></a> <a href="https://github.com/wes-davis"><img src="https://avatars.githubusercontent.com/u/16506720?v=4&s=48" width="48" height="48" alt="wes-davis" title="wes-davis"/></a> <a href="https://github.com/popomore"><img src="https://avatars.githubusercontent.com/u/360661?v=4&s=48" width="48" height="48" alt="popomore" title="popomore"/></a> <a href="https://github.com/cathrynlavery"><img src="https://avatars.githubusercontent.com/u/50469282?v=4&s=48" width="48" height="48" alt="cathrynlavery" title="cathrynlavery"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a> <a href="https://github.com/jscaldwell55"><img src="https://avatars.githubusercontent.com/u/111952840?v=4&s=48" width="48" height="48" alt="Jay Caldwell" title="Jay Caldwell"/></a> <a href="https://github.com/gut-puncture"><img src="https://avatars.githubusercontent.com/u/75851986?v=4&s=48" width="48" height="48" alt="Shailesh" title="Shailesh"/></a> <a href="https://github.com/KirillShchetinin"><img src="https://avatars.githubusercontent.com/u/13061871?v=4&s=48" width="48" height="48" alt="Kirill Shchetynin" title="Kirill Shchetynin"/></a> <a href="https://github.com/ruypang"><img src="https://avatars.githubusercontent.com/u/46941315?v=4&s=48" width="48" height="48" alt="ruypang" title="ruypang"/></a>
<a href="https://github.com/mitchmcalister"><img src="https://avatars.githubusercontent.com/u/209334?v=4&s=48" width="48" height="48" alt="mitchmcalister" title="mitchmcalister"/></a> <a href="https://github.com/pvoo"><img src="https://avatars.githubusercontent.com/u/20116814?v=4&s=48" width="48" height="48" alt="Paul van Oorschot" title="Paul van Oorschot"/></a> <a href="https://github.com/guxu11"><img src="https://avatars.githubusercontent.com/u/53551744?v=4&s=48" width="48" height="48" alt="Xu Gu" title="Xu Gu"/></a> <a href="https://github.com/lml2468"><img src="https://avatars.githubusercontent.com/u/39320777?v=4&s=48" width="48" height="48" alt="Menglin Li" title="Menglin Li"/></a> <a href="https://github.com/artuskg"><img src="https://avatars.githubusercontent.com/u/11966157?v=4&s=48" width="48" height="48" alt="artuskg" title="artuskg"/></a> <a href="https://github.com/jackheuberger"><img src="https://avatars.githubusercontent.com/u/7830838?v=4&s=48" width="48" height="48" alt="jackheuberger" title="jackheuberger"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/superman32432432"><img src="https://avatars.githubusercontent.com/u/7228420?v=4&s=48" width="48" height="48" alt="superman32432432" title="superman32432432"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a> <a href="https://github.com/Zitzak"><img src="https://avatars.githubusercontent.com/u/43185740?v=4&s=48" width="48" height="48" alt="Marvin" title="Marvin"/></a>
<a href="https://github.com/DrCrinkle"><img src="https://avatars.githubusercontent.com/u/62564740?v=4&s=48" width="48" height="48" alt="Taylor Asplund" title="Taylor Asplund"/></a> <a href="https://github.com/dakshaymehta"><img src="https://avatars.githubusercontent.com/u/50276213?v=4&s=48" width="48" height="48" alt="dakshaymehta" title="dakshaymehta"/></a> <a href="https://github.com/stefangalescu"><img src="https://avatars.githubusercontent.com/u/52995748?v=4&s=48" width="48" height="48" alt="Stefan Galescu" title="Stefan Galescu"/></a> <a href="https://github.com/lploc94"><img src="https://avatars.githubusercontent.com/u/28453843?v=4&s=48" width="48" height="48" alt="lploc94" title="lploc94"/></a> <a href="https://github.com/WalterSumbon"><img src="https://avatars.githubusercontent.com/u/45062253?v=4&s=48" width="48" height="48" alt="WalterSumbon" title="WalterSumbon"/></a> <a href="https://github.com/krizpoon"><img src="https://avatars.githubusercontent.com/u/1977532?v=4&s=48" width="48" height="48" alt="krizpoon" title="krizpoon"/></a> <a href="https://github.com/EnzeD"><img src="https://avatars.githubusercontent.com/u/9866900?v=4&s=48" width="48" height="48" alt="EnzeD" title="EnzeD"/></a> <a href="https://github.com/Evizero"><img src="https://avatars.githubusercontent.com/u/10854026?v=4&s=48" width="48" height="48" alt="Evizero" title="Evizero"/></a> <a href="https://github.com/Grynn"><img src="https://avatars.githubusercontent.com/u/212880?v=4&s=48" width="48" height="48" alt="Grynn" title="Grynn"/></a> <a href="https://github.com/hydro13"><img src="https://avatars.githubusercontent.com/u/6640526?v=4&s=48" width="48" height="48" alt="hydro13" title="hydro13"/></a>
<a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/kentaro"><img src="https://avatars.githubusercontent.com/u/3458?v=4&s=48" width="48" height="48" alt="kentaro" title="kentaro"/></a> <a href="https://github.com/kunalk16"><img src="https://avatars.githubusercontent.com/u/5303824?v=4&s=48" width="48" height="48" alt="kunalk16" title="kunalk16"/></a> <a href="https://github.com/longmaba"><img src="https://avatars.githubusercontent.com/u/9361500?v=4&s=48" width="48" height="48" alt="longmaba" title="longmaba"/></a> <a href="https://github.com/mjrussell"><img src="https://avatars.githubusercontent.com/u/1641895?v=4&s=48" width="48" height="48" alt="mjrussell" title="mjrussell"/></a> <a href="https://github.com/optimikelabs"><img src="https://avatars.githubusercontent.com/u/31423109?v=4&s=48" width="48" height="48" alt="optimikelabs" title="optimikelabs"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/RamiNoodle733"><img src="https://avatars.githubusercontent.com/u/117773986?v=4&s=48" width="48" height="48" alt="RamiNoodle733" title="RamiNoodle733"/></a> <a href="https://github.com/sauerdaniel"><img src="https://avatars.githubusercontent.com/u/81422812?v=4&s=48" width="48" height="48" alt="sauerdaniel" title="sauerdaniel"/></a> <a href="https://github.com/SleuthCo"><img src="https://avatars.githubusercontent.com/u/259695222?v=4&s=48" width="48" height="48" alt="SleuthCo" title="SleuthCo"/></a>
<a href="https://github.com/TaKO8Ki"><img src="https://avatars.githubusercontent.com/u/41065217?v=4&s=48" width="48" height="48" alt="TaKO8Ki" title="TaKO8Ki"/></a> <a href="https://github.com/travisp"><img src="https://avatars.githubusercontent.com/u/165698?v=4&s=48" width="48" height="48" alt="travisp" title="travisp"/></a> <a href="https://github.com/rodbland2021"><img src="https://avatars.githubusercontent.com/u/86267410?v=4&s=48" width="48" height="48" alt="rodbland2021" title="rodbland2021"/></a> <a href="https://github.com/fagemx"><img src="https://avatars.githubusercontent.com/u/117356295?v=4&s=48" width="48" height="48" alt="fagemx" title="fagemx"/></a> <a href="https://github.com/BigUncle"><img src="https://avatars.githubusercontent.com/u/9360607?v=4&s=48" width="48" height="48" alt="BigUncle" title="BigUncle"/></a> <a href="https://github.com/pycckuu"><img src="https://avatars.githubusercontent.com/u/1489583?v=4&s=48" width="48" height="48" alt="Igor Markelov" title="Igor Markelov"/></a> <a href="https://github.com/zhoulongchao77"><img src="https://avatars.githubusercontent.com/u/65058500?v=4&s=48" width="48" height="48" alt="zhoulc777" title="zhoulc777"/></a> <a href="https://github.com/connorshea"><img src="https://avatars.githubusercontent.com/u/2977353?v=4&s=48" width="48" height="48" alt="connorshea" title="connorshea"/></a> <a href="https://github.com/paceyw"><img src="https://avatars.githubusercontent.com/u/44923937?v=4&s=48" width="48" height="48" alt="TIHU" title="TIHU"/></a> <a href="https://github.com/tonydehnke"><img src="https://avatars.githubusercontent.com/u/36720180?v=4&s=48" width="48" height="48" alt="Tony Dehnke" title="Tony Dehnke"/></a>
<a href="https://github.com/pablohrcarvalho"><img src="https://avatars.githubusercontent.com/u/66948122?v=4&s=48" width="48" height="48" alt="pablohrcarvalho" title="pablohrcarvalho"/></a> <a href="https://github.com/bonald"><img src="https://avatars.githubusercontent.com/u/12394874?v=4&s=48" width="48" height="48" alt="bonald" title="bonald"/></a> <a href="https://github.com/rhuanssauro"><img src="https://avatars.githubusercontent.com/u/164682191?v=4&s=48" width="48" height="48" alt="rhuanssauro" title="rhuanssauro"/></a> <a href="https://github.com/CommanderCrowCode"><img src="https://avatars.githubusercontent.com/u/72845369?v=4&s=48" width="48" height="48" alt="Tanwa Arpornthip" title="Tanwa Arpornthip"/></a> <a href="https://github.com/webvijayi"><img src="https://avatars.githubusercontent.com/u/49924855?v=4&s=48" width="48" height="48" alt="webvijayi" title="webvijayi"/></a> <a href="https://github.com/tomron87"><img src="https://avatars.githubusercontent.com/u/126325152?v=4&s=48" width="48" height="48" alt="Tom Ron" title="Tom Ron"/></a> <a href="https://github.com/ozbillwang"><img src="https://avatars.githubusercontent.com/u/8954908?v=4&s=48" width="48" height="48" alt="ozbillwang" title="ozbillwang"/></a> <a href="https://github.com/Patrick-Barletta"><img src="https://avatars.githubusercontent.com/u/67929313?v=4&s=48" width="48" height="48" alt="Patrick Barletta" title="Patrick Barletta"/></a> <a href="https://github.com/ianderrington"><img src="https://avatars.githubusercontent.com/u/76016868?v=4&s=48" width="48" height="48" alt="Ian Derrington" title="Ian Derrington"/></a> <a href="https://github.com/austinm911"><img src="https://avatars.githubusercontent.com/u/31991302?v=4&s=48" width="48" height="48" alt="austinm911" title="austinm911"/></a>
<a href="https://github.com/Ayush10"><img src="https://avatars.githubusercontent.com/u/7945279?v=4&s=48" width="48" height="48" alt="Ayush10" title="Ayush10"/></a> <a href="https://github.com/boris721"><img src="https://avatars.githubusercontent.com/u/257853888?v=4&s=48" width="48" height="48" alt="boris721" title="boris721"/></a> <a href="https://github.com/damoahdominic"><img src="https://avatars.githubusercontent.com/u/4623434?v=4&s=48" width="48" height="48" alt="damoahdominic" title="damoahdominic"/></a> <a href="https://github.com/doodlewind"><img src="https://avatars.githubusercontent.com/u/7312949?v=4&s=48" width="48" height="48" alt="doodlewind" title="doodlewind"/></a> <a href="https://github.com/ikari-pl"><img src="https://avatars.githubusercontent.com/u/811702?v=4&s=48" width="48" height="48" alt="ikari-pl" title="ikari-pl"/></a> <a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/shayan919293"><img src="https://avatars.githubusercontent.com/u/60409704?v=4&s=48" width="48" height="48" alt="shayan919293" title="shayan919293"/></a> <a href="https://github.com/Harrington-bot"><img src="https://avatars.githubusercontent.com/u/261410808?v=4&s=48" width="48" height="48" alt="Harrington-bot" title="Harrington-bot"/></a> <a href="https://github.com/nonggialiang"><img src="https://avatars.githubusercontent.com/u/14367839?v=4&s=48" width="48" height="48" alt="nonggia.liang" title="nonggia.liang"/></a> <a href="https://github.com/TinyTb"><img src="https://avatars.githubusercontent.com/u/5957298?v=4&s=48" width="48" height="48" alt="Michael Lee" title="Michael Lee"/></a>
<a href="https://github.com/OscarMinjarez"><img src="https://avatars.githubusercontent.com/u/86080038?v=4&s=48" width="48" height="48" alt="OscarMinjarez" title="OscarMinjarez"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/Alg0rix"><img src="https://avatars.githubusercontent.com/u/53804949?v=4&s=48" width="48" height="48" alt="Alg0rix" title="Alg0rix"/></a> <a href="https://github.com/L-U-C-K-Y"><img src="https://avatars.githubusercontent.com/u/14868134?v=4&s=48" width="48" height="48" alt="Lucky" title="Lucky"/></a> <a href="https://github.com/Kepler2024"><img src="https://avatars.githubusercontent.com/u/166882517?v=4&s=48" width="48" height="48" alt="Harry Cui Kepler" title="Harry Cui Kepler"/></a> <a href="https://github.com/h0tp-ftw"><img src="https://avatars.githubusercontent.com/u/141889580?v=4&s=48" width="48" height="48" alt="h0tp-ftw" title="h0tp-ftw"/></a> <a href="https://github.com/Youyou972"><img src="https://avatars.githubusercontent.com/u/50808411?v=4&s=48" width="48" height="48" alt="Youyou972" title="Youyou972"/></a> <a href="https://github.com/dominicnunez"><img src="https://avatars.githubusercontent.com/u/43616264?v=4&s=48" width="48" height="48" alt="Dominic" title="Dominic"/></a> <a href="https://github.com/danielwanwx"><img src="https://avatars.githubusercontent.com/u/144515713?v=4&s=48" width="48" height="48" alt="danielwanwx" title="danielwanwx"/></a> <a href="https://github.com/0xJonHoldsCrypto"><img src="https://avatars.githubusercontent.com/u/81202085?v=4&s=48" width="48" height="48" alt="0xJonHoldsCrypto" title="0xJonHoldsCrypto"/></a>
<a href="https://github.com/akyourowngames"><img src="https://avatars.githubusercontent.com/u/123736861?v=4&s=48" width="48" height="48" alt="akyourowngames" title="akyourowngames"/></a> <a href="https://github.com/apps/clawdinator"><img src="https://avatars.githubusercontent.com/in/2607181?v=4&s=48" width="48" height="48" alt="clawdinator[bot]" title="clawdinator[bot]"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/thesash"><img src="https://avatars.githubusercontent.com/u/1166151?v=4&s=48" width="48" height="48" alt="thesash" title="thesash"/></a> <a href="https://github.com/thesomewhatyou"><img src="https://avatars.githubusercontent.com/u/162917831?v=4&s=48" width="48" height="48" alt="thesomewhatyou" title="thesomewhatyou"/></a> <a href="https://github.com/dashed"><img src="https://avatars.githubusercontent.com/u/139499?v=4&s=48" width="48" height="48" alt="dashed" title="dashed"/></a> <a href="https://github.com/minupla"><img src="https://avatars.githubusercontent.com/u/42547246?v=4&s=48" width="48" height="48" alt="Dale Babiy" title="Dale Babiy"/></a> <a href="https://github.com/Diaspar4u"><img src="https://avatars.githubusercontent.com/u/3605840?v=4&s=48" width="48" height="48" alt="Diaspar4u" title="Diaspar4u"/></a> <a href="https://github.com/brianleach"><img src="https://avatars.githubusercontent.com/u/1900805?v=4&s=48" width="48" height="48" alt="brianleach" title="brianleach"/></a> <a href="https://github.com/codexGW"><img src="https://avatars.githubusercontent.com/u/9350182?v=4&s=48" width="48" height="48" alt="codexGW" title="codexGW"/></a>
<a href="https://github.com/dirbalak"><img src="https://avatars.githubusercontent.com/u/30323349?v=4&s=48" width="48" height="48" alt="dirbalak" title="dirbalak"/></a> <a href="https://github.com/Iranb"><img src="https://avatars.githubusercontent.com/u/49674669?v=4&s=48" width="48" height="48" alt="Iranb" title="Iranb"/></a> <a href="https://github.com/rdev"><img src="https://avatars.githubusercontent.com/u/8418866?v=4&s=48" width="48" height="48" alt="Max" title="Max"/></a> <a href="https://github.com/papago2355"><img src="https://avatars.githubusercontent.com/u/68721273?v=4&s=48" width="48" height="48" alt="TideFinder" title="TideFinder"/></a> <a href="https://github.com/cdorsey"><img src="https://avatars.githubusercontent.com/u/12650570?v=4&s=48" width="48" height="48" alt="Chase Dorsey" title="Chase Dorsey"/></a> <a href="https://github.com/Joly0"><img src="https://avatars.githubusercontent.com/u/13993216?v=4&s=48" width="48" height="48" alt="Joly0" title="Joly0"/></a> <a href="https://github.com/adityashaw2"><img src="https://avatars.githubusercontent.com/u/41204444?v=4&s=48" width="48" height="48" alt="adityashaw2" title="adityashaw2"/></a> <a href="https://github.com/tumf"><img src="https://avatars.githubusercontent.com/u/69994?v=4&s=48" width="48" height="48" alt="tumf" title="tumf"/></a> <a href="https://github.com/slonce70"><img src="https://avatars.githubusercontent.com/u/130596182?v=4&s=48" width="48" height="48" alt="slonce70" title="slonce70"/></a> <a href="https://github.com/alexgleason"><img src="https://avatars.githubusercontent.com/u/3639540?v=4&s=48" width="48" height="48" alt="alexgleason" title="alexgleason"/></a>
<a href="https://github.com/theonejvo"><img src="https://avatars.githubusercontent.com/u/125909656?v=4&s=48" width="48" height="48" alt="theonejvo" title="theonejvo"/></a> <a href="https://github.com/adao-max"><img src="https://avatars.githubusercontent.com/u/153898832?v=4&s=48" width="48" height="48" alt="Skyler Miao" title="Skyler Miao"/></a> <a href="https://github.com/jlowin"><img src="https://avatars.githubusercontent.com/u/153965?v=4&s=48" width="48" height="48" alt="Jeremiah Lowin" title="Jeremiah Lowin"/></a> <a href="https://github.com/peetzweg"><img src="https://avatars.githubusercontent.com/u/839848?v=4&s=48" width="48" height="48" alt="peetzweg/" title="peetzweg/"/></a> <a href="https://github.com/chrisrodz"><img src="https://avatars.githubusercontent.com/u/2967620?v=4&s=48" width="48" height="48" alt="chrisrodz" title="chrisrodz"/></a> <a href="https://github.com/ghsmc"><img src="https://avatars.githubusercontent.com/u/68118719?v=4&s=48" width="48" height="48" alt="ghsmc" title="ghsmc"/></a> <a href="https://github.com/ibrahimq21"><img src="https://avatars.githubusercontent.com/u/8392472?v=4&s=48" width="48" height="48" alt="ibrahimq21" title="ibrahimq21"/></a> <a href="https://github.com/irtiq7"><img src="https://avatars.githubusercontent.com/u/3823029?v=4&s=48" width="48" height="48" alt="irtiq7" title="irtiq7"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/kelvinCB"><img src="https://avatars.githubusercontent.com/u/50544379?v=4&s=48" width="48" height="48" alt="kelvinCB" title="kelvinCB"/></a>
<a href="https://github.com/mitsuhiko"><img src="https://avatars.githubusercontent.com/u/7396?v=4&s=48" width="48" height="48" alt="mitsuhiko" title="mitsuhiko"/></a> <a href="https://github.com/rybnikov"><img src="https://avatars.githubusercontent.com/u/7761808?v=4&s=48" width="48" height="48" alt="rybnikov" title="rybnikov"/></a> <a href="https://github.com/santiagomed"><img src="https://avatars.githubusercontent.com/u/30184543?v=4&s=48" width="48" height="48" alt="santiagomed" title="santiagomed"/></a> <a href="https://github.com/suminhthanh"><img src="https://avatars.githubusercontent.com/u/2907636?v=4&s=48" width="48" height="48" alt="suminhthanh" title="suminhthanh"/></a> <a href="https://github.com/svkozak"><img src="https://avatars.githubusercontent.com/u/31941359?v=4&s=48" width="48" height="48" alt="svkozak" title="svkozak"/></a> <a href="https://github.com/kaizen403"><img src="https://avatars.githubusercontent.com/u/134706404?v=4&s=48" width="48" height="48" alt="kaizen403" title="kaizen403"/></a> <a href="https://github.com/sleontenko"><img src="https://avatars.githubusercontent.com/u/7135949?v=4&s=48" width="48" height="48" alt="sleontenko" title="sleontenko"/></a> <a href="https://github.com/nk1tz"><img src="https://avatars.githubusercontent.com/u/12980165?v=4&s=48" width="48" height="48" alt="Nate" title="Nate"/></a> <a href="https://github.com/CornBrother0x"><img src="https://avatars.githubusercontent.com/u/101160087?v=4&s=48" width="48" height="48" alt="CornBrother0x" title="CornBrother0x"/></a> <a href="https://github.com/DukeDeSouth"><img src="https://avatars.githubusercontent.com/u/51200688?v=4&s=48" width="48" height="48" alt="DukeDeSouth" title="DukeDeSouth"/></a>
<a href="https://github.com/crimeacs"><img src="https://avatars.githubusercontent.com/u/35071559?v=4&s=48" width="48" height="48" alt="crimeacs" title="crimeacs"/></a> <a href="https://github.com/liebertar"><img src="https://avatars.githubusercontent.com/u/99405438?v=4&s=48" width="48" height="48" alt="Cklee" title="Cklee"/></a> <a href="https://github.com/garnetlyx"><img src="https://avatars.githubusercontent.com/u/12513503?v=4&s=48" width="48" height="48" alt="Garnet Liu" title="Garnet Liu"/></a> <a href="https://github.com/Bermudarat"><img src="https://avatars.githubusercontent.com/u/10937319?v=4&s=48" width="48" height="48" alt="neverland" title="neverland"/></a> <a href="https://github.com/ryancontent"><img src="https://avatars.githubusercontent.com/u/39743613?v=4&s=48" width="48" height="48" alt="ryan" title="ryan"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a> <a href="https://github.com/AdeboyeDN"><img src="https://avatars.githubusercontent.com/u/65312338?v=4&s=48" width="48" height="48" alt="AdeboyeDN" title="AdeboyeDN"/></a> <a href="https://github.com/neooriginal"><img src="https://avatars.githubusercontent.com/u/54811660?v=4&s=48" width="48" height="48" alt="Neo" title="Neo"/></a> <a href="https://github.com/asklee-klawd"><img src="https://avatars.githubusercontent.com/u/105007315?v=4&s=48" width="48" height="48" alt="asklee-klawd" title="asklee-klawd"/></a> <a href="https://github.com/benediktjohannes"><img src="https://avatars.githubusercontent.com/u/253604130?v=4&s=48" width="48" height="48" alt="benediktjohannes" title="benediktjohannes"/></a>
<a href="https://github.com/zhangzhefang-github"><img src="https://avatars.githubusercontent.com/u/34058239?v=4&s=48" width="48" height="48" alt="张哲芳" title="张哲芳"/></a> <a href="https://github.com/constansino"><img src="https://avatars.githubusercontent.com/u/65108260?v=4&s=48" width="48" height="48" alt="constansino" title="constansino"/></a> <a href="https://github.com/yuting0624"><img src="https://avatars.githubusercontent.com/u/32728916?v=4&s=48" width="48" height="48" alt="Yuting Lin" title="Yuting Lin"/></a> <a href="https://github.com/joelnishanth"><img src="https://avatars.githubusercontent.com/u/140015627?v=4&s=48" width="48" height="48" alt="OfflynAI" title="OfflynAI"/></a> <a href="https://github.com/18-RAJAT"><img src="https://avatars.githubusercontent.com/u/78920780?v=4&s=48" width="48" height="48" alt="Rajat Joshi" title="Rajat Joshi"/></a> <a href="https://github.com/pahdo"><img src="https://avatars.githubusercontent.com/u/12799392?v=4&s=48" width="48" height="48" alt="Daniel Zou" title="Daniel Zou"/></a> <a href="https://github.com/manikv12"><img src="https://avatars.githubusercontent.com/u/49544491?v=4&s=48" width="48" height="48" alt="Manik Vahsith" title="Manik Vahsith"/></a> <a href="https://github.com/ProspectOre"><img src="https://avatars.githubusercontent.com/u/54486432?v=4&s=48" width="48" height="48" alt="ProspectOre" title="ProspectOre"/></a> <a href="https://github.com/detecti1"><img src="https://avatars.githubusercontent.com/u/1622461?v=4&s=48" width="48" height="48" alt="Lilo" title="Lilo"/></a> <a href="https://github.com/24601"><img src="https://avatars.githubusercontent.com/u/1157207?v=4&s=48" width="48" height="48" alt="24601" title="24601"/></a>
<a href="https://github.com/awkoy"><img src="https://avatars.githubusercontent.com/u/13995636?v=4&s=48" width="48" height="48" alt="awkoy" title="awkoy"/></a> <a href="https://github.com/dawondyifraw"><img src="https://avatars.githubusercontent.com/u/9797257?v=4&s=48" width="48" height="48" alt="dawondyifraw" title="dawondyifraw"/></a> <a href="https://github.com/apps/google-labs-jules"><img src="https://avatars.githubusercontent.com/in/842251?v=4&s=48" width="48" height="48" alt="google-labs-jules[bot]" title="google-labs-jules[bot]"/></a> <a href="https://github.com/hyojin"><img src="https://avatars.githubusercontent.com/u/3413183?v=4&s=48" width="48" height="48" alt="hyojin" title="hyojin"/></a> <a href="https://github.com/Kansodata"><img src="https://avatars.githubusercontent.com/u/225288021?v=4&s=48" width="48" height="48" alt="Kansodata" title="Kansodata"/></a> <a href="https://github.com/natedenh"><img src="https://avatars.githubusercontent.com/u/13399956?v=4&s=48" width="48" height="48" alt="natedenh" title="natedenh"/></a> <a href="https://github.com/pi0"><img src="https://avatars.githubusercontent.com/u/5158436?v=4&s=48" width="48" height="48" alt="pi0" title="pi0"/></a> <a href="https://github.com/dddabtc"><img src="https://avatars.githubusercontent.com/u/104875499?v=4&s=48" width="48" height="48" alt="dddabtc" title="dddabtc"/></a> <a href="https://github.com/AkashKobal"><img src="https://avatars.githubusercontent.com/u/98216083?v=4&s=48" width="48" height="48" alt="AkashKobal" title="AkashKobal"/></a> <a href="https://github.com/wu-tian807"><img src="https://avatars.githubusercontent.com/u/61640083?v=4&s=48" width="48" height="48" alt="wu-tian807" title="wu-tian807"/></a>
<a href="https://github.com/kyleok"><img src="https://avatars.githubusercontent.com/u/58307870?v=4&s=48" width="48" height="48" alt="Ganghyun Kim" title="Ganghyun Kim"/></a> <a href="https://github.com/sbking"><img src="https://avatars.githubusercontent.com/u/3913213?v=4&s=48" width="48" height="48" alt="Stephen Brian King" title="Stephen Brian King"/></a> <a href="https://github.com/tosh-hamburg"><img src="https://avatars.githubusercontent.com/u/58424326?v=4&s=48" width="48" height="48" alt="tosh-hamburg" title="tosh-hamburg"/></a> <a href="https://github.com/John-Rood"><img src="https://avatars.githubusercontent.com/u/62669593?v=4&s=48" width="48" height="48" alt="John Rood" title="John Rood"/></a> <a href="https://github.com/divisonofficer"><img src="https://avatars.githubusercontent.com/u/41609506?v=4&s=48" width="48" height="48" alt="JINNYEONG KIM" title="JINNYEONG KIM"/></a> <a href="https://github.com/dinakars777"><img src="https://avatars.githubusercontent.com/u/250428393?v=4&s=48" width="48" height="48" alt="Dinakar Sarbada" title="Dinakar Sarbada"/></a> <a href="https://github.com/aj47"><img src="https://avatars.githubusercontent.com/u/8023513?v=4&s=48" width="48" height="48" alt="aj47" title="aj47"/></a> <a href="https://github.com/Protocol-zero-0"><img src="https://avatars.githubusercontent.com/u/257158451?v=4&s=48" width="48" height="48" alt="Protocol Zero" title="Protocol Zero"/></a> <a href="https://github.com/Limitless2023"><img src="https://avatars.githubusercontent.com/u/127183162?v=4&s=48" width="48" height="48" alt="Limitless" title="Limitless"/></a> <a href="https://github.com/cheeeee"><img src="https://avatars.githubusercontent.com/u/21245729?v=4&s=48" width="48" height="48" alt="Mykyta Bozhenko" title="Mykyta Bozhenko"/></a>
<a href="https://github.com/nicholascyh"><img src="https://avatars.githubusercontent.com/u/188132635?v=4&s=48" width="48" height="48" alt="Nicholas" title="Nicholas"/></a> <a href="https://github.com/shivamraut101"><img src="https://avatars.githubusercontent.com/u/110457469?v=4&s=48" width="48" height="48" alt="Shivam Kumar Raut" title="Shivam Kumar Raut"/></a> <a href="https://github.com/andreesg"><img src="https://avatars.githubusercontent.com/u/810322?v=4&s=48" width="48" height="48" alt="andreesg" title="andreesg"/></a> <a href="https://github.com/fwhite13"><img src="https://avatars.githubusercontent.com/u/173006051?v=4&s=48" width="48" height="48" alt="Fred White" title="Fred White"/></a> <a href="https://github.com/Anandesh-Sharma"><img src="https://avatars.githubusercontent.com/u/30695364?v=4&s=48" width="48" height="48" alt="Anandesh-Sharma" title="Anandesh-Sharma"/></a> <a href="https://github.com/ysqander"><img src="https://avatars.githubusercontent.com/u/80843820?v=4&s=48" width="48" height="48" alt="ysqander" title="ysqander"/></a> <a href="https://github.com/ezhikkk"><img src="https://avatars.githubusercontent.com/u/105670095?v=4&s=48" width="48" height="48" alt="ezhikkk" title="ezhikkk"/></a> <a href="https://github.com/andreabadesso"><img src="https://avatars.githubusercontent.com/u/3586068?v=4&s=48" width="48" height="48" alt="andreabadesso" title="andreabadesso"/></a> <a href="https://github.com/BinaryMuse"><img src="https://avatars.githubusercontent.com/u/189606?v=4&s=48" width="48" height="48" alt="BinaryMuse" title="BinaryMuse"/></a> <a href="https://github.com/cordx56"><img src="https://avatars.githubusercontent.com/u/23298744?v=4&s=48" width="48" height="48" alt="cordx56" title="cordx56"/></a>
<a href="https://github.com/DevSecTim"><img src="https://avatars.githubusercontent.com/u/2226767?v=4&s=48" width="48" height="48" alt="DevSecTim" title="DevSecTim"/></a> <a href="https://github.com/edincampara"><img src="https://avatars.githubusercontent.com/u/142477787?v=4&s=48" width="48" height="48" alt="edincampara" title="edincampara"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/gildo"><img src="https://avatars.githubusercontent.com/u/133645?v=4&s=48" width="48" height="48" alt="gildo" title="gildo"/></a> <a href="https://github.com/itsjaydesu"><img src="https://avatars.githubusercontent.com/u/220390?v=4&s=48" width="48" height="48" alt="itsjaydesu" title="itsjaydesu"/></a> <a href="https://github.com/ivanrvpereira"><img src="https://avatars.githubusercontent.com/u/183991?v=4&s=48" width="48" height="48" alt="ivanrvpereira" title="ivanrvpereira"/></a> <a href="https://github.com/loeclos"><img src="https://avatars.githubusercontent.com/u/116607327?v=4&s=48" width="48" height="48" alt="loeclos" title="loeclos"/></a> <a href="https://github.com/MarvinCui"><img src="https://avatars.githubusercontent.com/u/130876763?v=4&s=48" width="48" height="48" alt="MarvinCui" title="MarvinCui"/></a> <a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a> <a href="https://github.com/thejhinvirtuoso"><img src="https://avatars.githubusercontent.com/u/258521837?v=4&s=48" width="48" height="48" alt="thejhinvirtuoso" title="thejhinvirtuoso"/></a>
<a href="https://github.com/yudshj"><img src="https://avatars.githubusercontent.com/u/16971372?v=4&s=48" width="48" height="48" alt="yudshj" title="yudshj"/></a> <a href="https://github.com/Wangnov"><img src="https://avatars.githubusercontent.com/u/48670012?v=4&s=48" width="48" height="48" alt="Wangnov" title="Wangnov"/></a> <a href="https://github.com/JonathanWorks"><img src="https://avatars.githubusercontent.com/u/124476234?v=4&s=48" width="48" height="48" alt="Jonathan Works" title="Jonathan Works"/></a> <a href="https://github.com/yassine20011"><img src="https://avatars.githubusercontent.com/u/59234686?v=4&s=48" width="48" height="48" alt="Yassine Amjad" title="Yassine Amjad"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/hirefrank"><img src="https://avatars.githubusercontent.com/u/183158?v=4&s=48" width="48" height="48" alt="Frank Harris" title="Frank Harris"/></a> <a href="https://github.com/kennyklee"><img src="https://avatars.githubusercontent.com/u/1432489?v=4&s=48" width="48" height="48" alt="Kenny Lee" title="Kenny Lee"/></a> <a href="https://github.com/ThomsenDrake"><img src="https://avatars.githubusercontent.com/u/120344051?v=4&s=48" width="48" height="48" alt="Drake Thomsen" title="Drake Thomsen"/></a> <a href="https://github.com/wangai-studio"><img src="https://avatars.githubusercontent.com/u/256938352?v=4&s=48" width="48" height="48" alt="wangai-studio" title="wangai-studio"/></a> <a href="https://github.com/AytuncYildizli"><img src="https://avatars.githubusercontent.com/u/47717026?v=4&s=48" width="48" height="48" alt="AytuncYildizli" title="AytuncYildizli"/></a>
<a href="https://github.com/KnHack"><img src="https://avatars.githubusercontent.com/u/2346724?v=4&s=48" width="48" height="48" alt="Charlie Niño" title="Charlie Niño"/></a> <a href="https://github.com/17jmumford"><img src="https://avatars.githubusercontent.com/u/36290330?v=4&s=48" width="48" height="48" alt="Jeremy Mumford" title="Jeremy Mumford"/></a> <a href="https://github.com/Yeom-JinHo"><img src="https://avatars.githubusercontent.com/u/81306489?v=4&s=48" width="48" height="48" alt="Yeom-JinHo" title="Yeom-JinHo"/></a> <a href="https://github.com/robaxelsen"><img src="https://avatars.githubusercontent.com/u/13132899?v=4&s=48" width="48" height="48" alt="Rob Axelsen" title="Rob Axelsen"/></a> <a href="https://github.com/junjunjunbong"><img src="https://avatars.githubusercontent.com/u/153147718?v=4&s=48" width="48" height="48" alt="junwon" title="junwon"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="Pratham Dubey" title="Pratham Dubey"/></a> <a href="https://github.com/amitbiswal007"><img src="https://avatars.githubusercontent.com/u/108086198?v=4&s=48" width="48" height="48" alt="amitbiswal007" title="amitbiswal007"/></a> <a href="https://github.com/Slats24"><img src="https://avatars.githubusercontent.com/u/42514321?v=4&s=48" width="48" height="48" alt="Slats" title="Slats"/></a> <a href="https://github.com/orenyomtov"><img src="https://avatars.githubusercontent.com/u/168856?v=4&s=48" width="48" height="48" alt="Oren" title="Oren"/></a> <a href="https://github.com/parkertoddbrooks"><img src="https://avatars.githubusercontent.com/u/585456?v=4&s=48" width="48" height="48" alt="Parker Todd Brooks" title="Parker Todd Brooks"/></a>
<a href="https://github.com/mattqdev"><img src="https://avatars.githubusercontent.com/u/115874885?v=4&s=48" width="48" height="48" alt="MattQ" title="MattQ"/></a> <a href="https://github.com/Milofax"><img src="https://avatars.githubusercontent.com/u/2537423?v=4&s=48" width="48" height="48" alt="Milofax" title="Milofax"/></a> <a href="https://github.com/stevebot-alive"><img src="https://avatars.githubusercontent.com/u/261149299?v=4&s=48" width="48" height="48" alt="Steve (OpenClaw)" title="Steve (OpenClaw)"/></a> <a href="https://github.com/ZetiMente"><img src="https://avatars.githubusercontent.com/u/76985631?v=4&s=48" width="48" height="48" alt="Matthew" title="Matthew"/></a> <a href="https://github.com/Cassius0924"><img src="https://avatars.githubusercontent.com/u/62874592?v=4&s=48" width="48" height="48" alt="Cassius0924" title="Cassius0924"/></a> <a href="https://github.com/0xbrak"><img src="https://avatars.githubusercontent.com/u/181251288?v=4&s=48" width="48" height="48" alt="0xbrak" title="0xbrak"/></a> <a href="https://github.com/8BlT"><img src="https://avatars.githubusercontent.com/u/162764392?v=4&s=48" width="48" height="48" alt="8BlT" title="8BlT"/></a> <a href="https://github.com/Abdul535"><img src="https://avatars.githubusercontent.com/u/54276938?v=4&s=48" width="48" height="48" alt="Abdul535" title="Abdul535"/></a> <a href="https://github.com/abhaymundhara"><img src="https://avatars.githubusercontent.com/u/62872231?v=4&s=48" width="48" height="48" alt="abhaymundhara" title="abhaymundhara"/></a> <a href="https://github.com/aduk059"><img src="https://avatars.githubusercontent.com/u/257603478?v=4&s=48" width="48" height="48" alt="aduk059" title="aduk059"/></a>
<a href="https://github.com/afurm"><img src="https://avatars.githubusercontent.com/u/6375192?v=4&s=48" width="48" height="48" alt="afurm" title="afurm"/></a> <a href="https://github.com/aisling404"><img src="https://avatars.githubusercontent.com/u/211950534?v=4&s=48" width="48" height="48" alt="aisling404" title="aisling404"/></a> <a href="https://github.com/akari-musubi"><img src="https://avatars.githubusercontent.com/u/259925157?v=4&s=48" width="48" height="48" alt="akari-musubi" title="akari-musubi"/></a> <a href="https://github.com/albertlieyingadrian"><img src="https://avatars.githubusercontent.com/u/12984659?v=4&s=48" width="48" height="48" alt="albertlieyingadrian" title="albertlieyingadrian"/></a> <a href="https://github.com/Alex-Alaniz"><img src="https://avatars.githubusercontent.com/u/88956822?v=4&s=48" width="48" height="48" alt="Alex-Alaniz" title="Alex-Alaniz"/></a> <a href="https://github.com/ali-aljufairi"><img src="https://avatars.githubusercontent.com/u/85583841?v=4&s=48" width="48" height="48" alt="ali-aljufairi" title="ali-aljufairi"/></a> <a href="https://github.com/altaywtf"><img src="https://avatars.githubusercontent.com/u/9790196?v=4&s=48" width="48" height="48" alt="altaywtf" title="altaywtf"/></a> <a href="https://github.com/araa47"><img src="https://avatars.githubusercontent.com/u/22760261?v=4&s=48" width="48" height="48" alt="araa47" title="araa47"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/avacadobanana352"><img src="https://avatars.githubusercontent.com/u/263496834?v=4&s=48" width="48" height="48" alt="avacadobanana352" title="avacadobanana352"/></a>
<a href="https://github.com/barronlroth"><img src="https://avatars.githubusercontent.com/u/5567884?v=4&s=48" width="48" height="48" alt="barronlroth" title="barronlroth"/></a> <a href="https://github.com/bennewton999"><img src="https://avatars.githubusercontent.com/u/458991?v=4&s=48" width="48" height="48" alt="bennewton999" title="bennewton999"/></a> <a href="https://github.com/bguidolim"><img src="https://avatars.githubusercontent.com/u/987360?v=4&s=48" width="48" height="48" alt="bguidolim" title="bguidolim"/></a> <a href="https://github.com/bigwest60"><img src="https://avatars.githubusercontent.com/u/12373979?v=4&s=48" width="48" height="48" alt="bigwest60" title="bigwest60"/></a> <a href="https://github.com/caelum0x"><img src="https://avatars.githubusercontent.com/u/130079063?v=4&s=48" width="48" height="48" alt="caelum0x" title="caelum0x"/></a> <a href="https://github.com/championswimmer"><img src="https://avatars.githubusercontent.com/u/1327050?v=4&s=48" width="48" height="48" alt="championswimmer" title="championswimmer"/></a> <a href="https://github.com/dutifulbob"><img src="https://avatars.githubusercontent.com/u/261991368?v=4&s=48" width="48" height="48" alt="dutifulbob" title="dutifulbob"/></a> <a href="https://github.com/eternauta1337"><img src="https://avatars.githubusercontent.com/u/550409?v=4&s=48" width="48" height="48" alt="eternauta1337" title="eternauta1337"/></a> <a href="https://github.com/foeken"><img src="https://avatars.githubusercontent.com/u/13864?v=4&s=48" width="48" height="48" alt="foeken" title="foeken"/></a> <a href="https://github.com/gittb"><img src="https://avatars.githubusercontent.com/u/8284364?v=4&s=48" width="48" height="48" alt="gittb" title="gittb"/></a>
<a href="https://github.com/HeimdallStrategy"><img src="https://avatars.githubusercontent.com/u/223014405?v=4&s=48" width="48" height="48" alt="HeimdallStrategy" title="HeimdallStrategy"/></a> <a href="https://github.com/junsuwhy"><img src="https://avatars.githubusercontent.com/u/4645498?v=4&s=48" width="48" height="48" alt="junsuwhy" title="junsuwhy"/></a> <a href="https://github.com/knocte"><img src="https://avatars.githubusercontent.com/u/331303?v=4&s=48" width="48" height="48" alt="knocte" title="knocte"/></a> <a href="https://github.com/MackDing"><img src="https://avatars.githubusercontent.com/u/19878893?v=4&s=48" width="48" height="48" alt="MackDing" title="MackDing"/></a> <a href="https://github.com/nobrainer-tech"><img src="https://avatars.githubusercontent.com/u/445466?v=4&s=48" width="48" height="48" alt="nobrainer-tech" title="nobrainer-tech"/></a> <a href="https://github.com/Noctivoro"><img src="https://avatars.githubusercontent.com/u/183974570?v=4&s=48" width="48" height="48" alt="Noctivoro" title="Noctivoro"/></a> <a href="https://github.com/Raikan10"><img src="https://avatars.githubusercontent.com/u/20675476?v=4&s=48" width="48" height="48" alt="Raikan10" title="Raikan10"/></a> <a href="https://github.com/Swader"><img src="https://avatars.githubusercontent.com/u/1430603?v=4&s=48" width="48" height="48" alt="Swader" title="Swader"/></a> <a href="https://github.com/algal"><img src="https://avatars.githubusercontent.com/u/264412?v=4&s=48" width="48" height="48" alt="Alexis Gallagher" title="Alexis Gallagher"/></a> <a href="https://github.com/alexstyl"><img src="https://avatars.githubusercontent.com/u/1665273?v=4&s=48" width="48" height="48" alt="alexstyl" title="alexstyl"/></a> <a href="https://github.com/ethanpalm"><img src="https://avatars.githubusercontent.com/u/56270045?v=4&s=48" width="48" height="48" alt="Ethan Palm" title="Ethan Palm"/></a>
<a href="https://github.com/yingchunbai"><img src="https://avatars.githubusercontent.com/u/33477283?v=4&s=48" width="48" height="48" alt="yingchunbai" title="yingchunbai"/></a> <a href="https://github.com/joshrad-dev"><img src="https://avatars.githubusercontent.com/u/62785552?v=4&s=48" width="48" height="48" alt="joshrad-dev" title="joshrad-dev"/></a> <a href="https://github.com/danballance"><img src="https://avatars.githubusercontent.com/u/13839912?v=4&s=48" width="48" height="48" alt="Dan Ballance" title="Dan Ballance"/></a> <a href="https://github.com/GHesericsu"><img src="https://avatars.githubusercontent.com/u/60202455?v=4&s=48" width="48" height="48" alt="Eric Su" title="Eric Su"/></a> <a href="https://github.com/kimitaka"><img src="https://avatars.githubusercontent.com/u/167225?v=4&s=48" width="48" height="48" alt="Kimitaka Watanabe" title="Kimitaka Watanabe"/></a> <a href="https://github.com/itsjling"><img src="https://avatars.githubusercontent.com/u/2521993?v=4&s=48" width="48" height="48" alt="Justin Ling" title="Justin Ling"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/RayBB"><img src="https://avatars.githubusercontent.com/u/921217?v=4&s=48" width="48" height="48" alt="Raymond Berger" title="Raymond Berger"/></a> <a href="https://github.com/atalovesyou"><img src="https://avatars.githubusercontent.com/u/3534502?v=4&s=48" width="48" height="48" alt="atalovesyou" title="atalovesyou"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a>
<a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a> <a href="https://github.com/efe-buken"><img src="https://avatars.githubusercontent.com/u/262546946?v=4&s=48" width="48" height="48" alt="efe-buken" title="efe-buken"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a> <a href="https://github.com/easternbloc"><img src="https://avatars.githubusercontent.com/u/92585?v=4&s=48" width="48" height="48" alt="easternbloc" title="easternbloc"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a>
<a href="https://github.com/sktbrd"><img src="https://avatars.githubusercontent.com/u/116202536?v=4&s=48" width="48" height="48" alt="sktbrd" title="sktbrd"/></a> <a href="https://github.com/larlyssa"><img src="https://avatars.githubusercontent.com/u/13128869?v=4&s=48" width="48" height="48" alt="larlyssa" title="larlyssa"/></a> <a href="https://github.com/Mind-Dragon"><img src="https://avatars.githubusercontent.com/u/262945885?v=4&s=48" width="48" height="48" alt="Mind-Dragon" title="Mind-Dragon"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/tmchow"><img src="https://avatars.githubusercontent.com/u/517103?v=4&s=48" width="48" height="48" alt="tmchow" title="tmchow"/></a> <a href="https://github.com/uli-will-code"><img src="https://avatars.githubusercontent.com/u/49715419?v=4&s=48" width="48" height="48" alt="uli-will-code" title="uli-will-code"/></a> <a href="https://github.com/mgratch"><img src="https://avatars.githubusercontent.com/u/2238658?v=4&s=48" width="48" height="48" alt="Marc Gratch" title="Marc Gratch"/></a> <a href="https://github.com/JackyWay"><img src="https://avatars.githubusercontent.com/u/53031570?v=4&s=48" width="48" height="48" alt="JackyWay" title="JackyWay"/></a> <a href="https://github.com/aaronveklabs"><img src="https://avatars.githubusercontent.com/u/225997828?v=4&s=48" width="48" height="48" alt="aaronveklabs" title="aaronveklabs"/></a> <a href="https://github.com/CJWTRUST"><img src="https://avatars.githubusercontent.com/u/235565898?v=4&s=48" width="48" height="48" alt="CJWTRUST" title="CJWTRUST"/></a>
<a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/odnxe"><img src="https://avatars.githubusercontent.com/u/403141?v=4&s=48" width="48" height="48" alt="odnxe" title="odnxe"/></a> <a href="https://github.com/T5-AndyML"><img src="https://avatars.githubusercontent.com/u/22801233?v=4&s=48" width="48" height="48" alt="T5-AndyML" title="T5-AndyML"/></a> <a href="https://github.com/j1philli"><img src="https://avatars.githubusercontent.com/u/3744255?v=4&s=48" width="48" height="48" alt="Josh Phillips" title="Josh Phillips"/></a> <a href="https://github.com/mujiannan"><img src="https://avatars.githubusercontent.com/u/46643837?v=4&s=48" width="48" height="48" alt="mujiannan" title="mujiannan"/></a> <a href="https://github.com/marcodd23"><img src="https://avatars.githubusercontent.com/u/3519682?v=4&s=48" width="48" height="48" alt="Marco Di Dionisio" title="Marco Di Dionisio"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/afern247"><img src="https://avatars.githubusercontent.com/u/34192856?v=4&s=48" width="48" height="48" alt="afern247" title="afern247"/></a> <a href="https://github.com/0oAstro"><img src="https://avatars.githubusercontent.com/u/79555780?v=4&s=48" width="48" height="48" alt="0oAstro" title="0oAstro"/></a> <a href="https://github.com/alexanderatallah"><img src="https://avatars.githubusercontent.com/u/1011391?v=4&s=48" width="48" height="48" alt="alexanderatallah" title="alexanderatallah"/></a>
<a href="https://github.com/testingabc321"><img src="https://avatars.githubusercontent.com/u/8577388?v=4&s=48" width="48" height="48" alt="testingabc321" title="testingabc321"/></a> <a href="https://github.com/humanwritten"><img src="https://avatars.githubusercontent.com/u/206531610?v=4&s=48" width="48" height="48" alt="humanwritten" title="humanwritten"/></a> <a href="https://github.com/aaronn"><img src="https://avatars.githubusercontent.com/u/1653630?v=4&s=48" width="48" height="48" alt="aaronn" title="aaronn"/></a> <a href="https://github.com/Alphonse-arianee"><img src="https://avatars.githubusercontent.com/u/254457365?v=4&s=48" width="48" height="48" alt="Alphonse-arianee" title="Alphonse-arianee"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/jiulingyun"><img src="https://avatars.githubusercontent.com/u/126459548?v=4&s=48" width="48" height="48" alt="jiulingyun" title="jiulingyun"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a>
<a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/odrobnik"><img src="https://avatars.githubusercontent.com/u/333270?v=4&s=48" width="48" height="48" alt="odrobnik" title="odrobnik"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/rhjoh"><img src="https://avatars.githubusercontent.com/u/105699450?v=4&s=48" width="48" height="48" alt="rhjoh" title="rhjoh"/></a> <a href="https://github.com/ronak-guliani"><img src="https://avatars.githubusercontent.com/u/23518228?v=4&s=48" width="48" height="48" alt="ronak-guliani" title="ronak-guliani"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a>
</p>

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.
@ -51,16 +52,22 @@ These are frequently reported but are typically closed with no code change:
- Prompt-injection-only chains without a boundary bypass (prompt injection is out of scope).
- Operator-intended local features (for example TUI local `!` shell) presented as remote injection.
- Reports that treat explicit operator-control surfaces (for example `canvas.eval`, browser evaluate/script execution, or direct `node.invoke` execution primitives) as vulnerabilities without demonstrating an auth/policy/sandbox boundary bypass. These capabilities are intentional when enabled and are trusted-operator features, not standalone security bugs.
- 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.
- Reports that depend on replacing or rewriting an already-approved executable path on a trusted host (same-path inode/content swap) without showing an untrusted path to perform that write.
- Reports that depend on pre-existing symlinked skill/workspace filesystem state (for example symlink chains involving `skills/*/SKILL.md`) without showing an untrusted path that can create/control that state.
- Missing HSTS findings on default local/loopback deployments.
- Slack webhook signature findings when HTTP mode already uses signing-secret verification.
- 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
@ -86,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.
@ -112,11 +120,16 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
- Deployments where mutually untrusted/adversarial operators share one gateway host and config (for example, reports expecting per-operator isolation for `sessions.list`, `sessions.preview`, `chat.history`, or similar control-plane reads)
- Prompt-injection-only attacks (without a policy/auth/sandbox boundary bypass)
- Reports that require write access to trusted local state (`~/.openclaw`, workspace files like `MEMORY.md` / `memory/*.md`)
- Reports where exploitability depends on attacker-controlled pre-existing symlink/hardlink filesystem state in trusted local paths (for example extraction/install target trees) unless a separate untrusted boundary bypass is shown that creates that state.
- Reports whose only claim is sandbox/workspace read expansion through trusted local skill/workspace symlink state (for example `skills/*/SKILL.md` symlink chains) unless a separate untrusted boundary bypass is shown that creates/controls that state.
- Reports whose only claim is post-approval executable identity drift on a trusted host via same-path file replacement/rewrite unless a separate untrusted boundary bypass is shown for that host write primitive.
- Reports where the only demonstrated impact is an already-authorized sender intentionally invoking a local-action command (for example `/export-session` writing to an absolute host path) without bypassing auth, sandbox, or another documented boundary
- Reports whose only claim is use of an explicit trusted-operator control surface (for example `canvas.eval`, browser evaluate/script execution, or direct `node.invoke` execution) without demonstrating an auth, policy, allowlist, approval, or sandbox bypass.
- Reports where the only claim is that a trusted-installed/enabled plugin can execute with gateway/host privileges (documented trust model behavior).
- 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.
@ -136,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.
@ -147,6 +161,8 @@ OpenClaw's security model is "personal assistant" (one trusted operator, potenti
- The model/agent is **not** a trusted principal. Assume prompt/content injection can manipulate behavior.
- Security boundaries come from host/config trust, auth, tool policy, sandboxing, and exec approvals.
- Prompt injection by itself is not a vulnerability report unless it crosses one of those boundaries.
- Hook/webhook-driven payloads should be treated as untrusted content; keep unsafe bypass flags disabled unless doing tightly scoped debugging (`hooks.gmail.allowUnsafeExternalContent`, `hooks.mappings[].allowUnsafeExternalContent`).
- Weak model tiers are generally easier to prompt-inject. For tool-enabled or hook-driven agents, prefer strong modern model tiers and strict tool policy (for example `tools.profile: "messaging"` or stricter), plus sandboxing where possible.
## Gateway and Node trust concept
@ -155,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.
@ -204,6 +221,14 @@ For threat model + hardening guidance (including `openclaw security audit --deep
- `tools.fs.workspaceOnly: true` (optional): restricts `read`/`write`/`edit`/`apply_patch` paths and native prompt image auto-load paths to the workspace directory.
- Avoid setting `tools.exec.applyPatch.workspaceOnly: false` unless you fully trust who can trigger tool execution.
### Sub-agent delegation hardening
- Keep `sessions_spawn` denied unless you explicitly need delegated runs.
- Keep `agents.list[].subagents.allowAgents` narrow, and only include agents with sandbox settings you trust.
- When delegation must stay sandboxed, call `sessions_spawn` with `sandbox: "require"` (default is `inherit`).
- `sandbox: "require"` rejects the spawn unless the target child runtime is sandboxed.
- This prevents a less-restricted session from delegating work into an unsandboxed child by mistake.
### Web Interface Safety
OpenClaw's web interface (Gateway Control UI + HTTP endpoints) is intended for **local use only**.

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,290 +3,246 @@
<channel>
<title>OpenClaw</title>
<item>
<title>2026.2.14</title>
<pubDate>Sun, 15 Feb 2026 04:24:34 +0100</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>202602140</sparkle:version>
<sparkle:shortVersionString>2026.2.14</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.2.14</h2>
<description><![CDATA[<h2>OpenClaw 2026.3.13</h2>
<h3>Changes</h3>
<ul>
<li>Telegram: add poll sending via <code>openclaw message poll</code> (duration seconds, silent delivery, anonymity controls). (#16209) Thanks @robbyczgw-cla.</li>
<li>Slack/Discord: add <code>dmPolicy</code> + <code>allowFrom</code> config aliases for DM access control; legacy <code>dm.policy</code> + <code>dm.allowFrom</code> keys remain supported and <code>openclaw doctor --fix</code> can migrate them.</li>
<li>Discord: allow exec approval prompts to target channels or both DM+channel via <code>channels.discord.execApprovals.target</code>. (#16051) Thanks @leonnardo.</li>
<li>Sandbox: add <code>sandbox.browser.binds</code> to configure browser-container bind mounts separately from exec containers. (#16230) Thanks @seheepeak.</li>
<li>Discord: add debug logging for message routing decisions to improve <code>--debug</code> tracing. (#16202) Thanks @jayleekr.</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>CLI/Plugins: ensure <code>openclaw message send</code> exits after successful delivery across plugin-backed channels so one-shot sends do not hang. (#16491) Thanks @yinghaosang.</li>
<li>CLI/Plugins: run registered plugin <code>gateway_stop</code> hooks before <code>openclaw message</code> exits (success and failure paths), so plugin-backed channels can clean up one-shot CLI resources. (#16580) Thanks @gumadeiras.</li>
<li>WhatsApp: honor per-account <code>dmPolicy</code> overrides (account-level settings now take precedence over channel defaults for inbound DMs). (#10082) Thanks @mcaxtr.</li>
<li>Telegram: when <code>channels.telegram.commands.native</code> is <code>false</code>, exclude plugin commands from <code>setMyCommands</code> menu registration while keeping plugin slash handlers callable. (#15132) Thanks @Glucksberg.</li>
<li>LINE: return 200 OK for Developers Console "Verify" requests (<code>{"events":[]}</code>) without <code>X-Line-Signature</code>, while still requiring signatures for real deliveries. (#16582) Thanks @arosstale.</li>
<li>Cron: deliver text-only output directly when <code>delivery.to</code> is set so cron recipients get full output instead of summaries. (#16360) Thanks @thewilloftheshadow.</li>
<li>Cron/Slack: preserve agent identity (name and icon) when cron jobs deliver outbound messages. (#16242) Thanks @robbyczgw-cla.</li>
<li>Media: accept <code>MEDIA:</code>-prefixed paths (lenient whitespace) when loading outbound media to prevent <code>ENOENT</code> for tool-returned local media paths. (#13107) Thanks @mcaxtr.</li>
<li>Agents: deliver tool result media (screenshots, images, audio) to channels regardless of verbose level. (#11735) Thanks @strelov1.</li>
<li>Agents/Image tool: allow workspace-local image paths by including the active workspace directory in local media allowlists, and trust sandbox-validated paths in image loaders to prevent false "not under an allowed directory" rejections. (#15541)</li>
<li>Agents/Image tool: propagate the effective workspace root into tool wiring so workspace-local image paths are accepted by default when running without an explicit <code>workspaceDir</code>. (#16722)</li>
<li>BlueBubbles: include sender identity in group chat envelopes and pass clean message text to the agent prompt, aligning with iMessage/Signal formatting. (#16210) Thanks @zerone0x.</li>
<li>CLI: fix lazy core command registration so top-level maintenance commands (<code>doctor</code>, <code>dashboard</code>, <code>reset</code>, <code>uninstall</code>) resolve correctly instead of exposing a non-functional <code>maintenance</code> placeholder command.</li>
<li>CLI/Dashboard: when <code>gateway.bind=lan</code>, generate localhost dashboard URLs to satisfy browser secure-context requirements while preserving non-LAN bind behavior. (#16434) Thanks @BinHPdev.</li>
<li>TUI/Gateway: resolve local gateway target URL from <code>gateway.bind</code> mode (tailnet/lan) instead of hardcoded localhost so <code>openclaw tui</code> connects when gateway is non-loopback. (#16299) Thanks @cortexuvula.</li>
<li>TUI: honor explicit <code>--session <key></code> in <code>openclaw tui</code> even when <code>session.scope</code> is <code>global</code>, so named sessions no longer collapse into shared global history. (#16575) Thanks @cinqu.</li>
<li>TUI: use available terminal width for session name display in searchable select lists. (#16238) Thanks @robbyczgw-cla.</li>
<li>TUI: refactor searchable select list description layout and add regression coverage for ANSI-highlight width bounds.</li>
<li>TUI: preserve in-flight streaming replies when a different run finalizes concurrently (avoid clearing active run or reloading history mid-stream). (#10704) Thanks @axschr73.</li>
<li>TUI: keep pre-tool streamed text visible when later tool-boundary deltas temporarily omit earlier text blocks. (#6958) Thanks @KrisKind75.</li>
<li>TUI: sanitize ANSI/control-heavy history text, redact binary-like lines, and split pathological long unbroken tokens before rendering to prevent startup crashes on binary attachment history. (#13007) Thanks @wilkinspoe.</li>
<li>TUI: harden render-time sanitizer for narrow terminals by chunking moderately long unbroken tokens and adding fast-path sanitization guards to reduce overhead on normal text. (#5355) Thanks @tingxueren.</li>
<li>TUI: render assistant body text in terminal default foreground (instead of fixed light ANSI color) so contrast remains readable on light themes such as Solarized Light. (#16750) Thanks @paymog.</li>
<li>TUI/Hooks: pass explicit reset reason (<code>new</code> vs <code>reset</code>) through <code>sessions.reset</code> and emit internal command hooks for gateway-triggered resets so <code>/new</code> hook workflows fire in TUI/webchat.</li>
<li>Cron: prevent <code>cron list</code>/<code>cron status</code> from silently skipping past-due recurring jobs by using maintenance recompute semantics. (#16156) Thanks @zerone0x.</li>
<li>Cron: repair missing/corrupt <code>nextRunAtMs</code> for the updated job without globally recomputing unrelated due jobs during <code>cron update</code>. (#15750)</li>
<li>Cron: skip missed-job replay on startup for jobs interrupted mid-run (stale <code>runningAtMs</code> markers), preventing restart loops for self-restarting jobs such as update tasks. (#16694) Thanks @sbmilburn.</li>
<li>Discord: prefer gateway guild id when logging inbound messages so cached-miss guilds do not appear as <code>guild=dm</code>. Thanks @thewilloftheshadow.</li>
<li>Discord: treat empty per-guild <code>channels: {}</code> config maps as no channel allowlist (not deny-all), so <code>groupPolicy: "open"</code> guilds without explicit channel entries continue to receive messages. (#16714) Thanks @xqliu.</li>
<li>Models/CLI: guard <code>models status</code> string trimming paths to prevent crashes from malformed non-string config values. (#16395) Thanks @BinHPdev.</li>
<li>Gateway/Subagents: preserve queued announce items and summary state on delivery errors, retry failed announce drains, and avoid dropping unsent announcements on timeout/failure. (#16729) Thanks @Clawdette-Workspace.</li>
<li>Gateway/Sessions: abort active embedded runs and clear queued session work before <code>sessions.reset</code>, returning unavailable if the run does not stop in time. (#16576) Thanks @Grynn.</li>
<li>Sessions/Agents: harden transcript path resolution for mismatched agent context by preserving explicit store roots and adding safe absolute-path fallback to the correct agent sessions directory. (#16288) Thanks @robbyczgw-cla.</li>
<li>Agents: add a safety timeout around embedded <code>session.compact()</code> to ensure stalled compaction runs settle and release blocked session lanes. (#16331) Thanks @BinHPdev.</li>
<li>Agents: keep unresolved mutating tool failures visible until the same action retry succeeds, scope mutation-error surfacing to mutating calls (including <code>session_status</code> model changes), and dedupe duplicate failure warnings in outbound replies. (#16131) Thanks @Swader.</li>
<li>Agents/Process/Bootstrap: preserve unbounded <code>process log</code> offset-only pagination (default tail applies only when both <code>offset</code> and <code>limit</code> are omitted) and enforce strict <code>bootstrapTotalMaxChars</code> budgeting across injected bootstrap content (including markers), skipping additional injection when remaining budget is too small. (#16539) Thanks @CharlieGreenman.</li>
<li>Agents/Workspace: persist bootstrap onboarding state so partially initialized workspaces recover missing <code>BOOTSTRAP.md</code> once, while completed onboarding keeps BOOTSTRAP deleted even if runtime files are later recreated. Thanks @gumadeiras.</li>
<li>Agents/Workspace: create <code>BOOTSTRAP.md</code> when core workspace files are seeded in partially initialized workspaces, while keeping BOOTSTRAP one-shot after onboarding deletion. (#16457) Thanks @robbyczgw-cla.</li>
<li>Agents: classify external timeout aborts during compaction the same as internal timeouts, preventing unnecessary auth-profile rotation and preserving compaction-timeout snapshot fallback behavior. (#9855) Thanks @mverrilli.</li>
<li>Agents: treat empty-stream provider failures (<code>request ended without sending any chunks</code>) as timeout-class failover signals, enabling auth-profile rotation/fallback and showing a friendly timeout message instead of raw provider errors. (#10210) Thanks @zenchantlive.</li>
<li>Agents: treat <code>read</code> tool <code>file_path</code> arguments as valid in tool-start diagnostics to avoid false “read tool called without path” warnings when alias parameters are used. (#16717) Thanks @Stache73.</li>
<li>Ollama/Agents: avoid forcing <code><final></code> tag enforcement for Ollama models, which could suppress all output as <code>(no output)</code>. (#16191) Thanks @Glucksberg.</li>
<li>Plugins: suppress false duplicate plugin id warnings when the same extension is discovered via multiple paths (config/workspace/global vs bundled), while still warning on genuine duplicates. (#16222) Thanks @shadril238.</li>
<li>Skills: watch <code>SKILL.md</code> only when refreshing skills snapshot to avoid file-descriptor exhaustion in large data trees. (#11325) Thanks @household-bard.</li>
<li>Memory/QMD: make <code>memory status</code> read-only by skipping QMD boot update/embed side effects for status-only manager checks.</li>
<li>Memory/QMD: keep original QMD failures when builtin fallback initialization fails (for example missing embedding API keys), instead of replacing them with fallback init errors.</li>
<li>Memory/Builtin: keep <code>memory status</code> dirty reporting stable across invocations by deriving status-only manager dirty state from persisted index metadata instead of process-start defaults. (#10863) Thanks @BarryYangi.</li>
<li>Memory/QMD: cap QMD command output buffering to prevent memory exhaustion from pathological <code>qmd</code> command output.</li>
<li>Memory/QMD: parse qmd scope keys once per request to avoid repeated parsing in scope checks.</li>
<li>Memory/QMD: query QMD index using exact docid matches before falling back to prefix lookup for better recall correctness and index efficiency.</li>
<li>Memory/QMD: pass result limits to <code>search</code>/<code>vsearch</code> commands so QMD can cap results earlier.</li>
<li>Memory/QMD: avoid reading full markdown files when a <code>from/lines</code> window is requested in QMD reads.</li>
<li>Memory/QMD: skip rewriting unchanged session export markdown files during sync to reduce disk churn.</li>
<li>Memory/QMD: make QMD result JSON parsing resilient to noisy command output by extracting the first JSON array from noisy <code>stdout</code>.</li>
<li>Memory/QMD: treat prefixed <code>no results found</code> marker output as an empty result set in qmd JSON parsing. (#11302) Thanks @blazerui.</li>
<li>Memory/QMD: avoid multi-collection <code>query</code> ranking corruption by running one <code>qmd query -c <collection></code> per managed collection and merging by best score (also used for <code>search</code>/<code>vsearch</code> fallback-to-query). (#16740) Thanks @volarian-vai.</li>
<li>Memory/QMD: detect null-byte <code>ENOTDIR</code> update failures, rebuild managed collections once, and retry update to self-heal corrupted collection metadata. (#12919) Thanks @jorgejhms.</li>
<li>Memory/QMD/Security: add <code>rawKeyPrefix</code> support for QMD scope rules and preserve legacy <code>keyPrefix: "agent:..."</code> matching, preventing scoped deny bypass when operators match agent-prefixed session keys.</li>
<li>Memory/Builtin: narrow memory watcher targets to markdown globs and ignore dependency/venv directories to reduce file-descriptor pressure during memory sync startup. (#11721) Thanks @rex05ai.</li>
<li>Security/Memory-LanceDB: treat recalled memories as untrusted context (escape injected memory text + explicit non-instruction framing), skip likely prompt-injection payloads during auto-capture, and restrict auto-capture to user messages to reduce memory-poisoning risk. (#12524) Thanks @davidschmid24.</li>
<li>Security/Memory-LanceDB: require explicit <code>autoCapture: true</code> opt-in (default is now disabled) to prevent automatic PII capture unless operators intentionally enable it. (#12552) Thanks @fr33d3m0n.</li>
<li>Diagnostics/Memory: prune stale diagnostic session state entries and cap tracked session states to prevent unbounded in-memory growth on long-running gateways. (#5136) Thanks @coygeek and @vignesh07.</li>
<li>Gateway/Memory: clean up <code>agentRunSeq</code> tracking on run completion/abort and enforce maintenance-time cap pruning to prevent unbounded sequence-map growth over long uptimes. (#6036) Thanks @coygeek and @vignesh07.</li>
<li>Auto-reply/Memory: bound <code>ABORT_MEMORY</code> growth by evicting oldest entries and deleting reset (<code>false</code>) flags so abort state tracking cannot grow unbounded over long uptimes. (#6629) Thanks @coygeek and @vignesh07.</li>
<li>Slack/Memory: bound thread-starter cache growth with TTL + max-size pruning to prevent long-running Slack gateways from accumulating unbounded thread cache state. (#5258) Thanks @coygeek and @vignesh07.</li>
<li>Outbound/Memory: bound directory cache growth with max-size eviction and proactive TTL pruning to prevent long-running gateways from accumulating unbounded directory entries. (#5140) Thanks @coygeek and @vignesh07.</li>
<li>Skills/Memory: remove disconnected nodes from remote-skills cache to prevent stale node metadata from accumulating over long uptimes. (#6760) Thanks @coygeek.</li>
<li>Sandbox/Tools: make sandbox file tools bind-mount aware (including absolute container paths) and enforce read-only bind semantics for writes. (#16379) Thanks @tasaankaeris.</li>
<li>Media/Security: allow local media reads from OpenClaw state <code>workspace/</code> and <code>sandboxes/</code> roots by default so generated workspace media can be delivered without unsafe global path bypasses. (#15541) Thanks @lanceji.</li>
<li>Media/Security: harden local media allowlist bypasses by requiring an explicit <code>readFile</code> override when callers mark paths as validated, and reject filesystem-root <code>localRoots</code> entries. (#16739)</li>
<li>Discord/Security: harden voice message media loading (SSRF + allowed-local-root checks) so tool-supplied paths/URLs cannot be used to probe internal URLs or read arbitrary local files.</li>
<li>Security/BlueBubbles: require explicit <code>mediaLocalRoots</code> allowlists for local outbound media path reads to prevent local file disclosure. (#16322) Thanks @mbelinky.</li>
<li>Security/BlueBubbles: reject ambiguous shared-path webhook routing when multiple webhook targets match the same guid/password.</li>
<li>Security/BlueBubbles: harden BlueBubbles webhook auth behind reverse proxies by only accepting passwordless webhooks for direct localhost loopback requests (forwarded/proxied requests now require a password). Thanks @simecek.</li>
<li>Feishu/Security: harden media URL fetching against SSRF and local file disclosure. (#16285) Thanks @mbelinky.</li>
<li>Security/Zalo: reject ambiguous shared-path webhook routing when multiple webhook targets match the same secret.</li>
<li>Security/Nostr: require loopback source and block cross-origin profile mutation/import attempts. Thanks @vincentkoc.</li>
<li>Security/Signal: harden signal-cli archive extraction during install to prevent path traversal outside the install root.</li>
<li>Security/Hooks: restrict hook transform modules to <code>~/.openclaw/hooks/transforms</code> (prevents path traversal/escape module loads via config). Config note: <code>hooks.transformsDir</code> must now be within that directory. Thanks @akhmittra.</li>
<li>Security/Hooks: ignore hook package manifest entries that point outside the package directory (prevents out-of-tree handler loads during hook discovery).</li>
<li>Security/Archive: enforce archive extraction entry/size limits to prevent resource exhaustion from high-expansion ZIP/TAR archives. Thanks @vincentkoc.</li>
<li>Security/Media: reject oversized base64-backed input media before decoding to avoid large allocations. Thanks @vincentkoc.</li>
<li>Security/Media: stream and bound URL-backed input media fetches to prevent memory exhaustion from oversized responses. Thanks @vincentkoc.</li>
<li>Security/Skills: harden archive extraction for download-installed skills to prevent path traversal outside the target directory. Thanks @markmusson.</li>
<li>Security/Slack: compute command authorization for DM slash commands even when <code>dmPolicy=open</code>, preventing unauthorized users from running privileged commands via DM. Thanks @christos-eth.</li>
<li>Security/iMessage: keep DM pairing-store identities out of group allowlist authorization (prevents cross-context command authorization). Thanks @vincentkoc.</li>
<li>Security/Google Chat: deprecate <code>users/<email></code> allowlists (treat <code>users/...</code> as immutable user id only); keep raw email allowlists for usability. Thanks @vincentkoc.</li>
<li>Security/Google Chat: reject ambiguous shared-path webhook routing when multiple webhook targets verify successfully (prevents cross-account policy-context misrouting). Thanks @vincentkoc.</li>
<li>Telegram/Security: require numeric Telegram sender IDs for allowlist authorization (reject <code>@username</code> principals), auto-resolve <code>@username</code> to IDs in <code>openclaw doctor --fix</code> (when possible), and warn in <code>openclaw security audit</code> when legacy configs contain usernames. Thanks @vincentkoc.</li>
<li>Telegram/Security: reject Telegram webhook startup when <code>webhookSecret</code> is missing or empty (prevents unauthenticated webhook request forgery). Thanks @yueyueL.</li>
<li>Security/Windows: avoid shell invocation when spawning child processes to prevent cmd.exe metacharacter injection via untrusted CLI arguments (e.g. agent prompt text).</li>
<li>Telegram: set webhook callback timeout handling to <code>onTimeout: "return"</code> (10s) so long-running update processing no longer emits webhook 500s and retry storms. (#16763) Thanks @chansearrington.</li>
<li>Signal: preserve case-sensitive <code>group:</code> target IDs during normalization so mixed-case group IDs no longer fail with <code>Group not found</code>. (#16748) Thanks @repfigit.</li>
<li>Feishu/Security: harden media URL fetching against SSRF and local file disclosure. (#16285) Thanks @mbelinky.</li>
<li>Security/Agents: scope CLI process cleanup to owned child PIDs to avoid killing unrelated processes on shared hosts. Thanks @aether-ai-agent.</li>
<li>Security/Agents: enforce workspace-root path bounds for <code>apply_patch</code> in non-sandbox mode to block traversal and symlink escape writes. Thanks @p80n-sec.</li>
<li>Security/Agents: enforce symlink-escape checks for <code>apply_patch</code> delete hunks under <code>workspaceOnly</code>, while still allowing deleting the symlink itself. Thanks @p80n-sec.</li>
<li>Security/Agents (macOS): prevent shell injection when writing Claude CLI keychain credentials. (#15924) Thanks @aether-ai-agent.</li>
<li>macOS: hard-limit unkeyed <code>openclaw://agent</code> deep links and ignore <code>deliver</code> / <code>to</code> / <code>channel</code> unless a valid unattended key is provided. Thanks @Cillian-Collins.</li>
<li>Scripts/Security: validate GitHub logins and avoid shell invocation in <code>scripts/update-clawtributors.ts</code> to prevent command injection via malicious commit records. Thanks @scanleale.</li>
<li>Security: fix Chutes manual OAuth login state validation by requiring the full redirect URL (reject code-only pastes) (thanks @aether-ai-agent).</li>
<li>Security/Gateway: harden tool-supplied <code>gatewayUrl</code> overrides by restricting them to loopback or the configured <code>gateway.remote.url</code>. Thanks @p80n-sec.</li>
<li>Security/Gateway: block <code>system.execApprovals.*</code> via <code>node.invoke</code> (use <code>exec.approvals.node.*</code> instead). Thanks @christos-eth.</li>
<li>Security/Gateway: reject oversized base64 chat attachments before decoding to avoid large allocations. Thanks @vincentkoc.</li>
<li>Security/Gateway: stop returning raw resolved config values in <code>skills.status</code> requirement checks (prevents operator.read clients from reading secrets). Thanks @simecek.</li>
<li>Security/Net: fix SSRF guard bypass via full-form IPv4-mapped IPv6 literals (blocks loopback/private/metadata access). Thanks @yueyueL.</li>
<li>Security/Browser: harden browser control file upload + download helpers to prevent path traversal / local file disclosure. Thanks @1seal.</li>
<li>Security/Browser: block cross-origin mutating requests to loopback browser control routes (CSRF hardening). Thanks @vincentkoc.</li>
<li>Security/Node Host: enforce <code>system.run</code> rawCommand/argv consistency to prevent allowlist/approval bypass. Thanks @christos-eth.</li>
<li>Security/Exec approvals: prevent safeBins allowlist bypass via shell expansion (host exec allowlist mode only; not enabled by default). Thanks @christos-eth.</li>
<li>Security/Exec: harden PATH handling by disabling project-local <code>node_modules/.bin</code> bootstrapping by default, disallowing node-host <code>PATH</code> overrides, and spawning ACP servers via the current executable by default. Thanks @akhmittra.</li>
<li>Security/Tlon: harden Urbit URL fetching against SSRF by blocking private/internal hosts by default (opt-in: <code>channels.tlon.allowPrivateNetwork</code>). Thanks @p80n-sec.</li>
<li>Security/Voice Call (Telnyx): require webhook signature verification when receiving inbound events; configs without <code>telnyx.publicKey</code> are now rejected unless <code>skipSignatureVerification</code> is enabled. Thanks @p80n-sec.</li>
<li>Security/Voice Call: require valid Twilio webhook signatures even when ngrok free tier loopback compatibility mode is enabled. Thanks @p80n-sec.</li>
<li>Security/Discovery: stop treating Bonjour TXT records as authoritative routing (prefer resolved service endpoints) and prevent discovery from overriding stored TLS pins; autoconnect now requires a previously trusted gateway. Thanks @simecek.</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.2.14/OpenClaw-2026.2.14.zip" length="22914034" type="application/octet-stream" sparkle:edSignature="lR3nuq46/akMIN8RFDpMkTE0VOVoDVG53Xts589LryMGEtUvJxRQDtHBXfx7ZvToTq6CFKG+L5Kq/4rUspMoAQ=="/>
<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.2.15</title>
<pubDate>Mon, 16 Feb 2026 05:04:34 +0100</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>202602150</sparkle:version>
<sparkle:shortVersionString>2026.2.15</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.2.15</h2>
<description><![CDATA[<h2>OpenClaw 2026.3.12</h2>
<h3>Changes</h3>
<ul>
<li>Discord: unlock rich interactive agent prompts with Components v2 (buttons, selects, modals, and attachment-backed file blocks) so for native interaction through Discord. Thanks @thewilloftheshadow.</li>
<li>Discord: components v2 UI + embeds passthrough + exec approval UX refinements (CV2 containers, button layout, Discord-forwarding skip). Thanks @thewilloftheshadow.</li>
<li>Plugins: expose <code>llm_input</code> and <code>llm_output</code> hook payloads so extensions can observe prompt/input context and model output usage details. (#16724) Thanks @SecondThread.</li>
<li>Subagents: nested sub-agents (sub-sub-agents) with configurable depth. Set <code>agents.defaults.subagents.maxSpawnDepth: 2</code> to allow sub-agents to spawn their own children. Includes <code>maxChildrenPerAgent</code> limit (default 5), depth-aware tool policy, and proper announce chain routing. (#14447) Thanks @tyler6204.</li>
<li>Slack/Discord/Telegram: add per-channel ack reaction overrides (account/channel-level) to support platform-specific emoji formats. (#17092) Thanks @zerone0x.</li>
<li>Cron/Gateway: add finished-run webhook delivery toggle (<code>notify</code>) and dedicated webhook auth token support (<code>cron.webhookToken</code>) for outbound cron webhook posts. (#14535) Thanks @advaitpaliwal.</li>
<li>Channels: deduplicate probe/token resolution base types across core + extensions while preserving per-channel error typing. (#16986) Thanks @iyoda and @thewilloftheshadow.</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>Security: replace deprecated SHA-1 sandbox configuration hashing with SHA-256 for deterministic sandbox cache identity and recreation checks. Thanks @kexinoh.</li>
<li>Security/Logging: redact Telegram bot tokens from error messages and uncaught stack traces to prevent accidental secret leakage into logs. Thanks @aether-ai-agent.</li>
<li>Sandbox/Security: block dangerous sandbox Docker config (bind mounts, host networking, unconfined seccomp/apparmor) to prevent container escape via config injection. Thanks @aether-ai-agent.</li>
<li>Sandbox: preserve array order in config hashing so order-sensitive Docker/browser settings trigger container recreation correctly. Thanks @kexinoh.</li>
<li>Gateway/Security: redact sensitive session/path details from <code>status</code> responses for non-admin clients; full details remain available to <code>operator.admin</code>. (#8590) Thanks @fr33d3m0n.</li>
<li>Gateway/Control UI: preserve requested operator scopes for Control UI bypass modes (<code>allowInsecureAuth</code> / <code>dangerouslyDisableDeviceAuth</code>) when device identity is unavailable, preventing false <code>missing scope</code> failures on authenticated LAN/HTTP operator sessions. (#17682) Thanks @leafbird.</li>
<li>LINE/Security: fail closed on webhook startup when channel token or channel secret is missing, and treat LINE accounts as configured only when both are present. (#17587) Thanks @davidahmann.</li>
<li>Skills/Security: restrict <code>download</code> installer <code>targetDir</code> to the per-skill tools directory to prevent arbitrary file writes. Thanks @Adam55A-code.</li>
<li>Skills/Linux: harden go installer fallback on apt-based systems by handling root/no-sudo environments safely, doing best-effort apt index refresh, and returning actionable errors instead of failing with spawn errors. (#17687) Thanks @mcrolly.</li>
<li>Web Fetch/Security: cap downloaded response body size before HTML parsing to prevent memory exhaustion from oversized or deeply nested pages. Thanks @xuemian168.</li>
<li>Config/Gateway: make sensitive-key whitelist suffix matching case-insensitive while preserving <code>passwordFile</code> path exemptions, preventing accidental redaction of non-secret config values like <code>maxTokens</code> and IRC password-file paths. (#16042) Thanks @akramcodez.</li>
<li>Dev tooling: harden git <code>pre-commit</code> hook against option injection from malicious filenames (for example <code>--force</code>), preventing accidental staging of ignored files. Thanks @mrthankyou.</li>
<li>Gateway/Agent: reject malformed <code>agent:</code>-prefixed session keys (for example, <code>agent:main</code>) in <code>agent</code> and <code>agent.identity.get</code> instead of silently resolving them to the default agent, preventing accidental cross-session routing. (#15707) Thanks @rodrigouroz.</li>
<li>Gateway/Chat: harden <code>chat.send</code> inbound message handling by rejecting null bytes, stripping unsafe control characters, and normalizing Unicode to NFC before dispatch. (#8593) Thanks @fr33d3m0n.</li>
<li>Gateway/Send: return an actionable error when <code>send</code> targets internal-only <code>webchat</code>, guiding callers to use <code>chat.send</code> or a deliverable channel. (#15703) Thanks @rodrigouroz.</li>
<li>Control UI: prevent stored XSS via assistant name/avatar by removing inline script injection, serving bootstrap config as JSON, and enforcing <code>script-src 'self'</code>. Thanks @Adam55A-code.</li>
<li>Agents/Security: sanitize workspace paths before embedding into LLM prompts (strip Unicode control/format chars) to prevent instruction injection via malicious directory names. Thanks @aether-ai-agent.</li>
<li>Agents/Sandbox: clarify system prompt path guidance so sandbox <code>bash/exec</code> uses container paths (for example <code>/workspace</code>) while file tools keep host-bridge mapping, avoiding first-attempt path misses from host-only absolute paths in sandbox command execution. (#17693) Thanks @app/juniordevbot.</li>
<li>Agents/Context: apply configured model <code>contextWindow</code> overrides after provider discovery so <code>lookupContextTokens()</code> honors operator config values (including discovery-failure paths). (#17404) Thanks @michaelbship and @vignesh07.</li>
<li>Agents/Context: derive <code>lookupContextTokens()</code> from auth-available model metadata and keep the smallest discovered context window for duplicate model ids, preventing cross-provider cache collisions from overestimating session context limits. (#17586) Thanks @githabideri and @vignesh07.</li>
<li>Agents/OpenAI: force <code>store=true</code> for direct OpenAI Responses/Codex runs to preserve multi-turn server-side conversation state, while leaving proxy/non-OpenAI endpoints unchanged. (#16803) Thanks @mark9232 and @vignesh07.</li>
<li>Memory/FTS: make <code>buildFtsQuery</code> Unicode-aware so non-ASCII queries (including CJK) produce keyword tokens instead of falling back to vector-only search. (#17672) Thanks @KinGP5471.</li>
<li>Auto-reply/Compaction: resolve <code>memory/YYYY-MM-DD.md</code> placeholders with timezone-aware runtime dates and append a <code>Current time:</code> line to memory-flush turns, preventing wrong-year memory filenames without making the system prompt time-variant. (#17603, #17633) Thanks @nicholaspapadam-wq and @vignesh07.</li>
<li>Agents: return an explicit timeout error reply when an embedded run times out before producing any payloads, preventing silent dropped turns during slow cache-refresh transitions. (#16659) Thanks @liaosvcaf and @vignesh07.</li>
<li>Group chats: always inject group chat context (name, participants, reply guidance) into the system prompt on every turn, not just the first. Prevents the model from losing awareness of which group it's in and incorrectly using the message tool to send to the same group. (#14447) Thanks @tyler6204.</li>
<li>Browser/Agents: when browser control service is unavailable, return explicit non-retry guidance (instead of "try again") so models do not loop on repeated browser tool calls until timeout. (#17673) Thanks @austenstone.</li>
<li>Subagents: use child-run-based deterministic announce idempotency keys across direct and queued delivery paths (with legacy queued-item fallback) to prevent duplicate announce retries without collapsing distinct same-millisecond announces. (#17150) Thanks @widingmarcus-cyber.</li>
<li>Subagents/Models: preserve <code>agents.defaults.model.fallbacks</code> when subagent sessions carry a model override, so subagent runs fail over to configured fallback models instead of retrying only the overridden primary model.</li>
<li>Telegram: omit <code>message_thread_id</code> for DM sends/draft previews and keep forum-topic handling (<code>id=1</code> general omitted, non-general kept), preventing DM failures with <code>400 Bad Request: message thread not found</code>. (#10942) Thanks @garnetlyx.</li>
<li>Telegram: replace inbound <code><media:audio></code> placeholder with successful preflight voice transcript in message body context, preventing placeholder-only prompt bodies for mention-gated voice messages. (#16789) Thanks @Limitless2023.</li>
<li>Telegram: retry inbound media <code>getFile</code> calls (3 attempts with backoff) and gracefully fall back to placeholder-only processing when retries fail, preventing dropped voice/media messages on transient Telegram network errors. (#16154) Thanks @yinghaosang.</li>
<li>Telegram: finalize streaming preview replies in place instead of sending a second final message, preventing duplicate Telegram assistant outputs at stream completion. (#17218) Thanks @obviyus.</li>
<li>Discord: preserve channel session continuity when runtime payloads omit <code>message.channelId</code> by falling back to event/raw <code>channel_id</code> values for routing/session keys, so same-channel messages keep history across turns/restarts. Also align diagnostics so active Discord runs no longer appear as <code>sessionKey=unknown</code>. (#17622) Thanks @shakkernerd.</li>
<li>Discord: dedupe native skill commands by skill name in multi-agent setups to prevent duplicated slash commands with <code>_2</code> suffixes. (#17365) Thanks @seewhyme.</li>
<li>Discord: ensure role allowlist matching uses raw role IDs for message routing authorization. Thanks @xinhuagu.</li>
<li>Web UI/Agents: hide <code>BOOTSTRAP.md</code> in the Agents Files list after onboarding is completed, avoiding confusing missing-file warnings for completed workspaces. (#17491) Thanks @gumadeiras.</li>
<li>Auto-reply/WhatsApp/TUI/Web: when a final assistant message is <code>NO_REPLY</code> and a messaging tool send succeeded, mirror the delivered messaging-tool text into session-visible assistant output so TUI/Web no longer show <code>NO_REPLY</code> placeholders. (#7010) Thanks @Morrowind-Xie.</li>
<li>Cron: infer <code>payload.kind="agentTurn"</code> for model-only <code>cron.update</code> payload patches, so partial agent-turn updates do not fail validation when <code>kind</code> is omitted. (#15664) Thanks @rodrigouroz.</li>
<li>TUI: make searchable-select filtering and highlight rendering ANSI-aware so queries ignore hidden escape codes and no longer corrupt ANSI styling sequences during match highlighting. (#4519) Thanks @bee4come.</li>
<li>TUI/Windows: coalesce rapid single-line submit bursts in Git Bash into one multiline message as a fallback when bracketed paste is unavailable, preventing pasted multiline text from being split into multiple sends. (#4986) Thanks @adamkane.</li>
<li>TUI: suppress false <code>(no output)</code> placeholders for non-local empty final events during concurrent runs, preventing external-channel replies from showing empty assistant bubbles while a local run is still streaming. (#5782) Thanks @LagWizard and @vignesh07.</li>
<li>TUI: preserve copy-sensitive long tokens (URLs/paths/file-like identifiers) during wrapping and overflow sanitization so wrapped output no longer inserts spaces that corrupt copy/paste values. (#17515, #17466, #17505) Thanks @abe238, @trevorpan, and @JasonCry.</li>
<li>CLI/Build: make legacy daemon CLI compatibility shim generation tolerant of minimal tsdown daemon export sets, while preserving restart/register compatibility aliases and surfacing explicit errors for unavailable legacy daemon commands. Thanks @vignesh07.</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.2.15/OpenClaw-2026.2.15.zip" length="22896513" type="application/octet-stream" sparkle:edSignature="MLGsd2NeHXFRH1Or0bFQnAjqfuuJDuhl1mvKFIqTQcRvwbeyvOyyLXrqSbmaOgJR3wBQBKLs6jYQ9dQ/3R8RCg=="/>
<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.2.25</title>
<pubDate>Thu, 26 Feb 2026 05:14:17 +0100</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>14883</sparkle:version>
<sparkle:shortVersionString>2026.2.25</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.2.25</h2>
<description><![CDATA[<h2>OpenClaw 2026.3.8-beta.1</h2>
<h3>Changes</h3>
<ul>
<li>Android/Chat: improve streaming delivery handling and markdown rendering quality in the native Android chat UI, including better GitHub-flavored markdown behavior. (#26079) Thanks @obviyus.</li>
<li>Android/Startup perf: defer foreground-service startup, move WebView debugging init out of critical startup, and add startup macrobenchmark + low-noise perf CLI scripts for deterministic cold-start tracking. (#26659) Thanks @obviyus.</li>
<li>UI/Chat compose: add mobile stacked layout for compose action buttons on small screens to improve send/session controls usability. (#11167) Thanks @junyiz.</li>
<li>Heartbeat/Config: replace heartbeat DM toggle with <code>agents.defaults.heartbeat.directPolicy</code> (<code>allow</code> | <code>block</code>; also supported per-agent via <code>agents.list[].heartbeat.directPolicy</code>) for clearer delivery semantics.</li>
<li>Onboarding/Security: clarify onboarding security notices that OpenClaw is personal-by-default (single trusted operator boundary) and shared/multi-user setups require explicit lock-down/hardening.</li>
<li>Branding/Docs + Apple surfaces: replace remaining <code>bot.molt</code> launchd label, bundle-id, logging subsystem, and command examples with <code>ai.openclaw</code> across docs, iOS app surfaces, helper scripts, and CLI test fixtures.</li>
<li>Agents/Config: remind agents to call <code>config.schema</code> before config edits or config-field questions to avoid guessing. Thanks @thewilloftheshadow.</li>
<li>Dependencies: update workspace dependency pins and lockfile (Bedrock SDK <code>3.998.0</code>, <code>@mariozechner/pi-*</code> <code>0.55.1</code>, TypeScript native preview <code>7.0.0-dev.20260225.1</code>) while keeping <code>@buape/carbon</code> pinned.</li>
</ul>
<h3>Breaking</h3>
<ul>
<li><strong>BREAKING:</strong> Heartbeat direct/DM delivery default is now <code>allow</code> again. To keep DM-blocked behavior from <code>2026.2.24</code>, set <code>agents.defaults.heartbeat.directPolicy: "block"</code> (or per-agent override).</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>Agents/Subagents delivery: refactor subagent completion announce dispatch into an explicit queue/direct/fallback state machine, recover outbound channel-plugin resolution in cold/stale plugin-registry states across announce/message/gateway send paths, finalize cleanup bookkeeping when announce flow rejects, and treat Telegram sends without <code>message_id</code> as delivery failures (instead of false-success <code>"unknown"</code> IDs). (#26867, #25961, #26803, #25069, #26741) Thanks @SmithLabsLLC and @docaohieu2808.</li>
<li>Telegram/Webhook: pre-initialize webhook bots, switch webhook processing to callback-mode JSON handling, and preserve full near-limit payload reads under delayed handlers to prevent webhook request hangs and dropped updates. (#26156)</li>
<li>Slack/Session threads: prevent oversized parent-session inheritance from silently bricking new thread sessions, surface embedded context-overflow empty-result failures to users, and add configurable <code>session.parentForkMaxTokens</code> (default <code>100000</code>, <code>0</code> disables). (#26912) Thanks @markshields-tl.</li>
<li>Cron/Message multi-account routing: honor explicit <code>delivery.accountId</code> for isolated cron delivery resolution, and when <code>message.send</code> omits <code>accountId</code>, fall back to the sending agent's bound channel account instead of defaulting to the global account. (#27015, #26975) Thanks @lbo728 and @stakeswky.</li>
<li>Gateway/Message media roots: thread <code>agentId</code> through gateway <code>send</code> RPC and prefer explicit <code>agentId</code> over session/default resolution so non-default agent workspace media sends no longer fail with <code>LocalMediaAccessError</code>; added regression coverage for agent precedence and blank-agent fallback. (#23249) Thanks @Sid-Qin.</li>
<li>Followups/Routing: when explicit origin routing fails, allow same-channel fallback dispatch (while still blocking cross-channel fallback) so followup replies do not get dropped on transient origin-adapter failures. (#26109) Thanks @Sid-Qin.</li>
<li>Cron/Announce duplicate guard: track attempted announce/direct delivery separately from confirmed <code>delivered</code>, and suppress fallback main-session cron summaries when delivery was already attempted to avoid duplicate end-user sends in uncertain-ack paths. (#27018)</li>
<li>LINE/Lifecycle: keep LINE <code>startAccount</code> pending until abort so webhook startup is no longer misread as immediate channel exit, preventing restart-loop storms on LINE provider boot. (#26528) Thanks @Sid-Qin.</li>
<li>Discord/Gateway: capture and drain startup-time gateway <code>error</code> events before lifecycle listeners attach so early <code>Fatal Gateway error: 4014</code> closes surface as actionable intent guidance instead of uncaught gateway crashes. (#23832) Thanks @theotarr.</li>
<li>Discord/Inbound text: preserve embed <code>title</code> + <code>description</code> fallback text in message and forwarded snapshot parsing so embed titles are not silently dropped from agent input. (#26946) Thanks @stakeswky.</li>
<li>Slack/Inbound media fallback: deliver file-only messages even when Slack media downloads fail by adding a filename placeholder fallback, capping fallback names to the shared media-file limit, and normalizing empty filenames to <code>file</code> so attachment-only messages are not silently dropped. (#25181) Thanks @justinhuangcode.</li>
<li>Telegram/Preview cleanup: keep finalized text previews when a later assistant message is media-only (for example mixed text plus voice turns) by skipping finalized preview archival at assistant-message boundaries, preventing cleanup from deleting already-visible final text messages. (#27042)</li>
<li>Telegram/Markdown spoilers: keep valid <code>||spoiler||</code> pairs while leaving unmatched trailing <code>||</code> delimiters as literal text, avoiding false all-or-nothing spoiler suppression. (#26105) Thanks @Sid-Qin.</li>
<li>Slack/Allowlist channels: match channel IDs case-insensitively during channel allowlist resolution so lowercase config keys (for example <code>c0abc12345</code>) correctly match Slack runtime IDs (<code>C0ABC12345</code>) under <code>groupPolicy: "allowlist"</code>, preventing silent channel-event drops. (#26878) Thanks @lbo728.</li>
<li>Discord/Typing indicator: prevent stuck typing indicators by sealing channel typing keepalive callbacks after idle/cleanup and ensuring Discord dispatch always marks typing idle even if preview-stream cleanup fails. (#26295) Thanks @ngutman.</li>
<li>Channels/Typing indicator: guard typing keepalive start callbacks after idle/cleanup close so post-close ticks cannot re-trigger stale typing indicators. (#26325) Thanks @win4r.</li>
<li>Followups/Typing indicator: ensure followup turns mark dispatch idle on every exit path (including <code>NO_REPLY</code>, empty payloads, and agent errors) so typing keepalive cleanup always runs and channel typing indicators do not get stuck after queued/silent followups. (#26881) Thanks @codexGW.</li>
<li>Voice-call/TTS tools: hide the <code>tts</code> tool when the message provider is <code>voice</code>, preventing voice-call runs from selecting self-playback TTS and falling into silent no-output loops. (#27025)</li>
<li>Agents/Tools: normalize non-standard plugin tool results that omit <code>content</code> so embedded runs no longer crash with <code>Cannot read properties of undefined (reading 'filter')</code> after tool completion (including <code>tesseramemo_query</code>). (#27007)</li>
<li>Cron/Model overrides: when isolated <code>payload.model</code> is no longer allowlisted, fall back to default model selection instead of failing the job, while still returning explicit errors for invalid model strings. (#26717) Thanks @Youyou972.</li>
<li>Agents/Model fallback: keep explicit text + image fallback chains reachable even when <code>agents.defaults.models</code> allowlists are present, prefer explicit run <code>agentId</code> over session-key parsing for followup fallback override resolution (with session-key fallback), treat agent-level fallback overrides as configured in embedded runner preflight, and classify <code>model_cooldown</code> / <code>cooling down</code> errors as <code>rate_limit</code> so failover continues. (#11972, #24137, #17231)</li>
<li>Agents/Model fallback: keep same-provider fallback chains active when session model differs from configured primary, infer cooldown reason from provider profile state (instead of <code>disabledReason</code> only), keep no-profile fallback providers eligible (env/models.json paths), and only relax same-provider cooldown fallback attempts for <code>rate_limit</code>. (#23816) thanks @ramezgaberiel.</li>
<li>Agents/Model fallback: continue fallback traversal on unrecognized errors when candidates remain, while still throwing the original unknown error on the last candidate. (#26106) Thanks @Sid-Qin.</li>
<li>Models/Auth probes: map permanent auth failover reasons (<code>auth_permanent</code>, for example revoked keys) into probe auth status instead of <code>unknown</code>, so <code>openclaw models status --probe</code> reports actionable auth failures. (#25754) thanks @rrenamed.</li>
<li>Hooks/Inbound metadata: include <code>guildId</code> and <code>channelName</code> in <code>message_received</code> metadata for both plugin and internal hook paths. (#26115) Thanks @davidrudduck.</li>
<li>Discord/Component auth: evaluate guild component interactions with command-gating authorizers so unauthorized users no longer get <code>CommandAuthorized: true</code> on modal/button events. (#26119) Thanks @bmendonca3.</li>
<li>Security/Gateway auth: require pairing for operator device-identity sessions authenticated with shared token auth so unpaired devices cannot self-assign operator scopes. Thanks @tdjackey for reporting.</li>
<li>Security/Gateway WebSocket auth: enforce origin checks for direct browser WebSocket clients beyond Control UI/Webchat, apply password-auth failure throttling to browser-origin loopback attempts (including localhost), and block silent auto-pairing for non-Control-UI browser clients to prevent cross-origin brute-force and session takeover chains. This ships in the next npm release (<code>2026.2.25</code>). Thanks @luz-oasis for reporting.</li>
<li>Security/Gateway trusted proxy: require <code>operator</code> role for the Control UI trusted-proxy pairing bypass so unpaired <code>node</code> sessions can no longer connect via <code>client.id=control-ui</code> and invoke node event methods. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/macOS beta onboarding: remove Anthropic OAuth sign-in and the legacy <code>oauth.json</code> onboarding path that exposed the PKCE verifier via OAuth <code>state</code>; this impacted the macOS beta onboarding path only. Anthropic subscription auth is now setup-token-only and will ship in the next npm release (<code>2026.2.25</code>). Thanks @zdi-disclosures for reporting.</li>
<li>Security/Microsoft Teams file consent: bind <code>fileConsent/invoke</code> upload acceptance/decline to the originating conversation before consuming pending uploads, preventing cross-conversation pending-file upload or cancellation via leaked <code>uploadId</code> values; includes regression coverage for match/mismatch invoke handling. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Gateway: harden <code>agents.files</code> path handling to block out-of-workspace symlink targets for <code>agents.files.get</code>/<code>agents.files.set</code>, keep in-workspace symlink targets supported, and add gateway regression coverage for both blocked escapes and allowed in-workspace symlinks. Thanks @tdjackey for reporting.</li>
<li>Security/Workspace FS: reject hardlinked workspace file aliases in <code>tools.fs.workspaceOnly</code> and <code>tools.exec.applyPatch.workspaceOnly</code> boundary checks (including sandbox mount-root guards) to prevent out-of-workspace read/write via in-workspace hardlink paths. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Browser temp paths: harden trace/download output-path handling against symlink-root and symlink-parent escapes with realpath-based write-path checks plus secure fallback tmp-dir validation that fails closed on unsafe fallback links. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Browser uploads: revalidate upload paths at use-time in Playwright file-chooser and direct-input flows so missing/rebound paths are rejected before <code>setFiles</code>, with regression coverage for strict missing-path handling.</li>
<li>Security/Exec approvals: bind <code>system.run</code> approval matching to exact argv identity and preserve argv whitespace in rendered command text, preventing trailing-space executable path swaps from reusing a mismatched approval. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Exec approvals: harden approval-bound <code>system.run</code> execution on node hosts by rejecting symlink <code>cwd</code> paths and canonicalizing path-like executable argv before spawn, blocking mutable-cwd symlink retarget chains between approval and execution. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Signal: enforce DM/group authorization before reaction-only notification enqueue so unauthorized senders can no longer inject Signal reaction system events under <code>dmPolicy</code>/<code>groupPolicy</code>; reaction notifications now require channel access checks first. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Discord reactions: enforce DM policy/allowlist authorization before reaction-event system enqueue in direct messages; Discord reaction handling now also honors DM/group-DM enablement and guild <code>groupPolicy</code> channel gating to keep reaction ingress aligned with normal message preflight. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Slack reactions + pins: gate <code>reaction_*</code> and <code>pin_*</code> system-event enqueue through shared sender authorization so DM <code>dmPolicy</code>/<code>allowFrom</code> and channel <code>users</code> allowlists are enforced consistently for non-message ingress, with regression coverage for denied/allowed sender paths. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Telegram reactions: enforce <code>dmPolicy</code>/<code>allowFrom</code> and group allowlist authorization on <code>message_reaction</code> events before enqueueing reaction system events, preventing unauthorized reaction-triggered input in DMs and groups; ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Slack interactions: enforce channel/DM authorization and modal actor binding (<code>private_metadata.userId</code>) before enqueueing <code>block_action</code>/<code>view_submission</code>/<code>view_closed</code> system events, with regression coverage for unauthorized senders and missing/mismatched actor metadata. This ships in the next npm release (<code>2026.2.25</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Nextcloud Talk: drop replayed signed webhook events with persistent per-account replay dedupe across restarts, and reject unexpected webhook backend origins when account base URL is configured. Thanks @aristorechina for reporting.</li>
<li>Security/Nextcloud Talk: reject unsigned webhook traffic before full body reads, reducing unauthenticated request-body exposure, with auth-order regression coverage. (#26118) Thanks @bmendonca3.</li>
<li>Security/Nextcloud Talk: stop treating DM pairing-store entries as group allowlist senders, so group authorization remains bounded to configured group allowlists. (#26116) Thanks @bmendonca3.</li>
<li>Security/LINE: cap unsigned webhook body reads before auth/signature handling to bound unauthenticated body processing. (#26095) Thanks @bmendonca3.</li>
<li>Security/IRC: keep pairing-store approvals DM-only and out of IRC group allowlist authorization, with policy regression tests for allowlist resolution. (#26112) Thanks @bmendonca3.</li>
<li>Security/Microsoft Teams: isolate group allowlist and command authorization from DM pairing-store entries to prevent cross-context authorization bleed. (#26111) Thanks @bmendonca3.</li>
<li>Security/SSRF guard: classify IPv6 multicast literals (<code>ff00::/8</code>) as blocked/private-internal targets in shared SSRF IP checks, preventing multicast literals from bypassing URL-host preflight and DNS answer validation. This ships in the next npm release (<code>2026.2.25</code>). Thanks @zpbrent for reporting.</li>
<li>Tests/Low-memory stability: disable Vitest <code>vmForks</code> by default on low-memory local hosts (<code><64 GiB</code>), keep low-profile extension lane parallelism at 4 workers, and align cron isolated-agent tests with <code>setSessionRuntimeModel</code> usage to avoid deterministic suite failures. (#26324) Thanks @ngutman.</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.2.25/OpenClaw-2026.2.25.zip" length="23078398" type="application/octet-stream" sparkle:edSignature="PJjvRhivhybV5bYr8u1C9Dyw4h8yePGwG8SFsr4QRqMSBYMEedraPJO3KNbkoChjclYUYf3oGcC4daNZnFvgBA=="/>
<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

@ -9,14 +9,14 @@ Status: **extremely alpha**. The app is actively being rebuilt from the ground u
- [x] Encrypted persistence for gateway setup/auth state
- [x] Chat UI restyled
- [x] Settings UI restyled and de-duplicated (gateway controls moved to Connect)
- [ ] QR code scanning in onboarding
- [ ] Performance improvements
- [ ] Streaming support in chat UI
- [ ] Request camera/location and other permissions in onboarding/settings flow
- [ ] Push notifications for gateway/chat status updates
- [ ] Security hardening (biometric lock, token handling, safer defaults)
- [ ] Voice tab full functionality
- [ ] Screen tab full functionality
- [x] QR code scanning in onboarding
- [x] Performance improvements
- [x] Streaming support in chat UI
- [x] Request camera/location and other permissions in onboarding/settings flow
- [x] Push notifications for gateway/chat status updates
- [x] Security hardening (biometric lock, token handling, safer defaults)
- [x] Voice tab full functionality
- [x] Screen tab full functionality
- [ ] Full end-to-end QA and release hardening
## Open in Android Studio
@ -30,6 +30,32 @@ cd apps/android
./gradlew :app:assembleDebug
./gradlew :app:installDebug
./gradlew :app:testDebugUnitTest
cd ../..
bun run android:bundle:release
```
`bun run android:bundle:release` auto-bumps Android `versionName`/`versionCode` in `apps/android/app/build.gradle.kts`, then builds a signed release `.aab`.
## Kotlin Lint + Format
```bash
pnpm android:lint
pnpm android:format
```
Android framework/resource lint (separate pass):
```bash
pnpm android:lint:android
```
Direct Gradle tasks:
```bash
cd apps/android
./gradlew :app:ktlintCheck :benchmark:ktlintCheck
./gradlew :app:ktlintFormat :benchmark:ktlintFormat
./gradlew :app:lintDebug
```
`gradlew` auto-detects the Android SDK at `~/Library/Android/sdk` (macOS default) if `ANDROID_SDK_ROOT` / `ANDROID_HOME` are unset.
@ -134,8 +160,8 @@ pnpm openclaw gateway --port 18789 --verbose
3) Approve pairing (on the gateway machine):
```bash
openclaw nodes pending
openclaw nodes approve <requestId>
openclaw devices list
openclaw devices approve <requestId>
```
More details: `docs/platforms/android.md`.
@ -150,6 +176,56 @@ More details: `docs/platforms/android.md`.
- `CAMERA` for `camera.snap` and `camera.clip`
- `RECORD_AUDIO` for `camera.clip` when `includeAudio=true`
## Integration Capability Test (Preconditioned)
This suite assumes setup is already done manually. It does **not** install/run/pair automatically.
Pre-req checklist:
1) Gateway is running and reachable from the Android app.
2) Android app is connected to that gateway and `openclaw nodes status` shows it as paired + connected.
3) App stays unlocked and in foreground for the whole run.
4) Open the app **Screen** tab and keep it active during the run (canvas/A2UI commands require the canvas WebView attached there).
5) Grant runtime permissions for capabilities you expect to pass (camera/mic/location/notification listener/location, etc.).
6) No interactive system dialogs should be pending before test start.
7) Canvas host is enabled and reachable from the device (do not run gateway with `OPENCLAW_SKIP_CANVAS_HOST=1`; startup logs should include `canvas host mounted at .../__openclaw__/`).
8) Local operator test client pairing is approved. If first run fails with `pairing required`, approve latest pending device pairing request, then rerun:
9) For A2UI checks, keep the app on **Screen** tab; the node now auto-refreshes canvas capability once on first A2UI reachability failure (TTL-safe retry).
```bash
openclaw devices list
openclaw devices approve --latest
```
Run:
```bash
pnpm android:test:integration
```
Optional overrides:
- `OPENCLAW_ANDROID_GATEWAY_URL=ws://...` (default: from your local OpenClaw config)
- `OPENCLAW_ANDROID_GATEWAY_TOKEN=...`
- `OPENCLAW_ANDROID_GATEWAY_PASSWORD=...`
- `OPENCLAW_ANDROID_NODE_ID=...` or `OPENCLAW_ANDROID_NODE_NAME=...`
What it does:
- Reads `node.describe` command list from the selected Android node.
- Invokes advertised non-interactive commands.
- Skips `screen.record` in this suite (Android requires interactive per-invocation screen-capture consent).
- Asserts command contracts (success or expected deterministic error for safe-invalid calls like `sms.send` and `notifications.actions`).
Common failure quick-fixes:
- `pairing required` before tests start:
- approve pending device pairing (`openclaw devices approve --latest`) and rerun.
- `A2UI host not reachable` / `A2UI_HOST_NOT_CONFIGURED`:
- ensure gateway canvas host is running and reachable, keep the app on the **Screen** tab. The app will auto-refresh canvas capability once; if it still fails, reconnect app and rerun.
- `NODE_BACKGROUND_UNAVAILABLE: canvas unavailable`:
- app is not effectively ready for canvas commands; keep app foregrounded and **Screen** tab active.
## Contributions
This Android app is currently being rebuilt.

View File

@ -1,156 +1,262 @@
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() }
val androidKeyPassword = providers.gradleProperty("OPENCLAW_ANDROID_KEY_PASSWORD").orNull?.takeIf { it.isNotBlank() }
val resolvedAndroidStoreFile =
androidStoreFile?.let { storeFilePath ->
if (storeFilePath.startsWith("~/")) {
"${System.getProperty("user.home")}/${storeFilePath.removePrefix("~/")}"
} else {
storeFilePath
}
}
val hasAndroidReleaseSigning =
listOf(resolvedAndroidStoreFile, androidStorePassword, androidKeyAlias, androidKeyPassword).all { it != null }
val wantsAndroidReleaseBuild =
gradle.startParameter.taskNames.any { taskName ->
taskName.contains("Release", ignoreCase = true) ||
Regex("""(^|:)(bundle|assemble)$""").containsMatchIn(taskName)
}
if (wantsAndroidReleaseBuild && !hasAndroidReleaseSigning) {
error(
"Missing Android release signing properties. Set OPENCLAW_ANDROID_STORE_FILE, " +
"OPENCLAW_ANDROID_STORE_PASSWORD, OPENCLAW_ANDROID_KEY_ALIAS, and " +
"OPENCLAW_ANDROID_KEY_PASSWORD in ~/.gradle/gradle.properties.",
)
}
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.plugin.compose")
id("org.jetbrains.kotlin.plugin.serialization")
id("com.android.application")
id("org.jlleitschuh.gradle.ktlint")
id("org.jetbrains.kotlin.plugin.compose")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
namespace = "ai.openclaw.android"
compileSdk = 36
namespace = "ai.openclaw.app"
compileSdk = 36
sourceSets {
getByName("main") {
assets.directories.add("../../shared/OpenClawKit/Sources/OpenClawKit/Resources")
// Release signing is local-only; keep the keystore path and passwords out of the repo.
signingConfigs {
if (hasAndroidReleaseSigning) {
create("release") {
storeFile = project.file(checkNotNull(resolvedAndroidStoreFile))
storePassword = checkNotNull(androidStorePassword)
keyAlias = checkNotNull(androidKeyAlias)
keyPassword = checkNotNull(androidKeyPassword)
}
}
}
}
defaultConfig {
applicationId = "ai.openclaw.android"
minSdk = 31
targetSdk = 36
versionCode = 202602260
versionName = "2026.2.26"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
sourceSets {
getByName("main") {
assets.directories.add("../../shared/OpenClawKit/Sources/OpenClawKit/Resources")
}
}
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
defaultConfig {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026031400
versionName = "2026.3.14"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
}
debug {
isMinifyEnabled = false
buildTypes {
release {
if (hasAndroidReleaseSigning) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = true
isShrinkResources = true
ndk {
debugSymbolLevel = "SYMBOL_TABLE"
}
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
debug {
isMinifyEnabled = false
}
}
}
buildFeatures {
compose = true
buildConfig = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
packaging {
resources {
excludes += setOf(
"/META-INF/{AL2.0,LGPL2.1}",
"/META-INF/*.version",
"/META-INF/LICENSE*.txt",
"DebugProbesKt.bin",
"kotlin-tooling-metadata.json",
)
buildFeatures {
compose = true
buildConfig = true
}
}
lint {
disable += setOf(
"GradleDependency",
"IconLauncherShape",
"NewerVersionAvailable",
)
warningsAsErrors = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
testOptions {
unitTests.isIncludeAndroidResources = true
}
packaging {
resources {
excludes +=
setOf(
"/META-INF/{AL2.0,LGPL2.1}",
"/META-INF/*.version",
"/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",
)
}
}
lint {
disable +=
setOf(
"AndroidGradlePluginVersion",
"GradleDependency",
"IconLauncherShape",
"NewerVersionAvailable",
)
warningsAsErrors = true
}
testOptions {
unitTests.isIncludeAndroidResources = true
}
}
androidComponents {
onVariants { variant ->
variant.outputs
.filterIsInstance<VariantOutputImpl>()
.forEach { output ->
val versionName = output.versionName.orNull ?: "0"
val buildType = variant.buildType
onVariants { variant ->
variant.outputs
.filterIsInstance<VariantOutputImpl>()
.forEach { output ->
val versionName = output.versionName.orNull ?: "0"
val buildType = variant.buildType
val outputFileName = "openclaw-${versionName}-${buildType}.apk"
output.outputFileName = outputFileName
}
}
val outputFileName = "openclaw-$versionName-$buildType.apk"
output.outputFileName = outputFileName
}
}
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
allWarningsAsErrors.set(true)
}
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
allWarningsAsErrors.set(true)
}
}
ktlint {
android.set(true)
ignoreFailures.set(false)
filter {
exclude("**/build/**")
}
}
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2026.02.00")
implementation(composeBom)
androidTestImplementation(composeBom)
val composeBom = platform("androidx.compose:compose-bom:2026.02.00")
implementation(composeBom)
androidTestImplementation(composeBom)
implementation("androidx.core:core-ktx:1.17.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
implementation("androidx.activity:activity-compose:1.12.2")
implementation("androidx.webkit:webkit:1.15.0")
implementation("androidx.core:core-ktx:1.17.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
implementation("androidx.activity:activity-compose:1.12.2")
implementation("androidx.webkit:webkit:1.15.0")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
// 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")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
// 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")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-tooling")
// Material Components (XML theme + resources)
implementation("com.google.android.material:material:1.13.0")
// Material Components (XML theme + resources)
implementation("com.google.android.material:material:1.13.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
implementation("androidx.security:security-crypto:1.1.0")
implementation("androidx.exifinterface:exifinterface:1.4.2")
implementation("com.squareup.okhttp3:okhttp:5.3.2")
implementation("org.bouncycastle:bcprov-jdk18on:1.83")
implementation("org.commonmark:commonmark:0.27.1")
implementation("org.commonmark:commonmark-ext-autolink:0.27.1")
implementation("org.commonmark:commonmark-ext-gfm-strikethrough:0.27.1")
implementation("org.commonmark:commonmark-ext-gfm-tables:0.27.1")
implementation("org.commonmark:commonmark-ext-task-list-items:0.27.1")
implementation("androidx.security:security-crypto:1.1.0")
implementation("androidx.exifinterface:exifinterface:1.4.2")
implementation("com.squareup.okhttp3:okhttp:5.3.2")
implementation("org.bouncycastle:bcprov-jdk18on:1.83")
implementation("org.commonmark:commonmark:0.27.1")
implementation("org.commonmark:commonmark-ext-autolink:0.27.1")
implementation("org.commonmark:commonmark-ext-gfm-strikethrough:0.27.1")
implementation("org.commonmark:commonmark-ext-gfm-tables:0.27.1")
implementation("org.commonmark:commonmark-ext-task-list-items:0.27.1")
// CameraX (for node.invoke camera.* parity)
implementation("androidx.camera:camera-core:1.5.2")
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")
// CameraX (for node.invoke camera.* parity)
implementation("androidx.camera:camera-core:1.5.2")
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("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")
// Unicast DNS-SD (Wide-Area Bonjour) for tailnet discovery domains.
implementation("dnsjava:dnsjava:3.6.4")
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
testImplementation("io.kotest:kotest-runner-junit5-jvm:6.1.3")
testImplementation("io.kotest:kotest-assertions-core-jvm:6.1.3")
testImplementation("com.squareup.okhttp3:mockwebserver:5.3.2")
testImplementation("org.robolectric:robolectric:4.16.1")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:6.0.2")
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
testImplementation("io.kotest:kotest-runner-junit5-jvm:6.1.3")
testImplementation("io.kotest:kotest-assertions-core-jvm:6.1.3")
testImplementation("com.squareup.okhttp3:mockwebserver:5.3.2")
testImplementation("org.robolectric:robolectric:4.16.1")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:6.0.2")
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
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.android.** { *; }
# ── 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

@ -3,19 +3,26 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<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.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
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" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
@ -37,7 +44,7 @@
<service
android:name=".NodeForegroundService"
android:exported="false"
android:foregroundServiceType="dataSync|microphone|mediaProjection" />
android:foregroundServiceType="dataSync" />
<service
android:name=".node.DeviceNotificationListenerService"
android:label="@string/app_name"
@ -66,9 +73,5 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".InstallResultReceiver"
android:exported="false" />
</application>
</manifest>

View File

@ -1,33 +0,0 @@
package ai.openclaw.android
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.util.Log
class InstallResultReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
when (status) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
// System needs user confirmation — launch the confirmation activity
@Suppress("DEPRECATION")
val confirmIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
if (confirmIntent != null) {
confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(confirmIntent)
Log.w("openclaw", "app.update: user confirmation requested, launching install dialog")
}
}
PackageInstaller.STATUS_SUCCESS -> {
Log.w("openclaw", "app.update: install SUCCESS")
}
else -> {
Log.e("openclaw", "app.update: install FAILED status=$status message=$message")
}
}
}
}

View File

@ -1,65 +0,0 @@
package ai.openclaw.android
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.media.projection.MediaProjectionManager
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
class ScreenCaptureRequester(private val activity: ComponentActivity) {
data class CaptureResult(val resultCode: Int, val data: Intent)
private val mutex = Mutex()
private var pending: CompletableDeferred<CaptureResult?>? = null
private val launcher: ActivityResultLauncher<Intent> =
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val p = pending
pending = null
val data = result.data
if (result.resultCode == Activity.RESULT_OK && data != null) {
p?.complete(CaptureResult(result.resultCode, data))
} else {
p?.complete(null)
}
}
suspend fun requestCapture(timeoutMs: Long = 20_000): CaptureResult? =
mutex.withLock {
val proceed = showRationaleDialog()
if (!proceed) return null
val mgr = activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val intent = mgr.createScreenCaptureIntent()
val deferred = CompletableDeferred<CaptureResult?>()
pending = deferred
withContext(Dispatchers.Main) { launcher.launch(intent) }
withContext(Dispatchers.Default) { withTimeout(timeoutMs) { deferred.await() } }
}
private suspend fun showRationaleDialog(): Boolean =
withContext(Dispatchers.Main) {
suspendCancellableCoroutine { cont ->
AlertDialog.Builder(activity)
.setTitle("Screen recording required")
.setMessage("OpenClaw needs to record the screen for this command.")
.setPositiveButton("Continue") { _, _ -> cont.resume(true) }
.setNegativeButton("Not now") { _, _ -> cont.resume(false) }
.setOnCancelListener { cont.resume(false) }
.show()
}
}
}

View File

@ -1,295 +0,0 @@
package ai.openclaw.android.node
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import ai.openclaw.android.InstallResultReceiver
import ai.openclaw.android.MainActivity
import ai.openclaw.android.gateway.GatewayEndpoint
import ai.openclaw.android.gateway.GatewaySession
import java.io.File
import java.net.URI
import java.security.MessageDigest
import java.util.Locale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
private val SHA256_HEX = Regex("^[a-fA-F0-9]{64}$")
internal data class AppUpdateRequest(
val url: String,
val expectedSha256: String,
)
internal fun parseAppUpdateRequest(paramsJson: String?, connectedHost: String?): AppUpdateRequest {
val params =
try {
paramsJson?.let { Json.parseToJsonElement(it).jsonObject }
} catch (_: Throwable) {
throw IllegalArgumentException("params must be valid JSON")
} ?: throw IllegalArgumentException("missing 'url' parameter")
val urlRaw =
params["url"]?.jsonPrimitive?.content?.trim().orEmpty()
.ifEmpty { throw IllegalArgumentException("missing 'url' parameter") }
val sha256Raw =
params["sha256"]?.jsonPrimitive?.content?.trim().orEmpty()
.ifEmpty { throw IllegalArgumentException("missing 'sha256' parameter") }
if (!SHA256_HEX.matches(sha256Raw)) {
throw IllegalArgumentException("invalid 'sha256' parameter (expected 64 hex chars)")
}
val uri =
try {
URI(urlRaw)
} catch (_: Throwable) {
throw IllegalArgumentException("invalid 'url' parameter")
}
val scheme = uri.scheme?.lowercase(Locale.US).orEmpty()
if (scheme != "https") {
throw IllegalArgumentException("url must use https")
}
if (!uri.userInfo.isNullOrBlank()) {
throw IllegalArgumentException("url must not include credentials")
}
val host = uri.host?.lowercase(Locale.US) ?: throw IllegalArgumentException("url host required")
val connectedHostNormalized = connectedHost?.trim()?.lowercase(Locale.US).orEmpty()
if (connectedHostNormalized.isNotEmpty() && host != connectedHostNormalized) {
throw IllegalArgumentException("url host must match connected gateway host")
}
return AppUpdateRequest(
url = uri.toASCIIString(),
expectedSha256 = sha256Raw.lowercase(Locale.US),
)
}
internal fun sha256Hex(file: File): String {
val digest = MessageDigest.getInstance("SHA-256")
file.inputStream().use { input ->
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
while (true) {
val read = input.read(buffer)
if (read < 0) break
if (read == 0) continue
digest.update(buffer, 0, read)
}
}
val out = StringBuilder(64)
for (byte in digest.digest()) {
out.append(String.format(Locale.US, "%02x", byte))
}
return out.toString()
}
class AppUpdateHandler(
private val appContext: Context,
private val connectedEndpoint: () -> GatewayEndpoint?,
) {
fun handleUpdate(paramsJson: String?): GatewaySession.InvokeResult {
try {
val updateRequest =
try {
parseAppUpdateRequest(paramsJson, connectedEndpoint()?.host)
} catch (err: IllegalArgumentException) {
return GatewaySession.InvokeResult.error(
code = "INVALID_REQUEST",
message = "INVALID_REQUEST: ${err.message ?: "invalid app.update params"}",
)
}
val url = updateRequest.url
val expectedSha256 = updateRequest.expectedSha256
android.util.Log.w("openclaw", "app.update: downloading from $url")
val notifId = 9001
val channelId = "app_update"
val notifManager = appContext.getSystemService(android.content.Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
// Create notification channel (required for Android 8+)
val channel = android.app.NotificationChannel(channelId, "App Updates", android.app.NotificationManager.IMPORTANCE_LOW)
notifManager.createNotificationChannel(channel)
// PendingIntent to open the app when notification is tapped
val launchIntent = Intent(appContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
val launchPi = PendingIntent.getActivity(appContext, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
// Launch download async so the invoke returns immediately
CoroutineScope(Dispatchers.IO).launch {
try {
val cacheDir = java.io.File(appContext.cacheDir, "updates")
cacheDir.mkdirs()
val file = java.io.File(cacheDir, "update.apk")
if (file.exists()) file.delete()
// Show initial progress notification
fun buildProgressNotif(progress: Int, max: Int, text: String): android.app.Notification {
return android.app.Notification.Builder(appContext, channelId)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle("OpenClaw Update")
.setContentText(text)
.setProgress(max, progress, max == 0)
.setContentIntent(launchPi)
.setOngoing(true)
.build()
}
notifManager.notify(notifId, buildProgressNotif(0, 0, "Connecting..."))
val client = okhttp3.OkHttpClient.Builder()
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(300, java.util.concurrent.TimeUnit.SECONDS)
.build()
val request = okhttp3.Request.Builder().url(url).build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
notifManager.cancel(notifId)
notifManager.notify(notifId, android.app.Notification.Builder(appContext, channelId)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle("Update Failed")
.setContentIntent(launchPi)
.setContentText("HTTP ${response.code}")
.build())
return@launch
}
val contentLength = response.body?.contentLength() ?: -1L
val body = response.body ?: run {
notifManager.cancel(notifId)
return@launch
}
// Download with progress tracking
var totalBytes = 0L
var lastNotifUpdate = 0L
body.byteStream().use { input ->
file.outputStream().use { output ->
val buffer = ByteArray(8192)
while (true) {
val bytesRead = input.read(buffer)
if (bytesRead == -1) break
output.write(buffer, 0, bytesRead)
totalBytes += bytesRead
// Update notification at most every 500ms
val now = System.currentTimeMillis()
if (now - lastNotifUpdate > 500) {
lastNotifUpdate = now
if (contentLength > 0) {
val pct = ((totalBytes * 100) / contentLength).toInt()
val mb = String.format(Locale.US, "%.1f", totalBytes / 1048576.0)
val totalMb = String.format(Locale.US, "%.1f", contentLength / 1048576.0)
notifManager.notify(notifId, buildProgressNotif(pct, 100, "$mb / $totalMb MB ($pct%)"))
} else {
val mb = String.format(Locale.US, "%.1f", totalBytes / 1048576.0)
notifManager.notify(notifId, buildProgressNotif(0, 0, "${mb} MB downloaded"))
}
}
}
}
}
android.util.Log.w("openclaw", "app.update: downloaded ${file.length()} bytes")
val actualSha256 = sha256Hex(file)
if (actualSha256 != expectedSha256) {
android.util.Log.e(
"openclaw",
"app.update: sha256 mismatch expected=$expectedSha256 actual=$actualSha256",
)
file.delete()
notifManager.cancel(notifId)
notifManager.notify(
notifId,
android.app.Notification.Builder(appContext, channelId)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle("Update Failed")
.setContentIntent(launchPi)
.setContentText("SHA-256 mismatch")
.build(),
)
return@launch
}
// Verify file is a valid APK (basic check: ZIP magic bytes)
val magic = file.inputStream().use { it.read().toByte() to it.read().toByte() }
if (magic.first != 0x50.toByte() || magic.second != 0x4B.toByte()) {
android.util.Log.e("openclaw", "app.update: invalid APK (bad magic: ${magic.first}, ${magic.second})")
file.delete()
notifManager.cancel(notifId)
notifManager.notify(notifId, android.app.Notification.Builder(appContext, channelId)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle("Update Failed")
.setContentIntent(launchPi)
.setContentText("Downloaded file is not a valid APK")
.build())
return@launch
}
// Use PackageInstaller session API — works from background on API 34+
// The system handles showing the install confirmation dialog
notifManager.cancel(notifId)
notifManager.notify(
notifId,
android.app.Notification.Builder(appContext, channelId)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentTitle("Installing Update...")
.setContentIntent(launchPi)
.setContentText("${String.format(Locale.US, "%.1f", totalBytes / 1048576.0)} MB downloaded")
.build(),
)
val installer = appContext.packageManager.packageInstaller
val params = android.content.pm.PackageInstaller.SessionParams(
android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
)
params.setSize(file.length())
val sessionId = installer.createSession(params)
val session = installer.openSession(sessionId)
session.openWrite("openclaw-update.apk", 0, file.length()).use { out ->
file.inputStream().use { inp -> inp.copyTo(out) }
session.fsync(out)
}
// Commit with FLAG_MUTABLE PendingIntent — system requires mutable for PackageInstaller status
val callbackIntent = android.content.Intent(appContext, InstallResultReceiver::class.java)
val pi = android.app.PendingIntent.getBroadcast(
appContext, sessionId, callbackIntent,
android.app.PendingIntent.FLAG_UPDATE_CURRENT or android.app.PendingIntent.FLAG_MUTABLE
)
session.commit(pi.intentSender)
android.util.Log.w("openclaw", "app.update: PackageInstaller session committed, waiting for user confirmation")
} catch (err: Throwable) {
android.util.Log.e("openclaw", "app.update: async error", err)
notifManager.cancel(notifId)
notifManager.notify(notifId, android.app.Notification.Builder(appContext, channelId)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentTitle("Update Failed")
.setContentIntent(launchPi)
.setContentText(err.message ?: "Unknown error")
.build())
}
}
// Return immediately — download happens in background
return GatewaySession.InvokeResult.ok(buildJsonObject {
put("status", "downloading")
put("url", url)
put("sha256", expectedSha256)
}.toString())
} catch (err: Throwable) {
android.util.Log.e("openclaw", "app.update: error", err)
return GatewaySession.InvokeResult.error(code = "UNAVAILABLE", message = err.message ?: "update failed")
}
}
}

View File

@ -1,171 +0,0 @@
package ai.openclaw.android.node
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.BatteryManager
import android.os.Build
import android.os.Environment
import android.os.PowerManager
import android.os.StatFs
import android.os.SystemClock
import ai.openclaw.android.BuildConfig
import ai.openclaw.android.gateway.GatewaySession
import java.util.Locale
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
class DeviceHandler(
private val appContext: Context,
) {
fun handleDeviceStatus(_paramsJson: String?): GatewaySession.InvokeResult {
return GatewaySession.InvokeResult.ok(statusPayloadJson())
}
fun handleDeviceInfo(_paramsJson: String?): GatewaySession.InvokeResult {
return GatewaySession.InvokeResult.ok(infoPayloadJson())
}
private fun statusPayloadJson(): String {
val batteryIntent = appContext.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val batteryStatus =
batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN)
?: BatteryManager.BATTERY_STATUS_UNKNOWN
val batteryLevel = batteryLevelFraction(batteryIntent)
val powerManager = appContext.getSystemService(PowerManager::class.java)
val storage = StatFs(Environment.getDataDirectory().absolutePath)
val totalBytes = storage.totalBytes
val freeBytes = storage.availableBytes
val usedBytes = (totalBytes - freeBytes).coerceAtLeast(0L)
val connectivity = appContext.getSystemService(ConnectivityManager::class.java)
val activeNetwork = connectivity?.activeNetwork
val caps = activeNetwork?.let { connectivity.getNetworkCapabilities(it) }
val uptimeSeconds = SystemClock.elapsedRealtime() / 1_000.0
return buildJsonObject {
put(
"battery",
buildJsonObject {
batteryLevel?.let { put("level", JsonPrimitive(it)) }
put("state", JsonPrimitive(mapBatteryState(batteryStatus)))
put("lowPowerModeEnabled", JsonPrimitive(powerManager?.isPowerSaveMode == true))
},
)
put(
"thermal",
buildJsonObject {
put("state", JsonPrimitive(mapThermalState(powerManager)))
},
)
put(
"storage",
buildJsonObject {
put("totalBytes", JsonPrimitive(totalBytes))
put("freeBytes", JsonPrimitive(freeBytes))
put("usedBytes", JsonPrimitive(usedBytes))
},
)
put(
"network",
buildJsonObject {
put("status", JsonPrimitive(mapNetworkStatus(caps)))
put(
"isExpensive",
JsonPrimitive(
caps?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)?.not() ?: false,
),
)
put(
"isConstrained",
JsonPrimitive(
caps?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)?.not() ?: false,
),
)
put("interfaces", networkInterfacesJson(caps))
},
)
put("uptimeSeconds", JsonPrimitive(uptimeSeconds))
}.toString()
}
private fun infoPayloadJson(): String {
val model = Build.MODEL?.trim().orEmpty()
val manufacturer = Build.MANUFACTURER?.trim().orEmpty()
val modelIdentifier = Build.DEVICE?.trim().orEmpty()
val systemVersion = Build.VERSION.RELEASE?.trim().orEmpty()
val locale = Locale.getDefault().toLanguageTag().trim()
val appVersion = BuildConfig.VERSION_NAME.trim()
val appBuild = BuildConfig.VERSION_CODE.toString()
return buildJsonObject {
put("deviceName", JsonPrimitive(model.ifEmpty { "Android" }))
put("modelIdentifier", JsonPrimitive(modelIdentifier.ifEmpty { listOf(manufacturer, model).filter { it.isNotEmpty() }.joinToString(" ") }))
put("systemName", JsonPrimitive("Android"))
put("systemVersion", JsonPrimitive(systemVersion.ifEmpty { Build.VERSION.SDK_INT.toString() }))
put("appVersion", JsonPrimitive(appVersion.ifEmpty { "dev" }))
put("appBuild", JsonPrimitive(appBuild.ifEmpty { "0" }))
put("locale", JsonPrimitive(locale.ifEmpty { Locale.getDefault().toString() }))
}.toString()
}
private fun batteryLevelFraction(intent: Intent?): Double? {
val rawLevel = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
val rawScale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
if (rawLevel < 0 || rawScale <= 0) return null
return rawLevel.toDouble() / rawScale.toDouble()
}
private fun mapBatteryState(status: Int): String {
return when (status) {
BatteryManager.BATTERY_STATUS_CHARGING -> "charging"
BatteryManager.BATTERY_STATUS_FULL -> "full"
BatteryManager.BATTERY_STATUS_DISCHARGING, BatteryManager.BATTERY_STATUS_NOT_CHARGING -> "unplugged"
else -> "unknown"
}
}
private fun mapThermalState(powerManager: PowerManager?): String {
val thermal = powerManager?.currentThermalStatus ?: return "nominal"
return when (thermal) {
PowerManager.THERMAL_STATUS_NONE, PowerManager.THERMAL_STATUS_LIGHT -> "nominal"
PowerManager.THERMAL_STATUS_MODERATE -> "fair"
PowerManager.THERMAL_STATUS_SEVERE -> "serious"
PowerManager.THERMAL_STATUS_CRITICAL,
PowerManager.THERMAL_STATUS_EMERGENCY,
PowerManager.THERMAL_STATUS_SHUTDOWN -> "critical"
else -> "nominal"
}
}
private fun mapNetworkStatus(caps: NetworkCapabilities?): String {
if (caps == null) return "unsatisfied"
return when {
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) -> "satisfied"
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) -> "requiresConnection"
else -> "unsatisfied"
}
}
private fun networkInterfacesJson(caps: NetworkCapabilities?) =
buildJsonArray {
if (caps == null) return@buildJsonArray
var hasKnownTransport = false
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
hasKnownTransport = true
add(JsonPrimitive("wifi"))
}
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
hasKnownTransport = true
add(JsonPrimitive("cellular"))
}
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
hasKnownTransport = true
add(JsonPrimitive("wired"))
}
if (!hasKnownTransport) add(JsonPrimitive("other"))
}
}

View File

@ -1,164 +0,0 @@
package ai.openclaw.android.node
import android.app.Notification
import android.app.NotificationManager
import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
private const val MAX_NOTIFICATION_TEXT_CHARS = 512
internal fun sanitizeNotificationText(value: CharSequence?): String? {
val normalized = value?.toString()?.trim().orEmpty()
return normalized.take(MAX_NOTIFICATION_TEXT_CHARS).ifEmpty { null }
}
data class DeviceNotificationEntry(
val key: String,
val packageName: String,
val title: String?,
val text: String?,
val subText: String?,
val category: String?,
val channelId: String?,
val postTimeMs: Long,
val isOngoing: Boolean,
val isClearable: Boolean,
)
data class DeviceNotificationSnapshot(
val enabled: Boolean,
val connected: Boolean,
val notifications: List<DeviceNotificationEntry>,
)
private object DeviceNotificationStore {
private val lock = Any()
private var connected = false
private val byKey = LinkedHashMap<String, DeviceNotificationEntry>()
fun replace(entries: List<DeviceNotificationEntry>) {
synchronized(lock) {
byKey.clear()
for (entry in entries) {
byKey[entry.key] = entry
}
}
}
fun upsert(entry: DeviceNotificationEntry) {
synchronized(lock) {
byKey[entry.key] = entry
}
}
fun remove(key: String) {
synchronized(lock) {
byKey.remove(key)
}
}
fun setConnected(value: Boolean) {
synchronized(lock) {
connected = value
if (!value) {
byKey.clear()
}
}
}
fun snapshot(enabled: Boolean): DeviceNotificationSnapshot {
val (isConnected, entries) =
synchronized(lock) {
connected to byKey.values.sortedByDescending { it.postTimeMs }
}
return DeviceNotificationSnapshot(
enabled = enabled,
connected = isConnected,
notifications = entries,
)
}
}
class DeviceNotificationListenerService : NotificationListenerService() {
override fun onListenerConnected() {
super.onListenerConnected()
DeviceNotificationStore.setConnected(true)
refreshActiveNotifications()
}
override fun onListenerDisconnected() {
DeviceNotificationStore.setConnected(false)
super.onListenerDisconnected()
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn)
val entry = sbn?.toEntry() ?: return
DeviceNotificationStore.upsert(entry)
}
override fun onNotificationRemoved(sbn: StatusBarNotification?) {
super.onNotificationRemoved(sbn)
val key = sbn?.key ?: return
DeviceNotificationStore.remove(key)
}
private fun refreshActiveNotifications() {
val entries =
runCatching {
activeNotifications
?.mapNotNull { it.toEntry() }
?: emptyList()
}.getOrElse { emptyList() }
DeviceNotificationStore.replace(entries)
}
private fun StatusBarNotification.toEntry(): DeviceNotificationEntry {
val extras = notification.extras
val keyValue = key.takeIf { it.isNotBlank() } ?: "$packageName:$id:$postTime"
val title = sanitizeNotificationText(extras?.getCharSequence(Notification.EXTRA_TITLE))
val body =
sanitizeNotificationText(extras?.getCharSequence(Notification.EXTRA_BIG_TEXT))
?: sanitizeNotificationText(extras?.getCharSequence(Notification.EXTRA_TEXT))
val subText = sanitizeNotificationText(extras?.getCharSequence(Notification.EXTRA_SUB_TEXT))
return DeviceNotificationEntry(
key = keyValue,
packageName = packageName,
title = title,
text = body,
subText = subText,
category = notification.category?.trim()?.ifEmpty { null },
channelId = notification.channelId?.trim()?.ifEmpty { null },
postTimeMs = postTime,
isOngoing = isOngoing,
isClearable = isClearable,
)
}
companion object {
private fun serviceComponent(context: Context): ComponentName {
return ComponentName(context, DeviceNotificationListenerService::class.java)
}
fun isAccessEnabled(context: Context): Boolean {
val manager = context.getSystemService(NotificationManager::class.java) ?: return false
return manager.isNotificationListenerAccessGranted(serviceComponent(context))
}
fun snapshot(context: Context, enabled: Boolean = isAccessEnabled(context)): DeviceNotificationSnapshot {
return DeviceNotificationStore.snapshot(enabled = enabled)
}
fun requestServiceRebind(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return
}
runCatching {
NotificationListenerService.requestRebind(serviceComponent(context))
}
}
}
}

View File

@ -1,125 +0,0 @@
package ai.openclaw.android.node
import ai.openclaw.android.protocol.OpenClawCanvasA2UICommand
import ai.openclaw.android.protocol.OpenClawCanvasCommand
import ai.openclaw.android.protocol.OpenClawCameraCommand
import ai.openclaw.android.protocol.OpenClawDeviceCommand
import ai.openclaw.android.protocol.OpenClawLocationCommand
import ai.openclaw.android.protocol.OpenClawNotificationsCommand
import ai.openclaw.android.protocol.OpenClawScreenCommand
import ai.openclaw.android.protocol.OpenClawSmsCommand
enum class InvokeCommandAvailability {
Always,
CameraEnabled,
LocationEnabled,
SmsAvailable,
DebugBuild,
}
data class InvokeCommandSpec(
val name: String,
val requiresForeground: Boolean = false,
val availability: InvokeCommandAvailability = InvokeCommandAvailability.Always,
)
object InvokeCommandRegistry {
val all: List<InvokeCommandSpec> =
listOf(
InvokeCommandSpec(
name = OpenClawCanvasCommand.Present.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawCanvasCommand.Hide.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawCanvasCommand.Navigate.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawCanvasCommand.Eval.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawCanvasCommand.Snapshot.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawCanvasA2UICommand.Push.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawCanvasA2UICommand.PushJSONL.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawCanvasA2UICommand.Reset.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawScreenCommand.Record.rawValue,
requiresForeground = true,
),
InvokeCommandSpec(
name = OpenClawCameraCommand.Snap.rawValue,
requiresForeground = true,
availability = InvokeCommandAvailability.CameraEnabled,
),
InvokeCommandSpec(
name = OpenClawCameraCommand.Clip.rawValue,
requiresForeground = true,
availability = InvokeCommandAvailability.CameraEnabled,
),
InvokeCommandSpec(
name = OpenClawLocationCommand.Get.rawValue,
availability = InvokeCommandAvailability.LocationEnabled,
),
InvokeCommandSpec(
name = OpenClawDeviceCommand.Status.rawValue,
),
InvokeCommandSpec(
name = OpenClawDeviceCommand.Info.rawValue,
),
InvokeCommandSpec(
name = OpenClawNotificationsCommand.List.rawValue,
),
InvokeCommandSpec(
name = OpenClawSmsCommand.Send.rawValue,
availability = InvokeCommandAvailability.SmsAvailable,
),
InvokeCommandSpec(
name = "debug.logs",
availability = InvokeCommandAvailability.DebugBuild,
),
InvokeCommandSpec(
name = "debug.ed25519",
availability = InvokeCommandAvailability.DebugBuild,
),
InvokeCommandSpec(name = "app.update"),
)
private val byNameInternal: Map<String, InvokeCommandSpec> = all.associateBy { it.name }
fun find(command: String): InvokeCommandSpec? = byNameInternal[command]
fun advertisedCommands(
cameraEnabled: Boolean,
locationEnabled: Boolean,
smsAvailable: Boolean,
debugBuild: Boolean,
): List<String> {
return all
.filter { spec ->
when (spec.availability) {
InvokeCommandAvailability.Always -> true
InvokeCommandAvailability.CameraEnabled -> cameraEnabled
InvokeCommandAvailability.LocationEnabled -> locationEnabled
InvokeCommandAvailability.SmsAvailable -> smsAvailable
InvokeCommandAvailability.DebugBuild -> debugBuild
}
}
.map { it.name }
}
}

View File

@ -1,81 +0,0 @@
package ai.openclaw.android.node
import android.content.Context
import ai.openclaw.android.gateway.GatewaySession
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
internal interface NotificationsStateProvider {
fun readSnapshot(context: Context): DeviceNotificationSnapshot
fun requestServiceRebind(context: Context)
}
private object SystemNotificationsStateProvider : NotificationsStateProvider {
override fun readSnapshot(context: Context): DeviceNotificationSnapshot {
val enabled = DeviceNotificationListenerService.isAccessEnabled(context)
if (!enabled) {
return DeviceNotificationSnapshot(
enabled = false,
connected = false,
notifications = emptyList(),
)
}
return DeviceNotificationListenerService.snapshot(context, enabled = true)
}
override fun requestServiceRebind(context: Context) {
DeviceNotificationListenerService.requestServiceRebind(context)
}
}
class NotificationsHandler private constructor(
private val appContext: Context,
private val stateProvider: NotificationsStateProvider,
) {
constructor(appContext: Context) : this(appContext = appContext, stateProvider = SystemNotificationsStateProvider)
suspend fun handleNotificationsList(_paramsJson: String?): GatewaySession.InvokeResult {
val snapshot = stateProvider.readSnapshot(appContext)
if (snapshot.enabled && !snapshot.connected) {
stateProvider.requestServiceRebind(appContext)
}
return GatewaySession.InvokeResult.ok(snapshotPayloadJson(snapshot))
}
private fun snapshotPayloadJson(snapshot: DeviceNotificationSnapshot): String {
return buildJsonObject {
put("enabled", JsonPrimitive(snapshot.enabled))
put("connected", JsonPrimitive(snapshot.connected))
put("count", JsonPrimitive(snapshot.notifications.size))
put(
"notifications",
JsonArray(
snapshot.notifications.map { entry ->
buildJsonObject {
put("key", JsonPrimitive(entry.key))
put("packageName", JsonPrimitive(entry.packageName))
put("postTimeMs", JsonPrimitive(entry.postTimeMs))
put("isOngoing", JsonPrimitive(entry.isOngoing))
put("isClearable", JsonPrimitive(entry.isClearable))
entry.title?.let { put("title", JsonPrimitive(it)) }
entry.text?.let { put("text", JsonPrimitive(it)) }
entry.subText?.let { put("subText", JsonPrimitive(it)) }
entry.category?.let { put("category", JsonPrimitive(it)) }
entry.channelId?.let { put("channelId", JsonPrimitive(it)) }
}
},
),
)
}.toString()
}
companion object {
internal fun forTesting(
appContext: Context,
stateProvider: NotificationsStateProvider,
): NotificationsHandler = NotificationsHandler(appContext = appContext, stateProvider = stateProvider)
}
}

View File

@ -1,25 +0,0 @@
package ai.openclaw.android.node
import ai.openclaw.android.gateway.GatewaySession
class ScreenHandler(
private val screenRecorder: ScreenRecordManager,
private val setScreenRecordActive: (Boolean) -> Unit,
private val invokeErrorFromThrowable: (Throwable) -> Pair<String, String>,
) {
suspend fun handleScreenRecord(paramsJson: String?): GatewaySession.InvokeResult {
setScreenRecordActive(true)
try {
val res =
try {
screenRecorder.record(paramsJson)
} catch (err: Throwable) {
val (code, message) = invokeErrorFromThrowable(err)
return GatewaySession.InvokeResult.error(code = code, message = message)
}
return GatewaySession.InvokeResult.ok(res.payloadJson)
} finally {
setScreenRecordActive(false)
}
}
}

View File

@ -1,199 +0,0 @@
package ai.openclaw.android.node
import android.content.Context
import android.hardware.display.DisplayManager
import android.media.MediaRecorder
import android.media.projection.MediaProjectionManager
import android.os.Build
import android.util.Base64
import ai.openclaw.android.ScreenCaptureRequester
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File
import kotlin.math.roundToInt
class ScreenRecordManager(private val context: Context) {
data class Payload(val payloadJson: String)
@Volatile private var screenCaptureRequester: ScreenCaptureRequester? = null
@Volatile private var permissionRequester: ai.openclaw.android.PermissionRequester? = null
fun attachScreenCaptureRequester(requester: ScreenCaptureRequester) {
screenCaptureRequester = requester
}
fun attachPermissionRequester(requester: ai.openclaw.android.PermissionRequester) {
permissionRequester = requester
}
suspend fun record(paramsJson: String?): Payload =
withContext(Dispatchers.Default) {
val requester =
screenCaptureRequester
?: throw IllegalStateException(
"SCREEN_PERMISSION_REQUIRED: grant Screen Recording permission",
)
val durationMs = (parseDurationMs(paramsJson) ?: 10_000).coerceIn(250, 60_000)
val fps = (parseFps(paramsJson) ?: 10.0).coerceIn(1.0, 60.0)
val fpsInt = fps.roundToInt().coerceIn(1, 60)
val screenIndex = parseScreenIndex(paramsJson)
val includeAudio = parseIncludeAudio(paramsJson) ?: true
val format = parseString(paramsJson, key = "format")
if (format != null && format.lowercase() != "mp4") {
throw IllegalArgumentException("INVALID_REQUEST: screen format must be mp4")
}
if (screenIndex != null && screenIndex != 0) {
throw IllegalArgumentException("INVALID_REQUEST: screenIndex must be 0 on Android")
}
val capture = requester.requestCapture()
?: throw IllegalStateException(
"SCREEN_PERMISSION_REQUIRED: grant Screen Recording permission",
)
val mgr =
context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val projection = mgr.getMediaProjection(capture.resultCode, capture.data)
?: throw IllegalStateException("UNAVAILABLE: screen capture unavailable")
val metrics = context.resources.displayMetrics
val width = metrics.widthPixels
val height = metrics.heightPixels
val densityDpi = metrics.densityDpi
val file = File.createTempFile("openclaw-screen-", ".mp4")
if (includeAudio) ensureMicPermission()
val recorder = createMediaRecorder()
var virtualDisplay: android.hardware.display.VirtualDisplay? = null
try {
if (includeAudio) {
recorder.setAudioSource(MediaRecorder.AudioSource.MIC)
}
recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
if (includeAudio) {
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
recorder.setAudioChannels(1)
recorder.setAudioSamplingRate(44_100)
recorder.setAudioEncodingBitRate(96_000)
}
recorder.setVideoSize(width, height)
recorder.setVideoFrameRate(fpsInt)
recorder.setVideoEncodingBitRate(estimateBitrate(width, height, fpsInt))
recorder.setOutputFile(file.absolutePath)
recorder.prepare()
val surface = recorder.surface
virtualDisplay =
projection.createVirtualDisplay(
"openclaw-screen",
width,
height,
densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
surface,
null,
null,
)
recorder.start()
delay(durationMs.toLong())
} finally {
try {
recorder.stop()
} catch (_: Throwable) {
// ignore
}
recorder.reset()
recorder.release()
virtualDisplay?.release()
projection.stop()
}
val bytes = withContext(Dispatchers.IO) { file.readBytes() }
file.delete()
val base64 = Base64.encodeToString(bytes, Base64.NO_WRAP)
Payload(
"""{"format":"mp4","base64":"$base64","durationMs":$durationMs,"fps":$fpsInt,"screenIndex":0,"hasAudio":$includeAudio}""",
)
}
private fun createMediaRecorder(): MediaRecorder = MediaRecorder(context)
private suspend fun ensureMicPermission() {
val granted =
androidx.core.content.ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.RECORD_AUDIO,
) == android.content.pm.PackageManager.PERMISSION_GRANTED
if (granted) return
val requester =
permissionRequester
?: throw IllegalStateException("MIC_PERMISSION_REQUIRED: grant Microphone permission")
val results = requester.requestIfMissing(listOf(android.Manifest.permission.RECORD_AUDIO))
if (results[android.Manifest.permission.RECORD_AUDIO] != true) {
throw IllegalStateException("MIC_PERMISSION_REQUIRED: grant Microphone permission")
}
}
private fun parseDurationMs(paramsJson: String?): Int? =
parseNumber(paramsJson, key = "durationMs")?.toIntOrNull()
private fun parseFps(paramsJson: String?): Double? =
parseNumber(paramsJson, key = "fps")?.toDoubleOrNull()
private fun parseScreenIndex(paramsJson: String?): Int? =
parseNumber(paramsJson, key = "screenIndex")?.toIntOrNull()
private fun parseIncludeAudio(paramsJson: String?): Boolean? {
val raw = paramsJson ?: return null
val key = "\"includeAudio\""
val idx = raw.indexOf(key)
if (idx < 0) return null
val colon = raw.indexOf(':', idx + key.length)
if (colon < 0) return null
val tail = raw.substring(colon + 1).trimStart()
return when {
tail.startsWith("true") -> true
tail.startsWith("false") -> false
else -> null
}
}
private fun parseNumber(paramsJson: String?, key: String): String? {
val raw = paramsJson ?: return null
val needle = "\"$key\""
val idx = raw.indexOf(needle)
if (idx < 0) return null
val colon = raw.indexOf(':', idx + needle.length)
if (colon < 0) return null
val tail = raw.substring(colon + 1).trimStart()
return tail.takeWhile { it.isDigit() || it == '.' || it == '-' }
}
private fun parseString(paramsJson: String?, key: String): String? {
val raw = paramsJson ?: return null
val needle = "\"$key\""
val idx = raw.indexOf(needle)
if (idx < 0) return null
val colon = raw.indexOf(':', idx + needle.length)
if (colon < 0) return null
val tail = raw.substring(colon + 1).trimStart()
if (!tail.startsWith('\"')) return null
val rest = tail.drop(1)
val end = rest.indexOf('\"')
if (end < 0) return null
return rest.substring(0, end)
}
private fun estimateBitrate(width: Int, height: Int, fps: Int): Int {
val pixels = width.toLong() * height.toLong()
val raw = (pixels * fps.toLong() * 2L).toInt()
return raw.coerceIn(1_000_000, 12_000_000)
}
}

View File

@ -1,106 +0,0 @@
package ai.openclaw.android.ui
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import ai.openclaw.android.R
internal val mobileBackgroundGradient =
Brush.verticalGradient(
listOf(
Color(0xFFFFFFFF),
Color(0xFFF7F8FA),
Color(0xFFEFF1F5),
),
)
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 val mobileFontFamily =
FontFamily(
Font(resId = R.font.manrope_400_regular, weight = FontWeight.Normal),
Font(resId = R.font.manrope_500_medium, weight = FontWeight.Medium),
Font(resId = R.font.manrope_600_semibold, weight = FontWeight.SemiBold),
Font(resId = R.font.manrope_700_bold, weight = FontWeight.Bold),
)
internal val mobileTitle1 =
TextStyle(
fontFamily = mobileFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 24.sp,
lineHeight = 30.sp,
letterSpacing = (-0.5).sp,
)
internal val mobileTitle2 =
TextStyle(
fontFamily = mobileFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 20.sp,
lineHeight = 26.sp,
letterSpacing = (-0.3).sp,
)
internal val mobileHeadline =
TextStyle(
fontFamily = mobileFontFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp,
lineHeight = 22.sp,
letterSpacing = (-0.1).sp,
)
internal val mobileBody =
TextStyle(
fontFamily = mobileFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 15.sp,
lineHeight = 22.sp,
)
internal val mobileCallout =
TextStyle(
fontFamily = mobileFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
)
internal val mobileCaption1 =
TextStyle(
fontFamily = mobileFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.2.sp,
)
internal val mobileCaption2 =
TextStyle(
fontFamily = mobileFontFamily,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 14.sp,
letterSpacing = 0.4.sp,
)

View File

@ -1,605 +0,0 @@
package ai.openclaw.android.ui
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
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.fillMaxHeight
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
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
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.material.icons.Icons
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import ai.openclaw.android.BuildConfig
import ai.openclaw.android.LocationMode
import ai.openclaw.android.MainViewModel
@Composable
fun SettingsSheet(viewModel: MainViewModel) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val instanceId by viewModel.instanceId.collectAsState()
val displayName by viewModel.displayName.collectAsState()
val cameraEnabled by viewModel.cameraEnabled.collectAsState()
val locationMode by viewModel.locationMode.collectAsState()
val locationPreciseEnabled by viewModel.locationPreciseEnabled.collectAsState()
val preventSleep by viewModel.preventSleep.collectAsState()
val canvasDebugStatusEnabled by viewModel.canvasDebugStatusEnabled.collectAsState()
val listState = rememberLazyListState()
val deviceModel =
remember {
listOfNotNull(Build.MANUFACTURER, Build.MODEL)
.joinToString(" ")
.trim()
.ifEmpty { "Android" }
}
val appVersion =
remember {
val versionName = BuildConfig.VERSION_NAME.trim().ifEmpty { "dev" }
if (BuildConfig.DEBUG && !versionName.contains("dev", ignoreCase = true)) {
"$versionName-dev"
} else {
versionName
}
}
val listItemColors =
ListItemDefaults.colors(
containerColor = Color.Transparent,
headlineColor = mobileText,
supportingColor = mobileTextSecondary,
trailingIconColor = mobileTextSecondary,
leadingIconColor = mobileTextSecondary,
)
val permissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { perms ->
val cameraOk = perms[Manifest.permission.CAMERA] == true
viewModel.setCameraEnabled(cameraOk)
}
var pendingLocationMode by remember { mutableStateOf<LocationMode?>(null) }
var pendingPreciseToggle by remember { mutableStateOf(false) }
val locationPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { perms ->
val fineOk = perms[Manifest.permission.ACCESS_FINE_LOCATION] == true
val coarseOk = perms[Manifest.permission.ACCESS_COARSE_LOCATION] == true
val granted = fineOk || coarseOk
val requestedMode = pendingLocationMode
pendingLocationMode = null
if (pendingPreciseToggle) {
pendingPreciseToggle = false
viewModel.setLocationPreciseEnabled(fineOk)
return@rememberLauncherForActivityResult
}
if (!granted) {
viewModel.setLocationMode(LocationMode.Off)
return@rememberLauncherForActivityResult
}
if (requestedMode != null) {
viewModel.setLocationMode(requestedMode)
if (requestedMode == LocationMode.Always) {
val backgroundOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (!backgroundOk) {
openAppSettings(context)
}
}
}
}
var micPermissionGranted by
remember {
mutableStateOf(
ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) ==
PackageManager.PERMISSION_GRANTED,
)
}
val audioPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
micPermissionGranted = granted
}
val smsPermissionAvailable =
remember {
context.packageManager?.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) == true
}
var smsPermissionGranted by
remember {
mutableStateOf(
ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) ==
PackageManager.PERMISSION_GRANTED,
)
}
val smsPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
smsPermissionGranted = granted
viewModel.refreshGatewayConnection()
}
DisposableEffect(lifecycleOwner, context) {
val observer =
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
micPermissionGranted =
ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) ==
PackageManager.PERMISSION_GRANTED
smsPermissionGranted =
ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) ==
PackageManager.PERMISSION_GRANTED
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
fun setCameraEnabledChecked(checked: Boolean) {
if (!checked) {
viewModel.setCameraEnabled(false)
return
}
val cameraOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) ==
PackageManager.PERMISSION_GRANTED
if (cameraOk) {
viewModel.setCameraEnabled(true)
} else {
permissionLauncher.launch(arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))
}
}
fun requestLocationPermissions(targetMode: LocationMode) {
val fineOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
val coarseOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (fineOk || coarseOk) {
viewModel.setLocationMode(targetMode)
if (targetMode == LocationMode.Always) {
val backgroundOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (!backgroundOk) {
openAppSettings(context)
}
}
} else {
pendingLocationMode = targetMode
locationPermissionLauncher.launch(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION),
)
}
}
fun setPreciseLocationChecked(checked: Boolean) {
if (!checked) {
viewModel.setLocationPreciseEnabled(false)
return
}
val fineOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (fineOk) {
viewModel.setLocationPreciseEnabled(true)
} else {
pendingPreciseToggle = true
locationPermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))
}
}
Box(
modifier =
Modifier
.fillMaxSize()
.background(mobileBackgroundGradient),
) {
LazyColumn(
state = listState,
modifier =
Modifier
.fillMaxWidth()
.fillMaxHeight()
.imePadding()
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)),
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.
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",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
ListItem(
modifier = settingsRowModifier(),
colors = listItemColors,
headlineContent = { Text("Microphone permission", style = mobileHeadline) },
supportingContent = {
Text(
if (micPermissionGranted) {
"Granted. Use the Voice tab mic button to capture transcript."
} else {
"Required for Voice tab transcription."
},
style = mobileCallout,
)
},
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.",
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 = 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 = 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."
},
style = mobileCallout,
)
},
trailingContent = {
Button(
onClick = {
if (!smsPermissionAvailable) return@Button
if (smsPermissionGranted) {
openAppSettings(context)
} else {
smsPermissionLauncher.launch(Manifest.permission.SEND_SMS)
}
},
enabled = smsPermissionAvailable,
colors = settingsPrimaryButtonColors(),
shape = RoundedCornerShape(14.dp),
) {
Text(buttonLabel, style = mobileCallout.copy(fontWeight = FontWeight.Bold))
}
},
)
}
item { HorizontalDivider(color = mobileBorder) }
// Location
item {
Text(
"LOCATION",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
Column(modifier = settingsRowModifier(), verticalArrangement = Arrangement.spacedBy(0.dp)) {
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Off", style = mobileHeadline) },
supportingContent = { Text("Disable location sharing.", style = mobileCallout) },
trailingContent = {
RadioButton(
selected = locationMode == LocationMode.Off,
onClick = { viewModel.setLocationMode(LocationMode.Off) },
)
},
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("While Using", style = mobileHeadline) },
supportingContent = { Text("Only while OpenClaw is open.", style = mobileCallout) },
trailingContent = {
RadioButton(
selected = locationMode == LocationMode.WhileUsing,
onClick = { requestLocationPermissions(LocationMode.WhileUsing) },
)
},
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Always", style = mobileHeadline) },
supportingContent = { Text("Allow background location (requires system permission).", style = mobileCallout) },
trailingContent = {
RadioButton(
selected = locationMode == LocationMode.Always,
onClick = { requestLocationPermissions(LocationMode.Always) },
)
},
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Precise Location", style = mobileHeadline) },
supportingContent = { Text("Use precise GPS when available.", style = mobileCallout) },
trailingContent = {
Switch(
checked = locationPreciseEnabled,
onCheckedChange = ::setPreciseLocationChecked,
enabled = locationMode != LocationMode.Off,
)
},
)
}
}
item {
Text(
"Always may require Android Settings to allow background location.",
style = mobileCallout,
color = mobileTextSecondary,
)
}
item { HorizontalDivider(color = mobileBorder) }
// Screen
item {
Text(
"SCREEN",
style = mobileCaption1.copy(fontWeight = FontWeight.Bold, letterSpacing = 1.sp),
color = mobileAccent,
)
}
item {
ListItem(
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 = 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,
)
},
)
}
item { Spacer(modifier = Modifier.height(24.dp)) }
}
}
}
@Composable
private fun settingsTextFieldColors() =
OutlinedTextFieldDefaults.colors(
focusedContainerColor = mobileSurface,
unfocusedContainerColor = mobileSurface,
focusedBorderColor = mobileAccent,
unfocusedBorderColor = mobileBorder,
focusedTextColor = mobileText,
unfocusedTextColor = mobileText,
cursorColor = mobileAccent,
)
private fun settingsRowModifier() =
Modifier
.fillMaxWidth()
.border(width = 1.dp, color = mobileBorder, shape = RoundedCornerShape(14.dp))
.background(Color.White, RoundedCornerShape(14.dp))
@Composable
private fun settingsPrimaryButtonColors() =
ButtonDefaults.buttonColors(
containerColor = mobileAccent,
contentColor = Color.White,
disabledContainerColor = mobileAccent.copy(alpha = 0.45f),
disabledContentColor = Color.White.copy(alpha = 0.9f),
)
@Composable
private fun settingsDangerButtonColors() =
ButtonDefaults.buttonColors(
containerColor = mobileDanger,
contentColor = Color.White,
disabledContainerColor = mobileDanger.copy(alpha = 0.45f),
disabledContentColor = Color.White.copy(alpha = 0.9f),
)
private fun openAppSettings(context: Context) {
val intent =
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", context.packageName, null),
)
context.startActivity(intent)
}

View File

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
enum class CameraHudKind {
Photo,

View File

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
import android.content.Context
import android.os.Build

View File

@ -1,14 +1,14 @@
package ai.openclaw.android
package ai.openclaw.app
enum class LocationMode(val rawValue: String) {
Off("off"),
WhileUsing("whileUsing"),
Always("always"),
;
companion object {
fun fromRawValue(raw: String?): LocationMode {
val normalized = raw?.trim()?.lowercase()
if (normalized == "always") return WhileUsing
return entries.firstOrNull { it.rawValue.lowercase() == normalized } ?: Off
}
}

View File

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
import android.os.Bundle
import android.view.WindowManager
@ -11,25 +11,21 @@ import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import ai.openclaw.android.ui.RootScreen
import ai.openclaw.android.ui.OpenClawTheme
import ai.openclaw.app.ui.RootScreen
import ai.openclaw.app.ui.OpenClawTheme
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
private lateinit var permissionRequester: PermissionRequester
private lateinit var screenCaptureRequester: ScreenCaptureRequester
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
permissionRequester = PermissionRequester(this)
screenCaptureRequester = ScreenCaptureRequester(this)
viewModel.camera.attachLifecycleOwner(this)
viewModel.camera.attachPermissionRequester(permissionRequester)
viewModel.sms.attachPermissionRequester(permissionRequester)
viewModel.screenRecorder.attachScreenCaptureRequester(screenCaptureRequester)
viewModel.screenRecorder.attachPermissionRequester(permissionRequester)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {

View File

@ -1,14 +1,13 @@
package ai.openclaw.android
package ai.openclaw.app
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import ai.openclaw.android.gateway.GatewayEndpoint
import ai.openclaw.android.chat.OutgoingAttachment
import ai.openclaw.android.node.CameraCaptureManager
import ai.openclaw.android.node.CanvasController
import ai.openclaw.android.node.ScreenRecordManager
import ai.openclaw.android.node.SmsManager
import ai.openclaw.android.voice.VoiceConversationEntry
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.chat.OutgoingAttachment
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.flow.StateFlow
class MainViewModel(app: Application) : AndroidViewModel(app) {
@ -20,7 +19,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
val canvasRehydratePending: StateFlow<Boolean> = runtime.canvasRehydratePending
val canvasRehydrateErrorText: StateFlow<String?> = runtime.canvasRehydrateErrorText
val camera: CameraCaptureManager = runtime.camera
val screenRecorder: ScreenRecordManager = runtime.screenRecorder
val sms: SmsManager = runtime.sms
val gateways: StateFlow<List<GatewayEndpoint>> = runtime.gateways
@ -38,7 +36,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
val cameraHud: StateFlow<CameraHudState?> = runtime.cameraHud
val cameraFlashToken: StateFlow<Long> = runtime.cameraFlashToken
val screenRecordActive: StateFlow<Boolean> = runtime.screenRecordActive
val instanceId: StateFlow<String> = runtime.instanceId
val displayName: StateFlow<String> = runtime.displayName
@ -47,6 +44,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
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
@ -54,6 +52,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
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
@ -117,6 +116,10 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
runtime.setGatewayToken(value)
}
fun setGatewayBootstrapToken(value: String) {
runtime.setGatewayBootstrapToken(value)
}
fun setGatewayPassword(value: String) {
runtime.setGatewayPassword(value)
}
@ -129,10 +132,18 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
runtime.setCanvasDebugStatusEnabled(value)
}
fun setVoiceScreenActive(active: Boolean) {
runtime.setVoiceScreenActive(active)
}
fun setMicEnabled(enabled: Boolean) {
runtime.setMicEnabled(enabled)
}
fun setSpeakerEnabled(enabled: Boolean) {
runtime.setSpeakerEnabled(enabled)
}
fun refreshGatewayConnection() {
runtime.refreshGatewayConnection()
}
@ -165,6 +176,10 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
runtime.requestCanvasRehydrate(source = source, force = true)
}
fun refreshHomeCanvasOverviewIfConnected() {
runtime.refreshHomeCanvasOverviewIfConnected()
}
fun loadChat(sessionKey: String) {
runtime.loadChat(sessionKey)
}

View File

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
import android.app.Application
import android.os.StrictMode

View File

@ -1,17 +1,14 @@
package ai.openclaw.android
package ai.openclaw.app
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.app.PendingIntent
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -23,14 +20,13 @@ import kotlinx.coroutines.launch
class NodeForegroundService : Service() {
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private var notificationJob: Job? = null
private var lastRequiresMic = false
private var didStartForeground = false
override fun onCreate() {
super.onCreate()
ensureChannel()
val initial = buildNotification(title = "OpenClaw Node", text = "Starting…")
startForegroundWithTypes(notification = initial, requiresMic = false)
startForegroundWithTypes(notification = initial)
val runtime = (application as NodeApp).runtime
notificationJob =
@ -53,11 +49,8 @@ class NodeForegroundService : Service() {
}
val text = (server?.let { "$status · $it" } ?: status) + micSuffix
val requiresMic =
micEnabled && hasRecordAudioPermission()
startForegroundWithTypes(
notification = buildNotification(title = title, text = text),
requiresMic = requiresMic,
)
}
}
@ -135,35 +128,20 @@ class NodeForegroundService : Service() {
mgr.notify(NOTIFICATION_ID, notification)
}
private fun startForegroundWithTypes(notification: Notification, requiresMic: Boolean) {
if (didStartForeground && requiresMic == lastRequiresMic) {
private fun startForegroundWithTypes(notification: Notification) {
if (didStartForeground) {
updateNotification(notification)
return
}
lastRequiresMic = requiresMic
val types =
if (requiresMic) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
} else {
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
}
startForeground(NOTIFICATION_ID, notification, types)
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
didStartForeground = true
}
private fun hasRecordAudioPermission(): Boolean {
return (
ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) ==
PackageManager.PERMISSION_GRANTED
)
}
companion object {
private const val CHANNEL_ID = "connection"
private const val NOTIFICATION_ID = 1
private const val ACTION_STOP = "ai.openclaw.android.action.STOP"
private const val ACTION_STOP = "ai.openclaw.app.action.STOP"
fun start(context: Context) {
val intent = Intent(context, NodeForegroundService::class.java)

View File

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
import android.Manifest
import android.content.Context
@ -6,21 +6,22 @@ import android.content.pm.PackageManager
import android.os.SystemClock
import android.util.Log
import androidx.core.content.ContextCompat
import ai.openclaw.android.chat.ChatController
import ai.openclaw.android.chat.ChatMessage
import ai.openclaw.android.chat.ChatPendingToolCall
import ai.openclaw.android.chat.ChatSessionEntry
import ai.openclaw.android.chat.OutgoingAttachment
import ai.openclaw.android.gateway.DeviceAuthStore
import ai.openclaw.android.gateway.DeviceIdentityStore
import ai.openclaw.android.gateway.GatewayDiscovery
import ai.openclaw.android.gateway.GatewayEndpoint
import ai.openclaw.android.gateway.GatewaySession
import ai.openclaw.android.gateway.probeGatewayTlsFingerprint
import ai.openclaw.android.node.*
import ai.openclaw.android.protocol.OpenClawCanvasA2UIAction
import ai.openclaw.android.voice.MicCaptureManager
import ai.openclaw.android.voice.VoiceConversationEntry
import ai.openclaw.app.chat.ChatController
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.DeviceAuthStore
import ai.openclaw.app.gateway.DeviceIdentityStore
import ai.openclaw.app.gateway.GatewayDiscovery
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.gateway.GatewaySession
import ai.openclaw.app.gateway.probeGatewayTlsFingerprint
import ai.openclaw.app.node.*
import ai.openclaw.app.protocol.OpenClawCanvasA2UIAction
import ai.openclaw.app.voice.MicCaptureManager
import ai.openclaw.app.voice.TalkModeManager
import ai.openclaw.app.voice.VoiceConversationEntry
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -32,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
@ -49,7 +52,6 @@ class NodeRuntime(context: Context) {
val canvas = CanvasController()
val camera = CameraCaptureManager(appContext)
val location = LocationCaptureManager(appContext)
val screenRecorder = ScreenRecordManager(appContext)
val sms = SmsManager(appContext)
private val json = Json { ignoreUnknownKeys = true }
@ -65,8 +67,6 @@ class NodeRuntime(context: Context) {
private val cameraHandler: CameraHandler = CameraHandler(
appContext = appContext,
camera = camera,
prefs = prefs,
connectedEndpoint = { connectedEndpoint },
externalAudioCaptureActive = externalAudioCaptureActive,
showCameraHud = ::showCameraHud,
triggerCameraFlash = ::triggerCameraFlash,
@ -78,17 +78,11 @@ class NodeRuntime(context: Context) {
identityStore = identityStore,
)
private val appUpdateHandler: AppUpdateHandler = AppUpdateHandler(
appContext = appContext,
connectedEndpoint = { connectedEndpoint },
)
private val locationHandler: LocationHandler = LocationHandler(
appContext = appContext,
location = location,
json = json,
isForeground = { _isForeground.value },
locationMode = { locationMode.value },
locationPreciseEnabled = { locationPreciseEnabled.value },
)
@ -100,10 +94,28 @@ class NodeRuntime(context: Context) {
appContext = appContext,
)
private val screenHandler: ScreenHandler = ScreenHandler(
screenRecorder = screenRecorder,
setScreenRecordActive = { _screenRecordActive.value = it },
invokeErrorFromThrowable = { invokeErrorFromThrowable(it) },
private val systemHandler: SystemHandler = SystemHandler(
appContext = appContext,
)
private val photosHandler: PhotosHandler = PhotosHandler(
appContext = appContext,
)
private val contactsHandler: ContactsHandler = ContactsHandler(
appContext = appContext,
)
private val calendarHandler: CalendarHandler = CalendarHandler(
appContext = appContext,
)
private val callLogHandler: CallLogHandler = CallLogHandler(
appContext = appContext,
)
private val motionHandler: MotionHandler = MotionHandler(
appContext = appContext,
)
private val smsHandlerImpl: SmsHandler = SmsHandler(
@ -122,6 +134,8 @@ class NodeRuntime(context: Context) {
cameraEnabled = { cameraEnabled.value },
locationMode = { locationMode.value },
voiceWakeMode = { VoiceWakeMode.Off },
motionActivityAvailable = { motionHandler.isActivityAvailable() },
motionPedometerAvailable = { motionHandler.isPedometerAvailable() },
smsAvailable = { sms.canSendSms() },
hasRecordAudioPermission = { hasRecordAudioPermission() },
manualTls = { manualTls.value },
@ -133,22 +147,29 @@ class NodeRuntime(context: Context) {
locationHandler = locationHandler,
deviceHandler = deviceHandler,
notificationsHandler = notificationsHandler,
screenHandler = screenHandler,
systemHandler = systemHandler,
photosHandler = photosHandler,
contactsHandler = contactsHandler,
calendarHandler = calendarHandler,
motionHandler = motionHandler,
smsHandler = smsHandlerImpl,
a2uiHandler = a2uiHandler,
debugHandler = debugHandler,
appUpdateHandler = appUpdateHandler,
callLogHandler = callLogHandler,
isForeground = { _isForeground.value },
cameraEnabled = { cameraEnabled.value },
locationEnabled = { locationMode.value != LocationMode.Off },
smsAvailable = { sms.canSendSms() },
debugBuild = { BuildConfig.DEBUG },
refreshNodeCanvasCapability = { nodeSession.refreshNodeCanvasCapability() },
onCanvasA2uiPush = {
_canvasA2uiHydrated.value = true
_canvasRehydratePending.value = false
_canvasRehydrateErrorText.value = null
},
onCanvasA2uiReset = { _canvasA2uiHydrated.value = false },
motionActivityAvailable = { motionHandler.isActivityAvailable() },
motionPedometerAvailable = { motionHandler.isPedometerAvailable() },
)
data class GatewayTrustPrompt(
@ -177,9 +198,6 @@ class NodeRuntime(context: Context) {
private val _cameraFlashToken = MutableStateFlow(0L)
val cameraFlashToken: StateFlow<Long> = _cameraFlashToken.asStateFlow()
private val _screenRecordActive = MutableStateFlow(false)
val screenRecordActive: StateFlow<Boolean> = _screenRecordActive.asStateFlow()
private val _canvasA2uiHydrated = MutableStateFlow(false)
val canvasA2uiHydrated: StateFlow<Boolean> = _canvasA2uiHydrated.asStateFlow()
private val _canvasRehydratePending = MutableStateFlow(false)
@ -199,7 +217,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
@ -220,7 +239,12 @@ class NodeRuntime(context: Context) {
applyMainSessionKey(mainSessionKey)
updateStatus()
micCapture.onGatewayConnectionChanged(true)
scope.launch { refreshBrandingFromGateway() }
scope.launch {
refreshHomeCanvasOverviewIfConnected()
if (voiceReplySpeakerLazy.isInitialized()) {
voiceReplySpeaker.refreshConfig()
}
}
},
onDisconnected = { message ->
operatorConnected = false
@ -254,7 +278,7 @@ class NodeRuntime(context: Context) {
_canvasRehydratePending.value = false
_canvasRehydrateErrorText.value = null
updateStatus()
maybeNavigateToA2uiOnConnect()
showLocalCanvasOnConnect()
},
onDisconnected = { message ->
_nodeConnected.value = false
@ -275,6 +299,14 @@ class NodeRuntime(context: Context) {
},
)
init {
DeviceNotificationListenerService.setNodeEventSink { event, payloadJson ->
scope.launch {
nodeSession.sendNodeEvent(event = event, payloadJson = payloadJson)
}
}
}
private val chat: ChatController =
ChatController(
scope = scope,
@ -282,12 +314,31 @@ class NodeRuntime(context: Context) {
json = json,
supportsChatSubscribe = false,
)
private val voiceReplySpeakerLazy: Lazy<TalkModeManager> = lazy {
// Reuse the existing TalkMode speech engine (ElevenLabs + deterministic system-TTS fallback)
// without enabling the legacy talk capture loop.
TalkModeManager(
context = appContext,
scope = scope,
session = operatorSession,
supportsChatSubscribe = false,
isConnected = { operatorConnected },
).also { speaker ->
speaker.setPlaybackEnabled(prefs.speakerEnabled.value)
}
}
private val voiceReplySpeaker: TalkModeManager
get() = voiceReplySpeakerLazy.value
private val micCapture: MicCaptureManager by lazy {
MicCaptureManager(
context = appContext,
scope = scope,
sendToGateway = { message ->
sendToGateway = { message, onRunIdKnown ->
val idempotencyKey = UUID.randomUUID().toString()
// Notify MicCaptureManager of the idempotency key *before* the network
// call so pendingRunId is set before any chat events can arrive.
onRunIdKnown(idempotencyKey)
val params =
buildJsonObject {
put("sessionKey", JsonPrimitive(resolveMainSessionKey()))
@ -299,6 +350,13 @@ class NodeRuntime(context: Context) {
val response = operatorSession.request("chat.send", params.toString())
parseChatSendRunId(response) ?: idempotencyKey
},
speakAssistantReply = { text ->
// Skip if TalkModeManager is handling TTS (ttsOnAllResponses) to avoid
// double-speaking the same assistant reply from both pipelines.
if (!talkMode.ttsOnAllResponses) {
voiceReplySpeaker.speakAssistantReply(text)
}
},
)
}
@ -314,6 +372,9 @@ class NodeRuntime(context: Context) {
val micEnabled: StateFlow<Boolean>
get() = micCapture.micEnabled
val micCooldown: StateFlow<Boolean>
get() = micCapture.micCooldown
val micQueuedMessages: StateFlow<List<String>>
get() = micCapture.queuedMessages
@ -326,12 +387,24 @@ class NodeRuntime(context: Context) {
val micIsSending: StateFlow<Boolean>
get() = micCapture.isSending
private val talkMode: TalkModeManager by lazy {
TalkModeManager(
context = appContext,
scope = scope,
session = operatorSession,
supportsChatSubscribe = true,
isConnected = { operatorConnected },
)
}
private fun applyMainSessionKey(candidate: String?) {
val trimmed = normalizeMainKey(candidate) ?: return
if (isCanonicalMainSessionKey(_mainSessionKey.value)) return
if (_mainSessionKey.value == trimmed) return
_mainSessionKey.value = trimmed
talkMode.setMainSessionKey(trimmed)
chat.applyMainSessionKey(trimmed)
updateHomeCanvasState()
}
private fun updateStatus() {
@ -351,6 +424,7 @@ class NodeRuntime(context: Context) {
operator.isNotBlank() && operator != "Offline" -> operator
else -> node
}
updateHomeCanvasState()
}
private fun resolveMainSessionKey(): String {
@ -358,23 +432,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) {
@ -439,6 +521,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
@ -468,7 +551,14 @@ class NodeRuntime(context: Context) {
scope.launch {
prefs.talkEnabled.collect { enabled ->
// MicCaptureManager handles STT + send to gateway.
// TalkModeManager plays TTS on assistant responses.
micCapture.setMicEnabled(enabled)
if (enabled) {
// Mic on = user is on voice screen and wants TTS responses.
talkMode.ttsOnAllResponses = true
scope.launch { talkMode.ensureChatSubscribed() }
}
externalAudioCaptureActive.value = enabled
}
}
@ -530,10 +620,15 @@ class NodeRuntime(context: Context) {
canvas.setDebugStatus(status, server ?: remote)
}
}
updateHomeCanvasState()
}
fun setForeground(value: Boolean) {
_isForeground.value = value
if (!value) {
stopActiveVoiceSession()
}
}
fun setDisplayName(value: String) {
@ -576,12 +671,45 @@ class NodeRuntime(context: Context) {
prefs.setCanvasDebugStatusEnabled(value)
}
fun setVoiceScreenActive(active: Boolean) {
if (!active) {
stopActiveVoiceSession()
}
// Don't re-enable on active=true; mic toggle drives that
}
fun setMicEnabled(value: Boolean) {
prefs.setTalkEnabled(value)
if (value) {
// Tapping mic on interrupts any active TTS (barge-in)
talkMode.stopTts()
talkMode.ttsOnAllResponses = true
scope.launch { talkMode.ensureChatSubscribed() }
}
micCapture.setMicEnabled(value)
externalAudioCaptureActive.value = value
}
val speakerEnabled: StateFlow<Boolean>
get() = prefs.speakerEnabled
fun setSpeakerEnabled(value: Boolean) {
prefs.setSpeakerEnabled(value)
if (voiceReplySpeakerLazy.isInitialized()) {
voiceReplySpeaker.setPlaybackEnabled(value)
}
// Keep TalkMode in sync so speaker mute works when ttsOnAllResponses is active.
talkMode.setPlaybackEnabled(value)
}
private fun stopActiveVoiceSession() {
talkMode.ttsOnAllResponses = false
talkMode.stopTts()
micCapture.setMicEnabled(false)
prefs.setTalkEnabled(false)
externalAudioCaptureActive.value = false
}
fun refreshGatewayConnection() {
val endpoint =
connectedEndpoint ?: run {
@ -591,10 +719,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()
}
@ -619,9 +762,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() {
@ -763,6 +921,7 @@ class NodeRuntime(context: Context) {
private fun handleGatewayEvent(event: String, payloadJson: String?) {
micCapture.handleGatewayEvent(event, payloadJson)
talkMode.handleGatewayEvent(event, payloadJson)
chat.handleGatewayEvent(event, payloadJson)
}
@ -789,11 +948,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()
@ -812,3 +1137,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

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
import android.content.pm.PackageManager
import android.content.Intent

View File

@ -1,6 +1,6 @@
@file:Suppress("DEPRECATION")
package ai.openclaw.android
package ai.openclaw.app
import android.content.Context
import android.content.SharedPreferences
@ -15,10 +15,14 @@ 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"
private const val locationModeKey = "location.enabledMode"
private const val voiceWakeModeKey = "voiceWake.mode"
private const val plainPrefsName = "openclaw.node"
private const val securePrefsName = "openclaw.node.secure"
@ -34,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
@ -46,8 +50,7 @@ class SecurePrefs(context: Context) {
private val _cameraEnabled = MutableStateFlow(plainPrefs.getBoolean("camera.enabled", true))
val cameraEnabled: StateFlow<Boolean> = _cameraEnabled
private val _locationMode =
MutableStateFlow(LocationMode.fromRawValue(plainPrefs.getString("location.enabledMode", "off")))
private val _locationMode = MutableStateFlow(loadLocationMode())
val locationMode: StateFlow<LocationMode> = _locationMode
private val _locationPreciseEnabled =
@ -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
@ -99,6 +105,9 @@ class SecurePrefs(context: Context) {
private val _talkEnabled = MutableStateFlow(plainPrefs.getBoolean("talk.enabled", false))
val talkEnabled: StateFlow<Boolean> = _talkEnabled
private val _speakerEnabled = MutableStateFlow(plainPrefs.getBoolean("voice.speakerEnabled", true))
val speakerEnabled: StateFlow<Boolean> = _speakerEnabled
fun setLastDiscoveredStableId(value: String) {
val trimmed = value.trim()
plainPrefs.edit { putString("gateway.lastDiscoveredStableID", trimmed) }
@ -117,7 +126,7 @@ class SecurePrefs(context: Context) {
}
fun setLocationMode(mode: LocationMode) {
plainPrefs.edit { putString("location.enabledMode", mode.rawValue) }
plainPrefs.edit { putString(locationModeKey, mode.rawValue) }
_locationMode.value = mode
}
@ -162,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
@ -190,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()
@ -270,6 +303,11 @@ class SecurePrefs(context: Context) {
_talkEnabled.value = value
}
fun setSpeakerEnabled(value: Boolean) {
plainPrefs.edit { putBoolean("voice.speakerEnabled", value) }
_speakerEnabled.value = value
}
private fun loadVoiceWakeMode(): VoiceWakeMode {
val raw = plainPrefs.getString(voiceWakeModeKey, null)
val resolved = VoiceWakeMode.fromRawValue(raw)
@ -282,6 +320,15 @@ class SecurePrefs(context: Context) {
return resolved
}
private fun loadLocationMode(): LocationMode {
val raw = plainPrefs.getString(locationModeKey, "off")
val resolved = LocationMode.fromRawValue(raw)
if (raw?.trim()?.lowercase() == "always") {
plainPrefs.edit { putString(locationModeKey, resolved.rawValue) }
}
return resolved
}
private fun loadWakeWords(): List<String> {
val raw = plainPrefs.getString("voiceWake.triggerWords", null)?.trim()
if (raw.isNullOrEmpty()) return defaultWakeWords

View File

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
internal fun normalizeMainKey(raw: String?): String {
val trimmed = raw?.trim()

View File

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
enum class VoiceWakeMode(val rawValue: String) {
Off("off"),

View File

@ -1,4 +1,4 @@
package ai.openclaw.android
package ai.openclaw.app
object WakeWords {
const val maxWords: Int = 32

View File

@ -1,6 +1,6 @@
package ai.openclaw.android.chat
package ai.openclaw.app.chat
import ai.openclaw.android.gateway.GatewaySession
import ai.openclaw.app.gateway.GatewaySession
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.CoroutineScope
@ -311,17 +311,14 @@ class ChatController(
if (!sessionKey.isNullOrEmpty() && sessionKey != _sessionKey.value) return
val runId = payload["runId"].asStringOrNull()
if (runId != null) {
val isPending =
synchronized(pendingRuns) {
pendingRuns.contains(runId)
}
if (!isPending) return
}
val isPending =
if (runId != null) synchronized(pendingRuns) { pendingRuns.contains(runId) } else true
val state = payload["state"].asStringOrNull()
when (state) {
"delta" -> {
// Only show streaming text for runs we initiated
if (!isPending) return
val text = parseAssistantDeltaText(payload)
if (!text.isNullOrEmpty()) {
_streamingAssistantText.value = text

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.chat
package ai.openclaw.app.chat
data class ChatMessage(
val id: String,

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
object BonjourEscapes {
fun decode(input: String): String {

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
internal object DeviceAuthPayload {
fun buildV3(

View File

@ -1,10 +1,11 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
import ai.openclaw.android.SecurePrefs
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

@ -1,4 +1,4 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
import android.content.Context
import android.util.Base64

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
import android.content.Context
import android.net.ConnectivityManager

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
data class GatewayEndpoint(
val stableId: String,

View File

@ -1,3 +1,3 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
const val GATEWAY_PROTOCOL_VERSION = 3

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
import android.util.Log
import java.util.Locale
@ -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()
}
@ -173,11 +216,53 @@ class GatewaySession(
throw IllegalStateException("${err?.code ?: "UNAVAILABLE"}: ${err?.message ?: "request failed"}")
}
suspend fun refreshNodeCanvasCapability(timeoutMs: Long = 8_000): Boolean {
val conn = currentConnection ?: return false
val response =
try {
conn.request(
"node.canvas.capability.refresh",
params = buildJsonObject {},
timeoutMs = timeoutMs,
)
} catch (err: Throwable) {
Log.w("OpenClawGateway", "node.canvas.capability.refresh failed: ${err.message ?: err::class.java.simpleName}")
return false
}
if (!response.ok) {
val err = response.error
Log.w(
"OpenClawGateway",
"node.canvas.capability.refresh rejected: ${err?.code ?: "UNAVAILABLE"}: ${err?.message ?: "request failed"}",
)
return false
}
val payloadObj = response.payloadJson?.let(::parseJsonOrNull)?.asObjectOrNull()
val refreshedCapability = payloadObj?.get("canvasCapability").asStringOrNull()?.trim().orEmpty()
if (refreshedCapability.isEmpty()) {
Log.w("OpenClawGateway", "node.canvas.capability.refresh missing canvasCapability")
return false
}
val scopedCanvasHostUrl = canvasHostUrl?.trim().orEmpty()
if (scopedCanvasHostUrl.isEmpty()) {
Log.w("OpenClawGateway", "node.canvas.capability.refresh missing local canvasHostUrl")
return false
}
val refreshedUrl = replaceCanvasCapabilityInScopedHostUrl(scopedCanvasHostUrl, refreshedCapability)
if (refreshedUrl == null) {
Log.w("OpenClawGateway", "node.canvas.capability.refresh unable to rewrite scoped canvas URL")
return false
}
canvasHostUrl = refreshedUrl
return true
}
private data class RpcResponse(val id: String, val ok: Boolean, val payloadJson: String?, val error: ErrorShape?)
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?,
@ -303,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)
@ -320,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()
@ -339,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()
@ -356,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
}
@ -379,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,
@ -442,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))
}
@ -501,11 +634,16 @@ class GatewaySession(
} catch (err: Throwable) {
invokeErrorFromThrowable(err)
}
sendInvokeResult(id, nodeId, result)
sendInvokeResult(id, nodeId, result, timeoutMs)
}
}
private suspend fun sendInvokeResult(id: String, nodeId: String, result: InvokeResult) {
private suspend fun sendInvokeResult(
id: String,
nodeId: String,
result: InvokeResult,
invokeTimeoutMs: Long?,
) {
val parsedPayload = result.payloadJson?.let { parseJsonOrNull(it) }
val params =
buildJsonObject {
@ -527,10 +665,14 @@ class GatewaySession(
)
}
}
val ackTimeoutMs = resolveInvokeResultAckTimeoutMs(invokeTimeoutMs)
try {
request("node.invoke.result", params, timeoutMs = 15_000)
request("node.invoke.result", params, timeoutMs = ackTimeoutMs)
} catch (err: Throwable) {
Log.w(loggerTag, "node.invoke.result failed: ${err.message ?: err::class.java.simpleName}")
Log.w(
loggerTag,
"node.invoke.result failed (ackTimeoutMs=$ackTimeoutMs): ${err.message ?: err::class.java.simpleName}",
)
}
}
@ -557,6 +699,10 @@ class GatewaySession(
delay(250)
continue
}
if (reconnectPausedForAuthFailure) {
delay(250)
continue
}
try {
onDisconnected(if (attempt == 0) "Connecting…" else "Reconnecting…")
@ -565,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)
}
@ -572,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()
@ -648,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
@ -687,3 +942,24 @@ private fun parseJsonOrNull(payload: String): JsonElement? {
null
}
}
internal fun replaceCanvasCapabilityInScopedHostUrl(
scopedUrl: String,
capability: String,
): String? {
val marker = "/__openclaw__/cap/"
val markerStart = scopedUrl.indexOf(marker)
if (markerStart < 0) return null
val capabilityStart = markerStart + marker.length
val slashEnd = scopedUrl.indexOf("/", capabilityStart).takeIf { it >= 0 }
val queryEnd = scopedUrl.indexOf("?", capabilityStart).takeIf { it >= 0 }
val fragmentEnd = scopedUrl.indexOf("#", capabilityStart).takeIf { it >= 0 }
val capabilityEnd = listOfNotNull(slashEnd, queryEnd, fragmentEnd).minOrNull() ?: scopedUrl.length
if (capabilityEnd <= capabilityStart) return null
return scopedUrl.substring(0, capabilityStart) + capability + scopedUrl.substring(capabilityEnd)
}
internal fun resolveInvokeResultAckTimeoutMs(invokeTimeoutMs: Long?): Long {
val normalized = invokeTimeoutMs?.takeIf { it > 0L } ?: 15_000L
return normalized.coerceIn(15_000L, 120_000L)
}

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
import android.annotation.SuppressLint
import kotlinx.coroutines.Dispatchers

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.gateway
package ai.openclaw.app.gateway
data class ParsedInvokeError(
val code: String,

View File

@ -1,6 +1,6 @@
package ai.openclaw.android.node
package ai.openclaw.app.node
import ai.openclaw.android.gateway.GatewaySession
import ai.openclaw.app.gateway.GatewaySession
import kotlinx.coroutines.delay
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray

View File

@ -0,0 +1,384 @@
package ai.openclaw.app.node
import android.Manifest
import android.content.ContentResolver
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.provider.CalendarContract
import androidx.core.content.ContextCompat
import ai.openclaw.app.gateway.GatewaySession
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.TimeZone
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
private const val DEFAULT_CALENDAR_LIMIT = 50
internal data class CalendarEventsRequest(
val startMs: Long,
val endMs: Long,
val limit: Int,
)
internal data class CalendarAddRequest(
val title: String,
val startMs: Long,
val endMs: Long,
val isAllDay: Boolean,
val location: String?,
val notes: String?,
val calendarId: Long?,
val calendarTitle: String?,
)
internal data class CalendarEventRecord(
val identifier: String,
val title: String,
val startISO: String,
val endISO: String,
val isAllDay: Boolean,
val location: String?,
val calendarTitle: String?,
)
internal interface CalendarDataSource {
fun hasReadPermission(context: Context): Boolean
fun hasWritePermission(context: Context): Boolean
fun events(context: Context, request: CalendarEventsRequest): List<CalendarEventRecord>
fun add(context: Context, request: CalendarAddRequest): CalendarEventRecord
}
private object SystemCalendarDataSource : CalendarDataSource {
override fun hasReadPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
}
override fun hasWritePermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
}
override fun events(context: Context, request: CalendarEventsRequest): List<CalendarEventRecord> {
val resolver = context.contentResolver
val builder = CalendarContract.Instances.CONTENT_URI.buildUpon()
ContentUris.appendId(builder, request.startMs)
ContentUris.appendId(builder, request.endMs)
val projection =
arrayOf(
CalendarContract.Instances.EVENT_ID,
CalendarContract.Instances.TITLE,
CalendarContract.Instances.BEGIN,
CalendarContract.Instances.END,
CalendarContract.Instances.ALL_DAY,
CalendarContract.Instances.EVENT_LOCATION,
CalendarContract.Instances.CALENDAR_DISPLAY_NAME,
)
val sortOrder = "${CalendarContract.Instances.BEGIN} ASC LIMIT ${request.limit}"
resolver.query(builder.build(), projection, null, null, sortOrder).use { cursor ->
if (cursor == null) return emptyList()
val out = mutableListOf<CalendarEventRecord>()
while (cursor.moveToNext() && out.size < request.limit) {
val id = cursor.getLong(0)
val title = cursor.getString(1)?.trim().orEmpty().ifEmpty { "(untitled)" }
val beginMs = cursor.getLong(2)
val endMs = cursor.getLong(3)
val isAllDay = cursor.getInt(4) == 1
val location = cursor.getString(5)?.trim()?.ifEmpty { null }
val calendarTitle = cursor.getString(6)?.trim()?.ifEmpty { null }
out +=
CalendarEventRecord(
identifier = id.toString(),
title = title,
startISO = Instant.ofEpochMilli(beginMs).toString(),
endISO = Instant.ofEpochMilli(endMs).toString(),
isAllDay = isAllDay,
location = location,
calendarTitle = calendarTitle,
)
}
return out
}
}
override fun add(context: Context, request: CalendarAddRequest): CalendarEventRecord {
val resolver = context.contentResolver
val resolvedCalendarId = resolveCalendarId(resolver, request.calendarId, request.calendarTitle)
val values =
ContentValues().apply {
put(CalendarContract.Events.CALENDAR_ID, resolvedCalendarId)
put(CalendarContract.Events.TITLE, request.title)
put(CalendarContract.Events.DTSTART, request.startMs)
put(CalendarContract.Events.DTEND, request.endMs)
put(CalendarContract.Events.ALL_DAY, if (request.isAllDay) 1 else 0)
put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().id)
request.location?.let { put(CalendarContract.Events.EVENT_LOCATION, it) }
request.notes?.let { put(CalendarContract.Events.DESCRIPTION, it) }
}
val uri = resolver.insert(CalendarContract.Events.CONTENT_URI, values)
?: throw IllegalStateException("calendar insert failed")
val eventId = uri.lastPathSegment?.toLongOrNull()
?: throw IllegalStateException("calendar insert failed")
return loadEventById(resolver, eventId)
?: throw IllegalStateException("calendar insert failed")
}
private fun resolveCalendarId(
resolver: ContentResolver,
calendarId: Long?,
calendarTitle: String?,
): Long {
if (calendarId != null) {
if (calendarExists(resolver, calendarId)) return calendarId
throw IllegalArgumentException("CALENDAR_NOT_FOUND: no calendar id $calendarId")
}
if (!calendarTitle.isNullOrEmpty()) {
findCalendarByTitle(resolver, calendarTitle)?.let { return it }
throw IllegalArgumentException("CALENDAR_NOT_FOUND: no calendar named $calendarTitle")
}
findDefaultCalendarId(resolver)?.let { return it }
throw IllegalArgumentException("CALENDAR_NOT_FOUND: no default calendar")
}
private fun calendarExists(resolver: ContentResolver, id: Long): Boolean {
val projection = arrayOf(CalendarContract.Calendars._ID)
resolver.query(
CalendarContract.Calendars.CONTENT_URI,
projection,
"${CalendarContract.Calendars._ID}=?",
arrayOf(id.toString()),
null,
).use { cursor ->
return cursor != null && cursor.moveToFirst()
}
}
private fun findCalendarByTitle(resolver: ContentResolver, title: String): Long? {
val projection = arrayOf(CalendarContract.Calendars._ID)
resolver.query(
CalendarContract.Calendars.CONTENT_URI,
projection,
"${CalendarContract.Calendars.CALENDAR_DISPLAY_NAME}=?",
arrayOf(title),
"${CalendarContract.Calendars.IS_PRIMARY} DESC",
).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) return null
return cursor.getLong(0)
}
}
private fun findDefaultCalendarId(resolver: ContentResolver): Long? {
val projection = arrayOf(CalendarContract.Calendars._ID)
resolver.query(
CalendarContract.Calendars.CONTENT_URI,
projection,
"${CalendarContract.Calendars.VISIBLE}=1",
null,
"${CalendarContract.Calendars.IS_PRIMARY} DESC, ${CalendarContract.Calendars._ID} ASC",
).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) return null
return cursor.getLong(0)
}
}
private fun loadEventById(
resolver: ContentResolver,
eventId: Long,
): CalendarEventRecord? {
val projection =
arrayOf(
CalendarContract.Events._ID,
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART,
CalendarContract.Events.DTEND,
CalendarContract.Events.ALL_DAY,
CalendarContract.Events.EVENT_LOCATION,
CalendarContract.Events.CALENDAR_DISPLAY_NAME,
)
resolver.query(
CalendarContract.Events.CONTENT_URI,
projection,
"${CalendarContract.Events._ID}=?",
arrayOf(eventId.toString()),
null,
).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) return null
return CalendarEventRecord(
identifier = cursor.getLong(0).toString(),
title = cursor.getString(1)?.trim().orEmpty().ifEmpty { "(untitled)" },
startISO = Instant.ofEpochMilli(cursor.getLong(2)).toString(),
endISO = Instant.ofEpochMilli(cursor.getLong(3)).toString(),
isAllDay = cursor.getInt(4) == 1,
location = cursor.getString(5)?.trim()?.ifEmpty { null },
calendarTitle = cursor.getString(6)?.trim()?.ifEmpty { null },
)
}
}
}
class CalendarHandler private constructor(
private val appContext: Context,
private val dataSource: CalendarDataSource,
) {
constructor(appContext: Context) : this(appContext = appContext, dataSource = SystemCalendarDataSource)
fun handleCalendarEvents(paramsJson: String?): GatewaySession.InvokeResult {
if (!dataSource.hasReadPermission(appContext)) {
return GatewaySession.InvokeResult.error(
code = "CALENDAR_PERMISSION_REQUIRED",
message = "CALENDAR_PERMISSION_REQUIRED: grant Calendar permission",
)
}
val request =
parseEventsRequest(paramsJson)
?: return GatewaySession.InvokeResult.error(
code = "INVALID_REQUEST",
message = "INVALID_REQUEST: expected JSON object",
)
return try {
val events = dataSource.events(appContext, request)
GatewaySession.InvokeResult.ok(
buildJsonObject {
put(
"events",
buildJsonArray { events.forEach { add(eventJson(it)) } },
)
}.toString(),
)
} catch (err: Throwable) {
GatewaySession.InvokeResult.error(
code = "CALENDAR_UNAVAILABLE",
message = "CALENDAR_UNAVAILABLE: ${err.message ?: "calendar query failed"}",
)
}
}
fun handleCalendarAdd(paramsJson: String?): GatewaySession.InvokeResult {
if (!dataSource.hasWritePermission(appContext)) {
return GatewaySession.InvokeResult.error(
code = "CALENDAR_PERMISSION_REQUIRED",
message = "CALENDAR_PERMISSION_REQUIRED: grant Calendar permission",
)
}
val request =
parseAddRequest(paramsJson)
?: return GatewaySession.InvokeResult.error(
code = "INVALID_REQUEST",
message = "INVALID_REQUEST: expected JSON object",
)
if (request.title.isEmpty()) {
return GatewaySession.InvokeResult.error(
code = "CALENDAR_INVALID",
message = "CALENDAR_INVALID: title required",
)
}
if (request.endMs <= request.startMs) {
return GatewaySession.InvokeResult.error(
code = "CALENDAR_INVALID",
message = "CALENDAR_INVALID: endISO must be after startISO",
)
}
return try {
val event = dataSource.add(appContext, request)
GatewaySession.InvokeResult.ok(
buildJsonObject {
put("event", eventJson(event))
}.toString(),
)
} catch (err: IllegalArgumentException) {
val msg = err.message ?: "CALENDAR_INVALID: invalid request"
val code = if (msg.startsWith("CALENDAR_NOT_FOUND")) "CALENDAR_NOT_FOUND" else "CALENDAR_INVALID"
GatewaySession.InvokeResult.error(code = code, message = msg)
} catch (err: Throwable) {
GatewaySession.InvokeResult.error(
code = "CALENDAR_UNAVAILABLE",
message = "CALENDAR_UNAVAILABLE: ${err.message ?: "calendar add failed"}",
)
}
}
private fun parseEventsRequest(paramsJson: String?): CalendarEventsRequest? {
if (paramsJson.isNullOrBlank()) {
val start = Instant.now()
val end = start.plus(7, ChronoUnit.DAYS)
return CalendarEventsRequest(startMs = start.toEpochMilli(), endMs = end.toEpochMilli(), limit = DEFAULT_CALENDAR_LIMIT)
}
val params =
try {
Json.parseToJsonElement(paramsJson).asObjectOrNull()
} catch (_: Throwable) {
null
} ?: return null
val start = parseISO((params["startISO"] as? JsonPrimitive)?.content)
val end = parseISO((params["endISO"] as? JsonPrimitive)?.content)
val resolvedStart = start ?: Instant.now()
val resolvedEnd = end ?: resolvedStart.plus(7, ChronoUnit.DAYS)
val limit = ((params["limit"] as? JsonPrimitive)?.content?.toIntOrNull() ?: DEFAULT_CALENDAR_LIMIT).coerceIn(1, 500)
return CalendarEventsRequest(
startMs = resolvedStart.toEpochMilli(),
endMs = resolvedEnd.toEpochMilli(),
limit = limit,
)
}
private fun parseAddRequest(paramsJson: String?): CalendarAddRequest? {
val params =
try {
paramsJson?.let { Json.parseToJsonElement(it).asObjectOrNull() }
} catch (_: Throwable) {
null
} ?: return null
val start = parseISO((params["startISO"] as? JsonPrimitive)?.content)
?: return null
val end = parseISO((params["endISO"] as? JsonPrimitive)?.content)
?: return null
return CalendarAddRequest(
title = (params["title"] as? JsonPrimitive)?.content?.trim().orEmpty(),
startMs = start.toEpochMilli(),
endMs = end.toEpochMilli(),
isAllDay = (params["isAllDay"] as? JsonPrimitive)?.content?.toBooleanStrictOrNull() ?: false,
location = (params["location"] as? JsonPrimitive)?.content?.trim()?.ifEmpty { null },
notes = (params["notes"] as? JsonPrimitive)?.content?.trim()?.ifEmpty { null },
calendarId = (params["calendarId"] as? JsonPrimitive)?.content?.toLongOrNull(),
calendarTitle = (params["calendarTitle"] as? JsonPrimitive)?.content?.trim()?.ifEmpty { null },
)
}
private fun parseISO(raw: String?): Instant? {
val value = raw?.trim().orEmpty()
if (value.isEmpty()) return null
return try {
Instant.parse(value)
} catch (_: Throwable) {
null
}
}
private fun eventJson(event: CalendarEventRecord): JsonObject {
return buildJsonObject {
put("identifier", JsonPrimitive(event.identifier))
put("title", JsonPrimitive(event.title))
put("startISO", JsonPrimitive(event.startISO))
put("endISO", JsonPrimitive(event.endISO))
put("isAllDay", JsonPrimitive(event.isAllDay))
event.location?.let { put("location", JsonPrimitive(it)) }
event.calendarTitle?.let { put("calendarTitle", JsonPrimitive(it)) }
}
}
companion object {
internal fun forTesting(
appContext: Context,
dataSource: CalendarDataSource,
): CalendarHandler = CalendarHandler(appContext = appContext, dataSource = dataSource)
}
}

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

@ -1,13 +1,16 @@
package ai.openclaw.android.node
package ai.openclaw.app.node
import android.Manifest
import android.content.Context
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.util.Base64
import android.content.pm.PackageManager
import android.hardware.camera2.CameraCharacteristics
import android.util.Base64
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.CameraInfo
import androidx.exifinterface.media.ExifInterface
import androidx.lifecycle.LifecycleOwner
import androidx.camera.core.CameraSelector
@ -25,11 +28,12 @@ import androidx.camera.video.VideoRecordEvent
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.core.graphics.scale
import ai.openclaw.android.PermissionRequester
import ai.openclaw.app.PermissionRequester
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonObject
import java.io.ByteArrayOutputStream
import java.io.File
import java.util.concurrent.Executor
@ -40,6 +44,12 @@ import kotlin.coroutines.resumeWithException
class CameraCaptureManager(private val context: Context) {
data class Payload(val payloadJson: String)
data class FilePayload(val file: File, val durationMs: Long, val hasAudio: Boolean)
data class CameraDeviceInfo(
val id: String,
val name: String,
val position: String,
val deviceType: String,
)
@Volatile private var lifecycleOwner: LifecycleOwner? = null
@Volatile private var permissionRequester: PermissionRequester? = null
@ -52,6 +62,14 @@ class CameraCaptureManager(private val context: Context) {
permissionRequester = requester
}
suspend fun listDevices(): List<CameraDeviceInfo> =
withContext(Dispatchers.Main) {
val provider = context.cameraProvider()
provider.availableCameraInfos
.mapNotNull { info -> cameraDeviceInfoOrNull(info) }
.sortedBy { it.id }
}
private suspend fun ensureCameraPermission() {
val granted = checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
if (granted) return
@ -80,14 +98,15 @@ class CameraCaptureManager(private val context: Context) {
withContext(Dispatchers.Main) {
ensureCameraPermission()
val owner = lifecycleOwner ?: throw IllegalStateException("UNAVAILABLE: camera not ready")
val facing = parseFacing(paramsJson) ?: "front"
val quality = (parseQuality(paramsJson) ?: 0.95).coerceIn(0.1, 1.0)
val maxWidth = parseMaxWidth(paramsJson) ?: 1600
val params = parseJsonParamsObject(paramsJson)
val facing = parseFacing(params) ?: "front"
val quality = (parseQuality(params) ?: 0.95).coerceIn(0.1, 1.0)
val maxWidth = parseMaxWidth(params) ?: 1600
val deviceId = parseDeviceId(params)
val provider = context.cameraProvider()
val capture = ImageCapture.Builder().build()
val selector =
if (facing == "front") CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA
val selector = resolveCameraSelector(provider, facing, deviceId)
provider.unbindAll()
provider.bindToLifecycle(owner, selector, capture)
@ -145,12 +164,14 @@ class CameraCaptureManager(private val context: Context) {
withContext(Dispatchers.Main) {
ensureCameraPermission()
val owner = lifecycleOwner ?: throw IllegalStateException("UNAVAILABLE: camera not ready")
val facing = parseFacing(paramsJson) ?: "front"
val durationMs = (parseDurationMs(paramsJson) ?: 3_000).coerceIn(200, 60_000)
val includeAudio = parseIncludeAudio(paramsJson) ?: true
val params = parseJsonParamsObject(paramsJson)
val facing = parseFacing(params) ?: "front"
val durationMs = (parseDurationMs(params) ?: 3_000).coerceIn(200, 60_000)
val includeAudio = parseIncludeAudio(params) ?: true
val deviceId = parseDeviceId(params)
if (includeAudio) ensureMicPermission()
android.util.Log.w("CameraCaptureManager", "clip: start facing=$facing duration=$durationMs audio=$includeAudio")
android.util.Log.w("CameraCaptureManager", "clip: start facing=$facing duration=$durationMs audio=$includeAudio deviceId=${deviceId ?: "-"}")
val provider = context.cameraProvider()
android.util.Log.w("CameraCaptureManager", "clip: got camera provider")
@ -162,8 +183,7 @@ class CameraCaptureManager(private val context: Context) {
)
.build()
val videoCapture = VideoCapture.withOutput(recorder)
val selector =
if (facing == "front") CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA
val selector = resolveCameraSelector(provider, facing, deviceId)
// CameraX requires a Preview use case for the camera to start producing frames;
// without it, the encoder may get no data (ERROR_NO_VALID_DATA).
@ -270,49 +290,84 @@ class CameraCaptureManager(private val context: Context) {
return rotated
}
private fun parseFacing(paramsJson: String?): String? =
when {
paramsJson?.contains("\"front\"") == true -> "front"
paramsJson?.contains("\"back\"") == true -> "back"
else -> null
}
private fun parseQuality(paramsJson: String?): Double? =
parseNumber(paramsJson, key = "quality")?.toDoubleOrNull()
private fun parseMaxWidth(paramsJson: String?): Int? =
parseNumber(paramsJson, key = "maxWidth")?.toIntOrNull()
private fun parseDurationMs(paramsJson: String?): Int? =
parseNumber(paramsJson, key = "durationMs")?.toIntOrNull()
private fun parseIncludeAudio(paramsJson: String?): Boolean? {
val raw = paramsJson ?: return null
val key = "\"includeAudio\""
val idx = raw.indexOf(key)
if (idx < 0) return null
val colon = raw.indexOf(':', idx + key.length)
if (colon < 0) return null
val tail = raw.substring(colon + 1).trimStart()
return when {
tail.startsWith("true") -> true
tail.startsWith("false") -> false
private fun parseFacing(params: JsonObject?): String? {
val value = parseJsonString(params, "facing")?.trim()?.lowercase() ?: return null
return when (value) {
"front", "back" -> value
else -> null
}
}
private fun parseNumber(paramsJson: String?, key: String): String? {
val raw = paramsJson ?: return null
val needle = "\"$key\""
val idx = raw.indexOf(needle)
if (idx < 0) return null
val colon = raw.indexOf(':', idx + needle.length)
if (colon < 0) return null
val tail = raw.substring(colon + 1).trimStart()
return tail.takeWhile { it.isDigit() || it == '.' }
}
private fun parseQuality(params: JsonObject?): Double? =
parseJsonDouble(params, "quality")
private fun parseMaxWidth(params: JsonObject?): Int? =
parseJsonInt(params, "maxWidth")
?.takeIf { it > 0 }
private fun parseDurationMs(params: JsonObject?): Int? =
parseJsonInt(params, "durationMs")
private fun parseDeviceId(params: JsonObject?): String? =
parseJsonString(params, "deviceId")
?.trim()
?.takeIf { it.isNotEmpty() }
private fun parseIncludeAudio(params: JsonObject?): Boolean? = parseJsonBooleanFlag(params, "includeAudio")
private fun Context.mainExecutor(): Executor = ContextCompat.getMainExecutor(this)
private fun resolveCameraSelector(
provider: ProcessCameraProvider,
facing: String,
deviceId: String?,
): CameraSelector {
if (deviceId.isNullOrEmpty()) {
return if (facing == "front") CameraSelector.DEFAULT_FRONT_CAMERA else CameraSelector.DEFAULT_BACK_CAMERA
}
val availableIds = provider.availableCameraInfos.mapNotNull { cameraIdOrNull(it) }.toSet()
if (!availableIds.contains(deviceId)) {
throw IllegalStateException("INVALID_REQUEST: unknown camera deviceId '$deviceId'")
}
return CameraSelector.Builder()
.addCameraFilter { infos -> infos.filter { cameraIdOrNull(it) == deviceId } }
.build()
}
@SuppressLint("UnsafeOptInUsageError")
private fun cameraDeviceInfoOrNull(info: CameraInfo): CameraDeviceInfo? {
val cameraId = cameraIdOrNull(info) ?: return null
val lensFacing =
runCatching {
Camera2CameraInfo.from(info).getCameraCharacteristic(CameraCharacteristics.LENS_FACING)
}.getOrNull()
val position =
when (lensFacing) {
CameraCharacteristics.LENS_FACING_FRONT -> "front"
CameraCharacteristics.LENS_FACING_BACK -> "back"
CameraCharacteristics.LENS_FACING_EXTERNAL -> "external"
else -> "unspecified"
}
val deviceType =
if (lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) "external" else "builtIn"
val name =
when (position) {
"front" -> "Front Camera"
"back" -> "Back Camera"
"external" -> "External Camera"
else -> "Camera $cameraId"
}
return CameraDeviceInfo(
id = cameraId,
name = name,
position = position,
deviceType = deviceType,
)
}
@SuppressLint("UnsafeOptInUsageError")
private fun cameraIdOrNull(info: CameraInfo): String? =
runCatching { Camera2CameraInfo.from(info).cameraId }.getOrNull()
}
private suspend fun Context.cameraProvider(): ProcessCameraProvider =

View File

@ -1,27 +1,59 @@
package ai.openclaw.android.node
package ai.openclaw.app.node
import android.content.Context
import ai.openclaw.android.CameraHudKind
import ai.openclaw.android.BuildConfig
import ai.openclaw.android.SecurePrefs
import ai.openclaw.android.gateway.GatewayEndpoint
import ai.openclaw.android.gateway.GatewaySession
import ai.openclaw.app.CameraHudKind
import ai.openclaw.app.BuildConfig
import ai.openclaw.app.gateway.GatewaySession
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.asRequestBody
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.put
internal const val CAMERA_CLIP_MAX_RAW_BYTES: Long = 18L * 1024L * 1024L
internal fun isCameraClipWithinPayloadLimit(rawBytes: Long): Boolean =
rawBytes in 0L..CAMERA_CLIP_MAX_RAW_BYTES
class CameraHandler(
private val appContext: Context,
private val camera: CameraCaptureManager,
private val prefs: SecurePrefs,
private val connectedEndpoint: () -> GatewayEndpoint?,
private val externalAudioCaptureActive: MutableStateFlow<Boolean>,
private val showCameraHud: (message: String, kind: CameraHudKind, autoHideMs: Long?) -> Unit,
private val triggerCameraFlash: () -> Unit,
private val invokeErrorFromThrowable: (err: Throwable) -> Pair<String, String>,
) {
suspend fun handleList(_paramsJson: String?): GatewaySession.InvokeResult {
return try {
val devices = camera.listDevices()
val payload =
buildJsonObject {
put(
"devices",
buildJsonArray {
devices.forEach { device ->
add(
buildJsonObject {
put("id", JsonPrimitive(device.id))
put("name", JsonPrimitive(device.name))
put("position", JsonPrimitive(device.position))
put("deviceType", JsonPrimitive(device.deviceType))
},
)
}
},
)
}.toString()
GatewaySession.InvokeResult.ok(payload)
} catch (err: Throwable) {
val (code, message) = invokeErrorFromThrowable(err)
GatewaySession.InvokeResult.error(code = code, message = message)
}
}
suspend fun handleSnap(paramsJson: String?): GatewaySession.InvokeResult {
val logFile = if (BuildConfig.DEBUG) java.io.File(appContext.cacheDir, "camera_debug.log") else null
@ -69,7 +101,7 @@ class CameraHandler(
clipLogFile?.appendText("[CLIP $ts] $msg\n")
android.util.Log.w("openclaw", "camera.clip: $msg")
}
val includeAudio = paramsJson?.contains("\"includeAudio\":true") != false
val includeAudio = parseIncludeAudio(paramsJson) ?: true
if (includeAudio) externalAudioCaptureActive.value = true
try {
clipLogFile?.writeText("") // clear
@ -89,62 +121,28 @@ class CameraHandler(
showCameraHud(message, CameraHudKind.Error, 2400)
return GatewaySession.InvokeResult.error(code = code, message = message)
}
// Upload file via HTTP instead of base64 through WebSocket
clipLog("uploading via HTTP...")
val uploadUrl = try {
withContext(Dispatchers.IO) {
val ep = connectedEndpoint()
val gatewayHost = if (ep != null) {
val isHttps = ep.tlsEnabled || ep.port == 443
if (!isHttps) {
clipLog("refusing to upload over plain HTTP — bearer token would be exposed; falling back to base64")
throw Exception("HTTPS required for upload (bearer token protection)")
}
if (ep.port == 443) "https://${ep.host}" else "https://${ep.host}:${ep.port}"
} else {
clipLog("error: no gateway endpoint connected, cannot upload")
throw Exception("no gateway endpoint connected")
}
val token = prefs.loadGatewayToken() ?: ""
val client = okhttp3.OkHttpClient.Builder()
.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
.writeTimeout(120, java.util.concurrent.TimeUnit.SECONDS)
.readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
.build()
val body = filePayload.file.asRequestBody("video/mp4".toMediaType())
val req = okhttp3.Request.Builder()
.url("$gatewayHost/upload/clip.mp4")
.put(body)
.header("Authorization", "Bearer $token")
.build()
clipLog("uploading ${filePayload.file.length()} bytes to $gatewayHost/upload/clip.mp4")
val resp = client.newCall(req).execute()
val respBody = resp.body?.string() ?: ""
clipLog("upload response: ${resp.code} $respBody")
filePayload.file.delete()
if (!resp.isSuccessful) throw Exception("upload failed: HTTP ${resp.code}")
// Parse URL from response
val urlMatch = Regex("\"url\":\"([^\"]+)\"").find(respBody)
urlMatch?.groupValues?.get(1) ?: throw Exception("no url in response: $respBody")
}
} catch (err: Throwable) {
clipLog("upload failed: ${err.message}, falling back to base64")
// Fallback to base64 if upload fails
val bytes = withContext(Dispatchers.IO) {
val b = filePayload.file.readBytes()
filePayload.file.delete()
b
}
val base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
showCameraHud("Clip captured", CameraHudKind.Success, 1800)
return GatewaySession.InvokeResult.ok(
"""{"format":"mp4","base64":"$base64","durationMs":${filePayload.durationMs},"hasAudio":${filePayload.hasAudio}}"""
val rawBytes = filePayload.file.length()
if (!isCameraClipWithinPayloadLimit(rawBytes)) {
clipLog("payload too large: bytes=$rawBytes max=$CAMERA_CLIP_MAX_RAW_BYTES")
withContext(Dispatchers.IO) { filePayload.file.delete() }
showCameraHud("Clip too large", CameraHudKind.Error, 2400)
return GatewaySession.InvokeResult.error(
code = "PAYLOAD_TOO_LARGE",
message =
"PAYLOAD_TOO_LARGE: camera clip is $rawBytes bytes; max is $CAMERA_CLIP_MAX_RAW_BYTES bytes. Reduce durationMs and retry.",
)
}
clipLog("returning URL result: $uploadUrl")
val bytes = withContext(Dispatchers.IO) {
val b = filePayload.file.readBytes()
filePayload.file.delete()
b
}
val base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
clipLog("returning base64 payload")
showCameraHud("Clip captured", CameraHudKind.Success, 1800)
return GatewaySession.InvokeResult.ok(
"""{"format":"mp4","url":"$uploadUrl","durationMs":${filePayload.durationMs},"hasAudio":${filePayload.hasAudio}}"""
"""{"format":"mp4","base64":"$base64","durationMs":${filePayload.durationMs},"hasAudio":${filePayload.hasAudio}}"""
)
} catch (err: Throwable) {
clipLog("outer error: ${err::class.java.simpleName}: ${err.message}")
@ -154,4 +152,24 @@ class CameraHandler(
if (includeAudio) externalAudioCaptureActive.value = false
}
}
private fun parseIncludeAudio(paramsJson: String?): Boolean? {
if (paramsJson.isNullOrBlank()) return null
val root =
try {
Json.parseToJsonElement(paramsJson).asObjectOrNull()
} catch (_: Throwable) {
null
} ?: return null
val value =
(root["includeAudio"] as? JsonPrimitive)
?.contentOrNull
?.trim()
?.lowercase()
return when (value) {
"true" -> true
"false" -> false
else -> null
}
}
}

View File

@ -1,4 +1,4 @@
package ai.openclaw.android.node
package ai.openclaw.app.node
import android.graphics.Bitmap
import android.graphics.Canvas
@ -20,7 +20,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import ai.openclaw.android.BuildConfig
import ai.openclaw.app.BuildConfig
import kotlin.coroutines.resume
class CanvasController {
@ -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()
@ -44,10 +45,19 @@ class CanvasController {
return (q * 100.0).toInt().coerceIn(1, 100)
}
private fun Bitmap.scaleForMaxWidth(maxWidth: Int?): Bitmap {
if (maxWidth == null || maxWidth <= 0 || width <= maxWidth) {
return this
}
val scaledHeight = (height.toDouble() * (maxWidth.toDouble() / width.toDouble())).toInt().coerceAtLeast(1)
return scale(maxWidth, scaledHeight)
}
fun attach(webView: WebView) {
this.webView = webView
reload()
applyDebugStatus()
applyHomeCanvasState()
}
fun detach(webView: WebView) {
@ -80,6 +90,12 @@ class CanvasController {
fun onPageFinished() {
applyDebugStatus()
applyHomeCanvasState()
}
fun updateHomeCanvasState(json: String?) {
homeCanvasStateJson = json
applyHomeCanvasState()
}
private inline fun withWebViewOnMain(crossinline block: (WebView) -> Unit) {
@ -134,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")
@ -148,13 +180,7 @@ class CanvasController {
withContext(Dispatchers.Main) {
val wv = webView ?: throw IllegalStateException("no webview")
val bmp = wv.captureBitmap()
val scaled =
if (maxWidth != null && maxWidth > 0 && bmp.width > maxWidth) {
val h = (bmp.height.toDouble() * (maxWidth.toDouble() / bmp.width.toDouble())).toInt().coerceAtLeast(1)
bmp.scale(maxWidth, h)
} else {
bmp
}
val scaled = bmp.scaleForMaxWidth(maxWidth)
val out = ByteArrayOutputStream()
scaled.compress(Bitmap.CompressFormat.PNG, 100, out)
@ -165,13 +191,7 @@ class CanvasController {
withContext(Dispatchers.Main) {
val wv = webView ?: throw IllegalStateException("no webview")
val bmp = wv.captureBitmap()
val scaled =
if (maxWidth != null && maxWidth > 0 && bmp.width > maxWidth) {
val h = (bmp.height.toDouble() * (maxWidth.toDouble() / bmp.width.toDouble())).toInt().coerceAtLeast(1)
bmp.scale(maxWidth, h)
} else {
bmp
}
val scaled = bmp.scaleForMaxWidth(maxWidth)
val out = ByteArrayOutputStream()
val (compressFormat, compressQuality) =

View File

@ -1,21 +1,22 @@
package ai.openclaw.android.node
package ai.openclaw.app.node
import android.os.Build
import ai.openclaw.android.BuildConfig
import ai.openclaw.android.SecurePrefs
import ai.openclaw.android.gateway.GatewayClientInfo
import ai.openclaw.android.gateway.GatewayConnectOptions
import ai.openclaw.android.gateway.GatewayEndpoint
import ai.openclaw.android.gateway.GatewayTlsParams
import ai.openclaw.android.protocol.OpenClawCapability
import ai.openclaw.android.LocationMode
import ai.openclaw.android.VoiceWakeMode
import ai.openclaw.app.BuildConfig
import ai.openclaw.app.SecurePrefs
import ai.openclaw.app.gateway.GatewayClientInfo
import ai.openclaw.app.gateway.GatewayConnectOptions
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.gateway.GatewayTlsParams
import ai.openclaw.app.LocationMode
import ai.openclaw.app.VoiceWakeMode
class ConnectionManager(
private val prefs: SecurePrefs,
private val cameraEnabled: () -> Boolean,
private val locationMode: () -> LocationMode,
private val voiceWakeMode: () -> VoiceWakeMode,
private val motionActivityAvailable: () -> Boolean,
private val motionPedometerAvailable: () -> Boolean,
private val smsAvailable: () -> Boolean,
private val hasRecordAudioPermission: () -> Boolean,
private val manualTls: () -> Boolean,
@ -73,28 +74,20 @@ class ConnectionManager(
}
}
fun buildInvokeCommands(): List<String> =
InvokeCommandRegistry.advertisedCommands(
private fun runtimeFlags(): NodeRuntimeFlags =
NodeRuntimeFlags(
cameraEnabled = cameraEnabled(),
locationEnabled = locationMode() != LocationMode.Off,
smsAvailable = smsAvailable(),
voiceWakeEnabled = voiceWakeMode() != VoiceWakeMode.Off && hasRecordAudioPermission(),
motionActivityAvailable = motionActivityAvailable(),
motionPedometerAvailable = motionPedometerAvailable(),
debugBuild = BuildConfig.DEBUG,
)
fun buildCapabilities(): List<String> =
buildList {
add(OpenClawCapability.Canvas.rawValue)
add(OpenClawCapability.Screen.rawValue)
add(OpenClawCapability.Device.rawValue)
if (cameraEnabled()) add(OpenClawCapability.Camera.rawValue)
if (smsAvailable()) add(OpenClawCapability.Sms.rawValue)
if (voiceWakeMode() != VoiceWakeMode.Off && hasRecordAudioPermission()) {
add(OpenClawCapability.VoiceWake.rawValue)
}
if (locationMode() != LocationMode.Off) {
add(OpenClawCapability.Location.rawValue)
}
}
fun buildInvokeCommands(): List<String> = InvokeCommandRegistry.advertisedCommands(runtimeFlags())
fun buildCapabilities(): List<String> = InvokeCommandRegistry.advertisedCapabilities(runtimeFlags())
fun resolvedVersionName(): String {
val versionName = BuildConfig.VERSION_NAME.trim().ifEmpty { "dev" }

View File

@ -0,0 +1,430 @@
package ai.openclaw.app.node
import android.Manifest
import android.content.ContentProviderOperation
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.provider.ContactsContract
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.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
private const val DEFAULT_CONTACTS_LIMIT = 25
internal data class ContactRecord(
val identifier: String,
val displayName: String,
val givenName: String,
val familyName: String,
val organizationName: String,
val phoneNumbers: List<String>,
val emails: List<String>,
)
internal data class ContactsSearchRequest(
val query: String?,
val limit: Int,
)
internal data class ContactsAddRequest(
val givenName: String?,
val familyName: String?,
val organizationName: String?,
val displayName: String?,
val phoneNumbers: List<String>,
val emails: List<String>,
)
internal interface ContactsDataSource {
fun hasReadPermission(context: Context): Boolean
fun hasWritePermission(context: Context): Boolean
fun search(context: Context, request: ContactsSearchRequest): List<ContactRecord>
fun add(context: Context, request: ContactsAddRequest): ContactRecord
}
private object SystemContactsDataSource : ContactsDataSource {
override fun hasReadPermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
}
override fun hasWritePermission(context: Context): Boolean {
return ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_CONTACTS) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
}
override fun search(context: Context, request: ContactsSearchRequest): List<ContactRecord> {
val resolver = context.contentResolver
val projection =
arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
)
val selection: String?
val selectionArgs: Array<String>?
if (request.query.isNullOrBlank()) {
selection = null
selectionArgs = null
} else {
selection = "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
selectionArgs = arrayOf("%${request.query}%")
}
val sortOrder = "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} COLLATE NOCASE ASC LIMIT ${request.limit}"
resolver.query(
ContactsContract.Contacts.CONTENT_URI,
projection,
selection,
selectionArgs,
sortOrder,
).use { cursor ->
if (cursor == null) return emptyList()
val idIndex = cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID)
val displayNameIndex = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
val out = mutableListOf<ContactRecord>()
while (cursor.moveToNext() && out.size < request.limit) {
val contactId = cursor.getLong(idIndex)
val displayName = cursor.getString(displayNameIndex).orEmpty()
out += loadContactRecord(resolver, contactId, fallbackDisplayName = displayName)
}
return out
}
}
override fun add(context: Context, request: ContactsAddRequest): ContactRecord {
val resolver = context.contentResolver
val operations = ArrayList<ContentProviderOperation>()
operations +=
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build()
if (!request.givenName.isNullOrEmpty() || !request.familyName.isNullOrEmpty() || !request.displayName.isNullOrEmpty()) {
operations +=
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, request.givenName)
.withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, request.familyName)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, request.displayName)
.build()
}
if (!request.organizationName.isNullOrEmpty()) {
operations +=
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Organization.COMPANY, request.organizationName)
.build()
}
request.phoneNumbers.forEach { number ->
operations +=
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, number)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
.build()
}
request.emails.forEach { email ->
operations +=
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email)
.withValue(ContactsContract.CommonDataKinds.Email.TYPE, ContactsContract.CommonDataKinds.Email.TYPE_HOME)
.build()
}
val results = resolver.applyBatch(ContactsContract.AUTHORITY, operations)
val rawContactUri = results.firstOrNull()?.uri
?: throw IllegalStateException("contact insert failed")
val rawContactId = rawContactUri.lastPathSegment?.toLongOrNull()
?: throw IllegalStateException("contact insert failed")
val contactId = resolveContactIdForRawContact(resolver, rawContactId)
?: throw IllegalStateException("contact insert failed")
return loadContactRecord(
resolver = resolver,
contactId = contactId,
fallbackDisplayName = request.displayName.orEmpty(),
)
}
private fun resolveContactIdForRawContact(resolver: ContentResolver, rawContactId: Long): Long? {
val projection = arrayOf(ContactsContract.RawContacts.CONTACT_ID)
resolver.query(
ContactsContract.RawContacts.CONTENT_URI,
projection,
"${ContactsContract.RawContacts._ID}=?",
arrayOf(rawContactId.toString()),
null,
).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) return null
val index = cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.CONTACT_ID)
return cursor.getLong(index)
}
}
private fun loadContactRecord(
resolver: ContentResolver,
contactId: Long,
fallbackDisplayName: String,
): ContactRecord {
val nameRow = loadNameRow(resolver, contactId)
val organization = loadOrganization(resolver, contactId)
val phones = loadPhones(resolver, contactId)
val emails = loadEmails(resolver, contactId)
val displayName =
when {
!nameRow.displayName.isNullOrEmpty() -> nameRow.displayName
!fallbackDisplayName.isNullOrEmpty() -> fallbackDisplayName
else -> listOfNotNull(nameRow.givenName, nameRow.familyName).joinToString(" ").trim()
}.ifEmpty { "(unnamed)" }
return ContactRecord(
identifier = contactId.toString(),
displayName = displayName,
givenName = nameRow.givenName.orEmpty(),
familyName = nameRow.familyName.orEmpty(),
organizationName = organization.orEmpty(),
phoneNumbers = phones,
emails = emails,
)
}
private data class NameRow(
val givenName: String?,
val familyName: String?,
val displayName: String?,
)
private fun loadNameRow(resolver: ContentResolver, contactId: Long): NameRow {
val projection =
arrayOf(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
)
resolver.query(
ContactsContract.Data.CONTENT_URI,
projection,
"${ContactsContract.Data.CONTACT_ID}=? AND ${ContactsContract.Data.MIMETYPE}=?",
arrayOf(
contactId.toString(),
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE,
),
null,
).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) {
return NameRow(givenName = null, familyName = null, displayName = null)
}
val given = cursor.getString(0)?.trim()?.ifEmpty { null }
val family = cursor.getString(1)?.trim()?.ifEmpty { null }
val display = cursor.getString(2)?.trim()?.ifEmpty { null }
return NameRow(givenName = given, familyName = family, displayName = display)
}
}
private fun loadOrganization(resolver: ContentResolver, contactId: Long): String? {
val projection = arrayOf(ContactsContract.CommonDataKinds.Organization.COMPANY)
resolver.query(
ContactsContract.Data.CONTENT_URI,
projection,
"${ContactsContract.Data.CONTACT_ID}=? AND ${ContactsContract.Data.MIMETYPE}=?",
arrayOf(contactId.toString(), ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE),
null,
).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) return null
return cursor.getString(0)?.trim()?.ifEmpty { null }
}
}
private fun loadPhones(resolver: ContentResolver, contactId: Long): List<String> {
return queryContactValues(
resolver = resolver,
contentUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
valueColumn = ContactsContract.CommonDataKinds.Phone.NUMBER,
contactIdColumn = ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
contactId = contactId,
)
}
private fun loadEmails(resolver: ContentResolver, contactId: Long): List<String> {
return queryContactValues(
resolver = resolver,
contentUri = ContactsContract.CommonDataKinds.Email.CONTENT_URI,
valueColumn = ContactsContract.CommonDataKinds.Email.ADDRESS,
contactIdColumn = ContactsContract.CommonDataKinds.Email.CONTACT_ID,
contactId = contactId,
)
}
private fun queryContactValues(
resolver: ContentResolver,
contentUri: android.net.Uri,
valueColumn: String,
contactIdColumn: String,
contactId: Long,
): List<String> {
val projection = arrayOf(valueColumn)
resolver.query(
contentUri,
projection,
"$contactIdColumn=?",
arrayOf(contactId.toString()),
null,
).use { cursor ->
if (cursor == null) return emptyList()
val out = LinkedHashSet<String>()
while (cursor.moveToNext()) {
val value = cursor.getString(0)?.trim().orEmpty()
if (value.isNotEmpty()) out += value
}
return out.toList()
}
}
}
class ContactsHandler private constructor(
private val appContext: Context,
private val dataSource: ContactsDataSource,
) {
constructor(appContext: Context) : this(appContext = appContext, dataSource = SystemContactsDataSource)
fun handleContactsSearch(paramsJson: String?): GatewaySession.InvokeResult {
if (!dataSource.hasReadPermission(appContext)) {
return GatewaySession.InvokeResult.error(
code = "CONTACTS_PERMISSION_REQUIRED",
message = "CONTACTS_PERMISSION_REQUIRED: grant Contacts permission",
)
}
val request =
parseSearchRequest(paramsJson)
?: return GatewaySession.InvokeResult.error(
code = "INVALID_REQUEST",
message = "INVALID_REQUEST: expected JSON object",
)
return try {
val contacts = dataSource.search(appContext, request)
GatewaySession.InvokeResult.ok(
buildJsonObject {
put(
"contacts",
buildJsonArray {
contacts.forEach { add(contactJson(it)) }
},
)
}.toString(),
)
} catch (err: Throwable) {
GatewaySession.InvokeResult.error(
code = "CONTACTS_UNAVAILABLE",
message = "CONTACTS_UNAVAILABLE: ${err.message ?: "contacts query failed"}",
)
}
}
fun handleContactsAdd(paramsJson: String?): GatewaySession.InvokeResult {
if (!dataSource.hasWritePermission(appContext)) {
return GatewaySession.InvokeResult.error(
code = "CONTACTS_PERMISSION_REQUIRED",
message = "CONTACTS_PERMISSION_REQUIRED: grant Contacts permission",
)
}
val request =
parseAddRequest(paramsJson)
?: return GatewaySession.InvokeResult.error(
code = "INVALID_REQUEST",
message = "INVALID_REQUEST: expected JSON object",
)
val hasName =
!(request.givenName.isNullOrEmpty() && request.familyName.isNullOrEmpty() && request.displayName.isNullOrEmpty())
val hasOrg = !request.organizationName.isNullOrEmpty()
val hasDetails = request.phoneNumbers.isNotEmpty() || request.emails.isNotEmpty()
if (!hasName && !hasOrg && !hasDetails) {
return GatewaySession.InvokeResult.error(
code = "CONTACTS_INVALID",
message = "CONTACTS_INVALID: include a name, organization, phone, or email",
)
}
return try {
val contact = dataSource.add(appContext, request)
GatewaySession.InvokeResult.ok(
buildJsonObject {
put("contact", contactJson(contact))
}.toString(),
)
} catch (err: Throwable) {
GatewaySession.InvokeResult.error(
code = "CONTACTS_UNAVAILABLE",
message = "CONTACTS_UNAVAILABLE: ${err.message ?: "contact add failed"}",
)
}
}
private fun parseSearchRequest(paramsJson: String?): ContactsSearchRequest? {
if (paramsJson.isNullOrBlank()) {
return ContactsSearchRequest(query = null, limit = DEFAULT_CONTACTS_LIMIT)
}
val params =
try {
Json.parseToJsonElement(paramsJson).asObjectOrNull()
} catch (_: Throwable) {
null
} ?: return null
val query = (params["query"] as? JsonPrimitive)?.content?.trim()?.ifEmpty { null }
val limit = ((params["limit"] as? JsonPrimitive)?.content?.toIntOrNull() ?: DEFAULT_CONTACTS_LIMIT).coerceIn(1, 200)
return ContactsSearchRequest(query = query, limit = limit)
}
private fun parseAddRequest(paramsJson: String?): ContactsAddRequest? {
val params =
try {
paramsJson?.let { Json.parseToJsonElement(it).asObjectOrNull() }
} catch (_: Throwable) {
null
} ?: return null
return ContactsAddRequest(
givenName = (params["givenName"] as? JsonPrimitive)?.content?.trim()?.ifEmpty { null },
familyName = (params["familyName"] as? JsonPrimitive)?.content?.trim()?.ifEmpty { null },
organizationName = (params["organizationName"] as? JsonPrimitive)?.content?.trim()?.ifEmpty { null },
displayName = (params["displayName"] as? JsonPrimitive)?.content?.trim()?.ifEmpty { null },
phoneNumbers = stringArray(params["phoneNumbers"] as? JsonArray),
emails = stringArray(params["emails"] as? JsonArray).map { it.lowercase() },
)
}
private fun stringArray(array: JsonArray?): List<String> {
if (array == null) return emptyList()
return array.mapNotNull { element ->
(element as? JsonPrimitive)?.content?.trim()?.ifEmpty { null }
}
}
private fun contactJson(contact: ContactRecord): JsonObject {
return buildJsonObject {
put("identifier", JsonPrimitive(contact.identifier))
put("displayName", JsonPrimitive(contact.displayName))
put("givenName", JsonPrimitive(contact.givenName))
put("familyName", JsonPrimitive(contact.familyName))
put("organizationName", JsonPrimitive(contact.organizationName))
put("phoneNumbers", buildJsonArray { contact.phoneNumbers.forEach { add(JsonPrimitive(it)) } })
put("emails", buildJsonArray { contact.emails.forEach { add(JsonPrimitive(it)) } })
}
}
companion object {
internal fun forTesting(
appContext: Context,
dataSource: ContactsDataSource,
): ContactsHandler = ContactsHandler(appContext = appContext, dataSource = dataSource)
}
}

View File

@ -1,9 +1,9 @@
package ai.openclaw.android.node
package ai.openclaw.app.node
import android.content.Context
import ai.openclaw.android.BuildConfig
import ai.openclaw.android.gateway.DeviceIdentityStore
import ai.openclaw.android.gateway.GatewaySession
import ai.openclaw.app.BuildConfig
import ai.openclaw.app.gateway.DeviceIdentityStore
import ai.openclaw.app.gateway.GatewaySession
import kotlinx.serialization.json.JsonPrimitive
class DebugHandler(
@ -62,7 +62,8 @@ class DebugHandler(
results.add("Signature.Ed25519: FAILED - ${e.javaClass.simpleName}: ${e.message}")
}
return GatewaySession.InvokeResult.ok("""{"diagnostics":"${results.joinToString("\\n").replace("\"", "\\\"")}"}"""")
val diagnostics = results.joinToString("\n")
return GatewaySession.InvokeResult.ok("""{"diagnostics":${JsonPrimitive(diagnostics)}}""")
} catch (e: Throwable) {
return GatewaySession.InvokeResult.error(code = "ED25519_TEST_FAILED", message = "${e.javaClass.simpleName}: ${e.message}\n${e.stackTraceToString().take(500)}")
}

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