

























@@ -7,6 +7,9 @@ const mocks = vi.hoisted(() => ({
77listOfficialExternalPluginCatalogEntries: vi.fn(),
88loadInstalledPluginIndexInstallRecords: vi.fn(),
99loadPluginMetadataSnapshot: vi.fn(),
10+getOfficialExternalPluginCatalogManifest: vi.fn(
11+(entry: { openclaw?: unknown }) => entry.openclaw,
12+),
1013resolveOfficialExternalPluginId: vi.fn((entry: { id?: string }) => entry.id),
1114resolveOfficialExternalPluginInstall: vi.fn(
1215(entry: { install?: unknown }) => entry.install ?? null,
@@ -51,6 +54,7 @@ vi.mock("../../../plugins/plugin-metadata-snapshot.js", () => ({
5154}));
52555356vi.mock("../../../plugins/official-external-plugin-catalog.js", () => ({
57+getOfficialExternalPluginCatalogManifest: mocks.getOfficialExternalPluginCatalogManifest,
5458listOfficialExternalPluginCatalogEntries: mocks.listOfficialExternalPluginCatalogEntries,
5559resolveOfficialExternalPluginId: mocks.resolveOfficialExternalPluginId,
5660resolveOfficialExternalPluginInstall: mocks.resolveOfficialExternalPluginInstall,
@@ -522,4 +526,169 @@ describe("repairMissingConfiguredPluginInstalls", () => {
522526);
523527expect(result.changes).toEqual(['Repaired missing configured plugin "demo".']);
524528});
529+530+it("reinstalls a recorded external web search plugin from provider-only config", async () => {
531+const records = {
532+brave: {
533+source: "npm",
534+spec: "@openclaw/brave-plugin@beta",
535+installPath: "/missing/brave",
536+},
537+};
538+mocks.loadInstalledPluginIndexInstallRecords.mockResolvedValue(records);
539+mocks.listOfficialExternalPluginCatalogEntries.mockReturnValue([
540+{
541+id: "brave",
542+label: "Brave",
543+install: {
544+npmSpec: "@openclaw/brave-plugin",
545+defaultChoice: "npm",
546+},
547+openclaw: {
548+plugin: { id: "brave", label: "Brave" },
549+webSearchProviders: [
550+{
551+id: "brave",
552+label: "Brave Search",
553+hint: "Brave Search",
554+envVars: ["BRAVE_API_KEY"],
555+placeholder: "BSA...",
556+signupUrl: "https://example.test/brave",
557+},
558+],
559+},
560+},
561+]);
562+mocks.updateNpmInstalledPlugins.mockResolvedValue({
563+changed: true,
564+config: {
565+plugins: {
566+installs: {
567+brave: {
568+source: "npm",
569+spec: "@openclaw/brave-plugin@beta",
570+installPath: "/tmp/openclaw-plugins/brave",
571+},
572+},
573+},
574+},
575+outcomes: [
576+{
577+pluginId: "brave",
578+status: "updated",
579+message: "Updated brave.",
580+},
581+],
582+});
583+584+const { repairMissingConfiguredPluginInstalls } =
585+await import("./missing-configured-plugin-install.js");
586+const result = await repairMissingConfiguredPluginInstalls({
587+cfg: {
588+tools: {
589+web: {
590+search: {
591+provider: "brave",
592+},
593+},
594+},
595+},
596+env: {},
597+});
598+599+expect(mocks.updateNpmInstalledPlugins).toHaveBeenCalledWith(
600+expect.objectContaining({
601+pluginIds: ["brave"],
602+config: expect.objectContaining({
603+plugins: expect.objectContaining({ installs: records }),
604+}),
605+}),
606+);
607+expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith(
608+expect.objectContaining({
609+brave: expect.objectContaining({ installPath: "/tmp/openclaw-plugins/brave" }),
610+}),
611+{ env: {} },
612+);
613+expect(result.changes).toEqual(['Repaired missing configured plugin "brave".']);
614+});
615+616+it("installs a configured external web search plugin from provider-only config", async () => {
617+mocks.listOfficialExternalPluginCatalogEntries.mockReturnValue([
618+{
619+id: "brave",
620+label: "Brave",
621+install: {
622+npmSpec: "@openclaw/brave-plugin",
623+defaultChoice: "npm",
624+},
625+openclaw: {
626+plugin: { id: "brave", label: "Brave" },
627+webSearchProviders: [
628+{
629+id: "brave",
630+label: "Brave Search",
631+hint: "Brave Search",
632+envVars: ["BRAVE_API_KEY"],
633+placeholder: "BSA...",
634+signupUrl: "https://example.test/brave",
635+credentialPath: "plugins.entries.brave.config.webSearch.apiKey",
636+},
637+],
638+install: {
639+npmSpec: "@openclaw/brave-plugin",
640+defaultChoice: "npm",
641+},
642+},
643+},
644+]);
645+mocks.resolveOfficialExternalPluginId.mockImplementation(
646+(entry: { id?: string; openclaw?: { plugin?: { id?: string } } }) =>
647+entry.openclaw?.plugin?.id ?? entry.id,
648+);
649+mocks.resolveOfficialExternalPluginInstall.mockImplementation(
650+(entry: { install?: unknown; openclaw?: { install?: unknown } }) =>
651+entry.openclaw?.install ?? entry.install ?? null,
652+);
653+mocks.resolveOfficialExternalPluginLabel.mockImplementation(
654+(entry: { label?: string; openclaw?: { plugin?: { label?: string } } }) =>
655+entry.openclaw?.plugin?.label ?? entry.label ?? "plugin",
656+);
657+mocks.installPluginFromNpmSpec.mockResolvedValueOnce({
658+ok: true,
659+pluginId: "brave",
660+targetDir: "/tmp/openclaw-plugins/brave",
661+version: "2026.5.2",
662+npmResolution: {
663+name: "@openclaw/brave-plugin",
664+version: "2026.5.2",
665+resolvedSpec: "@openclaw/brave-plugin@2026.5.2",
666+},
667+});
668+669+const { repairMissingConfiguredPluginInstalls } =
670+await import("./missing-configured-plugin-install.js");
671+const result = await repairMissingConfiguredPluginInstalls({
672+cfg: {
673+tools: {
674+web: {
675+search: {
676+provider: "brave",
677+},
678+},
679+},
680+},
681+env: {},
682+});
683+684+expect(mocks.installPluginFromNpmSpec).toHaveBeenCalledWith(
685+expect.objectContaining({
686+spec: "@openclaw/brave-plugin",
687+expectedPluginId: "brave",
688+}),
689+);
690+expect(result.changes).toEqual([
691+'Installed missing configured plugin "brave" from @openclaw/brave-plugin.',
692+]);
693+});
525694});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。