Compare commits

...

33 Commits

Author SHA1 Message Date
Peter Steinberger
8287402da7 ci: preserve release preflight artifact across checkout 2026-05-10 18:36:56 +01:00
Peter Steinberger
2dee06f7af build: refresh canvas bundle hash 2026-05-10 18:05:28 +01:00
Peter Steinberger
d094fc79f3 chore: bump version to 2026.5.10-beta.2 2026-05-10 18:00:40 +01:00
Peter Steinberger
2a609f8a7b test: refresh generated release backport hashes 2026-05-10 17:53:45 +01:00
Frank Yang
df99775c5d fix: pass media roots to gateway message actions 2026-05-10 17:48:59 +01:00
Kagura
b99398e807 fix(codex): normalize thread id/sessionId cross-fill before schema validation (#80137)
Merged via squash.

Prepared head SHA: b2c20dd5d6
Co-authored-by: kagura-agent <268167063+kagura-agent@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-05-10 17:48:59 +01:00
Peter Steinberger
0e6e63b20b fix: keep provider auth login sdk compat 2026-05-10 17:48:16 +01:00
Peter Steinberger
1b204f2504 fix(acpx): await startup probe before gateway ready 2026-05-10 17:46:40 +01:00
Peter Steinberger
f66b1d1738 fix: strip OpenAI-compatible replay reasoning 2026-05-10 17:46:18 +01:00
Andy Ye
1205a9a58a fix(agents): initialize context engines before CLI compaction 2026-05-10 17:45:57 +01:00
Peter Steinberger
a5b2efa336 fix: add strict OpenAI-compatible message key mode 2026-05-10 17:45:56 +01:00
Peter Steinberger
587bb5d34b fix: preserve custom provider context limits (#79911) 2026-05-10 17:45:09 +01:00
Jefsky
01af462223 fix(onboard): avoid custom-provider compaction deadlock (#79428)
Raise default/effective Custom Provider contextWindow above the compaction
reserveTokensFloor default so new onboard flows do not infinite-compact.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 17:44:41 +01:00
Peter Steinberger
d39cec9510 fix: honor voice agent tool allowlist 2026-05-10 17:44:41 +01:00
scoootscooob
720e51202c fix: reread config on in-process gateway restart (#80161)
* fix: reread config on in-process gateway restart

* fix: refresh swift protocol model
2026-05-10 17:44:22 +01:00
Peter Steinberger
6edf77c65f fix: normalize merged gemini model config 2026-05-10 17:43:52 +01:00
Peter Steinberger
964e944de5 fix(discord): make native opus opt-in 2026-05-10 17:43:52 +01:00
Peter Steinberger
ed40ef0a68 fix: show deep status config warnings 2026-05-10 16:12:07 +01:00
Peter Steinberger
e5296a2401 docs: note gateway status warning visibility 2026-05-10 16:00:53 +01:00
Peter Steinberger
aa9af608a1 ci: speed up release validation profiles 2026-05-10 15:50:47 +01:00
Peter Steinberger
410efd9850 ci: tighten release publish timeouts 2026-05-10 15:19:39 +01:00
Peter Steinberger
9c7e67b0f8 ci: skip OpenAI install tool smoke 2026-05-10 13:59:23 +01:00
Peter Steinberger
fcdbaaff2b ci: trim OpenAI install package smoke 2026-05-10 13:42:56 +01:00
Peter Steinberger
638dea598c ci: use faster OpenAI model for installer proof 2026-05-10 13:16:12 +01:00
Peter Steinberger
56d192be06 ci: parallelize OpenAI installer proof turns 2026-05-10 12:44:24 +01:00
Peter Steinberger
31d78ca77c ci: give OpenAI package lane cleanup margin 2026-05-10 11:55:40 +01:00
Peter Steinberger
9877a614f8 test: refresh canvas bundle hash 2026-05-10 11:18:13 +01:00
Peter Steinberger
e8920158f0 fix(agents): preserve OpenAI event streams 2026-05-10 11:07:40 +01:00
Peter Steinberger
85a0f1c018 test(agents): type stream setup timeout mock 2026-05-10 10:15:50 +01:00
Peter Steinberger
8541f69f89 fix(agents): cap provider setup timeout 2026-05-10 10:03:06 +01:00
Peter Steinberger
a14e0aefe7 fix(agents): abort timed out stream setup 2026-05-10 09:54:20 +01:00
Peter Steinberger
3f04632448 fix(agents): enforce idle timeout during stream setup 2026-05-10 09:39:23 +01:00
Peter Steinberger
e0d48a8913 ci: use current OpenAI model for release smokes 2026-05-10 09:01:29 +01:00
203 changed files with 2182 additions and 530 deletions

View File

@@ -32,7 +32,7 @@ on:
default: stable
type: choice
options:
- minimum
- beta
- stable
- full
run_release_soak:
@@ -202,7 +202,7 @@ jobs:
needs: [resolve_target]
if: contains(fromJSON('["all","ci"]'), inputs.rerun_group)
runs-on: ubuntu-24.04
timeout-minutes: 240
timeout-minutes: ${{ inputs.release_profile == 'full' && 240 || 60 }}
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
url: ${{ steps.dispatch.outputs.url }}
@@ -301,7 +301,7 @@ jobs:
needs: [resolve_target]
if: contains(fromJSON('["all","plugin-prerelease"]'), inputs.rerun_group)
runs-on: ubuntu-24.04
timeout-minutes: 300
timeout-minutes: ${{ inputs.release_profile == 'full' && 300 || 60 }}
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
url: ${{ steps.dispatch.outputs.url }}
@@ -400,7 +400,7 @@ jobs:
needs: [resolve_target]
if: contains(fromJSON('["all","release-checks","install-smoke","cross-os","live-e2e","package","qa","qa-parity","qa-live"]'), inputs.rerun_group)
runs-on: ubuntu-24.04
timeout-minutes: 720
timeout-minutes: ${{ inputs.release_profile == 'full' && 720 || 60 }}
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
url: ${{ steps.dispatch.outputs.url }}
@@ -616,7 +616,7 @@ jobs:
needs: [resolve_target, prepare_release_package]
if: ${{ always() && contains(fromJSON('["all","npm-telegram"]'), inputs.rerun_group) && (inputs.npm_telegram_package_spec != '' || (inputs.rerun_group == 'all' && inputs.release_profile == 'full')) }}
runs-on: ubuntu-24.04
timeout-minutes: 120
timeout-minutes: ${{ inputs.release_profile == 'full' && 120 || 60 }}
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
url: ${{ steps.dispatch.outputs.url }}

View File

@@ -94,7 +94,7 @@ on:
default: stable
type: choice
options:
- minimum
- beta
- stable
- full
workflow_call:
@@ -385,21 +385,21 @@ jobs:
if [[ -n "$live_model_providers" ]]; then
add_suite docker-live-models
else
add_profile_suite docker-live-models "minimum stable full"
add_profile_suite docker-live-models "beta minimum stable full"
fi
if [[ "$LIVE_MODELS_ONLY" != "true" ]]; then
add_suite live-cache
add_profile_suite native-live-src-agents "stable full"
add_profile_suite native-live-src-gateway-core "minimum stable full"
add_profile_suite native-live-src-gateway-core "beta minimum stable full"
add_profile_suite native-live-src-gateway-profiles-anthropic "stable full"
add_profile_suite native-live-src-gateway-profiles-anthropic-smoke "stable"
add_profile_suite native-live-src-gateway-profiles-anthropic-opus "full"
add_profile_suite native-live-src-gateway-profiles-anthropic-sonnet-haiku "full"
add_profile_suite native-live-src-gateway-profiles-google "stable full"
add_profile_suite native-live-src-gateway-profiles-minimax "stable full"
add_profile_suite native-live-src-gateway-profiles-openai "minimum stable full"
add_profile_suite native-live-src-gateway-profiles-openai "beta minimum stable full"
add_profile_suite native-live-src-gateway-profiles-fireworks "full"
add_profile_suite native-live-src-gateway-profiles-deepseek "full"
add_profile_suite native-live-src-gateway-profiles-opencode-go "full"
@@ -412,11 +412,11 @@ jobs:
add_profile_suite native-live-test "stable full"
add_profile_suite native-live-extensions-l-n "full"
add_profile_suite native-live-extensions-moonshot "full"
add_profile_suite native-live-extensions-openai "minimum stable full"
add_profile_suite native-live-extensions-openai "beta minimum stable full"
add_profile_suite native-live-extensions-o-z-other "full"
add_profile_suite native-live-extensions-xai "full"
add_profile_suite live-gateway-docker "minimum stable full"
add_profile_suite live-gateway-docker "beta minimum stable full"
add_profile_suite live-gateway-anthropic-docker "stable full"
add_profile_suite live-gateway-google-docker "stable full"
add_profile_suite live-gateway-minimax-docker "stable full"
@@ -505,7 +505,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_repo_e2e && inputs.live_suite_filter == ''
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 90
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
env:
OPENCLAW_VITEST_MAX_WORKERS: "2"
steps:
@@ -542,7 +542,7 @@ jobs:
- suite_id: openshell-e2e
label: OpenShell repo E2E
command: pnpm test:e2e:openshell
timeout_minutes: 120
timeout_minutes: 60
requires_repo_e2e: true
requires_live_suites: false
env:
@@ -615,46 +615,60 @@ jobs:
include:
- chunk_id: core
label: core
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: package-update-openai
label: package/update OpenAI install
timeout_minutes: 30
timeout_minutes: 20
profiles: beta minimum stable full
- chunk_id: package-update-anthropic
label: package/update Anthropic install
timeout_minutes: 180
timeout_minutes: 60
profiles: beta minimum stable full
- chunk_id: package-update-core
label: package/update core
timeout_minutes: 120
timeout_minutes: 60
profiles: beta minimum stable full
- chunk_id: plugins-runtime-plugins
label: plugins/runtime plugins
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-services
label: plugins/runtime services
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-a
label: plugins/runtime install A
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-b
label: plugins/runtime install B
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-c
label: plugins/runtime install C
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-d
label: plugins/runtime install D
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-e
label: plugins/runtime install E
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-f
label: plugins/runtime install F
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-g
label: plugins/runtime install G
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
- chunk_id: plugins-runtime-install-h
label: plugins/runtime install H
timeout_minutes: 120
timeout_minutes: 60
profiles: stable full
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -707,6 +721,7 @@ jobs:
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }}
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_DOCKER_ALL_RELEASE_PROFILE: ${{ inputs.release_test_profile }}
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 }}
@@ -716,12 +731,14 @@ jobs:
DOCKER_E2E_CHUNK: ${{ matrix.chunk_id }}
steps:
- name: Checkout selected ref
if: contains(matrix.profiles, inputs.release_test_profile)
uses: actions/checkout@v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 1
- name: Checkout trusted release harness
if: contains(matrix.profiles, inputs.release_test_profile)
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
@@ -729,6 +746,7 @@ jobs:
path: .release-harness
- name: Log in to GHCR for shared Docker E2E image
if: contains(matrix.profiles, inputs.release_test_profile)
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
@@ -736,6 +754,7 @@ jobs:
password: ${{ github.token }}
- name: Setup Node environment
if: contains(matrix.profiles, inputs.release_test_profile)
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
@@ -743,14 +762,17 @@ jobs:
install-bun: "true"
- name: Hydrate live auth/profile inputs
if: contains(matrix.profiles, inputs.release_test_profile)
run: bash scripts/ci-hydrate-live-auth.sh
- name: Plan Docker E2E chunk
if: contains(matrix.profiles, inputs.release_test_profile)
id: plan
shell: bash
env:
CHUNK: ${{ matrix.chunk_id }}
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
RELEASE_TEST_PROFILE: ${{ inputs.release_test_profile }}
run: |
set -euo pipefail
if [[ -z "$CHUNK" ]]; then
@@ -762,6 +784,7 @@ jobs:
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
export OPENCLAW_DOCKER_ALL_CHUNK="$CHUNK"
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI"
export OPENCLAW_DOCKER_ALL_RELEASE_PROFILE="$RELEASE_TEST_PROFILE"
plan_path=".artifacts/docker-tests/release-${CHUNK}-plan.json"
node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path"
@@ -769,27 +792,28 @@ jobs:
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"
- name: Download OpenClaw Docker E2E package
if: steps.plan.outputs.needs_package == '1'
if: contains(matrix.profiles, inputs.release_test_profile) && steps.plan.outputs.needs_package == '1'
uses: actions/download-artifact@v8
with:
name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
path: .artifacts/docker-e2e-package
- name: Pull shared bare Docker E2E image
if: steps.plan.outputs.needs_bare_image == '1'
if: contains(matrix.profiles, inputs.release_test_profile) && steps.plan.outputs.needs_bare_image == '1'
shell: bash
run: |
set -euo pipefail
bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}"
- name: Pull shared functional Docker E2E image
if: steps.plan.outputs.needs_functional_image == '1'
if: contains(matrix.profiles, inputs.release_test_profile) && steps.plan.outputs.needs_functional_image == '1'
shell: bash
run: |
set -euo pipefail
bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}"
- name: Validate Docker E2E credentials
if: contains(matrix.profiles, inputs.release_test_profile)
shell: bash
env:
CREDENTIALS: ${{ steps.plan.outputs.credentials }}
@@ -808,11 +832,13 @@ jobs:
fi
- name: Run Docker E2E chunk
if: contains(matrix.profiles, inputs.release_test_profile)
shell: bash
run: |
set -euo pipefail
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
export OPENCLAW_DOCKER_ALL_CHUNK="${DOCKER_E2E_CHUNK}"
export OPENCLAW_DOCKER_ALL_RELEASE_PROFILE="${OPENCLAW_DOCKER_ALL_RELEASE_PROFILE}"
export OPENCLAW_DOCKER_ALL_BUILD=0
export OPENCLAW_DOCKER_ALL_PREFLIGHT=0
export OPENCLAW_DOCKER_ALL_FAIL_FAST=0
@@ -877,7 +903,7 @@ jobs:
if: inputs.docker_lanes != ''
name: Docker E2E targeted lanes (${{ matrix.group.label }})
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 90
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
@@ -1086,7 +1112,7 @@ jobs:
if: inputs.include_openwebui && !inputs.include_release_path_suites && inputs.docker_lanes == ''
name: Docker E2E (openwebui)
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 75
timeout-minutes: 60
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
@@ -1213,7 +1239,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_release_path_suites || inputs.include_openwebui || inputs.docker_lanes != ''
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 90
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
permissions:
actions: read
contents: read
@@ -1252,6 +1278,7 @@ jobs:
LANES: ${{ inputs.docker_lanes }}
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }}
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
RELEASE_TEST_PROFILE: ${{ inputs.release_test_profile }}
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 }}
@@ -1268,6 +1295,7 @@ jobs:
export OPENCLAW_DOCKER_ALL_LANES=openwebui
fi
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI"
export OPENCLAW_DOCKER_ALL_RELEASE_PROFILE="$RELEASE_TEST_PROFILE"
plan_path=".artifacts/docker-tests/plan.json"
node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path"
@@ -1544,7 +1572,7 @@ jobs:
profiles: stable full
- provider_label: OpenAI
providers: openai
profiles: minimum stable full
profiles: beta minimum stable full
- provider_label: OpenCode
providers: opencode-go
profiles: full
@@ -1863,15 +1891,15 @@ jobs:
- suite_id: native-live-src-agents
label: Native live agents
command: node .release-harness/scripts/test-live-shard.mjs native-live-src-agents
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: stable full
- suite_id: native-live-src-gateway-core
label: Native live gateway core
command: node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-core
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: minimum stable full
profiles: beta minimum stable full
- suite_id: native-live-src-gateway-profiles-anthropic-smoke
suite_group: native-live-src-gateway-profiles-anthropic
label: Native live gateway profiles Anthropic smoke
@@ -1883,72 +1911,72 @@ jobs:
suite_group: native-live-src-gateway-profiles-anthropic
label: Native live gateway profiles Anthropic Opus
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-opus-4-7,anthropic/claude-opus-4-6 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-anthropic-sonnet-haiku
suite_group: native-live-src-gateway-profiles-anthropic
label: Native live gateway profiles Anthropic Sonnet/Haiku
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MODELS=anthropic/claude-sonnet-4-6,anthropic/claude-haiku-4-5 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-google
label: Native live gateway profiles Google
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview,google/gemini-3-flash-preview node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: stable full
- suite_id: native-live-src-gateway-profiles-minimax
label: Native live gateway profiles MiniMax
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: stable full
- suite_id: native-live-src-gateway-profiles-openai
label: Native live gateway profiles OpenAI
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MODELS=openai/gpt-5.5 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: minimum stable full
profiles: beta minimum stable full
- suite_id: native-live-src-gateway-profiles-fireworks
label: Native live gateway profiles Fireworks
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=fireworks node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-deepseek
label: Native live gateway profiles DeepSeek
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-opencode-go-deepseek-glm
suite_group: native-live-src-gateway-profiles-opencode-go
label: Native live gateway profiles OpenCode Go DeepSeek/GLM
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_MODELS=opencode-go/deepseek-v4-flash,opencode-go/deepseek-v4-pro,opencode-go/glm-5,opencode-go/glm-5.1 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-opencode-go-kimi
suite_group: native-live-src-gateway-profiles-opencode-go
label: Native live gateway profiles OpenCode Go Kimi
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_MODELS=opencode-go/kimi-k2.5,opencode-go/kimi-k2.6 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-opencode-go-mimo
suite_group: native-live-src-gateway-profiles-opencode-go
label: Native live gateway profiles OpenCode Go MiMo
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_MODELS=opencode-go/mimo-v2-omni,opencode-go/mimo-v2-pro,opencode-go/mimo-v2.5,opencode-go/mimo-v2.5-pro node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-opencode-go-minimax-qwen
suite_group: native-live-src-gateway-profiles-opencode-go
label: Native live gateway profiles OpenCode Go MiniMax/Qwen
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_MODELS=opencode-go/minimax-m2.5,opencode-go/minimax-m2.7,opencode-go/qwen3.5-plus,opencode-go/qwen3.6-plus node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-opencode-go-smoke
@@ -1960,25 +1988,25 @@ jobs:
- suite_id: native-live-src-gateway-profiles-openrouter
label: Native live gateway profiles OpenRouter
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openrouter node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-xai
label: Native live gateway profiles xAI
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-profiles-zai
label: Native live gateway profiles Z.ai
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=zai node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-src-gateway-backends
label: Native live gateway backends
command: node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-backends
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: stable full
- suite_id: native-live-src-infra
@@ -1990,13 +2018,13 @@ jobs:
- suite_id: native-live-test
label: Native live test harnesses
command: node .release-harness/scripts/test-live-shard.mjs native-live-test
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: stable full
- suite_id: native-live-extensions-l-n
label: Native live plugins L-N
command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-l-n
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-moonshot
@@ -2009,19 +2037,19 @@ jobs:
- suite_id: native-live-extensions-openai
label: Native live OpenAI plugin
command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-openai
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: minimum stable full
profiles: beta minimum stable full
- suite_id: native-live-extensions-o-z-other
label: Native live plugins O-Z other
command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-o-z-other
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-xai
label: Native live xAI plugin
command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-xai
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
env:
@@ -2188,7 +2216,7 @@ jobs:
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=openai OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 30
profile_env_only: false
profiles: minimum stable full
profiles: beta minimum stable full
- suite_id: live-gateway-anthropic-docker
label: Docker live gateway Anthropic
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=30000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=60000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 25m bash .release-harness/scripts/test-live-gateway-models-docker.sh
@@ -2391,53 +2419,53 @@ jobs:
- suite_id: native-live-extensions-a-k
label: Native live plugins A-K
command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-a-k
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-audio
label: Native live media audio plugins
command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-audio
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-music-google
label: Native live media music Google
command: OPENCLAW_LIVE_MUSIC_GENERATION_PROVIDERS=google node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-music-google
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-music-minimax
label: Native live media music MiniMax
command: OPENCLAW_LIVE_MUSIC_GENERATION_PROVIDERS=minimax node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-music-minimax
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-video-a
suite_group: native-live-extensions-media-video
label: Native live media video plugins A
command: OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=alibaba,byteplus,deepinfra,fal node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-video-b
suite_group: native-live-extensions-media-video
label: Native live media video plugins B
command: OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=google,minimax node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-video-c
suite_group: native-live-extensions-media-video
label: Native live media video plugins C
command: OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=openai,openrouter,xai node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
- suite_id: native-live-extensions-media-video-d
suite_group: native-live-extensions-media-video
label: Native live media video plugins D
command: OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=qwen,runway,together,vydra node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video
timeout_minutes: 90
timeout_minutes: 60
profile_env_only: false
profiles: full
env:

View File

@@ -239,6 +239,9 @@ jobs:
exit 1
fi
RELEASE_SHA="$(git rev-parse HEAD)"
PACKAGE_VERSION="$(node -p "require('./package.json').version")"
TARBALL_NAME="$(basename "$PACK_PATH")"
TARBALL_SHA256="$(sha256sum "$PACK_PATH" | awk '{print $1}')"
ARTIFACT_DIR="$RUNNER_TEMP/openclaw-npm-preflight"
rm -rf "$ARTIFACT_DIR"
mkdir -p "$ARTIFACT_DIR"
@@ -246,6 +249,24 @@ jobs:
printf '%s\n' "$RELEASE_TAG" > "$ARTIFACT_DIR/release-tag.txt"
printf '%s\n' "$RELEASE_SHA" > "$ARTIFACT_DIR/release-sha.txt"
printf '%s\n' "$RELEASE_NPM_DIST_TAG" > "$ARTIFACT_DIR/release-npm-dist-tag.txt"
ARTIFACT_DIR="$ARTIFACT_DIR" RELEASE_TAG="$RELEASE_TAG" RELEASE_SHA="$RELEASE_SHA" RELEASE_NPM_DIST_TAG="$RELEASE_NPM_DIST_TAG" PACKAGE_VERSION="$PACKAGE_VERSION" TARBALL_NAME="$TARBALL_NAME" TARBALL_SHA256="$TARBALL_SHA256" node <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const manifest = {
version: 1,
releaseTag: process.env.RELEASE_TAG,
releaseSha: process.env.RELEASE_SHA,
npmDistTag: process.env.RELEASE_NPM_DIST_TAG,
packageName: "openclaw",
packageVersion: process.env.PACKAGE_VERSION,
tarballName: process.env.TARBALL_NAME,
tarballSha256: process.env.TARBALL_SHA256,
};
fs.writeFileSync(
path.join(process.env.ARTIFACT_DIR, "preflight-manifest.json"),
`${JSON.stringify(manifest, null, 2)}\n`,
);
NODE
echo "dir=$ARTIFACT_DIR" >> "$GITHUB_OUTPUT"
- name: Upload prepared npm publish bundle
@@ -379,17 +400,17 @@ jobs:
run: |
set -euo pipefail
EXPECTED_RELEASE_SHA="$(git rev-parse HEAD)"
TAG_FILE="preflight-tarball/release-tag.txt"
SHA_FILE="preflight-tarball/release-sha.txt"
NPM_DIST_TAG_FILE="preflight-tarball/release-npm-dist-tag.txt"
if [[ ! -f "$TAG_FILE" || ! -f "$SHA_FILE" || ! -f "$NPM_DIST_TAG_FILE" ]]; then
MANIFEST_FILE="preflight-tarball/preflight-manifest.json"
if [[ ! -f "$MANIFEST_FILE" ]]; then
echo "Prepared preflight metadata is missing." >&2
ls -la preflight-tarball >&2 || true
exit 1
fi
ARTIFACT_RELEASE_TAG="$(tr -d '\r\n' < "$TAG_FILE")"
ARTIFACT_RELEASE_SHA="$(tr -d '\r\n' < "$SHA_FILE")"
ARTIFACT_RELEASE_NPM_DIST_TAG="$(tr -d '\r\n' < "$NPM_DIST_TAG_FILE")"
ARTIFACT_RELEASE_TAG="$(jq -r '.releaseTag // ""' "$MANIFEST_FILE")"
ARTIFACT_RELEASE_SHA="$(jq -r '.releaseSha // ""' "$MANIFEST_FILE")"
ARTIFACT_RELEASE_NPM_DIST_TAG="$(jq -r '.npmDistTag // ""' "$MANIFEST_FILE")"
ARTIFACT_TARBALL_NAME="$(jq -r '.tarballName // ""' "$MANIFEST_FILE")"
ARTIFACT_TARBALL_SHA256="$(jq -r '.tarballSha256 // ""' "$MANIFEST_FILE")"
if [[ "$ARTIFACT_RELEASE_TAG" != "$RELEASE_TAG" ]]; then
echo "Prepared preflight tag mismatch: expected $RELEASE_TAG, got $ARTIFACT_RELEASE_TAG" >&2
exit 1
@@ -402,6 +423,15 @@ jobs:
echo "Prepared preflight npm dist-tag mismatch: expected $RELEASE_NPM_DIST_TAG, got $ARTIFACT_RELEASE_NPM_DIST_TAG" >&2
exit 1
fi
if [[ -z "$ARTIFACT_TARBALL_NAME" || ! -f "preflight-tarball/$ARTIFACT_TARBALL_NAME" ]]; then
echo "Prepared preflight tarball named in manifest is missing: $ARTIFACT_TARBALL_NAME" >&2
exit 1
fi
actual_tarball_sha256="$(sha256sum "preflight-tarball/$ARTIFACT_TARBALL_NAME" | awk '{print $1}')"
if [[ "$actual_tarball_sha256" != "$ARTIFACT_TARBALL_SHA256" ]]; then
echo "Prepared preflight tarball digest mismatch." >&2
exit 1
fi
- name: Resolve publish tarball
id: publish_tarball

View File

@@ -36,7 +36,7 @@ on:
default: stable
type: choice
options:
- minimum
- beta
- stable
- full
run_release_soak:
@@ -259,7 +259,18 @@ jobs:
else
run_release_soak=true
fi
if [[ "$RELEASE_PROFILE_INPUT" == "full" ]]; then
release_profile="$RELEASE_PROFILE_INPUT"
if [[ "$release_profile" == "minimum" ]]; then
release_profile=beta
fi
case "$release_profile" in
beta|stable|full) ;;
*)
echo "release_profile must be one of: beta, stable, full" >&2
exit 1
;;
esac
if [[ "$release_profile" == "full" ]]; then
run_release_soak=true
fi
@@ -330,7 +341,7 @@ jobs:
printf 'ref=%s\n' "$RELEASE_REF_INPUT"
printf 'provider=%s\n' "$RELEASE_PROVIDER_INPUT"
printf 'mode=%s\n' "$RELEASE_MODE_INPUT"
printf 'release_profile=%s\n' "$RELEASE_PROFILE_INPUT"
printf 'release_profile=%s\n' "$release_profile"
printf 'run_release_soak=%s\n' "$run_release_soak"
printf 'rerun_group=%s\n' "$RELEASE_RERUN_GROUP_INPUT"
printf 'live_suite_filter=%s\n' "$RELEASE_LIVE_SUITE_FILTER_INPUT"
@@ -350,7 +361,7 @@ jobs:
RELEASE_REF_FAST_PATH: ${{ steps.fast_ref.outputs.fast }}
RELEASE_PROVIDER: ${{ inputs.provider }}
RELEASE_MODE: ${{ inputs.mode }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RELEASE_PROFILE: ${{ steps.inputs.outputs.release_profile }}
RUN_RELEASE_SOAK: ${{ steps.inputs.outputs.run_release_soak }}
RELEASE_RERUN_GROUP: ${{ inputs.rerun_group }}
RELEASE_LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
@@ -572,7 +583,7 @@ jobs:
ref: ${{ needs.resolve_target.outputs.revision }}
include_repo_e2e: false
include_release_path_suites: true
include_openwebui: ${{ needs.resolve_target.outputs.release_profile != 'minimum' }}
include_openwebui: ${{ needs.resolve_target.outputs.release_profile != 'beta' }}
include_live_suites: false
release_test_profile: ${{ needs.resolve_target.outputs.release_profile }}
package_artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}

View File

@@ -37,6 +37,15 @@ on:
required: true
default: true
type: boolean
release_profile:
description: Release coverage profile used for release evidence summaries
required: false
default: beta
type: choice
options:
- beta
- stable
- full
wait_for_clawhub:
description: Wait for ClawHub plugin publish before marking this workflow complete
required: true
@@ -62,7 +71,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
sha: ${{ steps.ref.outputs.sha }}
sha: ${{ steps.manifest.outputs.sha || steps.ref.outputs.sha }}
steps:
- name: Validate inputs
env:
@@ -72,6 +81,7 @@ jobs:
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
PLUGINS: ${{ inputs.plugins }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
WORKFLOW_REF: ${{ github.ref }}
run: |
set -euo pipefail
@@ -103,6 +113,23 @@ jobs:
echo "plugin_publish_scope=all-publishable must not include plugins." >&2
exit 1
fi
case "$RELEASE_PROFILE" in
beta|stable|full) ;;
*)
echo "release_profile must be one of: beta, stable, full" >&2
exit 1
;;
esac
- name: Download OpenClaw npm preflight manifest
if: ${{ inputs.publish_openclaw_npm }}
uses: actions/download-artifact@v8
with:
name: openclaw-npm-preflight-${{ inputs.tag }}
path: ${{ runner.temp }}/openclaw-npm-preflight-manifest
repository: ${{ github.repository }}
run-id: ${{ inputs.preflight_run_id }}
github-token: ${{ github.token }}
- name: Checkout release tag
uses: actions/checkout@v6
@@ -111,17 +138,54 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
install-bun: "false"
- name: Resolve checked-out release ref
id: ref
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Validate OpenClaw npm preflight manifest
id: manifest
if: ${{ inputs.publish_openclaw_npm }}
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
EXPECTED_SHA: ${{ steps.ref.outputs.sha }}
run: |
set -euo pipefail
preflight_dir="${RUNNER_TEMP}/openclaw-npm-preflight-manifest"
manifest="${preflight_dir}/preflight-manifest.json"
if [[ ! -f "$manifest" ]]; then
echo "OpenClaw npm preflight manifest is missing." >&2
ls -la "$preflight_dir" >&2 || true
exit 1
fi
release_tag="$(jq -r '.releaseTag // ""' "$manifest")"
release_sha="$(jq -r '.releaseSha // ""' "$manifest")"
npm_dist_tag="$(jq -r '.npmDistTag // ""' "$manifest")"
tarball_name="$(jq -r '.tarballName // ""' "$manifest")"
tarball_sha256="$(jq -r '.tarballSha256 // ""' "$manifest")"
if [[ "$release_tag" != "$RELEASE_TAG" ]]; then
echo "Preflight manifest tag mismatch: expected $RELEASE_TAG, got $release_tag" >&2
exit 1
fi
if [[ "$release_sha" != "$EXPECTED_SHA" ]]; then
echo "Preflight manifest SHA mismatch: expected $EXPECTED_SHA, got $release_sha" >&2
exit 1
fi
if [[ "$npm_dist_tag" != "$RELEASE_NPM_DIST_TAG" ]]; then
echo "Preflight manifest npm dist-tag mismatch: expected $RELEASE_NPM_DIST_TAG, got $npm_dist_tag" >&2
exit 1
fi
if [[ -z "$tarball_name" || ! -f "${preflight_dir}/${tarball_name}" ]]; then
echo "Preflight manifest tarball is missing: $tarball_name" >&2
exit 1
fi
actual_tarball_sha256="$(sha256sum "${preflight_dir}/${tarball_name}" | awk '{print $1}')"
if [[ "$actual_tarball_sha256" != "$tarball_sha256" ]]; then
echo "Preflight manifest tarball digest mismatch." >&2
exit 1
fi
echo "sha=$release_sha" >> "$GITHUB_OUTPUT"
- name: Validate release tag is reachable from main or release branch
run: |
set -euo pipefail
@@ -139,26 +203,25 @@ jobs:
echo "Release tag must point to a commit reachable from main or release/*." >&2
exit 1
- name: Verify plugin versions were synced for this release
run: pnpm plugins:sync:check
- name: Summarize release target
env:
RELEASE_TAG: ${{ inputs.tag }}
TARGET_SHA: ${{ steps.ref.outputs.sha }}
TARGET_SHA: ${{ steps.manifest.outputs.sha || steps.ref.outputs.sha }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
run: |
{
echo "### Release target"
echo
echo "- Tag: \`${RELEASE_TAG}\`"
echo "- SHA: \`${TARGET_SHA}\`"
echo "- Release profile: \`${RELEASE_PROFILE}\`"
} >> "$GITHUB_STEP_SUMMARY"
publish:
name: Publish plugins, then OpenClaw
needs: [resolve_release_target]
runs-on: ubuntu-latest
timeout-minutes: 360
timeout-minutes: 60
steps:
- name: Dispatch publish workflows
env:
@@ -274,15 +337,22 @@ jobs:
changelog_file="${RUNNER_TEMP}/CHANGELOG.md"
notes_file="${RUNNER_TEMP}/release-notes.md"
gh api --repo "$GITHUB_REPOSITORY" "repos/${GITHUB_REPOSITORY}/contents/CHANGELOG.md?ref=${TARGET_SHA}" \
gh api "repos/${GITHUB_REPOSITORY}/contents/CHANGELOG.md?ref=${TARGET_SHA}" \
--jq '.content' | base64 --decode > "${changelog_file}"
awk -v version="${notes_version}" '
$0 == "## " version { in_section = 1; next }
/^## / && in_section { exit }
in_section { print }
' "${changelog_file}" > "${notes_file}"
if [[ ! -s "${notes_file}" ]] && [[ "${RELEASE_TAG}" == *"-alpha."* || "${RELEASE_TAG}" == *"-beta."* ]]; then
awk '
$0 == "## Unreleased" { in_section = 1; next }
/^## / && in_section { exit }
in_section { print }
' "${changelog_file}" > "${notes_file}"
fi
if [[ ! -s "${notes_file}" ]]; then
echo "CHANGELOG.md does not contain release notes for ${notes_version}." >&2
echo "CHANGELOG.md does not contain release notes for ${notes_version} or an Unreleased prerelease fallback." >&2
exit 1
fi

View File

@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
- Discord/voice: add realtime voice diagnostics for speaker turns, playback resets, barge-in detection, and audio cutoff analysis.
- Talk: add `talk.realtime.instructions` so operators can append realtime voice style instructions while preserving OpenClaw's built-in agent-consult guidance. (#79081) Thanks @VACInc.
- Discord/voice: default test and source installs to the pure-JS `opusscript` decoder by ignoring optional native `@discordjs/opus` builds, avoiding slow native addon compiles outside dedicated voice-performance lanes.
- Discord/voice: add an opt-in native `@discordjs/opus` install script and decoder preference for live voice-performance lanes without charging unrelated Docker/tests for native addon builds.
- Gateway/skills: add an opt-in private skill archive upload install path gated by `skills.install.allowUploadedArchives`, so trusted Gateway clients can stage and install zip-backed skills only when operators explicitly enable the code-install surface. (#74430) Thanks @samzong.
- Dependencies: refresh workspace pins and patch targets, including ACPX `@agentclientprotocol/claude-agent-acp` `0.33.1`, Codex ACP `0.14.0`, Baileys `7.0.0-rc10`, Google GenAI `2.0.1`, OpenAI `6.37.0`, AWS SDK `3.1045.0`, Kysely `0.29.0`, Tlon skill `0.3.6`, Aimock `1.19.5`, and tsdown `0.22.0`.
- Agents/compaction: preserve scoped background exec/process session references across embedded compaction and after-turn runtime contexts without exposing sessions from unrelated scopes. Fixes #79284. (#79307) Thanks @TurboTheTurtle.
@@ -19,6 +20,15 @@ Docs: https://docs.openclaw.ai
### Fixes
- Telegram: pass agent-scoped media roots through gateway message actions so workspace-local media from the active agent is not rejected as cross-agent access. Thanks @frankekn.
- CLI/gateway: keep `gateway status --deep` plugin-aware so configured plugin manifest warnings, including missing channel config metadata, stay visible during install and update smoke checks.
- ACPX: run and await the embedded ACP backend startup probe by default so the gateway `ready` signal no longer fires before the acpx runtime has either become usable or reported a probe failure; set `OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=0` to restore lazy startup. Fixes #79596. Thanks @bzelones.
- OpenAI-compatible models: strip prior assistant reasoning fields from replayed Chat Completions history by default, preventing oMLX/vLLM Qwen follow-up turns from rejecting or stalling on stale `reasoning` payloads. Fixes #46637. Thanks @zipzagster and @lexhoefsloot.
- CLI/onboarding: give non-Azure custom providers a safe generated context window and heal legacy 4k wizard entries without overwriting explicit valid small model limits, preventing first-turn compaction loops. Fixes #79428. (#79911) Thanks @Jefsky.
- OpenAI-compatible models: add `compat.strictMessageKeys` to strip Chat Completions replay messages to `role` and `content` for strict providers that reject OpenAI-style tool and metadata keys. Fixes #50374. Thanks @choutos.
- Voice/Ollama: honor routed voice agent `tools.allow` for classic embedded voice responses, including empty allowlists, so no-tool Ollama agents do not receive tool schemas. Fixes #79506. Thanks @donkeykong91.
- Gateway: reread config from disk after the first in-process restart loop startup, preventing SIGUSR1 restarts from reusing a stale startup snapshot and dropping config written after boot. Fixes #79947. Thanks @TheLevti.
- Agents: apply the LLM idle watchdog while provider stream setup is still pending, preventing silent pre-stream model hangs from waiting for the full agent timeout.
- Cron: let isolated self-cleanup runs inspect their own job run history while keeping other cron jobs and mutation actions blocked. Fixes #80019. Thanks @hclsys.
- CLI/config: persist explicit `config set` and `config patch` values that equal runtime defaults instead of reporting success while dropping them. Fixes #79856. (#80106) Thanks @abodanty and @hclsys.
- OpenAI/realtime voice: accept Codex-compatible legacy audio and transcript event aliases so provider protocol drift does not drop assistant audio or captions.
@@ -61,9 +71,11 @@ Docs: https://docs.openclaw.ai
- OpenAI/Codex: point gateway missing-key recovery and wizard docs at the canonical `openai/gpt-5.5` plus Codex OAuth route, and fix trajectory export errors so they suggest the valid `openclaw sessions` command.
- Google/Gemini: normalize retired `google/gemini-3-pro-preview` primary, fallback, and model-map refs during config load and unrelated config writes so saved config keeps targeting Gemini 3.1 Pro Preview.
- Google/Gemini: normalize retired Gemini 3 Pro Preview ids inside emitted Google provider model config, so regenerated models.json rows test `google/gemini-3.1-pro-preview`.
- Google/Gemini: normalize retired Gemini 3 Pro Preview ids preserved from existing merged models.json providers so config emission keeps targeting `google/gemini-3.1-pro-preview`.
- GitHub Copilot: mint short-lived Copilot API tokens with the same `vscode-chat` integration identity used by runtime requests, and refresh legacy cached tokens missing that identity so image-capable Copilot models no longer inherit the `copilot-language-server` scope. Fixes #79946, #80074. Thanks @TurboTheTurtle.
- Plugins/doctor: drop stale managed npm install records when `openclaw doctor --fix` removes npm packages that shadow bundled plugins, so the rebuilt registry no longer resurrects the removed package metadata.
- Discord/voice: reuse or suppress late realtime consult tool calls without stealing newer speaker context or speaking forced fallback answers twice.
- Discord/voice: skip likely incomplete realtime forced-consult transcript fragments and non-actionable closings so stale partial speech does not queue delayed answers over the next turn.
- Discord/voice: synthesize realtime playback timestamps from emitted Discord PCM so OpenAI realtime barge-in truncation no longer sees `audioEndMs=0` and skips legitimate interruptions.
- Plugin SDK: keep activated linked plugin runtime facades loadable when bundled plugin fallback is disabled. Thanks @shakkernerd.
- Feishu: auto-thread `message(action="send")` replies inside the topic when the active session is group_topic or group_topic_sender, and propagate `replyInThread` through text, card, and media outbound adapters so topic-scoped sessions no longer post at the group root. Fixes #74903. (#77151) Thanks @ai-hpc.
@@ -93,6 +105,8 @@ Docs: https://docs.openclaw.ai
- Kimi Code: use Kimi's stable `kimi-for-coding` API model id in bundled catalog, onboarding, and docs while normalizing legacy `kimi-code` and `k2p5` refs. Fixes #79965.
- Volcengine/Kimi: strip provider-unsupported tool schema length and item constraint keywords for direct and coding-plan models so hosted Kimi runs do not reject message tools with `minLength`. Fixes #38817.
- DeepSeek: backfill V4 `reasoning_content` replay fields for unowned OpenAI-compatible proxy providers, preventing follow-up request failures outside the bundled DeepSeek and OpenRouter routes. Fixes #79608.
- iMessage: emit a WARN log when an action is blocked because the imsg private API bridge is not attached, so operators see the silent-drop in `~/.openclaw/logs/openclaw.log` instead of having to read per-session trajectory JSONL `tool.result` payloads. Common after a gateway restart un-injects the dylib from Messages.app. (#80035) Thanks @omarshahine.
- Codex: cross-fill missing `thread.id` and `thread.sessionId` before schema validation so live Codex app-server responses that omit `sessionId` no longer fail `thread/start` or `thread/resume`. Fixes #80124. (#80137) Thanks @kagura-agent.
## 2026.5.9

View File

@@ -716,6 +716,7 @@ public struct AgentParams: Codable, Sendable {
public let bootstrapcontextmode: AnyCodable?
public let bootstrapcontextrunkind: AnyCodable?
public let acpturnsource: String?
public let internalruntimehandoffid: String?
public let internalevents: [[String: AnyCodable]]?
public let inputprovenance: [String: AnyCodable]?
public let voicewaketrigger: String?
@@ -752,6 +753,7 @@ public struct AgentParams: Codable, Sendable {
bootstrapcontextmode: AnyCodable?,
bootstrapcontextrunkind: AnyCodable?,
acpturnsource: String?,
internalruntimehandoffid: String?,
internalevents: [[String: AnyCodable]]?,
inputprovenance: [String: AnyCodable]?,
voicewaketrigger: String?,
@@ -787,6 +789,7 @@ public struct AgentParams: Codable, Sendable {
self.bootstrapcontextmode = bootstrapcontextmode
self.bootstrapcontextrunkind = bootstrapcontextrunkind
self.acpturnsource = acpturnsource
self.internalruntimehandoffid = internalruntimehandoffid
self.internalevents = internalevents
self.inputprovenance = inputprovenance
self.voicewaketrigger = voicewaketrigger
@@ -824,6 +827,7 @@ public struct AgentParams: Codable, Sendable {
case bootstrapcontextmode = "bootstrapContextMode"
case bootstrapcontextrunkind = "bootstrapContextRunKind"
case acpturnsource = "acpTurnSource"
case internalruntimehandoffid = "internalRuntimeHandoffId"
case internalevents = "internalEvents"
case inputprovenance = "inputProvenance"
case voicewaketrigger = "voiceWakeTrigger"

View File

@@ -1,4 +1,4 @@
da702349b376821e0bc1420a945287dea0bccc79298e269abb028718983e94a5 config-baseline.json
8c647da77392bd4e87aac07fbdfc7592bbd656dc09f8844759d2c65dc374bd0d config-baseline.core.json
422a42f7dbd40e3cb6f6661a1a89019adc854712ede8b79c46078128110c1592 config-baseline.json
8d9b6bedecec620af4a135e8d86c22e8afda43fbc2fce81e4e13cc013122ab4a config-baseline.core.json
80f0f51caedf14dc2138d975b62852ff7c5cf085df1c734c9de279f5859a7eeb config-baseline.channel.json
dba159f639977bb96d79f0b78de2c6de48d25ed6ba1590f55812affb7ca6e4b0 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
32f0b7801c9e5e0b7ec8d7da11cec62713e968abf056560ad6372aac877fdf14 plugin-sdk-api-baseline.json
e26cfb7da5e6e8addd0bd4669bd53a4188c53f8371cb20216d854f7dd0154b1b plugin-sdk-api-baseline.jsonl
76508e5ff2d153b67d1766179e52734119a5a82b4c00216a270f51ce73fec710 plugin-sdk-api-baseline.json
7e57007f2086074a3a22b9cd970964c1565058a412c7b141e468bc908cbf9eb8 plugin-sdk-api-baseline.jsonl

View File

@@ -1214,7 +1214,7 @@ Notes:
- If `voice.autoJoin` has multiple entries for the same guild, OpenClaw joins the last configured channel for that guild.
- `voice.daveEncryption` and `voice.decryptionFailureTolerance` pass through to `@discordjs/voice` join options.
- `@discordjs/voice` defaults are `daveEncryption=true` and `decryptionFailureTolerance=24` if unset.
- OpenClaw defaults to the pure-JS `opusscript` decoder for Discord voice receive. The optional native `@discordjs/opus` package is ignored by the repo pnpm install policy so normal installs and tests do not compile a native addon; only opt into a native opus build in a dedicated voice-performance or live-lane environment.
- OpenClaw defaults to the pure-JS `opusscript` decoder for Discord voice receive. The optional native `@discordjs/opus` package is ignored by the repo pnpm install policy so normal installs, Docker lanes, and unrelated tests do not compile a native addon. Dedicated voice-performance hosts can opt in with `OPENCLAW_DISCORD_OPUS_DECODER=native` after installing the native addon.
- `voice.connectTimeoutMs` controls the initial `@discordjs/voice` Ready wait for `/vc join` and auto-join attempts. Default: `30000`.
- `voice.reconnectGraceMs` controls how long OpenClaw waits for a disconnected voice session to begin reconnecting before destroying it. Default: `15000`.
- In `stt-tts` mode, voice playback does not stop just because another user starts speaking. To avoid feedback loops, OpenClaw ignores new voice capture while TTS is playing; speak after playback finishes for the next turn. Realtime modes forward speaker starts as barge-in signals to the realtime provider.
@@ -1225,6 +1225,24 @@ Notes:
- If receive logs repeatedly show `DecryptionFailed(UnencryptedWhenPassthroughDisabled)` after updating, collect a dependency report and logs. The bundled `@discordjs/voice` line includes the upstream padding fix from discord.js PR #11449, which closed discord.js issue #11419.
- `The operation was aborted` receive events are expected when OpenClaw finalizes a captured speaker segment; they are verbose diagnostics, not warnings.
- Verbose Discord voice logs include a bounded one-line STT transcript preview for each accepted speaker segment, so debugging shows both the user side and the agent reply side without dumping unbounded transcript text.
- In `agent-proxy` mode, forced consult fallback skips likely incomplete transcript fragments such as text ending in `...` or a trailing connector like `and`, plus obvious non-actionable closings like “be right back” or “bye”. Logs show `forced agent consult skipped reason=...` when this prevents a stale queued answer.
Native opus setup for source checkouts:
```bash
pnpm install
mise exec node@22 -- pnpm discord:opus:install
```
Use Node 22 for the gateway when you want the upstream macOS arm64 prebuilt native addon. If you use another Node runtime, the opt-in installer may need a local `node-gyp` source-build toolchain.
After installing the native addon, start the Gateway with:
```bash
OPENCLAW_DISCORD_OPUS_DECODER=native pnpm gateway:watch
```
Verbose voice logs should show `discord voice: opus decoder: @discordjs/opus`. Without the env opt-in, or if the native addon is missing or cannot load on the host, OpenClaw logs `discord voice: opus decoder: opusscript` and keeps receiving voice through the pure-JS fallback.
STT plus TTS pipeline:
@@ -1371,6 +1389,7 @@ Expected voice logs:
- On join: `discord voice: joining ... voiceSession=... supervisorSession=... agentSessionMode=... voiceModel=... realtimeModel=...`
- On realtime start: `discord voice: realtime bridge starting ... autoRespond=false interruptResponse=false bargeIn=false minBargeInAudioEndMs=...`
- On speaker audio: `discord voice: realtime speaker turn opened ...`, `discord voice: realtime input audio started ... outputAudioMs=... outputActive=...`, and `discord voice: realtime speaker turn closed ... chunks=... discordBytes=... realtimeBytes=... interruptedPlayback=...`
- On skipped stale speech: `discord voice: realtime forced agent consult skipped reason=incomplete-transcript ...` or `reason=non-actionable-closing ...`
- On realtime response completion: `discord voice: realtime audio playback finishing reason=response.done ... audioMs=... chunks=...`
- On playback stop/reset: `discord voice: realtime audio playback stopped reason=... audioMs=... elapsedMs=... chunks=...`
- On realtime consult: `discord voice: realtime consult requested ... voiceSession=... supervisorSession=... question=...`

View File

@@ -474,6 +474,7 @@ OpenClaw uses the built-in model catalog. Add custom providers via `models.provi
- `models.providers.*.models.*.contextTokens`: optional runtime context cap. This overrides provider-level `contextTokens`; use it when you want a smaller effective context budget than the model's native `contextWindow`; `openclaw models list` shows both values when they differ.
- `models.providers.*.models.*.compat.supportsDeveloperRole`: optional compatibility hint. For `api: "openai-completions"` with a non-empty non-native `baseUrl` (host not `api.openai.com`), OpenClaw forces this to `false` at runtime. Empty/omitted `baseUrl` keeps default OpenAI behavior.
- `models.providers.*.models.*.compat.requiresStringContent`: optional compatibility hint for string-only OpenAI-compatible chat endpoints. When `true`, OpenClaw flattens pure text `messages[].content` arrays into plain strings before sending the request.
- `models.providers.*.models.*.compat.strictMessageKeys`: optional compatibility hint for strict OpenAI-compatible chat endpoints. When `true`, OpenClaw strips outgoing Chat Completions message objects to `role` and `content` before sending the request.
- `models.providers.*.models.*.compat.thinkingFormat`: optional thinking payload hint. Use `"qwen"` for top-level `enable_thinking`, or `"qwen-chat-template"` for `chat_template_kwargs.enable_thinking` on Qwen-family OpenAI-compatible servers that support request-level chat-template kwargs, such as vLLM.
</Accordion>

View File

@@ -327,6 +327,8 @@ If the model loads cleanly but full agent turns misbehave, work top-down — con
- Context errors? Lower `contextWindow` or raise your server limit.
- OpenAI-compatible server returns `messages[].content ... expected a string`?
Add `compat.requiresStringContent: true` on that model entry.
- OpenAI-compatible server returns `validation.keys` or says message entries only allow `role` and `content`?
Add `compat.strictMessageKeys: true` on that model entry.
- Direct tiny `/v1/chat/completions` calls work, but `openclaw infer model run --local`
fails on Gemma or another local model? Check the provider URL, model ref, auth
marker, and server logs first; local `model run` does not include agent tools.

View File

@@ -175,6 +175,7 @@ Look for:
<Accordion title="Common signatures">
- `model_not_found` with a local MLX/vLLM-style server → verify `baseUrl` includes `/v1`, `api` is `"openai-completions"` for `/v1/chat/completions` backends, and `models.providers.<provider>.models[].id` is the bare provider-local id. Select it with the provider prefix once, for example `mlx/mlx-community/Qwen3-30B-A3B-6bit`; keep the catalog entry as `mlx-community/Qwen3-30B-A3B-6bit`.
- `messages[...].content: invalid type: sequence, expected a string` → backend rejects structured Chat Completions content parts. Fix: set `models.providers.<provider>.models[].compat.requiresStringContent: true`.
- `validation.keys` or allowed message keys like `["role","content"]` → backend rejects OpenAI-style replay metadata on Chat Completions messages. Fix: set `models.providers.<provider>.models[].compat.strictMessageKeys: true`.
- `incomplete turn detected ... stopReason=stop payloads=0` → the backend completed the Chat Completions request but returned no user-visible assistant text for that turn. OpenClaw retries replay-safe empty OpenAI-compatible turns once; persistent failures usually mean the backend is emitting empty/non-text content or suppressing final-answer text.
- direct tiny requests succeed, but OpenClaw agent runs fail with backend/model crashes (for example Gemma on some `inferrs` builds) → OpenClaw transport is likely already correct; the backend is failing on the larger agent-runtime prompt shape.
- failures shrink after disabling tools but do not disappear → tool schemas were part of the pressure, but the remaining issue is still upstream model/server capacity or a backend bug.
@@ -182,9 +183,10 @@ Look for:
</Accordion>
<Accordion title="Fix options">
1. Set `compat.requiresStringContent: true` for string-only Chat Completions backends.
2. Set `compat.supportsTools: false` for models/backends that cannot handle OpenClaw's tool schema surface reliably.
3. Lower prompt pressure where possible: smaller workspace bootstrap, shorter session history, lighter local model, or a backend with stronger long-context support.
4. If tiny direct requests keep passing while OpenClaw agent turns still crash inside the backend, treat it as an upstream server/model limitation and file a repro there with the accepted payload shape.
2. Set `compat.strictMessageKeys: true` for strict Chat Completions backends that only accept `role` and `content` on each message.
3. Set `compat.supportsTools: false` for models/backends that cannot handle OpenClaw's tool schema surface reliably.
4. Lower prompt pressure where possible: smaller workspace bootstrap, shorter session history, lighter local model, or a backend with stronger long-context support.
5. If tiny direct requests keep passing while OpenClaw agent turns still crash inside the backend, treat it as an upstream server/model limitation and file a repro there with the accepted payload shape.
</Accordion>
</AccordionGroup>

View File

@@ -124,12 +124,15 @@ inter-session user turns that only have provenance metadata.
- Missing OpenAI Responses-family tool outputs are synthesized as `aborted` to match Codex replay normalization.
- No thought signature stripping.
**OpenAI-compatible Gemma 4**
**OpenAI-compatible Chat Completions**
- Historical assistant thinking/reasoning blocks are stripped before replay so local
OpenAI-compatible Gemma 4 servers do not receive prior-turn reasoning content.
- Historical assistant thinking/reasoning blocks are stripped before replay so
local and proxy-style OpenAI-compatible servers do not receive prior-turn
reasoning fields such as `reasoning` or `reasoning_content`.
- Current same-turn tool-call continuations keep the assistant reasoning block
attached to the tool call until the tool result has been replayed.
- Provider-owned exceptions can opt out when their wire protocol requires
replayed reasoning metadata.
**Google (Generative AI / Gemini CLI / Antigravity)**

View File

@@ -164,10 +164,10 @@ Then verify backend health:
### acpx command and version configuration
By default, the `acpx` plugin registers the embedded ACP backend without
spawning an ACP agent during Gateway startup. Run `/acp doctor` for an explicit
live probe. Set `OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=1` only when you need the
Gateway to probe the configured agent at startup.
By default, the `acpx` plugin probes the embedded ACP backend during Gateway
startup and waits for that probe before the gateway `ready` signal. Set
`OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=0` to skip the startup probe and register
the backend lazily instead. Run `/acp doctor` for an explicit on-demand probe.
Override the command or version in plugin config:
@@ -289,11 +289,10 @@ Restart the gateway after changing this value.
### Health probe agent configuration
When `/acp doctor` or the opt-in startup probe checks the backend, the bundled
`acpx` plugin probes one harness agent. If `acp.allowedAgents` is set, it
defaults to the first allowed agent; otherwise it defaults to `codex`. If your
deployment needs a different ACP agent for health checks, set the probe agent
explicitly:
When `/acp doctor` or the startup probe checks the backend, the bundled `acpx`
plugin probes one harness agent. If `acp.allowedAgents` is set, it defaults to
the first allowed agent; otherwise it defaults to `codex`. If your deployment
needs a different ACP agent for health checks, set the probe agent explicitly:
```bash
openclaw config set plugins.entries.acpx.config.probeAgent claude

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/acpx",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw ACP runtime backend",
"repository": {
"type": "git",
@@ -25,10 +25,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1",
"openclawVersion": "2026.5.10-beta.2",
"staticAssets": [
{
"source": "./src/runtime-internals/mcp-proxy.mjs",

View File

@@ -11,6 +11,7 @@ import type { OpenClawPluginService, OpenClawPluginServiceContext } from "opencl
const ACPX_BACKEND_ID = "acpx";
const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
const SKIP_RUNTIME_PROBE_ENV = "OPENCLAW_SKIP_ACPX_RUNTIME_PROBE";
type RealAcpxServiceModule = typeof import("./src/service.js");
type CreateAcpxRuntimeServiceParams = NonNullable<
@@ -39,7 +40,7 @@ function loadServiceModule(): Promise<RealAcpxServiceModule> {
}
function shouldRunStartupProbe(env: NodeJS.ProcessEnv = process.env): boolean {
return env[ENABLE_STARTUP_PROBE_ENV] === "1";
return env[ENABLE_STARTUP_PROBE_ENV] !== "0" && env[SKIP_RUNTIME_PROBE_ENV] !== "1";
}
async function startRealService(state: DeferredServiceState): Promise<AcpxRuntimeLike> {

View File

@@ -174,7 +174,8 @@ describe("createAcpxRuntimeService", () => {
expect(getAcpRuntimeBackend("acpx")).toBeUndefined();
});
it("creates the embedded runtime state directory without probing at startup by default", async () => {
it("skips the startup probe and defers acpx backend health reporting when explicitly opted out", async () => {
process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE = "0";
const workspaceDir = await makeTempDir();
const stateDir = path.join(workspaceDir, "custom-state");
const ctx = createServiceContext(workspaceDir);
@@ -200,6 +201,47 @@ describe("createAcpxRuntimeService", () => {
await service.stop?.(ctx);
});
it("waits for the embedded runtime startup probe before resolving", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
let releaseProbe!: () => void;
const probeStarted = vi.fn();
const probeAvailability = vi.fn(
() =>
new Promise<void>((resolve) => {
probeStarted();
releaseProbe = resolve;
}),
);
const runtime = createMockRuntime({
probeAvailability,
isHealthy: () => true,
});
const service = createAcpxRuntimeService({
runtimeFactory: () => runtime as never,
});
const startPromise = service.start(ctx) as Promise<void>;
await vi.waitFor(() => {
expect(probeStarted).toHaveBeenCalledOnce();
});
let resolved = false;
void startPromise.then(() => {
resolved = true;
});
await Promise.resolve();
expect(resolved).toBe(false);
releaseProbe();
await startPromise;
expect(resolved).toBe(true);
expect(ctx.logger.info).toHaveBeenCalledWith("embedded acpx runtime backend ready");
await service.stop?.(ctx);
});
it("reaps stale ACPX process leases from the generated wrapper root at startup", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
@@ -317,7 +359,8 @@ describe("createAcpxRuntimeService", () => {
await service.stop?.(ctx);
});
it("registers the default backend without importing ACPX runtime until first use", async () => {
it("registers the default backend lazily without importing ACPX runtime when startup probing is opted out", async () => {
process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE = "0";
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const service = createAcpxRuntimeService();
@@ -344,8 +387,7 @@ describe("createAcpxRuntimeService", () => {
await service.stop?.(ctx);
});
it("can run the embedded runtime probe at startup when explicitly enabled", async () => {
process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE = "1";
it("runs the embedded runtime probe at startup by default and reports health", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const probeAvailability = vi.fn(async () => {});
@@ -365,6 +407,30 @@ describe("createAcpxRuntimeService", () => {
await service.stop?.(ctx);
});
it("bounds the embedded runtime startup probe wait with the configured timeout", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const probeAvailability = vi.fn(() => new Promise<void>(() => {}));
const runtime = createMockRuntime({
probeAvailability,
isHealthy: () => false,
});
const service = createAcpxRuntimeService({
pluginConfig: { timeoutSeconds: 0.001 },
runtimeFactory: () => runtime as never,
});
await service.start(ctx);
expect(probeAvailability).toHaveBeenCalledOnce();
expect(getAcpRuntimeBackend("acpx")?.healthy?.()).toBe(false);
expect(ctx.logger.warn).toHaveBeenCalledWith(
"embedded acpx runtime setup failed: embedded acpx runtime backend startup probe timed out after 0.001s",
);
await service.stop?.(ctx);
});
it("passes the default runtime timeout to the embedded runtime factory", async () => {
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
@@ -497,7 +563,6 @@ describe("createAcpxRuntimeService", () => {
});
it("can skip the embedded runtime probe via env", async () => {
process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE = "1";
process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE = "1";
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
@@ -517,12 +582,12 @@ describe("createAcpxRuntimeService", () => {
expect(getAcpRuntimeBackend("acpx")).toMatchObject({
runtime: expect.any(Object),
});
expect(getAcpRuntimeBackend("acpx")?.healthy).toBeUndefined();
await service.stop?.(ctx);
});
it("formats non-string doctor details without losing object payloads", async () => {
process.env.OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE = "1";
const workspaceDir = await makeTempDir();
const ctx = createServiceContext(workspaceDir);
const runtime = createMockRuntime({
@@ -539,11 +604,9 @@ describe("createAcpxRuntimeService", () => {
await service.start(ctx);
await vi.waitFor(() => {
expect(ctx.logger.warn).toHaveBeenCalledWith(
'embedded acpx runtime backend probe failed: probe failed ({"code":"ACP_CLOSED","agent":"codex"}; stdin closed)',
);
});
expect(ctx.logger.warn).toHaveBeenCalledWith(
'embedded acpx runtime backend probe failed: probe failed ({"code":"ACP_CLOSED","agent":"codex"}; stdin closed)',
);
await service.stop?.(ctx);
});

View File

@@ -11,6 +11,7 @@ import type {
} from "../runtime-api.js";
import { registerAcpRuntimeBackend, unregisterAcpRuntimeBackend } from "../runtime-api.js";
import { prepareAcpxCodexAuthConfig } from "./codex-auth-bridge.js";
import { DEFAULT_ACPX_TIMEOUT_SECONDS } from "./config-schema.js";
import {
resolveAcpxPluginConfig,
toAcpMcpServers,
@@ -34,6 +35,7 @@ type AcpxRuntimeLike = AcpRuntime & {
};
const ENABLE_STARTUP_PROBE_ENV = "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE";
const SKIP_RUNTIME_PROBE_ENV = "OPENCLAW_SKIP_ACPX_RUNTIME_PROBE";
const ACPX_BACKEND_ID = "acpx";
type AcpxRuntimeModule = typeof import("./runtime.js");
@@ -200,7 +202,38 @@ function resolveAllowedAgentsProbeAgent(ctx: OpenClawPluginServiceContext): stri
}
function shouldRunStartupProbe(env: NodeJS.ProcessEnv = process.env): boolean {
return env[ENABLE_STARTUP_PROBE_ENV] === "1";
return env[ENABLE_STARTUP_PROBE_ENV] !== "0";
}
function shouldProbeRuntimeAtStartup(env: NodeJS.ProcessEnv = process.env): boolean {
return shouldRunStartupProbe(env) && env[SKIP_RUNTIME_PROBE_ENV] !== "1";
}
async function withStartupProbeTimeout<T>(params: {
promise: Promise<T>;
timeoutSeconds: number;
}): Promise<T> {
let timeout: ReturnType<typeof setTimeout> | undefined;
const timeoutMs = Math.max(1, params.timeoutSeconds * 1_000);
try {
return await Promise.race([
params.promise,
new Promise<never>((_, reject) => {
timeout = setTimeout(() => {
reject(
new Error(
`embedded acpx runtime backend startup probe timed out after ${params.timeoutSeconds}s`,
),
);
}, timeoutMs);
(timeout as { unref?: () => void }).unref?.();
}),
]);
} finally {
if (timeout) {
clearTimeout(timeout);
}
}
}
async function resolveGatewayInstanceId(stateDir: string): Promise<string> {
@@ -333,43 +366,47 @@ export function createAcpxRuntimeService(
logger: ctx.logger,
});
const shouldProbeRuntime = shouldProbeRuntimeAtStartup();
registerAcpRuntimeBackend({
id: ACPX_BACKEND_ID,
runtime,
...(shouldRunStartupProbe() ? { healthy: () => runtime?.isHealthy() ?? false } : {}),
...(shouldProbeRuntime ? { healthy: () => runtime?.isHealthy() ?? false } : {}),
});
ctx.logger.info(`embedded acpx runtime backend registered (cwd: ${pluginConfig.cwd})`);
if (!shouldRunStartupProbe() || process.env.OPENCLAW_SKIP_ACPX_RUNTIME_PROBE === "1") {
if (!shouldProbeRuntime) {
return;
}
lifecycleRevision += 1;
const currentRevision = lifecycleRevision;
void (async () => {
try {
await runtime?.probeAvailability();
if (currentRevision !== lifecycleRevision) {
return;
}
if (runtime?.isHealthy()) {
ctx.logger.info("embedded acpx runtime backend ready");
return;
}
const doctorReport = await runtime?.doctor?.();
if (currentRevision !== lifecycleRevision) {
return;
}
ctx.logger.warn(
`embedded acpx runtime backend probe failed: ${doctorReport ? formatDoctorFailureMessage(doctorReport) : "backend remained unhealthy after probe"}`,
);
} catch (err) {
if (currentRevision !== lifecycleRevision) {
return;
}
ctx.logger.warn(`embedded acpx runtime setup failed: ${formatErrorMessage(err)}`);
try {
if (runtime) {
await withStartupProbeTimeout({
promise: runtime.probeAvailability(),
timeoutSeconds: pluginConfig.timeoutSeconds ?? DEFAULT_ACPX_TIMEOUT_SECONDS,
});
}
})();
if (currentRevision !== lifecycleRevision) {
return;
}
if (runtime?.isHealthy()) {
ctx.logger.info("embedded acpx runtime backend ready");
return;
}
const doctorReport = await runtime?.doctor?.();
if (currentRevision !== lifecycleRevision) {
return;
}
ctx.logger.warn(
`embedded acpx runtime backend probe failed: ${doctorReport ? formatDoctorFailureMessage(doctorReport) : "backend remained unhealthy after probe"}`,
);
} catch (err) {
if (currentRevision !== lifecycleRevision) {
return;
}
ctx.logger.warn(`embedded acpx runtime setup failed: ${formatErrorMessage(err)}`);
}
},
async stop(_ctx: OpenClawPluginServiceContext): Promise<void> {
lifecycleRevision += 1;

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/alibaba-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Alibaba Model Studio video provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/amazon-bedrock-mantle-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Amazon Bedrock Mantle (OpenAI-compatible) provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/amazon-bedrock-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Amazon Bedrock provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/anthropic-vertex-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Anthropic Vertex provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/anthropic-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Anthropic provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/arcee-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Arcee provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/azure-speech",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Azure Speech plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/bonjour",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Bonjour/mDNS gateway discovery",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Brave plugin",
"repository": {
"type": "git",
@@ -20,10 +20,10 @@
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/browser-plugin",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw browser tool plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/byteplus-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw BytePlus provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/canvas-plugin",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Canvas plugin",
"type": "module",

View File

@@ -1 +1 @@
ec8b8421bc9c316712350ffe4ea0ab284768142190fcb90cb942ca4c326f2395
48d8566335ac7073480e66b0f5c6ad22788f19b7f0a1d5e4558eb7682a45bcce

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/cerebras-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Cerebras provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/chutes-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Chutes.ai provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/clickclack",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw ClickClack channel plugin",
"type": "module",
@@ -17,7 +17,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/cloudflare-ai-gateway-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Cloudflare AI Gateway provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/codex",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Codex harness and model provider plugin",
"repository": {
"type": "git",
@@ -26,10 +26,10 @@
"minHostVersion": ">=2026.5.1-beta.1"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -0,0 +1,71 @@
import { describe, expect, it } from "vitest";
import {
assertCodexThreadStartResponse,
assertCodexThreadResumeResponse,
} from "./protocol-validators.js";
function makeMinimalThread(overrides: Record<string, unknown> = {}) {
return {
id: "thread-1",
sessionId: "session-1",
cliVersion: "0.129.0",
createdAt: 1715299200,
updatedAt: 1715299200,
cwd: "/tmp",
ephemeral: false,
modelProvider: "openai",
preview: "test thread",
source: "appServer",
status: { type: "notLoaded" },
turns: [],
...overrides,
};
}
function makeMinimalResponse(threadOverrides: Record<string, unknown> = {}) {
return {
approvalPolicy: "never",
approvalsReviewer: "user",
cwd: "/tmp",
model: "gpt-5.4",
modelProvider: "openai",
sandbox: { type: "dangerFullAccess" },
thread: makeMinimalThread(threadOverrides),
};
}
describe("assertCodexThreadStartResponse", () => {
it("accepts response with both id and sessionId", () => {
const response = makeMinimalResponse();
const result = assertCodexThreadStartResponse(response);
expect(result.thread).toMatchObject({ id: "thread-1", sessionId: "session-1" });
});
it("normalizes missing sessionId from id", () => {
const response = makeMinimalResponse({ sessionId: undefined });
// Remove the sessionId key entirely
delete (response.thread as Record<string, unknown>).sessionId;
const result = assertCodexThreadStartResponse(response);
expect(result.thread).toMatchObject({ id: "thread-1", sessionId: "thread-1" });
});
it("normalizes missing id from sessionId", () => {
const response = makeMinimalResponse({ id: undefined, sessionId: "session-1" });
delete (response.thread as Record<string, unknown>).id;
const result = assertCodexThreadStartResponse(response);
expect(result.thread).toMatchObject({ id: "session-1", sessionId: "session-1" });
});
it("throws on invalid response", () => {
expect(() => assertCodexThreadStartResponse({})).toThrow("Invalid Codex app-server");
});
});
describe("assertCodexThreadResumeResponse", () => {
it("normalizes missing sessionId from id", () => {
const response = makeMinimalResponse({ sessionId: undefined });
delete (response.thread as Record<string, unknown>).sessionId;
const result = assertCodexThreadResumeResponse(response);
expect(result.thread).toMatchObject({ id: "thread-1", sessionId: "thread-1" });
});
});

View File

@@ -43,11 +43,19 @@ const validateTurnCompletedNotification = ajv.compile<CodexTurnCompletedNotifica
const validateTurnStartResponse = ajv.compile<CodexTurnStartResponse>(turnStartResponseSchema);
export function assertCodexThreadStartResponse(value: unknown): CodexThreadStartResponse {
return assertCodexShape(validateThreadStartResponse, value, "thread/start response");
return assertCodexShape(
validateThreadStartResponse,
normalizeThreadResponse(value),
"thread/start response",
);
}
export function assertCodexThreadResumeResponse(value: unknown): CodexThreadResumeResponse {
return assertCodexShape(validateThreadResumeResponse, value, "thread/resume response");
return assertCodexShape(
validateThreadResumeResponse,
normalizeThreadResponse(value),
"thread/resume response",
);
}
export function assertCodexTurnStartResponse(value: unknown): CodexTurnStartResponse {
@@ -140,6 +148,23 @@ function normalizeThreadItem(value: unknown): unknown {
}
}
function normalizeThreadResponse(value: unknown): unknown {
if (!value || typeof value !== "object" || Array.isArray(value) || !("thread" in value)) {
return value;
}
const thread = (value as { thread?: unknown }).thread;
if (thread && typeof thread === "object" && !Array.isArray(thread)) {
const t = thread as { id?: string; sessionId?: string };
if (typeof t.id === "string" && typeof t.sessionId !== "string") {
return { ...value, thread: { ...thread, sessionId: t.id } };
}
if (typeof t.sessionId === "string" && typeof t.id !== "string") {
return { ...value, thread: { ...thread, id: t.sessionId } };
}
}
return value;
}
function normalizeTurnStartResponse(value: unknown): unknown {
if (!value || typeof value !== "object" || Array.isArray(value) || !("turn" in value)) {
return value;

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/comfy-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw ComfyUI provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/copilot-proxy",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Copilot Proxy provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/deepgram-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Deepgram media-understanding provider",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/deepinfra-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw DeepInfra provider plugin",
"type": "module",

View File

@@ -45,7 +45,10 @@ export default defineSingleProviderPluginEntry({
}),
matchesContextOverflowError: ({ errorMessage }) =>
/\bdeepseek\b.*(?:input.*too long|context.*exceed)/i.test(errorMessage),
...buildProviderReplayFamilyHooks({ family: "openai-compatible" }),
...buildProviderReplayFamilyHooks({
family: "openai-compatible",
dropReasoningFromHistory: false,
}),
wrapStreamFn: (ctx) => createDeepSeekV4ThinkingWrapper(ctx.streamFn, ctx.thinkingLevel),
resolveThinkingProfile: ({ modelId }) => resolveDeepSeekV4ThinkingProfile(modelId),
isModernModelRef: ({ modelId }) => Boolean(resolveDeepSeekV4ThinkingProfile(modelId)),

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/deepseek-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw DeepSeek provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diagnostics-otel",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw diagnostics OpenTelemetry exporter",
"repository": {
"type": "git",
@@ -34,10 +34,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diagnostics-prometheus",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw diagnostics Prometheus exporter",
"repository": {
"type": "git",
@@ -21,10 +21,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diffs",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw diff viewer plugin",
"repository": {
"type": "git",
@@ -30,10 +30,10 @@
"minHostVersion": ">=2026.4.30"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1",
"openclawVersion": "2026.5.10-beta.2",
"staticAssets": [
{
"source": "./assets/viewer-runtime.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/discord",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Discord channel plugin",
"repository": {
"type": "git",
@@ -21,7 +21,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -65,10 +65,10 @@
"allowInvalidConfigRecovery": true
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,19 +1,36 @@
import { Readable } from "node:stream";
import { describe, expect, it } from "vitest";
import { decodeOpusStream } from "./audio.js";
import { decodeOpusStream, resolveOpusDecoderPreference } from "./audio.js";
describe("discord voice opus decoder selection", () => {
it("prefers the pure-JS opusscript decoder over optional native opus", async () => {
it("defaults to the pure-JS opusscript decoder", async () => {
const verbose: string[] = [];
const warnings: string[] = [];
const previousPreference = process.env.OPENCLAW_DISCORD_OPUS_DECODER;
delete process.env.OPENCLAW_DISCORD_OPUS_DECODER;
const decoded = await decodeOpusStream(Readable.from([]), {
onVerbose: (message) => verbose.push(message),
onWarn: (message) => warnings.push(message),
});
try {
const decoded = await decodeOpusStream(Readable.from([]), {
onVerbose: (message) => verbose.push(message),
onWarn: (message) => warnings.push(message),
});
expect(decoded.length).toBe(0);
expect(verbose).toContain("opus decoder: opusscript");
expect(warnings).toEqual([]);
expect(decoded.length).toBe(0);
expect(verbose).toContain("opus decoder: opusscript");
expect(warnings).toEqual([]);
} finally {
if (previousPreference === undefined) {
delete process.env.OPENCLAW_DISCORD_OPUS_DECODER;
} else {
process.env.OPENCLAW_DISCORD_OPUS_DECODER = previousPreference;
}
}
});
it("requires an explicit preference for native opus", () => {
expect(resolveOpusDecoderPreference()).toBe("opusscript");
expect(resolveOpusDecoderPreference("opusscript")).toBe("opusscript");
expect(resolveOpusDecoderPreference("native")).toBe("native");
expect(resolveOpusDecoderPreference("@discordjs/opus")).toBe("native");
});
});

View File

@@ -21,6 +21,8 @@ type OpusDecoderFactory = {
name: string;
};
type OpusDecoderPreference = "native" | "opusscript";
let warnedOpusMissing = false;
let cachedOpusDecoderFactory: OpusDecoderFactory | null | "unresolved" = "unresolved";
@@ -47,32 +49,34 @@ function buildWavBuffer(pcm: Buffer): Buffer {
function resolveOpusDecoderFactory(params: {
onWarn: (message: string) => void;
}): OpusDecoderFactory | null {
const factories: OpusDecoderFactory[] = [
{
name: "opusscript",
load: () => {
const OpusScript = require("opusscript") as {
new (sampleRate: number, channels: number, application: number): OpusDecoder;
Application: { AUDIO: number };
const nativeFactory: OpusDecoderFactory = {
name: "@discordjs/opus",
load: () => {
const DiscordOpus = require("@discordjs/opus") as {
OpusEncoder: new (
sampleRate: number,
channels: number,
) => {
decode: (buffer: Buffer) => Buffer;
};
return new OpusScript(SAMPLE_RATE, CHANNELS, OpusScript.Application.AUDIO);
},
};
return new DiscordOpus.OpusEncoder(SAMPLE_RATE, CHANNELS);
},
{
name: "@discordjs/opus",
load: () => {
const DiscordOpus = require("@discordjs/opus") as {
OpusEncoder: new (
sampleRate: number,
channels: number,
) => {
decode: (buffer: Buffer) => Buffer;
};
};
return new DiscordOpus.OpusEncoder(SAMPLE_RATE, CHANNELS);
},
};
const opusscriptFactory: OpusDecoderFactory = {
name: "opusscript",
load: () => {
const OpusScript = require("opusscript") as {
new (sampleRate: number, channels: number, application: number): OpusDecoder;
Application: { AUDIO: number };
};
return new OpusScript(SAMPLE_RATE, CHANNELS, OpusScript.Application.AUDIO);
},
];
};
const factories: OpusDecoderFactory[] =
resolveOpusDecoderPreference() === "native"
? [nativeFactory, opusscriptFactory]
: [opusscriptFactory, nativeFactory];
const failures: string[] = [];
for (const factory of factories) {
@@ -93,6 +97,16 @@ function resolveOpusDecoderFactory(params: {
return null;
}
export function resolveOpusDecoderPreference(
value = process.env.OPENCLAW_DISCORD_OPUS_DECODER,
): OpusDecoderPreference {
const normalized = value?.trim().toLowerCase();
if (normalized === "native" || normalized === "@discordjs/opus") {
return "native";
}
return "opusscript";
}
function getOrCreateOpusDecoderFactory(params: {
onWarn: (message: string) => void;
}): OpusDecoderFactory | null {

View File

@@ -1,4 +1,4 @@
import type { Readable } from "node:stream";
import { PassThrough, type Readable } from "node:stream";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { ChannelType } from "../internal/discord.js";
import { createVoiceCaptureState } from "./capture-state.js";
@@ -1223,9 +1223,70 @@ describe("DiscordVoiceManager", () => {
);
});
it("skips incomplete and non-actionable forced agent-proxy transcripts", async () => {
agentCommandMock.mockResolvedValueOnce({ payloads: [{ text: "valid answer" }] });
const manager = createManager({
groupPolicy: "open",
voice: {
enabled: true,
mode: "agent-proxy",
realtime: { provider: "openai" },
},
});
await manager.join({ guildId: "g1", channelId: "1001" });
const entry = getSessionEntry(manager) as {
realtime?: {
beginSpeakerTurn: (
context: { extraSystemPrompt?: string; senderIsOwner: boolean; speakerLabel: string },
userId: string,
) => { close: () => void; sendInputAudio: (audio: Buffer) => void };
};
};
const bridgeParams = createRealtimeVoiceBridgeSessionMock.mock.calls.at(-1)?.[0] as
| {
onTranscript?: (role: "user" | "assistant", text: string, isFinal: boolean) => void;
}
| undefined;
const incompleteTurn = entry.realtime?.beginSpeakerTurn(
{ extraSystemPrompt: undefined, senderIsOwner: true, speakerLabel: "Owner" },
"u-owner",
);
incompleteTurn?.sendInputAudio(Buffer.alloc(8));
bridgeParams?.onTranscript?.("user", "Get this working and...", true);
const closingTurn = entry.realtime?.beginSpeakerTurn(
{ extraSystemPrompt: undefined, senderIsOwner: true, speakerLabel: "Owner" },
"u-owner",
);
closingTurn?.sendInputAudio(Buffer.alloc(8));
bridgeParams?.onTranscript?.("user", "I'll be right back. See you guys. Bye-bye.", true);
await new Promise((resolve) => setTimeout(resolve, 260));
expect(agentCommandMock).not.toHaveBeenCalled();
const validTurn = entry.realtime?.beginSpeakerTurn(
{ extraSystemPrompt: undefined, senderIsOwner: true, speakerLabel: "Owner" },
"u-owner",
);
validTurn?.sendInputAudio(Buffer.alloc(8));
bridgeParams?.onTranscript?.("user", "ship it.", true);
await new Promise((resolve) => setTimeout(resolve, 260));
expect(agentCommandMock).toHaveBeenCalledWith(
expect.objectContaining({ message: expect.stringContaining("ship it.") }),
expect.anything(),
);
expect(realtimeSessionMock.sendUserMessage).toHaveBeenCalledWith(
expect.stringContaining("valid answer"),
);
});
it("queues forced agent-proxy answers until current realtime playback idles", async () => {
let resolveFirst: ((value: { payloads: Array<{ text: string }> }) => void) | undefined;
let resolveSecond: ((value: { payloads: Array<{ text: string }> }) => void) | undefined;
let resolveThird: ((value: { payloads: Array<{ text: string }> }) => void) | undefined;
agentCommandMock
.mockImplementationOnce(
() =>
@@ -1238,6 +1299,12 @@ describe("DiscordVoiceManager", () => {
new Promise<{ payloads: Array<{ text: string }> }>((resolve) => {
resolveSecond = resolve;
}),
)
.mockImplementationOnce(
() =>
new Promise<{ payloads: Array<{ text: string }> }>((resolve) => {
resolveThird = resolve;
}),
);
const manager = createManager({
groupPolicy: "open",
@@ -1263,6 +1330,7 @@ describe("DiscordVoiceManager", () => {
const bridgeParams = createRealtimeVoiceBridgeSessionMock.mock.calls.at(-1)?.[0] as
| {
audioSink?: { sendAudio: (audio: Buffer) => void };
onEvent?: (event: { direction: "server"; type: string }) => void;
onTranscript?: (role: "user" | "assistant", text: string, isFinal: boolean) => void;
}
| undefined;
@@ -1279,6 +1347,12 @@ describe("DiscordVoiceManager", () => {
);
secondTurn?.sendInputAudio(Buffer.alloc(8));
bridgeParams?.onTranscript?.("user", "second question", true);
const thirdTurn = entry.realtime?.beginSpeakerTurn(
{ extraSystemPrompt: undefined, senderIsOwner: true, speakerLabel: "Owner" },
"u-owner",
);
thirdTurn?.sendInputAudio(Buffer.alloc(8));
bridgeParams?.onTranscript?.("user", "third question", true);
await new Promise((resolve) => setTimeout(resolve, 260));
resolveFirst?.({ payloads: [{ text: "first answer" }] });
@@ -1290,6 +1364,18 @@ describe("DiscordVoiceManager", () => {
bridgeParams?.audioSink?.sendAudio(Buffer.alloc(480));
resolveSecond?.({ payloads: [{ text: "second answer" }] });
resolveThird?.({ payloads: [{ text: "third answer" }] });
await new Promise((resolve) => setTimeout(resolve, 0));
expect(realtimeSessionMock.sendUserMessage).not.toHaveBeenCalledWith(
expect.stringContaining("second answer"),
);
expect(realtimeSessionMock.sendUserMessage).not.toHaveBeenCalledWith(
expect.stringContaining("third answer"),
);
bridgeParams?.onEvent?.({ direction: "server", type: "response.done" });
const firstStream = createAudioResourceMock.mock.calls.at(-1)?.[0] as PassThrough | undefined;
await vi.waitFor(() => expect(firstStream?.writableEnded).toBe(true));
await new Promise((resolve) => setTimeout(resolve, 0));
expect(realtimeSessionMock.sendUserMessage).not.toHaveBeenCalledWith(
expect.stringContaining("second answer"),
@@ -1302,6 +1388,23 @@ describe("DiscordVoiceManager", () => {
expect(realtimeSessionMock.sendUserMessage).toHaveBeenCalledWith(
expect.stringContaining("second answer"),
);
expect(realtimeSessionMock.sendUserMessage).not.toHaveBeenCalledWith(
expect.stringContaining("third answer"),
);
bridgeParams?.audioSink?.sendAudio(Buffer.alloc(480));
bridgeParams?.onEvent?.({ direction: "server", type: "response.done" });
const secondStream = createAudioResourceMock.mock.calls.at(-1)?.[0] as PassThrough | undefined;
await vi.waitFor(() => expect(secondStream?.writableEnded).toBe(true));
await new Promise((resolve) => setTimeout(resolve, 0));
expect(realtimeSessionMock.sendUserMessage).not.toHaveBeenCalledWith(
expect.stringContaining("third answer"),
);
idleHandler?.();
expect(realtimeSessionMock.sendUserMessage).toHaveBeenCalledWith(
expect.stringContaining("third answer"),
);
});
it("matches agent-proxy consult tool calls to the pending transcript", async () => {

View File

@@ -46,6 +46,35 @@ const DISCORD_REALTIME_LOG_PREVIEW_CHARS = 500;
const DISCORD_REALTIME_DEFAULT_MIN_BARGE_IN_AUDIO_END_MS = 250;
const DISCORD_REALTIME_FORCED_CONSULT_FALLBACK_DELAY_MS = 200;
const REALTIME_PCM16_BYTES_PER_SAMPLE = 2;
const DISCORD_REALTIME_FORCED_CONSULT_TRAILING_FRAGMENT_WORDS = new Set([
"a",
"about",
"an",
"and",
"as",
"at",
"because",
"but",
"by",
"for",
"from",
"in",
"of",
"on",
"or",
"so",
"that",
"the",
"then",
"to",
"with",
]);
const DISCORD_REALTIME_VERBOSE_OMITTED_EVENTS = new Set([
"conversation.output_audio.delta",
"input_audio_buffer.append",
"response.audio.delta",
"response.output_audio.delta",
]);
export type DiscordVoiceMode = "stt-tts" | "agent-proxy" | "bidi";
@@ -123,6 +152,32 @@ function formatRealtimeInterruptionLog(event: RealtimeVoiceBridgeEvent): string
return undefined;
}
function shouldLogRealtimeVerboseEvent(event: RealtimeVoiceBridgeEvent): boolean {
return !DISCORD_REALTIME_VERBOSE_OMITTED_EVENTS.has(event.type);
}
function classifySkippableForcedAgentProxyTranscript(text: string): string | undefined {
const normalized = text.replace(/\s+/g, " ").trim().toLowerCase();
if (!normalized) {
return "empty";
}
if (/(\.\.\.|…)\s*$/.test(normalized)) {
return "incomplete-transcript";
}
const lastWord = normalized.match(/[a-z']+$/)?.[0]?.replace(/^'+|'+$/g, "");
if (lastWord && DISCORD_REALTIME_FORCED_CONSULT_TRAILING_FRAGMENT_WORDS.has(lastWord)) {
return "trailing-fragment";
}
if (
!normalized.includes("?") &&
(/^(i'?ll|i will) be (right )?back\b/.test(normalized) ||
/\b(see you|bye(?:-bye)?|goodbye)\b/.test(normalized))
) {
return "non-actionable-closing";
}
return undefined;
}
function readProviderConfigString(
config: RealtimeVoiceProviderConfig,
key: string,
@@ -381,7 +436,9 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
onToolCall: (event, session) => this.handleToolCall(event, session),
onEvent: (event) => {
const detail = event.detail ? ` ${event.detail}` : "";
logVoiceVerbose(`realtime ${event.direction}:${event.type}${detail}`);
if (shouldLogRealtimeVerboseEvent(event)) {
logVoiceVerbose(`realtime ${event.direction}:${event.type}${detail}`);
}
const responseEnded =
event.direction === "server" &&
(event.type === "response.done" || event.type === "response.cancelled");
@@ -539,6 +596,12 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
return;
}
this.syncOutputAudioTimestamp();
if (this.outputStreamEnding) {
logVoiceVerbose(
`realtime output audio ignored after stream ending: guild ${this.params.entry.guildId} channel ${this.params.entry.channelId}`,
);
return;
}
const stream = this.ensureOutputStream();
if (this.exactSpeechResponseActive) {
this.exactSpeechAudioStarted = true;
@@ -554,7 +617,7 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
}
private ensureOutputStream(): PassThrough {
if (this.outputStream && !this.outputStream.destroyed) {
if (this.outputStream && !this.outputStream.destroyed && !this.outputStream.writableEnded) {
return this.outputStream;
}
const voiceSdk = loadDiscordVoiceSdk();
@@ -566,7 +629,7 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
this.logOutputAudioStopped("stream-close");
this.outputStream = null;
this.resetOutputAudioStats();
this.completeExactSpeechResponse("stream-close");
this.completeExactSpeechResponse("stream-close", { drain: false });
}
});
const resource = voiceSdk.createAudioResource(stream, {
@@ -629,12 +692,15 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
this.bridge?.sendUserMessage(buildDiscordSpeakExactUserMessage(text));
}
private completeExactSpeechResponse(reason: string): void {
private completeExactSpeechResponse(reason: string, options?: { drain?: boolean }): void {
if (!this.exactSpeechResponseActive && this.queuedExactSpeechMessages.length === 0) {
return;
}
this.exactSpeechResponseActive = false;
this.exactSpeechAudioStarted = false;
if (options?.drain === false) {
return;
}
this.drainQueuedExactSpeechMessages(reason);
}
@@ -811,6 +877,13 @@ export class DiscordRealtimeVoiceSession implements VoiceRealtimeSession {
return;
}
const context = this.consumePendingSpeakerContext();
const skipReason = classifySkippableForcedAgentProxyTranscript(question);
if (skipReason) {
logger.info(
`discord voice: realtime forced agent consult skipped reason=${skipReason} chars=${question.length} speaker=${context?.speakerLabel ?? "unknown"} transcript=${formatRealtimeLogPreview(question)}`,
);
return;
}
if (!context) {
const recent = this.findRecentAgentProxyConsultContext(question);
if (recent) {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/document-extract-plugin",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw local document extraction plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/duckduckgo-plugin",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw DuckDuckGo plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/elevenlabs-speech",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw ElevenLabs speech plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/exa-plugin",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Exa plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/fal-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw fal provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/feishu",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -47,10 +47,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/file-transfer",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw file transfer plugin (file_fetch, dir_list, dir_fetch, file_write)",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/firecrawl-plugin",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Firecrawl plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/fireworks-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Fireworks provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/github-copilot-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw GitHub Copilot provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/google-meet",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Google Meet participant plugin",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -33,10 +33,10 @@
"minHostVersion": ">=2026.4.20"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/google-plugin",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Google plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/googlechat",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Google Chat channel plugin",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -74,10 +74,10 @@
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/gradium-speech",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Gradium speech plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/groq-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Groq media-understanding provider",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/huggingface-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Hugging Face provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/image-generation-core",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw image generation runtime package",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/imessage",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw iMessage channel plugin using imsg on a signed-in Mac",
"type": "module",
@@ -40,10 +40,10 @@
]
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
}
},
"pluginInspector": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/inworld-speech",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Inworld speech plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/irc",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw IRC channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/kilocode-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Kilo Gateway provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/kimi-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Kimi provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/line",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw LINE channel plugin",
"repository": {
"type": "git",
@@ -15,7 +15,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -45,10 +45,10 @@
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/litellm-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw LiteLLM provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/llm-task",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw JSON-only LLM task plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/lmstudio-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw LM Studio provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/lobster",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
"repository": {
"type": "git",
@@ -25,10 +25,10 @@
"minHostVersion": ">=2026.4.25"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/matrix",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Matrix channel plugin",
"repository": {
"type": "git",
@@ -21,7 +21,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -86,10 +86,10 @@
"allowInvalidConfigRecovery": true
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/mattermost",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Mattermost channel plugin",
"repository": {
"type": "git",
@@ -15,7 +15,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/media-understanding-core",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw media understanding runtime package",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/memory-core",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw core memory search plugin",
"type": "module",
@@ -14,7 +14,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/memory-lancedb",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture",
"repository": {
"type": "git",
@@ -26,10 +26,10 @@
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/memory-wiki",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw persistent wiki plugin",
"type": "module",
@@ -13,7 +13,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/microsoft-foundry",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Microsoft Foundry provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/microsoft-speech",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Microsoft speech plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/migrate-claude",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "Claude to OpenClaw migration provider",
"type": "module",
@@ -9,7 +9,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/migrate-hermes",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "Hermes to OpenClaw migration provider",
"type": "module",
@@ -12,7 +12,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/minimax-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw MiniMax provider and OAuth plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/mistral-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Mistral provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/moonshot-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Moonshot provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/msteams",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Microsoft Teams channel plugin",
"repository": {
"type": "git",
@@ -22,7 +22,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -58,10 +58,10 @@
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/nextcloud-talk",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Nextcloud Talk channel plugin",
"repository": {
"type": "git",
@@ -12,7 +12,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -44,10 +44,10 @@
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/nostr",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"description": "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs",
"repository": {
"type": "git",
@@ -15,7 +15,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {
@@ -53,10 +53,10 @@
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.5.10-beta.1"
"pluginApi": ">=2026.5.10-beta.2"
},
"build": {
"openclawVersion": "2026.5.10-beta.1"
"openclawVersion": "2026.5.10-beta.2"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/nvidia-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw NVIDIA provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/oc-path",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw oc:// workspace path plugin",
"type": "module",
@@ -14,7 +14,7 @@
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.5.10-beta.1"
"openclaw": ">=2026.5.10-beta.2"
},
"peerDependenciesMeta": {
"openclaw": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/ollama-provider",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenClaw Ollama provider plugin",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/open-prose",
"version": "2026.5.10-beta.1",
"version": "2026.5.10-beta.2",
"private": true,
"description": "OpenProse VM skill pack plugin (slash command + telemetry).",
"type": "module",

Some files were not shown because too many files have changed in this diff Show More