import { existsSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
import { SessionManager } from "@sheason/pi-coding-agent";
import { VERSION } from "../../version.js";
import { HubAgentAdapter } from "../agent/hub-agent-adapter.js";
import { AgentRegistry } from "../agents/agent-registry.js";
import { getChildAgentDir, getChildAgentMcpConfigPath, getChildAgentSourcesConfigPath, initializeChildAgentDirectory, } from "../agents/child-agent-layout.js";
import { HubAgentRuntime } from "../agents/hub-agent-runtime.js";
import { ROOT_AGENT_ID } from "../agents/types.js";
import { HubAuthTokenStore, isAuthIdentityAllowedForAgent } from "../auth/token-store.js";
import { getListenHost, getListenPort, getWorkspaceDir } from "../config.js";
import { ConfigLayerRegistry } from "../config-aggregation/config-layer-registry.js";
import { buildAgentConfigLayers } from "../config-aggregation/local-config-layers.js";
import { mergeConfigLayers } from "../config-aggregation/merge-config.js";
import { NodeContainerExecutorLauncher, } from "../executors/container-executor.js";
import { McpHost } from "../mcp/mcp-host.js";
import { MemoryStore } from "../memory/memory-store.js";
import { PeerRegistry } from "../peers/peer-registry.js";
import { HubSessionService } from "../session/hub-session-service.js";
import { loadChildSourcesConfigFromPath, loadSourcesConfigForAgents } from "../sources/source-config.js";
import { SourceHost } from "../sources/source-host.js";
import { createHostPeerRecord } from "../tools/host-peer.js";
import { HUB_PROTOCOL_VERSION } from "../transport/protocol.js";
import { SocketHubServer } from "../transport/socket-hub-server.js";
import { getErrorMessage } from "../utils.js";
import { assertWorkspaceInitialized } from "../workspace.js";
function publicOrgAgentModel(snapshot) {
    const model = snapshot.context.model;
    if (!model) {
        return undefined;
    }
    const availableModel = snapshot.availableModels.find((candidate) => candidate.provider === model.provider && candidate.modelId === model.modelId);
    return {
        provider: model.provider,
        modelId: model.modelId,
        ...(availableModel?.label && { label: availableModel.label }),
    };
}
function compactMemoryText(text, maxLength = 600) {
    const compacted = text.replace(/\s+/g, " ").trim();
    if (compacted.length <= maxLength) {
        return compacted;
    }
    return `${compacted.slice(0, maxLength - 3)}...`;
}
function memoryAgentIdFromMemoryId(memoryId) {
    const separatorIndex = memoryId.indexOf(":");
    if (separatorIndex <= 0) {
        return undefined;
    }
    return memoryId.slice(0, separatorIndex);
}
export class HubRuntime {
    cwd;
    agentRegistry;
    authTokenStore;
    sourceHost;
    mcpHost;
    rootTokenForDisplay;
    socketServer;
    agentRuntimes = new Map();
    configLayers;
    memoryStore;
    adapterUserOptions;
    liveEventUnsubs = [];
    createMcpClient;
    logs;
    containerExecutorLauncher;
    containerExecutorHandles = new Map();
    socketAddress;
    pendingPeerConfigChangesByAgentId = new Map();
    peerConfigApplyInFlightByAgentId = new Map();
    sessionLogUnsubs = new Map();
    memoryIndexUnsubs = new Map();
    temporaryCleanupUnsubs = new Map();
    temporaryCleanupInFlight = new Set();
    childStartInFlightByAgentId = new Map();
    childStartErrorsByAgentId = new Map();
    manuallyStoppedAgentIds = new Set();
    backgroundChildHydrationStarted = false;
    stopping = false;
    sessionFanout = [];
    constructor(cwd, agentRegistry, authTokenStore, rootTokenForDisplay, socketServer, sourceHost, mcpHost, adapterUserOptions, rootRuntime, createMcpClient, logs, containerExecutorLauncher, memoryStore, configLayers) {
        this.cwd = cwd;
        this.agentRegistry = agentRegistry;
        this.authTokenStore = authTokenStore;
        this.rootTokenForDisplay = rootTokenForDisplay;
        this.socketServer = socketServer;
        this.sourceHost = sourceHost;
        this.mcpHost = mcpHost;
        this.adapterUserOptions = adapterUserOptions;
        this.createMcpClient = createMcpClient;
        this.logs = logs;
        this.containerExecutorLauncher = containerExecutorLauncher;
        this.memoryStore = memoryStore;
        this.configLayers = configLayers;
        this.agentRuntimes.set(ROOT_AGENT_ID, rootRuntime);
    }
    log(level, message, details) {
        try {
            this.logs?.[level](message, details);
        }
        catch {
            // Logging is best-effort and must not affect runtime lifecycle.
        }
    }
    get sessionService() {
        return this.getRootAgentRuntime().sessionService;
    }
    get peerRegistry() {
        return this.getRootAgentRuntime().peerRegistry;
    }
    get tools() {
        return this.getRootAgentRuntime().tools;
    }
    get peerToolBridge() {
        return this.getRootAgentRuntime().peerToolBridge;
    }
    get agentAdapter() {
        return this.getRootAgentRuntime().agentAdapter;
    }
    set agentAdapter(value) {
        this.getRootAgentRuntime().agentAdapter = value;
    }
    getAgentRecords() {
        return this.agentRegistry.getAll();
    }
    getRootAgentRuntime() {
        return this.getAgentRuntime(ROOT_AGENT_ID);
    }
    getAgentRuntime(agentId = ROOT_AGENT_ID) {
        const rt = this.agentRuntimes.get(agentId);
        if (!rt) {
            throw new Error(`Unknown agent id: ${agentId}`);
        }
        return rt;
    }
    tryGetAgentRuntime(agentId) {
        return this.agentRuntimes.get(agentId);
    }
    getAgentHydrationStatus(agentId) {
        if (agentId === ROOT_AGENT_ID) {
            return this.getRootAgentRuntime().agentAdapter ? "running" : "loading";
        }
        if (this.childStartInFlightByAgentId.has(agentId)) {
            return "loading";
        }
        if (this.childStartErrorsByAgentId.has(agentId)) {
            return "error";
        }
        const runtime = this.agentRuntimes.get(agentId);
        return runtime?.agentAdapter ? "running" : "not_hydrated";
    }
    getPublicOrgSnapshot() {
        return {
            app: "d-pi hub",
            version: VERSION,
            protocolVersion: HUB_PROTOCOL_VERSION,
            generatedAt: new Date().toISOString(),
            agents: this.getAgentRecords().map((record) => {
                const runtime = this.tryGetAgentRuntime(record.id);
                const snapshot = runtime?.sessionService.getSnapshot();
                const activationStatus = this.getAgentHydrationStatus(record.id);
                const model = snapshot ? publicOrgAgentModel(snapshot) : undefined;
                return {
                    id: record.id,
                    ...(record.parentId && { parentId: record.parentId }),
                    kind: record.kind,
                    lifecycle: record.lifecycle,
                    ...(record.name && { name: record.name }),
                    ...(snapshot?.summary && { summary: snapshot.summary }),
                    activationStatus,
                    isRunning: snapshot?.isRunning ?? false,
                    peerCount: runtime?.peerRegistry.size() ?? 0,
                    hasProviderError: Boolean(snapshot?.lastError),
                    hasError: activationStatus === "error" || Boolean(snapshot?.lastError),
                    ...(model && { model }),
                };
            }),
        };
    }
    isAgentInSubtree(scopeRootAgentId, targetAgentId) {
        return this.agentRegistry.isInSubtree(scopeRootAgentId, targetAgentId);
    }
    /** Ids of all known agents. Child runtimes may hydrate lazily on first use. */
    getAllMessagingAgentIds() {
        return this.agentRegistry
            .getAll()
            .map((record) => record.id)
            .filter((agentId) => agentId === ROOT_AGENT_ID || !this.manuallyStoppedAgentIds.has(agentId));
    }
    deliverMessageToGuest(senderAgentId, targetAgentId, message, options) {
        if (this.agentRegistry.get(targetAgentId)?.kind !== "guest") {
            return false;
        }
        return this.socketServer.deliverGuestAgentMessage(senderAgentId, targetAgentId, message, options) > 0;
    }
    /**
     * Subscribes to `HubSessionService` activity for every running agent, forwarding `(agentId, event)` until unsubscribed.
     * New agents added after `SocketHubServer.start()` are wired automatically.
     */
    subscribeAllSessionServiceEvents(listener) {
        const subscriptions = [];
        for (const [agentId, rt] of this.agentRuntimes) {
            subscriptions.push({
                agentId,
                unsubscribe: rt.sessionService.subscribe((event) => {
                    listener(agentId, event);
                }),
            });
        }
        const rec = { listener, subscriptions };
        this.sessionFanout.push(rec);
        return () => {
            for (const subscription of subscriptions) {
                subscription.unsubscribe();
            }
            const i = this.sessionFanout.indexOf(rec);
            if (i >= 0) {
                this.sessionFanout.splice(i, 1);
            }
        };
    }
    static open(cwd = process.cwd(), openOptions = {}) {
        const agentRegistry = AgentRegistry.open(cwd);
        const authTokenStore = HubAuthTokenStore.open(cwd);
        const rootToken = authTokenStore.ensureRootToken().token;
        const rootRecord = agentRegistry.require(ROOT_AGENT_ID);
        const rootSession = HubSessionService.openAgent(cwd, rootRecord.sessionFile);
        const rootPeerRegistry = new PeerRegistry();
        const configLayers = new ConfigLayerRegistry();
        const hubRef = {};
        const sourceHost = new SourceHost({
            cwd,
            loadSources: () => loadSourcesConfigForAgents(cwd, agentRegistry
                .getAll()
                .filter((record) => record.kind === "child")
                .map((record) => record.id)),
            logs: openOptions.logs,
            inbound: {
                submitFromSource: async (sourceName, agentId, text) => {
                    const hub = hubRef.v;
                    if (!hub) {
                        throw new Error("HubRuntime is not ready");
                    }
                    const rt = await hub.ensureAgentStarted(agentId, "source");
                    if (!rt) {
                        throw new Error(`Unknown agent id: ${JSON.stringify(agentId)}`);
                    }
                    const adapter = rt.agentAdapter;
                    if (!adapter) {
                        throw new Error("Hub agent adapter is not initialized");
                    }
                    await adapter.enqueueFromSource(sourceName, text);
                },
            },
        });
        /** Assigned immediately after; socket handlers only run once clients connect. */
        let mcpHost;
        const adapterUserOptions = { current: {} };
        const socketServer = new SocketHubServer({
            getDefaultAgentId: () => ROOT_AGENT_ID,
            getAgentIds: () => agentRegistry.getAll().map((record) => record.id),
            getAgentRuntime: (agentId) => hubRef.v?.tryGetAgentRuntime(agentId),
            ensureAgentStarted: (agentId) => hubRef.v?.ensureAgentStarted(agentId, "socket") ?? Promise.resolve(undefined),
            getAgentMetadata: (agentId) => {
                const record = agentRegistry.get(agentId);
                return record
                    ? {
                        parentId: record.parentId,
                        kind: record.kind,
                        lifecycle: record.lifecycle,
                        name: record.name,
                        description: record.description,
                    }
                    : undefined;
            },
            getPublicOrgSnapshot: () => hubRef.v.getPublicOrgSnapshot(),
            authenticateToken: (token) => authTokenStore.authenticate(token),
            isAgentInScope: (identity, targetAgentId) => isAuthIdentityAllowedForAgent(identity, targetAgentId, (scopeRootAgentId, scopedTargetAgentId) => agentRegistry.isInSubtree(scopeRootAgentId, scopedTargetAgentId), (parentAgentId, scopedTargetAgentId) => agentRegistry.get(scopedTargetAgentId)?.parentId === parentAgentId),
            getHttpSessionService: () => hubRef.v.getRootAgentRuntime().sessionService,
            subscribeAllAgentSessionEvents: (onEvent) => {
                const h = hubRef.v;
                if (!h) {
                    return () => { };
                }
                return h.subscribeAllSessionServiceEvents((agentId, event) => onEvent(agentId, event));
            },
            getSourceStatuses: (agentId) => hubRef.v?.getSourceStatusesForAgent(agentId) ?? [],
            getSourceMessageTargetAgentIds: (boundAgentId, sourceName, requestedAgentId) => hubRef.v?.getSourceMessageTargetAgentIds(boundAgentId, sourceName, requestedAgentId) ?? [
                requestedAgentId ?? boundAgentId,
            ],
            sourceMutators: {
                pause: (resourceId) => sourceHost.pauseSource(resourceId),
                restart: (resourceId) => sourceHost.restartSource(resourceId),
                remove: (resourceId) => sourceHost.removeSource(resourceId),
            },
            getMcpServerStatuses: (agentId) => hubRef.v?.getMcpServerStatusesForAgent(agentId) ?? [],
            getMcpConfigError: (agentId) => hubRef.v?.getMcpHostForAgent(agentId)?.getConfigError(),
            onPeerConfigSnapshot: (agentId, peerId, snapshot) => {
                hubRef.v?.setPeerConfigSnapshot(agentId, peerId, snapshot);
            },
            onPeerConfigRemoved: (agentId, peerId) => {
                hubRef.v?.removePeerConfigSnapshot(agentId, peerId);
            },
            mcpMutators: {
                pauseServer: async (agentId, name) => {
                    const host = hubRef.v?.getMcpHostForAgent(agentId);
                    return host ? host.pauseServer(name) : { ok: false, error: `Unknown agent id: ${agentId}` };
                },
                restartServer: async (agentId, name) => {
                    const host = hubRef.v?.getMcpHostForAgent(agentId);
                    return host ? host.restartServer(name) : { ok: false, error: `Unknown agent id: ${agentId}` };
                },
                removeServer: async (agentId, name) => {
                    const host = hubRef.v?.getMcpHostForAgent(agentId);
                    return host ? host.removeServer(name) : { ok: false, error: `Unknown agent id: ${agentId}` };
                },
            },
            logs: openOptions.logs,
        });
        const rootRuntime = new HubAgentRuntime({
            cwd,
            record: rootRecord,
            sessionService: rootSession,
            socketServer,
            peerRegistry: rootPeerRegistry,
            configLayerRegistry: configLayers,
            peerMcpSnapshotsGetter: () => rootPeerRegistry.list().map((peer) => ({
                peerId: peer.peerId,
                servers: peer.mcpSnapshot?.servers ?? [],
            })),
            logs: openOptions.logs,
            resolvePeerForTool: (callerAgentId, peerId) => hubRef.v?.resolvePeerForTool(callerAgentId, peerId),
            beforeInputQueueDrain: () => hubRef.v?.applyPendingPeerConfigBeforeInput(ROOT_AGENT_ID),
            persistModelRef: (agentId, modelRef) => {
                const hub = hubRef.v;
                if (!hub)
                    return;
                const record = hub.agentRegistry.get(agentId);
                if (!record)
                    return;
                hub.agentRegistry.update({ ...record, model: modelRef });
                hub.agentRegistry.save();
            },
            refreshSources: async () => {
                await sourceHost.start();
            },
            refreshMcp: async () => {
                await mcpHost.start();
            },
            createAgentAdapter: (base) => {
                const hub = hubRef.v;
                if (!hub) {
                    throw new Error("HubRuntime is not ready");
                }
                // `base` from HubAgentRuntime may include explicit `model: undefined` / `services: undefined` which
                // must not win over `initializeAgentAdapter` options. Apply user options after `base`, then
                // re-pin hub-wired services.
                return HubAgentAdapter.create({
                    ...base,
                    ...adapterUserOptions.current,
                    cwd: hub.cwd,
                    configLayers: hub.buildConfigLayersForAgent(ROOT_AGENT_ID, undefined),
                    getConfigLayers: () => hub.buildConfigLayersForAgent(ROOT_AGENT_ID, undefined),
                    sessionService: base.sessionService,
                    tools: base.tools,
                    refreshSources: async () => {
                        await hub.sourceHost.start();
                    },
                    refreshMcp: async () => {
                        await hub.mcpHost.start();
                    },
                    beforeInputQueueDrain: () => hub.applyPendingPeerConfigBeforeInput(ROOT_AGENT_ID),
                    logs: hub.logs,
                });
            },
            getChildAgentHost: () => {
                const hub = hubRef.v;
                if (!hub) {
                    throw new Error("HubRuntime is not ready");
                }
                return hub;
            },
            getAgentMessagingHost: () => {
                const hub = hubRef.v;
                if (!hub) {
                    throw new Error("HubRuntime is not ready");
                }
                return hub;
            },
            getGroupHost: () => {
                const hub = hubRef.v;
                if (!hub) {
                    throw new Error("HubRuntime is not ready");
                }
                return hub;
            },
            getResourceStatusHost: () => {
                const hub = hubRef.v;
                if (!hub) {
                    throw new Error("HubRuntime is not ready");
                }
                return hub;
            },
            getAgentTokenHost: () => {
                const hub = hubRef.v;
                if (!hub) {
                    throw new Error("HubRuntime is not ready");
                }
                return hub;
            },
        });
        mcpHost = new McpHost({
            cwd,
            customTools: rootRuntime.tools,
            configRoot: () => mergeConfigLayers(hubRef.v?.buildConfigLayersForAgent(ROOT_AGENT_ID, undefined) ?? []).mcp,
            createClient: openOptions.mcp?.createClient,
            ...(openOptions.logs && { logs: openOptions.logs }),
        });
        const runtime = new HubRuntime(cwd, agentRegistry, authTokenStore, rootToken, socketServer, sourceHost, mcpHost, adapterUserOptions, rootRuntime, openOptions.mcp?.createClient, openOptions.logs, new NodeContainerExecutorLauncher({ spawn: openOptions.executors?.containerSpawn }), MemoryStore.open(cwd), configLayers);
        hubRef.v = runtime;
        runtime.wireSessionLogForAgent(ROOT_AGENT_ID, rootRuntime);
        runtime.wireMemoryIndexForAgent(ROOT_AGENT_ID, rootRuntime);
        return runtime;
    }
    getMcpHostForAgent(agentId) {
        if (agentId === ROOT_AGENT_ID) {
            return this.mcpHost;
        }
        return this.tryGetAgentRuntime(agentId)?.mcpHost;
    }
    getMcpServerStatusesForAgent(agentId) {
        const runtime = this.tryGetAgentRuntime(agentId);
        const local = this.getMcpHostForAgent(agentId)?.getStatuses() ?? [];
        const remote = runtime?.peerRegistry.list().flatMap((peer) => (peer.mcpSnapshot?.servers ?? []).map((server) => ({
            ...server,
        }))) ?? [];
        return [...local, ...remote];
    }
    resolvePeerForTool(callerAgentId, peerId) {
        for (const record of this.agentRegistry.getAll()) {
            if (!this.agentRegistry.isInSubtree(callerAgentId, record.id)) {
                continue;
            }
            const peer = this.tryGetAgentRuntime(record.id)?.peerRegistry.get(peerId);
            if (peer?.executorEnabled) {
                return peer;
            }
        }
        return undefined;
    }
    getVisibleExecutorPeersForAgent(callerAgentId) {
        const peers = [];
        for (const record of this.agentRegistry.getAll()) {
            if (!this.agentRegistry.isInSubtree(callerAgentId, record.id)) {
                continue;
            }
            for (const peer of this.tryGetAgentRuntime(record.id)?.peerRegistry.list() ?? []) {
                if (peer.executorEnabled) {
                    peers.push(peer);
                }
            }
        }
        return peers;
    }
    getSourceStatusesForAgent(agentId) {
        return this.sourceHost.getStatuses().filter((status) => status.agentId === agentId);
    }
    getSourceMessageTargetAgentIds(boundAgentId, sourceName, requestedAgentId) {
        if (requestedAgentId !== undefined) {
            return [requestedAgentId];
        }
        const targets = [boundAgentId];
        if (boundAgentId !== ROOT_AGENT_ID) {
            return targets;
        }
        for (const record of this.agentRegistry.getAll()) {
            if (record.kind !== "child") {
                continue;
            }
            const childConfig = loadChildSourcesConfigFromPath(getChildAgentSourcesConfigPath(this.cwd, record.id));
            const selection = childConfig.extends?.host?.sources;
            if (selection === true || (Array.isArray(selection) && selection.includes(sourceName))) {
                targets.push(record.id);
            }
        }
        return targets;
    }
    buildConfigLayersForAgent(agentId, agentDir) {
        return buildAgentConfigLayers({
            cwd: this.cwd,
            agentDir,
            peerSnapshots: this.configLayers.listPeerSnapshots(agentId),
        });
    }
    refreshAgentListViews() {
        this.socketServer.refreshAgentListViews();
    }
    setPeerConfigSnapshot(agentId, peerId, snapshot) {
        this.queuePeerConfigChange(agentId, peerId, { type: "set", snapshot });
        void this.applyPendingPeerConfigForAgent(agentId);
    }
    removePeerConfigSnapshot(agentId, peerId) {
        this.queuePeerConfigChange(agentId, peerId, { type: "remove" });
        void this.applyPendingPeerConfigForAgent(agentId);
    }
    queuePeerConfigChange(agentId, peerId, change) {
        let pending = this.pendingPeerConfigChangesByAgentId.get(agentId);
        if (!pending) {
            pending = new Map();
            this.pendingPeerConfigChangesByAgentId.set(agentId, pending);
        }
        pending.set(peerId, change);
    }
    async applyPendingPeerConfigBeforeInput(agentId) {
        const applied = await this.applyPendingPeerConfigForAgent(agentId);
        return !applied;
    }
    async applyPendingPeerConfigForAgent(agentId) {
        const inFlight = this.peerConfigApplyInFlightByAgentId.get(agentId);
        if (inFlight) {
            await inFlight;
            return true;
        }
        const apply = this.applyPendingPeerConfigForAgentOnce(agentId);
        this.peerConfigApplyInFlightByAgentId.set(agentId, apply);
        try {
            return await apply;
        }
        finally {
            if (this.peerConfigApplyInFlightByAgentId.get(agentId) === apply) {
                this.peerConfigApplyInFlightByAgentId.delete(agentId);
            }
        }
    }
    async applyPendingPeerConfigForAgentOnce(agentId) {
        const runtime = this.tryGetAgentRuntime(agentId);
        if (!runtime || runtime.sessionService.getSnapshot().isRunning) {
            return false;
        }
        const pending = this.pendingPeerConfigChangesByAgentId.get(agentId);
        if (!pending || pending.size === 0) {
            return false;
        }
        this.pendingPeerConfigChangesByAgentId.delete(agentId);
        for (const [peerId, change] of pending) {
            if (change.type === "remove") {
                this.configLayers.removePeerSnapshot(agentId, peerId);
            }
            else {
                this.configLayers.setPeerSnapshot(agentId, peerId, change.snapshot);
            }
        }
        // Check if any pending change requires a full adapter restart.
        // If peer content only affects skills/contextFiles/appendSystemPrompt (now served
        // by discover_peer_resources tool, not the system prompt), a lightweight reload
        // preserves the LLM prompt cache. Only auth/models/settings/systemPrompt changes
        // require a full restart.
        const needsFullRestart = this.pendingChangesRequireFullRestart(pending);
        if (needsFullRestart) {
            await runtime.restartAdapter();
        }
        else {
            const adapter = runtime.agentAdapter;
            if (adapter) {
                await adapter.reload();
            }
        }
        runtime.agentAdapter?.requestInputQueuePump?.();
        return true;
    }
    /** Check if any pending peer config change requires a full adapter restart. */
    pendingChangesRequireFullRestart(pending) {
        for (const [, change] of pending) {
            if (change.type === "remove") {
                // Removal could affect auth/models/settings — conservatively restart
                return true;
            }
            const snapshot = change.snapshot;
            if (!snapshot)
                continue;
            const layers = [snapshot.global, snapshot.cwdLayer];
            for (const layer of layers) {
                if (!layer)
                    continue;
                // These fields are baked into AgentSessionServices at construction time
                if (layer.auth !== undefined)
                    return true;
                if (layer.models !== undefined)
                    return true;
                if (layer.settings !== undefined)
                    return true;
                if (layer.systemPrompt !== undefined)
                    return true;
            }
        }
        return false;
    }
    async initializeAgentAdapter(options = {}) {
        const main = this.getRootAgentRuntime();
        if (main.agentAdapter) {
            return main.agentAdapter;
        }
        this.adapterUserOptions.current = options;
        let mainStartCompleted = false;
        let mainStartAttempted = false;
        try {
            console.log("[d-pi hub] 启动MCP服务...");
            const mcpStartedAt = Date.now();
            await this.mcpHost.start();
            const mcpDuration = Date.now() - mcpStartedAt;
            console.log(`[d-pi hub] MCP服务已启动 (${mcpDuration}ms)`);
            this.log("info", "hub startup timing", {
                phase: "root_mcp",
                agentId: ROOT_AGENT_ID,
                durationMs: mcpDuration,
            });
            console.log("[d-pi hub] 启动根智能体...");
            mainStartAttempted = true;
            const rootStartedAt = Date.now();
            await main.start();
            mainStartCompleted = true;
            const rootDuration = Date.now() - rootStartedAt;
            console.log(`[d-pi hub] 根智能体已启动 (${rootDuration}ms)`);
            this.log("info", "hub startup timing", {
                phase: "root_agent_start",
                agentId: ROOT_AGENT_ID,
                durationMs: rootDuration,
            });
            this.wireLiveEventsForAgent(ROOT_AGENT_ID, main);
            const adapter = main.agentAdapter;
            if (!adapter) {
                throw new Error("Hub agent adapter is not initialized");
            }
            return adapter;
        }
        catch (e) {
            if (mainStartAttempted && !mainStartCompleted) {
                await main.stop();
            }
            throw e;
        }
    }
    createChildHubAgentRuntime(rec) {
        const childSession = HubSessionService.openAgent(this.cwd, rec.sessionFile);
        const mcp = rec.kind === "guest"
            ? undefined
            : {
                configPath: getChildAgentMcpConfigPath(this.cwd, rec.id),
                configRoot: () => mergeConfigLayers(this.buildConfigLayersForAgent(rec.id, getChildAgentDir(this.cwd, rec.id))).mcp,
                createClient: this.createMcpClient,
            };
        return new HubAgentRuntime({
            cwd: this.cwd,
            agentDir: getChildAgentDir(this.cwd, rec.id),
            getConfigLayers: () => this.buildConfigLayersForAgent(rec.id, getChildAgentDir(this.cwd, rec.id)),
            configLayerRegistry: this.configLayers,
            peerMcpSnapshotsGetter: () => this.peerRegistry.list().map((peer) => ({
                peerId: peer.peerId,
                servers: peer.mcpSnapshot?.servers ?? [],
            })),
            record: rec,
            sessionService: childSession,
            socketServer: this.socketServer,
            mcp,
            logs: this.logs,
            resolvePeerForTool: (callerAgentId, peerId) => this.resolvePeerForTool(callerAgentId, peerId),
            beforeInputQueueDrain: () => this.applyPendingPeerConfigBeforeInput(rec.id),
            persistModelRef: (agentId, modelRef) => {
                const record = this.agentRegistry.get(agentId);
                if (!record)
                    return;
                this.agentRegistry.update({ ...record, model: modelRef });
                this.agentRegistry.save();
            },
            refreshSources: async () => {
                await this.sourceHost.start();
            },
            getChildAgentHost: () => this,
            getAgentMessagingHost: () => this,
            getGroupHost: () => this,
            getResourceStatusHost: () => this,
            getAgentTokenHost: () => this,
        });
    }
    ensureAgentRuntimeRegistered(agentId) {
        const existing = this.agentRuntimes.get(agentId);
        if (existing) {
            return existing;
        }
        const record = this.agentRegistry.get(agentId);
        if (!record || (record.kind !== "child" && record.kind !== "guest")) {
            return undefined;
        }
        const startedAt = Date.now();
        const child = this.createChildHubAgentRuntime(record);
        this.agentRuntimes.set(record.id, child);
        this.wireSessionLogForAgent(record.id, child);
        this.wireMemoryIndexForAgent(record.id, child);
        this.wireSessionFanoutForNewAgent(record.id, child);
        this.wireLiveEventsForAgent(record.id, child);
        this.refreshAgentListViews();
        this.log("info", "hub startup timing", {
            phase: "child_session_open",
            agentId: record.id,
            durationMs: Date.now() - startedAt,
        });
        return child;
    }
    async createGuestAgent(callerAgentId, input) {
        const parent = this.agentRegistry.require(callerAgentId);
        const record = this.agentRegistry.createGuestResolvingSessionPath({
            parentId: parent.id,
            name: input.name,
            description: input.description,
            createdBy: parent.id,
            lifecycle: "persistent",
        });
        initializeChildAgentDirectory(this.cwd, record.id);
        const paths = assertWorkspaceInitialized(this.cwd);
        const sm = SessionManager.open(record.sessionFile, paths.workspaceDir, this.cwd);
        HubRuntime.materializeSessionManagerToDisk(sm, record.sessionFile);
        this.agentRegistry.save();
        this.ensureAgentRuntimeRegistered(record.id);
        this.refreshAgentListViews();
        return JSON.stringify({
            ok: true,
            guestId: record.id,
            kind: record.kind,
            sessionFile: record.sessionFile,
            peerConnectCommand: this.createGuestAcpConnectCommand(record.id),
            recommendedTokenScope: {
                scopeMode: "self",
                scopeAgentId: record.id,
            },
        }, null, 2);
    }
    async ensureAgentStarted(agentId, reason = "on_demand") {
        if (agentId === ROOT_AGENT_ID) {
            const root = this.getRootAgentRuntime();
            if (!root.agentAdapter) {
                await root.start();
            }
            return root;
        }
        if (this.manuallyStoppedAgentIds.has(agentId) && reason !== "explicit_start" && reason !== "tool_start") {
            return undefined;
        }
        if (this.agentRegistry.get(agentId)?.kind === "guest") {
            return this.ensureAgentRuntimeRegistered(agentId);
        }
        const existingInFlight = this.childStartInFlightByAgentId.get(agentId);
        if (existingInFlight) {
            return existingInFlight;
        }
        const child = this.ensureAgentRuntimeRegistered(agentId);
        if (!child) {
            return undefined;
        }
        if (child.agentAdapter) {
            return child;
        }
        const startPromise = (async () => {
            const startedAt = Date.now();
            this.childStartErrorsByAgentId.delete(agentId);
            this.log("info", "child agent hydration started", { agentId, reason });
            try {
                await child.start();
                this.maybeTrackTemporaryChild(child.record, child);
                await this.startContainerExecutorsForAgent(child.record);
                this.log("info", "child agent started", { agentId });
                this.log("info", "hub startup timing", {
                    phase: "child_agent_start",
                    agentId,
                    durationMs: Date.now() - startedAt,
                });
                return child;
            }
            catch (error) {
                const message = getErrorMessage(error);
                this.childStartErrorsByAgentId.set(agentId, message);
                this.log("error", "child agent hydration failed", { agentId, reason, error: message });
                throw error;
            }
            finally {
                this.childStartInFlightByAgentId.delete(agentId);
            }
        })();
        this.childStartInFlightByAgentId.set(agentId, startPromise);
        return startPromise;
    }
    wireSessionFanoutForNewAgent(agentId, rt) {
        for (const e of this.sessionFanout) {
            e.subscriptions.push({
                agentId,
                unsubscribe: rt.sessionService.subscribe((ev) => {
                    e.listener(agentId, ev);
                }),
            });
        }
    }
    wireSessionLogForAgent(agentId, rt) {
        if (!this.logs || this.sessionLogUnsubs.has(agentId)) {
            return;
        }
        const unsubscribe = rt.sessionService.subscribe((event) => {
            if (event.type === "error") {
                this.log("error", "agent error", { agentId, error: event.message });
            }
        });
        this.sessionLogUnsubs.set(agentId, unsubscribe);
    }
    unwireSessionLogForAgent(agentId) {
        const unsubscribe = this.sessionLogUnsubs.get(agentId);
        if (!unsubscribe) {
            return;
        }
        unsubscribe();
        this.sessionLogUnsubs.delete(agentId);
    }
    wireMemoryIndexForAgent(agentId, rt) {
        if (this.memoryIndexUnsubs.has(agentId)) {
            return;
        }
        void this.indexAgentMemory(agentId, rt);
        const unsubscribe = rt.sessionService.subscribe((event) => {
            if (event.type !== "snapshot_updated" && event.type !== "run_state_changed") {
                return;
            }
            void this.indexAgentMemory(agentId, rt);
        });
        this.memoryIndexUnsubs.set(agentId, unsubscribe);
    }
    unwireMemoryIndexForAgent(agentId) {
        const unsubscribe = this.memoryIndexUnsubs.get(agentId);
        if (!unsubscribe) {
            return;
        }
        unsubscribe();
        this.memoryIndexUnsubs.delete(agentId);
    }
    async indexAgentMemory(agentId, rt) {
        try {
            const snapshot = rt.sessionService.getSnapshot();
            await this.memoryStore.indexSession({
                agentId,
                sessionFile: snapshot.sessionFile,
                entries: snapshot.entries,
            });
        }
        catch (error) {
            this.log("error", "agent memory indexing failed", {
                agentId,
                error: getErrorMessage(error),
            });
        }
    }
    wireLiveEventsForAgent(agentId, rt) {
        if (this.liveEventUnsubs.some((entry) => entry.agentId === agentId)) {
            return;
        }
        this.liveEventUnsubs.push({
            agentId,
            unsubscribe: rt.subscribeLiveEvents((event) => {
                this.socketServer.broadcastLiveEvent(rt.record.id, event);
            }),
        });
    }
    unwireSessionFanoutForAgent(agentId) {
        for (const e of this.sessionFanout) {
            const keep = [];
            for (const subscription of e.subscriptions) {
                if (subscription.agentId === agentId) {
                    subscription.unsubscribe();
                }
                else {
                    keep.push(subscription);
                }
            }
            e.subscriptions = keep;
        }
    }
    unwireLiveEventsForAgent(agentId) {
        const keep = [];
        for (const entry of this.liveEventUnsubs) {
            if (entry.agentId === agentId) {
                entry.unsubscribe();
            }
            else {
                keep.push(entry);
            }
        }
        this.liveEventUnsubs = keep;
    }
    static materializeSessionManagerToDisk(sm, path) {
        if (existsSync(path)) {
            return;
        }
        const header = sm.getHeader();
        if (!header) {
            throw new Error("Session is missing a header.");
        }
        const content = [header, ...sm.getEntries()].map((e) => JSON.stringify(e)).join("\n");
        writeFileSync(path, `${content}\n`, "utf8");
    }
    /**
     * Test hook: how many `subscribeAllSessionServiceEvents` calls are still active (unsub not run).
     * @internal
     */
    getSessionFanoutEntryCountForTest() {
        return this.sessionFanout.length;
    }
    assertAgentInSubtree(callerAgentId, targetAgentId, action) {
        this.agentRegistry.require(callerAgentId);
        this.agentRegistry.require(targetAgentId);
        if (!this.agentRegistry.isInSubtree(callerAgentId, targetAgentId)) {
            throw new Error(`${action}: target agent "${targetAgentId}" is outside caller subtree "${callerAgentId}".`);
        }
    }
    /**
     * After `agentRegistry` + session file are persisted, if adapter start fails, remove the registry row and
     * session file so disk matches the in-memory runtime map.
     */
    rollbackPersistedChildRecordIfPresent(record) {
        if (this.agentRegistry.get(record.id) !== undefined) {
            this.agentRegistry.removeChild(record.id);
            this.agentRegistry.save();
        }
        HubRuntime.tryUnlinkFile(record.sessionFile);
    }
    createPeerConnectCommand(agentId) {
        const host = getListenHost();
        const connectHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
        return `d-pi peer --hub http://${connectHost}:${getListenPort()} --agent ${agentId}`;
    }
    createGuestAcpConnectCommand(agentId) {
        const host = getListenHost();
        const connectHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
        return `d-pi guest acp --hub http://${connectHost}:${getListenPort()} --agent ${agentId}`;
    }
    static tryUnlinkFile(path) {
        try {
            if (existsSync(path)) {
                unlinkSync(path);
            }
        }
        catch {
            // best-effort: cleanup after failed child ingest
        }
    }
    continueChildCurrentTranscript(child) {
        const adapter = child.agentAdapter;
        if (!adapter) {
            throw new Error(`Child agent adapter is not initialized for ${child.record.id}.`);
        }
        adapter.continueCurrentTranscript?.();
    }
    writeChildResourceExtendsConfig(agentId, ext) {
        if (ext === undefined) {
            return;
        }
        if (ext.mcp !== undefined) {
            writeFileSync(getChildAgentMcpConfigPath(this.cwd, agentId), `${JSON.stringify({ extends: { host: { mcp: ext.mcp } }, servers: [] }, null, 2)}\n`, "utf8");
        }
        if (ext.sources !== undefined) {
            writeFileSync(getChildAgentSourcesConfigPath(this.cwd, agentId), `${JSON.stringify({ extends: { host: { sources: ext.sources } }, sources: [] }, null, 2)}\n`, "utf8");
        }
    }
    async refreshSourcesForChildExtends(ext) {
        if (ext?.sources === undefined) {
            return;
        }
        await this.sourceHost.start();
    }
    startBackgroundChildHydration() {
        if (this.backgroundChildHydrationStarted) {
            return;
        }
        this.backgroundChildHydrationStarted = true;
        const childRecords = this.agentRegistry.getAll().filter((record) => record.kind === "child");
        if (childRecords.length === 0) {
            return;
        }
        this.log("info", "child agent hydration queued", { agents: childRecords.length });
        setTimeout(() => {
            void this.hydrateChildAgentsInBackground(childRecords).catch((error) => {
                const message = getErrorMessage(error);
                this.log("error", "child agent hydration failed", { error: message });
            });
        }, 0);
    }
    async hydrateChildAgentsInBackground(childRecords) {
        const startedAt = Date.now();
        let started = 0;
        try {
            for (const record of childRecords) {
                if (this.stopping) {
                    return;
                }
                if (!this.agentRegistry.get(record.id)) {
                    continue;
                }
                try {
                    const child = await this.ensureAgentStarted(record.id, "startup_background");
                    if (child?.agentAdapter) {
                        started += 1;
                    }
                }
                catch (error) {
                    const message = getErrorMessage(error);
                    this.log("error", "child agent hydration failed", {
                        agentId: record.id,
                        reason: "startup_background",
                        error: message,
                    });
                }
            }
        }
        finally {
            this.log("info", "hub startup timing", {
                phase: "child_background_hydration",
                durationMs: Date.now() - startedAt,
                agents: started,
            });
        }
    }
    async startSourceHostAfterChildHydration() {
        if (this.stopping) {
            return;
        }
        const startedAt = Date.now();
        await this.sourceHost.start();
        this.log("info", "hub startup timing", {
            phase: "sources_start",
            durationMs: Date.now() - startedAt,
            sources: this.sourceHost.getStatuses().length,
        });
    }
    /** Starts a new child `HubAgentRuntime` after a registry record and session file were created (used by tools). */
    async addAndStartNewChildFromRegistry(record) {
        const child = this.ensureAgentRuntimeRegistered(record.id);
        if (!child) {
            throw new Error(`Unknown child agent id: ${record.id}`);
        }
        try {
            const started = await this.ensureAgentStarted(record.id, "tool_start");
            if (!started) {
                throw new Error(`Unknown child agent id: ${record.id}`);
            }
            return started;
        }
        catch (e) {
            this.stopContainerExecutorsForAgent(record.id);
            this.unwireLiveEventsForAgent(record.id);
            this.unwireSessionFanoutForAgent(record.id);
            this.unwireSessionLogForAgent(record.id);
            this.unwireMemoryIndexForAgent(record.id);
            this.agentRuntimes.delete(record.id);
            await child.stop();
            throw e;
        }
    }
    containerExecutorKey(agentId, executorId) {
        return `${agentId}:${executorId}`;
    }
    getHubUrlForContainerExecutors() {
        const address = this.socketAddress;
        if (!address) {
            return undefined;
        }
        return `http://${address.host}:${address.port}`;
    }
    async startContainerExecutorsForAgent(record) {
        if (record.kind !== "child" || !record.executors?.length) {
            return;
        }
        const hubUrl = this.getHubUrlForContainerExecutors();
        if (!hubUrl) {
            return;
        }
        for (const executor of record.executors) {
            const key = this.containerExecutorKey(record.id, executor.id);
            if (this.containerExecutorHandles.has(key)) {
                continue;
            }
            const handle = this.containerExecutorLauncher.start({
                cwd: this.cwd,
                hubUrl,
                agentId: record.id,
                executor,
            });
            this.containerExecutorHandles.set(key, handle);
            this.log("info", "container executor started", { agentId: record.id, executorId: executor.id });
        }
    }
    async startContainerExecutorsForRunningAgents() {
        for (const record of this.agentRegistry.getAll()) {
            if (record.id !== ROOT_AGENT_ID && this.agentRuntimes.has(record.id)) {
                await this.startContainerExecutorsForAgent(record);
            }
        }
    }
    stopContainerExecutorsForAgent(agentId) {
        for (const [key, handle] of [...this.containerExecutorHandles.entries()]) {
            if (!key.startsWith(`${agentId}:`)) {
                continue;
            }
            handle.stop();
            this.containerExecutorHandles.delete(key);
        }
    }
    stopAllContainerExecutors() {
        for (const [key, handle] of [...this.containerExecutorHandles.entries()]) {
            handle.stop();
            this.containerExecutorHandles.delete(key);
        }
    }
    maybeTrackTemporaryChild(record, child) {
        if (record.lifecycle !== "temporary" || !record.parentId || this.temporaryCleanupUnsubs.has(record.id)) {
            return;
        }
        let sawRunning = false;
        const unsubscribe = child.sessionService.subscribe((event) => {
            if (event.type === "run_state_changed" && event.isRunning) {
                sawRunning = true;
                return;
            }
            if (!sawRunning) {
                return;
            }
            const snapshot = child.sessionService.getSnapshot();
            if (!snapshot.isRunning && (snapshot.queuedMessages?.length ?? 0) === 0) {
                void this.cleanupTemporaryChild(record.id);
            }
        });
        this.temporaryCleanupUnsubs.set(record.id, unsubscribe);
    }
    async cleanupTemporaryChild(agentId) {
        if (this.temporaryCleanupInFlight.has(agentId)) {
            return;
        }
        this.temporaryCleanupInFlight.add(agentId);
        try {
            const record = this.agentRegistry.get(agentId);
            if (!record || record.lifecycle !== "temporary") {
                return;
            }
            if (record.reportResult !== false) {
                await this.reportTemporaryChildResult(record);
            }
            await this.removeChildAgentInternal(record.id, true);
        }
        finally {
            this.temporaryCleanupInFlight.delete(agentId);
        }
    }
    async reportTemporaryChildResult(record) {
        if (!record.parentId) {
            return;
        }
        const parent = this.tryGetAgentRuntime(record.parentId);
        const parentAdapter = parent?.agentAdapter;
        if (!parentAdapter) {
            return;
        }
        const result = this.extractLastAssistantText(record.id);
        if (!result) {
            return;
        }
        await parentAdapter.enqueueFromAgent(record.id, `Temporary child ${record.id} completed:\n\n${result}`);
    }
    extractLastAssistantText(agentId) {
        const rt = this.tryGetAgentRuntime(agentId);
        if (!rt) {
            return undefined;
        }
        const branch = rt.sessionService.getSessionManager().getBranch();
        for (const entry of [...branch].reverse()) {
            if (entry.type === "message" && entry.message.role === "assistant") {
                const text = HubRuntime.messagePlainText(entry.message).trim();
                return text.length > 0 ? text : undefined;
            }
        }
        return undefined;
    }
    async createChildAgent(callerAgentIdOrInput, maybeInput) {
        const callerAgentId = typeof callerAgentIdOrInput === "string" ? callerAgentIdOrInput : ROOT_AGENT_ID;
        const input = typeof callerAgentIdOrInput === "string" ? maybeInput : callerAgentIdOrInput;
        if (!input) {
            throw new Error("create_child_agent: input is required.");
        }
        if (input.temporary === true) {
            if (input.mode !== "spawn") {
                throw new Error('create_child_agent: temporary=true supports only mode="spawn".');
            }
            if (input.background === undefined || input.background.trim().length === 0) {
                throw new Error('create_child_agent: "background" is required when temporary=true.');
            }
            return this.spawnChildAgent(callerAgentId, {
                name: input.name,
                description: input.description,
                background: input.background,
                extends: input.extends,
                temporary: true,
                reportResult: input.reportResult,
                hubExecutor: input.hubExecutor,
                executors: input.executors,
                model: input.model,
            });
        }
        if (input.mode === "spawn") {
            if (input.background === undefined || input.background.trim().length === 0) {
                throw new Error('create_child_agent: "background" is required when mode="spawn".');
            }
            return this.spawnChildAgent(callerAgentId, {
                name: input.name,
                description: input.description,
                background: input.background,
                extends: input.extends,
                hubExecutor: input.hubExecutor,
                executors: input.executors,
                model: input.model,
            });
        }
        if (input.background !== undefined) {
            throw new Error('create_child_agent: "background" is only valid when mode="spawn".');
        }
        return this.forkChildAgent(callerAgentId, {
            name: input.name,
            description: input.description,
            instructions: input.instructions,
            extends: input.extends,
            hubExecutor: input.hubExecutor,
            executors: input.executors,
            model: input.model,
        });
    }
    async createTemporaryChildAgent(callerAgentId, input) {
        return this.spawnChildAgent(callerAgentId, {
            name: input.name,
            description: input.description,
            background: input.background,
            extends: input.extends,
            temporary: true,
            reportResult: input.reportResult,
            model: input.model,
        });
    }
    async spawnChildAgent(parentAgentId, input) {
        this.agentRegistry.require(parentAgentId);
        let record = this.agentRegistry.createChildResolvingSessionPath({
            parentId: parentAgentId,
            name: input.name,
            description: input.description,
            createdBy: parentAgentId,
            spawnMode: "spawn",
            background: input.background,
            lifecycle: input.temporary === true ? "temporary" : "persistent",
            reportResult: input.temporary === true ? input.reportResult !== false : undefined,
            hubExecutor: input.hubExecutor,
            model: input.model,
        });
        const executors = this.createContainerExecutorConfigs(parentAgentId, record.id, input.executors);
        if (executors !== undefined) {
            this.agentRegistry.update({ ...record, executors });
            record = this.agentRegistry.require(record.id);
        }
        initializeChildAgentDirectory(this.cwd, record.id);
        this.writeChildResourceExtendsConfig(record.id, input.extends);
        const paths = assertWorkspaceInitialized(this.cwd);
        const sm = SessionManager.open(record.sessionFile, paths.workspaceDir, this.cwd);
        sm.appendMessage({ role: "user", content: String(input.background), timestamp: Date.now() });
        HubRuntime.materializeSessionManagerToDisk(sm, record.sessionFile);
        this.agentRegistry.save();
        try {
            this.manuallyStoppedAgentIds.delete(record.id);
            const child = await this.addAndStartNewChildFromRegistry(this.agentRegistry.require(record.id));
            await this.refreshSourcesForChildExtends(input.extends);
            this.continueChildCurrentTranscript(child);
            this.refreshAgentListViews();
        }
        catch (e) {
            this.rollbackPersistedChildRecordIfPresent(record);
            throw e;
        }
        return JSON.stringify({
            ok: true,
            childId: record.id,
            sessionFile: record.sessionFile,
            status: "started",
            peerConnectCommand: this.createPeerConnectCommand(record.id),
            peerIdNote: "--peer-id only sets the peer identity; use --agent to choose the target agent.",
        }, null, 2);
    }
    createContainerExecutorConfigs(parentAgentId, childAgentId, inputExecutors) {
        if (inputExecutors === undefined) {
            return undefined;
        }
        return inputExecutors.map((executor) => {
            const token = this.authTokenStore.createScopedToken({
                name: `container executor ${executor.id}`,
                description: `Container peer executor ${executor.peerId} for child agent ${childAgentId}.`,
                user: `container executor ${executor.id}`,
                purpose: `Run container peer executor ${executor.peerId} for child agent ${childAgentId}.`,
                scopeRootAgentId: childAgentId,
                createdByAgentId: parentAgentId,
            }).token;
            return {
                id: executor.id,
                type: "node-container",
                peerId: executor.peerId,
                image: executor.image ?? "node:22",
                command: [...executor.command],
                token,
                ...(executor.env && { env: executor.env }),
                ...(executor.workdir && { workdir: executor.workdir }),
                ...(executor.containerName && { containerName: executor.containerName }),
            };
        });
    }
    async groupText(callerAgentId) {
        const caller = this.agentRegistry.require(callerAgentId);
        const agents = this.getAgentRecords().map((record) => {
            const runtime = this.tryGetAgentRuntime(record.id);
            const snapshot = runtime?.sessionService.getSnapshot();
            return {
                id: record.id,
                kind: record.kind,
                parentId: record.parentId,
                name: record.name,
                description: record.description,
                summary: snapshot?.summary,
                lifecycle: record.lifecycle,
                reportResult: record.reportResult,
                spawnMode: record.spawnMode,
                sessionFile: record.sessionFile,
                status: this.getAgentHydrationStatus(record.id),
                peerCount: runtime?.peerRegistry.size() ?? 0,
                isRunning: snapshot?.isRunning ?? false,
                isWorking: snapshot?.isRunning ?? false,
                lastError: snapshot?.lastError,
            };
        });
        const hostExecutorEnabled = caller.hubExecutor !== "disabled";
        const executors = [
            ...(hostExecutorEnabled ? [createHostPeerRecord(callerAgentId, this.cwd)] : []),
            ...this.getVisibleExecutorPeersForAgent(callerAgentId),
        ].map((peer) => ({
            agentId: peer.agentId,
            peerId: peer.peerId,
            transport: peer.transport,
            displayName: peer.displayName,
            hostname: peer.hostname,
            cwd: peer.cwd,
            tools: peer.tools,
            connectedAt: peer.connectedAt,
        }));
        return JSON.stringify({
            self: {
                id: caller.id,
                kind: caller.kind,
                name: caller.name,
                description: caller.description,
            },
            fieldNotes: {
                "agents.status": 'Hub runtime availability for this agent: "running" means the hub has an active agent runtime.',
                "agents.peerCount": "peerCount is connected d-pi peer client count only. Browser Web UI host connections are not counted because they are remote UIs, not tool executors. peerCount=0 does not mean the agent is offline, unavailable, or unable to receive agent messages.",
                "agents.isRunning": "Whether the agent is currently processing a turn.",
                "agents.isWorking": "Alias of agents.isRunning for compatibility with older prompts.",
                "agents.summary": "Short-lived current work summary set by update_agent_summary. Check this before deciding whether an urgent send_message_to_agent call should use flush=true.",
                "executors.peerId": hostExecutorEnabled
                    ? 'Tool executor id for routed peer tools. Use peer-id "host" to run tools on the D-Pi hub host workspace. Browser Web UI host connections are not executor peerIds.'
                    : "Tool executor id for routed peer tools. The D-Pi hub host executor is disabled for this agent. Browser Web UI host connections are not executor peerIds.",
            },
            agents,
            executors,
            tips: [
                ...(hostExecutorEnabled
                    ? ['Use peer-id "host" to run routed tools on the D-Pi hub host workspace.']
                    : []),
                "Use send_message_to_agent to send targeted messages to one or more other agents.",
                "Before using send_message_to_agent with flush=true, inspect the target agent summary and interrupt only when the message is urgent enough.",
                "Use broadcast_message_to_agents when every other agent should receive the same note.",
                "Keep update_agent_summary current while working, and keep child agent descriptions current for long-term capability discovery.",
            ],
        }, null, 2);
    }
    async resourceStatusText(callerAgentId) {
        this.agentRegistry.require(callerAgentId);
        const runtime = this.tryGetAgentRuntime(callerAgentId);
        const skills = runtime?.agentAdapter?.resourceLoader.getSkills();
        const skillUnavailableDiagnostic = skills === undefined
            ? [
                {
                    type: "warning",
                    message: `Agent runtime resources are not available for ${callerAgentId}.`,
                },
            ]
            : [];
        const mcpConfigError = this.getMcpHostForAgent(callerAgentId)?.getConfigError();
        return JSON.stringify({
            agentId: callerAgentId,
            fieldNotes: {
                "sources.status": "running/starting sources may be paused; stopped/error sources may be restarted.",
                "mcp.configError": "Config parse/read error from this agent's MCP host, if present.",
                "mcp.servers.error": "Per-server startup or capability discovery error, if present.",
                "skills.diagnostics": "Skill loading diagnostics with file paths when available.",
            },
            sources: this.getSourceStatusesForAgent(callerAgentId).map((source) => ({
                ...(source.resourceId && { resourceId: source.resourceId }),
                name: source.name,
                transport: source.transport,
                agentId: source.agentId,
                origin: source.origin,
                ...(source.peerId && { peerId: source.peerId }),
                status: source.status,
                ...(source.error && { error: source.error }),
            })),
            mcp: {
                ...(mcpConfigError !== undefined && { configError: mcpConfigError }),
                servers: this.getMcpServerStatusesForAgent(callerAgentId).map((server) => ({
                    ...(server.resourceId && { resourceId: server.resourceId }),
                    name: server.name,
                    transport: server.transport,
                    status: server.status,
                    ...(server.disabled !== undefined && { disabled: server.disabled }),
                    ...(server.error && { error: server.error }),
                    capabilities: {
                        tools: server.capabilities.tools.length,
                        resources: server.capabilities.resources.length,
                        prompts: server.capabilities.prompts.length,
                    },
                })),
            },
            skills: {
                count: skills?.skills.length ?? 0,
                skills: skills?.skills.map((skill) => ({
                    name: skill.name,
                    description: skill.description,
                    filePath: skill.filePath,
                    disableModelInvocation: skill.disableModelInvocation,
                })) ?? [],
                diagnostics: [
                    ...skillUnavailableDiagnostic,
                    ...(skills?.diagnostics.map((diagnostic) => ({
                        type: diagnostic.type,
                        message: diagnostic.message,
                        ...(diagnostic.path && { path: diagnostic.path }),
                    })) ?? []),
                ],
            },
        }, null, 2);
    }
    async updateAgentDescriptionText(callerAgentId, input) {
        const caller = this.agentRegistry.require(callerAgentId);
        const targetAgentId = input.agentId ?? callerAgentId;
        const target = this.agentRegistry.require(targetAgentId);
        if (target.kind !== "child") {
            throw new Error("update_agent_description: root agent description is not editable through this tool.");
        }
        if (!this.agentRegistry.isInSubtree(caller.id, target.id)) {
            throw new Error("update_agent_description: agents can update only themselves or descendants.");
        }
        this.agentRegistry.update({ ...target, description: input.description });
        this.agentRegistry.save();
        this.refreshAgentListViews();
        return JSON.stringify({
            ok: true,
            agentId: target.id,
            description: input.description,
        }, null, 2);
    }
    async updateAgentSummaryText(callerAgentId, input) {
        this.agentRegistry.require(callerAgentId);
        const runtime = this.tryGetAgentRuntime(callerAgentId);
        if (!runtime) {
            throw new Error(`update_agent_summary: agent runtime not found: ${callerAgentId}`);
        }
        runtime.sessionService.updateSummary(input.summary);
        const summary = runtime.sessionService.getSnapshot().summary;
        return JSON.stringify({
            ok: true,
            agentId: callerAgentId,
            ...(summary && { summary }),
        }, null, 2);
    }
    async updateChildAgent(callerAgentId, input) {
        const target = this.requireChildRecordForLifecycle(input.agentId, "update_child_agent");
        const updatesMetadata = input.name !== undefined || input.description !== undefined;
        const updatesHubExecutor = input.hubExecutor !== undefined;
        const updatesExecutors = input.executors !== undefined;
        if (!updatesMetadata && !updatesHubExecutor && !updatesExecutors) {
            throw new Error("update_child_agent: at least one field must be provided.");
        }
        if (updatesMetadata && !this.agentRegistry.isInSubtree(callerAgentId, target.id)) {
            throw new Error("update_child_agent: agents can update metadata only for themselves or descendants.");
        }
        if ((updatesHubExecutor || updatesExecutors) && target.parentId !== callerAgentId) {
            throw new Error("update_child_agent: executor policy can be changed only by the target child direct parent.");
        }
        if (updatesExecutors && this.agentRuntimes.has(target.id)) {
            throw new Error("update_child_agent: container executor config can be changed only when the child must be stopped.");
        }
        const next = { ...target };
        if (input.name !== undefined) {
            next.name = input.name;
        }
        if (input.description !== undefined) {
            next.description = input.description;
        }
        if (input.hubExecutor !== undefined) {
            next.hubExecutor = input.hubExecutor;
        }
        if (input.executors !== undefined) {
            next.executors = this.createContainerExecutorConfigs(callerAgentId, target.id, input.executors) ?? [];
        }
        this.agentRegistry.update(next);
        this.agentRegistry.save();
        const runtime = this.tryGetAgentRuntime(next.id);
        if (runtime) {
            if (input.name !== undefined)
                runtime.record.name = input.name;
            if (input.description !== undefined)
                runtime.record.description = input.description;
            if (input.hubExecutor !== undefined)
                runtime.record.hubExecutor = input.hubExecutor;
        }
        this.refreshAgentListViews();
        return JSON.stringify({
            ok: true,
            childId: next.id,
            name: next.name,
            description: next.description,
            hubExecutor: next.hubExecutor ?? "enabled",
            executors: next.executors,
        }, null, 2);
    }
    async renameChildAgent(callerAgentId, input) {
        const target = this.requireChildRecordForLifecycle(input.agentId, "rename_child_agent");
        if (target.parentId !== callerAgentId) {
            throw new Error("rename_child_agent: child agent id can be changed only by the target child direct parent.");
        }
        if (this.agentRuntimes.has(target.id)) {
            throw new Error("rename_child_agent: child agent id can be changed only when the child must be stopped.");
        }
        if (!input.newAgentId) {
            throw new Error("rename_child_agent: newAgentId is required.");
        }
        const newAgentId = this.agentRegistry.resolveNewChildAgentId(input.newAgentId);
        const oldDir = getChildAgentDir(this.cwd, target.id);
        const newDir = getChildAgentDir(this.cwd, newAgentId);
        if (!existsSync(oldDir)) {
            throw new Error(`rename_child_agent: child directory does not exist for agent "${target.id}".`);
        }
        if (existsSync(newDir)) {
            throw new Error(`rename_child_agent: child directory already exists for agent "${newAgentId}".`);
        }
        renameSync(oldDir, newDir);
        let renamed;
        try {
            renamed = this.agentRegistry.renameChild(target.id, newAgentId);
            this.agentRegistry.save();
        }
        catch (e) {
            if (this.agentRegistry.get(newAgentId) !== undefined && this.agentRegistry.get(target.id) === undefined) {
                this.agentRegistry.renameChild(newAgentId, target.id);
            }
            if (existsSync(newDir) && !existsSync(oldDir)) {
                renameSync(newDir, oldDir);
            }
            throw e;
        }
        for (const child of this.agentRegistry.getChildren(renamed.id)) {
            const runtime = this.tryGetAgentRuntime(child.id);
            if (runtime) {
                runtime.record.parentId = renamed.id;
            }
        }
        this.log("info", "child agent renamed", { oldAgentId: target.id, newAgentId: renamed.id });
        this.refreshAgentListViews();
        return JSON.stringify({
            ok: true,
            oldAgentId: target.id,
            childId: renamed.id,
            sessionFile: renamed.sessionFile,
        }, null, 2);
    }
    async createAgentTokenText(callerAgentId, input) {
        const caller = this.agentRegistry.require(callerAgentId);
        const scopeRootAgentId = input.scopeAgentId?.trim() || caller.id;
        this.assertAgentInSubtree(caller.id, scopeRootAgentId, "create_agent_token");
        const scopeMode = input.scopeMode ?? "subtree";
        const scope = {
            mode: scopeMode,
            rootAgentId: scopeRootAgentId,
        };
        if (scopeMode === "explicit") {
            const agentIds = (input.agentIds ?? [])
                .map((agentId) => agentId.trim())
                .filter((agentId) => agentId.length > 0);
            if (agentIds.length === 0) {
                throw new Error('create_agent_token: agentIds is required when scopeMode is "explicit".');
            }
            for (const agentId of agentIds) {
                this.assertAgentInSubtree(caller.id, agentId, "create_agent_token");
            }
            scope.agentIds = [...new Set(agentIds)];
        }
        const created = this.authTokenStore.createScopedToken({
            name: input.name.trim(),
            description: input.description.trim(),
            user: input.user.trim(),
            purpose: input.purpose.trim(),
            scopeRootAgentId,
            scope,
            createdByAgentId: caller.id,
        });
        return JSON.stringify({
            ok: true,
            token: created.token,
            tokenId: created.record.id,
            name: created.record.name,
            description: created.record.description,
            user: created.record.user,
            purpose: created.record.purpose,
            scopeRootAgentId: created.record.scopeRootAgentId,
            scope: created.record.scope,
            createdByAgentId: created.record.createdByAgentId,
            note: "This token is shown in this response and stored in the hub auth registry. Store it securely.",
        }, null, 2);
    }
    async revokeAgentTokenText(callerAgentId, input) {
        this.agentRegistry.require(callerAgentId);
        const tokenId = input.tokenId.trim();
        if (!tokenId) {
            throw new Error("revoke_agent_token: tokenId is required.");
        }
        const identity = this.authTokenStore.getMetadata(tokenId);
        if (!identity) {
            throw new Error(`revoke_agent_token: unknown token id "${tokenId}".`);
        }
        this.assertAgentInSubtree(callerAgentId, identity.scopeRootAgentId, "revoke_agent_token");
        const revoked = this.authTokenStore.revokeToken(tokenId);
        const revokedConnections = this.socketServer.disconnectToken(tokenId);
        return JSON.stringify({
            ok: true,
            tokenId,
            name: revoked?.name ?? identity.name,
            description: revoked?.description ?? identity.description,
            user: revoked?.user ?? identity.user,
            purpose: revoked?.purpose ?? identity.purpose,
            scopeRootAgentId: revoked?.scopeRootAgentId ?? identity.scopeRootAgentId,
            createdByAgentId: revoked?.createdByAgentId ?? identity.createdByAgentId,
            revokedConnections,
        }, null, 2);
    }
    async forkChildAgent(parentAgentId, input) {
        const parentRt = this.getAgentRuntime(parentAgentId);
        const parentSm = parentRt.sessionService.getSessionManager();
        const leaf = parentSm.getLeafId();
        if (leaf === null) {
            throw new Error('create_child_agent: mode="fork" requires the current agent session to have a leaf.');
        }
        const paths = assertWorkspaceInitialized(this.cwd);
        const forkSm = SessionManager.open(parentRt.record.sessionFile, paths.workspaceDir, this.cwd);
        let branchedPath;
        try {
            const created = forkSm.createBranchedSession(leaf);
            if (created === undefined) {
                throw new Error('create_child_agent: mode="fork" could not create branched session file.');
            }
            branchedPath = created;
            let record = this.agentRegistry.createChildResolvingSessionPath({
                parentId: parentAgentId,
                name: input.name,
                description: input.description,
                createdBy: parentAgentId,
                spawnMode: "fork",
                hubExecutor: input.hubExecutor,
                model: input.model,
            });
            const executors = this.createContainerExecutorConfigs(parentAgentId, record.id, input.executors);
            if (executors !== undefined) {
                this.agentRegistry.update({ ...record, executors });
                record = this.agentRegistry.require(record.id);
            }
            initializeChildAgentDirectory(this.cwd, record.id);
            this.writeChildResourceExtendsConfig(record.id, input.extends);
            HubRuntime.materializeSessionManagerToDisk(forkSm, branchedPath);
            renameSync(branchedPath, record.sessionFile);
            branchedPath = undefined;
            if (input.instructions !== undefined && input.instructions.length > 0) {
                const csm = SessionManager.open(record.sessionFile, paths.workspaceDir, this.cwd);
                csm.appendMessage({ role: "user", content: String(input.instructions), timestamp: Date.now() });
                HubRuntime.materializeSessionManagerToDisk(csm, record.sessionFile);
            }
            this.agentRegistry.save();
            try {
                const child = await this.addAndStartNewChildFromRegistry(this.agentRegistry.require(record.id));
                await this.refreshSourcesForChildExtends(input.extends);
                if (input.instructions !== undefined && input.instructions.length > 0) {
                    this.continueChildCurrentTranscript(child);
                }
                this.refreshAgentListViews();
            }
            catch (e) {
                this.rollbackPersistedChildRecordIfPresent(record);
                throw e;
            }
            return JSON.stringify({
                ok: true,
                childId: record.id,
                sessionFile: record.sessionFile,
                status: "started",
                peerConnectCommand: this.createPeerConnectCommand(record.id),
                peerIdNote: "--peer-id only sets the peer identity; use --agent to choose the target agent.",
            }, null, 2);
        }
        catch (e) {
            if (branchedPath !== undefined) {
                HubRuntime.tryUnlinkFile(branchedPath);
            }
            throw e;
        }
    }
    requireChildRecordForLifecycle(agentId, action) {
        if (agentId === ROOT_AGENT_ID) {
            throw new Error(`${action}: cannot operate on root agent "${ROOT_AGENT_ID}"; target must be a child agent.`);
        }
        const record = this.agentRegistry.require(agentId);
        if (record.kind !== "child") {
            throw new Error(`${action}: agent "${agentId}" is not a child agent.`);
        }
        return record;
    }
    async stopChildAgent(callerAgentId, input) {
        this.assertAgentInSubtree(callerAgentId, input.agentId, "stop_child_agent");
        const record = this.requireChildRecordForLifecycle(input.agentId, "stop_child_agent");
        const runtime = this.agentRuntimes.get(record.id);
        const disconnectedPeers = runtime?.peerRegistry.size() ?? 0;
        this.manuallyStoppedAgentIds.add(record.id);
        if (runtime) {
            this.stopContainerExecutorsForAgent(record.id);
            for (const peer of runtime.peerRegistry.list()) {
                this.configLayers.removePeerSnapshot(record.id, peer.peerId);
            }
            this.socketServer.disconnectAgentPeers(record.id);
            this.unwireLiveEventsForAgent(record.id);
            this.unwireSessionFanoutForAgent(record.id);
            this.unwireSessionLogForAgent(record.id);
            this.unwireMemoryIndexForAgent(record.id);
            this.agentRuntimes.delete(record.id);
            await runtime.stop();
            this.log("info", "child agent stopped", { agentId: record.id, disconnectedPeers });
        }
        this.refreshAgentListViews();
        return JSON.stringify({
            ok: true,
            childId: record.id,
            status: "stopped",
            wasRunning: runtime !== undefined,
            disconnectedPeers,
        }, null, 2);
    }
    async startChildAgent(callerAgentId, input) {
        this.assertAgentInSubtree(callerAgentId, input.agentId, "start_child_agent");
        const record = this.requireChildRecordForLifecycle(input.agentId, "start_child_agent");
        const existing = this.agentRuntimes.get(record.id);
        if (existing?.agentAdapter) {
            return JSON.stringify({
                ok: true,
                childId: record.id,
                status: "already_running",
                sessionFile: record.sessionFile,
                peerConnectCommand: this.createPeerConnectCommand(record.id),
            }, null, 2);
        }
        this.manuallyStoppedAgentIds.delete(record.id);
        await this.addAndStartNewChildFromRegistry(record);
        this.refreshAgentListViews();
        return JSON.stringify({
            ok: true,
            childId: record.id,
            status: "started",
            sessionFile: record.sessionFile,
            peerConnectCommand: this.createPeerConnectCommand(record.id),
        }, null, 2);
    }
    async removeChildAgent(callerAgentId, input) {
        this.assertAgentInSubtree(callerAgentId, input.agentId, "remove_child_agent");
        return this.removeChildAgentInternal(input.agentId, input.deleteFiles === true);
    }
    async removeChildAgentInternal(agentId, deleteFiles) {
        const record = this.requireChildRecordForLifecycle(agentId, "remove_child_agent");
        const subtreeIds = [...this.agentRegistry.getDescendantIds(record.id)].reverse();
        for (const descendantId of subtreeIds) {
            await this.removeChildAgentInternal(descendantId, deleteFiles);
        }
        const wasRunning = this.agentRuntimes.has(record.id);
        if (wasRunning) {
            await this.stopChildAgent(ROOT_AGENT_ID, { agentId: record.id });
        }
        const revokedTokens = this.revokeTokensScopedToAgents([record.id]);
        const temporaryUnsub = this.temporaryCleanupUnsubs.get(record.id);
        if (temporaryUnsub) {
            temporaryUnsub();
            this.temporaryCleanupUnsubs.delete(record.id);
        }
        this.configLayers.removeAgentSnapshots(record.id);
        this.manuallyStoppedAgentIds.delete(record.id);
        this.agentRegistry.removeChild(record.id);
        this.agentRegistry.save();
        if (deleteFiles) {
            rmSync(getChildAgentDir(this.cwd, record.id), { recursive: true, force: true });
        }
        this.refreshAgentListViews();
        this.log("info", "child agent removed", {
            agentId: record.id,
            wasRunning,
            revokedTokens: revokedTokens.revokedTokens,
            revokedConnections: revokedTokens.revokedConnections,
        });
        return JSON.stringify({
            ok: true,
            childId: record.id,
            status: "removed",
            wasRunning,
            filesDeleted: deleteFiles,
            childDir: getChildAgentDir(this.cwd, record.id),
            revokedTokens: revokedTokens.revokedTokens,
            revokedConnections: revokedTokens.revokedConnections,
        }, null, 2);
    }
    revokeTokensScopedToAgents(agentIds) {
        const revoked = this.authTokenStore.revokeTokensScopedTo(agentIds);
        let revokedConnections = 0;
        for (const token of revoked) {
            revokedConnections += this.socketServer.disconnectToken(token.id);
        }
        return { revokedTokens: revoked.length, revokedConnections };
    }
    async listAgentsText() {
        return JSON.stringify(this.getAgentRecords().map((r) => {
            const rt = this.tryGetAgentRuntime(r.id);
            const snap = rt?.sessionService.getSnapshot();
            return {
                id: r.id,
                kind: r.kind,
                parentId: r.parentId,
                name: r.name,
                description: r.description,
                lifecycle: r.lifecycle,
                reportResult: r.reportResult,
                sessionFile: r.sessionFile,
                spawnMode: r.spawnMode,
                background: r.background,
                hydrationStatus: this.getAgentHydrationStatus(r.id),
                peerCount: rt?.peerRegistry.size() ?? 0,
                isRunning: snap?.isRunning ?? false,
                lastError: snap?.lastError,
            };
        }), null, 2);
    }
    static messagePlainText(m) {
        if (m.role === "user") {
            const c = m.content;
            if (typeof c === "string") {
                return c;
            }
            if (Array.isArray(c)) {
                return c
                    .map((p) => {
                    if (p && typeof p === "object" && "type" in p && p.type === "text" && "text" in p) {
                        return String(p.text);
                    }
                    return "";
                })
                    .join("");
            }
            return JSON.stringify(c);
        }
        if (m.role === "assistant") {
            const c = m.content;
            if (typeof c === "string") {
                return c;
            }
            if (Array.isArray(c)) {
                return c
                    .map((p) => {
                    if (p && typeof p === "object" && "type" in p && p.type === "text" && "text" in p) {
                        return String(p.text);
                    }
                    return "";
                })
                    .join("");
            }
            return JSON.stringify(m);
        }
        if (m.role === "toolResult") {
            const c = m.content;
            if (Array.isArray(c) &&
                c[0] &&
                typeof c[0] === "object" &&
                "type" in c[0] &&
                c[0].type === "text" &&
                "text" in c[0]) {
                return String(c[0].text);
            }
            return JSON.stringify(m);
        }
        return JSON.stringify(m);
    }
    async searchMemoryText(callerAgentId, input) {
        const scopeAgentIds = this.getMemoryScopeAgentIds(callerAgentId, input.agentId, "search_memory");
        await this.refreshMemoryIndexForScope(scopeAgentIds);
        const results = await this.memoryStore.search({
            query: input.query,
            agentId: input.agentId,
            limit: input.limit,
            includeToolResults: input.includeToolResults,
            scopeAgentIds,
        });
        return JSON.stringify({
            ok: true,
            query: input.query,
            ...(input.agentId && { agentId: input.agentId }),
            results: results.map((result) => ({
                memoryId: result.memoryId,
                agentId: result.agentId,
                sessionEntryId: result.sessionEntryId,
                timestamp: result.timestamp,
                role: result.role,
                kind: result.kind,
                score: result.score,
                text: compactMemoryText(result.text),
                ...(result.modelProvider === undefined && result.modelId === undefined
                    ? {}
                    : { model: { provider: result.modelProvider, modelId: result.modelId } }),
                ...(result.toolName && { toolName: result.toolName }),
            })),
        }, null, 2);
    }
    async listMemoryText(callerAgentId, input) {
        const scopeAgentIds = this.getMemoryScopeAgentIds(callerAgentId, undefined, "list_memory");
        this.assertMemoryIdsInScope(callerAgentId, input.memoryIds);
        await this.refreshMemoryIndexForScope(scopeAgentIds);
        const contexts = await this.memoryStore.list({
            memoryIds: input.memoryIds,
            contextBefore: input.contextBefore,
            contextAfter: input.contextAfter,
            scopeAgentIds,
        });
        return JSON.stringify({
            ok: true,
            contexts: contexts.map((context) => ({
                memoryId: context.memoryId,
                agentId: context.agentId,
                sessionFile: context.sessionFile,
                sessionEntryId: context.sessionEntryId,
                items: context.items.map((item) => ({
                    memoryId: item.memoryId,
                    sessionEntryId: item.sessionEntryId,
                    ...(item.parentEntryId && { parentEntryId: item.parentEntryId }),
                    timestamp: item.timestamp,
                    role: item.role,
                    kind: item.kind,
                    text: item.text,
                    ...(item.modelProvider === undefined && item.modelId === undefined
                        ? {}
                        : { model: { provider: item.modelProvider, modelId: item.modelId } }),
                    ...(item.toolName && { toolName: item.toolName }),
                    rawJson: item.rawJson,
                })),
            })),
        }, null, 2);
    }
    getMemoryScopeAgentIds(callerAgentId, targetAgentId, action) {
        this.agentRegistry.require(callerAgentId);
        if (targetAgentId !== undefined) {
            this.assertAgentInSubtree(callerAgentId, targetAgentId, action);
            return [targetAgentId];
        }
        return this.agentRegistry
            .getAll()
            .filter((record) => this.agentRegistry.isInSubtree(callerAgentId, record.id))
            .map((record) => record.id);
    }
    assertMemoryIdsInScope(callerAgentId, memoryIds) {
        for (const memoryId of memoryIds) {
            const agentId = memoryAgentIdFromMemoryId(memoryId);
            if (!agentId) {
                continue;
            }
            try {
                this.assertAgentInSubtree(callerAgentId, agentId, "list_memory");
            }
            catch (error) {
                const message = getErrorMessage(error);
                throw new Error(`${message} Ask the parent agent to provide memory context if upward access is needed.`);
            }
        }
    }
    async refreshMemoryIndexForScope(agentIds) {
        for (const agentId of agentIds) {
            const rt = this.agentRuntimes.get(agentId);
            if (rt) {
                await this.indexAgentMemory(agentId, rt);
            }
            else {
                await this.indexRegisteredAgentMemory(agentId);
            }
        }
    }
    async indexRegisteredAgentMemory(agentId) {
        const record = this.agentRegistry.get(agentId);
        if (!record) {
            return;
        }
        try {
            const sessionManager = SessionManager.open(record.sessionFile, getWorkspaceDir(this.cwd), this.cwd);
            await this.memoryStore.indexSession({
                agentId,
                sessionFile: record.sessionFile,
                entries: sessionManager.getEntries(),
            });
        }
        catch (error) {
            this.log("error", "registered agent memory indexing failed", {
                agentId,
                error: getErrorMessage(error),
            });
        }
    }
    async start(options = {}) {
        const startedAt = Date.now();
        const address = await this.socketServer.start({
            host: options.host ?? getListenHost(),
            port: options.port ?? getListenPort(),
        });
        this.socketAddress = address;
        this.log("info", "hub startup timing", {
            phase: "socket_listen",
            durationMs: Date.now() - startedAt,
        });
        this.startBackgroundChildHydration();
        void this.startSourceHostAfterChildHydration().catch((error) => {
            const message = getErrorMessage(error);
            this.log("error", "source startup failed", { error: message });
        });
        await this.startContainerExecutorsForRunningAgents();
        return address;
    }
    /**
     * Terminal shutdown for this `HubRuntime` instance: tears down live forwarding, every `HubAgentRuntime`, source and
     * MCP hosts, and the socket server, then **clears** the internal agent-runtime map. Do not reuse this instance;
     * open a new hub with `HubRuntime.open()` if you need another lifecycle.
     */
    async stop() {
        this.stopping = true;
        this.stopAllContainerExecutors();
        for (const e of this.sessionFanout) {
            for (const subscription of e.subscriptions) {
                subscription.unsubscribe();
            }
        }
        this.sessionFanout = [];
        for (const unsubscribe of this.sessionLogUnsubs.values()) {
            unsubscribe();
        }
        this.sessionLogUnsubs.clear();
        for (const unsubscribe of this.memoryIndexUnsubs.values()) {
            unsubscribe();
        }
        this.memoryIndexUnsubs.clear();
        for (const u of this.liveEventUnsubs) {
            u.unsubscribe();
        }
        this.liveEventUnsubs = [];
        for (const rt of this.agentRuntimes.values()) {
            await rt.stop();
        }
        this.agentRuntimes.clear();
        await this.sourceHost.stop();
        try {
            await this.mcpHost.stop();
        }
        catch (e) {
            const message = getErrorMessage(e);
            console.error(`d-pi hub: error stopping McpHost: ${message}`);
        }
        await this.socketServer.stop();
        this.memoryStore.close();
    }
}
//# sourceMappingURL=hub-runtime.js.map