Commit da9c469db870600a8ca7d3192992a365f751f25c
1 parent
a50f59d5
perf: task list cache (10s TTL), base file memory cache, messages.log 512KB read limit
Showing
3 changed files
with
53 additions
and
12 deletions
server/routes/agent-chat.js
| ... | ... | @@ -238,6 +238,26 @@ function loadFile(agentDir, filename) { |
| 238 | 238 | // ═══════════════════════════════════════ |
| 239 | 239 | const BASE_DIR = path.join(__dirname, '../../packages/agent-core/base'); |
| 240 | 240 | |
| 241 | +// ── 启动时预加载 common + type 基类文件到内存(几乎不变的文件,避免每次请求读盘) ── | |
| 242 | +const _baseFileCache = new Map(); | |
| 243 | +function _loadBaseFile(filepath) { | |
| 244 | + if (_baseFileCache.has(filepath)) return _baseFileCache.get(filepath); | |
| 245 | + try { | |
| 246 | + const content = fs.readFileSync(filepath, 'utf-8').trim(); | |
| 247 | + _baseFileCache.set(filepath, content); | |
| 248 | + return content; | |
| 249 | + } catch { return ''; } | |
| 250 | +} | |
| 251 | +// 预热 common 文件 | |
| 252 | +['methodology.md', 'tools-base.md', 'capabilities.md'].forEach(f => { | |
| 253 | + _loadBaseFile(path.join(BASE_DIR, 'common', f)); | |
| 254 | +}); | |
| 255 | +// 预热 type 基类 | |
| 256 | +['master', 'cxo', 'skill', 'orchestrator', 'worker'].forEach(t => { | |
| 257 | + _loadBaseFile(path.join(BASE_DIR, t, 'agent-base.md')); | |
| 258 | + _loadBaseFile(path.join(BASE_DIR, t, 'cron-base.json')); | |
| 259 | +}); | |
| 260 | + | |
| 241 | 261 | /** |
| 242 | 262 | * 构建 system prompt — 三层继承 |
| 243 | 263 | * |
| ... | ... | @@ -282,18 +302,14 @@ function buildSystemPrompt(agentDir, userMessage) { |
| 282 | 302 | const commonFiles = ['methodology.md', 'tools-base.md', 'capabilities.md']; |
| 283 | 303 | for (const f of commonFiles) { |
| 284 | 304 | const fp = path.join(BASE_DIR, 'common', f); |
| 285 | - if (fs.existsSync(fp)) { | |
| 286 | - const content = fs.readFileSync(fp, 'utf-8').trim(); | |
| 287 | - if (content) parts.push(content); | |
| 288 | - } | |
| 305 | + const content = _loadBaseFile(fp); | |
| 306 | + if (content) parts.push(content); | |
| 289 | 307 | } |
| 290 | 308 | |
| 291 | 309 | // ── Layer 2: 类型基类 ── |
| 292 | 310 | const typeBaseAgent = path.join(BASE_DIR, agentType, 'agent-base.md'); |
| 293 | - if (fs.existsSync(typeBaseAgent)) { | |
| 294 | - const content = fs.readFileSync(typeBaseAgent, 'utf-8').trim(); | |
| 295 | - if (content) parts.push(content); | |
| 296 | - } | |
| 311 | + const typeContent = _loadBaseFile(typeBaseAgent); | |
| 312 | + if (typeContent) parts.push(typeContent); | |
| 297 | 313 | |
| 298 | 314 | // ── Layer 3: Agent 专属文件 ── |
| 299 | 315 | const agentListText = Object.entries(fullSystemConfig.agents || {}) |
| ... | ... | @@ -399,12 +415,13 @@ function buildCronInstructions(agentDir) { |
| 399 | 415 | |
| 400 | 416 | // Layer 1: 基类 cron |
| 401 | 417 | const baseCronPath = path.join(BASE_DIR, agentType, 'cron-base.json'); |
| 402 | - if (fs.existsSync(baseCronPath)) { | |
| 418 | + const baseCronContent = _loadBaseFile(baseCronPath); | |
| 419 | + if (baseCronContent) { | |
| 403 | 420 | try { |
| 404 | - const base = JSON.parse(fs.readFileSync(baseCronPath, 'utf-8')); | |
| 421 | + const base = JSON.parse(baseCronContent); | |
| 405 | 422 | allCrons = allCrons.concat(base); |
| 406 | 423 | } catch (err) { |
| 407 | - console.error(`[Cron] ⚠️ 读取 ${baseCronPath} 失败:`, err.message); | |
| 424 | + console.error(`[Cron] ⚠️ 解析 ${baseCronPath} 失败:`, err.message); | |
| 408 | 425 | } |
| 409 | 426 | } |
| 410 | 427 | ... | ... |
server/routes/tasks.js
| ... | ... | @@ -4,9 +4,18 @@ const { TaskService } = require('../services/task-service'); |
| 4 | 4 | |
| 5 | 5 | const taskService = new TaskService(); |
| 6 | 6 | |
| 7 | +// ── Task List 缓存 (10秒 TTL) ── | |
| 8 | +let _taskListCache = null; | |
| 9 | +let _taskListCacheTime = 0; | |
| 10 | +const TASK_LIST_CACHE_TTL = 10000; | |
| 11 | + | |
| 7 | 12 | // GET /api/tasks — 扫描获取系统中聚合的 Task 列表(含父子关系) |
| 8 | 13 | router.get('/', (req, res) => { |
| 9 | 14 | try { |
| 15 | + if (_taskListCache && Date.now() - _taskListCacheTime < TASK_LIST_CACHE_TTL) { | |
| 16 | + return res.json(_taskListCache); | |
| 17 | + } | |
| 18 | + | |
| 10 | 19 | const tasks = taskService.getAggregatedTasks(); |
| 11 | 20 | |
| 12 | 21 | const taskList = tasks.map(t => ({ |
| ... | ... | @@ -24,6 +33,9 @@ router.get('/', (req, res) => { |
| 24 | 33 | conversationCount: t.conversations.length, |
| 25 | 34 | })); |
| 26 | 35 | |
| 36 | + _taskListCache = taskList; | |
| 37 | + _taskListCacheTime = Date.now(); | |
| 38 | + | |
| 27 | 39 | res.json(taskList); |
| 28 | 40 | } catch (err) { |
| 29 | 41 | res.status(500).json({ error: err.message }); | ... | ... |
server/services/task-service.js
| ... | ... | @@ -290,7 +290,19 @@ class TaskService { |
| 290 | 290 | |
| 291 | 291 | if (fs.existsSync(logPath)) { |
| 292 | 292 | try { |
| 293 | - const logContent = fs.readFileSync(logPath, 'utf-8'); | |
| 293 | + // 限制最大读取 512KB,超出则只读尾部 | |
| 294 | + const MAX_LOG_READ = 512 * 1024; | |
| 295 | + const stat = fs.statSync(logPath); | |
| 296 | + let logContent; | |
| 297 | + if (stat.size > MAX_LOG_READ) { | |
| 298 | + const fd = fs.openSync(logPath, 'r'); | |
| 299 | + const buf = Buffer.alloc(MAX_LOG_READ); | |
| 300 | + fs.readSync(fd, buf, 0, MAX_LOG_READ, stat.size - MAX_LOG_READ); | |
| 301 | + fs.closeSync(fd); | |
| 302 | + logContent = buf.toString('utf-8'); | |
| 303 | + } else { | |
| 304 | + logContent = fs.readFileSync(logPath, 'utf-8'); | |
| 305 | + } | |
| 294 | 306 | const blocks = this._parseMessagesLog(logContent, conv); |
| 295 | 307 | timeline.push(...blocks); |
| 296 | 308 | } catch (e) { | ... | ... |