Files
bitvid/tools/fleet/bin/core/tmux.mjs
T
2026-10-24 15:42:46 -04:00

143 lines
4.6 KiB
JavaScript

// The fleet session lives on one tmux server. A sender (Codex MCP, a sandbox,
// a shell) often has a different TMUX or TMUX_TMPDIR, so a bare `tmux` looks
// at the wrong server and reports the pane missing. fleet up records the
// socket. Every later command passes -S and ignores the sender's TMUX.
import fs from 'node:fs';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
export function fleetTmuxSession(c) {
return `fleet-${c?.project || 'fleet'}`.replace(/[.:]/g, '-');
}
export function tmuxSocketFile(c) {
return path.join(c.stateDir, 'tmux-socket');
}
function readSocketFile(c) {
if (!c?.stateDir) return '';
try {
const sock = fs.readFileSync(tmuxSocketFile(c), 'utf8').trim().split('\n')[0].trim();
return sock.startsWith('/') ? sock : '';
} catch {
return '';
}
}
function writeSocketFile(c, sock) {
if (!c?.stateDir || !sock || !sock.startsWith('/')) return;
fs.mkdirSync(c.stateDir, { recursive: true });
fs.writeFileSync(tmuxSocketFile(c), `${sock}\n`);
}
function cleanEnv(base, { keepTmux = false } = {}) {
const env = { ...base };
if (!keepTmux) delete env.TMUX;
return env;
}
function hasSession(session, socket, env) {
const args = [...(socket ? ['-S', socket] : []), 'has-session', '-t', session];
const r = spawnSync('tmux', args, { encoding: 'utf8', env });
return r.status === 0;
}
function socketPathOf(session, socket, env) {
const args = [...(socket ? ['-S', socket] : []), 'display-message', '-p', '-t', session, '#{socket_path}'];
const r = spawnSync('tmux', args, { encoding: 'utf8', env });
const sock = String(r.stdout || '').trim();
return r.status === 0 && sock.startsWith('/') ? sock : '';
}
function socketDirs() {
const uid = typeof process.getuid === 'function' ? process.getuid() : '';
const roots = [process.env.TMUX_TMPDIR, process.env.XDG_RUNTIME_DIR, '/tmp'];
return [...new Set(roots.filter(Boolean).map((root) => path.join(root, `tmux-${uid}`)))];
}
function listSockets() {
const found = [];
for (const dir of socketDirs()) {
let names = [];
try { names = fs.readdirSync(dir); } catch { continue; }
for (const name of names) {
const full = path.join(dir, name);
try {
if (fs.statSync(full).isSocket()) found.push(full);
} catch { /* gone */ }
}
}
return found;
}
/** Socket that actually has this fleet session, or '' if none is visible. */
function findSocket(session) {
const bare = cleanEnv(process.env);
delete bare.TMUX_TMPDIR;
if (hasSession(session, '', bare)) {
const pathOf = socketPathOf(session, '', bare);
if (pathOf) return pathOf;
}
const fromEnv = String(process.env.TMUX || '').split(',')[0];
if (fromEnv.startsWith('/') && hasSession(session, fromEnv, cleanEnv(process.env))) return fromEnv;
for (const sock of listSockets()) {
if (sock === fromEnv) continue;
if (hasSession(session, sock, cleanEnv(process.env))) return sock;
}
return '';
}
export function resolveTmuxSocket(c) {
if (!c) return '';
if (c._tmuxSocket) return c._tmuxSocket;
const session = fleetTmuxSession(c);
const saved = readSocketFile(c);
if (saved && hasSession(session, saved, cleanEnv(process.env))) {
c._tmuxSocket = saved;
return saved;
}
const found = findSocket(session);
if (found) {
writeSocketFile(c, found);
c._tmuxSocket = found;
return found;
}
// Keep a recorded path even when the session is not on it, so the miss
// log names the server we actually queried instead of the sender's TMUX.
if (saved) {
c._tmuxSocket = saved;
return saved;
}
return '';
}
/** After fleet up creates or reuses the session, store the server it used. */
export function rememberTmuxServer(c) {
const session = fleetTmuxSession(c);
const known = c?._tmuxSocket || readSocketFile(c);
const env = cleanEnv(process.env, { keepTmux: !known });
const sock = socketPathOf(session, known, env);
if (!sock) return '';
writeSocketFile(c, sock);
c._tmuxSocket = sock;
return sock;
}
export function tmuxSocketFacts(c) {
const saved = c?._tmuxSocket || readSocketFile(c);
return {
socket: saved || null,
socketExists: saved ? fs.existsSync(saved) : false,
tmuxEnv: process.env.TMUX || null,
tmuxTmpdir: process.env.TMUX_TMPDIR || null,
};
}
export function tmuxCommand(c, args, opts = {}) {
const socket = resolveTmuxSocket(c);
const argv = socket ? ['-S', socket, ...args] : args;
const env = cleanEnv({ ...process.env, ...(opts.env || {}) }, { keepTmux: !socket });
const { env: _env, ...rest } = opts;
return spawnSync('tmux', argv, { encoding: 'utf8', ...rest, env });
}