autosnippet 2.6.0 → 2.7.0
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 +1 -1
- package/dashboard/dist/assets/{icons-rnn04CvH.js → icons-Cq4-iQhP.js} +148 -88
- package/dashboard/dist/assets/index-DBxH7pVn.css +1 -0
- package/dashboard/dist/assets/index-Dw2F6qAS.js +197 -0
- package/dashboard/dist/assets/{react-markdown-CWxUbOf4.js → react-markdown-BA6FB2NP.js} +1 -1
- package/dashboard/dist/assets/{syntax-highlighter-CJ2drQQb.js → syntax-highlighter-CVLHn9O5.js} +1 -1
- package/dashboard/dist/assets/{vendor-f83ah6cm.js → vendor-BotF760a.js} +61 -61
- package/dashboard/dist/index.html +6 -6
- package/lib/bootstrap.js +1 -1
- package/lib/cli/SetupService.js +33 -8
- package/lib/cli/UpgradeService.js +139 -2
- package/lib/core/ast/ProjectGraph.js +599 -0
- package/lib/core/gateway/GatewayActionRegistry.js +2 -2
- package/lib/domain/recipe/Recipe.js +3 -0
- package/lib/external/ai/AiProvider.js +83 -20
- package/lib/external/ai/providers/ClaudeProvider.js +197 -0
- package/lib/external/ai/providers/GoogleGeminiProvider.js +235 -1
- package/lib/external/ai/providers/OpenAiProvider.js +131 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +216 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +468 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +162 -0
- package/lib/external/mcp/handlers/bootstrap/skills.js +225 -0
- package/lib/external/mcp/handlers/bootstrap.js +151 -1634
- package/lib/external/mcp/handlers/browse.js +1 -1
- package/lib/external/mcp/handlers/candidate.js +1 -33
- package/lib/external/mcp/handlers/skill.js +54 -17
- package/lib/external/mcp/tools.js +4 -3
- package/lib/http/middleware/requestLogger.js +23 -4
- package/lib/http/routes/ai.js +3 -1
- package/lib/http/routes/auth.js +3 -2
- package/lib/http/routes/candidates.js +49 -25
- package/lib/http/routes/commands.js +0 -8
- package/lib/http/routes/guardRules.js +1 -16
- package/lib/http/routes/recipes.js +4 -17
- package/lib/http/routes/search.js +11 -19
- package/lib/http/routes/skills.js +2 -0
- package/lib/http/routes/snippets.js +0 -33
- package/lib/http/routes/spm.js +37 -63
- package/lib/http/utils/routeHelpers.js +31 -0
- package/lib/infrastructure/config/Paths.js +9 -0
- package/lib/infrastructure/logging/Logger.js +86 -3
- package/lib/infrastructure/realtime/RealtimeService.js +2 -5
- package/lib/infrastructure/vector/JsonVectorAdapter.js +24 -1
- package/lib/injection/ServiceContainer.js +55 -2
- package/lib/service/bootstrap/BootstrapTaskManager.js +400 -0
- package/lib/service/candidate/CandidateFileWriter.js +68 -27
- package/lib/service/candidate/CandidateService.js +156 -10
- package/lib/service/chat/AnalystAgent.js +216 -0
- package/lib/service/chat/CandidateGuardrail.js +134 -0
- package/lib/service/chat/ChatAgent.js +1036 -167
- package/lib/service/chat/ContextWindow.js +730 -0
- package/lib/service/chat/HandoffProtocol.js +180 -0
- package/lib/service/chat/ProducerAgent.js +240 -0
- package/lib/service/chat/ToolRegistry.js +149 -5
- package/lib/service/chat/tools.js +1397 -61
- package/lib/service/recipe/RecipeFileWriter.js +12 -1
- package/lib/service/skills/SignalCollector.js +31 -6
- package/lib/service/skills/SkillAdvisor.js +2 -1
- package/lib/service/skills/SkillHooks.js +13 -5
- package/lib/service/spm/SpmService.js +2 -2
- package/package.json +1 -1
- package/templates/copilot-instructions.md +20 -3
- package/templates/cursor-rules/autosnippet-conventions.mdc +21 -4
- package/templates/cursor-rules/autosnippet-skills.mdc +45 -0
- package/dashboard/dist/assets/index-BBKa3Dgi.js +0 -195
- package/dashboard/dist/assets/index-DLsECfzW.css +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenAiProvider - OpenAI / DeepSeek / Ollama 兼容提供商
|
|
3
3
|
* 使用标准 OpenAI Chat Completions API
|
|
4
|
+
*
|
|
5
|
+
* v2: 支持原生 Function Calling(结构化工具调用)
|
|
6
|
+
* - 使用 Chat Completions API 的 tools + tool_choice 参数
|
|
7
|
+
* - 兼容 DeepSeek / Ollama 等 OpenAI-compatible API
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
import { AiProvider } from '../AiProvider.js';
|
|
@@ -19,6 +23,14 @@ export class OpenAiProvider extends AiProvider {
|
|
|
19
23
|
this.logger = Logger.getInstance();
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
/**
|
|
27
|
+
* 是否支持原生结构化函数调用
|
|
28
|
+
* OpenAI / DeepSeek Chat Completions API 均支持
|
|
29
|
+
*/
|
|
30
|
+
get supportsNativeToolCalling() {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
async chat(prompt, context = {}) {
|
|
23
35
|
return this._withRetry(async () => {
|
|
24
36
|
const { history = [], temperature = 0.7, maxTokens = 4096 } = context;
|
|
@@ -41,6 +53,125 @@ export class OpenAiProvider extends AiProvider {
|
|
|
41
53
|
});
|
|
42
54
|
}
|
|
43
55
|
|
|
56
|
+
/**
|
|
57
|
+
* 带工具声明的结构化对话 — OpenAI Chat Completions Function Calling
|
|
58
|
+
*
|
|
59
|
+
* 接受统一消息格式,内部转换为 OpenAI Chat Completions 消息格式。
|
|
60
|
+
* 兼容 DeepSeek / Ollama 等 OpenAI-Compatible API。
|
|
61
|
+
*
|
|
62
|
+
* @param {string} prompt — fallback prompt
|
|
63
|
+
* @param {object} opts — 统一参数
|
|
64
|
+
* @returns {Promise<{text: string|null, functionCalls: Array<{id, name, args}>|null}>}
|
|
65
|
+
*/
|
|
66
|
+
async chatWithTools(prompt, opts = {}) {
|
|
67
|
+
return this._withRetry(async () => {
|
|
68
|
+
const {
|
|
69
|
+
messages: unifiedMessages,
|
|
70
|
+
toolSchemas,
|
|
71
|
+
toolChoice = 'auto',
|
|
72
|
+
systemPrompt,
|
|
73
|
+
temperature = 0.7,
|
|
74
|
+
maxTokens = 4096,
|
|
75
|
+
} = opts;
|
|
76
|
+
|
|
77
|
+
// 统一消息 → OpenAI Chat Completions messages
|
|
78
|
+
const messages = [];
|
|
79
|
+
if (systemPrompt) {
|
|
80
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const srcMessages = unifiedMessages?.length > 0
|
|
84
|
+
? unifiedMessages
|
|
85
|
+
: [{ role: 'user', content: prompt }];
|
|
86
|
+
|
|
87
|
+
for (const msg of srcMessages) {
|
|
88
|
+
if (msg.role === 'user') {
|
|
89
|
+
messages.push({ role: 'user', content: msg.content });
|
|
90
|
+
} else if (msg.role === 'assistant') {
|
|
91
|
+
const m = { role: 'assistant', content: msg.content || null };
|
|
92
|
+
if (msg.toolCalls?.length > 0) {
|
|
93
|
+
m.tool_calls = msg.toolCalls.map(tc => ({
|
|
94
|
+
id: tc.id,
|
|
95
|
+
type: 'function',
|
|
96
|
+
function: {
|
|
97
|
+
name: tc.name,
|
|
98
|
+
arguments: JSON.stringify(tc.args || {}),
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
messages.push(m);
|
|
103
|
+
} else if (msg.role === 'tool') {
|
|
104
|
+
messages.push({
|
|
105
|
+
role: 'tool',
|
|
106
|
+
tool_call_id: msg.toolCallId,
|
|
107
|
+
content: msg.content || '',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const body = {
|
|
113
|
+
model: this.model,
|
|
114
|
+
messages,
|
|
115
|
+
temperature,
|
|
116
|
+
max_tokens: maxTokens,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// 标准 tool schemas → OpenAI tools format
|
|
120
|
+
if (toolSchemas?.length > 0) {
|
|
121
|
+
body.tools = toolSchemas.map(s => ({
|
|
122
|
+
type: 'function',
|
|
123
|
+
function: {
|
|
124
|
+
name: s.name,
|
|
125
|
+
description: s.description || '',
|
|
126
|
+
parameters: s.parameters || { type: 'object', properties: {} },
|
|
127
|
+
},
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// toolChoice → OpenAI tool_choice
|
|
132
|
+
if (toolChoice === 'required') body.tool_choice = 'required';
|
|
133
|
+
else if (toolChoice === 'none') body.tool_choice = 'none';
|
|
134
|
+
else body.tool_choice = 'auto';
|
|
135
|
+
|
|
136
|
+
const data = await this._post(`${this.baseUrl}/chat/completions`, body);
|
|
137
|
+
return this.#parseToolResponse(data);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 解析 OpenAI Chat Completions 响应 — 提取 tool_calls 或 text
|
|
143
|
+
*
|
|
144
|
+
* OpenAI 返回格式:
|
|
145
|
+
* choices[0].message.tool_calls[]: { id, type: 'function', function: { name, arguments(JSON str) } }
|
|
146
|
+
*/
|
|
147
|
+
#parseToolResponse(data) {
|
|
148
|
+
const choice = data?.choices?.[0];
|
|
149
|
+
if (!choice) return { text: '', functionCalls: null };
|
|
150
|
+
|
|
151
|
+
const message = choice.message;
|
|
152
|
+
const text = message?.content || null;
|
|
153
|
+
|
|
154
|
+
if (message?.tool_calls?.length > 0) {
|
|
155
|
+
const functionCalls = message.tool_calls
|
|
156
|
+
.filter(tc => tc.type === 'function')
|
|
157
|
+
.map(tc => ({
|
|
158
|
+
id: tc.id,
|
|
159
|
+
name: tc.function.name,
|
|
160
|
+
args: (() => {
|
|
161
|
+
try { return JSON.parse(tc.function.arguments || '{}'); }
|
|
162
|
+
catch { return {}; }
|
|
163
|
+
})(),
|
|
164
|
+
}));
|
|
165
|
+
|
|
166
|
+
if (functionCalls.length > 0) {
|
|
167
|
+
this.logger.debug(`[OpenAI] native function calls: ${functionCalls.map(fc => fc.name).join(', ')}`);
|
|
168
|
+
return { text, functionCalls };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { text, functionCalls: null };
|
|
173
|
+
}
|
|
174
|
+
|
|
44
175
|
async summarize(code) {
|
|
45
176
|
const prompt = `请对以下代码生成结构化摘要,返回 JSON 格式 {title, description, language, patterns: [], keyAPIs: []}:\n\n${code}`;
|
|
46
177
|
const text = await this.chat(prompt, { temperature: 0.3 });
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pipeline/dimension-context.js — 跨维度上下文管理 (v6)
|
|
3
|
+
*
|
|
4
|
+
* 在 ChatAgent 按维度分批生产候选时,维护跨维度的上下文:
|
|
5
|
+
* - 项目基础信息 (不变)
|
|
6
|
+
* - 已完成维度的 DimensionDigest (累积)
|
|
7
|
+
* - 已提交候选的摘要列表 (累积)
|
|
8
|
+
*
|
|
9
|
+
* 确保每个维度的 ChatAgent 都能看到前序维度的分析结论,
|
|
10
|
+
* 实现跨维度透明互补。
|
|
11
|
+
*
|
|
12
|
+
* @module pipeline/dimension-context
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* DimensionContext — 跨维度上下文容器
|
|
17
|
+
*
|
|
18
|
+
* 在单次 bootstrap 会话中创建一次,按维度累积上下文。
|
|
19
|
+
*/
|
|
20
|
+
export class DimensionContext {
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} projectContext — 项目基础信息 (全程不变)
|
|
23
|
+
* @param {string} projectContext.projectName
|
|
24
|
+
* @param {string} projectContext.primaryLang
|
|
25
|
+
* @param {number} projectContext.fileCount
|
|
26
|
+
* @param {number} projectContext.targetCount
|
|
27
|
+
* @param {string[]} projectContext.modules
|
|
28
|
+
* @param {object} [projectContext.depGraph]
|
|
29
|
+
* @param {object} [projectContext.astMetrics]
|
|
30
|
+
* @param {object} [projectContext.guardSummary]
|
|
31
|
+
*/
|
|
32
|
+
constructor(projectContext) {
|
|
33
|
+
/** @type {object} 项目基础信息 */
|
|
34
|
+
this.projectContext = projectContext;
|
|
35
|
+
|
|
36
|
+
/** @type {Map<string, DimensionDigest>} 已完成维度的摘要 */
|
|
37
|
+
this.completedDimensions = new Map();
|
|
38
|
+
|
|
39
|
+
/** @type {Array<CandidateSummary>} 已提交候选的摘要 */
|
|
40
|
+
this.submittedCandidates = [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 维度完成后存储其摘要
|
|
45
|
+
*
|
|
46
|
+
* @param {string} dimId — 维度 ID
|
|
47
|
+
* @param {DimensionDigest} digest — 维度分析摘要
|
|
48
|
+
*/
|
|
49
|
+
addDimensionDigest(dimId, digest) {
|
|
50
|
+
this.completedDimensions.set(dimId, {
|
|
51
|
+
...digest,
|
|
52
|
+
dimId,
|
|
53
|
+
completedAt: Date.now(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 记录已提交的候选
|
|
59
|
+
*
|
|
60
|
+
* @param {string} dimId
|
|
61
|
+
* @param {object} candidateInfo — { title, subTopic, summary }
|
|
62
|
+
*/
|
|
63
|
+
addSubmittedCandidate(dimId, candidateInfo) {
|
|
64
|
+
this.submittedCandidates.push({
|
|
65
|
+
dimId,
|
|
66
|
+
title: candidateInfo.title || '',
|
|
67
|
+
subTopic: candidateInfo.subTopic || '',
|
|
68
|
+
summary: candidateInfo.summary || '',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 构建给 ChatAgent 的上下文快照
|
|
74
|
+
*
|
|
75
|
+
* @param {string} currentDimId — 当前维度 ID
|
|
76
|
+
* @returns {DimensionContextSnapshot}
|
|
77
|
+
*/
|
|
78
|
+
buildContextForDimension(currentDimId) {
|
|
79
|
+
const previousDimensions = {};
|
|
80
|
+
for (const [id, digest] of this.completedDimensions) {
|
|
81
|
+
previousDimensions[id] = {
|
|
82
|
+
summary: digest.summary,
|
|
83
|
+
candidateCount: digest.candidateCount,
|
|
84
|
+
keyFindings: digest.keyFindings || [],
|
|
85
|
+
crossRefs: digest.crossRefs || {},
|
|
86
|
+
gaps: digest.gaps || [],
|
|
87
|
+
remainingTasks: digest.remainingTasks || [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
project: this.projectContext,
|
|
93
|
+
previousDimensions,
|
|
94
|
+
existingCandidates: this.submittedCandidates.map(c => ({
|
|
95
|
+
dimId: c.dimId,
|
|
96
|
+
title: c.title,
|
|
97
|
+
subTopic: c.subTopic,
|
|
98
|
+
})),
|
|
99
|
+
currentDimension: currentDimId,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 重算某维度时,获取该维度已有候选
|
|
105
|
+
*
|
|
106
|
+
* @param {string} dimId
|
|
107
|
+
* @returns {Array<CandidateSummary>}
|
|
108
|
+
*/
|
|
109
|
+
getExistingCandidatesForDimension(dimId) {
|
|
110
|
+
return this.submittedCandidates.filter(c => c.dimId === dimId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 获取所有维度摘要的紧凑文本表示
|
|
115
|
+
* 用于 ChatAgent prompt 中注入
|
|
116
|
+
*
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
getDigestsSummaryText() {
|
|
120
|
+
if (this.completedDimensions.size === 0) return '(尚无已完成维度)';
|
|
121
|
+
|
|
122
|
+
const lines = [];
|
|
123
|
+
for (const [id, digest] of this.completedDimensions) {
|
|
124
|
+
lines.push(`### ${id}`);
|
|
125
|
+
lines.push(`- 摘要: ${digest.summary || '(无)'}`);
|
|
126
|
+
lines.push(`- 产出候选: ${digest.candidateCount || 0} 条`);
|
|
127
|
+
if (digest.keyFindings?.length) {
|
|
128
|
+
lines.push(`- 关键发现: ${digest.keyFindings.join('; ')}`);
|
|
129
|
+
}
|
|
130
|
+
if (digest.crossRefs && Object.keys(digest.crossRefs).length > 0) {
|
|
131
|
+
for (const [targetDim, suggestion] of Object.entries(digest.crossRefs)) {
|
|
132
|
+
lines.push(`- → ${targetDim}: ${suggestion}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (digest.gaps?.length) {
|
|
136
|
+
lines.push(`- 缺口: ${digest.gaps.join('; ')}`);
|
|
137
|
+
}
|
|
138
|
+
if (digest.remainingTasks?.length) {
|
|
139
|
+
lines.push(`- 遗留任务: ${digest.remainingTasks.map(t => t.signal || t).join('; ')}`);
|
|
140
|
+
}
|
|
141
|
+
lines.push('');
|
|
142
|
+
}
|
|
143
|
+
return lines.join('\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 将完整上下文序列化为 JSON (用于断点恢复)
|
|
148
|
+
* @returns {object}
|
|
149
|
+
*/
|
|
150
|
+
toJSON() {
|
|
151
|
+
return {
|
|
152
|
+
projectContext: this.projectContext,
|
|
153
|
+
completedDimensions: Object.fromEntries(this.completedDimensions),
|
|
154
|
+
submittedCandidates: this.submittedCandidates,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 从 JSON 恢复上下文 (断点恢复)
|
|
160
|
+
* @param {object} json
|
|
161
|
+
* @returns {DimensionContext}
|
|
162
|
+
*/
|
|
163
|
+
static fromJSON(json) {
|
|
164
|
+
const ctx = new DimensionContext(json.projectContext);
|
|
165
|
+
for (const [id, digest] of Object.entries(json.completedDimensions || {})) {
|
|
166
|
+
ctx.completedDimensions.set(id, digest);
|
|
167
|
+
}
|
|
168
|
+
ctx.submittedCandidates = json.submittedCandidates || [];
|
|
169
|
+
return ctx;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 从 ChatAgent 的最终回复中解析 DimensionDigest
|
|
175
|
+
*
|
|
176
|
+
* ChatAgent 被要求在回复末尾包含 JSON 格式的 dimensionDigest。
|
|
177
|
+
* 此函数从自由格式文本中提取该 JSON 块。
|
|
178
|
+
*
|
|
179
|
+
* @param {string} reply — ChatAgent 的完整回复文本
|
|
180
|
+
* @returns {DimensionDigest|null}
|
|
181
|
+
*/
|
|
182
|
+
export function parseDimensionDigest(reply) {
|
|
183
|
+
if (!reply || typeof reply !== 'string') return null;
|
|
184
|
+
|
|
185
|
+
// 尝试匹配 {"dimensionDigest": {...}} 格式
|
|
186
|
+
const jsonBlockRe = /```(?:json)?\s*\n?\s*(\{[\s\S]*?"dimensionDigest"[\s\S]*?\})\s*\n?\s*```/;
|
|
187
|
+
let match = reply.match(jsonBlockRe);
|
|
188
|
+
|
|
189
|
+
// 备选: 没有 code fence 的裸 JSON
|
|
190
|
+
if (!match) {
|
|
191
|
+
const bareRe = /(\{"dimensionDigest"\s*:\s*\{[\s\S]*?\}\s*\})/;
|
|
192
|
+
match = reply.match(bareRe);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!match) return null;
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(match[1]);
|
|
199
|
+
const digest = parsed.dimensionDigest || parsed;
|
|
200
|
+
|
|
201
|
+
// 验证必要字段
|
|
202
|
+
if (!digest.summary && !digest.candidateCount) return null;
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
summary: digest.summary || '',
|
|
206
|
+
candidateCount: digest.candidateCount || 0,
|
|
207
|
+
candidateTitles: digest.candidateTitles || [],
|
|
208
|
+
keyFindings: digest.keyFindings || [],
|
|
209
|
+
crossRefs: digest.crossRefs || {},
|
|
210
|
+
gaps: digest.gaps || [],
|
|
211
|
+
remainingTasks: digest.remainingTasks || [],
|
|
212
|
+
};
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|