diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts index 7cbe2c570564..7ec962de41ae 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.test.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.test.ts @@ -1209,6 +1209,67 @@ describe("repairMissingConfiguredPluginInstalls", () => { }); }); + it("prefers npm over ClawHub during post-core repair", async () => { + const npmRoot = makeTempDir(); + const packageDir = path.join(npmRoot, "node_modules", "@openclaw", "whatsapp"); + mocks.resolveDefaultPluginNpmDir.mockReturnValue(npmRoot); + mocks.listChannelPluginCatalogEntries.mockReturnValue([ + { + id: "whatsapp", + pluginId: "whatsapp", + meta: { label: "WhatsApp" }, + install: { + clawhubSpec: "clawhub:@openclaw/whatsapp", + npmSpec: "@openclaw/whatsapp", + }, + }, + ]); + mocks.installPluginFromClawHub.mockResolvedValue({ + ok: false, + error: 'Plugin "@openclaw/whatsapp" requires plugin API >=2026.5.18.', + }); + mocks.installPluginFromNpmSpec.mockResolvedValue({ + ok: true, + pluginId: "whatsapp", + targetDir: packageDir, + version: "1.2.3", + npmResolution: { + name: "@openclaw/whatsapp", + version: "1.2.3", + resolvedSpec: "@openclaw/whatsapp@1.2.3", + integrity: "sha512-whatsapp", + resolvedAt: "2026-05-01T00:00:00.000Z", + }, + }); + + const { repairMissingConfiguredPluginInstalls } = + await import("./missing-configured-plugin-install.js"); + const result = await repairMissingConfiguredPluginInstalls({ + cfg: { + plugins: { + entries: { + whatsapp: { enabled: true }, + }, + }, + channels: { + whatsapp: { enabled: true }, + }, + }, + env: { + OPENCLAW_UPDATE_POST_CORE_CONVERGENCE: "1", + }, + }); + + expect(mocks.installPluginFromClawHub).not.toHaveBeenCalled(); + expectRecordFields(mockCallArg(mocks.installPluginFromNpmSpec), { + spec: expectedNpmInstallSpec("@openclaw/whatsapp"), + npmDir: npmRoot, + mode: "install", + }); + expect(result.warnings).toEqual([]); + expect(result.records.whatsapp?.installPath).toBe(packageDir); + }); + it("repairs missing external payload during post-core convergence even with OPENCLAW_UPDATE_IN_PROGRESS=1", async () => { const records = { discord: { diff --git a/src/commands/doctor/shared/missing-configured-plugin-install.ts b/src/commands/doctor/shared/missing-configured-plugin-install.ts index 30c547c6bb0f..ee60f21bc495 100644 --- a/src/commands/doctor/shared/missing-configured-plugin-install.ts +++ b/src/commands/doctor/shared/missing-configured-plugin-install.ts @@ -1135,7 +1135,7 @@ async function repairMissingPluginInstalls(params: { configChannel: normalizeUpdateChannel(params.cfg.update?.channel), currentVersion: VERSION, }); - const preferNpmInstalls = isLegacyPackageUpdateDoctorPass(env); + const preferNpmInstalls = isLegacyPackageUpdateDoctorPass(env) || isPostCoreConvergencePass(env); let nextRecords = records; for (const [pluginId, record] of Object.entries(records)) {