foliko 1.0.64 → 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/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 +2 -2
- package/plugins/email.js +125 -5
- package/plugins/file-system-plugin.js +2 -2
- package/skills/workflow-guide/SKILL.md +139 -73
- package/src/capabilities/workflow-engine.js +137 -11
- package/src/core/agent.js +1 -1
- package/src/core/framework.js +3 -3
package/cli/src/commands/chat.js
CHANGED
package/package.json
CHANGED
package/plugins/ai-plugin.js
CHANGED
|
@@ -20,7 +20,8 @@ class AIPlugin extends Plugin {
|
|
|
20
20
|
model: config.model || 'deepseek-chat',
|
|
21
21
|
apiKey: config.apiKey,
|
|
22
22
|
baseURL: config.baseURL,
|
|
23
|
-
maxSteps: config.maxSteps || 20
|
|
23
|
+
maxSteps: config.maxSteps || 20,
|
|
24
|
+
maxOutputTokens:config.maxOutputTokens||8192
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
this._aiClient = null
|
|
@@ -428,7 +428,7 @@ class ExplorerLoop {
|
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
this._tickCount++
|
|
431
|
-
|
|
431
|
+
|
|
432
432
|
|
|
433
433
|
try {
|
|
434
434
|
await this._processGoals()
|
|
@@ -436,7 +436,7 @@ class ExplorerLoop {
|
|
|
436
436
|
console.error('[Ambient] Tick错误:', err.message)
|
|
437
437
|
this._addActivity('error', { message: err.message })
|
|
438
438
|
}
|
|
439
|
-
|
|
439
|
+
this._lastActionTime = Date.now()
|
|
440
440
|
// 调度下一次tick
|
|
441
441
|
this._scheduleNext()
|
|
442
442
|
}
|
package/plugins/email.js
CHANGED
|
@@ -201,6 +201,126 @@ 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
|
+
if (!to && !subject && !body && _event) {
|
|
230
|
+
const email = _event.data?.email || _event.email || {}
|
|
231
|
+
from = email.from?.text || email.from || ''
|
|
232
|
+
to = email.to?.text || email.to || ''
|
|
233
|
+
subject = email.subject || ''
|
|
234
|
+
body = email.text || email.body || ''
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 检查必要参数
|
|
238
|
+
if (!from && !to) {
|
|
239
|
+
return { success: false, error: '缺少收件人地址' }
|
|
240
|
+
}
|
|
241
|
+
if (!body) {
|
|
242
|
+
return { success: false, error: '缺少邮件内容' }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// 获取活跃的 Agent
|
|
247
|
+
const agent = this._getActiveAgent()
|
|
248
|
+
if (!agent) {
|
|
249
|
+
return { success: false, error: 'No active agent found' }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 构建提示让 LLM 生成回复
|
|
253
|
+
const finalPrompt = prompt || `你是一封邮件自动回复助手。请根据以下邮件内容,生成一封专业的回复邮件。
|
|
254
|
+
|
|
255
|
+
【原始邮件】
|
|
256
|
+
发件人: ${from || to}
|
|
257
|
+
主题: ${subject}
|
|
258
|
+
内容:
|
|
259
|
+
${body}
|
|
260
|
+
|
|
261
|
+
【要求】
|
|
262
|
+
1. 回复内容要针对邮件中的问题或内容进行回复
|
|
263
|
+
2. 语言要专业、礼貌、简洁
|
|
264
|
+
3. 只输出邮件正文内容,不要额外解释
|
|
265
|
+
4. 回复语言应与原邮件一致(如果原邮件是中文,则用中文回复)`
|
|
266
|
+
|
|
267
|
+
// 等待 Agent 生成回复(带超时保护)
|
|
268
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
269
|
+
setTimeout(() => reject(new Error('AI回复生成超时(30秒)')), 30000)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
const replyPromise = agent.pushMessage(finalPrompt, { maxSteps: 3 })
|
|
273
|
+
const replyResult = await Promise.race([replyPromise, timeoutPromise])
|
|
274
|
+
|
|
275
|
+
// 提取回复内容
|
|
276
|
+
let replyContent = ''
|
|
277
|
+
if (typeof replyResult === 'string') {
|
|
278
|
+
replyContent = replyResult.trim()
|
|
279
|
+
} else if (replyResult && replyResult.content) {
|
|
280
|
+
replyContent = replyResult.content.trim()
|
|
281
|
+
} else if (replyResult && replyResult.message) {
|
|
282
|
+
replyContent = replyResult.message.trim()
|
|
283
|
+
} else {
|
|
284
|
+
replyContent = JSON.stringify(replyResult).trim()
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 去掉思考过程标签
|
|
288
|
+
replyContent = replyContent.replace(/<think>[\s\S]*?<\/think>/g, '').trim()
|
|
289
|
+
|
|
290
|
+
if (!replyContent || replyContent.length < 5) {
|
|
291
|
+
return { success: false, error: 'AI未能生成有效的回复内容' }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 发送回复邮件
|
|
295
|
+
const sendResult = await this._sendEmail({
|
|
296
|
+
to: from || to,
|
|
297
|
+
subject: `Re: ${subject}`,
|
|
298
|
+
body: replyContent
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
if (sendResult.success) {
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
message: `自动回复已发送至 ${from || to}`,
|
|
305
|
+
replyContent
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
return { success: false, error: sendResult.error || '发送失败' }
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
return { success: false, error: err.message }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 获取活跃的 Agent
|
|
317
|
+
*/
|
|
318
|
+
_getActiveAgent() {
|
|
319
|
+
if (this._framework._mainAgent) {
|
|
320
|
+
return this._framework._mainAgent
|
|
321
|
+
}
|
|
322
|
+
const agents = this._framework._agents || []
|
|
323
|
+
return agents.length > 0 ? agents[agents.length - 1] : null
|
|
204
324
|
}
|
|
205
325
|
|
|
206
326
|
/**
|
|
@@ -209,10 +329,10 @@ class EmailPlugin extends Plugin {
|
|
|
209
329
|
async _handleEmailWatch(args) {
|
|
210
330
|
args={
|
|
211
331
|
...args,
|
|
212
|
-
host:
|
|
213
|
-
port:
|
|
214
|
-
user:
|
|
215
|
-
password:
|
|
332
|
+
host: process.env.IMAP_HOST,
|
|
333
|
+
port: parseInt(process.env.IMAP_PORT) || 993,
|
|
334
|
+
user: process.env.IMAP_USER,
|
|
335
|
+
password: process.env.IMAP_PASS
|
|
216
336
|
}
|
|
217
337
|
|
|
218
338
|
const { action, interval, host, port, user, password, box } = args
|
|
@@ -407,7 +527,7 @@ class EmailPlugin extends Plugin {
|
|
|
407
527
|
// 发送事件通知
|
|
408
528
|
this._emitEmailReceived(email)
|
|
409
529
|
|
|
410
|
-
|
|
530
|
+
console.log(`[Email] New email received: ${email.subject}`)
|
|
411
531
|
cleanup()
|
|
412
532
|
resolve({ success: true, newEmails: newEmails.length, email })
|
|
413
533
|
} 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()
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: workflow-guide
|
|
3
3
|
description: 工作流与多步任务指南。当用户说"创建工作流"、"多步任务"、"自动化流程"时立即调用。
|
|
4
|
-
allowed-tools:
|
|
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": "
|
|
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
|
|
82
|
+
"script": "var x=10; context.variables.count=x+1; return context.variables.count;"
|
|
77
83
|
}
|
|
78
84
|
```
|
|
79
85
|
|
|
80
|
-
|
|
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.
|
|
98
|
+
- `context.lastResult`: 上一步的输出结果
|
|
83
99
|
|
|
84
|
-
### 2.
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
195
|
+
### 6. sequential - 顺序步骤
|
|
139
196
|
|
|
140
|
-
|
|
197
|
+
将多个步骤组合为顺序执行(可嵌套使用):
|
|
141
198
|
|
|
142
199
|
```json
|
|
143
200
|
{
|
|
144
|
-
"type": "
|
|
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": "
|
|
157
|
-
"description": "
|
|
220
|
+
"name": "get-ip-notify",
|
|
221
|
+
"description": "获取本机IP并发送通知",
|
|
158
222
|
"steps": [
|
|
159
223
|
{
|
|
160
|
-
"type": "
|
|
161
|
-
"name": "
|
|
162
|
-
"
|
|
163
|
-
"
|
|
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": "
|
|
169
|
-
"script": "
|
|
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": "
|
|
173
|
-
"name": "
|
|
174
|
-
"
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
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": "
|
|
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. **
|
|
251
|
-
2. **
|
|
252
|
-
3. **
|
|
253
|
-
4.
|
|
254
|
-
5.
|
|
255
|
-
6.
|
|
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
|
+
```
|
|
@@ -20,6 +20,7 @@ const StepType = {
|
|
|
20
20
|
SEQUENTIAL: 'sequential',
|
|
21
21
|
LOOP: 'loop',
|
|
22
22
|
DELAY: 'delay',
|
|
23
|
+
TOOL: 'tool',
|
|
23
24
|
INPUT: 'input',
|
|
24
25
|
OUTPUT: 'output'
|
|
25
26
|
}
|
|
@@ -244,6 +245,112 @@ class DelayStep extends WorkflowStep {
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
/**
|
|
249
|
+
* 工具步骤
|
|
250
|
+
*/
|
|
251
|
+
class ToolStep extends WorkflowStep {
|
|
252
|
+
constructor(config) {
|
|
253
|
+
super({ ...config, type: StepType.TOOL })
|
|
254
|
+
this.tool = config.tool
|
|
255
|
+
this.args = config.args || {}
|
|
256
|
+
this.outputVariable = config.outputVariable || null
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async execute(context, engine) {
|
|
260
|
+
if (!this.tool) {
|
|
261
|
+
throw new Error('Tool step requires a tool name')
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log(`[Workflow] Executing tool: ${this.tool}`)
|
|
265
|
+
|
|
266
|
+
// 解析工具参数,支持变量引用
|
|
267
|
+
const resolvedArgs = this._resolveArgs(this.args, context)
|
|
268
|
+
|
|
269
|
+
// 获取当前 sessionId(从上下文变量或执行上下文)
|
|
270
|
+
let sessionId = context.variables._sessionId
|
|
271
|
+
if (!sessionId && engine.framework.getExecutionContext) {
|
|
272
|
+
const ctx = engine.framework.getExecutionContext()
|
|
273
|
+
sessionId = ctx?.sessionId
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 使用 sessionId 上下文执行工具
|
|
277
|
+
let result
|
|
278
|
+
if (sessionId && engine.framework.runWithContext) {
|
|
279
|
+
result = await engine.framework.runWithContext(
|
|
280
|
+
{ sessionId },
|
|
281
|
+
async () => await engine.framework.executeTool(this.tool, resolvedArgs)
|
|
282
|
+
)
|
|
283
|
+
} else {
|
|
284
|
+
result = await engine.framework.executeTool(this.tool, resolvedArgs)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 保存结果到变量
|
|
288
|
+
if (this.outputVariable) {
|
|
289
|
+
context.variables[this.outputVariable] = result
|
|
290
|
+
}
|
|
291
|
+
context.lastResult = result
|
|
292
|
+
|
|
293
|
+
// 检查工具执行结果
|
|
294
|
+
if (result && result.error) {
|
|
295
|
+
console.warn(`[Workflow] Tool ${this.tool} returned error: ${result.error}`)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return result
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 解析参数中的变量引用
|
|
303
|
+
* 支持 {{variableName}} 语法
|
|
304
|
+
*/
|
|
305
|
+
_resolveArgs(args, context) {
|
|
306
|
+
const resolved = {}
|
|
307
|
+
for (const [key, value] of Object.entries(args)) {
|
|
308
|
+
resolved[key] = this._resolveValue(value, context)
|
|
309
|
+
}
|
|
310
|
+
return resolved
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
_resolveValue(value, context) {
|
|
314
|
+
if (typeof value === 'string') {
|
|
315
|
+
// 替换 {{variable}} 或 {{variables.name}} 语法
|
|
316
|
+
return value.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
|
|
317
|
+
// 支持 variables.xxx 或直接 xxx 格式
|
|
318
|
+
if (path.startsWith('variables.')) {
|
|
319
|
+
return this._getNestedValue(context, path) ?? match
|
|
320
|
+
} else if (path.startsWith('context.')) {
|
|
321
|
+
return this._getNestedValue(context, path) ?? match
|
|
322
|
+
} else {
|
|
323
|
+
// 尝试从 context.variables 获取
|
|
324
|
+
const val = this._getNestedValue(context.variables, path)
|
|
325
|
+
if (val !== undefined) return val
|
|
326
|
+
// 尝试从 context 直接获取
|
|
327
|
+
return this._getNestedValue(context, path) ?? match
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
} else if (Array.isArray(value)) {
|
|
331
|
+
return value.map(v => this._resolveValue(v, context))
|
|
332
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
333
|
+
const resolved = {}
|
|
334
|
+
for (const [k, v] of Object.entries(value)) {
|
|
335
|
+
resolved[k] = this._resolveValue(v, context)
|
|
336
|
+
}
|
|
337
|
+
return resolved
|
|
338
|
+
}
|
|
339
|
+
return value
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
_getNestedValue(obj, path) {
|
|
343
|
+
if (!obj) return undefined
|
|
344
|
+
const parts = path.split('.')
|
|
345
|
+
let current = obj
|
|
346
|
+
for (const part of parts) {
|
|
347
|
+
if (current === null || current === undefined) return undefined
|
|
348
|
+
current = current[part]
|
|
349
|
+
}
|
|
350
|
+
return current
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
247
354
|
/**
|
|
248
355
|
* 工作流引擎
|
|
249
356
|
*/
|
|
@@ -316,6 +423,7 @@ class WorkflowPlugin extends Plugin {
|
|
|
316
423
|
this._engine.registerStepType(StepType.SEQUENTIAL, SequentialStep)
|
|
317
424
|
this._engine.registerStepType(StepType.LOOP, LoopStep)
|
|
318
425
|
this._engine.registerStepType(StepType.DELAY, DelayStep)
|
|
426
|
+
this._engine.registerStepType(StepType.TOOL, ToolStep)
|
|
319
427
|
|
|
320
428
|
// 注册工作流执行工具
|
|
321
429
|
framework.registerTool({
|
|
@@ -325,8 +433,14 @@ class WorkflowPlugin extends Plugin {
|
|
|
325
433
|
workflow: z.string().describe('工作流定义(JSON 或 JavaScript 代码)'),
|
|
326
434
|
input: z.object({}).optional().describe('工作流输入参数')
|
|
327
435
|
}),
|
|
328
|
-
execute: async (args) => {
|
|
329
|
-
|
|
436
|
+
execute: async (args, framework) => {
|
|
437
|
+
// 获取当前 sessionId
|
|
438
|
+
let sessionId = null
|
|
439
|
+
const ctx = framework.getExecutionContext()
|
|
440
|
+
if (ctx?.sessionId) {
|
|
441
|
+
sessionId = ctx.sessionId
|
|
442
|
+
}
|
|
443
|
+
return await this.executeWorkflow(args.workflow, args.input || {}, sessionId)
|
|
330
444
|
}
|
|
331
445
|
})
|
|
332
446
|
|
|
@@ -455,18 +569,24 @@ class WorkflowPlugin extends Plugin {
|
|
|
455
569
|
/**
|
|
456
570
|
* 执行工作流
|
|
457
571
|
*/
|
|
458
|
-
async executeWorkflow(workflowDef, input = {}) {
|
|
572
|
+
async executeWorkflow(workflowDef, input = {}, sessionId = null) {
|
|
459
573
|
try {
|
|
460
574
|
let workflow
|
|
461
575
|
|
|
462
576
|
if (typeof workflowDef === 'string') {
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
577
|
+
// 尝试作为工作流名称加载(先检查是否已加载的工作流)
|
|
578
|
+
const workflowName = workflowDef.trim()
|
|
579
|
+
if (this._workflows.has(workflowName)) {
|
|
580
|
+
workflow = this._workflows.get(workflowName)
|
|
581
|
+
} else {
|
|
582
|
+
// 尝试解析 JSON 或执行代码
|
|
583
|
+
try {
|
|
584
|
+
workflow = JSON.parse(workflowDef)
|
|
585
|
+
} catch {
|
|
586
|
+
// eslint-disable-next-line no-new-func
|
|
587
|
+
const fn = new Function('engine', 'return (' + workflowDef + ')')
|
|
588
|
+
workflow = fn(this._engine)
|
|
589
|
+
}
|
|
470
590
|
}
|
|
471
591
|
} else {
|
|
472
592
|
workflow = workflowDef
|
|
@@ -474,6 +594,11 @@ class WorkflowPlugin extends Plugin {
|
|
|
474
594
|
|
|
475
595
|
const context = this._engine.createContext(input, {})
|
|
476
596
|
|
|
597
|
+
// 将 sessionId 存储到上下文变量,供工具步骤使用
|
|
598
|
+
if (sessionId) {
|
|
599
|
+
context.variables._sessionId = sessionId
|
|
600
|
+
}
|
|
601
|
+
|
|
477
602
|
// 执行工作流步骤
|
|
478
603
|
if (workflow.steps && Array.isArray(workflow.steps)) {
|
|
479
604
|
const results = []
|
|
@@ -513,5 +638,6 @@ module.exports = {
|
|
|
513
638
|
ConditionStep,
|
|
514
639
|
SequentialStep,
|
|
515
640
|
LoopStep,
|
|
516
|
-
DelayStep
|
|
641
|
+
DelayStep,
|
|
642
|
+
ToolStep
|
|
517
643
|
}
|
package/src/core/agent.js
CHANGED
|
@@ -29,7 +29,7 @@ class Agent extends EventEmitter {
|
|
|
29
29
|
this.baseURL = config.baseURL
|
|
30
30
|
this.provider = config.provider || 'deepseek'
|
|
31
31
|
this.providerOptions = config.providerOptions || {}
|
|
32
|
-
|
|
32
|
+
this.providerOptions.maxOutputTokens=8192
|
|
33
33
|
// 原始 system prompt
|
|
34
34
|
this._originalPrompt = config.systemPrompt || '你是一个智能助手。当用户提出问题或任务时,你会主动分析需求,选择合适的工具来获取信息或执行操作。你善于将复杂任务拆解为多个步骤,通过工具协作完成。'
|
|
35
35
|
|
package/src/core/framework.js
CHANGED
|
@@ -215,7 +215,7 @@ class Framework extends EventEmitter {
|
|
|
215
215
|
name: `session_${sessionId}`,
|
|
216
216
|
...config
|
|
217
217
|
}
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
// 如果没有提供 AI 相关参数,从 AI 插件获取
|
|
220
220
|
if (!agentConfig.apiKey) {
|
|
221
221
|
const aiPlugin = this.pluginManager.get('ai')
|
|
@@ -223,10 +223,10 @@ class Framework extends EventEmitter {
|
|
|
223
223
|
agentConfig.apiKey = aiPlugin.config.apiKey
|
|
224
224
|
agentConfig.provider = agentConfig.provider || aiPlugin.config.provider
|
|
225
225
|
agentConfig.model = agentConfig.model || aiPlugin.config.model
|
|
226
|
-
agentConfig.baseURL = agentConfig.baseURL || aiPlugin.config.baseURL
|
|
226
|
+
agentConfig.baseURL = agentConfig.baseURL || aiPlugin.config.baseURL,
|
|
227
|
+
agentConfig.providerOptions||aiPlugin.config.providerOptions||{}
|
|
227
228
|
}
|
|
228
229
|
}
|
|
229
|
-
|
|
230
230
|
const agent = new Agent(this, agentConfig)
|
|
231
231
|
this._agents.push(agent)
|
|
232
232
|
|