mom: fix attachment downloads - pass store to SlackBot, process files, use absolute paths

This commit is contained in:
Mario Zechner 2025-12-11 23:55:49 +01:00
parent d277d739b3
commit e3576fe016
4 changed files with 246 additions and 107 deletions

View file

@ -576,9 +576,9 @@ function createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDi
// Format: "[username]: message" so LLM knows who's talking
let userMessage = `[${ctx.message.userName || "unknown"}]: ${ctx.message.text}`;
// Add attachment paths if any
// Add attachment paths if any (convert to absolute paths in execution environment)
if (ctx.message.attachments && ctx.message.attachments.length > 0) {
const attachmentPaths = ctx.message.attachments.map((a) => a.local).join("\n");
const attachmentPaths = ctx.message.attachments.map((a) => `${workspacePath}/${a.local}`).join("\n");
userMessage += `\n\nAttachments:\n${attachmentPaths}`;
}

View file

@ -100,7 +100,7 @@ function createSlackContext(event: SlackEvent, slack: SlackBot, state: ChannelSt
userName: user?.userName,
channel: event.channel,
ts: event.ts,
attachments: [],
attachments: (event.attachments || []).map((a) => ({ local: a.local })),
},
channelName: slack.getChannel(event.channel)?.name,
store: state.store,
@ -243,10 +243,14 @@ const handler: MomHandler = {
log.logStartup(workingDir, sandbox.type === "host" ? "host" : `docker:${sandbox.container}`);
// Shared store for attachment downloads (also used per-channel in getState)
const sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! });
const bot = new SlackBotClass(handler, {
appToken: MOM_SLACK_APP_TOKEN,
botToken: MOM_SLACK_BOT_TOKEN,
workingDir,
store: sharedStore,
});
bot.start();

View file

@ -3,6 +3,7 @@ import { WebClient } from "@slack/web-api";
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
import { basename, join } from "path";
import * as log from "./log.js";
import type { Attachment, ChannelStore } from "./store.js";
// ============================================================================
// Types
@ -14,7 +15,9 @@ export interface SlackEvent {
ts: string;
user: string;
text: string;
files?: Array<{ name: string; url_private_download?: string; url_private?: string }>;
files?: Array<{ name?: string; url_private_download?: string; url_private?: string }>;
/** Processed attachments with local paths (populated after logUserMessage) */
attachments?: Attachment[];
}
export interface SlackUser {
@ -118,6 +121,7 @@ export class SlackBot {
private webClient: WebClient;
private handler: MomHandler;
private workingDir: string;
private store: ChannelStore;
private botUserId: string | null = null;
private startupTs: string | null = null; // Messages older than this are just logged, not processed
@ -125,9 +129,13 @@ export class SlackBot {
private channels = new Map<string, SlackChannel>();
private queues = new Map<string, ChannelQueue>();
constructor(handler: MomHandler, config: { appToken: string; botToken: string; workingDir: string }) {
constructor(
handler: MomHandler,
config: { appToken: string; botToken: string; workingDir: string; store: ChannelStore },
) {
this.handler = handler;
this.workingDir = config.workingDir;
this.store = config.store;
this.socketClient = new SocketModeClient({ appToken: config.appToken });
this.webClient = new WebClient(config.botToken);
}
@ -258,7 +266,8 @@ export class SlackBot {
};
// SYNC: Log to log.jsonl (ALWAYS, even for old messages)
this.logUserMessage(slackEvent);
// Also downloads attachments in background and stores local paths
slackEvent.attachments = this.logUserMessage(slackEvent);
// Only trigger processing for messages AFTER startup (not replayed old messages)
if (this.startupTs && e.ts < this.startupTs) {
@ -336,7 +345,8 @@ export class SlackBot {
};
// SYNC: Log to log.jsonl (ALL messages - channel chatter and DMs)
this.logUserMessage(slackEvent);
// Also downloads attachments in background and stores local paths
slackEvent.attachments = this.logUserMessage(slackEvent);
// Only trigger processing for messages AFTER startup (not replayed old messages)
if (this.startupTs && e.ts < this.startupTs) {
@ -371,9 +381,12 @@ export class SlackBot {
/**
* Log a user message to log.jsonl (SYNC)
* Downloads attachments in background via store
*/
private logUserMessage(event: SlackEvent): void {
private logUserMessage(event: SlackEvent): Attachment[] {
const user = this.users.get(event.user);
// Process attachments - queues downloads in background
const attachments = event.files ? this.store.processAttachments(event.channel, event.files, event.ts) : [];
this.logToFile(event.channel, {
date: new Date(parseFloat(event.ts) * 1000).toISOString(),
ts: event.ts,
@ -381,9 +394,10 @@ export class SlackBot {
userName: user?.userName,
displayName: user?.displayName,
text: event.text,
attachments: event.files?.map((f) => f.name) || [],
attachments,
isBot: false,
});
return attachments;
}
// ==========================================================================
@ -464,6 +478,8 @@ export class SlackBot {
const user = this.users.get(msg.user!);
// Strip @mentions from text (same as live messages)
const text = (msg.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim();
// Process attachments - queues downloads in background
const attachments = msg.files ? this.store.processAttachments(channelId, msg.files, msg.ts!) : [];
this.logToFile(channelId, {
date: new Date(parseFloat(msg.ts!) * 1000).toISOString(),
@ -472,7 +488,7 @@ export class SlackBot {
userName: isMomMessage ? undefined : user?.userName,
displayName: isMomMessage ? undefined : user?.displayName,
text,
attachments: msg.files?.map((f) => f.name) || [],
attachments,
isBot: isMomMessage,
});
}