feat(formatter): enforce implementation code snippet section in wiki artifacts

This commit is contained in:
g1nation
2026-05-04 15:58:47 +09:00
parent a74c881375
commit 1395bbcdae
25 changed files with 108 additions and 42 deletions
+30 -38
View File
@@ -416,8 +416,6 @@ export class CacheManager {
* - Error Recovery Matrix 기반의 Transient/Permanent 오류 자동 분류 및 복구
*/
export class AgentEngine {
private state: MissionState | null = null;
constructor(
private readonly planner: IAgent,
private readonly researcher: IAgent,
@@ -435,14 +433,15 @@ export class AgentEngine {
signal: AbortSignal,
onProgress: (stage: PipelineStage, message: string) => void
): Promise<string> {
let state: MissionState;
// 0. 상태 복원 시도 (Resumption)
const existingState = MissionState.loadFromDisk(missionId);
if (existingState && existingState.stage !== 'completed') {
logInfo(`[AgentEngine] 기존 미션 발견. '${existingState.stage}' 단계부터 재개합니다.`);
this.state = existingState;
state = existingState;
} else {
this.state = new MissionState(missionId);
state = new MissionState(missionId);
}
// 1. 명시적 락 획득 (Mutex) - 동일 미션의 중복 실행 방지
@@ -454,9 +453,9 @@ export class AgentEngine {
logInfo(`[AgentEngine] 미션 시작: ${missionId}`);
// --- Phase 1: Planner ---
let plan = this.state!.getResult('plan');
let plan = state.getResult('plan');
if (!plan) {
this.transition('planner', '전략 수립 중...', onProgress);
this.transition(state, 'planner', '전략 수립 중...', onProgress);
this.checkAbort(signal);
// Deduplication: 동일 프롬프트 캐시 확인
@@ -466,26 +465,26 @@ export class AgentEngine {
plan = cachedPlan;
} else {
plan = await this.resilientExecute(
this.planner, 'Planner', prompt, brainContext, signal, onProgress,
state, this.planner, 'Planner', prompt, brainContext, signal, onProgress,
{ context: brainContext, signal, config: { role: 'planner' } }
);
CacheManager.set(prompt, brainContext, plan);
}
this.state!.setResult('plan', plan);
state.setResult('plan', plan);
}
this.validateResult(plan, 'Planner');
// --- Phase 2 & 3: Researcher ---
let research = this.state!.getResult('research');
let writerPrep = this.state!.getResult('writerPrep');
let research = state.getResult('research');
let writerPrep = state.getResult('writerPrep');
if (!research || !writerPrep) {
this.transition('researcher', '핵심 정보 수집 및 분석 중...', onProgress);
this.transition(state, 'researcher', '핵심 정보 수집 및 분석 중...', onProgress);
this.checkAbort(signal);
const [res, prep] = await Promise.all([
research || this.resilientExecute(
this.researcher, 'Researcher', plan, brainContext, signal, onProgress,
state, this.researcher, 'Researcher', plan, brainContext, signal, onProgress,
{ context: brainContext, signal, config: { role: 'researcher' } }
),
writerPrep || this.prepareWriterContext(prompt, plan, brainContext)
@@ -493,16 +492,16 @@ export class AgentEngine {
research = res;
writerPrep = prep;
this.state!.setResult('research', research);
this.state!.setResult('writerPrep', writerPrep);
state.setResult('research', research);
state.setResult('writerPrep', writerPrep);
}
this.validateResult(research, 'Researcher');
// --- Phase 3: Writer ---
this.transition('writer', '최종 리포트 작성 및 편집 중...', onProgress);
this.transition(state, 'writer', '최종 리포트 작성 및 편집 중...', onProgress);
this.checkAbort(signal);
const finalReport = await this.resilientExecute(
this.writer, 'Writer', research, prompt, signal, onProgress,
state, this.writer, 'Writer', research, prompt, signal, onProgress,
{ context: brainContext, signal, config: { role: 'writer' }, priorResults: { plan, writerPrep } }
);
this.validateResult(finalReport, 'Writer');
@@ -510,22 +509,21 @@ export class AgentEngine {
// 3. 지식 저장 포맷 표준화 (Standardization: Astra 피드백)
const standardizedReport = WikiFormatter.format(finalReport, missionId);
this.transition('completed', '미션 완료', onProgress);
logInfo(`[AgentEngine] 미션 완료: ${missionId} (총 ${this.state!.getElapsedMs()}ms)`);
this.transition(state, 'completed', '미션 완료', onProgress);
logInfo(`[AgentEngine] 미션 완료: ${missionId} (총 ${state.getElapsedMs()}ms)`);
return standardizedReport;
});
} catch (error: any) {
const { type, rule } = ErrorClassifier.classify(error);
const stageName = (this.state?.stage || 'unknown').toUpperCase();
const stageName = (state!.stage || 'unknown').toUpperCase();
this.transition('error', `오류 발생: ${error.message}`, onProgress);
this.transition(state!, 'error', `오류 발생: ${error.message}`, onProgress);
// 실패 원인 명시적 기록 (Astra 피드백: Record failure reason)
if (this.state) {
this.state.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
}
state!.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
// Error Recovery Matrix 기반 세분화된 로깅
switch (type) {
@@ -541,27 +539,22 @@ export class AgentEngine {
}
// 감사 이력 덤프 (디버깅용)
if (this.state) {
logError(`[AgentEngine] Audit Trail for ${missionId}:\n${this.state.summarizeAudit()}`);
}
logError(`[AgentEngine] Audit Trail for ${missionId}:\n${state!.summarizeAudit()}`);
throw error;
} finally {
// 3. 락 해제
release();
if (this.state) {
this.state = null;
}
}
}
/**
* 현재 미션 상태 객체를 외부에서 읽을 수 있도록 노출합니다.
* 외부 모니터링 시스템 연동에 활용됩니다.
* @deprecated 이제 미션 상태는 로컬 스코프에서 관리됩니다.
*/
public getMissionState(): MissionState | null {
return this.state;
return null;
}
// ─── Resilience Layer ───
/**
@@ -572,6 +565,7 @@ export class AgentEngine {
* - Abort: 조용하게 예외를 전파 (Graceful Exit).
*/
private async resilientExecute(
state: MissionState,
agent: IAgent,
agentName: string,
input: string,
@@ -589,7 +583,7 @@ export class AgentEngine {
if (attempt > 0) {
const backoffMs = transientRule.backoffBaseMs * Math.pow(2, attempt - 1);
logInfo(`[AgentEngine] [RETRY] ${agentName} 재시도 ${attempt}/${transientRule.maxRetries} (${backoffMs}ms 후)`);
onProgress(this.state?.stage || 'error', `${agentName} 재시도 중... (${attempt}/${transientRule.maxRetries})`);
onProgress(state.stage, `${agentName} 재시도 중... (${attempt}/${transientRule.maxRetries})`);
await new Promise(r => setTimeout(r, backoffMs));
this.checkAbort(signal);
}
@@ -647,10 +641,8 @@ export class AgentEngine {
/**
* MissionState를 통한 상태 전환 + 외부 콜백 호출.
*/
private transition(stage: PipelineStage, message: string, onProgress: (stage: PipelineStage, message: string) => void) {
if (this.state) {
this.state.transition(stage, message);
}
private transition(state: MissionState, stage: PipelineStage, message: string, onProgress: (stage: PipelineStage, message: string) => void) {
state.transition(stage, message);
onProgress(stage, message);
}