diff --git a/packages/app/e2e/terminal-responsiveness-benchmark.spec.ts b/packages/app/e2e/terminal-responsiveness-benchmark.spec.ts index 25329cdf6..fef59fce5 100644 --- a/packages/app/e2e/terminal-responsiveness-benchmark.spec.ts +++ b/packages/app/e2e/terminal-responsiveness-benchmark.spec.ts @@ -175,29 +175,6 @@ test("workspace terminal responsiveness benchmark (report-only, single stress pr timeout: 120_000, }).toBe(true); - const diagnostics = await page.evaluate(async () => { - const debug = ( - window as { - __PASEO_PERF_DIAGNOSTICS_DEBUG__?: { - consumeReports?: () => Promise; - }; - } - ).__PASEO_PERF_DIAGNOSTICS_DEBUG__; - if (!debug || typeof debug.consumeReports !== "function") { - return { available: false, reports: [] as unknown[] }; - } - try { - const reports = await debug.consumeReports(); - return { available: true, reports: Array.isArray(reports) ? reports : [] }; - } catch (error) { - return { - available: true, - reports: [] as unknown[], - error: error instanceof Error ? error.message : String(error), - }; - } - }); - const frameGapsMs = (rafResult.samples ?? []).filter( (sample) => Number.isFinite(sample) && sample > 0 ); @@ -220,12 +197,6 @@ test("workspace terminal responsiveness benchmark (report-only, single stress pr over500Ms: frameGapsMs.filter((gap) => gap > 500).length, }, explorerToggleLatencyMs: summarize(interactionLatenciesMs), - diagnostics: { - available: diagnostics.available, - reportCount: diagnostics.reports.length, - reports: diagnostics.reports, - error: "error" in diagnostics ? diagnostics.error : undefined, - }, }; await testInfo.attach("terminal-responsiveness-report", { diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx index 06cf49c0d..d082e1f25 100644 --- a/packages/app/src/app/_layout.tsx +++ b/packages/app/src/app/_layout.tsx @@ -64,7 +64,6 @@ import { parseWorkspaceOpenIntent, } from "@/utils/host-routes"; import { getTauri } from "@/utils/tauri"; -import { PerfDiagnosticsProvider } from "@/runtime/perf-diagnostics"; import { attachConsole } from "@/utils/tauri-attach-console"; polyfillCrypto(); @@ -556,55 +555,53 @@ export default function RootLayout() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/packages/app/src/app/index.tsx b/packages/app/src/app/index.tsx index 256934b72..a3fe70314 100644 --- a/packages/app/src/app/index.tsx +++ b/packages/app/src/app/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useSyncExternalStore, useState } from 'react' +import { useEffect, useSyncExternalStore, useState } from 'react' import { usePathname, useRouter } from 'expo-router' import { useHosts } from '@/runtime/host-runtime' import { shouldUseManagedDesktopDaemon } from '@/desktop/managed-runtime/managed-runtime' @@ -13,8 +13,6 @@ import { } from './index-startup' const STARTUP_TIMEOUT_MS = 30_000 -const APP_START_TIME = performance.now() - function useAnyHostOnline(serverIds: string[]): string | null { const runtime = getHostRuntimeStore() return useSyncExternalStore( @@ -61,18 +59,6 @@ export default function Index() { const [hasTimedOut, setHasTimedOut] = useState(false) const isDesktopStartupRace = shouldUseManagedDesktopDaemon() const onlineServerId = useAnyHostOnline(daemons.map((daemon) => daemon.serverId)) - const startupLogged = useRef(false) - - useEffect(() => { - if (onlineServerId && !startupLogged.current) { - startupLogged.current = true - console.info('[Startup] host online', { - serverId: onlineServerId, - elapsedMs: Math.round(performance.now() - APP_START_TIME), - }) - } - }, [onlineServerId]) - useEffect(() => { const timer = setTimeout(() => { setHasTimedOut(true) @@ -89,10 +75,6 @@ export default function Index() { if (pathname !== '/' && pathname !== '') { return } - console.info('[Startup] navigating to host route', { - serverId: onlineServerId, - elapsedMs: Math.round(performance.now() - APP_START_TIME), - }) router.replace(buildHostRootRoute(onlineServerId) as any) }, [onlineServerId, pathname, router]) diff --git a/packages/app/src/components/agent-list.tsx b/packages/app/src/components/agent-list.tsx index 66d9eccf8..1f86f3dab 100644 --- a/packages/app/src/components/agent-list.tsx +++ b/packages/app/src/components/agent-list.tsx @@ -16,7 +16,6 @@ import { shortenPath } from '@/utils/shorten-path' import { type AggregatedAgent } from '@/hooks/use-aggregated-agents' import { useSessionStore } from '@/stores/session-store' import { AgentStatusDot } from '@/components/agent-status-dot' -import { buildAgentNavigationKey, startNavigationTiming } from '@/utils/navigation-timing' import { buildHostWorkspaceAgentRoute } from '@/utils/host-routes' interface AgentListProps { @@ -271,13 +270,6 @@ export function AgentList({ const serverId = agent.serverId const agentId = agent.id - const navigationKey = buildAgentNavigationKey(serverId, agentId) - startNavigationTiming(navigationKey, { - from: 'home', - to: 'agent', - params: { serverId, agentId }, - }) - const shouldReplace = pathname.startsWith('/h/') const navigate = shouldReplace ? router.replace : router.push diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index 9fc2f99ae..08091f5ab 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -67,17 +67,12 @@ import { } from "./use-bottom-anchor-controller"; import { createMarkdownStyles } from "@/styles/markdown-styles"; import { MAX_CONTENT_WIDTH } from "@/constants/layout"; -import { isPerfLoggingEnabled, measurePayload, perfLog } from "@/utils/perf"; import { getMarkdownListMarker } from "@/utils/markdown-list"; import { buildHostWorkspaceFileRoute } from "@/utils/host-routes"; const isUserMessageItem = (item?: StreamItem) => item?.kind === "user_message"; const isToolSequenceItem = (item?: StreamItem) => item?.kind === "tool_call" || item?.kind === "thought" || item?.kind === "todo_list"; -const AGENT_STREAM_LOG_TAG = "[AgentStreamView]"; -const STREAM_ITEM_LOG_MIN_COUNT = 200; -const STREAM_ITEM_LOG_DELTA_THRESHOLD = 50; - export interface AgentStreamViewHandle { scrollToBottom(reason?: BottomAnchorLocalRequest["reason"]): void; prepareForViewportChange(): void; @@ -116,7 +111,6 @@ export const AgentStreamView = forwardRef>(new Set()); const openFileExplorer = usePanelStore((state) => state.openFileExplorer); const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout); @@ -462,74 +456,6 @@ export const AgentStreamView = forwardRef { - if (!isPerfLoggingEnabled()) { - return; - } - const totalCount = streamItems.length; - const prevCount = streamItemCountRef.current; - if (totalCount === prevCount) { - return; - } - const delta = Math.abs(totalCount - prevCount); - streamItemCountRef.current = totalCount; - if ( - totalCount < STREAM_ITEM_LOG_MIN_COUNT && - delta < STREAM_ITEM_LOG_DELTA_THRESHOLD - ) { - return; - } - let userCount = 0; - let assistantCount = 0; - let toolCallCount = 0; - let thoughtCount = 0; - let activityCount = 0; - let todoCount = 0; - for (const item of streamItems) { - switch (item.kind) { - case "user_message": - userCount += 1; - break; - case "assistant_message": - assistantCount += 1; - break; - case "tool_call": - toolCallCount += 1; - break; - case "thought": - thoughtCount += 1; - break; - case "activity_log": - activityCount += 1; - break; - case "todo_list": - todoCount += 1; - break; - default: - break; - } - } - const metrics = - totalCount >= STREAM_ITEM_LOG_MIN_COUNT - ? measurePayload(streamItems) - : null; - perfLog(AGENT_STREAM_LOG_TAG, { - event: "stream_items", - agentId, - totalCount, - userCount, - assistantCount, - toolCallCount, - thoughtCount, - activityCount, - todoCount, - pendingPermissionCount: pendingPermissionItems.length, - streamHeadCount: streamHead?.length ?? 0, - payloadApproxBytes: metrics?.approxBytes ?? 0, - payloadFieldCount: metrics?.fieldCount ?? 0, - }); - }, [agentId, pendingPermissionItems.length, streamHead, streamItems]); - const showWorkingIndicator = agent.status === "running"; const renderModel = useMemo(() => { const pendingPermissionsNode = diff --git a/packages/app/src/components/git-diff-pane.tsx b/packages/app/src/components/git-diff-pane.tsx index c381f51ab..95d00cfe8 100644 --- a/packages/app/src/components/git-diff-pane.tsx +++ b/packages/app/src/components/git-diff-pane.tsx @@ -38,7 +38,6 @@ import { useCheckoutPrStatusQuery } from "@/hooks/use-checkout-pr-status-query"; import { useHorizontalScrollOptional } from "@/contexts/horizontal-scroll-context"; import { useExplorerSidebarAnimation } from "@/contexts/explorer-sidebar-animation-context"; import { Fonts } from "@/constants/theme"; -import { getNowMs, isPerfLoggingEnabled, perfLog } from "@/utils/perf"; import { shouldAnchorHeaderBeforeCollapse } from "@/utils/git-diff-scroll"; import { DropdownMenu, @@ -66,11 +65,6 @@ function openURLInNewTab(url: string): void { void openExternalUrl(url); } -const DIFF_PANE_LOG_TAG = "[GitDiffPane]"; -const DIFF_FILE_LOG_TAG = "[DiffFileSection]"; -const DIFF_FILE_LOG_LINE_THRESHOLD = 500; -const DIFF_FILE_LOG_TOKEN_THRESHOLD = 5000; - type HighlightStyle = NonNullable; interface HighlightedTextProps { @@ -199,75 +193,14 @@ const DiffFileHeader = memo(function DiffFileHeader({ onHeaderHeightChange, testID, }: DiffFileSectionProps) { - const expandStartRef = useRef(null); const layoutYRef = useRef(null); const pressHandledRef = useRef(false); const pressInRef = useRef<{ ts: number; pageX: number; pageY: number } | null>(null); - const { hunkCount, lineCount, tokenCount } = useMemo(() => { - let totalLines = 0; - let totalTokens = 0; - for (const hunk of file.hunks) { - totalLines += hunk.lines.length; - for (const line of hunk.lines) { - if (line.tokens) { - totalTokens += line.tokens.length; - } - } - } - return { - hunkCount: file.hunks.length, - lineCount: totalLines, - tokenCount: totalTokens, - }; - }, [file]); - - const shouldLogFileMetrics = - lineCount >= DIFF_FILE_LOG_LINE_THRESHOLD || - tokenCount >= DIFF_FILE_LOG_TOKEN_THRESHOLD; - const toggleExpanded = useCallback(() => { pressHandledRef.current = true; - if (isPerfLoggingEnabled() && shouldLogFileMetrics) { - expandStartRef.current = getNowMs(); - perfLog(DIFF_FILE_LOG_TAG, { - event: "toggle", - path: file.path, - nextExpanded: !isExpanded, - hunkCount, - lineCount, - tokenCount, - }); - } onToggle(file.path); - }, [file.path, onToggle, isExpanded, hunkCount, lineCount, tokenCount, shouldLogFileMetrics]); - - useEffect(() => { - if (!isPerfLoggingEnabled() || !shouldLogFileMetrics) { - return; - } - const startMs = expandStartRef.current; - if (startMs === null) { - return; - } - expandStartRef.current = null; - const logCommit = () => { - const durationMs = getNowMs() - startMs; - perfLog(DIFF_FILE_LOG_TAG, { - event: isExpanded ? "expand_commit" : "collapse_commit", - path: file.path, - durationMs: Math.round(durationMs), - hunkCount, - lineCount, - tokenCount, - }); - }; - if (typeof requestAnimationFrame === "function") { - requestAnimationFrame(() => logCommit()); - } else { - logCommit(); - } - }, [isExpanded, file.path, hunkCount, lineCount, tokenCount, shouldLogFileMetrics]); + }, [file.path, onToggle]); return ( >({}); const bodyHeightByPathRef = useRef>({}); const defaultHeaderHeightRef = useRef(44); - const diffMetrics = useMemo(() => { - let hunkCount = 0; - let lineCount = 0; - let tokenCount = 0; - for (const file of files) { - hunkCount += file.hunks.length; - for (const hunk of file.hunks) { - lineCount += hunk.lines.length; - for (const line of hunk.lines) { - if (line.tokens) { - tokenCount += line.tokens.length; - } - } - } - } - return { - fileCount: files.length, - hunkCount, - lineCount, - tokenCount, - }; - }, [files]); - const lastMetricsKeyRef = useRef(null); - const handleRefresh = useCallback(() => { setIsManualRefresh(true); void refreshDiff(); @@ -712,28 +621,6 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi setDiffModeOverride(null); }, [autoDiffMode]); - useEffect(() => { - if (!isPerfLoggingEnabled()) { - return; - } - const metricsKey = `${diffMetrics.fileCount}:${diffMetrics.hunkCount}:${diffMetrics.lineCount}:${diffMetrics.tokenCount}`; - if (lastMetricsKeyRef.current === metricsKey) { - return; - } - lastMetricsKeyRef.current = metricsKey; - perfLog(DIFF_PANE_LOG_TAG, { - event: "files_snapshot", - serverId, - workspaceId: workspaceId ?? cwd, - fileCount: diffMetrics.fileCount, - hunkCount: diffMetrics.hunkCount, - lineCount: diffMetrics.lineCount, - tokenCount: diffMetrics.tokenCount, - isLoading: isDiffLoading, - isFetching: isDiffFetching, - }); - }, [cwd, diffMetrics, isDiffFetching, isDiffLoading, serverId, workspaceId]); - const commitStatus = useCheckoutGitActionsStore((state) => state.getStatus({ serverId, cwd, actionId: "commit" }) ); diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index 9b72a80c4..3f83d9772 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -68,7 +68,6 @@ import { buildToolCallDisplayModel, } from "@/utils/tool-call-display"; import { resolveToolCallIcon } from "@/utils/tool-call-icon"; -import { getNowMs, isPerfLoggingEnabled, perfLog } from "@/utils/perf"; import { parseInlinePathToken, type InlinePathTarget } from "@/utils/inline-path"; import { getMarkdownListMarker } from "@/utils/markdown-list"; import { openExternalUrl } from "@/utils/open-external-url"; @@ -1786,10 +1785,6 @@ interface ToolCallProps { onInlineDetailsExpandedChange?: (expanded: boolean) => void; } -const TOOL_CALL_LOG_TAG = "[ToolCall]"; -const TOOL_CALL_COMMIT_THRESHOLD_MS = 16; - - export const ToolCall = memo(function ToolCall({ toolName, args, @@ -1806,7 +1801,6 @@ export const ToolCall = memo(function ToolCall({ }: ToolCallProps) { const { openToolCall } = useToolCallSheet(); const [isExpanded, setIsExpanded] = useState(false); - const toggleStartRef = useRef(null); // Check if we're on mobile (use bottom sheet) or desktop (inline expand) const isMobile = @@ -1849,7 +1843,6 @@ export const ToolCall = memo(function ToolCall({ const displayName = displayModel.displayName; const summary = displayModel.summary; const errorText = displayModel.errorText; - const iconCategory = effectiveDetail?.type ?? toolName.trim().toLowerCase(); const IconComponent = resolveToolCallIcon(toolName, effectiveDetail); // Check if there's any content to display @@ -1862,9 +1855,6 @@ export const ToolCall = memo(function ToolCall({ : false); const handleToggle = useCallback(() => { - if (!isMobile && isPerfLoggingEnabled()) { - toggleStartRef.current = getNowMs(); - } if (isMobile) { openToolCall({ toolName, @@ -1878,33 +1868,6 @@ export const ToolCall = memo(function ToolCall({ } }, [isMobile, openToolCall, toolName, displayName, summary, effectiveDetail, errorText]); - useEffect(() => { - if (isMobile || !isPerfLoggingEnabled()) { - return; - } - const startMs = toggleStartRef.current; - if (startMs === null) { - return; - } - toggleStartRef.current = null; - const logCommit = () => { - const durationMs = getNowMs() - startMs; - if (durationMs >= TOOL_CALL_COMMIT_THRESHOLD_MS) { - perfLog(TOOL_CALL_LOG_TAG, { - event: isExpanded ? "expand_commit" : "collapse_commit", - toolName, - iconCategory, - durationMs: Math.round(durationMs), - }); - } - }; - if (typeof requestAnimationFrame === "function") { - requestAnimationFrame(() => logCommit()); - } else { - logCommit(); - } - }, [isExpanded, isMobile, toolName, iconCategory]); - useEffect(() => { if (!onInlineDetailsHoverChange || isMobile || isExpanded) { return; diff --git a/packages/app/src/runtime/host-runtime.test.ts b/packages/app/src/runtime/host-runtime.test.ts index 4de92ac57..c0c45d346 100644 --- a/packages/app/src/runtime/host-runtime.test.ts +++ b/packages/app/src/runtime/host-runtime.test.ts @@ -76,6 +76,8 @@ class FakeDaemonClient { return { rttMs: 0 }; } + setReconnectEnabled(_enabled: boolean): void {} + setConnectionState(next: ConnectionState): void { this.state = next; if (next.status === "disconnected") { diff --git a/packages/app/src/runtime/host-runtime.ts b/packages/app/src/runtime/host-runtime.ts index 217cf4d58..c26e94736 100644 --- a/packages/app/src/runtime/host-runtime.ts +++ b/packages/app/src/runtime/host-runtime.ts @@ -3,7 +3,6 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; import { DaemonClient, type ConnectionState, - type DaemonClientDiagnosticsEvent, type FetchAgentsOptions, } from "@server/client/daemon-client"; import { @@ -38,10 +37,6 @@ import { import { createTauriWebSocketTransportFactory } from "@/utils/tauri-daemon-transport"; import { applyFetchedAgentDirectory } from "@/utils/agent-directory-sync"; import { useSessionStore, type Agent } from "@/stores/session-store"; -import { - recordDaemonClientDiagnostics, - recordHostRuntimeCreateClient, -} from "@/runtime/perf-diagnostics/host-runtime-diagnostics"; export type HostRuntimeConnectionStatus = | "idle" @@ -461,16 +456,6 @@ function probeIntervalForConnection( function createDefaultDeps(): HostRuntimeControllerDeps { return { createClient: ({ host, connection, clientId, runtimeGeneration }) => { - recordHostRuntimeCreateClient({ - serverId: host.serverId, - connectionType: connection.type, - endpoint: - connection.type === "directTcp" - ? connection.endpoint - : connection.type === "directSocket" || connection.type === "directPipe" - ? connection.path - : connection.relayEndpoint, - }); const tauriTransportFactory = createTauriWebSocketTransportFactory(); const localTransportFactory = createTauriLocalDaemonTransportFactory(); const base = { @@ -478,8 +463,6 @@ function createDefaultDeps(): HostRuntimeControllerDeps { clientId, clientType: "mobile" as const, runtimeGeneration, - onDiagnosticsEvent: (event: DaemonClientDiagnosticsEvent) => - recordDaemonClientDiagnostics(host.serverId, event), }; if (connection.type === "directSocket" || connection.type === "directPipe") { return new DaemonClient({ @@ -1074,6 +1057,9 @@ export class HostRuntimeController { } const nextGeneration = this.snapshot.clientGeneration + 1; + if (existingClient) { + existingClient.setReconnectEnabled(true); + } const client = existingClient ?? this.deps.createClient({ host: this.host, connection, @@ -1264,15 +1250,10 @@ export class HostRuntimeStore { } private async bootstrapDesktop(): Promise { - const t0 = performance.now(); - const elapsed = () => Math.round(performance.now() - t0); try { - console.info("[HostRuntime] desktop bootstrap: starting", { elapsedMs: elapsed() }); await Promise.allSettled([ (async () => { - console.info("[HostRuntime] desktop bootstrap: startManagedDaemon", { elapsedMs: elapsed() }); const daemon = await startManagedDaemon(); - console.info("[HostRuntime] desktop bootstrap: managed daemon ready", { elapsedMs: elapsed(), serverId: daemon.serverId }); if (!daemon.serverId) { return; } @@ -1285,12 +1266,10 @@ export class HostRuntimeStore { label: daemon.hostname ?? undefined, connection, }); - console.info("[HostRuntime] desktop bootstrap: host connection upserted", { elapsedMs: elapsed() }); })().catch((error) => { - console.warn("[HostRuntime] Failed to bootstrap desktop daemon connection", { elapsedMs: elapsed() }, error); + console.warn("[HostRuntime] Failed to bootstrap desktop daemon connection", error); }), (async () => { - console.info("[HostRuntime] desktop bootstrap: probing localhost", { elapsedMs: elapsed() }); const { client, serverId, hostname } = await connectToDaemon( { id: `bootstrap:${DEFAULT_LOCALHOST_ENDPOINT}`, @@ -1299,17 +1278,13 @@ export class HostRuntimeStore { }, { timeoutMs: DEFAULT_LOCALHOST_BOOTSTRAP_TIMEOUT_MS } ); - console.info("[HostRuntime] desktop bootstrap: localhost probe succeeded", { elapsedMs: elapsed(), serverId }); await this.upsertDirectConnection({ serverId, endpoint: DEFAULT_LOCALHOST_ENDPOINT, label: hostname ?? undefined, existingClient: client, }); - console.info("[HostRuntime] desktop bootstrap: direct connection upserted", { elapsedMs: elapsed() }); - })().catch(() => { - console.info("[HostRuntime] desktop bootstrap: localhost probe failed", { elapsedMs: elapsed() }); - }), + })().catch(() => undefined), ]); } catch (error) { console.warn("[HostRuntime] Failed to bootstrap desktop startup host connections", error); diff --git a/packages/app/src/runtime/perf-diagnostics/debug-tools.ts b/packages/app/src/runtime/perf-diagnostics/debug-tools.ts deleted file mode 100644 index f4818e80b..000000000 --- a/packages/app/src/runtime/perf-diagnostics/debug-tools.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - consumePersistedPerfDiagnosticReports, - getPerfDiagnosticsSnapshot, - isPerfDiagnosticsEnabled, - peekPersistedPerfDiagnosticReports, -} from "./engine"; - -export function installPerfDiagnosticsDebugTools(): () => void { - if (!isPerfDiagnosticsEnabled()) { - return () => {}; - } - - ( - globalThis as { - __PASEO_PERF_DIAGNOSTICS_DEBUG__?: { - snapshot: (limit?: number) => ReturnType; - consumeReports: () => Promise< - Awaited> - >; - peekReports: () => Promise< - Awaited> - >; - }; - } - ).__PASEO_PERF_DIAGNOSTICS_DEBUG__ = { - snapshot: (limit?: number) => getPerfDiagnosticsSnapshot(limit ?? 120), - consumeReports: () => consumePersistedPerfDiagnosticReports(), - peekReports: () => peekPersistedPerfDiagnosticReports(), - }; - - return () => { - ( - globalThis as { - __PASEO_PERF_DIAGNOSTICS_DEBUG__?: unknown; - } - ).__PASEO_PERF_DIAGNOSTICS_DEBUG__ = undefined; - }; -} diff --git a/packages/app/src/runtime/perf-diagnostics/engine.ts b/packages/app/src/runtime/perf-diagnostics/engine.ts deleted file mode 100644 index 796b4cf2a..000000000 --- a/packages/app/src/runtime/perf-diagnostics/engine.ts +++ /dev/null @@ -1,311 +0,0 @@ -import AsyncStorage from "@react-native-async-storage/async-storage"; -import { Platform } from "react-native"; -import { getNowMs } from "@/utils/perf"; -import type { - PerfDiagnosticBreadcrumb, - PerfDiagnosticFields, - PerfDiagnosticsReport, - PerfDiagnosticsSnapshot, - RecordPerfDiagnosticMarkOptions, -} from "./types"; - -const STORAGE_KEY = "paseo:perf-diagnostics:v1"; -const LEGACY_STORAGE_KEY = "paseo:js-hang-diagnostics:v1"; -const MONITOR_INTERVAL_MS = 100; -const STALL_THRESHOLD_MS = 250; -const SAMPLE_EVERY_N = 40; -const MAX_BREADCRUMBS = 600; -const BREADCRUMBS_PER_REPORT = 220; -const MAX_STORED_REPORTS = 20; - -const state = { - breadcrumbs: [] as PerfDiagnosticBreadcrumb[], - sampleCursor: 0, - monitorHandle: null as ReturnType | null, - monitorLastTickMs: 0, - monitorScope: null as string | null, - monitorRefCount: 0, - loadLogged: false, - persistQueue: Promise.resolve(), -}; - -function isPrimitive(value: unknown): value is string | number | boolean | null { - return ( - value === null || - typeof value === "string" || - typeof value === "number" || - typeof value === "boolean" - ); -} - -function sanitizeFieldValue(value: unknown): unknown { - if (isPrimitive(value)) { - if (typeof value === "string" && value.length > 180) { - return `${value.slice(0, 180)}...`; - } - return value; - } - if (value instanceof Date) { - return value.toISOString(); - } - if (Array.isArray(value)) { - return `[array:${value.length}]`; - } - if (value instanceof Map) { - return `[map:${value.size}]`; - } - if (value instanceof Set) { - return `[set:${value.size}]`; - } - if (typeof value === "bigint") { - return value.toString(); - } - if (value === undefined) { - return "[undefined]"; - } - return "[object]"; -} - -function sanitizeFields( - fields?: PerfDiagnosticFields -): PerfDiagnosticFields | undefined { - if (!fields) { - return undefined; - } - const next: PerfDiagnosticFields = {}; - let count = 0; - for (const [key, value] of Object.entries(fields)) { - if (count >= 16) { - next.__truncated__ = true; - break; - } - next[key] = sanitizeFieldValue(value); - count += 1; - } - return next; -} - -function shouldEnableDiagnostics(): boolean { - const globalFlag = ( - globalThis as { - __PASEO_PERF_DIAGNOSTICS__?: unknown; - __PASEO_JS_HANG_DIAGNOSTICS__?: unknown; - } - ).__PASEO_PERF_DIAGNOSTICS__; - const legacyFlag = ( - globalThis as { - __PASEO_PERF_DIAGNOSTICS__?: unknown; - __PASEO_JS_HANG_DIAGNOSTICS__?: unknown; - } - ).__PASEO_JS_HANG_DIAGNOSTICS__; - if (typeof globalFlag === "boolean") { - return globalFlag; - } - if (typeof legacyFlag === "boolean") { - return legacyFlag; - } - const isDev = Boolean((globalThis as { __DEV__?: boolean }).__DEV__); - return Platform.OS === "android" || isDev; -} - -function shouldSample(): boolean { - state.sampleCursor += 1; - if (state.sampleCursor >= SAMPLE_EVERY_N) { - state.sampleCursor = 0; - return true; - } - return false; -} - -function pushBreadcrumb(item: PerfDiagnosticBreadcrumb): void { - state.breadcrumbs.push(item); - if (state.breadcrumbs.length > MAX_BREADCRUMBS) { - state.breadcrumbs.splice(0, state.breadcrumbs.length - MAX_BREADCRUMBS); - } -} - -function safeParseReports(raw: string | null): PerfDiagnosticsReport[] { - if (!raw) { - return []; - } - try { - const parsed = JSON.parse(raw); - if (!Array.isArray(parsed)) { - return []; - } - return parsed.filter( - (entry) => entry && typeof entry === "object" - ) as PerfDiagnosticsReport[]; - } catch { - return []; - } -} - -async function readPersistedReports(): Promise { - const stored = safeParseReports(await AsyncStorage.getItem(STORAGE_KEY)); - if (stored.length > 0) { - return stored; - } - const legacy = safeParseReports(await AsyncStorage.getItem(LEGACY_STORAGE_KEY)); - if (legacy.length > 0) { - await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(legacy)); - await AsyncStorage.removeItem(LEGACY_STORAGE_KEY); - } - return legacy; -} - -async function persistReport(report: PerfDiagnosticsReport): Promise { - state.persistQueue = state.persistQueue - .then(async () => { - const existing = await readPersistedReports(); - existing.push(report); - if (existing.length > MAX_STORED_REPORTS) { - existing.splice(0, existing.length - MAX_STORED_REPORTS); - } - await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(existing)); - }) - .catch((error) => { - console.warn("[PerfDiagnostics] Failed to persist report", { - message: error instanceof Error ? error.message : String(error), - }); - }); - await state.persistQueue; -} - -function buildReport( - scope: string, - atMs: number, - lagMs: number -): PerfDiagnosticsReport { - const id = `${Math.round(atMs)}-${Math.floor(Math.random() * 1_000_000)}`; - const breadcrumbs = state.breadcrumbs.slice(-BREADCRUMBS_PER_REPORT); - const wallTimeMs = Date.now(); - return { - id, - scope, - atMs, - wallTimeMs, - wallTimeIso: new Date(wallTimeMs).toISOString(), - lagMs, - platform: Platform.OS, - breadcrumbs, - }; -} - -export function recordPerfDiagnosticMark( - name: string, - fields?: PerfDiagnosticFields, - options?: RecordPerfDiagnosticMarkOptions -): void { - if (!shouldEnableDiagnostics()) { - return; - } - const force = options?.force === true; - if (!force && !shouldSample()) { - return; - } - pushBreadcrumb({ - atMs: getNowMs(), - kind: "mark", - name, - fields: sanitizeFields(fields), - }); -} - -export function installPerfDiagnosticsMonitor(scope: string): () => void { - if (!shouldEnableDiagnostics()) { - return () => {}; - } - - if (state.monitorRefCount === 0) { - state.monitorScope = scope; - state.monitorLastTickMs = getNowMs(); - state.monitorHandle = setInterval(() => { - const nowMs = getNowMs(); - const lagMs = nowMs - state.monitorLastTickMs - MONITOR_INTERVAL_MS; - if (lagMs >= STALL_THRESHOLD_MS) { - const report = buildReport(state.monitorScope ?? scope, nowMs, lagMs); - recordPerfDiagnosticMark( - "perf.stall_detected", - { - scope: report.scope, - lagMs: Math.round(lagMs), - reportId: report.id, - breadcrumbs: report.breadcrumbs.length, - }, - { force: true } - ); - console.warn("[PerfDiagnostics] JS stall detected", { - scope: report.scope, - lagMs: Math.round(lagMs), - reportId: report.id, - breadcrumbs: report.breadcrumbs.length, - }); - void persistReport(report); - } - state.monitorLastTickMs = nowMs; - }, MONITOR_INTERVAL_MS); - } - - state.monitorRefCount += 1; - - if (!state.loadLogged) { - state.loadLogged = true; - void readPersistedReports() - .then((reports) => { - if (reports.length > 0) { - console.warn("[PerfDiagnostics] Recovered persisted stall reports", { - count: reports.length, - latestReportId: reports[reports.length - 1]?.id ?? null, - }); - } - }) - .catch(() => undefined); - } - - return () => { - state.monitorRefCount = Math.max(0, state.monitorRefCount - 1); - if (state.monitorRefCount === 0 && state.monitorHandle) { - clearInterval(state.monitorHandle); - state.monitorHandle = null; - state.monitorScope = null; - } - }; -} - -export async function consumePersistedPerfDiagnosticReports(): Promise< - PerfDiagnosticsReport[] -> { - const reports = await readPersistedReports(); - await AsyncStorage.removeItem(STORAGE_KEY); - await AsyncStorage.removeItem(LEGACY_STORAGE_KEY); - return reports; -} - -export async function peekPersistedPerfDiagnosticReports(): Promise< - PerfDiagnosticsReport[] -> { - return readPersistedReports(); -} - -export function isPerfDiagnosticsEnabled(): boolean { - return shouldEnableDiagnostics(); -} - -export function getPerfDiagnosticBreadcrumbs( - limit = 120 -): PerfDiagnosticBreadcrumb[] { - if (limit <= 0) { - return []; - } - return state.breadcrumbs.slice(-Math.floor(limit)); -} - -export function getPerfDiagnosticsSnapshot(limit = 120): PerfDiagnosticsSnapshot { - return { - enabled: shouldEnableDiagnostics(), - monitorScope: state.monitorScope, - breadcrumbCount: state.breadcrumbs.length, - breadcrumbs: getPerfDiagnosticBreadcrumbs(limit), - }; -} diff --git a/packages/app/src/runtime/perf-diagnostics/host-runtime-diagnostics.ts b/packages/app/src/runtime/perf-diagnostics/host-runtime-diagnostics.ts deleted file mode 100644 index 634dfac02..000000000 --- a/packages/app/src/runtime/perf-diagnostics/host-runtime-diagnostics.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { DaemonClientDiagnosticsEvent } from "@server/client/daemon-client"; -import { recordPerfDiagnosticMark } from "./engine"; - -let fastTransportSampleCursor = 0; -const transportRateByServer = new Map< - string, - { - startedAtMs: number; - messageCount: number; - bytes: number; - errorCount: number; - slowCount: number; - } ->(); - -function shouldSampleFastTransportEvent(): boolean { - fastTransportSampleCursor += 1; - if (fastTransportSampleCursor >= 50) { - fastTransportSampleCursor = 0; - return true; - } - return false; -} - -export function recordHostRuntimeCreateClient(params: { - serverId: string; - connectionType: "directTcp" | "directSocket" | "directPipe" | "relay"; - endpoint: string; -}): void { - recordPerfDiagnosticMark( - "host_runtime.create_client", - { - serverId: params.serverId, - connectionType: params.connectionType, - endpoint: params.endpoint, - }, - { force: true } - ); -} - -export function recordDaemonClientDiagnostics( - serverId: string, - event: DaemonClientDiagnosticsEvent -): void { - if (event.type === "transport_message_timing") { - const nowWallTimeMs = Date.now(); - const current = - transportRateByServer.get(serverId) ?? { - startedAtMs: nowWallTimeMs, - messageCount: 0, - bytes: 0, - errorCount: 0, - slowCount: 0, - }; - current.messageCount += 1; - current.bytes += event.payloadBytes; - if (event.outcome !== "ok") { - current.errorCount += 1; - } - if (event.totalMs >= 8) { - current.slowCount += 1; - } - if (nowWallTimeMs - current.startedAtMs >= 1000) { - recordPerfDiagnosticMark( - "daemon_client.message_rate", - { - serverId, - windowMs: nowWallTimeMs - current.startedAtMs, - messageCount: current.messageCount, - bytes: current.bytes, - errorCount: current.errorCount, - slowCount: current.slowCount, - }, - { force: current.errorCount > 0 || current.slowCount > 0 } - ); - current.startedAtMs = nowWallTimeMs; - current.messageCount = 0; - current.bytes = 0; - current.errorCount = 0; - current.slowCount = 0; - } - transportRateByServer.set(serverId, current); - } - - if (event.type === "transport_binary_frame") { - if (event.payloadBytes < 16_384) { - return; - } - recordPerfDiagnosticMark( - "daemon_client.binary_frame", - { - serverId, - channel: event.channel, - messageType: event.messageType, - payloadBytes: event.payloadBytes, - }, - { force: true } - ); - return; - } - - const isSlow = - event.totalMs >= 8 || event.parseMs >= 4 || event.validateMs >= 4; - const isError = event.outcome !== "ok"; - if (!isSlow && !isError && !shouldSampleFastTransportEvent()) { - return; - } - recordPerfDiagnosticMark( - "daemon_client.transport_message", - { - serverId, - messageType: event.messageType, - outcome: event.outcome, - payloadBytes: event.payloadBytes, - parseMs: Math.round(event.parseMs * 100) / 100, - validateMs: Math.round(event.validateMs * 100) / 100, - totalMs: Math.round(event.totalMs * 100) / 100, - }, - { force: isSlow || isError } - ); -} diff --git a/packages/app/src/runtime/perf-diagnostics/index.ts b/packages/app/src/runtime/perf-diagnostics/index.ts deleted file mode 100644 index cbb7f0f26..000000000 --- a/packages/app/src/runtime/perf-diagnostics/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export type { - PerfDiagnosticBreadcrumb, - PerfDiagnosticFields, - PerfDiagnosticsReport, - PerfDiagnosticsSnapshot, - PerfDiagnosticsReporter, - RecordPerfDiagnosticMarkOptions, -} from "./types"; - -export { - consumePersistedPerfDiagnosticReports, - getPerfDiagnosticBreadcrumbs, - getPerfDiagnosticsSnapshot, - installPerfDiagnosticsMonitor, - isPerfDiagnosticsEnabled, - peekPersistedPerfDiagnosticReports, - recordPerfDiagnosticMark, -} from "./engine"; - -export { getPerfDiagnosticsReporter } from "./reporter"; -export { PerfDiagnosticsProvider } from "./perf-diagnostics-provider"; -export { usePerfDiagnostics } from "./use-perf-diagnostics"; diff --git a/packages/app/src/runtime/perf-diagnostics/perf-diagnostics-context.ts b/packages/app/src/runtime/perf-diagnostics/perf-diagnostics-context.ts deleted file mode 100644 index 703c612c2..000000000 --- a/packages/app/src/runtime/perf-diagnostics/perf-diagnostics-context.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createContext } from "react"; -import type { PerfDiagnosticsReporter, PerfDiagnosticsSnapshot } from "./types"; - -const EMPTY_SNAPSHOT: PerfDiagnosticsSnapshot = { - enabled: false, - monitorScope: null, - breadcrumbCount: 0, - breadcrumbs: [], -}; - -const NOOP_REPORTER: PerfDiagnosticsReporter = { - mark: () => {}, - installMonitor: () => () => {}, - consumeReports: async () => [], - peekReports: async () => [], - isEnabled: () => false, - getSnapshot: () => EMPTY_SNAPSHOT, -}; - -export const PerfDiagnosticsContext = - createContext(NOOP_REPORTER); diff --git a/packages/app/src/runtime/perf-diagnostics/perf-diagnostics-provider.tsx b/packages/app/src/runtime/perf-diagnostics/perf-diagnostics-provider.tsx deleted file mode 100644 index 907950ee7..000000000 --- a/packages/app/src/runtime/perf-diagnostics/perf-diagnostics-provider.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect, useMemo, type ReactNode } from "react"; -import { installPerfDiagnosticsDebugTools } from "./debug-tools"; -import { PerfDiagnosticsContext } from "./perf-diagnostics-context"; -import { getPerfDiagnosticsReporter } from "./reporter"; - -interface PerfDiagnosticsProviderProps { - children: ReactNode; - scope?: string; -} - -export function PerfDiagnosticsProvider({ - children, - scope = "root_layout", -}: PerfDiagnosticsProviderProps) { - const reporter = useMemo(() => getPerfDiagnosticsReporter(), []); - - useEffect(() => { - if (!reporter.isEnabled()) { - return; - } - - const removeDebugTools = installPerfDiagnosticsDebugTools(); - const stopMonitor = reporter.installMonitor(scope); - - void reporter - .peekReports() - .then((reports) => { - if (reports.length === 0) { - return; - } - const latest = reports[reports.length - 1]; - console.warn("[PerfDiagnostics] Loaded persisted stall reports", { - count: reports.length, - latestReportId: latest?.id ?? null, - latestWallTime: latest?.wallTimeIso ?? null, - latestLagMs: latest ? Math.round(latest.lagMs) : null, - latestBreadcrumbs: latest?.breadcrumbs.length ?? null, - }); - }) - .catch(() => undefined); - - return () => { - removeDebugTools(); - stopMonitor(); - }; - }, [scope, reporter]); - - return ( - - {children} - - ); -} diff --git a/packages/app/src/runtime/perf-diagnostics/reporter.ts b/packages/app/src/runtime/perf-diagnostics/reporter.ts deleted file mode 100644 index 490b339b5..000000000 --- a/packages/app/src/runtime/perf-diagnostics/reporter.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - consumePersistedPerfDiagnosticReports, - getPerfDiagnosticsSnapshot, - installPerfDiagnosticsMonitor, - isPerfDiagnosticsEnabled, - peekPersistedPerfDiagnosticReports, - recordPerfDiagnosticMark, -} from "./engine"; -import type { PerfDiagnosticsReporter } from "./types"; - -const reporter: PerfDiagnosticsReporter = { - mark: recordPerfDiagnosticMark, - installMonitor: installPerfDiagnosticsMonitor, - consumeReports: consumePersistedPerfDiagnosticReports, - peekReports: peekPersistedPerfDiagnosticReports, - isEnabled: isPerfDiagnosticsEnabled, - getSnapshot: getPerfDiagnosticsSnapshot, -}; - -export function getPerfDiagnosticsReporter(): PerfDiagnosticsReporter { - return reporter; -} diff --git a/packages/app/src/runtime/perf-diagnostics/types.ts b/packages/app/src/runtime/perf-diagnostics/types.ts deleted file mode 100644 index ab8245244..000000000 --- a/packages/app/src/runtime/perf-diagnostics/types.ts +++ /dev/null @@ -1,44 +0,0 @@ -export type PerfDiagnosticFields = Record; - -export interface PerfDiagnosticBreadcrumb { - atMs: number; - kind: "mark" | "span"; - name: string; - durationMs?: number; - fields?: PerfDiagnosticFields; -} - -export interface PerfDiagnosticsReport { - id: string; - scope: string; - atMs: number; - wallTimeMs: number; - wallTimeIso: string; - lagMs: number; - platform: string; - breadcrumbs: PerfDiagnosticBreadcrumb[]; -} - -export interface RecordPerfDiagnosticMarkOptions { - force?: boolean; -} - -export interface PerfDiagnosticsSnapshot { - enabled: boolean; - monitorScope: string | null; - breadcrumbCount: number; - breadcrumbs: PerfDiagnosticBreadcrumb[]; -} - -export interface PerfDiagnosticsReporter { - mark: ( - name: string, - fields?: PerfDiagnosticFields, - options?: RecordPerfDiagnosticMarkOptions - ) => void; - installMonitor: (scope: string) => () => void; - consumeReports: () => Promise; - peekReports: () => Promise; - isEnabled: () => boolean; - getSnapshot: (limit?: number) => PerfDiagnosticsSnapshot; -} diff --git a/packages/app/src/runtime/perf-diagnostics/use-perf-diagnostics.ts b/packages/app/src/runtime/perf-diagnostics/use-perf-diagnostics.ts deleted file mode 100644 index 65db3e1c1..000000000 --- a/packages/app/src/runtime/perf-diagnostics/use-perf-diagnostics.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { useContext } from "react"; -import { PerfDiagnosticsContext } from "./perf-diagnostics-context"; -import type { PerfDiagnosticsReporter } from "./types"; - -export function usePerfDiagnostics(): PerfDiagnosticsReporter { - return useContext(PerfDiagnosticsContext); -} diff --git a/packages/app/src/screens/agent/agent-ready-screen.tsx b/packages/app/src/screens/agent/agent-ready-screen.tsx index 3396c04ac..d980a148f 100644 --- a/packages/app/src/screens/agent/agent-ready-screen.tsx +++ b/packages/app/src/screens/agent/agent-ready-screen.tsx @@ -30,11 +30,6 @@ import { import { useCreateFlowStore } from "@/stores/create-flow-store"; import type { Agent } from "@/contexts/session-context"; import type { StreamItem } from "@/types/stream"; -import { - buildAgentNavigationKey, - endNavigationTiming, -} from "@/utils/navigation-timing"; -import { startPerfMonitor } from "@/utils/perf-monitor"; import { checkoutStatusQueryKey, type CheckoutStatusPayload, @@ -115,27 +110,6 @@ export function AgentReadyScreen({ const lastConnectionError = runtimeSnapshot?.lastError ?? null; const isRuntimeSessionAvailable = Boolean(resolvedServerId && runtimeClient); - const focusServerId = resolvedServerId; - const navigationStatus = isRuntimeSessionAvailable - ? "ready" - : "session_unavailable"; - - useFocusEffect( - useCallback(() => { - if (!resolvedAgentId || !focusServerId) { - return; - } - const navigationKey = buildAgentNavigationKey( - focusServerId, - resolvedAgentId - ); - endNavigationTiming(navigationKey, { - screen: "agent", - status: navigationStatus, - }); - }, [focusServerId, navigationStatus, resolvedAgentId]) - ); - if (!resolvedServerId || !runtimeClient) { return ( { - if (Platform.OS !== "web") { - return; - } - const scope = `agent:${serverId}:${agentId ?? "unknown"}`; - const stop = startPerfMonitor(scope); - return stop; - }, [serverId, agentId]); - // Swipe-left gesture to open explorer sidebar on mobile const explorerOpenGesture = useExplorerOpenGesture({ enabled: isMobile && mobileView === "agent", diff --git a/packages/app/src/screens/open-project-screen.tsx b/packages/app/src/screens/open-project-screen.tsx index dbddca892..3ab7fbf45 100644 --- a/packages/app/src/screens/open-project-screen.tsx +++ b/packages/app/src/screens/open-project-screen.tsx @@ -8,7 +8,7 @@ import { SidebarMenuToggle } from "@/components/headers/menu-header"; import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store"; import { usePanelStore } from "@/stores/panel-store"; import { getIsTauriMac } from "@/constants/layout"; -import { useTrafficLightPadding } from "@/utils/tauri-window"; +import { useTauriDragHandlers, useTrafficLightPadding } from "@/utils/tauri-window"; export function OpenProjectScreen({ serverId: _serverId }: { serverId: string }) { const { theme } = useUnistyles(); @@ -21,6 +21,7 @@ export function OpenProjectScreen({ serverId: _serverId }: { serverId: string }) const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm"; const needsTrafficLightInset = !isMobile && !desktopAgentListOpen && getIsTauriMac(); const trafficLightInset = needsTrafficLightInset ? trafficLightPadding.left : 0; + const dragHandlers = useTauriDragHandlers(); useEffect(() => { if (!isMobile) { @@ -29,7 +30,7 @@ export function OpenProjectScreen({ serverId: _serverId }: { serverId: string }) }, [isMobile, openAgentList]); return ( - + @@ -56,6 +57,7 @@ const styles = StyleSheet.create((theme) => ({ container: { flex: 1, backgroundColor: theme.colors.surface0, + userSelect: "none", }, menuToggle: { position: "absolute", diff --git a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx index c42edcd49..9b2ff3262 100644 --- a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx +++ b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx @@ -5,7 +5,6 @@ import { AgentInputArea } from '@/components/agent-input-area' import { FileDropZone } from '@/components/file-drop-zone' import { AgentStreamView } from '@/components/agent-stream-view' import type { ImageAttachment } from '@/components/message-input' -import { MAX_CONTENT_WIDTH } from '@/constants/layout' import { useAgentFormState } from '@/hooks/use-agent-form-state' import { useDraftAgentCreateFlow } from '@/hooks/use-draft-agent-create-flow' import { useHostRuntimeSession } from '@/runtime/host-runtime' @@ -260,8 +259,6 @@ const styles = StyleSheet.create((theme) => ({ container: { flex: 1, width: '100%', - alignSelf: 'center', - maxWidth: MAX_CONTENT_WIDTH, backgroundColor: theme.colors.surface0, }, contentContainer: { diff --git a/packages/app/src/stores/session-store.ts b/packages/app/src/stores/session-store.ts index 1bbd1652b..9367b0365 100644 --- a/packages/app/src/stores/session-store.ts +++ b/packages/app/src/stores/session-store.ts @@ -26,7 +26,6 @@ import type { WorkspaceDescriptorPayload, } from "@server/shared/messages"; import { normalizeWorkspaceIdentity } from "@/utils/workspace-identity"; -import { isPerfLoggingEnabled, measurePayload, perfLog } from "@/utils/perf"; import { createAgentLastActivityCoalescer, type AgentLastActivityCommitter, @@ -357,30 +356,8 @@ interface SessionStoreActions { type SessionStore = SessionStoreState & SessionStoreActions; -const SESSION_STORE_LOG_TAG = "[SessionStore]"; -let sessionStoreUpdateCount = 0; const agentLastActivityCoalescer = createAgentLastActivityCoalescer(); -function logSessionStoreUpdate( - type: string, - serverId: string, - payload?: unknown -) { - if (!isPerfLoggingEnabled()) { - return; - } - sessionStoreUpdateCount += 1; - const metrics = payload ? measurePayload(payload) : null; - perfLog(SESSION_STORE_LOG_TAG, { - event: type, - serverId, - updateCount: sessionStoreUpdateCount, - payloadApproxBytes: metrics?.approxBytes ?? 0, - payloadFieldCount: metrics?.fieldCount ?? 0, - timestamp: Date.now(), - }); -} - // Helper to create initial session state function createInitialSessionState(serverId: string, client: DaemonClient, audioPlayer: ReturnType): SessionState { return { @@ -452,7 +429,6 @@ export const useSessionStore = create()( if (prev.sessions[serverId]) { return prev; } - logSessionStoreUpdate("initializeSession", serverId); return { ...prev, sessions: { @@ -470,7 +446,6 @@ export const useSessionStore = create()( if (!session) { return prev; } - logSessionStoreUpdate("clearSession", serverId); const nextSessions = { ...prev.sessions }; delete nextSessions[serverId]; let nextActivity = prev.agentLastActivity; @@ -507,12 +482,6 @@ export const useSessionStore = create()( return prev; } - logSessionStoreUpdate("updateSessionClient", serverId, { - wasNull: session.client === null, - isNowConnected: client.isConnected, - isNowConnecting: client.isConnecting, - }); - return { ...prev, sessions: { @@ -549,12 +518,6 @@ export const useSessionStore = create()( return prev; } - logSessionStoreUpdate("updateSessionServerInfo", serverId, { - serverId: info.serverId, - hostname: nextHostname, - version: nextVersion, - }); - return { ...prev, sessions: { @@ -584,7 +547,6 @@ export const useSessionStore = create()( if (!session || session.isPlayingAudio === playing) { return prev; } - logSessionStoreUpdate("setIsPlayingAudio", serverId, { playing }); return { ...prev, sessions: { @@ -602,8 +564,6 @@ export const useSessionStore = create()( if (!session || session.focusedAgentId === agentId) { return prev; } - logSessionStoreUpdate("setFocusedAgentId", serverId, { agentId }); - return { ...prev, sessions: { @@ -629,7 +589,6 @@ export const useSessionStore = create()( if (session.messages === nextMessages) { return prev; } - logSessionStoreUpdate("setMessages", serverId, { count: nextMessages.length }); return { ...prev, sessions: { @@ -650,7 +609,6 @@ export const useSessionStore = create()( if (session.currentAssistantMessage === nextMessage) { return prev; } - logSessionStoreUpdate("setCurrentAssistantMessage", serverId, { length: nextMessage.length }); return { ...prev, sessions: { @@ -672,7 +630,6 @@ export const useSessionStore = create()( if (session.agentStreamTail === nextState) { return prev; } - logSessionStoreUpdate("setAgentStreamTail", serverId, { agentCount: nextState.size }); return { ...prev, sessions: { @@ -693,7 +650,6 @@ export const useSessionStore = create()( if (session.agentStreamHead === nextState) { return prev; } - logSessionStoreUpdate("setAgentStreamHead", serverId, { agentCount: nextState.size }); return { ...prev, sessions: { @@ -745,12 +701,6 @@ export const useSessionStore = create()( return prev; } - logSessionStoreUpdate("setAgentStreamState", serverId, { - agentId, - changedTail, - changedHead, - }); - return { ...prev, sessions: { @@ -776,7 +726,6 @@ export const useSessionStore = create()( } const nextHead = new Map(session.agentStreamHead); nextHead.delete(agentId); - logSessionStoreUpdate("clearAgentStreamHead", serverId, { agentId }); return { ...prev, sessions: { @@ -798,9 +747,6 @@ export const useSessionStore = create()( if (session.agentTimelineCursor === nextState) { return prev; } - logSessionStoreUpdate("setAgentTimelineCursor", serverId, { - agentCount: nextState.size, - }); return { ...prev, sessions: { @@ -818,9 +764,6 @@ export const useSessionStore = create()( return prev; } const nextGeneration = session.historySyncGeneration + 1; - logSessionStoreUpdate("bumpHistorySyncGeneration", serverId, { - generation: nextGeneration, - }); return { ...prev, sessions: { @@ -847,10 +790,6 @@ export const useSessionStore = create()( } const nextMap = new Map(session.agentHistorySyncGeneration); nextMap.set(agentId, currentGeneration); - logSessionStoreUpdate("markAgentHistorySynchronized", serverId, { - agentId, - generation: currentGeneration, - }); return { ...prev, sessions: { @@ -884,11 +823,6 @@ export const useSessionStore = create()( nextApplied.delete(agentId); } - logSessionStoreUpdate("setAgentAuthoritativeHistoryApplied", serverId, { - agentId, - applied, - }); - return { ...prev, sessions: { @@ -913,7 +847,6 @@ export const useSessionStore = create()( if (session.initializingAgents === nextState) { return prev; } - logSessionStoreUpdate("setInitializingAgents", serverId, { count: nextState.size }); return { ...prev, sessions: { @@ -935,7 +868,6 @@ export const useSessionStore = create()( if (session.agents === nextAgents) { return prev; } - logSessionStoreUpdate("setAgents", serverId, { count: nextAgents.size }); return { ...prev, sessions: { @@ -957,7 +889,6 @@ export const useSessionStore = create()( if (session.workspaces === nextWorkspaces) { return prev; } - logSessionStoreUpdate("setWorkspaces", serverId, { count: nextWorkspaces.size }); return { ...prev, sessions: { @@ -988,7 +919,6 @@ export const useSessionStore = create()( if (!changed) { return prev; } - logSessionStoreUpdate("mergeWorkspaces", serverId, { count: nextEntries.length }); return { ...prev, sessions: { @@ -1007,7 +937,6 @@ export const useSessionStore = create()( } const next = new Map(session.workspaces); next.delete(workspaceId); - logSessionStoreUpdate("removeWorkspace", serverId, { workspaceId }); return { ...prev, sessions: { @@ -1074,7 +1003,6 @@ export const useSessionStore = create()( if (session.pendingPermissions === nextPerms) { return prev; } - logSessionStoreUpdate("setPendingPermissions", serverId, { count: nextPerms.size }); return { ...prev, sessions: { @@ -1096,7 +1024,6 @@ export const useSessionStore = create()( if (session.fileExplorer === nextState) { return prev; } - logSessionStoreUpdate("setFileExplorer", serverId, { agentCount: nextState.size }); return { ...prev, sessions: { @@ -1118,7 +1045,6 @@ export const useSessionStore = create()( if (session.queuedMessages === nextValue) { return prev; } - logSessionStoreUpdate("setQueuedMessages", serverId, { agentCount: nextValue.size }); return { ...prev, sessions: { @@ -1136,7 +1062,6 @@ export const useSessionStore = create()( if (!session || session.hasHydratedAgents === hydrated) { return prev; } - logSessionStoreUpdate("setHasHydratedAgents", serverId, { hydrated }); return { ...prev, sessions: { @@ -1153,7 +1078,6 @@ export const useSessionStore = create()( if (!session || session.hasHydratedWorkspaces === hydrated) { return prev; } - logSessionStoreUpdate("setHasHydratedWorkspaces", serverId, { hydrated }); return { ...prev, sessions: { diff --git a/packages/app/src/utils/navigation-timing.ts b/packages/app/src/utils/navigation-timing.ts deleted file mode 100644 index c651b5a99..000000000 --- a/packages/app/src/utils/navigation-timing.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { getNowMs, isPerfLoggingEnabled, perfLog } from "@/utils/perf"; - -type NavigationTimingDetails = { - from: string; - to: string; - params?: Record; - targetMs?: number; -}; - -type NavigationTimingEntry = NavigationTimingDetails & { - startedAt: number; -}; - -const NAVIGATION_TAG = "[NavigationTiming]"; -const pendingNavigations = new Map(); - -export const HOME_NAVIGATION_KEY = "home"; - -export const buildAgentNavigationKey = (serverId: string, agentId: string) => - `agent:${serverId}:${agentId}`; - -export const startNavigationTiming = (key: string, details: NavigationTimingDetails): void => { - if (!isPerfLoggingEnabled()) { - return; - } - - pendingNavigations.set(key, { - ...details, - startedAt: getNowMs(), - }); - - perfLog(NAVIGATION_TAG, { - phase: "start", - key, - from: details.from, - to: details.to, - targetMs: details.targetMs ?? null, - params: details.params ?? null, - }); -}; - -export const endNavigationTiming = (key: string, extra?: Record): void => { - if (!isPerfLoggingEnabled()) { - return; - } - - const entry = pendingNavigations.get(key); - if (!entry) { - return; - } - pendingNavigations.delete(key); - - const durationMs = getNowMs() - entry.startedAt; - perfLog(NAVIGATION_TAG, { - phase: "complete", - key, - from: entry.from, - to: entry.to, - durationMs: Number(durationMs.toFixed(2)), - targetMs: entry.targetMs ?? null, - params: entry.params ?? null, - extra: extra ?? null, - }); -}; - -export const cancelNavigationTiming = (key: string, reason?: string): void => { - if (!isPerfLoggingEnabled()) { - return; - } - - if (!pendingNavigations.has(key)) { - return; - } - pendingNavigations.delete(key); - perfLog(NAVIGATION_TAG, { - phase: "cancelled", - key, - reason: reason ?? null, - }); -}; diff --git a/packages/app/src/utils/perf-diagnostics.ts b/packages/app/src/utils/perf-diagnostics.ts deleted file mode 100644 index adfcc02ff..000000000 --- a/packages/app/src/utils/perf-diagnostics.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Compatibility shim while callers move to `@/runtime/perf-diagnostics`. -// Keep this file side-effect free. -export { - consumePersistedPerfDiagnosticReports, - getPerfDiagnosticBreadcrumbs, - getPerfDiagnosticsSnapshot, - installPerfDiagnosticsMonitor, - isPerfDiagnosticsEnabled, - peekPersistedPerfDiagnosticReports, - recordPerfDiagnosticMark, -} from "@/runtime/perf-diagnostics"; - -export type { - PerfDiagnosticBreadcrumb, - PerfDiagnosticFields, - PerfDiagnosticsReport, - PerfDiagnosticsSnapshot, - PerfDiagnosticsReporter, - RecordPerfDiagnosticMarkOptions, -} from "@/runtime/perf-diagnostics"; diff --git a/packages/app/src/utils/perf-monitor.ts b/packages/app/src/utils/perf-monitor.ts deleted file mode 100644 index f8da81bf9..000000000 --- a/packages/app/src/utils/perf-monitor.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { getNowMs, isPerfLoggingEnabled, perfLog } from "@/utils/perf"; - -const PERF_MONITOR_LOG_TAG = "[PerfMonitor]"; -const FRAME_GAP_THRESHOLD_MS = 100; -const EVENT_LOOP_STALL_THRESHOLD_MS = 100; -const EVENT_LOOP_TICK_MS = 100; -const LONG_TASK_THRESHOLD_MS = 100; - -type StopPerfMonitor = () => void; - -type GlobalScope = typeof globalThis & { - requestAnimationFrame?: (callback: (timestamp: number) => void) => number; - cancelAnimationFrame?: (handle: number) => void; - PerformanceObserver?: unknown; -}; - -type PerfObserverEntry = { - duration: number; - startTime: number; -}; - -type PerfObserverLike = { - observe: (options: { entryTypes: string[] }) => void; - disconnect: () => void; -}; - -type PerfObserverConstructor = new ( - callback: (list: { getEntries(): PerfObserverEntry[] }) => void -) => PerfObserverLike; - -export function startPerfMonitor(scope: string): StopPerfMonitor { - if (!isPerfLoggingEnabled()) { - return () => {}; - } - - const globalScope = globalThis as GlobalScope; - if (!globalScope) { - return () => {}; - } - - const startMs = getNowMs(); - perfLog(PERF_MONITOR_LOG_TAG, { event: "monitor_start", scope }); - - let rafHandle: number | null = null; - let lastFrameMs = getNowMs(); - - if (typeof globalScope.requestAnimationFrame === "function") { - const onFrame = (now: number) => { - const delta = now - lastFrameMs; - if (delta >= FRAME_GAP_THRESHOLD_MS) { - perfLog(PERF_MONITOR_LOG_TAG, { - event: "frame_gap", - scope, - deltaMs: Math.round(delta), - sinceStartMs: Math.round(now - startMs), - }); - } - lastFrameMs = now; - rafHandle = globalScope.requestAnimationFrame?.(onFrame) ?? null; - }; - rafHandle = globalScope.requestAnimationFrame(onFrame); - } - - let lastTickMs = getNowMs(); - const intervalHandle = setInterval(() => { - const now = getNowMs(); - const drift = now - lastTickMs - EVENT_LOOP_TICK_MS; - if (drift >= EVENT_LOOP_STALL_THRESHOLD_MS) { - perfLog(PERF_MONITOR_LOG_TAG, { - event: "event_loop_stall", - scope, - driftMs: Math.round(drift), - sinceStartMs: Math.round(now - startMs), - }); - } - lastTickMs = now; - }, EVENT_LOOP_TICK_MS); - - let observer: PerfObserverLike | null = null; - const ObserverCtor = globalScope.PerformanceObserver as PerfObserverConstructor | undefined; - if (typeof ObserverCtor === "function") { - try { - observer = new ObserverCtor((list) => { - for (const entry of list.getEntries()) { - if (entry.duration >= LONG_TASK_THRESHOLD_MS) { - perfLog(PERF_MONITOR_LOG_TAG, { - event: "longtask", - scope, - durationMs: Math.round(entry.duration), - startMs: Math.round(entry.startTime), - }); - } - } - }); - observer.observe({ entryTypes: ["longtask"] }); - } catch (error) { - perfLog(PERF_MONITOR_LOG_TAG, { - event: "longtask_observer_error", - scope, - message: error instanceof Error ? error.message : String(error), - }); - } - } - - return () => { - if (rafHandle !== null && typeof globalScope.cancelAnimationFrame === "function") { - globalScope.cancelAnimationFrame(rafHandle); - } - clearInterval(intervalHandle); - observer?.disconnect(); - perfLog(PERF_MONITOR_LOG_TAG, { event: "monitor_stop", scope }); - }; -} diff --git a/packages/app/src/utils/perf.ts b/packages/app/src/utils/perf.ts deleted file mode 100644 index 45dc7f196..000000000 --- a/packages/app/src/utils/perf.ts +++ /dev/null @@ -1,105 +0,0 @@ -const PERF_LOGGING_ENABLED = false; - -export const isPerfLoggingEnabled = (): boolean => PERF_LOGGING_ENABLED; - -export const perfLog = (tag: string, details: Record): void => { - if (!PERF_LOGGING_ENABLED) { - return; - } - console.info(tag, details); -}; - -export const getNowMs = (): number => { - if (typeof performance !== "undefined" && typeof performance.now === "function") { - return performance.now(); - } - return Date.now(); -}; - -export interface PayloadMetrics { - approxBytes: number; - fieldCount: number; -} - -const MEASUREMENT_DEPTH = 2; - -export const measurePayload = (payload: unknown): PayloadMetrics => { - if (!payload || typeof payload !== "object") { - return { - approxBytes: 0, - fieldCount: 0, - }; - } - - const fieldCount = Object.keys(payload as Record).length; - const approxBytes = estimateSize(payload, MEASUREMENT_DEPTH, new WeakSet()); - - return { - approxBytes, - fieldCount, - }; -}; - -const estimateSize = (value: unknown, depth: number, seen: WeakSet): number => { - if (depth <= 0 || value === null || value === undefined) { - return 0; - } - - if (typeof value === "string") { - return value.length; - } - - if (typeof value === "number") { - return 8; - } - - if (typeof value === "boolean") { - return 4; - } - - if (typeof value === "bigint") { - return value.toString().length; - } - - if (typeof value === "symbol" || typeof value === "function") { - return 0; - } - - if (typeof value !== "object") { - return 0; - } - - const objectValue = value as object; - if (seen.has(objectValue)) { - return 0; - } - seen.add(objectValue); - - if (Array.isArray(value)) { - return value.reduce((total, item) => total + estimateSize(item, depth - 1, seen), 0); - } - - if (value instanceof Map) { - let total = 0; - for (const [mapKey, mapValue] of value.entries()) { - total += estimateSize(mapKey, depth - 1, seen); - total += estimateSize(mapValue, depth - 1, seen); - } - return total; - } - - if (value instanceof Set) { - let total = 0; - for (const setValue of value.values()) { - total += estimateSize(setValue, depth - 1, seen); - } - return total; - } - - let total = 0; - for (const [key, child] of Object.entries(value as Record)) { - total += key.length; - total += estimateSize(child, depth - 1, seen); - } - return total; -}; diff --git a/packages/app/src/utils/test-daemon-connection.ts b/packages/app/src/utils/test-daemon-connection.ts index 4b9b5bcdd..5c9589e2a 100644 --- a/packages/app/src/utils/test-daemon-connection.ts +++ b/packages/app/src/utils/test-daemon-connection.ts @@ -56,6 +56,7 @@ export async function buildClientConfig( clientId, clientType: "mobile" as const, suppressSendErrors: true, + reconnect: { enabled: false }, ...(connection.type === "directSocket" || connection.type === "directPipe" ? localTransportFactory ? { transportFactory: localTransportFactory } diff --git a/packages/app/src/utils/tool-call-parsers.ts b/packages/app/src/utils/tool-call-parsers.ts index fa915a94e..a521f6ea3 100644 --- a/packages/app/src/utils/tool-call-parsers.ts +++ b/packages/app/src/utils/tool-call-parsers.ts @@ -1,11 +1,4 @@ import { z } from "zod"; -import { getNowMs, isPerfLoggingEnabled, perfLog } from "./perf"; - -const TOOL_CALL_DIFF_LOG_TAG = "[ToolCallDiff]"; -const LINE_DIFF_DURATION_THRESHOLD_MS = 16; -const WORD_DIFF_DURATION_THRESHOLD_MS = 8; -const LINE_DIFF_MATRIX_THRESHOLD = 200000; -const WORD_DIFF_MATRIX_THRESHOLD = 50000; export type DiffSegment = { text: string; @@ -56,14 +49,11 @@ function splitIntoWords(text: string): string[] { } function computeWordLevelDiff(oldLine: string, newLine: string): { oldSegments: DiffSegment[]; newSegments: DiffSegment[] } { - const shouldLog = isPerfLoggingEnabled(); - const startMs = shouldLog ? getNowMs() : 0; const oldWords = splitIntoWords(oldLine); const newWords = splitIntoWords(newLine); const m = oldWords.length; const n = newWords.length; - const matrixSize = m * n; // LCS to find common words const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); @@ -131,22 +121,6 @@ function computeWordLevelDiff(oldLine: string, newLine: string): { oldSegments: const oldSegments = buildSegments(oldWords, oldInLCS); const newSegments = buildSegments(newWords, newInLCS); - if (shouldLog) { - const durationMs = getNowMs() - startMs; - if ( - durationMs >= WORD_DIFF_DURATION_THRESHOLD_MS || - matrixSize >= WORD_DIFF_MATRIX_THRESHOLD - ) { - perfLog(TOOL_CALL_DIFF_LOG_TAG, { - event: "word_diff", - durationMs: Math.round(durationMs), - oldWordCount: m, - newWordCount: n, - matrixSize, - }); - } - } - return { oldSegments, newSegments, @@ -154,8 +128,6 @@ function computeWordLevelDiff(oldLine: string, newLine: string): { oldSegments: } export function buildLineDiff(originalText: string, updatedText: string): DiffLine[] { - const shouldLog = isPerfLoggingEnabled(); - const startMs = shouldLog ? getNowMs() : 0; const originalLines = splitIntoLines(originalText); const updatedLines = splitIntoLines(updatedText); @@ -166,7 +138,6 @@ export function buildLineDiff(originalText: string, updatedText: string): DiffLi const m = originalLines.length; const n = updatedLines.length; - const matrixSize = m * n; const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0) ); @@ -225,23 +196,6 @@ export function buildLineDiff(originalText: string, updatedText: string): DiffLi } } - if (shouldLog) { - const durationMs = getNowMs() - startMs; - if ( - durationMs >= LINE_DIFF_DURATION_THRESHOLD_MS || - matrixSize >= LINE_DIFF_MATRIX_THRESHOLD - ) { - perfLog(TOOL_CALL_DIFF_LOG_TAG, { - event: "line_diff", - durationMs: Math.round(durationMs), - originalLineCount: m, - updatedLineCount: n, - diffLineCount: diff.length, - matrixSize, - }); - } - } - return diff; } diff --git a/packages/server/src/client/daemon-client.ts b/packages/server/src/client/daemon-client.ts index 60707e5b1..ca320ac7f 100644 --- a/packages/server/src/client/daemon-client.ts +++ b/packages/server/src/client/daemon-client.ts @@ -102,13 +102,6 @@ const consoleLogger: Logger = { error: (obj, msg) => console.error(msg, obj), } -function getNowMs(): number { - if (typeof performance !== 'undefined' && typeof performance.now === 'function') { - return performance.now() - } - return Date.now() -} - export type { DaemonTransport, DaemonTransportFactory, @@ -180,26 +173,8 @@ export type DaemonClientConfig = { baseDelayMs?: number maxDelayMs?: number } - onDiagnosticsEvent?: (event: DaemonClientDiagnosticsEvent) => void } -export type DaemonClientDiagnosticsEvent = - | { - type: 'transport_message_timing' - messageType: string - payloadBytes: number - parseMs: number - validateMs: number - totalMs: number - outcome: 'ok' | 'parse_error' | 'validation_error' - } - | { - type: 'transport_binary_frame' - channel: BinaryMuxChannel - messageType: number - payloadBytes: number - } - export type SendMessageOptions = { messageId?: string images?: Array<{ data: string; mimeType: string }> @@ -489,14 +464,6 @@ export class DaemonClient { }) } - private emitDiagnosticsEvent(event: DaemonClientDiagnosticsEvent): void { - try { - this.config.onDiagnosticsEvent?.(event) - } catch { - // Diagnostics hooks must never break daemon message handling. - } - } - // ============================================================================ // Connection // ============================================================================ @@ -2873,7 +2840,6 @@ export class DaemonClient { } private handleTransportMessage(data: unknown): void { - const startedAtMs = getNowMs() const rawData = data && typeof data === 'object' && 'data' in data ? (data as { data: unknown }).data : data @@ -2897,12 +2863,6 @@ export class DaemonClient { if (rawBytes) { const frame = decodeBinaryMuxFrame(rawBytes) if (frame) { - this.emitDiagnosticsEvent({ - type: 'transport_binary_frame', - channel: frame.channel, - messageType: frame.messageType, - payloadBytes: frame.payload?.byteLength ?? 0, - }) this.handleBinaryFrame(frame) return } @@ -2913,52 +2873,19 @@ export class DaemonClient { } let parsedJson: unknown - let parseMs = 0 try { - const parseStartedAtMs = getNowMs() parsedJson = JSON.parse(payload) - parseMs = getNowMs() - parseStartedAtMs } catch { - this.emitDiagnosticsEvent({ - type: 'transport_message_timing', - messageType: 'unknown', - payloadBytes: payload.length, - parseMs: 0, - validateMs: 0, - totalMs: getNowMs() - startedAtMs, - outcome: 'parse_error', - }) return } - const validateStartedAtMs = getNowMs() const parsed = WSOutboundMessageSchema.safeParse(parsedJson) - const validateMs = getNowMs() - validateStartedAtMs if (!parsed.success) { const msgType = (parsedJson as { type?: string })?.type ?? 'unknown' - this.emitDiagnosticsEvent({ - type: 'transport_message_timing', - messageType: msgType, - payloadBytes: payload.length, - parseMs, - validateMs, - totalMs: getNowMs() - startedAtMs, - outcome: 'validation_error', - }) this.logger.warn({ msgType, error: parsed.error.message }, 'Message validation failed') return } - this.emitDiagnosticsEvent({ - type: 'transport_message_timing', - messageType: parsed.data.type, - payloadBytes: payload.length, - parseMs, - validateMs, - totalMs: getNowMs() - startedAtMs, - outcome: 'ok', - }) - if (parsed.data.type === 'pong') { return } @@ -3018,6 +2945,10 @@ export class DaemonClient { } } + setReconnectEnabled(enabled: boolean): void { + this.config = { ...this.config, reconnect: { ...this.config.reconnect, enabled } } + } + private scheduleReconnect(input?: { reason?: string event?: string