autosnippet 3.3.6 → 3.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dashboard/dist/assets/icons-FHns2ypa.js +1 -0
  2. package/dashboard/dist/assets/index-BRJv5Y3r.js +135 -0
  3. package/dashboard/dist/assets/index-DzoB7kxK.css +1 -0
  4. package/dashboard/dist/index.html +3 -3
  5. package/dist/bin/cli.js +1 -0
  6. package/dist/lib/agent/AgentRuntime.d.ts +2 -2
  7. package/dist/lib/agent/AgentRuntime.js +26 -18
  8. package/dist/lib/agent/domain/ChatAgentTasks.js +4 -0
  9. package/dist/lib/agent/forced-summary.js +7 -2
  10. package/dist/lib/cli/AiScanService.js +4 -4
  11. package/dist/lib/core/discovery/ConfigWatcher.d.ts +64 -0
  12. package/dist/lib/core/discovery/ConfigWatcher.js +336 -0
  13. package/dist/lib/core/discovery/CustomConfigDiscoverer.d.ts +30 -0
  14. package/dist/lib/core/discovery/CustomConfigDiscoverer.js +1305 -0
  15. package/dist/lib/core/discovery/DiscovererPreference.d.ts +44 -0
  16. package/dist/lib/core/discovery/DiscovererPreference.js +141 -0
  17. package/dist/lib/core/discovery/DiscovererRegistry.d.ts +10 -1
  18. package/dist/lib/core/discovery/DiscovererRegistry.js +42 -2
  19. package/dist/lib/core/discovery/ProjectDiscoverer.d.ts +19 -0
  20. package/dist/lib/core/discovery/index.d.ts +2 -0
  21. package/dist/lib/core/discovery/index.js +4 -0
  22. package/dist/lib/core/discovery/parsers/CMakeParser.d.ts +32 -0
  23. package/dist/lib/core/discovery/parsers/CMakeParser.js +148 -0
  24. package/dist/lib/core/discovery/parsers/GradleDslParser.d.ts +43 -0
  25. package/dist/lib/core/discovery/parsers/GradleDslParser.js +171 -0
  26. package/dist/lib/core/discovery/parsers/JsonConfigParser.d.ts +45 -0
  27. package/dist/lib/core/discovery/parsers/JsonConfigParser.js +122 -0
  28. package/dist/lib/core/discovery/parsers/RubyDslParser.d.ts +49 -0
  29. package/dist/lib/core/discovery/parsers/RubyDslParser.js +282 -0
  30. package/dist/lib/core/discovery/parsers/StarlarkParser.d.ts +33 -0
  31. package/dist/lib/core/discovery/parsers/StarlarkParser.js +229 -0
  32. package/dist/lib/core/discovery/parsers/YamlConfigParser.d.ts +37 -0
  33. package/dist/lib/core/discovery/parsers/YamlConfigParser.js +212 -0
  34. package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +7 -1
  35. package/dist/lib/domain/knowledge/KnowledgeEntry.js +17 -3
  36. package/dist/lib/external/ai/AiProvider.d.ts +12 -0
  37. package/dist/lib/external/ai/AiProvider.js +24 -0
  38. package/dist/lib/external/ai/AiProviderManager.d.ts +101 -0
  39. package/dist/lib/external/ai/AiProviderManager.js +193 -0
  40. package/dist/lib/external/ai/providers/ClaudeProvider.js +11 -0
  41. package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +18 -0
  42. package/dist/lib/external/ai/providers/MockProvider.d.ts +21 -3
  43. package/dist/lib/external/ai/providers/MockProvider.js +290 -14
  44. package/dist/lib/external/ai/providers/OpenAiProvider.js +16 -0
  45. package/dist/lib/external/lark/LarkTransport.d.ts +5 -1
  46. package/dist/lib/external/lark/LarkTransport.js +10 -2
  47. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.d.ts +20 -0
  48. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.js +432 -0
  49. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +16 -8
  50. package/dist/lib/external/mcp/handlers/bootstrap/refine.js +8 -0
  51. package/dist/lib/external/mcp/handlers/bootstrap-external.d.ts +9 -0
  52. package/dist/lib/external/mcp/handlers/bootstrap-external.js +2 -0
  53. package/dist/lib/external/mcp/handlers/bootstrap-internal.js +2 -0
  54. package/dist/lib/external/mcp/handlers/consolidated.js +2 -1
  55. package/dist/lib/external/mcp/handlers/dimension-complete-external.js +2 -1
  56. package/dist/lib/external/mcp/handlers/evolve-external.js +5 -2
  57. package/dist/lib/external/mcp/handlers/knowledge.js +5 -4
  58. package/dist/lib/http/routes/ai.js +111 -30
  59. package/dist/lib/http/routes/candidates.js +11 -4
  60. package/dist/lib/http/routes/commands.js +10 -1
  61. package/dist/lib/http/routes/health.js +11 -0
  62. package/dist/lib/http/routes/modules.js +27 -0
  63. package/dist/lib/http/routes/recipes.js +7 -0
  64. package/dist/lib/http/utils/routeHelpers.js +2 -1
  65. package/dist/lib/injection/ServiceContainer.d.ts +6 -5
  66. package/dist/lib/injection/ServiceContainer.js +11 -27
  67. package/dist/lib/injection/ServiceMap.d.ts +2 -0
  68. package/dist/lib/injection/modules/AiModule.d.ts +6 -9
  69. package/dist/lib/injection/modules/AiModule.js +82 -39
  70. package/dist/lib/injection/modules/PanoramaModule.js +1 -1
  71. package/dist/lib/service/cleanup/CleanupService.d.ts +54 -7
  72. package/dist/lib/service/cleanup/CleanupService.js +284 -37
  73. package/dist/lib/service/knowledge/CodeEntityGraph.d.ts +6 -0
  74. package/dist/lib/service/knowledge/CodeEntityGraph.js +16 -0
  75. package/dist/lib/service/knowledge/KnowledgeService.js +23 -10
  76. package/dist/lib/service/module/ModuleService.js +10 -19
  77. package/dist/lib/service/panorama/CouplingAnalyzer.d.ts +10 -1
  78. package/dist/lib/service/panorama/CouplingAnalyzer.js +44 -2
  79. package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +1 -1
  80. package/dist/lib/service/panorama/DimensionAnalyzer.js +31 -17
  81. package/dist/lib/service/panorama/LayerInferrer.d.ts +16 -1
  82. package/dist/lib/service/panorama/LayerInferrer.js +118 -1
  83. package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +9 -0
  84. package/dist/lib/service/panorama/ModuleDiscoverer.js +58 -2
  85. package/dist/lib/service/panorama/PanoramaAggregator.d.ts +6 -2
  86. package/dist/lib/service/panorama/PanoramaAggregator.js +84 -6
  87. package/dist/lib/service/panorama/PanoramaScanner.js +28 -0
  88. package/dist/lib/service/panorama/PanoramaService.js +10 -5
  89. package/dist/lib/service/panorama/PanoramaTypes.d.ts +38 -0
  90. package/dist/lib/service/panorama/RoleRefiner.d.ts +2 -0
  91. package/dist/lib/service/panorama/RoleRefiner.js +41 -0
  92. package/dist/lib/service/panorama/TechStackProfiler.d.ts +13 -0
  93. package/dist/lib/service/panorama/TechStackProfiler.js +191 -0
  94. package/dist/lib/service/skills/SignalCollector.d.ts +1 -0
  95. package/dist/lib/service/skills/SignalCollector.js +6 -5
  96. package/dist/lib/service/vector/ContextualEnricher.d.ts +1 -0
  97. package/dist/lib/service/vector/ContextualEnricher.js +4 -0
  98. package/dist/lib/shared/LanguageService.js +3 -0
  99. package/dist/lib/shared/developer-identity.d.ts +18 -0
  100. package/dist/lib/shared/developer-identity.js +62 -0
  101. package/dist/lib/shared/schemas/http-requests.d.ts +8 -17
  102. package/dist/lib/shared/schemas/http-requests.js +9 -6
  103. package/dist/lib/types/knowledge-wire.d.ts +1 -0
  104. package/package.json +1 -1
  105. package/dashboard/dist/assets/icons-D1aVZYFW.js +0 -1
  106. package/dashboard/dist/assets/index-CxHOu8Hd.css +0 -1
  107. package/dashboard/dist/assets/index-DDdAOpYT.js +0 -128
@@ -25,6 +25,15 @@ const logger = Logger.getInstance();
25
25
  function getContainer() {
26
26
  return getServiceContainer();
27
27
  }
28
+ /** 检查 AI Provider 是否可用(非 mock),不可用则抛 ValidationError */
29
+ function requireAiReady() {
30
+ const container = getContainer();
31
+ const manager = container.singletons?._aiProviderManager;
32
+ if (manager?.isMock) {
33
+ throw new ValidationError('AI Provider 未配置,当前为 Mock 模式。请先在 .env 中配置 API Key。');
34
+ }
35
+ return container;
36
+ }
28
37
  // ═══════════════════════════════════════════════════════
29
38
  // UI 语言偏好 — 前端 ↔ 服务端同步
30
39
  // ═══════════════════════════════════════════════════════
@@ -76,22 +85,19 @@ router.get('/providers', async (req, res) => {
76
85
  });
77
86
  /**
78
87
  * GET /api/v1/ai/config
79
- * 获取当前 AI 配置
88
+ * 获取当前 AI 配置(优先从 AiProviderManager 读取)
80
89
  */
81
90
  router.get('/config', async (req, res) => {
82
91
  const container = getServiceContainer();
83
- const p = container.singletons?.aiProvider;
92
+ const manager = container.singletons?._aiProviderManager;
84
93
  res.json({
85
94
  success: true,
86
- data: {
87
- provider: p?.name || '',
88
- model: p?.model || '',
89
- },
95
+ data: { provider: manager.name, model: manager.model, isMock: manager.isMock },
90
96
  });
91
97
  });
92
98
  /**
93
99
  * POST /api/v1/ai/config
94
- * 更新 AI 配置(切换提供商/模型)
100
+ * 更新 AI 配置(切换提供商/模型)— 通过 AiProviderManager 统一热切换
95
101
  */
96
102
  router.post('/config', validate(AiConfigBody), async (req, res) => {
97
103
  const { provider, model } = req.body;
@@ -106,18 +112,13 @@ router.post('/config', validate(AiConfigBody), async (req, res) => {
106
112
  catch (error) {
107
113
  throw new ValidationError(`Invalid provider: ${error.message}`);
108
114
  }
109
- // 同步到 DI 容器,使 SearchEngine / Agent / IndexingPipeline 等也使用新 provider
110
- try {
111
- const container = getServiceContainer();
112
- container.reloadAiProvider(newProvider);
113
- logger.info('AI provider synced to DI container', {
114
- provider: provider.toLowerCase(),
115
- model: newProvider.model,
116
- });
117
- }
118
- catch (err) {
119
- logger.debug('DI container 同步 AI provider 失败', { error: err.message });
120
- }
115
+ // 通过 reloadAiProvider AiProviderManager.switchProvider() 统一热切换
116
+ const container = getServiceContainer();
117
+ container.reloadAiProvider(newProvider);
118
+ logger.info('AI provider switched via AiProviderManager', {
119
+ provider: provider.toLowerCase(),
120
+ model: newProvider.model,
121
+ });
121
122
  res.json({
122
123
  success: true,
123
124
  data: {
@@ -127,13 +128,58 @@ router.post('/config', validate(AiConfigBody), async (req, res) => {
127
128
  },
128
129
  });
129
130
  });
131
+ /**
132
+ * POST /api/v1/ai/mock/cleanup
133
+ * 清理 Mock 模式产生的候选数据
134
+ */
135
+ router.post('/mock/cleanup', async (_req, res) => {
136
+ const container = getContainer();
137
+ const knowledgeService = container.get('knowledgeService');
138
+ const dbConn = container.get('database');
139
+ const rawDb = dbConn.getDb();
140
+ // 查找所有 mock 来源的候选
141
+ const mockSources = ['mock-bootstrap', 'mock-pipeline'];
142
+ let totalDeleted = 0;
143
+ for (const source of mockSources) {
144
+ const rows = rawDb
145
+ .prepare('SELECT id FROM knowledge_entries WHERE source = ?')
146
+ .all(source);
147
+ for (const row of rows) {
148
+ try {
149
+ await knowledgeService.delete(row.id, { userId: 'system:mock-cleanup' });
150
+ totalDeleted++;
151
+ }
152
+ catch {
153
+ logger.debug(`Mock cleanup: failed to delete ${row.id}`);
154
+ }
155
+ }
156
+ }
157
+ // 清理 mock 相关的 semantic_memories
158
+ try {
159
+ rawDb
160
+ .prepare("DELETE FROM semantic_memories WHERE source = 'bootstrap' AND metadata LIKE '%mock%'")
161
+ .run();
162
+ }
163
+ catch {
164
+ // 表可能不存在
165
+ }
166
+ logger.info(`Mock cleanup completed: ${totalDeleted} entries deleted`);
167
+ const rt = getRealtimeService();
168
+ if (rt) {
169
+ rt.broadcastEvent('mock-cleanup-completed', { deleted: totalDeleted });
170
+ }
171
+ res.json({
172
+ success: true,
173
+ data: { deleted: totalDeleted },
174
+ });
175
+ });
130
176
  /**
131
177
  * POST /api/v1/ai/summarize
132
178
  * AI 摘要生成
133
179
  */
134
180
  router.post('/summarize', validate(AiSummarizeBody), async (req, res) => {
135
181
  const { code, language } = req.body;
136
- const container = getContainer();
182
+ const container = requireAiReady();
137
183
  const factory = container.get('agentFactory');
138
184
  const result = await factory.scanKnowledge({
139
185
  label: 'code',
@@ -158,7 +204,7 @@ router.post('/translate', validate(AiTranslateBody), async (req, res) => {
158
204
  });
159
205
  }
160
206
  try {
161
- const container = getContainer();
207
+ const container = requireAiReady();
162
208
  const factory = container.get('agentFactory');
163
209
  const result = await factory.translateToEnglish(summary, usageGuide);
164
210
  if (result?.error) {
@@ -197,7 +243,7 @@ router.post('/translate', validate(AiTranslateBody), async (req, res) => {
197
243
  */
198
244
  router.post('/chat', validate(AiChatBody), async (req, res) => {
199
245
  const { prompt, history, lang, conversationId } = req.body;
200
- const container = getContainer();
246
+ const container = requireAiReady();
201
247
  const factory = container.get('agentFactory');
202
248
  // ── 对话持久化: 从 ConversationStore 加载历史 ──
203
249
  let convStore = null;
@@ -309,7 +355,7 @@ router.post('/chat', validate(AiChatBody), async (req, res) => {
309
355
  */
310
356
  router.post('/agent/tool', validate(AiToolBody), async (req, res) => {
311
357
  const { tool, params } = req.body;
312
- const container = getContainer();
358
+ const container = requireAiReady();
313
359
  const factory = container.get('agentFactory');
314
360
  const result = await factory.invokeAgent(tool, params);
315
361
  res.json({ success: true, data: result });
@@ -332,7 +378,7 @@ const DAG_TASK_HANDLERS = {
332
378
  };
333
379
  router.post('/agent/task', validate(AiTaskBody), async (req, res) => {
334
380
  const { task, params } = req.body;
335
- const container = getContainer();
381
+ const container = requireAiReady();
336
382
  const factory = container.get('agentFactory');
337
383
  // 优先尝试 DAG 任务
338
384
  const dagHandler = DAG_TASK_HANDLERS[task];
@@ -530,7 +576,7 @@ router.post('/env-config', validate(AiEnvConfigBody), async (req, res) => {
530
576
  for (const [k, v] of Object.entries(updates)) {
531
577
  process.env[k] = String(v);
532
578
  }
533
- // 尝试热切换 AI Provider(包括依赖 AI 的所有服务)
579
+ // 尝试热切换 AI Provider(通过 AiProviderManager 统一处理)
534
580
  try {
535
581
  const newProvider = createProvider({
536
582
  provider: provider.toLowerCase(),
@@ -538,7 +584,7 @@ router.post('/env-config', validate(AiEnvConfigBody), async (req, res) => {
538
584
  });
539
585
  const container = getServiceContainer();
540
586
  container.reloadAiProvider(newProvider);
541
- logger.info('AI provider hot-swapped after env update', {
587
+ logger.info('AI provider hot-swapped via AiProviderManager after env update', {
542
588
  provider,
543
589
  model: newProvider.model,
544
590
  });
@@ -576,7 +622,7 @@ router.post('/env-config', validate(AiEnvConfigBody), async (req, res) => {
576
622
  */
577
623
  router.post('/chat/stream', validate(AiStreamBody), async (req, res) => {
578
624
  const { prompt, history, lang } = req.body;
579
- const container = getContainer();
625
+ const container = requireAiReady();
580
626
  const factory = container.get('agentFactory');
581
627
  const session = createStreamSession('chat');
582
628
  logger.debug('SSE session created', { sessionId: session.sessionId });
@@ -626,18 +672,53 @@ router.post('/chat/stream', validate(AiStreamBody), async (req, res) => {
626
672
  runtime
627
673
  .execute(message)
628
674
  .then((result) => {
675
+ const replyText = result.reply || '';
629
676
  // 发送最终文本
630
- if (result.reply) {
677
+ if (replyText) {
631
678
  const textId = `text_${Date.now()}`;
632
679
  session.send({ type: 'text:start', id: textId, role: 'assistant' });
633
- session.send({ type: 'text:delta', id: textId, delta: result.reply });
680
+ session.send({ type: 'text:delta', id: textId, delta: replyText });
634
681
  session.send({ type: 'text:end', id: textId });
635
682
  }
683
+ else {
684
+ logger.warn('SSE session: empty reply from AgentRuntime', {
685
+ sessionId: session.sessionId,
686
+ iterations: result.iterations,
687
+ toolCalls: result.toolCalls?.length || 0,
688
+ });
689
+ }
636
690
  session.end({
637
- text: result.reply,
691
+ text: replyText || '抱歉,AI 未能生成有效回复。请重试或换个问题。',
638
692
  toolCalls: result.toolCalls || [],
639
693
  iterations: result.iterations || 0,
640
694
  });
695
+ // ── Token 用量持久化(streaming) ──
696
+ try {
697
+ const tokenUsage = result.tokenUsage;
698
+ if (tokenUsage) {
699
+ const tokenStore = container.get('tokenUsageStore');
700
+ const aiProvider = container.singletons?.aiProvider;
701
+ tokenStore.record({
702
+ source: 'user',
703
+ provider: aiProvider?.name ?? undefined,
704
+ model: aiProvider?.model ?? undefined,
705
+ inputTokens: tokenUsage.input || 0,
706
+ outputTokens: tokenUsage.output || 0,
707
+ durationMs: result.durationMs || 0,
708
+ toolCalls: result.toolCalls?.length || 0,
709
+ });
710
+ try {
711
+ const realtime = getRealtimeService();
712
+ realtime?.broadcastTokenUsageUpdated?.();
713
+ }
714
+ catch {
715
+ /* ignore */
716
+ }
717
+ }
718
+ }
719
+ catch {
720
+ /* token tracking should never break streaming */
721
+ }
641
722
  logger.debug('SSE session completed', {
642
723
  sessionId: session.sessionId,
643
724
  events: session.buffer.length,
@@ -55,6 +55,13 @@ router.post('/enrich', validate(EnrichBody), async (req, res) => {
55
55
  let enrichedCount = 0;
56
56
  const results = [];
57
57
  if (aiProvider) {
58
+ // Mock 模式下跳过 AI enrichment
59
+ if (aiProvider.name === 'mock') {
60
+ return void res.json({
61
+ success: true,
62
+ data: { enriched: 0, total: candidates.length, results: [], mock: true },
63
+ });
64
+ }
58
65
  let enriched = [];
59
66
  try {
60
67
  // 获取用户语言偏好
@@ -418,8 +425,8 @@ router.post('/refine-preview', validate(RefinePreviewBody), async (req, res) =>
418
425
  const container = getServiceContainer();
419
426
  const knowledgeService = container.get('knowledgeService');
420
427
  const aiProvider = container.get('aiProvider');
421
- if (!aiProvider) {
422
- throw new ValidationError('AI provider not configured');
428
+ if (!aiProvider || aiProvider.name === 'mock') {
429
+ throw new ValidationError('AI Provider 未配置,当前为 Mock 模式。请先配置 API Key。');
423
430
  }
424
431
  const entry = await knowledgeService.get(candidateId);
425
432
  if (!entry) {
@@ -459,8 +466,8 @@ router.post('/refine-preview-stream', validate(RefinePreviewBody), async (req, r
459
466
  const container = getServiceContainer();
460
467
  const knowledgeService = container.get('knowledgeService');
461
468
  const aiProvider = container.get('aiProvider');
462
- if (!aiProvider) {
463
- throw new ValidationError('AI provider not configured');
469
+ if (!aiProvider || aiProvider.name === 'mock') {
470
+ throw new ValidationError('AI Provider 未配置,当前为 Mock 模式。请先配置 API Key。');
464
471
  }
465
472
  const entry = await knowledgeService.get(candidateId);
466
473
  if (!entry) {
@@ -29,6 +29,15 @@ router.post('/spm-map', async (req, res) => {
29
29
  */
30
30
  router.post('/embed', async (req, res) => {
31
31
  const container = getServiceContainer();
32
+ // Mock 模式下向量构建需要 embedding — 拒绝执行
33
+ const manager = container.singletons?._aiProviderManager;
34
+ if (manager?.isMock) {
35
+ res.status(400).json({
36
+ success: false,
37
+ message: 'AI Provider 未配置,当前为 Mock 模式。Embedding 不可用。',
38
+ });
39
+ return;
40
+ }
32
41
  // 优先使用 VectorService (新架构), 降级到 indexingPipeline (旧架构)
33
42
  const vectorService = container.services.vectorService ? container.get('vectorService') : null;
34
43
  let result;
@@ -80,7 +89,7 @@ router.get('/status', async (req, res) => {
80
89
  spmMap: { available: false },
81
90
  };
82
91
  try {
83
- const indexingPipeline = container.get('indexingPipeline');
92
+ const _indexingPipeline = container.get('indexingPipeline');
84
93
  status.index.ready = true; // IndexingPipeline is available
85
94
  }
86
95
  catch {
@@ -1,5 +1,6 @@
1
1
  /** 健康检查端点 */
2
2
  import express from 'express';
3
+ import { getDeveloperIdentity } from '../../shared/developer-identity.js';
3
4
  const router = express.Router();
4
5
  /**
5
6
  * GET /api/v1/health
@@ -25,4 +26,14 @@ router.get('/ready', (req, res) => {
25
26
  timestamp: Math.floor(Date.now() / 1000),
26
27
  });
27
28
  });
29
+ /**
30
+ * GET /api/v1/health/identity
31
+ * 当前开发者身份(从 git config / 环境变量解析)
32
+ */
33
+ router.get('/identity', (_req, res) => {
34
+ res.json({
35
+ success: true,
36
+ developer: getDeveloperIdentity(),
37
+ });
38
+ });
28
39
  export default router;
@@ -459,6 +459,33 @@ router.get('/bootstrap/status', async (req, res) => {
459
459
  data: taskManager.getSessionStatus(),
460
460
  });
461
461
  });
462
+ /**
463
+ * POST /api/v1/modules/bootstrap/cancel
464
+ * 取消正在运行的 bootstrap / rescan 异步填充会话
465
+ */
466
+ router.post('/bootstrap/cancel', async (req, res) => {
467
+ const container = getServiceContainer();
468
+ let taskManager = null;
469
+ try {
470
+ taskManager = container.get('bootstrapTaskManager');
471
+ }
472
+ catch {
473
+ /* not registered */
474
+ }
475
+ if (!taskManager) {
476
+ return void res.json({ success: true, message: 'No bootstrap task manager initialized' });
477
+ }
478
+ if (!taskManager.isRunning) {
479
+ return void res.json({ success: true, message: 'No active bootstrap session' });
480
+ }
481
+ const reason = req.body?.reason || 'Cancelled by user via Dashboard';
482
+ taskManager.abortSession(reason);
483
+ logger.info('Bootstrap session cancelled via HTTP', { reason });
484
+ res.json({
485
+ success: true,
486
+ data: taskManager.getSessionStatus(),
487
+ });
488
+ });
462
489
  /**
463
490
  * POST /api/v1/modules/rescan
464
491
  * 增量扫描:保留已有 Recipe,重新分析项目,补齐缺失知识
@@ -70,6 +70,13 @@ router.post('/discover-relations', async (req, res) => {
70
70
  data: { status: 'error', error: 'AgentFactory 不可用,请检查 AI Provider 配置' },
71
71
  });
72
72
  }
73
+ // Mock 模式下跳过 AI 关系发现
74
+ if (agentFactory.getAiProviderInfo?.()?.name === 'mock') {
75
+ return void res.json({
76
+ success: true,
77
+ data: { status: 'error', error: 'AI Provider 未配置,当前为 Mock 模式。请先配置 API Key。' },
78
+ });
79
+ }
73
80
  // 快速检查:至少需要 2 条活跃 Recipe
74
81
  try {
75
82
  const knowledgeService = container.get('knowledgeService');
@@ -3,13 +3,14 @@
3
3
  * 提取自各路由文件中的重复实现
4
4
  */
5
5
  import { KnowledgeEntry } from '../../domain/knowledge/KnowledgeEntry.js';
6
+ import { getDeveloperIdentity } from '../../shared/developer-identity.js';
6
7
  /**
7
8
  * 从请求中提取操作上下文(用户身份、IP、UA)
8
9
  * @returns }
9
10
  */
10
11
  export function getContext(req) {
11
12
  return {
12
- userId: String(req.headers['x-user-id'] || 'anonymous'),
13
+ userId: String(req.headers['x-user-id'] || getDeveloperIdentity()),
13
14
  ip: req.ip || '',
14
15
  userAgent: req.headers['user-agent'] || '',
15
16
  };
@@ -32,11 +32,12 @@ export declare class ServiceContainer {
32
32
  /**
33
33
  * 热重载 AI Provider(API Key 变更后调用,无需重启进程)
34
34
  *
35
- * 流程:
36
- * 1. 替换 singletons.aiProvider
37
- * 2. 重新创建 _embedProvider(如果主 provider 不支持 embedding)
38
- * 3. 清除已缓存的依赖 AI 的 singleton(SearchEngine 等),
39
- * 下次 get() 时会用新 provider 重新创建
35
+ * 委托给 AiProviderManager.switchProvider() — 原子操作:
36
+ * 1. 替换 provider 引用 + DI 数据管道同步
37
+ * 2. Token 追踪 AOP 重新挂载
38
+ * 3. Embedding fallback 重建
39
+ * 4. 清除已缓存的依赖 AI singleton(SearchEngine 等)
40
+ * 5. 监听器回调通知
40
41
  */
41
42
  reloadAiProvider(newProvider: Record<string, unknown> | null): void;
42
43
  /** 获取当前默认 UI 语言偏好 */
@@ -147,36 +147,20 @@ export class ServiceContainer {
147
147
  /**
148
148
  * 热重载 AI Provider(API Key 变更后调用,无需重启进程)
149
149
  *
150
- * 流程:
151
- * 1. 替换 singletons.aiProvider
152
- * 2. 重新创建 _embedProvider(如果主 provider 不支持 embedding)
153
- * 3. 清除已缓存的依赖 AI 的 singleton(SearchEngine 等),
154
- * 下次 get() 时会用新 provider 重新创建
150
+ * 委托给 AiProviderManager.switchProvider() — 原子操作:
151
+ * 1. 替换 provider 引用 + DI 数据管道同步
152
+ * 2. Token 追踪 AOP 重新挂载
153
+ * 3. Embedding fallback 重建
154
+ * 4. 清除已缓存的依赖 AI singleton(SearchEngine 等)
155
+ * 5. 监听器回调通知
155
156
  */
156
157
  reloadAiProvider(newProvider) {
157
- const old = this.singletons.aiProvider;
158
- this.singletons.aiProvider = newProvider;
159
- // 重新创建 embedding fallback provider(委托 AiModule)
160
- this.singletons._embedProvider = null;
161
- if (newProvider &&
162
- typeof newProvider.supportsEmbedding === 'function' &&
163
- !newProvider.supportsEmbedding()) {
164
- AiModule.initEmbeddingFallback(this);
158
+ if (!newProvider) {
159
+ this.logger.warn('[ServiceContainer] reloadAiProvider called with null — ignored');
160
+ return;
165
161
  }
166
- // 清除持有旧 aiProvider 引用的 singleton 缓存
167
- // 下次调用 container.get() 时会使用新 provider 重建
168
- const cleared = [];
169
- for (const key of this._aiDependentSingletons || []) {
170
- if (this.singletons[key]) {
171
- this.singletons[key] = null;
172
- cleared.push(key);
173
- }
174
- }
175
- this.logger.info('AI provider hot-reloaded', {
176
- old: old?.constructor?.name || 'none',
177
- new: newProvider?.constructor?.name || 'none',
178
- clearedSingletons: cleared,
179
- });
162
+ const manager = this.singletons._aiProviderManager;
163
+ manager.switchProvider(newProvider);
180
164
  }
181
165
  // ─── 跨进程缓存协调 ─────
182
166
  /**
@@ -14,6 +14,7 @@ import type ProjectGraph from '../core/ast/ProjectGraph.js';
14
14
  import type Constitution from '../core/constitution/Constitution.js';
15
15
  import type Gateway from '../core/gateway/Gateway.js';
16
16
  import type { AiProvider } from '../external/ai/AiProvider.js';
17
+ import type { AiProviderManager } from '../external/ai/AiProviderManager.js';
17
18
  import type AuditLogger from '../infrastructure/audit/AuditLogger.js';
18
19
  import type AuditStore from '../infrastructure/audit/AuditStore.js';
19
20
  import type { CacheCoordinator } from '../infrastructure/cache/CacheCoordinator.js';
@@ -98,6 +99,7 @@ export interface ServiceMap {
98
99
  dimensionCopy: typeof DimensionCopy;
99
100
  constitution: Constitution | null;
100
101
  aiProvider: AiProvider | null;
102
+ aiProviderManager: AiProviderManager;
101
103
  projectGraph: ProjectGraph | null;
102
104
  vectorService: VectorService;
103
105
  contextualEnricher: ContextualEnricher | null;
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * 职责:
8
8
  * - AI Provider 自动探测与创建
9
+ * - AiProviderManager 统一管理层
9
10
  * - Embedding fallback provider 管理
10
11
  * - AiFactory 实例注入
11
12
  *
@@ -17,19 +18,15 @@ import type { ServiceContainer } from '../ServiceContainer.js';
17
18
  *
18
19
  * 1. 动态导入 AiFactory
19
20
  * 2. 自动探测可用 AI Provider
20
- * 3. 创建 Embedding fallback(若主 provider 不支持 embedding)
21
+ * 3. 创建 AiProviderManager(统一管理层)
22
+ * 4. 绑定 Token 追踪、Embedding fallback、DI 级联清理
21
23
  */
22
24
  export declare function initialize(c: ServiceContainer): Promise<void>;
23
- /**
24
- * 创建/刷新 Embedding fallback provider
25
- *
26
- * 若主 provider 不支持 embedding(如 Claude),尝试从其他可用 provider 创建备用。
27
- */
28
- export declare function initEmbeddingFallback(c: ServiceContainer): void;
29
25
  /**
30
26
  * 注册 AI 相关的服务到容器
31
27
  *
32
- * 当前 AI Provider 和 AiFactory 通过 singletons 直接管理,
33
- * 此方法注册便于其他模块通过 container.get() 获取的快捷服务。
28
+ * - 标记 AI 模块就绪
29
+ * - 注册 aiProviderManager 服务
30
+ * - 延迟注入 TokenRecorder(tokenUsageStore 此时已可用)
34
31
  */
35
32
  export declare function register(c: ServiceContainer): void;