feat(FN-3971): add aggregate mailbox view for all agents

Adds an "All Agents" unified mailbox view to the dashboard, including a new aggregate API endpoint in `@fusion/core`, updated MailboxView UI with selection controls, CSS styling, and comprehensive tests across core and dashboard packages, with documentation updates.

Fusion-Task-Id: FN-3971
This commit is contained in:
Fusion
2026-05-10 21:37:30 -07:00
committed by gsxdsm
parent a156b74cdc
commit a3d67e088f
10 changed files with 316 additions and 16 deletions

View File

@@ -703,6 +703,72 @@ describe("MessageStore", () => {
});
});
describe("getAllAgentToAgentMessages() / getUnreadAgentToAgentCount()", () => {
it("returns newest-first agent-to-agent messages only", () => {
store.sendMessage({
fromId: "agent-1",
fromType: "agent",
toId: "agent-2",
toType: "agent",
content: "first",
type: "agent-to-agent",
});
const second = store.sendMessage({
fromId: "agent-2",
fromType: "agent",
toId: "agent-1",
toType: "agent",
content: "second",
type: "agent-to-agent",
});
store.sendMessage({
fromId: "agent-1",
fromType: "agent",
toId: "dashboard",
toType: "user",
content: "not included",
type: "agent-to-user",
});
const messages = store.getAllAgentToAgentMessages();
expect(messages).toHaveLength(2);
expect(messages[0].id).toBe(second.id);
expect(messages.every((message) => message.type === "agent-to-agent")).toBe(true);
});
it("counts unread agent-to-agent messages only", () => {
const unread = store.sendMessage({
fromId: "agent-1",
fromType: "agent",
toId: "agent-2",
toType: "agent",
content: "unread",
type: "agent-to-agent",
});
const read = store.sendMessage({
fromId: "agent-2",
fromType: "agent",
toId: "agent-1",
toType: "agent",
content: "read",
type: "agent-to-agent",
});
store.sendMessage({
fromId: "agent-1",
fromType: "agent",
toId: "dashboard",
toType: "user",
content: "non-agent",
type: "agent-to-user",
});
store.markAsRead(read.id);
expect(store.getUnreadAgentToAgentCount()).toBe(1);
expect(store.getAllAgentToAgentMessages().map((message) => message.id)).toContain(unread.id);
});
});
describe("events", () => {
it("emits message:sent event on send", () => {
const events: Message[] = [];

View File

@@ -417,6 +417,32 @@ export class MessageStore extends EventEmitter<MessageStoreEvents> {
};
}
/**
* Get all agent-to-agent messages across all agents.
* @returns Array of messages (newest first)
*/
getAllAgentToAgentMessages(): Message[] {
const rows = this.db.prepare(`
SELECT * FROM messages
WHERE type = ?
ORDER BY createdAt DESC, rowid DESC
`).all("agent-to-agent");
return (rows as unknown as MessageRow[]).map((row) => this.rowToMessage(row));
}
/**
* Get unread count across all agent-to-agent messages.
*/
getUnreadAgentToAgentCount(): number {
const row = this.db.prepare(`
SELECT COUNT(*) as count FROM messages
WHERE type = ? AND read = 0
`).get("agent-to-agent") as { count: number } | undefined;
return row?.count ?? 0;
}
/**
* Set or update the hook used when messages are sent to agents.
*/