"use strict"; const fs = require("fs"); const os = require("os"); const path = require("path"); const { execFile, execFileSync } = require("child_process"); const EXE_NAMES = process.platform === "win32" ? ["ChatGPT.exe", "Codex.exe", "OpenAI ChatGPT.exe", "ChatGPT Desktop.exe", "Codex Desktop.exe"] : ["ChatGPT", "Codex"]; const WIN_PROCESS_NAMES = EXE_NAMES.filter((n) => /\.exe$/i.test(n)); let lastDiscovery = null; const GLOBAL_DICTATION_HIDE_SCRIPT = ` set hiddenCount to 0 tell application "System Events" set targetProcesses to every process whose name is "ChatGPT" or name is "Codex" repeat with p in targetProcesses tell p repeat with w in windows try set titleText to title of w as text set roleText to role of w as text set subroleText to subrole of w as text set windowSize to size of w set widthValue to item 1 of windowSize as integer set heightValue to item 2 of windowSize as integer if titleText is "Codex" and roleText is "AXWindow" and subroleText is "AXDialog" and widthValue >= 500 and widthValue <= 900 and heightValue >= 60 and heightValue <= 120 then set position of w to {-10000, -10000} set hiddenCount to hiddenCount + 1 end if end try end repeat end tell end repeat end tell return hiddenCount `; function existsFile(p) { try { return !!p && fs.statSync(p).isFile(); } catch { return false; } } function isWindowsPackagedPath(p) { return process.platform === "win32" && /[\\/]WindowsApps[\\/]|[\\/]Program Files[\\/]WindowsApps[\\/]/i.test(String(p || "")); } function isOwnWindowsAppCandidate(name, appId) { const hay = [name, appId].filter(Boolean).join(" "); return /tokendun|codexmarket|codex-market|td工具箱|td toolbox|插件市场|技能市场/i.test(hay); } function isOpenAiDesktopCandidate(name, appId) { if (isOwnWindowsAppCandidate(name, appId)) return false; const n = String(name || "").trim(); const hay = [name, appId].filter(Boolean).join(" "); return /openai|chatgpt/i.test(hay) || /(^|[^a-z])codex([^a-z]|$)/i.test(n); } function addCandidate(out, seen, label, p, opts = {}) { if (!p) return; if (opts.launchType === "appId") { const appId = String(opts.appId || p || "").trim(); if (!appId || isOwnWindowsAppCandidate(opts.name || "", appId)) return; const key = "appid:" + appId.toLowerCase(); if (seen.has(key)) return; seen.add(key); out.push({ label, path: appId, appId, name: opts.name || "", launchType: "appId", packaged: true }); return; } let abs = ""; try { abs = path.resolve(p); } catch { return; } const key = process.platform === "win32" ? abs.toLowerCase() : abs; if (seen.has(key) || !existsFile(abs)) return; seen.add(key); out.push({ label, path: abs, launchType: opts.launchType || "exe", packaged: !!opts.packaged || isWindowsPackagedPath(abs), }); } function addChecked(report, p) { if (!report || !p) return; try { report.checkedPaths.push(path.resolve(p)); } catch {} } function shouldScanDir(name, depth) { if (!name) return false; if (/^(programs)$/i.test(name)) return true; if (/codex|chatgpt|openai/i.test(name)) return true; return depth === 0 && process.platform !== "win32"; } function scanDirForExecutables(base, out, seen, opts = {}) { const maxDepth = opts.maxDepth == null ? 4 : opts.maxDepth; const maxEntries = opts.maxEntries == null ? 400 : opts.maxEntries; const maxHits = opts.maxHits == null ? 12 : opts.maxHits; const report = opts.report || null; const targetNames = new Set(EXE_NAMES.map((n) => process.platform === "win32" ? n.toLowerCase() : n)); let visited = 0; if (report && base) report.scannedRoots.push(base); function walk(dir, depth) { if (!dir || depth > maxDepth || out.length >= maxHits || visited >= maxEntries) return; let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } visited += entries.length; for (const ent of entries) { if (out.length >= maxHits) return; const full = path.join(dir, ent.name); const entName = process.platform === "win32" ? ent.name.toLowerCase() : ent.name; if (ent.isFile() && targetNames.has(entName)) { addCandidate(out, seen, "ChatGPT/Codex 桌面程序(扫描)", full); continue; } if (ent.isDirectory() && depth < maxDepth && shouldScanDir(ent.name, depth)) { walk(full, depth + 1); } } } walk(base, 0); } function psJson(script, timeout = 3500) { if (process.platform !== "win32") return null; const shells = ["powershell.exe", "pwsh.exe"]; for (const shell of shells) { try { const out = execFileSync(shell, ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script], { encoding: "utf-8", windowsHide: true, timeout, }).trim(); if (!out) return null; return JSON.parse(out); } catch {} } return null; } function resolveWindowsShortcut(lnkPath) { if (process.platform !== "win32" || !lnkPath) return null; const script = [ "$p=$env:TD_LNK_PATH", "$s=(New-Object -ComObject WScript.Shell).CreateShortcut($p)", "$o=[pscustomobject]@{TargetPath=$s.TargetPath;Arguments=$s.Arguments;WorkingDirectory=$s.WorkingDirectory}", "$o|ConvertTo-Json -Compress", ].join(";"); const shells = ["powershell.exe", "pwsh.exe"]; for (const shell of shells) { try { const out = execFileSync(shell, ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script], { encoding: "utf-8", env: { ...process.env, TD_LNK_PATH: lnkPath }, windowsHide: true, timeout: 2500, }).trim(); if (!out) return null; return JSON.parse(out); } catch {} } return null; } function collectWindowsShortcuts(base, out, opts = {}) { if (process.platform !== "win32" || !base) return; const maxDepth = opts.maxDepth == null ? 5 : opts.maxDepth; const maxEntries = opts.maxEntries == null ? 900 : opts.maxEntries; const maxHits = opts.maxHits == null ? 24 : opts.maxHits; let visited = 0; function walk(dir, depth) { if (!dir || depth > maxDepth || out.length >= maxHits || visited >= maxEntries) return; let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } visited += entries.length; for (const ent of entries) { if (out.length >= maxHits) return; const full = path.join(dir, ent.name); if (ent.isFile() && /\.lnk$/i.test(ent.name) && /codex|chatgpt|openai/i.test(ent.name)) { out.push(full); continue; } if (ent.isDirectory() && depth < maxDepth && shouldScanDir(ent.name, depth)) { walk(full, depth + 1); } } } walk(base, 0); } function addSquirrelShortcutTargets(out, seen, report, shortcut, targetPath, args) { if (!targetPath || !/update\.exe$/i.test(path.basename(targetPath || ""))) return; const m = String(args || "").match(/--processStart\s+(?:"([^"]+)"|([^\s]+))/i); const exeName = (m && (m[1] || m[2])) || ""; if (!exeName || !/\.exe$/i.test(exeName)) return; const root = path.dirname(targetPath); let entries = []; try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch { return; } const appDirs = entries .filter((ent) => ent.isDirectory() && /^app-/i.test(ent.name)) .map((ent) => path.join(root, ent.name)) .sort() .reverse(); for (const dir of appDirs) { const exe = path.join(dir, exeName); addChecked(report, exe); addCandidate(out, seen, "ChatGPT/Codex 桌面程序(开始菜单快捷方式)", exe); } if (report) report.shortcutTargets.push({ shortcut, targetPath, args: args || "", inferred: exeName }); } function addWindowsShortcutCandidates(out, seen, report, roots) { const shortcuts = []; for (const root of roots.filter(Boolean)) collectWindowsShortcuts(root, shortcuts); for (const lnk of shortcuts) { if (report) report.shortcuts.push(lnk); const resolved = resolveWindowsShortcut(lnk); if (!resolved) continue; const targetPath = resolved.TargetPath || resolved.targetPath || ""; const args = resolved.Arguments || resolved.arguments || ""; if (report) report.shortcutTargets.push({ shortcut: lnk, targetPath, args }); if (targetPath && /\.exe$/i.test(targetPath) && !/update\.exe$/i.test(path.basename(targetPath))) { addChecked(report, targetPath); addCandidate(out, seen, "ChatGPT/Codex 桌面程序(开始菜单快捷方式)", targetPath); continue; } addSquirrelShortcutTargets(out, seen, report, lnk, targetPath, args); } } function addWindowsAppxCandidates(out, seen, report) { const script = [ "$startApps=Get-StartApps | Where-Object { $_.Name -match 'ChatGPT|Codex|OpenAI' -or $_.AppID -match 'ChatGPT|Codex|OpenAI' }", "$startRows=@()", "foreach($a in $startApps){ $startRows += [pscustomobject]@{Name=$a.Name;AppID=$a.AppID} }", "$pkgs=Get-AppxPackage | Where-Object { $_.Name -match 'ChatGPT|Codex|OpenAI' -or $_.PackageFullName -match 'ChatGPT|Codex|OpenAI' }", "$rows=@()", "$appIds=@()", "foreach($p in $pkgs){", " $loc=$p.InstallLocation", " if($loc){", " $manifestPath=Join-Path $loc 'AppxManifest.xml'", " if(Test-Path -LiteralPath $manifestPath){", " try{", " [xml]$manifest=Get-Content -LiteralPath $manifestPath -ErrorAction Stop", " foreach($app in @($manifest.Package.Applications.Application)){", " $id=[string]$app.Id", " if($id){ $appIds += [pscustomobject]@{Package=$p.Name;Family=$p.PackageFamilyName;AppID=($p.PackageFamilyName + '!' + $id);InstallLocation=$loc} }", " }", " }catch{}", " }", " $exes=Get-ChildItem -LiteralPath $loc -Recurse -Filter *.exe -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'ChatGPT|Codex|OpenAI' } | Select-Object -First 8", " foreach($e in $exes){ $rows += [pscustomobject]@{Package=$p.Name;Path=$e.FullName} }", " }", "}", "[pscustomobject]@{StartApps=$startRows;Packages=$rows;AppIds=$appIds}|ConvertTo-Json -Depth 5 -Compress", ].join(";"); const data = psJson(script, 6500); const startApps = data && data.StartApps ? (Array.isArray(data.StartApps) ? data.StartApps : [data.StartApps]) : []; const arr = data && data.Packages ? (Array.isArray(data.Packages) ? data.Packages : [data.Packages]) : []; const appIds = data && data.AppIds ? (Array.isArray(data.AppIds) ? data.AppIds : [data.AppIds]) : []; for (const app of startApps) { const appId = app && app.AppID ? String(app.AppID) : ""; const name = app && app.Name ? String(app.Name) : ""; if (!appId || !isOpenAiDesktopCandidate(name, appId)) continue; if (report) report.appx.push({ package: "StartApps", name: app.Name || "", appId }); addCandidate(out, seen, "ChatGPT/Codex 桌面程序(开始菜单 AppID)", appId, { launchType: "appId", appId, name, packaged: true, }); } for (const app of appIds) { const appId = app && app.AppID ? String(app.AppID) : ""; const name = app && app.Package ? String(app.Package) : ""; if (!appId || !isOpenAiDesktopCandidate(name, appId)) continue; if (report) report.appx.push({ package: name, family: app.Family || "", appId, manifest: true }); addCandidate(out, seen, "ChatGPT/Codex 桌面程序(MSIX AppID)", appId, { launchType: "appId", appId, name, packaged: true, }); } for (const row of arr) { const p = row && row.Path ? String(row.Path) : ""; if (report && row) report.appx.push({ package: row.Package || "", path: p }); addChecked(report, p); // Program Files\WindowsApps 里的包内 exe 通常不能被普通进程直接启动; // 只记录诊断,不当作启动候选,避免弹系统错误框。 if (p && !isWindowsPackagedPath(p)) { addCandidate(out, seen, "ChatGPT/Codex 桌面程序(Microsoft Store/MSIX)", p); } } } function desktopExecutableCandidates() { const home = os.homedir(); const out = []; const seen = new Set(); const report = { platform: process.platform, checkedPaths: [], scannedRoots: [], shortcuts: [], shortcutTargets: [], appx: [], candidates: [], }; if (process.platform === "darwin") { [ path.join(home, "Applications/ChatGPT.app/Contents/MacOS/ChatGPT"), "/Applications/ChatGPT.app/Contents/MacOS/ChatGPT", path.join(home, "Applications/Codex.app/Contents/MacOS/Codex"), "/Applications/Codex.app/Contents/MacOS/Codex", ].forEach((p) => { addChecked(report, p); addCandidate(out, seen, "ChatGPT/Codex 桌面程序", p); }); const sorted = sortCandidates(out); lastDiscovery = { ...report, candidates: sorted }; return sorted; } if (process.platform === "win32") { const local = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local"); const appdata = process.env.APPDATA || path.join(home, "AppData", "Roaming"); const programData = process.env.ProgramData || "C:\\ProgramData"; const pf = process.env.ProgramFiles || "C:\\Program Files"; const pf86 = process.env["ProgramFiles(x86)"] || ""; const windowsApps = path.join(local, "Microsoft", "WindowsApps"); const userStartMenu = path.join(appdata, "Microsoft", "Windows", "Start Menu", "Programs"); const systemStartMenu = path.join(programData, "Microsoft", "Windows", "Start Menu", "Programs"); const known = [ [local, "Programs", "ChatGPT", "ChatGPT.exe"], [local, "Programs", "OpenAI", "ChatGPT", "ChatGPT.exe"], [local, "Programs", "OpenAI ChatGPT", "ChatGPT.exe"], [local, "Programs", "ChatGPT Desktop", "ChatGPT.exe"], [local, "ChatGPT", "ChatGPT.exe"], [local, "OpenAI", "ChatGPT", "ChatGPT.exe"], [local, "OpenAI", "ChatGPT", "bin", "ChatGPT.exe"], [pf, "ChatGPT", "ChatGPT.exe"], [pf, "OpenAI", "ChatGPT", "ChatGPT.exe"], [pf, "OpenAI ChatGPT", "ChatGPT.exe"], pf86 && [pf86, "ChatGPT", "ChatGPT.exe"], pf86 && [pf86, "OpenAI", "ChatGPT", "ChatGPT.exe"], [local, "OpenAI", "Codex", "bin", "Codex.exe"], [local, "Programs", "Codex", "Codex.exe"], [local, "Programs", "codex", "Codex.exe"], [local, "Programs", "OpenAI Codex", "Codex.exe"], [local, "Programs", "Codex Desktop", "Codex.exe"], [local, "Codex", "Codex.exe"], [pf, "Codex", "Codex.exe"], [pf, "OpenAI Codex", "Codex.exe"], pf86 && [pf86, "Codex", "Codex.exe"], ].filter(Boolean); for (const parts of known) { const p = path.join(...parts); addChecked(report, p); addCandidate(out, seen, "ChatGPT/Codex 桌面程序", p); } for (const p of [path.join(windowsApps, "ChatGPT.exe"), path.join(windowsApps, "Codex.exe")]) { addChecked(report, p); if (existsFile(p)) { report.appx.push({ package: "WindowsApps alias", path: path.resolve(p) }); addCandidate(out, seen, "ChatGPT/Codex 桌面程序(WindowsApps 别名)", p, { launchType: "alias", packaged: true, }); } } addWindowsShortcutCandidates(out, seen, report, [userStartMenu, systemStartMenu]); addWindowsAppxCandidates(out, seen, report); for (const base of [path.join(local, "Programs"), local, pf, pf86, windowsApps].filter(Boolean)) { scanDirForExecutables(base, out, seen, { maxDepth: 5, maxEntries: 1600, maxHits: 24, report }); } const sorted = sortCandidates(out); lastDiscovery = { ...report, candidates: sorted }; return sorted; } lastDiscovery = { ...report, candidates: out }; return out; } function sortCandidates(items) { return [...items].sort((a, b) => scoreCandidate(b.path) - scoreCandidate(a.path)); } function scoreCandidate(p) { let score = 0; if (/chatgpt/i.test(path.basename(p))) score += 100; if (/codex/i.test(path.basename(p))) score += 50; if (isWindowsPackagedPath(p)) score -= 1000; if (/localappdata|AppData[\\/]Local|Applications/i.test(p)) score += 10; if (/programs/i.test(p)) score += 5; return score; } function codexExecutable() { const first = desktopExecutableCandidates().find((item) => !item.packaged); return first ? first.path : ""; } function discoveryReport() { return lastDiscovery || { platform: process.platform, checkedPaths: [], scannedRoots: [], shortcuts: [], shortcutTargets: [], appx: [], candidates: [], }; } function discoverySummary() { const r = discoveryReport(); const parts = []; if (r.checkedPaths && r.checkedPaths.length) parts.push(`固定路径 ${r.checkedPaths.length} 个`); if (r.scannedRoots && r.scannedRoots.length) parts.push(`目录扫描 ${r.scannedRoots.length} 处`); if (r.shortcuts && r.shortcuts.length) parts.push(`快捷方式 ${r.shortcuts.length} 个`); if (r.appx && r.appx.length) parts.push(`Store/MSIX 线索 ${r.appx.length} 个`); if (r.candidates && r.candidates.length) parts.push(`命中 ${r.candidates.length} 个`); return parts.join(","); } function codexRunning() { try { if (process.platform === "darwin") { return execFileSync("pgrep", ["-f", "(Codex.app/Contents/MacOS/Codex|ChatGPT.app/Contents/MacOS/ChatGPT)"], { encoding: "utf-8" }).trim().length > 0; } if (process.platform === "win32") { return windowsProcessRows().length > 0; } } catch { return false; } return false; } function execFileText(file, args, options = {}) { return new Promise((resolve, reject) => { execFile(file, args, { encoding: "utf-8", windowsHide: true, timeout: options.timeout === undefined ? 2200 : Math.max(1, Number(options.timeout) || 1), signal: options.signal, env: options.env || process.env, }, (error, stdout) => { if (error) { error.stdout = stdout || error.stdout || ""; reject(error); return; } resolve(String(stdout || "")); }); }); } function isCodexProcessCommand(command) { const text = String(command || "").replace(/\s+/g, " ").trim(); if (!text || isOwnWindowsAppCandidate(text, "")) return false; return /(?:^|[\\/])(?:Codex|ChatGPT)\.app[\\/]/i.test(text) || /(?:^|[\\/\s])(?:Codex|ChatGPT)(?:\.exe)?(?:\s|$)/i.test(text) || /WindowsApps[\\/]OpenAI\.(?:Codex|CodexBeta|ChatGPT-Desktop)_/i.test(text); } function hideGlobalDictationWindows(options = {}) { if (process.platform !== "darwin") { return Promise.resolve({ ok: false, status: "unsupported-platform", hidden: 0, error: "native global dictation window cleanup is only available on macOS", }); } const run = options.execFile || execFile; return new Promise((resolve) => { run("osascript", ["-e", GLOBAL_DICTATION_HIDE_SCRIPT], { encoding: "utf-8", windowsHide: true, timeout: Math.max(500, Number(options.timeoutMs) || 5000), signal: options.signal, }, (error, stdout, stderr) => { const output = String(stdout || "").trim(); const hidden = /^\d+$/.test(output) ? Number(output) : 0; if (error) { resolve({ ok: false, status: "native-command-failed", hidden, error: String((error && error.message) || stderr || error), }); return; } resolve({ ok: true, status: hidden ? "hidden" : "not-found", hidden, error: "", }); }); }); } async function debugPortOwner(port, options = {}) { const value = Number(port); if (!Number.isInteger(value) || value < 1 || value > 65535) { return { verified: false, owned: false, reason: "invalid-port", processes: [] }; } const signal = options.signal; const totalTimeoutMs = Math.max(1, Number(options.timeoutMs) || 5000); const deadline = Date.now() + totalTimeoutMs; const commandOptions = (maxMs) => { if (signal && signal.aborted) { throw signal.reason instanceof Error ? signal.reason : new Error("debug port owner check aborted"); } const remaining = deadline - Date.now(); if (remaining <= 0) { const error = new Error("debug port owner check timed out"); error.code = "ETIMEDOUT"; throw error; } return { timeout: Math.max(1, Math.min(maxMs, remaining)), signal, }; }; if (process.platform === "darwin") { try { const output = await execFileText("lsof", [ "-nP", "-a", `-iTCP:${value}`, "-sTCP:LISTEN", "-Fp", ], commandOptions(2200)); const pids = [...new Set( output.split(/\r?\n/).filter((line) => /^p\d+$/.test(line)).map((line) => line.slice(1)), )]; const processes = []; for (const pid of pids) { try { const command = (await execFileText( "ps", ["-p", pid, "-o", "command="], commandOptions(2200), )).trim(); processes.push({ pid: Number(pid), command }); } catch {} } return { verified: true, owned: processes.some((item) => isCodexProcessCommand(item.command)), reason: processes.length ? "process-inspected" : "listener-not-found", processes, }; } catch (e) { return { verified: false, owned: false, reason: String((e && e.message) || e), processes: [], }; } } if (process.platform === "win32") { const script = [ "$port=[int]$env:TD_CODEX_DEBUG_PORT", "$rows=@()", "Get-NetTCPConnection -State Listen -LocalPort $port -ErrorAction SilentlyContinue | ForEach-Object {", " $p=Get-CimInstance Win32_Process -Filter ('ProcessId=' + $_.OwningProcess) -ErrorAction SilentlyContinue", " if($p){ $rows += [pscustomobject]@{ProcessId=$p.ProcessId;Name=$p.Name;ExecutablePath=$p.ExecutablePath;CommandLine=$p.CommandLine} }", "}", "$rows|ConvertTo-Json -Depth 4 -Compress", ].join(";"); for (const shell of ["powershell.exe", "pwsh.exe"]) { try { const output = (await execFileText(shell, [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script, ], { ...commandOptions(3500), env: { ...process.env, TD_CODEX_DEBUG_PORT: String(value) }, })).trim(); const parsed = output ? JSON.parse(output) : []; const rows = Array.isArray(parsed) ? parsed : [parsed]; const processes = rows.map((row) => ({ pid: Number(row.ProcessId) || 0, command: [row.Name, row.ExecutablePath, row.CommandLine].filter(Boolean).join(" "), })); return { verified: true, owned: processes.some((item) => isCodexProcessCommand(item.command)), reason: processes.length ? "process-inspected" : "listener-not-found", processes, }; } catch {} } return { verified: false, owned: false, reason: "process-inspection-failed", processes: [] }; } return { verified: false, owned: false, reason: "unsupported-platform", processes: [] }; } function windowsProcessRows() { if (process.platform !== "win32") return []; const escapedNames = WIN_PROCESS_NAMES.map((name) => "'" + name.replace(/'/g, "''") + "'").join(","); const script = [ `$names=@(${escapedNames})`, "$pkg='WindowsApps\\\\OpenAI\\.(Codex|CodexBeta|ChatGPT-Desktop)_'", "$rows=Get-CimInstance Win32_Process | Where-Object { $names -contains $_.Name -or ($_.ExecutablePath -match $pkg) -or ($_.CommandLine -match $pkg) } | ForEach-Object {", " [pscustomobject]@{Name=$_.Name;ProcessId=$_.ProcessId;ExecutablePath=$_.ExecutablePath;CommandLine=$_.CommandLine}", "}", "$rows|ConvertTo-Json -Depth 4 -Compress", ].join(";"); const data = psJson(script, 4500); if (!data) return []; return Array.isArray(data) ? data : [data]; } function windowsProcessDiagnostics(port) { const rows = windowsProcessRows(); const portRe = new RegExp("--remote-debugging-port(?:=|\\s+)" + String(port || ""), "i"); const hasDebugArg = rows.some((row) => portRe.test(String((row && row.CommandLine) || ""))); const summary = rows.map((row) => { const cmd = String((row && row.CommandLine) || "").replace(/\s+/g, " ").slice(0, 260); const name = (row && row.Name) || "process"; const pid = (row && row.ProcessId) || "?"; return `${name}#${pid}${cmd ? " " + cmd : ""}`; }).join(" | "); return { rows, hasDebugArg, summary }; } function waitForCodexExit(maxMs = 9000) { return new Promise((resolve) => { const t0 = Date.now(); const tick = () => { if (!codexRunning()) { resolve(true); return; } if (Date.now() - t0 >= maxMs) { resolve(false); return; } setTimeout(tick, 300); }; tick(); }); } function killCodex() { return new Promise((resolve) => { if (process.platform === "darwin") { execFile("osascript", ["-e", 'tell application "Codex" to quit', "-e", 'tell application "ChatGPT" to quit'], () => { execFile("pkill", ["-f", "(Codex.app/Contents/MacOS/Codex|ChatGPT.app/Contents/MacOS/ChatGPT)"], () => { waitForCodexExit(12000).then((exited) => resolve({ exited })); }); }); return; } if (process.platform === "win32") { let pending = WIN_PROCESS_NAMES.length; const done = () => { pending -= 1; if (pending <= 0) { waitForCodexExit(9000).then((ok) => { if (!ok) { try { execFileSync("powershell.exe", [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "$pkg='WindowsApps\\\\OpenAI\\.(Codex|CodexBeta|ChatGPT-Desktop)_'; Get-CimInstance Win32_Process | Where-Object { @('ChatGPT.exe','Codex.exe','OpenAI ChatGPT.exe','ChatGPT Desktop.exe','Codex Desktop.exe') -contains $_.Name -or ($_.ExecutablePath -match $pkg) -or ($_.CommandLine -match $pkg) } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }", ], { windowsHide: true, timeout: 3500 }); } catch {} } waitForCodexExit(5000).then((exited) => resolve({ exited })); }); } }; for (const name of WIN_PROCESS_NAMES) execFile("taskkill", ["/IM", name, "/T", "/F"], done); return; } resolve({ exited: true }); }); } module.exports = { desktopExecutableCandidates, codexExecutable, isWindowsPackagedPath, discoveryReport, discoverySummary, codexRunning, debugPortOwner, hideGlobalDictationWindows, windowsProcessDiagnostics, killCodex, };