Files
Codewalkers/apps/server/agent/mock-manager.test.ts
Lukas May 34578d39c6 refactor: Restructure monorepo to apps/server/ and apps/web/ layout
Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt
standard monorepo conventions (apps/ for runnable apps, packages/
for reusable libraries). Update all config files, shared package
imports, test fixtures, and documentation to reflect new paths.

Key fixes:
- Update workspace config to ["apps/*", "packages/*"]
- Update tsconfig.json rootDir/include for apps/server/
- Add apps/web/** to vitest exclude list
- Update drizzle.config.ts schema path
- Fix ensure-schema.ts migration path detection (3 levels up in dev,
  2 levels up in dist)
- Fix tests/integration/cli-server.test.ts import paths
- Update packages/shared imports to apps/server/ paths
- Update all docs/ files with new paths
2026-03-03 11:22:53 +01:00

907 lines
30 KiB
TypeScript

/**
* MockAgentManager Tests
*
* Comprehensive test suite for the MockAgentManager adapter covering
* all scenario types: success, crash, waiting_for_input.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { MockAgentManager, type MockAgentScenario } from './mock-manager.js';
import type { EventBus, DomainEvent, AgentStoppedEvent } from '../events/types.js';
// =============================================================================
// Test Helpers
// =============================================================================
/**
* Create a mock EventBus that captures emitted events.
*/
function createMockEventBus(): EventBus & { emittedEvents: DomainEvent[] } {
const emittedEvents: DomainEvent[] = [];
return {
emittedEvents,
emit<T extends DomainEvent>(event: T): void {
emittedEvents.push(event);
},
on: vi.fn(),
off: vi.fn(),
once: vi.fn(),
};
}
// =============================================================================
// Tests
// =============================================================================
describe('MockAgentManager', () => {
let manager: MockAgentManager;
let eventBus: ReturnType<typeof createMockEventBus>;
beforeEach(() => {
vi.useFakeTimers();
eventBus = createMockEventBus();
manager = new MockAgentManager({ eventBus });
});
afterEach(() => {
manager.clear();
vi.useRealTimers();
});
// ===========================================================================
// spawn() with default scenario (immediate success)
// ===========================================================================
describe('spawn with default scenario', () => {
it('should create agent with running status', async () => {
const agent = await manager.spawn({
name: 'test-agent',
taskId: 'task-1',
prompt: 'Do something',
});
expect(agent.name).toBe('test-agent');
expect(agent.taskId).toBe('task-1');
expect(agent.status).toBe('running');
expect(agent.id).toBeDefined();
expect(agent.sessionId).toBeDefined();
expect(agent.worktreeId).toBeDefined();
});
it('should emit agent:spawned event', async () => {
await manager.spawn({
name: 'spawned-test',
taskId: 'task-1',
prompt: 'Do something',
});
expect(eventBus.emittedEvents.length).toBeGreaterThanOrEqual(1);
const spawnedEvent = eventBus.emittedEvents.find((e) => e.type === 'agent:spawned');
expect(spawnedEvent).toBeDefined();
expect((spawnedEvent as any).payload.name).toBe('spawned-test');
expect((spawnedEvent as any).payload.taskId).toBe('task-1');
});
it('should complete with success after timer fires', async () => {
const agent = await manager.spawn({
name: 'success-test',
taskId: 'task-1',
prompt: 'Do something',
});
// Timer hasn't fired yet
expect(agent.status).toBe('running');
// Advance timers
await vi.advanceTimersByTimeAsync(0);
// Check status changed
const updated = await manager.get(agent.id);
expect(updated?.status).toBe('idle');
// Check result available
const result = await manager.getResult(agent.id);
expect(result).not.toBeNull();
expect(result?.success).toBe(true);
expect(result?.message).toBe('Task completed successfully');
});
it('should emit agent:stopped event on success completion', async () => {
await manager.spawn({
name: 'stop-event-test',
taskId: 'task-1',
prompt: 'Do something',
});
await vi.advanceTimersByTimeAsync(0);
const stoppedEvent = eventBus.emittedEvents.find((e) => e.type === 'agent:stopped');
expect(stoppedEvent).toBeDefined();
expect((stoppedEvent as any).payload.reason).toBe('task_complete');
});
});
// ===========================================================================
// spawn() with configured delay
// ===========================================================================
describe('spawn with configured delay', () => {
it('should not complete before delay expires', async () => {
manager.setScenario('delayed-agent', {
status: 'done',
delay: 100,
result: 'Delayed completion',
});
const agent = await manager.spawn({
name: 'delayed-agent',
taskId: 'task-1',
prompt: 'Do something slowly',
});
// Advance by less than delay
await vi.advanceTimersByTimeAsync(50);
const updated = await manager.get(agent.id);
expect(updated?.status).toBe('running');
});
it('should complete after delay expires', async () => {
manager.setScenario('delayed-agent', {
status: 'done',
delay: 100,
result: 'Delayed completion',
});
const agent = await manager.spawn({
name: 'delayed-agent',
taskId: 'task-1',
prompt: 'Do something slowly',
});
// Advance past delay
await vi.advanceTimersByTimeAsync(100);
const updated = await manager.get(agent.id);
expect(updated?.status).toBe('idle');
const result = await manager.getResult(agent.id);
expect(result?.message).toBe('Delayed completion');
});
});
// ===========================================================================
// spawn() with crash scenario
// ===========================================================================
describe('spawn with error scenario', () => {
it('should emit agent:crashed and set result.success=false', async () => {
manager.setScenario('crash-agent', {
status: 'error',
delay: 0,
error: 'Something went terribly wrong',
});
const agent = await manager.spawn({
name: 'crash-agent',
taskId: 'task-1',
prompt: 'Do something risky',
});
await vi.advanceTimersByTimeAsync(0);
// Check status
const updated = await manager.get(agent.id);
expect(updated?.status).toBe('crashed');
// Check result
const result = await manager.getResult(agent.id);
expect(result?.success).toBe(false);
expect(result?.message).toBe('Something went terribly wrong');
// Check event
const crashedEvent = eventBus.emittedEvents.find((e) => e.type === 'agent:crashed');
expect(crashedEvent).toBeDefined();
expect((crashedEvent as any).payload.error).toBe('Something went terribly wrong');
});
});
// ===========================================================================
// spawn() with question scenario
// ===========================================================================
describe('spawn with questions scenario', () => {
it('should emit agent:waiting and set status to waiting_for_input', async () => {
manager.setScenario('waiting-agent', {
status: 'questions',
delay: 0,
questions: [{ id: 'q1', question: 'Should I continue?' }],
});
const agent = await manager.spawn({
name: 'waiting-agent',
taskId: 'task-1',
prompt: 'Ask a question',
});
await vi.advanceTimersByTimeAsync(0);
// Check status
const updated = await manager.get(agent.id);
expect(updated?.status).toBe('waiting_for_input');
// Check event
const waitingEvent = eventBus.emittedEvents.find((e) => e.type === 'agent:waiting');
expect(waitingEvent).toBeDefined();
expect((waitingEvent as any).payload.questions[0].question).toBe('Should I continue?');
});
});
// ===========================================================================
// resume() after waiting_for_input
// ===========================================================================
describe('resume after questions', () => {
it('should emit agent:resumed and continue with scenario', async () => {
manager.setScenario('resume-agent', {
status: 'questions',
delay: 0,
questions: [{ id: 'q1', question: 'Need your input' }],
});
const agent = await manager.spawn({
name: 'resume-agent',
taskId: 'task-1',
prompt: 'Start working',
});
// Let agent reach waiting state
await vi.advanceTimersByTimeAsync(0);
const waitingAgent = await manager.get(agent.id);
expect(waitingAgent?.status).toBe('waiting_for_input');
// Resume the agent with answers map
await manager.resume(agent.id, { q1: 'Continue with this input' });
// Check agent:resumed event emitted
const resumedEvent = eventBus.emittedEvents.find((e) => e.type === 'agent:resumed');
expect(resumedEvent).toBeDefined();
expect((resumedEvent as any).payload.agentId).toBe(agent.id);
expect((resumedEvent as any).payload.sessionId).toBe(agent.sessionId);
// Status should be running again
const runningAgent = await manager.get(agent.id);
expect(runningAgent?.status).toBe('running');
// Let it complete
await vi.advanceTimersByTimeAsync(0);
const completedAgent = await manager.get(agent.id);
expect(completedAgent?.status).toBe('idle');
const result = await manager.getResult(agent.id);
expect(result?.success).toBe(true);
});
it('should throw if agent not waiting for input', async () => {
const agent = await manager.spawn({
name: 'not-waiting',
taskId: 'task-1',
prompt: 'Work',
});
await expect(manager.resume(agent.id, { q1: 'input' })).rejects.toThrow(
'is not waiting for input'
);
});
it('should throw if agent not found', async () => {
await expect(manager.resume('non-existent-id', { q1: 'input' })).rejects.toThrow(
'not found'
);
});
});
// ===========================================================================
// stop() kills scheduled completion
// ===========================================================================
describe('stop', () => {
it('should cancel scheduled completion and emit agent:stopped', async () => {
manager.setScenario('stoppable-agent', {
status: 'done',
delay: 1000,
result: 'Should not see this',
});
const agent = await manager.spawn({
name: 'stoppable-agent',
taskId: 'task-1',
prompt: 'Long running task',
});
// Stop before completion
await manager.stop(agent.id);
// Check status
const updated = await manager.get(agent.id);
expect(updated?.status).toBe('stopped');
// Check event
const stoppedEvent = eventBus.emittedEvents.find(
(e) => e.type === 'agent:stopped' && (e as any).payload.reason === 'user_requested'
);
expect(stoppedEvent).toBeDefined();
// Advance time - should not complete now
await vi.advanceTimersByTimeAsync(1000);
const stillStopped = await manager.get(agent.id);
expect(stillStopped?.status).toBe('stopped');
});
it('should throw if agent not found', async () => {
await expect(manager.stop('non-existent-id')).rejects.toThrow('not found');
});
});
// ===========================================================================
// list() returns all agents with correct status
// ===========================================================================
describe('list', () => {
it('should return all agents', async () => {
await manager.spawn({ name: 'agent-1', taskId: 't1', prompt: 'p1' });
await manager.spawn({ name: 'agent-2', taskId: 't2', prompt: 'p2' });
await manager.spawn({ name: 'agent-3', taskId: 't3', prompt: 'p3' });
const agents = await manager.list();
expect(agents.length).toBe(3);
expect(agents.map((a) => a.name).sort()).toEqual(['agent-1', 'agent-2', 'agent-3']);
});
it('should return empty array when no agents', async () => {
const agents = await manager.list();
expect(agents).toEqual([]);
});
});
// ===========================================================================
// get() and getByName() lookups
// ===========================================================================
describe('get and getByName', () => {
it('get should return agent by ID', async () => {
const spawned = await manager.spawn({
name: 'get-test',
taskId: 't1',
prompt: 'p1',
});
const found = await manager.get(spawned.id);
expect(found).not.toBeNull();
expect(found?.name).toBe('get-test');
});
it('get should return null for unknown ID', async () => {
const found = await manager.get('unknown-id');
expect(found).toBeNull();
});
it('getByName should return agent by name', async () => {
await manager.spawn({ name: 'named-agent', taskId: 't1', prompt: 'p1' });
const found = await manager.getByName('named-agent');
expect(found).not.toBeNull();
expect(found?.name).toBe('named-agent');
});
it('getByName should return null for unknown name', async () => {
const found = await manager.getByName('unknown-name');
expect(found).toBeNull();
});
});
// ===========================================================================
// setScenario() overrides for specific agent names
// ===========================================================================
describe('setScenario overrides', () => {
it('should use scenario override for specific agent name', async () => {
// Set error scenario for one agent
manager.setScenario('crasher', {
status: 'error',
delay: 0,
error: 'Intentional crash',
});
// Spawn two agents - one with override, one with default
const crasher = await manager.spawn({
name: 'crasher',
taskId: 't1',
prompt: 'p1',
});
const normal = await manager.spawn({
name: 'normal',
taskId: 't2',
prompt: 'p2',
});
await vi.advanceTimersByTimeAsync(0);
// Crasher should have crashed
const crasherUpdated = await manager.get(crasher.id);
expect(crasherUpdated?.status).toBe('crashed');
// Normal should have succeeded
const normalUpdated = await manager.get(normal.id);
expect(normalUpdated?.status).toBe('idle');
});
it('should allow clearing scenario override', async () => {
manager.setScenario('flip-flop', {
status: 'error',
delay: 0,
error: 'Crash for test',
});
// First spawn crashes
const first = await manager.spawn({
name: 'flip-flop',
taskId: 't1',
prompt: 'p1',
});
await vi.advanceTimersByTimeAsync(0);
expect((await manager.get(first.id))?.status).toBe('crashed');
// Clear scenario and remove agent
manager.clearScenario('flip-flop');
manager.clear();
// Second spawn succeeds (default scenario)
const second = await manager.spawn({
name: 'flip-flop',
taskId: 't2',
prompt: 'p2',
});
await vi.advanceTimersByTimeAsync(0);
expect((await manager.get(second.id))?.status).toBe('idle');
});
});
// ===========================================================================
// Event emission order verification
// ===========================================================================
describe('event emission order', () => {
it('should emit spawned before completion events', async () => {
await manager.spawn({ name: 'order-test', taskId: 't1', prompt: 'p1' });
await vi.advanceTimersByTimeAsync(0);
const eventTypes = eventBus.emittedEvents.map((e) => e.type);
const spawnedIndex = eventTypes.indexOf('agent:spawned');
const stoppedIndex = eventTypes.indexOf('agent:stopped');
expect(spawnedIndex).toBeLessThan(stoppedIndex);
});
it('should emit spawned before crashed', async () => {
manager.setScenario('crash-order', { status: 'error', delay: 0, error: 'Crash' });
await manager.spawn({ name: 'crash-order', taskId: 't1', prompt: 'p1' });
await vi.advanceTimersByTimeAsync(0);
const eventTypes = eventBus.emittedEvents.map((e) => e.type);
const spawnedIndex = eventTypes.indexOf('agent:spawned');
const crashedIndex = eventTypes.indexOf('agent:crashed');
expect(spawnedIndex).toBeLessThan(crashedIndex);
});
it('should emit spawned before waiting', async () => {
manager.setScenario('wait-order', {
status: 'questions',
delay: 0,
questions: [{ id: 'q1', question: 'Test question' }],
});
await manager.spawn({ name: 'wait-order', taskId: 't1', prompt: 'p1' });
await vi.advanceTimersByTimeAsync(0);
const eventTypes = eventBus.emittedEvents.map((e) => e.type);
const spawnedIndex = eventTypes.indexOf('agent:spawned');
const waitingIndex = eventTypes.indexOf('agent:waiting');
expect(spawnedIndex).toBeLessThan(waitingIndex);
});
});
// ===========================================================================
// Name uniqueness validation
// ===========================================================================
describe('name uniqueness', () => {
it('should throw when spawning agent with duplicate name', async () => {
await manager.spawn({ name: 'unique-name', taskId: 't1', prompt: 'p1' });
await expect(
manager.spawn({ name: 'unique-name', taskId: 't2', prompt: 'p2' })
).rejects.toThrow("Agent with name 'unique-name' already exists");
});
});
// ===========================================================================
// Constructor options
// ===========================================================================
describe('constructor options', () => {
it('should work without eventBus', async () => {
const noEventManager = new MockAgentManager();
const agent = await noEventManager.spawn({
name: 'no-events',
taskId: 't1',
prompt: 'p1',
});
expect(agent.name).toBe('no-events');
noEventManager.clear();
});
it('should use provided default scenario', async () => {
const customDefault: MockAgentScenario = {
status: 'error',
delay: 0,
error: 'Default crash',
};
const customManager = new MockAgentManager({
eventBus,
defaultScenario: customDefault,
});
const agent = await customManager.spawn({
name: 'custom-default',
taskId: 't1',
prompt: 'p1',
});
await vi.advanceTimersByTimeAsync(0);
expect((await customManager.get(agent.id))?.status).toBe('crashed');
customManager.clear();
});
});
// ===========================================================================
// clear() cleanup
// ===========================================================================
describe('clear', () => {
it('should remove all agents and cancel pending timers', async () => {
manager.setScenario('pending', { status: 'done', delay: 1000 });
await manager.spawn({ name: 'pending', taskId: 't1', prompt: 'p1' });
await manager.spawn({ name: 'another', taskId: 't2', prompt: 'p2' });
expect((await manager.list()).length).toBe(2);
manager.clear();
expect((await manager.list()).length).toBe(0);
});
});
// ===========================================================================
// Agent modes (execute, discuss, plan)
// ===========================================================================
describe('agent modes', () => {
it('should spawn agent with default execute mode', async () => {
const agent = await manager.spawn({
name: 'exec-agent',
taskId: 't1',
prompt: 'test',
});
expect(agent.mode).toBe('execute');
});
it('should spawn agent in discuss mode', async () => {
manager.setScenario('discuss-agent', {
status: 'done',
delay: 0,
result: 'Auth discussion complete',
});
const agent = await manager.spawn({
name: 'discuss-agent',
taskId: 't1',
prompt: 'discuss auth',
mode: 'discuss',
});
expect(agent.mode).toBe('discuss');
});
it('should spawn agent in plan mode', async () => {
manager.setScenario('plan-agent', {
status: 'done',
delay: 0,
result: 'Plan complete',
});
const agent = await manager.spawn({
name: 'plan-agent',
taskId: 't1',
prompt: 'plan work',
mode: 'plan',
});
expect(agent.mode).toBe('plan');
});
it('should emit stopped event with context_complete reason for discuss mode', async () => {
manager.setScenario('discuss-done', {
status: 'done',
delay: 0,
result: 'Done',
});
await manager.spawn({
name: 'discuss-done',
taskId: 't1',
prompt: 'test',
mode: 'discuss',
});
await vi.runAllTimersAsync();
const stopped = eventBus.emittedEvents.find((e) => e.type === 'agent:stopped') as AgentStoppedEvent | undefined;
expect(stopped?.payload.reason).toBe('context_complete');
});
it('should emit stopped event with plan_complete reason for plan mode', async () => {
manager.setScenario('plan-done', {
status: 'done',
delay: 0,
result: 'Plan complete',
});
await manager.spawn({
name: 'plan-done',
taskId: 't1',
prompt: 'test',
mode: 'plan',
});
await vi.runAllTimersAsync();
const stopped = eventBus.emittedEvents.find((e) => e.type === 'agent:stopped') as AgentStoppedEvent | undefined;
expect(stopped?.payload.reason).toBe('plan_complete');
});
});
// ===========================================================================
// Detail mode (phase to tasks)
// ===========================================================================
describe('detail mode', () => {
it('should spawn agent in detail mode', async () => {
const agent = await manager.spawn({
name: 'detailer',
taskId: 'plan-1',
prompt: 'Detail this phase',
mode: 'detail',
});
expect(agent.mode).toBe('detail');
});
it('should complete with detail_complete reason in detail mode', async () => {
manager.setScenario('detailer', {
status: 'done',
result: 'Detail complete',
});
await manager.spawn({ name: 'detailer', taskId: 'plan-1', prompt: 'test', mode: 'detail' });
await vi.advanceTimersByTimeAsync(100);
// Verify agent:stopped event with detail_complete reason (derived from mode)
const stoppedEvent = eventBus.emittedEvents.find((e) => e.type === 'agent:stopped') as AgentStoppedEvent | undefined;
expect(stoppedEvent).toBeDefined();
expect(stoppedEvent?.payload.reason).toBe('detail_complete');
});
it('should pause on questions in detail mode', async () => {
manager.setScenario('detailer', {
status: 'questions',
questions: [{ id: 'q1', question: 'How many tasks?' }],
});
await manager.spawn({ name: 'detailer', taskId: 'plan-1', prompt: 'test', mode: 'detail' });
await vi.advanceTimersByTimeAsync(100);
// Verify agent pauses for questions
const stoppedEvent = eventBus.emittedEvents.find((e) => e.type === 'agent:waiting');
expect(stoppedEvent).toBeDefined();
// Check agent status
const agent = await manager.getByName('detailer');
expect(agent?.status).toBe('waiting_for_input');
});
it('should set result message for detail mode', async () => {
manager.setScenario('detailer', {
status: 'done',
result: 'Detail complete',
});
const agent = await manager.spawn({ name: 'detailer', taskId: 'plan-1', prompt: 'test', mode: 'detail' });
await vi.runAllTimersAsync();
const result = await manager.getResult(agent.id);
expect(result?.success).toBe(true);
expect(result?.message).toBe('Detail complete');
});
});
// ===========================================================================
// Structured question data (new schema tests)
// ===========================================================================
describe('structured questions data', () => {
it('emits agent:waiting with structured questions data', async () => {
manager.setScenario('test-agent', {
status: 'questions',
questions: [
{
id: 'q1',
question: 'Which database?',
options: [
{ label: 'PostgreSQL', description: 'Full-featured' },
{ label: 'SQLite', description: 'Lightweight' },
],
multiSelect: false,
},
],
});
await manager.spawn({ name: 'test-agent', taskId: 'task-1', prompt: 'test' });
await vi.runAllTimersAsync();
const events = eventBus.emittedEvents.filter((e) => e.type === 'agent:waiting');
expect(events.length).toBe(1);
expect((events[0] as any).payload.questions).toHaveLength(1);
expect((events[0] as any).payload.questions[0].options).toHaveLength(2);
expect((events[0] as any).payload.questions[0].options[0].label).toBe('PostgreSQL');
expect((events[0] as any).payload.questions[0].multiSelect).toBe(false);
});
it('stores pending questions for retrieval', async () => {
manager.setScenario('test-agent', {
status: 'questions',
questions: [
{
id: 'q1',
question: 'Which database?',
options: [{ label: 'PostgreSQL' }],
},
],
});
const agent = await manager.spawn({ name: 'test-agent', taskId: 'task-1', prompt: 'test' });
await vi.runAllTimersAsync();
const pending = await manager.getPendingQuestions(agent.id);
expect(pending?.questions[0].question).toBe('Which database?');
expect(pending?.questions[0].options).toHaveLength(1);
expect(pending?.questions[0].options?.[0].label).toBe('PostgreSQL');
});
it('clears pending questions after resume', async () => {
manager.setScenario('resume-test', {
status: 'questions',
questions: [
{
id: 'q1',
question: 'Need your input',
options: [{ label: 'Option A' }, { label: 'Option B' }],
},
],
});
const agent = await manager.spawn({ name: 'resume-test', taskId: 'task-1', prompt: 'test' });
await vi.runAllTimersAsync();
// Verify questions are pending
const pendingBefore = await manager.getPendingQuestions(agent.id);
expect(pendingBefore).not.toBeNull();
expect(pendingBefore?.questions[0].question).toBe('Need your input');
// Resume the agent with answers map
await manager.resume(agent.id, { q1: 'Option A' });
// Pending questions should be cleared
const pendingAfter = await manager.getPendingQuestions(agent.id);
expect(pendingAfter).toBeNull();
});
it('returns null for non-existent agent pending questions', async () => {
const pending = await manager.getPendingQuestions('non-existent-id');
expect(pending).toBeNull();
});
it('returns null for agent not in waiting state', async () => {
const agent = await manager.spawn({ name: 'running-agent', taskId: 'task-1', prompt: 'test' });
// Agent is running, not waiting
const pending = await manager.getPendingQuestions(agent.id);
expect(pending).toBeNull();
});
it('handles multiple questions in single scenario', async () => {
manager.setScenario('multi-q-agent', {
status: 'questions',
questions: [
{
id: 'q1',
question: 'Which database should we use?',
options: [
{ label: 'PostgreSQL', description: 'Full-featured relational DB' },
{ label: 'SQLite', description: 'Lightweight embedded DB' },
],
},
{
id: 'q2',
question: 'Which ORM do you prefer?',
options: [
{ label: 'Drizzle', description: 'TypeScript-first ORM' },
{ label: 'Prisma', description: 'Popular Node.js ORM' },
],
},
{
id: 'q3',
question: 'Any additional notes?',
// No options - free-form text question
},
],
});
const agent = await manager.spawn({ name: 'multi-q-agent', taskId: 'task-1', prompt: 'test' });
await vi.runAllTimersAsync();
// Check status
const updated = await manager.get(agent.id);
expect(updated?.status).toBe('waiting_for_input');
// Check event has all questions
const waitingEvent = eventBus.emittedEvents.find((e) => e.type === 'agent:waiting');
expect(waitingEvent).toBeDefined();
expect((waitingEvent as any).payload.questions).toHaveLength(3);
expect((waitingEvent as any).payload.questions[0].id).toBe('q1');
expect((waitingEvent as any).payload.questions[1].id).toBe('q2');
expect((waitingEvent as any).payload.questions[2].id).toBe('q3');
// Check pending questions retrieval
const pending = await manager.getPendingQuestions(agent.id);
expect(pending?.questions).toHaveLength(3);
expect(pending?.questions[0].question).toBe('Which database should we use?');
expect(pending?.questions[1].question).toBe('Which ORM do you prefer?');
expect(pending?.questions[2].question).toBe('Any additional notes?');
expect(pending?.questions[2].options).toBeUndefined();
// Resume with answers to all questions
await manager.resume(agent.id, { q1: 'PostgreSQL', q2: 'Drizzle', q3: 'Use WAL mode' });
await vi.runAllTimersAsync();
// Agent should complete
const completed = await manager.get(agent.id);
expect(completed?.status).toBe('idle');
// Pending questions should be cleared
const clearedPending = await manager.getPendingQuestions(agent.id);
expect(clearedPending).toBeNull();
});
});
});