foliko 1.0.81 → 1.0.82

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 (87) hide show
  1. package/cli/bin/foliko.js +12 -12
  2. package/cli/src/commands/chat.js +143 -143
  3. package/cli/src/commands/list.js +93 -93
  4. package/cli/src/index.js +75 -75
  5. package/cli/src/ui/chat-ui.js +201 -201
  6. package/cli/src/utils/ansi.js +40 -40
  7. package/cli/src/utils/markdown.js +292 -292
  8. package/examples/ambient-example.js +194 -194
  9. package/examples/basic.js +115 -115
  10. package/examples/bootstrap.js +121 -121
  11. package/examples/mcp-example.js +56 -56
  12. package/examples/skill-example.js +49 -49
  13. package/examples/test-chat.js +137 -137
  14. package/examples/test-mcp.js +85 -85
  15. package/examples/test-reload.js +59 -59
  16. package/examples/test-telegram.js +50 -50
  17. package/examples/test-tg-bot.js +45 -45
  18. package/examples/test-tg-simple.js +47 -47
  19. package/examples/test-tg.js +62 -62
  20. package/examples/test-think.js +43 -43
  21. package/examples/test-web-plugin.js +103 -103
  22. package/examples/test-weixin-feishu.js +103 -103
  23. package/examples/workflow.js +158 -158
  24. package/package.json +83 -83
  25. package/plugins/ai-plugin.js +102 -102
  26. package/plugins/ambient-agent/EventWatcher.js +113 -113
  27. package/plugins/ambient-agent/ExplorerLoop.js +640 -640
  28. package/plugins/ambient-agent/GoalManager.js +197 -197
  29. package/plugins/ambient-agent/Reflector.js +95 -95
  30. package/plugins/ambient-agent/StateStore.js +90 -90
  31. package/plugins/ambient-agent/constants.js +101 -101
  32. package/plugins/ambient-agent/index.js +579 -579
  33. package/plugins/audit-plugin.js +187 -187
  34. package/plugins/default-plugins.js +548 -548
  35. package/plugins/email/constants.js +64 -64
  36. package/plugins/email/handlers.js +461 -461
  37. package/plugins/email/index.js +278 -278
  38. package/plugins/email/monitor.js +269 -269
  39. package/plugins/email/parser.js +138 -138
  40. package/plugins/email/reply.js +151 -151
  41. package/plugins/email/utils.js +124 -124
  42. package/plugins/extension-executor-plugin.js +326 -326
  43. package/plugins/feishu-plugin.js +481 -481
  44. package/plugins/file-system-plugin.js +920 -920
  45. package/plugins/gate-trading.js +747 -747
  46. package/plugins/install-plugin.js +199 -199
  47. package/plugins/python-executor-plugin.js +367 -367
  48. package/plugins/python-plugin-loader.js +651 -651
  49. package/plugins/rules-plugin.js +294 -294
  50. package/plugins/scheduler-plugin.js +691 -691
  51. package/plugins/session-plugin.js +494 -494
  52. package/plugins/shell-executor-plugin.js +197 -197
  53. package/plugins/storage-plugin.js +263 -263
  54. package/plugins/subagent-plugin.js +845 -845
  55. package/plugins/telegram-plugin.js +482 -482
  56. package/plugins/think-plugin.js +345 -345
  57. package/plugins/tools-plugin.js +196 -196
  58. package/plugins/web-plugin.js +637 -637
  59. package/plugins/weixin-plugin.js +545 -545
  60. package/src/capabilities/index.js +11 -11
  61. package/src/capabilities/skill-manager.js +609 -609
  62. package/src/capabilities/workflow-engine.js +1109 -1109
  63. package/src/core/agent.js +958 -958
  64. package/src/core/framework.js +465 -465
  65. package/src/core/index.js +19 -19
  66. package/src/core/plugin-base.js +262 -262
  67. package/src/core/plugin-manager.js +863 -863
  68. package/src/core/provider.js +114 -114
  69. package/src/core/sub-agent-config.js +264 -264
  70. package/src/core/system-prompt-builder.js +120 -120
  71. package/src/core/tool-registry.js +517 -517
  72. package/src/core/tool-router.js +297 -297
  73. package/src/executors/executor-base.js +58 -58
  74. package/src/executors/mcp-executor.js +845 -845
  75. package/src/index.js +25 -25
  76. package/src/utils/circuit-breaker.js +301 -301
  77. package/src/utils/error-boundary.js +363 -363
  78. package/src/utils/error.js +374 -374
  79. package/src/utils/event-emitter.js +97 -97
  80. package/src/utils/id.js +133 -133
  81. package/src/utils/index.js +217 -217
  82. package/src/utils/logger.js +181 -181
  83. package/src/utils/plugin-helpers.js +90 -90
  84. package/src/utils/retry.js +122 -122
  85. package/src/utils/sandbox.js +292 -292
  86. package/test/tool-registry-validation.test.js +218 -218
  87. package/website/script.js +136 -136
@@ -1,651 +1,651 @@
1
- /**
2
- * PythonPluginLoader - Python 插件加载器
3
- * 通过 python_execute 工具执行 Python 插件
4
- */
5
-
6
- const fs = require('fs')
7
- const path = require('path')
8
- const { Plugin } = require('../src/core/plugin-base')
9
- const { logger } = require('../src/utils/logger')
10
- const log = logger.child('PythonPluginLoader')
11
- const { z } = require('zod')
12
-
13
- // 将 JSON Schema 转换为 Zod Schema
14
- function jsonSchemaToZod(jsonSchema) {
15
- if (!jsonSchema || jsonSchema.type !== 'object') {
16
- return z.object({})
17
- }
18
-
19
- const properties = jsonSchema.properties || {}
20
- const required = jsonSchema.required || []
21
-
22
- const shape = {}
23
- for (const [key, prop] of Object.entries(properties)) {
24
- let zodType
25
- switch (prop.type) {
26
- case 'string':
27
- zodType = z.string()
28
- break
29
- case 'number':
30
- zodType = z.number()
31
- break
32
- case 'boolean':
33
- zodType = z.boolean()
34
- break
35
- case 'array':
36
- zodType = z.array(z.any())
37
- break
38
- case 'object':
39
- zodType = jsonSchemaToZod(prop)
40
- break
41
- default:
42
- zodType = z.any()
43
- }
44
- // 如果不是 required 字段,则标记为 optional
45
- if (!required.includes(key)) {
46
- zodType = zodType.optional()
47
- }
48
- shape[key] = zodType
49
- }
50
-
51
- return z.object(shape)
52
- }
53
-
54
- class PythonPluginLoader extends Plugin {
55
- constructor(config = {}) {
56
- super()
57
- this.name = 'python-plugin-loader'
58
- this.version = '1.0.0'
59
- this.description = 'Python 插件加载器,属于Python的插件'
60
-
61
- this._agentDir = config.agentDir || '.agent'
62
- this._pythonPlugins = new Map()
63
- this._framework = null
64
- this.system = true
65
- }
66
-
67
- install(framework) {
68
- this._framework = framework
69
- return this
70
- }
71
-
72
- start(framework) {
73
- // 先设置 framework 引用(供 _loadPythonPlugins 使用)
74
- this._framework = framework
75
-
76
- // 加载所有 Python 插件(会注册工具到 ExtensionExecutor)
77
- this._loadPythonPlugins()
78
-
79
- // 监听 agent 创建事件,附加 Python 插件信息到系统提示词
80
- framework.on('agent:created', (agent) => {
81
- this._refreshAgentPythonPluginsPrompt(agent)
82
- })
83
-
84
- // 等待框架就绪后,刷新所有已有 agent 的 Python 插件提示词
85
- if (framework._ready) {
86
- this._refreshAllAgentsPythonPluginsPrompt(framework)
87
- } else {
88
- framework.once('framework:ready', () => {
89
- this._refreshAllAgentsPythonPluginsPrompt(framework)
90
- })
91
- }
92
-
93
- return this
94
- }
95
-
96
- /**
97
- * 构建 Python 插件描述
98
- */
99
- _buildPythonPluginsDescription() {
100
- if (this._pythonPlugins.size === 0) {
101
- return ''
102
- }
103
-
104
- let desc = '【Python 插件】\n'
105
- desc += 'Python 插件通过 ext_call 调用:\n\n'
106
-
107
- for (const [name, plugin] of this._pythonPlugins) {
108
- desc += `插件: ${plugin.info.name} - ${plugin.info.description || ''}\n`
109
- if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
110
- for (const tool of plugin.info.tools) {
111
- const paramsStr = Object.keys(tool.params || {}).join(', ') || '无参数'
112
- desc += ` - ${tool.name}(${paramsStr}): ${tool.description || '无描述'}\n`
113
- }
114
- }
115
- desc += '\n'
116
- }
117
-
118
- desc += '调用格式: ext_call({ plugin: "插件名", tool: "工具名", args: {...} })\n'
119
- return desc.trim()
120
- }
121
-
122
- /**
123
- * 刷新单个 agent 的 Python 插件提示词
124
- */
125
- _refreshAgentPythonPluginsPrompt(agent) {
126
- const existingPrompt = agent._originalPrompt || ''
127
- if (existingPrompt.includes('【Python 插件】')) {
128
- return
129
- }
130
-
131
- const pyDesc = this._buildPythonPluginsDescription()
132
- if (!pyDesc) return
133
-
134
- // 将 Python 插件描述追加到系统提示词
135
- agent.setSystemPrompt(existingPrompt + '\n\n' + pyDesc)
136
- }
137
-
138
- /**
139
- * 刷新所有 agent 的 Python 插件提示词
140
- */
141
- _refreshAllAgentsPythonPluginsPrompt(framework) {
142
- const visited = new Set()
143
-
144
- const traverse = (agent) => {
145
- if (!agent || visited.has(agent)) return
146
- visited.add(agent)
147
- this._refreshAgentPythonPluginsPrompt(agent)
148
-
149
- // 递归处理子 agent
150
- const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map()
151
- for (const [name, subAgentInfo] of subAgents) {
152
- traverse(subAgentInfo.agent)
153
- }
154
- }
155
-
156
- const agents = framework._agents || []
157
- for (const agent of agents) {
158
- traverse(agent)
159
- }
160
- }
161
-
162
- /**
163
- * 加载所有 Python 插件
164
- */
165
- _loadPythonPlugins() {
166
- const pluginsDir = path.join(this._agentDir, 'plugins')
167
- if (!fs.existsSync(pluginsDir)) {
168
- return
169
- }
170
-
171
- const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.py'))
172
-
173
- for (const file of files) {
174
- try {
175
- const pluginPath = path.join(pluginsDir, file)
176
- const pluginName = file.replace('.py', '')
177
-
178
- const pluginInfo = this._loadPythonPluginMeta(pluginPath)
179
- if (pluginInfo) {
180
- this._pythonPlugins.set(pluginName, {
181
- name: pluginName,
182
- path: pluginPath,
183
- info: pluginInfo,
184
- code: fs.readFileSync(pluginPath, 'utf-8')
185
- })
186
- log.info(` Loaded: ${pluginName}`)
187
-
188
- // 注册插件的每个工具
189
- if (pluginInfo.tools && Array.isArray(pluginInfo.tools)) {
190
- for (const tool of pluginInfo.tools) {
191
- this._registerPythonTool(pluginName, tool)
192
- }
193
- }
194
- }
195
- } catch (err) {
196
- log.error(` Failed to load ${file}:`, err.message)
197
- }
198
- }
199
-
200
- log.info(` Total Python plugins: ${this._pythonPlugins.size}`)
201
- }
202
-
203
- /**
204
- * 注册 Python 插件的工具
205
- */
206
- _registerPythonTool(pluginName, tool) {
207
- if (!tool.name) return
208
-
209
- try {
210
- this.registerTool({
211
- name: tool.name,
212
- description: tool.description || '',
213
- pluginName: pluginName,
214
- inputSchema: this._parseToolParams(tool.params || {}),
215
- execute: async (args) => {
216
- return this._executePythonTool(pluginName, tool.name, args)
217
- }
218
- })
219
- } catch (err) {
220
- log.error(` Failed to register tool ${tool.name}:`, err.message)
221
- }
222
- }
223
-
224
- /**
225
- * 解析工具参数 schema
226
- */
227
- _parseToolParams(params) {
228
- // 构建 JSON Schema 格式
229
- const properties = {}
230
- const required = []
231
-
232
- for (const [key, value] of Object.entries(params)) {
233
- let type = 'string'
234
- if (typeof value === 'boolean') type = 'boolean'
235
- else if (typeof value === 'number') type = 'number'
236
- else if (Array.isArray(value)) type = 'array'
237
- else if (typeof value === 'object') type = 'object'
238
-
239
- properties[key] = {
240
- type,
241
- description: ''
242
- }
243
- required.push(key)
244
- }
245
-
246
- const jsonSchema = {
247
- type: 'object',
248
- properties,
249
- required
250
- }
251
-
252
- // 转换为 Zod Schema 以兼容 AI SDK
253
- return jsonSchemaToZod(jsonSchema)
254
- }
255
-
256
- /**
257
- * 加载单个 Python 插件的元信息
258
- * 支持 PLUGIN 和 TOOLS 格式
259
- */
260
- _loadPythonPluginMeta(pluginPath) {
261
- const code = fs.readFileSync(pluginPath, 'utf-8')
262
-
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)
300
- return null
301
- }
302
- }
303
-
304
- /**
305
- * 从指定位置提取 Python 字典
306
- */
307
- _extractPythonDict(code, startIdx) {
308
- const braceStart = code.indexOf('{', startIdx)
309
- if (braceStart === -1) return null
310
-
311
- let braceCount = 0
312
- let endIdx = -1
313
- for (let i = braceStart; i < code.length; i++) {
314
- if (code[i] === '{') braceCount++
315
- else if (code[i] === '}') {
316
- braceCount--
317
- if (braceCount === 0) {
318
- endIdx = i
319
- break
320
- }
321
- }
322
- }
323
-
324
- if (endIdx === -1) return null
325
- return this._parsePythonDict(code.substring(braceStart, endIdx + 1))
326
- }
327
-
328
- /**
329
- * 从指定位置提取 Python 列表
330
- */
331
- _extractPythonList(code, startIdx) {
332
- const bracketStart = code.indexOf('[', startIdx)
333
- if (bracketStart === -1) return null
334
-
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
- }
346
- }
347
-
348
- if (endIdx === -1) return null
349
- return this._parsePythonList(code.substring(bracketStart, endIdx + 1))
350
- }
351
-
352
- /**
353
- * 解析 Python 字典为 JS 对象
354
- */
355
- _parsePythonDict(str) {
356
- str = str.replace(/#.*$/gm, '').trim()
357
- if (!str || str === '{}') return {}
358
-
359
- // 直接尝试 JSON.parse(PLUGIN 字典是有效的 JSON 格式)
360
- try {
361
- return JSON.parse(str)
362
- } catch {
363
- // 如果失败,使用手动解析
364
- }
365
-
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
374
-
375
- while (i < str.length) {
376
- const char = str[i]
377
-
378
- if (escaped) {
379
- escaped = false
380
- i++
381
- continue
382
- }
383
-
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
456
- }
457
-
458
- /**
459
- * 解析 Python 列表为 JS 数组
460
- */
461
- _parsePythonList(str) {
462
- str = str.replace(/#.*$/gm, '').trim()
463
- if (!str || str === '[]') return []
464
-
465
- // 直接尝试 JSON.parse(TOOLS 列表是有效的 JSON 格式)
466
- try {
467
- return JSON.parse(str)
468
- } catch {
469
- // 如果失败,使用手动解析
470
- }
471
-
472
- const result = []
473
- let i = 0
474
- let itemStart = 1
475
- let depth = 0
476
- let inString = false
477
- let stringChar = null
478
- let escaped = false
479
-
480
- while (i < str.length) {
481
- const char = str[i]
482
-
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
506
- }
507
-
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
- }
533
- }
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))
549
- }
550
- }
551
- itemStart = i + 1
552
- }
553
-
554
- i++
555
- }
556
-
557
- return result
558
- }
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
-
584
- /**
585
- * 执行 Python 插件工具
586
- */
587
- async _executePythonTool(pluginName, toolName, params) {
588
- const plugin = this._pythonPlugins.get(pluginName)
589
- if (!plugin) {
590
- return { success: false, error: `Plugin '${pluginName}' not found` }
591
- }
592
-
593
- // 构建执行代码 - 使用 base64 避免转义问题
594
- const pluginDir = path.dirname(plugin.path)
595
- const codeBase64 = Buffer.from(plugin.code, 'utf-8').toString('base64')
596
-
597
- const execCode = `
598
- import sys
599
- import base64
600
- sys.path.insert(0, r'${pluginDir}')
601
-
602
- # 加载插件代码
603
- plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')
604
- exec(compile(plugin_code, '${pluginName}.py', 'exec'))
605
-
606
- # 执行工具
607
- result = execute_tool('${toolName}', ${JSON.stringify(params)})
608
- print(result)
609
- `
610
-
611
- try {
612
- // 通过 python-execute 工具执行
613
- const pythonExe = this._framework.toolRegistry._tools.get('python-execute')
614
- if (!pythonExe) {
615
- return { success: false, error: 'python-execute tool not found. Please install python-executor-plugin.' }
616
- }
617
-
618
- const pythonResult = await pythonExe.execute({ code: execCode })
619
-
620
- if (pythonResult.success) {
621
- try {
622
- // 尝试解析 JSON 结果
623
- const parsed = JSON.parse(pythonResult.output)
624
- return parsed
625
- } catch {
626
- return { success: true, output: pythonResult.output }
627
- }
628
- } else {
629
- return { success: false, error: pythonResult.error }
630
- }
631
- } catch (err) {
632
- return { success: false, error: err.message }
633
- }
634
- }
635
-
636
- /**
637
- * 获取所有已加载的 Python 插件
638
- */
639
- getPythonPlugins() {
640
- return Array.from(this._pythonPlugins.values())
641
- }
642
-
643
- /**
644
- * 获取 Python 插件信息
645
- */
646
- getPythonPlugin(name) {
647
- return this._pythonPlugins.get(name)
648
- }
649
- }
650
-
651
- module.exports = PythonPluginLoader
1
+ /**
2
+ * PythonPluginLoader - Python 插件加载器
3
+ * 通过 python_execute 工具执行 Python 插件
4
+ */
5
+
6
+ const fs = require('fs')
7
+ const path = require('path')
8
+ const { Plugin } = require('../src/core/plugin-base')
9
+ const { logger } = require('../src/utils/logger')
10
+ const log = logger.child('PythonPluginLoader')
11
+ const { z } = require('zod')
12
+
13
+ // 将 JSON Schema 转换为 Zod Schema
14
+ function jsonSchemaToZod(jsonSchema) {
15
+ if (!jsonSchema || jsonSchema.type !== 'object') {
16
+ return z.object({})
17
+ }
18
+
19
+ const properties = jsonSchema.properties || {}
20
+ const required = jsonSchema.required || []
21
+
22
+ const shape = {}
23
+ for (const [key, prop] of Object.entries(properties)) {
24
+ let zodType
25
+ switch (prop.type) {
26
+ case 'string':
27
+ zodType = z.string()
28
+ break
29
+ case 'number':
30
+ zodType = z.number()
31
+ break
32
+ case 'boolean':
33
+ zodType = z.boolean()
34
+ break
35
+ case 'array':
36
+ zodType = z.array(z.any())
37
+ break
38
+ case 'object':
39
+ zodType = jsonSchemaToZod(prop)
40
+ break
41
+ default:
42
+ zodType = z.any()
43
+ }
44
+ // 如果不是 required 字段,则标记为 optional
45
+ if (!required.includes(key)) {
46
+ zodType = zodType.optional()
47
+ }
48
+ shape[key] = zodType
49
+ }
50
+
51
+ return z.object(shape)
52
+ }
53
+
54
+ class PythonPluginLoader extends Plugin {
55
+ constructor(config = {}) {
56
+ super()
57
+ this.name = 'python-plugin-loader'
58
+ this.version = '1.0.0'
59
+ this.description = 'Python 插件加载器,属于Python的插件'
60
+
61
+ this._agentDir = config.agentDir || '.agent'
62
+ this._pythonPlugins = new Map()
63
+ this._framework = null
64
+ this.system = true
65
+ }
66
+
67
+ install(framework) {
68
+ this._framework = framework
69
+ return this
70
+ }
71
+
72
+ start(framework) {
73
+ // 先设置 framework 引用(供 _loadPythonPlugins 使用)
74
+ this._framework = framework
75
+
76
+ // 加载所有 Python 插件(会注册工具到 ExtensionExecutor)
77
+ this._loadPythonPlugins()
78
+
79
+ // 监听 agent 创建事件,附加 Python 插件信息到系统提示词
80
+ framework.on('agent:created', (agent) => {
81
+ this._refreshAgentPythonPluginsPrompt(agent)
82
+ })
83
+
84
+ // 等待框架就绪后,刷新所有已有 agent 的 Python 插件提示词
85
+ if (framework._ready) {
86
+ this._refreshAllAgentsPythonPluginsPrompt(framework)
87
+ } else {
88
+ framework.once('framework:ready', () => {
89
+ this._refreshAllAgentsPythonPluginsPrompt(framework)
90
+ })
91
+ }
92
+
93
+ return this
94
+ }
95
+
96
+ /**
97
+ * 构建 Python 插件描述
98
+ */
99
+ _buildPythonPluginsDescription() {
100
+ if (this._pythonPlugins.size === 0) {
101
+ return ''
102
+ }
103
+
104
+ let desc = '【Python 插件】\n'
105
+ desc += 'Python 插件通过 ext_call 调用:\n\n'
106
+
107
+ for (const [name, plugin] of this._pythonPlugins) {
108
+ desc += `插件: ${plugin.info.name} - ${plugin.info.description || ''}\n`
109
+ if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
110
+ for (const tool of plugin.info.tools) {
111
+ const paramsStr = Object.keys(tool.params || {}).join(', ') || '无参数'
112
+ desc += ` - ${tool.name}(${paramsStr}): ${tool.description || '无描述'}\n`
113
+ }
114
+ }
115
+ desc += '\n'
116
+ }
117
+
118
+ desc += '调用格式: ext_call({ plugin: "插件名", tool: "工具名", args: {...} })\n'
119
+ return desc.trim()
120
+ }
121
+
122
+ /**
123
+ * 刷新单个 agent 的 Python 插件提示词
124
+ */
125
+ _refreshAgentPythonPluginsPrompt(agent) {
126
+ const existingPrompt = agent._originalPrompt || ''
127
+ if (existingPrompt.includes('【Python 插件】')) {
128
+ return
129
+ }
130
+
131
+ const pyDesc = this._buildPythonPluginsDescription()
132
+ if (!pyDesc) return
133
+
134
+ // 将 Python 插件描述追加到系统提示词
135
+ agent.setSystemPrompt(existingPrompt + '\n\n' + pyDesc)
136
+ }
137
+
138
+ /**
139
+ * 刷新所有 agent 的 Python 插件提示词
140
+ */
141
+ _refreshAllAgentsPythonPluginsPrompt(framework) {
142
+ const visited = new Set()
143
+
144
+ const traverse = (agent) => {
145
+ if (!agent || visited.has(agent)) return
146
+ visited.add(agent)
147
+ this._refreshAgentPythonPluginsPrompt(agent)
148
+
149
+ // 递归处理子 agent
150
+ const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map()
151
+ for (const [name, subAgentInfo] of subAgents) {
152
+ traverse(subAgentInfo.agent)
153
+ }
154
+ }
155
+
156
+ const agents = framework._agents || []
157
+ for (const agent of agents) {
158
+ traverse(agent)
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 加载所有 Python 插件
164
+ */
165
+ _loadPythonPlugins() {
166
+ const pluginsDir = path.join(this._agentDir, 'plugins')
167
+ if (!fs.existsSync(pluginsDir)) {
168
+ return
169
+ }
170
+
171
+ const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.py'))
172
+
173
+ for (const file of files) {
174
+ try {
175
+ const pluginPath = path.join(pluginsDir, file)
176
+ const pluginName = file.replace('.py', '')
177
+
178
+ const pluginInfo = this._loadPythonPluginMeta(pluginPath)
179
+ if (pluginInfo) {
180
+ this._pythonPlugins.set(pluginName, {
181
+ name: pluginName,
182
+ path: pluginPath,
183
+ info: pluginInfo,
184
+ code: fs.readFileSync(pluginPath, 'utf-8')
185
+ })
186
+ log.info(` Loaded: ${pluginName}`)
187
+
188
+ // 注册插件的每个工具
189
+ if (pluginInfo.tools && Array.isArray(pluginInfo.tools)) {
190
+ for (const tool of pluginInfo.tools) {
191
+ this._registerPythonTool(pluginName, tool)
192
+ }
193
+ }
194
+ }
195
+ } catch (err) {
196
+ log.error(` Failed to load ${file}:`, err.message)
197
+ }
198
+ }
199
+
200
+ log.info(` Total Python plugins: ${this._pythonPlugins.size}`)
201
+ }
202
+
203
+ /**
204
+ * 注册 Python 插件的工具
205
+ */
206
+ _registerPythonTool(pluginName, tool) {
207
+ if (!tool.name) return
208
+
209
+ try {
210
+ this.registerTool({
211
+ name: tool.name,
212
+ description: tool.description || '',
213
+ pluginName: pluginName,
214
+ inputSchema: this._parseToolParams(tool.params || {}),
215
+ execute: async (args) => {
216
+ return this._executePythonTool(pluginName, tool.name, args)
217
+ }
218
+ })
219
+ } catch (err) {
220
+ log.error(` Failed to register tool ${tool.name}:`, err.message)
221
+ }
222
+ }
223
+
224
+ /**
225
+ * 解析工具参数 schema
226
+ */
227
+ _parseToolParams(params) {
228
+ // 构建 JSON Schema 格式
229
+ const properties = {}
230
+ const required = []
231
+
232
+ for (const [key, value] of Object.entries(params)) {
233
+ let type = 'string'
234
+ if (typeof value === 'boolean') type = 'boolean'
235
+ else if (typeof value === 'number') type = 'number'
236
+ else if (Array.isArray(value)) type = 'array'
237
+ else if (typeof value === 'object') type = 'object'
238
+
239
+ properties[key] = {
240
+ type,
241
+ description: ''
242
+ }
243
+ required.push(key)
244
+ }
245
+
246
+ const jsonSchema = {
247
+ type: 'object',
248
+ properties,
249
+ required
250
+ }
251
+
252
+ // 转换为 Zod Schema 以兼容 AI SDK
253
+ return jsonSchemaToZod(jsonSchema)
254
+ }
255
+
256
+ /**
257
+ * 加载单个 Python 插件的元信息
258
+ * 支持 PLUGIN 和 TOOLS 格式
259
+ */
260
+ _loadPythonPluginMeta(pluginPath) {
261
+ const code = fs.readFileSync(pluginPath, 'utf-8')
262
+
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)
300
+ return null
301
+ }
302
+ }
303
+
304
+ /**
305
+ * 从指定位置提取 Python 字典
306
+ */
307
+ _extractPythonDict(code, startIdx) {
308
+ const braceStart = code.indexOf('{', startIdx)
309
+ if (braceStart === -1) return null
310
+
311
+ let braceCount = 0
312
+ let endIdx = -1
313
+ for (let i = braceStart; i < code.length; i++) {
314
+ if (code[i] === '{') braceCount++
315
+ else if (code[i] === '}') {
316
+ braceCount--
317
+ if (braceCount === 0) {
318
+ endIdx = i
319
+ break
320
+ }
321
+ }
322
+ }
323
+
324
+ if (endIdx === -1) return null
325
+ return this._parsePythonDict(code.substring(braceStart, endIdx + 1))
326
+ }
327
+
328
+ /**
329
+ * 从指定位置提取 Python 列表
330
+ */
331
+ _extractPythonList(code, startIdx) {
332
+ const bracketStart = code.indexOf('[', startIdx)
333
+ if (bracketStart === -1) return null
334
+
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
+ }
346
+ }
347
+
348
+ if (endIdx === -1) return null
349
+ return this._parsePythonList(code.substring(bracketStart, endIdx + 1))
350
+ }
351
+
352
+ /**
353
+ * 解析 Python 字典为 JS 对象
354
+ */
355
+ _parsePythonDict(str) {
356
+ str = str.replace(/#.*$/gm, '').trim()
357
+ if (!str || str === '{}') return {}
358
+
359
+ // 直接尝试 JSON.parse(PLUGIN 字典是有效的 JSON 格式)
360
+ try {
361
+ return JSON.parse(str)
362
+ } catch {
363
+ // 如果失败,使用手动解析
364
+ }
365
+
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
374
+
375
+ while (i < str.length) {
376
+ const char = str[i]
377
+
378
+ if (escaped) {
379
+ escaped = false
380
+ i++
381
+ continue
382
+ }
383
+
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
456
+ }
457
+
458
+ /**
459
+ * 解析 Python 列表为 JS 数组
460
+ */
461
+ _parsePythonList(str) {
462
+ str = str.replace(/#.*$/gm, '').trim()
463
+ if (!str || str === '[]') return []
464
+
465
+ // 直接尝试 JSON.parse(TOOLS 列表是有效的 JSON 格式)
466
+ try {
467
+ return JSON.parse(str)
468
+ } catch {
469
+ // 如果失败,使用手动解析
470
+ }
471
+
472
+ const result = []
473
+ let i = 0
474
+ let itemStart = 1
475
+ let depth = 0
476
+ let inString = false
477
+ let stringChar = null
478
+ let escaped = false
479
+
480
+ while (i < str.length) {
481
+ const char = str[i]
482
+
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
506
+ }
507
+
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
+ }
533
+ }
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))
549
+ }
550
+ }
551
+ itemStart = i + 1
552
+ }
553
+
554
+ i++
555
+ }
556
+
557
+ return result
558
+ }
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
+
584
+ /**
585
+ * 执行 Python 插件工具
586
+ */
587
+ async _executePythonTool(pluginName, toolName, params) {
588
+ const plugin = this._pythonPlugins.get(pluginName)
589
+ if (!plugin) {
590
+ return { success: false, error: `Plugin '${pluginName}' not found` }
591
+ }
592
+
593
+ // 构建执行代码 - 使用 base64 避免转义问题
594
+ const pluginDir = path.dirname(plugin.path)
595
+ const codeBase64 = Buffer.from(plugin.code, 'utf-8').toString('base64')
596
+
597
+ const execCode = `
598
+ import sys
599
+ import base64
600
+ sys.path.insert(0, r'${pluginDir}')
601
+
602
+ # 加载插件代码
603
+ plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')
604
+ exec(compile(plugin_code, '${pluginName}.py', 'exec'))
605
+
606
+ # 执行工具
607
+ result = execute_tool('${toolName}', ${JSON.stringify(params)})
608
+ print(result)
609
+ `
610
+
611
+ try {
612
+ // 通过 python-execute 工具执行
613
+ const pythonExe = this._framework.toolRegistry._tools.get('python-execute')
614
+ if (!pythonExe) {
615
+ return { success: false, error: 'python-execute tool not found. Please install python-executor-plugin.' }
616
+ }
617
+
618
+ const pythonResult = await pythonExe.execute({ code: execCode })
619
+
620
+ if (pythonResult.success) {
621
+ try {
622
+ // 尝试解析 JSON 结果
623
+ const parsed = JSON.parse(pythonResult.output)
624
+ return parsed
625
+ } catch {
626
+ return { success: true, output: pythonResult.output }
627
+ }
628
+ } else {
629
+ return { success: false, error: pythonResult.error }
630
+ }
631
+ } catch (err) {
632
+ return { success: false, error: err.message }
633
+ }
634
+ }
635
+
636
+ /**
637
+ * 获取所有已加载的 Python 插件
638
+ */
639
+ getPythonPlugins() {
640
+ return Array.from(this._pythonPlugins.values())
641
+ }
642
+
643
+ /**
644
+ * 获取 Python 插件信息
645
+ */
646
+ getPythonPlugin(name) {
647
+ return this._pythonPlugins.get(name)
648
+ }
649
+ }
650
+
651
+ module.exports = PythonPluginLoader