Channels: add per-group tool policies
This commit is contained in:
committed by
Peter Steinberger
parent
e51bf46abe
commit
c07949a99c
@@ -15,6 +15,7 @@ Docs: https://docs.clawd.bot
|
|||||||
- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc.
|
- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc.
|
||||||
- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
|
- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
|
||||||
- Tlon: add Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a.
|
- Tlon: add Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a.
|
||||||
|
- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Agents: ignore IDENTITY.md template placeholders when parsing identity to avoid placeholder replies. (#1556)
|
- Agents: ignore IDENTITY.md template placeholders when parsing identity to avoid placeholder replies. (#1556)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
normalizeAccountId,
|
normalizeAccountId,
|
||||||
PAIRING_APPROVED_MESSAGE,
|
PAIRING_APPROVED_MESSAGE,
|
||||||
resolveBlueBubblesGroupRequireMention,
|
resolveBlueBubblesGroupRequireMention,
|
||||||
|
resolveBlueBubblesGroupToolPolicy,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
} from "clawdbot/plugin-sdk";
|
} from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveBlueBubblesGroupRequireMention,
|
resolveRequireMention: resolveBlueBubblesGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveBlueBubblesGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
buildToolContext: ({ context, hasRepliedRef }) => ({
|
buildToolContext: ({ context, hasRepliedRef }) => ({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MarkdownConfigSchema } from "clawdbot/plugin-sdk";
|
import { MarkdownConfigSchema, ToolPolicySchema } from "clawdbot/plugin-sdk";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const allowFromEntry = z.union([z.string(), z.number()]);
|
const allowFromEntry = z.union([z.string(), z.number()]);
|
||||||
@@ -21,6 +21,7 @@ const bluebubblesActionSchema = z
|
|||||||
|
|
||||||
const bluebubblesGroupConfigSchema = z.object({
|
const bluebubblesGroupConfigSchema = z.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const bluebubblesAccountSchema = z.object({
|
const bluebubblesAccountSchema = z.object({
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export type GroupPolicy = "open" | "disabled" | "allowlist";
|
|||||||
export type BlueBubblesGroupConfig = {
|
export type BlueBubblesGroupConfig = {
|
||||||
/** If true, only respond in this group when mentioned. */
|
/** If true, only respond in this group when mentioned. */
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Optional tool policy overrides for this group. */
|
||||||
|
tools?: { allow?: string[]; deny?: string[] };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BlueBubblesAccountConfig = {
|
export type BlueBubblesAccountConfig = {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
resolveDiscordAccount,
|
resolveDiscordAccount,
|
||||||
resolveDefaultDiscordAccountId,
|
resolveDefaultDiscordAccountId,
|
||||||
resolveDiscordGroupRequireMention,
|
resolveDiscordGroupRequireMention,
|
||||||
|
resolveDiscordGroupToolPolicy,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
type ChannelMessageActionAdapter,
|
type ChannelMessageActionAdapter,
|
||||||
type ChannelPlugin,
|
type ChannelPlugin,
|
||||||
@@ -144,6 +145,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveDiscordGroupRequireMention,
|
resolveRequireMention: resolveDiscordGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveDiscordGroupToolPolicy,
|
||||||
},
|
},
|
||||||
mentions: {
|
mentions: {
|
||||||
stripPatterns: () => ["<@!?\\d+>"],
|
stripPatterns: () => ["<@!?\\d+>"],
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
resolveDefaultIMessageAccountId,
|
resolveDefaultIMessageAccountId,
|
||||||
resolveIMessageAccount,
|
resolveIMessageAccount,
|
||||||
resolveIMessageGroupRequireMention,
|
resolveIMessageGroupRequireMention,
|
||||||
|
resolveIMessageGroupToolPolicy,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
type ChannelPlugin,
|
type ChannelPlugin,
|
||||||
type ResolvedIMessageAccount,
|
type ResolvedIMessageAccount,
|
||||||
@@ -106,6 +107,7 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveIMessageGroupRequireMention,
|
resolveRequireMention: resolveIMessageGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveIMessageGroupToolPolicy,
|
||||||
},
|
},
|
||||||
messaging: {
|
messaging: {
|
||||||
targetResolver: {
|
targetResolver: {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
|
|
||||||
import { matrixMessageActions } from "./actions.js";
|
import { matrixMessageActions } from "./actions.js";
|
||||||
import { MatrixConfigSchema } from "./config-schema.js";
|
import { MatrixConfigSchema } from "./config-schema.js";
|
||||||
import { resolveMatrixGroupRequireMention } from "./group-mentions.js";
|
import { resolveMatrixGroupRequireMention, resolveMatrixGroupToolPolicy } from "./group-mentions.js";
|
||||||
import type { CoreConfig } from "./types.js";
|
import type { CoreConfig } from "./types.js";
|
||||||
import {
|
import {
|
||||||
listMatrixAccountIds,
|
listMatrixAccountIds,
|
||||||
@@ -167,6 +167,7 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveMatrixGroupRequireMention,
|
resolveRequireMention: resolveMatrixGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveMatrixGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: ({ cfg }) =>
|
resolveReplyToMode: ({ cfg }) =>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MarkdownConfigSchema } from "clawdbot/plugin-sdk";
|
import { MarkdownConfigSchema, ToolPolicySchema } from "clawdbot/plugin-sdk";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const allowFromEntry = z.union([z.string(), z.number()]);
|
const allowFromEntry = z.union([z.string(), z.number()]);
|
||||||
@@ -26,6 +26,7 @@ const matrixRoomSchema = z
|
|||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
allow: z.boolean().optional(),
|
allow: z.boolean().optional(),
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
autoReply: z.boolean().optional(),
|
autoReply: z.boolean().optional(),
|
||||||
users: z.array(allowFromEntry).optional(),
|
users: z.array(allowFromEntry).optional(),
|
||||||
skills: z.array(z.string()).optional(),
|
skills: z.array(z.string()).optional(),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ChannelGroupContext } from "clawdbot/plugin-sdk";
|
import type { ChannelGroupContext, GroupToolPolicyConfig } from "clawdbot/plugin-sdk";
|
||||||
|
|
||||||
import { resolveMatrixRoomConfig } from "./matrix/monitor/rooms.js";
|
import { resolveMatrixRoomConfig } from "./matrix/monitor/rooms.js";
|
||||||
import type { CoreConfig } from "./types.js";
|
import type { CoreConfig } from "./types.js";
|
||||||
@@ -32,3 +32,30 @@ export function resolveMatrixGroupRequireMention(params: ChannelGroupContext): b
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveMatrixGroupToolPolicy(
|
||||||
|
params: ChannelGroupContext,
|
||||||
|
): GroupToolPolicyConfig | undefined {
|
||||||
|
const rawGroupId = params.groupId?.trim() ?? "";
|
||||||
|
let roomId = rawGroupId;
|
||||||
|
const lower = roomId.toLowerCase();
|
||||||
|
if (lower.startsWith("matrix:")) {
|
||||||
|
roomId = roomId.slice("matrix:".length).trim();
|
||||||
|
}
|
||||||
|
if (roomId.toLowerCase().startsWith("channel:")) {
|
||||||
|
roomId = roomId.slice("channel:".length).trim();
|
||||||
|
}
|
||||||
|
if (roomId.toLowerCase().startsWith("room:")) {
|
||||||
|
roomId = roomId.slice("room:".length).trim();
|
||||||
|
}
|
||||||
|
const groupChannel = params.groupChannel?.trim() ?? "";
|
||||||
|
const aliases = groupChannel ? [groupChannel] : [];
|
||||||
|
const cfg = params.cfg as CoreConfig;
|
||||||
|
const resolved = resolveMatrixRoomConfig({
|
||||||
|
rooms: cfg.channels?.matrix?.groups ?? cfg.channels?.matrix?.rooms,
|
||||||
|
roomId,
|
||||||
|
aliases,
|
||||||
|
name: groupChannel || undefined,
|
||||||
|
}).config;
|
||||||
|
return resolved?.tools;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export type MatrixRoomConfig = {
|
|||||||
allow?: boolean;
|
allow?: boolean;
|
||||||
/** Require mentioning the bot to trigger replies. */
|
/** Require mentioning the bot to trigger replies. */
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Optional tool policy overrides for this room. */
|
||||||
|
tools?: { allow?: string[]; deny?: string[] };
|
||||||
/** If true, reply without mention requirements. */
|
/** If true, reply without mention requirements. */
|
||||||
autoReply?: boolean;
|
autoReply?: boolean;
|
||||||
/** Optional allowlist for room senders (user IDs or localparts). */
|
/** Optional allowlist for room senders (user IDs or localparts). */
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { msteamsOnboardingAdapter } from "./onboarding.js";
|
import { msteamsOnboardingAdapter } from "./onboarding.js";
|
||||||
import { msteamsOutbound } from "./outbound.js";
|
import { msteamsOutbound } from "./outbound.js";
|
||||||
import { probeMSTeams } from "./probe.js";
|
import { probeMSTeams } from "./probe.js";
|
||||||
|
import { resolveMSTeamsGroupToolPolicy } from "./policy.js";
|
||||||
import {
|
import {
|
||||||
normalizeMSTeamsMessagingTarget,
|
normalizeMSTeamsMessagingTarget,
|
||||||
normalizeMSTeamsUserInput,
|
normalizeMSTeamsUserInput,
|
||||||
@@ -77,6 +78,9 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
|||||||
hasRepliedRef,
|
hasRepliedRef,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
groups: {
|
||||||
|
resolveToolPolicy: resolveMSTeamsGroupToolPolicy,
|
||||||
|
},
|
||||||
reload: { configPrefixes: ["channels.msteams"] },
|
reload: { configPrefixes: ["channels.msteams"] },
|
||||||
configSchema: buildChannelConfigSchema(MSTeamsConfigSchema),
|
configSchema: buildChannelConfigSchema(MSTeamsConfigSchema),
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type {
|
import type {
|
||||||
AllowlistMatch,
|
AllowlistMatch,
|
||||||
|
ChannelGroupContext,
|
||||||
GroupPolicy,
|
GroupPolicy,
|
||||||
|
GroupToolPolicyConfig,
|
||||||
MSTeamsChannelConfig,
|
MSTeamsChannelConfig,
|
||||||
MSTeamsConfig,
|
MSTeamsConfig,
|
||||||
MSTeamsReplyStyle,
|
MSTeamsReplyStyle,
|
||||||
@@ -86,6 +88,50 @@ export function resolveMSTeamsRouteConfig(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveMSTeamsGroupToolPolicy(
|
||||||
|
params: ChannelGroupContext,
|
||||||
|
): GroupToolPolicyConfig | undefined {
|
||||||
|
const cfg = params.cfg.channels?.msteams;
|
||||||
|
if (!cfg) return undefined;
|
||||||
|
const groupId = params.groupId?.trim();
|
||||||
|
const groupChannel = params.groupChannel?.trim();
|
||||||
|
const groupSpace = params.groupSpace?.trim();
|
||||||
|
|
||||||
|
const resolved = resolveMSTeamsRouteConfig({
|
||||||
|
cfg,
|
||||||
|
teamId: groupSpace,
|
||||||
|
teamName: groupSpace,
|
||||||
|
conversationId: groupId,
|
||||||
|
channelName: groupChannel,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resolved.channelConfig) {
|
||||||
|
return resolved.channelConfig.tools ?? resolved.teamConfig?.tools;
|
||||||
|
}
|
||||||
|
if (resolved.teamConfig?.tools) return resolved.teamConfig.tools;
|
||||||
|
|
||||||
|
if (!groupId) return undefined;
|
||||||
|
|
||||||
|
const channelCandidates = buildChannelKeyCandidates(
|
||||||
|
groupId,
|
||||||
|
groupChannel,
|
||||||
|
groupChannel ? normalizeChannelSlug(groupChannel) : undefined,
|
||||||
|
);
|
||||||
|
for (const teamConfig of Object.values(cfg.teams ?? {})) {
|
||||||
|
const match = resolveChannelEntryMatchWithFallback({
|
||||||
|
entries: teamConfig?.channels ?? {},
|
||||||
|
keys: channelCandidates,
|
||||||
|
wildcardKey: "*",
|
||||||
|
normalizeKey: normalizeChannelSlug,
|
||||||
|
});
|
||||||
|
if (match.entry) {
|
||||||
|
return match.entry.tools ?? teamConfig?.tools;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export type MSTeamsReplyPolicy = {
|
export type MSTeamsReplyPolicy = {
|
||||||
requireMention: boolean;
|
requireMention: boolean;
|
||||||
replyStyle: MSTeamsReplyStyle;
|
replyStyle: MSTeamsReplyStyle;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { nextcloudTalkOnboardingAdapter } from "./onboarding.js";
|
|||||||
import { getNextcloudTalkRuntime } from "./runtime.js";
|
import { getNextcloudTalkRuntime } from "./runtime.js";
|
||||||
import { sendMessageNextcloudTalk } from "./send.js";
|
import { sendMessageNextcloudTalk } from "./send.js";
|
||||||
import type { CoreConfig } from "./types.js";
|
import type { CoreConfig } from "./types.js";
|
||||||
|
import { resolveNextcloudTalkGroupToolPolicy } from "./policy.js";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
id: "nextcloud-talk",
|
id: "nextcloud-talk",
|
||||||
@@ -159,6 +160,7 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
resolveToolPolicy: resolveNextcloudTalkGroupToolPolicy,
|
||||||
},
|
},
|
||||||
messaging: {
|
messaging: {
|
||||||
normalizeTarget: normalizeNextcloudTalkMessagingTarget,
|
normalizeTarget: normalizeNextcloudTalkMessagingTarget,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
DmPolicySchema,
|
DmPolicySchema,
|
||||||
GroupPolicySchema,
|
GroupPolicySchema,
|
||||||
MarkdownConfigSchema,
|
MarkdownConfigSchema,
|
||||||
|
ToolPolicySchema,
|
||||||
requireOpenAllowFrom,
|
requireOpenAllowFrom,
|
||||||
} from "clawdbot/plugin-sdk";
|
} from "clawdbot/plugin-sdk";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -11,6 +12,7 @@ import { z } from "zod";
|
|||||||
export const NextcloudTalkRoomSchema = z
|
export const NextcloudTalkRoomSchema = z
|
||||||
.object({
|
.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
skills: z.array(z.string()).optional(),
|
skills: z.array(z.string()).optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
allowFrom: z.array(z.string()).optional(),
|
allowFrom: z.array(z.string()).optional(),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { AllowlistMatch, GroupPolicy } from "clawdbot/plugin-sdk";
|
import type { AllowlistMatch, ChannelGroupContext, GroupPolicy, GroupToolPolicyConfig } from "clawdbot/plugin-sdk";
|
||||||
import {
|
import {
|
||||||
buildChannelKeyCandidates,
|
buildChannelKeyCandidates,
|
||||||
normalizeChannelSlug,
|
normalizeChannelSlug,
|
||||||
@@ -86,6 +86,21 @@ export function resolveNextcloudTalkRoomMatch(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveNextcloudTalkGroupToolPolicy(
|
||||||
|
params: ChannelGroupContext,
|
||||||
|
): GroupToolPolicyConfig | undefined {
|
||||||
|
const cfg = params.cfg as { channels?: { "nextcloud-talk"?: { rooms?: Record<string, NextcloudTalkRoomConfig> } } };
|
||||||
|
const roomToken = params.groupId?.trim();
|
||||||
|
if (!roomToken) return undefined;
|
||||||
|
const roomName = params.groupChannel?.trim() || undefined;
|
||||||
|
const match = resolveNextcloudTalkRoomMatch({
|
||||||
|
rooms: cfg.channels?.["nextcloud-talk"]?.rooms,
|
||||||
|
roomToken,
|
||||||
|
roomName,
|
||||||
|
});
|
||||||
|
return match.roomConfig?.tools ?? match.wildcardConfig?.tools;
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveNextcloudTalkRequireMention(params: {
|
export function resolveNextcloudTalkRequireMention(params: {
|
||||||
roomConfig?: NextcloudTalkRoomConfig;
|
roomConfig?: NextcloudTalkRoomConfig;
|
||||||
wildcardConfig?: NextcloudTalkRoomConfig;
|
wildcardConfig?: NextcloudTalkRoomConfig;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import type {
|
|||||||
|
|
||||||
export type NextcloudTalkRoomConfig = {
|
export type NextcloudTalkRoomConfig = {
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Optional tool policy overrides for this room. */
|
||||||
|
tools?: { allow?: string[]; deny?: string[] };
|
||||||
/** If specified, only load these skills for this room. Omit = all skills; empty = no skills. */
|
/** If specified, only load these skills for this room. Omit = all skills; empty = no skills. */
|
||||||
skills?: string[];
|
skills?: string[];
|
||||||
/** If false, disable the bot for this room. */
|
/** If false, disable the bot for this room. */
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
resolveSlackAccount,
|
resolveSlackAccount,
|
||||||
resolveSlackReplyToMode,
|
resolveSlackReplyToMode,
|
||||||
resolveSlackGroupRequireMention,
|
resolveSlackGroupRequireMention,
|
||||||
|
resolveSlackGroupToolPolicy,
|
||||||
buildSlackThreadingToolContext,
|
buildSlackThreadingToolContext,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
slackOnboardingAdapter,
|
slackOnboardingAdapter,
|
||||||
@@ -161,6 +162,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveSlackGroupRequireMention,
|
resolveRequireMention: resolveSlackGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveSlackGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
|
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
resolveDefaultTelegramAccountId,
|
resolveDefaultTelegramAccountId,
|
||||||
resolveTelegramAccount,
|
resolveTelegramAccount,
|
||||||
resolveTelegramGroupRequireMention,
|
resolveTelegramGroupRequireMention,
|
||||||
|
resolveTelegramGroupToolPolicy,
|
||||||
setAccountEnabledInConfigSection,
|
setAccountEnabledInConfigSection,
|
||||||
telegramOnboardingAdapter,
|
telegramOnboardingAdapter,
|
||||||
TelegramConfigSchema,
|
TelegramConfigSchema,
|
||||||
@@ -154,6 +155,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveTelegramGroupRequireMention,
|
resolveRequireMention: resolveTelegramGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveTelegramGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first",
|
resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
resolveDefaultWhatsAppAccountId,
|
resolveDefaultWhatsAppAccountId,
|
||||||
resolveWhatsAppAccount,
|
resolveWhatsAppAccount,
|
||||||
resolveWhatsAppGroupRequireMention,
|
resolveWhatsAppGroupRequireMention,
|
||||||
|
resolveWhatsAppGroupToolPolicy,
|
||||||
resolveWhatsAppHeartbeatRecipients,
|
resolveWhatsAppHeartbeatRecipients,
|
||||||
whatsappOnboardingAdapter,
|
whatsappOnboardingAdapter,
|
||||||
WhatsAppConfigSchema,
|
WhatsAppConfigSchema,
|
||||||
@@ -198,6 +199,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveWhatsAppGroupRequireMention,
|
resolveRequireMention: resolveWhatsAppGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveWhatsAppGroupToolPolicy,
|
||||||
resolveGroupIntroHint: () =>
|
resolveGroupIntroHint: () =>
|
||||||
"WhatsApp IDs: SenderId is the participant JID; [message_id: ...] is the message id for reactions (use SenderId as participant).",
|
"WhatsApp IDs: SenderId is the participant JID; [message_id: ...] is the message id for reactions (use SenderId as participant).",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import type {
|
|||||||
ChannelAccountSnapshot,
|
ChannelAccountSnapshot,
|
||||||
ChannelDirectoryEntry,
|
ChannelDirectoryEntry,
|
||||||
ChannelDock,
|
ChannelDock,
|
||||||
|
ChannelGroupContext,
|
||||||
ChannelPlugin,
|
ChannelPlugin,
|
||||||
ClawdbotConfig,
|
ClawdbotConfig,
|
||||||
|
GroupToolPolicyConfig,
|
||||||
} from "clawdbot/plugin-sdk";
|
} from "clawdbot/plugin-sdk";
|
||||||
import {
|
import {
|
||||||
applyAccountNameToChannelSection,
|
applyAccountNameToChannelSection,
|
||||||
@@ -79,6 +81,26 @@ function mapGroup(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveZalouserGroupToolPolicy(
|
||||||
|
params: ChannelGroupContext,
|
||||||
|
): GroupToolPolicyConfig | undefined {
|
||||||
|
const account = resolveZalouserAccountSync({
|
||||||
|
cfg: params.cfg as ClawdbotConfig,
|
||||||
|
accountId: params.accountId ?? undefined,
|
||||||
|
});
|
||||||
|
const groups = account.config.groups ?? {};
|
||||||
|
const groupId = params.groupId?.trim();
|
||||||
|
const groupChannel = params.groupChannel?.trim();
|
||||||
|
const candidates = [groupId, groupChannel, "*"].filter(
|
||||||
|
(value): value is string => Boolean(value),
|
||||||
|
);
|
||||||
|
for (const key of candidates) {
|
||||||
|
const entry = groups[key];
|
||||||
|
if (entry?.tools) return entry.tools;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export const zalouserDock: ChannelDock = {
|
export const zalouserDock: ChannelDock = {
|
||||||
id: "zalouser",
|
id: "zalouser",
|
||||||
capabilities: {
|
capabilities: {
|
||||||
@@ -101,6 +123,7 @@ export const zalouserDock: ChannelDock = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: () => true,
|
resolveRequireMention: () => true,
|
||||||
|
resolveToolPolicy: resolveZalouserGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: () => "off",
|
resolveReplyToMode: () => "off",
|
||||||
@@ -188,6 +211,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: () => true,
|
resolveRequireMention: () => true,
|
||||||
|
resolveToolPolicy: resolveZalouserGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: () => "off",
|
resolveReplyToMode: () => "off",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MarkdownConfigSchema } from "clawdbot/plugin-sdk";
|
import { MarkdownConfigSchema, ToolPolicySchema } from "clawdbot/plugin-sdk";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const allowFromEntry = z.union([z.string(), z.number()]);
|
const allowFromEntry = z.union([z.string(), z.number()]);
|
||||||
@@ -6,6 +6,7 @@ const allowFromEntry = z.union([z.string(), z.number()]);
|
|||||||
const groupConfigSchema = z.object({
|
const groupConfigSchema = z.object({
|
||||||
allow: z.boolean().optional(),
|
allow: z.boolean().optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const zalouserAccountSchema = z.object({
|
const zalouserAccountSchema = z.object({
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export type ZalouserAccountConfig = {
|
|||||||
dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
|
dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
|
||||||
allowFrom?: Array<string | number>;
|
allowFrom?: Array<string | number>;
|
||||||
groupPolicy?: "open" | "allowlist" | "disabled";
|
groupPolicy?: "open" | "allowlist" | "disabled";
|
||||||
groups?: Record<string, { allow?: boolean; enabled?: boolean }>;
|
groups?: Record<string, { allow?: boolean; enabled?: boolean; tools?: { allow?: string[]; deny?: string[] } }>;
|
||||||
messagePrefix?: string;
|
messagePrefix?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export type ZalouserConfig = {
|
|||||||
dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
|
dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
|
||||||
allowFrom?: Array<string | number>;
|
allowFrom?: Array<string | number>;
|
||||||
groupPolicy?: "open" | "allowlist" | "disabled";
|
groupPolicy?: "open" | "allowlist" | "disabled";
|
||||||
groups?: Record<string, { allow?: boolean; enabled?: boolean }>;
|
groups?: Record<string, { allow?: boolean; enabled?: boolean; tools?: { allow?: string[]; deny?: string[] } }>;
|
||||||
messagePrefix?: string;
|
messagePrefix?: string;
|
||||||
accounts?: Record<string, ZalouserAccountConfig>;
|
accounts?: Record<string, ZalouserAccountConfig>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ export async function runEmbeddedPiAgent(
|
|||||||
agentAccountId: params.agentAccountId,
|
agentAccountId: params.agentAccountId,
|
||||||
messageTo: params.messageTo,
|
messageTo: params.messageTo,
|
||||||
messageThreadId: params.messageThreadId,
|
messageThreadId: params.messageThreadId,
|
||||||
|
groupId: params.groupId,
|
||||||
|
groupChannel: params.groupChannel,
|
||||||
|
groupSpace: params.groupSpace,
|
||||||
currentChannelId: params.currentChannelId,
|
currentChannelId: params.currentChannelId,
|
||||||
currentThreadTs: params.currentThreadTs,
|
currentThreadTs: params.currentThreadTs,
|
||||||
replyToMode: params.replyToMode,
|
replyToMode: params.replyToMode,
|
||||||
|
|||||||
@@ -208,6 +208,9 @@ export async function runEmbeddedAttempt(
|
|||||||
agentAccountId: params.agentAccountId,
|
agentAccountId: params.agentAccountId,
|
||||||
messageTo: params.messageTo,
|
messageTo: params.messageTo,
|
||||||
messageThreadId: params.messageThreadId,
|
messageThreadId: params.messageThreadId,
|
||||||
|
groupId: params.groupId,
|
||||||
|
groupChannel: params.groupChannel,
|
||||||
|
groupSpace: params.groupSpace,
|
||||||
sessionKey: params.sessionKey ?? params.sessionId,
|
sessionKey: params.sessionKey ?? params.sessionId,
|
||||||
agentDir,
|
agentDir,
|
||||||
workspaceDir: effectiveWorkspace,
|
workspaceDir: effectiveWorkspace,
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ export type RunEmbeddedPiAgentParams = {
|
|||||||
messageTo?: string;
|
messageTo?: string;
|
||||||
/** Thread/topic identifier for routing replies to the originating thread. */
|
/** Thread/topic identifier for routing replies to the originating thread. */
|
||||||
messageThreadId?: string | number;
|
messageThreadId?: string | number;
|
||||||
|
/** Group id for channel-level tool policy resolution. */
|
||||||
|
groupId?: string | null;
|
||||||
|
/** Group channel label (e.g. #general) for channel-level tool policy resolution. */
|
||||||
|
groupChannel?: string | null;
|
||||||
|
/** Group space label (e.g. guild/team id) for channel-level tool policy resolution. */
|
||||||
|
groupSpace?: string | null;
|
||||||
/** Current channel ID for auto-threading (Slack). */
|
/** Current channel ID for auto-threading (Slack). */
|
||||||
currentChannelId?: string;
|
currentChannelId?: string;
|
||||||
/** Current thread timestamp for auto-threading (Slack). */
|
/** Current thread timestamp for auto-threading (Slack). */
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ export type EmbeddedRunAttemptParams = {
|
|||||||
agentAccountId?: string;
|
agentAccountId?: string;
|
||||||
messageTo?: string;
|
messageTo?: string;
|
||||||
messageThreadId?: string | number;
|
messageThreadId?: string | number;
|
||||||
|
/** Group id for channel-level tool policy resolution. */
|
||||||
|
groupId?: string | null;
|
||||||
|
/** Group channel label (e.g. #general) for channel-level tool policy resolution. */
|
||||||
|
groupChannel?: string | null;
|
||||||
|
/** Group space label (e.g. guild/team id) for channel-level tool policy resolution. */
|
||||||
|
groupSpace?: string | null;
|
||||||
currentChannelId?: string;
|
currentChannelId?: string;
|
||||||
currentThreadTs?: string;
|
currentThreadTs?: string;
|
||||||
replyToMode?: "off" | "first" | "all";
|
replyToMode?: "off" | "first" | "all";
|
||||||
|
|||||||
@@ -231,6 +231,70 @@ describe("Agent-specific tool filtering", () => {
|
|||||||
expect(familyToolNames).not.toContain("apply_patch");
|
expect(familyToolNames).not.toContain("apply_patch");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should apply group tool policy overrides (group-specific beats wildcard)", () => {
|
||||||
|
const cfg: ClawdbotConfig = {
|
||||||
|
channels: {
|
||||||
|
whatsapp: {
|
||||||
|
groups: {
|
||||||
|
"*": {
|
||||||
|
tools: { allow: ["read"] },
|
||||||
|
},
|
||||||
|
trusted: {
|
||||||
|
tools: { allow: ["read", "exec"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const trustedTools = createClawdbotCodingTools({
|
||||||
|
config: cfg,
|
||||||
|
sessionKey: "agent:main:whatsapp:group:trusted",
|
||||||
|
messageProvider: "whatsapp",
|
||||||
|
workspaceDir: "/tmp/test-group-trusted",
|
||||||
|
agentDir: "/tmp/agent-group",
|
||||||
|
});
|
||||||
|
const trustedNames = trustedTools.map((t) => t.name);
|
||||||
|
expect(trustedNames).toContain("read");
|
||||||
|
expect(trustedNames).toContain("exec");
|
||||||
|
|
||||||
|
const defaultTools = createClawdbotCodingTools({
|
||||||
|
config: cfg,
|
||||||
|
sessionKey: "agent:main:whatsapp:group:unknown",
|
||||||
|
messageProvider: "whatsapp",
|
||||||
|
workspaceDir: "/tmp/test-group-default",
|
||||||
|
agentDir: "/tmp/agent-group",
|
||||||
|
});
|
||||||
|
const defaultNames = defaultTools.map((t) => t.name);
|
||||||
|
expect(defaultNames).toContain("read");
|
||||||
|
expect(defaultNames).not.toContain("exec");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve telegram group tool policy for topic session keys", () => {
|
||||||
|
const cfg: ClawdbotConfig = {
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
groups: {
|
||||||
|
"123": {
|
||||||
|
tools: { allow: ["read"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const tools = createClawdbotCodingTools({
|
||||||
|
config: cfg,
|
||||||
|
sessionKey: "agent:main:telegram:group:123:topic:456",
|
||||||
|
messageProvider: "telegram",
|
||||||
|
workspaceDir: "/tmp/test-telegram-topic",
|
||||||
|
agentDir: "/tmp/agent-telegram",
|
||||||
|
});
|
||||||
|
const names = tools.map((t) => t.name);
|
||||||
|
expect(names).toContain("read");
|
||||||
|
expect(names).not.toContain("exec");
|
||||||
|
});
|
||||||
|
|
||||||
it("should apply global tool policy before agent-specific policy", () => {
|
it("should apply global tool policy before agent-specific policy", () => {
|
||||||
const cfg: ClawdbotConfig = {
|
const cfg: ClawdbotConfig = {
|
||||||
tools: {
|
tools: {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import { getChannelDock } from "../channels/dock.js";
|
||||||
|
import { resolveChannelGroupToolsPolicy } from "../config/group-policy.js";
|
||||||
import { resolveAgentConfig, resolveAgentIdFromSessionKey } from "./agent-scope.js";
|
import { resolveAgentConfig, resolveAgentIdFromSessionKey } from "./agent-scope.js";
|
||||||
import type { AnyAgentTool } from "./pi-tools.types.js";
|
import type { AnyAgentTool } from "./pi-tools.types.js";
|
||||||
import type { SandboxToolPolicy } from "./sandbox.js";
|
import type { SandboxToolPolicy } from "./sandbox.js";
|
||||||
import { expandToolGroups, normalizeToolName } from "./tool-policy.js";
|
import { expandToolGroups, normalizeToolName } from "./tool-policy.js";
|
||||||
|
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||||
|
import { resolveThreadParentSessionKey } from "../sessions/session-key-utils.js";
|
||||||
|
|
||||||
type CompiledPattern =
|
type CompiledPattern =
|
||||||
| { kind: "all" }
|
| { kind: "all" }
|
||||||
@@ -108,6 +112,23 @@ function normalizeProviderKey(value: string): string {
|
|||||||
return value.trim().toLowerCase();
|
return value.trim().toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveGroupContextFromSessionKey(sessionKey?: string | null): {
|
||||||
|
channel?: string;
|
||||||
|
groupId?: string;
|
||||||
|
} {
|
||||||
|
const raw = (sessionKey ?? "").trim();
|
||||||
|
if (!raw) return {};
|
||||||
|
const base = resolveThreadParentSessionKey(raw) ?? raw;
|
||||||
|
const parts = base.split(":").filter(Boolean);
|
||||||
|
const body = parts[0] === "agent" ? parts.slice(2) : parts;
|
||||||
|
if (body.length < 3) return {};
|
||||||
|
const [channel, kind, ...rest] = body;
|
||||||
|
if (kind !== "group" && kind !== "channel") return {};
|
||||||
|
const groupId = rest.join(":").trim();
|
||||||
|
if (!groupId) return {};
|
||||||
|
return { channel: channel.trim().toLowerCase(), groupId };
|
||||||
|
}
|
||||||
|
|
||||||
function resolveProviderToolPolicy(params: {
|
function resolveProviderToolPolicy(params: {
|
||||||
byProvider?: Record<string, ToolPolicyConfig>;
|
byProvider?: Record<string, ToolPolicyConfig>;
|
||||||
modelProvider?: string;
|
modelProvider?: string;
|
||||||
@@ -174,6 +195,45 @@ export function resolveEffectiveToolPolicy(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveGroupToolPolicy(params: {
|
||||||
|
config?: ClawdbotConfig;
|
||||||
|
sessionKey?: string;
|
||||||
|
messageProvider?: string;
|
||||||
|
groupId?: string | null;
|
||||||
|
groupChannel?: string | null;
|
||||||
|
groupSpace?: string | null;
|
||||||
|
accountId?: string | null;
|
||||||
|
}): SandboxToolPolicy | undefined {
|
||||||
|
if (!params.config) return undefined;
|
||||||
|
const sessionContext = resolveGroupContextFromSessionKey(params.sessionKey);
|
||||||
|
const groupId = params.groupId ?? sessionContext.groupId;
|
||||||
|
if (!groupId) return undefined;
|
||||||
|
const channelRaw = params.messageProvider ?? sessionContext.channel;
|
||||||
|
const channel = normalizeMessageChannel(channelRaw);
|
||||||
|
if (!channel) return undefined;
|
||||||
|
let dock;
|
||||||
|
try {
|
||||||
|
dock = getChannelDock(channel);
|
||||||
|
} catch {
|
||||||
|
dock = undefined;
|
||||||
|
}
|
||||||
|
const toolsConfig =
|
||||||
|
dock?.groups?.resolveToolPolicy?.({
|
||||||
|
cfg: params.config,
|
||||||
|
groupId,
|
||||||
|
groupChannel: params.groupChannel,
|
||||||
|
groupSpace: params.groupSpace,
|
||||||
|
accountId: params.accountId,
|
||||||
|
}) ??
|
||||||
|
resolveChannelGroupToolsPolicy({
|
||||||
|
cfg: params.config,
|
||||||
|
channel,
|
||||||
|
groupId,
|
||||||
|
accountId: params.accountId,
|
||||||
|
});
|
||||||
|
return pickToolPolicy(toolsConfig);
|
||||||
|
}
|
||||||
|
|
||||||
export function isToolAllowedByPolicies(
|
export function isToolAllowedByPolicies(
|
||||||
name: string,
|
name: string,
|
||||||
policies: Array<SandboxToolPolicy | undefined>,
|
policies: Array<SandboxToolPolicy | undefined>,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
filterToolsByPolicy,
|
filterToolsByPolicy,
|
||||||
isToolAllowedByPolicies,
|
isToolAllowedByPolicies,
|
||||||
resolveEffectiveToolPolicy,
|
resolveEffectiveToolPolicy,
|
||||||
|
resolveGroupToolPolicy,
|
||||||
resolveSubagentToolPolicy,
|
resolveSubagentToolPolicy,
|
||||||
} from "./pi-tools.policy.js";
|
} from "./pi-tools.policy.js";
|
||||||
import {
|
import {
|
||||||
@@ -128,6 +129,12 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
currentChannelId?: string;
|
currentChannelId?: string;
|
||||||
/** Current thread timestamp for auto-threading (Slack). */
|
/** Current thread timestamp for auto-threading (Slack). */
|
||||||
currentThreadTs?: string;
|
currentThreadTs?: string;
|
||||||
|
/** Group id for channel-level tool policy resolution. */
|
||||||
|
groupId?: string | null;
|
||||||
|
/** Group channel label (e.g. #general) for channel-level tool policy resolution. */
|
||||||
|
groupChannel?: string | null;
|
||||||
|
/** Group space label (e.g. guild/team id) for channel-level tool policy resolution. */
|
||||||
|
groupSpace?: string | null;
|
||||||
/** Reply-to mode for Slack auto-threading. */
|
/** Reply-to mode for Slack auto-threading. */
|
||||||
replyToMode?: "off" | "first" | "all";
|
replyToMode?: "off" | "first" | "all";
|
||||||
/** Mutable ref to track if a reply was sent (for "first" mode). */
|
/** Mutable ref to track if a reply was sent (for "first" mode). */
|
||||||
@@ -151,6 +158,15 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
modelProvider: options?.modelProvider,
|
modelProvider: options?.modelProvider,
|
||||||
modelId: options?.modelId,
|
modelId: options?.modelId,
|
||||||
});
|
});
|
||||||
|
const groupPolicy = resolveGroupToolPolicy({
|
||||||
|
config: options?.config,
|
||||||
|
sessionKey: options?.sessionKey,
|
||||||
|
messageProvider: options?.messageProvider,
|
||||||
|
groupId: options?.groupId,
|
||||||
|
groupChannel: options?.groupChannel,
|
||||||
|
groupSpace: options?.groupSpace,
|
||||||
|
accountId: options?.agentAccountId,
|
||||||
|
});
|
||||||
const profilePolicy = resolveToolProfilePolicy(profile);
|
const profilePolicy = resolveToolProfilePolicy(profile);
|
||||||
const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
|
const providerProfilePolicy = resolveToolProfilePolicy(providerProfile);
|
||||||
const scopeKey = options?.exec?.scopeKey ?? (agentId ? `agent:${agentId}` : undefined);
|
const scopeKey = options?.exec?.scopeKey ?? (agentId ? `agent:${agentId}` : undefined);
|
||||||
@@ -165,6 +181,7 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
globalProviderPolicy,
|
globalProviderPolicy,
|
||||||
agentPolicy,
|
agentPolicy,
|
||||||
agentProviderPolicy,
|
agentProviderPolicy,
|
||||||
|
groupPolicy,
|
||||||
sandbox?.tools,
|
sandbox?.tools,
|
||||||
subagentPolicy,
|
subagentPolicy,
|
||||||
]);
|
]);
|
||||||
@@ -285,6 +302,7 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
globalProviderPolicy,
|
globalProviderPolicy,
|
||||||
agentPolicy,
|
agentPolicy,
|
||||||
agentProviderPolicy,
|
agentProviderPolicy,
|
||||||
|
groupPolicy,
|
||||||
sandbox?.tools,
|
sandbox?.tools,
|
||||||
subagentPolicy,
|
subagentPolicy,
|
||||||
]),
|
]),
|
||||||
@@ -323,6 +341,10 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
stripPluginOnlyAllowlist(agentProviderPolicy, pluginGroups),
|
stripPluginOnlyAllowlist(agentProviderPolicy, pluginGroups),
|
||||||
pluginGroups,
|
pluginGroups,
|
||||||
);
|
);
|
||||||
|
const groupPolicyExpanded = expandPolicyWithPluginGroups(
|
||||||
|
stripPluginOnlyAllowlist(groupPolicy, pluginGroups),
|
||||||
|
pluginGroups,
|
||||||
|
);
|
||||||
const sandboxPolicyExpanded = expandPolicyWithPluginGroups(sandbox?.tools, pluginGroups);
|
const sandboxPolicyExpanded = expandPolicyWithPluginGroups(sandbox?.tools, pluginGroups);
|
||||||
const subagentPolicyExpanded = expandPolicyWithPluginGroups(subagentPolicy, pluginGroups);
|
const subagentPolicyExpanded = expandPolicyWithPluginGroups(subagentPolicy, pluginGroups);
|
||||||
|
|
||||||
@@ -344,9 +366,12 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
const agentProviderFiltered = agentProviderExpanded
|
const agentProviderFiltered = agentProviderExpanded
|
||||||
? filterToolsByPolicy(agentFiltered, agentProviderExpanded)
|
? filterToolsByPolicy(agentFiltered, agentProviderExpanded)
|
||||||
: agentFiltered;
|
: agentFiltered;
|
||||||
const sandboxed = sandboxPolicyExpanded
|
const groupFiltered = groupPolicyExpanded
|
||||||
? filterToolsByPolicy(agentProviderFiltered, sandboxPolicyExpanded)
|
? filterToolsByPolicy(agentProviderFiltered, groupPolicyExpanded)
|
||||||
: agentProviderFiltered;
|
: agentProviderFiltered;
|
||||||
|
const sandboxed = sandboxPolicyExpanded
|
||||||
|
? filterToolsByPolicy(groupFiltered, sandboxPolicyExpanded)
|
||||||
|
: groupFiltered;
|
||||||
const subagentFiltered = subagentPolicyExpanded
|
const subagentFiltered = subagentPolicyExpanded
|
||||||
? filterToolsByPolicy(sandboxed, subagentPolicyExpanded)
|
? filterToolsByPolicy(sandboxed, subagentPolicyExpanded)
|
||||||
: sandboxed;
|
: sandboxed;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "../../agents/pi-embedded-helpers.js";
|
} from "../../agents/pi-embedded-helpers.js";
|
||||||
import {
|
import {
|
||||||
resolveAgentIdFromSessionKey,
|
resolveAgentIdFromSessionKey,
|
||||||
|
resolveGroupSessionKey,
|
||||||
resolveSessionTranscriptPath,
|
resolveSessionTranscriptPath,
|
||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
updateSessionStore,
|
updateSessionStore,
|
||||||
@@ -214,6 +215,10 @@ export async function runAgentTurnWithFallback(params: {
|
|||||||
agentAccountId: params.sessionCtx.AccountId,
|
agentAccountId: params.sessionCtx.AccountId,
|
||||||
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,
|
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,
|
||||||
messageThreadId: params.sessionCtx.MessageThreadId ?? undefined,
|
messageThreadId: params.sessionCtx.MessageThreadId ?? undefined,
|
||||||
|
groupId: resolveGroupSessionKey(params.sessionCtx)?.id,
|
||||||
|
groupChannel:
|
||||||
|
params.sessionCtx.GroupChannel?.trim() ?? params.sessionCtx.GroupSubject?.trim(),
|
||||||
|
groupSpace: params.sessionCtx.GroupSpace?.trim() ?? undefined,
|
||||||
// Provider threading context for tool auto-injection
|
// Provider threading context for tool auto-injection
|
||||||
...buildThreadingToolContext({
|
...buildThreadingToolContext({
|
||||||
sessionCtx: params.sessionCtx,
|
sessionCtx: params.sessionCtx,
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ export function createFollowupRunner(params: {
|
|||||||
agentAccountId: queued.run.agentAccountId,
|
agentAccountId: queued.run.agentAccountId,
|
||||||
messageTo: queued.originatingTo,
|
messageTo: queued.originatingTo,
|
||||||
messageThreadId: queued.originatingThreadId,
|
messageThreadId: queued.originatingThreadId,
|
||||||
|
groupId: queued.run.groupId,
|
||||||
|
groupChannel: queued.run.groupChannel,
|
||||||
|
groupSpace: queued.run.groupSpace,
|
||||||
sessionFile: queued.run.sessionFile,
|
sessionFile: queued.run.sessionFile,
|
||||||
workspaceDir: queued.run.workspaceDir,
|
workspaceDir: queued.run.workspaceDir,
|
||||||
config: queued.run.config,
|
config: queued.run.config,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { resolveSessionAuthProfileOverride } from "../../agents/auth-profiles/se
|
|||||||
import type { ExecToolDefaults } from "../../agents/bash-tools.js";
|
import type { ExecToolDefaults } from "../../agents/bash-tools.js";
|
||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
import {
|
import {
|
||||||
|
resolveGroupSessionKey,
|
||||||
resolveSessionFilePath,
|
resolveSessionFilePath,
|
||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
updateSessionStore,
|
updateSessionStore,
|
||||||
@@ -366,6 +367,9 @@ export async function runPreparedReply(
|
|||||||
sessionKey,
|
sessionKey,
|
||||||
messageProvider: sessionCtx.Provider?.trim().toLowerCase() || undefined,
|
messageProvider: sessionCtx.Provider?.trim().toLowerCase() || undefined,
|
||||||
agentAccountId: sessionCtx.AccountId,
|
agentAccountId: sessionCtx.AccountId,
|
||||||
|
groupId: resolveGroupSessionKey(sessionCtx)?.id ?? undefined,
|
||||||
|
groupChannel: sessionCtx.GroupChannel?.trim() ?? sessionCtx.GroupSubject?.trim(),
|
||||||
|
groupSpace: sessionCtx.GroupSpace?.trim() ?? undefined,
|
||||||
sessionFile,
|
sessionFile,
|
||||||
workspaceDir,
|
workspaceDir,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ export type FollowupRun = {
|
|||||||
sessionKey?: string;
|
sessionKey?: string;
|
||||||
messageProvider?: string;
|
messageProvider?: string;
|
||||||
agentAccountId?: string;
|
agentAccountId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
groupChannel?: string;
|
||||||
|
groupSpace?: string;
|
||||||
sessionFile: string;
|
sessionFile: string;
|
||||||
workspaceDir: string;
|
workspaceDir: string;
|
||||||
config: ClawdbotConfig;
|
config: ClawdbotConfig;
|
||||||
|
|||||||
@@ -11,10 +11,15 @@ import { normalizeWhatsAppTarget } from "../whatsapp/normalize.js";
|
|||||||
import { requireActivePluginRegistry } from "../plugins/runtime.js";
|
import { requireActivePluginRegistry } from "../plugins/runtime.js";
|
||||||
import {
|
import {
|
||||||
resolveDiscordGroupRequireMention,
|
resolveDiscordGroupRequireMention,
|
||||||
|
resolveDiscordGroupToolPolicy,
|
||||||
resolveIMessageGroupRequireMention,
|
resolveIMessageGroupRequireMention,
|
||||||
|
resolveIMessageGroupToolPolicy,
|
||||||
resolveSlackGroupRequireMention,
|
resolveSlackGroupRequireMention,
|
||||||
|
resolveSlackGroupToolPolicy,
|
||||||
resolveTelegramGroupRequireMention,
|
resolveTelegramGroupRequireMention,
|
||||||
|
resolveTelegramGroupToolPolicy,
|
||||||
resolveWhatsAppGroupRequireMention,
|
resolveWhatsAppGroupRequireMention,
|
||||||
|
resolveWhatsAppGroupToolPolicy,
|
||||||
} from "./plugins/group-mentions.js";
|
} from "./plugins/group-mentions.js";
|
||||||
import type {
|
import type {
|
||||||
ChannelCapabilities,
|
ChannelCapabilities,
|
||||||
@@ -103,6 +108,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveTelegramGroupRequireMention,
|
resolveRequireMention: resolveTelegramGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveTelegramGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first",
|
resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first",
|
||||||
@@ -141,6 +147,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveWhatsAppGroupRequireMention,
|
resolveRequireMention: resolveWhatsAppGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveWhatsAppGroupToolPolicy,
|
||||||
resolveGroupIntroHint: () =>
|
resolveGroupIntroHint: () =>
|
||||||
"WhatsApp IDs: SenderId is the participant JID; [message_id: ...] is the message id for reactions (use SenderId as participant).",
|
"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: {
|
groups: {
|
||||||
resolveRequireMention: resolveDiscordGroupRequireMention,
|
resolveRequireMention: resolveDiscordGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveDiscordGroupToolPolicy,
|
||||||
},
|
},
|
||||||
mentions: {
|
mentions: {
|
||||||
stripPatterns: () => ["<@!?\\d+>"],
|
stripPatterns: () => ["<@!?\\d+>"],
|
||||||
@@ -222,6 +230,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveSlackGroupRequireMention,
|
resolveRequireMention: resolveSlackGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveSlackGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
|
resolveReplyToMode: ({ cfg, accountId, chatType }) =>
|
||||||
@@ -284,6 +293,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
|||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
resolveRequireMention: resolveIMessageGroupRequireMention,
|
resolveRequireMention: resolveIMessageGroupRequireMention,
|
||||||
|
resolveToolPolicy: resolveIMessageGroupToolPolicy,
|
||||||
},
|
},
|
||||||
threading: {
|
threading: {
|
||||||
buildToolContext: ({ context, hasRepliedRef }) => {
|
buildToolContext: ({ context, hasRepliedRef }) => {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
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 { DiscordConfig } from "../../config/types.js";
|
||||||
|
import type { GroupToolPolicyConfig } from "../../config/types.tools.js";
|
||||||
import { resolveSlackAccount } from "../../slack/accounts.js";
|
import { resolveSlackAccount } from "../../slack/accounts.js";
|
||||||
|
|
||||||
type GroupMentionParams = {
|
type GroupMentionParams = {
|
||||||
@@ -192,3 +196,103 @@ export function resolveBlueBubblesGroupRequireMention(params: GroupMentionParams
|
|||||||
accountId: params.accountId,
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
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 { OutboundDeliveryResult, OutboundSendDeps } from "../../infra/outbound/deliver.js";
|
||||||
import type { RuntimeEnv } from "../../runtime.js";
|
import type { RuntimeEnv } from "../../runtime.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -65,6 +66,7 @@ export type ChannelConfigAdapter<ResolvedAccount> = {
|
|||||||
export type ChannelGroupAdapter = {
|
export type ChannelGroupAdapter = {
|
||||||
resolveRequireMention?: (params: ChannelGroupContext) => boolean | undefined;
|
resolveRequireMention?: (params: ChannelGroupContext) => boolean | undefined;
|
||||||
resolveGroupIntroHint?: (params: ChannelGroupContext) => string | undefined;
|
resolveGroupIntroHint?: (params: ChannelGroupContext) => string | undefined;
|
||||||
|
resolveToolPolicy?: (params: ChannelGroupContext) => GroupToolPolicyConfig | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChannelOutboundContext = {
|
export type ChannelOutboundContext = {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import type { ChannelId } from "../channels/plugins/types.js";
|
import type { ChannelId } from "../channels/plugins/types.js";
|
||||||
import { normalizeAccountId } from "../routing/session-key.js";
|
import { normalizeAccountId } from "../routing/session-key.js";
|
||||||
import type { ClawdbotConfig } from "./config.js";
|
import type { ClawdbotConfig } from "./config.js";
|
||||||
|
import type { GroupToolPolicyConfig } from "./types.tools.js";
|
||||||
|
|
||||||
export type GroupPolicyChannel = ChannelId;
|
export type GroupPolicyChannel = ChannelId;
|
||||||
|
|
||||||
export type ChannelGroupConfig = {
|
export type ChannelGroupConfig = {
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChannelGroupPolicy = {
|
export type ChannelGroupPolicy = {
|
||||||
@@ -91,3 +93,15 @@ export function resolveChannelGroupRequireMention(params: {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveChannelGroupToolsPolicy(params: {
|
||||||
|
cfg: ClawdbotConfig;
|
||||||
|
channel: GroupPolicyChannel;
|
||||||
|
groupId?: string | null;
|
||||||
|
accountId?: string | null;
|
||||||
|
}): GroupToolPolicyConfig | undefined {
|
||||||
|
const { groupConfig, defaultConfig } = resolveChannelGroupPolicy(params);
|
||||||
|
if (groupConfig?.tools) return groupConfig.tools;
|
||||||
|
if (defaultConfig?.tools) return defaultConfig.tools;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
ReplyToMode,
|
ReplyToMode,
|
||||||
} from "./types.base.js";
|
} from "./types.base.js";
|
||||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||||
|
import type { GroupToolPolicyConfig } from "./types.tools.js";
|
||||||
|
|
||||||
export type DiscordDmConfig = {
|
export type DiscordDmConfig = {
|
||||||
/** If false, ignore all incoming Discord DMs. Default: true. */
|
/** If false, ignore all incoming Discord DMs. Default: true. */
|
||||||
@@ -24,6 +25,8 @@ export type DiscordDmConfig = {
|
|||||||
export type DiscordGuildChannelConfig = {
|
export type DiscordGuildChannelConfig = {
|
||||||
allow?: boolean;
|
allow?: boolean;
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Optional tool policy overrides for this channel. */
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
/** If specified, only load these skills for this channel. Omit = all skills; empty = no skills. */
|
/** If specified, only load these skills for this channel. Omit = all skills; empty = no skills. */
|
||||||
skills?: string[];
|
skills?: string[];
|
||||||
/** If false, disable the bot for this channel. */
|
/** If false, disable the bot for this channel. */
|
||||||
@@ -39,6 +42,8 @@ export type DiscordReactionNotificationMode = "off" | "own" | "all" | "allowlist
|
|||||||
export type DiscordGuildEntry = {
|
export type DiscordGuildEntry = {
|
||||||
slug?: string;
|
slug?: string;
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Optional tool policy overrides for this guild (used when channel override is missing). */
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
/** Reaction notification mode (off|own|all|allowlist). Default: own. */
|
/** Reaction notification mode (off|own|all|allowlist). Default: own. */
|
||||||
reactionNotifications?: DiscordReactionNotificationMode;
|
reactionNotifications?: DiscordReactionNotificationMode;
|
||||||
users?: Array<string | number>;
|
users?: Array<string | number>;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
MarkdownConfig,
|
MarkdownConfig,
|
||||||
} from "./types.base.js";
|
} from "./types.base.js";
|
||||||
import type { DmConfig } from "./types.messages.js";
|
import type { DmConfig } from "./types.messages.js";
|
||||||
|
import type { GroupToolPolicyConfig } from "./types.tools.js";
|
||||||
|
|
||||||
export type IMessageAccountConfig = {
|
export type IMessageAccountConfig = {
|
||||||
/** Optional display name for this account (used in CLI/UI lists). */
|
/** Optional display name for this account (used in CLI/UI lists). */
|
||||||
@@ -59,6 +60,7 @@ export type IMessageAccountConfig = {
|
|||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
MarkdownConfig,
|
MarkdownConfig,
|
||||||
} from "./types.base.js";
|
} from "./types.base.js";
|
||||||
import type { DmConfig } from "./types.messages.js";
|
import type { DmConfig } from "./types.messages.js";
|
||||||
|
import type { GroupToolPolicyConfig } from "./types.tools.js";
|
||||||
|
|
||||||
export type MSTeamsWebhookConfig = {
|
export type MSTeamsWebhookConfig = {
|
||||||
/** Port for the webhook server. Default: 3978. */
|
/** Port for the webhook server. Default: 3978. */
|
||||||
@@ -20,6 +21,8 @@ export type MSTeamsReplyStyle = "thread" | "top-level";
|
|||||||
export type MSTeamsChannelConfig = {
|
export type MSTeamsChannelConfig = {
|
||||||
/** Require @mention to respond. Default: true. */
|
/** Require @mention to respond. Default: true. */
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Optional tool policy overrides for this channel. */
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
/** Reply style: "thread" replies to the message, "top-level" posts a new message. */
|
/** Reply style: "thread" replies to the message, "top-level" posts a new message. */
|
||||||
replyStyle?: MSTeamsReplyStyle;
|
replyStyle?: MSTeamsReplyStyle;
|
||||||
};
|
};
|
||||||
@@ -28,6 +31,8 @@ export type MSTeamsChannelConfig = {
|
|||||||
export type MSTeamsTeamConfig = {
|
export type MSTeamsTeamConfig = {
|
||||||
/** Default requireMention for channels in this team. */
|
/** Default requireMention for channels in this team. */
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Default tool policy for channels in this team. */
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
/** Default reply style for channels in this team. */
|
/** Default reply style for channels in this team. */
|
||||||
replyStyle?: MSTeamsReplyStyle;
|
replyStyle?: MSTeamsReplyStyle;
|
||||||
/** Per-channel overrides. Key is conversation ID (e.g., "19:...@thread.tacv2"). */
|
/** Per-channel overrides. Key is conversation ID (e.g., "19:...@thread.tacv2"). */
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
ReplyToMode,
|
ReplyToMode,
|
||||||
} from "./types.base.js";
|
} from "./types.base.js";
|
||||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||||
|
import type { GroupToolPolicyConfig } from "./types.tools.js";
|
||||||
|
|
||||||
export type SlackDmConfig = {
|
export type SlackDmConfig = {
|
||||||
/** If false, ignore all incoming Slack DMs. Default: true. */
|
/** If false, ignore all incoming Slack DMs. Default: true. */
|
||||||
@@ -29,6 +30,8 @@ export type SlackChannelConfig = {
|
|||||||
allow?: boolean;
|
allow?: boolean;
|
||||||
/** Require mentioning the bot to trigger replies. */
|
/** Require mentioning the bot to trigger replies. */
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Optional tool policy overrides for this channel. */
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
/** Allow bot-authored messages to trigger replies (default: false). */
|
/** Allow bot-authored messages to trigger replies (default: false). */
|
||||||
allowBots?: boolean;
|
allowBots?: boolean;
|
||||||
/** Allowlist of users that can invoke the bot in this channel. */
|
/** Allowlist of users that can invoke the bot in this channel. */
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
ReplyToMode,
|
ReplyToMode,
|
||||||
} from "./types.base.js";
|
} from "./types.base.js";
|
||||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||||
|
import type { GroupToolPolicyConfig } from "./types.tools.js";
|
||||||
|
|
||||||
export type TelegramActionConfig = {
|
export type TelegramActionConfig = {
|
||||||
reactions?: boolean;
|
reactions?: boolean;
|
||||||
@@ -128,6 +129,8 @@ export type TelegramTopicConfig = {
|
|||||||
|
|
||||||
export type TelegramGroupConfig = {
|
export type TelegramGroupConfig = {
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
/** Optional tool policy overrides for this group. */
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
/** If specified, only load these skills for this group (when no topic). Omit = all skills; empty = no skills. */
|
/** If specified, only load these skills for this group (when no topic). Omit = all skills; empty = no skills. */
|
||||||
skills?: string[];
|
skills?: string[];
|
||||||
/** Per-topic configuration (key is message_thread_id as string) */
|
/** Per-topic configuration (key is message_thread_id as string) */
|
||||||
|
|||||||
@@ -120,6 +120,11 @@ export type ToolPolicyConfig = {
|
|||||||
profile?: ToolProfileId;
|
profile?: ToolProfileId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GroupToolPolicyConfig = {
|
||||||
|
allow?: string[];
|
||||||
|
deny?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ExecToolConfig = {
|
export type ExecToolConfig = {
|
||||||
/** Exec host routing (default: sandbox). */
|
/** Exec host routing (default: sandbox). */
|
||||||
host?: "sandbox" | "gateway" | "node";
|
host?: "sandbox" | "gateway" | "node";
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
MarkdownConfig,
|
MarkdownConfig,
|
||||||
} from "./types.base.js";
|
} from "./types.base.js";
|
||||||
import type { DmConfig } from "./types.messages.js";
|
import type { DmConfig } from "./types.messages.js";
|
||||||
|
import type { GroupToolPolicyConfig } from "./types.tools.js";
|
||||||
|
|
||||||
export type WhatsAppActionConfig = {
|
export type WhatsAppActionConfig = {
|
||||||
reactions?: boolean;
|
reactions?: boolean;
|
||||||
@@ -65,6 +66,7 @@ export type WhatsAppConfig = {
|
|||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
/** Acknowledgment reaction sent immediately upon message receipt. */
|
/** Acknowledgment reaction sent immediately upon message receipt. */
|
||||||
@@ -125,6 +127,7 @@ export type WhatsAppAccountConfig = {
|
|||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
requireMention?: boolean;
|
requireMention?: boolean;
|
||||||
|
tools?: GroupToolPolicyConfig;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
/** Acknowledgment reaction sent immediately upon message receipt. */
|
/** Acknowledgment reaction sent immediately upon message receipt. */
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
RetryConfigSchema,
|
RetryConfigSchema,
|
||||||
requireOpenAllowFrom,
|
requireOpenAllowFrom,
|
||||||
} from "./zod-schema.core.js";
|
} from "./zod-schema.core.js";
|
||||||
|
import { ToolPolicySchema } from "./zod-schema.agent-runtime.js";
|
||||||
import {
|
import {
|
||||||
normalizeTelegramCommandDescription,
|
normalizeTelegramCommandDescription,
|
||||||
normalizeTelegramCommandName,
|
normalizeTelegramCommandName,
|
||||||
@@ -44,6 +45,7 @@ export const TelegramTopicSchema = z
|
|||||||
export const TelegramGroupSchema = z
|
export const TelegramGroupSchema = z
|
||||||
.object({
|
.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
skills: z.array(z.string()).optional(),
|
skills: z.array(z.string()).optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
@@ -173,6 +175,7 @@ export const DiscordGuildChannelSchema = z
|
|||||||
.object({
|
.object({
|
||||||
allow: z.boolean().optional(),
|
allow: z.boolean().optional(),
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
skills: z.array(z.string()).optional(),
|
skills: z.array(z.string()).optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
users: z.array(z.union([z.string(), z.number()])).optional(),
|
users: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
@@ -185,6 +188,7 @@ export const DiscordGuildSchema = z
|
|||||||
.object({
|
.object({
|
||||||
slug: z.string().optional(),
|
slug: z.string().optional(),
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(),
|
reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(),
|
||||||
users: z.array(z.union([z.string(), z.number()])).optional(),
|
users: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
channels: z.record(z.string(), DiscordGuildChannelSchema.optional()).optional(),
|
channels: z.record(z.string(), DiscordGuildChannelSchema.optional()).optional(),
|
||||||
@@ -270,6 +274,7 @@ export const SlackChannelSchema = z
|
|||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
allow: z.boolean().optional(),
|
allow: z.boolean().optional(),
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
allowBots: z.boolean().optional(),
|
allowBots: z.boolean().optional(),
|
||||||
users: z.array(z.union([z.string(), z.number()])).optional(),
|
users: z.array(z.union([z.string(), z.number()])).optional(),
|
||||||
skills: z.array(z.string()).optional(),
|
skills: z.array(z.string()).optional(),
|
||||||
@@ -466,6 +471,7 @@ export const IMessageAccountSchemaBase = z
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -520,6 +526,7 @@ const BlueBubblesActionSchema = z
|
|||||||
const BlueBubblesGroupConfigSchema = z
|
const BlueBubblesGroupConfigSchema = z
|
||||||
.object({
|
.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
@@ -576,6 +583,7 @@ export const BlueBubblesConfigSchema = BlueBubblesAccountSchemaBase.extend({
|
|||||||
export const MSTeamsChannelSchema = z
|
export const MSTeamsChannelSchema = z
|
||||||
.object({
|
.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
replyStyle: MSTeamsReplyStyleSchema.optional(),
|
replyStyle: MSTeamsReplyStyleSchema.optional(),
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
@@ -583,6 +591,7 @@ export const MSTeamsChannelSchema = z
|
|||||||
export const MSTeamsTeamSchema = z
|
export const MSTeamsTeamSchema = z
|
||||||
.object({
|
.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
replyStyle: MSTeamsReplyStyleSchema.optional(),
|
replyStyle: MSTeamsReplyStyleSchema.optional(),
|
||||||
channels: z.record(z.string(), MSTeamsChannelSchema.optional()).optional(),
|
channels: z.record(z.string(), MSTeamsChannelSchema.optional()).optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
GroupPolicySchema,
|
GroupPolicySchema,
|
||||||
MarkdownConfigSchema,
|
MarkdownConfigSchema,
|
||||||
} from "./zod-schema.core.js";
|
} from "./zod-schema.core.js";
|
||||||
|
import { ToolPolicySchema } from "./zod-schema.agent-runtime.js";
|
||||||
|
|
||||||
export const WhatsAppAccountSchema = z
|
export const WhatsAppAccountSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -37,6 +38,7 @@ export const WhatsAppAccountSchema = z
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -98,6 +100,7 @@ export const WhatsAppConfigSchema = z
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
requireMention: z.boolean().optional(),
|
requireMention: z.boolean().optional(),
|
||||||
|
tools: ToolPolicySchema,
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export type {
|
|||||||
DmPolicy,
|
DmPolicy,
|
||||||
DmConfig,
|
DmConfig,
|
||||||
GroupPolicy,
|
GroupPolicy,
|
||||||
|
GroupToolPolicyConfig,
|
||||||
MarkdownConfig,
|
MarkdownConfig,
|
||||||
MarkdownTableMode,
|
MarkdownTableMode,
|
||||||
MSTeamsChannelConfig,
|
MSTeamsChannelConfig,
|
||||||
@@ -99,6 +100,7 @@ export {
|
|||||||
normalizeAllowFrom,
|
normalizeAllowFrom,
|
||||||
requireOpenAllowFrom,
|
requireOpenAllowFrom,
|
||||||
} from "../config/zod-schema.core.js";
|
} from "../config/zod-schema.core.js";
|
||||||
|
export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js";
|
||||||
export type { RuntimeEnv } from "../runtime.js";
|
export type { RuntimeEnv } from "../runtime.js";
|
||||||
export type { WizardPrompter } from "../wizard/prompts.js";
|
export type { WizardPrompter } from "../wizard/prompts.js";
|
||||||
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||||
@@ -143,6 +145,12 @@ export {
|
|||||||
resolveSlackGroupRequireMention,
|
resolveSlackGroupRequireMention,
|
||||||
resolveTelegramGroupRequireMention,
|
resolveTelegramGroupRequireMention,
|
||||||
resolveWhatsAppGroupRequireMention,
|
resolveWhatsAppGroupRequireMention,
|
||||||
|
resolveBlueBubblesGroupToolPolicy,
|
||||||
|
resolveDiscordGroupToolPolicy,
|
||||||
|
resolveIMessageGroupToolPolicy,
|
||||||
|
resolveSlackGroupToolPolicy,
|
||||||
|
resolveTelegramGroupToolPolicy,
|
||||||
|
resolveWhatsAppGroupToolPolicy,
|
||||||
} from "../channels/plugins/group-mentions.js";
|
} from "../channels/plugins/group-mentions.js";
|
||||||
export { recordInboundSession } from "../channels/session.js";
|
export { recordInboundSession } from "../channels/session.js";
|
||||||
export {
|
export {
|
||||||
|
|||||||
Reference in New Issue
Block a user