foliko 1.0.7 → 1.0.9
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 +7 -1
- package/.env.example +23 -0
- package/README.md +29 -2
- package/SPEC.md +75 -2
- package/cli/src/ui/chat-ui.js +41 -2
- package/docs/quick-reference.md +30 -4
- package/docs/user-manual.md +158 -3
- package/{test-chat.js → examples/test-chat.js} +2 -2
- 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/default-plugins.js +139 -59
- package/plugins/email.js +382 -0
- package/plugins/install-plugin.js +115 -12
- package/plugins/telegram-plugin.js +9 -0
- package/plugins/tools-plugin.js +75 -0
- package/skills/vb-agent-dev/AGENTS.md +81 -10
- package/skills/vb-agent-dev/SKILL.md +149 -25
- package/src/core/framework.js +27 -0
- package/src/core/plugin-manager.js +272 -16
- /package/{test-tg-simple.js → examples/test-tg-simple.js} +0 -0
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 }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* InstallPlugin - npm 包安装工具
|
|
3
|
-
* 自动安装缺少的 node 模块到 .agent
|
|
3
|
+
* 自动安装缺少的 node 模块到 .agent 目录或指定目录
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { execSync } = require('child_process')
|
|
@@ -14,7 +14,7 @@ class InstallPlugin extends Plugin {
|
|
|
14
14
|
super()
|
|
15
15
|
this.name = 'install'
|
|
16
16
|
this.version = '1.0.0'
|
|
17
|
-
this.description = '自动安装 npm
|
|
17
|
+
this.description = '自动安装 npm 包到指定目录'
|
|
18
18
|
|
|
19
19
|
this._agentDir = config.agentDir || '.agent'
|
|
20
20
|
this._nodeModulesDir = null
|
|
@@ -35,35 +35,73 @@ class InstallPlugin extends Plugin {
|
|
|
35
35
|
// 注册 install 工具
|
|
36
36
|
framework.registerTool({
|
|
37
37
|
name: 'install',
|
|
38
|
-
description: '安装 npm
|
|
38
|
+
description: '安装 npm 包到指定目录',
|
|
39
39
|
inputSchema: z.object({
|
|
40
|
-
package: z.string().describe('包名,如 lodash
|
|
40
|
+
package: z.string().optional().describe('包名,如 lodash、lodash@4.17.21 或 @scope/package'),
|
|
41
|
+
path: z.string().optional().describe('安装目标目录,如 .agent/plugins/my-plugin(默认为 .agent)'),
|
|
42
|
+
file: z.string().optional().describe('本地 package.json 路径,从该文件安装所有依赖')
|
|
41
43
|
}),
|
|
42
44
|
execute: async (args) => {
|
|
43
|
-
const { package: packageName } = args
|
|
44
|
-
|
|
45
|
+
const { package: packageName, path: targetPath, file: packageJsonPath } = args
|
|
46
|
+
|
|
47
|
+
// 优先处理 file 参数(从 package.json 安装)
|
|
48
|
+
if (packageJsonPath) {
|
|
49
|
+
const resolvedPath = path.resolve(process.cwd(), packageJsonPath)
|
|
50
|
+
return this._installFromPackageJson(resolvedPath, targetPath)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// package 和 path 都提供
|
|
54
|
+
if (packageName) {
|
|
55
|
+
return this._installPackage(packageName, targetPath)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 只有 path,安装该目录的 package.json
|
|
59
|
+
if (targetPath) {
|
|
60
|
+
const resolvedPath = path.resolve(process.cwd(), targetPath)
|
|
61
|
+
const pkgJson = path.join(resolvedPath, 'package.json')
|
|
62
|
+
if (fs.existsSync(pkgJson)) {
|
|
63
|
+
return this._installFromPackageJson(pkgJson, targetPath)
|
|
64
|
+
}
|
|
65
|
+
return { success: false, error: `package.json not found at ${pkgJson}` }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 什么都不提供,报错
|
|
69
|
+
return { success: false, error: 'Must provide package name or package.json path' }
|
|
45
70
|
}
|
|
46
71
|
})
|
|
47
72
|
|
|
48
73
|
return this
|
|
49
74
|
}
|
|
50
75
|
|
|
51
|
-
|
|
76
|
+
/**
|
|
77
|
+
* 安装单个包到指定目录
|
|
78
|
+
* @param {string} packageName - 包名
|
|
79
|
+
* @param {string|null} targetPath - 目标目录,默认为 .agent
|
|
80
|
+
*/
|
|
81
|
+
_installPackage(packageName, targetPath = null) {
|
|
52
82
|
try {
|
|
53
|
-
|
|
83
|
+
const installPath = targetPath
|
|
84
|
+
? path.resolve(process.cwd(), targetPath)
|
|
85
|
+
: this._agentDir
|
|
86
|
+
|
|
87
|
+
console.log(`[InstallPlugin] Installing ${packageName} to ${installPath}...`)
|
|
88
|
+
|
|
89
|
+
// 确保目标目录存在
|
|
90
|
+
if (!fs.existsSync(installPath)) {
|
|
91
|
+
fs.mkdirSync(installPath, { recursive: true })
|
|
92
|
+
}
|
|
54
93
|
|
|
55
94
|
// 使用 npm install 安装到指定目录
|
|
56
|
-
|
|
57
|
-
execSync(`npm install ${packageName} --prefix "${this._agentDir}"`, {
|
|
95
|
+
execSync(`npm install ${packageName} --prefix "${installPath}"`, {
|
|
58
96
|
stdio: 'inherit',
|
|
59
|
-
cwd:
|
|
97
|
+
cwd: installPath
|
|
60
98
|
})
|
|
61
99
|
|
|
62
100
|
console.log(`[InstallPlugin] Successfully installed ${packageName}`)
|
|
63
101
|
|
|
64
102
|
return {
|
|
65
103
|
success: true,
|
|
66
|
-
message: `Successfully installed ${packageName}`
|
|
104
|
+
message: `Successfully installed ${packageName} to ${installPath}`
|
|
67
105
|
}
|
|
68
106
|
} catch (err) {
|
|
69
107
|
return {
|
|
@@ -73,6 +111,61 @@ class InstallPlugin extends Plugin {
|
|
|
73
111
|
}
|
|
74
112
|
}
|
|
75
113
|
|
|
114
|
+
/**
|
|
115
|
+
* 从 package.json 安装所有依赖
|
|
116
|
+
* @param {string} packageJsonPath - package.json 路径
|
|
117
|
+
* @param {string|null} targetPath - 目标目录,默认为 package.json 所在目录
|
|
118
|
+
*/
|
|
119
|
+
_installFromPackageJson(packageJsonPath, targetPath = null) {
|
|
120
|
+
try {
|
|
121
|
+
const resolvedPkgPath = path.resolve(process.cwd(), packageJsonPath)
|
|
122
|
+
const pkgDir = path.dirname(resolvedPkgPath)
|
|
123
|
+
const installPath = targetPath
|
|
124
|
+
? path.resolve(process.cwd(), targetPath)
|
|
125
|
+
: pkgDir
|
|
126
|
+
|
|
127
|
+
console.log(`[InstallPlugin] Installing dependencies from ${resolvedPkgPath} to ${installPath}...`)
|
|
128
|
+
|
|
129
|
+
// 读取 package.json 获取要安装的包
|
|
130
|
+
const pkg = JSON.parse(fs.readFileSync(resolvedPkgPath, 'utf-8'))
|
|
131
|
+
const packages = []
|
|
132
|
+
|
|
133
|
+
if (pkg.dependencies) {
|
|
134
|
+
packages.push(...Object.keys(pkg.dependencies))
|
|
135
|
+
}
|
|
136
|
+
if (pkg.devDependencies) {
|
|
137
|
+
packages.push(...Object.keys(pkg.devDependencies))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (packages.length === 0) {
|
|
141
|
+
return { success: true, message: 'No dependencies to install' }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 确保目标目录存在
|
|
145
|
+
if (!fs.existsSync(installPath)) {
|
|
146
|
+
fs.mkdirSync(installPath, { recursive: true })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 使用 npm install 安装
|
|
150
|
+
execSync(`npm install --prefix "${installPath}" --no-save`, {
|
|
151
|
+
stdio: 'inherit',
|
|
152
|
+
cwd: pkgDir
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
console.log(`[InstallPlugin] Successfully installed ${packages.length} packages`)
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
message: `Successfully installed ${packages.length} dependencies to ${installPath}`
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
error: `Failed to install from package.json: ${err.message}`
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
76
169
|
/**
|
|
77
170
|
* 获取 node_modules 路径,供其他插件使用
|
|
78
171
|
*/
|
|
@@ -88,6 +181,16 @@ class InstallPlugin extends Plugin {
|
|
|
88
181
|
module.paths.unshift(this._nodeModulesDir)
|
|
89
182
|
}
|
|
90
183
|
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 添加自定义路径到 module.paths
|
|
187
|
+
* @param {string} dir - 目录路径
|
|
188
|
+
*/
|
|
189
|
+
addCustomModulePath(dir) {
|
|
190
|
+
if (dir && fs.existsSync(dir) && !module.paths.includes(dir)) {
|
|
191
|
+
module.paths.unshift(dir)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
91
194
|
}
|
|
92
195
|
|
|
93
196
|
module.exports = { InstallPlugin }
|
|
@@ -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
|
}
|