* feat(ctrl-w): add ps-node + windows-process-tree + tsx deps for close-priority feature * fix(ctrl-w): drop ps-node dep and add windows-process-tree to asarUnpack Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): add ptyProcessTree bridge with per-platform child-process enumeration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ctrl-w): ptyProcessTree uses args= for full command + warns on pid overwrite - Replace `comm=` with `args=` in defaultListPosix so the full command line is captured on both macOS (BSD ps) and Linux (GNU ps), avoiding the 15-char TASK_COMM_LEN truncation. - Add console.warn in registerPid when the same sessionId is overwritten with a different pid, making the race condition visible in logs. - Add test: registerPid warns exactly once on a pid change, not on a same-pid re-registration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): register local PTY pid with ptyProcessTree on spawn/exit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ctrl-w): unregister pids in cleanupAllSessions to match per-delete invariant Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): add IPC handlers for pty child processes and confirm-close dialog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ctrl-w): guard BrowserWindow.fromWebContents null and document dialog dismiss contract Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): expose ptyGetChildProcesses and confirmCloseBusy on window.netcatty Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): add i18n strings for close-busy-terminal dialog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): add resolveCloseIntent pure function with 8 unit tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): expose handleCloseSidePanel via ref to App.tsx Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): wire resolveCloseIntent + local-shell busy confirmation into closeTab hotkey Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ctrl-w): add re-entrancy guard, aggregate busy count, sync sidebar ref, dedupe intent branches Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ctrl-w): auto-close workspace when its last session is closed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ctrl-w): sidebar close wins over focused terminal in priority chain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ctrl-w): sidebar priority applies to single-session tabs too, not just workspaces Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ctrl-w): compute empty-workspace auto-close outside setSessions updater Addresses Codex P2 on #739: React 18+ does not guarantee updater execution timing under concurrent scheduling. Moving the decision outside the updater makes the microtask queue deterministic. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
2.7 KiB
JavaScript
90 lines
2.7 KiB
JavaScript
const { execFile } = require("node:child_process");
|
|
|
|
function createProcessTree({ platform, listPosix, listWindows } = {}) {
|
|
const sessionPidMap = new Map();
|
|
|
|
function registerPid(sessionId, pid) {
|
|
if (!sessionId || typeof pid !== "number") return;
|
|
if (sessionPidMap.has(sessionId) && sessionPidMap.get(sessionId) !== pid) {
|
|
console.warn(
|
|
`[ptyProcessTree] sessionId "${sessionId}" already registered with pid ${sessionPidMap.get(sessionId)}; overwriting with ${pid}.`,
|
|
);
|
|
}
|
|
sessionPidMap.set(sessionId, pid);
|
|
}
|
|
|
|
function unregisterPid(sessionId) {
|
|
sessionPidMap.delete(sessionId);
|
|
}
|
|
|
|
async function getChildProcesses(sessionId) {
|
|
const pid = sessionPidMap.get(sessionId);
|
|
if (!pid) return [];
|
|
if (platform === "win32") {
|
|
return listWindows ? listWindows(pid) : [];
|
|
}
|
|
return listPosix ? listPosix(pid) : [];
|
|
}
|
|
|
|
return { registerPid, unregisterPid, getChildProcesses };
|
|
}
|
|
|
|
function defaultListPosix(ppid) {
|
|
return new Promise((resolve) => {
|
|
// `ps -A -o pid=,ppid=,args=` works on both BSD (macOS) and GNU (Linux).
|
|
// `args=` shows the full command line (not truncated like `comm=`).
|
|
// The trailing `=` on each column suppresses the header row.
|
|
execFile("ps", ["-A", "-o", "pid=,ppid=,args="], (err, stdout) => {
|
|
if (err || typeof stdout !== "string") return resolve([]);
|
|
const out = [];
|
|
for (const line of stdout.split("\n")) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed) continue;
|
|
const m = trimmed.match(/^(\d+)\s+(\d+)\s+(.+)$/);
|
|
if (!m) continue;
|
|
if (Number(m[2]) !== ppid) continue;
|
|
out.push({ pid: Number(m[1]), command: m[3].trim() });
|
|
}
|
|
resolve(out);
|
|
});
|
|
});
|
|
}
|
|
|
|
function defaultListWindows(ppid) {
|
|
return new Promise((resolve) => {
|
|
let wpt;
|
|
try {
|
|
wpt = require("@vscode/windows-process-tree");
|
|
} catch {
|
|
return resolve([]);
|
|
}
|
|
try {
|
|
wpt.getProcessTree(ppid, (tree) => {
|
|
if (!tree || !Array.isArray(tree.children)) return resolve([]);
|
|
resolve(tree.children.map((c) => ({ pid: c.pid, command: c.name })));
|
|
});
|
|
} catch {
|
|
resolve([]);
|
|
}
|
|
});
|
|
}
|
|
|
|
function createDefaultProcessTree() {
|
|
const platform = process.platform;
|
|
return createProcessTree({
|
|
platform,
|
|
listPosix: platform === "win32" ? undefined : defaultListPosix,
|
|
listWindows: platform === "win32" ? defaultListWindows : undefined,
|
|
});
|
|
}
|
|
|
|
const defaultTree = createDefaultProcessTree();
|
|
|
|
module.exports = {
|
|
createProcessTree,
|
|
processTree: defaultTree,
|
|
registerPid: (id, pid) => defaultTree.registerPid(id, pid),
|
|
unregisterPid: (id) => defaultTree.unregisterPid(id),
|
|
getChildProcesses: (id) => defaultTree.getChildProcesses(id),
|
|
};
|