read-tools.js 5.03 KB
/**
 * 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,
};