UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

259 lines (258 loc) 11.2 kB
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 };