{"version":3,"file":"agent-registry.d.ts","sourceRoot":"","sources":["../../src/core/agent-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,EAAE,KAAK,eAAe,EAA0C,MAAM,wBAAwB,CAAC;AAEtG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,mDAAmD;AACnD,qBAAa,aAAa;IACzB,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,WAAW,CAA4B;IAE/C;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAkBnC;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE7C;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzB;IAED,IAAI,IAAI,eAAe,EAAE,CAExB;IAED,2DAA2D;IAC3D,cAAc,IAAI,kBAAkB,EAAE,CAErC;IAED,uEAAuE;IACvE,cAAc,CAAC,WAAW,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAEtD;CACD;AA8ED,MAAM,WAAW,wBAAwB;IACxC,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mGAAmG;IACnG,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAYD,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,aAAa,CA0DlF;AAED;;;;GAIG;AACH;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAwBrE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CA8BvE","sourcesContent":["/**\n * Agent registry: data-driven loading of subagent definitions.\n *\n * Replaces the hardcoded `SubagentMode` enum + `MODE_TOOLS` map with frontmatter\n * `.md` files (see agent-frontmatter.ts). Definitions are discovered from, in\n * increasing order of precedence:\n *\n *   1. builtin            embedded templates (EMBEDDED_AGENT_PROMPTS)\n *   2. package-manifest   paths from hoocode.agents in package.json\n *   3. claude-user        ~/.claude/agents/*.md          (D7 native import)\n *   4. user               ~/.hoocode/agents/*.md\n *   5. ancestor-walk      <git-root..cwd>/.agents/agents/*.md\n *   6. claude-project     <cwd>/.claude/agents/*.md       (D7 native import)\n *   7. project            <cwd>/.hoocode/agents/*.md\n *   8. cli                paths injected via --agent <path>\n *\n * Higher-precedence sources override lower ones by name. Overrides are recorded\n * as collision diagnostics. Loading never throws; problems surface as\n * diagnostics, matching skills.ts.\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../config.js\";\nimport { EMBEDDED_AGENT_PROMPTS } from \"../init-templates.generated.js\";\nimport { type AgentDefinition, type AgentSource, parseAgentDefinition } from \"./agent-frontmatter.js\";\nimport { getAgentCliPaths, getAgentManifestPaths } from \"./agent-manifest-paths.js\";\nimport type { ResourceDiagnostic } from \"./diagnostics.js\";\n\n/** Registry of agent definitions keyed by name. */\nexport class AgentRegistry {\n\tprivate agents = new Map<string, AgentDefinition>();\n\tprivate diagnostics: ResourceDiagnostic[] = [];\n\n\t/**\n\t * Add or override a definition. Later registrations win (used both by the\n\t * loader for precedence and as an escape hatch for programmatic agents).\n\t * Overriding an existing name records a collision diagnostic.\n\t */\n\tregister(def: AgentDefinition): void {\n\t\tconst existing = this.agents.get(def.name);\n\t\tif (existing) {\n\t\t\tthis.diagnostics.push({\n\t\t\t\ttype: \"collision\",\n\t\t\t\tmessage: `agent \"${def.name}\" from ${def.source} overrides ${existing.source}`,\n\t\t\t\tpath: def.filePath,\n\t\t\t\tcollision: {\n\t\t\t\t\tresourceType: \"skill\",\n\t\t\t\t\tname: def.name,\n\t\t\t\t\twinnerPath: def.filePath ?? `<${def.source}>`,\n\t\t\t\t\tloserPath: existing.filePath ?? `<${existing.source}>`,\n\t\t\t\t\twinnerSource: def.source,\n\t\t\t\t\tloserSource: existing.source,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t\tthis.agents.set(def.name, def);\n\t}\n\n\tget(name: string): AgentDefinition | undefined {\n\t\treturn this.agents.get(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.agents.has(name);\n\t}\n\n\tlist(): AgentDefinition[] {\n\t\treturn Array.from(this.agents.values());\n\t}\n\n\t/** Diagnostics accumulated during loading/registration. */\n\tgetDiagnostics(): ResourceDiagnostic[] {\n\t\treturn this.diagnostics;\n\t}\n\n\t/** Append externally-produced diagnostics (e.g. from a parse step). */\n\taddDiagnostics(diagnostics: ResourceDiagnostic[]): void {\n\t\tthis.diagnostics.push(...diagnostics);\n\t}\n}\n\n/** Load and register every built-in (embedded) agent definition. */\nfunction registerBuiltins(registry: AgentRegistry): void {\n\tfor (const [key, raw] of Object.entries(EMBEDDED_AGENT_PROMPTS)) {\n\t\tconst { agent, diagnostics } = parseAgentDefinition(raw, { source: \"builtin\", fallbackName: key });\n\t\tregistry.addDiagnostics(diagnostics);\n\t\tif (agent) registry.register(agent);\n\t}\n}\n\n/** Load flat `*.md` agent files from a directory. Non-`.md` entries and\n *  subdirectories are skipped (so runtime dispatch dirs are ignored). */\nfunction registerDir(registry: AgentRegistry, dir: string, source: AgentSource): void {\n\tif (!existsSync(dir)) return;\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(dir);\n\t} catch {\n\t\treturn;\n\t}\n\tfor (const entry of entries) {\n\t\tif (entry.startsWith(\".\") || !entry.endsWith(\".md\")) continue;\n\t\tconst filePath = join(dir, entry);\n\t\ttry {\n\t\t\tif (!statSync(filePath).isFile()) continue;\n\t\t\tconst raw = readFileSync(filePath, \"utf-8\");\n\t\t\tconst { agent, diagnostics } = parseAgentDefinition(raw, { source, filePath });\n\t\t\tregistry.addDiagnostics(diagnostics);\n\t\t\tif (agent) registry.register(agent);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read agent file\";\n\t\t\tregistry.addDiagnostics([{ type: \"warning\", message, path: filePath }]);\n\t\t}\n\t}\n}\n\nfunction findGitRepoRoot(startDir: string): string | null {\n\tlet dir = resolve(startDir);\n\twhile (true) {\n\t\tif (existsSync(join(dir, \".git\"))) return dir;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/** Collect `.agents/agents/` dirs from cwd up to the git root (cwd-first order). */\nfunction collectAncestorAgentsDirs(startDir: string): string[] {\n\tconst dirs: string[] = [];\n\tconst resolvedStart = resolve(startDir);\n\tconst gitRoot = findGitRepoRoot(resolvedStart);\n\tlet dir = resolvedStart;\n\twhile (true) {\n\t\tdirs.push(join(dir, \".agents\", \"agents\"));\n\t\tif (gitRoot && dir === gitRoot) break;\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) break;\n\t\tdir = parent;\n\t}\n\treturn dirs;\n}\n\n/** Register a single file as an agent. */\nfunction registerFile(registry: AgentRegistry, filePath: string, source: AgentSource): void {\n\tif (!existsSync(filePath)) return;\n\ttry {\n\t\tif (!statSync(filePath).isFile()) return;\n\t\tconst raw = readFileSync(filePath, \"utf-8\");\n\t\tconst { agent, diagnostics } = parseAgentDefinition(raw, { source, filePath });\n\t\tregistry.addDiagnostics(diagnostics);\n\t\tif (agent) registry.register(agent);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to read agent file\";\n\t\tregistry.addDiagnostics([{ type: \"warning\", message, path: filePath }]);\n\t}\n}\n\nexport interface LoadAgentRegistryOptions {\n\t/** Working directory for project-local agents. */\n\tcwd: string;\n\t/** User agent config directory (contains `agents/` subdir). Defaults to getAgentDir(). */\n\tagentDir?: string;\n\t/** Include embedded built-in agents. Defaults to true. */\n\tincludeBuiltins?: boolean;\n\t/** Discover `.claude/agents/` directories for native Claude Code import (D7). Defaults to true. */\n\tincludeClaude?: boolean;\n\t/**\n\t * Explicit agent definition paths (files or directories), resolved relative\n\t * to `cwd` (with `~` expansion). Mirrors `skillPaths`/`promptPaths` on the\n\t * skills and prompt-template loaders. These override all discovered sources\n\t * by name but yield to CLI-injected `--agent` paths.\n\t */\n\tagentPaths?: string[];\n}\n\n/** Resolve an explicit path: expand a leading `~` and resolve against `cwd`. */\nfunction normalizeAgentPath(input: string, cwd: string): string {\n\tconst trimmed = input.trim();\n\tlet expanded = trimmed;\n\tif (trimmed === \"~\") expanded = homedir();\n\telse if (trimmed.startsWith(\"~/\")) expanded = join(homedir(), trimmed.slice(2));\n\telse if (trimmed.startsWith(\"~\")) expanded = join(homedir(), trimmed.slice(1));\n\treturn resolve(cwd, expanded);\n}\n\n/** Build an AgentRegistry from all configured locations, applying precedence. */\nexport function loadAgentRegistry(options: LoadAgentRegistryOptions): AgentRegistry {\n\tconst { cwd, includeBuiltins = true, includeClaude = true } = options;\n\tconst userAgentDir = options.agentDir ?? getAgentDir();\n\tconst registry = new AgentRegistry();\n\n\t// Lowest precedence first; later sources override earlier ones by name.\n\tif (includeBuiltins) {\n\t\tregisterBuiltins(registry);\n\t}\n\n\t// Package-manifest agents (declared via `hoocode.agents` in package.json).\n\tfor (const filePath of getAgentManifestPaths()) {\n\t\tregisterFile(registry, filePath, \"user\");\n\t}\n\n\tif (includeClaude) {\n\t\tregisterDir(registry, join(homedir(), \".claude\", \"agents\"), \"claude-user\");\n\t}\n\tregisterDir(registry, join(userAgentDir, \"agents\"), \"user\");\n\n\t// Ancestor-walk .agents/agents/ dirs (git-root first so cwd-level overrides ancestors).\n\tfor (const dir of collectAncestorAgentsDirs(cwd).reverse()) {\n\t\tregisterDir(registry, dir, \"project\");\n\t}\n\n\tif (includeClaude) {\n\t\tregisterDir(registry, resolve(cwd, \".claude\", \"agents\"), \"claude-project\");\n\t}\n\tregisterDir(registry, resolve(cwd, CONFIG_DIR_NAME, \"agents\"), \"project\");\n\n\t// Explicit caller-provided paths override discovered sources (files or dirs).\n\tfor (const rawPath of options.agentPaths ?? []) {\n\t\tconst p = normalizeAgentPath(rawPath, cwd);\n\t\tif (!existsSync(p)) {\n\t\t\tregistry.addDiagnostics([{ type: \"warning\", message: `Agent path does not exist: ${p}`, path: p }]);\n\t\t\tcontinue;\n\t\t}\n\t\tif (statSync(p).isDirectory()) {\n\t\t\tregisterDir(registry, p, \"project\");\n\t\t} else {\n\t\t\tregisterFile(registry, p, \"project\");\n\t\t}\n\t}\n\n\t// CLI-injected paths have highest precedence (support both files and dirs).\n\tfor (const p of getAgentCliPaths()) {\n\t\tif (!existsSync(p)) {\n\t\t\tregistry.addDiagnostics([{ type: \"warning\", message: `Agent path does not exist: ${p}`, path: p }]);\n\t\t\tcontinue;\n\t\t}\n\t\tif (statSync(p).isDirectory()) {\n\t\t\tregisterDir(registry, p, \"user\");\n\t\t} else {\n\t\t\tregisterFile(registry, p, \"user\");\n\t\t}\n\t}\n\n\treturn registry;\n}\n\n/**\n * Format a list of agent definitions as an XML block for inclusion in a system\n * prompt, mirroring the `<available_skills>` format used by formatSkillsForPrompt.\n * Only intended for display when the Task tool is active.\n */\n/**\n * Condense a (possibly multi-line, bulleted) agent description into a single\n * useful one-liner.\n *\n * Built-in agent descriptions open with a boilerplate header (\"Use this\n * subagent ONLY when:\") followed by \"when to use\" bullets and a \"DO NOT use\"\n * section. Taking the first line alone yields that identical header for every\n * agent, so instead surface the first meaningful bullets (or the first prose\n * line) from the positive \"when to use\" region.\n */\nexport function summarizeAgentDescription(description: string): string {\n\tconst lines = description\n\t\t.split(\"\\n\")\n\t\t.map((line) => line.trim())\n\t\t.filter((line) => line.length > 0);\n\tif (lines.length === 0) return \"\";\n\n\t// Keep only the positive region: everything before a \"DO NOT use\" section.\n\tconst stop = lines.findIndex((line) => /^(do\\s*not|don'?t|avoid)\\b/i.test(line));\n\tconst region = stop === -1 ? lines : lines.slice(0, stop);\n\n\t// Drop a leading header line (e.g. \"Use this subagent ONLY when:\").\n\tconst body = region.length > 1 && region[0]!.endsWith(\":\") ? region.slice(1) : region;\n\n\tconst stripBullet = (line: string) => line.replace(/^[-*•]\\s+/, \"\").trim();\n\tconst bullets = body\n\t\t.filter((line) => /^[-*•]\\s+/.test(line))\n\t\t.map(stripBullet)\n\t\t.filter((line) => line.length > 0);\n\n\tconst summary = bullets.length > 0 ? bullets.slice(0, 3).join(\"; \") : (body[0] ?? lines[0] ?? \"\").replace(/:$/, \"\");\n\n\tconst MAX = 200;\n\treturn summary.length > MAX ? `${summary.slice(0, MAX - 1).trimEnd()}…` : summary;\n}\n\nexport function formatAgentsForPrompt(agents: AgentDefinition[]): string {\n\tif (agents.length === 0) return \"\";\n\n\tconst lines = [\n\t\t\"\\n\\nThe following specialized agents are available for delegation via the Task tool.\",\n\t\t\"Choose the agent whose description best matches the task and pass it as `subagent_type`.\",\n\t\t\"\",\n\t\t\"<available_agents>\",\n\t];\n\n\tfor (const agent of agents) {\n\t\tlines.push(\"  <agent>\");\n\t\tlines.push(`    <name>${escapeXml(agent.name)}</name>`);\n\t\t// Summarized (one positive \"when to use\" line) rather than the full\n\t\t// description: the roster is rendered every turn and the full text — with its\n\t\t// \"DO NOT use\"/Cost/Isolation metadata — costs ~2x the tokens for routing\n\t\t// detail the parent rarely needs once it has picked an agent.\n\t\tlines.push(`    <description>${escapeXml(summarizeAgentDescription(agent.description))}</description>`);\n\t\tif (agent.tools && agent.tools.length > 0) {\n\t\t\tlines.push(`    <tools>${escapeXml(agent.tools.join(\", \"))}</tools>`);\n\t\t}\n\t\tif (agent.model) {\n\t\t\tlines.push(`    <model>${escapeXml(agent.model)}</model>`);\n\t\t}\n\t\tlines.push(\"  </agent>\");\n\t}\n\n\tlines.push(\"</available_agents>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&apos;\");\n}\n"]}