foliko 1.1.7 → 1.1.9

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 (113) hide show
  1. package/.agent/agents/poster-expert.md +228 -0
  2. package/.agent/data/default.json +10 -0
  3. package/.agent/data/plugins-state.json +1 -1
  4. package/.agent/mcp_config.json +0 -6
  5. package/.agent/plugins/test-plugin.py +15 -0
  6. package/.agent/plugins/test_nested_plugin.py +85 -0
  7. package/.agent/sessions/cli_default.json +83 -803
  8. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +189 -0
  9. package/.claude/settings.local.json +8 -1
  10. package/cli/src/ui/chat-ui.js +32 -21
  11. package/foliko_poster.png +0 -0
  12. package/output/business_poster_final.png +0 -0
  13. package/output/emoji_test_v2.png +0 -0
  14. package/output/foliko-ai-launch-poster.png +0 -0
  15. package/output/foliko-poster.png +0 -0
  16. package/output/muji_style_poster.png +0 -0
  17. package/output/new_product_launch.png +0 -0
  18. package/output/news_poster_625yi_subsidy.png +0 -0
  19. package/output/tech_future_2026.png +0 -0
  20. package/output/tech_future_poster.png +0 -0
  21. package/package.json +2 -2
  22. package/plugins/default-plugins.js +60 -7
  23. package/plugins/extension-executor-plugin.js +32 -22
  24. package/plugins/feishu-plugin.js +2 -2
  25. package/plugins/python-plugin-loader.js +461 -40
  26. package/plugins/python-plugin-loader.js.bak +856 -0
  27. package/plugins/subagent-plugin.js +0 -121
  28. package/plugins/telegram-plugin.js +2 -2
  29. package/plugins/weixin-plugin.js +40 -31
  30. package/poster.png +0 -0
  31. package/skills/foliko-dev/SKILL.md +6 -0
  32. package/skills/python-plugin-dev/SKILL.md +124 -2
  33. package/src/core/agent-chat.js +220 -281
  34. package/src/core/agent.js +127 -29
  35. package/src/executors/mcp-executor.js +282 -26
  36. package/src/utils/plugin-helpers.js +22 -4
  37. package/system.md +362 -109
  38. package/test-scan.js +18 -0
  39. package/.agent/memory/feedback/mnt7jrlt-d67qs7.md +0 -15
  40. package/.agent/memory/feedback/mnt88ja3-al4fuy.md +0 -9
  41. package/.agent/plugins/poster-plugin/README.md +0 -304
  42. package/.agent/plugins/poster-plugin/emojis/rocket.png +0 -1
  43. package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
  44. package/.agent/plugins/poster-plugin/fonts/SegoeUI Emoji.ttf +0 -0
  45. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
  46. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
  47. package/.agent/plugins/poster-plugin/index.js +0 -13
  48. package/.agent/plugins/poster-plugin/package.json +0 -29
  49. package/.agent/plugins/poster-plugin/src/canvas.js +0 -224
  50. package/.agent/plugins/poster-plugin/src/components/arrow.js +0 -84
  51. package/.agent/plugins/poster-plugin/src/components/avatar.js +0 -71
  52. package/.agent/plugins/poster-plugin/src/components/badge.js +0 -85
  53. package/.agent/plugins/poster-plugin/src/components/barcode.js +0 -120
  54. package/.agent/plugins/poster-plugin/src/components/bubble.js +0 -153
  55. package/.agent/plugins/poster-plugin/src/components/button.js +0 -124
  56. package/.agent/plugins/poster-plugin/src/components/card.js +0 -88
  57. package/.agent/plugins/poster-plugin/src/components/chart.js +0 -127
  58. package/.agent/plugins/poster-plugin/src/components/chip.js +0 -88
  59. package/.agent/plugins/poster-plugin/src/components/columns.js +0 -107
  60. package/.agent/plugins/poster-plugin/src/components/cta.js +0 -87
  61. package/.agent/plugins/poster-plugin/src/components/divider.js +0 -55
  62. package/.agent/plugins/poster-plugin/src/components/feature.js +0 -85
  63. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +0 -117
  64. package/.agent/plugins/poster-plugin/src/components/frame.js +0 -230
  65. package/.agent/plugins/poster-plugin/src/components/grid.js +0 -118
  66. package/.agent/plugins/poster-plugin/src/components/highlightText.js +0 -144
  67. package/.agent/plugins/poster-plugin/src/components/icon.js +0 -94
  68. package/.agent/plugins/poster-plugin/src/components/imageFrame.js +0 -155
  69. package/.agent/plugins/poster-plugin/src/components/index.js +0 -81
  70. package/.agent/plugins/poster-plugin/src/components/listItem.js +0 -147
  71. package/.agent/plugins/poster-plugin/src/components/notification.js +0 -123
  72. package/.agent/plugins/poster-plugin/src/components/progress.js +0 -79
  73. package/.agent/plugins/poster-plugin/src/components/progressCircle.js +0 -117
  74. package/.agent/plugins/poster-plugin/src/components/qrcode.js +0 -74
  75. package/.agent/plugins/poster-plugin/src/components/quote.js +0 -97
  76. package/.agent/plugins/poster-plugin/src/components/rating.js +0 -85
  77. package/.agent/plugins/poster-plugin/src/components/ribbon.js +0 -193
  78. package/.agent/plugins/poster-plugin/src/components/seal.js +0 -146
  79. package/.agent/plugins/poster-plugin/src/components/star.js +0 -70
  80. package/.agent/plugins/poster-plugin/src/components/statCard.js +0 -105
  81. package/.agent/plugins/poster-plugin/src/components/stepper.js +0 -118
  82. package/.agent/plugins/poster-plugin/src/components/table.js +0 -167
  83. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +0 -85
  84. package/.agent/plugins/poster-plugin/src/components/timeline.js +0 -117
  85. package/.agent/plugins/poster-plugin/src/components/watermark.js +0 -52
  86. package/.agent/plugins/poster-plugin/src/composer.js +0 -2146
  87. package/.agent/plugins/poster-plugin/src/elements/artText.js +0 -60
  88. package/.agent/plugins/poster-plugin/src/elements/background.js +0 -84
  89. package/.agent/plugins/poster-plugin/src/elements/circle.js +0 -31
  90. package/.agent/plugins/poster-plugin/src/elements/image.js +0 -28
  91. package/.agent/plugins/poster-plugin/src/elements/index.js +0 -28
  92. package/.agent/plugins/poster-plugin/src/elements/line.js +0 -23
  93. package/.agent/plugins/poster-plugin/src/elements/polygon.js +0 -63
  94. package/.agent/plugins/poster-plugin/src/elements/rectangle.js +0 -32
  95. package/.agent/plugins/poster-plugin/src/elements/richText.js +0 -230
  96. package/.agent/plugins/poster-plugin/src/elements/svg.js +0 -108
  97. package/.agent/plugins/poster-plugin/src/elements/text.js +0 -109
  98. package/.agent/plugins/poster-plugin/src/fonts.js +0 -243
  99. package/.agent/plugins/poster-plugin/src/index.js +0 -2081
  100. package/.agent/plugins/poster-plugin/src/presets.js +0 -36
  101. package/.agent/plugins/poster-plugin/src/templates/business.js +0 -60
  102. package/.agent/plugins/poster-plugin/src/templates/gradient.js +0 -64
  103. package/.agent/plugins/poster-plugin/src/templates/index.js +0 -43
  104. package/.agent/plugins/poster-plugin/src/templates/modern.js +0 -69
  105. package/.agent/plugins/poster-plugin/src/templates/simple.js +0 -58
  106. package/.agent/plugins/poster-plugin/src/templates/social.js +0 -62
  107. package/.agent/plugins/poster-plugin/src/templates/tech.js +0 -84
  108. package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +0 -84
  109. package/.agent/plugins/poster-plugin/test-background.svg +0 -1
  110. package/.agent/plugins/poster-plugin/test-full-poster.svg +0 -2
  111. package/.agent/plugins/poster-plugin/test-image.png +0 -0
  112. package/.agent/plugins/poster-plugin/yarn.lock +0 -1007
  113. package/output/zen_silence.png +0 -0
@@ -74,6 +74,9 @@ class PythonPluginLoader extends Plugin {
74
74
  // 先设置 framework 引用(供 _loadPythonPlugins 使用)
75
75
  this._framework = framework
76
76
 
77
+ // 获取 ExtensionExecutorPlugin 实例,用于直接注册 Python 插件工具
78
+ this._extensionExecutor = framework.pluginManager?.get('extension-executor') || null
79
+
77
80
  // 加载所有 Python 插件(会注册工具到 ExtensionExecutor)
78
81
  this._loadPythonPlugins()
79
82
 
@@ -96,14 +99,15 @@ class PythonPluginLoader extends Plugin {
96
99
 
97
100
  /**
98
101
  * 构建 Python 插件描述
102
+ * 格式与 MCP 一致,通过 ext_call 调用
99
103
  */
100
104
  _buildPythonPluginsDescription() {
101
105
  if (this._pythonPlugins.size === 0) {
102
106
  return ''
103
107
  }
104
108
 
105
- let desc = '## 【Python 插件】\n'
106
- desc += 'Python 插件工具已注册为 `插件名_工具名` 格式,通过 ext_call 调用:\n\n'
109
+ let desc = '## 【Python 插件】\n\n'
110
+ desc += 'Python 插件工具通过 `ext_call` 调用:\n\n'
107
111
 
108
112
  for (const [name, plugin] of this._pythonPlugins) {
109
113
  desc += `### ${plugin.info.name}\n`
@@ -111,8 +115,21 @@ class PythonPluginLoader extends Plugin {
111
115
  if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
112
116
  for (const tool of plugin.info.tools) {
113
117
  const fullName = `${name}_${tool.name}`
114
- const paramsStr = this._formatParams(tool.params || {})
115
- desc += `- **${fullName}**(${paramsStr}): ${tool.description || '无描述'}\n`
118
+ desc += `- **${fullName}**: ${tool.description || '无描述'}\n`
119
+ // 使用 zodSchemaToMarkdown 生成参数描述
120
+ const params = tool.params || {}
121
+ if (Object.keys(params).length > 0) {
122
+ try {
123
+ const jsonSchema = this._paramsToJsonSchema(params)
124
+ const zodSchema = this._jsonSchemaToZod(jsonSchema)
125
+ const schemaMd = zodSchemaToMarkdown(zodSchema)
126
+ if (schemaMd) {
127
+ desc += `**参数:**\n\n${schemaMd}\n`
128
+ }
129
+ } catch (e) {
130
+ // 忽略参数描述生成错误
131
+ }
132
+ }
116
133
  }
117
134
  }
118
135
  desc += '\n'
@@ -124,6 +141,127 @@ class PythonPluginLoader extends Plugin {
124
141
  return desc.trim()
125
142
  }
126
143
 
144
+ /**
145
+ * 将参数对象转换为 JSON Schema 格式
146
+ */
147
+ _paramsToJsonSchema(params) {
148
+ const properties = {}
149
+ const required = []
150
+
151
+ for (const [key, value] of Object.entries(params)) {
152
+ let type = 'string'
153
+ if (typeof value === 'boolean') type = 'boolean'
154
+ else if (typeof value === 'number') type = 'number'
155
+ else if (Array.isArray(value)) type = 'array'
156
+ else if (typeof value === 'object' && value !== null) type = 'object'
157
+
158
+ properties[key] = {
159
+ type,
160
+ description: ''
161
+ }
162
+ required.push(key)
163
+ }
164
+
165
+ return {
166
+ type: 'object',
167
+ properties,
168
+ required
169
+ }
170
+ }
171
+
172
+ /**
173
+ * 将 JSON Schema 转换为 Zod schema
174
+ */
175
+ _jsonSchemaToZod(jsonSchema) {
176
+ if (!jsonSchema || !jsonSchema.properties) {
177
+ return z.object({})
178
+ }
179
+
180
+ try {
181
+ const shape = {}
182
+ const properties = jsonSchema.properties
183
+ const required = jsonSchema.required || []
184
+
185
+ for (const [key, prop] of Object.entries(properties)) {
186
+ shape[key] = this._jsonSchemaPropToZod(prop, required.includes(key))
187
+ }
188
+
189
+ return z.object(shape)
190
+ } catch (e) {
191
+ return z.object({})
192
+ }
193
+ }
194
+
195
+ /**
196
+ * 将 JSON Schema 属性转换为 Zod 类型(支持完整嵌套)
197
+ */
198
+ _jsonSchemaPropToZod(prop, isRequired) {
199
+ // 1. 处理 enum(枚举值)
200
+ if (prop.enum) {
201
+ let zodType = z.string().enum(prop.enum)
202
+ return isRequired ? zodType : zodType.optional()
203
+ }
204
+
205
+ const type = prop.type || 'string'
206
+
207
+ // 2. 简单类型
208
+ switch (type) {
209
+ case 'string':
210
+ return isRequired ? z.string() : z.string().optional()
211
+ case 'number':
212
+ case 'integer':
213
+ return isRequired ? z.number() : z.number().optional()
214
+ case 'boolean':
215
+ return isRequired ? z.boolean() : z.boolean().optional()
216
+ default:
217
+ break
218
+ }
219
+
220
+ // 3. 数组类型
221
+ if (type === 'array') {
222
+ let itemsZod = z.any()
223
+ if (prop.items) {
224
+ // 支持 oneOf(联合类型数组)
225
+ if (prop.items.oneOf) {
226
+ const unionTypes = prop.items.oneOf.map(item => this._jsonSchemaPropToZod(item, true))
227
+ if (unionTypes.length > 0) {
228
+ itemsZod = z.union(unionTypes)
229
+ }
230
+ } else {
231
+ itemsZod = this._jsonSchemaPropToZod(prop.items, true)
232
+ }
233
+ }
234
+ return isRequired ? z.array(itemsZod) : z.array(itemsZod).optional()
235
+ }
236
+
237
+ // 4. 对象类型
238
+ if (type === 'object') {
239
+ if (prop.properties && Object.keys(prop.properties).length > 0) {
240
+ const nested = {}
241
+ for (const [k, v] of Object.entries(prop.properties)) {
242
+ nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false)
243
+ }
244
+ const objZod = z.object(nested)
245
+ return isRequired ? objZod : objZod.optional()
246
+ }
247
+ // 无 properties 的 object,视为 record
248
+ const recordZod = z.record(z.any())
249
+ return isRequired ? recordZod : recordZod.optional()
250
+ }
251
+
252
+ // 5. oneOf(联合类型)
253
+ if (prop.oneOf) {
254
+ const unionTypes = prop.oneOf.map(item => this._jsonSchemaPropToZod(item, true))
255
+ if (unionTypes.length > 0) {
256
+ const unionZod = z.union(unionTypes)
257
+ return isRequired ? unionZod : unionZod.optional()
258
+ }
259
+ }
260
+
261
+ // 6. Fallback
262
+ return isRequired ? z.any() : z.any().optional()
263
+ }
264
+
127
265
  /**
128
266
  * 刷新单个 agent 的 Python 插件提示词
129
267
  */
@@ -208,6 +346,7 @@ class PythonPluginLoader extends Plugin {
208
346
  /**
209
347
  * 注册 Python 插件的工具
210
348
  * 工具名格式:插件名_工具名(如 stock_get_price)
349
+ * 直接注册到 ExtensionExecutor,使用 ext_call({ plugin: "python", tool: "插件名_工具名", args: {...} }) 调用
211
350
  */
212
351
  _registerPythonTool(pluginName, tool) {
213
352
  if (!tool.name) return
@@ -215,38 +354,56 @@ class PythonPluginLoader extends Plugin {
215
354
  // 格式:pluginname_toolname
216
355
  const fullToolName = `${pluginName}_${tool.name}`
217
356
 
357
+ const toolDef = {
358
+ name: fullToolName,
359
+ description: tool.description || `${pluginName} 的 ${tool.name} 工具`,
360
+ inputSchema: this._parseToolParams(tool.params || {}),
361
+ execute: async (args) => {
362
+ return this._executePythonTool(pluginName, tool.name, args)
363
+ }
364
+ }
365
+
218
366
  try {
219
- this.registerTool({
220
- name: fullToolName,
221
- description: `[${pluginName}] ${tool.description || ''}`,
222
- pluginName: pluginName,
223
- inputSchema: this._parseToolParams(tool.params || {}),
224
- execute: async (args) => {
225
- return this._executePythonTool(pluginName, tool.name, args)
226
- }
227
- })
367
+ // 直接注册到 ExtensionExecutor(如果可用)
368
+ if (this._extensionExecutor) {
369
+ this._extensionExecutor.registerTool(
370
+ 'python', // 插件名,用于 ext_call 的 plugin 参数
371
+ { name: 'python', description: 'Python 插件工具', version: '1.0.0' },
372
+ toolDef
373
+ )
374
+ } else {
375
+ // 回退到基类的 registerTool
376
+ this.registerTool({
377
+ name: fullToolName,
378
+ description: `[${pluginName}] ${tool.description || ''}`,
379
+ pluginName: pluginName,
380
+ inputSchema: this._parseToolParams(tool.params || {}),
381
+ execute: toolDef.execute
382
+ })
383
+ }
228
384
  } catch (err) {
229
385
  log.error(` Failed to register tool ${fullToolName}:`, err.message)
230
386
  }
231
387
  }
232
388
 
233
389
  /**
234
- * 解析工具参数 schema
390
+ * 解析工具参数 schema - 支持多重嵌套解析
391
+ * 支持 Python 常见写法:
392
+ * - "name": "string" # 类型字符串占位符
393
+ * - "data": {"type": "object", "properties": {...}} # 完整 JSON Schema
394
+ * - "tags": ["string"] # 简单数组
395
+ * - "tags": {"type": "array", "items": "string"} # JSON Schema 数组
396
+ * - "filter": {} # 简单 dict
397
+ * - "filter": [] # 简单 list
235
398
  */
236
399
  _parseToolParams(params) {
237
- // 构建 JSON Schema 格式
238
400
  const properties = {}
239
401
  const required = []
240
402
 
241
403
  for (const [key, value] of Object.entries(params)) {
242
- let type = 'string'
243
- if (typeof value === 'boolean') type = 'boolean'
244
- else if (typeof value === 'number') type = 'number'
245
- else if (Array.isArray(value)) type = 'array'
246
- else if (typeof value === 'object') type = 'object'
247
-
404
+ const parsed = this._parseTypeValue(value)
248
405
  properties[key] = {
249
- type,
406
+ ...parsed,
250
407
  description: ''
251
408
  }
252
409
  required.push(key)
@@ -259,7 +416,256 @@ class PythonPluginLoader extends Plugin {
259
416
  }
260
417
 
261
418
  // 转换为 Zod Schema 以兼容 AI SDK
262
- return jsonSchemaToZod(jsonSchema)
419
+ return this._jsonSchemaToZod(jsonSchema)
420
+ }
421
+
422
+ /**
423
+ * 解析单个值的类型(支持递归嵌套)
424
+ * @param {any} value - Python 插件中的参数值
425
+ * @returns {object} - JSON Schema 属性对象
426
+ */
427
+ _parseTypeValue(value) {
428
+ // 1. 空值/None/null
429
+ if (value === null || value === undefined) {
430
+ return { type: 'string' }
431
+ }
432
+
433
+ // 2. 字符串类型占位符(如 "string", "number", "boolean")
434
+ if (typeof value === 'string') {
435
+ return this._parseTypeString(value)
436
+ }
437
+
438
+ // 3. JavaScript 原生类型
439
+ if (typeof value === 'boolean') {
440
+ return { type: 'boolean' }
441
+ }
442
+ if (typeof value === 'number') {
443
+ return { type: 'number' }
444
+ }
445
+
446
+ // 4. 数组
447
+ if (Array.isArray(value)) {
448
+ return this._parseArrayValue(value)
449
+ }
450
+
451
+ // 5. 对象(字典)
452
+ if (typeof value === 'object') {
453
+ return this._parseObjectValue(value)
454
+ }
455
+
456
+ // 6. Fallback
457
+ return { type: 'string' }
458
+ }
459
+
460
+ /**
461
+ * 解析类型字符串占位符
462
+ * 支持:"string", "number", "boolean", "array", "object", "any"
463
+ * 也支持嵌套简写如:"string[]", "object{name:string}"
464
+ */
465
+ _parseTypeString(str) {
466
+ const typeStringMap = {
467
+ 'string': 'string',
468
+ 'number': 'number',
469
+ 'integer': 'number',
470
+ 'boolean': 'boolean',
471
+ 'bool': 'boolean',
472
+ 'array': 'array',
473
+ 'object': 'object',
474
+ 'any': 'string'
475
+ }
476
+
477
+ const normalized = str.toLowerCase().trim()
478
+
479
+ // 直接类型映射
480
+ if (typeStringMap[normalized]) {
481
+ return { type: typeStringMap[normalized] }
482
+ }
483
+
484
+ // 数组简写:string[] -> {type: array, items: {type: string}}
485
+ if (normalized.endsWith('[]')) {
486
+ const itemType = normalized.slice(0, -2).trim()
487
+ const mappedType = typeStringMap[itemType] || 'string'
488
+ return {
489
+ type: 'array',
490
+ items: { type: mappedType }
491
+ }
492
+ }
493
+
494
+ // 对象简写:{name:string,age:number} -> {type: object, properties: {...}}
495
+ if (normalized.startsWith('{') && normalized.endsWith('}')) {
496
+ const inner = normalized.slice(1, -1)
497
+ const properties = {}
498
+ const required = []
499
+
500
+ const parts = this._splitByComma(inner)
501
+ for (const part of parts) {
502
+ const colonIdx = part.indexOf(':')
503
+ if (colonIdx > 0) {
504
+ const propName = part.substring(0, colonIdx).trim()
505
+ const propType = part.substring(colonIdx + 1).trim()
506
+ properties[propName] = this._parseTypeString(propType)
507
+ required.push(propName)
508
+ }
509
+ }
510
+
511
+ return {
512
+ type: 'object',
513
+ properties,
514
+ required
515
+ }
516
+ }
517
+
518
+ // 未知类型,默认 string
519
+ return { type: 'string' }
520
+ }
521
+
522
+ /**
523
+ * 分割字符串(考虑嵌套的括号)
524
+ */
525
+ _splitByComma(str) {
526
+ const result = []
527
+ let current = ''
528
+ let depth = 0
529
+
530
+ for (const char of str) {
531
+ if (char === '(' || char === '[' || char === '{') {
532
+ depth++
533
+ current += char
534
+ } else if (char === ')' || char === ']' || char === '}') {
535
+ depth--
536
+ current += char
537
+ } else if (char === ',' && depth === 0) {
538
+ result.push(current.trim())
539
+ current = ''
540
+ } else {
541
+ current += char
542
+ }
543
+ }
544
+
545
+ if (current.trim()) {
546
+ result.push(current.trim())
547
+ }
548
+
549
+ return result
550
+ }
551
+
552
+ /**
553
+ * 解析数组值
554
+ * 支持:
555
+ * - [] -> {type: array, items: {type: string}}
556
+ * - ["string", "number"] -> {type: array, items: {type: string, enum: [...]}}
557
+ * - {"type": "array", "items": "string"} -> 完整 JSON Schema 格式
558
+ */
559
+ _parseArrayValue(arr) {
560
+ // 空数组,默认 string 类型数组
561
+ if (arr.length === 0) {
562
+ return { type: 'array', items: { type: 'string' } }
563
+ }
564
+
565
+ // 完整 JSON Schema 格式
566
+ if (arr.length === 1 && typeof arr[0] === 'object' && arr[0].type === 'array') {
567
+ return this._parseTypeValue(arr[0])
568
+ }
569
+
570
+ // 简单同类型数组:["string", "number"] -> 收集所有类型
571
+ const types = new Set()
572
+ for (const item of arr) {
573
+ if (typeof item === 'string') {
574
+ types.add(item.toLowerCase())
575
+ }
576
+ }
577
+
578
+ // 检查是否是类型字符串数组
579
+ const typeStringMap = ['string', 'number', 'integer', 'boolean', 'bool', 'array', 'object', 'any']
580
+ const isTypeArray = types.size > 0 && [...types].every(t => typeStringMap.includes(t))
581
+
582
+ if (isTypeArray) {
583
+ // 单一类型:["string"] -> {type: array, items: {type: "string"}}
584
+ if (types.size === 1) {
585
+ const itemType = [...types][0]
586
+ const mappedType = {
587
+ 'string': 'string', 'number': 'number', 'integer': 'number',
588
+ 'boolean': 'boolean', 'bool': 'boolean', 'array': 'array', 'object': 'object', 'any': 'string'
589
+ }[itemType] || 'string'
590
+ return { type: 'array', items: { type: mappedType } }
591
+ }
592
+ // 多类型联合数组:["string", "number"] -> z.union
593
+ const zodTypes = [...types].map(t => {
594
+ const mappedType = {
595
+ 'string': 'string', 'number': 'number', 'integer': 'number',
596
+ 'boolean': 'boolean', 'bool': 'boolean', 'array': 'array', 'object': 'object', 'any': 'string'
597
+ }[t] || 'string'
598
+ return mappedType
599
+ })
600
+ // 转换为 JSON Schema 的 oneOf
601
+ return { type: 'array', items: { oneOf: zodTypes.map(t => ({ type: t })) } }
602
+ }
603
+
604
+ // 复杂数组,内部是对象
605
+ const firstObj = arr.find(item => typeof item === 'object' && item !== null)
606
+ if (firstObj) {
607
+ return { type: 'array', items: this._parseTypeValue(firstObj) }
608
+ }
609
+
610
+ // 默认返回 string 数组
611
+ return { type: 'array', items: { type: 'string' } }
612
+ }
613
+
614
+ /**
615
+ * 解析对象值
616
+ * 支持:
617
+ * - {} -> {type: object}
618
+ * - {"type": "object", "properties": {...}} -> 完整 JSON Schema
619
+ * - {"type": "object", "properties": {"name": "string", "age": "number"}} -> 递归
620
+ */
621
+ _parseObjectValue(obj) {
622
+ // 已经是完整 JSON Schema 格式
623
+ if (obj.type) {
624
+ const typeStr = typeof obj.type === 'string' ? obj.type.toLowerCase() : ''
625
+
626
+ // object 类型
627
+ if (typeStr === 'object' || typeStr === '') {
628
+ if (obj.properties) {
629
+ const properties = {}
630
+ const required = obj.required || []
631
+
632
+ for (const [key, value] of Object.entries(obj.properties)) {
633
+ properties[key] = this._parseTypeValue(value)
634
+ }
635
+
636
+ return { type: 'object', properties, required }
637
+ }
638
+ return { type: 'object' }
639
+ }
640
+
641
+ // array 类型
642
+ if (typeStr === 'array') {
643
+ const items = obj.items ? this._parseTypeValue(obj.items) : { type: 'string' }
644
+ return { type: 'array', items }
645
+ }
646
+
647
+ // 简单类型
648
+ const typeMap = {
649
+ 'string': 'string', 'number': 'number', 'integer': 'number',
650
+ 'boolean': 'boolean', 'bool': 'boolean'
651
+ }
652
+ if (typeMap[typeStr]) {
653
+ return { type: typeMap[typeStr] }
654
+ }
655
+
656
+ return { type: typeStr }
657
+ }
658
+
659
+ // 普通对象字面量,递归解析每个属性
660
+ const properties = {}
661
+ const required = []
662
+
663
+ for (const [key, value] of Object.entries(obj)) {
664
+ properties[key] = this._parseTypeValue(value)
665
+ required.push(key)
666
+ }
667
+
668
+ return { type: 'object', properties, required }
263
669
  }
264
670
 
265
671
  /**
@@ -660,23 +1066,38 @@ class PythonPluginLoader extends Plugin {
660
1066
  return { success: false, error: `Plugin '${pluginName}' not found` }
661
1067
  }
662
1068
 
663
- // 构建执行代码 - 使用 base64 避免转义问题
1069
+ // 构建执行代码 - 使用数组拼接避免缩进问题
664
1070
  const pluginDir = path.dirname(plugin.path)
665
1071
  const codeBase64 = Buffer.from(plugin.code, 'utf-8').toString('base64')
666
1072
 
667
- const execCode = `
668
- import sys
669
- import base64
670
- sys.path.insert(0, r'${pluginDir}')
671
-
672
- # 加载插件代码
673
- plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')
674
- exec(compile(plugin_code, '${pluginName}.py', 'exec'))
675
-
676
- # 执行工具
677
- result = execute_tool('${toolName}', ${JSON.stringify(params)})
678
- print(result)
679
- `
1073
+ // TOOLS 提取工具名称列表,构建路由函数
1074
+ const toolNames = (plugin.info.tools || []).map(t => t.name)
1075
+
1076
+ const execCode = [
1077
+ 'import sys',
1078
+ 'import base64',
1079
+ 'import json as _json',
1080
+ `sys.path.insert(0, r'${pluginDir}')`,
1081
+ '',
1082
+ '# 加载插件代码(使用 textwrap.dedent 去除缩进)',
1083
+ `plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')`,
1084
+ `import textwrap`,
1085
+ `exec(compile(textwrap.dedent(plugin_code), '${pluginName}.py', 'exec'))`,
1086
+ '',
1087
+ '# 工具路由函数',
1088
+ `_tool_map = {${toolNames.map(n => `"${n}": ${n}`).join(', ')}}`,
1089
+ `def execute_tool(name, params):`,
1090
+ ` if name not in _tool_map:`,
1091
+ ` return {"success": False, "error": f"Tool '{name}' not found in plugin '${pluginName}'"}`,
1092
+ ` try:`,
1093
+ ` return _tool_map[name](params)`,
1094
+ ` except Exception as e:`,
1095
+ ` return {"success": False, "error": str(e)}`,
1096
+ '',
1097
+ '# 执行工具',
1098
+ `result = execute_tool('${toolName}', ${JSON.stringify(params)})`,
1099
+ 'print(_json.dumps(result))'
1100
+ ].join('\n')
680
1101
 
681
1102
  try {
682
1103
  // 通过 python-execute 工具执行
@@ -690,13 +1111,13 @@ print(result)
690
1111
  if (pythonResult.success) {
691
1112
  try {
692
1113
  // 尝试解析 JSON 结果
693
- const parsed = JSON.parse(pythonResult.output)
1114
+ const parsed = JSON.parse(pythonResult.stdout)
694
1115
  return parsed
695
1116
  } catch {
696
- return { success: true, output: pythonResult.output }
1117
+ return { success: true, output: pythonResult.stdout }
697
1118
  }
698
1119
  } else {
699
- return { success: false, error: pythonResult.error }
1120
+ return { success: false, error: pythonResult.stderr || pythonResult.error }
700
1121
  }
701
1122
  } catch (err) {
702
1123
  return { success: false, error: err.message }