refactor: enforce plugin-owned channel boundaries

This commit is contained in:
Peter Steinberger
2026-04-18 22:45:03 +01:00
parent e89e214516
commit 8bfa06e992
26 changed files with 546 additions and 388 deletions

View File

@@ -288,6 +288,13 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = create
messaging: {
defaultMarkdownTableMode: "off",
normalizeTarget: normalizeMattermostMessagingTarget,
resolveDeliveryTarget: ({ conversationId, parentConversationId }) => {
const parent = parentConversationId?.trim();
const child = conversationId.trim();
return parent && parent !== child
? { to: `channel:${parent}`, threadId: child }
: { to: normalizeMattermostMessagingTarget(`channel:${child}`) };
},
resolveOutboundSessionRoute: (params) => resolveMattermostOutboundSessionRoute(params),
targetResolver: {
looksLikeId: looksLikeMattermostTargetId,

View File

@@ -333,6 +333,13 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount, SlackProbe> = crea
},
messaging: {
normalizeTarget: normalizeSlackMessagingTarget,
resolveDeliveryTarget: ({ conversationId, parentConversationId }) => {
const parent = parentConversationId?.trim();
const child = conversationId.trim();
return parent && parent !== child
? { to: `channel:${parent}`, threadId: child }
: { to: normalizeSlackMessagingTarget(`channel:${child}`) };
},
resolveSessionTarget: ({ id }) => normalizeSlackMessagingTarget(`channel:${id}`),
parseExplicitTarget: ({ raw }) => parseSlackExplicitTarget(raw),
inferTargetChatType: ({ to }) => parseSlackExplicitTarget(to)?.chatType,

View File

@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { canonicalizeLegacySessionKey, isLegacyGroupSessionKey } from "./session-contract.js";
import {
canonicalizeLegacySessionKey,
deriveLegacySessionChatType,
isLegacyGroupSessionKey,
} from "./session-contract.js";
describe("whatsapp legacy session contract", () => {
it("canonicalizes legacy WhatsApp group keys to channel-qualified agent keys", () => {
@@ -16,6 +20,12 @@ describe("whatsapp legacy session contract", () => {
it("does not claim generic non-WhatsApp group keys", () => {
expect(isLegacyGroupSessionKey("group:abc")).toBe(false);
expect(deriveLegacySessionChatType("group:abc")).toBeUndefined();
expect(canonicalizeLegacySessionKey({ key: "group:abc", agentId: "main" })).toBeNull();
});
it("derives chat type for legacy WhatsApp group keys", () => {
expect(deriveLegacySessionChatType("123@g.us")).toBe("group");
expect(deriveLegacySessionChatType("whatsapp:123@g.us")).toBe("group");
});
});

View File

@@ -28,6 +28,10 @@ export function isLegacyGroupSessionKey(key: string): boolean {
return extractLegacyWhatsAppGroupId(key) !== null;
}
export function deriveLegacySessionChatType(key: string): "group" | undefined {
return isLegacyGroupSessionKey(key) ? "group" : undefined;
}
export function canonicalizeLegacySessionKey(params: {
key: string;
agentId: string;

View File

@@ -32,7 +32,11 @@ import {
unsupportedSecretRefSurfacePatterns,
} from "./security-contract.js";
import { applyWhatsAppSecurityConfigFixes } from "./security-fix.js";
import { canonicalizeLegacySessionKey, isLegacyGroupSessionKey } from "./session-contract.js";
import {
canonicalizeLegacySessionKey,
deriveLegacySessionChatType,
isLegacyGroupSessionKey,
} from "./session-contract.js";
export const WHATSAPP_CHANNEL = "whatsapp" as const;
@@ -245,6 +249,7 @@ export function createWhatsAppPluginBase(params: {
config: base.config!,
messaging: {
defaultMarkdownTableMode: "bullets",
deriveLegacySessionChatType,
resolveLegacyGroupSessionKey,
isLegacyGroupSessionKey,
canonicalizeLegacySessionKey: (params) =>