foliko 1.0.76 → 1.0.78
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/data/default.json +31559 -0
- package/.agent/data/plugins-state.json +10 -1
- package/.claude/settings.local.json +13 -2
- package/.env.example +56 -54
- package/examples/basic.js +1 -1
- package/package.json +83 -81
- package/plugins/ai-plugin.js +2 -2
- package/plugins/ambient-agent/index.js +1 -1
- package/plugins/audit-plugin.js +1 -1
- package/plugins/default-plugins.js +92 -209
- package/plugins/email/index.js +1 -1
- package/plugins/extension-executor-plugin.js +326 -0
- package/plugins/feishu-plugin.js +1 -1
- package/plugins/file-system-plugin.js +57 -6
- package/plugins/gate-trading.js +747 -0
- package/plugins/install-plugin.js +1 -1
- package/plugins/python-executor-plugin.js +1 -1
- package/plugins/python-plugin-loader.js +275 -105
- package/plugins/rules-plugin.js +1 -1
- package/plugins/scheduler-plugin.js +1 -1
- package/plugins/session-plugin.js +132 -7
- package/plugins/shell-executor-plugin.js +1 -1
- package/plugins/storage-plugin.js +24 -1
- package/plugins/subagent-plugin.js +2 -2
- package/plugins/telegram-plugin.js +1 -1
- package/plugins/think-plugin.js +10 -10
- package/plugins/tools-plugin.js +1 -1
- package/plugins/web-plugin.js +49 -18
- package/plugins/weixin-plugin.js +1 -1
- package/skills/foliko-dev/SKILL.md +583 -500
- package/skills/python-plugin-dev/SKILL.md +238 -266
- package/src/core/agent-chat.js +103 -4
- package/src/core/agent.js +84 -18
- package/src/core/plugin-base.js +43 -0
- package/src/executors/mcp-executor.js +126 -22
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExtensionExecutorPlugin 扩展插件执行器
|
|
3
|
+
* 统一管理扩展插件(如 gate-trading)的工具,通过 ext_call 调用
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Plugin } = require('../src/core/plugin-base');
|
|
7
|
+
const { logger } = require('../src/utils/logger');
|
|
8
|
+
const { z } = require('zod');
|
|
9
|
+
const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown');
|
|
10
|
+
|
|
11
|
+
const log = logger.child('ExtensionExecutor');
|
|
12
|
+
|
|
13
|
+
class ExtensionExecutorPlugin extends Plugin {
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
super();
|
|
16
|
+
this.name = 'extension-executor';
|
|
17
|
+
this.version = '1.0.0';
|
|
18
|
+
this.description = '扩展插件执行器 - 统一管理扩展工具';
|
|
19
|
+
this.priority = 10;
|
|
20
|
+
this.system = true;
|
|
21
|
+
|
|
22
|
+
this._framework = null;
|
|
23
|
+
this._extensions = new Map();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
install(framework) {
|
|
27
|
+
this._framework = framework;
|
|
28
|
+
|
|
29
|
+
// 监听插件加载事件,自动扫描插件的 this.tools
|
|
30
|
+
framework.on('plugin:loaded', (plugin) => {
|
|
31
|
+
this._scanPluginTools(plugin);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 扫描插件的 this.tools 并注册到扩展系统
|
|
39
|
+
*/
|
|
40
|
+
_scanPluginTools(plugin) {
|
|
41
|
+
if (!plugin || !plugin.tools || typeof plugin.tools !== 'object') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const pluginName = plugin.name;
|
|
46
|
+
if (!pluginName) return;
|
|
47
|
+
|
|
48
|
+
// 获取插件信息
|
|
49
|
+
const pluginInfo = {
|
|
50
|
+
name: plugin.name,
|
|
51
|
+
description: plugin.description || '',
|
|
52
|
+
version: plugin.version || '1.0.0',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// 遍历插件的所有工具
|
|
56
|
+
for (const [toolName, toolDef] of Object.entries(plugin.tools)) {
|
|
57
|
+
if (!toolDef || typeof toolDef !== 'object') continue;
|
|
58
|
+
|
|
59
|
+
this.registerTool(pluginName, pluginInfo, {
|
|
60
|
+
name: toolName,
|
|
61
|
+
description: toolDef.description || '',
|
|
62
|
+
inputSchema: toolDef.inputSchema,
|
|
63
|
+
execute: toolDef.execute,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log.debug(` Scanned ${Object.keys(plugin.tools).length} tools from plugin '${pluginName}'`);
|
|
68
|
+
|
|
69
|
+
// 刷新所有 Agent 的扩展提示词
|
|
70
|
+
if (this._framework && this._framework._ready) {
|
|
71
|
+
this._refreshAllAgentsExtPrompt(this._framework);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async start(framework) {
|
|
76
|
+
// 扫描所有已加载插件的 tools
|
|
77
|
+
const plugins = framework.pluginManager.getAll();
|
|
78
|
+
for (const { instance: plugin } of plugins) {
|
|
79
|
+
this._scanPluginTools(plugin);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
framework.registerTool({
|
|
83
|
+
name: 'ext_call',
|
|
84
|
+
description: '调用扩展插件的工具',
|
|
85
|
+
inputSchema: z.object({
|
|
86
|
+
plugin: z.string().describe('扩展插件名称'),
|
|
87
|
+
tool: z.string().describe('工具名称'),
|
|
88
|
+
args: z.record(z.any()).optional().describe('工具参数'),
|
|
89
|
+
}),
|
|
90
|
+
execute: async (args) => {
|
|
91
|
+
const { plugin, tool, args: toolArgs = {} } = args;
|
|
92
|
+
log.info(` ext_call: plugin=${plugin}, tool=${tool}`);
|
|
93
|
+
|
|
94
|
+
const ext = this._extensions.get(plugin);
|
|
95
|
+
if (!ext) {
|
|
96
|
+
return { success: false, error: `扩展插件 '${plugin}' 不存在` };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const toolDef = ext.tools.find((t) => t.name === tool);
|
|
100
|
+
if (!toolDef) {
|
|
101
|
+
return { success: false, error: `扩展工具 '${tool}' 不存在` };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
// 统一 execute 签名为 (args, framework)
|
|
106
|
+
const result = await toolDef.execute(toolArgs, framework);
|
|
107
|
+
return { success: true, result };
|
|
108
|
+
} catch (err) {
|
|
109
|
+
log.error(` Tool '${tool}' failed:`, err.message);
|
|
110
|
+
return { success: false, error: err.message };
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
framework.registerTool({
|
|
116
|
+
name: 'ext_list',
|
|
117
|
+
description: '列出所有可用的扩展插件及其工具',
|
|
118
|
+
inputSchema: z.object({}),
|
|
119
|
+
execute: async () => {
|
|
120
|
+
const extensions = [];
|
|
121
|
+
for (const [name, ext] of this._extensions) {
|
|
122
|
+
extensions.push({
|
|
123
|
+
name,
|
|
124
|
+
description: ext.description,
|
|
125
|
+
version: ext.version,
|
|
126
|
+
tools: ext.tools.map((t) => ({ name: t.name, description: t.description })),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return { success: true, extensions };
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
framework.registerTool({
|
|
134
|
+
name: 'ext_schema',
|
|
135
|
+
description: '查询扩展工具的调用参数结构',
|
|
136
|
+
inputSchema: z.object({
|
|
137
|
+
plugin: z.string().describe('扩展插件名称'),
|
|
138
|
+
tool: z.string().describe('工具名称'),
|
|
139
|
+
}),
|
|
140
|
+
execute: async (args) => {
|
|
141
|
+
const { plugin, tool } = args;
|
|
142
|
+
const ext = this._extensions.get(plugin);
|
|
143
|
+
if (!ext) {
|
|
144
|
+
return { success: false, error: `扩展插件 '${plugin}' 不存在` };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const toolDef = ext.tools.find((t) => t.name === tool);
|
|
148
|
+
if (!toolDef) {
|
|
149
|
+
return { success: false, error: `扩展工具 '${tool}' 不存在` };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
result: {
|
|
155
|
+
plugin,
|
|
156
|
+
tool,
|
|
157
|
+
description: toolDef.description,
|
|
158
|
+
inputSchema: toolDef.inputSchema,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
framework.on('agent:created', (agent) => {
|
|
165
|
+
this._refreshAgentExtPrompt(agent);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (framework._ready) {
|
|
169
|
+
this._refreshAllAgentsExtPrompt(framework);
|
|
170
|
+
} else {
|
|
171
|
+
framework.once('framework:ready', () => {
|
|
172
|
+
this._refreshAllAgentsExtPrompt(framework);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
registerTool(pluginName, pluginInfo, toolDef) {
|
|
180
|
+
if (!this._extensions.has(pluginName)) {
|
|
181
|
+
this._extensions.set(pluginName, {
|
|
182
|
+
name: pluginInfo.name || pluginName,
|
|
183
|
+
description: pluginInfo.description || '',
|
|
184
|
+
version: pluginInfo.version || '1.0.0',
|
|
185
|
+
tools: [],
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const ext = this._extensions.get(pluginName);
|
|
190
|
+
if (!ext.tools.find((t) => t.name === toolDef.name)) {
|
|
191
|
+
ext.tools.push({
|
|
192
|
+
name: toolDef.name,
|
|
193
|
+
description: toolDef.description || '',
|
|
194
|
+
inputSchema: toolDef.inputSchema,
|
|
195
|
+
execute: toolDef.execute,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
log.debug(` Registered tool '${toolDef.name}' for extension '${pluginName}'`);
|
|
200
|
+
|
|
201
|
+
// 刷新所有 Agent 的扩展提示词
|
|
202
|
+
if (this._framework && this._framework._ready) {
|
|
203
|
+
this._refreshAllAgentsExtPrompt(this._framework);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_refreshAllAgentsExtPrompt(framework) {
|
|
208
|
+
const visited = new Set();
|
|
209
|
+
const traverse = (agent) => {
|
|
210
|
+
if (!agent || visited.has(agent)) return;
|
|
211
|
+
visited.add(agent);
|
|
212
|
+
this._refreshAgentExtPrompt(agent);
|
|
213
|
+
const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map();
|
|
214
|
+
for (const [, subAgentInfo] of subAgents) {
|
|
215
|
+
traverse(subAgentInfo.agent);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
for (const agent of framework._agents || []) {
|
|
219
|
+
traverse(agent);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_refreshAgentExtPrompt(agent) {
|
|
224
|
+
const extDesc = this._buildExtensionsDescription();
|
|
225
|
+
if (!extDesc) return;
|
|
226
|
+
|
|
227
|
+
const existingPrompt = agent._originalPrompt || '';
|
|
228
|
+
|
|
229
|
+
// 如果已有 [Extensions] 部分,替换它
|
|
230
|
+
const extStartIdx = existingPrompt.indexOf('[Extensions]');
|
|
231
|
+
if (extStartIdx !== -1) {
|
|
232
|
+
const extEndMarker = '\n\n【';
|
|
233
|
+
let extEndIdx = existingPrompt.indexOf(extEndMarker, extStartIdx);
|
|
234
|
+
if (extEndIdx === -1) {
|
|
235
|
+
// 尝试其他标记
|
|
236
|
+
extEndIdx = existingPrompt.indexOf('\n\n---\n\n', extStartIdx);
|
|
237
|
+
}
|
|
238
|
+
if (extEndIdx === -1) {
|
|
239
|
+
extEndIdx = existingPrompt.length;
|
|
240
|
+
} else {
|
|
241
|
+
extEndIdx += extEndMarker.length;
|
|
242
|
+
}
|
|
243
|
+
const newPrompt = existingPrompt.substring(0, extStartIdx) + extDesc + existingPrompt.substring(extEndIdx);
|
|
244
|
+
agent.setSystemPrompt(newPrompt);
|
|
245
|
+
} else {
|
|
246
|
+
agent.setSystemPrompt(existingPrompt + '\n\n' + extDesc);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_buildExtensionsDescription() {
|
|
251
|
+
if (this._extensions.size === 0) {
|
|
252
|
+
return '';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let desc = '[Extensions]\n\n';
|
|
256
|
+
desc += '你可以通过 `ext_call` 工具调用以下扩展插件的功能。\n\n';
|
|
257
|
+
|
|
258
|
+
for (const [name, ext] of this._extensions) {
|
|
259
|
+
desc += `### ${ext.name || name}\n\n`;
|
|
260
|
+
desc += `${ext.description || ''}\n\n`;
|
|
261
|
+
for (const tool of ext.tools) {
|
|
262
|
+
desc += `- **${tool.name}**: ${tool.description || '无描述'}\n`;
|
|
263
|
+
// 添加参数描述
|
|
264
|
+
if (tool.inputSchema) {
|
|
265
|
+
try {
|
|
266
|
+
if (typeof tool.inputSchema.toJSON === 'function') {
|
|
267
|
+
desc += `**参数:**\n\n`;
|
|
268
|
+
desc += zodSchemaToMarkdown(tool.inputSchema) + '\n\n';
|
|
269
|
+
} else if (tool.inputSchema.properties) {
|
|
270
|
+
// JSON Schema 格式
|
|
271
|
+
desc += `**参数:**\n\n`;
|
|
272
|
+
desc += this._schemaToMarkdown(tool.inputSchema) + '\n\n';
|
|
273
|
+
}
|
|
274
|
+
} catch (e) {
|
|
275
|
+
// 忽略转换错误
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
desc += '\n';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
desc += '**调用格式:**\n';
|
|
283
|
+
desc += '```\next_call({ plugin: "插件名", tool: "工具名", args: {参数} })\n';
|
|
284
|
+
desc += '```\n';
|
|
285
|
+
return desc.trim();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 将 JSON Schema 转换为 Markdown
|
|
290
|
+
*/
|
|
291
|
+
_schemaToMarkdown(schema) {
|
|
292
|
+
if (!schema || !schema.properties) return '';
|
|
293
|
+
|
|
294
|
+
const props = schema.properties || {};
|
|
295
|
+
const required = schema.required || [];
|
|
296
|
+
let md = '';
|
|
297
|
+
|
|
298
|
+
for (const [key, prop] of Object.entries(props)) {
|
|
299
|
+
const isRequired = required.includes(key);
|
|
300
|
+
const type = prop.type || 'any';
|
|
301
|
+
const descText = prop.description || '';
|
|
302
|
+
md += `- \`${key}\`${isRequired ? ' (必填)' : ''}: ${type} ${descText}\n`;
|
|
303
|
+
}
|
|
304
|
+
return md;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
getExtensions() {
|
|
308
|
+
return Array.from(this._extensions.entries()).map(([name, ext]) => ({
|
|
309
|
+
name,
|
|
310
|
+
description: ext.description,
|
|
311
|
+
version: ext.version,
|
|
312
|
+
tools: ext.tools,
|
|
313
|
+
}));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
reload(framework) {
|
|
317
|
+
this._framework = framework;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async uninstall(framework) {
|
|
321
|
+
this._extensions.clear();
|
|
322
|
+
this._framework = null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
module.exports = ExtensionExecutorPlugin
|
package/plugins/feishu-plugin.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { Plugin } = require('../src/core/plugin-base')
|
|
7
|
-
|
|
7
|
+
const { NodeHtmlMarkdown } = require('node-html-markdown')
|
|
8
8
|
class FileSystemPlugin extends Plugin {
|
|
9
9
|
constructor(config = {}) {
|
|
10
10
|
super()
|
|
@@ -743,10 +743,12 @@ class FileSystemPlugin extends Plugin {
|
|
|
743
743
|
headers: z.record(z.string()).optional().describe('请求头'),
|
|
744
744
|
body: z.string().optional().describe('请求体(POST/PUT/PATCH 时使用)'),
|
|
745
745
|
timeout: z.number().optional().describe('超时时间(ms),默认 30000'),
|
|
746
|
-
proxy: z.boolean().optional().describe('是否使用代理,默认 false。访问失败时可自动设为 true 重试')
|
|
746
|
+
proxy: z.boolean().optional().describe('是否使用代理,默认 false。访问失败时可自动设为 true 重试'),
|
|
747
|
+
toMarkdown: z.boolean().default(true).describe('是否将 HTML 响应转换为 Markdown 格式,默认 true'),
|
|
748
|
+
maxLength: z.number().optional().describe('最大返回长度(字符数),超过则截断')
|
|
747
749
|
}),
|
|
748
750
|
execute: async (args, framework) => {
|
|
749
|
-
const { url, method = 'GET', headers = {}, body, timeout = 30000, proxy = false } = args
|
|
751
|
+
const { url, method = 'GET', headers = {}, body, timeout = 30000, proxy = false, toMarkdown = true, maxLength } = args
|
|
750
752
|
try {
|
|
751
753
|
const controller = new AbortController()
|
|
752
754
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
@@ -769,13 +771,62 @@ class FileSystemPlugin extends Plugin {
|
|
|
769
771
|
data = text
|
|
770
772
|
}
|
|
771
773
|
|
|
774
|
+
// 如果 toMarkdown 为 true 且响应是 HTML,则转换为 Markdown
|
|
775
|
+
if (toMarkdown && typeof data === 'string') {
|
|
776
|
+
// 检测是否为 HTML 内容
|
|
777
|
+
const isHtml = /<[a-z][\s\S]*>/i.test(data) || data.trim().startsWith('<')
|
|
778
|
+
if (isHtml) {
|
|
779
|
+
try {
|
|
780
|
+
|
|
781
|
+
const markdown = NodeHtmlMarkdown.translate(data)
|
|
782
|
+
// 限制返回长度
|
|
783
|
+
const truncatedMarkdown = maxLength && markdown.length > maxLength
|
|
784
|
+
? markdown.substring(0, maxLength) + '\n\n...(truncated)'
|
|
785
|
+
: markdown
|
|
786
|
+
return {
|
|
787
|
+
success: true,
|
|
788
|
+
status: response.status,
|
|
789
|
+
statusText: response.statusText,
|
|
790
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
791
|
+
usedProxy: proxy,
|
|
792
|
+
markdown: truncatedMarkdown,
|
|
793
|
+
originalLength: markdown.length,
|
|
794
|
+
truncated: maxLength && markdown.length > maxLength
|
|
795
|
+
}
|
|
796
|
+
} catch (e) {
|
|
797
|
+
// 转换失败时返回原始 body
|
|
798
|
+
const truncatedBody = maxLength && data.length > maxLength
|
|
799
|
+
? data.substring(0, maxLength) + '\n\n...(truncated)'
|
|
800
|
+
: data
|
|
801
|
+
return {
|
|
802
|
+
success: true,
|
|
803
|
+
status: response.status,
|
|
804
|
+
statusText: response.statusText,
|
|
805
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
806
|
+
usedProxy: proxy,
|
|
807
|
+
markdown: null,
|
|
808
|
+
body: truncatedBody,
|
|
809
|
+
originalLength: data.length,
|
|
810
|
+
truncated: maxLength && data.length > maxLength,
|
|
811
|
+
markdownError: e.message
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// toMarkdown 为 false 或非 HTML 内容时,返回原始内容
|
|
818
|
+
const truncatedData = maxLength && typeof data === 'string' && data.length > maxLength
|
|
819
|
+
? data.substring(0, maxLength) + '\n\n...(truncated)'
|
|
820
|
+
: data
|
|
772
821
|
return {
|
|
773
822
|
success: true,
|
|
774
823
|
status: response.status,
|
|
775
824
|
statusText: response.statusText,
|
|
776
825
|
headers: Object.fromEntries(response.headers.entries()),
|
|
777
|
-
|
|
778
|
-
|
|
826
|
+
usedProxy: proxy,
|
|
827
|
+
body: truncatedData,
|
|
828
|
+
originalLength: typeof data === 'string' ? data.length : null,
|
|
829
|
+
truncated: maxLength && typeof data === 'string' && data.length > maxLength
|
|
779
830
|
}
|
|
780
831
|
} catch (error) {
|
|
781
832
|
return {
|
|
@@ -823,4 +874,4 @@ class FileSystemPlugin extends Plugin {
|
|
|
823
874
|
}
|
|
824
875
|
}
|
|
825
876
|
|
|
826
|
-
module.exports =
|
|
877
|
+
module.exports = FileSystemPlugin
|