autosnippet 3.2.8 → 3.2.9

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 (113) hide show
  1. package/bin/cli.js +6 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +23 -26
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/core/AstAnalyzer.js +1 -1
  8. package/lib/core/discovery/index.js +2 -2
  9. package/lib/external/ai/AiProvider.js +66 -172
  10. package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
  11. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  12. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  13. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +1 -1
  14. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  15. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  16. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  17. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +287 -204
  18. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +7 -6
  19. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  20. package/lib/external/mcp/handlers/bootstrap-internal.js +2 -2
  21. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  22. package/lib/http/HttpServer.js +1 -1
  23. package/lib/http/middleware/requestLogger.js +1 -0
  24. package/lib/http/routes/ai.js +240 -35
  25. package/lib/http/routes/candidates.js +2 -3
  26. package/lib/http/routes/extract.js +13 -11
  27. package/lib/http/routes/modules.js +2 -2
  28. package/lib/http/routes/recipes.js +9 -5
  29. package/lib/http/routes/remote.js +134 -255
  30. package/lib/http/routes/violations.js +0 -54
  31. package/lib/http/utils/sse-sessions.js +1 -1
  32. package/lib/infrastructure/logging/Logger.js +5 -4
  33. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  34. package/lib/injection/ServiceContainer.js +64 -17
  35. package/lib/platform/ScreenCaptureService.js +177 -0
  36. package/lib/platform/ios/routes/spm.js +2 -2
  37. package/lib/service/agent/AgentEventBus.js +207 -0
  38. package/lib/service/agent/AgentFactory.js +490 -0
  39. package/lib/service/agent/AgentMessage.js +240 -0
  40. package/lib/service/agent/AgentRouter.js +228 -0
  41. package/lib/service/agent/AgentRuntime.js +1016 -0
  42. package/lib/service/agent/AgentState.js +217 -0
  43. package/lib/service/agent/IntentClassifier.js +331 -0
  44. package/lib/service/agent/LarkTransport.js +389 -0
  45. package/lib/service/agent/capabilities.js +408 -0
  46. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  47. package/lib/service/{chat → agent/context}/ExplorationTracker.js +25 -14
  48. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +1 -1
  49. package/lib/service/agent/core/LoopContext.js +170 -0
  50. package/lib/service/agent/core/MessageAdapter.js +223 -0
  51. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  52. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
  53. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  54. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  55. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  56. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +85 -135
  57. package/lib/service/agent/domain/insight-producer.js +267 -0
  58. package/lib/service/agent/domain/scan-prompts.js +105 -0
  59. package/lib/service/agent/forced-summary.js +266 -0
  60. package/lib/service/agent/index.js +91 -0
  61. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  62. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  63. package/lib/service/{chat → agent}/memory/SessionStore.js +1 -1
  64. package/lib/service/{chat → agent}/memory/index.js +1 -1
  65. package/lib/service/agent/policies.js +442 -0
  66. package/lib/service/agent/presets.js +303 -0
  67. package/lib/service/agent/strategies.js +717 -0
  68. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  69. package/lib/service/agent/tools/ai-analysis.js +75 -0
  70. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  71. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  72. package/lib/service/{chat → agent}/tools/index.js +27 -21
  73. package/lib/service/{chat → agent}/tools/infrastructure.js +1 -1
  74. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  75. package/lib/service/agent/tools/scan-recipe.js +189 -0
  76. package/lib/service/agent/tools/system-interaction.js +476 -0
  77. package/lib/service/automation/DirectiveDetector.js +0 -1
  78. package/lib/service/automation/FileWatcher.js +0 -8
  79. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  80. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  81. package/lib/service/module/ModuleService.js +40 -73
  82. package/lib/service/skills/SignalCollector.js +26 -19
  83. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  84. package/lib/shared/FieldSpec.js +1 -1
  85. package/lib/shared/StyleGuide.js +1 -1
  86. package/package.json +4 -1
  87. package/resources/native-ui/screenshot.swift +228 -0
  88. package/dashboard/dist/assets/index-D5jiDBQG.css +0 -1
  89. package/dashboard/dist/assets/index-e5OKj-Ni.js +0 -128
  90. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  91. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -750
  92. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  93. package/lib/http/routes/spm.js +0 -5
  94. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  95. package/lib/service/chat/ChatAgent.js +0 -1602
  96. package/lib/service/chat/Memory.js +0 -161
  97. package/lib/service/chat/ProducerAgent.js +0 -431
  98. package/lib/service/chat/ReasoningTrace.js +0 -523
  99. package/lib/service/chat/TaskPipeline.js +0 -357
  100. package/lib/service/chat/WorkingMemory.js +0 -359
  101. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  102. package/lib/service/chat/tools/ai-analysis.js +0 -267
  103. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  104. package/lib/service/chat/tools.js +0 -18
  105. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  106. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  107. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  108. /package/lib/service/{chat → agent}/memory/ActiveContext.js +0 -0
  109. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  110. /package/lib/service/{chat → agent}/tools/ast-graph.js +0 -0
  111. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  112. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  113. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -615,7 +615,7 @@ export async function runAllPhases(projectRoot, ctx, options = {}) {
615
615
  // ── 清除旧数据 (if requested) ──
616
616
  if (options.clearOldData) {
617
617
  try {
618
- const { clearCheckpoints, clearSnapshots } = await import('../../pipeline/orchestrator.js');
618
+ const { clearCheckpoints, clearSnapshots } = await import('../pipeline/orchestrator.js');
619
619
  await clearCheckpoints(projectRoot);
620
620
  await clearSnapshots(projectRoot, ctx);
621
621
  ctx.logger.info('[Bootstrap] Cleared old checkpoints and snapshots');
@@ -636,10 +636,12 @@ export async function runAllPhases(projectRoot, ctx, options = {}) {
636
636
  allFiles, langStats,
637
637
  primaryLang: null, discoverer, allTargets,
638
638
  astProjectSummary: null, astContext: '',
639
- codeEntityResult: null, depGraphData: null,
639
+ codeEntityResult: null, callGraphResult: null,
640
+ depGraphData: null, depEdgesWritten: 0,
640
641
  guardAudit: null, guardEngine: null,
641
642
  activeDimensions: [], enhancementPackInfo: [],
642
- enhancementPatterns: [], langProfile: {},
643
+ enhancementPatterns: [], enhancementGuardRules: [],
644
+ langProfile: {},
643
645
  targetsSummary: [], warnings,
644
646
  report: report || {},
645
647
  incrementalPlan: null,
@@ -652,12 +654,11 @@ export async function runAllPhases(projectRoot, ctx, options = {}) {
652
654
  let incrementalPlan = null;
653
655
  if (options.incremental) {
654
656
  try {
655
- const { IncrementalBootstrap } = await import('../../pipeline/IncrementalBootstrap.js');
657
+ const { IncrementalBootstrap } = await import('../pipeline/IncrementalBootstrap.js');
656
658
  const db = ctx.container?.resolve?.('db') ?? ctx.db;
657
659
  if (db) {
658
660
  const ib = new IncrementalBootstrap(db, projectRoot, { logger: ctx.logger });
659
- const dimIds = (await import('./dimension-configs.js')).getDimensionConfigs?.()
660
- ?.map(d => d.id) || [];
661
+ const dimIds = baseDimensions.map(d => d.id);
661
662
  incrementalPlan = await ib.evaluate(allFiles, dimIds);
662
663
  if (report) report.phases.incremental = { plan: incrementalPlan };
663
664
  ctx.logger.info(`[Bootstrap] Incremental mode: ${incrementalPlan.mode}, affected: ${incrementalPlan.affectedDimensions?.length || 0}`);
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * 参考:
8
8
  * - MetaGPT SOP 驱动模式 (docs/design/external-agent-quality-gap.md §4.2)
9
- * - 内部 Agent 的 AnalystAgent + ProducerAgent 双阶段流程
9
+ * - 内部 Agent 的 PipelineStrategy (Analyze QualityGate → Produce → RejectionGate)
10
10
  *
11
11
  * 设计原则:
12
12
  * - 每个维度 4 个阶段: 扫描 → 验证 → 异常检测 → 提交
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * 调用方:
8
8
  * - CLI: `asd bootstrap --knowledge`
9
- * - ChatAgent: `bootstrapKnowledgeTool` (infrastructure.js)
9
+ * - AgentRuntime: `bootstrapKnowledgeTool` (infrastructure.js)
10
10
  * - Dashboard HTTP: POST /api/bootstrap/knowledge
11
11
  *
12
12
  * 外部 Agent 路径(Cursor/Copilot 等 IDE Agent)请参见:
@@ -280,7 +280,7 @@ export async function bootstrapKnowledge(ctx, args) {
280
280
  expectedOutput: `候选知识(微观代码维度:code-pattern/best-practice/event-and-data-flow + 语言条件扫描)+ Project Skills(宏观叙事维度:code-standard/architecture/project-profile/agent-guidelines + 语言条件扫描)— 共 ${dimensions.length} 个维度`,
281
281
  },
282
282
 
283
- // AST 代码结构分析上下文(供 ChatAgent 使用)
283
+ // AST 代码结构分析上下文(供 Agent 使用)
284
284
  astContext: astContext || null,
285
285
  astSummary: astProjectSummary
286
286
  ? {
@@ -291,7 +291,7 @@ export async function dimensionComplete(ctx, args) {
291
291
  if (isComplete) {
292
292
  // R4: 自动触发 Cursor Delivery
293
293
  try {
294
- const { getServiceContainer } = await import('../../../../injection/ServiceContainer.js');
294
+ const { getServiceContainer } = await import('../../../injection/ServiceContainer.js');
295
295
  const container = getServiceContainer();
296
296
  if (container.services.cursorDeliveryPipeline) {
297
297
  const pipeline = container.get('cursorDeliveryPipeline');
@@ -311,9 +311,9 @@ export async function dimensionComplete(ctx, args) {
311
311
  // R5: 自动触发 Wiki 生成 (fire-and-forget)
312
312
  setImmediate(async () => {
313
313
  try {
314
- const { getServiceContainer: getWikiContainer } = await import('../../../../injection/ServiceContainer.js');
314
+ const { getServiceContainer: getWikiContainer } = await import('../../../injection/ServiceContainer.js');
315
315
  const wikiContainer = getWikiContainer();
316
- const { WikiGenerator } = await import('../../../../service/wiki/WikiGenerator.js');
316
+ const { WikiGenerator } = await import('../../../service/wiki/WikiGenerator.js');
317
317
  const moduleService = wikiContainer.get?.('moduleService');
318
318
  const knowledgeService = wikiContainer.get?.('knowledgeService');
319
319
  if (moduleService && knowledgeService) {
@@ -334,11 +334,11 @@ export async function dimensionComplete(ctx, args) {
334
334
  // R6: 全量 Semantic Memory 固化 (fire-and-forget)
335
335
  setImmediate(async () => {
336
336
  try {
337
- const { EpisodicConsolidator } = await import('../../../service/chat/EpisodicConsolidator.js');
337
+ const { EpisodicConsolidator } = await import('../../../service/agent/domain/EpisodicConsolidator.js');
338
338
  const db = ctx.container.get?.('database') ?? ctx.container.get?.('db');
339
339
  if (db && session.sessionStore) {
340
- const { ProjectSemanticMemory } = await import('../../../service/chat/ProjectSemanticMemory.js');
341
- const semanticMemory = new ProjectSemanticMemory(db, { logger });
340
+ const { PersistentMemory } = await import('../../../service/agent/memory/PersistentMemory.js');
341
+ const semanticMemory = new PersistentMemory(db, { logger });
342
342
  const consolidator = new EpisodicConsolidator(semanticMemory, { logger });
343
343
  const result = await consolidator.consolidate(session.sessionStore, {
344
344
  bootstrapSession: session.id,
@@ -36,7 +36,7 @@ import recipesRouter from './routes/recipes.js';
36
36
  import searchRouter from './routes/search.js';
37
37
  import skillsRouter from './routes/skills.js';
38
38
  import snippetRouter from './routes/snippets.js';
39
- import spmRouter from './routes/spm.js';
39
+ import spmRouter from '../platform/ios/routes/spm.js';
40
40
  import taskRouter from './routes/task.js';
41
41
  import remoteRouter from './routes/remote.js';
42
42
  import violationsRouter from './routes/violations.js';
@@ -20,6 +20,7 @@ const SILENT_PATHS = [
20
20
  '/api/health',
21
21
  '/api/realtime/events',
22
22
  '/api/sse',
23
+ '/api/v1/remote/wait',
23
24
  '/socket.io',
24
25
  ];
25
26
 
@@ -9,6 +9,18 @@ import express from 'express';
9
9
  import { createProvider } from '../../external/ai/AiFactory.js';
10
10
  import Logger from '../../infrastructure/logging/Logger.js';
11
11
  import { getServiceContainer } from '../../injection/ServiceContainer.js';
12
+ import { AgentMessage, Channel } from '../../service/agent/AgentMessage.js';
13
+ import { PRESETS } from '../../service/agent/presets.js';
14
+ import { ContextWindow } from '../../service/agent/context/ContextWindow.js';
15
+ import { ConversationStore } from '../../service/agent/ConversationStore.js';
16
+ import { buildProjectBriefing, cleanFinalAnswer } from '../../service/agent/core/ChatAgentPrompts.js';
17
+ import {
18
+ taskCheckAndSubmit,
19
+ taskDiscoverAllRelations,
20
+ taskFullEnrich,
21
+ taskQualityAudit,
22
+ taskGuardFullScan,
23
+ } from '../../service/agent/domain/ChatAgentTasks.js';
12
24
  import { ValidationError } from '../../shared/errors/index.js';
13
25
  import { asyncHandler } from '../middleware/errorHandler.js';
14
26
  import { createStreamSession, getStreamSession } from '../utils/sse-sessions.js';
@@ -16,10 +28,9 @@ import { createStreamSession, getStreamSession } from '../utils/sse-sessions.js'
16
28
  const router = express.Router();
17
29
  const logger = Logger.getInstance();
18
30
 
19
- /** 获取 ChatAgent 实例(统一 AI 入口) */
20
- function getChatAgent() {
21
- const container = getServiceContainer();
22
- return container.get('chatAgent');
31
+ /** 获取 DI 容器 */
32
+ function getContainer() {
33
+ return getServiceContainer();
23
34
  }
24
35
 
25
36
  // ═══════════════════════════════════════════════════════
@@ -33,8 +44,8 @@ function getChatAgent() {
33
44
  router.get(
34
45
  '/lang',
35
46
  asyncHandler(async (req, res) => {
36
- const chatAgent = getChatAgent();
37
- res.json({ success: true, data: { lang: chatAgent.getLang() || 'zh' } });
47
+ const container = getContainer();
48
+ res.json({ success: true, data: { lang: container.getLang() || 'zh' } });
38
49
  })
39
50
  );
40
51
 
@@ -49,8 +60,8 @@ router.post(
49
60
  if (lang !== 'zh' && lang !== 'en') {
50
61
  throw new ValidationError('lang must be "zh" or "en"');
51
62
  }
52
- const chatAgent = getChatAgent();
53
- chatAgent.setLang(lang);
63
+ const container = getContainer();
64
+ container.setLang(lang);
54
65
  logger.info(`UI language preference updated to "${lang}"`);
55
66
  res.json({ success: true, data: { lang } });
56
67
  })
@@ -168,8 +179,13 @@ router.post(
168
179
  throw new ValidationError('code is required');
169
180
  }
170
181
 
171
- const chatAgent = getChatAgent();
172
- const result = await chatAgent.executeTool('summarize_code', { code, language });
182
+ const container = getContainer();
183
+ const factory = container.get('agentFactory');
184
+ const result = await factory.scanKnowledge({
185
+ label: 'code',
186
+ files: [{ name: 'code', content: code, language }],
187
+ task: 'summarize',
188
+ });
173
189
 
174
190
  if (result?.error) {
175
191
  throw new ValidationError(result.error);
@@ -196,8 +212,9 @@ router.post(
196
212
  }
197
213
 
198
214
  try {
199
- const chatAgent = getChatAgent();
200
- const result = await chatAgent.executeTool('ai_translate', { summary, usageGuide });
215
+ const container = getContainer();
216
+ const factory = container.get('agentFactory');
217
+ const result = await factory.translateToEnglish(summary, usageGuide);
201
218
 
202
219
  if (result?.error) {
203
220
  // AI 不可用,降级返回原文
@@ -224,26 +241,123 @@ router.post(
224
241
  /**
225
242
  * POST /api/v1/ai/chat
226
243
  * AI 对话(RAG 模式,结合项目知识库)
244
+ *
245
+ * 增强特性 (Engine Migration):
246
+ * - 对话持久化 (ConversationStore)
247
+ * - ContextWindow 上下文窗口管理
248
+ * - Token 用量持久化
249
+ * - 项目概况注入 (buildProjectBriefing)
250
+ * - SSE 流式最终回答 (text:start/delta/end)
251
+ * - MemoryCoordinator 记忆提取
227
252
  */
228
253
  router.post(
229
254
  '/chat',
230
255
  asyncHandler(async (req, res) => {
231
- const { prompt, history = [], lang } = req.body;
256
+ const { prompt, history = [], lang, conversationId } = req.body;
232
257
 
233
258
  if (!prompt) {
234
259
  throw new ValidationError('prompt is required');
235
260
  }
236
261
 
237
- const chatAgent = getChatAgent();
238
- const result = await chatAgent.execute(prompt, { history, lang });
262
+ const container = getContainer();
263
+ const factory = container.get('agentFactory');
264
+
265
+ // ── 对话持久化: 从 ConversationStore 加载历史 ──
266
+ let convStore = null;
267
+ let effectiveHistory = history;
268
+ let effectiveConvId = conversationId || null;
269
+ try {
270
+ const projectRoot = container.get('projectRoot') || process.cwd();
271
+ convStore = new ConversationStore(projectRoot);
272
+ if (effectiveConvId) {
273
+ effectiveHistory = convStore.load(effectiveConvId);
274
+ convStore.append(effectiveConvId, { role: 'user', content: prompt });
275
+ } else {
276
+ effectiveConvId = convStore.create({ category: 'user', title: prompt.slice(0, 50) });
277
+ convStore.append(effectiveConvId, { role: 'user', content: prompt });
278
+ }
279
+ } catch {
280
+ /* ConversationStore 不可用时静默降级 */
281
+ }
282
+
283
+ // ── 项目概况刷新 ──
284
+ let projectBriefing = '';
285
+ try {
286
+ projectBriefing = await buildProjectBriefing({ container });
287
+ } catch { /* 静默降级 */ }
288
+
289
+ // ── 创建 ContextWindow ──
290
+ const contextWindow = factory.createContextWindow({ isSystem: false });
291
+
292
+ // ── 创建 Runtime 并注入 onProgress ──
293
+ const message = AgentMessage.fromHttp(req);
294
+ // 加载持久化历史到 message
295
+ if (effectiveHistory.length > 0) {
296
+ message.history = effectiveHistory;
297
+ }
298
+
299
+ const runtime = factory.createChat({
300
+ lang,
301
+ onProgress: (event) => {
302
+ // SSE 流式进度 (如果前端通过 SSE 建立了连接)
303
+ try {
304
+ const sessionId = req.body.sseSessionId;
305
+ if (sessionId) {
306
+ const session = getStreamSession(sessionId);
307
+ if (session) session.send(event);
308
+ }
309
+ } catch { /* SSE 不可用时静默 */ }
310
+ },
311
+ });
312
+ const result = await runtime.execute(message);
313
+
314
+ // ── 持久化 assistant 回复 ──
315
+ if (convStore && effectiveConvId && result.reply) {
316
+ try {
317
+ convStore.append(effectiveConvId, { role: 'assistant', content: result.reply });
318
+ } catch { /* 静默降级 */ }
319
+ }
320
+
321
+ // ── MemoryCoordinator: 提取记忆 ──
322
+ try {
323
+ const memoryCoordinator = container.get('memoryCoordinator');
324
+ if (memoryCoordinator) {
325
+ memoryCoordinator.extractFromConversation?.(prompt, result.reply, 'user');
326
+ }
327
+ } catch { /* 静默降级 */ }
328
+
329
+ // ── Token 用量持久化 ──
330
+ try {
331
+ const tokenStore = container.get('tokenUsageStore');
332
+ if (tokenStore && result.tokenUsage) {
333
+ const aiProvider = container.singletons?.aiProvider;
334
+ tokenStore.record({
335
+ source: 'user',
336
+ dimension: null,
337
+ provider: aiProvider?.name || null,
338
+ model: aiProvider?.model || null,
339
+ inputTokens: result.tokenUsage.input || 0,
340
+ outputTokens: result.tokenUsage.output || 0,
341
+ durationMs: result.durationMs || 0,
342
+ toolCalls: result.toolCalls?.length || 0,
343
+ sessionId: effectiveConvId,
344
+ });
345
+ // 通知前端 token 用量变化
346
+ try {
347
+ const realtime = container.get('realtimeService');
348
+ realtime?.broadcastTokenUsageUpdated?.();
349
+ } catch { /* optional */ }
350
+ }
351
+ } catch { /* token logging should never break execution */ }
239
352
 
240
353
  res.json({
241
354
  success: true,
242
355
  data: {
243
356
  reply: result.reply,
244
- hasContext: result.hasContext,
245
357
  toolCalls: result.toolCalls,
246
- reasoningQuality: result.reasoningQuality || null,
358
+ iterations: result.iterations || null,
359
+ conversationId: effectiveConvId,
360
+ tokenUsage: result.tokenUsage || null,
247
361
  },
248
362
  });
249
363
  })
@@ -263,8 +377,9 @@ router.post(
263
377
  throw new ValidationError('tool name is required');
264
378
  }
265
379
 
266
- const chatAgent = getChatAgent();
267
- const result = await chatAgent.executeTool(tool, params);
380
+ const container = getContainer();
381
+ const factory = container.get('agentFactory');
382
+ const result = await factory.invokeAgent(tool, params);
268
383
 
269
384
  res.json({ success: true, data: result });
270
385
  })
@@ -274,7 +389,19 @@ router.post(
274
389
  * POST /api/v1/ai/agent/task
275
390
  * 执行预定义任务流(查重提交 / 批量关系发现 / 批量补全)
276
391
  * Body: { task: string, params: object }
392
+ *
393
+ * 支持两种任务类型:
394
+ * 1. ToolRegistry 注册的工具 (直接通过 toolName 调用)
395
+ * 2. ChatAgentTasks 的 5 个预定义 DAG 任务
277
396
  */
397
+ const DAG_TASK_HANDLERS = {
398
+ check_and_submit: taskCheckAndSubmit,
399
+ discover_all_relations: taskDiscoverAllRelations,
400
+ full_enrich: taskFullEnrich,
401
+ quality_audit: taskQualityAudit,
402
+ guard_full_scan: taskGuardFullScan,
403
+ };
404
+
278
405
  router.post(
279
406
  '/agent/task',
280
407
  asyncHandler(async (req, res) => {
@@ -284,8 +411,25 @@ router.post(
284
411
  throw new ValidationError('task name is required');
285
412
  }
286
413
 
287
- const chatAgent = getChatAgent();
288
- const result = await chatAgent.runTask(task, params);
414
+ const container = getContainer();
415
+ const factory = container.get('agentFactory');
416
+
417
+ // 优先尝试 DAG 任务
418
+ const dagHandler = DAG_TASK_HANDLERS[task];
419
+ if (dagHandler) {
420
+ const aiProvider = container.singletons?.aiProvider;
421
+ const taskContext = {
422
+ invokeAgent: (name, p) => factory.invokeAgent(name, p),
423
+ aiProvider,
424
+ container,
425
+ logger,
426
+ };
427
+ const result = await dagHandler(taskContext, params);
428
+ return res.json({ success: true, data: result });
429
+ }
430
+
431
+ // 回退到 Agent 工具执行
432
+ const result = await factory.invokeAgent(task, params);
289
433
 
290
434
  res.json({ success: true, data: result });
291
435
  })
@@ -298,8 +442,29 @@ router.post(
298
442
  router.get(
299
443
  '/agent/capabilities',
300
444
  asyncHandler(async (req, res) => {
301
- const chatAgent = getChatAgent();
302
- res.json({ success: true, data: chatAgent.getCapabilities() });
445
+ const container = getContainer();
446
+ const toolRegistry = container.get('toolRegistry');
447
+ const tools = toolRegistry.getToolSchemas();
448
+ const presets = Object.entries(PRESETS).map(([name, p]) => ({
449
+ name,
450
+ description: p.description,
451
+ capabilities: p.capabilities,
452
+ strategy: p.strategy?.type || 'single',
453
+ }));
454
+ res.json({
455
+ success: true,
456
+ data: {
457
+ tools,
458
+ presets,
459
+ tasks: [
460
+ { name: 'check_and_submit', description: '提交候选前自动查重 + 质量预评' },
461
+ { name: 'discover_all_relations', description: '批量发现 Recipe 之间的知识图谱关系' },
462
+ { name: 'full_enrich', description: '批量 AI 语义补全候选字段' },
463
+ { name: 'quality_audit', description: '批量质量审计全部 Recipe,标记低分项' },
464
+ { name: 'guard_full_scan', description: '用全部 Guard 规则扫描指定代码,生成完整报告' },
465
+ ],
466
+ },
467
+ });
303
468
  })
304
469
  );
305
470
 
@@ -511,7 +676,7 @@ router.post(
511
676
 
512
677
  /**
513
678
  * POST /api/v1/ai/chat/stream
514
- * 启动 AI 对话流 — 创建 session,后台执行 ChatAgent,立即返回 sessionId
679
+ * 启动 AI 对话流 — 创建 session,后台执行 AgentRuntime,立即返回 sessionId
515
680
  *
516
681
  * 客户端拿到 sessionId 后通过 GET /chat/events/:sessionId (EventSource) 消费事件
517
682
  *
@@ -537,27 +702,67 @@ router.post(
537
702
  throw new ValidationError('prompt is required');
538
703
  }
539
704
 
540
- const chatAgent = getChatAgent();
705
+ const container = getContainer();
706
+ const factory = container.get('agentFactory');
541
707
  const session = createStreamSession('chat');
542
708
 
543
709
  logger.debug('SSE session created', { sessionId: session.sessionId });
544
710
 
545
- // 立即返回 sessionId(不等待 ChatAgent 执行)
711
+ // 立即返回 sessionId(不等待 Agent 执行)
546
712
  res.json({ success: true, sessionId: session.sessionId });
547
713
 
548
- // 后台执行 ChatAgent — 事件通过 session.send() 缓冲
549
- chatAgent
550
- .execute(prompt, {
551
- history,
552
- lang,
553
- onProgress: (event) => session.send(event),
554
- })
714
+ // AgentMessage 构建
715
+ const message = new AgentMessage({
716
+ content: prompt,
717
+ channel: Channel.HTTP,
718
+ session: { id: session.sessionId, history },
719
+ sender: { id: req.ip || 'http-user', type: 'user' },
720
+ metadata: { lang, stream: true },
721
+ });
722
+
723
+ // 创建 Runtime — 挂载 onProgress 回调映射到 SSE 事件
724
+ const runtime = factory.createChat({
725
+ lang,
726
+ onProgress: (event) => {
727
+ // 将 AgentRuntime 内部事件映射到前端 SSE 协议
728
+ switch (event.type) {
729
+ case 'thinking':
730
+ session.send({ type: 'step:start', step: event.iteration, maxSteps: event.maxIterations, phase: 'thinking' });
731
+ break;
732
+ case 'tool_call':
733
+ session.send({ type: 'tool:start', tool: event.tool, args: event.args });
734
+ break;
735
+ case 'tool_end':
736
+ session.send({
737
+ type: 'tool:end',
738
+ tool: event.tool,
739
+ status: event.status,
740
+ resultSize: event.resultSize,
741
+ duration: event.duration,
742
+ error: event.error,
743
+ });
744
+ break;
745
+ default:
746
+ session.send(event);
747
+ }
748
+ },
749
+ });
750
+
751
+ // 后台执行 AgentRuntime
752
+ runtime
753
+ .execute(message)
555
754
  .then((result) => {
556
- // text:start/delta/end 已由 ChatAgent.execute() 内部通过 onProgress 发送
755
+ // 发送最终文本
756
+ if (result.reply) {
757
+ const textId = `text_${Date.now()}`;
758
+ session.send({ type: 'text:start', id: textId, role: 'assistant' });
759
+ session.send({ type: 'text:delta', id: textId, delta: result.reply });
760
+ session.send({ type: 'text:end', id: textId });
761
+ }
557
762
  session.end({
558
763
  text: result.reply,
559
764
  toolCalls: result.toolCalls || [],
560
- hasContext: result.hasContext || false,
765
+ iterations: result.iterations || 0,
561
766
  });
562
767
  logger.debug('SSE session completed', {
563
768
  sessionId: session.sessionId,
@@ -75,10 +75,9 @@ router.post(
75
75
  // 获取用户语言偏好
76
76
  let lang = 'en';
77
77
  try {
78
- const chatAgent = container.get('chatAgent');
79
- lang = chatAgent?.getLang?.() || 'en';
78
+ lang = container.getLang?.() || 'en';
80
79
  } catch {
81
- /* chatAgent not available */
80
+ /* lang not available */
82
81
  }
83
82
  enriched = await aiProvider.enrichCandidates(candidates, { lang });
84
83
  } catch (err) {
@@ -17,7 +17,7 @@ const logger = Logger.getInstance();
17
17
  /**
18
18
  * POST /api/v1/extract/path
19
19
  * 从文件路径提取代码片段
20
- * 管线: RecipeParser(MD解析) → AI 提取(ChatAgent) → 原始兜底
20
+ * 管线: RecipeParser(MD解析) → AI 提取(AgentRuntime) → 原始兜底
21
21
  */
22
22
  router.post(
23
23
  '/path',
@@ -55,14 +55,15 @@ router.post(
55
55
  !items[0].frontmatter?.title;
56
56
 
57
57
  if (isRawFallback) {
58
- // 3. 尝试 ChatAgent AI 提取
58
+ // 3. 尝试 AI 提取
59
59
  try {
60
- const chatAgent = container.get('chatAgent');
60
+ const agentFactory = container.get('agentFactory');
61
61
  const file = items[0];
62
62
  const fileName = basename(relativePath); // 保留扩展名: BDMineViewController.m
63
- const aiResult = await chatAgent.executeTool('extract_recipes', {
64
- targetName: fileName,
63
+ const aiResult = await agentFactory.scanKnowledge({
64
+ label: fileName,
65
65
  files: [{ name: fileName, content: file.code || '' }],
66
+ task: 'extract',
66
67
  comprehensive: true,
67
68
  });
68
69
 
@@ -102,7 +103,7 @@ router.post(
102
103
  /**
103
104
  * POST /api/v1/extract/text
104
105
  * 从文本内容提取代码片段(剪贴板等)
105
- * 管线: RecipeParser(MD解析) → AI 提取(ChatAgent) → 基础兜底
106
+ * 管线: RecipeParser(MD解析) → AI 提取(AgentRuntime) → 基础兜底
106
107
  */
107
108
  router.post(
108
109
  '/text',
@@ -138,18 +139,19 @@ router.post(
138
139
  });
139
140
  }
140
141
 
141
- // 2. Recipe MD 解析失败 → 尝试 ChatAgent AI 提取
142
+ // 2. Recipe MD 解析失败 → 尝试 AI 提取
142
143
  try {
143
- const chatAgent = container.get('chatAgent');
144
- if (chatAgent) {
144
+ const agentFactory = container.get('agentFactory');
145
+ if (agentFactory) {
145
146
  const lang =
146
147
  language ||
147
148
  (relativePath ? LanguageService.inferLang(relativePath) || 'unknown' : 'unknown');
148
149
  const ext = LanguageService.extForLang(lang) || '.txt';
149
150
  const fileName = relativePath ? basename(relativePath) : `clipboard${ext}`;
150
- const aiResult = await chatAgent.executeTool('extract_recipes', {
151
- targetName: fileName,
151
+ const aiResult = await agentFactory.scanKnowledge({
152
+ label: fileName,
152
153
  files: [{ name: fileName, content: text }],
154
+ task: 'extract',
153
155
  comprehensive: true,
154
156
  });
155
157
 
@@ -531,11 +531,11 @@ router.post(
531
531
  const { maxFiles, skipGuard, contentMaxLines } = req.body || {};
532
532
 
533
533
  const container = getServiceContainer();
534
- const chatAgent = container.get('chatAgent');
534
+ const agentFactory = container.get('agentFactory');
535
535
 
536
536
  logger.info('Bootstrap cold start initiated (ModuleService path)');
537
537
 
538
- const bootstrapResult = await chatAgent.executeTool('bootstrap_knowledge', {
538
+ const bootstrapResult = await agentFactory.bootstrapKnowledge({
539
539
  maxFiles: maxFiles || 500,
540
540
  skipGuard: skipGuard || false,
541
541
  contentMaxLines: contentMaxLines || 120,
@@ -70,15 +70,15 @@ router.post(
70
70
  });
71
71
  }
72
72
 
73
- // 检查 ChatAgent 是否可用
73
+ // 检查 ToolRegistry 是否可用
74
74
  const container = getServiceContainer();
75
- let chatAgent;
75
+ let agentFactory;
76
76
  try {
77
- chatAgent = container.get('chatAgent');
77
+ agentFactory = container.get('agentFactory');
78
78
  } catch {
79
79
  return res.json({
80
80
  success: true,
81
- data: { status: 'error', error: 'ChatAgent 不可用,请检查 AI Provider 配置' },
81
+ data: { status: 'error', error: 'AgentFactory 不可用,请检查 AI Provider 配置' },
82
82
  });
83
83
  }
84
84
 
@@ -111,7 +111,11 @@ router.post(
111
111
  // 异步执行,不 await
112
112
  (async () => {
113
113
  try {
114
- const result = await chatAgent.runTask('discover_all_relations', { batchSize });
114
+ const result = await agentFactory.scanKnowledge({
115
+ label: 'knowledge-graph',
116
+ files: [],
117
+ task: 'relations',
118
+ });
115
119
  discoverTask.status = 'done';
116
120
  discoverTask.finishedAt = new Date().toISOString();
117
121
  discoverTask.discovered = result.discovered || 0;