Channels: add per-group tool policies

This commit is contained in:
Adam Holt
2026-01-24 15:35:05 +13:00
committed by Peter Steinberger
parent e51bf46abe
commit c07949a99c
47 changed files with 512 additions and 11 deletions

View File

@@ -11,10 +11,15 @@ import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js";
import { requireActivePluginRegistry } from "../plugins/runtime.js";
import {
resolveDiscordGroupRequireMention,
resolveDiscordGroupToolPolicy,
resolveIMessageGroupRequireMention,
resolveIMessageGroupToolPolicy,
resolveSlackGroupRequireMention,
resolveSlackGroupToolPolicy,
resolveTelegramGroupRequireMention,
resolveTelegramGroupToolPolicy,
resolveWhatsAppGroupRequireMention,
resolveWhatsAppGroupToolPolicy,
} from "./plugins/group-mentions.js";
import type {
ChannelCapabilities,
@@ -103,6 +108,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
},
groups: {
resolveRequireMention: resolveTelegramGroupRequireMention,
resolveToolPolicy: resolveTelegramGroupToolPolicy,
},
threading: {
resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first",
@@ -141,6 +147,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
},
groups: {
resolveRequireMention: resolveWhatsAppGroupRequireMention,
resolveToolPolicy: resolveWhatsAppGroupToolPolicy,
resolveGroupIntroHint: () =>
"WhatsApp IDs: SenderId is the participant JID; [message_id: ...] is the message id for reactions (use SenderId as participant).",
},
@@ -189,6 +196,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
},
groups: {
resolveRequireMention: resolveDiscordGroupRequireMention,
resolveToolPolicy: resolveDiscordGroupToolPolicy,
},
mentions: {
stripPatterns: () => ["<@!?\\d+>"],
@@ -222,6 +230,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
},
groups: {
resolveRequireMention: resolveSlackGroupRequireMention,
resolveToolPolicy: resolveSlackGroupToolPolicy,
},
threading: {
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
@@ -284,6 +293,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
},
groups: {
resolveRequireMention: resolveIMessageGroupRequireMention,
resolveToolPolicy: resolveIMessageGroupToolPolicy,
},
threading: {
buildToolContext: ({ context, hasRepliedRef }) => {

View File

@@ -1,6 +1,10 @@
import type { ClawdbotConfig } from "../../config/config.js";
import { resolveChannelGroupRequireMention } from "../../config/group-policy.js";
import {
resolveChannelGroupRequireMention,
resolveChannelGroupToolsPolicy,
} from "../../config/group-policy.js";
import type { DiscordConfig } from "../../config/types.js";
import type { GroupToolPolicyConfig } from "../../config/types.tools.js";
import { resolveSlackAccount } from "../../slack/accounts.js";
type GroupMentionParams = {
@@ -192,3 +196,103 @@ export function resolveBlueBubblesGroupRequireMention(params: GroupMentionParams
accountId: params.accountId,
});
}
export function resolveTelegramGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
const { chatId } = parseTelegramGroupId(params.groupId);
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "telegram",
groupId: chatId ?? params.groupId,
accountId: params.accountId,
});
}
export function resolveWhatsAppGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "whatsapp",
groupId: params.groupId,
accountId: params.accountId,
});
}
export function resolveIMessageGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "imessage",
groupId: params.groupId,
accountId: params.accountId,
});
}
export function resolveDiscordGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
const guildEntry = resolveDiscordGuildEntry(
params.cfg.channels?.discord?.guilds,
params.groupSpace,
);
const channelEntries = guildEntry?.channels;
if (channelEntries && Object.keys(channelEntries).length > 0) {
const groupChannel = params.groupChannel;
const channelSlug = normalizeDiscordSlug(groupChannel);
const entry =
(params.groupId ? channelEntries[params.groupId] : undefined) ??
(channelSlug
? (channelEntries[channelSlug] ?? channelEntries[`#${channelSlug}`])
: undefined) ??
(groupChannel ? channelEntries[normalizeDiscordSlug(groupChannel)] : undefined);
if (entry?.tools) return entry.tools;
}
if (guildEntry?.tools) return guildEntry.tools;
return undefined;
}
export function resolveSlackGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
const account = resolveSlackAccount({
cfg: params.cfg,
accountId: params.accountId,
});
const channels = account.channels ?? {};
const keys = Object.keys(channels);
if (keys.length === 0) return undefined;
const channelId = params.groupId?.trim();
const groupChannel = params.groupChannel;
const channelName = groupChannel?.replace(/^#/, "");
const normalizedName = normalizeSlackSlug(channelName);
const candidates = [
channelId ?? "",
channelName ? `#${channelName}` : "",
channelName ?? "",
normalizedName,
].filter(Boolean);
let matched: { tools?: GroupToolPolicyConfig } | undefined;
for (const candidate of candidates) {
if (candidate && channels[candidate]) {
matched = channels[candidate];
break;
}
}
const resolved = matched ?? channels["*"];
if (resolved?.tools) return resolved.tools;
return undefined;
}
export function resolveBlueBubblesGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "bluebubbles",
groupId: params.groupId,
accountId: params.accountId,
});
}

View File

@@ -1,4 +1,5 @@
import type { ClawdbotConfig } from "../../config/config.js";
import type { GroupToolPolicyConfig } from "../../config/types.tools.js";
import type { OutboundDeliveryResult, OutboundSendDeps } from "../../infra/outbound/deliver.js";
import type { RuntimeEnv } from "../../runtime.js";
import type {
@@ -65,6 +66,7 @@ export type ChannelConfigAdapter<ResolvedAccount> = {
export type ChannelGroupAdapter = {
resolveRequireMention?: (params: ChannelGroupContext) => boolean | undefined;
resolveGroupIntroHint?: (params: ChannelGroupContext) => string | undefined;
resolveToolPolicy?: (params: ChannelGroupContext) => GroupToolPolicyConfig | undefined;
};
export type ChannelOutboundContext = {