foliko 1.0.75 → 1.0.76

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 (88) hide show
  1. package/.claude/settings.local.json +159 -157
  2. package/cli/bin/foliko.js +12 -12
  3. package/cli/src/commands/chat.js +143 -143
  4. package/cli/src/commands/list.js +93 -93
  5. package/cli/src/index.js +75 -75
  6. package/cli/src/ui/chat-ui.js +201 -201
  7. package/cli/src/utils/ansi.js +40 -40
  8. package/cli/src/utils/markdown.js +292 -292
  9. package/examples/ambient-example.js +194 -194
  10. package/examples/basic.js +115 -115
  11. package/examples/bootstrap.js +121 -121
  12. package/examples/mcp-example.js +56 -56
  13. package/examples/skill-example.js +49 -49
  14. package/examples/test-chat.js +137 -137
  15. package/examples/test-mcp.js +85 -85
  16. package/examples/test-reload.js +59 -59
  17. package/examples/test-telegram.js +50 -50
  18. package/examples/test-tg-bot.js +45 -45
  19. package/examples/test-tg-simple.js +47 -47
  20. package/examples/test-tg.js +62 -62
  21. package/examples/test-think.js +43 -43
  22. package/examples/test-web-plugin.js +103 -103
  23. package/examples/test-weixin-feishu.js +103 -103
  24. package/examples/workflow.js +158 -158
  25. package/package.json +1 -1
  26. package/plugins/ai-plugin.js +102 -102
  27. package/plugins/ambient-agent/EventWatcher.js +113 -113
  28. package/plugins/ambient-agent/ExplorerLoop.js +640 -640
  29. package/plugins/ambient-agent/GoalManager.js +197 -197
  30. package/plugins/ambient-agent/Reflector.js +95 -95
  31. package/plugins/ambient-agent/StateStore.js +90 -90
  32. package/plugins/ambient-agent/constants.js +101 -101
  33. package/plugins/ambient-agent/index.js +579 -579
  34. package/plugins/audit-plugin.js +187 -187
  35. package/plugins/default-plugins.js +662 -662
  36. package/plugins/email/constants.js +64 -64
  37. package/plugins/email/handlers.js +461 -461
  38. package/plugins/email/index.js +278 -278
  39. package/plugins/email/monitor.js +269 -269
  40. package/plugins/email/parser.js +138 -138
  41. package/plugins/email/reply.js +151 -151
  42. package/plugins/email/utils.js +124 -124
  43. package/plugins/feishu-plugin.js +481 -481
  44. package/plugins/file-system-plugin.js +826 -826
  45. package/plugins/install-plugin.js +199 -199
  46. package/plugins/python-executor-plugin.js +367 -367
  47. package/plugins/python-plugin-loader.js +481 -481
  48. package/plugins/rules-plugin.js +294 -294
  49. package/plugins/scheduler-plugin.js +691 -691
  50. package/plugins/session-plugin.js +369 -369
  51. package/plugins/shell-executor-plugin.js +197 -197
  52. package/plugins/storage-plugin.js +240 -240
  53. package/plugins/subagent-plugin.js +845 -845
  54. package/plugins/telegram-plugin.js +482 -482
  55. package/plugins/think-plugin.js +345 -345
  56. package/plugins/tools-plugin.js +196 -196
  57. package/plugins/web-plugin.js +606 -606
  58. package/plugins/weixin-plugin.js +545 -545
  59. package/src/capabilities/index.js +11 -11
  60. package/src/capabilities/skill-manager.js +609 -609
  61. package/src/capabilities/workflow-engine.js +1109 -1109
  62. package/src/core/agent-chat.js +882 -882
  63. package/src/core/agent.js +892 -892
  64. package/src/core/framework.js +465 -465
  65. package/src/core/index.js +19 -19
  66. package/src/core/plugin-base.js +219 -219
  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 +741 -741
  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
  88. package/foliko-1.0.75.tgz +0 -0
@@ -1,481 +1,481 @@
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 插件(会注册工具)
77
- this._loadPythonPlugins()
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
- // 监听 agent 创建事件,附加 Python 插件信息到系统提示词
95
- framework.on('agent:created', (agent) => {
96
- this._refreshAgentPythonPluginsPrompt(agent)
97
- })
98
-
99
- // 等待框架就绪后,刷新所有已有 agent 的 Python 插件提示词
100
- if (framework._ready) {
101
- this._refreshAllAgentsPythonPluginsPrompt(framework)
102
- } else {
103
- framework.once('framework:ready', () => {
104
- this._refreshAllAgentsPythonPluginsPrompt(framework)
105
- })
106
- }
107
-
108
- return this
109
- }
110
-
111
- /**
112
- * 构建 Python 插件描述
113
- */
114
- _buildPythonPluginsDescription() {
115
- if (this._pythonPlugins.size === 0) {
116
- return ''
117
- }
118
-
119
- let desc = '【Python 插件工具】\n'
120
- desc += '可以直接调用以下 Python 插件工具:\n\n'
121
-
122
- for (const [name, plugin] of this._pythonPlugins) {
123
- desc += `[${plugin.info.name}] ${plugin.info.description || ''}\n`
124
- if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
125
- for (const tool of plugin.info.tools) {
126
- const paramsStr = Object.keys(tool.params || {}).join(', ') || '无参数'
127
- desc += ` - ${tool.name}(${paramsStr}): ${tool.description || '无描述'}\n`
128
- }
129
- }
130
- desc += '\n'
131
- }
132
-
133
- desc += '调用格式:python_plugin({ plugin: "插件名", tool: "工具名", params: {...} })\n'
134
- return desc.trim()
135
- }
136
-
137
- /**
138
- * 刷新单个 agent 的 Python 插件提示词
139
- */
140
- _refreshAgentPythonPluginsPrompt(agent) {
141
- const existingPrompt = agent._originalPrompt || ''
142
- if (existingPrompt.includes('[Python 插件工具]')) {
143
- return
144
- }
145
-
146
- const pyDesc = this._buildPythonPluginsDescription()
147
- if (!pyDesc) return
148
-
149
- // 将 Python 插件描述追加到系统提示词
150
- agent.setSystemPrompt(existingPrompt + '\n\n' + pyDesc)
151
- }
152
-
153
- /**
154
- * 刷新所有 agent 的 Python 插件提示词
155
- */
156
- _refreshAllAgentsPythonPluginsPrompt(framework) {
157
- const visited = new Set()
158
-
159
- const traverse = (agent) => {
160
- if (!agent || visited.has(agent)) return
161
- visited.add(agent)
162
- this._refreshAgentPythonPluginsPrompt(agent)
163
-
164
- // 递归处理子 agent
165
- const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map()
166
- for (const [name, subAgentInfo] of subAgents) {
167
- traverse(subAgentInfo.agent)
168
- }
169
- }
170
-
171
- const agents = framework._agents || []
172
- for (const agent of agents) {
173
- traverse(agent)
174
- }
175
- }
176
-
177
- /**
178
- * 加载所有 Python 插件
179
- */
180
- _loadPythonPlugins() {
181
- const pluginsDir = path.join(this._agentDir, 'plugins')
182
- if (!fs.existsSync(pluginsDir)) {
183
- return
184
- }
185
-
186
- const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.py'))
187
-
188
- for (const file of files) {
189
- try {
190
- const pluginPath = path.join(pluginsDir, file)
191
- const pluginName = file.replace('.py', '')
192
-
193
- const pluginInfo = this._loadPythonPluginMeta(pluginPath)
194
- if (pluginInfo) {
195
- this._pythonPlugins.set(pluginName, {
196
- name: pluginName,
197
- path: pluginPath,
198
- info: pluginInfo,
199
- code: fs.readFileSync(pluginPath, 'utf-8')
200
- })
201
- log.info(` Loaded: ${pluginName}`)
202
-
203
- // 注册插件的每个工具
204
- if (pluginInfo.tools && Array.isArray(pluginInfo.tools)) {
205
- for (const tool of pluginInfo.tools) {
206
- this._registerPythonTool(pluginName, tool)
207
- }
208
- }
209
- }
210
- } catch (err) {
211
- log.error(` Failed to load ${file}:`, err.message)
212
- }
213
- }
214
-
215
- log.info(` Total Python plugins: ${this._pythonPlugins.size}`)
216
- }
217
-
218
- /**
219
- * 注册 Python 插件的工具到框架
220
- */
221
- _registerPythonTool(pluginName, tool) {
222
- if (!tool.name) return
223
-
224
- try {
225
- this._framework.registerTool({
226
- name: tool.name,
227
- description: tool.description || `${tool.name} (from ${pluginName})`,
228
- inputSchema: this._parseToolParams(tool.params || {}),
229
- execute: async (args) => {
230
- return this._executePythonTool(pluginName, tool.name, args)
231
- }
232
- })
233
- //log.info(` Registered tool: ${tool.name}`)
234
- } catch (err) {
235
- log.error(` Failed to register tool ${tool.name}:`, err.message)
236
- }
237
- }
238
-
239
- /**
240
- * 解析工具参数 schema
241
- */
242
- _parseToolParams(params) {
243
- // 构建 JSON Schema 格式
244
- const properties = {}
245
- const required = []
246
-
247
- for (const [key, value] of Object.entries(params)) {
248
- let type = 'string'
249
- if (typeof value === 'boolean') type = 'boolean'
250
- else if (typeof value === 'number') type = 'number'
251
- else if (Array.isArray(value)) type = 'array'
252
- else if (typeof value === 'object') type = 'object'
253
-
254
- properties[key] = {
255
- type,
256
- description: ''
257
- }
258
- required.push(key)
259
- }
260
-
261
- const jsonSchema = {
262
- type: 'object',
263
- properties,
264
- required
265
- }
266
-
267
- // 转换为 Zod Schema 以兼容 AI SDK
268
- return jsonSchemaToZod(jsonSchema)
269
- }
270
-
271
- /**
272
- * 加载单个 Python 插件的元信息
273
- */
274
- _loadPythonPluginMeta(pluginPath) {
275
- const code = fs.readFileSync(pluginPath, 'utf-8')
276
-
277
- // 找到 plugin_info = {
278
- const startIdx = code.indexOf('plugin_info')
279
- if (startIdx === -1) {
280
- log.warn(` ${path.basename(pluginPath)}: no plugin_info found`)
281
- return null
282
- }
283
-
284
- // 从 plugin_info 后开始,找到第一个 { 的位置
285
- const braceStart = code.indexOf('{', startIdx)
286
- if (braceStart === -1) return null
287
-
288
- // 使用栈匹配找到对应的 }
289
- let braceCount = 0
290
- let endIdx = -1
291
- for (let i = braceStart; i < code.length; i++) {
292
- if (code[i] === '{') braceCount++
293
- else if (code[i] === '}') {
294
- braceCount--
295
- if (braceCount === 0) {
296
- endIdx = i
297
- break
298
- }
299
- }
300
- }
301
-
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, '')
313
-
314
- // 转换为 JS 兼容的格式
315
- infoStr = infoStr
316
- .replace(/True/g, 'true')
317
- .replace(/False/g, 'false')
318
- .replace(/None/g, 'null')
319
- .replace(/'/g, '"')
320
-
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
326
- }
327
- }
328
-
329
- /**
330
- * 解析单个值
331
- */
332
- _parseValue(str) {
333
- str = str.trim()
334
- if (!str) return null
335
-
336
- // 字典
337
- if (str.startsWith('{')) {
338
- return this._parseDict(str)
339
- }
340
-
341
- // 字符串
342
- if (str.startsWith('"') || str.startsWith("'")) {
343
- return str.slice(1, -1)
344
- }
345
-
346
- // 布尔值
347
- if (str === 'True') return true
348
- if (str === 'False') return false
349
- if (str === 'None') return null
350
-
351
- // 数字
352
- if (!isNaN(str)) return Number(str)
353
-
354
- return str
355
- }
356
-
357
- /**
358
- * 简单解析 Python 字典为 JS 对象
359
- */
360
- _parseDict(str) {
361
- // 移除注释并清理
362
- str = str.replace(/#.*$/gm, '').trim()
363
- if (!str) return {}
364
-
365
- const result = {}
366
- let i = 0
367
- let currentKey = null
368
- let currentValue = ''
369
- let inString = false
370
- let stringChar = null
371
-
372
- while (i < str.length) {
373
- const char = str[i]
374
-
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
- }
384
- }
385
-
386
- // 如果不在字符串内
387
- if (!inString) {
388
- // 找到键
389
- if (currentKey === null && char === ':') {
390
- currentKey = currentValue.trim().replace(/^["']|["']$/g, '')
391
- currentValue = ''
392
- }
393
- // 找到值的结尾(逗号或右括号)
394
- else if (char === ',' || char === '}') {
395
- if (currentKey !== null) {
396
- const value = currentValue.trim().replace(/,$/, '')
397
- result[currentKey] = this._parseValue(value)
398
- }
399
- currentKey = null
400
- currentValue = ''
401
- } else {
402
- currentValue += char
403
- }
404
- } else {
405
- currentValue += char
406
- }
407
-
408
- i++
409
- }
410
-
411
- return result
412
- }
413
-
414
- /**
415
- * 执行 Python 插件工具
416
- */
417
- async _executePythonTool(pluginName, toolName, params) {
418
- const plugin = this._pythonPlugins.get(pluginName)
419
- if (!plugin) {
420
- return { success: false, error: `Plugin '${pluginName}' not found` }
421
- }
422
-
423
- // 构建执行代码 - 使用 base64 避免转义问题
424
- const pluginDir = path.dirname(plugin.path)
425
- const codeBase64 = Buffer.from(plugin.code, 'utf-8').toString('base64')
426
-
427
- const execCode = `
428
- import sys
429
- import base64
430
- sys.path.insert(0, r'${pluginDir}')
431
-
432
- # 加载插件代码
433
- plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')
434
- exec(compile(plugin_code, '${pluginName}.py', 'exec'))
435
-
436
- # 执行工具
437
- result = execute_tool('${toolName}', ${JSON.stringify(params)})
438
- print(result)
439
- `
440
-
441
- try {
442
- // 通过 python-execute 工具执行
443
- const pythonExe = this._framework.toolRegistry._tools.get('python-execute')
444
- if (!pythonExe) {
445
- return { success: false, error: 'python-execute tool not found. Please install python-executor-plugin.' }
446
- }
447
-
448
- const pythonResult = await pythonExe.execute({ code: execCode })
449
-
450
- if (pythonResult.success) {
451
- try {
452
- // 尝试解析 JSON 结果
453
- const parsed = JSON.parse(pythonResult.output)
454
- return parsed
455
- } catch {
456
- return { success: true, output: pythonResult.output }
457
- }
458
- } else {
459
- return { success: false, error: pythonResult.error }
460
- }
461
- } catch (err) {
462
- return { success: false, error: err.message }
463
- }
464
- }
465
-
466
- /**
467
- * 获取所有已加载的 Python 插件
468
- */
469
- getPythonPlugins() {
470
- return Array.from(this._pythonPlugins.values())
471
- }
472
-
473
- /**
474
- * 获取 Python 插件信息
475
- */
476
- getPythonPlugin(name) {
477
- return this._pythonPlugins.get(name)
478
- }
479
- }
480
-
481
- 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 插件(会注册工具)
77
+ this._loadPythonPlugins()
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
+ // 监听 agent 创建事件,附加 Python 插件信息到系统提示词
95
+ framework.on('agent:created', (agent) => {
96
+ this._refreshAgentPythonPluginsPrompt(agent)
97
+ })
98
+
99
+ // 等待框架就绪后,刷新所有已有 agent 的 Python 插件提示词
100
+ if (framework._ready) {
101
+ this._refreshAllAgentsPythonPluginsPrompt(framework)
102
+ } else {
103
+ framework.once('framework:ready', () => {
104
+ this._refreshAllAgentsPythonPluginsPrompt(framework)
105
+ })
106
+ }
107
+
108
+ return this
109
+ }
110
+
111
+ /**
112
+ * 构建 Python 插件描述
113
+ */
114
+ _buildPythonPluginsDescription() {
115
+ if (this._pythonPlugins.size === 0) {
116
+ return ''
117
+ }
118
+
119
+ let desc = '【Python 插件工具】\n'
120
+ desc += '可以直接调用以下 Python 插件工具:\n\n'
121
+
122
+ for (const [name, plugin] of this._pythonPlugins) {
123
+ desc += `[${plugin.info.name}] ${plugin.info.description || ''}\n`
124
+ if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
125
+ for (const tool of plugin.info.tools) {
126
+ const paramsStr = Object.keys(tool.params || {}).join(', ') || '无参数'
127
+ desc += ` - ${tool.name}(${paramsStr}): ${tool.description || '无描述'}\n`
128
+ }
129
+ }
130
+ desc += '\n'
131
+ }
132
+
133
+ desc += '调用格式:python_plugin({ plugin: "插件名", tool: "工具名", params: {...} })\n'
134
+ return desc.trim()
135
+ }
136
+
137
+ /**
138
+ * 刷新单个 agent 的 Python 插件提示词
139
+ */
140
+ _refreshAgentPythonPluginsPrompt(agent) {
141
+ const existingPrompt = agent._originalPrompt || ''
142
+ if (existingPrompt.includes('[Python 插件工具]')) {
143
+ return
144
+ }
145
+
146
+ const pyDesc = this._buildPythonPluginsDescription()
147
+ if (!pyDesc) return
148
+
149
+ // 将 Python 插件描述追加到系统提示词
150
+ agent.setSystemPrompt(existingPrompt + '\n\n' + pyDesc)
151
+ }
152
+
153
+ /**
154
+ * 刷新所有 agent 的 Python 插件提示词
155
+ */
156
+ _refreshAllAgentsPythonPluginsPrompt(framework) {
157
+ const visited = new Set()
158
+
159
+ const traverse = (agent) => {
160
+ if (!agent || visited.has(agent)) return
161
+ visited.add(agent)
162
+ this._refreshAgentPythonPluginsPrompt(agent)
163
+
164
+ // 递归处理子 agent
165
+ const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map()
166
+ for (const [name, subAgentInfo] of subAgents) {
167
+ traverse(subAgentInfo.agent)
168
+ }
169
+ }
170
+
171
+ const agents = framework._agents || []
172
+ for (const agent of agents) {
173
+ traverse(agent)
174
+ }
175
+ }
176
+
177
+ /**
178
+ * 加载所有 Python 插件
179
+ */
180
+ _loadPythonPlugins() {
181
+ const pluginsDir = path.join(this._agentDir, 'plugins')
182
+ if (!fs.existsSync(pluginsDir)) {
183
+ return
184
+ }
185
+
186
+ const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.py'))
187
+
188
+ for (const file of files) {
189
+ try {
190
+ const pluginPath = path.join(pluginsDir, file)
191
+ const pluginName = file.replace('.py', '')
192
+
193
+ const pluginInfo = this._loadPythonPluginMeta(pluginPath)
194
+ if (pluginInfo) {
195
+ this._pythonPlugins.set(pluginName, {
196
+ name: pluginName,
197
+ path: pluginPath,
198
+ info: pluginInfo,
199
+ code: fs.readFileSync(pluginPath, 'utf-8')
200
+ })
201
+ log.info(` Loaded: ${pluginName}`)
202
+
203
+ // 注册插件的每个工具
204
+ if (pluginInfo.tools && Array.isArray(pluginInfo.tools)) {
205
+ for (const tool of pluginInfo.tools) {
206
+ this._registerPythonTool(pluginName, tool)
207
+ }
208
+ }
209
+ }
210
+ } catch (err) {
211
+ log.error(` Failed to load ${file}:`, err.message)
212
+ }
213
+ }
214
+
215
+ log.info(` Total Python plugins: ${this._pythonPlugins.size}`)
216
+ }
217
+
218
+ /**
219
+ * 注册 Python 插件的工具到框架
220
+ */
221
+ _registerPythonTool(pluginName, tool) {
222
+ if (!tool.name) return
223
+
224
+ try {
225
+ this._framework.registerTool({
226
+ name: tool.name,
227
+ description: tool.description || `${tool.name} (from ${pluginName})`,
228
+ inputSchema: this._parseToolParams(tool.params || {}),
229
+ execute: async (args) => {
230
+ return this._executePythonTool(pluginName, tool.name, args)
231
+ }
232
+ })
233
+ //log.info(` Registered tool: ${tool.name}`)
234
+ } catch (err) {
235
+ log.error(` Failed to register tool ${tool.name}:`, err.message)
236
+ }
237
+ }
238
+
239
+ /**
240
+ * 解析工具参数 schema
241
+ */
242
+ _parseToolParams(params) {
243
+ // 构建 JSON Schema 格式
244
+ const properties = {}
245
+ const required = []
246
+
247
+ for (const [key, value] of Object.entries(params)) {
248
+ let type = 'string'
249
+ if (typeof value === 'boolean') type = 'boolean'
250
+ else if (typeof value === 'number') type = 'number'
251
+ else if (Array.isArray(value)) type = 'array'
252
+ else if (typeof value === 'object') type = 'object'
253
+
254
+ properties[key] = {
255
+ type,
256
+ description: ''
257
+ }
258
+ required.push(key)
259
+ }
260
+
261
+ const jsonSchema = {
262
+ type: 'object',
263
+ properties,
264
+ required
265
+ }
266
+
267
+ // 转换为 Zod Schema 以兼容 AI SDK
268
+ return jsonSchemaToZod(jsonSchema)
269
+ }
270
+
271
+ /**
272
+ * 加载单个 Python 插件的元信息
273
+ */
274
+ _loadPythonPluginMeta(pluginPath) {
275
+ const code = fs.readFileSync(pluginPath, 'utf-8')
276
+
277
+ // 找到 plugin_info = {
278
+ const startIdx = code.indexOf('plugin_info')
279
+ if (startIdx === -1) {
280
+ log.warn(` ${path.basename(pluginPath)}: no plugin_info found`)
281
+ return null
282
+ }
283
+
284
+ // 从 plugin_info 后开始,找到第一个 { 的位置
285
+ const braceStart = code.indexOf('{', startIdx)
286
+ if (braceStart === -1) return null
287
+
288
+ // 使用栈匹配找到对应的 }
289
+ let braceCount = 0
290
+ let endIdx = -1
291
+ for (let i = braceStart; i < code.length; i++) {
292
+ if (code[i] === '{') braceCount++
293
+ else if (code[i] === '}') {
294
+ braceCount--
295
+ if (braceCount === 0) {
296
+ endIdx = i
297
+ break
298
+ }
299
+ }
300
+ }
301
+
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, '')
313
+
314
+ // 转换为 JS 兼容的格式
315
+ infoStr = infoStr
316
+ .replace(/True/g, 'true')
317
+ .replace(/False/g, 'false')
318
+ .replace(/None/g, 'null')
319
+ .replace(/'/g, '"')
320
+
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
326
+ }
327
+ }
328
+
329
+ /**
330
+ * 解析单个值
331
+ */
332
+ _parseValue(str) {
333
+ str = str.trim()
334
+ if (!str) return null
335
+
336
+ // 字典
337
+ if (str.startsWith('{')) {
338
+ return this._parseDict(str)
339
+ }
340
+
341
+ // 字符串
342
+ if (str.startsWith('"') || str.startsWith("'")) {
343
+ return str.slice(1, -1)
344
+ }
345
+
346
+ // 布尔值
347
+ if (str === 'True') return true
348
+ if (str === 'False') return false
349
+ if (str === 'None') return null
350
+
351
+ // 数字
352
+ if (!isNaN(str)) return Number(str)
353
+
354
+ return str
355
+ }
356
+
357
+ /**
358
+ * 简单解析 Python 字典为 JS 对象
359
+ */
360
+ _parseDict(str) {
361
+ // 移除注释并清理
362
+ str = str.replace(/#.*$/gm, '').trim()
363
+ if (!str) return {}
364
+
365
+ const result = {}
366
+ let i = 0
367
+ let currentKey = null
368
+ let currentValue = ''
369
+ let inString = false
370
+ let stringChar = null
371
+
372
+ while (i < str.length) {
373
+ const char = str[i]
374
+
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
+ }
384
+ }
385
+
386
+ // 如果不在字符串内
387
+ if (!inString) {
388
+ // 找到键
389
+ if (currentKey === null && char === ':') {
390
+ currentKey = currentValue.trim().replace(/^["']|["']$/g, '')
391
+ currentValue = ''
392
+ }
393
+ // 找到值的结尾(逗号或右括号)
394
+ else if (char === ',' || char === '}') {
395
+ if (currentKey !== null) {
396
+ const value = currentValue.trim().replace(/,$/, '')
397
+ result[currentKey] = this._parseValue(value)
398
+ }
399
+ currentKey = null
400
+ currentValue = ''
401
+ } else {
402
+ currentValue += char
403
+ }
404
+ } else {
405
+ currentValue += char
406
+ }
407
+
408
+ i++
409
+ }
410
+
411
+ return result
412
+ }
413
+
414
+ /**
415
+ * 执行 Python 插件工具
416
+ */
417
+ async _executePythonTool(pluginName, toolName, params) {
418
+ const plugin = this._pythonPlugins.get(pluginName)
419
+ if (!plugin) {
420
+ return { success: false, error: `Plugin '${pluginName}' not found` }
421
+ }
422
+
423
+ // 构建执行代码 - 使用 base64 避免转义问题
424
+ const pluginDir = path.dirname(plugin.path)
425
+ const codeBase64 = Buffer.from(plugin.code, 'utf-8').toString('base64')
426
+
427
+ const execCode = `
428
+ import sys
429
+ import base64
430
+ sys.path.insert(0, r'${pluginDir}')
431
+
432
+ # 加载插件代码
433
+ plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')
434
+ exec(compile(plugin_code, '${pluginName}.py', 'exec'))
435
+
436
+ # 执行工具
437
+ result = execute_tool('${toolName}', ${JSON.stringify(params)})
438
+ print(result)
439
+ `
440
+
441
+ try {
442
+ // 通过 python-execute 工具执行
443
+ const pythonExe = this._framework.toolRegistry._tools.get('python-execute')
444
+ if (!pythonExe) {
445
+ return { success: false, error: 'python-execute tool not found. Please install python-executor-plugin.' }
446
+ }
447
+
448
+ const pythonResult = await pythonExe.execute({ code: execCode })
449
+
450
+ if (pythonResult.success) {
451
+ try {
452
+ // 尝试解析 JSON 结果
453
+ const parsed = JSON.parse(pythonResult.output)
454
+ return parsed
455
+ } catch {
456
+ return { success: true, output: pythonResult.output }
457
+ }
458
+ } else {
459
+ return { success: false, error: pythonResult.error }
460
+ }
461
+ } catch (err) {
462
+ return { success: false, error: err.message }
463
+ }
464
+ }
465
+
466
+ /**
467
+ * 获取所有已加载的 Python 插件
468
+ */
469
+ getPythonPlugins() {
470
+ return Array.from(this._pythonPlugins.values())
471
+ }
472
+
473
+ /**
474
+ * 获取 Python 插件信息
475
+ */
476
+ getPythonPlugin(name) {
477
+ return this._pythonPlugins.get(name)
478
+ }
479
+ }
480
+
481
+ module.exports = { PythonPluginLoader }