fix(mattermost): carry thread context to non-inbound reply paths#44283
Conversation
Greptile SummaryThis PR fixes Mattermost thread routing for non-inbound reply paths (TUI/WebUI turns, tool-call callbacks, subagent responses) by closing three gaps: extracting
Confidence Score: 5/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: CHANGELOG.md
Line: 190
Comment:
**CHANGELOG entry describes the guard, not the primary fix**
The entry focuses on the defensive aspect ("keep cleared route threads cleared … instead of reviving stale `origin.threadId` metadata") but omits the user-visible primary change: non-inbound reply paths (TUI/WebUI turns, tool-call callbacks, subagent responses) now correctly route to the originating Mattermost thread instead of posting to the channel root.
A developer scanning the changelog to understand why their TUI replies were landing in the channel root would not find this entry. Consider rewording to lead with the positive fix, e.g.:
```suggestion
- Mattermost/thread routing: non-inbound reply paths (TUI/WebUI turns, tool-call callbacks, subagent responses) now correctly route to the originating Mattermost thread when `replyToMode: "all"` is active; also prevents stale `origin.threadId` metadata from resurrecting cleared thread routes. (#44283) thanks @teconomix
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 498de0f |
2a3a6e2 to
e557f20
Compare
CI status22/23 checks pass. The 1 remaining failure ( The
This same failure is present on the last merged PR (#44274) before this one: https://github.com/openclaw/openclaw/actions/runs/23017167737/job/66843452718 The 6 skipped checks (macOS, iOS, Android, docs, release, labels) are expected for a code-only PR. All checks relevant to the changes in this PR pass:
|
|
@mukhtharcm — pinging you since you merged #29587 today. This PR is a direct follow-up: it fixes the cases that With Happy to iterate if you have questions or suggestions. |
|
@greptile-apps please re review |
c80b26b to
e2deb91
Compare
Manual test results — 2026-03-13Tested on prod (v3.12) with Test setup
Results
Test 2 confirms the core fix: a The |
498de0f to
16ceeee
Compare
Fixes a bug where replies triggered from TUI/WebUI or by agent-initiated sends (tool callbacks, subagent responses, message tool) land in the Mattermost channel root instead of the originating thread. Root cause: three gaps in the outbound routing path for turns not directly triggered by an inbound Mattermost message: 1. dispatch-from-config.ts: sendPayloadAsync passed ctx.MessageThreadId, which is undefined for webchat/TUI turns. Now falls back to the session entry's deliveryContext.threadId (the lastThreadId stored when the session was first created from an inbound Mattermost message). 2. route-reply.ts: threadId was only forwarded as replyToId for Slack. Mattermost uses the same root_id mechanic, so the same fallback now applies to Mattermost too. 3. channel.ts (Mattermost outbound): sendText/sendMedia only consumed replyToId, ignoring threadId. Added threadId as a defense-in-depth fallback for any path that sets threadId but not replyToId. All three gaps in a single PR. Tests added for each fix path. Fixes openclaw#39759
lastThreadId is normalised from origin.threadId by loadSessionStore, so using it as a fallback here would re-acquire the same stale thread that deliveryContext.threadId was intentionally cleared from. Only deliveryContext.threadId is safe to use as the restored route thread. Also strengthens the regression test to include the normalised lastThreadId value that the real store produces, so the guard is verified against the actual production data shape. Addresses review feedback from chatgpt-codex-connector.
Do not recover route thread ids from the normalised session store in non-inbound reply paths. Store normalisation can fold origin.threadId back into lastThreadId/deliveryContext, which resurrects stale thread routing after delivery was intentionally cleared. Instead, restore thread context only from: - ctx.MessageThreadId (active inbound turn), or - the active thread-scoped session key (:thread: / :topic:) Also updates dispatch tests to verify that stale origin/store thread metadata cannot override a non-thread session key, while a thread-scoped session key still restores the correct route thread.
16ceeee to
2846a6c
Compare
|
Merged via squash.
Thanks @teconomix! |
* main: (724 commits) refactor: remove channel shim directories, point all imports to extensions (openclaw#45967) refactor: move Discord channel implementation to extensions/ (openclaw#45660) refactor: move Telegram channel implementation to extensions/ (openclaw#45635) refactor(slack): move Slack channel code to extensions/slack/src/ (openclaw#45621) refactor: move WhatsApp channel implementation to extensions/ (openclaw#45725) refactor: move iMessage channel to extensions/imessage (openclaw#45539) refactor(signal): move Signal channel code to extensions/signal/src/ (openclaw#45531) refactor: make OutboundSendDeps dynamic with channel-ID keys (openclaw#45517) fix(mattermost): carry thread context to non-inbound reply paths (openclaw#44283) test(ci): isolate cron heartbeat delivery cases fix: annotate shared failover mocks (openclaw#39820) thanks @lupuletic fix: tighten runner failover test types (openclaw#39820) thanks @lupuletic style: format probe regression test (openclaw#39820) thanks @lupuletic fix: harden wrapped rate-limit failover (openclaw#39820) thanks @lupuletic fix: move cause-chain traversal before timeout heuristic (review feedback) fix(agents): normalize abort-wrapped RESOURCE_EXHAUSTED into failover errors (openclaw#11972) build: refresh lockfile for plugin sync build: sync plugins for 2026.3.14 build: prepare 2026.3.14 cycle test: harden parallels beta smoke flows ...
…nclaw#44283) Merged via squash. Prepared head SHA: 2846a6c Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
) Merged via squash. Prepared head SHA: 2846a6c Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
* test: add parallels windows smoke harness * fix: force-stop lingering gateway client sockets * test: share gateway route auth helpers * test: share browser route test helpers * test: share gateway status auth fixtures * test: share models list forward compat fixtures * fix: tighten bonjour whitespace error coverage * docs: reorder changelog highlights by user impact * test: tighten proxy fetch helper coverage * test: tighten path guard helper coverage * test: tighten warning filter coverage * test: tighten wsl detection coverage * test: tighten system run command normalization coverage * fix(feishu): preserve non-ASCII filenames in file uploads (#33912) (#34262) * fix(feishu): preserve non-ASCII filenames in file uploads (#33912) * style(feishu): format media test file * fix(feishu): preserve UTF-8 filenames in file uploads (openclaw#34262) thanks @fabiaodemianyang --------- Co-authored-by: Robin Waslander <[email protected]> * test: tighten is-main helper coverage * test: tighten json file helper coverage * fix: resolve current ci regressions * test: tighten backoff abort coverage * docs(changelog): note upcoming security fixes * test: tighten bonjour ciao coverage * test: tighten channel activity account isolation * test: tighten update channel display precedence * test: tighten node list parse fallback coverage * test: tighten package tag prefix matching * test: tighten outbound identity normalization * test: tighten outbound session context coverage * macOS: respect exec-approvals.json settings in gateway prompter (#13707) Fix macOS gateway exec approvals to respect exec-approvals.json. This updates the macOS gateway prompter to resolve per-agent exec approval policy before deciding whether to show UI, use agentId for policy lookup, honor askFallback when prompts cannot be presented, and resolve no-prompt decisions from the configured security policy instead of hardcoded allow-once behavior. It also adds regression coverage for ask-policy and allowlist-fallback behavior, plus a changelog entry for the fix. Co-authored-by: ImLukeF <[email protected]> * fix: tighten target error hint coverage * test: tighten prototype key matching * test: tighten hostname normalization coverage * fix(ui): keep oversized chat replies readable (#45559) * fix(ui): keep oversized chat replies readable * Update ui/src/ui/markdown.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix(ui): preserve oversized markdown whitespace --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test: tighten openclaw exec env coverage * fix: tighten pairing token blank handling * test: tighten target error hint trimming * test: tighten node shell platform normalization * fix(gateway/ui): restore control-ui auth bypass and classify connect failures (#45512) Merged via squash. Prepared head SHA: 42b5595edec71897b479b3bbaa94bcb4ac6fab17 Co-authored-by: sallyom <[email protected]> Co-authored-by: BunsDev <[email protected]> Reviewed-by: @BunsDev * fix(macos): prevent PortGuard from killing Docker Desktop in remote mode (#13798) fix(macos): prevent PortGuardian from killing Docker Desktop in remote mode (#6755) PortGuardian.sweep() was killing non-SSH processes holding the gateway port in remote mode. When the gateway runs in a Docker container, `com.docker.backend` owns the port-forward, so this could shut down Docker Desktop entirely. Changes: - accept any process on the gateway port in remote mode - add a defense-in-depth guard to skip kills in remote mode - update remote-mode port diagnostics/reporting to match - add regression coverage for Docker and local-mode behavior - add a changelog entry for the fix Co-Authored-By: ImLukeF <[email protected]> * test: fix current ci regressions * test: share outbound action runner helpers * test: share telegram monitor startup helpers * refactor: share self hosted provider plugin helpers * test: share outbound delivery helpers * refactor: share onboarding diagnostics type * refactor: share delimited channel entry parsing * refactor: share zalo send context validation * refactor: share terminal note wrapping * refactor: share tts request setup * refactor: share gateway timeout parsing * refactor: share session send context lines * refactor: share memory tool builders * refactor: share browser console result formatting * refactor: share pinned sandbox entry finalization * refactor: share tool result char estimation * refactor: share agent tool fixture helpers * test: share compaction retry timer helpers * test: share embedded workspace attempt helpers * refactor: share whatsapp outbound adapter base * refactor: share zalo status issue helpers * test: share whatsapp outbound poll fixtures * refactor: share telegram reply chunk threading * refactor: share daemon install cli setup * fix: widen telegram reply progress typing * refactor: share slack text truncation * refactor: share allowlist wildcard matching * refactor: declone model picker model ref parsing * refactor: share dual text command gating * test: share startup account lifecycle helpers * test: share status issue assertion helpers * fix: restore imessage control command flag * test: share web fetch header helpers * refactor: share session tool context setup * test: share memory tool helpers * refactor: share request url resolution * Changelog: credit embedded runner queue deadlock fix * fix(voicewake): avoid crash on foreign transcript ranges * refactor(voicewake): mark transcript parameter unused * docs(changelog): note voice wake crash fix * fix: harden gateway status rpc smoke * test: add parallels linux smoke harness * fix(sessions): create transcript file on chat.inject when missing (#36645) `chat.inject` called `appendAssistantTranscriptMessage` with `createIfMissing: false`, causing a hard error when the transcript file did not exist on disk despite having a valid `transcriptPath` in session metadata. This commonly happens with ACP oneshot/run sessions where the session entry is created but the transcript file is not yet materialized. The fix is a one-character change: `createIfMissing: true`. The `ensureTranscriptFile` helper already handles directory creation and file initialization safely. Fixes #36170 Co-authored-by: Claude Opus 4.6 <[email protected]> * fix: harden discord guild allowlist resolution * chore: update dependencies * Plugins: fail fast on channel and binding collisions (#45628) * Plugins: reject duplicate channel ids * Bindings: reject duplicate adapter registration * Plugins: fail on export id mismatch * feat: add node-connect skill * test: share directory runtime helpers * refactor: share open allowFrom config checks * test: share send cfg threading helpers * refactor: reduce extension channel setup duplication * refactor: share extension channel status summaries * test: share feishu startup mock modules * test: share plugin api test harness * refactor: share extension monitor runtime setup * refactor: share extension deferred and runtime helpers * test: share sandbox fs bridge seeded workspace * test: share subagent gateway mock setup * test: share models config merge helpers * test: share workspace skills snapshot helpers * test: share context lookup helpers * test: share timeout failover assertions * test: share model selection config helpers * test: share provider discovery auth fixtures * test: share subagent announce timeout helpers * test: share workspace skill test helpers * refactor: share exec host approval helpers * test: share oauth profile fixtures * test: share memory search config helpers * fix(macos): align minimum Node.js version with runtime guard (22.16.0) (#45640) * macOS: align minimum Node.js version with runtime guard * macOS: add boundary and failure-message coverage for RuntimeLocator * docs: add changelog note for the macOS runtime locator fix * credit: original fix direction from @sumleo, cleaned up and rebased in #45640 by @ImLukeF * fix(agents): preserve blank local custom-provider API keys after onboarding Co-authored-by: Xinhua Gu <[email protected]> * fix(browser): harden existing-session driver validation and session lifecycle (#45682) * fix(browser): harden existing-session driver validation, session lifecycle, and code quality Fix config validation rejecting existing-session profiles that lack cdpPort/cdpUrl (they use Chrome MCP auto-connect instead). Fix callTool tearing down the MCP session on tool-level errors (element not found, script error), which caused expensive npx re-spawns. Skip unnecessary CDP port allocation for existing-session profiles. Remove redundant ensureChromeMcpAvailable call in isReachable. Extract shared ARIA role sets (INTERACTIVE_ROLES, CONTENT_ROLES, STRUCTURAL_ROLES) into snapshot-roles.ts so both the Playwright and Chrome MCP snapshot paths stay in sync. Add usesChromeMcp capability flag and replace ~20 scattered driver === "existing-session" string checks with the centralized flag. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(browser): harden existing-session driver validation and session lifecycle (#45682) (thanks @odysseus0) --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * fix(ci): repair helper typing regressions * fix: default Android TLS setup codes to port 443 * fix: unblock discord startup on deploy rate limits * fix(feishu): add early event-level dedup to prevent duplicate replies (#43762) * fix(feishu): add early event-level dedup to prevent duplicate replies Add synchronous in-memory dedup at EventDispatcher handler level using message_id as key with 5-minute TTL and 2000-entry cap. This catches duplicate events immediately when they arrive from the Lark SDK — before the inbound debouncer or processing queue — preventing the race condition where two concurrent dispatches enter the pipeline before either records the messageId in the downstream dedup layer. Fixes the root cause reported in #42687. * fix(feishu): correct inverted dedup condition check() returns false on first call (new key) and true on subsequent calls (duplicate). The previous `!check()` guard was inverted — dropping every first delivery and passing all duplicates. Remove the negation so the guard correctly drops duplicates. * fix(feishu): simplify eventDedup key — drop redundant accountId prefix eventDedup is already scoped per account (one instance per registerEventHandlers call), so the accountId prefix in the cache key is redundant. Use `evt:${messageId}` instead. * fix(feishu): share inbound processing claim dedupe --------- Co-authored-by: Tak Hoffman <[email protected]> * fix(models): apply Gemini model-id normalization to google-vertex provider (#42435) * fix(models): apply Gemini model-id normalization to google-vertex provider The existing normalizeGoogleModelId() (which maps e.g. gemini-3.1-flash-lite to gemini-3.1-flash-lite-preview) was only applied when the provider was "google". Users configuring google-vertex/gemini-3.1-flash-lite would get a "missing" model because the -preview suffix was never appended. Extend the normalization to google-vertex in both model-selection (parseModelRef path) and normalizeProviders (config normalization path). Ref: https://github.com/openclaw/openclaw/issues/36838 Ref: https://github.com/openclaw/openclaw/pull/36918#issuecomment-4032732959 * fix(models): normalize google-vertex flash-lite * fix(models): place unreleased changelog entry last * fix(models): place unreleased changelog entry before releases * fix(browser): add browser session selection * build(android): add auto-bump signed aab release script * build(android): strip unused dnsjava resolver service before R8 * test(discord): align rate limit error mock with carbon * docs: fix changelog formatting * fix: keep exec summaries inline * build: shrink Android app release bundle * Gateway: treat scope-limited probe RPC as degraded reachability (#45622) * Gateway: treat scope-limited probe RPC as degraded * Docs: clarify gateway probe degraded scope output * test: fix CI type regressions in gateway and outbound suites * Tests: fix Node24 diffs theme loading and Windows assertions * Tests: fix extension typing after main rebase * Tests: fix Windows CI regressions after rebase * Tests: normalize executable path assertions on Windows * Tests: remove duplicate gateway daemon result alias * Tests: stabilize Windows approval path assertions * Tests: fix Discord rate-limit startup fixture typing * Tests: use Windows-friendly relative exec fixtures --------- Co-authored-by: Mainframe <[email protected]> * build: upload Android native debug symbols * fix(browser): prefer user profile over chrome relay * chore: bump pi to 0.58.0 * test: harden parallels all-os smoke harness * fix: keep windows onboarding logs ascii-safe * docs: reorder unreleased changelog by impact * build: prepare 2026.3.13-beta.1 * ci: add npm token fallback for npm releases * fix(gateway): bound unanswered client requests (#45689) * fix(gateway): bound unanswered client requests * fix(gateway): skip default timeout for expectFinal requests * fix(gateway): preserve gateway call timeouts * fix(gateway): localize request timeout policy * fix(gateway): clamp explicit request timeouts * fix(gateway): clamp default request timeout * Revert "Browser: scope nested batch failures in switch" This reverts commit aaeb348bb7cbbaebe14a471776909bff129499a3. * build: prepare 2026.3.13 release * fix: keep android canvas home visible after restart * chore: update appcast for 2026.3.13 release * fix(browser): restore batch playwright dispatch * feat(cron): support custom session IDs and auto-bind to current session (#16511) feat(cron): support persistent session targets for cron jobs (#9765) Add support for `sessionTarget: "current"` and `session:<id>` so cron jobs can bind to the creating session or a persistent named session instead of only `main` or ephemeral `isolated` sessions. Also: - preserve custom session targets across reloads and restarts - update gateway validation and normalization for the new target forms - add cron coverage for current/custom session targets and fallback behavior - fix merged CI regressions in Discord and diffs tests - add a changelog entry for the new cron session behavior Co-authored-by: kkhomej33-netizen <[email protected]> Co-authored-by: ImLukeF <[email protected]> * test: harden parallels beta smoke flows * build: prepare 2026.3.14 cycle * build: sync plugins for 2026.3.14 * build: refresh lockfile for plugin sync * fix(agents): normalize abort-wrapped RESOURCE_EXHAUSTED into failover errors (#11972) * fix: move cause-chain traversal before timeout heuristic (review feedback) * fix: harden wrapped rate-limit failover (openclaw#39820) thanks @lupuletic * style: format probe regression test (openclaw#39820) thanks @lupuletic * fix: tighten runner failover test types (openclaw#39820) thanks @lupuletic * fix: annotate shared failover mocks (openclaw#39820) thanks @lupuletic * test(ci): isolate cron heartbeat delivery cases * fix(mattermost): carry thread context to non-inbound reply paths (#44283) Merged via squash. Prepared head SHA: 2846a6cfa959019d3ed811ccafae6b757db3bdf3 Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm * refactor: make OutboundSendDeps dynamic with channel-ID keys (#45517) * refactor: make OutboundSendDeps dynamic with channel-ID keys Replace hardcoded per-channel send fields (sendTelegram, sendDiscord, etc.) with a dynamic index-signature type keyed by channel ID. This unblocks moving channel implementations to extensions without breaking the outbound dispatch contract. - OutboundSendDeps and CliDeps are now { [channelId: string]: unknown } - Each outbound adapter resolves its send fn via bracket access with cast - Lazy-loading preserved via createLazySender with module cache - Delete 6 deps-send-*.runtime.ts one-liner re-export files - Harden guardrail scan against deleted-but-tracked files * fix: preserve outbound send-deps compatibility * style: fix formatting issues (import order, extra bracket, trailing whitespace) * fix: resolve type errors from dynamic OutboundSendDeps in tests and extension * fix: remove unused OutboundSendDeps import from deliver.test-helpers * refactor(signal): move Signal channel code to extensions/signal/src/ (#45531) Move all Signal channel implementation files from src/signal/ to extensions/signal/src/ and replace originals with re-export shims. This continues the channel plugin migration pattern used by other extensions, keeping backward compatibility via shims while the real code lives in the extension. - Copy 32 .ts files (source + tests) to extensions/signal/src/ - Transform all relative import paths for the new location - Create 2-line re-export shims in src/signal/ for each moved file - Preserve existing extension files (channel.ts, runtime.ts, etc.) - Change tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to support cross-boundary re-exports from extensions/ * refactor: move iMessage channel to extensions/imessage (#45539) * refactor: move WhatsApp channel implementation to extensions/ (#45725) * refactor: move WhatsApp channel from src/web/ to extensions/whatsapp/ Move all WhatsApp implementation code (77 source/test files + 9 channel plugin files) from src/web/ and src/channels/plugins/*/whatsapp* to extensions/whatsapp/src/. - Leave thin re-export shims at all original locations so cross-cutting imports continue to resolve - Update plugin-sdk/whatsapp.ts to only re-export generic framework utilities; channel-specific functions imported locally by the extension - Update vi.mock paths in 15 cross-cutting test files - Rename outbound.ts -> send.ts to match extension naming conventions and avoid false positive in cfg-threading guard test - Widen tsconfig.plugin-sdk.dts.json rootDir to support shim->extension cross-directory references Part of the core-channels-to-extensions migration (PR 6/10). * style: format WhatsApp extension files * fix: correct stale import paths in WhatsApp extension tests Fix vi.importActual, test mock, and hardcoded source paths that weren't updated during the file move: - media.test.ts: vi.importActual path - onboarding.test.ts: vi.importActual path - test-helpers.ts: test/mocks/baileys.js path - monitor-inbox.test-harness.ts: incomplete media/store mock - login.test.ts: hardcoded source file path - message-action-runner.media.test.ts: vi.mock/importActual path * refactor(slack): move Slack channel code to extensions/slack/src/ (#45621) Move all Slack channel implementation files from src/slack/ to extensions/slack/src/ and replace originals with shim re-exports. This follows the extension migration pattern for channel plugins. - Copy all .ts files to extensions/slack/src/ (preserving directory structure: monitor/, http/, monitor/events/, monitor/message-handler/) - Transform import paths: external src/ imports use relative paths back to src/, internal slack imports stay relative within extension - Replace all src/slack/ files with shim re-exports pointing to the extension copies - Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." so the DTS build can follow shim chains into extensions/ - Update write-plugin-sdk-entry-dts.ts re-export path accordingly - Preserve extensions/slack/index.ts, package.json, openclaw.plugin.json, src/channel.ts, src/runtime.ts, src/channel.test.ts (untouched) * refactor: move Telegram channel implementation to extensions/ (#45635) * refactor: move Telegram channel implementation to extensions/telegram/src/ Move all Telegram channel code (123 files + 10 bot/ files + 8 channel plugin files) from src/telegram/ and src/channels/plugins/*/telegram.ts to extensions/telegram/src/. Leave thin re-export shims at original locations so cross-cutting src/ imports continue to resolve. - Fix all relative import paths in moved files (../X/ -> ../../../src/X/) - Fix vi.mock paths in 60 test files - Fix inline typeof import() expressions - Update tsconfig.plugin-sdk.dts.json rootDir to "." for cross-directory DTS - Update write-plugin-sdk-entry-dts.ts for new rootDir structure - Move channel plugin files with correct path remapping * fix: support keyed telegram send deps * fix: sync telegram extension copies with latest main * fix: correct import paths and remove misplaced files in telegram extension * fix: sync outbound-adapter with main (add sendTelegramPayloadMessages) and fix delivery.test import path * refactor: move Discord channel implementation to extensions/ (#45660) * refactor: move Discord channel implementation to extensions/discord/src/ Move all Discord source files from src/discord/ to extensions/discord/src/, following the extension migration pattern. Source files in src/discord/ are replaced with re-export shims. Channel-plugin files from src/channels/plugins/*/discord* are similarly moved and shimmed. - Copy all .ts source files preserving subdirectory structure (monitor/, voice/) - Move channel-plugin files (actions, normalize, onboarding, outbound, status-issues) - Fix all relative imports to use correct paths from new location - Create re-export shims at original locations for backward compatibility - Delete test files from shim locations (tests live in extension now) - Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to accommodate extension files outside src/ - Update write-plugin-sdk-entry-dts.ts to match new declaration output paths * fix: add importOriginal to thread-bindings session-meta mock for extensions test * style: fix formatting in thread-bindings lifecycle test * refactor: remove channel shim directories, point all imports to extensions (#45967) * refactor: remove channel shim directories, point all imports to extensions Delete the 6 backward-compat shim directories (src/telegram, src/discord, src/slack, src/signal, src/imessage, src/web) that were re-exporting from extensions. Update all 112+ source files to import directly from extensions/{channel}/src/ instead of through the shims. Also: - Move src/channels/telegram/ (allow-from, api) to extensions/telegram/src/ - Fix outbound adapters to use resolveOutboundSendDep (fixes 5 pre-existing TS errors) - Update cross-extension imports (src/web/media.js → extensions/whatsapp/src/media.js) - Update vitest, tsdown, knip, labeler, and script configs for new paths - Update guard test allowlists for extension paths After this, src/ has zero channel-specific implementation code — only the generic plugin framework remains. * fix: update raw-fetch guard allowlist line numbers after shim removal * refactor: document direct extension channel imports * test: mock transcript module in delivery helpers * fix(zai): align explicit coding endpoint setup with detected model defaults (#45969) * fix: align Z.AI coding onboarding with endpoint docs * fix: align Z.AI coding onboarding with endpoint docs (#45969) * docs: mark memory bootstrap change as breaking * fix(ui): session dropdown shows label instead of key (#45130) Merged via squash. Prepared head SHA: 0255e3971b06b3641e6b26590eaa22a900079517 Co-authored-by: luzhidong <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf * feat: add --force-document to message.send for Telegram (bypass sendPhoto + image optimizer) (#45111) * feat: add --force-document to message.send for Telegram Adds --force-document CLI flag to bypass sendPhoto and use sendDocument instead, avoiding Telegram image compression for PNG/image files. - TelegramSendOpts: add forceDocument field - send.ts: skip sendPhoto when forceDocument=true (mediaSender pattern) - ChannelOutboundContext: add forceDocument field - telegramOutbound.sendMedia: pass forceDocument to sendMessageTelegram - ChannelHandlerParams / DeliverOutboundPayloadsCoreParams: add forceDocument - createChannelOutboundContextBase: propagate forceDocument - outbound-send-service.ts: add forceDocument to executeSendAction params - message-action-runner.ts: read forceDocument from params - message.ts: add forceDocument to MessageSendParams - register.send.ts: add --force-document CLI option * fix: pass forceDocument through telegram action dispatch path The actual send path goes through dispatchChannelMessageAction -> telegramMessageActions.handleAction -> handleTelegramAction, not deliverOutboundPayloads. forceDocument was not being read in readTelegramSendParams or passed to sendMessageTelegram. * fix: apply forceDocument to GIF branch to avoid sendAnimation * fix: add disable_content_type_detection=true to sendDocument for --force-document * fix: add forceDocument to buildSendSchema for agent discoverability * fix: scope telegram force-document detection * test: fix heartbeat target helper typing * fix: skip image optimization when forceDocument is set * fix: persist forceDocument in WAL queue for crash-recovery replay * test: tighten heartbeat target test entry typing --------- Co-authored-by: thepagent <[email protected]> Co-authored-by: Frank Yang <[email protected]> * Update CONTRIBUTING.md * ci: add dry-run gate to npm release workflow * ci: make npm release preview more verbose * ci: preserve manual npm release approval delays * docs: clarify npm release preview and publish flow * ci: switch npm release workflow to trusted publishing * chore: add code owners for npm release paths * ci: enforce calver freshness on npm publish * ci: move Docker release to GitHub-hosted runners (#46247) * ci: move docker release to GitHub-hosted runners * ci: annotate docker release runner guardrails * Add /btw side questions (#45444) * feat(agent): add /btw side questions * fix(agent): gate and log /btw reviews * feat(btw): isolate side-question delivery * test(reply): update route reply runtime mocks * fix(btw): complete side-result delivery across clients * fix(gateway): handle streamed btw side results * fix(telegram): unblock btw side questions * fix(reply): make external btw replies explicit * fix(chat): keep btw side results ephemeral in internal history * fix(btw): address remaining review feedback * fix(chat): preserve btw history on mobile refresh * fix(acp): keep btw replies out of prompt history * refactor(btw): narrow side questions to live channels * fix(btw): preserve channel typing indicators * fix(btw): keep side questions isolated in chat * fix(outbound): restore typed channel send deps * fix(btw): avoid blocking replies on transcript persistence * fix(btw): keep side questions fast * docs(commands): document btw slash command * docs(changelog): add btw side questions entry * test(outbound): align session transcript mocks * ci: add manual backfill support to Docker release (#46269) * ci: add docker release backfill workflow * ci: add manual backfill support to docker release * ci: keep docker latest tags off manual backfills * Fix configure startup stalls from outbound send-deps imports (#46301) * fix: avoid configure startup plugin stalls * fix: credit configure startup changelog entry * fix(btw): stop persisting side questions (#46328) * fix(btw): stop persisting side questions * docs(btw): document side-question behavior * Slack: preserve interactive reply blocks in DMs (#45890) * Slack: forward reply blocks in DM delivery * Slack: preserve reply blocks in preview finalization * Slack: cover block-only DM replies * Changelog: note Slack interactive reply fix * docs(nav): move btw to end of built-in tools (#46416) * Docs: sweep recent user-facing updates (#46424) * Docs: document Telegram force-document sends * Docs: note Telegram document send behavior * Docs: clarify memory file precedence * Docs: align default AGENTS memory guidance * Docs: update workspace FAQ memory note * Docs: document gateway status require-rpc * Docs: add require-rpc to gateway CLI index * docs: add ademczuk to maintainers list * fix(feishu): clear stale streamingStartPromise on card creation failure Fixes #43322 * fix(feishu): clear stale streamingStartPromise on card creation failure When FeishuStreamingSession.start() throws (HTTP 400), the catch block sets streaming = null but leaves streamingStartPromise dangling. The guard in startStreaming() checks streamingStartPromise first, so all future deliver() calls silently skip streaming - the session locks permanently. Clear streamingStartPromise in the catch block so subsequent messages can retry streaming instead of dropping all future replies. Fixes #43322 * test(feishu): wrap push override in try/finally for cleanup safety * fix(gateway): skip device pairing when auth.mode=none Fixes #42931 When gateway.auth.mode is set to "none", authentication succeeds with method "none" but sharedAuthOk remains false because the auth-context only recognises token/password/trusted-proxy methods. This causes all pairing-skip conditions to fail, so Control UI browser connections get closed with code 1008 "pairing required" despite auth being disabled. Short-circuit the skipPairing check: if the operator explicitly disabled authentication, device pairing (which is itself an auth mechanism) must also be bypassed. Fixes #42931 * fix(auth): clear stale lockout state when user re-authenticates Fixes #43057 * fix(auth): clear stale lockout on re-login Clear stale `auth_permanent` and `billing` disabled state for all profiles matching the target provider when `openclaw models auth login` is invoked, so users locked out by expired or revoked OAuth tokens can recover by re-authenticating instead of waiting for the cooldown timer. Uses the agent-scoped store (`loadAuthProfileStoreForRuntime`) for correct multi-agent profile resolution and wraps the housekeeping in try/catch so corrupt store files never block re-authentication. Fixes #43057 * test(auth): remove unnecessary non-null assertions oxlint no-unnecessary-type-assertion: invocationCallOrder[0] already returns number, not number | undefined. * fix(ci): update vitest configs after channel move to extensions/ (openclaw#46066) Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: scoootscooob <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> * fix(gateway/cli): relax local backend self-pairing and harden launchd restarts (#46290) Signed-off-by: sallyom <[email protected]> * ci: allow fallback npm correction tags (#46486) * fix(agents): restore usage tracking for non-native openai-completions providers Fixes #46142 Stop forcing supportsUsageInStreaming=false on non-native openai-completions endpoints. Most OpenAI-compatible APIs (DashScope, DeepSeek, Groq, Together, etc.) handle stream_options: { include_usage: true } correctly. The blanket disable broke usage/cost tracking for all non-OpenAI providers. supportsDeveloperRole is still forced off for non-native endpoints since the developer message role is genuinely OpenAI-specific. Users on backends that reject stream_options can opt out with compat.supportsUsageInStreaming: false in their model config. Fixes #46142 * Fix test environment regressions on main * fix(node): remove debug console.log on node host startup Fixes #46411 Fixes #46411 * style(gateway): fix oxfmt formatting and remove unused test helper * Fix full local gate on main * Security: add secops ownership for sensitive paths (#46440) * Meta: add secops ownership for sensitive paths * Docs: restrict Codeowners-managed security edits * Meta: guide agents away from secops-owned paths * Meta: broaden secops CODEOWNERS coverage * Meta: narrow secops workflow ownership * Docs: add config drift baseline statefile (#45891) * Docs: add config drift statefile generator * Docs: generate config drift baseline * CI: move config docs drift runner into workflow sanity * Docs: emit config drift baseline json * Docs: commit config drift baseline json * Docs: wire config baseline into release checks * Config: fix baseline drift walker coverage * Docs: regenerate config drift baselines * UI: surface gateway restart reasons in dashboard disconnect state (#46580) * UI: surface gateway shutdown reason * UI: add gateway restart disconnect tests * Changelog: add dashboard restart reason fix * UI: cover reconnect shutdown state * fix(deps): update package yauzl * feat(browser): add headless existing-session MCP support esp for Linux/Docker/VPS (#45769) * fix(browser): prefer managed default profile in headless mode * test(browser): cover headless default profile fallback * feat(browser): support headless MCP profile resolution * feat(browser): add headless and target-url Chrome MCP modes * feat(browser): allow MCP target URLs in profile creation * docs(browser): document headless MCP existing-session flows * fix(browser): restore playwright browser act helpers * fix(browser): preserve strict selector actions * docs(changelog): add existing-session MCP note * revert: 9bffa3422c4dc13f5c72ab5d2813cc287499cc14 * browser: drop chrome-relay auto-creation, simplify to user profile only (#46596) Merged via squash. Prepared head SHA: 74becc8f7dac245a345d2c7d549f604344df33fd Co-authored-by: odysseus0 <[email protected]> Co-authored-by: odysseus0 <[email protected]> Reviewed-by: @odysseus0 * chore: regenerate config baseline (#46598) * browser: drop headless/remote MCP attach modes, simplify existing-session to autoConnect-only (#46628) * fix(feishu): fetch thread context so AI can see bot replies in topic threads (#45254) * fix(feishu): fetch thread context so AI can see bot replies in topic threads When a user replies in a Feishu topic thread, the AI previously could only see the quoted parent message but not the bot's own prior replies in the thread. This made multi-turn conversations in threads feel broken. - Add `threadId` (omt_xxx) to `FeishuMessageInfo` and `getMessageFeishu` - Add `listFeishuThreadMessages()` using `container_id_type=thread` API to fetch all messages in a thread including bot replies - In `handleFeishuMessage`, fetch ThreadStarterBody and ThreadHistoryBody for topic session modes and pass them to the AI context - Reuse quoted message result when rootId === parentId to avoid redundant API calls; exclude root message from thread history to prevent duplication - Fall back to inbound ctx.threadId when rootId is absent or API fails - Fetch newest messages first (ByCreateTimeDesc + reverse) so long threads keep the most recent turns instead of the oldest Co-Authored-By: Claude Opus 4.6 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> * fix: persist context-engine auto-compaction counts (#42629) Merged via squash. Prepared head SHA: df8f292039e27edec45b8ed2ad65ab0ac7f56194 Co-authored-by: uf-hy <[email protected]> Co-authored-by: jalehman <[email protected]> Reviewed-by: @jalehman * feat(feishu): add reasoning stream support to streaming cards (openclaw#46029) Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: day253 <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> * Heartbeat: add isolatedSession option for fresh session per heartbeat run (#46634) Reuses the cron isolated session pattern (resolveCronSession with forceNew) to give each heartbeat a fresh session with no prior conversation history. Reduces per-heartbeat token cost from ~100K to ~2-5K tokens. Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * revert: restore supportsUsageInStreaming=false default for non-native endpoints Reverts #46500. Breaks Ollama, LM Studio, TGI, LocalAI, Mistral API - these backends reject stream_options with 400/422. This reverts commit bb06dc7cc9e71fbac29d7888d64323db2acec7ca. * Fix Codex CLI auth profile sync (#45353) Merged via squash. Prepared head SHA: e5432ec4e1685a78ca7251bc71f26c1f17355a15 Co-authored-by: Gugu-sugar <[email protected]> Co-authored-by: grp06 <[email protected]> Reviewed-by: @grp06 * fix(zalo): use plugin-sdk export for webhook client IP resolution (openclaw#46549) Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: Tomáš Dinh <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> * fix(gateway): remove re-introduced auth.mode=none pairing bypass The revert of #43478 (commit 39b4185d0b) was silently undone by 3704293e6f which was based on a branch that included the original change. This removes the auth.mode=none skipPairing condition again. The blanket skip was too broad - it disabled pairing for ALL websocket clients, not just Control UI behind reverse proxies. * fix(feishu): keep sender-scoped thread bootstrap across id types (#46651) * feat(webchat): add toggle to hide tool calls and thinking blocks (#20317) thanks @nmccready Merged via maintainer override after review.\n\nRed required checks are unrelated to this PR; local inspection found no blocker in the diff. * fix(zalouser): stop inheriting dm allowlist for groups (#46663) * docs: remove dead security README nav entry (#46675) Merged via squash. Prepared head SHA: 63331a54b8a6d50950a6ca85774fa1d915cd4e8d Co-authored-by: velvet-shark <[email protected]> Co-authored-by: velvet-shark <[email protected]> Reviewed-by: @velvet-shark * feat(provider): support new model zai glm-5-turbo, performs better for openclaw (openclaw#46670) Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: tomsun28 <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> * fix: validate edge tts output file is non-empty before reporting success (#43385) thanks @Huntterxx Merged after review.\n\nSmall, scoped fix: treat 0-byte Edge TTS output as failure so provider fallback can continue. * Add Feishu reactions and card action support (#46692) * Add Feishu reactions and card action support * Tighten Feishu action handling * feat(feishu): structured cards with identity header, note footer, and streaming enhancements (openclaw#29938) Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: nszhsl <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> * Docs: fix MDX markers blocking page refreshes (#46695) Merged via squash. Prepared head SHA: 56b25a9fb3acc1a3befbf33c28a6d27df8aca8ef Co-authored-by: velvet-shark <[email protected]> Co-authored-by: velvet-shark <[email protected]> Reviewed-by: @velvet-shark * fix(plugins): prefer explicit installs over bundled duplicates (#46722) * fix(plugins): prefer explicit installs over bundled duplicates * test(feishu): mock structured card sends in outbound tests * fix(plugins): align duplicate diagnostics with loader precedence * feat(gateway): make health monitor stale threshold and max restarts configurable (openclaw#42107) Verified: - pnpm exec vitest --run src/config/config-misc.test.ts -t "gateway.channelHealthCheckMinutes" - pnpm exec vitest --run src/gateway/server-channels.test.ts -t "health monitor" - pnpm exec vitest --run src/gateway/channel-health-monitor.test.ts src/gateway/server/readiness.test.ts - pnpm exec vitest --run extensions/feishu/src/outbound.test.ts - pnpm exec tsc --noEmit Co-authored-by: rstar327 <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> * docker: add lsof to runtime image (#46636) * fix(gateway): harden health monitor account gating (#46749) * gateway: harden health monitor account gating * gateway: tighten health monitor account-id guard * feat(android): add dark theme (#46249) * Android: add mobile dark theme * Android: fix remaining dark mode card surfaces * Android: address dark mode review comments * fix(android): theme onboarding flow * fix: add Android dark theme coverage (#46249) (thanks @sibbl) --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix(android): theme popup surfaces * docs: reorder unreleased changelog * External content: sanitize wrapped metadata (#46816) * fix(openrouter): silently dropped images for new OpenRouter models — runtime capability detection (#45824) * fix: fetch OpenRouter model capabilities at runtime for unknown models When an OpenRouter model is not in the built-in static snapshot from pi-ai, the fallback hardcodes input: ["text"], silently dropping images. Query the OpenRouter API at runtime to detect actual capabilities (image support, reasoning, context window) for models not in the built-in list. Results are cached in memory for 1 hour. On API failure/timeout, falls back to text-only (no regression). * feat(openrouter): add disk cache for OpenRouter model capabilities Persist the OpenRouter model catalog to ~/.openclaw/cache/openrouter-models.json so it survives process restarts. Cache lookup order: 1. In-memory Map (instant) 2. On-disk JSON file (avoids network on restart) 3. OpenRouter API fetch (populates both layers) Also triggers a background refresh when a model is not found in the cache, in case it was newly added to OpenRouter. * refactor(openrouter): remove pre-warm, use pure lazy-load with disk cache - Remove eager ensureOpenRouterModelCache() from run.ts - Remove TTL — model capabilities are stable, no periodic re-fetching - Cache lookup: in-memory → disk → API fetch (only when needed) - API is only called when no cache exists or a model is not found - Disk cache persists across gateway restarts * fix(openrouter): address review feedback - Fix timer leak: move clearTimeout to finally block - Fix modality check: only check input side of "->" separator to avoid matching image-generation models (text->image) - Use resolveStateDir() instead of hardcoded homedir()/.openclaw - Separate cache dir and filename constants - Add utf-8 encoding to writeFileSync for consistency - Add data validation when reading disk cache * ci: retrigger checks * fix: preload unknown OpenRouter model capabilities before resolve * fix: accept top-level OpenRouter max token metadata * fix: update changelog for OpenRouter runtime capability lookup (#45824) (thanks @DJjjjhao) * fix: avoid redundant OpenRouter refetches and preserve suppression guards --------- Co-authored-by: Ayaan Zaidi <[email protected]> * fix(context): skip eager warmup for non-model CLI commands * Tlon: honor explicit empty allowlists and defer cite expansion (#46788) * Tlon: fail closed on explicit empty allowlists * Tlon: preserve cited content for owner DMs * macOS: restrict canvas agent actions to trusted surfaces (#46790) * macOS: restrict canvas agent actions to trusted surfaces * Changelog: note trusted macOS canvas actions * macOS: encode allowed canvas schemes as JSON * feat: make compaction timeout configurable via agents.defaults.compaction.timeoutSeconds (#46889) * feat: make compaction timeout configurable via agents.defaults.compaction.timeoutSeconds The hardcoded 5-minute (300s) compaction timeout causes large sessions to enter a death spiral where compaction repeatedly fails and the session grows indefinitely. This adds agents.defaults.compaction.timeoutSeconds to allow operators to override the compaction safety timeout. Default raised to 900s (15min) which is sufficient for sessions up to ~400k tokens. The resolved timeout is also used for the session write lock duration so locks don't expire before compaction completes. Fixes #38233 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * fix: align compaction timeouts with abort handling * fix: land compaction timeout handling (#46889) (thanks @asyncjason) --------- Co-authored-by: Jason Separovic <[email protected]> Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> * fix: harden compaction timeout follow-ups * Docs: fix stale Clawdbot branding in agent workflow file (#46963) Co-authored-by: webdevpraveen <[email protected]> * docs: replace outdated Clawdbot references with OpenClaw in skill docs (#41563) Update 5 references to the old "Clawdbot" name in skills/apple-reminders/SKILL.md and skills/imsg/SKILL.md. Co-authored-by: imanisynapse <[email protected]> * Docs: switch README logo to SVG assets (#47049) * fix: Disable strict mode tools for non-native openai-completions compatible APIs (#45497) Merged via squash. Prepared head SHA: 20fe05fe747821455c020521e5c2072b368713d8 Co-authored-by: sahancava <[email protected]> Co-authored-by: frankekn <[email protected]> Reviewed-by: @frankekn * fix: forward forceDocument through sendPayload path (follow-up to #45111) (#47119) Merged via squash. Prepared head SHA: d791190f8303c664cea8737046eb653c0514e939 Co-authored-by: thepagent <[email protected]> Reviewed-by: @frankekn * fix(android): support android node `calllog.search` (#44073) * fix(android): support android node `calllog.search` * fix(android): support android node calllog.search * fix(android): wire callLog through shared surfaces * fix: land Android callLog support (#44073) (thanks @lxk7280) --------- Co-authored-by: lixuankai <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> * fix(whatsapp): restore append recency filter lost in extensions refactor, handle Long timestamps (#42588) Merged via squash. Prepared head SHA: 8ce59bb7153c1717dad4022e1cfd94857be53324 Co-authored-by: MonkeyLeeT <[email protected]> Co-authored-by: scoootscooob <[email protected]> Reviewed-by: @scoootscooob * fix(web): handle 515 Stream Error during WhatsApp QR pairing (#27910) * fix(web): handle 515 Stream Error during WhatsApp QR pairing getStatusCode() never unwrapped the lastDisconnect wrapper object, so login.errorStatus was always undefined and the 515 restart path in restartLoginSocket was dead code. - Add err.error?.output?.statusCode fallback to getStatusCode() - Export waitForCredsSaveQueue() so callers can await pending creds - Await creds flush in restartLoginSocket before creating new socket Fixes #3942 * test: update session mock for getStatusCode unwrap + waitForCredsSaveQueue Mirror the getStatusCode fix (err.error?.output?.statusCode fallback) in the test mock and export waitForCredsSaveQueue so restartLoginSocket tests work correctly. * fix(web): scope creds save queue per-authDir to avoid cross-account blocking The credential save queue was a single global promise chain shared by all WhatsApp accounts. In multi-account setups, a slow save on one account blocked credential writes and 515 restart recovery for unrelated accounts. Replace the global queue with a per-authDir Map so each account's creds serialize independently. waitForCredsSaveQueue() now accepts an optional authDir to wait on a single account's queue, or waits on all when omitted. Co-Authored-By: Claude Opus 4.6 <[email protected]> * 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) <[email protected]> * 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 <[email protected]> * fix: clear flush timeout handle and assert creds queue in test Co-Authored-By: Claude <[email protected]> * fix: evict settled credsSaveQueues entries to prevent unbounded growth Co-Authored-By: Claude <[email protected]> * fix: share WhatsApp 515 creds flush handling (#27910) (thanks @asyncjason) --------- Co-authored-by: Jason Separovic <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> * Deduplicate repeated tool call IDs for OpenAI-compatible APIs (#40996) Merged via squash. Prepared head SHA: 38d80483592de63866b07cd61edc7f41ffd56021 Co-authored-by: xaeon2026 <[email protected]> Co-authored-by: frankekn <[email protected]> Reviewed-by: @frankekn * fix(gateway): skip Control UI pairing when auth.mode=none (closes #42931) (#47148) When auth is completely disabled (mode=none), requiring device pairing for Control UI operator sessions adds friction without security value since any client can already connect without credentials. Add authMode parameter to shouldSkipControlUiPairing so the bypass fires only for Control UI + operator role + auth.mode=none. This avoids the #43478 regression where a top-level OR disabled pairing for ALL websocket clients. * fix: preserve Telegram word boundaries when rechunking HTML (#47274) * fix: preserve Telegram chunk word boundaries * fix: address Telegram chunking review feedback * fix: preserve Telegram retry separators * fix: preserve Telegram chunking boundaries (#47274) * tests: stabilize sessions_spawn mock/import ordering * chore: retrigger CI for flaky channel lane * tests: format sessions spawn depth limits spec * chore: retrigger CI for flaky channels lane * chore: retrigger CI for flaky channels lane * chore: retrigger CI for flaky channels lane * chore: retrigger CI for flaky channels lane * chore: retrigger CI for flaky channels lane --------- Signed-off-by: sallyom <[email protected]> Co-authored-by: Peter Steinberger <[email protected]> Co-authored-by: fabiaodemianyang <[email protected]> Co-authored-by: Robin Waslander <[email protected]> Co-authored-by: Steven <[email protected]> Co-authored-by: ImLukeF <[email protected]> Co-authored-by: Val Alexander <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Sally O'Malley <[email protected]> Co-authored-by: sallyom <[email protected]> Co-authored-by: Jaehoon You <[email protected]> Co-authored-by: Vincent Koc <[email protected]> Co-authored-by: 2233admin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> Co-authored-by: Frank Yang <[email protected]> Co-authored-by: Xinhua Gu <[email protected]> Co-authored-by: George Zhang <[email protected]> Co-authored-by: yunweibang <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> Co-authored-by: scoootscooob <[email protected]> Co-authored-by: Muhammed Mukhthar CM <[email protected]> Co-authored-by: Josh Avant <[email protected]> Co-authored-by: Mainframe <[email protected]> Co-authored-by: kkhomej33-netizen <[email protected]> Co-authored-by: kkhomej33-netizen <[email protected]> Co-authored-by: Catalin Lupuleti <[email protected]> Co-authored-by: Darshil <[email protected]> Co-authored-by: Teconomix <[email protected]> Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Co-authored-by: luzhidong <[email protected]> Co-authored-by: luzhidong <[email protected]> Co-authored-by: altaywtf <[email protected]> Co-authored-by: thepagent <[email protected]> Co-authored-by: thepagent <[email protected]> Co-authored-by: Radek Sienkiewicz <[email protected]> Co-authored-by: Onur Solmaz <[email protected]> Co-authored-by: Nimrod Gutman <[email protected]> Co-authored-by: Andrew Demczuk <[email protected]> Co-authored-by: odysseus0 <[email protected]> Co-authored-by: Josh Lehman <[email protected]> Co-authored-by: Brian Qu <[email protected]> Co-authored-by: ufhy <[email protected]> Co-authored-by: jalehman <[email protected]> Co-authored-by: day253 <[email protected]> Co-authored-by: Gugu-sugar <[email protected]> Co-authored-by: Gugu-sugar <[email protected]> Co-authored-by: grp06 <[email protected]> Co-authored-by: Tomáš Dinh <[email protected]> Co-authored-by: nmccready <[email protected]> Co-authored-by: velvet-shark <[email protected]> Co-authored-by: Tomsun28 <[email protected]> Co-authored-by: tomsun28 <[email protected]> Co-authored-by: Hiago Silva <[email protected]> Co-authored-by: songlei <[email protected]> Co-authored-by: nszhsl <[email protected]> Co-authored-by: rstar327 <[email protected]> Co-authored-by: rstar327 <[email protected]> Co-authored-by: Sebastian Schubotz <[email protected]> Co-authored-by: Jinhao Dong <[email protected]> Co-authored-by: Jason <[email protected]> Co-authored-by: Jason Separovic <[email protected]> Co-authored-by: Praveen K Singh <[email protected]> Co-authored-by: webdevpraveen <[email protected]> Co-authored-by: SkunkWorks0x <[email protected]> Co-authored-by: imanisynapse <[email protected]> Co-authored-by: Onur Solmaz <[email protected]> Co-authored-by: Sahan <[email protected]> Co-authored-by: frankekn <[email protected]> Co-authored-by: thepagent <[email protected]> Co-authored-by: Ace Lee <[email protected]> Co-authored-by: lixuankai <[email protected]> Co-authored-by: Ted Li <[email protected]> Co-authored-by: MonkeyLeeT <[email protected]> Co-authored-by: 助爪 <[email protected]> Co-authored-by: xaeon2026 <[email protected]>
* fix(imessage): sanitize SCP remote path to prevent shell metacharacter injection References GHSA-g2f6-pwvx-r275. * fix: tighten runtime status coverage * fix: tighten package json coverage * fix: tighten bonjour error coverage * fix: tighten package tag coverage * fix: tighten machine name coverage * test: tighten gateway process argv coverage * test: tighten install safe path coverage * test: tighten tmp dir fallback coverage * test: tighten brew helper coverage * test: add archive staging helper coverage * fix: tighten device identity helper coverage * UI: fix chat context notice icon sizing (openclaw#45533) * UI: fix chat context notice icon sizing * Update ui/src/ui/views/chat.browser.test.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * UI: tighten chat context notice regression test --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test: tighten transport ready coverage * test: tighten channel summary coverage * test: tighten safe bin policy coverage * fix: tighten safe bin runtime policy coverage * fix: tighten duration formatter coverage * fix: harden browser existing-session flows * test: tighten fetch helper coverage * test: extract provider usage load coverage * test: extract fingerprint helper coverage * test: add gateway tls helper coverage * test: extract archive helper coverage * test: extract apns relay coverage * test: extract apns auth helper coverage * test: extract apns store coverage * test: add device bootstrap coverage * test: add state migration coverage * test: tighten apns send coverage * fix(ui): stop dashboard chat history reload storm (openclaw#45541) * UI: stop dashboard chat history reload storm * Changelog: add PR number for chat reload fix * fix: resolve branch typecheck regressions * test: tighten fetch and channel summary coverage * fix: retry Telegram inbound media downloads over IPv4 fallback (openclaw#45327) * fix: retry telegram inbound media downloads over ipv4 * fix: preserve telegram media retry errors * fix: redact telegram media fetch errors * fix: harden bootstrap and transport ready coverage * test: expand browser existing-session coverage * fix: tighten package tag and channel summary coverage * fix: tighten runtime status detail coverage * fix: support bun lockfile detection * test: add home relative path coverage * test: tighten json file lock coverage * test: tighten path prepend casing coverage * refactor: share models command helpers * test: share cli help version assertions * test: share venice model response fixtures * test: share browser loopback auth error assertions * test: share config pruning defaults setup * test: share cron telegram delivery failure assertions * test: share agent acp turn helpers * test: share systemd service test helpers * test: share scheduled task stop helpers * test: share lane delivery final helpers * test: share outbound media fallback helpers * test: share telegram sticky fetch helpers * test: share embedded compaction hook helpers * test: share sanitize session usage helpers * test: share telegram draft stream helpers * test: share telegram account helpers * test: share line webhook gating helpers * test: share heartbeat scheduler helpers * test: share config-only channel status helpers * test: share restart health helpers * test: share lifecycle config guard helpers * test: share daemon cli service helpers * test: share qr cli setup code helpers * test: share gateway chat run helpers * refactor: share daemon lifecycle restart helpers * refactor: share daemon launchd and path helpers * test: share schtasks gateway script fixture * test: share startup auth token fixtures * test: share gateway reload helpers * test: share plugin http auth helpers * test: share gateway hook and cron helpers * test: share gateway chat history setup * refactor: share gateway chat text normalization * refactor: share gateway connection auth options * test: share channel health helpers * refactor: share plugin directory helpers * refactor: share browser route helpers * refactor: share cli install helpers * test: tighten system run command coverage * test: add parallels windows smoke harness * fix: force-stop lingering gateway client sockets * test: share gateway route auth helpers * test: share browser route test helpers * test: share gateway status auth fixtures * test: share models list forward compat fixtures * fix: tighten bonjour whitespace error coverage * docs: reorder changelog highlights by user impact * test: tighten proxy fetch helper coverage * test: tighten path guard helper coverage * test: tighten warning filter coverage * test: tighten wsl detection coverage * test: tighten system run command normalization coverage * fix(feishu): preserve non-ASCII filenames in file uploads (openclaw#33912) (openclaw#34262) * fix(feishu): preserve non-ASCII filenames in file uploads (openclaw#33912) * style(feishu): format media test file * fix(feishu): preserve UTF-8 filenames in file uploads (openclaw#34262) thanks @fabiaodemianyang --------- Co-authored-by: Robin Waslander <[email protected]> * test: tighten is-main helper coverage * test: tighten json file helper coverage * fix: resolve current ci regressions * test: tighten backoff abort coverage * docs(changelog): note upcoming security fixes * test: tighten bonjour ciao coverage * test: tighten channel activity account isolation * test: tighten update channel display precedence * test: tighten node list parse fallback coverage * test: tighten package tag prefix matching * test: tighten outbound identity normalization * test: tighten outbound session context coverage * macOS: respect exec-approvals.json settings in gateway prompter (openclaw#13707) Fix macOS gateway exec approvals to respect exec-approvals.json. This updates the macOS gateway prompter to resolve per-agent exec approval policy before deciding whether to show UI, use agentId for policy lookup, honor askFallback when prompts cannot be presented, and resolve no-prompt decisions from the configured security policy instead of hardcoded allow-once behavior. It also adds regression coverage for ask-policy and allowlist-fallback behavior, plus a changelog entry for the fix. Co-authored-by: ImLukeF <[email protected]> * fix: tighten target error hint coverage * test: tighten prototype key matching * test: tighten hostname normalization coverage * fix(ui): keep oversized chat replies readable (openclaw#45559) * fix(ui): keep oversized chat replies readable * Update ui/src/ui/markdown.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix(ui): preserve oversized markdown whitespace --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * test: tighten openclaw exec env coverage * fix: tighten pairing token blank handling * test: tighten target error hint trimming * test: tighten node shell platform normalization * fix(gateway/ui): restore control-ui auth bypass and classify connect failures (openclaw#45512) Merged via squash. Prepared head SHA: 42b5595 Co-authored-by: sallyom <[email protected]> Co-authored-by: BunsDev <[email protected]> Reviewed-by: @BunsDev * fix(macos): prevent PortGuard from killing Docker Desktop in remote mode (openclaw#13798) fix(macos): prevent PortGuardian from killing Docker Desktop in remote mode (openclaw#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 <[email protected]> * test: fix current ci regressions * test: share outbound action runner helpers * test: share telegram monitor startup helpers * refactor: share self hosted provider plugin helpers * test: share outbound delivery helpers * refactor: share onboarding diagnostics type * refactor: share delimited channel entry parsing * refactor: share zalo send context validation * refactor: share terminal note wrapping * refactor: share tts request setup * refactor: share gateway timeout parsing * refactor: share session send context lines * refactor: share memory tool builders * refactor: share browser console result formatting * refactor: share pinned sandbox entry finalization * refactor: share tool result char estimation * refactor: share agent tool fixture helpers * test: share compaction retry timer helpers * test: share embedded workspace attempt helpers * refactor: share whatsapp outbound adapter base * refactor: share zalo status issue helpers * test: share whatsapp outbound poll fixtures * refactor: share telegram reply chunk threading * refactor: share daemon install cli setup * fix: widen telegram reply progress typing * refactor: share slack text truncation * refactor: share allowlist wildcard matching * refactor: declone model picker model ref parsing * refactor: share dual text command gating * test: share startup account lifecycle helpers * test: share status issue assertion helpers * fix: restore imessage control command flag * test: share web fetch header helpers * refactor: share session tool context setup * test: share memory tool helpers * refactor: share request url resolution * Changelog: credit embedded runner queue deadlock fix * fix(voicewake): avoid crash on foreign transcript ranges * refactor(voicewake): mark transcript parameter unused * docs(changelog): note voice wake crash fix * fix: harden gateway status rpc smoke * test: add parallels linux smoke harness * fix(sessions): create transcript file on chat.inject when missing (openclaw#36645) `chat.inject` called `appendAssistantTranscriptMessage` with `createIfMissing: false`, causing a hard error when the transcript file did not exist on disk despite having a valid `transcriptPath` in session metadata. This commonly happens with ACP oneshot/run sessions where the session entry is created but the transcript file is not yet materialized. The fix is a one-character change: `createIfMissing: true`. The `ensureTranscriptFile` helper already handles directory creation and file initialization safely. Fixes openclaw#36170 Co-authored-by: Claude Opus 4.6 <[email protected]> * fix: harden discord guild allowlist resolution * chore: update dependencies * Plugins: fail fast on channel and binding collisions (openclaw#45628) * Plugins: reject duplicate channel ids * Bindings: reject duplicate adapter registration * Plugins: fail on export id mismatch * feat: add node-connect skill * test: share directory runtime helpers * refactor: share open allowFrom config checks * test: share send cfg threading helpers * refactor: reduce extension channel setup duplication * refactor: share extension channel status summaries * test: share feishu startup mock modules * test: share plugin api test harness * refactor: share extension monitor runtime setup * refactor: share extension deferred and runtime helpers * test: share sandbox fs bridge seeded workspace * test: share subagent gateway mock setup * test: share models config merge helpers * test: share workspace skills snapshot helpers * test: share context lookup helpers * test: share timeout failover assertions * test: share model selection config helpers * test: share provider discovery auth fixtures * test: share subagent announce timeout helpers * test: share workspace skill test helpers * refactor: share exec host approval helpers * test: share oauth profile fixtures * test: share memory search config helpers * fix(macos): align minimum Node.js version with runtime guard (22.16.0) (openclaw#45640) * macOS: align minimum Node.js version with runtime guard * macOS: add boundary and failure-message coverage for RuntimeLocator * docs: add changelog note for the macOS runtime locator fix * credit: original fix direction from @sumleo, cleaned up and rebased in openclaw#45640 by @ImLukeF * fix(agents): preserve blank local custom-provider API keys after onboarding Co-authored-by: Xinhua Gu <[email protected]> * fix(browser): harden existing-session driver validation and session lifecycle (openclaw#45682) * fix(browser): harden existing-session driver validation, session lifecycle, and code quality Fix config validation rejecting existing-session profiles that lack cdpPort/cdpUrl (they use Chrome MCP auto-connect instead). Fix callTool tearing down the MCP session on tool-level errors (element not found, script error), which caused expensive npx re-spawns. Skip unnecessary CDP port allocation for existing-session profiles. Remove redundant ensureChromeMcpAvailable call in isReachable. Extract shared ARIA role sets (INTERACTIVE_ROLES, CONTENT_ROLES, STRUCTURAL_ROLES) into snapshot-roles.ts so both the Playwright and Chrome MCP snapshot paths stay in sync. Add usesChromeMcp capability flag and replace ~20 scattered driver === "existing-session" string checks with the centralized flag. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> * fix(browser): harden existing-session driver validation and session lifecycle (openclaw#45682) (thanks @odysseus0) --------- Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> * fix(ci): repair helper typing regressions * fix: default Android TLS setup codes to port 443 * fix: unblock discord startup on deploy rate limits * fix(feishu): add early event-level dedup to prevent duplicate replies (openclaw#43762) * fix(feishu): add early event-level dedup to prevent duplicate replies Add synchronous in-memory dedup at EventDispatcher handler level using message_id as key with 5-minute TTL and 2000-entry cap. This catches duplicate events immediately when they arrive from the Lark SDK — before the inbound debouncer or processing queue — preventing the race condition where two concurrent dispatches enter the pipeline before either records the messageId in the downstream dedup layer. Fixes the root cause reported in openclaw#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 <[email protected]> * fix(models): apply Gemini model-id normalization to google-vertex provider (openclaw#42435) * fix(models): apply Gemini model-id normalization to google-vertex provider The existing normalizeGoogleModelId() (which maps e.g. gemini-3.1-flash-lite to gemini-3.1-flash-lite-preview) was only applied when the provider was "google". Users configuring google-vertex/gemini-3.1-flash-lite would get a "missing" model because the -preview suffix was never appended. Extend the normalization to google-vertex in both model-selection (parseModelRef path) and normalizeProviders (config normalization path). Ref: openclaw#36838 Ref: openclaw#36918 (comment) * fix(models): normalize google-vertex flash-lite * fix(models): place unreleased changelog entry last * fix(models): place unreleased changelog entry before releases * fix(browser): add browser session selection * build(android): add auto-bump signed aab release script * build(android): strip unused dnsjava resolver service before R8 * test(discord): align rate limit error mock with carbon * docs: fix changelog formatting * fix: keep exec summaries inline * build: shrink Android app release bundle * Gateway: treat scope-limited probe RPC as degraded reachability (openclaw#45622) * Gateway: treat scope-limited probe RPC as degraded * Docs: clarify gateway probe degraded scope output * test: fix CI type regressions in gateway and outbound suites * Tests: fix Node24 diffs theme loading and Windows assertions * Tests: fix extension typing after main rebase * Tests: fix Windows CI regressions after rebase * Tests: normalize executable path assertions on Windows * Tests: remove duplicate gateway daemon result alias * Tests: stabilize Windows approval path assertions * Tests: fix Discord rate-limit startup fixture typing * Tests: use Windows-friendly relative exec fixtures --------- Co-authored-by: Mainframe <[email protected]> * build: upload Android native debug symbols * fix(browser): prefer user profile over chrome relay * chore: bump pi to 0.58.0 * test: harden parallels all-os smoke harness * fix: keep windows onboarding logs ascii-safe * docs: reorder unreleased changelog by impact * build: prepare 2026.3.13-beta.1 * ci: add npm token fallback for npm releases * fix(gateway): bound unanswered client requests (openclaw#45689) * fix(gateway): bound unanswered client requests * fix(gateway): skip default timeout for expectFinal requests * fix(gateway): preserve gateway call timeouts * fix(gateway): localize request timeout policy * fix(gateway): clamp explicit request timeouts * fix(gateway): clamp default request timeout * Revert "Browser: scope nested batch failures in switch" This reverts commit aaeb348. * build: prepare 2026.3.13 release * fix: keep android canvas home visible after restart * chore: update appcast for 2026.3.13 release * fix(browser): restore batch playwright dispatch * feat(cron): support custom session IDs and auto-bind to current session (openclaw#16511) feat(cron): support persistent session targets for cron jobs (openclaw#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 <[email protected]> Co-authored-by: ImLukeF <[email protected]> * test: harden parallels beta smoke flows * build: prepare 2026.3.14 cycle * build: sync plugins for 2026.3.14 * build: refresh lockfile for plugin sync * fix(agents): normalize abort-wrapped RESOURCE_EXHAUSTED into failover errors (openclaw#11972) * fix: move cause-chain traversal before timeout heuristic (review feedback) * fix: harden wrapped rate-limit failover (openclaw#39820) thanks @lupuletic * style: format probe regression test (openclaw#39820) thanks @lupuletic * fix: tighten runner failover test types (openclaw#39820) thanks @lupuletic * fix: annotate shared failover mocks (openclaw#39820) thanks @lupuletic * test(ci): isolate cron heartbeat delivery cases * fix(mattermost): carry thread context to non-inbound reply paths (openclaw#44283) Merged via squash. Prepared head SHA: 2846a6c Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm * refactor: make OutboundSendDeps dynamic with channel-ID keys (openclaw#45517) * refactor: make OutboundSendDeps dynamic with channel-ID keys Replace hardcoded per-channel send fields (sendTelegram, sendDiscord, etc.) with a dynamic index-signature type keyed by channel ID. This unblocks moving channel implementations to extensions without breaking the outbound dispatch contract. - OutboundSendDeps and CliDeps are now { [channelId: string]: unknown } - Each outbound adapter resolves its send fn via bracket access with cast - Lazy-loading preserved via createLazySender with module cache - Delete 6 deps-send-*.runtime.ts one-liner re-export files - Harden guardrail scan against deleted-but-tracked files * fix: preserve outbound send-deps compatibility * style: fix formatting issues (import order, extra bracket, trailing whitespace) * fix: resolve type errors from dynamic OutboundSendDeps in tests and extension * fix: remove unused OutboundSendDeps import from deliver.test-helpers * refactor(signal): move Signal channel code to extensions/signal/src/ (openclaw#45531) Move all Signal channel implementation files from src/signal/ to extensions/signal/src/ and replace originals with re-export shims. This continues the channel plugin migration pattern used by other extensions, keeping backward compatibility via shims while the real code lives in the extension. - Copy 32 .ts files (source + tests) to extensions/signal/src/ - Transform all relative import paths for the new location - Create 2-line re-export shims in src/signal/ for each moved file - Preserve existing extension files (channel.ts, runtime.ts, etc.) - Change tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to support cross-boundary re-exports from extensions/ * refactor: move iMessage channel to extensions/imessage (openclaw#45539) * refactor: move WhatsApp channel implementation to extensions/ (openclaw#45725) * refactor: move WhatsApp channel from src/web/ to extensions/whatsapp/ Move all WhatsApp implementation code (77 source/test files + 9 channel plugin files) from src/web/ and src/channels/plugins/*/whatsapp* to extensions/whatsapp/src/. - Leave thin re-export shims at all original locations so cross-cutting imports continue to resolve - Update plugin-sdk/whatsapp.ts to only re-export generic framework utilities; channel-specific functions imported locally by the extension - Update vi.mock paths in 15 cross-cutting test files - Rename outbound.ts -> send.ts to match extension naming conventions and avoid false positive in cfg-threading guard test - Widen tsconfig.plugin-sdk.dts.json rootDir to support shim->extension cross-directory references Part of the core-channels-to-extensions migration (PR 6/10). * style: format WhatsApp extension files * fix: correct stale import paths in WhatsApp extension tests Fix vi.importActual, test mock, and hardcoded source paths that weren't updated during the file move: - media.test.ts: vi.importActual path - onboarding.test.ts: vi.importActual path - test-helpers.ts: test/mocks/baileys.js path - monitor-inbox.test-harness.ts: incomplete media/store mock - login.test.ts: hardcoded source file path - message-action-runner.media.test.ts: vi.mock/importActual path * refactor(slack): move Slack channel code to extensions/slack/src/ (openclaw#45621) Move all Slack channel implementation files from src/slack/ to extensions/slack/src/ and replace originals with shim re-exports. This follows the extension migration pattern for channel plugins. - Copy all .ts files to extensions/slack/src/ (preserving directory structure: monitor/, http/, monitor/events/, monitor/message-handler/) - Transform import paths: external src/ imports use relative paths back to src/, internal slack imports stay relative within extension - Replace all src/slack/ files with shim re-exports pointing to the extension copies - Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." so the DTS build can follow shim chains into extensions/ - Update write-plugin-sdk-entry-dts.ts re-export path accordingly - Preserve extensions/slack/index.ts, package.json, openclaw.plugin.json, src/channel.ts, src/runtime.ts, src/channel.test.ts (untouched) * refactor: move Telegram channel implementation to extensions/ (openclaw#45635) * refactor: move Telegram channel implementation to extensions/telegram/src/ Move all Telegram channel code (123 files + 10 bot/ files + 8 channel plugin files) from src/telegram/ and src/channels/plugins/*/telegram.ts to extensions/telegram/src/. Leave thin re-export shims at original locations so cross-cutting src/ imports continue to resolve. - Fix all relative import paths in moved files (../X/ -> ../../../src/X/) - Fix vi.mock paths in 60 test files - Fix inline typeof import() expressions - Update tsconfig.plugin-sdk.dts.json rootDir to "." for cross-directory DTS - Update write-plugin-sdk-entry-dts.ts for new rootDir structure - Move channel plugin files with correct path remapping * fix: support keyed telegram send deps * fix: sync telegram extension copies with latest main * fix: correct import paths and remove misplaced files in telegram extension * fix: sync outbound-adapter with main (add sendTelegramPayloadMessages) and fix delivery.test import path * refactor: move Discord channel implementation to extensions/ (openclaw#45660) * refactor: move Discord channel implementation to extensions/discord/src/ Move all Discord source files from src/discord/ to extensions/discord/src/, following the extension migration pattern. Source files in src/discord/ are replaced with re-export shims. Channel-plugin files from src/channels/plugins/*/discord* are similarly moved and shimmed. - Copy all .ts source files preserving subdirectory structure (monitor/, voice/) - Move channel-plugin files (actions, normalize, onboarding, outbound, status-issues) - Fix all relative imports to use correct paths from new location - Create re-export shims at original locations for backward compatibility - Delete test files from shim locations (tests live in extension now) - Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." to accommodate extension files outside src/ - Update write-plugin-sdk-entry-dts.ts to match new declaration output paths * fix: add importOriginal to thread-bindings session-meta mock for extensions test * style: fix formatting in thread-bindings lifecycle test * refactor: remove channel shim directories, point all imports to extensions (openclaw#45967) * refactor: remove channel shim directories, point all imports to extensions Delete the 6 backward-compat shim directories (src/telegram, src/discord, src/slack, src/signal, src/imessage, src/web) that were re-exporting from extensions. Update all 112+ source files to import directly from extensions/{channel}/src/ instead of through the shims. Also: - Move src/channels/telegram/ (allow-from, api) to extensions/telegram/src/ - Fix outbound adapters to use resolveOutboundSendDep (fixes 5 pre-existing TS errors) - Update cross-extension imports (src/web/media.js → extensions/whatsapp/src/media.js) - Update vitest, tsdown, knip, labeler, and script configs for new paths - Update guard test allowlists for extension paths After this, src/ has zero channel-specific implementation code — only the generic plugin framework remains. * fix: update raw-fetch guard allowlist line numbers after shim removal * refactor: document direct extension channel imports * test: mock transcript module in delivery helpers * fix(zai): align explicit coding endpoint setup with detected model defaults (openclaw#45969) * fix: align Z.AI coding onboarding with endpoint docs * fix: align Z.AI coding onboarding with endpoint docs (openclaw#45969) * docs: mark memory bootstrap change as breaking * fix(ui): session dropdown shows label instead of key (openclaw#45130) Merged via squash. Prepared head SHA: 0255e39 Co-authored-by: luzhidong <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf * feat: add --force-document to message.send for Telegram (bypass sendPhoto + image optimizer) (openclaw#45111) * feat: add --force-document to message.send for Telegram Adds --force-document CLI flag to bypass sendPhoto and use sendDocument instead, avoiding Telegram image compression for PNG/image files. - TelegramSendOpts: add forceDocument field - send.ts: skip sendPhoto when forceDocument=true (mediaSender pattern) - ChannelOutboundContext: add forceDocument field - telegramOutbound.sendMedia: pass forceDocument to sendMessageTelegram - ChannelHandlerParams / DeliverOutboundPayloadsCoreParams: add forceDocument - createChannelOutboundContextBase: propagate forceDocument - outbound-send-service.ts: add forceDocument to executeSendAction params - message-action-runner.ts: read forceDocument from params - message.ts: add forceDocument to MessageSendParams - register.send.ts: add --force-document CLI option * fix: pass forceDocument through telegram action dispatch path The actual send path goes through dispatchChannelMessageAction -> telegramMessageActions.handleAction -> handleTelegramAction, not deliverOutboundPayloads. forceDocument was not being read in readTelegramSendParams or passed to sendMessageTelegram. * fix: apply forceDocument to GIF branch to avoid sendAnimation * fix: add disable_content_type_detection=true to sendDocument for --force-document * fix: add forceDocument to buildSendSchema for agent discoverability * fix: scope telegram force-document detection * test: fix heartbeat target helper typing * fix: skip image optimization when forceDocument is set * fix: persist forceDocument in WAL queue for crash-recovery replay * test: tighten heartbeat target test entry typing --------- Co-authored-by: thepagent <[email protected]> Co-authored-by: Frank Yang <[email protected]> * Update CONTRIBUTING.md * fix(build): update build:restart script to include ui:build step * feat: add support for resolving implicit GitHub Copilot provider in narrative model compaction * feat: execute beforeMessage and afterResponse hooks in RPC/embedded agent path - Mount /rpc/agent/turn endpoint in server-http.ts (was defined but never registered) - Run beforeMessage hook (WOL + KV cache restore) before LLM call in agentCommandInternal - Run afterResponse hook (KV cache save) fire-and-forget after response - Add afterResponse to AgentModelEntryConfig and AgentDefaultsConfig types + Zod schema * feat(mind-memory): send desktop notifications on compaction and narrative regeneration Adds sendNotify() helper (fire-and-forget via ~/scripts/notify.sh) and calls it at the start and end of before_compaction and after_compaction hooks so the user gets notified when Mind is compacting, updating STORY.md, and regenerating QUICK/SUMMARY. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(status): use prompt fill (cacheRead+input) for context % and compact compaction label --------- Co-authored-by: Robin Waslander <[email protected]> Co-authored-by: Peter Steinberger <[email protected]> Co-authored-by: Val Alexander <[email protected]> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Frank Yang <[email protected]> Co-authored-by: fabiaodemianyang <[email protected]> Co-authored-by: Steven <[email protected]> Co-authored-by: ImLukeF <[email protected]> Co-authored-by: Sally O'Malley <[email protected]> Co-authored-by: sallyom <[email protected]> Co-authored-by: Jaehoon You <[email protected]> Co-authored-by: Vincent Koc <[email protected]> Co-authored-by: 2233admin <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> Co-authored-by: Xinhua Gu <[email protected]> Co-authored-by: George Zhang <[email protected]> Co-authored-by: yunweibang <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> Co-authored-by: scoootscooob <[email protected]> Co-authored-by: Muhammed Mukhthar CM <[email protected]> Co-authored-by: Josh Avant <[email protected]> Co-authored-by: Mainframe <[email protected]> Co-authored-by: kkhomej33-netizen <[email protected]> Co-authored-by: kkhomej33-netizen <[email protected]> Co-authored-by: Catalin Lupuleti <[email protected]> Co-authored-by: Darshil <[email protected]> Co-authored-by: Teconomix <[email protected]> Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Co-authored-by: luzhidong <[email protected]> Co-authored-by: luzhidong <[email protected]> Co-authored-by: altaywtf <[email protected]> Co-authored-by: thepagent <[email protected]> Co-authored-by: thepagent <[email protected]> Co-authored-by: Radek Sienkiewicz <[email protected]>
…nclaw#44283) Merged via squash. Prepared head SHA: 2846a6c Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm (cherry picked from commit 0c926a2)
…nclaw#44283) Merged via squash. Prepared head SHA: 2846a6c Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm (cherry picked from commit 0c926a2)
…nclaw#44283) Merged via squash. Prepared head SHA: 2846a6c Co-authored-by: teconomix <[email protected]> Co-authored-by: mukhtharcm <[email protected]> Reviewed-by: @mukhtharcm
Problem
When
replyToMode: "all"is active, replies to Mattermost threads only land in the correct thread when triggered by a direct inbound Mattermost message. Any other reply path — TUI/WebUI turns, tool call callbacks, subagent responses, or explicitmessagetool calls — ignores the thread context and posts to the channel root instead.Reported in #39759.
Root Cause
Three gaps in the outbound routing path for non-inbound-triggered deliveries:
Gap 1 —
dispatch-from-config.ts:sendPayloadAsyncpassedctx.MessageThreadIdtorouteReply. For TUI/WebUI turns the current context is a webchat surface, soctx.MessageThreadIdisundefined. The session entry'sdeliveryContext.threadId(set when the session was originally created from an inbound Mattermost message) was never consulted.Gap 2 —
route-reply.ts:The threadId→replyToId fallback was Slack-only:
Mattermost uses the same
root_idmechanic as Slack'sthread_tsbut was excluded.Gap 3 —
extensions/mattermost/src/channel.ts:sendText/sendMediadestructured onlyreplyToId, silently ignoringthreadIdeven when it was present in the outbound context.Fix
All three gaps addressed in a single change:
dispatch-from-config.ts: fall back todeliveryContextFromSession(sessionStoreEntry.entry)?.threadIdwhenctx.MessageThreadIdis absent. This uses the session store's persisted delivery context, which already stores the thread root ID from the original inbound message.route-reply.ts: extend the threadId→replyToId fallback to include Mattermost alongside Slack.extensions/mattermost/src/channel.ts: acceptthreadIdinsendText/sendMediaand use it as a defense-in-depth fallback whenreplyToIdis not set.How to test
channels.mattermost.replyToMode: "all"Tests
route-reply.test.ts:uses threadId as replyToId for Mattermost when replyToId is missingdispatch-from-config.test.ts:falls back to session deliveryContext threadId when current ctx has no MessageThreadIdchannel.test.ts:uses threadId as fallback when replyToId is absentpnpm checkandpnpm buildclean locally.