-
Notifications
You must be signed in to change notification settings - Fork 0
Feasibility Studies
pytmux 에서 새 기능을 구현하기 전에, "그게 원리적으로 가능한가 / 어떤 방식이 견고한가"를 먼저 따져본 타당성 검토를 한곳에 모았다. 각 절은 검토한 질문 → 접근/실험 → 발견 → 판정(가능 / 불가능 / 보류) 순서로 핵심 기술 내용을 정리한다.
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 가 막는 게 아니다.
- 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이 아니다 (와이드 문자 CJK 2셀, 이모지, 입력 줄바꿈, 프롬프트 박스 테두리, 긴 입력의 내부 가로 스크롤).
- Claude 가 받아주는 편집 키가 제한적 — Left/Right/Backspace/Delete·일부 워드이동 정도만 신뢰 가능. "선택 범위를 정확히 N자 지운다"를 키 합성으로 재현하기 어렵다.
-
비동기 재렌더·팝업 간섭 — 슬래시 명령 메뉴,
@파일 자동완성, 모드 표시, 이미지 미리보기가 입력 위에 떠 화면 추정을 망가뜨린다. - 잘못 편집 위험 — pytmux 가 "선택"을 흉내 내고 편집을 키로 번역하는 것이라, 한 글자라도 추정이 틀리면 사용자의 프롬프트가 조용히 잘못 편집된다.
반면 B 는 견고하다. 버퍼를 pytmux 가 소유하므로 셀↔인덱스 추정·키 합성이 불필요하고,
선택은 위젯이 정확히 처리한다. 투입 통로(bracketed paste)는 멀티라인 안전성이 이미 검증돼 있다.
대신 투명하지 않다(별도 작성창)는 점, 작성 중에는 Claude 인라인 기능(슬래시 메뉴·@ 자동완성
·↑히스토리·모드 순환)을 못 쓴다는 한계가 있어 상시 개입이 아니라 옵트인이어야 한다.
| 기준 | A 투명 in-place | B 컴포즈 오버레이 | C 최소 하이브리드 |
|---|---|---|---|
| 블록 선택 정확도 | 낮음(추정) | 높음(위젯 네이티브) | 중(제한적) |
| 잘못 편집 위험 | 높음 | 낮음 | 중 |
| Claude 인라인 기능 보존 | 부분 | 작성중 상실(옵트인) | 부분 |
| 구현·검증 비용 | 큼·취약 | 중·견고 | 중 |
| 멀티라인/CJK/이모지 | 취약 | 견고 | 단일행 한정 |
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.
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
파이프라인의 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.title→pane.title동기화 한 줄뿐.
풀스크린 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 이탈 시 잃는다.
개발 머신(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.
- 컨테이너는 호스트 커널을 공유한다(에뮬레이션 아님). Windows 컨테이너는 Windows 커널을 공유하므로 Windows 호스트(또는 Windows 커널을 품은 VM) 위에서만 돈다. "Windows 커널 에뮬레이션" 같은 건 없어 macOS 커널 위에선 못 돈다.
-
Docker Desktop/Mac 은 Linux VM 백엔드다. Mac 의 Docker 는 Apple Virtualization framework
로 Linux VM 을 띄워 그 안의 Linux 데몬과 통신하므로 측정값이
OSType: linux다. Windows 판에만 있는 "Switch to Windows containers" 토글이 Mac 엔 존재하지 않는다. -
Apple Silicon 은 arm64, Windows 컨테이너 이미지는 x86-64.
nanoserver/servercore/풀windowsbase 이미지 매니페스트는 전부amd64/windows전용이다. 풀windows:ltsc2019실행을 시도하면 레이어 다운로드 전에 데몬이 즉시 거부한다:no matching manifest for linux/arm64/v8. 문제는 "어떤 이미지냐"가 아니라 "Windows 커널 호스트(+amd64)가 없다"는 것이다.
설령 x86 Windows 박스에서 컨테이너를 돌려도, 컨테이너에는 인터랙티브 콘솔 세션이 없다(서비스/ 배치 격리 환경). pytmux 의 핵심 통증인 owned-ConPTY 바이트 왕복(CJK U+FFFD=0)은 실 콘솔이 있어야 자식 출력이 왕복하므로, 컨테이너는 헤드리스 러너와 같거나 더 나쁜 한계에 부딪힌다 — 우리가 진짜 자동화하고 싶은 항목에 이점이 0이다. (반면 자식종료→EOF 좀비 watcher 는 출력 왕복이 아니라 프로세스 종료 감지→콘솔 hangup→EOF 경로라 콘솔 없이도 검증된다.)
두 검증을 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 | ✅ 매 푸시 | 권위 자동화 경로 |
이 머신(arm64 macOS)에선 원리적으로 불가능하고, Windows 호스트에서도 헤드리스라 무이점이다.
대신 GHA windows-latest 가 실 Windows 자동화의 실현된 형태다 — 프로세스 수명(A)과 바이트
왕복(B)이 모두 green/PASS. B 스텝은 안전하게 non-blocking 으로 시작했고, 3개 파이썬 버전 PASS
가 안정 관찰되면 회귀 게이트(blocking)로 승격 후보다. 다만 진짜 인터랙티브 "반응성/느낌"과
실 Claude TUI(GHA 엔 Claude 바이너리가 없음)는 실박스 라이브 attach 가 권위로 남는다 —
이건 자동화 대상이 아니라 사람이 보는 항목이다.
소개 · 사용
- Project-Overview
- User-Manual
- Screenshots
- Settings-Popup
- Single-Session-Model
- Tmux-Feature-Comparison
플러그인
Claude Code 플러그인
- Claude-Code-Plugins
- Dev-Guide-claude-code
- Dev-Guide-claude-token-usage-view
- Dev-Guide-claude-prompt-history
- Dev-Guide-claude-resume
- Dev-Guide-claude-disable-feedback
플랫폼 · 성능
품질 · 보안
리뷰·분석 보고서
연혁
기여