Skip to content

Feasibility Studies

Woojin Kim edited this page Jun 23, 2026 · 2 revisions

타당성 검토 모음 (Feasibility Studies)

pytmux 에서 새 기능을 구현하기 전에, "그게 원리적으로 가능한가 / 어떤 방식이 견고한가"를 먼저 따져본 타당성 검토를 한곳에 모았다. 각 절은 검토한 질문 → 접근/실험 → 발견 → 판정(가능 / 불가능 / 보류) 순서로 핵심 기술 내용을 정리한다.


1. Claude Code 프롬프트 블록 선택(Shift+방향키/Home/End)

질문

pytmux 안에서 Claude Code 를 돌릴 때, Claude 의 프롬프트 입력기에 블록(범위) 선택 후 수정/삭제 편집(Shift+방향키·Home·End)을 pytmux 가 얹어줄 수 있는가? Claude Code 자체는 이 편집을 지원하지 않는다.

근본 제약 — 자식 입력기는 블랙박스

pytmux 는 Claude Code 를 패널의 자식 프로세스로 돌린다. pytmux 가 가진 것은:

  • 자식이 그린 화면 셀(파싱된 그리드)과 하드웨어 커서 셀 좌표
  • 자식에게 보내는 키 바이트(패스스루)

반대로 pytmux 가 갖지 못한 것:

  • 프롬프트의 논리 텍스트 버퍼(실제 문자열). 화면에 보이는 건 줄바꿈/와이드 문자/내부 가로 스크롤이 적용된 렌더 결과일 뿐, 원본 인덱스가 아니다.
  • 프롬프트 내 논리 커서 인덱스(몇 번째 문자인지). 커서의 셀 좌표만 안다.
  • 선택(selection) 상태 — Claude 내부에 그런 개념 자체가 없다.

블록 선택은 본질적으로 ①버퍼 내용 ②커서 인덱스 ③선택 범위에 대한 편집 연산을 요구하는데, pytmux 는 ①②를 직접 알 수 없다. 참고로 Shift+방향키 시퀀스(ESC[1;2D 등)는 이미 그대로 Claude 에 전달되고 있고, Claude 가 무시하는 것이다 — pytmux 가 막는 게 아니다.

접근 3가지

  • A. 투명 in-place 선택 — Claude 의 살아있는 프롬프트 위에서 Shift+방향키를 인터셉트해 자체 하이라이트를 그리고, 삭제/치환 시 커서이동 키 + Delete/Backspace 를 자식에 합성 전송.
  • B. 컴포즈 오버레이 — pytmux 자체 편집기(Textual TextArea 모달)에서 블록 선택으로 작성한 뒤, 결과를 bracketed paste(ESC[200~…ESC[201~)로 Claude 프롬프트에 통째 투입.
  • C. 최소 하이브리드 — 단일 행·내부 스크롤 없음이 확실할 때만 제한적으로 in-place 선택을 허용하고, 와이드 문자/wrap/스크롤이 감지되면 즉시 패스스루로 폴백.

발견

A 가 깨지는 이유가 본질적이다:

  1. 셀→논리 인덱스 매핑 불가능에 가까움 — 화면 셀 거리가 논리 문자 수와 1:1이 아니다 (와이드 문자 CJK 2셀, 이모지, 입력 줄바꿈, 프롬프트 박스 테두리, 긴 입력의 내부 가로 스크롤).
  2. Claude 가 받아주는 편집 키가 제한적 — Left/Right/Backspace/Delete·일부 워드이동 정도만 신뢰 가능. "선택 범위를 정확히 N자 지운다"를 키 합성으로 재현하기 어렵다.
  3. 비동기 재렌더·팝업 간섭 — 슬래시 명령 메뉴, @ 파일 자동완성, 모드 표시, 이미지 미리보기가 입력 위에 떠 화면 추정을 망가뜨린다.
  4. 잘못 편집 위험 — pytmux 가 "선택"을 흉내 내고 편집을 키로 번역하는 것이라, 한 글자라도 추정이 틀리면 사용자의 프롬프트가 조용히 잘못 편집된다.

반면 B 는 견고하다. 버퍼를 pytmux 가 소유하므로 셀↔인덱스 추정·키 합성이 불필요하고, 선택은 위젯이 정확히 처리한다. 투입 통로(bracketed paste)는 멀티라인 안전성이 이미 검증돼 있다. 대신 투명하지 않다(별도 작성창)는 점, 작성 중에는 Claude 인라인 기능(슬래시 메뉴·@ 자동완성 ·↑히스토리·모드 순환)을 못 쓴다는 한계가 있어 상시 개입이 아니라 옵트인이어야 한다.

기준 A 투명 in-place B 컴포즈 오버레이 C 최소 하이브리드
블록 선택 정확도 낮음(추정) 높음(위젯 네이티브) 중(제한적)
잘못 편집 위험 높음 낮음
Claude 인라인 기능 보존 부분 작성중 상실(옵트인) 부분
구현·검증 비용 큼·취약 중·견고
멀티라인/CJK/이모지 취약 견고 단일행 한정

판정 — 가능 (방식 B 채택)

B(컴포즈 오버레이)가 견고하게 구현 가능하며 권장안. A(투명 in-place)는 "프롬프트가 조용히 잘못 편집"될 위험이 본질적이라 명시적 비권장. 키 인터셉트 진입점은 클라이언트 코어에 최소로 추가하고, 작성창 UI 는 모달로 둔다.

구현 결과: 권고안 B 구현 완료. ESC 모드에서 Insert 키로 진입(옵트인) → ComposePromptScreen 모달(Textual TextArea, soft_wrap)에서 네이티브 블록 선택 편집. 키는 Claude Code 프롬프트와 동일하게 Enter=투입(전송)·Shift+Enter(또는 Ctrl+J)=줄바꿈으로 맞추고(Ctrl+S도 대체 투입), Esc=취소다. 선택은 Shift+방향키/Home/End(블록)와 Ctrl+A=전체 선택을 지원해 범위 삭제·교체 편집이 된다(자식 프롬프트엔 없는 편집). 투입은 자식이 bracketed paste 를 켰을 때 ESC[200~…ESC[201~ 로 감싸 멀티라인이 줄마다 제출되지 않게 하고, 끝에 Enter 를 붙이지 않아 자동 제출되지 않는다(사용자가 직접 Enter). 전용 테스트로 진입/투입/취소/네이티브 블록 선택 삭제·Ctrl+A 전체 선택을 커버하며 회귀 green.


2. 터미널 OSC 타이틀 → 패널 이름 자동 반영

질문

Claude Code 는 작업 맥락에 따라 터미널 창 제목을 OSC 2 타이틀 시퀀스로 동적으로 바꾼다 (예: ✳ · 이미지 파일에서 멈춤 현상 해결). 이 값을 pytmux 가 패널 단위로 캡처해 각 패널 이름(pane.title)에 자동 반영할 수 있는가?

접근 — 데이터 흐름 점검

파이프라인을 끝에서 끝까지 추적했다.

flowchart TD
    child["자식 프로세스 (Claude Code)"]
    pty["PTY 백엔드 (owned ConPTY / pywinpty / posix)"]
    feed["Pane.feed → VTTokenizer.feed → _finish_osc → screen.set_title()"]
    stitle["pane.screen.title (pyte.Screen 속성)"]
    ptitle["pane.title"]
    render["status/tree 메시지 → 클라이언트 패널 헤더/테두리 렌더"]

    child -->|"OSC 2 : ESC ] 2 ; title BEL 또는 ST"| pty
    pty -->|"raw 바이트 그대로 통과 (스트립 없음)"| feed
    feed --> stitle
    stitle -.->|"✗ 빠진 연결 (여기까지 '이미 동작')"| ptitle
    ptitle --> render
Loading

발견

파이프라인의 99% 가 이미 깔려 있다.

  • OSC 0/1/2 파싱은 native VT 파서가 이미 수행한다. OSC 0=icon+title, 1=icon, 2=title 을 파싱해 screen.set_title/set_icon_name 로 보내고 결과가 screen.title 에 저장된다. DoS 가드(OSC 본문 4096B 캡 + O(n) 벌크 스캔)도 회귀 테스트로 보장돼 있다.
  • 백엔드는 바이트를 스트립하지 않는다. owned ConPTY / pywinpty / posix 모두 raw 바이트를 그대로 파서에 넘긴다. ConPTY 가 타이틀 시퀀스를 자체 소비하지 않으므로 ESC ] 2 ; … BEL 이 파서까지 온전히 도달한다(라이브에서 창 제목이 바뀌는 게 그 증거).
  • 반대편 pane.title 은 이미 클라이언트로 흐른다 — status/tree 메시지로 전송되고 패널 헤더로 렌더되며, 재시작 복원(영속)·수동 rename-pane 도 갖춰져 있다.
  • 빠진 것은 screen.titlepane.title 동기화 한 줄뿐.

핵심 미묘점 — alt 화면 타이틀 수명

풀스크린 TUI(Claude Code 포함)는 대체 화면(alt screen, ?1049h) 에서 동작한다. native 파서는 alt 진입/이탈 시 화면 참조를 재지정하므로, set_title() 은 그 시점의 활성 화면에 얹힌다.

  • alt 안에서 설정된 타이틀은 alt 화면 인스턴스에 저장되고, alt 를 떠나면 그 인스턴스와 함께 타이틀도 사라진다.
  • Claude Code 는 세션 내내 alt 에 머무르므로 머무는 동안 pane.screen.title 로 읽을 수 있다.
  • 따라서 설계는 "타이틀 변경 콜백"이 아니라 활성 화면 타이틀을 주기적으로(30Hz) 스냅샷pane.title 로 복사하는 방식이어야 한다. 그래야 alt 가 뜯겨도 캡처한 이름이 패널 필드에 남는다. 동기화 소스는 항상 pane.screen.title(활성 화면)이어야 하며, main 화면만 보면 alt 타이틀을 놓친다.

판정 — 가능 (구현 권장, 비용 매우 작음)

완전 타당. 신규 표면은 "동기화 1개소 + 정책"으로 최소다.

  • 방식: 신규 플러그인 + server_scan 훅(feed 이후 30Hz)에서 pane.screen.title 을 읽어 pane.title 로 동기화(delete-to-disable). raw 바이트 훅이 아니라 server_scan 을 쓰는 이유는, raw 훅은 feed 이전이라 OSC 를 플러그인이 다시 파싱(코어와 중복/경합)해야 하지만 server_scan 은 이미 파싱된 속성을 읽기만 하면 되기 때문 — 핫패스 비용은 속성 읽기+비교뿐이다.
  • 정책: 기본 off 토글(영속), 수동 rename 우선(잠금 플래그로 OSC 동기화 건너뜀), 빈 타이틀은 무시(셸이 프롬프트 전후로 쏘는 빈 OSC 가 패널명을 지우지 않게), 40자 트림, "alt 활성 패널만 반영"으로 셸 노이즈를 좁히는 옵션, 변경 시에만 리드로 예약(떨림 방지).
  • 주의: alt 화면 타이틀 수명(위) 한 가지만 틀리면 Claude Code 타이틀을 못 잡거나 alt 이탈 시 잃는다.

3. Windows Docker 컨테이너로 Windows 박스 테스트 완전 자동화

질문

개발 머신(macOS, Apple Silicon)에서 Windows Docker 컨테이너를 구동해, "실 Windows 박스가 필요한" 테스트(owned-ConPTY 바이트 왕복, 자식종료→EOF 좀비 watcher 등)를 완전 자동화할 수 있는가?

컨테이너 한계의 배경 요약은 Why-pytmux-on-Windows 에도 정리돼 있다. 여기서는 그 한 줄을 뒷받침하는 정밀 근거와, "그래서 무엇으로 자동화하는가"의 결론만 다룬다.

접근 — 현재 머신 상태 실측

항목 의미
OS macOS (24G419)
CPU 아키텍처 arm64 (Apple Silicon) Windows 컨테이너 base 이미지(x86-64)와 불일치
Docker 서버 OSType linux Windows 컨테이너 못 띄움
Windows VM 도구 전부 미설치 로컬 Windows 실행 수단 0

docker version --format '{{.Server.Os}}/{{.Server.Arch}}'linux/arm64.

발견 — 세 가지 하드 블로커

  1. 컨테이너는 호스트 커널을 공유한다(에뮬레이션 아님). Windows 컨테이너는 Windows 커널을 공유하므로 Windows 호스트(또는 Windows 커널을 품은 VM) 위에서만 돈다. "Windows 커널 에뮬레이션" 같은 건 없어 macOS 커널 위에선 못 돈다.
  2. Docker Desktop/Mac 은 Linux VM 백엔드다. Mac 의 Docker 는 Apple Virtualization framework 로 Linux VM 을 띄워 그 안의 Linux 데몬과 통신하므로 측정값이 OSType: linux 다. Windows 판에만 있는 "Switch to Windows containers" 토글이 Mac 엔 존재하지 않는다.
  3. Apple Silicon 은 arm64, Windows 컨테이너 이미지는 x86-64. nanoserver/servercore/풀 windows base 이미지 매니페스트는 전부 amd64/windows 전용이다. 풀 windows:ltsc2019 실행을 시도하면 레이어 다운로드 전에 데몬이 즉시 거부한다: no matching manifest for linux/arm64/v8. 문제는 "어떤 이미지냐"가 아니라 "Windows 커널 호스트(+amd64)가 없다"는 것이다.

Windows 호스트가 있어도 — 헤드리스라 부분 검증만

설령 x86 Windows 박스에서 컨테이너를 돌려도, 컨테이너에는 인터랙티브 콘솔 세션이 없다(서비스/ 배치 격리 환경). pytmux 의 핵심 통증인 owned-ConPTY 바이트 왕복(CJK U+FFFD=0)은 실 콘솔이 있어야 자식 출력이 왕복하므로, 컨테이너는 헤드리스 러너와 같거나 더 나쁜 한계에 부딪힌다 — 우리가 진짜 자동화하고 싶은 항목에 이점이 0이다. (반면 자식종료→EOF 좀비 watcher 는 출력 왕복이 아니라 프로세스 종료 감지→콘솔 hangup→EOF 경로라 콘솔 없이도 검증된다.)

결정적 반전 — GitHub Actions windows-latest 가 이미 한다

두 검증을 GHA windows-latest 러너에 배선해 py3.11/3.12/3.13 전부 통과시켰다:

  • A. 자식종료→EOF 좀비 watcher — 실 ConPTY 로 green. cmd /c exit 자식을 실 conhost 로 띄워, 감시 스레드가 종료를 보고 콘솔 hangup→블로킹 read EOF→on_eof 발화를 확인. 그동안 수동 검증이던 원격 페더레이션 좀비 탭 회귀가 이제 매 푸시마다 자동 검증된다.

  • B. 바이트 왕복 + CJK 플러드 — GHA 에서 VERDICT: PASS (예상 밖 호재). owned 백엔드의 _ensure_hidden_console()(AllocConsole/SetStdHandle)가 GHA windows-latest VM 에서도 콘솔 왕복을 성립시켰다. "실 인터랙티브 콘솔이 필요"하던 바이트 왕복마저 헤드리스 러너에서 PASS:

    [2] input round-trip(+한글): 가나다=True fffd=0   -> OK
    [3] CJK flood: raw=480445 bytes  CJK(가)=20000  U+FFFD=0  -> OK
    VERDICT: PASS
    
방법 이 머신서 가능? 인터랙티브 콘솔 왕복 완전 자동화 평가
Windows Docker 컨테이너 ❌ (블로커 1·2·3) ❌ 헤드리스 불가·무이점
Windows VM (arm64) △ Win11 ARM64만, pywinpty arm64 휠 빌드 마찰 ❌ 수동/무거움 로컬 디버깅용만
자가호스팅 Windows 러너 △ 상시 박스 필요 ✅ but 중복 GHA 와 중복·관리비용
GHA windows-latest(현행) ✅ (클라우드) 실측 PASS 매 푸시 권위 자동화 경로

판정 — 불가능 (그리고 불필요), Docker-Windows 경로 종결

이 머신(arm64 macOS)에선 원리적으로 불가능하고, Windows 호스트에서도 헤드리스라 무이점이다. 대신 GHA windows-latest 가 실 Windows 자동화의 실현된 형태다 — 프로세스 수명(A)과 바이트 왕복(B)이 모두 green/PASS. B 스텝은 안전하게 non-blocking 으로 시작했고, 3개 파이썬 버전 PASS 가 안정 관찰되면 회귀 게이트(blocking)로 승격 후보다. 다만 진짜 인터랙티브 "반응성/느낌"과 실 Claude TUI(GHA 엔 Claude 바이너리가 없음)는 실박스 라이브 attach 가 권위로 남는다 — 이건 자동화 대상이 아니라 사람이 보는 항목이다.


관련 문서

Clone this wiki locally