"use strict";
// Custom agent loader.
//
// Reads agent definitions from `.harness/agents/*.md` files. Each agent has
// YAML frontmatter (name, description, role, model, etc.) and a body that
// becomes the system prompt. Mirrors the skill loader so authoring is
// familiar.
//
// Built-in role catalogue is also exported here so the harness can resolve
// either a custom agent (by name) or a built-in role (by id).
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.BUILTIN_AGENT_ROLES = void 0;
exports.loadAgentDefinitions = loadAgentDefinitions;
exports.scanAgentDefinitions = scanAgentDefinitions;
exports.parseAgentFile = parseAgentFile;
exports.resolveAgentDefinition = resolveAgentDefinition;
exports.writeCustomAgent = writeCustomAgent;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const atomicFile_1 = require("../persistence/atomicFile");
exports.BUILTIN_AGENT_ROLES = [
    {
        id: 'researcher',
        name: 'Researcher',
        description: 'Investigates topics, gathers sources, and summarizes findings without taking destructive action.',
        role: 'researcher',
        preset: 'explore',
        personality: 'Thorough, skeptical, cites sources.',
        goal: 'Produce well-sourced findings that ground later decisions.',
        strengths: ['Search', 'Reading', 'Cross-referencing'],
        allowedTools: ['file_read', 'list_files', 'grep', 'web_fetch', 'web_search', 'web_read', 'recall'],
        systemPrompt: 'You are a Researcher. Investigate carefully, cite sources, and produce a focused summary. Prefer reading over writing. Never modify files.',
        enabled: true,
        filePath: '<builtin>',
    },
    {
        id: 'developer',
        name: 'Developer',
        description: 'Writes and edits code, runs tests, and fixes regressions within the project.',
        role: 'developer',
        preset: 'general',
        personality: 'Pragmatic, idiomatic, conservative with changes.',
        goal: 'Land minimum-correct code that passes existing tests.',
        strengths: ['Refactoring', 'Bug fixes', 'Tests'],
        systemPrompt: 'You are a Developer. Make minimum-correct code changes. Run tests after edits. Match existing style. Never refactor adjacent code unless explicitly asked.',
        enabled: true,
        filePath: '<builtin>',
    },
    {
        id: 'qa',
        name: 'QA',
        description: 'Designs and runs tests; reports failures with reproduction steps.',
        role: 'qa',
        preset: 'review',
        personality: 'Methodical, adversarial, precise.',
        goal: 'Catch defects and confirm fixes with evidence.',
        strengths: ['Test design', 'Reproduction', 'Edge cases'],
        systemPrompt: 'You are QA. Verify behavior with explicit test cases. Report defects with reproduction steps. Never patch code yourself — return findings.',
        enabled: true,
        filePath: '<builtin>',
    },
    {
        id: 'writer',
        name: 'Writer',
        description: 'Produces user-facing documentation, release notes, and explanations.',
        role: 'writer',
        preset: 'summarize',
        personality: 'Clear, concise, audience-aware.',
        goal: 'Turn raw findings into readable documents.',
        strengths: ['Markdown', 'Explanations', 'Structure'],
        systemPrompt: 'You are a Writer. Turn raw notes into clear, concise documents. Match the existing voice. Avoid filler.',
        enabled: true,
        filePath: '<builtin>',
    },
    {
        id: 'architect',
        name: 'Architect',
        description: 'Plans larger work, identifies risks, and proposes a sequence of steps.',
        role: 'architect',
        preset: 'plan',
        personality: 'Big-picture, considers trade-offs explicitly.',
        goal: 'Produce a plan with clear phases, risks, and validation criteria.',
        strengths: ['System design', 'Trade-off analysis', 'Sequencing'],
        systemPrompt: 'You are an Architect. Produce phased plans with explicit risks and validation steps. Prefer minimum-viable approaches over speculative flexibility.',
        enabled: true,
        filePath: '<builtin>',
    },
    {
        id: 'security',
        name: 'Security Analyst',
        description: 'Reviews code and configurations for vulnerabilities and risk.',
        role: 'security',
        preset: 'review',
        personality: 'Cautious, focuses on attack surface.',
        goal: 'Identify exploitable conditions and recommend mitigations.',
        strengths: ['OWASP Top 10', 'Secrets', 'Privilege boundaries'],
        systemPrompt: 'You are a Security Analyst. Review for OWASP Top 10 risks, secret leakage, and privilege escalation. Recommend specific mitigations.',
        enabled: true,
        filePath: '<builtin>',
    },
];
async function loadAgentDefinitions(projectDir) {
    return (await scanAgentDefinitions(projectDir)).agents;
}
async function scanAgentDefinitions(projectDir) {
    const agents = [];
    const diagnostics = [];
    const dir = path.join(projectDir, '.harness', 'agents');
    let entries;
    try {
        entries = await fs.readdir(dir, { withFileTypes: true });
    }
    catch {
        return { agents, diagnostics };
    }
    for (const entry of entries) {
        if (!entry.isFile() || !entry.name.endsWith('.md'))
            continue;
        const filePath = path.join(dir, entry.name);
        const id = entry.name.replace(/\.md$/, '');
        try {
            const content = await fs.readFile(filePath, 'utf-8');
            const definition = parseAgentFile(content, filePath, id);
            if (!definition) {
                diagnostics.push({ id, filePath, reason: 'missing-frontmatter', message: 'Agent file is missing YAML frontmatter.' });
                continue;
            }
            agents.push(definition);
        }
        catch (error) {
            diagnostics.push({ id, filePath, reason: 'unreadable-file', message: error instanceof Error ? error.message : String(error) });
        }
    }
    return { agents, diagnostics };
}
function parseAgentFile(content, filePath, fallbackId) {
    const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
    if (!match)
        return null;
    const frontmatter = parseSimpleYaml(match[1]);
    const body = match[2].trim();
    const enabledRaw = frontmatter.enabled;
    const enabled = enabledRaw === undefined
        ? true
        : !(enabledRaw === false || (typeof enabledRaw === 'string' && enabledRaw.toLowerCase() === 'false'));
    // Coerce id/name to strings: YAML parses unquoted all-digit values (e.g.
    // `id: 2006`) as numbers. Downstream lookups use strict equality with the
    // string from URL params or POST bodies, so a numeric id silently fails
    // to match.
    const rawId = frontmatter.id;
    const idFromFrontmatter = typeof rawId === 'string' ? rawId
        : (typeof rawId === 'number' || typeof rawId === 'boolean') ? String(rawId)
            : undefined;
    const id = idFromFrontmatter ?? fallbackId ?? path.basename(filePath, '.md');
    const rawName = frontmatter.name;
    const name = typeof rawName === 'string' ? rawName
        : (typeof rawName === 'number' || typeof rawName === 'boolean') ? String(rawName)
            : id;
    return {
        id,
        name,
        description: frontmatter.description ?? '',
        role: typeof frontmatter.role === 'string' ? frontmatter.role : undefined,
        model: typeof frontmatter.model === 'string' ? frontmatter.model : undefined,
        preset: typeof frontmatter.preset === 'string' ? frontmatter.preset : undefined,
        maxTurns: typeof frontmatter.max_turns === 'number' ? frontmatter.max_turns : undefined,
        personality: typeof frontmatter.personality === 'string' ? frontmatter.personality : undefined,
        goal: typeof frontmatter.goal === 'string' ? frontmatter.goal : undefined,
        strengths: Array.isArray(frontmatter.strengths) ? frontmatter.strengths.filter((item) => typeof item === 'string') : undefined,
        allowedTools: Array.isArray(frontmatter.allowed_tools) ? frontmatter.allowed_tools.filter((item) => typeof item === 'string') : undefined,
        systemPrompt: body || (typeof frontmatter.system_prompt === 'string' ? frontmatter.system_prompt : ''),
        enabled,
        filePath,
    };
}
function resolveAgentDefinition(agentId, customAgents) {
    const fromCustom = customAgents.find((agent) => agent.id === agentId && agent.enabled);
    if (fromCustom)
        return fromCustom;
    return exports.BUILTIN_AGENT_ROLES.find((agent) => agent.id === agentId);
}
async function writeCustomAgent(projectDir, input) {
    if (!/^[a-z0-9][a-z0-9-_]*$/i.test(input.id))
        throw new Error('Agent id must be alphanumeric with - or _ only.');
    if (!input.systemPrompt.trim())
        throw new Error('systemPrompt is required.');
    const dir = path.join(projectDir, '.harness', 'agents');
    await fs.mkdir(dir, { recursive: true });
    const filePath = path.join(dir, `${input.id}.md`);
    const lines = ['---'];
    lines.push(`id: ${input.id}`);
    lines.push(`name: ${escapeYaml(input.name)}`);
    lines.push(`description: ${escapeYaml(input.description)}`);
    if (input.role)
        lines.push(`role: ${escapeYaml(input.role)}`);
    if (input.model)
        lines.push(`model: ${escapeYaml(input.model)}`);
    if (input.preset)
        lines.push(`preset: ${input.preset}`);
    if (input.personality)
        lines.push(`personality: ${escapeYaml(input.personality)}`);
    if (input.goal)
        lines.push(`goal: ${escapeYaml(input.goal)}`);
    if (input.allowedTools && input.allowedTools.length > 0) {
        lines.push('allowed_tools:');
        for (const tool of input.allowedTools)
            lines.push(`  - ${tool}`);
    }
    lines.push('---', '', input.systemPrompt.trim(), '');
    await (0, atomicFile_1.atomicWriteFile)(filePath, lines.join('\n'));
    return filePath;
}
function escapeYaml(value) {
    if (value.includes(':') || value.includes('#') || value.includes('"')) {
        return JSON.stringify(value);
    }
    return value;
}
function parseSimpleYaml(yaml) {
    const result = {};
    const lines = yaml.split('\n');
    let currentListKey = null;
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if (!line.trim()) {
            currentListKey = null;
            continue;
        }
        if (currentListKey && /^\s+-\s+/.test(line)) {
            const value = line.replace(/^\s+-\s+/, '').replace(/^"|"$/g, '').trim();
            const list = result[currentListKey] ?? [];
            list.push(value);
            result[currentListKey] = list;
            continue;
        }
        currentListKey = null;
        const colon = line.indexOf(':');
        if (colon === -1)
            continue;
        const key = line.slice(0, colon).trim();
        const rawValue = line.slice(colon + 1).trim();
        if (rawValue === '') {
            currentListKey = key;
            result[key] = [];
            continue;
        }
        if (rawValue.startsWith('"') && rawValue.endsWith('"')) {
            result[key] = rawValue.slice(1, -1);
            continue;
        }
        if (rawValue === 'true' || rawValue === 'false') {
            result[key] = rawValue === 'true';
            continue;
        }
        const numeric = Number(rawValue);
        if (rawValue !== '' && !Number.isNaN(numeric) && /^-?\d+(\.\d+)?$/.test(rawValue)) {
            result[key] = numeric;
            continue;
        }
        result[key] = rawValue;
    }
    return result;
}
//# sourceMappingURL=agentLoader.js.map