autosnippet 3.2.8 → 3.2.10
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/bin/cli.js +6 -5
- package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
- package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +23 -26
- package/lib/cli/SetupService.js +1 -1
- package/lib/cli/deploy/FileManifest.js +1 -1
- package/lib/core/AstAnalyzer.js +1 -1
- package/lib/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +29 -5
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +291 -204
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +7 -6
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-internal.js +2 -2
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/middleware/requestLogger.js +1 -0
- package/lib/http/routes/ai.js +240 -35
- package/lib/http/routes/candidates.js +2 -3
- package/lib/http/routes/extract.js +13 -11
- package/lib/http/routes/modules.js +2 -2
- package/lib/http/routes/recipes.js +5 -5
- package/lib/http/routes/remote.js +134 -255
- package/lib/http/routes/violations.js +0 -54
- package/lib/http/utils/sse-sessions.js +1 -1
- package/lib/infrastructure/logging/Logger.js +5 -4
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
- package/lib/injection/ServiceContainer.js +64 -17
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/routes/spm.js +2 -2
- package/lib/service/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +535 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1056 -0
- package/lib/service/agent/AgentState.js +217 -0
- package/lib/service/agent/IntentClassifier.js +331 -0
- package/lib/service/agent/LarkTransport.js +389 -0
- package/lib/service/agent/capabilities.js +409 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +112 -33
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +5 -3
- package/lib/service/agent/core/LoopContext.js +170 -0
- package/lib/service/agent/core/MessageAdapter.js +223 -0
- package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
- package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +15 -98
- package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
- package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
- package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
- package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +85 -135
- package/lib/service/agent/domain/insight-producer.js +270 -0
- package/lib/service/agent/domain/scan-prompts.js +444 -0
- package/lib/service/agent/forced-summary.js +266 -0
- package/lib/service/agent/index.js +91 -0
- package/lib/service/{chat → agent}/memory/ActiveContext.js +29 -1
- package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
- package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
- package/lib/service/{chat → agent}/memory/SessionStore.js +1 -1
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +305 -0
- package/lib/service/agent/strategies.js +756 -0
- package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
- package/lib/service/agent/tools/ai-analysis.js +75 -0
- package/lib/service/{chat → agent}/tools/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +27 -21
- package/lib/service/{chat → agent}/tools/infrastructure.js +1 -1
- package/lib/service/agent/tools/knowledge-graph.js +112 -0
- package/lib/service/agent/tools/scan-recipe.js +189 -0
- package/lib/service/agent/tools/system-interaction.js +476 -0
- package/lib/service/automation/DirectiveDetector.js +0 -1
- package/lib/service/automation/FileWatcher.js +0 -8
- package/lib/service/automation/handlers/CreateHandler.js +7 -3
- package/lib/service/automation/handlers/DraftHandler.js +7 -6
- package/lib/service/module/ModuleService.js +40 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/shared/FieldSpec.js +1 -1
- package/lib/shared/StyleGuide.js +1 -1
- package/package.json +4 -1
- package/resources/native-ui/screenshot.swift +228 -0
- package/dashboard/dist/assets/index-D5jiDBQG.css +0 -1
- package/dashboard/dist/assets/index-e5OKj-Ni.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -750
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
- package/lib/http/routes/spm.js +0 -5
- package/lib/infrastructure/external/XcodeAutomation.js +0 -15
- package/lib/service/chat/ChatAgent.js +0 -1602
- package/lib/service/chat/Memory.js +0 -161
- package/lib/service/chat/ProducerAgent.js +0 -431
- package/lib/service/chat/ReasoningTrace.js +0 -523
- package/lib/service/chat/TaskPipeline.js +0 -357
- package/lib/service/chat/WorkingMemory.js +0 -359
- package/lib/service/chat/memory/PersistentMemory.js +0 -450
- package/lib/service/chat/tools/ai-analysis.js +0 -267
- package/lib/service/chat/tools/knowledge-graph.js +0 -234
- package/lib/service/chat/tools.js +0 -18
- package/lib/service/snippet/PlaceholderConverter.js +0 -5
- package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
- /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
- /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
- /package/lib/service/{chat → agent}/tools/ast-graph.js +0 -0
- /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
- /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
- /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('../../../
|
|
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
|
|
141
|
-
const aiResult = await
|
|
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 摘要回退(通过
|
|
56
|
+
// AI 摘要回退(通过 Agent 统一管道)
|
|
57
57
|
try {
|
|
58
58
|
const { getServiceContainer } = await import('../../../injection/ServiceContainer.js');
|
|
59
59
|
const container = getServiceContainer();
|
|
60
|
-
const
|
|
60
|
+
const agentFactory = container.get('agentFactory');
|
|
61
61
|
const lang = LanguageService.inferLang(relativePath) || 'unknown';
|
|
62
|
-
const result = await
|
|
63
|
-
|
|
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
|
-
/*
|
|
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
|
-
#
|
|
92
|
-
#
|
|
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.
|
|
102
|
-
* @param {object} [options.
|
|
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.#
|
|
113
|
-
this.#
|
|
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.#
|
|
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
|
-
// 检查是否因为
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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.#
|
|
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
|
-
|
|
770
|
+
if (!this.#agentFactory) {
|
|
771
|
+
return [];
|
|
772
|
+
}
|
|
766
773
|
|
|
767
774
|
try {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
-
* 通过
|
|
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 →
|
|
14
|
-
* →
|
|
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 驱动 — 所有分析决策由
|
|
30
|
+
* 4. AI 驱动 — 所有分析决策由 AgentRuntime 完成
|
|
31
31
|
* 5. 自适应 — AI 根据信号密度动态调整执行频率
|
|
32
32
|
*
|
|
33
33
|
* 前提条件:
|
|
34
|
-
* 需要可用的 AI Provider
|
|
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
|
-
#
|
|
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.
|
|
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
|
-
|
|
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.#
|
|
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
|
-
|
|
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. 调用
|
|
221
|
-
this.#logger.debug('[SignalCollector] invoking
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
* 从
|
|
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 —
|
|
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.#
|
|
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 '
|
|
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 */
|
package/lib/shared/FieldSpec.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 消费方:
|
|
13
13
|
* - UnifiedValidator.js → 字段完整性检查
|
|
14
14
|
* - dimension-text.js → SUBMISSION_SCHEMA / REQUIRED_FIELDS_DESCRIPTION
|
|
15
|
-
* -
|
|
15
|
+
* - bootstrap-producer.js → STYLE_GUIDE 字段列表
|
|
16
16
|
* - MissionBriefingBuilder.js → submissionSpec 字段描述
|
|
17
17
|
* - lifecycle.js → JSON Schema required 数组
|
|
18
18
|
* - consolidated.js → 前置校验
|
package/lib/shared/StyleGuide.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autosnippet",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.10",
|
|
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
|
+
}
|