Skip to content

Commit 7b8c576

Browse files
committed
fix(slack): improve message handling and add setup screenshots
1 parent 0b9b448 commit 7b8c576

File tree

3 files changed

+185
-92
lines changed

3 files changed

+185
-92
lines changed
123 KB
Loading
58.8 KB
Loading

packages/client-slack/src/messages.ts

+185-92
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,159 @@
1-
import { WebClient } from '@slack/web-api';
2-
import { elizaLogger } from '@ai16z/eliza';
3-
import { IAgentRuntime, Memory, Content, State } from '@ai16z/eliza';
41
import {
52
stringToUuid,
63
getEmbeddingZeroVector,
74
composeContext,
8-
generateMessageResponse,
5+
generateMessageResponse,
96
generateShouldRespond,
10-
ModelClass
7+
ModelClass,
8+
Memory,
9+
Content,
10+
State,
11+
elizaLogger,
12+
HandlerCallback
1113
} from '@ai16z/eliza';
1214
import { slackMessageHandlerTemplate, slackShouldRespondTemplate } from './templates';
15+
import { WebClient } from '@slack/web-api';
16+
import { IAgentRuntime } from '@ai16z/eliza';
1317

1418
export class MessageManager {
1519
private client: WebClient;
1620
private runtime: IAgentRuntime;
1721
private botUserId: string;
22+
private processedMessages: Map<string, number> = new Map();
1823

1924
constructor(client: WebClient, runtime: IAgentRuntime, botUserId: string) {
2025
elizaLogger.log("📱 Initializing MessageManager...");
2126
this.client = client;
2227
this.runtime = runtime;
2328
this.botUserId = botUserId;
2429
elizaLogger.debug("MessageManager initialized with botUserId:", botUserId);
30+
31+
// Clear old processed messages every hour
32+
setInterval(() => {
33+
const oneHourAgo = Date.now() - 3600000;
34+
for (const [key, timestamp] of this.processedMessages.entries()) {
35+
if (timestamp < oneHourAgo) {
36+
this.processedMessages.delete(key);
37+
}
38+
}
39+
}, 3600000);
2540
}
2641

27-
public async handleMessage(event: any) {
28-
elizaLogger.debug("📥 [DETAILED] Incoming message event:", JSON.stringify(event, null, 2));
42+
private cleanMessage(text: string): string {
43+
elizaLogger.debug("🧹 [CLEAN] Cleaning message text:", text);
44+
// Remove bot mention
45+
const cleaned = text.replace(new RegExp(`<@${this.botUserId}>`, 'g'), '').trim();
46+
elizaLogger.debug("✨ [CLEAN] Cleaned result:", cleaned);
47+
return cleaned;
48+
}
2949

30-
// Ignore messages from bots (including ourselves)
31-
if (event.bot_id || event.user === this.botUserId) {
32-
elizaLogger.debug("⏭️ [SKIP] Message from bot or self:", {
33-
bot_id: event.bot_id,
34-
user: event.user,
35-
bot_user_id: this.botUserId
36-
});
37-
return;
50+
private async _shouldRespond(message: any, state: State): Promise<boolean> {
51+
// Always respond to direct mentions
52+
if (message.type === 'app_mention' || message.text?.includes(`<@${this.botUserId}>`)) {
53+
return true;
54+
}
55+
56+
// Always respond in direct messages
57+
if (message.channel_type === 'im') {
58+
return true;
59+
}
60+
61+
// Use the shouldRespond template to decide
62+
const shouldRespondContext = composeContext({
63+
state,
64+
template: this.runtime.character.templates?.slackShouldRespondTemplate ||
65+
this.runtime.character.templates?.shouldRespondTemplate ||
66+
slackShouldRespondTemplate,
67+
});
68+
69+
const response = await generateShouldRespond({
70+
runtime: this.runtime,
71+
context: shouldRespondContext,
72+
modelClass: ModelClass.SMALL,
73+
});
74+
75+
return response === 'RESPOND';
76+
}
77+
78+
private async _generateResponse(
79+
memory: Memory,
80+
state: State,
81+
context: string
82+
): Promise<Content> {
83+
const { userId, roomId } = memory;
84+
85+
const response = await generateMessageResponse({
86+
runtime: this.runtime,
87+
context,
88+
modelClass: ModelClass.SMALL,
89+
});
90+
91+
if (!response) {
92+
elizaLogger.error("No response from generateMessageResponse");
93+
return {
94+
text: "I apologize, but I'm having trouble generating a response right now.",
95+
source: 'slack'
96+
};
97+
}
98+
99+
await this.runtime.databaseAdapter.log({
100+
body: { memory, context, response },
101+
userId: userId,
102+
roomId,
103+
type: "response",
104+
});
105+
106+
// If response includes a CONTINUE action but there's no direct mention or thread,
107+
// remove the action to prevent automatic continuation
108+
if (
109+
response.action === 'CONTINUE' &&
110+
!memory.content.text?.includes(`<@${this.botUserId}>`) &&
111+
!state.recentMessages?.includes(memory.id)
112+
) {
113+
elizaLogger.debug("🛑 [CONTINUE] Removing CONTINUE action as message is not a direct interaction");
114+
delete response.action;
38115
}
39116

117+
return response;
118+
}
119+
120+
public async handleMessage(event: any) {
121+
elizaLogger.debug("📥 [DETAILED] Incoming message event:", JSON.stringify(event, null, 2));
122+
40123
try {
41-
// Check if this is a direct mention or a message in a channel where the bot is mentioned
42-
const isMention = event.type === 'app_mention' ||
43-
(event.text && event.text.includes(`<@${this.botUserId}>`));
44-
45-
// Skip if it's not a mention and not in a direct message
46-
if (!isMention && event.channel_type !== 'im') {
47-
elizaLogger.debug("⏭️ [SKIP] Not a mention or direct message");
124+
// Generate a unique key for this message that includes all relevant data
125+
const messageKey = `${event.channel}-${event.ts}-${event.type}-${event.text}`;
126+
const currentTime = Date.now();
127+
128+
// Check if we've already processed this message
129+
if (this.processedMessages.has(messageKey)) {
130+
elizaLogger.debug("⏭️ [SKIP] Message already processed:", {
131+
key: messageKey,
132+
originalTimestamp: this.processedMessages.get(messageKey),
133+
currentTime
134+
});
48135
return;
49136
}
50137

51-
elizaLogger.debug("🎯 [CONTEXT] Message details:", {
52-
is_mention: isMention,
53-
channel_type: event.channel_type,
54-
thread_ts: event.thread_ts,
55-
text: event.text,
56-
channel: event.channel,
57-
subtype: event.subtype,
58-
event_type: event.type
138+
// Add to processed messages map with current timestamp
139+
this.processedMessages.set(messageKey, currentTime);
140+
elizaLogger.debug("✨ [NEW] Processing new message:", {
141+
key: messageKey,
142+
timestamp: currentTime
59143
});
60144

61-
// Clean the message text by removing the bot mention
145+
// Ignore messages from bots (including ourselves)
146+
if (event.bot_id || event.user === this.botUserId) {
147+
elizaLogger.debug("⏭️ [SKIP] Message from bot or self:", {
148+
bot_id: event.bot_id,
149+
user: event.user,
150+
bot_user_id: this.botUserId
151+
});
152+
return;
153+
}
154+
155+
// Clean the message text
62156
const cleanedText = this.cleanMessage(event.text || '');
63-
elizaLogger.debug("🧹 [CLEAN] Cleaned message text:", cleanedText);
64157

65158
// Generate unique IDs for the conversation
66159
const roomId = stringToUuid(`${event.channel}-${this.runtime.agentId}`);
@@ -82,8 +175,6 @@ export class MessageManager {
82175
'slack'
83176
);
84177

85-
elizaLogger.debug("🔌 [CONNECTION] Connection ensured for user");
86-
87178
// Create memory for the message
88179
const content: Content = {
89180
text: cleanedText,
@@ -110,8 +201,8 @@ export class MessageManager {
110201
await this.runtime.messageManager.createMemory(memory);
111202
}
112203

113-
// Compose state for response generation
114-
const state = await this.runtime.composeState(
204+
// Initial state composition
205+
let state = await this.runtime.composeState(
115206
{ content, userId, agentId: this.runtime.agentId, roomId },
116207
{
117208
slackClient: this.client,
@@ -121,36 +212,27 @@ export class MessageManager {
121212
}
122213
);
123214

215+
// Update state with recent messages
216+
state = await this.runtime.updateRecentMessageState(state);
217+
124218
elizaLogger.debug("🔄 [STATE] Composed state:", {
125219
agentName: state.agentName,
126220
senderName: state.senderName,
127221
roomId: state.roomId,
128222
recentMessages: state.recentMessages?.length || 0
129223
});
130224

131-
// Always respond to direct mentions and direct messages
132-
const shouldRespond = isMention || event.channel_type === 'im' ? 'RESPOND' : 'IGNORE';
133-
134-
elizaLogger.debug("✅ [DECISION] Should respond:", {
135-
decision: shouldRespond,
136-
isMention,
137-
channelType: event.channel_type
138-
});
225+
// Check if we should respond
226+
const shouldRespond = await this._shouldRespond(event, state);
139227

140-
if (shouldRespond === 'RESPOND') {
141-
elizaLogger.debug("💭 [GENERATE] Generating response...");
142-
228+
if (shouldRespond) {
143229
// Generate response using message handler template
144230
const context = composeContext({
145231
state,
146232
template: this.runtime.character.templates?.slackMessageHandlerTemplate || slackMessageHandlerTemplate,
147233
});
148234

149-
const responseContent = await generateMessageResponse({
150-
runtime: this.runtime,
151-
context,
152-
modelClass: ModelClass.SMALL,
153-
});
235+
const responseContent = await this._generateResponse(memory, state, context);
154236

155237
elizaLogger.debug("📝 [RESPONSE] Generated response content:", {
156238
hasText: !!responseContent?.text,
@@ -182,52 +264,63 @@ export class MessageManager {
182264
text_length: responseContent.text.length
183265
});
184266

185-
await this.sendMessage(event.channel, responseContent.text, event.thread_ts);
186-
elizaLogger.debug("✉️ [SUCCESS] Response sent successfully");
267+
const callback: HandlerCallback = async (content: Content) => {
268+
try {
269+
const response = await this.client.chat.postMessage({
270+
channel: event.channel,
271+
text: content.text,
272+
thread_ts: event.thread_ts
273+
});
274+
275+
const responseMemory: Memory = {
276+
id: stringToUuid(`${response.ts}-${this.runtime.agentId}`),
277+
userId: this.runtime.agentId,
278+
agentId: this.runtime.agentId,
279+
roomId,
280+
content: {
281+
...content,
282+
inReplyTo: messageId,
283+
},
284+
createdAt: Date.now(),
285+
embedding: getEmbeddingZeroVector(),
286+
};
287+
288+
await this.runtime.messageManager.createMemory(responseMemory);
289+
return [responseMemory];
290+
} catch (error) {
291+
elizaLogger.error("Error sending message:", error);
292+
return [];
293+
}
294+
};
295+
296+
const responseMessages = await callback(responseContent);
297+
298+
// Update state with new messages
299+
state = await this.runtime.updateRecentMessageState(state);
300+
301+
// Process any actions
302+
await this.runtime.processActions(
303+
memory,
304+
responseMessages,
305+
state,
306+
callback
307+
);
187308
}
188-
} else {
189-
elizaLogger.debug("⏭️ [SKIP] Skipping response based on shouldRespond:", shouldRespond);
190309
}
191-
} catch (error) {
192-
elizaLogger.error("❌ [ERROR] Error handling message:", error);
193-
await this.sendMessage(
194-
event.channel,
195-
"Sorry, I encountered an error processing your message.",
196-
event.thread_ts
197-
);
198-
}
199-
}
200-
201-
private cleanMessage(text: string): string {
202-
elizaLogger.debug("🧼 [CLEAN] Cleaning message:", text);
203-
// Remove mention of the bot
204-
const cleaned = text.replace(new RegExp(`<@${this.botUserId}>`, 'g'), '').trim();
205-
elizaLogger.debug("✨ [CLEAN] Cleaned result:", cleaned);
206-
return cleaned;
207-
}
208310

209-
private async sendMessage(channel: string, text: string, thread_ts?: string) {
210-
elizaLogger.debug("📤 [SEND] Sending message:", {
211-
channel,
212-
text_length: text.length,
213-
thread_ts: thread_ts || 'none'
214-
});
311+
// Evaluate the interaction
312+
await this.runtime.evaluate(memory, state, shouldRespond);
215313

216-
try {
217-
const response = await this.client.chat.postMessage({
218-
channel,
219-
text,
220-
thread_ts,
314+
} catch (error) {
315+
elizaLogger.error("❌ [ERROR] Error handling message:", {
316+
error: error instanceof Error ? error.message : String(error),
317+
stack: error instanceof Error ? error.stack : undefined
221318
});
222-
elizaLogger.debug("📨 [SEND] Message sent successfully:", {
223-
ts: response.ts,
224-
channel: response.channel,
225-
ok: response.ok
319+
await this.client.chat.postMessage({
320+
channel: event.channel,
321+
text: "Sorry, I encountered an error processing your message.",
322+
thread_ts: event.thread_ts
226323
});
227-
return response;
228-
} catch (error) {
229-
elizaLogger.error("❌ [ERROR] Failed to send message:", error);
230-
throw error;
231324
}
232325
}
233326
}

0 commit comments

Comments
 (0)