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.
@@ -196,4 +196,4 @@ class InstallPlugin extends Plugin {
196
196
  }
197
197
  }
198
198
 
199
- module.exports = { InstallPlugin }
199
+ module.exports = InstallPlugin
@@ -365,4 +365,4 @@ except Exception:
365
365
  }
366
366
  }
367
367
 
368
- module.exports = { PythonExecutorPlugin }
368
+ module.exports = PythonExecutorPlugin
@@ -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 插件工具】\n'
120
- desc += '可以直接调用以下 Python 插件工具:\n\n'
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 += `[${plugin.info.name}] ${plugin.info.description || ''}\n`
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 += '调用格式:python_plugin({ plugin: "插件名", tool: "工具名", params: {...} })\n'
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('[Python 插件工具]')) {
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._framework.registerTool({
210
+ this.registerTool({
226
211
  name: tool.name,
227
- description: tool.description || `${tool.name} (from ${pluginName})`,
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
- // 找到 plugin_info = {
278
- const startIdx = code.indexOf('plugin_info')
279
- if (startIdx === -1) {
280
- log.warn(` ${path.basename(pluginPath)}: no plugin_info found`)
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
- // 从 plugin_info 后开始,找到第一个 { 的位置
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
- log.warn(` ${path.basename(pluginPath)}: unclosed brace`)
304
- return null
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
- // 转换为 JS 兼容的格式
315
- infoStr = infoStr
316
- .replace(/True/g, 'true')
317
- .replace(/False/g, 'false')
318
- .replace(/None/g, 'null')
319
- .replace(/'/g, '"')
328
+ /**
329
+ * 从指定位置提取 Python 列表
330
+ */
331
+ _extractPythonList(code, startIdx) {
332
+ const bracketStart = code.indexOf('[', startIdx)
333
+ if (bracketStart === -1) return null
320
334
 
321
- const info = JSON.parse(infoStr)
322
- return info
323
- } catch (err) {
324
- log.warn(` Failed to parse ${path.basename(pluginPath)}:`, err.message)
325
- return null
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
- _parseValue(str) {
333
- str = str.trim()
334
- if (!str) return null
355
+ _parsePythonDict(str) {
356
+ str = str.replace(/#.*$/gm, '').trim()
357
+ if (!str || str === '{}') return {}
335
358
 
336
- // 字典
337
- if (str.startsWith('{')) {
338
- return this._parseDict(str)
359
+ // 直接尝试 JSON.parse(PLUGIN 字典是有效的 JSON 格式)
360
+ try {
361
+ return JSON.parse(str)
362
+ } catch {
363
+ // 如果失败,使用手动解析
339
364
  }
340
365
 
341
- // 字符串
342
- if (str.startsWith('"') || str.startsWith("'")) {
343
- return str.slice(1, -1)
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
- if (str === 'True') return true
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
- if (!isNaN(str)) return Number(str)
378
+ if (escaped) {
379
+ escaped = false
380
+ i++
381
+ continue
382
+ }
353
383
 
354
- return str
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
- * 简单解析 Python 字典为 JS 对象
459
+ * 解析 Python 列表为 JS 数组
359
460
  */
360
- _parseDict(str) {
361
- // 移除注释并清理
461
+ _parsePythonList(str) {
362
462
  str = str.replace(/#.*$/gm, '').trim()
363
- if (!str) return {}
463
+ if (!str || str === '[]') return []
364
464
 
365
- const result = {}
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 currentKey = null
368
- let currentValue = ''
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
- if ((char === '"' || char === "'") && str[i-1] !== '\\') {
377
- if (!inString) {
378
- inString = true
379
- stringChar = char
380
- } else if (char === stringChar) {
381
- inString = false
382
- stringChar = null
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
- if (!inString) {
388
- // 找到键
389
- if (currentKey === null && char === ':') {
390
- currentKey = currentValue.trim().replace(/^["']|["']$/g, '')
391
- currentValue = ''
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
- else if (char === ',' || char === '}') {
395
- if (currentKey !== null) {
396
- const value = currentValue.trim().replace(/,$/, '')
397
- result[currentKey] = this._parseValue(value)
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
- } else {
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 = { PythonPluginLoader }
651
+ module.exports = PythonPluginLoader
@@ -292,4 +292,4 @@ class RulesPlugin extends Plugin {
292
292
  }
293
293
  }
294
294
 
295
- module.exports = { RulesPlugin }
295
+ module.exports = RulesPlugin
@@ -688,4 +688,4 @@ class SchedulerPlugin extends Plugin {
688
688
  }
689
689
  }
690
690
 
691
- module.exports = { SchedulerPlugin }
691
+ module.exports = SchedulerPlugin