import { logger } from '../utils/logger.js';
import { jsonErrorResponse } from './http/error-response.js';
import { summarizeError } from '../utils/error-display.js';
import { AgentManager } from '../tools/agent/index.js';
import {} from '../runtime/remote/index.js';
import { isSurfaceDeliveryEnabled } from './surface-policy.js';
import { AgentTaskAdapter } from '../runtime/tasks/adapters/agent-adapter.js';
import { configureDaemonSessionContinuation, createDaemonFacadeCollaborators, resolveDaemonFacadeRuntime, } from './facade-composition.js';
import { GlobalNetworkTransportInstaller, resolveInboundTlsContext, } from '../runtime/network/index.js';
import { createRuntimeServices } from '../runtime/services.js';
import { isSurfaceFeatureGateEnabled } from '../runtime/feature-flags/index.js';
import { readAutomationReasoningEffort, readAutomationWakeMode, readExternalContentSource, readStringList, } from './helpers.js';
import { requirePortAvailable } from './port-check.js';
import { resolveHostBinding } from './host-resolver.js';
import { createHostModeRestartWatcher } from './host-mode-watcher.js';
// ---------------------------------------------------------------------------
// DaemonServer
// ---------------------------------------------------------------------------
/**
 * DaemonServer — HTTP task server, disabled by default.
 *
 * Enable via: danger.daemon = true in config.
 * All routes require Bearer token auth (set via enable()).
 * POST /task    — submit a task; returns agentId.
 * GET  /task/:id — returns agent status.
 * GET  /status  — server health check.
 */
export class DaemonServer {
    config;
    enabled = false;
    server = null;
    port;
    host;
    agentManager;
    runtimeServices;
    integrationHelpers;
    configManager;
    authToken = null;
    userAuth;
    githubWebhookSecret;
    automationManager;
    runtimeBus;
    runtimeStore;
    runtimeDispatch;
    controlPlaneGateway;
    gatewayMethods;
    sessionBroker;
    approvalBroker;
    routeBindings;
    deliveryManager;
    surfaceRegistry;
    channelPolicy;
    channelPlugins;
    channelReplyPipeline;
    providerRuntime;
    builtinChannels;
    watcherRegistry;
    platformServiceManager;
    distributedRuntime;
    voiceService;
    webSearchService;
    knowledgeService;
    knowledgeGraphqlService;
    mediaProviders;
    multimodalService;
    artifactStore;
    serviceRegistry;
    serveFactory;
    pendingSurfaceReplies = new Map();
    controlPlaneHelper;
    surfaceDeliveryHelper;
    surfaceActionHelper;
    transportEventsHelper;
    httpRouter;
    replyPoller = null;
    companionChatManager;
    agentTaskAdapter = null;
    agentTaskAdapterUnsub = null;
    tlsState = null;
    approvalBrokerUnsubscribe = null;
    /** Unsubscribe from controlPlane config key watchers; cleared on stop(). */
    _configWatchUnsub = null;
    /** True while a config-driven restart is in progress — prevents re-entrancy. */
    _restarting = false;
    /** Awaitable promise for the active restart cycle; null when idle. */
    _restartingPromise = null;
    /** True if a config change arrived while _restarting was set; triggers a second cycle. */
    _restartDirty = false;
    constructor(config = {}) {
        this.config = config;
        const resolved = resolveDaemonFacadeRuntime(config);
        this.configManager = resolved.configManager;
        this.runtimeServices = resolved.runtimeServices;
        this.integrationHelpers = resolved.integrationHelpers;
        this.port = resolved.port;
        this.host = resolved.host;
        this.agentManager = resolved.agentManager;
        this.userAuth = resolved.userAuth;
        this.automationManager = resolved.automationManager;
        this.runtimeBus = resolved.runtimeBus;
        this.runtimeStore = resolved.runtimeStore;
        this.runtimeDispatch = resolved.runtimeDispatch;
        this.controlPlaneGateway = resolved.controlPlaneGateway;
        this.gatewayMethods = resolved.gatewayMethods;
        this.sessionBroker = resolved.sessionBroker;
        this.approvalBroker = resolved.approvalBroker;
        this.routeBindings = resolved.routeBindings;
        this.deliveryManager = resolved.deliveryManager;
        this.surfaceRegistry = resolved.surfaceRegistry;
        this.channelPolicy = resolved.channelPolicy;
        this.channelPlugins = resolved.channelPlugins;
        this.watcherRegistry = resolved.watcherRegistry;
        this.platformServiceManager = resolved.platformServiceManager;
        this.distributedRuntime = resolved.distributedRuntime;
        this.voiceService = resolved.voiceService;
        this.webSearchService = resolved.webSearchService;
        this.knowledgeService = resolved.knowledgeService;
        this.knowledgeGraphqlService = resolved.knowledgeGraphqlService;
        this.mediaProviders = resolved.mediaProviders;
        this.multimodalService = resolved.multimodalService;
        this.artifactStore = resolved.artifactStore;
        this.serviceRegistry = resolved.serviceRegistry;
        this.serveFactory = resolved.serveFactory;
        this.githubWebhookSecret = resolved.githubWebhookSecret;
        this.companionChatManager = resolved.companionChatManager;
        const collaborators = createDaemonFacadeCollaborators({
            runtime: resolved,
            pendingSurfaceReplies: this.pendingSurfaceReplies,
            authToken: () => this.authToken,
            trustProxyEnabled: () => this.trustProxyEnabled(),
            dispatchApiRoutes: (req) => this.dispatchApiRoutes(req),
            parseJsonBody: (req) => this.parseJsonBody(req),
            requireAuthenticatedSession: (req) => this.requireAuthenticatedSession(req),
            trySpawnAgent: (input, logLabel, sessionId) => this.trySpawnAgent(input, logLabel, sessionId),
            checkAuth: (req) => this.checkAuth(req),
            extractAuthToken: (req) => this.extractAuthToken(req),
            requireAdmin: (req) => this.requireAdmin(req),
            requireRemotePeer: (req, scope) => this.requireRemotePeer(req, scope),
            describeAuthenticatedPrincipal: (token) => this.describeAuthenticatedPrincipal(token),
            invokeGatewayMethodCall: (input) => this.invokeGatewayMethodCall(input),
            syncSpawnedAgentTask: (record, sessionId) => this.syncSpawnedAgentTask(record, sessionId),
            syncFinishedAgentTask: (record) => this.syncFinishedAgentTask(record),
            surfaceDeliveryEnabled: (surface) => this.surfaceDeliveryEnabled(surface),
            signWebhookPayload: (body, secret) => this.signWebhookPayload(body, secret),
            handleApprovalAction: (approvalId, action, req) => this.handleApprovalAction(approvalId, action, req),
            tlsState: () => this.tlsState,
            swapManager: this.config.swapManager ?? null,
            // Resolve companion-chat defaults from the live ProviderRegistry so session
            // creation uses the current route, not a snapshot captured at startup.
            resolveDefaultProviderModel: () => {
                try {
                    const current = resolved.runtimeServices.providerRegistry.getCurrentModel();
                    if (!current.provider || !current.id)
                        return null;
                    return { provider: current.provider, model: current.id };
                }
                catch {
                    return null;
                }
            },
        });
        this.channelReplyPipeline = collaborators.channelReplyPipeline;
        this.controlPlaneHelper = collaborators.controlPlaneHelper;
        this.surfaceDeliveryHelper = collaborators.surfaceDeliveryHelper;
        this.surfaceActionHelper = collaborators.surfaceActionHelper;
        this.transportEventsHelper = collaborators.transportEventsHelper;
        this.httpRouter = collaborators.httpRouter;
        this.providerRuntime = collaborators.providerRuntime;
        this.builtinChannels = collaborators.builtinChannels;
        // Wire AgentTaskAdapter to the RuntimeEventBus so task records reach
        // terminal states when their backing agent finishes.
        this.agentTaskAdapter = new AgentTaskAdapter(this.runtimeStore);
        this.agentTaskAdapterUnsub = this.agentTaskAdapter.attachRuntimeBus(this.runtimeBus);
        // Mark any tasks that were running at startup as aborted after daemon restart.
        this.agentTaskAdapter.reconcileOnRestart();
        configureDaemonSessionContinuation({
            sessionBroker: this.sessionBroker,
            trySpawnAgent: (input, logLabel, sessionId) => this.trySpawnAgent(input, logLabel, sessionId),
            queueSurfaceReplyFromBinding: (binding, input) => this.surfaceDeliveryHelper.queueSurfaceReplyFromBinding(binding, input),
        });
        this.distributedRuntime.attachRuntime({
            sessionBridge: this.sessionBroker,
            approvalBridge: this.approvalBroker,
            automationBridge: this.automationManager,
            eventPublisher: (event, payload) => {
                this.controlPlaneGateway.publishEvent(event, payload);
            },
        });
    }
    listRecentControlPlaneEvents(limit = 100) {
        return this.controlPlaneGateway.listRecentEvents(limit);
    }
    /**
     * Enable the daemon. Requires danger.daemon = true in config.
     * The provided token is used to authenticate all incoming requests.
     * Returns true if enabled, false if the config forbids it.
     */
    enable(dangerConfig, token) {
        if (!dangerConfig.daemon) {
            logger.info('DaemonServer.enable: danger.daemon is false — not enabling');
            return false;
        }
        this.enabled = true;
        this.authToken = token ?? null;
        this.controlPlaneGateway.setServerState({ enabled: true, host: this.host, port: this.port });
        return true;
    }
    /**
     * Start the daemon. Refuses to start if not explicitly enabled.
     */
    async start() {
        if (!this.enabled) {
            logger.info('Daemon mode is disabled. Enable via danger.daemon config.');
            return;
        }
        if (this.authToken === null) {
            logger.info('DaemonServer: starting with session-based authentication via UserAuth');
        }
        if (this.server !== null) {
            logger.info('DaemonServer: already running');
            return;
        }
        new GlobalNetworkTransportInstaller().install(this.configManager);
        if (!this.approvalBrokerUnsubscribe) {
            this.approvalBrokerUnsubscribe = this.approvalBroker.subscribe((approval) => {
                void this.surfaceDeliveryHelper.notifyApprovalUpdate(approval).catch((error) => {
                    logger.warn('DaemonServer: approval notification failed', {
                        approvalId: approval.id,
                        error: summarizeError(error),
                    });
                });
            });
        }
        this.routeBindings.attachRuntime({
            runtimeBus: this.runtimeBus,
            runtimeStore: this.runtimeStore,
        });
        this.surfaceRegistry.attachRuntime(this.runtimeStore);
        this.deliveryManager.attachRuntime({
            runtimeBus: this.runtimeBus,
            runtimeStore: this.runtimeStore,
        });
        this.automationManager.attachRuntime({
            runtimeBus: this.runtimeBus,
            runtimeStore: this.runtimeStore,
            deliveryManager: this.deliveryManager,
        });
        this.controlPlaneGateway.attachRuntime({
            runtimeBus: this.runtimeBus,
            runtimeStore: this.runtimeStore,
        });
        const self = this;
        // Skip the OS port probe when the host injects a custom serve factory.
        if (this.serveFactory === Bun.serve) {
            await requirePortAvailable(this.port, this.host, 'daemon');
        }
        this.transportEventsHelper.emitTransportInitializing();
        try {
            this.tlsState = resolveInboundTlsContext(this.configManager, 'controlPlane');
            this.server = this.serveFactory({
                port: this.port,
                hostname: this.host,
                ...(this.tlsState.tls ? { tls: this.tlsState.tls } : {}),
                async fetch(req, server) {
                    const upgrade = self.tryUpgradeControlPlaneWebSocket(req, server);
                    if (upgrade === 'upgraded')
                        return;
                    if (upgrade)
                        return upgrade;
                    return self.handleRequest(req);
                },
                websocket: {
                    open(ws) {
                        self.handleControlPlaneWebSocketOpen(ws);
                    },
                    message(ws, message) {
                        void self.handleControlPlaneWebSocketMessage(ws, message).catch((error) => {
                            logger.warn('DaemonServer: control-plane websocket message failed', {
                                error: summarizeError(error),
                            });
                        });
                    },
                    close(ws) {
                        self.handleControlPlaneWebSocketClose(ws);
                    },
                },
            });
            await Promise.all([
                this.sessionBroker.start(),
                this.approvalBroker.start(),
                this.channelPolicy.start(),
                this.automationManager.start(),
                this.distributedRuntime.start(),
            ]);
            await this.providerRuntime.startConfigured();
            // Load persisted companion sessions after providers are configured.
            await this.companionChatManager.init();
            if (this.replyPoller === null) {
                // Poll every 2 s for surface replies that have resolved asynchronously.
                // The interval is intentionally short to keep companion response latency
                // below a perceptible threshold while still batching concurrent replies.
                this.replyPoller = setInterval(() => {
                    void this.pollPendingSurfaceReplies().catch((error) => {
                        logger.warn('DaemonServer: surface reply poll failed', { error: summarizeError(error) });
                    });
                }, 2_000);
                // unref() prevents this timer from keeping the Node.js/Bun event loop
                // alive if all other async work has completed — same pattern as animInterval.
                this.replyPoller.unref?.();
            }
            this.surfaceRegistry.syncConfiguredSurfaces();
            if (this.configManager.get('watchers.enabled')) {
                this.watcherRegistry.registerPollingWatcher({
                    id: 'daemon-heartbeat',
                    label: 'Daemon heartbeat',
                    source: {
                        id: 'source:daemon-heartbeat',
                        kind: 'watcher',
                        label: 'Daemon heartbeat',
                        enabled: true,
                        createdAt: Date.now(),
                        updatedAt: Date.now(),
                        metadata: {},
                    },
                    intervalMs: Number(this.configManager.get('watchers.heartbeatIntervalMs') ?? 30_000),
                    run: () => new Date().toISOString(),
                });
                this.watcherRegistry.startWatcher('daemon-heartbeat');
            }
            this.controlPlaneGateway.setServerState({ enabled: true, host: this.host, port: this.port });
            this._attachControlPlaneConfigWatcher();
            this.transportEventsHelper.emitTransportConnected();
            logger.info('DaemonServer started', {
                port: this.port,
                host: this.host,
                tlsMode: this.tlsState.mode,
                scheme: this.tlsState.scheme,
                trustProxy: this.tlsState.trustProxy,
            });
        }
        catch (err) {
            const message = summarizeError(err);
            if (this.replyPoller !== null) {
                clearInterval(this.replyPoller);
                this.replyPoller = null;
            }
            this.pendingSurfaceReplies.clear();
            this.automationManager.stop();
            this.providerRuntime.stopAll();
            try {
                this.watcherRegistry.stopWatcher('daemon-heartbeat', 'daemon-start-failed');
            }
            catch (cleanupError) {
                logger.warn('DaemonServer startup cleanup failed to stop heartbeat watcher', {
                    error: summarizeError(cleanupError),
                });
            }
            this.approvalBrokerUnsubscribe?.();
            this.approvalBrokerUnsubscribe = null;
            if (this.server !== null) {
                this.server.stop(true);
                this.server = null;
            }
            this.tlsState = null;
            this.controlPlaneGateway.setServerState({ enabled: this.enabled, host: this.host, port: this.port });
            this.transportEventsHelper.emitTransportTerminalFailure(message);
            throw err;
        }
    }
    /**
     * Stop the daemon server.
     *
     * Services are stopped in reverse start order. Each service stop is raced
     * against a 10-second timeout so a hung service cannot block the full
     * shutdown sequence.
     */
    /**
     * Wait for any in-progress config-driven restart to settle.
     * Callers that change config mid-flight and need to know when the server
     * has rebounded should await this before inspecting state.
     */
    async waitForRestart() {
        // Loop to handle dirty-flag chained restarts: each cycle may spawn another.
        while (this._restartingPromise)
            await this._restartingPromise;
    }
    async stop() {
        if (this.server === null)
            return;
        // Tear down config watcher only on intentional stop, not mid-restart.
        // During a restart cycle (_restarting=true) the watcher must stay active so
        // config changes that arrive between stop() and the subsequent start() can be
        // captured by the dirty flag.
        if (!this._restarting) {
            this._configWatchUnsub?.();
            this._configWatchUnsub = null;
        }
        // Synchronous pre-stop teardown
        this.watcherRegistry.stopWatcher('daemon-heartbeat', 'daemon-stopped');
        if (this.replyPoller !== null) {
            clearInterval(this.replyPoller);
            this.replyPoller = null;
        }
        this.pendingSurfaceReplies.clear();
        this.approvalBrokerUnsubscribe?.();
        this.approvalBrokerUnsubscribe = null;
        this.httpRouter.dispose();
        this.companionChatManager.dispose();
        // Stop services that expose async teardown. Note: sessionBroker, approvalBroker,
        // channelPolicy, and distributedRuntime expose start() only — their lifecycle ends
        // when the server socket closes. We stop what we can in reverse start order.
        this.providerRuntime.stopAll();
        this.automationManager.stop();
        // Tear down the adapter bus subscription and session broker GC interval.
        this.agentTaskAdapterUnsub?.();
        this.agentTaskAdapterUnsub = null;
        await this.sessionBroker.stop();
        this.server.stop(true);
        this.server = null;
        this.tlsState = null;
        this.controlPlaneGateway.setServerState({ enabled: this.enabled, host: this.host, port: this.port });
        this.transportEventsHelper.emitTransportDisconnected('Daemon server stopped', false);
        logger.info('DaemonServer stopped');
    }
    /**
     * Subscribe to controlPlane binding keys and restart the server on change.
     * Called once from start() after the server is up. Clears itself on stop().
     */
    _attachControlPlaneConfigWatcher() {
        if (this._configWatchUnsub)
            return; // idempotent
        const restart = () => {
            if (this._restarting) {
                // A change arrived mid-restart — queue a second cycle via dirty flag.
                // Check _restarting BEFORE isRunning: stop() runs synchronously inside the
                // restart IIFE, so isRunning may be false even while a restart is in progress.
                this._restartDirty = true;
                return;
            }
            if (!this.isRunning)
                return;
            this._restarting = true;
            this._restartingPromise = (async () => {
                try {
                    logger.info('DaemonServer: controlPlane binding changed, restarting daemon server…');
                    await this.stop();
                    // Re-resolve host/port from updated config
                    const newBinding = resolveHostBinding(this.configManager.get('controlPlane.hostMode') ?? 'local', String(this.configManager.get('controlPlane.host') ?? '127.0.0.1'), Number(this.configManager.get('controlPlane.port') ?? 3421), 'controlPlane');
                    this.host = newBinding.host;
                    this.port = newBinding.port;
                    await this.start();
                }
                catch (err) {
                    logger.error('DaemonServer: restart after config change failed', { error: summarizeError(err) });
                }
                finally {
                    this._restarting = false;
                    // If a config change arrived while we were restarting, kick off a second
                    // cycle BEFORE nulling _restartingPromise so waitForRestart() chains correctly.
                    if (this._restartDirty) {
                        this._restartDirty = false;
                        restart(); // sets this._restartingPromise to the new cycle
                    }
                    else {
                        this._restartingPromise = null;
                    }
                }
            })();
        };
        // getIsRunning must also return true while a restart cycle is in progress
        // (_restarting=true) so that config changes arriving mid-restart reach the
        // dirty-flag path inside `restart`. When the server is intentionally stopped
        // (not mid-restart) isRunning and _restarting are both false.
        const watcher = createHostModeRestartWatcher({
            configManager: this.configManager,
            keys: ['controlPlane.hostMode', 'controlPlane.host', 'controlPlane.port'],
            onRestart: restart,
            getIsRunning: () => this.isRunning || this._restarting,
        });
        this._configWatchUnsub = () => watcher.unsubscribe();
    }
    /**
     * Returns true if the server is currently running.
     */
    get isRunning() {
        return this.server !== null;
    }
    // -------------------------------------------------------------------------
    // Auth
    // -------------------------------------------------------------------------
    extractAuthToken(req) {
        return this.controlPlaneHelper.extractAuthToken(req);
    }
    checkAuth(req) {
        return this.controlPlaneHelper.checkAuth(req);
    }
    requireAuthenticatedSession(req) {
        return this.controlPlaneHelper.requireAuthenticatedSession(req);
    }
    requireAdmin(req) {
        return this.controlPlaneHelper.requireAdmin(req);
    }
    async requireRemotePeer(req, scope) {
        return await this.controlPlaneHelper.requireRemotePeer(req, scope);
    }
    describeAuthenticatedPrincipal(token) {
        return this.controlPlaneHelper.describeAuthenticatedPrincipal(token);
    }
    getGrantedGatewayScopes(includeWrite) {
        return this.controlPlaneHelper.getGrantedGatewayScopes(includeWrite);
    }
    validateGatewayInvocation(descriptor, context) {
        return this.controlPlaneHelper.validateGatewayInvocation(descriptor, context);
    }
    tryUpgradeControlPlaneWebSocket(req, server) {
        return this.controlPlaneHelper.tryUpgradeControlPlaneWebSocket(req, server);
    }
    handleControlPlaneWebSocketOpen(ws) {
        this.controlPlaneHelper.handleControlPlaneWebSocketOpen(ws);
    }
    async handleControlPlaneWebSocketMessage(ws, message) {
        await this.controlPlaneHelper.handleControlPlaneWebSocketMessage(ws, message);
    }
    handleControlPlaneWebSocketClose(ws) {
        this.controlPlaneHelper.handleControlPlaneWebSocketClose(ws);
    }
    async invokeWebSocketControlPlaneCall(input) {
        return await this.controlPlaneHelper.invokeWebSocketControlPlaneCall(input);
    }
    async invokeGatewayMethodCall(input) {
        return await this.controlPlaneHelper.invokeGatewayMethodCall(input);
    }
    // -------------------------------------------------------------------------
    // Request handling
    // -------------------------------------------------------------------------
    async handleRequest(req) {
        return await this.httpRouter.handleRequest(req);
    }
    async dispatchApiRoutes(req) {
        return await this.httpRouter.dispatchApiRoutes(req);
    }
    async parseJsonBody(req) {
        return await this.httpRouter.parseJsonBody(req);
    }
    async parseOptionalJsonBody(req) {
        return await this.httpRouter.parseOptionalJsonBody(req);
    }
    parseJsonText(rawBody) {
        return this.httpRouter.parseJsonText(rawBody);
    }
    recordApiResponse(req, path, response, clientKind = 'web') {
        return this.httpRouter.recordApiResponse(req, path, response, clientKind);
    }
    async handleApprovalAction(approvalId, action, req) {
        const body = await this.parseOptionalJsonBody(req);
        const payload = body instanceof Response || body === null ? {} : body;
        const actor = this.requireAuthenticatedSession(req)?.username ?? (this.authToken ? 'shared-token' : 'operator');
        const note = typeof payload.note === 'string' ? payload.note : undefined;
        if (action === 'claim') {
            const approval = await this.approvalBroker.claimApproval(approvalId, actor, 'web', note);
            return approval
                ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
                : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
        }
        if (action === 'cancel') {
            const approval = await this.approvalBroker.cancelApproval(approvalId, actor, 'web', note);
            return approval
                ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
                : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
        }
        const approval = await this.approvalBroker.resolveApproval(approvalId, {
            approved: action === 'approve',
            remember: typeof payload.remember === 'boolean' ? payload.remember : false,
            actor,
            actorSurface: 'web',
            note,
        });
        return approval
            ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
            : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
    }
    trySpawnAgent(input, logLabel = 'DaemonServer', sessionId) {
        try {
            const spawnInput = Array.isArray(input.tools)
                ? {
                    ...input,
                    tools: [...(input.tools ?? [])],
                }
                : input;
            const record = this.agentManager.spawn(spawnInput);
            this.syncSpawnedAgentTask(record, sessionId);
            // Register the agent with AgentTaskAdapter so bus events can transition its task.
            if (this.agentTaskAdapter) {
                this.agentTaskAdapter.wrapAgent(record.id, record.task, { sessionId: sessionId ?? 'daemon' });
            }
            return record;
        }
        catch (err) {
            const message = summarizeError(err);
            logger.error(`${logLabel}: agent spawn failed`, { error: message });
            // Capacity-reached is backpressure, not a server error — return 429 so
            // clients can apply the Retry-After hint and SDK retry policy handles it.
            const isCapacity = typeof message === 'string' && message.includes('capacity reached');
            if (isCapacity) {
                return Response.json({
                    code: 'CAPACITY_EXCEEDED',
                    error: message,
                    category: 'service',
                    source: 'runtime',
                    recoverable: true,
                    hint: 'Wait for the current agent to complete or raise the orchestration.maxActiveAgents configuration.',
                    status: 429,
                }, {
                    status: 429,
                    headers: { 'Retry-After': '5' },
                });
            }
            return jsonErrorResponse(err, { status: 500, fallbackMessage: 'Failed to spawn agent' });
        }
    }
    syncSpawnedAgentTask(record, sessionId) {
        this.runtimeDispatch?.syncRuntimeTask({
            id: record.id,
            kind: 'agent',
            title: record.task.length > 80 ? `${record.task.slice(0, 77)}...` : record.task,
            description: record.task,
            status: record.status === 'pending' ? 'queued' : 'running',
            owner: record.id,
            cancellable: true,
            childTaskIds: [],
            queuedAt: record.startedAt,
            startedAt: record.status === 'pending' ? undefined : record.startedAt,
            correlationId: sessionId,
        }, 'daemon.server.agent-spawn');
    }
    syncFinishedAgentTask(record) {
        const status = record.status === 'completed'
            ? 'completed'
            : record.status === 'failed'
                ? 'failed'
                : 'cancelled';
        this.runtimeDispatch?.transitionRuntimeTask(record.id, status, {
            endedAt: record.completedAt ?? Date.now(),
            result: record.fullOutput ?? record.streamingContent,
            error: record.error,
        }, 'daemon.server.agent-finish');
    }
    surfaceDeliveryEnabled(surface) {
        return isSurfaceFeatureGateEnabled(this.runtimeServices.featureFlags, surface)
            && isSurfaceDeliveryEnabled(this.configManager, surface);
    }
    async pollPendingSurfaceReplies() {
        await this.surfaceDeliveryHelper.pollPendingSurfaceReplies((record) => this.syncFinishedAgentTask(record));
    }
    trustProxyEnabled() {
        return this.tlsState?.trustProxy ?? Boolean(this.configManager.get('controlPlane.trustProxy'));
    }
    signWebhookPayload(body, secret) {
        return this.surfaceDeliveryHelper.signWebhookPayload(body, secret);
    }
}
