mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-26 19:44:51 +02:00
[eric] cleanup: drop dead exports, fix invoke-result regex, strip em-dashes (frontend)
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user