fix: Codex 自动压缩不触发 —— 接管时链接模型目录(绝对路径)进 config.toml (hot 1.5.83)
This commit is contained in:
@@ -0,0 +1,537 @@
|
||||
"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;
|
||||
|
||||
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 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,
|
||||
windowsProcessDiagnostics,
|
||||
killCodex,
|
||||
};
|
||||
Reference in New Issue
Block a user