From 742e0c85978d2d7564efa85572b3ccf88f070cf0 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:24:43 -0500 Subject: [PATCH] fix(regression): track outbound bootstrap by channel surface --- src/infra/outbound/channel-resolution.test.ts | 27 ++++++++++++++++--- src/infra/outbound/channel-resolution.ts | 8 +++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/infra/outbound/channel-resolution.test.ts b/src/infra/outbound/channel-resolution.test.ts index 15c9dc288d4..2717622a359 100644 --- a/src/infra/outbound/channel-resolution.test.ts +++ b/src/infra/outbound/channel-resolution.test.ts @@ -6,7 +6,7 @@ const getChannelPluginMock = vi.hoisted(() => vi.fn()); const applyPluginAutoEnableMock = vi.hoisted(() => vi.fn()); const resolveRuntimePluginRegistryMock = vi.hoisted(() => vi.fn()); const getActivePluginRegistryMock = vi.hoisted(() => vi.fn()); -const getActivePluginRegistryKeyMock = vi.hoisted(() => vi.fn()); +const getActivePluginChannelRegistryVersionMock = vi.hoisted(() => vi.fn()); const normalizeMessageChannelMock = vi.hoisted(() => vi.fn()); const isDeliverableMessageChannelMock = vi.hoisted(() => vi.fn()); @@ -29,7 +29,8 @@ vi.mock("../../plugins/loader.js", () => ({ vi.mock("../../plugins/runtime.js", () => ({ getActivePluginRegistry: (...args: unknown[]) => getActivePluginRegistryMock(...args), - getActivePluginRegistryKey: (...args: unknown[]) => getActivePluginRegistryKeyMock(...args), + getActivePluginChannelRegistryVersion: (...args: unknown[]) => + getActivePluginChannelRegistryVersionMock(...args), })); vi.mock("../../utils/message-channel.js", () => ({ @@ -64,7 +65,7 @@ describe("outbound channel resolution", () => { applyPluginAutoEnableMock.mockReset(); resolveRuntimePluginRegistryMock.mockReset(); getActivePluginRegistryMock.mockReset(); - getActivePluginRegistryKeyMock.mockReset(); + getActivePluginChannelRegistryVersionMock.mockReset(); normalizeMessageChannelMock.mockReset(); isDeliverableMessageChannelMock.mockReset(); @@ -75,7 +76,7 @@ describe("outbound channel resolution", () => { ["telegram", "discord", "slack"].includes(String(value)), ); getActivePluginRegistryMock.mockReturnValue({ channels: [] }); - getActivePluginRegistryKeyMock.mockReturnValue("registry-key"); + getActivePluginChannelRegistryVersionMock.mockReturnValue(1); applyPluginAutoEnableMock.mockReturnValue({ config: { autoEnabled: true } }); resolveDefaultAgentIdMock.mockReturnValue("main"); resolveAgentWorkspaceDirMock.mockReturnValue("/tmp/workspace"); @@ -179,4 +180,22 @@ describe("outbound channel resolution", () => { }); expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(2); }); + + it("retries bootstrap when the pinned channel registry version changes", async () => { + getChannelPluginMock.mockReturnValue(undefined); + const channelResolution = await importChannelResolution("channel-version-change"); + + channelResolution.resolveOutboundChannelPlugin({ + channel: "telegram", + cfg: { channels: {} } as never, + }); + expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(1); + + getActivePluginChannelRegistryVersionMock.mockReturnValue(2); + channelResolution.resolveOutboundChannelPlugin({ + channel: "telegram", + cfg: { channels: {} } as never, + }); + expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(2); + }); }); diff --git a/src/infra/outbound/channel-resolution.ts b/src/infra/outbound/channel-resolution.ts index c98209b9f7e..77ae79cfd87 100644 --- a/src/infra/outbound/channel-resolution.ts +++ b/src/infra/outbound/channel-resolution.ts @@ -4,7 +4,10 @@ import type { ChannelPlugin } from "../../channels/plugins/types.js"; import type { OpenClawConfig } from "../../config/config.js"; import { applyPluginAutoEnable } from "../../config/plugin-auto-enable.js"; import { resolveRuntimePluginRegistry } from "../../plugins/loader.js"; -import { getActivePluginRegistry, getActivePluginRegistryKey } from "../../plugins/runtime.js"; +import { + getActivePluginRegistry, + getActivePluginChannelRegistryVersion, +} from "../../plugins/runtime.js"; import { isDeliverableMessageChannel, normalizeMessageChannel, @@ -44,8 +47,7 @@ function maybeBootstrapChannelPlugin(params: { return; } - const registryKey = getActivePluginRegistryKey() ?? ""; - const attemptKey = `${registryKey}:${params.channel}`; + const attemptKey = `${getActivePluginChannelRegistryVersion()}:${params.channel}`; if (bootstrapAttempts.has(attemptKey)) { return; }