/**
 * Swarm Coordinator — Event Bridge & Autonomous Coordination Loop
 *
 * Bridges PTY session events to:
 * 1. SSE clients (frontend dashboard) for real-time status
 * 2. LLM coordination decisions for unhandled blocking prompts
 *
 * The coordinator subscribes to PTYService session events and:
 * - Skips events already handled by auto-response rules (autoResponded=true)
 * - Routes unhandled blocking prompts through supervision levels:
 *   - autonomous: LLM decides immediately
 *   - confirm: queued for human approval
 *   - notify: broadcast only (no action)
 *
 * Heavy logic is extracted into:
 * - swarm-decision-loop.ts  (blocked, turn-complete, LLM decisions)
 * - swarm-idle-watchdog.ts  (idle session scanning)
 *
 * @module services/swarm-coordinator
 */
import type { ServerResponse } from "node:http";
import type { IAgentRuntime } from "@elizaos/core";
import type { PTYService } from "./pty-service.ts";
import type { CodingAgentType } from "./pty-types.ts";
import type { CoordinationLLMResponse, SharedDecision } from "./swarm-coordinator-prompts.ts";
import { SwarmHistory } from "./swarm-history.ts";
import { type CreateTaskThreadInput, type TaskNodeRecord, TaskRegistry, type TaskThreadDetail, type TaskThreadStatus, type TaskThreadSummary } from "./task-registry.ts";
/** Callback injected by server.ts to route chat messages to the user's conversation. */
export type ChatMessageCallback = (text: string, source?: string) => Promise<void>;
/** Callback injected by server.ts to relay coordinator events to WebSocket clients. */
export type WsBroadcastCallback = (event: SwarmEvent) => void;
/**
 * Callback injected by server.ts to route coordinator events through
 * Milaidy's full ElizaOS pipeline (conversation memory, personality, actions).
 * Returns a CoordinationLLMResponse parsed from Milaidy's natural language
 * response, or null if no actionable JSON block was found.
 */
export type AgentDecisionCallback = (eventDescription: string, sessionId: string, taskContext: TaskContext) => Promise<CoordinationLLMResponse | null>;
/** Per-task summary included in the swarm complete payload. */
export interface TaskCompletionSummary {
    sessionId: string;
    label: string;
    agentType: string;
    originalTask: string;
    status: string;
    completionSummary: string;
}
/** Callback fired when all tasks in a swarm reach terminal state. */
export type SwarmCompleteCallback = (payload: {
    tasks: TaskCompletionSummary[];
    total: number;
    completed: number;
    stopped: number;
    errored: number;
}) => Promise<void>;
export type SupervisionLevel = "autonomous" | "confirm" | "notify";
export interface TaskContext {
    threadId: string;
    taskNodeId?: string;
    sessionId: string;
    agentType: CodingAgentType;
    label: string;
    originalTask: string;
    workdir: string;
    /** Repository URL if provided, undefined for scratch directory tasks. */
    repo?: string;
    status: "active" | "blocked" | "tool_running" | "completed" | "error" | "stopped";
    decisions: CoordinationDecision[];
    autoResolvedCount: number;
    registeredAt: number;
    /** Timestamp of the last session event (any type). Used by idle watchdog. */
    lastActivityAt: number;
    /** How many idle checks have been performed on this session. */
    idleCheckCount: number;
    /** True once the initial task has been delivered to the agent. */
    taskDelivered: boolean;
    /** Summary of what the agent accomplished, populated on completion. */
    completionSummary?: string;
    /** Index into sharedDecisions[] — tracks which decisions this agent has already seen. */
    lastSeenDecisionIndex: number;
    /** Timestamp of last coordinator-sent input. Used to suppress stall/turn-complete
     *  events for a grace period so the agent has time to process the input. */
    lastInputSentAt?: number;
    /** Timestamp when the task was last transitioned to `stopped`. */
    stoppedAt?: number;
    /** Suppress the generic stop notice when the session is intentionally replaced. */
    suppressStopNotice?: boolean;
}
export interface CoordinationDecision {
    timestamp: number;
    event: string;
    promptText: string;
    decision: "respond" | "escalate" | "ignore" | "complete" | "auto_resolved" | "stopped";
    response?: string;
    reasoning: string;
}
export interface SwarmEvent {
    type: string;
    sessionId: string;
    timestamp: number;
    data: unknown;
}
export interface PendingDecision {
    sessionId: string;
    promptText: string;
    recentOutput: string;
    llmDecision: CoordinationLLMResponse;
    taskContext: TaskContext;
    createdAt: number;
}
/**
 * Context interface exposing internal state and helpers to extracted modules.
 * Implemented by SwarmCoordinator — passed as `this` to module-level functions.
 */
export interface SwarmCoordinatorContext {
    readonly runtime: IAgentRuntime;
    readonly ptyService: PTYService | null;
    readonly taskRegistry: TaskRegistry;
    readonly tasks: Map<string, TaskContext>;
    readonly inFlightDecisions: Set<string>;
    readonly pendingDecisions: Map<string, PendingDecision>;
    /** Buffered task_complete events that arrived while an in-flight decision was running. */
    readonly pendingTurnComplete: Map<string, unknown>;
    /** Fingerprint of the last blocked prompt per session — for re-render dedup. */
    readonly lastBlockedPromptFingerprint: Map<string, string>;
    /** Buffered blocked events that arrived while an in-flight decision was running. */
    readonly pendingBlocked: Map<string, unknown>;
    /** Last-seen output snapshot per session — used by idle watchdog. */
    readonly lastSeenOutput: Map<string, string>;
    /** Timestamp of last tool_running chat notification per session — for throttling. */
    readonly lastToolNotification: Map<string, number>;
    /** Whether LLM decisions are paused (user sent a chat message). */
    readonly isPaused: boolean;
    /** Significant decisions shared across the swarm. */
    readonly sharedDecisions: SharedDecision[];
    /** Get the shared context brief from the planning phase. */
    getSwarmContext(): string;
    /**
     * Guard flag: whether the swarm_complete event has already been fired
     * for the current swarm lifecycle. Set to `true` by `checkAllTasksComplete()`
     * when all tasks reach terminal state. Reset to `false` by:
     * - `stop()` — full coordinator teardown
     * - `registerTask()` — when detecting a new swarm (all previous tasks terminal)
     */
    swarmCompleteNotified: boolean;
    broadcast(event: SwarmEvent): void;
    sendChatMessage(text: string, source?: string): void;
    log(message: string): void;
    getSupervisionLevel(): SupervisionLevel;
    getAgentDecisionCallback(): AgentDecisionCallback | null;
    getSwarmCompleteCallback(): SwarmCompleteCallback | null;
    recordDecision(taskCtx: TaskContext, decision: CoordinationDecision): Promise<void>;
    syncTaskContext(taskCtx: TaskContext): Promise<void>;
}
export declare class SwarmCoordinator implements SwarmCoordinatorContext {
    static serviceType: string;
    readonly runtime: IAgentRuntime;
    readonly taskRegistry: TaskRegistry;
    ptyService: PTYService | null;
    private unsubscribeEvents;
    /** Per-session task context. */
    readonly tasks: Map<string, TaskContext>;
    /** SSE clients receiving live events. */
    private sseClients;
    /** Supervision level (default: autonomous). */
    private supervisionLevel;
    /** Pending confirmations for "confirm" mode. */
    readonly pendingDecisions: Map<string, PendingDecision>;
    /** In-flight decision lock — prevents parallel LLM calls for same session. */
    readonly inFlightDecisions: Set<string>;
    /** Buffered task_complete events that arrived while an in-flight decision was running. */
    readonly pendingTurnComplete: Map<string, unknown>;
    /** Fingerprint of the last blocked prompt per session — for re-render dedup. */
    readonly lastBlockedPromptFingerprint: Map<string, string>;
    /** Buffered blocked events that arrived while an in-flight decision was running. */
    readonly pendingBlocked: Map<string, unknown>;
    /** Callback to send chat messages to the user's conversation UI. */
    private chatCallback;
    /** Callback to relay coordinator events to WebSocket clients. */
    private wsBroadcast;
    /** Callback to route coordinator events through Milaidy's full pipeline. */
    private agentDecisionCb;
    /** Callback fired when all swarm tasks complete — for synthesis. */
    private swarmCompleteCb;
    /** Buffer for events arriving before task registration. */
    private unregisteredBuffer;
    /** Idle watchdog timer handle. */
    private idleWatchdogTimer;
    /** Last-seen output snapshot per session — used by idle watchdog to detect data flow. */
    readonly lastSeenOutput: Map<string, string>;
    /** Timestamp of last tool_running chat notification per session — for throttling. */
    readonly lastToolNotification: Map<string, number>;
    /** Whether LLM decisions are paused (user sent a chat message). */
    private _paused;
    /** Significant decisions shared across the swarm (Layer 2). */
    readonly sharedDecisions: SharedDecision[];
    /** Shared context brief generated during swarm planning phase. */
    private _swarmContext;
    /** @see SwarmCoordinatorContext.swarmCompleteNotified */
    swarmCompleteNotified: boolean;
    /** Buffered events during pause — replayed on resume. */
    private pauseBuffer;
    /** Buffered broadcasts waiting for wsBroadcast to be wired. */
    private preBridgeBroadcastBuffer;
    /** Auto-resume timeout handle. */
    private pauseTimeout;
    /** Coordinator startup timestamp — ignore events from sessions created before this. */
    private readonly startedAt;
    /** Active retry timers for unregistered session buffers. */
    private unregisteredRetryTimers;
    /** Turn-complete coalescing timers — debounces rapid events per session. */
    private turnCompleteCoalesceTimers;
    /** Persistent swarm history — JSONL log that survives restarts. */
    readonly history: SwarmHistory;
    constructor(runtime: IAgentRuntime);
    /** Inject a callback (from server.ts) to route messages to the user's chat UI. */
    /** Track whether we've already wired the scratch decision callback. */
    private scratchDecisionWired;
    setChatCallback(cb: ChatMessageCallback): void;
    /**
     * Wire the scratch workspace save prompt callback.
     * Called eagerly from setChatCallback and lazily from handleSessionEvent
     * in case the workspace service wasn't ready at chat-callback time.
     */
    private wireScratchDecisionCallback;
    /** Inject a callback (from server.ts) to relay events to WebSocket clients. */
    setWsBroadcast(cb: WsBroadcastCallback): void;
    /** Inject a callback fired when all swarm tasks reach terminal state. */
    setSwarmCompleteCallback(cb: SwarmCompleteCallback): void;
    /** Return the swarm complete callback (if wired). */
    getSwarmCompleteCallback(): SwarmCompleteCallback | null;
    /** Set the shared context brief for this swarm. */
    setSwarmContext(context: string): void;
    /** Return the swarm planning context (if set). */
    getSwarmContext(): string;
    /** Inject a callback (from server.ts) to route events through Milaidy's pipeline. */
    setAgentDecisionCallback(cb: AgentDecisionCallback): void;
    /** Return the agent decision callback (if wired). */
    getAgentDecisionCallback(): AgentDecisionCallback | null;
    /** Null-safe wrapper — sends a message to the user's conversation if callback is set. */
    sendChatMessage(text: string, source?: string): void;
    /**
     * Initialize the coordinator by subscribing to PTY session events.
     * Called from plugin init after services are ready.
     */
    start(ptyService: PTYService): Promise<void>;
    private restorePendingTaskContext;
    private restorePendingLlmDecision;
    private rehydratePendingDecisions;
    stop(): Promise<void>;
    /** Whether the coordinator is currently paused. */
    get isPaused(): boolean;
    /** Pause LLM-based decisions. Auto-responses and broadcasts continue. */
    pause(): void;
    /** Resume LLM-based decisions and replay buffered events. */
    resume(): void;
    registerTask(sessionId: string, context: {
        threadId?: string;
        taskNodeId?: string;
        agentType: CodingAgentType;
        label: string;
        originalTask: string;
        workdir: string;
        repo?: string;
        providerSource?: string | null;
        metadata?: Record<string, unknown>;
    }): Promise<void>;
    /**
     * Return the repo URL from the most recently registered task that had one.
     * Useful as a fallback when the user says "in the same repo" without a URL.
     */
    /**
     * Persisted separately from tasks so it survives task cleanup.
     * Updated whenever a task with a repo is registered.
     */
    private _lastUsedRepo;
    getLastUsedRepo(): string | undefined;
    /**
     * Async version that also checks disk history — survives process restarts.
     * Callers that can await should prefer this over the sync version.
     */
    getLastUsedRepoAsync(): Promise<string | undefined>;
    getTaskContext(sessionId: string): TaskContext | undefined;
    private mapDecisionRecord;
    private mapSessionStatus;
    private mapTaskContextStatusToNodeStatus;
    private buildTaskContextFromSession;
    getTaskContextSnapshot(sessionId: string): Promise<TaskContext | null>;
    getAllTaskContexts(): TaskContext[];
    createTaskThread(input: CreateTaskThreadInput): Promise<TaskThreadSummary>;
    planTaskThreadGraph(input: {
        threadId: string;
        title: string;
        originalRequest: string;
        sharedContext?: string;
        subtasks: Array<{
            label: string;
            originalTask: string;
            agentType: CodingAgentType;
            repo?: string;
        }>;
    }): Promise<{
        rootNode: TaskNodeRecord;
        workerNodes: TaskNodeRecord[];
    }>;
    listTaskThreads(options?: {
        includeArchived?: boolean;
        status?: TaskThreadStatus;
        statuses?: TaskThreadStatus[];
        kind?: import("./task-registry.js").TaskThreadKind;
        roomId?: string;
        worldId?: string;
        ownerUserId?: string;
        scenarioId?: string;
        batchId?: string;
        createdAfter?: string;
        createdBefore?: string;
        updatedAfter?: string;
        updatedBefore?: string;
        latestActivityAfter?: number;
        latestActivityBefore?: number;
        hasActiveSession?: boolean;
        search?: string;
        limit?: number;
    }): Promise<TaskThreadSummary[]>;
    getTaskThread(threadId: string): Promise<TaskThreadDetail | null>;
    archiveTaskThread(threadId: string): Promise<void>;
    reopenTaskThread(threadId: string): Promise<void>;
    countTaskThreads(options?: {
        includeArchived?: boolean;
        status?: TaskThreadStatus;
        statuses?: TaskThreadStatus[];
        kind?: import("./task-registry.js").TaskThreadKind;
        roomId?: string;
        worldId?: string;
        ownerUserId?: string;
        scenarioId?: string;
        batchId?: string;
        createdAfter?: string;
        createdBefore?: string;
        updatedAfter?: string;
        updatedBefore?: string;
        latestActivityAfter?: number;
        latestActivityBefore?: number;
        hasActiveSession?: boolean;
        search?: string;
    }): Promise<number>;
    private getLiveTaskContextsForThread;
    private stopLiveThreadSessions;
    private clipText;
    private formatResumePrompt;
    pauseTaskThread(threadId: string, note?: string): Promise<{
        threadId: string;
        stoppedSessionIds: string[];
    }>;
    stopTaskThread(threadId: string, note?: string): Promise<{
        threadId: string;
        stoppedSessionIds: string[];
    }>;
    resumeTaskThread(threadId: string, instruction?: string, agentType?: string): Promise<{
        threadId: string;
        sessionId: string;
        reusedSession: boolean;
        framework: CodingAgentType;
    }>;
    continueTaskThread(threadId: string, instruction: string, agentType?: string): Promise<{
        threadId: string;
        sessionId: string;
        reusedSession: boolean;
        framework: CodingAgentType;
    }>;
    syncTaskContext(taskCtx: TaskContext): Promise<void>;
    private isAutomaticFailoverFramework;
    private getFailoverCandidates;
    private getRecoveryCandidates;
    private shouldPreferAlternativeFrameworkForError;
    private formatFailoverPrompt;
    private formatErrorRecoveryPrompt;
    private handleFrameworkDepletion;
    private attemptTaskFailover;
    private attemptTaskRecovery;
    resumeTaskAfterProviderAuth(sessionId: string, reason: string): Promise<{
        replacementSessionId: string;
        replacementFramework: CodingAgentType;
        replacementLabel: string;
    } | null>;
    markTaskResumedAfterProviderAuth(sessionId: string): Promise<boolean>;
    recordDecision(taskCtx: TaskContext, decision: CoordinationDecision): Promise<void>;
    setTaskDelivered(sessionId: string): Promise<void>;
    /**
     * Schedule a retry check for buffered events from an unregistered session.
     * Uses exponential backoff: 2s → 4s → 8s → 16s, max 30s total.
     */
    private scheduleUnregisteredRetry;
    /**
     * Register an SSE client. Returns an unsubscribe function.
     * Sends a snapshot of current state on connect.
     */
    addSseClient(res: ServerResponse): () => void;
    broadcast(event: SwarmEvent): void;
    private writeSseEvent;
    handleSessionEvent(sessionId: string, event: string, data: unknown): Promise<void>;
    private handleNormalizedSessionEvent;
    makeCoordinationDecision(taskCtx: TaskContext, promptText: string, recentOutput: string): Promise<CoordinationLLMResponse | null>;
    executeDecision(sessionId: string, decision: CoordinationLLMResponse): Promise<void>;
    /**
     * Public entry point for external callers (e.g. server.ts) to execute
     * a coordination decision on a session. Wraps the internal executeDecision.
     */
    executeEventDecision(sessionId: string, decision: CoordinationLLMResponse): Promise<void>;
    setSupervisionLevel(level: SupervisionLevel): void;
    getSupervisionLevel(): SupervisionLevel;
    getPendingConfirmations(): PendingDecision[];
    confirmDecision(sessionId: string, approved: boolean, override?: {
        response?: string;
        useKeys?: boolean;
        keys?: string[];
    }): Promise<void>;
    log(message: string): void;
}
//# sourceMappingURL=swarm-coordinator.d.ts.map