diff --git a/.claude/mcp-servers/agor-launcher/index.mjs b/.claude/mcp-servers/agor-launcher/index.mjs new file mode 100644 index 0000000..5d1c1d2 --- /dev/null +++ b/.claude/mcp-servers/agor-launcher/index.mjs @@ -0,0 +1,99 @@ +#!/usr/bin/env node +/** + * agor-launcher MCP — manages Electrobun app lifecycle. + * Tools: start, stop, restart, clean, rebuild, status, kill-stale, build-native, logs + */ +import { execSync, spawn } from "child_process"; +import { createInterface } from "readline"; + +const SCRIPT = "/home/hibryda/code/ai/agent-orchestrator/scripts/launch.sh"; + +function run(cmd, timeout = 30000) { + try { + return execSync(`bash ${SCRIPT} ${cmd}`, { + timeout, + encoding: "utf-8", + stdio: ["pipe", "pipe", "pipe"], + }).trim(); + } catch (e) { + return `Error: ${e.stderr || e.message}`; + } +} + +const TOOLS = { + "agor-start": { + description: "Start Electrobun app (kills old instances first). Pass clean=true to remove build cache.", + schema: { type: "object", properties: { clean: { type: "boolean", default: false } } }, + handler: ({ clean }) => run(clean ? "start --clean" : "start", 60000), + }, + "agor-stop": { + description: "Stop all running Electrobun/PTY/Vite instances.", + schema: { type: "object", properties: {} }, + handler: () => run("stop"), + }, + "agor-restart": { + description: "Restart Electrobun app. Pass clean=true for clean restart.", + schema: { type: "object", properties: { clean: { type: "boolean", default: false } } }, + handler: ({ clean }) => run(clean ? "restart --clean" : "restart", 60000), + }, + "agor-clean": { + description: "Remove build artifacts, caches, and temp files.", + schema: { type: "object", properties: {} }, + handler: () => run("clean"), + }, + "agor-rebuild": { + description: "Full rebuild: clean + npm install + vite build + native C library + PTY daemon.", + schema: { type: "object", properties: {} }, + handler: () => run("rebuild", 120000), + }, + "agor-status": { + description: "Show running processes, ports, and window status.", + schema: { type: "object", properties: {} }, + handler: () => run("status"), + }, + "agor-kill-stale": { + description: "Kill ALL stale agor-ptyd instances that accumulated over sessions.", + schema: { type: "object", properties: {} }, + handler: () => run("kill-stale"), + }, + "agor-build-native": { + description: "Rebuild libagor-resize.so (GTK resize) and agor-ptyd (PTY daemon).", + schema: { type: "object", properties: {} }, + handler: () => run("build-native", 120000), + }, +}; + +// Minimal MCP stdio server +const rl = createInterface({ input: process.stdin }); +function send(msg) { process.stdout.write(JSON.stringify(msg) + "\n"); } + +rl.on("line", (line) => { + let msg; + try { msg = JSON.parse(line); } catch { return; } + + if (msg.method === "initialize") { + send({ jsonrpc: "2.0", id: msg.id, result: { + protocolVersion: "2024-11-05", + capabilities: { tools: {} }, + serverInfo: { name: "agor-launcher", version: "1.0.0" }, + }}); + } else if (msg.method === "notifications/initialized") { + // no-op + } else if (msg.method === "tools/list") { + send({ jsonrpc: "2.0", id: msg.id, result: { + tools: Object.entries(TOOLS).map(([name, t]) => ({ + name, description: t.description, inputSchema: t.schema, + })), + }}); + } else if (msg.method === "tools/call") { + const tool = TOOLS[msg.params.name]; + if (!tool) { + send({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: `Unknown tool: ${msg.params.name}` }}); + return; + } + const result = tool.handler(msg.params.arguments || {}); + send({ jsonrpc: "2.0", id: msg.id, result: { + content: [{ type: "text", text: result }], + }}); + } +}); diff --git a/.claude/rules/56-electrobun-launch.md b/.claude/rules/56-electrobun-launch.md index 997a4d7..c32a359 100644 --- a/.claude/rules/56-electrobun-launch.md +++ b/.claude/rules/56-electrobun-launch.md @@ -1,43 +1,41 @@ # Electrobun Launch Sequence (MANDATORY) -Before launching the Electrobun app, ALWAYS start dependencies in this exact order: +**Use the `agor-launcher` MCP tools** for ALL app lifecycle operations. Do NOT use raw bash commands for launch/stop/rebuild. -## Steps +## MCP Tools (preferred) -1. **Start Rust PTY daemon** (if not already running): - ```bash - /home/hibryda/code/ai/agent-orchestrator/agor-pty/target/release/agor-ptyd &>/dev/null & - ``` +| Tool | Use When | +| --------------------------------------- | ------------------------------------------------------------------ | +| `mcp__agor-launcher__agor-start` | Launch the app (`clean: true` to rebuild from scratch) | +| `mcp__agor-launcher__agor-stop` | Stop all running instances | +| `mcp__agor-launcher__agor-restart` | Stop + start (`clean: true` for clean restart) | +| `mcp__agor-launcher__agor-clean` | Remove build caches without launching | +| `mcp__agor-launcher__agor-rebuild` | Full rebuild: npm install + vite build + native C lib + PTY daemon | +| `mcp__agor-launcher__agor-status` | Check running processes, ports, window | +| `mcp__agor-launcher__agor-kill-stale` | Kill accumulated stale agor-ptyd processes | +| `mcp__agor-launcher__agor-build-native` | Rebuild libagor-resize.so + agor-ptyd | -2. **Start Vite dev server** on port 9760: - ```bash - cd /home/hibryda/code/ai/agent-orchestrator/ui-electrobun && npx vite dev --port 9760 --host localhost &>/dev/null & - ``` +## Fallback (if MCP unavailable) -3. **Wait 3-4 seconds** for Vite to be ready. - -4. **Launch Electrobun**: - ```bash - cd /home/hibryda/code/ai/agent-orchestrator/ui-electrobun && npx electrobun dev - ``` +```bash +./scripts/launch.sh start # normal launch +./scripts/launch.sh start --clean # clean build + launch +./scripts/launch.sh stop # stop all +./scripts/launch.sh rebuild # full rebuild +./scripts/launch.sh status # check state +./scripts/launch.sh kill-stale # clean up stale ptyd +``` ## What Happens If You Skip Steps -- **No PTY daemon**: App crashes with `Connection timeout (5s). Is agor-ptyd running?` — Bun process dies silently. -- **No Vite**: WebView loads a blank page (no frontend bundle served). -- **No wait**: Electrobun opens before Vite is ready — blank page or partial load. - -## Kill Sequence (Rule 55) - -Always kill previous instances before launching new ones: -```bash -pkill -f "electrobun|vite.*9760|agor-ptyd|WebKit|AgentOrch|launcher" 2>/dev/null -fuser -k 9760/tcp 2>/dev/null -``` +- **No PTY daemon**: App crashes with `Connection timeout (5s). Is agor-ptyd running?` +- **No Vite**: WebView loads blank page (no frontend bundle) +- **No kill first**: Duplicate windows, port conflicts ## Rules -- NEVER launch Electrobun without the PTY daemon running. -- NEVER launch Electrobun without Vite running on port 9760. -- ALWAYS kill previous instances first (Rule 55). -- Check `pgrep -f agor-ptyd` before launching to verify daemon is alive. +- ALWAYS use `agor-stop` before `agor-start` when relaunching +- Use `clean: true` when `src/bun/` files changed (Electrobun caches bundles) +- Use `agor-rebuild` after dependency changes or native code changes +- Use `agor-kill-stale` periodically (ptyd accumulates across sessions) +- NEVER launch manually with raw bash — always use MCP or scripts/launch.sh diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..ee010cf --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "agor-launcher": { + "command": "node", + "args": [".claude/mcp-servers/agor-launcher/index.mjs"] + } + } +} diff --git a/scripts/launch.sh b/scripts/launch.sh index 26aa0ea..5a4d211 100755 --- a/scripts/launch.sh +++ b/scripts/launch.sh @@ -1,31 +1,159 @@ #!/bin/bash -# Agents Orchestrator — Electrobun launch script -# Usage: ./scripts/launch.sh [--clean] +# Agents Orchestrator — Electrobun management script +# Usage: ./scripts/launch.sh [options] # -# Kills previous instances, optionally cleans build, starts PTY + Vite + Electrobun. +# Commands: +# start [--clean] Kill old, start PTY+Vite+Electrobun (--clean removes build/) +# stop Kill all running instances +# restart [--clean] Stop then start +# clean Remove build/, node_modules/.electrobun-cache, stale processes +# rebuild Full clean + npm install + vite build + rebuild PTY + rebuild C lib +# status Show running processes and ports +# kill-stale Kill ALL stale agor-ptyd instances (accumulate over sessions) +# build-native Rebuild libagor-resize.so and agor-ptyd +# logs Tail Electrobun output set -e ROOT="$(cd "$(dirname "$0")/.." && pwd)" EBUN="$ROOT/ui-electrobun" PTYD="$ROOT/agor-pty/target/release/agor-ptyd" +RESIZE_SO="$ROOT/agor-pty/native/libagor-resize.so" +RESIZE_C="$ROOT/agor-pty/native/agor_resize.c" +LOG="/tmp/agor-electrobun.log" -echo "[launch] Killing previous instances..." -pkill -f "electrobun|WebKit|AgentOrch|launcher" 2>/dev/null || true -fuser -k 9760/tcp 2>/dev/null || true -sleep 1 +cmd_stop() { + echo "[agor] Stopping all instances..." + pkill -f "electrobun|WebKit|AgentOrch|launcher" 2>/dev/null || true + fuser -k 9760/tcp 2>/dev/null || true + sleep 1 + echo "[agor] Stopped." +} -if [[ "$1" == "--clean" ]]; then - echo "[launch] Cleaning build..." +cmd_kill_stale() { + echo "[agor] Killing ALL stale agor-ptyd instances..." + pkill -f agor-ptyd 2>/dev/null || true + sleep 1 + local count=$(pgrep -fc agor-ptyd 2>/dev/null || echo 0) + echo "[agor] Remaining ptyd processes: $count" +} + +cmd_clean() { + echo "[agor] Cleaning build artifacts..." rm -rf "$EBUN/build/" -fi + rm -rf "$EBUN/node_modules/.electrobun-cache/" + rm -rf "$EBUN/node_modules/electrobun/.cache/" + rm -f "$LOG" + echo "[agor] Cleaned." +} -echo "[launch] Starting PTY daemon..." -pgrep -f agor-ptyd > /dev/null 2>&1 || "$PTYD" &>/dev/null & -sleep 1 +cmd_build_native() { + echo "[agor] Building libagor-resize.so..." + cd "$ROOT/agor-pty/native" + gcc -shared -fPIC -o libagor-resize.so agor_resize.c $(pkg-config --cflags --libs gtk+-3.0) + echo "[agor] Built: $RESIZE_SO" -echo "[launch] Starting Vite on :9760..." -fuser 9760/tcp > /dev/null 2>&1 || (cd "$EBUN" && npx vite dev --port 9760 --host localhost &>/dev/null &) -sleep 3 + echo "[agor] Building agor-ptyd..." + cd "$ROOT/agor-pty" + cargo build --release 2>&1 | tail -3 + echo "[agor] Built: $PTYD" +} -echo "[launch] Starting Electrobun..." -cd "$EBUN" && exec npx electrobun dev +cmd_rebuild() { + cmd_stop + cmd_kill_stale + cmd_clean + + echo "[agor] Installing npm dependencies..." + cd "$EBUN" && npm install --legacy-peer-deps 2>&1 | tail -3 + + echo "[agor] Building Vite frontend..." + cd "$EBUN" && npx vite build 2>&1 | tail -1 + + cmd_build_native + echo "[agor] Full rebuild complete." +} + +cmd_start() { + local clean=false + [[ "${1:-}" == "--clean" ]] && clean=true + + cmd_stop + + if $clean; then + cmd_clean + fi + + # Kill excess stale ptyd (keep max 1) + local ptyd_count=$(pgrep -fc agor-ptyd 2>/dev/null || echo 0) + if [ "$ptyd_count" -gt 1 ]; then + echo "[agor] Killing $ptyd_count stale ptyd instances..." + pkill -f agor-ptyd 2>/dev/null || true + sleep 1 + fi + + echo "[agor] Starting PTY daemon..." + pgrep -f agor-ptyd > /dev/null 2>&1 || "$PTYD" &>/dev/null & + sleep 1 + + echo "[agor] Starting Vite on :9760..." + fuser 9760/tcp > /dev/null 2>&1 || (cd "$EBUN" && npx vite dev --port 9760 --host localhost &>/dev/null &) + sleep 3 + + echo "[agor] Starting Electrobun..." + cd "$EBUN" && npx electrobun dev 2>&1 | tee "$LOG" +} + +cmd_restart() { + cmd_stop + sleep 1 + cmd_start "$@" +} + +cmd_status() { + echo "=== Processes ===" + echo "PTY daemons: $(pgrep -fc agor-ptyd 2>/dev/null || echo 0)" + echo "Vite: $(fuser 9760/tcp 2>/dev/null && echo 'running' || echo 'stopped')" + echo "Electrobun: $(pgrep -fc electrobun 2>/dev/null || echo 0)" + echo "WebKit: $(pgrep -fc WebKitWebProcess 2>/dev/null || echo 0)" + echo "" + echo "=== Ports ===" + echo "9760 (Vite): $(fuser 9760/tcp 2>/dev/null || echo 'free')" + echo "" + echo "=== Window ===" + xdotool search --class "WebKit" 2>/dev/null | head -1 || echo "No window found" +} + +cmd_logs() { + if [ -f "$LOG" ]; then + tail -f "$LOG" + else + echo "[agor] No log file at $LOG. Start with: ./scripts/launch.sh start" + fi +} + +# Dispatch +case "${1:-help}" in + start) shift; cmd_start "$@" ;; + stop) cmd_stop ;; + restart) shift; cmd_restart "$@" ;; + clean) cmd_clean ;; + rebuild) cmd_rebuild ;; + status) cmd_status ;; + kill-stale) cmd_kill_stale ;; + build-native) cmd_build_native ;; + logs) cmd_logs ;; + *) + echo "Usage: $0 [options]" + echo "" + echo "Commands:" + echo " start [--clean] Start the app (--clean removes build first)" + echo " stop Stop all instances" + echo " restart [--clean] Stop then start" + echo " clean Remove build artifacts and caches" + echo " rebuild Full clean + npm install + vite build + native build" + echo " status Show running processes and ports" + echo " kill-stale Kill ALL stale agor-ptyd instances" + echo " build-native Rebuild libagor-resize.so + agor-ptyd" + echo " logs Tail Electrobun output" + ;; +esac