foliko 1.0.7 → 1.0.8
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 +3 -1
- package/.env.example +23 -0
- package/SPEC.md +75 -2
- package/docs/quick-reference.md +30 -4
- package/docs/user-manual.md +158 -3
- package/{test-chat.js → examples/test-chat.js} +1 -1
- package/{test-mcp.js → examples/test-mcp.js} +2 -2
- package/{test-reload.js → examples/test-reload.js} +2 -2
- package/{test-telegram.js → examples/test-telegram.js} +1 -1
- package/{test-tg-bot.js → examples/test-tg-bot.js} +1 -1
- package/{test-tg.js → examples/test-tg.js} +1 -1
- package/{test-think.js → examples/test-think.js} +1 -1
- package/package.json +4 -1
- package/plugins/ai-plugin.js +8 -0
- package/plugins/email.js +382 -0
- package/plugins/telegram-plugin.js +9 -0
- package/plugins/tools-plugin.js +75 -0
- package/src/core/framework.js +27 -0
- package/src/core/plugin-manager.js +177 -4
- /package/{test-tg-simple.js → examples/test-tg-simple.js} +0 -0
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"Bash(cd D:/Code/vb-agent && pnpm install --shamefully-hoist 2>&1 | head -20)",
|
|
29
29
|
"Bash(cd D:/Code/vb-agent && rm -rf node_modules && pnpm install)",
|
|
30
30
|
"Bash(cd D:/Code/vb-agent && timeout 8 node test-tg.js 2>&1 || true)",
|
|
31
|
-
"Bash(cd D:/Code/vb-agent && timeout 10 node test-tg.js 2>&1 || true)"
|
|
31
|
+
"Bash(cd D:/Code/vb-agent && timeout 10 node test-tg.js 2>&1 || true)",
|
|
32
|
+
"Bash(find /d/Code/vb-agent -name \"*email*\" -type f 2>/dev/null | grep -v node_modules | grep -v .git)",
|
|
33
|
+
"Bash(find /d/Code/vb-agent -maxdepth 2 -name \"*.md\" -type f 2>/dev/null | grep -v node_modules)"
|
|
32
34
|
]
|
|
33
35
|
}
|
|
34
36
|
}
|
package/.env.example
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# ========== AI API Keys ==========
|
|
2
|
+
DEEPSEEK_API_KEY=sk-your-deepseek-api-key
|
|
3
|
+
MINIMAX_API_KEY=sk-your-minimax-api-key
|
|
4
|
+
|
|
5
|
+
# ========== Email Configuration ==========
|
|
6
|
+
# SMTP Settings (for sending emails)
|
|
7
|
+
SMTP_HOST=smtp.gmail.com
|
|
8
|
+
SMTP_PORT=587
|
|
9
|
+
SMTP_SECURE=false
|
|
10
|
+
SMTP_USER=your-email@gmail.com
|
|
11
|
+
SMTP_PASS=your-app-password
|
|
12
|
+
|
|
13
|
+
# IMAP Settings (for reading emails)
|
|
14
|
+
IMAP_HOST=imap.gmail.com
|
|
15
|
+
IMAP_PORT=993
|
|
16
|
+
IMAP_USER=your-email@gmail.com
|
|
17
|
+
IMAP_PASS=your-app-password
|
|
18
|
+
|
|
19
|
+
# Default sender email address
|
|
20
|
+
FROM_EMAIL=your-email@gmail.com
|
|
21
|
+
|
|
22
|
+
# ========== Telegram Bot (optional) ==========
|
|
23
|
+
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
|
package/SPEC.md
CHANGED
|
@@ -165,7 +165,9 @@ D:\code\vb-agent\
|
|
|
165
165
|
│ ├── rules-plugin.js # Rules规则引擎插件
|
|
166
166
|
│ ├── scheduler-plugin.js # Scheduler定时任务插件
|
|
167
167
|
│ ├── storage-plugin.js # Storage存储插件
|
|
168
|
-
│
|
|
168
|
+
│ ├── subagent-plugin.js # SubAgent子代理插件
|
|
169
|
+
│ ├── email.js # Email邮件插件
|
|
170
|
+
│ └── telegram-plugin.js # Telegram对话插件
|
|
169
171
|
├── examples/
|
|
170
172
|
│ ├── basic.js # 基础示例
|
|
171
173
|
│ ├── bootstrap.js # Bootstrap示例
|
|
@@ -182,6 +184,25 @@ D:\code\vb-agent\
|
|
|
182
184
|
|
|
183
185
|
## 四、核心模块
|
|
184
186
|
|
|
187
|
+
### 4.5 插件配置持久化
|
|
188
|
+
|
|
189
|
+
插件状态和配置自动保存到 `.agent/data/plugins-state.json`
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
// 更新插件配置
|
|
193
|
+
framework.updatePluginConfig('telegram', { allowedChats: ['123'] })
|
|
194
|
+
|
|
195
|
+
// 启用/禁用插件
|
|
196
|
+
framework.enablePlugin('telegram')
|
|
197
|
+
framework.disablePlugin('telegram')
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**保存的内容:**
|
|
201
|
+
- `enabled` - 插件启用状态
|
|
202
|
+
- `config` - 插件配置对象
|
|
203
|
+
|
|
204
|
+
**自动恢复:** 框架启动时自动加载保存的状态
|
|
205
|
+
|
|
185
206
|
### 4.1 Framework (src/core/framework.js)
|
|
186
207
|
|
|
187
208
|
**职责**:
|
|
@@ -278,6 +299,8 @@ const response = await agent.chat('你好')
|
|
|
278
299
|
'plugin:start' // 插件启动
|
|
279
300
|
'plugin:reload' // 插件重载
|
|
280
301
|
'plugin:uninstall' // 插件卸载
|
|
302
|
+
'plugin:enabled' // 插件启用
|
|
303
|
+
'plugin:disabled' // 插件禁用
|
|
281
304
|
'agent:message' // Agent消息
|
|
282
305
|
'agent:tool-call' // Agent调用工具
|
|
283
306
|
'agent:tool-result' // 工具执行结果
|
|
@@ -337,6 +360,40 @@ agent.chat('请重载 my-plugin 插件')
|
|
|
337
360
|
**内置工具**:
|
|
338
361
|
- `reload_plugins` - 热重载插件
|
|
339
362
|
- `list_plugins` - 列出已加载插件
|
|
363
|
+
- `enable_plugin` - 启用插件
|
|
364
|
+
- `disable_plugin` - 禁用插件
|
|
365
|
+
- `get_plugin_config` - 获取插件配置
|
|
366
|
+
- `update_plugin_config` - 更新插件配置
|
|
367
|
+
|
|
368
|
+
### 7.3 Email Plugin (plugins/email-plugin.js)
|
|
369
|
+
|
|
370
|
+
**功能**:
|
|
371
|
+
- SMTP 发送邮件
|
|
372
|
+
- IMAP 读取邮件
|
|
373
|
+
|
|
374
|
+
**工具**:
|
|
375
|
+
- `email_send` - 发送邮件
|
|
376
|
+
- `email_read` - 读取邮件
|
|
377
|
+
- `email_unread_count` - 未读数量
|
|
378
|
+
- `email_mark_read` - 标记已读
|
|
379
|
+
|
|
380
|
+
### 7.4 Telegram Plugin (plugins/telegram-plugin.js)
|
|
381
|
+
|
|
382
|
+
**功能**:
|
|
383
|
+
- Telegram Bot 对话
|
|
384
|
+
- 绑定主 Agent 持续对话
|
|
385
|
+
- 支持 MarkdownV2
|
|
386
|
+
- 图片/文档接收保存
|
|
387
|
+
|
|
388
|
+
**配置**:
|
|
389
|
+
```javascript
|
|
390
|
+
{
|
|
391
|
+
botToken: '...',
|
|
392
|
+
allowedChats: ['123'],
|
|
393
|
+
groupMode: false,
|
|
394
|
+
prefix: '/'
|
|
395
|
+
}
|
|
396
|
+
```
|
|
340
397
|
|
|
341
398
|
## 八、使用示例
|
|
342
399
|
|
|
@@ -437,16 +494,32 @@ class Framework {
|
|
|
437
494
|
- [x] Scheduler 定时任务 (`plugins/scheduler-plugin.js`) - Cron 调度
|
|
438
495
|
- [x] Storage 存储 (`plugins/storage-plugin.js`) - 键值对持久化存储
|
|
439
496
|
- [x] SubAgent 子Agent (`plugins/subagent-plugin.js`) - 子Agent隔离工具集
|
|
497
|
+
- [x] Email 插件 (`plugins/email.js`) - 邮件收发
|
|
498
|
+
- [x] Telegram 插件 (`plugins/telegram-plugin.js`) - Telegram Bot 对话
|
|
499
|
+
- [x] 插件配置持久化 - enabled/config 保存到 plugins-state.json
|
|
500
|
+
- [x] AI 热重载 - LLM 配置更新后自动刷新已有 Agent
|
|
440
501
|
|
|
441
502
|
## 十一、依赖
|
|
442
503
|
|
|
443
504
|
```json
|
|
444
505
|
{
|
|
445
506
|
"dependencies": {
|
|
507
|
+
"@ai-sdk/anthropic": "^3.0.58",
|
|
508
|
+
"@ai-sdk/mcp": "^1.0.25",
|
|
446
509
|
"@ai-sdk/openai": "^3.0.41",
|
|
447
510
|
"@ai-sdk/openai-compatible": "^2.0.35",
|
|
511
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
512
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
448
513
|
"ai": "^6.0.116",
|
|
449
|
-
"
|
|
514
|
+
"dotenv": "^17.3.1",
|
|
515
|
+
"imap": "^0.8.19",
|
|
516
|
+
"mailparser": "^3.7.2",
|
|
517
|
+
"marked": "^11.2.0",
|
|
518
|
+
"marked-terminal": "6",
|
|
519
|
+
"node-cron": "^4.2.1",
|
|
520
|
+
"node-telegram-bot-api": "^0.67.0",
|
|
521
|
+
"nodemailer": "^6.10.0",
|
|
522
|
+
"zod": "^3.24.0"
|
|
450
523
|
}
|
|
451
524
|
}
|
|
452
525
|
```
|
package/docs/quick-reference.md
CHANGED
|
@@ -40,19 +40,34 @@ const result = await agent.chat('你好')
|
|
|
40
40
|
| `execute_command` | 执行命令 |
|
|
41
41
|
| `schedule_once` | 定时任务 |
|
|
42
42
|
| `think_now` | 主动思考 |
|
|
43
|
+
| `email_send` | 发送邮件 |
|
|
44
|
+
| `email_read` | 读取邮件 |
|
|
45
|
+
| `enable_plugin` | 启用插件 |
|
|
46
|
+
| `disable_plugin` | 禁用插件 |
|
|
47
|
+
| `update_plugin_config` | 更新插件配置 |
|
|
43
48
|
|
|
44
49
|
## 插件位置
|
|
45
50
|
|
|
46
51
|
- 内置插件:`plugins/`
|
|
47
52
|
- 用户插件:`.agent/plugins/`
|
|
48
53
|
|
|
49
|
-
##
|
|
54
|
+
## 插件管理
|
|
50
55
|
|
|
51
56
|
```javascript
|
|
52
|
-
//
|
|
57
|
+
// 重启插件
|
|
53
58
|
await framework.executeTool('reload_plugins', {})
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
await framework.executeTool('reload_plugins', { pluginName: 'telegram' })
|
|
60
|
+
|
|
61
|
+
// 启用/禁用插件
|
|
62
|
+
await framework.executeTool('enable_plugin', { name: 'telegram' })
|
|
63
|
+
await framework.executeTool('disable_plugin', { name: 'telegram' })
|
|
64
|
+
|
|
65
|
+
// 更新插件配置(持久化)
|
|
66
|
+
await framework.executeTool('update_plugin_config', {
|
|
67
|
+
name: 'telegram',
|
|
68
|
+
config: { allowedChats: ['123'], groupMode: true }
|
|
69
|
+
})
|
|
70
|
+
await framework.executeTool('get_plugin_config', { name: 'telegram' })
|
|
56
71
|
```
|
|
57
72
|
|
|
58
73
|
## 事件监听
|
|
@@ -129,3 +144,14 @@ agent.getStatus() // 'idle' | 'busy' | 'error'
|
|
|
129
144
|
agent.resetStatus() // 重置状态
|
|
130
145
|
agent.clearHistory() // 清空历史
|
|
131
146
|
```
|
|
147
|
+
|
|
148
|
+
## 配置持久化
|
|
149
|
+
|
|
150
|
+
插件配置自动保存到 `.agent/data/plugins-state.json`
|
|
151
|
+
|
|
152
|
+
| 文件 | 用途 |
|
|
153
|
+
|------|------|
|
|
154
|
+
| `.agent/data/plugins-state.json` | 插件启用状态和配置 |
|
|
155
|
+
| `.agent/data/storage.json` | 键值存储数据 |
|
|
156
|
+
| `.agent/data/telegram_images/` | Telegram 接收的图片 |
|
|
157
|
+
| `.agent/data/telegram_documents/` | Telegram 接收的文档 |
|
package/docs/user-manual.md
CHANGED
|
@@ -244,6 +244,22 @@ await framework.loadPlugin(new AIPlugin({
|
|
|
244
244
|
}))
|
|
245
245
|
```
|
|
246
246
|
|
|
247
|
+
**热重载支持:** 更新 AI 配置后执行 `reload_plugins('ai')`,所有已有 Agent 会自动使用新的 LLM 设置。
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
// 1. 更新 LLM 配置
|
|
251
|
+
await framework.executeTool('update_plugin_config', {
|
|
252
|
+
name: 'ai',
|
|
253
|
+
config: {
|
|
254
|
+
model: 'gpt-4o',
|
|
255
|
+
apiKey: 'new-key'
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
// 2. 热重载 AI 插件
|
|
260
|
+
await framework.executeTool('reload_plugins', { pluginName: 'ai' })
|
|
261
|
+
```
|
|
262
|
+
|
|
247
263
|
### 4.2 存储插件 (storage-plugin)
|
|
248
264
|
|
|
249
265
|
键值对存储,支持 JSON 文件持久化。
|
|
@@ -263,9 +279,13 @@ storage_clear() // 清空存储
|
|
|
263
279
|
|
|
264
280
|
```javascript
|
|
265
281
|
// 注册的工具
|
|
266
|
-
reload_plugins(pluginName?) //
|
|
267
|
-
list_plugins()
|
|
268
|
-
list_tools()
|
|
282
|
+
reload_plugins(pluginName?) // 热重载插件(可选指定插件名)
|
|
283
|
+
list_plugins() // 列出已加载插件
|
|
284
|
+
list_tools() // 列出所有工具
|
|
285
|
+
enable_plugin(name) // 启用指定插件
|
|
286
|
+
disable_plugin(name) // 禁用指定插件
|
|
287
|
+
get_plugin_config(name) // 获取插件当前配置
|
|
288
|
+
update_plugin_config(name, config) // 更新插件配置(持久化)
|
|
269
289
|
```
|
|
270
290
|
|
|
271
291
|
### 4.4 文件系统插件 (file-system-plugin)
|
|
@@ -461,6 +481,132 @@ loadSkill(skill) // 加载指定技能
|
|
|
461
481
|
// 技能存放在 skills/ 或 .agent/skills/ 目录
|
|
462
482
|
```
|
|
463
483
|
|
|
484
|
+
### 4.14 Email 插件 (email-plugin)
|
|
485
|
+
|
|
486
|
+
邮件收发插件,支持 SMTP 发送和 IMAP 读取。
|
|
487
|
+
|
|
488
|
+
```javascript
|
|
489
|
+
// 配置
|
|
490
|
+
await framework.loadPlugin(new EmailPlugin({
|
|
491
|
+
smtp_host: 'smtp.gmail.com',
|
|
492
|
+
smtp_port: 587,
|
|
493
|
+
smtp_secure: false,
|
|
494
|
+
smtp_user: 'your-email@gmail.com',
|
|
495
|
+
smtp_pass: 'your-app-password',
|
|
496
|
+
imap_host: 'imap.gmail.com',
|
|
497
|
+
imap_port: 993,
|
|
498
|
+
imap_user: 'your-email@gmail.com',
|
|
499
|
+
imap_pass: 'your-app-password',
|
|
500
|
+
from_email: 'your-email@gmail.com'
|
|
501
|
+
}))
|
|
502
|
+
|
|
503
|
+
// 或通过工具配置
|
|
504
|
+
email_configure({
|
|
505
|
+
smtp_host: 'smtp.gmail.com',
|
|
506
|
+
smtp_user: 'your-email@gmail.com',
|
|
507
|
+
smtp_pass: 'your-app-password'
|
|
508
|
+
})
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**注册的工具:**
|
|
512
|
+
|
|
513
|
+
| 工具 | 说明 |
|
|
514
|
+
|------|------|
|
|
515
|
+
| `email_send` | 发送电子邮件 |
|
|
516
|
+
| `email_read` | 读取电子邮件(IMAP) |
|
|
517
|
+
| `email_unread_count` | 获取未读邮件数量 |
|
|
518
|
+
| `email_mark_read` | 标记邮件为已读 |
|
|
519
|
+
| `email_configure` | 配置邮箱连接参数 |
|
|
520
|
+
|
|
521
|
+
```javascript
|
|
522
|
+
// 发送邮件
|
|
523
|
+
email_send({
|
|
524
|
+
to: 'recipient@example.com',
|
|
525
|
+
subject: '主题',
|
|
526
|
+
body: '正文内容',
|
|
527
|
+
cc: 'cc@example.com', // 可选
|
|
528
|
+
isHtml: false
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
// 读取邮件
|
|
532
|
+
email_read({
|
|
533
|
+
box: 'INBOX',
|
|
534
|
+
limit: 10,
|
|
535
|
+
unreadOnly: false
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
// 获取未读数量
|
|
539
|
+
email_unread_count({ box: 'INBOX' })
|
|
540
|
+
|
|
541
|
+
// 标记为已读
|
|
542
|
+
email_mark_read({ messageId: '12345' })
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### 4.15 Telegram 插件 (telegram-plugin)
|
|
546
|
+
|
|
547
|
+
Telegram 对话插件,绑定主 Agent 进行持续对话。
|
|
548
|
+
|
|
549
|
+
```javascript
|
|
550
|
+
// 配置
|
|
551
|
+
await framework.loadPlugin(new TelegramPlugin({
|
|
552
|
+
botToken: 'YOUR_BOT_TOKEN', // 必需
|
|
553
|
+
allowedChats: ['123456789'], // 可选:允许的聊天ID
|
|
554
|
+
groupMode: false, // 可选:是否启用群组模式
|
|
555
|
+
prefix: '/', // 可选:命令前缀
|
|
556
|
+
systemPrompt: '你是一个有帮助的AI助手。'
|
|
557
|
+
}))
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**功能特性:**
|
|
561
|
+
|
|
562
|
+
- 支持私聊和群组模式
|
|
563
|
+
- MarkdownV2 格式支持(自动转义特殊字符)
|
|
564
|
+
- 持续对话(基于 sessionId)
|
|
565
|
+
- 支持图片和文档接收
|
|
566
|
+
- 媒体文件自动保存到 `.agent/data/`
|
|
567
|
+
|
|
568
|
+
**命令:**
|
|
569
|
+
|
|
570
|
+
| 命令 | 说明 |
|
|
571
|
+
|------|------|
|
|
572
|
+
| `/start` | 显示帮助信息 |
|
|
573
|
+
| `/clear` | 清除对话历史 |
|
|
574
|
+
| `/history` | 查看对话状态 |
|
|
575
|
+
|
|
576
|
+
### 4.16 插件管理扩展
|
|
577
|
+
|
|
578
|
+
#### 插件启用/禁用
|
|
579
|
+
|
|
580
|
+
```javascript
|
|
581
|
+
// 启用插件
|
|
582
|
+
await framework.enablePlugin('telegram')
|
|
583
|
+
|
|
584
|
+
// 禁用插件
|
|
585
|
+
await framework.disablePlugin('telegram')
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
#### 插件配置更新
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
// 更新插件配置(会自动保存到 .agent/data/plugins-state.json)
|
|
592
|
+
await framework.executeTool('update_plugin_config', {
|
|
593
|
+
name: 'telegram',
|
|
594
|
+
config: {
|
|
595
|
+
allowedChats: ['123456789', '987654321'],
|
|
596
|
+
groupMode: true
|
|
597
|
+
}
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
// 获取插件当前配置
|
|
601
|
+
await framework.executeTool('get_plugin_config', {
|
|
602
|
+
name: 'telegram'
|
|
603
|
+
})
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
**配置持久化:**
|
|
607
|
+
- 插件的 `enabled` 状态和 `config` 会自动保存到 `.agent/data/plugins-state.json`
|
|
608
|
+
- 重启后自动恢复配置
|
|
609
|
+
|
|
464
610
|
---
|
|
465
611
|
|
|
466
612
|
## 5. 插件开发
|
|
@@ -792,6 +938,8 @@ agent.emit('eventName', { key: 'value' })
|
|
|
792
938
|
| `plugin:loaded` | `plugin` | 插件加载完成 |
|
|
793
939
|
| `plugin:unloaded` | `plugin` | 插件卸载 |
|
|
794
940
|
| `plugin:reloaded` | `plugin` | 插件重载 |
|
|
941
|
+
| `plugin:enabled` | `plugin` | 插件启用 |
|
|
942
|
+
| `plugin:disabled` | `plugin` | 插件禁用 |
|
|
795
943
|
| `tool:registered` | `tool` | 工具注册 |
|
|
796
944
|
| `agent:created` | `agent` | Agent 创建 |
|
|
797
945
|
|
|
@@ -998,6 +1146,13 @@ await framework.loadPlugin(plugin)
|
|
|
998
1146
|
await framework.reloadPlugin(name)
|
|
999
1147
|
await framework.reloadAllPlugins()
|
|
1000
1148
|
|
|
1149
|
+
// 启用/禁用插件
|
|
1150
|
+
await framework.enablePlugin(name)
|
|
1151
|
+
await framework.disablePlugin(name)
|
|
1152
|
+
|
|
1153
|
+
// 更新插件配置
|
|
1154
|
+
framework.updatePluginConfig(name, config)
|
|
1155
|
+
|
|
1001
1156
|
// 执行工具
|
|
1002
1157
|
const result = await framework.executeTool(name, args)
|
|
1003
1158
|
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* MCP 插件测试脚本
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const { Framework } = require('
|
|
6
|
-
const { MCPExecutorPlugin } = require('
|
|
5
|
+
const { Framework } = require('../src')
|
|
6
|
+
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
7
7
|
|
|
8
8
|
async function test() {
|
|
9
9
|
console.log('=== MCP Plugin Test ===\n')
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* MCP mcp_reload 功能测试
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const { Framework } = require('
|
|
6
|
-
const { MCPExecutorPlugin } = require('
|
|
5
|
+
const { Framework } = require('../src')
|
|
6
|
+
const { MCPExecutorPlugin } = require('../src/executors/mcp-executor')
|
|
7
7
|
const fs = require('fs')
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foliko",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "简约的插件化 Agent 框架",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -28,10 +28,13 @@
|
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
29
29
|
"ai": "^6.0.116",
|
|
30
30
|
"dotenv": "^17.3.1",
|
|
31
|
+
"imap": "^0.8.19",
|
|
32
|
+
"mailparser": "^3.7.2",
|
|
31
33
|
"marked": "^11.2.0",
|
|
32
34
|
"marked-terminal": "6",
|
|
33
35
|
"node-cron": "^4.2.1",
|
|
34
36
|
"node-telegram-bot-api": "^0.67.0",
|
|
37
|
+
"nodemailer": "^6.10.0",
|
|
35
38
|
"zod": "^3.24.0"
|
|
36
39
|
}
|
|
37
40
|
}
|
package/plugins/ai-plugin.js
CHANGED
|
@@ -79,6 +79,14 @@ class AIPlugin extends Plugin {
|
|
|
79
79
|
console.log('[AIPlugin] Reloading...')
|
|
80
80
|
this._framework = framework
|
|
81
81
|
this._initAIClient()
|
|
82
|
+
|
|
83
|
+
// 刷新所有已有 Agent 的 AI client
|
|
84
|
+
for (const agent of framework._agents || []) {
|
|
85
|
+
if (agent._chatHandler) {
|
|
86
|
+
agent._chatHandler.setAIClient(this._aiClient)
|
|
87
|
+
console.log(`[AIPlugin] Refreshed AI client for agent: ${agent.name}`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
uninstall(framework) {
|
package/plugins/email.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email 插件
|
|
3
|
+
* 邮件收发插件 - 支持读取和发送电子邮件
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Plugin } = require('../src/core/plugin-base')
|
|
7
|
+
const { z } = require('zod')
|
|
8
|
+
|
|
9
|
+
class EmailPlugin extends Plugin {
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
super()
|
|
12
|
+
this.name = 'email'
|
|
13
|
+
this.version = '1.0.0'
|
|
14
|
+
this.description = '邮件收发插件 - 支持读取和发送电子邮件'
|
|
15
|
+
this.priority = 10
|
|
16
|
+
this.config = config
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
install(framework) {
|
|
20
|
+
this._framework = framework
|
|
21
|
+
this._registerTools()
|
|
22
|
+
return this
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_registerTools() {
|
|
26
|
+
// 发送邮件工具
|
|
27
|
+
this._framework.registerTool({
|
|
28
|
+
name: 'email_send',
|
|
29
|
+
description: '发送电子邮件',
|
|
30
|
+
inputSchema: z.object({
|
|
31
|
+
to: z.string().describe('收件人邮箱地址'),
|
|
32
|
+
subject: z.string().describe('邮件主题'),
|
|
33
|
+
body: z.string().describe('邮件正文内容'),
|
|
34
|
+
cc: z.string().optional().describe('抄送邮箱地址,多个用逗号分隔'),
|
|
35
|
+
bcc: z.string().optional().describe('密送邮箱地址,多个用逗号分隔'),
|
|
36
|
+
isHtml: z.boolean().optional().describe('是否为HTML格式,默认false')
|
|
37
|
+
}),
|
|
38
|
+
execute: async (args) => {
|
|
39
|
+
return this._sendEmail(args)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// 读取邮件工具
|
|
44
|
+
this._framework.registerTool({
|
|
45
|
+
name: 'email_read',
|
|
46
|
+
description: '读取电子邮件(支持IMAP协议)',
|
|
47
|
+
inputSchema: z.object({
|
|
48
|
+
host: z.string().optional().describe('IMAP服务器地址,默认从配置获取'),
|
|
49
|
+
port: z.number().optional().describe('IMAP端口,默认993'),
|
|
50
|
+
user: z.string().optional().describe('邮箱用户名,默认从配置获取'),
|
|
51
|
+
password: z.string().optional().describe('邮箱密码,默认从配置获取'),
|
|
52
|
+
box: z.string().optional().describe('邮箱文件夹,默认INBOX'),
|
|
53
|
+
limit: z.number().optional().describe('读取邮件数量,默认10'),
|
|
54
|
+
unreadOnly: z.boolean().optional().describe('仅读取未读邮件,默认false'),
|
|
55
|
+
searchCriteria: z.string().optional().describe('搜索条件,如 "UNSEEN", "FROM sender@example.com"')
|
|
56
|
+
}),
|
|
57
|
+
execute: async (args) => {
|
|
58
|
+
return this._readEmails(args)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// 获取未读邮件数量
|
|
63
|
+
this._framework.registerTool({
|
|
64
|
+
name: 'email_unread_count',
|
|
65
|
+
description: '获取邮箱未读邮件数量',
|
|
66
|
+
inputSchema: z.object({
|
|
67
|
+
host: z.string().optional().describe('IMAP服务器地址'),
|
|
68
|
+
port: z.number().optional().describe('IMAP端口'),
|
|
69
|
+
user: z.string().optional().describe('邮箱用户名'),
|
|
70
|
+
password: z.string().optional().describe('邮箱密码'),
|
|
71
|
+
box: z.string().optional().describe('邮箱文件夹,默认INBOX')
|
|
72
|
+
}),
|
|
73
|
+
execute: async (args) => {
|
|
74
|
+
return this._getUnreadCount(args)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// 标记邮件为已读
|
|
79
|
+
this._framework.registerTool({
|
|
80
|
+
name: 'email_mark_read',
|
|
81
|
+
description: '标记邮件为已读',
|
|
82
|
+
inputSchema: z.object({
|
|
83
|
+
host: z.string().optional().describe('IMAP服务器地址'),
|
|
84
|
+
port: z.number().optional().describe('IMAP端口'),
|
|
85
|
+
user: z.string().optional().describe('邮箱用户名'),
|
|
86
|
+
password: z.string().optional().describe('邮箱密码'),
|
|
87
|
+
messageId: z.string().describe('邮件UID或序列号')
|
|
88
|
+
}),
|
|
89
|
+
execute: async (args) => {
|
|
90
|
+
return this._markAsRead(args)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// 配置邮箱连接
|
|
95
|
+
this._framework.registerTool({
|
|
96
|
+
name: 'email_configure',
|
|
97
|
+
description: '配置邮箱连接参数',
|
|
98
|
+
inputSchema: z.object({
|
|
99
|
+
smtp_host: z.string().optional().describe('SMTP服务器地址'),
|
|
100
|
+
smtp_port: z.number().optional().describe('SMTP端口'),
|
|
101
|
+
smtp_secure: z.boolean().optional().describe('是否使用SSL/TLS'),
|
|
102
|
+
smtp_user: z.string().optional().describe('SMTP用户名'),
|
|
103
|
+
smtp_pass: z.string().optional().describe('SMTP密码'),
|
|
104
|
+
imap_host: z.string().optional().describe('IMAP服务器地址'),
|
|
105
|
+
imap_port: z.number().optional().describe('IMAP端口'),
|
|
106
|
+
imap_user: z.string().optional().describe('IMAP用户名'),
|
|
107
|
+
imap_pass: z.string().optional().describe('IMAP密码'),
|
|
108
|
+
from_email: z.string().optional().describe('默认发件人地址')
|
|
109
|
+
}),
|
|
110
|
+
execute: async (args) => {
|
|
111
|
+
Object.assign(this.config, args)
|
|
112
|
+
return {
|
|
113
|
+
success: true,
|
|
114
|
+
message: '邮箱配置已更新',
|
|
115
|
+
config: this.config
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async _sendEmail(args) {
|
|
122
|
+
try {
|
|
123
|
+
const nodemailer = require('nodemailer')
|
|
124
|
+
|
|
125
|
+
const smtpConfig = {
|
|
126
|
+
host: this.config.smtp_host || process.env.SMTP_HOST || 'smtp.gmail.com',
|
|
127
|
+
port: this.config.smtp_port || process.env.SMTP_PORT || 587,
|
|
128
|
+
secure: (this.config.smtp_secure || process.env.SMTP_SECURE || 'false') === 'true',
|
|
129
|
+
auth: {
|
|
130
|
+
user: this.config.smtp_user || process.env.SMTP_USER,
|
|
131
|
+
pass: this.config.smtp_pass || process.env.SMTP_PASS
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const transporter = nodemailer.createTransport(smtpConfig)
|
|
136
|
+
|
|
137
|
+
const mailOptions = {
|
|
138
|
+
from: this.config.from_email || process.env.FROM_EMAIL || smtpConfig.auth.user,
|
|
139
|
+
to: args.to,
|
|
140
|
+
subject: args.subject,
|
|
141
|
+
text: args.isHtml ? undefined : args.body,
|
|
142
|
+
html: args.isHtml ? args.body : undefined,
|
|
143
|
+
cc: args.cc,
|
|
144
|
+
bcc: args.bcc
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const info = await transporter.sendMail(mailOptions)
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
message: '邮件发送成功',
|
|
151
|
+
messageId: info.messageId,
|
|
152
|
+
accepted: info.accepted,
|
|
153
|
+
rejected: info.rejected
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
error: error.message,
|
|
159
|
+
details: '请检查SMTP配置是否正确'
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async _readEmails(args) {
|
|
165
|
+
try {
|
|
166
|
+
const Imap = require('imap')
|
|
167
|
+
const { simpleParser } = require('mailparser')
|
|
168
|
+
|
|
169
|
+
const imapConfig = {
|
|
170
|
+
user: args.user || this.config.imap_user || process.env.IMAP_USER,
|
|
171
|
+
password: args.password || this.config.imap_pass || process.env.IMAP_PASS,
|
|
172
|
+
host: args.host || this.config.imap_host || process.env.IMAP_HOST,
|
|
173
|
+
port: args.port || this.config.imap_port || 993,
|
|
174
|
+
tls: true,
|
|
175
|
+
tlsOptions: { rejectUnauthorized: false }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const box = args.box || 'INBOX'
|
|
179
|
+
const limit = args.limit || 10
|
|
180
|
+
const unreadOnly = args.unreadOnly || false
|
|
181
|
+
|
|
182
|
+
const emails = await this._fetchEmails(imapConfig, box, limit, unreadOnly, args.searchCriteria)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
success: true,
|
|
186
|
+
count: emails.length,
|
|
187
|
+
emails: emails
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
error: error.message,
|
|
193
|
+
details: '请检查IMAP配置是否正确'
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async _getUnreadCount(args) {
|
|
199
|
+
try {
|
|
200
|
+
const Imap = require('imap')
|
|
201
|
+
|
|
202
|
+
const imapConfig = {
|
|
203
|
+
user: args.user || this.config.imap_user || process.env.IMAP_USER,
|
|
204
|
+
password: args.password || this.config.imap_pass || process.env.IMAP_PASS,
|
|
205
|
+
host: args.host || this.config.imap_host || process.env.IMAP_HOST,
|
|
206
|
+
port: args.port || this.config.imap_port || 993,
|
|
207
|
+
tls: true,
|
|
208
|
+
tlsOptions: { rejectUnauthorized: false }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const box = args.box || 'INBOX'
|
|
212
|
+
const count = await this._fetchUnreadCount(imapConfig, box)
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
success: true,
|
|
216
|
+
unreadCount: count,
|
|
217
|
+
box: box
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: error.message
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async _markAsRead(args) {
|
|
228
|
+
try {
|
|
229
|
+
const Imap = require('imap')
|
|
230
|
+
|
|
231
|
+
const imapConfig = {
|
|
232
|
+
user: args.user || this.config.imap_user || process.env.IMAP_USER,
|
|
233
|
+
password: args.password || this.config.imap_pass || process.env.IMAP_PASS,
|
|
234
|
+
host: args.host || this.config.imap_host || process.env.IMAP_HOST,
|
|
235
|
+
port: args.port || this.config.imap_port || 993,
|
|
236
|
+
tls: true,
|
|
237
|
+
tlsOptions: { rejectUnauthorized: false }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
await this._markEmailAsRead(imapConfig, args.messageId)
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
message: '邮件已标记为已读'
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
error: error.message
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_fetchEmails(imapConfig, box, limit, unreadOnly, searchCriteria) {
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
const imap = new Imap(imapConfig)
|
|
257
|
+
const emails = []
|
|
258
|
+
|
|
259
|
+
imap.once('ready', () => {
|
|
260
|
+
imap.openBox(box, true, (err) => {
|
|
261
|
+
if (err) {
|
|
262
|
+
imap.end()
|
|
263
|
+
return reject(err)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let searchFilter = searchCriteria ? [searchCriteria] : ['ALL']
|
|
267
|
+
if (unreadOnly) {
|
|
268
|
+
searchFilter = ['UNSEEN']
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
imap.search(searchFilter, (err, results) => {
|
|
272
|
+
if (err) {
|
|
273
|
+
imap.end()
|
|
274
|
+
return reject(err)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (results.length === 0) {
|
|
278
|
+
imap.end()
|
|
279
|
+
return resolve([])
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const fetchIds = results.slice(-limit)
|
|
283
|
+
|
|
284
|
+
const f = imap.fetch(fetchIds, {
|
|
285
|
+
bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)',
|
|
286
|
+
struct: true
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
f.on('message', (msg, seqno) => {
|
|
290
|
+
const email = { seqno }
|
|
291
|
+
|
|
292
|
+
msg.on('body', (stream) => {
|
|
293
|
+
let buffer = ''
|
|
294
|
+
stream.on('data', chunk => buffer += chunk.toString('utf8'))
|
|
295
|
+
stream.once('end', () => {
|
|
296
|
+
const headers = Imap.parseHeader(buffer)
|
|
297
|
+
email.from = headers.from ? headers.from[0] : ''
|
|
298
|
+
email.to = headers.to ? headers.to[0] : ''
|
|
299
|
+
email.subject = headers.subject ? headers.subject[0] : ''
|
|
300
|
+
email.date = headers.date ? headers.date[0] : ''
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
msg.once('attributes', (attrs) => {
|
|
305
|
+
email.uid = attrs.uid
|
|
306
|
+
email.id = attrs.uid
|
|
307
|
+
email.flags = attrs.flags
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
msg.once('end', () => {
|
|
311
|
+
emails.push(email)
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
f.once('error', err => {
|
|
316
|
+
imap.end()
|
|
317
|
+
reject(err)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
f.once('end', () => {
|
|
321
|
+
imap.end()
|
|
322
|
+
resolve(emails)
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
imap.once('error', err => reject(err))
|
|
329
|
+
imap.connect()
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
_fetchUnreadCount(imapConfig, box) {
|
|
334
|
+
return new Promise((resolve, reject) => {
|
|
335
|
+
const imap = new Imap(imapConfig)
|
|
336
|
+
|
|
337
|
+
imap.once('ready', () => {
|
|
338
|
+
imap.openBox(box, true, (err, box) => {
|
|
339
|
+
if (err) {
|
|
340
|
+
imap.end()
|
|
341
|
+
return reject(err)
|
|
342
|
+
}
|
|
343
|
+
const unreadCount = box.messages.unread
|
|
344
|
+
imap.end()
|
|
345
|
+
resolve(unreadCount)
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
imap.once('error', err => reject(err))
|
|
350
|
+
imap.connect()
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
_markEmailAsRead(imapConfig, messageId) {
|
|
355
|
+
return new Promise((resolve, reject) => {
|
|
356
|
+
const imap = new Imap(imapConfig)
|
|
357
|
+
|
|
358
|
+
imap.once('ready', () => {
|
|
359
|
+
imap.openBox('INBOX', true, (err) => {
|
|
360
|
+
if (err) {
|
|
361
|
+
imap.end()
|
|
362
|
+
return reject(err)
|
|
363
|
+
}
|
|
364
|
+
imap.addFlags(messageId, '\\Seen', (err) => {
|
|
365
|
+
imap.end()
|
|
366
|
+
if (err) reject(err)
|
|
367
|
+
else resolve()
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
imap.once('error', err => reject(err))
|
|
373
|
+
imap.connect()
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
uninstall(framework) {
|
|
378
|
+
// 清理资源
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
module.exports = { EmailPlugin }
|
|
@@ -508,6 +508,15 @@ module.exports = function(Plugin) {
|
|
|
508
508
|
}
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
+
/**
|
|
512
|
+
* 启动 Bot(用于重新启用时)
|
|
513
|
+
*/
|
|
514
|
+
start(framework) {
|
|
515
|
+
if (this.config.botToken && !this._bot) {
|
|
516
|
+
this._initBot()
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
511
520
|
uninstall(framework) {
|
|
512
521
|
this.stopBot()
|
|
513
522
|
this._sessions.clear()
|
package/plugins/tools-plugin.js
CHANGED
|
@@ -78,6 +78,40 @@ class ToolsPlugin extends Plugin {
|
|
|
78
78
|
}
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
+
// 启用插件工具
|
|
82
|
+
framework.registerTool({
|
|
83
|
+
name: 'enable_plugin',
|
|
84
|
+
description: '启用指定插件',
|
|
85
|
+
inputSchema: z.object({
|
|
86
|
+
name: z.string().describe('插件名称')
|
|
87
|
+
}),
|
|
88
|
+
execute: async (args) => {
|
|
89
|
+
try {
|
|
90
|
+
await framework.enablePlugin(args.name)
|
|
91
|
+
return { success: true, message: `插件 '${args.name}' 已启用` }
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return { success: false, error: err.message }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// 禁用插件工具
|
|
99
|
+
framework.registerTool({
|
|
100
|
+
name: 'disable_plugin',
|
|
101
|
+
description: '禁用指定插件',
|
|
102
|
+
inputSchema: z.object({
|
|
103
|
+
name: z.string().describe('插件名称')
|
|
104
|
+
}),
|
|
105
|
+
execute: async (args) => {
|
|
106
|
+
try {
|
|
107
|
+
await framework.disablePlugin(args.name)
|
|
108
|
+
return { success: true, message: `插件 '${args.name}' 已禁用` }
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return { success: false, error: err.message }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
81
115
|
// 获取工具列表
|
|
82
116
|
framework.registerTool({
|
|
83
117
|
name: 'list_tools',
|
|
@@ -94,6 +128,43 @@ class ToolsPlugin extends Plugin {
|
|
|
94
128
|
}
|
|
95
129
|
}
|
|
96
130
|
})
|
|
131
|
+
|
|
132
|
+
// 获取插件配置
|
|
133
|
+
framework.registerTool({
|
|
134
|
+
name: 'get_plugin_config',
|
|
135
|
+
description: '获取指定插件的当前配置',
|
|
136
|
+
inputSchema: z.object({
|
|
137
|
+
name: z.string().describe('插件名称')
|
|
138
|
+
}),
|
|
139
|
+
execute: async (args) => {
|
|
140
|
+
const plugin = framework.pluginManager.get(args.name)
|
|
141
|
+
if (!plugin) {
|
|
142
|
+
return { success: false, error: `Plugin '${args.name}' not found` }
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
config: plugin.config || {}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// 更新插件配置
|
|
152
|
+
framework.registerTool({
|
|
153
|
+
name: 'update_plugin_config',
|
|
154
|
+
description: '更新指定插件的配置,会合并到现有配置并持久化保存',
|
|
155
|
+
inputSchema: z.object({
|
|
156
|
+
name: z.string().describe('插件名称'),
|
|
157
|
+
config: z.record(z.any()).describe('要更新的配置(会合并到现有配置)')
|
|
158
|
+
}),
|
|
159
|
+
execute: async (args) => {
|
|
160
|
+
try {
|
|
161
|
+
const newConfig = framework.updatePluginConfig(args.name, args.config)
|
|
162
|
+
return { success: true, message: `插件 '${args.name}' 配置已更新`, config: newConfig }
|
|
163
|
+
} catch (err) {
|
|
164
|
+
return { success: false, error: err.message }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
})
|
|
97
168
|
}
|
|
98
169
|
|
|
99
170
|
reload(framework) {
|
|
@@ -107,6 +178,10 @@ class ToolsPlugin extends Plugin {
|
|
|
107
178
|
framework.toolRegistry.unregister('reload_plugins')
|
|
108
179
|
framework.toolRegistry.unregister('list_plugins')
|
|
109
180
|
framework.toolRegistry.unregister('list_tools')
|
|
181
|
+
framework.toolRegistry.unregister('enable_plugin')
|
|
182
|
+
framework.toolRegistry.unregister('disable_plugin')
|
|
183
|
+
framework.toolRegistry.unregister('get_plugin_config')
|
|
184
|
+
framework.toolRegistry.unregister('update_plugin_config')
|
|
110
185
|
this._framework = null
|
|
111
186
|
}
|
|
112
187
|
}
|
package/src/core/framework.js
CHANGED
|
@@ -89,6 +89,33 @@ class Framework extends EventEmitter {
|
|
|
89
89
|
return this
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* 启用插件
|
|
94
|
+
* @param {string} name - 插件名称
|
|
95
|
+
*/
|
|
96
|
+
async enablePlugin(name) {
|
|
97
|
+
await this.pluginManager.enable(name)
|
|
98
|
+
return this
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 禁用插件
|
|
103
|
+
* @param {string} name - 插件名称
|
|
104
|
+
*/
|
|
105
|
+
async disablePlugin(name) {
|
|
106
|
+
await this.pluginManager.disable(name)
|
|
107
|
+
return this
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 更新插件配置
|
|
112
|
+
* @param {string} name - 插件名称
|
|
113
|
+
* @param {Object} config - 新配置
|
|
114
|
+
*/
|
|
115
|
+
updatePluginConfig(name, config) {
|
|
116
|
+
return this.pluginManager.updatePluginConfig(name, config)
|
|
117
|
+
}
|
|
118
|
+
|
|
92
119
|
/**
|
|
93
120
|
* 注册工具
|
|
94
121
|
* @param {Object} tool - 工具定义
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { Plugin } = require('./plugin-base')
|
|
7
|
+
const fs = require('fs')
|
|
8
|
+
const path = require('path')
|
|
7
9
|
|
|
8
10
|
class PluginManager {
|
|
9
11
|
/**
|
|
@@ -13,6 +15,53 @@ class PluginManager {
|
|
|
13
15
|
this.framework = framework
|
|
14
16
|
this._plugins = new Map()
|
|
15
17
|
this._loading = false
|
|
18
|
+
this._stateFile = path.join(process.cwd(), '.agent', 'data', 'plugins-state.json')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 获取状态文件路径
|
|
23
|
+
*/
|
|
24
|
+
_getStateFile() {
|
|
25
|
+
const dir = path.dirname(this._stateFile)
|
|
26
|
+
if (!fs.existsSync(dir)) {
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
28
|
+
}
|
|
29
|
+
return this._stateFile
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 保存插件状态到文件
|
|
34
|
+
*/
|
|
35
|
+
_saveState() {
|
|
36
|
+
try {
|
|
37
|
+
const state = {}
|
|
38
|
+
for (const [name, entry] of this._plugins) {
|
|
39
|
+
state[name] = {
|
|
40
|
+
enabled: entry.enabled,
|
|
41
|
+
config: entry.instance?.config || {}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
fs.writeFileSync(this._getStateFile(), JSON.stringify(state, null, 2))
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error('[PluginManager] Failed to save state:', err.message)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 加载插件状态从文件
|
|
52
|
+
*/
|
|
53
|
+
_loadState() {
|
|
54
|
+
try {
|
|
55
|
+
const stateFile = this._getStateFile()
|
|
56
|
+
if (fs.existsSync(stateFile)) {
|
|
57
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'))
|
|
58
|
+
console.log('[PluginManager] Loaded plugin state from file')
|
|
59
|
+
return state
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('[PluginManager] Failed to load state:', err.message)
|
|
63
|
+
}
|
|
64
|
+
return {}
|
|
16
65
|
}
|
|
17
66
|
|
|
18
67
|
/**
|
|
@@ -30,9 +79,20 @@ class PluginManager {
|
|
|
30
79
|
pluginInstance = this._createFromObject(plugin)
|
|
31
80
|
}
|
|
32
81
|
|
|
82
|
+
// 加载保存的状态
|
|
83
|
+
const savedState = this._loadState()
|
|
84
|
+
const savedEnabled = savedState[pluginInstance.name]?.enabled
|
|
85
|
+
const savedConfig = savedState[pluginInstance.name]?.config
|
|
86
|
+
|
|
87
|
+
// 恢复保存的配置到插件实例
|
|
88
|
+
if (savedConfig && pluginInstance.config) {
|
|
89
|
+
pluginInstance.config = { ...pluginInstance.config, ...savedConfig }
|
|
90
|
+
}
|
|
91
|
+
|
|
33
92
|
this._plugins.set(pluginInstance.name, {
|
|
34
93
|
instance: pluginInstance,
|
|
35
|
-
status: 'registered'
|
|
94
|
+
status: 'registered',
|
|
95
|
+
enabled: savedEnabled !== undefined ? savedEnabled : true // 使用保存的状态,默认启用
|
|
36
96
|
})
|
|
37
97
|
|
|
38
98
|
this.framework.emit('plugin:registered', pluginInstance)
|
|
@@ -61,6 +121,12 @@ class PluginManager {
|
|
|
61
121
|
if (existing) {
|
|
62
122
|
pluginInstance = existing.instance
|
|
63
123
|
|
|
124
|
+
// 如果插件被禁用,跳过加载
|
|
125
|
+
if (!existing.enabled) {
|
|
126
|
+
console.log(`[PluginManager] Plugin '${pluginInstance.name}' is disabled`)
|
|
127
|
+
return pluginInstance
|
|
128
|
+
}
|
|
129
|
+
|
|
64
130
|
// 如果已加载且已启动,直接返回
|
|
65
131
|
if (existing.status === 'loaded' && pluginInstance._started) {
|
|
66
132
|
console.warn(`[PluginManager] Plugin '${pluginInstance.name}' already loaded`)
|
|
@@ -270,11 +336,11 @@ class PluginManager {
|
|
|
270
336
|
}
|
|
271
337
|
|
|
272
338
|
/**
|
|
273
|
-
*
|
|
339
|
+
* 获取所有已加载且已启用的插件
|
|
274
340
|
*/
|
|
275
341
|
getAll() {
|
|
276
342
|
return Array.from(this._plugins.values())
|
|
277
|
-
.filter(e => e.status === 'loaded')
|
|
343
|
+
.filter(e => e.status === 'loaded' && e.enabled)
|
|
278
344
|
.map(e => ({ name: e.instance.name, instance: e.instance }))
|
|
279
345
|
}
|
|
280
346
|
|
|
@@ -294,12 +360,119 @@ class PluginManager {
|
|
|
294
360
|
return this._plugins.get(name)?.status === 'loaded'
|
|
295
361
|
}
|
|
296
362
|
|
|
363
|
+
/**
|
|
364
|
+
* 检查插件是否启用
|
|
365
|
+
* @param {string} name - 插件名称
|
|
366
|
+
*/
|
|
367
|
+
isEnabled(name) {
|
|
368
|
+
return this._plugins.get(name)?.enabled === true
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 启用插件
|
|
373
|
+
* @param {string} name - 插件名称
|
|
374
|
+
*/
|
|
375
|
+
async enable(name) {
|
|
376
|
+
const entry = this._plugins.get(name)
|
|
377
|
+
if (!entry) {
|
|
378
|
+
throw new Error(`Plugin '${name}' not found`)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (entry.enabled) {
|
|
382
|
+
console.log(`[PluginManager] Plugin '${name}' already enabled`)
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
entry.enabled = true
|
|
387
|
+
|
|
388
|
+
// 如果插件已加载但未启动,启动它
|
|
389
|
+
if (entry.status === 'loaded' && !entry.instance._started) {
|
|
390
|
+
try {
|
|
391
|
+
if (typeof entry.instance.start === 'function') {
|
|
392
|
+
await entry.instance.start(this.framework)
|
|
393
|
+
entry.instance._started = true
|
|
394
|
+
}
|
|
395
|
+
} catch (err) {
|
|
396
|
+
console.error(`[PluginManager] Start failed for '${name}':`, err.message)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this.framework.emit('plugin:enabled', entry.instance)
|
|
401
|
+
this._saveState()
|
|
402
|
+
console.log(`[PluginManager] Plugin '${name}' enabled`)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 禁用插件
|
|
407
|
+
* @param {string} name - 插件名称
|
|
408
|
+
*/
|
|
409
|
+
async disable(name) {
|
|
410
|
+
const entry = this._plugins.get(name)
|
|
411
|
+
if (!entry) {
|
|
412
|
+
throw new Error(`Plugin '${name}' not found`)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (!entry.enabled) {
|
|
416
|
+
console.log(`[PluginManager] Plugin '${name}' already disabled`)
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
entry.enabled = false
|
|
421
|
+
|
|
422
|
+
// 如果插件正在运行,停止它
|
|
423
|
+
if (entry.instance._started) {
|
|
424
|
+
try {
|
|
425
|
+
// 优先调用 stop 方法,其次调用 stopBot 方法
|
|
426
|
+
if (typeof entry.instance.stop === 'function') {
|
|
427
|
+
await entry.instance.stop()
|
|
428
|
+
} else if (typeof entry.instance.stopBot === 'function') {
|
|
429
|
+
await entry.instance.stopBot()
|
|
430
|
+
}
|
|
431
|
+
if (typeof entry.instance.uninstall === 'function') {
|
|
432
|
+
await entry.instance.uninstall(this.framework)
|
|
433
|
+
}
|
|
434
|
+
entry.instance._started = false
|
|
435
|
+
} catch (err) {
|
|
436
|
+
console.error(`[PluginManager] Stop failed for '${name}':`, err.message)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
this.framework.emit('plugin:disabled', entry.instance)
|
|
441
|
+
this._saveState()
|
|
442
|
+
console.log(`[PluginManager] Plugin '${name}' disabled`)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* 更新插件配置
|
|
447
|
+
* @param {string} name - 插件名称
|
|
448
|
+
* @param {Object} config - 新配置(会合并到现有配置)
|
|
449
|
+
*/
|
|
450
|
+
updatePluginConfig(name, config) {
|
|
451
|
+
const entry = this._plugins.get(name)
|
|
452
|
+
if (!entry) {
|
|
453
|
+
throw new Error(`Plugin '${name}' not found`)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (!entry.instance.config) {
|
|
457
|
+
entry.instance.config = {}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 合并配置
|
|
461
|
+
entry.instance.config = { ...entry.instance.config, ...config }
|
|
462
|
+
|
|
463
|
+
// 保存状态
|
|
464
|
+
this._saveState()
|
|
465
|
+
console.log(`[PluginManager] Plugin '${name}' config updated`)
|
|
466
|
+
|
|
467
|
+
return entry.instance.config
|
|
468
|
+
}
|
|
469
|
+
|
|
297
470
|
/**
|
|
298
471
|
* 启动所有已加载但未启动的插件(按优先级排序)
|
|
299
472
|
*/
|
|
300
473
|
async startAll() {
|
|
301
474
|
const entries = Array.from(this._plugins.values())
|
|
302
|
-
.filter(e => e.status === 'loaded')
|
|
475
|
+
.filter(e => e.status === 'loaded' && e.enabled) // 只启动已启用且已加载的插件
|
|
303
476
|
.sort((a, b) => (a.instance.priority || 100) - (b.instance.priority || 100))
|
|
304
477
|
|
|
305
478
|
for (const entry of entries) {
|
|
File without changes
|