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,767 +1,863 @@
1
- /**
2
- * PluginManager 插件管理器
3
- * 负责插件的加载、卸载、重载
4
- */
5
-
6
- const { Plugin } = require('./plugin-base')
7
- const fs = require('fs')
8
- const path = require('path')
9
-
10
- class PluginManager {
11
- /**
12
- * @param {Framework} framework - 框架实例
13
- */
14
- constructor(framework) {
15
- this.framework = framework
16
- this._plugins = new Map()
17
- this._loading = false
18
- this._stateFile = path.join(process.cwd(), '.agent', 'data', 'plugins-state.json')
19
- this._knownPlugins = new Set() // 已知插件列表(包括未加载的)
20
- }
21
-
22
- /**
23
- * 获取状态文件路径
24
- */
25
- _getStateFile() {
26
- const dir = path.dirname(this._stateFile)
27
- if (!fs.existsSync(dir)) {
28
- fs.mkdirSync(dir, { recursive: true })
29
- }
30
- return this._stateFile
31
- }
32
-
33
- /**
34
- * 保存插件状态到文件
35
- * 注意:AI 插件配置不保存(从环境变量和命令行获取)
36
- */
37
- _saveState() {
38
- try {
39
- const stateFile = this._getStateFile()
40
- // 读取现有状态(如果存在),保留原有内容
41
- let state = {}
42
- if (fs.existsSync(stateFile)) {
43
- try {
44
- state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'))
45
- } catch (e) {
46
- // 文件损坏,使用空对象
47
- state = {}
48
- }
49
- }
50
-
51
- // 合并新状态到现有状态
52
- for (const [name, entry] of this._plugins) {
53
- // AI 配置不保存,每次从环境变量和命令行获取
54
- if (name === 'ai') {
55
- state[name] = { enabled: entry.enabled }
56
- } else {
57
- state[name] = {
58
- ...(state[name] || {}), // 保留现有配置
59
- enabled: entry.enabled,
60
- config: entry.instance?.config || {}
61
- }
62
- }
63
- }
64
-
65
- fs.writeFileSync(stateFile, JSON.stringify(state, null, 2))
66
- } catch (err) {
67
- console.error('[PluginManager] Failed to save state:', err.message)
68
- }
69
- }
70
-
71
- /**
72
- * 加载插件状态从文件
73
- */
74
- _loadState() {
75
- try {
76
- const stateFile = this._getStateFile()
77
- if (fs.existsSync(stateFile)) {
78
- const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'))
79
- //console.log('[PluginManager] Loaded plugin state from file')
80
- return state
81
- }
82
- } catch (err) {
83
- console.error('[PluginManager] Failed to load state:', err.message)
84
- }
85
- return {}
86
- }
87
-
88
- /**
89
- * 注册插件(不加载)
90
- * @param {Plugin|Object} plugin - 插件实例或定义
91
- */
92
- register(plugin, options = {}) {
93
- if (!plugin.name) {
94
- throw new Error('Plugin must have a name')
95
- }
96
-
97
- let pluginInstance = plugin
98
- if (!(plugin instanceof Plugin)) {
99
- // 从对象创建插件实例
100
- pluginInstance = this._createFromObject(plugin)
101
- }
102
-
103
- // 加载保存的状态
104
- const savedState = this._loadState()
105
- const savedEnabled = savedState[pluginInstance.name]?.enabled
106
- const savedConfig = savedState[pluginInstance.name]?.config
107
-
108
- // 恢复保存的配置到插件实例(AI 插件不恢复配置,从环境变量获取)
109
- if (savedConfig && pluginInstance.config && pluginInstance.name !== 'ai') {
110
- pluginInstance.config = { ...pluginInstance.config, ...savedConfig }
111
- }
112
-
113
- // 系统插件强制启用,不能禁用
114
- // 普通插件:state 文件有记录则用 state,否则用插件默认配置
115
- let enabled
116
- if (pluginInstance.system) {
117
- enabled = true
118
- } else if (savedEnabled !== undefined) {
119
- enabled = savedEnabled
120
- } else {
121
- enabled = pluginInstance.enabled !== undefined ? pluginInstance.enabled : true
122
- }
123
-
124
- this._plugins.set(pluginInstance.name, {
125
- instance: pluginInstance,
126
- status: 'registered',
127
- enabled
128
- })
129
-
130
- this.framework.emit('plugin:registered', pluginInstance)
131
- return this
132
- }
133
-
134
- /**
135
- * 加载插件
136
- * @param {Plugin|Object} plugin - 插件实例或定义
137
- * @param {Object} options - 加载选项
138
- * @param {boolean} options.forceEnabled - 强制启用插件,忽略 state 文件的 disabled 状态(用于 .agent/plugins 目录的插件)
139
- */
140
- async load(plugin, options = {}) {
141
- if (this._loading) {
142
- throw new Error('Cannot load plugin during another load operation')
143
- }
144
-
145
- this._loading = true
146
- try {
147
- let pluginInstance = plugin
148
-
149
- if (!(plugin instanceof Plugin)) {
150
- pluginInstance = this._createFromObject(plugin)
151
- }
152
-
153
- // 如果已注册,使用已注册的实例和状态
154
- const existing = this._plugins.get(pluginInstance.name)
155
- if (existing) {
156
- pluginInstance = existing.instance
157
-
158
- // 如果插件被禁用,跳过加载
159
- if (!existing.enabled) {
160
- console.log(`[PluginManager] Plugin '${pluginInstance.name}' is disabled`)
161
- return pluginInstance
162
- }
163
-
164
- // 如果已加载且已启动,直接返回
165
- if (existing.status === 'loaded' && pluginInstance._started) {
166
- console.warn(`[PluginManager] Plugin '${pluginInstance.name}' already loaded`)
167
- return pluginInstance
168
- }
169
-
170
- // 如果已加载但未启动(重载场景),直接启动
171
- if (existing.status === 'loaded' && !pluginInstance._started) {
172
- try {
173
- if (typeof pluginInstance.start === 'function') {
174
- await pluginInstance.start(this.framework)
175
- pluginInstance._started = true
176
- }
177
- } catch (err) {
178
- console.error(`[PluginManager] Start failed for '${pluginInstance.name}':`, err.message)
179
- }
180
- this.framework.emit('plugin:loaded', pluginInstance)
181
- return pluginInstance
182
- }
183
- } else {
184
- // 未注册,先注册
185
- this.register(pluginInstance, options)
186
- }
187
-
188
- const entry = this._plugins.get(pluginInstance.name)
189
-
190
- // 注册后再次检查 enabled 状态
191
- if (!entry.enabled) {
192
- //console.log(`[PluginManager] Plugin '${pluginInstance.name}' is disabled, skipping install`)
193
- return pluginInstance
194
- }
195
-
196
- // 调用 install
197
- try {
198
- await entry.instance.install(this.framework)
199
- } catch (err) {
200
- console.error(`[PluginManager] Install failed for '${pluginInstance.name}':`, err.message)
201
- throw err
202
- }
203
-
204
- entry.status = 'loaded'
205
-
206
- // 如果处于 bootstrap 模式,由外部统一调用 startAll()
207
- // 否则直接启动
208
- if (this._bootstrapping) {
209
- // bootstrap 模式下不启动,由 bootstrapDefaults 统一启动
210
- } else {
211
- // bootstrap 模式下直接启动
212
- try {
213
- if (typeof pluginInstance.start === 'function') {
214
- await pluginInstance.start(this.framework)
215
- pluginInstance._started = true
216
- }
217
- } catch (err) {
218
- console.error(`[PluginManager] Start failed for '${pluginInstance.name}':`, err.message)
219
- }
220
- }
221
-
222
- this.framework.emit('plugin:loaded', pluginInstance)
223
-
224
- // 保存状态(创建或更新 state 文件)
225
- this._saveState()
226
-
227
- return pluginInstance
228
- } finally {
229
- this._loading = false
230
- }
231
- }
232
-
233
- /**
234
- * 设置 bootstrap 模式
235
- */
236
- setBootstrapping(value) {
237
- this._bootstrapping = value
238
- }
239
-
240
- /**
241
- * 卸载插件
242
- * @param {string} name - 插件名称
243
- */
244
- async unload(name) {
245
- const entry = this._plugins.get(name)
246
- if (!entry) {
247
- return false
248
- }
249
-
250
- const { instance } = entry
251
-
252
- // 调用 uninstall
253
- try {
254
- await instance.uninstall(this.framework)
255
- } catch (err) {
256
- console.error(`[PluginManager] Uninstall error for '${name}':`, err.message)
257
- }
258
-
259
- entry.status = 'unloaded'
260
- this.framework.emit('plugin:unloaded', instance)
261
- return true
262
- }
263
-
264
- /**
265
- * 重载插件
266
- * @param {string} name - 插件名称
267
- */
268
- async reload(name) {
269
- const entry = this._plugins.get(name)
270
- if (!entry) {
271
- throw new Error(`Plugin '${name}' not found`)
272
- }
273
-
274
- const { instance } = entry
275
-
276
- // 调用 reload
277
- try {
278
- await instance.reload(this.framework)
279
- this.framework.emit('plugin:reloaded', instance)
280
- } catch (err) {
281
- console.error(`[PluginManager] Reload error for '${name}':`, err.message)
282
- throw err
283
- }
284
-
285
- return instance
286
- }
287
-
288
- /**
289
- * 重载所有插件
290
- */
291
- async reloadAll() {
292
- // 1. 重置所有插件的启动标志
293
- for (const entry of this._plugins.values()) {
294
- entry.instance._started = false
295
- }
296
-
297
- // 2. 扫描 .agent/plugins 目录,加载新插件
298
- await this._discoverCustomPlugins()
299
-
300
- // 3. 启动所有未启动的插件
301
- await this.startAll()
302
- }
303
-
304
- /**
305
- * 解析插件路径
306
- * 支持两种结构:
307
- * 1. 文件夹结构: .agent/plugins/my-plugin/index.js
308
- * 2. 单文件结构: .agent/plugins/my-plugin.js
309
- * @param {string} pluginsDir - 插件目录
310
- * @param {string} name - 插件名称
311
- * @returns {{path: string, type: 'folder'|'file'}|null} 插件路径和类型
312
- * @private
313
- */
314
- _resolvePluginPath(pluginsDir, name) {
315
- const folderPath = path.join(pluginsDir, name)
316
- const filePath = path.join(pluginsDir, `${name}.js`)
317
-
318
- // 文件夹优先
319
- if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
320
- const pkgPath = path.join(folderPath, 'package.json')
321
- if (fs.existsSync(pkgPath)) {
322
- try {
323
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
324
- const main = pkg.main || 'index.js'
325
- const mainPath = path.join(folderPath, main)
326
- if (fs.existsSync(mainPath)) {
327
- return { path: mainPath, type: 'folder' }
328
- }
329
- } catch (err) {
330
- console.warn(`[_resolvePluginPath] Failed to parse package.json for ${name}:`, err.message)
331
- }
332
- }
333
- // 默认加载 index.js
334
- const indexPath = path.join(folderPath, 'index.js')
335
- if (fs.existsSync(indexPath)) {
336
- return { path: indexPath, type: 'folder' }
337
- }
338
- console.warn(`[_resolvePluginPath] No entry point found for plugin folder: ${name}`)
339
- return null
340
- }
341
-
342
- // 单文件回退
343
- if (fs.existsSync(filePath)) {
344
- return { path: filePath, type: 'file' }
345
- }
346
-
347
- return null
348
- }
349
-
350
- /**
351
- * 扫描插件目录,返回所有插件名称
352
- * @param {string} pluginsDir - 插件目录
353
- * @returns {string[]} 插件名称列表
354
- * @private
355
- */
356
- _scanPluginNames(pluginsDir) {
357
- if (!fs.existsSync(pluginsDir)) {
358
- return []
359
- }
360
-
361
- const names = new Set()
362
- const entries = fs.readdirSync(pluginsDir, { withFileTypes: true })
363
-
364
- for (const entry of entries) {
365
- if (entry.isDirectory()) {
366
- // 文件夹插件
367
- names.add(entry.name)
368
- } else if (entry.isFile() && entry.name.endsWith('.js')) {
369
- // 单文件插件(排除与文件夹同名的)
370
- const baseName = entry.name.replace(/\.js$/, '')
371
- if (!names.has(baseName)) {
372
- names.add(baseName)
373
- }
374
- }
375
- }
376
-
377
- return Array.from(names)
378
- }
379
-
380
- /**
381
- * 发现并加载自定义插件
382
- * @private
383
- */
384
- async _discoverCustomPlugins() {
385
- const pluginsDir = path.resolve(process.cwd(), '.agent', 'plugins')
386
- if (!fs.existsSync(pluginsDir)) {
387
- return
388
- }
389
-
390
- // 扫描所有插件名称(支持文件夹和单文件)
391
- const pluginNames = this._scanPluginNames(pluginsDir)
392
-
393
- // pluginsDir 推导 agentDir(pluginsDir = <agentDir>/plugins)
394
- const agentDir = path.dirname(pluginsDir)
395
- const agentNodeModules = path.join(agentDir, 'node_modules')
396
-
397
- for (const pluginName of pluginNames) {
398
- try {
399
- const resolved = this._resolvePluginPath(pluginsDir, pluginName)
400
- if (!resolved) {
401
- console.warn(`[PluginManager] Cannot resolve plugin: ${pluginName}`)
402
- continue
403
- }
404
-
405
- const { path: pluginPath, type } = resolved
406
-
407
- // 添加模块路径到搜索路径(优先级从高到低)
408
- const modulePathsToAdd = [
409
- agentNodeModules, // .agent/node_modules(项目本地安装的包)
410
- path.join(__dirname, '..', '..', 'node_modules') // 全局安装的包
411
- ]
412
- for (const mp of modulePathsToAdd) {
413
- if (fs.existsSync(mp) && !module.paths.includes(mp)) {
414
- module.paths.unshift(mp)
415
- }
416
- }
417
-
418
- // 清除缓存
419
- delete require.cache[require.resolve(pluginPath)]
420
- const pluginModule = require(pluginPath)
421
-
422
- let plugin
423
- if (typeof pluginModule === 'function') {
424
- plugin = pluginModule
425
- } else if (pluginModule.default) {
426
- plugin = pluginModule.default
427
- } else {
428
- plugin = pluginModule
429
- }
430
-
431
- // 获取插件名称
432
- let resolvedPluginName
433
- try {
434
- const tempPlugin = plugin.prototype instanceof require('./plugin-base')
435
- ? new plugin() : (typeof plugin === 'function' ? plugin() : plugin)
436
- resolvedPluginName = tempPlugin.name || pluginName
437
- } catch {
438
- resolvedPluginName = pluginName
439
- }
440
-
441
- // 如果插件已加载且已启动,跳过
442
- if (this.has(resolvedPluginName) && this.get(resolvedPluginName)?._started) {
443
- continue
444
- }
445
-
446
- console.log(`[PluginManager] Loading new plugin: ${pluginName} (${type})`)
447
- // .agent/plugins 目录下的插件默认强制启用,不受 state 文件影响
448
- await this.load(plugin, { forceEnabled: true })
449
- } catch (err) {
450
- console.error(`[PluginManager] Failed to load plugin ${pluginName}:`, err.message)
451
- }
452
- }
453
- }
454
-
455
- /**
456
- * 获取插件
457
- * @param {string} name - 插件名称
458
- */
459
- get(name) {
460
- return this._plugins.get(name)?.instance
461
- }
462
-
463
- /**
464
- * 获取所有已加载且已启用的插件
465
- */
466
- getAll() {
467
- return Array.from(this._plugins.values())
468
- .filter(e => e.status === 'loaded' && e.enabled)
469
- .map(e => ({ name: e.instance.name, instance: e.instance }))
470
- }
471
-
472
- /**
473
- * 注册一个已知插件(但不加载)
474
- * 用于显示所有可用插件列表
475
- * @param {string} name - 插件名称
476
- * @param {Object} info - 插件信息
477
- */
478
- registerKnownPlugin(name, info = {}) {
479
- this._knownPlugins.add(name)
480
- // 如果插件还没注册过,记录它的信息
481
- if (!this._plugins.has(name)) {
482
- this._plugins.set(name, {
483
- instance: { name, ...info },
484
- status: 'known',
485
- enabled: info.enabled !== undefined ? info.enabled : false
486
- })
487
- }
488
- }
489
-
490
- /**
491
- * 获取所有已知插件(包括未加载的)
492
- * @returns {Array} 插件列表,包含 name, status, enabled
493
- */
494
- getAllKnown() {
495
- const result = []
496
- for (const name of this._knownPlugins) {
497
- const entry = this._plugins.get(name)
498
- result.push({
499
- name,
500
- status: entry?.status || 'unknown',
501
- enabled: entry?.enabled || false,
502
- version: entry?.instance?.version,
503
- system: entry?.instance?.system || false
504
- })
505
- }
506
- // 也加入已加载但不在 knownPlugins 中的
507
- for (const [name, entry] of this._plugins) {
508
- if (!result.find(p => p.name === name)) {
509
- result.push({
510
- name,
511
- status: entry.status,
512
- enabled: entry.enabled,
513
- version: entry.instance?.version,
514
- system: entry.instance?.system || false
515
- })
516
- }
517
- }
518
- return result
519
- }
520
-
521
- /**
522
- * 检查插件是否存在
523
- * @param {string} name - 插件名称
524
- */
525
- has(name) {
526
- return this._plugins.has(name) || this._knownPlugins.has(name)
527
- }
528
-
529
- /**
530
- * 检查插件是否已加载
531
- * @param {string} name - 插件名称
532
- */
533
- isLoaded(name) {
534
- return this._plugins.get(name)?.status === 'loaded'
535
- }
536
-
537
- /**
538
- * 检查插件是否启用
539
- * @param {string} name - 插件名称
540
- */
541
- isEnabled(name) {
542
- return this._plugins.get(name)?.enabled === true
543
- }
544
-
545
- /**
546
- * 启用插件
547
- * @param {string} name - 插件名称
548
- */
549
- async enable(name) {
550
- const entry = this._plugins.get(name)
551
- if (!entry) {
552
- throw new Error(`Plugin '${name}' not found`)
553
- }
554
-
555
- // 系统插件不能被禁用,所以启用没有意义
556
- if (entry.instance?.system) {
557
- throw new Error(`Plugin '${name}' is a system plugin, cannot be disabled`)
558
- }
559
-
560
- if (entry.enabled) {
561
- console.log(`[PluginManager] Plugin '${name}' already enabled`)
562
- return
563
- }
564
-
565
- entry.enabled = true
566
- // 同步更新插件实例的 enabled 属性,避免 load() 时被跳过
567
- if (entry.instance) {
568
- entry.instance.enabled = true
569
- }
570
-
571
- // 如果插件已加载,尝试重新启动
572
- if (entry.status === 'loaded') {
573
- try {
574
- // 如果之前已经启动过,先调用 stop 停止旧实例
575
- if (entry.instance._started) {
576
- if (typeof entry.instance.stop === 'function') {
577
- await entry.instance.stop()
578
- } else if (typeof entry.instance.stopBot === 'function') {
579
- await entry.instance.stopBot()
580
- }
581
- }
582
- // 调用 reload 让插件重新初始化(会调用 install 和 start)
583
- await this.reload(name)
584
- } catch (err) {
585
- console.error(`[PluginManager] Enable/reload failed for '${name}':`, err.message)
586
- }
587
- } else if (entry.status === 'registered' || entry.status === 'known') {
588
- // 插件只注册过但未加载,现在加载它
589
- try {
590
- await this.load(entry.instance)
591
- } catch (err) {
592
- console.error(`[PluginManager] Enable/load failed for '${name}':`, err.message)
593
- }
594
- }
595
-
596
- this.framework.emit('plugin:enabled', entry.instance)
597
- this._saveState()
598
- console.log(`[PluginManager] Plugin '${name}' enabled`)
599
- }
600
-
601
- /**
602
- * 禁用插件
603
- * @param {string} name - 插件名称
604
- */
605
- async disable(name) {
606
- const entry = this._plugins.get(name)
607
- if (!entry) {
608
- throw new Error(`Plugin '${name}' not found`)
609
- }
610
-
611
- // 系统插件不能被禁用
612
- if (entry.instance?.system) {
613
- throw new Error(`Plugin '${name}' is a system plugin, cannot be disabled`)
614
- }
615
-
616
- if (!entry.enabled) {
617
- console.log(`[PluginManager] Plugin '${name}' already disabled`)
618
- return
619
- }
620
-
621
- entry.enabled = false
622
- // 同步更新插件实例的 enabled 属性
623
- if (entry.instance) {
624
- entry.instance.enabled = false
625
- }
626
-
627
- // 如果插件正在运行,停止它
628
- if (entry.instance._started) {
629
- try {
630
- // 优先调用 stop 方法,其次调用 stopBot 方法
631
- if (typeof entry.instance.stop === 'function') {
632
- await entry.instance.stop()
633
- } else if (typeof entry.instance.stopBot === 'function') {
634
- await entry.instance.stopBot()
635
- }
636
- entry.instance._started = false
637
- } catch (err) {
638
- console.error(`[PluginManager] Stop failed for '${name}':`, err.message)
639
- }
640
- }
641
-
642
- this.framework.emit('plugin:disabled', entry.instance)
643
- this._saveState()
644
- console.log(`[PluginManager] Plugin '${name}' disabled`)
645
- }
646
-
647
- /**
648
- * 更新插件配置
649
- * @param {string} name - 插件名称
650
- * @param {Object} config - 新配置(会合并到现有配置)
651
- */
652
- updatePluginConfig(name, config) {
653
- const entry = this._plugins.get(name)
654
- if (!entry) {
655
- throw new Error(`Plugin '${name}' not found`)
656
- }
657
-
658
- if (!entry.instance.config) {
659
- entry.instance.config = {}
660
- }
661
-
662
- // 合并配置
663
- entry.instance.config = { ...entry.instance.config, ...config }
664
-
665
- // 保存状态
666
- this._saveState()
667
- console.log(`[PluginManager] Plugin '${name}' config updated`)
668
-
669
- return entry.instance.config
670
- }
671
-
672
- /**
673
- * 启动所有已加载但未启动的插件(按优先级排序)
674
- */
675
- async startAll() {
676
- const entries = Array.from(this._plugins.values())
677
- .filter(e => e.status === 'loaded' && e.enabled) // 只启动已启用且已加载的插件
678
- .sort((a, b) => (a.instance.priority || 100) - (b.instance.priority || 100))
679
-
680
- for (const entry of entries) {
681
- const instance = entry.instance
682
- // 跳过已经启动过的插件
683
- if (instance._started) {
684
- continue
685
- }
686
- try {
687
- if (typeof instance.start === 'function') {
688
- await instance.start(this.framework)
689
- instance._started = true
690
- }
691
- } catch (err) {
692
- console.error(`[PluginManager] Start failed for '${instance.name}':`, err.message)
693
- }
694
- }
695
- }
696
-
697
- /**
698
- * 启动所有已加载插件
699
- * @private
700
- */
701
- async _startAll() {
702
- const loaded = this.getAll()
703
- .sort((a, b) => (a.instance.priority || 100) - (b.instance.priority || 100))
704
-
705
- for (const { instance } of loaded) {
706
- try {
707
- if (typeof instance.start === 'function') {
708
- await instance.start(this.framework)
709
- }
710
- } catch (err) {
711
- console.error(`[PluginManager] Start failed for '${instance.name}':`, err.message)
712
- }
713
- }
714
- }
715
-
716
- /**
717
- * 从对象创建插件实例
718
- * @private
719
- */
720
- _createFromObject(obj) {
721
- const { Plugin } = require('./plugin-base')
722
-
723
- // 如果是类(构造函数),直接实例化
724
- if (typeof obj === 'function') {
725
- // 检查是否是 Plugin 的子类(通过 prototype chain)
726
- if (obj.prototype instanceof Plugin) {
727
- return new obj()
728
- }
729
- // 否则是工厂函数,调用它获取类或实例
730
- const result = obj(Plugin)
731
- // 递归处理返回值
732
- if (typeof result === 'function' && result.prototype instanceof Plugin) {
733
- return new result()
734
- }
735
- return result
736
- }
737
-
738
- // 支持对象形式: { name, version, install, start, ... }
739
- class AnonymousPlugin extends Plugin {
740
- constructor() {
741
- super()
742
- this.name = obj.name
743
- this.version = obj.version || '1.0.0'
744
- this.description = obj.description || ''
745
- this.priority = obj.priority || 100
746
-
747
- // 如果提供了 install/start/reload/uninstall 方法,绑定它们
748
- if (typeof obj.install === 'function') {
749
- this.install = obj.install.bind(this)
750
- }
751
- if (typeof obj.start === 'function') {
752
- this.start = obj.start.bind(this)
753
- }
754
- if (typeof obj.reload === 'function') {
755
- this.reload = obj.reload.bind(this)
756
- }
757
- if (typeof obj.uninstall === 'function') {
758
- this.uninstall = obj.uninstall.bind(this)
759
- }
760
- }
761
- }
762
-
763
- return new AnonymousPlugin()
764
- }
765
- }
766
-
767
- module.exports = { PluginManager }
1
+ /**
2
+ * PluginManager 插件管理器
3
+ * 负责插件的加载、卸载、重载
4
+ *
5
+ * @module PluginManager
6
+ * @requires ./plugin-base
7
+ */
8
+
9
+ const { Plugin } = require('./plugin-base');
10
+ const { logger } = require('../utils/logger');
11
+ const { PluginError, PluginNotFoundError } = require('../utils/error');
12
+ const { safeJsonParse } = require('../utils');
13
+ const { resolvePluginPath, scanPluginNames } = require('../utils/plugin-helpers');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ class PluginManager {
18
+ /**
19
+ * @param {Framework} framework - 框架实例
20
+ */
21
+ constructor(framework) {
22
+ this.framework = framework;
23
+ this._plugins = new Map();
24
+ this._loading = false;
25
+ this._stateFile = path.join(process.cwd(), '.agent', 'data', 'plugins-state.json');
26
+ this._knownPlugins = new Set(); // 已知插件列表(包括未加载的)
27
+
28
+ // 创建子日志器
29
+ this._log = logger.child('plugin-manager');
30
+
31
+ // 事件描述注册表(供 Ambient Agent 使用)
32
+ this._eventDescriptions = new Map();
33
+
34
+ // 状态缓存(加载后保持,避免重复读文件)
35
+ this._stateCache = null;
36
+
37
+ // 注册默认事件描述
38
+ this._registerDefaultEventDescriptions();
39
+ }
40
+
41
+ /**
42
+ * 注册默认事件描述
43
+ * @private
44
+ */
45
+ _registerDefaultEventDescriptions() {
46
+ // 这些事件由框架内置组件触发,无需注册
47
+ // 但为了文档完整性,提供默认描述
48
+ const defaultDescriptions = {
49
+ 'tool:result': {
50
+ description: '工具执行结果事件',
51
+ params: 'name(工具名), args(执行参数), result(执行结果), error(错误信息)',
52
+ source: 'framework',
53
+ },
54
+ 'tool:error': {
55
+ description: '工具执行错误事件',
56
+ params: 'name(工具名), args(执行参数), error(错误信息)',
57
+ source: 'framework',
58
+ },
59
+ 'agent:message': {
60
+ description: '代理消息事件',
61
+ params: 'content(消息内容), sessionId(会话ID)',
62
+ source: 'framework',
63
+ },
64
+ 'think:thought_completed': {
65
+ description: '思考完成事件',
66
+ params: 'mode(思考模式), topic(思考主题), thought(思考内容), depth(思考深度)',
67
+ source: 'think-plugin',
68
+ },
69
+ 'email:received': {
70
+ description: '收到邮件事件',
71
+ params:
72
+ 'from(发件人), to(收件人), subject(主题), text(正文), body(HTML正文), messageId(消息ID), timestamp(时间戳)',
73
+ source: 'email-plugin',
74
+ },
75
+ 'webhook:received': {
76
+ description: 'Webhook接收事件',
77
+ params: 'headers(HTTP头), body(请求体), query(查询参数), path(请求路径)',
78
+ source: 'web-plugin',
79
+ },
80
+ 'scheduler:reminder': {
81
+ description: '定时提醒事件',
82
+ params: 'taskId(任务ID), message(提醒消息), time(触发时间)',
83
+ source: 'scheduler-plugin',
84
+ },
85
+ 'scheduler:task_completed': {
86
+ description: '任务完成事件',
87
+ params: 'taskId(任务ID), result(任务结果)',
88
+ source: 'scheduler-plugin',
89
+ },
90
+ 'scheduler:task_failed': {
91
+ description: '任务失败事件',
92
+ params: 'taskId(任务ID), error(错误信息)',
93
+ source: 'scheduler-plugin',
94
+ },
95
+ };
96
+
97
+ for (const [eventType, info] of Object.entries(defaultDescriptions)) {
98
+ this._eventDescriptions.set(eventType, info);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * 获取状态文件路径
104
+ * @private
105
+ */
106
+ _getStateFile() {
107
+ const dir = path.dirname(this._stateFile);
108
+ if (!fs.existsSync(dir)) {
109
+ fs.mkdirSync(dir, { recursive: true });
110
+ }
111
+ return this._stateFile;
112
+ }
113
+
114
+ /**
115
+ * 保存插件状态到文件
116
+ * 注意:AI 插件配置不保存(从环境变量和命令行获取)
117
+ * @private
118
+ */
119
+ _saveState() {
120
+ try {
121
+ const stateFile = this._getStateFile();
122
+ // 读取现有状态(如果存在),保留原有内容
123
+ let state = safeJsonParse(
124
+ fs.existsSync(stateFile) ? fs.readFileSync(stateFile, 'utf-8') : '{}',
125
+ {}
126
+ );
127
+
128
+ // 合并新状态到现有状态
129
+ for (const [name, entry] of this._plugins) {
130
+ // AI 配置不保存,每次从环境变量和命令行获取
131
+ if (name === 'ai') {
132
+ state[name] = { enabled: entry.enabled };
133
+ } else {
134
+ state[name] = {
135
+ ...(state[name] || {}), // 保留现有配置
136
+ enabled: entry.enabled,
137
+ config: entry.instance?.config || {},
138
+ };
139
+ }
140
+ }
141
+
142
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
143
+
144
+ // 更新缓存
145
+ this._stateCache = state;
146
+ } catch (err) {
147
+ this._log.error('Failed to save state:', err.message);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * 加载插件状态从文件(带缓存)
153
+ * @private
154
+ * @returns {Object}
155
+ */
156
+ _loadState() {
157
+ // 返回缓存的状态
158
+ if (this._stateCache !== null) {
159
+ return this._stateCache;
160
+ }
161
+
162
+ try {
163
+ const stateFile = this._getStateFile();
164
+ if (fs.existsSync(stateFile)) {
165
+ this._stateCache = safeJsonParse(fs.readFileSync(stateFile, 'utf-8'), {});
166
+ return this._stateCache;
167
+ }
168
+ } catch (err) {
169
+ this._log.error('Failed to load state:', err.message);
170
+ }
171
+ this._stateCache = {};
172
+ return this._stateCache;
173
+ }
174
+
175
+ /**
176
+ * 注册插件(不加载)
177
+ * @param {Plugin|Object} plugin - 插件实例或定义
178
+ * @param {Object} [options] - 注册选项
179
+ * @returns {PluginManager}
180
+ * @throws {PluginError} 插件没有名称时抛出
181
+ */
182
+ register(plugin, options = {}) {
183
+ if (!plugin.name) {
184
+ throw new PluginError('Plugin must have a name');
185
+ }
186
+
187
+ let pluginInstance = plugin;
188
+ if (!(plugin instanceof Plugin)) {
189
+ // 从对象创建插件实例
190
+ pluginInstance = this._createFromObject(plugin);
191
+ }
192
+
193
+ // 加载保存的状态
194
+ const savedState = this._loadState();
195
+ const savedEnabled = savedState[pluginInstance.name]?.enabled;
196
+ const savedConfig = savedState[pluginInstance.name]?.config;
197
+
198
+ // 恢复保存的配置到插件实例(AI 插件不恢复配置,从环境变量获取)
199
+ if (savedConfig && pluginInstance.config && pluginInstance.name !== 'ai') {
200
+ pluginInstance.config = { ...pluginInstance.config, ...savedConfig };
201
+ }
202
+
203
+ // 系统插件强制启用,不能禁用
204
+ // 普通插件:state 文件有记录则用 state,否则用插件默认配置
205
+ let enabled;
206
+ if (pluginInstance.system) {
207
+ enabled = true;
208
+ } else if (savedEnabled !== undefined) {
209
+ enabled = savedEnabled;
210
+ } else {
211
+ enabled = pluginInstance.enabled !== undefined ? pluginInstance.enabled : true;
212
+ }
213
+
214
+ this._plugins.set(pluginInstance.name, {
215
+ instance: pluginInstance,
216
+ status: 'registered',
217
+ enabled,
218
+ });
219
+
220
+ this.framework.emit('plugin:registered', pluginInstance);
221
+ return this;
222
+ }
223
+
224
+ /**
225
+ * 加载插件
226
+ * @param {Plugin|Object} plugin - 插件实例或定义
227
+ * @param {Object} [options] - 加载选项
228
+ * @param {boolean} [options.forceEnabled] - 强制启用插件,忽略 state 文件的 disabled 状态
229
+ * @returns {Promise<Plugin>}
230
+ * @throws {PluginError} 加载过程中发生错误时抛出
231
+ */
232
+ async load(plugin, options = {}) {
233
+ if (this._loading) {
234
+ throw new PluginError('Cannot load plugin during another load operation');
235
+ }
236
+
237
+ this._loading = true;
238
+ try {
239
+ let pluginInstance = plugin;
240
+
241
+ if (!(plugin instanceof Plugin)) {
242
+ pluginInstance = this._createFromObject(plugin);
243
+ }
244
+
245
+ // 如果已注册,使用已注册的实例和状态
246
+ const existing = this._plugins.get(pluginInstance.name);
247
+ if (existing) {
248
+ pluginInstance = existing.instance;
249
+
250
+ // 如果插件被禁用,跳过加载
251
+ if (!existing.enabled) {
252
+ this._log.info(`Plugin '${pluginInstance.name}' is disabled`);
253
+ return pluginInstance;
254
+ }
255
+
256
+ // 如果已加载且已启动,直接返回
257
+ if (existing.status === 'loaded' && pluginInstance._started) {
258
+ this._log.warn(`Plugin '${pluginInstance.name}' already loaded`);
259
+ return pluginInstance;
260
+ }
261
+
262
+ // 如果已加载但未启动(重载场景),直接启动
263
+ if (existing.status === 'loaded' && !pluginInstance._started) {
264
+ try {
265
+ if (typeof pluginInstance.start === 'function') {
266
+ await pluginInstance.start(this.framework);
267
+ pluginInstance._started = true;
268
+ }
269
+ } catch (err) {
270
+ this._log.error(`Start failed for '${pluginInstance.name}':`, err.message);
271
+ }
272
+ this.framework.emit('plugin:loaded', pluginInstance);
273
+ return pluginInstance;
274
+ }
275
+ } else {
276
+ // 未注册,先注册
277
+ this.register(pluginInstance, options);
278
+ }
279
+
280
+ const entry = this._plugins.get(pluginInstance.name);
281
+
282
+ // 注册后再次检查 enabled 状态
283
+ if (!entry.enabled) {
284
+ return pluginInstance;
285
+ }
286
+
287
+ // 调用 install
288
+ try {
289
+ await entry.instance.install(this.framework);
290
+ } catch (err) {
291
+ this._log.error(`Install failed for '${pluginInstance.name}':`, err.message);
292
+ throw new PluginError(`Install failed for '${pluginInstance.name}'`, {
293
+ context: { originalError: err.message },
294
+ });
295
+ }
296
+
297
+ entry.status = 'loaded';
298
+
299
+ // 如果处于 bootstrap 模式,由外部统一调用 startAll()
300
+ // 否则直接启动
301
+ if (this._bootstrapping) {
302
+ // bootstrap 模式下不启动,由 bootstrapDefaults 统一启动
303
+ } else {
304
+ // 非 bootstrap 模式下直接启动
305
+ try {
306
+ if (typeof pluginInstance.start === 'function') {
307
+ await pluginInstance.start(this.framework);
308
+ pluginInstance._started = true;
309
+ }
310
+ } catch (err) {
311
+ this._log.error(`Start failed for '${pluginInstance.name}':`, err.message);
312
+ }
313
+ }
314
+
315
+ this.framework.emit('plugin:loaded', pluginInstance);
316
+
317
+ // 保存状态(创建或更新 state 文件)
318
+ this._saveState();
319
+
320
+ return pluginInstance;
321
+ } finally {
322
+ this._loading = false;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * 设置 bootstrap 模式
328
+ * @param {boolean} value
329
+ */
330
+ setBootstrapping(value) {
331
+ this._bootstrapping = value;
332
+ }
333
+
334
+ /**
335
+ * 卸载插件
336
+ * @param {string} name - 插件名称
337
+ * @returns {Promise<boolean>}
338
+ */
339
+ async unload(name) {
340
+ const entry = this._plugins.get(name);
341
+ if (!entry) {
342
+ return false;
343
+ }
344
+
345
+ const { instance } = entry;
346
+
347
+ // 调用 uninstall
348
+ try {
349
+ await instance.uninstall(this.framework);
350
+ } catch (err) {
351
+ this._log.error(`Uninstall error for '${name}':`, err.message);
352
+ }
353
+
354
+ entry.status = 'unloaded';
355
+ this.framework.emit('plugin:unloaded', instance);
356
+ return true;
357
+ }
358
+
359
+ /**
360
+ * 重载插件
361
+ * @param {string} name - 插件名称
362
+ * @returns {Promise<Plugin>}
363
+ * @throws {PluginNotFoundError} 插件不存在时抛出
364
+ */
365
+ async reload(name) {
366
+ const entry = this._plugins.get(name);
367
+ if (!entry) {
368
+ throw new PluginNotFoundError(name);
369
+ }
370
+
371
+ const { instance } = entry;
372
+
373
+ // 调用 reload
374
+ try {
375
+ await instance.reload(this.framework);
376
+ this.framework.emit('plugin:reloaded', instance);
377
+ } catch (err) {
378
+ this._log.error(`Reload error for '${name}':`, err.message);
379
+ throw new PluginError(`Reload error for '${name}'`, {
380
+ context: { originalError: err.message },
381
+ });
382
+ }
383
+
384
+ return instance;
385
+ }
386
+
387
+ /**
388
+ * 重载所有插件
389
+ * @returns {Promise<void>}
390
+ */
391
+ async reloadAll() {
392
+ // 1. 重置所有插件的启动标志
393
+ for (const entry of this._plugins.values()) {
394
+ entry.instance._started = false;
395
+ }
396
+
397
+ // 2. 扫描 .agent/plugins 目录,加载新插件
398
+ await this._discoverCustomPlugins();
399
+
400
+ // 3. 启动所有未启动的插件
401
+ await this.startAll();
402
+ }
403
+
404
+ /**
405
+ * 解析插件路径
406
+ * 支持两种结构:
407
+ * 1. 文件夹结构: .agent/plugins/my-plugin/index.js
408
+ * 2. 单文件结构: .agent/plugins/my-plugin.js
409
+ * @param {string} pluginsDir - 插件目录
410
+ * @param {string} name - 插件名称
411
+ * @returns {{path: string, type: 'folder'|'file'}|null} 插件路径和类型
412
+ * @private
413
+ */
414
+ _resolvePluginPath(pluginsDir, name) {
415
+ return resolvePluginPath(pluginsDir, name, { logger: this._log });
416
+ }
417
+
418
+ /**
419
+ * 扫描插件目录,返回所有插件名称
420
+ * @param {string} pluginsDir - 插件目录
421
+ * @returns {string[]} 插件名称列表
422
+ * @private
423
+ */
424
+ _scanPluginNames(pluginsDir) {
425
+ return scanPluginNames(pluginsDir);
426
+ }
427
+
428
+ /**
429
+ * 发现并加载自定义插件
430
+ * @private
431
+ */
432
+ async _discoverCustomPlugins() {
433
+ const pluginsDir = path.resolve(process.cwd(), '.agent', 'plugins');
434
+ if (!fs.existsSync(pluginsDir)) {
435
+ return;
436
+ }
437
+
438
+ // 扫描所有插件名称(支持文件夹和单文件)
439
+ const pluginNames = this._scanPluginNames(pluginsDir);
440
+
441
+ // 从 pluginsDir 推导 agentDir(pluginsDir = <agentDir>/plugins)
442
+ const agentDir = path.dirname(pluginsDir);
443
+ const agentNodeModules = path.join(agentDir, 'node_modules');
444
+
445
+ for (const pluginName of pluginNames) {
446
+ try {
447
+ const resolved = this._resolvePluginPath(pluginsDir, pluginName);
448
+ if (!resolved) {
449
+ this._log.warn(`Cannot resolve plugin: ${pluginName}`);
450
+ continue;
451
+ }
452
+
453
+ const { path: pluginPath, type } = resolved;
454
+
455
+ // 添加模块路径到搜索路径(优先级从高到低)
456
+ const modulePathsToAdd = [
457
+ agentNodeModules, // .agent/node_modules(项目本地安装的包)
458
+ path.join(__dirname, '..', '..', 'node_modules'), // 全局安装的包
459
+ ];
460
+ for (const mp of modulePathsToAdd) {
461
+ if (fs.existsSync(mp) && !module.paths.includes(mp)) {
462
+ module.paths.unshift(mp);
463
+ }
464
+ }
465
+
466
+ // 清除缓存
467
+ delete require.cache[require.resolve(pluginPath)];
468
+ const pluginModule = require(pluginPath);
469
+
470
+ let plugin;
471
+ if (typeof pluginModule === 'function') {
472
+ plugin = pluginModule;
473
+ } else if (pluginModule.default) {
474
+ plugin = pluginModule.default;
475
+ } else {
476
+ plugin = pluginModule;
477
+ }
478
+
479
+ // 获取插件名称
480
+ let resolvedPluginName;
481
+ try {
482
+ const tempPlugin =
483
+ plugin.prototype instanceof require('./plugin-base')
484
+ ? new plugin()
485
+ : typeof plugin === 'function'
486
+ ? plugin()
487
+ : plugin;
488
+ resolvedPluginName = tempPlugin.name || pluginName;
489
+ } catch {
490
+ resolvedPluginName = pluginName;
491
+ }
492
+
493
+ // 如果插件已加载且已启动,跳过
494
+ if (this.has(resolvedPluginName) && this.get(resolvedPluginName)?._started) {
495
+ continue;
496
+ }
497
+
498
+ this._log.info(`Loading new plugin: ${pluginName} (${type})`);
499
+ // .agent/plugins 目录下的插件默认强制启用,不受 state 文件影响
500
+ await this.load(plugin, { forceEnabled: true });
501
+ } catch (err) {
502
+ this._log.error(`Failed to load plugin ${pluginName}:`, err.message);
503
+ }
504
+ }
505
+ }
506
+
507
+ /**
508
+ * 获取插件
509
+ * @param {string} name - 插件名称
510
+ * @returns {Plugin|undefined}
511
+ */
512
+ get(name) {
513
+ return this._plugins.get(name)?.instance;
514
+ }
515
+
516
+ /**
517
+ * 获取所有已加载且已启用的插件
518
+ * @returns {Array<{name: string, instance: Plugin}>}
519
+ */
520
+ getAll() {
521
+ return Array.from(this._plugins.values())
522
+ .filter((e) => e.status === 'loaded' && e.enabled)
523
+ .map((e) => ({ name: e.instance.name, instance: e.instance }));
524
+ }
525
+
526
+ /**
527
+ * 注册一个已知插件(但不加载)
528
+ * 用于显示所有可用插件列表
529
+ * @param {string} name - 插件名称
530
+ * @param {Object} [info] - 插件信息
531
+ */
532
+ registerKnownPlugin(name, info = {}) {
533
+ this._knownPlugins.add(name);
534
+ // 如果插件还没注册过,记录它的信息
535
+ if (!this._plugins.has(name)) {
536
+ this._plugins.set(name, {
537
+ instance: { name, ...info },
538
+ status: 'known',
539
+ enabled: info.enabled !== undefined ? info.enabled : false,
540
+ });
541
+ }
542
+ }
543
+
544
+ /**
545
+ * 获取所有已知插件(包括未加载的)
546
+ * @returns {Array<{name: string, status: string, enabled: boolean, version?: string, system?: boolean}>}
547
+ */
548
+ getAllKnown() {
549
+ const result = [];
550
+ for (const name of this._knownPlugins) {
551
+ const entry = this._plugins.get(name);
552
+ result.push({
553
+ name,
554
+ status: entry?.status || 'unknown',
555
+ enabled: entry?.enabled || false,
556
+ version: entry?.instance?.version,
557
+ system: entry?.instance?.system || false,
558
+ });
559
+ }
560
+ // 也加入已加载但不在 knownPlugins 中的
561
+ for (const [name, entry] of this._plugins) {
562
+ if (!result.find((p) => p.name === name)) {
563
+ result.push({
564
+ name,
565
+ status: entry.status,
566
+ enabled: entry.enabled,
567
+ version: entry.instance?.version,
568
+ system: entry.instance?.system || false,
569
+ });
570
+ }
571
+ }
572
+ return result;
573
+ }
574
+
575
+ /**
576
+ * 检查插件是否存在
577
+ * @param {string} name - 插件名称
578
+ * @returns {boolean}
579
+ */
580
+ has(name) {
581
+ return this._plugins.has(name) || this._knownPlugins.has(name);
582
+ }
583
+
584
+ /**
585
+ * 检查插件是否已加载
586
+ * @param {string} name - 插件名称
587
+ * @returns {boolean}
588
+ */
589
+ isLoaded(name) {
590
+ return this._plugins.get(name)?.status === 'loaded';
591
+ }
592
+
593
+ /**
594
+ * 检查插件是否启用
595
+ * @param {string} name - 插件名称
596
+ * @returns {boolean}
597
+ */
598
+ isEnabled(name) {
599
+ return this._plugins.get(name)?.enabled === true;
600
+ }
601
+
602
+ /**
603
+ * 启用插件
604
+ * @param {string} name - 插件名称
605
+ * @returns {Promise<void>}
606
+ * @throws {PluginNotFoundError} 插件不存在时抛出
607
+ * @throws {PluginError} 系统插件不能被禁用
608
+ */
609
+ async enable(name) {
610
+ const entry = this._plugins.get(name);
611
+ if (!entry) {
612
+ throw new PluginNotFoundError(name);
613
+ }
614
+
615
+ // 系统插件不能被禁用,所以启用没有意义
616
+ if (entry.instance?.system) {
617
+ throw new PluginError(`Plugin '${name}' is a system plugin, cannot be disabled`);
618
+ }
619
+
620
+ if (entry.enabled) {
621
+ this._log.info(`Plugin '${name}' already enabled`);
622
+ return;
623
+ }
624
+
625
+ entry.enabled = true;
626
+ // 同步更新插件实例的 enabled 属性,避免 load() 时被跳过
627
+ if (entry.instance) {
628
+ entry.instance.enabled = true;
629
+ }
630
+
631
+ // 如果插件已加载,尝试重新启动
632
+ if (entry.status === 'loaded') {
633
+ try {
634
+ // 如果之前已经启动过,先调用 stop 停止旧实例
635
+ if (entry.instance._started) {
636
+ if (typeof entry.instance.stop === 'function') {
637
+ await entry.instance.stop();
638
+ } else if (typeof entry.instance.stopBot === 'function') {
639
+ await entry.instance.stopBot();
640
+ }
641
+ }
642
+ // 调用 reload 让插件重新初始化(会调用 install 和 start)
643
+ await this.reload(name);
644
+ } catch (err) {
645
+ this._log.error(`Enable/reload failed for '${name}':`, err.message);
646
+ }
647
+ } else if (entry.status === 'registered' || entry.status === 'known') {
648
+ // 插件只注册过但未加载,现在加载它
649
+ try {
650
+ await this.load(entry.instance);
651
+ } catch (err) {
652
+ this._log.error(`Enable/load failed for '${name}':`, err.message);
653
+ }
654
+ }
655
+
656
+ this.framework.emit('plugin:enabled', entry.instance);
657
+ this._saveState();
658
+ this._log.info(`Plugin '${name}' enabled`);
659
+ }
660
+
661
+ /**
662
+ * 禁用插件
663
+ * @param {string} name - 插件名称
664
+ * @returns {Promise<void>}
665
+ * @throws {PluginNotFoundError} 插件不存在时抛出
666
+ * @throws {PluginError} 系统插件不能被禁用
667
+ */
668
+ async disable(name) {
669
+ const entry = this._plugins.get(name);
670
+ if (!entry) {
671
+ throw new PluginNotFoundError(name);
672
+ }
673
+
674
+ // 系统插件不能被禁用
675
+ if (entry.instance?.system) {
676
+ throw new PluginError(`Plugin '${name}' is a system plugin, cannot be disabled`);
677
+ }
678
+
679
+ if (!entry.enabled) {
680
+ this._log.info(`Plugin '${name}' already disabled`);
681
+ return;
682
+ }
683
+
684
+ entry.enabled = false;
685
+ // 同步更新插件实例的 enabled 属性
686
+ if (entry.instance) {
687
+ entry.instance.enabled = false;
688
+ }
689
+
690
+ // 如果插件正在运行,停止它
691
+ if (entry.instance._started) {
692
+ try {
693
+ // 优先调用 stop 方法,其次调用 stopBot 方法
694
+ if (typeof entry.instance.stop === 'function') {
695
+ await entry.instance.stop();
696
+ } else if (typeof entry.instance.stopBot === 'function') {
697
+ await entry.instance.stopBot();
698
+ }
699
+ entry.instance._started = false;
700
+ } catch (err) {
701
+ this._log.error(`Stop failed for '${name}':`, err.message);
702
+ }
703
+ }
704
+
705
+ this.framework.emit('plugin:disabled', entry.instance);
706
+ this._saveState();
707
+ this._log.info(`Plugin '${name}' disabled`);
708
+ }
709
+
710
+ /**
711
+ * 更新插件配置
712
+ * @param {string} name - 插件名称
713
+ * @param {Object} config - 新配置(会合并到现有配置)
714
+ * @returns {Object} 更新后的配置
715
+ * @throws {PluginNotFoundError} 插件不存在时抛出
716
+ */
717
+ updatePluginConfig(name, config) {
718
+ const entry = this._plugins.get(name);
719
+ if (!entry) {
720
+ throw new PluginNotFoundError(name);
721
+ }
722
+
723
+ if (!entry.instance.config) {
724
+ entry.instance.config = {};
725
+ }
726
+
727
+ // 合并配置
728
+ entry.instance.config = { ...entry.instance.config, ...config };
729
+
730
+ // 保存状态
731
+ this._saveState();
732
+ this._log.info(`Plugin '${name}' config updated`);
733
+
734
+ return entry.instance.config;
735
+ }
736
+
737
+ /**
738
+ * 启动所有已加载但未启动的插件(按优先级排序)
739
+ * @returns {Promise<void>}
740
+ */
741
+ async startAll() {
742
+ const entries = Array.from(this._plugins.values())
743
+ .filter((e) => e.status === 'loaded' && e.enabled) // 只启动已启用且已加载的插件
744
+ .sort((a, b) => (a.instance.priority || 100) - (b.instance.priority || 100));
745
+
746
+ for (const entry of entries) {
747
+ const instance = entry.instance;
748
+ // 跳过已经启动过的插件
749
+ if (instance._started) {
750
+ continue;
751
+ }
752
+ try {
753
+ if (typeof instance.start === 'function') {
754
+ await instance.start(this.framework);
755
+ instance._started = true;
756
+ }
757
+ } catch (err) {
758
+ this._log.error(`Start failed for '${instance.name}':`, err.message);
759
+ }
760
+ }
761
+ }
762
+
763
+ /**
764
+ * 从对象创建插件实例
765
+ * @private
766
+ * @param {Object|Function} obj
767
+ * @returns {Plugin}
768
+ */
769
+ _createFromObject(obj) {
770
+ const { Plugin } = require('./plugin-base');
771
+
772
+ // 如果是类(构造函数),直接实例化
773
+ if (typeof obj === 'function') {
774
+ // 检查是否是 Plugin 的子类(通过 prototype chain)
775
+ if (obj.prototype instanceof Plugin) {
776
+ return new obj();
777
+ }
778
+ // 否则是工厂函数,调用它获取类或实例
779
+ const result = obj(Plugin);
780
+ // 递归处理返回值
781
+ if (typeof result === 'function' && result.prototype instanceof Plugin) {
782
+ return new result();
783
+ }
784
+ return result;
785
+ }
786
+
787
+ // 支持对象形式: { name, version, install, start, ... }
788
+ class AnonymousPlugin extends Plugin {
789
+ constructor() {
790
+ super();
791
+ this.name = obj.name;
792
+ this.version = obj.version || '1.0.0';
793
+ this.description = obj.description || '';
794
+ this.priority = obj.priority || 100;
795
+
796
+ // 如果提供了 install/start/reload/uninstall 方法,绑定它们
797
+ if (typeof obj.install === 'function') {
798
+ this.install = obj.install.bind(this);
799
+ }
800
+ if (typeof obj.start === 'function') {
801
+ this.start = obj.start.bind(this);
802
+ }
803
+ if (typeof obj.reload === 'function') {
804
+ this.reload = obj.reload.bind(this);
805
+ }
806
+ if (typeof obj.uninstall === 'function') {
807
+ this.uninstall = obj.uninstall.bind(this);
808
+ }
809
+ }
810
+ }
811
+
812
+ return new AnonymousPlugin();
813
+ }
814
+
815
+ // ============================================================================
816
+ // 事件描述注册(供 Ambient Agent 使用)
817
+ // ============================================================================
818
+
819
+ /**
820
+ * 注册事件描述
821
+ * @param {string} eventType - 事件类型
822
+ * @param {string} description - 事件描述
823
+ * @param {Object} [schema] - 事件参数 Schema
824
+ */
825
+ registerEventDescription(eventType, description, schema = null) {
826
+ const existing = this._eventDescriptions.get(eventType);
827
+ this._eventDescriptions.set(eventType, {
828
+ description,
829
+ params: schema || existing?.params || '',
830
+ source: 'dynamic',
831
+ updatedAt: new Date().toISOString(),
832
+ });
833
+ return this;
834
+ }
835
+
836
+ /**
837
+ * 获取所有事件描述
838
+ * @returns {Map<string, Object>}
839
+ */
840
+ getEventDescriptions() {
841
+ return new Map(this._eventDescriptions);
842
+ }
843
+
844
+ /**
845
+ * 获取单个事件描述
846
+ * @param {string} eventType
847
+ * @returns {Object|null}
848
+ */
849
+ getEventDescription(eventType) {
850
+ return this._eventDescriptions.get(eventType) || null;
851
+ }
852
+
853
+ /**
854
+ * 检查事件是否已注册
855
+ * @param {string} eventType
856
+ * @returns {boolean}
857
+ */
858
+ hasEventDescription(eventType) {
859
+ return this._eventDescriptions.has(eventType);
860
+ }
861
+ }
862
+
863
+ module.exports = { PluginManager };