From 519d6b850e07a0387511a8f024dc394250b1a241 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sat, 28 Feb 2026 15:26:02 -0800 Subject: Clear artifacts when creating new session Each new session now starts with empty research.md and plan.md files, preventing stale content from previous sessions appearing. --- dist/main/claude/index.js | 109 +++++++++++++++ dist/main/claude/phases.js | 147 +++++++++++++++++++++ dist/main/db/index.js | 33 +++++ dist/main/db/projects.js | 28 ++++ dist/main/db/schema.js | 36 +++++ dist/main/db/sessions.js | 72 ++++++++++ dist/main/index.js | 54 ++++++++ .../index.sync-conflict-20260228-074140-M6474AW.js | 54 ++++++++ dist/main/ipc/handlers.js | 116 ++++++++++++++++ dist/main/preload.js | 38 ++++++ ...reload.sync-conflict-20260228-074140-M6474AW.js | 38 ++++++ 11 files changed, 725 insertions(+) create mode 100644 dist/main/claude/index.js create mode 100644 dist/main/claude/phases.js create mode 100644 dist/main/db/index.js create mode 100644 dist/main/db/projects.js create mode 100644 dist/main/db/schema.js create mode 100644 dist/main/db/sessions.js create mode 100644 dist/main/index.js create mode 100644 dist/main/index.sync-conflict-20260228-074140-M6474AW.js create mode 100644 dist/main/ipc/handlers.js create mode 100644 dist/main/preload.js create mode 100644 dist/main/preload.sync-conflict-20260228-074140-M6474AW.js (limited to 'dist') diff --git a/dist/main/claude/index.js b/dist/main/claude/index.js new file mode 100644 index 0000000..42a9652 --- /dev/null +++ b/dist/main/claude/index.js @@ -0,0 +1,109 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.sendMessage = sendMessage; +exports.interruptSession = interruptSession; +exports.triggerReview = triggerReview; +exports.advancePhase = advancePhase; +exports.readArtifact = readArtifact; +exports.writeArtifact = writeArtifact; +exports.getPhaseInitialMessage = getPhaseInitialMessage; +const claude_agent_sdk_1 = require("@anthropic-ai/claude-agent-sdk"); +const phases_1 = require("./phases"); +const projects_1 = require("../db/projects"); +const sessions_1 = require("../db/sessions"); +const node_fs_1 = __importDefault(require("node:fs")); +const node_path_1 = __importDefault(require("node:path")); +// Track active queries by session ID +const activeQueries = new Map(); +function ensureArtifactDir(projectPath) { + const dir = node_path_1.default.join(projectPath, ".claude-flow"); + if (!node_fs_1.default.existsSync(dir)) { + node_fs_1.default.mkdirSync(dir, { recursive: true }); + } +} +async function sendMessage({ session, message, onMessage, }) { + const project = (0, projects_1.getProject)(session.project_id); + if (!project) + throw new Error("Project not found"); + ensureArtifactDir(project.path); + const phaseConfig = (0, phases_1.getPhaseConfig)(session.phase, session.permission_mode); + const q = (0, claude_agent_sdk_1.query)({ + prompt: message, + options: { + cwd: project.path, + resume: session.claude_session_id ?? undefined, + tools: phaseConfig.tools, + permissionMode: phaseConfig.permissionMode, + systemPrompt: phaseConfig.systemPrompt, + }, + }); + activeQueries.set(session.id, q); + try { + for await (const msg of q) { + // Capture session ID from init message + if (msg.type === "system" && msg.subtype === "init") { + if (!session.claude_session_id) { + (0, sessions_1.updateSession)(session.id, { claude_session_id: msg.session_id }); + } + } + onMessage(msg); + } + } + finally { + activeQueries.delete(session.id); + } +} +function interruptSession(sessionId) { + const q = activeQueries.get(sessionId); + if (q) { + q.close(); + activeQueries.delete(sessionId); + } +} +/** + * Trigger a review: Claude reads the document and addresses user annotations + */ +async function triggerReview(session, onMessage) { + const docName = (0, phases_1.getArtifactFilename)(session.phase); + const message = `I've updated .claude-flow/${docName} with annotations. Read the file, find all my inline notes (marked with // REVIEW:, // NOTE:, TODO:, or similar), address each one, and update the document accordingly. Do not implement anything yet.`; + await sendMessage({ session, message, onMessage }); +} +/** + * Advance to the next phase + */ +function advancePhase(session) { + const nextPhase = (0, phases_1.getNextPhase)(session.phase); + if (nextPhase) { + (0, sessions_1.updateSession)(session.id, { phase: nextPhase }); + } + return nextPhase; +} +/** + * Read an artifact file from the project's .claude-flow directory + */ +function readArtifact(projectPath, filename) { + const filePath = node_path_1.default.join(projectPath, ".claude-flow", filename); + if (node_fs_1.default.existsSync(filePath)) { + return node_fs_1.default.readFileSync(filePath, "utf-8"); + } + return null; +} +/** + * Write an artifact file to the project's .claude-flow directory + */ +function writeArtifact(projectPath, filename, content) { + const dir = node_path_1.default.join(projectPath, ".claude-flow"); + if (!node_fs_1.default.existsSync(dir)) { + node_fs_1.default.mkdirSync(dir, { recursive: true }); + } + node_fs_1.default.writeFileSync(node_path_1.default.join(dir, filename), content, "utf-8"); +} +/** + * Get the initial message for a phase + */ +function getPhaseInitialMessage(phase) { + return (0, phases_1.getPhaseConfig)(phase).initialMessage; +} diff --git a/dist/main/claude/phases.js b/dist/main/claude/phases.js new file mode 100644 index 0000000..65ea46d --- /dev/null +++ b/dist/main/claude/phases.js @@ -0,0 +1,147 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.phaseConfigs = void 0; +exports.getPhaseConfig = getPhaseConfig; +exports.getNextPhase = getNextPhase; +exports.getArtifactFilename = getArtifactFilename; +exports.phaseConfigs = { + research: { + permissionMode: "acceptEdits", + tools: ["Read", "Glob", "Grep", "Bash", "Write"], + initialMessage: "What areas of the codebase should I research? What are you trying to build?", + systemPrompt: `You are in RESEARCH mode. Your ONLY job is to understand the codebase. + +CRITICAL RULES: +1. You MUST write ALL findings to .claude-flow/research.md — this is your PRIMARY output +2. DO NOT just respond in chat. The document viewer shows research.md, so write there. +3. DO NOT suggest moving to planning or implementation +4. DO NOT ask "are you ready to implement?" or similar +5. DO NOT modify any source code files +6. The user controls phase transitions via UI buttons — never prompt them about it + +WORKFLOW: +1. Ask what to research (if unclear) +2. Read files thoroughly using Read, Glob, Grep +3. Write structured findings to .claude-flow/research.md +4. Keep updating research.md as you learn more + +FORMAT for research.md: +\`\`\`markdown +# Research Findings + +## Overview +[High-level summary of what you found] + +## Architecture +[Key components, patterns, structure] + +## Relevant Files +[List of important files with descriptions] + +## Key Insights +[Important discoveries, patterns, potential issues] + +## Questions/Unknowns +[Things that need clarification] +\`\`\` + +When the user adds annotations (// REVIEW:, // NOTE:, TODO:) to research.md and clicks Review, address each annotation and update the document. + +Remember: Your output goes in research.md, not chat. Chat is for clarifying questions only.`, + }, + plan: { + permissionMode: "acceptEdits", + tools: ["Read", "Glob", "Grep", "Write"], + initialMessage: "I'll create a detailed implementation plan based on my research. Writing to plan.md...", + systemPrompt: `You are in PLANNING mode. Your ONLY job is to create an implementation plan. + +CRITICAL RULES: +1. You MUST write the plan to .claude-flow/plan.md — this is your PRIMARY output +2. DO NOT just respond in chat. The document viewer shows plan.md, so write there. +3. DO NOT implement anything — no code changes to source files +4. DO NOT ask "should I start implementing?" or similar +5. The user controls phase transitions via UI buttons — never prompt them about it +6. Base your plan on .claude-flow/research.md + +WORKFLOW: +1. Read .claude-flow/research.md to understand the codebase +2. Write a detailed plan to .claude-flow/plan.md +3. Include specific code snippets showing proposed changes +4. Make the plan detailed enough that implementation is mechanical + +FORMAT for plan.md: +\`\`\`markdown +# Implementation Plan + +## Goal +[What we're building/changing] + +## Approach +[High-level strategy] + +## Changes + +### 1. [First change] +**File:** path/to/file.ts +**What:** [Description] +**Code:** +\\\`\\\`\\\`typescript +// Proposed code +\\\`\\\`\\\` + +### 2. [Second change] +... + +## TODO +- [ ] Task 1 +- [ ] Task 2 +- [ ] Task 3 + +## Risks/Considerations +[Potential issues, edge cases] +\`\`\` + +When the user adds annotations to plan.md and clicks Review, address each annotation and update the document. + +Remember: Your output goes in plan.md, not chat. Chat is for clarifying questions only.`, + }, + implement: { + permissionMode: "acceptEdits", + tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"], + initialMessage: "Starting implementation. I'll follow the plan exactly and mark tasks complete as I go.", + systemPrompt: `You are in IMPLEMENTATION mode. Execute the approved plan. + +CRITICAL RULES: +1. Read .claude-flow/plan.md and follow it exactly +2. Mark tasks complete in plan.md as you finish them: - [ ] → - [x] +3. DO NOT deviate from the plan without asking +4. Run tests/typecheck if available +5. Stop and ask if you encounter issues not covered by the plan + +WORKFLOW: +1. Read .claude-flow/plan.md +2. Execute each task in order +3. Update plan.md to mark tasks complete +4. Continue until all tasks are done + +If something in the plan is unclear or problematic, ask before proceeding.`, + }, +}; +function getPhaseConfig(phase, userPermissionMode) { + const config = { ...exports.phaseConfigs[phase] }; + if (phase === "implement" && userPermissionMode) { + config.permissionMode = userPermissionMode; + } + return config; +} +function getNextPhase(phase) { + const transitions = { + research: "plan", + plan: "implement", + implement: null, + }; + return transitions[phase]; +} +function getArtifactFilename(phase) { + return phase === "research" ? "research.md" : "plan.md"; +} diff --git a/dist/main/db/index.js b/dist/main/db/index.js new file mode 100644 index 0000000..652d189 --- /dev/null +++ b/dist/main/db/index.js @@ -0,0 +1,33 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getDb = getDb; +exports.closeDb = closeDb; +const better_sqlite3_1 = __importDefault(require("better-sqlite3")); +const electron_1 = require("electron"); +const node_path_1 = __importDefault(require("node:path")); +const node_fs_1 = __importDefault(require("node:fs")); +const schema_1 = require("./schema"); +let db = null; +function getDb() { + if (db) + return db; + const dbDir = electron_1.app.getPath("userData"); + if (!node_fs_1.default.existsSync(dbDir)) { + node_fs_1.default.mkdirSync(dbDir, { recursive: true }); + } + const dbPath = node_path_1.default.join(dbDir, "claude-flow.db"); + db = new better_sqlite3_1.default(dbPath); + db.pragma("journal_mode = WAL"); + db.pragma("foreign_keys = ON"); + (0, schema_1.initSchema)(db); + return db; +} +function closeDb() { + if (db) { + db.close(); + db = null; + } +} diff --git a/dist/main/db/projects.js b/dist/main/db/projects.js new file mode 100644 index 0000000..af36cc0 --- /dev/null +++ b/dist/main/db/projects.js @@ -0,0 +1,28 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.listProjects = listProjects; +exports.getProject = getProject; +exports.createProject = createProject; +exports.deleteProject = deleteProject; +const index_1 = require("./index"); +const uuid_1 = require("uuid"); +function listProjects() { + return (0, index_1.getDb)() + .prepare("SELECT * FROM projects ORDER BY updated_at DESC") + .all(); +} +function getProject(id) { + return (0, index_1.getDb)() + .prepare("SELECT * FROM projects WHERE id = ?") + .get(id); +} +function createProject(name, projectPath) { + const db = (0, index_1.getDb)(); + const id = (0, uuid_1.v4)(); + const now = Math.floor(Date.now() / 1000); + db.prepare("INSERT INTO projects (id, name, path, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(id, name, projectPath, now, now); + return { id, name, path: projectPath, created_at: now, updated_at: now }; +} +function deleteProject(id) { + (0, index_1.getDb)().prepare("DELETE FROM projects WHERE id = ?").run(id); +} diff --git a/dist/main/db/schema.js b/dist/main/db/schema.js new file mode 100644 index 0000000..974e593 --- /dev/null +++ b/dist/main/db/schema.js @@ -0,0 +1,36 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.initSchema = initSchema; +function initSchema(db) { + db.exec(` + CREATE TABLE IF NOT EXISTS projects ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + path TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (unixepoch()), + updated_at INTEGER NOT NULL DEFAULT (unixepoch()) + ); + + CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + name TEXT NOT NULL, + phase TEXT NOT NULL DEFAULT 'research', + claude_session_id TEXT, + permission_mode TEXT NOT NULL DEFAULT 'acceptEdits', + created_at INTEGER NOT NULL DEFAULT (unixepoch()), + updated_at INTEGER NOT NULL DEFAULT (unixepoch()) + ); + + CREATE TABLE IF NOT EXISTS messages ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, + role TEXT NOT NULL, + content TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (unixepoch()) + ); + + CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id); + CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id); + `); +} diff --git a/dist/main/db/sessions.js b/dist/main/db/sessions.js new file mode 100644 index 0000000..b554898 --- /dev/null +++ b/dist/main/db/sessions.js @@ -0,0 +1,72 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.listSessions = listSessions; +exports.getSession = getSession; +exports.createSession = createSession; +exports.updateSession = updateSession; +exports.deleteSession = deleteSession; +exports.listMessages = listMessages; +exports.addMessage = addMessage; +const index_1 = require("./index"); +const uuid_1 = require("uuid"); +function listSessions(projectId) { + return (0, index_1.getDb)() + .prepare("SELECT * FROM sessions WHERE project_id = ? ORDER BY updated_at DESC") + .all(projectId); +} +function getSession(id) { + return (0, index_1.getDb)() + .prepare("SELECT * FROM sessions WHERE id = ?") + .get(id); +} +function createSession(projectId, name) { + const db = (0, index_1.getDb)(); + const id = (0, uuid_1.v4)(); + const now = Math.floor(Date.now() / 1000); + db.prepare(`INSERT INTO sessions (id, project_id, name, phase, permission_mode, created_at, updated_at) + VALUES (?, ?, ?, 'research', 'acceptEdits', ?, ?)`).run(id, projectId, name, now, now); + return { + id, + project_id: projectId, + name, + phase: "research", + claude_session_id: null, + permission_mode: "acceptEdits", + created_at: now, + updated_at: now, + }; +} +function updateSession(id, updates) { + const db = (0, index_1.getDb)(); + const sets = []; + const values = []; + for (const [key, value] of Object.entries(updates)) { + if (value !== undefined) { + sets.push(`${key} = ?`); + values.push(value); + } + } + if (sets.length > 0) { + sets.push("updated_at = ?"); + values.push(Math.floor(Date.now() / 1000)); + values.push(id); + db.prepare(`UPDATE sessions SET ${sets.join(", ")} WHERE id = ?`).run(...values); + } +} +function deleteSession(id) { + (0, index_1.getDb)().prepare("DELETE FROM sessions WHERE id = ?").run(id); +} +// Messages +function listMessages(sessionId) { + return (0, index_1.getDb)() + .prepare("SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC") + .all(sessionId); +} +function addMessage(sessionId, role, content) { + const db = (0, index_1.getDb)(); + const id = (0, uuid_1.v4)(); + const now = Math.floor(Date.now() / 1000); + db.prepare("INSERT INTO messages (id, session_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)").run(id, sessionId, role, content, now); + db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, sessionId); + return { id, session_id: sessionId, role, content, created_at: now }; +} diff --git a/dist/main/index.js b/dist/main/index.js new file mode 100644 index 0000000..a10a371 --- /dev/null +++ b/dist/main/index.js @@ -0,0 +1,54 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const node_path_1 = __importDefault(require("node:path")); +const db_1 = require("./db"); +const handlers_1 = require("./ipc/handlers"); +const isDev = !electron_1.app.isPackaged; +let mainWindow = null; +function createWindow() { + mainWindow = new electron_1.BrowserWindow({ + width: 1400, + height: 900, + minWidth: 1000, + minHeight: 600, + show: false, + titleBarStyle: "hiddenInset", + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + preload: node_path_1.default.join(__dirname, "preload.js"), + }, + }); + (0, handlers_1.registerIpcHandlers)(mainWindow); + if (isDev) { + const url = process.env.VITE_DEV_SERVER_URL ?? "http://localhost:5173"; + mainWindow.loadURL(url).finally(() => { + mainWindow.show(); + mainWindow.webContents.openDevTools({ mode: "detach" }); + }); + } + else { + const indexHtml = node_path_1.default.join(electron_1.app.getAppPath(), "renderer", "dist", "index.html"); + mainWindow.loadFile(indexHtml).finally(() => mainWindow.show()); + } +} +electron_1.app.whenReady().then(() => { + // Initialize database + (0, db_1.getDb)(); + createWindow(); + electron_1.app.on("activate", () => { + if (electron_1.BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); +}); +electron_1.app.on("window-all-closed", () => { + (0, db_1.closeDb)(); + if (process.platform !== "darwin") { + electron_1.app.quit(); + } +}); diff --git a/dist/main/index.sync-conflict-20260228-074140-M6474AW.js b/dist/main/index.sync-conflict-20260228-074140-M6474AW.js new file mode 100644 index 0000000..a10a371 --- /dev/null +++ b/dist/main/index.sync-conflict-20260228-074140-M6474AW.js @@ -0,0 +1,54 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const node_path_1 = __importDefault(require("node:path")); +const db_1 = require("./db"); +const handlers_1 = require("./ipc/handlers"); +const isDev = !electron_1.app.isPackaged; +let mainWindow = null; +function createWindow() { + mainWindow = new electron_1.BrowserWindow({ + width: 1400, + height: 900, + minWidth: 1000, + minHeight: 600, + show: false, + titleBarStyle: "hiddenInset", + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + preload: node_path_1.default.join(__dirname, "preload.js"), + }, + }); + (0, handlers_1.registerIpcHandlers)(mainWindow); + if (isDev) { + const url = process.env.VITE_DEV_SERVER_URL ?? "http://localhost:5173"; + mainWindow.loadURL(url).finally(() => { + mainWindow.show(); + mainWindow.webContents.openDevTools({ mode: "detach" }); + }); + } + else { + const indexHtml = node_path_1.default.join(electron_1.app.getAppPath(), "renderer", "dist", "index.html"); + mainWindow.loadFile(indexHtml).finally(() => mainWindow.show()); + } +} +electron_1.app.whenReady().then(() => { + // Initialize database + (0, db_1.getDb)(); + createWindow(); + electron_1.app.on("activate", () => { + if (electron_1.BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); +}); +electron_1.app.on("window-all-closed", () => { + (0, db_1.closeDb)(); + if (process.platform !== "darwin") { + electron_1.app.quit(); + } +}); diff --git a/dist/main/ipc/handlers.js b/dist/main/ipc/handlers.js new file mode 100644 index 0000000..c452cee --- /dev/null +++ b/dist/main/ipc/handlers.js @@ -0,0 +1,116 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.registerIpcHandlers = registerIpcHandlers; +const electron_1 = require("electron"); +const projects = __importStar(require("../db/projects")); +const sessions = __importStar(require("../db/sessions")); +const claude = __importStar(require("../claude")); +function registerIpcHandlers(mainWindow) { + // Projects + electron_1.ipcMain.handle("projects:list", () => projects.listProjects()); + electron_1.ipcMain.handle("projects:create", (_, name, path) => projects.createProject(name, path)); + electron_1.ipcMain.handle("projects:delete", (_, id) => projects.deleteProject(id)); + // Sessions + electron_1.ipcMain.handle("sessions:list", (_, projectId) => sessions.listSessions(projectId)); + electron_1.ipcMain.handle("sessions:create", (_, projectId, name) => sessions.createSession(projectId, name)); + electron_1.ipcMain.handle("sessions:delete", (_, id) => sessions.deleteSession(id)); + electron_1.ipcMain.handle("sessions:get", (_, id) => sessions.getSession(id)); + // Messages + electron_1.ipcMain.handle("messages:list", (_, sessionId) => sessions.listMessages(sessionId)); + // Chat + electron_1.ipcMain.handle("chat:send", async (_, sessionId, message) => { + 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 + .filter((c) => c.type === "text" && c.text) + .map((c) => c.text) + .join("\n"); + if (content) { + sessions.addMessage(sessionId, "assistant", content); + } + } + }, + }); + }); + electron_1.ipcMain.handle("chat:interrupt", (_, sessionId) => { + claude.interruptSession(sessionId); + }); + // Workflow + electron_1.ipcMain.handle("workflow:review", async (_, sessionId) => { + 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); + }); + }); + electron_1.ipcMain.handle("workflow:advance", (_, sessionId) => { + const session = sessions.getSession(sessionId); + if (!session) + throw new Error("Session not found"); + return claude.advancePhase(session); + }); + electron_1.ipcMain.handle("workflow:setPermissionMode", (_, sessionId, mode) => { + sessions.updateSession(sessionId, { + permission_mode: mode, + }); + }); + // Artifacts + electron_1.ipcMain.handle("artifact:read", (_, projectPath, filename) => { + return claude.readArtifact(projectPath, filename); + }); + electron_1.ipcMain.handle("artifact:write", (_, projectPath, filename, content) => { + claude.writeArtifact(projectPath, filename, content); + }); + // Dialogs + electron_1.ipcMain.handle("dialog:selectDirectory", async () => { + const result = await electron_1.dialog.showOpenDialog(mainWindow, { + properties: ["openDirectory"], + }); + return result.canceled ? null : result.filePaths[0]; + }); +} diff --git a/dist/main/preload.js b/dist/main/preload.js new file mode 100644 index 0000000..7978b53 --- /dev/null +++ b/dist/main/preload.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const api = { + // Projects + listProjects: () => electron_1.ipcRenderer.invoke("projects:list"), + createProject: (name, path) => electron_1.ipcRenderer.invoke("projects:create", name, path), + deleteProject: (id) => electron_1.ipcRenderer.invoke("projects:delete", id), + // Sessions + listSessions: (projectId) => electron_1.ipcRenderer.invoke("sessions:list", projectId), + createSession: (projectId, name) => electron_1.ipcRenderer.invoke("sessions:create", projectId, name), + deleteSession: (id) => electron_1.ipcRenderer.invoke("sessions:delete", id), + getSession: (id) => electron_1.ipcRenderer.invoke("sessions:get", id), + // Messages + listMessages: (sessionId) => electron_1.ipcRenderer.invoke("messages:list", sessionId), + // Chat + sendMessage: (sessionId, message) => electron_1.ipcRenderer.invoke("chat:send", sessionId, message), + interruptSession: (sessionId) => electron_1.ipcRenderer.invoke("chat:interrupt", sessionId), + // Workflow + triggerReview: (sessionId) => electron_1.ipcRenderer.invoke("workflow:review", sessionId), + advancePhase: (sessionId) => electron_1.ipcRenderer.invoke("workflow:advance", sessionId), + setPermissionMode: (sessionId, mode) => electron_1.ipcRenderer.invoke("workflow:setPermissionMode", sessionId, mode), + // Artifacts + readArtifact: (projectPath, filename) => electron_1.ipcRenderer.invoke("artifact:read", projectPath, filename), + writeArtifact: (projectPath, filename, content) => electron_1.ipcRenderer.invoke("artifact:write", projectPath, filename, content), + // Events + onClaudeMessage: (callback) => { + const handler = (_, sessionId, message) => callback(sessionId, message); + electron_1.ipcRenderer.on("claude:message", handler); + return () => electron_1.ipcRenderer.removeListener("claude:message", handler); + }, + // Dialogs + selectDirectory: async () => { + const result = await electron_1.ipcRenderer.invoke("dialog:selectDirectory"); + return result; + }, +}; +electron_1.contextBridge.exposeInMainWorld("api", api); diff --git a/dist/main/preload.sync-conflict-20260228-074140-M6474AW.js b/dist/main/preload.sync-conflict-20260228-074140-M6474AW.js new file mode 100644 index 0000000..7978b53 --- /dev/null +++ b/dist/main/preload.sync-conflict-20260228-074140-M6474AW.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const api = { + // Projects + listProjects: () => electron_1.ipcRenderer.invoke("projects:list"), + createProject: (name, path) => electron_1.ipcRenderer.invoke("projects:create", name, path), + deleteProject: (id) => electron_1.ipcRenderer.invoke("projects:delete", id), + // Sessions + listSessions: (projectId) => electron_1.ipcRenderer.invoke("sessions:list", projectId), + createSession: (projectId, name) => electron_1.ipcRenderer.invoke("sessions:create", projectId, name), + deleteSession: (id) => electron_1.ipcRenderer.invoke("sessions:delete", id), + getSession: (id) => electron_1.ipcRenderer.invoke("sessions:get", id), + // Messages + listMessages: (sessionId) => electron_1.ipcRenderer.invoke("messages:list", sessionId), + // Chat + sendMessage: (sessionId, message) => electron_1.ipcRenderer.invoke("chat:send", sessionId, message), + interruptSession: (sessionId) => electron_1.ipcRenderer.invoke("chat:interrupt", sessionId), + // Workflow + triggerReview: (sessionId) => electron_1.ipcRenderer.invoke("workflow:review", sessionId), + advancePhase: (sessionId) => electron_1.ipcRenderer.invoke("workflow:advance", sessionId), + setPermissionMode: (sessionId, mode) => electron_1.ipcRenderer.invoke("workflow:setPermissionMode", sessionId, mode), + // Artifacts + readArtifact: (projectPath, filename) => electron_1.ipcRenderer.invoke("artifact:read", projectPath, filename), + writeArtifact: (projectPath, filename, content) => electron_1.ipcRenderer.invoke("artifact:write", projectPath, filename, content), + // Events + onClaudeMessage: (callback) => { + const handler = (_, sessionId, message) => callback(sessionId, message); + electron_1.ipcRenderer.on("claude:message", handler); + return () => electron_1.ipcRenderer.removeListener("claude:message", handler); + }, + // Dialogs + selectDirectory: async () => { + const result = await electron_1.ipcRenderer.invoke("dialog:selectDirectory"); + return result; + }, +}; +electron_1.contextBridge.exposeInMainWorld("api", api); -- cgit v1.2.3