- Use stricter regex: /^[A-Za-z0-9+/]*={0,2}$/ ensures = only at end
- Normalize URL-safe base64 to standard (- → +, _ → /)
- Added tests for padding in wrong position and URL-safe normalization
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds explicit base64 format validation in sanitizeContentBlocksImages()
to prevent invalid image data from being sent to the Anthropic API.
The Problem:
- Node's Buffer.from(str, "base64") silently ignores invalid characters
- Invalid base64 passes local validation but fails at Anthropic's stricter API
- Once corrupted data persists in session history, every API call fails
The Fix:
- Add validateAndNormalizeBase64() function that:
- Strips data URL prefixes (e.g., "data:image/png;base64,...")
- Validates base64 character set with regex
- Checks for valid padding (0-2 '=' chars)
- Validates length is proper for base64 encoding
- Invalid images are replaced with descriptive text blocks
- Prevents permanent session corruption
Tests:
- Rejects invalid base64 characters
- Strips data URL prefixes correctly
- Rejects invalid padding
- Rejects invalid length
- Handles empty data gracefully
Closes#18212
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update test expectation: 'defaults to enable on Node 22'
- Update comment in fetch.ts to explain IPv4 fallback rationale
- Addresses greptile review feedback
Fixes issue where Telegram fails to send messages when IPv6 is configured
but not functional on the network.
Problem:
- Many networks (especially in Latin America) have IPv6 configured but
not properly routed by ISP/router
- Node.js tries IPv6 first, gets 'Network is unreachable' error
- With autoSelectFamily=false, Node doesn't fallback to IPv4
- Result: All Telegram API calls fail
Solution:
- Change default from false to true for Node.js 22+
- This enables automatic IPv4 fallback when IPv6 fails
- Config option channels.telegram.network.autoSelectFamily still available
for users who need to override
Symptoms fixed:
- Health check: Telegram | WARN | failed (unknown) - fetch failed
- Logs: Network request for 'sendMessage' failed
- Bot receives messages but cannot send replies
Tested on:
- macOS 26.2 (Sequoia)
- Node.js v22.15.0
- OpenClaw 2026.2.12
- Network with IPv6 configured but not routed
On macOS, `openclaw gateway install` hardcodes the system node
(/opt/homebrew/bin/node) in the launchd plist, ignoring the node from
version managers (fnm/nvm/volta). This causes the Gateway to run a
different node version than the user's shell environment.
Two fixes:
1. `resolvePreferredNodePath` now checks `process.execPath` first.
If the currently running node is a supported version, use it directly.
This respects the user's active version manager selection.
2. `buildMinimalServicePath` now includes version manager bin directories
on macOS (fnm, nvm, volta, pnpm, bun), matching the existing Linux
behavior.
Fixes#18090
Related: #6061, #6064
Follow-up to #18066 — three session file write sites were missed:
- auto-reply/reply/session.ts: forked session transcript header
- pi-embedded-runner/session-manager-init.ts: session file reset
- gateway/server-methods/sessions.ts: compacted transcript rewrite
All now use mode 0o600 consistent with transcript.ts and chat.ts.
Add sender_id (ctx.SenderId) to the openclaw.inbound_meta.v1 payload
so agents can reference it for moderation actions (delete, ban, etc.)
without relying on user-controlled text fields.
message_id and chat_id were already present; sender_id was the missing
piece needed for complete group moderation workflows.
When searching in FTS-only mode (no embedding provider), extract meaningful
keywords from conversational queries using LLM to improve search results.
Changes:
- New query-expansion module with keyword extraction
- Supports English and Chinese stop word filtering
- Null safety guards for FTS-only mode (provider can be null)
- Lint compliance fixes for string iteration
This helps users find relevant memory entries even with vague queries.
When no embedding provider is available (e.g., OAuth mode without API keys),
memory_search now falls back to FTS-only mode instead of returning disabled: true.
Changes:
- embeddings.ts: return null provider with reason instead of throwing
- manager.ts: handle null provider, use FTS-only search mode
- manager-search.ts: allow searching all models when provider is undefined
- memory-tool.ts: expose search mode in results
The search results now include a 'mode' field indicating 'hybrid' or 'fts-only'.
Extract parseGeminiAuth() to shared infra module and use it in both
embeddings-gemini.ts and inline-data.ts.
Previously, inline-data.ts directly set x-goog-api-key header without
handling OAuth JSON format. Now it properly supports both traditional
API keys and OAuth tokens.
Add parseGeminiAuth() to detect OAuth JSON format ({"token": "...", "projectId": "..."})
and use Bearer token authentication instead of x-goog-api-key header.
This allows OAuth users (using gemini-cli-auth extension) to use memory_search
with Gemini embedding API.
Walk users through Linq setup via `openclaw channels add` wizard
instead of requiring manual JSON config editing. Prompts for API
token, phone number, and webhook config with sensible defaults.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Send read receipt and typing indicator immediately on inbound messages
for a more natural iMessage experience. Add User-Agent header to all
Linq API requests. Fix delivery payload to use .text instead of .body.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes incorrect path matching that would reject valid webhooks with
querystrings and match unintended prefixes like /linq-webhookX.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a complete Linq iMessage channel adapter that replaces the existing
iMessage channel's Mac Mini + dedicated Apple ID + SSH wrapper + Full Disk
Access setup with a single API key and phone number.
Core implementation (src/linq/):
- types.ts: Linq webhook event and message types
- accounts.ts: Multi-account resolution from config (env/file/inline token)
- send.ts: REST outbound via Linq Blue V3 API (messages, typing, reactions)
- probe.ts: Health check via GET /v3/phonenumbers
- monitor.ts: Webhook HTTP server with HMAC-SHA256 signature verification,
replay protection, inbound debouncing, and full dispatch pipeline integration
Extension plugin (extensions/linq/):
- ChannelPlugin implementation with config, security, setup, outbound,
gateway, and status adapters
- Supports direct and group chats, reactions, and media
Wiring:
- Channel registry, dock, config schema, plugin-sdk exports, and plugin
runtime all updated to include the new linq channel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the missing extraArgs property to buildSandboxBrowserResolvedConfig
to satisfy the ResolvedBrowserConfig type, and fix import ordering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a `browser.extraArgs` config option (string array) that is appended
to Chrome's launch arguments. This enables users to add stealth flags,
window size overrides, custom user-agent strings, or other Chrome flags
without patching the source code.
Example config:
browser.extraArgs: ["--window-size=1920,1080", "--disable-infobars"]
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review feedback: the spread operator carries stale retry state
into replacement runs, potentially causing immediate force-expiration
without ever attempting announce delivery.
When runSubagentAnnounceFlow returns false (deferred), finalizeSubagentCleanup
resets cleanupHandled=false and removes from resumedRuns, allowing
retryDeferredCompletedAnnounces to pick it up again. If the underlying
condition persists (stale registry data, transient state), this creates an
infinite loop delivering 100+ announces over hours.
Fix:
- Add announceRetryCount + lastAnnounceRetryAt to SubagentRunRecord
- finalizeSubagentCleanup: after MAX_ANNOUNCE_RETRY_COUNT (3) failed attempts
or ANNOUNCE_EXPIRY_MS (5 min) since endedAt, mark as completed and stop
- resumeSubagentRun: skip entries that have exhausted retries or expired
- retryDeferredCompletedAnnounces: force-expire stale entries
When OPENCLAW_HOME is set (indicating an isolated instance), the gateway
port should be read from config rather than inheriting OPENCLAW_GATEWAY_PORT
from a parent process. This fixes running multiple OpenClaw instances
where a child process would incorrectly use the parent's port.
Changes:
- resolveGatewayPort() now prioritizes config.gateway.port when OPENCLAW_HOME is set
- Added getConfigPath() function for runtime-evaluated config path
- Deprecated CONFIG_PATH constant with warning about module-load-time evaluation
- Updated gateway run command to use getConfigPath() instead of CONFIG_PATH
Fixes the issue where spawning a sandbox OpenClaw instance from within
another OpenClaw process would fail because OPENCLAW_GATEWAY_PORT from
the parent (set in server.impl.ts) would override the child's config.
Pre-existing test mocks lacked pendingMessagingMediaUrls and
messagingToolSentMediaUrls fields added by the media dedup feature,
causing runtime errors in handleToolExecutionEnd.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire media URL tracking through the embedded agent pipeline so that
media already sent via messaging tools is not delivered again by the
reply dispatcher.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover three integration points where media dedup could silently regress:
- trimMessagingToolSent FIFO cap at 200 entries
- buildReplyPayloads media filter wiring (new test file)
- followup-runner messagingToolSentMediaUrls filtering
listActions now unions gates across all enabled accounts (matching the
Signal pattern), and handleDiscordAction/handleTelegramAction resolve
through the per-account merged config instead of reading only the
top-level channel actions object. This lets account-specific
moderation/sticker/presence overrides take effect at both listing and
execution time.