foliko 1.0.53 → 1.0.55
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 +141 -131
- package/CLAUDE.md +106 -0
- package/Dockerfile +2 -2
- package/cli/src/index.js +6 -3
- package/cli/src/ui/chat-ui.js +1 -1
- package/examples/ambient-example.js +196 -0
- package/package.json +1 -1
- package/plugins/ambient-agent-plugin.js +1134 -0
- package/plugins/default-plugins.js +38 -32
- package/plugins/email.js +484 -25
- package/plugins/feishu-plugin.js +2 -0
- package/plugins/file-system-plugin.js +57 -1
- package/plugins/python-executor-plugin.js +1 -1
- package/plugins/python-plugin-loader.js +2 -2
- package/plugins/subagent-plugin.js +25 -25
- package/plugins/telegram-plugin.js +3 -0
- package/plugins/weixin-plugin.js +2 -0
- package/src/capabilities/skill-manager.js +230 -2
- package/src/core/agent.js +19 -14
- package/src/core/plugin-manager.js +2 -2
- package/src/core/provider.js +0 -1
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient Agent Plugin
|
|
3
|
+
* Continuous background agent that monitors events and takes proactive actions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Plugin } = require('../src/core/plugin-base')
|
|
7
|
+
const { z } = require('zod')
|
|
8
|
+
const fs = require('fs')
|
|
9
|
+
const path = require('path')
|
|
10
|
+
|
|
11
|
+
// Goal lifecycle states
|
|
12
|
+
const GoalState = {
|
|
13
|
+
PENDING: 'pending',
|
|
14
|
+
ACTIVE: 'active',
|
|
15
|
+
COMPLETED: 'completed',
|
|
16
|
+
FAILED: 'failed'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Generate unique IDs
|
|
20
|
+
function generateId() {
|
|
21
|
+
if (require('crypto').randomUUID) {
|
|
22
|
+
return require('crypto').randomUUID()
|
|
23
|
+
}
|
|
24
|
+
return `goal_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// StateStore - Persists goals and memories to .agent/data/ambient/*.json
|
|
29
|
+
// ============================================================================
|
|
30
|
+
class StateStore {
|
|
31
|
+
constructor(persistencePath) {
|
|
32
|
+
this._persistencePath = persistencePath
|
|
33
|
+
this._ensureDir()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_ensureDir() {
|
|
37
|
+
if (!fs.existsSync(this._persistencePath)) {
|
|
38
|
+
fs.mkdirSync(this._persistencePath, { recursive: true })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_getGoalsPath() {
|
|
43
|
+
return path.join(this._persistencePath, 'goals.json')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_getMemoriesPath() {
|
|
47
|
+
return path.join(this._persistencePath, 'memories.json')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
saveGoals(goals) {
|
|
51
|
+
try {
|
|
52
|
+
fs.writeFileSync(this._getGoalsPath(), JSON.stringify(goals, null, 2))
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('[Ambient] Failed to save goals:', err.message)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
loadGoals() {
|
|
59
|
+
try {
|
|
60
|
+
const filePath = this._getGoalsPath()
|
|
61
|
+
if (fs.existsSync(filePath)) {
|
|
62
|
+
const data = fs.readFileSync(filePath, 'utf-8')
|
|
63
|
+
return JSON.parse(data)
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error('[Ambient] Failed to load goals:', err.message)
|
|
67
|
+
}
|
|
68
|
+
return []
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
saveMemories(memories) {
|
|
72
|
+
try {
|
|
73
|
+
fs.writeFileSync(this._getMemoriesPath(), JSON.stringify(memories, null, 2))
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error('[Ambient] Failed to save memories:', err.message)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
loadMemories() {
|
|
80
|
+
try {
|
|
81
|
+
const filePath = this._getMemoriesPath()
|
|
82
|
+
if (fs.existsSync(filePath)) {
|
|
83
|
+
const data = fs.readFileSync(filePath, 'utf-8')
|
|
84
|
+
return JSON.parse(data)
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error('[Ambient] Failed to load memories:', err.message)
|
|
88
|
+
}
|
|
89
|
+
return []
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// GoalManager - Manages goals with lifecycle states
|
|
95
|
+
// ============================================================================
|
|
96
|
+
class GoalManager {
|
|
97
|
+
constructor(stateStore) {
|
|
98
|
+
this._goals = new Map()
|
|
99
|
+
this._stateStore = stateStore
|
|
100
|
+
this._loadGoals()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_loadGoals() {
|
|
104
|
+
const savedGoals = this._stateStore.loadGoals()
|
|
105
|
+
for (const goal of savedGoals) {
|
|
106
|
+
this._goals.set(goal.id, goal)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_persist() {
|
|
111
|
+
const goalsArray = Array.from(this._goals.values())
|
|
112
|
+
this._stateStore.saveGoals(goalsArray)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
createGoal(goalData) {
|
|
116
|
+
const goal = {
|
|
117
|
+
id: generateId(),
|
|
118
|
+
title: goalData.title || 'Untitled Goal',
|
|
119
|
+
description: goalData.description || '',
|
|
120
|
+
priority: goalData.priority || 5,
|
|
121
|
+
state: GoalState.PENDING,
|
|
122
|
+
actions: goalData.actions || [],
|
|
123
|
+
conditions: goalData.conditions || {},
|
|
124
|
+
createdAt: new Date(),
|
|
125
|
+
updatedAt: new Date(),
|
|
126
|
+
activatedAt: null,
|
|
127
|
+
completedAt: null,
|
|
128
|
+
failedAt: null,
|
|
129
|
+
attempts: 0,
|
|
130
|
+
maxAttempts: goalData.maxAttempts || 10,
|
|
131
|
+
consecutiveSameActions: 0,
|
|
132
|
+
lastActionId: null,
|
|
133
|
+
eventsReceived: []
|
|
134
|
+
}
|
|
135
|
+
this._goals.set(goal.id, goal)
|
|
136
|
+
this._persist()
|
|
137
|
+
return goal
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getGoal(id) {
|
|
141
|
+
return this._goals.get(id)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getAllGoals() {
|
|
145
|
+
return Array.from(this._goals.values())
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getActiveGoals() {
|
|
149
|
+
return Array.from(this._goals.values()).filter(g => g.state === GoalState.ACTIVE)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getPendingGoals() {
|
|
153
|
+
return Array.from(this._goals.values()).filter(g => g.state === GoalState.PENDING)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
updateGoal(id, updates) {
|
|
157
|
+
const goal = this._goals.get(id)
|
|
158
|
+
if (!goal) return null
|
|
159
|
+
Object.assign(goal, updates, { updatedAt: new Date() })
|
|
160
|
+
this._persist()
|
|
161
|
+
return goal
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
activateGoal(id) {
|
|
165
|
+
const goal = this._goals.get(id)
|
|
166
|
+
if (!goal || goal.state !== GoalState.PENDING) return null
|
|
167
|
+
goal.state = GoalState.ACTIVE
|
|
168
|
+
goal.activatedAt = new Date()
|
|
169
|
+
goal.updatedAt = new Date()
|
|
170
|
+
this._persist()
|
|
171
|
+
return goal
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
completeGoal(id) {
|
|
175
|
+
const goal = this._goals.get(id)
|
|
176
|
+
if (!goal) return null
|
|
177
|
+
goal.state = GoalState.COMPLETED
|
|
178
|
+
goal.completedAt = new Date()
|
|
179
|
+
goal.updatedAt = new Date()
|
|
180
|
+
this._persist()
|
|
181
|
+
return goal
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
failGoal(id, reason) {
|
|
185
|
+
const goal = this._goals.get(id)
|
|
186
|
+
if (!goal) return null
|
|
187
|
+
goal.state = GoalState.FAILED
|
|
188
|
+
goal.failedAt = new Date()
|
|
189
|
+
goal.failureReason = reason
|
|
190
|
+
goal.updatedAt = new Date()
|
|
191
|
+
this._persist()
|
|
192
|
+
return goal
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
deleteGoal(id) {
|
|
196
|
+
const deleted = this._goals.delete(id)
|
|
197
|
+
if (deleted) this._persist()
|
|
198
|
+
return deleted
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
addEventToGoal(goalId, event) {
|
|
202
|
+
const goal = this._goals.get(goalId)
|
|
203
|
+
if (!goal) return
|
|
204
|
+
goal.eventsReceived.push({
|
|
205
|
+
event: event.type, // "email:received"
|
|
206
|
+
data: event.data, // the actual event data (contains email object)
|
|
207
|
+
timestamp: new Date()
|
|
208
|
+
})
|
|
209
|
+
this._persist()
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// EventWatcher - Subscribes to framework events, filters relevant ones
|
|
215
|
+
// ============================================================================
|
|
216
|
+
class EventWatcher {
|
|
217
|
+
constructor(goalManager, framework) {
|
|
218
|
+
this._goalManager = goalManager
|
|
219
|
+
this._framework = framework
|
|
220
|
+
this._handlers = []
|
|
221
|
+
this._relevantEvents = new Set([
|
|
222
|
+
'tool:result',
|
|
223
|
+
'scheduler:reminder',
|
|
224
|
+
'agent:message',
|
|
225
|
+
'think:thought_completed',
|
|
226
|
+
'scheduler:task_completed',
|
|
227
|
+
'scheduler:task_failed',
|
|
228
|
+
'email:received',
|
|
229
|
+
'webhook:received'
|
|
230
|
+
])
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
start() {
|
|
234
|
+
this._handlers.push(
|
|
235
|
+
this._framework.on('tool:result', (data) => this._handleEvent('tool:result', data)),
|
|
236
|
+
this._framework.on('scheduler:reminder', (data) => this._handleEvent('scheduler:reminder', data)),
|
|
237
|
+
this._framework.on('agent:message', (data) => this._handleEvent('agent:message', data)),
|
|
238
|
+
this._framework.on('think:thought_completed', (data) => this._handleEvent('think:thought_completed', data)),
|
|
239
|
+
this._framework.on('scheduler:task_completed', (data) => this._handleEvent('scheduler:task_completed', data)),
|
|
240
|
+
this._framework.on('scheduler:task_failed', (data) => this._handleEvent('scheduler:task_failed', data)),
|
|
241
|
+
this._framework.on('email:received', (data) => this._handleEvent('email:received', data)),
|
|
242
|
+
this._framework.on('webhook:received', (data) => this._handleEvent('webhook:received', data))
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
stop() {
|
|
247
|
+
for (const handler of this._handlers) {
|
|
248
|
+
handler()
|
|
249
|
+
}
|
|
250
|
+
this._handlers = []
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_handleEvent(type, data) {
|
|
254
|
+
// Check active goals
|
|
255
|
+
const activeGoals = this._goalManager.getActiveGoals()
|
|
256
|
+
for (const goal of activeGoals) {
|
|
257
|
+
if (this._isRelevantToGoal(goal, type, data)) {
|
|
258
|
+
this._goalManager.addEventToGoal(goal.id, { type, data })
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Also check pending goals - activate if conditions match
|
|
263
|
+
const pendingGoals = this._goalManager.getPendingGoals()
|
|
264
|
+
for (const goal of pendingGoals) {
|
|
265
|
+
if (this._isRelevantToGoal(goal, type, data)) {
|
|
266
|
+
this._goalManager.activateGoal(goal.id)
|
|
267
|
+
this._goalManager.addEventToGoal(goal.id, { type, data })
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
_isRelevantToGoal(goal, eventType, data) {
|
|
273
|
+
// Check explicit event types in conditions
|
|
274
|
+
if (goal.conditions && goal.conditions.events) {
|
|
275
|
+
const allowedEvents = Array.isArray(goal.conditions.events)
|
|
276
|
+
? goal.conditions.events
|
|
277
|
+
: [goal.conditions.events]
|
|
278
|
+
if (!allowedEvents.includes(eventType)) {
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Check tool name filters
|
|
283
|
+
if (goal.conditions && goal.conditions.toolNames) {
|
|
284
|
+
const toolNames = Array.isArray(goal.conditions.toolNames)
|
|
285
|
+
? goal.conditions.toolNames
|
|
286
|
+
: [goal.conditions.toolNames]
|
|
287
|
+
if (data && data.toolName && !toolNames.includes(data.toolName)) {
|
|
288
|
+
return false
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return true
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// Reflector - Evaluates action outcomes, updates goal states
|
|
297
|
+
// ============================================================================
|
|
298
|
+
class Reflector {
|
|
299
|
+
constructor(goalManager) {
|
|
300
|
+
this._goalManager = goalManager
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
evaluateOutcome(goal, actionResult) {
|
|
304
|
+
if (!goal) return null
|
|
305
|
+
|
|
306
|
+
// Check for errors
|
|
307
|
+
if (actionResult && actionResult.error) {
|
|
308
|
+
// Increment attempts but don't immediately fail
|
|
309
|
+
goal.attempts++
|
|
310
|
+
return {
|
|
311
|
+
shouldContinue: goal.attempts < goal.maxAttempts,
|
|
312
|
+
actionTaken: false
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Check for successful completion
|
|
317
|
+
if (actionResult && actionResult.success === true) {
|
|
318
|
+
goal.attempts++
|
|
319
|
+
|
|
320
|
+
// Check if goal is complete
|
|
321
|
+
if (goal.actions.length === 0 || goal.attempts >= goal.maxAttempts) {
|
|
322
|
+
return { shouldContinue: false, goalComplete: true }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { shouldContinue: true, actionTaken: true }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Tool executed without error - consider it successful
|
|
329
|
+
goal.attempts++
|
|
330
|
+
|
|
331
|
+
// Check if goal is complete (no more actions)
|
|
332
|
+
if (goal.actions.length === 0 || goal.attempts >= goal.maxAttempts) {
|
|
333
|
+
return { shouldContinue: false, goalComplete: true }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return { shouldContinue: true, actionTaken: true }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
checkLoopDetection(goal, actionId) {
|
|
340
|
+
// If same action repeats 3x consecutively, fail goal
|
|
341
|
+
if (goal.lastActionId === actionId) {
|
|
342
|
+
goal.consecutiveSameActions++
|
|
343
|
+
if (goal.consecutiveSameActions >= 3) {
|
|
344
|
+
this._goalManager.failGoal(goal.id, 'Loop detected: same action repeated 3 times')
|
|
345
|
+
return true
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
goal.consecutiveSameActions = 1
|
|
349
|
+
goal.lastActionId = actionId
|
|
350
|
+
}
|
|
351
|
+
return false
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ============================================================================
|
|
356
|
+
// ExplorerLoop - Main autonomous loop that decides and executes actions
|
|
357
|
+
// ============================================================================
|
|
358
|
+
class ExplorerLoop {
|
|
359
|
+
constructor(goalManager, reflector, framework, config) {
|
|
360
|
+
this._goalManager = goalManager
|
|
361
|
+
this._reflector = reflector
|
|
362
|
+
this._framework = framework
|
|
363
|
+
this._config = config
|
|
364
|
+
this._running = false
|
|
365
|
+
this._timer = null
|
|
366
|
+
this._lastActionTime = 0
|
|
367
|
+
this._tickCount = 0
|
|
368
|
+
this._recentActivities = []
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
start() {
|
|
372
|
+
if (this._running) return
|
|
373
|
+
this._running = true
|
|
374
|
+
this._scheduleNext()
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
stop() {
|
|
378
|
+
this._running = false
|
|
379
|
+
if (this._timer) {
|
|
380
|
+
clearTimeout(this._timer)
|
|
381
|
+
this._timer = null
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
pause() {
|
|
386
|
+
this._running = false
|
|
387
|
+
if (this._timer) {
|
|
388
|
+
clearTimeout(this._timer)
|
|
389
|
+
this._timer = null
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
resume() {
|
|
394
|
+
if (!this._running) {
|
|
395
|
+
this._running = true
|
|
396
|
+
this._scheduleNext()
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
isRunning() {
|
|
401
|
+
return this._running
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
getStatus() {
|
|
405
|
+
return {
|
|
406
|
+
running: this._running,
|
|
407
|
+
tickCount: this._tickCount,
|
|
408
|
+
lastTick: this._lastActionTime ? new Date(this._lastActionTime) : null,
|
|
409
|
+
recentActivities: this._recentActivities.slice(-10),
|
|
410
|
+
activeGoals: this._goalManager.getActiveGoals().length,
|
|
411
|
+
pendingGoals: this._goalManager.getPendingGoals().length
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
_scheduleNext() {
|
|
416
|
+
if (!this._running) return
|
|
417
|
+
this._timer = setTimeout(() => this._tick(), this._config.tickInterval)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async _tick() {
|
|
421
|
+
if (!this._running) return
|
|
422
|
+
|
|
423
|
+
// Skip tick if in nested execution context
|
|
424
|
+
try {
|
|
425
|
+
if (this._framework.getExecutionContext) {
|
|
426
|
+
const ctx = this._framework.getExecutionContext()
|
|
427
|
+
if (ctx && ctx.id) {
|
|
428
|
+
// Skip this tick, reschedule
|
|
429
|
+
this._scheduleNext()
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
} catch (e) {
|
|
434
|
+
// getExecutionContext might not exist, continue
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
this._tickCount++
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
await this._processGoals()
|
|
441
|
+
} catch (err) {
|
|
442
|
+
console.error('[Ambient] Tick error:', err.message)
|
|
443
|
+
this._addActivity('error', { message: err.message })
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Schedule next tick
|
|
447
|
+
this._scheduleNext()
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async _processGoals() {
|
|
451
|
+
const activeGoals = this._goalManager.getActiveGoals()
|
|
452
|
+
|
|
453
|
+
for (const goal of activeGoals) {
|
|
454
|
+
// Check cooldown
|
|
455
|
+
if (Date.now() - this._lastActionTime < this._config.cooldownPeriod) {
|
|
456
|
+
continue
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check if goal has unprocessed events
|
|
460
|
+
const hasNewEvents = goal.eventsReceived && goal.eventsReceived.length > 0
|
|
461
|
+
|
|
462
|
+
// Check if goal is event-driven
|
|
463
|
+
const isEventDriven = goal.conditions && goal.conditions.events && goal.conditions.events.length > 0
|
|
464
|
+
|
|
465
|
+
// Get next action
|
|
466
|
+
if (goal.actions && goal.actions.length > 0) {
|
|
467
|
+
const action = goal.actions[0] // Simple: take first action
|
|
468
|
+
|
|
469
|
+
// For event-driven goals, only execute if there are new events
|
|
470
|
+
if (isEventDriven && !hasNewEvents) {
|
|
471
|
+
// Skip execution but don't fail - just wait for events
|
|
472
|
+
continue
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Loop detection (only for non-event-driven actions)
|
|
476
|
+
if (!hasNewEvents && this._reflector.checkLoopDetection(goal, action.id)) {
|
|
477
|
+
this._addActivity('goal_failed', { goalId: goal.id, reason: 'Loop detected' })
|
|
478
|
+
continue
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Execute action
|
|
482
|
+
const result = await this._executeAction(action, hasNewEvents ? goal.eventsReceived[0] : null)
|
|
483
|
+
|
|
484
|
+
// Evaluate outcome
|
|
485
|
+
const outcome = this._reflector.evaluateOutcome(goal, result)
|
|
486
|
+
|
|
487
|
+
if (outcome.goalComplete) {
|
|
488
|
+
// For event-driven goals, don't complete - reset and wait for more events
|
|
489
|
+
if (isEventDriven) {
|
|
490
|
+
goal.eventsReceived = []
|
|
491
|
+
goal.attempts = 0
|
|
492
|
+
this._goalManager.updateGoal(goal.id, { eventsReceived: [], attempts: 0 })
|
|
493
|
+
this._addActivity('goal_reset', { goalId: goal.id, reason: 'Event-driven, waiting for more events' })
|
|
494
|
+
} else {
|
|
495
|
+
this._goalManager.completeGoal(goal.id)
|
|
496
|
+
this._addActivity('goal_completed', { goalId: goal.id, attempts: goal.attempts })
|
|
497
|
+
}
|
|
498
|
+
} else if (!outcome.shouldContinue) {
|
|
499
|
+
this._goalManager.failGoal(goal.id, 'Max attempts reached')
|
|
500
|
+
this._addActivity('goal_failed', { goalId: goal.id, reason: 'Max attempts' })
|
|
501
|
+
} else if (outcome.actionTaken) {
|
|
502
|
+
// For event-driven goals, keep actions in queue for continuous processing
|
|
503
|
+
// Only shift if this is a one-time goal (no pending events)
|
|
504
|
+
if (!isEventDriven) {
|
|
505
|
+
goal.actions.shift()
|
|
506
|
+
}
|
|
507
|
+
this._addActivity('action_executed', { goalId: goal.id, action: action.id, hasEvents: hasNewEvents })
|
|
508
|
+
// Update last action time for cooldown
|
|
509
|
+
this._lastActionTime = Date.now()
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Clear processed events after action is taken
|
|
513
|
+
if (hasNewEvents) {
|
|
514
|
+
goal.eventsReceived = []
|
|
515
|
+
this._goalManager.updateGoal(goal.id, { eventsReceived: [] })
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Activate pending goals that have matching events
|
|
521
|
+
const pendingGoals = this._goalManager.getPendingGoals()
|
|
522
|
+
for (const goal of pendingGoals) {
|
|
523
|
+
// Auto-activate goals without conditions, or with satisfied conditions
|
|
524
|
+
if (!goal.conditions || Object.keys(goal.conditions).length === 0) {
|
|
525
|
+
this._goalManager.activateGoal(goal.id)
|
|
526
|
+
this._addActivity('goal_activated', { goalId: goal.id })
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async _executeAction(action, eventData = null) {
|
|
532
|
+
if (!this._framework) {
|
|
533
|
+
return { success: false, error: 'Framework not available' }
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
if (action.type === 'tool') {
|
|
538
|
+
// Use getTools() which returns array, find by name
|
|
539
|
+
const tools = this._framework.getTools()
|
|
540
|
+
const tool = tools.find(t => t.name === action.name)
|
|
541
|
+
if (!tool) {
|
|
542
|
+
return { success: false, error: `Tool not found: ${action.name}` }
|
|
543
|
+
}
|
|
544
|
+
// Merge event data into args if available
|
|
545
|
+
const args = { ...action.args }
|
|
546
|
+
if (eventData) {
|
|
547
|
+
args._event = eventData
|
|
548
|
+
}
|
|
549
|
+
const result = await tool.execute(args, this._framework)
|
|
550
|
+
return result
|
|
551
|
+
} else if (action.type === 'message') {
|
|
552
|
+
const agent = this._getActiveAgent()
|
|
553
|
+
if (!agent) {
|
|
554
|
+
return { success: false, error: 'No active agent' }
|
|
555
|
+
}
|
|
556
|
+
// Include event context in message if available
|
|
557
|
+
let content = action.content
|
|
558
|
+
if (eventData) {
|
|
559
|
+
content = `${action.content}\n\n[Event Context: ${JSON.stringify(eventData)}]`
|
|
560
|
+
}
|
|
561
|
+
const result = await agent.pushMessage(content)
|
|
562
|
+
return { success: true, result }
|
|
563
|
+
} else if (action.type === 'think') {
|
|
564
|
+
// Trigger thinking
|
|
565
|
+
const thinkPlugin = this._framework.pluginManager.get('think')
|
|
566
|
+
if (thinkPlugin) {
|
|
567
|
+
// Include event context in topic if available
|
|
568
|
+
let topic = action.topic || 'Ambient agent reflection'
|
|
569
|
+
if (eventData) {
|
|
570
|
+
topic = `${topic}\n\n[Event Context: ${JSON.stringify(eventData)}]`
|
|
571
|
+
}
|
|
572
|
+
const result = await thinkPlugin._triggerThinking({
|
|
573
|
+
topic,
|
|
574
|
+
mode: action.mode || 'reflect',
|
|
575
|
+
depth: action.depth || 2
|
|
576
|
+
})
|
|
577
|
+
return result
|
|
578
|
+
}
|
|
579
|
+
return { success: false, error: 'Think plugin not available' }
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { success: false, error: `Unknown action type: ${action.type}` }
|
|
583
|
+
} catch (err) {
|
|
584
|
+
return { success: false, error: err.message }
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
_getActiveAgent() {
|
|
589
|
+
if (this._framework._mainAgent) {
|
|
590
|
+
return this._framework._mainAgent
|
|
591
|
+
}
|
|
592
|
+
const agents = this._framework._agents || []
|
|
593
|
+
return agents.length > 0 ? agents[agents.length - 1] : null
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
_addActivity(type, data) {
|
|
597
|
+
this._recentActivities.push({
|
|
598
|
+
type,
|
|
599
|
+
data,
|
|
600
|
+
timestamp: new Date()
|
|
601
|
+
})
|
|
602
|
+
// Keep only last 50 activities
|
|
603
|
+
if (this._recentActivities.length > 50) {
|
|
604
|
+
this._recentActivities = this._recentActivities.slice(-50)
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// ============================================================================
|
|
610
|
+
// Main Plugin Class
|
|
611
|
+
// ============================================================================
|
|
612
|
+
class AmbientAgentPlugin extends Plugin {
|
|
613
|
+
constructor(config = {}) {
|
|
614
|
+
super()
|
|
615
|
+
this.name = 'ambient'
|
|
616
|
+
this.version = '1.0.0'
|
|
617
|
+
this.description = `Ambient Agent - 持续运行的后台 Agent,用于主动监控和自动操作
|
|
618
|
+
支持监听的事件:
|
|
619
|
+
- email:received - 收到新邮件(需配合 email_watch 启动监控)
|
|
620
|
+
- webhook:received - 收到 webhook 请求
|
|
621
|
+
- scheduler:reminder - 定时提醒触发
|
|
622
|
+
- scheduler:task_completed - 定时任务完成
|
|
623
|
+
- scheduler:task_failed - 定时任务失败
|
|
624
|
+
- think:thought_completed - 思考完成
|
|
625
|
+
- tool:result - 工具执行结果
|
|
626
|
+
- agent:message - Agent 消息`
|
|
627
|
+
this.priority = 18
|
|
628
|
+
this.system = true
|
|
629
|
+
|
|
630
|
+
this.config = {
|
|
631
|
+
enabled: config.enabled !== false,
|
|
632
|
+
tickInterval: config.tickInterval || 5000,
|
|
633
|
+
cooldownPeriod: config.cooldownPeriod || 3000,
|
|
634
|
+
persistencePath: config.persistencePath || '.agent/data/ambient',
|
|
635
|
+
defaultGoals: config.defaultGoals || []
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
this._framework = null
|
|
639
|
+
this._stateStore = null
|
|
640
|
+
this._goalManager = null
|
|
641
|
+
this._eventWatcher = null
|
|
642
|
+
this._reflector = null
|
|
643
|
+
this._explorerLoop = null
|
|
644
|
+
this._memories = []
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
install(framework) {
|
|
648
|
+
this._framework = framework
|
|
649
|
+
this._stateStore = new StateStore(this.config.persistencePath)
|
|
650
|
+
this._goalManager = new GoalManager(this._stateStore)
|
|
651
|
+
this._reflector = new Reflector(this._goalManager)
|
|
652
|
+
|
|
653
|
+
// Load persisted memories
|
|
654
|
+
this._memories = this._stateStore.loadMemories()
|
|
655
|
+
|
|
656
|
+
return this
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
start(framework) {
|
|
660
|
+
if (!this.config.enabled) {
|
|
661
|
+
return this
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Create default goals if none exist
|
|
665
|
+
if (this._goalManager.getAllGoals().length === 0 && this.config.defaultGoals.length > 0) {
|
|
666
|
+
for (const goalDef of this.config.defaultGoals) {
|
|
667
|
+
this._goalManager.createGoal(goalDef)
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Start event watcher
|
|
672
|
+
this._eventWatcher = new EventWatcher(this._goalManager, this._framework)
|
|
673
|
+
this._eventWatcher.start()
|
|
674
|
+
|
|
675
|
+
// Start explorer loop
|
|
676
|
+
this._explorerLoop = new ExplorerLoop(this._goalManager, this._reflector, this._framework, {
|
|
677
|
+
tickInterval: this.config.tickInterval,
|
|
678
|
+
cooldownPeriod: this.config.cooldownPeriod
|
|
679
|
+
})
|
|
680
|
+
this._explorerLoop.start()
|
|
681
|
+
|
|
682
|
+
// Register tools
|
|
683
|
+
this._registerTools(framework)
|
|
684
|
+
|
|
685
|
+
return this
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
_registerTools(framework) {
|
|
689
|
+
// ambient_goals - List/create/update/delete goals
|
|
690
|
+
framework.registerTool({
|
|
691
|
+
name: 'ambient_goals',
|
|
692
|
+
description: 'Manage ambient agent goals - list, create, update, delete goals',
|
|
693
|
+
inputSchema: z.object({
|
|
694
|
+
action: z.enum(['list', 'create', 'update', 'delete', 'activate']).describe('Operation to perform'),
|
|
695
|
+
goalId: z.string().optional().describe('Goal ID (required for update/delete/activate)'),
|
|
696
|
+
title: z.string().optional().describe('Goal title (for create/update)'),
|
|
697
|
+
description: z.string().optional().describe('Goal description (for create/update)'),
|
|
698
|
+
priority: z.number().optional().describe('Priority 1-10, higher = more important (for create/update)'),
|
|
699
|
+
actions: z.array(z.object({
|
|
700
|
+
id: z.string().optional().describe('Action identifier'),
|
|
701
|
+
type: z.enum(['tool', 'message', 'think']).describe('Action type'),
|
|
702
|
+
name: z.string().optional().describe('Tool name (for tool type)'),
|
|
703
|
+
args: z.record(z.any()).optional().describe('Tool arguments (for tool type)'),
|
|
704
|
+
content: z.string().optional().describe('Message content (for message type)'),
|
|
705
|
+
topic: z.string().optional().describe('Think topic (for think type)'),
|
|
706
|
+
mode: z.enum(['reflect', 'brainstorm', 'analyze', 'plan']).optional().describe('Think mode (for think type)')
|
|
707
|
+
})).optional().describe('Actions to execute (for create/update)'),
|
|
708
|
+
conditions: z.object({
|
|
709
|
+
events: z.union([z.string(), z.array(z.string())]).optional().describe('Event types to listen for'),
|
|
710
|
+
toolNames: z.union([z.string(), z.array(z.string())]).optional().describe('Filter by tool names')
|
|
711
|
+
}).optional().describe('Activation conditions (for create/update)')
|
|
712
|
+
}),
|
|
713
|
+
execute: async (args) => {
|
|
714
|
+
return this._handleGoalsTool(args)
|
|
715
|
+
}
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
// ambient_status - Get current loop state
|
|
719
|
+
framework.registerTool({
|
|
720
|
+
name: 'ambient_status',
|
|
721
|
+
description: 'Get current ambient agent status - loop state, active goals, recent activity',
|
|
722
|
+
inputSchema: z.object({}),
|
|
723
|
+
execute: async () => {
|
|
724
|
+
return this._handleStatusTool()
|
|
725
|
+
}
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
// ambient_think - Trigger proactive LLM thinking
|
|
729
|
+
framework.registerTool({
|
|
730
|
+
name: 'ambient_think',
|
|
731
|
+
description: 'Trigger proactive LLM thinking for a goal - modes: reflect, brainstorm, plan, analyze',
|
|
732
|
+
inputSchema: z.object({
|
|
733
|
+
goalId: z.string().optional().describe('Goal ID to think about'),
|
|
734
|
+
mode: z.enum(['reflect', 'brainstorm', 'plan', 'analyze']).describe('Thinking mode'),
|
|
735
|
+
topic: z.string().optional().describe('Topic to think about'),
|
|
736
|
+
depth: z.number().optional().describe('Thinking depth 1-5')
|
|
737
|
+
}),
|
|
738
|
+
execute: async (args) => {
|
|
739
|
+
return this._handleThinkTool(args)
|
|
740
|
+
}
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
// ambient_remember - Store/retrieve/search persistent memories
|
|
744
|
+
framework.registerTool({
|
|
745
|
+
name: 'ambient_remember',
|
|
746
|
+
description: 'Store or retrieve persistent memories for the ambient agent',
|
|
747
|
+
inputSchema: z.object({
|
|
748
|
+
action: z.enum(['store', 'retrieve', 'search']).describe('Action: store a memory, retrieve recent, or search'),
|
|
749
|
+
content: z.string().optional().describe('Memory content (for store)'),
|
|
750
|
+
key: z.string().optional().describe('Memory key (for store/retrieve)'),
|
|
751
|
+
query: z.string().optional().describe('Search query (for search)'),
|
|
752
|
+
limit: z.number().optional().describe('Max results (for retrieve/search)')
|
|
753
|
+
}),
|
|
754
|
+
execute: async (args) => {
|
|
755
|
+
return this._handleRememberTool(args)
|
|
756
|
+
}
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
// ambient_control - Pause/resume/adjust the explorer loop
|
|
760
|
+
framework.registerTool({
|
|
761
|
+
name: 'ambient_control',
|
|
762
|
+
description: 'Control the ambient agent explorer loop - pause, resume, or adjust settings',
|
|
763
|
+
inputSchema: z.object({
|
|
764
|
+
action: z.enum(['pause', 'resume', 'status', 'adjust']).describe('Control action'),
|
|
765
|
+
tickInterval: z.number().optional().describe('New tick interval in ms (for adjust)'),
|
|
766
|
+
cooldownPeriod: z.number().optional().describe('New cooldown period in ms (for adjust)')
|
|
767
|
+
}),
|
|
768
|
+
execute: async (args) => {
|
|
769
|
+
return this._handleControlTool(args)
|
|
770
|
+
}
|
|
771
|
+
})
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
_handleGoalsTool(args) {
|
|
775
|
+
const { action, goalId, title, description, priority, actions, conditions } = args
|
|
776
|
+
|
|
777
|
+
switch (action) {
|
|
778
|
+
case 'list': {
|
|
779
|
+
const allGoals = this._goalManager.getAllGoals()
|
|
780
|
+
return {
|
|
781
|
+
success: true,
|
|
782
|
+
goals: allGoals.map(g => ({
|
|
783
|
+
id: g.id,
|
|
784
|
+
title: g.title,
|
|
785
|
+
state: g.state,
|
|
786
|
+
priority: g.priority,
|
|
787
|
+
actionsCount: g.actions ? g.actions.length : 0,
|
|
788
|
+
attempts: g.attempts,
|
|
789
|
+
createdAt: g.createdAt,
|
|
790
|
+
activatedAt: g.activatedAt,
|
|
791
|
+
completedAt: g.completedAt,
|
|
792
|
+
failedAt: g.failedAt
|
|
793
|
+
})),
|
|
794
|
+
total: allGoals.length,
|
|
795
|
+
active: this._goalManager.getActiveGoals().length,
|
|
796
|
+
pending: this._goalManager.getPendingGoals().length
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
case 'create': {
|
|
801
|
+
if (!title) {
|
|
802
|
+
return { success: false, error: 'Title is required for creating a goal' }
|
|
803
|
+
}
|
|
804
|
+
const goal = this._goalManager.createGoal({ title, description, priority, actions, conditions })
|
|
805
|
+
return { success: true, goal }
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
case 'update': {
|
|
809
|
+
if (!goalId) {
|
|
810
|
+
return { success: false, error: 'Goal ID is required for update' }
|
|
811
|
+
}
|
|
812
|
+
const updates = {}
|
|
813
|
+
if (title !== undefined) updates.title = title
|
|
814
|
+
if (description !== undefined) updates.description = description
|
|
815
|
+
if (priority !== undefined) updates.priority = priority
|
|
816
|
+
if (actions !== undefined) updates.actions = actions
|
|
817
|
+
if (conditions !== undefined) updates.conditions = conditions
|
|
818
|
+
const goal = this._goalManager.updateGoal(goalId, updates)
|
|
819
|
+
return goal ? { success: true, goal } : { success: false, error: 'Goal not found' }
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
case 'delete': {
|
|
823
|
+
if (!goalId) {
|
|
824
|
+
return { success: false, error: 'Goal ID is required for delete' }
|
|
825
|
+
}
|
|
826
|
+
const deleted = this._goalManager.deleteGoal(goalId)
|
|
827
|
+
return { success: deleted }
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
case 'activate': {
|
|
831
|
+
if (!goalId) {
|
|
832
|
+
return { success: false, error: 'Goal ID is required for activate' }
|
|
833
|
+
}
|
|
834
|
+
const goal = this._goalManager.activateGoal(goalId)
|
|
835
|
+
return goal ? { success: true, goal } : { success: false, error: 'Goal not found or not pending' }
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
default:
|
|
839
|
+
return { success: false, error: `Unknown action: ${action}` }
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
_handleStatusTool() {
|
|
844
|
+
if (!this._explorerLoop) {
|
|
845
|
+
return { success: false, error: 'Explorer loop not initialized' }
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const loopStatus = this._explorerLoop.getStatus()
|
|
849
|
+
const activeGoals = this._goalManager.getActiveGoals().map(g => ({
|
|
850
|
+
id: g.id,
|
|
851
|
+
title: g.title,
|
|
852
|
+
priority: g.priority,
|
|
853
|
+
attempts: g.attempts,
|
|
854
|
+
lastActionId: g.lastActionId
|
|
855
|
+
}))
|
|
856
|
+
const pendingGoals = this._goalManager.getPendingGoals().map(g => ({
|
|
857
|
+
id: g.id,
|
|
858
|
+
title: g.title,
|
|
859
|
+
priority: g.priority
|
|
860
|
+
}))
|
|
861
|
+
|
|
862
|
+
return {
|
|
863
|
+
success: true,
|
|
864
|
+
loop: {
|
|
865
|
+
running: loopStatus.running,
|
|
866
|
+
tickCount: loopStatus.tickCount,
|
|
867
|
+
lastTick: loopStatus.lastTick,
|
|
868
|
+
tickInterval: this.config.tickInterval,
|
|
869
|
+
cooldownPeriod: this.config.cooldownPeriod
|
|
870
|
+
},
|
|
871
|
+
activeGoals,
|
|
872
|
+
pendingGoals,
|
|
873
|
+
recentActivities: loopStatus.recentActivities
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async _handleThinkTool(args) {
|
|
878
|
+
const { goalId, mode, topic, depth } = args
|
|
879
|
+
|
|
880
|
+
// Get active agent
|
|
881
|
+
const agent = this._getActiveAgent()
|
|
882
|
+
if (!agent) {
|
|
883
|
+
return { success: false, error: 'No active agent available' }
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Build thinking prompt
|
|
887
|
+
let prompt = ''
|
|
888
|
+
if (goalId) {
|
|
889
|
+
const goal = this._goalManager.getGoal(goalId)
|
|
890
|
+
if (goal) {
|
|
891
|
+
prompt = this._buildGoalThinkPrompt(goal, mode, topic)
|
|
892
|
+
} else {
|
|
893
|
+
return { success: false, error: 'Goal not found' }
|
|
894
|
+
}
|
|
895
|
+
} else {
|
|
896
|
+
prompt = this._buildFreeThinkPrompt(mode, topic)
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Wait for agent to be available if busy
|
|
900
|
+
let attempts = 0
|
|
901
|
+
const maxWaitAttempts = 10
|
|
902
|
+
const waitInterval = 500
|
|
903
|
+
|
|
904
|
+
while (agent._status === 'busy' && attempts < maxWaitAttempts) {
|
|
905
|
+
await new Promise(resolve => setTimeout(resolve, waitInterval))
|
|
906
|
+
attempts++
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (agent._status === 'busy') {
|
|
910
|
+
return {
|
|
911
|
+
success: false,
|
|
912
|
+
error: 'Agent is busy and could not become available in time. Please try again later.',
|
|
913
|
+
queued: true
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Execute thinking via agent
|
|
918
|
+
const thinkPlugin = this._framework.pluginManager.get('think')
|
|
919
|
+
if (thinkPlugin) {
|
|
920
|
+
const result = await thinkPlugin._triggerThinking({ topic: prompt, mode, depth: depth || 2 })
|
|
921
|
+
// If the result indicates busy, return a more helpful message
|
|
922
|
+
if (result && result.message && result.message.includes && result.message.includes('busy')) {
|
|
923
|
+
return {
|
|
924
|
+
success: true,
|
|
925
|
+
queued: true,
|
|
926
|
+
message: 'Thinking request was queued and will be processed when the agent is free.'
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return result
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Fallback: push message directly
|
|
933
|
+
return agent.pushMessage(prompt).then(result => ({
|
|
934
|
+
success: true,
|
|
935
|
+
result: typeof result === 'string' ? result : JSON.stringify(result)
|
|
936
|
+
})).catch(err => ({
|
|
937
|
+
success: false,
|
|
938
|
+
error: err.message
|
|
939
|
+
}))
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
_buildGoalThinkPrompt(goal, mode, topic) {
|
|
943
|
+
const modePrompts = {
|
|
944
|
+
reflect: `As the Ambient Agent, reflect on the current goal:
|
|
945
|
+
Title: ${goal.title}
|
|
946
|
+
Description: ${goal.description || 'None'}
|
|
947
|
+
Priority: ${goal.priority}
|
|
948
|
+
State: ${goal.state}
|
|
949
|
+
Attempts: ${goal.attempts}
|
|
950
|
+
|
|
951
|
+
${topic ? `Additional focus: ${topic}` : ''}
|
|
952
|
+
|
|
953
|
+
Reflect on:
|
|
954
|
+
1. Is this goal still relevant and achievable?
|
|
955
|
+
2. Are the actions appropriate for the goal?
|
|
956
|
+
3. What could be improved?`,
|
|
957
|
+
brainstorm: `Brainstorm new approaches for the ambient agent goal:
|
|
958
|
+
Title: ${goal.title}
|
|
959
|
+
${goal.description ? `Description: ${goal.description}` : ''}
|
|
960
|
+
Actions planned: ${goal.actions ? goal.actions.length : 0}
|
|
961
|
+
|
|
962
|
+
${topic ? `Focus: ${topic}` : 'Generate new ideas for how this goal could be pursued more effectively.'}`,
|
|
963
|
+
plan: `Create a refined plan for the ambient agent goal:
|
|
964
|
+
Title: ${goal.title}
|
|
965
|
+
${goal.description ? `Description: ${goal.description}` : ''}
|
|
966
|
+
${topic ? `Additional context: ${topic}` : ''}
|
|
967
|
+
|
|
968
|
+
Develop a clear, actionable plan.`,
|
|
969
|
+
analyze: `Analyze the ambient agent goal:
|
|
970
|
+
Title: ${goal.title}
|
|
971
|
+
State: ${goal.state}
|
|
972
|
+
Priority: ${goal.priority}
|
|
973
|
+
Actions: ${goal.actions ? JSON.stringify(goal.actions) : 'None'}
|
|
974
|
+
|
|
975
|
+
${topic ? `Analysis focus: ${topic}` : 'What are the strengths, weaknesses, and potential issues with this goal?'}`
|
|
976
|
+
}
|
|
977
|
+
return modePrompts[mode] || modePrompts.reflect
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
_buildFreeThinkPrompt(mode, topic) {
|
|
981
|
+
const modePrompts = {
|
|
982
|
+
reflect: `Ambient Agent reflection: ${topic || 'Consider the current state of the system and goals. What could be improved?'}`,
|
|
983
|
+
brainstorm: `Ambient Agent brainstorming: ${topic || 'Generate creative ideas for new goals or approaches.'}`,
|
|
984
|
+
plan: `Ambient Agent planning: ${topic || 'Develop plans for pending objectives.'}`,
|
|
985
|
+
analyze: `Ambient Agent analysis: ${topic || 'Analyze current goals and their progress.'}`
|
|
986
|
+
}
|
|
987
|
+
return modePrompts[mode] || modePrompts.reflect
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
_handleRememberTool(args) {
|
|
991
|
+
const { action, content, key, query, limit } = args
|
|
992
|
+
const maxResults = limit || 10
|
|
993
|
+
|
|
994
|
+
switch (action) {
|
|
995
|
+
case 'store': {
|
|
996
|
+
if (!content) {
|
|
997
|
+
return { success: false, error: 'Content is required for store' }
|
|
998
|
+
}
|
|
999
|
+
const memoryKey = key || `memory_${Date.now()}`
|
|
1000
|
+
const memory = {
|
|
1001
|
+
key: memoryKey,
|
|
1002
|
+
content,
|
|
1003
|
+
timestamp: new Date()
|
|
1004
|
+
}
|
|
1005
|
+
this._memories.push(memory)
|
|
1006
|
+
this._stateStore.saveMemories(this._memories)
|
|
1007
|
+
return { success: true, memory }
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
case 'retrieve': {
|
|
1011
|
+
const memories = this._memories.slice(-maxResults).reverse()
|
|
1012
|
+
return {
|
|
1013
|
+
success: true,
|
|
1014
|
+
memories,
|
|
1015
|
+
total: this._memories.length
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
case 'search': {
|
|
1020
|
+
if (!query) {
|
|
1021
|
+
return { success: false, error: 'Query is required for search' }
|
|
1022
|
+
}
|
|
1023
|
+
const queryLower = query.toLowerCase()
|
|
1024
|
+
const results = this._memories
|
|
1025
|
+
.filter(m => m.content.toLowerCase().includes(queryLower))
|
|
1026
|
+
.slice(-maxResults)
|
|
1027
|
+
.reverse()
|
|
1028
|
+
return {
|
|
1029
|
+
success: true,
|
|
1030
|
+
results,
|
|
1031
|
+
total: results.length
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
default:
|
|
1036
|
+
return { success: false, error: `Unknown action: ${action}` }
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
_handleControlTool(args) {
|
|
1041
|
+
const { action, tickInterval, cooldownPeriod } = args
|
|
1042
|
+
|
|
1043
|
+
switch (action) {
|
|
1044
|
+
case 'pause': {
|
|
1045
|
+
if (!this._explorerLoop) {
|
|
1046
|
+
return { success: false, error: 'Explorer loop not initialized' }
|
|
1047
|
+
}
|
|
1048
|
+
this._explorerLoop.pause()
|
|
1049
|
+
return { success: true, message: 'Explorer loop paused' }
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
case 'resume': {
|
|
1053
|
+
if (!this._explorerLoop) {
|
|
1054
|
+
return { success: false, error: 'Explorer loop not initialized' }
|
|
1055
|
+
}
|
|
1056
|
+
this._explorerLoop.resume()
|
|
1057
|
+
return { success: true, message: 'Explorer loop resumed' }
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
case 'status': {
|
|
1061
|
+
if (!this._explorerLoop) {
|
|
1062
|
+
return { success: false, error: 'Explorer loop not initialized' }
|
|
1063
|
+
}
|
|
1064
|
+
const status = this._explorerLoop.getStatus()
|
|
1065
|
+
return {
|
|
1066
|
+
success: true,
|
|
1067
|
+
running: status.running,
|
|
1068
|
+
tickCount: status.tickCount,
|
|
1069
|
+
lastTick: status.lastTick,
|
|
1070
|
+
tickInterval: this.config.tickInterval,
|
|
1071
|
+
cooldownPeriod: this.config.cooldownPeriod
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
case 'adjust': {
|
|
1076
|
+
if (tickInterval) {
|
|
1077
|
+
this.config.tickInterval = tickInterval
|
|
1078
|
+
}
|
|
1079
|
+
if (cooldownPeriod) {
|
|
1080
|
+
this.config.cooldownPeriod = cooldownPeriod
|
|
1081
|
+
}
|
|
1082
|
+
return {
|
|
1083
|
+
success: true,
|
|
1084
|
+
message: 'Settings adjusted',
|
|
1085
|
+
tickInterval: this.config.tickInterval,
|
|
1086
|
+
cooldownPeriod: this.config.cooldownPeriod
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
default:
|
|
1091
|
+
return { success: false, error: `Unknown action: ${action}` }
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
_getActiveAgent() {
|
|
1096
|
+
if (this._framework._mainAgent) {
|
|
1097
|
+
return this._framework._mainAgent
|
|
1098
|
+
}
|
|
1099
|
+
const agents = this._framework._agents || []
|
|
1100
|
+
return agents.length > 0 ? agents[agents.length - 1] : null
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
reload(framework) {
|
|
1104
|
+
this._framework = framework
|
|
1105
|
+
if (this._explorerLoop && this._explorerLoop.isRunning()) {
|
|
1106
|
+
// Restart with new framework reference
|
|
1107
|
+
this._eventWatcher = new EventWatcher(this._goalManager, this._framework)
|
|
1108
|
+
this._eventWatcher.start()
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
uninstall(framework) {
|
|
1113
|
+
// Stop explorer loop
|
|
1114
|
+
if (this._explorerLoop) {
|
|
1115
|
+
this._explorerLoop.stop()
|
|
1116
|
+
this._explorerLoop = null
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Stop event watcher
|
|
1120
|
+
if (this._eventWatcher) {
|
|
1121
|
+
this._eventWatcher.stop()
|
|
1122
|
+
this._eventWatcher = null
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Clear references
|
|
1126
|
+
this._framework = null
|
|
1127
|
+
this._goalManager = null
|
|
1128
|
+
this._reflector = null
|
|
1129
|
+
this._stateStore = null
|
|
1130
|
+
this._memories = []
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
module.exports = { AmbientAgentPlugin, GoalState }
|