Files
locode/locode-upgrade-memo.md
T
kim 09cd992aa6 feat: normal screen architecture, burn rate by model time, dependency upgrades
Major changes:

- Replace alternate screen buffer + app-side mouse tracking/virtual scroll
  with normal screen buffer + Ink <Static> for permanent scrollback.
  Terminal's native scroll/selection/copy just works — no mouseInput.ts needed.
- Fix burn rate (🔥) to use model response time (modelTimeMs) instead of
  session elapsed time, so it reflects actual generation throughput.
- Upgrade dependencies: openai 6→7, commander 13→15, execa 9→10, vitest 3→4,
  node types 22→26, tsup target node20→node22.
- Update upgrade memo with current architecture, all completed upgrades,
  and working tree status.
2026-08-24 12:44:08 +09:00

11 KiB

locode 업그레이드 작업 메모

프로젝트 개요

locode — Claude Code의 설계 철학을 가져와 로컬 모델(Ollama/LM Studio)용으로 재구현한 에이전트 코딩 CLI. TypeScript + Ink(React-for-CLI) 기반. 백엔드는 OpenAI 호환 /v1/chat/completions 엔드포인트 사용.

  • 핵심 철학: "신뢰할 수 없고 느리고 비전/툴콜 지원이 불확실한 로컬 모델"이라는 현실에 맞춰 모든 가정을 비관적으로 재단
  • Claude Code 플러그인 포맷을 직접 소비하는 하위호환 브리지 (.claude-plugin/plugin.json, commands/agents/skills/hooks/MCP)

빌드/테스트 상태 (최신: c539995)

  • npx tsc --noEmit — 깨끗
  • npm run build (tsup) — 290.75 KB
  • npm test (vitest) — 43 파일 347개 전부 통과
  • Node 타겟: node22 (tsup.config.ts에서 변경)
  • 파일 인코딩: CRLF/LF 섹션 혼재 (edit_file의 EOL 정규화 로직으로 처리)

아키텍처 개요 (현재)

렌더링 모델: Normal Screen + <Static>

대폭 변경: 이전 alternate screen buffer + 인앱 가상 스크롤 + 마우스 트래킹 아키텍처를 완전히 폐기.

  • Normal screen buffer: index.tsx가 alternate screen에 진입하지 않음. 앱이 터미널의 일반 스크롤백에 직접 출력.
  • Ink <Static>: 완료된 턴/이벤트를 <Static> 컴포넌트로 한 번만 렌더링 → 터미널 스크롤백의 영구 부분이 됨. 재렌더링 없음.
  • 마우스 트래킹 완전 제거: mouseInput.ts 파일 삭제. SGR-1006, logicalButton(), copyToClipboard(), mouseToContentRow 모두 제거. 터미널 자체 네이티브 스크롤/선택/복사에 의존.
  • 이점: 코드 대폭 감소, 터미널 호환성 향상, SSH/tmux에서도 기본 스크롤 작동, 인앱 마우스 버그 불가.

핵심 파일 맵

  • src/ui/ink/index.tsx — 진입점. alternate screen 없이 Ink render. cleanup 시 flush + 종료.
  • src/ui/ink/App.tsx (1107행) — 메인 UI 컴포넌트. <Static> + 라이브 영역. 상태: starting→connecting→loading-models→model-select/session-select→input.
  • src/ui/ink/ChatInput.tsx — 커스텀 multiline 입력. Shift+Enter 줄바꿈, bracket paste, @멘션 fuzzy picker, IME 커서.
  • src/ui/ink/HistoryItemView.tsx — memo() 래핑. thinking/streaming_text/assistant/tool_call 등 다양한 아이템 렌더.
  • src/ui/ink/FilePanel.tsx — Ctrl+F 토글 사이드 패널 (Files/Activity 탭).
  • src/agent/loop.ts (1284행) — 메인 에이전트 루프. 턴/스트리밍/툴콜/컴팩션/서브에이전트/병렬 툴 배치/반복 루프 감지.
  • src/agent/session.ts — Session 객체, 통계, 상태, mutation gate.
  • src/agent/systemPrompt.ts — 시스템 프롬프트 빌더 (로컬/클라우드 분기).
  • src/agent/events.ts — AgentEvent 타입 (thinking_delta/thinking_done 추가).
  • src/tools/ — 14+ 내장 툴 (read_file, list_files, grep, definition, references, diagnostics, web_search, web_fetch, git_status, write_file, edit_file, multi_edit, notebook_edit, bash, bash_output, bash_kill, git_commit, todo_write, task_create/list/get/update, agent).
  • src/toolcalling/ — native 어댑터, fallback 파서/프롬프트, partialJson 복구, resolve (Ollama 빈키 복구 포함).
  • src/mcp/ — MCP 클라이언트/매니저/어댑터/config (모든 MCP 툴 mutating 강제).
  • src/codeintel/lspManager.ts (452행) — 언어별 LSP 서버 lazy spawn (tsserver/pyright/gopls/rust-analyzer/clangd + 사용자 설정 가능).
  • src/config/defaults.ts — 모든 기본값. DEFAULT_MAX_ITERATIONS = 300, DEFAULT_MAX_OUTPUT_TOKENS = 131072 등.
  • src/config/store.ts — StoredConfig 타입. 메모리 캐시 + 영속 설정.
  • src/persistence/sessionStore.ts — 세션 CRUD, 인덱스 자가 복구, 원자 쓰기, 큐잉.
  • src/utils/ — tokens(스크립트 인식 휴리스틱), truncate(head+tail), diff, shell, processTree, image, html, mentions, projectInstructions, writeFileAtomic.

완료된 업그레이드 전체 목록

초기 12개 (v0.6.0, 6fe9888)

# 항목 상태
1 병렬 툴 실행 (read-only Promise.all) ✅
2 재시도 정책 설정화 (maxRetries) ✅
3 정확한 토큰 추정 (CJK/기호 스크립트 인식) ✅
4 스마트 출력 캡 (head+tail 보존) ✅
5 부분 히스토리 보존 컴팩션 ✅
6 동적 max_tokens (resolveMaxTokens) ✅
7 툴 설명 풍부화 ✅
8 MCP 연결 재시도·재연결 ✅
9 컨텍스트 윈도우 캐시 TTL ✅
10 edit_file 유사 매치 제안 (Levenshtein) ✅
11 git 출력 head+tail ✅
12 auto-accept 모드 세분화 ✅

08-20 세션 (6fe9888..dfaf8d1)

  • LSP 코드 인텔리전스 + 병렬 서브에이전트 mutation gate
  • 로컬 모델 툴콜 안정성 3종 (부분 JSON 복구, fallback 파서, 빈 응답 재시도)
  • multi_edit, DiffView, notebook_edit, task 시스템, 트랜스크립트 내보내기
  • /mouse 토글, 출력 중 입력창 작동

08-21 세션 (dfaf8d1..5c3fcfd)

  • 마우스 기본 ON + SGR-1006 전체 파싱 + 드래그 선택/OSC 52 클립보드
  • 병렬 툴 실행 검증 (runToolBatch 디버그 로그 + 테스트)
  • maxIterations 100→300 상향
  • gateAndRun 에러 로깅
  • DiffView 삭제 라인 색상 조정 (#f85149 → #e8904e)

08-22 세션 #1 (5c3fcfd..5065990): 15개 업그레이드

# 항목 상태
1 시스템 프롬프트 대폭 강화 (로컬/클라우드 분기 포함) ✅
2 멀티라인 입력 (Shift+Enter) + 브래킷 페이스트 ✅
3 /undo 명령 — 마지막 턴 롤백 ✅
4 /dashboard — 이미 구현됨 ✅
5 컨텍스트 사용률 진행 바 — 이미 구현됨 ✅
6 추론(thinking) 토큰 지원 ✅
7 @ 멘션 퍼지 매칭 + 캐시 TTL ✅
8 readFile 바이너리 가드 + 대용량 보호 ✅
9 writeFile 원자 쓰기 (temp→rename) ✅
10 bash 문자열 누적 O(n²)→O(n) ✅
11 estimateTokens 정규식 고속화 ✅
12 handleCompletedMessage any→Record ✅
13 세션 복원 시 권한 상태 보존 ✅
14 컨텍스트 윈도우 병렬 감지 ✅
15 스트리밍 텍스트 누적 O(n²)→O(n) ✅

08-22 세션 #2 (47711a9..60c6021): 시스템 프롬프트 분리 + 안정성

  • 시스템 프롬프트 로컬/클라우드 분리 (isLocalBackendURL, buildSystemPrompt(..., isLocal))
  • 빈 응답 재시도 1→3 (MAX_EMPTY_RESPONSE_RETRIES = 3)
  • 마우스 드래그 SGR-1006 버그 수정 (모션 비트 32 인식)
  • maxIterations 기본값 300 반영

08-22 세션 #3 (Claude Code, c539995): 크래시/매칭 버그 수정

  • 마우스 크래시 수정 (effectiveScrollTopRef 미선언 → ReferenceError)
  • CRLF 제어문자 JSON 파싱 버그 (escapeRawControlCharsInStrings in partialJson.ts + nativeAdapter.ts 리페어 폴백)
  • CRLF edit_file/multi_edit 매칭 버그 (LF 정규화로 매칭, 쓰기 전 원래 EOL 복원)
  • 🔥 번인레이트 오해 수정 (input+output → output만)

현재 미커밋 변경 (working tree)

파일 변경 내용
package.json / package-lock.json 의존성 대거 업그레이드 (openai 6→7, commander 13→15, execa 9→10, vitest 3→4, node types 22→26, tsup target node20→node22 등)
src/ui/ink/mouseInput.ts 삭제됨 — 마우스 트래킹 전면 폐지
src/ui/ink/index.tsx alternate screen buffer 제거, normal screen + <Static> 아키텍처로 전환
src/ui/ink/App.tsx 마우스/가상스크롤 코드 제거, <Static> 기반 렌더링으로 재구조화
src/ui/ink/ChatInput.tsx 마우스 관련 코드 제거, 멀티라인/브래킷 페이스트/@멘션 유지
src/ui/ink/HistoryItemView.tsx 마우스 선택 관련 코드 제거
src/ui/ink/FilePanel.tsx (변경 있음 — 상세 확인 필요)
src/agent/loop.ts (변경 있음 — 상세 확인 필요)
src/agent/loop.test.ts (변경 있음 — 테스트 추가/수정)
src/codeintel/lspManager.ts (변경 있음)
src/config/defaults.ts (변경 있음)
src/config/store.ts (변경 있음)
src/persistence/sessionStore.ts / .test.ts (변경 있음)
tsup.config.ts target: node20 → node22

핵심 아키텍처 변경: Normal Screen + Static

이전 아키텍처에서 대폭 전환:

이전 (삭제됨)

  • Alternate screen buffer 진입/종료 (\x1b[?1049h / \x1b[?1049l)
  • 인앱 가상 스크롤 (PageUp/PageDown, scrollTop/ref)
  • 마우스 트래킹 (SGR-1006, \x1b[?1002h\x1b[?1006h)
  • 마우스 드래그 텍스트 선택 + OSC 52 클립보드 복사
  • mouseInput.ts: matchMouseSequence, logicalButton, copyToClipboard
  • effectiveScrollTopRef, selectionStart/End, extractSelectionText

현재 (새 아키텍처)

  • Normal screen buffer — 터미널의 일반 스크롤백에 직접 출력
  • Ink <Static> 컴포넌트 — 완료된 히스토리 아이템을 한 번만 렌더링, 재렌더링 없음
  • 마우스 트래킹 없음 — 터미널 자체 네이티브 스크롤/선택/복사에 의존
  • mouseInput.ts 파일 삭제됨
  • 이점: 코드 대폭 감소, 터미널 호환성 향상, SSH/tmux 기본 작동

설정 기본값 (현재)

설정 기본값 비고
DEFAULT_CONTEXT_WINDOW 8192 자동 감지 실패 시 폴백
DEFAULT_MAX_ITERATIONS 300 50→100→300 상향
DEFAULT_MAX_OUTPUT_TOKENS 131072 8192→131072 (128K). GLM 등 1M 컨텍스트 모델 대응
DEFAULT_MAX_RETRIES 0 SDK 지수 백오프
DEFAULT_AUTO_COMPACT_THRESHOLD 0.85
DEFAULT_REQUEST_TIMEOUT_MS 180,000 3분
DEFAULT_SUBAGENT_TIMEOUT_MS 600,000 10분
MAX_EMPTY_RESPONSE_RETRIES 3 1→3 상향
MAX_SUBAGENT_DEPTH 1 서브에이전트 중첩 금지
MAX_PRESERVED_TAIL_MESSAGES 8 컴팩션 시 보존
MAX_PRESERVED_TAIL_FRACTION 0.3 컴팩션 시 보존 비율
MAX_RETAINED_IMAGES 2 히스토리 이미지 보존

트러블슈팅 힌트

  • "자꾸 에러"의 주요 원인: max_tokens 잘림 → malformed 툴콜 (✅ 해결됨 — 동적 max_tokens + CRLF 제어문자 이스케이프)
  • CRLF 파일 edit_file 매칭 버그: ✅ 해결됨 (LF 정규화 공간에서 매칭, 쓰기 전 원래 EOL 복원)
  • "Paused after N steps": locode config set maxIterations <number> (기본 300)
  • 클라우드 모델 빈 응답: MAX_EMPTY_RESPONSE_RETRIES=3으로 재시도
  • 시스템 프롬프트가 로컬/클라우드 자동 분기 — localhost 감지 시 로컬 프롬프트, 그 외 클라우드 프롬프트
  • 반복 루프 감지: detectRepetitionLoop() — 스트리밍 텍스트에서 짧은 반복 패턴 감지 시 중단
  • 번인레이트(🔥) 표시: outputTokens/min만 반영 (inputTokens는 제외)

남은 후보 / TODO

  • LSP 실서버 통합 테스트 (실제 tsserver/pyright 띄워서 검증)
  • task store 영속화 (세션에 task 저장)
  • 의존성 업그레이드 커밋 (working tree에 미반영 변경 다수)
  • Normal screen 아키텍처 전환 커밋 (mouseInput.ts 삭제, index.tsx/App.tsx 재구조화)