foliko 1.0.83 → 1.0.85

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 (168) 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 +1418 -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 +38 -0
  130. package/plugins/plugin-manager-plugin.js +402 -0
  131. package/plugins/session-plugin.js +3 -3
  132. package/skills/find-skills/AGENTS.md +162 -162
  133. package/skills/find-skills/SKILL.md +133 -133
  134. package/skills/foliko-dev/SKILL.md +563 -563
  135. package/skills/plugin-guide/SKILL.md +139 -0
  136. package/skills/python-plugin-dev/SKILL.md +238 -238
  137. package/src/core/agent-chat.js +103 -45
  138. package/src/core/framework.js +42 -1
  139. package/src/executors/mcp-executor.js +33 -0
  140. package/src/utils/index.js +153 -0
  141. package/xhs_auth.json +268 -0
  142. package/.agent/agents/code-assistant.json +0 -14
  143. package/.agent/agents/email-assistant.json +0 -14
  144. package/.agent/agents/file-assistant.json +0 -15
  145. package/.agent/agents/system-assistant.json +0 -15
  146. package/.agent/agents/web-assistant.json +0 -12
  147. package/.agent/data/ambient/goals.json +0 -50
  148. package/.agent/data/ambient/memories.json +0 -7
  149. package/.agent/data/scheduler/tasks.json +0 -1
  150. package/.agent/package.json +0 -8
  151. package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
  152. package/.agent/plugins/system-info/index.js +0 -387
  153. package/.agent/plugins/system-info/package.json +0 -4
  154. package/.agent/plugins/system-info/test.js +0 -40
  155. package/.agent/plugins/test_plugin.py +0 -304
  156. package/.agent/python-scripts/test_sample.py +0 -24
  157. package/.agent/skills/sysinfo/SKILL.md +0 -38
  158. package/.agent/skills/sysinfo/system-info.sh +0 -130
  159. package/.agent/skills/workflow/SKILL.md +0 -324
  160. package/.agent/workflows/email-digest.json +0 -50
  161. package/.agent/workflows/file-backup.json +0 -21
  162. package/.agent/workflows/get-ip-notify.json +0 -32
  163. package/.agent/workflows/news-aggregator.json +0 -93
  164. package/.agent/workflows/news-dashboard-v2.json +0 -94
  165. package/.agent/workflows/notification-batch.json +0 -32
  166. package/reports/system-health-report-20260401.md +0 -79
  167. package/test/tool-registry-validation.test.js +0 -218
  168. package/test_report.md +0 -70
@@ -5,7 +5,8 @@
5
5
 
6
6
  const { EventEmitter } = require('../utils/event-emitter');
7
7
  const { logger } = require('../utils/logger');
8
- const { generateText, pruneMessages, stepCountIs, convertToModelMessages } = require('ai');
8
+ const { generateText, stepCountIs } = 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,25 +707,15 @@ ${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 的消息
712
+ // ToolLoopAgent 会自动处理消息格式,不需要手动 prune
692
713
  const agent = new ToolLoopAgent({
693
714
  model: this._aiClient,
694
715
  instructions: this._systemPrompt,
695
716
  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
- },
717
+ prepareStep: async ({ stepNumber, messages }) =>
718
+ this._prepareStepForPruning(stepNumber, messages),
706
719
  });
707
720
 
708
721
  // 使用 runWithContext 让工具执行时能获取 sessionId
@@ -744,6 +757,7 @@ ${truncatedContent}${truncatedNote}
744
757
  const sessionId = options.sessionId || null;
745
758
  const context = { sessionId, isStream: true };
746
759
  const framework = this.agent.framework;
760
+
747
761
  try {
748
762
  //从 session 加载聊天历史
749
763
  if (sessionId) {
@@ -782,31 +796,18 @@ ${truncatedContent}${truncatedNote}
782
796
 
783
797
  const maxSteps = options.maxSteps || this._maxSteps;
784
798
  const tools = this._getAITools(tool);
785
- const self = this; // 保存引用用于回调
786
799
  if (!this._aiClient) {
787
800
  throw new Error('AI client not configured.');
788
801
  }
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
- // });
795
802
 
803
+ // 准备传给 agent 的消息
804
+ // ToolLoopAgent 会自动处理消息格式,不需要手动 prune
796
805
  const agent = new ToolLoopAgent({
797
806
  model: this._aiClient,
798
807
  instructions: this._systemPrompt,
799
808
  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
- },
809
+ prepareStep: async ({ stepNumber, messages }) =>
810
+ this._prepareStepForPruning(stepNumber, messages),
810
811
  });
811
812
 
812
813
  // 使用 runWithContext 让工具执行时能获取 sessionId(支持并行)
@@ -839,16 +840,10 @@ ${truncatedContent}${truncatedNote}
839
840
  }
840
841
  }
841
842
 
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
- }
843
+ const finishMessages = (await result.response).messages;
844
+ this._messages.push(...finishMessages);
850
845
 
851
- // 保存聊天历史到 session
846
+ // 保存聊天历史到 session(使用实际传给 agent 的消息一致)
852
847
  if (sessionId) {
853
848
  this._saveHistoryToSession(sessionId, this._messages);
854
849
  }
@@ -858,6 +853,69 @@ ${truncatedContent}${truncatedNote}
858
853
  }
859
854
  }
860
855
 
856
+ /**
857
+ * 修剪消息确保配对的 tool call/result 不被拆分
858
+ * @param {number} stepNumber - 当前步骤号
859
+ * @param {Array} messages - 消息列表
860
+ * @returns {Object} { messages } 或空对象
861
+ * @private
862
+ */
863
+ _prepareStepForPruning(stepNumber, messages) {
864
+ if (messages.length <= 20) {
865
+ return {};
866
+ }
867
+
868
+ // 构建配对索引集合:确保 assistant 的 tool call 和 tool result 不会被单独裁剪
869
+ const assistantIndices = new Map(); // toolCallId -> assistant index
870
+ const toolResultIndices = new Map(); // toolCallId -> tool result indices
871
+
872
+ for (let i = 0; i < messages.length; i++) {
873
+ const msg = messages[i];
874
+ if (msg.role === 'assistant' && msg.content) {
875
+ const content = Array.isArray(msg.content) ? msg.content : [msg.content];
876
+ for (const item of content) {
877
+ if (item.type === 'tool-call' && item.toolCallId) {
878
+ assistantIndices.set(item.toolCallId, i);
879
+ }
880
+ }
881
+ }
882
+ if (msg.role === 'tool' && msg.toolCallId) {
883
+ if (!toolResultIndices.has(msg.toolCallId)) {
884
+ toolResultIndices.set(msg.toolCallId, []);
885
+ }
886
+ toolResultIndices.get(msg.toolCallId).push(i);
887
+ }
888
+ }
889
+
890
+ const recentCount = Math.max(10, messages.length - 10);
891
+ let minIndexToKeep = messages.length - recentCount;
892
+
893
+ // 情况1:保留被裁剪范围内的 assistant 消息及其 tool result
894
+ for (const [toolCallId, assistantIdx] of assistantIndices) {
895
+ if (assistantIdx < minIndexToKeep) {
896
+ minIndexToKeep = Math.min(minIndexToKeep, assistantIdx);
897
+ }
898
+ }
899
+
900
+ // 情况2:保留被裁剪范围内的 tool result 及其 assistant 消息
901
+ for (const [toolCallId, resultIndices] of toolResultIndices) {
902
+ for (const resultIdx of resultIndices) {
903
+ if (resultIdx >= minIndexToKeep) {
904
+ const assistantIdx = assistantIndices.get(toolCallId);
905
+ if (assistantIdx !== undefined && assistantIdx < minIndexToKeep) {
906
+ minIndexToKeep = Math.min(minIndexToKeep, assistantIdx);
907
+ }
908
+ }
909
+ }
910
+ }
911
+
912
+ const pruned = messages.slice(minIndexToKeep);
913
+ // logger.info(
914
+ // `prepareStep pruned: ${messages.length} -> ${pruned.length} (step ${stepNumber}, kept from index ${minIndexToKeep})`
915
+ // );
916
+ return { messages: pruned };
917
+ }
918
+
861
919
  /**
862
920
  * 获取 AI SDK 格式的工具
863
921
  * 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
  };