diff --git a/scripts/e2e/lib/bundled-channel/channel.sh b/scripts/e2e/lib/bundled-channel/channel.sh index 5b6a92d60fb..325d3affc85 100644 --- a/scripts/e2e/lib/bundled-channel/channel.sh +++ b/scripts/e2e/lib/bundled-channel/channel.sh @@ -21,6 +21,7 @@ run_channel_scenario() { set -euo pipefail source scripts/lib/openclaw-e2e-instance.sh +source scripts/e2e/lib/bundled-channel/common.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" @@ -79,154 +80,12 @@ test -d "$package_root/dist/extensions/slack" test -d "$package_root/dist/extensions/feishu" test -d "$package_root/dist/extensions/memory-lancedb" -stage_root() { - printf "%s/.openclaw/plugin-runtime-deps" "$HOME" -} - -find_external_dep_package() { - local dep_path="$1" - find "$(stage_root)" -maxdepth 12 -path "*/node_modules/$dep_path/package.json" -type f -print -quit 2>/dev/null || true -} - -assert_package_dep_absent() { - local channel="$1" - local dep_path="$2" - for candidate in \ - "$package_root/dist/extensions/$channel/node_modules/$dep_path/package.json" \ - "$package_root/dist/extensions/node_modules/$dep_path/package.json" \ - "$package_root/node_modules/$dep_path/package.json"; do - if [ -f "$candidate" ]; then - echo "packaged install should not mutate package tree for $channel: $candidate" >&2 - exit 1 - fi - done -} - if [ -d "$package_root/dist/extensions/$CHANNEL/node_modules" ]; then echo "$CHANNEL runtime deps should not be preinstalled in package" >&2 find "$package_root/dist/extensions/$CHANNEL/node_modules" -maxdepth 2 -type f | head -20 >&2 || true exit 1 fi -write_config() { - local mode="$1" - node - <<'NODE' "$mode" "$TOKEN" "$PORT" -const fs = require("node:fs"); -const path = require("node:path"); - -const mode = process.argv[2]; -const token = process.argv[3]; -const port = Number(process.argv[4]); -const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json"); -const config = fs.existsSync(configPath) - ? JSON.parse(fs.readFileSync(configPath, "utf8")) - : {}; - -config.gateway = { - ...(config.gateway || {}), - port, - auth: { mode: "token", token }, - controlUi: { enabled: false }, -}; -config.agents = { - ...(config.agents || {}), - defaults: { - ...(config.agents?.defaults || {}), - model: { primary: "openai/gpt-4.1-mini" }, - }, -}; -config.models = { - ...(config.models || {}), - providers: { - ...(config.models?.providers || {}), - openai: { - ...(config.models?.providers?.openai || {}), - apiKey: process.env.OPENAI_API_KEY, - baseUrl: "https://api.openai.com/v1", - models: [], - }, - }, -}; -config.plugins = { - ...(config.plugins || {}), - enabled: true, -}; - -if (mode === "telegram") { - config.channels = { - ...(config.channels || {}), - telegram: { - ...(config.channels?.telegram || {}), - enabled: true, - dmPolicy: "disabled", - groupPolicy: "disabled", - }, - }; -} -if (mode === "discord") { - config.channels = { - ...(config.channels || {}), - discord: { - ...(config.channels?.discord || {}), - enabled: true, - dmPolicy: "disabled", - groupPolicy: "disabled", - }, - }; -} -if (mode === "slack") { - config.channels = { - ...(config.channels || {}), - slack: { - ...(config.channels?.slack || {}), - enabled: true, - }, - }; -} -if (mode === "feishu") { - config.channels = { - ...(config.channels || {}), - feishu: { - ...(config.channels?.feishu || {}), - enabled: true, - }, - }; -} -if (mode === "memory-lancedb") { - config.plugins = { - ...(config.plugins || {}), - enabled: true, - allow: [...new Set([...(config.plugins?.allow || []), "memory-lancedb"])], - slots: { - ...(config.plugins?.slots || {}), - memory: "memory-lancedb", - }, - entries: { - ...(config.plugins?.entries || {}), - "memory-lancedb": { - ...(config.plugins?.entries?.["memory-lancedb"] || {}), - enabled: true, - config: { - ...(config.plugins?.entries?.["memory-lancedb"]?.config || {}), - embedding: { - ...(config.plugins?.entries?.["memory-lancedb"]?.config?.embedding || {}), - apiKey: process.env.OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: "~/.openclaw/memory/lancedb-e2e", - autoCapture: false, - autoRecall: false, - }, - }, - }, - }; -} - -fs.mkdirSync(path.dirname(configPath), { recursive: true }); -fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); -NODE -} - start_gateway() { local log_file="$1" local skip_sidecars="${2:-0}" @@ -347,12 +206,12 @@ assert_installed_once() { if [ "$count" -eq 1 ]; then return 0 fi - if [ "$count" -eq 0 ] && [ -n "$(find_external_dep_package "$dep_path")" ]; then + if [ "$count" -eq 0 ] && [ -n "$(bundled_channel_find_external_dep_package "$dep_path")" ]; then return 0 fi echo "expected one runtime deps install log or staged dependency sentinel for $channel, got $count log lines" >&2 cat "$log_file" >&2 - find "$(stage_root)" -maxdepth 12 -type f | sort | head -120 >&2 || true + find "$(bundled_channel_stage_root)" -maxdepth 12 -type f | sort | head -120 >&2 || true exit 1 } @@ -369,24 +228,13 @@ assert_not_installed() { assert_dep_sentinel() { local channel="$1" local dep_path="$2" - local sentinel - sentinel="$(find_external_dep_package "$dep_path")" - if [ -z "$sentinel" ]; then - echo "missing external dependency sentinel for $channel: $dep_path" >&2 - find "$(stage_root)" -maxdepth 12 -type f | sort | head -120 >&2 || true - exit 1 - fi - assert_package_dep_absent "$channel" "$dep_path" + bundled_channel_assert_dep_available "$channel" "$dep_path" "$package_root" } assert_no_dep_sentinel() { local channel="$1" local dep_path="$2" - assert_package_dep_absent "$channel" "$dep_path" - if [ -n "$(find_external_dep_package "$dep_path")" ]; then - echo "external dependency sentinel should be absent before activation for $channel: $dep_path" >&2 - exit 1 - fi + bundled_channel_assert_no_dep_available "$channel" "$dep_path" "$package_root" } assert_no_install_stage() { @@ -400,14 +248,14 @@ assert_no_install_stage() { } echo "Starting baseline gateway with OpenAI configured..." -write_config baseline +bundled_channel_write_config baseline start_gateway "/tmp/openclaw-$CHANNEL-baseline.log" 1 wait_for_gateway_health "/tmp/openclaw-$CHANNEL-baseline.log" stop_gateway assert_no_dep_sentinel "$CHANNEL" "$DEP_SENTINEL" echo "Enabling $CHANNEL by config edit, then restarting gateway..." -write_config "$CHANNEL" +bundled_channel_write_config "$CHANNEL" start_gateway "/tmp/openclaw-$CHANNEL-first.log" wait_for_gateway_health "/tmp/openclaw-$CHANNEL-first.log" assert_installed_once "/tmp/openclaw-$CHANNEL-first.log" "$CHANNEL" "$DEP_SENTINEL" diff --git a/scripts/e2e/lib/bundled-channel/common.sh b/scripts/e2e/lib/bundled-channel/common.sh new file mode 100644 index 00000000000..d9b294aea20 --- /dev/null +++ b/scripts/e2e/lib/bundled-channel/common.sh @@ -0,0 +1,194 @@ +#!/usr/bin/env bash +# +# Container-side helpers shared by bundled channel Docker E2E scenarios. +# These functions assume the OpenClaw package is installed globally inside the +# test container and the scenario has exported HOME/OPENAI_API_KEY as needed. + +bundled_channel_package_root() { + printf "%s/openclaw" "$(npm root -g)" +} + +bundled_channel_stage_root() { + printf "%s/.openclaw/plugin-runtime-deps" "$HOME" +} + +bundled_channel_find_external_dep_package() { + local dep_path="$1" + find "$(bundled_channel_stage_root)" -maxdepth 12 -path "*/node_modules/$dep_path/package.json" -type f -print -quit 2>/dev/null || true +} + +bundled_channel_assert_no_package_dep_available() { + local channel="$1" + local dep_path="$2" + local root="${3:-$(bundled_channel_package_root)}" + for candidate in \ + "$root/dist/extensions/$channel/node_modules/$dep_path/package.json" \ + "$root/dist/extensions/node_modules/$dep_path/package.json" \ + "$root/node_modules/$dep_path/package.json"; do + if [ -f "$candidate" ]; then + echo "packaged install should not mutate package tree for $channel: $candidate" >&2 + exit 1 + fi + done + if [ -f "$HOME/node_modules/$dep_path/package.json" ]; then + echo "bundled runtime deps should not use HOME npm project for $channel: $HOME/node_modules/$dep_path/package.json" >&2 + exit 1 + fi +} + +bundled_channel_assert_dep_available() { + local channel="$1" + local dep_path="$2" + local root="${3:-$(bundled_channel_package_root)}" + if [ -n "$(bundled_channel_find_external_dep_package "$dep_path")" ]; then + bundled_channel_assert_no_package_dep_available "$channel" "$dep_path" "$root" + return 0 + fi + echo "missing dependency sentinel for $channel: $dep_path" >&2 + find "$root/dist/extensions/$channel" -maxdepth 3 -type f | sort | head -80 >&2 || true + find "$root/node_modules" -maxdepth 3 -path "*/$dep_path/package.json" -type f -print >&2 || true + find "$(bundled_channel_stage_root)" -maxdepth 12 -type f | sort | head -120 >&2 || true + exit 1 +} + +bundled_channel_assert_no_dep_available() { + local channel="$1" + local dep_path="$2" + local root="${3:-$(bundled_channel_package_root)}" + bundled_channel_assert_no_package_dep_available "$channel" "$dep_path" "$root" + if [ -n "$(bundled_channel_find_external_dep_package "$dep_path")" ]; then + echo "dependency sentinel should be absent before repair for $channel: $dep_path" >&2 + exit 1 + fi +} + +bundled_channel_remove_runtime_dep() { + local channel="$1" + local dep_path="$2" + local root="${3:-$(bundled_channel_package_root)}" + rm -rf "$root/dist/extensions/$channel/node_modules" + rm -rf "$root/dist/extensions/node_modules/$dep_path" + rm -rf "$root/node_modules/$dep_path" + rm -rf "$(bundled_channel_stage_root)" +} + +bundled_channel_write_config() { + local mode="$1" + node - <<'NODE' "$mode" "${TOKEN:?missing TOKEN}" "${PORT:?missing PORT}" +const fs = require("node:fs"); +const path = require("node:path"); + +const mode = process.argv[2]; +const token = process.argv[3]; +const port = Number(process.argv[4]); +const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json"); +const config = fs.existsSync(configPath) + ? JSON.parse(fs.readFileSync(configPath, "utf8")) + : {}; + +config.gateway = { + ...(config.gateway || {}), + port, + auth: { mode: "token", token }, + controlUi: { enabled: false }, +}; +config.agents = { + ...(config.agents || {}), + defaults: { + ...(config.agents?.defaults || {}), + model: { primary: "openai/gpt-4.1-mini" }, + }, +}; +config.models = { + ...(config.models || {}), + providers: { + ...(config.models?.providers || {}), + openai: { + ...(config.models?.providers?.openai || {}), + apiKey: process.env.OPENAI_API_KEY, + baseUrl: "https://api.openai.com/v1", + models: [], + }, + }, +}; +config.plugins = { + ...(config.plugins || {}), + enabled: true, +}; +config.channels = { + ...(config.channels || {}), + telegram: { + ...(config.channels?.telegram || {}), + enabled: mode === "telegram", + botToken: "123456:bundled-channel-update-token", + dmPolicy: "disabled", + groupPolicy: "disabled", + }, + discord: { + ...(config.channels?.discord || {}), + enabled: mode === "discord", + dmPolicy: "disabled", + groupPolicy: "disabled", + }, + slack: { + ...(config.channels?.slack || {}), + enabled: mode === "slack", + botToken: "xoxb-bundled-channel-update-token", + appToken: "xapp-bundled-channel-update-token", + }, + feishu: { + ...(config.channels?.feishu || {}), + enabled: mode === "feishu", + }, +}; +if (mode === "memory-lancedb") { + config.plugins = { + ...(config.plugins || {}), + enabled: true, + allow: [...new Set([...(config.plugins?.allow || []), "memory-lancedb"])], + slots: { + ...(config.plugins?.slots || {}), + memory: "memory-lancedb", + }, + entries: { + ...(config.plugins?.entries || {}), + "memory-lancedb": { + ...(config.plugins?.entries?.["memory-lancedb"] || {}), + enabled: true, + config: { + ...(config.plugins?.entries?.["memory-lancedb"]?.config || {}), + embedding: { + ...(config.plugins?.entries?.["memory-lancedb"]?.config?.embedding || {}), + apiKey: process.env.OPENAI_API_KEY, + model: "text-embedding-3-small", + }, + dbPath: process.env.OPENCLAW_BUNDLED_CHANNEL_MEMORY_DB_PATH || "~/.openclaw/memory/lancedb-e2e", + autoCapture: false, + autoRecall: false, + }, + }, + }, + }; +} +if (mode === "acpx") { + config.plugins = { + ...(config.plugins || {}), + enabled: true, + allow: + Array.isArray(config.plugins?.allow) && config.plugins.allow.length > 0 + ? [...new Set([...config.plugins.allow, "acpx"])] + : config.plugins?.allow, + entries: { + ...(config.plugins?.entries || {}), + acpx: { + ...(config.plugins?.entries?.acpx || {}), + enabled: true, + }, + }, + }; +} + +fs.mkdirSync(path.dirname(configPath), { recursive: true }); +fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); +NODE +} diff --git a/scripts/e2e/lib/bundled-channel/disabled-config.sh b/scripts/e2e/lib/bundled-channel/disabled-config.sh index 507813357e5..62d4b757a6b 100644 --- a/scripts/e2e/lib/bundled-channel/disabled-config.sh +++ b/scripts/e2e/lib/bundled-channel/disabled-config.sh @@ -17,6 +17,7 @@ run_disabled_config_scenario() { set -euo pipefail source scripts/lib/openclaw-e2e-instance.sh +source scripts/e2e/lib/bundled-channel/common.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" @@ -24,10 +25,6 @@ export OPENCLAW_NO_ONBOARD=1 export OPENCLAW_PLUGIN_STAGE_DIR="$HOME/.openclaw/plugin-runtime-deps" mkdir -p "$OPENCLAW_PLUGIN_STAGE_DIR" -package_root() { - printf "%s/openclaw" "$(npm root -g)" -} - assert_dep_absent_everywhere() { local channel="$1" local dep_path="$2" @@ -99,7 +96,7 @@ echo "Installing mounted OpenClaw package..." package_tgz="${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}" npm install -g "$package_tgz" --no-fund --no-audit >/tmp/openclaw-disabled-config-install.log 2>&1 -root="$(package_root)" +root="$(bundled_channel_package_root)" test -d "$root/dist/extensions/telegram" test -d "$root/dist/extensions/discord" test -d "$root/dist/extensions/slack" diff --git a/scripts/e2e/lib/bundled-channel/load-failure.sh b/scripts/e2e/lib/bundled-channel/load-failure.sh index 16cdf4577cf..7abecb89484 100644 --- a/scripts/e2e/lib/bundled-channel/load-failure.sh +++ b/scripts/e2e/lib/bundled-channel/load-failure.sh @@ -17,20 +17,17 @@ run_load_failure_scenario() { set -euo pipefail source scripts/lib/openclaw-e2e-instance.sh +source scripts/e2e/lib/bundled-channel/common.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" export OPENCLAW_NO_ONBOARD=1 -package_root() { - printf "%s/openclaw" "$(npm root -g)" -} - echo "Installing mounted OpenClaw package..." package_tgz="${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}" npm install -g "$package_tgz" --no-fund --no-audit >/tmp/openclaw-load-failure-install.log 2>&1 -root="$(package_root)" +root="$(bundled_channel_package_root)" plugin_dir="$root/dist/extensions/load-failure-alpha" mkdir -p "$plugin_dir" cat >"$plugin_dir/package.json" <<'JSON' diff --git a/scripts/e2e/lib/bundled-channel/root-owned.sh b/scripts/e2e/lib/bundled-channel/root-owned.sh index f0642f78632..e481c6479a3 100644 --- a/scripts/e2e/lib/bundled-channel/root-owned.sh +++ b/scripts/e2e/lib/bundled-channel/root-owned.sh @@ -13,6 +13,7 @@ run_root_owned_global_scenario() { set -euo pipefail source scripts/lib/openclaw-e2e-instance.sh +source scripts/e2e/lib/bundled-channel/common.sh export HOME="/root" export OPENAI_API_KEY="sk-openclaw-bundled-channel-root-owned-e2e" export OPENCLAW_NO_ONBOARD=1 @@ -24,10 +25,6 @@ CHANNEL="slack" DEP_SENTINEL="@slack/web-api" gateway_pid="" -package_root() { - printf "%s/openclaw" "$(npm root -g)" -} - cleanup() { if [ -n "${gateway_pid:-}" ] && kill -0 "$gateway_pid" 2>/dev/null; then kill "$gateway_pid" 2>/dev/null || true @@ -44,7 +41,7 @@ if ! npm install -g "$package_tgz" --no-fund --no-audit >/tmp/openclaw-root-owne exit 1 fi -root="$(package_root)" +root="$(bundled_channel_package_root)" test -d "$root/dist/extensions/$CHANNEL" rm -rf "$root/dist/extensions/$CHANNEL/node_modules" chmod -R a-w "$root" diff --git a/scripts/e2e/lib/bundled-channel/setup-entry.sh b/scripts/e2e/lib/bundled-channel/setup-entry.sh index 648678a2330..baa2c35108a 100644 --- a/scripts/e2e/lib/bundled-channel/setup-entry.sh +++ b/scripts/e2e/lib/bundled-channel/setup-entry.sh @@ -17,6 +17,7 @@ run_setup_entry_scenario() { set -euo pipefail source scripts/lib/openclaw-e2e-instance.sh +source scripts/e2e/lib/bundled-channel/common.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" @@ -29,10 +30,6 @@ declare -A SETUP_ENTRY_DEP_SENTINELS=( [whatsapp]="@whiskeysockets/baileys" ) -package_root() { - printf "%s/openclaw" "$(npm root -g)" -} - echo "Installing mounted OpenClaw package..." package_tgz="${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}" if ! npm install -g "$package_tgz" --no-fund --no-audit >/tmp/openclaw-setup-entry-install.log 2>&1; then @@ -40,7 +37,7 @@ if ! npm install -g "$package_tgz" --no-fund --no-audit >/tmp/openclaw-setup-ent exit 1 fi -root="$(package_root)" +root="$(bundled_channel_package_root)" for channel in "${!SETUP_ENTRY_DEP_SENTINELS[@]}"; do dep_sentinel="${SETUP_ENTRY_DEP_SENTINELS[$channel]}" test -d "$root/dist/extensions/$channel" diff --git a/scripts/e2e/lib/bundled-channel/update.sh b/scripts/e2e/lib/bundled-channel/update.sh index 4ff7f843333..adcb7904b73 100644 --- a/scripts/e2e/lib/bundled-channel/update.sh +++ b/scripts/e2e/lib/bundled-channel/update.sh @@ -19,25 +19,19 @@ run_update_scenario() { set -euo pipefail source scripts/lib/openclaw-e2e-instance.sh +source scripts/e2e/lib/bundled-channel/common.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" export OPENAI_API_KEY="sk-openclaw-bundled-channel-update-e2e" export OPENCLAW_NO_ONBOARD=1 export OPENCLAW_UPDATE_PACKAGE_SPEC="" +export OPENCLAW_BUNDLED_CHANNEL_MEMORY_DB_PATH="~/.openclaw/memory/lancedb-update-e2e" TOKEN="bundled-channel-update-token" PORT="18790" UPDATE_TARGETS="${OPENCLAW_BUNDLED_CHANNEL_UPDATE_TARGETS:-telegram,discord,slack,feishu,memory-lancedb,acpx}" -package_root() { - printf "%s/openclaw" "$(npm root -g)" -} - -stage_root() { - printf "%s/.openclaw/plugin-runtime-deps" "$HOME" -} - poison_home_npm_project() { printf '{"name":"openclaw-home-prefix-poison","private":true}\n' >"$HOME/package.json" rm -rf "$HOME/node_modules" @@ -45,15 +39,10 @@ poison_home_npm_project() { chmod 500 "$HOME/node_modules" } -find_external_dep_package() { - local dep_path="$1" - find "$(stage_root)" -maxdepth 12 -path "*/node_modules/$dep_path/package.json" -type f -print -quit 2>/dev/null || true -} - assert_no_unknown_stage_roots() { - if find "$(stage_root)" -maxdepth 1 -type d -name 'openclaw-unknown-*' -print -quit 2>/dev/null | grep -q .; then + if find "$(bundled_channel_stage_root)" -maxdepth 1 -type d -name 'openclaw-unknown-*' -print -quit 2>/dev/null | grep -q .; then echo "runtime deps created second-generation unknown stage roots" >&2 - find "$(stage_root)" -maxdepth 1 -type d -name 'openclaw-*' -print | sort >&2 || true + find "$(bundled_channel_stage_root)" -maxdepth 1 -type d -name 'openclaw-*' -print | sort >&2 || true exit 1 fi } @@ -69,214 +58,6 @@ process.stdout.write(String(JSON.parse(raw).version)); NODE )" -write_config() { - local mode="$1" - node - <<'NODE' "$mode" "$TOKEN" "$PORT" -const fs = require("node:fs"); -const path = require("node:path"); - -const mode = process.argv[2]; -const token = process.argv[3]; -const port = Number(process.argv[4]); -const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json"); -const config = fs.existsSync(configPath) - ? JSON.parse(fs.readFileSync(configPath, "utf8")) - : {}; - -config.gateway = { - ...(config.gateway || {}), - port, - auth: { mode: "token", token }, - controlUi: { enabled: false }, -}; -config.agents = { - ...(config.agents || {}), - defaults: { - ...(config.agents?.defaults || {}), - model: { primary: "openai/gpt-4.1-mini" }, - }, -}; -config.models = { - ...(config.models || {}), - providers: { - ...(config.models?.providers || {}), - openai: { - ...(config.models?.providers?.openai || {}), - apiKey: process.env.OPENAI_API_KEY, - baseUrl: "https://api.openai.com/v1", - models: [], - }, - }, -}; -config.plugins = { - ...(config.plugins || {}), - enabled: true, -}; -config.channels = { - ...(config.channels || {}), - telegram: { - ...(config.channels?.telegram || {}), - enabled: mode === "telegram", - botToken: "123456:bundled-channel-update-token", - dmPolicy: "disabled", - groupPolicy: "disabled", - }, - discord: { - ...(config.channels?.discord || {}), - enabled: mode === "discord", - dmPolicy: "disabled", - groupPolicy: "disabled", - }, - slack: { - ...(config.channels?.slack || {}), - enabled: mode === "slack", - botToken: "xoxb-bundled-channel-update-token", - appToken: "xapp-bundled-channel-update-token", - }, - feishu: { - ...(config.channels?.feishu || {}), - enabled: mode === "feishu", - }, -}; -if (mode === "memory-lancedb") { - config.plugins = { - ...(config.plugins || {}), - enabled: true, - allow: [...new Set([...(config.plugins?.allow || []), "memory-lancedb"])], - slots: { - ...(config.plugins?.slots || {}), - memory: "memory-lancedb", - }, - entries: { - ...(config.plugins?.entries || {}), - "memory-lancedb": { - ...(config.plugins?.entries?.["memory-lancedb"] || {}), - enabled: true, - config: { - ...(config.plugins?.entries?.["memory-lancedb"]?.config || {}), - embedding: { - ...(config.plugins?.entries?.["memory-lancedb"]?.config?.embedding || {}), - apiKey: process.env.OPENAI_API_KEY, - model: "text-embedding-3-small", - }, - dbPath: "~/.openclaw/memory/lancedb-update-e2e", - autoCapture: false, - autoRecall: false, - }, - }, - }, - }; -} -if (mode === "acpx") { - config.plugins = { - ...(config.plugins || {}), - enabled: true, - allow: - Array.isArray(config.plugins?.allow) && config.plugins.allow.length > 0 - ? [...new Set([...config.plugins.allow, "acpx"])] - : config.plugins?.allow, - entries: { - ...(config.plugins?.entries || {}), - acpx: { - ...(config.plugins?.entries?.acpx || {}), - enabled: true, - }, - }, - }; -} - -fs.mkdirSync(path.dirname(configPath), { recursive: true }); -fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); -NODE -} - -assert_dep_sentinel() { - local channel="$1" - local dep_path="$2" - local root - local sentinel - root="$(package_root)" - sentinel="$(find_external_dep_package "$dep_path")" - if [ -z "$sentinel" ]; then - echo "missing external dependency sentinel for $channel: $dep_path" >&2 - find "$(stage_root)" -maxdepth 12 -type f | sort | head -120 >&2 || true - exit 1 - fi - assert_no_package_dep_available "$channel" "$dep_path" "$root" -} - -assert_no_dep_sentinel() { - local channel="$1" - local dep_path="$2" - local root - root="$(package_root)" - assert_no_package_dep_available "$channel" "$dep_path" "$root" - if [ -n "$(find_external_dep_package "$dep_path")" ]; then - echo "external dependency sentinel should be absent before repair for $channel: $dep_path" >&2 - exit 1 - fi -} - -assert_no_package_dep_available() { - local channel="$1" - local dep_path="$2" - local root="$3" - for candidate in \ - "$root/dist/extensions/$channel/node_modules/$dep_path/package.json" \ - "$root/dist/extensions/node_modules/$dep_path/package.json" \ - "$root/node_modules/$dep_path/package.json"; do - if [ -f "$candidate" ]; then - echo "packaged install should not mutate package tree for $channel: $candidate" >&2 - exit 1 - fi - done - if [ -f "$HOME/node_modules/$dep_path/package.json" ]; then - echo "bundled runtime deps should not use HOME npm project for $channel: $HOME/node_modules/$dep_path/package.json" >&2 - exit 1 - fi -} - -assert_dep_available() { - local channel="$1" - local dep_path="$2" - local root - local sentinel - root="$(package_root)" - sentinel="$(find_external_dep_package "$dep_path")" - if [ -n "$sentinel" ]; then - assert_no_package_dep_available "$channel" "$dep_path" "$root" - return 0 - fi - echo "missing dependency sentinel for $channel: $dep_path" >&2 - find "$root/dist/extensions/$channel" -maxdepth 3 -type f | sort | head -80 >&2 || true - find "$root/node_modules" -maxdepth 3 -path "*/$dep_path/package.json" -type f -print >&2 || true - find "$(stage_root)" -maxdepth 12 -type f | sort | head -120 >&2 || true - exit 1 -} - -assert_no_dep_available() { - local channel="$1" - local dep_path="$2" - local root - root="$(package_root)" - assert_no_package_dep_available "$channel" "$dep_path" "$root" - if [ -n "$(find_external_dep_package "$dep_path")" ]; then - echo "dependency sentinel should be absent before repair for $channel: $dep_path" >&2 - exit 1 - fi -} - -remove_runtime_dep() { - local channel="$1" - local dep_path="$2" - local root - root="$(package_root)" - rm -rf "$root/dist/extensions/$channel/node_modules" - rm -rf "$root/dist/extensions/node_modules/$dep_path" - rm -rf "$root/node_modules/$dep_path" - rm -rf "$(stage_root)" -} - assert_update_ok() { local json_file="$1" local expected_before="$2" @@ -337,95 +118,95 @@ echo "Update targets: $UPDATE_TARGETS" npm install -g "$package_tgz" --no-fund --no-audit >/tmp/openclaw-update-baseline-install.log 2>&1 command -v openclaw >/dev/null poison_home_npm_project -baseline_root="$(package_root)" +baseline_root="$(bundled_channel_package_root)" test -d "$baseline_root/dist/extensions/telegram" test -d "$baseline_root/dist/extensions/feishu" test -d "$baseline_root/dist/extensions/acpx" if should_run_update_target telegram; then echo "Replicating configured Telegram missing-runtime state..." - write_config telegram - assert_no_dep_available telegram grammy + bundled_channel_write_config telegram + bundled_channel_assert_no_dep_available telegram grammy set +e openclaw doctor --non-interactive >/tmp/openclaw-baseline-doctor.log 2>&1 baseline_doctor_status=$? set -e echo "baseline doctor exited with $baseline_doctor_status" - remove_runtime_dep telegram grammy - assert_no_dep_available telegram grammy + bundled_channel_remove_runtime_dep telegram grammy + bundled_channel_assert_no_dep_available telegram grammy echo "Updating from baseline to current candidate; candidate doctor must repair Telegram deps..." run_update_and_capture telegram /tmp/openclaw-update-telegram.json cat /tmp/openclaw-update-telegram.json assert_update_ok /tmp/openclaw-update-telegram.json "$candidate_version" - assert_dep_available telegram grammy + bundled_channel_assert_dep_available telegram grammy assert_no_unknown_stage_roots echo "Mutating installed package: remove Telegram deps, then update-mode doctor repairs them..." - remove_runtime_dep telegram grammy - assert_no_dep_available telegram grammy + bundled_channel_remove_runtime_dep telegram grammy + bundled_channel_assert_no_dep_available telegram grammy if ! OPENCLAW_UPDATE_IN_PROGRESS=1 openclaw doctor --non-interactive >/tmp/openclaw-update-mode-doctor.log 2>&1; then echo "update-mode doctor failed while repairing Telegram deps" >&2 cat /tmp/openclaw-update-mode-doctor.log >&2 exit 1 fi - assert_dep_available telegram grammy + bundled_channel_assert_dep_available telegram grammy assert_no_unknown_stage_roots fi if should_run_update_target discord; then echo "Mutating config to Discord and rerunning same-version update path..." - write_config discord - remove_runtime_dep discord discord-api-types - assert_no_dep_available discord discord-api-types + bundled_channel_write_config discord + bundled_channel_remove_runtime_dep discord discord-api-types + bundled_channel_assert_no_dep_available discord discord-api-types run_update_and_capture discord /tmp/openclaw-update-discord.json cat /tmp/openclaw-update-discord.json assert_update_ok /tmp/openclaw-update-discord.json "$candidate_version" - assert_dep_available discord discord-api-types + bundled_channel_assert_dep_available discord discord-api-types fi if should_run_update_target slack; then echo "Mutating config to Slack and rerunning same-version update path..." - write_config slack - remove_runtime_dep slack @slack/web-api - assert_no_dep_available slack @slack/web-api + bundled_channel_write_config slack + bundled_channel_remove_runtime_dep slack @slack/web-api + bundled_channel_assert_no_dep_available slack @slack/web-api run_update_and_capture slack /tmp/openclaw-update-slack.json cat /tmp/openclaw-update-slack.json assert_update_ok /tmp/openclaw-update-slack.json "$candidate_version" - assert_dep_available slack @slack/web-api + bundled_channel_assert_dep_available slack @slack/web-api fi if should_run_update_target feishu; then echo "Mutating config to Feishu and rerunning same-version update path..." - write_config feishu - remove_runtime_dep feishu @larksuiteoapi/node-sdk - assert_no_dep_available feishu @larksuiteoapi/node-sdk + bundled_channel_write_config feishu + bundled_channel_remove_runtime_dep feishu @larksuiteoapi/node-sdk + bundled_channel_assert_no_dep_available feishu @larksuiteoapi/node-sdk run_update_and_capture feishu /tmp/openclaw-update-feishu.json cat /tmp/openclaw-update-feishu.json assert_update_ok /tmp/openclaw-update-feishu.json "$candidate_version" - assert_dep_available feishu @larksuiteoapi/node-sdk + bundled_channel_assert_dep_available feishu @larksuiteoapi/node-sdk fi if should_run_update_target memory-lancedb; then echo "Mutating config to memory-lancedb and rerunning same-version update path..." - write_config memory-lancedb - remove_runtime_dep memory-lancedb @lancedb/lancedb - assert_no_dep_available memory-lancedb @lancedb/lancedb + bundled_channel_write_config memory-lancedb + bundled_channel_remove_runtime_dep memory-lancedb @lancedb/lancedb + bundled_channel_assert_no_dep_available memory-lancedb @lancedb/lancedb run_update_and_capture memory-lancedb /tmp/openclaw-update-memory-lancedb.json cat /tmp/openclaw-update-memory-lancedb.json assert_update_ok /tmp/openclaw-update-memory-lancedb.json "$candidate_version" - assert_dep_available memory-lancedb @lancedb/lancedb + bundled_channel_assert_dep_available memory-lancedb @lancedb/lancedb fi if should_run_update_target acpx; then echo "Removing ACPX runtime package and rerunning same-version update path..." - write_config acpx - remove_runtime_dep acpx acpx - assert_no_dep_available acpx acpx + bundled_channel_write_config acpx + bundled_channel_remove_runtime_dep acpx acpx + bundled_channel_assert_no_dep_available acpx acpx run_update_and_capture acpx /tmp/openclaw-update-acpx.json cat /tmp/openclaw-update-acpx.json assert_update_ok /tmp/openclaw-update-acpx.json "$candidate_version" - assert_dep_available acpx acpx + bundled_channel_assert_dep_available acpx acpx fi echo "bundled channel runtime deps Docker update E2E passed"