2
2
ChannelType ,
3
3
Client ,
4
4
Message as DiscordMessage ,
5
+ PermissionsBitField ,
5
6
TextChannel ,
7
+ ThreadChannel ,
6
8
} from "discord.js" ;
7
9
import { composeContext } from "../../core/context.ts" ;
8
10
import {
@@ -27,6 +29,7 @@ import { AttachmentManager } from "./attachments.ts";
27
29
import { messageHandlerTemplate , shouldRespondTemplate } from "./templates.ts" ;
28
30
import { InterestChannels } from "./types.ts" ;
29
31
import { VoiceManager } from "./voice.ts" ;
32
+ import { prettyConsole } from "../../index.ts" ;
30
33
31
34
const MAX_MESSAGE_LENGTH = 1900 ;
32
35
@@ -102,6 +105,52 @@ function splitMessage(content: string): string[] {
102
105
return messages ;
103
106
}
104
107
108
+
109
+ function canSendMessage ( channel ) {
110
+ // Get the bot member in the guild
111
+ const botMember = channel . guild ?. members . cache . get ( channel . client . user . id ) ;
112
+
113
+ if ( ! botMember ) {
114
+ return {
115
+ canSend : false ,
116
+ reason : 'Not a guild channel or bot member not found'
117
+ } ;
118
+ }
119
+
120
+ // Required permissions for sending messages
121
+ const requiredPermissions = [
122
+ PermissionsBitField . Flags . ViewChannel ,
123
+ PermissionsBitField . Flags . SendMessages ,
124
+ PermissionsBitField . Flags . ReadMessageHistory
125
+ ] ;
126
+
127
+ // Add thread-specific permission if it's a thread
128
+ if ( channel instanceof ThreadChannel ) {
129
+ requiredPermissions . push ( PermissionsBitField . Flags . SendMessagesInThreads ) ;
130
+ }
131
+
132
+ // Check permissions
133
+ const permissions = channel . permissionsFor ( botMember ) ;
134
+
135
+ if ( ! permissions ) {
136
+ return {
137
+ canSend : false ,
138
+ reason : 'Could not retrieve permissions'
139
+ } ;
140
+ }
141
+
142
+ // Check each required permission
143
+ const missingPermissions = requiredPermissions . filter ( perm => ! permissions . has ( perm ) ) ;
144
+
145
+ return {
146
+ canSend : missingPermissions . length === 0 ,
147
+ missingPermissions : missingPermissions ,
148
+ reason : missingPermissions . length > 0
149
+ ? `Missing permissions: ${ missingPermissions . map ( p => String ( p ) ) . join ( ', ' ) } `
150
+ : null
151
+ } ;
152
+ }
153
+
105
154
export class MessageManager {
106
155
private client : Client ;
107
156
private runtime : IAgentRuntime ;
@@ -122,7 +171,7 @@ export class MessageManager {
122
171
if (
123
172
message . interaction ||
124
173
message . author . id ===
125
- this . client . user ?. id /* || message.author?.bot*/
174
+ this . client . user ?. id /* || message.author?.bot*/
126
175
)
127
176
return ;
128
177
const userId = message . author . id as UUID ;
@@ -178,14 +227,6 @@ export class MessageManager {
178
227
roomId,
179
228
} ;
180
229
181
- let state = ( await this . runtime . composeState ( userMessage , {
182
- discordClient : this . client ,
183
- discordMessage : message ,
184
- agentName :
185
- this . runtime . character . name ||
186
- this . client . user ?. displayName ,
187
- } ) ) as State ;
188
-
189
230
const memory : Memory = {
190
231
id : stringToUuid ( message . id + "-" + this . runtime . agentId ) ,
191
232
...userMessage ,
@@ -201,7 +242,18 @@ export class MessageManager {
201
242
await this . runtime . messageManager . createMemory ( memory ) ;
202
243
}
203
244
204
- state = await this . runtime . updateRecentMessageState ( state ) ;
245
+ let state = ( await this . runtime . composeState ( userMessage , {
246
+ discordClient : this . client ,
247
+ discordMessage : message ,
248
+ agentName :
249
+ this . runtime . character . name ||
250
+ this . client . user ?. displayName ,
251
+ } ) ) as State ;
252
+
253
+ if ( ! canSendMessage ( message . channel ) . canSend ) {
254
+ return prettyConsole . warn ( `Cannot send message to channel ${ message . channel } ` , canSendMessage ( message . channel ) ) ;
255
+ }
256
+
205
257
206
258
if ( ! shouldIgnore ) {
207
259
shouldIgnore = await this . _shouldIgnore ( message ) ;
@@ -267,70 +319,70 @@ export class MessageManager {
267
319
files : any [ ]
268
320
) => {
269
321
try {
270
- if ( message . id && ! content . inReplyTo ) {
271
- content . inReplyTo = stringToUuid ( message . id + "-" + this . runtime . agentId ) ;
272
- }
273
- if ( message . channel . type === ChannelType . GuildVoice ) {
274
- // For voice channels, use text-to-speech
275
- const audioStream =
276
- await this . runtime . speechService . generate (
277
- this . runtime ,
278
- content . text
322
+ if ( message . id && ! content . inReplyTo ) {
323
+ content . inReplyTo = stringToUuid ( message . id + "-" + this . runtime . agentId ) ;
324
+ }
325
+ if ( message . channel . type === ChannelType . GuildVoice ) {
326
+ // For voice channels, use text-to-speech
327
+ const audioStream =
328
+ await this . runtime . speechService . generate (
329
+ this . runtime ,
330
+ content . text
331
+ ) ;
332
+ await this . voiceManager . playAudioStream (
333
+ userId ,
334
+ audioStream
279
335
) ;
280
- await this . voiceManager . playAudioStream (
281
- userId ,
282
- audioStream
283
- ) ;
284
- const memory : Memory = {
285
- id : stringToUuid ( message . id + "-" + this . runtime . agentId ) ,
286
- userId : this . runtime . agentId ,
287
- agentId : this . runtime . agentId ,
288
- content,
289
- roomId,
290
- embedding : embeddingZeroVector ,
291
- } ;
292
- return [ memory ] ;
293
- } else {
294
- // For text channels, send the message
295
- const messages = await sendMessageInChunks (
296
- message . channel as TextChannel ,
297
- content . text ,
298
- message . id ,
299
- files
300
- ) ;
301
-
302
- const memories : Memory [ ] = [ ] ;
303
- for ( const m of messages ) {
304
- let action = content . action ;
305
- // If there's only one message or it's the last message, keep the original action
306
- // For multiple messages, set all but the last to 'CONTINUE'
307
- if (
308
- messages . length > 1 &&
309
- m !== messages [ messages . length - 1 ]
310
- ) {
311
- action = "CONTINUE" ;
312
- }
313
-
314
336
const memory : Memory = {
315
- id : stringToUuid ( m . id + "-" + this . runtime . agentId ) ,
337
+ id : stringToUuid ( message . id + "-" + this . runtime . agentId ) ,
316
338
userId : this . runtime . agentId ,
317
339
agentId : this . runtime . agentId ,
318
- content : {
319
- ...content ,
320
- action,
321
- inReplyTo : messageId ,
322
- url : m . url ,
323
- } ,
340
+ content,
324
341
roomId,
325
342
embedding : embeddingZeroVector ,
326
- createdAt : m . createdTimestamp ,
327
343
} ;
328
- memories . push ( memory ) ;
329
- }
330
- for ( const m of memories ) {
331
- await this . runtime . messageManager . createMemory ( m ) ;
332
- }
333
- return memories ;
344
+ return [ memory ] ;
345
+ } else {
346
+ // For text channels, send the message
347
+ const messages = await sendMessageInChunks (
348
+ message . channel as TextChannel ,
349
+ content . text ,
350
+ message . id ,
351
+ files
352
+ ) ;
353
+
354
+ const memories : Memory [ ] = [ ] ;
355
+ for ( const m of messages ) {
356
+ let action = content . action ;
357
+ // If there's only one message or it's the last message, keep the original action
358
+ // For multiple messages, set all but the last to 'CONTINUE'
359
+ if (
360
+ messages . length > 1 &&
361
+ m !== messages [ messages . length - 1 ]
362
+ ) {
363
+ action = "CONTINUE" ;
364
+ }
365
+
366
+ const memory : Memory = {
367
+ id : stringToUuid ( m . id + "-" + this . runtime . agentId ) ,
368
+ userId : this . runtime . agentId ,
369
+ agentId : this . runtime . agentId ,
370
+ content : {
371
+ ...content ,
372
+ action,
373
+ inReplyTo : messageId ,
374
+ url : m . url ,
375
+ } ,
376
+ roomId,
377
+ embedding : embeddingZeroVector ,
378
+ createdAt : m . createdTimestamp ,
379
+ } ;
380
+ memories . push ( memory ) ;
381
+ }
382
+ for ( const m of memories ) {
383
+ await this . runtime . messageManager . createMemory ( m ) ;
384
+ }
385
+ return memories ;
334
386
}
335
387
} catch ( error ) {
336
388
console . error ( "Error sending message:" , error ) ;
0 commit comments