

























@@ -250,12 +250,24 @@ function createCodexAppServerInstallConfig(params: {
250250};
251251}
252252253-function createInstalledPackageDir(params: { name?: string; version: string }): string {
253+function createInstalledPackageDir(params: {
254+name?: string;
255+version: string;
256+peerDependencies?: Record<string, string>;
257+}): string {
254258const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-update-test-"));
255259tempDirs.push(dir);
256260fs.writeFileSync(
257261path.join(dir, "package.json"),
258-JSON.stringify({ name: params.name ?? "test-plugin", version: params.version }, null, 2),
262+JSON.stringify(
263+{
264+name: params.name ?? "test-plugin",
265+version: params.version,
266+ ...(params.peerDependencies ? { peerDependencies: params.peerDependencies } : {}),
267+},
268+null,
269+2,
270+),
259271);
260272return dir;
261273}
@@ -708,6 +720,119 @@ describe("updateNpmInstalledPlugins", () => {
708720]);
709721});
710722723+it("repairs missing openclaw peer links before skipping unchanged npm plugins", async () => {
724+const installPath = createInstalledPackageDir({
725+name: "@openclaw/codex",
726+version: "2026.5.3",
727+peerDependencies: { openclaw: ">=2026.5.3" },
728+});
729+mockNpmViewMetadata({
730+name: "@openclaw/codex",
731+version: "2026.5.3",
732+integrity: "sha512-same",
733+shasum: "same",
734+});
735+installPluginFromNpmSpecMock.mockResolvedValue(
736+createSuccessfulNpmUpdateResult({
737+pluginId: "codex",
738+targetDir: installPath,
739+version: "2026.5.3",
740+npmResolution: {
741+name: "@openclaw/codex",
742+version: "2026.5.3",
743+resolvedSpec: "@openclaw/codex@2026.5.3",
744+},
745+}),
746+);
747+const config: OpenClawConfig = {
748+plugins: {
749+installs: {
750+codex: {
751+source: "npm",
752+spec: "@openclaw/codex",
753+ installPath,
754+resolvedName: "@openclaw/codex",
755+resolvedVersion: "2026.5.3",
756+resolvedSpec: "@openclaw/codex@2026.5.3",
757+integrity: "sha512-same",
758+shasum: "same",
759+},
760+},
761+},
762+};
763+764+const result = await updateNpmInstalledPlugins({
765+ config,
766+pluginIds: ["codex"],
767+});
768+769+expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
770+expect.objectContaining({
771+spec: "@openclaw/codex",
772+mode: "update",
773+expectedPluginId: "codex",
774+}),
775+);
776+expect(result.changed).toBe(true);
777+expect(result.outcomes).toEqual([
778+{
779+pluginId: "codex",
780+status: "unchanged",
781+currentVersion: "2026.5.3",
782+nextVersion: "2026.5.3",
783+message: "codex already at 2026.5.3.",
784+},
785+]);
786+});
787+788+it("skips unchanged npm plugins when the openclaw peer link already resolves", async () => {
789+const installPath = createInstalledPackageDir({
790+name: "@openclaw/codex",
791+version: "2026.5.3",
792+peerDependencies: { openclaw: ">=2026.5.3" },
793+});
794+fs.mkdirSync(path.join(installPath, "node_modules", "openclaw"), { recursive: true });
795+mockNpmViewMetadata({
796+name: "@openclaw/codex",
797+version: "2026.5.3",
798+integrity: "sha512-same",
799+shasum: "same",
800+});
801+installPluginFromNpmSpecMock.mockRejectedValue(new Error("installer should not run"));
802+803+const result = await updateNpmInstalledPlugins({
804+config: {
805+plugins: {
806+installs: {
807+codex: {
808+source: "npm",
809+spec: "@openclaw/codex",
810+ installPath,
811+resolvedName: "@openclaw/codex",
812+resolvedVersion: "2026.5.3",
813+resolvedSpec: "@openclaw/codex@2026.5.3",
814+integrity: "sha512-same",
815+shasum: "same",
816+},
817+},
818+},
819+},
820+pluginIds: ["codex"],
821+});
822+823+expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
824+expect(result.changed).toBe(false);
825+expect(result.outcomes).toEqual([
826+{
827+pluginId: "codex",
828+status: "unchanged",
829+currentVersion: "2026.5.3",
830+nextVersion: "2026.5.3",
831+message: "codex is up to date (2026.5.3).",
832+},
833+]);
834+});
835+711836it("refreshes legacy npm install records before skipping unchanged artifacts", async () => {
712837const installPath = createInstalledPackageDir({
713838name: "@martian-engineering/lossless-claw",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。