import { isValidConfigKey } from '../../config/schema.js';
import { buildOperatorSessionCookie, OPERATOR_SESSION_COOKIE_NAME } from '../../security/http-auth.js';
import { normalizeAtSchedule, normalizeCronSchedule, normalizeEverySchedule, } from '../../automation/index.js';
import { buildOperatorContract } from '../../control-plane/operator-contract.js';
import { getProviderRuntimeSnapshot, getProviderUsageSnapshot, listProviderRuntimeSnapshots, } from '../../providers/runtime-snapshot.js';
import { inspectKnowledgeGraphqlAccess } from '../../knowledge/index.js';
import { emitCompanionMessageReceived } from '../../runtime/emitters/session.js';
import { correlationCtx } from '../../runtime/correlation.js';
import { TelemetryApiService } from '../../runtime/telemetry/api.js';
import { inspectInboundTls, inspectOutboundTls } from '../../runtime/network/index.js';
import { dispatchDaemonApiRoutes } from '../../control-plane/routes/index.js';
import { handleGitHubAutomationWebhook, handleSlackSurfaceWebhook, handleDiscordSurfaceWebhook, handleNtfySurfaceWebhook, handleGenericWebhookSurface } from '../../adapters/index.js';
import { createDaemonKnowledgeRouteHandlers } from './knowledge-routes.js';
import { createDaemonMediaRouteHandlers } from './media-routes.js';
import { createDaemonRemoteRouteHandlers, handleRemotePairRequest, handleRemotePairVerify, handleRemotePeerHeartbeat, handleRemotePeerWorkPull, handleRemotePeerWorkComplete, } from './remote-routes.js';
import { createDaemonRuntimeRouteHandlers } from './runtime-routes.js';
import { snapshotMetrics } from '../../runtime/metrics.js';
import { createDaemonControlRouteHandlers } from './control-routes.js';
import { createDaemonIntegrationRouteHandlers } from './integration-routes.js';
import { createDaemonTelemetryRouteHandlers } from './telemetry-routes.js';
import { createDaemonChannelRouteHandlers } from './channel-routes.js';
import { createDaemonSystemRouteHandlers } from './system-routes.js';
import { buildChannelRouteContext, buildKnowledgeRouteContext, buildMediaRouteContext, buildSystemRouteContext, } from './router-route-contexts.js';
import { jsonErrorResponse } from './error-response.js';
import { AppError } from '../../types/errors.js';
import { VERSION } from '../../version.js';
import { dispatchCompanionChatRoutes } from '../../companion/companion-chat-routes.js';
import { dispatchModelRoutes } from './model-routes.js';
import { dispatchBatchRoutes } from './batch-routes.js';
import { dispatchCloudflareRoutes } from './cloudflare-routes.js';
import { HomeAssistantConversationRoutes } from './homeassistant-routes.js';
import { HomeGraphRoutes } from './home-graph-routes.js';
import { dispatchOpenAICompatibleRoutes } from './openai-compatible-routes.js';
import { ProjectPlanningRoutes } from './project-planning-routes.js';
import { parseDaemonJsonBody, parseDaemonJsonText, parseOptionalDaemonJsonBody, } from './router-request-body.js';
export class DaemonHttpRouter {
    context;
    telemetryApi;
    homeAssistantRoutes = null;
    homeGraphRoutes = null;
    projectPlanningRoutes = null;
    constructor(context) {
        this.context = context;
        this.telemetryApi = context.runtimeStore
            ? new TelemetryApiService({
                runtimeBus: context.runtimeBus,
                runtimeStore: context.runtimeStore,
            })
            : null;
    }
    dispose() {
        this.telemetryApi?.dispose();
        this.context.batchManager?.dispose();
    }
    getConfigValue(key) {
        const getter = this.context.configManager.get;
        return typeof getter === 'function' ? getter.call(this.context.configManager, key) : undefined;
    }
    async handleRequest(req) {
        return correlationCtx.run({ requestId: req.headers.get('x-request-id') ?? crypto.randomUUID() }, async () => {
            // Pre-auth routes: no auth check applied (login, remote-peer handshake,
            // webhooks, and control-plane web UI).
            const preAuth = await this.dispatchPreAuthRoutes(req);
            if (preAuth)
                return preAuth;
            if (!this.context.checkAuth(req)) {
                return jsonErrorResponse(new AppError('Authentication required', 'AUTH_REQUIRED', false, {
                    category: 'authentication',
                    source: 'runtime',
                    guidance: 'Authenticate with the operator shared token or an authenticated user session before calling daemon APIs.',
                }), { status: 401 });
            }
            const apiResponse = await this.dispatchApiRoutes(req);
            if (apiResponse)
                return apiResponse;
            const url = new URL(req.url);
            return jsonErrorResponse(new AppError(`Route not found: ${url.pathname}`, 'NOT_FOUND', false, {
                category: 'not_found',
                source: 'runtime',
                guidance: 'Check the daemon API path and version. New SDK-facing routes are published under /api/v1.',
            }), { status: 404 });
        });
    }
    /**
     * Dispatches routes that are exempt from the auth gate: login, remote-peer
     * handshake, webhooks, and the control-plane web UI. Extracted from
     * handleRequest to eliminate the 8-entry inline if-chain (m2/m3).
     */
    async dispatchPreAuthRoutes(req) {
        const url = new URL(req.url);
        if (url.pathname === '/login' && req.method === 'POST') {
            return this.handleLogin(req);
        }
        if (url.pathname === '/api/remote/pair/request' && req.method === 'POST') {
            return handleRemotePairRequest({
                parseJsonBody: (request) => this.parseJsonBody(request),
                distributedRuntime: this.context.distributedRuntime,
            }, req);
        }
        if (url.pathname === '/api/remote/pair/verify' && req.method === 'POST') {
            return handleRemotePairVerify({
                parseJsonBody: (request) => this.parseJsonBody(request),
                distributedRuntime: this.context.distributedRuntime,
            }, req);
        }
        if (url.pathname === '/api/remote/heartbeat' && req.method === 'POST') {
            return handleRemotePeerHeartbeat({
                parseJsonBody: (request) => this.parseJsonBody(request),
                requireRemotePeer: (request, scope) => this.context.requireRemotePeer(request, scope),
                distributedRuntime: this.context.distributedRuntime,
            }, req);
        }
        if (url.pathname === '/api/remote/work/pull' && req.method === 'POST') {
            return handleRemotePeerWorkPull({
                parseJsonBody: (request) => this.parseJsonBody(request),
                requireRemotePeer: (request, scope) => this.context.requireRemotePeer(request, scope),
                distributedRuntime: this.context.distributedRuntime,
            }, req);
        }
        const remoteWorkCompleteMatch = url.pathname.match(/^\/api\/remote\/work\/([^/]+)\/complete$/);
        if (remoteWorkCompleteMatch && req.method === 'POST') {
            return handleRemotePeerWorkComplete({
                parseJsonBody: (request) => this.parseJsonBody(request),
                requireRemotePeer: (request, scope) => this.context.requireRemotePeer(request, scope),
                distributedRuntime: this.context.distributedRuntime,
            }, remoteWorkCompleteMatch[1], req);
        }
        if (url.pathname === '/webhook/github' && req.method === 'POST') {
            return this.handleGitHubWebhook(req);
        }
        if (url.pathname.startsWith('/webhook/')) {
            const pluginResponse = await this.context.channelPlugins.handleInbound(url.pathname, req);
            if (pluginResponse)
                return pluginResponse;
        }
        if (url.pathname === '/api/control-plane/web' && req.method === 'GET') {
            return this.context.controlPlaneGateway.renderWebUi();
        }
        if (url.pathname === '/api/control-plane/auth' && req.method === 'GET') {
            const apiResponse = await this.dispatchApiRoutes(req);
            if (apiResponse)
                return apiResponse;
        }
        return null;
    }
    async dispatchApiRoutes(req) {
        const url = new URL(req.url);
        const openAICompatibleEnabled = this.getConfigValue('controlPlane.openaiCompatible.enabled') !== false;
        if (openAICompatibleEnabled) {
            const pathPrefix = this.getConfigValue('controlPlane.openaiCompatible.pathPrefix');
            const response = await dispatchOpenAICompatibleRoutes(req, {
                providerRegistry: this.context.providerRegistry,
                parseJsonBody: (request) => this.parseJsonBody(request),
                recordApiResponse: (request, path, routeResponse) => this.recordApiResponse(request, path, routeResponse),
            }, typeof pathPrefix === 'string' && pathPrefix.trim() ? pathPrefix : '/v1');
            if (response)
                return response;
        }
        if (url.pathname.startsWith('/api/batch')) {
            if (!this.context.batchManager) {
                return Response.json({ error: 'Batch manager is not available', code: 'BATCH_MANAGER_UNAVAILABLE' }, { status: 503 });
            }
            const batchResponse = await dispatchBatchRoutes(req, {
                batchManager: this.context.batchManager,
                parseJsonBody: (request) => this.parseJsonBody(request),
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
            });
            if (batchResponse)
                return batchResponse;
        }
        if (url.pathname.startsWith('/api/cloudflare')) {
            const adminError = this.context.requireAdmin(req);
            if (adminError)
                return adminError;
            const cloudflareResponse = await dispatchCloudflareRoutes(req, {
                configManager: this.context.configManager,
                secretsManager: this.context.secretsManager,
                authToken: this.context.authToken,
                parseJsonBody: (request) => this.parseJsonBody(request),
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
            });
            if (cloudflareResponse)
                return cloudflareResponse;
        }
        if (url.pathname.startsWith('/api/homeassistant')) {
            const homeGraphResponse = await this.getHomeGraphRoutes().handle(req);
            if (homeGraphResponse)
                return homeGraphResponse;
            const homeAssistantResponse = await this.getHomeAssistantRoutes().handle(req);
            if (homeAssistantResponse)
                return homeAssistantResponse;
        }
        if (url.pathname.startsWith('/api/projects/planning')) {
            const projectPlanningResponse = await this.getProjectPlanningRoutes().handle(req);
            if (projectPlanningResponse)
                return projectPlanningResponse;
        }
        // Companion chat routes — scoped to /api/companion/chat/..., session-isolated.
        // Handled before the main API router so they never touch the global control-plane feed.
        // Model catalog + model-switching routes. Provider runtime metadata is owned
        // by the operator contract under /api/providers.
        if (url.pathname === '/api/models' || url.pathname.startsWith('/api/models/')) {
            const providerResponse = await dispatchModelRoutes(req, {
                providerRegistry: this.context.providerRegistry,
                configManager: this.context.configManager,
                runtimeBus: this.context.runtimeBus,
                parseJsonBody: (request) => this.parseJsonBody(request),
                secretsManager: this.context.secretsManager,
            });
            if (providerResponse)
                return providerResponse;
        }
        if (this.context.companionChatManager && req.url.includes('/api/companion/chat/')) {
            const gateway = this.context.controlPlaneGateway;
            const chatManager = this.context.companionChatManager;
            const companionResponse = await dispatchCompanionChatRoutes(req, {
                chatManager,
                parseJsonBody: (request) => this.parseJsonBody(request),
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                resolveDefaultProviderModel: this.context.resolveDefaultProviderModel,
                openSessionEventStream: (request, sessionId) => {
                    // Create a session-scoped SSE stream. Use the clientId as the isolation key.
                    const clientId = `companion-chat:${sessionId}`;
                    chatManager.registerSubscriber(sessionId, clientId);
                    return gateway.createEventStream(request, {
                        clientId,
                        // Companion chat sessions are isolated per-session via the clientId above;
                        // we reuse 'web' as the clientKind since the gateway's kind union does not
                        // yet include 'companion'. Client-side filtering uses clientId, not clientKind.
                        clientKind: 'web',
                        sessionId,
                        label: `companion-chat/${sessionId}`,
                    });
                },
            });
            if (companionResponse)
                return companionResponse;
        }
        const handlers = {
            ...createDaemonControlRouteHandlers({
                authToken: this.context.authToken(),
                version: VERSION,
                sessionCookieName: OPERATOR_SESSION_COOKIE_NAME,
                controlPlaneGateway: this.context.controlPlaneGateway,
                extractAuthToken: this.context.extractAuthToken,
                resolveAuthenticatedPrincipal: (request) => {
                    const token = this.context.extractAuthToken(request);
                    return token ? this.context.describeAuthenticatedPrincipal(token) : null;
                },
                gatewayMethods: this.context.gatewayMethods,
                getOperatorContract: () => buildOperatorContract(this.context.gatewayMethods),
                invokeGatewayMethodCall: this.context.invokeGatewayMethodCall,
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                requireAdmin: this.context.requireAdmin,
                requireAuthenticatedSession: this.context.requireAuthenticatedSession,
            }),
            postLogin: (request) => this.handleLogin(request),
            ...createDaemonIntegrationRouteHandlers({
                channelPlugins: this.context.channelPlugins,
                integrationHelpers: this.context.integrationHelpers,
                memoryEmbeddingRegistry: this.context.memoryEmbeddingRegistry,
                memoryRegistry: this.context.memoryRegistry,
                parseJsonBody: (request) => this.parseJsonBody(request),
                providerRuntime: {
                    listSnapshots: () => listProviderRuntimeSnapshots(this.context.providerRegistry),
                    getSnapshot: (providerId) => getProviderRuntimeSnapshot(this.context.providerRegistry, providerId),
                    getUsageSnapshot: (providerId) => getProviderUsageSnapshot(this.context.providerRegistry, providerId),
                },
                requireAdmin: (request) => this.context.requireAdmin(request),
                userAuth: this.context.userAuth,
            }),
            ...createDaemonTelemetryRouteHandlers({
                telemetryApi: this.telemetryApi,
                resolveAuthenticatedPrincipal: (request) => {
                    const token = this.context.extractAuthToken(request);
                    return token ? this.context.describeAuthenticatedPrincipal(token) : null;
                },
                ingestSink: this.telemetryApi,
            }),
            ...createDaemonChannelRouteHandlers({
                ...buildChannelRouteContext({
                    channelPlugins: this.context.channelPlugins,
                    channelPolicy: this.context.channelPolicy,
                    parseJsonBody: (request) => this.parseJsonBody(request),
                    parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                    requireAdmin: (request) => this.context.requireAdmin(request),
                    surfaceRegistry: this.context.surfaceRegistry,
                }),
            }),
            ...createDaemonSystemRouteHandlers({
                ...buildSystemRouteContext({
                    approvalBroker: this.context.approvalBroker,
                    configManager: this.context.configManager,
                    integrationHelpers: this.context.integrationHelpers,
                    inspectInboundTls: (surface) => inspectInboundTls(this.context.configManager, surface),
                    inspectOutboundTls: () => inspectOutboundTls(this.context.configManager),
                    isValidConfigKey,
                    parseJsonBody: (request) => this.parseJsonBody(request),
                    parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                    platformServiceManager: this.context.platformServiceManager,
                    recordApiResponse: (request, path, response, clientKind) => this.recordApiResponse(request, path, response, clientKind),
                    requireAdmin: (request) => this.context.requireAdmin(request),
                    requireAuthenticatedSession: (request) => this.context.requireAuthenticatedSession(request),
                    routeBindings: this.context.routeBindings,
                    swapManager: this.context.swapManager,
                    watcherRegistry: this.context.watcherRegistry,
                }),
            }),
            ...createDaemonRuntimeRouteHandlers({
                parseJsonBody: (request) => this.parseJsonBody(request),
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                recordApiResponse: (request, path, response) => this.recordApiResponse(request, path, response),
                requireAdmin: (request) => this.context.requireAdmin(request),
                sessionBroker: {
                    start: () => this.context.sessionBroker.start(),
                    submitMessage: (input) => this.context.sessionBroker.submitMessage(input),
                    steerMessage: (input) => this.context.sessionBroker.steerMessage(input),
                    followUpMessage: (input) => this.context.sessionBroker.followUpMessage(input),
                    bindAgent: async (sessionId, agentId) => {
                        await this.context.sessionBroker.bindAgent(sessionId, agentId);
                    },
                    createSession: (input) => this.context.sessionBroker.createSession(input),
                    getSession: (sessionId) => this.context.sessionBroker.getSession(sessionId),
                    getMessages: (sessionId, limit) => this.context.sessionBroker.getMessages(sessionId, limit),
                    getInputs: (sessionId, limit) => this.context.sessionBroker.getInputs(sessionId, limit),
                    closeSession: (sessionId) => this.context.sessionBroker.closeSession(sessionId),
                    reopenSession: (sessionId) => this.context.sessionBroker.reopenSession(sessionId),
                    cancelInput: (sessionId, inputId) => this.context.sessionBroker.cancelInput(sessionId, inputId),
                    completeAgent: async (sessionId, agentId, message, meta) => {
                        await this.context.sessionBroker.completeAgent(sessionId, agentId, message, meta);
                    },
                    appendCompanionMessage: (sessionId, input) => this.context.sessionBroker.appendCompanionMessage(sessionId, input),
                },
                agentManager: {
                    getStatus: (agentId) => this.context.agentManager.getStatus(agentId),
                    cancel: (agentId) => this.context.agentManager.cancel(agentId),
                },
                automationManager: {
                    listJobs: () => this.context.automationManager.listJobs(),
                    listRuns: () => this.context.automationManager.listRuns(),
                    getRun: (runId) => this.context.automationManager.getRun(runId) ?? null,
                    triggerHeartbeat: (input) => this.context.automationManager.triggerHeartbeat(input),
                    cancelRun: (runId, reason) => this.context.automationManager.cancelRun(runId, reason),
                    retryRun: (runId) => this.context.automationManager.retryRun(runId),
                    createJob: (input) => this.context.automationManager.createJob(input),
                    updateJob: (jobId, input) => this.context.automationManager.updateJob(jobId, input),
                    removeJob: async (jobId) => {
                        await this.context.automationManager.removeJob(jobId);
                    },
                    setEnabled: (jobId, enabled) => this.context.automationManager.setEnabled(jobId, enabled),
                    runNow: (jobId) => this.context.automationManager.runNow(jobId),
                    getSchedulerCapacity: () => this.context.automationManager.getSchedulerCapacity(),
                },
                normalizeAtSchedule,
                normalizeEverySchedule,
                normalizeCronSchedule,
                routeBindings: {
                    start: () => this.context.routeBindings.start(),
                    getBinding: (id) => this.context.routeBindings.getBinding(id),
                },
                trySpawnAgent: (input, logLabel, sessionId) => {
                    const { tools, ...spawnInput } = input;
                    return this.context.trySpawnAgent({
                        ...spawnInput,
                        ...(tools ? { tools: [...tools] } : {}),
                    }, logLabel, sessionId);
                },
                queueSurfaceReplyFromBinding: (binding, input) => this.context.queueSurfaceReplyFromBinding(binding, input),
                surfaceDeliveryEnabled: (surface) => this.context.surfaceDeliveryEnabled(surface),
                syncSpawnedAgentTask: (record, sessionId) => this.context.syncSpawnedAgentTask(record, sessionId),
                syncFinishedAgentTask: (record) => this.context.syncFinishedAgentTask(record),
                configManager: this.context.configManager,
                runtimeStore: this.context.runtimeStore,
                runtimeDispatch: this.context.runtimeDispatch,
                publishConversationFollowup: (sessionId, envelope) => {
                    // Scope the event to TUI-kind clients only: non-TUI clients (web, companion
                    // app, etc.) must not receive raw operator conversation follow-ups. Using
                    // clientKind:'tui' ensures only the TUI surface receives this event.
                    this.context.controlPlaneGateway.publishEvent('conversation.followup.companion', { sessionId, ...envelope }, { clientKind: 'tui' });
                    // Also emit on the runtime bus so the in-process TUI surface can
                    // subscribe and render the companion message in the conversation view.
                    emitCompanionMessageReceived(this.context.runtimeBus, { sessionId, traceId: `companion:${envelope.messageId}`, source: 'companion-followup' }, { sessionId, ...envelope });
                },
                snapshotMetrics: () => snapshotMetrics(),
                openSessionEventStream: (req, sessionId) => {
                    // Create a session-scoped SSE stream for the companion app to receive
                    // turn events (STREAM_DELTA, TURN_COMPLETED, etc.) and agent events.
                    // The 'turn' domain is included in DEFAULT_DOMAINS so turn events
                    // automatically flow to all SSE subscribers without extra configuration.
                    const clientId = `shared-session:${sessionId}`;
                    return this.context.controlPlaneGateway.createEventStream(req, {
                        clientId,
                        clientKind: 'web',
                        sessionId,
                        label: `shared-session/${sessionId}`,
                    });
                },
            }),
            ...createDaemonRemoteRouteHandlers({
                authToken: this.context.authToken(),
                parseJsonBody: (request) => this.parseJsonBody(request),
                requireAdmin: (request) => this.context.requireAdmin(request),
                requireRemotePeer: (request, scope) => this.context.requireRemotePeer(request, scope),
                requireAuthenticatedSession: (request) => this.context.requireAuthenticatedSession(request),
                distributedRuntime: this.context.distributedRuntime,
            }),
            ...createDaemonKnowledgeRouteHandlers({
                ...buildKnowledgeRouteContext({
                    artifactStore: this.context.artifactStore,
                    configManager: this.context.configManager,
                    inspectGraphqlAccess: inspectKnowledgeGraphqlAccess,
                    normalizeAtSchedule,
                    normalizeEverySchedule,
                    normalizeCronSchedule,
                    parseJsonBody: (request) => this.parseJsonBody(request),
                    parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                    parseJsonText: (raw) => this.parseJsonText(raw),
                    requireAdmin: (request) => this.context.requireAdmin(request),
                    resolveAuthenticatedPrincipal: (request) => {
                        const token = this.context.extractAuthToken(request);
                        return token ? this.context.describeAuthenticatedPrincipal(token) : null;
                    },
                    knowledgeService: this.context.knowledgeService,
                    knowledgeGraphqlService: this.context.knowledgeGraphqlService,
                }),
            }),
            ...createDaemonMediaRouteHandlers({
                ...buildMediaRouteContext({
                    artifactStore: this.context.artifactStore,
                    configManager: this.context.configManager,
                    mediaProviders: this.context.mediaProviders,
                    multimodalService: this.context.multimodalService,
                    parseJsonBody: (request) => this.parseJsonBody(request),
                    requireAdmin: (request) => this.context.requireAdmin(request),
                    voiceService: this.context.voiceService,
                    webSearchService: this.context.webSearchService,
                }),
            }),
        };
        return dispatchDaemonApiRoutes(req, handlers);
    }
    getHomeAssistantRoutes() {
        if (!this.homeAssistantRoutes) {
            const chatManager = this.context.companionChatManager;
            if (!chatManager) {
                throw new Error('Home Assistant remote chat manager is unavailable.');
            }
            this.homeAssistantRoutes = new HomeAssistantConversationRoutes({
                configManager: this.context.configManager,
                routeBindings: this.context.routeBindings,
                chatManager,
                parseJsonBody: (request) => this.parseJsonBody(request),
                resolveDefaultProviderModel: this.context.resolveDefaultProviderModel,
            });
        }
        return this.homeAssistantRoutes;
    }
    getHomeGraphRoutes() {
        if (!this.homeGraphRoutes) {
            this.homeGraphRoutes = new HomeGraphRoutes({
                artifactStore: this.context.artifactStore,
                homeGraphService: this.context.homeGraphService,
                parseJsonBody: (request) => this.parseJsonBody(request),
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                requireAdmin: (request) => this.context.requireAdmin(request),
            });
        }
        return this.homeGraphRoutes;
    }
    getProjectPlanningRoutes() {
        if (!this.projectPlanningRoutes) {
            this.projectPlanningRoutes = new ProjectPlanningRoutes({
                projectPlanningService: this.context.projectPlanningService,
                parseJsonBody: (request) => this.parseJsonBody(request),
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                requireAdmin: (request) => this.context.requireAdmin(request),
            });
        }
        return this.projectPlanningRoutes;
    }
    async parseJsonBody(req) {
        return parseDaemonJsonBody(req);
    }
    async parseOptionalJsonBody(req) {
        return parseOptionalDaemonJsonBody(req);
    }
    parseJsonText(rawBody) {
        return parseDaemonJsonText(rawBody);
    }
    recordApiResponse(req, path, response, clientKind = 'web') {
        this.context.controlPlaneGateway.recordApiRequest({
            method: req.method,
            path,
            status: response.status,
            clientKind,
            ...(response.status >= 400 ? { error: `${req.method} ${path} -> ${response.status}` } : {}),
        });
        return response;
    }
    async handleLogin(req) {
        const body = await this.parseJsonBody(req);
        if (body instanceof Response)
            return body;
        const username = typeof body.username === 'string' ? body.username : '';
        const password = typeof body.password === 'string' ? body.password : '';
        const user = this.context.userAuth.authenticate(username, password);
        if (!user) {
            return Response.json({ error: 'Invalid credentials' }, { status: 401 });
        }
        const session = this.context.userAuth.createSession(user.username);
        return Response.json({
            authenticated: true,
            token: session.token,
            username: session.username,
            expiresAt: session.expiresAt,
        }, {
            headers: {
                'Set-Cookie': buildOperatorSessionCookie(session.token, {
                    req,
                    expiresAt: session.expiresAt,
                    trustProxy: Boolean(this.context.configManager.get('controlPlane.trustProxy')),
                }),
            },
        });
    }
    async handleGitHubWebhook(req) {
        return handleGitHubAutomationWebhook(req, {
            serviceRegistry: this.context.serviceRegistry,
            githubWebhookSecret: this.context.githubWebhookSecret,
            trySpawnAgent: (input, logLabel, sessionId) => this.context.trySpawnAgent(input, logLabel, sessionId),
        });
    }
    async handleSlackWebhook(req) {
        return handleSlackSurfaceWebhook(req, this.context.buildSurfaceAdapterContext());
    }
    async handleDiscordWebhook(req) {
        return handleDiscordSurfaceWebhook(req, this.context.buildSurfaceAdapterContext());
    }
    async handleNtfyWebhook(req) {
        return handleNtfySurfaceWebhook(req, this.context.buildSurfaceAdapterContext());
    }
    async handleGenericWebhook(req) {
        return handleGenericWebhookSurface(req, this.context.buildGenericWebhookAdapterContext());
    }
}
