From 66f66d1c17213f55aa56d69c0cccc309b16f3362 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 28 Feb 2026 07:27:49 -0800 Subject: Phase 3: IPC layer - Implement src/main/preload.ts with typed API bridge - Projects, sessions, messages CRUD - Chat send/interrupt - Workflow review/advance/permissions - Artifact read/write - Directory picker dialog - Claude message event subscription - Implement src/main/ipc/handlers.ts - All IPC handlers with proper error handling - Message forwarding to renderer - Assistant message storage - Update src/main/index.ts - Initialize database on startup - Register IPC handlers - Clean database close on exit --- src/main/ipc/handlers.ts | 117 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/main/ipc/handlers.ts (limited to 'src/main/ipc/handlers.ts') diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts new file mode 100644 index 0000000..ce95f4c --- /dev/null +++ b/src/main/ipc/handlers.ts @@ -0,0 +1,117 @@ +import { ipcMain, dialog, type BrowserWindow } from "electron"; +import * as projects from "../db/projects"; +import * as sessions from "../db/sessions"; +import * as claude from "../claude"; +import type { UserPermissionMode } from "../claude/phases"; + +export function registerIpcHandlers(mainWindow: BrowserWindow): void { + // Projects + ipcMain.handle("projects:list", () => projects.listProjects()); + ipcMain.handle("projects:create", (_, name: string, path: string) => + projects.createProject(name, path) + ); + ipcMain.handle("projects:delete", (_, id: string) => + projects.deleteProject(id) + ); + + // Sessions + ipcMain.handle("sessions:list", (_, projectId: string) => + sessions.listSessions(projectId) + ); + ipcMain.handle("sessions:create", (_, projectId: string, name: string) => + sessions.createSession(projectId, name) + ); + ipcMain.handle("sessions:delete", (_, id: string) => + sessions.deleteSession(id) + ); + ipcMain.handle("sessions:get", (_, id: string) => sessions.getSession(id)); + + // Messages + ipcMain.handle("messages:list", (_, sessionId: string) => + sessions.listMessages(sessionId) + ); + + // Chat + ipcMain.handle( + "chat:send", + async (_, sessionId: string, message: string) => { + const session = sessions.getSession(sessionId); + if (!session) throw new Error("Session not found"); + + // Store user message + sessions.addMessage(sessionId, "user", message); + + await claude.sendMessage({ + session, + message, + onMessage: (msg) => { + // Forward all messages to renderer + mainWindow.webContents.send("claude:message", sessionId, msg); + + // Store assistant text messages + if (msg.type === "assistant") { + const content = (msg.message.content as Array<{ type: string; text?: string }>) + .filter((c) => c.type === "text" && c.text) + .map((c) => c.text!) + .join("\n"); + if (content) { + sessions.addMessage(sessionId, "assistant", content); + } + } + }, + }); + } + ); + + ipcMain.handle("chat:interrupt", (_, sessionId: string) => { + claude.interruptSession(sessionId); + }); + + // Workflow + ipcMain.handle("workflow:review", async (_, sessionId: string) => { + const session = sessions.getSession(sessionId); + if (!session) throw new Error("Session not found"); + + await claude.triggerReview(session, (msg) => { + mainWindow.webContents.send("claude:message", sessionId, msg); + }); + }); + + ipcMain.handle("workflow:advance", (_, sessionId: string) => { + const session = sessions.getSession(sessionId); + if (!session) throw new Error("Session not found"); + return claude.advancePhase(session); + }); + + ipcMain.handle( + "workflow:setPermissionMode", + (_, sessionId: string, mode: string) => { + sessions.updateSession(sessionId, { + permission_mode: mode as UserPermissionMode, + }); + } + ); + + // Artifacts + ipcMain.handle( + "artifact:read", + (_, projectPath: string, filename: string) => { + return claude.readArtifact(projectPath, filename); + } + ); + + ipcMain.handle( + "artifact:write", + (_, projectPath: string, filename: string, content: string) => { + claude.writeArtifact(projectPath, filename, content); + } + ); + + // Dialogs + ipcMain.handle("dialog:selectDirectory", async () => { + const result = await dialog.showOpenDialog(mainWindow, { + properties: ["openDirectory"], + }); + return result.canceled ? null : result.filePaths[0]; + }); +} -- cgit v1.2.3