/** * MessageStore — manages token tracking and subtask mutations. * * Extracted from chatStore.ts as part of the structured refactor (Phase 3). * * Design note: The `messages[]` array stays in chatStore because * `sendMessage` and `initStreamListener` use Zustand's `set((s) => ...)` * pattern for high-frequency streaming deltas (dozens of updates per second). * Moving messages out would force every streaming callback through * `getState().updateMessage()` — adding overhead and breaking the * producer-writes, consumer-reads separation that Zustand excels at. * * This store owns: * - Token usage counters (totalInputTokens, totalOutputTokens) * - Subtask mutation helpers (addSubtask, updateSubtask) * * Messages are read from chatStore by consumers that need them. * * @see docs/superpowers/specs/2026-04-02-chatstore-refactor-design.md §3.3 */ import { create } from 'zustand'; import type { Subtask } from '../../components/ai'; // --------------------------------------------------------------------------- // State interface // --------------------------------------------------------------------------- export interface MessageState { totalInputTokens: number; totalOutputTokens: number; // Token tracking addTokenUsage: (inputTokens: number, outputTokens: number) => void; getTotalTokens: () => { input: number; output: number; total: number }; resetTokenUsage: () => void; // Subtask mutations (delegated to chatStore internally) addSubtask: (messageId: string, task: Subtask) => void; updateSubtask: (messageId: string, taskId: string, updates: Partial) => void; } // --------------------------------------------------------------------------- // Internal reference to chatStore for message mutations // --------------------------------------------------------------------------- let _chatStore: { getState: () => { addSubtask: (messageId: string, task: Subtask) => void; updateSubtask: (messageId: string, taskId: string, updates: Partial) => void; }; } | null = null; /** * Inject chatStore reference for subtask delegation. * Called by chatStore during initialization to avoid circular imports. */ export function setMessageStoreChatStore(store: typeof _chatStore): void { _chatStore = store; } // --------------------------------------------------------------------------- // Store // --------------------------------------------------------------------------- export const useMessageStore = create()((set, get) => ({ totalInputTokens: 0, totalOutputTokens: 0, addTokenUsage: (inputTokens: number, outputTokens: number) => set((state) => ({ totalInputTokens: state.totalInputTokens + inputTokens, totalOutputTokens: state.totalOutputTokens + outputTokens, })), getTotalTokens: () => { const { totalInputTokens, totalOutputTokens } = get(); return { input: totalInputTokens, output: totalOutputTokens, total: totalInputTokens + totalOutputTokens, }; }, resetTokenUsage: () => set({ totalInputTokens: 0, totalOutputTokens: 0 }), addSubtask: (messageId: string, task: Subtask) => { if (_chatStore) { _chatStore.getState().addSubtask(messageId, task); } }, updateSubtask: (messageId: string, taskId: string, updates: Partial) => { if (_chatStore) { _chatStore.getState().updateSubtask(messageId, taskId, updates); } }, }));