Ctrl K
ComunidadGuías…

backend-ts/src/app-routes.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type { FastifyInstance } from 'fastify';
import type { Db } from './db/db.js';
import type { AppConfig } from './config/app-config.js';
import type { JwtService } from './crypto/jwt.service.js';
import type { AuthService } from './auth/auth.service.js';
import type { FeishuAuthService } from './auth/feishu-auth.service.js';
import type { EcpAuthService } from './auth/ecp-auth.service.js';
import type { UserService } from './user/user.service.js';
import type { MysqlUserRepository } from './user/user.repository.js';
import type { PermissionService } from './permission/permission.service.js';
import type { GitCredentialService } from './user/git-credential.service.js';
import type { EnvVariableService } from './env/env-variable.service.js';
import type { SessionRepository } from './session/session.repository.js';
import type { HttpToolService } from './http-tool/http-tool.service.js';
import type { HttpApiExecutor } from './harness/tool/impl/http-api-executor.js';
import type { FileService } from './file/file.service.js';
import type { AgentService } from './agent/agent.service.js';
import type { AgentExperienceService } from './agent/agent-experience.service.js';
import type { AgentSuggestedQuestionService } from './agent/agent-suggested-question.service.js';
import type { McpServerValidatorImpl } from './agent/mcp-validator.js';
import type { HttpToolValidatorImpl } from './http-tool/http-tool-validator.js';
import type { ModelService } from './model/model.service.js';
import type { SystemSettingService } from './settings/settings.service.js';
import type { UserCommandService } from './command/command.service.js';
import type { SkillLoader } from './harness/skill/skill-loader.js';
import type { SkillSyncService } from './harness/skill/skill-sync-service.js';
import type { UserWeixinPreferenceService } from './preference/weixin-preference.service.js';
import type { UserTaskPanelPreferenceService } from './preference/task-panel-preference.service.js';
import type { ToolService } from './tool/tool.service.js';
import type { AuditLogService } from './audit/audit.service.js';
import type { LlmCallService } from './usage/llm-call.service.js';
import type { SessionService } from './session/session.service.js';
import type { ActivityService } from './session/activity.service.js';
import type { MessageQueueService } from './session/message-queue.service.js';
import type { MysqlAgentRepository } from './agent/agent.repository.js';
import type { MysqlLlmModelRepository } from './model/model.repository.js';
import type { SessionTodoRepository } from './session/activity.repository.js';
import type { PathSandbox } from './harness/safety/path-sandbox.js';
import type { SubagentExecutionRepository } from './session/activity.repository.js';
import type { SessionCompactionEventService } from './session/session-compaction-event.service.js';
import type { SessionCompactionService } from './session/session-compaction.service.js';
import type { RunTraceService } from './session/run-trace.service.js';
import type { RunTraceRecordService } from './session/run-trace-record.service.js';
import type { ApprovalRegistry } from './harness/approval/approval-registry.js';
import type { AskUserQuestionsRegistry } from './harness/tool/ask-user-questions-registry.js';
import type { InFlightToolRegistry } from './harness/core/in-flight-tool-registry.js';
import type { SessionTreeSignalPublisher } from './harness/approval/session-tree-signal-publisher.js';
import type { WorkspaceBrowseService } from './file/workspace-browse.service.js';
import type { TerminalManager } from './harness/terminal/terminal-manager.js';
import type { WorkspaceGitService } from './file/workspace-git.service.js';
import type { GitCommitMessageService } from './file/git-commit-message.service.js';
import type { GitWriteOperationService } from './file/git-write-operation.service.js';
import type { RuntimeDataResolver } from './harness/runtime/runtime-data-resolver.js';
import type { OssStsService } from './oss/oss-sts.service.js';
import type { SkillBundleService } from './skill/skill-bundle.service.js';
import type { UserSkillService } from './skill/user-skill.service.js';
import type { SkillDocService } from './skill/skill-doc.service.js';
import type { ScheduledTaskService } from './schedule/scheduled-task.service.js';
import type { OpenRunService } from './openapi/open-run.service.js';
import type { ApiTokenService } from './openapi/api-token.service.js';
import type { WebhookTriggerService } from './openapi/webhook-trigger.service.js';
import type { OutboundSubscriptionService } from './openapi/outbound-subscription.service.js';
import type { FixedWindowRateLimiter } from './openapi/rate-limiter.js';
import type { OpenApiCallLogService } from './openapi/open-api-call-log.service.js';
import type { WatcherService } from './signal/watcher.service.js';
import type { MysqlSignalWatcherRepository } from './signal/signal.repository.js';
import type { AnalyticsService } from './analytics/analytics.service.js';
import type { StatisticsService } from './statistics/statistics.service.js';
import type { FeedbackService } from './feedback/feedback.service.js';
import type { MemoryService } from './memory/memory.service.js';
import type { ApprovalRuleService } from './approval-rule/approval-rule.service.js';
import type { InboxService } from './inbox/inbox.service.js';
import type { DigestService } from './digest/service.js';
import type { BudgetService } from './budget/budget.service.js';
import type { AdminAnalyticsService } from './admin/admin-analytics.service.js';
import type { AdminAnalyticsDbStore } from './admin/admin-analytics.service.js';
import type { MysqlUserCommandRepository } from './command/command.repository.js';
import type { McpServerService } from './harness/mcp/service/mcp-server.service.js';
import type { McpClientManager } from './harness/mcp/mcp-client-manager.js';
import type { UserMcpPreferenceService } from './harness/mcp/preference/service/user-mcp-preference.service.js';
import type { UserMcpLoadModeService } from './harness/mcp/preference/service/user-mcp-load-mode.service.js';
import type { McpLoadModeResolver } from './harness/mcp/workingset/mcp-load-mode.js';
import type { QrLoginService } from './weixin/qr-login.service.js';
import type { WeixinAccountRepository } from './weixin/account.repository.js';
import type { WeixinMonitorService } from './weixin/monitor.service.js';
import type { MysqlFeishuBotRepository } from './feishu/feishu_bot.repository.js';
import type { MysqlFeishuBindingRepository } from './feishu/binding.repository.js';
import type { FeishuMonitorService } from './feishu/monitor.service.js';
import type { TaskNotificationPreferenceService } from './notification/task/preference.service.js';
import type { StreamingWsHandler } from './session/ws/streaming-ws-handler.js';
import type { TerminalWsHandler } from './harness/terminal/terminal-ws-handler.js';
import type { RuntimeModeResolver } from './harness/container/runtime-mode.js';
import { validateRuntimeModeSave, RUNTIME_MODE_SETTING_KEY } from './harness/container/runtime-mode.js';
import type { ContainerHostsResolver } from './harness/container/container-hosts-resolver.js';
import type { ContainerAutoUpgradeResolver } from './harness/container/container-auto-upgrade.js';
import { CONTAINER_AUTO_UPGRADE_SETTING_KEY } from './common/container-auto-upgrade.js';
import type { ContainerOps } from './harness/container/container-registry.js';
import { registerAdminContainerRoutes } from './harness/container/container-admin.routes.js';
import { CONTAINER_EXTRA_HOSTS_KEY, CONTAINER_INHERIT_HOST_HOSTS_KEY } from './common/container-hosts.js';

import { sendOk } from './common/http-error.js';
import { registerAuthRoutes } from './auth/auth.routes.js';
import { registerEcpAuthRoutes } from './auth/ecp.routes.js';
import { registerCompanySsoRoutes } from './auth/company-sso.routes.js';
import { CompanySsoService } from './auth/company-sso.service.js';
import { CompanySsoClient } from './auth/company-sso.client.js';
import { CompanySsoIdentityRepository } from './auth/company-sso-identity.repository.js';
import { CompanySsoError } from './auth/company-sso.error.js';
import { registerUserRoutes } from './user/user.routes.js';
import { registerPermissionRoutes } from './permission/permission.routes.js';
import { registerAdminGitCredentialRoutes, registerGitCredentialRoutes } from './user/git-credential.routes.js';
import { registerAdminEnvVariableRoutes, registerEnvVariableRoutes } from './env/env-variable.routes.js';

import { registerAdminHttpToolRoutes } from './http-tool/http-tool-admin.routes.js';
import { registerAgentAvatarRoutes } from './agent/agent-avatar.js';
import { registerAgentRoutes } from './agent/agent.routes.js';
import { registerAgentExperienceLibraryRoutes } from './agent/agent-experience-library.routes.js';
import type { ExperienceIndexService } from './harness/experience/experience-index.service.js';
import { registerModelRoutes } from './model/model.routes.js';
import { registerSystemSettingRoutes } from './settings/settings.routes.js';
import { registerEmbedRoutes } from './embed/embed.routes.js';
import { registerCommandRoutes } from './command/command.routes.js';
import { registerPreferenceRoutes } from './preference/preference.routes.js';
import type { UserOnboardingPreferenceRepository } from './preference/types.js';
import { ReadinessService } from './settings/readiness.service.js';
import { registerReadinessRoutes } from './settings/readiness.routes.js';
import { registerToolRoutes } from './tool/tool.routes.js';
import { registerAuditLogRoutes } from './audit/audit.routes.js';
import { registerLlmCallRoutes } from './usage/llm-call.routes.js';
import { registerLlmUsageAdminRoutes } from './usage/llm-usage-admin.routes.js';
import { registerUploadRoutes } from './config/upload.routes.js';
import { registerSessionRoutes } from './session/session.routes.js';
import { registerSessionShareRoutes } from './session/session-share.routes.js';
import { MysqlSessionShareRepository } from './session/session-share.repository.js';
import { SessionShareService } from './session/session-share.service.js';
import { SessionExportService } from './session/session-export.service.js';
import { registerAdminSessionRoutes } from './session/admin-session.routes.js';
import type { FailedSessionRecoveryService } from './session/failed-session-recovery.service.js';
import { registerAdminSessionFilesRoutes } from './session/admin-session-files.routes.js';
import { registerTerminalRoutes } from './session/terminal.routes.js';
import { WorkspaceWriteService } from './file/workspace-write.service.js';
import { WorkspaceOverviewService } from './file/workspace-overview.service.js';
import { registerFileRoutes } from './file/file.routes.js';
import { registerOssRoutes } from './oss/oss.routes.js';
import { registerSkillBundleRoutes } from './skill/skill-bundle.routes.js';
import { registerSkillRoutes } from './skill/skill.routes.js';
import { registerScheduledTaskRoutes } from './schedule/scheduled-task.routes.js';
import { registerOpenApiRoutes } from './openapi/open.routes.js';
import { registerOpenApiCallLogRoutes } from './openapi/open-api-call-log.routes.js';
import { registerSignalWatcherRoutes } from './signal/watcher.routes.js';
import { registerSignalAdminRoutes } from './signal/signal-admin.routes.js';
import { registerAnalyticsRoutes } from './analytics/analytics.routes.js';
import { registerStatisticsRoutes } from './statistics/statistics.routes.js';
import { registerFeedbackRoutes } from './feedback/feedback.routes.js';
import { registerMemoryRoutes } from './memory/memory.routes.js';
import { registerApprovalRuleRoutes, type ApprovalRuleAuditInput } from './approval-rule/approval-rule.routes.js';
import { registerApprovalRuleAdminRoutes } from './approval-rule/approval-rule.admin.routes.js';
import { registerInboxRoutes } from './inbox/inbox.routes.js';
import { registerDigestRoutes } from './digest/routes.js';
import { registerBudgetRoutes } from './budget/budget.routes.js';
import { registerAdminAnalyticsRoutes, registerAdminRuntimeRoutes } from './admin/admin.routes.js';
import { registerAdminSystemCommandRoutes } from './command/admin-system-command.routes.js';
import { registerMcpServerRoutes } from './harness/mcp/controller/mcp-server.routes.js';
import { registerWeixinBotRoutes } from './weixin/weixin.routes.js';
import { registerFeishuBotRoutes } from './feishu/admin.routes.js';
import { registerFeishuBindingRoutes } from './feishu/binding.routes.js';
import { registerTaskNotificationPreferenceRoutes } from './notification/task/preference.routes.js';
import { attachWebSocket } from './session/ws/attach-websocket.js';

export interface RouteDeps {
  app: FastifyInstance;
  db: Db;
  cfg: AppConfig;
  apiPrefix: string;
  jwt: JwtService;
  agentRuntimeCfg: { wsIdleTimeoutMs: number };

  // auth
  authService: AuthService;
  feishu: FeishuAuthService;
  ecpAuth: EcpAuthService;
  userService: UserService;
  userRepo: MysqlUserRepository;
  permissionService: PermissionService;
  gitCredentials: GitCredentialService;
  envVariableService: EnvVariableService;
  sessionRepo: SessionRepository;

  // http-tool
  httpToolService: HttpToolService;
  httpApiExecutor: HttpApiExecutor;
  httpToolStatsStore: AdminAnalyticsDbStore;

  // agent
  fileService: FileService;
  agentService: AgentService;
  experienceService: AgentExperienceService;
  experienceIndexService: ExperienceIndexService;
  suggestedQuestionService: AgentSuggestedQuestionService;
  mcpValidator: McpServerValidatorImpl;
  httpToolValidator: HttpToolValidatorImpl;

  // model
  modelService: ModelService;

  // settings
  settingService: SystemSettingService;
  activateContainerRuntime: () => Promise<void>;
  runtimeModeResolver: RuntimeModeResolver;
  /** 容器 /etc/hosts 注入配置解析器(后台预览 + 保存后缓存失效);未注入时预览接口不注册。 */
  containerHostsResolver?: ContainerHostsResolver;
  /** 容器自动升级总开关解析器(V178):保存后失效缓存,后台切换即时生效。 */
  containerAutoUpgradeResolver?: ContainerAutoUpgradeResolver;
  /** 容器运维(列表 / 单用户重建);未注入时不注册容器管理接口。 */
  containerOps?: ContainerOps;

  // command
  commandService: UserCommandService;
  commandRepo: MysqlUserCommandRepository;
  skillLoader: SkillLoader;
  skillSync: SkillSyncService;

  // preference
  weixinPref: UserWeixinPreferenceService;
  taskPanelPref: UserTaskPanelPreferenceService;
  onboardingPrefRepo: UserOnboardingPreferenceRepository;

  // tool
  restToolService: ToolService;

  // audit
  auditService: AuditLogService;

  // llm-call
  llmCallService: LlmCallService;

  // session
  sessionService: SessionService;
  activityService: ActivityService;
  messageQueueService: MessageQueueService;
  agentRepo: MysqlAgentRepository;
  modelRepo: MysqlLlmModelRepository;
  todoRepo: SessionTodoRepository;
  pathSandbox: PathSandbox;
  subagentExecutionRepo: SubagentExecutionRepository;
  sessionCompactionEventService: SessionCompactionEventService;
  sessionCompactionService: SessionCompactionService;
  runTraceService: RunTraceService;
  runTraceRecordService: RunTraceRecordService;
  approvalRegistry: ApprovalRegistry;
  askUserQuestionsRegistry: AskUserQuestionsRegistry;
  /** 正在执行的工具调用(内存态),管理后台会话详情补进行中卡片 */
  inFlightToolRegistry: InFlightToolRegistry;
  /** 失败会话批量恢复:可选,未注入时管理端入口返回空清单/报错。 */
  failedSessionRecovery?: FailedSessionRecoveryService;
  /** LOCAL 设备在线判定(技术方案 2026-11-05 §4.1-7):会话 VO 的 targetDeviceOnline 快照用。 */
  hasDeviceConnection?: (userId: number, deviceId: string) => boolean;
  /** 当前执行连接的 clientType(electron / cli);会话 VO 的 executorClientType 快照用。 */
  getExecutorClientType?: (userId: number, deviceId: string) => string | null;
  treeSignalPublisher: SessionTreeSignalPublisher;
  workspaceBrowse: WorkspaceBrowseService;
  terminalManager: TerminalManager;
  workspaceGit: WorkspaceGitService;
  gitCommitMsg: GitCommitMessageService;
  gitWrite: GitWriteOperationService;
  runtimeResolver: RuntimeDataResolver;

  // file
  ossSts: OssStsService;

  // skill
  skillBundleService: SkillBundleService;
  userSkillService: UserSkillService;
  skillDocService: SkillDocService;

  // schedule
  scheduledService: ScheduledTaskService;

  // openapi
  openRunService: OpenRunService;
  apiTokenService: ApiTokenService;
  webhookTriggerService: WebhookTriggerService;
  outboundSubscriptionService: OutboundSubscriptionService;
  openApiRateLimiter: FixedWindowRateLimiter;
  callLogService: OpenApiCallLogService;

  // signal
  watcherService: WatcherService;
  signalWatcherRepo: MysqlSignalWatcherRepository;

  // analytics
  analyticsService: AnalyticsService;
  statisticsService: StatisticsService;
  adminAnalytics: AdminAnalyticsService;

  // feedback
  feedbackService: FeedbackService;

  // memory
  memoryService: MemoryService;

  // approval-rule
  approvalRuleService: ApprovalRuleService;

  // inbox
  inboxService: InboxService;

  // digest
  digestService: DigestService;

  // budget
  budgetService: BudgetService;

  // mcp
  mcpServerService: McpServerService;
  mcpClient: McpClientManager;
  mcpPref: UserMcpPreferenceService;
  userMcpLoadModeService: UserMcpLoadModeService;
  mcpLoadModeResolver: McpLoadModeResolver;

  // weixin
  qrLogin: QrLoginService;
  weixinAccounts: WeixinAccountRepository;
  weixinMonitor: WeixinMonitorService;

  // feishu
  feishuBots: MysqlFeishuBotRepository;
  feishuBinding: MysqlFeishuBindingRepository;
  feishuMonitor: FeishuMonitorService;

  // dingtalk
  dingtalk: { registerRoutes(api: FastifyInstance): void };

  // notification
  notifPref: TaskNotificationPreferenceService;

  // ws
  wsHandler: StreamingWsHandler;
  terminalWsHandler: TerminalWsHandler;
}

export async function registerAllRoutes(deps: RouteDeps): Promise<void> {
  const {
    app, db, cfg, apiPrefix, jwt, agentRuntimeCfg,
    authService, feishu, ecpAuth, userService, userRepo, permissionService,
    gitCredentials, envVariableService, sessionRepo,
    httpToolService, httpApiExecutor, httpToolStatsStore,
    httpToolValidator,
    fileService, agentService, experienceService, experienceIndexService, suggestedQuestionService,
    mcpValidator,
    modelService,
    settingService, activateContainerRuntime, runtimeModeResolver, containerHostsResolver, containerAutoUpgradeResolver, containerOps,
    commandService, commandRepo, skillLoader, skillSync,
    weixinPref, taskPanelPref,
    restToolService,
    auditService,
    llmCallService,
    sessionService, activityService, messageQueueService,
    agentRepo, modelRepo, todoRepo, pathSandbox, subagentExecutionRepo,
    sessionCompactionEventService, sessionCompactionService, runTraceService, runTraceRecordService,
    approvalRegistry, askUserQuestionsRegistry, inFlightToolRegistry, treeSignalPublisher,
    workspaceBrowse, terminalManager, workspaceGit, gitCommitMsg, gitWrite, runtimeResolver,
    ossSts,
    skillBundleService, userSkillService, skillDocService,
    scheduledService,
    openRunService, apiTokenService, webhookTriggerService, outboundSubscriptionService,
    openApiRateLimiter, callLogService,
    watcherService, signalWatcherRepo,
    analyticsService, statisticsService, adminAnalytics,
    feedbackService,
    memoryService,
    approvalRuleService,
    inboxService,
    digestService,
    budgetService,
    mcpServerService, mcpClient, mcpPref, userMcpLoadModeService, mcpLoadModeResolver,
    qrLogin, weixinAccounts, weixinMonitor,
    feishuBots, feishuBinding, feishuMonitor,
    dingtalk,
    notifPref,
    wsHandler, terminalWsHandler,
  } = deps;

  await app.register(async (api) => {
    api.get('/swagger-ui.html', async (_req, reply) => reply.redirect(`${apiPrefix}/swagger-ui`));
    api.get('/v3/api-docs', async (_req, reply) => {
      return reply.send(app.swagger());
    });
    api.get('/v1/health', async (_req, reply) => sendOk(reply, { status: 'UP' }));
    registerAuthRoutes(api, authService, feishu, ecpAuth);
    registerEcpAuthRoutes(api, ecpAuth);
    registerCompanySsoRoutes(api, new CompanySsoService(
      new CompanySsoClient(), new CompanySsoIdentityRepository(db), jwt,
    ), settingService, async (event) => {
      await auditService.record({
        action: event.action === 'created' ? 'CREATE' : event.action === 'bound' ? 'UPDATE' : 'LOGIN',
        objectType: 'sso.identity',
        objectId: event.userId == null ? null : String(event.userId),
        userId: event.userId,
        method: 'POST',
        path: '/v1/auth/sso/exchange',
        ip: event.ip,
        status: event.outcome === 'success' ? 200 : new CompanySsoError(event.outcome).status,
        success: event.outcome === 'success' ? 1 : 0,
        errorMessage: event.outcome === 'success' ? null : event.outcome,
        queryString: JSON.stringify({ requestId: event.requestId, provider: event.provider, durationMs: event.durationMs }),
      });
    });
    registerUserRoutes(api, userService, userRepo, permissionService, containerOps);
    if (containerOps) {
      registerAdminContainerRoutes(api, {
        containerOps,
        permissionService,
        userLookup: { findByIds: (ids) => userRepo.findByIds(ids) },
        agentLookup: { findByIds: (ids) => agentRepo.findByIds(ids) },
      });
    }
    registerPermissionRoutes(api, permissionService);
    registerGitCredentialRoutes(api, gitCredentials);
    registerAdminGitCredentialRoutes(api, { gitCredentialService: gitCredentials, permissionService });
    registerEnvVariableRoutes(api, { envVariableService, sessionLookup: sessionRepo });
    registerAdminEnvVariableRoutes(api, {
      envVariableService,
      permissionService,
      userLookup: { findById: (id: number) => userRepo.findById(id) },
    });
    registerAdminHttpToolRoutes(api, {
      service: httpToolService,
      stats: httpToolStatsStore,
      httpApiExecutor,
      permissionService,
    });
    registerAgentAvatarRoutes(api, fileService, permissionService);
    registerAgentRoutes(api, {
      agentService, experienceService, suggestedQuestionService, userRepo, mcpServerValidator: mcpValidator,
      httpToolValidator,
      permissionService,
    });
    registerAgentExperienceLibraryRoutes(api, {
      agentService, experienceIndexService, permissionService,
    });
    registerModelRoutes(api, { modelService, permissionService });
    registerSystemSettingRoutes(api, {
      systemSettingService: settingService,
      permissionService,
      adminEmailLookup: { findEmailById: (userId) => userRepo.findEmailById(userId) },
      containerHostsPreview: containerHostsResolver
        ? () => containerHostsResolver.preview()
        : undefined,
      beforeSave: async (items) => {
        await validateRuntimeModeSave(items, activateContainerRuntime);
      },
      afterSave: (keys) => {
        if (keys.includes(RUNTIME_MODE_SETTING_KEY)) runtimeModeResolver.invalidate();
        if (containerHostsResolver
          && keys.some((key) => key === CONTAINER_INHERIT_HOST_HOSTS_KEY || key === CONTAINER_EXTRA_HOSTS_KEY)) {
          containerHostsResolver.invalidate();
        }
        // 容器自动升级总开关(V178):保存后立即生效,无需等 5s TTL
        if (containerAutoUpgradeResolver && keys.includes(CONTAINER_AUTO_UPGRADE_SETTING_KEY)) {
          containerAutoUpgradeResolver.invalidate();
        }
      },
    });
    registerEmbedRoutes(api, { systemSettingService: settingService, agentService });
    registerCommandRoutes(api, {
      userCommandService: commandService,
      agentService,
      skillLoader,
      skillSyncService: skillSync as never,
    });
    registerPreferenceRoutes(api, {
      weixinPreferenceService: weixinPref,
      taskPanelPreferenceService: taskPanelPref,
      weixinVoiceReplyDefault: cfg.weixin.bot.voiceReply,
    });
    const readinessService = new ReadinessService({
      userLookup: { findById: (id) => userRepo.findById(id) },
      modelsConfiguredCount: () => modelRepo.countEnabledWithApiKey(),
      agentsEnabledCount: async () => (await agentRepo.selectList(null, false)).length,
      sessionHistory: sessionRepo,
      onboardingPreference: deps.onboardingPrefRepo,
    });
    registerReadinessRoutes(api, { readinessService, permissionService });
    registerToolRoutes(api, { toolService: restToolService });
    registerAuditLogRoutes(api, { auditLogService: auditService, permissionService });
    registerLlmCallRoutes(api, { llmCallService, permissionService });
    registerLlmUsageAdminRoutes(api, {
      llmCallService, permissionService,
      userLookup: { findById: (id: number) => userRepo.findById(id) },
    });
    registerUploadRoutes(api, () => settingService.getUploadConfig());
    registerSessionRoutes(api, {
      sessionService, activityService, messageQueueService,
      agentLookup: {
        findById: (id: number) => agentRepo.findById(id),
        findByIds: (ids: number[]) => agentRepo.findByIds(ids),
        requireDefaultAgent: () => agentService.requireDefaultAgent(),
        listOptions: async () => (await agentRepo.selectList(null, true)).map((a) => ({ id: a.id!, name: a.name })),
      } as never,
      modelLookup: {
        findById: (id: number) => modelRepo.findById(id),
        findByIds: (ids: number[]) => modelRepo.findByIds(ids),
        findDefault: () => modelRepo.findDefault(),
      } as never,
      todoRepo,
      pathSandbox,
      subagentExecutionRepo,
      sessionCompactionEventService,
      sessionCompactionService,
      runTraceService,
      runTraceRecordService,
      approvalRegistry,
      askUserQuestionsRegistry,
      treeSignalPublisher,
      existsSessionUnderWorkspace: (userId: number, path: string) => sessionRepo.existsSessionUnderWorkspace(userId, path),
      hasDeviceConnection: deps.hasDeviceConnection,
      getExecutorClientType: deps.getExecutorClientType,
    });
    const sessionShareService = new SessionShareService(
      new MysqlSessionShareRepository(db),
      sessionService,
      userRepo,
      {
        findById: (id: number) => agentRepo.findById(id),
        findByIds: (ids: number[]) => agentRepo.findByIds(ids),
        requireDefaultAgent: () => agentService.requireDefaultAgent(),
        listOptions: async () => (await agentRepo.selectList(null, true)).map((a) => ({ id: a.id!, name: a.name })),
      } as never,
      auditService,
      () => settingService.shareTokenLinksEnabled(),
    );
    registerSessionShareRoutes(api, {
      sessionService,
      shareService: sessionShareService,
      exportService: new SessionExportService(sessionService, {
        findById: (id: number) => agentRepo.findById(id),
        findByIds: (ids: number[]) => agentRepo.findByIds(ids),
        requireDefaultAgent: () => agentService.requireDefaultAgent(),
        listOptions: async () => (await agentRepo.selectList(null, true)).map((a) => ({ id: a.id!, name: a.name })),
      } as never),
      tokenLinksEnabled: () => settingService.shareTokenLinksEnabled(),
    });
    registerAdminSessionRoutes(api, {
      sessionService,
      userLookup: {
        findByIds: (ids: number[]) => userRepo.findByIds(ids),
        listOptions: () => userRepo.listOptions(),
      } as never,
      agentLookup: {
        findById: (id: number) => agentRepo.findById(id),
        findByIds: (ids: number[]) => agentRepo.findByIds(ids),
        requireDefaultAgent: () => agentService.requireDefaultAgent(),
        listOptions: async () => (await agentRepo.selectList(null, true)).map((a) => ({ id: a.id!, name: a.name })),
      } as never,
      modelLookup: {
        findById: (id: number) => modelRepo.findById(id),
        findByIds: (ids: number[]) => modelRepo.findByIds(ids),
        findDefault: () => modelRepo.findDefault(),
      } as never,
      permissionService,
      askUserQuestionsRegistry,
      inFlightToolRegistry,
      failedSessionRecovery: deps.failedSessionRecovery,
    });
    const experienceDirForAgent = (agentId: number) => experienceIndexService.dirFor(agentId);
    registerAdminSessionFilesRoutes(api, {
      sessionService,
      workspaceBrowseService: workspaceBrowse,
      permissionService,
      experienceDirForAgent,
    });
    registerTerminalRoutes(api, {
      terminalManager,
      sessionService,
      permissionService,
      userLookup: { findById: (id: number) => userRepo.findById(id) as Promise<{ username: string } | null> },
    });
    const workspaceWrite = new WorkspaceWriteService(pathSandbox, activityService);
    const workspaceOverviewService = new WorkspaceOverviewService(pathSandbox, sessionRepo);
    registerFileRoutes(api, {
      fileService, sessionService, workspaceBrowseService: workspaceBrowse,
      workspaceGitService: workspaceGit, gitCommitMessageService: gitCommitMsg,
      gitWriteOperationService: gitWrite, workspaceWriteService: workspaceWrite,
      workspaceOverviewService, pathSandbox,
      experienceDirForAgent,
      getUploadBaseUrl: async () => (await settingService.getUploadConfig()).baseUrl,
      runtimeDataResolver: runtimeResolver,
    });
    registerOssRoutes(api, { ossStsService: ossSts });
    registerSkillBundleRoutes(api, { skillBundleService, permissionService });
    registerSkillRoutes(api, {
      userSkillService, skillDocService, skillSyncService: skillSync,
      sessionService, agentService,
      agentLookup: {
        findById: (id: number) => agentRepo.findById(id),
        findByIds: (ids: number[]) => agentRepo.findByIds(ids),
        requireDefaultAgent: () => agentService.requireDefaultAgent(),
        listOptions: async () => (await agentRepo.selectList(null, true)).map((a) => ({ id: a.id!, name: a.name })),
      } as never,
      permissionService,
      userLookup: {
        findByIds: async (ids: number[]) => (await userRepo.findByIds(ids)).map((u) => ({
          id: u.id!,
          username: u.username,
          displayName: u.displayName,
        })),
        listOptions: async () => (await userRepo.listOptions()).map((u) => ({
          id: u.id!,
          username: u.username,
          displayName: u.displayName,
        })),
      },
    });
    registerScheduledTaskRoutes(api, { service: scheduledService, jwt, permission: permissionService });
    registerOpenApiRoutes(api, {
      apiPrefix,
      openRun: openRunService,
      apiTokenService,
      triggerService: webhookTriggerService,
      subscriptionService: outboundSubscriptionService,
      rateLimiter: openApiRateLimiter,
      callLog: callLogService,
    });
    registerOpenApiCallLogRoutes(api, { callLog: callLogService, permissionService });
    registerSignalWatcherRoutes(api, { service: watcherService });
    registerSignalAdminRoutes(api, {
      watcherRepo: signalWatcherRepo,
      service: watcherService,
      permissionService,
      userLookup: {
        findByIds: async (ids: number[]) => (await userRepo.findByIds(ids)).map((u) => ({
          id: u.id,
          username: u.username,
          displayName: u.displayName,
        })),
      },
    });
    registerAnalyticsRoutes(api, { analytics: analyticsService, jwt, permissionService });
    registerStatisticsRoutes(api, { statistics: statisticsService, jwt, permissionService });
    registerFeedbackRoutes(api, { feedback: feedbackService, jwt, permissionService });
    registerMemoryRoutes(api, { memoryService });
    registerApprovalRuleRoutes(api, {
      approvalRuleService,
      audit: (input: ApprovalRuleAuditInput) => {
        void auditService.record({
          action: input.action,
          objectType: 'approval.rule',
          objectId: String(input.objectId),
          method: input.request.method,
          path: input.request.url,
          userId: (input.request as { userId?: number }).userId ?? null,
          username: null,
          ip: input.request.ip ?? null,
          status: 200,
          success: 1,
          errorMessage: null,
          queryString: input.detail,
        }).catch((e) => console.error('Failed to record approval rule audit log', e));
      },
    });
    registerApprovalRuleAdminRoutes(api, { approvalRuleService, permissionService });
    registerInboxRoutes(api, { inboxService });
    registerDigestRoutes(api, { service: digestService });
    registerBudgetRoutes(api, { budgetService, permissionService });
    const adminDeps = {
      jwt, analytics: adminAnalytics,
      sessionLister: sessionService as never,
      permissionService,
      memoryAuditor: memoryService,
      memoryUserLookup: {
        findByIds: async (ids: number[]) => (await userRepo.findByIds(ids)).map((u) => ({
          id: u.id!,
          username: u.username,
          displayName: u.displayName,
        })),
      },
    };
    registerAdminAnalyticsRoutes(api, adminDeps);
    registerAdminRuntimeRoutes(api, adminDeps);
    registerAdminSystemCommandRoutes(api, {
      commandRepo,
      permissionService,
      userLookup: {
        findByIds: async (ids: number[]) => (await userRepo.findByIds(ids)).map((u) => ({
          id: u.id!,
          username: u.username,
          displayName: u.displayName,
        })),
      },
    });
    registerMcpServerRoutes(api, {
      mcpServerService, mcpClientManager: mcpClient, userMcpPreferenceService: mcpPref,
      userMcpLoadModeService, mcpLoadModeResolver, permissionService,
    });
    registerWeixinBotRoutes(api, { jwt, qrLogin, accountRepository: weixinAccounts, monitorService: weixinMonitor });
    registerFeishuBotRoutes(api, {
      repository: feishuBots,
      secretKey: cfg.feishu.bot.appSecretKey,
      permissionService,
      monitorStatus: { getStatus: (botId: number) => feishuMonitor.getStatus(botId) },
      monitorReconnect: { reconnect: (botId: number) => feishuMonitor.reconnect(botId) },
    });
    registerFeishuBindingRoutes(api, { jwt, repository: feishuBinding, auth: feishu });
    dingtalk.registerRoutes(api);
    registerTaskNotificationPreferenceRoutes(api, { preference: notifPref, jwt });
    await attachWebSocket(api, {
      handler: wsHandler,
      idleTimeoutMs: agentRuntimeCfg.wsIdleTimeoutMs,
      terminalHandler: terminalWsHandler,
    });
  }, { prefix: apiPrefix });
}