AgentScaffold.js 14 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
/**
 * ═══════════════════════════════════════════════════════════════
 *  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',
  'memory/cerebellum',      // v5: 小脑 — 固化的执行方案
  'knowledge',
  'workspace',
  'workspace/code',
  'workspace/data',
  'workspace/assets',
  'conversations',           // v5: 对话持久化
  '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);

    // 6. v5: 写入 todo.md + plan.md + hippocampus.md
    await this._writeTodo(agentDir);
    await this._writePlan(agentDir);
    await this._writeHippocampus(agentDir);

    // 7. v5: 写入 cron.json (定时任务声明)
    await this._writeCronJson(agentDir, type);

    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 变量, 不要硬编码

## 文件操作
- 自主创建的文件只写到运行时坐标(提示词开头)给出的绝对路径里,不要写死相对路径
  (cwd 是任务态目录,一次性、会被清理,裸相对路径等于没交付)
`;
  }

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

> 使用 Claude Code 原生 CronCreate 工具创建定时调度。

## 首次对话时创建

### Plan 检查 (每 5 分钟)
CronCreate: \`*/5 * * * *\`
读取 plan.md,检查已分发任务的进度,更新状态。

### Todo 扫描 (每 3 分钟)
CronCreate: \`*/3 * * * *\`
读取 todo.md,按优先级执行未开始的任务。

### 日报 (每 30 分钟)
CronCreate: \`*/30 * * * *\`
统计所有 Agent 状态,写入 memory/daily/。

## 注意
- 任务仅在会话运行且空闲时触发
- 关闭会话后定时任务自动取消
- 循环任务 3 天后过期
`,
      cxo: `# 心跳任务

> 使用 CronCreate 创建定时调度。

## 首次对话时创建

### 下属状态检查 (每 5 分钟)
CronCreate: \`*/5 * * * *\`
扫描下属 Agent 的 todo.md 和 plan.md,更新进度。

### 日报 (每小时)
CronCreate: \`0 * * * *\`
汇总关键指标,写入 memory/daily/。
`,
      role: `# 心跳任务

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

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

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

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

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

  async _writeConfig(agentDir, opts) {
    // 工具隔离策略:编排型 Agent 禁用所有原生工具,执行型保留全部
    const TOOL_POLICY_BY_TYPE = {
      master: { allowedTools: '' },       // 编排型:禁用全部原生工具
      meta: { allowedTools: '' },         // 元 Agent:禁用全部原生工具
      orchestrator: { allowedTools: '' }, // 编排型:禁用全部原生工具
      cxo: { allowedTools: '' },          // CXO 战略层:禁用全部
      role: {},                           // 职能层:保留全部工具
      skill: {},                          // 执行层:保留全部工具
      executor: {},                       // 执行层:保留全部工具
    };

    const config = {
      id: opts.id,
      type: opts.type,
      name: opts.name,
      parent_id: opts.parentId || null,
      goal_id: opts.goalId || null,
      status: 'ready',
      cli: {
        toolPolicy: TOOL_POLICY_BY_TYPE[opts.type] || {},
      },
      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');
  }

  // ─────────────────────────────────────────
  //  v5: 任务管理 + 认知架构
  // ─────────────────────────────────────────

  async _writeTodo(agentDir) {
    const content = `# Todo List

> 收到的任务会自动追加到此表。按优先级顺序执行。

| # | 任务 | 状态 | 分配人 | 优先级 | 接收时间 | 上次检查 |
|---|------|------|--------|--------|----------|----------|
| | (暂无待办) | | | | | |
`;
    await writeFile(join(agentDir, 'todo.md'), content, 'utf-8');
  }

  async _writePlan(agentDir) {
    const content = `# Plan

> 将大任务分解为子任务,分配给下级 Agent,并定期检查进度。

| # | 子任务 | 责任人 | 状态 | 上次检查 | 备注 |
|---|--------|--------|------|----------|------|
| | (暂无计划) | | | | |
`;
    await writeFile(join(agentDir, 'plan.md'), content, 'utf-8');
  }

  async _writeHippocampus(agentDir) {
    const content = `# 海马体 — 经验记忆索引

> 记录过往处理过的情景模式。遇到相似任务时,优先调用小脑中的固化方案。

| # | 情景模式 | 匹配条件 | 方案文件 | 置信度 | 使用次数 | 上次使用 |
|---|---------|---------|---------|--------|---------|----------|
| | (暂无经验记录) | | | | | |
`;
    await writeFile(join(agentDir, 'memory', 'hippocampus.md'), content, 'utf-8');
  }

  /**
   * 写入 cron.json — 定时任务声明文件
   * 会话重建时自动读取并注入 CronCreate 恢复指令
   * 参考: docs/claude-code-cron.md
   */
  async _writeCronJson(agentDir, type) {
    const cronByType = {
      master: [
        { schedule: '*/5 * * * *', prompt: '读取 plan.md,检查已分发任务的进度,更新状态。', recurring: true, enabled: true },
        { schedule: '*/3 * * * *', prompt: '读取 todo.md,按优先级执行未开始的任务。', recurring: true, enabled: true },
        { schedule: '*/30 * * * *', prompt: '统计所有 Agent 状态,写入 memory/daily/ 日报。', recurring: true, enabled: true },
      ],
      cxo: [
        { schedule: '*/5 * * * *', prompt: '扫描下属 Agent 的 todo.md 和 plan.md,更新进度。', recurring: true, enabled: true },
        { schedule: '0 * * * *', prompt: '汇总关键指标,写入 memory/daily/ 日报。', recurring: true, enabled: true },
      ],
      role: [
        { schedule: '*/10 * * * *', prompt: '读取 todo.md,检查未完成的任务并按优先级执行。', recurring: true, enabled: true },
      ],
      skill: [
        { schedule: '*/10 * * * *', prompt: '读取 todo.md,检查未完成的任务并按优先级执行。', recurring: true, enabled: true },
      ],
    };
    const crons = cronByType[type] || cronByType.skill;
    await writeFile(join(agentDir, 'cron.json'), JSON.stringify(crons, null, 2), 'utf-8');
  }
}

export default AgentScaffold;