foliko 1.0.75 → 1.0.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/.claude/settings.local.json +159 -157
  2. package/cli/bin/foliko.js +12 -12
  3. package/cli/src/commands/chat.js +143 -143
  4. package/cli/src/commands/list.js +93 -93
  5. package/cli/src/index.js +75 -75
  6. package/cli/src/ui/chat-ui.js +201 -201
  7. package/cli/src/utils/ansi.js +40 -40
  8. package/cli/src/utils/markdown.js +292 -292
  9. package/examples/ambient-example.js +194 -194
  10. package/examples/basic.js +115 -115
  11. package/examples/bootstrap.js +121 -121
  12. package/examples/mcp-example.js +56 -56
  13. package/examples/skill-example.js +49 -49
  14. package/examples/test-chat.js +137 -137
  15. package/examples/test-mcp.js +85 -85
  16. package/examples/test-reload.js +59 -59
  17. package/examples/test-telegram.js +50 -50
  18. package/examples/test-tg-bot.js +45 -45
  19. package/examples/test-tg-simple.js +47 -47
  20. package/examples/test-tg.js +62 -62
  21. package/examples/test-think.js +43 -43
  22. package/examples/test-web-plugin.js +103 -103
  23. package/examples/test-weixin-feishu.js +103 -103
  24. package/examples/workflow.js +158 -158
  25. package/package.json +1 -1
  26. package/plugins/ai-plugin.js +102 -102
  27. package/plugins/ambient-agent/EventWatcher.js +113 -113
  28. package/plugins/ambient-agent/ExplorerLoop.js +640 -640
  29. package/plugins/ambient-agent/GoalManager.js +197 -197
  30. package/plugins/ambient-agent/Reflector.js +95 -95
  31. package/plugins/ambient-agent/StateStore.js +90 -90
  32. package/plugins/ambient-agent/constants.js +101 -101
  33. package/plugins/ambient-agent/index.js +579 -579
  34. package/plugins/audit-plugin.js +187 -187
  35. package/plugins/default-plugins.js +662 -662
  36. package/plugins/email/constants.js +64 -64
  37. package/plugins/email/handlers.js +461 -461
  38. package/plugins/email/index.js +278 -278
  39. package/plugins/email/monitor.js +269 -269
  40. package/plugins/email/parser.js +138 -138
  41. package/plugins/email/reply.js +151 -151
  42. package/plugins/email/utils.js +124 -124
  43. package/plugins/feishu-plugin.js +481 -481
  44. package/plugins/file-system-plugin.js +826 -826
  45. package/plugins/install-plugin.js +199 -199
  46. package/plugins/python-executor-plugin.js +367 -367
  47. package/plugins/python-plugin-loader.js +481 -481
  48. package/plugins/rules-plugin.js +294 -294
  49. package/plugins/scheduler-plugin.js +691 -691
  50. package/plugins/session-plugin.js +369 -369
  51. package/plugins/shell-executor-plugin.js +197 -197
  52. package/plugins/storage-plugin.js +240 -240
  53. package/plugins/subagent-plugin.js +845 -845
  54. package/plugins/telegram-plugin.js +482 -482
  55. package/plugins/think-plugin.js +345 -345
  56. package/plugins/tools-plugin.js +196 -196
  57. package/plugins/web-plugin.js +606 -606
  58. package/plugins/weixin-plugin.js +545 -545
  59. package/src/capabilities/index.js +11 -11
  60. package/src/capabilities/skill-manager.js +609 -609
  61. package/src/capabilities/workflow-engine.js +1109 -1109
  62. package/src/core/agent-chat.js +882 -882
  63. package/src/core/agent.js +892 -892
  64. package/src/core/framework.js +465 -465
  65. package/src/core/index.js +19 -19
  66. package/src/core/plugin-base.js +219 -219
  67. package/src/core/plugin-manager.js +863 -863
  68. package/src/core/provider.js +114 -114
  69. package/src/core/sub-agent-config.js +264 -264
  70. package/src/core/system-prompt-builder.js +120 -120
  71. package/src/core/tool-registry.js +517 -517
  72. package/src/core/tool-router.js +297 -297
  73. package/src/executors/executor-base.js +58 -58
  74. package/src/executors/mcp-executor.js +741 -741
  75. package/src/index.js +25 -25
  76. package/src/utils/circuit-breaker.js +301 -301
  77. package/src/utils/error-boundary.js +363 -363
  78. package/src/utils/error.js +374 -374
  79. package/src/utils/event-emitter.js +97 -97
  80. package/src/utils/id.js +133 -133
  81. package/src/utils/index.js +217 -217
  82. package/src/utils/logger.js +181 -181
  83. package/src/utils/plugin-helpers.js +90 -90
  84. package/src/utils/retry.js +122 -122
  85. package/src/utils/sandbox.js +292 -292
  86. package/test/tool-registry-validation.test.js +218 -218
  87. package/website/script.js +136 -136
  88. package/foliko-1.0.75.tgz +0 -0
@@ -1,863 +1,863 @@
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 };
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 };