foliko 1.0.83 → 1.0.84

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 (167) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  28. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  29. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  30. package/.agent/ARCHITECTURE.md +288 -0
  31. package/.agent/agents/ambient-agent.md +57 -0
  32. package/.agent/agents/debugger.md +55 -0
  33. package/.agent/agents/email-assistant.md +49 -0
  34. package/.agent/agents/file-manager.md +42 -0
  35. package/.agent/agents/python-developer.md +60 -0
  36. package/.agent/agents/scheduler.md +59 -0
  37. package/.agent/agents/web-developer.md +45 -0
  38. package/.agent/data/default.json +355 -6
  39. package/.agent/data/plugins-state.json +185 -146
  40. package/.agent/data/puppeteer-sessions/undefined.json +6 -0
  41. package/.agent/mcp_config.json +0 -1
  42. package/.agent/mcp_config_updated.json +12 -0
  43. package/.agent/plugins/puppeteer-plugin/README.md +147 -0
  44. package/.agent/plugins/puppeteer-plugin/index.js +1320 -0
  45. package/.agent/plugins/puppeteer-plugin/package.json +9 -0
  46. package/.agent/plugins.json +5 -14
  47. package/.agent/rules/GEMINI.md +273 -0
  48. package/.agent/rules/allow-rule.md +77 -0
  49. package/.agent/rules/log-rule.md +83 -0
  50. package/.agent/rules/security-rule.md +93 -0
  51. package/.agent/scripts/auto_preview.py +148 -0
  52. package/.agent/scripts/checklist.py +217 -0
  53. package/.agent/scripts/session_manager.py +120 -0
  54. package/.agent/scripts/verify_all.py +327 -0
  55. package/.agent/skills/api-patterns/SKILL.md +81 -0
  56. package/.agent/skills/api-patterns/api-style.md +42 -0
  57. package/.agent/skills/api-patterns/auth.md +24 -0
  58. package/.agent/skills/api-patterns/documentation.md +26 -0
  59. package/.agent/skills/api-patterns/graphql.md +41 -0
  60. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  61. package/.agent/skills/api-patterns/response.md +37 -0
  62. package/.agent/skills/api-patterns/rest.md +40 -0
  63. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  64. package/.agent/skills/api-patterns/security-testing.md +122 -0
  65. package/.agent/skills/api-patterns/trpc.md +41 -0
  66. package/.agent/skills/api-patterns/versioning.md +22 -0
  67. package/.agent/skills/app-builder/SKILL.md +75 -0
  68. package/.agent/skills/app-builder/agent-coordination.md +71 -0
  69. package/.agent/skills/app-builder/feature-building.md +53 -0
  70. package/.agent/skills/app-builder/project-detection.md +34 -0
  71. package/.agent/skills/app-builder/scaffolding.md +118 -0
  72. package/.agent/skills/app-builder/tech-stack.md +40 -0
  73. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  74. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  75. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  76. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  77. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  78. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  79. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  80. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  81. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  82. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  83. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  84. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  85. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  86. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  87. package/.agent/skills/architecture/SKILL.md +55 -0
  88. package/.agent/skills/architecture/context-discovery.md +43 -0
  89. package/.agent/skills/architecture/examples.md +94 -0
  90. package/.agent/skills/architecture/pattern-selection.md +68 -0
  91. package/.agent/skills/architecture/patterns-reference.md +50 -0
  92. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  93. package/.agent/skills/clean-code/SKILL.md +201 -0
  94. package/.agent/skills/doc.md +177 -0
  95. package/.agent/skills/frontend-design/SKILL.md +418 -0
  96. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  97. package/.agent/skills/frontend-design/color-system.md +311 -0
  98. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  99. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  100. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  101. package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
  102. package/.agent/skills/frontend-design/typography-system.md +345 -0
  103. package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
  104. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  105. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  106. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  107. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  108. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  109. package/.agent/workflows/brainstorm.md +113 -0
  110. package/.agent/workflows/create.md +59 -0
  111. package/.agent/workflows/debug.md +103 -0
  112. package/.agent/workflows/deploy.md +176 -0
  113. package/.agent/workflows/enhance.md +63 -0
  114. package/.agent/workflows/orchestrate.md +237 -0
  115. package/.agent/workflows/plan.md +89 -0
  116. package/.agent/workflows/preview.md +81 -0
  117. package/.agent/workflows/simple-test.md +42 -0
  118. package/.agent/workflows/status.md +86 -0
  119. package/.agent/workflows/structured-orchestrate.md +180 -0
  120. package/.agent/workflows/test.md +144 -0
  121. package/.agent/workflows/ui-ux-pro-max.md +296 -0
  122. package/.claude/settings.local.json +178 -171
  123. package/.env.example +56 -56
  124. package/cli/src/commands/plugin.js +482 -0
  125. package/cli/src/index.js +7 -0
  126. package/cli/src/utils/plugin-config.js +50 -0
  127. package/package.json +1 -1
  128. package/plugins/audit-plugin.js +2 -0
  129. package/plugins/extension-executor-plugin.js +30 -0
  130. package/plugins/plugin-manager-plugin.js +402 -0
  131. package/skills/find-skills/AGENTS.md +162 -162
  132. package/skills/find-skills/SKILL.md +133 -133
  133. package/skills/foliko-dev/SKILL.md +563 -563
  134. package/skills/plugin-guide/SKILL.md +139 -0
  135. package/skills/python-plugin-dev/SKILL.md +238 -238
  136. package/src/core/agent-chat.js +115 -46
  137. package/src/core/framework.js +42 -1
  138. package/src/executors/mcp-executor.js +33 -0
  139. package/src/utils/index.js +153 -0
  140. package/xhs_auth.json +268 -0
  141. package/.agent/agents/code-assistant.json +0 -14
  142. package/.agent/agents/email-assistant.json +0 -14
  143. package/.agent/agents/file-assistant.json +0 -15
  144. package/.agent/agents/system-assistant.json +0 -15
  145. package/.agent/agents/web-assistant.json +0 -12
  146. package/.agent/data/ambient/goals.json +0 -50
  147. package/.agent/data/ambient/memories.json +0 -7
  148. package/.agent/data/scheduler/tasks.json +0 -1
  149. package/.agent/package.json +0 -8
  150. package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
  151. package/.agent/plugins/system-info/index.js +0 -387
  152. package/.agent/plugins/system-info/package.json +0 -4
  153. package/.agent/plugins/system-info/test.js +0 -40
  154. package/.agent/plugins/test_plugin.py +0 -304
  155. package/.agent/python-scripts/test_sample.py +0 -24
  156. package/.agent/skills/sysinfo/SKILL.md +0 -38
  157. package/.agent/skills/sysinfo/system-info.sh +0 -130
  158. package/.agent/skills/workflow/SKILL.md +0 -324
  159. package/.agent/workflows/email-digest.json +0 -50
  160. package/.agent/workflows/file-backup.json +0 -21
  161. package/.agent/workflows/get-ip-notify.json +0 -32
  162. package/.agent/workflows/news-aggregator.json +0 -93
  163. package/.agent/workflows/news-dashboard-v2.json +0 -94
  164. package/.agent/workflows/notification-batch.json +0 -32
  165. package/reports/system-health-report-20260401.md +0 -79
  166. package/test/tool-registry-validation.test.js +0 -218
  167. package/test_report.md +0 -70
@@ -6,6 +6,7 @@
6
6
  const { EventEmitter } = require('../utils/event-emitter');
7
7
  const { logger } = require('../utils/logger');
8
8
  const { generateText, pruneMessages, stepCountIs, convertToModelMessages } = require('ai');
9
+ const { prepareMessagesForAPI } = require('../utils');
9
10
 
10
11
  // 模型上下文限制表(留 15-20% 余量给 system prompt 和输出)
11
12
  const MODEL_CONTEXT_LIMITS = {
@@ -336,6 +337,25 @@ class AgentChatHandler extends EventEmitter {
336
337
  });
337
338
  }
338
339
 
340
+ async compressToolResults(messages) {
341
+ try {
342
+ const recentMessages = prepareMessagesForAPI(messages.slice(-10));
343
+ const messagesToSummarize = messages.slice(0, -10);
344
+ let summaryContent = '';
345
+ const summaryText = await this._summarizeMessages(messagesToSummarize);
346
+ summaryContent = `[早期对话摘要]: ${summaryText}`;
347
+ const summary = {
348
+ role: 'assistant',
349
+ content: summaryContent,
350
+ };
351
+ logger.info(`AI summary generated (${summaryText.length} chars)`);
352
+ return [summary, ...recentMessages];
353
+ } catch (err) {
354
+ logger.warn('Tool result compression failed:', err.message);
355
+ return prepareMessagesForAPI(messages);
356
+ }
357
+ }
358
+
339
359
  /**
340
360
  * 执行实际压缩逻辑
341
361
  * @private
@@ -393,11 +413,13 @@ class AgentChatHandler extends EventEmitter {
393
413
  const summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
394
414
 
395
415
  const summary = {
396
- role: 'system',
416
+ role: 'assistant',
397
417
  content: summaryContent,
398
418
  };
399
419
 
400
- this._messages = [...systemMessages, summary, ...recentMessages];
420
+ this._messages = [...systemMessages, summary, ...recentMessages].filter(
421
+ (item) => item.role !== 'tool'
422
+ );
401
423
  this._compressionCount++;
402
424
 
403
425
  logger.info(
@@ -512,7 +534,7 @@ ${truncatedContent}${truncatedNote}
512
534
  2. 关键信息点(不超过 5 个)
513
535
  3. 重要数据或结论
514
536
 
515
- 用简洁的中文总结,不超过 400 字:`;
537
+ 用简洁的中文总结,不超过 1000 字:`;
516
538
 
517
539
  // 使用 AI SDK 6.x 的 generateText
518
540
  const { text } = await generateText({
@@ -640,6 +662,7 @@ ${truncatedContent}${truncatedNote}
640
662
  const context = { sessionId, isStream: false };
641
663
  const framework = this.agent.framework;
642
664
  const self = this; // 保存引用用于回调
665
+
643
666
  try {
644
667
  // 从 session 加载聊天历史
645
668
  if (sessionId) {
@@ -684,30 +707,25 @@ ${truncatedContent}${truncatedNote}
684
707
  if (!this._aiClient) {
685
708
  throw new Error('AI client not configured.');
686
709
  }
687
- // const prunedMessages = pruneMessages({
688
- // messages: this._messages,
689
- // reasoning: 'all', // Remove all reasoning parts
690
- // toolCalls: 'before-last-6-messages', // Remove tool calls except those in the last message
691
- // });
710
+
711
+ // 准备传给 agent 的消息(移除 reasoning 节省 token,但保留 toolCalls)
712
+ const messagesForAgent = pruneMessages({
713
+ messages: this._messages,
714
+ reasoning: 'all',
715
+ toolCalls: 'none',
716
+ });
717
+
692
718
  const agent = new ToolLoopAgent({
693
719
  model: this._aiClient,
694
720
  instructions: this._systemPrompt,
695
721
  tools: tools,
696
- stopWhen: stepCountIs(maxSteps),
697
- prepareStep: async ({ stepNumber, steps, messages }) => {
698
- if (messages.length > 20) {
699
- return {
700
- messages: messages.slice(-10),
701
- };
702
- }
703
-
704
- return {};
705
- },
722
+ prepareStep: async ({ stepNumber, messages }) =>
723
+ this._prepareStepForPruning(stepNumber, messages),
706
724
  });
707
725
 
708
726
  // 使用 runWithContext 让工具执行时能获取 sessionId
709
727
  const result = await framework.runWithContext(context, async () => {
710
- return agent.generate({ messages: this._messages, ...this.providerOptions });
728
+ return agent.generate({ messages: messagesForAgent, ...this.providerOptions });
711
729
  });
712
730
 
713
731
  this._messages.push(...result.response.messages);
@@ -744,6 +762,7 @@ ${truncatedContent}${truncatedNote}
744
762
  const sessionId = options.sessionId || null;
745
763
  const context = { sessionId, isStream: true };
746
764
  const framework = this.agent.framework;
765
+
747
766
  try {
748
767
  //从 session 加载聊天历史
749
768
  if (sessionId) {
@@ -782,37 +801,30 @@ ${truncatedContent}${truncatedNote}
782
801
 
783
802
  const maxSteps = options.maxSteps || this._maxSteps;
784
803
  const tools = this._getAITools(tool);
785
- const self = this; // 保存引用用于回调
786
804
  if (!this._aiClient) {
787
805
  throw new Error('AI client not configured.');
788
806
  }
789
- // console.log(JSON.stringify(this._messages, null, 2));
790
- // const prunedMessages = pruneMessages({
791
- // messages: this._messages,
792
- // reasoning: 'all', // Remove all reasoning parts
793
- // toolCalls: 'before-last-6-messages', // Remove tool calls except those in the last message
794
- // });
807
+
808
+ // 准备传给 agent 的消息(不做提前修剪,由 prepareStep 处理)
809
+ // 但需要移除 reasoning 来节省 token
810
+ const messagesForAgent = pruneMessages({
811
+ messages: this._messages,
812
+ reasoning: 'all',
813
+ toolCalls: 'none', // 保留所有 tool calls,避免丢失工具执行历史
814
+ });
795
815
 
796
816
  const agent = new ToolLoopAgent({
797
817
  model: this._aiClient,
798
818
  instructions: this._systemPrompt,
799
819
  tools: tools,
800
- stopWhen: stepCountIs(maxSteps),
801
- prepareStep: async ({ stepNumber, steps, messages }) => {
802
- if (messages.length > 20) {
803
- return {
804
- messages: messages.slice(-10),
805
- };
806
- }
807
-
808
- return {};
809
- },
820
+ prepareStep: async ({ stepNumber, messages }) =>
821
+ this._prepareStepForPruning(stepNumber, messages),
810
822
  });
811
823
 
812
824
  // 使用 runWithContext 让工具执行时能获取 sessionId(支持并行)
813
825
  const result = await framework.runWithContext(context, async () => {
814
826
  return agent.stream({
815
- messages: this._messages,
827
+ messages: messagesForAgent,
816
828
  ...this.providerOptions,
817
829
  });
818
830
  });
@@ -839,16 +851,10 @@ ${truncatedContent}${truncatedNote}
839
851
  }
840
852
  }
841
853
 
842
- const finish_messages = (await result.response).messages;
843
- this._messages.push(...finish_messages);
844
- // console.log(JSON.stringify(this._messages, null, 2));
845
- // 生成后检查:如果消息太长,下次需要压缩
846
- const afterTokens = this._countMessagesTokens(this._messages);
847
- if (afterTokens > this._maxContextTokens * 0.8) {
848
- logger.info(`After generation: ${afterTokens} tokens, will compress on next turn`);
849
- }
854
+ const finishMessages = (await result.response).messages;
855
+ this._messages.push(...finishMessages);
850
856
 
851
- // 保存聊天历史到 session
857
+ // 保存聊天历史到 session(使用实际传给 agent 的消息一致)
852
858
  if (sessionId) {
853
859
  this._saveHistoryToSession(sessionId, this._messages);
854
860
  }
@@ -858,6 +864,69 @@ ${truncatedContent}${truncatedNote}
858
864
  }
859
865
  }
860
866
 
867
+ /**
868
+ * 修剪消息确保配对的 tool call/result 不被拆分
869
+ * @param {number} stepNumber - 当前步骤号
870
+ * @param {Array} messages - 消息列表
871
+ * @returns {Object} { messages } 或空对象
872
+ * @private
873
+ */
874
+ _prepareStepForPruning(stepNumber, messages) {
875
+ if (messages.length <= 20) {
876
+ return {};
877
+ }
878
+
879
+ // 构建配对索引集合:确保 assistant 的 tool call 和 tool result 不会被单独裁剪
880
+ const assistantIndices = new Map(); // toolCallId -> assistant index
881
+ const toolResultIndices = new Map(); // toolCallId -> tool result indices
882
+
883
+ for (let i = 0; i < messages.length; i++) {
884
+ const msg = messages[i];
885
+ if (msg.role === 'assistant' && msg.content) {
886
+ const content = Array.isArray(msg.content) ? msg.content : [msg.content];
887
+ for (const item of content) {
888
+ if (item.type === 'tool-call' && item.toolCallId) {
889
+ assistantIndices.set(item.toolCallId, i);
890
+ }
891
+ }
892
+ }
893
+ if (msg.role === 'tool' && msg.toolCallId) {
894
+ if (!toolResultIndices.has(msg.toolCallId)) {
895
+ toolResultIndices.set(msg.toolCallId, []);
896
+ }
897
+ toolResultIndices.get(msg.toolCallId).push(i);
898
+ }
899
+ }
900
+
901
+ const recentCount = Math.max(10, messages.length - 10);
902
+ let minIndexToKeep = messages.length - recentCount;
903
+
904
+ // 情况1:保留被裁剪范围内的 assistant 消息及其 tool result
905
+ for (const [toolCallId, assistantIdx] of assistantIndices) {
906
+ if (assistantIdx < minIndexToKeep) {
907
+ minIndexToKeep = Math.min(minIndexToKeep, assistantIdx);
908
+ }
909
+ }
910
+
911
+ // 情况2:保留被裁剪范围内的 tool result 及其 assistant 消息
912
+ for (const [toolCallId, resultIndices] of toolResultIndices) {
913
+ for (const resultIdx of resultIndices) {
914
+ if (resultIdx >= minIndexToKeep) {
915
+ const assistantIdx = assistantIndices.get(toolCallId);
916
+ if (assistantIdx !== undefined && assistantIdx < minIndexToKeep) {
917
+ minIndexToKeep = Math.min(minIndexToKeep, assistantIdx);
918
+ }
919
+ }
920
+ }
921
+ }
922
+
923
+ const pruned = messages.slice(minIndexToKeep);
924
+ // logger.info(
925
+ // `prepareStep pruned: ${messages.length} -> ${pruned.length} (step ${stepNumber}, kept from index ${minIndexToKeep})`
926
+ // );
927
+ return { messages: pruned };
928
+ }
929
+
861
930
  /**
862
931
  * 获取 AI SDK 格式的工具
863
932
  * AI SDK 6.x 需要对象形式 { toolName: tool }
@@ -48,14 +48,55 @@ class Framework extends EventEmitter {
48
48
  // 执行上下文(工具调用时可用)
49
49
  this._executionContext = null;
50
50
 
51
- // 事件转发
51
+ // 事件转发 - ToolRegistry 事件
52
52
  this.toolRegistry.on('tool:registered', (tool) => {
53
53
  this.emit('tool:registered', tool);
54
54
  });
55
55
 
56
+ this.toolRegistry.on('tool:call', (data) => {
57
+ this.emit('tool:call', data);
58
+ // 兼容连字符格式
59
+ this.emit('tool-call', data);
60
+ });
61
+
62
+ this.toolRegistry.on('tool:result', (data) => {
63
+ this.emit('tool:result', data);
64
+ });
65
+
66
+ this.toolRegistry.on('tool:error', (data) => {
67
+ this.emit('tool:error', data);
68
+ });
69
+
70
+ // 事件转发 - Agent 事件
71
+ this._setupAgentEventForwarding();
72
+
56
73
  this._registerBuiltinTools();
57
74
  }
58
75
 
76
+ /**
77
+ * 设置 Agent 事件转发
78
+ * @private
79
+ */
80
+ _setupAgentEventForwarding() {
81
+ // 监听 agent:created 事件,为新创建的 Agent 设置事件转发
82
+ this.on('agent:created', (agent) => {
83
+ // 转发 Agent 的 tool-call/result/error 事件
84
+ agent.on('tool-call', (data) => {
85
+ this.emit('tool:call', data);
86
+ });
87
+ agent.on('tool-result', (data) => {
88
+ this.emit('tool:result', data);
89
+ });
90
+ agent.on('tool-error', (data) => {
91
+ this.emit('tool:error', data);
92
+ });
93
+ // 转发 Agent 的 message 事件作为 agent:message
94
+ agent.on('message', (msg) => {
95
+ this.emit('agent:message', msg);
96
+ });
97
+ });
98
+ }
99
+
59
100
  /**
60
101
  * 从 AI 插件合并配置到目标配置对象
61
102
  * @param {Object} target - 目标配置对象
@@ -357,10 +357,43 @@ class MCPExecutorPlugin extends Plugin {
357
357
  if (!mcpTool) {
358
358
  return { success: false, error: `Tool '${tool}' not found on server '${server}'` };
359
359
  }
360
+
361
+ // 触发 MCP 工具开始事件
362
+ const mcpToolName = `${server}:${tool}`;
363
+ framework.emit('tool:call', {
364
+ name: mcpToolName,
365
+ args: finalArgs,
366
+ source: 'mcp',
367
+ });
368
+ framework.emit('tool-call', {
369
+ name: mcpToolName,
370
+ args: finalArgs,
371
+ source: 'mcp',
372
+ });
373
+
360
374
  const execResult = await mcpTool.execute(finalArgs);
375
+
376
+ // 触发 MCP 工具完成事件
377
+ framework.emit('tool:result', {
378
+ name: mcpToolName,
379
+ args: finalArgs,
380
+ result: execResult,
381
+ source: 'mcp',
382
+ });
383
+
361
384
  return { success: true, result: execResult };
362
385
  } catch (err) {
363
386
  log.error(` Tool '${tool}' failed:`, err.message);
387
+
388
+ // 触发 MCP 工具错误事件
389
+ const mcpToolName = `${server}:${tool}`;
390
+ framework.emit('tool:error', {
391
+ name: mcpToolName,
392
+ args: finalArgs,
393
+ error: err.message,
394
+ source: 'mcp',
395
+ });
396
+
364
397
  return { success: false, error: err.message };
365
398
  }
366
399
  },
@@ -161,6 +161,158 @@ async function retry(fn, options = {}) {
161
161
  throw lastError;
162
162
  }
163
163
 
164
+ function filterAndCleanMessages(messages) {
165
+ // 先配对
166
+ const pairedIds = new Set();
167
+ const toolCallToAssistant = new Map(); // toolCallId -> assistant index
168
+
169
+ // 收集所有 assistant 的 tool-call
170
+ for (let i = 0; i < messages.length; i++) {
171
+ const msg = messages[i];
172
+ if (msg.role === 'assistant' && msg.content) {
173
+ const content = Array.isArray(msg.content) ? msg.content : [msg.content];
174
+ for (const item of content) {
175
+ if (item.type === 'tool-call' && item.toolCallId) {
176
+ toolCallToAssistant.set(item.toolCallId, i);
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ // 找出配对的 tool result
183
+ for (const msg of messages) {
184
+ if (msg.role === 'tool' && msg.toolCallId) {
185
+ if (toolCallToAssistant.has(msg.toolCallId)) {
186
+ pairedIds.add(msg.toolCallId);
187
+ }
188
+ }
189
+ }
190
+
191
+ // 过滤并清理消息
192
+ const cleaned = [];
193
+ for (const msg of messages) {
194
+ let shouldKeep = true;
195
+ let cleanedMsg = { ...msg };
196
+
197
+ if (msg.role === 'assistant' && msg.content) {
198
+ let content = Array.isArray(msg.content) ? msg.content : [msg.content];
199
+
200
+ // 过滤掉未配对的 tool-call
201
+ const filteredContent = content.filter((item) => {
202
+ if (item.type === 'tool-call') {
203
+ return pairedIds.has(item.toolCallId);
204
+ }
205
+ return true; // 保留 text 类型
206
+ });
207
+
208
+ // 检查过滤后是否还有有效内容
209
+ if (filteredContent.length === 0) {
210
+ shouldKeep = false; // 丢弃空消息
211
+ } else {
212
+ cleanedMsg.content = filteredContent;
213
+ }
214
+ }
215
+
216
+ if (msg.role === 'tool') {
217
+ shouldKeep = pairedIds.has(msg.toolCallId);
218
+ }
219
+
220
+ if (shouldKeep && cleanedMsg.content) {
221
+ // 确保 content 不为空
222
+ if (Array.isArray(cleanedMsg.content) && cleanedMsg.content.length > 0) {
223
+ cleaned.push(cleanedMsg);
224
+ } else if (typeof cleanedMsg.content === 'string' && cleanedMsg.content.trim()) {
225
+ cleaned.push(cleanedMsg);
226
+ } else if (
227
+ cleanedMsg.content &&
228
+ typeof cleanedMsg.content !== 'string' &&
229
+ !Array.isArray(cleanedMsg.content)
230
+ ) {
231
+ // 处理其他类型的 content
232
+ cleaned.push(cleanedMsg);
233
+ }
234
+ // 否则跳过空消息
235
+ }
236
+ }
237
+
238
+ return cleaned;
239
+ }
240
+
241
+ // 更激进的方法:确保所有消息都有有效的 content
242
+ function validateAndFixMessages(messages) {
243
+ return messages.filter((msg) => {
244
+ // 检查 content 是否存在且非空
245
+ if (!msg.content) {
246
+ console.log(`丢弃消息: role=${msg.role}, 无 content`);
247
+ return false;
248
+ }
249
+
250
+ // 处理数组类型的 content
251
+ if (Array.isArray(msg.content)) {
252
+ if (msg.content.length === 0) {
253
+ console.log(`丢弃消息: role=${msg.role}, content 为空数组`);
254
+ return false;
255
+ }
256
+
257
+ // 检查数组中是否有有效内容
258
+ const hasValidContent = msg.content.some((item) => {
259
+ if (item.type === 'text' && item.text && item.text.trim()) {
260
+ return true;
261
+ }
262
+ if (item.type === 'tool-call' && item.toolCallId) {
263
+ return true;
264
+ }
265
+ if (item.type === 'tool-result' && item.toolCallId) {
266
+ return true;
267
+ }
268
+ return false;
269
+ });
270
+
271
+ if (!hasValidContent) {
272
+ console.log(`丢弃消息: role=${msg.role}, content 数组无有效内容`);
273
+ return false;
274
+ }
275
+ }
276
+
277
+ // 处理字符串类型的 content
278
+ if (typeof msg.content === 'string' && !msg.content.trim()) {
279
+ console.log(`丢弃消息: role=${msg.role}, content 为空字符串`);
280
+ return false;
281
+ }
282
+
283
+ return true;
284
+ });
285
+ }
286
+
287
+ // 完整处理流程
288
+ function prepareMessagesForAPI(rawMessages) {
289
+ // 1. 先配对
290
+ const paired = filterAndCleanMessages(rawMessages);
291
+
292
+ // 2. 验证并清理空消息
293
+ const cleaned = validateAndFixMessages(paired);
294
+
295
+ // 3. 确保消息交替顺序正确(user/assistant/tool)
296
+ const finalMessages = [];
297
+ let lastRole = null;
298
+
299
+ for (const msg of cleaned) {
300
+ // 跳过连续的相同角色消息(除了 tool)
301
+ if (msg.role === lastRole && msg.role !== 'tool') {
302
+ console.log(`跳过连续的 ${msg.role} 消息`);
303
+ continue;
304
+ }
305
+
306
+ finalMessages.push(msg);
307
+ lastRole = msg.role;
308
+ }
309
+
310
+ return finalMessages;
311
+ }
312
+
313
+ // 发送给 API
314
+ // await yourAPI.call({ messages: cleanMessages });
315
+
164
316
  module.exports = {
165
317
  // 日志
166
318
  Logger,
@@ -214,4 +366,5 @@ module.exports = {
214
366
  throttle,
215
367
  sleep,
216
368
  retry,
369
+ prepareMessagesForAPI,
217
370
  };