Files
fusion/packages/dashboard/app/utils/resumeInstrumentation.ts
Fusion (runfusion.ai) ef5dd7ba25 feat(FN-5435): reset fallback retry counter on success in reviewer
Adds a fallback retry counter to the reviewer that resets on successful step completion, preventing premature escalation when earlier steps exhaust retries while later ones succeed; also aligns dashboard trace buffer types and adds comprehensive coverage for the reset semantics.

Fusion-Task-Id: FN-5435
2026-05-21 08:43:57 -07:00

160 lines
3.7 KiB
TypeScript

import { pushTrace } from "./dashboardTraceBuffer";
export type ResumeTrigger =
| "visibility"
| "pageshow"
| "sse-error"
| "sse-reconnect"
| "sse-open"
| "remount"
| "route-active"
| "route-inactive"
| "project-context-change";
export type ResumeEvent = {
ts: string;
view: string;
trigger: ResumeTrigger;
projectId?: string;
gapMs?: number;
replayAttempted: boolean;
replayFromEventId?: number | null;
lastEventId?: number | null;
sseChannel?: string;
reason?: string;
detail?: Record<string, unknown>;
};
const RESUME_CAP = 500;
const POST_BATCH_CAP = 25;
const resumeEvents: ResumeEvent[] = [];
const pendingBatch: ResumeEvent[] = [];
const lastActivityByView = new Map<string, number>();
let lastActivityGlobal: number | undefined;
let flushScheduled = false;
let enabled = true;
function nowTs(now: number): string {
return new Date(now).toISOString();
}
function enqueueFlush(): void {
if (flushScheduled || pendingBatch.length === 0 || typeof window === "undefined") {
return;
}
flushScheduled = true;
const schedule = (cb: () => void) => {
if (typeof window.requestIdleCallback === "function") {
window.requestIdleCallback(cb);
return;
}
window.setTimeout(cb, 0);
};
schedule(() => {
flushScheduled = false;
void flushPendingBatch();
});
}
async function flushPendingBatch(): Promise<void> {
if (pendingBatch.length === 0 || typeof window === "undefined" || typeof globalThis.fetch !== "function") {
return;
}
const events = pendingBatch.splice(0, POST_BATCH_CAP);
try {
await globalThis.fetch("/api/diagnostics/resume-events", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ events }),
});
} catch {
// swallow instrumentation failures
}
if (pendingBatch.length > 0) {
enqueueFlush();
}
}
export function getResumeEvents(): ResumeEvent[] {
return [...resumeEvents];
}
export function clearResumeEvents(): void {
resumeEvents.length = 0;
pendingBatch.length = 0;
flushScheduled = false;
lastActivityByView.clear();
lastActivityGlobal = undefined;
}
export function setResumeInstrumentationEnabled(nextEnabled: boolean): void {
enabled = nextEnabled;
}
export function recordResumeEvent(
event: Omit<ResumeEvent, "ts" | "gapMs"> & { now?: number },
): ResumeEvent {
const now = event.now ?? Date.now();
const previousByView = lastActivityByView.get(event.view);
const previousGlobal = lastActivityGlobal;
const baseline = previousByView ?? previousGlobal;
const stampedEvent: ResumeEvent = {
...event,
ts: nowTs(now),
gapMs: baseline !== undefined ? Math.max(0, now - baseline) : undefined,
};
delete (stampedEvent as { now?: number }).now;
lastActivityByView.set(event.view, now);
lastActivityGlobal = now;
if (!enabled) {
return stampedEvent;
}
pushTrace("resumeInstrumentation", event.trigger, stampedEvent as unknown as Record<string, unknown>);
resumeEvents.push(stampedEvent);
if (resumeEvents.length > RESUME_CAP) {
resumeEvents.splice(0, resumeEvents.length - RESUME_CAP);
}
pendingBatch.push(stampedEvent);
enqueueFlush();
return stampedEvent;
}
declare global {
interface Window {
__fusionDebug?: {
dashboardTraces?: {
get: () => unknown[];
clear: () => void;
};
resumeInstrumentation?: {
get: () => unknown[];
clear: () => void;
setEnabled: (nextEnabled: boolean) => void;
};
};
}
}
if (typeof window !== "undefined") {
window.__fusionDebug ??= {};
window.__fusionDebug.resumeInstrumentation = {
get: getResumeEvents,
clear: clearResumeEvents,
setEnabled: setResumeInstrumentationEnabled,
};
}