mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 14:01:24 +08:00
Compare commits
33 Commits
codex/mark
...
v2026.5.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8287402da7 | ||
|
|
2dee06f7af | ||
|
|
d094fc79f3 | ||
|
|
2a609f8a7b | ||
|
|
df99775c5d | ||
|
|
b99398e807 | ||
|
|
0e6e63b20b | ||
|
|
1b204f2504 | ||
|
|
f66b1d1738 | ||
|
|
1205a9a58a | ||
|
|
a5b2efa336 | ||
|
|
587bb5d34b | ||
|
|
01af462223 | ||
|
|
d39cec9510 | ||
|
|
720e51202c | ||
|
|
6edf77c65f | ||
|
|
964e944de5 | ||
|
|
ed40ef0a68 | ||
|
|
e5296a2401 | ||
|
|
aa9af608a1 | ||
|
|
410efd9850 | ||
|
|
9c7e67b0f8 | ||
|
|
fcdbaaff2b | ||
|
|
638dea598c | ||
|
|
56d192be06 | ||
|
|
31d78ca77c | ||
|
|
9877a614f8 | ||
|
|
e8920158f0 | ||
|
|
85a0f1c018 | ||
|
|
8541f69f89 | ||
|
|
a14e0aefe7 | ||
|
|
3f04632448 | ||
|
|
e0d48a8913 |
10
.github/workflows/full-release-validation.yml
vendored
10
.github/workflows/full-release-validation.yml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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:
|
||||
|
||||
44
.github/workflows/openclaw-npm-release.yml
vendored
44
.github/workflows/openclaw-npm-release.yml
vendored
@@ -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
|
||||
|
||||
21
.github/workflows/openclaw-release-checks.yml
vendored
21
.github/workflows/openclaw-release-checks.yml
vendored
@@ -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 }}
|
||||
|
||||
100
.github/workflows/openclaw-release-publish.yml
vendored
100
.github/workflows/openclaw-release-publish.yml
vendored
@@ -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
|
||||
|
||||
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=...`
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)**
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1 +1 @@
|
||||
ec8b8421bc9c316712350ffe4ea0ab284768142190fcb90cb942ca4c326f2395
|
||||
48d8566335ac7073480e66b0f5c6ad22788f19b7f0a1d5e4558eb7682a45bcce
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
71
extensions/codex/src/app-server/protocol-validators.test.ts
Normal file
71
extensions/codex/src/app-server/protocol-validators.test.ts
Normal 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" });
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user