foliko 1.1.19 → 1.1.21

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.
@@ -202,7 +202,9 @@
202
202
  "Bash(node -e \"\nconst { createTools } = require\\('ai'\\);\nconsole.log\\('createTools exists:', typeof createTools\\);\n\" 2>&1)",
203
203
  "Bash(node -e \"\nconst poster = require\\('/d/code/foliko-plugins/poster-plugin'\\);\nconsole.log\\('poster.tools:', typeof poster.tools\\);\nconsole.log\\('poster.tools keys:', Object.keys\\(poster.tools || {}\\).slice\\(0, 5\\)\\);\n\" 2>&1)",
204
204
  "Bash(node -e \"require\\('./src/core/agent-chat.js'\\); console.log\\('OK'\\)\" 2>&1)",
205
- "Bash(node -e \"const ai = require\\('ai'\\); console.log\\('tool type:', typeof ai.tool\\)\")"
205
+ "Bash(node -e \"const ai = require\\('ai'\\); console.log\\('tool type:', typeof ai.tool\\)\")",
206
+ "Bash(taskkill //PID 26152 //F)",
207
+ "Bash(netstat -ano | grep 3000)"
206
208
  ]
207
209
  }
208
210
  }
@@ -198,9 +198,15 @@ class ChatUI {
198
198
  }
199
199
  }
200
200
  } catch (err) {
201
- console.log(err);
201
+ // 只打印简洁错误消息,不打印堆栈
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
+
202
208
  if (!interrupted) {
203
- console.error(`\n${colored('[错误]', RED)} ${err.message}\n`);
209
+ console.error(`\n${colored('[错误]', RED)} ${friendlyMessage}\n`);
204
210
  }
205
211
  } finally {
206
212
  process.removeListener('SIGINT', interruptHandler);
package/nul ADDED
@@ -0,0 +1,3 @@
1
+ dir: cannot access '/s': No such file or directory
2
+ dir: cannot access '/b': No such file or directory
3
+ dir: cannot access '*.png': No such file or directory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -4,7 +4,6 @@
4
4
 
5
5
  const { Plugin } = require('../../src/core/plugin-base')
6
6
  const { logger } = require('../../src/utils/logger')
7
- const { Agent } = require('../../src/core/agent')
8
7
  const { StepExecutor } = require('../../src/capabilities/workflow-engine')
9
8
  const { SystemPrompts, ThinkModePrompts, FreeThinkPrompts } = require('./constants')
10
9
 
@@ -37,13 +36,19 @@ class ExplorerLoop {
37
36
  const aiPlugin = this._framework.pluginManager?.get('ai')
38
37
  const llmConfig = aiPlugin ? aiPlugin.getConfig() : {}
39
38
 
40
- this._ambientAgent = new Agent(this._framework, {
39
+ // 使用 Subagent 代替 Agent,轻量化处理后台任务
40
+ this._ambientAgent = this._framework.createSubAgent({
41
41
  name: 'ambient-worker',
42
+ role: '后台任务执行助手',
43
+ description: '执行需要 AI 能力的后台任务',
42
44
  systemPrompt: SystemPrompts.ambientWorker,
43
- model: llmConfig.model,
44
- provider: llmConfig.provider,
45
- apiKey: llmConfig.apiKey,
46
- baseURL: llmConfig.baseURL
45
+ isAll:true,
46
+ llmConfig: {
47
+ model: llmConfig.model,
48
+ provider: llmConfig.provider,
49
+ apiKey: llmConfig.apiKey,
50
+ baseURL: llmConfig.baseURL
51
+ }
47
52
  })
48
53
 
49
54
  // 注册到主 agent(如果主 agent 已存在)
@@ -451,10 +456,22 @@ class ExplorerLoop {
451
456
  if (typeof value === 'string') {
452
457
  // 处理 ${actionId.output} 和 ${actionId.output.field} 格式
453
458
  const result = value.replace(/\$\{([^}]+)\}/g, (match, path) => {
459
+ // 特殊处理:${result.xxx} 引用上一步结果中的字段
460
+ if (path.startsWith('result.')) {
461
+ const fieldPath = path.replace(/^result\./, '')
462
+ const lastResult = stepOutputs._lastResult
463
+ if (lastResult) {
464
+ const val = this._getNestedValue(lastResult, fieldPath)
465
+ if (val !== undefined) return val
466
+ }
467
+ return match
468
+ }
469
+
454
470
  if (path === 'output' || path === 'result') {
455
- // {{result}} 引用上一步结果
471
+ // ${result} 引用上一步结果
456
472
  return stepOutputs._lastResult ?? match
457
473
  }
474
+
458
475
  if (path.startsWith('event.')) {
459
476
  // ${event.xxx} 引用事件数据
460
477
  let eventPath = path.replace(/^event\./, '')
@@ -478,10 +495,12 @@ class ExplorerLoop {
478
495
  }
479
496
  return match
480
497
  }
498
+
481
499
  if (path === 'event') {
482
500
  // ${event} 引用整个事件数据对象
483
501
  return eventData ?? match
484
502
  }
503
+
485
504
  if (path.includes('.')) {
486
505
  // ${actionId.output.field} 格式
487
506
  const [actionId, ...fieldParts] = path.split('.')
@@ -526,6 +545,66 @@ class ExplorerLoop {
526
545
  return current
527
546
  }
528
547
 
548
+ /**
549
+ * 判断 LLM 回复是否应该发送通知
550
+ * 识别模式:
551
+ * - 包含 "【通知】" 或 "[通知]" 开头的回复
552
+ * - JSON 中包含 notification_send 调用
553
+ * - 包含 "已发送通知"、"发送通知" 等关键词
554
+ */
555
+ _shouldSendNotification(resultText) {
556
+ if (!resultText) return false
557
+
558
+ // 检查通知标记
559
+ const notifyPatterns = [
560
+ /【通知】/,
561
+ /\[通知\]/,
562
+ /notification_send/,
563
+ /已发送通知/,
564
+ /发送通知成功/,
565
+ /通知已发送/,
566
+ /【提醒】/,
567
+ /\[提醒\]/,
568
+ /⚠️|🚨|🔔|📢/ // 常见通知 emoji
569
+ ]
570
+
571
+ return notifyPatterns.some(pattern => pattern.test(resultText))
572
+ }
573
+
574
+ /**
575
+ * 从 LLM 回复中提取通知内容
576
+ */
577
+ _extractNotification(resultText) {
578
+ if (!resultText) return null
579
+
580
+ // 尝试提取 【通知】... 内容
581
+ const zhMatch = resultText.match(/【通知】([\s\S]*?)(?:```|$)/)
582
+ if (zhMatch) {
583
+ const content = zhMatch[1].trim()
584
+ // 尝试分离标题和内容
585
+ const lines = content.split('\n').filter(l => l.trim())
586
+ if (lines.length >= 2) {
587
+ return { title: lines[0], message: lines.slice(1).join('\n') }
588
+ } else if (lines.length === 1) {
589
+ return { title: '通知提醒', message: lines[0] }
590
+ }
591
+ }
592
+
593
+ // 尝试提取 [通知]... 内容
594
+ const enMatch = resultText.match(/\[通知\]([\s\S]*?)(?:```|$)/)
595
+ if (enMatch) {
596
+ const content = enMatch[1].trim()
597
+ const lines = content.split('\n').filter(l => l.trim())
598
+ if (lines.length >= 1) {
599
+ return { title: lines[0], message: lines.slice(1).join('\n') || lines[0] }
600
+ }
601
+ }
602
+
603
+ // 默认:从结果中提取前100字符作为消息
604
+ const shortContent = resultText.substring(0, 200).replace(/```[\s\S]*?```/g, '').trim()
605
+ return { title: 'Ambient Agent 通知', message: shortContent }
606
+ }
607
+
529
608
  /**
530
609
  * 按顺序执行所有 actions
531
610
  * @param {Array} actions - action 数组
@@ -607,9 +686,36 @@ class ExplorerLoop {
607
686
  content = `${content}\n\n[事件上下文: ${JSON.stringify(context.variables._event)}]`
608
687
  }
609
688
 
689
+ // 添加通知能力提示
690
+ const notifyPrompt = `\n\n## 通知能力\n如果你需要发送通知,可以使用 notification_send 工具:\n- plugin: "notification"\n- tool: "notification_send" \n- args: { "title": "通知标题", "message": "通知内容" }\n\n**重要**:当判断结果满足条件时,你应该调用 notification_send 发送通知。不满足条件时,静默结束即可。`
691
+
610
692
  try {
611
- const result = await this._ambientAgent.pushMessage(content)
612
- return { success: true, result }
693
+ const result = await this._ambientAgent.chat(content + notifyPrompt, { maxRetries: 2 })
694
+
695
+ // Subagent 返回 { success, message, steps, error }
696
+ if (!result.success) {
697
+ return { success: false, error: result.error }
698
+ }
699
+
700
+ const resultText = result.message || ''
701
+
702
+ // 如果 result 中包含通知相关的内容,自动发送通知
703
+ if (this._shouldSendNotification(resultText)) {
704
+ const notification = this._extractNotification(resultText)
705
+ if (notification) {
706
+ // 通过事件系统发送通知
707
+ this._framework.emit('notification', {
708
+ title: notification.title || 'Ambient Agent 通知',
709
+ message: notification.message,
710
+ source: 'ambient',
711
+ level: 'info',
712
+ timestamp: new Date()
713
+ })
714
+ return { success: true, result: resultText, notified: true, notification }
715
+ }
716
+ }
717
+
718
+ return { success: true, result: resultText }
613
719
  } catch (err) {
614
720
  return { success: false, error: err.message }
615
721
  }
@@ -701,8 +807,12 @@ ${extensionsInfo}
701
807
  请直接返回 JSON 代码块。`
702
808
 
703
809
  try {
704
- const response = await this._ambientAgent.pushMessage(prompt)
705
- const responseText = typeof response === 'string' ? response : JSON.stringify(response)
810
+ const result = await this._ambientAgent.chat(prompt, { maxRetries: 2 })
811
+ if (!result.success) {
812
+ log.warn(`[ExplorerLoop] Subagent 调用失败: ${result.error}`)
813
+ return { success: false, error: result.error }
814
+ }
815
+ const responseText = result.message || ''
706
816
 
707
817
  // 尝试解析 LLM 返回的 JSON
708
818
  let toolChoice = null
@@ -183,7 +183,7 @@ class GoalManager {
183
183
  e.event === event.type && (now - new Date(e.timestamp).getTime()) < 2000
184
184
  )
185
185
  if (recentSameEvent) {
186
- log.info(`跳过重复事件: ${event.type} (2秒内不重复添加)`)
186
+ //log.info(`跳过重复事件: ${event.type} (2秒内不重复添加)`)
187
187
  return
188
188
  }
189
189
 
@@ -136,7 +136,7 @@ class ExtensionExecutorPlugin extends Plugin {
136
136
  }),
137
137
  execute: async (args) => {
138
138
  const { plugin, tool, args: toolArgs = {} } = args;
139
- log.info(` ext_call: plugin=${plugin}, tool=${tool}, args=${JSON.stringify(toolArgs)}`);
139
+ log.info(`[Extension] ext_call: plugin=${plugin}, tool=${tool}`);
140
140
 
141
141
  // MCP 服务器工具(已注册为 server_toolname 格式,如 github_search)
142
142
  if (plugin === 'mcp' && this._mcpExecutor) {
@@ -581,11 +581,12 @@ class SchedulerPlugin extends Plugin {
581
581
  // }
582
582
 
583
583
  // 根据 notify 参数决定是否发送通知
584
- if (task.notify !== false) {
584
+ // 只有当 LLM 返回了有效消息时才发送通知
585
+ if (task.notify !== false && responseText && responseText.trim()) {
585
586
  // 发送统一的通知事件
586
587
  this._framework.emit('notification', {
587
588
  title: task.name,
588
- message: responseText || task.message,
589
+ message: responseText,
589
590
  source: 'scheduler',
590
591
  level: 'info',
591
592
  sessionId: task.sessionId,
@@ -463,8 +463,11 @@ class WeixinPlugin extends Plugin {
463
463
  [用户发送了${type_dict[msg.type]}消息]\n
464
464
  文件列表:\n
465
465
  ${JSON.stringify(files,null,2)} \n
466
-
467
- *重要:* 不要试图读取文件内容!
466
+
467
+ **重要提醒:**
468
+ - 禁止读取任何文件内容!
469
+ - 禁止调用任何读取工具!
470
+ - 只告知用户文件保存路径
468
471
  `
469
472
  , msg)
470
473
  break
@@ -556,7 +559,7 @@ class WeixinPlugin extends Plugin {
556
559
  }else{
557
560
  const msg = `继续进行下一步`
558
561
  await this._sendMessageBatch(originalMsg, userId, msg, true)
559
- agent.sendMessage(msg, { sessionId, priority: 1 })
562
+ await agent.sendMessage(msg, { sessionId, priority: 1 })
560
563
  }
561
564
  lineBuffer = ''
562
565
  message=''
@@ -324,11 +324,10 @@ class AgentChatHandler extends EventEmitter {
324
324
  executeFunction: this.chatStream.bind(this),
325
325
  })
326
326
  .catch((err) => {
327
- logger.error('[AgentChat] Queue promise rejected:', err.message);
327
+ // 只设置 streamError,不打印日志(避免重复)
328
328
  if (!streamError) {
329
329
  streamError = err;
330
330
  }
331
- throw err; // 重新抛出,让 await queuePromise 能捕获
332
331
  });
333
332
 
334
333
  try {
@@ -489,6 +488,7 @@ class AgentChatHandler extends EventEmitter {
489
488
  AI_RetryError: 'AI 服务暂时不可用,请稍后重试',
490
489
  AI_APICallError: err?.isRetryable ? 'AI 服务暂时不可用,请稍后重试' : simpleMessage,
491
490
  AI_NoContentGeneratedError: 'AI 未生成有效内容,请重试',
491
+ AI_NoOutputGeneratedError: 'AI 未生成有效内容,请重试',
492
492
  AI_NoSuchModelError: '指定的 AI 模型不存在',
493
493
  AI_NoSuchProviderError: 'AI 提供商配置错误',
494
494
  AI_LoadAPIKeyError: 'AI API 密钥配置错误',
@@ -499,7 +499,7 @@ class AgentChatHandler extends EventEmitter {
499
499
  const friendlyMessage =
500
500
  errorMessages[errName] || (isRetryError ? 'AI 服务暂时不可用,请稍后重试' : simpleMessage);
501
501
 
502
- logger.error(`[AgentChat] Error [${errName}]: ${simpleMessage}`);
502
+ //logger.error(`[AgentChat] Error [${errName}]: ${simpleMessage}`);
503
503
  this.emit('message:error', { sessionId, error: friendlyMessage });
504
504
  yield { type: 'error', error: friendlyMessage };
505
505
  } finally {
@@ -606,6 +606,8 @@ class AgentChatHandler extends EventEmitter {
606
606
  */
607
607
  async _prepareSession(message, sessionId) {
608
608
  const messageStore = this._getSessionMessageStore(sessionId);
609
+ // 先加载 session 文件中的历史消息
610
+ this._chatSession.loadHistory(sessionId);
609
611
  const messages = messageStore.messages;
610
612
 
611
613
  // 刷新系统提示词
@@ -621,11 +623,17 @@ class AgentChatHandler extends EventEmitter {
621
623
  const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
622
624
  const limit = this._maxContextTokens * 0.5;
623
625
 
626
+ console.log(
627
+ `[_prepareSession] BEFORE: messages=${messages.length}, tokens=${totalTokens}/${limit}`
628
+ );
629
+
624
630
  if (totalTokens > limit) {
631
+ console.log(`[_prepareSession] Compressing context...`);
625
632
  logger.info(
626
633
  `Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
627
634
  );
628
635
  await this._compressContext(sessionId, messages, messageStore);
636
+ console.log(`[_prepareSession] AFTER compress: messages=${messages.length}`);
629
637
  }
630
638
 
631
639
  // 最终 token 检查
@@ -681,7 +689,7 @@ class AgentChatHandler extends EventEmitter {
681
689
 
682
690
  // 执行工具
683
691
  this.emit('tool-call', { name: toolName, args: cleanedArgs });
684
- logger.info(`[AgentChat] Calling tool: ${toolName}`);
692
+ logger.info(`[Tool] Call: ${toolName}`);
685
693
  try {
686
694
  const result = await toolDef.execute(cleanedArgs, this.agent.framework);
687
695
  this.emit('tool-result', { name: toolName, args: cleanedArgs, result });
@@ -743,26 +751,31 @@ class AgentChatHandler extends EventEmitter {
743
751
  try {
744
752
  const tokenCount = this._countMessagesTokens(inputMessages);
745
753
  const tokenLimit = this._maxContextTokens * 0.75;
746
- logger.debug(`prepareStep: messages=${inputMessages.length}, tokens=${tokenCount}`);
747
754
 
755
+ // 超过限制时,保留三分之一的消息
748
756
  if (tokenCount > tokenLimit) {
749
- logger.info(
750
- `Context compress triggered: messages=${inputMessages.length}, tokens=${tokenCount}/${tokenLimit}`
757
+ const keepCount = Math.max(10, Math.floor(inputMessages.length / 3));
758
+ console.log(
759
+ `[PrepareStep] Trimming: ${inputMessages.length} msgs -> ${keepCount}, tokens=${tokenCount}`
751
760
  );
752
- const pairedMessages = await this._compressContextForPrepare(inputMessages);
753
- if (pairedMessages.length !== inputMessages.length) {
754
- logger.info(`Compressed ${inputMessages.length} -> ${pairedMessages.length} messages`);
755
- inputMessages.length = 0;
756
- inputMessages.push(...pairedMessages);
757
- }
761
+ const trimmed = inputMessages.slice(-keepCount);
762
+ inputMessages.length = 0;
763
+ inputMessages.push(...trimmed);
758
764
  }
759
765
 
766
+ // 验证 tool-call 和 tool-result 配对
760
767
  const validated = this._validateMessagesPairing(inputMessages);
761
768
  if (validated.length !== inputMessages.length) {
769
+ console.log(
770
+ `[PrepareStep] After pairing validation: ${inputMessages.length} -> ${validated.length}`
771
+ );
762
772
  inputMessages.length = 0;
763
773
  inputMessages.push(...validated);
764
774
  }
775
+
776
+ // 验证 tool-call 格式
765
777
  this._validateToolCalls(inputMessages);
778
+
766
779
  return { messages: inputMessages };
767
780
  } catch (err) {
768
781
  logger.error('prepareStep error:', err.message, err.stack);
@@ -212,9 +212,14 @@ class ChatSession extends EventEmitter {
212
212
  return messageStore.messages;
213
213
  }
214
214
 
215
- // SessionContext 加载
215
+ // 确保 SessionContext 已初始化(会从文件加载或创建新的)
216
216
  if (this.agent?.framework) {
217
- const sessionCtx = this.agent.framework._sessionContexts?.get(sessionId);
217
+ // 先尝试获取已存在的 SessionContext
218
+ let sessionCtx = this.agent.framework._sessionContexts?.get(sessionId);
219
+ // 如果不存在,调用 getOrCreateSessionContext 来加载或创建
220
+ if (!sessionCtx) {
221
+ sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
222
+ }
218
223
  if (sessionCtx) {
219
224
  const messages = sessionCtx.getMessages();
220
225
  if (messages && messages.length > 0) {
@@ -15,7 +15,7 @@ const MODEL_CONTEXT_LIMITS = {
15
15
  'deepseek-chat': 128000,
16
16
  'deepseek-coder': 128000,
17
17
  'deepseek-reasoner': 128000,
18
- 'MiniMax-M2.7': 110000,
18
+ 'MiniMax-M2.7': 128000,
19
19
  'gpt-4': 100000,
20
20
  'gpt-4o': 100000,
21
21
  'gpt-4o-mini': 100000,
@@ -136,14 +136,14 @@ class ContextCompressor {
136
136
  const messagesToSummarize = otherMessages.slice(0, -this._keepRecentMessages);
137
137
 
138
138
  const compressedCount = messagesToSummarize.length;
139
+
139
140
  let summaryContent = '';
140
141
 
141
142
  // 使用 AI 生成摘要
142
143
  if (this._enableSmartCompress && this.agent?._chatHandler?._aiClient) {
143
144
  try {
144
145
  const summaryText = await this._summarizeMessages(messagesToSummarize);
145
- summaryContent = `[早期对话摘要]: ${summaryText}`;
146
- logger.info(`AI summary generated (${summaryText.length} chars)`);
146
+ summaryContent = `[早期对话摘要]: ${summaryText || '(无内容)'}`;
147
147
  } catch (err) {
148
148
  logger.warn('AI summary failed, using simple compression:', err.message);
149
149
  summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
@@ -409,23 +409,36 @@ class ContextCompressor {
409
409
  * @private
410
410
  */
411
411
  async _summarizeMessages(messages) {
412
- if (!this.agent?._chatHandler?._aiClient) {
412
+ const chatHandler = this.agent?._chatHandler;
413
+ if (!chatHandler?._aiClient) {
413
414
  throw new Error('AI client not available');
414
415
  }
415
416
 
416
417
  const { generateText } = await import('ai');
417
418
 
418
- const prompt = `请简要总结以下对话的主要内容,用 100 字以内描述:
419
-
420
- ${messages.map((m) => `${m.role}: ${typeof m.content === 'string' ? m.content : JSON.stringify(m.content)}`).join('\n')}
421
-
422
- 摘要:`;
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
+ ];
423
434
 
424
435
  try {
436
+ // _aiClient 已经是 model 对象,直接使用
437
+ const model = chatHandler._aiClient;
425
438
  const result = await generateText({
426
- model: this.agent._chatHandler._aiClient,
427
- prompt: prompt,
428
- maxTokens: 200,
439
+ model: model,
440
+ messages: chatMessages,
441
+ maxTokens: 1000,
429
442
  });
430
443
  return result.text || '';
431
444
  } catch (err) {
@@ -458,6 +471,10 @@ ${messages.map((m) => `${m.role}: ${typeof m.content === 'string' ? m.content :
458
471
 
459
472
  let total = 4; // 角色开销
460
473
 
474
+ if (!msg.content) {
475
+ return total;
476
+ }
477
+
461
478
  if (typeof msg.content === 'string') {
462
479
  total += Math.ceil(Buffer.byteLength(msg.content, 'utf8') / 4);
463
480
  } else if (Array.isArray(msg.content)) {
@@ -470,7 +487,11 @@ ${messages.map((m) => `${m.role}: ${typeof m.content === 'string' ? m.content :
470
487
  }
471
488
  } else if (block.type === 'tool-result' || block.type === 'tool_result') {
472
489
  const content =
473
- typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
490
+ block.content == null
491
+ ? ''
492
+ : typeof block.content === 'string'
493
+ ? block.content
494
+ : JSON.stringify(block.content);
474
495
  total += Math.ceil(Buffer.byteLength(content, 'utf8') / 4);
475
496
  }
476
497
  }