app.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. // 服务容器:组装数据库、各个 service 与引擎
  2. import fs from 'node:fs'
  3. import path from 'node:path'
  4. import type { ProviderId } from '@agent-server/shared'
  5. import { communitySkillsDir, config, customSkillsDir, dbPath, teamCasesDir } from './config'
  6. import { createDb, migrateDb, type DB } from './db'
  7. import { ClaudeProvider } from './providers/claude/adapter'
  8. import { CodexProvider } from './providers/codex/adapter'
  9. import type { AgentProvider } from './providers/types'
  10. import { AdminAuthService } from './services/admin-auth'
  11. import { ApiKeyService, AuditService, KnowledgeService, ProfileService } from './services/catalog'
  12. import { EventStore } from './services/event-store'
  13. import { FileService } from './services/files'
  14. import { InteractionService } from './services/interactions'
  15. import { PluginService } from './services/plugins'
  16. import { ClaudeLoginManager } from './services/claude-login'
  17. import { CodexSkillService } from './services/codex-skills'
  18. import { CommunitySkillService } from './services/community-skills'
  19. import { CodexPluginService } from './services/codex-plugins'
  20. import { RunTokenRegistry } from './mcp-gateway/tokens'
  21. import { RunManager } from './services/runs'
  22. import { SessionService } from './services/sessions'
  23. import { SettingsService } from './services/settings'
  24. import { UsageService } from './services/usage'
  25. import { PdfRenderer } from './api-reference/pdf'
  26. import { McpUpstream } from './mcp-gateway/upstream'
  27. import { McpServerService } from './services/mcp-servers'
  28. import { McpOAuthService } from './services/mcp-oauth'
  29. import { McpContentService } from './services/mcp-content'
  30. import { CustomSkillService } from './custom-skills/service'
  31. import { SkillDeviceLoginService } from './custom-skills/device-login'
  32. import { SkillRegistryService } from './custom-skills/registry'
  33. import { FileManagerService } from './services/file-manager'
  34. import { BrowserTokenService } from './services/browser-tokens'
  35. import { BotKeyService, BotService } from './teams/bots'
  36. import { BackgroundCallService } from './mcp-gateway/background'
  37. import { McpTaskRunner } from './mcp-gateway/tasks'
  38. import { ElicitationService } from './mcp-gateway/elicitation'
  39. import { ScheduleService } from './schedules/service'
  40. import { TeamCaseService } from './teams/cases'
  41. import { ensureLeadProfiles } from './teams/lead-profile'
  42. import { CardService } from './teams/cards'
  43. import { TeamService } from './teams/service'
  44. import { createSkillCatalog } from './services/skill-catalog'
  45. import { forgetGatewaySession } from './mcp-gateway/server'
  46. export interface AppLogger {
  47. info(o: object, m?: string): void
  48. warn(o: object, m?: string): void
  49. error(o: object, m?: string): void
  50. child(b: object): AppLogger
  51. }
  52. export function createApp(log: AppLogger, opts: { db?: DB; providers?: Partial<Record<ProviderId, AgentProvider>> } = {}) {
  53. const db = opts.db ?? createDb(dbPath())
  54. migrateDb(db)
  55. const settings = new SettingsService(db)
  56. const events = new EventStore(db)
  57. const files = new FileService(db)
  58. const profiles = new ProfileService(db, settings)
  59. const knowledge = new KnowledgeService(db, settings)
  60. const apiKeys = new ApiKeyService(db, profiles)
  61. const audit = new AuditService(db)
  62. const usage = new UsageService(db)
  63. const browserTokens = new BrowserTokenService(db)
  64. const apiPdf = new PdfRenderer()
  65. const mcpUpstream = new McpUpstream()
  66. const mcpServers = new McpServerService(db, mcpUpstream, () => settings.get(), summary => audit.log({ actor: 'admin', kind: 'admin', summary }))
  67. const adminAudit = (summary: string, detail?: unknown) => audit.log({ actor: 'admin', kind: 'admin', summary, detail })
  68. migrateManagedSkillDirs(log)
  69. const customSkills = new CustomSkillService(db, adminAudit)
  70. // 按数据库重建生效目录(缺失或被改动的技能目录重新发布)
  71. customSkills.reconcile(msg => log.warn({}, msg))
  72. const skillRegistry = new SkillRegistryService(db, customSkills, adminAudit)
  73. const skillDeviceLogins = new SkillDeviceLoginService(db, skillRegistry, adminAudit)
  74. const fileManager = new FileManagerService(db, () => settings.get(), () => knowledge.list(), files, adminAudit)
  75. const interactions = new InteractionService(db, events, settings)
  76. const sessions = new SessionService(db, settings, profiles, apiKeys, files, events)
  77. const adminAuth = new AdminAuthService(db, log, config.adminPassword)
  78. const plugins = new PluginService(settings, audit)
  79. const codexSkills = new CodexSkillService(summary => audit.log({ actor: 'admin', kind: 'admin', summary }))
  80. const communitySkills = new CommunitySkillService(summary => audit.log({ actor: 'admin', kind: 'admin', summary }))
  81. const gatewayTokens = new RunTokenRegistry()
  82. const claudeLogin = new ClaudeLoginManager(() => settings.get().providers.claude.cliPath, summary => audit.log({ actor: 'admin', kind: 'admin', summary }))
  83. const providers: Partial<Record<ProviderId, AgentProvider>> = opts.providers ?? { claude: new ClaudeProvider(), codex: new CodexProvider(log) }
  84. const codexPlugins = new CodexPluginService(
  85. () => (providers.codex instanceof CodexProvider ? providers.codex : null), () => settings.get(),
  86. summary => audit.log({ actor: 'admin', kind: 'admin', summary }),
  87. )
  88. const mcpContent = new McpContentService({ mcp: mcpServers, upstream: mcpUpstream, files, settings })
  89. const mcpOAuth = new McpOAuthService(db, mcpUpstream, summary => audit.log({ actor: 'admin', kind: 'admin', summary }), id => mcpServers.refresh(id))
  90. sessions.extraModels = p => providers[p]?.knownModels?.() ?? []
  91. const runs = new RunManager({ db, log, settings, sessions, profiles, knowledge, files, events, interactions, audit, usage, plugins, communitySkills, mcpServers, customSkills, gatewayTokens, providers, codexPlugins })
  92. const bots = new BotService(db, profiles)
  93. ensureLeadProfiles({ profiles, customSkills, bots, settings })
  94. const botKeys = new BotKeyService(db, bots)
  95. usage.extraSourceNames = () => botKeys.names()
  96. const skillCatalog = createSkillCatalog({ settings: () => settings.get(), plugins, communitySkills, customSkills, providers })
  97. const teamCards = new CardService({ bots, profiles, knowledge, mcpServers, skillCatalog })
  98. const teams = new TeamService({
  99. db, log, settings, profiles, sessions, runs, files, events, interactions, bots, botKeys, cards: teamCards,
  100. forgetSession: id => {
  101. gatewayTokens.forgetSession(id)
  102. forgetGatewaySession(id)
  103. },
  104. })
  105. const teamCases = new TeamCaseService({ db, teams, dir: teamCasesDir() })
  106. const backgroundCalls = new BackgroundCallService({ db, log, settings, runs, events, teams })
  107. const mcpTasks = new McpTaskRunner({ log, settings, upstream: mcpUpstream, mcp: mcpServers, background: backgroundCalls })
  108. const elicitation = new ElicitationService({ log, audit, interactions, background: backgroundCalls, tasks: mcpTasks })
  109. mcpUpstream.onElicit = e => elicitation.handle(e)
  110. const schedules = new ScheduleService({ db, log, settings, sessions, runs, events, teams, bots, profiles })
  111. return { db, log, settings, events, files, profiles, knowledge, apiKeys, audit, interactions, sessions, usage, browserTokens, apiPdf, mcpUpstream, mcpServers, mcpOAuth, mcpContent, customSkills, skillRegistry, skillDeviceLogins, fileManager, adminAuth, plugins, claudeLogin, codexSkills, communitySkills, codexPlugins, gatewayTokens, providers, runs, bots, botKeys, teams, teamCards, teamCases, schedules, backgroundCalls, mcpTasks, elicitation, skillCatalog }
  112. }
  113. export type App = ReturnType<typeof createApp>
  114. /**
  115. * 技能生效目录从 data/ 下移到顶层 managed-skills/ 后,一次性搬走旧目录。
  116. * systemd 的 ReadWritePaths 把每个目录单独挂载,跨目录改名会报 EXDEV,此时改为复制后删除
  117. */
  118. function migrateManagedSkillDirs(log: AppLogger) {
  119. const moves: [string, string][] = [
  120. [path.join(config.dataDir, 'data', 'community-skills'), communitySkillsDir()],
  121. [path.join(config.dataDir, 'data', 'custom-skills'), customSkillsDir()],
  122. ]
  123. for (const [from, to] of moves) {
  124. if (!fs.existsSync(from)) continue
  125. if (fs.existsSync(to)) {
  126. log.warn({ from, to }, '旧的技能目录仍然存在,新目录已有内容,保留旧目录不处理')
  127. continue
  128. }
  129. fs.mkdirSync(path.dirname(to), { recursive: true })
  130. try {
  131. fs.renameSync(from, to)
  132. } catch (e) {
  133. if ((e as NodeJS.ErrnoException).code !== 'EXDEV') throw e
  134. fs.cpSync(from, to, { recursive: true, verbatimSymlinks: true, preserveTimestamps: true })
  135. fs.rmSync(from, { recursive: true, force: true })
  136. }
  137. log.info({ from, to }, '技能目录已迁移')
  138. }
  139. }