Skip to content

fix(discord): force fresh gateway reconnects#54697

Merged
ngutman merged 5 commits intomainfrom
fix/discord-stale-socket-reconnect
Mar 26, 2026
Merged

fix(discord): force fresh gateway reconnects#54697
ngutman merged 5 commits intomainfrom
fix/discord-stale-socket-reconnect

Conversation

@ngutman
Copy link
Copy Markdown
Member

@ngutman ngutman commented Mar 25, 2026

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: Discord stale-socket recovery could race Carbon's socket-close auto-reconnect path, so a forced reconnect was not reliably fresh.
  • Why it matters: once the gateway got into a bad resume state, the health monitor could keep restarting the provider while the socket never reached READY again.
  • What changed: forced reconnect paths now drain the old socket without letting its close/error listeners auto-resume, and fresh reconnects explicitly clear cached resume state before reconnecting.
  • What did NOT change (scope boundary): this does not change model failover behavior or add a separate top-level health-monitor circuit breaker.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write N/A. If the cause is unclear, write Unknown.

  • Root cause: OpenClaw's Discord lifecycle called disconnect() + connect(false) for forced reconnects, but Carbon still lets the old socket's close handler trigger its own reconnect path and will still send RESUME on HELLO when resume state is cached.
  • Missing detection / guardrail: the lifecycle assumed an intentional disconnect prevented lower-level reconnect behavior and that connect(false) alone guaranteed a fresh IDENTIFY.
  • Prior context (git blame, prior PR, issue, or refactor if known): existing lifecycle guards already handled HELLO stalls and startup-not-ready timeouts, but they still relied on Carbon's socket lifecycle underneath.
  • Why this regressed now: a stale or invalid Discord session can leave the provider repeatedly attempting to recover with poisoned socket/session state.
  • If unknown, what was ruled out: this PR does not attribute the bug to model-side FailoverError handling; the reproduced failure mechanism was in Discord gateway reconnect sequencing.

Regression Test Plan (if applicable)

For bug fixes or regressions, name the smallest reliable test coverage that should have caught this. Otherwise write N/A.

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/discord/src/monitor/provider.lifecycle.test.ts
  • Scenario the test should lock in: a forced startup reconnect must clear cached resume state, suppress close-driven auto-resume from the stale socket, and reconnect with a fresh IDENTIFY.
  • Why this is the smallest reliable guardrail: the bug is in lifecycle coordination around the Carbon gateway socket, so a focused lifecycle test is enough to reproduce and lock the behavior without needing live Discord traffic.
  • Existing test that already covers this (if any): existing lifecycle tests already covered HELLO stall retries and startup-not-ready failures.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

List user-visible changes (including defaults/config).
If none, write None.

  • Discord forced reconnect recovery is more reliable after stale sockets or startup-not-ready conditions.
  • When a reconnect must be fresh, the provider now clears cached resume state before reconnecting.

Security Impact (required)

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

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 24 / pnpm workspace
  • Model/provider: N/A
  • Integration/channel (if any): Discord gateway lifecycle
  • Relevant config (redacted): default test harness config

Steps

  1. Start the Discord provider with cached resume state and a stale socket.
  2. Force the startup readiness timeout or HELLO-stall reconnect path.
  3. Observe whether the old socket's close handler can auto-resume before the forced fresh reconnect completes.

Expected

  • Forced fresh reconnect clears cached resume state and reconnects with a fresh IDENTIFY.
  • The stale socket cannot race in with a close-driven auto-resume.

Actual

  • Before this change, the old socket could still trigger reconnect behavior and the forced reconnect was not guaranteed to be fresh.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

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

  • Verified scenarios: ran the focused lifecycle test file after the fix and confirmed the new regression case plus existing lifecycle cases pass; also ran pnpm build and pnpm check.
  • Edge cases checked: intentional reconnect after startup-not-ready; HELLO stall recovery; stale socket attempting to emit a close/error during forced reconnect.
  • What you did not verify: live Discord gateway behavior against a real bot token.

Review Conversations

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

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

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this PR or restore the prior reconnect helpers in extensions/discord/src/monitor/provider.lifecycle.ts.
  • Files/config to restore: extensions/discord/src/monitor/provider.lifecycle.ts, extensions/discord/src/monitor/provider.lifecycle.test.ts
  • Known bad symptoms reviewers should watch for: reconnect attempts hanging unexpectedly during intentional disconnects.

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: removing socket listeners during a forced reconnect could miss an unexpected close/error signal from the stale socket.
    • Mitigation: the suppression is limited to intentional reconnect teardown, the lifecycle still tracks the new socket normally, and the regression test covers the specific stale-socket race.

@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord size: M maintainer Maintainer-authored PR labels Mar 25, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 25, 2026

Greptile Summary

This PR fixes a race condition in the Discord gateway reconnect path where Carbon's socket close handler could fire an auto-resume reconnect concurrently with the lifecycle's own forced fresh reconnect, leaving the gateway stuck in a poisoned resume state. The fix introduces disconnectGatewaySocketWithoutAutoReconnect which drains the old socket after removing its close/error listeners, preventing Carbon's auto-reconnect from interfering, and reconnectGatewayFresh which clears cached session state before reconnecting to guarantee a fresh IDENTIFY even when Carbon has stale resume data cached.

  • disconnectGatewaySocketWithoutAutoReconnect — captures gateway.ws, strips all existing close and error listeners, adds a no-op error sink to avoid unhandled-error crashes, then resolves on close or a 5-second safety timeout before returning. When ws is absent it falls back to a plain synchronous disconnect().
  • reconnectGatewayFresh — calls clearResumeState() first (so Carbon cannot send RESUME on HELLO even if connect(false) alone would not clear it), then delegates to reconnectGateway(false).
  • Hello-timeout handler refactored to async IIFE — the handler can now await the new helpers; a lifecycleStopping guard is also added before triggering any reconnect attempt.
  • Startup-not-ready path — replaced the two-step gateway.disconnect() + gateway.connect(false) with await reconnectGatewayFresh() for consistency and correctness.
  • Regression test — a focused lifecycle unit test simulates a stale socket whose close listener would call connect(true), verifying it is suppressed and that only one connect(false) call is made with all session state cleared.

Confidence Score: 4/5

  • Safe to merge; the fix correctly addresses the stated race condition with targeted, well-tested changes and no breaking surface changes.
  • The core logic is sound: listener removal happens on a captured reference before disconnect() is called, the no-op error handler prevents unhandled-error crashes on the stale socket, clearResumeState() runs before the new connect() call, and the 5-second drain timeout with unref() prevents the process from hanging. Existing tests verify that gateways without a ws property still follow the old fast-path, and the new regression test locks in the specific race scenario. One minor residual: the no-op socket.on("error", () => {}) listener added to the stale socket is never explicitly removed, but since the socket is being discarded it will be garbage-collected with the listener attached — no functional risk. Score of 4 rather than 5 reflects that the live Discord gateway path was not verified against a real bot token (as noted in the PR), so there is a small tail risk around how Carbon actually manages ws across reconnects at runtime.
  • No files require special attention; both changed files are focused and cohesive.

Reviews (1): Last reviewed commit: "fix(discord): force fresh gateway reconn..." | Re-trigger Greptile

@ngutman ngutman force-pushed the fix/discord-stale-socket-reconnect branch from f2ac744 to 02e02d9 Compare March 26, 2026 06:10
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02e02d908d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/discord/src/monitor/provider.lifecycle.ts Outdated
@ngutman
Copy link
Copy Markdown
Member Author

ngutman commented Mar 26, 2026

Addressed both review items in aee62a31ff:

  • Codex thread: added a post-drain shutdown guard so reconnectGateway() will not call connect(...) if lifecycleStopping or abortSignal.aborted flips while the old socket is draining.
  • Aisle security note: forced reconnects now serialize through a reconnect-in-flight guard, and the disconnect drain now fails closed if the old socket does not close within the drain budget. On timeout we best-effort terminate() the stale socket and stop the lifecycle instead of opening a parallel socket.
  • Added focused regression coverage for:
    • drain timeout -> no reconnect, lifecycle fails closed
    • shutdown during drain -> no reconnect after stop begins

Validation:

  • pnpm test -- extensions/discord/src/monitor/provider.lifecycle.test.ts
  • pnpm build
  • pnpm check

Also rolled this updated branch build onto the mac-mini (guti@100.65.30.41) and verified:

  • openclaw --version -> OpenClaw 2026.3.24 (aee62a3)
  • openclaw gateway health -> Discord ok (@Gutsy)
  • openclaw channels status --probe -> Discord running, connected, works

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ngutman
Copy link
Copy Markdown
Member Author

ngutman commented Mar 26, 2026

Addressed the latest Aisle comment in ca558d8f05.

What changed:

  • disconnectGatewaySocketWithoutAutoReconnect() no longer hard-stops the lifecycle on a socket-drain timeout.
  • On drain timeout it now logs the condition, best-effort terminate()s the stale socket, and continues with the reconnect path instead of rejecting.
  • Kept the existing reconnect serialization + post-drain shutdown guard in place.
  • Replaced the prior regression with coverage that locks in the new behavior: stale socket drain timeout -> forced terminate + reconnect continues.

Validation:

  • pnpm test -- extensions/discord/src/monitor/provider.lifecycle.test.ts
  • pnpm build
  • pnpm check

Also updated the mac-mini branch install to this exact commit and verified:

  • openclaw --version -> OpenClaw 2026.3.24 (ca558d8)
  • openclaw gateway health -> Discord ok (@Gutsy)
  • openclaw channels status --probe -> Discord running, connected, works

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca558d8f05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/discord/src/monitor/provider.lifecycle.ts Outdated
@ngutman
Copy link
Copy Markdown
Member Author

ngutman commented Mar 26, 2026

Addressed the latest Codex + Aisle feedback in 391db13c4c.

What changed:

  • kept the post-drain shutdown guard in reconnectGateway()
  • changed disconnectGatewaySocketWithoutAutoReconnect() so reconnect only proceeds after the old socket actually emits close
  • on the 5s drain timeout we now best-effort terminate() the socket, keep temporary error suppression installed while terminate unwinds, and wait for close
  • if forced terminate still does not produce close within the extra 1s grace window, the lifecycle now fails closed instead of opening a parallel socket

Regression coverage:

  • forced terminate path can emit a late socket error, then close, and reconnect still succeeds safely
  • forced terminate that never reaches close fails closed and does not reconnect

Validation:

  • pnpm test -- extensions/discord/src/monitor/provider.lifecycle.test.ts
  • pnpm build
  • pnpm check

mac-mini branch install is also updated to this exact commit and verified:

  • openclaw --version -> OpenClaw 2026.3.24 (391db13)
  • openclaw gateway health -> Discord ok (@Gutsy)
  • openclaw channels status --probe -> Discord running, connected, works

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 391db13c4c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/discord/src/monitor/provider.lifecycle.ts
@ngutman
Copy link
Copy Markdown
Member Author

ngutman commented Mar 26, 2026

Addressed the latest Codex comment in d2fa2294d7.

What changed:

  • disconnectGatewaySocketWithoutAutoReconnect() now treats drain-timeout failure as a graceful stop when abortSignal/lifecycle shutdown is already active
  • while still stopping cleanly, it keeps temporary late-error suppression in place until the old socket actually closes
  • added regression coverage for: abort during forced reconnect drain + stale socket never closes -> lifecycle resolves without reconnecting or surfacing a false failure

Validation:

  • pnpm test -- extensions/discord/src/monitor/provider.lifecycle.test.ts
  • pnpm build
  • pnpm check

mac-mini branch install is updated to this exact commit and verified:

  • openclaw --version -> OpenClaw 2026.3.24 (d2fa229)
  • openclaw gateway health -> Discord ok (@Gutsy)
  • openclaw channels status --probe -> Discord running, connected, works

@ngutman ngutman self-assigned this Mar 26, 2026
@ngutman ngutman force-pushed the fix/discord-stale-socket-reconnect branch from d2fa229 to 85967ca Compare March 26, 2026 10:04
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Mar 26, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Denial of Service: gateway lifecycle force-stops if old WebSocket never emits "close" during reconnect
Vulnerabilities

1. 🟡 Denial of Service: gateway lifecycle force-stops if old WebSocket never emits "close" during reconnect

Property Value
Severity Medium
CWE CWE-400
Location extensions/discord/src/monitor/provider.lifecycle.ts:275-332

Description

The new reconnect path introduces a fail-closed behavior in disconnectGatewaySocketWithoutAutoReconnect():

  • During reconnects, the code removes existing close/error listeners from the underlying gateway WebSocket and waits for the socket to emit "close".
  • If the socket fails to close within DISCORD_GATEWAY_DISCONNECT_DRAIN_TIMEOUT_MS (5000ms), it attempts socket.terminate() and waits an additional DISCORD_GATEWAY_FORCE_TERMINATE_CLOSE_TIMEOUT_MS (1000ms).
  • If the socket still does not emit "close" (or if terminate() is missing/broken), the promise rejects.
  • In the HELLO-timeout reconnect watchdog, this rejection is caught and routed to triggerForceStop(err), which causes waitForDiscordGatewayStop() to reject and the overall lifecycle to exit.

This creates an availability risk: a network condition or gateway behavior that prevents the close event from firing (even after terminate()), can repeatedly drive the monitor into a persistent stopped state rather than attempting recovery.

Vulnerable flow:

  • input/trigger: gateway/network conditions leading to stuck socket without close
  • sink: triggerForceStop(err) stops the monitor task

Vulnerable code:

terminateCloseTimeout = setTimeout(() => {
  ...
  rejectClose(new Error(
    `discord gateway socket did not close within ${DISCORD_GATEWAY_DISCONNECT_DRAIN_TIMEOUT_MS}ms before reconnect`,
  ));
}, DISCORD_GATEWAY_FORCE_TERMINATE_CLOSE_TIMEOUT_MS);

and in the HELLO timeout restart path:

await reconnectGateway({ resume: true });
...
} catch (err) {
  ...
  triggerForceStop(err);
}

Recommendation

Avoid turning an inability-to-observe "close" into an immediate lifecycle-wide force-stop.

Recommended hardening options (choose one or combine):

  1. Degrade to best-effort disconnect and continue reconnecting after the terminate timeout, while preventing parallel sockets via internal state:
// After terminate timeout, proceed with reconnect but mark old socket as abandoned// and keep suppressing its error events.
resolve();
  1. Retry/backoff instead of force-stopping: only force-stop after N consecutive stuck-close events within a time window.

  2. Add an explicit escape hatch: if terminate() was called, allow reconnect after a longer maximum (e.g., 30s) and record telemetry, rather than rejecting.

  3. Ensure the underlying ws close is actually triggered: call socket.close() (if available) before/alongside terminate(), and consider attaching a one-time "close" listener before removing Carbon listeners.

The goal is to keep the monitor self-healing under adverse network conditions, reserving triggerForceStop() for unrecoverable logic errors rather than transient socket teardown anomalies.


Analyzed PR: #54697 at commit 85967ca

Last updated on: 2026-03-26T10:08:07Z

@ngutman ngutman merged commit a3b85e1 into main Mar 26, 2026
20 checks passed
@ngutman ngutman deleted the fix/discord-stale-socket-reconnect branch March 26, 2026 10:05
@ngutman
Copy link
Copy Markdown
Member Author

ngutman commented Mar 26, 2026

Landed via temp rebase onto main.

  • Gate:
    • pnpm lint
    • pnpm build
    • pnpm test -- extensions/discord/src/monitor/provider.lifecycle.test.ts
    • pnpm test ❌ on latest main due to unrelated failures in test/scripts/test-parallel.test.ts plus worker OOM; landed with explicit maintainer approval in chat
  • Land commit: 85967ca
  • Merge commit: a3b85e1

Thanks @ngutman!

mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 26, 2026
* main: (673 commits)
  docs: add beta release testing guidance
  test: fix bluebubbles attachment ssrf expectations
  fix: surface provider-specific rate limit error message (openclaw#54433) (openclaw#54512)
  Matrix: gate verification notices on DM access (openclaw#55122)
  fix(bluebubbles): auto-allow private network for local serverUrl and add allowPrivateNetwork to channel schema
  fix(msteams): align feedback invoke authorization (openclaw#55108)
  Telegram: enforce DM auth for callbacks (openclaw#55112)
  fix(agents): enforce session_status guard after sessionId resolution (openclaw#55105)
  Feishu: validate webhook signatures before parsing (openclaw#55083)
  fix(discord): force fresh gateway reconnects (openclaw#54697)
  chore: add lockfile entry for `extensions/microsoft-foundry`
  BlueBubbles: enrich group participants with local Contacts names (openclaw#54984)
  fix(extensions): route fetch calls through fetchWithSsrFGuard (openclaw#53929)
  Remove Qwen OAuth integration (qwen-portal-auth) (openclaw#52709)
  build: update plugin sdk api baseline
  fix: add slack upload-file action (openclaw#54987) (thanks @kevinlin-openai)
  docs: refresh config baseline for microsoft foundry
  fix: wire microsoft foundry into contract registry
  Docs: rename modelstudio.md to qwen_modelstudio.md, add Standard API endpoints (openclaw#54407)
  fix: route codex responses over websocket and preserve tool warnings (openclaw#53702) (thanks @Nanako0129)
  ...
godlin-gh pushed a commit to YouMindInc/openclaw that referenced this pull request Mar 27, 2026
* fix(discord): force fresh gateway reconnects

* fix(discord): harden forced reconnect teardown

* fix(discord): retry after socket drain timeouts

* fix(discord): guard forced socket teardown

* fix(discord): stop cleanly during reconnect drain
BotstersOrg pushed a commit to TheBotsters/botster-ego that referenced this pull request Mar 27, 2026
* test: collapse msteams helper suites

* test: collapse msteams graph suites

* test: collapse msteams state and monitor suites

* fix: preserve Telegram forum topic last-route delivery (#53052) (thanks @VACInc)

* fix(telegram): preserve forum topic thread in last-route delivery

* style(telegram): format last-route update

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

* test(telegram): align topic route helper

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

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix(feishu): default requireMention to false for groupPolicy open

Groups configured with groupPolicy: open are expected to respond to all
messages. Previously, requireMention defaulted to true regardless of
groupPolicy, causing image (and other non-text) messages to be silently
dropped because they cannot carry @-mentions.

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

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

Closes #52553

* test(feishu): update config-schema test for removed requireMention default

* fix: finish feishu open-group docs and baselines (#54058) (thanks @byungsker)

* test: refresh pairing reply assertions for fenced codes (#54058) (thanks @byungsker)

* test: accept fenced discord pairing codes (#54058) (thanks @byungsker)

* test: allow daemon start hints to grow on linux (#54058) (thanks @byungsker)

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

* fix(telegram): validate photo dimensions before sendPhoto

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

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

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

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

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

* fix(telegram): validate photo aspect ratio

* refactor: use shared telegram image metadata

* fix: fail closed on telegram image metadata

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

---------

Co-authored-by: Bob Shah <bobshah@Macs-Mac-Studio.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

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

* fix(cron): track and log bestEffort delivery failures, mark not delivered on partial failure

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

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

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

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

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix(cli): route telegram thread create to topic-create

* fix: make telegram thread create use topic payload (#54336) (thanks @andyliu)

* cron: queue isolated delivery awareness

* fix(browser): add Edge LaunchServices bundle IDs for macOS default browser detection

macOS registers Edge as 'com.microsoft.edgemac' in LaunchServices, which
differs from the CFBundleIdentifier 'com.microsoft.Edge' in the app's own
Info.plist. Without recognising the LaunchServices IDs, Edge users who set
Edge as their default browser are not detected as having a Chromium browser.

Add the four com.microsoft.edgemac* variants to CHROMIUM_BUNDLE_IDS and a
corresponding test that mocks the LaunchServices → osascript resolution
path for Edge.

* fix: add changelog for macOS Edge default browser detection (#48561) (thanks @zoherghadyali)

* fix: cover macOS Edge osascript fallback path (#48561) (thanks @zoherghadyali)

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

* fix(process): auto-detect PTY cursor key mode for send-keys

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

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

Fixes #51488

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

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

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

* gateway: make session:patch hook typed and non-blocking

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

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

* fix: address review feedback on session:patch hook

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

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

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

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

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

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

---------

Co-authored-by: “graciegould” <“graciegould5@gmail.com”>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix: unify log timestamp offsets (#38904) (thanks @sahilsatralkar)

* fix: skip session:patch hook clone without listeners

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

* Plugins: add before_dispatch hook

* Tests: fix before_dispatch hook mock typing

* Rebase: adapt before_dispatch hook to routeReplyRuntime refactor

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix(whatsapp): avoid eager login tool runtime access

* fix: normalize before_dispatch conversation id

* test: harden parallels npm update runner

* docs: sort changelog by user impact

* test: collapse telegram helper suites

* test: collapse telegram context and transport suites

* docs: add missing changelog items

* refactor: align pairing replies, daemon hints, and feishu mention policy

* test: collapse telegram button and access suites

* test: collapse telegram transport and status suites

* fix(whatsapp): unwrap FutureProofMessage (botInvokeMessage) to restore reply-to-bot detection

* fix(whatsapp): compare selfLid for reply-to-bot implicit mention in groups

* fix(whatsapp): read selfLid from creds.json for reply-to-bot detection

* fix(whatsapp): use async fs.promises.readFile for selfLid creds read

* refactor(auth): separate profile ids from email metadata

* refactor(openai): extract codex auth identity helper

* test: isolate voice-call temp stores

* build: prepare 2026.3.24-beta.1

* fix: copy openclaw bin before docker install

* refactor: unify whatsapp identity handling

* test: fix clobbered config snapshot expectation

* fix(config): ignore same-base correction publish warnings

* test: add Open WebUI docker smoke

* fix(media): align outbound media access with fs policy

* fix(update): preflight npm target node engine

* fix(ci): restore e2e docker cache boundary

* test(release): sync llama peer fixture

* Discord: log rejected native command deploy failures (#54118)

Merged via squash.

Prepared head SHA: be250f96204ed6dc755c10d2b9640f7dd49bc70c
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo

* fix(runtime): support Node 22.14 installs

* test(media): make local roots fixture windows-safe

* Tests: isolate security audit home skill resolution (#54473)

Merged via squash.

Prepared head SHA: 82181e15fbfa47244cf166f57f40646bc35629c6
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo

* docs: prepare 2026.3.24-beta.2 release

* fix(sandbox): honor sandbox alsoAllow and explicit re-allows (#54492)

* fix(sandbox): honor effective sandbox alsoAllow policy

* fix(sandbox): prefer resolved sandbox context policy

* fix: honor sandbox alsoAllow policy (#54492) (thanks @ngutman)

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

* fix(feishu): close WebSocket connections on monitor stop/abort

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

* test(feishu): add WebSocket cleanup tests

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

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>

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

* fix(feishu): use message create_time instead of Date.now() for Timestamp field

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

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

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

* test(feishu): verify Timestamp uses message create_time

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

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

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

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

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

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>

* Fix local copied package installs honoring staged project .npmrc (#54543)

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

* fix(plugins): resolve sdk alias from import.meta.url for external plugins

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Spotted by greptile-apps review on #54283.

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

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

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

Made-with: Cursor

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

---------

Co-authored-by: bojsun <bojie.sun@bytedance.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jerry <jerry@JerrydeMacBook-Air-2.local>
Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>

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

* feat(minimax): add image generation and TTS providers, trim TUI model list

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

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

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

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

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

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

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

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

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

* MiniMax: remove web search and TTS providers

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

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

---------

Co-authored-by: tars90percent <tars@minimaxi.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>

* build: prepare 2026.3.24 release

* docs: format changelog for release

* fix: reconcile session compaction count after late compaction success (#45493)

Merged via squash.

Prepared head SHA: d0715a5555791dd44a406d4732843454a3e9619e
Co-authored-by: jackal092927 <3854860+jackal092927@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* build: update appcast for 2026.3.24

* Telegram: ignore self-authored DM message updates (#54530)

Merged via squash.

Prepared head SHA: c1c8a851682944c49259afe1741d6d976016b08b
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo

* test: keep vitest on forks only

* fix: stop leaking reply tags in iMessage outbound text (#39512) (thanks @mvanhorn)

* fix: stop leaking reply tags in iMessage outbound text (#39512) (thanks @mvanhorn)

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix: mid-turn 429 rate limit silent no-reply and context engine registration failure (#50930)

Merged via squash.

Prepared head SHA: eea7800df31f30caabfec2aefe8f20008365a8e8
Co-authored-by: infichen <13826604+infichen@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* plugin-runtime: expose runHeartbeatOnce in system API (#40299)

* plugin-runtime: expose runHeartbeatOnce in system API

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

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

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

Made-with: Cursor

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

---------

Co-authored-by: George Zhang <georgezhangtj97@gmail.com>

* fix(compaction): surface safeguard cancel reasons and clarify /compact skips (#51072)

Merged via squash.

Prepared head SHA: f1dbef044384fbd79ca5ef3616ad37fb03b05fae
Co-authored-by: afurm <6375192+afurm@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* Fix/telegram writeback admin scope gate (#54561)

* fix(telegram): require operator.admin for legacy target writeback persistence

* Address claude feedback

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

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

* Remove stray brace

* Add updated docs

* Add missing test file, address codex concerns

* Fix test formatting error

* Address comments, fix tests

---------

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

* test: fix windows tmp root assertions

* fix: per-model cooldown scope, stepped backoff, and user-facing rate-limit message (#49834)

Merged via squash.

Prepared head SHA: 7c488c070c0cafb5a4b53c598d8ccd38c418b67c
Co-authored-by: kiranvk-2011 <91108465+kiranvk-2011@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf

* fix: reject path traversal and home-dir patterns in media parse layer (#54642)

* fix: reject path traversal and home-dir patterns in media parse layer

* Update parse tests

* Block reset-profile on lower-privilege browser request surfaces (#54618)

* Block reset-profile on lower-privilege browser request surfaces

* add missing tests

* Fix tests

* Test fix

* fix: trigger compaction on LLM timeout with high context usage (#46417)

Merged via squash.

Prepared head SHA: 619bc4c1fa1db3829ff3aa78ffcab8e4201379b5
Co-authored-by: joeykrug <5925937+joeykrug@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix(talk-voice): enforce operator.admin scope on /voice set config writes (#54461)

* fix(talk-voice): enforce operator.admin scope on /voice set config writes

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

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

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

---------

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

* fix: apply host-env blocklist to auth-profile env refs in daemon install (#54627)

* fix: apply host-env blocklist to auth-profile env refs in daemon install

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

* ci: retrigger checks

---------

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

* OpenShell: exclude hooks/ from mirror sync (#54657)

* OpenShell: exclude hooks/ from mirror sync

* OpenShell: make excludeDirs case-insensitive for cross-platform safety

* Trigger preflight compaction from transcript estimates when usage is stale (#49479)

Merged via squash.

Prepared head SHA: 8d214b708b433702aba21b86ad19b6e2721c3ee0
Co-authored-by: jared596 <37019497+jared596@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* refactor(sandbox): remove tool policy facade (#54684)

* refactor(sandbox): remove tool policy facade

* fix(sandbox): harden blocked-tool guidance

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

* fix: harden sandbox blocked-tool guidance (#54684) (thanks @ngutman)

* fix(ci): restore main green

* Filter untrusted CWD .env entries before OpenClaw startup (#54631)

* Filter untrusted CWD .env entries before OpenClaw startup

* Add missing test file

* Fix missing and updated files

* Address feedback

* Feedback updates

* Feedback update

* Add test coverage

* Unit test fix

* fix: enforce localRoots sandbox on Feishu docx upload file reads (#54693)

* fix: enforce localRoots sandbox on Feishu docx upload file reads

* Formatting fixes

* Update tests

* Feedback updates

* test: introduce planner-backed test runner, stabilize local builds (#54650) 

* test: stabilize ci and local vitest workers

* test: introduce planner-backed test runner

* test: address planner review follow-ups

* test: derive planner budgets from host capabilities

* test: restore planner filter helper import

* test: align planner explain output with execution

* test: keep low profile as serial alias

* test: restrict explicit planner file targets

* test: clean planner exits and pnpm launch

* test: tighten wrapper flag validation

* ci: gate heavy fanout on check

* test: key shard assignments by unit identity

* ci(bun): shard vitest lanes further

* test: restore ci overlap and stabilize planner tests

* test: relax planner output worker assertions

* test: reset plugin runtime state in optional tools suite

* ci: split macos node and swift jobs

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

* ci: fix macos swift format lint

* test: cap max-profile top-level concurrency

* ci: shard macos node checks

* ci: use four macos node shards

* test: normalize explain targets before classification

* feat(cli): add json schema to cli tool (#54523)

Merged via squash.

Prepared head SHA: 39c15ee70d04a1721ebafde327b3b95355653fac
Co-authored-by: kvokka <15954013+kvokka@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf

* fix(schema): tools.web.fetch.maxResponseBytes #53397 (#53401)

Merged via squash.

Prepared head SHA: 5d10a98bdb1f7fc3065adf438c4d572c3d61f8ce
Co-authored-by: erhhung <5808864+erhhung@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf

* fix: make buttons schema optional in message tool (#54418)

Merged via squash.

Prepared head SHA: 0805c095e930d669ba1b3aedc09baec80882ce45
Co-authored-by: adzendo <246828680+adzendo@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf

* fix(gateway): block silent reconnect scope-upgrade escalation (#54694)

* fix(gateway): block silent reconnect scope-upgrade escalation

* formatting updateas

* Resolve feedback

* formatting fixes

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

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

* Feedback updates

* fix unit test

* Feedback update

* Review feedback update

* More Greptile nit fixes

---------

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

* fix: allow msteams feedback and welcome config keys (#54679)

Merged via squash.

Prepared head SHA: f56a15ddeaeb5369fd8feac09bb89d3d600d6ff4
Co-authored-by: gumclaw <265388744+gumclaw@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf

* fix(mattermost): thread resolved cfg through reply delivery send calls (#48347)

Merged via squash.

Prepared head SHA: 7ca468e365087c802be32675696080a8dd11fae2
Co-authored-by: mathiasnagler <9951231+mathiasnagler@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Reviewed-by: @mukhtharcm

* feat: add video generation core infrastructure and extend image generation parameters (#53681)

* feat: add video generation core infrastructure and extend image generation parameters

Add full video generation capability to OpenClaw core:

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

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

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

Made-with: Cursor

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

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

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

---------

Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com>

* fix: deliver verbose tool summaries in Telegram forum topics (#43236) (thanks @frankbuild)

* fix(auto-reply): deliver verbose tool summaries in Telegram forum topics

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

Fixes #43206

* test: add sendToolResult count assertion per review feedback

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* docs: add WeChat channel via official Tencent iLink Bot plugin (#52131) (thanks @MonkeyLeeT)

* docs: add WeChat channel via official Tencent iLink Bot plugin

Add WeChat to the README channel lists and setup section.

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

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

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

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

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

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

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

* docs: sync WeChat channel docs

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* ci: collapse preflight manifest routing (#54773)

* ci: collapse preflight manifest routing

* ci: fix preflight workflow outputs

* ci: restore compat workflow tasks

* ci: match macos shards to windows

* ci: collapse macos swift jobs

* ci: skip empty submodule setup

* ci: drop submodule setup from node env

* fix(whatsapp): clarify allowFrom policy error (#54850)

* fix: resolve telegram token fallback for binding-created accounts (#54362) (thanks @openperf)

* fix(telegram): resolve channel-level token fallthrough for binding-created accountIds

Fixes #53876

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

* fix(telegram): use normalized account lookup and require available token

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

* daemon: tighten systemd duplicate gateway detection (#15849)

* fix three issues from PR review

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

* attempt fix windows test due to fs path separator

* fix system_dir separator, fix platform side-effect

* change approach for mocking systemd filesystem test

* normalize systemd paths to linux style

* revert to vers that didnt impact win32 tests

* back out all systemd inspect tests

* change test approach to avoid other tests issues

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

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

* fix(status): use provider-aware context window lookup

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

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

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

* fix: pass agentId in CLI message command to enable session transcript writes

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

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

Closes #54186

* fix: format message.ts with oxfmt

* fix: use resolveDefaultAgentId instead of cfg.agent

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix: restore Kimi Code under Moonshot setup (#54619) (thanks @sparkyrider)

* Onboarding: restore Kimi Code under Moonshot setup

* Update extensions/kimi-coding/index.ts

Fix naming convention in metadata

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

---------

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

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

* CLI: auto-enable configured channel plugins in routed commands

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

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

* Image tool: restore generic provider fallback

* Image tool: cover multi-image generic fallback

* test: tighten minimax-portal image fallback coverage

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix: support OpenAI Codex media understanding (#54829) (thanks @neeravmakwana)

* OpenAI: register Codex media understanding provider

* fix: route codex image prompts through system instructions

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

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix(whatsapp): drop fromMe echoes in self-chat DMs using outbound ID tracking (#54570)

Merged via squash.

Prepared head SHA: dad53caf3974e2e90a358d0561a0b0ee9bd924f2
Co-authored-by: joelnishanth <140015627+joelnishanth@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr

* Revert "feat: add video generation core infrastructure and extend image generation parameters (#53681)" (#54943)

This reverts commit 4cb8dde894e3a85ce81b770869d7bd86e6b6e7a1.

* msteams: fetch thread history via Graph API for channel replies (#51643)

* msteams: fetch thread history via Graph API for channel replies

* msteams: address PR #51643 review feedback

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

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

* msteams: address thread history PR review comments

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

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

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

---------

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

* msteams: add search message action (#54832)

* msteams: add pin/unpin, list-pins, and read message actions

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

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

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

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

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

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

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

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

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

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

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

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

* msteams: add search message action via Graph API

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

---------

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

* fix(plugins): skip allowlist warning for config paths

* feat: Add Microsoft Foundry provider with Entra ID authentication (#51973)

* Microsoft Foundry: add native provider

* Microsoft Foundry: tighten review fixes

* Microsoft Foundry: enable by default

* Microsoft Foundry: stabilize API routing

* fix: restore inbound image embedding for CLI routed BlueBubbles turns (#51373)

* fix(cli): hydrate prompt image refs for inbound media

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

* test: fix CLI prompt image hydration helper mocks

* fix: route codex responses over websocket and preserve tool warnings (#53702) (thanks @Nanako0129)

* fix: route codex responses over websocket and suppress gated core tool warnings

* fix: rebase codex websocket patch onto main

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

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* Docs: rename modelstudio.md to qwen_modelstudio.md, add Standard API endpoints (#54407)

* Docs: rename modelstudio.md to qwen_modelstudio.md, add Standard API endpoints

* refine docs

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

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

* Docs: adjust the order in index.md

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

---------

Co-authored-by: George Zhang <georgezhangtj97@gmail.com>

* fix: wire microsoft foundry into contract registry

* docs: refresh config baseline for microsoft foundry

* fix: add slack upload-file action (#54987) (thanks @kevinlin-openai)

* feat(slack): add upload-file action

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

* fix(slack): guard upload-file routing

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

* fix(slack): tighten upload-file validation

---------

Co-authored-by: kevinlin-openai <kevin@dendron.so>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* build: update plugin sdk api baseline

* Remove Qwen OAuth integration (qwen-portal-auth) (#52709)

* Remove Qwen OAuth integration (qwen-portal-auth)

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

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

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

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

* Clean up residual qwen-portal references after OAuth removal

* Add migration hint for deprecated qwen-portal OAuth provider

* fix: finish qwen oauth removal follow-up

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>

* fix(extensions): route fetch calls through fetchWithSsrFGuard (#53929)

* fix(extensions): route fetch calls through fetchWithSsrFGuard

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* fix: regenerate config docs baseline for new allowPrivateNetwork fields

---------

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

* BlueBubbles: enrich group participants with local Contacts names (#54984)

* BlueBubbles: enrich group participants with Contacts names

* BlueBubbles: gate contact enrichment behind opt in config

* chore: add lockfile entry for `extensions/microsoft-foundry`

* fix(discord): force fresh gateway reconnects (#54697)

* fix(discord): force fresh gateway reconnects

* fix(discord): harden forced reconnect teardown

* fix(discord): retry after socket drain timeouts

* fix(discord): guard forced socket teardown

* fix(discord): stop cleanly during reconnect drain

* Feishu: validate webhook signatures before parsing (#55083)

* Feishu: validate webhook signatures before parsing

* Scripts: allow Feishu raw body guard callsite

* fix(agents): enforce session_status guard after sessionId resolution (#55105)

* fix(agents): enforce visibility guard after sessionId resolution in session_status

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

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

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

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

* test: cover session status sessionId guard

* test: align parent sessionId guard coverage

---------

Co-authored-by: Kevin Sheng <shenghuikevin@github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Telegram: enforce DM auth for callbacks (#55112)

* fix(msteams): align feedback invoke authorization (#55108)

* msteams: align feedback invoke authorization

* msteams: fix feedback allowlist regressions

* msteams: tighten feedback group authorization

* fix(bluebubbles): auto-allow private network for local serverUrl and add allowPrivateNetwork to channel schema

* Matrix: gate verification notices on DM access (#55122)

* fix: surface provider-specific rate limit error message (#54433) (#54512)

Merged via squash.

Prepared head SHA: 755cff833c1f6aca06ab2c3e1f802f35e7c4d553
Co-authored-by: bugkill3r <2924124+bugkill3r@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf

* test: fix bluebubbles attachment ssrf expectations

* docs: add beta release testing guidance

* fix(bluebubbles): enable group participant enrichment by default, add fallback fetch and handle field aliases

* fix: prefer freshest duplicate row promotion

* fix: replace stale canonical duplicate rows

* docs: sync config baseline

* build: prepare 2026.3.25 unreleased

* ACP: sanitize terminal tool titles (#55137)

* ACP: sanitize terminal tool titles

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

* Config: refresh config baseline and stabilize restart pid test

---------

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

* Google Chat: require stable group ids (#55131)

* Google Chat: require stable group ids

* Google Chat: fail closed on deprecated room keys

* Feishu: reject legacy raw card command payloads (#55130)

* Feishu: reject legacy raw card callbacks

* Feishu: cover legacy text card payloads

* Docs: refresh config baseline

* CI: refresh PR checks

* Feishu: limit legacy card guard scope

* fix(bluebubbles): refactor sendMessageBlueBubbles to use resolveBlueBubblesServerAccount and enhance private network handling in tests

* test(memory): initialize providers in lazy manager tests

* test: fix portable stderr capture and env leakage (#55184)

* docs: add beta blocker contributor guidance (#55199)

* docs: add beta blocker contributor guidance

* fix: tighten beta blocker labeling and flaky config test

* docs: update PR template review guidance

* refactor: dedupe msteams graph actions

* fix(bluebubbles): throttle webhook auth guesses (#55133)

* fix(bluebubbles): throttle webhook auth guesses

* test(bluebubbles): isolate attachment ssrf config

* test(bluebubbles): hoist attachment mocks

* docs: refresh bluebubbles config baseline

* fix(bluebubbles): trust proxied webhook client IPs

* fix(bluebubbles): honor trusted proxy webhook IPs

* fix(bluebubbles): honor real-ip fallback for webhooks

* refactor: share web media loader

* fix: avoid duplicate ACP Telegram finals (#55173)

* fix: avoid duplicate final ACP text on telegram

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

* fix: count telegram ACP block replies as success

* fix: recover ACP final fallback after block failures

* fix: settle telegram ACP block delivery before fallback

* test: isolate ACP dispatch mocks under shared workers

* fix: prefer telegram provider for ACP visibility

* refactor: share matrix and telegram dedupe helpers

* fix: prefer freshest duplicate store matches

* feat: pluginize cli inference backends

* test: dedupe foundry auth fixtures

* fix(whatsapp): unwrap quoted wrapper messages

* test: share pi compaction fixtures

* test: share redact and approval fixtures

* fix: preserve metadata on voice session touches

* test: dedupe web search provider fixtures

* synology-chat: throttle webhook token guesses (#55141)

* synology-chat: throttle webhook token guesses

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

* docs: refresh generated config baseline

* synology-chat: enforce lockout after repeated token failures

* fix: preserve reset ownership metadata

* test: improve test runner help text (#55227)

* test: improve test runner help text

* test: print extension help to stdout

* test: leave extension help passthrough alone

* test: parse timing update flags in one pass

* perf: speed up channel test runs

* test: share msteams monitor and pi runner fixtures

* fix: preserve reset spawn depth

* test: dedupe config compatibility fixtures

* ci: make docker release tag-driven

* fix: preserve reset elevated level

* fix: preserve reset spawn context

* test: share subagent and policy test fixtures

* perf: enable local channel planner parallelism on node 25

* test: dedupe remaining agent test seams

* telegram: throttle repeated webhook auth guesses (#55142)

* telegram: throttle repeated webhook auth guesses

* telegram: use per-listener webhook rate limits

* config: stabilize doc baseline ordering

* test: share matrix migration fixtures

* test: share gateway authz and watchdog fixtures

* test: share ui reconnect and storage helpers

* perf: overlap isolated channel runs with shared lane

* fix: preserve reset session behavior config

* fix: preserve reset channel identity

* fix: preserve reset acp session metadata

* fix: preserve reset cli session linkage

* refactor: share auto-reply reply helpers

* test: add docker cli-backend smoke

* docs: note guest openclaw shim in parallels skill

* test: share plugin auth and ui storage fixtures

* test: dedupe matrix setup seams

* test: dedupe ui chat seams

* style: normalize ui slash executor formatting

* fix(ci): refresh plugin sdk api baseline

* fix: keep plugin HTTP runtime scopes least-privileged (#55284)

* Gateway: require caller scope for subagent session deletion (#55281)

* gateway: require pairing for backend scope upgrades (#55286)

* test: dedupe secrets and guardrail fixtures

* bluebubbles: honor reaction mention gating (#55283)

* Gateway: align HTTP session history scopes (#55285)

* Gateway: require scopes for HTTP session history

* Gateway: cover missing HTTP history scope header

* refactor: dedupe gateway and binding helpers

* test: dedupe extension channel fixtures

* Gateway: require requester ownership for HTTP session kills (#55308)

* test: share cli and channel setup fixtures

* test: share auto-reply typing helpers

* refactor: share plugin setup helpers

* ci: optimize windows test shard fanout (#55261)

* ci: reduce windows test shard fanout

* ci: tighten windows shard target

* ci: back off windows shard target

* ci: restore windows shard cap

* fix: include dashboard children in owner filters

* fix: expose parent session keys in sessions list

* fix: expose spawned session owners in sessions list

* fix: keep spawned session owners in live events

* refactor: share browser and sandbox helpers

* refactor: share discord outbound session routing

* refactor: dedupe gateway session resolve visibility

* refactor: share slack and telegram action helpers

* fix: auto-load bundled plugin capabilities from config refs

* test: share cli and doctor test helpers

* fix(ci): repair discord regression tests

* test(gateway): strip MiniMax live scaffolding

* test: share cli command and discord test helpers

* test: dedupe extension channel fixtures

* fix(ci): repair discord message handler tests

* test: reduce remaining clone seams

* perf: speed up test parallelism

* telegram: rebuild transport after stalled polling cycles

* test: share discord monitor fixtures

* fix: preserve cli sessions across model changes

* test: split cli agent command coverage

* test: eliminate remaining clone seams

* refactor: dedupe cli runner session reuse

* fix(ci): repair discord harness regressions

* fix(ci): clean up discord harness types

* fix(agents): classify "Failed to extract accountId from token" as auth error for failover (#27055) (#55206)

Co-authored-by: Lyle Hopkins <55105+cosmicnet@users.noreply.github.com>

* test: dedupe discord provider proxy overrides

* fix(ci): restore discord provider test seams

* fix(ci): format discord provider follow-up

* refactor: split telegram polling and sdk surfaces

* fix: export shared channel action enum helpers

* fix: backfill claude cli chat history

* fix: skip cli backends in models auth warnings

* test: dedupe telegram polling session harness

* fix(ci): repair discord and telegram follow-ups

* refactor: clean plugin capability boundaries

* refactor: share speech normalization helpers

* perf: speed up shared extension test batches

* refactor: simplify bundled plugin contracts

* refactor: move memory flush ownership into memory plugin

* refactor: move memory tooling into memory-core extension

* refactor: add memory-core extension sources

* fix: unify claude cli imported tool messages

* test: share planner and sandbox test helpers

* refactor: split memory-core plugin helpers

* fix: regenerate pnpm-lock.yaml after upstream merge (workspace dep added)

* fix: resolve post-absorb CI failures

After absorbing 140 upstream commits, several botster-specific files
needed updating to match upstream SDK changes:

1. extensions/zulip/index.ts — import order reordered by oxfmt
   (types import after value import, as required by formatter)

2. scripts/lib/plugin-sdk-entrypoints.json + package.json —
   './plugin-sdk/zulip' subpath export missing from upstream's
   entrypoints list (our addition). Added 'zulip' to the canonical
   entrypoints JSON so sync-exports keeps it.

3. src/seks/spine-exec-intercept.test.ts —
   - TextContent|ImageContent union: .text access now requires type
     narrowing (cast to { type: 'text'; text: string })
   - AgentTool now requires execute: test case for missing-execute
     path casts to any (intentionally testing fallback behavior)

4. docs/.generated/* — regenerated config-baseline and
   plugin-sdk-api-baseline after upstream config schema + plugin SDK
   surface changes.

* fix: lint and docs issues from post-absorb CI

- src/seks/spine-exec-intercept.test.ts: replace 'as any' with
  'as Parameters<typeof createSpineExecTool>[0]' to satisfy
  oxlint no-explicit-any rule while still testing the no-execute
  fallback path
- docs/providers/anthropic.md: rename duplicate '### Config snippet'
  heading (added by upstream in ab4de18) to '### Config snippet (claude-cli)'
  to satisfy markdownlint MD024

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: VACInc <hixvac@gmail.com>
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: lbo728 <extreme0728@gmail.com>
Co-authored-by: hnshah <hnshah@gmail.com>
Co-authored-by: Bob Shah <bobshah@Macs-Mac-Studio.local>
Co-authored-by: ToToKr <friendnt@g.skku.edu>
Co-authored-by: Andy <lucktv@gmail.com>
Co-authored-by: Sparkyrider <will@willthings.com>
Co-authored-by: Zoher Ghadyali <zoherghadyali@users.noreply.github.com>
Co-authored-by: Liu Yuan <namei.unix@gmail.com>
Co-authored-by: Gracie Gould <66045258+graciegould@users.noreply.github.com>
Co-authored-by: “graciegould” <“graciegould5@gmail.com”>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: ZhangXuan <gfzhx@139.com>
Co-authored-by: khhjoe <joe264326832008@hotmail.com>
Co-authored-by: Harold Hunt <harold@pwrdrvr.com>
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
Co-authored-by: Lin Z <schumilin@163.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>
Co-authored-by: xieyongliang <yongliangxie@hotmail.com>
Co-authored-by: bojsun <bojie.sun@bytedance.com>
Co-authored-by: Jerry <jerry@JerrydeMacBook-Air-2.local>
Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com>
Co-authored-by: liyuan97 <33855278+liyuan97@users.noreply.github.com>
Co-authored-by: tars90percent <tars@minimaxi.com>
Co-authored-by: Jackal Xin <jackalxin2020@gmail.com>
Co-authored-by: jackal092927 <3854860+jackal092927@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
Co-authored-by: chenxingzhen <chenxingzhen0204@126.com>
Co-authored-by: infichen <13826604+infichen@users.noreply.github.com>
Co-authored-by: M1a0 <liyuhan.loveyana@bytedance.com>
Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
Co-authored-by: afurm <6375192+afurm@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: kiranvk2011 <91108465+kiranvk-2011@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: Joseph Krug <joeykrug@gmail.com>
Co-authored-by: joeykrug <5925937+joeykrug@users.noreply.github.com>
Co-authored-by: Jacob Tomlinson <jacobtomlinson@users.noreply.github.com>
Co-authored-by: Jared <37019497+jared596@users.noreply.github.com>
Co-authored-by: Mikhail Beliakov <kvokka@yahoo.com>
Co-authored-by: kvokka <15954013+kvokka@users.noreply.github.com>
Co-authored-by: Erhhung Yuan <erhhung@gmail.com>
Co-authored-by: erhhung <5808864+erhhung@users.noreply.github.com>
Co-authored-by: adzendo <nickfelixson@adzendo.com>
Co-authored-by: adzendo <246828680+adzendo@users.noreply.github.com>
Co-authored-by: gumclaw <gumclaw@gumroad.com>
Co-authored-by: gumclaw <265388744+gumclaw@users.noreply.github.com>
Co-authored-by: Mathias Nagler <mathiasnagler@users.noreply.github.com>
Co-authored-by: mathiasnagler <9951231+mathiasnagler@users.noreply.github.com>
Co-authored-by: mukhtharcm <56378562+mukhtharcm@users.noreply.github.com>
Co-authored-by: Frank the Builder <frank@codelumberjack.ai>
Co-authored-by: Ted Li <ted@portola.ai>
Co-authored-by: Marcus Castro <7562095+mcaxtr@users.noreply.github.com>
Co-authored-by: wangchunyue <80630709+openperf@users.noreply.github.com>
Co-authored-by: Greg Retkowski <greg@rage.net>
Co-authored-by: Neerav Makwana <neeravmakwana@gmail.com>
Co-authored-by: Kevin Boyle <kevincba17@gmail.com>
Co-authored-by: OfflynAI <140015627+joelnishanth@users.noreply.github.com>
Co-authored-by: sudie-codes <suvenkat95@gmail.com>
Co-authored-by: MetaX e|acc <xuhaoruins@hotmail.com>
Co-authored-by: Tyler Yust <64381258+tyler6204@users.noreply.github.com>
Co-authored-by: Nyanako <44753291+Nanako0129@users.noreply.github.com>
Co-authored-by: wenmeng zhou <wenmeng.zwm@alibaba-inc.com>
Co-authored-by: kevinlin-openai <kevinlin@openai.com>
Co-authored-by: kevinlin-openai <kevin@dendron.so>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: pomelo <czynwu@outlook.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: Altay <altay@uinaf.dev>
Co-authored-by: Kevin Sheng <shenghuikevin@github.com>
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
Co-authored-by: Saurabh Mishra <skmishra1991@gmail.com>
Co-authored-by: bugkill3r <2924124+bugkill3r@users.noreply.github.com>
Co-authored-by: Shakker <shakkerdroid@gmail.com>
Co-authored-by: nexrin <268879349+nexrin@users.noreply.github.com>
Co-authored-by: pkuGeo <geo.zhouhy@gmail.com>
Co-authored-by: Lyle Hopkins <cosmicnet@cpan.org>
Co-authored-by: Lyle Hopkins <55105+cosmicnet@users.noreply.github.com>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
* fix(discord): force fresh gateway reconnects

* fix(discord): harden forced reconnect teardown

* fix(discord): retry after socket drain timeouts

* fix(discord): guard forced socket teardown

* fix(discord): stop cleanly during reconnect drain
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* fix(discord): force fresh gateway reconnects

* fix(discord): harden forced reconnect teardown

* fix(discord): retry after socket drain timeouts

* fix(discord): guard forced socket teardown

* fix(discord): stop cleanly during reconnect drain
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* fix(discord): force fresh gateway reconnects

* fix(discord): harden forced reconnect teardown

* fix(discord): retry after socket drain timeouts

* fix(discord): guard forced socket teardown

* fix(discord): stop cleanly during reconnect drain
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord maintainer Maintainer-authored PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant