Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/example-daemon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,45 @@ observability:
# If not set, observability endpoints are accessible without auth
# Can use environment variable: token: ${OBSERVABILITY_TOKEN}
token: "your-secret-observability-token"

# Optional: Channel configurations for message tool
# Enables the agent to send messages to communication channels autonomously
channels:
# Discord webhook channel
# Get webhook URL from Discord: Server Settings > Integrations > Webhooks
discord-general:
type: "discord-webhook"
# Discord webhook URL (supports environment variable interpolation)
url: "https://discord.com/api/webhooks/123456789/your-webhook-token"
# Note: Messages are automatically truncated to Discord's 2000 character limit

# Matrix room channel
# Get access token from Matrix client and room ID from room settings
matrix-ops:
type: "matrix"
# Matrix homeserver URL
homeserver: "https://matrix.org"
# Matrix room ID (e.g., !AbCdEfGhIj:matrix.org)
room_id: "!your-room-id:matrix.org"
# Matrix access token (or use environment variable: ${MATRIX_TOKEN})
access_token: "your-matrix-access-token"

# Generic webhook (Slack, Teams, custom services)
alerts:
type: "webhook"
# Webhook URL
url: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
# HTTP method (optional, defaults to POST)
method: "POST"
# Custom headers (optional)
headers:
"X-Custom-Header": "value"
# Body template with {{content}} placeholder (optional)
# If not provided, defaults to JSON: {"content": "message"}
body_template: '{"text": "{{content}}"}'

# Simple webhook example (minimal configuration)
simple-webhook:
type: "webhook"
url: "https://example.com/webhook"
# Uses default POST method and JSON body: {"content": "message"}
108 changes: 108 additions & 0 deletions test/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@
},
http: {
...env.http,
fetch: async (url: string | URL) => {

Check failure on line 490 in test/agent.test.ts

View workflow job for this annotation

GitHub Actions / Build, Check, and Test

'url' is defined but never used
// Mock Brave Search API response
return {
ok: true,
Expand Down Expand Up @@ -571,6 +571,114 @@
expect(toolResultMessage).toBeDefined();
expect(toolResultMessage?.content).toContain("github.com/rookdaemon/daemon-engine");
});

it("should execute message tool within agent loop", async () => {
// Import and register the message tool
const { message } = await import("../src/tools/message.js");
toolRegistry.register("message", message);

// Mock fetch for Discord webhook
const mockFetch = vi.fn(async () => ({
ok: true,
status: 200,
statusText: "OK",
})) as unknown as (input: string | URL, init?: RequestInit) => Promise<Response>;

const mockEnv: Environment = {
...env,
http: {
...env.http,
fetch: mockFetch,
},
};

// Create tool context with config including channels
const toolContextWithConfig: ToolContext = {
...toolContext,
env: mockEnv,
config: {
model: {
provider: "anthropic",
name: "test-model",
apiKey: "test-key",
},
workspace: "/test/workspace",
server: { port: 3000 },
channels: {
"discord-general": {
type: "discord-webhook",
url: "https://discord.com/api/webhooks/123/abc",
},
},
},
};

let callCount = 0;
const mockProvider: LlmProvider = {
async generate(): Promise<ProviderResponse> {
callCount++;

if (callCount === 1) {
// First call: LLM decides to send a message
return {
type: "success",
result: "I'll send a message to Discord",
usage: { inputTokens: 10, outputTokens: 20, cacheReadTokens: 0, costUsd: 0.001 },
durationMs: 100,
stopReason: "tool_use",
toolCalls: [
{
id: "call_1",
name: "message",
input: {
channel: "discord-general",
content: "Hello from the agent!",
},
},
],
};
} else {
// Second call: Final response after tool execution
return {
type: "success",
result: "Message sent successfully to Discord",
usage: { inputTokens: 15, outputTokens: 10, cacheReadTokens: 0, costUsd: 0.0005 },
durationMs: 80,
stopReason: "end_turn",
};
}
},
async generateStream() {
throw new Error("Not implemented");
},
};

const result = await runAgent({
provider: mockProvider,
toolRegistry,
messages: [{ role: "user", content: "Send a message to discord-general" }],
systemPrompt: "You are a helpful assistant.",
toolContext: toolContextWithConfig,
env: mockEnv,
});

// Verify the agent completed the task
expect(callCount).toBe(2);
expect(result.turns).toBe(2);
expect(result.result).toBe("Message sent successfully to Discord");

// Verify the message tool was called correctly
expect(mockFetch).toHaveBeenCalledWith(
"https://discord.com/api/webhooks/123/abc",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ content: "Hello from the agent!" }),
}
);
});
});

describe("runAgent with edit tool", () => {
Expand Down
Loading