// multi-agent/task-router.ts — Dynamic task routing based on keywords (SPEC-055)
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
function loadCatalog() {
    // Load from JSON config — no external deps, bundled with project
    return require('../../config/agent-capabilities-catalog.json');
}
/**
 * C4 + C6 + C7: Route a task description to the best available agent.
 * Returns a TaskRoute with assigned agent, model tier, and confidence score.
 * If the task requires multiple agents (C5), decomposition is included.
 */
export function routeTask(taskDescription, availableAgents) {
    const catalog = loadCatalog();
    const taskId = generateTaskId(taskDescription);
    const lower = taskDescription.toLowerCase();
    // C5: Detect compound tasks that need decomposition
    if (requiresDecomposition(lower, catalog)) {
        return buildDecomposedRoute(taskId, lower, availableAgents, catalog);
    }
    const model = inferModel(lower, catalog);
    const role = inferRole(lower, catalog);
    const { agent, confidence } = findBestAgent(role, model, availableAgents);
    return {
        taskId,
        assignedAgent: agent,
        model,
        confidence,
    };
}
// ---------------------------------------------------------------------------
// Decomposition
// ---------------------------------------------------------------------------
/**
 * C5: A task requires decomposition when it contains paired verb categories
 * from different roles (e.g. "implement" + "test", "build" + "review").
 */
function requiresDecomposition(lower, catalog) {
    const matchedRoles = new Set();
    for (const [roleName, roleDef] of Object.entries(catalog.standardRoles)) {
        for (const keyword of roleDef.keywords) {
            if (lower.includes(keyword)) {
                matchedRoles.add(roleName);
                break;
            }
        }
    }
    // Decompose only when at least two distinct roles are detected
    return matchedRoles.size >= 2;
}
function buildDecomposedRoute(taskId, lower, availableAgents, catalog) {
    const subtasks = detectSubtasks(lower, catalog);
    const decomposition = subtasks.map((sub, idx) => {
        const { agent, confidence } = findBestAgent(sub.role, sub.model, availableAgents);
        return {
            taskId: `${taskId}-sub${idx + 1}`,
            assignedAgent: agent,
            model: sub.model,
            confidence,
        };
    });
    // Parent route uses the first sub-task's agent and the highest model tier needed
    const primarySub = decomposition[0];
    const primaryAgent = primarySub?.assignedAgent ?? 'unassigned';
    const primaryModel = highestModel(decomposition.map((d) => d.model));
    const avgConfidence = decomposition.length > 0
        ? decomposition.reduce((sum, d) => sum + d.confidence, 0) / decomposition.length
        : 0;
    return {
        taskId,
        assignedAgent: primaryAgent,
        model: primaryModel,
        confidence: round2(avgConfidence),
        decomposition,
    };
}
function detectSubtasks(lower, catalog) {
    const result = [];
    for (const [roleName, roleDef] of Object.entries(catalog.standardRoles)) {
        for (const keyword of roleDef.keywords) {
            if (lower.includes(keyword)) {
                result.push({ role: roleName, model: roleDef.model });
                break;
            }
        }
    }
    return result;
}
// ---------------------------------------------------------------------------
// Model + role inference (C7)
// ---------------------------------------------------------------------------
function inferModel(lower, catalog) {
    for (const [, roleDef] of Object.entries(catalog.standardRoles)) {
        for (const keyword of roleDef.keywords) {
            if (lower.includes(keyword)) {
                return roleDef.model;
            }
        }
    }
    // Default: sonnet for unrecognized tasks
    return 'sonnet';
}
function inferRole(lower, catalog) {
    for (const [roleName, roleDef] of Object.entries(catalog.standardRoles)) {
        for (const keyword of roleDef.keywords) {
            if (lower.includes(keyword)) {
                return roleName;
            }
        }
    }
    return 'implementer';
}
// ---------------------------------------------------------------------------
// Agent selection
// ---------------------------------------------------------------------------
function findBestAgent(role, model, agents) {
    if (agents.length === 0) {
        return { agent: 'unassigned', confidence: 0 };
    }
    // Prefer agents whose role matches exactly and model tier matches
    const exact = agents.find((a) => a.role === role && a.preferredModel === model);
    if (exact !== undefined) {
        return { agent: exact.agentId, confidence: 0.95 };
    }
    // Fall back to role match only
    const roleMatch = agents.find((a) => a.role === role);
    if (roleMatch !== undefined) {
        return { agent: roleMatch.agentId, confidence: 0.75 };
    }
    // Fall back to model tier match
    const modelMatch = agents.find((a) => a.preferredModel === model);
    if (modelMatch !== undefined) {
        return { agent: modelMatch.agentId, confidence: 0.5 };
    }
    // Last resort: first available agent (agents.length > 0 is guaranteed above)
    const first = agents[0];
    /* v8 ignore next 3 */
    if (first === undefined) {
        return { agent: 'unassigned', confidence: 0 };
    }
    return { agent: first.agentId, confidence: 0.25 };
}
// ---------------------------------------------------------------------------
// Utilities
// ---------------------------------------------------------------------------
const MODEL_ORDER = ['haiku', 'sonnet', 'opus'];
function highestModel(models) {
    let best = 'haiku';
    for (const m of models) {
        if (MODEL_ORDER.indexOf(m) > MODEL_ORDER.indexOf(best)) {
            best = m;
        }
    }
    return best;
}
function generateTaskId(description) {
    const slug = description
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '')
        .slice(0, 40);
    return `task-${slug}-${Date.now().toString(36)}`;
}
function round2(value) {
    return Math.round(value * 100) / 100;
}
//# sourceMappingURL=task-router.js.map