feat(08.1-02): update MockAgentManager to schema-aligned scenarios

- Change MockAgentScenario from outcome-based to status-based discriminated union
- Align with agent output schema: done/question/unrecoverable_error
- Update completeAgent() to handle new status types
- Update resume() to use new scenario format
This commit is contained in:
Lukas May
2026-01-31 15:28:38 +01:00
parent ee0d6eae33
commit ead4614383

View File

@@ -26,23 +26,28 @@ import type {
/** /**
* Scenario configuration for mock agent behavior. * Scenario configuration for mock agent behavior.
* Uses discriminated union on status to match agent output schema.
*/ */
export interface MockAgentScenario { export type MockAgentScenario =
/** How agent completes: 'success' | 'crash' | 'waiting_for_input' */ | {
outcome: 'success' | 'crash' | 'waiting_for_input'; status: 'done';
/** Delay before completion (ms). Default 0 for synchronous tests. */ result?: string;
delay?: number;
/** Result message for success/crash */
message?: string;
/** Files modified (for success) */
filesModified?: string[]; filesModified?: string[];
/** Question to surface (for waiting_for_input) */ delay?: number;
question?: string;
/** Options for question (for waiting_for_input) */
options?: Array<{ label: string; description?: string }>;
/** Whether multiple options can be selected (for waiting_for_input) */
multiSelect?: boolean;
} }
| {
status: 'question';
question: string;
options?: Array<{ label: string; description?: string }>;
multiSelect?: boolean;
delay?: number;
}
| {
status: 'unrecoverable_error';
error: string;
attempted?: string;
delay?: number;
};
/** /**
* Internal agent record with scenario and timer tracking. * Internal agent record with scenario and timer tracking.
@@ -59,10 +64,10 @@ interface MockAgentRecord {
* Default scenario: immediate success with generic message. * Default scenario: immediate success with generic message.
*/ */
const DEFAULT_SCENARIO: MockAgentScenario = { const DEFAULT_SCENARIO: MockAgentScenario = {
outcome: 'success', status: 'done',
delay: 0, result: 'Task completed successfully',
message: 'Task completed successfully',
filesModified: [], filesModified: [],
delay: 0,
}; };
/** /**
@@ -178,7 +183,7 @@ export class MockAgentManager implements AgentManager {
} }
/** /**
* Complete agent based on scenario outcome. * Complete agent based on scenario status.
*/ */
private completeAgent(agentId: string, scenario: MockAgentScenario): void { private completeAgent(agentId: string, scenario: MockAgentScenario): void {
const record = this.agents.get(agentId); const record = this.agents.get(agentId);
@@ -186,11 +191,11 @@ export class MockAgentManager implements AgentManager {
const { info } = record; const { info } = record;
switch (scenario.outcome) { switch (scenario.status) {
case 'success': case 'done':
record.result = { record.result = {
success: true, success: true,
message: scenario.message ?? 'Task completed successfully', message: scenario.result ?? 'Task completed successfully',
filesModified: scenario.filesModified, filesModified: scenario.filesModified,
}; };
record.info.status = 'idle'; record.info.status = 'idle';
@@ -211,10 +216,10 @@ export class MockAgentManager implements AgentManager {
} }
break; break;
case 'crash': case 'unrecoverable_error':
record.result = { record.result = {
success: false, success: false,
message: scenario.message ?? 'Agent crashed', message: scenario.error,
}; };
record.info.status = 'crashed'; record.info.status = 'crashed';
record.info.updatedAt = new Date(); record.info.updatedAt = new Date();
@@ -227,18 +232,18 @@ export class MockAgentManager implements AgentManager {
agentId, agentId,
name: info.name, name: info.name,
taskId: info.taskId, taskId: info.taskId,
error: scenario.message ?? 'Agent crashed', error: scenario.error,
}, },
}; };
this.eventBus.emit(event); this.eventBus.emit(event);
} }
break; break;
case 'waiting_for_input': case 'question':
record.info.status = 'waiting_for_input'; record.info.status = 'waiting_for_input';
record.info.updatedAt = new Date(); record.info.updatedAt = new Date();
record.pendingQuestion = { record.pendingQuestion = {
question: scenario.question ?? 'User input required', question: scenario.question,
options: scenario.options, options: scenario.options,
multiSelect: scenario.multiSelect, multiSelect: scenario.multiSelect,
}; };
@@ -252,7 +257,7 @@ export class MockAgentManager implements AgentManager {
name: info.name, name: info.name,
taskId: info.taskId, taskId: info.taskId,
sessionId: info.sessionId ?? '', sessionId: info.sessionId ?? '',
question: scenario.question ?? 'User input required', question: scenario.question,
options: scenario.options, options: scenario.options,
multiSelect: scenario.multiSelect, multiSelect: scenario.multiSelect,
}, },
@@ -369,11 +374,14 @@ export class MockAgentManager implements AgentManager {
// Re-run scenario (after resume, typically completes successfully) // Re-run scenario (after resume, typically completes successfully)
// For testing, we use a new scenario that defaults to success // For testing, we use a new scenario that defaults to success
// Extract filesModified from original scenario if it was a 'done' type
const originalFilesModified =
record.scenario.status === 'done' ? record.scenario.filesModified : undefined;
const resumeScenario: MockAgentScenario = { const resumeScenario: MockAgentScenario = {
outcome: 'success', status: 'done',
delay: record.scenario.delay ?? 0, delay: record.scenario.delay ?? 0,
message: 'Resumed and completed successfully', result: 'Resumed and completed successfully',
filesModified: record.scenario.filesModified, filesModified: originalFilesModified,
}; };
this.scheduleCompletion(agentId, resumeScenario); this.scheduleCompletion(agentId, resumeScenario);