openclaw/scripts/e2e/codex-npm-plugin-live-docker.sh
Josh Avant fdae00fe82
chore(qa): qualify execution identity across live boundaries (#156033)
* test(qa): repair forced restart and message inspection harnesses

* test(e2e): qualify installed execution identity persistence

Extend the packed npm onboarding owner with audit opt-in, one deterministic local turn, installed CLI inspection before and after Gateway restart, and isolated-state/privacy assertions.

Qualification from 267bb9d2: new shell-flow proof fails before the harness change at the missing opt-in. All 53 focused support tests pass; final changed shell/SQLite selection took 26.94s with one worker. Changed checks and P0-P2 autoreview pass; the full export scan was reused after brace-only lint fixes. Packed Docker proof remains a remote handoff.

* test(qa): qualify live execution and channel identity

* test(audit): qualify execution identity lifecycle gaps

* test(qa): align suppression audit with required replies

* test(qa): qualify Telegram participant identity

* test(qa): provision Telegram identity fixtures

* test(qa): qualify private-production Telegram identity

* test(e2e): preserve installed execution identity proof

* test(qa): await post-delivery memory maintenance

* fix(qa): repair qualification CI boundaries
2026-09-22 20:15:02 -05:00

586 lines
24 KiB
Bash

#!/usr/bin/env bash
# Bash 5.3+ can deadlock writing heredoc pipes on macOS before the reader starts.
if [[ ${OSTYPE:-} == darwin* && $BASH != /bin/bash ]] && ((BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3))); then
exec /bin/bash "$0" "$@"
fi
# Installs OpenClaw from a prepared package tarball, installs @openclaw/codex
# from a registry/git/tarball spec, and verifies a live Codex app-server turn.
set -Eeuo pipefail
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:-}"
AUDIT_IDENTITY="${OPENCLAW_CODEX_NPM_PLUGIN_AUDIT_IDENTITY:-0}"
case "$AUDIT_IDENTITY" in
0|1) ;;
*) echo "OPENCLAW_CODEX_NPM_PLUGIN_AUDIT_IDENTITY must be 0 or 1" >&2; exit 1 ;;
esac
CODEX_PLUGIN_MOUNT=()
CODEX_PLUGIN_PACK_DIR=""
CODEX_PLUGIN_REGISTRY_PACKAGE=""
CODEX_PLUGIN_REGISTRY_TARBALL=""
CODEX_PLUGIN_REGISTRY_VERSION=""
ASSERT_MAX_TEXT_FILE_BYTES="$(
docker_e2e_read_positive_int_env OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TEXT_FILE_BYTES 1048576
)"
ASSERT_MAX_ERROR_TAIL_BYTES="$(
docker_e2e_read_positive_int_env OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_ERROR_TAIL_BYTES 65536
)"
ASSERT_MAX_TRANSCRIPT_FILES="$(
docker_e2e_read_positive_int_env OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_FILES 64
)"
ASSERT_MAX_TRANSCRIPT_WALK_ENTRIES="$(
docker_e2e_read_positive_int_env OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_WALK_ENTRIES 4096
)"
ASSERT_MAX_TRANSCRIPT_SCAN_BYTES="$(
docker_e2e_read_positive_int_env OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_SCAN_BYTES 2097152
)"
AGENT_TURN_TIMEOUT_SECONDS="$(
docker_e2e_read_positive_int_env OPENCLAW_CODEX_NPM_PLUGIN_AGENT_TIMEOUT_SECONDS 420
)"
SESSION_STORE_CONTRACT="${OPENCLAW_CODEX_NPM_PLUGIN_SESSION_STORE_CONTRACT:-}"
if [[ -z "$SESSION_STORE_CONTRACT" ]]; then
if [[ -f "$CANDIDATE_ROOT/src/config/sessions/session-accessor.sqlite-contract.ts" ]]; then
SESSION_STORE_CONTRACT="sqlite"
else
# Trusted current harnesses also validate frozen targets from before the SQLite cutover.
SESSION_STORE_CONTRACT="legacy-json"
fi
fi
BINDING_STORE_CONTRACT="${OPENCLAW_CODEX_NPM_PLUGIN_BINDING_STORE_CONTRACT:-}"
if [[ -z "$BINDING_STORE_CONTRACT" ]]; then
if [[ -f "$CANDIDATE_ROOT/extensions/codex/src/app-server/session-binding-meta.ts" ]]; then
BINDING_STORE_CONTRACT="plugin-kv"
else
# Frozen targets before the binding-store migration persist a session sidecar.
BINDING_STORE_CONTRACT="legacy-sidecar"
fi
fi
if grep -q \
'continuesSourceReplyProgress' \
"$CANDIDATE_ROOT/extensions/codex/src/app-server/dynamic-tools.ts" 2>/dev/null; then
FOLLOWTHROUGH_PROGRESS_FINAL_MODE="explicit"
else
# Frozen candidates continue after omitted finality; current candidates require false.
FOLLOWTHROUGH_PROGRESS_FINAL_MODE="legacy"
fi
run_log=""
# Signal traps inherit the harness function's log redirection. Reserve the original stdout
# so EXIT cleanup cannot print the failure tail back into the log it is reading.
exec 3>&1
cleanup() {
local cleanup_status="$?"
if [ -n "${CODEX_PLUGIN_PACK_DIR:-}" ]; then
rm -rf "$CODEX_PLUGIN_PACK_DIR"
fi
if [ -n "${PACKAGE_TGZ:-}" ]; then
docker_e2e_cleanup_package_tgz "$PACKAGE_TGZ"
fi
if [ -n "${run_log:-}" ]; then
if [ "$cleanup_status" -ne 0 ]; then
docker_e2e_print_log "$run_log" >&3 || true
fi
rm -f "$run_log"
fi
return "$cleanup_status"
}
trap cleanup EXIT
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
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz codex-npm-plugin-live "$PACKAGE_TGZ")"
return 0
fi
if [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
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
configure_codex_plugin_registry_candidate() {
local source_path="$1"
local container_path="/tmp/$(basename "$source_path")"
local package_json
# Local npm-pack installs must stay untrusted. Serve the exact candidate through the
# fixture registry so this lane exercises the post-publish official install shape.
package_json="$(tar -xOf "$source_path" package/package.json)"
CODEX_PLUGIN_REGISTRY_PACKAGE="$(
node -e '
const pkg = JSON.parse(process.argv[1]);
if (pkg.name !== "@openclaw/codex") {
throw new Error(`unexpected Codex package name: ${String(pkg.name)}`);
}
process.stdout.write(pkg.name);
' "$package_json"
)"
CODEX_PLUGIN_REGISTRY_VERSION="$(
node -e '
const pkg = JSON.parse(process.argv[1]);
if (typeof pkg.version !== "string" || pkg.version.length === 0) {
throw new Error("packed Codex plugin is missing a version");
}
process.stdout.write(pkg.version);
' "$package_json"
)"
CODEX_PLUGIN_REGISTRY_TARBALL="$container_path"
CODEX_PLUGIN_MOUNT=(-v "$source_path":"$container_path":ro)
CODEX_PLUGIN_SPEC="npm:${CODEX_PLUGIN_REGISTRY_PACKAGE}@${CODEX_PLUGIN_REGISTRY_VERSION}"
}
prepare_codex_plugin_spec() {
local source_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
docker_e2e_print_log /tmp/openclaw-codex-plugin-pack.log >&2
exit 1
fi
source_path="${pack_output[0]}"
configure_codex_plugin_registry_candidate "$source_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
configure_codex_plugin_registry_candidate "$source_path"
fi
}
prepare_codex_plugin_spec
PROFILE_MOUNT=()
PROFILE_STATUS="none"
if [ -f "$PROFILE_FILE" ] && [ -r "$PROFILE_FILE" ]; then
set -a
# shellcheck disable=SC1090
source "$PROFILE_FILE"
set +a
PROFILE_MOUNT=(-v "$PROFILE_FILE":/home/appuser/.profile:ro)
PROFILE_STATUS="$PROFILE_FILE"
fi
AGENT_TURN_TIMEOUT_SECONDS="$(
docker_e2e_read_positive_int_env OPENCLAW_CODEX_NPM_PLUGIN_AGENT_TIMEOUT_SECONDS "$AGENT_TURN_TIMEOUT_SECONDS"
)"
COMMAND_TIMEOUT="${OPENCLAW_E2E_COMMAND_TIMEOUT:-$((10#$AGENT_TURN_TIMEOUT_SECONDS + 60))s}"
docker_e2e_package_mount_args "$PACKAGE_TGZ"
run_log="$(docker_e2e_run_log codex-npm-plugin-live)"
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 codex-npm-plugin-live empty)"
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 \
-v "$CANDIDATE_ROOT/extensions/codex/package.json:/tmp/openclaw-candidate-codex-package.json:ro" \
-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:-1}" \
-e OPENCLAW_CODEX_NPM_PLUGIN_MODEL="${OPENCLAW_CODEX_NPM_PLUGIN_MODEL:-openai/gpt-5.4}" \
-e OPENCLAW_CODEX_NPM_PLUGIN_SPEC="$CODEX_PLUGIN_SPEC" \
-e OPENCLAW_CODEX_NPM_PLUGIN_AUDIT_IDENTITY="$AUDIT_IDENTITY" \
-e OPENCLAW_CODEX_NPM_PLUGIN_REGISTRY_PACKAGE="$CODEX_PLUGIN_REGISTRY_PACKAGE" \
-e OPENCLAW_CODEX_NPM_PLUGIN_REGISTRY_TARBALL="$CODEX_PLUGIN_REGISTRY_TARBALL" \
-e OPENCLAW_CODEX_NPM_PLUGIN_REGISTRY_VERSION="$CODEX_PLUGIN_REGISTRY_VERSION" \
-e OPENCLAW_CODEX_NPM_PLUGIN_BINDING_STORE_CONTRACT="$BINDING_STORE_CONTRACT" \
-e OPENCLAW_CODEX_NPM_PLUGIN_FOLLOWTHROUGH_PROGRESS_FINAL_MODE="$FOLLOWTHROUGH_PROGRESS_FINAL_MODE" \
-e OPENCLAW_CODEX_NPM_PLUGIN_SESSION_STORE_CONTRACT="$SESSION_STORE_CONTRACT" \
-e "OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TEXT_FILE_BYTES=$ASSERT_MAX_TEXT_FILE_BYTES" \
-e "OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_ERROR_TAIL_BYTES=$ASSERT_MAX_ERROR_TAIL_BYTES" \
-e "OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_FILES=$ASSERT_MAX_TRANSCRIPT_FILES" \
-e "OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_WALK_ENTRIES=$ASSERT_MAX_TRANSCRIPT_WALK_ENTRIES" \
-e "OPENCLAW_CODEX_NPM_PLUGIN_ASSERT_MAX_TRANSCRIPT_SCAN_BYTES=$ASSERT_MAX_TRANSCRIPT_SCAN_BYTES" \
-e "OPENCLAW_CODEX_NPM_PLUGIN_AGENT_TIMEOUT_SECONDS=$AGENT_TURN_TIMEOUT_SECONDS" \
-e "OPENCLAW_E2E_COMMAND_TIMEOUT=$COMMAND_TIMEOUT" \
-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[@]+"${CODEX_PLUGIN_MOUNT[@]}"} \
${PROFILE_MOUNT[@]+"${PROFILE_MOUNT[@]}"} \
-i "$IMAGE_NAME" bash -s >"$run_log" 2>&1 <<'EOF'; then
set -Eeuo pipefail
source scripts/lib/openclaw-e2e-instance.sh
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
export NPM_CONFIG_PREFIX="$HOME/.npm-global"
export npm_config_prefix="$NPM_CONFIG_PREFIX"
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-$XDG_CACHE_HOME/npm}"
export npm_config_cache="$NPM_CONFIG_CACHE"
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
export OPENCLAW_AGENT_HARNESS_FALLBACK=none
for profile_path in "$HOME/.profile" /home/appuser/.profile; do
if [ -f "$profile_path" ] && [ -r "$profile_path" ]; then
set +e +u
source "$profile_path"
set -Eeuo pipefail
break
fi
done
if [ -z "${OPENAI_API_KEY:-}" ]; then
echo "ERROR: OPENAI_API_KEY was not available after sourcing ~/.profile." >&2
exit 1
fi
export OPENAI_API_KEY
if [ -n "${OPENAI_BASE_URL:-}" ]; then
export OPENAI_BASE_URL
fi
CODEX_PLUGIN_SPEC="${OPENCLAW_CODEX_NPM_PLUGIN_SPEC:?missing OPENCLAW_CODEX_NPM_PLUGIN_SPEC}"
CODEX_PLUGIN_REGISTRY_PACKAGE="${OPENCLAW_CODEX_NPM_PLUGIN_REGISTRY_PACKAGE:-}"
CODEX_PLUGIN_REGISTRY_TARBALL="${OPENCLAW_CODEX_NPM_PLUGIN_REGISTRY_TARBALL:-}"
CODEX_PLUGIN_REGISTRY_VERSION="${OPENCLAW_CODEX_NPM_PLUGIN_REGISTRY_VERSION:-}"
MODEL_REF="${OPENCLAW_CODEX_NPM_PLUGIN_MODEL:?missing OPENCLAW_CODEX_NPM_PLUGIN_MODEL}"
POST_UNINSTALL_MODEL_REF="$MODEL_REF"
SESSION_ID="codex-npm-plugin-live"
SUCCESS_MARKER="OPENCLAW-CODEX-NPM-PLUGIN-LIVE-OK"
AGENT_TURN_TIMEOUT_SECONDS="${OPENCLAW_CODEX_NPM_PLUGIN_AGENT_TIMEOUT_SECONDS:-420}"
PLUGIN_INSTALL_FLAGS=(--force)
if [ "${OPENCLAW_CODEX_NPM_PLUGIN_FORCE_UNSAFE_INSTALL:-0}" = "1" ]; then
PLUGIN_INSTALL_FLAGS+=(--dangerously-force-unsafe-install)
fi
dump_debug_logs() {
local status="$1"
debug_logs_dumped=1
echo "Codex npm plugin live scenario failed with exit code $status" >&2
openclaw_e2e_dump_logs \
/tmp/openclaw-install.log \
/tmp/openclaw-codex-plugin-registry.log \
/tmp/openclaw-codex-plugin-install.log \
/tmp/openclaw-codex-plugin-enable.log \
/tmp/openclaw-codex-plugins-list.json \
/tmp/openclaw-codex-plugin-inspect.json \
/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-audit-gateway.log \
/tmp/openclaw-codex-followthrough.json \
/tmp/openclaw-codex-followthrough.log \
/tmp/openclaw-codex-followthrough.err \
/tmp/openclaw-codex-plugin-uninstall.log \
/tmp/openclaw-codex-plugins-list-after-uninstall.json \
/tmp/openclaw-codex-agent-after-uninstall.json \
/tmp/openclaw-codex-agent-after-uninstall.err
}
registry_pid=""
audit_gateway_pid=""
debug_logs_dumped=0
cleanup_scenario() {
local status=$?
trap - EXIT
set +e
openclaw_e2e_stop_process "${registry_pid:-}"
openclaw_e2e_stop_process "${audit_gateway_pid:-}"
if [ "$status" -ne 0 ] && [ "$debug_logs_dumped" -eq 0 ]; then
dump_debug_logs "$status"
fi
exit "$status"
}
trap cleanup_scenario EXIT
mkdir -p "$NPM_CONFIG_PREFIX" "$XDG_CACHE_HOME" "$NPM_CONFIG_CACHE"
chmod 700 "$XDG_CACHE_HOME" "$NPM_CONFIG_CACHE" || true
openclaw_e2e_install_package /tmp/openclaw-install.log
command -v openclaw >/dev/null
openclaw_e2e_enable_openclaw_cli_timeout
if [ -n "$CODEX_PLUGIN_REGISTRY_TARBALL" ]; then
registry_port_file=/tmp/openclaw-codex-plugin-registry.port
rm -f "$registry_port_file"
OPENCLAW_NPM_REGISTRY_UPSTREAM="${OPENCLAW_CODEX_NPM_PLUGIN_REGISTRY_UPSTREAM:-${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_URL:-https://registry.npmjs.org}}" \
node scripts/e2e/lib/plugins/npm-registry-server.mjs \
"$registry_port_file" \
"$CODEX_PLUGIN_REGISTRY_PACKAGE" \
"$CODEX_PLUGIN_REGISTRY_VERSION" \
"$CODEX_PLUGIN_REGISTRY_TARBALL" \
>/tmp/openclaw-codex-plugin-registry.log 2>&1 &
registry_pid=$!
for _ in $(seq 1 100); do
if [ -s "$registry_port_file" ]; then
break
fi
if ! kill -0 "$registry_pid" 2>/dev/null; then
openclaw_e2e_print_log /tmp/openclaw-codex-plugin-registry.log >&2
exit 1
fi
sleep 0.1
done
if [ ! -s "$registry_port_file" ]; then
openclaw_e2e_print_log /tmp/openclaw-codex-plugin-registry.log >&2
echo "Timed out waiting for Codex plugin npm fixture registry." >&2
exit 1
fi
export NPM_CONFIG_REGISTRY="http://127.0.0.1:$(cat "$registry_port_file")"
export npm_config_registry="$NPM_CONFIG_REGISTRY"
fi
echo "Installing Codex plugin: $CODEX_PLUGIN_SPEC"
openclaw_e2e_fixture_plugin_command 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"
echo "Enabling Codex plugin..."
openclaw plugins enable codex >/tmp/openclaw-codex-plugin-enable.log 2>&1
openclaw plugins list --json >/tmp/openclaw-codex-plugins-list.json
openclaw plugins inspect codex --runtime --json >/tmp/openclaw-codex-plugin-inspect.json
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs assert-plugin "$CODEX_PLUGIN_SPEC"
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"
local status
echo "${label}_prompt: $message"
if openclaw agent --local \
--agent main \
--session-id "$SESSION_ID" \
--model "$MODEL_REF" \
--message "$message" \
--thinking low \
--timeout "$AGENT_TURN_TIMEOUT_SECONDS" \
--json >"$out" 2>"$err" </dev/null; then
status=0
else
status=$?
fi
echo "${label}_agent_status: $status stdout_bytes=$(wc -c <"$out" 2>/dev/null || printf 0) stderr_bytes=$(wc -c <"$err" 2>/dev/null || printf 0)"
if [ "$status" -ne 0 ]; then
dump_debug_logs "$status"
exit "$status"
fi
if ! print_agent_reply "$out" "$marker" "${label}_reply"; then
dump_debug_logs 1
exit 1
fi
}
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 </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 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"
if [ "${OPENCLAW_CODEX_NPM_PLUGIN_AUDIT_IDENTITY:-0}" = "1" ]; then
echo "Inspecting persisted Codex execution identity through the installed package Gateway..."
audit_package_root="$(openclaw_e2e_package_root "$NPM_CONFIG_PREFIX")"
audit_package_entry="$(openclaw_e2e_package_entrypoint "$audit_package_root")"
audit_gateway_pid="$(openclaw_e2e_start_gateway "$audit_package_entry" 18789 /tmp/openclaw-codex-audit-gateway.log)"
openclaw_e2e_wait_gateway_ready "$audit_gateway_pid" /tmp/openclaw-codex-audit-gateway.log
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs assert-audit "$SUCCESS_MARKER"
openclaw_e2e_stop_process "$audit_gateway_pid"
audit_gateway_pid=""
fi
FOLLOWTHROUGH_SESSION_ID="${SESSION_ID}-followthrough"
FOLLOWTHROUGH_PROGRESS_MARKER="${SUCCESS_MARKER}-FOLLOWTHROUGH-PROGRESS"
FOLLOWTHROUGH_COMPLETE_MARKER="${SUCCESS_MARKER}-FOLLOWTHROUGH-COMPLETE"
FOLLOWTHROUGH_WORKSPACE="${OPENCLAW_STATE_DIR:?missing OPENCLAW_STATE_DIR}/workspace"
FOLLOWTHROUGH_ARTIFACT="$FOLLOWTHROUGH_WORKSPACE/codex-progress-followthrough.txt"
FOLLOWTHROUGH_SUFFIX="$(node -e 'process.stdout.write(crypto.randomUUID().replaceAll("-", "").slice(0, 12))')"
mkdir -p "$FOLLOWTHROUGH_WORKSPACE"
printf 'qa_alpha=amber-%s\n' "$FOLLOWTHROUGH_SUFFIX" >"$FOLLOWTHROUGH_WORKSPACE/FOLLOWTHROUGH_ALPHA.md"
printf 'qa_beta=violet-%s\n' "$FOLLOWTHROUGH_SUFFIX" >"$FOLLOWTHROUGH_WORKSPACE/FOLLOWTHROUGH_BETA.md"
printf 'qa_gamma=silver-%s\n' "$FOLLOWTHROUGH_SUFFIX" >"$FOLLOWTHROUGH_WORKSPACE/FOLLOWTHROUGH_GAMMA.md"
rm -f "$FOLLOWTHROUGH_ARTIFACT"
case "${OPENCLAW_CODEX_NPM_PLUGIN_FOLLOWTHROUGH_PROGRESS_FINAL_MODE:?missing follow-through final mode}" in
explicit)
FOLLOWTHROUGH_PROGRESS_INSTRUCTION="with final=false"
;;
legacy)
FOLLOWTHROUGH_PROGRESS_INSTRUCTION="without passing final"
;;
*)
echo "invalid follow-through final mode: $OPENCLAW_CODEX_NPM_PLUGIN_FOLLOWTHROUGH_PROGRESS_FINAL_MODE" >&2
exit 1
;;
esac
FOLLOWTHROUGH_PROMPT="$(cat <<PROMPT
Live release follow-through check.
This is a Node.js test container: use node for any inline scripting needed for
the workspace work below. Do not assume a python executable is installed.
First call message(action=send) $FOLLOWTHROUGH_PROGRESS_INSTRUCTION and send exactly
$FOLLOWTHROUGH_PROGRESS_MARKER to this conversation. Make this progress send
your only tool call in this step,
and wait for its result before calling any other tool.
Only after that send succeeds, read FOLLOWTHROUGH_ALPHA.md,
FOLLOWTHROUGH_BETA.md, and FOLLOWTHROUGH_GAMMA.md from the workspace. Write
their three complete file lines byte-for-byte, including each key= prefix, to
./codex-progress-followthrough.txt in alpha, beta, gamma order. Write exactly
one newline after each line, including the final line.
Only after the artifact write succeeds, call message(action=send) with
final=true and send exactly $FOLLOWTHROUGH_COMPLETE_MARKER. Do not expose the
hidden values in either visible message.
PROMPT
)"
echo "Running Codex progress follow-through regression turn..."
OPENCLAW_PACKAGE_ROOT="$(openclaw_e2e_package_root "$NPM_CONFIG_PREFIX")"
if openclaw_e2e_run_command node scripts/e2e/lib/codex-npm-plugin-live/followthrough-turn.mjs \
"$OPENCLAW_PACKAGE_ROOT" \
"$FOLLOWTHROUGH_SESSION_ID" \
"$MODEL_REF" \
"$AGENT_TURN_TIMEOUT_SECONDS" \
/tmp/openclaw-codex-followthrough.json \
"$FOLLOWTHROUGH_PROMPT" \
>/tmp/openclaw-codex-followthrough.log \
2>/tmp/openclaw-codex-followthrough.err </dev/null; then
followthrough_status=0
else
followthrough_status=$?
fi
echo "followthrough_agent_status: $followthrough_status stdout_bytes=$(wc -c </tmp/openclaw-codex-followthrough.json 2>/dev/null || printf 0) stderr_bytes=$(wc -c </tmp/openclaw-codex-followthrough.err 2>/dev/null || printf 0)"
openclaw_e2e_print_log /tmp/openclaw-codex-followthrough.log
if [ "$followthrough_status" -ne 0 ]; then
dump_debug_logs "$followthrough_status"
exit "$followthrough_status"
fi
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs \
assert-followthrough \
"$FOLLOWTHROUGH_PROGRESS_MARKER" \
"$FOLLOWTHROUGH_COMPLETE_MARKER" \
"$FOLLOWTHROUGH_SESSION_ID" \
"$MODEL_REF" \
"$FOLLOWTHROUGH_ARTIFACT" \
"$FOLLOWTHROUGH_WORKSPACE/FOLLOWTHROUGH_ALPHA.md" \
"$FOLLOWTHROUGH_WORKSPACE/FOLLOWTHROUGH_BETA.md" \
"$FOLLOWTHROUGH_WORKSPACE/FOLLOWTHROUGH_GAMMA.md"
echo "followthrough_reply: ${FOLLOWTHROUGH_PROGRESS_MARKER} -> ${FOLLOWTHROUGH_COMPLETE_MARKER}"
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
if openclaw agent --local \
--agent main \
--session-id "${SESSION_ID}-after-uninstall" \
--model "$POST_UNINSTALL_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; then
post_uninstall_status=0
else
post_uninstall_status=$?
fi
node scripts/e2e/lib/codex-npm-plugin-live/assertions.mjs assert-agent-error "$post_uninstall_status"
echo "Codex npm plugin live Docker E2E passed"
EOF
exit 1
fi
awk '/TRANSCRIPT_BEGIN/{printing=1} printing{print} /TRANSCRIPT_END/{printing=0}' "$run_log"
echo "Codex npm plugin live Docker E2E passed"