2026-08-16 12:45:32 +08:00
"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 ;
2026-09-11 09:06:41 +08:00
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
` ;
2026-08-16 12:45:32 +08:00
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 ;
}
2026-09-11 09:06:41 +08:00
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 : [] };
}
2026-08-16 12:45:32 +08:00
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 ,
2026-09-11 09:06:41 +08:00
debugPortOwner ,
hideGlobalDictationWindows ,
2026-08-16 12:45:32 +08:00
windowsProcessDiagnostics ,
killCodex ,
};