xAI rejects minLength, maxLength, minItems, maxItems, minContains, and
maxContains in tool schemas with a 502 error instead of ignoring them.
This causes all requests to fail when any tool definition includes these
validation-constraint keywords (e.g. sessions_spawn uses maxLength and
maxItems on its attachment fields).
Add stripXaiUnsupportedKeywords() in schema/clean-for-xai.ts, mirroring
the existing cleanSchemaForGemini() pattern. Apply it in normalizeToolParameters()
when the provider is xai directly, or openrouter with an x-ai/* model id.
Fixes tool calls for x-ai/grok-* models both direct and via OpenRouter.
The downloadAndSaveTelegramFile inner function only used the server-side
file path (e.g. "documents/file_42.pdf") or the Content-Disposition
header (which Telegram doesn't send) to derive the saved filename.
The original filename provided by Telegram via msg.document.file_name,
msg.audio.file_name, msg.video.file_name, and msg.animation.file_name
was never passed through, causing all inbound files to lose their
user-provided names.
Now downloadAndSaveTelegramFile accepts an optional telegramFileName
parameter that takes priority over the fetched/server-side name.
The resolveMedia call site extracts the original name from the message
and passes it through.
Closes#31768
Made-with: Cursor
Replace the single per-account messageQueue Promise chain in
DiscordMessageListener with per-channel queues. This restores parallel
processing for channel-bound agents that regressed in 2026.3.1.
Messages within the same channel remain serialized to preserve ordering,
while messages to different channels now proceed independently. Completed
queue entries are cleaned up to prevent memory accumulation.
Closes#31530
The native streaming path (chatStream) and preview final edit path
(chat.update) send raw Markdown text without converting to Slack
mrkdwn format. This causes **bold** to appear as literal asterisks
instead of rendered bold text.
Apply markdownToSlackMrkdwn() in streaming.ts (start/append/stop) and
in dispatch.ts (preview final edit via chat.update) to match the
non-streaming delivery path behavior.
Closes#31892
When enforceFinalTag is active (Google providers), stripBlockTags
correctly returns empty for text without <final> tags. However, the
handleMessageEnd fallback recovered raw text, bypassing this protection
and leaking internal reasoning (e.g. "**Applying single-bot mention
rule**NO_REPLY") to Discord.
Guard the fallback with enforceFinalTag check: if the provider is
supposed to use <final> tags and none were seen, the text is treated
as leaked reasoning and suppressed.
Also harden stripSilentToken regex to allow bold markdown (**) as
separator before NO_REPLY, matching the pattern Gemini Flash Lite
produces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(synology-chat): prevent restart loop in startAccount
startAccount must return a Promise that stays pending while the channel
is running. The gateway wraps the return value in Promise.resolve(), and
when it resolves, the gateway thinks the channel crashed and auto-restarts
with exponential backoff (5s → 10s → 20s..., up to 10 attempts).
Replace the synchronous { stop } return with a Promise<void> that resolves
only when ctx.abortSignal fires, keeping the channel alive until shutdown.
Tested on Synology DS923+ with DSM 7.2 — single startup, no restart loop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(synology-chat): add type guards for startAccount return value
startAccount returns `void | { stop: () => void }` — TypeScript requires
a type guard before accessing .stop on the union type. Added proper checks
in both integration and unit tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(synology-chat): use Readable stream in integration test for Windows compat
Replace EventEmitter + process.nextTick with Readable stream for
request body simulation. The process.nextTick approach caused the test
to hang on Windows CI (120s timeout) because events were not reliably
delivered to readBody() listeners.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: stabilize synology gateway account lifecycle (#23074) (thanks @druide67)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
When an agent streams text and then immediately runs tool calls, the
webchat UI drops the streamed content: the "final" event arrives with
message: undefined (buffer consumed by sub-run), and the client clears
chatStream without saving it to chatMessages.
Before clearing chatStream on a "final" event, check whether the stream
buffer has content. If no finalMessage was provided but the stream is
non-empty, synthesize an assistant message from the buffered text —
mirroring the existing "aborted" handler's preservation logic.
Closes#31895
When an isolated cron agent returns HEARTBEAT_OK (nothing to announce),
the direct delivery is correctly skipped, but the fallback path in
timer.ts still enqueues the summary as a system event to the main
session. Filter out heartbeat-only summaries using isCronSystemEvent
before enqueuing, so internal ack tokens never reach user conversations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(synology-chat): resolve Chat API user_id for reply delivery
Synology Chat outgoing webhooks use a per-integration user_id that
differs from the global Chat API user_id required by method=chatbot.
This caused reply messages to fail silently when the IDs diverged.
Changes:
- Add fetchChatUsers() and resolveChatUserId() to resolve the correct
Chat API user_id via the user_list endpoint (cached 5min)
- Use resolved user_id for all sendMessage() calls in webhook handler
and channel dispatcher
- Add Provider field to MsgContext so the agent runner correctly
identifies the message channel (was "unknown", now "synology-chat")
- Log warnings when user_list API fails or when falling back to
unresolved webhook user_id
- Add 5 tests for user_id resolution (nickname, username, case,
not-found, URL rewrite)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(synology-chat): use Readable stream in integration test for Windows compat
Replace EventEmitter + process.nextTick with Readable stream for
request body simulation. The process.nextTick approach caused the test
to hang on Windows CI (120s timeout) because events were not reliably
delivered to readBody() listeners.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: harden synology reply user resolution and cache scope (#23709) (thanks @druide67)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(plugins): fallback install entrypoints for legacy manifests
* Voice Call: enforce exact webhook path match
* Tests: isolate webhook path suite and reset cron auth state
* chore: keep #31930 scoped to voice webhook path fix
* fix: add changelog for exact voice webhook path match (#31930) (thanks @afurm)
* fix: handle HTTP 529 (Anthropic overloaded) in failover error classification
Classify Anthropic's 529 status code as "rate_limit" so model fallback
triggers reliably without depending on fragile message-based detection.
Closes#28502
* fix: add changelog for HTTP 529 failover classification (#31854) (thanks @bugkill3r)
* fix(slack): guard against undefined text in includes calls during mention handling
* fix: add changelog for mentions/slack null-safe guards (#31865) (thanks @stone-jin)
* fix(memory-lancedb): pass dimensions to embedding API call
- Add dimensions parameter to Embeddings constructor
- Pass dimensions to OpenAI embeddings.create() API call
- Fixes dimension mismatch when using custom embedding models like DashScope text-embedding-v4
* fix: add regression for memory-lancedb dimensions pass-through (#32036) (thanks @scotthuang)
* fix(telegram): guard malformed native menu specs
* fix: harden plugin command registration + telegram menu guard (#31997) (thanks @liuxiaopai-ai)
* fix(gateway): restart heartbeat on model config changes
* fix: add changelog credit for heartbeat model reload (#32046) (thanks @stakeswky)
* test(process): replace no-output timer subprocess with spawn mock
* test(perf): trim repeated setup in cron memory and config suites
* test(perf): reduce per-case setup in script and git-hook tests
* fix(slack): scope debounce key by message timestamp to prevent cross-thread collisions
Top-level channel messages from the same sender shared a bare channel
debounce key, causing concurrent messages in different threads to merge
into a single reply on the wrong thread. Now the debounce key includes
the message timestamp for top-level messages, matching how the downstream
session layer already scopes by canonicalThreadId.
Extracted buildSlackDebounceKey() for testability.
Closes#31935
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: harden slack debounce key routing and ordering (#31951) (thanks @scoootscooob)
* fix(openrouter): skip reasoning.effort injection for x-ai/grok models
x-ai/grok models on OpenRouter do not support the reasoning.effort
parameter and reject payloads containing it with "Invalid arguments
passed to the model." Skip reasoning injection for these models, the
same way we already skip it for the dynamic "auto" routing model.
Closes#32039
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add changelog credit for openrouter x-ai reasoning guard (#32054) (thanks @scoootscooob)
* fix(agents): scope volcengine-plan/byteplus-plan auth lookup to profile resolution
The configure flow stores auth credentials under `provider: "volcengine"`,
but the coding model uses `volcengine-plan` as its provider. Add a scoped
`normalizeProviderIdForAuth` function used only by `listProfilesForProvider`
so coding-plan variants resolve to their base provider for auth credential
lookup without affecting global provider routing.
Closes#31731
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots
PR #28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.
When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.
Relates to #31716
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add changelog credit for fsPolicy image/pdf propagation (#31882) (thanks @justinhuangcode)
* fix: skip Telegram command sync when menu is unchanged (#32017)
Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(telegram): scope command-sync hash cache by bot identity (#32059)
* fix: normalize coding-plan providers in auth order validation
* feat(security): Harden Docker browser container chromium flags (#23889) (#31504)
* Gateway: honor OPENCLAW_GATEWAY_URL override for remote/local calls
* Agents: fix sandbox sessionKey usage for PI embedded subagent calls
* Sandbox: tighten browser container Chromium runtime flags
* fix: add sandbox browser defaults for container hardening
* docs: expand sandbox browser default flags list
* fix: make sandbox browser flags optional and preserve gateway env auth overrides
* docs: scope PR 31504 changelog entry
* style: format gateway call override handling
* fix: dedupe sandbox browser chrome args
* fix: preserve remote tls fingerprint for env gateway override
* fix: enforce auth for env gateway URL override
* chore: document gateway override auth security expectations
* fix(delivery): strip HTML tags for plain-text messaging surfaces
Models occasionally produce HTML tags in their output. While these render
fine on web surfaces, they appear as literal text on WhatsApp, Signal,
SMS, IRC, and Telegram.
Add sanitizeForPlainText() utility that converts common inline HTML to
lightweight-markup equivalents and strips remaining tags. Applied in the
outbound delivery pipeline for non-HTML surfaces only.
Closes#31884
See also: #18558
* fix(outbound): harden plain-text HTML sanitization paths (#32034)
* fix(security): harden file installs and race-path tests
* matrix: bootstrap crypto runtime when npm scripts are skipped
* fix(matrix): keep plugin register sync while bootstrapping crypto runtime (#31989)
* perf(runtime): reduce cron persistence and logger overhead
* test(perf): use prebuilt plugin install archive fixtures
* test(perf): increase guardrail scan read concurrency
* fix(queue): restart drain when message enqueued after idle window
After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES. If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.
Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts). enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately. clearSessionQueues cleans up the callback
cache alongside the queue state.
* fix: avoid stale followup drain callbacks (#31902) (thanks @Lanfei)
* fix(synology-chat): read cfg from outbound context so incomingUrl resolves
* fix: require openclaw.extensions for plugin installs (#32055) (thanks @liuxiaopai-ai)
---------
Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Saurabh <skmishra1991@gmail.com>
Co-authored-by: stone-jin <1520006273@qq.com>
Co-authored-by: scotthuang <scotthuang@tencent.com>
Co-authored-by: User <user@example.com>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: justinhuangcode <justinhuangcode@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: AytuncYildizli <cryptosquanch@gmail.com>
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
Co-authored-by: Jealous <CooLanfei@163.com>
Co-authored-by: white-rm <zhang.xujin@xydigit.com>