test-memory.js 6.36 KB
#!/usr/bin/env node
/**
 * Memory System Phase 1 MVP — 集成测试
 * 
 * 测试流程:
 *   1. 清理 CEO 的 short-term 和 memory.md
 *   2. 发送第一次对话 → 验证 session 摘要和 memory.md 写入
 *   3. 发送第二次对话 → 验证 prompt 中包含近期记忆
 */

const http = require('http');
const fs = require('fs');
const path = require('path');

const BASE = 'http://localhost:3000';
const AGENT = 'ceo';
const AGENT_DIR = path.resolve(__dirname, '../../data/agents', AGENT);
const SHORT_TERM_DIR = path.join(AGENT_DIR, 'memory', 'short-term');
const MEMORY_MD = path.join(AGENT_DIR, 'memory', 'memory.md');

let passed = 0, failed = 0;

function assert(condition, msg) {
  if (condition) { passed++; console.log(`  ✅ ${msg}`); }
  else { failed++; console.log(`  ❌ ${msg}`); }
}

function post(url, body) {
  return new Promise((resolve, reject) => {
    const data = JSON.stringify(body);
    const u = new URL(url);
    const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }
    }, res => {
      let buf = '';
      res.on('data', c => buf += c);
      res.on('end', () => { try { resolve(JSON.parse(buf)); } catch { resolve(buf); } });
    });
    req.on('error', reject);
    req.write(data);
    req.end();
  });
}

function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

function waitForCompletion(agentId, convId, timeoutMs = 120000) {
  const metaPath = path.join(AGENT_DIR, '..', agentId, 'conversations', convId, 'meta.json');
  const start = Date.now();
  return new Promise((resolve, reject) => {
    const check = () => {
      if (Date.now() - start > timeoutMs) return reject(new Error('Timeout'));
      try {
        const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
        if (meta.status === 'completed' || meta.status === 'failed') return resolve(meta);
      } catch {}
      setTimeout(check, 2000);
    };
    check();
  });
}

async function main() {
  console.log('\n🧪 Memory System Phase 1 — 集成测试\n');
  console.log('═'.repeat(50));

  // ── Step 0: 清理 ──
  console.log('\n📋 Step 0: 清理历史数据');
  // 清理 short-term
  if (fs.existsSync(SHORT_TERM_DIR)) {
    const files = fs.readdirSync(SHORT_TERM_DIR).filter(f => f.startsWith('session-'));
    files.forEach(f => fs.unlinkSync(path.join(SHORT_TERM_DIR, f)));
    console.log(`  🗑️  清理了 ${files.length} 个 session 文件`);
  }
  // 重置 memory.md
  fs.writeFileSync(MEMORY_MD, '# 我的记忆\n\n## 关键事实\n(尚无记录)\n\n## 已验证的模式\n(尚无记录)\n\n## 失败教训\n(尚无记录)\n', 'utf-8');
  console.log('  🗑️  memory.md 已重置');

  // ── Step 1: 第一次对话 ──
  console.log('\n📋 Step 1: 发送第一次对话');
  const resp1 = await post(`${BASE}/api/agent/task/dispatch`, {
    from: 'master', to: AGENT,
    task: '请用一句话总结BLM模型的核心思想。不需要使用任何工具,直接回答即可。',
    priority: 'P2', autoExecute: true,
  });
  assert(resp1.success, `dispatch 成功 (taskId: ${resp1.taskId})`);
  const convId1 = resp1.executionConvId;

  console.log('  ⏳ 等待对话完成...');
  const meta1 = await waitForCompletion(AGENT, convId1);
  assert(meta1.status === 'completed', `对话完成 (status: ${meta1.status})`);

  // 等待 memory 写入(异步)
  await sleep(2000);

  // 验证 short-term
  const sessions1 = fs.existsSync(SHORT_TERM_DIR)
    ? fs.readdirSync(SHORT_TERM_DIR).filter(f => f.startsWith('session-'))
    : [];
  assert(sessions1.length === 1, `short-term 有 ${sessions1.length} 个 session 文件`);

  if (sessions1.length > 0) {
    const sessionContent = fs.readFileSync(path.join(SHORT_TERM_DIR, sessions1[0]), 'utf-8');
    assert(sessionContent.includes('# Session 摘要'), 'session 包含标题');
    assert(sessionContent.includes('BLM') || sessionContent.includes('战略'), 'session 包含任务关键词');
    assert(sessionContent.includes('工具调用'), 'session 包含 toolCall 统计');
    console.log(`  📄 Session 文件: ${sessions1[0]} (${sessionContent.length} 字)`);
  }

  // 验证 memory.md
  const memContent1 = fs.readFileSync(MEMORY_MD, 'utf-8');
  const hasNewFact = !memContent1.includes('(尚无记录)') || memContent1.match(/- \[202/);
  assert(hasNewFact, `memory.md 有新事实记录`);
  console.log(`  📝 memory.md: ${memContent1.length} 字`);

  // ── Step 2: 第二次对话 ──
  console.log('\n📋 Step 2: 发送第二次对话 (验证近期记忆注入)');
  const resp2 = await post(`${BASE}/api/agent/task/dispatch`, {
    from: 'master', to: AGENT,
    task: '你还记得刚才我们讨论了什么模型吗?如果你的记忆中有相关信息,请引用它。不需要使用工具。',
    priority: 'P2', autoExecute: true,
  });
  assert(resp2.success, `第二次 dispatch 成功 (taskId: ${resp2.taskId})`);
  const convId2 = resp2.executionConvId;

  console.log('  ⏳ 等待第二次对话完成...');
  const meta2 = await waitForCompletion(AGENT, convId2);
  assert(meta2.status === 'completed', `第二次对话完成 (status: ${meta2.status})`);

  await sleep(2000);

  // 验证 short-term 增长
  const sessions2 = fs.readdirSync(SHORT_TERM_DIR).filter(f => f.startsWith('session-'));
  assert(sessions2.length === 2, `short-term 有 ${sessions2.length} 个 session 文件 (期望 2)`);

  // 验证 memory.md 条目增长
  const memContent2 = fs.readFileSync(MEMORY_MD, 'utf-8');
  const factCount = (memContent2.match(/- \[202/g) || []).length;
  assert(factCount >= 2, `memory.md 有 ${factCount} 条事实 (期望 ≥2)`);

  // 检查最后的 messages.log 是否表明 Agent "记住了"
  try {
    const logPath = path.join(AGENT_DIR, 'conversations', convId2, 'messages.log');
    const log2 = fs.readFileSync(logPath, 'utf-8');
    const mentionsBLM = log2.includes('BLM') || log2.includes('记忆') || log2.includes('之前');
    assert(mentionsBLM, `Agent 在第二次对话中引用了之前的信息`);
  } catch { console.log('  ⚠️  无法检查 messages.log'); }

  // ── 结果 ──
  console.log('\n' + '═'.repeat(50));
  console.log(`\n🏁 测试结果: ${passed} 通过, ${failed} 失败\n`);


  if (failed > 0) process.exit(1);
}

main().catch(err => { console.error('❌ 测试崩溃:', err); process.exit(1); });