@geminixiang/mama 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +60 -9
  3. package/dist/adapter.d.ts +50 -0
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js.map +1 -1
  6. package/dist/adapters/discord/bot.d.ts +47 -0
  7. package/dist/adapters/discord/bot.d.ts.map +1 -0
  8. package/dist/adapters/discord/bot.js +292 -0
  9. package/dist/adapters/discord/bot.js.map +1 -0
  10. package/dist/adapters/discord/context.d.ts +9 -0
  11. package/dist/adapters/discord/context.d.ts.map +1 -0
  12. package/dist/adapters/discord/context.js +148 -0
  13. package/dist/adapters/discord/context.js.map +1 -0
  14. package/dist/adapters/discord/index.d.ts +3 -0
  15. package/dist/adapters/discord/index.d.ts.map +1 -0
  16. package/dist/adapters/discord/index.js +3 -0
  17. package/dist/adapters/discord/index.js.map +1 -0
  18. package/dist/adapters/slack/bot.d.ts +7 -21
  19. package/dist/adapters/slack/bot.d.ts.map +1 -1
  20. package/dist/adapters/slack/bot.js +21 -3
  21. package/dist/adapters/slack/bot.js.map +1 -1
  22. package/dist/adapters/slack/context.d.ts +1 -3
  23. package/dist/adapters/slack/context.d.ts.map +1 -1
  24. package/dist/adapters/slack/context.js.map +1 -1
  25. package/dist/adapters/telegram/bot.d.ts +35 -0
  26. package/dist/adapters/telegram/bot.d.ts.map +1 -0
  27. package/dist/adapters/telegram/bot.js +234 -0
  28. package/dist/adapters/telegram/bot.js.map +1 -0
  29. package/dist/adapters/telegram/context.d.ts +9 -0
  30. package/dist/adapters/telegram/context.d.ts.map +1 -0
  31. package/dist/adapters/telegram/context.js +144 -0
  32. package/dist/adapters/telegram/context.js.map +1 -0
  33. package/dist/adapters/telegram/index.d.ts +3 -0
  34. package/dist/adapters/telegram/index.d.ts.map +1 -0
  35. package/dist/adapters/telegram/index.js +3 -0
  36. package/dist/adapters/telegram/index.js.map +1 -0
  37. package/dist/events.d.ts +4 -4
  38. package/dist/events.d.ts.map +1 -1
  39. package/dist/events.js +7 -7
  40. package/dist/events.js.map +1 -1
  41. package/dist/main.d.ts.map +1 -1
  42. package/dist/main.js +55 -36
  43. package/dist/main.js.map +1 -1
  44. package/package.json +6 -2
package/dist/main.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { join, resolve } from "path";
3
- import { createSlackAdapters, SlackBot as SlackBotClass, } from "./adapters/slack/index.js";
3
+ import { DiscordBot } from "./adapters/discord/index.js";
4
+ import { TelegramBot } from "./adapters/telegram/index.js";
5
+ import { SlackBot as SlackBotClass } from "./adapters/slack/index.js";
4
6
  import { createRunner } from "./agent.js";
5
7
  import { downloadChannel } from "./download.js";
6
8
  import { createEventsWatcher } from "./events.js";
@@ -12,6 +14,8 @@ import { ChannelStore } from "./store.js";
12
14
  // ============================================================================
13
15
  const MOM_SLACK_APP_TOKEN = process.env.MOM_SLACK_APP_TOKEN;
14
16
  const MOM_SLACK_BOT_TOKEN = process.env.MOM_SLACK_BOT_TOKEN;
17
+ const MOM_TELEGRAM_BOT_TOKEN = process.env.MOM_TELEGRAM_BOT_TOKEN;
18
+ const MOM_DISCORD_BOT_TOKEN = process.env.MOM_DISCORD_BOT_TOKEN;
15
19
  function parseArgs() {
16
20
  const args = process.argv.slice(2);
17
21
  let sandbox = { type: "host" };
@@ -42,7 +46,7 @@ function parseArgs() {
42
46
  };
43
47
  }
44
48
  const parsedArgs = parseArgs();
45
- // Handle --download mode
49
+ // Handle --download mode (Slack only)
46
50
  if (parsedArgs.downloadChannel) {
47
51
  if (!MOM_SLACK_BOT_TOKEN) {
48
52
  console.error("Missing env: MOM_SLACK_BOT_TOKEN");
@@ -58,8 +62,15 @@ if (!parsedArgs.workingDir) {
58
62
  process.exit(1);
59
63
  }
60
64
  const { workingDir, sandbox } = { workingDir: parsedArgs.workingDir, sandbox: parsedArgs.sandbox };
61
- if (!MOM_SLACK_APP_TOKEN || !MOM_SLACK_BOT_TOKEN) {
62
- console.error("Missing env: MOM_SLACK_APP_TOKEN, MOM_SLACK_BOT_TOKEN");
65
+ // Validate platform tokens
66
+ const hasSlack = !!(MOM_SLACK_APP_TOKEN && MOM_SLACK_BOT_TOKEN);
67
+ const hasTelegram = !!MOM_TELEGRAM_BOT_TOKEN;
68
+ const hasDiscord = !!MOM_DISCORD_BOT_TOKEN;
69
+ if (!hasSlack && !hasTelegram && !hasDiscord) {
70
+ console.error("No platform tokens found. Set one of:\n" +
71
+ " Slack: MOM_SLACK_APP_TOKEN + MOM_SLACK_BOT_TOKEN\n" +
72
+ " Telegram: MOM_TELEGRAM_BOT_TOKEN\n" +
73
+ " Discord: MOM_DISCORD_BOT_TOKEN");
63
74
  process.exit(1);
64
75
  }
65
76
  await validateSandbox(sandbox);
@@ -93,32 +104,22 @@ async function getState(channelId, sessionKey) {
93
104
  /**
94
105
  * Evict idle sessions from channelStates to bound memory usage.
95
106
  * Called after each handleEvent completes.
96
- *
97
- * Eviction rules:
98
- * - Never evict sessions that are currently running
99
- * - Evict sessions idle for more than IDLE_TIMEOUT_MS
100
- * - If still over MAX_SESSIONS, evict oldest idle sessions first
101
107
  */
102
108
  function evictIdleSessions() {
103
109
  const now = Date.now();
104
- // First pass: evict sessions that are idle and past the timeout
105
110
  for (const [key, state] of channelStates) {
106
111
  if (!state.running && now - state.lastAccessedAt > IDLE_TIMEOUT_MS) {
107
112
  channelStates.delete(key);
108
113
  }
109
114
  }
110
- // Second pass: if still over capacity, evict oldest idle sessions
111
115
  if (channelStates.size > MAX_SESSIONS) {
112
- // Collect all non-running sessions with their last access time
113
116
  const idleSessions = [];
114
117
  for (const [key, state] of channelStates) {
115
118
  if (!state.running) {
116
119
  idleSessions.push({ key, lastAccessedAt: state.lastAccessedAt });
117
120
  }
118
121
  }
119
- // Sort oldest first
120
122
  idleSessions.sort((a, b) => a.lastAccessedAt - b.lastAccessedAt);
121
- // Evict until under capacity
122
123
  const toEvict = channelStates.size - MAX_SESSIONS;
123
124
  for (let i = 0; i < toEvict && i < idleSessions.length; i++) {
124
125
  channelStates.delete(idleSessions[i].key);
@@ -133,19 +134,19 @@ const handler = {
133
134
  const state = channelStates.get(sessionKey);
134
135
  return state?.running ?? false;
135
136
  },
136
- async handleStop(sessionKey, channelId, slack) {
137
+ async handleStop(sessionKey, channelId, bot) {
137
138
  const state = channelStates.get(sessionKey);
138
139
  if (state?.running) {
139
140
  state.stopRequested = true;
140
141
  state.runner.abort();
141
- const ts = await slack.postMessage(channelId, "_Stopping..._");
142
- state.stopMessageTs = ts; // Save for updating later
142
+ const ts = await bot.postMessage(channelId, "_Stopping..._");
143
+ state.stopMessageTs = ts;
143
144
  }
144
145
  else {
145
- await slack.postMessage(channelId, "_Nothing running_");
146
+ await bot.postMessage(channelId, "_Nothing running_");
146
147
  }
147
148
  },
148
- async handleEvent(event, slack, isEvent) {
149
+ async handleEvent(event, bot, adapters, isEvent) {
149
150
  // Don't accept new events during shutdown
150
151
  if (isShuttingDown) {
151
152
  log.logInfo(`[${event.channel}] Rejected event during shutdown: ${event.text.substring(0, 50)}`);
@@ -160,8 +161,7 @@ const handler = {
160
161
  // Wrap in-flight run tracking
161
162
  const runPromise = (async () => {
162
163
  try {
163
- // Create platform-agnostic adapter objects
164
- const { message, responseCtx, platform } = createSlackAdapters(event, slack, isEvent);
164
+ const { message, responseCtx, platform } = adapters;
165
165
  // Run the agent
166
166
  await responseCtx.setTyping(true);
167
167
  await responseCtx.setWorking(true);
@@ -169,11 +169,11 @@ const handler = {
169
169
  await responseCtx.setWorking(false);
170
170
  if (result.stopReason === "aborted" && state.stopRequested) {
171
171
  if (state.stopMessageTs) {
172
- await slack.updateMessage(event.channel, state.stopMessageTs, "_Stopped_");
172
+ await bot.updateMessage(event.channel, state.stopMessageTs, "_Stopped_");
173
173
  state.stopMessageTs = undefined;
174
174
  }
175
175
  else {
176
- await slack.postMessage(event.channel, "_Stopped_");
176
+ await bot.postMessage(event.channel, "_Stopped_");
177
177
  }
178
178
  }
179
179
  }
@@ -199,24 +199,41 @@ const handler = {
199
199
  // Start
200
200
  // ============================================================================
201
201
  log.logStartup(workingDir, sandbox.type === "host" ? "host" : `docker:${sandbox.container}`);
202
- // Shared store for attachment downloads (also used per-channel in getState)
203
- const sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN });
204
- const bot = new SlackBotClass(handler, {
205
- appToken: MOM_SLACK_APP_TOKEN,
206
- botToken: MOM_SLACK_BOT_TOKEN,
207
- workingDir,
208
- store: sharedStore,
209
- });
202
+ // Create the appropriate platform bot
203
+ let bot;
204
+ if (hasSlack) {
205
+ const sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN });
206
+ bot = new SlackBotClass(handler, {
207
+ appToken: MOM_SLACK_APP_TOKEN,
208
+ botToken: MOM_SLACK_BOT_TOKEN,
209
+ workingDir,
210
+ store: sharedStore,
211
+ });
212
+ log.logInfo("Platform: Slack");
213
+ }
214
+ else if (hasTelegram) {
215
+ bot = new TelegramBot(handler, {
216
+ token: MOM_TELEGRAM_BOT_TOKEN,
217
+ workingDir,
218
+ });
219
+ log.logInfo("Platform: Telegram");
220
+ }
221
+ else {
222
+ bot = new DiscordBot(handler, {
223
+ token: MOM_DISCORD_BOT_TOKEN,
224
+ workingDir,
225
+ });
226
+ log.logInfo("Platform: Discord");
227
+ }
210
228
  // Start events watcher
211
229
  const eventsWatcher = createEventsWatcher(workingDir, bot);
212
230
  eventsWatcher.start();
213
231
  // Handle shutdown
214
232
  process.on("SIGINT", async () => {
215
233
  if (isShuttingDown)
216
- return; // Prevent duplicate signals
234
+ return;
217
235
  isShuttingDown = true;
218
236
  log.logInfo("Shutting down gracefully...");
219
- // Wait for in-flight runs (max 30 seconds)
220
237
  const timeout = Date.now() + 30000;
221
238
  while (inFlightRuns.size > 0 && Date.now() < timeout) {
222
239
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -229,10 +246,9 @@ process.on("SIGINT", async () => {
229
246
  });
230
247
  process.on("SIGTERM", async () => {
231
248
  if (isShuttingDown)
232
- return; // Prevent duplicate signals
249
+ return;
233
250
  isShuttingDown = true;
234
251
  log.logInfo("Shutting down gracefully...");
235
- // Wait for in-flight runs (max 30 seconds)
236
252
  const timeout = Date.now() + 30000;
237
253
  while (inFlightRuns.size > 0 && Date.now() < timeout) {
238
254
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -243,5 +259,8 @@ process.on("SIGTERM", async () => {
243
259
  eventsWatcher.stop();
244
260
  process.exit(0);
245
261
  });
246
- bot.start();
262
+ bot.start().catch((err) => {
263
+ log.logWarning("Failed to start bot", err instanceof Error ? err.message : String(err));
264
+ process.exit(1);
265
+ });
247
266
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { join, resolve } from \"path\";\nimport {\n\tcreateSlackAdapters,\n\ttype MomHandler,\n\ttype SlackBot,\n\tSlackBot as SlackBotClass,\n\ttype SlackEvent,\n} from \"./adapters/slack/index.js\";\nimport { type AgentRunner, createRunner } from \"./agent.js\";\nimport { downloadChannel } from \"./download.js\";\nimport { createEventsWatcher } from \"./events.js\";\nimport * as log from \"./log.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { ChannelStore } from \"./store.js\";\n\n// ============================================================================\n// Config\n// ============================================================================\n\nconst MOM_SLACK_APP_TOKEN = process.env.MOM_SLACK_APP_TOKEN;\nconst MOM_SLACK_BOT_TOKEN = process.env.MOM_SLACK_BOT_TOKEN;\n\ninterface ParsedArgs {\n\tworkingDir?: string;\n\tsandbox: SandboxConfig;\n\tdownloadChannel?: string;\n}\n\nfunction parseArgs(): ParsedArgs {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\tlet workingDir: string | undefined;\n\tlet downloadChannelId: string | undefined;\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (arg.startsWith(\"--download=\")) {\n\t\t\tdownloadChannelId = arg.slice(\"--download=\".length);\n\t\t} else if (arg === \"--download\") {\n\t\t\tdownloadChannelId = args[++i];\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tworkingDir = arg;\n\t\t}\n\t}\n\n\treturn {\n\t\tworkingDir: workingDir ? resolve(workingDir) : undefined,\n\t\tsandbox,\n\t\tdownloadChannel: downloadChannelId,\n\t};\n}\n\nconst parsedArgs = parseArgs();\n\n// Handle --download mode\nif (parsedArgs.downloadChannel) {\n\tif (!MOM_SLACK_BOT_TOKEN) {\n\t\tconsole.error(\"Missing env: MOM_SLACK_BOT_TOKEN\");\n\t\tprocess.exit(1);\n\t}\n\tawait downloadChannel(parsedArgs.downloadChannel, MOM_SLACK_BOT_TOKEN);\n\tprocess.exit(0);\n}\n\n// Normal bot mode - require working dir\nif (!parsedArgs.workingDir) {\n\tconsole.error(\"Usage: mama [--sandbox=host|docker:<name>] <working-directory>\");\n\tconsole.error(\" mama --download <channel-id>\");\n\tprocess.exit(1);\n}\n\nconst { workingDir, sandbox } = { workingDir: parsedArgs.workingDir, sandbox: parsedArgs.sandbox };\n\nif (!MOM_SLACK_APP_TOKEN || !MOM_SLACK_BOT_TOKEN) {\n\tconsole.error(\"Missing env: MOM_SLACK_APP_TOKEN, MOM_SLACK_BOT_TOKEN\");\n\tprocess.exit(1);\n}\n\nawait validateSandbox(sandbox);\n\n// ============================================================================\n// State (per channel)\n// ============================================================================\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstopRequested: boolean;\n\tstopMessageTs?: string;\n\tlastAccessedAt: number;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\n/** Track in-flight runs for graceful shutdown */\nconst inFlightRuns = new Set<Promise<void>>();\n\n/** Flag to stop accepting new events during shutdown */\nlet isShuttingDown = false;\n\n/** Maximum number of cached sessions */\nconst MAX_SESSIONS = 500;\n/** Idle timeout before a non-running session can be evicted (1 hour) */\nconst IDLE_TIMEOUT_MS = 3600000;\n\nasync function getState(channelId: string, sessionKey?: string): Promise<ChannelState> {\n\tconst key = sessionKey ?? channelId;\n\tlet state = channelStates.get(key);\n\tif (!state) {\n\t\tconst channelDir = join(workingDir, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: await createRunner(sandbox, key, channelId, channelDir, workingDir),\n\t\t\tstopRequested: false,\n\t\t\tlastAccessedAt: Date.now(),\n\t\t};\n\t\tchannelStates.set(key, state);\n\t} else {\n\t\tstate.lastAccessedAt = Date.now();\n\t}\n\treturn state;\n}\n\n/**\n * Evict idle sessions from channelStates to bound memory usage.\n * Called after each handleEvent completes.\n *\n * Eviction rules:\n * - Never evict sessions that are currently running\n * - Evict sessions idle for more than IDLE_TIMEOUT_MS\n * - If still over MAX_SESSIONS, evict oldest idle sessions first\n */\nfunction evictIdleSessions(): void {\n\tconst now = Date.now();\n\n\t// First pass: evict sessions that are idle and past the timeout\n\tfor (const [key, state] of channelStates) {\n\t\tif (!state.running && now - state.lastAccessedAt > IDLE_TIMEOUT_MS) {\n\t\t\tchannelStates.delete(key);\n\t\t}\n\t}\n\n\t// Second pass: if still over capacity, evict oldest idle sessions\n\tif (channelStates.size > MAX_SESSIONS) {\n\t\t// Collect all non-running sessions with their last access time\n\t\tconst idleSessions: Array<{ key: string; lastAccessedAt: number }> = [];\n\t\tfor (const [key, state] of channelStates) {\n\t\t\tif (!state.running) {\n\t\t\t\tidleSessions.push({ key, lastAccessedAt: state.lastAccessedAt });\n\t\t\t}\n\t\t}\n\n\t\t// Sort oldest first\n\t\tidleSessions.sort((a, b) => a.lastAccessedAt - b.lastAccessedAt);\n\n\t\t// Evict until under capacity\n\t\tconst toEvict = channelStates.size - MAX_SESSIONS;\n\t\tfor (let i = 0; i < toEvict && i < idleSessions.length; i++) {\n\t\t\tchannelStates.delete(idleSessions[i].key);\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Handler\n// ============================================================================\n\nconst handler: MomHandler = {\n\tisRunning(sessionKey: string): boolean {\n\t\tconst state = channelStates.get(sessionKey);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(sessionKey: string, channelId: string, slack: SlackBot): Promise<void> {\n\t\tconst state = channelStates.get(sessionKey);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tconst ts = await slack.postMessage(channelId, \"_Stopping..._\");\n\t\t\tstate.stopMessageTs = ts; // Save for updating later\n\t\t} else {\n\t\t\tawait slack.postMessage(channelId, \"_Nothing running_\");\n\t\t}\n\t},\n\n\tasync handleEvent(event: SlackEvent, slack: SlackBot, isEvent?: boolean): Promise<void> {\n\t\t// Don't accept new events during shutdown\n\t\tif (isShuttingDown) {\n\t\t\tlog.logInfo(`[${event.channel}] Rejected event during shutdown: ${event.text.substring(0, 50)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst sessionKey = `${event.channel}:${event.thread_ts ?? event.ts}`;\n\t\tconst state = await getState(event.channel, sessionKey);\n\n\t\t// Start run\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tlog.logInfo(`[${event.channel}] Starting run: ${event.text.substring(0, 50)}`);\n\n\t\t// Wrap in-flight run tracking\n\t\tconst runPromise = (async () => {\n\t\t\ttry {\n\t\t\t\t// Create platform-agnostic adapter objects\n\t\t\t\tconst { message, responseCtx, platform } = createSlackAdapters(event, slack, isEvent);\n\n\t\t\t\t// Run the agent\n\t\t\t\tawait responseCtx.setTyping(true);\n\t\t\t\tawait responseCtx.setWorking(true);\n\t\t\t\tconst result = await state.runner.run(message, responseCtx, platform);\n\t\t\t\tawait responseCtx.setWorking(false);\n\n\t\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\t\tif (state.stopMessageTs) {\n\t\t\t\t\t\tawait slack.updateMessage(event.channel, state.stopMessageTs, \"_Stopped_\");\n\t\t\t\t\t\tstate.stopMessageTs = undefined;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait slack.postMessage(event.channel, \"_Stopped_\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tlog.logWarning(`[${event.channel}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t\t} finally {\n\t\t\t\tstate.running = false;\n\t\t\t\tstate.lastAccessedAt = Date.now();\n\t\t\t\tevictIdleSessions();\n\t\t\t}\n\t\t})();\n\n\t\tinFlightRuns.add(runPromise);\n\t\ttry {\n\t\t\tawait runPromise;\n\t\t} finally {\n\t\t\tinFlightRuns.delete(runPromise);\n\t\t}\n\t},\n};\n\n// ============================================================================\n// Start\n// ============================================================================\n\nlog.logStartup(workingDir, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\n// Shared store for attachment downloads (also used per-channel in getState)\nconst sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! });\n\nconst bot = new SlackBotClass(handler, {\n\tappToken: MOM_SLACK_APP_TOKEN,\n\tbotToken: MOM_SLACK_BOT_TOKEN,\n\tworkingDir,\n\tstore: sharedStore,\n});\n\n// Start events watcher\nconst eventsWatcher = createEventsWatcher(workingDir, bot);\neventsWatcher.start();\n\n// Handle shutdown\nprocess.on(\"SIGINT\", async () => {\n\tif (isShuttingDown) return; // Prevent duplicate signals\n\tisShuttingDown = true;\n\tlog.logInfo(\"Shutting down gracefully...\");\n\n\t// Wait for in-flight runs (max 30 seconds)\n\tconst timeout = Date.now() + 30000;\n\twhile (inFlightRuns.size > 0 && Date.now() < timeout) {\n\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t}\n\n\tif (inFlightRuns.size > 0) {\n\t\tlog.logWarning(`Forcing exit with ${inFlightRuns.size} runs still in progress`);\n\t}\n\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nprocess.on(\"SIGTERM\", async () => {\n\tif (isShuttingDown) return; // Prevent duplicate signals\n\tisShuttingDown = true;\n\tlog.logInfo(\"Shutting down gracefully...\");\n\n\t// Wait for in-flight runs (max 30 seconds)\n\tconst timeout = Date.now() + 30000;\n\twhile (inFlightRuns.size > 0 && Date.now() < timeout) {\n\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t}\n\n\tif (inFlightRuns.size > 0) {\n\t\tlog.logWarning(`Forcing exit with ${inFlightRuns.size} runs still in progress`);\n\t}\n\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nbot.start();\n"]}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAoB,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,eAAe,EAAsB,eAAe,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;AAC5D,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;AAC5D,MAAM,sBAAsB,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;AAClE,MAAM,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;AAQhE,SAAS,SAAS,GAAe;IAChC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,OAAO,GAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC9C,IAAI,UAA8B,CAAC;IACnC,IAAI,iBAAqC,CAAC;IAE1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAClC,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3D,CAAC;aAAM,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YAChC,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5C,CAAC;aAAM,IAAI,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YAC1C,iBAAiB,GAAG,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACrD,CAAC;aAAM,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YACjC,iBAAiB,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,UAAU,GAAG,GAAG,CAAC;QAClB,CAAC;IACF,CAAC;IAED,OAAO;QACN,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;QACxD,OAAO;QACP,eAAe,EAAE,iBAAiB;KAClC,CAAC;AAAA,CACF;AAED,MAAM,UAAU,GAAG,SAAS,EAAE,CAAC;AAE/B,sCAAsC;AACtC,IAAI,UAAU,CAAC,eAAe,EAAE,CAAC;IAChC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,MAAM,eAAe,CAAC,UAAU,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAC;IACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,wCAAwC;AACxC,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;IAChF,OAAO,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC;AAEnG,2BAA2B;AAC3B,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,mBAAmB,IAAI,mBAAmB,CAAC,CAAC;AAChE,MAAM,WAAW,GAAG,CAAC,CAAC,sBAAsB,CAAC;AAC7C,MAAM,UAAU,GAAG,CAAC,CAAC,qBAAqB,CAAC;AAE3C,IAAI,CAAC,QAAQ,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,EAAE,CAAC;IAC9C,OAAO,CAAC,KAAK,CACZ,yCAAyC;QACxC,yDAAyD;QACzD,sCAAsC;QACtC,mCAAmC,CACpC,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;AAc/B,MAAM,aAAa,GAAG,IAAI,GAAG,EAAwB,CAAC;AAEtD,iDAAiD;AACjD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAiB,CAAC;AAE9C,wDAAwD;AACxD,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B,wCAAwC;AACxC,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,wEAAwE;AACxE,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC,KAAK,UAAU,QAAQ,CAAC,SAAiB,EAAE,UAAmB,EAAyB;IACtF,MAAM,GAAG,GAAG,UAAU,IAAI,SAAS,CAAC;IACpC,IAAI,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC/C,KAAK,GAAG;YACP,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,MAAM,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC;YAC3E,aAAa,EAAE,KAAK;YACpB,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE;SAC1B,CAAC;QACF,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;SAAM,CAAC;QACP,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACnC,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED;;;GAGG;AACH,SAAS,iBAAiB,GAAS;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,GAAG,GAAG,KAAK,CAAC,cAAc,GAAG,eAAe,EAAE,CAAC;YACpE,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;IACF,CAAC;IAED,IAAI,aAAa,CAAC,IAAI,GAAG,YAAY,EAAE,CAAC;QACvC,MAAM,YAAY,GAAmD,EAAE,CAAC;QACxE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,aAAa,EAAE,CAAC;YAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBACpB,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC;YAClE,CAAC;QACF,CAAC;QAED,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,cAAc,CAAC,CAAC;QAEjE,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,GAAG,YAAY,CAAC;QAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7D,aAAa,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3C,CAAC;IACF,CAAC;AAAA,CACD;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,MAAM,OAAO,GAAe;IAC3B,SAAS,CAAC,UAAkB,EAAW;QACtC,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,OAAO,KAAK,EAAE,OAAO,IAAI,KAAK,CAAC;IAAA,CAC/B;IAED,KAAK,CAAC,UAAU,CAAC,UAAkB,EAAE,SAAiB,EAAE,GAAQ,EAAiB;QAChF,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,KAAK,EAAE,OAAO,EAAE,CAAC;YACpB,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC;YAC3B,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACrB,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;YAC7D,KAAK,CAAC,aAAa,GAAG,EAAE,CAAC;QAC1B,CAAC;aAAM,CAAC;YACP,MAAM,GAAG,CAAC,WAAW,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;QACvD,CAAC;IAAA,CACD;IAED,KAAK,CAAC,WAAW,CAAC,KAAe,EAAE,GAAQ,EAAE,QAAqB,EAAE,OAAiB,EAAiB;QACrG,0CAA0C;QAC1C,IAAI,cAAc,EAAE,CAAC;YACpB,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,qCAAqC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;YACjG,OAAO;QACR,CAAC;QAED,MAAM,UAAU,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;QACrE,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAExD,YAAY;QACZ,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC;QAE5B,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,mBAAmB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QAE/E,8BAA8B;QAC9B,MAAM,UAAU,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACJ,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;gBAEpD,gBAAgB;gBAChB,MAAM,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;gBAClC,MAAM,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBACnC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;gBACtE,MAAM,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;gBAEpC,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;oBAC5D,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;wBACzB,MAAM,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;wBACzE,KAAK,CAAC,aAAa,GAAG,SAAS,CAAC;oBACjC,CAAC;yBAAM,CAAC;wBACP,MAAM,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;oBACnD,CAAC;gBACF,CAAC;YACF,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,GAAG,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,OAAO,aAAa,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAClG,CAAC;oBAAS,CAAC;gBACV,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;gBACtB,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAClC,iBAAiB,EAAE,CAAC;YACrB,CAAC;QAAA,CACD,CAAC,EAAE,CAAC;QAEL,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC7B,IAAI,CAAC;YACJ,MAAM,UAAU,CAAC;QAClB,CAAC;gBAAS,CAAC;YACV,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;IAAA,CACD;CACD,CAAC;AAEF,+EAA+E;AAC/E,QAAQ;AACR,+EAA+E;AAE/E,GAAG,CAAC,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;AAE7F,sCAAsC;AACtC,IAAI,GAAQ,CAAC;AAEb,IAAI,QAAQ,EAAE,CAAC;IACd,MAAM,WAAW,GAAG,IAAI,YAAY,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,mBAAoB,EAAE,CAAC,CAAC;IACrF,GAAG,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,mBAAoB;QAC9B,QAAQ,EAAE,mBAAoB;QAC9B,UAAU;QACV,KAAK,EAAE,WAAW;KAClB,CAAC,CAAC;IACH,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAChC,CAAC;KAAM,IAAI,WAAW,EAAE,CAAC;IACxB,GAAG,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE;QAC9B,KAAK,EAAE,sBAAuB;QAC9B,UAAU;KACV,CAAC,CAAC;IACH,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;AACnC,CAAC;KAAM,CAAC;IACP,GAAG,GAAG,IAAI,UAAU,CAAC,OAAO,EAAE;QAC7B,KAAK,EAAE,qBAAsB;QAC7B,UAAU;KACV,CAAC,CAAC;IACH,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAClC,CAAC;AAED,uBAAuB;AACvB,MAAM,aAAa,GAAG,mBAAmB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AAC3D,aAAa,CAAC,KAAK,EAAE,CAAC;AAEtB,kBAAkB;AAClB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE,CAAC;IAChC,IAAI,cAAc;QAAE,OAAO;IAC3B,cAAc,GAAG,IAAI,CAAC;IACtB,GAAG,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;IAE3C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;IACnC,OAAO,YAAY,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;QACtD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,YAAY,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,UAAU,CAAC,qBAAqB,YAAY,CAAC,IAAI,yBAAyB,CAAC,CAAC;IACjF,CAAC;IAED,aAAa,CAAC,IAAI,EAAE,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAAA,CAChB,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC;IACjC,IAAI,cAAc;QAAE,OAAO;IAC3B,cAAc,GAAG,IAAI,CAAC;IACtB,GAAG,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;IAE3C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;IACnC,OAAO,YAAY,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;QACtD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,YAAY,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,UAAU,CAAC,qBAAqB,YAAY,CAAC,IAAI,yBAAyB,CAAC,CAAC;IACjF,CAAC;IAED,aAAa,CAAC,IAAI,EAAE,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAAA,CAChB,CAAC,CAAC;AAEH,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;IAC1B,GAAG,CAAC,UAAU,CAAC,qBAAqB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACxF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAAA,CAChB,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\n\nimport { join, resolve } from \"path\";\nimport type { Bot, BotAdapters, BotEvent, BotHandler } from \"./adapter.js\";\nimport { DiscordBot } from \"./adapters/discord/index.js\";\nimport { TelegramBot } from \"./adapters/telegram/index.js\";\nimport { SlackBot as SlackBotClass } from \"./adapters/slack/index.js\";\nimport { type AgentRunner, createRunner } from \"./agent.js\";\nimport { downloadChannel } from \"./download.js\";\nimport { createEventsWatcher } from \"./events.js\";\nimport * as log from \"./log.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { ChannelStore } from \"./store.js\";\n\n// ============================================================================\n// Config\n// ============================================================================\n\nconst MOM_SLACK_APP_TOKEN = process.env.MOM_SLACK_APP_TOKEN;\nconst MOM_SLACK_BOT_TOKEN = process.env.MOM_SLACK_BOT_TOKEN;\nconst MOM_TELEGRAM_BOT_TOKEN = process.env.MOM_TELEGRAM_BOT_TOKEN;\nconst MOM_DISCORD_BOT_TOKEN = process.env.MOM_DISCORD_BOT_TOKEN;\n\ninterface ParsedArgs {\n\tworkingDir?: string;\n\tsandbox: SandboxConfig;\n\tdownloadChannel?: string;\n}\n\nfunction parseArgs(): ParsedArgs {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\tlet workingDir: string | undefined;\n\tlet downloadChannelId: string | undefined;\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (arg.startsWith(\"--download=\")) {\n\t\t\tdownloadChannelId = arg.slice(\"--download=\".length);\n\t\t} else if (arg === \"--download\") {\n\t\t\tdownloadChannelId = args[++i];\n\t\t} else if (!arg.startsWith(\"-\")) {\n\t\t\tworkingDir = arg;\n\t\t}\n\t}\n\n\treturn {\n\t\tworkingDir: workingDir ? resolve(workingDir) : undefined,\n\t\tsandbox,\n\t\tdownloadChannel: downloadChannelId,\n\t};\n}\n\nconst parsedArgs = parseArgs();\n\n// Handle --download mode (Slack only)\nif (parsedArgs.downloadChannel) {\n\tif (!MOM_SLACK_BOT_TOKEN) {\n\t\tconsole.error(\"Missing env: MOM_SLACK_BOT_TOKEN\");\n\t\tprocess.exit(1);\n\t}\n\tawait downloadChannel(parsedArgs.downloadChannel, MOM_SLACK_BOT_TOKEN);\n\tprocess.exit(0);\n}\n\n// Normal bot mode - require working dir\nif (!parsedArgs.workingDir) {\n\tconsole.error(\"Usage: mama [--sandbox=host|docker:<name>] <working-directory>\");\n\tconsole.error(\" mama --download <channel-id>\");\n\tprocess.exit(1);\n}\n\nconst { workingDir, sandbox } = { workingDir: parsedArgs.workingDir, sandbox: parsedArgs.sandbox };\n\n// Validate platform tokens\nconst hasSlack = !!(MOM_SLACK_APP_TOKEN && MOM_SLACK_BOT_TOKEN);\nconst hasTelegram = !!MOM_TELEGRAM_BOT_TOKEN;\nconst hasDiscord = !!MOM_DISCORD_BOT_TOKEN;\n\nif (!hasSlack && !hasTelegram && !hasDiscord) {\n\tconsole.error(\n\t\t\"No platform tokens found. Set one of:\\n\" +\n\t\t\t\" Slack: MOM_SLACK_APP_TOKEN + MOM_SLACK_BOT_TOKEN\\n\" +\n\t\t\t\" Telegram: MOM_TELEGRAM_BOT_TOKEN\\n\" +\n\t\t\t\" Discord: MOM_DISCORD_BOT_TOKEN\",\n\t);\n\tprocess.exit(1);\n}\n\nawait validateSandbox(sandbox);\n\n// ============================================================================\n// State (per channel)\n// ============================================================================\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstopRequested: boolean;\n\tstopMessageTs?: string;\n\tlastAccessedAt: number;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\n/** Track in-flight runs for graceful shutdown */\nconst inFlightRuns = new Set<Promise<void>>();\n\n/** Flag to stop accepting new events during shutdown */\nlet isShuttingDown = false;\n\n/** Maximum number of cached sessions */\nconst MAX_SESSIONS = 500;\n/** Idle timeout before a non-running session can be evicted (1 hour) */\nconst IDLE_TIMEOUT_MS = 3600000;\n\nasync function getState(channelId: string, sessionKey?: string): Promise<ChannelState> {\n\tconst key = sessionKey ?? channelId;\n\tlet state = channelStates.get(key);\n\tif (!state) {\n\t\tconst channelDir = join(workingDir, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: await createRunner(sandbox, key, channelId, channelDir, workingDir),\n\t\t\tstopRequested: false,\n\t\t\tlastAccessedAt: Date.now(),\n\t\t};\n\t\tchannelStates.set(key, state);\n\t} else {\n\t\tstate.lastAccessedAt = Date.now();\n\t}\n\treturn state;\n}\n\n/**\n * Evict idle sessions from channelStates to bound memory usage.\n * Called after each handleEvent completes.\n */\nfunction evictIdleSessions(): void {\n\tconst now = Date.now();\n\n\tfor (const [key, state] of channelStates) {\n\t\tif (!state.running && now - state.lastAccessedAt > IDLE_TIMEOUT_MS) {\n\t\t\tchannelStates.delete(key);\n\t\t}\n\t}\n\n\tif (channelStates.size > MAX_SESSIONS) {\n\t\tconst idleSessions: Array<{ key: string; lastAccessedAt: number }> = [];\n\t\tfor (const [key, state] of channelStates) {\n\t\t\tif (!state.running) {\n\t\t\t\tidleSessions.push({ key, lastAccessedAt: state.lastAccessedAt });\n\t\t\t}\n\t\t}\n\n\t\tidleSessions.sort((a, b) => a.lastAccessedAt - b.lastAccessedAt);\n\n\t\tconst toEvict = channelStates.size - MAX_SESSIONS;\n\t\tfor (let i = 0; i < toEvict && i < idleSessions.length; i++) {\n\t\t\tchannelStates.delete(idleSessions[i].key);\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Handler\n// ============================================================================\n\nconst handler: BotHandler = {\n\tisRunning(sessionKey: string): boolean {\n\t\tconst state = channelStates.get(sessionKey);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(sessionKey: string, channelId: string, bot: Bot): Promise<void> {\n\t\tconst state = channelStates.get(sessionKey);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tconst ts = await bot.postMessage(channelId, \"_Stopping..._\");\n\t\t\tstate.stopMessageTs = ts;\n\t\t} else {\n\t\t\tawait bot.postMessage(channelId, \"_Nothing running_\");\n\t\t}\n\t},\n\n\tasync handleEvent(event: BotEvent, bot: Bot, adapters: BotAdapters, isEvent?: boolean): Promise<void> {\n\t\t// Don't accept new events during shutdown\n\t\tif (isShuttingDown) {\n\t\t\tlog.logInfo(`[${event.channel}] Rejected event during shutdown: ${event.text.substring(0, 50)}`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst sessionKey = `${event.channel}:${event.thread_ts ?? event.ts}`;\n\t\tconst state = await getState(event.channel, sessionKey);\n\n\t\t// Start run\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tlog.logInfo(`[${event.channel}] Starting run: ${event.text.substring(0, 50)}`);\n\n\t\t// Wrap in-flight run tracking\n\t\tconst runPromise = (async () => {\n\t\t\ttry {\n\t\t\t\tconst { message, responseCtx, platform } = adapters;\n\n\t\t\t\t// Run the agent\n\t\t\t\tawait responseCtx.setTyping(true);\n\t\t\t\tawait responseCtx.setWorking(true);\n\t\t\t\tconst result = await state.runner.run(message, responseCtx, platform);\n\t\t\t\tawait responseCtx.setWorking(false);\n\n\t\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\t\tif (state.stopMessageTs) {\n\t\t\t\t\t\tawait bot.updateMessage(event.channel, state.stopMessageTs, \"_Stopped_\");\n\t\t\t\t\t\tstate.stopMessageTs = undefined;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait bot.postMessage(event.channel, \"_Stopped_\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tlog.logWarning(`[${event.channel}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t\t} finally {\n\t\t\t\tstate.running = false;\n\t\t\t\tstate.lastAccessedAt = Date.now();\n\t\t\t\tevictIdleSessions();\n\t\t\t}\n\t\t})();\n\n\t\tinFlightRuns.add(runPromise);\n\t\ttry {\n\t\t\tawait runPromise;\n\t\t} finally {\n\t\t\tinFlightRuns.delete(runPromise);\n\t\t}\n\t},\n};\n\n// ============================================================================\n// Start\n// ============================================================================\n\nlog.logStartup(workingDir, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\n// Create the appropriate platform bot\nlet bot: Bot;\n\nif (hasSlack) {\n\tconst sharedStore = new ChannelStore({ workingDir, botToken: MOM_SLACK_BOT_TOKEN! });\n\tbot = new SlackBotClass(handler, {\n\t\tappToken: MOM_SLACK_APP_TOKEN!,\n\t\tbotToken: MOM_SLACK_BOT_TOKEN!,\n\t\tworkingDir,\n\t\tstore: sharedStore,\n\t});\n\tlog.logInfo(\"Platform: Slack\");\n} else if (hasTelegram) {\n\tbot = new TelegramBot(handler, {\n\t\ttoken: MOM_TELEGRAM_BOT_TOKEN!,\n\t\tworkingDir,\n\t});\n\tlog.logInfo(\"Platform: Telegram\");\n} else {\n\tbot = new DiscordBot(handler, {\n\t\ttoken: MOM_DISCORD_BOT_TOKEN!,\n\t\tworkingDir,\n\t});\n\tlog.logInfo(\"Platform: Discord\");\n}\n\n// Start events watcher\nconst eventsWatcher = createEventsWatcher(workingDir, bot);\neventsWatcher.start();\n\n// Handle shutdown\nprocess.on(\"SIGINT\", async () => {\n\tif (isShuttingDown) return;\n\tisShuttingDown = true;\n\tlog.logInfo(\"Shutting down gracefully...\");\n\n\tconst timeout = Date.now() + 30000;\n\twhile (inFlightRuns.size > 0 && Date.now() < timeout) {\n\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t}\n\n\tif (inFlightRuns.size > 0) {\n\t\tlog.logWarning(`Forcing exit with ${inFlightRuns.size} runs still in progress`);\n\t}\n\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nprocess.on(\"SIGTERM\", async () => {\n\tif (isShuttingDown) return;\n\tisShuttingDown = true;\n\tlog.logInfo(\"Shutting down gracefully...\");\n\n\tconst timeout = Date.now() + 30000;\n\twhile (inFlightRuns.size > 0 && Date.now() < timeout) {\n\t\tawait new Promise((resolve) => setTimeout(resolve, 500));\n\t}\n\n\tif (inFlightRuns.size > 0) {\n\t\tlog.logWarning(`Forcing exit with ${inFlightRuns.size} runs still in progress`);\n\t}\n\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nbot.start().catch((err) => {\n\tlog.logWarning(\"Failed to start bot\", err instanceof Error ? err.message : String(err));\n\tprocess.exit(1);\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminixiang/mama",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Slack bot that delegates messages to the pi coding agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,9 @@
29
29
  "@slack/web-api": "^7.0.0",
30
30
  "chalk": "^5.6.2",
31
31
  "croner": "^9.1.0",
32
- "diff": "^8.0.2"
32
+ "diff": "^8.0.2",
33
+ "discord.js": "^14.0.0",
34
+ "grammy": "^1.41.1"
33
35
  },
34
36
  "devDependencies": {
35
37
  "@types/diff": "^7.0.2",
@@ -41,6 +43,8 @@
41
43
  },
42
44
  "keywords": [
43
45
  "slack",
46
+ "telegram",
47
+ "discord",
44
48
  "bot",
45
49
  "ai",
46
50
  "agent"