import { randomUUID } from 'node:crypto';
import { hostname } from 'node:os';
import { AppLifecycle, installProcessShutdownHandlers, type CloseableResource } from './app/lifecycle.js';
import { createServer, createServices, createWorkerHealthServer } from './app/server.js';
import { createArtifactObjectStorage } from './artifacts/storage.js';
import { HttpCompletionCallbackSender, type CompletionCallbackSender } from './callbacks/service.js';
import {
  loadConfig,
  requireAgentSandboxOrchestratorToken,
  requireAgentSandboxOrchestratorUrl,
  requireDatabaseUrl,
  requireDaytonaApiKey,
  requireDockerOrchestratorUrl,
  requireGitHubAppCredentials,
  requireLambdaMicrovmImageIdentifier,
  requireRunnerModelDefault,
  requireSuperserveApiKey,
  requireSuperserveTemplate,
  requireTensorlakeApiKey,
  requireTensorlakeRegisteredImage,
} from './config/index.js';
import { startEventCompactor } from './events/compaction.js';
import { GitHubArchivedSessionNotifier } from './integrations/github/archived-session-notifier.js';
import { GitHubCompletionCallbackSender } from './integrations/github/callback-sender.js';
import { GitHubClient } from './integrations/github/client.js';
import { GitHubIssueContextFetcher } from './integrations/github/issue-context-fetcher.js';
import { GitHubReactionSender } from './integrations/github/reaction-sender.js';
import { GitHubRepositoryAccessService } from './integrations/github/repository-access.js';
import { SlackClient } from './integrations/slack/client.js';
import { SlackCompletionCallbackSender } from './integrations/slack/callback-sender.js';
import { SlackRunProgressNotifier } from './integrations/slack/progress-notifier.js';
import { FakeRunner } from './runner/fake.js';
import type { Runner } from './runner/types.js';
import { PiRunner, type PiRunnerOptions } from './runner-pi/runner.js';
import { PostgresPiSessionStore } from './runner-pi/session-store.js';
import { sandboxBridgeSkippedCookieNames } from './sandbox/bridge-env.js';
import { DaytonaSandboxProvider } from './sandbox/daytona.js';
import { DockerSandboxProvider, HttpDockerOrchestratorClient, InProcessDockerOrchestrator } from './sandbox/docker.js';
import { FakeSandboxProvider } from './sandbox/fake.js';
import {
  AgentSandboxProvider,
  HttpAgentSandboxOrchestratorClient,
  InProcessAgentSandboxOrchestrator,
} from './sandbox/k8s-agent-sandbox.js';
import { LambdaMicrovmSandboxProvider } from './sandbox/lambda-microvm.js';
import { LocalSandboxProvider } from './sandbox/local.js';
import { startSandboxReaper } from './sandbox/reaper.js';
import { TensorlakeSandboxProvider } from './sandbox/tensorlake.js';
import { SuperserveSandboxProvider } from './sandbox/superserve.js';
import type { SandboxProvider } from './sandbox/types.js';
import { startSessionSearchIndexer } from './search/indexer.js';
import { MemoryStore } from './store/memory.js';
import { PostgresStore } from './store/postgres.js';
import { PostgresIntegrationCredentialRepository } from './integration-credentials/postgres.js';
import { createPostgresCodexCredentialStore } from './runner-pi/postgres-credential-store.js';
import { startTelemetry } from './telemetry/index.js';
import { instrumentStore } from './telemetry/store.js';
import type { WebSearchToolServices } from './web-search/tool.js';
import { startWorkerLoop, WorkerService, type WorkerLoopHandle } from './worker/service.js';
import type { DeputyToolBaseServices } from './sessions/deputy-tool.js';
import type { NotepadToolBaseServices } from './notepads/tool.js';
import type { ScheduledFollowUpToolBaseServices } from './scheduled-follow-ups/tool.js';

const config = loadConfig(process.env);
const telemetry = startTelemetry({ runMode: config.runMode });
const databaseUrl = config.appDataStore === 'postgres' ? requireDatabaseUrl(config) : '';
const baseStore =
  config.appDataStore === 'postgres' ? new PostgresStore(databaseUrl, postgresStoreOptions()) : new MemoryStore();
const store = instrumentStore(baseStore, { kind: config.appDataStore });
const sandboxProvider = createSandboxProvider();
const artifactObjectStorage = config.artifactStorage === 'disabled' ? undefined : createArtifactObjectStorage(config);
const services = createServices(store, {
  sandboxProvider,
  unsafeAllowLocalHttpCallbacks: config.unsafeAllowLocalHttpCallbacks,
  scheduledFollowUpContext: {
    modelConfig: {
      ...(config.runnerModelDefault ? { runnerModelDefault: config.runnerModelDefault } : {}),
      runnerModelChoices: config.runnerModelChoices,
    },
    skillsEnabled: config.skillsEnabled,
    repoSkillsEnabled: config.repoSkillsEnabled,
  },
  scheduledFollowUpExternalResolver: {
    slackBotConfigured: Boolean(config.slackBotToken),
    slackAllowedTeamIds: config.slackAllowedTeamIds,
    slackAllowedChannelIds: config.slackAllowedChannelIds,
    githubAppConfigured: Boolean(config.githubAppId && config.githubAppPrivateKey),
    githubAllowedRepositories: config.githubAllowedRepositories,
    ...(config.githubWebhookTriggerPhrases[0] ? { githubReplyPhrase: config.githubWebhookTriggerPhrases[0] } : {}),
    ...(config.webBaseUrl ? { webBaseUrl: config.webBaseUrl } : {}),
  },
  ...(artifactObjectStorage ? { artifactObjectStorage } : {}),
});
const webSearch = createWebSearchServices();
const repositorySetupScript = {
  enabled: config.repositorySetupScriptEnabled,
  timeoutMs: config.repositorySetupScriptTimeoutMs,
};
const githubClient =
  config.githubAppId || config.githubAppPrivateKey ? new GitHubClient({ apiBaseUrl: config.githubApiBaseUrl }) : null;
const githubRepositoryAccess = githubClient ? createGitHubRepositoryAccess(githubClient) : null;
if (githubClient && githubRepositoryAccess) {
  services.scheduledFollowUps.setGitHubAccessVerifier((repository) =>
    githubRepositoryAccess.getRepositoryAccess(repository),
  );
  services.githubReactionSender = new GitHubReactionSender(githubClient, githubRepositoryAccess);
  services.githubIssueContextFetcher = new GitHubIssueContextFetcher(githubClient, githubRepositoryAccess);
  services.githubArchivedSessionNotifier = new GitHubArchivedSessionNotifier(githubClient, githubRepositoryAccess);
  services.githubRepositoryAccess = githubRepositoryAccess;
}
const resources: CloseableResource[] = [];
let server: ReturnType<typeof createServer> | undefined;
let workerLoop: WorkerLoopHandle | undefined;
let eventCompactor: ReturnType<typeof startEventCompactor> | undefined;
let sessionSearchIndexer: ReturnType<typeof startSessionSearchIndexer> | undefined;
let sandboxReaper: ReturnType<typeof startSandboxReaper> | undefined;
const processInstanceId = `${hostname()}-${process.pid}-${randomUUID()}`;
const automationSchedulerLockOwner = `automation-scheduler-${processInstanceId}`;
const scheduledFollowUpSchedulerLockOwner = `scheduled-follow-up-scheduler-${processInstanceId}`;

if (telemetry) resources.push(telemetry);
if ('close' in baseStore && typeof baseStore.close === 'function') resources.push(baseStore);
if (
  baseStore instanceof PostgresStore &&
  (config.runMode === 'combined' || config.runMode === 'all' || config.runMode === 'api' || config.runMode === 'worker')
) {
  resources.unshift(await baseStore.listenEvents((event) => services.events.publishExternal(event)));
}

if (config.runMode === 'combined' || config.runMode === 'all' || config.runMode === 'api') {
  server = createServer(config, services);
  server.listen(config.port, () => {
    console.log(`background-agent service listening on :${config.port} (${config.runMode})`);
  });
} else {
  server = createWorkerHealthServer(config);
  server.listen(config.port, () => {
    console.log(`background-agent worker health listening on :${config.port} (${config.runMode})`);
  });
}

if (config.runMode === 'combined' || config.runMode === 'all' || config.runMode === 'worker') {
  const runner = await createRunner();
  const callbackSenders = createCallbackSenders();
  const progressNotifiers = createProgressNotifiers();
  const automationSchedulerLoop = startWorkerLoop(
    {
      processNext: () => services.automations.processNextScheduled({ lockOwner: automationSchedulerLockOwner }),
    },
    config.workerPollIntervalMs,
  );
  const scheduledFollowUpSchedulerLoop = startWorkerLoop(
    {
      processNext: () => services.scheduledFollowUps.processNext({ lockOwner: scheduledFollowUpSchedulerLockOwner }),
    },
    config.workerPollIntervalMs,
  );
  const workerLoops = Array.from({ length: config.workerConcurrency }, (_, index) => {
    const worker = new WorkerService({
      store,
      events: services.events,
      artifacts: services.artifacts,
      runner,
      runnerType: config.runner,
      sandboxProvider,
      leaseOwner: `worker-${processInstanceId}-${index + 1}`,
      cancellationPollIntervalMs: config.runCancellationPollIntervalMs,
      titleGenerationEnabled: config.titleGenerationEnabled,
      ...(config.titleGenerationModel ? { titleGenerationModel: config.titleGenerationModel } : {}),
      callbackSenders,
      progressNotifiers,
    });
    return startWorkerLoop(worker, config.workerPollIntervalMs);
  });
  workerLoop = {
    wake(): void {
      automationSchedulerLoop.wake();
      scheduledFollowUpSchedulerLoop.wake();
      for (const loop of workerLoops) loop.wake();
    },
    async stop(): Promise<void> {
      await Promise.all([
        automationSchedulerLoop.stop(),
        scheduledFollowUpSchedulerLoop.stop(),
        ...workerLoops.map((loop) => loop.stop()),
      ]);
    },
  };
  const unsubscribeWorkerWake = services.events.subscribeAllEvents((event) => {
    if (event.type === 'message_created' || event.type === 'callback_retry_scheduled') workerLoop?.wake();
  });
  resources.unshift({ close: unsubscribeWorkerWake });
  if (config.eventDeltaCompactionEnabled) {
    eventCompactor = startEventCompactor({
      store,
      retentionMs: config.eventDeltaCompactionRetentionMs,
      intervalMs: config.eventDeltaCompactionIntervalMs,
      batchSize: config.eventDeltaCompactionBatchSize,
      onError: (error: unknown) => console.error(error instanceof Error ? error.message : error),
    });
  }
  if (services.sandboxCleanup) {
    sandboxReaper = startSandboxReaper({
      cleanup: services.sandboxCleanup,
      store,
      stopDelayMs: config.sandboxStopDelayMs,
      retentionMs: config.sandboxRetentionMs,
      onError: (error: unknown) => console.error(error instanceof Error ? error.message : error),
    });
  }
  console.log(`background-agent worker started (${config.runMode}, concurrency=${config.workerConcurrency})`);
}

sessionSearchIndexer = startSessionSearchIndexer({
  store,
  events: services.events,
  onError: (error: unknown) => console.error(error instanceof Error ? error.message : error),
});

function createCallbackSenders(): CompletionCallbackSender[] {
  const senders: CompletionCallbackSender[] = [
    new HttpCompletionCallbackSender({ unsafeAllowLocalNetwork: config.unsafeAllowLocalHttpCallbacks }),
  ];
  if (config.slackBotToken) {
    senders.push(
      new SlackCompletionCallbackSender(
        new SlackClient({ apiBaseUrl: config.slackApiBaseUrl, botToken: config.slackBotToken }),
      ),
    );
  }
  if (config.githubAppId || config.githubAppPrivateKey) {
    if (!githubClient || !githubRepositoryAccess)
      throw new Error('GitHub callback sender requires GitHub App credentials');
    senders.push(new GitHubCompletionCallbackSender(githubClient, githubRepositoryAccess));
  }
  return senders;
}

function createProgressNotifiers() {
  if (!config.slackBotToken) return [];
  return [
    new SlackRunProgressNotifier(
      new SlackClient({ apiBaseUrl: config.slackApiBaseUrl, botToken: config.slackBotToken }),
    ),
  ];
}

function createRepositoryAccess() {
  if (!config.githubAppId && !config.githubAppPrivateKey) return {};
  if (!githubRepositoryAccess) throw new Error('GitHub repository access requires GitHub App credentials');
  return { github: githubRepositoryAccess };
}

function createGitHubRepositoryAccess(client: GitHubClient): GitHubRepositoryAccessService {
  const credentials = requireGitHubAppCredentials(config);
  return new GitHubRepositoryAccessService({
    ...credentials,
    client,
    cloneBaseUrl: config.githubCloneBaseUrl,
    allowedRepositories: config.githubAllowedRepositories,
  });
}

const lifecycleOptions = {
  resources,
  onError: (error: unknown) => console.error(error instanceof Error ? error.message : error),
};
if (server) Object.assign(lifecycleOptions, { server });
if (workerLoop) Object.assign(lifecycleOptions, { workerLoop });
if (eventCompactor) resources.unshift(eventCompactor);
if (sessionSearchIndexer) resources.unshift(sessionSearchIndexer);
if (sandboxReaper) resources.unshift(sandboxReaper);
installProcessShutdownHandlers(new AppLifecycle(lifecycleOptions));

function createSandboxProvider(): SandboxProvider {
  if (config.sandboxProvider === 'fake') return new FakeSandboxProvider();
  if (config.sandboxProvider === 'unsafe-local') {
    console.warn(
      'WARNING: SANDBOX_PROVIDER=unsafe-local is not a security boundary. Agent commands run on the API/worker host runtime; use only for trusted local development.',
    );
    return new LocalSandboxProvider(
      config.localSandboxAllowedCommands.length ? { allowedCommands: config.localSandboxAllowedCommands } : {},
    );
  }
  if (config.sandboxProvider === 'docker') {
    const orchestrator =
      config.dockerOrchestratorMode === 'http'
        ? new HttpDockerOrchestratorClient(
            optional({ baseUrl: requireDockerOrchestratorUrl(config), token: config.dockerOrchestratorToken }),
          )
        : new InProcessDockerOrchestrator(
            optional({
              image: config.dockerSandboxImage,
              workspacePath: config.sandboxWorkspacePath,
              bridgeHost: config.dockerSandboxBridgeHost,
              network: config.dockerSandboxNetwork,
              memory: config.dockerSandboxMemory,
              cpus: config.dockerSandboxCpus,
              dockerCliTimeoutMs: config.dockerCliTimeoutMs,
              bridgeSkippedCookieNames: sandboxBridgeSkippedCookieNames(config),
            }),
          );
    return new DockerSandboxProvider({ orchestrator });
  }
  if (config.sandboxProvider === 'daytona') {
    const resources = daytonaSandboxResources();
    const options = {
      apiKey: requireDaytonaApiKey(config),
      idleTimeoutMs: config.sandboxIdleTimeoutMs,
    };
    if (config.daytonaApiUrl) Object.assign(options, { apiUrl: config.daytonaApiUrl });
    if (config.daytonaTarget) Object.assign(options, { target: config.daytonaTarget });
    if (config.daytonaImage) Object.assign(options, { image: config.daytonaImage });
    if (config.daytonaSnapshot) Object.assign(options, { snapshot: config.daytonaSnapshot });
    if (resources) Object.assign(options, { resources });
    Object.assign(options, {
      workspacePath: config.sandboxWorkspacePath,
      bridgeSkippedCookieNames: sandboxBridgeSkippedCookieNames(config),
    });
    return new DaytonaSandboxProvider(options);
  }
  if (config.sandboxProvider === 'tensorlake') {
    const options = {
      apiKey: requireTensorlakeApiKey(config),
      image: requireTensorlakeRegisteredImage(config),
      idleTimeoutMs: Math.max(config.sandboxIdleTimeoutMs, config.sandboxKeepaliveMaxExtensionMs),
      workspacePath: config.sandboxWorkspacePath,
      bridgeSkippedCookieNames: sandboxBridgeSkippedCookieNames(config),
    };
    if (config.tensorlakeSandboxCpu !== undefined) Object.assign(options, { cpus: config.tensorlakeSandboxCpu });
    if (config.tensorlakeSandboxMemoryMb !== undefined)
      Object.assign(options, { memoryMb: config.tensorlakeSandboxMemoryMb });
    if (config.tensorlakeSandboxDiskMb !== undefined)
      Object.assign(options, { diskMb: config.tensorlakeSandboxDiskMb });
    if (config.tensorlakeAllowInternetAccess !== undefined)
      Object.assign(options, { allowInternetAccess: config.tensorlakeAllowInternetAccess });
    return new TensorlakeSandboxProvider(options);
  }
  if (config.sandboxProvider === 'superserve') {
    const options = {
      apiKey: requireSuperserveApiKey(config),
      template: requireSuperserveTemplate(config),
      workspacePath: config.sandboxWorkspacePath,
      bridgeSkippedCookieNames: sandboxBridgeSkippedCookieNames(config),
    };
    if (config.superserveBaseUrl) Object.assign(options, { baseUrl: config.superserveBaseUrl });
    return new SuperserveSandboxProvider(options);
  }
  if (config.sandboxProvider === 'lambda-microvm') {
    return new LambdaMicrovmSandboxProvider(
      optional({
        region: config.lambdaMicrovmRegion,
        imageIdentifier: requireLambdaMicrovmImageIdentifier(config),
        imageVersion: config.lambdaMicrovmImageVersion,
        executionRoleArn: config.lambdaMicrovmExecutionRoleArn,
        ingressNetworkConnectors: config.lambdaMicrovmIngressNetworkConnectors,
        egressNetworkConnectors: config.lambdaMicrovmEgressNetworkConnectors,
        idleTimeoutMs: Math.max(config.sandboxIdleTimeoutMs, config.sandboxKeepaliveMaxExtensionMs),
        suspendedDurationMs: config.sandboxRetentionMs,
        maximumDurationSeconds: config.lambdaMicrovmMaximumDurationSeconds,
        authTokenTtlMinutes: config.lambdaMicrovmAuthTokenTtlMinutes,
        workspacePath: config.sandboxWorkspacePath,
        bridgePort: config.lambdaMicrovmBridgePort,
        logGroup: config.lambdaMicrovmLogGroup,
        bridgeSkippedCookieNames: sandboxBridgeSkippedCookieNames(config),
      }),
    );
  }
  if (config.sandboxProvider === 'k8s-agent-sandbox') {
    const orchestrator =
      config.agentSandboxOrchestratorMode === 'http'
        ? new HttpAgentSandboxOrchestratorClient(
            optional({
              baseUrl: requireAgentSandboxOrchestratorUrl(config),
              token: requireAgentSandboxOrchestratorToken(config),
            }),
          )
        : new InProcessAgentSandboxOrchestrator(
            optional({
              namespace: config.agentSandboxNamespace,
              image: config.agentSandboxImage,
              workspacePath: config.sandboxWorkspacePath,
              storageSize: config.agentSandboxStorageSize,
              storageClassName: config.agentSandboxStorageClassName,
              bridgeSkippedCookieNames: sandboxBridgeSkippedCookieNames(config),
            }),
          );
    return new AgentSandboxProvider({ orchestrator });
  }

  return assertUnreachableSandboxProvider(config.sandboxProvider);
}

function assertUnreachableSandboxProvider(provider: never): never {
  throw new Error(`SANDBOX_PROVIDER=${String(provider)} is not wired yet`);
}

function optional<T extends Record<string, unknown>>(input: T): T {
  return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined)) as T;
}

function daytonaSandboxResources(): { cpu?: number; gpu?: number; memory?: number; disk?: number } | undefined {
  const resources: { cpu?: number; gpu?: number; memory?: number; disk?: number } = {};
  if (config.daytonaSandboxCpu !== undefined) resources.cpu = config.daytonaSandboxCpu;
  if (config.daytonaSandboxGpu !== undefined) resources.gpu = config.daytonaSandboxGpu;
  if (config.daytonaSandboxMemoryGiB !== undefined) resources.memory = config.daytonaSandboxMemoryGiB;
  if (config.daytonaSandboxDiskGiB !== undefined) resources.disk = config.daytonaSandboxDiskGiB;
  return Object.keys(resources).length ? resources : undefined;
}

function postgresStoreOptions(): { sandboxSecretEncryptionKey?: string } {
  const options: { sandboxSecretEncryptionKey?: string } = {};
  if (config.sandboxSecretEncryptionKey) options.sandboxSecretEncryptionKey = config.sandboxSecretEncryptionKey;
  return options;
}

async function createRunner(): Promise<Runner> {
  if (config.runner === 'fake') {
    return new FakeRunner(config.fakeRunnerArtifact ? { artifact: config.fakeRunnerArtifact } : {});
  }

  const model = requireRunnerModelDefault(config);
  const deputy = createDeputyToolServices();
  let postgresCredentials;
  if (config.openaiCodexAuth.mode === 'postgres') {
    const repository = new PostgresIntegrationCredentialRepository(databaseUrl, config.openaiCodexAuth.cipher);
    try {
      postgresCredentials = await createPostgresCodexCredentialStore(repository, config.openaiCodexAuth.seedBase64);
      resources.unshift(repository);
    } catch (error) {
      await repository.close().catch(() => undefined);
      throw error;
    }
  }
  const piOptions: PiRunnerOptions = {
    model,
    ...(config.runnerReasoningLevelDefault ? { reasoningLevelDefault: config.runnerReasoningLevelDefault } : {}),
    ...(postgresCredentials ? { credentials: postgresCredentials } : {}),
    ...(config.openaiCodexAuth.mode === 'file' && config.openaiCodexAuth.authFile
      ? { authFile: config.openaiCodexAuth.authFile }
      : {}),
    ...(config.openaiCodexAuth.mode === 'legacy-base64' ? { authBase64: config.openaiCodexAuth.authBase64 } : {}),
    modelUnavailableReason: (inputModel: string | undefined) =>
      services.modelAvailability.unavailableFor(inputModel || model)?.reason,
    setupScript: repositorySetupScript,
    resolveAgentProfile: (id) => services.agentProfiles.resolveRuntimeProfile(id, 'subagent'),
    listAgentProfiles: () => services.agentProfiles.listRuntimeProfiles('subagent'),
    listDeputyProfiles: () => services.agentProfiles.listRuntimeProfiles('agent'),
  };
  if (artifactObjectStorage) {
    piOptions.artifacts = services.artifacts;
    piOptions.artifactToolMaxBytes = config.artifactCreateMaxBytes;
  }
  if (webSearch) piOptions.webSearch = webSearch;
  if (config.mcpServers.length) {
    piOptions.mcp = {
      servers: config.mcpServers,
      connectTimeoutMs: config.mcpConnectTimeoutMs,
      toolTimeoutMs: config.mcpToolTimeoutMs,
      toolResultMaxChars: config.mcpToolResultMaxChars,
      responseMaxBytes: config.mcpResponseMaxBytes,
    };
  }
  if (deputy) piOptions.deputy = deputy;
  piOptions.scheduledFollowUps = createScheduledFollowUpToolServices();
  piOptions.notepad = createNotepadToolServices();
  if (config.skillsEnabled) {
    piOptions.skills = {
      repoScanEnabled: config.repoSkillsEnabled,
      listForRun: async (input) => {
        const skills = await services.skills.listForRun(input);
        return skills.map((skill) => ({
          id: skill.id,
          revisionId: skill.resolvedRevisionId,
          revisionNumber: skill.resolvedRevisionNumber,
          name: skill.name,
          description: skill.description,
          body: skill.body,
          autoLoad: skill.autoLoad,
          source: skill.source,
          createdAt: skill.createdAt,
        }));
      },
    };
  }
  piOptions.repositoryAccess = createRepositoryAccess();
  piOptions.environments = services.environments;
  piOptions.externalResources = services.externalResources;
  if (services.sandboxKeepalive) piOptions.sandboxKeepalive = services.sandboxKeepalive;
  piOptions.sandboxKeepaliveMaxExtensionMs = config.sandboxKeepaliveMaxExtensionMs;
  if (config.runnerStateStore === 'postgres') {
    const sessionStore = new PostgresPiSessionStore(requireDatabaseUrl(config));
    resources.push(sessionStore);
    piOptions.sessionStore = sessionStore;
  }
  return new PiRunner(piOptions);
}

function createDeputyToolServices(): DeputyToolBaseServices | undefined {
  if (!config.deputyToolEnabled) return undefined;
  return {
    store: services.store,
    events: services.events,
    messages: services.messages,
    sessions: services.sessions,
    ...(services.sandboxCleanup ? { sandboxCleanup: services.sandboxCleanup } : {}),
    ...(githubRepositoryAccess ? { github: githubRepositoryAccess } : {}),
    ...(config.webBaseUrl ? { webBaseUrl: config.webBaseUrl } : {}),
    maxSpawnDepth: config.deputyMaxSpawnDepth,
    maxChildrenPerSession: config.deputyMaxChildrenPerSession,
    maxSpawnsPerRun: config.deputyMaxSpawnsPerRun,
    privateSessionsEnabled: config.privateSessionsEnabled,
    agentProfiles: services.agentProfiles,
  };
}

function createNotepadToolServices(): NotepadToolBaseServices {
  return { store: services.store, notepads: services.notepads };
}

function createScheduledFollowUpToolServices(): ScheduledFollowUpToolBaseServices {
  return { store: services.store, scheduledFollowUps: services.scheduledFollowUps };
}

function createWebSearchServices(): WebSearchToolServices | undefined {
  if (config.webSearchProvider === 'disabled') return undefined;

  const services: WebSearchToolServices = {
    provider: config.webSearchProvider,
    maxResults: config.webSearchMaxResults,
    contentMaxChars: config.webSearchContentMaxChars,
    timeoutMs: config.webSearchTimeoutMs,
  };
  if (config.webSearchBraveApiKey) services.braveApiKey = config.webSearchBraveApiKey;
  return services;
}
