foliko 1.1.63 → 1.1.64
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/sessions/cli_default.json +119 -301
- package/cli/bin/foliko.js +2 -2
- package/cli/src/commands/chat.js +15 -26
- package/cli/src/ui/chat-ui.js +102 -165
- package/cli/src/ui/footer-bar.js +7 -32
- package/cli/src/ui/message-bubble.js +24 -2
- package/cli/src/ui/status-bar.js +177 -0
- package/package.json +1 -2
- package/plugins/qq-plugin.js +1 -1
- package/src/core/agent-chat.js +50 -17
- package/src/core/agent.js +17 -27
- package/src/core/chat-session.js +7 -161
- package/src/core/constants.js +198 -0
- package/src/core/context-compressor.js +6 -181
- package/src/core/framework.js +125 -6
- package/src/core/plugin-base.js +7 -5
- package/src/core/provider.js +6 -0
- package/src/core/subagent.js +16 -135
- package/src/core/tool-executor.js +2 -70
- package/src/executors/mcp-executor.js +1 -1
- package/src/utils/chat-queue.js +11 -22
- package/src/utils/download.js +5 -4
- package/src/utils/message-validator.js +283 -0
- package/src/utils/retry.js +168 -22
- package/src/utils/sandbox.js +60 -207
- package/cli/src/utils/debounce.js +0 -106
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foliko Framework 统一常量
|
|
3
|
+
*
|
|
4
|
+
* 集中管理所有 magic numbers / strings,消除散落各处的重复值。
|
|
5
|
+
* 所有模块应引用此文件而非硬编码。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// AI 模型与提供者
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/** 默认 AI 提供者 */
|
|
15
|
+
const DEFAULT_PROVIDER = 'deepseek';
|
|
16
|
+
|
|
17
|
+
/** 默认 AI 模型 */
|
|
18
|
+
const DEFAULT_MODEL = 'deepseek-chat';
|
|
19
|
+
|
|
20
|
+
/** 默认 Max Output Tokens */
|
|
21
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 8192;
|
|
22
|
+
|
|
23
|
+
/** 默认 Temperature */
|
|
24
|
+
const DEFAULT_TEMPERATURE = 0.3;
|
|
25
|
+
|
|
26
|
+
/** 默认 Max Steps(工具调用最大轮数) */
|
|
27
|
+
const DEFAULT_MAX_STEPS = 20;
|
|
28
|
+
|
|
29
|
+
/** 需要禁用 temperature 的 thinking mode 模型列表 */
|
|
30
|
+
const THINKING_MODELS = [
|
|
31
|
+
'deepseek-v4-pro',
|
|
32
|
+
'deepseek-v4-flash',
|
|
33
|
+
'deepseek-reasoner',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Session 上下文
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
/** Session TTL:默认 30 分钟 */
|
|
41
|
+
const DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000;
|
|
42
|
+
|
|
43
|
+
/** Session 清理检查间隔 */
|
|
44
|
+
const DEFAULT_SESSION_CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
45
|
+
|
|
46
|
+
/** 每个 Session 最大消息数 */
|
|
47
|
+
const DEFAULT_MAX_MESSAGES_PER_SESSION = 1000;
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// 上下文压缩
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
/** 保留最近的 N 条消息 */
|
|
54
|
+
const KEEP_RECENT_MESSAGES = 20;
|
|
55
|
+
|
|
56
|
+
/** 智能压缩启用 */
|
|
57
|
+
const ENABLE_SMART_COMPRESS = true;
|
|
58
|
+
|
|
59
|
+
/** 压缩超时(毫秒) */
|
|
60
|
+
const COMPRESSION_TIMEOUT_MS = 1_200_000;
|
|
61
|
+
|
|
62
|
+
/** 工具结果最大字符数 */
|
|
63
|
+
const MAX_TOOL_RESULT_SIZE = 4000;
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// 消息队列
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
/** 默认最大并发数 */
|
|
70
|
+
const DEFAULT_MAX_CONCURRENT = 1;
|
|
71
|
+
|
|
72
|
+
/** 默认重试次数 */
|
|
73
|
+
const DEFAULT_RETRY_ATTEMPTS = 3;
|
|
74
|
+
|
|
75
|
+
/** 默认重试延迟(毫秒) */
|
|
76
|
+
const DEFAULT_RETRY_DELAY_MS = 2000;
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// System Prompt 优先级
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
const PROMPT_PRIORITY = {
|
|
83
|
+
DATETIME: 50,
|
|
84
|
+
ORIGINAL_PROMPT: 100,
|
|
85
|
+
SHARED_PROMPT: 200,
|
|
86
|
+
METADATA: 300,
|
|
87
|
+
TOOLS: 400,
|
|
88
|
+
SKILLS: 500,
|
|
89
|
+
SUB_AGENTS: 600,
|
|
90
|
+
CAPABILITIES: 700,
|
|
91
|
+
MCP_TOOLS: 750,
|
|
92
|
+
EXTENSION_TOOLS: 800,
|
|
93
|
+
TOOL_CORE_RULES: 95,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// 熔断器
|
|
98
|
+
// ============================================================================
|
|
99
|
+
|
|
100
|
+
/** 熔断器默认失败阈值 */
|
|
101
|
+
const DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 3;
|
|
102
|
+
|
|
103
|
+
/** 熔断器默认成功阈值 */
|
|
104
|
+
const DEFAULT_CIRCUIT_SUCCESS_THRESHOLD = 2;
|
|
105
|
+
|
|
106
|
+
/** 熔断器默认超时(毫秒) */
|
|
107
|
+
const DEFAULT_CIRCUIT_TIMEOUT_MS = 60_000;
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// 插件
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
/** 默认插件优先级 */
|
|
114
|
+
const DEFAULT_PLUGIN_PRIORITY = 100;
|
|
115
|
+
|
|
116
|
+
/** 系统插件名称集合 */
|
|
117
|
+
const SYSTEM_PLUGINS = new Set([
|
|
118
|
+
'ai', 'defaults', 'tools', 'skill-manager', 'session',
|
|
119
|
+
'storage', 'audit', 'rules', 'file-system',
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
/** Agent 配置目录名 */
|
|
123
|
+
const AGENT_DIR = '.agent';
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Session 存储
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
/** 默认 Session 存储类型 */
|
|
130
|
+
const DEFAULT_SESSION_STORAGE_TYPE = 'file';
|
|
131
|
+
|
|
132
|
+
/** 默认 Session 存储目录 */
|
|
133
|
+
const DEFAULT_SESSION_STORAGE_DIR = path.join(AGENT_DIR, 'sessions');
|
|
134
|
+
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// Token 计数(估算系数)
|
|
137
|
+
// ============================================================================
|
|
138
|
+
|
|
139
|
+
/** 每字符对应的 token 数(中文系数更大) */
|
|
140
|
+
const TOKENS_PER_CHAR = 0.25;
|
|
141
|
+
|
|
142
|
+
/** 每工具对应的 token 数 */
|
|
143
|
+
const TOKENS_PER_TOOL = 500;
|
|
144
|
+
|
|
145
|
+
/** 每消息固定开销 token */
|
|
146
|
+
const TOKENS_PER_MESSAGE_OVERHEAD = 4;
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// 导出
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
// AI
|
|
154
|
+
DEFAULT_PROVIDER,
|
|
155
|
+
DEFAULT_MODEL,
|
|
156
|
+
DEFAULT_MAX_OUTPUT_TOKENS,
|
|
157
|
+
DEFAULT_TEMPERATURE,
|
|
158
|
+
DEFAULT_MAX_STEPS,
|
|
159
|
+
THINKING_MODELS,
|
|
160
|
+
|
|
161
|
+
// Session
|
|
162
|
+
DEFAULT_SESSION_TTL_MS,
|
|
163
|
+
DEFAULT_SESSION_CLEANUP_INTERVAL_MS,
|
|
164
|
+
DEFAULT_MAX_MESSAGES_PER_SESSION,
|
|
165
|
+
|
|
166
|
+
// Compression
|
|
167
|
+
KEEP_RECENT_MESSAGES,
|
|
168
|
+
ENABLE_SMART_COMPRESS,
|
|
169
|
+
COMPRESSION_TIMEOUT_MS,
|
|
170
|
+
MAX_TOOL_RESULT_SIZE,
|
|
171
|
+
|
|
172
|
+
// Queue
|
|
173
|
+
DEFAULT_MAX_CONCURRENT,
|
|
174
|
+
DEFAULT_RETRY_ATTEMPTS,
|
|
175
|
+
DEFAULT_RETRY_DELAY_MS,
|
|
176
|
+
|
|
177
|
+
// Priority
|
|
178
|
+
PROMPT_PRIORITY,
|
|
179
|
+
|
|
180
|
+
// Circuit Breaker
|
|
181
|
+
DEFAULT_CIRCUIT_FAILURE_THRESHOLD,
|
|
182
|
+
DEFAULT_CIRCUIT_SUCCESS_THRESHOLD,
|
|
183
|
+
DEFAULT_CIRCUIT_TIMEOUT_MS,
|
|
184
|
+
|
|
185
|
+
// Plugin
|
|
186
|
+
DEFAULT_PLUGIN_PRIORITY,
|
|
187
|
+
SYSTEM_PLUGINS,
|
|
188
|
+
AGENT_DIR,
|
|
189
|
+
|
|
190
|
+
// Storage
|
|
191
|
+
DEFAULT_SESSION_STORAGE_TYPE,
|
|
192
|
+
DEFAULT_SESSION_STORAGE_DIR,
|
|
193
|
+
|
|
194
|
+
// Token
|
|
195
|
+
TOKENS_PER_CHAR,
|
|
196
|
+
TOKENS_PER_TOOL,
|
|
197
|
+
TOKENS_PER_MESSAGE_OVERHEAD,
|
|
198
|
+
};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const { logger } = require('../utils/logger');
|
|
11
11
|
const { TokenCounter } = require('./token-counter');
|
|
12
|
-
const {
|
|
12
|
+
const { validateMessagesPairing, filterPairedMessages } = require('../utils/message-validator');
|
|
13
13
|
|
|
14
14
|
// 模型上下文限制表
|
|
15
15
|
const MODEL_CONTEXT_LIMITS = {
|
|
@@ -30,7 +30,7 @@ const MODEL_CONTEXT_LIMITS = {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
// 压缩超时
|
|
33
|
-
const
|
|
33
|
+
const COMPRESSION_TIMEOUT_MS = 1200000;
|
|
34
34
|
|
|
35
35
|
class ContextCompressor {
|
|
36
36
|
/**
|
|
@@ -128,8 +128,8 @@ class ContextCompressor {
|
|
|
128
128
|
return new Promise((_, reject) => {
|
|
129
129
|
this._compressionTimeoutId = setTimeout(() => {
|
|
130
130
|
this._compressionTimeoutId = null;
|
|
131
|
-
reject(new Error(`Compression timeout (${
|
|
132
|
-
},
|
|
131
|
+
reject(new Error(`Compression timeout (${COMPRESSION_TIMEOUT_MS}ms)`));
|
|
132
|
+
}, COMPRESSION_TIMEOUT_MS);
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -242,122 +242,7 @@ class ContextCompressor {
|
|
|
242
242
|
* @private
|
|
243
243
|
*/
|
|
244
244
|
_filterPairedMessages(messages) {
|
|
245
|
-
|
|
246
|
-
const isToolCall = (item) =>
|
|
247
|
-
item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
|
|
248
|
-
const isToolResult = (item) =>
|
|
249
|
-
item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;
|
|
250
|
-
|
|
251
|
-
// 第一遍:找出所有 tool call 和它们的 results
|
|
252
|
-
const assistantToolCalls = new Map(); // toolCallId -> assistant message index
|
|
253
|
-
const toolResults = new Map(); // toolCallId -> tool result indices
|
|
254
|
-
|
|
255
|
-
for (let i = 0; i < messages.length; i++) {
|
|
256
|
-
const msg = messages[i];
|
|
257
|
-
if (msg.role === 'assistant') {
|
|
258
|
-
// 格式1: msg.content 中的 tool-call 块
|
|
259
|
-
if (msg.content) {
|
|
260
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
261
|
-
for (const item of content) {
|
|
262
|
-
if (isToolCall(item)) {
|
|
263
|
-
assistantToolCalls.set(item.toolCallId, i);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
// 格式2: msg.tool_calls 数组 (OpenAI 格式)
|
|
268
|
-
if (Array.isArray(msg.tool_calls)) {
|
|
269
|
-
for (const tc of msg.tool_calls) {
|
|
270
|
-
if (tc.id) {
|
|
271
|
-
assistantToolCalls.set(tc.id, i);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
277
|
-
for (const item of msg.content) {
|
|
278
|
-
if (isToolResult(item)) {
|
|
279
|
-
if (!toolResults.has(item.toolCallId)) {
|
|
280
|
-
toolResults.set(item.toolCallId, []);
|
|
281
|
-
}
|
|
282
|
-
toolResults.get(item.toolCallId).push(i);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// 第二遍:找出哪些 assistant 消息需要保留
|
|
289
|
-
const assistantIndicesToKeep = new Set();
|
|
290
|
-
for (let i = 0; i < messages.length; i++) {
|
|
291
|
-
const msg = messages[i];
|
|
292
|
-
if (msg.role === 'assistant') {
|
|
293
|
-
let hasToolCall = false;
|
|
294
|
-
// 检查 msg.content 格式
|
|
295
|
-
if (msg.content) {
|
|
296
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
297
|
-
for (const item of content) {
|
|
298
|
-
if (isToolCall(item)) {
|
|
299
|
-
hasToolCall = true;
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
// 检查 msg.tool_calls 格式 (OpenAI)
|
|
305
|
-
if (!hasToolCall && Array.isArray(msg.tool_calls)) {
|
|
306
|
-
for (const tc of msg.tool_calls) {
|
|
307
|
-
if (tc.id) {
|
|
308
|
-
hasToolCall = true;
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
if (hasToolCall) {
|
|
314
|
-
assistantIndicesToKeep.add(i);
|
|
315
|
-
} else if (i >= messages.length - 3) {
|
|
316
|
-
// 保留最近几条 assistant 消息
|
|
317
|
-
assistantIndicesToKeep.add(i);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// 如果有孤儿的 tool call(没有 result),保留该 assistant 消息
|
|
323
|
-
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
324
|
-
if (!toolResults.has(toolCallId)) {
|
|
325
|
-
assistantIndicesToKeep.add(assistantIdx);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// 第三遍:确定哪些 tool result 要保留
|
|
330
|
-
const indicesToKeep = new Set();
|
|
331
|
-
for (let i = 0; i < messages.length; i++) {
|
|
332
|
-
const msg = messages[i];
|
|
333
|
-
if (msg.role === 'tool') {
|
|
334
|
-
let hasPairedAssistant = false;
|
|
335
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
336
|
-
for (const item of content) {
|
|
337
|
-
if (isToolResult(item)) {
|
|
338
|
-
const assistantIdx = assistantToolCalls.get(item.toolCallId);
|
|
339
|
-
if (assistantIdx !== undefined && assistantIndicesToKeep.has(assistantIdx)) {
|
|
340
|
-
hasPairedAssistant = true;
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
if (hasPairedAssistant) {
|
|
346
|
-
indicesToKeep.add(i);
|
|
347
|
-
}
|
|
348
|
-
} else if (msg.role === 'assistant') {
|
|
349
|
-
if (assistantIndicesToKeep.has(i)) {
|
|
350
|
-
indicesToKeep.add(i);
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
// user, system 等角色默认保留
|
|
354
|
-
indicesToKeep.add(i);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// 按索引排序并返回
|
|
359
|
-
const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
|
|
360
|
-
return sortedIndices.map((i) => messages[i]);
|
|
245
|
+
return filterPairedMessages(messages);
|
|
361
246
|
}
|
|
362
247
|
|
|
363
248
|
/**
|
|
@@ -366,67 +251,7 @@ class ContextCompressor {
|
|
|
366
251
|
* @returns {Array} 验证后的消息
|
|
367
252
|
*/
|
|
368
253
|
validateMessagesPairing(messages) {
|
|
369
|
-
|
|
370
|
-
const assistantToolCallIds = new Set();
|
|
371
|
-
for (const msg of messages) {
|
|
372
|
-
if (msg.role === 'assistant') {
|
|
373
|
-
// 格式1: msg.content 中的 tool-call 块
|
|
374
|
-
if (Array.isArray(msg.content)) {
|
|
375
|
-
for (const item of msg.content) {
|
|
376
|
-
if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
|
|
377
|
-
assistantToolCallIds.add(item.toolCallId);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
// 格式2: msg.tool_calls 数组 (OpenAI 格式)
|
|
382
|
-
if (Array.isArray(msg.tool_calls)) {
|
|
383
|
-
for (const tc of msg.tool_calls) {
|
|
384
|
-
if (tc.id) {
|
|
385
|
-
assistantToolCallIds.add(tc.id);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// 检查并删除没有配对的 tool result
|
|
393
|
-
let removedCount = 0;
|
|
394
|
-
for (const msg of messages) {
|
|
395
|
-
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
396
|
-
const originalLength = msg.content.length;
|
|
397
|
-
msg.content = msg.content.filter((item) => {
|
|
398
|
-
// 兼容 tool-result 和 tool_result 两种类型
|
|
399
|
-
if (
|
|
400
|
-
item &&
|
|
401
|
-
(item.type === 'tool-result' || item.type === 'tool_result') &&
|
|
402
|
-
item.toolCallId
|
|
403
|
-
) {
|
|
404
|
-
if (!assistantToolCallIds.has(item.toolCallId)) {
|
|
405
|
-
removedCount++;
|
|
406
|
-
return false; // 删除没有配对的 tool result
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return true;
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
// 如果所有 content 都被删除了,标记整个消息待删除
|
|
413
|
-
if (msg.content.length === 0 && originalLength > 0) {
|
|
414
|
-
msg._orphaned = true;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// 移除被标记为 orphaned 的 tool 消息
|
|
420
|
-
const originalLength = messages.length;
|
|
421
|
-
const filtered = messages.filter((msg) => !(msg.role === 'tool' && msg._orphaned));
|
|
422
|
-
|
|
423
|
-
if (removedCount > 0 || filtered.length !== originalLength) {
|
|
424
|
-
logger.debug(
|
|
425
|
-
`Removed ${removedCount} orphaned tool-results, ${originalLength - filtered.length} orphaned tool messages`
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return filtered;
|
|
254
|
+
return validateMessagesPairing(messages);
|
|
430
255
|
}
|
|
431
256
|
|
|
432
257
|
/**
|
package/src/core/framework.js
CHANGED
|
@@ -324,6 +324,55 @@ class Framework extends EventEmitter {
|
|
|
324
324
|
return this.toolRegistry.getAll();
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// ============================================================================
|
|
328
|
+
// 公开 API — 减少外部直接访问私有属性
|
|
329
|
+
// ============================================================================
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 获取主 Agent
|
|
333
|
+
* @returns {Agent|null}
|
|
334
|
+
*/
|
|
335
|
+
getMainAgent() {
|
|
336
|
+
return this._mainAgent;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 获取主 Agent 的 System Prompt
|
|
341
|
+
* @returns {string}
|
|
342
|
+
*/
|
|
343
|
+
getSystemPrompt() {
|
|
344
|
+
if (this._mainAgent) {
|
|
345
|
+
return this._mainAgent._buildSystemPrompt();
|
|
346
|
+
}
|
|
347
|
+
return '';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 获取插件实例(安全访问)
|
|
352
|
+
* @param {string} name - 插件名称
|
|
353
|
+
* @returns {Object|null}
|
|
354
|
+
*/
|
|
355
|
+
getPluginInstance(name) {
|
|
356
|
+
const entry = this.pluginManager.get(name);
|
|
357
|
+
return entry || null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 获取所有 Agent 列表
|
|
362
|
+
* @returns {Array<Agent>}
|
|
363
|
+
*/
|
|
364
|
+
getAgents() {
|
|
365
|
+
return [...this._agents];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 获取所有 SessionContext 的 ID 列表
|
|
370
|
+
* @returns {string[]}
|
|
371
|
+
*/
|
|
372
|
+
listSessionContexts() {
|
|
373
|
+
return Array.from(this._sessionContexts.keys());
|
|
374
|
+
}
|
|
375
|
+
|
|
327
376
|
// ============================================================================
|
|
328
377
|
// 事件描述注册(供 Ambient Agent 使用)
|
|
329
378
|
// ============================================================================
|
|
@@ -551,6 +600,68 @@ class Framework extends EventEmitter {
|
|
|
551
600
|
}
|
|
552
601
|
}
|
|
553
602
|
|
|
603
|
+
// Session TTL 清理配置
|
|
604
|
+
_sessionTTL = 30 * 60 * 1000; // 默认 30 分钟
|
|
605
|
+
_sessionCleanupInterval = null;
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* 配置 Session TTL
|
|
609
|
+
* @param {number} ttlMs - TTL 毫秒数
|
|
610
|
+
*/
|
|
611
|
+
setSessionTTL(ttlMs) {
|
|
612
|
+
this._sessionTTL = ttlMs;
|
|
613
|
+
return this;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* 启动 Session 自动清理
|
|
618
|
+
* @param {number} [intervalMs=60000] - 检查间隔
|
|
619
|
+
*/
|
|
620
|
+
startSessionCleanup(intervalMs = 60000) {
|
|
621
|
+
if (this._sessionCleanupInterval) {
|
|
622
|
+
clearInterval(this._sessionCleanupInterval);
|
|
623
|
+
}
|
|
624
|
+
this._sessionCleanupInterval = setInterval(() => {
|
|
625
|
+
this._cleanupStaleSessions();
|
|
626
|
+
}, intervalMs);
|
|
627
|
+
// 允许进程退出时不阻止
|
|
628
|
+
if (this._sessionCleanupInterval.unref) {
|
|
629
|
+
this._sessionCleanupInterval.unref();
|
|
630
|
+
}
|
|
631
|
+
return this;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* 停止 Session 自动清理
|
|
636
|
+
*/
|
|
637
|
+
stopSessionCleanup() {
|
|
638
|
+
if (this._sessionCleanupInterval) {
|
|
639
|
+
clearInterval(this._sessionCleanupInterval);
|
|
640
|
+
this._sessionCleanupInterval = null;
|
|
641
|
+
}
|
|
642
|
+
return this;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* 清理过期的 Session
|
|
647
|
+
* @private
|
|
648
|
+
*/
|
|
649
|
+
_cleanupStaleSessions() {
|
|
650
|
+
const now = Date.now();
|
|
651
|
+
const staleIds = [];
|
|
652
|
+
for (const [sessionId, ctx] of this._sessionContexts) {
|
|
653
|
+
if (ctx.metadata && (now - ctx.metadata.lastActive) > this._sessionTTL) {
|
|
654
|
+
staleIds.push(sessionId);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
for (const id of staleIds) {
|
|
658
|
+
this.destroySessionContext(id);
|
|
659
|
+
}
|
|
660
|
+
if (staleIds.length > 0) {
|
|
661
|
+
this.logger.debug(`Cleaned up ${staleIds.length} stale sessions`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
554
665
|
/**
|
|
555
666
|
* 列出所有活跃的 Session ID
|
|
556
667
|
* @returns {string[]}
|
|
@@ -737,9 +848,9 @@ class Framework extends EventEmitter {
|
|
|
737
848
|
|
|
738
849
|
/**
|
|
739
850
|
* 同步主Agent的提示词部分到所有子Agent
|
|
740
|
-
* @
|
|
851
|
+
* @public — 插件可能需要调用
|
|
741
852
|
*/
|
|
742
|
-
|
|
853
|
+
syncPromptPartsToSubagents() {
|
|
743
854
|
if (!this._mainAgent || !this._mainAgent._systemPromptBuilder) return;
|
|
744
855
|
const mainParts = this._mainAgent._systemPromptBuilder._parts;
|
|
745
856
|
if (!mainParts) return;
|
|
@@ -784,8 +895,9 @@ class Framework extends EventEmitter {
|
|
|
784
895
|
lines.push('');
|
|
785
896
|
|
|
786
897
|
// 2. 主Agent的系统提示词(基础部分)
|
|
787
|
-
|
|
788
|
-
|
|
898
|
+
const mainAgent = this.getMainAgent();
|
|
899
|
+
if (mainAgent) {
|
|
900
|
+
const mainPrompt = mainAgent.getOriginalPrompt();
|
|
789
901
|
if (mainPrompt) {
|
|
790
902
|
lines.push('## 主Agent的系统提示词');
|
|
791
903
|
lines.push(mainPrompt);
|
|
@@ -988,10 +1100,17 @@ class Framework extends EventEmitter {
|
|
|
988
1100
|
this._toolRegistryListeners = [];
|
|
989
1101
|
}
|
|
990
1102
|
|
|
991
|
-
//
|
|
1103
|
+
// 停止 Session 清理
|
|
1104
|
+
this.stopSessionCleanup();
|
|
1105
|
+
|
|
1106
|
+
// 卸载所有插件(逐个保护,防止一个失败阻断后续)
|
|
992
1107
|
const plugins = this.pluginManager.getAll();
|
|
993
1108
|
for (const { name } of plugins) {
|
|
994
|
-
|
|
1109
|
+
try {
|
|
1110
|
+
await this.pluginManager.unload(name);
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
this.logger.warn(`Failed to unload plugin '${name}': ${err.message}`);
|
|
1113
|
+
}
|
|
995
1114
|
}
|
|
996
1115
|
|
|
997
1116
|
// 清空工具
|
package/src/core/plugin-base.js
CHANGED
|
@@ -286,10 +286,11 @@ class Plugin {
|
|
|
286
286
|
this._promptParts.push({ name, priority, provider });
|
|
287
287
|
|
|
288
288
|
// 如果主 agent 已存在,直接注册
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
const mainAgent = this._framework.getMainAgent();
|
|
290
|
+
if (mainAgent) {
|
|
291
|
+
mainAgent.registerPromptPart(name, priority, provider);
|
|
291
292
|
// 同步到所有子Agent
|
|
292
|
-
this._framework.
|
|
293
|
+
this._framework.syncPromptPartsToSubagents();
|
|
293
294
|
return;
|
|
294
295
|
}
|
|
295
296
|
|
|
@@ -307,14 +308,15 @@ class Plugin {
|
|
|
307
308
|
if (this._deferredPromptParts) {
|
|
308
309
|
for (const part of this._deferredPromptParts) {
|
|
309
310
|
try {
|
|
310
|
-
this._framework.
|
|
311
|
+
const mainAgent = this._framework.getMainAgent();
|
|
312
|
+
mainAgent?.registerPromptPart(part.name, part.priority, part.provider);
|
|
311
313
|
} catch (err) {
|
|
312
314
|
log.error(` Failed to register prompt part ${part.name}:`, err.message);
|
|
313
315
|
}
|
|
314
316
|
}
|
|
315
317
|
this._deferredPromptParts = null;
|
|
316
318
|
// 同步到所有子Agent
|
|
317
|
-
this._framework.
|
|
319
|
+
this._framework.syncPromptPartsToSubagents();
|
|
318
320
|
}
|
|
319
321
|
}, 100);
|
|
320
322
|
});
|
package/src/core/provider.js
CHANGED
|
@@ -14,26 +14,32 @@ const DEFAULT_PROVIDERS = {
|
|
|
14
14
|
openai: {
|
|
15
15
|
name: 'OpenAI',
|
|
16
16
|
baseURL: 'https://api.openai.com/v1',
|
|
17
|
+
defaultModel: 'gpt-4o',
|
|
17
18
|
},
|
|
18
19
|
ollama: {
|
|
19
20
|
name: 'Ollama',
|
|
20
21
|
baseURL: 'http://localhost:11434/v1',
|
|
22
|
+
defaultModel: 'llama3',
|
|
21
23
|
},
|
|
22
24
|
lmstudio: {
|
|
23
25
|
name: 'LM Studio',
|
|
24
26
|
baseURL: 'http://localhost:1234/v1',
|
|
27
|
+
defaultModel: 'local-model',
|
|
25
28
|
},
|
|
26
29
|
deepseek: {
|
|
27
30
|
name: 'DeepSeek',
|
|
28
31
|
baseURL: 'https://api.deepseek.com/v1',
|
|
32
|
+
defaultModel: 'deepseek-chat',
|
|
29
33
|
},
|
|
30
34
|
anthropic: {
|
|
31
35
|
name: 'Anthropic',
|
|
32
36
|
baseURL: 'https://api.anthropic.com/v1',
|
|
37
|
+
defaultModel: 'claude-sonnet-4-20250514',
|
|
33
38
|
},
|
|
34
39
|
minimax: {
|
|
35
40
|
name: 'MiniMax',
|
|
36
41
|
baseURL: 'https://api.minimaxi.com/v1',
|
|
42
|
+
defaultModel: 'MiniMax-M2.7',
|
|
37
43
|
},
|
|
38
44
|
};
|
|
39
45
|
|