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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -689,6 +689,24 @@ export function usePilotChat(opts: {
|
||||
pushPilotNote,
|
||||
refetchChatMessages,
|
||||
refetchChatConversations,
|
||||
// realtime pilot trace handlers (called from useCrmRealtime)
|
||||
handleRealtimePilotTrace(log: { text: string; at: string }) {
|
||||
const text = String(log.text ?? "").trim();
|
||||
if (!text) return;
|
||||
// Mark as sending so the UI shows live-log panel
|
||||
if (!pilotSending.value) pilotSending.value = true;
|
||||
pilotLiveLogs.value = [
|
||||
...pilotLiveLogs.value,
|
||||
{ id: `ws-${Date.now()}-${Math.random()}`, text, at: log.at || new Date().toISOString() },
|
||||
];
|
||||
},
|
||||
async handleRealtimePilotFinished() {
|
||||
pilotSending.value = false;
|
||||
livePilotUserText.value = "";
|
||||
livePilotAssistantText.value = "";
|
||||
pilotLiveLogs.value = [];
|
||||
await Promise.all([refetchChatMessages(), refetchChatConversations(), opts.refetchAllCrmQueries()]);
|
||||
},
|
||||
// cleanup
|
||||
destroyPilotWaveSurfer,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user