diff --git a/config/scripts/mobile-agent-status-projection-benchmark.mjs b/config/scripts/mobile-agent-status-projection-benchmark.mjs index 8d5d58a2dee..d9cf145df1c 100644 --- a/config/scripts/mobile-agent-status-projection-benchmark.mjs +++ b/config/scripts/mobile-agent-status-projection-benchmark.mjs @@ -19,14 +19,19 @@ import { performance } from 'node:perf_hooks' import { fileURLToPath } from 'node:url' const GRAPH_SOURCE = readFileSync( - fileURLToPath(new URL('../../src/renderer/src/runtime/sync-runtime-graph.ts', import.meta.url)), + fileURLToPath( + new URL( + '../../src/renderer/src/runtime/sync-runtime-graph/agent-status-projection.ts', + import.meta.url + ) + ), 'utf8' ) const bucketMatch = GRAPH_SOURCE.match(/AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS = ([0-9_]+)/) if (!bucketMatch) { throw new Error( - 'sync-runtime-graph.ts no longer defines the updatedAt bucket; re-sync this benchmark.' + 'agent-status-projection.ts no longer defines the updatedAt bucket; re-sync this benchmark.' ) } const BUCKET_MS = Number(bucketMatch[1].replaceAll('_', '')) diff --git a/src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts b/src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts index 587e30a769e..a2ab6485ae7 100644 --- a/src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts +++ b/src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts @@ -20,6 +20,7 @@ vi.mock('./sync-runtime-graph/editor-draft-hash', async (importOriginal) => { const { stableHashString } = await import('./sync-runtime-graph/editor-draft-hash') const { + AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS_FOR_TESTS: AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS, buildRuntimeMobileAgentStatusProjectionForTests, getRuntimeMobileSessionSyncKey, resetRuntimeMobileAgentStatusProjectionCacheForTests, @@ -31,7 +32,6 @@ const { buildRuntimeMobileEditorDraftsProjection, buildRuntimeMobileOpenFilesProjection } = await import('./sync-runtime-graph/sync-projections') -const { AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS } = await import('./sync-runtime-graph/graph-state') // ── Reference implementations: the pre-change bodies, kept verbatim ──────────────────── diff --git a/src/renderer/src/runtime/sync-runtime-graph/agent-status-projection.ts b/src/renderer/src/runtime/sync-runtime-graph/agent-status-projection.ts index 25b65573839..1603dec5668 100644 --- a/src/renderer/src/runtime/sync-runtime-graph/agent-status-projection.ts +++ b/src/renderer/src/runtime/sync-runtime-graph/agent-status-projection.ts @@ -1,7 +1,19 @@ import type { AppState } from '@/store/types' import type { AgentMainAgentStatus } from '../../../../shared/main-agent-status' -import { AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS, graphState } from './graph-state' -import type { AgentStatusProjectionCacheEntry } from './types' + +const AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS = 30_000 + +type AgentStatusProjectionCacheEntry = { + entry: AppState['agentStatusByPaneKey'][string] + projection: string +} +type AgentStatusProjectionCache = { + source: AppState['agentStatusByPaneKey'] + entries: Map + projection: string +} + +let cachedAgentStatusProjection: AgentStatusProjectionCache | null = null function mainAgentKey(mainAgent: AgentMainAgentStatus | undefined) { return mainAgent ? [mainAgent.state, mainAgent.outcome ?? null, mainAgent.stateStartedAt] : null @@ -44,7 +56,7 @@ function serializeAgentStatusEntry( export function buildRuntimeMobileAgentStatusProjection( agentStatusByPaneKey: AppState['agentStatusByPaneKey'] ): string { - const cached = graphState.cachedAgentStatusProjection + const cached = cachedAgentStatusProjection if (cached?.source === agentStatusByPaneKey) { return cached.projection } @@ -59,7 +71,7 @@ export function buildRuntimeMobileAgentStatusProjection( nextEntries.length === cached.entries.size && nextEntries.every(([paneKey, entry]) => cached.entries.get(paneKey)?.entry === entry) ) { - graphState.cachedAgentStatusProjection = { + cachedAgentStatusProjection = { ...cached, source: agentStatusByPaneKey } @@ -85,7 +97,7 @@ export function buildRuntimeMobileAgentStatusProjection( } // Same-bucket heartbeats must not rejoin every pane's accumulated preview text. const projection = projectionUnchanged && cached ? cached.projection : `[${parts.join(',')}]` - graphState.cachedAgentStatusProjection = { + cachedAgentStatusProjection = { source: agentStatusByPaneKey, entries, projection @@ -103,5 +115,5 @@ export const AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS_FOR_TESTS = AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS export function resetRuntimeMobileAgentStatusProjectionCacheForTests(): void { - graphState.cachedAgentStatusProjection = null + cachedAgentStatusProjection = null } diff --git a/src/renderer/src/runtime/sync-runtime-graph/graph-state.ts b/src/renderer/src/runtime/sync-runtime-graph/graph-state.ts index cfb2c7522fd..bd97475cfdd 100644 --- a/src/renderer/src/runtime/sync-runtime-graph/graph-state.ts +++ b/src/renderer/src/runtime/sync-runtime-graph/graph-state.ts @@ -2,11 +2,9 @@ import { createBrowserUuid } from '@/lib/browser-uuid' import type { AppState } from '@/store/types' import type { RuntimeMobileSessionSnapshotTab, - RuntimeMobileSessionTabsSnapshot, - RuntimeMobileTerminalTheme + RuntimeMobileSessionTabsSnapshot } from '../../../../shared/runtime-types' import type { - AgentStatusProjectionCache, BrowserPagesProjectionCache, BrowserWorkspacesProjectionCache, EditorDraftHashCache, @@ -17,7 +15,6 @@ import type { } from './types' export const NO_TRANSPORT_GRACE_MS = 10_000 -export const AGENT_STATUS_SYNC_UPDATED_AT_BUCKET_MS = 30_000 export const RUNTIME_GRAPH_SYNC_COALESCE_MS = 16 export const EMPTY_ACTIVE_BROWSER_TAB_ID_BY_WORKTREE: AppState['activeBrowserTabIdByWorktree'] = {} @@ -41,7 +38,6 @@ export const graphState = { syncEnabled: false, syncTimer: null as ReturnType | null, getStoreState: null as (() => AppState) | null, - mobileSessionSnapshotVersion: 0, mobileSessionSnapshotCacheByWorktree: new Map< string, { @@ -52,17 +48,13 @@ export const graphState = { >(), publishedMobileSessionSnapshotByWorktree: new Map(), cachedTabsProjection: null as TabsProjectionCache | null, - cachedAgentStatusProjection: null as AgentStatusProjectionCache | null, cachedOpenFilesProjection: null as OpenFilesProjectionCache | null, cachedBrowserWorkspacesProjection: null as BrowserWorkspacesProjectionCache | null, cachedBrowserPagesProjection: null as BrowserPagesProjectionCache | null, cachedOpenFileIndexesSource: null as AppState['openFiles'] | null, cachedOpenFileIndexes: null as OpenFileIndexes | null, cachedEditorDraftHashes: null as EditorDraftHashCache | null, - cachedMobileTerminalThemeSettings: null as AppState['settings'] | null, - cachedMobileTerminalThemeSystemPrefersDark: null as boolean | null, - cachedMobileTerminalTheme: undefined as RuntimeMobileTerminalTheme | undefined, - hasCachedMobileTerminalTheme: false + mobileSessionSnapshotVersion: 0 } /** IDs occurring more than once cannot address the legacy tab-keyed runtime maps safely. */ diff --git a/src/renderer/src/runtime/sync-runtime-graph/mobile-terminal-theme.ts b/src/renderer/src/runtime/sync-runtime-graph/mobile-terminal-theme.ts index c4bae609a39..3d1bc85c3e9 100644 --- a/src/renderer/src/runtime/sync-runtime-graph/mobile-terminal-theme.ts +++ b/src/renderer/src/runtime/sync-runtime-graph/mobile-terminal-theme.ts @@ -1,7 +1,6 @@ import { getSystemPrefersDark, resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme' import type { AppState } from '@/store/types' import type { RuntimeMobileTerminalTheme } from '../../../../shared/runtime-types' -import { graphState } from './graph-state' import { normalizeTerminalMinimumContrastRatio } from '@/lib/terminal-contrast-correction' function hexToRgba(hex: string, alpha: number): string { @@ -64,20 +63,25 @@ export function resolveMobileTerminalTheme( } } +let cachedMobileTerminalThemeSettings: AppState['settings'] | null = null +let cachedMobileTerminalThemeSystemPrefersDark: boolean | null = null +let cachedMobileTerminalTheme: RuntimeMobileTerminalTheme | undefined +let hasCachedMobileTerminalTheme = false + export function getMobileTerminalTheme( state: AppState, systemPrefersDark = getSystemPrefersDark() ): RuntimeMobileTerminalTheme | undefined { if ( - graphState.hasCachedMobileTerminalTheme && - graphState.cachedMobileTerminalThemeSettings === state.settings && - graphState.cachedMobileTerminalThemeSystemPrefersDark === systemPrefersDark + hasCachedMobileTerminalTheme && + cachedMobileTerminalThemeSettings === state.settings && + cachedMobileTerminalThemeSystemPrefersDark === systemPrefersDark ) { - return graphState.cachedMobileTerminalTheme + return cachedMobileTerminalTheme } - graphState.cachedMobileTerminalTheme = resolveMobileTerminalTheme(state, systemPrefersDark) - graphState.cachedMobileTerminalThemeSettings = state.settings - graphState.cachedMobileTerminalThemeSystemPrefersDark = systemPrefersDark - graphState.hasCachedMobileTerminalTheme = true - return graphState.cachedMobileTerminalTheme + cachedMobileTerminalTheme = resolveMobileTerminalTheme(state, systemPrefersDark) + cachedMobileTerminalThemeSettings = state.settings + cachedMobileTerminalThemeSystemPrefersDark = systemPrefersDark + hasCachedMobileTerminalTheme = true + return cachedMobileTerminalTheme } diff --git a/src/renderer/src/runtime/sync-runtime-graph/types.ts b/src/renderer/src/runtime/sync-runtime-graph/types.ts index 648aa73eb5b..6ab1eca3754 100644 --- a/src/renderer/src/runtime/sync-runtime-graph/types.ts +++ b/src/renderer/src/runtime/sync-runtime-graph/types.ts @@ -92,15 +92,6 @@ export type EditorDraftHashCache = { hashByFileId: Map projection: string } -export type AgentStatusProjectionCacheEntry = { - entry: AppState['agentStatusByPaneKey'][string] - projection: string -} -export type AgentStatusProjectionCache = { - source: AppState['agentStatusByPaneKey'] - entries: Map - projection: string -} export type MobileSessionAgentStatusByWorktree = ReadonlyMap< string, ReadonlyMap