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.
- package/dashboard/dist/assets/icons-FHns2ypa.js +1 -0
- package/dashboard/dist/assets/index-BRJv5Y3r.js +135 -0
- package/dashboard/dist/assets/index-DzoB7kxK.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/dist/bin/cli.js +1 -0
- package/dist/lib/agent/AgentRuntime.d.ts +2 -2
- package/dist/lib/agent/AgentRuntime.js +26 -18
- package/dist/lib/agent/domain/ChatAgentTasks.js +4 -0
- package/dist/lib/agent/forced-summary.js +7 -2
- package/dist/lib/cli/AiScanService.js +4 -4
- package/dist/lib/core/discovery/ConfigWatcher.d.ts +64 -0
- package/dist/lib/core/discovery/ConfigWatcher.js +336 -0
- package/dist/lib/core/discovery/CustomConfigDiscoverer.d.ts +30 -0
- package/dist/lib/core/discovery/CustomConfigDiscoverer.js +1305 -0
- package/dist/lib/core/discovery/DiscovererPreference.d.ts +44 -0
- package/dist/lib/core/discovery/DiscovererPreference.js +141 -0
- package/dist/lib/core/discovery/DiscovererRegistry.d.ts +10 -1
- package/dist/lib/core/discovery/DiscovererRegistry.js +42 -2
- package/dist/lib/core/discovery/ProjectDiscoverer.d.ts +19 -0
- package/dist/lib/core/discovery/index.d.ts +2 -0
- package/dist/lib/core/discovery/index.js +4 -0
- package/dist/lib/core/discovery/parsers/CMakeParser.d.ts +32 -0
- package/dist/lib/core/discovery/parsers/CMakeParser.js +148 -0
- package/dist/lib/core/discovery/parsers/GradleDslParser.d.ts +43 -0
- package/dist/lib/core/discovery/parsers/GradleDslParser.js +171 -0
- package/dist/lib/core/discovery/parsers/JsonConfigParser.d.ts +45 -0
- package/dist/lib/core/discovery/parsers/JsonConfigParser.js +122 -0
- package/dist/lib/core/discovery/parsers/RubyDslParser.d.ts +49 -0
- package/dist/lib/core/discovery/parsers/RubyDslParser.js +282 -0
- package/dist/lib/core/discovery/parsers/StarlarkParser.d.ts +33 -0
- package/dist/lib/core/discovery/parsers/StarlarkParser.js +229 -0
- package/dist/lib/core/discovery/parsers/YamlConfigParser.d.ts +37 -0
- package/dist/lib/core/discovery/parsers/YamlConfigParser.js +212 -0
- package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +7 -1
- package/dist/lib/domain/knowledge/KnowledgeEntry.js +17 -3
- package/dist/lib/external/ai/AiProvider.d.ts +12 -0
- package/dist/lib/external/ai/AiProvider.js +24 -0
- package/dist/lib/external/ai/AiProviderManager.d.ts +101 -0
- package/dist/lib/external/ai/AiProviderManager.js +193 -0
- package/dist/lib/external/ai/providers/ClaudeProvider.js +11 -0
- package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +18 -0
- package/dist/lib/external/ai/providers/MockProvider.d.ts +21 -3
- package/dist/lib/external/ai/providers/MockProvider.js +290 -14
- package/dist/lib/external/ai/providers/OpenAiProvider.js +16 -0
- package/dist/lib/external/lark/LarkTransport.d.ts +5 -1
- package/dist/lib/external/lark/LarkTransport.js +10 -2
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.d.ts +20 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.js +432 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +16 -8
- package/dist/lib/external/mcp/handlers/bootstrap/refine.js +8 -0
- package/dist/lib/external/mcp/handlers/bootstrap-external.d.ts +9 -0
- package/dist/lib/external/mcp/handlers/bootstrap-external.js +2 -0
- package/dist/lib/external/mcp/handlers/bootstrap-internal.js +2 -0
- package/dist/lib/external/mcp/handlers/consolidated.js +2 -1
- package/dist/lib/external/mcp/handlers/dimension-complete-external.js +2 -1
- package/dist/lib/external/mcp/handlers/evolve-external.js +5 -2
- package/dist/lib/external/mcp/handlers/knowledge.js +5 -4
- package/dist/lib/http/routes/ai.js +111 -30
- package/dist/lib/http/routes/candidates.js +11 -4
- package/dist/lib/http/routes/commands.js +10 -1
- package/dist/lib/http/routes/health.js +11 -0
- package/dist/lib/http/routes/modules.js +27 -0
- package/dist/lib/http/routes/recipes.js +7 -0
- package/dist/lib/http/utils/routeHelpers.js +2 -1
- package/dist/lib/injection/ServiceContainer.d.ts +6 -5
- package/dist/lib/injection/ServiceContainer.js +11 -27
- package/dist/lib/injection/ServiceMap.d.ts +2 -0
- package/dist/lib/injection/modules/AiModule.d.ts +6 -9
- package/dist/lib/injection/modules/AiModule.js +82 -39
- package/dist/lib/injection/modules/PanoramaModule.js +1 -1
- package/dist/lib/service/cleanup/CleanupService.d.ts +54 -7
- package/dist/lib/service/cleanup/CleanupService.js +284 -37
- package/dist/lib/service/knowledge/CodeEntityGraph.d.ts +6 -0
- package/dist/lib/service/knowledge/CodeEntityGraph.js +16 -0
- package/dist/lib/service/knowledge/KnowledgeService.js +23 -10
- package/dist/lib/service/module/ModuleService.js +10 -19
- package/dist/lib/service/panorama/CouplingAnalyzer.d.ts +10 -1
- package/dist/lib/service/panorama/CouplingAnalyzer.js +44 -2
- package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +1 -1
- package/dist/lib/service/panorama/DimensionAnalyzer.js +31 -17
- package/dist/lib/service/panorama/LayerInferrer.d.ts +16 -1
- package/dist/lib/service/panorama/LayerInferrer.js +118 -1
- package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +9 -0
- package/dist/lib/service/panorama/ModuleDiscoverer.js +58 -2
- package/dist/lib/service/panorama/PanoramaAggregator.d.ts +6 -2
- package/dist/lib/service/panorama/PanoramaAggregator.js +84 -6
- package/dist/lib/service/panorama/PanoramaScanner.js +28 -0
- package/dist/lib/service/panorama/PanoramaService.js +10 -5
- package/dist/lib/service/panorama/PanoramaTypes.d.ts +38 -0
- package/dist/lib/service/panorama/RoleRefiner.d.ts +2 -0
- package/dist/lib/service/panorama/RoleRefiner.js +41 -0
- package/dist/lib/service/panorama/TechStackProfiler.d.ts +13 -0
- package/dist/lib/service/panorama/TechStackProfiler.js +191 -0
- package/dist/lib/service/skills/SignalCollector.d.ts +1 -0
- package/dist/lib/service/skills/SignalCollector.js +6 -5
- package/dist/lib/service/vector/ContextualEnricher.d.ts +1 -0
- package/dist/lib/service/vector/ContextualEnricher.js +4 -0
- package/dist/lib/shared/LanguageService.js +3 -0
- package/dist/lib/shared/developer-identity.d.ts +18 -0
- package/dist/lib/shared/developer-identity.js +62 -0
- package/dist/lib/shared/schemas/http-requests.d.ts +8 -17
- package/dist/lib/shared/schemas/http-requests.js +9 -6
- package/dist/lib/types/knowledge-wire.d.ts +1 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/icons-D1aVZYFW.js +0 -1
- package/dashboard/dist/assets/index-CxHOu8Hd.css +0 -1
- 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
|
|
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
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 (
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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'] ||
|
|
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. 替换
|
|
37
|
-
* 2.
|
|
38
|
-
* 3.
|
|
39
|
-
*
|
|
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. 替换
|
|
152
|
-
* 2.
|
|
153
|
-
* 3.
|
|
154
|
-
*
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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. 创建
|
|
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
|
-
*
|
|
33
|
-
*
|
|
28
|
+
* - 标记 AI 模块就绪
|
|
29
|
+
* - 注册 aiProviderManager 服务
|
|
30
|
+
* - 延迟注入 TokenRecorder(tokenUsageStore 此时已可用)
|
|
34
31
|
*/
|
|
35
32
|
export declare function register(c: ServiceContainer): void;
|