11import { execCli } from '@main/utils/childProcess' ;
22import { getAppDataPath } from '@main/utils/pathDecoder' ;
33import { safeSendToRenderer } from '@main/utils/safeWebContentsSend' ;
4- import { getCachedShellEnv } from '@main/utils/shellEnv' ;
4+ import { getCachedShellEnv , resolveInteractiveShellEnvBestEffort } from '@main/utils/shellEnv' ;
55import { getErrorMessage } from '@shared/utils/errorHandling' ;
66import { createLogger } from '@shared/utils/logger' ;
77import { createHash , randomUUID } from 'crypto' ;
@@ -22,6 +22,7 @@ const MAX_TARBALL_BYTES = 250 * 1024 * 1024;
2222const MAX_BINARY_BYTES = 350 * 1024 * 1024 ;
2323const FETCH_TIMEOUT_MS = 60_000 ;
2424const VERSION_TIMEOUT_MS = 10_000 ;
25+ const PATH_SHELL_ENV_TIMEOUT_MS = 1_500 ;
2526
2627interface NpmPackageMetadata {
2728 name ?: string ;
@@ -134,9 +135,15 @@ function splitPathEnv(pathValue: string | undefined): string[] {
134135 . filter ( Boolean ) ;
135136}
136137
137- function resolvePathOpenCodeBinary ( ) : string | null {
138+ function resolvePathOpenCodeBinary (
139+ additionalEnvSources : ( NodeJS . ProcessEnv | null | undefined ) [ ] = [ ]
140+ ) : string | null {
138141 const shellEnv = getCachedShellEnv ( ) ?? { } ;
139- const pathEntries = [ ...splitPathEnv ( shellEnv . PATH ) , ...splitPathEnv ( process . env . PATH ) ] ;
142+ const pathEntries = [
143+ ...additionalEnvSources . flatMap ( ( env ) => splitPathEnv ( env ?. PATH ) ) ,
144+ ...splitPathEnv ( shellEnv . PATH ) ,
145+ ...splitPathEnv ( process . env . PATH ) ,
146+ ] ;
140147 const seen = new Set < string > ( ) ;
141148 for ( const entry of pathEntries ) {
142149 const normalizedEntry = path . resolve ( entry ) ;
@@ -154,6 +161,57 @@ function resolvePathOpenCodeBinary(): string | null {
154161 return null ;
155162}
156163
164+ type OpenCodeBinaryVersionProbe =
165+ | { ok : true ; version : string | null }
166+ | { ok : false ; error : string } ;
167+
168+ async function probeOpenCodeBinaryVersion ( binaryPath : string ) : Promise < OpenCodeBinaryVersionProbe > {
169+ try {
170+ const { stdout } = await execCli ( binaryPath , [ '--version' ] , {
171+ timeout : VERSION_TIMEOUT_MS ,
172+ windowsHide : true ,
173+ } ) ;
174+ return { ok : true , version : stdout . trim ( ) || null } ;
175+ } catch ( error ) {
176+ return { ok : false , error : getErrorMessage ( error ) } ;
177+ }
178+ }
179+
180+ async function resolvePathOpenCodeBinaryWithBestEffortEnv (
181+ options : { shellEnvTimeoutMs ?: number } = { }
182+ ) : Promise < string | null > {
183+ const cachedCandidate = resolvePathOpenCodeBinary ( ) ;
184+ if ( cachedCandidate ) {
185+ return cachedCandidate ;
186+ }
187+
188+ const shellEnv = await resolveInteractiveShellEnvBestEffort ( {
189+ timeoutMs : options . shellEnvTimeoutMs ?? PATH_SHELL_ENV_TIMEOUT_MS ,
190+ fallbackEnv : process . env ,
191+ } ) ;
192+ return resolvePathOpenCodeBinary ( [ shellEnv ] ) ;
193+ }
194+
195+ async function resolveVerifiedPathOpenCodeBinaryPath (
196+ options : { shellEnvTimeoutMs ?: number } = { }
197+ ) : Promise < string | null > {
198+ const binaryPath = await resolvePathOpenCodeBinaryWithBestEffortEnv ( options ) ;
199+ if ( ! binaryPath ) {
200+ return null ;
201+ }
202+
203+ return ( await probeOpenCodeBinaryVersion ( binaryPath ) ) . ok ? binaryPath : null ;
204+ }
205+
206+ export async function resolveVerifiedOpenCodeRuntimeBinaryPath (
207+ options : { shellEnvTimeoutMs ?: number } = { }
208+ ) : Promise < string | null > {
209+ return (
210+ ( await resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath ( ) ) ??
211+ ( await resolveVerifiedPathOpenCodeBinaryPath ( options ) )
212+ ) ;
213+ }
214+
157215function isLinuxMuslRuntime ( ) : boolean {
158216 if ( process . platform !== 'linux' ) {
159217 return false ;
@@ -466,31 +524,27 @@ export class OpenCodeRuntimeInstallerService {
466524 }
467525
468526 private async getPathStatus ( ) : Promise < OpenCodeRuntimeStatus > {
469- const binaryPath = resolvePathOpenCodeBinary ( ) ;
527+ const binaryPath = await resolvePathOpenCodeBinaryWithBestEffortEnv ( ) ;
470528 if ( ! binaryPath ) {
471529 return { installed : false , source : 'missing' , state : 'idle' } ;
472530 }
473- try {
474- const { stdout } = await execCli ( binaryPath , [ '--version' ] , {
475- timeout : VERSION_TIMEOUT_MS ,
476- windowsHide : true ,
477- } ) ;
478- return {
479- installed : true ,
480- binaryPath,
481- version : stdout . trim ( ) || undefined ,
482- source : 'path' ,
483- state : 'ready' ,
484- } ;
485- } catch ( error ) {
531+ const version = await probeOpenCodeBinaryVersion ( binaryPath ) ;
532+ if ( ! version . ok ) {
486533 return {
487534 installed : false ,
488535 binaryPath,
489536 source : 'path' ,
490537 state : 'failed' ,
491- error : getErrorMessage ( error ) ,
538+ error : version . error ,
492539 } ;
493540 }
541+ return {
542+ installed : true ,
543+ binaryPath,
544+ version : version . version ?? undefined ,
545+ source : 'path' ,
546+ state : 'ready' ,
547+ } ;
494548 }
495549
496550 private async installInternal ( ) : Promise < OpenCodeRuntimeStatus > {
0 commit comments