foliko 1.0.67 → 1.0.69

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.
@@ -424,8 +424,18 @@ class WeixinPlugin extends Plugin {
424
424
 
425
425
  // 确定用户ID
426
426
  let userId = null
427
- if (sessionId && sessionId.startsWith('weixin_')) {
428
- userId = sessionId.replace('weixin_', '')
427
+ let effectiveSessionId = sessionId
428
+
429
+ // 如果没有 sessionId,尝试从执行上下文获取
430
+ if (!effectiveSessionId) {
431
+ const ctx = this._framework.getExecutionContext()
432
+ if (ctx?.sessionId) {
433
+ effectiveSessionId = ctx.sessionId
434
+ }
435
+ }
436
+
437
+ if (effectiveSessionId && effectiveSessionId.startsWith('weixin_')) {
438
+ userId = effectiveSessionId.replace('weixin_', '')
429
439
  } else if (this._sessionPlugin) {
430
440
  // 获取最近的 weixin 会话
431
441
  const sessions = this._sessionPlugin.listSessions()
@@ -144,7 +144,7 @@ think → think
144
144
  ```
145
145
 
146
146
  这意味着:
147
- - 变量引用语法一致:`{{variableName}}`
147
+ - 变量引用语法一致:`${variableName}`(Ambient Agent 使用 `${}`)
148
148
  - sessionId 传递机制一致
149
149
  - 错误处理方式一致
150
150
 
@@ -162,6 +162,66 @@ context.variables.loopIndex // 循环索引(如果被 loop 执行)
162
162
  context.input // 工作流/目标输入参数
163
163
  ```
164
164
 
165
+ ## 变量引用语法
166
+
167
+ Ambient Agent 使用 `${}` 语法在 action 参数中引用变量:
168
+
169
+ ### 1. 事件数据引用 `${event.xxx}`
170
+
171
+ 当 goal 监听事件时,事件数据可通过 `${event.xxx}` 直接引用:
172
+
173
+ ```javascript
174
+ {
175
+ id: 'auto-reply',
176
+ type: 'tool',
177
+ name: 'email_auto_reply',
178
+ args: {
179
+ to: '${event.from}',
180
+ subject: '${event.subject}',
181
+ body: '${event.text}',
182
+ from: ''
183
+ }
184
+ }
185
+ ```
186
+
187
+ **注意**:`email:received` 事件的数据结构:
188
+ - `event.from` - 发件人地址
189
+ - `event.subject` - 邮件主题
190
+ - `event.text` - 邮件正文(纯文本)
191
+ - `event.body` - 邮件正文(可能含 HTML)
192
+ - `event.messageId` - 邮件消息 ID
193
+
194
+ ### 2. 多步骤引用 `${actionId.output.field}`
195
+
196
+ 当有多个顺序执行的 action 时,后续 action 可以引用前一个 action 的输出:
197
+
198
+ ```javascript
199
+ actions: [
200
+ {
201
+ id: 'read-email',
202
+ type: 'tool',
203
+ name: 'email_read',
204
+ args: { limit: 1, unreadOnly: true }
205
+ },
206
+ {
207
+ id: 'process',
208
+ type: 'tool',
209
+ name: 'some_processor',
210
+ args: {
211
+ // 引用 read-email 输出的 emails 数组的第一个元素的 from 字段
212
+ sender: '${read-email.output.emails[0].from}'
213
+ }
214
+ }
215
+ ]
216
+ ```
217
+
218
+ ### 3. 变量解析规则
219
+
220
+ `${}` 语法支持嵌套属性访问:
221
+ - `${event.from}` - 直接字段
222
+ - `${actionId.output.field}` - 嵌套字段
223
+ - `${event.data.xxx}` - data 对象内的字段(兼容性别名)
224
+
165
225
  ## 创建目标的最佳实践
166
226
 
167
227
  1. **明确目标标题** - 让用户清楚知道目标意图
@@ -172,13 +232,13 @@ context.input // 工作流/目标输入参数
172
232
 
173
233
  ## 示例:创建邮件自动回复目标
174
234
 
175
- **重要**:`email:received` 事件的 `email` 对象包含:
176
- - `email.from` - 发件人地址
177
- - `email.to` - 收件人地址
178
- - `email.subject` - 邮件主题
179
- - `email.body` - 邮件正文
235
+ **重要**:`email:received` 事件的数据结构:
236
+ - `event.from` - 发件人地址
237
+ - `event.subject` - 邮件主题
238
+ - `event.text` - 邮件正文(纯文本)
239
+ - `event.messageId` - 邮件消息 ID
180
240
 
181
- 使用 `{{}}` 语法从事件中提取参数:
241
+ 使用 `${}` 语法从事件中提取参数:
182
242
 
183
243
  ```javascript
184
244
  await ambient_goals({
@@ -192,9 +252,18 @@ await ambient_goals({
192
252
  type: 'tool',
193
253
  name: 'email_auto_reply',
194
254
  args: {
195
- to: '{{_event.email.from}}',
196
- subject: '{{_event.email.subject}}',
197
- body: '{{_event.email.body}}'
255
+ to: '${event.from}',
256
+ subject: '${event.subject}',
257
+ body: '${event.text}',
258
+ from: ''
259
+ }
260
+ },
261
+ {
262
+ id: 'mark-read',
263
+ type: 'tool',
264
+ name: 'email_mark_read',
265
+ args: {
266
+ messageId: '${event.messageId}'
198
267
  }
199
268
  }
200
269
  ],
@@ -211,14 +280,15 @@ await ambient_goals({
211
280
 
212
281
  // ✅ 正确 - 从事件中提取参数
213
282
  { type: 'tool', name: 'email_auto_reply', args: {
214
- to: '{{_event.email.from}}',
215
- subject: '{{_event.email.subject}}',
216
- body: '{{_event.email.body}}'
283
+ to: '${event.from}',
284
+ subject: '${event.subject}',
285
+ body: '${event.text}',
286
+ from: ''
217
287
  }
218
288
  }
219
289
  ```
220
290
 
221
- **注意**:`email_auto_reply` 需要 `to`、`subject`、`body` 三个必需参数,必须从 `{{_event.email.xxx}}` 提取。
291
+ **注意**:`email_auto_reply` 需要 `to`、`subject`、`body` 三个必需参数,必须从 `${event.xxx}` 提取。由于 `email:received` 事件已携带邮件数据,不需要额外的 `email_read` 步骤来读取邮件。
222
292
 
223
293
  ## 生命周期
224
294
 
@@ -70,6 +70,23 @@ allowed-tools: execute_workflow,reloadWorkflows
70
70
 
71
71
  ## 工作流步骤类型
72
72
 
73
+ ### 支持的步骤类型
74
+
75
+ | 类型 | 说明 | 自动推断 |
76
+ |------|------|---------|
77
+ | `tool` | 工具调用 | 有 `tool` 字段 |
78
+ | `script` | JavaScript 脚本 | 有 `code` 或 `script` 字段 |
79
+ | `condition` | 条件分支 | 有 `branches` 字段 |
80
+ | `switch` | 开关分支 | 有 `value` + `branches` |
81
+ | `try` | 异常捕获 | 有 `try` 或 `catch` 字段 |
82
+ | `parallel` | 并行执行 | `"parallel": true` 或 `"mode": "parallel"` |
83
+ | `sequential` | 顺序执行 | 有 `steps` 字段 |
84
+ | `loop` | 循环执行 | 有 `steps` + `loopVariable`/`maxIterations` |
85
+ | `delay` | 延时等待 | 有 `delayMs` 字段 |
86
+ | `workflow` | 嵌套工作流 | 有 `workflow` 字段 |
87
+ | `message` | 发送消息 | 有 `message` 字段 |
88
+ | `think` | 主动思考 | 有 `topic` 字段 |
89
+
73
90
  ### 1. script - 脚本步骤
74
91
 
75
92
  执行 JavaScript 脚本,**必须用 return 返回值**:
@@ -219,7 +236,90 @@ allowed-tools: execute_workflow,reloadWorkflows
219
236
  }
220
237
  ```
221
238
 
222
- ### 5. delay - 延时步骤
239
+ ### 5. switch - 开关分支
240
+
241
+ 根据值匹配执行对应分支(类似编程语言的 switch-case):
242
+
243
+ ```json
244
+ {
245
+ "type": "switch",
246
+ "value": "{{status}}",
247
+ "branches": [
248
+ { "case": "success", "steps": [...] },
249
+ { "case": "error", "steps": [...] },
250
+ { "case": "pending", "steps": [...] }
251
+ ],
252
+ "default": { "steps": [...] }
253
+ }
254
+ ```
255
+
256
+ - `value`: 要匹配的值,支持 `{{variable}}` 引用
257
+ - `branches`: 分支数组,匹配第一个 `case` 等于 `value` 的分支
258
+ - `default`: 默认分支(可选)
259
+
260
+ ### 6. try - 异常捕获
261
+
262
+ 尝试执行,失败时执行 catch 分支:
263
+
264
+ ```json
265
+ {
266
+ "type": "try",
267
+ "name": "尝试执行",
268
+ "try": {
269
+ "steps": [
270
+ { "type": "tool", "tool": "可能失败的工具", "args": {...} }
271
+ ]
272
+ },
273
+ "catch": {
274
+ "steps": [
275
+ { "type": "tool", "tool": "notification_send", "args": {"message": "执行失败: {{lastError}}"} }
276
+ ]
277
+ }
278
+ }
279
+ ```
280
+
281
+ - `try`: 尝试执行的步骤
282
+ - `catch`: 捕获异常后执行的步骤
283
+
284
+ ### 7. parallel - 并行执行
285
+
286
+ 多个步骤同时并行执行:
287
+
288
+ ```json
289
+ {
290
+ "type": "parallel",
291
+ "name": "并行获取数据",
292
+ "steps": [
293
+ { "type": "tool", "tool": "fetch", "args": {"url": "https://api1.com"}, "output": "data1" },
294
+ { "type": "tool", "tool": "fetch", "args": {"url": "https://api2.com"}, "output": "data2" },
295
+ { "type": "tool", "tool": "fetch", "args": {"url": "https://api3.com"}, "output": "data3" }
296
+ ]
297
+ }
298
+ ```
299
+
300
+ 返回数组形式的执行结果。
301
+
302
+ ### 8. workflow - 嵌套工作流
303
+
304
+ 在一个工作流中调用另一个工作流:
305
+
306
+ ```json
307
+ {
308
+ "type": "workflow",
309
+ "name": "调用子工作流",
310
+ "workflow": {
311
+ "name": "子工作流名称",
312
+ "steps": [...]
313
+ },
314
+ "input": {
315
+ "param1": "{{parentVar}}"
316
+ }
317
+ }
318
+ ```
319
+
320
+ 子工作流的输出会合并到父工作流的上下文变量中。
321
+
322
+ ### 9. delay - 延时步骤
223
323
 
224
324
  等待指定毫秒数:
225
325
 
@@ -231,7 +331,7 @@ allowed-tools: execute_workflow,reloadWorkflows
231
331
  }
232
332
  ```
233
333
 
234
- ### 6. sequential - 顺序步骤
334
+ ### 10. sequential - 顺序步骤
235
335
 
236
336
  将多个步骤组合为顺序执行(可嵌套使用):
237
337
 
@@ -338,6 +438,91 @@ allowed-tools: execute_workflow,reloadWorkflows
338
438
  }
339
439
  ```
340
440
 
441
+ ### 示例:并行获取多个数据源
442
+
443
+ ```json
444
+ {
445
+ "name": "multi-source-fetch",
446
+ "description": "并行获取多个数据源",
447
+ "steps": [
448
+ {
449
+ "type": "parallel",
450
+ "name": "并行获取数据",
451
+ "steps": [
452
+ { "type": "tool", "tool": "fetch", "args": {"url": "https://api.baidu.com"}, "output": "baiduData" },
453
+ { "type": "tool", "tool": "fetch", "args": {"url": "https://api.bbc.com"}, "output": "bbcData" },
454
+ { "type": "tool", "tool": "fetch", "args": {"url": "https://api.reddit.com"}, "output": "redditData" }
455
+ ]
456
+ },
457
+ {
458
+ "type": "tool",
459
+ "tool": "notification_send",
460
+ "args": {
461
+ "message": "数据获取完成: 百度 {{result[0].length}} 字节, BBC {{result[1].length}} 字节"
462
+ }
463
+ }
464
+ ]
465
+ }
466
+ ```
467
+
468
+ ### 示例:try-catch 异常处理
469
+
470
+ ```json
471
+ {
472
+ "name": "safe-execute",
473
+ "description": "带异常处理的工作流",
474
+ "steps": [
475
+ {
476
+ "type": "try",
477
+ "name": "尝试执行",
478
+ "try": {
479
+ "steps": [
480
+ { "type": "tool", "tool": "fetch", "args": {"url": "https://可能失败的api.com"} }
481
+ ]
482
+ },
483
+ "catch": {
484
+ "steps": [
485
+ { "type": "script", "outputVariable": "errorMsg", "script": "return 'API 调用失败,使用备用数据';" }
486
+ ]
487
+ }
488
+ },
489
+ {
490
+ "type": "tool",
491
+ "tool": "notification_send",
492
+ "args": {
493
+ "message": "结果: {{result.result || result.error}}"
494
+ }
495
+ }
496
+ ]
497
+ }
498
+ ```
499
+
500
+ ### 示例:switch 分支处理
501
+
502
+ ```json
503
+ {
504
+ "name": "status-handler",
505
+ "description": "根据状态分发处理",
506
+ "steps": [
507
+ {
508
+ "type": "script",
509
+ "outputVariable": "status",
510
+ "script": "return 'success';"
511
+ },
512
+ {
513
+ "type": "switch",
514
+ "value": "{{status}}",
515
+ "branches": [
516
+ { "case": "success", "steps": [{ "type": "tool", "tool": "notification_send", "args": {"message": "操作成功"} }] },
517
+ { "case": "error", "steps": [{ "type": "tool", "tool": "notification_send", "args": {"message": "操作失败"} }] },
518
+ { "case": "pending", "steps": [{ "type": "tool", "tool": "notification_send", "args": {"message": "操作进行中"} }] }
519
+ ],
520
+ "default": { "steps": [{ "type": "tool", "tool": "notification_send", "args": {"message": "未知状态"} }] }
521
+ }
522
+ ]
523
+ }
524
+ ```
525
+
341
526
  ## 注意事项
342
527
 
343
528
  1. **优先使用 `{{result}}` 引用**:上一步结果直接用 `{{result.field}}` 提取,比 script 更简洁
@@ -349,6 +534,10 @@ allowed-tools: execute_workflow,reloadWorkflows
349
534
  7. 循环变量 `i` 从 0 开始计数
350
535
  8. 条件分支的 `condition` 是 JavaScript 表达式字符串
351
536
  9. **自动 JSON 解析**:`{{result.body}}` 如果是 JSON 字符串会自动解析
537
+ 10. **类型自动推断**:如果步骤没有指定 `type`,会根据字段自动推断类型
538
+ 11. **`${stepId.output}` 引用**:可引用指定 id 步骤的输出(兼容旧格式)
539
+ 12. **`input` 字段**:可传递上一步输出给工具(兼容旧格式)
540
+ 13. **try-catch 异常处理**:使用 try 包裹可能失败的步骤,catch 捕获错误
352
541
 
353
542
  ## 最佳实践
354
543
 
@@ -359,6 +548,9 @@ allowed-tools: execute_workflow,reloadWorkflows
359
548
  5. 复杂的业务逻辑优先使用脚本步骤
360
549
  6. **script 必须 return**:`script` 是函数体,必须用 return 返回值
361
550
  7. **JSON 中 script 单行写**:用分号分隔多个语句,不要换行
551
+ 8. **并行获取独立数据**:使用 `parallel` 同时获取多个数据源,节省时间
552
+ 9. **使用 try-catch**:对可能失败的步骤使用异常捕获,避免整个工作流中断
553
+ 10. **嵌套工作流**:将复杂工作流拆分为子工作流,提高复用性和可维护性
362
554
 
363
555
  **错误示例**:
364
556
  ```json
@@ -377,6 +569,16 @@ allowed-tools: execute_workflow,reloadWorkflows
377
569
  {
378
570
  "script": "return 1; // 这是注释"
379
571
  }
572
+
573
+ // ❌ 错误 - switch 缺少 default 兜底
574
+ {
575
+ "type": "switch",
576
+ "value": "{{status}}",
577
+ "branches": [
578
+ { "case": "success", "steps": [...] }
579
+ ]
580
+ // 没有 default 分支,匹配不到会静默忽略
581
+ }
380
582
  ```
381
583
 
382
584
  **正确示例**:
@@ -386,4 +588,14 @@ allowed-tools: execute_workflow,reloadWorkflows
386
588
 
387
589
  // ✅ 正确 - 复杂逻辑拆成多步
388
590
  { "type": "script", "script": "context.variables.a=10; context.variables.b=20; return context.variables.a+context.variables.b;" }
591
+
592
+ // ✅ 正确 - switch 有 default 兜底
593
+ {
594
+ "type": "switch",
595
+ "value": "{{status}}",
596
+ "branches": [
597
+ { "case": "success", "steps": [...] }
598
+ ],
599
+ "default": { "steps": [{ "type": "tool", "tool": "notification_send", "args": {"message": "未知状态"} }] }
600
+ }
389
601
  ```
@@ -0,0 +1,182 @@
1
+ # 工作流开发调试指南
2
+
3
+ ## 快速测试工作流
4
+
5
+ ### 1. 使用 execute_workflow 直接测试
6
+
7
+ ```javascript
8
+ execute_workflow({
9
+ workflow: {
10
+ name: "test-workflow",
11
+ description: "测试工作流",
12
+ steps: [
13
+ {
14
+ id: "step1",
15
+ type: "tool",
16
+ tool: "python-execute",
17
+ args: {
18
+ code: "print('Hello from Python!')"
19
+ },
20
+ outputVariable: "result"
21
+ }
22
+ ]
23
+ }
24
+ })
25
+ ```
26
+
27
+ ### 2. JSON 格式注意事项
28
+
29
+ - ✅ 使用双引号包裹字符串
30
+ - ✅ 确保 JSON 语法正确
31
+ - ❌ 避免在 JSON 中使用未转义的特殊字符
32
+ - ❌ 不要在 code 中混用单引号和双引号
33
+
34
+ ### 3. 常见错误
35
+
36
+ ```
37
+ Unexpected identifier 'xxx'
38
+ ```
39
+ - 原因:JSON 格式错误,可能是引号或逗号问题
40
+ - 解决:检查 JSON 语法
41
+
42
+ ```
43
+ Unexpected end of JSON input
44
+ ```
45
+ - 原因:模板字符串 `${}` 在 JSON 中导致解析错误
46
+ - 解决:不要在 JSON 中使用模板变量,改用纯 Python 代码
47
+
48
+ ## 工作流重载问题
49
+
50
+ ### 问题
51
+ 添加新工作流后,`reloadWorkflows` 不会重新加载同名工作流。
52
+
53
+ ### 解决
54
+ ```bash
55
+ # 方法1:使用不同的文件名
56
+ news-dashboard-v3.json # 不会被 v2 覆盖
57
+
58
+ # 方法2:重启插件
59
+ reload_plugins() # 重载所有插件
60
+ ```
61
+
62
+ ## Python 执行最佳实践
63
+
64
+ ### 1. 错误处理
65
+ ```python
66
+ try:
67
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
68
+ with urllib.request.urlopen(req, timeout=10) as resp:
69
+ data = resp.read().decode('utf-8')
70
+ print('SUCCESS')
71
+ except Exception as e:
72
+ print(f'ERROR: {e}')
73
+ # fallback 逻辑
74
+ ```
75
+
76
+ ### 2. 输出格式
77
+ ```python
78
+ # 简单输出
79
+ print('Status: OK')
80
+ print(f'Count: {len(items)}')
81
+
82
+ # JSON 输出
83
+ import json
84
+ print('---JSON_START---')
85
+ print(json.dumps(data, ensure_ascii=False))
86
+ print('---JSON_END---')
87
+ ```
88
+
89
+ ### 3. 超时设置
90
+ ```python
91
+ with urllib.request.urlopen(req, timeout=30) as resp: # 30秒超时
92
+ ...
93
+ ```
94
+
95
+ ## 工作流步骤类型
96
+
97
+ | 类型 | 用途 | 示例 |
98
+ |------|------|------|
99
+ | `tool` | 调用工具 | `python-execute`, `shell`, `get_time` |
100
+ | `script` | 执行 JavaScript | 数据处理、变量操作 |
101
+ | `condition` | 条件分支 | if/else 逻辑 |
102
+ | `loop` | 循环执行 | 遍历列表 |
103
+ | `delay` | 延时等待 | 等待指定时间 |
104
+ | `message` | 消息通知 | 发送通知 |
105
+ | `think` | LLM 思考 | 反思/分析 |
106
+
107
+ ## 工具输出变量
108
+
109
+ ### 正确用法
110
+ ```json
111
+ {
112
+ "id": "step1",
113
+ "type": "tool",
114
+ "tool": "get_time",
115
+ "outputVariable": "current_time"
116
+ }
117
+ ```
118
+
119
+ ### 访问输出
120
+ ```javascript
121
+ // 在 script 步骤中
122
+ context.input.current_time
123
+
124
+ // 在下一个 tool 步骤中
125
+ ${step1.current_time}
126
+ ```
127
+
128
+ ## 调试技巧
129
+
130
+ ### 1. 打印中间变量
131
+ ```python
132
+ print('Debug - data:', data)
133
+ print('Debug - length:', len(data))
134
+ ```
135
+
136
+ ### 2. 保存到文件
137
+ ```python
138
+ with open('debug.log', 'w') as f:
139
+ f.write(str(data))
140
+ ```
141
+
142
+ ### 3. 逐步执行
143
+ ```javascript
144
+ {
145
+ "steps": [
146
+ { "id": "step1", ... }, // 先测试 step1
147
+ { "id": "step2", ... } // 确认 step1 成功后添加
148
+ ]
149
+ }
150
+ ```
151
+
152
+ ## 性能优化
153
+
154
+ ### 1. 合并步骤
155
+ ```javascript
156
+ // ❌ 多个小步骤
157
+ { "id": "step1", "tool": "python-execute", "code": "a = 1" }
158
+ { "id": "step2", "tool": "python-execute", "code": "b = 2" }
159
+
160
+ // ✅ 单个大步骤
161
+ { "id": "step1", "tool": "python-execute", "code": "a = 1; b = 2; print(a + b)" }
162
+ ```
163
+
164
+ ### 2. 减少变量传递
165
+ ```javascript
166
+ // ❌ 多步骤传递
167
+ { "outputVariable": "data1" }
168
+ { "outputVariable": "data2" }
169
+
170
+ // ✅ 单步骤完成
171
+ { "outputVariable": "final_result" }
172
+ ```
173
+
174
+ ### 3. 超时设置
175
+ ```json
176
+ {
177
+ "args": {
178
+ "code": "...",
179
+ "timeout": 60000 // 60秒超时
180
+ }
181
+ }
182
+ ```