



















@@ -8,6 +8,7 @@ import {
88applyExclusiveSlotSelection,
99buildPluginSnapshotReport,
1010enablePluginInConfig,
11+findBundledPluginSourceMock,
1112installHooksFromNpmSpec,
1213installHooksFromPath,
1314installPluginFromClawHub,
@@ -652,7 +653,91 @@ describe("plugins cli install", () => {
652653});
653654});
654655655-it("installs bare plugin specs through npm without ClawHub lookup", async () => {
656+it("resolves exact official external plugin ids through their npm package", async () => {
657+const cfg = createEmptyPluginConfig();
658+const enabledCfg = createEnabledPluginConfig("brave");
659+loadConfig.mockReturnValue(cfg);
660+findBundledPluginSourceMock.mockReturnValue(undefined);
661+installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("brave"));
662+enablePluginInConfig.mockReturnValue({ config: enabledCfg });
663+applyExclusiveSlotSelection.mockReturnValue({
664+config: enabledCfg,
665+warnings: [],
666+});
667+668+await runPluginsCommand(["plugins", "install", "brave"]);
669+670+expect(findBundledPluginSourceMock).toHaveBeenCalledWith({
671+lookup: { kind: "pluginId", value: "brave" },
672+});
673+expect(installPluginFromClawHub).not.toHaveBeenCalled();
674+expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
675+expect.objectContaining({
676+spec: "@openclaw/brave-plugin",
677+expectedPluginId: "brave",
678+}),
679+);
680+expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith({
681+brave: expect.objectContaining({
682+source: "npm",
683+spec: "@openclaw/brave-plugin",
684+installPath: cliInstallPath("brave"),
685+version: "1.2.3",
686+}),
687+});
688+expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg);
689+});
690+691+it("passes official external catalog integrity to npm installs", async () => {
692+const cfg = createEmptyPluginConfig();
693+const enabledCfg = createEnabledPluginConfig("wecom");
694+loadConfig.mockReturnValue(cfg);
695+findBundledPluginSourceMock.mockReturnValue(undefined);
696+installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("wecom"));
697+enablePluginInConfig.mockReturnValue({ config: enabledCfg });
698+applyExclusiveSlotSelection.mockReturnValue({
699+config: enabledCfg,
700+warnings: [],
701+});
702+703+await runPluginsCommand(["plugins", "install", "wecom"]);
704+705+expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
706+expect.objectContaining({
707+spec: "@wecom/wecom-openclaw-plugin@2026.4.23",
708+expectedPluginId: "wecom",
709+expectedIntegrity:
710+"sha512-bnzfdIEEu1/LFvcdyjaTkyxt27w6c7dqhkPezU62OWaqmcdFsUGR3T55USK/O9pIKsNcnL1Tnu1pqKYCWHFgWQ==",
711+}),
712+);
713+});
714+715+it("passes official external catalog integrity to hook-pack fallback", async () => {
716+loadConfig.mockReturnValue(createEmptyPluginConfig());
717+findBundledPluginSourceMock.mockReturnValue(undefined);
718+installPluginFromNpmSpec.mockResolvedValue({
719+ok: false,
720+error: "package.json missing openclaw.extensions",
721+code: "missing_openclaw_extensions",
722+});
723+installHooksFromNpmSpec.mockResolvedValue({
724+ok: false,
725+error:
726+"aborted: npm package integrity drift detected for @wecom/wecom-openclaw-plugin@2026.4.23",
727+});
728+729+await expect(runPluginsCommand(["plugins", "install", "wecom"])).rejects.toThrow("__exit__:1");
730+731+expect(installHooksFromNpmSpec).toHaveBeenCalledWith(
732+expect.objectContaining({
733+spec: "@wecom/wecom-openclaw-plugin@2026.4.23",
734+expectedIntegrity:
735+"sha512-bnzfdIEEu1/LFvcdyjaTkyxt27w6c7dqhkPezU62OWaqmcdFsUGR3T55USK/O9pIKsNcnL1Tnu1pqKYCWHFgWQ==",
736+}),
737+);
738+});
739+740+it("installs ordinary bare plugin specs through npm without ClawHub lookup", async () => {
656741const cfg = createEmptyPluginConfig();
657742const enabledCfg = createEnabledPluginConfig("demo");
658743loadConfig.mockReturnValue(cfg);
@@ -735,6 +820,34 @@ describe("plugins cli install", () => {
735820expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg);
736821});
737822823+it("keeps npm-prefixed official plugin ids on explicit npm semantics", async () => {
824+const cfg = createEmptyPluginConfig();
825+const enabledCfg = createEnabledPluginConfig("brave");
826+827+loadConfig.mockReturnValue(cfg);
828+installPluginFromNpmSpec.mockResolvedValue(createNpmPluginInstallResult("brave"));
829+enablePluginInConfig.mockReturnValue({ config: enabledCfg });
830+recordPluginInstall.mockReturnValue(enabledCfg);
831+applyExclusiveSlotSelection.mockReturnValue({
832+config: enabledCfg,
833+warnings: [],
834+});
835+836+await runPluginsCommand(["plugins", "install", "npm:brave"]);
837+838+expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
839+expect.objectContaining({
840+spec: "brave",
841+}),
842+);
843+expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
844+expect.not.objectContaining({
845+expectedPluginId: "brave",
846+}),
847+);
848+expect(installPluginFromClawHub).not.toHaveBeenCalled();
849+});
850+738851it("passes the active profile extensions dir to npm installs", async () => {
739852const extensionsDir = useProfileExtensionsDir();
740853const cfg = createEmptyPluginConfig();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。