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.
- package/.agent/data/email/processed-emails.json +1 -0
- package/.agent/data/plugins-state.json +5 -1
- package/.agent/data/web/web-config.json +5 -0
- package/.agent/memory/feedback/mnt7jrlt-d67qs7.md +15 -0
- package/.agent/memory/feedback/mnt88ja3-al4fuy.md +9 -0
- package/.agent/plugins/test-plugin.py +108 -0
- package/.agent/sessions/cli_default.json +514 -5298
- package/.claude/settings.local.json +2 -1
- package/SPEC.md +735 -696
- package/output/zen_silence.png +0 -0
- package/package.json +2 -2
- package/plugins/ambient-agent/EventWatcher.js +33 -37
- package/plugins/ambient-agent/ExplorerLoop.js +338 -36
- package/plugins/ambient-agent/GoalManager.js +7 -3
- package/plugins/ambient-agent/StateStore.js +30 -1
- package/plugins/ambient-agent/constants.js +15 -1
- package/plugins/ambient-agent/index.js +26 -33
- package/plugins/coordinator-plugin.js +3 -3
- package/plugins/default-plugins.js +2 -2
- package/plugins/email/index.js +150 -36
- package/plugins/email/monitor.js +79 -5
- package/plugins/email/reply.js +15 -25
- package/plugins/extension-executor-plugin.js +160 -31
- package/plugins/file-system-plugin.js +57 -24
- package/plugins/memory-plugin.js +176 -64
- package/plugins/python-plugin-loader.js +79 -9
- package/plugins/scheduler-plugin.js +64 -24
- package/plugins/think-plugin.js +7 -2
- package/plugins/web-plugin.js +263 -4
- package/skills/ambient-agent/SKILL.md +342 -314
- package/src/core/agent-chat.js +64 -9
- package/src/core/agent.js +118 -59
- package/src/core/tool-registry.js +5 -5
- package/src/executors/mcp-executor.js +188 -26
- package/src/utils/id.js +5 -0
- package/system.md +3480 -0
- package/.agent/data/ambient/goals.json +0 -50
- package/.agent/data/ambient/memories.json +0 -7
- package/.agent/memory/core.md +0 -1
- package/.agent/memory/feedback/mnrsiuoc-e1ru74.md +0 -9
- package/.agent/memory/feedback/mnrt2mmz-98az6n.md +0 -9
- package/.agent/memory/feedback/mnrtqrhm-kxsicz.md +0 -9
- package/.agent/memory/feedback/mnrts8vg-i0ngzp.md +0 -15
- package/.agent/memory/feedback/mnrtt7jt-c0trb2.md +0 -9
- package/.agent/memory/feedback/mnruc2f0-5s52la.md +0 -16
- package/.agent/memory/feedback/mnrumbmx-63sa0v.md +0 -9
- package/.agent/memory/project/mnn93ogy-ypjn27.md +0 -9
- package/.agent/memory/project/mnn98fqy-5nhc1u.md +0 -25
- package/.agent/memory/project/mnrp7p5n-8enm2a.md +0 -31
- package/.agent/memory/project/mnrp9ifb-yynks0.md +0 -40
- package/.agent/memory/project/mnrpb3b8-f617s4.md +0 -25
- package/.agent/memory/project/mnrrmqgg-focprv.md +0 -9
- package/.agent/memory/project/mnrtykbh-6atsor.md +0 -9
- package/.agent/memory/project/mnru9jiu-kgau16.md +0 -35
- package/.agent/memory/reference/mnq3oenw-46haj6.md +0 -63
- package/.agent/memory/reference/mnq5qxm2-mjoooh.md +0 -116
- package/.agent/memory/reference/mnrnvpwo-rcqv9m.md +0 -52
- package/.agent/memory/reference/mnrovxvz-zy9xqm.md +0 -25
- package/.agent/memory/reference/mnroxabj-1b3930.md +0 -68
- package/.agent/memory/reference/mnrpjtlp-mnb9od.md +0 -35
- package/.agent/memory/reference/mnrps1x3-6b8xfm.md +0 -28
- package/.agent/memory/reference/mnrpt9ov-15er5w.md +0 -22
- package/.agent/memory/reference/mnrq82dn-y9tv9e.md +0 -50
- package/.agent/memory/reference/mnrqnr5v-v75drf.md +0 -34
- package/.agent/memory/reference/mnrrfzys-urudaf.md +0 -31
- package/.agent/memory/reference/mnrrocha-t0027n.md +0 -21
- package/.agent/memory/reference/mnrukklc-bxndsb.md +0 -35
- package/.agent/memory/user/mnm67t9m-x8rekk.md +0 -9
- package/.agent/memory/user/mnn5mmqh-w6aktx.md +0 -11
- package/.agent/memory/user/mnnbfhhn-dk1bd1.md +0 -22
- package/.agent/memory/user/mnrt39t8-8eosy0.md +0 -9
- package/foliko-cloud-rising.png +0 -0
- package/foliko-dawn-of-ai.png +0 -0
- package/foliko-mindful-observation.png +0 -0
- package/foliko-stellar-dreams.png +0 -0
- package/foliko-zen-jing.png +0 -0
- package/foliko-zen-kong.png +0 -0
- package/foliko-zen-wu.png +0 -0
- 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.
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
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
|
|
93
|
-
const
|
|
94
|
-
|
|
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
|
|
102
|
-
const toolNames = Array.isArray(goal.conditions.toolNames)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
340
|
+
// 重置所有 actions 的完成标记
|
|
341
|
+
for (const action of goal.actions) {
|
|
342
|
+
action.completed = false
|
|
298
343
|
}
|
|
299
|
-
this._goalManager.updateGoal(goal.id, { eventsReceived: []
|
|
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
|
-
//
|
|
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
|
-
//
|
|
461
|
+
// 直接在 eventData 中查找(最常见的情况)
|
|
462
|
+
let val = this._getNestedValue(eventData, eventPath)
|
|
463
|
+
if (val !== undefined) return val
|
|
464
|
+
// 兼容:如果 eventData.data 存在,尝试从中查找
|
|
391
465
|
if (eventData?.data) {
|
|
392
|
-
|
|
466
|
+
val = this._getNestedValue(eventData.data, eventPath)
|
|
393
467
|
if (val !== undefined) return val
|
|
394
468
|
}
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
//
|
|
405
|
-
if (
|
|
406
|
-
|
|
407
|
-
if (
|
|
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
|
|
194
|
+
data: event.data || event,
|
|
191
195
|
timestamp: new Date()
|
|
192
196
|
})
|
|
193
197
|
this._persist()
|