foliko 1.1.19 → 1.1.21
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/agents/ui-designer.md +1 -1
- package/.agent/data/ambient/goals.json +1 -0
- package/.agent/data/plugins-state.json +1 -1
- package/.agent/data/scheduler/tasks.json +86 -0
- package/.agent/memory/feedback/mnzugpej-esdwsr.md +9 -0
- package/.agent/memory/project/mnztftxj-af82rh.md +9 -0
- package/.agent/memory/project/mo0y04d0-bmaefl.md +9 -0
- package/.agent/memory/user/mnzuh4cw-zvee8w.md +13 -0
- package/.agent/memory/user/mnzvewyj-jl67cq.md +9 -0
- package/.agent/memory/user/mnzwh9xo-43ys3f.md +9 -0
- package/.agent/memory/user/mo0ycvpn-eebsxc.md +9 -0
- package/.agent/sessions/cli_default.json +2377 -3
- package/.claude/settings.local.json +3 -1
- package/cli/src/ui/chat-ui.js +8 -2
- package/nul +3 -0
- package/package.json +1 -1
- package/plugins/ambient-agent/ExplorerLoop.js +121 -11
- package/plugins/ambient-agent/GoalManager.js +1 -1
- package/plugins/extension-executor-plugin.js +1 -1
- package/plugins/scheduler-plugin.js +3 -2
- package/plugins/weixin-plugin.js +6 -3
- package/src/core/agent-chat.js +26 -13
- package/src/core/chat-session.js +7 -2
- package/src/core/context-compressor.js +34 -13
- package/src/core/subagent.js +99 -44
- package/src/utils/chat-queue.js +93 -12
- package/system.md +79 -0
- package/system.md.bak +1978 -0
- package/undefined.svg +8 -0
- package/weixin-bot-poster-v2.png +0 -0
- package/weixin-bot-poster.png +0 -0
- package/.agent/sessions/default.json +0 -111
- package/audio/bed_moonlight.mp3 +0 -0
package/src/core/subagent.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
const { EventEmitter } = require('../utils/event-emitter');
|
|
8
8
|
const { cleanResponse } = require('../utils');
|
|
9
|
-
const { generateText, tool, stepCountIs, isLoopFinished } = require('ai');
|
|
9
|
+
const { generateText, tool, stepCountIs, isLoopFinished, RetryError } = require('ai');
|
|
10
10
|
const { z } = require('zod');
|
|
11
|
+
const { logger } = require('../utils/logger');
|
|
11
12
|
|
|
12
13
|
class Subagent extends EventEmitter {
|
|
13
14
|
/**
|
|
@@ -49,7 +50,7 @@ class Subagent extends EventEmitter {
|
|
|
49
50
|
};
|
|
50
51
|
// 如果提供了 systemPrompt 则使用,否则标记为需要动态构建
|
|
51
52
|
this._customSystemPrompt = config.systemPrompt || null;
|
|
52
|
-
this.parentTools = config?.parentTools
|
|
53
|
+
this.parentTools = config?.parentTools;
|
|
53
54
|
// 工具管理
|
|
54
55
|
this._tools = new Map();
|
|
55
56
|
this._registerTools(config.tools || []);
|
|
@@ -120,20 +121,31 @@ class Subagent extends EventEmitter {
|
|
|
120
121
|
const tools = {};
|
|
121
122
|
// 从父Agent继承工具
|
|
122
123
|
const all_tools = this.framework.getTools();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
let parentTools = [];
|
|
125
|
+
if (Array.isArray(this.parentTools)) {
|
|
126
|
+
parentTools = this.parentTools.map((key) => {
|
|
127
|
+
return this.bindTools[key.toLocaleLowerCase()] || key;
|
|
128
|
+
});
|
|
129
|
+
for (const toolName of parentTools) {
|
|
130
|
+
const toolDef = all_tools.find((t) => t.name === toolName);
|
|
131
|
+
if (toolDef) {
|
|
132
|
+
tools[toolDef.name] = toolDef;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const defaulTools = all_tools.filter((a) => this.defaulTools.includes(a.name));
|
|
136
|
+
|
|
137
|
+
defaulTools.map((tool) => {
|
|
138
|
+
tools[tool.name] = tool;
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
for (const toolName of all_tools) {
|
|
142
|
+
const toolDef = all_tools.find((t) => t.name === toolName);
|
|
143
|
+
if (toolDef) {
|
|
144
|
+
tools[toolDef.name] = toolDef;
|
|
145
|
+
}
|
|
130
146
|
}
|
|
131
147
|
}
|
|
132
|
-
const defaulTools = all_tools.filter((a) => this.defaulTools.includes(a.name));
|
|
133
148
|
|
|
134
|
-
defaulTools.map((tool) => {
|
|
135
|
-
tools[tool.name] = tool;
|
|
136
|
-
});
|
|
137
149
|
return { ...tools, ...this._tools };
|
|
138
150
|
}
|
|
139
151
|
|
|
@@ -217,47 +229,90 @@ class Subagent extends EventEmitter {
|
|
|
217
229
|
* @param {Object} [options] - 选项
|
|
218
230
|
* @param {number} [options.maxSteps] - 最大步数
|
|
219
231
|
* @param {AbortSignal} [options.signal] - 中止信号
|
|
232
|
+
* @param {number} [options.maxRetries] - 最大重试次数
|
|
220
233
|
* @returns {Promise<{success: boolean, message: string, steps: number}>}
|
|
221
234
|
*/
|
|
222
235
|
async chat(task, options = {}) {
|
|
223
236
|
const maxSteps = options?.maxSteps || 30;
|
|
237
|
+
const maxRetries = options?.maxRetries ?? 2;
|
|
224
238
|
const aiProvider = this._getAIProvider();
|
|
225
239
|
const messages = [];
|
|
226
240
|
messages.push({ role: 'user', content: task });
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
message: full_text,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
241
|
+
|
|
242
|
+
let lastError;
|
|
243
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
244
|
+
try {
|
|
245
|
+
const tools = this._buildAITools();
|
|
246
|
+
const systemPrompt = this._buildSystemPrompt();
|
|
247
|
+
const result = await generateText({
|
|
248
|
+
model: aiProvider(this.model),
|
|
249
|
+
system: systemPrompt,
|
|
250
|
+
messages: messages,
|
|
251
|
+
tools: tools,
|
|
252
|
+
stopWhen: stepCountIs(maxSteps),
|
|
253
|
+
...this.providerOptions,
|
|
254
|
+
abortSignal: options.signal,
|
|
255
|
+
onChunk: (chunk) => {
|
|
256
|
+
this.emit('chunk', chunk);
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
messages.push(...result.response.messages);
|
|
260
|
+
const full_text = cleanResponse(result.text);
|
|
261
|
+
this.emit('complete', { message: full_text, steps: result.steps?.length || 0 });
|
|
262
|
+
return {
|
|
263
|
+
success: true,
|
|
264
|
+
message: full_text,
|
|
265
|
+
steps: result.steps?.length || 0,
|
|
266
|
+
};
|
|
267
|
+
} catch (err) {
|
|
268
|
+
lastError = err;
|
|
269
|
+
const errName = err?.name || '';
|
|
270
|
+
|
|
271
|
+
// 判断是否是重试错误
|
|
272
|
+
const isRetryError =
|
|
273
|
+
RetryError.isInstance?.(err) ||
|
|
274
|
+
errName === 'AI_RetryError' ||
|
|
275
|
+
errName === 'RetryError' ||
|
|
276
|
+
err?.reason === 'maxRetriesExceeded';
|
|
277
|
+
|
|
278
|
+
if (isRetryError && attempt < maxRetries) {
|
|
279
|
+
logger.warn(`[Subagent:${this.name}] AI 服务不可用,${attempt + 1}/${maxRetries} 次重试`);
|
|
280
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 最终失败,转换为友好消息
|
|
285
|
+
const friendlyMessage = this._getFriendlyError(err);
|
|
286
|
+
this.emit('error', { error: friendlyMessage });
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
message: '',
|
|
290
|
+
error: friendlyMessage,
|
|
291
|
+
steps: 0,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
258
294
|
}
|
|
259
295
|
}
|
|
260
296
|
|
|
297
|
+
/**
|
|
298
|
+
* 获取友好错误消息
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
_getFriendlyError(err) {
|
|
302
|
+
const errName = err?.name || '';
|
|
303
|
+
const errorMessages = {
|
|
304
|
+
AI_RetryError: 'AI 服务暂时不可用,请稍后重试',
|
|
305
|
+
AI_APICallError: err?.isRetryable
|
|
306
|
+
? 'AI 服务暂时不可用,请稍后重试'
|
|
307
|
+
: err.message?.split('\n')[0] || 'AI 请求失败',
|
|
308
|
+
AI_NoContentGeneratedError: 'AI 未生成有效内容,请重试',
|
|
309
|
+
AI_NoSuchModelError: '指定的 AI 模型不存在',
|
|
310
|
+
AI_NoSuchProviderError: 'AI 提供商配置错误',
|
|
311
|
+
AI_LoadAPIKeyError: 'AI API 密钥配置错误',
|
|
312
|
+
};
|
|
313
|
+
return errorMessages[errName] || err.message?.split('\n')[0] || '未知错误';
|
|
314
|
+
}
|
|
315
|
+
|
|
261
316
|
/**
|
|
262
317
|
* 设置角色(会清除自定义 systemPrompt)
|
|
263
318
|
* @param {string} role - 角色描述
|
package/src/utils/chat-queue.js
CHANGED
|
@@ -70,13 +70,25 @@ class ChatQueueManager extends EventEmitter {
|
|
|
70
70
|
|
|
71
71
|
try {
|
|
72
72
|
const result = await this.executeWithRetry(item);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
// 检查 result 是否有错误(而不是通过 try/catch)
|
|
74
|
+
if (result.error) {
|
|
75
|
+
console.log('[ChatQueue] Rejecting with error from result:', result.error.message);
|
|
76
|
+
item.reject(result.error);
|
|
77
|
+
this.emit('queue:failed', {
|
|
78
|
+
requestId: item.id,
|
|
79
|
+
sessionId: item.sessionId,
|
|
80
|
+
error: result.error.message,
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
item.resolve(result);
|
|
84
|
+
this.emit('queue:completed', {
|
|
85
|
+
requestId: item.id,
|
|
86
|
+
sessionId: item.sessionId,
|
|
87
|
+
duration: Date.now() - item.timestamp,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
79
90
|
} catch (error) {
|
|
91
|
+
console.log('[ChatQueue] Rejecting with thrown error:', error.message);
|
|
80
92
|
item.reject(error);
|
|
81
93
|
this.emit('queue:failed', {
|
|
82
94
|
requestId: item.id,
|
|
@@ -103,8 +115,34 @@ class ChatQueueManager extends EventEmitter {
|
|
|
103
115
|
maxRetries: this.retryAttempts,
|
|
104
116
|
});
|
|
105
117
|
|
|
106
|
-
|
|
118
|
+
const result = await this.executeStream(item);
|
|
119
|
+
|
|
120
|
+
// 检查是否有错误(通过返回的 result.error)
|
|
121
|
+
if (result.error) {
|
|
122
|
+
console.log(
|
|
123
|
+
'[ChatQueue] executeWithRetry: attempt',
|
|
124
|
+
attempt,
|
|
125
|
+
'got error:',
|
|
126
|
+
result.error.message
|
|
127
|
+
);
|
|
128
|
+
lastError = result.error;
|
|
129
|
+
if (attempt < this.retryAttempts && this.isRetryableError(lastError)) {
|
|
130
|
+
await this.sleep(this.retryDelay * Math.pow(2, attempt - 1));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
// 返回 result,让 processQueue 处理错误
|
|
134
|
+
console.log('[ChatQueue] executeWithRetry: returning result with error, no more retries');
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return result;
|
|
107
139
|
} catch (error) {
|
|
140
|
+
console.log(
|
|
141
|
+
'[ChatQueue] executeWithRetry: attempt',
|
|
142
|
+
attempt,
|
|
143
|
+
'threw error:',
|
|
144
|
+
error.message
|
|
145
|
+
);
|
|
108
146
|
lastError = error;
|
|
109
147
|
if (attempt < this.retryAttempts && this.isRetryableError(error)) {
|
|
110
148
|
await this.sleep(this.retryDelay * Math.pow(2, attempt - 1));
|
|
@@ -113,7 +151,18 @@ class ChatQueueManager extends EventEmitter {
|
|
|
113
151
|
break;
|
|
114
152
|
}
|
|
115
153
|
}
|
|
116
|
-
|
|
154
|
+
|
|
155
|
+
// 将最后的错误转换为友好消息
|
|
156
|
+
const errName = lastError?.name || '';
|
|
157
|
+
const isRetryError = errName === 'AI_RetryError' || errName === 'RetryError';
|
|
158
|
+
const friendlyMessage = isRetryError
|
|
159
|
+
? 'AI 服务暂时不可用,请稍后重试'
|
|
160
|
+
: (lastError?.message || String(lastError)).split('\n')[0];
|
|
161
|
+
|
|
162
|
+
const friendlyError = new Error(friendlyMessage);
|
|
163
|
+
friendlyError.originalError = lastError;
|
|
164
|
+
console.log('[ChatQueue] executeWithRetry: throwing friendly error:', friendlyMessage);
|
|
165
|
+
throw friendlyError;
|
|
117
166
|
}
|
|
118
167
|
|
|
119
168
|
/**
|
|
@@ -128,12 +177,34 @@ class ChatQueueManager extends EventEmitter {
|
|
|
128
177
|
|
|
129
178
|
const stream = item.executeFunction(item.message, item.options);
|
|
130
179
|
|
|
131
|
-
|
|
132
|
-
|
|
180
|
+
try {
|
|
181
|
+
for await (const chunk of stream) {
|
|
182
|
+
chunks.push(chunk);
|
|
183
|
+
this.emit('stream:chunk', {
|
|
184
|
+
requestId: item.id,
|
|
185
|
+
sessionId: item.sessionId,
|
|
186
|
+
chunk,
|
|
187
|
+
accumulated: chunks.length,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
// SDK 直接抛出错误(没有通过 chunk 传递)
|
|
192
|
+
// 转换为友好错误消息
|
|
193
|
+
const errName = err?.name || '';
|
|
194
|
+
const isRetryError = errName === 'AI_RetryError' || errName === 'RetryError';
|
|
195
|
+
const friendlyMessage = isRetryError
|
|
196
|
+
? 'AI 服务暂时不可用,请稍后重试'
|
|
197
|
+
: (err.message || err.toString()).split('\n')[0];
|
|
198
|
+
|
|
199
|
+
console.log(
|
|
200
|
+
'[ChatQueue] executeStream caught error, converting to friendly:',
|
|
201
|
+
friendlyMessage
|
|
202
|
+
);
|
|
203
|
+
chunks.push({ type: 'error', error: friendlyMessage });
|
|
133
204
|
this.emit('stream:chunk', {
|
|
134
205
|
requestId: item.id,
|
|
135
206
|
sessionId: item.sessionId,
|
|
136
|
-
chunk,
|
|
207
|
+
chunk: chunks[chunks.length - 1],
|
|
137
208
|
accumulated: chunks.length,
|
|
138
209
|
});
|
|
139
210
|
}
|
|
@@ -141,9 +212,19 @@ class ChatQueueManager extends EventEmitter {
|
|
|
141
212
|
// 检查是否有错误 chunk
|
|
142
213
|
const errorChunk = chunks.find((c) => c.type === 'error');
|
|
143
214
|
if (errorChunk) {
|
|
215
|
+
// 不再抛出错误,而是返回包含错误的 result
|
|
216
|
+
// 让调用者通过 result 判断是否有错误
|
|
144
217
|
const error = new Error(errorChunk.error || 'Stream error');
|
|
145
218
|
error.chunks = chunks;
|
|
146
|
-
|
|
219
|
+
error.isStreamError = true;
|
|
220
|
+
console.log('[ChatQueue] executeStream returning result with error:', error.message);
|
|
221
|
+
return {
|
|
222
|
+
chunks,
|
|
223
|
+
content: cleanResponse(''),
|
|
224
|
+
sessionId: item.sessionId,
|
|
225
|
+
requestId: item.id,
|
|
226
|
+
error,
|
|
227
|
+
};
|
|
147
228
|
}
|
|
148
229
|
|
|
149
230
|
const fullText = chunks
|
package/system.md
CHANGED
|
@@ -5,6 +5,85 @@
|
|
|
5
5
|
【元数据】
|
|
6
6
|
- WORK_DIR: D:\code\vb-agent
|
|
7
7
|
|
|
8
|
+
## 【Ambient Agent 监控任务指南】
|
|
9
|
+
|
|
10
|
+
当用户请求创建条件监控任务(如"价格高于X时通知我"、"收到某邮件时自动回复")时,使用以下模式:
|
|
11
|
+
|
|
12
|
+
### 标准条件监控配置
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
{
|
|
16
|
+
"title": "监控任务标题",
|
|
17
|
+
"description": "监控条件描述(包含阈值等信息)",
|
|
18
|
+
"conditions": { "events": ["scheduler:reminder"] },
|
|
19
|
+
"persistent": true,
|
|
20
|
+
"actions": [
|
|
21
|
+
{
|
|
22
|
+
"id": "get_data",
|
|
23
|
+
"type": "tool",
|
|
24
|
+
"plugin": "gate-trading", // 或 email, web 等
|
|
25
|
+
"name": "gate_get_market_info",
|
|
26
|
+
"args": { "currency_pair": "BTC_USDT", "info_type": "tickers" }
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "judge",
|
|
30
|
+
"type": "message",
|
|
31
|
+
"content": "你是一个条件判断助手。\n\n上一步获取到了数据:${result.last}\n\n判断规则:\n- 如果数据满足条件,调用 notification_send 发送通知\n- 如果不满足条件,静默结束(不要发送任何通知)\n\n你的任务是分析数据,判断是否需要通知用户。"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 关键要点
|
|
38
|
+
|
|
39
|
+
1. **actions 必须包含两个步骤**:
|
|
40
|
+
- 第一步:用 `type: "tool"` 获取数据
|
|
41
|
+
- 第二步:用 `type: "message"` 让 LLM 判断是否发送通知
|
|
42
|
+
|
|
43
|
+
2. **message content 中的判断提示词**:
|
|
44
|
+
- 明确告诉 LLM 阈值是多少
|
|
45
|
+
- 说明满足条件时应该做什么
|
|
46
|
+
- 说明不满足条件时应该静默
|
|
47
|
+
|
|
48
|
+
3. **使用 ${result.xxx} 引用上一步结果**:
|
|
49
|
+
- `${result.last}` - 获取价格数据
|
|
50
|
+
- `${result.from}` - 获取发件人
|
|
51
|
+
- `${result.subject}` - 获取邮件主题
|
|
52
|
+
|
|
53
|
+
### 示例:比特币价格监控
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
{
|
|
57
|
+
"title": "比特币价格监控 (BTC > $73000)",
|
|
58
|
+
"description": "每2分钟检查BTC价格,高于73000美元时通知",
|
|
59
|
+
"conditions": { "events": ["scheduler:reminder"] },
|
|
60
|
+
"persistent": true,
|
|
61
|
+
"actions": [
|
|
62
|
+
{
|
|
63
|
+
"type": "tool",
|
|
64
|
+
"plugin": "gate-trading",
|
|
65
|
+
"name": "gate_get_market_info",
|
|
66
|
+
"args": { "currency_pair": "BTC_USDT", "info_type": "tickers" }
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "message",
|
|
70
|
+
"content": "你是条件判断助手。\n\n上一步获取到 BTC 价格数据:${result.last} USDT\n\n监控条件:价格 > 73000 USDT\n\n判断规则:\n- 如果 ${result.last} > 73000,调用 notification_send 发送通知\n- 通知内容:当前 BTC 价格 ${result.last} USDT,已超过 $73000 阈值\n- 如果价格 <= 73000,静默结束,不要发送任何通知"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 常见监控场景配置
|
|
77
|
+
|
|
78
|
+
| 场景 | 工具 | 判断逻辑 |
|
|
79
|
+
|------|------|----------|
|
|
80
|
+
| 价格监控 | gate_get_market_info | 价格 > 阈值 |
|
|
81
|
+
| 邮件监控 | email_read | 主题/发件人匹配 |
|
|
82
|
+
| 余额监控 | gate_get_account_balance | 余额变化 |
|
|
83
|
+
| 网页监控 | web_request | 内容包含某字符串 |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
8
87
|
## 【记忆上下文】
|
|
9
88
|
### 【用户偏好】
|
|
10
89
|
- 用户海报风格偏好: ## 用户海报设计风格偏好
|