|
| 1 | +import { ipcMain, IpcMainInvokeEvent, dialog } from "electron"; |
| 2 | +import { logger } from "../logger"; |
| 3 | +import * as agentTools from "../agent-tools"; |
| 4 | +import { detectSandboxCapabilities } from "../command-sandbox"; |
| 5 | +import * as terminalManager from "../terminal-manager"; |
| 6 | +import * as mcpClient from "../mcp-client"; |
| 7 | +import { agentToolArgsSchemas, mcpToolArgsSchema, parseOrThrow } from "../schemas"; |
| 8 | +import { getMainWindow, requireString } from "../app-state"; |
| 9 | + |
| 10 | +export function registerAgentIpc(): void { |
| 11 | + ipcMain.handle("agent:pickWorkspace", async () => { |
| 12 | + const mainWindow = getMainWindow(); |
| 13 | + const result = mainWindow |
| 14 | + ? await dialog.showOpenDialog(mainWindow, { properties: ["openDirectory"] }) |
| 15 | + : await dialog.showOpenDialog({ properties: ["openDirectory"] }); |
| 16 | + return result.canceled || result.filePaths.length === 0 ? null : result.filePaths[0]; |
| 17 | + }); |
| 18 | + |
| 19 | + ipcMain.handle( |
| 20 | + "tools:execute", |
| 21 | + async ( |
| 22 | + _event: IpcMainInvokeEvent, |
| 23 | + { workspaceRoot, name, args }: { workspaceRoot: string; name: string; args: unknown } |
| 24 | + ) => { |
| 25 | + requireString(workspaceRoot, "workspace root"); |
| 26 | + requireString(name, "tool name"); |
| 27 | + try { |
| 28 | + const isMcpTool = mcpClient.isMcpTool(name); |
| 29 | + // MCP tool schemas are server-defined and unknown to us ahead of |
| 30 | + // time — just check for a plain object. Built-in tools get a |
| 31 | + // real per-tool schema; a tool name with no entry here (a typo, |
| 32 | + // or a tool the model invented) falls through with args |
| 33 | + // unvalidated and is rejected by executeTool()'s own "Unknown |
| 34 | + // tool" check instead. |
| 35 | + const schema = isMcpTool ? mcpToolArgsSchema : agentToolArgsSchemas[name]; |
| 36 | + const validatedArgs = (schema |
| 37 | + ? parseOrThrow(schema, args ?? {}, `arguments for tool "${name}"`) |
| 38 | + : (args ?? {})) as Record<string, unknown>; |
| 39 | + const result = isMcpTool |
| 40 | + ? await mcpClient.callMcpTool(name, validatedArgs) |
| 41 | + : await agentTools.executeTool(workspaceRoot, name, validatedArgs); |
| 42 | + return { result }; |
| 43 | + } catch (err) { |
| 44 | + const error = err as Error; |
| 45 | + logger.error(`Tool execution failed (tool=${name}): ${error.message}`); |
| 46 | + return { error: error.message }; |
| 47 | + } |
| 48 | + } |
| 49 | + ); |
| 50 | + |
| 51 | + ipcMain.handle("agent:rollbackLastWrite", (_event: IpcMainInvokeEvent, workspaceRoot: string) => { |
| 52 | + requireString(workspaceRoot, "workspace root"); |
| 53 | + return agentTools.rollbackLastWrite(workspaceRoot); |
| 54 | + }); |
| 55 | + |
| 56 | + ipcMain.handle("agent:detectScripts", (_event: IpcMainInvokeEvent, workspaceRoot: string) => { |
| 57 | + requireString(workspaceRoot, "workspace root"); |
| 58 | + return agentTools.detectProjectScripts(workspaceRoot); |
| 59 | + }); |
| 60 | + |
| 61 | + // Called when the renderer is about to stop using a workspace (switching |
| 62 | + // to a different folder, or loading a session that points elsewhere) — |
| 63 | + // without this, background tasks started against the old workspace kept |
| 64 | + // running indefinitely, since killAllBackgroundCommands() only ever ran |
| 65 | + // on app quit. |
| 66 | + ipcMain.handle("agent:closeWorkspace", (_event: IpcMainInvokeEvent, workspaceRoot: string) => { |
| 67 | + requireString(workspaceRoot, "workspace root"); |
| 68 | + const killedBackgroundTasks = agentTools.killBackgroundCommandsForWorkspace(workspaceRoot); |
| 69 | + const killedTerminals = terminalManager.closeAllForWorkspace(workspaceRoot); |
| 70 | + return { killedBackgroundTasks, killedTerminals }; |
| 71 | + }); |
| 72 | + |
| 73 | + ipcMain.handle("agent:getSandboxCapabilities", () => detectSandboxCapabilities()); |
| 74 | +} |
0 commit comments