foliko 1.0.74 → 1.0.75
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.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +288 -0
- package/.agent/agents/ambient-agent.md +57 -0
- package/.agent/agents/debugger.md +55 -0
- package/.agent/agents/email-assistant.md +49 -0
- package/.agent/agents/file-manager.md +42 -0
- package/.agent/agents/python-developer.md +60 -0
- package/.agent/agents/scheduler.md +59 -0
- package/.agent/agents/web-developer.md +45 -0
- package/.agent/data/default.json +29 -0
- package/.agent/data/plugins-state.json +255 -0
- package/.agent/mcp_config.json +4 -0
- package/.agent/mcp_config_updated.json +12 -0
- package/.agent/plugins.json +5 -0
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/rules/allow-rule.md +77 -0
- package/.agent/rules/log-rule.md +83 -0
- package/.agent/rules/security-rule.md +93 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +81 -0
- package/.agent/workflows/simple-test.md +42 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/structured-orchestrate.md +180 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/.claude/settings.local.json +157 -149
- package/.editorconfig +56 -0
- package/.husky/pre-commit +4 -0
- package/.lintstagedrc +7 -0
- package/.prettierignore +29 -0
- package/.prettierrc +11 -0
- package/CLAUDE.md +2 -0
- package/README.md +64 -55
- package/SPEC.md +102 -61
- package/cli/bin/foliko.js +4 -4
- package/cli/src/commands/chat.js +53 -51
- package/cli/src/commands/list.js +40 -37
- package/cli/src/index.js +18 -18
- package/cli/src/ui/chat-ui.js +78 -76
- package/cli/src/utils/ansi.js +15 -15
- package/cli/src/utils/markdown.js +112 -116
- package/docker-compose.yml +1 -1
- package/docs/ai-sdk-optimization.md +655 -643
- package/docs/features.md +80 -80
- package/docs/quick-reference.md +49 -46
- package/docs/user-manual.md +411 -380
- package/examples/ambient-example.js +95 -97
- package/examples/basic.js +115 -110
- package/examples/bootstrap.js +52 -43
- package/examples/mcp-example.js +56 -53
- package/examples/skill-example.js +49 -49
- package/examples/test-chat.js +60 -58
- package/examples/test-mcp.js +49 -43
- package/examples/test-reload.js +38 -40
- package/examples/test-telegram.js +3 -3
- package/examples/test-tg-bot.js +7 -4
- package/examples/test-tg-simple.js +4 -3
- package/examples/test-tg.js +3 -3
- package/examples/test-think.js +13 -7
- package/examples/test-web-plugin.js +61 -56
- package/examples/test-weixin-feishu.js +40 -37
- package/examples/workflow.js +49 -49
- package/foliko-1.0.75.tgz +0 -0
- package/package.json +37 -3
- package/plugins/ai-plugin.js +7 -5
- package/plugins/ambient-agent/EventWatcher.js +113 -0
- package/plugins/ambient-agent/ExplorerLoop.js +640 -0
- package/plugins/ambient-agent/GoalManager.js +197 -0
- package/plugins/ambient-agent/Reflector.js +95 -0
- package/plugins/ambient-agent/StateStore.js +90 -0
- package/plugins/ambient-agent/constants.js +101 -0
- package/plugins/ambient-agent/index.js +579 -0
- package/plugins/default-plugins.js +62 -49
- package/plugins/email/constants.js +64 -0
- package/plugins/email/handlers.js +461 -0
- package/plugins/email/index.js +278 -0
- package/plugins/email/monitor.js +269 -0
- package/plugins/email/parser.js +138 -0
- package/plugins/email/reply.js +151 -0
- package/plugins/email/utils.js +124 -0
- package/plugins/feishu-plugin.js +23 -19
- package/plugins/file-system-plugin.js +456 -106
- package/plugins/install-plugin.js +6 -4
- package/plugins/python-executor-plugin.js +3 -1
- package/plugins/python-plugin-loader.js +10 -8
- package/plugins/rules-plugin.js +5 -3
- package/plugins/scheduler-plugin.js +18 -16
- package/plugins/session-plugin.js +3 -1
- package/plugins/storage-plugin.js +5 -3
- package/plugins/subagent-plugin.js +152 -92
- package/plugins/telegram-plugin.js +26 -19
- package/plugins/think-plugin.js +4 -2
- package/plugins/tools-plugin.js +3 -1
- package/plugins/web-plugin.js +15 -13
- package/plugins/weixin-plugin.js +43 -36
- package/reports/system-health-report-20260401.md +79 -0
- package/skills/ambient-agent/SKILL.md +49 -39
- package/skills/foliko-dev/AGENTS.md +64 -61
- package/skills/foliko-dev/SKILL.md +125 -119
- package/skills/mcp-usage/SKILL.md +19 -17
- package/skills/python-plugin-dev/SKILL.md +16 -15
- package/skills/skill-guide/SKILL.md +12 -12
- package/skills/subagent-guide/SKILL.md +237 -0
- package/skills/workflow-guide/SKILL.md +90 -45
- package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
- package/skills/workflow-troubleshooting/SKILL.md +156 -79
- package/src/capabilities/index.js +4 -4
- package/src/capabilities/skill-manager.js +211 -197
- package/src/capabilities/workflow-engine.js +461 -547
- package/src/core/agent-chat.js +426 -279
- package/src/core/agent.js +453 -249
- package/src/core/framework.js +183 -149
- package/src/core/index.js +8 -8
- package/src/core/plugin-base.js +52 -52
- package/src/core/plugin-manager.js +377 -281
- package/src/core/provider.js +35 -32
- package/src/core/sub-agent-config.js +264 -0
- package/src/core/system-prompt-builder.js +120 -0
- package/src/core/tool-registry.js +416 -33
- package/src/core/tool-router.js +149 -68
- package/src/executors/executor-base.js +58 -58
- package/src/executors/mcp-executor.js +269 -257
- package/src/index.js +5 -17
- package/src/utils/circuit-breaker.js +301 -0
- package/src/utils/error-boundary.js +363 -0
- package/src/utils/error.js +374 -0
- package/src/utils/event-emitter.js +20 -20
- package/src/utils/id.js +133 -0
- package/src/utils/index.js +217 -3
- package/src/utils/logger.js +181 -0
- package/src/utils/plugin-helpers.js +90 -0
- package/src/utils/retry.js +122 -0
- package/src/utils/sandbox.js +292 -0
- package/test/tool-registry-validation.test.js +218 -0
- package/test_report.md +70 -0
- package/website/docs/api.html +169 -107
- package/website/docs/configuration.html +296 -144
- package/website/docs/plugin-development.html +154 -85
- package/website/docs/project-structure.html +110 -109
- package/website/docs/skill-development.html +117 -61
- package/website/index.html +209 -205
- package/website/script.js +20 -17
- package/website/styles.css +1 -1
- package/plugins/ambient-agent-plugin.js +0 -1565
- package/plugins/email.js +0 -1142
|
@@ -1,207 +1,300 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PluginManager 插件管理器
|
|
3
3
|
* 负责插件的加载、卸载、重载
|
|
4
|
+
*
|
|
5
|
+
* @module PluginManager
|
|
6
|
+
* @requires ./plugin-base
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
|
-
const { Plugin } = require('./plugin-base')
|
|
7
|
-
const
|
|
8
|
-
const
|
|
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');
|
|
9
16
|
|
|
10
17
|
class PluginManager {
|
|
11
18
|
/**
|
|
12
19
|
* @param {Framework} framework - 框架实例
|
|
13
20
|
*/
|
|
14
21
|
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()
|
|
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
|
+
}
|
|
20
100
|
}
|
|
21
101
|
|
|
22
102
|
/**
|
|
23
103
|
* 获取状态文件路径
|
|
104
|
+
* @private
|
|
24
105
|
*/
|
|
25
106
|
_getStateFile() {
|
|
26
|
-
const dir = path.dirname(this._stateFile)
|
|
107
|
+
const dir = path.dirname(this._stateFile);
|
|
27
108
|
if (!fs.existsSync(dir)) {
|
|
28
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
109
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
29
110
|
}
|
|
30
|
-
return this._stateFile
|
|
111
|
+
return this._stateFile;
|
|
31
112
|
}
|
|
32
113
|
|
|
33
114
|
/**
|
|
34
115
|
* 保存插件状态到文件
|
|
35
116
|
* 注意:AI 插件配置不保存(从环境变量和命令行获取)
|
|
117
|
+
* @private
|
|
36
118
|
*/
|
|
37
119
|
_saveState() {
|
|
38
120
|
try {
|
|
39
|
-
const stateFile = this._getStateFile()
|
|
121
|
+
const stateFile = this._getStateFile();
|
|
40
122
|
// 读取现有状态(如果存在),保留原有内容
|
|
41
|
-
let state =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} catch (e) {
|
|
46
|
-
// 文件损坏,使用空对象
|
|
47
|
-
state = {}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
123
|
+
let state = safeJsonParse(
|
|
124
|
+
fs.existsSync(stateFile) ? fs.readFileSync(stateFile, 'utf-8') : '{}',
|
|
125
|
+
{}
|
|
126
|
+
);
|
|
50
127
|
|
|
51
128
|
// 合并新状态到现有状态
|
|
52
129
|
for (const [name, entry] of this._plugins) {
|
|
53
130
|
// AI 配置不保存,每次从环境变量和命令行获取
|
|
54
131
|
if (name === 'ai') {
|
|
55
|
-
state[name] = { enabled: entry.enabled }
|
|
132
|
+
state[name] = { enabled: entry.enabled };
|
|
56
133
|
} else {
|
|
57
134
|
state[name] = {
|
|
58
|
-
...(state[name] || {}),
|
|
135
|
+
...(state[name] || {}), // 保留现有配置
|
|
59
136
|
enabled: entry.enabled,
|
|
60
|
-
config: entry.instance?.config || {}
|
|
61
|
-
}
|
|
137
|
+
config: entry.instance?.config || {},
|
|
138
|
+
};
|
|
62
139
|
}
|
|
63
140
|
}
|
|
64
141
|
|
|
65
|
-
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2))
|
|
142
|
+
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
|
|
143
|
+
|
|
144
|
+
// 更新缓存
|
|
145
|
+
this._stateCache = state;
|
|
66
146
|
} catch (err) {
|
|
67
|
-
|
|
147
|
+
this._log.error('Failed to save state:', err.message);
|
|
68
148
|
}
|
|
69
149
|
}
|
|
70
150
|
|
|
71
151
|
/**
|
|
72
|
-
*
|
|
152
|
+
* 加载插件状态从文件(带缓存)
|
|
153
|
+
* @private
|
|
154
|
+
* @returns {Object}
|
|
73
155
|
*/
|
|
74
156
|
_loadState() {
|
|
157
|
+
// 返回缓存的状态
|
|
158
|
+
if (this._stateCache !== null) {
|
|
159
|
+
return this._stateCache;
|
|
160
|
+
}
|
|
161
|
+
|
|
75
162
|
try {
|
|
76
|
-
const stateFile = this._getStateFile()
|
|
163
|
+
const stateFile = this._getStateFile();
|
|
77
164
|
if (fs.existsSync(stateFile)) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return state
|
|
165
|
+
this._stateCache = safeJsonParse(fs.readFileSync(stateFile, 'utf-8'), {});
|
|
166
|
+
return this._stateCache;
|
|
81
167
|
}
|
|
82
168
|
} catch (err) {
|
|
83
|
-
|
|
169
|
+
this._log.error('Failed to load state:', err.message);
|
|
84
170
|
}
|
|
85
|
-
|
|
171
|
+
this._stateCache = {};
|
|
172
|
+
return this._stateCache;
|
|
86
173
|
}
|
|
87
174
|
|
|
88
175
|
/**
|
|
89
176
|
* 注册插件(不加载)
|
|
90
177
|
* @param {Plugin|Object} plugin - 插件实例或定义
|
|
178
|
+
* @param {Object} [options] - 注册选项
|
|
179
|
+
* @returns {PluginManager}
|
|
180
|
+
* @throws {PluginError} 插件没有名称时抛出
|
|
91
181
|
*/
|
|
92
182
|
register(plugin, options = {}) {
|
|
93
183
|
if (!plugin.name) {
|
|
94
|
-
throw new
|
|
184
|
+
throw new PluginError('Plugin must have a name');
|
|
95
185
|
}
|
|
96
186
|
|
|
97
|
-
let pluginInstance = plugin
|
|
187
|
+
let pluginInstance = plugin;
|
|
98
188
|
if (!(plugin instanceof Plugin)) {
|
|
99
189
|
// 从对象创建插件实例
|
|
100
|
-
pluginInstance = this._createFromObject(plugin)
|
|
190
|
+
pluginInstance = this._createFromObject(plugin);
|
|
101
191
|
}
|
|
102
192
|
|
|
103
193
|
// 加载保存的状态
|
|
104
|
-
const savedState = this._loadState()
|
|
105
|
-
const savedEnabled = savedState[pluginInstance.name]?.enabled
|
|
106
|
-
const savedConfig = savedState[pluginInstance.name]?.config
|
|
194
|
+
const savedState = this._loadState();
|
|
195
|
+
const savedEnabled = savedState[pluginInstance.name]?.enabled;
|
|
196
|
+
const savedConfig = savedState[pluginInstance.name]?.config;
|
|
107
197
|
|
|
108
198
|
// 恢复保存的配置到插件实例(AI 插件不恢复配置,从环境变量获取)
|
|
109
199
|
if (savedConfig && pluginInstance.config && pluginInstance.name !== 'ai') {
|
|
110
|
-
pluginInstance.config = { ...pluginInstance.config, ...savedConfig }
|
|
200
|
+
pluginInstance.config = { ...pluginInstance.config, ...savedConfig };
|
|
111
201
|
}
|
|
112
202
|
|
|
113
203
|
// 系统插件强制启用,不能禁用
|
|
114
204
|
// 普通插件:state 文件有记录则用 state,否则用插件默认配置
|
|
115
|
-
let enabled
|
|
205
|
+
let enabled;
|
|
116
206
|
if (pluginInstance.system) {
|
|
117
|
-
enabled = true
|
|
207
|
+
enabled = true;
|
|
118
208
|
} else if (savedEnabled !== undefined) {
|
|
119
|
-
enabled = savedEnabled
|
|
209
|
+
enabled = savedEnabled;
|
|
120
210
|
} else {
|
|
121
|
-
enabled = pluginInstance.enabled !== undefined ? pluginInstance.enabled : true
|
|
211
|
+
enabled = pluginInstance.enabled !== undefined ? pluginInstance.enabled : true;
|
|
122
212
|
}
|
|
123
213
|
|
|
124
214
|
this._plugins.set(pluginInstance.name, {
|
|
125
215
|
instance: pluginInstance,
|
|
126
216
|
status: 'registered',
|
|
127
|
-
enabled
|
|
128
|
-
})
|
|
217
|
+
enabled,
|
|
218
|
+
});
|
|
129
219
|
|
|
130
|
-
this.framework.emit('plugin:registered', pluginInstance)
|
|
131
|
-
return this
|
|
220
|
+
this.framework.emit('plugin:registered', pluginInstance);
|
|
221
|
+
return this;
|
|
132
222
|
}
|
|
133
223
|
|
|
134
224
|
/**
|
|
135
225
|
* 加载插件
|
|
136
226
|
* @param {Plugin|Object} plugin - 插件实例或定义
|
|
137
|
-
* @param {Object} options - 加载选项
|
|
138
|
-
* @param {boolean} options.forceEnabled - 强制启用插件,忽略 state 文件的 disabled
|
|
227
|
+
* @param {Object} [options] - 加载选项
|
|
228
|
+
* @param {boolean} [options.forceEnabled] - 强制启用插件,忽略 state 文件的 disabled 状态
|
|
229
|
+
* @returns {Promise<Plugin>}
|
|
230
|
+
* @throws {PluginError} 加载过程中发生错误时抛出
|
|
139
231
|
*/
|
|
140
232
|
async load(plugin, options = {}) {
|
|
141
233
|
if (this._loading) {
|
|
142
|
-
throw new
|
|
234
|
+
throw new PluginError('Cannot load plugin during another load operation');
|
|
143
235
|
}
|
|
144
236
|
|
|
145
|
-
this._loading = true
|
|
237
|
+
this._loading = true;
|
|
146
238
|
try {
|
|
147
|
-
let pluginInstance = plugin
|
|
239
|
+
let pluginInstance = plugin;
|
|
148
240
|
|
|
149
241
|
if (!(plugin instanceof Plugin)) {
|
|
150
|
-
pluginInstance = this._createFromObject(plugin)
|
|
242
|
+
pluginInstance = this._createFromObject(plugin);
|
|
151
243
|
}
|
|
152
244
|
|
|
153
245
|
// 如果已注册,使用已注册的实例和状态
|
|
154
|
-
const existing = this._plugins.get(pluginInstance.name)
|
|
246
|
+
const existing = this._plugins.get(pluginInstance.name);
|
|
155
247
|
if (existing) {
|
|
156
|
-
pluginInstance = existing.instance
|
|
248
|
+
pluginInstance = existing.instance;
|
|
157
249
|
|
|
158
250
|
// 如果插件被禁用,跳过加载
|
|
159
251
|
if (!existing.enabled) {
|
|
160
|
-
|
|
161
|
-
return pluginInstance
|
|
252
|
+
this._log.info(`Plugin '${pluginInstance.name}' is disabled`);
|
|
253
|
+
return pluginInstance;
|
|
162
254
|
}
|
|
163
255
|
|
|
164
256
|
// 如果已加载且已启动,直接返回
|
|
165
257
|
if (existing.status === 'loaded' && pluginInstance._started) {
|
|
166
|
-
|
|
167
|
-
return pluginInstance
|
|
258
|
+
this._log.warn(`Plugin '${pluginInstance.name}' already loaded`);
|
|
259
|
+
return pluginInstance;
|
|
168
260
|
}
|
|
169
261
|
|
|
170
262
|
// 如果已加载但未启动(重载场景),直接启动
|
|
171
263
|
if (existing.status === 'loaded' && !pluginInstance._started) {
|
|
172
264
|
try {
|
|
173
265
|
if (typeof pluginInstance.start === 'function') {
|
|
174
|
-
await pluginInstance.start(this.framework)
|
|
175
|
-
pluginInstance._started = true
|
|
266
|
+
await pluginInstance.start(this.framework);
|
|
267
|
+
pluginInstance._started = true;
|
|
176
268
|
}
|
|
177
269
|
} catch (err) {
|
|
178
|
-
|
|
270
|
+
this._log.error(`Start failed for '${pluginInstance.name}':`, err.message);
|
|
179
271
|
}
|
|
180
|
-
this.framework.emit('plugin:loaded', pluginInstance)
|
|
181
|
-
return pluginInstance
|
|
272
|
+
this.framework.emit('plugin:loaded', pluginInstance);
|
|
273
|
+
return pluginInstance;
|
|
182
274
|
}
|
|
183
275
|
} else {
|
|
184
276
|
// 未注册,先注册
|
|
185
|
-
this.register(pluginInstance, options)
|
|
277
|
+
this.register(pluginInstance, options);
|
|
186
278
|
}
|
|
187
279
|
|
|
188
|
-
const entry = this._plugins.get(pluginInstance.name)
|
|
280
|
+
const entry = this._plugins.get(pluginInstance.name);
|
|
189
281
|
|
|
190
282
|
// 注册后再次检查 enabled 状态
|
|
191
283
|
if (!entry.enabled) {
|
|
192
|
-
|
|
193
|
-
return pluginInstance
|
|
284
|
+
return pluginInstance;
|
|
194
285
|
}
|
|
195
286
|
|
|
196
287
|
// 调用 install
|
|
197
288
|
try {
|
|
198
|
-
await entry.instance.install(this.framework)
|
|
289
|
+
await entry.instance.install(this.framework);
|
|
199
290
|
} catch (err) {
|
|
200
|
-
|
|
201
|
-
throw
|
|
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
|
+
});
|
|
202
295
|
}
|
|
203
296
|
|
|
204
|
-
entry.status = 'loaded'
|
|
297
|
+
entry.status = 'loaded';
|
|
205
298
|
|
|
206
299
|
// 如果处于 bootstrap 模式,由外部统一调用 startAll()
|
|
207
300
|
// 否则直接启动
|
|
@@ -211,94 +304,101 @@ class PluginManager {
|
|
|
211
304
|
// 非 bootstrap 模式下直接启动
|
|
212
305
|
try {
|
|
213
306
|
if (typeof pluginInstance.start === 'function') {
|
|
214
|
-
await pluginInstance.start(this.framework)
|
|
215
|
-
pluginInstance._started = true
|
|
307
|
+
await pluginInstance.start(this.framework);
|
|
308
|
+
pluginInstance._started = true;
|
|
216
309
|
}
|
|
217
310
|
} catch (err) {
|
|
218
|
-
|
|
311
|
+
this._log.error(`Start failed for '${pluginInstance.name}':`, err.message);
|
|
219
312
|
}
|
|
220
313
|
}
|
|
221
314
|
|
|
222
|
-
this.framework.emit('plugin:loaded', pluginInstance)
|
|
315
|
+
this.framework.emit('plugin:loaded', pluginInstance);
|
|
223
316
|
|
|
224
317
|
// 保存状态(创建或更新 state 文件)
|
|
225
|
-
this._saveState()
|
|
318
|
+
this._saveState();
|
|
226
319
|
|
|
227
|
-
return pluginInstance
|
|
320
|
+
return pluginInstance;
|
|
228
321
|
} finally {
|
|
229
|
-
this._loading = false
|
|
322
|
+
this._loading = false;
|
|
230
323
|
}
|
|
231
324
|
}
|
|
232
325
|
|
|
233
326
|
/**
|
|
234
327
|
* 设置 bootstrap 模式
|
|
328
|
+
* @param {boolean} value
|
|
235
329
|
*/
|
|
236
330
|
setBootstrapping(value) {
|
|
237
|
-
this._bootstrapping = value
|
|
331
|
+
this._bootstrapping = value;
|
|
238
332
|
}
|
|
239
333
|
|
|
240
334
|
/**
|
|
241
335
|
* 卸载插件
|
|
242
336
|
* @param {string} name - 插件名称
|
|
337
|
+
* @returns {Promise<boolean>}
|
|
243
338
|
*/
|
|
244
339
|
async unload(name) {
|
|
245
|
-
const entry = this._plugins.get(name)
|
|
340
|
+
const entry = this._plugins.get(name);
|
|
246
341
|
if (!entry) {
|
|
247
|
-
return false
|
|
342
|
+
return false;
|
|
248
343
|
}
|
|
249
344
|
|
|
250
|
-
const { instance } = entry
|
|
345
|
+
const { instance } = entry;
|
|
251
346
|
|
|
252
347
|
// 调用 uninstall
|
|
253
348
|
try {
|
|
254
|
-
await instance.uninstall(this.framework)
|
|
349
|
+
await instance.uninstall(this.framework);
|
|
255
350
|
} catch (err) {
|
|
256
|
-
|
|
351
|
+
this._log.error(`Uninstall error for '${name}':`, err.message);
|
|
257
352
|
}
|
|
258
353
|
|
|
259
|
-
entry.status = 'unloaded'
|
|
260
|
-
this.framework.emit('plugin:unloaded', instance)
|
|
261
|
-
return true
|
|
354
|
+
entry.status = 'unloaded';
|
|
355
|
+
this.framework.emit('plugin:unloaded', instance);
|
|
356
|
+
return true;
|
|
262
357
|
}
|
|
263
358
|
|
|
264
359
|
/**
|
|
265
360
|
* 重载插件
|
|
266
361
|
* @param {string} name - 插件名称
|
|
362
|
+
* @returns {Promise<Plugin>}
|
|
363
|
+
* @throws {PluginNotFoundError} 插件不存在时抛出
|
|
267
364
|
*/
|
|
268
365
|
async reload(name) {
|
|
269
|
-
const entry = this._plugins.get(name)
|
|
366
|
+
const entry = this._plugins.get(name);
|
|
270
367
|
if (!entry) {
|
|
271
|
-
throw new
|
|
368
|
+
throw new PluginNotFoundError(name);
|
|
272
369
|
}
|
|
273
370
|
|
|
274
|
-
const { instance } = entry
|
|
371
|
+
const { instance } = entry;
|
|
275
372
|
|
|
276
373
|
// 调用 reload
|
|
277
374
|
try {
|
|
278
|
-
await instance.reload(this.framework)
|
|
279
|
-
this.framework.emit('plugin:reloaded', instance)
|
|
375
|
+
await instance.reload(this.framework);
|
|
376
|
+
this.framework.emit('plugin:reloaded', instance);
|
|
280
377
|
} catch (err) {
|
|
281
|
-
|
|
282
|
-
throw
|
|
378
|
+
this._log.error(`Reload error for '${name}':`, err.message);
|
|
379
|
+
throw new PluginError(`Reload error for '${name}'`, {
|
|
380
|
+
context: { originalError: err.message },
|
|
381
|
+
});
|
|
283
382
|
}
|
|
284
383
|
|
|
285
|
-
return instance
|
|
384
|
+
return instance;
|
|
286
385
|
}
|
|
287
386
|
|
|
288
387
|
/**
|
|
289
388
|
* 重载所有插件
|
|
389
|
+
* @returns {Promise<void>}
|
|
290
390
|
*/
|
|
291
391
|
async reloadAll() {
|
|
292
392
|
// 1. 重置所有插件的启动标志
|
|
293
393
|
for (const entry of this._plugins.values()) {
|
|
294
|
-
entry.instance._started = false
|
|
394
|
+
entry.instance._started = false;
|
|
295
395
|
}
|
|
296
396
|
|
|
297
397
|
// 2. 扫描 .agent/plugins 目录,加载新插件
|
|
298
|
-
await this._discoverCustomPlugins()
|
|
398
|
+
await this._discoverCustomPlugins();
|
|
299
399
|
|
|
300
400
|
// 3. 启动所有未启动的插件
|
|
301
|
-
await this.startAll()
|
|
401
|
+
await this.startAll();
|
|
302
402
|
}
|
|
303
403
|
|
|
304
404
|
/**
|
|
@@ -312,39 +412,7 @@ class PluginManager {
|
|
|
312
412
|
* @private
|
|
313
413
|
*/
|
|
314
414
|
_resolvePluginPath(pluginsDir, name) {
|
|
315
|
-
|
|
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
|
|
415
|
+
return resolvePluginPath(pluginsDir, name, { logger: this._log });
|
|
348
416
|
}
|
|
349
417
|
|
|
350
418
|
/**
|
|
@@ -354,27 +422,7 @@ class PluginManager {
|
|
|
354
422
|
* @private
|
|
355
423
|
*/
|
|
356
424
|
_scanPluginNames(pluginsDir) {
|
|
357
|
-
|
|
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)
|
|
425
|
+
return scanPluginNames(pluginsDir);
|
|
378
426
|
}
|
|
379
427
|
|
|
380
428
|
/**
|
|
@@ -382,72 +430,76 @@ class PluginManager {
|
|
|
382
430
|
* @private
|
|
383
431
|
*/
|
|
384
432
|
async _discoverCustomPlugins() {
|
|
385
|
-
const pluginsDir = path.resolve(process.cwd(), '.agent', 'plugins')
|
|
433
|
+
const pluginsDir = path.resolve(process.cwd(), '.agent', 'plugins');
|
|
386
434
|
if (!fs.existsSync(pluginsDir)) {
|
|
387
|
-
return
|
|
435
|
+
return;
|
|
388
436
|
}
|
|
389
437
|
|
|
390
438
|
// 扫描所有插件名称(支持文件夹和单文件)
|
|
391
|
-
const pluginNames = this._scanPluginNames(pluginsDir)
|
|
439
|
+
const pluginNames = this._scanPluginNames(pluginsDir);
|
|
392
440
|
|
|
393
441
|
// 从 pluginsDir 推导 agentDir(pluginsDir = <agentDir>/plugins)
|
|
394
|
-
const agentDir = path.dirname(pluginsDir)
|
|
395
|
-
const agentNodeModules = path.join(agentDir, 'node_modules')
|
|
442
|
+
const agentDir = path.dirname(pluginsDir);
|
|
443
|
+
const agentNodeModules = path.join(agentDir, 'node_modules');
|
|
396
444
|
|
|
397
445
|
for (const pluginName of pluginNames) {
|
|
398
446
|
try {
|
|
399
|
-
const resolved = this._resolvePluginPath(pluginsDir, pluginName)
|
|
447
|
+
const resolved = this._resolvePluginPath(pluginsDir, pluginName);
|
|
400
448
|
if (!resolved) {
|
|
401
|
-
|
|
402
|
-
continue
|
|
449
|
+
this._log.warn(`Cannot resolve plugin: ${pluginName}`);
|
|
450
|
+
continue;
|
|
403
451
|
}
|
|
404
452
|
|
|
405
|
-
const { path: pluginPath, type } = resolved
|
|
453
|
+
const { path: pluginPath, type } = resolved;
|
|
406
454
|
|
|
407
455
|
// 添加模块路径到搜索路径(优先级从高到低)
|
|
408
456
|
const modulePathsToAdd = [
|
|
409
|
-
agentNodeModules,
|
|
410
|
-
path.join(__dirname, '..', '..', 'node_modules')
|
|
411
|
-
]
|
|
457
|
+
agentNodeModules, // .agent/node_modules(项目本地安装的包)
|
|
458
|
+
path.join(__dirname, '..', '..', 'node_modules'), // 全局安装的包
|
|
459
|
+
];
|
|
412
460
|
for (const mp of modulePathsToAdd) {
|
|
413
461
|
if (fs.existsSync(mp) && !module.paths.includes(mp)) {
|
|
414
|
-
module.paths.unshift(mp)
|
|
462
|
+
module.paths.unshift(mp);
|
|
415
463
|
}
|
|
416
464
|
}
|
|
417
465
|
|
|
418
466
|
// 清除缓存
|
|
419
|
-
delete require.cache[require.resolve(pluginPath)]
|
|
420
|
-
const pluginModule = require(pluginPath)
|
|
467
|
+
delete require.cache[require.resolve(pluginPath)];
|
|
468
|
+
const pluginModule = require(pluginPath);
|
|
421
469
|
|
|
422
|
-
let plugin
|
|
470
|
+
let plugin;
|
|
423
471
|
if (typeof pluginModule === 'function') {
|
|
424
|
-
plugin = pluginModule
|
|
472
|
+
plugin = pluginModule;
|
|
425
473
|
} else if (pluginModule.default) {
|
|
426
|
-
plugin = pluginModule.default
|
|
474
|
+
plugin = pluginModule.default;
|
|
427
475
|
} else {
|
|
428
|
-
plugin = pluginModule
|
|
476
|
+
plugin = pluginModule;
|
|
429
477
|
}
|
|
430
478
|
|
|
431
479
|
// 获取插件名称
|
|
432
|
-
let resolvedPluginName
|
|
480
|
+
let resolvedPluginName;
|
|
433
481
|
try {
|
|
434
|
-
const tempPlugin =
|
|
435
|
-
|
|
436
|
-
|
|
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;
|
|
437
489
|
} catch {
|
|
438
|
-
resolvedPluginName = pluginName
|
|
490
|
+
resolvedPluginName = pluginName;
|
|
439
491
|
}
|
|
440
492
|
|
|
441
493
|
// 如果插件已加载且已启动,跳过
|
|
442
494
|
if (this.has(resolvedPluginName) && this.get(resolvedPluginName)?._started) {
|
|
443
|
-
continue
|
|
495
|
+
continue;
|
|
444
496
|
}
|
|
445
497
|
|
|
446
|
-
|
|
498
|
+
this._log.info(`Loading new plugin: ${pluginName} (${type})`);
|
|
447
499
|
// .agent/plugins 目录下的插件默认强制启用,不受 state 文件影响
|
|
448
|
-
await this.load(plugin, { forceEnabled: true })
|
|
500
|
+
await this.load(plugin, { forceEnabled: true });
|
|
449
501
|
} catch (err) {
|
|
450
|
-
|
|
502
|
+
this._log.error(`Failed to load plugin ${pluginName}:`, err.message);
|
|
451
503
|
}
|
|
452
504
|
}
|
|
453
505
|
}
|
|
@@ -455,117 +507,125 @@ class PluginManager {
|
|
|
455
507
|
/**
|
|
456
508
|
* 获取插件
|
|
457
509
|
* @param {string} name - 插件名称
|
|
510
|
+
* @returns {Plugin|undefined}
|
|
458
511
|
*/
|
|
459
512
|
get(name) {
|
|
460
|
-
return this._plugins.get(name)?.instance
|
|
513
|
+
return this._plugins.get(name)?.instance;
|
|
461
514
|
}
|
|
462
515
|
|
|
463
516
|
/**
|
|
464
517
|
* 获取所有已加载且已启用的插件
|
|
518
|
+
* @returns {Array<{name: string, instance: Plugin}>}
|
|
465
519
|
*/
|
|
466
520
|
getAll() {
|
|
467
521
|
return Array.from(this._plugins.values())
|
|
468
|
-
.filter(e => e.status === 'loaded' && e.enabled)
|
|
469
|
-
.map(e => ({ name: e.instance.name, instance: e.instance }))
|
|
522
|
+
.filter((e) => e.status === 'loaded' && e.enabled)
|
|
523
|
+
.map((e) => ({ name: e.instance.name, instance: e.instance }));
|
|
470
524
|
}
|
|
471
525
|
|
|
472
526
|
/**
|
|
473
527
|
* 注册一个已知插件(但不加载)
|
|
474
528
|
* 用于显示所有可用插件列表
|
|
475
529
|
* @param {string} name - 插件名称
|
|
476
|
-
* @param {Object} info - 插件信息
|
|
530
|
+
* @param {Object} [info] - 插件信息
|
|
477
531
|
*/
|
|
478
532
|
registerKnownPlugin(name, info = {}) {
|
|
479
|
-
this._knownPlugins.add(name)
|
|
533
|
+
this._knownPlugins.add(name);
|
|
480
534
|
// 如果插件还没注册过,记录它的信息
|
|
481
535
|
if (!this._plugins.has(name)) {
|
|
482
536
|
this._plugins.set(name, {
|
|
483
537
|
instance: { name, ...info },
|
|
484
538
|
status: 'known',
|
|
485
|
-
enabled: info.enabled !== undefined ? info.enabled : false
|
|
486
|
-
})
|
|
539
|
+
enabled: info.enabled !== undefined ? info.enabled : false,
|
|
540
|
+
});
|
|
487
541
|
}
|
|
488
542
|
}
|
|
489
543
|
|
|
490
544
|
/**
|
|
491
545
|
* 获取所有已知插件(包括未加载的)
|
|
492
|
-
* @returns {Array
|
|
546
|
+
* @returns {Array<{name: string, status: string, enabled: boolean, version?: string, system?: boolean}>}
|
|
493
547
|
*/
|
|
494
548
|
getAllKnown() {
|
|
495
|
-
const result = []
|
|
549
|
+
const result = [];
|
|
496
550
|
for (const name of this._knownPlugins) {
|
|
497
|
-
const entry = this._plugins.get(name)
|
|
551
|
+
const entry = this._plugins.get(name);
|
|
498
552
|
result.push({
|
|
499
553
|
name,
|
|
500
554
|
status: entry?.status || 'unknown',
|
|
501
555
|
enabled: entry?.enabled || false,
|
|
502
556
|
version: entry?.instance?.version,
|
|
503
|
-
system: entry?.instance?.system || false
|
|
504
|
-
})
|
|
557
|
+
system: entry?.instance?.system || false,
|
|
558
|
+
});
|
|
505
559
|
}
|
|
506
560
|
// 也加入已加载但不在 knownPlugins 中的
|
|
507
561
|
for (const [name, entry] of this._plugins) {
|
|
508
|
-
if (!result.find(p => p.name === name)) {
|
|
562
|
+
if (!result.find((p) => p.name === name)) {
|
|
509
563
|
result.push({
|
|
510
564
|
name,
|
|
511
565
|
status: entry.status,
|
|
512
566
|
enabled: entry.enabled,
|
|
513
567
|
version: entry.instance?.version,
|
|
514
|
-
system: entry.instance?.system || false
|
|
515
|
-
})
|
|
568
|
+
system: entry.instance?.system || false,
|
|
569
|
+
});
|
|
516
570
|
}
|
|
517
571
|
}
|
|
518
|
-
return result
|
|
572
|
+
return result;
|
|
519
573
|
}
|
|
520
574
|
|
|
521
575
|
/**
|
|
522
576
|
* 检查插件是否存在
|
|
523
577
|
* @param {string} name - 插件名称
|
|
578
|
+
* @returns {boolean}
|
|
524
579
|
*/
|
|
525
580
|
has(name) {
|
|
526
|
-
return this._plugins.has(name) || this._knownPlugins.has(name)
|
|
581
|
+
return this._plugins.has(name) || this._knownPlugins.has(name);
|
|
527
582
|
}
|
|
528
583
|
|
|
529
584
|
/**
|
|
530
585
|
* 检查插件是否已加载
|
|
531
586
|
* @param {string} name - 插件名称
|
|
587
|
+
* @returns {boolean}
|
|
532
588
|
*/
|
|
533
589
|
isLoaded(name) {
|
|
534
|
-
return this._plugins.get(name)?.status === 'loaded'
|
|
590
|
+
return this._plugins.get(name)?.status === 'loaded';
|
|
535
591
|
}
|
|
536
592
|
|
|
537
593
|
/**
|
|
538
594
|
* 检查插件是否启用
|
|
539
595
|
* @param {string} name - 插件名称
|
|
596
|
+
* @returns {boolean}
|
|
540
597
|
*/
|
|
541
598
|
isEnabled(name) {
|
|
542
|
-
return this._plugins.get(name)?.enabled === true
|
|
599
|
+
return this._plugins.get(name)?.enabled === true;
|
|
543
600
|
}
|
|
544
601
|
|
|
545
602
|
/**
|
|
546
603
|
* 启用插件
|
|
547
604
|
* @param {string} name - 插件名称
|
|
605
|
+
* @returns {Promise<void>}
|
|
606
|
+
* @throws {PluginNotFoundError} 插件不存在时抛出
|
|
607
|
+
* @throws {PluginError} 系统插件不能被禁用
|
|
548
608
|
*/
|
|
549
609
|
async enable(name) {
|
|
550
|
-
const entry = this._plugins.get(name)
|
|
610
|
+
const entry = this._plugins.get(name);
|
|
551
611
|
if (!entry) {
|
|
552
|
-
throw new
|
|
612
|
+
throw new PluginNotFoundError(name);
|
|
553
613
|
}
|
|
554
614
|
|
|
555
615
|
// 系统插件不能被禁用,所以启用没有意义
|
|
556
616
|
if (entry.instance?.system) {
|
|
557
|
-
throw new
|
|
617
|
+
throw new PluginError(`Plugin '${name}' is a system plugin, cannot be disabled`);
|
|
558
618
|
}
|
|
559
619
|
|
|
560
620
|
if (entry.enabled) {
|
|
561
|
-
|
|
562
|
-
return
|
|
621
|
+
this._log.info(`Plugin '${name}' already enabled`);
|
|
622
|
+
return;
|
|
563
623
|
}
|
|
564
624
|
|
|
565
|
-
entry.enabled = true
|
|
625
|
+
entry.enabled = true;
|
|
566
626
|
// 同步更新插件实例的 enabled 属性,避免 load() 时被跳过
|
|
567
627
|
if (entry.instance) {
|
|
568
|
-
entry.instance.enabled = true
|
|
628
|
+
entry.instance.enabled = true;
|
|
569
629
|
}
|
|
570
630
|
|
|
571
631
|
// 如果插件已加载,尝试重新启动
|
|
@@ -574,54 +634,57 @@ class PluginManager {
|
|
|
574
634
|
// 如果之前已经启动过,先调用 stop 停止旧实例
|
|
575
635
|
if (entry.instance._started) {
|
|
576
636
|
if (typeof entry.instance.stop === 'function') {
|
|
577
|
-
await entry.instance.stop()
|
|
637
|
+
await entry.instance.stop();
|
|
578
638
|
} else if (typeof entry.instance.stopBot === 'function') {
|
|
579
|
-
await entry.instance.stopBot()
|
|
639
|
+
await entry.instance.stopBot();
|
|
580
640
|
}
|
|
581
641
|
}
|
|
582
642
|
// 调用 reload 让插件重新初始化(会调用 install 和 start)
|
|
583
|
-
await this.reload(name)
|
|
643
|
+
await this.reload(name);
|
|
584
644
|
} catch (err) {
|
|
585
|
-
|
|
645
|
+
this._log.error(`Enable/reload failed for '${name}':`, err.message);
|
|
586
646
|
}
|
|
587
647
|
} else if (entry.status === 'registered' || entry.status === 'known') {
|
|
588
648
|
// 插件只注册过但未加载,现在加载它
|
|
589
649
|
try {
|
|
590
|
-
await this.load(entry.instance)
|
|
650
|
+
await this.load(entry.instance);
|
|
591
651
|
} catch (err) {
|
|
592
|
-
|
|
652
|
+
this._log.error(`Enable/load failed for '${name}':`, err.message);
|
|
593
653
|
}
|
|
594
654
|
}
|
|
595
655
|
|
|
596
|
-
this.framework.emit('plugin:enabled', entry.instance)
|
|
597
|
-
this._saveState()
|
|
598
|
-
|
|
656
|
+
this.framework.emit('plugin:enabled', entry.instance);
|
|
657
|
+
this._saveState();
|
|
658
|
+
this._log.info(`Plugin '${name}' enabled`);
|
|
599
659
|
}
|
|
600
660
|
|
|
601
661
|
/**
|
|
602
662
|
* 禁用插件
|
|
603
663
|
* @param {string} name - 插件名称
|
|
664
|
+
* @returns {Promise<void>}
|
|
665
|
+
* @throws {PluginNotFoundError} 插件不存在时抛出
|
|
666
|
+
* @throws {PluginError} 系统插件不能被禁用
|
|
604
667
|
*/
|
|
605
668
|
async disable(name) {
|
|
606
|
-
const entry = this._plugins.get(name)
|
|
669
|
+
const entry = this._plugins.get(name);
|
|
607
670
|
if (!entry) {
|
|
608
|
-
throw new
|
|
671
|
+
throw new PluginNotFoundError(name);
|
|
609
672
|
}
|
|
610
673
|
|
|
611
674
|
// 系统插件不能被禁用
|
|
612
675
|
if (entry.instance?.system) {
|
|
613
|
-
throw new
|
|
676
|
+
throw new PluginError(`Plugin '${name}' is a system plugin, cannot be disabled`);
|
|
614
677
|
}
|
|
615
678
|
|
|
616
679
|
if (!entry.enabled) {
|
|
617
|
-
|
|
618
|
-
return
|
|
680
|
+
this._log.info(`Plugin '${name}' already disabled`);
|
|
681
|
+
return;
|
|
619
682
|
}
|
|
620
683
|
|
|
621
|
-
entry.enabled = false
|
|
684
|
+
entry.enabled = false;
|
|
622
685
|
// 同步更新插件实例的 enabled 属性
|
|
623
686
|
if (entry.instance) {
|
|
624
|
-
entry.instance.enabled = false
|
|
687
|
+
entry.instance.enabled = false;
|
|
625
688
|
}
|
|
626
689
|
|
|
627
690
|
// 如果插件正在运行,停止它
|
|
@@ -629,86 +692,70 @@ class PluginManager {
|
|
|
629
692
|
try {
|
|
630
693
|
// 优先调用 stop 方法,其次调用 stopBot 方法
|
|
631
694
|
if (typeof entry.instance.stop === 'function') {
|
|
632
|
-
await entry.instance.stop()
|
|
695
|
+
await entry.instance.stop();
|
|
633
696
|
} else if (typeof entry.instance.stopBot === 'function') {
|
|
634
|
-
await entry.instance.stopBot()
|
|
697
|
+
await entry.instance.stopBot();
|
|
635
698
|
}
|
|
636
|
-
entry.instance._started = false
|
|
699
|
+
entry.instance._started = false;
|
|
637
700
|
} catch (err) {
|
|
638
|
-
|
|
701
|
+
this._log.error(`Stop failed for '${name}':`, err.message);
|
|
639
702
|
}
|
|
640
703
|
}
|
|
641
704
|
|
|
642
|
-
this.framework.emit('plugin:disabled', entry.instance)
|
|
643
|
-
this._saveState()
|
|
644
|
-
|
|
705
|
+
this.framework.emit('plugin:disabled', entry.instance);
|
|
706
|
+
this._saveState();
|
|
707
|
+
this._log.info(`Plugin '${name}' disabled`);
|
|
645
708
|
}
|
|
646
709
|
|
|
647
710
|
/**
|
|
648
711
|
* 更新插件配置
|
|
649
712
|
* @param {string} name - 插件名称
|
|
650
713
|
* @param {Object} config - 新配置(会合并到现有配置)
|
|
714
|
+
* @returns {Object} 更新后的配置
|
|
715
|
+
* @throws {PluginNotFoundError} 插件不存在时抛出
|
|
651
716
|
*/
|
|
652
717
|
updatePluginConfig(name, config) {
|
|
653
|
-
const entry = this._plugins.get(name)
|
|
718
|
+
const entry = this._plugins.get(name);
|
|
654
719
|
if (!entry) {
|
|
655
|
-
throw new
|
|
720
|
+
throw new PluginNotFoundError(name);
|
|
656
721
|
}
|
|
657
722
|
|
|
658
723
|
if (!entry.instance.config) {
|
|
659
|
-
entry.instance.config = {}
|
|
724
|
+
entry.instance.config = {};
|
|
660
725
|
}
|
|
661
726
|
|
|
662
727
|
// 合并配置
|
|
663
|
-
entry.instance.config = { ...entry.instance.config, ...config }
|
|
728
|
+
entry.instance.config = { ...entry.instance.config, ...config };
|
|
664
729
|
|
|
665
730
|
// 保存状态
|
|
666
|
-
this._saveState()
|
|
667
|
-
|
|
731
|
+
this._saveState();
|
|
732
|
+
this._log.info(`Plugin '${name}' config updated`);
|
|
668
733
|
|
|
669
|
-
return entry.instance.config
|
|
734
|
+
return entry.instance.config;
|
|
670
735
|
}
|
|
671
736
|
|
|
672
737
|
/**
|
|
673
738
|
* 启动所有已加载但未启动的插件(按优先级排序)
|
|
739
|
+
* @returns {Promise<void>}
|
|
674
740
|
*/
|
|
675
741
|
async startAll() {
|
|
676
742
|
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))
|
|
743
|
+
.filter((e) => e.status === 'loaded' && e.enabled) // 只启动已启用且已加载的插件
|
|
744
|
+
.sort((a, b) => (a.instance.priority || 100) - (b.instance.priority || 100));
|
|
679
745
|
|
|
680
746
|
for (const entry of entries) {
|
|
681
|
-
const instance = entry.instance
|
|
747
|
+
const instance = entry.instance;
|
|
682
748
|
// 跳过已经启动过的插件
|
|
683
749
|
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)
|
|
750
|
+
continue;
|
|
693
751
|
}
|
|
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
752
|
try {
|
|
707
753
|
if (typeof instance.start === 'function') {
|
|
708
|
-
await instance.start(this.framework)
|
|
754
|
+
await instance.start(this.framework);
|
|
755
|
+
instance._started = true;
|
|
709
756
|
}
|
|
710
757
|
} catch (err) {
|
|
711
|
-
|
|
758
|
+
this._log.error(`Start failed for '${instance.name}':`, err.message);
|
|
712
759
|
}
|
|
713
760
|
}
|
|
714
761
|
}
|
|
@@ -716,52 +763,101 @@ class PluginManager {
|
|
|
716
763
|
/**
|
|
717
764
|
* 从对象创建插件实例
|
|
718
765
|
* @private
|
|
766
|
+
* @param {Object|Function} obj
|
|
767
|
+
* @returns {Plugin}
|
|
719
768
|
*/
|
|
720
769
|
_createFromObject(obj) {
|
|
721
|
-
const { Plugin } = require('./plugin-base')
|
|
770
|
+
const { Plugin } = require('./plugin-base');
|
|
722
771
|
|
|
723
772
|
// 如果是类(构造函数),直接实例化
|
|
724
773
|
if (typeof obj === 'function') {
|
|
725
774
|
// 检查是否是 Plugin 的子类(通过 prototype chain)
|
|
726
775
|
if (obj.prototype instanceof Plugin) {
|
|
727
|
-
return new obj()
|
|
776
|
+
return new obj();
|
|
728
777
|
}
|
|
729
778
|
// 否则是工厂函数,调用它获取类或实例
|
|
730
|
-
const result = obj(Plugin)
|
|
779
|
+
const result = obj(Plugin);
|
|
731
780
|
// 递归处理返回值
|
|
732
781
|
if (typeof result === 'function' && result.prototype instanceof Plugin) {
|
|
733
|
-
return new result()
|
|
782
|
+
return new result();
|
|
734
783
|
}
|
|
735
|
-
return result
|
|
784
|
+
return result;
|
|
736
785
|
}
|
|
737
786
|
|
|
738
787
|
// 支持对象形式: { name, version, install, start, ... }
|
|
739
788
|
class AnonymousPlugin extends Plugin {
|
|
740
789
|
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
|
|
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;
|
|
746
795
|
|
|
747
796
|
// 如果提供了 install/start/reload/uninstall 方法,绑定它们
|
|
748
797
|
if (typeof obj.install === 'function') {
|
|
749
|
-
this.install = obj.install.bind(this)
|
|
798
|
+
this.install = obj.install.bind(this);
|
|
750
799
|
}
|
|
751
800
|
if (typeof obj.start === 'function') {
|
|
752
|
-
this.start = obj.start.bind(this)
|
|
801
|
+
this.start = obj.start.bind(this);
|
|
753
802
|
}
|
|
754
803
|
if (typeof obj.reload === 'function') {
|
|
755
|
-
this.reload = obj.reload.bind(this)
|
|
804
|
+
this.reload = obj.reload.bind(this);
|
|
756
805
|
}
|
|
757
806
|
if (typeof obj.uninstall === 'function') {
|
|
758
|
-
this.uninstall = obj.uninstall.bind(this)
|
|
807
|
+
this.uninstall = obj.uninstall.bind(this);
|
|
759
808
|
}
|
|
760
809
|
}
|
|
761
810
|
}
|
|
762
811
|
|
|
763
|
-
return new AnonymousPlugin()
|
|
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);
|
|
764
860
|
}
|
|
765
861
|
}
|
|
766
862
|
|
|
767
|
-
module.exports = { PluginManager }
|
|
863
|
+
module.exports = { PluginManager };
|