release: ship v1.5.0 with on-device writing suggestions
Some checks failed
deploy-site / deploy (push) Failing after 33s
portable-unsigned / portable-windows (push) Failing after 4m7s
release / release-windows (push) Failing after 3m16s

Adds next-sentence suggestions while typing, weekly input insights and a
personal phrase memory to the desktop app, and fixes custom instructions so
they process the text instead of inserting the instruction's own wording.
Local model requests are now bounded and individually cancellable.

Bumps the product version to 1.5.0 (Android/iOS build 1050000), refreshes the
landing and web download links, and records the new INPUT feature rows and the
open verification gaps in the infrastructure map.
This commit is contained in:
Yun Chan 2026-09-23 16:04:27 +09:00
parent 99f06c253c
commit 5c11ee2fde
104 changed files with 14410 additions and 174 deletions

View file

@ -1,5 +1,145 @@
# D3RO-VOICE 프로젝트 현황
## Local Flow Intelligence 10-pack — 문서/자동 증거 handoff (2026-09-22)
- 데스크톱 기존 InputTelemetry/Suggestion/UI 확장으로 Flow Radar, Edit Friction, App DNA, App Quality, Privacy Receipt, Smart Exclusion, Why This Suggestion, Memory Decay, Instant Recall, Shortcut Safety Audit을 구현했다. raw key events/key codes/content stream은 저장하지 않지만, 동의한 학습 텍스트는 `typing_samples`/`personal_phrases`에 별도 정책으로 남을 수 있다. `personal_phrases`는 나이 기반 자동 만료가 없고 개별 삭제·전체 삭제·동의 철회로 제거된다.
- 이 handoff 시점의 자동 증거는 domain 9/9 + service 12/12 = targeted 21/21, desktop build passed다. 이는 strict/full suite 전체 또는 GUI 증거가 아니다.
- 재시작 가능한 다음 게이트: 외부 터미널에서 `run-desktop.bat`을 실행해 편집 가능/읽기 불가/비밀번호 앱, flow·friction·app-quality 화면, receipt count와 failure, exclusion recommendation, provenance·local fallback, shortcut audit, 좁은 overlay geometry를 수동으로 확인한다. 전체 strict/full suite와 Electron GUI 검증은 별도 미완료 게이트로 유지한다.
---
## 입력 인텔리전스 — 속도 진단 정정 + 토큰 스트리밍 (2026-09-22 09:40~09:55) ⚡
사용자 지적: "gemma4 로컬에서 엄청 빠르지 않나". **맞았고 내 이전 측정이 틀렸다.**
**재측정 (`ollama ps` / `/api/generate`, 이 머신)**
| 항목 | 값 |
|---|---|
| 로드 상태 | `gemma4:e4b` 3.2GB **100% GPU**, 컨텍스트 4096 |
| 단문 생성 | 126~137 tok/s |
| 제안형 프롬프트(400자 접두 + 64토큰) | wall **1.11초** (load 0.56 + prompt 0.06 + eval 0.48) |
→ 앞서 기록한 24.7초(콜드)/4.9초(32토큰)는 **Ollama 기본 `keep_alive` 5분 때문에 매 요청 모델을 다시 올린 비용**이었다. 내 측정 스크립트가 `load_duration` 을 분리하지 않아 지연 전체를 모델 탓으로 오진했다. (`keep_alive: 30m` 적용 후 부팅 워밍업 1.8~2.5초, 요청 ~1초.)
**UX 변경 (사용자 요청: "계속 생성되서 올라왔으면")**
- 트리거 1000 → **300ms** (Continue 350 / Tabby 250 / twinny 300 구간 복귀).
- **토큰 스트리밍**: 생성 중 도착하는 대로 오버레이에 흘려보낸다(120ms 간격 방출, `partialText`), 팝업은 깜빡이는 커서와 함께 부분 텍스트를 표시.
- **지속 갱신**: 접두가 12자 이상 자라면 다시 생성(`SUGGESTION_REGENERATE_GROWTH_CHARS`) — "한 번 뜨면 끝" 이 아니다.
- 토큰 상한 48, 몇 초 멈출 필요 없이 치면서 갱신된다.
**검증**: 유닛 70건 GREEN, lint/design clean, build 성공, `typecheck:strict` 로 이 작업 파일 오류 0건(main 13 / renderer 35 는 선재). 앱 09:52 재기동 + 워밍업 1.8초.
---
## 입력 인텔리전스 — 실사용 피드백 4건 수정 (2026-09-22 11:00~11:15) 🔧
**사용자 신고**
1. `Ctrl+Alt+화살표` 를 누르면 음성 전사가 뜬다 → 후보 탐색 불가
2. X를 눌러도 생성 중이면 안 꺼진다
3. 입력창을 클릭만 해도 제안이 만들어진다
4. 엔터로 입력을 끝낸 뒤에도 계속 제안한다
**원인과 수정**
- **AltGr 함정**: 오른쪽 Alt 는 Windows 가 Ctrl+Alt 로 보낸다. 저장된 바인딩이 `command = Ctrl + RightAlt` 였으므로 Ctrl+Alt+화살표에서는 **Alt 를 누르는 순간** command 가 발동해 음성 파이프라인이 돌았다. → 수정자만으로 끝나면서 Ctrl+Alt 가 함께 눌린 트리거는 280ms 보류하고, 그 사이 다른 키가 이어지면 취소(+ released 도 억제). 받아쓰기(Alt 단독)·Alt+Shift 계열은 모양이 달라 영향 없음.
- **닫기 무효화**: 진행 중이던 생성이 나중에 완료되며 결과를 다시 표시했다. → **생성 세대 토큰**을 두고, 닫으면 증가시켜 늦게 온 결과를 폐기.
- **클릭만으로 제안**: 백그라운드 샘플러가 마우스 이벤트에도 돌았다. 유휴 시간도 `_lastEventAt`(키+마우스) 기준이었다. → **키보드 기준(`_lastKeyAt`)** 으로 게이트.
- **엔터 후 반복**: 엔터 뒤 UIA 가 옛 텍스트를 돌려주면 같은 텍스트로 재요청했다. → **동일 접두 가드**(같은 텍스트는 다시 만들지 않음, 더 치면 자연히 새로 생성, 수동 "지금 제안 받기" 는 우회).
**함께**: 직전 배치에서 KeyBindingService 의 emit 블록에 중복 브레이스가 생겨 빌드가 깨졌던 것을 복구하고, AltGr 취소 키를 서비스 내부 bindingKey 와 일치시켰다(불일치 시 release 만 남아 음성이 오작동할 수 있었다).
검증: 데스크톱 유닛 73 + core 117 GREEN, lint clean, build 성공, 11:10 재기동.
---
## 입력 인텔리전스 — 실사용 성공 + 연속 샘플링 (2026-09-22 10:40~11:00) ✅
**사용자 피드백**: ①계속 치면 잘 안 나옴(백그라운드에서 부단히 돌아야) ②후보 2개는 적다, 5개+스크롤 ③워밍업/생성 중에도 반응이 보여야.
**근본 원인 (치명적)**: 스냅샷이 "타이핑을 멈춘 뒤"에만 예약돼 있어(디바운스 타이머가 키 입력마다 리셋), 에이전트 개발처럼 끊김 없이 치면 **샘플이 하나도 만들어지지 않았다**.
→ 최근 입력이 있는 동안 800ms 주기로 도는 **백그라운드 샘플러**를 추가.
**함께 고친 것**
- 후보 2 → **5개**, 토큰 48 → 128, 스트리밍 유지. 오버레이 높이 96 → 240 + 목록 내부 스크롤.
- **워밍업 스피너**: 모델 적재 중이면 오버레이에 스피너 + "모델 준비 중" 을 띄운다(무반응 = 고장으로 보이던 문제).
- **생성 스피너**: 후보 도착 전에도 스피너 + 도착한 부분 텍스트(깜빡이는 커서).
- 재생성 최소 간격 1500 → 900ms (지속 갱신 체감).
- 터미널: Windows Terminal 은 TextPattern 은 있으나 ${b('GetSelection()')} 이 0개라 "편집 불가" 로 판정되던 것을, TextPattern 존재 기준으로 변경.
- 통계 UI: inner tab(요약/키보드/마우스/앱/문구·제안) 재구성 + 막대 그래프를 축·라벨·고정폭으로 다시 구현(1일치 데이터에서 거대 사각형이 되던 버그). 통계는 **지식 베이스 > 입력 인사이트** 로 이동, 설정 > 입력은 동의·정책·진단만.
**검증 (10:57, 카톡 타이핑 중 실제 로그)**
- 스냅샷 67건/50초 · 제안 생성 3회 · 실패 0건 · 삭제 20건
- `제안 5개 생성 (1436ms, model=gemma4:e4b)` — 조합 중(comp=true, idle=25ms)에서도 생성됨
---
## 입력 인텔리전스 — 실기동 검증에서 잡은 결함 6건 (2026-09-22 09:00~09:20) 🔧
사용자가 실제로 앱을 띄우고 타이핑한 로그로 검증하면서, 유닛 테스트가 못 잡는 결함을 순서대로 잡았다.
**측정/관찰 (실제 로그)**
- UIA 자체는 정상: Notepad 는 edit=true src=value len=4000 anchor=yes, WindowsTerminal 은 not-editable 로 정확히 거부.
- KakaoTalk 은 legacy/len=0 → 이후 edit=true src=value 인데도 len=0 → 커스텀 렌더 앱은 텍스트를 노출하지 않는다(결함 아님).
- 제안 생성은 시도됐으나 "LLM generation cancelled" — 6초 제한이 워밍업 후 4.9초 요청까지 잘라냈다.
- 워밍업+keep_alive 적용 후 모델 재적재 2.5초(콜드 24.7초 대비).
**고친 결함**
1. settle 게이트 미개방(치명적): 트리거 지연(1000ms)이 스냅샷 디바운스(700ms)보다 커 "멈춘 뒤" 조건이 열리지 않았다 → settle 스냅샷 추가.
2. 부팅 시 워밍업 미실행: 이미 켜 둔 설정으로 켜면 워밍업이 안 돌았다(가용성 폴링 전 early return) → 부팅 워밍업 + 대기 재시도(2초×15).
3. 케어렛 폴백 부재: 오프셋이 없는 앱에서 문서 전체를 접두로 썼다 → tail 400자 + 진단 플래그.
4. 오버레이 지연 표시: 후보 도착 후에야 떠서 "아무것도 안 나옴" 으로 보였다 → 요청 즉시 "생성 중" 표시.
5. 느린 하드웨어 대응: 컨텍스트 600→400자, 토큰 96→64, 후보 3→2, 응답 제한 6→12초, 트리거 320→1000ms, keep_alive 30m.
6. 진단 불가: 왜 안 뜨는지 알 수 없었다 → 설정 > 입력에 실시간 진단 줄(포커스 앱·읽기 가부·소스·글자 수·비밀번호/케어렛 폴백), 12개 로케일 8키.
**검증 도구 주의**: 데스크톱 npm run typecheck 는 no-op(GAP-INFRA-04)이라 이번에도 "clean" 이 거짓이었다. typecheck:strict 로 이 작업이 만든 타입 오류 7건을 고쳤다(main 13 / renderer 35 는 전부 선재). 유닛 70건 GREEN.
**남은 검증**: 오버레이 위치·외관, Alt+Shift+← 수락 삽입, 비밀번호 필드 차단, 주간 수치 정확도, PyInstaller 번들에 uiautomation 포함 여부.
---
## 입력 인텔리전스 — 입력 수집 + 다음 문장 제안 (2026-09-21) ⌨️
"일본어 IME 처럼 다음에 칠 문장을 커서 옆에 제시" 요청. **뇌피셜 대신 조사 먼저** 지침으로 받았다.
**조사로 확정한 사실 (설계 근거)**
- 케어렛 위치: `GetGUIThreadInfo.rcCaret`(AutoHotkey `CaretGetPos` 방식)은 **Chromium/Electron/VS Code 에서 아무것도
돌려주지 않는다**(자체 커서 렌더). 정본은 UIA `TextPattern.GetSelection()` → `ExpandToEnclosingUnit(Character)` →
`GetBoundingRectangles()`. Chromium 은 `ITextProvider` 는 구현하지만 `ITextPattern2::GetCaretRange` 는 미구현.
- 입력창 읽기: `ValuePattern.Value` → `TextPattern.DocumentRange` → `LegacyIAccessiblePattern` 순서,
비밀번호는 `UIA_IsPasswordPropertyId(30019)` 로 **읽기 전에** 차단. 트리 전체 순회 금지
(PowerToys #46385: Chrome/VS Code UIA 트리 워크 10~30초).
- 한/일 IME: 키코드로 텍스트 복원은 **원리적으로 불가능**(조합 결과가 텍스트). 그래서 UIA 로 커밋된 텍스트를
읽어 **스냅샷 diff**(최장 공통 접두/접미)로 계산한다. 조합 중에는 통계·제안을 모두 억제한다.
- Node 후보 비교: `koffi` 3.3.1(N-API prebuild) 채택. `selection-hook`(선택 스트만·케어렛 없음),
`@crowecawcaw/xa11y`(caret rect 없음·IsPassword 미검사), `get-windows`(ESM + install script 필요 → 이 저장소 정책과 충돌),
`node-ffi-napi`(2021년 이후 방치) 배제. UIA COM vtable 을 JS 로 직접 다루는 것은 크래시 위험으로 배제.
- 입력 수집 정책: ActivityWatch `aw-watcher-input` 그대로 — `presses/clicks/deltaX/deltaY/scroll`, **키 내용 미저장**,
5초 heartbeat. 마우스 거리는 축별 절대값 합(맨해튼).
- 제안 타이밍: Continue 350 / Tabby 250(adaptive) / twinny 300 / minuet 400 ms, Zed p50<200ms,
출력 토큰 Tabby 64 / KeyType 4~16 → 기본 320ms·96토큰·후보 3개.
**구현**
- core SSOT `packages/core/src/input-intelligence.ts`(정책·집계·정제·앵커 전부 순수 함수), IPC 3그룹
(`INPUT_TELEMETRY`/`SUGGESTION`/`POPUP_SUGGESTION`), 키바인딩 션 3개(`suggestion-accept/next/dismiss`).
- 메인: `InputTelemetryService`(uiohook + koffi 포그라운드 창 + UIA diff 학습), `UiaContextService`(사이드카 브리지, fail-closed+백오프),
`SuggestionService`(디바운스→`buildSuggestionPrompt`→`streamGenerate`(호출자 시그널 취소)→수락 시 `TextInsertService` 삽입),
`global-input-hook.ts`(uiohook 참조 카운트 — KeyBindingService 와 공존).
- 사이드카: `uia_bridge.py` + `GET /uia/focus`(전용 COM 스레드, `uiautomation` 2.0.29). 실측 워 0~15ms / 최초 200~590ms.
- DB: `input_activity`(시간×앱), `typing_samples`, `personal_phrases`, `suggestions` + 30일 보존 정리.
- UI: 설정 > 입력 탭(동의·정책·주간 인사이트·문구 관리), 대시보드 주간 카드, `suggestion-overlay` 팝업(케어 앵커,
클릭 통과 기본).
- 검증: 유닛 48건(`input-intelligence.test.ts` 44 + `llm-prompts.test.ts` 4) GREEN, typecheck/lint/design GREEN,
Electron ABI 전체 실행에서 **신규 실패 0건**(베이스라인 366 failed/994 passed → 366 failed/1042 passed).
사이드카 `/uia/focus` 실제 호출 확인(200, 포커스 요소 반환).
**라이브 실측 (실제 Ollama + 프로덕션 프롬프트, 2026-09-21)**
- 프롬프트/품질은 정상: 한국어 "오늘 회의에서 논의한 내용을 정리해서" → "관련 자료와 함께 다시 한번 정리해서 보내드릴게요."
(후보 3개 전부 정제 통과), 영어도 자연스럽게 이어졌다.
- **속도가 예산 밖**: `gemma4:e4b` 96토큰 한도에서 **콜드 24.7초 / 워 4.9초(32토큰)**. Zed 목표 p90<500ms 와 두 자릿수 차이.
→ 완화 3종을 넣었다: 활성화 시 워밍업(1토큰), `keep_alive: 30m` (Ollama 기본 5분 → 재콜드로딩 방지), 응답 제한
(기본 6초, 설정 > 입력에서 조절, 초과 시 그 요청을 버리고 다음 타이핑 주기에 재시도). 설정 패널이 마지막 응답 시간을
보여주고 1.5초 초과면 경고해 사용자가 더 작은 모델(`suggestionModelId`)을 고를 수 있다. (GAP-INPUT-06)
**남은 것 (GAP-INPUT-01~06, GAP-LLM-03)**: 실 타이핑 검증(수락 삽입·오버레이 위치·비밀번호 차단),
PyInstaller 번들에 `uiautomation`/`comtypes` 포함 확인, IME preedit 미수집, 중국어 단어 수 과소, macOS/Linux 미지원.
---
## v1.3.3 — 배포본 시작 실패(네이티브 ABI) + gemma4 기본값 (2026-09-18) 🛠️
배포한 1.3.2 설치본이 **시작 즉시 죽었다**: `NODE_MODULE_VERSION 131 ... requires 130`.