yuangs 6.1.0 → 6.2.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/dist/agent/AgentRuntime.d.ts +21 -0
- package/dist/agent/AgentRuntime.js +475 -412
- package/dist/agent/AgentRuntime.js.map +1 -1
- package/dist/agent/dynamicPrompt.d.ts +5 -0
- package/dist/agent/dynamicPrompt.js +46 -0
- package/dist/agent/dynamicPrompt.js.map +1 -1
- package/dist/agent/executor.d.ts +8 -0
- package/dist/agent/executor.js +42 -0
- package/dist/agent/executor.js.map +1 -1
- package/dist/agent/llmAdapter.d.ts +13 -0
- package/dist/agent/llmAdapter.js +141 -47
- package/dist/agent/llmAdapter.js.map +1 -1
- package/dist/cli.js +11 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/handleAIChat.js +31 -7
- package/dist/commands/handleAIChat.js.map +1 -1
- package/package.json +1 -1
|
@@ -56,458 +56,521 @@ const renderer_1 = require("../utils/renderer");
|
|
|
56
56
|
class AgentRuntime {
|
|
57
57
|
context;
|
|
58
58
|
executionId;
|
|
59
|
+
maxTurns = 10;
|
|
60
|
+
readOnlyTools = [
|
|
61
|
+
'read_file', 'list_files', 'read_file_lines', 'read_file_lines_from_end',
|
|
62
|
+
'file_info', 'git_status', 'git_log', 'git_diff', 'list_directory_tree',
|
|
63
|
+
'search_in_files', 'search_symbol', 'continue_reading', 'analyze_dependencies'
|
|
64
|
+
];
|
|
59
65
|
constructor(initialContext) {
|
|
60
66
|
this.context = new smartContextManager_1.SmartContextManager(initialContext);
|
|
61
67
|
this.executionId = (0, crypto_1.randomUUID)();
|
|
62
68
|
}
|
|
63
69
|
async run(userInput, mode = "chat", onChunk, model, renderer) {
|
|
64
70
|
let turnCount = 0;
|
|
65
|
-
const maxTurns = 10;
|
|
66
71
|
let lastError;
|
|
67
|
-
let
|
|
68
|
-
let
|
|
69
|
-
let writeModeFileRead = null; // 跟踪 Write Mode 下已读取的文件
|
|
70
|
-
let writeModeFileContent = null; // 缓存 Write Mode 下已读取的文件内容
|
|
71
|
-
// 构建初始动态上下文
|
|
72
|
-
const initialDynamicContext = await (0, dynamicPrompt_1.buildDynamicContext)();
|
|
72
|
+
let lastToolCall = null;
|
|
73
|
+
let writeModeState = null;
|
|
73
74
|
if (userInput) {
|
|
74
75
|
this.context.addMessage("user", userInput);
|
|
75
76
|
}
|
|
76
|
-
while (turnCount < maxTurns) {
|
|
77
|
-
// 检查是否应该完成(上一轮只读工具成功)
|
|
78
|
-
if (shouldComplete) {
|
|
79
|
-
console.log(chalk_1.default.gray('[Task Complete] 任务已完成'));
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
77
|
+
while (turnCount < this.maxTurns) {
|
|
82
78
|
const currentTurn = ++turnCount;
|
|
83
79
|
if (currentTurn > 1) {
|
|
84
80
|
console.log(chalk_1.default.blue(`\n--- Turn ${currentTurn} ---`));
|
|
85
81
|
}
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
messages.push({
|
|
97
|
-
role: 'system',
|
|
98
|
-
content: enhancedContext.summary
|
|
99
|
-
});
|
|
82
|
+
const { agentRenderer, agentOnChunk } = this.prepareRenderer(renderer, onChunk);
|
|
83
|
+
const { prompt: enhancedPrompt, userInput: query } = await this.buildPrompt(userInput, lastError);
|
|
84
|
+
// === Step 1: LLM Thinking ===
|
|
85
|
+
const thought = await this.callLLM(enhancedPrompt, query, mode, agentOnChunk, model, agentRenderer);
|
|
86
|
+
if (!thought)
|
|
87
|
+
break; // error or empty
|
|
88
|
+
// === Step 2: Build Action ===
|
|
89
|
+
const action = this.buildAction(thought);
|
|
90
|
+
if (action.reasoning && !onChunk) {
|
|
91
|
+
console.log(chalk_1.default.gray(`\n🤔 Reasoning: ${action.reasoning}`));
|
|
100
92
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
messages.push({
|
|
104
|
-
role: 'user',
|
|
105
|
-
content: `@${item.path} (相关度: ${(item.relevance * 100).toFixed(0)}%)\n${item.summary || item.content || ''}`
|
|
106
|
-
});
|
|
93
|
+
if (thought.usedRouter) {
|
|
94
|
+
console.log(chalk_1.default.gray(`[Router] 🤖 Model: ${thought.modelName}`));
|
|
107
95
|
}
|
|
108
|
-
//
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
content
|
|
113
|
-
|
|
96
|
+
// === Step 3: Handle answer type ===
|
|
97
|
+
if (thought.isDone || action.type === "answer") {
|
|
98
|
+
// Command 模式下 AI 返回 answer:引导其使用工具
|
|
99
|
+
if (mode === "command") {
|
|
100
|
+
const content = action.payload.content || action.payload.text || '';
|
|
101
|
+
this.context.addMessage('system', `在命令执行模式下,请直接使用工具执行命令,不要回复对话。请使用 list_directory_tree 查看文件列表,然后用 shell_cmd 执行命令来查找最大文件。任务: "${userInput}"`);
|
|
102
|
+
continue; // 不 break,让 AI 重试使用工具
|
|
103
|
+
}
|
|
104
|
+
await this.handleAnswer(action, thought, userInput, mode, renderer, agentRenderer);
|
|
105
|
+
break;
|
|
114
106
|
}
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
107
|
+
// === Step 4: Causal ACK Check ===
|
|
108
|
+
if (!this.verifyAckCausality(thought))
|
|
109
|
+
continue;
|
|
110
|
+
// === Step 5: Governance ===
|
|
111
|
+
if (!await this.passGovernance(action))
|
|
112
|
+
continue;
|
|
113
|
+
// === Step 6: Record KG Edge ===
|
|
114
|
+
await this.recordKnowledgeGraphEdge(thought, action);
|
|
115
|
+
// === Step 7: Pre-execution Checks ===
|
|
116
|
+
const cachedResult = await this.checkExecutionBlock(action, writeModeState);
|
|
117
|
+
if (cachedResult === 'blocked')
|
|
118
|
+
continue;
|
|
119
|
+
// === Step 8: Execute ===
|
|
120
|
+
const result = cachedResult && typeof cachedResult === 'object'
|
|
121
|
+
? cachedResult
|
|
122
|
+
: await this.executeAction(action, writeModeState);
|
|
123
|
+
if (result.success) {
|
|
124
|
+
lastError = undefined;
|
|
125
|
+
lastToolCall = this.handleSuccessfulExecution(result, action, lastToolCall, userInput, writeModeState, mode, thought, agentRenderer);
|
|
126
|
+
if (lastToolCall === null)
|
|
127
|
+
break; // null signals break (stabilization or duplicate)
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
lastError = await this.handleFailedExecution(result, action, mode, thought, userInput);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (turnCount >= this.maxTurns) {
|
|
134
|
+
console.log(chalk_1.default.red(`\n⚠️ Max turns (${this.maxTurns}) reached.`));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// === Private Helper Methods ===
|
|
138
|
+
prepareRenderer(renderer, onChunk) {
|
|
139
|
+
let agentRenderer = renderer;
|
|
140
|
+
let agentOnChunk = onChunk;
|
|
141
|
+
if (!agentRenderer && agentOnChunk) {
|
|
142
|
+
agentRenderer = new renderer_1.StreamMarkdownRenderer(chalk_1.default.bgHex('#3b82f6').white.bold(' 🤖 Agent ') + ' ', undefined, { autoFinish: false });
|
|
143
|
+
agentOnChunk = agentRenderer.startChunking();
|
|
144
|
+
}
|
|
145
|
+
return { agentRenderer, agentOnChunk };
|
|
146
|
+
}
|
|
147
|
+
async buildPrompt(userInput, lastError) {
|
|
148
|
+
const dynamicContext = await (0, dynamicPrompt_1.buildDynamicContext)(lastError);
|
|
149
|
+
const basePrompt = governance_1.GovernanceService.getPolicyManual();
|
|
150
|
+
return {
|
|
151
|
+
prompt: (0, dynamicPrompt_1.injectDynamicContext)(basePrompt, dynamicContext),
|
|
152
|
+
userInput
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
async callLLM(enhancedPrompt, userInput, mode, onChunk, model, renderer) {
|
|
156
|
+
const messages = [];
|
|
157
|
+
const enhancedContext = await this.context.getEnhancedContext({
|
|
158
|
+
query: userInput || enhancedPrompt,
|
|
159
|
+
minRelevance: 0.3,
|
|
160
|
+
maxTokens: 8000,
|
|
161
|
+
enableSmartSummary: true
|
|
162
|
+
});
|
|
163
|
+
if (enhancedContext.summary) {
|
|
164
|
+
messages.push({ role: 'system', content: enhancedContext.summary });
|
|
165
|
+
}
|
|
166
|
+
for (const item of enhancedContext.rankedItems) {
|
|
167
|
+
messages.push({
|
|
168
|
+
role: 'user',
|
|
169
|
+
content: `@${item.path} (相关度: ${(item.relevance * 100).toFixed(0)}%)\n${item.summary || item.content || ''}`
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// 关键:确保用户输入被包含为最后一条消息
|
|
173
|
+
if (userInput) {
|
|
174
|
+
messages.push({ role: 'user', content: userInput });
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
messages.push({ role: 'user', content: enhancedPrompt });
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const thought = await llmAdapter_1.LLMAdapter.think(messages, mode, onChunk, model, enhancedPrompt, this.context);
|
|
181
|
+
if (!thought.raw || thought.raw.trim() === '') {
|
|
182
|
+
console.log(chalk_1.default.red('\n⚠️ AI 返回了空响应,请检查网络连接或模型配置。'));
|
|
183
|
+
return null;
|
|
127
184
|
}
|
|
128
|
-
|
|
185
|
+
return thought;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
this.handleLLMError(error);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
handleLLMError(error) {
|
|
193
|
+
let errorMessage = '未知内部错误';
|
|
194
|
+
let statusCode = 0;
|
|
195
|
+
if (error instanceof llm_1.AIError) {
|
|
196
|
+
errorMessage = error.message;
|
|
197
|
+
statusCode = error.statusCode;
|
|
198
|
+
}
|
|
199
|
+
else if (error instanceof Error) {
|
|
200
|
+
errorMessage = error.message;
|
|
201
|
+
statusCode = error.statusCode || 0;
|
|
202
|
+
}
|
|
203
|
+
else if (typeof error === 'string') {
|
|
204
|
+
errorMessage = error;
|
|
205
|
+
}
|
|
206
|
+
const statusInfo = statusCode ? ` (状态码: ${statusCode})` : '';
|
|
207
|
+
console.log(chalk_1.default.red(`\n❌ AI 思考过程发生错误: ${errorMessage}${statusInfo}`));
|
|
208
|
+
const isTransient = statusCode === 429 || statusCode >= 500
|
|
209
|
+
|| errorMessage.includes('timeout') || errorMessage.includes('network') || errorMessage.includes('ETIMEDOUT');
|
|
210
|
+
if (isTransient) {
|
|
211
|
+
console.log(chalk_1.default.yellow('⚠️ 检测到瞬时错误,自动跳过此轮'));
|
|
212
|
+
this.context.addMessage("system", `AI 调用失败${statusInfo},请稍后重试`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
this.context.addMessage("system", `思考过程中发生错误${statusInfo}: ${errorMessage}`);
|
|
216
|
+
if (statusCode === 401 || statusCode === 403 || errorMessage.includes('401') || errorMessage.includes('403')) {
|
|
217
|
+
console.log(chalk_1.default.yellow('💡 检测到权限或授权错误,请检查 API 配置。'));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
buildAction(thought) {
|
|
221
|
+
return {
|
|
222
|
+
id: (0, crypto_1.randomUUID)(),
|
|
223
|
+
type: thought.type || "answer",
|
|
224
|
+
payload: thought.payload || { text: thought.raw },
|
|
225
|
+
riskLevel: "low",
|
|
226
|
+
reasoning: thought.reasoning || "",
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
async handleAnswer(action, thought, userInput, mode, externalRenderer, agentRenderer) {
|
|
230
|
+
const result = await executor_1.ToolExecutor.execute(action);
|
|
231
|
+
if (!externalRenderer && agentRenderer) {
|
|
232
|
+
for (let i = 0; i < result.output.length; i += 10) {
|
|
233
|
+
agentRenderer.onChunk(result.output.slice(i, i + 10));
|
|
234
|
+
}
|
|
235
|
+
agentRenderer.finish();
|
|
236
|
+
}
|
|
237
|
+
else if (!externalRenderer) {
|
|
238
|
+
const rendered = marked.parse(result.output);
|
|
239
|
+
console.log(chalk_1.default.green(`\n🤖 AI:\n`) + rendered);
|
|
240
|
+
}
|
|
241
|
+
this.context.addMessage("assistant", result.output);
|
|
242
|
+
await this.learnFromExecution(userInput, mode, thought);
|
|
243
|
+
}
|
|
244
|
+
verifyAckCausality(thought) {
|
|
245
|
+
const lastObs = this.context.getLastAckableObservation();
|
|
246
|
+
const ackText = thought.parsedPlan?.acknowledged_observation;
|
|
247
|
+
if (lastObs && ackText && ackText !== 'NONE') {
|
|
248
|
+
if (lastObs.content.trim() !== ackText.trim()) {
|
|
249
|
+
console.log(chalk_1.default.red(`[CAUSAL BREAK] ❌ ACK mismatch!`));
|
|
250
|
+
console.log(chalk_1.default.red(` Expected: ${lastObs.content.trim().substring(0, 100)}...`));
|
|
251
|
+
console.log(chalk_1.default.red(` Received: ${ackText.trim().substring(0, 100)}...`));
|
|
252
|
+
this.context.addMessage("system", `CAUSAL BREAK: ACK does not match physical Observation. Cannot proceed without acknowledging reality.`);
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
console.log(chalk_1.default.green(`[CAUSAL LOCK] ✅ ACK verified`));
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
async passGovernance(action) {
|
|
260
|
+
const preCheck = (0, core_1.evaluateProposal)(action, governance_1.GovernanceService.getRules(), governance_1.GovernanceService.getLedgerSnapshot());
|
|
261
|
+
if (preCheck.effect === "deny") {
|
|
262
|
+
console.log(chalk_1.default.red(`[PRE-FLIGHT] 🛡️ Policy Blocked: ${preCheck.reason}`));
|
|
263
|
+
this.context.addMessage("system", `POLICY DENIED: ${preCheck.reason}. Find a different way.`);
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
const decision = await governance_1.GovernanceService.adjudicate(action);
|
|
267
|
+
if (decision.status === "rejected") {
|
|
268
|
+
console.log(chalk_1.default.red(`[GOVERNANCE] ❌ Rejected: ${decision.reason}`));
|
|
269
|
+
this.context.addMessage("system", `Rejected by Governance: ${decision.reason}`);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
async recordKnowledgeGraphEdge(thought, action) {
|
|
275
|
+
const lastObs = this.context.getLastAckableObservation();
|
|
276
|
+
const ackText = thought.parsedPlan?.acknowledged_observation;
|
|
277
|
+
if (lastObs && lastObs.metadata?.obsId && ackText && ackText !== 'NONE') {
|
|
129
278
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
279
|
+
const { recordEdge } = await Promise.resolve().then(() => __importStar(require('../engine/agent/knowledgeGraph')));
|
|
280
|
+
recordEdge({
|
|
281
|
+
from: lastObs.metadata.obsId,
|
|
282
|
+
to: action.id,
|
|
283
|
+
type: 'ACKNOWLEDGED_BY',
|
|
284
|
+
metadata: { verified: true, timestamp: Date.now() }
|
|
285
|
+
});
|
|
286
|
+
console.log(chalk_1.default.gray(`[KG] ⚓ Causal edge recorded`));
|
|
135
287
|
}
|
|
136
288
|
catch (error) {
|
|
137
|
-
|
|
138
|
-
let statusCode = 0;
|
|
139
|
-
if (error instanceof llm_1.AIError) {
|
|
140
|
-
errorMessage = error.message;
|
|
141
|
-
statusCode = error.statusCode;
|
|
142
|
-
}
|
|
143
|
-
else if (error instanceof Error) {
|
|
144
|
-
errorMessage = error.message;
|
|
145
|
-
statusCode = error.statusCode || 0;
|
|
146
|
-
}
|
|
147
|
-
else if (typeof error === 'string') {
|
|
148
|
-
errorMessage = error;
|
|
149
|
-
}
|
|
150
|
-
const statusInfo = statusCode ? ` (状态码: ${statusCode})` : '';
|
|
151
|
-
console.log(chalk_1.default.red(`\n❌ AI 思考过程发生错误: ${errorMessage}${statusInfo}`));
|
|
152
|
-
this.context.addMessage("system", `思考过程中发生错误${statusInfo}: ${errorMessage}`);
|
|
153
|
-
if (statusCode === 401 || statusCode === 403 || errorMessage.includes('401') || errorMessage.includes('403')) {
|
|
154
|
-
console.log(chalk_1.default.yellow('💡 检测到权限或授权错误,请检查 API 配置。'));
|
|
155
|
-
break;
|
|
156
|
-
}
|
|
157
|
-
if (statusCode === 429) {
|
|
158
|
-
console.log(chalk_1.default.yellow('💡 API 调用频率过高,请稍后再试。'));
|
|
159
|
-
}
|
|
160
|
-
break;
|
|
289
|
+
console.warn(chalk_1.default.yellow(`[KG] Warning: Failed to record causal edge: ${error.message}`));
|
|
161
290
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async checkExecutionBlock(action, writeModeState) {
|
|
294
|
+
if (action.type !== 'tool_call')
|
|
295
|
+
return null;
|
|
296
|
+
const toolName = action.payload.tool_name;
|
|
297
|
+
// Write Mode cache
|
|
298
|
+
if (writeModeState && toolName === 'read_file') {
|
|
299
|
+
const filePath = action.payload.parameters.path;
|
|
300
|
+
if (filePath === writeModeState.filePath && writeModeState.content) {
|
|
301
|
+
console.log(chalk_1.default.yellow(`[CACHED] 📋 文件 ${filePath} 内容已缓存,直接返回`));
|
|
302
|
+
return { success: true, output: writeModeState.content, artifacts: [filePath] };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const blockCheck = errorTracker_1.ErrorTracker.shouldBlockExecution(toolName, action.payload.parameters);
|
|
306
|
+
if (blockCheck.blocked) {
|
|
307
|
+
console.log(chalk_1.default.red(`[BLOCKED] 🚫 ${blockCheck.reason}`));
|
|
308
|
+
if (blockCheck.existingError) {
|
|
309
|
+
console.log(chalk_1.default.yellow(`[Error History] 此错误已发生 ${blockCheck.existingError.attemptCount} 次`));
|
|
310
|
+
console.log(chalk_1.default.gray(`上次错误: ${blockCheck.existingError.errorMessage}`));
|
|
311
|
+
}
|
|
312
|
+
this.context.addMessage("system", `BLOCKED: ${blockCheck.reason}. 建议尝试不同的方法。`);
|
|
313
|
+
return 'blocked';
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
async executeAction(action, _writeModeState) {
|
|
318
|
+
console.log(chalk_1.default.yellow(`[EXECUTING] ⚙️ ${action.type}...`));
|
|
319
|
+
return await executor_1.ToolExecutor.execute(action);
|
|
320
|
+
}
|
|
321
|
+
handleSuccessfulExecution(result, action, lastToolCall, userInput, writeModeState, mode, thought, agentRenderer) {
|
|
322
|
+
const actualToolName = action.payload?.tool_name || action.type;
|
|
323
|
+
this.context.addToolResult(actualToolName, result.output);
|
|
324
|
+
const preview = result.output.length > 300 ? result.output.substring(0, 300) + '...' : result.output;
|
|
325
|
+
console.log(chalk_1.default.green(`[SUCCESS] Result:\n${preview}`));
|
|
326
|
+
// Stabilization detection: 检查输出是否稳定(排除错误输出)
|
|
327
|
+
const normalizedOutput = result.output?.trim()?.split('\n').filter(Boolean)[0] || '';
|
|
328
|
+
const isErrorMessage = /invalid option|unknown|error|failed|no such|cannot|usage:|not found/i.test(normalizedOutput);
|
|
329
|
+
if (!isErrorMessage) {
|
|
330
|
+
// 方案3: 语义完成检测 — 输出已经是直接答案(适用于首轮查询,必须在 prevOutput 检测之前)
|
|
331
|
+
// 只有当完整输出都很短时才触发,避免在截断的多行输出上误触发
|
|
332
|
+
if (result.output.length < 100 && this.isSemanticComplete(normalizedOutput, userInput)) {
|
|
333
|
+
console.log(chalk_1.default.yellow('[Semantic Complete] 输出已经是直接答案,自动完成'));
|
|
334
|
+
console.log(chalk_1.default.cyan(`\n✓ 结果: ${normalizedOutput}\n`));
|
|
335
|
+
this.context.addMessage("assistant", result.output);
|
|
336
|
+
agentRenderer && (() => { agentRenderer.buffer = ''; agentRenderer.quietMode = true; agentRenderer.finish(); })();
|
|
337
|
+
return null; // signal break
|
|
338
|
+
}
|
|
339
|
+
// 方案4: 截断输出检测 — 结果被截断但已包含数据,引导 AI 完成
|
|
340
|
+
if (result.output.includes('[⚠️ OUTPUT TRUNCATED]') && normalizedOutput.length > 0) {
|
|
341
|
+
this.context.addMessage('system', `输出已截断,但已包含足够的数据。请直接使用 answer 类型向用户呈现你看到的结果,不要再运行更多命令。`);
|
|
342
|
+
}
|
|
343
|
+
// 方案1: 连续两次输出完全相同
|
|
344
|
+
const prevOutput = lastToolCall?.lastOutput;
|
|
345
|
+
const isOutputStable = prevOutput && normalizedOutput && prevOutput === normalizedOutput;
|
|
346
|
+
// 方案2: 最近2次输出包含相同的文件/路径名(即使命令不同)
|
|
347
|
+
const extractPath = (line) => {
|
|
348
|
+
const parts = line.trim().split(/\s+/);
|
|
349
|
+
return parts.length > 1 ? parts[parts.length - 1] : '';
|
|
350
|
+
};
|
|
351
|
+
const currentPath = extractPath(normalizedOutput);
|
|
352
|
+
const prevPath = prevOutput ? extractPath(prevOutput) : '';
|
|
353
|
+
const isOutputStableByName = currentPath && prevPath && currentPath === prevPath;
|
|
354
|
+
if (isOutputStable || isOutputStableByName) {
|
|
355
|
+
console.log(chalk_1.default.yellow('[Stabilization] 输出结果已稳定,自动完成'));
|
|
356
|
+
console.log(chalk_1.default.cyan(`\n✓ 结果: ${normalizedOutput}\n`));
|
|
357
|
+
this.context.addMessage("assistant", result.output);
|
|
358
|
+
agentRenderer && (() => { agentRenderer.buffer = ''; agentRenderer.quietMode = true; agentRenderer.finish(); })();
|
|
359
|
+
return null; // signal break
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Duplicate detection: 区分 tool_call 和 shell_cmd
|
|
363
|
+
const actualTool = action.payload?.tool_name || action.type;
|
|
364
|
+
const params = action.payload?.tool_name === 'shell_cmd' || action.type === 'shell_cmd'
|
|
365
|
+
? { command: (action.payload?.command || action.payload?.parameters?.command || '') }
|
|
366
|
+
: (action.payload?.parameters || action.payload);
|
|
367
|
+
const currentToolCall = { tool: actualTool, params };
|
|
368
|
+
const isDuplicate = lastToolCall &&
|
|
369
|
+
lastToolCall.tool === currentToolCall.tool &&
|
|
370
|
+
JSON.stringify(lastToolCall.params) === JSON.stringify(currentToolCall.params);
|
|
371
|
+
// Build output history for stabilization detection
|
|
372
|
+
const outputHistory = [...(lastToolCall?.outputHistory || []), normalizedOutput].slice(-3);
|
|
373
|
+
if (isDuplicate && lastToolCall) {
|
|
374
|
+
const count = lastToolCall.count + 1;
|
|
375
|
+
// 如果是错误输出,提示 AI 换方案
|
|
376
|
+
if (isErrorMessage) {
|
|
377
|
+
if (count >= 2) {
|
|
378
|
+
const failedCmd = actualTool === 'shell_cmd'
|
|
379
|
+
? (lastToolCall.params?.command || '')
|
|
380
|
+
: '';
|
|
381
|
+
console.log(chalk_1.default.yellow('[Duplicate Error] 相同命令重复失败 2 次,自动终止该路径'));
|
|
382
|
+
let errorMsg = `命令执行失败,且已重复尝试 2 次。请换一种完全不同的方法来完成用户任务: "${userInput}"。`;
|
|
383
|
+
if (failedCmd) {
|
|
384
|
+
errorMsg += `\n⚠️ 命令 "${failedCmd}" 在当前系统不可用,已被列入本次对话黑名单,不要再使用此命令。`;
|
|
233
385
|
}
|
|
234
|
-
|
|
386
|
+
errorMsg += `\n注意:当前系统可能是 macOS(BSD 工具链),不支持 Linux 特定的命令选项。请使用 macOS 兼容的命令。`;
|
|
387
|
+
this.context.addMessage('system', errorMsg);
|
|
388
|
+
// 重置重复计数让新命令不被当作重复
|
|
389
|
+
lastToolCall = { tool: 'reset', params: {}, count: 0, lastOutput: '', outputHistory: [] };
|
|
390
|
+
return lastToolCall;
|
|
235
391
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
392
|
+
console.log(chalk_1.default.gray(`[Repeat Error] 重复失败 (${count + 1}/2),请换方案...`));
|
|
393
|
+
}
|
|
394
|
+
else if (count >= 2) {
|
|
395
|
+
console.log(chalk_1.default.yellow('[Duplicate Detection] 达到重复限制,强制完成'));
|
|
396
|
+
console.log(chalk_1.default.cyan(`\n✓ ${action.payload?.tool_name || action.type} 已完成\n`));
|
|
397
|
+
agentRenderer && (() => { agentRenderer.buffer = ''; agentRenderer.quietMode = true; agentRenderer.finish(); })();
|
|
398
|
+
return null; // signal break
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
console.log(chalk_1.default.gray(`[Repeat Detection] 重复调用 (${count + 1}/2),继续...`));
|
|
402
|
+
}
|
|
403
|
+
lastToolCall.count = count;
|
|
404
|
+
lastToolCall.lastOutput = normalizedOutput;
|
|
405
|
+
lastToolCall.outputHistory = outputHistory;
|
|
406
|
+
if (!isErrorMessage) {
|
|
407
|
+
// 告诉 AI 结果已重复,应该返回答案而不是继续调用工具
|
|
408
|
+
this.context.addMessage('system', `同样的操作已经重复执行了 ${count} 次,结果没有变化。请停止继续调用工具,使用 answer 类型返回最终结果给用户。输出: "${result.output.slice(0, 200)}"`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
lastToolCall = { ...currentToolCall, count: 0, lastOutput: normalizedOutput, outputHistory };
|
|
413
|
+
}
|
|
414
|
+
// Auto-complete logic for specific tool types
|
|
415
|
+
if (action.type === 'tool_call') {
|
|
416
|
+
const toolName = action.payload.tool_name;
|
|
417
|
+
if (toolName === 'write_file') {
|
|
418
|
+
console.log(chalk_1.default.gray('[Auto-Complete] 文件写入成功,自动完成任务'));
|
|
419
|
+
console.log(chalk_1.default.green(`✓ 已创建文件: ${action.payload.parameters.path}\n`));
|
|
420
|
+
this.context.addMessage("assistant", `已成功创建文件 ${action.payload.parameters.path}`);
|
|
421
|
+
agentRenderer && (() => { agentRenderer.buffer = ''; agentRenderer.quietMode = true; agentRenderer.finish(); })();
|
|
422
|
+
result.shouldBreak = true;
|
|
423
|
+
return lastToolCall;
|
|
424
|
+
}
|
|
425
|
+
if (this.readOnlyTools.includes(toolName)) {
|
|
426
|
+
const requiresAnalysis = /^(.*?)(帮我|请)?(分析|解释|说明|总结)|\b(review|explain)\s+(this|the|it)\b/i.test(userInput);
|
|
427
|
+
const requiresWrite = /替换|replace|修改|modify|添加|append|插入|insert|删除|delete|移除|remove|更新|update|改成|改成|改为/i.test(userInput);
|
|
428
|
+
// Command 模式下不自动完成,让 AI 自行决定后续操作
|
|
429
|
+
if (mode === 'command') {
|
|
430
|
+
console.log(chalk_1.default.gray('[Command Mode] 只读工具执行成功,等待 AI 决定下一步'));
|
|
431
|
+
return lastToolCall;
|
|
249
432
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
metadata: {
|
|
259
|
-
verified: true,
|
|
260
|
-
timestamp: Date.now()
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
console.log(chalk_1.default.gray(`[KG] ⚓ Causal edge recorded`));
|
|
264
|
-
}
|
|
265
|
-
catch (error) {
|
|
266
|
-
console.warn(chalk_1.default.yellow(`[KG] Warning: Failed to record causal edge: ${error.message}`));
|
|
267
|
-
}
|
|
433
|
+
if (!requiresAnalysis && !requiresWrite) {
|
|
434
|
+
console.log(chalk_1.default.gray('[Auto-Complete] 只读工具执行成功,自动完成任务'));
|
|
435
|
+
console.log(chalk_1.default.cyan(`\n📄 ${toolName} 结果:\n`));
|
|
436
|
+
console.log(result.output);
|
|
437
|
+
this.context.addMessage("assistant", `已成功执行 ${toolName},结果:\n${result.output}`);
|
|
438
|
+
agentRenderer && (() => { agentRenderer.buffer = ''; agentRenderer.quietMode = true; agentRenderer.finish(); })();
|
|
439
|
+
result.shouldBreak = true;
|
|
440
|
+
return lastToolCall;
|
|
268
441
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// Write Mode 阻止重复 read_file 调用,但返回缓存的内容
|
|
275
|
-
if (writeModeFileRead && toolName === 'read_file') {
|
|
276
|
-
const filePath = action.payload.parameters.path;
|
|
277
|
-
if (filePath === writeModeFileRead && writeModeFileContent) {
|
|
278
|
-
console.log(chalk_1.default.yellow(`[CACHED] 📋 文件 ${filePath} 内容已缓存,直接返回(避免重复读取)`));
|
|
279
|
-
// 使用缓存的结果,跳过实际执行
|
|
280
|
-
cachedResult = {
|
|
281
|
-
success: true,
|
|
282
|
-
output: writeModeFileContent,
|
|
283
|
-
artifacts: [filePath]
|
|
284
|
-
};
|
|
285
|
-
}
|
|
442
|
+
else if (requiresWrite) {
|
|
443
|
+
console.log(chalk_1.default.gray('[Write Mode] 文件已读取,继续执行写入操作...'));
|
|
444
|
+
if (writeModeState) {
|
|
445
|
+
writeModeState.filePath = action.payload.parameters.path;
|
|
446
|
+
writeModeState.content = result.output;
|
|
286
447
|
}
|
|
287
|
-
|
|
288
|
-
const blockCheck = errorTracker_1.ErrorTracker.shouldBlockExecution(toolName, action.payload.parameters);
|
|
289
|
-
if (blockCheck.blocked) {
|
|
290
|
-
console.log(chalk_1.default.red(`[BLOCKED] 🚫 ${blockCheck.reason}`));
|
|
291
|
-
// 显示错误历史
|
|
292
|
-
if (blockCheck.existingError) {
|
|
293
|
-
console.log(chalk_1.default.yellow(`[Error History] 此错误已发生 ${blockCheck.existingError.attemptCount} 次`));
|
|
294
|
-
console.log(chalk_1.default.gray(`上次错误: ${blockCheck.existingError.errorMessage}`));
|
|
295
|
-
}
|
|
296
|
-
this.context.addMessage("system", `BLOCKED: ${blockCheck.reason}. 建议尝试不同的方法。`);
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
// === 执行 ===
|
|
302
|
-
let result;
|
|
303
|
-
if (cachedResult) {
|
|
304
|
-
result = cachedResult;
|
|
305
|
-
// 添加到上下文
|
|
306
|
-
this.context.addToolResult('read_file', result.output);
|
|
307
|
-
const preview = result.output.length > 300
|
|
308
|
-
? result.output.substring(0, 300) + '...'
|
|
309
|
-
: result.output;
|
|
310
|
-
console.log(chalk_1.default.green(`[SUCCESS] Result (cached):\n${preview}`));
|
|
448
|
+
this.context.addMessage('system', `文件 ${action.payload.parameters.path} 已成功读取。请根据用户要求修改内容后,调用 write_file 工具写入文件。不要重复调用 read_file!`);
|
|
311
449
|
}
|
|
312
450
|
else {
|
|
313
|
-
console.log(chalk_1.default.
|
|
314
|
-
result = await executor_1.ToolExecutor.execute(action);
|
|
451
|
+
console.log(chalk_1.default.gray('[Analysis Mode] 文件已读取,继续分析...'));
|
|
315
452
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// Learn from this execution
|
|
456
|
+
this.learnFromExecution(userInput, mode, thought).catch(() => { });
|
|
457
|
+
return lastToolCall;
|
|
458
|
+
}
|
|
459
|
+
async handleFailedExecution(result, action, mode, thought, userInput) {
|
|
460
|
+
const actualToolName = action.payload?.tool_name || action.type;
|
|
461
|
+
this.context.addToolResult(actualToolName, `Error: ${result.error}`);
|
|
462
|
+
console.log(chalk_1.default.red(`[ERROR] ${result.error}`));
|
|
463
|
+
// Record to error tracker
|
|
464
|
+
if (action.type === 'tool_call') {
|
|
465
|
+
errorTracker_1.ErrorTracker.recordError(action.payload.tool_name, action.payload.parameters, result.error || 'Unknown error', { mode, model: thought.modelName, userInput });
|
|
466
|
+
}
|
|
467
|
+
else if (action.type === 'shell_cmd') {
|
|
468
|
+
errorTracker_1.ErrorTracker.recordError('shell_cmd', { command: action.payload.command }, result.error || 'Unknown error', { mode, model: thought.modelName, userInput });
|
|
469
|
+
}
|
|
470
|
+
// Auto-fix for shell_cmd failures
|
|
471
|
+
if (action.type === 'shell_cmd' && result.error) {
|
|
472
|
+
console.log(chalk_1.default.yellow('[AutoFix] 尝试自动修复命令...'));
|
|
473
|
+
try {
|
|
474
|
+
const { autoFixCommand } = await Promise.resolve().then(() => __importStar(require('../core/autofix')));
|
|
475
|
+
const { getOSProfile } = await Promise.resolve().then(() => __importStar(require('../core/os')));
|
|
476
|
+
const os = getOSProfile();
|
|
477
|
+
const fixPlan = await autoFixCommand(action.payload.command, result.error || result.output || '', os, thought.modelName);
|
|
478
|
+
if (fixPlan && fixPlan.command) {
|
|
479
|
+
console.log(chalk_1.default.cyan(`[AutoFix] 建议修复命令: ${fixPlan.command}`));
|
|
480
|
+
console.log(chalk_1.default.gray(`[AutoFix] 说明: ${fixPlan.plan || '无'}`));
|
|
481
|
+
this.context.addMessage('system', `自动修复建议:${fixPlan.command}\n原因:${fixPlan.plan || '无'}`);
|
|
482
|
+
if (fixPlan.risk === 'low') {
|
|
483
|
+
console.log(chalk_1.default.yellow('[AutoFix] 修复方案风险低,自动执行...'));
|
|
484
|
+
const fixedAction = {
|
|
485
|
+
id: (0, crypto_1.randomUUID)(),
|
|
486
|
+
type: 'shell_cmd',
|
|
487
|
+
payload: { command: fixPlan.command },
|
|
488
|
+
riskLevel: 'low',
|
|
489
|
+
reasoning: `AutoFix from failed command: ${action.payload.command}`
|
|
490
|
+
};
|
|
491
|
+
const fixedResult = await executor_1.ToolExecutor.execute(fixedAction);
|
|
492
|
+
if (fixedResult.success) {
|
|
493
|
+
console.log(chalk_1.default.green('[AutoFix] 修复成功!'));
|
|
494
|
+
this.context.addToolResult('shell_cmd', fixedResult.output);
|
|
495
|
+
return ''; // clear error
|
|
345
496
|
}
|
|
346
497
|
else {
|
|
347
|
-
console.log(chalk_1.default.
|
|
498
|
+
console.log(chalk_1.default.red('[AutoFix] 修复失败,继续原始错误处理'));
|
|
348
499
|
}
|
|
349
500
|
}
|
|
350
|
-
// 更新上次工具调用记录(包含计数)
|
|
351
|
-
if (isDuplicate && lastToolCall) {
|
|
352
|
-
lastToolCall.count = duplicateCount + 1;
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
355
|
-
lastToolCall = { ...currentToolCall, count: 0 };
|
|
356
|
-
}
|
|
357
|
-
// 智能完成:根据工具类型和用户意图决定是否自动完成
|
|
358
|
-
if (action.type === 'tool_call') {
|
|
359
|
-
const toolName = action.payload.tool_name;
|
|
360
|
-
// 写入文件成功后自动完成
|
|
361
|
-
if (toolName === 'write_file') {
|
|
362
|
-
console.log(chalk_1.default.gray('[Auto-Complete] 文件写入成功,自动完成任务'));
|
|
363
|
-
console.log(chalk_1.default.green(`✓ 已创建文件: ${action.payload.parameters.path}\n`));
|
|
364
|
-
this.context.addMessage("assistant", `已成功创建文件 ${action.payload.parameters.path}`);
|
|
365
|
-
// 清除 Write Mode 标记,因为写入已完成
|
|
366
|
-
writeModeFileRead = null;
|
|
367
|
-
writeModeFileContent = null;
|
|
368
|
-
if (agentRenderer) {
|
|
369
|
-
agentRenderer.buffer = '';
|
|
370
|
-
agentRenderer.quietMode = true;
|
|
371
|
-
agentRenderer.finish();
|
|
372
|
-
}
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
// 只读工具处理
|
|
376
|
-
const readOnlyTools = ['read_file', 'list_files', 'read_file_lines', 'read_file_lines_from_end', 'file_info', 'git_status', 'git_log', 'git_diff', 'list_directory_tree', 'search_in_files', 'search_symbol', 'continue_reading', 'analyze_dependencies'];
|
|
377
|
-
if (readOnlyTools.includes(toolName)) {
|
|
378
|
-
// 检测用户意图:
|
|
379
|
-
// 1. 如果要求"分析"、"解释"等,则不自动完成(继续分析)
|
|
380
|
-
// 2. 如果涉及写入操作(替换、修改、添加等),则不自动完成(继续执行写入)
|
|
381
|
-
// 使用更精确的匹配,避免文件名中的关键词(如 git_reviews.md 中的 review)误触发
|
|
382
|
-
const requiresAnalysis = /^(.*?)(帮我|请)?(分析|解释|说明|总结)|\b(review|explain)\s+(this|the|it)\b/i.test(userInput);
|
|
383
|
-
const requiresWrite = /替换|replace|修改|modify|添加|append|插入|insert|删除|delete|移除|remove|更新|update|改成|改成|改为/i.test(userInput);
|
|
384
|
-
if (!requiresAnalysis && !requiresWrite) {
|
|
385
|
-
// 简单读取请求,直接返回结果
|
|
386
|
-
console.log(chalk_1.default.gray('[Auto-Complete] 只读工具执行成功,自动完成任务'));
|
|
387
|
-
console.log(chalk_1.default.cyan(`\n📄 ${toolName} 结果:\n`));
|
|
388
|
-
console.log(result.output);
|
|
389
|
-
this.context.addMessage("assistant", `已成功执行 ${toolName},结果:\n${result.output}`);
|
|
390
|
-
if (agentRenderer) {
|
|
391
|
-
agentRenderer.buffer = '';
|
|
392
|
-
agentRenderer.quietMode = true;
|
|
393
|
-
agentRenderer.finish();
|
|
394
|
-
}
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
else if (requiresWrite) {
|
|
398
|
-
// 写入请求,继续循环让 AI 执行写入操作
|
|
399
|
-
console.log(chalk_1.default.gray('[Write Mode] 文件已读取,继续执行写入操作...'));
|
|
400
|
-
// 标记此文件已在 Write Mode 下读取,防止重复读取
|
|
401
|
-
writeModeFileRead = action.payload.parameters.path;
|
|
402
|
-
// 缓存文件内容,避免重复读取
|
|
403
|
-
writeModeFileContent = result.output;
|
|
404
|
-
// 添加系统消息明确指导 AI 下一步操作
|
|
405
|
-
this.context.addMessage('system', `文件 ${action.payload.parameters.path} 已成功读取。请根据用户要求修改内容后,调用 write_file 工具写入文件。不要重复调用 read_file!`);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
// 分析请求,继续循环让 AI 分析内容
|
|
409
|
-
console.log(chalk_1.default.gray('[Analysis Mode] 文件已读取,继续分析...'));
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
// Learn from this successful execution
|
|
414
|
-
try {
|
|
415
|
-
const { createExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionRecord')));
|
|
416
|
-
const { inferCapabilityRequirement } = await Promise.resolve().then(() => __importStar(require('../core/capabilityInference')));
|
|
417
|
-
const { saveExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionStore')));
|
|
418
|
-
const record = createExecutionRecord(`agent-${mode}`, { required: [], preferred: [] }, {
|
|
419
|
-
aiProxyUrl: { value: '', source: 'built-in' },
|
|
420
|
-
defaultModel: { value: '', source: 'built-in' },
|
|
421
|
-
accountType: { value: 'free', source: 'built-in' }
|
|
422
|
-
}, { selected: null, candidates: [], fallbackOccurred: false }, { success: true }, undefined, userInput, mode);
|
|
423
|
-
// Attach thought/plan data for skill learning
|
|
424
|
-
record.llmResult = { plan: thought.parsedPlan };
|
|
425
|
-
record.input = { rawInput: userInput };
|
|
426
|
-
const savedRecordId = saveExecutionRecord(record);
|
|
427
|
-
const { loadExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionStore')));
|
|
428
|
-
const savedRecord = loadExecutionRecord(savedRecordId);
|
|
429
|
-
if (savedRecord) {
|
|
430
|
-
const { learnSkillFromRecord } = await Promise.resolve().then(() => __importStar(require('./skills')));
|
|
431
|
-
learnSkillFromRecord(savedRecord, true);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
catch (error) {
|
|
435
|
-
console.warn(chalk_1.default.yellow(`[Skill Learning] Failed: ${error}`));
|
|
436
|
-
}
|
|
437
501
|
}
|
|
438
502
|
else {
|
|
439
|
-
|
|
440
|
-
lastError = result.error;
|
|
441
|
-
const actualToolName = action.payload?.tool_name || action.type;
|
|
442
|
-
this.context.addToolResult(actualToolName, `Error: ${result.error}`);
|
|
443
|
-
console.log(chalk_1.default.red(`[ERROR] ${result.error}`));
|
|
444
|
-
// 记录到错误追踪器
|
|
445
|
-
if (action.type === 'tool_call') {
|
|
446
|
-
errorTracker_1.ErrorTracker.recordError(action.payload.tool_name, action.payload.parameters, result.error || 'Unknown error', { mode, model: thought.modelName, userInput });
|
|
447
|
-
}
|
|
448
|
-
else if (action.type === 'shell_cmd') {
|
|
449
|
-
errorTracker_1.ErrorTracker.recordError('shell_cmd', { command: action.payload.command }, result.error || 'Unknown error', { mode, model: thought.modelName, userInput });
|
|
450
|
-
}
|
|
451
|
-
// 尝试自动修复(仅针对 shell_cmd 失败)
|
|
452
|
-
if (action.type === 'shell_cmd' && result.error) {
|
|
453
|
-
console.log(chalk_1.default.yellow('[AutoFix] 尝试自动修复命令...'));
|
|
454
|
-
try {
|
|
455
|
-
const { autoFixCommand } = await Promise.resolve().then(() => __importStar(require('../core/autofix')));
|
|
456
|
-
const { getOSProfile } = await Promise.resolve().then(() => __importStar(require('../core/os')));
|
|
457
|
-
const os = getOSProfile();
|
|
458
|
-
const fixPlan = await autoFixCommand(action.payload.command, result.error || result.output || '', os, thought.modelName);
|
|
459
|
-
if (fixPlan && fixPlan.command) {
|
|
460
|
-
console.log(chalk_1.default.cyan(`[AutoFix] 建议修复命令: ${fixPlan.command}`));
|
|
461
|
-
console.log(chalk_1.default.gray(`[AutoFix] 说明: ${fixPlan.plan || '无'}`));
|
|
462
|
-
// 将修复建议添加到上下文
|
|
463
|
-
this.context.addMessage('system', `自动修复建议:${fixPlan.command}\n原因:${fixPlan.plan || '无'}`);
|
|
464
|
-
// 如果修复方案风险低,直接执行
|
|
465
|
-
if (fixPlan.risk === 'low') {
|
|
466
|
-
console.log(chalk_1.default.yellow('[AutoFix] 修复方案风险低,自动执行...'));
|
|
467
|
-
const fixedAction = {
|
|
468
|
-
id: (0, crypto_1.randomUUID)(),
|
|
469
|
-
type: 'shell_cmd',
|
|
470
|
-
payload: { command: fixPlan.command },
|
|
471
|
-
riskLevel: 'low',
|
|
472
|
-
reasoning: `AutoFix from failed command: ${action.payload.command}`
|
|
473
|
-
};
|
|
474
|
-
const fixedResult = await executor_1.ToolExecutor.execute(fixedAction);
|
|
475
|
-
if (fixedResult.success) {
|
|
476
|
-
console.log(chalk_1.default.green('[AutoFix] 修复成功!'));
|
|
477
|
-
lastError = undefined;
|
|
478
|
-
this.context.addToolResult('shell_cmd', fixedResult.output);
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
console.log(chalk_1.default.red('[AutoFix] 修复失败,继续原始错误处理'));
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
else {
|
|
487
|
-
console.log(chalk_1.default.gray('[AutoFix] 无法生成修复建议'));
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
catch (fixError) {
|
|
491
|
-
console.warn(chalk_1.default.yellow(`[AutoFix] 修复过程出错: ${fixError.message}`));
|
|
492
|
-
}
|
|
493
|
-
}
|
|
503
|
+
console.log(chalk_1.default.gray('[AutoFix] 无法生成修复建议'));
|
|
494
504
|
}
|
|
495
505
|
}
|
|
496
|
-
catch (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
506
|
+
catch (fixError) {
|
|
507
|
+
console.warn(chalk_1.default.yellow(`[AutoFix] 修复过程出错: ${fixError.message}`));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return result.error;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* 语义完成检测:输出是否已经是直接答案
|
|
514
|
+
* 用于防止 AI 在已经获取答案后继续无意义地查询
|
|
515
|
+
*/
|
|
516
|
+
isSemanticComplete(output, userInput) {
|
|
517
|
+
const trimmed = output.trim();
|
|
518
|
+
// 输出必须是短的(直接答案通常很短)
|
|
519
|
+
if (trimmed.length > 100 || trimmed.length === 0)
|
|
520
|
+
return false;
|
|
521
|
+
// 输出不能包含多行
|
|
522
|
+
if (trimmed.includes('\n'))
|
|
523
|
+
return false;
|
|
524
|
+
const q = userInput.toLowerCase();
|
|
525
|
+
// 1. 问"大小/多少字节" → 输出是纯数字 或 带单位的短文本(如 "4.0K"、"288K"、"1.5MB")
|
|
526
|
+
if (/(大小|多少字节|多大|size|bytes?)/.test(q)) {
|
|
527
|
+
if (/^\d+(\.\d+)?$/.test(trimmed))
|
|
528
|
+
return true;
|
|
529
|
+
// 人类可读格式:数字+单位,可能带前后空格和路径
|
|
530
|
+
if (/^[\d.]+\s*[KMGT]?B?$/i.test(trimmed))
|
|
531
|
+
return true;
|
|
532
|
+
// 带路径的格式:如 "4.0K ./package.json"
|
|
533
|
+
if (/^[\d.]+\s*[KMGT]?B?\s+\S+/i.test(trimmed))
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
// 2. 问"几个/多少个/数量/count" → 输出是纯数字
|
|
537
|
+
if (/(几个|多少个|数量|count|how many)/.test(q) && /^\d+$/.test(trimmed))
|
|
538
|
+
return true;
|
|
539
|
+
// 3. 问"最大/最小的文件" → 输出是 "数字 路径" 格式(如 "0 ./foo.txt")
|
|
540
|
+
if (/(最大|最小|largest|smallest).*文件/.test(q) && /^\d+\s+\.\//.test(trimmed))
|
|
541
|
+
return true;
|
|
542
|
+
// 4. 问"最新/最早"的文件 → 输出是单个文件名或带信息的短行
|
|
543
|
+
if (/(最新|最旧|最近|最早|newest|latest|oldest)/.test(q) && trimmed.split(/\s+/).length <= 5 && trimmed.length < 80)
|
|
544
|
+
return true;
|
|
545
|
+
// 5. 问"行数" → 输出是纯数字
|
|
546
|
+
if (/(行数|多少行|line.?count|多少 line)/.test(q) && /^\d+$/.test(trimmed))
|
|
547
|
+
return true;
|
|
548
|
+
// 6. 通用:输出是短小的、看起来像答案的内容(单行,< 50 字符,不是命令输出)
|
|
549
|
+
// 排除:以 - 开头(ls -l 风格)、包含多个空格的长行
|
|
550
|
+
if (trimmed.length < 50 && !trimmed.startsWith('-') && trimmed.split(/\s+/).length <= 4) {
|
|
551
|
+
// 包含数字或路径,看起来像答案
|
|
552
|
+
if (/\d+|\.\//.test(trimmed))
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
async learnFromExecution(userInput, mode, thought) {
|
|
558
|
+
try {
|
|
559
|
+
const { createExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionRecord')));
|
|
560
|
+
const { saveExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionStore')));
|
|
561
|
+
const record = createExecutionRecord(`agent-${mode}`, { required: [], preferred: [] }, { aiProxyUrl: { value: '', source: 'built-in' }, defaultModel: { value: '', source: 'built-in' }, accountType: { value: 'free', source: 'built-in' } }, { selected: null, candidates: [], fallbackOccurred: false }, { success: true }, undefined, userInput, mode);
|
|
562
|
+
record.llmResult = { plan: thought.parsedPlan };
|
|
563
|
+
record.input = { rawInput: userInput };
|
|
564
|
+
const savedRecordId = saveExecutionRecord(record);
|
|
565
|
+
const { loadExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionStore')));
|
|
566
|
+
const savedRecord = loadExecutionRecord(savedRecordId);
|
|
567
|
+
if (savedRecord) {
|
|
568
|
+
const { learnSkillFromRecord } = await Promise.resolve().then(() => __importStar(require('./skills')));
|
|
569
|
+
learnSkillFromRecord(savedRecord, true);
|
|
507
570
|
}
|
|
508
571
|
}
|
|
509
|
-
|
|
510
|
-
console.
|
|
572
|
+
catch (error) {
|
|
573
|
+
console.warn(chalk_1.default.yellow(`[Skill Learning] Failed: ${error}`));
|
|
511
574
|
}
|
|
512
575
|
}
|
|
513
576
|
}
|