From 61758e3a4181055876224fbc2b8f9179aaf31474 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 21 Apr 2026 23:28:46 -0400 Subject: [PATCH] fix: cache channel doctor hook discovery --- .../doctor/shared/channel-doctor.test.ts | 20 +++++++++++++++- src/commands/doctor/shared/channel-doctor.ts | 24 ++++++++++++++----- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/commands/doctor/shared/channel-doctor.test.ts b/src/commands/doctor/shared/channel-doctor.test.ts index b8f75bbbec5..ec059c77213 100644 --- a/src/commands/doctor/shared/channel-doctor.test.ts +++ b/src/commands/doctor/shared/channel-doctor.test.ts @@ -384,6 +384,11 @@ describe("channel doctor compatibility mutations", () => { personal: {}, }, }, + slack: { + accounts: { + team: {}, + }, + }, }, }; const env = { OPENCLAW_HOME: "/tmp/openclaw-test-home" }; @@ -396,6 +401,12 @@ describe("channel doctor compatibility mutations", () => { shouldSkipDefaultEmptyGroupAllowlistWarning, }, }, + { + id: "slack", + doctor: { + collectEmptyAllowlistExtraWarnings, + }, + }, ], }); @@ -422,13 +433,20 @@ describe("channel doctor compatibility mutations", () => { prefix: "channels.matrix.accounts.personal", }), ).toEqual(["channels.matrix.accounts.personal extra"]); + expect( + hooks.extraWarningsForAccount({ + account: {}, + channelName: "slack", + prefix: "channels.slack.accounts.team", + }), + ).toEqual(["channels.slack.accounts.team extra"]); expect(mocks.resolveReadOnlyChannelPluginsForConfig).toHaveBeenCalledTimes(1); expect(mocks.resolveReadOnlyChannelPluginsForConfig).toHaveBeenCalledWith(cfg, { env, includePersistedAuthState: false, }); - expect(collectEmptyAllowlistExtraWarnings).toHaveBeenCalledTimes(2); + expect(collectEmptyAllowlistExtraWarnings).toHaveBeenCalledTimes(3); expect(shouldSkipDefaultEmptyGroupAllowlistWarning).toHaveBeenCalledTimes(1); }); }); diff --git a/src/commands/doctor/shared/channel-doctor.ts b/src/commands/doctor/shared/channel-doctor.ts index 4bdd3c5524a..75e6ff02423 100644 --- a/src/commands/doctor/shared/channel-doctor.ts +++ b/src/commands/doctor/shared/channel-doctor.ts @@ -16,6 +16,11 @@ type ChannelDoctorEntry = { doctor: ChannelDoctorAdapter; }; +type ChannelDoctorPluginCandidate = { + id: string; + doctor?: ChannelDoctorAdapter; +}; + type ChannelDoctorLookupContext = { cfg: OpenClawConfig; env?: NodeJS.ProcessEnv; @@ -113,6 +118,12 @@ function safeListReadOnlyChannelPlugins(context: ChannelDoctorLookupContext) { } } +function listReadOnlyChannelPluginsById( + context: ChannelDoctorLookupContext, +): Map { + return new Map(safeListReadOnlyChannelPlugins(context).map((plugin) => [plugin.id, plugin])); +} + function mergeDoctorAdapters( adapters: Array, ): ChannelDoctorAdapter | undefined { @@ -162,16 +173,16 @@ function isValidChannelDoctorAdapterValue( function listChannelDoctorEntries( channelIds: readonly string[], context: ChannelDoctorLookupContext, + options: { + readOnlyPluginsById?: ReadonlyMap; + } = {}, ): ChannelDoctorEntry[] { if (channelIds.length === 0) { return []; } const selectedIds = new Set(channelIds); - const readOnlyPluginsById = new Map( - safeListReadOnlyChannelPlugins(context) - .filter((plugin) => selectedIds.has(plugin.id)) - .map((plugin) => [plugin.id, plugin]), - ); + const readOnlyPluginsById = + options.readOnlyPluginsById ?? listReadOnlyChannelPluginsById(context); const entries: ChannelDoctorEntry[] = []; for (const id of selectedIds) { @@ -224,13 +235,14 @@ function shouldSkipDefaultEmptyGroupAllowlistWarningForEntries( export function createChannelDoctorEmptyAllowlistPolicyHooks( context: ChannelDoctorLookupContext, ): ChannelDoctorEmptyAllowlistPolicyHooks { + const readOnlyPluginsById = listReadOnlyChannelPluginsById(context); const entriesByChannel = new Map(); const entriesForChannel = (channelName: string) => { const existing = entriesByChannel.get(channelName); if (existing) { return existing; } - const entries = listChannelDoctorEntries([channelName], context); + const entries = listChannelDoctorEntries([channelName], context, { readOnlyPluginsById }); entriesByChannel.set(channelName, entries); return entries; };