/** * Agent Definitions — scalable sub-agent configuration. * * Each agent has: * - A config entry in AGENT_CONFIGS (tools, maxTurns, model, etc.) * - A prompt file in ./prompts/{name}.txt * * Adding a new agent: 1) create prompts/{name}.txt, 2) add config entry below. */ import fs from 'fs'; import path from 'path'; import { log } from '../../shared/logger.js'; import { WORKSPACE_DIR } from '../../shared/paths.js'; const PROMPTS_DIR = path.join(import.meta.dirname, 'prompts'); /** Agent config without prompt (prompt is loaded from file) */ interface AgentConfig { description: string; promptFile: string; background: boolean; maxTurns: number; permissionMode?: string; tools?: string[]; disallowedTools?: string[]; effort?: 'low' | 'medium' | 'high' | 'max'; model?: string; memory?: 'user' | 'project' | 'local'; } // ── Agent Configurations ────────────────────────────────────────────────────── // Add new agents here. Each needs a matching prompts/{promptFile}. const AGENT_CONFIGS: Record = { coder: { description: 'Handles all coding tasks: building features, fixing bugs, refactoring, full-stack development in the workspace. Use this for any task that requires reading, writing, or editing code files.', promptFile: 'coder.txt', background: true, maxTurns: 50, permissionMode: 'bypassPermissions', effort: 'high', }, // ── Future agents ── // researcher: { // description: 'Researches topics on the web, gathers data, writes findings to files.', // promptFile: 'researcher.txt', // background: true, // maxTurns: 30, // disallowedTools: ['Write', 'Edit'], // effort: 'medium', // }, // // marketplace: { // description: 'Browses the Bloby Marketplace, installs and configures skills.', // promptFile: 'marketplace.txt', // background: true, // maxTurns: 20, // permissionMode: 'bypassPermissions', // effort: 'medium', // }, }; // ── Helpers ────────────────────────────────────────────────────────────────── /** Read a workspace memory file, return '(empty)' if missing */ function readMemoryFile(filename: string): string { try { const content = fs.readFileSync(path.join(WORKSPACE_DIR, filename), 'utf-8').trim(); return content || '(empty)'; } catch { return '(empty)'; } } /** Read a prompt file from the prompts directory */ function readPromptFile(filename: string): string { const filePath = path.join(PROMPTS_DIR, filename); try { const content = fs.readFileSync(filePath, 'utf-8').trim(); if (!content) { log.warn(`[agents] Prompt file empty: ${filename}`); return 'You are a background sub-agent. Complete the task described below.'; } return content; } catch (err) { log.warn(`[agents] Could not read prompt file ${filename}: ${err}`); return 'You are a background sub-agent. Complete the task described below.'; } } // ── Public API ────────────────────────────────────────────────────────────── /** * Build the agents config for the SDK's query() options. * Reads prompt files and injects project memory. */ export function buildAgents(): Record { const agents: Record = {}; const memory = readMemoryFile('MEMORY.md'); for (const [name, config] of Object.entries(AGENT_CONFIGS)) { let prompt = readPromptFile(config.promptFile); // Inject project memory at the end of the prompt prompt += `\n\n---\n\n# Project Memory\n\n## MEMORY.md\n${memory}`; agents[name] = { description: config.description, prompt, background: config.background, maxTurns: config.maxTurns, ...(config.permissionMode && { permissionMode: config.permissionMode }), ...(config.tools && { tools: config.tools }), ...(config.disallowedTools && { disallowedTools: config.disallowedTools }), ...(config.effort && { effort: config.effort }), ...(config.model && { model: config.model }), ...(config.memory && { memory: config.memory }), }; log.info(`[agents] Loaded agent "${name}" — prompt: ${prompt.length} chars, bg: ${config.background}, maxTurns: ${config.maxTurns}`); } log.info(`[agents] Built ${Object.keys(agents).length} agent definition(s): ${Object.keys(agents).join(', ')}`); return agents; }