docs(FN-3887): clarify ntfy allowlist requirement for agent messages

- Update settings reference language for ntfy integration
- Clarify that agent messages require allowlisting configuration
- Keep notification setup guidance aligned with current behavior

Fusion-Task-Id: FN-3887
This commit is contained in:
Fusion
2026-05-09 14:03:57 -07:00
committed by gsxdsm
parent 13a3e15367
commit 2864f70608
6 changed files with 167 additions and 1 deletions

View File

@@ -0,0 +1,155 @@
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { beforeEach, afterEach, describe, expect, it, vi } from "vitest";
import { Database, MessageStore, TaskStore } from "@fusion/core";
import { NotificationService } from "../notification/notification-service.js";
function makeTempRoot(): string {
return mkdtempSync(join(tmpdir(), "fn-msg-notify-pipeline-"));
}
describe("message notification pipeline integration", () => {
let rootDir: string;
let taskStore: TaskStore;
let messageDb: Database;
let messageStore: MessageStore;
let service: NotificationService;
let fetchSpy: ReturnType<typeof vi.fn>;
beforeEach(async () => {
rootDir = makeTempRoot();
taskStore = new TaskStore(rootDir, undefined, { inMemoryDb: true });
await taskStore.init();
messageDb = new Database(join(rootDir, ".fusion"), { inMemory: true });
messageDb.init();
messageStore = new MessageStore(messageDb);
fetchSpy = vi.fn().mockResolvedValue(new Response(null, { status: 200 }));
vi.stubGlobal("fetch", fetchSpy);
service = new NotificationService(taskStore, { messageStore });
await service.start();
});
afterEach(async () => {
await service.stop();
vi.unstubAllGlobals();
messageDb.close();
taskStore.close();
rmSync(rootDir, { recursive: true, force: true });
});
it("sends and suppresses agent-to-agent notifications according to runtime ntfy settings", async () => {
await taskStore.updateGlobalSettings({
ntfyEnabled: true,
ntfyTopic: "test-topic",
ntfyEvents: ["message:agent-to-user", "message:agent-to-agent"],
});
messageStore.sendMessage({
fromId: "agent-A",
fromType: "agent",
toId: "agent-B",
toType: "agent",
content: "hi from agent A",
type: "agent-to-agent",
});
await vi.waitFor(() => {
expect(fetchSpy).toHaveBeenCalledTimes(1);
});
expect(fetchSpy.mock.calls[0]?.[0]).toBe("https://ntfy.sh/test-topic");
const firstOptions = fetchSpy.mock.calls[0]?.[1] as RequestInit;
const firstHeaders = firstOptions.headers as Record<string, string>;
expect(firstHeaders.Title).toBe("agent-A → agent-B");
expect(String(firstOptions.body)).toContain("agent-A messaged agent-B: hi from agent A");
messageStore.sendMessage({
fromId: "agent-A",
fromType: "agent",
toId: "agent-B",
toType: "agent",
content: "reply preview",
type: "agent-to-agent",
metadata: { replyTo: { messageId: "msg-origin" } },
});
await vi.waitFor(() => {
expect(fetchSpy).toHaveBeenCalledTimes(2);
});
const replyHeaders = (fetchSpy.mock.calls[1]?.[1] as RequestInit).headers as Record<string, string>;
expect(replyHeaders.Title).toBe("Re: reply preview");
await taskStore.updateGlobalSettings({ ntfyEvents: ["message:agent-to-user"] });
messageStore.sendMessage({
fromId: "agent-A",
fromType: "agent",
toId: "agent-B",
toType: "agent",
content: "should be filtered",
type: "agent-to-agent",
});
await new Promise((resolve) => setTimeout(resolve, 0));
expect(fetchSpy).toHaveBeenCalledTimes(2);
await taskStore.updateGlobalSettings({ ntfyEvents: ["message:agent-to-user", "message:agent-to-agent"] });
messageStore.sendMessage({
fromId: "agent-A",
fromType: "agent",
toId: "agent-B",
toType: "agent",
content: "enabled again",
type: "agent-to-agent",
});
await vi.waitFor(() => {
expect(fetchSpy).toHaveBeenCalledTimes(3);
});
await taskStore.updateGlobalSettings({ ntfyEnabled: false });
messageStore.sendMessage({
fromId: "agent-A",
fromType: "agent",
toId: "agent-B",
toType: "agent",
content: "disabled should suppress",
type: "agent-to-agent",
});
await new Promise((resolve) => setTimeout(resolve, 0));
expect(fetchSpy).toHaveBeenCalledTimes(3);
});
it("keeps agent-to-user notifications working", async () => {
await taskStore.updateGlobalSettings({
ntfyEnabled: true,
ntfyTopic: "test-topic",
ntfyEvents: ["message:agent-to-user", "message:agent-to-agent"],
});
messageStore.sendMessage({
fromId: "agent-A",
fromType: "agent",
toId: "user-1",
toType: "user",
content: "hello user",
type: "agent-to-user",
});
await vi.waitFor(() => {
expect(fetchSpy).toHaveBeenCalledTimes(1);
});
const request = fetchSpy.mock.calls[0]?.[1] as RequestInit;
const headers = request.headers as Record<string, string>;
expect(headers.Title).toBe("New message from agent-A");
expect(String(request.body)).toContain("agent-A → you: hello user");
});
});