





















@@ -1,3 +1,4 @@
1+import type { Stats } from "node:fs";
12import fs from "node:fs/promises";
23import os from "node:os";
34import path from "node:path";
@@ -52,6 +53,8 @@ type ManagedNpmRootLogger = {
52535354type ManagedNpmRootRunCommand = typeof runCommandWithTimeout;
545556+type ManagedNpmRootOpenClawHostState = "none" | "managed-active-host" | "linked-active-host";
57+5558function isRecord(value: unknown): value is Record<string, unknown> {
5659return typeof value === "object" && value !== null && !Array.isArray(value);
5760}
@@ -758,12 +761,11 @@ export async function repairManagedNpmRootOpenClawPeer(params: {
758761}): Promise<boolean> {
759762await fs.mkdir(params.npmRoot, { recursive: true });
760763761-if (
762-await managedNpmRootOpenClawPackageIsActiveHost({
763-npmRoot: params.npmRoot,
764-packageRoot: params.packageRoot,
765-})
766-) {
764+const activeHostState = await readManagedNpmRootOpenClawHostState({
765+npmRoot: params.npmRoot,
766+packageRoot: params.packageRoot,
767+});
768+if (activeHostState === "managed-active-host") {
767769return false;
768770}
769771@@ -773,10 +775,19 @@ export async function repairManagedNpmRootOpenClawPeer(params: {
773775const hasManifestDependency = "openclaw" in dependencies;
774776const hasLockDependency = await managedNpmRootLockfileHasOpenClawPeer(params.npmRoot);
775777const hasPackageDir = await pathExists(path.join(params.npmRoot, "node_modules", "openclaw"));
776-if (!hasManifestDependency && !hasLockDependency && !hasPackageDir) {
778+const preserveActiveHostLink = activeHostState === "linked-active-host";
779+if (!hasManifestDependency && !hasLockDependency && (!hasPackageDir || preserveActiveHostLink)) {
777780return false;
778781}
779782783+if (preserveActiveHostLink) {
784+await scrubManagedNpmRootOpenClawPeer({
785+npmRoot: params.npmRoot,
786+preservePackageDir: true,
787+});
788+return true;
789+}
790+780791const command = params.runCommand ?? runCommandWithTimeout;
781792const npmArgs = hasManifestDependency
782793 ? [
@@ -823,10 +834,10 @@ export async function repairManagedNpmRootOpenClawPeer(params: {
823834return true;
824835}
825836826-async function managedNpmRootOpenClawPackageIsActiveHost(params: {
837+async function readManagedNpmRootOpenClawHostState(params: {
827838npmRoot: string;
828839packageRoot?: string | null;
829-}): Promise<boolean> {
840+}): Promise<ManagedNpmRootOpenClawHostState> {
830841const packageRoot =
831842params.packageRoot === undefined
832843 ? resolveOpenClawPackageRootSync({
@@ -836,15 +847,19 @@ async function managedNpmRootOpenClawPackageIsActiveHost(params: {
836847})
837848 : params.packageRoot;
838849if (!packageRoot) {
839-return false;
850+return "none";
840851}
841852842853const managedOpenClawPackageDir = path.join(params.npmRoot, "node_modules", "openclaw");
843-const [hostPackageRoot, managedPackageRoot] = await Promise.all([
854+const [hostPackageRoot, managedPackageRoot, managedPackageStat] = await Promise.all([
844855realpathIfExists(packageRoot),
845856realpathIfExists(managedOpenClawPackageDir),
857+lstatIfExists(managedOpenClawPackageDir),
846858]);
847-return hostPackageRoot !== null && hostPackageRoot === managedPackageRoot;
859+if (hostPackageRoot === null || hostPackageRoot !== managedPackageRoot) {
860+return "none";
861+}
862+return managedPackageStat?.isSymbolicLink() ? "linked-active-host" : "managed-active-host";
848863}
849864850865async function managedNpmRootLockfileHasOpenClawPeer(npmRoot: string): Promise<boolean> {
@@ -884,6 +899,17 @@ async function realpathIfExists(filePath: string): Promise<string | null> {
884899}
885900}
886901902+async function lstatIfExists(filePath: string): Promise<Stats | null> {
903+try {
904+return await fs.lstat(filePath);
905+} catch (err) {
906+if ((err as NodeJS.ErrnoException).code === "ENOENT") {
907+return null;
908+}
909+throw err;
910+}
911+}
912+887913async function pathExists(filePath: string): Promise<boolean> {
888914return await fs
889915.lstat(filePath)
@@ -896,7 +922,10 @@ async function pathExists(filePath: string): Promise<boolean> {
896922});
897923}
898924899-async function scrubManagedNpmRootOpenClawPeer(params: { npmRoot: string }): Promise<void> {
925+async function scrubManagedNpmRootOpenClawPeer(params: {
926+npmRoot: string;
927+preservePackageDir?: boolean;
928+}): Promise<void> {
900929const manifestPath = path.join(params.npmRoot, "package.json");
901930const manifest = await readManagedNpmRootManifest(manifestPath);
902931const dependencies = readDependencyRecord(manifest.dependencies);
@@ -944,7 +973,7 @@ async function scrubManagedNpmRootOpenClawPeer(params: { npmRoot: string }): Pro
944973}
945974946975const openclawPackageDir = path.join(params.npmRoot, "node_modules", "openclaw");
947-if (await pathExists(openclawPackageDir)) {
976+if (!params.preservePackageDir && (await pathExists(openclawPackageDir))) {
948977await fs.rm(openclawPackageDir, { recursive: true, force: true });
949978}
950979const binDir = path.join(params.npmRoot, "node_modules", ".bin");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。