feat(FN-1975): stream chat session updates through SSE

- Emit chat:session:updated from ChatStore when message deletion mutates a session and cover deleteMessage false returns
- Pass ChatStore into SSE setup and forward chat session update events to connected clients
- Update useChat to consume SSE updates in real time with safer EnrichedChatSession typing
- Add core and dashboard tests for chat-store emissions, SSE forwarding, route wiring, and hook behavior
This commit is contained in:
Fusion
2026-04-18 00:44:27 -07:00
committed by gsxdsm
parent ddbb60e297
commit 17fcbfe6c8
12 changed files with 692 additions and 7 deletions

View File

@@ -9,6 +9,7 @@ import type {
MessageStore,
MissionValidatorRun,
FixFeatureCreatedPayload,
ChatStore,
} from "@fusion/core";
import type { AiSessionStore } from "./ai-session-store.js";
@@ -191,6 +192,7 @@ export function createSSE(
options?: CreateSSEOptions,
agentStore?: AgentStore,
messageStore?: MessageStore,
chatStore?: ChatStore,
) {
const { projectId } = options ?? {};
@@ -389,6 +391,27 @@ export function createSSE(
send(`event: message:deleted\ndata: ${JSON.stringify({ id: messageId })}\n\n`);
};
// --- Chat store event handlers ---
const onChatSessionCreated = (session: any) => {
send(`event: chat:session:created\ndata: ${JSON.stringify(session)}\n\n`);
};
const onChatSessionUpdated = (session: any) => {
send(`event: chat:session:updated\ndata: ${JSON.stringify(session)}\n\n`);
};
const onChatSessionDeleted = (sessionId: string) => {
send(`event: chat:session:deleted\ndata: ${JSON.stringify({ id: sessionId })}\n\n`);
};
const onChatMessageAdded = (message: any) => {
send(`event: chat:message:added\ndata: ${JSON.stringify(message)}\n\n`);
};
const onChatMessageDeleted = (messageId: string) => {
send(`event: chat:message:deleted\ndata: ${JSON.stringify({ id: messageId })}\n\n`);
};
// --- Cleanup (all handlers are defined above, safe to reference) ---
let cleaned = false;
@@ -452,6 +475,13 @@ export function createSSE(
messageStore.off("message:read", onMessageRead);
messageStore.off("message:deleted", onMessageDeleted);
}
if (chatStore) {
chatStore.off("chat:session:created", onChatSessionCreated);
chatStore.off("chat:session:updated", onChatSessionUpdated);
chatStore.off("chat:session:deleted", onChatSessionDeleted);
chatStore.off("chat:message:added", onChatMessageAdded);
chatStore.off("chat:message:deleted", onChatMessageDeleted);
}
};
// --- Subscribe ---
@@ -517,6 +547,14 @@ export function createSSE(
messageStore.on("message:deleted", onMessageDeleted);
}
if (chatStore) {
chatStore.on("chat:session:created", onChatSessionCreated);
chatStore.on("chat:session:updated", onChatSessionUpdated);
chatStore.on("chat:session:deleted", onChatSessionDeleted);
chatStore.on("chat:message:added", onChatMessageAdded);
chatStore.on("chat:message:deleted", onChatMessageDeleted);
}
// Heartbeat every 30s to keep connection alive.
// Sent as a named event so the client's EventSource can detect it
// (SSE comments starting with ":" are silently consumed and never