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>
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.
* 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>
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>
* 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>
* 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>
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.
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>
* 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>
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
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.
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
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
* 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>
* 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
* 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
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)
* 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
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/
* 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
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>
* 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>
* 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
* 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>
* 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>
* 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
`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>
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>
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>
* 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>
* 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>
* 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
* Fix updater refresh cwd for service reinstall
* Update: preserve relative env overrides during service refresh
* Test: cover updater service refresh env rebasing
* 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>
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.
## 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>
* 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>
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
* 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>
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.
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.
- 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
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).
* 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)
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>
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>
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.
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
* 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
* 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>
* 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
* 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>
## 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.
* 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>
* 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>
* 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>
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)
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
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
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
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
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#8239Closes#3494
Co-Authored-By: Jeffrey Morgan <jmorganca@gmail.com>
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
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
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
* 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>
- 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
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>
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>
* 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>
* 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>
- 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
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.
* 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>
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
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>
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>
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>
- 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
- 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)
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').
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.
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)
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
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>
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>
* 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>
* 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>
* 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>
* 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>
- 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>
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>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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
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>
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
- 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)
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).
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.
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
* 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
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>
* 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
* 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#28216Fixes#29135Fixes#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>
* 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>
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>
Land #9040 by @joetomasone. Add fail-closed config loading, compat coverage, and changelog entry for #5052.
Co-authored-by: Joe Tomasone <joe@tomasone.com>
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>
* 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)
* 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>
* 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>
* 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>
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
* 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>
* 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>
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>
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`).
* 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>
* 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
* 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
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.
* 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
* 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>
* 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>
- 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>
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>
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>
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>
- 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>
- 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>
## 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>
* 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>
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>
* 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>
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>
* 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>
* 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
Fixesopenclaw/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>
* 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#32980Fixes#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>
* 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
* 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
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
* 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>
* 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>
- 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.
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
* 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>
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>
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>
* 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)
* 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>
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>
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)
* 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>
* 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&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>
* 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>
* 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>
* 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>
* 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>
* 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>
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.
@ -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.
// Labels prefixed with "r:" are auto-response triggers.
const activePrLimit = 10;
const rules = [
{
label:"r: skill",
@ -48,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.",
"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.";
@ -411,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,
@ -422,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,
@ -433,6 +487,10 @@ jobs:
return;
}
if (pullRequest && labelSet.has(activePrLimitOverrideLabel)) {
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
- 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: don’t 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.
- 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`.
- 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.
- Don’t 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`; don’t 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.
- "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.
- Release guardrails: do not change version numbers without operator’s 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.
- 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).
- 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
@ -100,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.
@ -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,9 +52,11 @@ 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.
@ -64,6 +67,7 @@ These are frequently reported but are typically closed with no code change:
- Discord inbound webhook signature findings for paths not used by this repo's Discord integration.
- Claims that Microsoft Teams `fileConsent/invoke``uploadInfo.uploadUrl` is attacker-controlled without demonstrating one of: auth boundary bypass, a real authenticated Teams/Bot Framework event carrying attacker-chosen URL, or compromise of the Microsoft/Bot trust path.
- Scanner-only claims against stale/nonexistent paths, or claims without a working repro.
- Reports that restate an already-fixed issue against later released versions without showing the vulnerable path still exists in the shipped tag or published artifact for that later version.
### Duplicate Report Handling
@ -89,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.
@ -119,10 +124,12 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
- 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.
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.
@ -163,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.
<li>Agents/Thinking defaults: set <code>adaptive</code> as the default thinking level for Anthropic Claude 4.6 models (including Bedrock Claude 4.6 refs) while keeping other reasoning-capable models at <code>low</code> unless explicitly configured.</li>
<li>Gateway/Container probes: add built-in HTTP liveness/readiness endpoints (<code>/health</code>, <code>/healthz</code>, <code>/ready</code>, <code>/readyz</code>) for Docker/Kubernetes health checks, with fallback routing so existing handlers on those paths are not shadowed. (#31272) Thanks @vincentkoc.</li>
<li>Android/Nodes: add <code>camera.list</code>, <code>device.permissions</code>, <code>device.health</code>, and <code>notifications.actions</code> (<code>open</code>/<code>dismiss</code>/<code>reply</code>) on Android nodes, plus first-class node-tool actions for the new device/notification commands. (#28260) Thanks @obviyus.</li>
<li>Discord/Thread bindings: replace fixed TTL lifecycle with inactivity (<code>idleHours</code>, default 24h) plus optional hard <code>maxAgeHours</code> lifecycle controls, and add <code>/session idle</code> + <code>/session max-age</code> commands for focused thread-bound sessions. (#27845) Thanks @osolmaz.</li>
<li>Telegram/DM topics: add per-DM <code>direct</code> + topic config (allowlists, <code>dmPolicy</code>, <code>skills</code>, <code>systemPrompt</code>, <code>requireTopic</code>), route DM topics as distinct inbound/outbound sessions, and enforce topic-aware authorization/debounce for messages, callbacks, commands, and reactions. Landed from contributor PR #30579 by @kesor. Thanks @kesor.</li>
<li>Web UI/Cron i18n: localize cron page labels, filters, form help text, and validation/error messaging in English and zh-CN. (#29315) Thanks @BUGKillerKing.</li>
<li>OpenAI/Streaming transport: make <code>openai</code> Responses WebSocket-first by default (<code>transport: "auto"</code> with SSE fallback), add shared OpenAI WS stream/connection runtime wiring with per-session cleanup, and preserve server-side compaction payload mutation (<code>store</code> + <code>context_management</code>) on the WS path.</li>
<li>Android/Gateway capability refresh: add live Android capability integration coverage and node canvas capability refresh wiring, plus runtime hardening for A2UI readiness retries, scoped canvas URL normalization, debug diagnostics JSON, and JavaScript MIME delivery. (#28388) Thanks @obviyus.</li>
<li>Android/Nodes parity: add <code>system.notify</code>, <code>photos.latest</code>, <code>contacts.search</code>/<code>contacts.add</code>, <code>calendar.events</code>/<code>calendar.add</code>, and <code>motion.activity</code>/<code>motion.pedometer</code>, with motion sensor-aware command gating and improved activity sampling reliability. (#29398) Thanks @obviyus.</li>
<li>CLI/Config: add <code>openclaw config file</code> to print the active config file path resolved from <code>OPENCLAW_CONFIG_PATH</code> or the default location. (#26256) thanks @cyb1278588254.</li>
<li>Feishu/Docx tables + uploads: add <code>feishu_doc</code> actions for Docx table creation/cell writing (<code>create_table</code>, <code>write_table_cells</code>, <code>create_table_with_values</code>) and image/file uploads (<code>upload_image</code>, <code>upload_file</code>) with stricter create/upload error handling for missing <code>document_id</code> and placeholder cleanup failures. (#20304) Thanks @xuhao1.</li>
<li>Feishu/Reactions: add inbound <code>im.message.reaction.created_v1</code> handling, route verified reactions through synthetic inbound turns, and harden verification with timeout + fail-closed filtering so non-bot or unverified reactions are dropped. (#16716) Thanks @schumilin.</li>
<li>Feishu/Chat tooling: add <code>feishu_chat</code> tool actions for chat info and member queries, with configurable enablement under <code>channels.feishu.tools.chat</code>. (#14674) Thanks @liuweifly.</li>
<li>Feishu/Doc permissions: support optional owner permission grant fields on <code>feishu_doc</code> create and report permission metadata only when the grant call succeeds, with regression coverage for success/failure/omitted-owner paths. (#28295) Thanks @zhoulongchao77.</li>
<li>Web UI/i18n: add German (<code>de</code>) locale support and auto-render language options from supported locale constants in Overview settings. (#28495) thanks @dsantoreis.</li>
<li>Tools/Diffs: add a new optional <code>diffs</code> plugin tool for read-only diff rendering from before/after text or unified patches, with gateway viewer URLs for canvas and PNG image output. Thanks @gumadeiras.</li>
<li>Memory/LanceDB: support custom OpenAI <code>baseUrl</code> and embedding dimensions for LanceDB memory. (#17874) Thanks @rish2jain and @vincentkoc.</li>
<li>ACP/ACPX streaming: pin ACPX plugin support to <code>0.1.15</code>, add configurable ACPX command/version probing, and streamline ACP stream delivery (<code>final_only</code> default + reduced tool-event noise) with matching runtime and test updates. (#30036) Thanks @osolmaz.</li>
<li>Shell env markers: set <code>OPENCLAW_SHELL</code> across shell-like runtimes (<code>exec</code>, <code>acp</code>, <code>acp-client</code>, <code>tui-local</code>) so shell startup/config rules can target OpenClaw contexts consistently, and document the markers in env/exec/acp/TUI docs. Thanks @vincentkoc.</li>
<li>Cron/Heartbeat light bootstrap context: add opt-in lightweight bootstrap mode for automation runs (<code>--light-context</code> for cron agent turns and <code>agents.*.heartbeat.lightContext</code> for heartbeat), keeping only <code>HEARTBEAT.md</code> for heartbeat runs and skipping bootstrap-file injection for cron lightweight runs. (#26064) Thanks @jose-velez.</li>
<li>OpenAI/WebSocket warm-up: add optional OpenAI Responses WebSocket warm-up (<code>response.create</code> with <code>generate:false</code>), enable it by default for <code>openai/*</code>, and expose <code>params.openaiWsWarmup</code> for per-model enable/disable control.</li>
<li>Agents/Subagents runtime events: replace ad-hoc subagent completion system-message handoff with typed internal completion events (<code>task_completion</code>) that are rendered consistently across direct and queued announce paths, with gateway/CLI plumbing for structured <code>internalEvents</code>.</li>
</ul>
<h3>Breaking</h3>
<ul>
<li><strong>BREAKING:</strong> Node exec approval payloads now require <code>systemRunPlan</code>. <code>host=node</code> approval requests without that plan are rejected.</li>
<li><strong>BREAKING:</strong> Node <code>system.run</code> execution now pins path-token commands to the canonical executable path (<code>realpath</code>) in both allowlist and approval execution flows. Integrations/tests that asserted token-form argv (for example <code>tr</code>) must now accept canonical paths (for example <code>/usr/bin/tr</code>).</li>
<li>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 Chrome’s 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>Android/Nodes reliability: reject <code>facing=both</code> when <code>deviceId</code> is set to avoid mislabeled duplicate captures, allow notification <code>open</code>/<code>reply</code> on non-clearable entries while still gating dismiss, trigger listener rebind before notification actions, and scale invoke-result ack timeout to invoke budget for large clip payloads. (#28260) Thanks @obviyus.</li>
<li>Windows/Plugin install: avoid <code>spawn EINVAL</code> on Windows npm/npx invocations by resolving to <code>node</code> + npm CLI scripts instead of spawning <code>.cmd</code> directly. Landed from contributor PR #31147 by @codertony. Thanks @codertony.</li>
<li>LINE/Voice transcription: classify M4A voice media as <code>audio/mp4</code> (not <code>video/mp4</code>) by checking the MPEG-4 <code>ftyp</code> major brand (<code>M4A </code> / <code>M4B </code>), restoring voice transcription for LINE voice messages. Landed from contributor PR #31151 by @scoootscooob. Thanks @scoootscooob.</li>
<li>Slack/Announce target account routing: enable session-backed announce-target lookup for Slack so multi-account announces resolve the correct <code>accountId</code> instead of defaulting to bot-token context. Landed from contributor PR #31028 by @taw0002. Thanks @taw0002.</li>
<li>Android/Voice screen TTS: stream assistant speech via ElevenLabs WebSocket in Talk Mode, stop cleanly on speaker mute/barge-in, and ignore stale out-of-order stream events. (#29521) Thanks @gregmousseau.</li>
<li>Android/Photos permissions: declare Android 14+ selected-photo access permission (<code>READ_MEDIA_VISUAL_USER_SELECTED</code>) and align Android permission/settings paths with current minSdk behavior for more reliable permission state handling.</li>
<li>Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.</li>
<li>Cron/Delivery: disable the agent messaging tool when <code>delivery.mode</code> is <code>"none"</code> so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.</li>
<li>CLI/Cron: clarify <code>cron list</code> output by renaming <code>Agent</code> to <code>Agent ID</code> and adding a <code>Model</code> column for isolated agent-turn jobs. (#26259) Thanks @openperf.</li>
<li>Feishu/Reply media attachments: send Feishu reply <code>mediaUrl</code>/<code>mediaUrls</code> payloads as attachments alongside text/streamed replies in the reply dispatcher, including legacy fallback when <code>mediaUrls</code> is empty. (#28959) Thanks @icesword0760.</li>
<li>Slack/User-token resolution: normalize Slack account user-token sourcing through resolved account metadata (<code>SLACK_USER_TOKEN</code> env + config) so monitor reads, Slack actions, directory lookups, onboarding allow-from resolution, and capabilities probing consistently use the effective user token. (#28103) Thanks @Glucksberg.</li>
<li>Feishu/Outbound session routing: stop assuming bare <code>oc_</code> identifiers are always group chats, honor explicit <code>dm:</code>/<code>group:</code> prefixes for <code>oc_</code> chat IDs, and default ambiguous bare <code>oc_</code> targets to direct routing to avoid DM session misclassification. (#10407) Thanks @Bermudarat.</li>
<li>Feishu/Group session routing: add configurable group session scopes (<code>group</code>, <code>group_sender</code>, <code>group_topic</code>, <code>group_topic_sender</code>) with legacy <code>topicSessionMode=enabled</code> compatibility so Feishu group conversations can isolate sessions by sender/topic as configured. (#17798) Thanks @yfge.</li>
<li>Feishu/Reply-in-thread routing: add <code>replyInThread</code> config (<code>disabled|enabled</code>) for group replies, propagate <code>reply_in_thread</code> across text/card/media/streaming sends, and align topic-scoped session routing so newly created reply threads stay on the same session root. (#27325) Thanks @kcinzgg.</li>
<li>Feishu/Probe status caching: cache successful <code>probeFeishu()</code> bot-info results for 10 minutes (bounded cache with per-account keying) to reduce repeated status/onboarding probe API calls, while bypassing cache for failures and exceptions. (#28907) Thanks @Glucksberg.</li>
<li>Feishu/Opus media send type: send <code>.opus</code> attachments with <code>msg_type: "audio"</code> (instead of <code>"media"</code>) so Feishu voice messages deliver correctly while <code>.mp4</code> remains <code>msg_type: "media"</code> and documents remain <code>msg_type: "file"</code>. (#28269) Thanks @Glucksberg.</li>
<li>Feishu/Mobile video media type: treat inbound <code>message_type: "media"</code> as video-equivalent for media key extraction, placeholder inference, and media download resolution so mobile-app video sends ingest correctly. (#25502) Thanks @4ier.</li>
<li>Feishu/Inbound sender fallback: fall back to <code>sender_id.user_id</code> when <code>sender_id.open_id</code> is missing on inbound events, and use ID-type-aware sender lookup so mobile-delivered messages keep stable sender identity/routing. (#26703) Thanks @NewdlDewdl.</li>
<li>Feishu/Reply context metadata: include inbound <code>parent_id</code> and <code>root_id</code> as <code>ReplyToId</code>/<code>RootMessageId</code> in inbound context, and parse interactive-card quote bodies into readable text when fetching replied messages. (#18529) Thanks @qiangu.</li>
<li>Feishu/Post embedded media: extract <code>media</code> tags from inbound rich-text (<code>post</code>) messages and download embedded video/audio files alongside existing embedded-image handling, with regression coverage. (#21786) Thanks @laopuhuluwa.</li>
<li>Feishu/Local media sends: propagate <code>mediaLocalRoots</code> through Feishu outbound media sending into <code>loadWebMedia</code> so local path attachments work with post-CVE local-root enforcement. (#27884) Thanks @joelnishanth.</li>
<li>Feishu/Group wildcard policy fallback: honor <code>channels.feishu.groups["*"]</code> when no explicit group match exists so unmatched groups inherit wildcard reply-policy settings instead of falling back to global defaults. (#29456) Thanks @WaynePika.</li>
<li>Feishu/Inbound media regression coverage: add explicit tests for message resource type mapping (<code>image</code> stays <code>image</code>, non-image maps to <code>file</code>) to prevent reintroducing unsupported Feishu <code>type=audio</code> fetches. (#16311, #8746) Thanks @Yaxuan42.</li>
<li>TTS/Voice bubbles: use opus output and enable <code>audioAsVoice</code> routing for Feishu and WhatsApp (in addition to Telegram) so supported channels receive voice-bubble playback instead of file-style audio attachments. (#27366) Thanks @smthfoxy.</li>
<li>Telegram/Reply media context: include replied media files in inbound context when replying to media, defer reply-media downloads to debounce flush, gate reply-media fetch behind DM authorization, and preserve replied media when non-vision sticker fallback runs (including cached-sticker paths). (#28488) Thanks @obviyus.</li>
<li>Android/Nodes notification wake flow: enable Android <code>system.notify</code> default allowlist, emit <code>notifications.changed</code> events for posted/removed notifications (excluding OpenClaw app-owned notifications), canonicalize notification session keys before enqueue/wake routing, and skip heartbeat wakes when consecutive notification summaries dedupe. (#29440) Thanks @obviyus.</li>
<li>Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.</li>
<li>Feishu/Multi-account + reply reliability: add <code>channels.feishu.defaultAccount</code> outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as <code>msg_type: "file"</code>, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.</li>
<li>Cron/Delivery: disable the agent messaging tool when <code>delivery.mode</code> is <code>"none"</code> so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.</li>
<li>Feishu/Inbound rich-text parsing: preserve <code>share_chat</code> payload summaries when available and add explicit parsing for rich-text <code>code</code>/<code>code_block</code>/<code>pre</code> tags so forwarded and code-heavy messages keep useful context in agent input. (#28591) Thanks @kevinWangSheng.</li>
<li>Feishu/Post markdown parsing: parse rich-text <code>post</code> payloads through a shared markdown-aware parser with locale-wrapper support, preserved mention/image metadata extraction, and inline/fenced code fidelity for agent input rendering. (#12755) Thanks @WilsonLiu95.</li>
<li>Telegram/Outbound chunking: route oversize splitting through the shared outbound pipeline (including subagents), retry Telegram sends when escaped HTML exceeds limits, and preserve boundary whitespace when retry re-splitting rendered chunks so plain-text/transcript fidelity is retained. (#29342, #27317; follow-up to #27461) Thanks @obviyus.</li>
<li>Slack/Native commands: register Slack native status as <code>/agentstatus</code> (Slack-reserved <code>/status</code>) so manifest slash command registration stays valid while text <code>/status</code> still works. Landed from contributor PR #29032 by @maloqab. Thanks @maloqab.</li>
<li>Android/Camera clip: remove <code>camera.clip</code> HTTP-upload fallback to base64 so clip transport is deterministic and fail-loud, and reject non-positive <code>maxWidth</code> values so invalid inputs fall back to the safe resize default. (#28229) Thanks @obviyus.</li>
<li>Android/Gateway canvas capability refresh: send <code>node.canvas.capability.refresh</code> with object <code>params</code> (<code>{}</code>) from Android node runtime so gateway object-schema validation accepts refresh retries and A2UI host recovery works after scoped capability expiry. (#28413) Thanks @obviyus.</li>
<li>Gateway/Control UI origins: honor <code>gateway.controlUi.allowedOrigins: ["*"]</code> wildcard entries (including trimmed values) and lock behavior with regression tests. Landed from contributor PR #31058 by @byungsker. Thanks @byungsker.</li>
<li>Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.</li>
<li>Agents/Sessions list transcript paths: handle missing/non-string/relative <code>sessions.list.path</code> values and per-agent <code>{agentId}</code> templates when deriving <code>transcriptPath</code>, so cross-agent session listings resolve to concrete agent session files instead of workspace-relative paths. (#24775) Thanks @martinfrancois.</li>
<li>Gateway/Control UI CSP: allow required Google Fonts origins in Control UI CSP. (#29279) Thanks @Glucksberg and @vincentkoc.</li>
<li>CLI/Install: add an npm-link fallback to fix CLI startup <code>Permission denied</code> failures (<code>exit 127</code>) on affected installs. (#17151) Thanks @sskyu and @vincentkoc.</li>
<li>Onboarding/Custom providers: improve verification reliability for slower local endpoints (for example Ollama) during setup. (#27380) Thanks @Sid-Qin.</li>
<li>Plugins/NPM spec install: fix npm-spec plugin installs when <code>npm pack</code> output is empty by detecting newly created <code>.tgz</code> archives in the pack directory. (#21039) Thanks @graysurf and @vincentkoc.</li>
<li>Plugins/Install: clear stale install errors when an npm package is not found so follow-up install attempts report current state correctly. (#25073) Thanks @dalefrieswthat.</li>
<li>Security/Feishu webhook ingress: bound unauthenticated webhook rate-limit state with stale-window pruning and a hard key cap to prevent unbounded pre-auth memory growth from rotating source keys. (#26050) Thanks @bmendonca3.</li>
<li>Gateway/macOS supervised restart: actively <code>launchctl kickstart -k</code> during intentional supervised restarts to bypass LaunchAgent <code>ThrottleInterval</code> delays, and fall back to in-process restart when kickstart fails. Landed from contributor PR #29078 by @cathrynlavery. Thanks @cathrynlavery.</li>
<li>Daemon/macOS TLS certs: default LaunchAgent service env <code>NODE_EXTRA_CA_CERTS</code> to <code>/etc/ssl/cert.pem</code> (while preserving explicit overrides) so HTTPS clients no longer fail with local-issuer errors under launchd. (#27915) Thanks @Lukavyi.</li>
<li>Discord/Components wildcard handlers: use distinct internal registration sentinel IDs and parse those sentinels as wildcard keys so select/user/role/channel/mentionable/modal interactions are not dropped by raw customId dedupe paths. Landed from contributor PR #29459 by @Sid-Qin. Thanks @Sid-Qin.</li>
<li>Feishu/Reaction notifications: add <code>channels.feishu.reactionNotifications</code> (<code>off | own | all</code>, default <code>own</code>) so operators can disable reaction ingress or allow all verified reaction events (not only bot-authored message reactions). (#28529) Thanks @cowboy129.</li>
<li>Feishu/Typing backoff: re-throw Feishu typing add/remove rate-limit and quota errors (<code>429</code>, <code>99991400</code>, <code>99991403</code>) and detect SDK non-throwing backoff responses so the typing keepalive circuit breaker can stop retries instead of looping indefinitely. (#28494) Thanks @guoqunabc.</li>
<li>Feishu/Zalo runtime logging: replace direct <code>console.log/error</code> usage in Feishu typing-indicator paths and Zalo monitor paths with runtime-gated logger calls so verbosity controls are respected while preserving typing backoff behavior. (#18841) Thanks @Clawborn.</li>
<li>Feishu/Group sender allowlist fallback: add global <code>channels.feishu.groupSenderAllowFrom</code> sender authorization for group chats, with per-group <code>groups.<id>.allowFrom</code> precedence and regression coverage for allow/block/precedence behavior. (#29174) Thanks @1MoreBuild.</li>
<li>Feishu/Docx append/write ordering: insert converted Docx blocks sequentially (single-block creates) so Feishu append/write preserves markdown block order instead of returning shuffled sections in asynchronous batch inserts. (#26172, #26022) Thanks @echoVic.</li>
<li>Feishu/Docx convert fallback chunking: recursively split oversized markdown chunks (including long no-heading sections) when <code>document.convert</code> hits content limits, while keeping fenced-code-aware split boundaries whenever possible. (#14402) Thanks @lml2468.</li>
<li>Feishu/API quota controls: add <code>typingIndicator</code> and <code>resolveSenderNames</code> config flags (top-level and per-account) so operators can disable typing reactions and sender-name lookup requests while keeping default behavior unchanged. (#10513) Thanks @BigUncle.</li>
<li>Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted <code>System:</code> context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.</li>
<li>Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.</li>
<li>Sessions/Internal routing: preserve established external <code>lastTo</code>/<code>lastChannel</code> routes for internal/non-deliverable turns, with added coverage for no-fallback internal routing behavior. Landed from contributor PR #30941 by @graysurf. Thanks @graysurf.</li>
<li>Control UI/Debug log layout: render Debug Event Log payloads at full width to prevent payload JSON from being squeezed into a narrow side column. Landed from contributor PR #30978 by @stozo04. Thanks @stozo04.</li>
<li>Auto-reply/NO_REPLY: strip <code>NO_REPLY</code> token from mixed-content messages instead of leaking raw control text to end users. Landed from contributor PR #31080 by @scoootscooob. Thanks @scoootscooob.</li>
<li>Install/npm: fix npm global install deprecation warnings. (#28318) Thanks @vincentkoc.</li>
<li>Update/Global npm: fallback to <code>--omit=optional</code> when global <code>npm update</code> fails so optional dependency install failures no longer abort update flows. (#24896) Thanks @xinhuagu and @vincentkoc.</li>
<li>Inbound metadata/Multi-account routing: include <code>account_id</code> in trusted inbound metadata so multi-account channel sessions can reliably disambiguate the receiving account in prompt context. Landed from contributor PR #30984 by @Stxle2. Thanks @Stxle2.</li>
<li>Model directives/Auth profiles: split <code>/model</code> profile suffixes at the first <code>@</code> after the last slash so email-based auth profile IDs (for example OAuth profile IDs) resolve correctly. Landed from contributor PR #30932 by @haosenwang1018. Thanks @haosenwang1018.</li>
<li>Cron/Delivery mode none: send explicit <code>delivery: { mode: "none" }</code> from cron editor for both add and update flows so previous announce delivery is actually cleared. Landed from contributor PR #31145 by @byungsker. Thanks @byungsker.</li>
<li>Cron editor viewport: make the sticky cron edit form independently scrollable with viewport-bounded height so lower fields/actions are reachable on shorter screens. Landed from contributor PR #31133 by @Sid-Qin. Thanks @Sid-Qin.</li>
<li>Agents/Thinking fallback: when providers reject unsupported thinking levels without enumerating alternatives, retry with <code>think=off</code> to avoid hard failure during model/provider fallback chains. Landed from contributor PR #31002 by @yfge. Thanks @yfge.</li>
<li>Ollama/Embedded runner base URL precedence: prioritize configured provider <code>baseUrl</code> over model defaults for embedded Ollama runs so Docker and remote-host setups avoid localhost fetch failures. (#30964) Thanks @stakeswky.</li>
<li>Agents/Failover reason classification: avoid false rate-limit classification from incidental <code>tpm</code> substrings by matching TPM as a standalone token/phrase and keeping auth-context errors on the auth path. Landed from contributor PR #31007 by @HOYALIM. Thanks @HOYALIM.</li>
<li>CLI/Cron: clarify <code>cron list</code> output by renaming <code>Agent</code> to <code>Agent ID</code> and adding a <code>Model</code> column for isolated agent-turn jobs. (#26259) Thanks @openperf.</li>
<li>Gateway/WS: close repeated post-handshake <code>unauthorized role:*</code> request floods per connection and sample duplicate rejection logs, preventing a single misbehaving client from degrading gateway responsiveness. (#20168) Thanks @acy103, @vibecodooor, and @vincentkoc.</li>
<li>Gateway/Auth: improve device-auth v2 migration diagnostics so operators get clearer guidance when legacy clients connect. (#28305) Thanks @vincentkoc.</li>
<li>CLI/Ollama config: allow <code>config set</code> for Ollama <code>apiKey</code> without predeclared provider config. (#29299) Thanks @vincentkoc.</li>
<li>Ollama/Autodiscovery: harden autodiscovery and warning behavior. (#29201) Thanks @marcodelpin and @vincentkoc.</li>
<li>Ollama/Context window: unify context window handling across discovery, merge, and OpenAI-compatible transport paths. (#29205) Thanks @Sid-Qin, @jimmielightner, and @vincentkoc.</li>
<li>Agents/Ollama: demote empty-discovery logging from <code>warn</code> to <code>debug</code> to reduce noisy warnings in normal edge-case discovery flows. (#26379) Thanks @byungsker.</li>
<li>Docker/Image permissions: normalize <code>/app/extensions</code>, <code>/app/.agent</code>, and <code>/app/.agents</code> to directory mode <code>755</code> and file mode <code>644</code> during image build so plugin discovery does not block inherited world-writable paths. (#30191) Fixes #30139. Thanks @edincampara.</li>
<li>OpenAI Responses/Compaction: rewrite and unify the OpenAI Responses store patches to treat empty <code>baseUrl</code> as non-direct, honor <code>compat.supportsStore=false</code>, and auto-inject server-side compaction <code>context_management</code> for compatible direct OpenAI models (with per-model opt-out/threshold overrides). Landed from contributor PRs #16930 (@OiPunk), #22441 (@EdwardWu7), and #25088 (@MoerAI). Thanks @OiPunk, @EdwardWu7, and @MoerAI.</li>
<li>Sandbox/Browser Docker: pass <code>OPENCLAW_BROWSER_NO_SANDBOX=1</code> to sandbox browser containers and bump sandbox browser security hash epoch so existing containers are recreated and pick up the env on upgrade. (#29879) Thanks @Lukavyi.</li>
<li>Usage normalization: clamp negative prompt/input token values to zero (including <code>prompt_tokens</code> alias inputs) so <code>/usage</code> and TUI usage displays cannot show nonsensical negative counts. Landed from contributor PR #31211 by @scoootscooob. Thanks @scoootscooob.</li>
<li>Secrets/Auth profiles: normalize inline SecretRef <code>token</code>/<code>key</code> values to canonical <code>tokenRef</code>/<code>keyRef</code> before persistence, and keep explicit <code>keyRef</code> precedence when inline refs are also present. Landed from contributor PR #31047 by @minupla. Thanks @minupla.</li>
<li>Tools/Edit workspace boundary errors: preserve the real <code>Path escapes workspace root</code> failure path instead of surfacing a misleading access/file-not-found error when editing outside workspace roots. Landed from contributor PR #31015 by @haosenwang1018. Thanks @haosenwang1018.</li>
<li>Browser/Open & navigate: accept <code>url</code> as an alias parameter for <code>open</code> and <code>navigate</code>. (#29260) Thanks @vincentkoc.</li>
<li>Codex/Usage window: label weekly usage window as <code>Week</code> instead of <code>Day</code>. (#26267) Thanks @Sid-Qin.</li>
<li>Signal/Sync message null-handling: treat <code>syncMessage</code> presence (including <code>null</code>) as sync envelope traffic so replayed sentTranscript payloads cannot bypass loop guards after daemon restart. Landed from contributor PR #31138 by @Sid-Qin. Thanks @Sid-Qin.</li>
<li>Infra/fs-safe: sanitize directory-read failures so raw <code>EISDIR</code> text never leaks to messaging surfaces, with regression tests for both root-scoped and direct safe reads. Landed from contributor PR #31205 by @polooooo. Thanks @polooooo.</li>
<li>Security/Compaction audit: remove the post-compaction audit injection message. (#28507) Thanks @fuller-stack-dev and @vincentkoc.</li>
<li>Web tools/RFC2544 fake-IP compatibility: allow RFC2544 benchmark range (<code>198.18.0.0/15</code>) for trusted web-tool fetch endpoints so proxy fake-IP networking modes do not trigger false SSRF blocks. Landed from contributor PR #31176 by @sunkinux. Thanks @sunkinux.</li>
<li>Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.</li>
<li>Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted <code>System:</code> context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.</li>
<li>Feishu/Multi-account + reply reliability: add <code>channels.feishu.defaultAccount</code> outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as <code>msg_type: "file"</code>, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.</li>
<li>Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.</li>
<li>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><ahref="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
<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>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: 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><ahref="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
<li>Highlight: External Secrets Management introduces a full <code>openclaw secrets</code> workflow (<code>audit</code>, <code>configure</code>, <code>apply</code>, <code>reload</code>) with runtime snapshot activation, strict <code>secrets apply</code> target-path validation, safer migration scrubbing, ref-only auth-profile support, and dedicated docs. (#26155) Thanks @joshavant.</li>
<li>ACP/Thread-bound agents: make ACP agents first-class runtimes for thread sessions with <code>acp</code> spawn/send dispatch integration, acpx backend bridging, lifecycle controls, startup reconciliation, runtime cleanup, and coalesced thread replies. (#23580) thanks @osolmaz.</li>
<li>Agents/Routing CLI: add <code>openclaw agents bindings</code>, <code>openclaw agents bind</code>, and <code>openclaw agents unbind</code> for account-scoped route management, including channel-only to account-scoped binding upgrades, role-aware binding identity handling, plugin-resolved binding account IDs, and optional account-binding prompts in <code>openclaw channels add</code>. (#27195) thanks @gumadeiras.</li>
<li>Codex/WebSocket transport: make <code>openai-codex</code> WebSocket-first by default (<code>transport: "auto"</code> with SSE fallback), keep explicit per-model/runtime transport overrides, and add regression coverage + docs for transport selection.</li>
<li>Onboarding/Plugins: let channel plugins own interactive onboarding flows with optional <code>configureInteractive</code> and <code>configureWhenConfigured</code> hooks while preserving the generic fallback path. (#27191) thanks @gumadeiras.</li>
<li>Android/Nodes: add Android <code>device</code> capability plus <code>device.status</code> and <code>device.info</code> node commands, including runtime handler wiring and protocol/registry coverage for device status/info payloads. (#27664) Thanks @obviyus.</li>
<li>Android/Nodes: add <code>notifications.list</code> support on Android nodes and expose <code>nodes notifications_list</code> in agent tooling for listing active device notifications. (#27344) thanks @obviyus.</li>
<li>Docs/Contributing: add Nimrod Gutman to the maintainer roster in <code>CONTRIBUTING.md</code>. (#27840) Thanks @ngutman.</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>Telegram/DM allowlist runtime inheritance: enforce <code>dmPolicy: "allowlist"</code><code>allowFrom</code> requirements using effective account-plus-parent config across account-capable channels (Telegram, Discord, Slack, Signal, iMessage, IRC, BlueBubbles, WhatsApp), and align <code>openclaw doctor</code> checks to the same inheritance logic so DM traffic is not silently dropped after upgrades. (#27936) Thanks @widingmarcus-cyber.</li>
<li>Delivery queue/recovery backoff: prevent retry starvation by persisting <code>lastAttemptAt</code> on failed sends and deferring recovery retries until each entry's <code>lastAttemptAt + backoff</code> window is eligible, while continuing to recover ready entries behind deferred ones. Landed from contributor PR #27710 by @Jimmy-xuzimo. Thanks @Jimmy-xuzimo.</li>
<li>Google Chat/Lifecycle: keep Google Chat <code>startAccount</code> pending until abort in webhook mode so startup is no longer interpreted as immediate exit, preventing auto-restart loops and webhook-target churn. (#27384) thanks @junsuwhy.</li>
<li>Temp dirs/Linux umask: force <code>0700</code> permissions after temp-dir creation and self-heal existing writable temp dirs before trust checks so <code>umask 0002</code> installs no longer crash-loop on startup. Landed from contributor PR #27860 by @stakeswky. (#27853) Thanks @stakeswky.</li>
<li>Nextcloud Talk/Lifecycle: keep <code>startAccount</code> pending until abort and stop the webhook monitor on shutdown, preventing <code>EADDRINUSE</code> restart loops when the gateway manages account lifecycle. (#27897)</li>
<li>Microsoft Teams/File uploads: acknowledge <code>fileConsent/invoke</code> immediately (<code>invokeResponse</code> before upload + file card send) so Teams no longer shows false "Something went wrong" timeout banners while upload completion continues asynchronously; includes updated async regression coverage. Landed from contributor PR #27641 by @scz2011.</li>
<li>Queue/Drain/Cron reliability: harden lane draining with guaranteed <code>draining</code> flag reset on synchronous pump failures, reject new queue enqueues during gateway restart drain windows (instead of silently killing accepted tasks), add <code>/stop</code> queued-backlog cutoff metadata with stale-message skipping (while avoiding cross-session native-stop cutoff bleed), and raise isolated cron <code>agentTurn</code> outer safety timeout to avoid false 10-minute timeout races against longer agent session timeouts. (#27407, #27332, #27427)</li>
<li>Typing/Main reply pipeline: always mark dispatch idle in <code>agent-runner</code> finalization so typing cleanup runs even when dispatcher <code>onIdle</code> does not fire, preventing stuck typing indicators after run completion. (#27250) Thanks @Sid-Qin.</li>
<li>Typing/TTL safety net: add max-duration guardrails to shared typing callbacks so stuck lifecycle edges auto-stop typing indicators even when explicit idle/cleanup signals are missed. (#27428) Thanks @Crpdim.</li>
<li>Typing/Cross-channel leakage: unify run-scoped typing suppression for cross-channel/internal-webchat routes, preserve current inbound origin as embedded run message channel context, harden shared typing keepalive with consecutive-failure circuit breaker edge-case handling, and enforce dispatcher completion/idle waits in extension dispatcher callsites (Feishu, Matrix, Mattermost, MSTeams) so typing indicators always clean up on success/error paths. Related: #27647, #27493, #27598. Supersedes/replaces draft PRs: #27640, #27593, #27540.</li>
<li>Telegram/sendChatAction 401 handling: add bounded exponential backoff + temporary local typing suppression after repeated unauthorized failures to stop unbounded <code>sendChatAction</code> retry loops that can trigger Telegram abuse enforcement and bot deletion. (#27415) Thanks @widingmarcus-cyber.</li>
<li>Telegram/Webhook startup: clarify webhook config guidance, allow <code>channels.telegram.webhookPort: 0</code> for ephemeral listener binding, and log both the local listener URL and Telegram-advertised webhook URL with the bound port. (#25732) thanks @huntharo.</li>
<li>Browser/Chrome extension handshake: bind relay WS message handling before <code>onopen</code> and add non-blocking <code>connect.challenge</code> response handling for gateway-style handshake frames, avoiding stuck <code>…</code> badge states when challenge frames arrive immediately on connect. Landed from contributor PR #22571 by @pandego. (#22553)</li>
<li>Browser/Extension relay init: dedupe concurrent same-port relay startup with shared in-flight initialization promises so callers await one startup lifecycle and receive consistent success/failure results. Landed from contributor PR #21277 by @HOYALIM. (Related #20688)</li>
<li>Browser/Fill relay + CLI parity: accept <code>act.fill</code> fields without explicit <code>type</code> by defaulting missing/empty <code>type</code> to <code>text</code> in both browser relay route parsing and <code>openclaw browser fill</code> CLI field parsing, so relay calls no longer fail when the model omits field type metadata. Landed from contributor PR #27662 by @Uface11. (#27296) Thanks @Uface11.</li>
<li>Feishu/Permission error dispatch: merge sender-name permission notices into the main inbound dispatch so one user message produces one agent turn/reply (instead of a duplicate permission-notice turn), with regression coverage. (#27381) thanks @byungsker.</li>
<li>Agents/Canvas default node resolution: when multiple connected canvas-capable nodes exist and no single <code>mac-*</code> candidate is selected, default to the first connected candidate instead of failing with <code>node required</code> for implicit-node canvas tool calls. Landed from contributor PR #27444 by @carbaj03. Thanks @carbaj03.</li>
<li>TUI/stream assembly: preserve streamed text across real tool-boundary drops without keeping stale streamed text when non-text blocks appear only in the final payload. Landed from contributor PR #27711 by @scz2011. (#27674)</li>
<li>Hooks/Internal <code>message:sent</code>: forward <code>sessionKey</code> on outbound sends from agent delivery, cron isolated delivery, gateway receipt acks, heartbeat sends, session-maintenance warnings, and restart-sentinel recovery so internal <code>message:sent</code> hooks consistently dispatch with session context, including <code>openclaw agent --deliver</code> runs resumed via <code>--session-id</code> (without explicit <code>--session-key</code>). Landed from contributor PR #27584 by @qualiobra. Thanks @qualiobra.</li>
<li>Pi image-token usage: stop re-injecting history image blocks each turn, process image references from the current prompt only, and prune already-answered user-image blocks in stored history to prevent runaway token growth. (#27602)</li>
<li>BlueBubbles/SSRF: auto-allowlist the configured <code>serverUrl</code> hostname for attachment fetches so localhost/private-IP BlueBubbles setups are no longer false-blocked by default SSRF checks. Landed from contributor PR #27648 by @lailoo. (#27599) Thanks @taylorhou for reporting.</li>
<li>Agents/Compaction + onboarding safety: prevent destructive double-compaction by stripping stale assistant usage around compaction boundaries, skipping post-compaction custom metadata writes in the same attempt, and cancelling safeguard compaction when there are no real conversation messages to summarize; harden workspace/bootstrap detection for memory-backed workspaces; and change <code>openclaw onboard --reset</code> default scope to <code>config+creds+sessions</code> (workspace deletion now requires <code>--reset-scope full</code>). (#26458, #27314) Thanks @jaden-clovervnd, @Sid-Qin, and @widingmarcus-cyber for fix direction in #26502, #26529, and #27492.</li>
<li>NO_REPLY suppression: suppress <code>NO_REPLY</code> before Slack API send and in sub-agent announce completion flow so sentinel text no longer leaks into user channels. Landed from contributor PRs #27529 (by @Sid-Qin) and #27535 (rewritten minimal landing by maintainers). (#27387, #27531)</li>
<li>Matrix/Group sender identity: preserve sender labels in Matrix group inbound prompt text (<code>BodyForAgent</code>) for both channel and threaded messages, and align group envelopes with shared inbound sender-prefix formatting so first-person requests resolve against the current sender. (#27401) thanks @koushikxd.</li>
<li>Auto-reply/Streaming: suppress only exact <code>NO_REPLY</code> final replies while still filtering streaming partial sentinel fragments (<code>NO_</code>, <code>NO_RE</code>, <code>HEARTBEAT_...</code>) so substantive replies ending with <code>NO_REPLY</code> are delivered and partial silent tokens do not leak during streaming. (#19576) Thanks @aldoeliacim.</li>
<li>Auto-reply/Inbound metadata: add a readable <code>timestamp</code> field to conversation info and ignore invalid/out-of-range timestamp values so prompt assembly never crashes on malformed timestamp inputs. (#17017) thanks @liuy.</li>
<li>Typing/Run completion race: prevent post-run keepalive ticks from re-triggering typing callbacks by guarding <code>triggerTyping()</code> with <code>runComplete</code>, with regression coverage for no-restart behavior during run-complete/dispatch-idle boundaries. (#27413) Thanks @widingmarcus-cyber.</li>
<li>Typing/Dispatch idle: force typing cleanup when <code>markDispatchIdle</code> never arrives after run completion, avoiding leaked typing keepalive loops in cron/announce edges. Landed from contributor PR #27541 by @Sid-Qin. (#27493)</li>
<li>Telegram/Inline buttons: allow callback-query button handling in groups (including <code>/models</code> follow-up buttons) when group policy authorizes the sender, by removing the redundant callback allowlist gate that blocked open-policy groups. (#27343) Thanks @GodsBoy.</li>
<li>Telegram/Streaming preview: when finalizing without an existing preview message, prime pending preview text with final answer before stop-flush so users do not briefly see stale 1-2 word fragments (for example <code>no</code> before <code>no problem</code>). (#27449) Thanks @emanuelst for the original fix direction in #19673.</li>
<li>Browser/Extension relay CORS: handle <code>/json*</code><code>OPTIONS</code> preflight before auth checks, allow Chrome extension origins, and return extension-origin CORS headers on relay HTTP responses so extension token validation no longer fails cross-origin. Landed from contributor PR #23962 by @miloudbelarebia. (#23842)</li>
<li>Browser/Extension relay auth: allow <code>?token=</code> query-param auth on relay <code>/json*</code> endpoints (consistent with relay WebSocket auth) so curl/devtools-style <code>/json/version</code> and <code>/json/list</code> probes work without requiring custom headers. Landed from contributor PR #26015 by @Sid-Qin. (#25928)</li>
<li>Browser/Extension relay shutdown: flush pending extension-request timers/rejections during relay <code>stop()</code> before socket/server teardown so in-flight extension waits do not survive shutdown windows. Landed from contributor PR #24142 by @kevinWangSheng.</li>
<li>Browser/Extension relay reconnect resilience: keep CDP clients alive across brief MV3 extension disconnect windows, wait briefly for extension reconnect before failing in-flight CDP commands, and only tear down relay target/client state after reconnect grace expires. Landed from contributor PR #27617 by @davidemanuelDEV.</li>
<li>Browser/Route decode hardening: guard malformed percent-encoding in relay target action routes and browser route-param decoding so crafted <code>%</code> paths return <code>400</code> instead of crashing/unhandled URI decode failures. Landed from contributor PR #11880 by @Yida-Dev.</li>
<li>Feishu/Inbound message metadata: include inbound <code>message_id</code> in <code>BodyForAgent</code> on a dedicated metadata line so agents can reliably correlate and act on media/message operations that require message IDs, with regression coverage. (#27253) thanks @xss925175263.</li>
<li>Feishu/Doc tools: route <code>feishu_doc</code> and <code>feishu_app_scopes</code> through the active agent account context (with explicit <code>accountId</code> override support) so multi-account agents no longer default to the first configured app, with regression coverage for context routing and explicit override behavior. (#27338) thanks @AaronL725.</li>
<li>LINE/Inline directives auth: gate directive parsing (<code>/model</code>, <code>/think</code>, <code>/verbose</code>, <code>/reasoning</code>, <code>/queue</code>) on resolved authorization (<code>command.isAuthorizedSender</code>) so <code>commands.allowFrom</code>-authorized LINE senders are not silently stripped when raw <code>CommandAuthorized</code> is unset. Landed from contributor PR #27248 by @kevinWangSheng. (#27240)</li>
<li>Onboarding/Gateway: seed default Control UI <code>allowedOrigins</code> for non-loopback binds during onboarding (<code>localhost</code>/<code>127.0.0.1</code> plus custom bind host) so fresh non-loopback setups do not fail startup due to missing origin policy. (#26157) thanks @stakeswky.</li>
<li>Docker/GCP onboarding: reduce first-build OOM risk by capping Node heap during <code>pnpm install</code>, reuse existing gateway token during <code>docker-setup.sh</code> reruns so <code>.env</code> stays aligned with config, auto-bootstrap Control UI allowed origins for non-loopback Docker binds, and add GCP docs guidance for tokenized dashboard links + pairing recovery commands. (#26253) Thanks @pandego.</li>
<li>CLI/Gateway <code>--force</code> in non-root Docker: recover from <code>lsof</code> permission failures (<code>EACCES</code>/<code>EPERM</code>) by falling back to <code>fuser</code> kill + probe-based port checks, so <code>openclaw gateway --force</code> works for default container <code>node</code> user flows. (#27941)</li>
<li>Gateway/Bind visibility: emit a startup warning when binding to non-loopback addresses so operators get explicit exposure guidance in runtime logs. (#25397) thanks @let5sne.</li>
<li>Sessions cleanup/Doctor: add <code>openclaw sessions cleanup --fix-missing</code> to prune store entries whose transcript files are missing, including doctor guidance and CLI coverage. Landed from contributor PR #27508 by @Sid-Qin. (#27422)</li>
<li>Doctor/State integrity: ignore metadata-only slash routing sessions when checking recent missing transcripts so <code>openclaw doctor</code> no longer reports false-positive transcript-missing warnings for <code>*:slash:*</code> keys. (#27375) thanks @gumadeiras.</li>
<li>CLI/Gateway status: force local <code>gateway status</code> probe host to <code>127.0.0.1</code> for <code>bind=lan</code> so co-located probes do not trip non-loopback plaintext WebSocket checks. (#26997) thanks @chikko80.</li>
<li>CLI/Gateway auth: align <code>gateway run --auth</code> parsing/help text with supported gateway auth modes by accepting <code>none</code> and <code>trusted-proxy</code> (in addition to <code>token</code>/<code>password</code>) for CLI overrides. (#27469) thanks @s1korrrr.</li>
<li>CLI/Daemon status TLS probe: use <code>wss://</code> and forward local TLS certificate fingerprint for TLS-enabled gateway daemon probes so <code>openclaw daemon status</code> works with <code>gateway.bind=lan</code> + <code>gateway.tls.enabled=true</code>. (#24234) thanks @liuy.</li>
<li>Podman/Default bind: change <code>run-openclaw-podman.sh</code> default gateway bind from <code>lan</code> to <code>loopback</code> and document explicit LAN opt-in with Control UI origin configuration. (#27491) thanks @robbyczgw-cla.</li>
<li>Daemon/macOS launchd: forward proxy env vars into supervised service environments, keep LaunchAgent <code>KeepAlive=true</code> semantics, and harden restart sequencing to <code>print -> bootout -> wait old pid exit -> bootstrap -> kickstart</code>. (#27276) thanks @frankekn.</li>
<li>Gateway/macOS restart-loop hardening: detect OpenClaw-managed supervisor markers during SIGUSR1 restart handoff, clean stale gateway PIDs before <code>/restart</code> launchctl/systemctl triggers, and set LaunchAgent <code>ThrottleInterval=60</code> to bound launchd retry storms during lock-release races. Landed from contributor PRs #27655 (@taw0002), #27448 (@Sid-Qin), and #27650 (@kevinWangSheng). (#27605, #27590, #26904, #26736)</li>
<li>Models/MiniMax auth header defaults: set <code>authHeader: true</code> for both onboarding-generated MiniMax API providers and implicit built-in MiniMax (<code>minimax</code>, <code>minimax-portal</code>) provider templates so first requests no longer fail with MiniMax <code>401 authentication_error</code> due to missing <code>Authorization</code> header. Landed from contributor PRs #27622 by @riccoyuanft and #27631 by @kevinWangSheng. (#27600, #15303)</li>
<li>Auth/Auth profiles: normalize <code>auth-profiles.json</code> alias fields (<code>mode -> type</code>, <code>apiKey -> key</code>) before credential validation so entries copied from <code>openclaw.json</code> auth examples are no longer silently dropped. (#26950) thanks @byungsker.</li>
<li>Models/Profile suffix parsing: centralize trailing <code>@profile</code> parsing and only treat <code>@</code> as a profile separator when it appears after the final <code>/</code>, preserving model IDs like <code>openai/@cf/...</code> and <code>openrouter/@preset/...</code> across <code>/model</code> directive parsing and allowlist model resolution, with regression coverage.</li>
<li>Models/OpenAI Codex config schema parity: accept <code>openai-codex-responses</code> in the config model API schema and TypeScript <code>ModelApi</code> union, with regression coverage for config validation. Landed from contributor PR #27501 by @AytuncYildizli. Thanks @AytuncYildizli.</li>
<li>Agents/Models config: preserve agent-level provider <code>apiKey</code> and <code>baseUrl</code> during merge-mode <code>models.json</code> updates when agent values are present. (#27293) thanks @Sid-Qin.</li>
<li>Azure OpenAI Responses: force <code>store=true</code> for <code>azure-openai-responses</code> direct responses API calls to avoid multi-turn 400 failures. Landed from contributor PR #27499 by @polarbear-Yang. (#27497)</li>
<li>Security/Node exec approvals: require structured <code>commandArgv</code> approvals for <code>host=node</code>, enforce versioned <code>systemRunBindingV1</code> matching for argv/cwd/session/agent/env context with fail-closed behavior on missing/mismatched bindings, and add <code>GIT_EXTERNAL_DIFF</code> to blocked host env keys. This ships in the next npm release (<code>2026.2.26</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Plugin channel HTTP auth: normalize protected <code>/api/channels</code> path checks against canonicalized request paths (case + percent-decoding + slash normalization), resolve encoded dot-segment traversal variants, and fail closed on malformed <code>%</code>-encoded channel prefixes so alternate-path variants cannot bypass gateway auth. This ships in the next npm release (<code>2026.2.26</code>). Thanks @zpbrent for reporting.</li>
<li>Security/Gateway node pairing: pin paired-device <code>platform</code>/<code>deviceFamily</code> metadata across reconnects and bind those fields into device-auth signatures, so reconnect metadata spoofing cannot expand node command allowlists without explicit repair pairing. This ships in the next npm release (<code>2026.2.26</code>). Thanks @76embiid21 for reporting.</li>
<li>Security/Sandbox path alias guard: reject broken symlink targets by resolving through existing ancestors and failing closed on out-of-root targets, preventing workspace-only <code>apply_patch</code> writes from escaping sandbox/workspace boundaries via dangling symlinks. This ships in the next npm release (<code>2026.2.26</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Workspace FS boundary aliases: harden canonical boundary resolution for non-existent-leaf symlink aliases while preserving valid in-root aliases, preventing first-write workspace escapes via out-of-root symlink targets. This ships in the next npm release (<code>2026.2.26</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Config includes: harden <code>$include</code> file loading with verified-open reads, reject hardlinked include aliases, and enforce include file-size guardrails so config include resolution remains bounded to trusted in-root files. This ships in the next npm release (<code>2026.2.26</code>). Thanks @zpbrent for reporting.</li>
<li>Security/Node exec approvals hardening: freeze immutable approval-time execution plans (<code>argv</code>/<code>cwd</code>/<code>agentId</code>/<code>sessionKey</code>) via <code>system.run.prepare</code>, enforce those canonical plan values during approval forwarding/execution, and reject mutable parent-symlink cwd paths during approval-plan building to prevent approval bypass via symlink rebind. This ships in the next npm release (<code>2026.2.26</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Microsoft Teams media fetch: route Graph message/hosted-content/attachment fetches and auth-scope fallback attachment downloads through shared SSRF-guarded fetch paths, and centralize hostname-suffix allowlist policy helpers in the plugin SDK to remove channel/plugin drift. This ships in the next npm release (<code>2026.2.26</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Voice Call (Twilio): bind webhook replay + manager dedupe identity to authenticated request material, remove unsigned <code>i-twilio-idempotency-token</code> trust from replay/dedupe keys, and thread verified request identity through provider parse flow to harden cross-provider event dedupe. This ships in the next npm release (<code>2026.2.26</code>). Thanks @tdjackey for reporting.</li>
<li>Security/Exec approvals forwarding: prefer turn-source channel/account/thread metadata when resolving approval delivery targets so stale session routes do not misroute approval prompts.</li>
<li>Security/Pairing multi-account isolation: enforce account-scoped pairing allowlists and pending-request storage across core + extension message channels while preserving channel-scoped defaults for the default account. This ships in the next npm release (<code>2026.2.26</code>). Thanks @tdjackey for reporting and @gumadeiras for implementation.</li>
<li>Config/Plugins entries: treat unknown <code>plugins.entries.*</code> ids as startup warnings (ignored stale keys) instead of hard validation failures that can crash-loop gateway boot. Landed from contributor PR #27506 by @Sid-Qin. (#27455)</li>
<li>Telegram native commands: degrade command registration on <code>BOT_COMMANDS_TOO_MUCH</code> by retrying with fewer commands instead of crash-looping startup sync. Landed from contributor PR #27512 by @Sid-Qin. (#27456)</li>
<li>Web tools/Proxy: route <code>web_search</code> provider HTTP calls (Brave, Perplexity, xAI, Gemini, Kimi), redirect resolution, and <code>web_fetch</code> through a shared proxy-aware SSRF guard path so gateway installs behind <code>HTTP_PROXY</code>/<code>HTTPS_PROXY</code>/<code>ALL_PROXY</code> no longer fail with transport <code>fetch failed</code> errors. (#27430) thanks @kevinWangSheng.</li>
<li>Gateway shared-auth scopes: preserve requested operator scopes for shared-token clients when device identity is unavailable, instead of clearing scopes during auth handling. Landed from contributor PR #27498 by @kevinWangSheng. (#27494)</li>
<li>Cron/Hooks isolated routing: preserve canonical <code>agent:*</code> session keys in isolated runs so already-qualified keys are not double-prefixed (for example <code>agent:main:main</code> no longer becomes <code>agent:main:agent:main:main</code>). Landed from contributor PR #27333 by @MaheshBhushan. (#27289, #27282)</li>
<li>Channels/Multi-account config: when adding a non-default channel account to a single-account top-level channel setup, move existing account-scoped top-level single-account values into <code>channels.<channel>.accounts.default</code> before writing the new account so the original account keeps working without duplicated account values at channel root; <code>openclaw doctor --fix</code> now repairs previously mixed channel account shapes the same way. (#27334) thanks @gumadeiras.</li>
<li>iOS/Talk mode: stop injecting the voice directive hint into iOS Talk prompts and remove the Voice Directive Hint setting, reducing model bias toward tool-style TTS directives and keeping relay responses text-first by default. (#27543) thanks @ngutman.</li>
<li>CI/Windows: shard the Windows <code>checks-windows</code> test lane into two matrix jobs and honor explicit shard index overrides in <code>scripts/test-parallel.mjs</code> to reduce CI critical-path wall time. (#27234) Thanks @joshavant.</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><ahref="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
`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
@ -211,7 +215,7 @@ 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`, `notifications.actions`, `app.update`).
- Asserts command contracts (success or expected deterministic error for safe-invalid calls like `sms.send` and `notifications.actions`).