autosnippet 3.3.9 → 3.4.1
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/README.md +44 -19
- package/config/default.json +1 -1
- package/dashboard/dist/assets/{index-DEU4tJtP.js → index-BX6r2fiy.js} +40 -40
- package/dashboard/dist/assets/index-BvZcGN02.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/lib/agent/AgentRuntime.js +13 -1
- package/dist/lib/agent/AgentRuntimeTypes.d.ts +2 -0
- package/dist/lib/agent/PipelineStrategy.js +32 -2
- package/dist/lib/agent/context/ContextWindow.d.ts +2 -1
- package/dist/lib/agent/context/ContextWindow.js +18 -4
- package/dist/lib/agent/context/ExplorationTracker.js +6 -1
- package/dist/lib/agent/context/exploration/ExplorationStrategies.js +2 -1
- package/dist/lib/agent/core/LoopContext.d.ts +3 -0
- package/dist/lib/agent/core/LoopContext.js +3 -0
- package/dist/lib/agent/domain/EpisodicConsolidator.d.ts +5 -0
- package/dist/lib/agent/domain/EpisodicConsolidator.js +60 -5
- package/dist/lib/agent/domain/insight-analyst.d.ts +16 -0
- package/dist/lib/agent/domain/insight-analyst.js +38 -0
- package/dist/lib/agent/domain/insight-gate.js +12 -0
- package/dist/lib/agent/memory/MemoryConsolidator.js +17 -0
- package/dist/lib/core/AstAnalyzer.js +0 -1
- package/dist/lib/core/ast/lang-dart.js +0 -1
- package/dist/lib/core/ast/lang-go.js +0 -1
- package/dist/lib/core/ast/lang-java.js +0 -1
- package/dist/lib/core/ast/lang-javascript.js +0 -1
- package/dist/lib/core/ast/lang-objc.js +0 -1
- package/dist/lib/core/ast/lang-python.js +0 -1
- package/dist/lib/core/ast/lang-rust.js +0 -1
- package/dist/lib/core/ast/lang-swift.js +0 -1
- package/dist/lib/core/ast/lang-typescript.js +0 -1
- package/dist/lib/domain/dimension/DimensionRegistry.d.ts +6 -4
- package/dist/lib/domain/dimension/DimensionRegistry.js +19 -23
- package/dist/lib/external/ai/AiFactory.d.ts +14 -0
- package/dist/lib/external/ai/AiFactory.js +33 -1
- package/dist/lib/external/ai/AiProvider.d.ts +2 -0
- package/dist/lib/external/ai/AiProvider.js +4 -0
- package/dist/lib/external/ai/providers/ClaudeProvider.d.ts +1 -1
- package/dist/lib/external/ai/providers/ClaudeProvider.js +6 -2
- package/dist/lib/external/ai/providers/GoogleGeminiProvider.d.ts +1 -1
- package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +13 -5
- package/dist/lib/external/ai/providers/OpenAiProvider.d.ts +1 -1
- package/dist/lib/external/ai/providers/OpenAiProvider.js +8 -4
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +10 -2
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.d.ts +1 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +39 -5
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +2 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +4 -0
- package/dist/lib/external/mcp/handlers/guard.js +11 -6
- package/dist/lib/http/routes/ai.js +20 -3
- package/dist/lib/infrastructure/vector/IndexingPipeline.js +6 -1
- package/dist/lib/injection/modules/AiModule.js +22 -1
- package/dist/lib/service/bootstrap/BootstrapTaskManager.d.ts +7 -0
- package/dist/lib/service/bootstrap/BootstrapTaskManager.js +17 -0
- package/dist/lib/service/guard/ComplianceReporter.js +5 -1
- package/dist/lib/service/guard/GuardCheckEngine.d.ts +12 -1
- package/dist/lib/service/guard/GuardCheckEngine.js +36 -4
- package/dist/lib/service/guard/GuardCodeChecks.js +27 -9
- package/dist/lib/service/guard/SourceFileCollector.d.ts +3 -2
- package/dist/lib/service/guard/SourceFileCollector.js +3 -3
- package/dist/lib/service/search/SearchEngine.js +165 -61
- package/dist/lib/service/task/PrimeSearchPipeline.js +17 -2
- package/dist/lib/service/vector/VectorService.js +10 -1
- package/dist/lib/shared/LanguageService.d.ts +12 -0
- package/dist/lib/shared/LanguageService.js +85 -0
- package/dist/lib/shared/schemas/http-requests.d.ts +4 -0
- package/dist/lib/shared/schemas/http-requests.js +4 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/index-DHJ1Dj7u.css +0 -1
|
@@ -42,9 +42,11 @@ export function createProvider(options = {}) {
|
|
|
42
42
|
break;
|
|
43
43
|
case 'ollama':
|
|
44
44
|
config.name = 'ollama';
|
|
45
|
-
config.baseUrl =
|
|
45
|
+
config.baseUrl =
|
|
46
|
+
config.baseUrl || process.env.ASD_OLLAMA_BASE_URL || 'http://localhost:11434/v1';
|
|
46
47
|
config.apiKey = config.apiKey || 'ollama';
|
|
47
48
|
config.model = config.model || 'llama3';
|
|
49
|
+
config.embedModel = config.embedModel || 'qwen3-embedding:0.6b';
|
|
48
50
|
break;
|
|
49
51
|
default:
|
|
50
52
|
break;
|
|
@@ -172,10 +174,37 @@ export async function getProviderWithFallback() {
|
|
|
172
174
|
}
|
|
173
175
|
return primary;
|
|
174
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* 创建独立的 Embedding Provider
|
|
179
|
+
*
|
|
180
|
+
* 当 ASD_EMBED_PROVIDER 被设置时,创建一个专用于 embedding 的 provider 实例,
|
|
181
|
+
* 使 embedding 和 LLM 生成可以使用不同的提供商/模型。
|
|
182
|
+
*
|
|
183
|
+
* 典型场景:LLM 用 Google Gemini,Embedding 用本地 Ollama + qwen3-embedding
|
|
184
|
+
*
|
|
185
|
+
* @returns 独立的 embed provider,或 null(未配置时)
|
|
186
|
+
*/
|
|
187
|
+
export function createEmbedProvider() {
|
|
188
|
+
const embedProviderName = process.env.ASD_EMBED_PROVIDER;
|
|
189
|
+
if (!embedProviderName) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const logger = Logger.getInstance();
|
|
193
|
+
logger.info(`[AiFactory] Creating dedicated embed provider: ${embedProviderName}`);
|
|
194
|
+
return createProvider({
|
|
195
|
+
provider: embedProviderName,
|
|
196
|
+
model: process.env.ASD_EMBED_MODEL || undefined,
|
|
197
|
+
baseUrl: process.env.ASD_EMBED_BASE_URL || undefined,
|
|
198
|
+
apiKey: process.env.ASD_EMBED_API_KEY || undefined,
|
|
199
|
+
embedModel: process.env.ASD_EMBED_MODEL || undefined,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
175
202
|
/** 获取当前 AI 配置信息(同步,用于 UI 展示) */
|
|
176
203
|
export function getAiConfigInfo() {
|
|
177
204
|
const provider = process.env.ASD_AI_PROVIDER || 'auto';
|
|
178
205
|
const model = process.env.ASD_AI_MODEL || '';
|
|
206
|
+
const embedProvider = process.env.ASD_EMBED_PROVIDER || '';
|
|
207
|
+
const embedModel = process.env.ASD_EMBED_MODEL || '';
|
|
179
208
|
const hasGoogleKey = !!process.env.ASD_GOOGLE_API_KEY;
|
|
180
209
|
const hasOpenAiKey = !!process.env.ASD_OPENAI_API_KEY;
|
|
181
210
|
const hasClaudeKey = !!process.env.ASD_CLAUDE_API_KEY;
|
|
@@ -183,6 +212,8 @@ export function getAiConfigInfo() {
|
|
|
183
212
|
return {
|
|
184
213
|
provider,
|
|
185
214
|
model,
|
|
215
|
+
embedProvider,
|
|
216
|
+
embedModel,
|
|
186
217
|
hasKey: hasGoogleKey || hasOpenAiKey || hasClaudeKey || hasDeepSeekKey,
|
|
187
218
|
keys: {
|
|
188
219
|
google: hasGoogleKey,
|
|
@@ -200,6 +231,7 @@ export { MockProvider } from './providers/MockProvider.js';
|
|
|
200
231
|
export { OpenAiProvider } from './providers/OpenAiProvider.js';
|
|
201
232
|
export default {
|
|
202
233
|
createProvider,
|
|
234
|
+
createEmbedProvider,
|
|
203
235
|
autoDetectProvider,
|
|
204
236
|
getAiConfigInfo,
|
|
205
237
|
getProviderWithFallback,
|
|
@@ -684,6 +684,10 @@ ${items}`;
|
|
|
684
684
|
}
|
|
685
685
|
catch (err) {
|
|
686
686
|
const e = err;
|
|
687
|
+
// AbortError — 外部主动中止(如 PipelineStrategy hard timeout),不重试直接抛出
|
|
688
|
+
if (e.name === 'AbortError' || e.cause?.name === 'AbortError') {
|
|
689
|
+
throw e;
|
|
690
|
+
}
|
|
687
691
|
// ── 综合判断是否为可重试的网络/服务端错误 ──
|
|
688
692
|
const causeCode = e.cause?.code || '';
|
|
689
693
|
// 网络级错误:无 HTTP status,底层连接失败
|
|
@@ -32,6 +32,6 @@ export declare class ClaudeProvider extends AiProvider {
|
|
|
32
32
|
summarize(code: string): Promise<{}>;
|
|
33
33
|
embed(_text: string | string[]): Promise<never[]>;
|
|
34
34
|
supportsEmbedding(): boolean;
|
|
35
|
-
_post(url: string, body: Record<string, unknown
|
|
35
|
+
_post(url: string, body: Record<string, unknown>, externalSignal?: AbortSignal): Promise<ApiResponse>;
|
|
36
36
|
}
|
|
37
37
|
export default ClaudeProvider;
|
|
@@ -100,7 +100,7 @@ export class ClaudeProvider extends AiProvider {
|
|
|
100
100
|
body.tool_choice = { type: 'auto' };
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
const data = await this._post(`${CLAUDE_BASE}/messages`, body);
|
|
103
|
+
const data = await this._post(`${CLAUDE_BASE}/messages`, body, opts.abortSignal);
|
|
104
104
|
return this.#parseToolResponse(data);
|
|
105
105
|
}
|
|
106
106
|
// ─── 内部转换方法 ──────────────────────
|
|
@@ -241,7 +241,7 @@ export class ClaudeProvider extends AiProvider {
|
|
|
241
241
|
supportsEmbedding() {
|
|
242
242
|
return false;
|
|
243
243
|
}
|
|
244
|
-
async _post(url, body) {
|
|
244
|
+
async _post(url, body, externalSignal) {
|
|
245
245
|
if (!this.apiKey) {
|
|
246
246
|
const err = new Error('Claude API Key 未配置。请在 .env 中设置 ASD_CLAUDE_API_KEY,或运行 asd setup 完成配置。');
|
|
247
247
|
err.code = 'API_KEY_MISSING';
|
|
@@ -249,6 +249,9 @@ export class ClaudeProvider extends AiProvider {
|
|
|
249
249
|
}
|
|
250
250
|
const controller = new AbortController();
|
|
251
251
|
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
252
|
+
// 外部中止信号 → 联动本地 controller
|
|
253
|
+
const onExternalAbort = () => controller.abort();
|
|
254
|
+
externalSignal?.addEventListener('abort', onExternalAbort, { once: true });
|
|
252
255
|
try {
|
|
253
256
|
const res = await this._fetch(url, {
|
|
254
257
|
method: 'POST',
|
|
@@ -269,6 +272,7 @@ export class ClaudeProvider extends AiProvider {
|
|
|
269
272
|
}
|
|
270
273
|
finally {
|
|
271
274
|
clearTimeout(timer);
|
|
275
|
+
externalSignal?.removeEventListener('abort', onExternalAbort);
|
|
272
276
|
}
|
|
273
277
|
}
|
|
274
278
|
}
|
|
@@ -34,6 +34,6 @@ export declare class GoogleGeminiProvider extends AiProvider {
|
|
|
34
34
|
*/
|
|
35
35
|
chatWithStructuredOutput(prompt: string, opts?: StructuredOutputOptions): Promise<any>;
|
|
36
36
|
embed(text: string | string[]): Promise<number[] | number[][]>;
|
|
37
|
-
_post(url: string, body: Record<string, unknown
|
|
37
|
+
_post(url: string, body: Record<string, unknown>, externalSignal?: AbortSignal): Promise<ApiResponse>;
|
|
38
38
|
}
|
|
39
39
|
export default GoogleGeminiProvider;
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
import Logger from '#infra/logging/Logger.js';
|
|
10
10
|
import { AiProvider, } from '../AiProvider.js';
|
|
11
11
|
const GEMINI_BASE = 'https://generativelanguage.googleapis.com/v1beta';
|
|
12
|
-
const
|
|
12
|
+
const DEFAULT_EMBED_MODEL = 'models/gemini-embedding-001';
|
|
13
13
|
export class GoogleGeminiProvider extends AiProvider {
|
|
14
|
+
#embedModel;
|
|
14
15
|
constructor(config = {}) {
|
|
15
16
|
super({
|
|
16
17
|
...config,
|
|
@@ -20,6 +21,9 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
20
21
|
this.name = 'google-gemini';
|
|
21
22
|
this.model = config.model || 'gemini-3-flash-preview';
|
|
22
23
|
this.apiKey = config.apiKey || process.env.ASD_GOOGLE_API_KEY || '';
|
|
24
|
+
this.#embedModel = config.embedModel
|
|
25
|
+
? `models/${config.embedModel.replace(/^models\//, '')}`
|
|
26
|
+
: DEFAULT_EMBED_MODEL;
|
|
23
27
|
this.logger = Logger.getInstance();
|
|
24
28
|
}
|
|
25
29
|
/** 是否支持原生结构化函数调用 */
|
|
@@ -108,7 +112,7 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
108
112
|
body.systemInstruction = { parts: [{ text: systemPrompt }] };
|
|
109
113
|
}
|
|
110
114
|
const url = `${GEMINI_BASE}/models/${this.model}:generateContent?key=${this.apiKey}`;
|
|
111
|
-
const data = await this._post(url, body);
|
|
115
|
+
const data = await this._post(url, body, opts.abortSignal);
|
|
112
116
|
return this.#parseToolResponse(data);
|
|
113
117
|
});
|
|
114
118
|
}
|
|
@@ -345,10 +349,10 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
345
349
|
for (let i = 0; i < texts.length; i += 100) {
|
|
346
350
|
const batch = texts.slice(i, i + 100);
|
|
347
351
|
const requests = batch.map((t) => ({
|
|
348
|
-
model:
|
|
352
|
+
model: this.#embedModel,
|
|
349
353
|
content: { parts: [{ text: t.slice(0, 8000) }] },
|
|
350
354
|
}));
|
|
351
|
-
const url = `${GEMINI_BASE}/${
|
|
355
|
+
const url = `${GEMINI_BASE}/${this.#embedModel}:batchEmbedContents?key=${this.apiKey}`;
|
|
352
356
|
const data = await this._post(url, { requests });
|
|
353
357
|
if (data?.embeddings) {
|
|
354
358
|
results.push(...data.embeddings.map((e) => e.values));
|
|
@@ -356,7 +360,7 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
356
360
|
}
|
|
357
361
|
return Array.isArray(text) ? results : results[0] || [];
|
|
358
362
|
}
|
|
359
|
-
async _post(url, body) {
|
|
363
|
+
async _post(url, body, externalSignal) {
|
|
360
364
|
if (!this.apiKey) {
|
|
361
365
|
const err = new Error('Google Gemini API Key 未配置。请在 .env 中设置 ASD_GOOGLE_API_KEY,或运行 asd setup 完成配置。');
|
|
362
366
|
err.code = 'API_KEY_MISSING';
|
|
@@ -364,6 +368,9 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
364
368
|
}
|
|
365
369
|
const controller = new AbortController();
|
|
366
370
|
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
371
|
+
// 外部中止信号 → 联动本地 controller
|
|
372
|
+
const onExternalAbort = () => controller.abort();
|
|
373
|
+
externalSignal?.addEventListener('abort', onExternalAbort, { once: true });
|
|
367
374
|
try {
|
|
368
375
|
const res = await this._fetch(url, {
|
|
369
376
|
method: 'POST',
|
|
@@ -401,6 +408,7 @@ export class GoogleGeminiProvider extends AiProvider {
|
|
|
401
408
|
}
|
|
402
409
|
finally {
|
|
403
410
|
clearTimeout(timer);
|
|
411
|
+
externalSignal?.removeEventListener('abort', onExternalAbort);
|
|
404
412
|
}
|
|
405
413
|
}
|
|
406
414
|
}
|
|
@@ -37,6 +37,6 @@ export declare class OpenAiProvider extends AiProvider {
|
|
|
37
37
|
*/
|
|
38
38
|
chatWithStructuredOutput(prompt: string, opts?: StructuredOutputOptions): Promise<any>;
|
|
39
39
|
embed(text: string | string[]): Promise<any>;
|
|
40
|
-
_post(url: string, body: Record<string, unknown
|
|
40
|
+
_post(url: string, body: Record<string, unknown>, externalSignal?: AbortSignal): Promise<ApiResponse>;
|
|
41
41
|
}
|
|
42
42
|
export default OpenAiProvider;
|
|
@@ -14,10 +14,10 @@ export class OpenAiProvider extends AiProvider {
|
|
|
14
14
|
constructor(config = {}) {
|
|
15
15
|
super(config);
|
|
16
16
|
this.name = config.name || 'openai';
|
|
17
|
-
this.model = config.model || 'gpt-
|
|
17
|
+
this.model = config.model || 'gpt-5.4-mini';
|
|
18
18
|
this.apiKey = config.apiKey || process.env.ASD_OPENAI_API_KEY || '';
|
|
19
19
|
this.baseUrl = config.baseUrl || OPENAI_BASE;
|
|
20
|
-
this.embedModel = config.embedModel || 'text-embedding-3-small';
|
|
20
|
+
this.embedModel = config.embedModel || process.env.ASD_EMBED_MODEL || 'text-embedding-3-small';
|
|
21
21
|
this.logger = Logger.getInstance();
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
@@ -129,7 +129,7 @@ export class OpenAiProvider extends AiProvider {
|
|
|
129
129
|
else {
|
|
130
130
|
body.tool_choice = 'auto';
|
|
131
131
|
}
|
|
132
|
-
const data = await this._post(`${this.baseUrl}/chat/completions`, body);
|
|
132
|
+
const data = await this._post(`${this.baseUrl}/chat/completions`, body, opts.abortSignal);
|
|
133
133
|
return this.#parseToolResponse(data);
|
|
134
134
|
});
|
|
135
135
|
}
|
|
@@ -251,7 +251,7 @@ export class OpenAiProvider extends AiProvider {
|
|
|
251
251
|
return Array.isArray(text) ? texts.map(() => []) : [];
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
|
-
async _post(url, body) {
|
|
254
|
+
async _post(url, body, externalSignal) {
|
|
255
255
|
// Ollama 使用固定 dummy key,不需要校验
|
|
256
256
|
if (!this.apiKey && this.name !== 'ollama') {
|
|
257
257
|
const envKey = this.name === 'deepseek' ? 'ASD_DEEPSEEK_API_KEY' : 'ASD_OPENAI_API_KEY';
|
|
@@ -261,6 +261,9 @@ export class OpenAiProvider extends AiProvider {
|
|
|
261
261
|
}
|
|
262
262
|
const controller = new AbortController();
|
|
263
263
|
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
264
|
+
// 外部中止信号 → 联动本地 controller
|
|
265
|
+
const onExternalAbort = () => controller.abort();
|
|
266
|
+
externalSignal?.addEventListener('abort', onExternalAbort, { once: true });
|
|
264
267
|
try {
|
|
265
268
|
const res = await this._fetch(url, {
|
|
266
269
|
method: 'POST',
|
|
@@ -280,6 +283,7 @@ export class OpenAiProvider extends AiProvider {
|
|
|
280
283
|
}
|
|
281
284
|
finally {
|
|
282
285
|
clearTimeout(timer);
|
|
286
|
+
externalSignal?.removeEventListener('abort', onExternalAbort);
|
|
283
287
|
}
|
|
284
288
|
}
|
|
285
289
|
}
|
|
@@ -760,11 +760,19 @@ function buildExecutionPlan(activeDimensions) {
|
|
|
760
760
|
const scheduler = new TierScheduler();
|
|
761
761
|
const tiers = scheduler.getTiers();
|
|
762
762
|
const activeDimIds = new Set(activeDimensions.map((d) => d.id));
|
|
763
|
-
const tierLabels = [
|
|
763
|
+
const tierLabels = [
|
|
764
|
+
'基础数据层',
|
|
765
|
+
'规范 + 设计 + 网络',
|
|
766
|
+
'核心质量',
|
|
767
|
+
'领域专项',
|
|
768
|
+
'终端优化 + 总结',
|
|
769
|
+
];
|
|
764
770
|
const tierNotes = [
|
|
765
771
|
'这些维度相互独立,可以任意顺序分析。产出的上下文将帮助后续维度。',
|
|
766
772
|
'建议利用 Tier 1 中了解到的项目结构和代码特征。',
|
|
767
|
-
'
|
|
773
|
+
'利用前两层建立的架构和规范上下文深入分析。',
|
|
774
|
+
'各维度相对独立,可充分利用并行能力。',
|
|
775
|
+
'agent-guidelines 应综合前序所有维度的发现。',
|
|
768
776
|
];
|
|
769
777
|
const plan = tiers
|
|
770
778
|
.map((tierDimIds, index) => {
|
|
@@ -68,6 +68,7 @@ interface BootstrapFileEntry {
|
|
|
68
68
|
/** Task manager minimal shape */
|
|
69
69
|
interface TaskManagerLike {
|
|
70
70
|
isSessionValid(sessionId: string): boolean;
|
|
71
|
+
getSessionAbortSignal?(): AbortSignal | null;
|
|
71
72
|
emitProgress?(event: string, data: Record<string, unknown>): void;
|
|
72
73
|
[key: string]: unknown;
|
|
73
74
|
}
|
|
@@ -18,7 +18,7 @@ import path from 'node:path';
|
|
|
18
18
|
import { AgentMessage } from '#agent/AgentMessage.js';
|
|
19
19
|
import { ExplorationTracker } from '#agent/context/ExplorationTracker.js';
|
|
20
20
|
import { EpisodicConsolidator } from '#agent/domain/EpisodicConsolidator.js';
|
|
21
|
-
import {
|
|
21
|
+
import { computeAnalystBudget } from '#agent/domain/insight-analyst.js';
|
|
22
22
|
import { MemoryCoordinator } from '#agent/memory/MemoryCoordinator.js';
|
|
23
23
|
import { MemoryEmbeddingStore } from '#agent/memory/MemoryEmbeddingStore.js';
|
|
24
24
|
import { PersistentMemory } from '#agent/memory/PersistentMemory.js';
|
|
@@ -113,6 +113,7 @@ export async function fillDimensionsV3(view, dimensions) {
|
|
|
113
113
|
/* not available */
|
|
114
114
|
}
|
|
115
115
|
const sessionId = view.bootstrapSession?.id ?? '';
|
|
116
|
+
const sessionAbortSignal = taskManager?.getSessionAbortSignal?.() ?? null;
|
|
116
117
|
const isIncremental = incrementalPlan?.canIncremental && incrementalPlan?.mode === 'incremental';
|
|
117
118
|
const emitter = new BootstrapEventEmitter(ctx.container);
|
|
118
119
|
logger.info(`[Insight-v3] ═══ fillDimensionsV3 entered — ${isIncremental ? 'INCREMENTAL' : 'FULL'} pipeline`);
|
|
@@ -600,9 +601,9 @@ export async function fillDimensionsV3(view, dimensions) {
|
|
|
600
601
|
// ── 引擎增强参数 (PipelineStrategy → reactLoop 透传) ──
|
|
601
602
|
contextWindow: agentFactory?.createContextWindow({ isSystem: true }),
|
|
602
603
|
// B1 fix: 分析阶段使用 analyst 策略 (SCAN→EXPLORE→VERIFY→SUMMARIZE)
|
|
603
|
-
//
|
|
604
|
-
//
|
|
605
|
-
tracker: ExplorationTracker.resolve({ source: 'system', strategy: 'analyst' },
|
|
604
|
+
// B3 fix: 透传完整 ANALYST_BUDGET
|
|
605
|
+
// B4 fix: 自适应预算 — 根据项目文件数缩放 maxIterations (24~40)
|
|
606
|
+
tracker: ExplorationTracker.resolve({ source: 'system', strategy: 'analyst' }, computeAnalystBudget(projectInfo.fileCount || 0)),
|
|
606
607
|
trace: memoryCoordinator.getActiveContext(analystScopeId),
|
|
607
608
|
memoryCoordinator,
|
|
608
609
|
sharedState: {
|
|
@@ -623,7 +624,7 @@ export async function fillDimensionsV3(view, dimensions) {
|
|
|
623
624
|
// 外层超时 = 安全网 (各阶段已有独立超时: Analyst 300s + Producer 180s + 硬缓冲 60s)
|
|
624
625
|
const outerTimeoutMs = 3_600_000; // 1 小时——维度分析本身耗时长
|
|
625
626
|
const runResult = await Promise.race([
|
|
626
|
-
runtime.execute(message, { strategyContext }),
|
|
627
|
+
runtime.execute(message, { strategyContext, abortSignal: sessionAbortSignal }),
|
|
627
628
|
new Promise((_, reject) => setTimeout(() => reject(new Error(`Bootstrap runtime timeout for "${dimId}"`)), outerTimeoutMs)),
|
|
628
629
|
]);
|
|
629
630
|
// ── 提取结果 ──
|
|
@@ -826,6 +827,7 @@ export async function fillDimensionsV3(view, dimensions) {
|
|
|
826
827
|
source: 'enhanced-pipeline-strategy',
|
|
827
828
|
});
|
|
828
829
|
const dimTokenUsage = combinedTokenUsage;
|
|
830
|
+
const qualityScores = artifact.qualityReport;
|
|
829
831
|
const dimResult = {
|
|
830
832
|
candidateCount: producerResult.candidateCount,
|
|
831
833
|
rejectedCount: producerResult.rejectedCount || 0,
|
|
@@ -836,6 +838,13 @@ export async function fillDimensionsV3(view, dimensions) {
|
|
|
836
838
|
tokenUsage: dimTokenUsage,
|
|
837
839
|
analysisText: analysisReport.analysisText,
|
|
838
840
|
referencedFilesList: analysisReport.referencedFiles || [],
|
|
841
|
+
qualityGate: qualityScores
|
|
842
|
+
? {
|
|
843
|
+
totalScore: qualityScores.totalScore,
|
|
844
|
+
scores: qualityScores.scores,
|
|
845
|
+
action: gateResult?.action || (runResult?.degraded ? 'degrade' : 'pass'),
|
|
846
|
+
}
|
|
847
|
+
: null,
|
|
839
848
|
};
|
|
840
849
|
dimensionStats[dimId] = dimResult;
|
|
841
850
|
// P3: 保存 checkpoint — 仅当有实质分析内容时(避免 degraded/空结果污染后续 run)
|
|
@@ -1061,6 +1070,30 @@ export async function fillDimensionsV3(view, dimensions) {
|
|
|
1061
1070
|
`~${consolidationResult.total.updated} UPDATE, ` +
|
|
1062
1071
|
`⊕${consolidationResult.total.merged} MERGE | ` +
|
|
1063
1072
|
`Total: ${smStats.total} memories (avg importance: ${smStats.avgImportance})`);
|
|
1073
|
+
// 按类型和来源分布
|
|
1074
|
+
logger.info(`[Insight-v3] Memory by type: ${Object.entries(smStats.byType)
|
|
1075
|
+
.map(([t, n]) => `${t}=${n}`)
|
|
1076
|
+
.join(', ')} | ` +
|
|
1077
|
+
`by source: ${Object.entries(smStats.bySource)
|
|
1078
|
+
.map(([s, n]) => `${s}=${n}`)
|
|
1079
|
+
.join(', ')}`);
|
|
1080
|
+
// 蒸馏管线详情 (来自 EpisodicConsolidator 增强返回值)
|
|
1081
|
+
const cr = consolidationResult;
|
|
1082
|
+
if (cr.perDimension) {
|
|
1083
|
+
const perDim = cr.perDimension;
|
|
1084
|
+
const topDims = Object.entries(perDim)
|
|
1085
|
+
.sort(([, a], [, b]) => b - a)
|
|
1086
|
+
.slice(0, 6);
|
|
1087
|
+
logger.info(`[Insight-v3] Memory per-dimension (top ${topDims.length}): ${topDims.map(([d, n]) => `${d}=${n}`).join(', ')}`);
|
|
1088
|
+
}
|
|
1089
|
+
if (cr.importanceDistribution) {
|
|
1090
|
+
const hist = cr.importanceDistribution;
|
|
1091
|
+
const histStr = Object.entries(hist)
|
|
1092
|
+
.sort(([a], [b]) => Number(a) - Number(b))
|
|
1093
|
+
.map(([k, v]) => `imp${k}=${v}`)
|
|
1094
|
+
.join(' ');
|
|
1095
|
+
logger.info(`[Insight-v3] Importance histogram: ${histStr}`);
|
|
1096
|
+
}
|
|
1064
1097
|
}
|
|
1065
1098
|
else {
|
|
1066
1099
|
logger.warn('[Insight-v3] Database not available — skipping Semantic Memory consolidation');
|
|
@@ -1163,6 +1196,7 @@ export async function fillDimensionsV3(view, dimensions) {
|
|
|
1163
1196
|
durationMs: stat.durationMs || 0,
|
|
1164
1197
|
toolCallCount: stat.toolCallCount || 0,
|
|
1165
1198
|
tokenUsage: stat.tokenUsage || { input: 0, output: 0 },
|
|
1199
|
+
qualityGate: stat.qualityGate || null,
|
|
1166
1200
|
};
|
|
1167
1201
|
}
|
|
1168
1202
|
// Phase E: 附加 Code Entity Graph 拓扑到报告
|
|
@@ -39,6 +39,8 @@ interface BootstrapFileEntry {
|
|
|
39
39
|
relativePath: string;
|
|
40
40
|
content: string;
|
|
41
41
|
targetName: string;
|
|
42
|
+
/** Whether this file belongs to a test target or matches test file naming patterns */
|
|
43
|
+
isTest: boolean;
|
|
42
44
|
}
|
|
43
45
|
/** Target item — either a plain string or an object with metadata */
|
|
44
46
|
type TargetItem = string | {
|
|
@@ -67,6 +67,7 @@ export async function runPhase1_FileCollection(projectRoot, logger, options = {}
|
|
|
67
67
|
const seenPaths = new Set();
|
|
68
68
|
const allFiles = [];
|
|
69
69
|
for (const t of allTargets) {
|
|
70
|
+
const isTestTarget = typeof t === 'object' && /^test/i.test(t.type || '');
|
|
70
71
|
try {
|
|
71
72
|
const fileList = await discoverer.getTargetFiles(t);
|
|
72
73
|
for (const f of fileList) {
|
|
@@ -86,6 +87,7 @@ export async function runPhase1_FileCollection(projectRoot, logger, options = {}
|
|
|
86
87
|
relativePath: f.relativePath || path.basename(fp),
|
|
87
88
|
content,
|
|
88
89
|
targetName: typeof t === 'string' ? t : t.name,
|
|
90
|
+
isTest: isTestTarget || LanguageService.isTestFile(fp),
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
catch {
|
|
@@ -382,6 +384,7 @@ export async function runPhase3_GuardAudit(allFiles, container, logger, options
|
|
|
382
384
|
const guardFiles = allFiles.map((f) => ({
|
|
383
385
|
path: f.path,
|
|
384
386
|
content: f.content,
|
|
387
|
+
isTest: f.isTest,
|
|
385
388
|
}));
|
|
386
389
|
guardAudit = guardEngine.auditFiles(guardFiles, { scope: 'project' });
|
|
387
390
|
// 写入 ViolationsStore
|
|
@@ -484,6 +487,7 @@ export async function runPhase4_DimensionResolve(params) {
|
|
|
484
487
|
const guardFiles = allFiles.map((f) => ({
|
|
485
488
|
path: f.path,
|
|
486
489
|
content: f.content,
|
|
490
|
+
isTest: f.isTest,
|
|
487
491
|
}));
|
|
488
492
|
guardAudit = guardEngine.auditFiles(guardFiles, { scope: 'project' });
|
|
489
493
|
logger.info(`[Bootstrap] Guard re-audit with ${guardEngine.getExternalRuleCount()} Enhancement Pack rules → ${guardAudit.summary?.totalViolations ?? 0} total violations`);
|
|
@@ -10,6 +10,7 @@ import { execSync } from 'node:child_process';
|
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import { readFile } from 'node:fs/promises';
|
|
12
12
|
import path from 'node:path';
|
|
13
|
+
import { LanguageService } from '#shared/LanguageService.js';
|
|
13
14
|
import { resolveProjectRoot } from '#shared/resolveProjectRoot.js';
|
|
14
15
|
import { envelope } from '../envelope.js';
|
|
15
16
|
// ═══ Review 轮次追踪(模块私有) ═══════════════════
|
|
@@ -93,7 +94,7 @@ export async function guardAuditFiles(ctx, args) {
|
|
|
93
94
|
content = '';
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
|
-
return { path: absPath, content };
|
|
97
|
+
return { path: absPath, content, isTest: LanguageService.isTestFile(absPath) };
|
|
97
98
|
}));
|
|
98
99
|
const result = engine.auditFiles(filesToAudit, { scope });
|
|
99
100
|
// 写入 ViolationsStore + GuardFeedbackLoop
|
|
@@ -224,7 +225,7 @@ export async function guardReview(ctx, args) {
|
|
|
224
225
|
for (const fp of filePaths) {
|
|
225
226
|
try {
|
|
226
227
|
const code = await readFile(fp, 'utf8');
|
|
227
|
-
const auditResult = engine.auditFile(fp, code);
|
|
228
|
+
const auditResult = engine.auditFile(fp, code, { isTest: LanguageService.isTestFile(fp) });
|
|
228
229
|
const violations = auditResult.violations;
|
|
229
230
|
// 收集 uncertain
|
|
230
231
|
if (auditResult.uncertainResults?.length) {
|
|
@@ -523,7 +524,7 @@ export async function scanProject(ctx, args) {
|
|
|
523
524
|
content = '';
|
|
524
525
|
}
|
|
525
526
|
}
|
|
526
|
-
return { path: f.path, content };
|
|
527
|
+
return { path: f.path, content, isTest: LanguageService.isTestFile(f.path) };
|
|
527
528
|
}));
|
|
528
529
|
guardAudit = engine.auditFiles(filesToAudit, { scope: 'project' });
|
|
529
530
|
// 写入 ViolationsStore
|
|
@@ -629,9 +630,13 @@ async function _injectEnhancementGuardRules(engine, ctx) {
|
|
|
629
630
|
try {
|
|
630
631
|
const { initEnhancementRegistry } = await import('#core/enhancement/index.js');
|
|
631
632
|
const enhReg = await initEnhancementRegistry();
|
|
632
|
-
//
|
|
633
|
-
//
|
|
634
|
-
|
|
633
|
+
// 仅注入无框架条件的通用 Pack 规则(如 go-web 无 frameworks 条件)
|
|
634
|
+
// 有框架条件的 Pack(如 go-grpc 需要 frameworks: ['grpc'])由 Bootstrap Phase 4
|
|
635
|
+
// 通过 resolve(lang, detectedFrameworks) 精确注入,避免非 gRPC 项目出现误报
|
|
636
|
+
const allPacks = enhReg.all().filter((pack) => {
|
|
637
|
+
const cond = pack.conditions;
|
|
638
|
+
return !cond?.frameworks?.length;
|
|
639
|
+
});
|
|
635
640
|
const allGuardRules = [];
|
|
636
641
|
for (const pack of allPacks) {
|
|
637
642
|
try {
|
|
@@ -70,9 +70,9 @@ router.get('/providers', async (req, res) => {
|
|
|
70
70
|
};
|
|
71
71
|
const providers = [
|
|
72
72
|
{ id: 'google', label: 'Google Gemini', defaultModel: 'gemini-3-flash-preview' },
|
|
73
|
-
{ id: 'openai', label: 'OpenAI', defaultModel: 'gpt-
|
|
73
|
+
{ id: 'openai', label: 'OpenAI', defaultModel: 'gpt-5.4' },
|
|
74
74
|
{ id: 'deepseek', label: 'DeepSeek', defaultModel: 'deepseek-chat' },
|
|
75
|
-
{ id: 'claude', label: 'Claude', defaultModel: 'claude-
|
|
75
|
+
{ id: 'claude', label: 'Claude', defaultModel: 'claude-sonnet-4-20250514' },
|
|
76
76
|
{ id: 'ollama', label: 'Ollama', defaultModel: 'llama3' },
|
|
77
77
|
{ id: 'mock', label: 'Mock (测试)', defaultModel: 'mock-l3' },
|
|
78
78
|
].map((p) => ({
|
|
@@ -462,6 +462,10 @@ const LLM_ENV_KEYS = [
|
|
|
462
462
|
'ASD_CLAUDE_API_KEY',
|
|
463
463
|
'ASD_DEEPSEEK_API_KEY',
|
|
464
464
|
'ASD_AI_PROXY',
|
|
465
|
+
'ASD_EMBED_PROVIDER',
|
|
466
|
+
'ASD_EMBED_MODEL',
|
|
467
|
+
'ASD_EMBED_BASE_URL',
|
|
468
|
+
'ASD_EMBED_API_KEY',
|
|
465
469
|
];
|
|
466
470
|
/**
|
|
467
471
|
* 解析 .env 内容为 key-value(仅提取 LLM 相关变量)
|
|
@@ -523,7 +527,7 @@ router.get('/env-config', async (req, res) => {
|
|
|
523
527
|
* Body: { provider, model, apiKey, proxy? }
|
|
524
528
|
*/
|
|
525
529
|
router.post('/env-config', validate(AiEnvConfigBody), async (req, res) => {
|
|
526
|
-
const { provider, model, apiKey, proxy } = req.body;
|
|
530
|
+
const { provider, model, apiKey, proxy, embedProvider, embedModel, embedBaseUrl, embedApiKey } = req.body;
|
|
527
531
|
const envPath = _getProjectEnvPath();
|
|
528
532
|
let content = existsSync(envPath) ? readFileSync(envPath, 'utf8') : '';
|
|
529
533
|
// 构建 key-value 更新列表
|
|
@@ -547,6 +551,19 @@ router.post('/env-config', validate(AiEnvConfigBody), async (req, res) => {
|
|
|
547
551
|
if (keyName && apiKey) {
|
|
548
552
|
updates[keyName] = apiKey;
|
|
549
553
|
}
|
|
554
|
+
// Embedding 独立配置
|
|
555
|
+
if (embedProvider) {
|
|
556
|
+
updates.ASD_EMBED_PROVIDER = embedProvider;
|
|
557
|
+
if (embedModel) {
|
|
558
|
+
updates.ASD_EMBED_MODEL = embedModel;
|
|
559
|
+
}
|
|
560
|
+
if (embedBaseUrl) {
|
|
561
|
+
updates.ASD_EMBED_BASE_URL = embedBaseUrl;
|
|
562
|
+
}
|
|
563
|
+
if (embedApiKey) {
|
|
564
|
+
updates.ASD_EMBED_API_KEY = embedApiKey;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
550
567
|
// 逐条合并到 .env 内容
|
|
551
568
|
for (const [k, v] of Object.entries(updates)) {
|
|
552
569
|
// 匹配已有行(包括被注释的行)
|
|
@@ -44,7 +44,12 @@ export class IndexingPipeline {
|
|
|
44
44
|
constructor(options = {}) {
|
|
45
45
|
this.#vectorStore = options.vectorStore || null;
|
|
46
46
|
this.#aiProvider = options.aiProvider || null;
|
|
47
|
-
this.#scanDirs = options.scanDirs || [
|
|
47
|
+
this.#scanDirs = options.scanDirs || [
|
|
48
|
+
'recipes',
|
|
49
|
+
'candidates',
|
|
50
|
+
`${KNOWLEDGE_BASE_DIR}/recipes`,
|
|
51
|
+
`${KNOWLEDGE_BASE_DIR}/candidates`,
|
|
52
|
+
];
|
|
48
53
|
this.#projectRoot = options.projectRoot || process.cwd();
|
|
49
54
|
this.#chunkingOptions = {
|
|
50
55
|
strategy: options.chunking?.strategy ?? 'auto',
|
|
@@ -72,7 +72,28 @@ export async function initialize(c) {
|
|
|
72
72
|
// Token 追踪 AOP(manager 自身已在构造时 wire,此处延迟注入 recorder)
|
|
73
73
|
// recorder 注入放到 register() 之后(tokenUsageStore 需先注册)
|
|
74
74
|
// Embedding fallback: manager 的 embedFallbackInit 回调已绑定,初始化时主动触发一次
|
|
75
|
-
|
|
75
|
+
// 优先使用独立的 embed provider(ASD_EMBED_PROVIDER),其次 fallback 机制
|
|
76
|
+
let initialEmbed = null;
|
|
77
|
+
try {
|
|
78
|
+
const aiFactory = c.singletons._aiFactory;
|
|
79
|
+
if (typeof aiFactory?.createEmbedProvider === 'function') {
|
|
80
|
+
initialEmbed = aiFactory.createEmbedProvider();
|
|
81
|
+
if (initialEmbed) {
|
|
82
|
+
logger.info('Dedicated embed provider created from ASD_EMBED_PROVIDER', {
|
|
83
|
+
provider: initialEmbed.name,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
logger.warn('Failed to create dedicated embed provider', {
|
|
90
|
+
error: err.message,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// 若无独立 embed provider,走旧的 fallback 逻辑
|
|
94
|
+
if (!initialEmbed) {
|
|
95
|
+
initialEmbed = createEmbedFallback(c, c.singletons.aiProvider);
|
|
96
|
+
}
|
|
76
97
|
if (initialEmbed) {
|
|
77
98
|
manager.setEmbedProvider(initialEmbed);
|
|
78
99
|
c.singletons._embedProvider = initialEmbed;
|
|
@@ -116,6 +116,13 @@ export declare class BootstrapTaskManager {
|
|
|
116
116
|
* @param [reason='Aborted by user']
|
|
117
117
|
*/
|
|
118
118
|
abortSession(reason?: string): void;
|
|
119
|
+
/**
|
|
120
|
+
* 获取当前 session 的 AbortSignal
|
|
121
|
+
*
|
|
122
|
+
* 用于传入 AgentRuntime.execute(),使得 abortSession() 可以立即中断正在执行的 AI 调用,
|
|
123
|
+
* 而不是等到下一个维度边界才检测到取消。
|
|
124
|
+
*/
|
|
125
|
+
getSessionAbortSignal(): AbortSignal | null;
|
|
119
126
|
/**
|
|
120
127
|
* 验证 sessionId 是否仍然是活跃 session
|
|
121
128
|
*
|
|
@@ -119,6 +119,8 @@ export class BootstrapTaskManager {
|
|
|
119
119
|
#currentSession = null;
|
|
120
120
|
#eventBus = null;
|
|
121
121
|
#signalBus = null;
|
|
122
|
+
/** 当前 session 的 AbortController,用于取消正在执行的 AI 调用 */
|
|
123
|
+
#sessionAbortController = null;
|
|
122
124
|
/** 获取 RealtimeService 的 getter(延迟获取,避免循环依赖) */
|
|
123
125
|
#getRealtimeService = null;
|
|
124
126
|
constructor({ eventBus, signalBus, getRealtimeService } = {}) {
|
|
@@ -144,6 +146,7 @@ export class BootstrapTaskManager {
|
|
|
144
146
|
}
|
|
145
147
|
const sessionId = `bs_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
146
148
|
this.#currentSession = new BootstrapSession(sessionId);
|
|
149
|
+
this.#sessionAbortController = new AbortController();
|
|
147
150
|
for (const { id, meta } of taskDefs) {
|
|
148
151
|
this.#currentSession.addTask(id, meta);
|
|
149
152
|
}
|
|
@@ -177,6 +180,11 @@ export class BootstrapTaskManager {
|
|
|
177
180
|
task.error = reason;
|
|
178
181
|
}
|
|
179
182
|
}
|
|
183
|
+
// 触发 AbortController,中断正在执行的 AI 调用
|
|
184
|
+
if (this.#sessionAbortController) {
|
|
185
|
+
this.#sessionAbortController.abort(reason);
|
|
186
|
+
this.#sessionAbortController = null;
|
|
187
|
+
}
|
|
180
188
|
session.status = 'aborted';
|
|
181
189
|
session.completedAt = Date.now();
|
|
182
190
|
session.summary = {
|
|
@@ -209,6 +217,15 @@ export class BootstrapTaskManager {
|
|
|
209
217
|
},
|
|
210
218
|
});
|
|
211
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* 获取当前 session 的 AbortSignal
|
|
222
|
+
*
|
|
223
|
+
* 用于传入 AgentRuntime.execute(),使得 abortSession() 可以立即中断正在执行的 AI 调用,
|
|
224
|
+
* 而不是等到下一个维度边界才检测到取消。
|
|
225
|
+
*/
|
|
226
|
+
getSessionAbortSignal() {
|
|
227
|
+
return this.#sessionAbortController?.signal ?? null;
|
|
228
|
+
}
|
|
212
229
|
/**
|
|
213
230
|
* 验证 sessionId 是否仍然是活跃 session
|
|
214
231
|
*
|