v2.2.299~307: 아키텍처 수렴 + 채팅 정리 + 벤치마킹 강화 + Claude 구독 엔진

- v2.2.299 아키텍처 수렴: coreChat 통일(엔진 휴리스틱 3벌 제거), 기업 모드
  검색 오케스트레이터 승격, lib/execUtil(실행 래퍼 6곳·Python 탐지 3벌 단일화),
  lib/kstSchedule(워처 4개 nowInKst 통합), estimateTokens 통합, 설정 접근 규칙 명문화
- v2.2.300 채팅 화면 정리: LiveReasoningFilter(스트리밍 중 <think>/Harmony 추론
  토큰 단위 차단), 확신도·검토요청 footer 기본 숨김(계산·Reflection 은 유지)
- v2.2.301 문맥·의도 이해: [답변 전 이해 원칙] 상시 주입, 워크플로우 의도 브리핑,
  Report QA 루프(규칙 레지스트리+실측치+회귀 게이트, 블로그_v3 개념 이식)
- v2.2.302 /benchmark 비즈니스 렌즈(가격·수익·운영)+빌드 프롬프트 모드+QA 연계
- v2.2.303 handoff 모드(측정치 무손실 인수인계 문서)+/claude(Claude Code 터미널 위임)
- v2.2.304 이식성: 지식 경로 두뇌-상대 규약(pickWikiDir 상대 해석), 이사 체크리스트
- v2.2.305 Claude 구독 엔진: claude: 프로바이더(CLI 위임, 모델 드롭다운 자동 노출,
  coreChat 지원 — 워크플로우·QA도 구독 모델 가능)
- v2.2.306 Tone Guard: AI 상투어 금지 레지스트리(상담사 화법 실사례 8종+대조 예시)
- v2.2.307 /benchmark 레이아웃 골격(sectionRoles 결정론 분석, 롤링 배너 즉답),
  파트별 실패 격리, 합성 타임아웃 120→300초

검증: tsc 무오류 + jest 888 통과 + esbuild 정상

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 21:02:15 +09:00
co-authored by Claude Fable 5
parent 98d533f045
commit 47b3b9f93a
66 changed files with 2421 additions and 459 deletions
+52 -6
View File
@@ -41,6 +41,7 @@ import {
} from './lib/contextBuilders/promptDetection';
import { stripAstraFormattingForAgentMode, computeModeSignature } from './lib/contextBuilders/systemPromptShaping';
import { sanitizeAssistantContent, isRestartedAnswer, parseRationale } from './lib/contextBuilders/outputSanitization';
import { LiveReasoningFilter } from './lib/contextBuilders/liveReasoningFilter';
import { buildEngineMessageVariants } from './lib/contextBuilders/engineMessages';
import { buildMemoryContext as buildMemoryContextFn } from './lib/contextBuilders/memoryContext';
import { extractEvidenceFilesFromProjectKnowledge, extractPriorityPreviewFiles } from './lib/contextBuilders/projectEvidence';
@@ -102,6 +103,7 @@ import {
} from './features/secondBrainTrace';
import { MemoryManager } from './memory';
import { RetrievalOrchestrator } from './retrieval';
import { embedQuery } from './retrieval/embeddings';
import { isQaRegressionFeedback, findUnaddressedChecklistItems } from './retrieval/lessonHelpers';
import { buildKnowledgeMixPolicy, ResolvedKnowledgeMix } from './retrieval/knowledgeMix';
import {
@@ -358,6 +360,44 @@ export class AgentExecutor {
this.restoreLastSession();
}
/**
* [코어 수렴] 기업 모드(dispatcher)용 두뇌 컨텍스트 블록 — 경량 scopedBrainRetriever
* 대신 메인 오케스트레이터의 전체 검색 경로(임베딩 하이브리드·청크)를 태운다.
* 텔레그램 경량 경로는 의존성 최소화를 위해 의도적으로 그대로 둔다.
* 반환 포맷은 기존 buildContextBlock 과 동일 — specialist 프롬프트 형식 불변.
*/
public async retrieveBrainBlockForCompany(query: string, scopeFolders: string[], limit: number): Promise<string> {
const config = getConfig();
const brain = getActiveBrainProfile();
if (!brain?.localBrainPath) return '';
let queryEmbedding: number[] | undefined;
if (config.embeddingModel) {
try {
queryEmbedding = await Promise.race([
embedQuery(query, { baseUrl: config.ollamaUrl, model: config.embeddingModel }),
new Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), 4000)),
]);
} catch { queryEmbedding = undefined; }
}
const chunks = this.retrievalOrchestrator.retrieveBrainChunksScoped(query, brain, {
limit,
scopeFolders,
queryEmbedding,
embeddingModel: config.embeddingModel || undefined,
embeddingBlendAlpha: config.embeddingBlendAlpha,
chunkLevelRetrieval: config.chunkLevelRetrieval === true,
chunkTargetChars: config.chunkTargetChars,
});
if (chunks.length === 0) return '';
const header = scopeFolders.length > 0
? '[제2뇌 컨텍스트 — 매핑된 지식 폴더에서 검색]'
: '[제2뇌 컨텍스트 — 전체 브레인 검색]';
const body = chunks
.map((c, i) => `(#${i + 1}) ${c.title}\n${c.content}`)
.join('\n\n---\n\n');
return `${header}\n\n${body}`;
}
private async restoreLastSession() {
return restoreLastSessionFn({
sessionManager: this.sessionManager,
@@ -946,10 +986,16 @@ export class AgentExecutor {
// policy enforcement) emits a final `streamReplace` so the bubble
// ends up matching the cleaned answer regardless of what slipped
// through live.
// [Clean Stream] g1nation.liveStreamTokens=false (기본) 이면 토큰을 내부에만
// 누적하고 sanitize 끝난 최종 답변만 한 번에 표시 → Harmony/think 마커가 잠깐
// 화면에 노출되는 누설을 원천 차단한다. true 로 두면 legacy 라이브 스트리밍.
// [Clean Stream] liveStreamTokens=true(기본) 면 토큰을 실시간 표시하되,
// LiveReasoningFilter 가 <think>/<|channel|>thought 류 추론 구간을 토큰 단위로
// 걸러 사용자가 볼 필요 없는 생각 텍스트는 화면에 흐르지 않는다. false 면
// 내부 누적 후 sanitize 된 최종 답변만 한 번에 표시.
const postLiveDeltas = loopDepth === 0 && getConfig().liveStreamTokens === true;
const liveFilter = postLiveDeltas ? new LiveReasoningFilter() : null;
const postLiveToken = (token: string) => {
const visible = liveFilter ? liveFilter.push(token) : token;
if (visible) this.webview?.postMessage({ type: 'streamChunk', value: visible });
};
let lmStudioStats: ChatStreamStats | undefined;
if (useLmStudioSdk) {
@@ -970,7 +1016,7 @@ export class AgentExecutor {
if (this.isStaleRun(runId)) return;
if (token) {
aiResponseText += token;
if (postLiveDeltas) this.webview.postMessage({ type: 'streamChunk', value: token });
if (postLiveDeltas) postLiveToken(token);
}
if (stopReason) finishStopReason = stopReason;
if (stats) lmStudioStats = stats;
@@ -1045,7 +1091,7 @@ export class AgentExecutor {
const token = engine === 'lmstudio' ? json.choices?.[0]?.delta?.content || '' : json.message?.content || json.response || '';
if (token) {
aiResponseText += token;
if (postLiveDeltas) this.webview.postMessage({ type: 'streamChunk', value: token });
if (postLiveDeltas) postLiveToken(token);
}
const fr = engine === 'lmstudio'
? json.choices?.[0]?.finish_reason
@@ -1077,7 +1123,7 @@ export class AgentExecutor {
const token = engine === 'lmstudio' ? json.choices?.[0]?.delta?.content || '' : json.message?.content || json.response || '';
if (token) {
aiResponseText += token;
if (postLiveDeltas) this.webview.postMessage({ type: 'streamChunk', value: token });
if (postLiveDeltas) postLiveToken(token);
}
const fr = engine === 'lmstudio'
? json.choices?.[0]?.finish_reason