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:
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* MockLLMClient — IAIService 의 Mock 구현체.
|
||||
*
|
||||
* 의도: 회사 모드 dispatcher / ChunkedWriter / ceoPlanner 등 LLM 을 호출하는 코드
|
||||
* 경로를 *CI 환경에서도 테스트* 가능하게. 실제 Ollama / LM Studio 없이도 응답을
|
||||
* 미리 정의하거나 동적으로 생성 가능.
|
||||
*
|
||||
* 사용 예:
|
||||
* const ai = new MockLLMClient();
|
||||
* ai.setNextResponse('plan generated');
|
||||
* const result = await ai.chat({ user: 'do this' });
|
||||
*
|
||||
* // 또는 동적 응답:
|
||||
* ai.setResponder((req) => req.user.includes('analyze') ? 'analysis...' : 'ok');
|
||||
*
|
||||
* // 호출 이력 검증:
|
||||
* expect(ai.calls).toHaveLength(2);
|
||||
* expect(ai.calls[0].user).toBe('do this');
|
||||
*/
|
||||
import type {
|
||||
IAIService,
|
||||
AIChatRequest,
|
||||
AIChatResult,
|
||||
} from '../../src/core/services';
|
||||
|
||||
export interface RecordedCall {
|
||||
user: string;
|
||||
system?: string;
|
||||
model?: string;
|
||||
timeoutMs?: number;
|
||||
signalAborted?: boolean;
|
||||
}
|
||||
|
||||
type Responder = (req: AIChatRequest) => string | { content: string; empty?: boolean };
|
||||
|
||||
export class MockLLMClient implements IAIService {
|
||||
/** 모든 chat / call 호출의 입력 인자가 시간 순서로 누적. */
|
||||
public readonly calls: RecordedCall[] = [];
|
||||
|
||||
/** FIFO 큐 — setNextResponse 로 push, chat 호출마다 shift. */
|
||||
private readonly queued: string[] = [];
|
||||
|
||||
/** 큐가 비었을 때 사용할 fallback. setResponder 로 정의 가능. */
|
||||
private responder: Responder | null = null;
|
||||
|
||||
/** queued / responder 모두 없으면 이 값을 그대로. */
|
||||
private defaultResponse = 'mock response — set via setNextResponse / setResponder';
|
||||
|
||||
/**
|
||||
* 다음 호출(들) 에 사용할 응답을 FIFO 큐에 push.
|
||||
* 여러 번 push 하면 순차적으로 소비.
|
||||
*/
|
||||
setNextResponse(text: string): void {
|
||||
this.queued.push(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 호출에 대해 동적으로 응답 생성. setNextResponse 큐가 우선.
|
||||
*/
|
||||
setResponder(fn: Responder): void {
|
||||
this.responder = fn;
|
||||
}
|
||||
|
||||
/** 모든 큐 / responder / 이력 초기화. test setup 사이에 reset 용. */
|
||||
reset(): void {
|
||||
this.calls.length = 0;
|
||||
this.queued.length = 0;
|
||||
this.responder = null;
|
||||
}
|
||||
|
||||
async call(prompt: string): Promise<string> {
|
||||
const result = await this.chat({ user: prompt });
|
||||
return result.content;
|
||||
}
|
||||
|
||||
async chat(req: AIChatRequest): Promise<AIChatResult> {
|
||||
this.calls.push({
|
||||
user: req.user,
|
||||
system: req.system,
|
||||
model: req.model,
|
||||
timeoutMs: req.timeoutMs,
|
||||
signalAborted: req.signal?.aborted,
|
||||
});
|
||||
// signal 이 이미 aborted 면 AbortError 던짐 — 실제 fetch 동작 모방.
|
||||
if (req.signal?.aborted) {
|
||||
const err = new Error('AbortError');
|
||||
err.name = 'AbortError';
|
||||
throw err;
|
||||
}
|
||||
let content: string;
|
||||
let empty = false;
|
||||
if (this.queued.length > 0) {
|
||||
content = this.queued.shift()!;
|
||||
} else if (this.responder) {
|
||||
const out = this.responder(req);
|
||||
if (typeof out === 'string') {
|
||||
content = out;
|
||||
} else {
|
||||
content = out.content;
|
||||
empty = !!out.empty;
|
||||
}
|
||||
} else {
|
||||
content = this.defaultResponse;
|
||||
}
|
||||
return {
|
||||
content,
|
||||
engine: 'lmstudio',
|
||||
model: req.model || 'mock-model',
|
||||
empty: empty || !content,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user