-
- agent-session-lock.js 新增 acquireLockWithWait:锁被占用时按 pollMs 轮询排队等待 AGENT_CHAT_QUEUE_WAIT_MS 预算(默认 60s),check-and-set 之间无 await,天然原子; 等到锁释放就正常执行,等满预算仍抢不到才 429,不再"撞上就立刻拒绝"。 内部自唤醒(TaskCallback/TaskDispatch 等 selfChat 调用)是 fire-and-forget,之前 429 等于这次唤醒直接消失,只能靠别处巡检兜底;排队等待让大多数几秒到几十秒的 短暂碰撞能自然衔接上。 - AgentChat.vue 的 handleSend() 之前拿到 fetch 响应后不检查 r.ok 就直接当 SSE 流读, 非 200 响应体是普通 JSON,逐行解析全部匹配不上 event/data 前缀,被静默当空流吞掉—— 用户发的消息挂在界面上,没有任何回复也没有报错。现在先判 r.ok,非 200 时读 JSON 错误体,429 显示"Agent 正忙,请稍后重试",其他状态显示带状态码的错误提示。 - agent-chat-interrupt.test.js 相应更新:原来断言"并发第二个请求立刻 429"的用例 改为断言"预算内排队等待、等到就正常执行、两次调用不重叠",另加一条"预算耗尽仍 抢不到锁时返回 429"的回归。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CwGcvMfUcgCcweN5vjhTkn
-
排查「前端刷新不及时/没有打字机效果」发现现状已是 SSE,不需要换 WebSocket, 真正缺的是三件独立的事: - 打字机平滑:后端把 ACP chunk 按 10 字符切片再发 SSE,前端用定时器队列 (agentChatEvents.js 的 createTypewriterQueue)匀速吐出,抹平网络突发。 - 打断并发送:执行中解锁输入框,新增 ⤴ 按钮与 ⏹ Stop 视觉区分;stop 端点 改为轮询确认 agentChatLocks 真正释放(3 秒预算)再返回 cleaned 字段, 前端据此决定直接发送还是走"强制发送"兜底。顺手修了一个真实并发漏洞: 同一 agentId + 同一 convId 的并发请求过去完全没有闸门,只是一直靠前端 textarea 的 disabled 属性侥幸没被触发。 - 串台加固:把 handleEvent 分发逻辑抽成不依赖组件实例的纯函数 (agentChatEvents.js),补上 9b1c3f1 那条不变式的回归测试。 已用真实 Claude CLI 会话(非 mock)在浏览器里验证打字机效果、打断并发送 全流程、以及纯 Stop 无回归。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDtmdnE6pay9BhwZDAKK6M
-
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>