diff --git a/package-lock.json b/package-lock.json index 798b4f0..4b4957b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "lettabot", "version": "1.0.0", + "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@clack/prompts": "^0.11.0", diff --git a/src/core/bot.ts b/src/core/bot.ts index 7f914fb..92d9903 100644 --- a/src/core/bot.ts +++ b/src/core/bot.ts @@ -443,6 +443,15 @@ export class LettaBot { // Helper to finalize and send current accumulated response const finalizeMessage = async () => { + // Check for silent marker - agent chose not to reply + if (response.trim() === '') { + console.log('[Bot] Agent chose not to reply (no-reply marker)'); + sentAnyMessage = true; + response = ''; + messageId = null; + lastUpdate = Date.now(); + return; + } if (response.trim()) { try { if (messageId) { @@ -521,8 +530,10 @@ export class LettaBot { response += streamMsg.content; // Stream updates only for channels that support editing (Telegram, Slack) + // Hold back streaming edits while response could still become const canEdit = adapter.supportsEditing?.() ?? true; - if (canEdit && Date.now() - lastUpdate > 500 && response.length > 0) { + const mayBeNoReply = ''.startsWith(response.trim()); + if (canEdit && !mayBeNoReply && Date.now() - lastUpdate > 500 && response.length > 0) { try { if (messageId) { await adapter.editMessage(msg.chatId, messageId, response); @@ -600,6 +611,13 @@ export class LettaBot { clearInterval(typingInterval); } + // Check for silent marker - agent chose not to reply + if (response.trim() === '') { + console.log('[Bot] Agent chose not to reply (no-reply marker)'); + sentAnyMessage = true; + response = ''; + } + // Send final response if (response.trim()) { try { diff --git a/src/core/formatter.test.ts b/src/core/formatter.test.ts index 21bdbab..793aea2 100644 --- a/src/core/formatter.test.ts +++ b/src/core/formatter.test.ts @@ -180,6 +180,18 @@ describe('formatMessageEnvelope', () => { const result = formatMessageEnvelope(msg); expect(result).toContain('**Mentioned**: yes'); }); + + it('includes no-reply hint for group chats', () => { + const msg = createMessage({ isGroup: true }); + const result = formatMessageEnvelope(msg); + expect(result).toContain(''); + }); + + it('omits no-reply hint for DMs', () => { + const msg = createMessage({ isGroup: false }); + const result = formatMessageEnvelope(msg); + expect(result).not.toContain('no-reply'); + }); }); describe('format hints', () => { diff --git a/src/core/formatter.ts b/src/core/formatter.ts index 1f110cb..2cdaf45 100644 --- a/src/core/formatter.ts +++ b/src/core/formatter.ts @@ -231,6 +231,7 @@ function buildChatContextLines(msg: InboundMessage, options: EnvelopeOptions): s if (msg.wasMentioned) { lines.push(`- **Mentioned**: yes`); } + lines.push(`- **Hint**: To skip replying, respond with exactly: \`\``); } else { lines.push(`- **Type**: Direct message`); } diff --git a/src/core/system-prompt.ts b/src/core/system-prompt.ts index f064232..c71883f 100644 --- a/src/core/system-prompt.ts +++ b/src/core/system-prompt.ts @@ -6,6 +6,8 @@ export const SYSTEM_PROMPT = `You are a self-improving AI agent with advanced me You are connected to a multi-channel messaging system (LettaBot) that allows you to communicate with users across Telegram, Slack, Discord, WhatsApp, and Signal. You run on a remote server and can execute tools, manage files, and interact with various services. +Not every message requires a response. Before replying, consider whether your response adds value. In group chats especially, avoid replying to messages not directed at you, simple acknowledgments, or conversations between other users. Quality over quantity — only reply when you have something meaningful to contribute. + # Communication System You communicate through multiple channels and trigger types. Understanding when your messages are delivered is critical: @@ -68,6 +70,21 @@ During heartbeats and background tasks: You don't need to notify the user about everything. Use judgment about what's worth interrupting them for. +## Choosing Not to Reply + +Not all messages warrant a response. If a message doesn't need a reply, respond with exactly: + +\`\` + +This suppresses the message so nothing is sent to the user. Use this for: +- Messages in a group not directed at you +- Simple acknowledgments (e.g., "ok", "thanks", thumbs up) +- Conversations between other users you don't need to join +- Notifications or updates that don't require a response +- Messages you've already addressed + +When in doubt, prefer \`\` over a low-value response. Users appreciate an agent that knows when to stay quiet. + ## Available Channels - **telegram** - Telegram messenger