/** * read-tools.js — Master Agent 的读取工具 * * 让 Master Agent 可以: * - 读取 retrain job 的 checkpoint 状态 * - 读取 sub-agent 的 log 文件尾部 * - 列出活跃的 retrain jobs */ const fs = require('fs'); const path = require('path'); const CONVERSATIONS_DIR = path.resolve(__dirname, '../../../conversations'); /** * read_checkpoint 工具 — 读取 retrain job 的 checkpoint */ function createReadCheckpointTool() { return { name: 'read_checkpoint', description: `读取 retrain 任务的 checkpoint 状态。返回当前进行到哪一步 (hunt/digest/evolve/publish),状态 (running/done/failed),重试次数等。 适用场景: - 检查 retrain 任务进度 - 巡检时判断任务是否卡住 - 恢复任务前了解断点位置`, parameters: { type: 'object', required: ['jobId'], properties: { jobId: { type: 'string', description: 'Retrain Job ID,如 "job-1773311603902-8d5a39c0"', }, }, }, async execute(params) { const { jobId } = params; const checkpointPath = path.join(CONVERSATIONS_DIR, `retrain-${jobId}`, 'checkpoint.json'); if (!fs.existsSync(checkpointPath)) { return { status: 'not_found', message: `Job ${jobId} 没有 checkpoint 文件` }; } try { const checkpoint = JSON.parse(fs.readFileSync(checkpointPath, 'utf-8')); return { status: 'ok', checkpoint }; } catch (err) { return { status: 'error', message: `读取 checkpoint 失败: ${err.message}` }; } }, }; } /** * read_log 工具 — 读取 sub-agent 的 log 文件尾部 */ function createReadLogTool() { return { name: 'read_log', description: `读取 retrain 任务中 sub-agent 的日志尾部。可以查看 l3-hunt、l2-digest、innovator-evolver 的最新输出。 适用场景: - 检查 sub-agent 的执行进度 - 判断 agent 是否产出了有价值的内容 - 分析失败原因`, parameters: { type: 'object', required: ['jobId'], properties: { jobId: { type: 'string', description: 'Retrain Job ID', }, agent: { type: 'string', description: '要读取的 agent 名称 (如 "innovator-hunter"),不填则读取所有 agent 日志', }, lines: { type: 'number', description: '读取最后 N 行,默认 30', }, }, }, async execute(params) { const { jobId, agent, lines = 30 } = params; const trainDir = path.join(CONVERSATIONS_DIR, `retrain-${jobId}`, 'train'); if (!fs.existsSync(trainDir)) { return { status: 'not_found', message: `Job ${jobId} 没有 train 目录` }; } const logFiles = fs.readdirSync(trainDir).filter(f => f.endsWith('.log')); if (logFiles.length === 0) { return { status: 'empty', message: '没有 log 文件' }; } const results = {}; for (const logFile of logFiles) { const agentName = logFile.replace('.log', ''); if (agent && agentName !== agent) continue; try { const content = fs.readFileSync(path.join(trainDir, logFile), 'utf-8'); const allLines = content.split('\n').filter(l => l.trim()); const tail = allLines.slice(-lines); results[agentName] = { totalLines: allLines.length, fileSize: `${Math.round(content.length / 1024)}KB`, tail: tail.join('\n'), }; } catch (err) { results[agentName] = { error: err.message }; } } return { status: 'ok', logs: results }; }, }; } /** * list_retrain_jobs 工具 — 列出活跃的 retrain jobs */ function createListRetrainJobsTool() { return { name: 'list_retrain_jobs', description: `列出当前所有 retrain 任务及其状态。 适用场景: - 查看有哪些正在进行的 retrain 任务 - 巡检时扫描所有任务状态`, parameters: { type: 'object', properties: {}, }, async execute(_params, context) { const { jobManager } = context; if (!jobManager) { // fallback: 扫描 conversations 目录 if (!fs.existsSync(CONVERSATIONS_DIR)) return { status: 'ok', jobs: [] }; const dirs = fs.readdirSync(CONVERSATIONS_DIR) .filter(d => d.startsWith('retrain-') && fs.statSync(path.join(CONVERSATIONS_DIR, d)).isDirectory()); const jobs = []; for (const dir of dirs) { const cpPath = path.join(CONVERSATIONS_DIR, dir, 'checkpoint.json'); let checkpoint = null; try { checkpoint = JSON.parse(fs.readFileSync(cpPath, 'utf-8')); } catch {} jobs.push({ conversationId: dir, jobId: dir.replace('retrain-', ''), checkpoint, }); } return { status: 'ok', jobs }; } const jobs = jobManager.listJobs({ limit: 20 }); return { status: 'ok', jobs }; }, }; } module.exports = { createReadCheckpointTool, createReadLogTool, createListRetrainJobsTool, };