foliko 1.0.66 → 1.0.67

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.0.66",
3
+ "version": "1.0.67",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
 
@@ -138,6 +138,45 @@ allowed-tools: execute_workflow,reloadWorkflows
138
138
  }
139
139
  ```
140
140
 
141
+ **重要:使用 `{{result}}` 引用上一步结果(推荐)**
142
+
143
+ 上一步骤的结果自动保存在 `{{result}}` 中,可以直接提取字段:
144
+
145
+ ```json
146
+ {
147
+ "type": "tool",
148
+ "name": "获取IP",
149
+ "tool": "fetch",
150
+ "args": {
151
+ "url": "https://api.ipify.org?format=json"
152
+ }
153
+ },
154
+ {
155
+ "type": "tool",
156
+ "name": "发送通知",
157
+ "tool": "notification_send",
158
+ "args": {
159
+ "title": "IP 信息",
160
+ "message": "当前公网IP: {{result.body.ip}}"
161
+ }
162
+ }
163
+ ```
164
+
165
+ 支持的引用格式:
166
+
167
+ | 格式 | 说明 |
168
+ |------|------|
169
+ | `{{result}}` | 上一步完整结果 |
170
+ | `{{result.body}}` | 提取 body 字段 |
171
+ | `{{result.body.ip}}` | 从 body 提取嵌套字段 |
172
+ | `{{result.body.data[0].name}}` | 支持数组索引 |
173
+ | `{{lastResult}}` | result 的别名 |
174
+ | `{{lastResult.error}}` | 从错误对象提取信息 |
175
+ | `{{variables.xxx}}` | 显式引用 context.variables |
176
+ | `{{input.xxx}}` | 引用工作流输入参数 |
177
+
178
+ **自动 JSON 解析**:如果字段值是 JSON 字符串,会自动解析为对象后再提取嵌套字段。
179
+
141
180
  ### 3. loop - 循环步骤
142
181
 
143
182
  重复执行一组步骤:
@@ -215,6 +254,7 @@ allowed-tools: execute_workflow,reloadWorkflows
215
254
 
216
255
  ### 示例:获取IP并发送通知
217
256
 
257
+ **推荐写法**(使用 `{{result}}` 直接引用上一步结果):
218
258
  ```json
219
259
  {
220
260
  "name": "get-ip-notify",
@@ -227,23 +267,41 @@ allowed-tools: execute_workflow,reloadWorkflows
227
267
  "args": {
228
268
  "url": "https://api.ipify.org?format=json",
229
269
  "proxy": true
230
- },
270
+ }
271
+ },
272
+ {
273
+ "type": "tool",
274
+ "name": "发送通知",
275
+ "tool": "notification_send",
276
+ "args": {
277
+ "title": "IP 信息",
278
+ "message": "当前公网IP: {{result.body.ip}}"
279
+ }
280
+ }
281
+ ]
282
+ }
283
+ ```
284
+
285
+ **旧写法**(仍支持,但不推荐):
286
+ ```json
287
+ {
288
+ "name": "get-ip-notify-old",
289
+ "steps": [
290
+ {
291
+ "type": "tool",
292
+ "tool": "fetch",
293
+ "args": { "url": "https://api.ipify.org?format=json" },
231
294
  "outputVariable": "ipResult"
232
295
  },
233
296
  {
234
297
  "type": "script",
235
- "name": "提取IP",
236
298
  "outputVariable": "currentIp",
237
299
  "script": "var r=context.variables.ipResult; return (r&&r.success&&r.body)?(typeof r.body==='object'?r.body.ip:r.body.trim()):'获取失败';"
238
300
  },
239
301
  {
240
302
  "type": "tool",
241
- "name": "发送通知",
242
303
  "tool": "notification_send",
243
- "args": {
244
- "title": "IP 信息",
245
- "message": "当前公网IP: {{currentIp}}"
246
- }
304
+ "args": { "message": "当前公网IP: {{currentIp}}" }
247
305
  }
248
306
  ]
249
307
  }
@@ -282,13 +340,15 @@ allowed-tools: execute_workflow,reloadWorkflows
282
340
 
283
341
  ## 注意事项
284
342
 
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 表达式字符串
343
+ 1. **优先使用 `{{result}}` 引用**:上一步结果直接用 `{{result.field}}` 提取,比 script 更简洁
344
+ 2. **script 必须 return**:`script` 是函数体,必须用 return 返回值
345
+ 3. **JSON script 单行写**:用分号分隔多个语句,不要换行
346
+ 4. **JSON 中不能有注释**:注释会导致 JSON 解析失败
347
+ 5. **context.variables** 在所有步骤间共享,可存储中间结果
348
+ 6. **context.input** 是工作流的输入参数
349
+ 7. 循环变量 `i` 0 开始计数
350
+ 8. 条件分支的 `condition` 是 JavaScript 表达式字符串
351
+ 9. **自动 JSON 解析**:`{{result.body}}` 如果是 JSON 字符串会自动解析
292
352
 
293
353
  ## 最佳实践
294
354
 
@@ -328,12 +328,29 @@ class StepExecutor {
328
328
 
329
329
  _resolveValue(value, context) {
330
330
  if (typeof value === 'string') {
331
+ // 支持多种引用格式:
332
+ // {{result}} - 上一步结果 (lastResult)
333
+ // {{result.body.ip}} - 从上一步结果提取嵌套字段
334
+ // {{variables.xxx}} - 显式引用 context.variables
335
+ // {{context.xxx}} - 显式引用 context 根属性
336
+ // {{lastResult}} - 上一步结果(result 的别名)
331
337
  return value.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
338
+ // result 和 lastResult 都指向 context.lastResult
339
+ if (path === 'result' || path === 'lastResult') {
340
+ return context.lastResult ?? match
341
+ }
342
+ if (path.startsWith('result.') || path.startsWith('lastResult.')) {
343
+ const nestedPath = path.replace(/^(result|lastResult)\./, '')
344
+ const val = this._getNestedValue(context.lastResult, nestedPath)
345
+ if (val !== undefined) return val
346
+ return match
347
+ }
332
348
  if (path.startsWith('variables.')) {
333
349
  return this._getNestedValue(context, path) ?? match
334
350
  } else if (path.startsWith('context.')) {
335
351
  return this._getNestedValue(context, path) ?? match
336
352
  } else {
353
+ // 先从 variables 查找,再从 context 根属性查找
337
354
  const val = this._getNestedValue(context.variables, path)
338
355
  if (val !== undefined) return val
339
356
  return this._getNestedValue(context, path) ?? match
@@ -357,6 +374,14 @@ class StepExecutor {
357
374
  let current = obj
358
375
  for (const part of parts) {
359
376
  if (current === null || current === undefined) return undefined
377
+ // 如果当前值是字符串,尝试解析为 JSON
378
+ if (typeof current === 'string' && part !== '0' && part !== 'length') {
379
+ try {
380
+ current = JSON.parse(current)
381
+ } catch {
382
+ // 解析失败,继续用原值
383
+ }
384
+ }
360
385
  current = current[part]
361
386
  }
362
387
  return current