test-memory-phase3.js 6.9 KB
#!/usr/bin/env node
/**
 * Memory System Phase 3 — 集成测试
 *
 * 测试:
 *   1. autoReflect: 高复杂度对话自动触发反思
 *   2. consolidateMemory: 过期 short-term 合并到 long-term
 *   3. generateDailyReport: 日报生成
 */

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 MEMORY_DIR = path.join(AGENT_DIR, 'memory');

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)); }

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

  // ── Test 1: 直接调用 autoReflect (模拟高复杂度对话) ──
  console.log('\n📋 Test 1: autoReflect 引擎');
  const { autoReflect } = require('../../server/services/memory-service');

  // 模拟一个 5-tool-call 的成功对话
  const mockMeta = {
    status: 'completed',
    stats: { toolCallCount: 6, messageCount: 3 },
    createdAt: new Date(Date.now() - 120000).toISOString(),
    updatedAt: new Date().toISOString(),
    title: '创建新的 Marketing Agent 并配置所有技能文件',
  };
  const mockOutput = `创建完成!以下是步骤:
1. 创建 data/agents/marketing 目录结构
2. 编写 agent.md 和 soul.md
3. 配置 identity.md (名称: Marketing Agent)
4. 编写 SKILL.md 主技能
5. 更新 config.json
6. 验证 Agent 列表

文件已写入:
- data/agents/marketing/agent.md
- data/agents/marketing/soul.md
- data/agents/marketing/.claude/skills/main_skill/SKILL.md

Agent 已成功创建并加入系统。`;

  const reflection = autoReflect(AGENT, 'test-reflect-001', mockMeta, mockOutput);

  assert(reflection !== null, 'autoReflect 返回了结果');
  assert(reflection.patterns.length >= 1, `提取了 ${reflection.patterns.length} 个模式`);
  assert(reflection.plan !== null, '自动固化了方案到小脑');

  // 验证反思日志
  const reflectDir = path.join(MEMORY_DIR, 'reflections');
  const reflectFiles = fs.existsSync(reflectDir)
    ? fs.readdirSync(reflectDir).filter(f => f.startsWith('reflect-'))
    : [];
  assert(reflectFiles.length >= 1, `reflections/ 有 ${reflectFiles.length} 个日志文件`);

  // 验证 memory.md 更新
  const memContent = fs.readFileSync(path.join(MEMORY_DIR, 'memory.md'), 'utf-8');
  assert(!memContent.includes('已验证的模式\n(尚无记录)'), 'memory.md "已验证模式" 段有内容');

  // 验证小脑方案
  const cerebellumFiles = fs.readdirSync(path.join(MEMORY_DIR, 'cerebellum'));
  assert(cerebellumFiles.length >= 3, `cerebellum/ 有 ${cerebellumFiles.length} 个方案 (含自动固化的)`);

  // ── Test 2: autoReflect 对简单对话不触发 ──
  console.log('\n📋 Test 2: 简单对话不触发反思');
  const simpleMeta = {
    status: 'completed',
    stats: { toolCallCount: 1, messageCount: 1 },
    createdAt: new Date(Date.now() - 10000).toISOString(),
    updatedAt: new Date().toISOString(),
    title: '回答一个简单问题',
  };
  const simpleResult = autoReflect(AGENT, 'test-simple', simpleMeta, '这是一个简单回答');
  assert(simpleResult === null, '简单对话 (1 tool, 10s) 不触发反思');

  // ── Test 3: autoReflect 对失败对话提取教训 ──
  console.log('\n📋 Test 3: 失败对话提取教训');
  const failMeta = {
    status: 'failed',
    stats: { toolCallCount: 3, messageCount: 2 },
    createdAt: new Date(Date.now() - 90000).toISOString(),
    updatedAt: new Date().toISOString(),
    title: '部署服务到生产环境',
  };
  const failOutput = 'error: Connection refused to database. Error: ECONNREFUSED 127.0.0.1:5432';
  const failReflect = autoReflect(AGENT, 'test-fail', failMeta, failOutput);
  assert(failReflect !== null, '失败对话触发了反思');
  assert(failReflect.lessons.length >= 1, `提取了 ${failReflect.lessons.length} 个教训`);

  // ── Test 4: consolidateMemory ──
  console.log('\n📋 Test 4: 记忆合并');
  // 创建一些"过期"的 short-term 文件 (时间戳 = 8天前)
  const shortDir = path.join(MEMORY_DIR, 'short-term');
  const eightDaysAgo = Date.now() - 8 * 24 * 60 * 60 * 1000;
  for (let i = 0; i < 3; i++) {
    const ts = eightDaysAgo + i * 1000;
    fs.writeFileSync(path.join(shortDir, `session-${ts}.md`),
      `# Session 摘要\n\n- **时间**: 2026-03-17 10:${i}0:00\n\n## 任务\n测试任务${i}\n\n## 结果\n测试结果${i}\n`, 'utf-8');
  }

  const consolidateResult = await post(`${BASE}/api/agent/${AGENT}/memory/consolidate`, {});
  assert(consolidateResult.merged === 3, `合并了 ${consolidateResult.merged} 个过期 session (期望 3)`);

  // 验证 long-term 目录有文件
  const longTermDir = path.join(MEMORY_DIR, 'long-term');
  const ltFiles = fs.existsSync(longTermDir)
    ? fs.readdirSync(longTermDir).filter(f => f.startsWith('consolidate-'))
    : [];
  assert(ltFiles.length >= 1, `long-term/ 有 ${ltFiles.length} 个合并记录`);

  // 验证过期文件已删除
  const remainingShort = fs.readdirSync(shortDir).filter(f => {
    const ts = parseInt(f.replace('session-', '').replace('.md', ''));
    return !isNaN(ts) && ts < eightDaysAgo + 5000;
  });
  assert(remainingShort.length === 0, '过期的 short-term 文件已删除');

  // ── Test 5: generateDailyReport ──
  console.log('\n📋 Test 5: 日报生成');
  const reportResult = await post(`${BASE}/api/agent/${AGENT}/memory/daily-report`, {});
  assert(reportResult.success, '日报生成成功');

  const today = new Date().toISOString().substring(0, 10);
  const dailyFile = path.join(MEMORY_DIR, 'daily', `${today}.md`);
  assert(fs.existsSync(dailyFile), `daily/${today}.md 文件存在`);

  if (fs.existsSync(dailyFile)) {
    const dailyContent = fs.readFileSync(dailyFile, 'utf-8');
    assert(dailyContent.includes('# 日报'), '日报包含标题');
    assert(dailyContent.includes('对话数'), '日报包含统计');
  }

  // ── 结果 ──
  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); });