UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

619 lines (618 loc) 27.3 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { o as isRecord } from "./record-coerce-DHZ4bFlT.js"; import { n as resolveOpenClawPackageRootSync } from "./openclaw-root-CNp1Ofdk.js"; import { o as hasErrnoCode } from "./errors-BXgSefBE.js"; import { a as readJsonIfExists, m as writeJson, n as readJson } from "./json-files-2umMHm0W.js"; import { r as runCommandWithTimeout } from "./exec-DubsSJS2.js"; import { n as createSafeNpmInstallEnv, t as createSafeNpmInstallArgs } from "./safe-package-install-BpZqyGc8.js"; import path from "node:path"; import fs from "node:fs/promises"; import os from "node:os"; import { parse } from "yaml"; //#region src/infra/npm-managed-root.ts function readDependencyRecord(value) { if (!isRecord(value)) return {}; const dependencies = {}; for (const [key, raw] of Object.entries(value)) if (typeof raw === "string") dependencies[key] = raw; return dependencies; } function isSafePackageName(name) { if (name.startsWith("@")) { const parts = name.split("/"); return parts.length === 2 && parts.every((part) => part.length > 0 && part !== "." && part !== ".."); } return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".."; } function isManagedNpmRootHostPeerPackageName(name) { return name === "openclaw"; } function readOverrideRecord(value) { if (!isRecord(value)) return {}; const overrides = {}; for (const [key, raw] of Object.entries(value)) if (key.trim()) overrides[key] = raw; return overrides; } function readManagedOverrideKeys(value) { if (!isRecord(value) || !Array.isArray(value.managedOverrides)) return []; return value.managedOverrides.filter((key) => typeof key === "string"); } function readManagedPeerDependencyKeys(value) { if (!isRecord(value) || !Array.isArray(value.managedPeerDependencies)) return []; return value.managedPeerDependencies.filter((key) => typeof key === "string"); } function buildManagedOpenClawMetadata(params) { const metadata = isRecord(params.current) ? { ...params.current } : {}; if (params.managedOverrideKeys.length > 0) metadata.managedOverrides = params.managedOverrideKeys; else delete metadata.managedOverrides; const managedPeerDependencyKeys = params.managedPeerDependencyKeys; if (managedPeerDependencyKeys && managedPeerDependencyKeys.length > 0) metadata.managedPeerDependencies = managedPeerDependencyKeys; else if (managedPeerDependencyKeys) delete metadata.managedPeerDependencies; return Object.keys(metadata).length > 0 ? metadata : void 0; } async function readManagedNpmRootManifest(filePath) { const parsed = await readJsonIfExists(filePath); return isRecord(parsed) ? { ...parsed } : {}; } async function readHostWorkspaceOverrides(packageRoot) { const workspace = parse(await fs.readFile(path.join(packageRoot, "pnpm-workspace.yaml"), "utf8")); return isRecord(workspace) ? readOverrideRecord(workspace.overrides) : {}; } function readHostDependencySpec(manifest, packageName) { return manifest.dependencies?.[packageName] ?? manifest.optionalDependencies?.[packageName] ?? manifest.peerDependencies?.[packageName] ?? manifest.devDependencies?.[packageName]; } function resolveHostOverrideReferences(value, manifest) { if (typeof value === "string" && value.startsWith("$")) return readHostDependencySpec(manifest, value.slice(1)) ?? value; if (!isRecord(value)) return value; const resolved = {}; for (const [key, nested] of Object.entries(value)) resolved[key] = resolveHostOverrideReferences(nested, manifest); return resolved; } function isUnsupportedManagedNpmOverride(value) { return typeof value === "string" && value.trim().startsWith("npm:"); } function filterUnsupportedManagedNpmRootOverrides(value) { const overrides = readOverrideRecord(value); const filtered = {}; for (const [key, raw] of Object.entries(overrides)) { if (isUnsupportedManagedNpmOverride(raw)) continue; if (isRecord(raw)) { const nested = filterUnsupportedManagedNpmRootOverrides(raw); if (Object.keys(nested).length > 0) filtered[key] = nested; continue; } filtered[key] = raw; } return filtered; } /** Read host OpenClaw pnpm overrides for reuse inside a managed npm root. */ async function readOpenClawManagedNpmRootOverrides(params) { const packageRoot = params?.packageRoot ?? resolveOpenClawPackageRootSync({ argv1: params?.argv1 ?? process.argv[1], moduleUrl: params?.moduleUrl ?? import.meta.url, cwd: params?.cwd ?? process.cwd() }); if (!packageRoot) return {}; try { const manifest = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8")); if (!isRecord(manifest)) return {}; const hostManifest = manifest; const overrides = await readHostWorkspaceOverrides(packageRoot); return Object.fromEntries(Object.entries(overrides).map(([key, value]) => [key, resolveHostOverrideReferences(value, hostManifest)])); } catch { return {}; } } /** Resolve the dependency spec to write for a parsed registry package. */ function resolveManagedNpmRootDependencySpec(params) { return params.resolution.version ?? params.parsedSpec.selector ?? "latest"; } /** Insert or update a dependency and managed override metadata in package.json. */ async function upsertManagedNpmRootDependency(params) { await fs.mkdir(params.npmRoot, { recursive: true }); const manifestPath = path.join(params.npmRoot, "package.json"); const manifest = await readManagedNpmRootManifest(manifestPath); const dependencies = readDependencyRecord(manifest.dependencies); const managedOverrides = params.omitUnsupportedManagedOverrides ? filterUnsupportedManagedNpmRootOverrides(params.managedOverrides) : readOverrideRecord(params.managedOverrides); const managedOverrideKeys = Object.keys(managedOverrides).toSorted(); const overrides = readOverrideRecord(manifest.overrides); for (const key of readManagedOverrideKeys(manifest.openclaw)) delete overrides[key]; Object.assign(overrides, managedOverrides); const openclawMetadata = buildManagedOpenClawMetadata({ current: manifest.openclaw, managedOverrideKeys }); const next = { ...manifest, private: true, dependencies: { ...dependencies, [params.packageName]: params.dependencySpec } }; if (Object.keys(overrides).length > 0) next.overrides = overrides; else delete next.overrides; if (openclawMetadata) next.openclaw = openclawMetadata; else delete next.openclaw; await writeJson(manifestPath, next, { trailingNewline: true }); } function isOptionalPeerDependency(manifest, peerName) { if (!isRecord(manifest.peerDependenciesMeta)) return false; const peerMetadata = manifest.peerDependenciesMeta[peerName]; return isRecord(peerMetadata) && peerMetadata.optional === true; } function isDevOnlyLockPackage(value) { return isRecord(value) && value.dev === true; } function readStringList(value) { if (typeof value === "string") return [value]; if (!Array.isArray(value)) return; const values = value.filter((entry) => typeof entry === "string"); return values.length > 0 ? values : void 0; } function matchesNpmPlatformList(value, list) { if (!list) return true; if (list.length === 1 && list[0] === "any") return true; if (!value) return false; let negated = 0; let matched = false; for (const entry of list) { const negate = entry.startsWith("!"); const test = negate ? entry.slice(1) : entry; if (negate) { negated += 1; if (value === test) return false; } else matched = matched || value === test; } return matched || negated === list.length; } function resolveCurrentLibc() { if (process.platform !== "linux") return; const report = process.report?.getReport(); const header = isRecord(report) ? report.header : void 0; if (isRecord(header) && header.glibcVersionRuntime) return "glibc"; const sharedObjects = isRecord(report) ? report.sharedObjects : void 0; if (Array.isArray(sharedObjects) && sharedObjects.some((file) => typeof file === "string" && file.includes("musl"))) return "musl"; } function isUnsupportedOptionalLockPackage(value) { if (!isRecord(value) || value.optional !== true) return false; return !matchesNpmPlatformList(process.platform, readStringList(value.os)) || !matchesNpmPlatformList(process.arch, readStringList(value.cpu)) || !matchesNpmPlatformList(resolveCurrentLibc(), readStringList(value.libc)); } function readLockPackageName(location, value) { if (isRecord(value)) { const packageName = normalizeOptionalString(value.name); if (packageName) return packageName; } const parts = location.split("/"); for (let index = parts.length - 1; index >= 0; index -= 1) { if (parts[index] !== "node_modules") continue; const first = parts[index + 1]; if (!first) return; if (!first.startsWith("@")) return first; const second = parts[index + 2]; return second ? `${first}/${second}` : void 0; } } function isTopLevelLockPackageLocation(location) { return location.split("/").filter((part) => part === "node_modules").length === 1; } function findLockPackageVersion(params) { if (!isRecord(params.lockfile.packages)) return; const preferredLocation = `node_modules/${params.packageName}`; const preferredPackage = params.lockfile.packages[preferredLocation]; if (isRecord(preferredPackage) && !isDevOnlyLockPackage(preferredPackage) && !isUnsupportedOptionalLockPackage(preferredPackage)) { const preferredVersion = normalizeOptionalString(preferredPackage.version); if (preferredVersion) return preferredVersion; } } function collectNpmLockPeerDependencyPins(params) { const pins = /* @__PURE__ */ new Map(); const packages = isRecord(params.lockfile.packages) ? params.lockfile.packages : {}; for (const [location, value] of Object.entries(packages).toSorted(([left], [right]) => left.localeCompare(right))) { if (location === "" || !isRecord(value) || isDevOnlyLockPackage(value) || isUnsupportedOptionalLockPackage(value)) continue; const packageName = readLockPackageName(location, value); if (packageName && isManagedNpmRootHostPeerPackageName(packageName)) continue; const peerDependencies = readDependencyRecord(value.peerDependencies); for (const [peerName, peerRange] of Object.entries(peerDependencies)) { if (isManagedNpmRootHostPeerPackageName(peerName) || pins.has(peerName) || !isSafePackageName(peerName)) continue; const version = findLockPackageVersion({ lockfile: params.lockfile, packageName: peerName }); if (!version && isOptionalPeerDependency(value, peerName)) continue; if (!version && !isTopLevelLockPackageLocation(location)) continue; pins.set(peerName, version ?? peerRange); } } return Object.fromEntries([...pins.entries()].toSorted(([left], [right]) => left.localeCompare(right))); } async function copyPathIfExists(source, destination) { try { await fs.cp(source, destination, { recursive: true }); } catch (err) { if (err.code === "ENOENT") return; throw err; } } function scrubHostPeerFromLockPackage(value) { if (!isRecord(value)) return false; let changed = false; if (isRecord(value.peerDependencies) && "openclaw" in value.peerDependencies) { const peerDependencies = { ...value.peerDependencies }; delete peerDependencies.openclaw; if (Object.keys(peerDependencies).length > 0) value.peerDependencies = peerDependencies; else delete value.peerDependencies; changed = true; } if (isRecord(value.peerDependenciesMeta) && "openclaw" in value.peerDependenciesMeta) { const peerDependenciesMeta = { ...value.peerDependenciesMeta }; delete peerDependenciesMeta.openclaw; if (Object.keys(peerDependenciesMeta).length > 0) value.peerDependenciesMeta = peerDependenciesMeta; else delete value.peerDependenciesMeta; changed = true; } return changed; } async function scrubHostPeerFromTempPackageLock(lockPath) { const parsed = await readJsonIfExists(lockPath); if (!isRecord(parsed)) return; let changed = false; if (isRecord(parsed.packages)) for (const value of Object.values(parsed.packages)) changed = scrubHostPeerFromLockPackage(value) || changed; if (isRecord(parsed.dependencies)) for (const value of Object.values(parsed.dependencies)) changed = scrubHostPeerFromLockPackage(value) || changed; if (changed) await writeJson(lockPath, parsed, { trailingNewline: true }); } function collectExistingManagedPeerDependencyPins(dependencies, previousManagedPeerDependencies) { const pins = {}; for (const packageName of previousManagedPeerDependencies) { const dependencySpec = dependencies[packageName]; if (dependencySpec) pins[packageName] = dependencySpec; } return pins; } function isHostPeerResolutionFailure(result) { const output = `${result.stdout}\n${result.stderr}`; return /(^|[^@\w.-])openclaw(?=$|[@\s:,"'])/i.test(output); } function createManagedNpmPeerPlanArgs(params) { return [ "npm", "install", "--package-lock-only", ...params?.force ? ["--force"] : [], ...createSafeNpmInstallArgs({ omitDev: true, omitPeer: true, legacyPeerDeps: params?.legacyPeerDeps, loglevel: "error", ignoreWorkspaces: true, noAudit: true, noFund: true }).slice(1) ]; } async function collectNpmResolvedManagedNpmRootPeerDependencyPins(params) { const manifest = await readManagedNpmRootManifest(path.join(params.npmRoot, "package.json")); const dependencies = readDependencyRecord(manifest.dependencies); const previousManagedPeerDependencies = readManagedPeerDependencyKeys(manifest.openclaw); const fallbackPeerPins = collectExistingManagedPeerDependencyPins(dependencies, previousManagedPeerDependencies); for (const packageName of previousManagedPeerDependencies) delete dependencies[packageName]; const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-managed-peer-plan-")); try { delete dependencies.openclaw; await writeJson(path.join(tempRoot, "package.json"), { ...manifest, private: true, dependencies }, { trailingNewline: true }); await copyPathIfExists(path.join(params.npmRoot, "package-lock.json"), path.join(tempRoot, "package-lock.json")); const tempLockPath = path.join(tempRoot, "package-lock.json"); await scrubHostPeerFromTempPackageLock(tempLockPath); await copyPathIfExists(path.join(params.npmRoot, ".npmrc"), path.join(tempRoot, ".npmrc")); await copyPathIfExists(path.join(params.npmRoot, "_openclaw-pack-archives"), path.join(tempRoot, "_openclaw-pack-archives")); const command = params.runCommand ?? runCommandWithTimeout; const npmPeerPlanArgs = createManagedNpmPeerPlanArgs({ force: true }); const npmPlanOptions = { cwd: tempRoot, timeoutMs: Math.max(params.timeoutMs ?? 3e5, 3e5), env: createSafeNpmInstallEnv(process.env, { legacyPeerDeps: false, npmConfigCwd: tempRoot, packageLock: true, quiet: true }) }; const result = await command(npmPeerPlanArgs, npmPlanOptions); if (result.code !== 0) { if (isHostPeerResolutionFailure(result)) { if ((await command(createManagedNpmPeerPlanArgs({ force: true, legacyPeerDeps: true }), { ...npmPlanOptions, env: createSafeNpmInstallEnv(process.env, { legacyPeerDeps: true, npmConfigCwd: tempRoot, packageLock: true, quiet: true }) })).code === 0) return collectNpmLockPeerDependencyPins({ lockfile: await readManagedNpmRootManifest(tempLockPath) }); } return fallbackPeerPins; } return collectNpmLockPeerDependencyPins({ lockfile: await readManagedNpmRootManifest(tempLockPath) }); } finally { await fs.rm(tempRoot, { recursive: true, force: true }); } } /** Snapshot managed peer dependencies before a risky install/update operation. */ async function readManagedNpmRootPeerDependencySnapshot(params) { const manifest = await readManagedNpmRootManifest(path.join(params.npmRoot, "package.json")); const dependencies = readDependencyRecord(manifest.dependencies); const managedPeerDependencies = readManagedPeerDependencyKeys(manifest.openclaw).toSorted(); const dependencySnapshot = {}; for (const packageName of managedPeerDependencies) { const dependencySpec = dependencies[packageName]; if (dependencySpec) dependencySnapshot[packageName] = dependencySpec; } return { dependencies: dependencySnapshot, managedPeerDependencies }; } /** Restore a previously captured managed peer dependency snapshot. */ async function restoreManagedNpmRootPeerDependencySnapshot(params) { const manifestPath = path.join(params.npmRoot, "package.json"); const manifest = await readManagedNpmRootManifest(manifestPath); const dependencies = readDependencyRecord(manifest.dependencies); for (const packageName of readManagedPeerDependencyKeys(manifest.openclaw)) delete dependencies[packageName]; Object.assign(dependencies, params.snapshot.dependencies); const managedOverrideKeys = readManagedOverrideKeys(manifest.openclaw).toSorted(); const openclawMetadata = buildManagedOpenClawMetadata({ current: manifest.openclaw, managedOverrideKeys, managedPeerDependencyKeys: params.snapshot.managedPeerDependencies.toSorted() }); const next = { ...manifest, private: true, dependencies }; if (openclawMetadata) next.openclaw = openclawMetadata; else delete next.openclaw; await writeJson(manifestPath, next, { trailingNewline: true }); } /** Sync package.json with peer dependency pins resolved from npm's lock plan. */ async function syncManagedNpmRootPeerDependencies(params) { const manifestPath = path.join(params.npmRoot, "package.json"); const manifest = await readManagedNpmRootManifest(manifestPath); const dependencies = readDependencyRecord(manifest.dependencies); const previousManagedPeerDependencies = readManagedPeerDependencyKeys(manifest.openclaw); const previousManagedPeerDependencySet = new Set(previousManagedPeerDependencies); const peerPins = await collectNpmResolvedManagedNpmRootPeerDependencyPins({ npmRoot: params.npmRoot, runCommand: params.runCommand, timeoutMs: params.timeoutMs }); const nextDependencies = { ...dependencies }; for (const packageName of previousManagedPeerDependencies) if (!Object.hasOwn(peerPins, packageName)) delete nextDependencies[packageName]; for (const [packageName, dependencySpec] of Object.entries(peerPins)) nextDependencies[packageName] = dependencies[packageName] ?? dependencySpec; const managedOverrides = params.omitUnsupportedManagedOverrides ? filterUnsupportedManagedNpmRootOverrides(params.managedOverrides) : readOverrideRecord(params.managedOverrides); const managedOverrideKeys = Object.keys(managedOverrides).toSorted(); const overrides = readOverrideRecord(manifest.overrides); for (const key of readManagedOverrideKeys(manifest.openclaw)) delete overrides[key]; Object.assign(overrides, managedOverrides); const managedPeerDependencyKeys = Object.keys(peerPins).filter((packageName) => previousManagedPeerDependencySet.has(packageName) || !Object.hasOwn(dependencies, packageName)).toSorted(); const openclawMetadata = buildManagedOpenClawMetadata({ current: manifest.openclaw, managedOverrideKeys, managedPeerDependencyKeys }); const next = { ...manifest, private: true, dependencies: nextDependencies }; if (Object.keys(overrides).length > 0) next.overrides = overrides; else delete next.overrides; if (openclawMetadata) next.openclaw = openclawMetadata; else delete next.openclaw; const changed = JSON.stringify(next) !== JSON.stringify(manifest); if (changed) await writeJson(manifestPath, next, { trailingNewline: true }); return changed; } /** Remove stale managed-root openclaw peer installs while preserving active host links. */ async function repairManagedNpmRootOpenClawPeer(params) { await fs.mkdir(params.npmRoot, { recursive: true }); const activeHostState = await readManagedNpmRootOpenClawHostState({ npmRoot: params.npmRoot, packageRoot: params.packageRoot }); if (activeHostState === "managed-active-host") return false; const hasManifestDependency = "openclaw" in readDependencyRecord((await readManagedNpmRootManifest(path.join(params.npmRoot, "package.json"))).dependencies); const hasLockDependency = await managedNpmRootLockfileHasOpenClawPeer(params.npmRoot); const hasPackageDir = await pathExists(path.join(params.npmRoot, "node_modules", "openclaw")); const preserveActiveHostLink = activeHostState === "linked-active-host"; if (!hasManifestDependency && !hasLockDependency && (!hasPackageDir || preserveActiveHostLink)) return false; if (preserveActiveHostLink) { await scrubManagedNpmRootOpenClawPeer({ npmRoot: params.npmRoot, preservePackageDir: true }); return true; } const command = params.runCommand ?? runCommandWithTimeout; const npmArgs = hasManifestDependency ? [ "npm", "uninstall", "--loglevel=error", "--legacy-peer-deps", "--ignore-scripts", "--no-audit", "--no-fund", "openclaw" ] : [ "npm", "prune", "--loglevel=error", "--legacy-peer-deps", "--ignore-scripts", "--no-audit", "--no-fund" ]; try { const result = await command(npmArgs, { cwd: params.npmRoot, timeoutMs: Math.max(params.timeoutMs ?? 3e5, 3e5), env: createSafeNpmInstallEnv(process.env, { legacyPeerDeps: true, npmConfigCwd: params.npmRoot, packageLock: true, quiet: true }) }); if (result.code !== 0) params.logger?.warn?.(`npm ${hasManifestDependency ? "uninstall openclaw" : "prune"} failed while repairing managed npm root; falling back to direct cleanup: ${result.stderr.trim() || result.stdout.trim()}`); } catch (error) { params.logger?.warn?.(`npm ${hasManifestDependency ? "uninstall openclaw" : "prune"} failed while repairing managed npm root; falling back to direct cleanup: ${String(error)}`); } await scrubManagedNpmRootOpenClawPeer({ npmRoot: params.npmRoot }); return true; } async function readManagedNpmRootOpenClawHostState(params) { const packageRoot = params.packageRoot === void 0 ? resolveOpenClawPackageRootSync({ argv1: process.argv[1], moduleUrl: import.meta.url, cwd: process.cwd() }) : params.packageRoot; if (!packageRoot) return "none"; const managedOpenClawPackageDir = path.join(params.npmRoot, "node_modules", "openclaw"); const [hostPackageRoot, managedPackageRoot, managedPackageStat] = await Promise.all([ realpathIfExists(packageRoot), realpathIfExists(managedOpenClawPackageDir), lstatIfExists(managedOpenClawPackageDir) ]); if (hostPackageRoot === null || hostPackageRoot !== managedPackageRoot) return "none"; return managedPackageStat?.isSymbolicLink() ? "linked-active-host" : "managed-active-host"; } async function managedNpmRootLockfileHasOpenClawPeer(npmRoot) { const lockPath = path.join(npmRoot, "package-lock.json"); try { const parsed = JSON.parse(await fs.readFile(lockPath, "utf8")); if (isRecord(parsed.packages)) { const rootPackage = parsed.packages[""]; if (isRecord(rootPackage) && isRecord(rootPackage.dependencies) && "openclaw" in rootPackage.dependencies) return true; if ("node_modules/openclaw" in parsed.packages) return true; } return isRecord(parsed.dependencies) && "openclaw" in parsed.dependencies; } catch (err) { if (err.code === "ENOENT") return false; throw err; } } async function realpathIfExists(filePath) { try { return await fs.realpath(filePath); } catch (err) { if (err.code === "ENOENT") return null; throw err; } } async function lstatIfExists(filePath) { try { return await fs.lstat(filePath); } catch (err) { if (err.code === "ENOENT") return null; throw err; } } async function pathExists(filePath) { return await fs.lstat(filePath).then(() => true).catch((err) => { if (hasErrnoCode(err, "ENOENT")) return false; throw err; }); } async function scrubManagedNpmRootOpenClawPeer(params) { const manifestPath = path.join(params.npmRoot, "package.json"); const manifest = await readManagedNpmRootManifest(manifestPath); const dependencies = readDependencyRecord(manifest.dependencies); if ("openclaw" in dependencies) { const { openclaw: _removed, ...nextDependencies } = dependencies; await fs.writeFile(manifestPath, `${JSON.stringify({ ...manifest, private: true, dependencies: nextDependencies }, null, 2)}\n`, "utf8"); } const lockPath = path.join(params.npmRoot, "package-lock.json"); try { const parsed = JSON.parse(await fs.readFile(lockPath, "utf8")); let lockChanged = false; if (isRecord(parsed.packages)) { const rootPackage = parsed.packages[""]; if (isRecord(rootPackage) && isRecord(rootPackage.dependencies)) { const dependenciesValue = { ...rootPackage.dependencies }; if ("openclaw" in dependenciesValue) { delete dependenciesValue.openclaw; parsed.packages[""] = { ...rootPackage, dependencies: dependenciesValue }; lockChanged = true; } } if ("node_modules/openclaw" in parsed.packages) { delete parsed.packages["node_modules/openclaw"]; lockChanged = true; } } if (isRecord(parsed.dependencies) && "openclaw" in parsed.dependencies) { const dependenciesLocal = { ...parsed.dependencies }; delete dependenciesLocal.openclaw; parsed.dependencies = dependenciesLocal; lockChanged = true; } if (lockChanged) await fs.writeFile(lockPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8"); } catch (err) { if (err.code !== "ENOENT") throw err; } const openclawPackageDir = path.join(params.npmRoot, "node_modules", "openclaw"); if (!params.preservePackageDir && await pathExists(openclawPackageDir)) await fs.rm(openclawPackageDir, { recursive: true, force: true }); const binDir = path.join(params.npmRoot, "node_modules", ".bin"); await Promise.all([ "openclaw", "openclaw.cmd", "openclaw.ps1" ].map((binName) => fs.rm(path.join(binDir, binName), { force: true }))); await fs.rm(path.join(params.npmRoot, "node_modules", ".package-lock.json"), { force: true }); } /** Read lockfile metadata for an installed dependency in the managed root. */ async function readManagedNpmRootInstalledDependency(params) { const parsed = await readJson(path.join(params.npmRoot, "package-lock.json")); if (!isRecord(parsed) || !isRecord(parsed.packages)) return null; const entry = parsed.packages[`node_modules/${params.packageName}`]; if (!isRecord(entry)) return null; return { version: normalizeOptionalString(entry.version), integrity: normalizeOptionalString(entry.integrity), resolved: normalizeOptionalString(entry.resolved) }; } /** Remove a dependency from the managed root manifest. */ async function removeManagedNpmRootDependency(params) { const manifestPath = path.join(params.npmRoot, "package.json"); const manifest = await readManagedNpmRootManifest(manifestPath); const dependencies = readDependencyRecord(manifest.dependencies); if (!(params.packageName in dependencies)) return; const { [params.packageName]: _removed, ...nextDependencies } = dependencies; await writeJson(manifestPath, { ...manifest, private: true, dependencies: nextDependencies }, { trailingNewline: true }); } //#endregion export { repairManagedNpmRootOpenClawPeer as a, syncManagedNpmRootPeerDependencies as c, removeManagedNpmRootDependency as i, upsertManagedNpmRootDependency as l, readManagedNpmRootPeerDependencySnapshot as n, resolveManagedNpmRootDependencySpec as o, readOpenClawManagedNpmRootOverrides as r, restoreManagedNpmRootPeerDependencySnapshot as s, readManagedNpmRootInstalledDependency as t };