feat: 发布热更 1.5.94,修复中转历史会话并支持一键清空

This commit is contained in:
mac
2026-09-11 09:06:41 +08:00
parent 0446d09b3b
commit a74576c3e1
28 changed files with 3217 additions and 155 deletions
+201
View File
@@ -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,
};