openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
259 lines (258 loc) • 11.2 kB
JavaScript
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { t as exitCliAfterOutput } from "./one-shot-exit-BkFPnjbM.js";
import { n as isTruthyEnvValue } from "./env-M3R40TOb.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { a as isDefaultInstallIdentity } from "./paths-D2sRr1a_.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { r as runCommandWithTimeout } from "./exec-BIE-3oLG.js";
import { a as readGatewayServiceState, o as resolveGatewayService } from "./service-Cc6NX6Jm.js";
import { l as ScheduledTaskAutoStartRecoveryError } from "./schtasks-CgL6lOw1.js";
import { r as isTerminalInteractive } from "./terminal-interactivity-DXUXAq5U.js";
import { n as classifyUpdateOutcome } from "./update-outcome-DbgisgjK.js";
import { n as UPDATE_RUNNER_TIMEOUT_MS } from "./update-runner-command-Cn6wUQJP.js";
import { t as note } from "./note-DqHk3fA1.js";
import { g as tryResolveInvocationCwd } from "./shared-BVh4WwpA.js";
import { r as resolveServiceRefreshEnv } from "./update-command-service-env-aputCzub.js";
import { t as createUpdateProgress } from "./progress-DO6ssDxW.js";
import { t as runGatewayUpdate } from "./update-runner-jVXrOKM6.js";
import { t as resolveUnsafeUpdateRecoveryGuidance } from "./update-recovery-guidance-BgtjnTYD.js";
import { i as isServiceRepairExternallyManaged, t as EXTERNAL_SERVICE_REPAIR_NOTE } from "./doctor-service-repair-policy-S9bPaCiq.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/commands/doctor-update.ts
/** Optional pre-doctor update prompt for source checkouts and package installs. */
async function resolveComparablePath(target) {
return await fs.realpath(target).catch(() => path.resolve(target));
}
async function detectOpenClawGitCheckout(root) {
const res = await runCommandWithTimeout([
"git",
"-C",
root,
"rev-parse",
"--show-toplevel"
], { timeoutMs: 5e3 }).catch(() => null);
if (!res) return "unknown";
if (res.code !== 0) {
if (normalizeLowercaseStringOrEmpty(res.stderr).includes("not a git repository")) return "not-git";
return "unknown";
}
return await resolveComparablePath(res.stdout.trim()) === await resolveComparablePath(root) ? "git" : "not-git";
}
/** Offers to update OpenClaw before doctor when running interactively from an updatable install. */
async function maybeOfferUpdateBeforeDoctor(params) {
if (!(!isTruthyEnvValue(process.env.OPENCLAW_UPDATE_IN_PROGRESS) && params.options.nonInteractive !== true && params.options.yes !== true && params.options.repair !== true && process.stdin.isTTY) || !params.root) return { updated: false };
const git = await detectOpenClawGitCheckout(params.root);
if (git === "git") {
if (!await params.confirm({
message: "Update OpenClaw from git before running doctor?",
initialValue: true
})) return { updated: false };
const updateRoot = params.root;
const invocationCwd = tryResolveInvocationCwd();
const operatorEnv = resolveServiceRefreshEnv(process.env, invocationCwd);
const { prepareUpdateFailureTriage } = await import("./update-triage-NRMm3Jqo.js");
const runTriage = await prepareUpdateFailureTriage({
runtime: params.runtime,
mode: isTerminalInteractive() ? "interactive" : "non-interactive",
invocationCwd
});
const completeFailedUpdate = async (result, serviceEnv) => {
await runTriage({
failure: { result },
target: {
root: updateRoot,
env: serviceEnv ?? operatorEnv
}
});
exitCliAfterOutput(params.runtime, 1);
};
const externallyManaged = isServiceRepairExternallyManaged();
const serviceLifecycle = isDefaultInstallIdentity(process.env) && !externallyManaged ? await import("./managed-gateway-update.runtime.js") : void 0;
let inspection = await serviceLifecycle?.maybeStopManagedServiceBeforeMutableUpdate({
updateInstallKind: "git",
root: updateRoot,
shouldRestart: true,
jsonMode: false,
phase: "inspect"
});
if (inspection?.blockMessage) {
note(inspection.blockMessage, "Update");
return { updated: false };
}
if (inspection?.serviceMutationSkipMessage) note(inspection.serviceMutationSkipMessage, "Update");
let gitMutationAuthorized = false;
let restartSafe = false;
let recoveryEnv;
note("Running update…", "Update");
const { progress, stop } = createUpdateProgress(process.stdout.isTTY);
const startedAt = Date.now();
let result;
const failedUpdate = (error, reason) => {
const message = formatErrorMessage(error);
const durationMs = Date.now() - startedAt;
params.runtime.error(message);
return {
...result,
status: "error",
mode: "git",
root: updateRoot,
reason,
recovery: result?.recovery?.serviceRestartSafe === false ? result.recovery : {
serviceRestartSafe: false,
reason: "runtime-verification-failed"
},
steps: [...result?.steps ?? [], {
name: reason,
command: "openclaw update",
cwd: updateRoot,
durationMs,
exitCode: 1,
stderrTail: message
}],
durationMs
};
};
try {
result = await runGatewayUpdate({
cwd: updateRoot,
argv1: process.argv[1],
progress,
allowGatewayServiceRepair: inspection?.serviceUpdateVerdict?.kind === "owned" && inspection.serviceUpdateVerdict.refreshDefinition,
allowGatewayActivation: Boolean(inspection?.running && inspection.serviceUpdateVerdict?.kind === "owned"),
beforeGitMutation: async () => {
if (serviceLifecycle) {
const previousSkip = inspection?.serviceMutationSkipMessage;
inspection = await serviceLifecycle.maybeStopManagedServiceBeforeMutableUpdate({
updateInstallKind: "git",
root: updateRoot,
shouldRestart: true,
jsonMode: false,
phase: "prepare",
expectedService: inspection?.serviceUpdateVerdict?.kind === "owned" ? inspection : void 0
});
if (inspection.blockMessage) throw new Error(inspection.blockMessage);
if (inspection.serviceMutationSkipMessage !== previousSkip && inspection.serviceMutationSkipMessage) note(inspection.serviceMutationSkipMessage, "Update");
inspection.windowsTaskAutoStartRecovery?.beginMutation();
}
gitMutationAuthorized = true;
return serviceLifecycle && inspection ? serviceLifecycle.resolvePreparedGatewayUpdatePolicy(inspection, true) : void 0;
}
});
restartSafe = result.recovery?.serviceRestartSafe ?? result.status === "ok";
if (restartSafe) await inspection?.windowsTaskAutoStartRecovery?.restore(true);
} catch (err) {
if (err instanceof ScheduledTaskAutoStartRecoveryError) recoveryEnv = err.serviceEnv;
else if (!gitMutationAuthorized) throw err;
const reason = err instanceof ScheduledTaskAutoStartRecoveryError ? "gateway-service-recovery-failed" : result ? "windows-task-autostart-restore-failed" : "update-failed";
result = failedUpdate(err, reason);
restartSafe = false;
if (reason === "update-failed") note("The source checkout may be partially mutated.", "Update");
} finally {
inspection?.windowsTaskAutoStartRecovery?.complete(restartSafe);
stop();
}
const ownedServiceEnv = recoveryEnv ?? (inspection?.serviceUpdateVerdict?.kind === "owned" ? inspection.serviceEnv : void 0);
const resultDetails = [
`Status: ${result.status}`,
`Mode: ${result.mode}`,
result.root && `Root: ${result.root}`,
result.reason && `Reason: ${result.reason}`
].filter(Boolean);
note(resultDetails.join("\n"), "Update result");
if (result.status !== "ok" || !restartSafe) {
if (result.recovery?.serviceRestartSafe === false || result.status === "error" && result.recovery?.serviceRestartSafe !== true) {
const recovery = result.recovery?.serviceRestartSafe === false ? result.recovery : {
serviceRestartSafe: false,
reason: "runtime-verification-failed"
};
result = {
...result,
status: "error",
recovery
};
const managedGatewayStopped = inspection?.stopped === true;
const summary = managedGatewayStopped ? `Managed gateway remains stopped because update recovery could not prove a runnable installation (${recovery.reason}).` : `Update recovery could not prove a runnable installation (${recovery.reason}).`;
const keepStopped = managedGatewayStopped ? "\nKeep the gateway stopped until the update succeeds." : "";
note(`${summary}\n${resolveUnsafeUpdateRecoveryGuidance(recovery.reason)}${keepStopped}`, "Update");
} else if (result.recovery?.serviceRestartSafe === true) {
const recovered = await serviceLifecycle?.maybeRestartServiceAfterFailedMutableUpdate({
recovery: result.recovery,
preManagedServiceStop: inspection,
jsonMode: false,
timeoutMs: UPDATE_RUNNER_TIMEOUT_MS,
invocationCwd
});
if (recovered) result = {
...result,
status: recovered === "failed" ? "error" : result.status,
recovery: {
...result.recovery,
service: recovered
}
};
}
if (classifyUpdateOutcome(result) === "failed") {
await completeFailedUpdate(result, ownedServiceEnv);
return {
updated: true,
handled: true
};
}
return {
updated: true,
handled: false
};
}
if (externallyManaged) note(EXTERNAL_SERVICE_REPAIR_NOTE, "Update");
else if (inspection?.stopped && inspection.serviceEnv && serviceLifecycle) try {
const service = resolveGatewayService();
const serviceState = await readGatewayServiceState(service, {
env: inspection.serviceEnv,
requireEffective: true
});
const verdict = await serviceLifecycle.revalidateManagedGatewayServiceAfterUpdate({
state: serviceState,
root: updateRoot,
preManagedServiceStop: inspection
});
if (!await serviceLifecycle.maybeRestartService({
shouldRestart: true,
result,
channel: "dev",
opts: {},
refreshServiceEnv: false,
serviceUpdateVerdict: verdict.kind === "owned" ? {
...verdict,
refreshDefinition: false
} : verdict,
serviceEnv: serviceState.env,
gatewayPort: await serviceLifecycle.resolveUpdatedGatewayRestartPort({
serviceEnv: serviceState.env,
serviceCommand: serviceState.command
}),
requireRunningServiceAfterRestart: true,
timeoutMs: 12e5
})) throw new Error("Gateway restart was not verified; run `openclaw gateway status --deep` before restarting manually.");
note("Restarted the running gateway service after updating OpenClaw.", "Update");
} catch (err) {
const message = "Update completed, but gateway service restart failed";
result = failedUpdate(/* @__PURE__ */ new Error(`${message}: ${formatErrorMessage(err)}`), "gateway-restart-failed");
params.outro(`${message}.`);
await completeFailedUpdate(result, ownedServiceEnv);
return {
updated: true,
handled: true
};
}
params.outro("Update completed (doctor already ran as part of the update).");
return {
updated: true,
handled: true
};
}
if (git === "not-git") note(["This install is not a git checkout.", `Run \`${formatCliCommand("openclaw update")}\` to update via your package manager (npm/pnpm), then rerun doctor.`].join("\n"), "Update");
return { updated: false };
}
//#endregion
export { maybeOfferUpdateBeforeDoctor };