feat: v2.2.92 → v2.2.158 — god-file 분해 + Stocks feature + 대화 연속성

R56–R59: agent.ts 2731→1529줄 god-file 분해 (25 modules)
  · attrParsers + LLM 메서드 8개 (callNonStreaming, streamChatOnce 등)
  · executeActions 415줄 → 8 handler 그룹 (file/run/list/brain/calendar/sheets/tasks)
  · handlePrompt 1100줄 → 7 phase 모듈 (system prompt + budget + autoContinue 등)

R50–R55: extension.ts 1145→349줄 (telegram/settings/provider commands 분리)

Stocks feature 신규: /stocks slash command (v2.2.152~158)
  · .astra/stocks.json 저장소 + Yahoo Finance 현재가 갱신
  · 8 키워드 필터 (ROE/성장성/유동성/수익성/영업효율/기술력/안정성/PBR)
  · Naver 시가총액 페이지 JSON API (m.stock.naver.com) 발굴
  · LLM Top 5 매력도 분석 + Telegram 자동 보고서
  · KST 09:00/15:00 watcher 자동 모니터링

대화 연속성 (v2.2.150~157):
  · [PRIOR TURN CONCLUSION] block 으로 직전 결론 anchor
  · thin follow-up 분류 → boilerplate 헤더 suppression
  · slash 명령 결과 chatHistory mirror (capture wrapper)
  · echo/parrot 금지 system prompt rule

기타: /stocks 슬래시 자동완성 dropdown UI, Naver JSON API 전환 (cheerio 제거)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
g1nation
2026-05-25 09:59:32 +09:00
parent 4153f640c2
commit 0a97324f1b
149 changed files with 14628 additions and 6927 deletions
@@ -0,0 +1,96 @@
import { getConfig } from '../../config';
import { estimateTokens, estimateModelParamsB } from '../contextManager';
import { isCasualConversationPrompt } from './promptDetection';
import { isAstraModeArchitectureQuestion } from './astraModeArchitecture';
import { shouldPreflightLocalProjectPath } from './localProjectIntent';
/**
* 단일-LLM vs Multi-Agent (5단계 파이프라인) 라우팅 결정. 사용자가 명시 토글을
* 켰거나 (configEnabled), 작은 모델인데 prompt 가 크거나, 복합 작업 키워드가
* 매치되면 multi-agent 활성화. 잡담/짧은 prompt 는 무조건 단일 LLM.
*
* Priority (위→아래로 evaluated, 먼저 매치되면 그 결정 사용):
* 1) Astra-mode meta question / local-path preflight → 단일 (false)
* 2) mode='off' 인 사용자 — legacy 키워드+길이 휴리스틱만 사용
* 3) casual prompt / 너무 짧음 (<12자) → 단일
* 4) mode='always' → 무조건 multi (위 가드 통과 시)
* 5) mode='auto' — 작은 모델 / context fraction / 키워드 / 길이 중 하나라도
* 매치되면 multi. 단 prompt tokens 가 `chunkedSwitchTokens` 미만이면 *모든*
* 트리거 무시하고 단일 (큰 context 모델에서 키워드만으로 chunked 강제 발동
* 되는 회귀 차단 — 사용자 명시 요청).
*
* Stateless — agent.ts 의 private 메서드를 그대로 추출. 의존: config / token
* 추정기 / 다른 detection 함수 (모두 이미 stateless).
*/
export function shouldUseMultiAgentWorkflow(prompt: string, configEnabled: boolean): boolean {
if (!prompt || isAstraModeArchitectureQuestion(prompt)) {
return false;
}
if (shouldPreflightLocalProjectPath(prompt)) {
return false;
}
const cfg = getConfig();
const mode = cfg.workflowMultiAgentMode || 'auto';
// 'off' → 기존 키워드/길이 휴리스틱만 사용 (legacy multiAgentEnabled 토글 존중).
if (mode === 'off') {
const legacyComplex = prompt.length > 180 || /(보고서|심층|종합\s*분석|리서치|조사|전략\s*수립|기획안|제안서|roadmap|research|report|deep\s*analysis|strategy|proposal)/i.test(prompt);
if (!legacyComplex) return false;
return configEnabled || /(보고서|심층|종합\s*분석|리서치|조사|전략\s*수립|기획안|제안서|research|report|deep\s*analysis|strategy|proposal)/i.test(prompt);
}
// 인사·잡담은 5단계 파이프라인 낭비. 짧은 casual prompt 는 제외.
if (isCasualConversationPrompt(prompt)) {
return false;
}
if (prompt.trim().length < 12) {
return false;
}
// 'always' → 위 가드만 통과하면 무조건 발동.
if (mode === 'always') return true;
// 'auto' → 다음 중 하나라도 만족하면 발동:
// (1) 사용자가 multiAgentEnabled 를 명시적으로 켰다,
// (2) 작은 모델 (≤4B params) 이라 한 번에 처리하기 위험,
// (3) prompt 토큰이 효과적 context window 의 임계 이상을 차지한다,
// (4) "보고서/리뷰/심층 분석" 같은 명백한 복합 작업 키워드 매치,
// (5) prompt 길이 자체가 큼 (>240 chars).
if (configEnabled) return true;
const paramB = estimateModelParamsB(cfg.defaultModel);
if (paramB !== null && paramB <= 4) return true;
// ── 절대 임계값 게이트 (사용자 명시 요청) ────────────────────────────
// 입력 prompt 가 `chunkedSwitchTokens` 미만이면 *키워드·길이 트리거 모두 무시*
// 하고 단일 LLM 호출. 큰 컨텍스트 모델(131k 등)에서 "요약/리뷰" 같은 키워드만
// 써도 chunked 가 강제 발동해 답변이 느려지던 문제 해결.
//
// 이 게이트는 fraction 안전 체크보다 *먼저* 평가됨 — 사용자가 절대 임계값을
// 명시한 의도(50k 미만은 한 번에 처리)를 fraction 이 뒤집지 못하게. 작은
// 컨텍스트 모델 사용자는 config 에서 이 값을 모델 윈도우의 ~30% 로 낮춰야 함.
try {
const promptTokensForGate = estimateTokens(prompt);
if (promptTokensForGate < cfg.chunkedSwitchTokens) {
return false;
}
} catch { /* fall through — 안전 측 fraction/keyword 체크가 처리 */ }
try {
const effectiveCtx = cfg.smallModelContextCap > 0 && paramB !== null && paramB <= 4
? cfg.smallModelContextCap
: cfg.contextLength;
const promptTokens = estimateTokens(prompt);
const threshold = Math.floor(effectiveCtx * cfg.workflowAutoCtxFractionThreshold);
if (promptTokens >= threshold) return true;
} catch { /* 안전한 폴백: 키워드/길이 체크로 진행 */ }
if (/(보고서|심층|종합\s*분석|리서치|조사|전략\s*수립|기획안|제안서|코드\s*리뷰|리뷰|아키텍처|architecture|research|report|deep\s*analysis|strategy|proposal|review)/i.test(prompt)) {
return true;
}
if (prompt.length > 240) return true;
return false;
}