From 08fd1c2732e288604241adc446944e5ecda2cd06 Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Wed, 17 Dec 2025 23:48:14 -0600 Subject: [PATCH] # CHANGES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added file system write capability for saving content directly 🚀 - Introduced unified API to fetch all named sessions across providers 🎯 - Enhanced session management with cross-provider named session support 📋 - Exposed writeFile method in preload for renderer process access ✍️ - Added getAllNamedSessions handler for aggregating session data 🔄 - Updated TabSwitcherModal to use new unified session API 🔧 - Extended type definitions for better TypeScript support 📝 - Improved session discovery across multiple storage providers 🔍 - Added error handling for multi-provider session aggregation 🛡️ - Streamlined named session retrieval for better performance ⚡ --- src/main/index.ts | 9 ++++ src/main/ipc/handlers/agentSessions.ts | 45 ++++++++++++++++++++ src/main/preload.ts | 19 +++++++++ src/renderer/components/TabSwitcherModal.tsx | 2 +- src/renderer/global.d.ts | 10 +++++ 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/main/index.ts b/src/main/index.ts index 4c78b702..d98abd3d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -885,6 +885,15 @@ function setupIpcHandlers() { } }); + ipcMain.handle('fs:writeFile', async (_, filePath: string, content: string) => { + try { + await fs.writeFile(filePath, content, 'utf-8'); + return { success: true }; + } catch (error) { + throw new Error(`Failed to write file: ${error}`); + } + }); + // Live session management - toggle sessions as live/offline in web interface ipcMain.handle('live:toggle', async (_, sessionId: string, agentSessionId?: string) => { if (!webServer) { diff --git a/src/main/ipc/handlers/agentSessions.ts b/src/main/ipc/handlers/agentSessions.ts index 6b3b93b4..a9bb3109 100644 --- a/src/main/ipc/handlers/agentSessions.ts +++ b/src/main/ipc/handlers/agentSessions.ts @@ -478,6 +478,51 @@ export function registerAgentSessionsHandlers(deps?: AgentSessionsHandlerDepende }) ); + // ============ Get All Named Sessions ============ + + ipcMain.handle( + 'agentSessions:getAllNamedSessions', + withIpcErrorLogging( + handlerOpts('getAllNamedSessions'), + async (): Promise< + Array<{ + agentSessionId: string; + projectPath: string; + sessionName: string; + starred?: boolean; + lastActivityAt?: number; + }> + > => { + // Aggregate named sessions from all providers that support it + const allNamedSessions: Array<{ + agentSessionId: string; + projectPath: string; + sessionName: string; + starred?: boolean; + lastActivityAt?: number; + }> = []; + + const storages = getAllSessionStorages(); + for (const storage of storages) { + if ('getAllNamedSessions' in storage && typeof storage.getAllNamedSessions === 'function') { + try { + const sessions = await storage.getAllNamedSessions(); + allNamedSessions.push(...sessions); + } catch (error) { + logger.warn( + `Failed to get named sessions from ${storage.agentId}: ${error}`, + LOG_CONTEXT + ); + } + } + } + + logger.info(`Found ${allNamedSessions.length} named sessions across all providers`, LOG_CONTEXT); + return allNamedSessions; + } + ) + ); + // ============ Get Global Stats (All Providers) ============ ipcMain.handle( diff --git a/src/main/preload.ts b/src/main/preload.ts index 98d4c16f..a7afcffe 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -353,6 +353,8 @@ contextBridge.exposeInMainWorld('maestro', { homeDir: () => ipcRenderer.invoke('fs:homeDir') as Promise, readDir: (dirPath: string) => ipcRenderer.invoke('fs:readDir', dirPath), readFile: (filePath: string) => ipcRenderer.invoke('fs:readFile', filePath), + writeFile: (filePath: string, content: string) => + ipcRenderer.invoke('fs:writeFile', filePath, content) as Promise<{ success: boolean }>, stat: (filePath: string) => ipcRenderer.invoke('fs:stat', filePath), }, @@ -637,6 +639,15 @@ contextBridge.exposeInMainWorld('maestro', { // Get global stats aggregated from all providers getGlobalStats: () => ipcRenderer.invoke('agentSessions:getGlobalStats'), + // Get all named sessions across all providers + getAllNamedSessions: () => + ipcRenderer.invoke('agentSessions:getAllNamedSessions') as Promise>, // Subscribe to global stats updates (streaming) onGlobalStatsUpdate: (callback: (stats: { totalSessions: number; @@ -1027,6 +1038,7 @@ export interface MaestroAPI { homeDir: () => Promise; readDir: (dirPath: string) => Promise; readFile: (filePath: string) => Promise; + writeFile: (filePath: string, content: string) => Promise<{ success: boolean }>; stat: (filePath: string) => Promise<{ size: number; createdAt: string; @@ -1297,6 +1309,13 @@ export interface MaestroAPI { deleteMessagePair: (agentId: string, projectPath: string, sessionId: string, userMessageUuid: string, fallbackContent?: string) => Promise<{ success: boolean; linesRemoved?: number; error?: string }>; hasStorage: (agentId: string) => Promise; getAvailableStorages: () => Promise; + getAllNamedSessions: () => Promise>; registerSessionOrigin: (projectPath: string, agentSessionId: string, origin: 'user' | 'auto', sessionName?: string) => Promise; updateSessionName: (projectPath: string, agentSessionId: string, sessionName: string) => Promise; }; diff --git a/src/renderer/components/TabSwitcherModal.tsx b/src/renderer/components/TabSwitcherModal.tsx index ba94a261..bfdde753 100644 --- a/src/renderer/components/TabSwitcherModal.tsx +++ b/src/renderer/components/TabSwitcherModal.tsx @@ -208,7 +208,7 @@ export function TabSwitcherModal({ ) ); // Then load all named sessions (including the ones we just synced) - const sessions = await window.maestro.claude.getAllNamedSessions(); + const sessions = await window.maestro.agentSessions.getAllNamedSessions(); setNamedSessions(sessions); setNamedSessionsLoaded(true); }; diff --git a/src/renderer/global.d.ts b/src/renderer/global.d.ts index 6012cd5e..eb1aa258 100644 --- a/src/renderer/global.d.ts +++ b/src/renderer/global.d.ts @@ -201,6 +201,7 @@ interface MaestroAPI { fs: { readDir: (dirPath: string) => Promise; readFile: (filePath: string) => Promise; + writeFile: (filePath: string, content: string) => Promise<{ success: boolean }>; }; webserver: { getUrl: () => Promise; @@ -328,6 +329,15 @@ interface MaestroAPI { hasCostData: boolean; }>; }) => void) => () => void; + getAllNamedSessions: () => Promise>; + registerSessionOrigin: (projectPath: string, agentSessionId: string, origin: 'user' | 'auto', sessionName?: string) => Promise; + updateSessionName: (projectPath: string, agentSessionId: string, sessionName: string) => Promise; }; dialog: { selectFolder: () => Promise;