foliko 1.0.47 → 1.0.49

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.47",
3
+ "version": "1.0.49",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/plugins/email.js CHANGED
@@ -11,7 +11,12 @@ class EmailPlugin extends Plugin {
11
11
  super()
12
12
  this.name = 'email'
13
13
  this.version = '1.0.0'
14
- this.description = '邮件收发插件 - 支持读取和发送电子邮件'
14
+ this.description = `邮件收发插件 - 支持读取和发送电子邮件
15
+ 发送邮件支持附件:
16
+ - attachments.path: 本地文件路径
17
+ - attachments.url: 远程图片/文件URL(自动下载)
18
+ - attachments.content: Base64内容
19
+ - attachments.cid: 嵌入式图片CID(HTML中用 <img src="cid:xxx"> 引用)`
15
20
  this.priority = 10
16
21
  // 默认不启用,需要在 plugins.json 中设置 enabled: true
17
22
  this.enabled = false
@@ -34,7 +39,14 @@ class EmailPlugin extends Plugin {
34
39
  body: z.string().describe('邮件正文内容'),
35
40
  cc: z.string().optional().describe('抄送邮箱地址,多个用逗号分隔'),
36
41
  bcc: z.string().optional().describe('密送邮箱地址,多个用逗号分隔'),
37
- isHtml: z.boolean().optional().describe('是否为HTML格式,默认false')
42
+ isHtml: z.boolean().optional().describe('是否为HTML格式,默认false'),
43
+ attachments: z.array(z.object({
44
+ filename: z.string().describe('附件文件名'),
45
+ path: z.string().optional().describe('本地文件路径'),
46
+ url: z.string().optional().describe('远程图片URL'),
47
+ content: z.string().optional().describe('Base64编码内容(可带前缀如 data:image/png;base64,)'),
48
+ cid: z.string().optional().describe('嵌入式图片的CID(用于在HTML中嵌入图片,如 <img src="cid:xxx">)')
49
+ })).optional().describe('附件列表,支持本地文件、远程URL或Base64内容')
38
50
  }),
39
51
  execute: async (args) => {
40
52
  return this._sendEmail(args)
@@ -119,6 +131,10 @@ class EmailPlugin extends Plugin {
119
131
  async _sendEmail(args) {
120
132
  try {
121
133
  const nodemailer = require('nodemailer')
134
+ const https = require('https')
135
+ const http = require('http')
136
+ const fs = require('fs')
137
+ const path = require('path')
122
138
 
123
139
  const smtpConfig = {
124
140
  host: process.env.SMTP_HOST || 'smtp.gmail.com',
@@ -142,6 +158,32 @@ class EmailPlugin extends Plugin {
142
158
  bcc: args.bcc
143
159
  }
144
160
 
161
+ // 处理附件
162
+ if (args.attachments && args.attachments.length > 0) {
163
+ mailOptions.attachments = []
164
+ for (const att of args.attachments) {
165
+ const attachment = { filename: att.filename }
166
+
167
+ if (att.path) {
168
+ // 本地文件
169
+ attachment.content = fs.createReadStream(att.path)
170
+ } else if (att.url) {
171
+ // 远程URL
172
+ attachment.content = await this._fetchUrl(att.url)
173
+ } else if (att.content) {
174
+ // Base64 内容
175
+ const base64Data = att.content.replace(/^data:[^;]+;base64,/, '')
176
+ attachment.content = Buffer.from(base64Data, 'base64')
177
+ }
178
+
179
+ if (att.cid) {
180
+ attachment.cid = att.cid
181
+ }
182
+
183
+ mailOptions.attachments.push(attachment)
184
+ }
185
+ }
186
+
145
187
  const info = await transporter.sendMail(mailOptions)
146
188
  return {
147
189
  success: true,
@@ -159,6 +201,35 @@ class EmailPlugin extends Plugin {
159
201
  }
160
202
  }
161
203
 
204
+ _fetchUrl(url) {
205
+ return new Promise((resolve, reject) => {
206
+ const protocol = url.startsWith('https') ? https : http
207
+ const lib = url.startsWith('https') ? require('https') : require('http')
208
+
209
+ const request = lib.get(url, (response) => {
210
+ if (response.statusCode === 301 || response.statusCode === 302) {
211
+ // 处理重定向
212
+ this._fetchUrl(response.headers.location).then(resolve).catch(reject)
213
+ return
214
+ }
215
+
216
+ const chunks = []
217
+ response.on('data', (chunk) => chunks.push(chunk))
218
+ response.on('end', () => {
219
+ const buffer = Buffer.concat(chunks)
220
+ resolve(buffer)
221
+ })
222
+ response.on('error', reject)
223
+ })
224
+
225
+ request.on('error', reject)
226
+ request.setTimeout(10000, () => {
227
+ request.destroy()
228
+ reject(new Error('下载超时'))
229
+ })
230
+ })
231
+ }
232
+
162
233
  async _readEmails(args) {
163
234
  try {
164
235
  const Imap = require('imap-mkl')
@@ -39,6 +39,12 @@ class WebPlugin extends Plugin {
39
39
  install(framework) {
40
40
  this._framework = framework
41
41
  this._registerTools()
42
+
43
+ // 将 WEB_BASE_URL 注入到 framework 的元数据,供所有 agent 使用
44
+ if (this._baseUrl && framework._mainAgent) {
45
+ framework._mainAgent.setMetadata('WEB_BASE_URL', this._baseUrl)
46
+ }
47
+
42
48
  return this
43
49
  }
44
50
 
@@ -48,6 +54,10 @@ class WebPlugin extends Plugin {
48
54
 
49
55
  reload(framework) {
50
56
  this._framework = framework
57
+ // 重新注入 WEB_BASE_URL
58
+ if (this._baseUrl && framework._mainAgent) {
59
+ framework._mainAgent.setMetadata('WEB_BASE_URL', this._baseUrl)
60
+ }
51
61
  }
52
62
 
53
63
  uninstall() {
@@ -246,7 +256,7 @@ class WebPlugin extends Plugin {
246
256
  async _handleRoute(c, route, pathname) {
247
257
  const params = this._extractParams(route.path, pathname)
248
258
  const query = this._parseQuery(c)
249
- const body = await c.req.json().catch(() => ({}))
259
+ const body = await this._parseBody(c)
250
260
 
251
261
  const context = { params, query, body }
252
262
  const result = await this._executeHandler(route.handler, context)
@@ -255,7 +265,7 @@ class WebPlugin extends Plugin {
255
265
 
256
266
  async _handleWebhook(c, webhook) {
257
267
  const query = this._parseQuery(c)
258
- const body = await c.req.json().catch(() => ({}))
268
+ const body = await this._parseBody(c)
259
269
  const webhookData = {
260
270
  path: webhook.path,
261
271
  method: c.req.method,
@@ -354,7 +364,7 @@ class WebPlugin extends Plugin {
354
364
  }
355
365
 
356
366
  console.log(`[Web] Route registered: ${method} ${path}`)
357
- return { success: true, message: `Route ${method} ${path} registered`, route: { method, path, description } }
367
+ return { success: true, message: `Route ${method} ${path} registered`, url: this._getUrl(path), route: { method, path, description } }
358
368
  }
359
369
 
360
370
  async _registerWebhook(prompt, awaitResponse = false) {
@@ -480,6 +490,17 @@ class WebPlugin extends Plugin {
480
490
  return query
481
491
  }
482
492
 
493
+ async _parseBody(c) {
494
+ try {
495
+ const rawText = await c.req.text()
496
+ console.log(rawText)
497
+ if (!rawText) return {}
498
+ return JSON.parse(rawText)
499
+ } catch (e) {
500
+ return {}
501
+ }
502
+ }
503
+
483
504
  _getUrl(path='') {
484
505
  if (this._baseUrl) {
485
506
  return `${this._baseUrl}${path}`