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.
- 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 +54 -54
- package/cli/src/commands/chat.js +1 -1
- package/examples/basic.js +1 -1
- package/package.json +5 -3
- package/plugins/ai-plugin.js +1 -1
- 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 +85 -19
- package/src/core/plugin-base.js +43 -0
- package/src/executors/mcp-executor.js +126 -22
|
@@ -73,24 +73,9 @@ class PythonPluginLoader extends Plugin {
|
|
|
73
73
|
// 先设置 framework 引用(供 _loadPythonPlugins 使用)
|
|
74
74
|
this._framework = framework
|
|
75
75
|
|
|
76
|
-
// 加载所有 Python
|
|
76
|
+
// 加载所有 Python 插件(会注册工具到 ExtensionExecutor)
|
|
77
77
|
this._loadPythonPlugins()
|
|
78
78
|
|
|
79
|
-
// 注册 python_plugin 工具
|
|
80
|
-
framework.registerTool({
|
|
81
|
-
name: 'python_plugin',
|
|
82
|
-
description: '执行 Python 插件工具(当工具未注册时使用)',
|
|
83
|
-
inputSchema: z.object({
|
|
84
|
-
plugin: z.string().describe('插件名称(不含 .py)'),
|
|
85
|
-
tool: z.string().describe('工具名称'),
|
|
86
|
-
params: z.record(z.any()).describe('工具参数')
|
|
87
|
-
}),
|
|
88
|
-
execute: async (args) => {
|
|
89
|
-
const { plugin, tool, params } = args
|
|
90
|
-
return this._executePythonTool(plugin, tool, params)
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
|
|
94
79
|
// 监听 agent 创建事件,附加 Python 插件信息到系统提示词
|
|
95
80
|
framework.on('agent:created', (agent) => {
|
|
96
81
|
this._refreshAgentPythonPluginsPrompt(agent)
|
|
@@ -116,11 +101,11 @@ class PythonPluginLoader extends Plugin {
|
|
|
116
101
|
return ''
|
|
117
102
|
}
|
|
118
103
|
|
|
119
|
-
let desc = '【Python
|
|
120
|
-
desc += '
|
|
104
|
+
let desc = '【Python 插件】\n'
|
|
105
|
+
desc += 'Python 插件通过 ext_call 调用:\n\n'
|
|
121
106
|
|
|
122
107
|
for (const [name, plugin] of this._pythonPlugins) {
|
|
123
|
-
desc +=
|
|
108
|
+
desc += `插件: ${plugin.info.name} - ${plugin.info.description || ''}\n`
|
|
124
109
|
if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
|
|
125
110
|
for (const tool of plugin.info.tools) {
|
|
126
111
|
const paramsStr = Object.keys(tool.params || {}).join(', ') || '无参数'
|
|
@@ -130,7 +115,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
130
115
|
desc += '\n'
|
|
131
116
|
}
|
|
132
117
|
|
|
133
|
-
desc += '
|
|
118
|
+
desc += '调用格式: ext_call({ plugin: "插件名", tool: "工具名", args: {...} })\n'
|
|
134
119
|
return desc.trim()
|
|
135
120
|
}
|
|
136
121
|
|
|
@@ -139,7 +124,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
139
124
|
*/
|
|
140
125
|
_refreshAgentPythonPluginsPrompt(agent) {
|
|
141
126
|
const existingPrompt = agent._originalPrompt || ''
|
|
142
|
-
if (existingPrompt.includes('
|
|
127
|
+
if (existingPrompt.includes('【Python 插件】')) {
|
|
143
128
|
return
|
|
144
129
|
}
|
|
145
130
|
|
|
@@ -216,21 +201,21 @@ class PythonPluginLoader extends Plugin {
|
|
|
216
201
|
}
|
|
217
202
|
|
|
218
203
|
/**
|
|
219
|
-
* 注册 Python
|
|
204
|
+
* 注册 Python 插件的工具
|
|
220
205
|
*/
|
|
221
206
|
_registerPythonTool(pluginName, tool) {
|
|
222
207
|
if (!tool.name) return
|
|
223
208
|
|
|
224
209
|
try {
|
|
225
|
-
this.
|
|
210
|
+
this.registerTool({
|
|
226
211
|
name: tool.name,
|
|
227
|
-
description: tool.description ||
|
|
212
|
+
description: tool.description || '',
|
|
213
|
+
pluginName: pluginName,
|
|
228
214
|
inputSchema: this._parseToolParams(tool.params || {}),
|
|
229
215
|
execute: async (args) => {
|
|
230
216
|
return this._executePythonTool(pluginName, tool.name, args)
|
|
231
217
|
}
|
|
232
218
|
})
|
|
233
|
-
//log.info(` Registered tool: ${tool.name}`)
|
|
234
219
|
} catch (err) {
|
|
235
220
|
log.error(` Failed to register tool ${tool.name}:`, err.message)
|
|
236
221
|
}
|
|
@@ -270,22 +255,59 @@ class PythonPluginLoader extends Plugin {
|
|
|
270
255
|
|
|
271
256
|
/**
|
|
272
257
|
* 加载单个 Python 插件的元信息
|
|
258
|
+
* 支持 PLUGIN 和 TOOLS 格式
|
|
273
259
|
*/
|
|
274
260
|
_loadPythonPluginMeta(pluginPath) {
|
|
275
261
|
const code = fs.readFileSync(pluginPath, 'utf-8')
|
|
276
262
|
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
263
|
+
const pluginIdx = code.indexOf('PLUGIN')
|
|
264
|
+
const toolsIdx = code.indexOf('TOOLS')
|
|
265
|
+
|
|
266
|
+
if (pluginIdx === -1 && toolsIdx === -1) {
|
|
267
|
+
console.warn(`[PythonPluginLoader] ${path.basename(pluginPath)}: no PLUGIN or TOOLS found`)
|
|
268
|
+
return null
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const info = {}
|
|
273
|
+
|
|
274
|
+
// 解析 PLUGIN
|
|
275
|
+
if (pluginIdx !== -1) {
|
|
276
|
+
const pluginData = this._extractPythonDict(code, pluginIdx)
|
|
277
|
+
if (pluginData && pluginData.name) {
|
|
278
|
+
info.name = pluginData.name
|
|
279
|
+
info.version = pluginData.version || '1.0.0'
|
|
280
|
+
info.description = pluginData.description || ''
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 解析 TOOLS
|
|
285
|
+
if (toolsIdx !== -1) {
|
|
286
|
+
const toolsData = this._extractPythonList(code, toolsIdx)
|
|
287
|
+
if (toolsData && Array.isArray(toolsData)) {
|
|
288
|
+
info.tools = toolsData
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!info.name || !info.tools) {
|
|
293
|
+
console.warn(`[PythonPluginLoader] ${path.basename(pluginPath)}: invalid format - name: ${info.name}, tools: ${info.tools ? info.tools.length : 0}`)
|
|
294
|
+
return null
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return info
|
|
298
|
+
} catch (err) {
|
|
299
|
+
console.warn(`[PythonPluginLoader] Failed to parse ${path.basename(pluginPath)}:`, err.message)
|
|
281
300
|
return null
|
|
282
301
|
}
|
|
302
|
+
}
|
|
283
303
|
|
|
284
|
-
|
|
304
|
+
/**
|
|
305
|
+
* 从指定位置提取 Python 字典
|
|
306
|
+
*/
|
|
307
|
+
_extractPythonDict(code, startIdx) {
|
|
285
308
|
const braceStart = code.indexOf('{', startIdx)
|
|
286
309
|
if (braceStart === -1) return null
|
|
287
310
|
|
|
288
|
-
// 使用栈匹配找到对应的 }
|
|
289
311
|
let braceCount = 0
|
|
290
312
|
let endIdx = -1
|
|
291
313
|
for (let i = braceStart; i < code.length; i++) {
|
|
@@ -299,110 +321,234 @@ class PythonPluginLoader extends Plugin {
|
|
|
299
321
|
}
|
|
300
322
|
}
|
|
301
323
|
|
|
302
|
-
if (endIdx === -1)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
try {
|
|
308
|
-
// 提取并清理 plugin_info 内容
|
|
309
|
-
let infoStr = code.substring(braceStart, endIdx + 1)
|
|
310
|
-
|
|
311
|
-
// 移除 Python 注释
|
|
312
|
-
infoStr = infoStr.replace(/#.*$/gm, '')
|
|
324
|
+
if (endIdx === -1) return null
|
|
325
|
+
return this._parsePythonDict(code.substring(braceStart, endIdx + 1))
|
|
326
|
+
}
|
|
313
327
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
328
|
+
/**
|
|
329
|
+
* 从指定位置提取 Python 列表
|
|
330
|
+
*/
|
|
331
|
+
_extractPythonList(code, startIdx) {
|
|
332
|
+
const bracketStart = code.indexOf('[', startIdx)
|
|
333
|
+
if (bracketStart === -1) return null
|
|
320
334
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
335
|
+
let bracketCount = 0
|
|
336
|
+
let endIdx = -1
|
|
337
|
+
for (let i = bracketStart; i < code.length; i++) {
|
|
338
|
+
if (code[i] === '[') bracketCount++
|
|
339
|
+
else if (code[i] === ']') {
|
|
340
|
+
bracketCount--
|
|
341
|
+
if (bracketCount === 0) {
|
|
342
|
+
endIdx = i
|
|
343
|
+
break
|
|
344
|
+
}
|
|
345
|
+
}
|
|
326
346
|
}
|
|
347
|
+
|
|
348
|
+
if (endIdx === -1) return null
|
|
349
|
+
return this._parsePythonList(code.substring(bracketStart, endIdx + 1))
|
|
327
350
|
}
|
|
328
351
|
|
|
329
352
|
/**
|
|
330
|
-
*
|
|
353
|
+
* 解析 Python 字典为 JS 对象
|
|
331
354
|
*/
|
|
332
|
-
|
|
333
|
-
str = str.trim()
|
|
334
|
-
if (!str) return
|
|
355
|
+
_parsePythonDict(str) {
|
|
356
|
+
str = str.replace(/#.*$/gm, '').trim()
|
|
357
|
+
if (!str || str === '{}') return {}
|
|
335
358
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
return
|
|
359
|
+
// 直接尝试 JSON.parse(PLUGIN 字典是有效的 JSON 格式)
|
|
360
|
+
try {
|
|
361
|
+
return JSON.parse(str)
|
|
362
|
+
} catch {
|
|
363
|
+
// 如果失败,使用手动解析
|
|
339
364
|
}
|
|
340
365
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
366
|
+
const result = {}
|
|
367
|
+
let i = 0
|
|
368
|
+
let key = null
|
|
369
|
+
let valueStart = -1
|
|
370
|
+
let depth = 0
|
|
371
|
+
let inString = false
|
|
372
|
+
let stringChar = null
|
|
373
|
+
let escaped = false
|
|
345
374
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (str === 'False') return false
|
|
349
|
-
if (str === 'None') return null
|
|
375
|
+
while (i < str.length) {
|
|
376
|
+
const char = str[i]
|
|
350
377
|
|
|
351
|
-
|
|
352
|
-
|
|
378
|
+
if (escaped) {
|
|
379
|
+
escaped = false
|
|
380
|
+
i++
|
|
381
|
+
continue
|
|
382
|
+
}
|
|
353
383
|
|
|
354
|
-
|
|
384
|
+
if (char === '\\' && inString) {
|
|
385
|
+
escaped = true
|
|
386
|
+
i++
|
|
387
|
+
continue
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if ((char === '"' || char === "'") && !inString) {
|
|
391
|
+
inString = true
|
|
392
|
+
stringChar = char
|
|
393
|
+
i++
|
|
394
|
+
continue
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (inString && char === stringChar) {
|
|
398
|
+
inString = false
|
|
399
|
+
i++
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (inString) {
|
|
404
|
+
i++
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (char === '{' || char === '[') {
|
|
409
|
+
if (depth === 0 && char === '{') {
|
|
410
|
+
valueStart = i + 1
|
|
411
|
+
}
|
|
412
|
+
depth++
|
|
413
|
+
i++
|
|
414
|
+
continue
|
|
415
|
+
}
|
|
416
|
+
if (char === '}' || char === ']') {
|
|
417
|
+
if (depth > 0) {
|
|
418
|
+
depth--
|
|
419
|
+
if (depth === 0 && char === '}') {
|
|
420
|
+
if (key !== null && valueStart !== -1) {
|
|
421
|
+
const valueStr = str.substring(valueStart, i).trim()
|
|
422
|
+
if (valueStr) result[key] = this._parseValue(valueStr)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
i++
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
if (depth > 0) {
|
|
430
|
+
i++
|
|
431
|
+
continue
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (char === ':') {
|
|
435
|
+
key = str.substring(valueStart, i).trim().replace(/["']/g, '')
|
|
436
|
+
valueStart = i + 1
|
|
437
|
+
i++
|
|
438
|
+
continue
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (char === ',') {
|
|
442
|
+
if (key !== null && valueStart !== -1) {
|
|
443
|
+
const valueStr = str.substring(valueStart, i).trim()
|
|
444
|
+
if (valueStr) result[key] = this._parseValue(valueStr)
|
|
445
|
+
}
|
|
446
|
+
key = null
|
|
447
|
+
valueStart = i + 1
|
|
448
|
+
i++
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
i++
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return result
|
|
355
456
|
}
|
|
356
457
|
|
|
357
458
|
/**
|
|
358
|
-
*
|
|
459
|
+
* 解析 Python 列表为 JS 数组
|
|
359
460
|
*/
|
|
360
|
-
|
|
361
|
-
// 移除注释并清理
|
|
461
|
+
_parsePythonList(str) {
|
|
362
462
|
str = str.replace(/#.*$/gm, '').trim()
|
|
363
|
-
if (!str) return
|
|
463
|
+
if (!str || str === '[]') return []
|
|
364
464
|
|
|
365
|
-
|
|
465
|
+
// 直接尝试 JSON.parse(TOOLS 列表是有效的 JSON 格式)
|
|
466
|
+
try {
|
|
467
|
+
return JSON.parse(str)
|
|
468
|
+
} catch {
|
|
469
|
+
// 如果失败,使用手动解析
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const result = []
|
|
366
473
|
let i = 0
|
|
367
|
-
let
|
|
368
|
-
let
|
|
474
|
+
let itemStart = 1
|
|
475
|
+
let depth = 0
|
|
369
476
|
let inString = false
|
|
370
477
|
let stringChar = null
|
|
478
|
+
let escaped = false
|
|
371
479
|
|
|
372
480
|
while (i < str.length) {
|
|
373
481
|
const char = str[i]
|
|
374
482
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
483
|
+
if (escaped) {
|
|
484
|
+
escaped = false
|
|
485
|
+
i++
|
|
486
|
+
continue
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (char === '\\' && inString) {
|
|
490
|
+
escaped = true
|
|
491
|
+
i++
|
|
492
|
+
continue
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if ((char === '"' || char === "'") && !inString) {
|
|
496
|
+
inString = true
|
|
497
|
+
stringChar = char
|
|
498
|
+
i++
|
|
499
|
+
continue
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (inString && char === stringChar) {
|
|
503
|
+
inString = false
|
|
504
|
+
i++
|
|
505
|
+
continue
|
|
384
506
|
}
|
|
385
507
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
508
|
+
if (inString) {
|
|
509
|
+
i++
|
|
510
|
+
continue
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (char === '{' || char === '[') {
|
|
514
|
+
if (depth === 0) itemStart = i
|
|
515
|
+
depth++
|
|
516
|
+
i++
|
|
517
|
+
continue
|
|
518
|
+
}
|
|
519
|
+
if (char === '}' || char === ']') {
|
|
520
|
+
if (depth > 0) {
|
|
521
|
+
depth--
|
|
522
|
+
if (depth === 0) {
|
|
523
|
+
const itemStr = str.substring(itemStart, i + 1).trim()
|
|
524
|
+
if (itemStr && itemStr !== ',') {
|
|
525
|
+
if (itemStr.startsWith('{')) {
|
|
526
|
+
result.push(this._parsePythonDict(itemStr))
|
|
527
|
+
} else {
|
|
528
|
+
result.push(this._parseValue(itemStr))
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
itemStart = i + 1
|
|
532
|
+
}
|
|
392
533
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
534
|
+
i++
|
|
535
|
+
continue
|
|
536
|
+
}
|
|
537
|
+
if (depth > 0) {
|
|
538
|
+
i++
|
|
539
|
+
continue
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (char === ',') {
|
|
543
|
+
const itemStr = str.substring(itemStart, i).trim()
|
|
544
|
+
if (itemStr) {
|
|
545
|
+
if (itemStr.startsWith('{')) {
|
|
546
|
+
result.push(this._parsePythonDict(itemStr))
|
|
547
|
+
} else {
|
|
548
|
+
result.push(this._parseValue(itemStr))
|
|
398
549
|
}
|
|
399
|
-
currentKey = null
|
|
400
|
-
currentValue = ''
|
|
401
|
-
} else {
|
|
402
|
-
currentValue += char
|
|
403
550
|
}
|
|
404
|
-
|
|
405
|
-
currentValue += char
|
|
551
|
+
itemStart = i + 1
|
|
406
552
|
}
|
|
407
553
|
|
|
408
554
|
i++
|
|
@@ -411,6 +557,30 @@ class PythonPluginLoader extends Plugin {
|
|
|
411
557
|
return result
|
|
412
558
|
}
|
|
413
559
|
|
|
560
|
+
/**
|
|
561
|
+
* 解析单个值
|
|
562
|
+
*/
|
|
563
|
+
_parseValue(str) {
|
|
564
|
+
str = str.trim()
|
|
565
|
+
if (!str) return null
|
|
566
|
+
|
|
567
|
+
if (str.startsWith('{')) {
|
|
568
|
+
return this._parsePythonDict(str)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (str.startsWith('"') || str.startsWith("'")) {
|
|
572
|
+
return str.slice(1, -1)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (str === 'True') return true
|
|
576
|
+
if (str === 'False') return false
|
|
577
|
+
if (str === 'None') return null
|
|
578
|
+
|
|
579
|
+
if (!isNaN(str)) return Number(str)
|
|
580
|
+
|
|
581
|
+
return str
|
|
582
|
+
}
|
|
583
|
+
|
|
414
584
|
/**
|
|
415
585
|
* 执行 Python 插件工具
|
|
416
586
|
*/
|
|
@@ -478,4 +648,4 @@ print(result)
|
|
|
478
648
|
}
|
|
479
649
|
}
|
|
480
650
|
|
|
481
|
-
module.exports =
|
|
651
|
+
module.exports = PythonPluginLoader
|
package/plugins/rules-plugin.js
CHANGED