
























@@ -766,6 +766,72 @@ describe("ensureBundledPluginRuntimeDeps", () => {
766766).toBe(false);
767767});
768768769+it("does not expire fresh ownerless runtime-deps install locks", () => {
770+expect(
771+bundledRuntimeDepsTesting.shouldRemoveRuntimeDepsLock(
772+{ lockDirMtimeMs: 1_000 },
773+31_000,
774+() => true,
775+),
776+).toBe(false);
777+});
778+779+it("does not expire ownerless runtime-deps install locks when the owner file changed recently", () => {
780+expect(
781+bundledRuntimeDepsTesting.shouldRemoveRuntimeDepsLock(
782+{ lockDirMtimeMs: 1_000, ownerFileMtimeMs: 31_000 },
783+61_000,
784+() => true,
785+),
786+).toBe(false);
787+});
788+789+it("expires ownerless runtime-deps install locks after the owner write grace window", () => {
790+expect(
791+bundledRuntimeDepsTesting.shouldRemoveRuntimeDepsLock(
792+{ lockDirMtimeMs: 1_000 },
793+31_001,
794+() => true,
795+),
796+).toBe(true);
797+});
798+799+it("expires ownerless runtime-deps install locks when lock and owner file are stale", () => {
800+expect(
801+bundledRuntimeDepsTesting.shouldRemoveRuntimeDepsLock(
802+{ lockDirMtimeMs: 1_000, ownerFileMtimeMs: 2_000 },
803+32_001,
804+() => true,
805+),
806+).toBe(true);
807+});
808+809+it("includes runtime-deps lock owner details in timeout messages", () => {
810+const message = bundledRuntimeDepsTesting.formatRuntimeDepsLockTimeoutMessage({
811+lockDir: "/tmp/openclaw-plugin/.openclaw-runtime-deps.lock",
812+owner: {
813+pid: 0,
814+createdAtMs: 1_000,
815+ownerFileState: "invalid",
816+ownerFilePath: "/tmp/openclaw-plugin/.openclaw-runtime-deps.lock/owner.json",
817+ownerFileMtimeMs: 2_500,
818+ownerFileIsSymlink: true,
819+lockDirMtimeMs: 2_000,
820+},
821+waitedMs: 300_123,
822+nowMs: 303_000,
823+});
824+825+expect(message).toContain("waited=300123ms");
826+expect(message).toContain("ownerFile=invalid");
827+expect(message).toContain("ownerFileSymlink=true");
828+expect(message).toContain("pid=0 alive=false");
829+expect(message).toContain("ownerAge=302000ms");
830+expect(message).toContain("ownerFileAge=300500ms");
831+expect(message).toContain("lockAge=301000ms");
832+expect(message).toContain(".openclaw-runtime-deps.lock/owner.json");
833+});
834+769835it("removes stale runtime-deps install locks before repairing deps", () => {
770836const packageRoot = makeTempDir();
771837const pluginRoot = path.join(packageRoot, "dist", "extensions", "openai");
@@ -808,6 +874,100 @@ describe("ensureBundledPluginRuntimeDeps", () => {
808874expect(fs.existsSync(lockDir)).toBe(false);
809875});
810876877+it("removes stale malformed runtime-deps install locks before repairing deps", () => {
878+const packageRoot = makeTempDir();
879+const pluginRoot = path.join(packageRoot, "dist", "extensions", "browser");
880+fs.mkdirSync(pluginRoot, { recursive: true });
881+fs.writeFileSync(
882+path.join(pluginRoot, "package.json"),
883+JSON.stringify({
884+dependencies: {
885+"browser-runtime": "1.0.0",
886+},
887+}),
888+);
889+const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginRoot, { env: {} });
890+const lockDir = path.join(installRoot, ".openclaw-runtime-deps.lock");
891+fs.mkdirSync(lockDir, { recursive: true });
892+const ownerPath = path.join(lockDir, "owner.json");
893+fs.writeFileSync(ownerPath, "{", "utf8");
894+fs.utimesSync(ownerPath, new Date(0), new Date(0));
895+fs.utimesSync(lockDir, new Date(0), new Date(0));
896+897+const calls: BundledRuntimeDepsInstallParams[] = [];
898+const result = ensureBundledPluginRuntimeDeps({
899+env: {},
900+installDeps: (params) => {
901+calls.push(params);
902+fs.mkdirSync(path.join(params.installRoot, "node_modules", "browser-runtime"), {
903+recursive: true,
904+});
905+fs.writeFileSync(
906+path.join(params.installRoot, "node_modules", "browser-runtime", "package.json"),
907+JSON.stringify({ name: "browser-runtime", version: "1.0.0" }),
908+);
909+},
910+pluginId: "browser",
911+ pluginRoot,
912+});
913+914+expect(result).toEqual({
915+installedSpecs: ["browser-runtime@1.0.0"],
916+retainSpecs: ["browser-runtime@1.0.0"],
917+});
918+expect(calls).toHaveLength(1);
919+expect(fs.existsSync(lockDir)).toBe(false);
920+});
921+922+const itSupportsSymlinks = process.platform === "win32" ? it.skip : it;
923+itSupportsSymlinks(
924+"removes stale runtime-deps install locks with broken owner symlinks before repairing deps",
925+() => {
926+const packageRoot = makeTempDir();
927+const pluginRoot = path.join(packageRoot, "dist-runtime", "extensions", "browser");
928+fs.mkdirSync(pluginRoot, { recursive: true });
929+fs.writeFileSync(
930+path.join(pluginRoot, "package.json"),
931+JSON.stringify({
932+dependencies: {
933+"browser-runtime": "1.0.0",
934+},
935+}),
936+);
937+const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginRoot, { env: {} });
938+const lockDir = path.join(installRoot, ".openclaw-runtime-deps.lock");
939+fs.mkdirSync(lockDir, { recursive: true });
940+const ownerPath = path.join(lockDir, "owner.json");
941+fs.symlinkSync("../missing-owner.json", ownerPath);
942+fs.lutimesSync(ownerPath, new Date(0), new Date(0));
943+fs.utimesSync(lockDir, new Date(0), new Date(0));
944+945+const calls: BundledRuntimeDepsInstallParams[] = [];
946+const result = ensureBundledPluginRuntimeDeps({
947+env: {},
948+installDeps: (params) => {
949+calls.push(params);
950+fs.mkdirSync(path.join(params.installRoot, "node_modules", "browser-runtime"), {
951+recursive: true,
952+});
953+fs.writeFileSync(
954+path.join(params.installRoot, "node_modules", "browser-runtime", "package.json"),
955+JSON.stringify({ name: "browser-runtime", version: "1.0.0" }),
956+);
957+},
958+pluginId: "browser",
959+ pluginRoot,
960+});
961+962+expect(result).toEqual({
963+installedSpecs: ["browser-runtime@1.0.0"],
964+retainSpecs: ["browser-runtime@1.0.0"],
965+});
966+expect(calls).toHaveLength(1);
967+expect(fs.existsSync(lockDir)).toBe(false);
968+},
969+);
970+811971it("does not install when runtime deps are only workspace links", () => {
812972const packageRoot = makeTempDir();
813973const extensionsRoot = path.join(packageRoot, "dist", "extensions");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。