/** * agent-menu.ts — Top-level /agents command hub. * * Presents the main agents menu with entries for running agents, agent types, * scheduled jobs, creation, and settings. Routes selections to appropriate handlers. */ import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import type { AgentManager } from "../agent-manager.js"; import { getAllTypes } from "../agent-types.js"; import type { SubagentScheduler } from "../schedule.js"; import { showAgentDetail } from "./agent-detail.js"; import { showAgentList } from "./agent-list.js"; import { showCreateWizard } from "./create-wizard.js"; import { showSchedulesMenu } from "./schedule-menu.js"; /** * Show the main /agents command hub. * * @param ctx Extension command context * @param manager AgentManager for listing running agents * @param scheduler SubagentScheduler for scheduled jobs */ export async function showAgentsMenu( ctx: ExtensionCommandContext, manager: AgentManager, scheduler: SubagentScheduler, pi: ExtensionAPI, ): Promise { const allNames = getAllTypes(); // Build menu options const options: string[] = []; // Running agents entry const agents = manager.listAgents(); if (agents.length > 0) { const running = agents.filter(a => a.status === "running" || a.status === "queued").length; const done = agents.filter(a => a.status === "completed" || a.status === "steered").length; options.push(`Running agents (${agents.length}) — ${running} running, ${done} done`); } // Agent types entry if (allNames.length > 0) { options.push(`Agent types (${allNames.length})`); } // Scheduled jobs entry if (scheduler.isActive()) { const jobCount = scheduler.list().length; options.push(`Scheduled jobs (${jobCount})`); } // Actions options.push("Create new agent"); options.push("Settings"); const noAgentsMsg = allNames.length === 0 && agents.length === 0 ? "No agents found. Create specialized subagents that can be delegated to.\n\n" + "Each subagent has its own context window, custom system prompt, and specific tools.\n\n" + "Try creating: Code Reviewer, Security Auditor, Test Writer, or Documentation Writer.\n" : ""; if (noAgentsMsg) { ctx.ui.notify(noAgentsMsg, "info"); } const choice = await ctx.ui.select("Agents", options); if (!choice) return; if (choice.startsWith("Running agents (")) { await showRunningAgents(ctx, manager); await showAgentsMenu(ctx, manager, scheduler, pi); } else if (choice.startsWith("Agent types (")) { await showAgentList(ctx, async (name: string) => { await showAgentDetail(ctx, name); await showAgentsMenu(ctx, manager, scheduler, pi); }); } else if (choice.startsWith("Scheduled jobs (")) { await showSchedulesMenu(ctx, scheduler); await showAgentsMenu(ctx, manager, scheduler, pi); } else if (choice === "Create new agent") { await showCreateWizard(ctx, manager, pi); } else if (choice === "Settings") { // Settings are handled by the calling index.ts ctx.ui.notify("Settings are managed from the main /agents menu in pi.", "info"); } } /** * Show running agents list. */ async function showRunningAgents( ctx: ExtensionCommandContext, manager: AgentManager, ): Promise { const agents = manager.listAgents(); if (agents.length === 0) { ctx.ui.notify("No running agents.", "info"); return; } const options = agents.map(a => { const dn = a.type; const dur = a.startedAt && a.completedAt ? `${Math.floor((a.completedAt - a.startedAt) / 1000)}s` : a.startedAt ? `${Math.floor((Date.now() - a.startedAt) / 1000)}s` : a.status; return `${dn} (${a.description}) · ${a.toolUses} tools · ${a.status} · ${dur}`; }); const choice = await ctx.ui.select("Running agents", options); if (!choice) return; const idx = options.indexOf(choice); if (idx < 0) return; const record = agents[idx]; // View the agent's conversation if (record.session) { if (record.status === "running") { const { showLiveSession } = await import("./live-session.js"); await showLiveSession(ctx, record.session, record, undefined); } else { const { ConversationViewer } = await import("./conversation-viewer.js"); await ctx.ui.custom( (tui, theme, _keybindings, done) => { return new ConversationViewer(tui, record.session!, record, undefined, theme, done); }, { overlay: true, overlayOptions: { anchor: "center", width: "90%" } }, ); } } else { ctx.ui.notify(`Agent is ${record.status === "queued" ? "queued" : "expired"} — no session available.`, "info"); } }