feat: v2.2.3 - Stability, Self-Reflector & Intent Alignment
- 버전 2.2.3 상향 및 PATCHNOTES.md 업데이트 - [신규] src/features/selfReflector/ - 성찰 실행/검증/프롬프트 모듈 추가 - [신규] intentAlignment.ts, intentClassifier.ts - 의도 정렬 시스템 추가 - [신규] pixelOfficeState.ts - 픽셀 오피스 상태 관리 추가 - sidebarProvider, dispatcher, chatHandlers 핵심 로직 최적화 - astra-2.2.3.vsix 패키지 생성 완료 (298 tests PASS)
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* Pixel Office — Agent Work Pipeline 상태를 시각화하는 *UI Layer 전용* 모듈.
|
||||
*
|
||||
* ─────────────────── 설계 원칙 ───────────────────
|
||||
* 1. **Agent 핵심 판단 로직을 절대 바꾸지 않는다.** Pipeline 진행, contract
|
||||
* 합의, 검수 cycle, 승인 게이트 — 모두 기존 dispatcher / chatHandlers /
|
||||
* SidebarChatProvider 안에서 결정된다. 이 모듈은 그 결정을 *읽고* webview용
|
||||
* 상태 객체로 변환할 뿐이다.
|
||||
* 2. 입력은 기존에 emit되던 이벤트 (CompanyTurnEvent + alignment phase +
|
||||
* intent classifier 결과)뿐. 새로운 인터럽트 포인트를 만들지 않는다.
|
||||
* 3. 출력은 두 종류 — `AgentWorkState` (현재 작업 패널)와 `AgentBubble` 큐
|
||||
* (말풍선 연출). 둘 다 webview가 그대로 받아 그리기만 하면 된다.
|
||||
*
|
||||
* 즉 dispatcher 안의 어떤 한 줄도 "if pixelOffice ..."로 분기하지 않는다.
|
||||
*/
|
||||
|
||||
/** Agent의 현재 단계. 사용자가 명세한 11개 상태값 전부 포함. */
|
||||
export type AgentStatus =
|
||||
| 'idle'
|
||||
| 'intake'
|
||||
| 'analyzing'
|
||||
| 'need_clarification'
|
||||
| 'contract_ready'
|
||||
| 'planning'
|
||||
| 'executing'
|
||||
| 'reviewing'
|
||||
| 'waiting_approval'
|
||||
| 'error'
|
||||
| 'done';
|
||||
|
||||
/** 말풍선이 어떤 카테고리에서 나왔는지 — 스타일링 / 우선순위에 활용. */
|
||||
export type BubbleType = 'status' | 'event' | 'warning' | 'error' | 'success';
|
||||
|
||||
/** 말풍선 발생 트리거가 되는 이벤트 — 사용자가 명세한 10개 + 약간 확장. */
|
||||
export type AgentEvent =
|
||||
| 'missing_required_info'
|
||||
| 'clarification_needed'
|
||||
| 'requirement_contract_created'
|
||||
| 'plan_completed'
|
||||
| 'execution_started'
|
||||
| 'review_failed'
|
||||
| 'review_passed'
|
||||
| 'risky_change_detected'
|
||||
| 'approval_required'
|
||||
| 'error_occurred'
|
||||
| 'task_completed'
|
||||
| 'stage_loop_retry';
|
||||
|
||||
export interface AgentWorkState {
|
||||
agentId: string;
|
||||
agentName: string;
|
||||
status: AgentStatus;
|
||||
/** 사용자 원본 요청 한 줄 — 패널 상단 "Current Task"에 표시. */
|
||||
currentTask?: string;
|
||||
/** 현재 stage / phase 라벨 — "기획 논의", "QA 진행" 등. */
|
||||
currentStep?: string;
|
||||
/** 다음 stage 라벨 (있으면) — 예측 표시용. */
|
||||
nextStep?: string;
|
||||
/** 짧은 보조 메시지 (예: "라운드 2/3", "검수자: 민지"). */
|
||||
message?: string;
|
||||
/** 진행률 0~1 — 파이프라인 모드일 때 stage index / total로 계산. */
|
||||
progress?: number;
|
||||
/** Requirement Contract 요약 (alignment 완료 후 채워짐). */
|
||||
requirementContract?: {
|
||||
goal?: string;
|
||||
context?: string;
|
||||
criteria?: string[];
|
||||
format?: string;
|
||||
openQuestions?: string[];
|
||||
confidence?: 'low' | 'medium' | 'high';
|
||||
};
|
||||
/** 사용자에게 던지는 미해결 질문 목록 — need_clarification 상태에서 채움. */
|
||||
needUserInput?: string[];
|
||||
/** 승인 대기 중 항목 — waiting_approval 상태에서 채움. */
|
||||
awaitingApproval?: string;
|
||||
/** 짧은 최근 로그 — 사용자가 한눈에 흐름 파악. 최대 6개 ring buffer. */
|
||||
recentLogs?: string[];
|
||||
/** epoch ms — webview의 "n초 전" 표시용. */
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface AgentBubble {
|
||||
id: string;
|
||||
/** 어떤 캐릭터 위에 띄울지 — 단일 캐릭터 모드면 'main' 고정도 가능. */
|
||||
agentId: string;
|
||||
text: string;
|
||||
type: BubbleType;
|
||||
/** 생성 시각 epoch ms. */
|
||||
createdAt: number;
|
||||
/** 자동 사라짐 ms (webview가 사용). 기본값은 webview에서 결정. */
|
||||
durationMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자가 설정으로 켜고 끌 수 있는 행동 옵션.
|
||||
* webview는 broadcast마다 같이 받아서 즉시 반영.
|
||||
*/
|
||||
export interface PixelOfficeConfig {
|
||||
enabled: boolean;
|
||||
bubblesEnabled: boolean;
|
||||
maxVisibleBubbles: number;
|
||||
bubbleDurationMs: number;
|
||||
}
|
||||
|
||||
// ─────────────────── 상태→말풍선 텍스트 풀 ───────────────────
|
||||
// 사용자가 명세한 톤(가벼운 사무실 코미디) 유지. 무작위 선택을 위해 같은 상태에
|
||||
// 여러 안을 두되 너무 길어지지 않게 4~5개로 제한.
|
||||
|
||||
const STATUS_BUBBLE_POOL: Record<AgentStatus, string[]> = {
|
||||
idle: [
|
||||
'오늘은 무슨 일을 할까?',
|
||||
'주문 대기 중…',
|
||||
'커피 한 잔 더 하고 시작할까.',
|
||||
],
|
||||
intake: [
|
||||
'요청서 들어왔다.',
|
||||
'한번 읽어보자.',
|
||||
'오케이, 뭘 원하시는지 보자.',
|
||||
],
|
||||
analyzing: [
|
||||
'음… 의도가 조금 모호한데?',
|
||||
'맥락부터 정리해보자.',
|
||||
'핵심이 뭐였더라.',
|
||||
'이거 작업 범위가 어디까지지?',
|
||||
],
|
||||
need_clarification: [
|
||||
'이건 사용자 확인이 먼저야.',
|
||||
'질문 하나만 하고 가자.',
|
||||
'추측으로 가면 위험해.',
|
||||
'핵심 정보가 빠졌네.',
|
||||
],
|
||||
contract_ready: [
|
||||
'좋아, 작업 조건 정리 완료.',
|
||||
'이제 방향은 잡혔어.',
|
||||
'계약서 도장 찍었다.',
|
||||
'이제 진짜 시작.',
|
||||
],
|
||||
planning: [
|
||||
'순서부터 잡아보자.',
|
||||
'기존 기능은 건드리지 말자.',
|
||||
'화이트보드 좀 빌릴게.',
|
||||
'단계 나눠서 가자.',
|
||||
],
|
||||
executing: [
|
||||
'코드 들어간다.',
|
||||
'이번엔 단순하게 가자.',
|
||||
'집중 모드 진입.',
|
||||
'키보드 워밍업 완료.',
|
||||
],
|
||||
reviewing: [
|
||||
'잠깐, 이건 다시 보자.',
|
||||
'기존 기능 깨지는지 확인해야 해.',
|
||||
'검수자 시점으로 한 번 더.',
|
||||
'엣지 케이스 빠진 거 없나.',
|
||||
],
|
||||
waiting_approval: [
|
||||
'이건 승인 없이 못 바꿔.',
|
||||
'위험 작업 감지. 확인 필요!',
|
||||
'사장님 결재 부탁드립니다.',
|
||||
'도장 받기 전엔 멈춤.',
|
||||
],
|
||||
error: [
|
||||
'앗, 이건 예상 못 했는데…',
|
||||
'조건 하나 놓쳤네.',
|
||||
'잠깐, 다시 정리.',
|
||||
'엇, 이게 깨졌네.',
|
||||
],
|
||||
done: [
|
||||
'좋아, 끝났다!',
|
||||
'이번 작업 깔끔하게 완료.',
|
||||
'커피 한 잔.',
|
||||
'오늘치 끝!',
|
||||
],
|
||||
};
|
||||
|
||||
const EVENT_BUBBLE_POOL: Record<AgentEvent, string[]> = {
|
||||
missing_required_info: [
|
||||
'핵심 정보가 빠졌어.',
|
||||
'이거 빠지면 추측해야 해.',
|
||||
],
|
||||
clarification_needed: [
|
||||
'질문 하나만.',
|
||||
'확실하지 않으면 먼저 물어보자.',
|
||||
],
|
||||
requirement_contract_created: [
|
||||
'계약서 도장 찍었다.',
|
||||
'이제 방향은 잡혔어.',
|
||||
],
|
||||
plan_completed: [
|
||||
'계획 정리 끝!',
|
||||
'순서대로 가자.',
|
||||
],
|
||||
execution_started: [
|
||||
'코드 들어간다.',
|
||||
'시작!',
|
||||
],
|
||||
review_failed: [
|
||||
'조건 하나 놓쳤네. 다시 보자.',
|
||||
'너무 복잡하게 가는 거 아냐?',
|
||||
],
|
||||
review_passed: [
|
||||
'검수 통과!',
|
||||
'셋 다 만족이래.',
|
||||
],
|
||||
risky_change_detected: [
|
||||
'잠깐, 이건 승인 필요!',
|
||||
'파일 삭제는 함부로 하면 안 돼.',
|
||||
],
|
||||
approval_required: [
|
||||
'결재 부탁드립니다.',
|
||||
'도장 받고 이어 갈게.',
|
||||
],
|
||||
error_occurred: [
|
||||
'앗, 이건 예상 못 했는데…',
|
||||
'엇, 이게 깨졌네.',
|
||||
],
|
||||
task_completed: [
|
||||
'좋아, 끝났다!',
|
||||
'오늘치 끝.',
|
||||
],
|
||||
stage_loop_retry: [
|
||||
'한 번 더 가자.',
|
||||
'버그 잡고 다시 시도.',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* 텍스트 풀에서 하나 뽑기. 같은 결과가 연달아 나오지 않게 lastPicked를
|
||||
* 받아 회피. 풀이 1개뿐이면 어쩔 수 없이 그걸 반환.
|
||||
*/
|
||||
export function pickBubbleText(pool: string[], lastPicked?: string): string {
|
||||
if (!pool || pool.length === 0) return '';
|
||||
if (pool.length === 1) return pool[0];
|
||||
let candidates = pool;
|
||||
if (lastPicked) candidates = pool.filter((s) => s !== lastPicked);
|
||||
if (candidates.length === 0) candidates = pool;
|
||||
return candidates[Math.floor(Math.random() * candidates.length)];
|
||||
}
|
||||
|
||||
export function getStatusBubbleText(status: AgentStatus, lastPicked?: string): string {
|
||||
return pickBubbleText(STATUS_BUBBLE_POOL[status] ?? [], lastPicked);
|
||||
}
|
||||
|
||||
export function getEventBubbleText(event: AgentEvent, lastPicked?: string): string {
|
||||
return pickBubbleText(EVENT_BUBBLE_POOL[event] ?? [], lastPicked);
|
||||
}
|
||||
|
||||
/** 이벤트 → 말풍선 type 매핑. 색상/스타일을 webview가 결정할 때 사용. */
|
||||
export function eventBubbleType(event: AgentEvent): BubbleType {
|
||||
switch (event) {
|
||||
case 'error_occurred': return 'error';
|
||||
case 'review_failed':
|
||||
case 'risky_change_detected':
|
||||
case 'missing_required_info':
|
||||
return 'warning';
|
||||
case 'task_completed':
|
||||
case 'review_passed':
|
||||
case 'plan_completed':
|
||||
case 'requirement_contract_created':
|
||||
return 'success';
|
||||
default: return 'event';
|
||||
}
|
||||
}
|
||||
|
||||
/** AgentBubble factory — id 자동 생성. */
|
||||
export function makeBubble(opts: {
|
||||
agentId: string;
|
||||
text: string;
|
||||
type?: BubbleType;
|
||||
durationMs?: number;
|
||||
}): AgentBubble {
|
||||
return {
|
||||
id: `b-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
|
||||
agentId: opts.agentId,
|
||||
text: opts.text,
|
||||
type: opts.type ?? 'status',
|
||||
createdAt: Date.now(),
|
||||
durationMs: opts.durationMs,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user