fix: add missing role-based type definitions for RBAC routing

This commit is contained in:
Minidoracat
2026-02-12 03:53:31 +00:00
committed by Shadow
parent ad508c8c89
commit e084f07420
6 changed files with 25 additions and 0 deletions

View File

@@ -78,5 +78,6 @@ export type AgentBinding = {
peer?: { kind: ChatType; id: string }; peer?: { kind: ChatType; id: string };
guildId?: string; guildId?: string;
teamId?: string; teamId?: string;
roles?: string[];
}; };
}; };

View File

@@ -36,6 +36,8 @@ export type DiscordGuildChannelConfig = {
enabled?: boolean; enabled?: boolean;
/** Optional allowlist for channel senders (ids or names). */ /** Optional allowlist for channel senders (ids or names). */
users?: Array<string | number>; users?: Array<string | number>;
/** Optional allowlist for channel senders by role (ids or names). */
roles?: Array<string | number>;
/** Optional system prompt snippet for this channel. */ /** Optional system prompt snippet for this channel. */
systemPrompt?: string; systemPrompt?: string;
/** If false, omit thread starter context for this channel (default: true). */ /** If false, omit thread starter context for this channel (default: true). */
@@ -53,6 +55,7 @@ export type DiscordGuildEntry = {
/** 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>;
roles?: Array<string | number>;
channels?: Record<string, DiscordGuildChannelConfig>; channels?: Record<string, DiscordGuildChannelConfig>;
}; };

View File

@@ -35,6 +35,7 @@ export const BindingsSchema = z
.optional(), .optional(),
guildId: z.string().optional(), guildId: z.string().optional(),
teamId: z.string().optional(), teamId: z.string().optional(),
roles: z.array(z.string()).optional(),
}) })
.strict(), .strict(),
}) })

View File

@@ -235,6 +235,7 @@ export const DiscordGuildChannelSchema = z
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(),
roles: z.array(z.union([z.string(), z.number()])).optional(),
systemPrompt: z.string().optional(), systemPrompt: z.string().optional(),
includeThreadStarter: z.boolean().optional(), includeThreadStarter: z.boolean().optional(),
autoThread: z.boolean().optional(), autoThread: z.boolean().optional(),
@@ -249,6 +250,7 @@ export const DiscordGuildSchema = z
toolsBySender: ToolPolicyBySenderSchema, toolsBySender: ToolPolicyBySenderSchema,
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(),
roles: 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(),
}) })
.strict(); .strict();

View File

@@ -22,6 +22,7 @@ export type DiscordGuildEntryResolved = {
requireMention?: boolean; requireMention?: boolean;
reactionNotifications?: "off" | "own" | "all" | "allowlist"; reactionNotifications?: "off" | "own" | "all" | "allowlist";
users?: Array<string | number>; users?: Array<string | number>;
roles?: Array<string | number>;
channels?: Record< channels?: Record<
string, string,
{ {
@@ -30,6 +31,7 @@ export type DiscordGuildEntryResolved = {
skills?: string[]; skills?: string[];
enabled?: boolean; enabled?: boolean;
users?: Array<string | number>; users?: Array<string | number>;
roles?: Array<string | number>;
systemPrompt?: string; systemPrompt?: string;
includeThreadStarter?: boolean; includeThreadStarter?: boolean;
autoThread?: boolean; autoThread?: boolean;
@@ -43,6 +45,7 @@ export type DiscordChannelConfigResolved = {
skills?: string[]; skills?: string[];
enabled?: boolean; enabled?: boolean;
users?: Array<string | number>; users?: Array<string | number>;
roles?: Array<string | number>;
systemPrompt?: string; systemPrompt?: string;
includeThreadStarter?: boolean; includeThreadStarter?: boolean;
autoThread?: boolean; autoThread?: boolean;
@@ -283,6 +286,7 @@ function resolveDiscordChannelConfigEntry(
skills: entry.skills, skills: entry.skills,
enabled: entry.enabled, enabled: entry.enabled,
users: entry.users, users: entry.users,
roles: entry.roles,
systemPrompt: entry.systemPrompt, systemPrompt: entry.systemPrompt,
includeThreadStarter: entry.includeThreadStarter, includeThreadStarter: entry.includeThreadStarter,
autoThread: entry.autoThread, autoThread: entry.autoThread,

View File

@@ -29,6 +29,8 @@ export type ResolveAgentRouteInput = {
parentPeer?: RoutePeer | null; parentPeer?: RoutePeer | null;
guildId?: string | null; guildId?: string | null;
teamId?: string | null; teamId?: string | null;
/** Discord member role IDs — used for role-based agent routing. */
memberRoleIds?: string[];
}; };
export type ResolvedAgentRoute = { export type ResolvedAgentRoute = {
@@ -169,12 +171,24 @@ function matchesTeam(match: { teamId?: string | undefined } | undefined, teamId:
return id === teamId; return id === teamId;
} }
function matchesRoles(
match: { roles?: string[] | undefined } | undefined,
memberRoleIds: string[],
): boolean {
const roles = match?.roles;
if (!Array.isArray(roles) || roles.length === 0) {
return false;
}
return roles.some((r) => memberRoleIds.includes(r));
}
export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentRoute { export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentRoute {
const channel = normalizeToken(input.channel); const channel = normalizeToken(input.channel);
const accountId = normalizeAccountId(input.accountId); const accountId = normalizeAccountId(input.accountId);
const peer = input.peer ? { kind: input.peer.kind, id: normalizeId(input.peer.id) } : null; const peer = input.peer ? { kind: input.peer.kind, id: normalizeId(input.peer.id) } : null;
const guildId = normalizeId(input.guildId); const guildId = normalizeId(input.guildId);
const teamId = normalizeId(input.teamId); const teamId = normalizeId(input.teamId);
const memberRoleIds = input.memberRoleIds ?? [];
const bindings = listBindings(input.cfg).filter((binding) => { const bindings = listBindings(input.cfg).filter((binding) => {
if (!binding || typeof binding !== "object") { if (!binding || typeof binding !== "object") {