ci: add live Codex plugin release check

This commit is contained in:
Peter Steinberger
2026-05-21 08:44:07 +01:00
parent b05c6158c0
commit 3eb2d64392
10 changed files with 316 additions and 64 deletions
@@ -88,6 +88,11 @@ on:
required: false
default: ""
type: string
codex_plugin_spec:
description: Optional Codex plugin install spec for live Docker package checks; blank derives from release_package_spec or packs the selected ref
required: false
default: ""
type: string
npm_telegram_provider_mode:
description: Provider mode for the package Telegram E2E lane
required: false
@@ -151,6 +156,7 @@ jobs:
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
EVIDENCE_PACKAGE_SPEC: ${{ inputs.evidence_package_spec }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'full' }}
RERUN_GROUP: ${{ inputs.rerun_group }}
@@ -208,6 +214,9 @@ jobs:
else
echo "- Package Acceptance package spec: SHA-built release artifact"
fi
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
echo "- Codex plugin spec: \`${CODEX_PLUGIN_SPEC}\`"
fi
} >> "$GITHUB_STEP_SUMMARY"
docker_runtime_assets_preflight:
@@ -463,6 +472,7 @@ jobs:
CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
run: |
set -euo pipefail
@@ -558,6 +568,9 @@ jobs:
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
echo "- Package Acceptance package spec: \`${PACKAGE_ACCEPTANCE_PACKAGE_SPEC}\`"
fi
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
echo "- Codex plugin spec: \`${CODEX_PLUGIN_SPEC}\`"
fi
} >> "$GITHUB_STEP_SUMMARY"
child_rerun_group="$RERUN_GROUP"
@@ -586,6 +599,9 @@ jobs:
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
args+=(-f package_acceptance_package_spec="$PACKAGE_ACCEPTANCE_PACKAGE_SPEC")
fi
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
args+=(-f codex_plugin_spec="$CODEX_PLUGIN_SPEC")
fi
dispatch_and_wait openclaw-release-checks.yml "${args[@]}"
@@ -68,6 +68,11 @@ on:
required: false
default: ""
type: string
codex_plugin_spec:
description: Optional Codex plugin install spec for the live package lane; blank packs extensions/codex from the selected ref
required: false
default: ""
type: string
include_live_suites:
description: Whether to run live-provider coverage
required: false
@@ -173,6 +178,11 @@ on:
required: false
default: ""
type: string
codex_plugin_spec:
description: Optional Codex plugin install spec for the live package lane; blank packs extensions/codex from the selected ref
required: false
default: ""
type: string
include_live_suites:
description: Whether to run live-provider coverage
required: false
@@ -631,7 +641,7 @@ jobs:
profiles: stable full
- chunk_id: package-update-openai
label: package/update OpenAI install
timeout_minutes: 20
timeout_minutes: 45
profiles: beta minimum stable full
- chunk_id: package-update-anthropic
label: package/update Anthropic install
@@ -734,6 +744,7 @@ jobs:
OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }}
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_DOCKER_ALL_RELEASE_PROFILE: ${{ inputs.release_test_profile }}
OPENCLAW_CODEX_NPM_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
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 }}
@@ -974,6 +985,7 @@ jobs:
OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || 'docker-e2e-package' }}
OPENCLAW_DOCKER_E2E_REPO_ROOT: ${{ github.workspace }}
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
OPENCLAW_CODEX_NPM_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
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 }}
@@ -78,6 +78,11 @@ on:
required: false
default: ""
type: string
codex_plugin_spec:
description: Optional Codex plugin install spec for live Docker package checks; blank derives from release_package_spec or packs the selected ref
required: false
default: ""
type: string
concurrency:
group: openclaw-release-checks-${{ inputs.expected_sha || inputs.ref }}-${{ inputs.rerun_group }}
@@ -112,6 +117,7 @@ jobs:
qa_live_slack_enabled: ${{ steps.inputs.outputs.qa_live_slack_enabled }}
release_package_spec: ${{ steps.inputs.outputs.release_package_spec }}
package_acceptance_package_spec: ${{ steps.inputs.outputs.package_acceptance_package_spec }}
codex_plugin_spec: ${{ steps.inputs.outputs.codex_plugin_spec }}
steps:
- name: Require trusted workflow ref for release checks
env:
@@ -262,6 +268,7 @@ jobs:
RELEASE_QA_SLACK_LIVE_CI_ENABLED: ${{ vars.OPENCLAW_RELEASE_QA_SLACK_LIVE_CI_ENABLED || 'false' }}
RELEASE_PACKAGE_SPEC_INPUT: ${{ inputs.release_package_spec }}
RELEASE_PACKAGE_ACCEPTANCE_PACKAGE_SPEC_INPUT: ${{ inputs.package_acceptance_package_spec }}
RELEASE_CODEX_PLUGIN_SPEC_INPUT: ${{ inputs.codex_plugin_spec }}
run: |
set -euo pipefail
qa_live_matrix_enabled=true
@@ -307,6 +314,10 @@ jobs:
if [[ "$release_profile" == "full" ]]; then
run_release_soak=true
fi
codex_plugin_spec="$RELEASE_CODEX_PLUGIN_SPEC_INPUT"
if [[ -z "${codex_plugin_spec// }" && "$RELEASE_PACKAGE_SPEC_INPUT" =~ ^openclaw@(.+)$ ]]; then
codex_plugin_spec="npm:@openclaw/codex@${BASH_REMATCH[1]}"
fi
filter="$(printf '%s' "$RELEASE_LIVE_SUITE_FILTER_INPUT" | tr '[:upper:]' '[:lower:]')"
if [[ -n "${filter// }" ]]; then
@@ -387,6 +398,7 @@ jobs:
printf 'qa_live_slack_enabled=%s\n' "$qa_live_slack_enabled"
printf 'release_package_spec=%s\n' "$RELEASE_PACKAGE_SPEC_INPUT"
printf 'package_acceptance_package_spec=%s\n' "$RELEASE_PACKAGE_ACCEPTANCE_PACKAGE_SPEC_INPUT"
printf 'codex_plugin_spec=%s\n' "$codex_plugin_spec"
} >> "$GITHUB_OUTPUT"
- name: Summarize validated ref
@@ -403,6 +415,7 @@ jobs:
RELEASE_CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
CODEX_PLUGIN_SPEC: ${{ steps.inputs.outputs.codex_plugin_spec }}
run: |
{
echo "## Release checks"
@@ -432,6 +445,11 @@ jobs:
else
echo "- Package Acceptance package spec: prepared release artifact"
fi
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
echo "- Codex plugin spec: \`${CODEX_PLUGIN_SPEC}\`"
else
echo "- Codex plugin spec: packed from selected ref"
fi
if [[ "$RUN_RELEASE_SOAK" == "true" ]]; then
echo "- This run will execute blocking release validation plus exhaustive live/Docker soak coverage."
else
@@ -640,6 +658,7 @@ jobs:
include_live_suites: false
release_test_profile: ${{ needs.resolve_target.outputs.release_profile }}
package_artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
codex_plugin_spec: ${{ needs.resolve_target.outputs.codex_plugin_spec }}
secrets: *live_e2e_release_secrets
package_acceptance_release_checks:
+3 -3
View File
@@ -154,7 +154,7 @@ Every lane uploads GitHub artifacts. When `CLAWGRIT_REPORTS_TOKEN` is configured
## Full Release Validation
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, cross-OS package checks, QA Lab parity, Matrix, and Telegram lanes. Stable/default runs keep exhaustive live/E2E and Docker release-path coverage behind `run_release_soak=true`; `release_profile=full` forces that soak coverage on so broad advisory validation remains broad. With `rerun_group=all` and `release_profile=full`, it also runs `NPM Telegram Beta E2E` against the `release-package-under-test` artifact from release checks. After publishing, pass `release_package_spec` to reuse the shipped npm package across release checks, Package Acceptance, Docker, cross-OS, and Telegram without rebuilding. Use `npm_telegram_package_spec` only when Telegram must prove a different package.
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, cross-OS package checks, QA Lab parity, Matrix, and Telegram lanes. Stable/default runs keep exhaustive live/E2E and Docker release-path coverage behind `run_release_soak=true`; `release_profile=full` forces that soak coverage on so broad advisory validation remains broad. With `rerun_group=all` and `release_profile=full`, it also runs `NPM Telegram Beta E2E` against the `release-package-under-test` artifact from release checks. After publishing, pass `release_package_spec` to reuse the shipped npm package across release checks, Package Acceptance, Docker, cross-OS, and Telegram without rebuilding. Use `npm_telegram_package_spec` only when Telegram must prove a different package. The Codex plugin live package lane uses the same selected state by default: published `release_package_spec=openclaw@<tag>` derives `codex_plugin_spec=npm:@openclaw/codex@<tag>`, while SHA/artifact runs pack `extensions/codex` from the selected ref. Set `codex_plugin_spec` explicitly for custom plugin sources such as `npm:`, `npm-pack:`, or `git:` specs.
See [Full release validation](/reference/full-release-validation) for the
stage matrix, exact workflow job names, profile differences, artifacts, and
@@ -203,7 +203,7 @@ The umbrella records the dispatched child run ids, and the final `Verify full va
For recovery, both `Full Release Validation` and `OpenClaw Release Checks` accept `rerun_group`. Use `all` for a release candidate, `ci` for only the normal full CI child, `plugin-prerelease` for only the plugin prerelease child, `release-checks` for every release child, or a narrower group: `install-smoke`, `cross-os`, `live-e2e`, `package`, `qa`, `qa-parity`, `qa-live`, or `npm-telegram` on the umbrella. This keeps a failed release box rerun bounded after a focused fix. For one failed cross-OS lane, combine `rerun_group=cross-os` with `cross_os_suite_filter`, for example `windows/packaged-upgrade`; long cross-OS commands emit heartbeat lines and packaged-upgrade summaries include per-phase timings. QA release-check lanes are advisory except the standard runtime tool coverage gate, which blocks when required OpenClaw dynamic tools drift or disappear from the standard tier summary.
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the selected ref once into a `release-package-under-test` tarball, then passes that artifact to cross-OS checks and Package Acceptance, plus the live/E2E release-path Docker workflow when soak coverage runs. That keeps the package bytes consistent across release boxes and avoids repacking the same candidate in multiple child jobs.
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the selected ref once into a `release-package-under-test` tarball, then passes that artifact to cross-OS checks and Package Acceptance, plus the live/E2E release-path Docker workflow when soak coverage runs. That keeps the package bytes consistent across release boxes and avoids repacking the same candidate in multiple child jobs. For the Codex npm-plugin live lane, release checks either pass a matching published plugin spec derived from `release_package_spec`, pass the operator-supplied `codex_plugin_spec`, or leave the input blank so the Docker script packs the selected checkout's Codex plugin.
Duplicate `Full Release Validation` runs for `ref=main` and `rerun_group=all`
supersede the older umbrella. The parent monitor cancels any child workflow it
@@ -371,7 +371,7 @@ Release Docker coverage runs smaller chunked jobs with `OPENCLAW_SKIP_DOCKER_BUI
- `OPENCLAW_DOCKER_ALL_PROFILE=release-path`
- `OPENCLAW_DOCKER_ALL_CHUNK=core | package-update-openai | package-update-anthropic | package-update-core | plugins-runtime-plugins | plugins-runtime-services | plugins-runtime-install-a..h`
Current release Docker chunks are `core`, `package-update-openai`, `package-update-anthropic`, `package-update-core`, `plugins-runtime-plugins`, `plugins-runtime-services`, and `plugins-runtime-install-a` through `plugins-runtime-install-h`. `plugins-runtime-core`, `plugins-runtime`, and `plugins-integrations` remain aggregate plugin/runtime aliases. The `install-e2e` lane alias remains the aggregate manual rerun alias for both provider installer lanes.
Current release Docker chunks are `core`, `package-update-openai`, `package-update-anthropic`, `package-update-core`, `plugins-runtime-plugins`, `plugins-runtime-services`, and `plugins-runtime-install-a` through `plugins-runtime-install-h`. `package-update-openai` includes the live Codex plugin package lane, which installs the candidate OpenClaw package, installs the Codex plugin from `codex_plugin_spec` or a same-ref tarball with explicit Codex CLI install approval, runs Codex CLI preflight, then runs multiple same-session OpenClaw agent turns against OpenAI. `plugins-runtime-core`, `plugins-runtime`, and `plugins-integrations` remain aggregate plugin/runtime aliases. The `install-e2e` lane alias remains the aggregate manual rerun alias for both provider installer lanes.
OpenWebUI is folded into `plugins-runtime-services` when full release-path coverage requests it, and keeps a standalone `openwebui` chunk only for OpenWebUI-only dispatches. Bundled-channel update lanes retry once for transient npm network failures.
+15 -9
View File
@@ -38,6 +38,12 @@ beta publish, pass `release_package_spec=openclaw@YYYY.M.D-beta.N` to reuse the
shipped npm package across release checks, Package Acceptance, cross-OS,
release-path Docker, and package Telegram. Use `package_acceptance_package_spec`
only when Package Acceptance should intentionally prove a different package.
The Codex plugin live package lane follows the same state: published
`release_package_spec` values derive `codex_plugin_spec=npm:@openclaw/codex@<version>`;
SHA/artifact runs pack `extensions/codex` from the selected ref; and operators
can set `codex_plugin_spec` directly for `npm:`, `npm-pack:`, or `git:` plugin
sources. The lane grants the explicit Codex CLI install approval required by
that plugin, then runs Codex CLI preflight and same-session OpenAI agent turns.
## Top-level stages
@@ -81,15 +87,15 @@ or Docker-facing stages need it.
The Docker release-path stage runs these chunks when `live_suite_filter` is
empty:
| Chunk | Coverage |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `core` | Core Docker release-path smoke lanes. |
| `package-update-openai` | OpenAI package install/update behavior, Codex on-demand install, and Chat Completions tool calls. |
| `package-update-anthropic` | Anthropic package install and update behavior. |
| `package-update-core` | Provider-neutral package and update behavior. |
| `plugins-runtime-plugins` | Plugin runtime lanes that exercise plugin behavior. |
| `plugins-runtime-services` | Service-backed and live plugin runtime lanes; includes OpenWebUI when requested. |
| `plugins-runtime-install-a` through `plugins-runtime-install-h` | Plugin install/runtime batches split for parallel release validation. |
| Chunk | Coverage |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `core` | Core Docker release-path smoke lanes. |
| `package-update-openai` | OpenAI package install/update behavior, Codex on-demand install, Codex plugin live turns, and Chat Completions tool calls. |
| `package-update-anthropic` | Anthropic package install and update behavior. |
| `package-update-core` | Provider-neutral package and update behavior. |
| `plugins-runtime-plugins` | Plugin runtime lanes that exercise plugin behavior. |
| `plugins-runtime-services` | Service-backed and live plugin runtime lanes; includes OpenWebUI when requested. |
| `plugins-runtime-install-a` through `plugins-runtime-install-h` | Plugin install/runtime batches split for parallel release validation. |
Use targeted `docker_lanes=<lane[,lane]>` on the reusable live/E2E workflow when
only one Docker lane failed. The release artifacts include per-lane rerun
+146 -24
View File
@@ -1,19 +1,27 @@
#!/usr/bin/env bash
# Installs OpenClaw from a prepared package tarball, installs @openclaw/codex
# from the real npm registry, and verifies a live Codex app-server turn.
# from a registry/git/tarball spec, and verifies a live Codex app-server turn.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
SCRIPT_ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
TRUSTED_HARNESS_DIR="${OPENCLAW_LIVE_DOCKER_TRUSTED_HARNESS_DIR:-$SCRIPT_ROOT_DIR}"
CANDIDATE_ROOT="${OPENCLAW_LIVE_DOCKER_REPO_ROOT:-$SCRIPT_ROOT_DIR}"
TRUSTED_HARNESS_DIR="$(cd "$TRUSTED_HARNESS_DIR" && pwd)"
CANDIDATE_ROOT="$(cd "$CANDIDATE_ROOT" && pwd)"
ROOT_DIR="$TRUSTED_HARNESS_DIR"
source "$TRUSTED_HARNESS_DIR/scripts/lib/docker-e2e-image.sh"
source "$TRUSTED_HARNESS_DIR/scripts/lib/docker-e2e-package.sh"
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-codex-npm-plugin-live-e2e" OPENCLAW_CODEX_NPM_PLUGIN_E2E_IMAGE)"
DOCKER_TARGET="${OPENCLAW_CODEX_NPM_PLUGIN_DOCKER_TARGET:-bare}"
HOST_BUILD="${OPENCLAW_CODEX_NPM_PLUGIN_HOST_BUILD:-1}"
PACKAGE_TGZ="${OPENCLAW_CURRENT_PACKAGE_TGZ:-}"
PROFILE_FILE="${OPENCLAW_CODEX_NPM_PLUGIN_PROFILE_FILE:-${OPENCLAW_TESTBOX_PROFILE_FILE:-$HOME/.openclaw-testbox-live.profile}}"
CODEX_PLUGIN_SPEC="${OPENCLAW_CODEX_NPM_PLUGIN_SPEC:-}"
CODEX_PLUGIN_MOUNT=()
CODEX_PLUGIN_PACK_DIR=""
docker_e2e_build_or_reuse "$IMAGE_NAME" codex-npm-plugin-live "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "$DOCKER_TARGET"
docker_e2e_build_or_reuse "$IMAGE_NAME" codex-npm-plugin-live "$CANDIDATE_ROOT/scripts/e2e/Dockerfile" "$CANDIDATE_ROOT" "$DOCKER_TARGET"
prepare_package_tgz() {
if [ -n "$PACKAGE_TGZ" ]; then
@@ -24,11 +32,60 @@ prepare_package_tgz() {
echo "OPENCLAW_CODEX_NPM_PLUGIN_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ" >&2
exit 1
fi
local harness_root="$ROOT_DIR"
ROOT_DIR="$CANDIDATE_ROOT"
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz codex-npm-plugin-live)"
ROOT_DIR="$harness_root"
}
prepare_package_tgz
prepare_codex_plugin_spec() {
local source_path
local container_path
local pack_output
if [ -z "$CODEX_PLUGIN_SPEC" ]; then
CODEX_PLUGIN_PACK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-codex-plugin-pack.XXXXXX")"
(
cd "$CANDIDATE_ROOT"
node scripts/lib/plugin-npm-runtime-build.mjs extensions/codex
node scripts/lib/plugin-npm-package-manifest.mjs --run extensions/codex -- \
npm pack --json --ignore-scripts --pack-destination "$CODEX_PLUGIN_PACK_DIR"
) >/tmp/openclaw-codex-plugin-pack.log 2>&1
pack_output=()
while IFS= read -r packed_file; do
pack_output+=("$packed_file")
done < <(find "$CODEX_PLUGIN_PACK_DIR" -maxdepth 1 -type f -name '*.tgz' | sort)
if [ "${#pack_output[@]}" -ne 1 ]; then
echo "Expected one packed Codex plugin tarball; found ${#pack_output[@]}." >&2
cat /tmp/openclaw-codex-plugin-pack.log >&2 || true
exit 1
fi
source_path="${pack_output[0]}"
container_path="/tmp/$(basename "$source_path")"
CODEX_PLUGIN_MOUNT=(-v "$source_path":"$container_path":ro)
CODEX_PLUGIN_SPEC="npm-pack:$container_path"
return 0
fi
if [[ "$CODEX_PLUGIN_SPEC" == npm-pack:* ]]; then
source_path="${CODEX_PLUGIN_SPEC#npm-pack:}"
if [[ "$source_path" != /* ]]; then
source_path="$CANDIDATE_ROOT/$source_path"
fi
if [ ! -f "$source_path" ]; then
echo "Codex plugin npm-pack tarball not found: $source_path" >&2
exit 1
fi
container_path="/tmp/$(basename "$source_path")"
CODEX_PLUGIN_MOUNT=(-v "$source_path":"$container_path":ro)
CODEX_PLUGIN_SPEC="npm-pack:$container_path"
fi
}
prepare_codex_plugin_spec
PROFILE_MOUNT=()
PROFILE_STATUS="none"
if [ -f "$PROFILE_FILE" ] && [ -r "$PROFILE_FILE" ]; then
@@ -46,16 +103,18 @@ OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 codex-npm-plug
echo "Running Codex npm plugin live Docker E2E..."
echo "Profile file: $PROFILE_STATUS"
echo "Codex plugin spec: $CODEX_PLUGIN_SPEC"
if ! docker_e2e_run_with_harness \
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
-e OPENCLAW_CODEX_NPM_PLUGIN_ALLOW_BETA_COMPAT_DIAGNOSTICS="${OPENCLAW_CODEX_NPM_PLUGIN_ALLOW_BETA_COMPAT_DIAGNOSTICS:-0}" \
-e OPENCLAW_CODEX_NPM_PLUGIN_FORCE_UNSAFE_INSTALL="${OPENCLAW_CODEX_NPM_PLUGIN_FORCE_UNSAFE_INSTALL:-0}" \
-e OPENCLAW_CODEX_NPM_PLUGIN_FORCE_UNSAFE_INSTALL="${OPENCLAW_CODEX_NPM_PLUGIN_FORCE_UNSAFE_INSTALL:-1}" \
-e OPENCLAW_CODEX_NPM_PLUGIN_MODEL="${OPENCLAW_CODEX_NPM_PLUGIN_MODEL:-codex/gpt-5.4}" \
-e OPENCLAW_CODEX_NPM_PLUGIN_SPEC="${OPENCLAW_CODEX_NPM_PLUGIN_SPEC:-npm:@openclaw/codex}" \
-e OPENCLAW_CODEX_NPM_PLUGIN_SPEC="$CODEX_PLUGIN_SPEC" \
-e OPENAI_API_KEY \
-e OPENAI_BASE_URL \
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
"${CODEX_PLUGIN_MOUNT[@]}" \
"${PROFILE_MOUNT[@]}" \
-i "$IMAGE_NAME" bash -s >"$run_log" 2>&1 <<'EOF'; then
set -euo pipefail
@@ -108,6 +167,10 @@ dump_debug_logs() {
/tmp/openclaw-codex-preflight.log \
/tmp/openclaw-codex-agent.json \
/tmp/openclaw-codex-agent.err \
/tmp/openclaw-codex-agent-turn1.json \
/tmp/openclaw-codex-agent-turn1.err \
/tmp/openclaw-codex-agent-turn2.json \
/tmp/openclaw-codex-agent-turn2.err \
/tmp/openclaw-codex-plugin-uninstall.log \
/tmp/openclaw-codex-plugins-list-after-uninstall.json \
/tmp/openclaw-codex-agent-after-uninstall.json \
@@ -121,7 +184,7 @@ chmod 700 "$XDG_CACHE_HOME" "$NPM_CONFIG_CACHE" || true
openclaw_e2e_install_package /tmp/openclaw-install.log
command -v openclaw >/dev/null
echo "Installing Codex plugin from npm: $CODEX_PLUGIN_SPEC"
echo "Installing Codex plugin: $CODEX_PLUGIN_SPEC"
openclaw plugins install "$CODEX_PLUGIN_SPEC" "${PLUGIN_INSTALL_FLAGS[@]}" >/tmp/openclaw-codex-plugin-install.log 2>&1
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs configure "$MODEL_REF"
@@ -137,43 +200,101 @@ node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs assert-npm-deps
CODEX_BIN="$(node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs print-codex-bin)"
printf '%s\n' "$OPENAI_API_KEY" | "$CODEX_BIN" login --with-api-key >/dev/null
print_agent_reply() {
node -e '
const fs = require("node:fs");
const file = process.argv[1];
const marker = process.argv[2];
const label = process.argv[3];
const response = JSON.parse(fs.readFileSync(file, "utf8"));
const text = (response.payloads || [])
.map((payload) => (payload && typeof payload.text === "string" ? payload.text : ""))
.filter(Boolean)
.join("\n")
.trim();
console.log(`${label}: ${text}`);
if (!text.includes(marker)) {
console.error(`missing marker ${marker} in ${file}`);
process.exit(1);
}
' "$1" "$2" "$3"
}
run_agent_turn() {
local label="$1"
local marker="$2"
local message="$3"
local out="$4"
local err="$5"
echo "${label}_prompt: $message"
openclaw agent --local \
--agent main \
--session-id "$SESSION_ID" \
--model "$MODEL_REF" \
--message "$message" \
--thinking low \
--timeout 420 \
--json >"$out" 2>"$err" </dev/null
print_agent_reply "$out" "$marker" "${label}_reply"
}
echo "TRANSCRIPT_BEGIN"
echo "Running Codex CLI preflight via managed npm dependency..."
echo "codex_cli_prompt: Reply exactly: ${SUCCESS_MARKER}-PREFLIGHT"
"$CODEX_BIN" exec \
--json \
--color never \
--skip-git-repo-check \
"Reply exactly: ${SUCCESS_MARKER}-PREFLIGHT" >/tmp/openclaw-codex-preflight.log 2>&1
"Reply exactly: ${SUCCESS_MARKER}-PREFLIGHT" >/tmp/openclaw-codex-preflight.log 2>&1 </dev/null
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs assert-preflight "${SUCCESS_MARKER}-PREFLIGHT"
echo "codex_cli_reply: ${SUCCESS_MARKER}-PREFLIGHT"
echo "Running OpenClaw local agent turn through npm-installed Codex plugin..."
openclaw agent --local \
--agent main \
--session-id "$SESSION_ID" \
--model "$MODEL_REF" \
--message "Reply exactly: $SUCCESS_MARKER" \
--thinking low \
--timeout 420 \
--json >/tmp/openclaw-codex-agent.json 2>/tmp/openclaw-codex-agent.err
echo "Running OpenClaw local agent turns through npm-installed Codex plugin..."
run_agent_turn \
"turn1" \
"${SUCCESS_MARKER}-TURN-1" \
"Reply in one short sentence. Include token ${SUCCESS_MARKER}-TURN-1 and say hello from the OpenClaw Codex plugin." \
/tmp/openclaw-codex-agent-turn1.json \
/tmp/openclaw-codex-agent-turn1.err
run_agent_turn \
"turn2" \
"${SUCCESS_MARKER}-TURN-2" \
"Using this same conversation, name the exact token from your previous reply, then include token ${SUCCESS_MARKER}-TURN-2." \
/tmp/openclaw-codex-agent-turn2.json \
/tmp/openclaw-codex-agent-turn2.err
run_agent_turn \
"turn3" \
"$SUCCESS_MARKER" \
"Answer 7 plus 8, include token $SUCCESS_MARKER, and mention whether you saw ${SUCCESS_MARKER}-TURN-2 earlier." \
/tmp/openclaw-codex-agent.json \
/tmp/openclaw-codex-agent.err
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs assert-agent-turn "$SUCCESS_MARKER" "$SESSION_ID" "$MODEL_REF"
echo "TRANSCRIPT_END"
echo "Uninstalling Codex plugin and verifying the configured harness now fails..."
openclaw plugins uninstall codex --force >/tmp/openclaw-codex-plugin-uninstall.log 2>&1
openclaw plugins list --json >/tmp/openclaw-codex-plugins-list-after-uninstall.json
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs assert-uninstalled
set +e
openclaw agent --local \
if openclaw agent --local \
--agent main \
--session-id "${SESSION_ID}-after-uninstall" \
--model "$MODEL_REF" \
--message "Reply exactly: ${SUCCESS_MARKER}-AFTER-UNINSTALL" \
--thinking low \
--timeout 120 \
--json >/tmp/openclaw-codex-agent-after-uninstall.json 2>/tmp/openclaw-codex-agent-after-uninstall.err
after_uninstall_status=$?
set -e
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs assert-agent-error "$after_uninstall_status"
--json >/tmp/openclaw-codex-agent-after-uninstall.json 2>/tmp/openclaw-codex-agent-after-uninstall.err; then
echo "Expected OpenClaw agent to fail after Codex uninstall, got status 0" >&2
exit 1
fi
if ! grep -Fq 'Requested agent harness "codex" is not registered' /tmp/openclaw-codex-agent-after-uninstall.err &&
! grep -Fq 'Unknown model: codex/' /tmp/openclaw-codex-agent-after-uninstall.err; then
echo "Unexpected post-uninstall agent error:" >&2
cat /tmp/openclaw-codex-agent-after-uninstall.err >&2 || true
exit 1
fi
echo "Codex npm plugin live Docker E2E passed"
EOF
@@ -182,5 +303,6 @@ EOF
exit 1
fi
awk '/TRANSCRIPT_BEGIN/{printing=1} printing{print} /TRANSCRIPT_END/{printing=0}' "$run_log"
rm -f "$run_log"
echo "Codex npm plugin live Docker E2E passed"
@@ -95,6 +95,32 @@ function readInstallRecords() {
return index.installRecords || index.records || {};
}
function normalizePluginSpec(spec) {
if (spec.startsWith("npm:")) {
return {
expectedSpec: spec.slice("npm:".length),
source: "npm",
};
}
if (spec.startsWith("npm-pack:")) {
return {
artifactKind: "npm-pack",
source: "npm",
sourcePath: spec.slice("npm-pack:".length),
};
}
if (spec.startsWith("git:")) {
return {
expectedSpec: spec,
source: "git",
};
}
return {
expectedSpec: spec,
source: "npm",
};
}
function assertPlugin() {
const spec = process.argv[3] || "npm:@openclaw/codex";
const list = readJson("/tmp/openclaw-codex-plugins-list.json");
@@ -141,16 +167,32 @@ function assertPlugin() {
}
const record = readInstallRecord();
const expectedSpec = spec.replace(/^npm:/u, "");
if (record.source !== "npm") {
throw new Error(`expected codex npm install record, got source=${record.source}`);
const expected = normalizePluginSpec(spec);
if (record.source !== expected.source) {
throw new Error(
`expected codex ${expected.source} install record, got source=${record.source}`,
);
}
if (record.spec !== expectedSpec) {
throw new Error(`expected codex npm spec ${expectedSpec}, got ${record.spec}`);
if (expected.expectedSpec && record.spec !== expected.expectedSpec) {
throw new Error(`expected codex install spec ${expected.expectedSpec}, got ${record.spec}`);
}
if (!record.resolvedVersion || !record.resolvedSpec) {
if (expected.artifactKind && record.artifactKind !== expected.artifactKind) {
throw new Error(
`expected codex artifact kind ${expected.artifactKind}, got ${record.artifactKind}`,
);
}
if (
expected.sourcePath &&
realPathMaybe(record.sourcePath || "") !== realPathMaybe(expected.sourcePath)
) {
throw new Error(`expected codex source path ${expected.sourcePath}, got ${record.sourcePath}`);
}
if (record.source === "npm" && (!record.resolvedVersion || !record.resolvedSpec)) {
throw new Error(`missing codex npm resolution metadata: ${JSON.stringify(record)}`);
}
if (record.source === "git" && !record.gitCommit) {
throw new Error(`missing codex git resolution metadata: ${JSON.stringify(record)}`);
}
}
function managedNpmRoot() {
@@ -307,6 +349,12 @@ function assertAgentTurn() {
`OpenClaw agent reply did not contain ${marker}:\nstdout=${stdout}\nstderr=${stderr}`,
);
}
const executionTrace = response.meta?.executionTrace;
if (!executionTrace || executionTrace.winnerProvider !== "codex") {
throw new Error(
`expected Codex plugin to win the agent turn, got ${JSON.stringify(executionTrace)}`,
);
}
const sessionsDir = path.join(stateDir(), "agents", "main", "sessions");
const storePath = path.join(sessionsDir, "sessions.json");
@@ -333,17 +381,22 @@ function assertAgentTurn() {
if (binding.model !== modelRef.split("/").slice(1).join("/")) {
throw new Error(`unexpected Codex binding model: ${binding.model}`);
}
if (binding.modelProvider && binding.modelProvider !== "codex") {
if (binding.modelProvider && !["codex", "openai"].includes(binding.modelProvider)) {
throw new Error(`unexpected Codex binding provider: ${binding.modelProvider}`);
}
const codexHome = path.join(stateDir(), "agents", "main", "agent", "codex-home");
const nativeHome = path.join(codexHome, "home");
if (!fs.existsSync(codexHome) || !fs.existsSync(nativeHome)) {
throw new Error(`missing isolated Codex home: ${codexHome}`);
const agentDir = path.join(stateDir(), "agents", "main");
const codexHomes = [
path.join(agentDir, "codex-home"),
path.join(agentDir, "agent", "codex-home"),
path.join(path.dirname(agentDir), "codex-home"),
].filter((entry, index, entries) => entries.indexOf(entry) === index);
const codexHome = codexHomes.find((entry) => fs.existsSync(entry));
if (!codexHome) {
throw new Error(`missing isolated Codex home; checked ${codexHomes.join(", ")}`);
}
const codexSessionRoot = path.join(codexHome, "sessions");
const nativeSessionRoot = path.join(nativeHome, ".codex", "sessions");
const nativeSessionRoot = path.join(codexHome, "home", ".codex", "sessions");
assertNativeCodexSessionEvidence({
codexHome,
marker,
@@ -387,7 +440,10 @@ function assertAgentError() {
? fs.readFileSync("/tmp/openclaw-codex-agent-after-uninstall.err", "utf8")
: "";
const combined = `${stdout}\n${stderr}`;
if (!combined.includes('Requested agent harness "codex" is not registered')) {
if (
!combined.includes('Requested agent harness "codex" is not registered') &&
!combined.includes("Unknown model: codex/")
) {
throw new Error(`unexpected post-uninstall agent error:\nstdout=${stdout}\nstderr=${stderr}`);
}
}
+18 -13
View File
@@ -159,6 +159,22 @@ function liveOpenAiChatToolsLane() {
);
}
function liveCodexNpmPluginLane() {
return liveLane(
"live-codex-npm-plugin",
liveDockerScriptCommand("e2e/codex-npm-plugin-live-docker.sh"),
{
cacheKey: "codex-npm-plugin",
e2eImageKind: "bare",
provider: "openai",
resources: ["npm"],
stateScenario: "empty",
timeoutMs: 30 * 60 * 1000,
weight: 3,
},
);
}
export const mainLanes = [
liveLane("live-models", liveDockerScriptCommand("test-live-models-docker.sh"), {
providers: ["claude-cli", "google-gemini-cli"],
@@ -484,19 +500,7 @@ export const tailLanes = [
weight: 3,
},
),
liveLane(
"live-codex-npm-plugin",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-codex-npm-plugin",
{
cacheKey: "codex-npm-plugin",
e2eImageKind: "bare",
provider: "openai",
resources: ["npm"],
stateScenario: "empty",
timeoutMs: 30 * 60 * 1000,
weight: 3,
},
),
liveCodexNpmPluginLane(),
livePluginToolLane(),
liveLane(
"live-acp-bind-claude",
@@ -633,6 +637,7 @@ const releasePathPackageInstallOpenAiLanes = [
},
),
liveOpenAiChatToolsLane(),
liveCodexNpmPluginLane(),
npmLane("codex-on-demand", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:codex-on-demand", {
resources: ["service"],
stateScenario: "empty",
+6 -1
View File
@@ -113,6 +113,7 @@ describe("scripts/lib/docker-e2e-plan", () => {
expect(plan.credentials).toEqual(["anthropic", "openai"]);
expect(plan.lanes.map((lane) => lane.name)).toContain("install-e2e-openai");
expect(plan.lanes.map((lane) => lane.name)).toContain("openai-chat-tools");
expect(plan.lanes.map((lane) => lane.name)).toContain("live-codex-npm-plugin");
expect(plan.lanes.map((lane) => lane.name)).toContain("codex-on-demand");
expect(plan.lanes.map((lane) => lane.name)).toContain("install-e2e-anthropic");
expect(plan.lanes.map((lane) => lane.name)).toContain("mcp-channels");
@@ -157,6 +158,7 @@ describe("scripts/lib/docker-e2e-plan", () => {
expect(plan.releaseProfile).toBe("beta");
expect(laneNames).toContain("install-e2e-openai");
expect(laneNames).toContain("openai-chat-tools");
expect(laneNames).toContain("live-codex-npm-plugin");
expect(laneNames).toContain("install-e2e-anthropic");
expect(laneNames).toContain("update-channel-switch");
expect(laneNames).not.toContain("plugins");
@@ -246,6 +248,7 @@ describe("scripts/lib/docker-e2e-plan", () => {
expect(packageInstallOpenAi.lanes.map((lane) => lane.name)).toEqual([
"install-e2e-openai",
"openai-chat-tools",
"live-codex-npm-plugin",
"codex-on-demand",
]);
expect(packageInstallAnthropic.lanes.map((lane) => lane.name)).toEqual([
@@ -483,6 +486,7 @@ describe("scripts/lib/docker-e2e-plan", () => {
expect(packageUpdate.lanes.map((lane) => lane.name)).toEqual([
"install-e2e-openai",
"openai-chat-tools",
"live-codex-npm-plugin",
"codex-on-demand",
"install-e2e-anthropic",
"npm-onboard-channel-agent",
@@ -653,7 +657,8 @@ describe("scripts/lib/docker-e2e-plan", () => {
expect(plan.credentials).toEqual(["openai"]);
expect(plan.lanes.map(summarizeLane)).toEqual([
{
command: "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:live-codex-npm-plugin",
command:
'OPENCLAW_SKIP_DOCKER_BUILD=1 bash -c \'harness="${OPENCLAW_DOCKER_E2E_TRUSTED_HARNESS_DIR:-}"; if [ -z "$harness" ]; then if [ -d .release-harness/scripts ]; then harness=.release-harness; else harness=.; fi; fi; OPENCLAW_LIVE_DOCKER_REPO_ROOT="${OPENCLAW_DOCKER_E2E_REPO_ROOT:-$PWD}" bash "$harness/scripts/e2e/codex-npm-plugin-live-docker.sh"\'',
imageKind: "bare",
live: true,
name: "live-codex-npm-plugin",
@@ -166,6 +166,14 @@ describe("package acceptance workflow", () => {
expect(workflow).toContain(
'args+=(-f package_acceptance_package_spec="$PACKAGE_ACCEPTANCE_PACKAGE_SPEC")',
);
expect(workflow).toContain("codex_plugin_spec:");
expect(workflow).toContain('args+=(-f codex_plugin_spec="$CODEX_PLUGIN_SPEC")');
expect(releaseChecksWorkflow).toContain(
'codex_plugin_spec="npm:@openclaw/codex@${BASH_REMATCH[1]}"',
);
expect(releaseChecksWorkflow).toContain(
"codex_plugin_spec: ${{ needs.resolve_target.outputs.codex_plugin_spec }}",
);
expect(workflow).toContain("--json status,conclusion,url,attempt,headSha,jobs");
expect(workflow).toContain(
'[[ "$CHILD_WORKFLOW_REF" == release-ci/* && -n "${TARGET_SHA// }" && "$head_sha" != "$TARGET_SHA" ]]',
@@ -525,6 +533,9 @@ describe("package artifact reuse", () => {
expect(scenarios).toMatch(/liveDockerScriptCommand\(\s*"test-live-cli-backend-docker\.sh"/u);
expect(scenarios).toMatch(/liveDockerScriptCommand\(\s*"test-live-acp-bind-docker\.sh"/u);
expect(scenarios).toMatch(/liveDockerScriptCommand\(\s*"test-live-codex-harness-docker\.sh"/u);
expect(scenarios).toMatch(
/liveDockerScriptCommand\(\s*"e2e\/codex-npm-plugin-live-docker\.sh"/u,
);
expect(scenarios).toMatch(
/liveDockerScriptCommand\(\s*"test-live-subagent-announce-docker\.sh"/u,
);