foliko 1.0.74 → 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 (238) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  28. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  29. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  30. package/.agent/ARCHITECTURE.md +288 -0
  31. package/.agent/agents/ambient-agent.md +57 -0
  32. package/.agent/agents/debugger.md +55 -0
  33. package/.agent/agents/email-assistant.md +49 -0
  34. package/.agent/agents/file-manager.md +42 -0
  35. package/.agent/agents/python-developer.md +60 -0
  36. package/.agent/agents/scheduler.md +59 -0
  37. package/.agent/agents/web-developer.md +45 -0
  38. package/.agent/data/default.json +29 -0
  39. package/.agent/data/plugins-state.json +255 -0
  40. package/.agent/mcp_config.json +4 -0
  41. package/.agent/mcp_config_updated.json +12 -0
  42. package/.agent/plugins.json +5 -0
  43. package/.agent/rules/GEMINI.md +273 -0
  44. package/.agent/rules/allow-rule.md +77 -0
  45. package/.agent/rules/log-rule.md +83 -0
  46. package/.agent/rules/security-rule.md +93 -0
  47. package/.agent/scripts/auto_preview.py +148 -0
  48. package/.agent/scripts/checklist.py +217 -0
  49. package/.agent/scripts/session_manager.py +120 -0
  50. package/.agent/scripts/verify_all.py +327 -0
  51. package/.agent/skills/api-patterns/SKILL.md +81 -0
  52. package/.agent/skills/api-patterns/api-style.md +42 -0
  53. package/.agent/skills/api-patterns/auth.md +24 -0
  54. package/.agent/skills/api-patterns/documentation.md +26 -0
  55. package/.agent/skills/api-patterns/graphql.md +41 -0
  56. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  57. package/.agent/skills/api-patterns/response.md +37 -0
  58. package/.agent/skills/api-patterns/rest.md +40 -0
  59. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  60. package/.agent/skills/api-patterns/security-testing.md +122 -0
  61. package/.agent/skills/api-patterns/trpc.md +41 -0
  62. package/.agent/skills/api-patterns/versioning.md +22 -0
  63. package/.agent/skills/app-builder/SKILL.md +75 -0
  64. package/.agent/skills/app-builder/agent-coordination.md +71 -0
  65. package/.agent/skills/app-builder/feature-building.md +53 -0
  66. package/.agent/skills/app-builder/project-detection.md +34 -0
  67. package/.agent/skills/app-builder/scaffolding.md +118 -0
  68. package/.agent/skills/app-builder/tech-stack.md +40 -0
  69. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  70. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  71. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  72. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  73. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  74. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  75. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  76. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  77. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  78. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  79. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  80. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  81. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  82. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  83. package/.agent/skills/architecture/SKILL.md +55 -0
  84. package/.agent/skills/architecture/context-discovery.md +43 -0
  85. package/.agent/skills/architecture/examples.md +94 -0
  86. package/.agent/skills/architecture/pattern-selection.md +68 -0
  87. package/.agent/skills/architecture/patterns-reference.md +50 -0
  88. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  89. package/.agent/skills/clean-code/SKILL.md +201 -0
  90. package/.agent/skills/doc.md +177 -0
  91. package/.agent/skills/frontend-design/SKILL.md +418 -0
  92. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  93. package/.agent/skills/frontend-design/color-system.md +311 -0
  94. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  95. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  96. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  97. package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
  98. package/.agent/skills/frontend-design/typography-system.md +345 -0
  99. package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
  100. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  101. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  102. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  103. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  104. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  105. package/.agent/workflows/brainstorm.md +113 -0
  106. package/.agent/workflows/create.md +59 -0
  107. package/.agent/workflows/debug.md +103 -0
  108. package/.agent/workflows/deploy.md +176 -0
  109. package/.agent/workflows/enhance.md +63 -0
  110. package/.agent/workflows/orchestrate.md +237 -0
  111. package/.agent/workflows/plan.md +89 -0
  112. package/.agent/workflows/preview.md +81 -0
  113. package/.agent/workflows/simple-test.md +42 -0
  114. package/.agent/workflows/status.md +86 -0
  115. package/.agent/workflows/structured-orchestrate.md +180 -0
  116. package/.agent/workflows/test.md +144 -0
  117. package/.agent/workflows/ui-ux-pro-max.md +296 -0
  118. package/.claude/settings.local.json +11 -1
  119. package/.editorconfig +56 -0
  120. package/.husky/pre-commit +4 -0
  121. package/.lintstagedrc +7 -0
  122. package/.prettierignore +29 -0
  123. package/.prettierrc +11 -0
  124. package/CLAUDE.md +2 -0
  125. package/README.md +64 -55
  126. package/SPEC.md +102 -61
  127. package/cli/bin/foliko.js +11 -11
  128. package/cli/src/commands/chat.js +143 -141
  129. package/cli/src/commands/list.js +93 -90
  130. package/cli/src/index.js +75 -75
  131. package/cli/src/ui/chat-ui.js +201 -199
  132. package/cli/src/utils/ansi.js +40 -40
  133. package/cli/src/utils/markdown.js +292 -296
  134. package/docker-compose.yml +1 -1
  135. package/docs/ai-sdk-optimization.md +655 -643
  136. package/docs/features.md +80 -80
  137. package/docs/quick-reference.md +49 -46
  138. package/docs/user-manual.md +411 -380
  139. package/examples/ambient-example.js +194 -196
  140. package/examples/basic.js +50 -45
  141. package/examples/bootstrap.js +121 -112
  142. package/examples/mcp-example.js +19 -16
  143. package/examples/skill-example.js +20 -20
  144. package/examples/test-chat.js +137 -135
  145. package/examples/test-mcp.js +85 -79
  146. package/examples/test-reload.js +59 -61
  147. package/examples/test-telegram.js +50 -50
  148. package/examples/test-tg-bot.js +45 -42
  149. package/examples/test-tg-simple.js +47 -46
  150. package/examples/test-tg.js +62 -62
  151. package/examples/test-think.js +43 -37
  152. package/examples/test-web-plugin.js +103 -98
  153. package/examples/test-weixin-feishu.js +103 -100
  154. package/examples/workflow.js +158 -158
  155. package/package.json +37 -3
  156. package/plugins/ai-plugin.js +102 -100
  157. package/plugins/ambient-agent/EventWatcher.js +113 -0
  158. package/plugins/ambient-agent/ExplorerLoop.js +640 -0
  159. package/plugins/ambient-agent/GoalManager.js +197 -0
  160. package/plugins/ambient-agent/Reflector.js +95 -0
  161. package/plugins/ambient-agent/StateStore.js +90 -0
  162. package/plugins/ambient-agent/constants.js +101 -0
  163. package/plugins/ambient-agent/index.js +579 -0
  164. package/plugins/audit-plugin.js +187 -187
  165. package/plugins/default-plugins.js +662 -649
  166. package/plugins/email/constants.js +64 -0
  167. package/plugins/email/handlers.js +461 -0
  168. package/plugins/email/index.js +278 -0
  169. package/plugins/email/monitor.js +269 -0
  170. package/plugins/email/parser.js +138 -0
  171. package/plugins/email/reply.js +151 -0
  172. package/plugins/email/utils.js +124 -0
  173. package/plugins/feishu-plugin.js +481 -477
  174. package/plugins/file-system-plugin.js +826 -476
  175. package/plugins/install-plugin.js +199 -197
  176. package/plugins/python-executor-plugin.js +367 -365
  177. package/plugins/python-plugin-loader.js +481 -479
  178. package/plugins/rules-plugin.js +294 -292
  179. package/plugins/scheduler-plugin.js +691 -689
  180. package/plugins/session-plugin.js +369 -367
  181. package/plugins/shell-executor-plugin.js +197 -197
  182. package/plugins/storage-plugin.js +240 -238
  183. package/plugins/subagent-plugin.js +845 -785
  184. package/plugins/telegram-plugin.js +482 -475
  185. package/plugins/think-plugin.js +345 -343
  186. package/plugins/tools-plugin.js +196 -194
  187. package/plugins/web-plugin.js +606 -604
  188. package/plugins/weixin-plugin.js +545 -538
  189. package/reports/system-health-report-20260401.md +79 -0
  190. package/skills/ambient-agent/SKILL.md +49 -39
  191. package/skills/foliko-dev/AGENTS.md +64 -61
  192. package/skills/foliko-dev/SKILL.md +125 -119
  193. package/skills/mcp-usage/SKILL.md +19 -17
  194. package/skills/python-plugin-dev/SKILL.md +16 -15
  195. package/skills/skill-guide/SKILL.md +12 -12
  196. package/skills/subagent-guide/SKILL.md +237 -0
  197. package/skills/workflow-guide/SKILL.md +90 -45
  198. package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
  199. package/skills/workflow-troubleshooting/SKILL.md +156 -79
  200. package/src/capabilities/index.js +11 -11
  201. package/src/capabilities/skill-manager.js +609 -595
  202. package/src/capabilities/workflow-engine.js +1109 -1195
  203. package/src/core/agent-chat.js +882 -735
  204. package/src/core/agent.js +892 -688
  205. package/src/core/framework.js +465 -431
  206. package/src/core/index.js +19 -19
  207. package/src/core/plugin-base.js +219 -219
  208. package/src/core/plugin-manager.js +863 -767
  209. package/src/core/provider.js +114 -111
  210. package/src/core/sub-agent-config.js +264 -0
  211. package/src/core/system-prompt-builder.js +120 -0
  212. package/src/core/tool-registry.js +517 -134
  213. package/src/core/tool-router.js +297 -216
  214. package/src/executors/executor-base.js +12 -12
  215. package/src/executors/mcp-executor.js +741 -729
  216. package/src/index.js +25 -37
  217. package/src/utils/circuit-breaker.js +301 -0
  218. package/src/utils/error-boundary.js +363 -0
  219. package/src/utils/error.js +374 -0
  220. package/src/utils/event-emitter.js +97 -97
  221. package/src/utils/id.js +133 -0
  222. package/src/utils/index.js +217 -3
  223. package/src/utils/logger.js +181 -0
  224. package/src/utils/plugin-helpers.js +90 -0
  225. package/src/utils/retry.js +122 -0
  226. package/src/utils/sandbox.js +292 -0
  227. package/test/tool-registry-validation.test.js +218 -0
  228. package/test_report.md +70 -0
  229. package/website/docs/api.html +169 -107
  230. package/website/docs/configuration.html +296 -144
  231. package/website/docs/plugin-development.html +154 -85
  232. package/website/docs/project-structure.html +110 -109
  233. package/website/docs/skill-development.html +117 -61
  234. package/website/index.html +209 -205
  235. package/website/script.js +136 -133
  236. package/website/styles.css +1 -1
  237. package/plugins/ambient-agent-plugin.js +0 -1565
  238. package/plugins/email.js +0 -1142
@@ -1,479 +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 { z } = require('zod')
10
-
11
- // JSON Schema 转换为 Zod Schema
12
- function jsonSchemaToZod(jsonSchema) {
13
- if (!jsonSchema || jsonSchema.type !== 'object') {
14
- return z.object({})
15
- }
16
-
17
- const properties = jsonSchema.properties || {}
18
- const required = jsonSchema.required || []
19
-
20
- const shape = {}
21
- for (const [key, prop] of Object.entries(properties)) {
22
- let zodType
23
- switch (prop.type) {
24
- case 'string':
25
- zodType = z.string()
26
- break
27
- case 'number':
28
- zodType = z.number()
29
- break
30
- case 'boolean':
31
- zodType = z.boolean()
32
- break
33
- case 'array':
34
- zodType = z.array(z.any())
35
- break
36
- case 'object':
37
- zodType = jsonSchemaToZod(prop)
38
- break
39
- default:
40
- zodType = z.any()
41
- }
42
- // 如果不是 required 字段,则标记为 optional
43
- if (!required.includes(key)) {
44
- zodType = zodType.optional()
45
- }
46
- shape[key] = zodType
47
- }
48
-
49
- return z.object(shape)
50
- }
51
-
52
- class PythonPluginLoader extends Plugin {
53
- constructor(config = {}) {
54
- super()
55
- this.name = 'python-plugin-loader'
56
- this.version = '1.0.0'
57
- this.description = 'Python 插件加载器,属于Python的插件'
58
-
59
- this._agentDir = config.agentDir || '.agent'
60
- this._pythonPlugins = new Map()
61
- this._framework = null
62
- this.system = true
63
- }
64
-
65
- install(framework) {
66
- this._framework = framework
67
- return this
68
- }
69
-
70
- start(framework) {
71
- // 先设置 framework 引用(供 _loadPythonPlugins 使用)
72
- this._framework = framework
73
-
74
- // 加载所有 Python 插件(会注册工具)
75
- this._loadPythonPlugins()
76
-
77
- // 注册 python_plugin 工具
78
- framework.registerTool({
79
- name: 'python_plugin',
80
- description: '执行 Python 插件工具(当工具未注册时使用)',
81
- inputSchema: z.object({
82
- plugin: z.string().describe('插件名称(不含 .py)'),
83
- tool: z.string().describe('工具名称'),
84
- params: z.record(z.any()).describe('工具参数')
85
- }),
86
- execute: async (args) => {
87
- const { plugin, tool, params } = args
88
- return this._executePythonTool(plugin, tool, params)
89
- }
90
- })
91
-
92
- // 监听 agent 创建事件,附加 Python 插件信息到系统提示词
93
- framework.on('agent:created', (agent) => {
94
- this._refreshAgentPythonPluginsPrompt(agent)
95
- })
96
-
97
- // 等待框架就绪后,刷新所有已有 agent 的 Python 插件提示词
98
- if (framework._ready) {
99
- this._refreshAllAgentsPythonPluginsPrompt(framework)
100
- } else {
101
- framework.once('framework:ready', () => {
102
- this._refreshAllAgentsPythonPluginsPrompt(framework)
103
- })
104
- }
105
-
106
- return this
107
- }
108
-
109
- /**
110
- * 构建 Python 插件描述
111
- */
112
- _buildPythonPluginsDescription() {
113
- if (this._pythonPlugins.size === 0) {
114
- return ''
115
- }
116
-
117
- let desc = '【Python 插件工具】\n'
118
- desc += '可以直接调用以下 Python 插件工具:\n\n'
119
-
120
- for (const [name, plugin] of this._pythonPlugins) {
121
- desc += `[${plugin.info.name}] ${plugin.info.description || ''}\n`
122
- if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
123
- for (const tool of plugin.info.tools) {
124
- const paramsStr = Object.keys(tool.params || {}).join(', ') || '无参数'
125
- desc += ` - ${tool.name}(${paramsStr}): ${tool.description || '无描述'}\n`
126
- }
127
- }
128
- desc += '\n'
129
- }
130
-
131
- desc += '调用格式:python_plugin({ plugin: "插件名", tool: "工具名", params: {...} })\n'
132
- return desc.trim()
133
- }
134
-
135
- /**
136
- * 刷新单个 agent 的 Python 插件提示词
137
- */
138
- _refreshAgentPythonPluginsPrompt(agent) {
139
- const existingPrompt = agent._originalPrompt || ''
140
- if (existingPrompt.includes('[Python 插件工具]')) {
141
- return
142
- }
143
-
144
- const pyDesc = this._buildPythonPluginsDescription()
145
- if (!pyDesc) return
146
-
147
- // Python 插件描述追加到系统提示词
148
- agent.setSystemPrompt(existingPrompt + '\n\n' + pyDesc)
149
- }
150
-
151
- /**
152
- * 刷新所有 agent 的 Python 插件提示词
153
- */
154
- _refreshAllAgentsPythonPluginsPrompt(framework) {
155
- const visited = new Set()
156
-
157
- const traverse = (agent) => {
158
- if (!agent || visited.has(agent)) return
159
- visited.add(agent)
160
- this._refreshAgentPythonPluginsPrompt(agent)
161
-
162
- // 递归处理子 agent
163
- const subAgents = agent.getSubAgents?.() || agent._subAgents || new Map()
164
- for (const [name, subAgentInfo] of subAgents) {
165
- traverse(subAgentInfo.agent)
166
- }
167
- }
168
-
169
- const agents = framework._agents || []
170
- for (const agent of agents) {
171
- traverse(agent)
172
- }
173
- }
174
-
175
- /**
176
- * 加载所有 Python 插件
177
- */
178
- _loadPythonPlugins() {
179
- const pluginsDir = path.join(this._agentDir, 'plugins')
180
- if (!fs.existsSync(pluginsDir)) {
181
- return
182
- }
183
-
184
- const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.py'))
185
-
186
- for (const file of files) {
187
- try {
188
- const pluginPath = path.join(pluginsDir, file)
189
- const pluginName = file.replace('.py', '')
190
-
191
- const pluginInfo = this._loadPythonPluginMeta(pluginPath)
192
- if (pluginInfo) {
193
- this._pythonPlugins.set(pluginName, {
194
- name: pluginName,
195
- path: pluginPath,
196
- info: pluginInfo,
197
- code: fs.readFileSync(pluginPath, 'utf-8')
198
- })
199
- console.log(`[PythonPluginLoader] Loaded: ${pluginName}`)
200
-
201
- // 注册插件的每个工具
202
- if (pluginInfo.tools && Array.isArray(pluginInfo.tools)) {
203
- for (const tool of pluginInfo.tools) {
204
- this._registerPythonTool(pluginName, tool)
205
- }
206
- }
207
- }
208
- } catch (err) {
209
- console.error(`[PythonPluginLoader] Failed to load ${file}:`, err.message)
210
- }
211
- }
212
-
213
- console.log(`[PythonPluginLoader] Total Python plugins: ${this._pythonPlugins.size}`)
214
- }
215
-
216
- /**
217
- * 注册 Python 插件的工具到框架
218
- */
219
- _registerPythonTool(pluginName, tool) {
220
- if (!tool.name) return
221
-
222
- try {
223
- this._framework.registerTool({
224
- name: tool.name,
225
- description: tool.description || `${tool.name} (from ${pluginName})`,
226
- inputSchema: this._parseToolParams(tool.params || {}),
227
- execute: async (args) => {
228
- return this._executePythonTool(pluginName, tool.name, args)
229
- }
230
- })
231
- //console.log(`[PythonPluginLoader] Registered tool: ${tool.name}`)
232
- } catch (err) {
233
- console.error(`[PythonPluginLoader] Failed to register tool ${tool.name}:`, err.message)
234
- }
235
- }
236
-
237
- /**
238
- * 解析工具参数 schema
239
- */
240
- _parseToolParams(params) {
241
- // 构建 JSON Schema 格式
242
- const properties = {}
243
- const required = []
244
-
245
- for (const [key, value] of Object.entries(params)) {
246
- let type = 'string'
247
- if (typeof value === 'boolean') type = 'boolean'
248
- else if (typeof value === 'number') type = 'number'
249
- else if (Array.isArray(value)) type = 'array'
250
- else if (typeof value === 'object') type = 'object'
251
-
252
- properties[key] = {
253
- type,
254
- description: ''
255
- }
256
- required.push(key)
257
- }
258
-
259
- const jsonSchema = {
260
- type: 'object',
261
- properties,
262
- required
263
- }
264
-
265
- // 转换为 Zod Schema 以兼容 AI SDK
266
- return jsonSchemaToZod(jsonSchema)
267
- }
268
-
269
- /**
270
- * 加载单个 Python 插件的元信息
271
- */
272
- _loadPythonPluginMeta(pluginPath) {
273
- const code = fs.readFileSync(pluginPath, 'utf-8')
274
-
275
- // 找到 plugin_info = {
276
- const startIdx = code.indexOf('plugin_info')
277
- if (startIdx === -1) {
278
- console.warn(`[PythonPluginLoader] ${path.basename(pluginPath)}: no plugin_info found`)
279
- return null
280
- }
281
-
282
- // 从 plugin_info 后开始,找到第一个 { 的位置
283
- const braceStart = code.indexOf('{', startIdx)
284
- if (braceStart === -1) return null
285
-
286
- // 使用栈匹配找到对应的 }
287
- let braceCount = 0
288
- let endIdx = -1
289
- for (let i = braceStart; i < code.length; i++) {
290
- if (code[i] === '{') braceCount++
291
- else if (code[i] === '}') {
292
- braceCount--
293
- if (braceCount === 0) {
294
- endIdx = i
295
- break
296
- }
297
- }
298
- }
299
-
300
- if (endIdx === -1) {
301
- console.warn(`[PythonPluginLoader] ${path.basename(pluginPath)}: unclosed brace`)
302
- return null
303
- }
304
-
305
- try {
306
- // 提取并清理 plugin_info 内容
307
- let infoStr = code.substring(braceStart, endIdx + 1)
308
-
309
- // 移除 Python 注释
310
- infoStr = infoStr.replace(/#.*$/gm, '')
311
-
312
- // 转换为 JS 兼容的格式
313
- infoStr = infoStr
314
- .replace(/True/g, 'true')
315
- .replace(/False/g, 'false')
316
- .replace(/None/g, 'null')
317
- .replace(/'/g, '"')
318
-
319
- const info = JSON.parse(infoStr)
320
- return info
321
- } catch (err) {
322
- console.warn(`[PythonPluginLoader] Failed to parse ${path.basename(pluginPath)}:`, err.message)
323
- return null
324
- }
325
- }
326
-
327
- /**
328
- * 解析单个值
329
- */
330
- _parseValue(str) {
331
- str = str.trim()
332
- if (!str) return null
333
-
334
- // 字典
335
- if (str.startsWith('{')) {
336
- return this._parseDict(str)
337
- }
338
-
339
- // 字符串
340
- if (str.startsWith('"') || str.startsWith("'")) {
341
- return str.slice(1, -1)
342
- }
343
-
344
- // 布尔值
345
- if (str === 'True') return true
346
- if (str === 'False') return false
347
- if (str === 'None') return null
348
-
349
- // 数字
350
- if (!isNaN(str)) return Number(str)
351
-
352
- return str
353
- }
354
-
355
- /**
356
- * 简单解析 Python 字典为 JS 对象
357
- */
358
- _parseDict(str) {
359
- // 移除注释并清理
360
- str = str.replace(/#.*$/gm, '').trim()
361
- if (!str) return {}
362
-
363
- const result = {}
364
- let i = 0
365
- let currentKey = null
366
- let currentValue = ''
367
- let inString = false
368
- let stringChar = null
369
-
370
- while (i < str.length) {
371
- const char = str[i]
372
-
373
- // 处理字符串
374
- if ((char === '"' || char === "'") && str[i-1] !== '\\') {
375
- if (!inString) {
376
- inString = true
377
- stringChar = char
378
- } else if (char === stringChar) {
379
- inString = false
380
- stringChar = null
381
- }
382
- }
383
-
384
- // 如果不在字符串内
385
- if (!inString) {
386
- // 找到键
387
- if (currentKey === null && char === ':') {
388
- currentKey = currentValue.trim().replace(/^["']|["']$/g, '')
389
- currentValue = ''
390
- }
391
- // 找到值的结尾(逗号或右括号)
392
- else if (char === ',' || char === '}') {
393
- if (currentKey !== null) {
394
- const value = currentValue.trim().replace(/,$/, '')
395
- result[currentKey] = this._parseValue(value)
396
- }
397
- currentKey = null
398
- currentValue = ''
399
- } else {
400
- currentValue += char
401
- }
402
- } else {
403
- currentValue += char
404
- }
405
-
406
- i++
407
- }
408
-
409
- return result
410
- }
411
-
412
- /**
413
- * 执行 Python 插件工具
414
- */
415
- async _executePythonTool(pluginName, toolName, params) {
416
- const plugin = this._pythonPlugins.get(pluginName)
417
- if (!plugin) {
418
- return { success: false, error: `Plugin '${pluginName}' not found` }
419
- }
420
-
421
- // 构建执行代码 - 使用 base64 避免转义问题
422
- const pluginDir = path.dirname(plugin.path)
423
- const codeBase64 = Buffer.from(plugin.code, 'utf-8').toString('base64')
424
-
425
- const execCode = `
426
- import sys
427
- import base64
428
- sys.path.insert(0, r'${pluginDir}')
429
-
430
- # 加载插件代码
431
- plugin_code = base64.b64decode('${codeBase64}').decode('utf-8')
432
- exec(compile(plugin_code, '${pluginName}.py', 'exec'))
433
-
434
- # 执行工具
435
- result = execute_tool('${toolName}', ${JSON.stringify(params)})
436
- print(result)
437
- `
438
-
439
- try {
440
- // 通过 python-execute 工具执行
441
- const pythonExe = this._framework.toolRegistry._tools.get('python-execute')
442
- if (!pythonExe) {
443
- return { success: false, error: 'python-execute tool not found. Please install python-executor-plugin.' }
444
- }
445
-
446
- const pythonResult = await pythonExe.execute({ code: execCode })
447
-
448
- if (pythonResult.success) {
449
- try {
450
- // 尝试解析 JSON 结果
451
- const parsed = JSON.parse(pythonResult.output)
452
- return parsed
453
- } catch {
454
- return { success: true, output: pythonResult.output }
455
- }
456
- } else {
457
- return { success: false, error: pythonResult.error }
458
- }
459
- } catch (err) {
460
- return { success: false, error: err.message }
461
- }
462
- }
463
-
464
- /**
465
- * 获取所有已加载的 Python 插件
466
- */
467
- getPythonPlugins() {
468
- return Array.from(this._pythonPlugins.values())
469
- }
470
-
471
- /**
472
- * 获取 Python 插件信息
473
- */
474
- getPythonPlugin(name) {
475
- return this._pythonPlugins.get(name)
476
- }
477
- }
478
-
479
- 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 }