foliko 1.1.93 → 2.0.0
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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +56 -30
- package/REFACTORING_PLAN.md +645 -0
- package/docs/architecture.md +131 -0
- package/docs/migration.md +57 -0
- package/docs/public-api.md +138 -0
- package/docs/usage.md +385 -0
- package/examples/ambient-example.js +20 -137
- package/examples/basic.js +21 -48
- package/examples/bootstrap.js +16 -74
- package/examples/mcp-example.js +6 -29
- package/examples/skill-example.js +6 -19
- package/examples/workflow.js +8 -56
- package/package.json +8 -4
- package/plugins/README.md +49 -0
- package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
- package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
- package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
- package/plugins/ambient/README.md +14 -0
- package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
- package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
- package/plugins/{ambient-agent → ambient}/index.js +2 -2
- package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
- package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
- package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
- package/plugins/core/default/bootstrap.js +202 -0
- package/plugins/core/default/config.js +220 -0
- package/plugins/core/default/index.js +58 -0
- package/plugins/core/mcp/index.js +1 -0
- package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
- package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
- package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
- package/plugins/{session-plugin.js → core/session/index.js} +9 -73
- package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
- package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
- package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
- package/plugins/{think-plugin.js → core/think/index.js} +24 -91
- package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
- package/plugins/default-plugins.js +6 -720
- package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
- package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
- package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
- package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
- package/plugins/install/README.md +9 -0
- package/plugins/{install-plugin.js → install/index.js} +3 -3
- package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
- package/plugins/{web-plugin.js → io/web/index.js} +11 -113
- package/plugins/memory/README.md +13 -0
- package/plugins/{memory-plugin.js → memory/index.js} +4 -18
- package/plugins/messaging/email/README.md +19 -0
- package/plugins/{email → messaging/email}/index.js +2 -2
- package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
- package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
- package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
- package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
- package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
- package/plugins/{tools-plugin.js → tools/index.js} +68 -116
- package/plugins/trading/README.md +15 -0
- package/plugins/{gate-trading.js → trading/index.js} +8 -8
- package/{examples → sandbox}/test-concurrent-chat.js +2 -2
- package/{examples → sandbox}/test-long-chat.js +2 -2
- package/{examples → sandbox}/test-session-chat.js +2 -2
- package/{examples → sandbox}/test-web-plugin.js +1 -1
- package/{examples → sandbox}/test-weixin-feishu.js +2 -2
- package/src/agent/base.js +56 -0
- package/src/{core/agent-chat.js → agent/chat.js} +11 -11
- package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
- package/src/agent/index.js +111 -0
- package/src/agent/main.js +337 -0
- package/src/agent/prompt.js +78 -0
- package/src/agent/sub.js +198 -0
- package/src/agent/worker.js +104 -0
- package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
- package/{cli/src → src/cli}/commands/chat.js +25 -21
- package/{cli/src → src/cli}/index.js +1 -0
- package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
- package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
- package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
- package/src/common/errors.js +402 -0
- package/src/{utils → common}/logger.js +33 -0
- package/src/{utils/chat-queue.js → common/queue.js} +2 -2
- package/src/config/plugin-config.js +50 -0
- package/src/context/agent.js +32 -0
- package/src/context/compaction-prompts.js +170 -0
- package/src/context/compaction-utils.js +191 -0
- package/src/context/compressor.js +413 -0
- package/src/context/index.js +9 -0
- package/src/{core/context-manager.js → context/manager.js} +1 -1
- package/src/context/request.js +50 -0
- package/src/context/session.js +33 -0
- package/src/context/storage.js +30 -0
- package/src/executors/mcp-client.js +153 -0
- package/src/executors/mcp-desc.js +236 -0
- package/src/executors/mcp-executor.js +91 -956
- package/src/{core → framework}/command-registry.js +1 -1
- package/src/framework/framework.js +300 -0
- package/src/framework/index.js +18 -0
- package/src/framework/lifecycle.js +203 -0
- package/src/framework/loader.js +78 -0
- package/src/framework/registry.js +86 -0
- package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
- package/src/index.js +130 -15
- package/src/llm/index.js +26 -0
- package/src/llm/provider.js +212 -0
- package/src/llm/registry.js +11 -0
- package/src/{core/token-counter.js → llm/tokens.js} +4 -37
- package/src/{core/plugin-base.js → plugin/base.js} +10 -136
- package/src/plugin/index.js +14 -0
- package/src/plugin/loader.js +101 -0
- package/src/plugin/manager.js +261 -0
- package/src/{core → session}/branch-summary-auto.js +2 -2
- package/src/{core/chat-session.js → session/chat.js} +2 -2
- package/src/session/index.js +7 -0
- package/src/{core/session-manager.js → session/session.js} +2 -2
- package/src/session/ttl.js +92 -0
- package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
- package/src/tool/executor.js +85 -0
- package/src/tool/index.js +15 -0
- package/src/tool/registry.js +143 -0
- package/src/{core/tool-router.js → tool/router.js} +17 -124
- package/src/tool/schema.js +108 -0
- package/src/utils/data-splitter.js +1 -1
- package/src/utils/download.js +1 -1
- package/src/utils/index.js +6 -6
- package/src/utils/message-validator.js +1 -1
- package/tests/core/context-storage.test.js +46 -0
- package/tests/core/llm.test.js +54 -0
- package/tests/core/plugin.test.js +42 -0
- package/tests/core/tool.test.js +60 -0
- package/tests/setup.js +10 -0
- package/tests/smoke.test.js +58 -0
- package/vitest.config.js +9 -0
- package/cli/src/daemon.js +0 -149
- package/docs/CONTEXT_DESIGN.md +0 -1596
- package/docs/ai-sdk-optimization.md +0 -655
- package/docs/features.md +0 -120
- package/docs/qq-bot.md +0 -976
- package/docs/quick-reference.md +0 -160
- package/docs/user-manual.md +0 -1391
- package/images/geometric_shapes.jpg +0 -0
- package/images/sunset_mountain_lake.jpg +0 -0
- package/skills/poster-guide/SKILL.md +0 -792
- package/src/capabilities/index.js +0 -11
- package/src/core/agent.js +0 -808
- package/src/core/context-compressor.js +0 -959
- package/src/core/enhanced-context-compressor.js +0 -210
- package/src/core/framework.js +0 -1422
- package/src/core/index.js +0 -30
- package/src/core/plugin-manager.js +0 -961
- package/src/core/provider-registry.js +0 -159
- package/src/core/provider.js +0 -156
- package/src/core/request-context.js +0 -98
- package/src/core/subagent.js +0 -442
- package/src/core/system-prompt-builder.js +0 -120
- package/src/core/tool-executor.js +0 -202
- package/src/core/tool-registry.js +0 -517
- package/src/core/worker-agent.js +0 -192
- package/src/executors/executor-base.js +0 -58
- package/src/utils/error-boundary.js +0 -363
- package/src/utils/error.js +0 -374
- package/system.md +0 -1645
- package/website_v2/README.md +0 -57
- package/website_v2/SPEC.md +0 -1
- package/website_v2/docs/api.html +0 -128
- package/website_v2/docs/configuration.html +0 -147
- package/website_v2/docs/plugin-development.html +0 -129
- package/website_v2/docs/project-structure.html +0 -89
- package/website_v2/docs/skill-development.html +0 -85
- package/website_v2/index.html +0 -489
- package/website_v2/scripts/main.js +0 -93
- package/website_v2/styles/animations.css +0 -8
- package/website_v2/styles/docs.css +0 -83
- package/website_v2/styles/main.css +0 -417
- package/xhs_auth.json +0 -268
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
- /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/handlers.js +0 -0
- /package/plugins/{email → messaging/email}/monitor.js +0 -0
- /package/plugins/{email → messaging/email}/parser.js +0 -0
- /package/plugins/{email → messaging/email}/reply.js +0 -0
- /package/plugins/{email → messaging/email}/utils.js +0 -0
- /package/{examples → sandbox}/test-chat.js +0 -0
- /package/{examples → sandbox}/test-mcp.js +0 -0
- /package/{examples → sandbox}/test-reload.js +0 -0
- /package/{examples → sandbox}/test-telegram.js +0 -0
- /package/{examples → sandbox}/test-tg-bot.js +0 -0
- /package/{examples → sandbox}/test-tg-simple.js +0 -0
- /package/{examples → sandbox}/test-tg.js +0 -0
- /package/{examples → sandbox}/test-think.js +0 -0
- /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
- /package/{cli/src → src/cli}/commands/daemon.js +0 -0
- /package/{cli/src → src/cli}/commands/list.js +0 -0
- /package/{cli/src → src/cli}/commands/plugin.js +0 -0
- /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
- /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
- /package/{cli/src → src/cli}/utils/ansi.js +0 -0
- /package/{cli/src → src/cli}/utils/config.js +0 -0
- /package/{cli/src → src/cli}/utils/markdown.js +0 -0
- /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
- /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
- /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
- /package/src/{core → common}/constants.js +0 -0
- /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
- /package/src/{utils/event-emitter.js → common/events.js} +0 -0
- /package/src/{utils → common}/id.js +0 -0
- /package/src/{utils → common}/retry.js +0 -0
- /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
- /package/src/{core/session-entry.js → session/entry.js} +0 -0
- /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
|
@@ -1,94 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Plugin 基类
|
|
3
5
|
* 所有插件的基类
|
|
4
6
|
*/
|
|
5
7
|
|
|
8
|
+
const { logger } = require('../common/logger');
|
|
9
|
+
const log = logger.child('plugin');
|
|
10
|
+
|
|
6
11
|
class Plugin {
|
|
7
|
-
/**
|
|
8
|
-
* 插件名称
|
|
9
|
-
* @type {string}
|
|
10
|
-
*/
|
|
11
12
|
name = 'unnamed-plugin';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 插件版本
|
|
15
|
-
* @type {string}
|
|
16
|
-
*/
|
|
17
13
|
version = '1.0.0';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 插件描述
|
|
21
|
-
* @type {string}
|
|
22
|
-
*/
|
|
23
14
|
description = '';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 插件优先级,数值越小越先加载
|
|
27
|
-
* @type {number}
|
|
28
|
-
*/
|
|
29
15
|
priority = 100;
|
|
30
16
|
|
|
31
|
-
/**
|
|
32
|
-
* 子Agent配置数组 - 配置后自动注册子Agent
|
|
33
|
-
* 格式: [{ name: 'xxx', role: '角色', tools: {...}, parentTools: [...] }]
|
|
34
|
-
* @type {Array}
|
|
35
|
-
*/
|
|
36
17
|
agents = [];
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 插件是否启用,默认为 true
|
|
40
|
-
* 设置为 false 时插件不会被加载,但在列表中仍可见
|
|
41
|
-
* @type {boolean}
|
|
42
|
-
*/
|
|
43
18
|
enabled = true;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 是否为系统插件,默认为 false
|
|
47
|
-
* 系统插件不能被禁用,始终启用
|
|
48
|
-
* @type {boolean}
|
|
49
|
-
*/
|
|
50
19
|
system = false;
|
|
51
20
|
|
|
52
21
|
_framework = null;
|
|
53
22
|
_subAgents = [];
|
|
54
|
-
_promptParts = [];
|
|
55
|
-
_deferredPromptParts = null;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 插件注册的工具集(供 ExtensionExecutor 自动加载)
|
|
59
|
-
* 格式: { toolName: { name, description, inputSchema, execute } }
|
|
60
|
-
* @type {Object}
|
|
61
|
-
*/
|
|
23
|
+
_promptParts = [];
|
|
24
|
+
_deferredPromptParts = null;
|
|
62
25
|
tools = {};
|
|
63
26
|
|
|
64
|
-
/**
|
|
65
|
-
* 安装插件 - 注册工具/事件等
|
|
66
|
-
* @param {Framework} framework - 框架实例
|
|
67
|
-
*/
|
|
68
27
|
install(framework) {
|
|
69
28
|
this._framework = framework;
|
|
70
29
|
return this;
|
|
71
30
|
}
|
|
72
31
|
|
|
73
|
-
/**
|
|
74
|
-
* 注册工具到扩展执行器(供 ExtensionExecutor 统一管理)
|
|
75
|
-
* 支持三种调用方式:
|
|
76
|
-
* this.registerTool('name', { description, inputSchema, execute })
|
|
77
|
-
* this.registerTool({ name, description, inputSchema, execute })
|
|
78
|
-
* this.registerTool({ name, description, inputSchema, execute, pluginName: 'xxx' })
|
|
79
|
-
* @param {string|Object} name - 工具名称或工具定义对象
|
|
80
|
-
* @param {Object} [def] - 工具定义 { description, inputSchema, execute }
|
|
81
|
-
*/
|
|
82
32
|
registerTool(name, def) {
|
|
83
33
|
if (!this._framework) return;
|
|
84
|
-
// 支持 this.registerTool({ name, description, ... }) 形式
|
|
85
34
|
if (typeof name === 'object' && name !== null) {
|
|
86
35
|
def = name;
|
|
87
36
|
name = def.name;
|
|
88
37
|
}
|
|
89
38
|
if (!name || !def) return;
|
|
90
39
|
|
|
91
|
-
// 支持自定义插件名
|
|
92
40
|
const pluginName = def.pluginName || this.name;
|
|
93
41
|
delete def.pluginName;
|
|
94
42
|
|
|
@@ -106,39 +54,25 @@ class Plugin {
|
|
|
106
54
|
}
|
|
107
55
|
}
|
|
108
56
|
|
|
109
|
-
/**
|
|
110
|
-
* 启动插件 - 初始化完成后的启动
|
|
111
|
-
* @param {Framework} framework - 框架实例
|
|
112
|
-
*/
|
|
113
57
|
start(framework) {
|
|
114
|
-
// 自动注册配置的子Agent
|
|
115
58
|
this._autoRegisterSubAgents();
|
|
116
59
|
return this;
|
|
117
60
|
}
|
|
118
61
|
|
|
119
|
-
/**
|
|
120
|
-
* 自动注册子Agent(根据 this.agents 配置)
|
|
121
|
-
* @private
|
|
122
|
-
*/
|
|
123
62
|
_autoRegisterSubAgents() {
|
|
124
|
-
// 确定要注册的agent配置
|
|
125
63
|
const agentsToRegister = this._deferredSubAgents || this.agents;
|
|
126
64
|
if (!agentsToRegister || !Array.isArray(agentsToRegister) || agentsToRegister.length === 0) {
|
|
127
65
|
return;
|
|
128
66
|
}
|
|
129
67
|
|
|
130
|
-
// 获取父Agent
|
|
131
68
|
const parentAgent = this._getParentAgent();
|
|
132
69
|
|
|
133
70
|
if (!parentAgent) {
|
|
134
|
-
// 没有父Agent,推迟到框架准备好时再注册
|
|
135
71
|
if (!this._deferredSubAgents) {
|
|
136
72
|
log.debug(` No parent agent found, deferring subagent registration`);
|
|
137
73
|
this._deferredSubAgents = agentsToRegister;
|
|
138
|
-
// 监听框架准备好事件
|
|
139
74
|
if (this._framework) {
|
|
140
75
|
this._framework.once('framework:ready', () => {
|
|
141
|
-
// 延迟注册,确保主Agent已创建
|
|
142
76
|
setTimeout(() => this._autoRegisterSubAgents(), 100);
|
|
143
77
|
});
|
|
144
78
|
}
|
|
@@ -146,7 +80,6 @@ class Plugin {
|
|
|
146
80
|
return;
|
|
147
81
|
}
|
|
148
82
|
|
|
149
|
-
// 有父Agent了,清除延迟标记
|
|
150
83
|
this._deferredSubAgents = null;
|
|
151
84
|
|
|
152
85
|
for (const agentConfig of agentsToRegister) {
|
|
@@ -159,10 +92,6 @@ class Plugin {
|
|
|
159
92
|
}
|
|
160
93
|
}
|
|
161
94
|
|
|
162
|
-
/**
|
|
163
|
-
* 获取父Agent
|
|
164
|
-
* @private
|
|
165
|
-
*/
|
|
166
95
|
_getParentAgent() {
|
|
167
96
|
if (!this._framework) return null;
|
|
168
97
|
if (this._framework._mainAgent) return this._framework._mainAgent;
|
|
@@ -170,29 +99,17 @@ class Plugin {
|
|
|
170
99
|
return agents.length > 0 ? agents[agents.length - 1] : null;
|
|
171
100
|
}
|
|
172
101
|
|
|
173
|
-
/**
|
|
174
|
-
* 注册子Agent
|
|
175
|
-
* @param {Object} config - 子Agent配置
|
|
176
|
-
* @param {string} config.name - 子Agent名称
|
|
177
|
-
* @param {string} config.role - 子Agent角色
|
|
178
|
-
* @param {string} [config.description] - 子Agent描述
|
|
179
|
-
* @param {Object} [config.tools] - 自定义工具 { name: toolDef }
|
|
180
|
-
* @param {string[]} [config.parentTools] - 从父Agent继承的工具名称
|
|
181
|
-
* @returns {Object} 注册的子Agent信息
|
|
182
|
-
*/
|
|
183
102
|
registerSubAgent(config) {
|
|
184
103
|
if (!this._framework) {
|
|
185
104
|
throw new Error('Plugin not installed, call install(framework) first');
|
|
186
105
|
}
|
|
187
106
|
|
|
188
|
-
// 获取 subagent-manager 插件
|
|
189
107
|
const subAgentManager = this._framework.pluginManager.get('subagent-manager');
|
|
190
108
|
|
|
191
109
|
if (!subAgentManager) {
|
|
192
110
|
throw new Error('subagent-manager plugin not found');
|
|
193
111
|
}
|
|
194
112
|
|
|
195
|
-
// 创建子Agent实例
|
|
196
113
|
const agent = this._framework.createSubAgent({
|
|
197
114
|
name: config.name,
|
|
198
115
|
role: config.role,
|
|
@@ -201,7 +118,6 @@ class Plugin {
|
|
|
201
118
|
parentTools: config.parentTools || [],
|
|
202
119
|
});
|
|
203
120
|
|
|
204
|
-
// 注册到 subagent-manager
|
|
205
121
|
subAgentManager.registerPlugin({
|
|
206
122
|
name: config.name,
|
|
207
123
|
role: config.role,
|
|
@@ -210,28 +126,21 @@ class Plugin {
|
|
|
210
126
|
tools: config.tools || {},
|
|
211
127
|
});
|
|
212
128
|
|
|
213
|
-
// 保存引用以便后续清理
|
|
214
129
|
this._subAgents.push({
|
|
215
130
|
name: config.name,
|
|
216
131
|
agent,
|
|
217
|
-
plugin: null,
|
|
132
|
+
plugin: null,
|
|
218
133
|
});
|
|
219
134
|
|
|
220
135
|
log.debug(` Registered subagent: ${config.name}`);
|
|
221
136
|
return { name: config.name, agent };
|
|
222
137
|
}
|
|
223
138
|
|
|
224
|
-
/**
|
|
225
|
-
* 热重载插件 - 手动调用重载时执行
|
|
226
|
-
* @param {Framework} framework - 框架实例
|
|
227
|
-
*/
|
|
228
139
|
reload(framework) {
|
|
229
140
|
this._framework = framework;
|
|
230
|
-
// 清除之前的延迟注册
|
|
231
141
|
this._deferredSubAgents = null;
|
|
232
142
|
this._deferredPromptParts = null;
|
|
233
143
|
|
|
234
|
-
// 重新注册提示词部分
|
|
235
144
|
for (const part of this._promptParts) {
|
|
236
145
|
try {
|
|
237
146
|
framework._mainAgent?.registerPromptPart(part.name, part.priority, part.provider);
|
|
@@ -240,38 +149,24 @@ class Plugin {
|
|
|
240
149
|
}
|
|
241
150
|
}
|
|
242
151
|
|
|
243
|
-
// 重新注册子Agent
|
|
244
152
|
this._autoRegisterSubAgents();
|
|
245
153
|
}
|
|
246
154
|
|
|
247
|
-
/**
|
|
248
|
-
* 工作目录变更钩子 — 框架级 cwd 切换时由 Framework.setCwd 调用
|
|
249
|
-
* 子类可重写以重新加载 cwd 相关的资源(路径、文件句柄等)
|
|
250
|
-
* @param {string} oldCwd
|
|
251
|
-
* @param {string} newCwd
|
|
252
|
-
* @param {Framework} framework
|
|
253
|
-
*/
|
|
254
155
|
onCwdChanged(oldCwd, newCwd, framework) {
|
|
255
|
-
//
|
|
156
|
+
// default no-op
|
|
256
157
|
}
|
|
257
158
|
|
|
258
|
-
/**
|
|
259
|
-
* 卸载插件 - 清理资源
|
|
260
|
-
* @param {Framework} framework - 框架实例
|
|
261
|
-
*/
|
|
262
159
|
uninstall(framework) {
|
|
263
|
-
// 注销所有提示词部分
|
|
264
160
|
for (const part of this._promptParts) {
|
|
265
161
|
try {
|
|
266
162
|
framework._mainAgent?.unregisterPromptPart(part.name);
|
|
267
163
|
} catch (err) {
|
|
268
|
-
//
|
|
164
|
+
// ignore
|
|
269
165
|
}
|
|
270
166
|
}
|
|
271
167
|
this._promptParts = [];
|
|
272
168
|
this._deferredPromptParts = null;
|
|
273
169
|
|
|
274
|
-
// 销毁所有子Agent
|
|
275
170
|
for (const plugin of this._subAgents) {
|
|
276
171
|
try {
|
|
277
172
|
plugin.uninstall(framework);
|
|
@@ -283,38 +178,25 @@ class Plugin {
|
|
|
283
178
|
this._framework = null;
|
|
284
179
|
}
|
|
285
180
|
|
|
286
|
-
/**
|
|
287
|
-
* 注册系统提示词部分
|
|
288
|
-
* 插件可以在 install() 或 start() 中调用此方法注册自己的提示词
|
|
289
|
-
* @param {string} name - 部分名称(唯一标识)
|
|
290
|
-
* @param {number} [priority=100] - 优先级,数值越小越靠前
|
|
291
|
-
* @param {Function} provider - 返回提示词内容的函数,签名: () => string
|
|
292
|
-
*/
|
|
293
181
|
registerPromptPart(name, priority = 100, provider) {
|
|
294
182
|
if (!this._framework) return;
|
|
295
183
|
|
|
296
|
-
// 保存记录
|
|
297
184
|
this._promptParts.push({ name, priority, provider });
|
|
298
185
|
|
|
299
|
-
// 如果主 agent 已存在,直接注册
|
|
300
186
|
const mainAgent = this._framework.getMainAgent();
|
|
301
187
|
if (mainAgent) {
|
|
302
188
|
mainAgent.registerPromptPart(name, priority, provider);
|
|
303
|
-
// 同步到所有子Agent
|
|
304
189
|
this._framework.syncPromptPartsToSubagents();
|
|
305
190
|
return;
|
|
306
191
|
}
|
|
307
192
|
|
|
308
|
-
// 否则延迟注册,等待框架准备好
|
|
309
193
|
if (!this._deferredPromptParts) {
|
|
310
194
|
this._deferredPromptParts = [];
|
|
311
195
|
}
|
|
312
196
|
this._deferredPromptParts.push({ name, priority, provider });
|
|
313
197
|
|
|
314
|
-
// 监听框架准备好事件
|
|
315
198
|
const alreadyListening = this._framework._events?.['framework:ready']?.length > 0;
|
|
316
199
|
this._framework.once('framework:ready', () => {
|
|
317
|
-
// 延迟确保主 agent 已创建
|
|
318
200
|
setTimeout(() => {
|
|
319
201
|
if (this._deferredPromptParts) {
|
|
320
202
|
for (const part of this._deferredPromptParts) {
|
|
@@ -326,17 +208,12 @@ class Plugin {
|
|
|
326
208
|
}
|
|
327
209
|
}
|
|
328
210
|
this._deferredPromptParts = null;
|
|
329
|
-
// 同步到所有子Agent
|
|
330
211
|
this._framework.syncPromptPartsToSubagents();
|
|
331
212
|
}
|
|
332
213
|
}, 100);
|
|
333
214
|
});
|
|
334
215
|
}
|
|
335
216
|
|
|
336
|
-
/**
|
|
337
|
-
* 获取插件信息
|
|
338
|
-
* @returns {Object}
|
|
339
|
-
*/
|
|
340
217
|
getInfo() {
|
|
341
218
|
return {
|
|
342
219
|
name: this.name,
|
|
@@ -347,9 +224,6 @@ class Plugin {
|
|
|
347
224
|
}
|
|
348
225
|
}
|
|
349
226
|
|
|
350
|
-
/**
|
|
351
|
-
* 内置插件类型常量
|
|
352
|
-
*/
|
|
353
227
|
const PluginType = {
|
|
354
228
|
AI: 'ai',
|
|
355
229
|
TOOLS: 'tools',
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Plugin, PluginType } = require('./base');
|
|
4
|
+
const { PluginManager } = require('./manager');
|
|
5
|
+
const { resolvePluginPath, scanPluginNames, loadPluginFromPath } = require('./loader');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
Plugin,
|
|
9
|
+
PluginType,
|
|
10
|
+
PluginManager,
|
|
11
|
+
resolvePluginPath,
|
|
12
|
+
scanPluginNames,
|
|
13
|
+
loadPluginFromPath,
|
|
14
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin Loader - 加载插件配置与路径解析
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const { logger } = require('../common/logger');
|
|
10
|
+
const log = logger.child('plugin-loader');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve plugin path from name
|
|
14
|
+
* @param {string} pluginName
|
|
15
|
+
* @param {Object} options
|
|
16
|
+
* @returns {string|null}
|
|
17
|
+
*/
|
|
18
|
+
function resolvePluginPath(pluginName, options = {}) {
|
|
19
|
+
const { pluginDirs = [], searchPaths = [] } = options;
|
|
20
|
+
|
|
21
|
+
// Check explicit paths first
|
|
22
|
+
const allPaths = [...pluginDirs, ...searchPaths];
|
|
23
|
+
for (const dir of allPaths) {
|
|
24
|
+
const fullPath = path.resolve(dir, pluginName);
|
|
25
|
+
if (fs.existsSync(fullPath)) {
|
|
26
|
+
return fullPath;
|
|
27
|
+
}
|
|
28
|
+
const indexJs = path.join(fullPath, 'index.js');
|
|
29
|
+
if (fs.existsSync(indexJs)) {
|
|
30
|
+
return fullPath;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check node_modules
|
|
35
|
+
try {
|
|
36
|
+
const resolved = require.resolve(pluginName);
|
|
37
|
+
return resolved;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Scan directory for plugin names
|
|
45
|
+
* @param {string} dir
|
|
46
|
+
* @returns {string[]}
|
|
47
|
+
*/
|
|
48
|
+
function scanPluginNames(dir) {
|
|
49
|
+
if (!fs.existsSync(dir)) return [];
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
return fs.readdirSync(dir)
|
|
53
|
+
.filter(f => {
|
|
54
|
+
const fullPath = path.join(dir, f);
|
|
55
|
+
const stat = fs.statSync(fullPath);
|
|
56
|
+
if (stat.isDirectory()) {
|
|
57
|
+
return fs.existsSync(path.join(fullPath, 'index.js'));
|
|
58
|
+
}
|
|
59
|
+
return f.endsWith('.js');
|
|
60
|
+
})
|
|
61
|
+
.map(f => f.replace(/\.js$/, ''));
|
|
62
|
+
} catch (err) {
|
|
63
|
+
log.warn(`Failed to scan plugin directory ${dir}: ${err.message}`);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load a plugin from a file path
|
|
70
|
+
* @param {string} pluginPath
|
|
71
|
+
* @returns {Object|null} Plugin instance
|
|
72
|
+
*/
|
|
73
|
+
function loadPluginFromPath(pluginPath) {
|
|
74
|
+
try {
|
|
75
|
+
const resolved = require.resolve(pluginPath);
|
|
76
|
+
const module = require(resolved);
|
|
77
|
+
|
|
78
|
+
// Handle both direct export and { Plugin } export
|
|
79
|
+
const PluginClass = module.Plugin || module.default || module;
|
|
80
|
+
if (typeof PluginClass === 'function') {
|
|
81
|
+
const instance = new PluginClass();
|
|
82
|
+
instance.sourcePath = resolved;
|
|
83
|
+
return instance;
|
|
84
|
+
}
|
|
85
|
+
// If already an instance
|
|
86
|
+
if (PluginClass.name && PluginClass.install) {
|
|
87
|
+
PluginClass.sourcePath = resolved;
|
|
88
|
+
return PluginClass;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
log.warn(`Failed to load plugin from ${pluginPath}: ${err.message}`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
resolvePluginPath,
|
|
99
|
+
scanPluginNames,
|
|
100
|
+
loadPluginFromPath,
|
|
101
|
+
};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PluginManager 插件管理器
|
|
5
|
+
* 负责插件的加载、卸载、重载
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Plugin } = require('./base');
|
|
9
|
+
const { logger } = require('../common/logger');
|
|
10
|
+
const { PluginError, PluginNotFoundError } = require('../common/errors');
|
|
11
|
+
const { safeJsonParse } = require('../utils');
|
|
12
|
+
const { resolvePluginPath, scanPluginNames } = require('../utils/plugin-helpers');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
class PluginManager {
|
|
17
|
+
constructor(framework) {
|
|
18
|
+
this.framework = framework;
|
|
19
|
+
this._plugins = new Map();
|
|
20
|
+
this._loading = false;
|
|
21
|
+
this._stateFile = path.join(
|
|
22
|
+
framework?.getCwd?.() ?? process.cwd(),
|
|
23
|
+
'.foliko',
|
|
24
|
+
'data',
|
|
25
|
+
'plugins-state.json'
|
|
26
|
+
);
|
|
27
|
+
this._knownPlugins = new Set();
|
|
28
|
+
|
|
29
|
+
this._log = logger.child('plugin-manager');
|
|
30
|
+
this._eventDescriptions = new Map();
|
|
31
|
+
this._stateCache = null;
|
|
32
|
+
|
|
33
|
+
this._registerDefaultEventDescriptions();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_registerDefaultEventDescriptions() {
|
|
37
|
+
// placeholder - plugins can register event descriptions
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
register(plugin) {
|
|
41
|
+
if (!(plugin instanceof Plugin)) {
|
|
42
|
+
throw new PluginError('Plugin must be an instance of Plugin');
|
|
43
|
+
}
|
|
44
|
+
this._plugins.set(plugin.name, { instance: plugin, enabled: true });
|
|
45
|
+
this._knownPlugins.add(plugin.name);
|
|
46
|
+
this._log.debug(`Plugin registered: ${plugin.name}`);
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
has(name) {
|
|
51
|
+
return this._plugins.has(name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get(name) {
|
|
55
|
+
const entry = this._plugins.get(name);
|
|
56
|
+
return entry ? entry.instance : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
isEnabled(name) {
|
|
60
|
+
const entry = this._plugins.get(name);
|
|
61
|
+
return entry ? entry.enabled !== false : false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getAll() {
|
|
65
|
+
return Array.from(this._plugins.values());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getNames() {
|
|
69
|
+
return Array.from(this._plugins.keys());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 获取所有已知插件信息(包括已加载和未加载的)
|
|
74
|
+
* @returns {Array<{name: string, status: string, enabled: boolean, version?: string, system?: boolean}>}
|
|
75
|
+
*/
|
|
76
|
+
getAllKnown() {
|
|
77
|
+
const result = [];
|
|
78
|
+
for (const name of this._knownPlugins) {
|
|
79
|
+
const entry = this._plugins.get(name);
|
|
80
|
+
if (entry) {
|
|
81
|
+
const inst = entry.instance;
|
|
82
|
+
result.push({
|
|
83
|
+
name,
|
|
84
|
+
status: 'loaded',
|
|
85
|
+
enabled: entry.enabled !== false,
|
|
86
|
+
version: inst.version,
|
|
87
|
+
system: inst.system,
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
result.push({ name, status: 'known', enabled: false });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
count() {
|
|
97
|
+
return this._plugins.size;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async load(plugin) {
|
|
101
|
+
const name = plugin.name;
|
|
102
|
+
|
|
103
|
+
if (this._plugins.has(name)) {
|
|
104
|
+
this._log.debug(`Plugin '${name}' already loaded, skipping`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this._log.info(`Loading plugin: ${name}`);
|
|
109
|
+
|
|
110
|
+
if (typeof plugin.install === 'function') {
|
|
111
|
+
plugin.install(this.framework);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this._plugins.set(name, {
|
|
115
|
+
instance: plugin,
|
|
116
|
+
sourcePath: plugin.sourcePath || null,
|
|
117
|
+
enabled: plugin.enabled !== false,
|
|
118
|
+
});
|
|
119
|
+
this._knownPlugins.add(name);
|
|
120
|
+
|
|
121
|
+
if (typeof plugin.start === 'function') {
|
|
122
|
+
await plugin.start(this.framework);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this._log.info(`Plugin loaded: ${name}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async unload(name) {
|
|
129
|
+
const entry = this._plugins.get(name);
|
|
130
|
+
if (!entry) {
|
|
131
|
+
throw new PluginNotFoundError(`Plugin '${name}' not found`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const plugin = entry.instance;
|
|
135
|
+
this._log.info(`Unloading plugin: ${name}`);
|
|
136
|
+
|
|
137
|
+
if (typeof plugin.uninstall === 'function') {
|
|
138
|
+
plugin.uninstall(this.framework);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this._plugins.delete(name);
|
|
142
|
+
this._log.info(`Plugin unloaded: ${name}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async unloadExcept(keepSet) {
|
|
146
|
+
const unloaded = [];
|
|
147
|
+
for (const [name, entry] of this._plugins) {
|
|
148
|
+
if (keepSet.has(name) || (entry.instance && entry.instance.system)) continue;
|
|
149
|
+
try {
|
|
150
|
+
await this.unload(name);
|
|
151
|
+
this._plugins.delete(name);
|
|
152
|
+
unloaded.push(name);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
this._log.warn(`unloadExcept: ${name} failed: ${err.message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return unloaded;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async reload(name) {
|
|
161
|
+
const entry = this._plugins.get(name);
|
|
162
|
+
if (!entry) {
|
|
163
|
+
throw new PluginNotFoundError(`Plugin '${name}' not found`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this._log.info(`Reloading plugin: ${name}`);
|
|
167
|
+
const plugin = entry.instance;
|
|
168
|
+
|
|
169
|
+
if (typeof plugin.reload === 'function') {
|
|
170
|
+
await plugin.reload(this.framework);
|
|
171
|
+
} else {
|
|
172
|
+
// Default reload: uninstall + install + start
|
|
173
|
+
if (typeof plugin.uninstall === 'function') {
|
|
174
|
+
plugin.uninstall(this.framework);
|
|
175
|
+
}
|
|
176
|
+
if (typeof plugin.install === 'function') {
|
|
177
|
+
plugin.install(this.framework);
|
|
178
|
+
}
|
|
179
|
+
if (typeof plugin.start === 'function') {
|
|
180
|
+
await plugin.start(this.framework);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this._log.info(`Plugin reloaded: ${name}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async startAll() {
|
|
188
|
+
for (const [name, entry] of this._plugins) {
|
|
189
|
+
const plugin = entry.instance;
|
|
190
|
+
if (typeof plugin.start === 'function') {
|
|
191
|
+
try {
|
|
192
|
+
await plugin.start(this.framework);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
this._log.warn(`startAll: ${name} failed: ${err.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async reloadAll() {
|
|
201
|
+
const names = this.getNames();
|
|
202
|
+
for (const name of names) {
|
|
203
|
+
try {
|
|
204
|
+
await this.reload(name);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
this._log.warn(`Failed to reload '${name}': ${err.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async enable(name) {
|
|
212
|
+
const entry = this._plugins.get(name);
|
|
213
|
+
if (entry) {
|
|
214
|
+
entry.enabled = true;
|
|
215
|
+
this._log.info(`Plugin enabled: ${name}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async disable(name) {
|
|
220
|
+
const entry = this._plugins.get(name);
|
|
221
|
+
if (entry) {
|
|
222
|
+
if (entry.instance.system) {
|
|
223
|
+
throw new PluginError(`Cannot disable system plugin '${name}'`);
|
|
224
|
+
}
|
|
225
|
+
entry.enabled = false;
|
|
226
|
+
this._log.info(`Plugin disabled: ${name}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
updatePluginConfig(name, config) {
|
|
231
|
+
const entry = this._plugins.get(name);
|
|
232
|
+
if (!entry) {
|
|
233
|
+
throw new PluginNotFoundError(`Plugin '${name}' not found`);
|
|
234
|
+
}
|
|
235
|
+
if (entry.instance.config) {
|
|
236
|
+
Object.assign(entry.instance.config, config);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
registerEventDescription(eventType, description, schema = null) {
|
|
241
|
+
this._eventDescriptions.set(eventType, { description, schema });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
getEventDescriptions() {
|
|
245
|
+
return new Map(this._eventDescriptions);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
getEventDescription(eventType) {
|
|
249
|
+
return this._eventDescriptions.get(eventType) || null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
setBootstrapping(val) {
|
|
253
|
+
this._bootstrapping = val !== false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
_setStateFile(cwd) {
|
|
257
|
+
this._stateFile = path.join(cwd, '.foliko', 'data', 'plugins-state.json');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = { PluginManager };
|