refactor: remove performance monitoring and diagnostics infrastructure

This commit is contained in:
Mohamed Boudra
2026-03-12 21:15:01 +07:00
parent b09a731d85
commit be93f8b240
29 changed files with 65 additions and 1553 deletions
@@ -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<unknown[]>;
};
}
).__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", {
+47 -50
View File
@@ -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() {
<GestureHandlerRootView
style={{ flex: 1, backgroundColor: darkTheme.colors.surface0 }}
>
<PerfDiagnosticsProvider scope="root_layout">
<PortalProvider>
<SafeAreaProvider>
<KeyboardProvider>
<BottomSheetModalProvider>
<QueryProvider>
<HostRuntimeBootstrapProvider>
<PushNotificationRouter />
<HostSessionManager />
<ProvidersWrapper>
<SidebarAnimationProvider>
<HorizontalScrollProvider>
<ToastProvider>
<AppWithSidebar>
<Stack
screenOptions={{
headerShown: false,
animation: "none",
contentStyle: {
backgroundColor: darkTheme.colors.surface0,
},
}}
>
<Stack.Screen name="index" />
<Stack.Screen name="settings" />
<Stack.Screen name="h/[serverId]/workspace/[workspaceId]" />
<Stack.Screen
name="h/[serverId]/agent/[agentId]"
options={{ gestureEnabled: false }}
/>
<Stack.Screen name="h/[serverId]/index" />
<Stack.Screen name="h/[serverId]/agents" />
<Stack.Screen name="h/[serverId]/new-agent" />
<Stack.Screen name="h/[serverId]/open-project" />
<Stack.Screen name="h/[serverId]/settings" />
<Stack.Screen name="pair-scan" />
</Stack>
</AppWithSidebar>
</ToastProvider>
</HorizontalScrollProvider>
</SidebarAnimationProvider>
</ProvidersWrapper>
</HostRuntimeBootstrapProvider>
</QueryProvider>
</BottomSheetModalProvider>
</KeyboardProvider>
</SafeAreaProvider>
</PortalProvider>
</PerfDiagnosticsProvider>
<PortalProvider>
<SafeAreaProvider>
<KeyboardProvider>
<BottomSheetModalProvider>
<QueryProvider>
<HostRuntimeBootstrapProvider>
<PushNotificationRouter />
<HostSessionManager />
<ProvidersWrapper>
<SidebarAnimationProvider>
<HorizontalScrollProvider>
<ToastProvider>
<AppWithSidebar>
<Stack
screenOptions={{
headerShown: false,
animation: "none",
contentStyle: {
backgroundColor: darkTheme.colors.surface0,
},
}}
>
<Stack.Screen name="index" />
<Stack.Screen name="settings" />
<Stack.Screen name="h/[serverId]/workspace/[workspaceId]" />
<Stack.Screen
name="h/[serverId]/agent/[agentId]"
options={{ gestureEnabled: false }}
/>
<Stack.Screen name="h/[serverId]/index" />
<Stack.Screen name="h/[serverId]/agents" />
<Stack.Screen name="h/[serverId]/new-agent" />
<Stack.Screen name="h/[serverId]/open-project" />
<Stack.Screen name="h/[serverId]/settings" />
<Stack.Screen name="pair-scan" />
</Stack>
</AppWithSidebar>
</ToastProvider>
</HorizontalScrollProvider>
</SidebarAnimationProvider>
</ProvidersWrapper>
</HostRuntimeBootstrapProvider>
</QueryProvider>
</BottomSheetModalProvider>
</KeyboardProvider>
</SafeAreaProvider>
</PortalProvider>
</GestureHandlerRootView>
);
}
+1 -19
View File
@@ -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])
@@ -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
@@ -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<AgentStreamViewHandle, AgentStreamView
[isMobile]
);
const [isNearBottom, setIsNearBottom] = useState(true);
const streamItemCountRef = useRef(0);
const [expandedInlineToolCallIds, setExpandedInlineToolCallIds] = useState<Set<string>>(new Set());
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout);
@@ -462,74 +456,6 @@ export const AgentStreamView = forwardRef<AgentStreamViewHandle, AgentStreamView
[pendingPermissions, agentId]
);
useEffect(() => {
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<AgentStreamRenderModel>(() => {
const pendingPermissionsNode =
+1 -114
View File
@@ -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<HighlightToken["style"]>;
interface HighlightedTextProps {
@@ -199,75 +193,14 @@ const DiffFileHeader = memo(function DiffFileHeader({
onHeaderHeightChange,
testID,
}: DiffFileSectionProps) {
const expandStartRef = useRef<number | null>(null);
const layoutYRef = useRef<number | null>(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 (
<View
@@ -506,30 +439,6 @@ export function GitDiffPane({ serverId, workspaceId, cwd, hideHeaderRow }: GitDi
const headerHeightByPathRef = useRef<Record<string, number>>({});
const bodyHeightByPathRef = useRef<Record<string, number>>({});
const defaultHeaderHeightRef = useRef<number>(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<string | null>(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" })
);
-37
View File
@@ -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<number | null>(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;
@@ -76,6 +76,8 @@ class FakeDaemonClient {
return { rttMs: 0 };
}
setReconnectEnabled(_enabled: boolean): void {}
setConnectionState(next: ConnectionState): void {
this.state = next;
if (next.status === "disconnected") {
+5 -30
View File
@@ -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<void> {
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);
@@ -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<typeof getPerfDiagnosticsSnapshot>;
consumeReports: () => Promise<
Awaited<ReturnType<typeof consumePersistedPerfDiagnosticReports>>
>;
peekReports: () => Promise<
Awaited<ReturnType<typeof peekPersistedPerfDiagnosticReports>>
>;
};
}
).__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;
};
}
@@ -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<typeof setInterval> | 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<PerfDiagnosticsReport[]> {
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<void> {
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),
};
}
@@ -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 }
);
}
@@ -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";
@@ -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<PerfDiagnosticsReporter>(NOOP_REPORTER);
@@ -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 (
<PerfDiagnosticsContext.Provider value={reporter}>
{children}
</PerfDiagnosticsContext.Provider>
);
}
@@ -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;
}
@@ -1,44 +0,0 @@
export type PerfDiagnosticFields = Record<string, unknown>;
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<PerfDiagnosticsReport[]>;
peekReports: () => Promise<PerfDiagnosticsReport[]>;
isEnabled: () => boolean;
getSnapshot: (limit?: number) => PerfDiagnosticsSnapshot;
}
@@ -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);
}
@@ -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 (
<AgentSessionUnavailableState
@@ -294,15 +268,6 @@ function AgentScreenContent({
}
openFileExplorer();
}, [activateExplorerTabForCheckout, openFileExplorer, resolveCurrentExplorerCheckout]);
useEffect(() => {
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",
@@ -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 (
<View style={styles.container}>
<View style={styles.container} {...dragHandlers}>
<View style={[styles.menuToggle, { paddingTop: insets.top, paddingLeft: trafficLightInset }]}>
<SidebarMenuToggle />
</View>
@@ -56,6 +57,7 @@ const styles = StyleSheet.create((theme) => ({
container: {
flex: 1,
backgroundColor: theme.colors.surface0,
userSelect: "none",
},
menuToggle: {
position: "absolute",
@@ -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: {
-76
View File
@@ -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<typeof useAudioPlayer>): SessionState {
return {
@@ -452,7 +429,6 @@ export const useSessionStore = create<SessionStore>()(
if (prev.sessions[serverId]) {
return prev;
}
logSessionStoreUpdate("initializeSession", serverId);
return {
...prev,
sessions: {
@@ -470,7 +446,6 @@ export const useSessionStore = create<SessionStore>()(
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<SessionStore>()(
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<SessionStore>()(
return prev;
}
logSessionStoreUpdate("updateSessionServerInfo", serverId, {
serverId: info.serverId,
hostname: nextHostname,
version: nextVersion,
});
return {
...prev,
sessions: {
@@ -584,7 +547,6 @@ export const useSessionStore = create<SessionStore>()(
if (!session || session.isPlayingAudio === playing) {
return prev;
}
logSessionStoreUpdate("setIsPlayingAudio", serverId, { playing });
return {
...prev,
sessions: {
@@ -602,8 +564,6 @@ export const useSessionStore = create<SessionStore>()(
if (!session || session.focusedAgentId === agentId) {
return prev;
}
logSessionStoreUpdate("setFocusedAgentId", serverId, { agentId });
return {
...prev,
sessions: {
@@ -629,7 +589,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.messages === nextMessages) {
return prev;
}
logSessionStoreUpdate("setMessages", serverId, { count: nextMessages.length });
return {
...prev,
sessions: {
@@ -650,7 +609,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.currentAssistantMessage === nextMessage) {
return prev;
}
logSessionStoreUpdate("setCurrentAssistantMessage", serverId, { length: nextMessage.length });
return {
...prev,
sessions: {
@@ -672,7 +630,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.agentStreamTail === nextState) {
return prev;
}
logSessionStoreUpdate("setAgentStreamTail", serverId, { agentCount: nextState.size });
return {
...prev,
sessions: {
@@ -693,7 +650,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.agentStreamHead === nextState) {
return prev;
}
logSessionStoreUpdate("setAgentStreamHead", serverId, { agentCount: nextState.size });
return {
...prev,
sessions: {
@@ -745,12 +701,6 @@ export const useSessionStore = create<SessionStore>()(
return prev;
}
logSessionStoreUpdate("setAgentStreamState", serverId, {
agentId,
changedTail,
changedHead,
});
return {
...prev,
sessions: {
@@ -776,7 +726,6 @@ export const useSessionStore = create<SessionStore>()(
}
const nextHead = new Map(session.agentStreamHead);
nextHead.delete(agentId);
logSessionStoreUpdate("clearAgentStreamHead", serverId, { agentId });
return {
...prev,
sessions: {
@@ -798,9 +747,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.agentTimelineCursor === nextState) {
return prev;
}
logSessionStoreUpdate("setAgentTimelineCursor", serverId, {
agentCount: nextState.size,
});
return {
...prev,
sessions: {
@@ -818,9 +764,6 @@ export const useSessionStore = create<SessionStore>()(
return prev;
}
const nextGeneration = session.historySyncGeneration + 1;
logSessionStoreUpdate("bumpHistorySyncGeneration", serverId, {
generation: nextGeneration,
});
return {
...prev,
sessions: {
@@ -847,10 +790,6 @@ export const useSessionStore = create<SessionStore>()(
}
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<SessionStore>()(
nextApplied.delete(agentId);
}
logSessionStoreUpdate("setAgentAuthoritativeHistoryApplied", serverId, {
agentId,
applied,
});
return {
...prev,
sessions: {
@@ -913,7 +847,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.initializingAgents === nextState) {
return prev;
}
logSessionStoreUpdate("setInitializingAgents", serverId, { count: nextState.size });
return {
...prev,
sessions: {
@@ -935,7 +868,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.agents === nextAgents) {
return prev;
}
logSessionStoreUpdate("setAgents", serverId, { count: nextAgents.size });
return {
...prev,
sessions: {
@@ -957,7 +889,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.workspaces === nextWorkspaces) {
return prev;
}
logSessionStoreUpdate("setWorkspaces", serverId, { count: nextWorkspaces.size });
return {
...prev,
sessions: {
@@ -988,7 +919,6 @@ export const useSessionStore = create<SessionStore>()(
if (!changed) {
return prev;
}
logSessionStoreUpdate("mergeWorkspaces", serverId, { count: nextEntries.length });
return {
...prev,
sessions: {
@@ -1007,7 +937,6 @@ export const useSessionStore = create<SessionStore>()(
}
const next = new Map(session.workspaces);
next.delete(workspaceId);
logSessionStoreUpdate("removeWorkspace", serverId, { workspaceId });
return {
...prev,
sessions: {
@@ -1074,7 +1003,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.pendingPermissions === nextPerms) {
return prev;
}
logSessionStoreUpdate("setPendingPermissions", serverId, { count: nextPerms.size });
return {
...prev,
sessions: {
@@ -1096,7 +1024,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.fileExplorer === nextState) {
return prev;
}
logSessionStoreUpdate("setFileExplorer", serverId, { agentCount: nextState.size });
return {
...prev,
sessions: {
@@ -1118,7 +1045,6 @@ export const useSessionStore = create<SessionStore>()(
if (session.queuedMessages === nextValue) {
return prev;
}
logSessionStoreUpdate("setQueuedMessages", serverId, { agentCount: nextValue.size });
return {
...prev,
sessions: {
@@ -1136,7 +1062,6 @@ export const useSessionStore = create<SessionStore>()(
if (!session || session.hasHydratedAgents === hydrated) {
return prev;
}
logSessionStoreUpdate("setHasHydratedAgents", serverId, { hydrated });
return {
...prev,
sessions: {
@@ -1153,7 +1078,6 @@ export const useSessionStore = create<SessionStore>()(
if (!session || session.hasHydratedWorkspaces === hydrated) {
return prev;
}
logSessionStoreUpdate("setHasHydratedWorkspaces", serverId, { hydrated });
return {
...prev,
sessions: {
@@ -1,80 +0,0 @@
import { getNowMs, isPerfLoggingEnabled, perfLog } from "@/utils/perf";
type NavigationTimingDetails = {
from: string;
to: string;
params?: Record<string, unknown>;
targetMs?: number;
};
type NavigationTimingEntry = NavigationTimingDetails & {
startedAt: number;
};
const NAVIGATION_TAG = "[NavigationTiming]";
const pendingNavigations = new Map<string, NavigationTimingEntry>();
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<string, unknown>): 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,
});
};
@@ -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";
-113
View File
@@ -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 });
};
}
-105
View File
@@ -1,105 +0,0 @@
const PERF_LOGGING_ENABLED = false;
export const isPerfLoggingEnabled = (): boolean => PERF_LOGGING_ENABLED;
export const perfLog = (tag: string, details: Record<string, unknown>): 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<string, unknown>).length;
const approxBytes = estimateSize(payload, MEASUREMENT_DEPTH, new WeakSet());
return {
approxBytes,
fieldCount,
};
};
const estimateSize = (value: unknown, depth: number, seen: WeakSet<object>): 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<string, unknown>)) {
total += key.length;
total += estimateSize(child, depth - 1, seen);
}
return total;
};
@@ -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 }
@@ -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;
}
+4 -73
View File
@@ -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