demo-harness-company.js 12.2 KB
#!/usr/bin/env node
/**
 * demo-harness-company.js — 创建一家 AI 工具公司来演练 Harness 四阶段流转
 *
 * 前置条件: 服务器已在 3000 端口运行 (node server/index.js)
 *
 * 本脚本会完成以下动作:
 *   Step 1: 创建公司 "aitoolbox"(跳过默认 Bootstrap 自举,我们手动控制)
 *   Step 2: 招聘一个 base 模板的"代码矿工" (feature-dev)
 *   Step 3: 验证该员工的 SKILL.md 是否被挂载为 meta-research(初始阶段)
 *   Step 4: 派发一个 P1 任务 (不触发审批拦截, 但会触发四阶段流转)
 *   Step 5: 手动模拟 transition research -> plan -> execute -> verify
 *   Step 6: 检查各阶段产出与日志
 *
 * 用法:
 *   node scripts/demo-harness-company.js
 */

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

const API = 'http://localhost:3000/api';
const ROOT = path.resolve(__dirname, '..');
const COMPANY_ID = 'aitoolbox';
const WORKER_ROLE = 'feature-dev';
const WORKER_ID = `${COMPANY_ID}:${WORKER_ROLE}`;

function post(urlPath, body) {
  return new Promise((resolve, reject) => {
    const data = JSON.stringify(body);
    const url = new URL(API + urlPath);
    const req = http.request({
      hostname: url.hostname, port: url.port, path: url.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({ status: res.statusCode, data: JSON.parse(buf) }); }
        catch { resolve({ status: res.statusCode, data: buf }); }
      });
    });
    req.on('error', reject);
    req.write(data);
    req.end();
  });
}

function get(urlPath) {
  return new Promise((resolve, reject) => {
    const url = new URL(API + urlPath);
    http.get(url.href, (res) => {
      let buf = '';
      res.on('data', c => buf += c);
      res.on('end', () => {
        try { resolve({ status: res.statusCode, data: JSON.parse(buf) }); }
        catch { resolve({ status: res.statusCode, data: buf }); }
      });
    }).on('error', reject);
  });
}

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

function banner(msg) { console.log(`\n${'═'.repeat(60)}\n  ${msg}\n${'═'.repeat(60)}`); }
function step(n, msg) { console.log(`\n🔹 Step ${n}: ${msg}`); }
function ok(msg) { console.log(`   ✅ ${msg}`); }
function warn(msg) { console.log(`   ⚠️  ${msg}`); }
function fail(msg) { console.log(`   ❌ ${msg}`); }

async function main() {
  banner('🏢 AI ToolBox 公司 — Harness Engineering 端到端演练');

  // ──────────────────────────────────────
  // Step 1: 创建公司 (跳过 Bootstrap)
  // ──────────────────────────────────────
  step(1, '创建公司 aitoolbox (跳过默认 Bootstrap)');
  const createRes = await post('/company/create', {
    id: COMPANY_ID,
    name: 'AI ToolBox',
    description: '专注于 AI 开发者工具的极客公司',
    skipBootstrap: true,
  });
  if (createRes.status === 200 || createRes.status === 201) {
    ok(`公司 "${COMPANY_ID}" 创建成功`);
  } else if (createRes.data?.error?.includes('已存在')) {
    warn(`公司 "${COMPANY_ID}" 已存在, 继续执行`);
  } else {
    fail(`创建失败: ${JSON.stringify(createRes.data)}`);
  }

  // ──────────────────────────────────────
  // Step 2: 招聘一个 Base 模板的 Agent
  // ──────────────────────────────────────
  step(2, `招聘 ${WORKER_ROLE} (使用 base 模板)`);
  const hireRes = await post(`/company/${COMPANY_ID}/hire`, {
    role: WORKER_ROLE,
    type: 'base',
    name: '代码矿工',
    description: '负责功能开发、代码编写、Bug 修复',
  });
  if (hireRes.status === 200) {
    ok(`${WORKER_ID} 招聘成功: ${JSON.stringify(hireRes.data?.status || 'ok')}`);
  } else {
    warn(`招聘结果: ${JSON.stringify(hireRes.data)}`);
  }

  // 等一下让脚手架完成文件写入
  await sleep(1000);

  // ──────────────────────────────────────
  // Step 3: 验证初始状态
  // ──────────────────────────────────────
  step(3, '验证 Agent 初始状态');
  const agentDir = path.join(ROOT, 'data/companies', COMPANY_ID, 'agents', WORKER_ROLE);
  
  // 3a: 手动复制 base template 到这个 agent(模拟四阶段范式挂载)
  const baseAgentMd = path.join(ROOT, 'system/agents/_base/agent.md');
  const targetAgentMd = path.join(agentDir, 'agent.md');
  if (fs.existsSync(baseAgentMd) && fs.existsSync(agentDir)) {
    const currentContent = fs.existsSync(targetAgentMd) ? fs.readFileSync(targetAgentMd, 'utf-8') : '';
    if (!currentContent.includes('四段式法则')) {
      fs.copyFileSync(baseAgentMd, targetAgentMd);
      ok('已将 Base Agent 四阶段规范注入到 agent.md');
    } else {
      ok('agent.md 已包含四阶段规范');
    }
  }
  
  // 3b: 挂载初始 meta-research 技能
  const researchSkill = path.join(ROOT, 'system/skills/meta-research/SKILL.md');
  const mainSkillDir = path.join(agentDir, '.claude', 'skills', 'main_skill');
  const mainSkillPath = path.join(mainSkillDir, 'SKILL.md');
  if (fs.existsSync(researchSkill) && fs.existsSync(agentDir)) {
    if (!fs.existsSync(mainSkillDir)) fs.mkdirSync(mainSkillDir, { recursive: true });
    fs.copyFileSync(researchSkill, mainSkillPath);
    ok('已挂载 meta-research 作为初始技能');
  }
  
  // 3c: 复制 base config.json (restricted 工具策略)
  const baseConfig = path.join(ROOT, 'system/agents/_base/config.json');
  const agentConfig = path.join(agentDir, 'config.json');
  if (fs.existsSync(baseConfig) && fs.existsSync(agentDir)) {
    const existingConfig = fs.existsSync(agentConfig) ? JSON.parse(fs.readFileSync(agentConfig, 'utf-8')) : {};
    const baseData = JSON.parse(fs.readFileSync(baseConfig, 'utf-8'));
    const merged = { ...existingConfig, ...baseData, id: WORKER_ROLE };
    fs.writeFileSync(agentConfig, JSON.stringify(merged, null, 2), 'utf-8');
    ok(`config.json 已合并 restricted 工具策略: ${JSON.stringify(merged.cli?.toolPolicy?.mode)}`);
  }

  // ──────────────────────────────────────
  // Step 4: 派发 P1 开发任务
  // ──────────────────────────────────────
  step(4, '派发 P1 开发任务');
  const taskRes = await post('/agent/task/dispatch', {
    from: `${COMPANY_ID}:ceo`,
    to: WORKER_ID,
    task: '请帮我调研一下项目 server/routes/ 目录下所有的 API 路由文件,统计有多少个 GET/POST/PUT/DELETE 端点,然后写一个简洁的 API 清单文档。',
    priority: 'P1',
    autoExecute: false,  // 不自动执行, 我们手动控制流转
  });
  if (taskRes.status === 200 && taskRes.data?.taskId) {
    ok(`任务已派发: ${taskRes.data.taskId}`);
  } else {
    warn(`派发结果: ${JSON.stringify(taskRes.data)}`);
  }

  // ──────────────────────────────────────
  // Step 5: 模拟四阶段流转
  // ──────────────────────────────────────
  step(5, '模拟四阶段流转 (Research → Plan → Execute → Verify)');

  const phases = ['plan', 'execute', 'verify'];
  const phaseNames = { plan: '规划', execute: '执行', verify: '验证' };
  const skillNames = { plan: 'meta-plan', execute: 'meta-execute', verify: 'meta-verify' };

  // 当前处于 Research 阶段, 模拟产出 _research.md
  const wsDir = path.join(agentDir, 'workspace');
  if (!fs.existsSync(wsDir)) fs.mkdirSync(wsDir, { recursive: true });
  fs.writeFileSync(path.join(wsDir, '_research.md'), `# 调研结果
- server/routes/ 中有约 10 个 API 路由文件
- 主要模块: agent-chat.js, company.js, goals.js, evolution.js 等
- 估计有 60+ 个 REST endpoint
`, 'utf-8');
  ok('Research 阶段: 已产出 workspace/_research.md');

  for (const phase of phases) {
    console.log(`\n   🔄 触发 transition → ${phase} (${phaseNames[phase]})`);
    
    const trRes = await post(`/agent/${WORKER_ID}/task/transition`, {
      phase,
      artifact: phase === 'plan' ? 'workspace/_research.md' : (phase === 'execute' ? 'workspace/_plan.md' : ''),
    });
    
    if (trRes.status === 200 && trRes.data?.success) {
      ok(`Transition → ${phase} 成功: ${trRes.data.message}`);
    } else {
      fail(`Transition → ${phase} 失败: ${JSON.stringify(trRes.data)}`);
      continue;
    }
    
    // 验证 SKILL.md 被替换
    await sleep(500);
    if (fs.existsSync(mainSkillPath)) {
      const content = fs.readFileSync(mainSkillPath, 'utf-8');
      if (content.includes(skillNames[phase])) {
        ok(`SKILL.md 已切换为 ${skillNames[phase]}`);
      } else {
        warn(`SKILL.md 内容未包含 ${skillNames[phase]} 关键词`);
      }
    }
    
    // 模拟该阶段的产出 (只有 plan 阶段写 _plan.md)
    if (phase === 'plan') {
      fs.writeFileSync(path.join(wsDir, '_plan.md'), `# 执行计划
1. 遍历 server/routes/*.js 文件
2. 用正则提取 router.get/post/put/delete 端点
3. 聚合为 Markdown 表格输出到 workspace/api-inventory.md
`, 'utf-8');
      ok('Plan 阶段: 已产出 workspace/_plan.md');
    }
    
    if (phase === 'execute') {
      fs.writeFileSync(path.join(wsDir, 'api-inventory.md'), `# API 清单
| 方法 | 路径 | 模块 |
|------|------|------|
| GET | /api/agent/list | agent-chat.js |
| POST | /api/agent/:id/chat | agent-chat.js |
| POST | /api/company/create | company.js |
| ... | ... | ... |
`, 'utf-8');
      ok('Execute 阶段: 已产出 workspace/api-inventory.md');
    }
  }

  // ──────────────────────────────────────
  // Step 6: 最终检查
  // ──────────────────────────────────────
  step(6, '最终检查');
  
  // 检查所有产出文件
  const expectedFiles = ['_research.md', '_plan.md', 'api-inventory.md'];
  for (const f of expectedFiles) {
    const fp = path.join(wsDir, f);
    if (fs.existsSync(fp)) {
      const size = fs.statSync(fp).size;
      ok(`${f} 存在 (${size} bytes)`);
    } else {
      fail(`${f} 缺失`);
    }
  }
  
  // 检查当前挂载的技能是 meta-verify
  if (fs.existsSync(mainSkillPath)) {
    const finalSkill = fs.readFileSync(mainSkillPath, 'utf-8');
    if (finalSkill.includes('meta-verify')) {
      ok('最终技能树: meta-verify ✓ (验证阶段)');
    } else {
      warn('最终技能树不是 meta-verify');
    }
  }
  
  // 检查 billing.log
  const billingPath = path.join(ROOT, 'data/logs/billing.log');
  if (fs.existsSync(billingPath)) {
    ok(`billing.log 存在 (${fs.statSync(billingPath).size} bytes)`);
  } else {
    warn('billing.log 尚未产生 (需要真实 Agent 对话才会写入)');
  }

  banner('🎉 演练完成!AI ToolBox 四阶段流转验证通过');
  
  console.log(`
📋 验证清单:
   ✅ 公司创建 + 员工招聘 (aitoolbox:feature-dev)
   ✅ Base Agent 规范注入 (agent.md + config.json restricted)
   ✅ 初始技能挂载 (meta-research)
   ✅ Transition API: research → plan → execute → verify
   ✅ 每次 Transition 的 Hot Swap 换脑验证
   ✅ 各阶段 Artifact 产出 (_research.md / _plan.md / api-inventory.md)

📂 公司目录: data/companies/${COMPANY_ID}/
📂 员工目录: data/companies/${COMPANY_ID}/agents/${WORKER_ROLE}/

💡 接下来你可以:
   1. 用 UI 打开 ${WORKER_ID} 跟它对话,观察它的受限行为
   2. 派发一个 P0 任务测试审批拦截:
      node bin/lemon.js task dispatch --from ${COMPANY_ID}:ceo --to ${WORKER_ID} --task "重构核心架构" --priority P0
   3. 查看 billing.log: cat data/logs/billing.log
`);
}

main().catch(err => {
  console.error('❌ 脚本执行错误:', err.message);
  process.exit(1);
});