foliko 1.0.77 → 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.
Files changed (36) hide show
  1. package/.agent/data/default.json +31559 -0
  2. package/.agent/data/plugins-state.json +10 -1
  3. package/.claude/settings.local.json +13 -2
  4. package/.env.example +54 -54
  5. package/cli/src/commands/chat.js +1 -1
  6. package/examples/basic.js +1 -1
  7. package/package.json +5 -3
  8. package/plugins/ai-plugin.js +1 -1
  9. package/plugins/ambient-agent/index.js +1 -1
  10. package/plugins/audit-plugin.js +1 -1
  11. package/plugins/default-plugins.js +92 -209
  12. package/plugins/email/index.js +1 -1
  13. package/plugins/extension-executor-plugin.js +326 -0
  14. package/plugins/feishu-plugin.js +1 -1
  15. package/plugins/file-system-plugin.js +57 -6
  16. package/plugins/gate-trading.js +747 -0
  17. package/plugins/install-plugin.js +1 -1
  18. package/plugins/python-executor-plugin.js +1 -1
  19. package/plugins/python-plugin-loader.js +275 -105
  20. package/plugins/rules-plugin.js +1 -1
  21. package/plugins/scheduler-plugin.js +1 -1
  22. package/plugins/session-plugin.js +132 -7
  23. package/plugins/shell-executor-plugin.js +1 -1
  24. package/plugins/storage-plugin.js +24 -1
  25. package/plugins/subagent-plugin.js +2 -2
  26. package/plugins/telegram-plugin.js +1 -1
  27. package/plugins/think-plugin.js +10 -10
  28. package/plugins/tools-plugin.js +1 -1
  29. package/plugins/web-plugin.js +49 -18
  30. package/plugins/weixin-plugin.js +1 -1
  31. package/skills/foliko-dev/SKILL.md +583 -500
  32. package/skills/python-plugin-dev/SKILL.md +238 -266
  33. package/src/core/agent-chat.js +103 -4
  34. package/src/core/agent.js +85 -19
  35. package/src/core/plugin-base.js +43 -0
  36. 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
@@ -478,4 +478,4 @@ class FeishuPlugin extends Plugin {
478
478
  }
479
479
  }
480
480
 
481
- module.exports = { FeishuPlugin }
481
+ module.exports = FeishuPlugin
@@ -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
- body: data,
778
- usedProxy: proxy
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 = { FileSystemPlugin }
877
+ module.exports = FileSystemPlugin