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
@@ -18,7 +18,6 @@ import { detectTriggers, REGEX } from './DirectiveDetector.js';
18
18
  import { handleAlink } from './handlers/AlinkHandler.js';
19
19
  /* ── Handler imports ── */
20
20
  import { handleCreate } from './handlers/CreateHandler.js';
21
- import { handleDraft } from './handlers/DraftHandler.js';
22
21
  import { handleGuard } from './handlers/GuardHandler.js';
23
22
  import { handleHeader } from './handlers/HeaderHandler.js';
24
23
  import { handleSearch } from './handlers/SearchHandler.js';
@@ -53,8 +52,6 @@ const DEFAULT_FILE_PATTERN = [
53
52
  '**/*.cpp',
54
53
  '**/*.cc',
55
54
  '**/*.hpp',
56
- // Draft
57
- '**/_draft_*.md',
58
55
  ];
59
56
  const IGNORED = [
60
57
  '**/node_modules/**',
@@ -221,11 +218,6 @@ export class FileWatcher {
221
218
 
222
219
  const filename = basename(fullPath);
223
220
 
224
- // _draft_*.md 文件自动处理
225
- if (REGEX.DRAFT_FILE.test(filename)) {
226
- await handleDraft(this, fullPath, relativePath, data);
227
- }
228
-
229
221
  // 检测指令
230
222
  const triggers = detectTriggers(data, filename);
231
223
 
@@ -16,7 +16,7 @@ import { REGEX } from '../DirectiveDetector.js';
16
16
  * @param {string} createOption 'c' | 'f' | undefined
17
17
  */
18
18
  export async function handleCreate(watcher, fullPath, relativePath, createOption) {
19
- const XA = await import('../../../infrastructure/external/XcodeAutomation.js');
19
+ const XA = await import('../../../platform/ios/xcode/XcodeAutomation.js');
20
20
  const CM = await import('../../../infrastructure/external/ClipboardManager.js');
21
21
 
22
22
  // 1. 读剪贴板(仅 -c 模式)
@@ -137,8 +137,12 @@ async function silentCreateCandidate(watcher, text, relativePath) {
137
137
  try {
138
138
  const { getServiceContainer } = await import('../../../injection/ServiceContainer.js');
139
139
  const container = getServiceContainer();
140
- const chatAgent = container.get('chatAgent');
141
- const aiResult = await chatAgent.executeTool('summarize_code', { code: text, language: lang });
140
+ const agentFactory = container.get('agentFactory');
141
+ const aiResult = await agentFactory.scanKnowledge({
142
+ label: title,
143
+ files: [{ name: title, content: text, language: lang }],
144
+ task: 'summarize',
145
+ });
142
146
  if (aiResult && !aiResult.error) {
143
147
  title = aiResult.title || title;
144
148
  summary = aiResult.summary || '';
@@ -53,22 +53,23 @@ export async function handleDraft(watcher, fullPath, relativePath, content) {
53
53
  }
54
54
  }
55
55
 
56
- // AI 摘要回退(通过 ChatAgent 统一入口)
56
+ // AI 摘要回退(通过 Agent 统一管道)
57
57
  try {
58
58
  const { getServiceContainer } = await import('../../../injection/ServiceContainer.js');
59
59
  const container = getServiceContainer();
60
- const chatAgent = container.get('chatAgent');
60
+ const agentFactory = container.get('agentFactory');
61
61
  const lang = LanguageService.inferLang(relativePath) || 'unknown';
62
- const result = await chatAgent.executeTool('summarize_code', {
63
- code: content,
64
- language: lang,
62
+ const result = await agentFactory.scanKnowledge({
63
+ label: relativePath,
64
+ files: [{ name: relativePath, content, language: lang }],
65
+ task: 'summarize',
65
66
  });
66
67
  if (result && !result.error && result.title && result.code) {
67
68
  await watcher._appendCandidates([result], 'draft-file');
68
69
  watcher._notify(`已创建候选「${result.title}」`);
69
70
  }
70
71
  } catch {
71
- /* ChatAgent 不可用 */
72
+ /* AgentFactory 不可用 */
72
73
  }
73
74
  } catch (e) {
74
75
  console.warn('[Watcher] 草稿文件解析失败:', e.message);
@@ -88,8 +88,8 @@ export class ModuleService {
88
88
  #logger;
89
89
 
90
90
  // AI pipeline deps
91
- #aiFactory;
92
- #chatAgent;
91
+ #agentFactory;
92
+ #container;
93
93
  #qualityScorer;
94
94
  #recipeExtractor;
95
95
  #guardCheckEngine;
@@ -98,8 +98,8 @@ export class ModuleService {
98
98
  /**
99
99
  * @param {string} projectRoot
100
100
  * @param {object} [options]
101
- * @param {object} [options.aiFactory]
102
- * @param {object} [options.chatAgent]
101
+ * @param {object} [options.agentFactory]
102
+ * @param {object} [options.container]
103
103
  * @param {object} [options.qualityScorer]
104
104
  * @param {object} [options.recipeExtractor]
105
105
  * @param {object} [options.guardCheckEngine]
@@ -109,8 +109,8 @@ export class ModuleService {
109
109
  this.#projectRoot = projectRoot;
110
110
  this.#registry = getDiscovererRegistry();
111
111
  this.#logger = Logger.getInstance();
112
- this.#aiFactory = options.aiFactory || null;
113
- this.#chatAgent = options.chatAgent || null;
112
+ this.#agentFactory = options.agentFactory || null;
113
+ this.#container = options.container || null;
114
114
  this.#qualityScorer = options.qualityScorer || null;
115
115
  this.#recipeExtractor = options.recipeExtractor || null;
116
116
  this.#guardCheckEngine = options.guardCheckEngine || null;
@@ -443,7 +443,7 @@ export class ModuleService {
443
443
  this.#logger.info(`[ModuleService] scanTarget: ${targetName}, ${files.length} files`);
444
444
 
445
445
  // 3. AI 提取
446
- if (!this.#chatAgent && !this.#aiFactory) {
446
+ if (!this.#agentFactory) {
447
447
  return {
448
448
  recipes: [],
449
449
  scannedFiles,
@@ -481,14 +481,16 @@ export class ModuleService {
481
481
 
482
482
  const result = { recipes, scannedFiles };
483
483
  if (recipes.length === 0) {
484
- // 检查是否因为 API Key 缺失导致 mock / 空结果
485
- const aiInfo = this.#aiFactory
486
- ? (await import('../../external/ai/AiFactory.js')).getAiConfigInfo()
487
- : null;
488
- if (aiInfo && !aiInfo.hasKey) {
489
- result.noAi = true;
490
- result.message = 'AI 未配置,已跳过智能提取。请在 .env 中设置 API Key 后重试。';
491
- } else {
484
+ // 检查是否因为 AI Provider mock 导致空结果
485
+ try {
486
+ const aiProvider = this.#container?.singletons?.aiProvider;
487
+ if (!aiProvider || aiProvider.name === 'mock') {
488
+ result.noAi = true;
489
+ result.message = 'AI 未配置,已跳过智能提取。请在 .env 中设置 API Key 后重试。';
490
+ } else {
491
+ result.message = `AI 提取完成,但未发现可复用的代码模式(${targetName}, ${files.length} 个文件)`;
492
+ }
493
+ } catch {
492
494
  result.message = `AI 提取完成,但未发现可复用的代码模式(${targetName}, ${files.length} 个文件)`;
493
495
  }
494
496
  }
@@ -585,7 +587,7 @@ export class ModuleService {
585
587
  const TOTAL_TIMEOUT = options.totalTimeout || 540000;
586
588
  let timedOut = false;
587
589
 
588
- if (this.#chatAgent || this.#aiFactory) {
590
+ if (this.#agentFactory) {
589
591
  const BATCH_SIZE = options.batchSize || 20;
590
592
 
591
593
  for (let i = 0; i < allFiles.length; i += BATCH_SIZE) {
@@ -759,75 +761,40 @@ export class ModuleService {
759
761
  }
760
762
 
761
763
  /**
762
- * AI 提取 Recipes — 统一入口
764
+ * AI 提取 Recipes — 委托 AgentFactory.scanKnowledge
765
+ *
766
+ * AgentFactory.scanKnowledge 内部创建 insight Agent,
767
+ * Agent(LLM) 直接分析代码 + 使用 AST 工具,输出 Recipe JSON。
763
768
  */
764
769
  async #aiExtractRecipes(targetName, files) {
765
- const AI_EXTRACT_TIMEOUT = 120_000;
770
+ if (!this.#agentFactory) {
771
+ return [];
772
+ }
766
773
 
767
774
  try {
768
- if (this.#chatAgent) {
769
- const extractPromise = this.#chatAgent.executeTool('extract_recipes', {
770
- targetName,
771
- files,
772
- });
773
- const timeoutPromise = new Promise((_, reject) =>
774
- setTimeout(
775
- () => reject(new Error(`AI extraction timeout (${AI_EXTRACT_TIMEOUT / 1000}s)`)),
776
- AI_EXTRACT_TIMEOUT
777
- )
778
- );
779
- const result = await Promise.race([extractPromise, timeoutPromise]);
780
- if (result?.error) {
781
- // API Key 缺失属于配置问题,降为 info 级别避免频繁报错
782
- if (/API_KEY_MISSING|API.Key.未配置|unregistered callers/i.test(result.error)) {
783
- this.#logger.info(`[ModuleService] AI 未启用(未配置 API Key),跳过 AI 提取。`);
784
- } else {
785
- this.#logger.warn(`[ModuleService] AI extraction error: ${result.error}`);
786
- }
787
- return [];
788
- }
789
- return result?.recipes || [];
790
- }
791
-
792
- if (this.#aiFactory) {
793
- const {
794
- getProviderWithFallback,
795
- isGeoOrProviderError,
796
- getAvailableFallbacks,
797
- createProvider,
798
- } = this.#aiFactory;
799
- let ai = await getProviderWithFallback();
800
- if (!ai) {
801
- return [];
802
- }
775
+ const result = await this.#agentFactory.scanKnowledge({
776
+ label: targetName, files, task: 'extract',
777
+ });
778
+ const recipes = result.recipes || [];
803
779
 
804
- try {
805
- return await ai.extractRecipes(targetName, files, {});
806
- } catch (primaryErr) {
807
- if (isGeoOrProviderError(primaryErr)) {
808
- const currentProvider = (process.env.ASD_AI_PROVIDER || 'google').toLowerCase();
809
- const fallbacks = getAvailableFallbacks(currentProvider);
810
- for (const fbName of fallbacks) {
811
- try {
812
- ai = createProvider({ provider: fbName });
813
- return await ai.extractRecipes(targetName, files, {});
814
- } catch (fbErr) {
815
- this.#logger.warn(`[ModuleService] fallback "${fbName}" failed: ${fbErr.message}`);
816
- }
817
- }
818
- }
819
- throw primaryErr;
820
- }
780
+ if (recipes.length === 0) {
781
+ this.#logger.info(
782
+ `[ModuleService] Agent 未产出 recipe (${targetName}, ${files.length} files)`,
783
+ );
784
+ } else {
785
+ this.#logger.info(
786
+ `[ModuleService] Agent 提取 ${recipes.length} recipes (${targetName})`,
787
+ );
821
788
  }
789
+ return recipes;
822
790
  } catch (err) {
823
- if (err.code === 'API_KEY_MISSING') {
791
+ if (err.code === 'API_KEY_MISSING' || /API_KEY_MISSING|API.Key.未配置|unregistered callers/i.test(err.message)) {
824
792
  this.#logger.info(`[ModuleService] AI 未启用(未配置 API Key),跳过 AI 提取。`);
825
793
  } else {
826
794
  this.#logger.warn(`[ModuleService] AI extraction failed: ${err.message}`);
827
795
  }
796
+ return [];
828
797
  }
829
-
830
- return [];
831
798
  }
832
799
 
833
800
  /**
@@ -2,7 +2,7 @@
2
2
  * SignalCollector — AI 驱动的后台行为分析与 Skill 推荐引擎
3
3
  *
4
4
  * 在 `asd ui` 运行时作为后台守护进程运行,周期性收集多维度信号并
5
- * 通过 ChatAgent(AI ReAct 循环)进行深度分析,生成 Skill 推荐。
5
+ * 通过 AgentFactory(统一 Agent 系统)进行深度分析,生成 Skill 推荐。
6
6
  *
7
7
  * 三种工作模式:
8
8
  * - off — 不收集,不推荐
@@ -10,8 +10,8 @@
10
10
  * - auto — 收集信号 → AI 分析 → 推送推荐 + AI 自动创建 Skill
11
11
  *
12
12
  * 核心架构:
13
- * 每次 tick → 收集 6 维度信号 → 构造分析 prompt → ChatAgent.execute()
14
- * → AI ReAct 循环(可调用 suggest_skills / create_skill 等工具)
13
+ * 每次 tick → 收集 6 维度信号 → 构造分析 prompt → AgentFactory.createChat()
14
+ * → Agent 执行(可调用 suggest_skills / create_skill 等工具)
15
15
  * → 解析 AI 响应(suggestions + nextIntervalMinutes + summary)
16
16
  * → 推送建议 → 动态调整下次执行间隔
17
17
  *
@@ -27,11 +27,11 @@
27
27
  * 1. 静默 — 不打断用户,后台运行,所有错误降级
28
28
  * 2. 增量 — 只分析上次快照以来的新数据
29
29
  * 3. 去重 — 同一推荐仅推送一次
30
- * 4. AI 驱动 — 所有分析决策由 ChatAgent 完成
30
+ * 4. AI 驱动 — 所有分析决策由 AgentRuntime 完成
31
31
  * 5. 自适应 — AI 根据信号密度动态调整执行频率
32
32
  *
33
33
  * 前提条件:
34
- * 需要可用的 AI Provider(chatAgent.hasAI === true)
34
+ * 需要可用的 AI Provider
35
35
  *
36
36
  * 生命周期:
37
37
  * new SignalCollector(opts) → instance.start() → ... → instance.stop()
@@ -52,7 +52,8 @@ const SNAPSHOT_FILE = 'signal-snapshot.json';
52
52
  export class SignalCollector {
53
53
  #projectRoot;
54
54
  #db;
55
- #chatAgent; // ChatAgent 实例 — AI 核心
55
+ #agentFactory; // AgentFactory 实例 — 统一 Agent 系统
56
+ #container; // ServiceContainer — 用于获取 aiProvider 等
56
57
  #mode; // 'off' | 'suggest' | 'auto'
57
58
  #intervalMs;
58
59
  #timer = null;
@@ -68,7 +69,8 @@ export class SignalCollector {
68
69
  * @param {object} opts
69
70
  * @param {string} opts.projectRoot — 用户项目根目录
70
71
  * @param {object} [opts.database] — better-sqlite3 实例
71
- * @param {object} [opts.chatAgent] ChatAgent 实例
72
+ * @param {object} [opts.agentFactory] AgentFactory 实例
73
+ * @param {object} [opts.container] — ServiceContainer 实例
72
74
  * @param {string} [opts.mode] — 'off' | 'suggest' | 'auto'
73
75
  * @param {number} [opts.intervalMs] — 初始收集间隔(毫秒),后续由 AI 动态调整
74
76
  * @param {function} [opts.onSuggestions] — 新建议回调 (suggestions[]) => void
@@ -76,14 +78,16 @@ export class SignalCollector {
76
78
  constructor({
77
79
  projectRoot,
78
80
  database = null,
79
- chatAgent = null,
81
+ agentFactory = null,
82
+ container = null,
80
83
  mode = 'auto',
81
84
  intervalMs = DEFAULT_INTERVAL_MS,
82
85
  onSuggestions = null,
83
86
  }) {
84
87
  this.#projectRoot = projectRoot;
85
88
  this.#db = database;
86
- this.#chatAgent = chatAgent;
89
+ this.#agentFactory = agentFactory;
90
+ this.#container = container;
87
91
  this.#mode = ['off', 'suggest', 'auto'].includes(mode) ? mode : 'auto';
88
92
  this.#intervalMs = Math.max(Math.min(intervalMs, MAX_INTERVAL_MS), MIN_INTERVAL_MS);
89
93
  this.#logger = Logger.getInstance();
@@ -115,7 +119,8 @@ export class SignalCollector {
115
119
  this.#logger.info('[SignalCollector] mode=off, skipping start');
116
120
  return;
117
121
  }
118
- if (!this.#chatAgent?.hasAI) {
122
+ const aiProvider = this.#container?.get('aiProvider');
123
+ if (!aiProvider || aiProvider.name === 'mock') {
119
124
  this.#logger.info('[SignalCollector] no AI provider available, skipping start');
120
125
  return;
121
126
  }
@@ -217,12 +222,14 @@ export class SignalCollector {
217
222
  // 2. 构造分析 prompt
218
223
  const prompt = this.#buildAnalysisPrompt(signals);
219
224
 
220
- // 3. 调用 ChatAgent AI 分析(source: 'system' 确保 Memory 隔离)
221
- this.#logger.debug('[SignalCollector] invoking ChatAgent for analysis...');
222
- const { reply, toolCalls } = await this.#chatAgent.execute(prompt, {
223
- history: [],
224
- source: 'system',
225
- });
225
+ // 3. 调用 Agent 系统进行 AI 分析
226
+ this.#logger.debug('[SignalCollector] invoking Agent for analysis...');
227
+ const agent = this.#agentFactory.createChat({ lang: 'en' });
228
+ const { AgentMessage } = await import('../agent/AgentMessage.js');
229
+ const message = AgentMessage.internal(prompt, { source: 'signal_collector' });
230
+ const result = await agent.execute(message);
231
+ const reply = result?.reply ?? result?.text ?? '';
232
+ const toolCalls = result?.toolCalls ?? [];
226
233
 
227
234
  // 4. 解析 AI 响应 — 使用 AiProvider.extractJSON 统一 structured output 解析
228
235
  const parsed = this.#parseStructuredReply(reply);
@@ -514,13 +521,13 @@ ${JSON.stringify(signals.codeChanges, null, 2)}
514
521
  // ═══════════════════════════════════════════════════════
515
522
 
516
523
  /**
517
- * 从 ChatAgent ReAct 回复中提取结构化 JSON
524
+ * 从 AgentRuntime ReAct 回复中提取结构化 JSON
518
525
  *
519
526
  * 优先级链:
520
527
  * 1. AiProvider.extractJSON (支持 markdown 清理、截断修复、trailing comma 等)
521
528
  * 2. 最后一行 JSON 回退 (兼容 prompt 要求的 "最后一行输出 JSON" 格式)
522
529
  *
523
- * @param {string} reply — ChatAgent.execute() 的回复文本
530
+ * @param {string} reply — AgentRuntime.execute() 的回复文本
524
531
  * @returns {{ suggestions: Array, nextIntervalMinutes: number|null, summary: string }}
525
532
  */
526
533
  #parseStructuredReply(reply) {
@@ -531,7 +538,7 @@ ${JSON.stringify(signals.codeChanges, null, 2)}
531
538
 
532
539
  try {
533
540
  // 策略 1: 通过 AiProvider.extractJSON 统一解析
534
- const aiProvider = this.#chatAgent?.aiProvider;
541
+ const aiProvider = this.#container?.get('aiProvider');
535
542
  if (aiProvider && typeof aiProvider.extractJSON === 'function') {
536
543
  const obj = aiProvider.extractJSON(reply, '{', '}');
537
544
  if (obj && Array.isArray(obj.suggestions)) {
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { join } from 'node:path';
12
- import { PlaceholderConverter } from '../PlaceholderConverter.js';
12
+ import { PlaceholderConverter } from '../../../platform/ios/snippet/PlaceholderConverter.js';
13
13
  import { SnippetCodec } from './SnippetCodec.js';
14
14
 
15
15
  /** AutoSnippet language → VSCode snippet scope */
@@ -12,7 +12,7 @@
12
12
  * 消费方:
13
13
  * - UnifiedValidator.js → 字段完整性检查
14
14
  * - dimension-text.js → SUBMISSION_SCHEMA / REQUIRED_FIELDS_DESCRIPTION
15
- * - ProducerAgent.js → STYLE_GUIDE 字段列表
15
+ * - bootstrap-producer.js → STYLE_GUIDE 字段列表
16
16
  * - MissionBriefingBuilder.js → submissionSpec 字段描述
17
17
  * - lifecycle.js → JSON Schema required 数组
18
18
  * - consolidated.js → 前置校验
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * StyleGuide.js — 项目特写写作指南(唯一权威来源)
3
3
  *
4
- * ProducerAgent.js 提取,供内部 Agent (Producer) 和
4
+ * 供内部 Agent (bootstrap-producer.js) 和
5
5
  * 外部 Agent (MissionBriefing submissionSpec) 共享使用。
6
6
  *
7
7
  * @module shared/StyleGuide
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "3.2.8",
3
+ "version": "3.2.9",
4
4
  "description": "Extract code patterns into a knowledge base for AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "lib/bootstrap.js",
@@ -11,6 +11,7 @@
11
11
  "postinstall": "node scripts/postinstall-safe.js",
12
12
  "prepublishOnly": "npm run build:dashboard && node scripts/build-native-ui.js",
13
13
  "build:native-ui": "swiftc resources/native-ui/main.swift resources/native-ui/combined-window.swift -o resources/native-ui/native-ui -framework AppKit",
14
+ "build:screenshot": "swiftc -O -framework ScreenCaptureKit -framework AppKit resources/native-ui/screenshot.swift -o resources/native-ui/screenshot",
14
15
  "install:cursor-skill": "node scripts/install-cursor-skill.js",
15
16
  "install:cursor-skill:mcp": "node scripts/install-cursor-skill.js --mcp",
16
17
  "install:vscode-copilot": "node scripts/install-vscode-copilot.js",
@@ -116,6 +117,8 @@
116
117
  "resources/native-ui/main.swift",
117
118
  "resources/native-ui/combined-window.swift",
118
119
  "resources/native-ui/native-ui",
120
+ "resources/native-ui/screenshot.swift",
121
+ "resources/native-ui/screenshot",
119
122
  "resources/native-ui/README.md",
120
123
  "resources/openChrome.applescript",
121
124
  "resources/grammars"
@@ -0,0 +1,228 @@
1
+ import Foundation
2
+ import ScreenCaptureKit
3
+ import AppKit
4
+ import CoreGraphics
5
+
6
+ /**
7
+ * AutoSnippet Screenshot Tool
8
+ *
9
+ * 使用 macOS ScreenCaptureKit 原生 API 截取窗口/屏幕画面。
10
+ * 息屏时可用(不依赖显示器输出)。无需 OBS。
11
+ *
12
+ * Usage:
13
+ * screenshot # 截取整个主屏幕
14
+ * screenshot --window "Visual Studio Code" # 截取匹配标题的窗口
15
+ * screenshot --list-windows # 列出所有可截取窗口 (JSON)
16
+ * screenshot --output /tmp/shot.png # 指定输出路径
17
+ * screenshot --format jpeg # 指定格式 (png|jpeg)
18
+ * screenshot --scale 0.5 # 缩放因子
19
+ *
20
+ * Requirements: macOS 12.3+, Screen Recording permission
21
+ */
22
+
23
+ // MARK: - 输出格式
24
+ enum ImageFormat: String {
25
+ case png, jpeg
26
+ }
27
+
28
+ // MARK: - 参数解析
29
+ struct Args {
30
+ var windowTitle: String? = nil
31
+ var listWindows = false
32
+ var outputPath: String? = nil
33
+ var format: ImageFormat = .jpeg
34
+ var scale: CGFloat = 1.0
35
+
36
+ static func parse(_ args: [String]) -> Args {
37
+ var result = Args()
38
+ var i = 1 // skip executable name
39
+ while i < args.count {
40
+ switch args[i] {
41
+ case "--window", "-w":
42
+ i += 1
43
+ if i < args.count { result.windowTitle = args[i] }
44
+ case "--list-windows", "-l":
45
+ result.listWindows = true
46
+ case "--output", "-o":
47
+ i += 1
48
+ if i < args.count { result.outputPath = args[i] }
49
+ case "--format", "-f":
50
+ i += 1
51
+ if i < args.count {
52
+ result.format = ImageFormat(rawValue: args[i].lowercased()) ?? .jpeg
53
+ }
54
+ case "--scale", "-s":
55
+ i += 1
56
+ if i < args.count { result.scale = CGFloat(Double(args[i]) ?? 1.0) }
57
+ case "--help", "-h":
58
+ printUsage()
59
+ exit(0)
60
+ default:
61
+ break
62
+ }
63
+ i += 1
64
+ }
65
+ return result
66
+ }
67
+
68
+ static func printUsage() {
69
+ let usage = """
70
+ AutoSnippet Screenshot Tool (ScreenCaptureKit)
71
+
72
+ Usage:
73
+ screenshot Capture main screen
74
+ screenshot --window "Code" Capture window matching title
75
+ screenshot --list-windows List capturable windows (JSON)
76
+ screenshot --output /path/to/file.png Specify output path
77
+ screenshot --format jpeg|png Image format (default: jpeg)
78
+ screenshot --scale 0.5 Scale factor (default: 1.0)
79
+ """
80
+ fputs(usage + "\n", stderr)
81
+ }
82
+ }
83
+
84
+ // MARK: - 主逻辑
85
+
86
+ @available(macOS 12.3, *)
87
+ func run() async {
88
+ let args = Args.parse(CommandLine.arguments)
89
+
90
+ // 获取可共享内容(窗口/屏幕列表)
91
+ let content: SCShareableContent
92
+ do {
93
+ content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: false)
94
+ } catch {
95
+ fputs("{\"error\":\"ScreenCaptureKit access denied: \\(error.localizedDescription). Grant Screen Recording permission in System Settings.\"}\n", stderr)
96
+ exit(1)
97
+ }
98
+
99
+ // --list-windows: 输出 JSON 窗口列表
100
+ if args.listWindows {
101
+ let windows = content.windows.compactMap { w -> [String: Any]? in
102
+ guard let app = w.owningApplication else { return nil }
103
+ return [
104
+ "windowID": w.windowID,
105
+ "title": w.title ?? "",
106
+ "app": app.applicationName,
107
+ "bundleID": app.bundleIdentifier,
108
+ "width": w.frame.width,
109
+ "height": w.frame.height,
110
+ "onScreen": w.isOnScreen,
111
+ ]
112
+ }
113
+ if let data = try? JSONSerialization.data(withJSONObject: windows, options: [.prettyPrinted, .sortedKeys]),
114
+ let json = String(data: data, encoding: .utf8) {
115
+ print(json)
116
+ }
117
+ exit(0)
118
+ }
119
+
120
+ // 确定截取目标 — 统一使用 desktopIndependentWindow(亮屏/息屏均可用)
121
+ var filter: SCContentFilter
122
+
123
+ // 筛选有效窗口(面积 > 100x100,排除菜单栏等微型窗口)
124
+ let validWindows = content.windows.filter { $0.frame.width > 100 && $0.frame.height > 100 }
125
+
126
+ if let title = args.windowTitle {
127
+ // 模糊匹配窗口标题或 app 名,选面积最大的
128
+ let pattern = title.lowercased()
129
+ let candidates = validWindows.filter { w in
130
+ let wTitle = (w.title ?? "").lowercased()
131
+ let appName = (w.owningApplication?.applicationName ?? "").lowercased()
132
+ return wTitle.contains(pattern) || appName.contains(pattern)
133
+ }
134
+ let matched = candidates.max(by: { $0.frame.width * $0.frame.height < $1.frame.width * $1.frame.height })
135
+
136
+ guard let window = matched else {
137
+ let windowList: String = validWindows.prefix(20).compactMap {
138
+ "\($0.owningApplication?.applicationName ?? "?") - \($0.title ?? "(no title)") [\(Int($0.frame.width))x\(Int($0.frame.height))]"
139
+ }.joined(separator: "\n ")
140
+ fputs("{\"error\":\"No window matching '\(title)'. Available:\\n \(windowList)\"}\n", stderr)
141
+ exit(1)
142
+ }
143
+
144
+ filter = SCContentFilter(desktopIndependentWindow: window)
145
+ } else {
146
+ // 未指定窗口 — 自动选最大窗口
147
+ guard let largest = validWindows.max(by: { $0.frame.width * $0.frame.height < $1.frame.width * $1.frame.height }) else {
148
+ fputs("{\"error\":\"No capturable window available\"}\n", stderr)
149
+ exit(1)
150
+ }
151
+ filter = SCContentFilter(desktopIndependentWindow: largest)
152
+ }
153
+
154
+ // 配置截图参数
155
+ let config = SCStreamConfiguration()
156
+ let sourceRect = filter.contentRect
157
+ config.width = Int(sourceRect.width * args.scale)
158
+ config.height = Int(sourceRect.height * args.scale)
159
+ config.showsCursor = false
160
+ config.capturesAudio = false
161
+
162
+ // 截图
163
+ let image: CGImage
164
+ do {
165
+ image = try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config)
166
+ } catch {
167
+ fputs("{\"error\":\"Screenshot failed: \\(error.localizedDescription)\"}\n", stderr)
168
+ exit(1)
169
+ }
170
+
171
+ // 编码为图片数据
172
+ let bitmapRep = NSBitmapImageRep(cgImage: image)
173
+ let imageData: Data?
174
+ switch args.format {
175
+ case .jpeg:
176
+ imageData = bitmapRep.representation(using: .jpeg, properties: [.compressionFactor: 0.85])
177
+ case .png:
178
+ imageData = bitmapRep.representation(using: .png, properties: [:])
179
+ }
180
+
181
+ guard let data = imageData else {
182
+ fputs("{\"error\":\"Image encoding failed\"}\n", stderr)
183
+ exit(1)
184
+ }
185
+
186
+ // 确定输出路径
187
+ let ext = args.format == .png ? "png" : "jpg"
188
+ let outputPath = args.outputPath ?? NSTemporaryDirectory() + "asd-screenshot-\(Int(Date().timeIntervalSince1970 * 1000)).\(ext)"
189
+
190
+ do {
191
+ try data.write(to: URL(fileURLWithPath: outputPath))
192
+ } catch {
193
+ fputs("{\"error\":\"Write failed: \\(error.localizedDescription)\"}\n", stderr)
194
+ exit(1)
195
+ }
196
+
197
+ // 输出 JSON 结果到 stdout
198
+ let result: [String: Any] = [
199
+ "success": true,
200
+ "path": outputPath,
201
+ "width": image.width,
202
+ "height": image.height,
203
+ "format": args.format.rawValue,
204
+ "bytes": data.count,
205
+ ]
206
+ if let jsonData = try? JSONSerialization.data(withJSONObject: result, options: [.sortedKeys]),
207
+ let json = String(data: jsonData, encoding: .utf8) {
208
+ print(json)
209
+ }
210
+ }
211
+
212
+ // MARK: - Entry Point
213
+
214
+ // 初始化 AppKit 连接到 WindowServer(解决 CGS_REQUIRE_INIT)
215
+ let app = NSApplication.shared
216
+ app.setActivationPolicy(.prohibited)
217
+
218
+ if #available(macOS 12.3, *) {
219
+ let semaphore = DispatchSemaphore(value: 0)
220
+ Task {
221
+ await run()
222
+ semaphore.signal()
223
+ }
224
+ semaphore.wait()
225
+ } else {
226
+ fputs("{\"error\":\"Requires macOS 12.3 or later\"}\n", stderr)
227
+ exit(1)
228
+ }