AG X v2.0.3 - Antigravity fork with multi-provider support
Features: - Welcome screen on first run (provider choice before LS starts) - 15+ AI providers (Google Gemini, OpenAI, Anthropic, DeepSeek, Ollama, etc.) - Provider config syncs to endpoints.json for translation proxy - Built-in Node.js translation proxy for non-native backends - Auto-update support, tray integration, URI scheme handler
This commit is contained in:
587
dist/main.js
vendored
Normal file
587
dist/main.js
vendored
Normal file
@@ -0,0 +1,587 @@
|
||||
"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;
|
||||
};
|
||||
})();
|
||||
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 main_1 = __importDefault(require("electron-log/main"));
|
||||
const ipcHandlers_1 = require("./ipcHandlers");
|
||||
const fs = __importStar(require("fs"));
|
||||
const crypto = __importStar(require("crypto"));
|
||||
const readline = __importStar(require("readline"));
|
||||
const utils_1 = require("./utils");
|
||||
const languageServer_1 = require("./languageServer");
|
||||
const updater_1 = require("./updater");
|
||||
const constants_1 = require("./constants");
|
||||
const tray_1 = require("./tray");
|
||||
const storage_1 = require("./storage");
|
||||
const paths_1 = require("./paths");
|
||||
const menu_1 = require("./menu");
|
||||
const customScheme_1 = require("./customScheme");
|
||||
const settingsService_1 = require("./services/settingsService");
|
||||
const ideInstall_1 = require("./ideInstall");
|
||||
const providerService_1 = require("./services/providerService");
|
||||
const apiProxy_1 = require("./services/apiProxy");
|
||||
const providerSettings_1 = require("./providerSettings");
|
||||
const path_2 = require("path");
|
||||
const fs_1 = require("fs");
|
||||
const gotTheLock = electron_1.app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
electron_1.app.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// State
|
||||
// ---------------------------------------------------------------------------
|
||||
let storageManager;
|
||||
let settingsService;
|
||||
let hasStartedMainApplication = false;
|
||||
let isQuitting = false;
|
||||
let providerService;
|
||||
let apiProxy;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config
|
||||
// ---------------------------------------------------------------------------
|
||||
// Driven by ELECTRON_OZONE_PLATFORM_HINT=headless env var.
|
||||
// This single env var both prevents GTK from crashing (Electron 33+)
|
||||
// and tells our code to skip createWindow().
|
||||
const HEADLESS = process.env.ELECTRON_OZONE_PLATFORM_HINT === 'headless';
|
||||
// When set, skip LS startup and load this URL directly (for dev iteration).
|
||||
const DEV_URL = process.env.DEV_URL;
|
||||
if (HEADLESS) {
|
||||
electron_1.app.commandLine.appendSwitch('ozone-platform', 'headless');
|
||||
electron_1.app.commandLine.appendSwitch('headless');
|
||||
electron_1.app.commandLine.appendSwitch('disable-gpu');
|
||||
electron_1.app.commandLine.appendSwitch('no-sandbox');
|
||||
}
|
||||
if (!electron_1.app.commandLine.hasSwitch('remote-debugging-port')) {
|
||||
electron_1.app.commandLine.appendSwitch('remote-debugging-port', '0');
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Application Lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
let pendingDeepLink = null;
|
||||
function handleDeepLink(url) {
|
||||
const wins = electron_1.BrowserWindow.getAllWindows();
|
||||
// This block handles deep links when windows are already open.
|
||||
if (wins.length > 0) {
|
||||
if (wins[0].isMinimized()) {
|
||||
wins[0].restore();
|
||||
}
|
||||
wins[0].show();
|
||||
wins[0].focus();
|
||||
electron_1.app.focus({ steal: true });
|
||||
wins[0].webContents.send('deep-link', url);
|
||||
}
|
||||
else {
|
||||
pendingDeepLink = url;
|
||||
}
|
||||
}
|
||||
electron_1.app.on('second-instance', (event, commandLine) => {
|
||||
const wins = electron_1.BrowserWindow.getAllWindows();
|
||||
if (wins.length > 0) {
|
||||
if (wins[0].isMinimized()) {
|
||||
wins[0].restore();
|
||||
}
|
||||
wins[0].show();
|
||||
wins[0].focus();
|
||||
electron_1.app.focus({ steal: true });
|
||||
}
|
||||
const url = commandLine.find((arg) => arg.startsWith('ag-x://'));
|
||||
if (url) {
|
||||
handleDeepLink(url);
|
||||
}
|
||||
});
|
||||
(0, customScheme_1.registerCustomSchemes)();
|
||||
// Register as default protocol client for deep linking
|
||||
const PROTOCOL = 'ag-x';
|
||||
if (!electron_1.app.isDefaultProtocolClient(PROTOCOL)) {
|
||||
electron_1.app.setAsDefaultProtocolClient(PROTOCOL);
|
||||
}
|
||||
electron_1.app.on('open-url', (event, url) => {
|
||||
event.preventDefault();
|
||||
handleDeepLink(url);
|
||||
});
|
||||
/**
|
||||
* App entry point. Runs once Electron has finished initializing.
|
||||
* Validates the LS binary, frees the port if needed, spawns the LS,
|
||||
* and opens the initial browser window.
|
||||
*/
|
||||
electron_1.app
|
||||
.whenReady()
|
||||
.then(async () => {
|
||||
// Initialize electron-log and override console
|
||||
main_1.default.initialize();
|
||||
Object.assign(console, main_1.default.functions);
|
||||
const storagePath = (0, paths_1.getAppStoragePath)();
|
||||
storageManager = new storage_1.StorageManager(storagePath, settingsService_1.DEFAULTS);
|
||||
settingsService = new settingsService_1.SettingsService(storageManager);
|
||||
// Initialize AI Provider Service
|
||||
providerService = new providerService_1.ProviderService(storageManager);
|
||||
console.log(`[Provider] Active provider: ${providerService.getActiveProvider()}`);
|
||||
|
||||
// Start API proxy if a non-Gemini provider is active
|
||||
if (providerService.needsProxy()) {
|
||||
apiProxy = new apiProxy_1.ApiProxy(providerService);
|
||||
apiProxy.start();
|
||||
console.log(`[Provider] API proxy started for ${providerService.getActiveProvider()}`);
|
||||
}
|
||||
|
||||
// Handle deep link URL from command line arguments (All platforms)
|
||||
const deepLinkFromArg = process.argv.find((arg) => arg.startsWith('ag-x://'));
|
||||
if (deepLinkFromArg) {
|
||||
console.log('Launched with deep link:', deepLinkFromArg);
|
||||
pendingDeepLink = deepLinkFromArg;
|
||||
}
|
||||
// Register IPC handlers
|
||||
(0, ipcHandlers_1.registerIpcHandlers)(storageManager);
|
||||
// Register provider IPC handlers
|
||||
electron_1.ipcMain.handle('provider:get-active', async () => {
|
||||
return providerService.getActiveProvider();
|
||||
});
|
||||
electron_1.ipcMain.handle('provider:get-all', async () => {
|
||||
return providerService.getAllProviders();
|
||||
});
|
||||
electron_1.ipcMain.handle('provider:open-settings', async () => {
|
||||
(0, providerSettings_1.openProviderSettings)(providerService);
|
||||
});
|
||||
|
||||
electron_1.ipcMain.handle('deep-link:get-stored', () => {
|
||||
const link = pendingDeepLink;
|
||||
pendingDeepLink = null; // Clear after read
|
||||
return link;
|
||||
});
|
||||
// Handle requests coming from custom schemes
|
||||
(0, customScheme_1.registerCustomSchemeHandlers)();
|
||||
// Set About panel options with LS CL
|
||||
const cl = await (0, languageServer_1.getLsCL)();
|
||||
electron_1.app.setAboutPanelOptions({
|
||||
applicationName: 'AG X',
|
||||
applicationVersion: electron_1.app.getVersion(),
|
||||
version: cl || undefined,
|
||||
});
|
||||
// Pre-onboarding: check if we should offer to re-install the IDE.
|
||||
// This runs before the LS starts so we can show a standalone wizard.
|
||||
if (!HEADLESS) {
|
||||
await (0, ideInstall_1.maybeShowIdeInstallWizard)(storageManager);
|
||||
}
|
||||
if (DEV_URL) {
|
||||
console.log('Starting in dev mode with URL:', DEV_URL);
|
||||
(0, utils_1.createWindow)(DEV_URL);
|
||||
hasStartedMainApplication = true;
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(languageServer_1.LS_BINARY)) {
|
||||
const msg = `language_server binary not found at:\n${languageServer_1.LS_BINARY}\n\nPlease build set a valid location.`;
|
||||
if (HEADLESS) {
|
||||
console.error('ERROR:', msg);
|
||||
}
|
||||
else {
|
||||
await electron_1.dialog.showErrorBox('Binary not found', msg);
|
||||
}
|
||||
electron_1.app.quit();
|
||||
return;
|
||||
}
|
||||
const csrf = crypto.randomUUID();
|
||||
console.log(`Starting app (v${electron_1.app.getVersion()}) with dynamic port…`);
|
||||
// Check if first run BEFORE starting LS so we can configure provider first
|
||||
const isFirstRun = !fs_1.existsSync(providerService.configPath);
|
||||
if (isFirstRun && !HEADLESS) {
|
||||
console.log('[Welcome] First run detected — showing provider choice screen');
|
||||
await showWelcomeScreen('about:blank');
|
||||
console.log('[Welcome] User selected provider:', providerService.getActiveProvider());
|
||||
// Sync to endpoints.json (for Google, the handler already set the endpoint;
|
||||
// for custom, the settings save handler synced. This is a safety sync.)
|
||||
if (providerService.getActiveProvider() !== 'google_gemini') {
|
||||
syncProviderToEndpoints(providerService);
|
||||
}
|
||||
}
|
||||
let handle;
|
||||
const targetPort = Number(process.env.JETSKI_LS_PORT) || constants_1.DYNAMIC_PORT;
|
||||
try {
|
||||
handle = await (0, languageServer_1.startAndMonitorLanguageServer)(targetPort, csrf, {
|
||||
headless: HEADLESS,
|
||||
onPortChanged: (newPort) => {
|
||||
const newUrl = `${constants_1.WINDOW_ORIGIN}:${newPort}/`;
|
||||
console.log(`[Auto-Restart] Port changed! Reloading all windows with URL: ${newUrl}`);
|
||||
(0, languageServer_1.setupLocalCertTrust)();
|
||||
if (!HEADLESS) {
|
||||
const windows = electron_1.BrowserWindow.getAllWindows();
|
||||
for (const win of windows) {
|
||||
if (win.getTitle() === 'Welcome to AG X' || win.getTitle() === 'AI Provider Settings') continue;
|
||||
void win.loadURL(newUrl);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
const msg = err.message;
|
||||
if (HEADLESS) {
|
||||
console.error('Startup failed:', msg);
|
||||
}
|
||||
else {
|
||||
await electron_1.dialog.showErrorBox('Startup failed', msg);
|
||||
}
|
||||
electron_1.app.quit();
|
||||
return;
|
||||
}
|
||||
const url = `${constants_1.WINDOW_ORIGIN}:${handle.port}/`;
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log(` Local: ${url}`);
|
||||
console.log(` LS Logs: ${(0, paths_1.getLsLogPath)()}`);
|
||||
console.log(` Electron Logs: ${main_1.default.transports.file.getFile().path}`);
|
||||
console.log(` Provider: ${providerService.getActiveProvider()}`);
|
||||
console.log('='.repeat(60) + '\n');
|
||||
if (HEADLESS) {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
rl.on('line', (line) => {
|
||||
const lsProc = (0, languageServer_1.getLsProcess)();
|
||||
if (lsProc && lsProc.stdin) {
|
||||
lsProc.stdin.write(line + '\n');
|
||||
console.log('-> Forwarded input to Language Server.');
|
||||
}
|
||||
else {
|
||||
console.log('Language Server process is not running.');
|
||||
}
|
||||
});
|
||||
}
|
||||
// Initial window — opened once after the LS has successfully started.
|
||||
if (!HEADLESS) {
|
||||
(0, menu_1.setupApplicationMenu)(url);
|
||||
// Create the main window (welcome screen already completed above for first run)
|
||||
(0, utils_1.createWindow)(url);
|
||||
if (electron_1.app.dock) {
|
||||
const dockMenu = electron_1.Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'New Window',
|
||||
click: () => (0, utils_1.createWindow)(url),
|
||||
},
|
||||
]);
|
||||
electron_1.app.dock.setMenu(dockMenu);
|
||||
}
|
||||
(0, tray_1.createTray)([
|
||||
{
|
||||
id: 'running-agents',
|
||||
label: 'No agents running',
|
||||
enabled: false,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: `Open ${electron_1.app.getName()}`,
|
||||
click: () => (0, utils_1.showOrCreateWindow)((0, languageServer_1.getLsPort)()),
|
||||
},
|
||||
{
|
||||
label: 'AI Provider Settings',
|
||||
click: () => (0, providerSettings_1.openProviderSettings)(providerService),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Quit',
|
||||
click: () => {
|
||||
// Triggers 'before-quit' to run graceful cleanup without confirmation.
|
||||
electron_1.app.quit();
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
// Start checking for app updates.
|
||||
(0, updater_1.initAutoUpdater)(HEADLESS);
|
||||
hasStartedMainApplication = true;
|
||||
})
|
||||
.catch(() => {
|
||||
hasStartedMainApplication = true;
|
||||
});
|
||||
|
||||
/**
|
||||
* Shows the first-run welcome screen for provider selection.
|
||||
* Returns a Promise that resolves when the user has made their choice.
|
||||
*/
|
||||
|
||||
// === AG X: Sync provider config to ~/.codex/endpoints.json ===
|
||||
function syncProviderToEndpoints(providerService) {
|
||||
try {
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const pathMod = require('path');
|
||||
const home = os.homedir();
|
||||
const endpointsPath = pathMod.join(home, '.codex', 'endpoints.json');
|
||||
const activePath = pathMod.join(home, '.codex', '.active-endpoint.json');
|
||||
|
||||
const activeProvider = providerService.getActiveProvider();
|
||||
const providerConfig = providerService.getProviderConfig(activeProvider);
|
||||
|
||||
if (!providerConfig) {
|
||||
console.log('[Sync] No provider config to sync');
|
||||
return;
|
||||
}
|
||||
|
||||
let endpointsData = { default: '', endpoints: [] };
|
||||
if (fs.existsSync(endpointsPath)) {
|
||||
try {
|
||||
endpointsData = JSON.parse(fs.readFileSync(endpointsPath, 'utf8'));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const endpointName = 'AG X: ' + (providerConfig.name || activeProvider);
|
||||
const existingIdx = endpointsData.endpoints.findIndex(e => e.name === endpointName);
|
||||
|
||||
const endpoint = {
|
||||
name: endpointName,
|
||||
backend_type: providerConfig.backendType || 'openai-compat',
|
||||
base_url: providerConfig.apiUrl || '',
|
||||
api_key: providerConfig.apiKey || '',
|
||||
default_model: providerConfig.model || providerConfig.defaultModel || '',
|
||||
models: providerConfig.models || [],
|
||||
provider_preset: 'AG X',
|
||||
};
|
||||
|
||||
if (existingIdx >= 0) {
|
||||
endpointsData.endpoints[existingIdx] = endpoint;
|
||||
} else {
|
||||
endpointsData.endpoints.push(endpoint);
|
||||
}
|
||||
|
||||
endpointsData.default = endpointName;
|
||||
fs.writeFileSync(endpointsPath, JSON.stringify(endpointsData, null, 2), 'utf-8');
|
||||
fs.writeFileSync(activePath, JSON.stringify({ active: endpointName }, null, 2), 'utf-8');
|
||||
console.log('[Sync] Provider synced to endpoints.json:', endpointName);
|
||||
} catch (err) {
|
||||
console.error('[Sync] Failed to sync provider to endpoints:', err);
|
||||
}
|
||||
}
|
||||
// === End sync function ===
|
||||
|
||||
function showWelcomeScreen(mainUrl) {
|
||||
return new Promise((resolve) => {
|
||||
const welcomeWin = new electron_1.BrowserWindow({
|
||||
width: 720,
|
||||
height: 560,
|
||||
title: 'Welcome to AG X',
|
||||
resizable: false,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
backgroundColor: '#0a0a1a',
|
||||
show: true,
|
||||
center: true,
|
||||
frame: false,
|
||||
titleBarStyle: 'hidden',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: false,
|
||||
},
|
||||
});
|
||||
welcomeWin.loadFile(path_2.join(__dirname, 'provider', 'welcome.html')).catch((err) => {
|
||||
console.error('[Welcome] Failed to load welcome.html:', err);
|
||||
});
|
||||
welcomeWin.webContents.on('did-finish-load', () => {
|
||||
console.log('[Welcome] Welcome screen loaded successfully');
|
||||
});
|
||||
welcomeWin.webContents.on('did-fail-load', (_event, code, desc) => {
|
||||
console.error('[Welcome] Failed to load:', code, desc);
|
||||
});
|
||||
console.log('[Welcome] Window created, title:', welcomeWin.getTitle());
|
||||
welcomeWin.on('closed', () => {
|
||||
// If closed without choosing, default to Google Gemini
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
providerService.setActiveProvider('google_gemini');
|
||||
providerService.saveConfig();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
let resolved = false;
|
||||
electron_1.ipcMain.once('welcome:choice', (_event, data) => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
if (data.provider === 'google_gemini') {
|
||||
console.log('[Welcome] User chose Google Gemini (OAuth)');
|
||||
providerService.setActiveProvider('google_gemini');
|
||||
providerService.saveConfig();
|
||||
// Set Google as active endpoint in ~/.codex/
|
||||
try {
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const pathMod = require('path');
|
||||
const activePath = pathMod.join(os.homedir(), '.codex', '.active-endpoint.json');
|
||||
const endpointsPath = pathMod.join(os.homedir(), '.codex', 'endpoints.json');
|
||||
let epData = { default: '', endpoints: [] };
|
||||
if (fs.existsSync(endpointsPath)) {
|
||||
try { epData = JSON.parse(fs.readFileSync(endpointsPath, 'utf8')); } catch(e) {}
|
||||
}
|
||||
let googleEp = epData.endpoints.find(e => e.name === 'Google Gemini');
|
||||
if (!googleEp) {
|
||||
googleEp = {
|
||||
name: 'Google Gemini',
|
||||
backend_type: 'gemini-native',
|
||||
base_url: 'https://daily-cloudcode-pa.sandbox.googleapis.com',
|
||||
api_key: '',
|
||||
default_model: 'gemini-2.5-pro',
|
||||
models: ['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.0-flash'],
|
||||
provider_preset: 'AG X',
|
||||
};
|
||||
epData.endpoints.push(googleEp);
|
||||
}
|
||||
epData.default = 'Google Gemini';
|
||||
fs.writeFileSync(endpointsPath, JSON.stringify(epData, null, 2), 'utf-8');
|
||||
fs.writeFileSync(activePath, JSON.stringify({ active: 'Google Gemini' }, null, 2), 'utf-8');
|
||||
console.log('[Welcome] Google Gemini endpoint set as active');
|
||||
} catch(e) { console.error('[Welcome] Failed to set Google endpoint:', e); }
|
||||
welcomeWin.close();
|
||||
// Don't createWindow here — LS hasn't started yet, main window opens after LS starts
|
||||
} else {
|
||||
console.log('[Welcome] User chose custom provider — opening settings');
|
||||
welcomeWin.close();
|
||||
// Open provider settings and wait for user to save
|
||||
(0, providerSettings_1.openProviderSettings)(providerService);
|
||||
// Listen for the settings save to complete before continuing
|
||||
const settingsSaveHandler = async () => {
|
||||
console.log('[Welcome] Provider settings saved, syncing and continuing startup');
|
||||
syncProviderToEndpoints(providerService);
|
||||
electron_1.ipcMain.removeListener('provider:settings-saved', settingsSaveHandler);
|
||||
resolve();
|
||||
};
|
||||
electron_1.ipcMain.on('provider:settings-saved', settingsSaveHandler);
|
||||
return; // Don't resolve yet - wait for settings save
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when all windows have been closed.
|
||||
* On macOS the app (and LS) stay alive so the user can re-open via the tray.
|
||||
* On all other platforms, shut down the LS and quit.
|
||||
*/
|
||||
electron_1.app.on('window-all-closed', async () => {
|
||||
if (isQuitting) {
|
||||
return;
|
||||
}
|
||||
if (!hasStartedMainApplication) {
|
||||
return;
|
||||
}
|
||||
// Show provider settings on first launch if no API key configured
|
||||
const activeProviderType = providerService.getActiveProvider();
|
||||
const activeProviderConfig = providerService.getProviderConfig(activeProviderType);
|
||||
if (activeProviderConfig && activeProviderConfig.requiresApiKey && !activeProviderConfig.apiKey) {
|
||||
console.log('[Provider] No API key configured for active provider, showing settings...');
|
||||
// Delay slightly so the main window renders first
|
||||
setTimeout(() => {
|
||||
(0, providerSettings_1.openProviderSettings)(providerService);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
const runInBackground = await settingsService.getSetting(settingsService_1.SettingKey.RUN_IN_BACKGROUND);
|
||||
if (!runInBackground) {
|
||||
// Triggers 'before-quit' to run graceful cleanup without confirmation.
|
||||
electron_1.app.quit();
|
||||
}
|
||||
else {
|
||||
electron_1.app.dock?.hide();
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Fired just before the app quits (e.g. Cmd+Q on macOS, or after
|
||||
* window-all-closed on non-macOS). Ensures the LS is terminated even if
|
||||
* window-all-closed didn't handle it (e.g. on macOS quit via menu).
|
||||
*/
|
||||
electron_1.app.on('before-quit', async (event) => {
|
||||
if (isQuitting) {
|
||||
return;
|
||||
}
|
||||
if (!utils_1.showQuitConfirmation) {
|
||||
event.preventDefault();
|
||||
isQuitting = true;
|
||||
// Destroy all windows to terminate renderers and release keep-alive sockets
|
||||
const windows = electron_1.BrowserWindow.getAllWindows();
|
||||
for (const win of windows) {
|
||||
win.destroy();
|
||||
}
|
||||
// Stop API proxy
|
||||
if (apiProxy) {
|
||||
apiProxy.stop();
|
||||
}
|
||||
|
||||
// Close all active connections and kill the language server in parallel
|
||||
await Promise.all([
|
||||
electron_1.session.defaultSession.closeAllConnections().catch((err) => {
|
||||
console.error('Failed to close session connections:', err);
|
||||
}),
|
||||
(0, languageServer_1.killLanguageServer)(),
|
||||
]);
|
||||
electron_1.app.quit();
|
||||
return;
|
||||
}
|
||||
// Show a confirmation dialog before quitting
|
||||
event.preventDefault();
|
||||
const win = electron_1.BrowserWindow.getFocusedWindow() || electron_1.BrowserWindow.getAllWindows()[0];
|
||||
const options = {
|
||||
type: 'question',
|
||||
buttons: ['Cancel', 'Quit'],
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
title: 'Confirm Quit',
|
||||
message: 'Are you sure you want to quit?',
|
||||
detail: 'There may be agents or background tasks running.',
|
||||
};
|
||||
(0, utils_1.setShowQuitConfirmation)(false);
|
||||
if (win) {
|
||||
void electron_1.dialog.showMessageBox(win, options).then((result) => {
|
||||
if (result.response === 1) {
|
||||
// Quit - this will retrigger 'before-quit'
|
||||
electron_1.app.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Fired when the app is re-activated (e.g. clicking the dock icon on macOS).
|
||||
* Re-opens a window if none are currently open.
|
||||
*/
|
||||
electron_1.app.on('activate', () => {
|
||||
// On Mac, re-open a window when the user clicks the dock
|
||||
// icon and no windows are open.
|
||||
if (!HEADLESS && electron_1.BrowserWindow.getAllWindows().length === 0) {
|
||||
const url = DEV_URL ?? `${constants_1.WINDOW_ORIGIN}:${(0, languageServer_1.getLsPort)()}/`;
|
||||
(0, utils_1.createWindow)(url);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user