foliko 1.1.21 → 1.1.23

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.
@@ -204,7 +204,19 @@
204
204
  "Bash(node -e \"require\\('./src/core/agent-chat.js'\\); console.log\\('OK'\\)\" 2>&1)",
205
205
  "Bash(node -e \"const ai = require\\('ai'\\); console.log\\('tool type:', typeof ai.tool\\)\")",
206
206
  "Bash(taskkill //PID 26152 //F)",
207
- "Bash(netstat -ano | grep 3000)"
207
+ "Bash(netstat -ano | grep 3000)",
208
+ "Bash(node test-error.js 2>&1)",
209
+ "Bash(node -c src/core/agent-chat.js && node -c src/utils/chat-queue.js && node -c cli/src/ui/chat-ui.js && node -c plugins/weixin-plugin.js && node -c plugins/telegram-plugin.js)",
210
+ "Bash(node -c plugins/weixin-plugin.js && node -c plugins/telegram-plugin.js && node -c cli/src/ui/chat-ui.js)",
211
+ "Bash(node -c src/utils/chat-queue.js)",
212
+ "Bash(node -c src/utils/logger.js)",
213
+ "WebFetch(domain:github.com)",
214
+ "mcp__MiniMax__web_search",
215
+ "WebFetch(domain:www.npmjs.com)",
216
+ "Bash(cd D:/code/vb-agent && npm show @anthropic-ai/tokenizer 2>&1 | head -30)",
217
+ "Bash(cd D:/code/vb-agent && node -e \"const t = require\\('@anthropic-ai/tokenizer'\\); console.log\\(typeof t.countTokens\\)\" 2>&1)",
218
+ "Bash(cd D:/code/vb-agent && node -e \"const { countTokens } = require\\('@anthropic-ai/tokenizer'\\); console.log\\('hello:', countTokens\\('hello'\\)\\); console.log\\('你好:', countTokens\\('你好'\\)\\);\")",
219
+ "Bash(node -c src/core/subagent.js)"
208
220
  ]
209
221
  }
210
222
  }
@@ -126,7 +126,7 @@ async function runContinuousTest(agent, options) {
126
126
  } else if (chunk.type === 'tool-call') {
127
127
  console.log(`\n[工具调用] ${chunk.toolName} ${JSON.stringify(chunk.input)}`);
128
128
  } else if (chunk.type === 'error') {
129
- console.error(`\n[错误] ${chunk.error}`);
129
+ //console.error(`\n[错误] ${chunk.error}`);
130
130
  }
131
131
  }
132
132
  console.log('\n' + '-'.repeat(50));
@@ -136,6 +136,8 @@ class ChatUI {
136
136
 
137
137
  process.on('SIGINT', interruptHandler);
138
138
 
139
+ let hasError = false;
140
+
139
141
  try {
140
142
  const renderState = { inThink: false, inCodeBlock: false };
141
143
  const { sessionId } = this;
@@ -150,7 +152,6 @@ class ChatUI {
150
152
  // 创建 session scope 过滤事件
151
153
  const sessionScope = this.agent.createSessionScope(sessionId);
152
154
 
153
- // 监听流式数据
154
155
  sessionScope.on('stream:chunk', ({ chunk }) => {
155
156
  if (interrupted) return;
156
157
 
@@ -167,11 +168,15 @@ class ChatUI {
167
168
  console.log(renderLine(line, renderState));
168
169
  }
169
170
  }
170
- } else if (chunk.type === 'error') {
171
- console.error(`\n${colored('[错误]', RED)} ${chunk.error}`);
172
171
  }
173
172
  });
174
173
 
174
+ // 监听 queue:failed 事件(处理队列级别的最终错误)
175
+ sessionScope.on('queue:failed', ({ error }) => {
176
+ hasError = true;
177
+ console.error(`\n${colored('[错误]', RED)} ${error}`);
178
+ });
179
+
175
180
  // 调用 sendMessage 触发事件
176
181
  const res = await this.agent.sendMessage(message, { sessionId });
177
182
  // 清理
@@ -199,13 +204,14 @@ class ChatUI {
199
204
  }
200
205
  } catch (err) {
201
206
  // 只打印简洁错误消息,不打印堆栈
202
- const errName = err?.name || '';
203
- const isRetryError = errName === 'AI_RetryError' || errName === 'RetryError';
204
- const friendlyMessage = isRetryError
205
- ? 'AI 服务暂时不可用,请稍后重试'
206
- : (err.message || '未知错误').split('\n')[0];
207
+ // 避免与 message:error 事件重复打印
208
+ if (!hasError && !interrupted) {
209
+ const errName = err?.name || '';
210
+ const isRetryError = errName === 'AI_RetryError' || errName === 'RetryError';
211
+ const friendlyMessage = isRetryError
212
+ ? 'AI 服务暂时不可用,请稍后重试'
213
+ : (err.message || '未知错误').split('\n')[0];
207
214
 
208
- if (!interrupted) {
209
215
  console.error(`\n${colored('[错误]', RED)} ${friendlyMessage}\n`);
210
216
  }
211
217
  } finally {
@@ -89,7 +89,9 @@ async function main() {
89
89
  } else if (chunk.type === 'tool-result') {
90
90
  console.log('\n[工具结果]', JSON.stringify(chunk.result).substring(0, 100));
91
91
  } else if (chunk.type === 'error') {
92
- console.error('\n[错误]', chunk.error);
92
+ throw new Error('sdfsdf');
93
+
94
+ //console.error('\n[错误]', chunk.error);
93
95
  }
94
96
  }
95
97
 
@@ -110,6 +112,10 @@ async function main() {
110
112
  '请帮我注册一个 GET 路由:\n- 路径:/test\n- 处理函数:return "1232432"\n\n注册完成后,用 web_request 工具测试访问这个路由,确认返回 "1232432"'
111
113
  );
112
114
 
115
+ await ask(
116
+ '关闭服务'
117
+ );
118
+
113
119
  console.log('\n--- 测试完成,现在你可以继续对话 ---\n');
114
120
 
115
121
  // 主循环
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -53,6 +53,7 @@
53
53
  "@ai-sdk/openai": "^3.0.41",
54
54
  "@ai-sdk/openai-compatible": "^2.0.35",
55
55
  "@anthropic-ai/sdk": "^0.39.0",
56
+ "@anthropic-ai/tokenizer": "^0.0.4",
56
57
  "@chnak/weixin-bot": "^1.2.9",
57
58
  "@chnak/zod-to-markdown": "1.0.7",
58
59
  "@hono/node-server": "^1.19.11",
@@ -329,6 +329,14 @@ class TelegramPlugin extends Plugin {
329
329
  }
330
330
  })
331
331
 
332
+ // 监听错误事件
333
+ sessionScope.on('queue:failed', async ({ error }) => {
334
+ await this._bot.editMessageText(escapeMarkdown(`❌ ${error}`), {
335
+ chat_id: chatId,
336
+ message_id: thinkingMsg.message_id
337
+ })
338
+ })
339
+
332
340
  await agent.sendMessage(text, { sessionId })
333
341
 
334
342
  sessionScope.removeAllListeners()
@@ -537,8 +537,6 @@ class WeixinPlugin extends Plugin {
537
537
  message+=colored(`[工具开始](${chunk.toolName})`,YELLOW)
538
538
  }else if(chunk.type==='tool-result'){
539
539
  message+=colored(`[工具结束](${chunk.toolName})`,YELLOW)
540
- }else if(chunk.type==='error'){
541
- message+=colored(`[错误](${chunk.error})`,RED)
542
540
  }
543
541
  while (message.includes('\n')) {
544
542
  const nlIndex = message.indexOf('\n');
@@ -564,7 +562,7 @@ class WeixinPlugin extends Plugin {
564
562
  lineBuffer = ''
565
563
  message=''
566
564
  })
567
- sessionScope.on('message:error', async ({error}) => {
565
+ sessionScope.on('queue:failed', async ({error}) => {
568
566
  this.stopTypingInterval()
569
567
  await this._sendMessageBatch(originalMsg, userId, error, true)
570
568
  lineBuffer = ''
@@ -0,0 +1 @@
1
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1080" height="1920" viewBox="0,0,1080,1920"><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="none" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,1920v-1920h1080v1920z" fill="#f5f5f0" stroke="none" stroke-width="1"></path><text x="540" y="120" fill="#c41e3a" stroke="none" stroke-width="1" font-family="Microsoft YaHei, SimHei, KaiTi, seguiemj, sans-serif" font-weight="bold" font-size="48" text-anchor="middle">广东信宜特产</text><text x="540" y="200" fill="#2c1810" stroke="none" stroke-width="1" font-family="Microsoft YaHei, SimHei, KaiTi, seguiemj, sans-serif" font-weight="bold" font-size="72" text-anchor="middle">钱排银妃三华李</text><text x="540" y="300" fill="#8b4513" stroke="none" stroke-width="1" font-family="Microsoft YaHei, SimHei, KaiTi, seguiemj, sans-serif" font-weight="normal" font-size="36" text-anchor="middle">果中爱马仕 | 酸甜脆爽</text><text x="540" y="480" fill="#c41e3a" stroke="none" stroke-width="1" font-family="Microsoft YaHei, SimHei, KaiTi, seguiemj, sans-serif" font-weight="bold" font-size="32" text-anchor="middle">核心卖点</text><g><rect x="140" y="520" width="300" height="100" rx="12" fill="#c41e3a" stroke="none"/><text x="290" y="570" fill="#ffffff" stroke="none" font-size="28" text-anchor="middle" font-family="Microsoft YaHei">📍 产地直发</text><text x="290" y="600" fill="#ffe4e1" stroke="none" font-size="18" text-anchor="middle" font-family="Microsoft YaHei">广东信宜钱排镇</text></g><g><rect x="640" y="520" width="300" height="100" rx="12" fill="#c41e3a" stroke="none"/><text x="790" y="570" fill="#ffffff" stroke="none" font-size="28" text-anchor="middle" font-family="Microsoft YaHei">⭐ 精选大果</text><text x="790" y="600" fill="#ffe4e1" stroke="none" font-size="18" text-anchor="middle" font-family="Microsoft YaHei">单果30g以上</text></g><g><rect x="140" y="650" width="300" height="100" rx="12" fill="#228b22" stroke="none"/><text x="290" y="700" fill="#ffffff" stroke="none" font-size="28" text-anchor="middle" font-family="Microsoft YaHei">🌿 新鲜采摘</text><text x="290" y="730" fill="#90ee90" stroke="none" font-size="18" text-anchor="middle" font-family="Microsoft YaHei">当季现摘 冷链配送</text></g><g><rect x="640" y="650" width="300" height="100" rx="12" fill="#228b22" stroke="none"/><text x="790" y="700" fill="#ffffff" stroke="none" font-size="28" text-anchor="middle" font-family="Microsoft YaHei">💚 酸甜脆爽</text><text x="790" y="730" fill="#90ee90" stroke="none" font-size="18" text-anchor="middle" font-family="Microsoft YaHei">自然成熟 口感极佳</text></g><g><rect x="240" y="800" width="600" height="180" rx="20" fill="#ffffff" stroke="#c41e3a" stroke-width="4"/><text x="540" y="870" fill="#c41e3a" stroke="none" font-size="24" text-anchor="middle" font-family="Microsoft YaHei">活动价格</text><text x="540" y="940" fill="#c41e3a" stroke="none" font-size="72" font-weight="bold" text-anchor="middle" font-family="Microsoft YaHei">¥19.8</text><text x="540" y="980" fill="#666666" stroke="none" font-size="20" text-anchor="middle" font-family="Microsoft YaHei">5斤装 顺丰包邮</text></g><g><rect x="340" y="1100" width="400" height="400" rx="20" fill="#fff5ee" stroke="#deb887" stroke-width="2"/><text x="540" y="1300" fill="#ff6b6b" stroke="none" font-size="120" text-anchor="middle">🍑</text><text x="540" y="1450" fill="#8b4513" stroke="none" font-size="28" text-anchor="middle" font-family="Microsoft YaHei">钱排三华李</text><text x="540" y="1480" fill="#d2691e" stroke="none" font-size="20" text-anchor="middle" font-family="Microsoft YaHei">新鲜水果 产地直发</text></g><g><rect x="340" y="1550" width="400" height="250" rx="16" fill="#ffffff" stroke="#e0e0e0" stroke-width="2"/><text x="540" y="1600" fill="#333333" stroke="none" font-size="24" text-anchor="middle" font-family="Microsoft YaHei">[ 扫码下单 ]</text><text x="540" y="1640" fill="#666666" stroke="none" font-size="18" text-anchor="middle" font-family="Microsoft YaHei">手机淘宝/京东 扫码购买</text><text x="540" y="1700" fill="#c41e3a" stroke="none" font-size="20" text-anchor="middle" font-family="Microsoft YaHei">长按识别二维码</text></g><text x="540" y="1850" fill="#666666" stroke="none" font-size="24" text-anchor="middle" font-family="Microsoft YaHei">长按识别 立即购买</text><text x="540" y="1880" fill="#999999" stroke="none" font-size="18" text-anchor="middle" font-family="Microsoft YaHei">正宗钱排三华李 假一赔十</text></g></svg>
@@ -21,6 +21,7 @@ const {
21
21
  } = require('ai');
22
22
  const { prepareMessagesForAPI, cleanResponse } = require('../utils');
23
23
  const { ChatQueueManager } = require('../utils/chat-queue');
24
+ const { countTokens } = require('@anthropic-ai/tokenizer');
24
25
  const fs = require('fs/promises');
25
26
  // 新模块
26
27
  const { ChatSession } = require('./chat-session');
@@ -56,7 +57,7 @@ class AgentChatHandler extends EventEmitter {
56
57
  agent,
57
58
  maxConcurrent: config.maxConcurrent || 1,
58
59
  retryAttempts: config.retryAttempts || 3,
59
- retryDelay: config.retryDelay || 1000,
60
+ retryDelay: config.retryDelay || 10000,
60
61
  });
61
62
 
62
63
  // ToolExecutor: 工具管理
@@ -438,8 +439,13 @@ class AgentChatHandler extends EventEmitter {
438
439
  prepareStep: this._createPrepareStep(),
439
440
  });
440
441
 
442
+ // AI SDK 错误回调:仅记录日志,不在这里处理(统一由 catch/yield 处理)
443
+ const handleSDKError = ({ error }) => {
444
+ logger.debug(`[AgentChat] SDK onError: ${error?.message}`);
445
+ };
446
+
441
447
  const result = await framework.runInSession(sessionId, {}, async () => {
442
- return agent.stream({ messages, ...this.providerOptions });
448
+ return agent.stream({ messages, ...this.providerOptions, onError: handleSDKError });
443
449
  });
444
450
 
445
451
  const stream = result.fullStream;
@@ -458,7 +464,9 @@ class AgentChatHandler extends EventEmitter {
458
464
  } else if (part.type === 'tool-result') {
459
465
  yield { type: 'tool-result', toolName: part.toolName, result: part.output };
460
466
  } else if (part.type === 'error') {
461
- yield { type: 'error', error: part.error };
467
+ // 统一错误消息
468
+ const errMsg = part.error?.message || 'AI 服务错误';
469
+ yield { type: 'error', error: errMsg.split('\n')[0] };
462
470
  }
463
471
  }
464
472
 
@@ -472,35 +480,10 @@ class AgentChatHandler extends EventEmitter {
472
480
  const userMsg = messages[messages.length - finishMessages.length - 1];
473
481
  this.emit('message', { content: fullText, sessionId: sessionId, userMessage: userMsg });
474
482
  } catch (err) {
475
- // 提取简洁的错误消息(不包含堆栈)
476
- const simpleMessage = err?.message?.split('\n')[0] || '未知错误';
477
- const errName = err?.name || '';
478
-
479
- // 使用 AI SDK 的错误类型判断
480
- const isRetryError =
481
- RetryError.isInstance?.(err) ||
482
- errName === 'AI_RetryError' ||
483
- errName === 'RetryError' ||
484
- err?.reason === 'maxRetriesExceeded';
485
-
486
- // AI SDK 的错误 map
487
- const errorMessages = {
488
- AI_RetryError: 'AI 服务暂时不可用,请稍后重试',
489
- AI_APICallError: err?.isRetryable ? 'AI 服务暂时不可用,请稍后重试' : simpleMessage,
490
- AI_NoContentGeneratedError: 'AI 未生成有效内容,请重试',
491
- AI_NoOutputGeneratedError: 'AI 未生成有效内容,请重试',
492
- AI_NoSuchModelError: '指定的 AI 模型不存在',
493
- AI_NoSuchProviderError: 'AI 提供商配置错误',
494
- AI_LoadAPIKeyError: 'AI API 密钥配置错误',
495
- AI_InvalidPromptError: '输入内容格式错误',
496
- AI_ToolCallRepairError: '工具调用处理失败,请重试',
497
- };
498
-
499
- const friendlyMessage =
500
- errorMessages[errName] || (isRetryError ? 'AI 服务暂时不可用,请稍后重试' : simpleMessage);
483
+ // 统一错误处理:提取简洁消息并通过 yield 传递
484
+ const friendlyMessage = err?.message?.split('\n')[0] || '未知错误';
501
485
 
502
- //logger.error(`[AgentChat] Error [${errName}]: ${simpleMessage}`);
503
- this.emit('message:error', { sessionId, error: friendlyMessage });
486
+ this.emit('message:error', { sessionId, error: friendlyMessage, originalError: err });
504
487
  yield { type: 'error', error: friendlyMessage };
505
488
  } finally {
506
489
  const messageStore = this._getSessionMessageStore(sessionId);
@@ -623,12 +606,9 @@ class AgentChatHandler extends EventEmitter {
623
606
  const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
624
607
  const limit = this._maxContextTokens * 0.5;
625
608
 
626
- console.log(
627
- `[_prepareSession] BEFORE: messages=${messages.length}, tokens=${totalTokens}/${limit}`
628
- );
609
+ logger.info(`BEFORE: messages=${messages.length}, tokens=${totalTokens}/${limit}`);
629
610
 
630
611
  if (totalTokens > limit) {
631
- console.log(`[_prepareSession] Compressing context...`);
632
612
  logger.info(
633
613
  `Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
634
614
  );
@@ -946,9 +926,9 @@ class AgentChatHandler extends EventEmitter {
946
926
  * 计算 token
947
927
  * @private
948
928
  */
949
- _countTokens(text, bytesPerToken = 4) {
929
+ _countTokens(text) {
950
930
  if (!text) return 0;
951
- return Math.ceil(Buffer.byteLength(String(text), 'utf8') / bytesPerToken);
931
+ return countTokens(String(text));
952
932
  }
953
933
 
954
934
  /**
@@ -9,25 +9,26 @@
9
9
 
10
10
  const { logger } = require('../utils/logger');
11
11
  const { zodSchemaToMarkdown, zodSchemaToTable } = require('@chnak/zod-to-markdown');
12
+ const { Subagent } = require('./subagent');
12
13
 
13
14
  // 模型上下文限制表
14
15
  const MODEL_CONTEXT_LIMITS = {
15
- 'deepseek-chat': 128000,
16
- 'deepseek-coder': 128000,
17
- 'deepseek-reasoner': 128000,
18
- 'MiniMax-M2.7': 128000,
16
+ 'deepseek-chat': 110000,
17
+ 'deepseek-coder': 110000,
18
+ 'deepseek-reasoner': 110000,
19
+ 'MiniMax-M2.7': 110000,
19
20
  'gpt-4': 100000,
20
21
  'gpt-4o': 100000,
21
22
  'gpt-4o-mini': 100000,
22
23
  'gpt-4-turbo': 100000,
23
- 'claude-3-5-sonnet': 150000,
24
- 'claude-3-opus': 150000,
25
- 'claude-3-sonnet': 150000,
26
- 'glm-5.1': 200000,
24
+ 'claude-3-5-sonnet': 110000,
25
+ 'claude-3-opus': 110000,
26
+ 'claude-3-sonnet': 110000,
27
+ 'glm-5.1': 110000,
27
28
  };
28
29
 
29
30
  // 压缩超时
30
- const COMPRESSION_TIMEOUT = 120000;
31
+ const COMPRESSION_TIMEOUT = 1200000;
31
32
 
32
33
  class ContextCompressor {
33
34
  /**
@@ -409,38 +410,53 @@ class ContextCompressor {
409
410
  * @private
410
411
  */
411
412
  async _summarizeMessages(messages) {
412
- const chatHandler = this.agent?._chatHandler;
413
- if (!chatHandler?._aiClient) {
414
- throw new Error('AI client not available');
413
+ if (!this.framework) {
414
+ throw new Error('Framework not available');
415
415
  }
416
416
 
417
- const { generateText } = await import('ai');
418
-
419
- // 使用 AI SDK 消息格式
420
- const chatMessages = [
421
- {
422
- role: 'user',
423
- content:
424
- '请简要总结以下对话的主要内容,用1000字以内描述:\n' +
425
- messages
426
- .map((m) => {
427
- const content =
428
- typeof m.content === 'string' ? m.content : JSON.stringify(m.content || '');
429
- return `${m.role}: ${content}`;
430
- })
431
- .join('\n'),
432
- },
433
- ];
417
+ // 提取消息文本内容,忽略工具调用等
418
+ const msg_str = messages
419
+ .map((m) => {
420
+ let text = '';
421
+ if (typeof m.content === 'string') {
422
+ text = m.content;
423
+ } else if (Array.isArray(m.content)) {
424
+ // 只提取 text 类型,忽略 tool-call、tool-result 等
425
+ text = m.content
426
+ .filter((c) => c.type === 'text')
427
+ .map((c) => c.text)
428
+ .join(' ');
429
+ }
430
+ // 跳过 tool 角色的结果(太长且无意义)
431
+ if (m.role === 'tool') {
432
+ return '';
433
+ }
434
+ return `${m.role}: ${text}`;
435
+ })
436
+ .filter((line) => line.length > 0)
437
+ .join('\n');
438
+
439
+ const task = `请总结以下对话,只保留有意义的信息(如任务需求、决策结论、重要上下文),忽略无意义的闲聊和重复内容。用1000字以内描述:\n
440
+
441
+ ${msg_str}`;
434
442
 
435
443
  try {
436
- // _aiClient 已经是 model 对象,直接使用
437
- const model = chatHandler._aiClient;
438
- const result = await generateText({
439
- model: model,
440
- messages: chatMessages,
441
- maxTokens: 1000,
444
+ // 使用 framework.createSubAgent 创建 Subagent 进行摘要
445
+ // maxRetries: 0 禁用重试,避免长时间等待
446
+ const subagent = this.framework.createSubAgent({
447
+ name: 'context-compressor',
448
+ role: '信息提取专家',
449
+ systemPrompt:
450
+ '你是一个对话摘要助手。只提取和保留有意义的信息(如任务需求、决策结论、重要上下文),忽略无意义的闲聊、重复内容和中间过程。输出要简洁。',
451
+ maxRetries: 0,
452
+ disableTools: true,
442
453
  });
443
- return result.text || '';
454
+
455
+ const result = await subagent.chat(task);
456
+ if (result.success) {
457
+ return result.message;
458
+ }
459
+ throw new Error(result.error || '摘要生成失败');
444
460
  } catch (err) {
445
461
  logger.warn('Summarize failed:', err.message);
446
462
  throw err;
@@ -586,6 +586,8 @@ class Framework extends EventEmitter {
586
586
  tools = {},
587
587
  parentTools = [], // 如果不传,默认继承主agent所有工具
588
588
  llmConfig = null,
589
+ maxRetries,
590
+ disableTools,
589
591
  } = config;
590
592
 
591
593
  // 获取 AI 配置
@@ -626,6 +628,8 @@ class Framework extends EventEmitter {
626
628
  tools: toolList,
627
629
  parentTools: parentTools,
628
630
  framework: this, // 传递 framework 引用用于动态构建系统提示词
631
+ maxRetries,
632
+ disableTools,
629
633
  });
630
634
 
631
635
  this._agents.push(subagent);
@@ -55,6 +55,12 @@ class Subagent extends EventEmitter {
55
55
  this._tools = new Map();
56
56
  this._registerTools(config.tools || []);
57
57
 
58
+ // 重试配置
59
+ this.maxRetries = config.maxRetries ?? 2;
60
+ this.retryDelay = config.retryDelay ?? 5000;
61
+ // 禁用工具
62
+ this.disableTools = config.disableTools ?? false;
63
+
58
64
  this.framework.once('framework:ready', () => {
59
65
  const extExecutor = this.framework?.pluginManager?.get('extension-executor');
60
66
  extExecutor._refreshAllAgentsExtPrompt(this.framework);
@@ -118,6 +124,8 @@ class Subagent extends EventEmitter {
118
124
  * 构建 AI 工具格式
119
125
  */
120
126
  _buildAITools() {
127
+ // 禁用工具时返回空
128
+
121
129
  const tools = {};
122
130
  // 从父Agent继承工具
123
131
  const all_tools = this.framework.getTools();
@@ -165,9 +173,9 @@ class Subagent extends EventEmitter {
165
173
  }
166
174
  lines.push('');
167
175
 
168
- // if(this._customSystemPrompt){
169
- // lines.push(this._customSystemPrompt)
170
- // }
176
+ if (this._customSystemPrompt) {
177
+ lines.push(this._customSystemPrompt);
178
+ }
171
179
 
172
180
  // 2. 主Agent的系统提示词
173
181
  if (this.framework && this.framework._mainAgent) {
@@ -234,28 +242,42 @@ class Subagent extends EventEmitter {
234
242
  */
235
243
  async chat(task, options = {}) {
236
244
  const maxSteps = options?.maxSteps || 30;
237
- const maxRetries = options?.maxRetries ?? 2;
245
+ const maxRetries = options?.maxRetries ?? this.maxRetries;
246
+ const retryDelay = options?.retryDelay ?? this.retryDelay;
238
247
  const aiProvider = this._getAIProvider();
239
248
  const messages = [];
240
249
  messages.push({ role: 'user', content: task });
241
250
 
251
+ logger.info(
252
+ `[Subagent:${this.name}] chat called, maxRetries=${maxRetries}, disableTools=${this.disableTools}`
253
+ );
254
+
242
255
  let lastError;
243
256
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
257
+ logger.info(
258
+ `[Subagent:${this.name}] attempt ${attempt + 1}, model=${this.model}, provider=${this.provider}`
259
+ );
244
260
  try {
245
- const tools = this._buildAITools();
246
- const systemPrompt = this._buildSystemPrompt();
261
+ const tools = this.disableTools ? {} : this._buildAITools();
262
+ const systemPrompt = this.disableTools
263
+ ? this._customSystemPrompt
264
+ : this._buildSystemPrompt();
265
+ // logger.info(`[Subagent:${this.name}] calling generateText...`);
247
266
  const result = await generateText({
267
+ ...this.providerOptions,
248
268
  model: aiProvider(this.model),
249
269
  system: systemPrompt,
250
270
  messages: messages,
251
271
  tools: tools,
252
272
  stopWhen: stepCountIs(maxSteps),
253
- ...this.providerOptions,
254
273
  abortSignal: options.signal,
255
274
  onChunk: (chunk) => {
256
275
  this.emit('chunk', chunk);
257
276
  },
258
277
  });
278
+ logger.info(
279
+ `[Subagent:${this.name}] generateText completed, text length=${result.text.length}`
280
+ );
259
281
  messages.push(...result.response.messages);
260
282
  const full_text = cleanResponse(result.text);
261
283
  this.emit('complete', { message: full_text, steps: result.steps?.length || 0 });
@@ -265,6 +287,7 @@ class Subagent extends EventEmitter {
265
287
  steps: result.steps?.length || 0,
266
288
  };
267
289
  } catch (err) {
290
+ logger.warn(`[Subagent:${this.name}] generateText error: ${err.message}`);
268
291
  lastError = err;
269
292
  const errName = err?.name || '';
270
293
 
@@ -276,8 +299,10 @@ class Subagent extends EventEmitter {
276
299
  err?.reason === 'maxRetriesExceeded';
277
300
 
278
301
  if (isRetryError && attempt < maxRetries) {
279
- logger.warn(`[Subagent:${this.name}] AI 服务不可用,${attempt + 1}/${maxRetries} 次重试`);
280
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
302
+ logger.warn(
303
+ `[Subagent:${this.name}] AI 服务不可用,正在重试 (${attempt + 1}/${maxRetries})`
304
+ );
305
+ await new Promise((resolve) => setTimeout(resolve, retryDelay * Math.pow(2, attempt)));
281
306
  continue;
282
307
  }
283
308
 
@@ -1,5 +1,7 @@
1
1
  const { EventEmitter } = require('./event-emitter');
2
2
  const { cleanResponse } = require('./index');
3
+ const { logger } = require('./logger');
4
+ const log = logger.child('ChatQueue');
3
5
  // ChatQueueManager.js
4
6
  class ChatQueueManager extends EventEmitter {
5
7
  constructor(options = {}) {
@@ -8,7 +10,7 @@ class ChatQueueManager extends EventEmitter {
8
10
  this.isProcessing = false;
9
11
  this.maxConcurrent = options.maxConcurrent || 1;
10
12
  this.activeCount = 0;
11
- this.retryAttempts = options.retryAttempts || 5;
13
+ this.retryAttempts = options.retryAttempts || 3;
12
14
  this.retryDelay = options.retryDelay || 10000;
13
15
  }
14
16
 
@@ -72,13 +74,8 @@ class ChatQueueManager extends EventEmitter {
72
74
  const result = await this.executeWithRetry(item);
73
75
  // 检查 result 是否有错误(而不是通过 try/catch)
74
76
  if (result.error) {
75
- console.log('[ChatQueue] Rejecting with error from result:', result.error.message);
76
- item.reject(result.error);
77
- this.emit('queue:failed', {
78
- requestId: item.id,
79
- sessionId: item.sessionId,
80
- error: result.error.message,
81
- });
77
+ //console.log('[ChatQueue] Rejecting with error from result:', result.error.message);
78
+ throw result.error;
82
79
  } else {
83
80
  item.resolve(result);
84
81
  this.emit('queue:completed', {
@@ -88,7 +85,7 @@ class ChatQueueManager extends EventEmitter {
88
85
  });
89
86
  }
90
87
  } catch (error) {
91
- console.log('[ChatQueue] Rejecting with thrown error:', error.message);
88
+ log.info('[ChatQueue] Rejecting Error:', error.message);
92
89
  item.reject(error);
93
90
  this.emit('queue:failed', {
94
91
  requestId: item.id,
@@ -119,30 +116,18 @@ class ChatQueueManager extends EventEmitter {
119
116
 
120
117
  // 检查是否有错误(通过返回的 result.error)
121
118
  if (result.error) {
122
- console.log(
123
- '[ChatQueue] executeWithRetry: attempt',
124
- attempt,
125
- 'got error:',
126
- result.error.message
127
- );
128
119
  lastError = result.error;
129
120
  if (attempt < this.retryAttempts && this.isRetryableError(lastError)) {
130
121
  await this.sleep(this.retryDelay * Math.pow(2, attempt - 1));
131
122
  continue;
132
123
  }
133
- // 返回 result,让 processQueue 处理错误
134
- console.log('[ChatQueue] executeWithRetry: returning result with error, no more retries');
135
- return result;
124
+ // 重试耗尽,直接抛出 result.error
125
+ throw lastError;
136
126
  }
137
127
 
138
128
  return result;
139
129
  } catch (error) {
140
- console.log(
141
- '[ChatQueue] executeWithRetry: attempt',
142
- attempt,
143
- 'threw error:',
144
- error.message
145
- );
130
+ log.info('[ChatQueue] executeWithRetry: ', attempt, 'error:', error.message);
146
131
  lastError = error;
147
132
  if (attempt < this.retryAttempts && this.isRetryableError(error)) {
148
133
  await this.sleep(this.retryDelay * Math.pow(2, attempt - 1));
@@ -161,7 +146,7 @@ class ChatQueueManager extends EventEmitter {
161
146
 
162
147
  const friendlyError = new Error(friendlyMessage);
163
148
  friendlyError.originalError = lastError;
164
- console.log('[ChatQueue] executeWithRetry: throwing friendly error:', friendlyMessage);
149
+ //log.info('[ChatQueue] executeWithRetry: throwing friendly error:', friendlyMessage);
165
150
  throw friendlyError;
166
151
  }
167
152
 
@@ -196,10 +181,7 @@ class ChatQueueManager extends EventEmitter {
196
181
  ? 'AI 服务暂时不可用,请稍后重试'
197
182
  : (err.message || err.toString()).split('\n')[0];
198
183
 
199
- console.log(
200
- '[ChatQueue] executeStream caught error, converting to friendly:',
201
- friendlyMessage
202
- );
184
+ log.info('[ChatQueue] executeStream Error:', friendlyMessage);
203
185
  chunks.push({ type: 'error', error: friendlyMessage });
204
186
  this.emit('stream:chunk', {
205
187
  requestId: item.id,
@@ -217,7 +199,7 @@ class ChatQueueManager extends EventEmitter {
217
199
  const error = new Error(errorChunk.error || 'Stream error');
218
200
  error.chunks = chunks;
219
201
  error.isStreamError = true;
220
- console.log('[ChatQueue] executeStream returning result with error:', error.message);
202
+ log.info('[ChatQueue] executeStream Error:', error.message);
221
203
  return {
222
204
  chunks,
223
205
  content: cleanResponse(''),