AgentScaffold.js 9.23 KB
/**
 * ═══════════════════════════════════════════════════════════════
 *  AgentScaffold — Agent 目录脚手架
 * ═══════════════════════════════════════════════════════════════
 *
 *  创建 Agent = 生成一个标准目录结构 + 写入 Bootstrap Files
 *
 *  用法:
 *    const scaffold = new AgentScaffold('data/agents');
 *    const agentDir = await scaffold.create({
 *      id: 'cxo-ceo-001',
 *      type: 'cxo',
 *      name: 'Lemon CEO',
 *      parentId: 'master-001',
 *    });
 */

import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';

/**
 * Agent 目录内的标准子目录
 */
const STANDARD_SUBDIRS = [
  '.claude/skills',
  '.claude/skills/main_skill',
  'memory',
  'memory/short-term',
  'memory/long-term',
  'memory/sessions',
  'memory/daily',
  'knowledge',
  'workspace',
  'workspace/code',
  'workspace/data',
  'workspace/assets',
  'logs',
];

export class AgentScaffold {

  /**
   * @param {string} agentsBaseDir - Agent 实例的根目录 (如 data/agents)
   */
  constructor(agentsBaseDir) {
    this.baseDir = agentsBaseDir;
  }

  /**
   * 创建一个完整的 Agent 目录
   *
   * @param {Object} options
   * @param {string} options.id        - Agent 唯一标识 (如 'cxo-ceo-001')
   * @param {string} options.type      - Agent 类型 ('master' | 'cxo' | 'role' | 'skill')
   * @param {string} options.name      - 显示名称
   * @param {string} [options.parentId]  - 上级 Agent ID
   * @param {string} [options.goalId]    - 绑定的 Goal ID
   * @param {Object} [options.bootstrap] - 自定义 Bootstrap Files 内容
   *
   * @returns {Promise<string>} 创建的 Agent 目录路径
   */
  async create(options) {
    const { id, type, name, parentId, goalId, bootstrap = {} } = options;
    const agentDir = join(this.baseDir, id);

    // 1. 创建目录结构
    await this._createDirectories(agentDir);

    // 2. 生成并写入 Bootstrap Files
    await this._writeBootstrapFiles(agentDir, { id, type, name, parentId, ...bootstrap });

    // 3. 写入 config.json
    await this._writeConfig(agentDir, { id, type, name, parentId, goalId });

    // 4. 写入 skills.md 空索引
    await this._writeSkillsIndex(agentDir);

    // 5. 写入 memory.md 空索引
    await this._writeMemoryIndex(agentDir);

    return agentDir;
  }

  // ─────────────────────────────────────────
  //  目录创建
  // ─────────────────────────────────────────

  async _createDirectories(agentDir) {
    // 创建 agent 根目录
    await mkdir(agentDir, { recursive: true });

    // 创建所有标准子目录
    for (const subdir of STANDARD_SUBDIRS) {
      await mkdir(join(agentDir, subdir), { recursive: true });
    }
  }

  // ─────────────────────────────────────────
  //  Bootstrap Files 生成
  // ─────────────────────────────────────────

  async _writeBootstrapFiles(agentDir, opts) {
    const { id, type, name, parentId } = opts;

    // agent.md — 如果上层提供了自定义内容则使用, 否则生成默认模板
    const agentMd = opts.agentMd || this._defaultAgentMd(id, type, name);
    await writeFile(join(agentDir, 'agent.md'), agentMd, 'utf-8');

    // soul.md
    const soulMd = opts.soulMd || this._defaultSoulMd(type, name);
    await writeFile(join(agentDir, 'soul.md'), soulMd, 'utf-8');

    // identity.md
    const identityMd = opts.identityMd || this._defaultIdentityMd(type, name);
    await writeFile(join(agentDir, 'identity.md'), identityMd, 'utf-8');

    // user.md
    const userMd = opts.userMd || this._defaultUserMd(parentId);
    await writeFile(join(agentDir, 'user.md'), userMd, 'utf-8');

    // tools.md
    const toolsMd = opts.toolsMd || this._defaultToolsMd();
    await writeFile(join(agentDir, 'tools.md'), toolsMd, 'utf-8');

    // heartbeat.md
    const heartbeatMd = opts.heartbeatMd || this._defaultHeartbeatMd(type);
    await writeFile(join(agentDir, 'heartbeat.md'), heartbeatMd, 'utf-8');
  }

  // ─────────────────────────────────────────
  //  默认模板
  // ─────────────────────────────────────────

  _defaultAgentMd(id, type, name) {
    const toolsByType = {
      master: [
        '- cxo.create: 创建 CXO 战略层 Agent',
        '- cxo.redefine: 调整 CXO 的职责定义',
        '- cxo.destroy: 销毁 CXO Agent',
        '- org.status: 查看全局组织状态',
        '- goal.set: 设定公司 Goal',
      ],
      cxo: [
        '- role.create: 创建职能部门 Role Agent',
        '- role.redefine: 调整部门职责定义',
        '- role.destroy: 裁撤部门',
        '- pipeline.set: 设计工作流',
        '- pipeline.adjust: 调整工作流',
      ],
      role: [
        '- skill.create: 创建 Skill Agent',
        '- skill.redefine: 调整 Skill 定义',
        '- skill.destroy: 销毁 Skill Agent',
        '- skill.reuse: 复用其他 Role 的 Skill',
      ],
      skill: [
        '# 无管理工具 — Skill 只负责执行, 不管理其他 Agent',
      ],
    };

    const tools = (toolsByType[type] || toolsByType.skill).join('\n');

    return `# ${name}

## 身份
你是 ${name}。

## 职责
(待上层 Agent 定义)

## Never Rules
- 不要越级管理不属于你的 Agent

## Always Rules
- 按心跳频率主动检查工作进展

## 可用工具
${tools}
`;
  }

  _defaultSoulMd(type, name) {
    return `# ${name} 的灵魂

## 思维方式
(待上层 Agent 定义)

## 沟通风格
- 清晰、简洁

## 价值观
- 高质量交付
- 持续改进
`;
  }

  _defaultIdentityMd(type, name) {
    const emojiByType = { master: '👑', cxo: '👔', role: '🏢', skill: '⚙️' };
    return `# 身份信息

## 基础信息
- 名称: ${name}
- Emoji: ${emojiByType[type] || '🤖'}
- 类型: ${type}

## 问候语
"你好,我是 ${name}。"
`;
  }

  _defaultUserMd(parentId) {
    if (!parentId) {
      return `# 上层上下文

## 我的上级
- 直接向用户汇报 (无上级 Agent)

## 用户偏好
(待用户定义)
`;
    }

    return `# 上层上下文

## 我的上级
- ${parentId}

## 上级偏好
(待上级 Agent 定义)
`;
  }

  _defaultToolsMd() {
    return `# 工具使用备注

## 安全规则
- 不要在日志中输出 API Key
- 不要在 memory 中存储密码
- API Token 使用 env 变量, 不要硬编码

## 文件操作
- 所有自主创建的文件仅限在 workspace/ 内操作
`;
  }

  _defaultHeartbeatMd(type) {
    const heartbeatByType = {
      master: `# 心跳任务

## 每日
- [ ] 校准用户意图是否被正确理解
- [ ] 评估 CXO 层表现
- [ ] 向用户提交进度报告
`,
      cxo: `# 心跳任务

## 每 5 分钟
- [ ] 扫描所有 Role Agent 运行状态
- [ ] 对比 Goal 进度, 识别 Gap
- [ ] 扫描 Pipeline 执行情况

## 每小时
- [ ] 汇总关键指标

## 每日
- [ ] 生成工作总结写入 memory/daily/
`,
      role: `# 心跳任务

## 每轮周期后
- [ ] 检查下属 Skill Agent 任务完成情况
- [ ] 评估产出质量
- [ ] 向 CXO 汇报进展

## 每日
- [ ] 写入每日总结到 memory/daily/
`,
      skill: `# 心跳任务

## 每次任务后
- [ ] 验证执行结果正确性
- [ ] 记录执行指标到 memory/
`,
    };

    return heartbeatByType[type] || heartbeatByType.skill;
  }

  // ─────────────────────────────────────────
  //  config.json 与索引文件
  // ─────────────────────────────────────────

  async _writeConfig(agentDir, opts) {
    const config = {
      id: opts.id,
      type: opts.type,
      name: opts.name,
      parent_id: opts.parentId || null,
      goal_id: opts.goalId || null,
      gateway: { channels: [] },
      api: { endpoints: [] },
      mcp: { servers: [] },
      sandbox: {
        maxMemoryMB: 512,
        maxDiskMB: 1024,
        maxProcesses: 5,
      },
    };

    await writeFile(
      join(agentDir, 'config.json'),
      JSON.stringify(config, null, 2),
      'utf-8',
    );
  }

  async _writeSkillsIndex(agentDir) {
    const content = `# 我的技能

## 技能清单
(尚未添加技能)

## 说明
- 每个技能是 skills/ 目录下的一个子文件夹
- 每个技能文件夹内必须包含 SKILL.md 定义文件
`;
    await writeFile(join(agentDir, '.claude/skills', 'skills.md'), content, 'utf-8');
  }

  async _writeMemoryIndex(agentDir) {
    const content = `# 我的记忆

## 关键事实
(尚无记录)

## 已验证的模式
(尚无记录)

## 失败教训
(尚无记录)
`;
    await writeFile(join(agentDir, 'memory', 'memory.md'), content, 'utf-8');
  }
}

export default AgentScaffold;