From 72186091975ec7b557dd3d33a4c00c0fce11bb79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:14:05 +0000 Subject: [PATCH 1/3] Initial plan From 04c49aab0957c65c8760ef3dbfd05b08b77dc008 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:16:39 +0000 Subject: [PATCH 2/3] Initial analysis: message tool already implemented Co-authored-by: rookdaemon <258400181+rookdaemon@users.noreply.github.com> --- package-lock.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index c689544..3d75d3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1145,6 +1145,7 @@ "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1184,6 +1185,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -1499,6 +1501,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1754,6 +1757,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2456,6 +2460,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2742,6 +2747,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2773,6 +2779,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", From 278901583d934415ae26003a48e74b449c8afe6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:18:53 +0000 Subject: [PATCH 3/3] Add channel configuration examples and integration test for message tool Co-authored-by: rookdaemon <258400181+rookdaemon@users.noreply.github.com> --- docs/example-daemon.yaml | 42 +++++++++++++++ test/agent.test.ts | 108 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/docs/example-daemon.yaml b/docs/example-daemon.yaml index 777ac49..625afd8 100644 --- a/docs/example-daemon.yaml +++ b/docs/example-daemon.yaml @@ -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"} diff --git a/test/agent.test.ts b/test/agent.test.ts index 3b6aa47..dba3049 100644 --- a/test/agent.test.ts +++ b/test/agent.test.ts @@ -469,4 +469,112 @@ describe("runAgent", () => { required: ["param1"], }); }); + + 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; + + 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 { + 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!" }), + } + ); + }); });