#!/usr/bin/env node import { execFileSync, spawnSync } from 'node:child_process'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ENSURE_GOOSE_SESSION_POSTGRES = path.join(__dirname, 'ensure-goose-session-postgres.sh'); const DRY_RUN = process.argv.includes('--dry-run'); const FD_WARN = Number(process.env.GOOSED_FD_WARN ?? 180); const FD_RESTART = Number(process.env.GOOSED_FD_RESTART ?? 3200); const SANDBOX_MAX_TOTAL = Number(process.env.SANDBOX_MCP_MAX_TOTAL ?? 80); const SANDBOX_MAX_PER_ROOT = Number(process.env.SANDBOX_MCP_MAX_PER_ROOT ?? 2); function sh(command) { return execFileSync('/bin/zsh', ['-lc', command], { encoding: 'utf8' }).trim(); } function run(command, args) { if (DRY_RUN) { console.log(`[dry-run] ${command} ${args.join(' ')}`); return { status: 0 }; } return spawnSync(command, args, { stdio: 'inherit' }); } function psLines(pattern) { const output = sh( `ps -axo pid=,ppid=,etime=,command= | grep ${JSON.stringify(pattern)} | grep -v grep || true`, ); if (!output) return []; return output.split('\n').map((line) => { const match = line.trim().match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/); if (!match) return null; return { pid: Number(match[1]), ppid: Number(match[2]), etimes: etimeToSeconds(match[3]), command: match[4], }; }).filter(Boolean); } function etimeToSeconds(value) { const parts = String(value ?? '').split('-'); const dayPart = parts.length === 2 ? Number(parts[0]) : 0; const timePart = parts.at(-1) ?? '0'; const nums = timePart.split(':').map((item) => Number(item)); if (nums.some((item) => !Number.isFinite(item))) return 0; const [hours, minutes, seconds] = nums.length === 3 ? nums : nums.length === 2 ? [0, nums[0], nums[1]] : [0, 0, nums[0]]; return dayPart * 86400 + hours * 3600 + minutes * 60 + seconds; } function fdCount(pid) { const count = sh(`lsof -n -p ${pid} 2>/dev/null | wc -l || true`); return Number(count.trim() || 0); } function killPid(pid, reason) { console.log(`kill pid=${pid} reason=${reason}`); run('/bin/kill', ['-TERM', String(pid)]); } function rootForSandbox(command) { const marker = 'mindspace-sandbox-mcp.mjs '; const idx = command.indexOf(marker); if (idx < 0) return ''; return command.slice(idx + marker.length).trim().split(/\s+/)[0] ?? ''; } function cleanupSandboxProcesses() { const processes = psLines('mindspace-sandbox-mcp.mjs'); const byParentRoot = new Map(); for (const proc of processes) { const root = rootForSandbox(proc.command); const key = `${proc.ppid}:${root}`; if (!byParentRoot.has(key)) byParentRoot.set(key, []); byParentRoot.get(key).push(proc); } const toKill = new Map(); for (const group of byParentRoot.values()) { group.sort((a, b) => b.etimes - a.etimes); for (const proc of group.slice(0, Math.max(0, group.length - SANDBOX_MAX_PER_ROOT))) { toKill.set(proc.pid, { proc, reason: 'duplicate-sandbox-root' }); } } for (const proc of processes) { const parentAlive = proc.ppid > 1 && psLines(`^${proc.ppid} `).length > 0; if (!parentAlive && proc.etimes > 60) { toKill.set(proc.pid, { proc, reason: 'orphan-sandbox' }); } } if (processes.length > SANDBOX_MAX_TOTAL) { const oldest = [...processes].sort((a, b) => b.etimes - a.etimes); for (const proc of oldest.slice(0, processes.length - SANDBOX_MAX_TOTAL)) { toKill.set(proc.pid, { proc, reason: `over-total-${SANDBOX_MAX_TOTAL}` }); } } for (const { proc, reason } of toKill.values()) { killPid(proc.pid, reason); } console.log(`sandbox processes=${processes.length} killed=${toKill.size}`); } function goosedLabel(command) { const port = command.match(/\b(18006|18007)\b/)?.[1]; return port ? `cn.tkmind.goosed-${port}` : null; } function monitorGoosed() { const processes = psLines('goosed agent'); for (const proc of processes) { const fds = fdCount(proc.pid); console.log(`goosed pid=${proc.pid} fd=${fds} cmd=${proc.command}`); if (fds >= FD_RESTART) { const label = goosedLabel(proc.command); if (label) { console.log(`restart ${label} fd=${fds} threshold=${FD_RESTART}`); run('/bin/launchctl', ['kickstart', '-k', `gui/${process.getuid()}/${label}`]); } } else if (fds >= FD_WARN) { console.warn(`warn goosed pid=${proc.pid} fd=${fds} threshold=${FD_WARN}`); } } } function ensureGooseSessionPostgres() { try { const result = spawnSync('/bin/bash', [ENSURE_GOOSE_SESSION_POSTGRES], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }); const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim(); if (output) console.log(output); if (result.status !== 0) { console.warn('goose session postgres ensure failed'); } } catch (err) { console.warn( 'goose session postgres ensure skipped:', err instanceof Error ? err.message : err, ); } } ensureGooseSessionPostgres(); cleanupSandboxProcesses(); monitorGoosed();