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.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +288 -0
- package/.agent/agents/ambient-agent.md +57 -0
- package/.agent/agents/debugger.md +55 -0
- package/.agent/agents/email-assistant.md +49 -0
- package/.agent/agents/file-manager.md +42 -0
- package/.agent/agents/python-developer.md +60 -0
- package/.agent/agents/scheduler.md +59 -0
- package/.agent/agents/web-developer.md +45 -0
- package/.agent/data/default.json +355 -6
- package/.agent/data/plugins-state.json +185 -146
- package/.agent/data/puppeteer-sessions/undefined.json +6 -0
- package/.agent/mcp_config.json +0 -1
- package/.agent/mcp_config_updated.json +12 -0
- package/.agent/plugins/puppeteer-plugin/README.md +147 -0
- package/.agent/plugins/puppeteer-plugin/index.js +1320 -0
- package/.agent/plugins/puppeteer-plugin/package.json +9 -0
- package/.agent/plugins.json +5 -14
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/rules/allow-rule.md +77 -0
- package/.agent/rules/log-rule.md +83 -0
- package/.agent/rules/security-rule.md +93 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +81 -0
- package/.agent/workflows/simple-test.md +42 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/structured-orchestrate.md +180 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/.claude/settings.local.json +178 -171
- package/.env.example +56 -56
- package/cli/src/commands/plugin.js +482 -0
- package/cli/src/index.js +7 -0
- package/cli/src/utils/plugin-config.js +50 -0
- package/package.json +1 -1
- package/plugins/audit-plugin.js +2 -0
- package/plugins/extension-executor-plugin.js +30 -0
- package/plugins/plugin-manager-plugin.js +402 -0
- package/skills/find-skills/AGENTS.md +162 -162
- package/skills/find-skills/SKILL.md +133 -133
- package/skills/foliko-dev/SKILL.md +563 -563
- package/skills/plugin-guide/SKILL.md +139 -0
- package/skills/python-plugin-dev/SKILL.md +238 -238
- package/src/core/agent-chat.js +115 -46
- package/src/core/framework.js +42 -1
- package/src/executors/mcp-executor.js +33 -0
- package/src/utils/index.js +153 -0
- package/xhs_auth.json +268 -0
- package/.agent/agents/code-assistant.json +0 -14
- package/.agent/agents/email-assistant.json +0 -14
- package/.agent/agents/file-assistant.json +0 -15
- package/.agent/agents/system-assistant.json +0 -15
- package/.agent/agents/web-assistant.json +0 -12
- package/.agent/data/ambient/goals.json +0 -50
- package/.agent/data/ambient/memories.json +0 -7
- package/.agent/data/scheduler/tasks.json +0 -1
- package/.agent/package.json +0 -8
- package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
- package/.agent/plugins/system-info/index.js +0 -387
- package/.agent/plugins/system-info/package.json +0 -4
- package/.agent/plugins/system-info/test.js +0 -40
- package/.agent/plugins/test_plugin.py +0 -304
- package/.agent/python-scripts/test_sample.py +0 -24
- package/.agent/skills/sysinfo/SKILL.md +0 -38
- package/.agent/skills/sysinfo/system-info.sh +0 -130
- package/.agent/skills/workflow/SKILL.md +0 -324
- package/.agent/workflows/email-digest.json +0 -50
- package/.agent/workflows/file-backup.json +0 -21
- package/.agent/workflows/get-ip-notify.json +0 -32
- package/.agent/workflows/news-aggregator.json +0 -93
- package/.agent/workflows/news-dashboard-v2.json +0 -94
- package/.agent/workflows/notification-batch.json +0 -32
- package/reports/system-health-report-20260401.md +0 -79
- package/test/tool-registry-validation.test.js +0 -218
- package/test_report.md +0 -70
package/src/core/agent-chat.js
CHANGED
|
@@ -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: '
|
|
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
|
-
用简洁的中文总结,不超过
|
|
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
|
-
|
|
688
|
-
//
|
|
689
|
-
|
|
690
|
-
|
|
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
|
-
|
|
697
|
-
|
|
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:
|
|
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
|
-
|
|
790
|
-
//
|
|
791
|
-
//
|
|
792
|
-
|
|
793
|
-
|
|
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
|
-
|
|
801
|
-
|
|
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:
|
|
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
|
|
843
|
-
this._messages.push(...
|
|
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 }
|
package/src/core/framework.js
CHANGED
|
@@ -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
|
},
|
package/src/utils/index.js
CHANGED
|
@@ -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
|
};
|