"use strict";
// Squad channels.
//
// A squad is a persisted group of agents that share a routing policy. The
// model (or any caller) picks an agent for a message via `routeMessage`,
// which evaluates ordered regex rules and falls back to the lead agent when
// nothing matches. Handoff chains track depth so an agent passing work to
// another agent cannot loop forever.
//
// Persisted as `.harness/squads/<id>.json`. Mutations emit events through
// the event store so live WebSocket clients see roster changes.
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.listSquads = listSquads;
exports.getSquad = getSquad;
exports.createSquad = createSquad;
exports.updateSquad = updateSquad;
exports.deleteSquad = deleteSquad;
exports.routeMessage = routeMessage;
exports.planHandoff = planHandoff;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const eventStore_1 = require("../persistence/eventStore");
const DEFAULT_HANDOFF_DEPTH = 3;
const DEFAULT_CONCURRENT_AGENTS = 3;
function squadsDir(projectDir) {
    return path.join(projectDir, '.harness', 'squads');
}
function squadFile(projectDir, id) {
    return path.join(squadsDir(projectDir), `${id}.json`);
}
async function listSquads(projectDir, filter) {
    const dir = squadsDir(projectDir);
    let entries;
    try {
        entries = await fs.readdir(dir, { withFileTypes: true });
    }
    catch {
        return [];
    }
    const squads = [];
    for (const entry of entries) {
        if (!entry.isFile() || !entry.name.endsWith('.json'))
            continue;
        try {
            const raw = await fs.readFile(path.join(dir, entry.name), 'utf-8');
            squads.push(normalizeSquad(JSON.parse(raw)));
        }
        catch {
            // Skip corrupt files; the read returns successfully with whatever loaded.
        }
    }
    return squads;
}
async function getSquad(projectDir, id) {
    try {
        const raw = await fs.readFile(squadFile(projectDir, id), 'utf-8');
        return normalizeSquad(JSON.parse(raw));
    }
    catch {
        return undefined;
    }
}
async function createSquad(projectDir, input, now = new Date()) {
    if (!input.name?.trim())
        throw new Error('Squad name is required.');
    if (!input.leadAgentId?.trim())
        throw new Error('leadAgentId is required.');
    const id = (input.id?.trim() || crypto.randomUUID()).slice(0, 80);
    if (!/^[a-z0-9][a-z0-9-_]*$/i.test(id))
        throw new Error('Squad id must be alphanumeric with - or _ only.');
    const squad = normalizeSquad({
        id,
        name: input.name.trim(),
        description: input.description?.trim(),
        leadAgentId: input.leadAgentId.trim(),
        roster: input.roster ?? [],
        routingRules: input.routingRules ?? [],
        autonomy: input.autonomy ?? 'supervised',
        maxHandoffDepth: input.maxHandoffDepth ?? DEFAULT_HANDOFF_DEPTH,
        maxConcurrentAgents: input.maxConcurrentAgents ?? DEFAULT_CONCURRENT_AGENTS,
        createdAt: now.toISOString(),
        updatedAt: now.toISOString(),
    });
    const dir = squadsDir(projectDir);
    await fs.mkdir(dir, { recursive: true });
    const fp = squadFile(projectDir, id);
    try {
        await fs.access(fp);
        throw new Error(`Squad ${id} already exists.`);
    }
    catch (error) {
        if (error instanceof Error && error.message.includes('already exists'))
            throw error;
        // ENOENT path — happy case.
    }
    await fs.writeFile(fp, JSON.stringify(squad, null, 2), 'utf-8');
    await (0, eventStore_1.emitEvent)(projectDir, 'system', 'squad.created', { squadId: id, name: squad.name }, 'system', id).catch(() => { });
    return squad;
}
async function updateSquad(projectDir, id, patch, now = new Date()) {
    const existing = await getSquad(projectDir, id);
    if (!existing)
        throw new Error(`Squad not found: ${id}`);
    const merged = normalizeSquad({
        ...existing,
        ...(patch.name !== undefined ? { name: patch.name.trim() } : {}),
        ...(patch.description !== undefined ? { description: patch.description.trim() } : {}),
        ...(patch.leadAgentId !== undefined ? { leadAgentId: patch.leadAgentId.trim() } : {}),
        ...(patch.roster !== undefined ? { roster: patch.roster } : {}),
        ...(patch.routingRules !== undefined ? { routingRules: patch.routingRules } : {}),
        ...(patch.autonomy !== undefined ? { autonomy: patch.autonomy } : {}),
        ...(patch.maxHandoffDepth !== undefined ? { maxHandoffDepth: patch.maxHandoffDepth } : {}),
        ...(patch.maxConcurrentAgents !== undefined ? { maxConcurrentAgents: patch.maxConcurrentAgents } : {}),
        updatedAt: now.toISOString(),
    });
    await fs.writeFile(squadFile(projectDir, id), JSON.stringify(merged, null, 2), 'utf-8');
    await (0, eventStore_1.emitEvent)(projectDir, 'system', 'squad.updated', { squadId: id }, 'system', id).catch(() => { });
    return merged;
}
async function deleteSquad(projectDir, id) {
    try {
        await fs.unlink(squadFile(projectDir, id));
        await (0, eventStore_1.emitEvent)(projectDir, 'system', 'squad.deleted', { squadId: id }, 'system', id).catch(() => { });
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Pick an agent for the message. Iterates routing rules in priority order
 * (highest first); first regex match wins. When nothing matches, the lead
 * agent is returned as a fallback.
 */
function routeMessage(squad, message) {
    const sortedRules = [...squad.routingRules].sort((a, b) => b.priority - a.priority);
    for (const rule of sortedRules) {
        let regex;
        try {
            regex = new RegExp(rule.pattern, 'i');
        }
        catch {
            continue;
        }
        if (regex.test(message)) {
            return {
                agentId: rule.agentId,
                reason: `matched rule /${rule.pattern}/i (priority=${rule.priority})`,
                matchedRule: rule,
                isFallback: false,
            };
        }
    }
    return {
        agentId: squad.leadAgentId,
        reason: 'no rule matched — routed to lead agent',
        isFallback: true,
    };
}
/**
 * Decide whether an agent is allowed to hand off work to another agent.
 * Hard ceiling on chain depth prevents loops. Both agents must appear in
 * the squad roster (or be the lead).
 */
function planHandoff(squad, fromAgentId, toAgentId, currentDepth) {
    if (currentDepth >= squad.maxHandoffDepth) {
        return { fromAgentId, toAgentId, depth: currentDepth, allowed: false, reason: `handoff depth ${currentDepth} would exceed limit ${squad.maxHandoffDepth}` };
    }
    if (fromAgentId === toAgentId) {
        return { fromAgentId, toAgentId, depth: currentDepth, allowed: false, reason: 'cannot hand off to self' };
    }
    const knownAgents = new Set([squad.leadAgentId, ...squad.roster.map((slot) => slot.agentId)]);
    if (!knownAgents.has(fromAgentId)) {
        return { fromAgentId, toAgentId, depth: currentDepth, allowed: false, reason: `from-agent ${fromAgentId} is not on the squad roster` };
    }
    if (!knownAgents.has(toAgentId)) {
        return { fromAgentId, toAgentId, depth: currentDepth, allowed: false, reason: `to-agent ${toAgentId} is not on the squad roster` };
    }
    return { fromAgentId, toAgentId, depth: currentDepth + 1, allowed: true, reason: `handoff approved (depth ${currentDepth + 1}/${squad.maxHandoffDepth})` };
}
function normalizeSquad(value) {
    return {
        id: String(value.id ?? '').slice(0, 80),
        name: String(value.name ?? '').trim() || 'Untitled Squad',
        description: value.description?.toString().trim() || undefined,
        leadAgentId: String(value.leadAgentId ?? '').trim(),
        roster: Array.isArray(value.roster) ? value.roster.filter(isAgentSlotLike).map(normalizeSlot) : [],
        routingRules: Array.isArray(value.routingRules) ? value.routingRules.filter(isRuleLike).map(normalizeRule) : [],
        autonomy: normalizeAutonomy(value.autonomy),
        maxHandoffDepth: clamp(value.maxHandoffDepth, 1, 10, DEFAULT_HANDOFF_DEPTH),
        maxConcurrentAgents: clamp(value.maxConcurrentAgents, 1, 16, DEFAULT_CONCURRENT_AGENTS),
        companyId: value.companyId || undefined,
        orgChart: Array.isArray(value.orgChart) ? value.orgChart : undefined,
        createdAt: typeof value.createdAt === 'string' ? value.createdAt : new Date().toISOString(),
        updatedAt: typeof value.updatedAt === 'string' ? value.updatedAt : new Date().toISOString(),
    };
}
function isAgentSlotLike(value) {
    if (!value || typeof value !== 'object')
        return false;
    const v = value;
    return typeof v.agentId === 'string' && typeof v.role === 'string';
}
function normalizeSlot(slot) {
    return {
        agentId: slot.agentId.trim(),
        role: slot.role.trim(),
        capabilities: Array.isArray(slot.capabilities) ? slot.capabilities.filter((cap) => typeof cap === 'string') : [],
    };
}
function isRuleLike(value) {
    if (!value || typeof value !== 'object')
        return false;
    const v = value;
    return typeof v.pattern === 'string' && typeof v.agentId === 'string';
}
function normalizeRule(rule) {
    return {
        pattern: rule.pattern,
        agentId: rule.agentId.trim(),
        priority: Number.isFinite(rule.priority) ? Math.floor(rule.priority) : 0,
    };
}
function normalizeAutonomy(value) {
    if (value === 'autonomous' || value === 'semi-autonomous' || value === 'supervised')
        return value;
    return 'supervised';
}
function clamp(value, min, max, fallback) {
    const n = Number(value);
    if (!Number.isFinite(n))
        return fallback;
    return Math.max(min, Math.min(max, Math.floor(n)));
}
//# sourceMappingURL=squad.js.map