// Tiny in-app event bus for session metadata changes that need to propagate // across hooks (e.g. AI rename arriving via WS in the session view needs to // also refresh the sidebar's session list). import type { Chat, ErrorReason, HtmlArtifactState, MarkdownArtifactState, Project, Session, } from '@/api/types'; import type { Attachment } from '@/lib/attachments'; export interface SessionRenamedEvent { type: 'session_renamed'; session_id: string; name: string; } export interface ProjectCreatedEvent { type: 'project_created'; project: Project; } export interface ProjectDeletedEvent { type: 'project_deleted'; project_id: string; } export interface SessionCreatedEvent { type: 'session_created'; session: Session; project_id: string; } export interface SessionDeletedEvent { type: 'session_deleted'; session_id: string; project_id: string; } export interface SessionUpdatedEvent { type: 'session_updated'; session_id: string; project_id: string; name: string; updated_at: string; } export interface SessionWorkspaceUpdatedEvent { type: 'session_workspace_updated'; session_id: string; workspace_panes: import('@/api/types').WorkspacePane[]; } export interface SessionLoadedEvent { type: 'session_loaded'; session_id: string; project_id: string; } export interface OpenFileInBrowserEvent { type: 'open_file_in_browser'; path: string; // project-relative } export interface AttachChatFileEvent { type: 'attach_chat_file'; attachment: Omit; } export interface OpenChatInActivePaneEvent { type: 'open_chat_in_active_pane'; chat_id: string; } // v1.14.x-html-artifact-panes: ActionRow's "Open in pane" button emits one of // these; useWorkspacePanes subscribes and inserts the corresponding artifact // pane (or focuses an existing one keyed by message_id). export interface OpenMarkdownArtifactPaneEvent { type: 'open_markdown_artifact_pane'; state: MarkdownArtifactState; } export interface OpenHtmlArtifactPaneEvent { type: 'open_html_artifact_pane'; state: HtmlArtifactState; } // Client-side event fired by the sidebar Settings button when a session is // currently mounted. Session.tsx subscribes and calls // panesHook.toggleSettingsPane() (open on first click, close on second). // Sidebar handles the no-session case by navigating to /settings directly. export interface OpenSettingsPaneEvent { type: 'open_settings_pane'; } export interface SessionArchivedEvent { type: 'session_archived'; session_id: string; project_id: string; } export interface ChatCreatedEvent { type: 'chat_created'; chat: Chat; session_id: string; } export interface ChatUpdatedEvent { type: 'chat_updated'; chat_id: string; session_id: string; name: string | null; updated_at: string; } export interface ChatArchivedEvent { type: 'chat_archived'; chat_id: string; session_id: string; } export interface ChatUnarchivedEvent { type: 'chat_unarchived'; chat: Chat; } export interface ChatDeletedEvent { type: 'chat_deleted'; chat_id: string; session_id: string; } export interface ProjectArchivedEvent { type: 'project_archived'; project_id: string; } export interface ProjectUnarchivedEvent { type: 'project_unarchived'; project: Project; } export interface ProjectUpdatedEvent { type: 'project_updated'; project_id: string; name: string; } // v1.8 mobile-tabs: broadcast on user channel from inference.ts so any device // subscribed sees a chat working/idle/error. Frontend stores per-chat; panes // derive their dot from pane.activeChatId. // v1.8.2: optional `reason` carries a machine-readable code when status is // 'error'. UI prefers reason for inline error rendering. export interface ChatStatusEvent { type: 'chat_status'; chat_id: string; status: 'streaming' | 'tool_running' | 'waiting_for_input' | 'idle' | 'error'; at: string; reason?: ErrorReason; } export interface RefetchMessagesEvent { type: 'refetch_messages'; } export type SessionEvent = | SessionRenamedEvent | ProjectCreatedEvent | ProjectDeletedEvent | SessionCreatedEvent | SessionDeletedEvent | SessionUpdatedEvent | SessionWorkspaceUpdatedEvent | SessionLoadedEvent | OpenFileInBrowserEvent | AttachChatFileEvent | OpenChatInActivePaneEvent | OpenMarkdownArtifactPaneEvent | OpenHtmlArtifactPaneEvent | OpenSettingsPaneEvent | SessionArchivedEvent | ChatCreatedEvent | ChatUpdatedEvent | ChatArchivedEvent | ChatUnarchivedEvent | ChatDeletedEvent | ProjectArchivedEvent | ProjectUnarchivedEvent | ProjectUpdatedEvent | ChatStatusEvent | RefetchMessagesEvent; type Listener = (event: SessionEvent) => void; const listeners = new Set(); export const sessionEvents = { emit(event: SessionEvent) { for (const listener of listeners) { try { listener(event); } catch { // swallow — one bad listener shouldn't break others } } }, subscribe(listener: Listener): () => void { listeners.add(listener); return () => { listeners.delete(listener); }; }, };