import { HubAgentAdapter } from "../agent/hub-agent-adapter.js";
import { createAgentTokenToolDefinitions } from "../auth/token-tools.js";
import { McpHost } from "../mcp/mcp-host.js";
import { PeerRegistry } from "../peers/peer-registry.js";
import { createHubTools } from "../tools/index.js";
import { PeerToolBridge } from "../tools/peer-tool-bridge.js";
import { createAgentMessagingToolDefinitions } from "./agent-messaging-tools.js";
import { createChildAgentToolDefinitions } from "./child-agent-tools.js";
import { createGroupToolDefinitions } from "./group-tools.js";
import { createReloadConfigToolDefinition } from "./reload-config-tool.js";
import { createResourceStatusToolDefinition } from "./resource-status-tool.js";
const START_ABORTED_MESSAGE = "HubAgentRuntime: start() aborted by stop()";
const START_AFTER_STOP_MESSAGE = "HubAgentRuntime: cannot start() after stop()";
/**
 * Per-agent shell: session service wiring, peer registry/bridge, tools, and optional hub agent adapter.
 * Intended for future multi-agent `HubRuntime` orchestration; safe to use for a single main agent.
 *
 * **Lifecycle:** each instance is one-shot. After `stop()` finishes, the bridge is disposed and
 * the runtime is closed. To run an agent again, construct a new `HubAgentRuntime` (or have
 * orchestration do so) rather than reusing a stopped instance.
 */
export class HubAgentRuntime {
    record;
    sessionService;
    peerRegistry;
    peerToolBridge;
    tools;
    mcpHost;
    agentAdapter;
    cwd;
    agentDir;
    createAgentAdapterImpl;
    getConfigLayers;
    getChildAgentHost;
    refreshSources;
    refreshMcp;
    model;
    thinkingLevel;
    scopedModels;
    services;
    prepareServices;
    beforeInputQueueDrain;
    logs;
    persistModelRefCallback;
    liveEventSubscribers = new Set();
    remoteMcpToolNames = new Set();
    adapterLiveUnsub;
    startInFlight;
    /** `stop()` sets this so `runStart` can avoid committing an adapter (or can dispose) after a slow `create`. */
    abortStartRequested = false;
    /** `true` from the beginning of `stop()` through full teardown; not cleared until `close()`-equivalent. */
    closed = false;
    stopInFlight;
    peerBridgeActive = true;
    constructor(options) {
        this.record = options.record;
        this.sessionService = options.sessionService;
        this.cwd = options.cwd;
        this.agentDir = options.agentDir;
        this.getConfigLayers = options.getConfigLayers;
        this.peerRegistry = options.peerRegistry ?? new PeerRegistry();
        this.peerToolBridge = new PeerToolBridge(this.record.id, this.peerRegistry, options.socketServer, {
            resolvePeer: (peerId) => options.resolvePeerForTool?.(this.record.id, peerId) ?? this.peerRegistry.get(peerId),
        });
        this.getChildAgentHost = options.getChildAgentHost;
        const isGuest = options.record.kind === "guest";
        const childManagementTools = !isGuest && options.getChildAgentHost != null
            ? createChildAgentToolDefinitions(options.getChildAgentHost, options.record.id)
            : [];
        const agentMessagingTools = options.getAgentMessagingHost != null
            ? createAgentMessagingToolDefinitions(options.getAgentMessagingHost, options.record.id)
            : [];
        const groupTools = options.getGroupHost != null ? createGroupToolDefinitions(options.getGroupHost, options.record.id) : [];
        const allowedGroupTools = isGuest ? groupTools.filter((tool) => tool.name === "group") : groupTools;
        const resourceStatusTool = !isGuest && options.getResourceStatusHost != null
            ? [createResourceStatusToolDefinition(options.getResourceStatusHost, options.record.id)]
            : [];
        const agentTokenTools = !isGuest && options.getAgentTokenHost != null
            ? createAgentTokenToolDefinitions(options.getAgentTokenHost, options.record.id)
            : [];
        const reloadConfigTool = createReloadConfigToolDefinition({
            agentId: options.record.id,
            getAdapter: () => this.agentAdapter,
        });
        const baseAgentTools = isGuest ? [] : [reloadConfigTool];
        this.tools = createHubTools({
            cwd: options.cwd,
            agentId: options.record.id,
            peerRegistry: this.peerRegistry,
            peerToolBridge: this.peerToolBridge,
            allowHostExecutor: () => this.record.hubExecutor !== "disabled",
            includePeerTools: !isGuest,
            configLayers: options.configLayerRegistry,
            peerMcpSnapshots: options.peerMcpSnapshotsGetter,
            sharedTools: options.sharedTools,
            agentTools: [
                ...baseAgentTools,
                ...childManagementTools,
                ...allowedGroupTools,
                ...resourceStatusTool,
                ...agentTokenTools,
                ...agentMessagingTools,
                ...(options.agentTools ?? []),
            ],
        });
        this.mcpHost = options.mcp
            ? new McpHost({
                cwd: options.cwd,
                customTools: this.tools,
                configPath: options.mcp.configPath,
                configRoot: options.mcp.configRoot,
                createClient: options.mcp.createClient,
                ...(options.logs && { logs: options.logs }),
            })
            : undefined;
        this.createAgentAdapterImpl = options.createAgentAdapter;
        this.refreshSources = options.refreshSources;
        this.refreshMcp = options.refreshMcp;
        this.model = options.model;
        this.thinkingLevel = options.thinkingLevel;
        this.scopedModels = options.scopedModels;
        this.services = options.services;
        this.prepareServices = options.prepareServices;
        this.beforeInputQueueDrain = options.beforeInputQueueDrain;
        this.logs = options.logs;
        this.persistModelRefCallback = options.persistModelRef;
    }
    subscribeLiveEvents(listener) {
        this.liveEventSubscribers.add(listener);
        return () => {
            this.liveEventSubscribers.delete(listener);
        };
    }
    async start() {
        if (this.closed) {
            throw new Error(START_AFTER_STOP_MESSAGE);
        }
        if (this.agentAdapter) {
            return;
        }
        if (this.startInFlight) {
            await this.startInFlight;
            if (this.agentAdapter) {
                return;
            }
            if (this.closed) {
                throw new Error(START_AFTER_STOP_MESSAGE);
            }
        }
        this.startInFlight = this.runStart();
        try {
            await this.startInFlight;
        }
        finally {
            this.startInFlight = undefined;
        }
    }
    async runStart() {
        await this.mcpHost?.start();
        this.refreshRemoteMcpTools();
        const create = this.createAgentAdapterImpl ?? ((opts) => HubAgentAdapter.create(opts));
        const adapter = await create({
            agentId: this.record.id,
            cwd: this.cwd,
            agentDir: this.agentDir,
            configLayers: this.getConfigLayers?.(),
            getConfigLayers: this.getConfigLayers,
            getPeerMcpSnapshots: () => this.peerRegistry.list().map((peer) => ({
                peerId: peer.peerId,
                servers: peer.mcpSnapshot?.servers ?? [],
            })),
            sessionService: this.sessionService,
            tools: this.tools,
            services: this.services,
            prepareServices: this.prepareServices,
            beforeInputQueueDrain: this.beforeInputQueueDrain,
            model: this.model,
            persistedModel: this.record.model,
            onModelChange: this.persistModelRefCallback
                ? (modelRef) => {
                    this.record.model = modelRef;
                    this.persistModelRefCallback(this.record.id, modelRef);
                }
                : undefined,
            thinkingLevel: this.thinkingLevel,
            scopedModels: this.scopedModels,
            refreshSources: this.refreshSources,
            refreshMcp: this.refreshMcp ?? (this.mcpHost ? async () => await this.mcpHost?.start() : undefined),
            logs: this.logs,
        });
        if (this.abortStartRequested) {
            adapter.dispose();
            throw new Error(START_ABORTED_MESSAGE);
        }
        this.agentAdapter = adapter;
        this.adapterLiveUnsub = this.agentAdapter.subscribeLiveEvents((event) => {
            for (const listener of this.liveEventSubscribers) {
                listener(event);
            }
        });
    }
    refreshRemoteMcpTools() {
        let writeIndex = 0;
        for (let readIndex = 0; readIndex < this.tools.length; readIndex++) {
            const tool = this.tools[readIndex];
            if (tool && !this.remoteMcpToolNames.has(tool.name)) {
                this.tools[writeIndex] = tool;
                writeIndex++;
            }
        }
        this.tools.length = writeIndex;
        this.remoteMcpToolNames.clear();
    }
    async restartAdapter() {
        if (this.closed) {
            return;
        }
        this.adapterLiveUnsub?.();
        this.adapterLiveUnsub = undefined;
        const adapter = this.agentAdapter;
        if (adapter) {
            try {
                await adapter.abort();
            }
            catch {
                // Restart should still replace the adapter if abort races with provider teardown.
            }
            adapter.dispose();
        }
        this.agentAdapter = undefined;
        await this.start();
    }
    async stop() {
        if (this.closed) {
            return;
        }
        if (this.stopInFlight) {
            await this.stopInFlight;
            return;
        }
        this.stopInFlight = this.runStop();
        try {
            await this.stopInFlight;
        }
        finally {
            this.stopInFlight = undefined;
        }
    }
    async runStop() {
        this.abortStartRequested = true;
        if (this.startInFlight) {
            try {
                await this.startInFlight;
            }
            catch {
                // `runStart` may reject (e.g. start aborted) — proceed with teardown
            }
        }
        this.adapterLiveUnsub?.();
        this.adapterLiveUnsub = undefined;
        const adapter = this.agentAdapter;
        if (adapter) {
            try {
                await adapter.abort();
            }
            catch {
                // Stop must be best-effort: dispose below detaches the runtime even if abort races with provider teardown.
            }
            adapter.dispose();
        }
        this.agentAdapter = undefined;
        await this.mcpHost?.stop();
        if (this.peerBridgeActive) {
            this.peerToolBridge.dispose();
            this.peerBridgeActive = false;
        }
        this.abortStartRequested = false;
        this.closed = true;
    }
}
//# sourceMappingURL=hub-agent-runtime.js.map