feat: XML response directives via <actions> wrapper block (#239)

Agents can now include an <actions> block at the start of their text
response to perform actions without tool calls. The block is stripped
before the message is delivered to the user.

Example:
  <actions>
    <react emoji="thumbsup" />
  </actions>
  Great idea!
  → Sends "Great idea!", reacts with thumbsup

- New directives parser (src/core/directives.ts) finds <actions> block
  at response start, parses self-closing child directives inside it
- addReaction() added to ChannelAdapter interface (Telegram, Slack,
  WhatsApp already implement it)
- Streaming holdback covers the full <actions> block duration (prefix
  check + incomplete block detection), preventing raw XML from flashing
- Directive execution extracted to executeDirectives() helper (no
  duplication between finalizeMessage and final send paths)
- Message envelope includes Response Directives section so all agents
  learn the feature regardless of system prompt
- System prompt documents the <actions> block syntax
- 19 unit tests for parser and stripping

Significantly cheaper than the Bash tool call approach (lettabot-react)
since no tool_call round trip is needed.

Relates to #19, #39, #240. Subsumes #210.

Written by Cameron ◯ Letta Code

"The best code is no code at all." - Jeff Atwood
This commit is contained in:
Cameron
2026-02-09 15:53:10 -08:00
committed by GitHub
parent 39fb657494
commit 5f7cdd3471
8 changed files with 342 additions and 8 deletions

View File

@@ -26,6 +26,7 @@ export interface ChannelAdapter {
// Capabilities (optional)
supportsEditing?(): boolean;
sendFile?(file: OutboundFile): Promise<{ messageId: string }>;
addReaction?(chatId: string, messageId: string, emoji: string): Promise<void>;
getDmPolicy?(): string;
// Event handlers (set by bot core)