@@ -39,6 +39,7 @@ import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js
3939import { normalizeMessageChannel } from "../../../src/utils/message-channel.js" ;
4040import { isDiscordExecApprovalClientEnabled } from "./exec-approvals.js" ;
4141import type { DiscordProbe } from "./probe.js" ;
42+ import { resolveDiscordUserAllowlist } from "./resolve-users.js" ;
4243import { getDiscordRuntime } from "./runtime.js" ;
4344import { fetchChannelPermissionsDiscord } from "./send.js" ;
4445import { createDiscordSetupWizardProxy , discordSetupAdapter } from "./setup-core.js" ;
@@ -116,6 +117,84 @@ function hasDiscordExecApprovalDmRoute(cfg: OpenClawConfig): boolean {
116117 } ) ;
117118}
118119
120+ function readDiscordAllowlistConfig ( account : ResolvedDiscordAccount ) {
121+ const groupOverrides : Array < { label : string ; entries : string [ ] } > = [ ] ;
122+ for ( const [ guildKey , guildCfg ] of Object . entries ( account . config . guilds ?? { } ) ) {
123+ const entries = ( guildCfg ?. users ?? [ ] ) . map ( String ) . filter ( Boolean ) ;
124+ if ( entries . length > 0 ) {
125+ groupOverrides . push ( { label : `guild ${ guildKey } ` , entries } ) ;
126+ }
127+ for ( const [ channelKey , channelCfg ] of Object . entries ( guildCfg ?. channels ?? { } ) ) {
128+ const channelEntries = ( channelCfg ?. users ?? [ ] ) . map ( String ) . filter ( Boolean ) ;
129+ if ( channelEntries . length > 0 ) {
130+ groupOverrides . push ( {
131+ label : `guild ${ guildKey } / channel ${ channelKey } ` ,
132+ entries : channelEntries ,
133+ } ) ;
134+ }
135+ }
136+ }
137+ return {
138+ dmAllowFrom : ( account . config . allowFrom ?? account . config . dm ?. allowFrom ?? [ ] ) . map ( String ) ,
139+ groupPolicy : account . config . groupPolicy ,
140+ groupOverrides,
141+ } ;
142+ }
143+
144+ async function resolveDiscordAllowlistNames ( params : {
145+ cfg : Parameters < typeof resolveDiscordAccount > [ 0 ] [ "cfg" ] ;
146+ accountId ?: string | null ;
147+ entries : string [ ] ;
148+ } ) {
149+ const account = resolveDiscordAccount ( { cfg : params . cfg , accountId : params . accountId } ) ;
150+ const token = account . token ?. trim ( ) ;
151+ if ( ! token ) {
152+ return [ ] ;
153+ }
154+ return await resolveDiscordUserAllowlist ( { token, entries : params . entries } ) ;
155+ }
156+
157+ function normalizeDiscordAcpConversationId ( conversationId : string ) {
158+ const normalized = conversationId . trim ( ) ;
159+ return normalized ? { conversationId : normalized } : null ;
160+ }
161+
162+ function matchDiscordAcpConversation ( params : {
163+ bindingConversationId : string ;
164+ conversationId : string ;
165+ parentConversationId ?: string ;
166+ } ) {
167+ if ( params . bindingConversationId === params . conversationId ) {
168+ return { conversationId : params . conversationId , matchPriority : 2 } ;
169+ }
170+ if (
171+ params . parentConversationId &&
172+ params . parentConversationId !== params . conversationId &&
173+ params . bindingConversationId === params . parentConversationId
174+ ) {
175+ return {
176+ conversationId : params . parentConversationId ,
177+ matchPriority : 1 ,
178+ } ;
179+ }
180+ return null ;
181+ }
182+
183+ function parseDiscordExplicitTarget ( raw : string ) {
184+ try {
185+ const target = parseDiscordTarget ( raw , { defaultKind : "channel" } ) ;
186+ if ( ! target ) {
187+ return null ;
188+ }
189+ return {
190+ to : target . id ,
191+ chatType : target . kind === "user" ? ( "direct" as const ) : ( "channel" as const ) ,
192+ } ;
193+ } catch {
194+ return null ;
195+ }
196+ }
197+
119198const discordConfigAccessors = createScopedAccountConfigAccessors ( {
120199 resolveAccount : ( { cfg, accountId } ) => resolveDiscordAccount ( { cfg, accountId } ) ,
121200 resolveAllowFrom : ( account : ResolvedDiscordAccount ) => account . config . dm ?. allowFrom ,
@@ -177,6 +256,23 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
177256 } ) ,
178257 ...discordConfigAccessors ,
179258 } ,
259+ allowlist : {
260+ supportsScope : ( { scope } ) => scope === "dm" ,
261+ readConfig : ( { cfg, accountId } ) =>
262+ readDiscordAllowlistConfig ( resolveDiscordAccount ( { cfg, accountId } ) ) ,
263+ resolveNames : async ( { cfg, accountId, entries } ) =>
264+ await resolveDiscordAllowlistNames ( { cfg, accountId, entries } ) ,
265+ resolveConfigEdit : ( { scope, pathPrefix, writeTarget } ) =>
266+ scope === "dm"
267+ ? {
268+ pathPrefix,
269+ writeTarget,
270+ readPaths : [ [ "allowFrom" ] , [ "dm" , "allowFrom" ] ] ,
271+ writePath : [ "allowFrom" ] ,
272+ cleanupPaths : [ [ "dm" , "allowFrom" ] ] ,
273+ }
274+ : null ,
275+ } ,
180276 security : {
181277 resolveDmPolicy : ( { cfg, accountId, account } ) => {
182278 return buildAccountScopedDmSecurityPolicy ( {
@@ -238,6 +334,8 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
238334 } ,
239335 messaging : {
240336 normalizeTarget : normalizeDiscordMessagingTarget ,
337+ parseExplicitTarget : ( { raw } ) => parseDiscordExplicitTarget ( raw ) ,
338+ inferTargetChatType : ( { to } ) => parseDiscordExplicitTarget ( to ) ?. chatType ,
241339 buildCrossContextComponents : buildDiscordCrossContextComponents ,
242340 targetResolver : {
243341 looksLikeId : looksLikeDiscordTargetId ,
@@ -356,6 +454,12 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
356454 silent : silent ?? undefined ,
357455 } ) ,
358456 } ,
457+ acpBindings : {
458+ normalizeConfiguredBindingTarget : ( { conversationId } ) =>
459+ normalizeDiscordAcpConversationId ( conversationId ) ,
460+ matchConfiguredBinding : ( { bindingConversationId, conversationId, parentConversationId } ) =>
461+ matchDiscordAcpConversation ( { bindingConversationId, conversationId, parentConversationId } ) ,
462+ } ,
359463 status : {
360464 defaultRuntime : {
361465 accountId : DEFAULT_ACCOUNT_ID ,
0 commit comments