feat: 发布热更 1.5.94,修复中转历史会话并支持一键清空
This commit is contained in:
@@ -12,6 +12,32 @@ const EXE_NAMES = process.platform === "win32"
|
||||
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();
|
||||
@@ -443,6 +469,179 @@ function codexRunning() {
|
||||
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(",");
|
||||
@@ -532,6 +731,8 @@ module.exports = {
|
||||
discoveryReport,
|
||||
discoverySummary,
|
||||
codexRunning,
|
||||
debugPortOwner,
|
||||
hideGlobalDictationWindows,
|
||||
windowsProcessDiagnostics,
|
||||
killCodex,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user