feat: broadcast pilot agent traces via WebSocket for live status on reconnect

Agent trace logs are now stored in-memory (pilotRunStore) and broadcast
through the existing /ws/crm-updates WebSocket channel. When a client
reconnects, it receives a pilot.catchup with all accumulated logs so the
user sees agent progress even after page reload. Three new WS event
types: pilot.trace, pilot.finished, pilot.catchup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ruslan Bakiev
2026-02-25 08:45:32 +07:00
parent b830f3728c
commit bf7f4ae933
6 changed files with 174 additions and 16 deletions

View File

@@ -9,10 +9,14 @@ export type RealtimeNewMessage = {
at: string;
};
export type RealtimePilotTrace = { text: string; at: string };
export function useCrmRealtime(opts: {
isAuthenticated: () => boolean;
onDashboardChanged: () => Promise<void>;
onNewMessage?: (msg: RealtimeNewMessage) => void;
onPilotTrace?: (log: RealtimePilotTrace) => void;
onPilotFinished?: () => void;
}) {
const crmRealtimeState = ref<"idle" | "connecting" | "open" | "error">("idle");
let crmRealtimeSocket: WebSocket | null = null;
@@ -116,6 +120,17 @@ export function useCrmRealtime(opts: {
if (payload.type === "message.new" && opts.onNewMessage) {
opts.onNewMessage(payload as unknown as RealtimeNewMessage);
}
if (payload.type === "pilot.trace" && opts.onPilotTrace) {
opts.onPilotTrace({ text: String(payload.text ?? ""), at: String(payload.at ?? "") });
}
if (payload.type === "pilot.catchup" && opts.onPilotTrace && Array.isArray(payload.logs)) {
for (const log of payload.logs) {
opts.onPilotTrace({ text: String((log as any).text ?? ""), at: String((log as any).at ?? "") });
}
}
if (payload.type === "pilot.finished" && opts.onPilotFinished) {
opts.onPilotFinished();
}
} catch {
// ignore malformed realtime payloads
}