Commit b095d3a73f72aa6509b1d03039bd36dbd3f86b11

Authored by yibo
1 parent 598bef1e

perf: reuse existing session for task callbacks to save ~30K tokens/callback

When a task completes and triggers a callback to the parent agent,
previously it always created a new convId (callback-{taskId}), spawning
a fresh CLI process with full system prompt re-injection.

Now it checks the sessions pool for an existing connected session of the
target agent and reuses its convId. This avoids creating a new CLI
process and saves the system prompt overhead (~30K tokens for master).

Falls back to creating new session if no active one exists.
Showing 1 changed file with 25 additions and 1 deletions
server/routes/agent-chat.js
... ... @@ -665,10 +665,25 @@ async function getOrCreateProtocol(agentId, convId) {
665 665  
666 666 const protocol = new ClaudeCliProtocol({ name: identity.name }, cliOptions);
667 667  
  668 + const releaseLock = () => {
  669 + const lock = agentChatLocks.get(agentId);
  670 + if (lock && lock.convId === convId) {
  671 + agentChatLocks.delete(agentId);
  672 + console.log(`[AgentChat] 🔓 ${identity.name} 锁已释放 (conv: ${convId})`);
  673 + }
  674 + };
  675 +
668 676 // 绑定错误监听器,防止 EventEmitter 抛出未捕获异常导致整个 Node 进程奔溃 (例如 spawn claude ENOENT)
669 677 protocol.on('error', (err) => {
670 678 console.error(`[AgentChat] 🚨 ${identity.name} 协议级异常:`, err.message);
671 679 sessions.delete(key); // 下次提问将重建进程
  680 + releaseLock();
  681 + });
  682 +
  683 + protocol.on('disconnected', ({ code, signal }) => {
  684 + sessions.delete(key);
  685 + releaseLock();
  686 + console.log(`[AgentChat] 💤 ${identity.name} 会话断开 (code=${code}, signal=${signal})`);
672 687 });
673 688  
674 689 await protocol.connect(null, agentDir);
... ... @@ -1733,7 +1748,16 @@ router.post('/task/report', (req, res) => {
1733 1748 try {
1734 1749 // 读取 task 元数据获取上下文
1735 1750 const taskMeta = loadTaskMeta(taskId);
1736   - const callbackConvId = `callback-${taskId}`;
  1751 +
  1752 + // 💰 Token 优化:尝试复用目标 Agent 的已有 session,避免创建新 CLI 进程
  1753 + let callbackConvId = `callback-${taskId}`;
  1754 + for (const [key, entry] of sessions.entries()) {
  1755 + if (key.startsWith(`${to}:`) && entry.protocol.isConnected) {
  1756 + callbackConvId = entry.convId;
  1757 + console.log(`[TaskCallback] ♻️ 复用 ${to} 已有 session: ${callbackConvId}`);
  1758 + break;
  1759 + }
  1760 + }
1737 1761  
1738 1762 const statusText = status === 'done' ? '✅ 已完成' : '❌ 失败';
1739 1763 const callbackMessage = `[TASK_CALLBACK]\n任务编号: ${taskId}\n执行者: ${from}\n状态: ${statusText}\n摘要: ${summary || '(无摘要)'}\n产出文件: ${outputs.length > 0 ? outputs.join(', ') : '(无)'}\n${taskMeta ? `原始任务: ${taskMeta.task}` : ''}\n\n**去重提醒**:在 dispatch 任何新任务前,请务必先检查目标 Agent 的 todo.md 和全局任务状态。如果同一目标已有相似任务在进行中,直接跳过即可,绝对禁止重复分发。\n\n请检查上述任务的执行结果,然后根据你的 plan.md 继续执行下一步计划。`;
... ...