diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock index 4ea40fda..3ade767d 100644 --- a/packages/desktop/src-tauri/Cargo.lock +++ b/packages/desktop/src-tauri/Cargo.lock @@ -2847,7 +2847,7 @@ dependencies = [ [[package]] name = "openchamber-desktop" -version = "1.3.3" +version = "1.3.4" dependencies = [ "anyhow", "axum", diff --git a/packages/desktop/src-tauri/src/opencode_manager.rs b/packages/desktop/src-tauri/src/opencode_manager.rs index 9bb62f5e..bcd3f0e2 100644 --- a/packages/desktop/src-tauri/src/opencode_manager.rs +++ b/packages/desktop/src-tauri/src/opencode_manager.rs @@ -108,7 +108,7 @@ impl OpenCodeManager { is_ready: Arc::new(AtomicBool::new(false)), shutting_down: Arc::new(AtomicBool::new(false)), http_client: Client::builder() - .timeout(Duration::from_secs(5)) + .timeout(Duration::from_secs(2)) .build() .unwrap(), } @@ -406,11 +406,9 @@ impl OpenCodeManager { while tokio::time::Instant::now() < deadline { let api_prefix = self.api_prefix(); - // Try /health, /config, /agent endpoints + // Try /config, /agent endpoints match self.check_endpoints(port, &api_prefix).await { Ok(()) => { - // Once ready, attempt to detect and persist the API prefix for proxying - let _ = self.detect_api_prefix().await; return Ok(()); } Err(e) => { @@ -431,23 +429,20 @@ impl OpenCodeManager { async fn check_endpoints(&self, port: u16, prefix: &str) -> Result<()> { let base_url = format!("http://127.0.0.1:{port}{prefix}"); - // Check /health - let health_url = format!("{base_url}/health"); - let health_resp = self.http_client.get(&health_url).send().await?; - if !health_resp.status().is_success() { - return Err(anyhow!("/health returned {}", health_resp.status())); - } - - // Check /config let config_url = format!("{base_url}/config"); - let config_resp = self.http_client.get(&config_url).send().await?; + let agent_url = format!("{base_url}/agent"); + + let (config_resp, agent_resp) = tokio::join!( + self.http_client.get(&config_url).send(), + self.http_client.get(&agent_url).send() + ); + + let config_resp = config_resp?; if !config_resp.status().is_success() { return Err(anyhow!("/config returned {}", config_resp.status())); } - // Check /agent - let agent_url = format!("{base_url}/agent"); - let agent_resp = self.http_client.get(&agent_url).send().await?; + let agent_resp = agent_resp?; if !agent_resp.status().is_success() { return Err(anyhow!("/agent returned {}", agent_resp.status())); } @@ -497,8 +492,7 @@ impl OpenCodeManager { // SIGKILL let _ = child.kill().await; - // Wait up to 5 seconds for hard kill - match timeout(Duration::from_secs(5), child.wait()).await { + match timeout(Duration::from_secs(2), child.wait()).await { Ok(_) => { info!("[desktop:opencode] exited after SIGKILL"); } diff --git a/packages/vscode/src/opencode.ts b/packages/vscode/src/opencode.ts index cac0cf3e..ae0f1264 100644 --- a/packages/vscode/src/opencode.ts +++ b/packages/vscode/src/opencode.ts @@ -182,16 +182,10 @@ async function checkHealth(apiUrl: string, quick = false): Promise { const controller = new AbortController(); const timeoutMs = quick ? 1500 : 3000; const timeout = setTimeout(() => controller.abort(), timeoutMs); - + const normalized = apiUrl.replace(/\/+$/, ''); const candidates: string[] = [`${normalized}/config`]; - // Some deployments expose a /health endpoint (not guaranteed for OpenCode). - if (!quick) { - const healthUrl = normalized.endsWith('/api') ? `${normalized.slice(0, -4)}/health` : `${normalized}/health`; - candidates.push(healthUrl); - } - for (const target of candidates) { try { const response = await fetch(target, { @@ -227,7 +221,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo let lastStartAt: number | null = null; let lastConnectedAt: number | null = null; let lastExitCode: number | null = null; - + // Port detection state (like desktop) let detectedPort: number | null = null; let portWaiters: Array<(port: number) => void> = []; @@ -235,12 +229,12 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo // OpenCode API prefix detection (some versions serve under /api) let apiPrefix: string = ''; let apiPrefixDetected = false; - + // Check if user configured a specific API URL const config = vscode.workspace.getConfiguration('openchamber'); const configuredApiUrl = config.get('apiUrl') || ''; const useConfiguredUrl = configuredApiUrl && configuredApiUrl.trim().length > 0; - + // Parse configured URL to extract port if specified let configuredPort: number | null = null; if (useConfiguredUrl) { @@ -367,7 +361,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo function setDetectedPort(port: number) { if (detectedPort !== port) { detectedPort = port; - + // Notify all waiters const waiters = portWaiters; portWaiters = []; @@ -429,7 +423,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo async function waitForReady(apiUrl: string, timeoutMs: number): Promise { const deadline = Date.now() + timeoutMs; - + while (Date.now() < deadline) { // Use quick health check during startup for faster response if (await checkHealth(apiUrl, true)) { @@ -437,7 +431,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo } await new Promise(r => setTimeout(r, READY_CHECK_INTERVAL_MS)); } - + return false; } @@ -461,7 +455,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo } return; } - + const healthy = await checkHealth(apiUrl); if (healthy && status !== 'connected') { setStatus('connected'); @@ -529,7 +523,7 @@ export function createOpenCodeManager(_context: vscode.ExtensionContext): OpenCo lastExitCode = null; const spawnCwd = workingDirectory || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || os.homedir(); - + // Use port 0 for dynamic assignment unless user configured a specific port const portArg = configuredPort !== null ? configuredPort.toString() : '0'; diff --git a/packages/web/server/index.js b/packages/web/server/index.js index e9bed59a..30beab02 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -1356,29 +1356,18 @@ async function waitForOpenCodeReady(timeoutMs = 20000, intervalMs = 400) { for (const prefix of prefixes) { try { const normalizedPrefix = normalizeApiPrefix(prefix); - const healthPromise = fetch(buildOpenCodeUrl('/health', normalizedPrefix), { - method: 'GET', - headers: { Accept: 'application/json' } - }).catch((error) => error); const configPromise = fetch(buildOpenCodeUrl('/config', normalizedPrefix), { method: 'GET', headers: { Accept: 'application/json' } }).catch((error) => error); - const [healthResult, configResult] = await Promise.all([healthPromise, configPromise]); + const agentPromise = fetch(buildOpenCodeUrl('/agent', normalizedPrefix), { + method: 'GET', + headers: { Accept: 'application/json' } + }).catch((error) => error); - if (healthResult instanceof Error) { - lastError = healthResult; - } else if (healthResult.ok) { - const healthData = await healthResult.json().catch(() => null); - if (healthData && healthData.isOpenCodeReady === false) { - lastError = new Error('OpenCode health indicates not ready'); - continue; - } - } else { - lastError = new Error(`OpenCode health endpoint responded with status ${healthResult.status}`); - } + const [configResult, agentResult] = await Promise.all([configPromise, agentPromise]); if (configResult instanceof Error) { lastError = configResult; @@ -1402,30 +1391,21 @@ async function waitForOpenCodeReady(timeoutMs = 20000, intervalMs = 400) { setDetectedOpenCodeApiPrefix(normalizedPrefix); } + if (agentResult instanceof Error) { + lastError = agentResult; + continue; + } + + if (!agentResult.ok) { + lastError = new Error(`Agent endpoint responded with status ${agentResult.status}`); + continue; + } + + await agentResult.json().catch(() => []); + const effectivePrefix = detectedPrefix !== null ? detectedPrefix : normalizedPrefix; - - const agentResponse = await fetch( - buildOpenCodeUrl('/agent', effectivePrefix), - { - method: 'GET', - headers: { Accept: 'application/json' } - } - ).catch((error) => error); - - if (agentResponse instanceof Error) { - lastError = agentResponse; - continue; - } - - if (!agentResponse.ok) { - lastError = new Error(`Agent endpoint responded with status ${agentResponse.status}`); - continue; - } - - await agentResponse.json().catch(() => []); - if (detectedPrefix === null) { - const agentPrefix = extractApiPrefixFromUrl(agentResponse.url, '/agent'); + const agentPrefix = extractApiPrefixFromUrl(agentResult.url, '/agent'); if (agentPrefix !== null) { setDetectedOpenCodeApiPrefix(agentPrefix); } else if (normalizedPrefix) {