fix(doctor): honor configured plugin load paths (#113200)

This commit is contained in:
TUARAN
2026-07-28 11:06:01 +08:00
committed by GitHub
parent 8d682b74bd
commit 5723d36b46
3 changed files with 89 additions and 14 deletions
@@ -283,19 +283,15 @@ export async function detectConfiguredPluginInstallHealthIssues(params: {
) {
continue;
}
const hasRecord = Object.hasOwn(records, candidate.pluginId);
const hasUsableRecord =
Object.hasOwn(records, candidate.pluginId) &&
!isInstalledRecordMissingOnDisk(records[candidate.pluginId], env);
hasRecord && !isInstalledRecordMissingOnDisk(records[candidate.pluginId], env);
if (
!shouldReplaceBrokenOfficialInstall &&
knownIds.has(candidate.pluginId) &&
hasUsableRecord
(hasUsableRecord || (knownIds.has(candidate.pluginId) && !hasRecord))
) {
continue;
}
if (!shouldReplaceBrokenOfficialInstall && hasUsableRecord) {
continue;
}
const installSpec = resolveCandidateInstallSpec({
candidate,
updateChannel,
@@ -0,0 +1,83 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { clearCurrentPluginMetadataSnapshot } from "../../../plugins/current-plugin-metadata-state.js";
import { loadManifestMetadataSnapshot } from "../../../plugins/manifest-contract-eligibility.js";
import {
detectConfiguredPluginInstallHealthIssues,
repairMissingConfiguredPluginInstalls,
} from "./missing-configured-plugin-install.js";
const tempDirs: string[] = [];
afterEach(() => {
clearCurrentPluginMetadataSnapshot();
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
function writeProviderPlugin(rootDir: string): void {
fs.mkdirSync(path.join(rootDir, "dist"), { recursive: true });
fs.writeFileSync(path.join(rootDir, "dist", "index.js"), "export default {};\n", "utf8");
fs.writeFileSync(
path.join(rootDir, "package.json"),
JSON.stringify({
name: "@openclaw/kilocode-provider",
version: "2026.7.1",
openclaw: {
extensions: ["./index.ts"],
runtimeExtensions: ["./dist/index.js"],
},
}),
"utf8",
);
fs.writeFileSync(
path.join(rootDir, "openclaw.plugin.json"),
JSON.stringify({
id: "kilocode",
enabledByDefault: true,
providers: ["kilocode"],
configSchema: { type: "object", properties: {} },
}),
"utf8",
);
}
describe("configured plugin install health for explicit load paths", () => {
it("does not install a provider plugin already present at a configured load path", async () => {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-load-path-provider-"));
tempDirs.push(rootDir);
const pluginDir = path.join(rootDir, "kilocode-provider");
writeProviderPlugin(pluginDir);
const cfg = {
plugins: {
load: { paths: [pluginDir] },
},
};
const env = {
KILOCODE_API_KEY: "test-key",
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(rootDir, "bundled"),
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
OPENCLAW_STATE_DIR: path.join(rootDir, "state"),
VITEST: "true",
};
const snapshot = loadManifestMetadataSnapshot({ config: cfg, env });
expect(snapshot.plugins.map((plugin) => plugin.id)).toContain("kilocode");
const issues = await detectConfiguredPluginInstallHealthIssues({
cfg,
env,
});
expect(issues).toStrictEqual([]);
const repair = await repairMissingConfiguredPluginInstalls({ cfg, env });
expect(repair).toMatchObject({
changes: [],
records: {},
warnings: [],
});
});
});
@@ -378,19 +378,15 @@ async function repairMissingPluginInstalls(params: {
) {
continue;
}
const hasRecord = Object.hasOwn(nextRecords, candidate.pluginId);
const hasUsableRecord =
Object.hasOwn(nextRecords, candidate.pluginId) &&
!isInstalledRecordMissingOnDisk(nextRecords[candidate.pluginId], env);
hasRecord && !isInstalledRecordMissingOnDisk(nextRecords[candidate.pluginId], env);
if (
!shouldReplaceBrokenOfficialInstall &&
knownIds.has(candidate.pluginId) &&
hasUsableRecord
(hasUsableRecord || (knownIds.has(candidate.pluginId) && !hasRecord))
) {
continue;
}
if (!shouldReplaceBrokenOfficialInstall && hasUsableRecord) {
continue;
}
const removalPath = shouldReplaceBrokenOfficialInstall
? resolveSafeBrokenOfficialInstallRemovalPath({
pluginId: candidate.pluginId,