feat: enrich listWaitingAgents with task/phase/initiative context via DB joins
Replaces the in-memory filter (agentManager.list() + filter) with a direct repository query that LEFT JOINs tasks, phases, and initiatives to return taskName, phaseName, initiativeName, and taskDescription alongside agent fields. - Adds AgentWithContext interface and findWaitingWithContext() to AgentRepository port - Implements findWaitingWithContext() in DrizzleAgentRepository using getTableColumns - Wires agentRepository into TRPCContext, CreateContextOptions, and TrpcAdapterOptions - Adds requireAgentRepository() helper following existing pattern - Updates listWaitingAgents to use repository query instead of agentManager - Adds 5 unit tests for findWaitingWithContext() covering all FK join edge cases - Updates existing AgentRepository mocks to satisfy updated interface Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -277,3 +277,91 @@ describe('DrizzleAgentRepository', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('DrizzleAgentRepository.findWaitingWithContext()', () => {
|
||||
let agentRepo: DrizzleAgentRepository;
|
||||
let taskRepo: DrizzleTaskRepository;
|
||||
let phaseRepo: DrizzlePhaseRepository;
|
||||
let initiativeRepo: DrizzleInitiativeRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
const db = createTestDatabase();
|
||||
agentRepo = new DrizzleAgentRepository(db);
|
||||
taskRepo = new DrizzleTaskRepository(db);
|
||||
phaseRepo = new DrizzlePhaseRepository(db);
|
||||
initiativeRepo = new DrizzleInitiativeRepository(db);
|
||||
});
|
||||
|
||||
it('returns empty array when no waiting agents exist', async () => {
|
||||
const result = await agentRepo.findWaitingWithContext();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('only returns agents with status waiting_for_input', async () => {
|
||||
await agentRepo.create({ name: 'running-agent', worktreeId: 'wt1', status: 'running' });
|
||||
await agentRepo.create({ name: 'waiting-agent', worktreeId: 'wt2', status: 'waiting_for_input' });
|
||||
|
||||
const result = await agentRepo.findWaitingWithContext();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe('waiting-agent');
|
||||
});
|
||||
|
||||
it('populates taskName, phaseName, initiativeName, taskDescription when FK associations exist', async () => {
|
||||
const initiative = await initiativeRepo.create({ name: 'My Initiative' });
|
||||
const phase = await phaseRepo.create({ initiativeId: initiative.id, name: 'Phase 1' });
|
||||
const task = await taskRepo.create({
|
||||
phaseId: phase.id,
|
||||
name: 'Implement feature',
|
||||
description: 'Write the feature code',
|
||||
});
|
||||
|
||||
await agentRepo.create({
|
||||
name: 'ctx-agent',
|
||||
worktreeId: 'wt3',
|
||||
status: 'waiting_for_input',
|
||||
taskId: task.id,
|
||||
initiativeId: initiative.id,
|
||||
});
|
||||
|
||||
const result = await agentRepo.findWaitingWithContext();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].taskName).toBe('Implement feature');
|
||||
expect(result[0].phaseName).toBe('Phase 1');
|
||||
expect(result[0].initiativeName).toBe('My Initiative');
|
||||
expect(result[0].taskDescription).toBe('Write the feature code');
|
||||
});
|
||||
|
||||
it('returns null for context fields when agent has no taskId or initiativeId', async () => {
|
||||
await agentRepo.create({ name: 'bare-agent', worktreeId: 'wt4', status: 'waiting_for_input' });
|
||||
|
||||
const result = await agentRepo.findWaitingWithContext();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].taskName).toBeNull();
|
||||
expect(result[0].phaseName).toBeNull();
|
||||
expect(result[0].initiativeName).toBeNull();
|
||||
expect(result[0].taskDescription).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null phaseName when task has no phaseId', async () => {
|
||||
const initiative = await initiativeRepo.create({ name: 'Orphan Init' });
|
||||
const task = await taskRepo.create({
|
||||
phaseId: null,
|
||||
name: 'Orphan Task',
|
||||
description: null,
|
||||
});
|
||||
|
||||
await agentRepo.create({
|
||||
name: 'orphan-agent',
|
||||
worktreeId: 'wt5',
|
||||
status: 'waiting_for_input',
|
||||
taskId: task.id,
|
||||
initiativeId: initiative.id,
|
||||
});
|
||||
|
||||
const result = await agentRepo.findWaitingWithContext();
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].phaseName).toBeNull();
|
||||
expect(result[0].taskName).toBe('Orphan Task');
|
||||
expect(result[0].initiativeName).toBe('Orphan Init');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user