feat: v2.2.74 → v2.2.82 — chunked writer + 코드 리뷰 패치 + /youtube 확장

주요 변경:

[chunked writer 아키텍처 (v2.2.74~v2.2.75)]
- 5-stage 다중 에이전트(planner/researcher/reflector/writer/synthesizer)
  파이프라인 제거 → 단일 ChunkedWriter 의 outline → section[N] → polish
  3-step 으로 교체. 본문 분석에서 추상화 손실 / 토큰 폭증 문제 해소
- 답변 길이 자동 분기: 짧은 prompt 는 fast-path direct 1회 호출,
  본문 분석은 chunked. outline 빈 배열도 direct 폴백

[코드 리뷰 9개 항목 일괄 패치 (v2.2.76)]
- /research polling hang 방어 (heartbeat + status 정규화 + 연속 실패 abort)
- 회사 모드 dispatcher abort 신호를 AIService.chat 까지 전달
- bridgeFetch 에 onHeartbeat 콜백 도입 (slow endpoint 사용자 친화적)
- dead code 정리: reflectionPersister.ts 제거 + enableReflection 등 좀비 config 키
- parseOutline 의 empty vs fallback reason 명시적 분리
- chatHandlers 의 회사 모드 케이스 ~325줄을 src/sidebar/companyHandlers.ts 로 분리
- Intent Alignment 라운드 한도 도달 시 smart 모드 자동 진행
- LM Studio doSwitch unload 실패 시 currentModel 정리 + load 강행
- retrieval informationDensity → queryCoverage 정합화

[/youtube 채널 지원 (v2.2.77~v2.2.82)]
- 채널/플레이리스트 URL 자동 감지 + n:N 으로 영상 개수 지정 (최대 50)
- 채널 루트 URL 에 /videos 탭 자동 append (yt-dlp enumeration 정상화)
- 영상별 순차 처리 (queue 패턴) + i/N 진행 표시 + 마지막 통계 요약
- mode:info / mode:benchmark / mode:both 분석 모드 분기
  - info: 영상 내용을 지식 카드로 추출 (튜토리얼·강의·뉴스용)
  - benchmark: 4-렌즈 대본 역기획서 (콘텐츠 제작 벤치마크용)
  - both: 둘 다 (기본)
  - bare keyword 도 허용: /youtube <url> n:1 info
- bridge 에러 메시지 [object Object] 깨짐 수정 (구조화 에러 추출)
- "패키지 없음" 등 환경 의존성 에러에 자동 가이드 첨부

[Astra: Setup Datacollect Dependencies 명령 추가 (v2.2.80)]
- Python 자동 감지 + yt-dlp / youtube-transcript-api 자동 설치
- macOS PEP 668 환경 자동 폴백 (--user --break-system-packages)
- /youtube 등에서 패키지 미설치 감지 시 "Install Now" 버튼 notification

[테스트]
- tests/agentEngine.test.ts 를 chunked flow 에 맞춰 전체 재작성
- tests/resilience_stress.test.ts Scenario B/D 를 role-aware mock 으로 갱신
- 399/399 통과

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
g1nation
2026-05-23 23:13:21 +09:00
parent 0712014fcb
commit ded3eea7ce
39 changed files with 2098 additions and 1820 deletions
+17 -2
View File
@@ -260,19 +260,28 @@ export class ModelLifecycleManager {
this.cancelLoad();
this.clearIdleTimer();
// ── 1) Unload 이전 모델 (있으면) ──────────────────────────────────────
// 의도: 메모리 회수. 실패해도 load 는 *무조건* 진행 — LM Studio 가 unload
// 못 한 모델은 보통 그냥 그대로 메모리에 떠 있고, load 가 새 모델로 메모리를
// 덮어쓰면서 자연 회수되는 경우가 많다. 여기서 throw 하면 사용자가 모델
// 교체 자체를 못 함.
// 또한 unload 실패해도 currentModel 은 null 로 정리 — 다음 단계에서 어차피
// modelKey 로 덮어쓰지만, 그 사이에 다른 코드가 currentModel 을 읽을 때
// "이미 없는 prev" 를 가리키지 않도록.
if (this.state === 'loaded' && this.currentModel && this.currentModel !== modelKey) {
const prev = this.currentModel;
this.state = 'unloading';
try {
await this.deps.client.unload(prev);
} catch (e: any) {
logError('LM Studio unload before switch failed.', { prev, error: e?.message ?? String(e) });
logError('LM Studio unload before switch failed — load 진행 강행.', { prev, error: e?.message ?? String(e) });
}
this.currentModel = null;
}
this.checkMemoryBudget(modelKey);
// ── 2) Load 새 모델 ───────────────────────────────────────────────────
this.state = 'loading';
this.currentModel = modelKey;
const ac = new AbortController();
@@ -289,10 +298,16 @@ export class ModelLifecycleManager {
void this.deps.client.preloadDraftModel(cfg.draftModel);
}
} catch (e: any) {
if (ac.signal.aborted) return; // superseded — newer switch owns state
if (ac.signal.aborted) {
// 새 switch 가 우리를 abort 시킨 경우 → 그 switch 가 state 를 새로 정함.
// 우리는 손대지 말고 빠진다.
return;
}
logError('LM Studio model load failed.', { model: modelKey, error: e?.message ?? String(e) });
this.deps.notifyError?.(`LM Studio load failed: ${e?.message ?? e}`);
if (this.loadAbort === ac) this.loadAbort = undefined;
// Load 실패 → 어떤 모델도 안 떠 있는 깨끗한 상태로 복귀. 다음 호출이 같은
// 모델을 다시 시도할 수 있게 currentModel 도 비운다.
this.state = 'idle';
this.currentModel = null;
}