mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:50:44 +00:00
ci: shard release validation hotspots
This commit is contained in:
10
.github/workflows/install-smoke.yml
vendored
10
.github/workflows/install-smoke.yml
vendored
@@ -107,13 +107,15 @@ jobs:
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
# Blacksmith's builder owns the Docker layer cache; keep smoke builds off
|
||||
# explicit gha cache directives so local tags still load cleanly.
|
||||
# Keep the Blacksmith builder hot across release runs. The image still
|
||||
# loads into the local Docker daemon for downstream smoke containers.
|
||||
- name: Build root Dockerfile smoke image
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
cache-from: type=gha,scope=openclaw-dockerfile-smoke
|
||||
cache-to: type=gha,scope=openclaw-dockerfile-smoke,mode=max
|
||||
build-args: |
|
||||
OPENCLAW_EXTENSIONS=matrix
|
||||
tags: |
|
||||
@@ -216,8 +218,6 @@ jobs:
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
# Blacksmith's builder owns the Docker layer cache; keep smoke builds off
|
||||
# explicit gha cache directives so local tags still load cleanly.
|
||||
- name: Run QR package install smoke
|
||||
env:
|
||||
OPENCLAW_QR_SMOKE_FORCE_INSTALL: "1"
|
||||
@@ -230,6 +230,8 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
cache-from: type=gha,scope=openclaw-dockerfile-smoke
|
||||
cache-to: type=gha,scope=openclaw-dockerfile-smoke,mode=max
|
||||
build-args: |
|
||||
OPENCLAW_EXTENSIONS=matrix
|
||||
tags: |
|
||||
|
||||
@@ -1747,9 +1747,31 @@ jobs:
|
||||
timeout_minutes: 90
|
||||
profile_env_only: false
|
||||
profiles: full
|
||||
- suite_id: native-live-src-gateway-profiles-opencode-go
|
||||
label: Native live gateway profiles OpenCode Go deep
|
||||
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=opencode-go node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
|
||||
- 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
|
||||
@@ -1871,14 +1893,14 @@ jobs:
|
||||
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)
|
||||
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-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)
|
||||
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-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-')))
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
@@ -1886,7 +1908,7 @@ jobs:
|
||||
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)
|
||||
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-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 }}
|
||||
@@ -1894,11 +1916,11 @@ jobs:
|
||||
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)
|
||||
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-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)
|
||||
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-opencode-go' && startsWith(matrix.suite_id, 'native-live-src-gateway-profiles-opencode-go-')))
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1951,7 +1973,7 @@ jobs:
|
||||
esac
|
||||
|
||||
- name: Run ${{ matrix.label }}
|
||||
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id)
|
||||
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-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 }}
|
||||
@@ -2169,9 +2191,31 @@ jobs:
|
||||
timeout_minutes: 90
|
||||
profile_env_only: false
|
||||
profiles: full
|
||||
- suite_id: native-live-extensions-media-video
|
||||
label: Native live media video plugins
|
||||
command: node .release-harness/scripts/test-live-shard.mjs native-live-extensions-media-video
|
||||
- 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
|
||||
@@ -2226,14 +2270,14 @@ jobs:
|
||||
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)
|
||||
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)
|
||||
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 }}
|
||||
@@ -2241,7 +2285,7 @@ jobs:
|
||||
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)
|
||||
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
|
||||
@@ -2249,7 +2293,7 @@ jobs:
|
||||
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)
|
||||
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 }}
|
||||
@@ -2257,11 +2301,11 @@ jobs:
|
||||
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)
|
||||
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)
|
||||
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
|
||||
@@ -2270,5 +2314,5 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Run ${{ matrix.label }}
|
||||
if: contains(matrix.profiles, inputs.release_test_profile) && (inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id)
|
||||
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 }}
|
||||
|
||||
@@ -24,6 +24,40 @@ OPENAI_API_KEY="${OPENAI_API_KEY:-}"
|
||||
ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}"
|
||||
ANTHROPIC_API_TOKEN="${ANTHROPIC_API_TOKEN:-}"
|
||||
AGENT_TURN_TIMEOUT_SECONDS="${OPENCLAW_INSTALL_E2E_AGENT_TURN_TIMEOUT_SECONDS:-600}"
|
||||
AGENT_TURNS_PARALLEL="${OPENCLAW_INSTALL_E2E_AGENT_TURNS_PARALLEL:-1}"
|
||||
|
||||
time_phase() {
|
||||
local name="$1"
|
||||
shift
|
||||
local started_at
|
||||
local finished_at
|
||||
local status
|
||||
started_at="$(date +%s)"
|
||||
echo "==> Phase start: $name"
|
||||
set +e
|
||||
"$@"
|
||||
status="$?"
|
||||
set -e
|
||||
finished_at="$(date +%s)"
|
||||
if [[ "$status" -eq 0 ]]; then
|
||||
echo "==> Phase passed: $name ($((finished_at - started_at))s)"
|
||||
else
|
||||
echo "==> Phase failed: $name ($((finished_at - started_at))s, status=$status)" >&2
|
||||
fi
|
||||
return "$status"
|
||||
}
|
||||
|
||||
PHASE_MARK_STARTED_AT=0
|
||||
|
||||
phase_mark_start() {
|
||||
PHASE_MARK_STARTED_AT="$(date +%s)"
|
||||
echo "==> Phase start: $1"
|
||||
}
|
||||
|
||||
phase_mark_passed() {
|
||||
local name="$1"
|
||||
echo "==> Phase passed: $name ($(($(date +%s) - PHASE_MARK_STARTED_AT))s)"
|
||||
}
|
||||
|
||||
# This image runs as a non-root user, so seed a user-local npm prefix before we
|
||||
# preinstall an older global version to exercise the upgrade path.
|
||||
@@ -53,48 +87,58 @@ elif [[ "$MODELS_MODE" == "anthropic" && -z "$ANTHROPIC_API_TOKEN" && -z "$ANTHR
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "==> Resolve npm versions"
|
||||
EXPECTED_VERSION="$(quiet_npm view "openclaw@${INSTALL_TAG}" version)"
|
||||
if [[ -z "$EXPECTED_VERSION" || "$EXPECTED_VERSION" == "undefined" || "$EXPECTED_VERSION" == "null" ]]; then
|
||||
echo "ERROR: unable to resolve openclaw@${INSTALL_TAG} version" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -n "$E2E_PREVIOUS_VERSION" ]]; then
|
||||
PREVIOUS_VERSION="$E2E_PREVIOUS_VERSION"
|
||||
else
|
||||
PREVIOUS_VERSION="$(VERSIONS_JSON="$(quiet_npm view openclaw versions --json)" node - <<'NODE'
|
||||
resolve_npm_versions() {
|
||||
EXPECTED_VERSION="$(quiet_npm view "openclaw@${INSTALL_TAG}" version)"
|
||||
if [[ -z "$EXPECTED_VERSION" || "$EXPECTED_VERSION" == "undefined" || "$EXPECTED_VERSION" == "null" ]]; then
|
||||
echo "ERROR: unable to resolve openclaw@${INSTALL_TAG} version" >&2
|
||||
return 2
|
||||
fi
|
||||
if [[ -n "$E2E_PREVIOUS_VERSION" ]]; then
|
||||
PREVIOUS_VERSION="$E2E_PREVIOUS_VERSION"
|
||||
else
|
||||
PREVIOUS_VERSION="$(VERSIONS_JSON="$(quiet_npm view openclaw versions --json)" node - <<'NODE'
|
||||
const versions = JSON.parse(process.env.VERSIONS_JSON || "[]");
|
||||
if (!Array.isArray(versions) || versions.length === 0) process.exit(1);
|
||||
process.stdout.write(versions.length >= 2 ? versions[versions.length - 2] : versions[0]);
|
||||
NODE
|
||||
)"
|
||||
fi
|
||||
echo "expected=$EXPECTED_VERSION previous=$PREVIOUS_VERSION"
|
||||
)"
|
||||
fi
|
||||
echo "expected=$EXPECTED_VERSION previous=$PREVIOUS_VERSION"
|
||||
}
|
||||
|
||||
if [[ "$SKIP_PREVIOUS" == "1" ]]; then
|
||||
echo "==> Skip preinstall previous (OPENCLAW_INSTALL_E2E_SKIP_PREVIOUS=1)"
|
||||
else
|
||||
echo "==> Preinstall previous (forces installer upgrade path; avoids read() prompt)"
|
||||
quiet_npm install -g "openclaw@${PREVIOUS_VERSION}"
|
||||
fi
|
||||
preinstall_previous_version() {
|
||||
if [[ "$SKIP_PREVIOUS" == "1" ]]; then
|
||||
echo "Skip preinstall previous (OPENCLAW_INSTALL_E2E_SKIP_PREVIOUS=1)"
|
||||
else
|
||||
echo "Preinstall previous (forces installer upgrade path; avoids read() prompt)"
|
||||
quiet_npm install -g "openclaw@${PREVIOUS_VERSION}"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==> Run official installer one-liner"
|
||||
if [[ "$INSTALL_TAG" == "beta" ]]; then
|
||||
curl -fsSL "$INSTALL_URL" | OPENCLAW_BETA=1 bash
|
||||
elif [[ "$INSTALL_TAG" != "latest" ]]; then
|
||||
curl -fsSL "$INSTALL_URL" | OPENCLAW_VERSION="$INSTALL_TAG" bash
|
||||
else
|
||||
curl -fsSL "$INSTALL_URL" | bash
|
||||
fi
|
||||
run_official_installer() {
|
||||
if [[ "$INSTALL_TAG" == "beta" ]]; then
|
||||
curl -fsSL "$INSTALL_URL" | OPENCLAW_BETA=1 bash
|
||||
elif [[ "$INSTALL_TAG" != "latest" ]]; then
|
||||
curl -fsSL "$INSTALL_URL" | OPENCLAW_VERSION="$INSTALL_TAG" bash
|
||||
else
|
||||
curl -fsSL "$INSTALL_URL" | bash
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==> Verify installed version"
|
||||
INSTALLED_VERSION="$(openclaw --version 2>/dev/null | head -n 1 | tr -d '\r')"
|
||||
INSTALLED_VERSION="$(extract_openclaw_semver "$INSTALLED_VERSION")"
|
||||
echo "installed=$INSTALLED_VERSION expected=$EXPECTED_VERSION"
|
||||
if [[ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]]; then
|
||||
echo "ERROR: expected openclaw@$EXPECTED_VERSION, got openclaw@$INSTALLED_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
verify_installed_version() {
|
||||
INSTALLED_VERSION="$(openclaw --version 2>/dev/null | head -n 1 | tr -d '\r')"
|
||||
INSTALLED_VERSION="$(extract_openclaw_semver "$INSTALLED_VERSION")"
|
||||
echo "installed=$INSTALLED_VERSION expected=$EXPECTED_VERSION"
|
||||
if [[ "$INSTALLED_VERSION" != "$EXPECTED_VERSION" ]]; then
|
||||
echo "ERROR: expected openclaw@$EXPECTED_VERSION, got openclaw@$INSTALLED_VERSION" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
time_phase "Resolve npm versions" resolve_npm_versions
|
||||
time_phase "Preinstall previous" preinstall_previous_version
|
||||
time_phase "Run official installer one-liner" run_official_installer
|
||||
time_phase "Verify installed version" verify_installed_version
|
||||
|
||||
set_image_model() {
|
||||
local profile="$1"
|
||||
@@ -249,6 +293,46 @@ fs.writeFileSync(path, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
|
||||
NODE
|
||||
}
|
||||
|
||||
RUN_AGENT_TURN_BG_PID=""
|
||||
|
||||
run_agent_turn_logged() {
|
||||
local label="$1"
|
||||
local profile="$2"
|
||||
local session_id="$3"
|
||||
local prompt="$4"
|
||||
local out_json="$5"
|
||||
local started_at
|
||||
SESSION_JSONL="$(session_jsonl_path "$profile" "$session_id")"
|
||||
started_at="$(date +%s)"
|
||||
echo "==> Agent turn start: $label ($profile)"
|
||||
run_agent_turn "$profile" "$session_id" "$prompt" "$out_json"
|
||||
echo "==> Agent turn passed: $label ($profile, $(($(date +%s) - started_at))s)"
|
||||
}
|
||||
|
||||
run_agent_turn_bg() {
|
||||
local label="$1"
|
||||
local profile="$2"
|
||||
local session_id="$3"
|
||||
local prompt="$4"
|
||||
local out_json="$5"
|
||||
(
|
||||
set -euo pipefail
|
||||
run_agent_turn_logged "$label" "$profile" "$session_id" "$prompt" "$out_json"
|
||||
) &
|
||||
RUN_AGENT_TURN_BG_PID="$!"
|
||||
}
|
||||
|
||||
wait_agent_turn_batch() {
|
||||
local failed=0
|
||||
local pid
|
||||
for pid in "$@"; do
|
||||
if ! wait "$pid"; then
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
return "$failed"
|
||||
}
|
||||
|
||||
dump_profile_debug() {
|
||||
local profile="$1"
|
||||
local turn_output="$2"
|
||||
@@ -450,7 +534,7 @@ run_profile() {
|
||||
local workspace="$3"
|
||||
local agent_model_provider="$4" # "openai"|"anthropic"
|
||||
|
||||
echo "==> Onboard ($profile)"
|
||||
phase_mark_start "Onboard ($profile)"
|
||||
if [[ "$agent_model_provider" == "openai" ]]; then
|
||||
openclaw --profile "$profile" onboard \
|
||||
--non-interactive \
|
||||
@@ -501,8 +585,9 @@ run_profile() {
|
||||
--workspace "$workspace" \
|
||||
--skip-health
|
||||
fi
|
||||
phase_mark_passed "Onboard ($profile)"
|
||||
|
||||
echo "==> Verify workspace identity files ($profile)"
|
||||
phase_mark_start "Verify workspace identity files ($profile)"
|
||||
test -f "$workspace/AGENTS.md"
|
||||
test -f "$workspace/IDENTITY.md"
|
||||
test -f "$workspace/USER.md"
|
||||
@@ -512,8 +597,9 @@ run_profile() {
|
||||
# first-run identity ritual. Drop BOOTSTRAP.md so provider prompts stay focused
|
||||
# on the fixture task and do not spend turns following onboarding copy.
|
||||
rm -f "$workspace/BOOTSTRAP.md"
|
||||
phase_mark_passed "Verify workspace identity files ($profile)"
|
||||
|
||||
echo "==> Configure models ($profile)"
|
||||
phase_mark_start "Configure models ($profile)"
|
||||
local agent_model
|
||||
local image_model
|
||||
if [[ "$agent_model_provider" == "openai" ]]; then
|
||||
@@ -534,8 +620,9 @@ run_profile() {
|
||||
fi
|
||||
echo "model=$agent_model"
|
||||
echo "imageModel=$image_model"
|
||||
phase_mark_passed "Configure models ($profile)"
|
||||
|
||||
echo "==> Prepare tool fixtures ($profile)"
|
||||
phase_mark_start "Prepare tool fixtures ($profile)"
|
||||
PROOF_TXT="$workspace/proof.txt"
|
||||
PROOF_COPY="$workspace/copy.txt"
|
||||
HOSTNAME_TXT="$workspace/hostname.txt"
|
||||
@@ -548,8 +635,9 @@ run_profile() {
|
||||
echo -n "$PROOF_VALUE" >"$PROOF_TXT"
|
||||
write_png_lr_rg "$IMAGE_PNG"
|
||||
EXPECTED_HOSTNAME="$(hostname | tr -d '\r\n')"
|
||||
phase_mark_passed "Prepare tool fixtures ($profile)"
|
||||
|
||||
echo "==> Start gateway ($profile)"
|
||||
phase_mark_start "Start gateway ($profile)"
|
||||
GATEWAY_LOG="$workspace/gateway.log"
|
||||
openclaw --profile "$profile" gateway --port "$port" --bind loopback >"$GATEWAY_LOG" 2>&1 &
|
||||
GATEWAY_PID="$!"
|
||||
@@ -560,6 +648,7 @@ run_profile() {
|
||||
fi
|
||||
}
|
||||
trap cleanup_profile EXIT
|
||||
phase_mark_passed "Start gateway ($profile)"
|
||||
|
||||
TURN1_JSON="/tmp/agent-${profile}-1.json"
|
||||
TURN2_JSON="/tmp/agent-${profile}-2.json"
|
||||
@@ -569,7 +658,7 @@ run_profile() {
|
||||
TURN4_JSON="/tmp/agent-${profile}-4.json"
|
||||
HEALTH_JSON="/tmp/health-${profile}.json"
|
||||
|
||||
echo "==> Wait for health ($profile)"
|
||||
phase_mark_start "Wait for health ($profile)"
|
||||
for _ in $(seq 1 240); do
|
||||
if openclaw --profile "$profile" health --timeout 5000 --json >/dev/null 2>&1; then
|
||||
break
|
||||
@@ -581,14 +670,46 @@ run_profile() {
|
||||
dump_profile_debug "$profile" "$HEALTH_JSON" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
phase_mark_passed "Wait for health ($profile)"
|
||||
|
||||
echo "==> Agent turns ($profile)"
|
||||
phase_mark_start "Agent turns ($profile)"
|
||||
|
||||
TURN1_SESSION_ID="${SESSION_ID_PREFIX}-read-proof"
|
||||
SESSION_JSONL="$(session_jsonl_path "$profile" "$TURN1_SESSION_ID")"
|
||||
run_agent_turn "$profile" "$TURN1_SESSION_ID" \
|
||||
"Use the read tool (not exec) to read ${PROOF_TXT}. Reply with the exact contents only (no extra whitespace)." \
|
||||
"$TURN1_JSON"
|
||||
local prompt1
|
||||
prompt1="Use the read tool (not exec) to read ${PROOF_TXT}. Reply with the exact contents only (no extra whitespace)."
|
||||
local prompt2
|
||||
prompt2=$'Use the write tool (not exec) to write exactly this string into '"${PROOF_COPY}"$':\n'"${PROOF_VALUE}"$'\nReply with exactly: WROTE'
|
||||
local prompt3
|
||||
prompt3="Use the exec tool to run this command: hostname. Reply with the exact stdout only (trim trailing newline)."
|
||||
local prompt3b
|
||||
prompt3b=$'Use the write tool to write exactly this string into '"${HOSTNAME_TXT}"$':\n'"${EXPECTED_HOSTNAME}"$'\nReply with exactly: WROTE'
|
||||
local prompt4
|
||||
prompt4="Use the image tool on ${IMAGE_PNG}. Determine which color is on the left half and which is on the right half. Then use the write tool to write exactly: LEFT=RED RIGHT=GREEN into ${IMAGE_TXT}. Reply with exactly: LEFT=RED RIGHT=GREEN"
|
||||
TURN2_SESSION_ID="${SESSION_ID_PREFIX}-write-copy"
|
||||
TURN3_SESSION_ID="${SESSION_ID_PREFIX}-exec-hostname"
|
||||
TURN3B_SESSION_ID="${SESSION_ID_PREFIX}-write-hostname"
|
||||
TURN4_SESSION_ID="${SESSION_ID_PREFIX}-image-write"
|
||||
if [[ "$AGENT_TURNS_PARALLEL" == "1" ]]; then
|
||||
local turn_pids=()
|
||||
run_agent_turn_bg "read proof" "$profile" "$TURN1_SESSION_ID" "$prompt1" "$TURN1_JSON"
|
||||
turn_pids+=("$RUN_AGENT_TURN_BG_PID")
|
||||
run_agent_turn_bg "write proof copy" "$profile" "$TURN2_SESSION_ID" "$prompt2" "$TURN2_JSON"
|
||||
turn_pids+=("$RUN_AGENT_TURN_BG_PID")
|
||||
run_agent_turn_bg "exec hostname" "$profile" "$TURN3_SESSION_ID" "$prompt3" "$TURN3_JSON"
|
||||
turn_pids+=("$RUN_AGENT_TURN_BG_PID")
|
||||
run_agent_turn_bg "write hostname" "$profile" "$TURN3B_SESSION_ID" "$prompt3b" "$TURN3B_JSON"
|
||||
turn_pids+=("$RUN_AGENT_TURN_BG_PID")
|
||||
run_agent_turn_bg "image write" "$profile" "$TURN4_SESSION_ID" "$prompt4" "$TURN4_JSON"
|
||||
turn_pids+=("$RUN_AGENT_TURN_BG_PID")
|
||||
wait_agent_turn_batch "${turn_pids[@]}"
|
||||
else
|
||||
run_agent_turn_logged "read proof" "$profile" "$TURN1_SESSION_ID" "$prompt1" "$TURN1_JSON"
|
||||
run_agent_turn_logged "write proof copy" "$profile" "$TURN2_SESSION_ID" "$prompt2" "$TURN2_JSON"
|
||||
run_agent_turn_logged "exec hostname" "$profile" "$TURN3_SESSION_ID" "$prompt3" "$TURN3_JSON"
|
||||
run_agent_turn_logged "write hostname" "$profile" "$TURN3B_SESSION_ID" "$prompt3b" "$TURN3B_JSON"
|
||||
run_agent_turn_logged "image write" "$profile" "$TURN4_SESSION_ID" "$prompt4" "$TURN4_JSON"
|
||||
fi
|
||||
|
||||
assert_agent_json_has_text "$TURN1_JSON"
|
||||
assert_agent_json_ok "$TURN1_JSON" "$agent_model_provider"
|
||||
local reply1
|
||||
@@ -598,11 +719,6 @@ run_profile() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local prompt2
|
||||
prompt2=$'Use the write tool (not exec) to write exactly this string into '"${PROOF_COPY}"$':\n'"${reply1}"$'\nReply with exactly: WROTE'
|
||||
TURN2_SESSION_ID="${SESSION_ID_PREFIX}-write-copy"
|
||||
SESSION_JSONL="$(session_jsonl_path "$profile" "$TURN2_SESSION_ID")"
|
||||
run_agent_turn "$profile" "$TURN2_SESSION_ID" "$prompt2" "$TURN2_JSON"
|
||||
assert_agent_json_has_text "$TURN2_JSON"
|
||||
assert_agent_json_ok "$TURN2_JSON" "$agent_model_provider"
|
||||
local copy_value
|
||||
@@ -612,8 +728,7 @@ run_profile() {
|
||||
exit 1
|
||||
fi
|
||||
TURN2B_SESSION_ID="${SESSION_ID_PREFIX}-read-copy"
|
||||
SESSION_JSONL="$(session_jsonl_path "$profile" "$TURN2B_SESSION_ID")"
|
||||
run_agent_turn "$profile" "$TURN2B_SESSION_ID" \
|
||||
run_agent_turn_logged "read proof copy" "$profile" "$TURN2B_SESSION_ID" \
|
||||
"Use the read tool (not exec) to read ${PROOF_COPY}. Reply with the exact contents only (no extra whitespace)." \
|
||||
"$TURN2B_JSON"
|
||||
assert_agent_json_has_text "$TURN2B_JSON"
|
||||
@@ -625,11 +740,6 @@ run_profile() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TURN3_SESSION_ID="${SESSION_ID_PREFIX}-exec-hostname"
|
||||
SESSION_JSONL="$(session_jsonl_path "$profile" "$TURN3_SESSION_ID")"
|
||||
run_agent_turn "$profile" "$TURN3_SESSION_ID" \
|
||||
"Use the exec tool to run this command: hostname. Reply with the exact stdout only (trim trailing newline)." \
|
||||
"$TURN3_JSON"
|
||||
assert_agent_json_has_text "$TURN3_JSON"
|
||||
assert_agent_json_ok "$TURN3_JSON" "$agent_model_provider"
|
||||
local reply3
|
||||
@@ -638,11 +748,6 @@ run_profile() {
|
||||
echo "ERROR: agent did not run hostname correctly ($profile): $reply3" >&2
|
||||
exit 1
|
||||
fi
|
||||
local prompt3b
|
||||
prompt3b=$'Use the write tool to write exactly this string into '"${HOSTNAME_TXT}"$':\n'"${reply3}"$'\nReply with exactly: WROTE'
|
||||
TURN3B_SESSION_ID="${SESSION_ID_PREFIX}-write-hostname"
|
||||
SESSION_JSONL="$(session_jsonl_path "$profile" "$TURN3B_SESSION_ID")"
|
||||
run_agent_turn "$profile" "$TURN3B_SESSION_ID" "$prompt3b" "$TURN3B_JSON"
|
||||
assert_agent_json_has_text "$TURN3B_JSON"
|
||||
assert_agent_json_ok "$TURN3B_JSON" "$agent_model_provider"
|
||||
if [[ "$(cat "$HOSTNAME_TXT" 2>/dev/null | tr -d '\r\n' || true)" != "$EXPECTED_HOSTNAME" ]]; then
|
||||
@@ -650,11 +755,6 @@ run_profile() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TURN4_SESSION_ID="${SESSION_ID_PREFIX}-image-write"
|
||||
SESSION_JSONL="$(session_jsonl_path "$profile" "$TURN4_SESSION_ID")"
|
||||
run_agent_turn "$profile" "$TURN4_SESSION_ID" \
|
||||
"Use the image tool on ${IMAGE_PNG}. Determine which color is on the left half and which is on the right half. Then use the write tool to write exactly: LEFT=RED RIGHT=GREEN into ${IMAGE_TXT}. Reply with exactly: LEFT=RED RIGHT=GREEN" \
|
||||
"$TURN4_JSON"
|
||||
assert_agent_json_has_text "$TURN4_JSON"
|
||||
assert_agent_json_ok "$TURN4_JSON" "$agent_model_provider"
|
||||
if [[ "$(cat "$IMAGE_TXT" 2>/dev/null | tr -d '\r\n' || true)" != "LEFT=RED RIGHT=GREEN" ]]; then
|
||||
@@ -667,8 +767,9 @@ run_profile() {
|
||||
echo "ERROR: agent reply did not contain expected marker ($profile): $reply4" >&2
|
||||
exit 1
|
||||
fi
|
||||
phase_mark_passed "Agent turns ($profile)"
|
||||
|
||||
echo "==> Verify tool usage via session transcript ($profile)"
|
||||
phase_mark_start "Verify tool usage via session transcript ($profile)"
|
||||
# Give the gateway a moment to flush transcripts.
|
||||
sleep 1
|
||||
assert_session_used_tools "$(session_jsonl_path "$profile" "$TURN1_SESSION_ID")" read
|
||||
@@ -677,6 +778,7 @@ run_profile() {
|
||||
assert_session_used_tools "$(session_jsonl_path "$profile" "$TURN3_SESSION_ID")" exec
|
||||
assert_session_used_tools "$(session_jsonl_path "$profile" "$TURN3B_SESSION_ID")" write
|
||||
assert_session_used_tools "$(session_jsonl_path "$profile" "$TURN4_SESSION_ID")" image write
|
||||
phase_mark_passed "Verify tool usage via session transcript ($profile)"
|
||||
|
||||
cleanup_profile
|
||||
trap - EXIT
|
||||
|
||||
@@ -25,6 +25,7 @@ docker run --rm \
|
||||
-e OPENCLAW_INSTALL_E2E_PREVIOUS="${OPENCLAW_INSTALL_E2E_PREVIOUS:-}" \
|
||||
-e OPENCLAW_INSTALL_E2E_SKIP_PREVIOUS="${OPENCLAW_INSTALL_E2E_SKIP_PREVIOUS:-0}" \
|
||||
-e OPENCLAW_INSTALL_E2E_AGENT_TURN_TIMEOUT_SECONDS="${OPENCLAW_INSTALL_E2E_AGENT_TURN_TIMEOUT_SECONDS:-600}" \
|
||||
-e OPENCLAW_INSTALL_E2E_AGENT_TURNS_PARALLEL="${OPENCLAW_INSTALL_E2E_AGENT_TURNS_PARALLEL:-1}" \
|
||||
-e OPENCLAW_NO_ONBOARD=1 \
|
||||
-e OPENAI_API_KEY \
|
||||
-e ANTHROPIC_API_KEY \
|
||||
|
||||
@@ -114,6 +114,23 @@ describe("docker build helper", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("times and parallelizes release installer E2E agent turns after gateway startup", () => {
|
||||
const runner = readFileSync(INSTALL_E2E_RUNNER_PATH, "utf8");
|
||||
const wrapper = readFileSync("scripts/test-install-sh-e2e-docker.sh", "utf8");
|
||||
|
||||
expect(runner).toContain(
|
||||
'AGENT_TURNS_PARALLEL="${OPENCLAW_INSTALL_E2E_AGENT_TURNS_PARALLEL:-1}"',
|
||||
);
|
||||
expect(runner).toContain("time_phase");
|
||||
expect(runner).toContain("phase_mark_start");
|
||||
expect(runner).toContain("run_agent_turn_bg");
|
||||
expect(runner).toContain("wait_agent_turn_batch");
|
||||
expect(runner).toContain('run_agent_turn_bg "read proof"');
|
||||
expect(runner).toContain('run_agent_turn_bg "image write"');
|
||||
expect(runner).toContain('run_agent_turn_logged "read proof copy"');
|
||||
expect(wrapper).toContain("OPENCLAW_INSTALL_E2E_AGENT_TURNS_PARALLEL");
|
||||
});
|
||||
|
||||
it("keeps package acceptance plugin coverage offline-capable", () => {
|
||||
const scenarios = readFileSync(DOCKER_E2E_SCENARIOS_PATH, "utf8");
|
||||
|
||||
@@ -214,7 +231,7 @@ describe("docker build helper", () => {
|
||||
|
||||
expect(runner).toContain('rm -f "$workspace/BOOTSTRAP.md"');
|
||||
expect(runner.indexOf('rm -f "$workspace/BOOTSTRAP.md"')).toBeLessThan(
|
||||
runner.indexOf('echo "==> Agent turns ($profile)"'),
|
||||
runner.indexOf('phase_mark_start "Agent turns ($profile)"'),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -177,6 +177,15 @@ describe("package artifact reuse", () => {
|
||||
expect(workflow).toContain("suite_id: native-live-extensions-media-music-google");
|
||||
expect(workflow).toContain("suite_id: native-live-extensions-media-music-minimax");
|
||||
expect(workflow).toContain("suite_id: native-live-extensions-media-video");
|
||||
expect(workflow).toContain("suite_group: native-live-extensions-media-video");
|
||||
expect(workflow).toContain("OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=google,minimax");
|
||||
expect(workflow).toContain("OPENCLAW_LIVE_VIDEO_GENERATION_PROVIDERS=openai,openrouter,xai");
|
||||
expect(workflow).toContain("suite_group: native-live-src-gateway-profiles-opencode-go");
|
||||
expect(workflow).toContain("opencode-go/mimo-v2-omni");
|
||||
expect(workflow).toContain(
|
||||
"inputs.live_suite_filter == 'native-live-src-gateway-profiles-opencode-go'",
|
||||
);
|
||||
expect(workflow).toContain("inputs.live_suite_filter == 'native-live-extensions-media-video'");
|
||||
expect(workflow).not.toContain("needs_ffmpeg: true");
|
||||
expect(retryHelper).toContain("OPENCLAW_LIVE_COMMAND_ATTEMPTS:-2");
|
||||
expect(retryHelper).toContain("ECONNRESET");
|
||||
|
||||
@@ -201,6 +201,8 @@ describe("bun global install smoke", () => {
|
||||
expect(workflow).toContain("install-smoke-fast:");
|
||||
expect(workflow).toContain("run_fast_install_smoke");
|
||||
expect(workflow).toContain("run_full_install_smoke");
|
||||
expect(workflow).toContain("cache-from: type=gha,scope=openclaw-dockerfile-smoke");
|
||||
expect(workflow).toContain("cache-to: type=gha,scope=openclaw-dockerfile-smoke,mode=max");
|
||||
expect(workflow).toContain('OPENCLAW_INSTALL_SMOKE_SKIP_NPM_GLOBAL: "1"');
|
||||
expect(releaseChecks).toContain("install_smoke_release_checks:");
|
||||
expect(releaseChecks).toContain("uses: ./.github/workflows/install-smoke.yml");
|
||||
|
||||
Reference in New Issue
Block a user