diff --git a/.agents/skills/blacksmith-testbox/SKILL.md b/.agents/skills/blacksmith-testbox/SKILL.md index 7df209fd010..1827bcc475e 100644 --- a/.agents/skills/blacksmith-testbox/SKILL.md +++ b/.agents/skills/blacksmith-testbox/SKILL.md @@ -10,6 +10,9 @@ description: Run Blacksmith Testbox for CI-parity checks, secrets, hosted servic Use Testbox when you need remote CI parity, injected secrets, hosted services, or an OS/runtime image that your local machine cannot provide cheaply. +For OpenClaw, Crabbox is a supported alternative when Blacksmith is unavailable +or owned cloud capacity is preferable. + Do not default to Testbox for every local test/build loop. If the repo has documented local commands for normal iteration, use those first so you keep warm caches, local build state, and fast feedback. diff --git a/.agents/skills/crabbox/SKILL.md b/.agents/skills/crabbox/SKILL.md new file mode 100644 index 00000000000..78d6d23c0b9 --- /dev/null +++ b/.agents/skills/crabbox/SKILL.md @@ -0,0 +1,81 @@ +--- +name: crabbox +description: Use Crabbox for OpenClaw remote Linux validation, warmed reusable boxes, GitHub Actions hydration, sync timing, logs, results, caches, and lease cleanup. +--- + +# Crabbox + +Use Crabbox when OpenClaw needs remote Linux proof on owned capacity, a large +runner class, reusable warm state, or a Blacksmith alternative. + +## Before Running + +- Run from the repo root. Crabbox sync mirrors the current checkout. +- Prefer local targeted tests for tight edit loops. +- Prefer Blacksmith Testbox when the task explicitly asks for Blacksmith or a + Blacksmith-specific CI comparison. +- Use Crabbox for broad OpenClaw gates when owned AWS/Hetzner capacity is the + right remote lane. +- Check `.crabbox.yaml` for repo defaults before adding flags. +- Install with `brew install openclaw/tap/crabbox`; auth is required before use: + `printf '%s' "$CRABBOX_COORDINATOR_TOKEN" | crabbox login --url https://crabbox-coordinator.steipete.workers.dev --provider aws --token-stdin`. +- On macOS the user config is `~/Library/Application Support/crabbox/config.yaml`; + it must include `broker.url`, `broker.token`, and usually `provider: aws`. + +## OpenClaw Flow + +Warm a reusable box: + +```sh +pnpm crabbox:warmup -- --idle-timeout 90m +``` + +Hydrate it through the repository workflow: + +```sh +pnpm crabbox:hydrate -- --id +``` + +Run broad proof: + +```sh +pnpm crabbox:run -- --id --shell "OPENCLAW_TESTBOX=1 pnpm check:changed" +pnpm crabbox:run -- --id --shell "corepack enable && pnpm install --frozen-lockfile && pnpm test" +``` + +Stop boxes you created before handoff: + +```sh +pnpm crabbox:stop -- +``` + +## Useful Commands + +```sh +crabbox status --id --wait +crabbox inspect --id --json +crabbox sync-plan +crabbox history --lease +crabbox logs +crabbox results +crabbox cache stats --id +crabbox ssh --id +``` + +Use `--debug` on `run` when measuring sync timing. + +## Hydration Boundary + +`.github/workflows/crabbox-hydrate.yml` is repo-specific on purpose. It owns +OpenClaw checkout, setup-node, pnpm setup, provider env hydration, ready marker, +and keepalive. Crabbox owns runner registration, workflow dispatch, SSH sync, +command execution, logs/results, local lease claims, and idle cleanup. + +Do not add OpenClaw-specific setup to Crabbox. Put repo setup in the hydration +workflow and generic lease/sync behavior in Crabbox. + +## Cleanup + +Crabbox has coordinator-owned idle expiry and local lease claims, so OpenClaw +does not need a custom ledger. Default idle timeout is 30 minutes unless config +or flags set a different value. Still stop boxes you created when done. diff --git a/.crabbox.yaml b/.crabbox.yaml index e31d8f86bc2..ab0046d8ce9 100644 --- a/.crabbox.yaml +++ b/.crabbox.yaml @@ -9,6 +9,7 @@ capacity: - eu-west-1 actions: workflow: .github/workflows/crabbox-hydrate.yml + job: hydrate ref: main runnerLabels: - crabbox @@ -26,14 +27,8 @@ sync: baseRef: main exclude: - .artifacts - - .cache - .codex - .DS_Store - - .turbo - - coverage - - dist - - dist-runtime - - node_modules - playwright-report - test-results env: diff --git a/.github/workflows/crabbox-hydrate.yml b/.github/workflows/crabbox-hydrate.yml index b26f599d246..35da86a752f 100644 --- a/.github/workflows/crabbox-hydrate.yml +++ b/.github/workflows/crabbox-hydrate.yml @@ -7,10 +7,19 @@ on: description: "Crabbox lease ID" required: true type: string + ref: + description: "Git ref to hydrate" + required: false + type: string crabbox_runner_label: description: "Dynamic Crabbox runner label" required: true type: string + crabbox_job: + description: "Hydration job identifier expected by Crabbox" + required: false + default: "hydrate" + type: string crabbox_keep_alive_minutes: description: "Minutes to keep the hydrated job alive" required: false @@ -30,6 +39,8 @@ jobs: timeout-minutes: 120 steps: - uses: actions/checkout@v6 + with: + ref: ${{ inputs.ref || github.ref }} - name: Setup Node environment uses: ./.github/actions/setup-node-env @@ -81,12 +92,37 @@ jobs: shell: bash run: | set -euo pipefail + job="${{ inputs.crabbox_job }}" + if [ -z "$job" ]; then job=hydrate; fi mkdir -p "$HOME/.crabbox/actions" state="$HOME/.crabbox/actions/${{ inputs.crabbox_id }}.env" + env_file="$HOME/.crabbox/actions/${{ inputs.crabbox_id }}.env.sh" + services_file="$HOME/.crabbox/actions/${{ inputs.crabbox_id }}.services" + write_export() { + key="$1" + value="${!key-}" + if [ -n "$value" ]; then + printf 'export %s=%q\n' "$key" "$value" + fi + } + { + for key in CI GITHUB_ACTIONS GITHUB_WORKSPACE GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_RUN_ATTEMPT GITHUB_REF GITHUB_REF_NAME GITHUB_SHA GITHUB_EVENT_NAME GITHUB_ACTOR RUNNER_OS RUNNER_ARCH RUNNER_TEMP RUNNER_TOOL_CACHE; do + write_export "$key" + done + } > "${env_file}.tmp" + mv "${env_file}.tmp" "$env_file" + { + echo "# Docker containers visible from the hydrated runner" + docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' 2>/dev/null || true + } > "${services_file}.tmp" + mv "${services_file}.tmp" "$services_file" tmp="${state}.tmp" { echo "WORKSPACE=${GITHUB_WORKSPACE}" echo "RUN_ID=${GITHUB_RUN_ID}" + echo "JOB=${job}" + echo "ENV_FILE=${env_file}" + echo "SERVICES_FILE=${services_file}" echo "READY_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)" } > "$tmp" mv "$tmp" "$state" diff --git a/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml b/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml index 9c08237f1e3..c0a43f227b9 100644 --- a/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml +++ b/.github/workflows/openclaw-live-and-e2e-checks-reusable.yml @@ -38,6 +38,16 @@ on: required: false default: openclaw@latest type: string + published_upgrade_survivor_baselines: + description: Optional exact baseline list for published-upgrade-survivor lane expansion + required: false + default: "" + type: string + published_upgrade_survivor_scenarios: + description: Optional scenario list for published-upgrade-survivor lane expansion + required: false + default: "" + type: string package_artifact_name: description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref required: false @@ -123,6 +133,16 @@ on: required: false default: openclaw@latest type: string + published_upgrade_survivor_baselines: + description: Optional exact baseline list for published-upgrade-survivor lane expansion + required: false + default: "" + type: string + published_upgrade_survivor_scenarios: + description: Optional scenario list for published-upgrade-survivor lane expansion + required: false + default: "" + type: string package_artifact_name: description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref required: false @@ -695,6 +715,8 @@ jobs: OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }} OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }} + OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }} + OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }} OPENCLAW_SKIP_DOCKER_BUILD: "1" INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }} DOCKER_E2E_CHUNK: ${{ matrix.chunk_id }} @@ -929,6 +951,8 @@ jobs: OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }} OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }} + OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }} + OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }} OPENCLAW_SKIP_DOCKER_BUILD: "1" INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }} DOCKER_E2E_LANES: ${{ matrix.group.docker_lanes }} diff --git a/.github/workflows/package-acceptance.yml b/.github/workflows/package-acceptance.yml index 41ed7ac4b8d..2c8e6878930 100644 --- a/.github/workflows/package-acceptance.yml +++ b/.github/workflows/package-acceptance.yml @@ -69,6 +69,16 @@ on: required: false default: openclaw@latest type: string + published_upgrade_survivor_baselines: + description: Optional baseline list for published-upgrade-survivor; use release-history for last 6 plus key legacy releases + required: false + default: "" + type: string + published_upgrade_survivor_scenarios: + description: Optional scenario list for published-upgrade-survivor; use reported-issues for known upgrade failure shapes + required: false + default: "" + type: string telegram_mode: description: Optional Telegram QA lane for the resolved package candidate required: true @@ -139,6 +149,16 @@ on: required: false default: openclaw@latest type: string + published_upgrade_survivor_baselines: + description: Optional baseline list for published-upgrade-survivor; use release-history for last 6 plus key legacy releases + required: false + default: "" + type: string + published_upgrade_survivor_scenarios: + description: Optional scenario list for published-upgrade-survivor; use reported-issues for known upgrade failure shapes + required: false + default: "" + type: string telegram_mode: description: Optional Telegram QA lane for the resolved package candidate required: false @@ -275,6 +295,8 @@ jobs: package_source_sha: ${{ steps.resolve.outputs.package_source_sha }} package_sha256: ${{ steps.resolve.outputs.sha256 }} package_version: ${{ steps.resolve.outputs.package_version }} + published_upgrade_survivor_baselines: ${{ steps.upgrade_survivor_baselines.outputs.baselines }} + published_upgrade_survivor_scenarios: ${{ inputs.published_upgrade_survivor_scenarios }} telegram_enabled: ${{ steps.profile.outputs.telegram_enabled }} telegram_mode: ${{ steps.profile.outputs.telegram_mode }} steps: @@ -405,6 +427,44 @@ jobs: echo "package_artifact_name=${PACKAGE_ARTIFACT_NAME}" } >> "$GITHUB_OUTPUT" + - name: Resolve published upgrade survivor baselines + id: upgrade_survivor_baselines + env: + FALLBACK_BASELINE: ${{ inputs.published_upgrade_survivor_baseline }} + REQUESTED_BASELINES: ${{ inputs.published_upgrade_survivor_baselines }} + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + if [[ -z "${REQUESTED_BASELINES// }" ]]; then + echo "baselines=" >> "$GITHUB_OUTPUT" + exit 0 + fi + releases_json="" + npm_versions_json="" + if [[ "$REQUESTED_BASELINES" == *"release-history"* ]]; then + releases_json=".artifacts/package-candidate-input/openclaw-releases.json" + npm_versions_json=".artifacts/package-candidate-input/openclaw-npm-versions.json" + mkdir -p "$(dirname "$releases_json")" + gh release list --repo "$GITHUB_REPOSITORY" --limit 100 --json tagName,publishedAt,isPrerelease > "$releases_json" + npm view openclaw versions --json > "$npm_versions_json" + fi + args=( + --requested "$REQUESTED_BASELINES" + --fallback "$FALLBACK_BASELINE" + --github-output "$GITHUB_OUTPUT" + ) + if [[ -n "$releases_json" ]]; then + args+=( + --releases-json "$releases_json" + --npm-versions-json "$npm_versions_json" + --history-count 6 + --include-version 2026.4.23 + --pre-date 2026-03-15T00:00:00Z + ) + fi + node scripts/resolve-upgrade-survivor-baselines.mjs "${args[@]}" >/dev/null + - name: Upload package-under-test artifact uses: actions/upload-artifact@v7 with: @@ -424,6 +484,8 @@ jobs: SUITE_PROFILE: ${{ inputs.suite_profile }} WORKFLOW_REF: ${{ inputs.workflow_ref }} PUBLISHED_UPGRADE_SURVIVOR_BASELINE: ${{ inputs.published_upgrade_survivor_baseline }} + PUBLISHED_UPGRADE_SURVIVOR_BASELINES: ${{ steps.upgrade_survivor_baselines.outputs.baselines }} + PUBLISHED_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }} shell: bash run: | { @@ -438,6 +500,8 @@ jobs: echo "- SHA-256: \`${PACKAGE_SHA256}\`" echo "- Profile: \`${SUITE_PROFILE}\`" echo "- Published upgrade survivor baseline: \`${PUBLISHED_UPGRADE_SURVIVOR_BASELINE}\`" + echo "- Published upgrade survivor baselines: \`${PUBLISHED_UPGRADE_SURVIVOR_BASELINES}\`" + echo "- Published upgrade survivor scenarios: \`${PUBLISHED_UPGRADE_SURVIVOR_SCENARIOS}\`" } >> "$GITHUB_STEP_SUMMARY" docker_acceptance: @@ -451,6 +515,8 @@ jobs: include_openwebui: ${{ needs.resolve_package.outputs.include_openwebui == 'true' }} docker_lanes: ${{ needs.resolve_package.outputs.docker_lanes }} published_upgrade_survivor_baseline: ${{ inputs.published_upgrade_survivor_baseline }} + published_upgrade_survivor_baselines: ${{ needs.resolve_package.outputs.published_upgrade_survivor_baselines }} + published_upgrade_survivor_scenarios: ${{ needs.resolve_package.outputs.published_upgrade_survivor_scenarios }} package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }} include_live_suites: ${{ needs.resolve_package.outputs.include_live_suites == 'true' }} live_models_only: false diff --git a/.gitignore b/.gitignore index b3a56ddb49b..a882138473d 100644 --- a/.gitignore +++ b/.gitignore @@ -104,6 +104,8 @@ USER.md .agents/skills/* !.agents/skills/blacksmith-testbox/ !.agents/skills/blacksmith-testbox/** +!.agents/skills/crabbox/ +!.agents/skills/crabbox/** !.agents/skills/gitcrawl/ !.agents/skills/gitcrawl/** !.agents/skills/openclaw-ghsa-maintainer/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0702cc87b89..f1ee6403bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ Docs: https://docs.openclaw.ai ### Changes +- Agents/workspace: add `agents.defaults.skipOptionalBootstrapFiles` for skipping selected optional workspace files during bootstrap without disabling required workspace setup. (#62110) Thanks @mainstay22. +- Plugins/CLI: add first-class `git:` plugin installs with ref checkout, commit metadata, normal scanner/staging, and `plugins update` support for recorded git sources. Thanks @badlogic. +- Voice Call/Google Meet: add Twilio Meet join phase logs around pre-connect DTMF, realtime stream setup, and initial greeting handoff for easier live-call debugging. Thanks @donkeykong91 and @PfanP. +- macOS app: move recent session context rows into a Context submenu while keeping usage and cost details root-level, so the menu bar companion stays compact with many active sessions. Thanks @guti. +- Gateway/SDK: add SDK-facing tools.invoke RPC with shared HTTP policy, typed approval/refusal results, and SDK helper support. Refs #74705. Thanks @BunsDev and @ai-hpc. - Messages/docs: clarify that `BodyForAgent` is the primary inbound model text while `Body` is the legacy envelope fallback, and add Signal coverage so channel hardening patches target the real prompt path. Refs #66198. Thanks @defonota3box. - Control UI/Usage: add UTC quarter-hour token buckets for the Usage Mosaic and reuse them for hour filtering, keeping the legacy session-span fallback for older summaries. (#74337) Thanks @konanok. - BlueBubbles: add opt-in `channels.bluebubbles.replyContextApiFallback` that fetches the original message from the BlueBubbles HTTP API when the in-memory reply-context cache misses (multi-instance deployments sharing one BB account, post-restart, after long-lived TTL/LRU eviction). Off by default; channel-level setting propagates to accounts that omit the flag through `mergeAccountConfig`; routed through the typed `BlueBubblesClient` so every fetch is SSRF-guarded by the same three-mode policy as every other BB client request; reply-id shape is validated and part-index prefixes (`p:0/`) are stripped before the request; concurrent webhooks for the same `replyToId` coalesce into one fetch and successful responses populate the reply cache for subsequent hits. Also promotes BlueBubbles attachment download failures from verbose to runtime error so silently-dropped inbound images are visible at default log level, and extends `sanitizeForLog` to redact `?password=…`/`?token=…` query params and `Authorization:` headers before they reach the log sink (CWE-532). (#71820) Thanks @coletebou and @zqchris. @@ -14,11 +19,32 @@ Docs: https://docs.openclaw.ai ### Fixes +- Discord/voice: lengthen the default voice join Ready wait, add configurable `voice.connectTimeoutMs`/`voice.reconnectGraceMs`, and warn before destroying unrecovered disconnected sessions so slow Discord voice handshakes and reconnects no longer fail silently. Fixes #63098; refs #39825 and #65039. Thanks @darealgege, @kzicherman, and @ayochim. +- Gateway/health: refresh cached health RPC snapshots when channel runtime state diverges, so Discord and other channel status reads no longer report stale running or connected values until the cache TTL expires. (#75423) Thanks @clawsweeper. +- Discord/voice: merge configured media-understanding providers such as Deepgram into partial active provider registries, so follow-up voice turns keep transcribing after another media plugin is already active. Fixes #65687. Thanks @OneMintJulep. +- WhatsApp: stage `qrcode` through root mirrored runtime dependencies so packaged QR pairing can render from staged plugin-runtime-deps installs. Fixes #75394. Thanks @FelipeX2001. +- Discord/voice: apply per-channel Discord `systemPrompt` overrides to voice transcript turns by forwarding the trusted channel prompt through the voice agent run. Fixes #47095. Thanks @qearlyao. +- Discord/voice: run voice-channel turns under a voice-output policy that hides the agent `tts` tool and asks for spoken reply text, so `/vc join` sessions synthesize and play agent replies instead of ending with `NO_REPLY`. Fixes #61536. Thanks @aounakram. +- Plugins/runtime-deps: include packaged OpenClaw identity in bundled plugin loader cache keys, so same-path package upgrades stop reusing stale versioned runtime-deps mirrors. Fixes #75045. Thanks @sahilsatralkar. +- Plugin SDK: restore reply-prefix and reply-pipeline helpers on the deprecated root/compat SDK surface so external plugins still using `openclaw/plugin-sdk` do not fail message dispatch after update. Fixes #75171. Thanks @zhangxiliang. +- Plugins/runtime-deps: prune inactive same-package versioned runtime-deps roots after bundled dependency repair, so upgrades do not leave old `openclaw--` package caches behind after doctor runs. Thanks @vincentkoc. +- Plugins/runtime-deps: prune legacy version-scoped plugin runtime-deps roots during bundled dependency repair and cover the path in Package Acceptance's upgrade-survivor matrix, so upgrades from 2026.4.x no longer leave stale per-plugin runtime trees after doctor runs. Thanks @vincentkoc. +- Plugins/runtime-deps: keep Gateway startup plugin imports and runtime plugin fallback loads verify-only after startup/config repair planning, so packaged installs no longer spawn package-manager repair from hot paths after readiness. Refs #75283 and #75069. Thanks @brokemac79 and @xiaohuaxi. +- Plugins/runtime-deps: treat package.json runtime-deps manifests as supersets when generated materialization metadata is absent, so bundled plugin activation stops restaging already-installed dependency subsets on every activation. Fixes #75429. (#75431) Thanks @loyur. +- Voice Call/realtime: add default-off fast memory/session context for `openclaw_agent_consult`, giving live calls a bounded answer-or-miss path before the full agent consult. Fixes #71849. Thanks @amzzzzzzz. +- Google Meet: interrupt Realtime provider output when local barge-in clears playback, so command-pair audio stops model speech instead of only restarting Chrome playback. Fixes #73850. (#73834) Thanks @shhtheonlyperson. +- Gateway/config: cap oversized plugin-owned schemas in the full `config.schema` response so large installed plugin sets cannot balloon Gateway RSS or crash schema clients. Thanks @vincentkoc. +- Plugins/update: skip ClawHub and marketplace plugin updates when the bundled version is newer than the recorded installed version, so `openclaw update` no longer overwrites working bundled plugins with older external packages. Fixes #75447. Thanks @amknight. +- Gateway/sessions: use bounded tail reads for sessions-list transcript usage fallbacks and cap bulk title/last-message hydration, keeping large session stores responsive when rows request derived previews. Thanks @vincentkoc. +- Gateway/chat: bound chat-history transcript reads to the requested display window so large session logs no longer OOM the Gateway when clients ask for a small history page. Thanks @vincentkoc. - Voice Call/Twilio: honor stored pre-connect TwiML before realtime webhook shortcuts and reject DTMF sequences outside conversation mode, so Meet PIN entry cannot be skipped or silently dropped. Thanks @donkeykong91 and @PfanP. +- Docs/sandboxing: clarify that sandbox setup scripts (`sandbox-setup.sh`, `sandbox-common-setup.sh`, `sandbox-browser-setup.sh`) are only available from a source checkout, and add inline `docker build` commands for npm-installed users so sandbox image setup works without cloning the repo. Fixes #75485. Thanks @amknight. - Google Meet/Voice Call: play Twilio Meet DTMF before opening the realtime media stream and carry the intro as the initial Voice Call message, so the greeting is generated after Meet admits the phone participant instead of racing a live-call TwiML update. Thanks @donkeykong91 and @PfanP. -- Google Meet/Voice Call: make Twilio setup preflight honor explicit `--transport twilio` and fail local/private Voice Call webhook URLs before joins. Thanks @donkeykong91 and @PfanP. +- Google Meet/Voice Call: make Twilio setup preflight honor explicit `--transport twilio` and fail local/private Voice Call webhook URLs, including IPv6 loopback and unique-local forms, before joins. Thanks @donkeykong91 and @PfanP. - Voice Call/Twilio: retry transient 21220 live-call TwiML updates and catch answered-path initial-greeting failures, so a fast answered callback no longer crashes the Gateway or drops the Twilio greeting/listen transition. (#74606) Thanks @Sivan22. +- CLI/startup: preserve `OPENCLAW_HIDE_BANNER` banner suppression for route-first startup callers that rely on the default process environment while keeping read-only status/channel paths from repairing bundled plugin runtime dependencies. Refs #75183. - Voice Call/Twilio: register accepted media streams immediately but wait for realtime transcription readiness before speaking the initial greeting, so reconnect grace handling stays live while OpenAI STT startup is no longer starved by TTS. Fixes #75197. (#75257) Thanks @donkeykong91 and @PfanP. +- Voice Call CLI: run gateway-delegated `voicecall continue` through operation-id polling and protocol-shaped errors, so long conversational turns keep their transcript result without blocking a single Gateway RPC. (#75459) Thanks @serrurco and @DougButdorf. - Voice Call CLI: delegate operational `voicecall` commands to the running Gateway runtime and skip webhook startup during CLI-only plugin loading, preventing webhook port conflicts and `setup --json` hangs. Fixes #72345. Thanks @serrurco and @DougButdorf. - Agents/pi-embedded-runner: extract the `abortable` provider-call wrapper from `runEmbeddedAttempt` to module scope so its promise handlers no longer close over the run lexical context, releasing transcripts, tool buffers, and subscription callbacks when a provider call hangs past abort. (#74182) Thanks @cjboy007. - Docker: restore `python3` in the gateway runtime image after the slim-runtime switch. Fixes #75041. @@ -33,12 +59,19 @@ Docs: https://docs.openclaw.ai - Security/config-audit: redact CLI argv and execArgv secrets before persisting config audit records, covering write, observe, and recovery paths. Fixes #60826. Thanks @koshaji. - Gateway/models: keep default and configured model-list views responsive when provider catalog discovery stalls, without hiding real catalog load failures, while `--all` still waits for the exact full catalog. Fixes #75297; refs #74404. Thanks @lisandromachado and @najef1979-code. - Plugins/runtime-deps: accept already materialized package-level runtime-deps supersets as converged, so later lazy plugin activation no longer prunes and relaunches `pnpm install` after gateway startup pre-staging, reducing event-loop pressure from repeated runtime-deps repair on packaged installs. Fixes #75283; refs #75297 and #72338. Thanks @brokemac79, @lisandromachado, and @midhunmonachan. +- Plugins/runtime-deps: remove OpenClaw-owned legacy runtime-deps symlinks before replacing staged bundled plugin dependencies, so updates can recover from older symlinked installs instead of failing the symlink safety guard. Thanks @goldmar. - Discord: retry queued REST 429s against learned bucket/global cooldowns and reacquire fresh voice upload URLs after CDN upload rate limits, so outbound sends recover without reusing stale single-use upload URLs. Thanks @discord. - TTS/providers: keep bundled speech-provider compat fallback available when plugins are globally disabled, so cold gateway and CLI startup can still resolve fallback speech providers instead of leaving explicit TTS provider selection with no registered providers. Refs #75265. Thanks @sliekens. - Discord: collapse repeated native slash-command deploy rate-limit startup logs into one non-fatal warning while keeping per-request REST timing in verbose output. Thanks @discord. - Discord: report native slash-command deploy aborts as REST timeouts with method, path, timeout budget, and observed duration, so startup logs explain slow Discord API calls instead of showing a generic aborted operation. Thanks @discord. - Security/logging: redact payment credential field names such as card number, CVC/CVV, shared payment token, and payment credential across default log and tool-payload redaction patterns so wallet-style MCP tools do not expose raw payment credentials in UI events or transcripts. Thanks @stainlu. - Providers/OpenAI Codex: preserve existing wrapped Codex streams during OpenAI attribution so PI OAuth bearer injection reaches ChatGPT/Codex Responses, and strip native Codex-only unsupported payload fields without touching custom compatible endpoints. (#75111) Thanks @keshavbotagent. +- Plugins/runtime-deps: materialize newly required bundled plugin packages after local `openclaw onboard` and `openclaw configure` config writes, while keeping remote setup read-only, so first Gateway startup no longer discovers missing channel/provider deps after setup claimed success. Fixes #75309; refs #75069. Thanks @scottgl9 and @xiaohuaxi. +- Plugins/runtime-deps: expire stale legacy install locks whose live PID cannot be tied to the current process incarnation, so Docker PID reuse no longer leaves bundled dependency repair stuck behind old `.openclaw-runtime-deps.lock` directories. Fixes #74948; refs #74950 and #74346. Thanks @dchekmarev. +- Plugins/runtime-deps: recover interrupted bundled runtime-dependency installs whose package sentinels exist but generated materialization is incomplete, forcing npm/pnpm repair in Gateway startup, doctor, and lazy plugin loads instead of leaving channels crash-looping on missing packages. Fixes #75309; refs #75310, #75296, and #75304. Thanks @scottgl9. +- Plugins/runtime-deps: treat no-main and export-map package sentinels without reachable entry files as incomplete, so Gateway startup, doctor, and lazy plugin loads repair interrupted bundled dependency installs instead of accepting package.json-only partial installs. Fixes #75309; refs #75183. Thanks @shakkernerd. +- Plugins/runtime-deps: keep runtime inspection and channel maintenance commands from downloading bundled plugin dependencies, route explicit repairs through `openclaw plugins deps --repair`, and still allow Gateway/DO paths to repair missing deps before import. Refs #75069. Thanks @xiaohuaxi. +- Updates: force non-deferred, no-cooldown update restarts after package-manager updates requested through the live Gateway control plane and fail release validation on post-swap stale chunk import crashes, so Telegram/Discord imports do not stay pointed at removed dist files. Fixes #75206. Thanks @xonaman and @faux123. - Agents/tool-result guard: use the resolved runtime context token budget for non-context-engine tool-result overflow checks, so long tool-heavy sessions no longer compact early when `contextTokens` is larger than native `contextWindow`. Fixes #74917. Thanks @kAIborg24. - Gateway/systemd: exit with sysexits 78 for supervised lock and `EADDRINUSE` conflicts so `RestartPreventExitStatus=78` stops `Restart=always` restart loops instead of repeatedly reloading plugins against an occupied port. Fixes #75115. Thanks @yhyatt. - Agents/runtime: skip blank visible user prompts at the embedded-runner boundary before provider submission while still allowing internal runtime-only turns and media-only prompts, so Telegram/group sessions no longer leak raw empty-input provider errors when replay history exists. Fixes #74137. Thanks @yelog, @Gracker, and @nhaener. @@ -70,6 +103,8 @@ Docs: https://docs.openclaw.ai - Plugins/runtime-deps: hash the OS-canonical `packageRoot` via `fs.realpathSync.native` (with `path.resolve` fallback) when computing the bundled runtime-deps stage key, so loader and channel `bundled-root` callers no longer derive divergent stage directories under `~/.openclaw/plugin-runtime-deps/openclaw--/` and bundled channels stop failing with `ENOENT` on shared dist chunks under Windows npm symlinks, junctions, or PM2 multi-instance worker layouts. Fixes #74963. (#75048) Thanks @openperf and @vincentkoc. - fix(logging): add redaction patterns for Tencent Cloud, Alibaba Cloud, HuggingFace and Replicate API keys (#58162). Thanks @gavyngong - Pairing: surface unexpected allowlist filesystem stat errors instead of treating the allowlist as missing, so permission and I/O failures are visible during pairing authorization checks. (#63324) Thanks @franciscomaestre. +- macOS app: reserve layout space for exec approval command details so the allow dialog no longer overlaps the command, context, and action buttons. (#75470) Thanks @ngutman. +- Agents/failover: carry `sessionId`, `lane`, `provider`, `model`, and `profileId` attribution through `FailoverError` and `describeFailoverError`/`coerceToFailoverError` so structured error logs (e.g. `gateway.err.log` ingestion) can attribute exhausted-fallback wrapper errors to the originating session and last-attempted provider instead of dropping the metadata after the per-profile errors. Fixes #42713. (#73506) Thanks @wenxu007. ## 2026.4.29 @@ -112,6 +147,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Voice Call: resolve SecretRef-backed Twilio auth tokens and realtime/streaming provider API keys before initializing call providers, so SecretRef-backed voice-call credentials reach runtime as strings. (#73632) Thanks @VACInc. - Security/outbound: strip re-formed HTML tags during plain-text sanitization so nested tag fragments cannot leave a CodeQL-detected `