mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
2549 lines
120 KiB
YAML
2549 lines
120 KiB
YAML
name: OpenClaw Live And E2E Checks (Reusable)
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
ref:
|
|
description: Ref, tag, or SHA to validate
|
|
required: true
|
|
default: main
|
|
type: string
|
|
include_repo_e2e:
|
|
description: Whether to run pnpm test:e2e plus repo-specific extra E2E lanes
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
include_release_path_suites:
|
|
description: Whether to run the Docker release-path suites
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
include_openwebui:
|
|
description: Whether to run the Open WebUI Docker smoke
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
docker_lanes:
|
|
description: Comma/space separated Docker scheduler lane names to run against the prepared image
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
targeted_docker_lane_group_size:
|
|
description: Number of targeted Docker lanes to batch into one runner job
|
|
required: false
|
|
default: 1
|
|
type: number
|
|
published_upgrade_survivor_baseline:
|
|
description: Published OpenClaw package baseline for the published-upgrade-survivor/update-migration Docker lanes
|
|
required: false
|
|
default: openclaw@latest
|
|
type: string
|
|
published_upgrade_survivor_baselines:
|
|
description: Optional exact baseline list for published-upgrade-survivor/update-migration lane expansion
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
published_upgrade_survivor_scenarios:
|
|
description: Optional scenario list for published-upgrade-survivor/update-migration lane expansion
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_name:
|
|
description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_run_id:
|
|
description: Prior run id containing package_artifact_name; blank uses this run or packs the selected ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
docker_e2e_bare_image:
|
|
description: Existing bare Docker E2E image to reuse; blank derives from package SHA/ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
docker_e2e_functional_image:
|
|
description: Existing functional Docker E2E image to reuse; blank derives from package SHA/ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
include_live_suites:
|
|
description: Whether to run live-provider coverage
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
live_models_only:
|
|
description: Whether to run only the Docker live model matrix when live suites are enabled
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
live_model_providers:
|
|
description: Comma/space separated provider ids for the Docker live model matrix; blank runs all providers
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
live_suite_filter:
|
|
description: Optional exact live suite id to run for focused failed-shard recovery; blank runs all selected suites
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
release_test_profile:
|
|
description: Release coverage profile for live/Docker/provider breadth
|
|
required: false
|
|
default: stable
|
|
type: choice
|
|
options:
|
|
- minimum
|
|
- stable
|
|
- full
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: Ref, tag, or SHA to validate
|
|
required: true
|
|
type: string
|
|
include_repo_e2e:
|
|
description: Whether to run pnpm test:e2e
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
include_release_path_suites:
|
|
description: Whether to run the Docker release-path suites
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
include_openwebui:
|
|
description: Whether to run the Open WebUI Docker smoke
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
docker_lanes:
|
|
description: Comma/space separated Docker scheduler lane names to run against the prepared image
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
targeted_docker_lane_group_size:
|
|
description: Number of targeted Docker lanes to batch into one runner job
|
|
required: false
|
|
default: 1
|
|
type: number
|
|
published_upgrade_survivor_baseline:
|
|
description: Published OpenClaw package baseline for the published-upgrade-survivor/update-restart-auth/update-migration Docker lanes
|
|
required: false
|
|
default: openclaw@latest
|
|
type: string
|
|
published_upgrade_survivor_baselines:
|
|
description: Optional exact baseline list for published-upgrade-survivor/update-migration lane expansion
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
published_upgrade_survivor_scenarios:
|
|
description: Optional scenario list for published-upgrade-survivor/update-migration lane expansion
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_name:
|
|
description: Existing workflow artifact containing openclaw-current.tgz; blank packs the selected ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
package_artifact_run_id:
|
|
description: Prior run id containing package_artifact_name; blank uses this run or packs the selected ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
docker_e2e_bare_image:
|
|
description: Existing bare Docker E2E image to reuse; blank derives from package SHA/ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
docker_e2e_functional_image:
|
|
description: Existing functional Docker E2E image to reuse; blank derives from package SHA/ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
include_live_suites:
|
|
description: Whether to run live-provider coverage
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
live_models_only:
|
|
description: Whether to run only the Docker live model matrix when live suites are enabled
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
live_model_providers:
|
|
description: Comma/space separated provider ids for the Docker live model matrix; blank runs all providers
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
live_suite_filter:
|
|
description: Optional exact live suite id to run for focused failed-shard recovery; blank runs all selected suites
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
release_test_profile:
|
|
description: Release coverage profile for live/Docker/provider breadth
|
|
required: false
|
|
default: stable
|
|
type: string
|
|
secrets:
|
|
OPENAI_API_KEY:
|
|
required: false
|
|
OPENAI_BASE_URL:
|
|
required: false
|
|
ANTHROPIC_API_KEY:
|
|
required: false
|
|
ANTHROPIC_API_KEY_OLD:
|
|
required: false
|
|
ANTHROPIC_API_TOKEN:
|
|
required: false
|
|
BYTEPLUS_API_KEY:
|
|
required: false
|
|
CEREBRAS_API_KEY:
|
|
required: false
|
|
DEEPINFRA_API_KEY:
|
|
required: false
|
|
DASHSCOPE_API_KEY:
|
|
required: false
|
|
GROQ_API_KEY:
|
|
required: false
|
|
KIMI_API_KEY:
|
|
required: false
|
|
MODELSTUDIO_API_KEY:
|
|
required: false
|
|
MOONSHOT_API_KEY:
|
|
required: false
|
|
MISTRAL_API_KEY:
|
|
required: false
|
|
MINIMAX_API_KEY:
|
|
required: false
|
|
OPENCODE_API_KEY:
|
|
required: false
|
|
OPENCODE_ZEN_API_KEY:
|
|
required: false
|
|
OPENCLAW_LIVE_BROWSER_CDP_URL:
|
|
required: false
|
|
OPENCLAW_LIVE_SETUP_TOKEN:
|
|
required: false
|
|
OPENCLAW_LIVE_SETUP_TOKEN_MODEL:
|
|
required: false
|
|
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE:
|
|
required: false
|
|
OPENCLAW_LIVE_SETUP_TOKEN_VALUE:
|
|
required: false
|
|
GEMINI_API_KEY:
|
|
required: false
|
|
GOOGLE_API_KEY:
|
|
required: false
|
|
OPENROUTER_API_KEY:
|
|
required: false
|
|
QWEN_API_KEY:
|
|
required: false
|
|
FAL_KEY:
|
|
required: false
|
|
RUNWAY_API_KEY:
|
|
required: false
|
|
DEEPGRAM_API_KEY:
|
|
required: false
|
|
TOGETHER_API_KEY:
|
|
required: false
|
|
VYDRA_API_KEY:
|
|
required: false
|
|
XAI_API_KEY:
|
|
required: false
|
|
ZAI_API_KEY:
|
|
required: false
|
|
Z_AI_API_KEY:
|
|
required: false
|
|
BYTEPLUS_ACCESS_KEY_ID:
|
|
required: false
|
|
BYTEPLUS_SECRET_ACCESS_KEY:
|
|
required: false
|
|
CLAUDE_CODE_OAUTH_TOKEN:
|
|
required: false
|
|
OPENCLAW_CODEX_AUTH_JSON:
|
|
required: false
|
|
OPENCLAW_CODEX_CONFIG_TOML:
|
|
required: false
|
|
OPENCLAW_CLAUDE_JSON:
|
|
required: false
|
|
OPENCLAW_CLAUDE_CREDENTIALS_JSON:
|
|
required: false
|
|
OPENCLAW_CLAUDE_SETTINGS_JSON:
|
|
required: false
|
|
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON:
|
|
required: false
|
|
OPENCLAW_GEMINI_SETTINGS_JSON:
|
|
required: false
|
|
FIREWORKS_API_KEY:
|
|
required: false
|
|
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
pull-requests: read
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
NODE_VERSION: "24.x"
|
|
PNPM_VERSION: "10.32.1"
|
|
|
|
jobs:
|
|
validate_selected_ref:
|
|
runs-on: ubuntu-24.04
|
|
outputs:
|
|
selected_sha: ${{ steps.validate.outputs.selected_sha }}
|
|
trusted_reason: ${{ steps.validate.outputs.trusted_reason }}
|
|
steps:
|
|
- name: Checkout workflow repository
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Validate selected ref
|
|
id: validate
|
|
env:
|
|
INPUT_REF: ${{ inputs.ref }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
trusted_reason=""
|
|
|
|
git fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'
|
|
git fetch --tags origin '+refs/tags/*:refs/tags/*'
|
|
|
|
# Resolve here instead of in actions/checkout so short SHAs work too.
|
|
if ! selected_sha="$(git rev-parse --verify "${INPUT_REF}^{commit}")"; then
|
|
echo "Ref '${INPUT_REF}' could not be resolved to a commit." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if git merge-base --is-ancestor "$selected_sha" refs/remotes/origin/main; then
|
|
trusted_reason="main-ancestor"
|
|
elif git tag --points-at "$selected_sha" | grep -Eq '^v'; then
|
|
trusted_reason="release-tag"
|
|
elif git for-each-ref --format='%(refname:short)' --contains "$selected_sha" refs/remotes/origin | grep -Eq '^origin/'; then
|
|
trusted_reason="repository-branch-history"
|
|
else
|
|
trusted_reason=""
|
|
fi
|
|
|
|
if [[ -z "$trusted_reason" ]]; then
|
|
echo "Ref '${INPUT_REF}' resolved to $selected_sha, which is not trusted for secret-bearing live/E2E checks." >&2
|
|
echo "Allowed refs must be reachable from an OpenClaw branch or release tag." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "selected_sha=$selected_sha" >> "$GITHUB_OUTPUT"
|
|
echo "trusted_reason=$trusted_reason" >> "$GITHUB_OUTPUT"
|
|
{
|
|
echo "Validated ref: \`${INPUT_REF}\`"
|
|
echo "Resolved SHA: \`$selected_sha\`"
|
|
echo "Trust reason: \`$trusted_reason\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
validate_live_suite_filter:
|
|
runs-on: ubuntu-24.04
|
|
if: inputs.live_suite_filter != ''
|
|
steps:
|
|
- name: Validate focused live suite filter
|
|
shell: bash
|
|
env:
|
|
LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
|
|
RELEASE_TEST_PROFILE: ${{ inputs.release_test_profile }}
|
|
INCLUDE_REPO_E2E: ${{ inputs.include_repo_e2e }}
|
|
INCLUDE_LIVE_SUITES: ${{ inputs.include_live_suites }}
|
|
LIVE_MODELS_ONLY: ${{ inputs.live_models_only }}
|
|
LIVE_MODEL_PROVIDERS: ${{ inputs.live_model_providers }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
selected_suites=()
|
|
|
|
add_suite() {
|
|
selected_suites+=("$1")
|
|
}
|
|
|
|
add_profile_suite() {
|
|
local suite_id="$1"
|
|
local profiles="$2"
|
|
if [[ " ${profiles} " == *" ${RELEASE_TEST_PROFILE} "* ]]; then
|
|
add_suite "$suite_id"
|
|
fi
|
|
}
|
|
|
|
live_model_providers="${LIVE_MODEL_PROVIDERS//,/}"
|
|
live_model_providers="${live_model_providers//[[:space:]]/}"
|
|
|
|
if [[ "$INCLUDE_REPO_E2E" == "true" ]]; then
|
|
add_suite openshell-e2e
|
|
fi
|
|
|
|
if [[ "$INCLUDE_LIVE_SUITES" == "true" ]]; then
|
|
if [[ -n "$live_model_providers" ]]; then
|
|
add_suite docker-live-models
|
|
else
|
|
add_profile_suite docker-live-models "minimum stable full"
|
|
fi
|
|
|
|
if [[ "$LIVE_MODELS_ONLY" != "true" ]]; then
|
|
add_suite live-cache
|
|
add_suite openai-ws-stream-live-e2e
|
|
|
|
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-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-fireworks "full"
|
|
add_profile_suite native-live-src-gateway-profiles-deepseek "full"
|
|
add_profile_suite native-live-src-gateway-profiles-opencode-go "full"
|
|
add_profile_suite native-live-src-gateway-profiles-opencode-go-smoke "stable"
|
|
add_profile_suite native-live-src-gateway-profiles-openrouter "full"
|
|
add_profile_suite native-live-src-gateway-profiles-xai "full"
|
|
add_profile_suite native-live-src-gateway-profiles-zai "full"
|
|
add_profile_suite native-live-src-gateway-backends "stable full"
|
|
add_profile_suite native-live-src-infra "stable full"
|
|
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-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-anthropic-docker "stable full"
|
|
add_profile_suite live-gateway-google-docker "stable full"
|
|
add_profile_suite live-gateway-minimax-docker "stable full"
|
|
add_profile_suite live-gateway-advisory-docker "full"
|
|
add_profile_suite live-gateway-advisory-docker-deepseek-fireworks "full"
|
|
add_profile_suite live-gateway-advisory-docker-opencode-openrouter "full"
|
|
add_profile_suite live-gateway-advisory-docker-xai-zai "full"
|
|
add_profile_suite live-cli-backend-docker "stable full"
|
|
add_profile_suite live-acp-bind-docker "stable full"
|
|
add_profile_suite live-codex-harness-docker "stable full"
|
|
|
|
add_profile_suite native-live-extensions-a-k "full"
|
|
add_profile_suite native-live-extensions-media-audio "full"
|
|
add_profile_suite native-live-extensions-media-music-google "full"
|
|
add_profile_suite native-live-extensions-media-music-minimax "full"
|
|
add_profile_suite native-live-extensions-media-video "full"
|
|
fi
|
|
fi
|
|
|
|
for suite_id in "${selected_suites[@]}"; do
|
|
if [[ "$LIVE_SUITE_FILTER" == "$suite_id" ]]; then
|
|
echo "Focused live suite filter is valid: ${LIVE_SUITE_FILTER}"
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
echo "live_suite_filter '${LIVE_SUITE_FILTER}' does not match any runnable suite for release_test_profile='${RELEASE_TEST_PROFILE}' and the selected include flags." >&2
|
|
if [[ "${#selected_suites[@]}" -gt 0 ]]; then
|
|
printf 'Runnable focused suite ids: %s\n' "${selected_suites[*]}" >&2
|
|
else
|
|
echo "No focused live suites are runnable for the selected include flags." >&2
|
|
fi
|
|
exit 1
|
|
|
|
validate_release_live_cache:
|
|
needs: validate_selected_ref
|
|
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'live-cache')
|
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
|
timeout-minutes: 60
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
OPENCLAW_LIVE_CACHE_TEST: "1"
|
|
OPENCLAW_LIVE_TEST: "1"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Validate live cache credentials
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
|
echo "Missing OPENAI_API_KEY secret for live-cache validation." >&2
|
|
exit 1
|
|
fi
|
|
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
echo "Missing ANTHROPIC_API_KEY secret for live-cache validation." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Verify live prompt cache floors
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2 3; do
|
|
echo "live-cache attempt ${attempt}/3"
|
|
if pnpm test:live:cache; then
|
|
exit 0
|
|
fi
|
|
if [[ "$attempt" == "3" ]]; then
|
|
exit 1
|
|
fi
|
|
sleep $((attempt * 15))
|
|
done
|
|
|
|
validate_repo_e2e:
|
|
needs: validate_selected_ref
|
|
if: inputs.include_repo_e2e && inputs.live_suite_filter == ''
|
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
|
timeout-minutes: 90
|
|
env:
|
|
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Build dist for repo E2E
|
|
run: pnpm build
|
|
|
|
- name: Run repo E2E suite
|
|
run: pnpm test:e2e
|
|
|
|
validate_special_e2e:
|
|
needs: validate_selected_ref
|
|
if: (inputs.include_repo_e2e || (inputs.include_live_suites && !inputs.live_models_only)) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'openshell-e2e' || inputs.live_suite_filter == 'openai-ws-stream-live-e2e')
|
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
|
timeout-minutes: ${{ matrix.timeout_minutes }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- suite_id: openshell-e2e
|
|
label: OpenShell repo E2E
|
|
command: pnpm test:e2e:openshell
|
|
timeout_minutes: 120
|
|
requires_repo_e2e: true
|
|
requires_live_suites: false
|
|
- suite_id: openai-ws-stream-live-e2e
|
|
label: OpenAI WebSocket live E2E
|
|
command: pnpm test:e2e src/agents/openai-ws-stream.e2e.test.ts
|
|
timeout_minutes: 90
|
|
requires_repo_e2e: false
|
|
requires_live_suites: true
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENCLAW_E2E_WORKERS: "1"
|
|
OPENCLAW_VITEST_MAX_WORKERS: "1"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Build dist for special E2E
|
|
if: |
|
|
(
|
|
(inputs.include_repo_e2e && matrix.requires_repo_e2e) ||
|
|
(inputs.include_live_suites && matrix.requires_live_suites)
|
|
) &&
|
|
(inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id)
|
|
run: pnpm build
|
|
|
|
- name: Configure suite-specific env
|
|
if: inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "${{ matrix.suite_id }}" in
|
|
openai-ws-stream-live-e2e)
|
|
echo "OPENAI_LIVE_TEST=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_LIVE_TEST=1" >> "$GITHUB_ENV"
|
|
;;
|
|
esac
|
|
|
|
- name: Validate suite credentials
|
|
if: inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
case "${{ matrix.suite_id }}" in
|
|
openai-ws-stream-live-e2e)
|
|
[[ -n "${OPENAI_API_KEY:-}" ]] || {
|
|
echo "OPENAI_API_KEY is required for the OpenAI WebSocket live E2E suite." >&2
|
|
exit 1
|
|
}
|
|
;;
|
|
esac
|
|
|
|
- name: Run ${{ matrix.label }}
|
|
if: |
|
|
(
|
|
(inputs.include_repo_e2e && matrix.requires_repo_e2e) ||
|
|
(inputs.include_live_suites && matrix.requires_live_suites)
|
|
) &&
|
|
(inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id)
|
|
run: ${{ matrix.command }}
|
|
|
|
validate_docker_e2e:
|
|
needs: [validate_selected_ref, prepare_docker_e2e_image]
|
|
if: inputs.include_release_path_suites && inputs.docker_lanes == ''
|
|
name: Docker E2E (${{ matrix.label }})
|
|
runs-on: blacksmith-32vcpu-ubuntu-2404
|
|
timeout-minutes: ${{ matrix.timeout_minutes }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- chunk_id: core
|
|
label: core
|
|
timeout_minutes: 120
|
|
- chunk_id: package-update-openai
|
|
label: package/update OpenAI install
|
|
timeout_minutes: 180
|
|
- chunk_id: package-update-anthropic
|
|
label: package/update Anthropic install
|
|
timeout_minutes: 180
|
|
- chunk_id: package-update-core
|
|
label: package/update core
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-plugins
|
|
label: plugins/runtime plugins
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-services
|
|
label: plugins/runtime services
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-install-a
|
|
label: plugins/runtime install A
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-install-b
|
|
label: plugins/runtime install B
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-install-c
|
|
label: plugins/runtime install C
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-install-d
|
|
label: plugins/runtime install D
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-install-e
|
|
label: plugins/runtime install E
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-install-f
|
|
label: plugins/runtime install F
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-install-g
|
|
label: plugins/runtime install G
|
|
timeout_minutes: 120
|
|
- chunk_id: plugins-runtime-install-h
|
|
label: plugins/runtime install H
|
|
timeout_minutes: 120
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
|
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
|
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
|
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
|
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
|
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
|
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
|
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
|
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
|
FAL_KEY: ${{ secrets.FAL_KEY }}
|
|
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
|
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
|
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
|
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
|
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
|
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
|
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
|
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
|
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
|
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
|
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
|
|
OPENCLAW_DOCKER_E2E_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
|
|
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
|
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_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
|
|
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }}
|
|
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
|
|
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }}
|
|
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
|
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
|
DOCKER_E2E_CHUNK: ${{ matrix.chunk_id }}
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Checkout trusted release harness
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Log in to GHCR for shared Docker E2E image
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Hydrate live auth/profile inputs
|
|
run: bash scripts/ci-hydrate-live-auth.sh
|
|
|
|
- name: Plan Docker E2E chunk
|
|
id: plan
|
|
shell: bash
|
|
env:
|
|
CHUNK: ${{ matrix.chunk_id }}
|
|
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -z "$CHUNK" ]]; then
|
|
echo "chunk input is required for Docker E2E chunk planning." >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p .artifacts/docker-tests
|
|
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
|
export OPENCLAW_DOCKER_ALL_CHUNK="$CHUNK"
|
|
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI"
|
|
|
|
plan_path=".artifacts/docker-tests/release-${CHUNK}-plan.json"
|
|
node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path"
|
|
node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT"
|
|
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Download OpenClaw Docker E2E package
|
|
if: 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'
|
|
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'
|
|
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
|
|
shell: bash
|
|
env:
|
|
CREDENTIALS: ${{ steps.plan.outputs.credentials }}
|
|
run: |
|
|
set -euo pipefail
|
|
credentials=",$CREDENTIALS,"
|
|
if [[ "$credentials" == *",openai,"* ]]; then
|
|
[[ -n "${OPENAI_API_KEY:-}" ]] || {
|
|
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run Docker E2E chunk
|
|
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_BUILD=0
|
|
export OPENCLAW_DOCKER_ALL_PREFLIGHT=0
|
|
export OPENCLAW_DOCKER_ALL_FAIL_FAST=0
|
|
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="${INCLUDE_OPENWEBUI}"
|
|
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}"
|
|
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}-timings.json"
|
|
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
|
|
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
|
|
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
|
|
fi
|
|
|
|
node .release-harness/scripts/test-docker-all.mjs
|
|
|
|
- name: Summarize Docker E2E chunk
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
summary=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}/summary.json"
|
|
if [[ ! -f "$summary" ]]; then
|
|
echo "Docker chunk summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY"
|
|
exit 0
|
|
fi
|
|
node .release-harness/scripts/docker-e2e.mjs summary "$summary" "Docker E2E chunk: ${DOCKER_E2E_CHUNK:-unknown}" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload Docker E2E chunk artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: docker-e2e-${{ matrix.chunk_id }}
|
|
path: .artifacts/docker-tests/
|
|
if-no-files-found: ignore
|
|
|
|
plan_docker_lane_groups:
|
|
needs: validate_selected_ref
|
|
if: inputs.docker_lanes != ''
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
timeout-minutes: 5
|
|
outputs:
|
|
groups_json: ${{ steps.groups.outputs.groups_json }}
|
|
steps:
|
|
- name: Checkout trusted release harness
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Build targeted Docker lane groups
|
|
id: groups
|
|
shell: bash
|
|
env:
|
|
LANES: ${{ inputs.docker_lanes }}
|
|
GROUP_SIZE: ${{ inputs.targeted_docker_lane_group_size }}
|
|
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
|
|
run: |
|
|
set -euo pipefail
|
|
groups_json="$(node scripts/plan-targeted-docker-lane-groups.mjs)"
|
|
echo "groups_json=${groups_json}" >> "$GITHUB_OUTPUT"
|
|
|
|
validate_docker_lanes:
|
|
needs: [validate_selected_ref, prepare_docker_e2e_image, plan_docker_lane_groups]
|
|
if: inputs.docker_lanes != ''
|
|
name: Docker E2E targeted lanes (${{ matrix.group.label }})
|
|
runs-on: blacksmith-32vcpu-ubuntu-2404
|
|
timeout-minutes: 90
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
group: ${{ fromJson(needs.plan_docker_lane_groups.outputs.groups_json) }}
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
|
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
|
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
|
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
|
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
|
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
|
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
|
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
|
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
|
FAL_KEY: ${{ secrets.FAL_KEY }}
|
|
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
|
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
|
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
|
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
|
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
|
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
|
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
|
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
|
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
|
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
|
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
|
|
OPENCLAW_DOCKER_E2E_BARE_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
|
|
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
|
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_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: ${{ matrix.group.published_upgrade_survivor_baselines || inputs.published_upgrade_survivor_baselines }}
|
|
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }}
|
|
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
|
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
|
DOCKER_E2E_LANES: ${{ matrix.group.docker_lanes }}
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Checkout trusted release harness
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Log in to GHCR for shared Docker E2E image
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Hydrate live auth/profile inputs
|
|
run: bash scripts/ci-hydrate-live-auth.sh
|
|
|
|
- name: Plan targeted Docker E2E lanes
|
|
id: plan
|
|
shell: bash
|
|
env:
|
|
LANES: ${{ matrix.group.docker_lanes }}
|
|
GROUP_LABEL: ${{ matrix.group.label }}
|
|
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
|
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -z "$LANES" ]]; then
|
|
echo "lanes input is required for Docker E2E targeted planning." >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p .artifacts/docker-tests
|
|
export OPENCLAW_DOCKER_ALL_LANES="$LANES"
|
|
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI"
|
|
if [[ "$INCLUDE_RELEASE_PATH_SUITES" == "true" ]]; then
|
|
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
|
fi
|
|
|
|
plan_path=".artifacts/docker-tests/targeted-plan.json"
|
|
node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path"
|
|
node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT"
|
|
suffix="$(printf '%s' "${GROUP_LABEL:-$LANES}" | tr ',[:space:]' '-' | tr -cd 'A-Za-z0-9._-' | sed -E 's/-+/-/g; s/^-//; s/-$//')"
|
|
echo "artifact_suffix=${suffix:-targeted}" >> "$GITHUB_OUTPUT"
|
|
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Download OpenClaw Docker E2E package
|
|
if: 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'
|
|
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'
|
|
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
|
|
shell: bash
|
|
env:
|
|
CREDENTIALS: ${{ steps.plan.outputs.credentials }}
|
|
run: |
|
|
set -euo pipefail
|
|
credentials=",$CREDENTIALS,"
|
|
if [[ "$credentials" == *",openai,"* ]]; then
|
|
[[ -n "${OPENAI_API_KEY:-}" ]] || {
|
|
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run targeted Docker E2E lanes
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
export OPENCLAW_DOCKER_ALL_LANES="${DOCKER_E2E_LANES}"
|
|
export OPENCLAW_DOCKER_ALL_PREFLIGHT=0
|
|
export OPENCLAW_DOCKER_ALL_FAIL_FAST=0
|
|
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="${INCLUDE_OPENWEBUI}"
|
|
if [[ "${{ inputs.include_release_path_suites }}" == "true" ]]; then
|
|
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
|
fi
|
|
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/targeted-${{ steps.plan.outputs.artifact_suffix }}"
|
|
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/targeted-${{ steps.plan.outputs.artifact_suffix }}-timings.json"
|
|
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
|
|
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
|
|
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
|
|
fi
|
|
export OPENCLAW_DOCKER_ALL_BUILD=0
|
|
|
|
node .release-harness/scripts/test-docker-all.mjs
|
|
|
|
- name: Summarize targeted Docker E2E lanes
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
summary=".artifacts/docker-tests/targeted-${{ steps.plan.outputs.artifact_suffix }}/summary.json"
|
|
if [[ ! -f "$summary" ]]; then
|
|
echo "Docker targeted summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY"
|
|
exit 0
|
|
fi
|
|
node .release-harness/scripts/docker-e2e.mjs summary "$summary" "Docker E2E targeted lanes" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload targeted Docker E2E artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: docker-e2e-${{ steps.plan.outputs.artifact_suffix }}
|
|
path: .artifacts/docker-tests/
|
|
if-no-files-found: ignore
|
|
|
|
validate_docker_openwebui:
|
|
needs: [validate_selected_ref, prepare_docker_e2e_image]
|
|
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
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
|
|
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
|
|
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_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
|
|
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Checkout trusted release harness
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Log in to GHCR for shared Docker E2E image
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Validate Open WebUI credentials
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
[[ -n "${OPENAI_API_KEY:-}" ]] || {
|
|
echo "OPENAI_API_KEY is required for the Open WebUI Docker smoke." >&2
|
|
exit 1
|
|
}
|
|
|
|
- name: Plan Open WebUI Docker E2E chunk
|
|
id: plan
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p .artifacts/docker-tests
|
|
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
|
export OPENCLAW_DOCKER_ALL_CHUNK=openwebui
|
|
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI=true
|
|
|
|
plan_path=".artifacts/docker-tests/release-openwebui-plan.json"
|
|
node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path"
|
|
node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT"
|
|
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Download OpenClaw Docker E2E package
|
|
if: 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'
|
|
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'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
bash .release-harness/scripts/ci-docker-pull-retry.sh "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}"
|
|
|
|
- name: Run Open WebUI Docker E2E chunk
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
|
export OPENCLAW_DOCKER_ALL_CHUNK=openwebui
|
|
export OPENCLAW_DOCKER_ALL_BUILD=0
|
|
export OPENCLAW_DOCKER_ALL_PREFLIGHT=0
|
|
export OPENCLAW_DOCKER_ALL_FAIL_FAST=0
|
|
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI=1
|
|
export OPENCLAW_DOCKER_ALL_LOG_DIR=".artifacts/docker-tests/release-openwebui"
|
|
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-openwebui-timings.json"
|
|
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
|
|
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
|
|
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
|
|
fi
|
|
|
|
node .release-harness/scripts/test-docker-all.mjs
|
|
|
|
- name: Summarize Open WebUI Docker E2E chunk
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
summary=".artifacts/docker-tests/release-openwebui/summary.json"
|
|
if [[ ! -f "$summary" ]]; then
|
|
echo "Docker Open WebUI summary missing: \`$summary\`" >> "$GITHUB_STEP_SUMMARY"
|
|
exit 0
|
|
fi
|
|
node .release-harness/scripts/docker-e2e.mjs summary "$summary" "Docker E2E chunk: openwebui" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload Open WebUI Docker E2E artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: docker-e2e-openwebui
|
|
path: .artifacts/docker-tests/
|
|
if-no-files-found: ignore
|
|
|
|
prepare_docker_e2e_image:
|
|
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
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
packages: write
|
|
outputs:
|
|
image: ${{ steps.image.outputs.image }}
|
|
bare_image: ${{ steps.image.outputs.bare_image }}
|
|
functional_image: ${{ steps.image.outputs.functional_image }}
|
|
needs_bare_image: ${{ steps.plan.outputs.needs_bare_image }}
|
|
needs_e2e_image: ${{ steps.plan.outputs.needs_e2e_image }}
|
|
needs_functional_image: ${{ steps.plan.outputs.needs_functional_image }}
|
|
needs_live_image: ${{ steps.plan.outputs.needs_live_image }}
|
|
needs_package: ${{ steps.plan.outputs.needs_package }}
|
|
env:
|
|
DOCKER_BUILD_SUMMARY: "false"
|
|
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
|
OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }}
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Checkout trusted release harness
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Plan Docker E2E images
|
|
id: plan
|
|
shell: bash
|
|
env:
|
|
LANES: ${{ inputs.docker_lanes }}
|
|
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }}
|
|
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
|
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 }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p .artifacts/docker-tests
|
|
|
|
if [[ "$INCLUDE_RELEASE_PATH_SUITES" == "true" ]]; then
|
|
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
|
export OPENCLAW_DOCKER_ALL_PLAN_RELEASE_ALL=1
|
|
elif [[ -n "$LANES" ]]; then
|
|
export OPENCLAW_DOCKER_ALL_LANES="$LANES"
|
|
elif [[ "$INCLUDE_OPENWEBUI" == "true" ]]; then
|
|
export OPENCLAW_DOCKER_ALL_LANES=openwebui
|
|
fi
|
|
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI"
|
|
|
|
plan_path=".artifacts/docker-tests/plan.json"
|
|
node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path"
|
|
node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT"
|
|
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Setup Node environment
|
|
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name == '' && inputs.package_artifact_run_id == ''
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Download current-run OpenClaw Docker E2E package
|
|
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name != '' && inputs.package_artifact_run_id == ''
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: ${{ inputs.package_artifact_name }}
|
|
path: .artifacts/docker-e2e-package
|
|
|
|
- name: Download previous-run OpenClaw Docker E2E package
|
|
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_run_id != ''
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
|
path: .artifacts/docker-e2e-package
|
|
run-id: ${{ inputs.package_artifact_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Pack OpenClaw package for Docker E2E
|
|
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name == '' && inputs.package_artifact_run_id == ''
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p .artifacts/docker-e2e-package
|
|
node scripts/package-openclaw-for-docker.mjs \
|
|
--output-dir .artifacts/docker-e2e-package \
|
|
--output-name openclaw-current.tgz
|
|
|
|
- name: Validate OpenClaw Docker E2E package
|
|
id: package
|
|
if: steps.plan.outputs.needs_package == '1'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p .artifacts/docker-e2e-package
|
|
target=".artifacts/docker-e2e-package/openclaw-current.tgz"
|
|
if [[ ! -f "$target" ]]; then
|
|
mapfile -t tgzs < <(find .artifacts/docker-e2e-package -type f -name '*.tgz' | sort)
|
|
if [[ "${#tgzs[@]}" -ne 1 ]]; then
|
|
echo "Expected exactly one package tarball in .artifacts/docker-e2e-package; found ${#tgzs[@]}." >&2
|
|
printf '%s\n' "${tgzs[@]}" >&2
|
|
exit 1
|
|
fi
|
|
cp "${tgzs[0]}" "$target"
|
|
fi
|
|
echo "Validating Docker E2E package tarball: $target"
|
|
started_at="$(date +%s)"
|
|
timeout --foreground 5m node scripts/check-openclaw-package-tarball.mjs "$target"
|
|
finished_at="$(date +%s)"
|
|
echo "Docker E2E package tarball validation finished in $((finished_at - started_at))s."
|
|
digest="$(sha256sum "$target" | awk '{print $1}')"
|
|
tag="pkg-${digest:0:32}"
|
|
echo "sha256=$digest" >> "$GITHUB_OUTPUT"
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
{
|
|
echo "Docker E2E package: \`$target\`"
|
|
echo "Docker E2E package SHA-256: \`$digest\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload OpenClaw Docker E2E package
|
|
if: steps.plan.outputs.needs_package == '1' && (inputs.package_artifact_name == '' || inputs.package_artifact_run_id != '')
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
|
|
path: .artifacts/docker-e2e-package/openclaw-current.tgz
|
|
if-no-files-found: error
|
|
|
|
- name: Resolve shared Docker E2E image tags
|
|
id: image
|
|
shell: bash
|
|
env:
|
|
PACKAGE_TAG: ${{ steps.package.outputs.tag }}
|
|
SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }}
|
|
PROVIDED_FUNCTIONAL_IMAGE: ${{ inputs.docker_e2e_functional_image }}
|
|
run: |
|
|
set -euo pipefail
|
|
repository="${GITHUB_REPOSITORY,,}"
|
|
image_tag="${PACKAGE_TAG:-$SELECTED_SHA}"
|
|
bare_image="${PROVIDED_BARE_IMAGE:-ghcr.io/${repository}-docker-e2e-bare:${image_tag}}"
|
|
functional_image="${PROVIDED_FUNCTIONAL_IMAGE:-ghcr.io/${repository}-docker-e2e-functional:${image_tag}}"
|
|
image="$functional_image"
|
|
echo "image=$image" >> "$GITHUB_OUTPUT"
|
|
echo "bare_image=$bare_image" >> "$GITHUB_OUTPUT"
|
|
echo "functional_image=$functional_image" >> "$GITHUB_OUTPUT"
|
|
echo "Shared Docker E2E bare image: \`$bare_image\`" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "Shared Docker E2E functional image: \`$functional_image\`" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Log in to GHCR
|
|
if: steps.plan.outputs.needs_e2e_image == '1'
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Check existing shared Docker E2E images
|
|
id: image_exists
|
|
if: steps.plan.outputs.needs_e2e_image == '1'
|
|
shell: bash
|
|
env:
|
|
PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }}
|
|
PROVIDED_FUNCTIONAL_IMAGE: ${{ inputs.docker_e2e_functional_image }}
|
|
run: |
|
|
set -euo pipefail
|
|
bare_exists=0
|
|
functional_exists=0
|
|
needs_build=0
|
|
|
|
if [[ "${{ steps.plan.outputs.needs_bare_image }}" == "1" ]]; then
|
|
if docker manifest inspect "${{ steps.image.outputs.bare_image }}" >/dev/null 2>&1; then
|
|
bare_exists=1
|
|
echo "Shared Docker E2E bare image already exists: ${{ steps.image.outputs.bare_image }}"
|
|
elif [[ -n "$PROVIDED_BARE_IMAGE" ]]; then
|
|
echo "Provided bare Docker E2E image does not exist: $PROVIDED_BARE_IMAGE" >&2
|
|
exit 1
|
|
else
|
|
needs_build=1
|
|
fi
|
|
fi
|
|
|
|
if [[ "${{ steps.plan.outputs.needs_functional_image }}" == "1" ]]; then
|
|
if docker manifest inspect "${{ steps.image.outputs.functional_image }}" >/dev/null 2>&1; then
|
|
functional_exists=1
|
|
echo "Shared Docker E2E functional image already exists: ${{ steps.image.outputs.functional_image }}"
|
|
elif [[ -n "$PROVIDED_FUNCTIONAL_IMAGE" ]]; then
|
|
echo "Provided functional Docker E2E image does not exist: $PROVIDED_FUNCTIONAL_IMAGE" >&2
|
|
exit 1
|
|
else
|
|
needs_build=1
|
|
fi
|
|
fi
|
|
|
|
echo "bare_exists=$bare_exists" >> "$GITHUB_OUTPUT"
|
|
echo "functional_exists=$functional_exists" >> "$GITHUB_OUTPUT"
|
|
echo "needs_build=$needs_build" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Setup Docker builder
|
|
if: steps.image_exists.outputs.needs_build == '1'
|
|
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
|
|
with:
|
|
max-cache-size-mb: 800000
|
|
|
|
- name: Build and push bare Docker E2E image
|
|
if: steps.plan.outputs.needs_bare_image == '1' && steps.image_exists.outputs.bare_exists != '1'
|
|
uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2
|
|
with:
|
|
context: .
|
|
file: ./scripts/e2e/Dockerfile
|
|
target: bare
|
|
platforms: linux/amd64
|
|
tags: ${{ steps.image.outputs.bare_image }}
|
|
sbom: true
|
|
provenance: mode=max
|
|
push: true
|
|
|
|
- name: Build and push functional Docker E2E image
|
|
if: steps.plan.outputs.needs_functional_image == '1' && steps.image_exists.outputs.functional_exists != '1'
|
|
uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2
|
|
with:
|
|
context: .
|
|
file: ./scripts/e2e/Dockerfile
|
|
target: functional
|
|
build-contexts: |
|
|
openclaw_package=.artifacts/docker-e2e-package
|
|
platforms: linux/amd64
|
|
tags: ${{ steps.image.outputs.functional_image }}
|
|
sbom: true
|
|
provenance: mode=max
|
|
push: true
|
|
|
|
prepare_live_test_image:
|
|
needs: validate_selected_ref
|
|
if: inputs.include_live_suites && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-') || startsWith(inputs.live_suite_filter, 'docker-live-models'))
|
|
runs-on: blacksmith-32vcpu-ubuntu-2404
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
outputs:
|
|
live_image: ${{ steps.image.outputs.live_image }}
|
|
env:
|
|
DOCKER_BUILD_SUMMARY: "false"
|
|
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Resolve shared live-test image tag
|
|
id: image
|
|
shell: bash
|
|
env:
|
|
SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
repository="${GITHUB_REPOSITORY,,}"
|
|
live_image="ghcr.io/${repository}-live-test:${SELECTED_SHA}"
|
|
echo "live_image=${live_image}" >> "$GITHUB_OUTPUT"
|
|
echo "Shared live-test image: \`${live_image}\`" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Log in to GHCR
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Check existing shared live-test image
|
|
id: image_exists
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if docker manifest inspect "${{ steps.image.outputs.live_image }}" >/dev/null 2>&1; then
|
|
echo "Shared live-test image already exists: ${{ steps.image.outputs.live_image }}"
|
|
echo "exists=1" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "exists=0" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Setup Docker builder
|
|
if: steps.image_exists.outputs.exists != '1'
|
|
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
|
|
with:
|
|
max-cache-size-mb: 800000
|
|
|
|
- name: Build and push shared live-test image
|
|
if: steps.image_exists.outputs.exists != '1'
|
|
uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2
|
|
with:
|
|
context: .
|
|
file: ./Dockerfile
|
|
target: build
|
|
build-args: |
|
|
OPENCLAW_EXTENSIONS=matrix
|
|
platforms: linux/amd64
|
|
tags: ${{ steps.image.outputs.live_image }}
|
|
sbom: true
|
|
provenance: mode=max
|
|
push: true
|
|
|
|
validate_live_models_docker:
|
|
name: Docker live models (${{ matrix.provider_label }})
|
|
needs: [validate_selected_ref, prepare_live_test_image]
|
|
if: inputs.include_live_suites && inputs.live_model_providers == '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
|
|
runs-on: blacksmith-32vcpu-ubuntu-2404
|
|
timeout-minutes: 45
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- provider_label: Anthropic
|
|
providers: anthropic
|
|
profiles: stable full
|
|
- provider_label: Google
|
|
providers: google
|
|
profiles: stable full
|
|
- provider_label: MiniMax
|
|
providers: minimax
|
|
profiles: stable full
|
|
- provider_label: OpenAI
|
|
providers: openai
|
|
profiles: minimum stable full
|
|
- provider_label: OpenCode
|
|
providers: opencode-go
|
|
profiles: full
|
|
- provider_label: OpenRouter
|
|
providers: openrouter
|
|
profiles: full
|
|
- provider_label: xAI
|
|
providers: xai
|
|
profiles: full
|
|
- provider_label: Z.ai
|
|
providers: zai
|
|
profiles: full
|
|
- provider_label: Fireworks
|
|
providers: fireworks
|
|
profiles: full
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
|
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
|
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
|
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
|
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
|
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
|
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
|
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
|
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
|
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
|
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
|
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
|
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
OPENCLAW_LIVE_PROVIDERS: ${{ matrix.providers }}
|
|
OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
|
OPENCLAW_LIVE_MAX_MODELS: "6"
|
|
OPENCLAW_LIVE_MODEL_TIMEOUT_MS: "45000"
|
|
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
|
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
|
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 live Docker harness
|
|
if: contains(matrix.profiles, inputs.release_test_profile)
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Setup Node environment
|
|
if: contains(matrix.profiles, inputs.release_test_profile)
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
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: Log in to GHCR
|
|
if: contains(matrix.profiles, inputs.release_test_profile)
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Validate provider credential
|
|
if: contains(matrix.profiles, inputs.release_test_profile)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
require_any() {
|
|
local label="$1"
|
|
shift
|
|
local key
|
|
for key in "$@"; do
|
|
if [[ -n "${!key:-}" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
echo "Missing credential for ${label}: expected one of $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
case "${{ matrix.providers }}" in
|
|
anthropic) require_any Anthropic ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;
|
|
google) require_any Google GEMINI_API_KEY GOOGLE_API_KEY ;;
|
|
minimax) require_any MiniMax MINIMAX_API_KEY ;;
|
|
openai) require_any OpenAI OPENAI_API_KEY ;;
|
|
opencode-go) require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY ;;
|
|
openrouter) require_any OpenRouter OPENROUTER_API_KEY ;;
|
|
xai) require_any xAI XAI_API_KEY ;;
|
|
zai) require_any Z.ai ZAI_API_KEY Z_AI_API_KEY ;;
|
|
fireworks) require_any Fireworks FIREWORKS_API_KEY ;;
|
|
*)
|
|
echo "Unhandled live model provider shard: ${{ matrix.providers }}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
- name: Run Docker live model sweep
|
|
if: contains(matrix.profiles, inputs.release_test_profile)
|
|
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
|
|
|
|
validate_live_models_docker_targeted:
|
|
name: Docker live models (selected providers)
|
|
needs: [validate_selected_ref, prepare_live_test_image]
|
|
if: inputs.include_live_suites && inputs.live_model_providers != '' && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'docker-live-models')
|
|
runs-on: blacksmith-32vcpu-ubuntu-2404
|
|
timeout-minutes: 45
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
|
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
|
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
|
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
|
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
|
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
|
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
|
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
|
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
|
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
|
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
|
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
|
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
REQUESTED_LIVE_MODEL_PROVIDERS: ${{ inputs.live_model_providers }}
|
|
OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
|
OPENCLAW_LIVE_MAX_MODELS: "6"
|
|
OPENCLAW_LIVE_MODEL_TIMEOUT_MS: "45000"
|
|
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
|
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Checkout trusted live Docker harness
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Normalize provider allowlist
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
all_providers=(anthropic google minimax openai opencode-go openrouter xai zai fireworks)
|
|
|
|
normalize_provider() {
|
|
local value="${1,,}"
|
|
case "$value" in
|
|
z.ai|z-ai) echo "zai" ;;
|
|
opencode|opencode-go) echo "opencode-go" ;;
|
|
open-router|openrouter) echo "openrouter" ;;
|
|
*) echo "$value" ;;
|
|
esac
|
|
}
|
|
|
|
is_known_provider() {
|
|
local value="$1"
|
|
local provider
|
|
for provider in "${all_providers[@]}"; do
|
|
[[ "$provider" == "$value" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
selected=()
|
|
declare -A seen=()
|
|
raw="${REQUESTED_LIVE_MODEL_PROVIDERS:-}"
|
|
normalized_all="${raw,,}"
|
|
normalized_all="${normalized_all//[[:space:],]/}"
|
|
if [[ -z "$normalized_all" || "$normalized_all" == "all" ]]; then
|
|
selected=("${all_providers[@]}")
|
|
else
|
|
while IFS= read -r entry; do
|
|
[[ -z "$entry" ]] && continue
|
|
provider="$(normalize_provider "$entry")"
|
|
if ! is_known_provider "$provider"; then
|
|
echo "Unknown live model provider '${entry}'. Expected one of: ${all_providers[*]}" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -z "${seen[$provider]:-}" ]]; then
|
|
selected+=("$provider")
|
|
seen[$provider]=1
|
|
fi
|
|
done < <(printf '%s\n' "$raw" | tr ',' '\n' | tr '[:space:]' '\n')
|
|
fi
|
|
|
|
if [[ "${#selected[@]}" -eq 0 ]]; then
|
|
echo "No live model providers selected." >&2
|
|
exit 1
|
|
fi
|
|
|
|
providers_csv="$(IFS=,; echo "${selected[*]}")"
|
|
echo "OPENCLAW_LIVE_PROVIDERS=$providers_csv" >> "$GITHUB_ENV"
|
|
{
|
|
echo "Live model providers: \`$providers_csv\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Hydrate live auth/profile inputs
|
|
run: bash scripts/ci-hydrate-live-auth.sh
|
|
|
|
- name: Log in to GHCR
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Validate provider credentials
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
require_any() {
|
|
local label="$1"
|
|
shift
|
|
local key
|
|
for key in "$@"; do
|
|
if [[ -n "${!key:-}" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
echo "Missing credential for ${label}: expected one of $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
IFS=',' read -r -a providers <<<"${OPENCLAW_LIVE_PROVIDERS}"
|
|
for provider in "${providers[@]}"; do
|
|
case "$provider" in
|
|
anthropic) require_any Anthropic ANTHROPIC_API_KEY ANTHROPIC_API_KEY_OLD ANTHROPIC_API_TOKEN ;;
|
|
google) require_any Google GEMINI_API_KEY GOOGLE_API_KEY ;;
|
|
minimax) require_any MiniMax MINIMAX_API_KEY ;;
|
|
openai) require_any OpenAI OPENAI_API_KEY ;;
|
|
opencode-go) require_any OpenCode OPENCODE_API_KEY OPENCODE_ZEN_API_KEY ;;
|
|
openrouter) require_any OpenRouter OPENROUTER_API_KEY ;;
|
|
xai) require_any xAI XAI_API_KEY ;;
|
|
zai) require_any Z.ai ZAI_API_KEY Z_AI_API_KEY ;;
|
|
fireworks) require_any Fireworks FIREWORKS_API_KEY ;;
|
|
*)
|
|
echo "Unhandled live model provider shard: ${provider}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
- name: Run Docker live model sweep
|
|
run: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-models-docker.sh
|
|
|
|
validate_live_provider_suites:
|
|
needs: validate_selected_ref
|
|
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || (startsWith(inputs.live_suite_filter, 'native-live-') && !startsWith(inputs.live_suite_filter, 'native-live-extensions-media') && inputs.live_suite_filter != 'native-live-extensions-a-k'))
|
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
|
timeout-minutes: ${{ matrix.timeout_minutes }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- 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
|
|
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
|
|
profile_env_only: false
|
|
profiles: 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
|
|
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic OPENCLAW_LIVE_GATEWAY_SMOKE=1 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
|
|
timeout_minutes: 45
|
|
profile_env_only: false
|
|
profiles: stable
|
|
- suite_id: native-live-src-gateway-profiles-anthropic-opus
|
|
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
|
|
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
|
|
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
|
|
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 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
|
|
timeout_minutes: 90
|
|
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
|
|
profile_env_only: false
|
|
profiles: 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
profile_env_only: false
|
|
profiles: full
|
|
- suite_id: native-live-src-gateway-profiles-opencode-go-smoke
|
|
label: Native live gateway profiles OpenCode Go smoke
|
|
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go OPENCLAW_LIVE_GATEWAY_SMOKE=1 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
|
|
timeout_minutes: 45
|
|
profile_env_only: false
|
|
profiles: stable
|
|
- 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
|
|
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
|
|
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
|
|
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
|
|
profile_env_only: false
|
|
profiles: stable full
|
|
- suite_id: native-live-src-infra
|
|
label: Native live infra
|
|
command: OPENCLAW_LIVE_APNS_REACHABILITY=1 node .release-harness/scripts/test-live-shard.mjs native-live-src-infra
|
|
timeout_minutes: 45
|
|
profile_env_only: false
|
|
profiles: stable full
|
|
- 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
|
|
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
|
|
profile_env_only: false
|
|
profiles: full
|
|
- suite_id: native-live-extensions-moonshot
|
|
label: Native live Moonshot plugin
|
|
command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-moonshot
|
|
timeout_minutes: 60
|
|
profile_env_only: false
|
|
advisory: true
|
|
profiles: full
|
|
- 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
|
|
profile_env_only: false
|
|
profiles: 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
|
|
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
|
|
profile_env_only: false
|
|
profiles: full
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
|
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
|
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
|
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
|
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
|
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
|
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
|
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
|
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
|
FAL_KEY: ${{ secrets.FAL_KEY }}
|
|
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
|
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
|
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
|
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
|
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
|
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
|
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
|
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
|
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
|
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
|
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS: ""
|
|
OPENCLAW_LIVE_VYDRA_VIDEO: "1"
|
|
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-')))
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Checkout trusted live shard harness
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-')))
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Setup Node environment
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-')))
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Hydrate live auth/profile inputs
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-')))
|
|
run: bash scripts/ci-hydrate-live-auth.sh
|
|
|
|
- name: Configure suite-specific env
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-')))
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "${{ matrix.profile_env_only }}" == "true" ]]; then
|
|
echo "OPENCLAW_DOCKER_PROFILE_ENV_ONLY=1" >> "$GITHUB_ENV"
|
|
fi
|
|
case "${{ matrix.suite_id }}" in
|
|
live-cli-backend-docker)
|
|
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
|
|
# Keep the release-blocking CI lane on Codex API-key auth. The
|
|
# staged auth-file path remains supported for local maintainer
|
|
# reruns, but it can hang on stale subscription/session state in
|
|
# an otherwise healthy release run.
|
|
echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV"
|
|
# Replace the staged config.toml with a minimal CI-safe config so
|
|
# the repo stays trusted for MCP/tool use without inheriting
|
|
# maintainer-local provider/profile overrides that do not exist
|
|
# inside CI.
|
|
# Codex's workspace-write sandbox relies on user namespaces that
|
|
# this Docker lane does not provide, so run Codex unsandboxed
|
|
# inside the already-isolated container to keep MCP cron/tool
|
|
# execution representative instead of failing on nested sandbox
|
|
# setup.
|
|
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
|
|
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV"
|
|
;;
|
|
live-codex-harness-docker)
|
|
# Keep CI on the API-key path for now. The staged Codex auth secret
|
|
# is currently stale, but the wrapper still supports codex-auth for
|
|
# local maintainer reruns without changing Peter's flow.
|
|
echo "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_LIVE_CODEX_HARNESS_DEBUG=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
|
|
;;
|
|
live-acp-bind-docker)
|
|
if [[ -n "${GEMINI_API_KEY:-}" || -n "${GOOGLE_API_KEY:-}" ]]; then
|
|
echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex,gemini" >> "$GITHUB_ENV"
|
|
else
|
|
# The hydrated Gemini settings file only selects Gemini CLI auth
|
|
# mode. CI still needs a usable Gemini or Google API key before
|
|
# ACP bind can initialize a Gemini session.
|
|
echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex" >> "$GITHUB_ENV"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
- name: Run ${{ matrix.label }}
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-anthropic' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-anthropic-')) || (inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-')))
|
|
env:
|
|
OPENCLAW_LIVE_COMMAND: ${{ matrix.command }}
|
|
OPENCLAW_LIVE_SUITE_ADVISORY: ${{ matrix.advisory }}
|
|
run: |
|
|
set +e
|
|
bash .release-harness/scripts/ci-live-command-retry.sh
|
|
status=$?
|
|
set -e
|
|
if [[ "$status" -eq 0 ]]; then
|
|
exit 0
|
|
fi
|
|
if [[ "${OPENCLAW_LIVE_SUITE_ADVISORY:-}" == "true" ]]; then
|
|
echo "::warning::Advisory live suite failed with exit code ${status}: ${{ matrix.suite_id }}"
|
|
exit 0
|
|
fi
|
|
exit "$status"
|
|
|
|
validate_live_docker_provider_suites:
|
|
name: Docker live suites (${{ matrix.label }})
|
|
needs: [validate_selected_ref, prepare_live_test_image]
|
|
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'live-'))
|
|
runs-on: blacksmith-32vcpu-ubuntu-2404
|
|
timeout-minutes: ${{ matrix.timeout_minutes }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- suite_id: live-gateway-docker
|
|
label: Docker live gateway OpenAI
|
|
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
|
|
- 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
|
|
timeout_minutes: 30
|
|
profile_env_only: false
|
|
profiles: stable full
|
|
- suite_id: live-gateway-google-docker
|
|
label: Docker live gateway Google
|
|
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=google OPENCLAW_LIVE_GATEWAY_MODELS=google/gemini-3.1-pro-preview,google/gemini-3-flash-preview 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: stable full
|
|
- suite_id: live-gateway-minimax-docker
|
|
label: Docker live gateway MiniMax
|
|
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal 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: stable full
|
|
- suite_id: live-gateway-advisory-docker-deepseek-fireworks
|
|
suite_group: live-gateway-advisory-docker
|
|
label: Docker live gateway advisory DeepSeek/Fireworks
|
|
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=deepseek,fireworks 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: full
|
|
- suite_id: live-gateway-advisory-docker-opencode-openrouter
|
|
suite_group: live-gateway-advisory-docker
|
|
label: Docker live gateway advisory OpenCode/OpenRouter
|
|
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go,openrouter 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: full
|
|
- suite_id: live-gateway-advisory-docker-xai-zai
|
|
suite_group: live-gateway-advisory-docker
|
|
label: Docker live gateway advisory xAI/Z.ai
|
|
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=xai,zai 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: full
|
|
- suite_id: live-cli-backend-docker
|
|
label: Docker live CLI backend
|
|
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-cli-backend-docker.sh
|
|
timeout_minutes: 50
|
|
profile_env_only: false
|
|
profiles: stable full
|
|
- suite_id: live-acp-bind-docker
|
|
label: Docker live ACP bind
|
|
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 45m bash .release-harness/scripts/test-live-acp-bind-docker.sh
|
|
timeout_minutes: 50
|
|
profile_env_only: false
|
|
profiles: stable full
|
|
- suite_id: live-codex-harness-docker
|
|
label: Docker live Codex harness
|
|
command: OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-codex-harness-docker.sh
|
|
timeout_minutes: 40
|
|
profile_env_only: false
|
|
profiles: stable full
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
|
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
|
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
|
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
|
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
|
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
|
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
|
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
|
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
|
FAL_KEY: ${{ secrets.FAL_KEY }}
|
|
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
|
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
|
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
|
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
|
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
|
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
|
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
|
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
|
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
|
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
|
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
OPENCLAW_LIVE_IMAGE: ${{ needs.prepare_live_test_image.outputs.live_image }}
|
|
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
|
OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS: ""
|
|
OPENCLAW_LIVE_VYDRA_VIDEO: "1"
|
|
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Checkout trusted live shard harness
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Setup Node environment
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Hydrate live auth/profile inputs
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
|
|
run: bash scripts/ci-hydrate-live-auth.sh
|
|
|
|
- name: Log in to GHCR
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
|
|
- name: Configure suite-specific env
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "${{ matrix.profile_env_only }}" == "true" ]]; then
|
|
echo "OPENCLAW_DOCKER_PROFILE_ENV_ONLY=1" >> "$GITHUB_ENV"
|
|
fi
|
|
case "${{ matrix.suite_id }}" in
|
|
live-cli-backend-docker)
|
|
echo "OPENCLAW_LIVE_CLI_BACKEND_MODEL=codex-cli/gpt-5.4" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key" >> "$GITHUB_ENV"
|
|
echo 'OPENCLAW_LIVE_CLI_BACKEND_ARGS=["exec","--json","--color","never","--sandbox","danger-full-access","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
|
|
echo 'OPENCLAW_LIVE_CLI_BACKEND_RESUME_ARGS=["exec","resume","{sessionId}","-c","sandbox_mode=\"danger-full-access\"","-c","service_tier=\"fast\"","--skip-git-repo-check"]' >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=1" >> "$GITHUB_ENV"
|
|
;;
|
|
live-codex-harness-docker)
|
|
echo "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_LIVE_CODEX_HARNESS_DEBUG=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_CLI_BACKEND_LOG_OUTPUT=1" >> "$GITHUB_ENV"
|
|
echo "OPENCLAW_TEST_CONSOLE=1" >> "$GITHUB_ENV"
|
|
;;
|
|
live-acp-bind-docker)
|
|
if [[ -n "${GEMINI_API_KEY:-}" || -n "${GOOGLE_API_KEY:-}" ]]; then
|
|
echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex,gemini" >> "$GITHUB_ENV"
|
|
else
|
|
echo "OPENCLAW_LIVE_ACP_BIND_AGENTS=claude,codex" >> "$GITHUB_ENV"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
- name: Run ${{ matrix.label }}
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'live-gateway-advisory-docker' && startsWith(matrix.suite_id, 'live-gateway-advisory-docker-')))
|
|
env:
|
|
OPENCLAW_LIVE_COMMAND: ${{ matrix.command }}
|
|
run: bash .release-harness/scripts/ci-live-command-retry.sh
|
|
|
|
validate_live_media_provider_suites:
|
|
name: Live media suites (${{ matrix.label }})
|
|
needs: validate_selected_ref
|
|
if: inputs.include_live_suites && !inputs.live_models_only && (inputs.live_suite_filter == '' || startsWith(inputs.live_suite_filter, 'native-live-extensions-media') || inputs.live_suite_filter == 'native-live-extensions-a-k')
|
|
runs-on: blacksmith-8vcpu-ubuntu-2404
|
|
container:
|
|
image: ghcr.io/openclaw/openclaw-live-media-runner:ubuntu-24.04
|
|
credentials:
|
|
username: ${{ github.actor }}
|
|
password: ${{ github.token }}
|
|
timeout-minutes: ${{ matrix.timeout_minutes }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
profile_env_only: false
|
|
profiles: full
|
|
env:
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
|
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
|
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
|
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
|
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
|
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
|
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
|
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
|
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
|
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
|
FAL_KEY: ${{ secrets.FAL_KEY }}
|
|
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
|
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
|
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
|
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
|
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
|
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
|
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
|
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
|
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
|
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
|
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
|
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
|
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
OPENCLAW_LIVE_VIDEO_GENERATION_SKIP_PROVIDERS: ""
|
|
OPENCLAW_LIVE_VYDRA_VIDEO: "1"
|
|
OPENCLAW_VITEST_MAX_WORKERS: "2"
|
|
steps:
|
|
- name: Checkout selected ref
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
|
fetch-depth: 1
|
|
|
|
- name: Checkout trusted live shard harness
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 1
|
|
path: .release-harness
|
|
|
|
- name: Verify preinstalled live media dependencies
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
ffmpeg -version | head -1
|
|
ffprobe -version | head -1
|
|
|
|
- name: Setup Node environment
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
install-bun: "true"
|
|
|
|
- name: Hydrate live auth/profile inputs
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
|
|
run: bash scripts/ci-hydrate-live-auth.sh
|
|
|
|
- name: Configure suite-specific env
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "${{ matrix.profile_env_only }}" == "true" ]]; then
|
|
echo "OPENCLAW_DOCKER_PROFILE_ENV_ONLY=1" >> "$GITHUB_ENV"
|
|
fi
|
|
|
|
- name: Run ${{ matrix.label }}
|
|
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id || (inputs.live_suite_filter == 'native-live-extensions-media-video' && startsWith(matrix.suite_id, 'native-live-extensions-media-video-')))
|
|
run: ${{ matrix.command }}
|