| 66 | * (components subscribe to workspace changes, delegates to manager for state) |
| 67 | */ |
| 68 | export class WorkspaceConsumerManager { |
| 69 | // Track scheduled calculations (in debounce window, not yet executing) |
| 70 | private scheduledCalcs = new Set<string>(); |
| 71 | |
| 72 | // Track executing calculations (Web Worker running) |
| 73 | private pendingCalcs = new Set<string>(); |
| 74 | |
| 75 | // Track workspaces that need recalculation after current one completes |
| 76 | private needsRecalc = new Map<string, StreamingMessageAggregator>(); |
| 77 | |
| 78 | // Cache calculated consumer data (persists across bumps) |
| 79 | private cache = new Map<string, WorkspaceConsumersState>(); |
| 80 | |
| 81 | // Debounce timers for consumer calculations (prevents rapid-fire during tool sequences) |
| 82 | private debounceTimers = new Map<string, NodeJS.Timeout>(); |
| 83 | |
| 84 | // Callback to bump the store when calculation completes |
| 85 | private readonly onCalculationComplete: (workspaceId: string) => void; |
| 86 | // Stable provider-config fingerprint to stamp persisted token stats cache entries. |
| 87 | // Returns null before the first config fetch completes (blocks calculation). |
| 88 | private readonly getProvidersConfigVersion: () => number | null; |
| 89 | |
| 90 | // Track pending store notifications to avoid duplicate bumps within the same tick |
| 91 | private pendingNotifications = new Set<string>(); |
| 92 | |
| 93 | constructor( |
| 94 | onCalculationComplete: (workspaceId: string) => void, |
| 95 | getProvidersConfigVersion: () => number | null |
| 96 | ) { |
| 97 | this.onCalculationComplete = onCalculationComplete; |
| 98 | this.getProvidersConfigVersion = getProvidersConfigVersion; |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Get cached state without side effects. |
| 103 | * Returns null if no cache exists. |
| 104 | */ |
| 105 | getCachedState(workspaceId: string): WorkspaceConsumersState | null { |
| 106 | return this.cache.get(workspaceId) ?? null; |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Check if calculation is pending or scheduled for workspace. |
| 111 | */ |
| 112 | isPending(workspaceId: string): boolean { |
| 113 | return this.scheduledCalcs.has(workspaceId) || this.pendingCalcs.has(workspaceId); |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Get current state synchronously without triggering calculations. |
| 118 | * Returns cached result if available, otherwise returns default state. |
| 119 | * |
| 120 | * Note: This is called from WorkspaceStore.getWorkspaceConsumers(), |
| 121 | * which handles the lazy trigger logic separately. |
| 122 | */ |
| 123 | getStateSync(workspaceId: string): WorkspaceConsumersState { |
| 124 | const cached = this.cache.get(workspaceId); |
| 125 | if (cached) { |
nothing calls this directly
no outgoing calls
no test coverage detected