foliko 2.0.4 → 2.0.6
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 +4 -1
- package/.cli_default_systemPrompt.md +291 -0
- package/CLAUDE.md +3 -0
- package/README.md +20 -3
- package/docs/architecture.md +34 -2
- package/docs/extensions.md +199 -0
- package/docs/migration.md +100 -0
- package/docs/public-api.md +1180 -6
- package/docs/usage.md +122 -30
- package/package.json +1 -1
- package/plugins/core/audit/index.js +1 -1
- package/plugins/core/default/bootstrap.js +65 -19
- package/plugins/core/python-loader/index.js +43 -25
- package/plugins/core/skill-manager/PROMPT.md +6 -0
- package/plugins/core/skill-manager/index.js +538 -93
- package/plugins/core/sub-agent/PROMPT.md +10 -0
- package/plugins/core/sub-agent/index.js +36 -3
- package/plugins/core/think/index.js +1 -0
- package/plugins/core/workflow/index.js +106 -22
- package/plugins/executors/data-splitter/PROMPT.md +13 -0
- package/plugins/executors/data-splitter/index.js +5 -4
- package/plugins/executors/extension/extension-registry.js +145 -0
- package/plugins/executors/extension/index.js +405 -437
- package/plugins/executors/extension/prompt-builder.js +359 -0
- package/plugins/executors/extension/skill-helper.js +143 -0
- package/plugins/messaging/feishu/index.js +5 -3
- package/plugins/messaging/qq/index.js +6 -4
- package/plugins/messaging/telegram/index.js +6 -3
- package/plugins/messaging/weixin/index.js +5 -3
- package/plugins/tools/PROMPT.md +26 -0
- package/plugins/tools/index.js +6 -5
- package/skills/foliko/AGENTS.md +196 -43
- package/skills/foliko/SKILL.md +157 -28
- package/skills/mcp/SKILL.md +77 -118
- package/skills/plugins/SKILL.md +89 -3
- package/skills/python/SKILL.md +57 -39
- package/skills/skill-guide/SKILL.md +42 -34
- package/skills/workflows/SKILL.md +224 -9
- package/skills/workflows/workflow-troubleshooting/SKILL.md +221 -281
- package/src/agent/chat.js +48 -27
- package/src/agent/main.js +34 -13
- package/src/agent/prompt-registry.js +133 -87
- package/src/agent/prompts/PROMPT.md +3 -0
- package/src/agent/sub.js +1 -1
- package/src/cli/ui/chat-ui-old.js +5 -2
- package/src/cli/ui/chat-ui.js +5 -2
- package/src/common/constants.js +12 -0
- package/src/common/error-capture.js +91 -0
- package/src/common/logger.js +2 -2
- package/src/context/compressor.js +6 -2
- package/src/executors/mcp-executor.js +105 -125
- package/src/framework/framework.js +632 -6
- package/src/index.js +4 -0
- package/src/plugin/base.js +913 -10
- package/src/plugin/manager.js +29 -8
- package/src/tool/schema.js +32 -9
- package/tests/core/plugin-prompts.test.js +13 -13
- package/tests/core/prompt-registry.test.js +59 -51
- package/website/index.html +821 -0
|
@@ -306,10 +306,14 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
306
306
|
prompts = [
|
|
307
307
|
{
|
|
308
308
|
name: 'subagents',
|
|
309
|
-
|
|
309
|
+
file: path.join(__dirname, 'PROMPT.md'),
|
|
310
|
+
scope: 'global',
|
|
311
|
+
priority: 60,
|
|
310
312
|
description: '子Agent描述与分配规则',
|
|
311
|
-
|
|
312
|
-
|
|
313
|
+
// interpolate 在渲染时注入动态内容
|
|
314
|
+
interpolate: (content) => {
|
|
315
|
+
const list = this._buildSubAgentsList();
|
|
316
|
+
return content.replace('{{subagents}}', list || '(暂无)');
|
|
313
317
|
},
|
|
314
318
|
},
|
|
315
319
|
];
|
|
@@ -567,6 +571,35 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
567
571
|
return config
|
|
568
572
|
}
|
|
569
573
|
|
|
574
|
+
_buildSubAgentsList() {
|
|
575
|
+
const allSubAgents = this.getAllSubAgents();
|
|
576
|
+
if (!allSubAgents || allSubAgents.length === 0) {
|
|
577
|
+
return '';
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const lines = [];
|
|
581
|
+
for (const plugin of allSubAgents) {
|
|
582
|
+
const name = plugin.name;
|
|
583
|
+
const role = plugin.role;
|
|
584
|
+
const goal = plugin.description || '';
|
|
585
|
+
|
|
586
|
+
const config = this._framework._subAgentConfigManager?.get(name);
|
|
587
|
+
if (config) {
|
|
588
|
+
const agentDesc = config.getDescription();
|
|
589
|
+
const skills = config.getSkills();
|
|
590
|
+
let info = agentDesc || role || goal || '子代理';
|
|
591
|
+
if (skills && skills.length > 0) {
|
|
592
|
+
info += ` (技能: ${skills.join(', ')})`;
|
|
593
|
+
}
|
|
594
|
+
lines.push(`- **${name}**:${info}`);
|
|
595
|
+
} else {
|
|
596
|
+
lines.push(`- **${name}**:${role || goal || '子代理'}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return lines.join('\n');
|
|
601
|
+
}
|
|
602
|
+
|
|
570
603
|
_buildDescription() {
|
|
571
604
|
const allSubAgents = this.getAllSubAgents();
|
|
572
605
|
if (!allSubAgents || allSubAgents.length === 0) {
|
|
@@ -287,33 +287,93 @@ class StepExecutor {
|
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
289
289
|
* 执行循环步骤
|
|
290
|
+
* 支持两种模式:
|
|
291
|
+
* 1. 固定次数循环:maxIterations + loopVariable
|
|
292
|
+
* 2. 数组迭代循环:over + as + index
|
|
290
293
|
*/
|
|
291
294
|
async executeLoop(step, context) {
|
|
292
295
|
this._log.debug(` Executing loop: ${step.name || step.id}`);
|
|
293
296
|
const results = [];
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
297
|
+
|
|
298
|
+
// 检查是否是数组迭代模式 (over + as)
|
|
299
|
+
if (step.over && step.as) {
|
|
300
|
+
// 数组迭代模式
|
|
301
|
+
const arrayPath = step.over;
|
|
302
|
+
const itemVar = step.as;
|
|
303
|
+
const indexVar = step.index || 'index';
|
|
304
|
+
|
|
305
|
+
// 解析数组路径 - 特殊处理 input.xxx 路径
|
|
306
|
+
let array;
|
|
307
|
+
if (arrayPath.startsWith('input.')) {
|
|
308
|
+
// 直接从 context.input 获取
|
|
309
|
+
const key = arrayPath.substring(6); // 去掉 'input.' 前缀
|
|
310
|
+
array = context.input ? context.input[key] : undefined;
|
|
311
|
+
} else {
|
|
312
|
+
array = this._resolveValue(`{{${arrayPath}}}`, context);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!Array.isArray(array)) {
|
|
316
|
+
this._log.warn(` Loop over: '${arrayPath}' is not an array, skipping. Found:`, typeof array);
|
|
317
|
+
return results;
|
|
303
318
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
319
|
+
|
|
320
|
+
const maxIterations = step.maxIterations || array.length;
|
|
321
|
+
for (let i = 0; i < Math.min(maxIterations, array.length); i++) {
|
|
322
|
+
// 设置循环变量
|
|
323
|
+
context.variables[indexVar] = i;
|
|
324
|
+
context.variables[itemVar] = array[i];
|
|
325
|
+
context.variables.loopIndex = i; // 兼容旧代码
|
|
326
|
+
|
|
327
|
+
this._log.debug(` Loop iteration ${i}: ${itemVar}=`, array[i]);
|
|
328
|
+
|
|
329
|
+
// 执行循环体
|
|
330
|
+
const iterationResults = [];
|
|
331
|
+
for (const stepConfig of step.steps || []) {
|
|
332
|
+
const result = await this.executeStep(stepConfig, context);
|
|
333
|
+
iterationResults.push(result);
|
|
334
|
+
}
|
|
335
|
+
results.push(iterationResults);
|
|
336
|
+
|
|
337
|
+
// 检查终止条件
|
|
338
|
+
if (step.until) {
|
|
339
|
+
let shouldStop = false;
|
|
340
|
+
if (typeof step.until === 'function') {
|
|
341
|
+
shouldStop = step.until(context);
|
|
342
|
+
} else if (typeof step.until === 'string') {
|
|
343
|
+
shouldStop = await evaluateInSandbox(step.until, context, { timeout: 5000 });
|
|
344
|
+
}
|
|
345
|
+
if (shouldStop) {
|
|
346
|
+
this._log.debug(` Loop terminated at iteration ${i}`);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
// 固定次数循环模式(原有逻辑)
|
|
353
|
+
const maxIterations = step.maxIterations || 10;
|
|
354
|
+
const loopVariable = step.loopVariable || 'loopIndex';
|
|
355
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
356
|
+
context.variables[loopVariable] = i;
|
|
357
|
+
context.variables.loopIndex = i; // 兼容
|
|
358
|
+
this._log.debug(` Loop iteration ${i}`);
|
|
359
|
+
const iterationResults = [];
|
|
360
|
+
for (const stepConfig of step.steps || []) {
|
|
361
|
+
const result = await this.executeStep(stepConfig, context);
|
|
362
|
+
iterationResults.push(result);
|
|
313
363
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
364
|
+
results.push(iterationResults);
|
|
365
|
+
// 检查终止条件
|
|
366
|
+
if (step.until) {
|
|
367
|
+
let shouldStop = false;
|
|
368
|
+
if (typeof step.until === 'function') {
|
|
369
|
+
shouldStop = step.until(context);
|
|
370
|
+
} else if (typeof step.until === 'string') {
|
|
371
|
+
shouldStop = await evaluateInSandbox(step.until, context, { timeout: 5000 });
|
|
372
|
+
}
|
|
373
|
+
if (shouldStop) {
|
|
374
|
+
this._log.debug(` Loop terminated at iteration ${i}`);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
317
377
|
}
|
|
318
378
|
}
|
|
319
379
|
}
|
|
@@ -773,7 +833,7 @@ class WorkflowEngine extends EventEmitter {
|
|
|
773
833
|
type = StepType.WORKFLOW;
|
|
774
834
|
} else if (config.branches) {
|
|
775
835
|
type = StepType.CONDITION;
|
|
776
|
-
} else if (config.steps && (config.loopVariable || config.maxIterations)) {
|
|
836
|
+
} else if (config.steps && (config.loopVariable || config.maxIterations || config.over)) {
|
|
777
837
|
type = StepType.LOOP;
|
|
778
838
|
} else if (config.steps) {
|
|
779
839
|
type = StepType.SEQUENTIAL;
|
|
@@ -981,6 +1041,30 @@ class WorkflowPlugin extends Plugin {
|
|
|
981
1041
|
// log.info(` Registered tool: ${toolName}`);
|
|
982
1042
|
}
|
|
983
1043
|
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* 移除工作流
|
|
1047
|
+
* @param {string} name 工作流名称
|
|
1048
|
+
* @returns {boolean} 是否成功移除
|
|
1049
|
+
*/
|
|
1050
|
+
remove(name) {
|
|
1051
|
+
if (!this._workflows.has(name)) {
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// 从工作流定义中删除
|
|
1056
|
+
this._workflows.delete(name);
|
|
1057
|
+
|
|
1058
|
+
// 清除已注册的工具
|
|
1059
|
+
const toolName = this._workflowTools.get(name);
|
|
1060
|
+
if (toolName) {
|
|
1061
|
+
// 工具注销需要框架支持,这里只清理内部状态
|
|
1062
|
+
this._workflowTools.delete(name);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
return true;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
984
1068
|
/**
|
|
985
1069
|
* 重载工作流
|
|
986
1070
|
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## 大数据处理能力
|
|
2
|
+
|
|
3
|
+
你具备自动处理大文件和大网页的能力:
|
|
4
|
+
|
|
5
|
+
1. **内容预览**:当工具返回的内容很大时,先用 `get_content_preview` 查看统计信息
|
|
6
|
+
2. **分拆处理**:如果内容超过 10 万字符,用 `split_and_process` 将内容分块,每块交给独立的子 Agent 并行处理
|
|
7
|
+
3. **自动汇总**:`split_and_process` 会自动汇总所有子 Agent 的处理结果,你只需基于汇总结果回答即可
|
|
8
|
+
4. **性能提示**:分拆处理会并行运行多个子 Agent,处理大文件时效率很高
|
|
9
|
+
|
|
10
|
+
### 使用建议
|
|
11
|
+
- 读取大文件(> 100KB)时:先读取,如果内容太大,用 `split_and_process` 分拆分析
|
|
12
|
+
- 抓取网页时:如果页面内容过多,用 `split_and_process` 分拆提取
|
|
13
|
+
- 分拆时指定清晰的任务描述,例如"提取所有关键代码函数"、"总结每段内容要点"
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* DataSplitterPlugin — 大数据自动分拆插件
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const path = require('path');
|
|
5
6
|
const { Plugin } = require('../../../src/plugin/base');
|
|
6
7
|
const { z } = require('zod');
|
|
7
8
|
const { logger } = require('../../../src/common/logger');
|
|
8
9
|
const { DataSplitter } = require('../../../src/utils/data-splitter');
|
|
9
10
|
|
|
10
11
|
const AUTO_SPLIT_THRESHOLD = 50000;
|
|
12
|
+
const PROMPT_DIR = __dirname;
|
|
11
13
|
|
|
12
14
|
class DataSplitterPlugin extends Plugin {
|
|
13
15
|
constructor(config = {}) {
|
|
@@ -43,11 +45,10 @@ class DataSplitterPlugin extends Plugin {
|
|
|
43
45
|
prompts = [
|
|
44
46
|
{
|
|
45
47
|
name: 'data-splitter-rules',
|
|
46
|
-
|
|
48
|
+
file: path.join(PROMPT_DIR, 'PROMPT.md'),
|
|
49
|
+
scope: 'global',
|
|
50
|
+
priority: 50,
|
|
47
51
|
description: '大工具结果自动分拆规则',
|
|
48
|
-
provider: function () {
|
|
49
|
-
return this._getPromptRules();
|
|
50
|
-
},
|
|
51
52
|
},
|
|
52
53
|
];
|
|
53
54
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ExtensionRegistry - 扩展注册表
|
|
5
|
+
* 管理扩展插件及其工具的增删改查
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class ExtensionRegistry {
|
|
9
|
+
constructor(framework) {
|
|
10
|
+
this._extensions = new Map();
|
|
11
|
+
this._framework = framework;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ============ 注册 ============
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 注册或获取扩展条目
|
|
18
|
+
*/
|
|
19
|
+
_getOrCreate(pluginName, pluginInfo) {
|
|
20
|
+
if (!this._extensions.has(pluginName)) {
|
|
21
|
+
this._extensions.set(pluginName, {
|
|
22
|
+
name: pluginInfo.name || pluginName,
|
|
23
|
+
description: pluginInfo.description || '',
|
|
24
|
+
version: pluginInfo.version || '1.0.0',
|
|
25
|
+
tools: [],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return this._extensions.get(pluginName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 注册扩展工具
|
|
33
|
+
*/
|
|
34
|
+
registerTool(pluginName, pluginInfo, toolDef) {
|
|
35
|
+
if (!toolDef || !toolDef.name) return false;
|
|
36
|
+
|
|
37
|
+
const ext = this._getOrCreate(pluginName, pluginInfo);
|
|
38
|
+
|
|
39
|
+
const toolEntry = {
|
|
40
|
+
name: toolDef.name,
|
|
41
|
+
description: toolDef.description || '',
|
|
42
|
+
inputSchema: toolDef.inputSchema,
|
|
43
|
+
execute: toolDef.execute,
|
|
44
|
+
};
|
|
45
|
+
if (toolDef._options) {
|
|
46
|
+
toolEntry._options = toolDef._options;
|
|
47
|
+
}
|
|
48
|
+
if (toolDef._rawSchema) {
|
|
49
|
+
toolEntry._rawSchema = toolDef._rawSchema;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const existingIdx = ext.tools.findIndex((t) => t.name === toolDef.name);
|
|
53
|
+
if (existingIdx >= 0) {
|
|
54
|
+
ext.tools[existingIdx] = toolEntry;
|
|
55
|
+
} else {
|
|
56
|
+
ext.tools.push(toolEntry);
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 注销扩展工具
|
|
63
|
+
* @returns {boolean} 是否真正移除了扩展
|
|
64
|
+
*/
|
|
65
|
+
unregisterTool(pluginName, toolName) {
|
|
66
|
+
const ext = this._extensions.get(pluginName);
|
|
67
|
+
if (!ext) return false;
|
|
68
|
+
|
|
69
|
+
const idx = ext.tools.findIndex((t) => t.name === toolName);
|
|
70
|
+
if (idx >= 0) {
|
|
71
|
+
ext.tools.splice(idx, 1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 如果扩展没有工具了,删除整个扩展
|
|
75
|
+
if (ext.tools.length === 0) {
|
|
76
|
+
this._extensions.delete(pluginName);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 清空指定扩展的所有工具
|
|
84
|
+
*/
|
|
85
|
+
clearPlugin(pluginName) {
|
|
86
|
+
const ext = this._extensions.get(pluginName);
|
|
87
|
+
if (!ext) return false;
|
|
88
|
+
ext.tools = [];
|
|
89
|
+
this._extensions.delete(pluginName);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 清空所有
|
|
95
|
+
*/
|
|
96
|
+
clear() {
|
|
97
|
+
this._extensions.clear();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============ 查询 ============
|
|
101
|
+
|
|
102
|
+
hasPlugin(name) {
|
|
103
|
+
return this._extensions.has(name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
hasTool(pluginName, toolName) {
|
|
107
|
+
const ext = this._extensions.get(pluginName);
|
|
108
|
+
if (!ext) return false;
|
|
109
|
+
return ext.tools.some((t) => t.name === toolName);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getPlugin(name) {
|
|
113
|
+
return this._extensions.get(name) || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getTool(pluginName, toolName) {
|
|
117
|
+
const ext = this._extensions.get(pluginName);
|
|
118
|
+
if (!ext) return null;
|
|
119
|
+
return ext.tools.find((t) => t.name === toolName) || null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
listPlugins() {
|
|
123
|
+
return Array.from(this._extensions.keys());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
listTools(pluginName) {
|
|
127
|
+
const ext = this._extensions.get(pluginName);
|
|
128
|
+
return ext ? ext.tools : [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getAll() {
|
|
132
|
+
return Array.from(this._extensions.entries()).map(([name, ext]) => ({
|
|
133
|
+
name,
|
|
134
|
+
description: ext.description,
|
|
135
|
+
version: ext.version,
|
|
136
|
+
tools: ext.tools,
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
size() {
|
|
141
|
+
return this._extensions.size;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = { ExtensionRegistry };
|