From 2502d4d41e6dbd88b2d24f0c6648869c7e6169a2 Mon Sep 17 00:00:00 2001 From: nullxnothing Date: Mon, 4 May 2026 08:22:37 -0600 Subject: [PATCH 1/3] chore: normalize line endings repo-wide Pure crlf-to-lf normalization following the .gitattributes added in v3.1. Verified zero semantic diff with git diff --ignore-cr-at-eol --ignore-all-space. --- electron/main/index.ts | 804 +++++++++++++------------- electron/services/SagaOrchestrator.ts | 508 ++++++++-------- 2 files changed, 656 insertions(+), 656 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index aabf6a58..db24558a 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1,44 +1,44 @@ -import 'dotenv/config' -import { app, BrowserWindow, shell, ipcMain, protocol, net, session } from 'electron' -import { fileURLToPath, pathToFileURL } from 'node:url' -import path from 'node:path' -import crypto from 'node:crypto' -import { getDb, closeDb } from '../db/db' -import { isPathSafe } from '../shared/pathValidation' -import { registerTerminalHandlers, killAllSessions } from '../ipc/terminal' -import { registerFilesystemHandlers } from '../ipc/filesystem' -import { registerProjectHandlers } from '../ipc/projects' -import { registerAgentHandlers } from '../ipc/agents' -import { registerClaudeHandlers } from '../ipc/claude' -import { registerCodexHandlers } from '../ipc/codex' -import { registerProviderHandlers } from '../ipc/provider' -import { registerActivityHandlers } from '../ipc/activity' -import { ClaudeProvider, CodexProvider, ProviderRegistry } from '../services/providers' -import { registerGitHandlers } from '../ipc/git' -import { registerProcessHandlers } from '../ipc/processes' -import { registerEnvHandlers } from '../ipc/env' -import { registerPortHandlers } from '../ipc/ports' -import { registerWalletHandlers } from '../ipc/wallet' -import { registerProHandlers } from '../ipc/pro' -import { registerSettingsHandlers } from '../ipc/settings' -import { registerPluginHandlers } from '../ipc/plugins' -import { registerTweetHandlers } from '../ipc/tweets' -import { registerRecoveryHandlers } from '../ipc/recovery' -import { registerEngineHandlers } from '../ipc/engine' -import { registerToolHandlers } from '../ipc/tools' -import { registerPumpFunHandlers } from '../ipc/pumpfun' -import { registerBrowserHandlers } from '../ipc/browser' -import { registerDeployHandlers } from '../ipc/deploy' -import { registerEmailHandlers } from '../ipc/email' -import { registerImageHandlers } from '../ipc/images' -import { registerAriaHandlers } from '../ipc/aria' -import { registerLaunchHandlers } from '../ipc/launch' -import { registerDashboardHandlers } from '../ipc/dashboard' -import { registerRegistryHandlers } from '../ipc/registry' -import { registerColosseumHandlers } from '../ipc/colosseum' -import { registerVaultHandlers } from '../ipc/vault' -import { registerValidatorHandlers } from '../ipc/validator' -import { registerPnlHandlers } from '../ipc/pnl' +import 'dotenv/config' +import { app, BrowserWindow, shell, ipcMain, protocol, net, session } from 'electron' +import { fileURLToPath, pathToFileURL } from 'node:url' +import path from 'node:path' +import crypto from 'node:crypto' +import { getDb, closeDb } from '../db/db' +import { isPathSafe } from '../shared/pathValidation' +import { registerTerminalHandlers, killAllSessions } from '../ipc/terminal' +import { registerFilesystemHandlers } from '../ipc/filesystem' +import { registerProjectHandlers } from '../ipc/projects' +import { registerAgentHandlers } from '../ipc/agents' +import { registerClaudeHandlers } from '../ipc/claude' +import { registerCodexHandlers } from '../ipc/codex' +import { registerProviderHandlers } from '../ipc/provider' +import { registerActivityHandlers } from '../ipc/activity' +import { ClaudeProvider, CodexProvider, ProviderRegistry } from '../services/providers' +import { registerGitHandlers } from '../ipc/git' +import { registerProcessHandlers } from '../ipc/processes' +import { registerEnvHandlers } from '../ipc/env' +import { registerPortHandlers } from '../ipc/ports' +import { registerWalletHandlers } from '../ipc/wallet' +import { registerProHandlers } from '../ipc/pro' +import { registerSettingsHandlers } from '../ipc/settings' +import { registerPluginHandlers } from '../ipc/plugins' +import { registerTweetHandlers } from '../ipc/tweets' +import { registerRecoveryHandlers } from '../ipc/recovery' +import { registerEngineHandlers } from '../ipc/engine' +import { registerToolHandlers } from '../ipc/tools' +import { registerPumpFunHandlers } from '../ipc/pumpfun' +import { registerBrowserHandlers } from '../ipc/browser' +import { registerDeployHandlers } from '../ipc/deploy' +import { registerEmailHandlers } from '../ipc/email' +import { registerImageHandlers } from '../ipc/images' +import { registerAriaHandlers } from '../ipc/aria' +import { registerLaunchHandlers } from '../ipc/launch' +import { registerDashboardHandlers } from '../ipc/dashboard' +import { registerRegistryHandlers } from '../ipc/registry' +import { registerColosseumHandlers } from '../ipc/colosseum' +import { registerVaultHandlers } from '../ipc/vault' +import { registerValidatorHandlers } from '../ipc/validator' +import { registerPnlHandlers } from '../ipc/pnl' import { registerFeedbackHandlers } from '../ipc/feedback' import { registerAgentStationHandlers } from '../ipc/agentStation' import { registerReplayHandlers } from '../ipc/replay' @@ -46,79 +46,79 @@ import { registerLspHandlers } from '../ipc/lsp' import { clearLoadedWallets } from '../services/RecoveryService' import { maybeRecoverUnstableUiState, type UiRecoveryResult } from '../services/SettingsService' import { shutdownAllLspSessions } from '../services/LspService' -import pkg from 'electron-updater' -const { autoUpdater } = pkg - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -process.env.APP_ROOT = path.join(__dirname, '../..') -export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron') -export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist') -export const VITE_DEV_SERVER_URL = app.isPackaged ? undefined : process.env.VITE_DEV_SERVER_URL -const SMOKE_TEST_MODE = process.env.DAEMON_SMOKE_TEST === '1' - -process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL - ? path.join(process.env.APP_ROOT, 'public') - : RENDERER_DIST - -if (process.env.DAEMON_USER_DATA_DIR) { - app.setPath('userData', process.env.DAEMON_USER_DATA_DIR) -} - -if (SMOKE_TEST_MODE) { - app.commandLine.appendSwitch('remote-debugging-port', process.env.DAEMON_SMOKE_CDP_PORT ?? '9333') -} else if (!app.isPackaged) { - app.commandLine.appendSwitch('remote-debugging-port', '9222') -} - -// Monaco offline protocol — must be registered before app.whenReady() -// In production, Monaco workers load via this custom protocol instead of CDN -protocol.registerSchemesAsPrivileged([{ - scheme: 'monaco-editor', - privileges: { standard: true, supportFetchAPI: true }, -}, { - scheme: 'daemon-icon', - privileges: { standard: true, supportFetchAPI: true }, -}, { - scheme: 'minipaint', - privileges: { standard: true, supportFetchAPI: true, allowServiceWorkers: false }, -}]) - +import pkg from 'electron-updater' +const { autoUpdater } = pkg + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +process.env.APP_ROOT = path.join(__dirname, '../..') +export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron') +export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist') +export const VITE_DEV_SERVER_URL = app.isPackaged ? undefined : process.env.VITE_DEV_SERVER_URL +const SMOKE_TEST_MODE = process.env.DAEMON_SMOKE_TEST === '1' + +process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL + ? path.join(process.env.APP_ROOT, 'public') + : RENDERER_DIST + +if (process.env.DAEMON_USER_DATA_DIR) { + app.setPath('userData', process.env.DAEMON_USER_DATA_DIR) +} + +if (SMOKE_TEST_MODE) { + app.commandLine.appendSwitch('remote-debugging-port', process.env.DAEMON_SMOKE_CDP_PORT ?? '9333') +} else if (!app.isPackaged) { + app.commandLine.appendSwitch('remote-debugging-port', '9222') +} + +// Monaco offline protocol — must be registered before app.whenReady() +// In production, Monaco workers load via this custom protocol instead of CDN +protocol.registerSchemesAsPrivileged([{ + scheme: 'monaco-editor', + privileges: { standard: true, supportFetchAPI: true }, +}, { + scheme: 'daemon-icon', + privileges: { standard: true, supportFetchAPI: true }, +}, { + scheme: 'minipaint', + privileges: { standard: true, supportFetchAPI: true, allowServiceWorkers: false }, +}]) + if (process.platform === 'win32') app.setAppUserModelId('com.daemon.app') - -// Crash capture — write unhandled errors to app_crashes table -process.on('uncaughtException', (error) => { - try { - const db = getDb() - db.prepare('INSERT INTO app_crashes (id, type, message, stack, created_at) VALUES (?,?,?,?,?)').run( - crypto.randomUUID(), 'uncaughtException', error.message, error.stack ?? '', Date.now() - ) - } catch { /* DB may not be ready */ } -}) - -process.on('unhandledRejection', (reason) => { - try { - const db = getDb() - const message = reason instanceof Error ? reason.message : String(reason) - const stack = reason instanceof Error ? reason.stack ?? '' : '' - db.prepare('INSERT INTO app_crashes (id, type, message, stack, created_at) VALUES (?,?,?,?,?)').run( - crypto.randomUUID(), 'unhandledRejection', message, stack, Date.now() - ) - } catch { /* DB may not be ready */ } -}) - -if (!SMOKE_TEST_MODE && !app.requestSingleInstanceLock()) { - app.quit() - process.exit(0) -} - -let win: BrowserWindow | null = null -let ipcRegistered = false -let startupUiRecovery: UiRecoveryResult | null = null + +// Crash capture — write unhandled errors to app_crashes table +process.on('uncaughtException', (error) => { + try { + const db = getDb() + db.prepare('INSERT INTO app_crashes (id, type, message, stack, created_at) VALUES (?,?,?,?,?)').run( + crypto.randomUUID(), 'uncaughtException', error.message, error.stack ?? '', Date.now() + ) + } catch { /* DB may not be ready */ } +}) + +process.on('unhandledRejection', (reason) => { + try { + const db = getDb() + const message = reason instanceof Error ? reason.message : String(reason) + const stack = reason instanceof Error ? reason.stack ?? '' : '' + db.prepare('INSERT INTO app_crashes (id, type, message, stack, created_at) VALUES (?,?,?,?,?)').run( + crypto.randomUUID(), 'unhandledRejection', message, stack, Date.now() + ) + } catch { /* DB may not be ready */ } +}) + +if (!SMOKE_TEST_MODE && !app.requestSingleInstanceLock()) { + app.quit() + process.exit(0) +} + +let win: BrowserWindow | null = null +let ipcRegistered = false +let startupUiRecovery: UiRecoveryResult | null = null let shutdownStarted = false const preload = path.join(__dirname, '../preload/index.mjs') const indexHtml = path.join(RENDERER_DIST, 'index.html') - + function cleanupRuntimeState() { killAllSessions() shutdownAllLspSessions() @@ -144,279 +144,279 @@ function shutdownApp() { app.quit() } } - -function registerAllIpc() { - if (ipcRegistered) return - ipcRegistered = true - - // Bootstrap provider registry before any handlers that resolve providers - ProviderRegistry.register(ClaudeProvider) - ProviderRegistry.register(CodexProvider) - - registerTerminalHandlers() - registerFilesystemHandlers() - registerProjectHandlers() - registerAgentHandlers() - registerClaudeHandlers() - registerCodexHandlers() - registerProviderHandlers() - registerActivityHandlers() - registerGitHandlers() - registerProcessHandlers() - registerEnvHandlers() - registerPortHandlers() - registerWalletHandlers() - registerProHandlers() - registerSettingsHandlers() - registerPluginHandlers() - registerTweetHandlers() - registerRecoveryHandlers() - registerEngineHandlers() - registerToolHandlers() - registerPumpFunHandlers() - registerBrowserHandlers() - registerDeployHandlers() - registerEmailHandlers() - registerImageHandlers() - registerAriaHandlers() - registerLaunchHandlers() - registerDashboardHandlers() - registerRegistryHandlers() - registerColosseumHandlers() - registerVaultHandlers() - registerValidatorHandlers() - registerPnlHandlers() + +function registerAllIpc() { + if (ipcRegistered) return + ipcRegistered = true + + // Bootstrap provider registry before any handlers that resolve providers + ProviderRegistry.register(ClaudeProvider) + ProviderRegistry.register(CodexProvider) + + registerTerminalHandlers() + registerFilesystemHandlers() + registerProjectHandlers() + registerAgentHandlers() + registerClaudeHandlers() + registerCodexHandlers() + registerProviderHandlers() + registerActivityHandlers() + registerGitHandlers() + registerProcessHandlers() + registerEnvHandlers() + registerPortHandlers() + registerWalletHandlers() + registerProHandlers() + registerSettingsHandlers() + registerPluginHandlers() + registerTweetHandlers() + registerRecoveryHandlers() + registerEngineHandlers() + registerToolHandlers() + registerPumpFunHandlers() + registerBrowserHandlers() + registerDeployHandlers() + registerEmailHandlers() + registerImageHandlers() + registerAriaHandlers() + registerLaunchHandlers() + registerDashboardHandlers() + registerRegistryHandlers() + registerColosseumHandlers() + registerVaultHandlers() + registerValidatorHandlers() + registerPnlHandlers() registerFeedbackHandlers() registerAgentStationHandlers() registerReplayHandlers() registerLspHandlers() - - // Window controls - ipcMain.on('window:minimize', () => win?.minimize()) - ipcMain.on('window:maximize', () => { - if (win?.isMaximized()) { - win.unmaximize() - } else { - win?.maximize() - } - }) + + // Window controls + ipcMain.on('window:minimize', () => win?.minimize()) + ipcMain.on('window:maximize', () => { + if (win?.isMaximized()) { + win.unmaximize() + } else { + win?.maximize() + } + }) ipcMain.on('window:close', () => shutdownApp()) - ipcMain.on('window:reload', () => { - if (!win) return - if (VITE_DEV_SERVER_URL) { - win.webContents.reloadIgnoringCache() - } else { - win.reload() - } - }) - ipcMain.handle('window:isMaximized', () => win?.isMaximized() ?? false) - - // Shell utilities - ipcMain.handle('shell:open-external', async (_event, url: string) => { - try { - const parsed = new URL(url) - if (parsed.protocol !== 'https:') return - if (parsed.username || parsed.password) return - await shell.openExternal(url) - } catch { /* invalid URL */ } - }) -} - -async function createWindow() { - if (SMOKE_TEST_MODE) console.log('[smoke] createWindow:start') - getDb() - registerAllIpc() - - // CSP headers only in production — in dev, Vite serves /@react-refresh and - // HMR websockets from localhost which a restrictive 'self' policy blocks - if (!VITE_DEV_SERVER_URL) { - session.defaultSession.webRequest.onHeadersReceived((details, callback) => { - callback({ - responseHeaders: { - ...details.responseHeaders, - 'Content-Security-Policy': ["default-src 'self' minipaint:; script-src 'self' minipaint: 'sha256-+1m5I+GGgMQpppazcRWmPjEueczyuTJO92jm308NkKc='; style-src 'self' 'unsafe-inline' minipaint:; img-src 'self' data: daemon-icon: minipaint:; worker-src 'self' blob: monaco-editor: minipaint:; connect-src 'self' https://*.anthropic.com https://*.helius-rpc.com https://price.jup.ag https://api.coingecko.com; font-src 'self' minipaint:; frame-src minipaint:; object-src 'none'"] - } - }) - }) - } - - // Monaco offline: serve node_modules/monaco-editor files via custom protocol - // Electron normalizes custom:///path → custom://path/ (host=path, pathname=/) so parse via URL. - protocol.handle('monaco-editor', (request) => { - const parsed = new URL(request.url) - const relativePath = decodeURIComponent( - (parsed.host + parsed.pathname).replace(/^\//, '').replace(/\/$/, '') - ) - const basePath = path.resolve(process.env.APP_ROOT, 'node_modules', 'monaco-editor', 'min') - const filePath = path.resolve(basePath, relativePath) - if (!filePath.startsWith(basePath + path.sep) && filePath !== basePath) { - return new Response('Forbidden: path traversal', { status: 403 }) - } - return net.fetch(pathToFileURL(filePath).toString()) - }) - - protocol.handle('daemon-icon', (request) => { - const encodedPath = request.url.replace(/^daemon-icon:\/\/\/?/, '') - const filePath = decodeURIComponent(encodedPath) - - // Restrict to image file extensions only - const ALLOWED_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.svg', '.ico', '.gif', '.webp', '.bmp', '.avif']) - const ext = path.extname(filePath).toLowerCase() - if (!ALLOWED_EXTENSIONS.has(ext)) { - return new Response('Forbidden: not an image file', { status: 403 }) - } - - // Only serve files from app resources, node_modules, or registered project paths - const resolved = path.resolve(filePath) - const appRoot = path.resolve(process.env.APP_ROOT) - const isAppResource = resolved.startsWith(appRoot + path.sep) - - let isProjectPath = false - try { - isProjectPath = isPathSafe(resolved) - } catch { - // DB not ready — only allow app resources - } - - if (!isAppResource && !isProjectPath) { - return new Response('Forbidden: path outside allowed directories', { status: 403 }) - } - - return net.fetch(pathToFileURL(resolved).toString()) - }) - - // miniPaint: serve vendor/miniPaint files via custom protocol. - // URL format: minipaint://app/ — "app" is a fixed host that keeps relative URLs working. - // e.g. minipaint://app/index.html loads index.html, its