chore: v2.2.73 — ASTRA-DEBUG 로그 레벨 + webview CSP font-src 보강

- ASTRA-DEBUG 정상 흐름 로그를 console.error → logInfo/console.log 로 강등
  (chatHandlers, extension, slashRouter): DevTools에 ERR로 찍히던 오탐 제거
- sidebar webview에 명시적 CSP meta 추가 + font-src에 data: 허용
  (sidebar.html, sidebarProvider._getHtml): VS Code outer iframe이 codicon.ttf를
  data:font/ttf 로 inject하면서 기본 CSP에 막혀 매 prompt 마다 violation
  경고가 찍히던 문제 해소
- 누적된 LM Studio / agent / 컨텍스트 매니저 / 테스트 갱신 동반

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
g1nation
2026-05-23 15:52:19 +09:00
parent 36db170844
commit 0712014fcb
43 changed files with 2417 additions and 977 deletions
+140
View File
@@ -1,5 +1,145 @@
# Astra Patch Notes
## v2.2.72 (2026-05-23)
### ⚡ LM Studio 통신 hardening + 속도 부스트 팩
한 turn 에 두 라운드 작업을 묶었다. (A) 통신 경로 안전망 9건 + (B) 속도 개선 4건.
**(A) 통신 hardening**
- **Sampling parity SDK ↔ REST.** 기존엔 `topP/topK/minP/repeatPenalty` 가 SDK 경로에만 적용 → 핸들이 죽어 REST 로 fallback 되면 한글 토큰 깨짐(`붕괴→붕점`) 재발. 공유 `LmStudioSampling` + `samplingToRestBody` 로 두 경로가 동일 값을 보내도록 통일. Ollama 도 `options.{top_p,top_k,min_p,repeat_penalty}` 로 같이 받음.
- **신규 설정:** `g1nation.lmStudio.sampling.{topP=0.9, topK=20, minP=0.05, repeatPenalty=1.1}` + `g1nation.lmStudio.statsInBudget=true`.
- **prediction.stats UI 노출.** 매 turn 끝나면 ctx-badge 에 `… · 32.1 tok/s · TTFT 0.40s` 표시 (SDK 경로만). 툴팁에 출력 토큰 수 / 총 시간 / stopReason 도 추가.
- **listDownloaded TTL 캐시 (60s).** 사이드바 드롭다운 열 때마다 LM Studio 디스크 walk 하던 호출을 캐싱. 빈 결과는 캐싱 안 함 (방금 켠 LM Studio 가리는 회귀 방지). `setBaseUrl` / `invalidateCaches()` 가 캐시 초기화.
- **Empty-response 복구 일원화.** `LMStudioStreamer.stream()` 의 attempt-2 retry 가 dead-handle 에러뿐 아니라 "에러 없이 0 token" 케이스도 다룸. agent.ts 의 중복된 handle-reset retry 블록 (~30 LOC) 삭제. REST fallback 은 유지.
- **handle-dead 패턴 확장.** `channel closed`, `WebSocket (is not open|closed|disconnected)`, `Connection (lost|reset|closed)`, `ECONNRESET`, `socket hang up` 추가.
- **`httpToWebSocketUrl` path 정리.** `/api/v0`, `/api/v1`, `/v1`, `/api` 를 loop 으로 unwind — `http://host/api/v0` → ws root 까지 한 번에.
- **service-down 조기 break.** `createStreamingRequest``error.cause.code === 'ECONNREFUSED' | 'ENOTFOUND' | 'EAI_AGAIN'` 감지 시 attempt/variant/candidate 루프 즉시 종료. 12회 fetch → 1회 → 사용자 에러 ~1s.
- **callAgent cutoff warn.** sub-agent SDK 호출도 `stopReason` 검사 → `/maxPredicted|context|truncat/` 매칭 시 logError. 잘린 specialist 출력이 silently pipeline 을 오염시키는 거 방지.
**(B) 속도 부스트**
- **Speculative decoding.** 신규 설정 `g1nation.lmStudio.draftModel` (빈 값 = OFF). 작은 draft 모델 (e.g. `gemma-2b-it`) 지정 시 large 모델 1.5~3× throughput. `ChatStreamRequest.draftModel` → SDK `respond({draftModel})`. main 모델 load 직후 lifecycle 이 `unstable_preloadDraftModel` 호출해 cold-load 비용 제거. ctx-badge 에 `spec 68%` accept-ratio 표시 (60%+ healthy, 30% 미만 = draft 가 잘못 골라 오히려 느려질 수 있음).
- **Load-time 옵션 (8개).** `client.load()``LMStudioLoadConfig` 받아 `LLMLoadModelConfig` (GPUSetting 래퍼 포함) 으로 SDK 에 전달.
- `g1nation.lmStudio.load.flashAttention` (true) — long-context 10~20%
- `g1nation.lmStudio.load.gpuOffloadRatio` ("max" | "off" | 0-1)
- `g1nation.lmStudio.load.offloadKVCacheToGpu` (true)
- `g1nation.lmStudio.load.keepModelInMemory` (true) — swap-out 방지
- `g1nation.lmStudio.load.useFp16ForKVCache` (false) — KV-cache 메모리 반감 (VRAM 빠듯할 때)
- `g1nation.lmStudio.load.evalBatchSize` (0 = 엔진 default) — prefill 속도
- **`liveStreamTokens` 기본 → true (← false).** TTFT 체감 향상 — sanitize + `streamReplace` 가 생성 끝에 최종 답변으로 교체하므로 control token 노출은 잠깐만 가능. (memory 의 "sanitize-before-post handles the leak" 가 근거)
**시스템 프롬프트 KV-cache (item 1+5)**: 조사 결과 현재 ordering (stable head → `[CONTEXT]` body → stable tail) 이 이미 prefix-cache 친화적이라 코드 변경 없음. 단, agent persona-first 배치는 small-model anchoring 우선으로 유지 — KV-cache 잠재 이득보다 답변 품질이 우선.
**Touched:** `src/{config,agent,extension}.ts`, `src/lmstudio/{client,streamer,lifecycleManager}.ts`, `media/sidebar.js`, `package.json` (12개 신규 설정 + `liveStreamTokens` default flip), 2개 test FakeClient 에 `listDownloadedCached` stub. 401/401 jest 통과 · tsc clean · esbuild 2.9MB.
**신규 패키징:** `astra-2.2.72.vsix`.
---
## v2.2.71 (2026-05-23)
### 📦 자동 기록 줄 전체를 도구 ▾ 메뉴 안으로 이동
- **요청 명확화:** v2.2.70 에서 토글만 도구 메뉴에 추가했는데, 사용자가 원했던 건 사이드바 하단의 records-line ("● 자동 기록 · filename · 기록 ▾") **줄 전체** 를 도구 메뉴 안으로 옮기는 것.
- **수정:** `media/sidebar.html``<div class="records-line">` 통째로 삭제. 그 안에 있던 모든 요소 (자동 기록 status, recordsLatest, chronicleRecordSel, openChronicleRecordBtn, refreshChronicleRecordsBtn, openDesignerBtn) 를 도구 ▾ 드롭다운 안으로 흡수. 도구 메뉴는 `hdr-menu-wide` 로 변경.
- **새 메뉴 레이아웃:** 도구 ▾ 클릭 시
- "자동 기록" 섹션 — 토글 / 최근 저장 기록 status / 기록 selector / 선택 기록 열기 / 새로고침 / 폴더 열기
- "도구" 섹션 — 근거 추적 JSON 보기 / 원본 답변을 두뇌에 저장 / 두뇌 동기화
- **JS 안전성:** 모든 element ID 는 유지했으므로 기존 sidebar.js 핸들러는 그대로 작동. `renderChronicleAutoToggle` 만 살짝 수정 — chronicleAutoStatus 가 이제 자식 (status-dot · recordsLatest) 을 가진 컨테이너라 `textContent` 직접 할당이 자식을 지우는 회귀를 막기 위해 opacity / title 만 갱신하도록 변경.
- **사이드바 공간:** records-line 줄 한 줄 제거 → context bar 와 chat 본문 사이 noise 감소.
- **신규 패키징:** `astra-2.2.71.vsix`.
---
## v2.2.70 (2026-05-23)
### 🎚 자동 기록 On/Off 토글 (도구 메뉴)
- **추가:** 사이드바 records-line 의 "자동 기록" 표시를 끄고 켤 수 있는 토글. 위치는 **도구 ▾** 드롭다운 첫 항목 (`자동 기록: 켜짐 / 꺼짐`).
- **신규 설정:** `g1nation.chronicleAutoRecord` (기본 `true`). 토글 클릭 시 즉시 VS Code Global 설정에 영구 저장 — 다음 세션에도 유지.
- **gating:** `_autoWriteChronicleAfterPrompt` 진입 시 `getConfig().chronicleAutoRecord === false` 면 early return. 자동 저장만 멈추고, 수동 기록 (도구 메뉴의 다른 기록 항목, `/wiki` 등) 은 그대로 동작.
- **UI 피드백:** OFF 일 때 records-line 의 "자동 기록" 라벨이 "자동 기록 (꺼짐)" + dim opacity, 최근 기록 라벨도 dim 처리해 한눈에 상태 파악. 클릭 반응성을 위해 낙관적 갱신 후 서버 응답으로 보정.
- **메시지:** 신규 webview ↔ extension 메시지 `setChronicleAutoRecord` / `getChronicleAutoRecord` / `chronicleAutoRecordStatus`. `chronicleHandlers.ts` 에서 라우팅.
- **신규 패키징:** `astra-2.2.70.vsix`.
---
## v2.2.69 (2026-05-23)
### 💾 대화 히스토리 — 슬라이딩 윈도우 + 모드 전환 bridge
- **현황 확인:** "히스토리 전역 단일 관리" 요구는 이미 충족 — `AgentExecutor.chatHistory` 단일 인스턴스이며 에이전트/회사/멀티에이전트 모드 전환은 history 를 비우지 않는다. 명시적 `/newChat` 이나 세션 삭제 때만 reset.
- **수정 1 — sliding window 요약.** 기존 `trimHistoryToBudget` 은 오래된 메시지를 단순히 `[이전 대화 N개 ... 생략됨]` count 마커로 대체 → 모델이 "이전에 무슨 얘기를 했는지" 모름. 이제는 dropped 메시지 배열을 marker factory 로 같이 넘기고, agent.ts `buildDroppedHistorySummary()` 가 추가 LLM 호출 없이 heuristic 으로 (a) 각 user prompt 첫 문장 (b) 각 assistant 답변 첫 문장 (R1 conclusion-first 가정) 만 뽑아 `U1: ... / A1: ... / U2: ...` 형식의 한 system 메시지로 압축. 8턴 이상이면 가장 오래된 절반은 한 줄로 축약.
- **수정 2 — 모드 전환 bridge.** `AgentExecutor._lastModeSignature` 로 (agent skill, multiAgent, company mode, brain) 의 해시를 추적. handlePrompt 진입 시 직전 값과 다르면 system prompt 에 `[MODE TRANSITION BRIDGE] 이전 모드 / 현재 모드 / 직전 대화 주제` 한 블록을 끼움. chatHistory 는 그대로라 사용자에겐 대화 연속이고, 모델은 새 페르소나/포맷을 따르면서도 직전 맥락을 잊지 않는다. `clearHistory` / `resetConversation` 에서 signature 도 함께 초기화해 새 세션 첫 메시지에 spurious bridge 가 끼지 않게 함.
- **시그니처 변경:** `trimHistoryToBudget``makeMarker``(droppedCount, droppedMessages)` 두 인자를 받는다. 호출부 (`agent.ts`) 와 단위 테스트 (`contextManager.test.ts`) 갱신.
- **신규 패키징:** `astra-2.2.69.vsix`.
---
## v2.2.68 (2026-05-23)
### 📐 답변 형식 — 7개 hard rules
- **변경:** 답변 포맷 규칙을 사용자가 지정한 7개 hard rule 로 전면 교체. 기존 "긴 답변엔 핵심 요약 블록 + 상세 설명 + 제안" 의 3-section 템플릿은 폐기.
- R1. 첫 문장에 결론 (no 인삿말, no "분석해보겠습니다", no "핵심 요약" 라벨)
- R2. 섹션 최대 3개
- R3. 같은 내용을 두 번 말하지 않는다
- R4. 볼드는 전체 답변에서 3개 이하
- R5. 추가 정보 없이 판단 가능하면 바로 실행
- R6. 질문은 1개만 — (a) 방향이 두 갈래로 갈리고 사용자 의도를 알 수 없을 때, 또는 (b) 비가역 작업 직전
- R7. 추측 가능하면 추측+실행하되 가정 한 줄 명시 ("가정: ...")
- **적용 범위:** 단일 에이전트 경로 (`BASE_SYSTEM_PROMPT [OUTPUT FORMAT]`) + 멀티 에이전트 최종 단계 (`SynthesizerAgent` persona) 양쪽에 동일 규칙 주입. 두 경로 모두 같은 형식으로 답변하도록 보장.
- **부수 정리:** `[FOLLOW-UP QUESTION RULES]` 섹션은 R6 에 흡수돼 제거. `[ENGINEERING STANCE]` 의 "Give the verdict first, then explain tradeoffs" 도 R1 과 중복이라 정리.
- **신규 패키징:** `astra-2.2.68.vsix`.
---
## v2.2.67 (2026-05-23)
### 🧠 두뇌 추가/수정/삭제 정상화
- **문제:** v2.2.66 이후 신고된 두 가지 회귀.
1. 두뇌 "추가" 후 dropdown 에 새 brain 이 들어가긴 하는데 선택된 표시는 마지막 옵션인 `+ Add New Brain...` 으로 굳어버림 → 사용자에겐 "이름이 add new brain 으로 바뀜" 으로 보임.
2. 그 상태에서 "수정"/"삭제" 버튼이 침묵 — 클릭해도 아무 일 안 일어남.
- **원인 1 — selected 적용 순서:** brainProfiles 핸들러가 `option.selected = true``appendChild` 직전에 거는 패턴이라 일부 Chromium webview 가 무시. 결과적으로 dropdown selectedIndex 가 마지막 옵션 (`+ Add New Brain...`) 에 머무름.
- **원인 2 — `brainSel.value === 'new'` 잠금:** 사용자가 dropdown 의 `+ Add New Brain...` 옵션을 직접 클릭하거나, 폴더 선택 모달을 취소하면 `brainSel.value``'new'` 로 굳어버림. 수정/삭제 버튼 onclick 첫 줄 `if (brainSel.value === 'new') return;` 가 silently early-return → 수정 안 됨.
- **수정 1 — selected 적용 순서 변경.** 옵션을 다 넣은 *후에* `brainSel.value = activeBrainId` 로 한 번에 selection 적용. appendChild 전 `o.selected = true` 패턴 제거. 이전에 `'new'` 로 굳어있던 값도 확실히 덮어쓴다.
- **수정 2 — 'new' 클릭 즉시 복원.** `brainSel.onchange``'new'` 를 감지하면 `addBrain` 메시지를 보냄과 *동시에* dropdown 을 직전 유효 선택(`brainSel.dataset.lastSelected`)으로 즉시 되돌림. 사용자가 폴더 선택을 취소해도 dropdown 이 `'new'` 로 굳지 않는다.
- **수정 3 — Edit/Delete 폴백.** 두 버튼이 만약 dropdown 이 `'new'` 인 순간에도 작동하도록 직전 유효 선택 또는 첫 실제 옵션으로 폴백. 더 이상 silent early-return 없음.
- **신규 패키징:** `astra-2.2.67.vsix`.
---
## v2.2.66 (2026-05-23)
### 🧠 두뇌(지식 폴더) 드롭다운 회귀 수정
- **문제:** 사이드바 컨텍스트 바의 두뇌 선택자가 갑자기 `+ Add New Brain...` 하나만 보이는 상태. readyBar 에는 `Brain 5407` 처럼 brain 자체는 정상 인식되는데 dropdown 만 빔.
- **원인 추정:** webview 의 `ready` 핸드셰이크 체인 (`_sendBrainStatus → _sendBrainProfiles → _sendSessionList → _sendModels → _sendChronicleProjects → _restoreActiveSessionIntoView → _sendReadyStatus`) 도중 한 단계가 throw 하면 그 뒤가 통째로 안 도는 구조. 또는 빈 profiles 배열 메시지가 한 번이라도 도착하면 기존 dropdown 옵션을 그대로 비워버리는 핸들러.
- **수정 1 — sidebar.js 방어:** `brainProfiles` 메시지의 `profiles` 가 빈 배열/undefined 이면 기존 dropdown 옵션을 보존하고 warn 로그만 남긴다. 잘못된 상태로 옵션을 0개로 만들어 `+ Add New Brain...` 만 남기는 회귀 차단. `case` 블록을 `{...}` scope 로 감싸 향후 const 명 충돌도 예방.
- **수정 2 — 초기 setup 이중 보장:** sidebarProvider.ts view 생성 시점에 `_restoreActiveSessionIntoView` + `_sendReadyStatus` 외에 `_sendBrainProfiles` / `_sendAgentsList` / `_sendModels` 도 직접 한 번 푸시. 'ready' 체인이 깨져도 dropdown 은 살아 있음.
- **수정 3 — 진단 로그:** `_sendBrainProfiles` 가 호출될 때마다 `profiles=N activeBrainId=X` 를 logInfo. 재발 시 Output → Astra 채널만 보면 원인 즉시 판별.
- **신규 패키징:** `astra-2.2.66.vsix`.
---
## v2.2.65 (2026-05-23)
### 🧼 마크다운 마커 2차 sanitize — enforcer 재주입까지 차단
- **문제:** v2.2.64 에서 `stripMarkdownFormatting``cleanedVisible` (모델 raw 출력 직후) 에만 적용했는데, 그 이후 단계인 `enforceLocalPathReviewAnswer` 가 sanitize 된 답변 앞에 `## 경로 확인 결과` 헤더를 하드코딩으로 다시 prepend → 화면에는 마커가 그대로 노출. `## 간단 요약`, `## 강점`, `## 근거`, `## 다음 액션` 등 ~20곳에서 같은 패턴.
- **수정:** `agent.ts` `finalAssistantContent` (webview / chatHistory 에 들어가는 진짜 최종 문자열) 단계에 sanitizer 2차 패스를 추가. 1차(모델 출력) + 2차(enforcer 출력) 이중 방어로 어떤 코드 경로에서 `##`/`**` 가 prepend 되어도 디스플레이 직전에 모두 벗겨진다.
- **신규 패키징:** `astra-2.2.65.vsix`.
---
## v2.2.64 (2026-05-23)
### 🪶 Plain-text 출력 + 긴 답변 강제 요약
- **문제:** 작은 로컬 모델이 학습된 습관으로 `## 다음 한 수`, `**` 강조 마커를 그대로 노출. 어떤 답변엔 라벨만 있고 본론으로 곧장 들어가서 사용자가 현황을 빠르게 파악하기 어려움.
- **수정 1 — 후처리 sanitizer.** `responseRecovery.ts``stripMarkdownFormatting(text)` 추가. 코드 블록/인라인 코드는 보존하고 줄 시작 `#{1,6}\s+` 헤더 마커, `**bold**` / `__bold__`, 단일 `*강조*`, blockquote `> `, asterisk 불릿 `* ` 만 제거. 라벨 텍스트(`핵심 요약`, `다음 한 수`)는 그대로 유지.
- **수정 2 — Synthesizer 강제 규칙.** Synthesizer 페르소나에 `[FORMAT — PLAIN TEXT ONLY, NO MARKDOWN]` 블록과 `[STRUCTURE]` 블록 추가. **답변이 ~4문장 / ~400자 를 넘으면 반드시 `핵심 요약` 블록(2~4 bullets)을 답변 맨 앞에 넣는다.** 짧은 답변은 그대로.
- **수정 3 — BASE_SYSTEM_PROMPT 정비.** 기존 `[OUTPUT FORMAT]``## 핵심 요약`, `## 상세 설명` 같은 마크다운 헤더 예시 → bare label (예: `핵심 요약`) 로 교체. `[STRICT GLOBAL RULES]``[NO MARKDOWN MARKERS]` 명문화. 단일 에이전트 경로도 plain text 출력.
- **수정 4 — review-evaluation 가이드 정비.** `1. ## 한 줄 판단` 같은 markdown-prefix 라벨 → `1) 한 줄 판단` 으로 교체. 마크다운 마커가 prompt 단계에서 모델에 학습되지 않도록.
- **수정 5 — Drafter 페르소나 정비.** 섹션 라벨도 plain text. Synthesizer가 받는 입력이 깨끗해야 최종 출력도 깨끗.
- **양쪽 경로 적용.** 단일 에이전트(`agent.ts` line ~1189) + multi-agent (`finalReport` 직전) 두 곳 모두에서 `outputFormat === 'plain'` 이면 sanitizer 통과. `chatHistory` 에도 정제본만 저장 → 다음 턴 컨텍스트에서 마커가 재학습되지 않음.
- **신규 설정:** `g1nation.outputFormat` (`plain` 기본 / `markdown` opt-out).
### 🧩 5단계 파이프라인 (Planner → Researcher → Reflector → Drafter → Synthesizer) + 깔끔한 스트림
- **문제:** 작은 로컬 모델(예: gemma 4 e2b/e4b)이 컨텍스트 한계 때문에 한 번에 답을 끝내려다 EOS/잘림 발생. 또 multi-agent 모드일 때 채팅 본문에 `> **[Planner]** ...` 같은 단계 메시지가 본문에 섞여 사용자에게 답답함. 일부 응답에서 `<|channel|>thought ...` 같은 control token이 짧게 노출.
- **수정 1 — Synthesizer 단계 추가 (5번째).** Drafter(=기존 Writer)가 1차 초안만 빠르게 생성하고, **Synthesizer**가 작은 draft만 받아 도입 한 줄·섹션 흐름·결론을 정리. 입력이 가벼워 작은 모델로도 한 번에 처리 가능. 신규 클래스 `SynthesizerAgent` (`src/agents/factory.ts`), `AgentEngine` 생성자 4번째 파라미터, `PipelineStage``synthesizer` 추가.
- **수정 2 — 자동 발동 확장.** 기존 트리거는 prompt > 180자 + 키워드일 때만 → 작은 모델일 때도 single-agent 로 가다 폭사. 신규 `g1nation.workflow.multiAgentMode` (`auto`/`always`/`off`) 에서 **`auto` 기본값:** (a) 모델 ≤4B (b) prompt 가 컨텍스트의 30% 이상 (c) "코드 리뷰/심층 분석/보고서" 등 키워드 (d) 사용자가 `multiAgentEnabled` 명시적 ON — 중 하나만 만족해도 5단계 발동. 인사·12자 미만 prompt 는 제외.
- **수정 3 — 단계 메시지를 채팅 본문에서 분리.** 진행 상태(`> **[Researcher]** ...`)를 채팅 버블에 흘리던 코드 제거. 대신 신규 webview message `workflowStage` 가 사이드바 상단의 `statusLabel + thinkingBar` 한 줄에만 표시 → "생각 단계가 본문에 계속 보임" 답답함 제거. 라벨도 `① 계획 → ② 자료 수집 → ③ 자기 검증 → ④ 초안 작성 → ⑤ 최종 정리` 한국어 + 번호로 통일.
- **수정 4 — 라이브 토큰 스트리밍 기본 OFF.** 신규 `g1nation.liveStreamTokens` (기본 `false`): 토큰을 내부에서만 누적하고 `extractVisibleFinal` sanitize 끝난 최종 답변만 한 번에 표시 → Harmony `<|channel|>thought`/`<think>` 마커가 잠시라도 화면에 노출되는 누설 원천 차단. `true` 로 두면 legacy 라이브 스트리밍 복원.
- **신규 설정 4개:** `g1nation.workflow.synthesizerEnabled` (기본 true), `g1nation.workflow.multiAgentMode` (auto/always/off, 기본 auto), `g1nation.workflow.autoCtxFractionThreshold` (기본 0.30), `g1nation.liveStreamTokens` (기본 false).
- **소프트 페일 보장:** Synthesizer가 빈 출력/예외를 내도 미션을 막지 않고 Drafter 초안을 그대로 최종 답변으로 사용. Reflector와 동일한 패턴.
- **신규 패키징:** `astra-2.2.64.vsix`.
---
## v2.2.63 (2026-05-22)
### 🎚️ 한국어 오타 최소화 — 채팅 Temperature 설정 + anti-glitch 샘플링
- **문제:** 채팅 분석 답변에 한국어 오타(`붕괴``붕점`, `핵심``핵점`, `텍스트``텍록`)가 잦음. 토큰 단위 샘플링 glitch.