[eric] cleanup: drop dead exports, fix invoke-result regex, strip em-dashes (frontend)

This commit is contained in:
ciregenz
2026-05-23 02:40:11 -07:00
parent 81fea443fd
commit bcc72c0228
16 changed files with 57 additions and 138 deletions
@@ -225,7 +225,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
// events starting at last_seq=0, which includes every stream_*
// event for messages that finished before the disconnect. The
// replay-skip guard in WebSocketManager._messageAlreadyComplete
// checks `session.messages` to decide whether to drop deltas — so
// checks `session.messages` to decide whether to drop deltas , so
// if we connect first, the slice is empty when the replay arrives,
// the guard returns false, and the user sees the chat type itself
// out again. Awaiting fetchSession before connect makes the slice
@@ -234,7 +234,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
try {
await dispatch(fetchSession(id));
} catch {
// Even if the REST hydrate fails, still connect — the WS resume
// Even if the REST hydrate fails, still connect , the WS resume
// protocol can hydrate from buffered events as a fallback.
}
if (cancelled) return;
@@ -885,7 +885,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
if (!(session.cost_usd > 0)) return null;
// The SDK reports a per-call $ figure regardless of how
// the request was routed. For requests that went through
// a subscription path, that figure is misleading — the
// a subscription path, that figure is misleading , the
// user pays flat-rate. Show "subscription" instead in
// those cases. Show $ only when the call was actually
// metered (Anthropic API key, OpenAI API key, etc.).
@@ -919,7 +919,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
<Typography
variant="caption"
sx={{ color: c.text.tertiary }}
title="Routed through subscription — flat-rate, per-call cost not metered"
title="Routed through subscription, flat-rate, per-call cost not metered"
>
subscription
</Typography>
@@ -1013,18 +1013,18 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
overflow: 'auto',
px: 2,
py: 1,
// Smoothness bundle (perf-only — no behavior change):
// 1. overflow-anchor: auto — Chromium's native scroll
// Smoothness bundle (perf-only , no behavior change):
// 1. overflow-anchor: auto , Chromium's native scroll
// anchoring keeps the viewport pinned to the user's
// visible content as siblings above/below resize.
// Eliminates the "transcript snaps back" feel during
// streaming and parallel tool fan-outs. Runs on the
// compositor thread, free.
// 2. contain: layout — tells the browser layout shifts
// 2. contain: layout , tells the browser layout shifts
// inside this scroll container don't affect siblings
// outside it. Prevents reflow from cascading up to
// the dashboard layout when bubbles grow.
// 3. overscroll-behavior: contain — keeps over-scroll
// 3. overscroll-behavior: contain , keeps over-scroll
// gestures from leaking up to the dashboard pan/zoom
// when the user hits the chat top/bottom.
overflowAnchor: 'auto',
@@ -1649,7 +1649,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
Haiku is the fastest Claude model but holds the least at once.
Each connected app adds instructions Claude has to read first.
If your message fails with “Prompt is too long,” turn off a few
apps (Microsoft 365 is the heaviest) or switch to Sonnet/Opus —
apps (Microsoft 365 is the heaviest) or switch to Sonnet/Opus,
both have 5× more room.
</Typography>
</Box>
@@ -1241,7 +1241,7 @@ function parseCreateAgentResult(rawText: string): string {
function parseInvokeAgentResult(rawText: string): InvokeAgentParsed | null {
const headerMatch = rawText.match(
/\*\*Invoked Agent Result\*\*(?:\s*—\s*(.+?))?\s*\(forked session:\s*([a-f0-9]+)\)/,
/\*\*Invoked Agent Result\*\*(?:\s*;\s*(.+?))?\s*\(forked session:\s*([a-f0-9]+)\)/,
);
if (!headerMatch) return null;
@@ -106,7 +106,7 @@ function getAgentWorkTime(
// True wall-clock duration: how long the user actually waited, from
// their prompt to the LAST assistant/system message of that turn.
// Covers thinking + every tool call + assistant text generation +
// any subagent/MCP work — anything that consumed user attention.
// any subagent/MCP work , anything that consumed user attention.
//
// This is intentionally NOT the sum of `thinking.elapsed_ms` (which
// would cover only reasoning time and miss tool execution). The
@@ -115,13 +115,13 @@ function getAgentWorkTime(
// did this take?" which is a different question.
//
// For each user message we find the LAST adjacent assistant/system
// message before the next user message — that's the turn boundary.
// message before the next user message , that's the turn boundary.
// If the turn is still in flight (last user message has no assistant
// reply yet AND session is running/waiting), extrapolate to now so
// the timer ticks live.
//
// Hidden messages (auto-continuation prompts from MCPActivate, etc.)
// are skipped — they're system-internal turns the user didn't see
// are skipped , they're system-internal turns the user didn't see
// and shouldn't be billed for.
const visible = messages.filter((m) => !m.hidden);
let totalMs = 0;
@@ -259,7 +259,7 @@ const HANDLE_DEFS: { dir: ResizeDir; sx: Record<string, any> }[] = [
interface OuterProps {
sessionId: string;
expanded: boolean;
// Stable getter — cards read pan/zoom on demand (drag math) instead of
// Stable getter , cards read pan/zoom on demand (drag math) instead of
// receiving them as props. Without this, every wheel/pan tick on the
// canvas re-rendered every card, even though the canvas root's CSS
// transform is what actually moves them visually. Cards only need the
@@ -346,7 +346,7 @@ const AgentCard: React.FC<Props> = ({
// through so the layout reconciles to the truth right then.
let suppressedHeight: number | null = null;
const ro = new ResizeObserver((entries) => {
// Short-circuit when dashboard is hidden — observer stays attached so
// Short-circuit when dashboard is hidden , observer stays attached so
// the next resize after returning to the dashboard fires correctly.
if (!isDashboardActiveRef.current) return;
// Short-circuit during active canvas interaction (pan/drag/wheel).
@@ -690,7 +690,7 @@ const AgentCard: React.FC<Props> = ({
position: 'relative',
// contain: streaming chat updates inside don't reflow the dashboard.
// Skipping `paint` here because the highlighted/selected/glow
// boxShadows legitimately extend past the card border — `paint`
// boxShadows legitimately extend past the card border , `paint`
// containment would clip those visuals.
contain: 'layout style',
// Promote each card to its own compositor layer so paint
@@ -902,7 +902,7 @@ const AgentCard: React.FC<Props> = ({
/>
))}
{/* Selection overlay – blocks click interaction while selected, enabling drag from anywhere */}
{/* Selection overlay , blocks click interaction while selected, enabling drag from anywhere */}
{isSelected && (
<Box
ref={scrollOverlayRef}
@@ -923,7 +923,7 @@ const AgentCard: React.FC<Props> = ({
/>
)}
{/* Drag zone: header + metadata – entire region above separator is draggable */}
{/* Drag zone: header + metadata , entire region above separator is draggable */}
<Box
onPointerDown={handleDragPointerDown}
onPointerMove={handleDragPointerMove}
+16 -16
View File
@@ -60,7 +60,7 @@ import NoteCard from './NoteCard';
import CanvasControls from './CanvasControls';
import CardSearchPalette from './CardSearchPalette';
import DirectionHints from './DirectionHints';
// OnboardingWalkthrough was retired in v2 — the new OnboardingRoot/Panel
// OnboardingWalkthrough was retired in v2 , the new OnboardingRoot/Panel
// (mounted in Main.tsx) replaces it. Keeping this banner to prevent stale
// imports from sneaking back in via auto-completion.
import DashboardToolbar from './DashboardToolbar';
@@ -163,7 +163,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const [pendingSelectSessionId, setPendingSelectSessionId] = useState<string | null>(null);
const [focusedCardId, setFocusedCardId] = useState<string | null>(null);
const [newAgentBounce, setNewAgentBounce] = useState(false);
// Cleanup any leftover walkthrough localStorage from v1 — the v2 panel
// Cleanup any leftover walkthrough localStorage from v1 , the v2 panel
// ignores it but it would otherwise hang around forever.
useEffect(() => {
try {
@@ -213,7 +213,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const restoredExpandedRef = useRef(false);
const canvasStateRef = useRef({ panX: canvas.panX, panY: canvas.panY, zoom: canvas.zoom });
canvasStateRef.current = { panX: canvas.panX, panY: canvas.panY, zoom: canvas.zoom };
// Stable getter — AgentCards read pan/zoom on demand during drag math.
// Stable getter , AgentCards read pan/zoom on demand during drag math.
const getCanvasState = useCallback(() => canvasStateRef.current, []);
// Notify the currently dragging card (if any) that pan/zoom changed so
// it can re-pin to the cursor. useEffect rather than render-body
@@ -402,7 +402,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
if (e.button !== 0) return;
if (isCardTarget(e.target, e.currentTarget)) return;
// Canvas click — drop any lingering input focus so arrow-key nav
// Canvas click , drop any lingering input focus so arrow-key nav
// works immediately without the user having to press Escape first.
const active = document.activeElement as HTMLElement | null;
const activeTag = active?.tagName;
@@ -522,7 +522,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
);
for (const s of dashSessions) {
if (warmAbort.signal.aborted) break;
// Fire-and-forget — the endpoint always 200s and the side
// Fire-and-forget , the endpoint always 200s and the side
// effect is invisible cache population.
fetch(`${API_BASE}/agents/sessions/${s.id}/warm-cache`, {
method: 'POST',
@@ -584,7 +584,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
|| Object.keys(allCards.viewCards).length > 0
|| Object.keys(allCards.browserCards).length > 0;
if (!hasCards) {
// Empty dashboard — queue a thumbnail clear (sent on exit alongside
// Empty dashboard , queue a thumbnail clear (sent on exit alongside
// the existing capture-update path). Backend treats '' as "set to
// empty"; null in PUT body means "don't update".
pendingThumbnailRef.current = '';
@@ -710,7 +710,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const prevParentStatusRef = useRef<Record<string, string>>({});
useEffect(() => {
if (!isActive) return; // Heavy logic — pause when dashboard is hidden
if (!isActive) return; // Heavy logic , pause when dashboard is hidden
if (!layoutInitialized || !autoRevealSubAgents) return;
const subSessions = Object.values(sessions).filter(
@@ -816,7 +816,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const pendingSaveRef = useRef<Parameters<typeof saveLayout>[0] | null>(null);
useEffect(() => {
if (!isActive) return; // Don't persist layout while dashboard is hidden — save buffers in pendingSaveRef and flushes on resume
if (!isActive) return; // Don't persist layout while dashboard is hidden , save buffers in pendingSaveRef and flushes on resume
if (!layoutInitialized || !dashboardId) return;
if (skipInitialSave.current) {
skipInitialSave.current = false;
@@ -1132,7 +1132,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const handleKey = (e: KeyboardEvent) => {
if (!isActive) return; // Don't fire shortcuts when dashboard is hidden
// Escape blurs any active input and restores focus to the canvas —
// Escape blurs any active input and restores focus to the canvas ,
// so you can quickly "unstick" keyboard focus and start navigating.
if (e.key === 'Escape') {
const active = document.activeElement as HTMLElement | null;
@@ -1172,7 +1172,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
const target = findNearestCard(currentFocused, direction);
if (!target) {
// No card in that direction — shake
// No card in that direction , shake
if (shakeTimerRef.current) clearTimeout(shakeTimerRef.current);
setShakeDirection(direction);
shakeTimerRef.current = setTimeout(() => {
@@ -1311,7 +1311,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
if (bc) {
// Use placeCard (collision-aware) instead of
// setCardPosition (blind setter). The "left of the
// browser" anchor is the IDEAL spot — but if it's
// browser" anchor is the IDEAL spot , but if it's
// already taken by an existing chat (e.g. step 3's
// YouTube agent that's still on canvas when step 5
// creates a new chat for the same browser), placeCard
@@ -1466,7 +1466,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
}, [dispatch, canvas.actions]);
useEffect(() => {
if (!isActive) return; // Heavy geometry recalculation — pause when dashboard is hidden
if (!isActive) return; // Heavy geometry recalculation , pause when dashboard is hidden
const DRIFT_THRESHOLD = 60;
// Group tethered sub-agent cards by source, only including those still in the spawn column
@@ -1507,7 +1507,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
}, [isActive, expandedSessionIds, glowingAgentCards, cards, dispatch, measuredHeightsTick]);
useEffect(() => {
if (!isActive) return; // Heavy geometry recalculation — pause when dashboard is hidden
if (!isActive) return; // Heavy geometry recalculation , pause when dashboard is hidden
const DRIFT_THRESHOLD = 60;
const sourceToSiblings = new Map<string, string[]>();
@@ -1604,8 +1604,8 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
}).filter(Boolean) as Array<{ key: string; path: string; labelX: number; labelY: number; label: string; fading: boolean }>;
// Build browser tethers from TWO sources and merge:
// 1. glowingBrowserCards — the short-lived "flash" when a browser is first assigned
// 2. Active browser-agent sessions — persistent as long as the agent runs
// 1. glowingBrowserCards , the short-lived "flash" when a browser is first assigned
// 2. Active browser-agent sessions , persistent as long as the agent runs
//
// Source #2 is the fix for tethers disappearing when the parent session
// completes a turn (which clears glowingBrowserCards even though the
@@ -1713,7 +1713,7 @@ const DashboardInner: React.FC<DashboardProps> = ({ dashboardId, isActive = true
if (t) glowTethers.set(browserId, t);
}
// Source 2: active browser-agent sessions (persistent — survives parent turn completion)
// Source 2: active browser-agent sessions (persistent , survives parent turn completion)
for (const s of sessionList) {
if (s.mode !== 'browser-agent') continue;
if (s.status !== 'running' && s.status !== 'waiting_approval') continue;
@@ -107,7 +107,7 @@ const DashboardToolbar = React.forwardRef<HTMLDivElement, Props>(
// backend. Without the settingsLoaded guard, the effect fires against the
// Redux initialState ('sonnet') before the real default has loaded, and
// the settingsApplied flag then locks out the real default for the rest
// of the session — so new chats spawn under the stale value.
// of the session , so new chats spawn under the stale value.
const settingsApplied = useRef(false);
useEffect(() => {
if (settingsLoaded && !settingsApplied.current) {
@@ -244,7 +244,7 @@ export function useDashboardSelection(
// Inject (once) a global CSS rule that makes browser webviews and iframes
// transparent to mouse events while a marquee drag is active. Without this,
// the Electron <webview> hit-tests the cursor at the OS level — when the
// the Electron <webview> hit-tests the cursor at the OS level , when the
// cursor lands on an interactable element inside the browser (button,
// link, text), the webview steals the cursor and the marquee drag visually
// freezes until the cursor escapes. Setting `pointer-events: none` makes
-16
View File
@@ -44,14 +44,6 @@ export function getWebview(browserId: string, tabId?: string): BrowserWebview |
return registry.get(makeKey(browserId, resolvedTabId));
}
export function getActiveTabId(browserId: string): string | undefined {
return activeTabMap.get(browserId);
}
export function getAllWebviews(): Map<string, BrowserWebview> {
return new Map(registry);
}
export function findBrowserByWebContentsId(wcId: number): string | undefined {
for (const [key, wv] of registry.entries()) {
if ((wv as any).getWebContentsId?.() === wcId) {
@@ -60,11 +52,3 @@ export function findBrowserByWebContentsId(wcId: number): string | undefined {
}
return undefined;
}
export function unregisterAllForBrowser(browserId: string): void {
const prefix = `${browserId}:`;
for (const key of registry.keys()) {
if (key.startsWith(prefix)) registry.delete(key);
}
activeTabMap.delete(browserId);
}
@@ -22,10 +22,3 @@ function getServerSnapshot(): boolean {
export function useReducedMotion(): boolean {
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}
/** Returns 0 (or `opts.floor`) when reduced-motion is on, else `ms`. */
export function useMotionDuration(ms: number, opts: { floor?: number } = {}): number {
const reduced = useReducedMotion();
if (!reduced) return ms;
return opts.floor ?? 0;
}
-11
View File
@@ -93,16 +93,5 @@ export function getSessionTraceState(): {
};
}
export function _resetForTest(): void {
_queue.length = 0;
if (_flushTimer != null) {
clearTimeout(_flushTimer);
_flushTimer = null;
}
_appStart = Date.now();
_lastTs = _appStart;
_recentReports.length = 0;
}
const serviceClient = { sync, report, getSessionTraceState, getRecentActions };
export default serviceClient;
@@ -247,7 +247,7 @@ export function findOpenGridCell(
// sibling). Spirals outward from the anchor on a grid, snapping to
// cell-aligned positions so the result still looks intentional, not
// dropped from orbit. Caps the spiral search at ~1000 cells to avoid
// pathological work in adversarial layouts — falls back to
// pathological work in adversarial layouts , falls back to
// findOpenGridCell after that.
//
// Cost: O(rects × cells_scanned). Spawn events are rare (not per-frame),
@@ -304,7 +304,7 @@ export function findOpenSpotNear(
}
}
// Pathological — full canvas occupied near anchor. Fall back to the
// Pathological , full canvas occupied near anchor. Fall back to the
// global first-empty scan so we never return an overlap.
return findOpenGridCell(occupiedRects, newW, newH);
}
@@ -347,8 +347,8 @@ const dashboardLayoutSlice = createSlice({
height: number;
// Optional: which existing sessions are currently expanded
// (showing their full chat history). Without this, the collision
// check uses each card's STORED height — which is the collapsed
// value — even when the card is currently rendering at the
// check uses each card's STORED height , which is the collapsed
// value , even when the card is currently rendering at the
// expanded ~620px. Result: new sub-agent cards spawn into the
// collapsed footprint but overlap the visually expanded one.
// Caller (Dashboard.tsx) passes the current expanded set so
@@ -559,7 +559,7 @@ const dashboardLayoutSlice = createSlice({
const h = card.height || DEFAULT_BROWSER_CARD_H;
// Collision-resolve the backend-proposed position. Backend agents
// often spawn sub-browsers at the parent's coordinates or at a
// default (0,0) — without this guard, the new card lands on top
// default (0,0) , without this guard, the new card lands on top
// of an existing one and the user sees a single card with
// multiple titles fighting for the z-index. Bias toward the
// proposed position so the spawn still LOOKS related to wherever
-10
View File
@@ -39,16 +39,6 @@ export function buildServeUrl(
return `${SERVE_BASE}/${outputId}/serve/index.html?_d=${encodeURIComponent(encoded)}`;
}
export function buildWorkspaceServeUrl(
workspaceId: string,
inputData: Record<string, any> = {},
backendResult: Record<string, any> | null = null,
): string {
const dataPayload = JSON.stringify({ i: inputData, r: backendResult });
const encoded = btoa(unescape(encodeURIComponent(dataPayload)));
return `${SERVE_BASE}/workspace/${workspaceId}/serve/index.html?_d=${encodeURIComponent(encoded)}`;
}
export interface OutputExecuteResult {
output_id: string;
output_name: string;
+1 -1
View File
@@ -44,7 +44,7 @@ export const store = configureStore({
// "SerializableStateInvariantMiddleware took 41ms" repeatedly under load.
//
// Production builds skip these middlewares anyway, so disabling them in
// dev makes dev behavior match prod — no surprises at packaging time.
// dev makes dev behavior match prod , no surprises at packaging time.
// Trade-off: serializability bugs (e.g. accidentally putting a Map or
// Date directly into state) won't be caught at dev time. We've shipped
// many versions with stable slice shapes; that risk is now low.
@@ -71,15 +71,6 @@ export function selectSubscriptionConnections(
return providers.connections ?? [];
}
export function isProviderConnected(
state: WithSubscriptions,
providerId: string,
): boolean {
return selectSubscriptionConnections(state).some(
(p) => p.provider === providerId && (p.isActive || p.testStatus === 'active'),
);
}
export function hasAnyActiveSubscription(state: WithSubscriptions): boolean {
return selectSubscriptionConnections(state).some(
(p) => p.isActive || p.testStatus === 'active',
@@ -1,12 +0,0 @@
export const getStyleValue = (className: string, property: string, defaultValue: string = "none"): string => {
if (typeof document !== 'undefined') {
const element = document.createElement("div");
element.setAttribute("class", className);
document.body.appendChild(element);
const style = window.getComputedStyle(element);
const value = style.getPropertyValue(property);
document.body.removeChild(element);
return value || defaultValue;
}
return defaultValue; // Return default value if not in a browser environment
};
@@ -24,22 +24,6 @@ export const EASE = {
pulse: 'cubic-bezier(0.4, 0, 0.6, 1)',
} as const;
/** Array-form easing for framer-motion; same curves as EASE. */
export const FRAMER_EASE = {
out: [0.16, 1, 0.3, 1] as [number, number, number, number],
inOut: [0.4, 0, 0.2, 1] as [number, number, number, number],
spring: [0.34, 1.56, 0.64, 1] as [number, number, number, number],
pulse: [0.4, 0, 0.6, 1] as [number, number, number, number],
};
/** Shared fadeIn keyframe; import once. */
export const fadeInKeyframes = {
'@keyframes openswarmFadeIn': {
from: { opacity: 0 },
to: { opacity: 1 },
},
};
/** Shared skeleton/indicator pulse keyframe. */
export const pulseKeyframes = {
'@keyframes openswarmPulse': {
+15 -15
View File
@@ -30,7 +30,7 @@ import { notifyAgentCompletion } from '../notifications';
// Thin wrapper around getAuthToken so the connect() call site stays
// synchronous. If the token isn't cached yet, returns '' and the WS
// handshake will 4401 — onclose catches that and refreshes the token
// handshake will 4401 , onclose catches that and refreshes the token
// before the next reconnect.
const _getAuthTokenSafe = (): string => {
try { return getAuthToken() || ''; } catch { return ''; }
@@ -38,7 +38,7 @@ const _getAuthTokenSafe = (): string => {
const _genUuid = (): string => {
// Avoid pulling in `crypto.randomUUID` for compat — this is a
// Avoid pulling in `crypto.randomUUID` for compat , this is a
// disambiguator, not a security boundary, so a 96-bit hex string is
// plenty.
const a = Math.floor(Math.random() * 2 ** 32).toString(16).padStart(8, '0');
@@ -89,7 +89,7 @@ class WebSocketManager {
// Resume state. lastSeq is the highest server-assigned seq this
// client has applied; it's sent on every (re)connect so the server
// can replay missed events. Persists for the lifetime of this
// WebSocketManager instance — when the user navigates away and a
// WebSocketManager instance , when the user navigates away and a
// new createSessionWs() is constructed, lastSeq starts at 0 and we
// get a full replay.
private connectionUuid: string;
@@ -112,7 +112,7 @@ class WebSocketManager {
private pongTimeoutTimer: ReturnType<typeof setTimeout> | null = null;
// Outbound queue. Frames the user enqueues while the WS isn't
// OPEN — or while OPEN but pre-resume-ack — wait here and flush
// OPEN , or while OPEN but pre-resume-ack , wait here and flush
// after the resume handshake completes. Queue is in-memory only:
// surviving a full app restart isn't worth the localStorage
// complexity given how rare that case is for a transient drop.
@@ -122,7 +122,7 @@ class WebSocketManager {
// Frame-aligned message coalescer. Buffers incoming WS messages from
// all WebSocketManager instances and flushes them in ONE batched
// React render per animation frame. Without this, N concurrent agents
// each cause their own renders on every WS message — dozens of full
// each cause their own renders on every WS message , dozens of full
// app re-renders per second, fanning out to every useSelector. With
// it: max one render per frame regardless of message volume.
private static _messageQueue: Array<{ mgr: WebSocketManager; msg: WSEvent }> = [];
@@ -220,7 +220,7 @@ class WebSocketManager {
// Buffer incoming messages and flush them per animation frame
// in a single React batch. With N concurrent agents/browsers
// streaming, each WS instance used to trigger its own React
// render — dozens per frame, fanning out to every useSelector
// render , dozens per frame, fanning out to every useSelector
// subscriber, starving the main thread. Coalescing flips that
// to ONE batched render per frame regardless of how many
// messages arrived. Stream deltas dispatch directly into Redux
@@ -254,7 +254,7 @@ class WebSocketManager {
};
this.ws.onerror = () => {
// Force the close path to run — onclose will mark state
// Force the close path to run , onclose will mark state
// reconnecting and schedule a retry.
this.ws?.close();
};
@@ -312,7 +312,7 @@ class WebSocketManager {
try {
this.ws.send(JSON.stringify({ event: 'client:ping', data: { nonce } }));
} catch {
// socket dying — let the close handler take over
// socket dying , let the close handler take over
return;
}
if (this.pongTimeoutTimer != null) clearTimeout(this.pongTimeoutTimer);
@@ -397,7 +397,7 @@ class WebSocketManager {
// REST so the slice's view doesn't have a silent gap.
if (session_id) {
store.dispatch(fetchSession(session_id));
// Reset lastSeq — the REST refetch is the new authoritative
// Reset lastSeq , the REST refetch is the new authoritative
// baseline; subsequent server events with seq numbers will
// re-establish the high-water mark. Also wipe the cross-mount
// persistent map so a remount during this gap window doesn't
@@ -514,7 +514,7 @@ class WebSocketManager {
// The discriminator is `resumeAcked`: it flips to true when
// server:hello arrives, which the server sends AFTER the replay
// completes. Any stream_* event arriving while !resumeAcked is
// replay-from-buffer (historical) and can be dropped — the REST
// replay-from-buffer (historical) and can be dropped , the REST
// snapshot we awaited before connect is authoritative for any
// already-finalized message, and any genuinely live turn the
// server is pushing will continue emitting events after the ack.
@@ -600,7 +600,7 @@ class WebSocketManager {
// compacted_through_msg_id locally so the renderer can drop a
// visible "N earlier turns summarized" chip into the transcript.
// Other reasons (cleared, etc.) flow through this same event but
// don't currently need a chip — ignore them for now.
// don't currently need a chip , ignore them for now.
if (session_id && data.reason === 'compacted') {
store.dispatch(recordCompaction({
sessionId: session_id,
@@ -623,7 +623,7 @@ class WebSocketManager {
break;
case 'agent:auth_error':
// Re-uses the context_overflow card slot — both are "this session is
// Re-uses the context_overflow card slot , both are "this session is
// blocked, here's what to do" cards. Reason field disambiguates.
if (session_id) {
store.dispatch(setContextOverflow({
@@ -690,7 +690,7 @@ class WebSocketManager {
dashboard_id: data.dashboard_id,
}));
// Auto-delete browsers spawned by this agent when it finishes
// normally or errors out. We intentionally skip 'stopped' — the
// normally or errors out. We intentionally skip 'stopped' , the
// user may want to inspect the browser after manually stopping.
if (closedStatus === 'completed' || closedStatus === 'error') {
const browserCards = store.getState().dashboardLayout.browserCards;
@@ -746,7 +746,7 @@ class WebSocketManager {
send(event: string, data: Record<string, any>) {
// Queue if the socket isn't open OR resume hasn't been ack'd yet.
// The pre-ack gate prevents an outbound user message from racing
// the resume replay — the server might process the message
// the resume replay , the server might process the message
// before the replay finishes, leaving the slice's view of
// history incomplete.
const open = this.ws?.readyState === WebSocket.OPEN;
@@ -815,7 +815,7 @@ export const dashboardWs = new WebSocketManager(`${WS_BASE}/ws/dashboard`, { ski
// the user sees their completed chat "type itself out" on every reopen.
//
// Lifetime: tied to the JS module load, which means the page tab. Lost
// on full app reload (intentional — that should re-hydrate from REST).
// on full app reload (intentional , that should re-hydrate from REST).
// On backend restart the buffers are wiped anyway, so a stale
// lastSeq pointing past the buffer top falls into the "fresh client"
// path on the server (last_seq>0 but no buffer) which short-circuits