From 47e5d448052e1b856aed4866ca9ef58c29d25c82 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 21 Apr 2026 17:42:40 -0400 Subject: [PATCH] fix(cli): prefer built doctor plugin runtimes --- CHANGELOG.md | 6 ++ src/plugins/discovery.test.ts | 2 + src/plugins/discovery.ts | 2 +- src/plugins/doctor-contract-registry.test.ts | 58 ++++++++++++-------- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b366602b819..61e8258179c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Docs: https://docs.openclaw.ai +## Unreleased + +### Changes + +- CLI/doctor plugins: lazy-load doctor plugin paths and prefer installed plugin `dist/*` runtime entries over source-adjacent JavaScript fallbacks, keeping cold doctor startup on built plugin artifacts. (#69840) Thanks @gumadeiras. + ## 2026.4.21 ### Changes diff --git a/src/plugins/discovery.test.ts b/src/plugins/discovery.test.ts index 0b42dc7fe97..06d8ca735ab 100644 --- a/src/plugins/discovery.test.ts +++ b/src/plugins/discovery.test.ts @@ -424,6 +424,8 @@ describe("discoverOpenClawPlugins", () => { }); writePluginEntry(path.join(pluginDir, "src", "index.ts")); writePluginEntry(path.join(pluginDir, "src", "setup-entry.ts")); + writePluginEntry(path.join(pluginDir, "src", "index.js")); + writePluginEntry(path.join(pluginDir, "src", "setup-entry.js")); writePluginEntry(path.join(pluginDir, "dist", "index.js")); writePluginEntry(path.join(pluginDir, "dist", "setup-entry.js")); diff --git a/src/plugins/discovery.ts b/src/plugins/discovery.ts index 10dcd3124ef..9d1895fa449 100644 --- a/src/plugins/discovery.ts +++ b/src/plugins/discovery.ts @@ -635,8 +635,8 @@ function listBuiltRuntimeEntryCandidates(entryPath: string): string[] { `${basePath}.cjs`, ]; const candidates = [ - ...withJavaScriptExtensions(withoutExtension), ...withJavaScriptExtensions(distWithoutExtension), + ...withJavaScriptExtensions(withoutExtension), ]; return [...new Set(candidates)].filter((candidate) => candidate !== normalized); } diff --git a/src/plugins/doctor-contract-registry.test.ts b/src/plugins/doctor-contract-registry.test.ts index f4dd3d362df..a7c0ec4eade 100644 --- a/src/plugins/doctor-contract-registry.test.ts +++ b/src/plugins/doctor-contract-registry.test.ts @@ -63,6 +63,7 @@ describe("doctor-contract-registry getJiti", () => { it("prefers doctor-contract-api over the broader contract-api surface", () => { const pluginRoot = makeTempDir(); + const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); fs.writeFileSync( path.join(pluginRoot, "doctor-contract-api.cjs"), "module.exports = { legacyConfigRules: [{ path: ['plugins', 'entries', 'demo', 'doctor'], message: 'doctor contract' }] };\n", @@ -78,22 +79,27 @@ describe("doctor-contract-registry getJiti", () => { diagnostics: [], }); - expect( - listPluginDoctorLegacyConfigRules({ - workspaceDir: pluginRoot, - env: {}, - }), - ).toEqual([ - { - path: ["plugins", "entries", "demo", "doctor"], - message: "doctor contract", - }, - ]); - expect(mocks.createJiti).not.toHaveBeenCalled(); + try { + expect( + listPluginDoctorLegacyConfigRules({ + workspaceDir: pluginRoot, + env: {}, + }), + ).toEqual([ + { + path: ["plugins", "entries", "demo", "doctor"], + message: "doctor contract", + }, + ]); + expect(mocks.createJiti).not.toHaveBeenCalled(); + } finally { + platformSpy.mockRestore(); + } }); it("uses native require for compatible JavaScript contract modules", () => { const pluginRoot = makeTempDir(); + const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); fs.writeFileSync( path.join(pluginRoot, "doctor-contract-api.cjs"), "module.exports = { legacyConfigRules: [{ path: ['plugins', 'entries', 'demo', 'legacy'], message: 'legacy demo key' }] };\n", @@ -104,18 +110,22 @@ describe("doctor-contract-registry getJiti", () => { diagnostics: [], }); - expect( - listPluginDoctorLegacyConfigRules({ - workspaceDir: pluginRoot, - env: {}, - }), - ).toEqual([ - { - path: ["plugins", "entries", "demo", "legacy"], - message: "legacy demo key", - }, - ]); - expect(mocks.createJiti).not.toHaveBeenCalled(); + try { + expect( + listPluginDoctorLegacyConfigRules({ + workspaceDir: pluginRoot, + env: {}, + }), + ).toEqual([ + { + path: ["plugins", "entries", "demo", "legacy"], + message: "legacy demo key", + }, + ]); + expect(mocks.createJiti).not.toHaveBeenCalled(); + } finally { + platformSpy.mockRestore(); + } }); it("narrows touched-path doctor ids for scoped dry-run validation", () => {