{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/core/agents.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAO9E,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,gBAAgB,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,WAAW,EAAE,kBAAkB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CACf;AAyBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,gBAAgB,CA0CrF;AA+CD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAuBpE;AAWD,MAAM,WAAW,iBAAiB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CA4FvE","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, dirname, extname, join, relative, resolve, sep } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { canonicalizePath, resolvePath } from \"../utils/paths.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport { validateDescription, validateName } from \"./skills.ts\";\n\nfunction toPosixPath(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\nexport interface AgentFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\tmodel?: string;\n\tskill?: string;\n\ttools?: string;\n\tsystemPromptMode?: string;\n\tinheritProjectContext?: boolean;\n\tinheritSkills?: boolean;\n\toutput?: string;\n\tdefaultReads?: string;\n\tdefaultContext?: string;\n\t[key: string]: unknown;\n}\n\nexport interface AgentPersona {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsourceInfo: SourceInfo;\n\tfrontmatter: AgentFrontmatter;\n}\n\nexport interface LoadAgentsResult {\n\tagents: AgentPersona[];\n\tdiagnostics: ResourceDiagnostic[];\n}\n\nexport interface LoadAgentsFromDirOptions {\n\tdir: string;\n\tsource: string;\n}\n\nfunction createAgentSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {\n\tswitch (source) {\n\t\tcase \"user\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tcase \"project\":\n\t\t\treturn createSyntheticSourceInfo(filePath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir,\n\t\t\t});\n\t\tdefault:\n\t\t\treturn createSyntheticSourceInfo(filePath, { source, baseDir });\n\t}\n}\n\nfunction isMarkdown(filePath: string): boolean {\n\treturn extname(filePath).toLowerCase() === \".md\";\n}\n\n/**\n * Load agent persona markdown files from a directory.\n * Only flat .md files at the top level are loaded (agents are not folder-based like skills).\n */\nexport function loadAgentsFromDir(options: LoadAgentsFromDirOptions): LoadAgentsResult {\n\tconst { dir, source } = options;\n\tconst agents: AgentPersona[] = [];\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { agents, diagnostics };\n\t}\n\n\tconst root = dir;\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\") || entry.isDirectory()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tisFile = statSync(fullPath).isFile();\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst relPath = toPosixPath(relative(root, fullPath));\n\t\t\tif (!isFile || !isMarkdown(fullPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst result = loadAgentFromFile(fullPath, source);\n\t\t\tif (result.agent) {\n\t\t\t\tagents.push(result.agent);\n\t\t\t}\n\t\t\tdiagnostics.push(...result.diagnostics);\n\t\t}\n\t} catch {}\n\n\treturn { agents, diagnostics };\n}\n\nfunction loadAgentFromFile(\n\tfilePath: string,\n\tsource: string,\n): { agent: AgentPersona | null; diagnostics: ResourceDiagnostic[] } {\n\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter } = parseFrontmatter<AgentFrontmatter>(rawContent);\n\t\tconst baseDir = dirname(filePath);\n\t\tconst fileName = basename(filePath, extname(filePath));\n\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tconst name = frontmatter.name || fileName;\n\t\tconst nameErrors = validateName(name);\n\t\tfor (const error of nameErrors) {\n\t\t\tdiagnostics.push({ type: \"warning\", message: error, path: filePath });\n\t\t}\n\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { agent: null, diagnostics };\n\t\t}\n\n\t\treturn {\n\t\t\tagent: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir,\n\t\t\t\tsourceInfo: createAgentSourceInfo(filePath, baseDir, source),\n\t\t\t\tfrontmatter,\n\t\t\t},\n\t\t\tdiagnostics,\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"failed to parse agent file\";\n\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\treturn { agent: null, diagnostics };\n\t}\n}\n\n/**\n * Format available agents for inclusion in the system prompt.\n * Tells the model which subagent personas exist and where to read them.\n */\nexport function formatAgentsForPrompt(agents: AgentPersona[]): string {\n\tif (agents.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following subagent personas are available for delegated work.\",\n\t\t\"Use the read tool to load a persona's file when you need to act as that agent or spawn it via a subagent tool.\",\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\tlines.push(`    <description>${escapeXml(agent.description)}</description>`);\n\t\tlines.push(`    <location>${escapeXml(agent.filePath)}</location>`);\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\nexport interface LoadAgentsOptions {\n\tcwd: string;\n\tagentDir: string;\n\tagentPaths: string[];\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load agent personas from all configured locations.\n */\nexport function loadAgents(options: LoadAgentsOptions): LoadAgentsResult {\n\tconst { agentDir, agentPaths, includeDefaults } = options;\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(agentDir ?? \"\");\n\n\tconst agentMap = new Map<string, AgentPersona>();\n\tconst realPathSet = new Set<string>();\n\tconst allDiagnostics: ResourceDiagnostic[] = [];\n\tconst collisionDiagnostics: ResourceDiagnostic[] = [];\n\n\tfunction addAgents(result: LoadAgentsResult) {\n\t\tallDiagnostics.push(...result.diagnostics);\n\t\tfor (const agent of result.agents) {\n\t\t\tconst realPath = canonicalizePath(agent.filePath);\n\t\t\tif (realPathSet.has(realPath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst existing = agentMap.get(agent.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionDiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${agent.name}\" collision`,\n\t\t\t\t\tpath: agent.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"agent\",\n\t\t\t\t\t\tname: agent.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: agent.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tagentMap.set(agent.name, agent);\n\t\t\t\trealPathSet.add(realPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (includeDefaults) {\n\t\taddAgents(loadAgentsFromDir({ dir: join(resolvedAgentDir, \"agents\"), source: \"user\" }));\n\t\taddAgents(loadAgentsFromDir({ dir: resolve(resolvedCwd, CONFIG_DIR_NAME, \"agents\"), source: \"project\" }));\n\t}\n\n\tconst userAgentsDir = join(resolvedAgentDir, \"agents\");\n\tconst projectAgentsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"agents\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) return true;\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSource = (resolvedPath: string): \"user\" | \"project\" | \"path\" => {\n\t\tif (!includeDefaults) {\n\t\t\tif (isUnderPath(resolvedPath, userAgentsDir)) return \"user\";\n\t\t\tif (isUnderPath(resolvedPath, projectAgentsDir)) return \"project\";\n\t\t}\n\t\treturn \"path\";\n\t};\n\n\tfor (const rawPath of agentPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tallDiagnostics.push({ type: \"warning\", message: \"agent path does not exist\", path: resolvedPath });\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tconst source = getSource(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\taddAgents(loadAgentsFromDir({ dir: resolvedPath, source }));\n\t\t\t} else if (stats.isFile() && isMarkdown(resolvedPath)) {\n\t\t\t\tconst result = loadAgentFromFile(resolvedPath, source);\n\t\t\t\tif (result.agent) {\n\t\t\t\t\taddAgents({ agents: [result.agent], diagnostics: result.diagnostics });\n\t\t\t\t} else {\n\t\t\t\t\tallDiagnostics.push(...result.diagnostics);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tallDiagnostics.push({ type: \"warning\", message: \"agent path is not a markdown file\", path: resolvedPath });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read agent path\";\n\t\t\tallDiagnostics.push({ type: \"warning\", message, path: resolvedPath });\n\t\t}\n\t}\n\n\treturn {\n\t\tagents: Array.from(agentMap.values()),\n\t\tdiagnostics: [...allDiagnostics, ...collisionDiagnostics],\n\t};\n}"]}