• company.js 的 hire 路由原来用一发 fetch(...).catch(...) 回环自己去触发 agent-creator
    填新角色的定义文件——fire-and-forget,.catch 只接网络层错误,agent-creator 撞车时
    (另一次调用正占着它)/api/agent/:agentId/chat 返回 429,被原样当成"成功"吞掉。
    新角色因此永久停在 base 空壳 + status=training,唯一痕迹是一行 console.error(且这行
    在 429 场景下根本不会触发)。
    
    新增两个模块:
    - agent-session-lock.js:从 agent-chat.js 抽出的共享互斥锁存储(纯数据结构,Map +
      超时常量),agent-chat.js 的路由与 hire 路由现在用同一把锁。不能各起一把——
      SessionPool.getOrCreate() 不检查上一次调用有没有跑完,两条路径分开维护锁的话,
      各自都以为自己没有并发,会把第二轮 prompt 发进一个还在处理中的 CLI 会话
      (未定义行为,比现在的"被拒绝丢弃"更危险,见 32 号文档 §二方案 B)。
    - agent-body-filler.js:同进程直接调 AgentManager.callAgent,替掉 HTTP 回环。
      拿到锁才发起调用,锁被占用时等一等重试(最多 3 次,5s/15s 退避),重试耗尽在
      本体目录写一个哨兵文件 .body_fill_failed.json(不动 system_agents.status /
      config.json.status——那两个字段还有 draft→active 毕业逻辑在读,改取值域风险
      大于收益)。
    
    company.js 的 hire 路由第 1 步从 fire-and-forget 换成 await,顺带让第 2/3 步
    (技能注入、5 秒后触发 Innovator 训练)拿到"agent-creator 真的写完了"这个保证——
    第 3 步原来的 setTimeout(5000) 靠的是"假设 5 秒内写完",现在这个假设不再需要,
    但没有顺手删掉那个 setTimeout(不在这次修复范围内,留着无害)。hire 接口本身
    仍是异步的,调用方还是立即拿到 training 状态,没有改成同步等待完成。
    
    agent-chat.js 的锁声明改成从共享模块 require,行为逐字不变——用现有 5 个
    agent-chat 相关测试文件(36 条)验证过零回归。
    
    新增 agent-body-filler.test.js(6 条):首次成功、重试后成功、锁被占用时等待
    重试、重试耗尽写失败标记、成功后清掉上一轮的失败标记、只释放自己抢到的锁
    (不误删第三方在途期间抢到的新锁)。用 vi.useFakeTimers() 控制重试等待,
    不用真等 5s/15s。
    
    全量 npx vitest run:151 文件 / 1384 用例,零回归。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse File »