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:
59
frontend/server/utils/pilotRunStore.ts
Normal file
59
frontend/server/utils/pilotRunStore.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export type PilotRunLog = { id: string; text: string; at: string };
|
||||
|
||||
export type PilotRun = {
|
||||
status: "running" | "finished" | "error";
|
||||
logs: PilotRunLog[];
|
||||
startedAt: string;
|
||||
finishedAt?: string;
|
||||
};
|
||||
|
||||
const activeRuns = new Map<string, PilotRun>();
|
||||
|
||||
const MAX_AGE_MS = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
function cleanup() {
|
||||
const now = Date.now();
|
||||
for (const [key, run] of activeRuns) {
|
||||
if (now - new Date(run.startedAt).getTime() > MAX_AGE_MS) {
|
||||
activeRuns.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function startPilotRun(conversationId: string) {
|
||||
cleanup();
|
||||
activeRuns.set(conversationId, {
|
||||
status: "running",
|
||||
logs: [],
|
||||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
export function addPilotTrace(conversationId: string, text: string) {
|
||||
const run = activeRuns.get(conversationId);
|
||||
if (!run || run.status !== "running") return;
|
||||
run.logs.push({
|
||||
id: `${Date.now()}-${Math.floor(Math.random() * 1_000_000)}`,
|
||||
text,
|
||||
at: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
export function finishPilotRun(conversationId: string, status: "finished" | "error" = "finished") {
|
||||
const run = activeRuns.get(conversationId);
|
||||
if (!run) return;
|
||||
run.status = status;
|
||||
run.finishedAt = new Date().toISOString();
|
||||
// Keep for a short time so late-connecting clients can see it finished
|
||||
setTimeout(() => {
|
||||
if (activeRuns.get(conversationId) === run) {
|
||||
activeRuns.delete(conversationId);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
export function getActivePilotRun(conversationId: string): PilotRun | null {
|
||||
const run = activeRuns.get(conversationId);
|
||||
if (!run || run.status !== "running") return null;
|
||||
return run;
|
||||
}
|
||||
Reference in New Issue
Block a user