fix(regression): use active channel registry for generic bindings

This commit is contained in:
Tak Hoffman
2026-03-27 23:08:17 -05:00
parent 5e93419c31
commit d50526dddc
2 changed files with 57 additions and 3 deletions

View File

@@ -1,5 +1,11 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { setDefaultChannelPluginRegistryForTests } from "../../commands/channel-test-helpers.js";
import { createEmptyPluginRegistry } from "../../plugins/registry-empty.js";
import {
pinActivePluginChannelRegistry,
releasePinnedPluginChannelRegistry,
setActivePluginRegistry,
} from "../../plugins/runtime.js";
import {
__testing,
getSessionBindingService,
@@ -358,6 +364,50 @@ describe("session binding service", () => {
});
});
it("does not advertise generic plugin bindings from a stale global registry when the active channel registry is empty", async () => {
const activeRegistry = createEmptyPluginRegistry();
activeRegistry.channels.push({
plugin: {
id: "external-chat",
meta: { aliases: ["external-chat-alias"] },
} as never,
} as never);
setActivePluginRegistry(activeRegistry);
const pinnedEmptyChannelRegistry = createEmptyPluginRegistry();
pinActivePluginChannelRegistry(pinnedEmptyChannelRegistry);
try {
const service = getSessionBindingService();
expect(
service.getCapabilities({
channel: "external-chat-alias",
accountId: "default",
}),
).toEqual({
adapterAvailable: false,
bindSupported: false,
unbindSupported: false,
placements: [],
});
await expect(
service.bind({
targetSessionKey: "agent:codex:acp:external-chat",
targetKind: "session",
conversation: {
channel: "external-chat-alias",
accountId: "default",
conversationId: "room-1",
},
}),
).rejects.toMatchObject({
code: "BINDING_ADAPTER_UNAVAILABLE",
});
} finally {
releasePinnedPluginChannelRegistry(pinnedEmptyChannelRegistry);
}
});
it("keeps the first live adapter authoritative until it unregisters", () => {
const firstBinding = {
bindingId: "first-binding",

View File

@@ -1,4 +1,4 @@
import { normalizeAnyChannelId, normalizeChannelId } from "../../channels/registry.js";
import { normalizeChannelId } from "../../channels/registry.js";
import { getActivePluginChannelRegistry } from "../../plugins/runtime.js";
import { normalizeAccountId } from "../../routing/session-key.js";
import { resolveGlobalMap } from "../../shared/global-singleton.js";
@@ -244,11 +244,15 @@ function supportsGenericCurrentConversationBindings(params: {
accountId: string;
}): boolean {
void params.accountId;
const normalizedChannel = params.channel.trim().toLowerCase();
return Boolean(
normalizeChannelId(params.channel) ||
normalizeAnyChannelId(params.channel) ||
getActivePluginChannelRegistry()?.channels.some(
(entry) => entry.plugin.id === params.channel.trim().toLowerCase(),
(entry) =>
entry.plugin.id.trim().toLowerCase() === normalizedChannel ||
(entry.plugin.meta?.aliases ?? []).some(
(alias) => alias.trim().toLowerCase() === normalizedChannel,
),
),
);
}