mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(update): preserve plugin convergence through package restart (#131062)
* fix(update): preserve plugin convergence during package upgrades * fix(e2e): isolate upgrade restart auth setup * fix(e2e): isolate candidate restart config * fix(doctor): skip repairs for disabled plugins * fix(e2e): use canonical identity for restart install * fix(update): release plugin lease before doctor * fix(e2e): seed upgrade companion installs * fix(e2e): validate companion install versions * fix(ci): register upgrade config parking script * test(plugins): type npm install config fixture
This commit is contained in:
@@ -64,73 +64,6 @@ async function assertNoRequests(baseUrl) {
|
||||
}
|
||||
}
|
||||
|
||||
function parkPrepublishAuthoredConfig(configPath, snapshotPath) {
|
||||
if (!configPath || !snapshotPath) {
|
||||
throw new Error("park-prepublish-auth-config requires <config-path> <snapshot-path>");
|
||||
}
|
||||
const authoredConfig = fs.readFileSync(configPath);
|
||||
const config = JSON.parse(authoredConfig.toString("utf8"));
|
||||
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
||||
throw new Error("prepublish auth config must be a JSON object");
|
||||
}
|
||||
for (const key of ["plugins", "channels", "gateway"]) {
|
||||
const value = config[key];
|
||||
if (value !== undefined && (!value || typeof value !== "object" || Array.isArray(value))) {
|
||||
throw new Error(`prepublish auth config ${key} must be an object`);
|
||||
}
|
||||
}
|
||||
if (config.plugins?.allow !== undefined && !Array.isArray(config.plugins.allow)) {
|
||||
throw new Error("prepublish auth config plugins.allow must be an array");
|
||||
}
|
||||
if (
|
||||
config.plugins?.entries !== undefined &&
|
||||
(!config.plugins.entries ||
|
||||
typeof config.plugins.entries !== "object" ||
|
||||
Array.isArray(config.plugins.entries))
|
||||
) {
|
||||
throw new Error("prepublish auth config plugins.entries must be an object");
|
||||
}
|
||||
if (
|
||||
config.gateway?.reload !== undefined &&
|
||||
(!config.gateway.reload ||
|
||||
typeof config.gateway.reload !== "object" ||
|
||||
Array.isArray(config.gateway.reload))
|
||||
) {
|
||||
throw new Error("prepublish auth config gateway.reload must be an object");
|
||||
}
|
||||
if (Array.isArray(config.plugins?.allow)) {
|
||||
config.plugins.allow = config.plugins.allow.filter((id) => id !== "whatsapp");
|
||||
}
|
||||
if (config.plugins?.entries && typeof config.plugins.entries === "object") {
|
||||
delete config.plugins.entries.whatsapp;
|
||||
}
|
||||
if (config.channels && typeof config.channels === "object") {
|
||||
delete config.channels.whatsapp;
|
||||
}
|
||||
config.gateway ??= {};
|
||||
config.gateway.reload = { ...config.gateway.reload, mode: "off" };
|
||||
fs.writeFileSync(snapshotPath, authoredConfig, { mode: 0o600 });
|
||||
replaceFileAtomically(configPath, Buffer.from(`${JSON.stringify(config, null, 2)}\n`));
|
||||
}
|
||||
|
||||
function restorePrepublishAuthoredConfig(configPath, snapshotPath) {
|
||||
if (!configPath || !snapshotPath) {
|
||||
throw new Error("restore-prepublish-auth-config requires <config-path> <snapshot-path>");
|
||||
}
|
||||
replaceFileAtomically(configPath, fs.readFileSync(snapshotPath));
|
||||
}
|
||||
|
||||
function replaceFileAtomically(filePath, contents) {
|
||||
const tempPath = `${filePath}.tmp.${process.pid}`;
|
||||
const mode = fs.statSync(filePath).mode;
|
||||
try {
|
||||
fs.writeFileSync(tempPath, contents, { mode });
|
||||
fs.renameSync(tempPath, filePath);
|
||||
} finally {
|
||||
fs.rmSync(tempPath, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function startPrepublishArtifactServer() {
|
||||
const manifest = JSON.parse(fs.readFileSync(artifactManifestFile, "utf8"));
|
||||
if (!Array.isArray(manifest.packages) || manifest.packages.length === 0) {
|
||||
@@ -731,16 +664,6 @@ if (profile === "assert-no-requests") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile === "park-prepublish-auth-config") {
|
||||
parkPrepublishAuthoredConfig(portFile, artifactManifestFile);
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile === "restore-prepublish-auth-config") {
|
||||
restorePrepublishAuthoredConfig(portFile, artifactManifestFile);
|
||||
return;
|
||||
}
|
||||
|
||||
const fixture = profiles[profile];
|
||||
if (!fixture || !portFile) {
|
||||
if (profile === "prepublish-artifacts" && portFile && artifactManifestFile) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
// Assertions for upgrade-survivor E2E scenarios.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -36,6 +37,18 @@ const PERSONA_FILES = new Map([
|
||||
const LEGACY_SESSION_MAIN_ID = "upgrade-main-session";
|
||||
const LEGACY_SESSION_DIRECT_ID = "upgrade-direct-session";
|
||||
const LEGACY_SESSION_GROUP_ID = "upgrade-group-session";
|
||||
const PLUGIN_DECLARED_SURFACE_GROUPS = [
|
||||
"channels",
|
||||
"providers",
|
||||
"tools",
|
||||
"contracts",
|
||||
"hooks",
|
||||
"mcpServers",
|
||||
"cliCommands",
|
||||
"cliBackends",
|
||||
"skills",
|
||||
"dangerousConfigFlags",
|
||||
];
|
||||
|
||||
function requireEnv(name) {
|
||||
const value = process.env[name];
|
||||
@@ -469,8 +482,14 @@ function assertConfigSurvived() {
|
||||
if (acceptsIntent(coverage, "discord-channel")) {
|
||||
const discord = config.channels?.discord;
|
||||
assert(discord?.enabled === true, "discord enabled flag changed");
|
||||
const discordAllowFrom = discord.allowFrom ?? discord.dm?.allowFrom;
|
||||
const discordDmPolicy = discord.dmPolicy ?? discord.dm?.policy;
|
||||
const stage = process.env.OPENCLAW_UPGRADE_SURVIVOR_ASSERT_STAGE || "survival";
|
||||
const discordAllowFrom =
|
||||
stage === "baseline" ? (discord.allowFrom ?? discord.dm?.allowFrom) : discord.allowFrom;
|
||||
const discordDmPolicy =
|
||||
stage === "baseline" ? (discord.dmPolicy ?? discord.dm?.policy) : discord.dmPolicy;
|
||||
if (stage !== "baseline") {
|
||||
assert(!Object.hasOwn(discord, "dm"), "legacy Discord DM config survived update");
|
||||
}
|
||||
assert(discordDmPolicy === "allowlist", "discord DM policy changed");
|
||||
assert(
|
||||
Array.isArray(discordAllowFrom) && discordAllowFrom.includes("111111111111111111"),
|
||||
@@ -1000,7 +1019,7 @@ function assertExternalPluginInstall(records, pluginId, packageName) {
|
||||
String(record.spec ?? record.resolvedSpec ?? "").startsWith(packageName),
|
||||
`configured external ${pluginId} plugin npm spec changed`,
|
||||
);
|
||||
return;
|
||||
return packageJson;
|
||||
}
|
||||
assert(
|
||||
record.clawhubPackage === packageName,
|
||||
@@ -1011,6 +1030,73 @@ function assertExternalPluginInstall(records, pluginId, packageName) {
|
||||
isPathInside(extensionsRoot, installPath),
|
||||
`configured external ${pluginId} ClawHub install path outside managed extensions root: ${installPath}`,
|
||||
);
|
||||
return packageJson;
|
||||
}
|
||||
|
||||
function pluginInstallIntegrity(record) {
|
||||
return record.integrity ?? record.npmIntegrity ?? record.clawpackSha256 ?? record.gitCommit;
|
||||
}
|
||||
|
||||
function acceptedSurfaceHash(surface) {
|
||||
const canonical = Object.fromEntries(
|
||||
PLUGIN_DECLARED_SURFACE_GROUPS.map((group) => [group, surface[group].toSorted()]),
|
||||
);
|
||||
return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
|
||||
}
|
||||
|
||||
function assertCompanionPluginConsent(record, pluginId) {
|
||||
const integrity = pluginInstallIntegrity(record);
|
||||
assert(
|
||||
typeof integrity === "string" && integrity.length > 0,
|
||||
`${pluginId} plugin integrity missing`,
|
||||
);
|
||||
assert(
|
||||
record.acceptedSurface && typeof record.acceptedSurface === "object",
|
||||
`${pluginId} plugin accepted surface missing`,
|
||||
);
|
||||
for (const group of PLUGIN_DECLARED_SURFACE_GROUPS) {
|
||||
assert(
|
||||
Array.isArray(record.acceptedSurface[group]),
|
||||
`${pluginId} plugin accepted surface ${group} missing`,
|
||||
);
|
||||
}
|
||||
assert(
|
||||
record.acceptedSurfaceHash === acceptedSurfaceHash(record.acceptedSurface),
|
||||
`${pluginId} plugin consent hash changed`,
|
||||
);
|
||||
assert(
|
||||
record.acceptedSurfaceIntegrity === integrity,
|
||||
`${pluginId} plugin consent integrity changed`,
|
||||
);
|
||||
assert(
|
||||
typeof record.acceptedSurfaceAt === "string" &&
|
||||
Number.isFinite(Date.parse(record.acceptedSurfaceAt)),
|
||||
`${pluginId} plugin consent timestamp missing`,
|
||||
);
|
||||
}
|
||||
|
||||
function assertCompanionPluginInstalls([expectedVersion]) {
|
||||
assert(expectedVersion, "assert-companion-installs requires <expected-version>");
|
||||
const records = readInstalledPluginIndex().installRecords ?? {};
|
||||
for (const [pluginId, packageName, source] of [
|
||||
["discord", "@openclaw/discord", "npm"],
|
||||
["whatsapp", "@openclaw/whatsapp", "clawhub"],
|
||||
["codex", "@openclaw/codex", "npm"],
|
||||
]) {
|
||||
const packageJson = assertExternalPluginInstall(records, pluginId, packageName);
|
||||
const record = records[pluginId];
|
||||
assert(record.source === source, `${pluginId} plugin source changed: ${record.source}`);
|
||||
const installedVersion = source === "clawhub" ? record.version : record.resolvedVersion;
|
||||
assert(
|
||||
installedVersion === expectedVersion,
|
||||
`${pluginId} plugin version changed: ${String(installedVersion)}`,
|
||||
);
|
||||
assert(
|
||||
packageJson.version === expectedVersion,
|
||||
`${pluginId} installed package version changed: ${String(packageJson.version)}`,
|
||||
);
|
||||
assertCompanionPluginConsent(record, pluginId);
|
||||
}
|
||||
}
|
||||
|
||||
function assertConfiguredPluginInstalls() {
|
||||
@@ -1229,6 +1315,8 @@ if (command === "list-scenarios") {
|
||||
} else if (command === "assert-state") {
|
||||
assertStateSurvived();
|
||||
assertConfiguredPluginInstalls();
|
||||
} else if (command === "assert-companion-installs") {
|
||||
assertCompanionPluginInstalls(process.argv.slice(3));
|
||||
} else if (command === "assert-status-json") {
|
||||
assertStatusJson(process.argv.slice(3));
|
||||
} else if (command === "assert-update-run-self-upgrade") {
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "node:fs";
|
||||
|
||||
function requireObject(value, label) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new Error(`${label} must be an object`);
|
||||
}
|
||||
}
|
||||
|
||||
function requirePaths(command, configPath, snapshotPath) {
|
||||
if (!configPath || !snapshotPath) {
|
||||
throw new Error(`${command} requires <config-path> <snapshot-path>`);
|
||||
}
|
||||
}
|
||||
|
||||
function replaceFileAtomically(filePath, contents) {
|
||||
const tempPath = `${filePath}.tmp.${process.pid}`;
|
||||
const mode = fs.statSync(filePath).mode;
|
||||
try {
|
||||
fs.writeFileSync(tempPath, contents, { mode });
|
||||
fs.renameSync(tempPath, filePath);
|
||||
} finally {
|
||||
fs.rmSync(tempPath, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function snapshotAndReplace(configPath, snapshotPath, authoredConfig, parkedConfig) {
|
||||
fs.writeFileSync(snapshotPath, authoredConfig, { mode: 0o600 });
|
||||
fs.chmodSync(snapshotPath, 0o600);
|
||||
replaceFileAtomically(
|
||||
configPath,
|
||||
Buffer.from(`${JSON.stringify(parkedConfig, null, 2)}\n`, "utf8"),
|
||||
);
|
||||
}
|
||||
|
||||
function parkPrepublish(configPath, snapshotPath) {
|
||||
requirePaths("park-prepublish", configPath, snapshotPath);
|
||||
const authoredConfig = fs.readFileSync(configPath);
|
||||
const config = JSON.parse(authoredConfig.toString("utf8"));
|
||||
requireObject(config, "prepublish auth config");
|
||||
for (const key of ["plugins", "channels", "gateway"]) {
|
||||
const value = config[key];
|
||||
if (value !== undefined) {
|
||||
requireObject(value, `prepublish auth config ${key}`);
|
||||
}
|
||||
}
|
||||
if (config.plugins?.allow !== undefined && !Array.isArray(config.plugins.allow)) {
|
||||
throw new Error("prepublish auth config plugins.allow must be an array");
|
||||
}
|
||||
if (config.plugins?.entries !== undefined) {
|
||||
requireObject(config.plugins.entries, "prepublish auth config plugins.entries");
|
||||
}
|
||||
if (config.gateway?.reload !== undefined) {
|
||||
requireObject(config.gateway.reload, "prepublish auth config gateway.reload");
|
||||
}
|
||||
if (Array.isArray(config.plugins?.allow)) {
|
||||
config.plugins.allow = config.plugins.allow.filter((id) => id !== "whatsapp");
|
||||
}
|
||||
if (config.plugins?.entries) {
|
||||
delete config.plugins.entries.whatsapp;
|
||||
}
|
||||
if (config.channels) {
|
||||
delete config.channels.whatsapp;
|
||||
}
|
||||
config.gateway ??= {};
|
||||
config.gateway.reload = { ...config.gateway.reload, mode: "off" };
|
||||
snapshotAndReplace(configPath, snapshotPath, authoredConfig, config);
|
||||
}
|
||||
|
||||
function parkRestartProbe(configPath, snapshotPath, rawPort) {
|
||||
requirePaths("park-restart-probe", configPath, snapshotPath);
|
||||
const port = Number(rawPort);
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
||||
throw new Error("park-restart-probe requires a valid port");
|
||||
}
|
||||
const authoredConfig = fs.readFileSync(configPath);
|
||||
requireObject(JSON.parse(authoredConfig.toString("utf8")), "restart probe config");
|
||||
snapshotAndReplace(configPath, snapshotPath, authoredConfig, {
|
||||
plugins: { enabled: false },
|
||||
gateway: {
|
||||
port,
|
||||
mode: "local",
|
||||
bind: "loopback",
|
||||
controlUi: { enabled: false },
|
||||
auth: {
|
||||
mode: "token",
|
||||
token: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "GATEWAY_AUTH_TOKEN_REF",
|
||||
},
|
||||
},
|
||||
reload: { mode: "off" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function parkCompanionInstall(configPath, snapshotPath) {
|
||||
requirePaths("park-companion-install", configPath, snapshotPath);
|
||||
const authoredConfig = fs.readFileSync(configPath);
|
||||
requireObject(JSON.parse(authoredConfig.toString("utf8")), "companion install config");
|
||||
snapshotAndReplace(configPath, snapshotPath, authoredConfig, {
|
||||
plugins: { enabled: false },
|
||||
});
|
||||
}
|
||||
|
||||
function restore(configPath, snapshotPath) {
|
||||
requirePaths("restore", configPath, snapshotPath);
|
||||
const authoredConfig = fs.readFileSync(snapshotPath);
|
||||
replaceFileAtomically(configPath, authoredConfig);
|
||||
if (!fs.readFileSync(configPath).equals(authoredConfig)) {
|
||||
throw new Error("restored config did not match authored bytes");
|
||||
}
|
||||
fs.rmSync(snapshotPath);
|
||||
}
|
||||
|
||||
const [command, configPath, snapshotPath, port] = process.argv.slice(2);
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case "park-prepublish":
|
||||
parkPrepublish(configPath, snapshotPath);
|
||||
break;
|
||||
case "park-restart-probe":
|
||||
parkRestartProbe(configPath, snapshotPath, port);
|
||||
break;
|
||||
case "park-companion-install":
|
||||
parkCompanionInstall(configPath, snapshotPath);
|
||||
break;
|
||||
case "restore":
|
||||
restore(configPath, snapshotPath);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
"usage: config-parking.mjs <park-prepublish|park-restart-probe|park-companion-install|restore> <config-path> <snapshot-path> [port]",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : error);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
@@ -462,8 +462,8 @@ prepublish_auto_auth_enabled() {
|
||||
|
||||
park_prepublish_authored_config() {
|
||||
prepublish_auto_auth_enabled || return 0
|
||||
node "${OPENCLAW_UPGRADE_SURVIVOR_CLAWHUB_FIXTURE_SERVER:-scripts/e2e/lib/clawhub-fixture-server.cjs}" \
|
||||
park-prepublish-auth-config "$OPENCLAW_CONFIG_PATH" "$PREPUBLISH_AUTHORED_CONFIG"
|
||||
node "${OPENCLAW_UPGRADE_SURVIVOR_CONFIG_PARKING_HELPER:-scripts/e2e/lib/upgrade-survivor/config-parking.mjs}" \
|
||||
park-prepublish "$OPENCLAW_CONFIG_PATH" "$PREPUBLISH_AUTHORED_CONFIG"
|
||||
}
|
||||
|
||||
assert_prepublish_fixture_idle() {
|
||||
@@ -474,15 +474,8 @@ assert_prepublish_fixture_idle() {
|
||||
|
||||
restore_prepublish_authored_config() {
|
||||
prepublish_auto_auth_enabled || return 0
|
||||
if ! node "${OPENCLAW_UPGRADE_SURVIVOR_CLAWHUB_FIXTURE_SERVER:-scripts/e2e/lib/clawhub-fixture-server.cjs}" \
|
||||
restore-prepublish-auth-config "$OPENCLAW_CONFIG_PATH" "$PREPUBLISH_AUTHORED_CONFIG"; then
|
||||
return 1
|
||||
fi
|
||||
if ! cmp -s "$PREPUBLISH_AUTHORED_CONFIG" "$OPENCLAW_CONFIG_PATH"; then
|
||||
echo "restored prepublish config did not match authored bytes" >&2
|
||||
return 1
|
||||
fi
|
||||
rm -f "$PREPUBLISH_AUTHORED_CONFIG"
|
||||
node "${OPENCLAW_UPGRADE_SURVIVOR_CONFIG_PARKING_HELPER:-scripts/e2e/lib/upgrade-survivor/config-parking.mjs}" \
|
||||
restore "$OPENCLAW_CONFIG_PATH" "$PREPUBLISH_AUTHORED_CONFIG"
|
||||
}
|
||||
|
||||
configure_plugin_registry() {
|
||||
|
||||
@@ -413,29 +413,85 @@ prepare_update_restart_probe_current_install() {
|
||||
local log_file="$2"
|
||||
local command_timeout="${OPENCLAW_UPGRADE_SURVIVOR_COMMAND_TIMEOUT:-900s}"
|
||||
local doctor_log="${log_file}.doctor"
|
||||
local authored_config="${log_file}.authored-config"
|
||||
local parking_helper="${OPENCLAW_UPGRADE_SURVIVOR_CONFIG_PARKING_HELPER:-scripts/e2e/lib/upgrade-survivor/config-parking.mjs}"
|
||||
local failure_stage=""
|
||||
local probe_status=0
|
||||
local restore_status=0
|
||||
local start_epoch
|
||||
local ready_epoch
|
||||
|
||||
echo "Preparing candidate-auth gateway for automatic update restart."
|
||||
install_update_restart_systemctl_shim
|
||||
seed_update_restart_probe_device_auth
|
||||
if ! openclaw_e2e_maybe_timeout "$command_timeout" openclaw doctor --fix --non-interactive >"$doctor_log" 2>&1; then
|
||||
# Service installation persists OPENCLAW_CONFIG_PATH, so isolate the canonical file in place.
|
||||
# Reload stays off through service setup; restoring authored bytes cannot restart this probe.
|
||||
node "$parking_helper" \
|
||||
park-restart-probe "$OPENCLAW_CONFIG_PATH" "$authored_config" "$port" || probe_status=$?
|
||||
if [ "$probe_status" -ne 0 ]; then
|
||||
echo "failed to park authored config for candidate restart probe" >&2
|
||||
if [ -e "$authored_config" ]; then
|
||||
node "$parking_helper" restore "$OPENCLAW_CONFIG_PATH" "$authored_config" ||
|
||||
restore_status=$?
|
||||
fi
|
||||
if [ "$restore_status" -ne 0 ]; then
|
||||
return "$restore_status"
|
||||
fi
|
||||
return "$probe_status"
|
||||
fi
|
||||
# This setup pass migrates candidate device identity while deferring plugin convergence.
|
||||
# Parent-write support lets that migration persist before the real update begins.
|
||||
openclaw_e2e_maybe_timeout \
|
||||
"$command_timeout" \
|
||||
env \
|
||||
OPENCLAW_UPDATE_IN_PROGRESS=1 \
|
||||
OPENCLAW_UPDATE_DEFER_CONFIGURED_PLUGIN_INSTALL_REPAIR=1 \
|
||||
OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE=1 \
|
||||
openclaw doctor --fix --non-interactive >"$doctor_log" 2>&1 || {
|
||||
probe_status=$?
|
||||
failure_stage="doctor"
|
||||
}
|
||||
if [ "$probe_status" -ne 0 ]; then
|
||||
echo "candidate device identity migration failed" >&2
|
||||
cat "$doctor_log" >&2 || true
|
||||
return 1
|
||||
fi
|
||||
start_epoch="$(node -e "process.stdout.write(String(Date.now()))")"
|
||||
env -u OPENCLAW_GATEWAY_TOKEN -u OPENCLAW_GATEWAY_PASSWORD openclaw gateway --port "$port" --bind loopback --allow-unconfigured >"$log_file" 2>&1 &
|
||||
gateway_pid="$!"
|
||||
printf '%s\n' "$gateway_pid" >"$OPENCLAW_UPGRADE_SURVIVOR_SYSTEMCTL_SHIM_PID_FILE"
|
||||
openclaw_e2e_wait_gateway_ready "$gateway_pid" "$log_file" 360 "$port"
|
||||
ready_epoch="$(node -e "process.stdout.write(String(Date.now()))")"
|
||||
start_seconds=$(((ready_epoch - start_epoch + 999) / 1000))
|
||||
write_update_restart_service_auth_env
|
||||
if ! openclaw_e2e_maybe_timeout "$command_timeout" env -u OPENCLAW_GATEWAY_TOKEN -u OPENCLAW_GATEWAY_PASSWORD openclaw gateway install --force --json >"$OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SERVICE_INSTALL_JSON" 2>"$OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SERVICE_INSTALL_ERR"; then
|
||||
if [ "$probe_status" -eq 0 ]; then
|
||||
start_epoch="$(node -e "process.stdout.write(String(Date.now()))")"
|
||||
env -u OPENCLAW_GATEWAY_TOKEN -u OPENCLAW_GATEWAY_PASSWORD openclaw gateway --port "$port" --bind loopback --allow-unconfigured >"$log_file" 2>&1 &
|
||||
gateway_pid="$!"
|
||||
printf '%s\n' "$gateway_pid" >"$OPENCLAW_UPGRADE_SURVIVOR_SYSTEMCTL_SHIM_PID_FILE"
|
||||
openclaw_e2e_wait_gateway_ready "$gateway_pid" "$log_file" 360 "$port" || {
|
||||
probe_status=$?
|
||||
failure_stage="readiness"
|
||||
}
|
||||
fi
|
||||
if [ "$probe_status" -eq 0 ]; then
|
||||
ready_epoch="$(node -e "process.stdout.write(String(Date.now()))")"
|
||||
start_seconds=$(((ready_epoch - start_epoch + 999) / 1000))
|
||||
write_update_restart_service_auth_env || {
|
||||
probe_status=$?
|
||||
failure_stage="service-env"
|
||||
}
|
||||
fi
|
||||
if [ "$probe_status" -eq 0 ]; then
|
||||
openclaw_e2e_maybe_timeout "$command_timeout" env -u OPENCLAW_GATEWAY_TOKEN -u OPENCLAW_GATEWAY_PASSWORD openclaw gateway install --force --json >"$OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SERVICE_INSTALL_JSON" 2>"$OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SERVICE_INSTALL_ERR" || {
|
||||
probe_status=$?
|
||||
failure_stage="install"
|
||||
}
|
||||
fi
|
||||
if [ "$failure_stage" = "install" ]; then
|
||||
echo "gateway service install failed" >&2
|
||||
cat "$OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SERVICE_INSTALL_ERR" >&2 || true
|
||||
cat "$OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SERVICE_INSTALL_JSON" >&2 || true
|
||||
return 1
|
||||
elif [ "$failure_stage" = "readiness" ]; then
|
||||
echo "candidate restart probe gateway did not become ready" >&2
|
||||
elif [ "$failure_stage" = "service-env" ]; then
|
||||
echo "failed to write candidate restart service environment" >&2
|
||||
fi
|
||||
node "$parking_helper" restore "$OPENCLAW_CONFIG_PATH" "$authored_config" || restore_status=$?
|
||||
if [ "$restore_status" -ne 0 ]; then
|
||||
echo "failed to restore authored config after candidate restart probe" >&2
|
||||
return "$restore_status"
|
||||
fi
|
||||
return "$probe_status"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ DOCKER_E2E_HARNESS_ROOT_DIR="$HARNESS_ROOT_DIR"
|
||||
source "$HARNESS_ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$HARNESS_ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
source "$HARNESS_ROOT_DIR/scripts/lib/openclaw-e2e-instance.sh"
|
||||
source "$HARNESS_ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-upgrade-survivor-e2e" OPENCLAW_UPGRADE_SURVIVOR_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_UPGRADE_SURVIVOR_E2E_SKIP_BUILD:-0}"
|
||||
@@ -91,7 +92,7 @@ LANE_ARTIFACT_SUFFIX="$(resolve_lane_artifact_suffix)"
|
||||
LANE_ARTIFACT_SUFFIX="${LANE_ARTIFACT_SUFFIX//[^A-Za-z0-9_.-]/_}"
|
||||
ARTIFACT_DIR="${OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_DIR:-$ROOT_DIR/.artifacts/upgrade-survivor/$LANE_ARTIFACT_SUFFIX}"
|
||||
DOCKER_RUN_USER_ARGS=()
|
||||
PREPUBLISH_PLUGIN_REGISTRY_ARGS=()
|
||||
OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS=()
|
||||
AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT=""
|
||||
PROBE_ENV_ARGS=(
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_PROBE_TIMEOUT_MS="$PROBE_TIMEOUT_MS"
|
||||
@@ -108,34 +109,9 @@ if [ -n "${OPENCLAW_UPGRADE_SURVIVOR_READYZ_ALLOW_DEGRADED:-}" ]; then
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_READYZ_ALLOW_DEGRADED="$OPENCLAW_UPGRADE_SURVIVOR_READYZ_ALLOW_DEGRADED"
|
||||
)
|
||||
fi
|
||||
configure_prepublish_plugin_registry() {
|
||||
local registry_dir="$1"
|
||||
PREPUBLISH_PLUGIN_REGISTRY_DIR="$(
|
||||
cd "$registry_dir" && pwd
|
||||
)"
|
||||
local manifest="$PREPUBLISH_PLUGIN_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 "$PREPUBLISH_PLUGIN_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_outer() {
|
||||
docker_e2e_cleanup_package_tgz "${PACKAGE_TGZ:-}"
|
||||
@@ -223,7 +199,7 @@ if [ "${OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE:-0}" = "1" ]; then
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS="$BASELINE_SPEC" \
|
||||
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS="$SCENARIO" \
|
||||
node "$HARNESS_ROOT_DIR/scripts/test-docker-all.mjs" --prepare-plugin-registry
|
||||
configure_prepublish_plugin_registry \
|
||||
openclaw_prepublish_plugin_registry_configure_docker_args \
|
||||
"$AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT/prepublish-plugin-registry"
|
||||
fi
|
||||
|
||||
@@ -260,13 +236,15 @@ if [ "${OPENCLAW_UPGRADE_SURVIVOR_PUBLISHED_BASELINE:-0}" = "1" ]; then
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS="$START_BUDGET_SECONDS" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS="$STATUS_BUDGET_SECONDS" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_CLAWHUB_FIXTURE_SERVER=/tmp/openclaw-clawhub-fixture-server.cjs \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_CONFIG_PARKING_HELPER=/tmp/openclaw-config-parking.mjs \
|
||||
"${PROBE_ENV_ARGS[@]}" \
|
||||
${LIVE_OPENAI_ENV_ARGS[@]+"${LIVE_OPENAI_ENV_ARGS[@]}"} \
|
||||
-v "$ARTIFACT_DIR:/tmp/openclaw-upgrade-survivor-artifacts" \
|
||||
-v "$TRUSTED_TSX_NODE_MODULES:/tmp/openclaw-release-harness/node_modules:ro" \
|
||||
-v "$HARNESS_ROOT_DIR/scripts/e2e/lib/clawhub-fixture-server.cjs:/tmp/openclaw-clawhub-fixture-server.cjs:ro" \
|
||||
-v "$HARNESS_ROOT_DIR/scripts/e2e/lib/upgrade-survivor/config-parking.mjs:/tmp/openclaw-config-parking.mjs:ro" \
|
||||
-v "$HARNESS_ROOT_DIR/scripts/e2e/lib/upgrade-survivor/run.sh:/tmp/openclaw-upgrade-survivor-run.sh:ro" \
|
||||
${PREPUBLISH_PLUGIN_REGISTRY_ARGS[@]+"${PREPUBLISH_PLUGIN_REGISTRY_ARGS[@]}"} \
|
||||
${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]+"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]}"} \
|
||||
${DOCKER_E2E_PACKAGE_ARGS[@]+"${DOCKER_E2E_PACKAGE_ARGS[@]}"} \
|
||||
${DOCKER_RUN_USER_ARGS[@]+"${DOCKER_RUN_USER_ARGS[@]}"} \
|
||||
"$IMAGE_NAME" \
|
||||
@@ -276,7 +254,23 @@ fi
|
||||
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz upgrade-survivor "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 upgrade-survivor upgrade-survivor)"
|
||||
if [ -z "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ]; then
|
||||
AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT="$(
|
||||
mktemp -d "${TMPDIR:-/tmp}/openclaw-upgrade-survivor-plugin-registry.XXXXXX"
|
||||
)"
|
||||
planner_lane="upgrade-survivor"
|
||||
if [ "$UPDATE_RESTART_MODE" = "auto-auth" ]; then
|
||||
planner_lane="update-restart-auth"
|
||||
fi
|
||||
OPENCLAW_DOCKER_ALL_LANES="$planner_lane" \
|
||||
OPENCLAW_DOCKER_ALL_LOG_DIR="$AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT" \
|
||||
OPENCLAW_DOCKER_ALL_TIMINGS=0 \
|
||||
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS="$SCENARIO" \
|
||||
node "$HARNESS_ROOT_DIR/scripts/test-docker-all.mjs" --prepare-plugin-registry
|
||||
openclaw_prepublish_plugin_registry_configure_docker_args \
|
||||
"$AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT/prepublish-plugin-registry"
|
||||
fi
|
||||
OPENCLAW_TEST_STATE_FUNCTION_B64="$(docker_e2e_test_state_function_b64)"
|
||||
mkdir -p "$ARTIFACT_DIR"
|
||||
chmod -R a+rwX "$ARTIFACT_DIR" || true
|
||||
|
||||
@@ -285,7 +279,7 @@ docker_e2e_build_or_reuse "$IMAGE_NAME" upgrade-survivor "$ROOT_DIR/scripts/e2e/
|
||||
echo "Running upgrade survivor Docker E2E..."
|
||||
docker_e2e_run_with_harness \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-e OPENCLAW_TEST_STATE_SCRIPT_B64="$OPENCLAW_TEST_STATE_SCRIPT_B64" \
|
||||
-e OPENCLAW_TEST_STATE_FUNCTION_B64="$OPENCLAW_TEST_STATE_FUNCTION_B64" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT=/tmp/openclaw-upgrade-survivor-artifacts \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_ROOT_MANAGED_VPS="$ROOT_MANAGED_VPS" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_SCENARIO="$SCENARIO" \
|
||||
@@ -294,10 +288,12 @@ docker_e2e_run_with_harness \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_START_BUDGET_SECONDS="$START_BUDGET_SECONDS" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_STATUS_BUDGET_SECONDS="$STATUS_BUDGET_SECONDS" \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_CLAWHUB_FIXTURE_SERVER=/tmp/openclaw-clawhub-fixture-server.cjs \
|
||||
-e OPENCLAW_UPGRADE_SURVIVOR_CONFIG_PARKING_HELPER=/tmp/openclaw-config-parking.mjs \
|
||||
"${PROBE_ENV_ARGS[@]}" \
|
||||
-v "$ARTIFACT_DIR:/tmp/openclaw-upgrade-survivor-artifacts" \
|
||||
-v "$HARNESS_ROOT_DIR/scripts/e2e/lib/clawhub-fixture-server.cjs:/tmp/openclaw-clawhub-fixture-server.cjs:ro" \
|
||||
"${PREPUBLISH_PLUGIN_REGISTRY_ARGS[@]}" \
|
||||
-v "$HARNESS_ROOT_DIR/scripts/e2e/lib/upgrade-survivor/config-parking.mjs:/tmp/openclaw-config-parking.mjs:ro" \
|
||||
"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]}" \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
"${DOCKER_RUN_USER_ARGS[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
@@ -331,10 +327,13 @@ export GATEWAY_AUTH_TOKEN_REF="upgrade-survivor-token"
|
||||
export OPENAI_API_KEY="sk-openclaw-upgrade-survivor"
|
||||
export DISCORD_BOT_TOKEN="upgrade-survivor-discord-token"
|
||||
export TELEGRAM_BOT_TOKEN="123456:upgrade-survivor-telegram-token"
|
||||
if [ "${OPENCLAW_UPGRADE_SURVIVOR_SCENARIO:-base}" = "feishu-channel" ]; then
|
||||
SCENARIO="${OPENCLAW_UPGRADE_SURVIVOR_SCENARIO:-base}"
|
||||
if [ "$SCENARIO" = "feishu-channel" ]; then
|
||||
export FEISHU_APP_SECRET="upgrade-survivor-feishu-secret"
|
||||
fi
|
||||
export BRAVE_API_KEY="BSA_upgrade_survivor_brave_key"
|
||||
if [ "$SCENARIO" = "configured-plugin-installs" ] || [ "$SCENARIO" = "sqlite-volume" ]; then
|
||||
export BRAVE_API_KEY="BSA_upgrade_survivor_brave_key"
|
||||
fi
|
||||
|
||||
UPDATE_RESTART_MODE="${OPENCLAW_UPGRADE_SURVIVOR_UPDATE_RESTART_MODE:-manual}"
|
||||
command_timeout="${OPENCLAW_UPGRADE_SURVIVOR_COMMAND_TIMEOUT:-900s}"
|
||||
@@ -462,7 +461,7 @@ NODE
|
||||
[ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ] || return 0
|
||||
fi
|
||||
|
||||
openclaw_prepublish_plugin_registry_start \
|
||||
openclaw_prepublish_plugin_registry_start \
|
||||
"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" \
|
||||
"${OPENCLAW_DOCKER_E2E_SELECTED_SHA:-}" \
|
||||
"$package_version" \
|
||||
@@ -472,7 +471,60 @@ NODE
|
||||
"${registry_args[@]}"
|
||||
}
|
||||
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
|
||||
install_companion_plugins() {
|
||||
local authored_config="$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT/companion-install-authored.json"
|
||||
local install_status=0
|
||||
local restore_status=0
|
||||
node "$OPENCLAW_UPGRADE_SURVIVOR_CONFIG_PARKING_HELPER" \
|
||||
park-companion-install "$OPENCLAW_CONFIG_PATH" "$authored_config"
|
||||
|
||||
set +e
|
||||
openclaw plugins install "npm:@openclaw/discord@$package_version" --pin --accept-capabilities
|
||||
install_status=$?
|
||||
if [ "$install_status" -eq 0 ]; then
|
||||
openclaw plugins install "clawhub:@openclaw/whatsapp@$package_version" --accept-capabilities
|
||||
install_status=$?
|
||||
fi
|
||||
if [ "$install_status" -eq 0 ]; then
|
||||
node "$OPENCLAW_UPGRADE_SURVIVOR_CLAWHUB_FIXTURE_SERVER" \
|
||||
assert-prepublish-requests "$OPENCLAW_CLAWHUB_URL" "@openclaw/whatsapp" "$package_version"
|
||||
install_status=$?
|
||||
fi
|
||||
if [ "$install_status" -eq 0 ]; then
|
||||
openclaw plugins install "npm:@openclaw/codex@$package_version" --pin --accept-capabilities
|
||||
install_status=$?
|
||||
fi
|
||||
node "$OPENCLAW_UPGRADE_SURVIVOR_CONFIG_PARKING_HELPER" \
|
||||
restore "$OPENCLAW_CONFIG_PATH" "$authored_config"
|
||||
restore_status=$?
|
||||
set -e
|
||||
|
||||
if [ "$install_status" -ne 0 ]; then
|
||||
return "$install_status"
|
||||
fi
|
||||
if [ "$restore_status" -ne 0 ]; then
|
||||
return "$restore_status"
|
||||
fi
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs \
|
||||
assert-companion-installs "$package_version"
|
||||
}
|
||||
|
||||
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_FUNCTION_B64:?missing OPENCLAW_TEST_STATE_FUNCTION_B64}"
|
||||
if [ "$UPDATE_RESTART_MODE" = "auto-auth" ]; then
|
||||
account_home="$(getent passwd "$(id -u)" | cut -d: -f6)"
|
||||
if [ -z "$account_home" ]; then
|
||||
echo "Could not resolve the current account home" >&2
|
||||
exit 1
|
||||
fi
|
||||
openclaw_test_state_create "$account_home" upgrade-survivor
|
||||
export HOME="$account_home"
|
||||
export USERPROFILE="$account_home"
|
||||
export OPENCLAW_STATE_DIR="$account_home/.openclaw"
|
||||
export OPENCLAW_CONFIG_PATH="$OPENCLAW_STATE_DIR/openclaw.json"
|
||||
unset OPENCLAW_HOME
|
||||
else
|
||||
openclaw_test_state_create upgrade-survivor upgrade-survivor
|
||||
fi
|
||||
node scripts/e2e/lib/upgrade-survivor/assertions.mjs seed
|
||||
|
||||
openclaw_e2e_install_package "$OPENCLAW_UPGRADE_SURVIVOR_ARTIFACT_ROOT/install.log" "upgrade survivor package" "$npm_config_prefix"
|
||||
@@ -487,13 +539,14 @@ echo "Checking dirty-state config before update..."
|
||||
OPENCLAW_UPGRADE_SURVIVOR_ASSERT_STAGE=baseline node scripts/e2e/lib/upgrade-survivor/assertions.mjs assert-config
|
||||
OPENCLAW_UPGRADE_SURVIVOR_ASSERT_STAGE=baseline node scripts/e2e/lib/upgrade-survivor/assertions.mjs assert-state
|
||||
configure_clawhub_fixture
|
||||
configure_plugin_registry
|
||||
install_companion_plugins
|
||||
if [ "$UPDATE_RESTART_MODE" = "auto-auth" ]; then
|
||||
# shellcheck disable=SC1091
|
||||
source scripts/e2e/lib/upgrade-survivor/update-restart-auth.sh
|
||||
prepare_update_restart_probe_current_install "$PORT" "$GATEWAY_LOG"
|
||||
fi
|
||||
|
||||
configure_plugin_registry
|
||||
echo "Running package update against the mounted tarball..."
|
||||
update_args=(update --tag "${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}" --yes --json)
|
||||
if [ "$UPDATE_RESTART_MODE" != "auto-auth" ]; then
|
||||
@@ -514,11 +567,6 @@ if [ "$update_status" -ne 0 ]; then
|
||||
openclaw_e2e_print_log /tmp/openclaw-upgrade-survivor-update.json >&2 || true
|
||||
exit "$update_status"
|
||||
fi
|
||||
if [ -n "${OPENCLAW_CLAWHUB_URL:-}" ]; then
|
||||
node "$OPENCLAW_UPGRADE_SURVIVOR_CLAWHUB_FIXTURE_SERVER" \
|
||||
assert-prepublish-requests "$OPENCLAW_CLAWHUB_URL" "@openclaw/whatsapp" "$package_version"
|
||||
fi
|
||||
|
||||
if [ "$UPDATE_RESTART_MODE" = "auto-auth" ]; then
|
||||
echo "Skipping doctor repair until after restart proof."
|
||||
else
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { join } from "node:path";
|
||||
import type { LaneBaseParams, LaneState } from "./config.ts";
|
||||
import { runInstalledCli } from "./installed.ts";
|
||||
import { runTimedLanePhase } from "./reporting.ts";
|
||||
import { runOpenClaw } from "./runtime.ts";
|
||||
|
||||
export async function installLaneCompanions(
|
||||
params: Pick<LaneBaseParams, "companions" | "logsDir"> & {
|
||||
lane: LaneState;
|
||||
env: NodeJS.ProcessEnv;
|
||||
cliPath?: string;
|
||||
},
|
||||
) {
|
||||
if (params.companions.length === 0) {
|
||||
return;
|
||||
}
|
||||
await runTimedLanePhase(params.lane, "install-companions", async () => {
|
||||
for (const companion of params.companions) {
|
||||
const logPath = join(
|
||||
params.logsDir,
|
||||
`companion-${companion.name.replace(/[^a-z0-9]+/giu, "-")}.log`,
|
||||
);
|
||||
const args = [
|
||||
"plugins",
|
||||
"install",
|
||||
`npm-pack:${companion.tarballPath}`,
|
||||
"--force",
|
||||
"--accept-capabilities",
|
||||
];
|
||||
if (params.cliPath) {
|
||||
await runInstalledCli({
|
||||
cliPath: params.cliPath,
|
||||
args,
|
||||
env: params.env,
|
||||
cwd: params.lane.homeDir,
|
||||
logPath,
|
||||
timeoutMs: 10 * 60 * 1000,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
await runOpenClaw({
|
||||
lane: params.lane,
|
||||
args,
|
||||
env: params.env,
|
||||
logPath,
|
||||
timeoutMs: 10 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
waitForInstalledGateway,
|
||||
waitForInstalledGatewayToStop,
|
||||
} from "./installed.ts";
|
||||
import { installLaneCompanions } from "./lane-companions.ts";
|
||||
import { maybeRunDiscordRoundtrip } from "./network-smokes.ts";
|
||||
import {
|
||||
reserveGatewayPortForLane,
|
||||
@@ -75,45 +76,6 @@ import {
|
||||
} from "./runtime.ts";
|
||||
import { formatError, trimForSummary } from "./shared.ts";
|
||||
|
||||
async function installLaneCompanions(
|
||||
params: LaneBaseParams & {
|
||||
lane: LaneState;
|
||||
env: NodeJS.ProcessEnv;
|
||||
cliPath?: string;
|
||||
},
|
||||
) {
|
||||
if (params.companions.length === 0) {
|
||||
return;
|
||||
}
|
||||
await runTimedLanePhase(params.lane, "install-companions", async () => {
|
||||
for (const companion of params.companions) {
|
||||
const logPath = join(
|
||||
params.logsDir,
|
||||
`companion-${companion.name.replace(/[^a-z0-9]+/giu, "-")}.log`,
|
||||
);
|
||||
const args = ["plugins", "install", `npm-pack:${companion.tarballPath}`, "--force"];
|
||||
if (params.cliPath) {
|
||||
await runInstalledCli({
|
||||
cliPath: params.cliPath,
|
||||
args,
|
||||
env: params.env,
|
||||
cwd: params.lane.homeDir,
|
||||
logPath,
|
||||
timeoutMs: 10 * 60 * 1000,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
await runOpenClaw({
|
||||
lane: params.lane,
|
||||
args,
|
||||
env: params.env,
|
||||
logPath,
|
||||
timeoutMs: 10 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function runFreshLane(params: LaneBaseParams & { build: CandidateBuild }) {
|
||||
const lane = createLaneState("fresh");
|
||||
const cleanup: Cleanup[] = [];
|
||||
|
||||
Reference in New Issue
Block a user