foliko 1.0.63 → 1.0.65

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/plugins/email.js CHANGED
@@ -44,19 +44,19 @@ class EmailPlugin extends Plugin {
44
44
  return this
45
45
  }
46
46
 
47
- if (process.env.IMAP_USER && process.env.IMAP_PASS) {
48
- console.log('[Email] Auto-starting email watch...')
49
- this._startEmailWatch({
50
- interval: 60,
51
- host: process.env.IMAP_HOST,
52
- port: parseInt(process.env.IMAP_PORT) || 993,
53
- user: process.env.IMAP_USER,
54
- password: process.env.IMAP_PASS,
55
- box: 'INBOX'
56
- })
57
- } else {
58
- console.log('[Email] IMAP credentials not configured, skipping auto-start')
59
- }
47
+ // if (process.env.IMAP_USER && process.env.IMAP_PASS) {
48
+ // console.log('[Email] Auto-starting email watch...')
49
+ // this._startEmailWatch({
50
+ // interval: 60,
51
+ // host: process.env.IMAP_HOST,
52
+ // port: parseInt(process.env.IMAP_PORT) || 993,
53
+ // user: process.env.IMAP_USER,
54
+ // password: process.env.IMAP_PASS,
55
+ // box: 'INBOX'
56
+ // })
57
+ // } else {
58
+ // console.log('[Email] IMAP credentials not configured, skipping auto-start')
59
+ // }
60
60
  return this
61
61
  }
62
62
 
@@ -148,6 +148,19 @@ class EmailPlugin extends Plugin {
148
148
  }
149
149
  })
150
150
 
151
+ // 删除邮件
152
+ this._framework.registerTool({
153
+ name: 'email_delete',
154
+ description: '删除邮件(标记为已删除,然后永久删除)',
155
+ inputSchema: z.object({
156
+ messageId: z.string().describe('邮件UID或序列号'),
157
+ box: z.string().optional().describe('邮箱文件夹,默认INBOX')
158
+ }),
159
+ execute: async (args) => {
160
+ return this._deleteEmail(args)
161
+ }
162
+ })
163
+
151
164
  // 配置邮箱连接
152
165
  this._framework.registerTool({
153
166
  name: 'email_configure',
@@ -190,20 +203,20 @@ class EmailPlugin extends Plugin {
190
203
  })
191
204
 
192
205
  // 自动回复邮件
193
- // this._framework.registerTool({
194
- // name: 'email_auto_reply',
195
- // description: '自动分析邮件内容并发送回复(无需用户确认)',
196
- // inputSchema: z.object({
197
- // to: z.string().describe('收件人邮箱地址'),
198
- // subject: z.string().describe('原始邮件主题'),
199
- // body: z.string().describe('原始邮件内容'),
200
- // from: z.string().optional().describe('发件人邮箱地址(可选)'),
201
- // prompt: z.string().optional().describe('自定义提示词,用于指导AI生成回复内容')
202
- // }),
203
- // execute: async (args) => {
204
- // return this._handleAutoReply(args)
205
- // }
206
- // })
206
+ this._framework.registerTool({
207
+ name: 'email_auto_reply',
208
+ description: '自动分析邮件内容并发送回复(无需用户确认)',
209
+ inputSchema: z.object({
210
+ to: z.string().describe('收件人邮箱地址'),
211
+ subject: z.string().describe('原始邮件主题'),
212
+ body: z.string().describe('原始邮件内容'),
213
+ from: z.string().optional().describe('发件人邮箱地址(可选)'),
214
+ prompt: z.string().optional().describe('自定义提示词,用于指导AI生成回复内容')
215
+ }),
216
+ execute: async (args) => {
217
+ return this._handleAutoReply(args)
218
+ }
219
+ })
207
220
  }
208
221
 
209
222
  /**
@@ -757,6 +770,40 @@ ${body}
757
770
  }
758
771
  }
759
772
 
773
+ async _deleteEmail(args) {
774
+ try {
775
+ const Imap = require('imap-mkl')
776
+
777
+ const imapConfig = {
778
+ user: args.user || process.env.IMAP_USER,
779
+ password: args.password || process.env.IMAP_PASS,
780
+ host: args.host || process.env.IMAP_HOST,
781
+ port: args.port || parseInt(process.env.IMAP_PORT) || 993,
782
+ tls: true,
783
+ tlsOptions: { rejectUnauthorized: false },
784
+ id: {
785
+ name: process.env.IMAP_CLIENT_NAME || 'FolikoAgent',
786
+ version: process.env.IMAP_CLIENT_VERSION || '1.0.0',
787
+ vendor: process.env.IMAP_CLIENT_VENDOR || 'Foliko',
788
+ 'support-email': process.env.IMAP_CLIENT_SUPPORT_EMAIL || 'unknown@example.com'
789
+ }
790
+ }
791
+
792
+ const box = args.box || 'INBOX'
793
+ await this._moveToTrash(imapConfig, box, args.messageId)
794
+
795
+ return {
796
+ success: true,
797
+ message: '邮件已删除'
798
+ }
799
+ } catch (error) {
800
+ return {
801
+ success: false,
802
+ error: error.message
803
+ }
804
+ }
805
+ }
806
+
760
807
  _fetchEmails(imapConfig, box, limit, unreadOnly, searchCriteria) {
761
808
  return new Promise((resolve, reject) => {
762
809
  const Imap = require('imap-mkl')
@@ -924,6 +971,44 @@ ${body}
924
971
  })
925
972
  }
926
973
 
974
+ _moveToTrash(imapConfig, box, messageId) {
975
+ return new Promise((resolve, reject) => {
976
+ const Imap = require('imap-mkl')
977
+ const imap = new Imap(imapConfig)
978
+
979
+ const cleanup = () => {
980
+ try { imap.end() } catch (e) {}
981
+ }
982
+
983
+ imap.on('ready', () => {
984
+ imap.openBox(box, true, (err) => {
985
+ if (err) {
986
+ cleanup()
987
+ return reject(err)
988
+ }
989
+ // 标记邮件为已删除
990
+ imap.addFlags(messageId, '\\Deleted', (err) => {
991
+ if (err) {
992
+ cleanup()
993
+ return reject(err)
994
+ }
995
+ // 执行 expunge 永久删除
996
+ imap.expunge((err) => {
997
+ cleanup()
998
+ if (err) reject(err)
999
+ else resolve()
1000
+ })
1001
+ })
1002
+ })
1003
+ })
1004
+
1005
+ imap.on('error', (err) => reject(err))
1006
+ imap.on('end', () => {})
1007
+
1008
+ imap.connect()
1009
+ })
1010
+ }
1011
+
927
1012
  uninstall(framework) {
928
1013
  // 停止邮件监控
929
1014
  if (this._watchInterval) {
@@ -394,6 +394,36 @@ class FileSystemPlugin extends Plugin {
394
394
  }
395
395
  })
396
396
 
397
+ // 发送通知
398
+ framework.registerTool({
399
+ name: 'notification_send',
400
+ description: '发送系统通知,仅发送给当前聊天会话,通知会显示给用户或在下次对话时呈现',
401
+ inputSchema: z.object({
402
+ title: z.string().describe('通知标题'),
403
+ message: z.string().describe('通知内容'),
404
+ source: z.string().optional().describe('通知来源标识,默认 系统消息')
405
+ }),
406
+ execute: async (args, framework) => {
407
+ const { title, message, source = '系统消息' } = args
408
+ try {
409
+ // 获取当前执行上下文中的 sessionId,只发送到当前会话
410
+ const ctx = framework.getExecutionContext()
411
+ const sessionId = ctx?.sessionId || null
412
+
413
+ framework.emit('notification', {
414
+ title,
415
+ message,
416
+ source,
417
+ sessionId,
418
+ timestamp: new Date().toISOString()
419
+ })
420
+ return { success: true, message: '通知已发送' }
421
+ } catch (error) {
422
+ return { success: false, error: error.message }
423
+ }
424
+ }
425
+ })
426
+
397
427
  return this
398
428
  }
399
429
  }
@@ -203,7 +203,7 @@ class SchedulerPlugin extends Plugin {
203
203
  return bTime - aTime
204
204
  })
205
205
  targetSessionId = sessions[0].id
206
- console.log(`[Scheduler] Auto-detected active session: ${targetSessionId}`)
206
+ //console.log(`[Scheduler] Auto-detected active session: ${targetSessionId}`)
207
207
  }
208
208
  }
209
209
  }
@@ -534,12 +534,12 @@ class SchedulerPlugin extends Plugin {
534
534
  * 执行任务
535
535
  */
536
536
  async _executeTask(task) {
537
- console.log(`[Scheduler] Executing task: ${task.name} (${task.id})`)
538
- console.log(`[Scheduler] Message: ${task.message}`)
539
- if (task.sessionId) {
540
- console.log(`[Scheduler] Target session: ${task.sessionId}`)
541
- }
542
- console.log(`[Scheduler] LLM mode: ${task.llm ? 'enabled' : 'disabled'}`)
537
+ // console.log(`[Scheduler] Executing task: ${task.name} (${task.id})`)
538
+ // console.log(`[Scheduler] Message: ${task.message}`)
539
+ // if (task.sessionId) {
540
+ // console.log(`[Scheduler] Target session: ${task.sessionId}`)
541
+ // }
542
+ // console.log(`[Scheduler] LLM mode: ${task.llm ? 'enabled' : 'disabled'}`)
543
543
 
544
544
  task.lastRun = new Date()
545
545
  task.runCount++
@@ -569,9 +569,9 @@ class SchedulerPlugin extends Plugin {
569
569
  responseText = result.text
570
570
  }
571
571
 
572
- if (responseText) {
573
- console.log(`\n🔔 [定时提醒] ${responseText}\n`)
574
- }
572
+ // if (responseText) {
573
+ // console.log(`\n🔔 [定时提醒] ${responseText}\n`)
574
+ // }
575
575
 
576
576
  // 发送统一的通知事件
577
577
  this._framework.emit('notification', {
@@ -584,7 +584,7 @@ class SchedulerPlugin extends Plugin {
584
584
  })
585
585
  } else {
586
586
  // 直接显示模式:只显示提醒,不发 LLM
587
- console.log(`\n🔔 [定时提醒] ${task.message}\n`)
587
+ //console.log(`\n🔔 [定时提醒] ${task.message}\n`)
588
588
 
589
589
  // 发送统一的通知事件
590
590
  this._framework.emit('notification', {
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: workflow-guide
3
3
  description: 工作流与多步任务指南。当用户说"创建工作流"、"多步任务"、"自动化流程"时立即调用。
4
- allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWorkflows
4
+ allowed-tools: execute_workflow,reloadWorkflows
5
5
  ---
6
6
 
7
7
  # 工作流(Workflow)开发指南
8
8
 
9
9
  ## 概述
10
10
 
11
- 工作流引擎用于处理多步骤任务,通过 `execute_workflow` 工具执行。工作流由多个步骤组成,支持脚本、条件分支、循环、延时等类型。
11
+ 工作流引擎用于处理多步骤任务,通过 `execute_workflow` 工具执行。工作流由多个步骤组成,支持脚本、条件分支、循环、延时、工具调用等类型。
12
12
 
13
13
  ## 工作流存放位置
14
14
 
@@ -46,7 +46,7 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
46
46
  {
47
47
  "tool": "execute_workflow",
48
48
  "args": {
49
- "workflow": "<工作流 JSON 字符串>",
49
+ "workflow": "my-workflow", // 工作流名称(自动从 .agent/workflows/ 加载)
50
50
  "input": { "变量名": "值" }
51
51
  }
52
52
  }
@@ -62,26 +62,83 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
62
62
  }
63
63
  ```
64
64
 
65
+ ## 重要注意事项
66
+
67
+ 1. **script 必须用 return 返回值**:`script` 是函数体,不是表达式
68
+ 2. **JSON 中不能使用多行字符串**:所有 script 内容写成单行,用分号分隔
69
+ 3. **JSON 中不能有注释**:注释会导致 JSON 解析失败
70
+
65
71
  ## 工作流步骤类型
66
72
 
67
73
  ### 1. script - 脚本步骤
68
74
 
69
- 执行 JavaScript 脚本:
75
+ 执行 JavaScript 脚本,**必须用 return 返回值**:
70
76
 
71
77
  ```json
72
78
  {
73
79
  "type": "script",
74
80
  "name": "步骤名称",
75
81
  "outputVariable": "result",
76
- "script": "context.variables.x = 10; return context.variables.x;"
82
+ "script": "var x=10; context.variables.count=x+1; return context.variables.count;"
77
83
  }
78
84
  ```
79
85
 
80
- - `outputVariable`: 可选,将返回值存入上下文变量
86
+ **正确写法**:
87
+ ```json
88
+ { "script": "return context.variables.value + 1;" }
89
+ ```
90
+
91
+ **错误写法**(缺少 return):
92
+ ```json
93
+ { "script": "context.variables.value + 1;" }
94
+ ```
95
+
96
+ - `outputVariable`: 可选,将返回值存入 `context.variables`
81
97
  - `context.variables`: 所有步骤共享的变量对象
82
- - `context.stepResults`: 上一步的输出结果
98
+ - `context.lastResult`: 上一步的输出结果
83
99
 
84
- ### 2. loop - 循环步骤
100
+ ### 2. tool - 工具调用步骤
101
+
102
+ 在工作流中调用框架注册的工具:
103
+
104
+ ```json
105
+ {
106
+ "type": "tool",
107
+ "name": "发送通知",
108
+ "tool": "notification_send",
109
+ "args": {
110
+ "title": "标题",
111
+ "message": "内容"
112
+ },
113
+ "outputVariable": "result"
114
+ }
115
+ ```
116
+
117
+ **参数说明**:
118
+
119
+ | 字段 | 类型 | 必填 | 说明 |
120
+ |------|------|------|------|
121
+ | `tool` | string | 是 | 工具名称 |
122
+ | `args` | object | 否 | 工具参数 |
123
+ | `outputVariable` | string | 否 | 结果保存到的变量名 |
124
+
125
+ **args 中支持变量引用**:
126
+
127
+ 使用 `{{variableName}}` 语法引用 context.variables 中的变量:
128
+
129
+ ```json
130
+ {
131
+ "type": "tool",
132
+ "name": "发送通知",
133
+ "tool": "notification_send",
134
+ "args": {
135
+ "title": "IP 信息",
136
+ "message": "当前公网IP: {{currentIp}}"
137
+ }
138
+ }
139
+ ```
140
+
141
+ ### 3. loop - 循环步骤
85
142
 
86
143
  重复执行一组步骤:
87
144
 
@@ -100,7 +157,7 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
100
157
  - `maxIterations`: 最大迭代次数
101
158
  - `loopVariable`: 循环计数器变量名(从 0 开始)
102
159
 
103
- ### 3. condition - 条件分支
160
+ ### 4. condition - 条件分支
104
161
 
105
162
  根据条件选择执行分支:
106
163
 
@@ -123,7 +180,7 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
123
180
  }
124
181
  ```
125
182
 
126
- ### 4. delay - 延时步骤
183
+ ### 5. delay - 延时步骤
127
184
 
128
185
  等待指定毫秒数:
129
186
 
@@ -135,85 +192,63 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
135
192
  }
136
193
  ```
137
194
 
138
- ### 5. parallel - 并行步骤(可选)
195
+ ### 6. sequential - 顺序步骤
139
196
 
140
- 并行执行多个步骤:
197
+ 将多个步骤组合为顺序执行(可嵌套使用):
141
198
 
142
199
  ```json
143
200
  {
144
- "type": "parallel",
145
- "name": "并行执行",
146
- "steps": [...]
201
+ "type": "sequential",
202
+ "name": "顺序执行",
203
+ "steps": [
204
+ { "type": "script", "name": "步骤1", "script": "return 1;" },
205
+ { "type": "script", "name": "步骤2", "script": "return 2;" }
206
+ ]
147
207
  }
148
208
  ```
149
209
 
210
+ ## sessionId 传递
211
+
212
+ 工作流执行时会自动获取当前 sessionId,所有 tool 调用都会使用该 sessionId,确保通知发送到当前会话。
213
+
150
214
  ## 完整示例
151
215
 
152
- ### 示例:用户问候与天气查询工作流
216
+ ### 示例:获取IP并发送通知
153
217
 
154
218
  ```json
155
219
  {
156
- "name": "greet-and-query-weather",
157
- "description": "问候用户并查询天气",
220
+ "name": "get-ip-notify",
221
+ "description": "获取本机IP并发送通知",
158
222
  "steps": [
159
223
  {
160
- "type": "script",
161
- "name": "获取用户名",
162
- "outputVariable": "username",
163
- "script": "context.variables.username = context.input.username || '用户'; return context.variables.username;"
224
+ "type": "tool",
225
+ "name": "获取IP信息",
226
+ "tool": "fetch",
227
+ "args": {
228
+ "url": "https://api.ipify.org?format=json",
229
+ "proxy": true
230
+ },
231
+ "outputVariable": "ipResult"
164
232
  },
165
233
  {
166
234
  "type": "script",
167
- "name": "生成问候语",
168
- "outputVariable": "greeting",
169
- "script": "return '你好,' + context.variables.username + '';"
235
+ "name": "提取IP",
236
+ "outputVariable": "currentIp",
237
+ "script": "var r=context.variables.ipResult; return (r&&r.success&&r.body)?(typeof r.body==='object'?r.body.ip:r.body.trim()):'获取失败';"
170
238
  },
171
239
  {
172
- "type": "condition",
173
- "name": "检查是否查询天气",
174
- "branches": [
175
- {
176
- "name": "需要查询天气",
177
- "condition": "context.input.queryWeather === true",
178
- "steps": [
179
- {
180
- "type": "script",
181
- "name": "模拟天气查询",
182
- "outputVariable": "weather",
183
- "script": "return '今天天气晴,温度 25°C';"
184
- }
185
- ]
186
- },
187
- {
188
- "name": "不需要查询天气",
189
- "condition": "true",
190
- "steps": [
191
- {
192
- "type": "script",
193
- "name": "跳过天气",
194
- "outputVariable": "weather",
195
- "script": "return '好的,有需要随时叫我!';"
196
- }
197
- ]
198
- }
199
- ]
240
+ "type": "tool",
241
+ "name": "发送通知",
242
+ "tool": "notification_send",
243
+ "args": {
244
+ "title": "IP 信息",
245
+ "message": "当前公网IP: {{currentIp}}"
246
+ }
200
247
  }
201
248
  ]
202
249
  }
203
250
  ```
204
251
 
205
- 调用:
206
-
207
- ```json
208
- {
209
- "tool": "execute_workflow",
210
- "args": {
211
- "workflow": "<上面 JSON 转字符串>",
212
- "input": { "username": "张三", "queryWeather": true }
213
- }
214
- }
215
- ```
216
-
217
252
  ### 示例:循环处理任务列表
218
253
 
219
254
  ```json
@@ -225,7 +260,7 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
225
260
  "type": "script",
226
261
  "name": "初始化列表",
227
262
  "outputVariable": "items",
228
- "script": "context.variables.items = ['任务A', '任务B', '任务C']; return context.variables.items;"
263
+ "script": "context.variables.items=['任务A','任务B','任务C']; return context.variables.items;"
229
264
  },
230
265
  {
231
266
  "type": "loop",
@@ -237,7 +272,7 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
237
272
  "type": "script",
238
273
  "name": "处理任务",
239
274
  "outputVariable": "processed",
240
- "script": "const item = context.variables.items[context.variables.i]; context.variables.processed = (context.variables.processed || []); context.variables.processed.push(item + '_完成'); return item;"
275
+ "script": "var item=context.variables.items[context.variables.i]; context.variables.processed=(context.variables.processed||[]); context.variables.processed.push(item+'_完成'); return item;"
241
276
  }
242
277
  ]
243
278
  }
@@ -247,12 +282,13 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
247
282
 
248
283
  ## 注意事项
249
284
 
250
- 1. **context.variables** 在所有步骤间共享,可存储中间结果
251
- 2. **context.input** 是工作流的输入参数
252
- 3. **context.stepResults** 包含前面各步骤的输出
253
- 4. 脚本中使用 `return` 值会传递给下一步
254
- 5. 循环变量 `i` 从 0 开始计数
255
- 6. 条件分支的 `condition` JavaScript 表达式字符串
285
+ 1. **script 必须 return**:`script` 是函数体,必须用 return 返回值
286
+ 2. **JSON 中 script 单行写**:用分号分隔多个语句,不要换行
287
+ 3. **JSON 中不能有注释**:注释会导致 JSON 解析失败
288
+ 4. **context.variables** 在所有步骤间共享,可存储中间结果
289
+ 5. **context.input** 是工作流的输入参数
290
+ 6. 循环变量 `i` 0 开始计数
291
+ 7. 条件分支的 `condition` 是 JavaScript 表达式字符串
256
292
 
257
293
  ## 最佳实践
258
294
 
@@ -261,3 +297,33 @@ allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWork
261
297
  3. 循环前确保有合理的 `maxIterations` 限制
262
298
  4. 条件分支要有兜底的 `condition: "true"` 分支
263
299
  5. 复杂的业务逻辑优先使用脚本步骤
300
+ 6. **script 必须 return**:`script` 是函数体,必须用 return 返回值
301
+ 7. **JSON 中 script 单行写**:用分号分隔多个语句,不要换行
302
+
303
+ **错误示例**:
304
+ ```json
305
+ // ❌ 错误 - script 没有 return
306
+ { "type": "script", "script": "context.variables.count + 1;" }
307
+
308
+ // ❌ 错误 - JSON 多行字符串
309
+ {
310
+ "script": "
311
+ const a = 10;
312
+ return a + 1;
313
+ "
314
+ }
315
+
316
+ // ❌ 错误 - JSON 中有注释
317
+ {
318
+ "script": "return 1; // 这是注释"
319
+ }
320
+ ```
321
+
322
+ **正确示例**:
323
+ ```json
324
+ // ✅ 正确
325
+ { "type": "script", "outputVariable": "count", "script": "return (context.variables.count||0) + 1;" }
326
+
327
+ // ✅ 正确 - 复杂逻辑拆成多步
328
+ { "type": "script", "script": "context.variables.a=10; context.variables.b=20; return context.variables.a+context.variables.b;" }
329
+ ```