import type { ExtensionAPI } from "@jaeswift/jae-coding-agent"; import { spawn, type ChildProcess } from "node:child_process"; import { Type } from "@sinclair/typebox"; interface AgentInstance { id: string; role: string; systemPrompt: string; status: "running" | "idle" | "stopped" | "error"; process: ChildProcess | null; output: string; createdAt: number; lastMessage: string; } const ROLE_PROMPTS: Record = { frontend: "You are a frontend development specialist. Focus on UI/UX, React, CSS, accessibility, responsive design, and browser APIs. Write clean component code with proper state management.", backend: "You are a backend development specialist. Focus on API design, databases, server architecture, authentication, and performance. Write robust server-side code with proper error handling.", tester: "You are a QA and testing specialist. Focus on writing comprehensive tests: unit tests, integration tests, E2E tests. Identify edge cases, write test plans, and ensure code reliability.", reviewer: "You are a senior code reviewer. Analyze code for bugs, security issues, performance problems, and style violations. Provide constructive feedback with specific suggestions.", devops: "You are a DevOps and infrastructure specialist. Focus on CI/CD pipelines, Docker, Kubernetes, monitoring, and deployment automation. Write infrastructure as code.", security: "You are a security specialist. Focus on vulnerability assessment, secure coding practices, authentication/authorization, encryption, and threat modeling.", architect: "You are a software architect. Focus on system design, scalability patterns, technology selection, API contracts, and architectural decision records.", docs: "You are a technical documentation specialist. Focus on writing clear README files, API docs, tutorials, inline comments, and architectural documentation.", }; export default function (pi: ExtensionAPI) { const agents = new Map(); let idCounter = 0; function generateId(role: string): string { idCounter++; return `${role}-${idCounter}`; } function getSystemPrompt(role: string): string { return ROLE_PROMPTS[role] || `You are a ${role} specialist. Focus on tasks related to ${role} and provide expert-level assistance.`; } async function spawnAgent(role: string, cwd: string): Promise { const id = generateId(role); const systemPrompt = getSystemPrompt(role); const agent: AgentInstance = { id, role, systemPrompt, status: "idle", process: null, output: "", createdAt: Date.now(), lastMessage: "", }; agents.set(id, agent); return agent; } async function sendMessage( agent: AgentInstance, message: string, cwd: string, ): Promise { return new Promise((resolve) => { const timeout = 180_000; // 3 minutes let output = ""; let resolved = false; agent.status = "running"; agent.lastMessage = message; const prompt = `[System: ${agent.systemPrompt}]\n\n${message}`; try { const child = spawn( "jae", ["-p", prompt], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout, env: { ...process.env }, }, ); agent.process = child; child.stdout?.on("data", (data: Buffer) => { output += data.toString(); }); child.stderr?.on("data", (data: Buffer) => { output += data.toString(); }); child.on("close", (code) => { agent.process = null; agent.status = code === 0 ? "idle" : "error"; agent.output = output.trim(); if (!resolved) { resolved = true; resolve(output.trim() || "(no output)"); } }); child.on("error", (err) => { agent.process = null; agent.status = "error"; if (!resolved) { resolved = true; resolve(`Agent error: ${err.message}`); } }); setTimeout(() => { if (!resolved) { resolved = true; try { child.kill("SIGTERM"); } catch {} agent.status = "error"; resolve(output.trim() + "\n(timed out after 3 minutes)"); } }, timeout); } catch (err: any) { agent.status = "error"; resolve(`Spawn error: ${err.message}`); } }); } function killAgent(id: string): boolean { const agent = agents.get(id); if (!agent) return false; if (agent.process) { try { agent.process.kill("SIGTERM"); } catch {} } agent.status = "stopped"; agent.process = null; agents.delete(id); return true; } function formatAgentList(): string { if (agents.size === 0) return "No active agents. Use /a2a spawn to create one."; const lines: string[] = []; for (const [id, agent] of agents) { const uptime = Math.floor((Date.now() - agent.createdAt) / 1000); const uptimeStr = uptime > 60 ? `${Math.floor(uptime / 60)}m${uptime % 60}s` : `${uptime}s`; const statusIcon = agent.status === "running" ? "\u{1F7E2}" : agent.status === "idle" ? "\u{1F535}" : agent.status === "error" ? "\u{1F534}" : "\u26AB"; lines.push(` ${statusIcon} ${id} [${agent.role}] — ${agent.status} — uptime: ${uptimeStr}`); } return lines.join("\n"); } pi.registerTool({ name: "a2a_communicate", label: "Agent-to-Agent Communication", description: "Spawn specialized AI agents and communicate with them. Agents can be given roles like 'frontend', 'backend', 'tester', 'reviewer', 'devops', 'security', 'architect', or 'docs'.", parameters: Type.Object({ action: Type.String({ description: "Action: 'spawn', 'send', 'broadcast', 'list', 'kill'" }), role: Type.Optional(Type.String({ description: "Agent role for spawn (e.g., 'frontend', 'backend', 'tester')" })), agent_id: Type.Optional(Type.String({ description: "Target agent ID for send/kill" })), message: Type.Optional(Type.String({ description: "Message to send to agent(s)" })), }), execute: async (args, ctx) => { const action = args.action.toLowerCase(); switch (action) { case "spawn": { const role = args.role || "general"; const agent = await spawnAgent(role, ctx.cwd); return { output: `\u2705 Agent spawned: ${agent.id} [${role}]\nSystem prompt: ${agent.systemPrompt.substring(0, 100)}...` }; } case "send": { if (!args.agent_id) return { error: "Specify agent_id to send message to." }; if (!args.message) return { error: "Specify message to send." }; const agent = agents.get(args.agent_id); if (!agent) return { error: `Agent '${args.agent_id}' not found. Use action 'list' to see agents.` }; if (agent.status === "running") return { error: `Agent '${args.agent_id}' is busy processing. Wait for completion.` }; const response = await sendMessage(agent, args.message, ctx.cwd); return { output: `\u{1F4AC} Response from ${args.agent_id}:\n\n${response}` }; } case "broadcast": { if (!args.message) return { error: "Specify message to broadcast." }; if (agents.size === 0) return { error: "No active agents. Spawn some first." }; const idle = Array.from(agents.values()).filter((a) => a.status === "idle"); if (idle.length === 0) return { error: "All agents are busy. Wait for them to finish." }; const promises = idle.map(async (agent) => { const response = await sendMessage(agent, args.message!, ctx.cwd); return `### ${agent.id} [${agent.role}]:\n${response.substring(0, 500)}`; }); const results = await Promise.all(promises); return { output: `\u{1F4E2} Broadcast to ${idle.length} agents:\n\n${results.join("\n\n")}` }; } case "list": { return { output: `\u{1F916} Active Agents:\n${formatAgentList()}` }; } case "kill": { if (!args.agent_id) return { error: "Specify agent_id to kill." }; const success = killAgent(args.agent_id); return { output: success ? `\u2705 Agent '${args.agent_id}' terminated.` : `\u274C Agent '${args.agent_id}' not found.` }; } default: return { error: `Unknown action '${action}'. Use: spawn, send, broadcast, list, kill` }; } }, }); pi.registerCommand("a2a", { description: "Agent-to-Agent: /a2a spawn | list | send | broadcast | kill ", handler: async (args, ctx) => { const parts = args.trim().split(/\s+/); const subcommand = (parts[0] || "list").toLowerCase(); switch (subcommand) { case "spawn": { const role = parts[1] || "general"; const validRoles = [...Object.keys(ROLE_PROMPTS), "general"]; const agent = await spawnAgent(role, ctx.cwd); ctx.ui.notify( `\u{1F916} Agent Spawned!\n${'\u2500'.repeat(30)}\n\n` + ` ID: ${agent.id}\n` + ` Role: ${role}\n` + ` Prompt: ${agent.systemPrompt.substring(0, 80)}...\n\n` + `Available roles: ${validRoles.join(", ")}\n` + `Send messages: /a2a send ${agent.id} `, "info", ); break; } case "list": case "ls": { ctx.ui.notify(`\u{1F916} Agent Registry\n${'\u2500'.repeat(30)}\n\n${formatAgentList()}`, "info"); break; } case "send": { const agentId = parts[1]; const message = parts.slice(2).join(" "); if (!agentId || !message) { ctx.ui.notify("Usage: /a2a send ", "warning"); return; } const agent = agents.get(agentId); if (!agent) { ctx.ui.notify(`\u274C Agent '${agentId}' not found. Use /a2a list.`, "warning"); return; } if (agent.status === "running") { ctx.ui.notify(`\u23F3 Agent '${agentId}' is busy. Wait for completion.`, "warning"); return; } ctx.ui.notify(`\u{1F4E4} Sending to ${agentId}...`, "info"); const response = await sendMessage(agent, message, ctx.cwd); ctx.ui.notify(`\u{1F4AC} ${agentId} responded:\n\n${response.substring(0, 2000)}`, "info"); break; } case "broadcast": { const message = parts.slice(1).join(" "); if (!message) { ctx.ui.notify("Usage: /a2a broadcast ", "warning"); return; } const idle = Array.from(agents.values()).filter((a) => a.status === "idle"); if (idle.length === 0) { ctx.ui.notify("\u274C No idle agents. Spawn some first with /a2a spawn .", "warning"); return; } ctx.ui.notify(`\u{1F4E2} Broadcasting to ${idle.length} agents...`, "info"); const promises = idle.map(async (agent) => { const response = await sendMessage(agent, message, ctx.cwd); return `\u{1F916} ${agent.id}:\n${response.substring(0, 500)}`; }); const results = await Promise.all(promises); ctx.ui.notify(`\u{1F4E2} Broadcast Results:\n${'\u2500'.repeat(30)}\n\n${results.join("\n\n")}`, "info"); break; } case "kill": case "stop": { const agentId = parts[1]; if (!agentId) { ctx.ui.notify("Usage: /a2a kill ", "warning"); return; } if (agentId === "all") { const count = agents.size; for (const id of Array.from(agents.keys())) killAgent(id); ctx.ui.notify(`\u2705 Terminated ${count} agents.`, "info"); return; } const success = killAgent(agentId); ctx.ui.notify( success ? `\u2705 Agent '${agentId}' terminated.` : `\u274C Agent '${agentId}' not found.`, success ? "info" : "warning", ); break; } default: ctx.ui.notify( `\u{1F916} Agent-to-Agent Communication\n${'\u2500'.repeat(30)}\n\n` + "Commands:\n" + " /a2a spawn - Spawn specialized agent\n" + " /a2a list - List active agents\n" + " /a2a send - Send message to agent\n" + " /a2a broadcast - Send to all idle agents\n" + " /a2a kill - Terminate agent(s)\n\n" + `Roles: ${Object.keys(ROLE_PROMPTS).join(", ")}`, "info", ); } }, }); }