diff --git a/.claude/mcp-servers/agor-launcher/index.mjs b/.claude/mcp-servers/agor-launcher/index.mjs index 2b83585..584bb92 100644 --- a/.claude/mcp-servers/agor-launcher/index.mjs +++ b/.claude/mcp-servers/agor-launcher/index.mjs @@ -1,13 +1,12 @@ #!/usr/bin/env node /** * agor-launcher MCP — manages Electrobun app lifecycle. - * Tools: start, stop, restart, clean, rebuild, status, kill-stale, build-native, logs + * Tools: start, stop, restart, clean, rebuild, status, kill-stale, build-native */ -import { execSync, spawn } from "child_process"; +import { execSync, spawnSync, spawn } from "child_process"; import { createInterface } from "readline"; const SCRIPT = "/home/hibryda/code/ai/agent-orchestrator/scripts/launch.sh"; - const ROOT = "/home/hibryda/code/ai/agent-orchestrator"; const EBUN = `${ROOT}/ui-electrobun`; const PTYD = `${ROOT}/agor-pty/target/release/agor-ptyd`; @@ -15,74 +14,62 @@ const PTYD = `${ROOT}/agor-pty/target/release/agor-ptyd`; function run(cmd, timeout = 30000) { try { return execSync(`bash ${SCRIPT} ${cmd}`, { - timeout, - encoding: "utf-8", - stdio: ["pipe", "pipe", "pipe"], + timeout, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], }).trim(); } catch (e) { return `Error: ${e.stderr || e.message}`; } } -/** Start the app: stop old → start PTY → start Vite → launch Electrobun (backgrounded) */ +/** Non-blocking start: spawn everything detached, return immediately */ function startApp(clean = false) { const steps = []; - // Step 1: Stop old instances - try { - execSync('pkill -f "electrobun|WebKit|AgentOrch|launcher" 2>/dev/null; fuser -k 9760/tcp 2>/dev/null; true', { - timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], - }); - steps.push("[1/4] Stopped old instances"); - } catch { steps.push("[1/4] Stop (nothing running)"); } + // Step 1: Kill old Electrobun/WebKit (NOT node — that's us) + spawnSync("bash", ["-c", + 'pkill -f "AgentOrch" 2>/dev/null; pkill -f "WebKitWebProcess.*9760" 2>/dev/null; pkill -f "electrobun dev" 2>/dev/null; fuser -k 9760/tcp 2>/dev/null; true' + ], { timeout: 3000, stdio: "ignore" }); + steps.push("[1/4] Stopped old instances"); // Step 1.5: Clean if requested if (clean) { - try { - execSync(`rm -rf ${EBUN}/build/ ${EBUN}/node_modules/.electrobun-cache/`, { timeout: 5000 }); - steps.push("[1.5] Cleaned build cache"); - } catch { steps.push("[1.5] Clean skipped"); } + spawnSync("rm", ["-rf", `${EBUN}/build/`, `${EBUN}/node_modules/.electrobun-cache/`], { timeout: 3000 }); + steps.push("[1.5] Cleaned build cache"); } // Step 2: Start PTY daemon (if not running) try { - const ptydCount = execSync("pgrep -fc agor-ptyd 2>/dev/null || echo 0", { encoding: "utf-8" }).trim(); - if (ptydCount === "0") { - const ptyd = spawn(PTYD, [], { detached: true, stdio: "ignore" }); - ptyd.unref(); + const r = spawnSync("pgrep", ["-fc", "agor-ptyd"], { encoding: "utf-8", timeout: 2000 }); + const count = (r.stdout || "0").trim(); + if (count === "0" || count === "") { + const p = spawn(PTYD, [], { detached: true, stdio: "ignore" }); + p.unref(); steps.push("[2/4] PTY daemon started"); } else { - steps.push(`[2/4] PTY daemon already running (${ptydCount})`); + steps.push(`[2/4] PTY daemon already running (${count})`); } - } catch (e) { steps.push(`[2/4] PTY daemon error: ${e.message}`); } + } catch (e) { steps.push(`[2/4] PTY error: ${e.message}`); } // Step 3: Start Vite (if port 9760 not in use) try { - const portCheck = execSync("fuser 9760/tcp 2>/dev/null || echo free", { encoding: "utf-8" }).trim(); - if (portCheck === "free") { - const vite = spawn("npx", ["vite", "dev", "--port", "9760", "--host", "localhost"], { + const r = spawnSync("fuser", ["9760/tcp"], { encoding: "utf-8", timeout: 2000 }); + if (r.status !== 0) { + const v = spawn("npx", ["vite", "dev", "--port", "9760", "--host", "localhost"], { cwd: EBUN, detached: true, stdio: "ignore", }); - vite.unref(); + v.unref(); steps.push("[3/4] Vite started on :9760"); } else { - steps.push("[3/4] Vite already running on :9760"); + steps.push("[3/4] Vite already on :9760"); } } catch (e) { steps.push(`[3/4] Vite error: ${e.message}`); } - // Wait for Vite to be ready - try { execSync("sleep 3", { timeout: 5000 }); } catch {} - - // Step 4: Launch Electrobun (detached, output to log) - try { - const ebun = spawn("npx", ["electrobun", "dev"], { - cwd: EBUN, - detached: true, - stdio: ["ignore", "ignore", "ignore"], - }); - ebun.unref(); - steps.push(`[4/4] Electrobun launched (PID ${ebun.pid})`); - } catch (e) { steps.push(`[4/4] Electrobun error: ${e.message}`); } + // Step 4: Launch Electrobun (detached) after a brief pause via a wrapper script + const wrapper = spawn("bash", ["-c", `sleep 4; cd "${EBUN}" && npx electrobun dev`], { + detached: true, stdio: "ignore", + }); + wrapper.unref(); + steps.push(`[4/4] Electrobun launching in 4s (PID ${wrapper.pid})`); return steps.join("\n"); } @@ -96,12 +83,22 @@ const TOOLS = { "agor-stop": { description: "Stop all running Electrobun/PTY/Vite instances.", schema: { type: "object", properties: {} }, - handler: () => run("stop"), + handler: () => { + spawnSync("bash", ["-c", + 'pkill -f "AgentOrch" 2>/dev/null; pkill -f "electrobun dev" 2>/dev/null; pkill -f "WebKitWebProcess" 2>/dev/null; fuser -k 9760/tcp 2>/dev/null; true' + ], { timeout: 5000, stdio: "ignore" }); + return "Stopped all instances."; + }, }, "agor-restart": { description: "Restart Electrobun app. Pass clean=true for clean restart.", schema: { type: "object", properties: { clean: { type: "boolean", default: false } } }, - handler: ({ clean }) => { run("stop"); return startApp(clean); }, + handler: ({ clean }) => { + spawnSync("bash", ["-c", + 'pkill -f "AgentOrch" 2>/dev/null; pkill -f "electrobun dev" 2>/dev/null; pkill -f "WebKitWebProcess" 2>/dev/null; fuser -k 9760/tcp 2>/dev/null; true' + ], { timeout: 5000, stdio: "ignore" }); + return startApp(clean); + }, }, "agor-clean": { description: "Remove build artifacts, caches, and temp files.", @@ -121,7 +118,11 @@ const TOOLS = { "agor-kill-stale": { description: "Kill ALL stale agor-ptyd instances that accumulated over sessions.", schema: { type: "object", properties: {} }, - handler: () => run("kill-stale"), + handler: () => { + spawnSync("pkill", ["-9", "-f", "agor-ptyd"], { timeout: 3000, stdio: "ignore" }); + const r = spawnSync("pgrep", ["-fc", "agor-ptyd"], { encoding: "utf-8", timeout: 2000 }); + return `Killed stale ptyd. Remaining: ${(r.stdout || "0").trim()}`; + }, }, "agor-build-native": { description: "Rebuild libagor-resize.so (GTK resize) and agor-ptyd (PTY daemon).", @@ -142,7 +143,7 @@ rl.on("line", (line) => { send({ jsonrpc: "2.0", id: msg.id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, - serverInfo: { name: "agor-launcher", version: "1.0.0" }, + serverInfo: { name: "agor-launcher", version: "1.1.0" }, }}); } else if (msg.method === "notifications/initialized") { // no-op @@ -160,7 +161,7 @@ rl.on("line", (line) => { } const result = tool.handler(msg.params.arguments || {}); send({ jsonrpc: "2.0", id: msg.id, result: { - content: [{ type: "text", text: result }], + content: [{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result) }], }}); } });