foliko 1.1.6 → 1.1.7

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.
Files changed (79) hide show
  1. package/.agent/data/email/processed-emails.json +1 -0
  2. package/.agent/data/plugins-state.json +5 -1
  3. package/.agent/data/web/web-config.json +5 -0
  4. package/.agent/memory/feedback/mnt7jrlt-d67qs7.md +15 -0
  5. package/.agent/memory/feedback/mnt88ja3-al4fuy.md +9 -0
  6. package/.agent/plugins/test-plugin.py +108 -0
  7. package/.agent/sessions/cli_default.json +514 -5298
  8. package/.claude/settings.local.json +2 -1
  9. package/SPEC.md +735 -696
  10. package/output/zen_silence.png +0 -0
  11. package/package.json +2 -2
  12. package/plugins/ambient-agent/EventWatcher.js +33 -37
  13. package/plugins/ambient-agent/ExplorerLoop.js +338 -36
  14. package/plugins/ambient-agent/GoalManager.js +7 -3
  15. package/plugins/ambient-agent/StateStore.js +30 -1
  16. package/plugins/ambient-agent/constants.js +15 -1
  17. package/plugins/ambient-agent/index.js +26 -33
  18. package/plugins/coordinator-plugin.js +3 -3
  19. package/plugins/default-plugins.js +2 -2
  20. package/plugins/email/index.js +150 -36
  21. package/plugins/email/monitor.js +79 -5
  22. package/plugins/email/reply.js +15 -25
  23. package/plugins/extension-executor-plugin.js +160 -31
  24. package/plugins/file-system-plugin.js +57 -24
  25. package/plugins/memory-plugin.js +176 -64
  26. package/plugins/python-plugin-loader.js +79 -9
  27. package/plugins/scheduler-plugin.js +64 -24
  28. package/plugins/think-plugin.js +7 -2
  29. package/plugins/web-plugin.js +263 -4
  30. package/skills/ambient-agent/SKILL.md +342 -314
  31. package/src/core/agent-chat.js +64 -9
  32. package/src/core/agent.js +118 -59
  33. package/src/core/tool-registry.js +5 -5
  34. package/src/executors/mcp-executor.js +188 -26
  35. package/src/utils/id.js +5 -0
  36. package/system.md +3480 -0
  37. package/.agent/data/ambient/goals.json +0 -50
  38. package/.agent/data/ambient/memories.json +0 -7
  39. package/.agent/memory/core.md +0 -1
  40. package/.agent/memory/feedback/mnrsiuoc-e1ru74.md +0 -9
  41. package/.agent/memory/feedback/mnrt2mmz-98az6n.md +0 -9
  42. package/.agent/memory/feedback/mnrtqrhm-kxsicz.md +0 -9
  43. package/.agent/memory/feedback/mnrts8vg-i0ngzp.md +0 -15
  44. package/.agent/memory/feedback/mnrtt7jt-c0trb2.md +0 -9
  45. package/.agent/memory/feedback/mnruc2f0-5s52la.md +0 -16
  46. package/.agent/memory/feedback/mnrumbmx-63sa0v.md +0 -9
  47. package/.agent/memory/project/mnn93ogy-ypjn27.md +0 -9
  48. package/.agent/memory/project/mnn98fqy-5nhc1u.md +0 -25
  49. package/.agent/memory/project/mnrp7p5n-8enm2a.md +0 -31
  50. package/.agent/memory/project/mnrp9ifb-yynks0.md +0 -40
  51. package/.agent/memory/project/mnrpb3b8-f617s4.md +0 -25
  52. package/.agent/memory/project/mnrrmqgg-focprv.md +0 -9
  53. package/.agent/memory/project/mnrtykbh-6atsor.md +0 -9
  54. package/.agent/memory/project/mnru9jiu-kgau16.md +0 -35
  55. package/.agent/memory/reference/mnq3oenw-46haj6.md +0 -63
  56. package/.agent/memory/reference/mnq5qxm2-mjoooh.md +0 -116
  57. package/.agent/memory/reference/mnrnvpwo-rcqv9m.md +0 -52
  58. package/.agent/memory/reference/mnrovxvz-zy9xqm.md +0 -25
  59. package/.agent/memory/reference/mnroxabj-1b3930.md +0 -68
  60. package/.agent/memory/reference/mnrpjtlp-mnb9od.md +0 -35
  61. package/.agent/memory/reference/mnrps1x3-6b8xfm.md +0 -28
  62. package/.agent/memory/reference/mnrpt9ov-15er5w.md +0 -22
  63. package/.agent/memory/reference/mnrq82dn-y9tv9e.md +0 -50
  64. package/.agent/memory/reference/mnrqnr5v-v75drf.md +0 -34
  65. package/.agent/memory/reference/mnrrfzys-urudaf.md +0 -31
  66. package/.agent/memory/reference/mnrrocha-t0027n.md +0 -21
  67. package/.agent/memory/reference/mnrukklc-bxndsb.md +0 -35
  68. package/.agent/memory/user/mnm67t9m-x8rekk.md +0 -9
  69. package/.agent/memory/user/mnn5mmqh-w6aktx.md +0 -11
  70. package/.agent/memory/user/mnnbfhhn-dk1bd1.md +0 -22
  71. package/.agent/memory/user/mnrt39t8-8eosy0.md +0 -9
  72. package/foliko-cloud-rising.png +0 -0
  73. package/foliko-dawn-of-ai.png +0 -0
  74. package/foliko-mindful-observation.png +0 -0
  75. package/foliko-stellar-dreams.png +0 -0
  76. package/foliko-zen-jing.png +0 -0
  77. package/foliko-zen-kong.png +0 -0
  78. package/foliko-zen-wu.png +0 -0
  79. package/zen_karesansui.png +0 -0
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -54,7 +54,7 @@
54
54
  "@ai-sdk/openai-compatible": "^2.0.35",
55
55
  "@anthropic-ai/sdk": "^0.39.0",
56
56
  "@chnak/weixin-bot": "^1.2.7",
57
- "@chnak/zod-to-markdown": "1.0.3",
57
+ "@chnak/zod-to-markdown": "1.0.6",
58
58
  "@hono/node-server": "^1.19.11",
59
59
  "@larksuiteoapi/node-sdk": "^1.59.0",
60
60
  "@modelcontextprotocol/sdk": "^1.27.1",
@@ -20,14 +20,26 @@ class EventWatcher {
20
20
  */
21
21
  start() {
22
22
  const events = [
23
+ // 工具事件
24
+ ['tool:call', (data) => this._handleEvent('tool:call', data)],
23
25
  ['tool:result', (data) => this._handleEvent('tool:result', data)],
26
+ ['tool:error', (data) => this._handleEvent('tool:error', data)],
27
+ // Scheduler 事件
28
+ ['scheduler:task_created', (data) => this._handleEvent('scheduler:task_created', data)],
24
29
  ['scheduler:reminder', (data) => this._handleEvent('scheduler:reminder', data)],
25
- ['agent:message', (data) => this._handleEvent('agent:message', data)],
26
- ['think:thought_completed', (data) => this._handleEvent('think:thought_completed', data)],
27
30
  ['scheduler:task_completed', (data) => this._handleEvent('scheduler:task_completed', data)],
28
31
  ['scheduler:task_failed', (data) => this._handleEvent('scheduler:task_failed', data)],
32
+ // Agent/Think 事件
33
+ ['agent:message', (data) => this._handleEvent('agent:message', data)],
34
+ ['think:thought_completed', (data) => this._handleEvent('think:thought_completed', data)],
35
+ // Email/Webhook 事件
29
36
  ['email:received', (data) => this._handleEvent('email:received', data)],
30
- ['webhook:received', (data) => this._handleEvent('webhook:received', data)]
37
+ ['webhook:received', (data) => this._handleEvent('webhook:received', data)],
38
+ ['webhook:processed', (data) => this._handleEvent('webhook:processed', data)],
39
+ // Worker/Subagent 事件
40
+ ['worker:completed', (data) => this._handleEvent('worker:completed', data)],
41
+ ['worker:error', (data) => this._handleEvent('worker:error', data)],
42
+ ['subagent:chat:end', (data) => this._handleEvent('subagent:chat:end', data)]
31
43
  ]
32
44
 
33
45
  for (const [event, handler] of events) {
@@ -53,58 +65,42 @@ class EventWatcher {
53
65
  */
54
66
  _handleEvent(type, data) {
55
67
  const activeGoals = this._goalManager.getActiveGoals()
56
- // log.info(`_handleEvent: type=${type}, activeGoals=${activeGoals.length}`)
57
68
 
58
69
  for (const goal of activeGoals) {
59
- // 检查目标条件是否匹配此事件
60
- const isRelevant = this._isRelevantToGoal(goal, type, data)
61
- // log.info(`goal=${goal.id}, isRelevant=${isRelevant}, conditions=${JSON.stringify(goal.conditions)}`)
62
-
63
- if (isRelevant) {
64
- // 将 data 展平到事件对象中,方便通过 {{_event.xxx}} 访问
65
- this._goalManager.addEventToGoal(goal.id, { type, ...data })
66
- // log.info(`Event added to goal ${goal.id}`)
70
+ if (this._isRelevantToGoal(goal, type, data)) {
71
+ // 直接传入原样数据
72
+ this._goalManager.addEventToGoal(goal.id, { eventType: type, data })
67
73
  }
68
74
  }
69
75
 
70
- // 检查是否有 pending 目标需要激活
71
76
  const pendingGoals = this._goalManager.getPendingGoals()
72
77
  for (const goal of pendingGoals) {
73
78
  if (this._isRelevantToGoal(goal, type, data)) {
74
- // 激活目标
75
79
  this._goalManager.activateGoal(goal.id)
76
- // 添加事件
77
- this._goalManager.addEventToGoal(goal.id, { type, ...data })
78
- // log.info(`事件触发激活目标: ${goal.id} (事件: ${type})`)
80
+ this._goalManager.addEventToGoal(goal.id, { eventType: type, data })
79
81
  }
80
82
  }
81
83
  }
82
84
 
83
85
  /**
84
86
  * 检查事件是否与目标相关
85
- * @param {Object} goal - 目标对象
86
- * @param {string} eventType - 事件类型
87
- * @param {Object} data - 事件数据
88
- * @returns {boolean} 是否相关
89
87
  */
90
88
  _isRelevantToGoal(goal, eventType, data) {
91
- // 检查条件中是否有明确的事件类型
92
- if (goal.conditions && goal.conditions.events) {
93
- const allowedEvents = Array.isArray(goal.conditions.events)
94
- ? goal.conditions.events
95
- : [goal.conditions.events]
96
- if (!allowedEvents.includes(eventType)) {
97
- return false
98
- }
89
+ // 检查事件类型
90
+ if (goal.conditions?.events) {
91
+ const allowed = Array.isArray(goal.conditions.events) ? goal.conditions.events : [goal.conditions.events]
92
+ if (!allowed.includes(eventType)) return false
99
93
  }
100
- // 检查工具名称过滤器
101
- if (goal.conditions && goal.conditions.toolNames) {
102
- const toolNames = Array.isArray(goal.conditions.toolNames)
103
- ? goal.conditions.toolNames
104
- : [goal.conditions.toolNames]
105
- if (data && data.toolName && !toolNames.includes(data.toolName)) {
106
- return false
107
- }
94
+ // 检查工具名称
95
+ if (goal.conditions?.toolNames) {
96
+ const toolNames = Array.isArray(goal.conditions.toolNames) ? goal.conditions.toolNames : [goal.conditions.toolNames]
97
+ const eventName = data?.toolName || data?.name
98
+ if (!eventName) return true
99
+ // 统一下划线和冒号格式进行匹配
100
+ const normalize = (n) => n?.replace(/_/g, ':')
101
+ const normName = normalize(eventName)
102
+ const isMatch = toolNames.some(tn => normalize(tn) === normName || tn === eventName)
103
+ if (!isMatch) return false
108
104
  }
109
105
  return true
110
106
  }
@@ -176,10 +176,14 @@ class ExplorerLoop {
176
176
 
177
177
  async _processGoals() {
178
178
  const activeGoals = this._goalManager.getActiveGoals()
179
+ //log.info(`[ExplorerLoop] _processGoals: ${activeGoals.length} 个活跃目标, _lastActionTime=${this._lastActionTime}, cooldown=${this._config.cooldownPeriod}`)
179
180
 
180
181
  for (const goal of activeGoals) {
181
182
  // 检查冷却时间
182
- if (Date.now() - this._lastActionTime < this._config.cooldownPeriod) {
183
+ const timeSinceLastAction = Date.now() - this._lastActionTime
184
+ //log.info(`[ExplorerLoop] 检查 goal=${goal.id}, timeSinceLastAction=${timeSinceLastAction}`)
185
+ if (timeSinceLastAction < this._config.cooldownPeriod) {
186
+ //log.info(`[ExplorerLoop] 跳过 goal=${goal.id}, 还在冷却中`)
183
187
  continue
184
188
  }
185
189
 
@@ -191,15 +195,28 @@ class ExplorerLoop {
191
195
 
192
196
  // 事件驱动目标:只有在新事件时才执行 actions
193
197
  if (isEventDriven && !hasNewEvents) {
198
+ //log.info(`[ExplorerLoop] 跳过 goal=${goal.id}, 事件驱动但无新事件`)
194
199
  continue
195
200
  }
196
201
 
197
- // 获取下一个操作
202
+ log.info(`[ExplorerLoop] 开始执行 goal=${goal.id}, hasNewEvents=${hasNewEvents}, actions=${goal.actions?.length}`)
203
+
204
+ // 获取下一个操作(跳过已完成的 actions)
198
205
  if (goal.actions && goal.actions.length > 0) {
206
+ // 过滤掉已完成的 actions
207
+ const pendingActions = goal.actions.filter(a => !a.completed)
208
+ if (pendingActions.length === 0) {
209
+ log.info(`[ExplorerLoop] goal=${goal.id} 所有 actions 已完成,等待下一事件`)
210
+ this._resetGoalForNextEvent(goal)
211
+ continue
212
+ }
213
+
199
214
  // 如果有多个 action,按顺序执行所有 action
200
215
  if (goal.actions.length > 1) {
201
216
  const eventData = hasNewEvents ? goal.eventsReceived[0] : null
217
+ log.info(`[ExplorerLoop] 执行 ${goal.actions.length} 个 actions, eventData=${JSON.stringify(eventData?.data?.subject || 'none')}`)
202
218
  const results = await this._executeAllActions(goal.actions, eventData)
219
+ log.info(`[ExplorerLoop] actions 执行完成, results.length=${results.length}`)
203
220
 
204
221
  // 最后一个结果用于评估
205
222
  const lastResult = results.length > 0 ? results[results.length - 1].result : null
@@ -220,17 +237,39 @@ class ExplorerLoop {
220
237
  } else {
221
238
  // 事件驱动目标:不论结果如何,都重置并继续等待下一事件
222
239
  this._addActivity('actions_completed', { goalId: goal.id, actionCount: results.length })
223
- }
224
240
 
225
- // 执行操作后清除已处理的事件,并重置 actions 以处理下一事件
226
- if (hasNewEvents) {
227
- this._resetGoalForNextEvent(goal)
241
+ // 检查所有 actions 是否都已完成
242
+ const allDone = goal.actions.every(a => a.completed)
243
+ if (allDone && !goal.persistent) {
244
+ // 一次性目标:所有 actions 完成后直接完成
245
+ this._goalManager.completeGoal(goal.id)
246
+ this._addActivity('goal_completed', { goalId: goal.id, reason: '一次性任务执行完成' })
247
+ this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
248
+ } else if (hasNewEvents) {
249
+ // 持久化目标且有新事件,重置 actions
250
+ this._resetGoalForNextEvent(goal)
251
+ }
228
252
  }
229
253
  continue
230
254
  }
231
255
 
232
256
  // 单个 action,使用原有逻辑
233
- const action = goal.actions[0]
257
+ const action = goal.actions.find(a => !a.completed)
258
+ if (!action) {
259
+ // 所有 actions 已完成
260
+ log.info(`[ExplorerLoop] goal=${goal.id} 所有 actions 已完成`)
261
+ const allDone = goal.actions.every(a => a.completed)
262
+ if (allDone && !goal.persistent) {
263
+ // 一次性目标:所有 actions 完成后直接完成
264
+ this._goalManager.completeGoal(goal.id)
265
+ this._addActivity('goal_completed', { goalId: goal.id, reason: '一次性任务执行完成' })
266
+ this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
267
+ } else {
268
+ // 持久化目标:等待下一事件
269
+ this._resetGoalForNextEvent(goal)
270
+ }
271
+ continue
272
+ }
234
273
 
235
274
  // 循环检测(仅用于非事件驱动的操作)
236
275
  if (!hasNewEvents && this._reflector.checkLoopDetection(goal, action.id)) {
@@ -243,6 +282,9 @@ class ExplorerLoop {
243
282
  // 执行操作
244
283
  const result = await this._executeAction(action, hasNewEvents ? goal.eventsReceived[0] : null)
245
284
 
285
+ // 标记 action 为已完成
286
+ action.completed = true
287
+
246
288
  // 评估结果
247
289
  const outcome = this._reflector.evaluateOutcome(goal, result)
248
290
 
@@ -256,17 +298,20 @@ class ExplorerLoop {
256
298
  this._addActivity('goal_failed', { goalId: goal.id, reason: '达到最大尝试次数' })
257
299
  // 通知用户目标失败
258
300
  this._notifyUser('目标失败', `目标 "${goal.title}" 失败:达到最大尝试次数`, 'error')
259
- } else if (outcome.actionTaken) {
260
- // 从队列中移除已完成的操作
261
- if (!hasNewEvents) {
262
- goal.actions.shift()
263
- }
301
+ } else {
264
302
  this._addActivity('action_executed', { goalId: goal.id, action: action.id, hasEvents: hasNewEvents })
265
- }
266
303
 
267
- // 执行操作后清除已处理的事件,并重置 actions 以处理下一事件
268
- if (hasNewEvents) {
269
- this._resetGoalForNextEvent(goal)
304
+ // 检查所有 actions 是否都已完成
305
+ const allDone = goal.actions.every(a => a.completed)
306
+ if (allDone && !goal.persistent) {
307
+ // 一次性目标:所有 actions 完成后直接完成
308
+ this._goalManager.completeGoal(goal.id)
309
+ this._addActivity('goal_completed', { goalId: goal.id, reason: '一次性任务执行完成' })
310
+ this._notifyUser('目标完成', `目标 "${goal.title}" 已完成`, 'success')
311
+ } else if (hasNewEvents) {
312
+ // 持久化目标且有新事件,重置 actions
313
+ this._resetGoalForNextEvent(goal)
314
+ }
270
315
  }
271
316
  }
272
317
  }
@@ -288,15 +333,15 @@ class ExplorerLoop {
288
333
  }
289
334
 
290
335
  /**
291
- * 重置目标以处理下一事件
336
+ * 重置目标以处理下一事件(仅用于持久化目标)
292
337
  */
293
338
  _resetGoalForNextEvent(goal) {
294
339
  goal.eventsReceived = []
295
- // 重置 actions 为原始 actions,以便处理下一事件
296
- if (goal._originalActions) {
297
- goal.actions = goal._originalActions.slice()
340
+ // 重置所有 actions 的完成标记
341
+ for (const action of goal.actions) {
342
+ action.completed = false
298
343
  }
299
- this._goalManager.updateGoal(goal.id, { eventsReceived: [], actions: goal.actions })
344
+ this._goalManager.updateGoal(goal.id, { eventsReceived: [] })
300
345
  }
301
346
 
302
347
  // ============================================================================
@@ -335,6 +380,18 @@ class ExplorerLoop {
335
380
  // 将 action 转换为 step 配置
336
381
  const step = this._actionToStep(action)
337
382
 
383
+ // 将 _event 注入到 step.args 中,供工具使用
384
+ // 统一使用 eventData.data 作为事件数据源
385
+ const effectiveEventData = eventData || context?.variables?._eventRaw
386
+ if (effectiveEventData) {
387
+ step.args = step.args || {}
388
+ // 从 eventData.data 中提取邮件对象(email 事件的数据在 data.email 中)
389
+ const eventObj = effectiveEventData?.data?.email || effectiveEventData?.data || effectiveEventData
390
+ // 统一注入到 _event,让 ${event.xxx} 可以直接访问
391
+ step.args._event = eventObj
392
+ step.args._eventRaw = effectiveEventData
393
+ }
394
+
338
395
  // 解析 step.args 中的变量引用(支持 ${actionId.output} 格式)
339
396
  if (step.args) {
340
397
  step.args = this._resolveActionArgs(step.args, context)
@@ -348,7 +405,21 @@ class ExplorerLoop {
348
405
  return await this._executeWithAmbientAgent(step, context)
349
406
  }
350
407
 
351
- // 其他类型使用 StepExecutor 直接执行
408
+ // 工具类型
409
+ if (step.type === 'tool') {
410
+ if (step.tool) {
411
+ // 工具已指定,通过 ext_call 执行(plugin 可选)
412
+ const extResult = await this._executeViaExtCall(step.tool, step.args || {}, step.plugin)
413
+ if (extResult && (extResult.success !== false || !extResult.error?.includes('not found'))) {
414
+ return extResult
415
+ }
416
+ log.info(`[ExplorerLoop] ext_call 回退到 StepExecutor: ${step.tool}`)
417
+ } else {
418
+ // 工具未指定,让 LLM 选择工具
419
+ return await this._executeWithAmbientAgent(step, context)
420
+ }
421
+ }
422
+
352
423
  const result = await this._stepExecutor.executeStep(step, context)
353
424
  return result
354
425
  } catch (err) {
@@ -387,24 +458,23 @@ class ExplorerLoop {
387
458
  if (path.startsWith('event.')) {
388
459
  // ${event.xxx} 引用事件数据
389
460
  let eventPath = path.replace(/^event\./, '')
390
- // 优先在 eventData.data 中查找(因为事件数据通常存储在 data 字段)
461
+ // 直接在 eventData 中查找(最常见的情况)
462
+ let val = this._getNestedValue(eventData, eventPath)
463
+ if (val !== undefined) return val
464
+ // 兼容:如果 eventData.data 存在,尝试从中查找
391
465
  if (eventData?.data) {
392
- let val = this._getNestedValue(eventData.data, eventPath)
466
+ val = this._getNestedValue(eventData.data, eventPath)
393
467
  if (val !== undefined) return val
394
468
  }
395
- // 再尝试直接在 eventData 中查找
396
- let val = this._getNestedValue(eventData, eventPath)
397
- if (val !== undefined) return val
398
- // 兼容:如果 path 是 event.data.xxx 但 eventData 没有 data,尝试跳过一层
399
- if (eventPath.startsWith('data.')) {
400
- eventPath = eventPath.replace(/^data\./, '')
401
- val = this._getNestedValue(eventData, eventPath)
469
+ // 兼容:如果 eventData.email 存在(邮件事件,字段直接在 email 对象下),查找 eventData.email
470
+ if (eventData?.email) {
471
+ val = this._getNestedValue(eventData.email, eventPath)
402
472
  if (val !== undefined) return val
403
473
  }
404
- // 特殊回退:如果查找 body 但找不到,尝试 text(邮件数据中用 text)
405
- if (eventPath === 'body' || eventPath === 'text') {
406
- const textVal = this._getNestedValue(eventData?.data, 'text')
407
- if (textVal !== undefined) return textVal
474
+ // 兼容:如果 eventData.data.email 存在(嵌套的邮件事件),直接在 email 对象中查找
475
+ if (eventData?.data?.email) {
476
+ val = this._getNestedValue(eventData.data.email, eventPath)
477
+ if (val !== undefined) return val
408
478
  }
409
479
  return match
410
480
  }
@@ -477,11 +547,19 @@ class ExplorerLoop {
477
547
  }
478
548
 
479
549
  for (const action of actions) {
550
+ // 跳过已完成的 action
551
+ if (action.completed) {
552
+ continue
553
+ }
554
+
480
555
  // 执行前更新 stepOutputs
481
556
  context.variables._stepOutputs = stepOutputs
482
557
 
483
558
  const result = await this._executeAction(action, eventData, context)
484
559
 
560
+ // 标记 action 为已完成
561
+ action.completed = true
562
+
485
563
  // 保存结果:使用 action id 作为键
486
564
  if (action.id) {
487
565
  stepOutputs[action.id] = result
@@ -501,6 +579,18 @@ class ExplorerLoop {
501
579
  return results
502
580
  }
503
581
 
582
+ /**
583
+ * 获取所有扩展工具的描述(供 LLM 选择)
584
+ */
585
+ _getExtensionsDescription() {
586
+ const extExec = this._framework.pluginManager?.get('extension-executor')
587
+ if (!extExec || !extExec._buildExtensionsDescription) {
588
+ return '无可用的扩展工具'
589
+ }
590
+ const desc = extExec._buildExtensionsDescription()
591
+ return desc || '无可用的扩展工具'
592
+ }
593
+
504
594
  /**
505
595
  * 使用 ambient 子 agent 执行 AI 相关操作
506
596
  */
@@ -550,6 +640,94 @@ class ExplorerLoop {
550
640
  }
551
641
  }
552
642
 
643
+ if (step.type === 'tool') {
644
+ // 如果工具已指定,直接执行
645
+ if (step.tool) {
646
+ return await this._executeViaExtCall(step.tool, step.args || {})
647
+ }
648
+
649
+ // 工具未指定,让 LLM 根据上下文选择
650
+ const eventData = context.variables._event
651
+ let eventContextStr = '无事件上下文'
652
+ if (eventData) {
653
+ eventContextStr = `**事件数据结构(从 event.xxx 引用任何字段)**:\n\`\`\`json\n${JSON.stringify(eventData, null, 2)}\n\`\`\``
654
+ }
655
+
656
+ // 上一步的输出结果(可用于引用)
657
+ const lastResult = context.variables._stepOutputs?._lastResult
658
+ let lastResultStr = ''
659
+ if (lastResult) {
660
+ lastResultStr = `**上一步结果(从 result.xxx 引用任何字段)**:\n\`\`\`json\n${JSON.stringify(lastResult, null, 2)}\n\`\`\``
661
+ }
662
+
663
+ const extensionsInfo = this._getExtensionsDescription()
664
+
665
+ const prompt = `你是 Ambient Agent,需要为以下目标选择一个合适的工具来执行。
666
+
667
+ **目标**: ${step.name || '根据事件自动决定执行什么操作'}
668
+
669
+ ${eventContextStr}
670
+
671
+ ${lastResultStr}
672
+
673
+ ## 可用的扩展工具及其参数要求:
674
+ ${extensionsInfo}
675
+
676
+ ## 如何决定参数传递:
677
+ 1. 查看上方的**事件数据结构**,找到需要的字段
678
+ 2. 查看工具的**参数要求**(parameters 或 properties 下列出的字段)
679
+ 3. 根据字段名称和含义,判断应该传递什么值
680
+ 4. 使用 \${event.xxx} 或 \${result.xxx} 引用对应字段
681
+
682
+ **示例**:
683
+ 如果事件中有 \`from: "user@example.com"\`,而工具需要 \`to\` 参数,
684
+ 则传递 \`"to": "\${event.from}"\`
685
+
686
+ **返回格式**:
687
+ \`\`\`json
688
+ {
689
+ "plugin": "插件名",
690
+ "tool": "工具名",
691
+ "args": {
692
+ "参数名": "\${event.字段名}"
693
+ }
694
+ }
695
+ \`\`\`
696
+
697
+ 如果不需要执行任何工具,返回:\`\`\`json
698
+ {"plugin": null, "tool": null, "args": {}}
699
+ \`\`\`
700
+
701
+ 请直接返回 JSON 代码块。`
702
+
703
+ try {
704
+ const response = await this._ambientAgent.pushMessage(prompt)
705
+ const responseText = typeof response === 'string' ? response : JSON.stringify(response)
706
+
707
+ // 尝试解析 LLM 返回的 JSON
708
+ let toolChoice = null
709
+ try {
710
+ // 提取 JSON(可能在 markdown 代码块中)
711
+ const jsonMatch = responseText.match(/```json\n?([\s\S]*?)\n?```/) || responseText.match(/(\{[\s\S]*\})/)
712
+ if (jsonMatch) {
713
+ toolChoice = JSON.parse(jsonMatch[1])
714
+ }
715
+ } catch (e) {
716
+ log.warn(`[ExplorerLoop] 解析 LLM 工具选择失败: ${e.message}`)
717
+ return { success: false, error: `LLM 返回格式错误: ${responseText.slice(0, 200)}` }
718
+ }
719
+
720
+ if (toolChoice && toolChoice.tool) {
721
+ log.info(`[ExplorerLoop] LLM 选择工具: plugin=${toolChoice.plugin}, tool=${toolChoice.tool}, args=${JSON.stringify(toolChoice.args)}`)
722
+ return await this._executeViaExtCall(toolChoice.tool, toolChoice.args || {}, toolChoice.plugin)
723
+ } else {
724
+ return { success: true, message: 'LLM 选择不执行任何工具', response: responseText }
725
+ }
726
+ } catch (err) {
727
+ return { success: false, error: err.message }
728
+ }
729
+ }
730
+
553
731
  return { success: false, error: `未知 AI 操作类型: ${step.type}` }
554
732
  }
555
733
 
@@ -560,7 +738,8 @@ class ExplorerLoop {
560
738
  const step = {
561
739
  id: action.id || `action_${Date.now()}`,
562
740
  type: action.type,
563
- name: action.name || action.id
741
+ name: action.name || action.id,
742
+ plugin: action.plugin // 插件名称(如 email, gate-trading)
564
743
  }
565
744
 
566
745
  switch (action.type) {
@@ -585,6 +764,129 @@ class ExplorerLoop {
585
764
  // 辅助方法
586
765
  // ============================================================================
587
766
 
767
+ /**
768
+ * 通过 ext_call 执行插件工具
769
+ * 支持所有通过 extension-executor 注册的工具:email, mcp, python, extension 等
770
+ * @param {string} toolName - 工具名称(如 email_auto_reply, mcp_github_search)
771
+ * @param {Object} args - 工具参数
772
+ * @param {string} [plugin] - 插件名称(如指定则直接使用,否则从 toolName 推断)
773
+ * @returns {Promise<Object>} 执行结果
774
+ */
775
+ async _executeViaExtCall(toolName, args, plugin) {
776
+ // 尝试多种可能的插件前缀
777
+ const pluginPrefixes = [
778
+ ['email_', 'email'],
779
+ ['scheduler_', 'scheduler'],
780
+ ['memory_', 'memory'],
781
+ ['web_', 'web'],
782
+ ['shell_', 'shell'],
783
+ ['python_', 'python'],
784
+ ['think_', 'think'],
785
+ ['mcp_', 'mcp'],
786
+ ['ext_', 'extension'],
787
+ ]
788
+
789
+ // 如果指定了 plugin,直接使用
790
+ if (plugin) {
791
+ log.info(`[ExplorerLoop] 通过 ext_call 执行: plugin=${plugin}, tool=${toolName}`)
792
+ try {
793
+ const result = await this._framework.executeTool('ext_call', {
794
+ plugin,
795
+ tool: toolName,
796
+ args
797
+ })
798
+ return result
799
+ } catch (err) {
800
+ log.warn(`[ExplorerLoop] ext_call 错误: ${err.message}`)
801
+ return { success: false, error: err.message }
802
+ }
803
+ }
804
+
805
+ // 先尝试从工具名推断的插件
806
+ const inferredPlugin = this._inferPluginName(toolName)
807
+ log.info(`[ExplorerLoop] 通过 ext_call 执行: plugin=${inferredPlugin}, tool=${toolName}`)
808
+
809
+ try {
810
+ const result = await this._framework.executeTool('ext_call', {
811
+ plugin: inferredPlugin,
812
+ tool: toolName,
813
+ args
814
+ })
815
+ return result
816
+ } catch (err) {
817
+ // 如果错误不是 "not found",返回错误
818
+ if (!err.message.includes("not found") && !err.message.includes("不存在")) {
819
+ log.warn(`[ExplorerLoop] ext_call 错误: ${err.message}`)
820
+ return { success: false, error: err.message }
821
+ }
822
+ // 否则尝试其他可能的插件
823
+ }
824
+
825
+ // 尝试其他可能的插件前缀
826
+ for (const [prefix, extPlugin] of pluginPrefixes) {
827
+ if (toolName.startsWith(prefix) && extPlugin !== inferredPlugin) {
828
+ try {
829
+ const result = await this._framework.executeTool('ext_call', {
830
+ plugin: extPlugin,
831
+ tool: toolName,
832
+ args
833
+ })
834
+ log.info(`[ExplorerLoop] ext_call 成功: plugin=${extPlugin}, tool=${toolName}`)
835
+ return result
836
+ } catch (err) {
837
+ continue
838
+ }
839
+ }
840
+ }
841
+
842
+ // 尝试不带前缀的工具名(某些扩展可能用其他命名)
843
+ const shortName = toolName.replace(/^[a-z]+_/, '')
844
+ for (const [prefix, plugin] of pluginPrefixes) {
845
+ try {
846
+ const result = await this._framework.executeTool('ext_call', {
847
+ plugin,
848
+ tool: shortName,
849
+ args
850
+ })
851
+ log.info(`[ExplorerLoop] ext_call 成功(短名): plugin=${plugin}, tool=${shortName}`)
852
+ return result
853
+ } catch (err) {
854
+ continue
855
+ }
856
+ }
857
+
858
+ return { success: false, error: `工具 ${toolName} 未找到` }
859
+ }
860
+
861
+ /**
862
+ * 从工具名称推断插件名称
863
+ * @param {string} toolName - 工具名称
864
+ * @returns {string} 插件名称
865
+ */
866
+ _inferPluginName(toolName) {
867
+ // 常见插件工具前缀映射
868
+ const pluginPrefixes = {
869
+ email_: 'email',
870
+ scheduler_: 'scheduler',
871
+ memory_: 'memory',
872
+ web_: 'web',
873
+ shell_: 'shell',
874
+ python_: 'python',
875
+ think_: 'think',
876
+ mcp_: 'mcp',
877
+ ext_: 'extension',
878
+ }
879
+
880
+ for (const [prefix, plugin] of Object.entries(pluginPrefixes)) {
881
+ if (toolName.startsWith(prefix)) {
882
+ return plugin
883
+ }
884
+ }
885
+
886
+ // 默认返回 'extension'(extension-executor 默认命名空间)
887
+ return 'extension'
888
+ }
889
+
588
890
  _getActiveAgent() {
589
891
  if (this._framework._mainAgent) {
590
892
  return this._framework._mainAgent
@@ -33,7 +33,11 @@ class GoalManager {
33
33
  * @returns {Object} 创建的目标
34
34
  */
35
35
  createGoal(goalData) {
36
- const actions = goalData.actions || []
36
+ const actions = (goalData.actions || []).map((action, index) => ({
37
+ ...action,
38
+ completed: false,
39
+ order: index
40
+ }))
37
41
  const goal = {
38
42
  id: generateId(),
39
43
  title: goalData.title || '未命名目标',
@@ -41,8 +45,8 @@ class GoalManager {
41
45
  priority: goalData.priority || 5,
42
46
  state: GoalState.PENDING,
43
47
  actions: actions,
44
- _originalActions: actions.slice(), // 保存原始 actions,用于事件驱动目标重置
45
48
  conditions: goalData.conditions || {},
49
+ persistent: goalData.persistent !== false, // 默认 true,false 为一次性
46
50
  createdAt: new Date(),
47
51
  updatedAt: new Date(),
48
52
  activatedAt: null,
@@ -187,7 +191,7 @@ class GoalManager {
187
191
  // 需要提取 type 和 data(email 对象)
188
192
  goal.eventsReceived.push({
189
193
  event: event.type,
190
- data: event.data || event.email || event,
194
+ data: event.data || event,
191
195
  timestamp: new Date()
192
196
  })
193
197
  this._persist()