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,6 +238,26 @@ function loadFile(agentDir, filename) { | ||
| 238 | // ═══════════════════════════════════════ | 238 | // ═══════════════════════════════════════ |
| 239 | const BASE_DIR = path.join(__dirname, '../../packages/agent-core/base'); | 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 | * 构建 system prompt — 三层继承 | 262 | * 构建 system prompt — 三层继承 |
| 243 | * | 263 | * |
| @@ -282,18 +302,14 @@ function buildSystemPrompt(agentDir, userMessage) { | @@ -282,18 +302,14 @@ function buildSystemPrompt(agentDir, userMessage) { | ||
| 282 | const commonFiles = ['methodology.md', 'tools-base.md', 'capabilities.md']; | 302 | const commonFiles = ['methodology.md', 'tools-base.md', 'capabilities.md']; |
| 283 | for (const f of commonFiles) { | 303 | for (const f of commonFiles) { |
| 284 | const fp = path.join(BASE_DIR, 'common', f); | 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 | // ── Layer 2: 类型基类 ── | 309 | // ── Layer 2: 类型基类 ── |
| 292 | const typeBaseAgent = path.join(BASE_DIR, agentType, 'agent-base.md'); | 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 | // ── Layer 3: Agent 专属文件 ── | 314 | // ── Layer 3: Agent 专属文件 ── |
| 299 | const agentListText = Object.entries(fullSystemConfig.agents || {}) | 315 | const agentListText = Object.entries(fullSystemConfig.agents || {}) |
| @@ -399,12 +415,13 @@ function buildCronInstructions(agentDir) { | @@ -399,12 +415,13 @@ function buildCronInstructions(agentDir) { | ||
| 399 | 415 | ||
| 400 | // Layer 1: 基类 cron | 416 | // Layer 1: 基类 cron |
| 401 | const baseCronPath = path.join(BASE_DIR, agentType, 'cron-base.json'); | 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 | try { | 420 | try { |
| 404 | - const base = JSON.parse(fs.readFileSync(baseCronPath, 'utf-8')); | 421 | + const base = JSON.parse(baseCronContent); |
| 405 | allCrons = allCrons.concat(base); | 422 | allCrons = allCrons.concat(base); |
| 406 | } catch (err) { | 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,9 +4,18 @@ const { TaskService } = require('../services/task-service'); | ||
| 4 | 4 | ||
| 5 | const taskService = new TaskService(); | 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 | // GET /api/tasks — 扫描获取系统中聚合的 Task 列表(含父子关系) | 12 | // GET /api/tasks — 扫描获取系统中聚合的 Task 列表(含父子关系) |
| 8 | router.get('/', (req, res) => { | 13 | router.get('/', (req, res) => { |
| 9 | try { | 14 | try { |
| 15 | + if (_taskListCache && Date.now() - _taskListCacheTime < TASK_LIST_CACHE_TTL) { | ||
| 16 | + return res.json(_taskListCache); | ||
| 17 | + } | ||
| 18 | + | ||
| 10 | const tasks = taskService.getAggregatedTasks(); | 19 | const tasks = taskService.getAggregatedTasks(); |
| 11 | 20 | ||
| 12 | const taskList = tasks.map(t => ({ | 21 | const taskList = tasks.map(t => ({ |
| @@ -24,6 +33,9 @@ router.get('/', (req, res) => { | @@ -24,6 +33,9 @@ router.get('/', (req, res) => { | ||
| 24 | conversationCount: t.conversations.length, | 33 | conversationCount: t.conversations.length, |
| 25 | })); | 34 | })); |
| 26 | 35 | ||
| 36 | + _taskListCache = taskList; | ||
| 37 | + _taskListCacheTime = Date.now(); | ||
| 38 | + | ||
| 27 | res.json(taskList); | 39 | res.json(taskList); |
| 28 | } catch (err) { | 40 | } catch (err) { |
| 29 | res.status(500).json({ error: err.message }); | 41 | res.status(500).json({ error: err.message }); |
server/services/task-service.js
| @@ -290,7 +290,19 @@ class TaskService { | @@ -290,7 +290,19 @@ class TaskService { | ||
| 290 | 290 | ||
| 291 | if (fs.existsSync(logPath)) { | 291 | if (fs.existsSync(logPath)) { |
| 292 | try { | 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 | const blocks = this._parseMessagesLog(logContent, conv); | 306 | const blocks = this._parseMessagesLog(logContent, conv); |
| 295 | timeline.push(...blocks); | 307 | timeline.push(...blocks); |
| 296 | } catch (e) { | 308 | } catch (e) { |