feat(FN-3484): normalize dashboard mailbox and user identity for messaging

This merge normalizes dashboard user identity and mailbox messaging (FN-3484, 4 steps), adds workflow step execution for plugins (FN-3490), and updates the restart integration store mock for plugin templates (FN-3096). Core changes touch the message store and store modules with identity normalizatio

Fusion-Task-Id: FN-3484
This commit is contained in:
Fusion
2026-05-05 06:27:05 -07:00
committed by gsxdsm
parent 0097212dd9
commit 59f6c8426e
11 changed files with 248 additions and 54 deletions

View File

@@ -4,6 +4,7 @@ import { join } from "node:path";
import { tmpdir } from "node:os";
import { Database } from "../db.js";
import { MessageStore } from "../message-store.js";
import { DASHBOARD_USER_ID } from "../types.js";
import type { Message, Mailbox } from "../types.js";
describe("MessageStore", () => {
@@ -123,6 +124,40 @@ describe("MessageStore", () => {
const result = store.getMessage("msg-nonexistent");
expect(result).toBeNull();
});
it.each(["dashboard", "user:dashboard", "User: user:dashboard"])(
"canonicalizes dashboard user alias '%s' when writing recipient",
(dashboardAlias) => {
const message = store.sendMessage({
fromId: "agent-1",
fromType: "agent",
toId: dashboardAlias,
toType: "user",
content: "Hello dashboard",
type: "agent-to-user",
});
expect(message.toId).toBe(DASHBOARD_USER_ID);
expect(store.getMessage(message.id)?.toId).toBe(DASHBOARD_USER_ID);
},
);
it.each(["dashboard", "user:dashboard", "User: user:dashboard"])(
"canonicalizes dashboard user alias '%s' when writing sender",
(dashboardAlias) => {
const message = store.sendMessage({
fromId: dashboardAlias,
fromType: "user",
toId: "agent-1",
toType: "agent",
content: "Reply",
type: "user-to-agent",
});
expect(message.fromId).toBe(DASHBOARD_USER_ID);
expect(store.getMessage(message.id)?.fromId).toBe(DASHBOARD_USER_ID);
},
);
});
describe("message-to-agent hook", () => {
@@ -234,6 +269,15 @@ describe("MessageStore", () => {
expect(inbox).toEqual([]);
});
it("includes legacy dashboard aliases in canonical dashboard inbox reads", () => {
store.sendMessage({ fromId: "agent-1", fromType: "agent", toId: DASHBOARD_USER_ID, toType: "user", content: "A", type: "agent-to-user" });
store.sendMessage({ fromId: "agent-1", fromType: "agent", toId: "user:dashboard", toType: "user", content: "B", type: "agent-to-user" });
store.sendMessage({ fromId: "agent-1", fromType: "agent", toId: "User: user:dashboard", toType: "user", content: "C", type: "agent-to-user" });
const inbox = store.getInbox(DASHBOARD_USER_ID, "user");
expect(inbox).toHaveLength(3);
});
it("filters by read status", () => {
const msg1 = store.sendMessage({
fromId: "agent-1",
@@ -418,6 +462,14 @@ describe("MessageStore", () => {
const count = store.markAllAsRead("user-99", "user");
expect(count).toBe(0);
});
it("marks canonical dashboard aliases as read together", () => {
store.sendMessage({ fromId: "agent-1", fromType: "agent", toId: DASHBOARD_USER_ID, toType: "user", content: "A", type: "agent-to-user" });
store.sendMessage({ fromId: "agent-2", fromType: "agent", toId: "user:dashboard", toType: "user", content: "B", type: "agent-to-user" });
const marked = store.markAllAsRead(DASHBOARD_USER_ID, "user");
expect(marked).toBe(2);
expect(store.getMailbox(DASHBOARD_USER_ID, "user").unreadCount).toBe(0);
});
});
describe("deleteMessage()", () => {
@@ -524,6 +576,31 @@ describe("MessageStore", () => {
);
expect(conversation).toEqual([]);
});
it("treats canonical dashboard identity as equivalent to legacy aliases in conversation reads", () => {
const sent = store.sendMessage({
fromId: "dashboard",
fromType: "user",
toId: "agent-1",
toType: "agent",
content: "Question",
type: "user-to-agent",
});
const reply = store.sendMessage({
fromId: "agent-1",
fromType: "agent",
toId: "user:dashboard",
toType: "user",
content: "Answer",
type: "agent-to-user",
});
const conversation = store.getConversation(
{ id: DASHBOARD_USER_ID, type: "user" },
{ id: "agent-1", type: "agent" },
);
expect(conversation.map((message) => message.id)).toEqual([sent.id, reply.id]);
});
});
describe("getMailbox()", () => {
@@ -561,6 +638,15 @@ describe("MessageStore", () => {
expect(mailbox.lastMessage).toBeUndefined();
});
it("aggregates unread count across canonical and legacy dashboard aliases", () => {
store.sendMessage({ fromId: "agent-1", fromType: "agent", toId: DASHBOARD_USER_ID, toType: "user", content: "A", type: "agent-to-user" });
store.sendMessage({ fromId: "agent-1", fromType: "agent", toId: "User: user:dashboard", toType: "user", content: "B", type: "agent-to-user" });
const mailbox = store.getMailbox(DASHBOARD_USER_ID, "user");
expect(mailbox.unreadCount).toBe(2);
expect(mailbox.lastMessage).toBeTruthy();
});
it("counts only unread messages", () => {
const msg1 = store.sendMessage({
fromId: "agent-1",