v2.0.5: Fix E2E flow - proxy, welcome screen, provider sync
Critical fixes: - Translation proxy now uses system Node.js (not Electron binary) - Removed duplicate proxy start causing port conflicts - Added port availability check before spawning proxy - Fixed welcome:choice double resolve() - Fixed settings.html close using deprecated remote - Fixed translationProxy /v1 for openai-compat backends - Proxy no longer detached/unref - properly tracked as child - SingletonLock cleanup on startup Verified E2E: - Welcome screen on first run ✓ - Provider selection works ✓ - Settings save + sync ✓ - Translation proxy starts correctly ✓ - LS connects to proxy ✓ - --ag-reset works ✓
This commit is contained in:
41
dist/languageServer.js
vendored
41
dist/languageServer.js
vendored
@@ -187,7 +187,7 @@ function setupNodeModules(env, modules) {
|
||||
*/
|
||||
const os = require('os');
|
||||
function ensureProxyStarted() {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise(async (resolve) => {
|
||||
if (_proxyProcess) {
|
||||
resolve();
|
||||
return;
|
||||
@@ -234,16 +234,45 @@ function ensureProxyStarted() {
|
||||
models: modelList.map(m => ({ id: m, object: "model", created: 1700000000, owned_by: endpoint.name }))
|
||||
};
|
||||
fs.writeFileSync(activeConfigPath, JSON.stringify(pcfg, null, 2), 'utf8');
|
||||
// Check if proxy is already running on port 48080
|
||||
const net = require('net');
|
||||
const proxyPortTest = await new Promise((resolve) => {
|
||||
const tester = net.createConnection(48080, '127.0.0.1');
|
||||
tester.on('connect', () => { tester.destroy(); resolve(true); });
|
||||
tester.on('error', () => { tester.destroy(); resolve(false); });
|
||||
setTimeout(() => { tester.destroy(); resolve(false); }, 1000);
|
||||
});
|
||||
if (proxyPortTest) {
|
||||
console.log('[AG X] Translation proxy already running on port 48080, skipping spawn');
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
console.log('[AG X] Starting built-in Node.js translation proxy for:', endpoint.name);
|
||||
// Use Electron's built-in Node.js to run our translation proxy
|
||||
const proxyScript = path_1.default.join(__dirname, 'services', 'translationProxy.js');
|
||||
const nodeBin = process.execPath;
|
||||
_proxyProcess = (0, child_process_1.spawn)(nodeBin, ['--no-sandbox', proxyScript], {
|
||||
stdio: 'ignore',
|
||||
detached: true,
|
||||
// Use system node (not Electron's process.execPath which starts a GUI)
|
||||
const nodeBin = process.execPath.includes('electron') || process.execPath.includes('AG-X')
|
||||
? (require('child_process').execSync('which node 2>/dev/null || echo /usr/bin/node').toString().trim())
|
||||
: process.execPath;
|
||||
_proxyProcess = (0, child_process_1.spawn)(nodeBin, [proxyScript], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
env: { ...process.env }
|
||||
});
|
||||
_proxyProcess.unref();
|
||||
_proxyProcess.stdout?.on('data', (d) => {
|
||||
for (const line of d.toString().split('\n')) {
|
||||
if (line.trim()) console.log('[Proxy]', line.trim());
|
||||
}
|
||||
});
|
||||
_proxyProcess.stderr?.on('data', (d) => {
|
||||
for (const line of d.toString().split('\n')) {
|
||||
if (line.trim()) console.log('[Proxy:err]', line.trim());
|
||||
}
|
||||
});
|
||||
_proxyProcess.on('exit', (code) => {
|
||||
console.log('[AG X] Translation proxy exited with code:', code);
|
||||
_proxyProcess = null;
|
||||
});
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 500);
|
||||
|
||||
10
dist/main.js
vendored
10
dist/main.js
vendored
@@ -156,12 +156,9 @@ electron_1.app
|
||||
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()}`);
|
||||
}
|
||||
// NOTE: Translation proxy is now started by ensureProxyStarted() in languageServer.js
|
||||
// which reads ~/.codex/endpoints.json (synced by syncProviderToEndpoints) and spawns
|
||||
// the Node.js translation proxy on port 48080. No duplicate proxy needed here.
|
||||
|
||||
// Handle deep link URL from command line arguments (All platforms)
|
||||
const deepLinkFromArg = process.argv.find((arg) => arg.startsWith('ag-x://'));
|
||||
@@ -469,6 +466,7 @@ function showWelcomeScreen(mainUrl) {
|
||||
} 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
|
||||
return; // Don't fall through to resolve() again
|
||||
} else {
|
||||
console.log('[Welcome] User chose custom provider — opening settings');
|
||||
welcomeWin.close();
|
||||
|
||||
4
dist/provider/settings.html
vendored
4
dist/provider/settings.html
vendored
@@ -585,9 +585,7 @@ async function saveSettings() {
|
||||
updateStatus();
|
||||
// Auto-close settings window after a brief delay
|
||||
setTimeout(() => {
|
||||
const currentWindow = require('electron').remote?.getCurrentWindow();
|
||||
if (currentWindow) currentWindow.close();
|
||||
// Fallback: close via ipcRenderer
|
||||
// Close via IPC — providerSettings.js handles the actual close
|
||||
require('electron').ipcRenderer.send('provider:close-settings');
|
||||
}, 1500);
|
||||
} else {
|
||||
|
||||
7
dist/services/apiProxy.js
vendored
7
dist/services/apiProxy.js
vendored
@@ -70,8 +70,11 @@ class ApiProxy {
|
||||
console.log(`[ApiProxy] Starting built-in Node.js translation proxy on port ${this.port}`);
|
||||
// Use Electron's Node.js binary to run our translation proxy
|
||||
const proxyScript = path.join(__dirname, 'translationProxy.js');
|
||||
const nodeBin = process.execPath;
|
||||
this.proxyProcess = child_process.spawn(nodeBin, ['--no-sandbox', proxyScript], {
|
||||
// Use system node (not Electron's process.execPath which starts a GUI)
|
||||
const nodeBin = process.execPath.includes('electron') || process.execPath.includes('AG-X')
|
||||
? child_process.execSync('which node 2>/dev/null || echo /usr/bin/node').toString().trim()
|
||||
: process.execPath;
|
||||
this.proxyProcess = child_process.spawn(nodeBin, [proxyScript], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
env: { ...process.env },
|
||||
|
||||
4
dist/services/translationProxy.js
vendored
4
dist/services/translationProxy.js
vendored
@@ -78,6 +78,10 @@ function applyConfig(cfg) {
|
||||
PORT = cfg.port || 48080;
|
||||
BACKEND = cfg.backend_type || "openai-compat";
|
||||
TARGET_URL = cfg.target_url || "http://localhost:11434/v1";
|
||||
// Ensure /v1 suffix for openai-compat backends
|
||||
if (BACKEND === "openai-compat" && !TARGET_URL.endsWith("/v1")) {
|
||||
TARGET_URL = TARGET_URL.replace(/\/+$/, "") + "/v1";
|
||||
}
|
||||
API_KEY = cfg.api_key || "";
|
||||
OAUTH_PROVIDER = cfg.oauth_provider || "";
|
||||
REASONING_ENABLED = cfg.reasoning_enabled !== undefined ? cfg.reasoning_enabled : true;
|
||||
|
||||
Reference in New Issue
Block a user