diff --git a/packages/mom/CHANGELOG.md b/packages/mom/CHANGELOG.md index ae7a4cd6..fcb26c73 100644 --- a/packages/mom/CHANGELOG.md +++ b/packages/mom/CHANGELOG.md @@ -9,6 +9,7 @@ - One-shot events: trigger at specific time (for reminders) - Periodic events: trigger on cron schedule (for recurring tasks) - `SlackBot.enqueueEvent()` for queueing events (max 5 per channel) +- `[SILENT]` response marker: deletes status message, posts nothing to Slack (for periodic events with nothing to report) - Events documentation in `docs/events.md` - System prompt section explaining events to mom diff --git a/packages/mom/docs/events.md b/packages/mom/docs/events.md index 1b7c3f23..5bbb0732 100644 --- a/packages/mom/docs/events.md +++ b/packages/mom/docs/events.md @@ -130,9 +130,16 @@ When an event is dequeued and executes: - For one-shot: `[EVENT:dentist.json:one-shot:2025-12-15T09:00:00+01:00] Remind Mario` - For periodic: `[EVENT:daily-inbox.json:periodic:0 9 * * 1-5] Check inbox` 3. After execution: + - If response is `[SILENT]`: delete status message, post nothing to Slack - Immediate and one-shot: delete the event file - Periodic: keep the file, event will trigger again on schedule +## Silent Completion + +For periodic events that check for activity (inbox, notifications, etc.), mom may find nothing to report. To avoid spamming the channel, mom can respond with just `[SILENT]`. This deletes the "Starting event..." status message and posts nothing to Slack. + +Example: A periodic event checks for new emails every 15 minutes. If there are no new emails, mom responds `[SILENT]`. If there are new emails, mom posts a summary. + ## File Naming Event files should have descriptive names ending in `.json`: diff --git a/packages/mom/src/agent.ts b/packages/mom/src/agent.ts index 42cb7d33..22fcba76 100644 --- a/packages/mom/src/agent.ts +++ b/packages/mom/src/agent.ts @@ -211,6 +211,9 @@ You receive a message like: \`\`\` Immediate and one-shot events auto-delete after triggering. Periodic events persist until you delete them. +### Silent Completion +For periodic events where there's nothing to report, respond with just \`[SILENT]\` (no other text). This deletes the status message and posts nothing to Slack. Use this to avoid spamming the channel when periodic checks find nothing actionable. + ### Debouncing When writing programs that create immediate events (email watchers, webhook handlers, etc.), always debounce. If 50 emails arrive in a minute, don't create 50 immediate events. Instead collect events over a window and create ONE immediate event summarizing what happened, or just signal "new activity, check inbox" rather than per-item events. Or simpler: use a periodic event to check for new items every N minutes instead of immediate events. @@ -687,7 +690,16 @@ function createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDi .map((c) => c.text) .join("\n") || ""; - if (finalText.trim()) { + // Check for [SILENT] marker - delete message instead of posting + if (finalText.trim() === "[SILENT]" || finalText.trim().startsWith("[SILENT]")) { + try { + await ctx.deleteMessage(); + log.logInfo("Silent response - deleted status message"); + } catch (err) { + const errMsg = err instanceof Error ? err.message : String(err); + log.logWarning("Failed to delete message for silent response", errMsg); + } + } else if (finalText.trim()) { try { const mainText = finalText.length > SLACK_MAX_LENGTH diff --git a/packages/mom/src/main.ts b/packages/mom/src/main.ts index 02a2bdd8..933a8464 100644 --- a/packages/mom/src/main.ts +++ b/packages/mom/src/main.ts @@ -207,6 +207,16 @@ function createSlackContext(event: SlackEvent, slack: SlackBot, state: ChannelSt }); await updatePromise; }, + + deleteMessage: async () => { + updatePromise = updatePromise.then(async () => { + if (messageTs) { + await slack.deleteMessage(event.channel, messageTs); + messageTs = null; + } + }); + await updatePromise; + }, }; } diff --git a/packages/mom/src/slack.ts b/packages/mom/src/slack.ts index 315172c0..6bfd93f4 100644 --- a/packages/mom/src/slack.ts +++ b/packages/mom/src/slack.ts @@ -62,6 +62,7 @@ export interface SlackContext { setTyping: (isTyping: boolean) => Promise; uploadFile: (filePath: string, title?: string) => Promise; setWorking: (working: boolean) => Promise; + deleteMessage: () => Promise; } export interface MomHandler { @@ -192,6 +193,10 @@ export class SlackBot { await this.webClient.chat.update({ channel, ts, text }); } + async deleteMessage(channel: string, ts: string): Promise { + await this.webClient.chat.delete({ channel, ts }); + } + async postInThread(channel: string, threadTs: string, text: string): Promise { await this.webClient.chat.postMessage({ channel, thread_ts: threadTs, text }); }