Conversation
…-in catalog When a built-in provider model has reasoning:true (e.g. MiniMax-M2.5) and the user explicitly sets reasoning:false in their config, mergeProviderModels unconditionally overwrote the user's value with the built-in catalog value. The merge code refreshes capability metadata (input, contextWindow, maxTokens, reasoning) from the implicit catalog. This is correct for fields like contextWindow and maxTokens — the catalog has authoritative values that shouldn't be stale. But reasoning is a user preference, not just a capability descriptor: users may need to disable it to avoid 'Message ordering conflict' errors with certain models or backends. Fix: check whether 'reasoning' is present in the explicit (user-supplied) model entry. If the user has set it (even to false), honour that value. If the user hasn't set it, fall back to the built-in catalog default. This allows users to configure tools.models.providers.minimax.models with reasoning:false for MiniMax-M2.5 without being silently overridden. Fixes openclaw#25244
resolveAgentModelPrimary() only checks the agent-level model config and does not fall back to the system-wide default. When users configure a non-Anthropic provider (e.g. Gemini, Minimax) as their global default without setting it at the agent level, the slug-generator falls through to DEFAULT_PROVIDER (anthropic) and fails with a missing API key error. Switch to resolveAgentEffectiveModelPrimary() which correctly respects the full model resolution chain including global defaults. Fixes openclaw#25365
Kimi K2 models use automatic prefix caching and return cache stats in a nested field: usage.prompt_tokens_details.cached_tokens This fixes issue openclaw#7073 where cacheRead was showing 0 for K2.5 users. Also adds cached_tokens (top-level) for moonshot-v1 explicit caching API. Closes openclaw#7073
Add @icesword760/openclaw-wechat to the community plugins page. This plugin connects OpenClaw to WeChat personal accounts via WeChatPadPro (iPad protocol) with support for text, image, and file exchange. Co-authored-by: Cursor <cursoragent@cursor.com>
When an assistant message with toolCalls has stopReason 'aborted' or 'error', the guard should not add those tool call IDs to the pending map. Creating synthetic tool results for incomplete/aborted tool calls causes API 400 errors: 'unexpected tool_use_id found in tool_result blocks' This aligns the WRITE path (session-tool-result-guard.ts) with the READ path (session-transcript-repair.ts) which already skips aborted messages. Fixes: orphaned tool_result causing session corruption Tests added: - does NOT create synthetic toolResult for aborted assistant messages - does NOT create synthetic toolResult for errored assistant messages
…am media fetch on IPv6-broken hosts On hosts where IPv6 is configured but not routed (common on cloud VMs), Telegram media downloads fail because the pinned DNS lookup may return IPv6 addresses first. Even though autoSelectFamily (Happy Eyeballs) is enabled, the round-robin pinned lookup serves individual IPv6 addresses that fail before IPv4 is attempted. Sort resolved addresses so IPv4 comes first, ensuring both Happy Eyeballs and single-address round-robin try the working address family first. Fixes openclaw#23975 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… (thanks @Glucksberg) Co-Authored-By: Glucksberg <80581902+Glucksberg@users.noreply.github.com>
When a third-party channel plugin declares a channel ID that differs from
its plugin ID (e.g. plugin id="apn-channel", channels=["apn"]), the
doctor plugin auto-enable logic was using the channel ID ("apn") as the
key for plugins.entries, producing an entry that fails config validation:
Error: plugins.entries.apn: plugin not found: apn
Root cause: resolveConfiguredPlugins iterated over cfg.channels keys and
used each key directly as both the channel ID (for isChannelConfigured)
and the plugin ID (for plugins.entries). For built-in channels these are
always the same, but for third-party plugins they can differ.
Fix: load the installed plugin manifest registry and build a reverse map
from channel ID to plugin ID. When a cfg.channels key does not resolve to
a built-in channel, look up the declaring plugin's manifest ID and use
that as the pluginId in the PluginEnableChange, so registerPluginEntry
writes the correct plugins.entries["apn-channel"] key.
The applyPluginAutoEnable function now accepts an optional manifestRegistry
parameter for testing, avoiding filesystem access in unit tests.
Fixes openclaw#25261
Co-Authored-By: Claude <noreply@anthropic.com>
…ng fails (openclaw#26109) * fix(followup): fall back to dispatcher when same-channel origin routing fails When routeReply fails for an originating channel that matches the session's messageProvider, the onBlockReply callback was created by that same channel's handler and can safely deliver the reply. Previously the payload was silently dropped on any routeReply failure, causing Feishu DM replies to never reach the user. Cross-channel fallback (origin ≠ provider) still drops the payload to preserve origin isolation. Closes openclaw#25767 Co-authored-by: Cursor <cursoragent@cursor.com> * fix: allow same-channel followup fallback routing (openclaw#26109) (thanks @Sid-Qin) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…penclaw#26106) * fix(agents): continue fallback loop for unrecognized provider errors When a provider returns an error that coerceToFailoverError cannot classify (e.g., custom error messages without standard HTTP status codes), the fallback loop threw immediately instead of trying the next candidate. This caused fallback to stop after 2 models even when 17 were configured. Only rethrow unrecognized errors when they occur on the last candidate. For intermediate candidates, record the error as an attempt and continue to the next model. Closes openclaw#25926 Co-authored-by: Cursor <cursoragent@cursor.com> * test: cover unknown-error fallback telemetry and land openclaw#26106 (thanks @Sid-Qin) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…enclaw#26105) * fix(markdown): require paired || delimiters for spoiler detection An unpaired || (odd count across all inline tokens) would open a spoiler that never closes, causing closeRemainingStyles to extend it to the end of the text. This made all content after an unpaired || appear as hidden/spoiler in Telegram. Pre-count || delimiters across the entire inline token group and skip spoiler injection entirely when the count is less than 2 or odd. This prevents single | characters and unpaired || from triggering spoiler formatting. Closes openclaw#26068 Co-authored-by: Cursor <cursoragent@cursor.com> * fix: preserve valid spoiler pairs with trailing unmatched delimiters (openclaw#26105) (thanks @Sid-Qin) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
…ata (openclaw#26115) * fix(hooks): include guildId and channelName in message_received metadata The message_received hook (both plugin and internal) already exposes sender identity fields (senderId, senderName, senderUsername, senderE164) but omits the guild/channel context. Plugins that track per-channel activity receive NULL values for channel identification. Add guildId (ctx.GroupSpace) and channelName (ctx.GroupChannel) to the metadata block in both the plugin hook and internal hook dispatch paths. These properties are already populated by channel providers (e.g. Discord sets GroupSpace to the guild ID and GroupChannel to #channel-name) and used elsewhere in the codebase (channels/conversation-label.ts). * test: cover guild/channel hook metadata propagation (openclaw#26115) (thanks @davidrudduck) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…ions (openclaw#26119) * Discord: gate component command authorization * test: cover allowlisted guild component authorization path (openclaw#26119) (thanks @bmendonca3) --------- Co-authored-by: Brian Mendonca <brianmendonca@Brians-MacBook-Air.local> Co-authored-by: Peter Steinberger <steipete@gmail.com>
openclaw#25130) * fix(brave-search): swap ui_lang and search_lang formats (openclaw#23826) * fix(web-search): normalize Brave ui_lang/search_lang params --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
…t modelProvider (openclaw#25874) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: f0953a7 Co-authored-by: lbo728 <72309817+lbo728@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
When a Slack message contains only files/audio (no text) and every file download fails, `resolveSlackMedia` returns null and `rawBody` becomes empty, causing `prepareSlackMessage` to silently drop the message. Build a fallback placeholder from the original file names so the agent still receives the message, matching the pattern already used in `resolveSlackThreadHistory` for file-only thread entries. Closes openclaw#25064
Replace the hardcoded limit of 5 with the existing MAX_SLACK_MEDIA_FILES constant (8) from media.ts for consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…anks @justinhuangcode)
Summary of ChangesHello @siri666942, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request focuses on updating the core and extensions to version 2026.2.25, incorporating numerous bug fixes, security enhancements, and user experience improvements. The changes span across multiple areas, including Android app UX, security protocols, dependency updates, and model fallback mechanisms, aiming to provide a more stable and secure experience. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Ignored Files
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a vast number of changes across the repository, including documentation updates, dependency upgrades, a major UI refactoring for the Android app, and numerous security enhancements and bug fixes. The PR title seems to be a misnomer given the broad scope of these changes. Key improvements include a more robust and user-friendly Android app experience, enhanced markdown rendering, and significant hardening of the command execution security model. I've identified a critical compilation issue in a Swift file and some areas for improvement regarding code duplication and performance on Android. Overall, this is a substantial and valuable set of updates.
| private static func extractPayload(command: [String], spec: WrapperSpec) -> String? { | ||
| switch spec.kind { | ||
| case .posix: | ||
| return self.extractPosixInlineCommand(command) | ||
| self.extractPosixInlineCommand(command) | ||
| case .cmd: | ||
| return self.extractCmdInlineCommand(command) | ||
| self.extractCmdInlineCommand(command) | ||
| case .powershell: | ||
| return self.extractPowerShellInlineCommand(command) | ||
| self.extractPowerShellInlineCommand(command) | ||
| } | ||
| } |
There was a problem hiding this comment.
This function is declared to return a String?, but the case statements inside the switch do not return a value. This will cause a compilation error. You should add return statements to each case.
| private static func extractPayload(command: [String], spec: WrapperSpec) -> String? { | |
| switch spec.kind { | |
| case .posix: | |
| return self.extractPosixInlineCommand(command) | |
| self.extractPosixInlineCommand(command) | |
| case .cmd: | |
| return self.extractCmdInlineCommand(command) | |
| self.extractCmdInlineCommand(command) | |
| case .powershell: | |
| return self.extractPowerShellInlineCommand(command) | |
| self.extractPowerShellInlineCommand(command) | |
| } | |
| } | |
| private static func extractPayload(command: [String], spec: WrapperSpec) -> String? { | |
| switch spec.kind { | |
| case .posix: | |
| return self.extractPosixInlineCommand(command) | |
| case .cmd: | |
| return self.extractCmdInlineCommand(command) | |
| case .powershell: | |
| return self.extractPowerShellInlineCommand(command) | |
| } | |
| } | |
| - Telegram/Media fetch: prioritize IPv4 before IPv6 in SSRF pinned DNS address ordering so media downloads still work on hosts with broken IPv6 routing. (#24295, #23975) Thanks @Glucksberg. | ||
| - Telegram/Outbound API: replace Node 22's global undici dispatcher when applying Telegram `autoSelectFamily` decisions so outbound `fetch` calls inherit IPv4 fallback instead of staying pinned to stale dispatcher settings. (#25682, #25676) Thanks @lairtonlelis. | ||
| - Agents/Billing classification: prevent long assistant/user-facing text from being rewritten as billing failures while preserving explicit `status/code/http 402` detection for oversized structured error payloads. (#25680, #25661) Thanks @lairtonlelis. | ||
| - Telegram/Replies: when markdown formatting renders to empty HTML (for example syntax-only chunks in threaded replies), retry delivery with plain text, and fail loud when both formatted and plain payloads are empty to avoid false delivered states. (#25096, #25091) Thanks @Glucksberg. | ||
| - Sessions/Tool-result guard: avoid generating synthetic `toolResult` entries for assistant turns that ended with `stopReason: "aborted"` or `"error"`, preventing orphaned tool-use IDs from triggering downstream API validation errors. (#25429) Thanks @mikaeldiakhate-cell. | ||
| - Gateway/Sessions: preserve `modelProvider` on `sessions.reset` and avoid incorrect provider prefixes for legacy session models. (#25874) Thanks @lbo728. | ||
| - Usage accounting: parse Moonshot/Kimi `cached_tokens` fields (including `prompt_tokens_details.cached_tokens`) into normalized cache-read usage metrics. (#25436) Thanks @Elarwei001. | ||
| - Doctor/Sandbox: when sandbox mode is enabled but Docker is unavailable, surface a clear actionable warning (including failure impact and remediation) instead of a mild “skip checks” note. (#25438) Thanks @mcaxtr. | ||
| - Config/Meta: accept numeric `meta.lastTouchedAt` timestamps and coerce them to ISO strings, preserving compatibility with agent edits that write `Date.now()` values. (#25491) Thanks @mcaxtr. | ||
| - Auto-reply/Reset hooks: guarantee native `/new` and `/reset` flows emit command/reset hooks even on early-return command paths, with dedupe protection to avoid double hook emission. (#25459) Thanks @chilu18. | ||
| - Hooks/Slug generator: resolve session slug model from the agent’s effective model (including defaults/fallback resolution) instead of raw agent-primary config only. (#25485) Thanks @SudeepMalipeddi. | ||
| - Slack/DM routing: treat `D*` channel IDs as direct messages even when Slack sends an incorrect `channel_type`, preventing DM traffic from being misclassified as channel/group chats. (#25479) Thanks @mcaxtr. | ||
| - Models/Providers: preserve explicit user `reasoning` overrides when merging provider model config with built-in catalog metadata, so `reasoning: false` is no longer overwritten by catalog defaults. (#25314) Thanks @lbo728. | ||
| - Exec approvals: treat bare allowlist `*` as a true wildcard for parsed executables, including unresolved PATH lookups, so global opt-in allowlists work as configured. (#25250) Thanks @widingmarcus-cyber. | ||
| - Gateway/Auth: allow trusted-proxy authenticated Control UI websocket sessions to skip device pairing when device identity is absent, preventing false `pairing required` failures behind trusted reverse proxies. (#25428) Thanks @SidQin-cyber. | ||
| - Agents/Tool dispatch: await block-reply flush before tool execution starts so buffered block replies preserve message ordering around tool calls. (#25427) Thanks @SidQin-cyber. | ||
| - iOS/Signing: improve `scripts/ios-team-id.sh` for Xcode 16+ by falling back to Xcode-managed provisioning profiles, add actionable guidance when an Apple account exists but no Team ID can be resolved, and ignore Xcode `xcodebuild` output directories (`apps/ios/build`, `apps/shared/OpenClawKit/build`, `Swabble/build`). (#22773) Thanks @brianleach. | ||
| - macOS/Menu bar: stop reusing the injector delegate for the "Usage cost (30 days)" submenu to prevent recursive submenu injection loops when opening cost history. (#25341) Thanks @yingchunbai. | ||
| - Control UI/Chat images: route image-click opens through a shared safe-open helper (allowing only safe URL schemes) and open new tabs with opener isolation to block tabnabbing. (#18685, #25444, #25847) Thanks @Mariana-Codebase and @shakkernerd. | ||
| - CLI/Doctor: correct stale recovery hints to use valid commands (`openclaw gateway status --deep` and `openclaw configure --section model`). (#24485) Thanks @chilu18. | ||
| - CLI/Memory search: accept `--query <text>` for `openclaw memory search` (while keeping positional query support), and emit a clear error when neither form is provided. (#25904, #25857) Thanks @niceysam and @stakeswky. | ||
| - Security/Sandbox: canonicalize bind-mount source paths via existing-ancestor realpath so symlink-parent + non-existent-leaf paths cannot bypass allowed-source-roots or blocked-path checks. Thanks @tdjackey. |
| fun setGatewayToken(value: String) { | ||
| prefs.edit { putString("gateway.manual.token", value) } | ||
| _gatewayToken.value = value | ||
| val trimmed = value.trim() | ||
| prefs.edit(commit = true) { putString("gateway.manual.token", trimmed) } | ||
| _gatewayToken.value = trimmed | ||
| } |
There was a problem hiding this comment.
Using commit = true performs a synchronous write to disk, which can block the main thread and potentially lead to an Application Not Responding (ANR) error. It is generally recommended to use apply() for asynchronous writes, which is the default for the edit KTX extension. If a synchronous write is not strictly necessary here, please consider removing commit = true to default to apply().
| fun setGatewayToken(value: String) { | |
| prefs.edit { putString("gateway.manual.token", value) } | |
| _gatewayToken.value = value | |
| val trimmed = value.trim() | |
| prefs.edit(commit = true) { putString("gateway.manual.token", trimmed) } | |
| _gatewayToken.value = trimmed | |
| } | |
| fun setGatewayToken(value: String) { | |
| val trimmed = value.trim() | |
| prefs.edit { putString("gateway.manual.token", trimmed) } | |
| _gatewayToken.value = trimmed | |
| } | |
Summary
Describe the problem and fix in 2–5 bullets:
Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
List user-visible changes (including defaults/config).
If none, write
None.Security Impact (required)
Yes/No)Yes/No)Yes/No)Yes/No)Yes/No)Yes, explain risk + mitigation:Repro + Verification
Environment
Steps
Expected
Actual
Evidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
Compatibility / Migration
Yes/No)Yes/No)Yes/No)Failure Recovery (if this breaks)
Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write
None.