From 100d7b0227e62889a6dc1703b2ca79989a1c8478 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 17 Mar 2026 23:13:30 -0700 Subject: [PATCH] Doctor: add bundle plugin capability summary to workspace status --- src/commands/doctor-workspace-status.test.ts | 69 ++++++++++++++++++++ src/commands/doctor-workspace-status.ts | 8 +++ 2 files changed, 77 insertions(+) diff --git a/src/commands/doctor-workspace-status.test.ts b/src/commands/doctor-workspace-status.test.ts index 8d206ac56d7..427bc56dd99 100644 --- a/src/commands/doctor-workspace-status.test.ts +++ b/src/commands/doctor-workspace-status.test.ts @@ -98,6 +98,75 @@ describe("noteWorkspaceStatus", () => { } }); + it("surfaces bundle plugin capabilities in the plugins note", async () => { + resolveDefaultAgentIdMock.mockReturnValue("default"); + resolveAgentWorkspaceDirMock.mockReturnValue("/workspace"); + buildWorkspaceSkillStatusMock.mockReturnValue({ + skills: [], + }); + loadOpenClawPluginsMock.mockReturnValue({ + plugins: [ + { + id: "claude-bundle", + name: "Claude Bundle", + source: "/tmp/claude-bundle", + origin: "workspace", + enabled: true, + status: "loaded", + format: "bundle", + bundleFormat: "claude", + bundleCapabilities: ["skills", "commands", "agents"], + toolNames: [], + hookNames: [], + channelIds: [], + providerIds: [], + speechProviderIds: [], + mediaUnderstandingProviderIds: [], + imageGenerationProviderIds: [], + webSearchProviderIds: [], + gatewayMethods: [], + cliCommands: [], + services: [], + commands: [], + httpRoutes: 0, + hookCount: 0, + configSchema: false, + }, + ], + diagnostics: [], + channels: [], + channelSetups: [], + providers: [], + speechProviders: [], + mediaUnderstandingProviders: [], + imageGenerationProviders: [], + webSearchProviders: [], + tools: [], + hooks: [], + typedHooks: [], + httpRoutes: [], + gatewayHandlers: {}, + cliRegistrars: [], + services: [], + commands: [], + conversationBindingResolvedHandlers: [], + }); + + const noteSpy = vi.spyOn(noteModule, "note").mockImplementation(() => {}); + try { + const { noteWorkspaceStatus } = await import("./doctor-workspace-status.js"); + noteWorkspaceStatus({}); + + const pluginCalls = noteSpy.mock.calls.filter(([, title]) => title === "Plugins"); + expect(pluginCalls).toHaveLength(1); + const body = String(pluginCalls[0]?.[0]); + expect(body).toContain("Bundle plugins: 1"); + expect(body).toContain("agents, commands, skills"); + } finally { + noteSpy.mockRestore(); + } + }); + it("omits plugin compatibility note when no legacy compatibility paths are present", async () => { resolveDefaultAgentIdMock.mockReturnValue("default"); resolveAgentWorkspaceDirMock.mockReturnValue("/workspace"); diff --git a/src/commands/doctor-workspace-status.ts b/src/commands/doctor-workspace-status.ts index 5e8132c0216..f0069ab0bd5 100644 --- a/src/commands/doctor-workspace-status.ts +++ b/src/commands/doctor-workspace-status.ts @@ -53,6 +53,14 @@ export function noteWorkspaceStatus(cfg: OpenClawConfig) { : null, ].filter((line): line is string => Boolean(line)); + const bundlePlugins = loaded.filter( + (p) => p.format === "bundle" && (p.bundleCapabilities?.length ?? 0) > 0, + ); + if (bundlePlugins.length > 0) { + const allCaps = new Set(bundlePlugins.flatMap((p) => p.bundleCapabilities ?? [])); + lines.push(`Bundle plugins: ${bundlePlugins.length} (${[...allCaps].toSorted().join(", ")})`); + } + note(lines.join("\n"), "Plugins"); } const compatibilityWarnings = buildPluginCompatibilityWarnings({