feat: agor-launcher MCP server + .mcp.json + updated Rule 56
This commit is contained in:
parent
58ac5e8c84
commit
e9d7356064
4 changed files with 282 additions and 49 deletions
99
.claude/mcp-servers/agor-launcher/index.mjs
Normal file
99
.claude/mcp-servers/agor-launcher/index.mjs
Normal file
|
|
@ -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 }],
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,43 +1,41 @@
|
||||||
# Electrobun Launch Sequence (MANDATORY)
|
# 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):
|
| Tool | Use When |
|
||||||
```bash
|
| --------------------------------------- | ------------------------------------------------------------------ |
|
||||||
/home/hibryda/code/ai/agent-orchestrator/agor-pty/target/release/agor-ptyd &>/dev/null &
|
| `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:
|
## Fallback (if MCP unavailable)
|
||||||
```bash
|
|
||||||
cd /home/hibryda/code/ai/agent-orchestrator/ui-electrobun && npx vite dev --port 9760 --host localhost &>/dev/null &
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Wait 3-4 seconds** for Vite to be ready.
|
```bash
|
||||||
|
./scripts/launch.sh start # normal launch
|
||||||
4. **Launch Electrobun**:
|
./scripts/launch.sh start --clean # clean build + launch
|
||||||
```bash
|
./scripts/launch.sh stop # stop all
|
||||||
cd /home/hibryda/code/ai/agent-orchestrator/ui-electrobun && npx electrobun dev
|
./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
|
## What Happens If You Skip Steps
|
||||||
|
|
||||||
- **No PTY daemon**: App crashes with `Connection timeout (5s). Is agor-ptyd running?` — Bun process dies silently.
|
- **No PTY daemon**: App crashes with `Connection timeout (5s). Is agor-ptyd running?`
|
||||||
- **No Vite**: WebView loads a blank page (no frontend bundle served).
|
- **No Vite**: WebView loads blank page (no frontend bundle)
|
||||||
- **No wait**: Electrobun opens before Vite is ready — blank page or partial load.
|
- **No kill first**: Duplicate windows, port conflicts
|
||||||
|
|
||||||
## 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- NEVER launch Electrobun without the PTY daemon running.
|
- ALWAYS use `agor-stop` before `agor-start` when relaunching
|
||||||
- NEVER launch Electrobun without Vite running on port 9760.
|
- Use `clean: true` when `src/bun/` files changed (Electrobun caches bundles)
|
||||||
- ALWAYS kill previous instances first (Rule 55).
|
- Use `agor-rebuild` after dependency changes or native code changes
|
||||||
- Check `pgrep -f agor-ptyd` before launching to verify daemon is alive.
|
- Use `agor-kill-stale` periodically (ptyd accumulates across sessions)
|
||||||
|
- NEVER launch manually with raw bash — always use MCP or scripts/launch.sh
|
||||||
|
|
|
||||||
8
.mcp.json
Normal file
8
.mcp.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"agor-launcher": {
|
||||||
|
"command": "node",
|
||||||
|
"args": [".claude/mcp-servers/agor-launcher/index.mjs"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,31 +1,159 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Agents Orchestrator — Electrobun launch script
|
# Agents Orchestrator — Electrobun management script
|
||||||
# Usage: ./scripts/launch.sh [--clean]
|
# Usage: ./scripts/launch.sh <command> [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
|
set -e
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
EBUN="$ROOT/ui-electrobun"
|
EBUN="$ROOT/ui-electrobun"
|
||||||
PTYD="$ROOT/agor-pty/target/release/agor-ptyd"
|
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..."
|
cmd_stop() {
|
||||||
pkill -f "electrobun|WebKit|AgentOrch|launcher" 2>/dev/null || true
|
echo "[agor] Stopping all instances..."
|
||||||
fuser -k 9760/tcp 2>/dev/null || true
|
pkill -f "electrobun|WebKit|AgentOrch|launcher" 2>/dev/null || true
|
||||||
sleep 1
|
fuser -k 9760/tcp 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
echo "[agor] Stopped."
|
||||||
|
}
|
||||||
|
|
||||||
if [[ "$1" == "--clean" ]]; then
|
cmd_kill_stale() {
|
||||||
echo "[launch] Cleaning build..."
|
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/"
|
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..."
|
cmd_build_native() {
|
||||||
pgrep -f agor-ptyd > /dev/null 2>&1 || "$PTYD" &>/dev/null &
|
echo "[agor] Building libagor-resize.so..."
|
||||||
sleep 1
|
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..."
|
echo "[agor] Building agor-ptyd..."
|
||||||
fuser 9760/tcp > /dev/null 2>&1 || (cd "$EBUN" && npx vite dev --port 9760 --host localhost &>/dev/null &)
|
cd "$ROOT/agor-pty"
|
||||||
sleep 3
|
cargo build --release 2>&1 | tail -3
|
||||||
|
echo "[agor] Built: $PTYD"
|
||||||
|
}
|
||||||
|
|
||||||
echo "[launch] Starting Electrobun..."
|
cmd_rebuild() {
|
||||||
cd "$EBUN" && exec npx electrobun dev
|
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 <command> [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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue