yuangs 6.1.0 → 6.2.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.
@@ -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 shouldComplete = false; // 标记是否应该完成
68
- let lastToolCall = null; // 跟踪上次工具调用及重复次数
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
- // Use smart context manager to get relevance-ranked context
87
- const enhancedContext = await this.context.getEnhancedContext({
88
- query: userInput,
89
- minRelevance: 0.3,
90
- maxTokens: 8000,
91
- enableSmartSummary: true
92
- });
93
- const messages = [];
94
- // Add context overview as system message
95
- if (enhancedContext.summary) {
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
- // Add ranked context files
102
- for (const item of enhancedContext.rankedItems) {
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
- // Add user input
109
- if (userInput) {
110
- messages.push({
111
- role: 'user',
112
- content: userInput
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
- // Build dynamic context (if previous step had error)
116
- const dynamicContext = await (0, dynamicPrompt_1.buildDynamicContext)(lastError);
117
- // 构建基础prompt(包括治理策略)
118
- const basePrompt = governance_1.GovernanceService.getPolicyManual();
119
- // 注入动态上下文
120
- const enhancedPrompt = (0, dynamicPrompt_1.injectDynamicContext)(basePrompt, dynamicContext);
121
- // Create renderer if not provided but onChunk is available
122
- let agentRenderer = renderer;
123
- let agentOnChunk = onChunk;
124
- if (!agentRenderer && agentOnChunk) {
125
- agentRenderer = new renderer_1.StreamMarkdownRenderer(chalk_1.default.bgHex('#3b82f6').white.bold(' 🤖 Agent ') + ' ', undefined, { autoFinish: false });
126
- agentOnChunk = agentRenderer.startChunking();
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
- let thought;
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
- thought = await llmAdapter_1.LLMAdapter.think(messages, mode, agentOnChunk, model, enhancedPrompt, this.context);
131
- if (!thought.raw || thought.raw.trim() === '') {
132
- console.log(chalk_1.default.red('\n⚠️ AI 返回了空响应,请检查网络连接或模型配置。'));
133
- break;
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
- let errorMessage = '未知内部错误';
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
- try {
163
- const action = {
164
- id: (0, crypto_1.randomUUID)(),
165
- type: thought.type || "answer",
166
- payload: thought.payload || { text: thought.raw },
167
- riskLevel: "low",
168
- reasoning: thought.reasoning || "",
169
- };
170
- if (action.reasoning && !onChunk) {
171
- console.log(chalk_1.default.gray(`\n🤔 Reasoning: ${action.reasoning}`));
172
- }
173
- if (thought.usedRouter) {
174
- console.log(chalk_1.default.gray(`[Router] 🤖 Model: ${thought.modelName}`));
175
- }
176
- // 如果 LLM 认为已经完成或者当前的动作就是回答
177
- if (thought.isDone || action.type === "answer") {
178
- const result = await executor_1.ToolExecutor.execute(action);
179
- // ... rest of the logic
180
- // 如果没有 renderer,使用内部创建的
181
- if (!renderer && agentRenderer) {
182
- // Stream final answer through internal renderer
183
- for (let i = 0; i < result.output.length; i += 10) {
184
- const chunk = result.output.slice(i, i + 10);
185
- agentRenderer.onChunk(chunk);
186
- }
187
- agentRenderer.finish();
188
- }
189
- else if (!renderer) {
190
- // Fallback to marked if no renderer
191
- const rendered = marked.parse(result.output);
192
- console.log(chalk_1.default.green(`\n🤖 AI:\n`) + rendered);
193
- }
194
- // 如果外部传入了 renderer,由外部调用 finish()
195
- this.context.addMessage("assistant", result.output);
196
- // Learn from successful chat
197
- try {
198
- const { createExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionRecord')));
199
- const { inferCapabilityRequirement } = await Promise.resolve().then(() => __importStar(require('../core/capabilityInference')));
200
- const { saveExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionStore')));
201
- const record = createExecutionRecord('agent-chat', { required: [], preferred: [] }, {
202
- aiProxyUrl: { value: '', source: 'built-in' },
203
- defaultModel: { value: '', source: 'built-in' },
204
- accountType: { value: 'free', source: 'built-in' }
205
- }, { selected: null, candidates: [], fallbackOccurred: false }, { success: true }, undefined, userInput, 'chat');
206
- record.llmResult = { plan: thought.parsedPlan };
207
- record.input = { rawInput: userInput };
208
- const savedRecordId = saveExecutionRecord(record);
209
- const { loadExecutionRecord } = await Promise.resolve().then(() => __importStar(require('../core/executionStore')));
210
- const savedRecord = loadExecutionRecord(savedRecordId);
211
- if (savedRecord) {
212
- const { learnSkillFromRecord } = await Promise.resolve().then(() => __importStar(require('./skills')));
213
- learnSkillFromRecord(savedRecord, true);
214
- }
215
- }
216
- catch (error) {
217
- console.warn(chalk_1.default.yellow(`[Skill Learning] Failed: ${error}`));
218
- }
219
- break;
220
- }
221
- // === 强制 ACK 校验(Causal Lock) ===
222
- const lastObs = this.context.getLastAckableObservation();
223
- const ackText = thought.parsedPlan?.acknowledged_observation;
224
- if (lastObs && ackText && ackText !== 'NONE') {
225
- const actualContent = lastObs.content.trim();
226
- const ackedContent = ackText.trim();
227
- if (actualContent !== ackedContent) {
228
- console.log(chalk_1.default.red(`[CAUSAL BREAK] ❌ ACK mismatch!`));
229
- console.log(chalk_1.default.red(` Expected: ${actualContent.substring(0, 100)}...`));
230
- console.log(chalk_1.default.red(` Received: ${ackedContent.substring(0, 100)}...`));
231
- this.context.addMessage("system", `CAUSAL BREAK: ACK does not match physical Observation. Cannot proceed without acknowledging reality.`);
232
- continue;
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
- console.log(chalk_1.default.green(`[CAUSAL LOCK] ACK verified`));
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
- // === 预检 (Pre-flight) ===
237
- const preCheck = (0, core_1.evaluateProposal)(action, governance_1.GovernanceService.getRules(), governance_1.GovernanceService.getLedgerSnapshot());
238
- if (preCheck.effect === "deny") {
239
- console.log(chalk_1.default.red(`[PRE-FLIGHT] 🛡️ Policy Blocked: ${preCheck.reason}`));
240
- this.context.addMessage("system", `POLICY DENIED: ${preCheck.reason}. Find a different way.`);
241
- continue;
242
- }
243
- // === 正式治理 (WASM + 人工/自动) ===
244
- const decision = await governance_1.GovernanceService.adjudicate(action);
245
- if (decision.status === "rejected") {
246
- console.log(chalk_1.default.red(`[GOVERNANCE] ❌ Rejected: ${decision.reason}`));
247
- this.context.addMessage("system", `Rejected by Governance: ${decision.reason}`);
248
- continue;
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
- // === 记录因果边到 KG ===
251
- if (lastObs && lastObs.metadata?.obsId && ackText && ackText !== 'NONE') {
252
- try {
253
- const { recordEdge } = await Promise.resolve().then(() => __importStar(require('../engine/agent/knowledgeGraph')));
254
- recordEdge({
255
- from: lastObs.metadata.obsId,
256
- to: action.id,
257
- type: 'ACKNOWLEDGED_BY',
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
- let cachedResult = null;
272
- if (action.type === 'tool_call') {
273
- const toolName = action.payload.tool_name;
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
- if (!cachedResult) {
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.yellow(`[EXECUTING] ⚙️ ${action.type}...`));
314
- result = await executor_1.ToolExecutor.execute(action);
451
+ console.log(chalk_1.default.gray('[Analysis Mode] 文件已读取,继续分析...'));
315
452
  }
316
- if (result.success) {
317
- // 成功时清除错误状态
318
- lastError = undefined;
319
- // 使用实际工具名称而不是 action.type,这样 LLM 知道调用了哪个工具
320
- const actualToolName = action.payload?.tool_name || action.type;
321
- this.context.addToolResult(actualToolName, result.output);
322
- const preview = result.output.length > 300
323
- ? result.output.substring(0, 300) + '...'
324
- : result.output;
325
- console.log(chalk_1.default.green(`[SUCCESS] Result:\n${preview}`));
326
- // 通用重复检测:所有工具
327
- const currentToolCall = { tool: action.payload.tool_name || action.type, params: action.payload.parameters || action.payload };
328
- // 检查是否重复,并允许一定次数的重复(给 AI 时间完成多步骤任务)
329
- let duplicateCount = 0;
330
- const isDuplicate = lastToolCall &&
331
- lastToolCall.tool === currentToolCall.tool &&
332
- JSON.stringify(lastToolCall.params) === JSON.stringify(currentToolCall.params);
333
- if (isDuplicate) {
334
- duplicateCount = lastToolCall.count || 0;
335
- // 允许最多 2 次重复调用(给 AI 时间完成任务)
336
- if (duplicateCount >= 2) {
337
- console.log(chalk_1.default.yellow('[Duplicate Detection] 达到重复限制,强制完成'));
338
- console.log(chalk_1.default.cyan(`\n✓ ${action.payload.tool_name || action.type} 已完成\n`));
339
- if (agentRenderer) {
340
- agentRenderer.buffer = '';
341
- agentRenderer.quietMode = true;
342
- agentRenderer.finish();
343
- }
344
- break;
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.gray(`[Repeat Detection] 重复调用 (${duplicateCount + 1}/2),继续...`));
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 (error) {
497
- let errorMessage = '未知执行错误';
498
- if (error instanceof Error) {
499
- errorMessage = error.message;
500
- }
501
- else if (typeof error === 'string') {
502
- errorMessage = error;
503
- }
504
- console.log(chalk_1.default.red(`\n❌ 任务执行失败 [Action: ${thought?.type}]: ${errorMessage}`));
505
- this.context.addMessage("system", `执行引擎错误 [Action: ${thought?.type}]: ${errorMessage}`);
506
- break;
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
- if (turnCount >= maxTurns) {
510
- console.log(chalk_1.default.red(`\n⚠️ Max turns (${maxTurns}) reached.`));
572
+ catch (error) {
573
+ console.warn(chalk_1.default.yellow(`[Skill Learning] Failed: ${error}`));
511
574
  }
512
575
  }
513
576
  }