diff --git a/CHANGELOG.md b/CHANGELOG.md index b930ee7514a..e092ef78685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ Docs: https://docs.openclaw.ai ### Changes +### Breaking + +### Fixes + +## 2026.3.8 + +### Changes + - CLI/backup: add `openclaw backup create` and `openclaw backup verify` for local state archives, including `--only-config`, `--no-include-workspace`, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs. - macOS/onboarding: add a remote gateway token field for remote mode, preserve existing non-plaintext `gateway.remote.token` config values until explicitly replaced, and warn when the loaded token shape cannot be used directly from the macOS app. (#40187, supersedes #34614) Thanks @cgdusek. - Talk mode: add top-level `talk.silenceTimeoutMs` config so Talk waits a configurable amount of silence before auto-sending the current transcript, while keeping each platform's existing default pause window when unset. (#39607) Thanks @danodoesdesign. Fixes #17147. @@ -18,10 +26,9 @@ Docs: https://docs.openclaw.ai - Docs/Web search: restore $5/month free-credit details, replace defunct "Data for Search"/"Data for AI" plan names with current "Search" plan, and note legacy subscription validity in Brave setup docs. Follows up on #26860. (#40111) Thanks @remusao. - Extensions/ACPX tests: move the shared runtime fixture helper from `src/runtime-internals/` to `src/test-utils/` so the test-only helper no longer looks like shipped runtime code. -### Breaking - ### Fixes +- Update/macOS launchd restart: re-enable disabled LaunchAgent services before updater bootstrap so `openclaw update` can recover from a disabled gateway service instead of leaving the restart step stuck. - macOS app/chat UI: route browser proxy through the local node browser service, preserve plain-text paste semantics, strip completed assistant trace/debug wrapper noise from transcripts, refresh permission state after returning from System Settings, and tolerate malformed cron rows in the macOS tab. (#39516) Thanks @Imhermes1. - Android/Play distribution: remove self-update, background location, `screen.record`, and background mic capture from the Android app, narrow the foreground service to `dataSync` only, and clean up the legacy `location.enabledMode=always` preference migration. (#39660) Thanks @obviyus. - Telegram/DM routing: dedupe inbound Telegram DMs per agent instead of per session key so the same DM cannot trigger duplicate replies when both `agent:main:main` and `agent:main:telegram:direct:` resolve for one agent. Fixes #40005. Supersedes #40116. (#40519) thanks @obviyus. diff --git a/appcast.xml b/appcast.xml index 7d0a1988b39..4bceb205614 100644 --- a/appcast.xml +++ b/appcast.xml @@ -2,6 +2,80 @@ OpenClaw + + 2026.3.8-beta.1 + Mon, 09 Mar 2026 07:19:57 +0000 + https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml + 2026030801 + 2026.3.8-beta.1 + 15.0 + OpenClaw 2026.3.8-beta.1 +

Changes

+
    +
  • CLI/backup: add openclaw backup create and openclaw backup verify for local state archives, including --only-config, --no-include-workspace, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs.
  • +
  • macOS/onboarding: add a remote gateway token field for remote mode, preserve existing non-plaintext gateway.remote.token config values until explicitly replaced, and warn when the loaded token shape cannot be used directly from the macOS app. (#40187, supersedes #34614) Thanks @cgdusek.
  • +
  • Talk mode: add top-level talk.silenceTimeoutMs config so Talk waits a configurable amount of silence before auto-sending the current transcript, while keeping each platform's existing default pause window when unset. (#39607) Thanks @danodoesdesign. Fixes #17147.
  • +
  • TUI: infer the active agent from the current workspace when launched inside a configured agent workspace, while preserving explicit agent: session targets. (#39591) thanks @arceus77-7.
  • +
  • Tools/Brave web search: add opt-in tools.web.search.brave.mode: "llm-context" so web_search can call Brave's LLM Context endpoint and return extracted grounding snippets with source metadata, plus config/docs/test coverage. (#33383) Thanks @thirumaleshp.
  • +
  • CLI/install: include the short git commit hash in openclaw --version output when metadata is available, and keep installer version checks compatible with the decorated format. (#39712) thanks @sourman.
  • +
  • CLI/backup: improve archive naming for date sorting, add config-only backup mode, and harden backup planning, publication, and verification edge cases. (#40163) Thanks @gumadeiras.
  • +
  • ACP/Provenance: add optional ACP ingress provenance metadata and visible receipt injection (openclaw acp --provenance off|meta|meta+receipt) so OpenClaw agents can retain and report ACP-origin context with session trace IDs. (#40473) thanks @mbelinky.
  • +
  • Tools/web search: alphabetize provider ordering across runtime selection, onboarding/configure pickers, and config metadata, so provider lists stay neutral and multi-key auto-detect now prefers Grok before Kimi. (#40259) thanks @kesku.
  • +
  • Docs/Web search: restore $5/month free-credit details, replace defunct "Data for Search"/"Data for AI" plan names with current "Search" plan, and note legacy subscription validity in Brave setup docs. Follows up on #26860. (#40111) Thanks @remusao.
  • +
  • Extensions/ACPX tests: move the shared runtime fixture helper from src/runtime-internals/ to src/test-utils/ so the test-only helper no longer looks like shipped runtime code.
  • +
+

Fixes

+
    +
  • macOS app/chat UI: route browser proxy through the local node browser service, preserve plain-text paste semantics, strip completed assistant trace/debug wrapper noise from transcripts, refresh permission state after returning from System Settings, and tolerate malformed cron rows in the macOS tab. (#39516) Thanks @Imhermes1.
  • +
  • Android/Play distribution: remove self-update, background location, screen.record, and background mic capture from the Android app, narrow the foreground service to dataSync only, and clean up the legacy location.enabledMode=always preference migration. (#39660) Thanks @obviyus.
  • +
  • Telegram/DM routing: dedupe inbound Telegram DMs per agent instead of per session key so the same DM cannot trigger duplicate replies when both agent:main:main and agent:main:telegram:direct: resolve for one agent. Fixes #40005. Supersedes #40116. (#40519) thanks @obviyus.
  • +
  • Cron/Telegram announce delivery: route text-only announce jobs through the real outbound adapters after finalizing descendant output so plain Telegram targets no longer report delivered: true when no message actually reached Telegram. (#40575) thanks @obviyus.
  • +
  • Matrix/DM routing: add safer fallback detection for broken m.direct homeservers, honor explicit room bindings over DM classification, and preserve room-bound agent selection for Matrix DM rooms. (#19736) Thanks @derbronko.
  • +
  • Feishu/plugin onboarding: clear the short-lived plugin discovery cache before reloading the registry after installing a channel plugin, so onboarding no longer re-prompts to download Feishu immediately after a successful install. Fixes #39642. (#39752) Thanks @GazeKingNuWu.
  • +
  • Plugins/channel onboarding: prefer bundled channel plugins over duplicate npm-installed copies during onboarding and release-channel sync, preventing bundled plugins from being shadowed by npm installs with the same plugin ID. (#40092)
  • +
  • Config/runtime snapshots: keep secrets-runtime-resolved config and auth-profile snapshots intact after config writes so follow-up reads still see file-backed secret values while picking up the persisted config update. (#37313) thanks @bbblending.
  • +
  • Gateway/Control UI: resolve bundled dashboard assets through symlinked global wrappers and auto-detected package roots, while keeping configured and custom roots on the strict hardlink boundary. (#40385) Thanks @LarytheLord.
  • +
  • Browser/extension relay: add browser.relayBindHost so the Chrome relay can bind to an explicit non-loopback address for WSL2 and other cross-namespace setups, while preserving loopback-only defaults. (#39364) Thanks @mvanhorn.
  • +
  • Browser/CDP: normalize loopback direct WebSocket CDP URLs back to HTTP(S) for /json/* tab operations so local ws:// / wss:// profiles can still list, focus, open, and close tabs after the new direct-WS support lands. (#31085) Thanks @shrey150.
  • +
  • Browser/CDP: rewrite wildcard ws://0.0.0.0 and ws://[::] debugger URLs from remote /json/version responses back to the external CDP host/port, fixing Browserless-style container endpoints. (#17760) Thanks @joeharouni.
  • +
  • Browser/extension relay: wait briefly for a previously attached Chrome tab to reappear after transient relay drops before failing with tab not found, reducing noisy reconnect flakes. (#32461) Thanks @AaronWander.
  • +
  • macOS/Tailscale gateway discovery: keep Tailscale Serve probing alive when other remote gateways are already discovered, prefer direct transport for resolved .ts.net and Tailscale Serve gateways, and set TERM=dumb for GUI-launched Tailscale CLI discovery. (#40167) thanks @ngutman.
  • +
  • TUI/theme: detect light terminal backgrounds via COLORFGBG and pick a WCAG AA-compliant light palette, with OPENCLAW_THEME=light|dark override for terminals without auto-detection. (#38636) Thanks @ademczuk and @vincentkoc.
  • +
  • Agents/openai-codex: normalize gpt-5.4 fallback transport back to openai-codex-responses on chatgpt.com/backend-api when config drifts to the generic OpenAI responses endpoint. (#38736) Thanks @0xsline.
  • +
  • Models/openai-codex GPT-5.4 forward-compat: use the GPT-5.4 1,050,000-token context window and 128,000 max tokens for openai-codex/gpt-5.4 instead of inheriting stale legacy Codex limits in resolver fallbacks and model listing. (#37876) thanks @yuweuii.
  • +
  • Tools/web search: restore Perplexity OpenRouter/Sonar compatibility for legacy OPENROUTER_API_KEY, sk-or-..., and explicit perplexity.baseUrl / model setups while keeping direct Perplexity keys on the native Search API path. (#39937) Thanks @obviyus.
  • +
  • Agents/failover: detect Amazon Bedrock Too many tokens per day quota errors as rate limits across fallback, cron retry, and memory embeddings while keeping context-window too many tokens per request errors out of the rate-limit lane. (#39377) Thanks @gambletan.
  • +
  • Mattermost replies: keep root_id pinned to the existing thread root when an agent replies inside a thread, while still using reply-target threading for top-level posts. (#27744) thanks @hnykda.
  • +
  • Telegram/DM partial streaming: keep DM preview lanes on real message edits instead of native draft materialization so final replies no longer flash a second duplicate copy before collapsing back to one.
  • +
  • macOS overlays: fix VoiceWake, Talk, and Notify overlay exclusivity crashes by removing shared inout visibility mutation from OverlayPanelFactory.present, and add a repeated Talk overlay smoke test. (#39275, #39321) Thanks @fellanH.
  • +
  • macOS Talk Mode: set the speech recognition request taskHint to .dictation for mic capture, and add regression coverage for the request defaults. (#38445) Thanks @dmiv.
  • +
  • macOS release packaging: default scripts/package-mac-app.sh to universal binaries for BUILD_CONFIG=release, and clarify that scripts/package-mac-dist.sh already produces the release zip + DMG. (#33891) Thanks @cgdusek.
  • +
  • Hooks/session-memory: keep /new and /reset memory artifacts in the bound agent workspace and align saved reset session keys with that workspace when stale main-agent keys leak into the hook path. (#39875) thanks @rbutera.
  • +
  • Sessions/model switch: clear stale cached contextTokens when a session changes models so status and runtime paths recompute against the active model window. (#38044) thanks @yuweuii.
  • +
  • ACP/session history: persist transcripts for successful ACP child runs, preserve exact transcript text, record ACP spawned-session lineage, and keep spawn-time transcript-path persistence best-effort so history storage failures do not block execution. (#40137) thanks @mbelinky.
  • +
  • Docs/browser: add a layered WSL2 + Windows remote Chrome CDP troubleshooting guide, including Control UI origin pitfalls and extension-relay bind-address guidance. (#39407) Thanks @Owlock.
  • +
  • Context engine registry/bundled builds: share the registry state through a globalThis singleton so duplicated bundled module copies can resolve engines registered by each other at runtime, with regression coverage for duplicate-module imports. (#40115) thanks @jalehman.
  • +
  • Podman/setup: fix cannot chdir: Permission denied in run_as_user when setup-podman.sh is invoked from a directory the target user cannot access, by wrapping user-switch calls in a subshell that cd's to /tmp with / fallback. (#39435) Thanks @langdon and @jlcbk.
  • +
  • Podman/SELinux: auto-detect SELinux enforcing/permissive mode and add :Z relabel to bind mounts in run-openclaw-podman.sh and the Quadlet template, fixing EACCES on Fedora/RHEL hosts. Supports OPENCLAW_BIND_MOUNT_OPTIONS override. (#39449) Thanks @langdon and @githubbzxs.
  • +
  • Agents/context-engine plugins: bootstrap runtime plugins once at embedded-run, compaction, and subagent boundaries so plugin-provided context engines and hooks load from the active workspace before runtime resolution. (#40232)
  • +
  • Docs/Changelog: correct the contributor credit for the bundled Control UI global-install fix to @LarytheLord. (#40420) Thanks @velvet-shark.
  • +
  • Telegram/media downloads: time out only stalled body reads so polling recovers from hung file downloads without aborting slow downloads that are still streaming data. (#40098) thanks @tysoncung.
  • +
  • Docker/runtime image: prune dev dependencies, strip build-only dist metadata for smaller Docker images. (#40307) Thanks @vincentkoc.
  • +
  • Gateway/restart timeout recovery: exit non-zero when restart-triggered shutdown drains time out so launchd/systemd restart the gateway instead of treating the failed restart as a clean stop. Landed from contributor PR #40380 by @dsantoreis. Thanks @dsantoreis.
  • +
  • Gateway/config restart guard: validate config before service start/restart and keep post-SIGUSR1 startup failures from crashing the gateway process, reducing invalid-config restart loops and macOS permission loss. Landed from contributor PR #38699 by @lml2468. Thanks @lml2468.
  • +
  • Gateway/launchd respawn detection: treat XPC_SERVICE_NAME as a launchd supervision hint so macOS restarts exit cleanly under launchd instead of attempting detached self-respawn. Landed from contributor PR #20555 by @dimat. Thanks @dimat.
  • +
  • Telegram/poll restart cleanup: abort the in-flight Telegram API fetch when shutdown or forced polling restarts stop a runner, preventing stale getUpdates long polls from colliding with the replacement runner. Landed from contributor PR #23950 by @Gkinthecodeland. Thanks @Gkinthecodeland.
  • +
  • Cron/restart catch-up staggering: limit immediate missed-job replay on startup and reschedule the deferred remainder from the post-catchup clock so restart bursts do not starve the gateway or silently skip overdue recurring jobs. Landed from contributor PR #18925 by @rexlunae. Thanks @rexlunae.
  • +
  • Cron/owner-only tools: pass trusted isolated cron runs into the embedded agent with owner context so cron/gateway tooling remains available after the owner-auth hardening narrowed direct-message ownership inference.
  • +
  • Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent.
  • +
  • MS Teams/authz: keep groupPolicy: "allowlist" enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent.
  • +
  • Security/system.run: bind approved bun and deno run script operands to on-disk file snapshots so post-approval script rewrites are denied before execution.
  • +
  • Skills/download installs: pin the validated per-skill tools root before writing downloaded archives, so rebinding the lexical tools path cannot redirect download writes outside the intended tools directory. Thanks @tdjackey.
  • +
+

View full changelog

+]]>
+ +
2026.3.7 Sun, 08 Mar 2026 04:42:35 +0000 @@ -584,144 +658,5 @@ - - 2026.3.1 - Mon, 02 Mar 2026 04:40:59 +0000 - https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml - 2026030190 - 2026.3.1 - 15.0 - OpenClaw 2026.3.1 -

Changes

-
    -
  • Agents/Thinking defaults: set adaptive as the default thinking level for Anthropic Claude 4.6 models (including Bedrock Claude 4.6 refs) while keeping other reasoning-capable models at low unless explicitly configured.
  • -
  • Gateway/Container probes: add built-in HTTP liveness/readiness endpoints (/health, /healthz, /ready, /readyz) for Docker/Kubernetes health checks, with fallback routing so existing handlers on those paths are not shadowed. (#31272) Thanks @vincentkoc.
  • -
  • Android/Nodes: add camera.list, device.permissions, device.health, and notifications.actions (open/dismiss/reply) on Android nodes, plus first-class node-tool actions for the new device/notification commands. (#28260) Thanks @obviyus.
  • -
  • Discord/Thread bindings: replace fixed TTL lifecycle with inactivity (idleHours, default 24h) plus optional hard maxAgeHours lifecycle controls, and add /session idle + /session max-age commands for focused thread-bound sessions. (#27845) Thanks @osolmaz.
  • -
  • Telegram/DM topics: add per-DM direct + topic config (allowlists, dmPolicy, skills, systemPrompt, requireTopic), route DM topics as distinct inbound/outbound sessions, and enforce topic-aware authorization/debounce for messages, callbacks, commands, and reactions. Landed from contributor PR #30579 by @kesor. Thanks @kesor.
  • -
  • Web UI/Cron i18n: localize cron page labels, filters, form help text, and validation/error messaging in English and zh-CN. (#29315) Thanks @BUGKillerKing.
  • -
  • OpenAI/Streaming transport: make openai Responses WebSocket-first by default (transport: "auto" with SSE fallback), add shared OpenAI WS stream/connection runtime wiring with per-session cleanup, and preserve server-side compaction payload mutation (store + context_management) on the WS path.
  • -
  • Android/Gateway capability refresh: add live Android capability integration coverage and node canvas capability refresh wiring, plus runtime hardening for A2UI readiness retries, scoped canvas URL normalization, debug diagnostics JSON, and JavaScript MIME delivery. (#28388) Thanks @obviyus.
  • -
  • Android/Nodes parity: add system.notify, photos.latest, contacts.search/contacts.add, calendar.events/calendar.add, and motion.activity/motion.pedometer, with motion sensor-aware command gating and improved activity sampling reliability. (#29398) Thanks @obviyus.
  • -
  • CLI/Config: add openclaw config file to print the active config file path resolved from OPENCLAW_CONFIG_PATH or the default location. (#26256) thanks @cyb1278588254.
  • -
  • Feishu/Docx tables + uploads: add feishu_doc actions for Docx table creation/cell writing (create_table, write_table_cells, create_table_with_values) and image/file uploads (upload_image, upload_file) with stricter create/upload error handling for missing document_id and placeholder cleanup failures. (#20304) Thanks @xuhao1.
  • -
  • Feishu/Reactions: add inbound im.message.reaction.created_v1 handling, route verified reactions through synthetic inbound turns, and harden verification with timeout + fail-closed filtering so non-bot or unverified reactions are dropped. (#16716) Thanks @schumilin.
  • -
  • Feishu/Chat tooling: add feishu_chat tool actions for chat info and member queries, with configurable enablement under channels.feishu.tools.chat. (#14674) Thanks @liuweifly.
  • -
  • Feishu/Doc permissions: support optional owner permission grant fields on feishu_doc create and report permission metadata only when the grant call succeeds, with regression coverage for success/failure/omitted-owner paths. (#28295) Thanks @zhoulongchao77.
  • -
  • Web UI/i18n: add German (de) locale support and auto-render language options from supported locale constants in Overview settings. (#28495) thanks @dsantoreis.
  • -
  • Tools/Diffs: add a new optional diffs plugin tool for read-only diff rendering from before/after text or unified patches, with gateway viewer URLs for canvas and PNG image output. Thanks @gumadeiras.
  • -
  • Memory/LanceDB: support custom OpenAI baseUrl and embedding dimensions for LanceDB memory. (#17874) Thanks @rish2jain and @vincentkoc.
  • -
  • ACP/ACPX streaming: pin ACPX plugin support to 0.1.15, add configurable ACPX command/version probing, and streamline ACP stream delivery (final_only default + reduced tool-event noise) with matching runtime and test updates. (#30036) Thanks @osolmaz.
  • -
  • Shell env markers: set OPENCLAW_SHELL across shell-like runtimes (exec, acp, acp-client, tui-local) so shell startup/config rules can target OpenClaw contexts consistently, and document the markers in env/exec/acp/TUI docs. Thanks @vincentkoc.
  • -
  • Cron/Heartbeat light bootstrap context: add opt-in lightweight bootstrap mode for automation runs (--light-context for cron agent turns and agents.*.heartbeat.lightContext for heartbeat), keeping only HEARTBEAT.md for heartbeat runs and skipping bootstrap-file injection for cron lightweight runs. (#26064) Thanks @jose-velez.
  • -
  • OpenAI/WebSocket warm-up: add optional OpenAI Responses WebSocket warm-up (response.create with generate:false), enable it by default for openai/*, and expose params.openaiWsWarmup for per-model enable/disable control.
  • -
  • Agents/Subagents runtime events: replace ad-hoc subagent completion system-message handoff with typed internal completion events (task_completion) that are rendered consistently across direct and queued announce paths, with gateway/CLI plumbing for structured internalEvents.
  • -
-

Breaking

-
    -
  • BREAKING: Node exec approval payloads now require systemRunPlan. host=node approval requests without that plan are rejected.
  • -
  • BREAKING: Node system.run execution now pins path-token commands to the canonical executable path (realpath) in both allowlist and approval execution flows. Integrations/tests that asserted token-form argv (for example tr) must now accept canonical paths (for example /usr/bin/tr).
  • -
-

Fixes

-
    -
  • Android/Nodes reliability: reject facing=both when deviceId is set to avoid mislabeled duplicate captures, allow notification open/reply on non-clearable entries while still gating dismiss, trigger listener rebind before notification actions, and scale invoke-result ack timeout to invoke budget for large clip payloads. (#28260) Thanks @obviyus.
  • -
  • Windows/Plugin install: avoid spawn EINVAL on Windows npm/npx invocations by resolving to node + npm CLI scripts instead of spawning .cmd directly. Landed from contributor PR #31147 by @codertony. Thanks @codertony.
  • -
  • LINE/Voice transcription: classify M4A voice media as audio/mp4 (not video/mp4) by checking the MPEG-4 ftyp major brand (M4A / M4B ), restoring voice transcription for LINE voice messages. Landed from contributor PR #31151 by @scoootscooob. Thanks @scoootscooob.
  • -
  • Slack/Announce target account routing: enable session-backed announce-target lookup for Slack so multi-account announces resolve the correct accountId instead of defaulting to bot-token context. Landed from contributor PR #31028 by @taw0002. Thanks @taw0002.
  • -
  • Android/Voice screen TTS: stream assistant speech via ElevenLabs WebSocket in Talk Mode, stop cleanly on speaker mute/barge-in, and ignore stale out-of-order stream events. (#29521) Thanks @gregmousseau.
  • -
  • Android/Photos permissions: declare Android 14+ selected-photo access permission (READ_MEDIA_VISUAL_USER_SELECTED) and align Android permission/settings paths with current minSdk behavior for more reliable permission state handling.
  • -
  • Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.
  • -
  • Cron/Delivery: disable the agent messaging tool when delivery.mode is "none" so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.
  • -
  • CLI/Cron: clarify cron list output by renaming Agent to Agent ID and adding a Model column for isolated agent-turn jobs. (#26259) Thanks @openperf.
  • -
  • Feishu/Reply media attachments: send Feishu reply mediaUrl/mediaUrls payloads as attachments alongside text/streamed replies in the reply dispatcher, including legacy fallback when mediaUrls is empty. (#28959) Thanks @icesword0760.
  • -
  • Slack/User-token resolution: normalize Slack account user-token sourcing through resolved account metadata (SLACK_USER_TOKEN env + config) so monitor reads, Slack actions, directory lookups, onboarding allow-from resolution, and capabilities probing consistently use the effective user token. (#28103) Thanks @Glucksberg.
  • -
  • Feishu/Outbound session routing: stop assuming bare oc_ identifiers are always group chats, honor explicit dm:/group: prefixes for oc_ chat IDs, and default ambiguous bare oc_ targets to direct routing to avoid DM session misclassification. (#10407) Thanks @Bermudarat.
  • -
  • Feishu/Group session routing: add configurable group session scopes (group, group_sender, group_topic, group_topic_sender) with legacy topicSessionMode=enabled compatibility so Feishu group conversations can isolate sessions by sender/topic as configured. (#17798) Thanks @yfge.
  • -
  • Feishu/Reply-in-thread routing: add replyInThread config (disabled|enabled) for group replies, propagate reply_in_thread across text/card/media/streaming sends, and align topic-scoped session routing so newly created reply threads stay on the same session root. (#27325) Thanks @kcinzgg.
  • -
  • Feishu/Probe status caching: cache successful probeFeishu() bot-info results for 10 minutes (bounded cache with per-account keying) to reduce repeated status/onboarding probe API calls, while bypassing cache for failures and exceptions. (#28907) Thanks @Glucksberg.
  • -
  • Feishu/Opus media send type: send .opus attachments with msg_type: "audio" (instead of "media") so Feishu voice messages deliver correctly while .mp4 remains msg_type: "media" and documents remain msg_type: "file". (#28269) Thanks @Glucksberg.
  • -
  • Feishu/Mobile video media type: treat inbound message_type: "media" as video-equivalent for media key extraction, placeholder inference, and media download resolution so mobile-app video sends ingest correctly. (#25502) Thanks @4ier.
  • -
  • Feishu/Inbound sender fallback: fall back to sender_id.user_id when sender_id.open_id is missing on inbound events, and use ID-type-aware sender lookup so mobile-delivered messages keep stable sender identity/routing. (#26703) Thanks @NewdlDewdl.
  • -
  • Feishu/Reply context metadata: include inbound parent_id and root_id as ReplyToId/RootMessageId in inbound context, and parse interactive-card quote bodies into readable text when fetching replied messages. (#18529) Thanks @qiangu.
  • -
  • Feishu/Post embedded media: extract media tags from inbound rich-text (post) messages and download embedded video/audio files alongside existing embedded-image handling, with regression coverage. (#21786) Thanks @laopuhuluwa.
  • -
  • Feishu/Local media sends: propagate mediaLocalRoots through Feishu outbound media sending into loadWebMedia so local path attachments work with post-CVE local-root enforcement. (#27884) Thanks @joelnishanth.
  • -
  • Feishu/Group wildcard policy fallback: honor channels.feishu.groups["*"] when no explicit group match exists so unmatched groups inherit wildcard reply-policy settings instead of falling back to global defaults. (#29456) Thanks @WaynePika.
  • -
  • Feishu/Inbound media regression coverage: add explicit tests for message resource type mapping (image stays image, non-image maps to file) to prevent reintroducing unsupported Feishu type=audio fetches. (#16311, #8746) Thanks @Yaxuan42.
  • -
  • TTS/Voice bubbles: use opus output and enable audioAsVoice routing for Feishu and WhatsApp (in addition to Telegram) so supported channels receive voice-bubble playback instead of file-style audio attachments. (#27366) Thanks @smthfoxy.
  • -
  • Telegram/Reply media context: include replied media files in inbound context when replying to media, defer reply-media downloads to debounce flush, gate reply-media fetch behind DM authorization, and preserve replied media when non-vision sticker fallback runs (including cached-sticker paths). (#28488) Thanks @obviyus.
  • -
  • Android/Nodes notification wake flow: enable Android system.notify default allowlist, emit notifications.changed events for posted/removed notifications (excluding OpenClaw app-owned notifications), canonicalize notification session keys before enqueue/wake routing, and skip heartbeat wakes when consecutive notification summaries dedupe. (#29440) Thanks @obviyus.
  • -
  • Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.
  • -
  • Feishu/Multi-account + reply reliability: add channels.feishu.defaultAccount outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as msg_type: "file", and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.
  • -
  • Cron/Delivery: disable the agent messaging tool when delivery.mode is "none" so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo.
  • -
  • Feishu/Inbound rich-text parsing: preserve share_chat payload summaries when available and add explicit parsing for rich-text code/code_block/pre tags so forwarded and code-heavy messages keep useful context in agent input. (#28591) Thanks @kevinWangSheng.
  • -
  • Feishu/Post markdown parsing: parse rich-text post payloads through a shared markdown-aware parser with locale-wrapper support, preserved mention/image metadata extraction, and inline/fenced code fidelity for agent input rendering. (#12755) Thanks @WilsonLiu95.
  • -
  • Telegram/Outbound chunking: route oversize splitting through the shared outbound pipeline (including subagents), retry Telegram sends when escaped HTML exceeds limits, and preserve boundary whitespace when retry re-splitting rendered chunks so plain-text/transcript fidelity is retained. (#29342, #27317; follow-up to #27461) Thanks @obviyus.
  • -
  • Slack/Native commands: register Slack native status as /agentstatus (Slack-reserved /status) so manifest slash command registration stays valid while text /status still works. Landed from contributor PR #29032 by @maloqab. Thanks @maloqab.
  • -
  • Android/Camera clip: remove camera.clip HTTP-upload fallback to base64 so clip transport is deterministic and fail-loud, and reject non-positive maxWidth values so invalid inputs fall back to the safe resize default. (#28229) Thanks @obviyus.
  • -
  • Android/Gateway canvas capability refresh: send node.canvas.capability.refresh with object params ({}) from Android node runtime so gateway object-schema validation accepts refresh retries and A2UI host recovery works after scoped capability expiry. (#28413) Thanks @obviyus.
  • -
  • Gateway/Control UI origins: honor gateway.controlUi.allowedOrigins: ["*"] wildcard entries (including trimmed values) and lock behavior with regression tests. Landed from contributor PR #31058 by @byungsker. Thanks @byungsker.
  • -
  • Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin.
  • -
  • Agents/Sessions list transcript paths: handle missing/non-string/relative sessions.list.path values and per-agent {agentId} templates when deriving transcriptPath, so cross-agent session listings resolve to concrete agent session files instead of workspace-relative paths. (#24775) Thanks @martinfrancois.
  • -
  • Gateway/Control UI CSP: allow required Google Fonts origins in Control UI CSP. (#29279) Thanks @Glucksberg and @vincentkoc.
  • -
  • CLI/Install: add an npm-link fallback to fix CLI startup Permission denied failures (exit 127) on affected installs. (#17151) Thanks @sskyu and @vincentkoc.
  • -
  • Onboarding/Custom providers: improve verification reliability for slower local endpoints (for example Ollama) during setup. (#27380) Thanks @Sid-Qin.
  • -
  • Plugins/NPM spec install: fix npm-spec plugin installs when npm pack output is empty by detecting newly created .tgz archives in the pack directory. (#21039) Thanks @graysurf and @vincentkoc.
  • -
  • Plugins/Install: clear stale install errors when an npm package is not found so follow-up install attempts report current state correctly. (#25073) Thanks @dalefrieswthat.
  • -
  • Security/Feishu webhook ingress: bound unauthenticated webhook rate-limit state with stale-window pruning and a hard key cap to prevent unbounded pre-auth memory growth from rotating source keys. (#26050) Thanks @bmendonca3.
  • -
  • Gateway/macOS supervised restart: actively launchctl kickstart -k during intentional supervised restarts to bypass LaunchAgent ThrottleInterval delays, and fall back to in-process restart when kickstart fails. Landed from contributor PR #29078 by @cathrynlavery. Thanks @cathrynlavery.
  • -
  • Daemon/macOS TLS certs: default LaunchAgent service env NODE_EXTRA_CA_CERTS to /etc/ssl/cert.pem (while preserving explicit overrides) so HTTPS clients no longer fail with local-issuer errors under launchd. (#27915) Thanks @Lukavyi.
  • -
  • Discord/Components wildcard handlers: use distinct internal registration sentinel IDs and parse those sentinels as wildcard keys so select/user/role/channel/mentionable/modal interactions are not dropped by raw customId dedupe paths. Landed from contributor PR #29459 by @Sid-Qin. Thanks @Sid-Qin.
  • -
  • Feishu/Reaction notifications: add channels.feishu.reactionNotifications (off | own | all, default own) so operators can disable reaction ingress or allow all verified reaction events (not only bot-authored message reactions). (#28529) Thanks @cowboy129.
  • -
  • Feishu/Typing backoff: re-throw Feishu typing add/remove rate-limit and quota errors (429, 99991400, 99991403) and detect SDK non-throwing backoff responses so the typing keepalive circuit breaker can stop retries instead of looping indefinitely. (#28494) Thanks @guoqunabc.
  • -
  • Feishu/Zalo runtime logging: replace direct console.log/error usage in Feishu typing-indicator paths and Zalo monitor paths with runtime-gated logger calls so verbosity controls are respected while preserving typing backoff behavior. (#18841) Thanks @Clawborn.
  • -
  • Feishu/Group sender allowlist fallback: add global channels.feishu.groupSenderAllowFrom sender authorization for group chats, with per-group groups..allowFrom precedence and regression coverage for allow/block/precedence behavior. (#29174) Thanks @1MoreBuild.
  • -
  • Feishu/Docx append/write ordering: insert converted Docx blocks sequentially (single-block creates) so Feishu append/write preserves markdown block order instead of returning shuffled sections in asynchronous batch inserts. (#26172, #26022) Thanks @echoVic.
  • -
  • Feishu/Docx convert fallback chunking: recursively split oversized markdown chunks (including long no-heading sections) when document.convert hits content limits, while keeping fenced-code-aware split boundaries whenever possible. (#14402) Thanks @lml2468.
  • -
  • Feishu/API quota controls: add typingIndicator and resolveSenderNames config flags (top-level and per-account) so operators can disable typing reactions and sender-name lookup requests while keeping default behavior unchanged. (#10513) Thanks @BigUncle.
  • -
  • Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted System: context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.
  • -
  • Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.
  • -
  • Sessions/Internal routing: preserve established external lastTo/lastChannel routes for internal/non-deliverable turns, with added coverage for no-fallback internal routing behavior. Landed from contributor PR #30941 by @graysurf. Thanks @graysurf.
  • -
  • Control UI/Debug log layout: render Debug Event Log payloads at full width to prevent payload JSON from being squeezed into a narrow side column. Landed from contributor PR #30978 by @stozo04. Thanks @stozo04.
  • -
  • Auto-reply/NO_REPLY: strip NO_REPLY token from mixed-content messages instead of leaking raw control text to end users. Landed from contributor PR #31080 by @scoootscooob. Thanks @scoootscooob.
  • -
  • Install/npm: fix npm global install deprecation warnings. (#28318) Thanks @vincentkoc.
  • -
  • Update/Global npm: fallback to --omit=optional when global npm update fails so optional dependency install failures no longer abort update flows. (#24896) Thanks @xinhuagu and @vincentkoc.
  • -
  • Inbound metadata/Multi-account routing: include account_id in trusted inbound metadata so multi-account channel sessions can reliably disambiguate the receiving account in prompt context. Landed from contributor PR #30984 by @Stxle2. Thanks @Stxle2.
  • -
  • Model directives/Auth profiles: split /model profile suffixes at the first @ after the last slash so email-based auth profile IDs (for example OAuth profile IDs) resolve correctly. Landed from contributor PR #30932 by @haosenwang1018. Thanks @haosenwang1018.
  • -
  • Cron/Delivery mode none: send explicit delivery: { mode: "none" } from cron editor for both add and update flows so previous announce delivery is actually cleared. Landed from contributor PR #31145 by @byungsker. Thanks @byungsker.
  • -
  • Cron editor viewport: make the sticky cron edit form independently scrollable with viewport-bounded height so lower fields/actions are reachable on shorter screens. Landed from contributor PR #31133 by @Sid-Qin. Thanks @Sid-Qin.
  • -
  • Agents/Thinking fallback: when providers reject unsupported thinking levels without enumerating alternatives, retry with think=off to avoid hard failure during model/provider fallback chains. Landed from contributor PR #31002 by @yfge. Thanks @yfge.
  • -
  • Ollama/Embedded runner base URL precedence: prioritize configured provider baseUrl over model defaults for embedded Ollama runs so Docker and remote-host setups avoid localhost fetch failures. (#30964) Thanks @stakeswky.
  • -
  • Agents/Failover reason classification: avoid false rate-limit classification from incidental tpm substrings by matching TPM as a standalone token/phrase and keeping auth-context errors on the auth path. Landed from contributor PR #31007 by @HOYALIM. Thanks @HOYALIM.
  • -
  • CLI/Cron: clarify cron list output by renaming Agent to Agent ID and adding a Model column for isolated agent-turn jobs. (#26259) Thanks @openperf.
  • -
  • Gateway/WS: close repeated post-handshake unauthorized role:* request floods per connection and sample duplicate rejection logs, preventing a single misbehaving client from degrading gateway responsiveness. (#20168) Thanks @acy103, @vibecodooor, and @vincentkoc.
  • -
  • Gateway/Auth: improve device-auth v2 migration diagnostics so operators get clearer guidance when legacy clients connect. (#28305) Thanks @vincentkoc.
  • -
  • CLI/Ollama config: allow config set for Ollama apiKey without predeclared provider config. (#29299) Thanks @vincentkoc.
  • -
  • Ollama/Autodiscovery: harden autodiscovery and warning behavior. (#29201) Thanks @marcodelpin and @vincentkoc.
  • -
  • Ollama/Context window: unify context window handling across discovery, merge, and OpenAI-compatible transport paths. (#29205) Thanks @Sid-Qin, @jimmielightner, and @vincentkoc.
  • -
  • Agents/Ollama: demote empty-discovery logging from warn to debug to reduce noisy warnings in normal edge-case discovery flows. (#26379) Thanks @byungsker.
  • -
  • fix(model): preserve reasoning in provider fallback resolution. (#29285) Fixes #25636. Thanks @vincentkoc.
  • -
  • Docker/Image permissions: normalize /app/extensions, /app/.agent, and /app/.agents to directory mode 755 and file mode 644 during image build so plugin discovery does not block inherited world-writable paths. (#30191) Fixes #30139. Thanks @edincampara.
  • -
  • OpenAI Responses/Compaction: rewrite and unify the OpenAI Responses store patches to treat empty baseUrl as non-direct, honor compat.supportsStore=false, and auto-inject server-side compaction context_management for compatible direct OpenAI models (with per-model opt-out/threshold overrides). Landed from contributor PRs #16930 (@OiPunk), #22441 (@EdwardWu7), and #25088 (@MoerAI). Thanks @OiPunk, @EdwardWu7, and @MoerAI.
  • -
  • Sandbox/Browser Docker: pass OPENCLAW_BROWSER_NO_SANDBOX=1 to sandbox browser containers and bump sandbox browser security hash epoch so existing containers are recreated and pick up the env on upgrade. (#29879) Thanks @Lukavyi.
  • -
  • Usage normalization: clamp negative prompt/input token values to zero (including prompt_tokens alias inputs) so /usage and TUI usage displays cannot show nonsensical negative counts. Landed from contributor PR #31211 by @scoootscooob. Thanks @scoootscooob.
  • -
  • Secrets/Auth profiles: normalize inline SecretRef token/key values to canonical tokenRef/keyRef before persistence, and keep explicit keyRef precedence when inline refs are also present. Landed from contributor PR #31047 by @minupla. Thanks @minupla.
  • -
  • Tools/Edit workspace boundary errors: preserve the real Path escapes workspace root failure path instead of surfacing a misleading access/file-not-found error when editing outside workspace roots. Landed from contributor PR #31015 by @haosenwang1018. Thanks @haosenwang1018.
  • -
  • Browser/Open & navigate: accept url as an alias parameter for open and navigate. (#29260) Thanks @vincentkoc.
  • -
  • Codex/Usage window: label weekly usage window as Week instead of Day. (#26267) Thanks @Sid-Qin.
  • -
  • Signal/Sync message null-handling: treat syncMessage presence (including null) as sync envelope traffic so replayed sentTranscript payloads cannot bypass loop guards after daemon restart. Landed from contributor PR #31138 by @Sid-Qin. Thanks @Sid-Qin.
  • -
  • Infra/fs-safe: sanitize directory-read failures so raw EISDIR text never leaks to messaging surfaces, with regression tests for both root-scoped and direct safe reads. Landed from contributor PR #31205 by @polooooo. Thanks @polooooo.
  • -
  • Sandbox/mkdirp boundary checks: allow directory-safe boundary validation for existing in-boundary subdirectories, preventing false cannot create directories failures in sandbox write mode. (#30610) Thanks @glitch418x.
  • -
  • Security/Compaction audit: remove the post-compaction audit injection message. (#28507) Thanks @fuller-stack-dev and @vincentkoc.
  • -
  • Web tools/RFC2544 fake-IP compatibility: allow RFC2544 benchmark range (198.18.0.0/15) for trusted web-tool fetch endpoints so proxy fake-IP networking modes do not trigger false SSRF blocks. Landed from contributor PR #31176 by @sunkinux. Thanks @sunkinux.
  • -
  • Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger.
  • -
  • Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted System: context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky.
  • -
  • Feishu/Multi-account + reply reliability: add channels.feishu.defaultAccount outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as msg_type: "file", and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.
  • -
  • Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077.
  • -
-

View full changelog

-]]>
- - -
\ No newline at end of file diff --git a/extensions/acpx/package.json b/extensions/acpx/package.json index 6c1231c41d5..ae560c8e9af 100644 --- a/extensions/acpx/package.json +++ b/extensions/acpx/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/acpx", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw ACP runtime backend via acpx", "type": "module", "dependencies": { diff --git a/extensions/bluebubbles/package.json b/extensions/bluebubbles/package.json index fcd1c8f8a38..80d314cee38 100644 --- a/extensions/bluebubbles/package.json +++ b/extensions/bluebubbles/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/bluebubbles", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw BlueBubbles channel plugin", "type": "module", "dependencies": { diff --git a/extensions/copilot-proxy/package.json b/extensions/copilot-proxy/package.json index 52ffbafe2cc..9d72a353069 100644 --- a/extensions/copilot-proxy/package.json +++ b/extensions/copilot-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/copilot-proxy", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw Copilot Proxy provider plugin", "type": "module", diff --git a/extensions/diagnostics-otel/package.json b/extensions/diagnostics-otel/package.json index 67f06348554..27dcdda7fb1 100644 --- a/extensions/diagnostics-otel/package.json +++ b/extensions/diagnostics-otel/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/diagnostics-otel", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw diagnostics OpenTelemetry exporter", "type": "module", "dependencies": { diff --git a/extensions/diffs/package.json b/extensions/diffs/package.json index 581777e2bd0..5df160b125a 100644 --- a/extensions/diffs/package.json +++ b/extensions/diffs/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/diffs", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw diff viewer plugin", "type": "module", diff --git a/extensions/discord/package.json b/extensions/discord/package.json index d37f8644626..607a0a8dd3b 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/discord", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Discord channel plugin", "type": "module", "openclaw": { diff --git a/extensions/feishu/package.json b/extensions/feishu/package.json index 41279e48111..7fcd26f67fc 100644 --- a/extensions/feishu/package.json +++ b/extensions/feishu/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/feishu", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)", "type": "module", "dependencies": { diff --git a/extensions/google-gemini-cli-auth/package.json b/extensions/google-gemini-cli-auth/package.json index 9643ee78ee6..2e5eca8365a 100644 --- a/extensions/google-gemini-cli-auth/package.json +++ b/extensions/google-gemini-cli-auth/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/google-gemini-cli-auth", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw Gemini CLI OAuth provider plugin", "type": "module", diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index 1a7a876b4ef..3fbfb30a472 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/googlechat", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw Google Chat channel plugin", "type": "module", diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index 38d4262befe..1a350de2146 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/imessage", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw iMessage channel plugin", "type": "module", diff --git a/extensions/irc/package.json b/extensions/irc/package.json index 9cbdee3a923..44040e8428b 100644 --- a/extensions/irc/package.json +++ b/extensions/irc/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/irc", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw IRC channel plugin", "type": "module", "dependencies": { diff --git a/extensions/line/package.json b/extensions/line/package.json index 1a90e0a0080..227da7e8f84 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/line", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw LINE channel plugin", "type": "module", diff --git a/extensions/llm-task/package.json b/extensions/llm-task/package.json index 537b4aa6d7f..1cdcdf7cb83 100644 --- a/extensions/llm-task/package.json +++ b/extensions/llm-task/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/llm-task", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw JSON-only LLM task plugin", "type": "module", diff --git a/extensions/lobster/package.json b/extensions/lobster/package.json index f9e0b458d5e..67495efb300 100644 --- a/extensions/lobster/package.json +++ b/extensions/lobster/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/lobster", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)", "type": "module", "dependencies": { diff --git a/extensions/matrix/CHANGELOG.md b/extensions/matrix/CHANGELOG.md index 75241a274e8..8a8bbcb87f4 100644 --- a/extensions/matrix/CHANGELOG.md +++ b/extensions/matrix/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026.3.8-beta.1 + +### Changes + +- Version alignment with core OpenClaw release numbers. + ## 2026.3.8 ### Changes diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index 1b769b65011..11fa647936a 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/matrix", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Matrix channel plugin", "type": "module", "dependencies": { diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index 4042b01101f..1b823e4d08b 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/mattermost", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Mattermost channel plugin", "type": "module", "dependencies": { diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index d7a551f4909..ce7521de44a 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/memory-core", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw core memory search plugin", "type": "module", diff --git a/extensions/memory-lancedb/package.json b/extensions/memory-lancedb/package.json index 0c282a85edc..067b32446eb 100644 --- a/extensions/memory-lancedb/package.json +++ b/extensions/memory-lancedb/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/memory-lancedb", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture", "type": "module", diff --git a/extensions/minimax-portal-auth/package.json b/extensions/minimax-portal-auth/package.json index 6e6c9fc3cb8..b588a963474 100644 --- a/extensions/minimax-portal-auth/package.json +++ b/extensions/minimax-portal-auth/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/minimax-portal-auth", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw MiniMax Portal OAuth provider plugin", "type": "module", diff --git a/extensions/msteams/CHANGELOG.md b/extensions/msteams/CHANGELOG.md index 4f23bc09499..4ca638b8f7c 100644 --- a/extensions/msteams/CHANGELOG.md +++ b/extensions/msteams/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026.3.8-beta.1 + +### Changes + +- Version alignment with core OpenClaw release numbers. + ## 2026.3.8 ### Changes diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index 0415203ff0d..c6b4106ee05 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/msteams", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Microsoft Teams channel plugin", "type": "module", "dependencies": { diff --git a/extensions/nextcloud-talk/package.json b/extensions/nextcloud-talk/package.json index 5a8193b9621..bef40590adc 100644 --- a/extensions/nextcloud-talk/package.json +++ b/extensions/nextcloud-talk/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/nextcloud-talk", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Nextcloud Talk channel plugin", "type": "module", "dependencies": { diff --git a/extensions/nostr/CHANGELOG.md b/extensions/nostr/CHANGELOG.md index b05a75d233a..7daad7c06f9 100644 --- a/extensions/nostr/CHANGELOG.md +++ b/extensions/nostr/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026.3.8-beta.1 + +### Changes + +- Version alignment with core OpenClaw release numbers. + ## 2026.3.8 ### Changes diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index 389d7b210ff..c6fdf0056d4 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/nostr", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs", "type": "module", "dependencies": { diff --git a/extensions/open-prose/package.json b/extensions/open-prose/package.json index 956472bf65c..d323b6ca9e6 100644 --- a/extensions/open-prose/package.json +++ b/extensions/open-prose/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/open-prose", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenProse VM skill pack plugin (slash command + telemetry).", "type": "module", diff --git a/extensions/signal/package.json b/extensions/signal/package.json index f51c86f6141..88fe04ac1e4 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/signal", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw Signal channel plugin", "type": "module", diff --git a/extensions/slack/package.json b/extensions/slack/package.json index a76b301f545..cf62b6ce627 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/slack", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw Slack channel plugin", "type": "module", diff --git a/extensions/synology-chat/package.json b/extensions/synology-chat/package.json index 4215e36650e..ff83998b456 100644 --- a/extensions/synology-chat/package.json +++ b/extensions/synology-chat/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/synology-chat", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "Synology Chat channel plugin for OpenClaw", "type": "module", "dependencies": { diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index 9fc1c662d46..eba1967b064 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/telegram", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw Telegram channel plugin", "type": "module", diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index 9fe4d84353a..159daa5e69d 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/tlon", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Tlon/Urbit channel plugin", "type": "module", "dependencies": { diff --git a/extensions/twitch/CHANGELOG.md b/extensions/twitch/CHANGELOG.md index ebc77095f9d..3ef565f84a9 100644 --- a/extensions/twitch/CHANGELOG.md +++ b/extensions/twitch/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026.3.8-beta.1 + +### Changes + +- Version alignment with core OpenClaw release numbers. + ## 2026.3.8 ### Changes diff --git a/extensions/twitch/package.json b/extensions/twitch/package.json index 21b95602ec7..04bdf10977b 100644 --- a/extensions/twitch/package.json +++ b/extensions/twitch/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/twitch", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Twitch channel plugin", "type": "module", "dependencies": { diff --git a/extensions/voice-call/CHANGELOG.md b/extensions/voice-call/CHANGELOG.md index 0ac97782967..6bd0d2aadeb 100644 --- a/extensions/voice-call/CHANGELOG.md +++ b/extensions/voice-call/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026.3.8-beta.1 + +### Changes + +- Version alignment with core OpenClaw release numbers. + ## 2026.3.8 ### Changes diff --git a/extensions/voice-call/package.json b/extensions/voice-call/package.json index 82bdf122a52..c01c2194cb4 100644 --- a/extensions/voice-call/package.json +++ b/extensions/voice-call/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/voice-call", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw voice-call plugin", "type": "module", "dependencies": { diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index 636805ab1bd..b3cf0a655c3 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/whatsapp", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "private": true, "description": "OpenClaw WhatsApp channel plugin", "type": "module", diff --git a/extensions/zalo/CHANGELOG.md b/extensions/zalo/CHANGELOG.md index b964ba2f7b2..647dc8c3f45 100644 --- a/extensions/zalo/CHANGELOG.md +++ b/extensions/zalo/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026.3.8-beta.1 + +### Changes + +- Version alignment with core OpenClaw release numbers. + ## 2026.3.8 ### Changes diff --git a/extensions/zalo/package.json b/extensions/zalo/package.json index 1dab87a713c..30de1e0ea03 100644 --- a/extensions/zalo/package.json +++ b/extensions/zalo/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/zalo", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Zalo channel plugin", "type": "module", "dependencies": { diff --git a/extensions/zalouser/CHANGELOG.md b/extensions/zalouser/CHANGELOG.md index ed4be6a2c9a..15ecfd974cc 100644 --- a/extensions/zalouser/CHANGELOG.md +++ b/extensions/zalouser/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2026.3.8-beta.1 + +### Changes + +- Version alignment with core OpenClaw release numbers. + ## 2026.3.8 ### Changes diff --git a/extensions/zalouser/package.json b/extensions/zalouser/package.json index 217653508db..9f352c14361 100644 --- a/extensions/zalouser/package.json +++ b/extensions/zalouser/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/zalouser", - "version": "2026.3.8", + "version": "2026.3.8-beta.1", "description": "OpenClaw Zalo Personal Account plugin via native zca-js integration", "type": "module", "dependencies": { diff --git a/src/cli/gateway-cli/run-loop.test.ts b/src/cli/gateway-cli/run-loop.test.ts index be1a6200040..9e44d67c59b 100644 --- a/src/cli/gateway-cli/run-loop.test.ts +++ b/src/cli/gateway-cli/run-loop.test.ts @@ -47,10 +47,10 @@ vi.mock("../../logging/subsystem.js", () => ({ createSubsystemLogger: () => gatewayLog, })); -function removeNewSignalListeners( - signal: NodeJS.Signals, - existing: Set<(...args: unknown[]) => void>, -) { +const LOOP_SIGNALS = ["SIGTERM", "SIGINT", "SIGUSR1"] as const; +type LoopSignal = (typeof LOOP_SIGNALS)[number]; + +function removeNewSignalListeners(signal: LoopSignal, existing: Set<(...args: unknown[]) => void>) { for (const listener of process.listeners(signal)) { const fn = listener as (...args: unknown[]) => void; if (!existing.has(fn)) { @@ -59,20 +59,42 @@ function removeNewSignalListeners( } } -async function withIsolatedSignals(run: () => Promise) { - const beforeSigterm = new Set( - process.listeners("SIGTERM") as Array<(...args: unknown[]) => void>, - ); - const beforeSigint = new Set(process.listeners("SIGINT") as Array<(...args: unknown[]) => void>); - const beforeSigusr1 = new Set( - process.listeners("SIGUSR1") as Array<(...args: unknown[]) => void>, - ); +function addedSignalListener( + signal: LoopSignal, + existing: Set<(...args: unknown[]) => void>, +): (() => void) | null { + const listeners = process.listeners(signal) as Array<(...args: unknown[]) => void>; + for (let i = listeners.length - 1; i >= 0; i -= 1) { + const listener = listeners[i]; + if (listener && !existing.has(listener)) { + return listener as () => void; + } + } + return null; +} + +async function withIsolatedSignals( + run: (helpers: { captureSignal: (signal: LoopSignal) => () => void }) => Promise, +) { + const existingListeners = Object.fromEntries( + LOOP_SIGNALS.map((signal) => [ + signal, + new Set(process.listeners(signal) as Array<(...args: unknown[]) => void>), + ]), + ) as Record void>>; + const captureSignal = (signal: LoopSignal) => { + const listener = addedSignalListener(signal, existingListeners[signal]); + if (!listener) { + throw new Error(`expected new ${signal} listener`); + } + return () => listener(); + }; try { - await run(); + await run({ captureSignal }); } finally { - removeNewSignalListeners("SIGTERM", beforeSigterm); - removeNewSignalListeners("SIGINT", beforeSigint); - removeNewSignalListeners("SIGUSR1", beforeSigusr1); + for (const signal of LOOP_SIGNALS) { + removeNewSignalListeners(signal, existingListeners[signal]); + } } } @@ -144,10 +166,11 @@ describe("runGatewayLoop", () => { it("exits 0 on SIGTERM after graceful close", async () => { vi.clearAllMocks(); - await withIsolatedSignals(async () => { + await withIsolatedSignals(async ({ captureSignal }) => { const { close, runtime, exited } = await createSignaledLoopHarness(); + const sigterm = captureSignal("SIGTERM"); - process.emit("SIGTERM"); + sigterm(); await expect(exited).resolves.toBe(0); expect(close).toHaveBeenCalledWith({ @@ -161,7 +184,7 @@ describe("runGatewayLoop", () => { it("restarts after SIGUSR1 even when drain times out, and resets lanes for the new iteration", async () => { vi.clearAllMocks(); - await withIsolatedSignals(async () => { + await withIsolatedSignals(async ({ captureSignal }) => { getActiveTaskCount.mockReturnValueOnce(2).mockReturnValueOnce(0); waitForActiveTasks.mockResolvedValueOnce({ drained: false }); @@ -171,6 +194,8 @@ describe("runGatewayLoop", () => { const closeFirst = vi.fn(async () => {}); const closeSecond = vi.fn(async () => {}); + const closeThird = vi.fn(async () => {}); + const { runtime, exited } = createRuntimeWithExitSignal(); const start = vi.fn(); let resolveFirst: (() => void) | null = null; @@ -191,24 +216,28 @@ describe("runGatewayLoop", () => { return { close: closeSecond }; }); - start.mockRejectedValueOnce(new Error("stop-loop")); + let resolveThird: (() => void) | null = null; + const startedThird = new Promise((resolve) => { + resolveThird = resolve; + }); + start.mockImplementationOnce(async () => { + resolveThird?.(); + return { close: closeThird }; + }); const { runGatewayLoop } = await import("./run-loop.js"); - const runtime = { - log: vi.fn(), - error: vi.fn(), - exit: vi.fn(), - }; - const loopPromise = runGatewayLoop({ + void runGatewayLoop({ start: start as unknown as Parameters[0]["start"], runtime: runtime as unknown as Parameters[0]["runtime"], }); await startedFirst; + const sigusr1 = captureSignal("SIGUSR1"); + const sigterm = captureSignal("SIGTERM"); expect(start).toHaveBeenCalledTimes(1); await new Promise((resolve) => setImmediate(resolve)); - process.emit("SIGUSR1"); + sigusr1(); await startedSecond; expect(start).toHaveBeenCalledTimes(2); @@ -224,9 +253,10 @@ describe("runGatewayLoop", () => { expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(1); expect(resetAllLanes).toHaveBeenCalledTimes(1); - process.emit("SIGUSR1"); + sigusr1(); - await expect(loopPromise).rejects.toThrow("stop-loop"); + await startedThird; + await new Promise((resolve) => setImmediate(resolve)); expect(closeSecond).toHaveBeenCalledWith({ reason: "gateway restarting", restartExpectedMs: 1500, @@ -235,13 +265,20 @@ describe("runGatewayLoop", () => { expect(markGatewayDraining).toHaveBeenCalledTimes(2); expect(resetAllLanes).toHaveBeenCalledTimes(2); expect(acquireGatewayLock).toHaveBeenCalledTimes(3); + + sigterm(); + await expect(exited).resolves.toBe(0); + expect(closeThird).toHaveBeenCalledWith({ + reason: "gateway stopping", + restartExpectedMs: null, + }); }); }); it("releases the lock before exiting on spawned restart", async () => { vi.clearAllMocks(); - await withIsolatedSignals(async () => { + await withIsolatedSignals(async ({ captureSignal }) => { const lockRelease = vi.fn(async () => {}); acquireGatewayLock.mockResolvedValueOnce({ release: lockRelease, @@ -255,11 +292,12 @@ describe("runGatewayLoop", () => { const exitCallOrder: string[] = []; const { runtime, exited } = await createSignaledLoopHarness(exitCallOrder); + const sigusr1 = captureSignal("SIGUSR1"); lockRelease.mockImplementation(async () => { exitCallOrder.push("lockRelease"); }); - process.emit("SIGUSR1"); + sigusr1(); await exited; expect(lockRelease).toHaveBeenCalled(); @@ -271,40 +309,45 @@ describe("runGatewayLoop", () => { it("forwards lockPort to initial and restart lock acquisitions", async () => { vi.clearAllMocks(); - await withIsolatedSignals(async () => { + await withIsolatedSignals(async ({ captureSignal }) => { const closeFirst = vi.fn(async () => {}); const closeSecond = vi.fn(async () => {}); - restartGatewayProcessWithFreshPid.mockReturnValueOnce({ mode: "disabled" }); + const closeThird = vi.fn(async () => {}); + const { runtime, exited } = createRuntimeWithExitSignal(); const start = vi .fn() .mockResolvedValueOnce({ close: closeFirst }) .mockResolvedValueOnce({ close: closeSecond }) - .mockRejectedValueOnce(new Error("stop-loop")); - const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() }; + .mockResolvedValueOnce({ close: closeThird }); const { runGatewayLoop } = await import("./run-loop.js"); - const loopPromise = runGatewayLoop({ + void runGatewayLoop({ start: start as unknown as Parameters[0]["start"], runtime: runtime as unknown as Parameters[0]["runtime"], lockPort: 18789, }); + await new Promise((resolve) => setImmediate(resolve)); + const sigusr1 = captureSignal("SIGUSR1"); + const sigterm = captureSignal("SIGTERM"); + + sigusr1(); + await new Promise((resolve) => setImmediate(resolve)); + sigusr1(); await new Promise((resolve) => setImmediate(resolve)); - process.emit("SIGUSR1"); - await new Promise((resolve) => setImmediate(resolve)); - process.emit("SIGUSR1"); - - await expect(loopPromise).rejects.toThrow("stop-loop"); expect(acquireGatewayLock).toHaveBeenNthCalledWith(1, { port: 18789 }); expect(acquireGatewayLock).toHaveBeenNthCalledWith(2, { port: 18789 }); expect(acquireGatewayLock).toHaveBeenNthCalledWith(3, { port: 18789 }); + + sigterm(); + await expect(exited).resolves.toBe(0); }); }); it("exits when lock reacquire fails during in-process restart fallback", async () => { vi.clearAllMocks(); - await withIsolatedSignals(async () => { + await withIsolatedSignals(async ({ captureSignal }) => { const lockRelease = vi.fn(async () => {}); acquireGatewayLock .mockResolvedValueOnce({ @@ -317,7 +360,8 @@ describe("runGatewayLoop", () => { }); const { start, exited } = await createSignaledLoopHarness(); - process.emit("SIGUSR1"); + const sigusr1 = captureSignal("SIGUSR1"); + sigusr1(); await expect(exited).resolves.toBe(1); expect(acquireGatewayLock).toHaveBeenCalledTimes(2); diff --git a/src/cli/update-cli/restart-helper.test.ts b/src/cli/update-cli/restart-helper.test.ts index 1e15556d89e..c8b59d69afa 100644 --- a/src/cli/update-cli/restart-helper.test.ts +++ b/src/cli/update-cli/restart-helper.test.ts @@ -98,7 +98,8 @@ describe("restart-helper", () => { expect(scriptPath.endsWith(".sh")).toBe(true); expect(content).toContain("#!/bin/sh"); expect(content).toContain("launchctl kickstart -k 'gui/501/ai.openclaw.gateway'"); - // Should fall back to bootstrap when kickstart fails (service deregistered after bootout) + // Should clear disabled state and fall back to bootstrap when kickstart fails. + expect(content).toContain("launchctl enable 'gui/501/ai.openclaw.gateway'"); expect(content).toContain("launchctl bootstrap 'gui/501'"); expect(content).toContain('rm -f "$0"'); await cleanupScript(scriptPath); diff --git a/src/cli/update-cli/restart-helper.ts b/src/cli/update-cli/restart-helper.ts index 02ac29d03bb..c27f25cdc49 100644 --- a/src/cli/update-cli/restart-helper.ts +++ b/src/cli/update-cli/restart-helper.ts @@ -95,8 +95,10 @@ rm -f "$0" # Wait briefly to ensure file locks are released after update. sleep 1 # Try kickstart first (works when the service is still registered). -# If it fails (e.g. after bootout), re-register via bootstrap then kickstart. +# If it fails (e.g. after bootout), clear any persisted disabled state, +# then re-register via bootstrap and kickstart. if ! launchctl kickstart -k 'gui/${uid}/${escaped}' 2>/dev/null; then + launchctl enable 'gui/${uid}/${escaped}' 2>/dev/null launchctl bootstrap 'gui/${uid}' '${escapedPlistPath}' 2>/dev/null launchctl kickstart -k 'gui/${uid}/${escaped}' 2>/dev/null || true fi diff --git a/src/infra/exec-wrapper-resolution.test.ts b/src/infra/exec-wrapper-resolution.test.ts new file mode 100644 index 00000000000..b271c97ee8d --- /dev/null +++ b/src/infra/exec-wrapper-resolution.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from "vitest"; +import { normalizeExecutableToken } from "./exec-wrapper-resolution.js"; + +describe("normalizeExecutableToken", () => { + test("strips common windows executable suffixes", () => { + expect(normalizeExecutableToken("bun.cmd")).toBe("bun"); + expect(normalizeExecutableToken("deno.bat")).toBe("deno"); + expect(normalizeExecutableToken("pwsh.com")).toBe("pwsh"); + expect(normalizeExecutableToken("cmd.exe")).toBe("cmd"); + }); + + test("normalizes path-qualified windows shims", () => { + expect(normalizeExecutableToken("C:\\tools\\bun.cmd")).toBe("bun"); + expect(normalizeExecutableToken("/tmp/deno.exe")).toBe("deno"); + }); +}); diff --git a/src/infra/exec-wrapper-resolution.ts b/src/infra/exec-wrapper-resolution.ts index 006a0a65612..0cb423a11b3 100644 --- a/src/infra/exec-wrapper-resolution.ts +++ b/src/infra/exec-wrapper-resolution.ts @@ -7,7 +7,7 @@ import { export const MAX_DISPATCH_WRAPPER_DEPTH = 4; -const WINDOWS_EXE_SUFFIX = ".exe"; +const WINDOWS_EXECUTABLE_SUFFIXES = [".exe", ".cmd", ".bat", ".com"] as const; const POSIX_SHELL_WRAPPER_NAMES = ["ash", "bash", "dash", "fish", "ksh", "sh", "zsh"] as const; const WINDOWS_CMD_WRAPPER_NAMES = ["cmd"] as const; @@ -31,13 +31,18 @@ function withWindowsExeAliases(names: readonly string[]): string[] { const expanded = new Set(); for (const name of names) { expanded.add(name); - expanded.add(`${name}${WINDOWS_EXE_SUFFIX}`); + expanded.add(`${name}.exe`); } return Array.from(expanded); } -function stripWindowsExeSuffix(value: string): string { - return value.endsWith(WINDOWS_EXE_SUFFIX) ? value.slice(0, -WINDOWS_EXE_SUFFIX.length) : value; +function stripWindowsExecutableSuffix(value: string): string { + for (const suffix of WINDOWS_EXECUTABLE_SUFFIXES) { + if (value.endsWith(suffix)) { + return value.slice(0, -suffix.length); + } + } + return value; } export const POSIX_SHELL_WRAPPERS = new Set(POSIX_SHELL_WRAPPER_NAMES); @@ -115,7 +120,7 @@ export function basenameLower(token: string): string { } export function normalizeExecutableToken(token: string): string { - return stripWindowsExeSuffix(basenameLower(token)); + return stripWindowsExecutableSuffix(basenameLower(token)); } export function isDispatchWrapperExecutable(token: string): boolean { @@ -132,7 +137,7 @@ function normalizeRawCommand(rawCommand?: string | null): string | null { } function findShellWrapperSpec(baseExecutable: string): ShellWrapperSpec | null { - const canonicalBase = stripWindowsExeSuffix(baseExecutable); + const canonicalBase = stripWindowsExecutableSuffix(baseExecutable); for (const spec of SHELL_WRAPPER_SPECS) { if (spec.names.has(canonicalBase)) { return spec; diff --git a/src/node-host/invoke-system-run.test.ts b/src/node-host/invoke-system-run.test.ts index 2d78ec46500..dfbcc6b028a 100644 --- a/src/node-host/invoke-system-run.test.ts +++ b/src/node-host/invoke-system-run.test.ts @@ -229,9 +229,16 @@ describe("handleSystemRunInvoke mac app exec host routing", () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), `openclaw-${params.runtime}-path-`)); const binDir = path.join(tmp, "bin"); fs.mkdirSync(binDir, { recursive: true }); - const runtimePath = path.join(binDir, params.runtime); - fs.writeFileSync(runtimePath, "#!/bin/sh\nexit 0\n", { mode: 0o755 }); - fs.chmodSync(runtimePath, 0o755); + const runtimePath = + process.platform === "win32" + ? path.join(binDir, `${params.runtime}.cmd`) + : path.join(binDir, params.runtime); + const runtimeBody = + process.platform === "win32" ? "@echo off\r\nexit /b 0\r\n" : "#!/bin/sh\nexit 0\n"; + fs.writeFileSync(runtimePath, runtimeBody, { mode: 0o755 }); + if (process.platform !== "win32") { + fs.chmodSync(runtimePath, 0o755); + } const oldPath = process.env.PATH; process.env.PATH = `${binDir}${path.delimiter}${oldPath ?? ""}`; try {