foliko 1.0.64 → 1.0.66
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 +145 -142
- package/cli/src/commands/chat.js +4 -1
- package/package.json +1 -1
- package/plugins/ai-plugin.js +2 -1
- package/plugins/ambient-agent-plugin.js +160 -48
- package/plugins/email.js +170 -18
- package/plugins/file-system-plugin.js +2 -2
- package/skills/ambient-agent/SKILL.md +234 -0
- package/skills/workflow-guide/SKILL.md +139 -73
- package/src/capabilities/workflow-engine.js +435 -127
- package/src/core/agent-chat.js +26 -4
- package/src/core/agent.js +1 -1
- package/src/core/framework.js +3 -3
package/plugins/email.js
CHANGED
|
@@ -44,19 +44,19 @@ class EmailPlugin extends Plugin {
|
|
|
44
44
|
return this
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
|
@@ -201,6 +201,158 @@ class EmailPlugin extends Plugin {
|
|
|
201
201
|
return this._handleEmailWatch(args)
|
|
202
202
|
}
|
|
203
203
|
})
|
|
204
|
+
|
|
205
|
+
// 自动回复邮件
|
|
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
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 处理自动回复
|
|
224
|
+
*/
|
|
225
|
+
async _handleAutoReply(args) {
|
|
226
|
+
let { to, subject, body, from, _event, prompt } = args
|
|
227
|
+
|
|
228
|
+
// 如果没有直接参数,尝试从 _event 提取
|
|
229
|
+
// _event 格式: { type: 'email:received', email: {...}, timestamp: ... }
|
|
230
|
+
if (!to && !subject && !body && _event) {
|
|
231
|
+
const email = _event.email || _event.data?.email || {}
|
|
232
|
+
from = email.from?.text || email.from || ''
|
|
233
|
+
to = email.to?.text || email.to || ''
|
|
234
|
+
subject = email.subject || ''
|
|
235
|
+
body = email.text || email.body || ''
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 检查必要参数
|
|
239
|
+
if (!from && !to) {
|
|
240
|
+
return { success: false, error: '缺少收件人地址' }
|
|
241
|
+
}
|
|
242
|
+
if (!body) {
|
|
243
|
+
return { success: false, error: '缺少邮件内容' }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
// 获取活跃的 Agent
|
|
248
|
+
const agent = this._getActiveAgent()
|
|
249
|
+
if (!agent) {
|
|
250
|
+
return { success: false, error: 'No active agent found' }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 构建提示让 LLM 生成回复
|
|
254
|
+
const finalPrompt = prompt || `你是一封邮件自动回复助手。请根据以下邮件内容,生成一封专业的回复邮件。
|
|
255
|
+
|
|
256
|
+
【原始邮件】
|
|
257
|
+
发件人: ${from || to}
|
|
258
|
+
主题: ${subject}
|
|
259
|
+
内容:
|
|
260
|
+
${body}
|
|
261
|
+
|
|
262
|
+
【要求】
|
|
263
|
+
1. 回复内容要针对邮件中的问题或内容进行回复
|
|
264
|
+
2. 语言要专业、礼貌、简洁
|
|
265
|
+
3. 只输出邮件正文内容,不要额外解释
|
|
266
|
+
4. 回复语言应与原邮件一致(如果原邮件是中文,则用中文回复)`
|
|
267
|
+
|
|
268
|
+
// 等待 Agent 生成回复(带超时保护)
|
|
269
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
270
|
+
setTimeout(() => reject(new Error('AI回复生成超时(30秒)')), 30000)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
const replyPromise = agent.pushMessage(finalPrompt, { maxSteps: 3 })
|
|
274
|
+
const replyResult = await Promise.race([replyPromise, timeoutPromise])
|
|
275
|
+
|
|
276
|
+
// 提取回复内容
|
|
277
|
+
let replyContent = ''
|
|
278
|
+
if (typeof replyResult === 'string') {
|
|
279
|
+
replyContent = replyResult.trim()
|
|
280
|
+
} else if (replyResult && replyResult.content) {
|
|
281
|
+
replyContent = replyResult.content.trim()
|
|
282
|
+
} else if (replyResult && replyResult.message) {
|
|
283
|
+
replyContent = replyResult.message.trim()
|
|
284
|
+
} else {
|
|
285
|
+
replyContent = JSON.stringify(replyResult).trim()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 去掉思考过程标签
|
|
289
|
+
replyContent = replyContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
|
|
290
|
+
|
|
291
|
+
if (!replyContent || replyContent.length < 5) {
|
|
292
|
+
return { success: false, error: 'AI未能生成有效的回复内容' }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 发送回复邮件
|
|
296
|
+
const sendResult = await this._sendEmail({
|
|
297
|
+
to: from || to,
|
|
298
|
+
subject: `Re: ${subject}`,
|
|
299
|
+
body: replyContent
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
if (sendResult.success) {
|
|
303
|
+
return {
|
|
304
|
+
success: true,
|
|
305
|
+
message: `自动回复已发送至 ${from || to}`,
|
|
306
|
+
replyContent
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
return { success: false, error: sendResult.error || '发送失败' }
|
|
310
|
+
}
|
|
311
|
+
} catch (err) {
|
|
312
|
+
return { success: false, error: err.message }
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 获取活跃的 Agent
|
|
318
|
+
* 优先返回空闲的 agent,避免与主 agent 冲突
|
|
319
|
+
*/
|
|
320
|
+
_getActiveAgent() {
|
|
321
|
+
const mainAgent = this._framework._mainAgent
|
|
322
|
+
if (mainAgent) {
|
|
323
|
+
// 检查主 agent 是否空闲
|
|
324
|
+
if (mainAgent.getStatus && mainAgent.getStatus() === 'idle') {
|
|
325
|
+
console.log('[Email] Using main agent (idle)')
|
|
326
|
+
return mainAgent
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 检查 subagents 是否空闲
|
|
330
|
+
if (mainAgent._subAgents && mainAgent._subAgents.size > 0) {
|
|
331
|
+
for (const [name, entry] of mainAgent._subAgents) {
|
|
332
|
+
const subAgent = entry.agent || entry
|
|
333
|
+
const status = subAgent.getStatus ? subAgent.getStatus() : 'unknown'
|
|
334
|
+
console.log(`[Email] Found subagent ${name}, status: ${status}`)
|
|
335
|
+
if (status === 'idle') {
|
|
336
|
+
console.log(`[Email] Using subagent ${name} for AI operation`)
|
|
337
|
+
return subAgent
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
console.log('[Email] No subagents found on main agent')
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 主 agent busy,但需要返回一个来避免错误
|
|
345
|
+
console.log('[Email] Main agent busy, returning main agent anyway')
|
|
346
|
+
return mainAgent
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const agents = this._framework._agents || []
|
|
350
|
+
if (agents.length > 0) {
|
|
351
|
+
console.log('[Email] No main agent, using first available agent')
|
|
352
|
+
return agents[0]
|
|
353
|
+
}
|
|
354
|
+
console.log('[Email] No agents available')
|
|
355
|
+
return null
|
|
204
356
|
}
|
|
205
357
|
|
|
206
358
|
/**
|
|
@@ -209,10 +361,10 @@ class EmailPlugin extends Plugin {
|
|
|
209
361
|
async _handleEmailWatch(args) {
|
|
210
362
|
args={
|
|
211
363
|
...args,
|
|
212
|
-
host:
|
|
213
|
-
port:
|
|
214
|
-
user:
|
|
215
|
-
password:
|
|
364
|
+
host: process.env.IMAP_HOST,
|
|
365
|
+
port: parseInt(process.env.IMAP_PORT) || 993,
|
|
366
|
+
user: process.env.IMAP_USER,
|
|
367
|
+
password: process.env.IMAP_PASS
|
|
216
368
|
}
|
|
217
369
|
|
|
218
370
|
const { action, interval, host, port, user, password, box } = args
|
|
@@ -407,7 +559,7 @@ class EmailPlugin extends Plugin {
|
|
|
407
559
|
// 发送事件通知
|
|
408
560
|
this._emitEmailReceived(email)
|
|
409
561
|
|
|
410
|
-
|
|
562
|
+
console.log(`[Email] New email received: ${email.subject}`)
|
|
411
563
|
cleanup()
|
|
412
564
|
resolve({ success: true, newEmails: newEmails.length, email })
|
|
413
565
|
} else {
|
|
@@ -401,10 +401,10 @@ class FileSystemPlugin extends Plugin {
|
|
|
401
401
|
inputSchema: z.object({
|
|
402
402
|
title: z.string().describe('通知标题'),
|
|
403
403
|
message: z.string().describe('通知内容'),
|
|
404
|
-
source: z.string().optional().describe('通知来源标识,默认
|
|
404
|
+
source: z.string().optional().describe('通知来源标识,默认 系统消息')
|
|
405
405
|
}),
|
|
406
406
|
execute: async (args, framework) => {
|
|
407
|
-
const { title, message, source = '
|
|
407
|
+
const { title, message, source = '系统消息' } = args
|
|
408
408
|
try {
|
|
409
409
|
// 获取当前执行上下文中的 sessionId,只发送到当前会话
|
|
410
410
|
const ctx = framework.getExecutionContext()
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ambient-agent
|
|
3
|
+
description: Ambient Agent - 持续后台运行的智能代理,用于主动监控事件和执行操作
|
|
4
|
+
allowed-tools: ambient_goals,ambient_status,ambient_think,ambient_remember,ambient_control
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Ambient Agent
|
|
8
|
+
|
|
9
|
+
Ambient Agent 是一个持续后台运行的智能代理,监控事件并主动执行操作。
|
|
10
|
+
|
|
11
|
+
## 核心概念
|
|
12
|
+
|
|
13
|
+
### Goal(目标)
|
|
14
|
+
目标是对话式 LLM 派发的任务,包含:
|
|
15
|
+
- `title` - 目标标题
|
|
16
|
+
- `description` - 目标描述
|
|
17
|
+
- `priority` - 优先级 1-10
|
|
18
|
+
- `actions` - 要执行的操作列表
|
|
19
|
+
- `conditions` - 激活条件(如监听特定事件)
|
|
20
|
+
- `state` - 状态:`pending` | `active` | `completed` | `failed`
|
|
21
|
+
|
|
22
|
+
### Action(操作)
|
|
23
|
+
操作是目标的最小执行单元,有三种类型:
|
|
24
|
+
|
|
25
|
+
| 类型 | 说明 | 参数 |
|
|
26
|
+
|------|------|------|
|
|
27
|
+
| `tool` | 调用工具 | `name` 工具名, `args` 参数 |
|
|
28
|
+
| `message` | 发送消息 | `content` 消息内容 |
|
|
29
|
+
| `think` | 触发思考 | `topic` 主题, `mode` 模式, `depth` 深度 |
|
|
30
|
+
|
|
31
|
+
### Event(事件)
|
|
32
|
+
目标可以监听框架事件:
|
|
33
|
+
- `tool:result` - 工具执行结果
|
|
34
|
+
- `scheduler:reminder` - 定时提醒
|
|
35
|
+
- `agent:message` - 代理消息
|
|
36
|
+
- `email:received` - 收到邮件
|
|
37
|
+
- `webhook:received` - Webhook 接收
|
|
38
|
+
|
|
39
|
+
## 工具
|
|
40
|
+
|
|
41
|
+
### ambient_goals - 目标管理
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// 列出所有目标
|
|
45
|
+
await ambient_goals({ action: 'list' })
|
|
46
|
+
|
|
47
|
+
// 创建目标
|
|
48
|
+
await ambient_goals({
|
|
49
|
+
action: 'create',
|
|
50
|
+
title: '监控邮件',
|
|
51
|
+
description: '定期检查新邮件',
|
|
52
|
+
priority: 8,
|
|
53
|
+
actions: [
|
|
54
|
+
{ id: 'check-email', type: 'tool', name: 'email_list', args: { box: 'INBOX' } }
|
|
55
|
+
],
|
|
56
|
+
conditions: {
|
|
57
|
+
events: ['email:received']
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// 更新目标
|
|
62
|
+
await ambient_goals({
|
|
63
|
+
action: 'update',
|
|
64
|
+
goalId: 'xxx-xxx',
|
|
65
|
+
title: '新标题',
|
|
66
|
+
actions: [...] // 新操作列表
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// 删除目标
|
|
70
|
+
await ambient_goals({ action: 'delete', goalId: 'xxx-xxx' })
|
|
71
|
+
|
|
72
|
+
// 激活目标
|
|
73
|
+
await ambient_goals({ action: 'activate', goalId: 'xxx-xxx' })
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### ambient_status - 获取状态
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
await ambient_status({})
|
|
80
|
+
// 返回: { running, tickCount, activeGoals, pendingGoals, recentActivities }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### ambient_think - 触发思考
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
await ambient_think({
|
|
87
|
+
goalId: 'xxx-xxx', // 可选,关联目标
|
|
88
|
+
mode: 'reflect', // reflect | brainstorm | plan | analyze
|
|
89
|
+
topic: '反思主题', // 可选
|
|
90
|
+
depth: 2 // 可选,1-5
|
|
91
|
+
})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### ambient_remember - 记忆管理
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// 存储记忆
|
|
98
|
+
await ambient_remember({
|
|
99
|
+
action: 'store',
|
|
100
|
+
key: 'user-preference',
|
|
101
|
+
content: '用户喜欢在早上收到摘要'
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// 检索最近记忆
|
|
105
|
+
await ambient_remember({
|
|
106
|
+
action: 'retrieve',
|
|
107
|
+
limit: 10
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// 搜索记忆
|
|
111
|
+
await ambient_remember({
|
|
112
|
+
action: 'search',
|
|
113
|
+
query: '邮件'
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### ambient_control - 控制循环
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
// 暂停
|
|
121
|
+
await ambient_control({ action: 'pause' })
|
|
122
|
+
|
|
123
|
+
// 恢复
|
|
124
|
+
await ambient_control({ action: 'resume' })
|
|
125
|
+
|
|
126
|
+
// 调整参数
|
|
127
|
+
await ambient_control({
|
|
128
|
+
action: 'adjust',
|
|
129
|
+
tickInterval: 10000, // 新 tick 间隔(毫秒)
|
|
130
|
+
cooldownPeriod: 5000 // 新冷却时间(毫秒)
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## action 与 step 的关系
|
|
135
|
+
|
|
136
|
+
Ambient Agent 的 action 与 Workflow Engine 的 step 共用 `StepExecutor` 执行:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
action.type → step.type
|
|
140
|
+
───────────────────────
|
|
141
|
+
tool → tool
|
|
142
|
+
message → message
|
|
143
|
+
think → think
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
这意味着:
|
|
147
|
+
- 变量引用语法一致:`{{variableName}}`
|
|
148
|
+
- sessionId 传递机制一致
|
|
149
|
+
- 错误处理方式一致
|
|
150
|
+
|
|
151
|
+
## action 执行上下文
|
|
152
|
+
|
|
153
|
+
当 action 执行时,可用的上下文:
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
// context.variables 中可用
|
|
157
|
+
context.variables._event // 触发的事件数据(如果有)
|
|
158
|
+
context.variables._action // 当前执行的 action 对象
|
|
159
|
+
context.variables.loopIndex // 循环索引(如果被 loop 执行)
|
|
160
|
+
|
|
161
|
+
// context.input 中可用
|
|
162
|
+
context.input // 工作流/目标输入参数
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 创建目标的最佳实践
|
|
166
|
+
|
|
167
|
+
1. **明确目标标题** - 让用户清楚知道目标意图
|
|
168
|
+
2. **设置合适优先级** - 高优先级目标会被优先处理
|
|
169
|
+
3. **操作要原子化** - 每个 action 只做一件事
|
|
170
|
+
4. **设置冷却时间** - 避免同一目标过度频繁执行
|
|
171
|
+
5. **检查循环** - 同一 action 连续失败 3 次会判定为循环
|
|
172
|
+
|
|
173
|
+
## 示例:创建邮件自动回复目标
|
|
174
|
+
|
|
175
|
+
**重要**:`email:received` 事件的 `email` 对象包含:
|
|
176
|
+
- `email.from` - 发件人地址
|
|
177
|
+
- `email.to` - 收件人地址
|
|
178
|
+
- `email.subject` - 邮件主题
|
|
179
|
+
- `email.body` - 邮件正文
|
|
180
|
+
|
|
181
|
+
使用 `{{}}` 语法从事件中提取参数:
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
await ambient_goals({
|
|
185
|
+
action: 'create',
|
|
186
|
+
title: '邮件自动回复',
|
|
187
|
+
description: '收到邮件后自动回复发件人',
|
|
188
|
+
priority: 8,
|
|
189
|
+
actions: [
|
|
190
|
+
{
|
|
191
|
+
id: 'auto-reply',
|
|
192
|
+
type: 'tool',
|
|
193
|
+
name: 'email_auto_reply',
|
|
194
|
+
args: {
|
|
195
|
+
to: '{{_event.email.from}}',
|
|
196
|
+
subject: '{{_event.email.subject}}',
|
|
197
|
+
body: '{{_event.email.body}}'
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
conditions: {
|
|
202
|
+
events: ['email:received']
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**错误示例**(缺少参数):
|
|
208
|
+
```javascript
|
|
209
|
+
// ❌ 错误 - 没有传递必需参数
|
|
210
|
+
{ type: 'tool', name: 'email_auto_reply', args: {} }
|
|
211
|
+
|
|
212
|
+
// ✅ 正确 - 从事件中提取参数
|
|
213
|
+
{ type: 'tool', name: 'email_auto_reply', args: {
|
|
214
|
+
to: '{{_event.email.from}}',
|
|
215
|
+
subject: '{{_event.email.subject}}',
|
|
216
|
+
body: '{{_event.email.body}}'
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**注意**:`email_auto_reply` 需要 `to`、`subject`、`body` 三个必需参数,必须从 `{{_event.email.xxx}}` 提取。
|
|
222
|
+
|
|
223
|
+
## 生命周期
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
Goal 创建 → [条件满足] → ACTIVE → 执行 actions → COMPLETED/FAILED
|
|
227
|
+
↑ ↓
|
|
228
|
+
└────── 重新激活 ← ─────────────┘
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- **pending** - 待激活,满足条件后自动变为 active
|
|
232
|
+
- **active** - 执行中,循环处理 actions
|
|
233
|
+
- **completed** - 所有 actions 执行完毕
|
|
234
|
+
- **failed** - 达到最大尝试次数或检测到循环
|