Commit Graph
34 Commits
Author SHA1 Message Date
kimandClaude Sonnet 5 9641f689b8 feat: 로봇 프로젝트 관리 대시보드(robot-app) 추가
메카넘휠 이동로봇 프로젝트(파이5+RPLidar, 추후 팔 추가)의 부품리스트/
단계별 작업/예산을 관리하는 웹앱. doctor-app의 GET/PUT 단일문서 패턴을
재사용하되 케이스 목록 없이 단일 프로젝트 문서로 단순화했고, 첫 로드시
project_robot_dog.md 메모 기준 실제 진행상황을 기본 시드로 반환한다.

- src/gateway/routes/routes-robot.ts: GET/PUT /api/robot/project,
  사용자별 workspace/.smallclaw/robot-project.json에 원자적 저장
- web-ui/html/robot-app.html: 부품/작업/메모 3탭, 실시간 예산 요약,
  디바운스 자동저장
- index.html 🏠 홈 드롭다운에 🤖 로봇 링크 추가

장비연동/전용챗봇/외부데이터연동/CAD플러그인은 다음 단계로 보류.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 13:46:37 +09:00
kimandClaude Sonnet 5 a8149c136a feat: 프린터실 카메라 웹코덱(WebRTC/MSE) 전환 + K2 콤보 카메라 프록시, 옛 3D프린터 앱 제거
- go2rtc가 CORS/Origin 체크로 브라우저 직접접속을 막던 문제 해결: 클라이언트
  스크립트를 벤더링해 같은 오리진으로 서빙하고, WS는 게이트웨이 프록시
  (attachGo2rtcWsProxy)가 Origin을 다시 써서 우회. CCTV 앱의 프린터실 카메라
  라이브뷰가 MJPEG 대신 WebRTC/MSE를 쓰도록 전환(대역폭 절감).
- go2rtc mjpeg 트랜스코더가 멈추는 경우를 감지해 자동 재기동하는
  /api/nvr/mjpeg-health 엔드포인트 추가.
- 신규 3D프린터(K2 콤보, Klipper 스탠드얼론)의 내장 WebRTC 카메라 시그널링을
  프록시하는 /api/k2/webrtc-signal 추가 — 홈클로가 HTTPS로 서빙되는데 카메라가
  평문 HTTP만 지원해 브라우저 mixed-content 차단을 우회하기 위함.
- 옛 3D프린터(단종된 Anet A8 Plus, OctoPrint) 앱과 홈 드롭다운 링크 제거.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PppKcQmeTJDQCpfcgKietu
2026-09-18 22:40:10 +09:00
kimandClaude Sonnet 5 c22377778e feat: 유저별 로고·브랜드명 커스터마이징 — .smallclaw/users/<user>/logo.png, brand_name.txt
헤더/환영화면/채팅 아바타·라벨을 로그인 유저별로 스왑. 파일 없으면 기본 HomeClaw 유지.
애플·자스민 계정에 기본 로고(이모지 아이콘) 적용.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5kyXurhNSboRcY3dAGceY
2026-09-11 16:13:38 +09:00
kimandClaude Sonnet 5 797de9b03f fix: 스킬 컨텍스트가 direct 호출에서 버려지고 8000자에서 잘리던 문제
스킬 프롬프트 전면 점검 중 발견한 버그 2건.

1) callerContext가 'no_preflight' 제어 센티널·BOOT.md 마커·호출자 스킬 텍스트를
   한 필드에 섞어 쓰면서 삼항으로 골랐다 → direct:true와 skillContext를 같이 보내면
   스킬이 조용히 버려짐. pptx-wizard.js의 아웃라인 경로(line 798)가 정확히 그 조합이라
   프레젠터 스킬이 한 번도 전달되지 않았다. 둘 다 싣도록 변경.

2) 캡이 8,000자인데 위저드가 보내는 프레젠터 스킬은 9,861자 → 매 호출 뒤 1,861자 손실.
   잘려나간 것이 [필수] source_files 규칙과 **금지 사항 블록 전체**(파이썬으로 PPTX 직접
   생성 금지, 1회 호출 원칙, 수정은 edit_presentation, spawn_agent 금지)였다. 스킬에서
   가장 값어치 있는 제약들이 모델에 도달한 적이 없다. 캡 12,000으로 올리고 초과 시 경고 로그.

검증: direct 유무 양쪽 경로에서 금지사항·source_files 포함 확인.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XZX9JHFSLZRuK9rR4sCVBs
2026-09-02 15:02:13 +09:00
kimandClaude Sonnet 5 63bbc36c6e fix: 임베딩 모델 콜드로드로 벡터메모리 회상이 타임아웃되던 문제
Ollama 재시작(예: 버전 업그레이드) 후 embeddinggemma가 언로드되면
다음 채팅의 buildPersonalityContext에서 콜드 로드(~30s)가
15초 타임아웃을 넘겨 "vector memory query failed ... aborted due to
timeout" 경고가 났다. 답변엔 영향 없지만 회상이 빠지고 첫 응답이
15초 지연됐다.

- EMBED_KEEP_ALIVE '24h' → -1 (영구 상주). embeddinggemma가 유일한
  로컬 모델이고 ~0.6GB라 안전. ollama.service의 OLLAMA_KEEP_ALIVE=-1
  drop-in과 이중 방어.
- warmupEmbedding() 추가 — 게이트웨이 부팅 시 백그라운드 예열해서
  첫 채팅이 콜드 로드를 안 기다리게.
- embedText에 timeoutMs 파라미터 추가(기본 15s 유지, 워밍업만 60s).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gh45CPB94UFQFiHov2CQe7
2026-08-27 13:11:44 +09:00
kimandClaude Opus 5 68bfba1e4d fix: 해석 실패한 컨텍스트 창 추측값을 브라우저가 캐시해 세션창이 8K에 박히던 문제
사용자 제보: "지서버 모델 연결됐는데 컨텍스트가 세션창에서만 8킬로", 그리고
"웹을 리로드하면 256킬로가 된다".

서버 쪽 마지막 폴백(8192)은 이미 캐시하지 않게 돼 있어서 매번 다시 조회한다
(그 사고를 겪고 고친 주석이 남아 있다). 문제는 브라우저였다 — app.js가

  if (_appCtxMax > 0 && !force) return;   // 한 번 받으면 끝

로 한 번 받은 값을 페이지 수명 내내 들고 있어서, 지서버가 자고 있을 때 열면
8192가 그대로 박혔다. 리로드가 고친 게 아니라 리로드로 캐시가 비워진 것.

메인 채팅 게이지는 SSE usage로 라이브 값을 받아서 멀쩡했고 세션창만 틀렸던
이유가 이것 — 두 화면이 서로 다른 경로로 같은 숫자를 구하고 있었다.

어댑터 쪽(bfbf04e)과 같은 원칙으로 고친다: **추측을 측정처럼 다루지 않는다.**

- resolveNumCtxInfo()가 value와 known(진짜 해석 결과인지)을 함께 반환
- /api/model-context가 provisional 플래그를 실어 보낸다. /api/show 실패나
  마지막 폴백이면 true
- app.js: provisional이면 화면에는 보여주되 _appCtxMax에 저장하지 않아 다음
  호출에서 다시 묻는다
- code.js: provisional이면 모델별 1회 래치(_codeAiCtxMaxFetchedFor)를 풀어
  다시 조회하게 한다

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 10:28:10 +09:00
kimandClaude Opus 5 244900b873 feat: 지서버 keep-alive 재도입 (1분 간격) — 폴링이 절전 방지 실효 확인됨
08-10에 만들었던 2분 간격 버전을 효과가 불확실하다며 지웠는데(그
시점엔 지서버 쪽에 사용자가 직접 건 30초 하트비트가 병행되고 있어
어느 쪽 효과인지 분리가 안 됐음), 오늘 실제 실험으로 인과관계가
확인됐다: 지서버 쪽 30초 폴링을 끄자 몇 분 안에 Windows가 실제로
절전 상태에 들어갔다(게이트가 "깨우는 중" HTML 반환, ARP FAILED,
RDP·ping 전부 무응답 — 전부 실측 확인).

즉 주기적 폴링이 절전을 막는 효과 자체는 실재하고, 남은 건 그걸
지서버 쪽 별도 스크립트로 둘지 게이트웨이가 직접 할지의 문제였다.
사용자가 게이트웨이 쪽으로 다시 넣기로 결정 — 간격은 이전 2분보다
촘촘한 1분으로.

게이팅 로직은 이전과 동일: llm.provider === 'ollama_local'이고
WS 클라이언트가 최소 1개 연결돼 있을 때만(앱이 실제로 열려있고
지서버가 활성 모델일 때만) 직결 엔드포인트에 가벼운 요청을 보낸다.
게이트가 아니라 직결로만 보내므로 이미 잠들어 있으면 조용히
실패한다 — 깨우는 신호가 아니라 "깨어있는 동안 재우지 않는" 신호.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 00:57:42 +09:00
kimandClaude Opus 5 1a77efd106 feat: 모델별 num_ctx 고정 옵션 추가 — 지서버 재로드 문제 대응
사용자 관찰("크기가 바뀔 때마다 올라마가 모델을 다시 로드하네")을
직접 재현·측정: 같은 num_ctx로 두 번 호출하면 load_duration 1.3초,
num_ctx만 바꿔 호출하면 18.8초. ollama-adapter.ts의 _resolveCtx()가
매 턴 프롬프트 크기에 맞춰 2의 거듭제곱으로 동적으로 재계산하는데,
그 값이 바뀔 때마다 Ollama가 모델을 통째로 재로드하는 것 — 대화
도중 설명 안 되는 수 초짜리 멈춤으로 나타났다.

동적 사이징 자체는 VRAM이 빠듯한 배포를 위해 존재하는데, 지서버는
그 경우가 아니다: gemma4:26b를 native 최대 컨텍스트(262144)로
로드해도 32GB 중 18.3GB만 쓰고 13.7GB가 남는다(실측, size_vram).
아낄 필요가 없는데 아끼다가 재로드 비용만 치르고 있었다.

models.profiles[<model>].fixedNumCtx로 모델별로 동적 사이징을
끄고 항상 고정값을 요청하게 하는 옵션을 추가했다(getModelProfileForceToolChoice와
같은 패턴). gemma4:26b는 262144로 설정 — 한 번 로드되면 대화가
아무리 길어져도 재로드가 없다. 다른 모델/제공자는 기존 동적
사이징 그대로 유지된다(opt-in이라 이 모델 외엔 영향 없음).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 17:30:43 +09:00
kimandClaude Opus 5 f972b184d6 revert: 지서버 서버측 keep-alive 제거 — 지서버 자체 10초 하트비트로 대체
효과가 불확실했다. 로그로 보면 2분 핑이 도는 동안에도 한 번은 실제로
잠들었고("방금 또 잠들었던거 같으네"), 다른 구간에선 40분 연속 안
잤는데 그게 핑 덕분인지 그 시간 내내 실사용 채팅이 있었기 때문인지
분리할 수 없었다.

사용자가 지서버 쪽에 직접 10초 간격 하트비트를 새로 걸었다 — 원격에서
2분마다 찔러보는 것보다 훨씬 촘촘하고, 무엇보다 지서버 자체에서 도는
메커니즘이라 신뢰도를 판단하기 쉽다. 서버 쪽 것은 이제 중복이라
사용자 결정으로 제거.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 15:19:13 +09:00
kimandClaude Opus 5 5d88d34b33 feat: 지서버가 활성 모델일 때 절전 안 하도록 keep-alive 신호 추가
08-09에 이미 조사했던 "지서버가 30분 설정보다 훨씬 빨리 잔다" 문제는
사용자가 전기료 절약 관점에서 선호하는 동작으로 확정하고 조사를 접었던
건이다([[project_wol_gate]]). 그런데 오늘(08-10) 외부에서 실제로 사용
중일 때 이 빠른 절전이 매번 ~23초 기상 지연으로 이어져 불편해졌고,
사용자가 "지서버가 안자게 신호를 계속 보내자"고 명시적으로 요청했다 —
원인 재조사가 아니라 능동적 keep-alive를 원하는 것으로 확인.

원인 후보였던 "Windows 유휴 타이머가 마지막 사용자 입력 기준이라
API 트래픽으로는 리셋 안 됨"이 맞다면, 주기적으로 실제 HTTP 요청을
보내는 것 자체가 대응책이 된다.

`llm.provider === 'ollama_local'`이고(getProvider()의 단일 캐시 구조상
어차피 이 조건에서만 실제 채팅 트래픽도 지서버로 간다) WS 클라이언트가
최소 1개 연결돼 있을 때만(앱이 실제로 열려 있을 때만) 2분마다 직결
엔드포인트에 가벼운 /api/tags 요청을 보낸다. 게이트가 아니라 직결로만
보내므로, 이미 잠들어 있으면 그냥 조용히 실패한다 — 깨우는 신호가
아니라 "깨어있는 동안 재우지 않는" 신호라, 나머지 시간대의 절전
이점(사용자가 이미 선택한)을 훼손하지 않는다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:19:40 +09:00
kimandClaude Opus 5 c830c9fbf6 feat: 지서버 절전 상태를 표시하고 모델 선택 시 자동으로 깨우기
지서버는 유휴 시 절전에 들어간다. 그동안 UI가 이를 "Offline"(빨강)으로만 보여줘서
고장난 것처럼 읽혔고, 실제로는 메시지를 보내면 자동으로 깨어나 정상 응답한다.
사용자가 붉은 표시를 보고 원인을 찾아 나선 뒤 발견했다.

- /api/status가 sleeping을 함께 반환한다. 게이트를 찔러 확인하지 않고 wake_url
  설정 유무로 판별하는데, 상태 표시는 10초마다 갱신되고 게이트를 찌르는 행위가
  곧 매직패킷 발사라 확인하러 가면 지서버를 영영 못 자게 만든다.
- 표시를 셋으로 분리: Online(초록) / 절전 중(노랑) / Offline(빨강).
  노랑엔 "메시지를 보내면 자동으로 깨어납니다" 툴팁을 붙였다.

또한 설정에서 provider를 고르면 그 자리에서 깨우도록 했다. UI는 이미 선택 즉시
/api/models/test로 모델 목록을 가져오는데, 지서버가 자면 여기서 "모델 없음 —
서버가 실행 중인가요?"로 실패했다. 모델 선택은 쓰겠다는 의사가 분명하므로 이를
기상 트리거로 삼는다. 새 엔드포인트 없이 기존 동작에 얹었고, "자면 모델 목록이
빈다"는 문제도 같이 해결된다. 잠든 지서버로 실측 시 7초 만에 모델 5개를 받았고,
깨어있을 때는 0초로 통과해 불필요한 패킷을 보내지 않는다. wake_url이 없는
provider(클로서버 등)는 기존 동작 그대로다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:40:07 +09:00
kimandClaude Opus 5 3594d26047 fix: 단위를 감싼 LaTeX를 평문으로 정규화
모델이 미세먼지 단위를 $\mu\text{g/m}^3$처럼 LaTeX로 쓴다. 단위는 수식이 아닌데
이 표기가 세 곳에서 깨진다: index.html은 KaTeX가 렌더하지만 복사하면 MathML까지
딸려와 "μ g/m 3 μg/m 3"처럼 중복돼 보이고, 나머지 채팅 페이지 8개(weather/doctor/
lawyer/detective 등)는 KaTeX 자체가 없어 생 LaTeX가 그대로 노출되며, TTS는
백슬래시를 소리 내어 읽는다.

단위만 담긴 수식 span만 유니코드로 바꾸고, 연산자·분수·변수가 있으면 그대로 둔다:
  $\mu\text{g/m}^3$ → μg/m³      $\text{m/s}$ → m/s
  $E = mc^2$ / $x + y$ / $\frac{a}{b}$ → 유지

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:39:20 +09:00
kimandClaude Opus 5 1146b59199 fix: 컨텍스트 윈도우가 이전 모델 값에 고정되던 문제
게이지가 8192로 표시됐으나 지서버 gemma4:26b의 실제 값은 262144, 32배 차이.
원인이 세 겹이었다.

1. resolveNumCtx()의 _cachedNumCtx가 아무 키 없이 프로세스 수명 내내 유지됐다.
   6곳에서 설정되는데 무효화하는 코드가 어디에도 없어, 모델/provider를 바꿔도
   이전 값을 계속 반환했다(클로서버로 바꿨는데 지서버의 262k가 계속 보이던 증상).
   → (provider, models.primary, provider별 model, llm.num_ctx) 키에 연동

2. 활성 provider가 아니라 localhost:11434를 하드코딩해 조회했다.
   provider가 ollama_local(지서버)일 때 클로서버 로컬 Ollama에 물어보니
   "model not found" → 최후 폴백 8192로 떨어졌다.
   → activeOllamaEndpoint() 추가, /api/model-context의 per-model 조회에도 적용

3. 프론트엔드 _fetchAppCtxMax()가 페이지 로드 시 한 번만 조회했다.
   → force 인자 추가, 설정 저장 직후 재조회

검증: 게이지 8192 → 262144(지서버 실제값 일치), 반복 호출 5회 안정.
캐시 키는 모델 전환 시 무효화되고 설정 동일 시 적중.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:04:12 +09:00
kimandClaude Sonnet 5 968aab1faa fix: 비전 없는 모델이 이미지 못 본다는 걸 명시 (환각 방지)
weather_map_screenshot 같은 도구의 stdout에는 "위 이미지에서 실제로 보이는
것만 설명하라" 같은 비전 모델 대상 지시문이 섞여 있는 경우가 있는데, 이걸
비전 없는 모델이 받으면 응답 안 된 명령으로 읽고 색상·좌표·아이콘 같은
시각적 디테일을 그럴듯하게 지어내는 문제가 있었음. 경로 힌트 뒤에 "이 모델은
이미지를 볼 수 없다"는 명시적 안내를 덧붙여 추측 대신 솔직히 모른다고
답하도록 함.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 13:16:43 +09:00
kimandClaude Sonnet 5 d51c40484a feat: 의료 어시스턴트 앱 + Home Assistant 임베드 앱 추가
법률 사건관리 앱과 같은 패턴으로 의료 사건(진료기록) 관리 앱 추가
(routes-doctor-cases.ts + doctor-app.html + WOPI 라우트). Home Assistant를
iframe으로 띄우는 얇은 래퍼 앱 추가(ha.applecherry.net, 2026-08-01 통합
완료된 것 URL만 새로 노출). weather_openmeteo의 요청 성형/리포트 포맷팅
로직을 weather-openmeteo-format.ts로 분리(순수함수라 테스트 가능,
2026-07-29 있었던 타임존/롤링윈도우/daily-in-hourly 버그 3건 회귀 방지
테스트 포함) + geocode 재시도 테스트.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 13:16:01 +09:00
kimandClaude Sonnet 5 3ae9b986e9 feat: 안드로이드 에뮬레이터 관리콘솔/ADB 쉘 + 컨테이너 정리
세션 영속화, 관리콘솔 WS 프록시(attachAndroidConsoleWsProxy), ADB 쉘
엔드포인트, 고아 컨테이너 정리 로직 추가. 2026-07-31 실사용 검증 완료된
작업을 커밋만 안 하고 남겨뒀던 것.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 13:13:15 +09:00
kimandClaude Sonnet 5 b001cd9318 feat: OpenAI 모델 목록 조회 라우트 추가, LM Studio thinking 비활성화 스위치
프론트엔드가 예전부터 호출하던 POST /api/openai/models가 백엔드에 구현이
안 되어 있어 항상 404였던 버그 수정 - OpenAICompatAdapter로 실제 OpenAI
API에 모델 목록을 조회하도록 구현.

지서버(LM Studio)의 gemma-4-26b-a4b가 응답당 완성 토큰을 2.5배 더 많이
쓰는 것을 벤치마크로 확인(322 vs 132 토큰/응답, thinking 오버헤드로 추정) -
chat_template_kwargs.enable_thinking=false를 기본 적용하되, 복잡한 질문에서
품질이 떨어질 수 있어 설정 화면(모델 → LM Studio)에 체크박스로 켜고 끌 수
있게 구현. 기본값은 비활성화(현재 상태 유지).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 12:57:22 +09:00
kimandClaude Sonnet 5 6014e51ba3 이미지스튜디오를 ComfyUI 백엔드로 전환, 원본 UI 임베드, LTX 비디오 고화질 옵션 추가
diffusers venv 스크립트 방식 대신 상시구동 ComfyUI(systemd, GPU1)를 통해
SDXL/FLUX/PuLID 스타일변환/LTX 비디오를 생성하도록 전환. ComfyUI 자체 웹
UI를 리버스 프록시로 스튜디오 앱에 같은 도메인으로 임베드(routes-comfyui.ts)
하고, LTX-Video에 non-distilled "dev" 체크포인트 기반 고화질 옵션을 추가.
날씨 앱에는 Windy 제트기류/태풍 마커, 게이트웨이에는 wol-gate 웨이크 로그
조회 API도 함께 반영.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 17:21:36 +09:00
kimandClaude Opus 5 1c2c5e23bc v4.3.38: 법률 어시스턴트 앱에 사건(사건 기록) 관리 기능 추가
- 채팅창 파일 첨부 버튼 추가 (PDF/TXT, 회계사 앱과 같은 /api/upload/file 재사용,
  pdf_read로 계약서 분석 — 스킬 프롬프트엔 이미 방법론이 있었는데 버튼만 없었음)
- 탐정 앱의 "사건" 관리를 법률 앱에 이식: 사건 목록(검색/정렬) + 사건별 메모·
  체크리스트·타임라인·첨부자료·제출서류 탭 + 사건별 독립 AI 대화 세션
  (다른 사건 컨텍스트가 안 섞임, 탐정 앱과 동일 패턴)
- PDF 편집기(Stirling-PDF)·Collabora 문서 편집 연동도 그대로 이식 — 사건 폴더에
  자동 저장됨
- 백엔드는 탐정 앱 기존 코드를 건드리지 않고 새 공용 모듈(case-storage.ts)로
  일반화 — appType 파라미터로 detective/lawyer 둘 다 같은 로직 재사용,
  탐정 앱 데이터·동작은 그대로 유지(회귀 테스트로 확인)
- 사진 자동 날짜인식(탐정 앱 전용, 카톡 캡처 등에서 날짜 추출) 등 법률 사건과
  무관한 기능은 이식 범위에서 제외

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:34:52 +09:00
kimandClaude Opus 5 8359901cac v4.3.19-26: 환각 방어 전역화 + 답변 길이 압박 완화 + 토큰 로깅 구멍 수정
실사용 로그 감사(07-29 GPU/PCIe 대화)에서 발견한 문제들 일괄 수정.

환각 방어:
- looksLikeUnverifiedSpecClaim이 놓치던 패턴 2개 추가 — 단위 없는
  "15토큰"(strict tier), 퍼센트 벤치마크 "약 5%"(ambiguous tier,
  SPEC_CONTEXT_KEYWORD 게이트로 오탐 방지)
- forceToolChoiceOnLiveData를 모델별 opt-in → 전역 기본 ON + opt-out.
  모델을 수시로 바꾸는데 profiles에 등재된 2개만 방어되던 구멍이 원인
  (프로필 없던 gemini-3.5-flash-lite가 tok/s 날조 + 없는 제품명 창작)

답변 길이:
- "Keep responses SHORT" 1-2 → 2-4문장, 대화/실행 답변으로 범위 한정.
  정보성 답변(뉴스·날씨·설명·비교)은 OUTPUT FORMAT을 따르도록 명시
- 불확실성 표현은 길이 제한 면제. 1-2문장 압박이 "모른다"고 말할 자리를
  없애 환각을 확신형 한 줄로 포장하던 것이 실측으로 확인됨
- 뉴스는 도구가 반환한 기사 전부 표기(3건 체리픽 금지, 8-10행 목표)

토큰 로깅:
- logUsage가 ollama-adapter 내부 private이라 Ollama 경유만 기록되고,
  openai-compat은 usage 필드를 추출조차 안 하고 버리던 문제. 비용 비교
  대상인 Gemini가 통째로 누락돼 공짜처럼 보이는 상태였음
- providers/usage-log.ts로 공용화 → openai-compat(chat/generate)과
  openai-codex(SSE response.completed)에서도 캡처·로깅·호출자 반환

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 14:44:21 +09:00
kimandClaude Sonnet 5 3bff465c08 v4.3.19-25: 탐정 앱 자체 WOPI 호스트로 NextCloud 없이 Collabora 직결
기존 col.applecherry.net Collabora 서버를 재사용하되, NextCloud(WebDAV
업로드/가져오기 왕복) 없이 홈클로 게이트웨이가 직접 WOPI 호스트 역할
수행 (CheckFileInfo/GetFile/PutFile). hwp는 사건 폴더 안에서 odt로
1회 변환 후 그 자리에서 바로 편집 — 타이핑하는 대로 사건 폴더에 즉시
저장되어 별도 "가져오기" 버튼이 필요 없어짐(dtFetchFromNextcloud 제거).
config.json에 collabora.baseUrl/publicUrl 추가. Express 5의
res.sendFile 이슈(이미 known) 재발 → createReadStream으로 우회.

실기기(col.applecherry.net + 실제 사건 고소장.odt)로 전체 라운드트립
검증 완료.

부수: 사이드바 사건목록이 빠른조사/법률질문 버튼 늘어나며 눌리는 문제
— 잘 안쓰는 버튼 정리(19개→11개)로 완화 + .dt-cases min-height 안전장치.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 14:18:18 +09:00
kimandClaude Sonnet 5 8a7f6443fc v4.3.19-23: 글로벌 하트비트가 빈 워크스페이스를 보던 버그 수정
멀티유저 전환 후 USER.md/SOUL.md가 유저별 워크스페이스로 옮겨졌는데
HeartbeatRunner는 여전히 글로벌 워크스페이스(파일 없음)를 보고 있어서
30분마다 memory_read(user/soul)가 매번 "not found"로 실패하던 문제.
workspacePath를 papa 워크스페이스로, handleChat에 username도 함께
전달하도록 수정.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 13:22:09 +09:00
kimandClaude Sonnet 5 a80ad9e809 v4.3.19-6: Google 일일 사용량 표시 + 한도 도달시 자동 폴백
- 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>
2026-07-26 17:37:03 +09:00
kimandClaude Sonnet 5 814f32a63e v4.3.19-5: 컨텍스트 게이지 provider-aware하게 수정 + 캐시 버그 제거
- 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>
2026-07-26 17:17:14 +09:00
kimandClaude Sonnet 5 0963231327 v4.3.19-2: 회계사 앱 스프레드시트 뷰어 수정 + helpers.js 5개 파일 분리
- 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>
2026-07-25 21:30:05 +09:00
kimandClaude 52b373f2d4 v4.3.18: web-ui 폴더 정리 - html/js/css 분리, server.ts 레거시 리다이렉트 추가
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-25 19:39:44 +09:00
kim cf15061e39 v4.3.14: 죽은 코드 5개 정리 (readDailyMemoryContext, readMemoryCategories, renderTaskCandidatesForHuman, latestTaskForSession, buildBlockedTaskStatusMessage)
정의만 있고 호출부 없는 함수들. readDailyMemoryContext/readMemoryCategories는
Chroma 벡터 메모리(2026-07-24 도입, buildPersonalityContext의 [RECALLED_FACTS])가
관련성 기반 회상으로 대체하면서 이미 실질적으로 무용해진 상태였음.
2026-07-25 18:34:48 +09:00
kim 394d990fba v4.3.13: handleTaskControlAction → gateway/tasks/handle-task-control.ts 분리 (5,021→4,668줄)
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 생성 후 실제 구현을 대입

실제 백그라운드 태스크 생성→조회→삭제 왕복으로 순환 경로 전체 검증 완료.
2026-07-25 18:31:45 +09:00
kim dc546ab5e2 v4.3.12: buildPersonalityContext → gateway/chat/personality-context.ts 분리 (5,455→5,021줄)
detectToolCategories/TOOL_BLOCKS/readMemorySnippets 등 관련 헬퍼 클러스터를
createPersonalityContext(resolvePromptPath) 팩토리로 통째 이동. 외부 의존은
resolvePromptPath 하나뿐이라 순환 의존성 없이 분리 가능했음.
2026-07-25 18:23:50 +09:00
kimandClaude Sonnet 5 5ed28240f3 v4.3.11: handleChat()+handleCodeChat()을 gateway/chat/handle-chat.ts로 분리 (server.ts -3712줄)
오늘 분석에서 찾은 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>
2026-07-25 18:06:31 +09:00
kimandClaude Sonnet 5 0d2372d0df v4.3.10: executeTool()을 gateway/chat/execute-tool.ts로 분리 (server.ts -1417줄)
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>
2026-07-25 17:46:46 +09:00
kimandClaude Sonnet 5 44de254ab4 v4.3.9: buildTools()를 gateway/chat/build-tools.ts로 분리 (server.ts -1201줄)
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>
2026-07-25 17:32:50 +09:00
kimandClaude Sonnet 5 05976e3f05 v4.3.6: gateway 루트에 남아있던 나머지 20개 파일도 서브디렉토리로 분류
- memory/ (3): fact-store, memory-manager, memory-vector
- hooks/ (2): hooks, hook-loader
- infra/ (4): boot, gpu-detector, ollama-process-manager, mcp-manager
- guards/ (6): link-validator, visual-error-detection, context-injection,
  prompt-gates, retry-strategy, verification-flow
- internal-agent-task.ts(실제 express 라우터), music-renderer.ts(routes-music.ts
  전용 백엔드)는 routes/로 편입
- server.ts/session.ts/skills-manager.ts는 5곳 안팎에서 두루 참조되는 핵심
  모듈이라 루트 유지, orchestrator.ts(export {}뿐인 죽은 레거시 껍데기)도
  분류 대상이 아니라 별도 이슈로 루트에 남김
- 이번에도 import 경로 자동 재계산 스크립트 사용, 41곳 수정 후 빌드/재시작/
  20개 엔드포인트 재검증 완료

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 17:10:44 +09:00
kimandClaude Sonnet 5 db7260ac46 v4.3.5: gateway 폴더 구조 정리 — routes/channels/mocks/errors/tasks/automation 서브디렉토리 분리
- src/gateway/ 76개 .ts 파일 중 55개를 종류별 서브디렉토리로 이동
  (routes/ 35, channels/ 4, mocks/ 3, errors/ 5, tasks/ 8, automation/ 3)
- 루트엔 server.ts(server-v2.ts에서 개명) 포함 21개 핵심 인프라 파일만 유지
- gateway 내부 상대경로 import 101곳 + 외부 참조 5곳(cli/index.ts,
  tools/kakao.ts, tools/pptx.ts, tools/shell.ts, tools/task-control.ts)을
  스크립트로 재계산, 빌드/재시작/26개 엔드포인트 스모크테스트로 검증

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 17:02:54 +09:00