foliko 1.1.67 → 1.1.69
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/.claude/settings.local.json +19 -10
- package/.dockerignore +45 -45
- package/.env.example +56 -56
- package/CLAUDE.md +2 -2
- package/README.md +13 -13
- package/SPEC.md +3 -3
- package/cli/src/commands/chat.js +2 -20
- package/cli/src/commands/list.js +7 -6
- package/cli/src/commands/plugin.js +3 -2
- package/cli/src/daemon.js +2 -2
- package/cli/src/ui/chat-ui-old.js +15 -4
- package/cli/src/ui/chat-ui.js +236 -203
- package/cli/src/ui/footer-bar.js +20 -46
- package/cli/src/ui/message-bubble.js +24 -2
- package/cli/src/ui/status-bar.js +177 -0
- package/cli/src/utils/config.js +29 -0
- package/cli/src/utils/plugin-config.js +1 -1
- package/docker-compose.yml +33 -33
- package/docs/features.md +120 -120
- package/docs/quick-reference.md +160 -160
- package/docs/user-manual.md +1391 -1391
- package/examples/ambient-example.js +2 -2
- package/examples/bootstrap.js +3 -3
- package/examples/test-chat.js +1 -1
- package/examples/test-reload.js +1 -1
- package/examples/test-telegram.js +1 -1
- package/examples/test-tg-bot.js +1 -1
- package/examples/test-tg-simple.js +2 -2
- package/examples/test-tg.js +1 -1
- package/examples/test-think.js +1 -1
- package/examples/test-weixin-feishu.js +3 -3
- package/package.json +3 -1
- package/plugins/ambient-agent/index.js +1 -1
- package/plugins/audit-plugin.js +84 -29
- package/plugins/coordinator-plugin.js +14 -12
- package/plugins/data-splitter-plugin.js +323 -0
- package/plugins/default-plugins.js +23 -12
- package/plugins/email/index.js +1 -1
- package/plugins/extension-executor-plugin.js +87 -9
- package/plugins/feishu-plugin.js +118 -16
- package/plugins/file-system-plugin.js +68 -50
- package/plugins/gate-trading.js +10 -10
- package/plugins/install-plugin.js +7 -7
- package/plugins/memory-plugin.js +9 -12
- package/plugins/plugin-manager-plugin.js +12 -14
- package/plugins/python-executor-plugin.js +1 -1
- package/plugins/python-plugin-loader.js +1 -1
- package/plugins/qq-plugin.js +151 -24
- package/plugins/rules-plugin.js +8 -8
- package/plugins/scheduler-plugin.js +24 -20
- package/plugins/session-plugin.js +313 -397
- package/plugins/storage-plugin.js +235 -175
- package/plugins/subagent-plugin.js +17 -13
- package/plugins/telegram-plugin.js +116 -17
- package/plugins/think-plugin.js +64 -60
- package/plugins/tools-plugin.js +8 -8
- package/plugins/web-plugin.js +2 -2
- package/plugins/weixin-plugin.js +107 -24
- package/skills/find-skills/AGENTS.md +2 -2
- package/skills/find-skills/SKILL.md +133 -133
- package/skills/foliko-dev/AGENTS.md +236 -236
- package/skills/foliko-dev/SKILL.md +19 -19
- package/skills/mcp-usage/SKILL.md +200 -200
- package/skills/plugin-guide/SKILL.md +4 -4
- package/skills/python-plugin-dev/SKILL.md +5 -5
- package/skills/skill-guide/SKILL.md +104 -6
- package/skills/subagent-guide/SKILL.md +237 -237
- package/skills/workflow-guide/SKILL.md +646 -646
- package/src/capabilities/skill-manager.js +124 -17
- package/src/capabilities/workflow-engine.js +3 -3
- package/src/core/agent-chat.js +72 -26
- package/src/core/agent.js +17 -27
- package/src/core/branch-summary-auto.js +206 -0
- package/src/core/chat-session.js +45 -169
- package/src/core/command-registry.js +200 -0
- package/src/core/constants.js +198 -0
- package/src/core/context-compressor.js +702 -326
- package/src/core/context-manager.js +0 -1
- package/src/core/enhanced-context-compressor.js +210 -0
- package/src/core/framework.js +260 -84
- package/src/core/jsonl-storage.js +253 -0
- package/src/core/plugin-base.js +7 -5
- package/src/core/plugin-manager.js +15 -10
- package/src/core/provider-registry.js +159 -0
- package/src/core/provider.js +2 -0
- package/src/core/session-entry.js +225 -0
- package/src/core/session-manager.js +701 -0
- package/src/core/storage-manager.js +494 -0
- package/src/core/sub-agent-config.js +1 -1
- package/src/core/subagent.js +16 -135
- package/src/core/token-counter.js +177 -58
- package/src/core/tool-executor.js +2 -70
- package/src/core/ui-extension-context.js +174 -0
- package/src/executors/mcp-executor.js +27 -16
- package/src/utils/chat-queue.js +11 -22
- package/src/utils/data-splitter.js +345 -0
- package/src/utils/logger.js +152 -180
- package/src/utils/message-validator.js +283 -0
- package/src/utils/plugin-helpers.js +2 -2
- package/src/utils/retry.js +168 -22
- package/website_v2/docs/api.html +1 -1
- package/website_v2/docs/configuration.html +2 -2
- package/website_v2/docs/plugin-development.html +4 -4
- package/website_v2/docs/project-structure.html +2 -2
- package/website_v2/docs/skill-development.html +2 -2
- package/website_v2/index.html +1 -1
- package/website_v2/styles/animations.css +7 -7
- package/.agent/agents/backend-dev.md +0 -102
- package/.agent/agents/data-analyst.md +0 -117
- package/.agent/agents/devops.md +0 -115
- package/.agent/agents/frontend-dev.md +0 -94
- package/.agent/agents/network-requester.md +0 -44
- package/.agent/agents/poster-designer.md +0 -52
- package/.agent/agents/product-manager.md +0 -85
- package/.agent/agents/qa-engineer.md +0 -100
- package/.agent/agents/security-engineer.md +0 -99
- package/.agent/agents/team-lead.md +0 -137
- package/.agent/agents/ui-designer.md +0 -116
- package/.agent/data/default.json +0 -58
- package/.agent/data/email/processed-emails.json +0 -1
- package/.agent/data/plugins-state.json +0 -199
- package/.agent/data/scheduler/tasks.json +0 -1
- package/.agent/data/web/web-config.json +0 -5
- package/.agent/data/weixin/images/file_1776188148383jpg +0 -0
- package/.agent/data/weixin/images/file_1776188458326.jpg +0 -0
- package/.agent/data/weixin/images/file_1776188689423.jpg +0 -0
- package/.agent/data/weixin/images/file_1776188813604.jpg +0 -0
- package/.agent/data/weixin/images/file_1776189097450.jpg +0 -0
- package/.agent/data/weixin/videos/file_1776188318431.mp4 +0 -0
- package/.agent/data/weixin.json +0 -6
- package/.agent/mcp_config.json +0 -14
- package/.agent/memory/user/mof6gk94-kneeuh.md +0 -9
- package/.agent/package.json +0 -8
- package/.agent/plugins/marknative/README.md +0 -134
- package/.agent/plugins/marknative/fonts/SegoeUI Emoji.ttf +0 -0
- package/.agent/plugins/marknative/fonts.zip +0 -0
- package/.agent/plugins/marknative/index.js +0 -256
- package/.agent/plugins/marknative/package.json +0 -12
- package/.agent/plugins/test-plugin.py +0 -99
- package/.agent/plugins.json +0 -14
- package/.agent/python-scripts/test_sample.py +0 -24
- package/.agent/sessions/cli_default.json +0 -247
- package/.agent/skills/agent-browser/SKILL.md +0 -311
- package/.agent/skills/agent-browser/TEST_PLAN.md +0 -200
- 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/test-agent.js +0 -35
- package/.agent/weixin.json +0 -6
- 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/src/core/session-context.js +0 -346
- package/src/core/session-storage.js +0 -295
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataSplitterPlugin — 大数据自动分拆插件
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* 1. 注册 `split_and_process` 工具:AI 可主动调用,将大文本分拆给子 Agent 处理
|
|
6
|
+
* 2. 注册 `get_content_preview` 工具:获取大内容的前 N 行,判断是否需要分拆
|
|
7
|
+
* 3. 自动检测工具返回的大数据,触发透明分拆
|
|
8
|
+
* 4. 系统提示词中告知 AI 大数据处理能力
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { Plugin } = require('../src/core/plugin-base');
|
|
12
|
+
const { z } = require('zod');
|
|
13
|
+
const { logger } = require('../src/utils/logger');
|
|
14
|
+
const { DataSplitter } = require('../src/utils/data-splitter');
|
|
15
|
+
|
|
16
|
+
// 超过此大小的工具结果自动触发分拆(字符数,默认 100K tokens ≈ 200K chars)
|
|
17
|
+
const AUTO_SPLIT_THRESHOLD = 50000;
|
|
18
|
+
|
|
19
|
+
class DataSplitterPlugin extends Plugin {
|
|
20
|
+
constructor(config = {}) {
|
|
21
|
+
super();
|
|
22
|
+
this.name = 'data-splitter';
|
|
23
|
+
this.version = '1.0.0';
|
|
24
|
+
this.description = '大数据自动分拆处理:读取大文件或抓取网页时,自动分拆为多个子Agent并行处理';
|
|
25
|
+
this.priority = 5;
|
|
26
|
+
|
|
27
|
+
this._framework = null;
|
|
28
|
+
this._splitter = null;
|
|
29
|
+
|
|
30
|
+
// 配置
|
|
31
|
+
this.config = {
|
|
32
|
+
autoSplitThreshold: config.autoSplitThreshold || AUTO_SPLIT_THRESHOLD,
|
|
33
|
+
chunkSize: config.chunkSize || 60000,
|
|
34
|
+
maxConcurrent: config.maxConcurrent || 3,
|
|
35
|
+
...config,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
install(framework) {
|
|
40
|
+
this._framework = framework;
|
|
41
|
+
this._splitter = new DataSplitter(framework, {
|
|
42
|
+
chunkSize: this.config.chunkSize,
|
|
43
|
+
safeThreshold: this.config.autoSplitThreshold,
|
|
44
|
+
maxConcurrent: this.config.maxConcurrent,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
start(framework) {
|
|
51
|
+
// 注册大数据处理工具
|
|
52
|
+
this._registerSplitTools();
|
|
53
|
+
|
|
54
|
+
// 注册系统提示词
|
|
55
|
+
this.registerPromptPart('data-splitter-rules', 85, () => this._getPromptRules());
|
|
56
|
+
|
|
57
|
+
// 注册工具结果监听器(自动检测大结果)
|
|
58
|
+
this._registerAutoSplitHook();
|
|
59
|
+
|
|
60
|
+
logger.info('[DataSplitterPlugin] 已启动,阈值:', this.config.autoSplitThreshold);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 注册分拆工具
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
_registerSplitTools() {
|
|
68
|
+
const framework = this._framework;
|
|
69
|
+
|
|
70
|
+
// ─── 工具1: split_and_process — AI 主动调用分拆 ───
|
|
71
|
+
framework.registerTool({
|
|
72
|
+
name: 'split_and_process',
|
|
73
|
+
description: `将大文本内容按大小分块,创建多个子 Agent 并行处理每块内容,最后自动汇总结果。
|
|
74
|
+
当你读取文件或抓取网页返回的内容超过 10 万字符时,应该使用此工具来分拆处理。
|
|
75
|
+
它会将内容拆成多块,每块由一个子 Agent 独立处理,最后给你一个汇总。`,
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
content: z.string().describe('要分拆处理的大文本内容(如果超过 100K 字符建议使用此工具)'),
|
|
78
|
+
taskDescription: z.string().describe('每个子 Agent 要执行的任务描述,例如"提取所有函数定义"、"总结内容要点"等'),
|
|
79
|
+
chunkSize: z.number().optional().default(60000).describe('每块最大字符数,默认 60000'),
|
|
80
|
+
maxConcurrent: z.number().optional().default(3).describe('最大并行子 Agent 数,默认 3'),
|
|
81
|
+
}),
|
|
82
|
+
execute: async (args, ctx) => {
|
|
83
|
+
const { content, taskDescription, chunkSize, maxConcurrent } = args;
|
|
84
|
+
const splitter = this._getSplitter(ctx);
|
|
85
|
+
|
|
86
|
+
if (!content || content.length === 0) {
|
|
87
|
+
return { success: false, error: '内容为空,无需处理' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const stats = splitter.getContentStats(content);
|
|
91
|
+
if (!splitter.needsSplit(content)) {
|
|
92
|
+
// 内容较小,直接返回
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
data: content,
|
|
96
|
+
metadata: {
|
|
97
|
+
needsSplit: false,
|
|
98
|
+
stats,
|
|
99
|
+
message: '内容大小在安全范围内,无需分拆处理。可直接使用原始内容。'
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 生成上下文中的 sessionId 用于取消信号
|
|
105
|
+
const sessionCtx = ctx?.getCurrentSessionContext?.();
|
|
106
|
+
const signal = sessionCtx?.abortSignal;
|
|
107
|
+
|
|
108
|
+
logger.info(
|
|
109
|
+
`[split_and_process] 开始分拆: ${stats.chars} 字符, ` +
|
|
110
|
+
`${stats.chunks} 块, 任务="${taskDescription?.slice(0, 40)}..."`
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const startTime = Date.now();
|
|
114
|
+
const chunks = splitter.splitContent(content, chunkSize);
|
|
115
|
+
const result = await splitter.dispatchToSubAgents({
|
|
116
|
+
chunks,
|
|
117
|
+
taskDescription,
|
|
118
|
+
maxConcurrent: maxConcurrent || this.config.maxConcurrent,
|
|
119
|
+
signal,
|
|
120
|
+
});
|
|
121
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
success: true,
|
|
125
|
+
data: result.summary,
|
|
126
|
+
metadata: {
|
|
127
|
+
needsSplit: true,
|
|
128
|
+
stats: { ...stats, actualChunks: chunks.length },
|
|
129
|
+
totalChunks: chunks.length,
|
|
130
|
+
successfulChunks: result.results.filter((r) => r.success).length,
|
|
131
|
+
failedChunks: result.errors.length,
|
|
132
|
+
durationSec: parseFloat(duration),
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ─── 工具2: get_content_preview — 获取大内容预览信息 ───
|
|
139
|
+
framework.registerTool({
|
|
140
|
+
name: 'get_content_preview',
|
|
141
|
+
description: `获取大文本内容的预览信息(前 20 行 + 统计),判断是否需要分拆处理。
|
|
142
|
+
适合在读取大文件或抓取网页后用于检查内容大小。`,
|
|
143
|
+
inputSchema: z.object({
|
|
144
|
+
content: z.string().describe('要预览的大文本内容'),
|
|
145
|
+
previewLines: z.number().optional().default(20).describe('预览前 N 行,默认 20'),
|
|
146
|
+
}),
|
|
147
|
+
execute: async (args) => {
|
|
148
|
+
const { content, previewLines = 20 } = args;
|
|
149
|
+
const splitter = this._getSplitter();
|
|
150
|
+
|
|
151
|
+
const stats = splitter.getContentStats(content);
|
|
152
|
+
const lines = content.split('\n');
|
|
153
|
+
const preview = lines.slice(0, previewLines).join('\n');
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
data: preview || '(空内容)',
|
|
158
|
+
metadata: {
|
|
159
|
+
stats,
|
|
160
|
+
needsSplit: splitter.needsSplit(content),
|
|
161
|
+
totalLines: lines.length,
|
|
162
|
+
suggestion: splitter.needsSplit(content)
|
|
163
|
+
? `内容较大 (${stats.chars} 字符, 约 ${stats.estimatedTokens} tokens),建议使用 split_and_process 分拆处理`
|
|
164
|
+
: '内容大小在安全范围内',
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 获取系统提示词
|
|
173
|
+
* @private
|
|
174
|
+
*/
|
|
175
|
+
_getPromptRules() {
|
|
176
|
+
return `## 大数据处理能力
|
|
177
|
+
|
|
178
|
+
你具备自动处理大文件和大网页的能力:
|
|
179
|
+
|
|
180
|
+
1. **内容预览**:当工具返回的内容很大时,先用 \`get_content_preview\` 查看统计信息
|
|
181
|
+
2. **分拆处理**:如果内容超过 10 万字符,用 \`split_and_process\` 将内容分块,每块交给独立的子 Agent 并行处理
|
|
182
|
+
3. **自动汇总**:\`split_and_process\` 会自动汇总所有子 Agent 的处理结果,你只需基于汇总结果回答即可
|
|
183
|
+
4. **性能提示**:分拆处理会并行运行多个子 Agent,处理大文件时效率很高
|
|
184
|
+
|
|
185
|
+
### 使用建议
|
|
186
|
+
- 读取大文件(> 100KB)时:先读取,如果内容太大,用 \`split_and_process\` 分拆分析
|
|
187
|
+
- 抓取网页时:如果页面内容过多,用 \`split_and_process\` 分拆提取
|
|
188
|
+
- 分拆时指定清晰的任务描述,例如"提取所有关键代码函数"、"总结每段内容要点"`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 注册自动分拆钩子
|
|
193
|
+
* 监听 tool:result 事件,如果结果太大自动触发分拆
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
_registerAutoSplitHook() {
|
|
197
|
+
const framework = this._framework;
|
|
198
|
+
|
|
199
|
+
// 监听工具结果,检测大数据
|
|
200
|
+
this._toolResultHandler = (data) => {
|
|
201
|
+
const { name, result } = data;
|
|
202
|
+
|
|
203
|
+
// 跳过已经标记为分拆结果的工具
|
|
204
|
+
if (name === 'split_and_process') return;
|
|
205
|
+
|
|
206
|
+
// 检查工具返回的内容是否过大(优先使用 data 字段)
|
|
207
|
+
let checkContent = '';
|
|
208
|
+
if (result && typeof result === 'object' && result.data) {
|
|
209
|
+
checkContent = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
|
|
210
|
+
} else {
|
|
211
|
+
checkContent = typeof result === 'string' ? result : JSON.stringify(result);
|
|
212
|
+
}
|
|
213
|
+
if (!checkContent || checkContent.length < this.config.autoSplitThreshold) return;
|
|
214
|
+
|
|
215
|
+
// 不阻塞执行,只在日志中记录建议
|
|
216
|
+
logger.info(
|
|
217
|
+
`[DataSplitter] 检测到大数据工具结果: ${name}, ` +
|
|
218
|
+
`${checkContent.length} 字符, 建议使用 split_and_process 分拆处理`
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
framework.on('tool:result', this._toolResultHandler);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 获取 DataSplitter 实例
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
_getSplitter(ctx) {
|
|
230
|
+
return this._splitter || new DataSplitter(this._framework, this.config);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
reload(framework) {
|
|
234
|
+
// 清理旧监听器
|
|
235
|
+
if (this._toolResultHandler) {
|
|
236
|
+
framework.off('tool:result', this._toolResultHandler);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this._framework = framework;
|
|
240
|
+
this._splitter = new DataSplitter(framework, this.config);
|
|
241
|
+
|
|
242
|
+
// 重新注册
|
|
243
|
+
if (framework._mainAgent) {
|
|
244
|
+
this.start(framework);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 请求刷新 mainAgent 的 system prompt
|
|
248
|
+
if (framework._mainAgent) {
|
|
249
|
+
framework._mainAgent._refreshContext();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
uninstall(framework) {
|
|
254
|
+
if (this._toolResultHandler) {
|
|
255
|
+
framework.off('tool:result', this._toolResultHandler);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 自动检测工具结果是否过大,并透明处理
|
|
262
|
+
* 在 agent-chat 中调用
|
|
263
|
+
*
|
|
264
|
+
* @param {string} toolName - 工具名称
|
|
265
|
+
* @param {*} result - 工具返回结果
|
|
266
|
+
* @param {Object} framework - Framework 实例
|
|
267
|
+
* @returns {Promise<{ wasSplit: boolean, result: *, splitterResult?: Object }>}
|
|
268
|
+
*/
|
|
269
|
+
async function autoSplitToolResult(toolName, result, framework) {
|
|
270
|
+
// 跳过某些工具
|
|
271
|
+
const skipTools = ['split_and_process', 'get_content_preview'];
|
|
272
|
+
if (skipTools.includes(toolName)) return { wasSplit: false, result };
|
|
273
|
+
|
|
274
|
+
// 无 framework 时无法创建子 Agent,跳过
|
|
275
|
+
if (!framework || typeof framework.createSubAgent !== 'function') {
|
|
276
|
+
return { wasSplit: false, result };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 提取用于判断大小的内容
|
|
280
|
+
// 优先使用 result.data(统一格式),再回退到整个结果字符串
|
|
281
|
+
let checkContent = '';
|
|
282
|
+
if (typeof result === 'object' && result !== null && result.data) {
|
|
283
|
+
checkContent = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
|
|
284
|
+
} else {
|
|
285
|
+
checkContent = typeof result === 'string' ? result : JSON.stringify(result);
|
|
286
|
+
}
|
|
287
|
+
if (!checkContent || checkContent.length < AUTO_SPLIT_THRESHOLD) {
|
|
288
|
+
return { wasSplit: false, result };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const splitter = new DataSplitter(framework);
|
|
292
|
+
const taskDescription = `从以下内容中提取关键信息(代码、配置、数据等),以结构化方式输出。`;
|
|
293
|
+
|
|
294
|
+
logger.info(
|
|
295
|
+
`[DataSplitter] 自动分拆工具 "${toolName}": ${checkContent.length} 字符`
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const chunks = splitter.splitContent(checkContent);
|
|
300
|
+
const splitResult = await splitter.dispatchToSubAgents({
|
|
301
|
+
chunks,
|
|
302
|
+
taskDescription,
|
|
303
|
+
maxConcurrent: 3,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
wasSplit: true,
|
|
308
|
+
// 返回原始结果 + 分拆汇总,AI 可以同时看到两者
|
|
309
|
+
result: {
|
|
310
|
+
_autoSplit: true,
|
|
311
|
+
_originalSize: checkContent.length,
|
|
312
|
+
_summary: splitResult.summary,
|
|
313
|
+
_originalResult: result, // 保留原始结果供参考
|
|
314
|
+
},
|
|
315
|
+
splitterResult: splitResult,
|
|
316
|
+
};
|
|
317
|
+
} catch (err) {
|
|
318
|
+
logger.warn(`[DataSplitter] 自动分拆失败: ${err.message}`);
|
|
319
|
+
return { wasSplit: false, result };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
module.exports = { DataSplitterPlugin, autoSplitToolResult };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 默认插件配置加载器
|
|
3
|
-
* 检测 .
|
|
3
|
+
* 检测 .foliko/ 目录下的配置,提供给 bootstrap() 使用
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs')
|
|
@@ -11,10 +11,10 @@ const log = logger.child('AgentConfig')
|
|
|
11
11
|
const bootstrapLog = logger.child('Bootstrap')
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* 加载 .
|
|
14
|
+
* 加载 .foliko 目录下的配置
|
|
15
15
|
* 返回配置对象,可用于手动加载插件
|
|
16
16
|
*/
|
|
17
|
-
function loadAgentConfig(agentDir = '.
|
|
17
|
+
function loadAgentConfig(agentDir = '.foliko') {
|
|
18
18
|
const config = {
|
|
19
19
|
agentDir,
|
|
20
20
|
ai: {},
|
|
@@ -28,7 +28,7 @@ function loadAgentConfig(agentDir = '.agent') {
|
|
|
28
28
|
const resolvedDir = path.resolve(process.cwd(), agentDir)
|
|
29
29
|
|
|
30
30
|
if (!fs.existsSync(resolvedDir)) {
|
|
31
|
-
log.info(` .
|
|
31
|
+
log.info(` .foliko directory not found: ${resolvedDir}`)
|
|
32
32
|
return config
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -136,7 +136,7 @@ function loadAgentConfig(agentDir = '.agent') {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// 添加 .
|
|
139
|
+
// 添加 .foliko/skills 目录(不存在则创建)
|
|
140
140
|
const skillsDir = path.join(resolvedDir, 'skills')
|
|
141
141
|
if (fs.existsSync(resolvedDir) && !fs.existsSync(skillsDir)) {
|
|
142
142
|
fs.mkdirSync(skillsDir, { recursive: true })
|
|
@@ -146,7 +146,7 @@ function loadAgentConfig(agentDir = '.agent') {
|
|
|
146
146
|
config.skillsDirs.push(skillsDir)
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
// 添加 .
|
|
149
|
+
// 添加 .foliko/skills 目录(不存在则创建)
|
|
150
150
|
const cmdskillsDir = path.join(cmdDir, 'skills')
|
|
151
151
|
if (fs.existsSync(resolvedDir) && !fs.existsSync(cmdskillsDir)) {
|
|
152
152
|
fs.mkdirSync(cmdskillsDir, { recursive: true })
|
|
@@ -184,7 +184,7 @@ class DefaultPlugins extends Plugin {
|
|
|
184
184
|
this.system = true
|
|
185
185
|
|
|
186
186
|
this._framework = null
|
|
187
|
-
this._agentDir = config.agentDir || '.
|
|
187
|
+
this._agentDir = config.agentDir || '.foliko'
|
|
188
188
|
this._config = null
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -364,7 +364,18 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
// 6.
|
|
367
|
+
// 6. 大数据分拆插件
|
|
368
|
+
if (shouldLoad('data-splitter')) {
|
|
369
|
+
const { DataSplitterPlugin } = require('./data-splitter-plugin')
|
|
370
|
+
await framework.loadPlugin(new DataSplitterPlugin({
|
|
371
|
+
autoSplitThreshold: agentConfig.dataSplitter?.autoSplitThreshold || 50000,
|
|
372
|
+
chunkSize: agentConfig.dataSplitter?.chunkSize || 60000,
|
|
373
|
+
maxConcurrent: agentConfig.dataSplitter?.maxConcurrent || 3,
|
|
374
|
+
}))
|
|
375
|
+
framework._debug&&bootstrapLog.debug(' DataSplitter Plugin loaded')
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// 7. 自动加载 plugins/ 目录下的所有插件
|
|
368
379
|
await loadCustomPlugins(framework, agentConfig)
|
|
369
380
|
|
|
370
381
|
framework._debug&&bootstrapLog.debug(' All plugins loaded')
|
|
@@ -382,8 +393,8 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
382
393
|
/**
|
|
383
394
|
* 解析插件路径
|
|
384
395
|
* 支持两种结构:
|
|
385
|
-
* 1. 文件夹结构: .
|
|
386
|
-
* 2. 单文件结构: .
|
|
396
|
+
* 1. 文件夹结构: .foliko/plugins/my-plugin/index.js
|
|
397
|
+
* 2. 单文件结构: .foliko/plugins/my-plugin.js
|
|
387
398
|
* 支持符号链接
|
|
388
399
|
* @param {string} pluginsDir - 插件目录
|
|
389
400
|
* @param {string} name - 插件名称
|
|
@@ -501,10 +512,10 @@ async function loadCustomPlugins(framework, agentConfig) {
|
|
|
501
512
|
const { Plugin } = require('../src/core/plugin-base')
|
|
502
513
|
|
|
503
514
|
// 加载两个目录下的自定义插件:
|
|
504
|
-
// 1. .
|
|
515
|
+
// 1. .foliko/plugins/ - 用户自定义插件(强制启用)
|
|
505
516
|
// 2. plugins/ - 项目内置插件(自动加载)
|
|
506
517
|
const dirs = [
|
|
507
|
-
{ dir: path.resolve(process.cwd(), '.
|
|
518
|
+
{ dir: path.resolve(process.cwd(), '.foliko', 'plugins'), forceEnabled: true },
|
|
508
519
|
{ dir: path.resolve(process.cwd(), 'plugins'), forceEnabled: false },
|
|
509
520
|
{ dir: path.resolve(__dirname, '..', 'plugins'), forceEnabled: false }
|
|
510
521
|
// 项目下的 plugins 目录(兼容旧版本)
|
package/plugins/email/index.js
CHANGED
|
@@ -94,7 +94,7 @@ class EmailPlugin extends Plugin {
|
|
|
94
94
|
|
|
95
95
|
// 持久化存储
|
|
96
96
|
this._emailStore = null
|
|
97
|
-
this._persistencePath = config.persistencePath || '.
|
|
97
|
+
this._persistencePath = config.persistencePath || '.foliko/data/email/processed-emails.json'
|
|
98
98
|
|
|
99
99
|
// 防抖相关
|
|
100
100
|
this._emailDebounceMap = new Map() // msgId -> timer
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const { Plugin } = require('../src/core/plugin-base');
|
|
7
7
|
const { logger } = require('../src/utils/logger');
|
|
8
8
|
const { z } = require('zod');
|
|
9
|
+
const { PROMPT_PRIORITY } = require('../src/core/constants');
|
|
9
10
|
const { zodSchemaToMarkdown,zodSchemaToTable } = require('@chnak/zod-to-markdown');
|
|
10
11
|
|
|
11
12
|
const log = logger.child('ExtensionExecutor');
|
|
@@ -208,7 +209,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
208
209
|
source: 'extension'
|
|
209
210
|
});
|
|
210
211
|
|
|
211
|
-
return { success: true, result };
|
|
212
|
+
return { success: true, data: result };
|
|
212
213
|
} catch (err) {
|
|
213
214
|
log.error(` Tool '${tool}' failed:`, err.message);
|
|
214
215
|
|
|
@@ -239,7 +240,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
239
240
|
tools: ext.tools.map((t) => ({ name: t.name, description: t.description })),
|
|
240
241
|
});
|
|
241
242
|
}
|
|
242
|
-
return { success: true, extensions };
|
|
243
|
+
return { success: true, data: extensions };
|
|
243
244
|
},
|
|
244
245
|
});
|
|
245
246
|
|
|
@@ -268,7 +269,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
// 注册扩展插件的提示词部分(使用基类方法)
|
|
271
|
-
this.registerPromptPart('extension-tools',
|
|
272
|
+
this.registerPromptPart('extension-tools', PROMPT_PRIORITY.EXTENSION_TOOLS, () => this._buildExtensionsDescription());
|
|
272
273
|
|
|
273
274
|
return this;
|
|
274
275
|
}
|
|
@@ -391,14 +392,41 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
391
392
|
if (this._extensions.size > 0 || (this._mcpExecutor && Object.keys(this._mcpExecutor.tools || {}).length > 0)) {
|
|
392
393
|
desc += '## 【Extensions】扩展插件\n\n';
|
|
393
394
|
desc += '**使用流程(必须按顺序执行):**\n';
|
|
394
|
-
desc += '1. 调用 `ext_skill({
|
|
395
|
-
desc += '2. 根据返回的参数定义,使用 `ext_call({ plugin, tool, args })`
|
|
395
|
+
desc += '1. 调用 `ext_skill({ plugin: "skill" })` 获取技能命令的详细参数\n';
|
|
396
|
+
desc += '2. 根据返回的参数定义,使用 `ext_call({ plugin: "skill", tool: "技能名:命令名", args: {...} })` 调用\n\n';
|
|
396
397
|
desc += '> **警告**:禁止在未执行第1步获取参数的情况下直接调用 `ext_call`!\n\n';
|
|
397
398
|
|
|
398
399
|
for (const [name, ext] of this._extensions) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
400
|
+
if (name === 'skill') {
|
|
401
|
+
// Skill 命令按技能名分组显示
|
|
402
|
+
const toolsBySkill = {};
|
|
403
|
+
for (const tool of ext.tools) {
|
|
404
|
+
// 命令格式: skillname:cmdname
|
|
405
|
+
const parts = tool.name.split(':');
|
|
406
|
+
const skillName = parts[0];
|
|
407
|
+
const cmdName = parts.slice(1).join(':');
|
|
408
|
+
if (!toolsBySkill[skillName]) {
|
|
409
|
+
toolsBySkill[skillName] = [];
|
|
410
|
+
}
|
|
411
|
+
toolsBySkill[skillName].push({ name: cmdName, description: tool.description });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
for (const [skillName, cmds] of Object.entries(toolsBySkill)) {
|
|
415
|
+
desc += `### skill.${skillName}\n`;
|
|
416
|
+
desc += `**调用方式:**\n`;
|
|
417
|
+
desc += `1. 先调用 \`loadSkill({ skill: "${skillName}" })\` 获取 \`${skillName}\` 技能的详细参数\n`;
|
|
418
|
+
desc += `2. 调用 \`ext_call({ plugin: "skill", tool: "${skillName}:<命令名>", args: { args: "参数" } })\`\n\n`;
|
|
419
|
+
desc += `**可用命令:**\n`;
|
|
420
|
+
for (const c of cmds) {
|
|
421
|
+
desc += `- \`${c.name}\`: ${c.description}\n`;
|
|
422
|
+
}
|
|
423
|
+
desc += '\n';
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
desc += `### ${ext.name || name}\n`;
|
|
427
|
+
desc += `${ext.description || '无描述'}\n`;
|
|
428
|
+
desc += `**工具:** ${ext.tools.map(t => `\`${t.name}\``).join(', ')}\n\n`;
|
|
429
|
+
}
|
|
402
430
|
}
|
|
403
431
|
|
|
404
432
|
// MCP 服务器工具
|
|
@@ -534,6 +562,56 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
534
562
|
}));
|
|
535
563
|
}
|
|
536
564
|
|
|
565
|
+
/**
|
|
566
|
+
* 获取 skill 命令列表(用于 autocomplete)
|
|
567
|
+
*/
|
|
568
|
+
getSkillCommands() {
|
|
569
|
+
const ext = this._extensions.get('skill');
|
|
570
|
+
if (!ext) return [];
|
|
571
|
+
return ext.tools.map(t => ({
|
|
572
|
+
name: t.name.replace(/^([^:]+):(.*)$/, '$1:$2'),
|
|
573
|
+
description: t.description || t.name,
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* 获取 skill 命令帮助文本
|
|
579
|
+
*/
|
|
580
|
+
getSkillCommandsHelp() {
|
|
581
|
+
const ext = this._extensions.get('skill');
|
|
582
|
+
if (!ext || ext.tools.length === 0) return '';
|
|
583
|
+
|
|
584
|
+
const lines = [];
|
|
585
|
+
for (const tool of ext.tools) {
|
|
586
|
+
const parts = tool.name.split(':');
|
|
587
|
+
if (parts.length === 2) {
|
|
588
|
+
lines.push(`/${parts[0]}:${parts[1]} - ${tool.description || '无描述'}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return lines.join('\n');
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* 检查并执行 skill 命令
|
|
596
|
+
* @param {string} command - 命令名(如 "skillname:cmdname")
|
|
597
|
+
* @param {string} args - 命令参数
|
|
598
|
+
* @returns {Promise<object|null>} 执行结果,失败返回 null
|
|
599
|
+
*/
|
|
600
|
+
async executeSkillCommand(command, args) {
|
|
601
|
+
if (!command.includes(':')) return null;
|
|
602
|
+
try {
|
|
603
|
+
const result = await this._framework.executeTool('ext_call', {
|
|
604
|
+
plugin: 'skill',
|
|
605
|
+
tool: command,
|
|
606
|
+
args: { args }
|
|
607
|
+
});
|
|
608
|
+
return result.success !== false ? result : null;
|
|
609
|
+
} catch (err) {
|
|
610
|
+
log.warn('Skill command failed:', err.message);
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
537
615
|
async reload(framework) {
|
|
538
616
|
this._framework = framework;
|
|
539
617
|
// 重新扫描所有已加载插件的 tools
|
|
@@ -559,7 +637,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
559
637
|
if (!mcpExecutor) return;
|
|
560
638
|
|
|
561
639
|
try {
|
|
562
|
-
const configPath = path.resolve('.
|
|
640
|
+
const configPath = path.resolve('.foliko/mcp_config.json');
|
|
563
641
|
if (fs.existsSync(configPath)) {
|
|
564
642
|
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
565
643
|
const config = JSON.parse(configContent);
|