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.
11 KiB
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 KBnpm 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 파싱 버그 (
escapeRawControlCharsInStringsin 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,copyToClipboardeffectiveScrollTopRef,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 재구조화)