diff --git a/scripts/e2e/codex-on-demand-docker.sh b/scripts/e2e/codex-on-demand-docker.sh index 788efab0c02a..24e4e3ed5785 100755 --- a/scripts/e2e/codex-on-demand-docker.sh +++ b/scripts/e2e/codex-on-demand-docker.sh @@ -6,12 +6,13 @@ 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" +source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh" IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-codex-on-demand-e2e" OPENCLAW_CODEX_ON_DEMAND_E2E_IMAGE)" DOCKER_TARGET="${OPENCLAW_CODEX_ON_DEMAND_DOCKER_TARGET:-bare}" HOST_BUILD="${OPENCLAW_CODEX_ON_DEMAND_HOST_BUILD:-1}" PACKAGE_TGZ="${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" -PREPUBLISH_PLUGIN_REGISTRY_ARGS=() +OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS=() AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT="" run_log="" @@ -20,33 +21,9 @@ run_log="" # the Codex assertions instead of failing as a silent package-install timeout. export OPENCLAW_E2E_NPM_INSTALL_TIMEOUT="${OPENCLAW_E2E_NPM_INSTALL_TIMEOUT:-1200s}" -configure_prepublish_plugin_registry() { - local registry_dir="$1" - local resolved_registry_dir - resolved_registry_dir="$(cd "$registry_dir" && pwd)" - local manifest="$resolved_registry_dir/prepublish-plugin-registry.json" - if [ ! -f "$manifest" ]; then - echo "Prepublish plugin registry manifest is missing." >&2 - exit 1 - fi - local source_sha="${OPENCLAW_DOCKER_E2E_SELECTED_SHA:-}" - local candidate_version="${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION:-}" - local manifest_sha256="${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256:-}" - source_sha="${source_sha:-$(node -e 'process.stdout.write(require(process.argv[1]).sourceSha)' "$manifest")}" - candidate_version="${candidate_version:-$(node -e 'process.stdout.write(require(process.argv[1]).candidateVersion)' "$manifest")}" - if [ -z "$manifest_sha256" ]; then - manifest_sha256="$(node -e 'const fs=require("node:fs"),crypto=require("node:crypto");process.stdout.write(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$manifest")" - fi - PREPUBLISH_PLUGIN_REGISTRY_ARGS=( - -e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR=/tmp/openclaw-prepublish-plugin-registry - -e OPENCLAW_DOCKER_E2E_SELECTED_SHA="$source_sha" - -e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION="$candidate_version" - -e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256="$manifest_sha256" - -v "$resolved_registry_dir:/tmp/openclaw-prepublish-plugin-registry:ro" - ) -} if [ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ]; then - configure_prepublish_plugin_registry "$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR" + openclaw_prepublish_plugin_registry_configure_docker_args \ + "$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR" fi cleanup() { @@ -88,7 +65,7 @@ if [ -z "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ] && OPENCLAW_DOCKER_ALL_LOG_DIR="$AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT" \ OPENCLAW_DOCKER_ALL_TIMINGS=0 \ node "$ROOT_DIR/scripts/test-docker-all.mjs" --prepare-plugin-registry >/dev/null - configure_prepublish_plugin_registry \ + openclaw_prepublish_plugin_registry_configure_docker_args \ "$AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT/prepublish-plugin-registry" fi @@ -100,7 +77,7 @@ echo "Running Codex on-demand Docker E2E..." if ! docker_e2e_run_with_harness \ -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ -e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \ - ${PREPUBLISH_PLUGIN_REGISTRY_ARGS[@]+"${PREPUBLISH_PLUGIN_REGISTRY_ARGS[@]}"} \ + "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]}" \ "${DOCKER_E2E_PACKAGE_ARGS[@]}" \ -i "$IMAGE_NAME" bash -s >"$run_log" 2>&1 <<'EOF'; then set -euo pipefail @@ -135,16 +112,8 @@ cleanup_inner() { trap cleanup_inner EXIT configure_plugin_registry() { - [ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ] || return 0 - local registry_root="/tmp/openclaw-codex-registry" - OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_REQUIRED_PACKAGES_JSON='["@openclaw/codex"]' \ - openclaw_prepublish_plugin_registry_start \ - "$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR" \ - "${OPENCLAW_DOCKER_E2E_SELECTED_SHA:?missing selected SHA}" \ - "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION:?missing candidate version}" \ - "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256:?missing manifest SHA-256}" \ - "$registry_root" \ - plugin_registry_pid + openclaw_prepublish_plugin_registry_start_mounted \ + /tmp/openclaw-codex-registry plugin_registry_pid '["@openclaw/codex"]' } mkdir -p "$NPM_CONFIG_PREFIX" "$XDG_CACHE_HOME" "$NPM_CONFIG_CACHE" diff --git a/scripts/e2e/lib/prepublish-plugin-registry.sh b/scripts/e2e/lib/prepublish-plugin-registry.sh index 5469f4e7a3f6..979e382d7055 100644 --- a/scripts/e2e/lib/prepublish-plugin-registry.sh +++ b/scripts/e2e/lib/prepublish-plugin-registry.sh @@ -1,5 +1,33 @@ #!/usr/bin/env bash +openclaw_prepublish_plugin_registry_configure_docker_args() { + local registry_dir="$1" + local resolved_registry_dir + resolved_registry_dir="$(cd "$registry_dir" && pwd)" + local manifest="$resolved_registry_dir/prepublish-plugin-registry.json" + if [ ! -f "$manifest" ]; then + echo "Prepublish plugin registry manifest is missing." >&2 + return 1 + fi + + local source_sha="${OPENCLAW_DOCKER_E2E_SELECTED_SHA:-}" + local candidate_version="${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION:-}" + local manifest_sha256="${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256:-}" + source_sha="${source_sha:-$(node -e 'process.stdout.write(require(process.argv[1]).sourceSha)' "$manifest")}" + candidate_version="${candidate_version:-$(node -e 'process.stdout.write(require(process.argv[1]).candidateVersion)' "$manifest")}" + if [ -z "$manifest_sha256" ]; then + manifest_sha256="$(node -e 'const fs=require("node:fs"),crypto=require("node:crypto");process.stdout.write(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$manifest")" + fi + + OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS=( + -e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR=/tmp/openclaw-prepublish-plugin-registry + -e OPENCLAW_DOCKER_E2E_SELECTED_SHA="$source_sha" + -e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION="$candidate_version" + -e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256="$manifest_sha256" + -v "$resolved_registry_dir:/tmp/openclaw-prepublish-plugin-registry:ro" + ) +} + openclaw_prepublish_plugin_registry_start() { local artifact_dir="$1" source_sha="$2" candidate_version="$3" local manifest_sha256="$4" registry_root="$5" pid_variable="$6" @@ -90,3 +118,19 @@ NODE export npm_config_registry="$NPM_CONFIG_REGISTRY" printf -v "$pid_variable" "%s" "$server_pid" } + +openclaw_prepublish_plugin_registry_start_mounted() { + local registry_root="$1" pid_variable="$2" required_packages_json="$3" + shift 3 + [ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ] || return 0 + + OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_REQUIRED_PACKAGES_JSON="$required_packages_json" \ + openclaw_prepublish_plugin_registry_start \ + "$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR" \ + "${OPENCLAW_DOCKER_E2E_SELECTED_SHA:?missing selected SHA}" \ + "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION:?missing candidate version}" \ + "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256:?missing manifest SHA-256}" \ + "$registry_root" \ + "$pid_variable" \ + "$@" +} diff --git a/scripts/e2e/npm-onboard-channel-agent-docker.sh b/scripts/e2e/npm-onboard-channel-agent-docker.sh index 53c18a79dce1..2e8962b58f4a 100644 --- a/scripts/e2e/npm-onboard-channel-agent-docker.sh +++ b/scripts/e2e/npm-onboard-channel-agent-docker.sh @@ -7,6 +7,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" SOURCE_ROOT="$(cd "${OPENCLAW_NPM_ONBOARD_SOURCE_ROOT:-${OPENCLAW_LIVE_DOCKER_REPO_ROOT:-$ROOT_DIR}}" && pwd)" source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh" source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh" +source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh" IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-npm-onboard-channel-agent-e2e" OPENCLAW_NPM_ONBOARD_E2E_IMAGE)" DOCKER_TARGET="${OPENCLAW_NPM_ONBOARD_DOCKER_TARGET:-bare}" @@ -23,6 +24,7 @@ STATUS_TEXT_MAX_BYTES="$( run_log="" plugin_pack_dir="" plugin_package_args=() +OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS=() cleanup() { if [ -n "${PACKAGE_TGZ:-}" ]; then @@ -95,6 +97,11 @@ prepare_source_plugin_package() { prepare_source_plugin_package +if [ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ]; then + openclaw_prepublish_plugin_registry_configure_docker_args \ + "$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR" +fi + docker_e2e_package_mount_args "$PACKAGE_TGZ" run_log="$(docker_e2e_run_log npm-onboard-channel-agent)" OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 npm-onboard-channel-agent empty)" @@ -108,10 +115,12 @@ if ! docker_e2e_run_with_harness \ -e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \ "${DOCKER_E2E_PACKAGE_ARGS[@]}" \ "${plugin_package_args[@]}" \ + "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]}" \ -i "$IMAGE_NAME" bash -s >"$run_log" 2>&1 <<'EOF'; then set -Eeuo pipefail source scripts/lib/openclaw-e2e-instance.sh +source scripts/e2e/lib/prepublish-plugin-registry.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 PATH="$NPM_CONFIG_PREFIX/bin:$PATH" @@ -126,6 +135,7 @@ scenario_tmp="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-npm-onboard-channel-agent.XX MOCK_REQUEST_LOG="$scenario_tmp/mock-openai-requests.jsonl" export SUCCESS_MARKER MOCK_REQUEST_LOG mock_pid="" +plugin_registry_pid="" case "$CHANNEL" in telegram) @@ -155,6 +165,7 @@ esac cleanup() { openclaw_e2e_stop_process "${mock_pid:-}" + openclaw_e2e_stop_process "${plugin_registry_pid:-}" rm -rf "$scenario_tmp" } trap cleanup EXIT @@ -180,6 +191,11 @@ dump_debug_logs() { } trap 'status=$?; dump_debug_logs "$status"; exit "$status"' ERR +if [ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ]; then + openclaw_prepublish_plugin_registry_start_mounted \ + /tmp/openclaw-npm-onboard-plugin-registry plugin_registry_pid '["@openclaw/codex"]' +fi + openclaw_e2e_install_package /tmp/openclaw-install.log command -v openclaw >/dev/null diff --git a/scripts/lib/docker-e2e-scenarios.mts b/scripts/lib/docker-e2e-scenarios.mts index 74945c12f73c..4179ee96fd94 100644 --- a/scripts/lib/docker-e2e-scenarios.mts +++ b/scripts/lib/docker-e2e-scenarios.mts @@ -64,6 +64,12 @@ const updateMigrationCommand = upgradeSurvivorScriptCommand( const updateRunPackageSelfUpgradeCommand = "OPENCLAW_QA_ALLOW_UPDATE_RUN_SELF=1 OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:update-run-package-self-upgrade"; const CODEX_HARNESS_API_KEY_ENV = "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key"; +const npmOnboardLaneOptions = { + prepublishPluginPackages: ["@openclaw/codex"], + resources: ["service"], + stateScenario: "empty", + weight: 3, +} satisfies LaneOptions; const LIVE_RETRY_PATTERNS = [ /529\b/i, @@ -461,17 +467,17 @@ export const mainLanes: DockerE2eLane[] = [ npmLane( "npm-onboard-channel-agent", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:npm-onboard-channel-agent", - { resources: ["service"], stateScenario: "empty", weight: 3 }, + npmOnboardLaneOptions, ), npmLane( "npm-onboard-discord-channel-agent", "OPENCLAW_NPM_ONBOARD_CHANNEL=discord OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:npm-onboard-channel-agent", - { resources: ["service"], stateScenario: "empty", weight: 3 }, + npmOnboardLaneOptions, ), npmLane( "npm-onboard-slack-channel-agent", "OPENCLAW_NPM_ONBOARD_CHANNEL=slack OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:npm-onboard-channel-agent", - { resources: ["service"], stateScenario: "empty", weight: 3 }, + npmOnboardLaneOptions, ), // Prerelease validation must pair frozen core bytes with matching target plugin bytes. // Keep the registry-backed lanes above unchanged for published-package proof. @@ -481,7 +487,7 @@ export const mainLanes: DockerE2eLane[] = [ "e2e/npm-onboard-channel-agent-docker.sh", "OPENCLAW_NPM_ONBOARD_CHANNEL=discord OPENCLAW_NPM_ONBOARD_USE_SOURCE_PLUGIN_PACKAGE=1", ), - { resources: ["service"], stateScenario: "empty", weight: 3 }, + npmOnboardLaneOptions, ), npmLane( "npm-onboard-slack-candidate-channel-agent", @@ -489,7 +495,7 @@ export const mainLanes: DockerE2eLane[] = [ "e2e/npm-onboard-channel-agent-docker.sh", "OPENCLAW_NPM_ONBOARD_CHANNEL=slack OPENCLAW_NPM_ONBOARD_USE_SOURCE_PLUGIN_PACKAGE=1", ), - { resources: ["service"], stateScenario: "empty", weight: 3 }, + npmOnboardLaneOptions, ), npmLane( "release-user-journey", diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index 9f74aba02a13..a7caaacf8c82 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -3882,10 +3882,10 @@ grep -Fxq preserved "$TMPDIR/caller-fd" expectTextToIncludeAll(runner, [ "OPENCLAW_DOCKER_ALL_LANES=codex-on-demand", - "OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR=/tmp/openclaw-prepublish-plugin-registry", "source scripts/e2e/lib/prepublish-plugin-registry.sh", - "openclaw_prepublish_plugin_registry_start", - "OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_REQUIRED_PACKAGES_JSON='[\"@openclaw/codex\"]'", + "openclaw_prepublish_plugin_registry_configure_docker_args", + "openclaw_prepublish_plugin_registry_start_mounted", + "'[\"@openclaw/codex\"]'", ]); expectTextToIncludeAll(registryHelper, [ 'OPENCLAW_NPM_REGISTRY_DIST_TAGS="$dist_tags"', @@ -3899,6 +3899,20 @@ grep -Fxq preserved "$TMPDIR/caller-fd" ); }); + it("reuses the candidate registry lifecycle for channel onboarding", () => { + const runner = readFileSync(NPM_ONBOARD_CHANNEL_AGENT_DOCKER_E2E_PATH, "utf8"); + + expectTextToIncludeAll(runner, [ + 'source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh"', + "openclaw_prepublish_plugin_registry_configure_docker_args", + "openclaw_prepublish_plugin_registry_start_mounted", + "'[\"@openclaw/codex\"]'", + ]); + expect(runner.indexOf("openclaw_prepublish_plugin_registry_start_mounted")).toBeLessThan( + runner.indexOf("\nopenclaw_e2e_install_package"), + ); + }); + it("cleans package-backed onboarding and plugin Docker artifacts on every exit path", () => { for (const path of [ CODEX_ON_DEMAND_DOCKER_E2E_PATH, diff --git a/test/scripts/docker-e2e-plan.test.ts b/test/scripts/docker-e2e-plan.test.ts index 26844c2df3e8..e34c6a527fdb 100644 --- a/test/scripts/docker-e2e-plan.test.ts +++ b/test/scripts/docker-e2e-plan.test.ts @@ -144,6 +144,22 @@ function bundledPluginSweepLane(index: number): ReturnType } describe("scripts/lib/docker-e2e-plan", () => { + it("prepares the matching Codex package for candidate npm onboarding", () => { + const laneNames = [ + "npm-onboard-channel-agent", + "npm-onboard-discord-channel-agent", + "npm-onboard-slack-channel-agent", + "npm-onboard-discord-candidate-channel-agent", + "npm-onboard-slack-candidate-channel-agent", + ]; + const lanes = laneNames.map((name) => findLaneByName(name)); + + expect(lanes.map((lane) => lane?.name)).toEqual(laneNames); + expect(requiredPrepublishPluginPackagesForLanes(lanes.flatMap((lane) => lane ?? []))).toEqual([ + "@openclaw/codex", + ]); + }); + it("omits a package-script lane unavailable from the candidate", () => { const plan = planFor({ candidatePackageRoot: writeCandidatePackage({}), diff --git a/test/scripts/prepublish-plugin-registry-shell.test.ts b/test/scripts/prepublish-plugin-registry-shell.test.ts index bc97283d30f8..0bb6df6af219 100644 --- a/test/scripts/prepublish-plugin-registry-shell.test.ts +++ b/test/scripts/prepublish-plugin-registry-shell.test.ts @@ -30,6 +30,39 @@ function createTarball( } describe("prepublish plugin registry shell helper", () => { + it("derives the immutable Docker mount contract from the registry artifact", () => { + const root = tempDirs.make("openclaw-prepublish-registry-mount-"); + const manifestPath = join(root, "prepublish-plugin-registry.json"); + writeFileSync( + manifestPath, + `${JSON.stringify({ candidateVersion: VERSION, packages: [], sourceSha: SOURCE_SHA })}\n`, + ); + + const result = spawnSync( + "bash", + [ + "-c", + ` +set -euo pipefail +source "$HELPER" +openclaw_prepublish_plugin_registry_configure_docker_args "$ARTIFACT_DIR" +printf '%s\n' "\${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]}" +`, + ], + { encoding: "utf8", env: { ...process.env, ARTIFACT_DIR: root, HELPER: SCRIPT } }, + ); + + expect(result.status, result.stderr).toBe(0); + expect(result.stdout).toContain(`OPENCLAW_DOCKER_E2E_SELECTED_SHA=${SOURCE_SHA}`); + expect(result.stdout).toContain( + `OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION=${VERSION}`, + ); + expect(result.stdout).toContain(`${root}:/tmp/openclaw-prepublish-plugin-registry:ro`); + expect(result.stdout).toContain( + `OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256=${sha256(manifestPath)}`, + ); + }); + it("verifies and serves every artifact package plus caller-owned fixtures", () => { const root = tempDirs.make("openclaw-prepublish-registry-shell-"); const artifactDir = join(root, "artifact"); @@ -85,10 +118,12 @@ cleanup() { fi } trap cleanup EXIT -export OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_REQUIRED_PACKAGES_JSON='["@openclaw/codex"]' -openclaw_prepublish_plugin_registry_start \ - "$ARTIFACT_DIR" "$SOURCE_SHA" "$VERSION" "$MANIFEST_SHA256" \ - "$REGISTRY_ROOT" registry_pid \ +export OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR="$ARTIFACT_DIR" +export OPENCLAW_DOCKER_E2E_SELECTED_SHA="$SOURCE_SHA" +export OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION="$VERSION" +export OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256="$MANIFEST_SHA256" +openclaw_prepublish_plugin_registry_start_mounted \ + "$REGISTRY_ROOT" registry_pid '["@openclaw/codex"]' \ "@openclaw/brave-plugin" "$VERSION" "$EXTRA_TARBALL" node <<'NODE' const packages = ["@openclaw/codex", "@openclaw/telegram", "@openclaw/brave-plugin"];