// orchestrator/runtime.ts — Orchestration lifecycle manager (SPEC-079 AC-01, AC-08, AC-13)
// Manages init, spawn, monitor, collect, finalize for orchestration sessions.
import { decideAgentParallelism } from '../system-resources/spawn-policy.js';
import { sendTelemetryEvent } from '../telemetry/telemetry-client.js';
import { planExecution } from './topologies.js';
import { distributeTasks } from './task-distributor.js';
import { createAgentProcess, buildSpawnDescriptor, spawnAgent, parseTokenUsage, } from './agent-spawner.js';
import { generateFileOwnershipMap } from './file-ownership.js';
import { buildOrchestrationResult } from './result-collector.js';
import { generateAllHandoffs, formatHandoffAsPrompt } from './handoff-generator.js';
import { ProgressTracker } from './progress-tracker.js';
/** In-memory store for active orchestration sessions. */
const sessions = new Map();
let sessionCounter = 0;
/** Generate a unique session ID. */
function generateSessionId() {
    sessionCounter++;
    return `orch-${Date.now()}-${String(sessionCounter)}`;
}
/**
 * Start a new orchestration session.
 * If dryRun is true, plans execution without spawning agents.
 */
export async function startOrchestration(config) {
    const sessionId = generateSessionId();
    const startTime = Date.now();
    // Distribute tasks to agents
    const agents = config.agents ?? buildDefaultAgents(config.tasks.length);
    const assignments = distributeTasks(config.tasks, agents);
    // Generate file ownership map
    const { map: ownershipMap } = generateFileOwnershipMap(assignments);
    // Create agent processes
    const agentProcesses = agents.map((a) => createAgentProcess(a.id));
    // Initialize session
    const session = {
        id: sessionId,
        topology: config.topology,
        config,
        agents: agentProcesses,
        tasks: assignments,
        status: 'initializing',
        startedAt: new Date(startTime).toISOString(),
        fileOwnershipMap: ownershipMap,
    };
    sessions.set(sessionId, session);
    // Plan execution graph
    const executionGraph = planExecution(config.topology, assignments, agentProcesses);
    // Initialize progress tracker
    const tracker = new ProgressTracker(sessionId);
    tracker.init(agentProcesses, assignments);
    if (config.options.dryRun) {
        return buildDryRunResult(sessionId, agentProcesses, assignments, executionGraph, startTime);
    }
    // Execute orchestration
    sessions.set(sessionId, { ...session, status: 'running' });
    const executionResult = await executeOrchestration(session, agents, tracker, config);
    // Finalize — update session with results
    const finalAgents = executionResult.agents.map((a) => ({
        ...a,
        tokensUsed: a.tokensUsed ?? parseTokenUsage(a.stdout),
    }));
    const finalStatus = executionResult.failFast
        ? 'failed'
        : determineSessionStatus(finalAgents);
    sessions.set(sessionId, {
        ...session,
        agents: finalAgents,
        status: finalStatus,
        completedAt: new Date().toISOString(),
    });
    return buildOrchestrationResult(sessionId, finalAgents, assignments, config.options.conflictStrategy, startTime);
}
/**
 * SPEC-705: Run tasks with a maximum concurrency limit (p-limit-style).
 * Returns PromiseSettledResult[] in same order as items.
 */
async function runWithConcurrencyLimit(items, concurrency, fn) {
    if (concurrency >= items.length) {
        return Promise.allSettled(items.map(fn));
    }
    // Using Array.from to avoid unsafe `new Array()` assignment (no-unsafe-assignment)
    const results = Array.from({ length: items.length });
    let nextIndex = 0;
    async function worker() {
        while (nextIndex < items.length) {
            const idx = nextIndex++;
            const item = items[idx];
            if (item === undefined) {
                break;
            }
            try {
                results[idx] = { status: 'fulfilled', value: await fn(item) };
            }
            catch (reason) {
                results[idx] = { status: 'rejected', reason };
            }
        }
    }
    await Promise.all(Array.from({ length: Math.max(1, concurrency) }, () => worker()));
    return results;
}
/**
 * Execute the orchestration by spawning agents per the execution graph.
 * Tasks run in parallel (Promise.allSettled). When failFast is true and any
 * agent fails, the result is flagged so the caller can mark the session failed.
 * Returns updated agents and whether failFast was triggered.
 */
async function executeOrchestration(session, agentConfigs, tracker, config) {
    const handoffs = generateAllHandoffs(agentConfigs, session.tasks, session.fileOwnershipMap);
    // Index agents by ID for O(1) lookups without shared-array mutation during parallelism
    const agentMap = new Map(session.agents.map((a) => [a.agentId, a]));
    /**
     * Execute a single task assignment and update the shared agentMap.
     * Returns true if the agent failed (used for failFast detection).
     */
    async function executeTask(assignment) {
        const agentConfig = agentConfigs.find((a) => a.id === assignment.agentId);
        if (!agentConfig) {
            return false;
        }
        const currentAgent = agentMap.get(assignment.agentId);
        if (!currentAgent) {
            return false;
        }
        const handoff = handoffs.find((h) => h.agentId === assignment.agentId);
        const prompt = handoff ? formatHandoffAsPrompt(handoff) : assignment.context;
        tracker.updateTaskStatus(assignment.taskId, 'in_progress');
        const updatedAgent = await spawnWithRetry(agentConfig, currentAgent, prompt, config, tracker);
        agentMap.set(assignment.agentId, updatedAgent);
        const taskStatus = updatedAgent.status === 'completed' ? 'completed' : 'failed';
        tracker.updateTaskStatus(assignment.taskId, taskStatus);
        return updatedAgent.status === 'failed';
    }
    // SPEC-705: Gate parallel spawn on available host RAM.
    const ramDecision = await decideAgentParallelism({ requested: session.tasks.length });
    if (ramDecision.allowed < ramDecision.requested) {
        sendTelemetryEvent({
            event: 'ram_guardrail_downgrade',
            installationId: 'anonymous',
            properties: {
                requested: ramDecision.requested,
                allowed: ramDecision.allowed,
                freeGBObserved: ramDecision.freeGBObserved,
                pressureLevel: ramDecision.pressureLevel,
                context: 'agent_team',
            },
        });
    }
    const concurrency = ramDecision.allowed;
    // Run all tasks with RAM-aware concurrency limit
    const outcomes = await runWithConcurrencyLimit(session.tasks, concurrency, (assignment) => executeTask(assignment));
    // Reconstruct agents array preserving original order
    const updatedAgents = session.agents.map((a) => agentMap.get(a.agentId) ?? a);
    // Detect any task-level failure for failFast evaluation
    const anyFailed = outcomes.some((outcome) => outcome.status === 'fulfilled' && outcome.value);
    return {
        agents: updatedAgents,
        failFast: config.options.failFast && anyFailed,
    };
}
/**
 * Spawn an agent with retry logic (AC-08).
 */
async function spawnWithRetry(agentConfig, initialProcess, prompt, config, tracker) {
    const maxRetries = config.options.maxRetries;
    let current = initialProcess;
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
        const descriptor = buildSpawnDescriptor(agentConfig, prompt, config.options.spawnMethod, config.options.timeoutMs);
        tracker.updateAgentStatus(current.agentId, 'spawning');
        const result = await spawnAgent(descriptor, current, (id, status) => {
            tracker.updateAgentStatus(id, status);
        });
        if (result.status === 'completed') {
            return result;
        }
        current = {
            ...result,
            retryCount: attempt + 1,
        };
        if (attempt < maxRetries) {
            // Reset for retry
            current = {
                ...current,
                status: 'pending',
                stdout: '',
                stderr: '',
                exitCode: undefined,
                completedAt: undefined,
            };
        }
    }
    return current;
}
/**
 * Build a dry-run result without spawning processes.
 */
function buildDryRunResult(sessionId, agents, assignments, executionGraph, startTime) {
    const dryRunAgents = agents.map((a) => ({
        ...a,
        status: 'completed',
        completedAt: new Date().toISOString(),
    }));
    return {
        sessionId,
        agents: dryRunAgents,
        tasks: assignments.map((a) => ({
            taskId: a.taskId,
            agentId: a.agentId,
            status: 'completed',
            output: {
                dryRun: true,
                executionGraph,
                assignment: a,
            },
            duration: 0,
        })),
        outputs: { dryRun: true, executionGraph },
        conflicts: [],
        duration: Date.now() - startTime,
        totalCost: { tokens: 0, usd: 0, breakdown: {} },
    };
}
/**
 * Get current status of an orchestration session.
 */
export function getStatus(sessionId) {
    return sessions.get(sessionId);
}
/**
 * List all sessions, optionally filtered by status.
 */
export function listSessions(filter) {
    const all = [...sessions.values()];
    if (!filter) {
        return all;
    }
    return all.filter((s) => s.status === filter);
}
/**
 * Cancel an active orchestration session.
 */
export function cancelOrchestration(sessionId) {
    const session = sessions.get(sessionId);
    if (!session) {
        return false;
    }
    if (session.status === 'completed' || session.status === 'cancelled') {
        return false;
    }
    sessions.set(sessionId, {
        ...session,
        status: 'cancelled',
        completedAt: new Date().toISOString(),
    });
    return true;
}
/**
 * Determine overall session status from agent results.
 */
function determineSessionStatus(agents) {
    if (agents.every((a) => a.status === 'completed')) {
        return 'completed';
    }
    if (agents.every((a) => a.status === 'failed')) {
        return 'failed';
    }
    return 'completed'; // partial success = completed
}
/**
 * Build default agent configs when none are provided.
 */
function buildDefaultAgents(count) {
    return Array.from({ length: count }, (_, i) => ({
        id: `agent-${String(i + 1)}`,
        command: 'claude',
        args: [],
        capabilities: ['general'],
        modelTier: 'sonnet',
    }));
}
/** Clear all sessions (for testing). */
export function clearSessions() {
    sessions.clear();
    sessionCounter = 0;
}
//# sourceMappingURL=runtime.js.map