foliko 1.0.27 → 1.0.29
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 +9 -1
- package/cli/src/commands/chat.js +0 -1
- package/package.json +2 -1
- package/plugins/ai-plugin.js +0 -1
- package/plugins/default-plugins.js +25 -3
- package/plugins/email.js +110 -40
- package/plugins/weixin-plugin.js +1 -1
- package/src/core/plugin-manager.js +11 -5
- package/src/core/provider.js +1 -1
|
@@ -64,7 +64,15 @@
|
|
|
64
64
|
"Bash(node -c src/core/plugin-base.js && node -c src/core/plugin-manager.js 2>&1)",
|
|
65
65
|
"Bash(node -c plugins/telegram-plugin.js && node -c plugins/weixin-plugin.js 2>&1)",
|
|
66
66
|
"Bash(node -c src/core/plugin-manager.js 2>&1)",
|
|
67
|
-
"Bash(node -c src/core/plugin-manager.js && node -c src/core/plugin-base.js && node -c plugins/default-plugins.js && node -c plugins/telegram-plugin.js && node -c plugins/weixin-plugin.js 2>&1)"
|
|
67
|
+
"Bash(node -c src/core/plugin-manager.js && node -c src/core/plugin-base.js && node -c plugins/default-plugins.js && node -c plugins/telegram-plugin.js && node -c plugins/weixin-plugin.js 2>&1)",
|
|
68
|
+
"Bash(node -e \"require\\('dotenv'\\).config\\(\\); console.log\\('PROVIDER:', process.env.FOLIKO_PROVIDER\\); console.log\\('MODEL:', process.env.FOLIKO_MODEL\\); console.log\\('KEY:', process.env.DEEPSEEK_API_KEY ? 'set' : 'not set'\\)\" 2>&1)",
|
|
69
|
+
"Bash(node cli/src/index.js chat 2>&1 | head -30)",
|
|
70
|
+
"Bash(node -e 'const { DEFAULT_PROVIDERS } = require\\(\"./src/core/provider\"\\); console.log\\(DEFAULT_PROVIDERS\\);')",
|
|
71
|
+
"Bash(node -e \"const dotenv = require\\('dotenv'\\); const result = dotenv.config\\(\\); console.log\\('Result:', result\\); console.log\\('PROVIDER after dotenv:', process.env.FOLIKO_PROVIDER\\);\" 2>&1)",
|
|
72
|
+
"Bash(node -c plugins/email.js && node -c plugins/default-plugins.js 2>&1)",
|
|
73
|
+
"Bash(node -c plugins/email.js 2>&1)",
|
|
74
|
+
"Bash(npm list:*)",
|
|
75
|
+
"Bash(node -e \"const {EmailPlugin} = require\\('./plugins/email'\\); const p = new EmailPlugin\\(\\); console.log\\('email plugin loaded ok'\\); console.log\\('enabled:', p.enabled\\); console.log\\('version:', p.version\\);\" 2>&1)"
|
|
68
76
|
]
|
|
69
77
|
}
|
|
70
78
|
}
|
package/cli/src/commands/chat.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foliko",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
4
4
|
"description": "简约的插件化 Agent 框架",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"ai": "^6.0.116",
|
|
31
31
|
"dotenv": "^17.3.1",
|
|
32
32
|
"imap": "^0.8.19",
|
|
33
|
+
"imap-mkl": "^1.0.2",
|
|
33
34
|
"mailparser": "^3.7.2",
|
|
34
35
|
"marked": "^11.2.0",
|
|
35
36
|
"marked-terminal": "6",
|
package/plugins/ai-plugin.js
CHANGED
|
@@ -79,7 +79,7 @@ function loadAgentConfig(agentDir = '.agent') {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// 加载 plugins.json (支持 telegram, weixin 等插件配置)
|
|
82
|
+
// 加载 plugins.json (支持 telegram, weixin, email 等插件配置)
|
|
83
83
|
const pluginsFile = path.join(resolvedDir, 'plugins.json')
|
|
84
84
|
if (fs.existsSync(pluginsFile)) {
|
|
85
85
|
try {
|
|
@@ -93,6 +93,10 @@ function loadAgentConfig(agentDir = '.agent') {
|
|
|
93
93
|
if (pluginsConfig.weixin) {
|
|
94
94
|
config.weixin = pluginsConfig.weixin
|
|
95
95
|
}
|
|
96
|
+
// email 配置
|
|
97
|
+
if (pluginsConfig.email) {
|
|
98
|
+
config.email = pluginsConfig.email
|
|
99
|
+
}
|
|
96
100
|
} catch (err) {
|
|
97
101
|
console.error(`[AgentConfig] Failed to load plugins.json:`, err.message)
|
|
98
102
|
}
|
|
@@ -244,7 +248,6 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
244
248
|
]
|
|
245
249
|
|
|
246
250
|
console.log('[Bootstrap] Loading default plugins...')
|
|
247
|
-
|
|
248
251
|
// AI 插件(如果已禁用则跳过)
|
|
249
252
|
if (!shouldLoad('ai') || !(aiConfig.provider || aiConfig.model || aiConfig.apiKey)) {
|
|
250
253
|
// 跳过或已禁用
|
|
@@ -421,7 +424,26 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
421
424
|
}
|
|
422
425
|
}
|
|
423
426
|
|
|
424
|
-
// 12.8
|
|
427
|
+
// 12.8 Email 插件(默认禁用,需要在 plugins.json 中设置 enabled: true)
|
|
428
|
+
if (shouldLoad('email')) {
|
|
429
|
+
try {
|
|
430
|
+
const { Plugin } = require('../src/core/plugin-base')
|
|
431
|
+
const { EmailPlugin } = require('./email')
|
|
432
|
+
// 支持两种格式:email.smtp 或 email.config.smtp
|
|
433
|
+
const emailData = agentConfig.email || {}
|
|
434
|
+
const emailCfg = emailData.config || {}
|
|
435
|
+
const emailConfig = {
|
|
436
|
+
smtp: emailCfg.smtp || emailData.smtp || {},
|
|
437
|
+
imap: emailCfg.imap || emailData.imap || {},
|
|
438
|
+
clientId: emailCfg.clientId || emailData.clientId
|
|
439
|
+
}
|
|
440
|
+
await framework.loadPlugin(new EmailPlugin(emailConfig))
|
|
441
|
+
} catch (err) {
|
|
442
|
+
console.warn('[Bootstrap] Email Plugin not available:', err.message)
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// 12.9 SubAgent 管理器
|
|
425
447
|
if (shouldLoad('subagent-manager')) {
|
|
426
448
|
try {
|
|
427
449
|
const { SubAgentManagerPlugin } = require('./subagent-plugin')
|
package/plugins/email.js
CHANGED
|
@@ -13,7 +13,18 @@ class EmailPlugin extends Plugin {
|
|
|
13
13
|
this.version = '1.0.0'
|
|
14
14
|
this.description = '邮件收发插件 - 支持读取和发送电子邮件'
|
|
15
15
|
this.priority = 10
|
|
16
|
-
|
|
16
|
+
// 默认不启用,需要在 plugins.json 中设置 enabled: true
|
|
17
|
+
this.enabled = false
|
|
18
|
+
// IMAP ID 信息
|
|
19
|
+
this.config = {
|
|
20
|
+
...config,
|
|
21
|
+
clientId: config.clientId || {
|
|
22
|
+
name: 'FolikoAgent',
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
vendor: 'Foliko',
|
|
25
|
+
supportEmail: config.imap?.user || 'unknown@example.com'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
install(framework) {
|
|
@@ -105,9 +116,26 @@ class EmailPlugin extends Plugin {
|
|
|
105
116
|
imap_port: z.number().optional().describe('IMAP端口'),
|
|
106
117
|
imap_user: z.string().optional().describe('IMAP用户名'),
|
|
107
118
|
imap_pass: z.string().optional().describe('IMAP密码'),
|
|
108
|
-
from_email: z.string().optional().describe('默认发件人地址')
|
|
119
|
+
from_email: z.string().optional().describe('默认发件人地址'),
|
|
120
|
+
client_id_name: z.string().optional().describe('IMAP客户端名称'),
|
|
121
|
+
client_id_version: z.string().optional().describe('IMAP客户端版本'),
|
|
122
|
+
client_id_vendor: z.string().optional().describe('IMAP客户端厂商'),
|
|
123
|
+
client_id_support_email: z.string().optional().describe('IMAP客户端支持邮箱')
|
|
109
124
|
}),
|
|
110
125
|
execute: async (args) => {
|
|
126
|
+
// 处理 clientId 字段
|
|
127
|
+
if (args.client_id_name || args.client_id_version || args.client_id_vendor || args.client_id_support_email) {
|
|
128
|
+
this.config.clientId = {
|
|
129
|
+
name: args.client_id_name || this.config.clientId?.name || 'FolikoAgent',
|
|
130
|
+
version: args.client_id_version || this.config.clientId?.version || '1.0.0',
|
|
131
|
+
vendor: args.client_id_vendor || this.config.clientId?.vendor || 'Foliko',
|
|
132
|
+
supportEmail: args.client_id_support_email || this.config.clientId?.supportEmail
|
|
133
|
+
}
|
|
134
|
+
delete args.client_id_name
|
|
135
|
+
delete args.client_id_version
|
|
136
|
+
delete args.client_id_vendor
|
|
137
|
+
delete args.client_id_support_email
|
|
138
|
+
}
|
|
111
139
|
Object.assign(this.config, args)
|
|
112
140
|
return {
|
|
113
141
|
success: true,
|
|
@@ -172,7 +200,12 @@ class EmailPlugin extends Plugin {
|
|
|
172
200
|
host: args.host || this.config.imap_host || process.env.IMAP_HOST,
|
|
173
201
|
port: args.port || this.config.imap_port || 993,
|
|
174
202
|
tls: true,
|
|
175
|
-
tlsOptions: { rejectUnauthorized: false }
|
|
203
|
+
tlsOptions: { rejectUnauthorized: false },
|
|
204
|
+
id: {
|
|
205
|
+
name: this.config.clientId?.name || 'FolikoAgent',
|
|
206
|
+
version: this.config.clientId?.version || '1.0.0',
|
|
207
|
+
vendor: this.config.clientId?.vendor || 'Foliko'
|
|
208
|
+
}
|
|
176
209
|
}
|
|
177
210
|
|
|
178
211
|
const box = args.box || 'INBOX'
|
|
@@ -205,7 +238,13 @@ class EmailPlugin extends Plugin {
|
|
|
205
238
|
host: args.host || this.config.imap_host || process.env.IMAP_HOST,
|
|
206
239
|
port: args.port || this.config.imap_port || 993,
|
|
207
240
|
tls: true,
|
|
208
|
-
tlsOptions: { rejectUnauthorized: false }
|
|
241
|
+
tlsOptions: { rejectUnauthorized: false },
|
|
242
|
+
id: {
|
|
243
|
+
name: this.config.clientId?.name || 'FolikoAgent',
|
|
244
|
+
version: this.config.clientId?.version || '1.0.0',
|
|
245
|
+
vendor: this.config.clientId?.vendor || 'Foliko',
|
|
246
|
+
'support-email': this.config.clientId?.supportEmail || 'unknown@example.com'
|
|
247
|
+
}
|
|
209
248
|
}
|
|
210
249
|
|
|
211
250
|
const box = args.box || 'INBOX'
|
|
@@ -234,7 +273,13 @@ class EmailPlugin extends Plugin {
|
|
|
234
273
|
host: args.host || this.config.imap_host || process.env.IMAP_HOST,
|
|
235
274
|
port: args.port || this.config.imap_port || 993,
|
|
236
275
|
tls: true,
|
|
237
|
-
tlsOptions: { rejectUnauthorized: false }
|
|
276
|
+
tlsOptions: { rejectUnauthorized: false },
|
|
277
|
+
id: {
|
|
278
|
+
name: this.config.clientId?.name || 'FolikoAgent',
|
|
279
|
+
version: this.config.clientId?.version || '1.0.0',
|
|
280
|
+
vendor: this.config.clientId?.vendor || 'Foliko',
|
|
281
|
+
'support-email': this.config.clientId?.supportEmail || 'unknown@example.com'
|
|
282
|
+
}
|
|
238
283
|
}
|
|
239
284
|
|
|
240
285
|
await this._markEmailAsRead(imapConfig, args.messageId)
|
|
@@ -253,13 +298,19 @@ class EmailPlugin extends Plugin {
|
|
|
253
298
|
|
|
254
299
|
_fetchEmails(imapConfig, box, limit, unreadOnly, searchCriteria) {
|
|
255
300
|
return new Promise((resolve, reject) => {
|
|
301
|
+
const Imap = require('imap-mkl')
|
|
302
|
+
const { simpleParser } = require('mailparser')
|
|
256
303
|
const imap = new Imap(imapConfig)
|
|
257
304
|
const emails = []
|
|
258
305
|
|
|
259
|
-
|
|
306
|
+
const cleanup = () => {
|
|
307
|
+
try { imap.end() } catch (e) {}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
imap.on('ready', () => {
|
|
260
311
|
imap.openBox(box, true, (err) => {
|
|
261
312
|
if (err) {
|
|
262
|
-
|
|
313
|
+
cleanup()
|
|
263
314
|
return reject(err)
|
|
264
315
|
}
|
|
265
316
|
|
|
@@ -270,106 +321,125 @@ class EmailPlugin extends Plugin {
|
|
|
270
321
|
|
|
271
322
|
imap.search(searchFilter, (err, results) => {
|
|
272
323
|
if (err) {
|
|
273
|
-
|
|
324
|
+
cleanup()
|
|
274
325
|
return reject(err)
|
|
275
326
|
}
|
|
276
327
|
|
|
277
|
-
if (results.length === 0) {
|
|
278
|
-
|
|
328
|
+
if (!results || results.length === 0) {
|
|
329
|
+
cleanup()
|
|
279
330
|
return resolve([])
|
|
280
331
|
}
|
|
281
332
|
|
|
282
333
|
const fetchIds = results.slice(-limit)
|
|
334
|
+
const f = imap.fetch(fetchIds, { bodies: '' })
|
|
283
335
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
struct: true
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
f.on('message', (msg, seqno) => {
|
|
290
|
-
const email = { seqno }
|
|
336
|
+
f.on('message', (msg) => {
|
|
337
|
+
const email = {}
|
|
291
338
|
|
|
292
339
|
msg.on('body', (stream) => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
340
|
+
simpleParser(stream, (err, mail) => {
|
|
341
|
+
if (err) {
|
|
342
|
+
email.error = err.message
|
|
343
|
+
} else {
|
|
344
|
+
email.subject = mail.subject
|
|
345
|
+
email.from = mail.from?.text || ''
|
|
346
|
+
email.to = mail.to?.text || ''
|
|
347
|
+
email.date = mail.date?.toISOString() || ''
|
|
348
|
+
email.text = mail.text
|
|
349
|
+
email.html = mail.html
|
|
350
|
+
email.attachments = mail.attachments?.map(a => ({
|
|
351
|
+
filename: a.filename,
|
|
352
|
+
contentType: a.contentType
|
|
353
|
+
})) || []
|
|
354
|
+
}
|
|
301
355
|
})
|
|
302
356
|
})
|
|
303
357
|
|
|
304
|
-
msg.
|
|
358
|
+
msg.on('attributes', (attrs) => {
|
|
305
359
|
email.uid = attrs.uid
|
|
306
360
|
email.id = attrs.uid
|
|
307
361
|
email.flags = attrs.flags
|
|
308
362
|
})
|
|
309
363
|
|
|
310
|
-
msg.
|
|
364
|
+
msg.on('end', () => {
|
|
311
365
|
emails.push(email)
|
|
312
366
|
})
|
|
313
367
|
})
|
|
314
368
|
|
|
315
|
-
f.
|
|
316
|
-
|
|
369
|
+
f.on('error', (err) => {
|
|
370
|
+
cleanup()
|
|
317
371
|
reject(err)
|
|
318
372
|
})
|
|
319
373
|
|
|
320
|
-
f.
|
|
321
|
-
|
|
374
|
+
f.on('end', () => {
|
|
375
|
+
cleanup()
|
|
322
376
|
resolve(emails)
|
|
323
377
|
})
|
|
324
378
|
})
|
|
325
379
|
})
|
|
326
380
|
})
|
|
327
381
|
|
|
328
|
-
imap.
|
|
382
|
+
imap.on('error', (err) => reject(err))
|
|
383
|
+
imap.on('end', () => {})
|
|
384
|
+
|
|
329
385
|
imap.connect()
|
|
330
386
|
})
|
|
331
387
|
}
|
|
332
388
|
|
|
333
389
|
_fetchUnreadCount(imapConfig, box) {
|
|
334
390
|
return new Promise((resolve, reject) => {
|
|
391
|
+
const Imap = require('imap-mkl')
|
|
335
392
|
const imap = new Imap(imapConfig)
|
|
336
393
|
|
|
337
|
-
|
|
394
|
+
const cleanup = () => {
|
|
395
|
+
try { imap.end() } catch (e) {}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
imap.on('ready', () => {
|
|
338
399
|
imap.openBox(box, true, (err, box) => {
|
|
339
400
|
if (err) {
|
|
340
|
-
|
|
401
|
+
cleanup()
|
|
341
402
|
return reject(err)
|
|
342
403
|
}
|
|
343
404
|
const unreadCount = box.messages.unread
|
|
344
|
-
|
|
405
|
+
cleanup()
|
|
345
406
|
resolve(unreadCount)
|
|
346
407
|
})
|
|
347
408
|
})
|
|
348
409
|
|
|
349
|
-
imap.
|
|
410
|
+
imap.on('error', (err) => reject(err))
|
|
411
|
+
imap.on('end', () => {})
|
|
412
|
+
|
|
350
413
|
imap.connect()
|
|
351
414
|
})
|
|
352
415
|
}
|
|
353
416
|
|
|
354
417
|
_markEmailAsRead(imapConfig, messageId) {
|
|
355
418
|
return new Promise((resolve, reject) => {
|
|
419
|
+
const Imap = require('imap-mkl')
|
|
356
420
|
const imap = new Imap(imapConfig)
|
|
357
421
|
|
|
358
|
-
|
|
422
|
+
const cleanup = () => {
|
|
423
|
+
try { imap.end() } catch (e) {}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
imap.on('ready', () => {
|
|
359
427
|
imap.openBox('INBOX', true, (err) => {
|
|
360
428
|
if (err) {
|
|
361
|
-
|
|
429
|
+
cleanup()
|
|
362
430
|
return reject(err)
|
|
363
431
|
}
|
|
364
432
|
imap.addFlags(messageId, '\\Seen', (err) => {
|
|
365
|
-
|
|
433
|
+
cleanup()
|
|
366
434
|
if (err) reject(err)
|
|
367
435
|
else resolve()
|
|
368
436
|
})
|
|
369
437
|
})
|
|
370
438
|
})
|
|
371
439
|
|
|
372
|
-
imap.
|
|
440
|
+
imap.on('error', (err) => reject(err))
|
|
441
|
+
imap.on('end', () => {})
|
|
442
|
+
|
|
373
443
|
imap.connect()
|
|
374
444
|
})
|
|
375
445
|
}
|
package/plugins/weixin-plugin.js
CHANGED
|
@@ -24,7 +24,7 @@ module.exports = function(Plugin) {
|
|
|
24
24
|
this.config = {
|
|
25
25
|
forceLogin: config.forceLogin || process.env.WEIXIN_FORCE_LOGIN === 'true',
|
|
26
26
|
qrcodeTerminal: config.qrcodeTerminal !== false && process.env.WEIXIN_QRCODE_TERMINAL !== 'false',
|
|
27
|
-
systemPrompt: config.systemPrompt || '你是一个有帮助的AI
|
|
27
|
+
systemPrompt: config.systemPrompt || '你是一个有帮助的AI助手。回复内容不要使用markdown格式文本',
|
|
28
28
|
allowedUsers: config.allowedUsers || []
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -32,14 +32,20 @@ class PluginManager {
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* 保存插件状态到文件
|
|
35
|
+
* 注意:AI 插件配置不保存(从环境变量和命令行获取)
|
|
35
36
|
*/
|
|
36
37
|
_saveState() {
|
|
37
38
|
try {
|
|
38
39
|
const state = {}
|
|
39
40
|
for (const [name, entry] of this._plugins) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// AI 配置不保存,每次从环境变量和命令行获取
|
|
42
|
+
if (name === 'ai') {
|
|
43
|
+
state[name] = { enabled: entry.enabled }
|
|
44
|
+
} else {
|
|
45
|
+
state[name] = {
|
|
46
|
+
enabled: entry.enabled,
|
|
47
|
+
config: entry.instance?.config || {}
|
|
48
|
+
}
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
fs.writeFileSync(this._getStateFile(), JSON.stringify(state, null, 2))
|
|
@@ -85,8 +91,8 @@ class PluginManager {
|
|
|
85
91
|
const savedEnabled = savedState[pluginInstance.name]?.enabled
|
|
86
92
|
const savedConfig = savedState[pluginInstance.name]?.config
|
|
87
93
|
|
|
88
|
-
//
|
|
89
|
-
if (savedConfig && pluginInstance.config) {
|
|
94
|
+
// 恢复保存的配置到插件实例(AI 插件不恢复配置,从环境变量获取)
|
|
95
|
+
if (savedConfig && pluginInstance.config && pluginInstance.name !== 'ai') {
|
|
90
96
|
pluginInstance.config = { ...pluginInstance.config, ...savedConfig }
|
|
91
97
|
}
|
|
92
98
|
|
package/src/core/provider.js
CHANGED
|
@@ -44,7 +44,7 @@ const DEFAULT_PROVIDERS = {
|
|
|
44
44
|
function createAI(config) {
|
|
45
45
|
const { provider, model, apiKey, baseURL } = config
|
|
46
46
|
const providerName = (provider || 'deepseek').toLowerCase()
|
|
47
|
-
|
|
47
|
+
console.log(config)
|
|
48
48
|
// 检查是否是预定义提供商
|
|
49
49
|
if (DEFAULT_PROVIDERS[providerName]) {
|
|
50
50
|
const providerConfig = DEFAULT_PROVIDERS[providerName]
|