101 lines
3.9 KiB
TypeScript
101 lines
3.9 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import { lockManager } from '../core/lock';
|
|
import { actionQueue } from '../core/queue';
|
|
import { logInfo, logError } from '../utils';
|
|
|
|
/**
|
|
* 에이전트 인터페이스 정의 (의존성 주입을 위함)
|
|
*/
|
|
export interface IAgent {
|
|
execute(input: string, context?: string, signal?: AbortSignal): Promise<string>;
|
|
}
|
|
|
|
/**
|
|
* 파이프라인 단계 상태 정의
|
|
*/
|
|
export type PipelineStage = 'idle' | 'planner' | 'researcher' | 'writer' | 'completed' | 'error';
|
|
|
|
/**
|
|
* AgentEngine:
|
|
* Producer-Consumer 패턴을 기반으로 멀티 에이전트 워크플로우를 오케스트레이션하는 핵심 엔진.
|
|
* 명시적 락(Mutex)과 의존성 주입(DI)을 통해 안정성과 유연성을 확보합니다.
|
|
*/
|
|
export class AgentEngine {
|
|
private stage: PipelineStage = 'idle';
|
|
|
|
constructor(
|
|
private readonly planner: IAgent,
|
|
private readonly researcher: IAgent,
|
|
private readonly writer: IAgent
|
|
) {}
|
|
|
|
/**
|
|
* 멀티 에이전트 워크플로우 실행
|
|
* @param missionId 작업을 식별하기 위한 고유 ID (Mutex 락에 사용)
|
|
*/
|
|
public async runMission(
|
|
missionId: string,
|
|
prompt: string,
|
|
brainContext: string,
|
|
signal: AbortSignal,
|
|
onProgress: (stage: PipelineStage, message: string) => void
|
|
): Promise<string> {
|
|
|
|
// 1. 명시적 락 획득 (Mutex) - 동일 미션의 중복 실행 방지
|
|
const release = await lockManager.acquire(`mission_${missionId}`);
|
|
|
|
try {
|
|
// 2. 작업을 비동기 큐에 등록 (Producer-Consumer)
|
|
return await actionQueue.enqueue(async () => {
|
|
logInfo(`[AgentEngine] 미션 시작: ${missionId}`);
|
|
|
|
// --- Phase 1: Planner ---
|
|
this.updateStage('planner', '전략 수립 중...', onProgress);
|
|
if (signal.aborted) throw new Error('AbortError');
|
|
const plan = await this.planner.execute(prompt, brainContext, signal);
|
|
this.validateResult(plan, 'Planner');
|
|
|
|
// --- Phase 2: Researcher ---
|
|
this.updateStage('researcher', '핵심 정보 수집 및 분석 중...', onProgress);
|
|
if (signal.aborted) throw new Error('AbortError');
|
|
await this.delay(500); // 시스템 부하 분산을 위한 미세 지연
|
|
const research = await this.researcher.execute(plan, brainContext, signal);
|
|
this.validateResult(research, 'Researcher');
|
|
|
|
// --- Phase 3: Writer ---
|
|
this.updateStage('writer', '최종 리포트 작성 및 편집 중...', onProgress);
|
|
if (signal.aborted) throw new Error('AbortError');
|
|
await this.delay(500);
|
|
const finalReport = await this.writer.execute(research, prompt, signal);
|
|
this.validateResult(finalReport, 'Writer');
|
|
|
|
this.updateStage('completed', '미션 완료', onProgress);
|
|
return finalReport;
|
|
});
|
|
} catch (error: any) {
|
|
this.updateStage('error', `오류 발생: ${error.message}`, onProgress);
|
|
logError(`[AgentEngine] 미션 실패 (${missionId}):`, error);
|
|
throw error;
|
|
} finally {
|
|
// 3. 락 해제
|
|
release();
|
|
this.stage = 'idle';
|
|
}
|
|
}
|
|
|
|
private updateStage(stage: PipelineStage, message: string, onProgress: (stage: PipelineStage, message: string) => void) {
|
|
this.stage = stage;
|
|
onProgress(stage, message);
|
|
}
|
|
|
|
private validateResult(data: string, step: string) {
|
|
if (!data || data.trim().length < 10) {
|
|
throw new Error(`${step} 에이전트로부터 유효한 응답을 받지 못했습니다.`);
|
|
}
|
|
}
|
|
|
|
private delay(ms: number) {
|
|
return new Promise(r => setTimeout(r, ms));
|
|
}
|
|
}
|