-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Add a message tool that sends messages to communication channels (Discord webhooks, Matrix rooms, or generic HTTP webhooks). Currently daemon-engine has no way to proactively communicate — it can only respond to incoming requests via the HTTP gateway.
This is a P1 gap identified in the gap analysis. Without this, the agent cannot alert, notify, or communicate through channels that Rook uses daily.
Specification
Tool Name: message
Parameters
interface MessageParams {
/** Channel identifier (matches a key in config channels section). */
channel: string;
/** Message content to send. */
content: string;
}JSON Schema (for LLM)
{
"type": "object",
"properties": {
"channel": { "type": "string", "description": "Channel identifier (e.g., 'discord-general', 'matrix-ops'). Must match a configured channel." },
"content": { "type": "string", "description": "Message content to send." }
},
"required": ["channel", "content"]
}Configuration (in daemon.yaml)
channels:
discord-general:
type: discord-webhook
url: ${DISCORD_WEBHOOK_URL}
matrix-ops:
type: matrix
homeserver: ${MATRIX_HOMESERVER}
room_id: ${MATRIX_ROOM_ID}
access_token: ${MATRIX_ACCESS_TOKEN}
alerts:
type: webhook
url: ${ALERT_WEBHOOK_URL}
method: POST
headers:
Content-Type: application/json
body_template: '{"text": "{{content}}"}'Behavior
Channel Resolution:
- Read
channelsfromcontext.config(passed via ToolContext) - Look up
channelparameter in config - If not found → throw:
"Unknown channel: {channel}. Available channels: {list}"
Channel Types:
discord-webhook:
- POST to webhook URL with JSON body:
{ "content": "{content}" } - Content-Type:
application/json - Truncate content to 2000 chars (Discord limit)
matrix:
- PUT to
{homeserver}/_matrix/client/v3/rooms/{room_id}/send/m.room.message/{txnId} - Authorization:
Bearer {access_token} - Body:
{ "msgtype": "m.text", "body": "{content}" } - Generate
txnIdfrom timestamp
webhook (generic):
- POST (or configured method) to URL
- Apply
body_templatewith{{content}}placeholder replaced - Use configured headers
- Fallback body:
{ "content": "{content}" }
- On success → return:
"Message sent to {channel}" - On failure → throw:
"Failed to send to {channel}: {status} {statusText}"
Implementation Guide
Files to Create/Modify
-
Create
src/tools/message.ts— Follow existing tool pattern:- Export
const message: ToolDefinition<MessageParams> - Export
messageWithEnv(params, env, config)for testability - Implement channel type dispatchers as private functions
- Use
env.http.fetch()for all HTTP calls
- Export
-
Modify
src/tools/registry.ts— Add tocreateBuiltInRegistry():const { message } = await import("./message.js"); registry.register("message", message);
-
Modify
src/config.ts(or equivalent config types) — Addchannelssection to Config type:interface ChannelConfig { type: "discord-webhook" | "matrix" | "webhook"; url?: string; homeserver?: string; room_id?: string; access_token?: string; method?: string; headers?: Record<string, string>; body_template?: string; } // Add to Config: channels?: Record<string, ChannelConfig>;
-
Create
test/tools/message.test.ts— Test cases (mockenv.http.fetch):- Sends Discord webhook with correct format
- Sends Matrix message with correct endpoint and auth
- Sends generic webhook with body_template substitution
- Throws on unknown channel
- Throws on HTTP error response
- Truncates Discord messages to 2000 chars
- Lists available channels in error message
Key Implementation Notes
- Use
env.http.fetch()— not global fetch - Config access: The
ToolContextalready has an optionalconfig?: Configfield. The message tool needscontext.config?.channels - No external dependencies — pure HTTP calls via Environment abstraction
- Start simple: Discord webhook is the highest priority (Rook's primary channel). Matrix and generic webhook can be follow-up if needed, but include the abstractions now
- Security: Never log webhook URLs or access tokens. Log only channel name and success/failure
Reference: Discord Webhook Call
async function sendDiscord(url: string, content: string, env: Environment): Promise<void> {
const truncated = content.length > 2000 ? content.slice(0, 1997) + "..." : content;
const response = await env.http.fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content: truncated }),
});
if (!response.ok) {
throw new Error(`Discord webhook failed: ${response.status} ${response.statusText}`);
}
}Acceptance Criteria
-
src/tools/message.tsexists and exportsmessageandmessageWithEnv - Tool is registered as
"message"increateBuiltInRegistry() - Discord webhook channel type works
- Generic webhook channel type works with body_template
- Config type updated with
channelssection - Throws descriptive error for unknown channels
- All test cases pass
- Uses
env.http.fetch()(not global fetch) - No secrets logged
-
npm run buildsucceeds -
npm run lintpasses