foliko 1.1.63 → 1.1.65

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.
Files changed (48) hide show
  1. package/.agent/data/plugins-state.json +8 -0
  2. package/.agent/sessions/cli_default.json +258 -260
  3. package/cli/bin/foliko.js +2 -2
  4. package/cli/src/commands/chat.js +15 -26
  5. package/cli/src/ui/chat-ui.js +102 -165
  6. package/cli/src/ui/footer-bar.js +7 -32
  7. package/cli/src/ui/message-bubble.js +24 -2
  8. package/cli/src/ui/status-bar.js +177 -0
  9. package/package.json +1 -2
  10. package/plugins/audit-plugin.js +11 -7
  11. package/plugins/coordinator-plugin.js +14 -12
  12. package/plugins/data-splitter-plugin.js +323 -0
  13. package/plugins/default-plugins.js +12 -1
  14. package/plugins/extension-executor-plugin.js +2 -2
  15. package/plugins/file-system-plugin.js +68 -50
  16. package/plugins/gate-trading.js +10 -10
  17. package/plugins/install-plugin.js +3 -3
  18. package/plugins/memory-plugin.js +8 -11
  19. package/plugins/plugin-manager-plugin.js +9 -11
  20. package/plugins/qq-plugin.js +9 -9
  21. package/plugins/rules-plugin.js +7 -7
  22. package/plugins/scheduler-plugin.js +22 -18
  23. package/plugins/session-plugin.js +14 -14
  24. package/plugins/storage-plugin.js +11 -10
  25. package/plugins/subagent-plugin.js +13 -9
  26. package/plugins/think-plugin.js +63 -59
  27. package/plugins/tools-plugin.js +8 -8
  28. package/plugins/weixin-plugin.js +5 -5
  29. package/src/capabilities/skill-manager.js +23 -15
  30. package/src/capabilities/workflow-engine.js +2 -2
  31. package/src/core/agent-chat.js +70 -26
  32. package/src/core/agent.js +17 -27
  33. package/src/core/chat-session.js +7 -161
  34. package/src/core/constants.js +198 -0
  35. package/src/core/context-compressor.js +6 -181
  36. package/src/core/framework.js +125 -6
  37. package/src/core/plugin-base.js +7 -5
  38. package/src/core/provider.js +6 -0
  39. package/src/core/subagent.js +16 -135
  40. package/src/core/tool-executor.js +2 -70
  41. package/src/executors/mcp-executor.js +12 -10
  42. package/src/utils/chat-queue.js +11 -22
  43. package/src/utils/data-splitter.js +345 -0
  44. package/src/utils/download.js +5 -4
  45. package/src/utils/message-validator.js +283 -0
  46. package/src/utils/retry.js +168 -22
  47. package/src/utils/sandbox.js +60 -207
  48. package/cli/src/utils/debounce.js +0 -106
@@ -9,7 +9,7 @@
9
9
 
10
10
  const { logger } = require('../utils/logger');
11
11
  const { TokenCounter } = require('./token-counter');
12
- const { Subagent } = require('./subagent');
12
+ const { validateMessagesPairing, filterPairedMessages } = require('../utils/message-validator');
13
13
 
14
14
  // 模型上下文限制表
15
15
  const MODEL_CONTEXT_LIMITS = {
@@ -30,7 +30,7 @@ const MODEL_CONTEXT_LIMITS = {
30
30
  };
31
31
 
32
32
  // 压缩超时
33
- const COMPRESSION_TIMEOUT = 1200000;
33
+ const COMPRESSION_TIMEOUT_MS = 1200000;
34
34
 
35
35
  class ContextCompressor {
36
36
  /**
@@ -128,8 +128,8 @@ class ContextCompressor {
128
128
  return new Promise((_, reject) => {
129
129
  this._compressionTimeoutId = setTimeout(() => {
130
130
  this._compressionTimeoutId = null;
131
- reject(new Error(`Compression timeout (${COMPRESSION_TIMEOUT}ms)`));
132
- }, COMPRESSION_TIMEOUT);
131
+ reject(new Error(`Compression timeout (${COMPRESSION_TIMEOUT_MS}ms)`));
132
+ }, COMPRESSION_TIMEOUT_MS);
133
133
  });
134
134
  }
135
135
 
@@ -242,122 +242,7 @@ class ContextCompressor {
242
242
  * @private
243
243
  */
244
244
  _filterPairedMessages(messages) {
245
- // 工具类型检查
246
- const isToolCall = (item) =>
247
- item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
248
- const isToolResult = (item) =>
249
- item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;
250
-
251
- // 第一遍:找出所有 tool call 和它们的 results
252
- const assistantToolCalls = new Map(); // toolCallId -> assistant message index
253
- const toolResults = new Map(); // toolCallId -> tool result indices
254
-
255
- for (let i = 0; i < messages.length; i++) {
256
- const msg = messages[i];
257
- if (msg.role === 'assistant') {
258
- // 格式1: msg.content 中的 tool-call 块
259
- if (msg.content) {
260
- const content = Array.isArray(msg.content) ? msg.content : [msg.content];
261
- for (const item of content) {
262
- if (isToolCall(item)) {
263
- assistantToolCalls.set(item.toolCallId, i);
264
- }
265
- }
266
- }
267
- // 格式2: msg.tool_calls 数组 (OpenAI 格式)
268
- if (Array.isArray(msg.tool_calls)) {
269
- for (const tc of msg.tool_calls) {
270
- if (tc.id) {
271
- assistantToolCalls.set(tc.id, i);
272
- }
273
- }
274
- }
275
- }
276
- if (msg.role === 'tool' && Array.isArray(msg.content)) {
277
- for (const item of msg.content) {
278
- if (isToolResult(item)) {
279
- if (!toolResults.has(item.toolCallId)) {
280
- toolResults.set(item.toolCallId, []);
281
- }
282
- toolResults.get(item.toolCallId).push(i);
283
- }
284
- }
285
- }
286
- }
287
-
288
- // 第二遍:找出哪些 assistant 消息需要保留
289
- const assistantIndicesToKeep = new Set();
290
- for (let i = 0; i < messages.length; i++) {
291
- const msg = messages[i];
292
- if (msg.role === 'assistant') {
293
- let hasToolCall = false;
294
- // 检查 msg.content 格式
295
- if (msg.content) {
296
- const content = Array.isArray(msg.content) ? msg.content : [msg.content];
297
- for (const item of content) {
298
- if (isToolCall(item)) {
299
- hasToolCall = true;
300
- break;
301
- }
302
- }
303
- }
304
- // 检查 msg.tool_calls 格式 (OpenAI)
305
- if (!hasToolCall && Array.isArray(msg.tool_calls)) {
306
- for (const tc of msg.tool_calls) {
307
- if (tc.id) {
308
- hasToolCall = true;
309
- break;
310
- }
311
- }
312
- }
313
- if (hasToolCall) {
314
- assistantIndicesToKeep.add(i);
315
- } else if (i >= messages.length - 3) {
316
- // 保留最近几条 assistant 消息
317
- assistantIndicesToKeep.add(i);
318
- }
319
- }
320
- }
321
-
322
- // 如果有孤儿的 tool call(没有 result),保留该 assistant 消息
323
- for (const [toolCallId, assistantIdx] of assistantToolCalls) {
324
- if (!toolResults.has(toolCallId)) {
325
- assistantIndicesToKeep.add(assistantIdx);
326
- }
327
- }
328
-
329
- // 第三遍:确定哪些 tool result 要保留
330
- const indicesToKeep = new Set();
331
- for (let i = 0; i < messages.length; i++) {
332
- const msg = messages[i];
333
- if (msg.role === 'tool') {
334
- let hasPairedAssistant = false;
335
- const content = Array.isArray(msg.content) ? msg.content : [msg.content];
336
- for (const item of content) {
337
- if (isToolResult(item)) {
338
- const assistantIdx = assistantToolCalls.get(item.toolCallId);
339
- if (assistantIdx !== undefined && assistantIndicesToKeep.has(assistantIdx)) {
340
- hasPairedAssistant = true;
341
- break;
342
- }
343
- }
344
- }
345
- if (hasPairedAssistant) {
346
- indicesToKeep.add(i);
347
- }
348
- } else if (msg.role === 'assistant') {
349
- if (assistantIndicesToKeep.has(i)) {
350
- indicesToKeep.add(i);
351
- }
352
- } else {
353
- // user, system 等角色默认保留
354
- indicesToKeep.add(i);
355
- }
356
- }
357
-
358
- // 按索引排序并返回
359
- const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
360
- return sortedIndices.map((i) => messages[i]);
245
+ return filterPairedMessages(messages);
361
246
  }
362
247
 
363
248
  /**
@@ -366,67 +251,7 @@ class ContextCompressor {
366
251
  * @returns {Array} 验证后的消息
367
252
  */
368
253
  validateMessagesPairing(messages) {
369
- // 收集所有 assistant 的 tool-call IDs(兼容多种类型名)
370
- const assistantToolCallIds = new Set();
371
- for (const msg of messages) {
372
- if (msg.role === 'assistant') {
373
- // 格式1: msg.content 中的 tool-call 块
374
- if (Array.isArray(msg.content)) {
375
- for (const item of msg.content) {
376
- if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
377
- assistantToolCallIds.add(item.toolCallId);
378
- }
379
- }
380
- }
381
- // 格式2: msg.tool_calls 数组 (OpenAI 格式)
382
- if (Array.isArray(msg.tool_calls)) {
383
- for (const tc of msg.tool_calls) {
384
- if (tc.id) {
385
- assistantToolCallIds.add(tc.id);
386
- }
387
- }
388
- }
389
- }
390
- }
391
-
392
- // 检查并删除没有配对的 tool result
393
- let removedCount = 0;
394
- for (const msg of messages) {
395
- if (msg.role === 'tool' && Array.isArray(msg.content)) {
396
- const originalLength = msg.content.length;
397
- msg.content = msg.content.filter((item) => {
398
- // 兼容 tool-result 和 tool_result 两种类型
399
- if (
400
- item &&
401
- (item.type === 'tool-result' || item.type === 'tool_result') &&
402
- item.toolCallId
403
- ) {
404
- if (!assistantToolCallIds.has(item.toolCallId)) {
405
- removedCount++;
406
- return false; // 删除没有配对的 tool result
407
- }
408
- }
409
- return true;
410
- });
411
-
412
- // 如果所有 content 都被删除了,标记整个消息待删除
413
- if (msg.content.length === 0 && originalLength > 0) {
414
- msg._orphaned = true;
415
- }
416
- }
417
- }
418
-
419
- // 移除被标记为 orphaned 的 tool 消息
420
- const originalLength = messages.length;
421
- const filtered = messages.filter((msg) => !(msg.role === 'tool' && msg._orphaned));
422
-
423
- if (removedCount > 0 || filtered.length !== originalLength) {
424
- logger.debug(
425
- `Removed ${removedCount} orphaned tool-results, ${originalLength - filtered.length} orphaned tool messages`
426
- );
427
- }
428
-
429
- return filtered;
254
+ return validateMessagesPairing(messages);
430
255
  }
431
256
 
432
257
  /**
@@ -324,6 +324,55 @@ class Framework extends EventEmitter {
324
324
  return this.toolRegistry.getAll();
325
325
  }
326
326
 
327
+ // ============================================================================
328
+ // 公开 API — 减少外部直接访问私有属性
329
+ // ============================================================================
330
+
331
+ /**
332
+ * 获取主 Agent
333
+ * @returns {Agent|null}
334
+ */
335
+ getMainAgent() {
336
+ return this._mainAgent;
337
+ }
338
+
339
+ /**
340
+ * 获取主 Agent 的 System Prompt
341
+ * @returns {string}
342
+ */
343
+ getSystemPrompt() {
344
+ if (this._mainAgent) {
345
+ return this._mainAgent._buildSystemPrompt();
346
+ }
347
+ return '';
348
+ }
349
+
350
+ /**
351
+ * 获取插件实例(安全访问)
352
+ * @param {string} name - 插件名称
353
+ * @returns {Object|null}
354
+ */
355
+ getPluginInstance(name) {
356
+ const entry = this.pluginManager.get(name);
357
+ return entry || null;
358
+ }
359
+
360
+ /**
361
+ * 获取所有 Agent 列表
362
+ * @returns {Array<Agent>}
363
+ */
364
+ getAgents() {
365
+ return [...this._agents];
366
+ }
367
+
368
+ /**
369
+ * 获取所有 SessionContext 的 ID 列表
370
+ * @returns {string[]}
371
+ */
372
+ listSessionContexts() {
373
+ return Array.from(this._sessionContexts.keys());
374
+ }
375
+
327
376
  // ============================================================================
328
377
  // 事件描述注册(供 Ambient Agent 使用)
329
378
  // ============================================================================
@@ -551,6 +600,68 @@ class Framework extends EventEmitter {
551
600
  }
552
601
  }
553
602
 
603
+ // Session TTL 清理配置
604
+ _sessionTTL = 30 * 60 * 1000; // 默认 30 分钟
605
+ _sessionCleanupInterval = null;
606
+
607
+ /**
608
+ * 配置 Session TTL
609
+ * @param {number} ttlMs - TTL 毫秒数
610
+ */
611
+ setSessionTTL(ttlMs) {
612
+ this._sessionTTL = ttlMs;
613
+ return this;
614
+ }
615
+
616
+ /**
617
+ * 启动 Session 自动清理
618
+ * @param {number} [intervalMs=60000] - 检查间隔
619
+ */
620
+ startSessionCleanup(intervalMs = 60000) {
621
+ if (this._sessionCleanupInterval) {
622
+ clearInterval(this._sessionCleanupInterval);
623
+ }
624
+ this._sessionCleanupInterval = setInterval(() => {
625
+ this._cleanupStaleSessions();
626
+ }, intervalMs);
627
+ // 允许进程退出时不阻止
628
+ if (this._sessionCleanupInterval.unref) {
629
+ this._sessionCleanupInterval.unref();
630
+ }
631
+ return this;
632
+ }
633
+
634
+ /**
635
+ * 停止 Session 自动清理
636
+ */
637
+ stopSessionCleanup() {
638
+ if (this._sessionCleanupInterval) {
639
+ clearInterval(this._sessionCleanupInterval);
640
+ this._sessionCleanupInterval = null;
641
+ }
642
+ return this;
643
+ }
644
+
645
+ /**
646
+ * 清理过期的 Session
647
+ * @private
648
+ */
649
+ _cleanupStaleSessions() {
650
+ const now = Date.now();
651
+ const staleIds = [];
652
+ for (const [sessionId, ctx] of this._sessionContexts) {
653
+ if (ctx.metadata && (now - ctx.metadata.lastActive) > this._sessionTTL) {
654
+ staleIds.push(sessionId);
655
+ }
656
+ }
657
+ for (const id of staleIds) {
658
+ this.destroySessionContext(id);
659
+ }
660
+ if (staleIds.length > 0) {
661
+ this.logger.debug(`Cleaned up ${staleIds.length} stale sessions`);
662
+ }
663
+ }
664
+
554
665
  /**
555
666
  * 列出所有活跃的 Session ID
556
667
  * @returns {string[]}
@@ -737,9 +848,9 @@ class Framework extends EventEmitter {
737
848
 
738
849
  /**
739
850
  * 同步主Agent的提示词部分到所有子Agent
740
- * @private
851
+ * @public — 插件可能需要调用
741
852
  */
742
- _syncPromptPartsToSubagents() {
853
+ syncPromptPartsToSubagents() {
743
854
  if (!this._mainAgent || !this._mainAgent._systemPromptBuilder) return;
744
855
  const mainParts = this._mainAgent._systemPromptBuilder._parts;
745
856
  if (!mainParts) return;
@@ -784,8 +895,9 @@ class Framework extends EventEmitter {
784
895
  lines.push('');
785
896
 
786
897
  // 2. 主Agent的系统提示词(基础部分)
787
- if (this._mainAgent) {
788
- const mainPrompt = this._mainAgent._originalPrompt;
898
+ const mainAgent = this.getMainAgent();
899
+ if (mainAgent) {
900
+ const mainPrompt = mainAgent.getOriginalPrompt();
789
901
  if (mainPrompt) {
790
902
  lines.push('## 主Agent的系统提示词');
791
903
  lines.push(mainPrompt);
@@ -988,10 +1100,17 @@ class Framework extends EventEmitter {
988
1100
  this._toolRegistryListeners = [];
989
1101
  }
990
1102
 
991
- // 卸载所有插件
1103
+ // 停止 Session 清理
1104
+ this.stopSessionCleanup();
1105
+
1106
+ // 卸载所有插件(逐个保护,防止一个失败阻断后续)
992
1107
  const plugins = this.pluginManager.getAll();
993
1108
  for (const { name } of plugins) {
994
- await this.pluginManager.unload(name);
1109
+ try {
1110
+ await this.pluginManager.unload(name);
1111
+ } catch (err) {
1112
+ this.logger.warn(`Failed to unload plugin '${name}': ${err.message}`);
1113
+ }
995
1114
  }
996
1115
 
997
1116
  // 清空工具
@@ -286,10 +286,11 @@ class Plugin {
286
286
  this._promptParts.push({ name, priority, provider });
287
287
 
288
288
  // 如果主 agent 已存在,直接注册
289
- if (this._framework._mainAgent) {
290
- this._framework._mainAgent.registerPromptPart(name, priority, provider);
289
+ const mainAgent = this._framework.getMainAgent();
290
+ if (mainAgent) {
291
+ mainAgent.registerPromptPart(name, priority, provider);
291
292
  // 同步到所有子Agent
292
- this._framework._syncPromptPartsToSubagents();
293
+ this._framework.syncPromptPartsToSubagents();
293
294
  return;
294
295
  }
295
296
 
@@ -307,14 +308,15 @@ class Plugin {
307
308
  if (this._deferredPromptParts) {
308
309
  for (const part of this._deferredPromptParts) {
309
310
  try {
310
- this._framework._mainAgent?.registerPromptPart(part.name, part.priority, part.provider);
311
+ const mainAgent = this._framework.getMainAgent();
312
+ mainAgent?.registerPromptPart(part.name, part.priority, part.provider);
311
313
  } catch (err) {
312
314
  log.error(` Failed to register prompt part ${part.name}:`, err.message);
313
315
  }
314
316
  }
315
317
  this._deferredPromptParts = null;
316
318
  // 同步到所有子Agent
317
- this._framework._syncPromptPartsToSubagents();
319
+ this._framework.syncPromptPartsToSubagents();
318
320
  }
319
321
  }, 100);
320
322
  });
@@ -14,26 +14,32 @@ const DEFAULT_PROVIDERS = {
14
14
  openai: {
15
15
  name: 'OpenAI',
16
16
  baseURL: 'https://api.openai.com/v1',
17
+ defaultModel: 'gpt-4o',
17
18
  },
18
19
  ollama: {
19
20
  name: 'Ollama',
20
21
  baseURL: 'http://localhost:11434/v1',
22
+ defaultModel: 'llama3',
21
23
  },
22
24
  lmstudio: {
23
25
  name: 'LM Studio',
24
26
  baseURL: 'http://localhost:1234/v1',
27
+ defaultModel: 'local-model',
25
28
  },
26
29
  deepseek: {
27
30
  name: 'DeepSeek',
28
31
  baseURL: 'https://api.deepseek.com/v1',
32
+ defaultModel: 'deepseek-chat',
29
33
  },
30
34
  anthropic: {
31
35
  name: 'Anthropic',
32
36
  baseURL: 'https://api.anthropic.com/v1',
37
+ defaultModel: 'claude-sonnet-4-20250514',
33
38
  },
34
39
  minimax: {
35
40
  name: 'MiniMax',
36
41
  baseURL: 'https://api.minimaxi.com/v1',
42
+ defaultModel: 'MiniMax-M2.7',
37
43
  },
38
44
  };
39
45
 
@@ -8,8 +8,9 @@ const { EventEmitter } = require('../utils/event-emitter');
8
8
  const { cleanResponse } = require('../utils');
9
9
  const { generateText, tool, stepCountIs, isLoopFinished, RetryError, ToolLoopAgent } = require('ai');
10
10
  const { z } = require('zod');
11
- const fs=require('fs/promises')
12
11
  const { logger } = require('../utils/logger');
12
+ const { validateAll } = require('../utils/message-validator');
13
+ const { isNetworkError, calculateDelay } = require('../utils/retry');
13
14
 
14
15
  class Subagent extends EventEmitter {
15
16
  /**
@@ -94,64 +95,7 @@ class Subagent extends EventEmitter {
94
95
  * @private
95
96
  */
96
97
  _validateMessagesPairing(messages) {
97
- // 收集所有 assistant 的 tool-call IDs
98
- const assistantToolCallIds = new Set();
99
- for (const msg of messages) {
100
- if (msg.role === 'assistant') {
101
- // 格式1: msg.content 中的 tool-call 块
102
- if (Array.isArray(msg.content)) {
103
- for (const item of msg.content) {
104
- if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
105
- assistantToolCallIds.add(item.toolCallId);
106
- }
107
- }
108
- }
109
- // 格式2: msg.tool_calls 数组 (OpenAI 格式)
110
- if (Array.isArray(msg.tool_calls)) {
111
- for (const tc of msg.tool_calls) {
112
- if (tc.id) {
113
- assistantToolCallIds.add(tc.id);
114
- }
115
- }
116
- }
117
- }
118
- }
119
-
120
- // 检查并删除没有配对的 tool result
121
- let removedCount = 0;
122
- for (const msg of messages) {
123
- if (msg.role === 'tool' && Array.isArray(msg.content)) {
124
- const originalLength = msg.content.length;
125
- msg.content = msg.content.filter((item) => {
126
- if (
127
- item &&
128
- (item.type === 'tool-result' || item.type === 'tool_result') &&
129
- item.toolCallId
130
- ) {
131
- if (!assistantToolCallIds.has(item.toolCallId)) {
132
- removedCount++;
133
- return false;
134
- }
135
- }
136
- return true;
137
- });
138
-
139
- if (msg.content.length === 0 && originalLength > 0) {
140
- msg._orphaned = true;
141
- }
142
- }
143
- }
144
-
145
- const originalLength = messages.length;
146
- const filtered = messages.filter((msg) => !(msg.role === 'tool' && msg._orphaned));
147
-
148
- if (removedCount > 0 || filtered.length !== originalLength) {
149
- logger.debug(
150
- `[Subagent:${this.name}] _validateMessagesPairing: removed ${removedCount} orphaned tool-results, ${originalLength - filtered.length} orphaned tool messages`
151
- );
152
- }
153
-
154
- return filtered;
98
+ return validateAll(messages);
155
99
  }
156
100
 
157
101
  /**
@@ -159,64 +103,8 @@ class Subagent extends EventEmitter {
159
103
  * @private
160
104
  */
161
105
  _validateToolCalls(messages) {
162
- let fixedCount = 0;
163
- const invalidatedToolCallIds = new Set();
164
-
165
- for (const msg of messages) {
166
- if (msg.role === 'assistant' && Array.isArray(msg.content)) {
167
- for (const item of msg.content) {
168
- if (item.type !== 'tool-call' && item.type !== 'tool-use') {
169
- continue;
170
- }
171
-
172
- const input = item.input;
173
- if (typeof input !== 'string') {
174
- continue;
175
- }
176
-
177
- const trimmed = input.trim();
178
- if (trimmed === '{' || trimmed === '' || !trimmed.startsWith('{')) {
179
- if (item.toolCallId) {
180
- invalidatedToolCallIds.add(item.toolCallId);
181
- }
182
- logger.warn(
183
- `[Subagent:${this.name}] _validateToolCalls: invalid tool-call input="${input}", toolCallId=${item.toolCallId}`
184
- );
185
- item.type = 'text';
186
- item.text = `(工具调用 ${item.toolName} 参数不完整,已跳过)`;
187
- delete item.toolCallId;
188
- delete item.toolName;
189
- delete item.input;
190
- fixedCount++;
191
- }
192
- }
193
- }
194
- }
195
-
196
- if (invalidatedToolCallIds.size > 0) {
197
- for (const msg of messages) {
198
- if (msg.role === 'tool' && Array.isArray(msg.content)) {
199
- const oldLen = msg.content.length;
200
- msg.content = msg.content.filter((item) => {
201
- if (item.type !== 'tool-result' && item.type !== 'tool_result') {
202
- return true;
203
- }
204
- if (item.toolCallId && invalidatedToolCallIds.has(item.toolCallId)) {
205
- logger.warn(
206
- `[Subagent:${this.name}] _validateToolCalls: removing orphaned tool-result with toolCallId=${item.toolCallId}`
207
- );
208
- fixedCount++;
209
- return false;
210
- }
211
- return true;
212
- });
213
- }
214
- }
215
- }
216
-
217
- if (fixedCount > 0) {
218
- logger.info(`[Subagent:${this.name}] _validateToolCalls: Fixed ${fixedCount} incomplete tool calls/results`);
219
- }
106
+ // validateAll 内部已包含 validateMessagesPairing + validateToolCalls
107
+ // Subagent chat() 中 _validateMessagesPairing 已调用 validateAll,所以这里不再重复
220
108
  }
221
109
 
222
110
  /**
@@ -327,8 +215,9 @@ class Subagent extends EventEmitter {
327
215
  }
328
216
 
329
217
  // 2. 主Agent的系统提示词
330
- if (this.framework && this.framework._mainAgent) {
331
- const mainPrompt = this.framework._mainAgent._originalPrompt;
218
+ const mainAgent = this.framework?.getMainAgent?.();
219
+ if (mainAgent) {
220
+ const mainPrompt = mainAgent.getOriginalPrompt();
332
221
  if (mainPrompt) {
333
222
  lines.push('## 主Agent的系统提示词');
334
223
  lines.push(mainPrompt);
@@ -472,18 +361,8 @@ class Subagent extends EventEmitter {
472
361
  } catch (err) {
473
362
  logger.warn(`[Subagent:${this.name}] generateText error: ${err.message}`);
474
363
  lastError = err;
475
- const errName = err?.name || '';
476
- const errMessage = err?.message || '';
477
- const errCode = err?.code || err?.status;
478
-
479
- // 判断是否是重试错误
480
- const isRetryError =
481
- RetryError.isInstance?.(err) ||
482
- errName === 'AI_RetryError' ||
483
- errName === 'RetryError' ||
484
- err?.reason === 'maxRetriesExceeded';
485
-
486
- if (isRetryError && attempt < maxRetries) {
364
+ // 统一使用 retry.js 的错误判断
365
+ if (isNetworkError(err) && attempt < maxRetries) {
487
366
  logger.warn(
488
367
  `[Subagent:${this.name}] AI 服务暂时不可用,正在重试 (${attempt + 1}/${maxRetries})`
489
368
  );
@@ -511,11 +390,13 @@ class Subagent extends EventEmitter {
511
390
  */
512
391
  _getFriendlyError(err) {
513
392
  const errName = err?.name || '';
393
+
394
+ // 先用 isNetworkError 统一判断
395
+ if (isNetworkError(err)) {
396
+ return 'AI 服务暂时不可用,请稍后重试';
397
+ }
398
+
514
399
  const errorMessages = {
515
- AI_RetryError: 'AI 服务暂时不可用,请稍后重试',
516
- AI_APICallError: err?.isRetryable
517
- ? 'AI 服务暂时不可用,请稍后重试'
518
- : err.message?.split('\n')[0] || 'AI 请求失败',
519
400
  AI_NoContentGeneratedError: 'AI 未生成有效内容,请重试',
520
401
  AI_NoSuchModelError: '指定的 AI 模型不存在',
521
402
  AI_NoSuchProviderError: 'AI 提供商配置错误',