Files
clientsflow/frontend/server/utils/pilotRunStore.ts
Ruslan Bakiev bf7f4ae933 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>
2026-02-25 08:45:32 +07:00

60 lines
1.6 KiB
TypeScript

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;
}