foliko 1.0.39 → 1.0.41
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 +37 -1
- package/cli/src/ui/chat-ui.js +7 -2
- package/docs/features.md +120 -0
- package/examples/basic.js +110 -110
- package/examples/bootstrap.js +19 -0
- package/examples/mcp-example.js +53 -53
- package/examples/skill-example.js +49 -49
- package/examples/test-chat.js +6 -0
- package/examples/test-mcp.js +79 -79
- package/examples/test-reload.js +61 -61
- package/examples/test-web-plugin.js +98 -0
- package/package.json +4 -3
- package/plugins/default-plugins.js +11 -10
- package/plugins/feishu-plugin.js +291 -537
- package/plugins/scheduler-plugin.js +1 -1
- package/plugins/subagent-plugin.js +4 -4
- package/plugins/telegram-plugin.js +307 -522
- package/plugins/think-plugin.js +2 -2
- package/plugins/web-plugin.js +542 -0
- package/plugins/weixin-plugin.js +320 -274
- package/skills/workflow-guide/SKILL.md +263 -0
- package/src/capabilities/workflow-engine.js +120 -5
- package/src/core/agent-chat.js +24 -27
- package/src/core/agent.js +77 -0
- package/src/core/framework.js +35 -0
- package/src/executors/executor-base.js +58 -58
- package/test-server.js +25 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow-guide
|
|
3
|
+
description: 工作流与多步任务指南。当用户说"创建工作流"、"多步任务"、"自动化流程"时立即调用。
|
|
4
|
+
allowed-tools: Read, Write, Edit, Glob, Grep, Bash, execute_workflow, reloadWorkflows
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 工作流(Workflow)开发指南
|
|
8
|
+
|
|
9
|
+
## 概述
|
|
10
|
+
|
|
11
|
+
工作流引擎用于处理多步骤任务,通过 `execute_workflow` 工具执行。工作流由多个步骤组成,支持脚本、条件分支、循环、延时等类型。
|
|
12
|
+
|
|
13
|
+
## 工作流存放位置
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
项目目录/
|
|
17
|
+
└── .agent/
|
|
18
|
+
└── workflows/ # 工作流定义目录
|
|
19
|
+
└── my-workflow.json # 工作流 JSON 定义
|
|
20
|
+
└── another.js # 或 JS 文件(导出 default 工作流)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 自动加载与注册
|
|
24
|
+
|
|
25
|
+
将工作流文件放入 `.agent/workflows/` 目录后,系统会自动:
|
|
26
|
+
1. 加载所有 `.json` 和 `.js` 文件
|
|
27
|
+
2. 将工作流注册为 `workflow_<文件名>` 工具
|
|
28
|
+
|
|
29
|
+
例如 `my-workflow.json` 会注册为 `workflow_my_workflow` 工具,可直接调用。
|
|
30
|
+
|
|
31
|
+
## 重载工作流
|
|
32
|
+
|
|
33
|
+
创建或修改工作流后,调用 `reloadWorkflows` 重载:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"tool": "reloadWorkflows",
|
|
38
|
+
"args": {}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 使用方式
|
|
43
|
+
|
|
44
|
+
### 方式一:直接执行(任意工作流)
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"tool": "execute_workflow",
|
|
48
|
+
"args": {
|
|
49
|
+
"workflow": "<工作流 JSON 字符串>",
|
|
50
|
+
"input": { "变量名": "值" }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 方式二:执行已注册的工作流工具
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"tool": "workflow_my_workflow",
|
|
59
|
+
"args": {
|
|
60
|
+
"input": { "变量名": "值" }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 工作流步骤类型
|
|
66
|
+
|
|
67
|
+
### 1. script - 脚本步骤
|
|
68
|
+
|
|
69
|
+
执行 JavaScript 脚本:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"type": "script",
|
|
74
|
+
"name": "步骤名称",
|
|
75
|
+
"outputVariable": "result",
|
|
76
|
+
"script": "context.variables.x = 10; return context.variables.x;"
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `outputVariable`: 可选,将返回值存入上下文变量
|
|
81
|
+
- `context.variables`: 所有步骤共享的变量对象
|
|
82
|
+
- `context.stepResults`: 上一步的输出结果
|
|
83
|
+
|
|
84
|
+
### 2. loop - 循环步骤
|
|
85
|
+
|
|
86
|
+
重复执行一组步骤:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"type": "loop",
|
|
91
|
+
"name": "循环3次",
|
|
92
|
+
"maxIterations": 3,
|
|
93
|
+
"loopVariable": "i",
|
|
94
|
+
"steps": [
|
|
95
|
+
{ "type": "script", "name": "执行内容", "script": "..." }
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `maxIterations`: 最大迭代次数
|
|
101
|
+
- `loopVariable`: 循环计数器变量名(从 0 开始)
|
|
102
|
+
|
|
103
|
+
### 3. condition - 条件分支
|
|
104
|
+
|
|
105
|
+
根据条件选择执行分支:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"type": "condition",
|
|
110
|
+
"name": "判断条件",
|
|
111
|
+
"branches": [
|
|
112
|
+
{
|
|
113
|
+
"name": "条件1",
|
|
114
|
+
"condition": "context.variables.value > 10",
|
|
115
|
+
"steps": [...]
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "条件2",
|
|
119
|
+
"condition": "context.variables.value <= 10",
|
|
120
|
+
"steps": [...]
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 4. delay - 延时步骤
|
|
127
|
+
|
|
128
|
+
等待指定毫秒数:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"type": "delay",
|
|
133
|
+
"name": "等待1秒",
|
|
134
|
+
"delayMs": 1000
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 5. parallel - 并行步骤(可选)
|
|
139
|
+
|
|
140
|
+
并行执行多个步骤:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"type": "parallel",
|
|
145
|
+
"name": "并行执行",
|
|
146
|
+
"steps": [...]
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 完整示例
|
|
151
|
+
|
|
152
|
+
### 示例:用户问候与天气查询工作流
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"name": "greet-and-query-weather",
|
|
157
|
+
"description": "问候用户并查询天气",
|
|
158
|
+
"steps": [
|
|
159
|
+
{
|
|
160
|
+
"type": "script",
|
|
161
|
+
"name": "获取用户名",
|
|
162
|
+
"outputVariable": "username",
|
|
163
|
+
"script": "context.variables.username = context.input.username || '用户'; return context.variables.username;"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"type": "script",
|
|
167
|
+
"name": "生成问候语",
|
|
168
|
+
"outputVariable": "greeting",
|
|
169
|
+
"script": "return '你好,' + context.variables.username + '!';"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
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
|
+
]
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
调用:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"tool": "execute_workflow",
|
|
210
|
+
"args": {
|
|
211
|
+
"workflow": "<上面 JSON 转字符串>",
|
|
212
|
+
"input": { "username": "张三", "queryWeather": true }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 示例:循环处理任务列表
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"name": "process-items",
|
|
222
|
+
"description": "循环处理列表中的每个项目",
|
|
223
|
+
"steps": [
|
|
224
|
+
{
|
|
225
|
+
"type": "script",
|
|
226
|
+
"name": "初始化列表",
|
|
227
|
+
"outputVariable": "items",
|
|
228
|
+
"script": "context.variables.items = ['任务A', '任务B', '任务C']; return context.variables.items;"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"type": "loop",
|
|
232
|
+
"name": "处理每个任务",
|
|
233
|
+
"maxIterations": 3,
|
|
234
|
+
"loopVariable": "i",
|
|
235
|
+
"steps": [
|
|
236
|
+
{
|
|
237
|
+
"type": "script",
|
|
238
|
+
"name": "处理任务",
|
|
239
|
+
"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;"
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## 注意事项
|
|
249
|
+
|
|
250
|
+
1. **context.variables** 在所有步骤间共享,可存储中间结果
|
|
251
|
+
2. **context.input** 是工作流的输入参数
|
|
252
|
+
3. **context.stepResults** 包含前面各步骤的输出
|
|
253
|
+
4. 脚本中使用 `return` 值会传递给下一步
|
|
254
|
+
5. 循环变量 `i` 从 0 开始计数
|
|
255
|
+
6. 条件分支的 `condition` 是 JavaScript 表达式字符串
|
|
256
|
+
|
|
257
|
+
## 最佳实践
|
|
258
|
+
|
|
259
|
+
1. 为每个步骤设置有意义的 `name`
|
|
260
|
+
2. 使用 `outputVariable` 保存需要跨步骤共享的数据
|
|
261
|
+
3. 循环前确保有合理的 `maxIterations` 限制
|
|
262
|
+
4. 条件分支要有兜底的 `condition: "true"` 分支
|
|
263
|
+
5. 复杂的业务逻辑优先使用脚本步骤
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
const { EventEmitter } = require('../utils/event-emitter')
|
|
7
7
|
const { Plugin } = require('../core/plugin-base')
|
|
8
8
|
const { z } = require('zod')
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
const path = require('path')
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* 工作流步骤类型
|
|
@@ -298,6 +300,9 @@ class WorkflowPlugin extends Plugin {
|
|
|
298
300
|
|
|
299
301
|
this._framework = null
|
|
300
302
|
this._engine = null
|
|
303
|
+
this._workflowsDir = config.workflowsDir || '.agent/workflows'
|
|
304
|
+
this._workflows = new Map()
|
|
305
|
+
this._workflowTools = new Map()
|
|
301
306
|
}
|
|
302
307
|
|
|
303
308
|
install(framework) {
|
|
@@ -328,9 +333,124 @@ class WorkflowPlugin extends Plugin {
|
|
|
328
333
|
}
|
|
329
334
|
|
|
330
335
|
start(framework) {
|
|
336
|
+
this._loadWorkflows()
|
|
337
|
+
this._registerWorkflowTools()
|
|
338
|
+
|
|
339
|
+
// 注册 reloadWorkflows 工具
|
|
340
|
+
framework.registerTool({
|
|
341
|
+
name: 'reloadWorkflows',
|
|
342
|
+
description: '重载所有工作流,当用户添加或修改工作流后调用此工具',
|
|
343
|
+
inputSchema: z.object({}),
|
|
344
|
+
execute: async () => {
|
|
345
|
+
this.reload(this._framework)
|
|
346
|
+
return {
|
|
347
|
+
success: true,
|
|
348
|
+
message: `Workflows reloaded. Total: ${this._workflows.size}`,
|
|
349
|
+
workflows: Array.from(this._workflows.keys())
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
})
|
|
353
|
+
|
|
331
354
|
return this
|
|
332
355
|
}
|
|
333
356
|
|
|
357
|
+
/**
|
|
358
|
+
* 加载目录中的工作流定义
|
|
359
|
+
*/
|
|
360
|
+
_loadWorkflows() {
|
|
361
|
+
const dir = path.resolve(process.cwd(), this._workflowsDir)
|
|
362
|
+
if (!fs.existsSync(dir)) {
|
|
363
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
364
|
+
console.log(`[Workflow] Created workflows directory: ${dir}`)
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const files = fs.readdirSync(dir)
|
|
370
|
+
for (const file of files) {
|
|
371
|
+
if (!file.endsWith('.json') && !file.endsWith('.js')) continue
|
|
372
|
+
|
|
373
|
+
const filePath = path.join(dir, file)
|
|
374
|
+
const workflowName = path.basename(file, path.extname(file))
|
|
375
|
+
|
|
376
|
+
// 跳过已存在的工作流
|
|
377
|
+
if (this._workflows.has(workflowName)) continue
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
381
|
+
let workflowDef
|
|
382
|
+
|
|
383
|
+
if (file.endsWith('.js')) {
|
|
384
|
+
// 执行 JS 文件获取工作流定义
|
|
385
|
+
// eslint-disable-next-line no-new-func
|
|
386
|
+
const fn = new Function('module', 'exports', 'require', 'process', 'console', '__dirname', '__filename', content)
|
|
387
|
+
const module = { exports: {} }
|
|
388
|
+
fn(module, module.exports, require, process, console, path.dirname(filePath), filePath)
|
|
389
|
+
workflowDef = module.exports.default || module.exports
|
|
390
|
+
} else {
|
|
391
|
+
workflowDef = JSON.parse(content)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (workflowDef && workflowDef.steps) {
|
|
395
|
+
this._workflows.set(workflowName, workflowDef)
|
|
396
|
+
console.log(`[Workflow] Loaded: ${workflowName}`)
|
|
397
|
+
}
|
|
398
|
+
} catch (err) {
|
|
399
|
+
console.error(`[Workflow] Failed to load ${file}:`, err.message)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} catch (err) {
|
|
403
|
+
console.error('[Workflow] Failed to read workflows directory:', err.message)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* 注册工作流为工具
|
|
409
|
+
*/
|
|
410
|
+
_registerWorkflowTools() {
|
|
411
|
+
for (const [name, workflow] of this._workflows) {
|
|
412
|
+
if (this._workflowTools.has(name)) continue
|
|
413
|
+
|
|
414
|
+
const toolName = `workflow_${name}`
|
|
415
|
+
const description = workflow.description || `执行工作流: ${name}`
|
|
416
|
+
|
|
417
|
+
this._framework.registerTool({
|
|
418
|
+
name: toolName,
|
|
419
|
+
description,
|
|
420
|
+
inputSchema: z.object({
|
|
421
|
+
input: z.object({}).optional().describe('工作流输入参数')
|
|
422
|
+
}),
|
|
423
|
+
execute: async (args) => {
|
|
424
|
+
return await this.executeWorkflow(workflow, args.input || {})
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
this._workflowTools.set(name, toolName)
|
|
429
|
+
console.log(`[Workflow] Registered tool: ${toolName}`)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* 重载工作流
|
|
435
|
+
*/
|
|
436
|
+
reload(framework) {
|
|
437
|
+
console.log('[Workflow] Reloading...')
|
|
438
|
+
this._framework = framework
|
|
439
|
+
|
|
440
|
+
// 清除已注册的工具
|
|
441
|
+
for (const toolName of this._workflowTools.values()) {
|
|
442
|
+
// 工具注销需要框架支持,这里只清理内部状态
|
|
443
|
+
}
|
|
444
|
+
this._workflowTools.clear()
|
|
445
|
+
this._workflows.clear()
|
|
446
|
+
|
|
447
|
+
// 重新加载
|
|
448
|
+
this._loadWorkflows()
|
|
449
|
+
this._registerWorkflowTools()
|
|
450
|
+
|
|
451
|
+
console.log(`[Workflow] Reloaded. Total workflows: ${this._workflows.size}`)
|
|
452
|
+
}
|
|
453
|
+
|
|
334
454
|
/**
|
|
335
455
|
* 执行工作流
|
|
336
456
|
*/
|
|
@@ -377,11 +497,6 @@ class WorkflowPlugin extends Plugin {
|
|
|
377
497
|
return this._engine
|
|
378
498
|
}
|
|
379
499
|
|
|
380
|
-
reload(framework) {
|
|
381
|
-
console.log('[WorkflowPlugin] Reloading...')
|
|
382
|
-
this._framework = framework
|
|
383
|
-
}
|
|
384
|
-
|
|
385
500
|
uninstall(framework) {
|
|
386
501
|
this._engine = null
|
|
387
502
|
this._framework = null
|
package/src/core/agent-chat.js
CHANGED
|
@@ -96,6 +96,9 @@ class AgentChatHandler extends EventEmitter {
|
|
|
96
96
|
* @param {Object} options - 选项
|
|
97
97
|
*/
|
|
98
98
|
async chat(message, options = {}) {
|
|
99
|
+
const context = { sessionId: options.sessionId || null, isStream: false }
|
|
100
|
+
const framework = this.agent.framework
|
|
101
|
+
|
|
99
102
|
// 动态导入 AI SDK
|
|
100
103
|
const { tool, ToolLoopAgent } = await this._importAI()
|
|
101
104
|
|
|
@@ -108,32 +111,27 @@ class AgentChatHandler extends EventEmitter {
|
|
|
108
111
|
const maxSteps = options.maxSteps || this._maxSteps
|
|
109
112
|
const tools = this._getAITools(tool)
|
|
110
113
|
|
|
111
|
-
// 如果没有 AI 客户端,抛出错误
|
|
112
114
|
if (!this._aiClient) {
|
|
113
|
-
throw new Error('AI client not configured.
|
|
115
|
+
throw new Error('AI client not configured.')
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
stopWhen: (step) => step.stepCount >= maxSteps
|
|
123
|
-
})
|
|
118
|
+
const agent = new ToolLoopAgent({
|
|
119
|
+
model: this._aiClient,
|
|
120
|
+
instructions: this._systemPrompt,
|
|
121
|
+
tools: tools,
|
|
122
|
+
stopWhen: (step) => step.stepCount >= maxSteps
|
|
123
|
+
})
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
]
|
|
125
|
+
const messages = [
|
|
126
|
+
...this._cleanMessages(this._messages)
|
|
127
|
+
]
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
try {
|
|
130
|
+
// 使用 runWithContext 让工具执行时能获取 sessionId
|
|
131
|
+
const result = await framework.runWithContext(context, async () => {
|
|
132
|
+
return agent.generate({ messages, ...this.providerOptions })
|
|
134
133
|
})
|
|
135
134
|
|
|
136
|
-
// 添加助手消息到历史
|
|
137
135
|
if (result.text) {
|
|
138
136
|
this._messages.push({
|
|
139
137
|
role: 'assistant',
|
|
@@ -159,6 +157,9 @@ class AgentChatHandler extends EventEmitter {
|
|
|
159
157
|
* @param {Object} options - 选项
|
|
160
158
|
*/
|
|
161
159
|
async *chatStream(message, options = {}) {
|
|
160
|
+
const context = { sessionId: options.sessionId || null, isStream: true }
|
|
161
|
+
const framework = this.agent.framework
|
|
162
|
+
|
|
162
163
|
// 动态导入 AI SDK
|
|
163
164
|
const { tool, ToolLoopAgent } = await this._importAI()
|
|
164
165
|
|
|
@@ -171,12 +172,10 @@ class AgentChatHandler extends EventEmitter {
|
|
|
171
172
|
const maxSteps = options.maxSteps || this._maxSteps
|
|
172
173
|
const tools = this._getAITools(tool)
|
|
173
174
|
|
|
174
|
-
// 如果没有 AI 客户端,抛出错误
|
|
175
175
|
if (!this._aiClient) {
|
|
176
|
-
throw new Error('AI client not configured.
|
|
176
|
+
throw new Error('AI client not configured.')
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
// 使用 ToolLoopAgent 流式接口
|
|
180
179
|
const agent = new ToolLoopAgent({
|
|
181
180
|
model: this._aiClient,
|
|
182
181
|
instructions: this._systemPrompt,
|
|
@@ -189,9 +188,9 @@ class AgentChatHandler extends EventEmitter {
|
|
|
189
188
|
]
|
|
190
189
|
|
|
191
190
|
try {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
// 使用 runWithContext 让工具执行时能获取 sessionId(支持并行)
|
|
192
|
+
const result = await framework.runWithContext(context, async () => {
|
|
193
|
+
return agent.stream({ messages, ...this.providerOptions })
|
|
195
194
|
})
|
|
196
195
|
|
|
197
196
|
const stream = result.fullStream
|
|
@@ -203,7 +202,6 @@ class AgentChatHandler extends EventEmitter {
|
|
|
203
202
|
fullText += text
|
|
204
203
|
yield { type: 'text', text }
|
|
205
204
|
} else if (part.type === 'reasoning') {
|
|
206
|
-
// 通过事件发射推理内容
|
|
207
205
|
this.emit('thinking', { content: part.text })
|
|
208
206
|
} else if (part.type === 'tool-call') {
|
|
209
207
|
yield { type: 'tool-call', toolName: part.toolName, args: part.input }
|
|
@@ -214,7 +212,6 @@ class AgentChatHandler extends EventEmitter {
|
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
214
|
|
|
217
|
-
// 保存消息到历史
|
|
218
215
|
if (fullText) {
|
|
219
216
|
this._messages.push({
|
|
220
217
|
role: 'assistant',
|
package/src/core/agent.js
CHANGED
|
@@ -47,6 +47,10 @@ class Agent extends EventEmitter {
|
|
|
47
47
|
// 子Agent管理
|
|
48
48
|
this._subAgents = new Map()
|
|
49
49
|
|
|
50
|
+
// 消息队列(用于 pushMessage)
|
|
51
|
+
this._messageQueue = []
|
|
52
|
+
this._isProcessingQueue = false
|
|
53
|
+
|
|
50
54
|
// 处理后的 system prompt (带上下文)
|
|
51
55
|
this.systemPrompt = this._buildSystemPrompt()
|
|
52
56
|
|
|
@@ -447,14 +451,80 @@ class Agent extends EventEmitter {
|
|
|
447
451
|
const result = await this._chatHandler.chat(enhancedMessage, options)
|
|
448
452
|
this._status = 'idle'
|
|
449
453
|
this.emit('status', { status: 'idle' })
|
|
454
|
+
|
|
455
|
+
// 处理队列中的下一条消息
|
|
456
|
+
setImmediate(() => this._processQueue())
|
|
457
|
+
|
|
450
458
|
return result
|
|
451
459
|
} catch (err) {
|
|
452
460
|
this._status = 'error'
|
|
453
461
|
this.emit('status', { status: 'error', error: err.message })
|
|
462
|
+
|
|
463
|
+
// 发生错误时也要处理队列
|
|
464
|
+
setImmediate(() => this._processQueue())
|
|
465
|
+
|
|
454
466
|
throw err
|
|
455
467
|
}
|
|
456
468
|
}
|
|
457
469
|
|
|
470
|
+
/**
|
|
471
|
+
* 推送消息(自动继承当前 sessionId 上下文,自动排队)
|
|
472
|
+
* 工具中调用此方法,自动带上当前执行上下文的 sessionId
|
|
473
|
+
* 如果 agent 忙碌,消息会进入队列等待
|
|
474
|
+
* @param {string|Object} message - 消息
|
|
475
|
+
* @param {Object} options - 选项 { maxSteps }
|
|
476
|
+
* @returns {Promise<{success: boolean, message: string, stepCount: number}>}
|
|
477
|
+
*/
|
|
478
|
+
async pushMessage(message, options = {}) {
|
|
479
|
+
// 自动从执行上下文获取 sessionId
|
|
480
|
+
const ctx = this.framework.getExecutionContext()
|
|
481
|
+
if (ctx?.sessionId && !options.sessionId) {
|
|
482
|
+
options.sessionId = ctx.sessionId
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 如果忙碌,进入队列等待
|
|
486
|
+
if (this._status === 'busy') {
|
|
487
|
+
const queuedItem = { message, options, resolve: null, reject: null }
|
|
488
|
+
const promise = new Promise((resolve, reject) => {
|
|
489
|
+
queuedItem.resolve = resolve
|
|
490
|
+
queuedItem.reject = reject
|
|
491
|
+
})
|
|
492
|
+
this._messageQueue.push(queuedItem)
|
|
493
|
+
console.log('[Agent] busy, message queued')
|
|
494
|
+
return promise
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 空闲则直接处理
|
|
498
|
+
return this.chat(message, options)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* 处理消息队列
|
|
503
|
+
* @private
|
|
504
|
+
*/
|
|
505
|
+
async _processQueue() {
|
|
506
|
+
if (this._isProcessingQueue || this._messageQueue.length === 0) {
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
this._isProcessingQueue = true
|
|
511
|
+
|
|
512
|
+
// 强制设置为 idle,确保 this.chat() 能执行
|
|
513
|
+
this._status = 'idle'
|
|
514
|
+
|
|
515
|
+
while (this._messageQueue.length > 0) {
|
|
516
|
+
const item = this._messageQueue.shift()
|
|
517
|
+
try {
|
|
518
|
+
const result = await this.chat(item.message, item.options)
|
|
519
|
+
item.resolve(result)
|
|
520
|
+
} catch (err) {
|
|
521
|
+
item.reject(err)
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
this._isProcessingQueue = false
|
|
526
|
+
}
|
|
527
|
+
|
|
458
528
|
/**
|
|
459
529
|
* 发送消息(流式)
|
|
460
530
|
*/
|
|
@@ -484,9 +554,16 @@ class Agent extends EventEmitter {
|
|
|
484
554
|
yield* this._chatHandler.chatStream(enhancedMessage, options)
|
|
485
555
|
this._status = 'idle'
|
|
486
556
|
this.emit('status', { status: 'idle' })
|
|
557
|
+
|
|
558
|
+
// 处理队列中的下一条消息
|
|
559
|
+
setImmediate(() => this._processQueue())
|
|
487
560
|
} catch (err) {
|
|
488
561
|
this._status = 'error'
|
|
489
562
|
this.emit('status', { status: 'error', error: err.message })
|
|
563
|
+
|
|
564
|
+
// 发生错误时也要处理队列
|
|
565
|
+
setImmediate(() => this._processQueue())
|
|
566
|
+
|
|
490
567
|
throw err
|
|
491
568
|
}
|
|
492
569
|
}
|
package/src/core/framework.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const path = require('path')
|
|
7
|
+
const { AsyncLocalStorage } = require('async_hooks')
|
|
7
8
|
const { EventEmitter } = require('../utils/event-emitter')
|
|
8
9
|
const { PluginManager } = require('./plugin-manager')
|
|
9
10
|
const { ToolRegistry } = require('./tool-registry')
|
|
@@ -16,6 +17,9 @@ if (!module.paths.includes(frameworkNodeModules)) {
|
|
|
16
17
|
module.paths.unshift(frameworkNodeModules)
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
// AsyncLocalStorage 实现真正的上下文隔离(支持并行处理)
|
|
21
|
+
const asyncLocalStorage = new AsyncLocalStorage()
|
|
22
|
+
|
|
19
23
|
class Framework extends EventEmitter {
|
|
20
24
|
/**
|
|
21
25
|
* @param {Object} config - 配置
|
|
@@ -37,6 +41,9 @@ class Framework extends EventEmitter {
|
|
|
37
41
|
this._agents = [] // 所有创建的 agent
|
|
38
42
|
this._mainAgent = null // 主 agent
|
|
39
43
|
|
|
44
|
+
// 执行上下文(工具调用时可用)
|
|
45
|
+
this._executionContext = null
|
|
46
|
+
|
|
40
47
|
// 事件转发
|
|
41
48
|
this.toolRegistry.on('tool:registered', (tool) => {
|
|
42
49
|
this.emit('tool:registered', tool)
|
|
@@ -142,6 +149,34 @@ class Framework extends EventEmitter {
|
|
|
142
149
|
return this.toolRegistry.execute(name, args, this)
|
|
143
150
|
}
|
|
144
151
|
|
|
152
|
+
/**
|
|
153
|
+
* 在上下文中执行函数(支持并行处理,自动隔离 session)
|
|
154
|
+
* @param {Object} context - 执行上下文 { sessionId, ... }
|
|
155
|
+
* @param {Function} fn - 要执行的异步函数
|
|
156
|
+
* @returns {Promise}
|
|
157
|
+
*/
|
|
158
|
+
runWithContext(context, fn) {
|
|
159
|
+
return asyncLocalStorage.run(context, fn)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 获取当前执行上下文(在 runWithContext 内部调用)
|
|
164
|
+
* @returns {Object|null}
|
|
165
|
+
*/
|
|
166
|
+
getExecutionContext() {
|
|
167
|
+
return asyncLocalStorage.getStore() || null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @deprecated 使用 runWithContext 代替
|
|
172
|
+
*/
|
|
173
|
+
setExecutionContext() {}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @deprecated 使用 runWithContext 代替
|
|
177
|
+
*/
|
|
178
|
+
clearExecutionContext() {}
|
|
179
|
+
|
|
145
180
|
/**
|
|
146
181
|
* 创建 Agent
|
|
147
182
|
* @param {Object} config - Agent 配置
|