| 609 | * components re-render when workspace state changes. |
| 610 | */ |
| 611 | export class WorkspaceStore { |
| 612 | // Per-workspace state (lazy computed on get) |
| 613 | private states = new MapStore<string, WorkspaceState>(); |
| 614 | |
| 615 | // Stable subset for WorkspaceShell so message deltas do not re-render shell chrome. |
| 616 | private workspaceShellStatusCache = new Map<string, WorkspaceShellStatus>(); |
| 617 | |
| 618 | // Derived aggregate state (computed from multiple workspaces) |
| 619 | private derived = new MapStore<string, DerivedState>(); |
| 620 | |
| 621 | // Usage and consumer stores (two-store approach for CostsTab optimization) |
| 622 | private usageStore = new MapStore<string, WorkspaceUsageState>(); |
| 623 | private client: RouterClient<AppRouter> | null = null; |
| 624 | private clientChangeController = new AbortController(); |
| 625 | private providersConfig: ProvidersConfigMap | null = null; |
| 626 | /** Stable fingerprint for cache freshness checks across reconnects/app restarts. |
| 627 | * `null` until the first successful config fetch — prevents hydrating stale caches |
| 628 | * and blocks tokenization until we know the real configuration. */ |
| 629 | private providersConfigFingerprint: number | null = null; |
| 630 | /** Monotonic request counter for serializing provider config refreshes (latest wins). */ |
| 631 | private providersConfigVersion = 0; |
| 632 | /** Version of the last successfully applied provider config (prevents stale overwrites). */ |
| 633 | private providersConfigAppliedVersion = 0; |
| 634 | /** Consecutive provider-config subscription/refresh failures (used for exponential backoff). */ |
| 635 | private providersConfigFailureStreak = 0; |
| 636 | // Workspaces that need a clean history replay once a new iterator is established. |
| 637 | // We keep the existing UI visible until the replay can actually start. |
| 638 | private pendingReplayReset = new Set<string>(); |
| 639 | // Last usage snapshot captured right before full replay clears the aggregator. |
| 640 | // Used as a temporary fallback so context/cost indicators don't flash empty |
| 641 | // during reconnect until replayed usage catches up. |
| 642 | private preReplayUsageSnapshot = new Map<string, WorkspaceUsageState>(); |
| 643 | private consumersStore = new MapStore<string, WorkspaceConsumersState>(); |
| 644 | |
| 645 | // Manager for consumer calculations (debouncing, caching, lazy loading) |
| 646 | // Architecture: WorkspaceStore orchestrates (decides when), manager executes (performs calculations) |
| 647 | // Dual-cache: consumersStore (MapStore) handles subscriptions, manager owns data cache |
| 648 | private readonly consumerManager: WorkspaceConsumerManager; |
| 649 | |
| 650 | // Supporting data structures |
| 651 | private aggregators = new Map<string, StreamingMessageAggregator>(); |
| 652 | // Active onChat subscription cleanup handlers (must stay size <= 1). |
| 653 | private ipcUnsubscribers = new Map<string, () => void>(); |
| 654 | |
| 655 | // Workspace selected in the UI (set from WorkspaceContext routing state). |
| 656 | private activeWorkspaceId: string | null = null; |
| 657 | |
| 658 | // Workspace currently owning the live onChat subscription. |
| 659 | private activeOnChatWorkspaceId: string | null = null; |
| 660 | |
| 661 | // Lightweight activity snapshots from workspace.activity.list/subscribe. |
| 662 | private workspaceActivity = new Map<string, WorkspaceActivitySnapshot>(); |
| 663 | // Recency timestamp observed when a workspace transitions into streaming=true. |
| 664 | // Used to distinguish true stream completion (recency bumps on stream-end) from |
| 665 | // abort/error transitions (streaming=false without recency advance). |
| 666 | private activityStreamingStartRecency = new Map<string, number>(); |
| 667 | private activityAbortController: AbortController | null = null; |
| 668 | // True once the initial activity.list() snapshot has been applied (or the |
nothing calls this directly
no test coverage detected