- google-usage.ts: Gemini 무료티어 RPD(하루 500회 추정치) 대비 요청 카운트를
날짜별로 추적(.smallclaw/google-usage.json, 자정 자동 리셋)
- openai-compat-adapter.ts: google provider일 때 post() 호출마다 카운트
- factory.ts: getProvider()/getModelForRole() 둘 다 getEffectiveProviderConfig()
경유하도록 통일 — 카운트가 480(여유분 20 적용) 넘으면 자동으로
ollama+deepseek-v4-flash:cloud로 전환(429 맞고 반응하는 게 아니라 사전 차단).
provider/model 선택이 별도 함수라 따로 고치면 불일치 날 수 있어서 공유
헬퍼로 묶음
- 메인 채팅 사이드바 모델 pill에 "N/500" 뱃지 추가(Google일 때만 표시,
70%↑ 노랑/90%↑ 빨강), 기존 10초 폴링(checkStatus)에 얹어서 실시간 반영
- 실사용 확인: 배포 직후 실제 채팅으로 카운트 0→1 증가 확인, 480 시뮬레이션
으로 폴백 전환/복귀 양쪽 다 확인
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- resolveNumCtx()가 Ollama 전용이라 primary가 Google로 바뀐 뒤 세션
컨텍스트 게이지가 419%까지 잘못 표시되던 버그 수정 — google provider면
Gemini API에서 inputTokenLimit을 실시간 조회(하드코딩 아님, API 실패시만
1048576 폴백)
- 최종 폴백(8192)을 캐시하던 버그 제거 — 재시작 직후 첫 조회가 일시적으로
실패하면 프로세스 수명 내내 8192에 고정되던 문제, 이제 재시도 가능
- /api/model-context: model 파라미터 없을 때(메인 채팅 사이드바 게이지)
무조건 200000(클로드 가정) 반환하던 걸 resolveNumCtx() 재사용으로 교체
— 이제 실제 활성 provider와 항상 동기화됨
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- LLMProvider.ts/factory.ts: 'google' ProviderID 추가, 기존 openai_compat
어댑터를 Gemini의 OpenAI 호환 엔드포인트로 재사용(새 어댑터 클래스 없음).
api_key는 resolveEnvKey가 아닌 getConfig().resolveSecret()로 해석 —
vault: 참조를 실제로 못 푸는 기존 openai/anthropic 케이스의 버그를
google 케이스에서는 피해감
- config.ts: llm.providers.google 기본값 추가(기본모델 gemini-3.6-flash),
SECRET_FIELD_MAP에 api_key 자동 vault 이관 등록
- 설정 화면(index.html/settings-provider.js): LLM 제공자 드롭다운에
Google 옵션 + API키/모델 선택 UI, anthropic 패턴 그대로 재사용
- openai-compat-adapter.ts: 기본 max_tokens 512→4000 — 사고형 모델
(gemini-3.6-flash)이 내부 사고 토큰에 예산을 다 써서 빈 응답 반환하는
버그 실측 후 수정(llama_cpp/lm_studio/openai/google 전부 공유하는 값)
- 5문항 벤치마크로 gemini-3.6-flash/3.5-flash-lite/3.1-flash-lite 비교,
secondary 후보로 3.5-flash-lite 확정(정직함 동률 최상위+5배 빠름+저가)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- handle-chat.ts: excel/coder+shell/email/kakao/sqlite_query/image_edit/
image_read/run_command/news_search/pdf_*/schedule_job를 전용 앱 세션·
키워드·첨부 신호로 스코핑 (기존 weather/legal/pptx/dental/mind 패턴과
동일). 일반 대화 기준 도구 스키마 오버헤드 10,204→3,951 토큰(-61%)
- desktop_*(10개, Windows 전용) 완전 제외 — 이 리눅스 서버에서 전량
실패 확인(tool_audit.log), process.platform 체크라 윈도우 이전시 자동 복구
- 채팅 기본 num_predict 4096→6000 (사고형 모델 빈 응답 여유분)
- ollama-adapter.ts: 턴마다 prompt/completion 토큰을 token-usage.jsonl에
기록 (Ollama Cloud vs 종량제 API 비용 비교용)
- web-ui/js/ 25개 파일을 app/·code-editor/·voice/·music/·claude-app/
서브디렉토리로 재배치, 전체 참조 경로 갱신
- package.json build 스크립트에 dist 클린 단계 추가
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- coder_write_file이 xlsx/pptx/docx/pdf/zip 같은 바이너리 확장자로 파일을
만들려 하면 차단 (텍스트 escape로 바이너리를 흉내내다 파일이 깨지던 문제)
- 레거시 HTML 리다이렉트(/xlsx-viewer.html 등)가 쿼리스트링을 버리던 버그 수정
- xlsx-viewer: "새 시트"가 존재하지 않는 __new__.xlsx를 fetch하다 404나던
것을 빈 워크북 생성 후 저장 시 파일명 입력받는 흐름으로 교체
- xlsx-viewer: LuckyExcel 변환이 내놓는 손상된 숫자 서식("???.???")을
Luckysheet SSF 파서가 못 읽고 죽던 문제에 sanitizer 적용
- 회계사 앱 업로드가 존재하지 않는 /api/upload를 호출하던 것을
/api/upload/file로 수정
- web-ui/js/helpers.js(2240줄)를 helpers-core/markdown/voice/media/files.js
5개로 분리 (기능 변경 없음, 함수 단위 1:1 이관 확인됨)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
app.js 4728-6961(Helpers 섹션 전체)을 web-ui/js/helpers.js로 추출.
escHtml/_normalizeFileUrl/_renderLinkButton, renderMarkdown+embed 변환기,
TTS/voice(mic 포함), music/MIDI, smiles/tradingview, 파일 미리보기/업로드,
동적 로더(SheetJS/Luckysheet/PDF.js) 등 75개 함수 + midiRouter 객체.
모든 함수를 전역 function 선언으로 유지(모듈 래핑 안 함)해 inline onclick
문자열(downloadFile, openImageZoom, togglePdfEmbed, playTTSMsg, ...)이
그대로 동작. state vars(_xxxLoaded, stagedAttachments, _tts*, _appSendFn
등)도 함께 이동.
escHtml 중복(원래 3625, dead code — 5412 버전이 이김) 제거로 단일 정의.
escapeHtml(이메일 렌더링 전용, 다른 매핑)은 app.js에 유지.
index.html: voice-call → helpers → chat-sse → app 순서로 로드.
node --check 통과, 재시작 후 /js/helpers.js 200 확인.
다음(B2)에서 helpers.js를 5개 파일로 세분화 예정.
Co-Authored-By: Claude <noreply@anthropic.com>
app.js 1411-2092의 async function sendChat을 web-ui/js/chat-sse.js로
추출. 범위 내 top-level 선언은 sendChat 하나뿝이라 외부 노출 대상은
sendChat 단일. 끝에 window.sendChat 할당 추가. app.js에서 제거 후
index.html에서 app.js 직전에 로드.
공유 상태(currentAbortController/isThinking)와 외부 헬퍼
(authHeaders/renderChatMessages/addProcessEntry 등)는 전역 그대로
참조. node --check 통과, 재시작 후 /js/chat-sse.js 200 확인.
Co-Authored-By: Claude <noreply@anthropic.com>
- handle-chat.ts에서 handleCodeChat(180줄) + _overlapLen 헬퍼를 별도 파일로 이동
(handle-chat.ts 3,222→3,033줄, 189줄 감소)
- code-chat.ts는 createCodeChat 팩토리로 정의, handle-chat.ts에서 필요한 의존성만 주입
- server.ts는 기존 createHandleChat 팩토리 사용 그대로 유지, handleCodeChat은
handle-chat.ts 내부에서 code-chat.ts 팩토리로 생성
- 빌드 성공 + 재시작 후 일반 채팅/code_ai 채팅(python_eval) 모두 정상 동작 확인
- handle-chat.ts에서 preflight advisor 블록(1092-1284줄, 약 195줄)과
replaceCurrentUserPromptWithAdvisorObjective 헬퍼를 별도 파일로 이동
(handle-chat.ts 3,400→3,222줄, 178줄 감소)
- preflightRoute/preflightReasonForTurn은 getter/setter 뷰 객체로 연결하여
메인 루프의 sanitizeFinalReply 및 route 조건에서 계속 사용 가능
- cron 스케줄러(server.ts)의 callSecondaryPreflight 사용은 그대로 유지
- 빌드 성공 + 재시작 후 일반 채팅/code_ai 채팅 모두 정상 동작 확인
- handle-chat.ts에서 maybeRunBrowserAdvisorPass(430줄) + maybeRunDesktopAdvisorPass(140줄)
+ 헬퍼 4개를 별도 파일로 이동 (handle-chat.ts 3,900→3,400줄, 500줄 감소)
- 메인 루프의 가변 상태는 그대로 두고, getter/setter 기반 advisorCtx 뷰 객체를 통해
새 파일과 공유 → 메인 루프 변수명/조건식은 모두 유지
- 현재 배포에선 dormantBrowserTools / Linux desktop 제한으로 여전히 도달 불가능한
코드이나, 향후 브라우저/데스크톱 자동화 복구 시 이 파일만 다시 연결하면 됨
- 빌드 성공 + 재시작 후 일반 채팅/code_ai 채팅 모두 정상 동작 확인
정의만 있고 호출부 없는 함수들. readDailyMemoryContext/readMemoryCategories는
Chroma 벡터 메모리(2026-07-24 도입, buildPersonalityContext의 [RECALLED_FACTS])가
관련성 기반 회상으로 대체하면서 이미 실질적으로 무용해진 상태였음.
3자 순환 의존성(executeTool→handleTaskControlAction→handleChat→executeTool) 발견:
- inferTaskChannelFromSession/findBlockedTaskForSession/parseTaskIdFromText는 실제로는
handleChat에 의존하지 않는 순수 쿼리 헬퍼임을 확인하고 팩토리 밖으로 빼서 일반 export로
전환 → handle-chat.ts가 deps 주입 없이 직접 import하도록 변경 (HandleChatDeps 2개 필드 제거)
- handleTaskControlAction 자체만 진짜 순환: launchBackgroundTaskRunner가 handleChat을 필요로
하는데 handleChat이 executeTool을, executeTool이 handleTaskControlAction을 필요로 함
→ lazy binding으로 해결: executeTool 인스턴스화 이전에 hoisted wrapper 함수
(let _handleTaskControlActionImpl + function handleTaskControlAction)를 선언해 dep로 넘기고,
handleChat 생성 후 실제 구현을 대입
실제 백그라운드 태스크 생성→조회→삭제 왕복으로 순환 경로 전체 검증 완료.
detectToolCategories/TOOL_BLOCKS/readMemorySnippets 등 관련 헬퍼 클러스터를
createPersonalityContext(resolvePromptPath) 팩토리로 통째 이동. 외부 의존은
resolvePromptPath 하나뿐이라 순환 의존성 없이 분리 가능했음.
오늘 분석에서 찾은 3대 핵심 엔진 함수(buildTools/executeTool/handleChat,
합쳐서 원래 6043줄=51%) 중 가장 크고 마지막 조각. handleChat 바로 앞에
정의된 형제 함수 handleCodeChat(코드 에디터 /api/chat 전용 경로)이 정확히
같은 의존성 집합을 공유하는 걸 확인하고 함께 이동 — 둘 다 buildTools/
executeTool/normalizeToolArgs/logToolCall/ToolResult를 그대로 씀.
app/config/server로 잡혔던 "외부 심볼 참조"는 buildTools 때와 동일하게
전부 도구 설명/주석 안 우연한 단어 오탐으로 확인. 실제 dep는 34개
(밖에서도 쓰이는 telegramChannel/skillsManager/broadcastWS/codeAiLocalSessions/
buildTools/executeTool 등 + handleChat 전용이지만 재귀적 의존성 파악 부담을
피하기 위해 dep로 남긴 로컬 헬퍼 30개), createHandleChat(deps) 팩토리로 주입.
MAX_TOOL_ROUNDS/ExecutionMode/SubagentProfile+TOOL_PROFILES(죽은 코드)는
server.ts에서 정리.
server.ts: 9167 → 5455줄. handle-chat.ts는 3903줄.
검증: 빌드 1차 시도에서 에러 4종류(HandleChatResult 미export, getModelForRole/
fs/path 미import)만 발생 — 나머지 30여개 dep는 전부 정확히 맞았음. 재시작 후
실제 채팅으로 (1) 일반 텍스트 (2) 도구 호출 (3) handleCodeChat 코드에디터 경로
(4) web_search (5) 백그라운드 태스크(BackgroundTaskRunner 경유 handleChat) —
5개 시나리오 전부 실제 왕복 검증. 백그라운드 태스크가 needs_assistance로 끝난
건 handleChat 문제가 아니라 이 환경의 오케스트레이션 disabled 설정 때문임을
daily memory 로그("1+1은 2입니다" 3회 정상 응답 확인)로 교차검증 후 확인.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
executeTool 전용으로만 쓰이던 헬퍼(SAFE_COMMANDS, buildUrlOpenCommand,
buildBrowserLaunchCommand, hasUriScheme, quoteShellArg, BLOCKED_PATTERNS,
lastFilenameUsed, normalizeScheduleJobAction, summarizeCronJob,
normalizeDeliveryChannel, ToolResult 타입)도 함께 이동 — 전부 executeTool
밖에서 전혀 참조되지 않는 것을 grep으로 확인 후 판단. 죽은 코드였던
isLinux(정의만 있고 미사용)도 이 김에 삭제.
밖에서도 쓰이는 10개(isPathInsideDir/cronScheduler/resolvePromptPath/
webSearch/webFetch/imageSearch/IMAGE_TYPES/codeAiLocalSessions/
handleTaskControlAction/broadcastWS)는 createExecuteTool(deps) 팩토리로 주입,
호출부 시그니처는 그대로 유지.
server.ts: 10584 → 9167줄. 빌드/재시작 후 실제 채팅으로 shell/coder_write_file/
web_search 3개 도구 실행 왕복 검증(생성 파일 확인 후 정리 완료), 기존
라우트 8개 재확인.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3개 핵심 엔진 함수(buildTools/executeTool/handleChat, 합쳐서 6043줄=51%) 중
가장 독립적인 것부터 분리 시작. 실제 참조하는 외부 심볼은
isOrchestrationSkillEnabled 단 하나뿐 — app/config/server는 텍스트 검색에서
도구 설명 문자열("node app.js" 등) 안 우연한 단어와 오탐이었음. 캐시(_toolsCache)
도 함수 스코프 안에서만 쓰여 통째로 이동. createBuildTools(dep) 팩토리 패턴으로
감싸 호출부 시그니처는 그대로 유지.
server.ts: 11785 → 10584줄. 빌드/재시작 후 실제 채팅 메시지로 coder_list_files
도구 호출 왕복까지 검증(HTTP 200 체크만으론 부족 — 실제 도구 실행까지 확인).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
routes-*.ts를 routes/ 서브디렉토리로 옮기면서 import 경로는 스크립트로
재계산했지만, __dirname 기반 런타임 파일 경로(디렉토리 한 단계 더 깊어짐)는
놓쳤음. dental_dict.db·web-ui/schematics·scripts/pptx_preview.py·
edge_tts_synth.py(voice/writer/language 3곳) 전부 ../를 하나씩 추가.
실제 DB 통계(4809건)·검색 API로 재검증 완료.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
export {} 뿐인 빈 껍데기, 아무 곳에서도 참조 안 됨(reactor.ts +
orchestration/multi-agent.ts로 대체된 지 오래). 빌드/재시작 확인 완료.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 7개 라우트 클러스터 추가 추출 (12,785 → 11,785줄)
- detective는 2곳에 흩어진 사례(9761~)와 네트워크도구(11581~)를 하나로 통합,
/pdf-editor 프록시가 쓰는 detectiveFilesDir/resolveDetectiveUploadPath는 export
- users는 CRUD/이메일계정/텔레그램봇 3곳을 통합, 생성→조회→삭제 전체 사이클 실제
테스트로 검증(테스트 계정 정리 완료)
- 핵심 로그인/세션(/api/auth/status·setup·login·logout·change-password)과
handleChat에 직결된 거대 POST /api/chat 루프는 위험도 높아 이번에도 제외
(기존 /api/code/round 제외와 동일한 이유)
- 오늘 세 차례 분리 누적: 14,913 → 11,785줄 (-3,128줄, -21%)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 9개 라우트 클러스터 추가 추출 (14,002 → 12,785줄)
- 오늘 두 차례 분리로 총 14,913 → 12,785줄 (-2,128줄, -14.3%)
- 남은 detective/auth/admin/user/chat 클러스터는 최대 4,700줄 범위에 걸쳐
뒤섞여 있어 이 패턴으로는 분리 어려움 — 별도 전략 필요
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- news_search 스키마에 지역코드 안내 부족 버그 수정 (동유럽 country code 예시 추가)
- "소식"을 뉴스 동의어로 게이트에 추가, "무슨 좋은 소식 있어" 같은 개인 안부 관용구는
PERSONAL_NEWS_IDIOM 예외로 오탐 방지
- 하드코딩 가드 9개 추가 정리: prompt-gates.ts로 통합(isNewsRequest 중복 제거,
task-intent 6개, isHighStakesFile/requestedFullTemplate/
shouldForceSessionScopeForTemporalClaim/userRequestedImageEdit 이동), 죽은
코드 4개 삭제
- 브라우저 자동화 재시도 가드가 dormant된 browser_* 도구를 강제 호출하려던
사각지대 수정 (도구 가용성 체크로 게이팅)
- tool_choice를 provider 체인 전체에 플러밍, 모델별 프로필에
forceToolChoiceOnLiveData 플래그 추가 — 미스트랄이 뉴스/사실질의에서 도구
호출 없이 답변을 지어내던 문제를 라운드 0부터 강제 도구호출로 차단
(프롬프트 경고만으로는 불충분함을 실측 확인)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- USER.md/SOUL.md 문자캡 잘림 보완용 벡터 검색(Chroma, EmbeddingGemma) 신설
- 일별 대화로그 LLM 추출 파이프라인(user_facts/daily_extracts 컬렉션 분리)
- 검증-강제 게이트(isFactualInfoRequest 등)를 prompt-gates.ts로 분리, 개인 인프라
별명(지서버/클로서버) 관련 불필요한 web_search 강제 호출 버그 수정
- Qwen3-Embedding-8B는 실측 결과 이미지생성 GPU와 자원 충돌 확인되어 폐기,
EmbeddingGemma(경량+다국어)로 최종 선택
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v4.2.1 이북 리더 커밋에서 빠뜨렸던 부분 — 이제 이 두 공급자 키를 평문으로
저장하면 다음 config 저장 때 자동으로 vault로 이전된다(기존 nvr.password 등과 동일 패턴).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 메인 NVR과 별개인 독립 IP카메라(예: Mercusys/Tapo) 등록 지원
(nvr.extraCameras, id 1000+) — RTSP/화면 표시는 메인 NVR과 동일 그리드에 통합
- ONVIF(WS-Security) 기반 PTZ 제어 — 라이브 모달의 기존 PTZ 버튼이 독립 카메라에서도
그대로 작동(백엔드에서 채널 id로 NVR/독립 카메라 라우팅)
- 카메라 자체에 상하반전 옵션이 없는 모델을 위한 ffmpeg 소프트웨어 180도 회전,
반전 시 PTZ 팬/틸트 방향도 화면 기준으로 자동 보정
- 채널 이름/위치 라벨 커스텀 표시, 채널별 화질(메인/서브) 강제 고정
- 6분할 라이브로 확장(기존 5분할 하드코딩 제거)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Project Gutenberg/Google Books/알라딘 검색 및 서재(EPUB) 보관
- 페이지 넘김 리더: 책 판형 비율 고정 페이지, 챕터 자동 새 페이지 시작,
단어/문단 경계 유지, 브라우저 창 크기와 무관한 고정 페이지 분량(스케일만 조정)
- Gutenberg 원문의 강제 줄바꿈을 문단 단위로 재조판(reflowHardWrappedProse)
- 서재에서도 바로 읽기 지원(다운로드 시 원문 URL 함께 저장)
- TTS 낭독(현재 페이지 기준)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- weather.ts: "Rome"처럼 국가 미명시 동명 도시 조회시 지오코딩 API의
index 0을 그대로 믿지 않도록 개선. Open-Meteo 결과는 인구수 기준으로
재정렬해 최적 후보를 고르고, OpenWeather 쪽은 country 미명시일 때
Open-Meteo 좌표로 재조회해 이름 모호성 자체를 피함. 국가 파싱 로직
(parseExplicitCountry)을 두 경로가 공유하도록 정리.
- server-v2.ts: PTY 셸 세션 생성시 cwd 기본값을 고정된 homeclaw 경로
대신 사용자별 워크스페이스로 사용, 세션 시작 전 워크스페이스 보장.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- EseeCloud K8208-3WS NVR RTSP(포트80, /ch{N}_{S}.264)를 ffmpeg로
MJPEG(multipart/x-mixed-replace) 변환, 4분할 자동재생+모달 확대
- PTZ 팬/틸트: NVR 웹UI와 동일한 /netsdk/Channel/PTZ + S.PtzControl
포맷, 숫자 Cmd코드로 수정(기존 PTZControl 문자열 명령은 무반응이었음)
- ffmpeg NVDEC를 GPU1에 고정(GPU0은 이미지 스튜디오가 이미 점유)
- mid-stream 드롭(PTZ 이동 등) 자동 respawn으로 화면 끊김 복구
- 화면 전환시 서버에 명시적 stop 요청 + activeStreams를 abort()
콜백으로 전환해 레이스컨디션으로 인한 ffmpeg 프로세스 배가 수정
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
브랜드명 전체 변경: "CherryClaw"→"HomeClaw", localStorage 키 6종
(cherryclaw_theme 등)→homeclaw_*, alt="🍒"→alt="🏠". 15개 web-ui 파일 +
OpenAI OAuth 완료 페이지 전체 반영.
로고를 체리 아이콘에서 집 모양으로 교체 후, 사용자 피드백을 반영해 동화풍
성(castle) 실루엣으로 재작업 — 대리석/벽돌 텍스처의 회색 톤, 검은 윤곽선,
중앙 첨탑 우측에 작은 반짝임 별. FLUX.1로 생성 후 배경 제거해 투명 PNG로
저장. 파일명도 cherry_logo.png → homeclaw_logo.png로 통일(web-ui/, assets/
양쪽).
시스템 프롬프트("You are SmallClaw 🦞")와 IDENTITY.md(papa/cherry/jasmine
+ 템플릿 4곳)의 Name 필드에서 🦞를 빼고, 습관적으로 이모지를 붙이지 말라는
지시를 추가 — 모델이 응답 끝마다 무의식적으로 🦞를 붙이던 문제 해결.
텔레그램 봇 응답/self-update 알림/연동 테스트 메시지 등 하드코딩된 🦞도
전부 제거.
부수적으로 발견한 문서 버그도 수정: 웹훅 가이드와 curl 복사 버튼이
"x-cherryclaw-token" 헤더를 안내하고 있었는데, 서버가 실제로 검사하는
헤더는 X-Gateway-Token이었음 — 그대로 썼으면 401 에러가 났을 문제.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
NewsData.io는 country를 6개 이상 넘기면 하드 에러("Number of country
cannot exceeded 5")를 내는데, "소아시아 뉴스"처럼 자연스럽게 6개 이상의
국가코드로 매핑되는 요청에서 모델이 왕복을 한 번 날리게 되던 문제 —
5개 초과분은 조용히 잘라내도록 수정.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
video_generate에 model 파라미터 추가 — 기존 LTX-Video(빠름, 기본값)에
CogVideoX-2B(THUDM, 느리지만 화질 비교용)를 선택지로 추가. CogVideoX는
12GB VRAM 한계에 거의 딱 맞아(enable_model_cpu_offload에도 peak ~11GB)
해상도/프레임수 기본값 이상으로 올리면 여유가 없음.
스튜디오앱 동영상 탭에서 모델/프레임수/FPS 세 필드가 320px 폭 폼에
한 줄로 욱여넣어져 프레임수부터 우측 패널에 가려 안 보이던 레이아웃
버그 수정 — 모델 드롭다운을 단독 줄로 분리하고 프레임수/FPS는 그 아래
별도 행으로 이동.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
검색예산 하드캡·image_edit 차단·FILE_OP v2 대체실행 등 도구호출을 가로채는
5곳이 messages 배열에 매칭되는 role:'tool' 응답을 안 넣고 continue만 해서,
Ollama가 다음 호출에서 "Not the same number of function calls and responses"로
거부하는 버그 수정.
검색예산은 소프트캡 5회 유지하되 새 토큰(도시명 등)이 포함된 진짜 새 주제
검색이면 하드캡 12회까지 자동 연장 — "유럽 주요 도시 최고온도"처럼 정당한
다건 검색이 캡에 걸려 차단되던 문제 해결.
시스템프롬프트에 TEMPORAL CONSISTENCY 문단 추가 — 시장개장/영업 여부 판단시
검색결과보다 주입된 현재날짜(요일 포함)를 우선 신뢰하도록 지시. mistral이
검색은 하면서도 요일을 반영 못해 주말에 "시장 열려있음" 같은 오답을 내던
잔여 문제 대응.
ollama-adapter.ts에 parseOrphanTrailingToolJson 추가 — 모델이 함수명을
빠뜨리고 원시 JSON(예: write_note 인자)을 답변 끝에 그대로 노출하는 케이스를,
알려진 도구 스키마와 매칭될 때만 실제 tool_call로 복구하거나(유일 매칭)
화면에서만 제거(모호한 매칭)하도록 처리.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
toolsDisabledForRestOfTurn(검색예산 2회 초과 시 그 턴 tools를 []로 비우는 안전장치)가
걸려도, "모델이 함수호출 대신 텍스트로 toolname{...}를 흉내낸 경우 복구"하는 로직이
이 상태를 확인하지 않고 원래 도구 목록으로 계속 텍스트를 재파싱해 tool call로
되살리고 있었음 — 실질적으로 tools:[] 캡을 완전히 무력화시키는 구멍이었음.
실측: "GPT-5.6 가격 비교" 질문 하나에 web_search가 37라운드 넘게 반복되다 결국
"못 찾았다"로 종료(로그로 재현·확인). toolsDisabledForRestOfTurn일 때 이 복구
로직을 스킵하도록 수정.
AUTO-RECOVER의 근거도구 인식 정규식에 news_search가 빠져있어서, 뉴스 검색을
몇 번을 성공시켜도 "아직 도구 안 불렀다"고 오판해 강제 재호출을 반복 주입하던
버그 수정(server-v2.ts) — 실측상 "오늘 유럽 뉴스" 요청 하나에 13라운드 넘게
반복 호출되던 원인이었음, 모델 종류와 무관하게 재현됨.
news_search(NewsData.io)는 country+language 불일치, country+category=world
조합에서 country 필터를 조용히 무시하고 엉뚱한 나라 뉴스를 반환함을 확인 —
country 지정 시 language/category=world를 드롭하도록 수정하고, "유럽" 같은
대륙 코드가 없다는 점 + country는 콤마로 최대 5개까지 받는다는 점을 도구
설명에 명시(news.ts).
SearXNG 쿼리에 language/time_range 파라미터 추가로 오래된 결과 필터링
정교화(web.ts).
우즈베크어 전용으로 하드코딩돼 있던 language-app을 lang 파라미터 기반으로
리팩터링하고 독일어 어휘 56개·레슨 8개를 추가. 진도/커스텀 카테고리도
언어별로 분리 저장(기존 우즈베크어 데이터는 자동 마이그레이션).
덤으로 퀴즈 시작 시 id 불일치로 showQuizQuestion()이 매번 죽던 버그도 수정.
기본 보기를 타일 그리드로 전환하고 사용자가 카테고리를 직접 추가할 수 있게 함.
문법/단어/문화 질문에 답하는 "선생님" 채팅 패널 신설(/api/language/ask-teacher).
선생님 답변의 마크다운(표·볼드·목록·인용)이 그대로 노출되던 버그를 자체 렌더러로 수정.
- [CRITICAL] image_edit/imagegen 도구의 숫자 파라미터(x,y,width,height,degrees,opacity,quality,seed,guidance_scale,num_frames,fps)가 검증 없이 Python 소스에 직접 문자열 삽입되어 원격 코드 실행 가능했음. toFiniteNumber()로 전 지점 강제 숫자 변환 — 실제 페이로드로 라이브 검증 완료
- [HIGH] imageStyleTransformTool과 /api/imagegen/save-result에 경로 탈출 방어(isPathInsideDir) 누락 — 다른 사용자 워크스페이스/임의 파일 접근 가능했음. 둘 다 수정 후 실제 ../ 페이로드로 차단 확인
- [HIGH] 언어 앱 진행상황 저장 API가 URL의 :userId를 그대로 신뢰해 다른 사용자 진행상황을 읽고 덮어쓸 수 있었음(IDOR). 세션 기반으로 수정, 실제 다른 사용자명으로 테스트해 본인 워크스페이스에만 저장됨을 확인
- [MEDIUM] 언어 앱 플래시카드·단어장 렌더링(LLM 생성 콘텐츠)이 이스케이프 없이 innerHTML에 삽입되던 XSS 경로 수정(escHtml 추가)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- news_search 도구 신설(NewsData.io /latest API) — 과거 48시간 실시간 뉴스, 국가/언어/카테고리 필터, 실제 pubDate·출처 포함. 뉴스 요청엔 web_search보다 우선 사용하도록 시스템프롬프트·강제재시도 넛지 전부 갱신 — 위키피디아 연도페이지·날짜 오라벨링 문제가 구조적으로 해결됨
- 검색예산(5회) 차단 후에도 모델이 무시하고 계속 재시도하는 문제 수정 — "오늘 남미 주요 뉴스" 요청에서 실제 검색 6회 후 42라운드가 전부 헛되이 낭비된 사례 확인(TOOL[48]까지 감). 두 번째 차단부터는 해당 턴 나머지 동안 모델에게 tools를 빈 배열로 보내 물리적으로 더 이상 호출 못 하게 함
- OpenWeather 도시명 모호성 버그 수정 — "Rome, Italy"가 미국 조지아주 Rome으로 잘못 resolve되던 문제. 국가명→ISO코드 자동 정규화(40여개국) + 국가 불일치 시 에러로 재시도 유도
- SearXNG general 카테고리에 뉴스 아닌 위키피디아 엔진이 섞여있던 문제 수정 — 뉴스 쿼리는 categories=news로 daum_news/yahoo_news 등 뉴스 전용 엔진만 사용하도록 변경(사후 필터링 아닌 근본 차단)
- 설정 화면에서 모델 전환 시 models.fallback/profiles가 매번 사라지던 버그 수정 — /api/settings/model 저장 로직이 models 객체를 통째로 새로 만들어서 덮어쓰던 것을, 기존 값을 먼저 펼친 뒤 필요한 필드만 덮어쓰도록 변경
- news.newsdata_api_key를 SECRET_FIELD_MAP에 추가해 vault 자동 암호화 대상에 포함(평문 미저장)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- AUTO-RECOVER 구조적 허점 수정: "라운드 0 & 도구 0회 호출"일 때만 발동하던 걸 "이 요청에 필요한 도구가 아직 안 불렸으면" 언제든 발동하도록 변경 — 엉뚱한 도구 하나만 불러도 안전장치가 통째로 무장해제되던 버그 수정
- 뉴스/날씨/스펙·가격·비교 등 팩트성 질문에 도구 호출 강제(isLiveDataRequest/isFactualInfoRequest) — 확신에 차서 지어내는 것(mistral-large-3에서 다발, 없는 "2026 파리 올림픽" 등)을 사전 넛지+사후 강제재시도로 이중 차단
- web_search/web_fetch 턴당 5회 하드캡 추가 — kimi-k2.6이 단순 뉴스요청에 최대 50회까지 검색하던 문제 차단
- 모델이 함수 호출 대신 텍스트로 tool call을 흉내내는 케이스(`web_search{...}`, 반복시 토큰 열화로 `웍웍웍_search`, 가비지 토큰 삽입 `web_search Räikk{...}`) 복구 로직을 브레이스 앞 텍스트에서 실제 도구명을 찾는 방식으로 일반화
- 뉴스 검색: 대화 이력 주제로 새는 것 방지, 국제뉴스는 영어 쿼리 사용, 위키/홈페이지뿐인 저품질 결과는 다음 provider(tavily 등)로 폴백, 기사 실제 게재일 라벨링(오늘 날짜로 잘못 표기 금지)
- email_send/kakao_send_message 실제 호출 없이 "보냈습니다"라고 답하면 강제 재시도
- browser_open: image_edit와 동일하게 명시 요청 없으면 코드 레벨 차단(기존엔 프롬프트 텍스트뿐)
- config.json models.profiles로 모델별 시스템프롬프트 추가지침 주입 가능(코드 재배포 없이 모델별 이상행동 교정)
- ollama-client 모델 fallback 재시도를 quota 초과 외 retired/deprecated 에러까지 확장, models.fallback 값 유지
- 각종 인텐트 판별 함수(브라우저/데스크톱/실행형 요청, 백그라운드 작업 재개/취소/상태질문)에 한국어 키워드 커버리지 추가
- shell 툴 스키마 예시와 PACKAGE INSTALL 프롬프트 규칙의 모순(pip install 예시 vs 금지 규칙) 제거
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 스튜디오 앱: 사진 편집(스타일 변환/이모티콘 세트) + 결과 뷰어 내 크롭/회전/필터/보정/텍스트 오버레이 편집 툴바 + 저장 플로우(중간 편집은 갤러리에 안 남기고 최종본만 저장+업로드 목록에 재사용 등록)
- image_style_transform: SDXL img2img + IP-Adapter-FaceID로 cartoon 스타일 추가 (watercolor/sketch는 identity drift로 재검토 후 제외)
- 음성엔진 provider 값 xtts_gpu → omnivoice_gpu로 리네이밍 (실제 로딩 모델과 이름 일치, XTTS는 이미 OmniVoice로 교체된 지 오래)
- 메인창 사이드바 GPU 게이지를 GPU0/GPU1로 분리 표시
- 단종된 gemini-3-flash-preview를 기본 모델값에서 전부 제거, kimi-k2.6:cloud로 교체
- vault 백업을 cron→systemd timer(Persistent=true)로 전환, /DATA 전체 동기화 스크립트 추가
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 이미지·동영상 생성 전용 앱(studio-app.html) + API 라우트 추가, 갤러리 기능 포함
- SD1.5 → SDXL 교체 (프롬프트 반영력/해상도 개선), 한글 프롬프트 자동 영어 번역 추가
- image_generate에 quality="high" 옵션 추가 (FLUX.1-schnell 4비트 양자화, 게이트 없는 커뮤니티 미러 사용)
- video_generate에 guidance_scale 노출, 기본 steps 상향
- 생성기 서브프로세스가 결과 없이 죽는 경우의 크래시 방지 + 에러 로깅 강화
- 멀티 GPU 환경에서 시스템 통계 GPU 미터가 깨지던 nvidia-smi 파싱 버그 수정
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- SMILES 위젯을 버튼 클릭식 큰 위젯 → 자동렌더링 소형 인라인(교과서식)으로 전면 개편
- 링크/이미지/볼드 등 후속 정규식이 SMILES 입체화학 표기([C@@H](C) 등)를 마크다운 링크로
오인해 구조식이 깨지던 버그 수정 (코드블록 결과물을 플레이스홀더로 보호 후 최종 복원)
- 구조 복잡도(중원자 수)에 따라 SMILES 캔버스 크기 동적 조절 (벤젠은 작게, 콜레스테롤 등은 크게)
- SMILES 위젯 테두리/배경/여백 제거로 더 가벼운 인라인 표시
- 코드블록(.md-code-block) 검은 배경 제거 + 고정폭 폰트 누락으로 ASCII 다이어그램이
틀어지던 버그 수정 (font-family 미지정 → 본문 가변폭 폰트 상속이 원인)
- code.trim()이 전체 문자열 기준으로만 동작해 ASCII 아트 첫 줄의 들여쓰기만 사라지던
버그 수정 (줄 단위가 아닌 blank 줄만 제거하도록 변경)
- 시스템 프롬프트에 CHEMISTRY NOTATION 규칙 추가 (ASCII 아트 금지, LaTeX/mhchem·SMILES 사용 유도)
- Ollama Cloud 일시적 오류(5xx/타임아웃) 발생 시 즉시 실패 대신 백오프 재시도 추가
- weather_map_screenshot 툴을 ToolRegistry에도 등록 (buildImageMarkdown export 추가)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- KaTeX self-host (vendor/katex) 추가, $...$/$$...$$ 자동 렌더링 + mhchem 확장으로
\ce{C4H10} 같은 화학식·반응식 표기 지원 (renderKatexIn, renderChatMessages 후처리)
- SmilesDrawer self-host (vendor/smiles-drawer) 추가, ```smiles``` 코드블록으로 SMILES
표기를 실제 결합선 있는 2D 구조식으로 렌더링. 라이브러리의 Drawer(canvas) 클래스가
내부 svgWrapper null 참조로 깨지는 버그가 있어 SvgDrawer로 우회
- 진짜 원인 버그 수정: escHtml()가 백틱(`)/백슬래시/달러도 이스케이프하는데, 코드블록·
인라인코드 정규식이 이스케이프 *이후*에 원본 백틱을 찾고 있어 애초에 매치가 안 됐음
— tradingview 차트·네이버 주식차트·MIDI 위젯 등 기존 기능도 전부 이 버그로 작동 안
하고 있었고, 이번에 같이 고쳐서 정상 작동 확인
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>