UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

295 lines (294 loc) • 11.9 kB
import { l as normalizeOptionalString } from "./string-coerce-6kR-86XC.mjs"; import { o as asRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as hasErrnoCode } from "./errno-FR4CRyya.mjs"; import { t as formatErrorMessage } from "./errors-BSdPMFao.mjs"; import { n as AUTO_UPDATE_STEP_TIMEOUT_MS } from "./update-run-timeouts-Byb-PlTk.mjs"; import { i as runCommandWithTimeout } from "./exec-DkOImFhW.mjs"; import { r as isExactSemverVersion } from "./npm-registry-spec-CwucyVOH.mjs"; import { t as cancelUnreadResponseBody } from "./http-response-body-L4SAGcXJ.mjs"; import "./http-body-CZtbU96N.mjs"; import { n as parsePackageOpenClawSchemaVersions } from "./openclaw-schema-versions-PVhojTxP.mjs"; import { n as buildTimeoutAbortSignal } from "./fetch-timeout-DySwRfSX.mjs"; import "./npm-install-env-BzCWZDMx.mjs"; import { n as fetchRegistryPackageDocument } from "./update-check-package-target-R9enUjqC.mjs"; import { _ as runStep, l as resolveUpdateRegistryTarget } from "./update-check-Iw_iZ5NV.mjs"; import { r as collectPackageDistContentInventoryErrors } from "./package-dist-inventory-BxF3ZNuT.mjs"; import { i as createGlobalInstallEnv, p as resolveGlobalInstallTarget, r as collectInstalledGlobalPackageErrors } from "./update-global-CIjAVd4C.mjs"; import { a as resolveNpmGlobalPrefixLayoutFromPrefix } from "./update-npm-prefix-P-zkeNR6.mjs"; import { a as packNpmSpecToArchive, c as resolveNpmSpecMetadata, i as normalizeNpmViewMetadata, l as withInstallWorkspace } from "./install-source-utils-C8ESZ4tU.mjs"; import { t as withUpdateCommandExecutor } from "./update-command-executor-TUUoFiFs.mjs"; import { t as resolveNpmIntegrityDriftWithDefaultMessage } from "./npm-integrity-B2cxrzSS.mjs"; import { t as runGlobalPackageUpdateSteps } from "./package-update-steps-ZS3Gt9os.mjs"; import { n as assertNodeRuntimeUpdateCompatible, r as readNodeRuntimeUpdateManifest, t as assertNodeRuntimeSchemaVersions } from "./auto-update-compatibility-BUK-eidk.mjs"; import { createWriteStream } from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; import { createHash, randomUUID } from "node:crypto"; import { pipeline } from "node:stream/promises"; //#region src/infra/registry-package-archive.ts async function fetchRegistryPackageManifest(params) { try { const json = await fetchRegistryPackageDocument({ ...params, target: params.version, label: "npm package manifest", operation: "npm-registry-package-manifest", bodyTimeoutMs: Math.max(1, params.timeoutMs ?? 3e5) }); const metadata = normalizeNpmViewMetadata(json, `${params.packageName}@${params.version}`); const tarball = normalizeOptionalString(asRecord(asRecord(json).dist).tarball); if (!metadata?.name || !metadata.version || !tarball) throw new Error("Registry returned incomplete package metadata (name, version, or tarball)."); return { ok: true, metadata: { ...metadata, tarball } }; } catch (error) { return { ok: false, error: `Registry package manifest failed: ${String(error)}` }; } } async function downloadRegistryPackageArchive(params) { const { signal, cleanup, refresh } = buildTimeoutAbortSignal({ timeoutMs: Math.max(1, params.timeoutMs ?? 3e5), signal: params.signal, operation: "npm-registry-package-download" }); let response; let archivePath; try { const url = new URL(params.tarballUrl); if (url.origin !== new URL(params.registryUrl).origin) throw new Error("Package tarball must share the registry origin."); if (!/^sha512-[A-Za-z0-9+/]{86}==$/u.test(params.integrity)) throw new Error("Package archive requires supported sha512 integrity."); response = await fetch(url.toString(), { signal, redirect: "error" }); if (!response.ok || !response.body) throw new Error(`Package archive download failed: HTTP ${response.status}`); refresh(); const hash = createHash("sha512"); archivePath = path.join(params.cwd, `${randomUUID()}.tgz`); await pipeline(response.body, async function* (chunks) { for await (const chunk of chunks) { hash.update(chunk); refresh(); yield chunk; } }, createWriteStream(archivePath, { flags: "wx", mode: 384 }), { signal }); signal?.throwIfAborted(); if (`sha512-${hash.digest("base64")}` !== params.integrity) throw new Error("Package archive integrity mismatch."); return { ok: true, archivePath }; } catch (error) { if (archivePath) await fs$1.rm(archivePath, { force: true }); return { ok: false, error: `Registry package archive failed: ${String(error)}` }; } finally { await cancelUnreadResponseBody(response); cleanup(); } } //#endregion //#region src/node-host/auto-update-install.ts /** Install only into an immutable private generation; activation belongs to the node launcher. */ async function prepareNodeRuntimeUpdate(params) { const version = params.targetVersion.trim(); if (!isExactSemverVersion(version) || /[\\/]/u.test(version)) throw new Error("Node auto-update requires an exact published OpenClaw version."); params.signal?.throwIfAborted(); const spec = `openclaw@${version}`; const manager = process.versions.bun && process.platform !== "win32" ? "bun" : "npm"; const registryTarget = resolveUpdateRegistryTarget(); const registryManifest = manager === "bun" ? await fetchRegistryPackageManifest({ ...registryTarget, version, signal: params.signal }) : void 0; const resolved = registryManifest ?? await resolveNpmSpecMetadata({ spec, signal: params.signal }); if (!resolved.ok) throw new Error(resolved.error); const { metadata } = resolved; if (metadata.name !== "openclaw" || metadata.version !== version || !metadata.integrity) throw new Error("Node auto-update registry metadata does not identify the requested release and integrity."); assertNodeRuntimeSchemaVersions(parsePackageOpenClawSchemaVersions({ name: metadata.name, version: metadata.version, openclaw: metadata.packageOpenClaw })); const integrity = metadata.integrity; const digest = createHash("sha256").update(integrity).digest("hex"); const runtimeRoot = path.resolve(params.stateDir, "node-runtime", "releases", `${version}-${digest}`); const prepareGeneration = async (generationRoot) => { const layout = resolveNpmGlobalPrefixLayoutFromPrefix(generationRoot); const packageRoot = path.join(layout.globalRoot, "openclaw"); const prepared = { runtimeRoot: generationRoot, packageRoot, version, integrity }; return await withUpdateCommandExecutor(randomUUID(), async (executor) => { const fence = await executor.enter(generationRoot); const assertCurrent = () => { fence.assertCurrent(); params.signal?.throwIfAborted(); }; assertCurrent(); let retained = false; try { await fs$1.lstat(packageRoot); retained = true; } catch (error) { if (!hasErrnoCode(error, "ENOENT")) throw error; } if (retained) { try { const errors = [...await collectInstalledGlobalPackageErrors({ packageRoot, expectedVersion: version }), ...await collectPackageDistContentInventoryErrors(packageRoot)]; if (errors.length) throw new Error(errors.join("; ")); await readNodeRuntimeUpdateManifest(packageRoot); } catch (error) { assertCurrent(); return { invalidRuntime: `Preserved invalid node runtime at ${generationRoot}: ${formatErrorMessage(error)}` }; } await assertNodeRuntimeUpdateCompatible({ ...params, packageRoot }); assertCurrent(); return prepared; } return await withInstallWorkspace("openclaw-node-update-", async (workspace) => { assertCurrent(); let archivePath; if (registryManifest?.ok) { const downloaded = await downloadRegistryPackageArchive({ tarballUrl: registryManifest.metadata.tarball, registryUrl: registryTarget.registryUrl, integrity, cwd: workspace, signal: params.signal }); assertCurrent(); if (!downloaded.ok) throw new Error(downloaded.error); archivePath = downloaded.archivePath; } else { const packed = await packNpmSpecToArchive({ spec, cwd: workspace, timeoutMs: AUTO_UPDATE_STEP_TIMEOUT_MS, signal: params.signal }); assertCurrent(); if (!packed.ok) throw new Error(packed.error); if (packed.metadata.name !== "openclaw" || packed.metadata.version !== version) throw new Error("Node auto-update archive does not match the requested release."); const drift = await resolveNpmIntegrityDriftWithDefaultMessage({ spec, expectedIntegrity: integrity, resolution: packed.metadata }); if (drift.error) throw new Error(drift.error); archivePath = packed.archivePath; } const env = await createGlobalInstallEnv({ ...process.env, OPENCLAW_STATE_DIR: path.join(workspace, "state"), OPENCLAW_CONFIG_PATH: path.join(workspace, "openclaw.json"), ...manager === "bun" ? { BUN_INSTALL_GLOBAL_DIR: path.dirname(layout.globalRoot), BUN_INSTALL_BIN: layout.binDir } : {} }, { manager }); const runCommand = async (argv, options) => { assertCurrent(); const result = await runCommandWithTimeout(argv, { ...options, env: { ...env, ...options.env }, signal: params.signal, killProcessTree: true }); assertCurrent(); return result; }; const installTarget = await resolveGlobalInstallTarget({ manager, runCommand, timeoutMs: AUTO_UPDATE_STEP_TIMEOUT_MS, pkgRoot: packageRoot, honorPackageRoot: true, packageName: "openclaw", ...manager === "bun" ? { env } : {} }); if (installTarget.manager !== manager || installTarget.packageRoot !== packageRoot || installTarget.globalRoot !== layout.globalRoot) throw new Error(`Node auto-update ${manager} target escaped its private runtime prefix.`); assertCurrent(); if (manager === "bun") { await fs$1.mkdir(layout.globalRoot, { recursive: true }); await fs$1.mkdir(layout.binDir, { recursive: true }); await fs$1.writeFile(path.join(path.dirname(layout.globalRoot), "package.json"), "{\"private\":true}\n", { flag: "wx" }).catch((error) => { if (!hasErrnoCode(error, "EEXIST")) throw error; }); assertCurrent(); } const result = await runGlobalPackageUpdateSteps({ installTarget, installSpec: archivePath, packageName: "openclaw", runCommand, runStep: (step) => runStep({ ...step, runCommand, cwd: step.cwd ?? workspace, stepIndex: 0, totalSteps: 0 }), timeoutMs: AUTO_UPDATE_STEP_TIMEOUT_MS, env, installCwd: workspace, assertCurrent, validateCandidate: async (candidateRoot) => { if ((await readNodeRuntimeUpdateManifest(candidateRoot)).version !== version) throw new Error("Node auto-update installed an unexpected package version."); await assertNodeRuntimeUpdateCompatible({ ...params, packageRoot: candidateRoot }); assertCurrent(); return []; } }); assertCurrent(); if (result.failedStep || result.activePackageRoot !== packageRoot || result.afterVersion !== version) throw new Error(result.failedStep?.stderrTail ?? "Node auto-update private installation could not be verified."); return prepared; }); }); }; const initial = await prepareGeneration(runtimeRoot); if (!("invalidRuntime" in initial)) return initial; try { const replacement = await prepareGeneration(`${runtimeRoot}-${randomUUID()}`); if ("invalidRuntime" in replacement) throw new Error(replacement.invalidRuntime); return { ...replacement, warnings: [initial.invalidRuntime] }; } catch (cause) { throw new Error(`${initial.invalidRuntime}; fresh installation failed: ${formatErrorMessage(cause)}`, { cause }); } } //#endregion export { prepareNodeRuntimeUpdate };