fix(doctor): defer channel plugin repair during updates

This commit is contained in:
Vincent Koc
2026-05-02 18:29:57 -07:00
parent 53bd718a1a
commit 9765a5777c
2 changed files with 114 additions and 2 deletions

View File

@@ -630,6 +630,90 @@ describe("repairMissingConfiguredPluginInstalls", () => {
});
});
it("defers channel-selected external payload repair during the package update doctor pass", async () => {
const records = {
discord: {
source: "npm",
spec: "@openclaw/discord",
installPath: "/missing/discord",
},
};
mocks.loadInstalledPluginIndexInstallRecords.mockResolvedValue(records);
mocks.listChannelPluginCatalogEntries.mockReturnValue([
{
id: "discord",
pluginId: "discord",
meta: { label: "Discord" },
install: {
npmSpec: "@openclaw/discord",
},
},
]);
const { repairMissingConfiguredPluginInstalls } =
await import("./missing-configured-plugin-install.js");
const result = await repairMissingConfiguredPluginInstalls({
cfg: {
channels: {
discord: { enabled: true, token: "secret" },
},
},
env: {
OPENCLAW_UPDATE_IN_PROGRESS: "1",
},
});
expect(mocks.updateNpmInstalledPlugins).not.toHaveBeenCalled();
expect(mocks.installPluginFromClawHub).not.toHaveBeenCalled();
expect(mocks.installPluginFromNpmSpec).not.toHaveBeenCalled();
expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith(
{},
{
env: {
OPENCLAW_UPDATE_IN_PROGRESS: "1",
},
},
);
expect(result).toEqual({
changes: [
'Deferred missing configured plugin "discord" install repair until post-update doctor.',
],
warnings: [],
});
});
it("does not install channel-selected external plugins during the package update doctor pass", async () => {
mocks.listChannelPluginCatalogEntries.mockReturnValue([
{
id: "discord",
pluginId: "discord",
meta: { label: "Discord" },
install: {
npmSpec: "@openclaw/discord",
},
},
]);
const { repairMissingConfiguredPluginInstalls } =
await import("./missing-configured-plugin-install.js");
const result = await repairMissingConfiguredPluginInstalls({
cfg: {
channels: {
discord: { enabled: true, token: "secret" },
},
},
env: {
OPENCLAW_UPDATE_IN_PROGRESS: "1",
},
});
expect(mocks.updateNpmInstalledPlugins).not.toHaveBeenCalled();
expect(mocks.installPluginFromClawHub).not.toHaveBeenCalled();
expect(mocks.installPluginFromNpmSpec).not.toHaveBeenCalled();
expect(mocks.writePersistedInstalledPluginIndexInstallRecords).not.toHaveBeenCalled();
expect(result).toEqual({ changes: [], warnings: [] });
});
it("does not install configured plugins when plugins are globally disabled", async () => {
mocks.listChannelPluginCatalogEntries.mockReturnValue([
{

View File

@@ -275,6 +275,27 @@ function collectDownloadableInstallCandidates(params: {
);
}
function collectUpdateDeferredPluginIds(params: {
cfg: OpenClawConfig;
env: NodeJS.ProcessEnv;
configuredPluginIds: ReadonlySet<string>;
configuredChannelIds: ReadonlySet<string>;
blockedPluginIds?: ReadonlySet<string>;
}): Set<string> {
const pluginIds = new Set(params.configuredPluginIds);
for (const candidate of collectDownloadableInstallCandidates({
cfg: params.cfg,
env: params.env,
missingPluginIds: new Set(),
configuredPluginIds: params.configuredPluginIds,
configuredChannelIds: params.configuredChannelIds,
blockedPluginIds: params.blockedPluginIds,
})) {
pluginIds.add(candidate.pluginId);
}
return pluginIds;
}
function collectConfiguredPluginIdsWithMissingChannelConfigDescriptors(params: {
snapshot: PluginMetadataSnapshot;
configuredPluginIds: ReadonlySet<string>;
@@ -515,7 +536,15 @@ async function repairMissingPluginInstalls(params: {
}
if (isUpdatePackageDoctorPass(env)) {
for (const pluginId of params.pluginIds) {
const updateDeferredPluginIds = collectUpdateDeferredPluginIds({
cfg: params.cfg,
env,
configuredPluginIds: params.pluginIds,
configuredChannelIds: params.channelIds,
blockedPluginIds: params.blockedPluginIds,
});
for (const pluginId of updateDeferredPluginIds) {
deferredPluginIds.add(pluginId);
const record = nextRecords[pluginId];
if (!record || !isInstalledRecordMissingOnDisk(record, env)) {
continue;
@@ -524,7 +553,6 @@ async function repairMissingPluginInstalls(params: {
nextRecords = { ...records };
}
delete nextRecords[pluginId];
deferredPluginIds.add(pluginId);
changes.push(
`Deferred missing configured plugin "${pluginId}" install repair until post-update doctor.`,
);