import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { DesktopNativeTools } from "./native-tools.js"; const execute = promisify(execFile); const PIPE_PREFIX = "\\\\.\\pipe\\codex-browser-use-"; const DISCOVERY_TIMEOUT_MS = 15_000; // Match the pipe owner to the installed package, not just an executable name. const WINDOWS_DISCOVERY = String.raw` $ErrorActionPreference = 'Stop' Add-Type -TypeDefinition @' using System; using System.Runtime.InteropServices; public static class PiCodexDesktopPipe { [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] public static extern IntPtr CreateFile(string name, uint access, uint share, IntPtr security, uint creation, uint flags, IntPtr template); [DllImport("kernel32.dll")] public static extern bool GetNamedPipeServerProcessId(IntPtr handle, out uint processId); [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr handle); } '@ $desktopExecutables = @() foreach ($package in @(Get-AppxPackage -Name OpenAI.Codex)) { foreach ($name in @('ChatGPT.exe', 'Codex.exe')) { $desktopExecutables += Join-Path $package.InstallLocation ('app\' + $name) } } $desktopProcessIds = [System.Collections.Generic.HashSet[uint32]]::new() foreach ($process in @(Get-CimInstance Win32_Process -Filter "Name='ChatGPT.exe' OR Name='Codex.exe'")) { if ($desktopExecutables -contains $process.ExecutablePath -and $process.CommandLine -notmatch '(?:^|\s)--type(?:=|\s)') { [void]$desktopProcessIds.Add([uint32]$process.ProcessId) } } function Test-DesktopPipe([string] $pipePath) { $handle = [PiCodexDesktopPipe]::CreateFile($pipePath, 0, 3, [IntPtr]::Zero, 3, 0, [IntPtr]::Zero) if ($handle -eq [IntPtr](-1)) { return $false } try { $ownerId = [uint32]0 if (-not [PiCodexDesktopPipe]::GetNamedPipeServerProcessId($handle, [ref]$ownerId)) { return $false } return $desktopProcessIds.Contains($ownerId) } finally { [void][PiCodexDesktopPipe]::CloseHandle($handle) } } $preferred = $env:PI_CODEX_DESKTOP_PREFERRED_ENDPOINT $matches = @() if ($preferred -and (Test-DesktopPipe $preferred)) { $matches += $preferred } foreach ($pipe in @(Get-ChildItem -LiteralPath '\\.\pipe\' | Where-Object Name -Like 'codex-browser-use-*')) { $pipePath = '\\.\pipe\' + $pipe.Name if (Test-DesktopPipe $pipePath) { $matches += $pipePath } } ConvertTo-Json -InputObject @($matches) -Compress `; const discoverWindowsEndpoints = async ( preferred: string | undefined ): Promise => { const { stdout } = await execute( "powershell.exe", [ "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(WINDOWS_DISCOVERY, "utf16le").toString("base64"), ], { env: { ...process.env, PI_CODEX_DESKTOP_PREFERRED_ENDPOINT: preferred ?? "", }, maxBuffer: 64 * 1024, timeout: DISCOVERY_TIMEOUT_MS, windowsHide: true, } ); return stdout; }; export interface DesktopEndpointDiscovery { platform: string; discover: (preferred: string | undefined) => Promise; supportsAppTools: (endpoint: string) => Promise; } const supportsAppTools = async (endpoint: string): Promise => { const tools = new DesktopNativeTools(endpoint, { timeoutMs: 1000 }); try { const catalog = await tools.listTools(); return catalog.some( (tool) => tool.namespace === "codex_app" && tool.name === "list_threads" ); } catch { return false; } finally { tools.close(); } }; const runtimeDiscovery: DesktopEndpointDiscovery = { discover: discoverWindowsEndpoints, platform: process.platform, supportsAppTools, }; const appToolEndpoints = async ( candidates: readonly string[], discovery: DesktopEndpointDiscovery ): Promise => { const result: string[] = []; const deadline = Date.now() + DISCOVERY_TIMEOUT_MS; for (let offset = 0; offset < candidates.length; offset += 8) { if (Date.now() >= deadline) { throw new Error("Codex desktop capability discovery timed out"); } // eslint-disable-next-line no-await-in-loop -- Bound simultaneous pipe connections and the complete read-only preflight. const batch = await Promise.all( candidates .slice(offset, offset + 8) .map(async (endpoint) => (await discovery.supportsAppTools(endpoint)) ? endpoint : null ) ); result.push(...batch.filter((endpoint) => endpoint !== null)); } return result; }; export const resolveDesktopEndpoint = async ( preferred: string | undefined, discovery: DesktopEndpointDiscovery = runtimeDiscovery ): Promise => { if (discovery.platform !== "win32") { if (!preferred) { throw new Error( "Desktop native tools require an explicit endpoint on this platform" ); } return preferred; } if (preferred && !preferred.startsWith(PIPE_PREFIX)) { throw new Error( "Desktop native tools endpoint is not a Codex app-tools named pipe" ); } let stdout: string; try { stdout = await discovery.discover(preferred); } catch { throw new Error( "Could not verify the running Codex desktop app-tools endpoint" ); } let endpoints: unknown; try { endpoints = JSON.parse(stdout); } catch { throw new Error("Invalid Codex desktop endpoint discovery result"); } if ( !Array.isArray(endpoints) || endpoints.length > 2048 || !endpoints.every( (endpoint): endpoint is string => typeof endpoint === "string" && endpoint.startsWith(PIPE_PREFIX) ) ) { throw new Error("Invalid Codex desktop endpoint discovery result"); } const candidates = [...new Set(endpoints)]; if ( preferred && candidates.includes(preferred) && (await discovery.supportsAppTools(preferred)) ) { return preferred; } const unique = await appToolEndpoints( candidates.filter((candidate) => candidate !== preferred), discovery ); const [endpoint] = unique; if (unique.length !== 1 || endpoint === undefined) { throw new Error( unique.length === 0 ? "No verified Codex desktop app-tools endpoint is available" : "Multiple Codex desktop app-tools endpoints are available; select an explicit endpoint" ); } return endpoint; };