@geminixiang/mama 0.2.0-beta.6 → 0.2.0-beta.7

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 (82) hide show
  1. package/README.md +19 -11
  2. package/dist/adapter.d.ts +3 -0
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts.map +1 -1
  6. package/dist/adapters/discord/bot.js +22 -5
  7. package/dist/adapters/discord/bot.js.map +1 -1
  8. package/dist/adapters/slack/bot.d.ts +8 -0
  9. package/dist/adapters/slack/bot.d.ts.map +1 -1
  10. package/dist/adapters/slack/bot.js +160 -8
  11. package/dist/adapters/slack/bot.js.map +1 -1
  12. package/dist/adapters/slack/context.d.ts.map +1 -1
  13. package/dist/adapters/slack/context.js +5 -0
  14. package/dist/adapters/slack/context.js.map +1 -1
  15. package/dist/adapters/slack/tools/attach.d.ts +1 -1
  16. package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
  17. package/dist/adapters/slack/tools/attach.js.map +1 -1
  18. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  19. package/dist/adapters/telegram/bot.js +31 -0
  20. package/dist/adapters/telegram/bot.js.map +1 -1
  21. package/dist/agent.d.ts.map +1 -1
  22. package/dist/agent.js +39 -23
  23. package/dist/agent.js.map +1 -1
  24. package/dist/commands/index.d.ts.map +1 -1
  25. package/dist/commands/index.js +2 -0
  26. package/dist/commands/index.js.map +1 -1
  27. package/dist/commands/model.d.ts +1 -1
  28. package/dist/commands/model.d.ts.map +1 -1
  29. package/dist/commands/model.js +25 -7
  30. package/dist/commands/model.js.map +1 -1
  31. package/dist/commands/sandbox.d.ts +10 -0
  32. package/dist/commands/sandbox.d.ts.map +1 -0
  33. package/dist/commands/sandbox.js +65 -0
  34. package/dist/commands/sandbox.js.map +1 -0
  35. package/dist/commands/session-view.d.ts.map +1 -1
  36. package/dist/commands/session-view.js +29 -9
  37. package/dist/commands/session-view.js.map +1 -1
  38. package/dist/commands/utils.d.ts +3 -0
  39. package/dist/commands/utils.d.ts.map +1 -1
  40. package/dist/commands/utils.js +5 -0
  41. package/dist/commands/utils.js.map +1 -1
  42. package/dist/config.d.ts +3 -1
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +29 -0
  45. package/dist/config.js.map +1 -1
  46. package/dist/context.d.ts +1 -1
  47. package/dist/context.d.ts.map +1 -1
  48. package/dist/context.js +50 -35
  49. package/dist/context.js.map +1 -1
  50. package/dist/main.d.ts.map +1 -1
  51. package/dist/main.js +7 -1
  52. package/dist/main.js.map +1 -1
  53. package/dist/provisioner.d.ts +12 -0
  54. package/dist/provisioner.d.ts.map +1 -1
  55. package/dist/provisioner.js +41 -10
  56. package/dist/provisioner.js.map +1 -1
  57. package/dist/session-store.d.ts +1 -1
  58. package/dist/session-store.d.ts.map +1 -1
  59. package/dist/session-store.js +1 -1
  60. package/dist/session-store.js.map +1 -1
  61. package/dist/session-view/service.d.ts.map +1 -1
  62. package/dist/session-view/service.js +1 -1
  63. package/dist/session-view/service.js.map +1 -1
  64. package/dist/tools/bash.d.ts +1 -1
  65. package/dist/tools/bash.d.ts.map +1 -1
  66. package/dist/tools/bash.js.map +1 -1
  67. package/dist/tools/edit.d.ts +1 -1
  68. package/dist/tools/edit.d.ts.map +1 -1
  69. package/dist/tools/edit.js.map +1 -1
  70. package/dist/tools/event.d.ts +1 -1
  71. package/dist/tools/event.d.ts.map +1 -1
  72. package/dist/tools/event.js.map +1 -1
  73. package/dist/tools/index.d.ts +1 -1
  74. package/dist/tools/index.d.ts.map +1 -1
  75. package/dist/tools/index.js.map +1 -1
  76. package/dist/tools/read.d.ts +1 -1
  77. package/dist/tools/read.d.ts.map +1 -1
  78. package/dist/tools/read.js.map +1 -1
  79. package/dist/tools/write.d.ts +1 -1
  80. package/dist/tools/write.d.ts.map +1 -1
  81. package/dist/tools/write.js.map +1 -1
  82. package/package.json +4 -4
package/README.md CHANGED
@@ -141,21 +141,29 @@ mama reads global settings from `<state-dir>/settings.json` (default `~/.mama/se
141
141
  },
142
142
  "sandbox": {
143
143
  "cpus": "0.5",
144
- "memory": "512m"
144
+ "memory": "512m",
145
+ "boost": {
146
+ "cpus": "2",
147
+ "memory": "4g"
148
+ }
145
149
  }
146
150
  }
147
151
  ```
148
152
 
149
- | Field | Default | Description |
150
- | ------------------- | ------------------- | -------------------------------------------------------- |
151
- | `llm.provider` | `anthropic` | AI provider |
152
- | `llm.model` | `claude-sonnet-4-5` | Model name |
153
- | `llm.thinkingLevel` | `off` | `off` / `low` / `medium` / `high` |
154
- | `log.format` | `console` | `console` (colored stdout) or `json` (GCP Cloud Logging) |
155
- | `log.level` | `info` | `trace` / `debug` / `info` / `warn` / `error` |
156
- | `sentry.dsn` | unset | Sentry DSN; sensitive prompt/tool content is redacted |
157
- | `sandbox.cpus` | unset | CPU limit for managed containers |
158
- | `sandbox.memory` | unset | Memory limit for managed containers |
153
+ | Field | Default | Description |
154
+ | ---------------------- | ------------------- | -------------------------------------------------------- |
155
+ | `llm.provider` | `anthropic` | AI provider |
156
+ | `llm.model` | `claude-sonnet-4-5` | Model name |
157
+ | `llm.thinkingLevel` | `off` | `off` / `low` / `medium` / `high` |
158
+ | `log.format` | `console` | `console` (colored stdout) or `json` (GCP Cloud Logging) |
159
+ | `log.level` | `info` | `trace` / `debug` / `info` / `warn` / `error` |
160
+ | `sentry.dsn` | unset | Sentry DSN; sensitive prompt/tool content is redacted |
161
+ | `sandbox.cpus` | unset | CPU limit for managed containers |
162
+ | `sandbox.memory` | unset | Memory limit for managed containers |
163
+ | `sandbox.boost.cpus` | unset | Temporary CPU limit used by `/pi-sandbox boost` |
164
+ | `sandbox.boost.memory` | unset | Temporary memory limit used by `/pi-sandbox boost` |
165
+
166
+ `/pi-sandbox` shows the current managed-container CPU/memory limits. `/pi-sandbox boost` temporarily applies `sandbox.boost` to the current conversation; the boost ends when that sandbox container is stopped.
159
167
 
160
168
  Conversation-local settings written by `/pi-model` use the same shape and usually only include the override:
161
169
 
package/dist/adapter.d.ts CHANGED
@@ -97,6 +97,9 @@ export interface Bot {
97
97
  * any other private channel. Absent on platforms with no private delivery.
98
98
  */
99
99
  postPrivate?(conversationId: string, userId: string, text: string): Promise<void>;
100
+ postPrivateDiagnostic?(conversationId: string, userId: string, text: string, options?: {
101
+ style?: "muted" | "error";
102
+ }): Promise<void>;
100
103
  }
101
104
  /** Pre-created platform adapters passed to the handler */
102
105
  export interface BotAdapters {
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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","sourcesContent":["export type ConversationKind = \"direct\" | \"shared\";\n\nexport type PlatformName = \"slack\" | \"discord\" | \"telegram\";\n\nexport interface ChatMessage {\n id: string;\n sessionKey: string;\n conversationKind: ConversationKind;\n userId: string;\n userName?: string;\n text: string;\n attachments?: { name: string; localPath: string }[];\n threadTs?: string;\n}\n\nexport interface ChatToolResult {\n toolName: string;\n label?: string;\n args?: Record<string, unknown>;\n result: string;\n isError: boolean;\n durationMs: number;\n}\n\nexport interface ChatResponseContext {\n respond(text: string): Promise<void>;\n replaceResponse(text: string): Promise<void>;\n respondDiagnostic(text: string, options?: { style?: \"muted\" | \"error\" }): Promise<void>;\n respondToolResult(result: ChatToolResult): Promise<void>;\n setTyping(isTyping: boolean): Promise<void>;\n setWorking(working: boolean): Promise<void>;\n uploadFile(filePath: string, title?: string): Promise<void>;\n deleteResponse(): Promise<void>;\n}\n\nexport interface PlatformInfo {\n name: string;\n formattingGuide: string;\n channels: { id: string; name: string }[];\n users: { id: string; userName: string; displayName: string }[];\n diagnostics?: {\n showUsageSummary?: boolean;\n };\n}\n\nexport interface ChatAdapter {\n start(): Promise<void>;\n stop(): Promise<void>;\n getPlatformInfo(): PlatformInfo;\n}\n\n// ============================================================================\n// Generic cross-platform event and bot interfaces\n// ============================================================================\n\n/**\n * A platform-agnostic event (message/mention) that triggers the agent.\n */\nexport interface BotEvent {\n type: string;\n /** Platform-specific raw conversation/channel/chat identifier */\n conversationId: string;\n /** Optional alternate conversation identity used for vault routing. */\n vaultConversationId?: string;\n /** Cross-platform conversation shape: direct message vs shared space */\n conversationKind: ConversationKind;\n /** Message timestamp or ID as string */\n ts: string;\n /** Parent message ID for threaded replies (optional) */\n thread_ts?: string;\n /** User ID */\n user: string;\n /** Message text (already stripped of bot mentions) */\n text: string;\n /** Downloaded attachments */\n attachments?: { name: string; localPath: string }[];\n /** Platform-computed session key; overrides default conversationId:thread_ts computation */\n sessionKey?: string;\n}\n\n/**\n * Minimum interface that every platform bot must implement,\n * used by the central handler in main.ts and by EventsWatcher.\n */\nexport interface Bot {\n start(): Promise<void>;\n postMessage(channel: string, text: string): Promise<string>;\n updateMessage(channel: string, ts: string, text: string): Promise<void>;\n enqueueEvent(event: BotEvent): boolean;\n getPlatformInfo(): PlatformInfo;\n /**\n * Deliver a message visible only to the given user.\n * Implementations may use ephemeral messages (Slack), DMs (Discord), or\n * any other private channel. Absent on platforms with no private delivery.\n */\n postPrivate?(conversationId: string, userId: string, text: string): Promise<void>;\n}\n\n/** Pre-created platform adapters passed to the handler */\nexport interface BotAdapters {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n}\n\n/**\n * Handler callbacks invoked by each platform bot.\n * Each bot creates platform-specific adapters before calling handleEvent.\n */\nexport interface RunningSession {\n sessionKey: string;\n startedAt: number; // Date.now() when run started\n /** Last activity timestamp (for detecting hung tasks) */\n lastActivityAt?: number;\n /** Current tool/step being executed (if any) */\n currentTool?: string;\n}\n\nexport interface BotHandler {\n isRunning(sessionKey: string): boolean;\n getRunningSessions(): RunningSession[];\n handleEvent(event: BotEvent, bot: Bot, adapters: BotAdapters, isEvent?: boolean): Promise<void>;\n handleStop(sessionKey: string, conversationId: string, bot: Bot): Promise<void>;\n /** Force stop a running session (bypass normal stop mechanism) */\n forceStop(sessionKey: string): void;\n /** Reset a session: abort if running, delete history, remove from cache */\n handleNew(sessionKey: string, conversationId: string, bot: Bot): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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","sourcesContent":["export type ConversationKind = \"direct\" | \"shared\";\n\nexport type PlatformName = \"slack\" | \"discord\" | \"telegram\";\n\nexport interface ChatMessage {\n id: string;\n sessionKey: string;\n conversationKind: ConversationKind;\n userId: string;\n userName?: string;\n text: string;\n attachments?: { name: string; localPath: string }[];\n threadTs?: string;\n}\n\nexport interface ChatToolResult {\n toolName: string;\n label?: string;\n args?: Record<string, unknown>;\n result: string;\n isError: boolean;\n durationMs: number;\n}\n\nexport interface ChatResponseContext {\n respond(text: string): Promise<void>;\n replaceResponse(text: string): Promise<void>;\n respondDiagnostic(text: string, options?: { style?: \"muted\" | \"error\" }): Promise<void>;\n respondToolResult(result: ChatToolResult): Promise<void>;\n setTyping(isTyping: boolean): Promise<void>;\n setWorking(working: boolean): Promise<void>;\n uploadFile(filePath: string, title?: string): Promise<void>;\n deleteResponse(): Promise<void>;\n}\n\nexport interface PlatformInfo {\n name: string;\n formattingGuide: string;\n channels: { id: string; name: string }[];\n users: { id: string; userName: string; displayName: string }[];\n diagnostics?: {\n showUsageSummary?: boolean;\n };\n}\n\nexport interface ChatAdapter {\n start(): Promise<void>;\n stop(): Promise<void>;\n getPlatformInfo(): PlatformInfo;\n}\n\n// ============================================================================\n// Generic cross-platform event and bot interfaces\n// ============================================================================\n\n/**\n * A platform-agnostic event (message/mention) that triggers the agent.\n */\nexport interface BotEvent {\n type: string;\n /** Platform-specific raw conversation/channel/chat identifier */\n conversationId: string;\n /** Optional alternate conversation identity used for vault routing. */\n vaultConversationId?: string;\n /** Cross-platform conversation shape: direct message vs shared space */\n conversationKind: ConversationKind;\n /** Message timestamp or ID as string */\n ts: string;\n /** Parent message ID for threaded replies (optional) */\n thread_ts?: string;\n /** User ID */\n user: string;\n /** Message text (already stripped of bot mentions) */\n text: string;\n /** Downloaded attachments */\n attachments?: { name: string; localPath: string }[];\n /** Platform-computed session key; overrides default conversationId:thread_ts computation */\n sessionKey?: string;\n}\n\n/**\n * Minimum interface that every platform bot must implement,\n * used by the central handler in main.ts and by EventsWatcher.\n */\nexport interface Bot {\n start(): Promise<void>;\n postMessage(channel: string, text: string): Promise<string>;\n updateMessage(channel: string, ts: string, text: string): Promise<void>;\n enqueueEvent(event: BotEvent): boolean;\n getPlatformInfo(): PlatformInfo;\n /**\n * Deliver a message visible only to the given user.\n * Implementations may use ephemeral messages (Slack), DMs (Discord), or\n * any other private channel. Absent on platforms with no private delivery.\n */\n postPrivate?(conversationId: string, userId: string, text: string): Promise<void>;\n postPrivateDiagnostic?(\n conversationId: string,\n userId: string,\n text: string,\n options?: { style?: \"muted\" | \"error\" },\n ): Promise<void>;\n}\n\n/** Pre-created platform adapters passed to the handler */\nexport interface BotAdapters {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n}\n\n/**\n * Handler callbacks invoked by each platform bot.\n * Each bot creates platform-specific adapters before calling handleEvent.\n */\nexport interface RunningSession {\n sessionKey: string;\n startedAt: number; // Date.now() when run started\n /** Last activity timestamp (for detecting hung tasks) */\n lastActivityAt?: number;\n /** Current tool/step being executed (if any) */\n currentTool?: string;\n}\n\nexport interface BotHandler {\n isRunning(sessionKey: string): boolean;\n getRunningSessions(): RunningSession[];\n handleEvent(event: BotEvent, bot: Bot, adapters: BotAdapters, isEvent?: boolean): Promise<void>;\n handleStop(sessionKey: string, conversationId: string, bot: Bot): Promise<void>;\n /** Force stop a running session (bypass normal stop mechanism) */\n forceStop(sessionKey: string): void;\n /** Reset a session: abort if running, delete history, remove from cache */\n handleNew(sessionKey: string, conversationId: string, bot: Bot): Promise<void>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"","sourcesContent":["export type ConversationKind = \"direct\" | \"shared\";\n\nexport type PlatformName = \"slack\" | \"discord\" | \"telegram\";\n\nexport interface ChatMessage {\n id: string;\n sessionKey: string;\n conversationKind: ConversationKind;\n userId: string;\n userName?: string;\n text: string;\n attachments?: { name: string; localPath: string }[];\n threadTs?: string;\n}\n\nexport interface ChatToolResult {\n toolName: string;\n label?: string;\n args?: Record<string, unknown>;\n result: string;\n isError: boolean;\n durationMs: number;\n}\n\nexport interface ChatResponseContext {\n respond(text: string): Promise<void>;\n replaceResponse(text: string): Promise<void>;\n respondDiagnostic(text: string, options?: { style?: \"muted\" | \"error\" }): Promise<void>;\n respondToolResult(result: ChatToolResult): Promise<void>;\n setTyping(isTyping: boolean): Promise<void>;\n setWorking(working: boolean): Promise<void>;\n uploadFile(filePath: string, title?: string): Promise<void>;\n deleteResponse(): Promise<void>;\n}\n\nexport interface PlatformInfo {\n name: string;\n formattingGuide: string;\n channels: { id: string; name: string }[];\n users: { id: string; userName: string; displayName: string }[];\n diagnostics?: {\n showUsageSummary?: boolean;\n };\n}\n\nexport interface ChatAdapter {\n start(): Promise<void>;\n stop(): Promise<void>;\n getPlatformInfo(): PlatformInfo;\n}\n\n// ============================================================================\n// Generic cross-platform event and bot interfaces\n// ============================================================================\n\n/**\n * A platform-agnostic event (message/mention) that triggers the agent.\n */\nexport interface BotEvent {\n type: string;\n /** Platform-specific raw conversation/channel/chat identifier */\n conversationId: string;\n /** Optional alternate conversation identity used for vault routing. */\n vaultConversationId?: string;\n /** Cross-platform conversation shape: direct message vs shared space */\n conversationKind: ConversationKind;\n /** Message timestamp or ID as string */\n ts: string;\n /** Parent message ID for threaded replies (optional) */\n thread_ts?: string;\n /** User ID */\n user: string;\n /** Message text (already stripped of bot mentions) */\n text: string;\n /** Downloaded attachments */\n attachments?: { name: string; localPath: string }[];\n /** Platform-computed session key; overrides default conversationId:thread_ts computation */\n sessionKey?: string;\n}\n\n/**\n * Minimum interface that every platform bot must implement,\n * used by the central handler in main.ts and by EventsWatcher.\n */\nexport interface Bot {\n start(): Promise<void>;\n postMessage(channel: string, text: string): Promise<string>;\n updateMessage(channel: string, ts: string, text: string): Promise<void>;\n enqueueEvent(event: BotEvent): boolean;\n getPlatformInfo(): PlatformInfo;\n /**\n * Deliver a message visible only to the given user.\n * Implementations may use ephemeral messages (Slack), DMs (Discord), or\n * any other private channel. Absent on platforms with no private delivery.\n */\n postPrivate?(conversationId: string, userId: string, text: string): Promise<void>;\n}\n\n/** Pre-created platform adapters passed to the handler */\nexport interface BotAdapters {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n}\n\n/**\n * Handler callbacks invoked by each platform bot.\n * Each bot creates platform-specific adapters before calling handleEvent.\n */\nexport interface RunningSession {\n sessionKey: string;\n startedAt: number; // Date.now() when run started\n /** Last activity timestamp (for detecting hung tasks) */\n lastActivityAt?: number;\n /** Current tool/step being executed (if any) */\n currentTool?: string;\n}\n\nexport interface BotHandler {\n isRunning(sessionKey: string): boolean;\n getRunningSessions(): RunningSession[];\n handleEvent(event: BotEvent, bot: Bot, adapters: BotAdapters, isEvent?: boolean): Promise<void>;\n handleStop(sessionKey: string, conversationId: string, bot: Bot): Promise<void>;\n /** Force stop a running session (bypass normal stop mechanism) */\n forceStop(sessionKey: string): void;\n /** Reset a session: abort if running, delete history, remove from cache */\n handleNew(sessionKey: string, conversationId: string, bot: Bot): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"","sourcesContent":["export type ConversationKind = \"direct\" | \"shared\";\n\nexport type PlatformName = \"slack\" | \"discord\" | \"telegram\";\n\nexport interface ChatMessage {\n id: string;\n sessionKey: string;\n conversationKind: ConversationKind;\n userId: string;\n userName?: string;\n text: string;\n attachments?: { name: string; localPath: string }[];\n threadTs?: string;\n}\n\nexport interface ChatToolResult {\n toolName: string;\n label?: string;\n args?: Record<string, unknown>;\n result: string;\n isError: boolean;\n durationMs: number;\n}\n\nexport interface ChatResponseContext {\n respond(text: string): Promise<void>;\n replaceResponse(text: string): Promise<void>;\n respondDiagnostic(text: string, options?: { style?: \"muted\" | \"error\" }): Promise<void>;\n respondToolResult(result: ChatToolResult): Promise<void>;\n setTyping(isTyping: boolean): Promise<void>;\n setWorking(working: boolean): Promise<void>;\n uploadFile(filePath: string, title?: string): Promise<void>;\n deleteResponse(): Promise<void>;\n}\n\nexport interface PlatformInfo {\n name: string;\n formattingGuide: string;\n channels: { id: string; name: string }[];\n users: { id: string; userName: string; displayName: string }[];\n diagnostics?: {\n showUsageSummary?: boolean;\n };\n}\n\nexport interface ChatAdapter {\n start(): Promise<void>;\n stop(): Promise<void>;\n getPlatformInfo(): PlatformInfo;\n}\n\n// ============================================================================\n// Generic cross-platform event and bot interfaces\n// ============================================================================\n\n/**\n * A platform-agnostic event (message/mention) that triggers the agent.\n */\nexport interface BotEvent {\n type: string;\n /** Platform-specific raw conversation/channel/chat identifier */\n conversationId: string;\n /** Optional alternate conversation identity used for vault routing. */\n vaultConversationId?: string;\n /** Cross-platform conversation shape: direct message vs shared space */\n conversationKind: ConversationKind;\n /** Message timestamp or ID as string */\n ts: string;\n /** Parent message ID for threaded replies (optional) */\n thread_ts?: string;\n /** User ID */\n user: string;\n /** Message text (already stripped of bot mentions) */\n text: string;\n /** Downloaded attachments */\n attachments?: { name: string; localPath: string }[];\n /** Platform-computed session key; overrides default conversationId:thread_ts computation */\n sessionKey?: string;\n}\n\n/**\n * Minimum interface that every platform bot must implement,\n * used by the central handler in main.ts and by EventsWatcher.\n */\nexport interface Bot {\n start(): Promise<void>;\n postMessage(channel: string, text: string): Promise<string>;\n updateMessage(channel: string, ts: string, text: string): Promise<void>;\n enqueueEvent(event: BotEvent): boolean;\n getPlatformInfo(): PlatformInfo;\n /**\n * Deliver a message visible only to the given user.\n * Implementations may use ephemeral messages (Slack), DMs (Discord), or\n * any other private channel. Absent on platforms with no private delivery.\n */\n postPrivate?(conversationId: string, userId: string, text: string): Promise<void>;\n postPrivateDiagnostic?(\n conversationId: string,\n userId: string,\n text: string,\n options?: { style?: \"muted\" | \"error\" },\n ): Promise<void>;\n}\n\n/** Pre-created platform adapters passed to the handler */\nexport interface BotAdapters {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n}\n\n/**\n * Handler callbacks invoked by each platform bot.\n * Each bot creates platform-specific adapters before calling handleEvent.\n */\nexport interface RunningSession {\n sessionKey: string;\n startedAt: number; // Date.now() when run started\n /** Last activity timestamp (for detecting hung tasks) */\n lastActivityAt?: number;\n /** Current tool/step being executed (if any) */\n currentTool?: string;\n}\n\nexport interface BotHandler {\n isRunning(sessionKey: string): boolean;\n getRunningSessions(): RunningSession[];\n handleEvent(event: BotEvent, bot: Bot, adapters: BotAdapters, isEvent?: boolean): Promise<void>;\n handleStop(sessionKey: string, conversationId: string, bot: Bot): Promise<void>;\n /** Force stop a running session (bypass normal stop mechanism) */\n forceStop(sessionKey: string): void;\n /** Reset a session: abort if running, delete history, remove from cache */\n handleNew(sessionKey: string, conversationId: string, bot: Bot): Promise<void>;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/discord/bot.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApplicationCommandOptionType,\n Client,\n Events,\n GatewayIntentBits,\n Partials,\n type ChatInputCommandInteraction,\n type Collection,\n type Message,\n type Attachment,\n type TextChannel,\n type DMChannel,\n type NewsChannel,\n type ThreadChannel,\n} from \"discord.js\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\n\nimport type {\n Bot,\n BotAdapters,\n BotEvent,\n BotHandler,\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { resolveChatSessionKey } from \"../../session-policy.js\";\nimport { formatNothingRunning } from \"../../ui-copy.js\";\nimport {\n appendBotResponseLog,\n appendChannelLog,\n ChannelQueue,\n resolveOnlyScopedStopTarget,\n resolveStopTarget,\n withRetry,\n} from \"../shared.js\";\nimport { createDiscordAdapters } from \"./context.js\";\n\n// discord.js: DiscordAPIError exposes `.status` (HTTP status) and a `.code`.\n// RateLimitError fires when the internal queue gives up. Both should retry.\nfunction discordIsRateLimited(err: Error): boolean {\n if ((err as { status?: number }).status === 429) return true;\n if ((err as { httpStatus?: number }).httpStatus === 429) return true;\n if (err.name === \"RateLimitError\") return true;\n return false;\n}\n\nconst discordRetry = <T>(fn: () => Promise<T>): Promise<T> =>\n withRetry(fn, { isRateLimited: discordIsRateLimited });\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DiscordEvent extends BotEvent {\n type: \"mention\" | \"dm\";\n userName?: string;\n}\n\n// ============================================================================\n// DiscordBot\n// ============================================================================\n\nexport class DiscordBot implements Bot {\n private client: Client;\n private handler: BotHandler;\n private workingDir: string;\n private botUserId: string | null = null;\n private queues = new Map<string, ChannelQueue>();\n private startupTime: number = 0;\n private channels = new Map<string, { id: string; name: string }>();\n private users = new Map<string, { id: string; userName: string; displayName: string }>();\n\n constructor(handler: BotHandler, config: { token: string; workingDir: string }) {\n this.handler = handler;\n this.workingDir = config.workingDir;\n this.client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n ],\n partials: [Partials.Channel, Partials.Message],\n });\n }\n\n // ==========================================================================\n // Public API (implements Bot)\n // ==========================================================================\n\n async start(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.client.once(Events.ClientReady, async (readyClient) => {\n this.botUserId = readyClient.user.id;\n this.startupTime = Date.now();\n log.logConnected();\n log.logInfo(`Discord bot started as ${readyClient.user.tag}`);\n this.loadCachedGuildData();\n this.setupEventHandlers();\n try {\n await readyClient.application.commands.set([\n {\n name: \"login\",\n description: \"Store credentials in your private vault\",\n },\n {\n name: \"session\",\n description: \"Open the current session in the web viewer\",\n },\n {\n name: \"new\",\n description: \"Reset conversation history and start fresh\",\n },\n {\n name: \"stop\",\n description: \"Stop the current conversation\",\n },\n {\n name: \"pi-model\",\n description: \"Switch this conversation's LLM model\",\n options: [\n {\n name: \"model\",\n description: \"provider/model[:thinking], e.g. anthropic/claude-sonnet-4-5:off\",\n type: ApplicationCommandOptionType.String,\n required: false,\n },\n ],\n },\n ]);\n } catch (err) {\n log.logWarning(\n \"Failed to register Discord slash commands\",\n err instanceof Error ? err.message : String(err),\n );\n }\n resolve();\n });\n this.client.once(Events.Error, reject);\n this.client.login(process.env.MAMA_DISCORD_BOT_TOKEN!).catch(reject);\n });\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channel);\n const msg = await ch.send(text);\n return msg.id;\n });\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n await this.updateMessageRaw(channel, ts, text);\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const conversationId = event.conversationId;\n const queue = this.getQueue(conversationId);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const adapters = createDiscordAdapters(event as DiscordEvent, this, true);\n return this.handler.handleEvent(event, this, adapters, true);\n });\n return true;\n }\n\n getPlatformInfo(): PlatformInfo {\n return {\n name: \"discord\",\n formattingGuide:\n \"## Discord Formatting (Markdown)\\nBold: **text**, Italic: *text*, Code: `code`, Block: ```language\\ncode```\\nLinks: [text](url)\",\n channels: this.getAllChannels(),\n users: this.getAllUsers(),\n diagnostics: {\n showUsageSummary: false,\n },\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async updateMessageRaw(channelId: string, messageId: string, text: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.edit(text);\n });\n }\n\n async postReply(channelId: string, replyToId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const replyTarget = await ch.messages.fetch(replyToId);\n const sent = await replyTarget.reply(text);\n return sent.id;\n });\n }\n\n async postInThread(channelId: string, threadOrMessageId: string, text: string): Promise<string> {\n // Try as a thread channel first, then fall back to posting in the channel\n try {\n const thread = await this.client.channels.fetch(threadOrMessageId);\n if (thread && (thread.isThread() || thread.isTextBased())) {\n return discordRetry(async () => {\n const msg = await (thread as ThreadChannel).send(text);\n return msg.id;\n });\n }\n } catch {\n // Not a thread channel, treat as message ID for reply\n }\n return this.postReply(channelId, threadOrMessageId, text);\n }\n\n async deleteMessageRaw(channelId: string, messageId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.delete();\n } catch {\n // Ignore if already deleted\n }\n }\n\n async sendTyping(channelId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n await ch.sendTyping();\n } catch {\n // Non-fatal\n }\n }\n\n async uploadFile(channelId: string, filePath: string, title?: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await ch.send({ files: [{ attachment: fileContent, name: fileName }] });\n });\n }\n\n async sendDirectMessage(userId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const user = await this.client.users.fetch(userId);\n const msg = await user.send(text);\n return msg.id;\n });\n }\n\n async postPrivate(_conversationId: string, userId: string, text: string): Promise<void> {\n await this.sendDirectMessage(userId, text);\n }\n\n getAllChannels(): { id: string; name: string }[] {\n return Array.from(this.channels.values());\n }\n\n getAllUsers(): { id: string; userName: string; displayName: string }[] {\n return Array.from(this.users.values());\n }\n\n logToFile(channelId: string, entry: object): void {\n appendChannelLog(this.workingDir, channelId, entry);\n }\n\n logBotResponse(channelId: string, text: string, ts: string): void {\n appendBotResponseLog(this.workingDir, channelId, text, ts);\n }\n\n /**\n * Process attachments from a Discord message.\n * Downloads files before returning so the agent can read them immediately.\n */\n async processAttachments(\n channelId: string,\n attachments: Collection<string, Attachment>,\n _messageId: string,\n ): Promise<{ name: string; localPath: string }[]> {\n const downloads: Array<Promise<{ name: string; localPath: string } | null>> = [];\n\n // Discord attachments Collection - iterate over values\n for (const attachment of attachments.values()) {\n if (!attachment.name) {\n log.logWarning(\"Discord attachment missing name, skipping\", attachment.url);\n continue;\n }\n\n const ts = Date.now();\n const sanitizedName = attachment.name.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const filename = `${ts}_${sanitizedName}`;\n const localPath = `${channelId}/attachments/${filename}`;\n const fullDir = join(this.workingDir, channelId, \"attachments\");\n const result = {\n name: attachment.name,\n localPath,\n };\n\n downloads.push(\n this.downloadAttachment(fullDir, filename, attachment.url)\n .then(() => result)\n .catch((err) => {\n log.logWarning(`Failed to download Discord attachment`, `${filename}: ${err}`);\n return null;\n }),\n );\n }\n\n const results = await Promise.all(downloads);\n return results.filter(\n (attachment): attachment is { name: string; localPath: string } => attachment !== null,\n );\n }\n\n /**\n * Download an attachment from URL to local file\n */\n private async downloadAttachment(dir: string, filename: string, url: string): Promise<void> {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n writeFileSync(join(dir, filename), Buffer.from(buffer));\n } catch (err) {\n throw new Error(`Download failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): ChannelQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new ChannelQueue(\"Discord\");\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private resolveStopTarget(channelId: string, sessionKey: string): string | null {\n const directTarget = resolveStopTarget({\n handler: this.handler,\n conversationId: channelId,\n sessionKey,\n });\n if (directTarget) return directTarget;\n if (sessionKey !== channelId) return null;\n return resolveOnlyScopedStopTarget(this.handler, channelId);\n }\n\n private loadCachedGuildData(): void {\n for (const guild of this.client.guilds.cache.values()) {\n for (const channel of guild.channels.cache.values()) {\n if (channel.isTextBased() && \"name\" in channel) {\n this.channels.set(channel.id, { id: channel.id, name: channel.name ?? channel.id });\n }\n }\n for (const member of guild.members.cache.values()) {\n this.users.set(member.id, {\n id: member.id,\n userName: member.user.username,\n displayName: member.displayName,\n });\n }\n }\n }\n\n private stripBotMention(text: string): string {\n if (!this.botUserId) return text;\n return text.replace(new RegExp(`<@!?${this.botUserId}>`, \"g\"), \"\").trim();\n }\n\n private resolveConversationContext(input: {\n channelId: string;\n inGuild: boolean;\n isThread: boolean;\n parentChannelId?: string | null;\n referencedMsgId?: string;\n }): { conversationId: string; threadTs?: string } {\n if (!input.inGuild) {\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n if (input.isThread) {\n return {\n conversationId: input.parentChannelId ?? input.channelId,\n threadTs: input.channelId,\n };\n }\n\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n private createSlashCommandAdapters(\n interaction: ChatInputCommandInteraction,\n commandText: string,\n sessionKey: string,\n conversationId: string,\n ): BotAdapters {\n const isDM = !interaction.inGuild();\n const userId = interaction.user.id;\n const userName = interaction.user.username;\n const platform = this.getPlatformInfo();\n const shouldUseEphemeral = !isDM;\n\n const message: ChatMessage = {\n id: interaction.id,\n sessionKey,\n conversationKind: isDM ? \"direct\" : \"shared\",\n userId,\n userName,\n text: commandText,\n attachments: [],\n };\n\n const respondPrivately = async (text: string, replace = false): Promise<void> => {\n if (interaction.replied || interaction.deferred) {\n if (replace) {\n await interaction.editReply({ content: text });\n } else {\n await interaction.followUp({ content: text, ephemeral: shouldUseEphemeral });\n }\n return;\n }\n\n await interaction.reply({ content: text, ephemeral: shouldUseEphemeral });\n };\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n await respondPrivately(text);\n },\n replaceResponse: async (text: string) => {\n await respondPrivately(text, true);\n },\n respondDiagnostic: async (text: string) => {\n await respondPrivately(text);\n },\n respondToolResult: async (result: ChatToolResult) => {\n const duration = (result.durationMs / 1000).toFixed(1);\n const formatted = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName} (${duration}s)\\n${result.result}`;\n await respondPrivately(formatted);\n },\n setTyping: async () => {},\n setWorking: async () => {},\n uploadFile: async (filePath: string, title?: string) => {\n await this.uploadFile(conversationId, filePath, title);\n },\n deleteResponse: async () => {},\n };\n\n return { message, responseCtx, platform };\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (\n interaction.commandName !== \"login\" &&\n interaction.commandName !== \"session\" &&\n interaction.commandName !== \"new\" &&\n interaction.commandName !== \"stop\" &&\n interaction.commandName !== \"pi-model\"\n ) {\n return;\n }\n\n const isDM = !interaction.inGuild();\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: interaction.channelId,\n inGuild: interaction.inGuild(),\n isThread: interaction.channel?.isThread() ?? false,\n parentChannelId:\n interaction.channel && \"parentId\" in interaction.channel\n ? interaction.channel.parentId\n : null,\n });\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n messageId: interaction.id,\n persistentTopLevel: true,\n threadTs,\n });\n const modelOption =\n interaction.commandName === \"pi-model\"\n ? interaction.options.getString(\"model\")?.trim()\n : undefined;\n const commandText =\n interaction.commandName === \"pi-model\" && modelOption\n ? `/${interaction.commandName} ${modelOption}`\n : `/${interaction.commandName}`;\n\n this.logToFile(conversationId, {\n date: new Date(interaction.createdTimestamp).toISOString(),\n ts: interaction.id,\n ...(threadTs ? { threadTs } : {}),\n user: interaction.user.id,\n userName: interaction.user.username,\n text: commandText,\n attachments: [],\n isBot: false,\n });\n\n const adapters = this.createSlashCommandAdapters(\n interaction,\n commandText,\n sessionKey,\n conversationId,\n );\n try {\n if (interaction.commandName === \"new\") {\n await this.handler.handleNew(sessionKey, conversationId, this);\n return;\n }\n\n if (interaction.commandName === \"stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n await this.handler.handleStop(stopTarget, conversationId, this);\n } else {\n await adapters.responseCtx.respond(formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n const event: BotEvent = {\n type: \"dm\",\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n ts: interaction.id,\n thread_ts: threadTs,\n sessionKey,\n user: interaction.user.id,\n text: commandText,\n attachments: [],\n };\n\n await this.handler.handleEvent(event, this, adapters, false);\n } catch (err) {\n log.logWarning(\n \"Discord slash command error\",\n err instanceof Error ? err.message : String(err),\n );\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: `${interaction.commandName} command failed. Please try again later.`,\n ephemeral: !isDM,\n });\n }\n }\n });\n\n this.client.on(Events.MessageCreate, async (msg: Message) => {\n // Skip messages from before startup\n if (msg.createdTimestamp < this.startupTime) return;\n // Skip bot messages\n if (msg.author.bot) return;\n const isDM = msg.channel.type === 1; // ChannelType.DM = 1\n const isInThread = msg.channel.isThread();\n const referencedMsgId = msg.reference?.messageId;\n const isThreadReply = isInThread || !!referencedMsgId;\n const isMentioned = msg.mentions.users.has(this.botUserId ?? \"\");\n // Shared-channel top-level messages require a mention. Thread/reply follow-ups do not.\n if (!isDM && !isMentioned && !isThreadReply) return;\n\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: msg.channelId,\n inGuild: !isDM,\n isThread: isInThread,\n parentChannelId: \"parentId\" in msg.channel ? msg.channel.parentId : null,\n referencedMsgId,\n });\n const userId = msg.author.id;\n const userName = msg.author.username;\n const msgId = msg.id;\n\n // Track user\n this.users.set(userId, {\n id: userId,\n userName,\n displayName: msg.member?.displayName ?? userName,\n });\n\n // Track channel\n if (!this.channels.has(conversationId) && \"name\" in msg.channel) {\n const ch = msg.channel as TextChannel | NewsChannel;\n this.channels.set(conversationId, { id: conversationId, name: ch.name });\n }\n\n const conversationKind = isDM ? \"direct\" : \"shared\";\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind,\n messageId: msgId,\n persistentTopLevel: true,\n threadTs,\n });\n\n const cleanedText = this.stripBotMention(msg.content);\n\n const processedAttachments = await this.processAttachments(\n conversationId,\n msg.attachments,\n msgId,\n );\n\n const event: DiscordEvent = {\n type: isDM ? \"dm\" : \"mention\",\n conversationId,\n conversationKind,\n ts: msgId,\n thread_ts: threadTs,\n sessionKey,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log message\n this.logToFile(conversationId, {\n date: msg.createdAt.toISOString(),\n ts: msgId,\n ...(!isDM && threadTs ? { threadTs } : {}),\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n // Handle stop command\n if (cleanedText.toLowerCase() === \"stop\" || cleanedText.toLowerCase() === \"/stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n this.handler.handleStop(stopTarget, conversationId, this);\n } else {\n await this.postMessage(conversationId, formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n this.getQueue(sessionKey).enqueue(() => {\n const adapters = createDiscordAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n });\n }\n\n private async fetchTextChannel(\n channelId: string,\n ): Promise<TextChannel | DMChannel | NewsChannel | ThreadChannel> {\n const ch = await this.client.channels.fetch(channelId);\n if (!ch || !ch.isTextBased()) {\n throw new Error(`Channel ${channelId} is not a text channel`);\n }\n return ch as TextChannel | DMChannel | NewsChannel | ThreadChannel;\n }\n}\n"]}
1
+ {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/discord/bot.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,UAAU,EAEf,KAAK,UAAU,EAKhB,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EACV,GAAG,EAEH,QAAQ,EACR,UAAU,EAIV,YAAY,EACb,MAAM,kBAAkB,CAAC;AA8B1B,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC5C,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD,qBAAa,UAAW,YAAW,GAAG;IACpC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAmD;IACnE,OAAO,CAAC,KAAK,CAA4E;IAEzF,YAAY,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAY7E;IAMK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA+D3B;IAEK,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhE;IAEK,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;IAED,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAerC;IAED,eAAe,IAAI,YAAY,CAW9B;IAMK,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;IAEK,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnF;IAEK,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc9F;IAEK,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1E;IAEK,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOjD;IAEK,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnF;IAEK,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMrE;IAEK,WAAW,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtF;IAED,cAAc,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAE/C;IAED,WAAW,IAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE,CAErE;IAED,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEhD;IAED,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAEhE;IAED;;;OAGG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,EAC3C,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAkChD;YAKa,kBAAkB;IAoBhC,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,0BAA0B;IA2BlC,OAAO,CAAC,0BAA0B;IA6DlC,OAAO,CAAC,kBAAkB;YAyMZ,gBAAgB;CAS/B","sourcesContent":["import {\n ApplicationCommandOptionType,\n Client,\n Events,\n GatewayIntentBits,\n Partials,\n type ChatInputCommandInteraction,\n type Collection,\n type Message,\n type Attachment,\n type TextChannel,\n type DMChannel,\n type NewsChannel,\n type ThreadChannel,\n} from \"discord.js\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\n\nimport type {\n Bot,\n BotAdapters,\n BotEvent,\n BotHandler,\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { resolveChatSessionKey } from \"../../session-policy.js\";\nimport { formatNothingRunning } from \"../../ui-copy.js\";\nimport {\n appendBotResponseLog,\n appendChannelLog,\n ChannelQueue,\n resolveOnlyScopedStopTarget,\n resolveStopTarget,\n withRetry,\n} from \"../shared.js\";\nimport { createDiscordAdapters } from \"./context.js\";\n\n// discord.js: DiscordAPIError exposes `.status` (HTTP status) and a `.code`.\n// RateLimitError fires when the internal queue gives up. Both should retry.\nfunction discordIsRateLimited(err: Error): boolean {\n if ((err as { status?: number }).status === 429) return true;\n if ((err as { httpStatus?: number }).httpStatus === 429) return true;\n if (err.name === \"RateLimitError\") return true;\n return false;\n}\n\nconst discordRetry = <T>(fn: () => Promise<T>): Promise<T> =>\n withRetry(fn, { isRateLimited: discordIsRateLimited });\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DiscordEvent extends BotEvent {\n type: \"mention\" | \"dm\";\n userName?: string;\n}\n\n// ============================================================================\n// DiscordBot\n// ============================================================================\n\nexport class DiscordBot implements Bot {\n private client: Client;\n private handler: BotHandler;\n private workingDir: string;\n private botUserId: string | null = null;\n private queues = new Map<string, ChannelQueue>();\n private startupTime: number = 0;\n private channels = new Map<string, { id: string; name: string }>();\n private users = new Map<string, { id: string; userName: string; displayName: string }>();\n\n constructor(handler: BotHandler, config: { token: string; workingDir: string }) {\n this.handler = handler;\n this.workingDir = config.workingDir;\n this.client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n ],\n partials: [Partials.Channel, Partials.Message],\n });\n }\n\n // ==========================================================================\n // Public API (implements Bot)\n // ==========================================================================\n\n async start(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.client.once(Events.ClientReady, async (readyClient) => {\n this.botUserId = readyClient.user.id;\n this.startupTime = Date.now();\n log.logConnected();\n log.logInfo(`Discord bot started as ${readyClient.user.tag}`);\n this.loadCachedGuildData();\n this.setupEventHandlers();\n try {\n await readyClient.application.commands.set([\n {\n name: \"login\",\n description: \"Store credentials in your private vault\",\n },\n {\n name: \"session\",\n description: \"Open the current session in the web viewer\",\n },\n {\n name: \"new\",\n description: \"Reset conversation history and start fresh\",\n },\n {\n name: \"stop\",\n description: \"Stop the current conversation\",\n },\n {\n name: \"model\",\n description: \"Switch this conversation's LLM model\",\n options: [\n {\n name: \"model\",\n description: \"provider/model[:thinking], e.g. anthropic/claude-sonnet-4-5:off\",\n type: ApplicationCommandOptionType.String,\n required: false,\n },\n ],\n },\n {\n name: \"sandbox\",\n description: \"Show or temporarily boost this conversation's sandbox limits\",\n options: [\n {\n name: \"action\",\n description: \"Use 'boost' to temporarily apply the configured boost limits\",\n type: ApplicationCommandOptionType.String,\n required: false,\n },\n ],\n },\n ]);\n } catch (err) {\n log.logWarning(\n \"Failed to register Discord slash commands\",\n err instanceof Error ? err.message : String(err),\n );\n }\n resolve();\n });\n this.client.once(Events.Error, reject);\n this.client.login(process.env.MAMA_DISCORD_BOT_TOKEN!).catch(reject);\n });\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channel);\n const msg = await ch.send(text);\n return msg.id;\n });\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n await this.updateMessageRaw(channel, ts, text);\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const conversationId = event.conversationId;\n const queue = this.getQueue(conversationId);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const adapters = createDiscordAdapters(event as DiscordEvent, this, true);\n return this.handler.handleEvent(event, this, adapters, true);\n });\n return true;\n }\n\n getPlatformInfo(): PlatformInfo {\n return {\n name: \"discord\",\n formattingGuide:\n \"## Discord Formatting (Markdown)\\nBold: **text**, Italic: *text*, Code: `code`, Block: ```language\\ncode```\\nLinks: [text](url)\",\n channels: this.getAllChannels(),\n users: this.getAllUsers(),\n diagnostics: {\n showUsageSummary: false,\n },\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async updateMessageRaw(channelId: string, messageId: string, text: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.edit(text);\n });\n }\n\n async postReply(channelId: string, replyToId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const replyTarget = await ch.messages.fetch(replyToId);\n const sent = await replyTarget.reply(text);\n return sent.id;\n });\n }\n\n async postInThread(channelId: string, threadOrMessageId: string, text: string): Promise<string> {\n // Try as a thread channel first, then fall back to posting in the channel\n try {\n const thread = await this.client.channels.fetch(threadOrMessageId);\n if (thread && (thread.isThread() || thread.isTextBased())) {\n return discordRetry(async () => {\n const msg = await (thread as ThreadChannel).send(text);\n return msg.id;\n });\n }\n } catch {\n // Not a thread channel, treat as message ID for reply\n }\n return this.postReply(channelId, threadOrMessageId, text);\n }\n\n async deleteMessageRaw(channelId: string, messageId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.delete();\n } catch {\n // Ignore if already deleted\n }\n }\n\n async sendTyping(channelId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n await ch.sendTyping();\n } catch {\n // Non-fatal\n }\n }\n\n async uploadFile(channelId: string, filePath: string, title?: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await ch.send({ files: [{ attachment: fileContent, name: fileName }] });\n });\n }\n\n async sendDirectMessage(userId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const user = await this.client.users.fetch(userId);\n const msg = await user.send(text);\n return msg.id;\n });\n }\n\n async postPrivate(_conversationId: string, userId: string, text: string): Promise<void> {\n await this.sendDirectMessage(userId, text);\n }\n\n getAllChannels(): { id: string; name: string }[] {\n return Array.from(this.channels.values());\n }\n\n getAllUsers(): { id: string; userName: string; displayName: string }[] {\n return Array.from(this.users.values());\n }\n\n logToFile(channelId: string, entry: object): void {\n appendChannelLog(this.workingDir, channelId, entry);\n }\n\n logBotResponse(channelId: string, text: string, ts: string): void {\n appendBotResponseLog(this.workingDir, channelId, text, ts);\n }\n\n /**\n * Process attachments from a Discord message.\n * Downloads files before returning so the agent can read them immediately.\n */\n async processAttachments(\n channelId: string,\n attachments: Collection<string, Attachment>,\n _messageId: string,\n ): Promise<{ name: string; localPath: string }[]> {\n const downloads: Array<Promise<{ name: string; localPath: string } | null>> = [];\n\n // Discord attachments Collection - iterate over values\n for (const attachment of attachments.values()) {\n if (!attachment.name) {\n log.logWarning(\"Discord attachment missing name, skipping\", attachment.url);\n continue;\n }\n\n const ts = Date.now();\n const sanitizedName = attachment.name.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const filename = `${ts}_${sanitizedName}`;\n const localPath = `${channelId}/attachments/${filename}`;\n const fullDir = join(this.workingDir, channelId, \"attachments\");\n const result = {\n name: attachment.name,\n localPath,\n };\n\n downloads.push(\n this.downloadAttachment(fullDir, filename, attachment.url)\n .then(() => result)\n .catch((err) => {\n log.logWarning(`Failed to download Discord attachment`, `${filename}: ${err}`);\n return null;\n }),\n );\n }\n\n const results = await Promise.all(downloads);\n return results.filter(\n (attachment): attachment is { name: string; localPath: string } => attachment !== null,\n );\n }\n\n /**\n * Download an attachment from URL to local file\n */\n private async downloadAttachment(dir: string, filename: string, url: string): Promise<void> {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n writeFileSync(join(dir, filename), Buffer.from(buffer));\n } catch (err) {\n throw new Error(`Download failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): ChannelQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new ChannelQueue(\"Discord\");\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private resolveStopTarget(channelId: string, sessionKey: string): string | null {\n const directTarget = resolveStopTarget({\n handler: this.handler,\n conversationId: channelId,\n sessionKey,\n });\n if (directTarget) return directTarget;\n if (sessionKey !== channelId) return null;\n return resolveOnlyScopedStopTarget(this.handler, channelId);\n }\n\n private loadCachedGuildData(): void {\n for (const guild of this.client.guilds.cache.values()) {\n for (const channel of guild.channels.cache.values()) {\n if (channel.isTextBased() && \"name\" in channel) {\n this.channels.set(channel.id, { id: channel.id, name: channel.name ?? channel.id });\n }\n }\n for (const member of guild.members.cache.values()) {\n this.users.set(member.id, {\n id: member.id,\n userName: member.user.username,\n displayName: member.displayName,\n });\n }\n }\n }\n\n private stripBotMention(text: string): string {\n if (!this.botUserId) return text;\n return text.replace(new RegExp(`<@!?${this.botUserId}>`, \"g\"), \"\").trim();\n }\n\n private resolveConversationContext(input: {\n channelId: string;\n inGuild: boolean;\n isThread: boolean;\n parentChannelId?: string | null;\n referencedMsgId?: string;\n }): { conversationId: string; threadTs?: string } {\n if (!input.inGuild) {\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n if (input.isThread) {\n return {\n conversationId: input.parentChannelId ?? input.channelId,\n threadTs: input.channelId,\n };\n }\n\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n private createSlashCommandAdapters(\n interaction: ChatInputCommandInteraction,\n commandText: string,\n sessionKey: string,\n conversationId: string,\n ): BotAdapters {\n const isDM = !interaction.inGuild();\n const userId = interaction.user.id;\n const userName = interaction.user.username;\n const platform = this.getPlatformInfo();\n const shouldUseEphemeral = !isDM;\n\n const message: ChatMessage = {\n id: interaction.id,\n sessionKey,\n conversationKind: isDM ? \"direct\" : \"shared\",\n userId,\n userName,\n text: commandText,\n attachments: [],\n };\n\n const respondPrivately = async (text: string, replace = false): Promise<void> => {\n if (interaction.replied || interaction.deferred) {\n if (replace) {\n await interaction.editReply({ content: text });\n } else {\n await interaction.followUp({ content: text, ephemeral: shouldUseEphemeral });\n }\n return;\n }\n\n await interaction.reply({ content: text, ephemeral: shouldUseEphemeral });\n };\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n await respondPrivately(text);\n },\n replaceResponse: async (text: string) => {\n await respondPrivately(text, true);\n },\n respondDiagnostic: async (text: string) => {\n await respondPrivately(text);\n },\n respondToolResult: async (result: ChatToolResult) => {\n const duration = (result.durationMs / 1000).toFixed(1);\n const formatted = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName} (${duration}s)\\n${result.result}`;\n await respondPrivately(formatted);\n },\n setTyping: async () => {},\n setWorking: async () => {},\n uploadFile: async (filePath: string, title?: string) => {\n await this.uploadFile(conversationId, filePath, title);\n },\n deleteResponse: async () => {},\n };\n\n return { message, responseCtx, platform };\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (\n interaction.commandName !== \"login\" &&\n interaction.commandName !== \"session\" &&\n interaction.commandName !== \"new\" &&\n interaction.commandName !== \"stop\" &&\n interaction.commandName !== \"model\" &&\n interaction.commandName !== \"sandbox\"\n ) {\n return;\n }\n\n const isDM = !interaction.inGuild();\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: interaction.channelId,\n inGuild: interaction.inGuild(),\n isThread: interaction.channel?.isThread() ?? false,\n parentChannelId:\n interaction.channel && \"parentId\" in interaction.channel\n ? interaction.channel.parentId\n : null,\n });\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n messageId: interaction.id,\n persistentTopLevel: true,\n threadTs,\n });\n const modelOption =\n interaction.commandName === \"model\"\n ? interaction.options.getString(\"model\")?.trim()\n : undefined;\n const sandboxAction =\n interaction.commandName === \"sandbox\"\n ? interaction.options.getString(\"action\")?.trim()\n : undefined;\n const commandArg = modelOption ?? sandboxAction;\n const commandText = commandArg\n ? `/${interaction.commandName} ${commandArg}`\n : `/${interaction.commandName}`;\n\n this.logToFile(conversationId, {\n date: new Date(interaction.createdTimestamp).toISOString(),\n ts: interaction.id,\n ...(threadTs ? { threadTs } : {}),\n user: interaction.user.id,\n userName: interaction.user.username,\n text: commandText,\n attachments: [],\n isBot: false,\n });\n\n const adapters = this.createSlashCommandAdapters(\n interaction,\n commandText,\n sessionKey,\n conversationId,\n );\n try {\n if (interaction.commandName === \"new\") {\n await this.handler.handleNew(sessionKey, conversationId, this);\n return;\n }\n\n if (interaction.commandName === \"stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n await this.handler.handleStop(stopTarget, conversationId, this);\n } else {\n await adapters.responseCtx.respond(formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n const event: BotEvent = {\n type: \"dm\",\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n ts: interaction.id,\n thread_ts: threadTs,\n sessionKey,\n user: interaction.user.id,\n text: commandText,\n attachments: [],\n };\n\n await this.handler.handleEvent(event, this, adapters, false);\n } catch (err) {\n log.logWarning(\n \"Discord slash command error\",\n err instanceof Error ? err.message : String(err),\n );\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: `${interaction.commandName} command failed. Please try again later.`,\n ephemeral: !isDM,\n });\n }\n }\n });\n\n this.client.on(Events.MessageCreate, async (msg: Message) => {\n // Skip messages from before startup\n if (msg.createdTimestamp < this.startupTime) return;\n // Skip bot messages\n if (msg.author.bot) return;\n const isDM = msg.channel.type === 1; // ChannelType.DM = 1\n const isInThread = msg.channel.isThread();\n const referencedMsgId = msg.reference?.messageId;\n const isThreadReply = isInThread || !!referencedMsgId;\n const isMentioned = msg.mentions.users.has(this.botUserId ?? \"\");\n // Shared-channel top-level messages require a mention. Thread/reply follow-ups do not.\n if (!isDM && !isMentioned && !isThreadReply) return;\n\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: msg.channelId,\n inGuild: !isDM,\n isThread: isInThread,\n parentChannelId: \"parentId\" in msg.channel ? msg.channel.parentId : null,\n referencedMsgId,\n });\n const userId = msg.author.id;\n const userName = msg.author.username;\n const msgId = msg.id;\n\n // Track user\n this.users.set(userId, {\n id: userId,\n userName,\n displayName: msg.member?.displayName ?? userName,\n });\n\n // Track channel\n if (!this.channels.has(conversationId) && \"name\" in msg.channel) {\n const ch = msg.channel as TextChannel | NewsChannel;\n this.channels.set(conversationId, { id: conversationId, name: ch.name });\n }\n\n const conversationKind = isDM ? \"direct\" : \"shared\";\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind,\n messageId: msgId,\n persistentTopLevel: true,\n threadTs,\n });\n\n const cleanedText = this.stripBotMention(msg.content);\n\n const processedAttachments = await this.processAttachments(\n conversationId,\n msg.attachments,\n msgId,\n );\n\n const event: DiscordEvent = {\n type: isDM ? \"dm\" : \"mention\",\n conversationId,\n conversationKind,\n ts: msgId,\n thread_ts: threadTs,\n sessionKey,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log message\n this.logToFile(conversationId, {\n date: msg.createdAt.toISOString(),\n ts: msgId,\n ...(!isDM && threadTs ? { threadTs } : {}),\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n // Handle stop command\n if (cleanedText.toLowerCase() === \"stop\" || cleanedText.toLowerCase() === \"/stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n this.handler.handleStop(stopTarget, conversationId, this);\n } else {\n await this.postMessage(conversationId, formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n this.getQueue(sessionKey).enqueue(() => {\n const adapters = createDiscordAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n });\n }\n\n private async fetchTextChannel(\n channelId: string,\n ): Promise<TextChannel | DMChannel | NewsChannel | ThreadChannel> {\n const ch = await this.client.channels.fetch(channelId);\n if (!ch || !ch.isTextBased()) {\n throw new Error(`Channel ${channelId} is not a text channel`);\n }\n return ch as TextChannel | DMChannel | NewsChannel | ThreadChannel;\n }\n}\n"]}
@@ -71,7 +71,7 @@ export class DiscordBot {
71
71
  description: "Stop the current conversation",
72
72
  },
73
73
  {
74
- name: "pi-model",
74
+ name: "model",
75
75
  description: "Switch this conversation's LLM model",
76
76
  options: [
77
77
  {
@@ -82,6 +82,18 @@ export class DiscordBot {
82
82
  },
83
83
  ],
84
84
  },
85
+ {
86
+ name: "sandbox",
87
+ description: "Show or temporarily boost this conversation's sandbox limits",
88
+ options: [
89
+ {
90
+ name: "action",
91
+ description: "Use 'boost' to temporarily apply the configured boost limits",
92
+ type: ApplicationCommandOptionType.String,
93
+ required: false,
94
+ },
95
+ ],
96
+ },
85
97
  ]);
86
98
  }
87
99
  catch (err) {
@@ -381,7 +393,8 @@ export class DiscordBot {
381
393
  interaction.commandName !== "session" &&
382
394
  interaction.commandName !== "new" &&
383
395
  interaction.commandName !== "stop" &&
384
- interaction.commandName !== "pi-model") {
396
+ interaction.commandName !== "model" &&
397
+ interaction.commandName !== "sandbox") {
385
398
  return;
386
399
  }
387
400
  const isDM = !interaction.inGuild();
@@ -400,11 +413,15 @@ export class DiscordBot {
400
413
  persistentTopLevel: true,
401
414
  threadTs,
402
415
  });
403
- const modelOption = interaction.commandName === "pi-model"
416
+ const modelOption = interaction.commandName === "model"
404
417
  ? interaction.options.getString("model")?.trim()
405
418
  : undefined;
406
- const commandText = interaction.commandName === "pi-model" && modelOption
407
- ? `/${interaction.commandName} ${modelOption}`
419
+ const sandboxAction = interaction.commandName === "sandbox"
420
+ ? interaction.options.getString("action")?.trim()
421
+ : undefined;
422
+ const commandArg = modelOption ?? sandboxAction;
423
+ const commandText = commandArg
424
+ ? `/${interaction.commandName} ${commandArg}`
408
425
  : `/${interaction.commandName}`;
409
426
  this.logToFile(conversationId, {
410
427
  date: new Date(interaction.createdTimestamp).toISOString(),
@@ -1 +1 @@
1
- {"version":3,"file":"bot.js","sourceRoot":"","sources":["../../../src/adapters/discord/bot.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApplicationCommandOptionType,\n Client,\n Events,\n GatewayIntentBits,\n Partials,\n type ChatInputCommandInteraction,\n type Collection,\n type Message,\n type Attachment,\n type TextChannel,\n type DMChannel,\n type NewsChannel,\n type ThreadChannel,\n} from \"discord.js\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\n\nimport type {\n Bot,\n BotAdapters,\n BotEvent,\n BotHandler,\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { resolveChatSessionKey } from \"../../session-policy.js\";\nimport { formatNothingRunning } from \"../../ui-copy.js\";\nimport {\n appendBotResponseLog,\n appendChannelLog,\n ChannelQueue,\n resolveOnlyScopedStopTarget,\n resolveStopTarget,\n withRetry,\n} from \"../shared.js\";\nimport { createDiscordAdapters } from \"./context.js\";\n\n// discord.js: DiscordAPIError exposes `.status` (HTTP status) and a `.code`.\n// RateLimitError fires when the internal queue gives up. Both should retry.\nfunction discordIsRateLimited(err: Error): boolean {\n if ((err as { status?: number }).status === 429) return true;\n if ((err as { httpStatus?: number }).httpStatus === 429) return true;\n if (err.name === \"RateLimitError\") return true;\n return false;\n}\n\nconst discordRetry = <T>(fn: () => Promise<T>): Promise<T> =>\n withRetry(fn, { isRateLimited: discordIsRateLimited });\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DiscordEvent extends BotEvent {\n type: \"mention\" | \"dm\";\n userName?: string;\n}\n\n// ============================================================================\n// DiscordBot\n// ============================================================================\n\nexport class DiscordBot implements Bot {\n private client: Client;\n private handler: BotHandler;\n private workingDir: string;\n private botUserId: string | null = null;\n private queues = new Map<string, ChannelQueue>();\n private startupTime: number = 0;\n private channels = new Map<string, { id: string; name: string }>();\n private users = new Map<string, { id: string; userName: string; displayName: string }>();\n\n constructor(handler: BotHandler, config: { token: string; workingDir: string }) {\n this.handler = handler;\n this.workingDir = config.workingDir;\n this.client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n ],\n partials: [Partials.Channel, Partials.Message],\n });\n }\n\n // ==========================================================================\n // Public API (implements Bot)\n // ==========================================================================\n\n async start(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.client.once(Events.ClientReady, async (readyClient) => {\n this.botUserId = readyClient.user.id;\n this.startupTime = Date.now();\n log.logConnected();\n log.logInfo(`Discord bot started as ${readyClient.user.tag}`);\n this.loadCachedGuildData();\n this.setupEventHandlers();\n try {\n await readyClient.application.commands.set([\n {\n name: \"login\",\n description: \"Store credentials in your private vault\",\n },\n {\n name: \"session\",\n description: \"Open the current session in the web viewer\",\n },\n {\n name: \"new\",\n description: \"Reset conversation history and start fresh\",\n },\n {\n name: \"stop\",\n description: \"Stop the current conversation\",\n },\n {\n name: \"pi-model\",\n description: \"Switch this conversation's LLM model\",\n options: [\n {\n name: \"model\",\n description: \"provider/model[:thinking], e.g. anthropic/claude-sonnet-4-5:off\",\n type: ApplicationCommandOptionType.String,\n required: false,\n },\n ],\n },\n ]);\n } catch (err) {\n log.logWarning(\n \"Failed to register Discord slash commands\",\n err instanceof Error ? err.message : String(err),\n );\n }\n resolve();\n });\n this.client.once(Events.Error, reject);\n this.client.login(process.env.MAMA_DISCORD_BOT_TOKEN!).catch(reject);\n });\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channel);\n const msg = await ch.send(text);\n return msg.id;\n });\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n await this.updateMessageRaw(channel, ts, text);\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const conversationId = event.conversationId;\n const queue = this.getQueue(conversationId);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const adapters = createDiscordAdapters(event as DiscordEvent, this, true);\n return this.handler.handleEvent(event, this, adapters, true);\n });\n return true;\n }\n\n getPlatformInfo(): PlatformInfo {\n return {\n name: \"discord\",\n formattingGuide:\n \"## Discord Formatting (Markdown)\\nBold: **text**, Italic: *text*, Code: `code`, Block: ```language\\ncode```\\nLinks: [text](url)\",\n channels: this.getAllChannels(),\n users: this.getAllUsers(),\n diagnostics: {\n showUsageSummary: false,\n },\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async updateMessageRaw(channelId: string, messageId: string, text: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.edit(text);\n });\n }\n\n async postReply(channelId: string, replyToId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const replyTarget = await ch.messages.fetch(replyToId);\n const sent = await replyTarget.reply(text);\n return sent.id;\n });\n }\n\n async postInThread(channelId: string, threadOrMessageId: string, text: string): Promise<string> {\n // Try as a thread channel first, then fall back to posting in the channel\n try {\n const thread = await this.client.channels.fetch(threadOrMessageId);\n if (thread && (thread.isThread() || thread.isTextBased())) {\n return discordRetry(async () => {\n const msg = await (thread as ThreadChannel).send(text);\n return msg.id;\n });\n }\n } catch {\n // Not a thread channel, treat as message ID for reply\n }\n return this.postReply(channelId, threadOrMessageId, text);\n }\n\n async deleteMessageRaw(channelId: string, messageId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.delete();\n } catch {\n // Ignore if already deleted\n }\n }\n\n async sendTyping(channelId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n await ch.sendTyping();\n } catch {\n // Non-fatal\n }\n }\n\n async uploadFile(channelId: string, filePath: string, title?: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await ch.send({ files: [{ attachment: fileContent, name: fileName }] });\n });\n }\n\n async sendDirectMessage(userId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const user = await this.client.users.fetch(userId);\n const msg = await user.send(text);\n return msg.id;\n });\n }\n\n async postPrivate(_conversationId: string, userId: string, text: string): Promise<void> {\n await this.sendDirectMessage(userId, text);\n }\n\n getAllChannels(): { id: string; name: string }[] {\n return Array.from(this.channels.values());\n }\n\n getAllUsers(): { id: string; userName: string; displayName: string }[] {\n return Array.from(this.users.values());\n }\n\n logToFile(channelId: string, entry: object): void {\n appendChannelLog(this.workingDir, channelId, entry);\n }\n\n logBotResponse(channelId: string, text: string, ts: string): void {\n appendBotResponseLog(this.workingDir, channelId, text, ts);\n }\n\n /**\n * Process attachments from a Discord message.\n * Downloads files before returning so the agent can read them immediately.\n */\n async processAttachments(\n channelId: string,\n attachments: Collection<string, Attachment>,\n _messageId: string,\n ): Promise<{ name: string; localPath: string }[]> {\n const downloads: Array<Promise<{ name: string; localPath: string } | null>> = [];\n\n // Discord attachments Collection - iterate over values\n for (const attachment of attachments.values()) {\n if (!attachment.name) {\n log.logWarning(\"Discord attachment missing name, skipping\", attachment.url);\n continue;\n }\n\n const ts = Date.now();\n const sanitizedName = attachment.name.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const filename = `${ts}_${sanitizedName}`;\n const localPath = `${channelId}/attachments/${filename}`;\n const fullDir = join(this.workingDir, channelId, \"attachments\");\n const result = {\n name: attachment.name,\n localPath,\n };\n\n downloads.push(\n this.downloadAttachment(fullDir, filename, attachment.url)\n .then(() => result)\n .catch((err) => {\n log.logWarning(`Failed to download Discord attachment`, `${filename}: ${err}`);\n return null;\n }),\n );\n }\n\n const results = await Promise.all(downloads);\n return results.filter(\n (attachment): attachment is { name: string; localPath: string } => attachment !== null,\n );\n }\n\n /**\n * Download an attachment from URL to local file\n */\n private async downloadAttachment(dir: string, filename: string, url: string): Promise<void> {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n writeFileSync(join(dir, filename), Buffer.from(buffer));\n } catch (err) {\n throw new Error(`Download failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): ChannelQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new ChannelQueue(\"Discord\");\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private resolveStopTarget(channelId: string, sessionKey: string): string | null {\n const directTarget = resolveStopTarget({\n handler: this.handler,\n conversationId: channelId,\n sessionKey,\n });\n if (directTarget) return directTarget;\n if (sessionKey !== channelId) return null;\n return resolveOnlyScopedStopTarget(this.handler, channelId);\n }\n\n private loadCachedGuildData(): void {\n for (const guild of this.client.guilds.cache.values()) {\n for (const channel of guild.channels.cache.values()) {\n if (channel.isTextBased() && \"name\" in channel) {\n this.channels.set(channel.id, { id: channel.id, name: channel.name ?? channel.id });\n }\n }\n for (const member of guild.members.cache.values()) {\n this.users.set(member.id, {\n id: member.id,\n userName: member.user.username,\n displayName: member.displayName,\n });\n }\n }\n }\n\n private stripBotMention(text: string): string {\n if (!this.botUserId) return text;\n return text.replace(new RegExp(`<@!?${this.botUserId}>`, \"g\"), \"\").trim();\n }\n\n private resolveConversationContext(input: {\n channelId: string;\n inGuild: boolean;\n isThread: boolean;\n parentChannelId?: string | null;\n referencedMsgId?: string;\n }): { conversationId: string; threadTs?: string } {\n if (!input.inGuild) {\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n if (input.isThread) {\n return {\n conversationId: input.parentChannelId ?? input.channelId,\n threadTs: input.channelId,\n };\n }\n\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n private createSlashCommandAdapters(\n interaction: ChatInputCommandInteraction,\n commandText: string,\n sessionKey: string,\n conversationId: string,\n ): BotAdapters {\n const isDM = !interaction.inGuild();\n const userId = interaction.user.id;\n const userName = interaction.user.username;\n const platform = this.getPlatformInfo();\n const shouldUseEphemeral = !isDM;\n\n const message: ChatMessage = {\n id: interaction.id,\n sessionKey,\n conversationKind: isDM ? \"direct\" : \"shared\",\n userId,\n userName,\n text: commandText,\n attachments: [],\n };\n\n const respondPrivately = async (text: string, replace = false): Promise<void> => {\n if (interaction.replied || interaction.deferred) {\n if (replace) {\n await interaction.editReply({ content: text });\n } else {\n await interaction.followUp({ content: text, ephemeral: shouldUseEphemeral });\n }\n return;\n }\n\n await interaction.reply({ content: text, ephemeral: shouldUseEphemeral });\n };\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n await respondPrivately(text);\n },\n replaceResponse: async (text: string) => {\n await respondPrivately(text, true);\n },\n respondDiagnostic: async (text: string) => {\n await respondPrivately(text);\n },\n respondToolResult: async (result: ChatToolResult) => {\n const duration = (result.durationMs / 1000).toFixed(1);\n const formatted = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName} (${duration}s)\\n${result.result}`;\n await respondPrivately(formatted);\n },\n setTyping: async () => {},\n setWorking: async () => {},\n uploadFile: async (filePath: string, title?: string) => {\n await this.uploadFile(conversationId, filePath, title);\n },\n deleteResponse: async () => {},\n };\n\n return { message, responseCtx, platform };\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (\n interaction.commandName !== \"login\" &&\n interaction.commandName !== \"session\" &&\n interaction.commandName !== \"new\" &&\n interaction.commandName !== \"stop\" &&\n interaction.commandName !== \"pi-model\"\n ) {\n return;\n }\n\n const isDM = !interaction.inGuild();\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: interaction.channelId,\n inGuild: interaction.inGuild(),\n isThread: interaction.channel?.isThread() ?? false,\n parentChannelId:\n interaction.channel && \"parentId\" in interaction.channel\n ? interaction.channel.parentId\n : null,\n });\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n messageId: interaction.id,\n persistentTopLevel: true,\n threadTs,\n });\n const modelOption =\n interaction.commandName === \"pi-model\"\n ? interaction.options.getString(\"model\")?.trim()\n : undefined;\n const commandText =\n interaction.commandName === \"pi-model\" && modelOption\n ? `/${interaction.commandName} ${modelOption}`\n : `/${interaction.commandName}`;\n\n this.logToFile(conversationId, {\n date: new Date(interaction.createdTimestamp).toISOString(),\n ts: interaction.id,\n ...(threadTs ? { threadTs } : {}),\n user: interaction.user.id,\n userName: interaction.user.username,\n text: commandText,\n attachments: [],\n isBot: false,\n });\n\n const adapters = this.createSlashCommandAdapters(\n interaction,\n commandText,\n sessionKey,\n conversationId,\n );\n try {\n if (interaction.commandName === \"new\") {\n await this.handler.handleNew(sessionKey, conversationId, this);\n return;\n }\n\n if (interaction.commandName === \"stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n await this.handler.handleStop(stopTarget, conversationId, this);\n } else {\n await adapters.responseCtx.respond(formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n const event: BotEvent = {\n type: \"dm\",\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n ts: interaction.id,\n thread_ts: threadTs,\n sessionKey,\n user: interaction.user.id,\n text: commandText,\n attachments: [],\n };\n\n await this.handler.handleEvent(event, this, adapters, false);\n } catch (err) {\n log.logWarning(\n \"Discord slash command error\",\n err instanceof Error ? err.message : String(err),\n );\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: `${interaction.commandName} command failed. Please try again later.`,\n ephemeral: !isDM,\n });\n }\n }\n });\n\n this.client.on(Events.MessageCreate, async (msg: Message) => {\n // Skip messages from before startup\n if (msg.createdTimestamp < this.startupTime) return;\n // Skip bot messages\n if (msg.author.bot) return;\n const isDM = msg.channel.type === 1; // ChannelType.DM = 1\n const isInThread = msg.channel.isThread();\n const referencedMsgId = msg.reference?.messageId;\n const isThreadReply = isInThread || !!referencedMsgId;\n const isMentioned = msg.mentions.users.has(this.botUserId ?? \"\");\n // Shared-channel top-level messages require a mention. Thread/reply follow-ups do not.\n if (!isDM && !isMentioned && !isThreadReply) return;\n\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: msg.channelId,\n inGuild: !isDM,\n isThread: isInThread,\n parentChannelId: \"parentId\" in msg.channel ? msg.channel.parentId : null,\n referencedMsgId,\n });\n const userId = msg.author.id;\n const userName = msg.author.username;\n const msgId = msg.id;\n\n // Track user\n this.users.set(userId, {\n id: userId,\n userName,\n displayName: msg.member?.displayName ?? userName,\n });\n\n // Track channel\n if (!this.channels.has(conversationId) && \"name\" in msg.channel) {\n const ch = msg.channel as TextChannel | NewsChannel;\n this.channels.set(conversationId, { id: conversationId, name: ch.name });\n }\n\n const conversationKind = isDM ? \"direct\" : \"shared\";\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind,\n messageId: msgId,\n persistentTopLevel: true,\n threadTs,\n });\n\n const cleanedText = this.stripBotMention(msg.content);\n\n const processedAttachments = await this.processAttachments(\n conversationId,\n msg.attachments,\n msgId,\n );\n\n const event: DiscordEvent = {\n type: isDM ? \"dm\" : \"mention\",\n conversationId,\n conversationKind,\n ts: msgId,\n thread_ts: threadTs,\n sessionKey,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log message\n this.logToFile(conversationId, {\n date: msg.createdAt.toISOString(),\n ts: msgId,\n ...(!isDM && threadTs ? { threadTs } : {}),\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n // Handle stop command\n if (cleanedText.toLowerCase() === \"stop\" || cleanedText.toLowerCase() === \"/stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n this.handler.handleStop(stopTarget, conversationId, this);\n } else {\n await this.postMessage(conversationId, formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n this.getQueue(sessionKey).enqueue(() => {\n const adapters = createDiscordAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n });\n }\n\n private async fetchTextChannel(\n channelId: string,\n ): Promise<TextChannel | DMChannel | NewsChannel | ThreadChannel> {\n const ch = await this.client.channels.fetch(channelId);\n if (!ch || !ch.isTextBased()) {\n throw new Error(`Channel ${channelId} is not a text channel`);\n }\n return ch as TextChannel | DMChannel | NewsChannel | ThreadChannel;\n }\n}\n"]}
1
+ {"version":3,"file":"bot.js","sourceRoot":"","sources":["../../../src/adapters/discord/bot.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApplicationCommandOptionType,\n Client,\n Events,\n GatewayIntentBits,\n Partials,\n type ChatInputCommandInteraction,\n type Collection,\n type Message,\n type Attachment,\n type TextChannel,\n type DMChannel,\n type NewsChannel,\n type ThreadChannel,\n} from \"discord.js\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\n\nimport type {\n Bot,\n BotAdapters,\n BotEvent,\n BotHandler,\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { resolveChatSessionKey } from \"../../session-policy.js\";\nimport { formatNothingRunning } from \"../../ui-copy.js\";\nimport {\n appendBotResponseLog,\n appendChannelLog,\n ChannelQueue,\n resolveOnlyScopedStopTarget,\n resolveStopTarget,\n withRetry,\n} from \"../shared.js\";\nimport { createDiscordAdapters } from \"./context.js\";\n\n// discord.js: DiscordAPIError exposes `.status` (HTTP status) and a `.code`.\n// RateLimitError fires when the internal queue gives up. Both should retry.\nfunction discordIsRateLimited(err: Error): boolean {\n if ((err as { status?: number }).status === 429) return true;\n if ((err as { httpStatus?: number }).httpStatus === 429) return true;\n if (err.name === \"RateLimitError\") return true;\n return false;\n}\n\nconst discordRetry = <T>(fn: () => Promise<T>): Promise<T> =>\n withRetry(fn, { isRateLimited: discordIsRateLimited });\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DiscordEvent extends BotEvent {\n type: \"mention\" | \"dm\";\n userName?: string;\n}\n\n// ============================================================================\n// DiscordBot\n// ============================================================================\n\nexport class DiscordBot implements Bot {\n private client: Client;\n private handler: BotHandler;\n private workingDir: string;\n private botUserId: string | null = null;\n private queues = new Map<string, ChannelQueue>();\n private startupTime: number = 0;\n private channels = new Map<string, { id: string; name: string }>();\n private users = new Map<string, { id: string; userName: string; displayName: string }>();\n\n constructor(handler: BotHandler, config: { token: string; workingDir: string }) {\n this.handler = handler;\n this.workingDir = config.workingDir;\n this.client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n ],\n partials: [Partials.Channel, Partials.Message],\n });\n }\n\n // ==========================================================================\n // Public API (implements Bot)\n // ==========================================================================\n\n async start(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.client.once(Events.ClientReady, async (readyClient) => {\n this.botUserId = readyClient.user.id;\n this.startupTime = Date.now();\n log.logConnected();\n log.logInfo(`Discord bot started as ${readyClient.user.tag}`);\n this.loadCachedGuildData();\n this.setupEventHandlers();\n try {\n await readyClient.application.commands.set([\n {\n name: \"login\",\n description: \"Store credentials in your private vault\",\n },\n {\n name: \"session\",\n description: \"Open the current session in the web viewer\",\n },\n {\n name: \"new\",\n description: \"Reset conversation history and start fresh\",\n },\n {\n name: \"stop\",\n description: \"Stop the current conversation\",\n },\n {\n name: \"model\",\n description: \"Switch this conversation's LLM model\",\n options: [\n {\n name: \"model\",\n description: \"provider/model[:thinking], e.g. anthropic/claude-sonnet-4-5:off\",\n type: ApplicationCommandOptionType.String,\n required: false,\n },\n ],\n },\n {\n name: \"sandbox\",\n description: \"Show or temporarily boost this conversation's sandbox limits\",\n options: [\n {\n name: \"action\",\n description: \"Use 'boost' to temporarily apply the configured boost limits\",\n type: ApplicationCommandOptionType.String,\n required: false,\n },\n ],\n },\n ]);\n } catch (err) {\n log.logWarning(\n \"Failed to register Discord slash commands\",\n err instanceof Error ? err.message : String(err),\n );\n }\n resolve();\n });\n this.client.once(Events.Error, reject);\n this.client.login(process.env.MAMA_DISCORD_BOT_TOKEN!).catch(reject);\n });\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channel);\n const msg = await ch.send(text);\n return msg.id;\n });\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n await this.updateMessageRaw(channel, ts, text);\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const conversationId = event.conversationId;\n const queue = this.getQueue(conversationId);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const adapters = createDiscordAdapters(event as DiscordEvent, this, true);\n return this.handler.handleEvent(event, this, adapters, true);\n });\n return true;\n }\n\n getPlatformInfo(): PlatformInfo {\n return {\n name: \"discord\",\n formattingGuide:\n \"## Discord Formatting (Markdown)\\nBold: **text**, Italic: *text*, Code: `code`, Block: ```language\\ncode```\\nLinks: [text](url)\",\n channels: this.getAllChannels(),\n users: this.getAllUsers(),\n diagnostics: {\n showUsageSummary: false,\n },\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async updateMessageRaw(channelId: string, messageId: string, text: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.edit(text);\n });\n }\n\n async postReply(channelId: string, replyToId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const replyTarget = await ch.messages.fetch(replyToId);\n const sent = await replyTarget.reply(text);\n return sent.id;\n });\n }\n\n async postInThread(channelId: string, threadOrMessageId: string, text: string): Promise<string> {\n // Try as a thread channel first, then fall back to posting in the channel\n try {\n const thread = await this.client.channels.fetch(threadOrMessageId);\n if (thread && (thread.isThread() || thread.isTextBased())) {\n return discordRetry(async () => {\n const msg = await (thread as ThreadChannel).send(text);\n return msg.id;\n });\n }\n } catch {\n // Not a thread channel, treat as message ID for reply\n }\n return this.postReply(channelId, threadOrMessageId, text);\n }\n\n async deleteMessageRaw(channelId: string, messageId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.delete();\n } catch {\n // Ignore if already deleted\n }\n }\n\n async sendTyping(channelId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n await ch.sendTyping();\n } catch {\n // Non-fatal\n }\n }\n\n async uploadFile(channelId: string, filePath: string, title?: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await ch.send({ files: [{ attachment: fileContent, name: fileName }] });\n });\n }\n\n async sendDirectMessage(userId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const user = await this.client.users.fetch(userId);\n const msg = await user.send(text);\n return msg.id;\n });\n }\n\n async postPrivate(_conversationId: string, userId: string, text: string): Promise<void> {\n await this.sendDirectMessage(userId, text);\n }\n\n getAllChannels(): { id: string; name: string }[] {\n return Array.from(this.channels.values());\n }\n\n getAllUsers(): { id: string; userName: string; displayName: string }[] {\n return Array.from(this.users.values());\n }\n\n logToFile(channelId: string, entry: object): void {\n appendChannelLog(this.workingDir, channelId, entry);\n }\n\n logBotResponse(channelId: string, text: string, ts: string): void {\n appendBotResponseLog(this.workingDir, channelId, text, ts);\n }\n\n /**\n * Process attachments from a Discord message.\n * Downloads files before returning so the agent can read them immediately.\n */\n async processAttachments(\n channelId: string,\n attachments: Collection<string, Attachment>,\n _messageId: string,\n ): Promise<{ name: string; localPath: string }[]> {\n const downloads: Array<Promise<{ name: string; localPath: string } | null>> = [];\n\n // Discord attachments Collection - iterate over values\n for (const attachment of attachments.values()) {\n if (!attachment.name) {\n log.logWarning(\"Discord attachment missing name, skipping\", attachment.url);\n continue;\n }\n\n const ts = Date.now();\n const sanitizedName = attachment.name.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const filename = `${ts}_${sanitizedName}`;\n const localPath = `${channelId}/attachments/${filename}`;\n const fullDir = join(this.workingDir, channelId, \"attachments\");\n const result = {\n name: attachment.name,\n localPath,\n };\n\n downloads.push(\n this.downloadAttachment(fullDir, filename, attachment.url)\n .then(() => result)\n .catch((err) => {\n log.logWarning(`Failed to download Discord attachment`, `${filename}: ${err}`);\n return null;\n }),\n );\n }\n\n const results = await Promise.all(downloads);\n return results.filter(\n (attachment): attachment is { name: string; localPath: string } => attachment !== null,\n );\n }\n\n /**\n * Download an attachment from URL to local file\n */\n private async downloadAttachment(dir: string, filename: string, url: string): Promise<void> {\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n writeFileSync(join(dir, filename), Buffer.from(buffer));\n } catch (err) {\n throw new Error(`Download failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): ChannelQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new ChannelQueue(\"Discord\");\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private resolveStopTarget(channelId: string, sessionKey: string): string | null {\n const directTarget = resolveStopTarget({\n handler: this.handler,\n conversationId: channelId,\n sessionKey,\n });\n if (directTarget) return directTarget;\n if (sessionKey !== channelId) return null;\n return resolveOnlyScopedStopTarget(this.handler, channelId);\n }\n\n private loadCachedGuildData(): void {\n for (const guild of this.client.guilds.cache.values()) {\n for (const channel of guild.channels.cache.values()) {\n if (channel.isTextBased() && \"name\" in channel) {\n this.channels.set(channel.id, { id: channel.id, name: channel.name ?? channel.id });\n }\n }\n for (const member of guild.members.cache.values()) {\n this.users.set(member.id, {\n id: member.id,\n userName: member.user.username,\n displayName: member.displayName,\n });\n }\n }\n }\n\n private stripBotMention(text: string): string {\n if (!this.botUserId) return text;\n return text.replace(new RegExp(`<@!?${this.botUserId}>`, \"g\"), \"\").trim();\n }\n\n private resolveConversationContext(input: {\n channelId: string;\n inGuild: boolean;\n isThread: boolean;\n parentChannelId?: string | null;\n referencedMsgId?: string;\n }): { conversationId: string; threadTs?: string } {\n if (!input.inGuild) {\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n if (input.isThread) {\n return {\n conversationId: input.parentChannelId ?? input.channelId,\n threadTs: input.channelId,\n };\n }\n\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n private createSlashCommandAdapters(\n interaction: ChatInputCommandInteraction,\n commandText: string,\n sessionKey: string,\n conversationId: string,\n ): BotAdapters {\n const isDM = !interaction.inGuild();\n const userId = interaction.user.id;\n const userName = interaction.user.username;\n const platform = this.getPlatformInfo();\n const shouldUseEphemeral = !isDM;\n\n const message: ChatMessage = {\n id: interaction.id,\n sessionKey,\n conversationKind: isDM ? \"direct\" : \"shared\",\n userId,\n userName,\n text: commandText,\n attachments: [],\n };\n\n const respondPrivately = async (text: string, replace = false): Promise<void> => {\n if (interaction.replied || interaction.deferred) {\n if (replace) {\n await interaction.editReply({ content: text });\n } else {\n await interaction.followUp({ content: text, ephemeral: shouldUseEphemeral });\n }\n return;\n }\n\n await interaction.reply({ content: text, ephemeral: shouldUseEphemeral });\n };\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n await respondPrivately(text);\n },\n replaceResponse: async (text: string) => {\n await respondPrivately(text, true);\n },\n respondDiagnostic: async (text: string) => {\n await respondPrivately(text);\n },\n respondToolResult: async (result: ChatToolResult) => {\n const duration = (result.durationMs / 1000).toFixed(1);\n const formatted = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName} (${duration}s)\\n${result.result}`;\n await respondPrivately(formatted);\n },\n setTyping: async () => {},\n setWorking: async () => {},\n uploadFile: async (filePath: string, title?: string) => {\n await this.uploadFile(conversationId, filePath, title);\n },\n deleteResponse: async () => {},\n };\n\n return { message, responseCtx, platform };\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n if (\n interaction.commandName !== \"login\" &&\n interaction.commandName !== \"session\" &&\n interaction.commandName !== \"new\" &&\n interaction.commandName !== \"stop\" &&\n interaction.commandName !== \"model\" &&\n interaction.commandName !== \"sandbox\"\n ) {\n return;\n }\n\n const isDM = !interaction.inGuild();\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: interaction.channelId,\n inGuild: interaction.inGuild(),\n isThread: interaction.channel?.isThread() ?? false,\n parentChannelId:\n interaction.channel && \"parentId\" in interaction.channel\n ? interaction.channel.parentId\n : null,\n });\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n messageId: interaction.id,\n persistentTopLevel: true,\n threadTs,\n });\n const modelOption =\n interaction.commandName === \"model\"\n ? interaction.options.getString(\"model\")?.trim()\n : undefined;\n const sandboxAction =\n interaction.commandName === \"sandbox\"\n ? interaction.options.getString(\"action\")?.trim()\n : undefined;\n const commandArg = modelOption ?? sandboxAction;\n const commandText = commandArg\n ? `/${interaction.commandName} ${commandArg}`\n : `/${interaction.commandName}`;\n\n this.logToFile(conversationId, {\n date: new Date(interaction.createdTimestamp).toISOString(),\n ts: interaction.id,\n ...(threadTs ? { threadTs } : {}),\n user: interaction.user.id,\n userName: interaction.user.username,\n text: commandText,\n attachments: [],\n isBot: false,\n });\n\n const adapters = this.createSlashCommandAdapters(\n interaction,\n commandText,\n sessionKey,\n conversationId,\n );\n try {\n if (interaction.commandName === \"new\") {\n await this.handler.handleNew(sessionKey, conversationId, this);\n return;\n }\n\n if (interaction.commandName === \"stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n await this.handler.handleStop(stopTarget, conversationId, this);\n } else {\n await adapters.responseCtx.respond(formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n const event: BotEvent = {\n type: \"dm\",\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n ts: interaction.id,\n thread_ts: threadTs,\n sessionKey,\n user: interaction.user.id,\n text: commandText,\n attachments: [],\n };\n\n await this.handler.handleEvent(event, this, adapters, false);\n } catch (err) {\n log.logWarning(\n \"Discord slash command error\",\n err instanceof Error ? err.message : String(err),\n );\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: `${interaction.commandName} command failed. Please try again later.`,\n ephemeral: !isDM,\n });\n }\n }\n });\n\n this.client.on(Events.MessageCreate, async (msg: Message) => {\n // Skip messages from before startup\n if (msg.createdTimestamp < this.startupTime) return;\n // Skip bot messages\n if (msg.author.bot) return;\n const isDM = msg.channel.type === 1; // ChannelType.DM = 1\n const isInThread = msg.channel.isThread();\n const referencedMsgId = msg.reference?.messageId;\n const isThreadReply = isInThread || !!referencedMsgId;\n const isMentioned = msg.mentions.users.has(this.botUserId ?? \"\");\n // Shared-channel top-level messages require a mention. Thread/reply follow-ups do not.\n if (!isDM && !isMentioned && !isThreadReply) return;\n\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: msg.channelId,\n inGuild: !isDM,\n isThread: isInThread,\n parentChannelId: \"parentId\" in msg.channel ? msg.channel.parentId : null,\n referencedMsgId,\n });\n const userId = msg.author.id;\n const userName = msg.author.username;\n const msgId = msg.id;\n\n // Track user\n this.users.set(userId, {\n id: userId,\n userName,\n displayName: msg.member?.displayName ?? userName,\n });\n\n // Track channel\n if (!this.channels.has(conversationId) && \"name\" in msg.channel) {\n const ch = msg.channel as TextChannel | NewsChannel;\n this.channels.set(conversationId, { id: conversationId, name: ch.name });\n }\n\n const conversationKind = isDM ? \"direct\" : \"shared\";\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind,\n messageId: msgId,\n persistentTopLevel: true,\n threadTs,\n });\n\n const cleanedText = this.stripBotMention(msg.content);\n\n const processedAttachments = await this.processAttachments(\n conversationId,\n msg.attachments,\n msgId,\n );\n\n const event: DiscordEvent = {\n type: isDM ? \"dm\" : \"mention\",\n conversationId,\n conversationKind,\n ts: msgId,\n thread_ts: threadTs,\n sessionKey,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log message\n this.logToFile(conversationId, {\n date: msg.createdAt.toISOString(),\n ts: msgId,\n ...(!isDM && threadTs ? { threadTs } : {}),\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n // Handle stop command\n if (cleanedText.toLowerCase() === \"stop\" || cleanedText.toLowerCase() === \"/stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n this.handler.handleStop(stopTarget, conversationId, this);\n } else {\n await this.postMessage(conversationId, formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n this.getQueue(sessionKey).enqueue(() => {\n const adapters = createDiscordAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n });\n }\n\n private async fetchTextChannel(\n channelId: string,\n ): Promise<TextChannel | DMChannel | NewsChannel | ThreadChannel> {\n const ch = await this.client.channels.fetch(channelId);\n if (!ch || !ch.isTextBased()) {\n throw new Error(`Channel ${channelId} is not a text channel`);\n }\n return ch as TextChannel | DMChannel | NewsChannel | ThreadChannel;\n }\n}\n"]}
@@ -68,6 +68,7 @@ export declare class SlackBot implements Bot {
68
68
  private workingDir;
69
69
  private store;
70
70
  private botUserId;
71
+ private botId;
71
72
  private ownMentionRegex;
72
73
  private startupTs;
73
74
  private users;
@@ -89,7 +90,12 @@ export declare class SlackBot implements Bot {
89
90
  private stripOwnMention;
90
91
  postMessage(channel: string, text: string): Promise<string>;
91
92
  postEphemeral(channel: string, user: string, text: string): Promise<void>;
93
+ postEphemeralBlocks(channel: string, user: string, text: string, blocks: object[]): Promise<void>;
94
+ postMessageBlocks(channel: string, text: string, blocks: object[]): Promise<string>;
92
95
  postPrivate(conversationId: string, userId: string, text: string): Promise<void>;
96
+ postPrivateDiagnostic(conversationId: string, userId: string, text: string, options?: {
97
+ style?: "muted" | "error";
98
+ }): Promise<void>;
93
99
  openDirectMessage(userId: string): Promise<string>;
94
100
  updateMessage(channel: string, ts: string, text: string): Promise<void>;
95
101
  deleteMessage(channel: string, ts: string): Promise<void>;
@@ -117,9 +123,11 @@ export declare class SlackBot implements Bot {
117
123
  private routeSlashLoginCommand;
118
124
  private routeSlashNewCommand;
119
125
  private routeSlashModelCommand;
126
+ private routeSlashSandboxCommand;
120
127
  private routeSlashSessionCommand;
121
128
  private setupEventHandlers;
122
129
  private logUserMessage;
130
+ private logExternalBotMessage;
123
131
  private getExistingTimestamps;
124
132
  private backfillChannel;
125
133
  private backfillAllChannels;