feat: Auto-resume idle agents for inter-agent conversations
When an agent asks a question via `cw ask` targeting an idle agent, the conversation router now auto-resumes the idle agent's session so it can answer. Previously, questions to idle agents sat unanswered forever because target resolution only matched running agents. Changes: - Add `resumeForConversation()` to AgentManager interface and implement on MultiProviderAgentManager (mirrors resumeForCommit pattern) - Relax createConversation target resolution: prefer running, fall back to idle (was running-only) - Trigger auto-resume after conversation creation for idle targets - Add concurrency lock (conversationResumeLocks Set) to prevent double-resume race conditions
This commit is contained in:
@@ -8,6 +8,9 @@ import { z } from 'zod';
|
||||
import type { ProcedureBuilder } from '../trpc.js';
|
||||
import { requireConversationRepository, requireAgentManager, requireTaskRepository } from './_helpers.js';
|
||||
import type { ConversationCreatedEvent, ConversationAnsweredEvent } from '../../events/types.js';
|
||||
import { createModuleLogger } from '../../logger/index.js';
|
||||
|
||||
const log = createModuleLogger('conversation-router');
|
||||
|
||||
export function conversationProcedures(publicProcedure: ProcedureBuilder) {
|
||||
return {
|
||||
@@ -25,30 +28,34 @@ export function conversationProcedures(publicProcedure: ProcedureBuilder) {
|
||||
|
||||
let toAgentId = input.toAgentId;
|
||||
|
||||
// Resolve target agent from taskId
|
||||
// Resolve target agent from taskId — prefer running, fall back to idle
|
||||
if (!toAgentId && input.taskId) {
|
||||
const agents = await agentManager.list();
|
||||
const match = agents.find(a => a.taskId === input.taskId && a.status === 'running');
|
||||
const running = agents.find(a => a.taskId === input.taskId && a.status === 'running');
|
||||
const idle = agents.find(a => a.taskId === input.taskId && a.status === 'idle');
|
||||
const match = running ?? idle;
|
||||
if (!match) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: `No running agent found for task '${input.taskId}'`,
|
||||
message: `No running or idle agent found for task '${input.taskId}'`,
|
||||
});
|
||||
}
|
||||
toAgentId = match.id;
|
||||
}
|
||||
|
||||
// Resolve target agent from phaseId
|
||||
// Resolve target agent from phaseId — prefer running, fall back to idle
|
||||
if (!toAgentId && input.phaseId) {
|
||||
const taskRepo = requireTaskRepository(ctx);
|
||||
const tasks = await taskRepo.findByPhaseId(input.phaseId);
|
||||
const taskIds = new Set(tasks.map(t => t.id));
|
||||
const agents = await agentManager.list();
|
||||
const match = agents.find(a => a.taskId && taskIds.has(a.taskId) && a.status === 'running');
|
||||
const running = agents.find(a => a.taskId && taskIds.has(a.taskId) && a.status === 'running');
|
||||
const idle = agents.find(a => a.taskId && taskIds.has(a.taskId) && a.status === 'idle');
|
||||
const match = running ?? idle;
|
||||
if (!match) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: `No running agent found for phase '${input.phaseId}'`,
|
||||
message: `No running or idle agent found for phase '${input.phaseId}'`,
|
||||
});
|
||||
}
|
||||
toAgentId = match.id;
|
||||
@@ -80,6 +87,24 @@ export function conversationProcedures(publicProcedure: ProcedureBuilder) {
|
||||
},
|
||||
});
|
||||
|
||||
// Auto-resume idle target agent so it can answer the conversation
|
||||
const targetAgent = await agentManager.get(toAgentId);
|
||||
if (targetAgent && targetAgent.status === 'idle') {
|
||||
try {
|
||||
const resumed = await agentManager.resumeForConversation(
|
||||
toAgentId, conversation.id, input.question, input.fromAgentId,
|
||||
);
|
||||
if (resumed) {
|
||||
log.info({ conversationId: conversation.id, toAgentId }, 'auto-resumed idle agent for conversation');
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn(
|
||||
{ conversationId: conversation.id, toAgentId, err: err instanceof Error ? err.message : String(err) },
|
||||
'failed to auto-resume agent for conversation',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return conversation;
|
||||
}),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user