-
Notifications
You must be signed in to change notification settings - Fork 2
fix: AI channel blocklist - rebase on main #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
70e9d34
c008d20
96db004
ec4bf40
5789869
e1e0785
9ea35c1
f34199c
a9a7718
8a60db5
dcad790
99c4290
648caff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ export const CONFIG_SCHEMA = { | |||||
| enabled: { type: 'boolean' }, | ||||||
| systemPrompt: { type: 'string' }, | ||||||
| channels: { type: 'array' }, | ||||||
| blockedChannelIds: { type: 'array' }, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Allowing any array element type can persist invalid config and silently break block matching. Suggested schema fix- blockedChannelIds: { type: 'array' },
+ blockedChannelIds: { type: 'array', items: { type: 'string' } },📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| historyLength: { type: 'number' }, | ||||||
| historyTTLDays: { type: 'number' }, | ||||||
| threadMode: { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,6 +25,32 @@ let cleanupTimer = null; | |||||||||||||||||||||||||||||||||
| /** In-flight async hydrations keyed by channel ID (dedupes concurrent DB reads) */ | ||||||||||||||||||||||||||||||||||
| const pendingHydrations = new Map(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||
| * Check whether a channel (or its parent thread channel) is in the AI blocklist. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * When `parentId` is provided (i.e. the message is inside a thread), the parent | ||||||||||||||||||||||||||||||||||
| * channel ID is also checked so that blocking a channel implicitly blocks all | ||||||||||||||||||||||||||||||||||
| * threads that belong to it. | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * @param {string} channelId - The channel ID to test. | ||||||||||||||||||||||||||||||||||
| * @param {string|null} [parentId] - Optional parent channel ID (for threads). | ||||||||||||||||||||||||||||||||||
| * @param {string} guildId - The guild ID for per-guild configuration. | ||||||||||||||||||||||||||||||||||
| * @returns {boolean} `true` when the channel is blocked, `false` otherwise. | ||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||
| export function isChannelBlocked(channelId, parentId = null, guildId) { | ||||||||||||||||||||||||||||||||||
BillChirico marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const config = getConfig(guildId); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+42
|
||||||||||||||||||||||||||||||||||
| * @param {string} guildId - The guild ID for per-guild configuration. | |
| * @returns {boolean} `true` when the channel is blocked, `false` otherwise. | |
| */ | |
| export function isChannelBlocked(channelId, parentId = null, guildId) { | |
| try { | |
| const config = getConfig(guildId); | |
| * @param {string} [guildId] - Optional guild ID for per-guild configuration. When omitted, | |
| * the global/default configuration is used via {@link getConfig} with no arguments. | |
| * @returns {boolean} `true` when the channel is blocked, `false` otherwise. | |
| */ | |
| export function isChannelBlocked(channelId, parentId = null, guildId = null) { | |
| try { | |
| // When guildId is provided, use per-guild configuration; otherwise fall back | |
| // to the global/default configuration, matching the behavior of other helpers | |
| // such as getHistoryLength and getHistoryTTLDays. | |
| const config = guildId ? getConfig(guildId) : getConfig(); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ import { getUserFriendlyMessage } from '../utils/errors.js'; | |||||||||||||||||
| // safe wrapper applies identically to either target type. | ||||||||||||||||||
| import { safeEditReply, safeReply } from '../utils/safeSend.js'; | ||||||||||||||||||
| import { handleAfkMentions } from './afkHandler.js'; | ||||||||||||||||||
| import { isChannelBlocked } from './ai.js'; | ||||||||||||||||||
| import { handleHintButton, handleSolveButton } from './challengeScheduler.js'; | ||||||||||||||||||
| import { getConfig } from './config.js'; | ||||||||||||||||||
| import { trackMessage, trackReaction } from './engagement.js'; | ||||||||||||||||||
|
|
@@ -219,6 +220,12 @@ export function registerMessageCreateHandler(client, _config, healthMonitor) { | |||||||||||||||||
| const isAllowedChannel = | ||||||||||||||||||
| allowedChannels.length === 0 || allowedChannels.includes(channelIdToCheck); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Check blocklist — blocked channels never get AI responses. | ||||||||||||||||||
| // For threads, parentId is also checked so blocking the parent channel | ||||||||||||||||||
| // blocks all its child threads. | ||||||||||||||||||
| const parentId = message.channel.isThread?.() ? message.channel.parentId : null; | ||||||||||||||||||
| if (isChannelBlocked(message.channel.id, parentId, message.guild.id)) return; | ||||||||||||||||||
BillChirico marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+224
to
+228
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Run the blocklist guard before reply-reference fetch logic. This early return is correct, but moving it earlier in the AI branch avoids unnecessary Discord fetches in blocked channels. 🤖 Prompt for AI Agents
|
||||||||||||||||||
| if (isChannelBlocked(message.channel.id, parentId, message.guild.id)) return; | |
| const blockedChannels = guildConfig.ai?.blockedChannelIds || []; | |
| const isBlockedChannel = | |
| blockedChannels.includes(message.channel.id) || | |
| (parentId !== null && blockedChannels.includes(parentId)); | |
| if (isBlockedChannel) { | |
| return; | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConversationHistory, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getHistoryAsync, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initConversationHistory, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isChannelBlocked, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setConversationHistory, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setPool, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| startConversationCleanup, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -204,6 +205,58 @@ describe('ai module', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ── isChannelBlocked ───────────────────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('isChannelBlocked', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should return false when blockedChannelIds is not set', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockReturnValue({ ai: {} }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('ch1')).toBe(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should return false when blockedChannelIds is empty', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockReturnValue({ ai: { blockedChannelIds: [] } }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('ch1')).toBe(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should return true when channelId is in blockedChannelIds', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockReturnValue({ ai: { blockedChannelIds: ['ch1', 'ch2'] } }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('ch1')).toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('ch2')).toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should return false when channelId is not in blockedChannelIds', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockReturnValue({ ai: { blockedChannelIds: ['ch1'] } }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('ch3')).toBe(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should return true when parentId is in blockedChannelIds (thread in blocked parent)', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockReturnValue({ ai: { blockedChannelIds: ['parent-ch'] } }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('thread-ch', 'parent-ch')).toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should return true when channelId matches even if parentId is not blocked', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockReturnValue({ ai: { blockedChannelIds: ['thread-ch'] } }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('thread-ch', 'parent-ch')).toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should return false when neither channelId nor parentId is in blockedChannelIds', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockReturnValue({ ai: { blockedChannelIds: ['other-ch'] } }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('thread-ch', 'parent-ch')).toBe(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+210
to
+245
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should return false when parentId is null and channelId is not blocked', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockReturnValue({ ai: { blockedChannelIds: ['ch1'] } }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('ch2', null)).toBe(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should fail open (return false) when getConfig throws', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getConfig.mockImplementation(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Config not loaded'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(isChannelBlocked('ch1')).toBe(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BillChirico marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+210
to
+257
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add guild-scoped assertions for These cases validate matching logic, but they don’t verify guild scoping. Since the helper now accepts ✅ Suggested test addition+ it('uses guild-scoped config lookup', () => {
+ getConfig.mockReturnValue({ ai: { blockedChannelIds: ['ch1'] } });
+ expect(isChannelBlocked('ch1', null, 'guild-1')).toBe(true);
+ expect(getConfig).toHaveBeenCalledWith('guild-1');
+ });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ── cleanup scheduler ───────────────────────────────────────────────── | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('cleanup scheduler', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | |
| import { toast } from 'sonner'; | ||
| import { Button } from '@/components/ui/button'; | ||
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; | ||
| import { ChannelSelector } from '@/components/ui/channel-selector'; | ||
| import { Input } from '@/components/ui/input'; | ||
| import { RoleSelector } from '@/components/ui/role-selector'; | ||
| import { GUILD_SELECTED_EVENT, SELECTED_GUILD_KEY } from '@/lib/guild-selection'; | ||
|
|
@@ -413,6 +414,16 @@ export function ConfigEditor() { | |
| [updateDraftConfig], | ||
| ); | ||
|
|
||
| const updateAiBlockedChannels = useCallback( | ||
| (channels: string[]) => { | ||
| updateDraftConfig((prev) => { | ||
| if (!prev) return prev; | ||
| return { ...prev, ai: { ...prev.ai, blockedChannelIds: channels } } as GuildConfig; | ||
| }); | ||
| }, | ||
| [updateDraftConfig], | ||
| ); | ||
|
|
||
| const updateWelcomeEnabled = useCallback( | ||
| (enabled: boolean) => { | ||
| updateDraftConfig((prev) => { | ||
|
|
@@ -747,6 +758,31 @@ export function ConfigEditor() { | |
| maxLength={SYSTEM_PROMPT_MAX_LENGTH} | ||
| /> | ||
|
|
||
| {/* AI Blocked Channels */} | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle className="text-base">Blocked Channels</CardTitle> | ||
| <CardDescription> | ||
| The AI will not respond in these channels (or their threads). | ||
| </CardDescription> | ||
| </CardHeader> | ||
| <CardContent className="space-y-2"> | ||
| {guildId ? ( | ||
| <ChannelSelector | ||
| id="ai-blocked-channels" | ||
| guildId={guildId} | ||
| selected={(draftConfig.ai?.blockedChannelIds ?? []) as string[]} | ||
| onChange={updateAiBlockedChannels} | ||
| placeholder="Select channels to block AI in..." | ||
| disabled={saving} | ||
| filter="text" | ||
| /> | ||
|
Comment on lines
+771
to
+779
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Use the store-backed channel source for this picker. This newly added blocked-channel selector should follow the dashboard’s cached Discord-entities flow (Zustand) rather than a direct-fetch path, to avoid repeated fetches and stale channel lists across sections. As per coding guidelines, "Component files should integrate with Zustand stores for state management (e.g., discord-entities store for caching Discord channels and roles per guild)". 🤖 Prompt for AI Agents |
||
| ) : ( | ||
| <p className="text-muted-foreground text-sm">Select a server first</p> | ||
| )} | ||
| </CardContent> | ||
| </Card> | ||
|
|
||
| {/* Welcome section */} | ||
| <Card> | ||
| <CardHeader> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.