Skip to main content
CHANGELOG

What shipped

Released versions only, generated at build time from the product Keep a Changelog file. Unreleased work is not listed here: if it is not in a version heading, it has not shipped.

Latest released v3.23.4 · · 61 releases

Currently downloadable v3.25.2-preview.1 · preview channel

Those are two different questions. This page lists versions that have been released, from the product changelog. The download page serves whatever the live preview channel points at, which moves ahead of the last released version with every build. Download the current build from /download. Channel and exact version always come from the live release manifest.

Releases 51–55 of 61

Page 11 of 13

v2.8.4

Agent Notification Pipeline Restoration

사용자가 보고한 "Claude 가 작업을 끝내도 사이드바 dot, unread 배지, OS 토스트 — 3가지 신호 전부 안 뜬다" 결함을 root-cause 수준에서 복구. main 의 감지 레이어 (PTYBridge, AgentDetector, ActivityMonitor) 가 emit 하는 신호를 renderer UI 까지 연결하는 wiring 이 4 군데 끊겨 있었고, wmux production 인 daemon mode 에서는 PTYBridge 가 아예 우회되어 본 fix 가 0 효과 라는 더 큰 결함도 포함. 메인은 PR #30 (4 commits, +1579/-141, 29 files) 이고, 같은 릴리즈에 두 개의 다른 PR — #28 (@dev-minggyu, workspace drag reorder 복구 — 외부 기여 첫 컨트리뷰션) 과 #29 (multiview sticky group + MiniSidebar feature parity) — 도 함께 ship 됐다.

Fixed

  • Workspace 드래그 정렬이 동작하지 않던 결함 (#28, @dev-minggyu — 외부 기여 첫 컨트리뷰션) — 좌측 사이드바의 전역 파일-드롭 핸들러가 내부 워크스페이스 드래그 이벤트까지 OS 파일 드롭처럼 처리하면서 move 드래그가 충돌해 정렬이 막혀 있었다. 신규 src/shared/dragDrop.ts 헬퍼가 DataTransfer 가 실제 OS 파일 드래그인지 판별, 전역 드롭 핸들러와 오버레이가 파일 드래그에만 반응하도록 제한. 내부 text/plain 드래그 회귀 테스트 21 라인 추가.
  • Multiview sticky group + MiniSidebar feature parity (#29) — 사용자가 보고한 multiview 3개 결함을 묶어 수정. (a) Ctrl-click 순서 무시되고 grid 가 항상 workspace 배열 순서로 렌더되던 결함 → AppLayout 이 multiviewIds 자체를 iterate 해서 Ctrl-click 순서 보존. (b) 그룹 밖 workspace 를 plain-click 하면 그룹이 통째로 사라지던 결함 → setActiveWorkspace 가 multiviewIds clear 안 함 + activeWorkspaceId ∈ multiviewIds 일 때만 grid 렌더 (그룹 외부 클릭 시엔 단일 view, 멤버 재클릭 시 grid 복구). (c) 접힌 사이드바 (MiniSidebar) 가 multiview indicator / drag-reorder / W1·W2 라벨 / unread 배지 / agent dot 전부 없던 결함 → 펼친 사이드바와 동일 기능 부여, AGENT_STATUS_ICON 을 Sidebar/agentStatusIcon.ts 로 추출해 두 사이드바 lockstep. Codex review 가 잡은 reseed 결함 (stale 그룹에서 새 multiview 시작 시 Ctrl-click 무반응) 도 함께 수정. +5 multiview 회귀 테스트.
  • AgentDetector status event 가 아무에게도 listen 되지 않던 결함 — src/main/pty/PTYBridge.ts:207 가 agentDetector.onCritical 만 구독하고 onEvent 는 dead code. Claude/Codex/Aider 의 "esc to interrupt" / "shift+tab to cycle" / "Applied edit to" 같은 정확한 prompt 패턴은 감지되어 emit 되었지만 호출되는 콜백이 0 개라 사이드바 dot 이 영영 켜지지 않았다. PTYBridge 가 onEvent 도 구독하도록 추가, IPC.METADATA_UPDATE 로 agentStatus/agentName broadcast + sendNotification 호출.
  • IPC.NOTIFICATION payload shape 가 sender 마다 달라서 외부 RPC 알림이 깨지던 결함 — PTYBridge 는 (channel, ptyId, notification) 3-arg, notify.rpc.ts 는 (channel, { title, body, type }) 1-arg. preload notification.onNew 는 3-arg signature 라 RPC path 의 첫 인자가 ptyId 자리로 들어가 payload 가 silent 하게 깨졌다. 새 sendNotification utility (src/main/notification/sendNotification.ts) 가 단일 (window, ptyId|null, payload) contract 로 통일.
  • IPC.METADATA_UPDATE 가 두 sender 사이에 shape 불일치였던 결함 — metadata.handler 는 (ptyId, data) 2-arg, meta.rpc 는 (payload) 1-arg 로 같은 채널에 송신. 한 path 가 정상 동작하는 동안 다른 path 가 silent 하게 깨졌다. MetadataUpdatePayload (src/shared/types.ts) 를 단일 discriminated payload 로 정의, broadcastMetadataUpdate utility 로 모든 sender 통일. meta.rpc 의 {kind: 'status'|'progress'} discriminator 폐기, workspace-level field 로 직접 매핑.
  • WorkspaceMetadata.agentStatus 가 자동으로 'idle' 로 복귀하지 않던 결함 — 'waiting'/'complete'/'running' 이 한 번 set 되면 lifecycle reset 없음. 사용자 입력 후 agent 가 다시 실행되어도 dot 은 'waiting', PTY 가 죽어도 dot 은 'running' 으로 남는 거짓말 발생. ActivityMonitor 의 새 onActive 콜백이 burst 진입 시점에 'running' 설정, PTYBridge.onExit 가 'idle' broadcast, cleanupInstance 도 dispose path 에서 동일하게 broadcast (idempotent). renderer 의 AppLayout 가 session restore 직후 모든 workspace 의 stale agentStatus 를 sanitize.
  • Daemon mode 에서 알림 wiring 이 통째로 빠져 있던 결함 (production blocker) — wmux 의 production normal 은 daemon mode. PTY output 은 DaemonPTYBridge 를 통과하고 PTYBridge 는 우회된다. DaemonPTYBridge 가 이미 'agent'/'critical'/'idle' event 를 emit 하고 있었지만 DaemonSessionManager 는 'idle' 만 forward, daemon/index.ts 는 'activity.idle' 만 broadcast, DaemonClient 는 'session.died' 만 specific emit. 즉 local mode fix 만으로는 사용자 환경에서 0 효과. 신규 DaemonNotificationRouter (src/main/notification/DaemonNotificationRouter.ts) 가 daemon broadcast event 5 종 (session:agent/active/critical/idle/died/destroyed) 을 listen 해서 PTYBridge 와 동일한 로직 실행. DaemonEvent type 에 'activity.active' + 'session.destroyed' 추가, daemon/index.ts 가 신규 type 모두 broadcast, DaemonClient 가 specific emit. daemon 측 AgentDetector 의 dedup state 도 onActive burst 시점에 in-process 로 reset (main 에서 daemon process 의 detector 에 접근 불가하기 때문).
  • PTY echo / SIGWINCH redraw 가 false-positive idle 알림을 유발하던 결함 (사용자 발견) — 7-round review pipeline (CEO + Eng + Codex × 4 + Claude subagent) 가 catch 못 한 케이스. ActivityMonitor 는 byte count 휴리스틱이라 "agent task ending" 과 "외부 상태 변화로 인한 PTY redraw" 를 구분 못 함. (a) 사용자 keystroke 가 PTY echo 로 돌아와 active threshold 를 넘기고 잠시 멈추면 "Task may have finished" 가 사용자 입력 중에 발화. (b) workspace 전환 시 FitAddon.fit() → IPC.PTY_RESIZE → SIGWINCH → TUI agent 의 full-screen redraw 가 active 진입 → 5s 후 idle timer 발화. 신규 idleSuppression 모듈 (src/main/notification/idleSuppression.ts) 이 lastResizeAt/lastUserWriteAt 을 per-ptyId 로 추적, 30 s window 내면 activity-fallback 알림 suppress. AgentDetector 의 precise event 는 gate 안 함 (정확한 신호이므로). pty.handler.ts 의 4 path (write × 2 + resize × 2) 가 markResize/markUserWrite 호출. 사용자가 보고한 "타자 치는 중 알람" + "워크스페이스만 눌렀다가 다른 곳 가면 +1" 두 시나리오 모두 해결.
  • 사용자가 보고 있는 surface 에도 알림이 누적되던 결함 — useNotificationListener 가 active workspace 의 active surface 일치 여부 체크 없이 무조건 addNotification + pushToast 호출. 사용자가 직접 보고 있는 곳은 알림 의미 0 인데 unread 배지가 계속 올라갔다. 알림 발생 직전 isActivePtySurface 체크 → 일치하면 in-app surface (addNotification + pushToast) skip. OS toast 는 ToastManager 가 자체 focus gate 가지고 있어 변경 없음.
  • workspace 전환만으로는 unread 가 read 처리 되지 않던 결함 — 사용자 보고: "워크스페이스만 눌러서 들렀다가 다른 곳 가면 unread 가 +1." Pane click 만이 markRead 트리거였고 sidebar 의 workspace 타일 click 은 read 영향 0. workspaceSlice.setActiveWorkspace action 이 해당 workspace 의 모든 unread 를 read 로 자동 처리하도록 변경. Array.isArray(state.notifications) 가드로 workspaceSlice 단독 테스트 호환.
  • pushToast 가 사용자 toast 설정 무시하던 결함 — useNotificationListener 가 settings 의 toastEnabled 무시하고 매번 in-app overlay 띄움. 사용자가 "Toast notifications" 끄면 OS toast 만 suppress, in-app 은 그대로 표시되던 결함. state.toastEnabled gate 추가 (sound playback 패턴과 동일).
  • AgentDetector 의 Claude esc to interrupt 가 false-positive 'waiting' — 실제로는 "지금 response 가 진행 중, ESC 로 중단 가능" 힌트이지 idle 신호가 아니다. 패턴 제거. mid-turn 에 잘못된 알림 fire 차단.
  • AgentDetector enum 명명 불일치 — AgentEvent.status: 'completed' vs WorkspaceMetadata.agentStatus: 'complete'. AgentStatus enum 으로 통일 (Aider 패턴 'completed' → 'complete' 텍스트 변경 포함). 외부 consumer 없어 안전.
  • AgentDetector dedup 이 turn N+1 의 같은 prompt 를 영영 차단하던 결함 — lastEmittedKey 가 single global string 이라 한 번 emit 한 prompt 는 다시 emit 안 됨 → 사용자가 추가 입력해도 사이드바 dot 갱신 0. lastEmittedFor Map 으로 per-(agent:status) 분리 + resetEmissionState() method 추가, ActivityMonitor 의 새 active burst 시점에 reset (turn boundary). local mode 는 PTYBridge 가 직접 호출, daemon mode 는 DaemonPTYBridge.onActive 콜백이 in-process 에서 호출.
  • AgentDetector 의 ANSI strip 이 private-mode prefix 를 못 잡던 결함 — \x1b[?25h 같은 cursor visibility 시퀀스 (? 포함) 가 [0-9;]*[a-zA-Z] regex 와 안 맞아 clean 에 잔존, gate 매칭 실패 가능. [0-9;?<=>]*[a-zA-Z@] 로 확장.
  • AgentDetector 가 lone \r redraw 를 한 라인으로 처리하던 결함 — Claude/Codex TUI footer 는 CR 단독으로 redraw. split(/\r?\n/) 가 통째로 묶어 line-anchored regex 가 매칭 실패. split(/\r?\n|\r(?!\n)/) 로 확장.
  • AgentDetector.onEvent/onCritical 이 unsubscribe 안 돌려주던 결함 — void 반환이라 PTY recycle 시마다 listener 누적. v2.7.2 의 PlaywrightEngine CDP 세션 누수와 동일 카테고리. unsubscribe 함수 반환으로 변경, PTYBridge cleanupInstance + DaemonPTYBridge cleanup 에서 호출. ActivityMonitor 의 onActiveToIdle/onActive 도 같은 패턴.
  • AgentDetector callback 내부 throw 가 후속 라인 감지를 죽이던 결함 — PTYBridge middleware 패턴과 일치시켜 onEvent/onActive 콜백 본문에 try/catch 가드 추가. 한 callback 의 실패가 PTY stream 전체를 죽이지 않게 격리.
  • AGENT_EVENT_SUPPRESSION_MS 로 ActivityMonitor 의 fallback 알림 dedup — AgentDetector 가 precise event emit 직후 ActivityMonitor 가 또 idle 발화하면 같은 turn 에 알림 2 회. PTYBridge / DaemonNotificationRouter 가 lastAgentEventAt 추적, 10 s 이내면 fallback skip.
  • notify RPC 가 workspaceId 없이는 깨지던 결함 — preload signature 가 ptyId: string 강제, addNotification 이 surfaceId 강제. RPC path 는 ptyId 가 없어 silent drop 되거나 type error. workspaceId optional 로 변경 (CLI wmux notify backward compat 유지), Notification.surfaceId optional, useNotificationListener 가 null ptyId 면 workspaceId 로 active surface resolve (or active workspace fallback).

Added

  • sendNotification utility (src/main/notification/sendNotification.ts) — 모든 IPC.NOTIFICATION 송신의 단일 entry point. window null/destroyed 가드 + (ptyId | null, payload) 시그니처 통일. PTYBridge 4 호출 지점 + notify.rpc + DaemonNotificationRouter 모두 import.
  • broadcastMetadataUpdate utility (src/main/ipc/handlers/metadata.handler.ts) — 모든 IPC.METADATA_UPDATE 송신의 단일 entry point. MetadataUpdatePayload 단일 shape.
  • idleSuppression 모듈 (src/main/notification/idleSuppression.ts) — per-PTY resize/user-write 시점 추적. 30 s suppression window 로 ActivityMonitor 의 byte-count heuristic false-positive 차단.
  • DaemonNotificationRouter (src/main/notification/DaemonNotificationRouter.ts) — daemon mode 에서 PTYBridge 의 알림 라우팅 역할 대체. DaemonClient event 5 종 listen → sendNotification + broadcastMetadataUpdate + toast.
  • AgentDetector 의 in-process API 확장 — getActiveAgents() / getLastAgent() / resetEmissionState() public method 추가. PTYBridge 가 lastAgent name 을 onActive metadata 에 채워 넣을 수 있게.
  • 37 신규 unit test — AgentDetector.test.ts (18, enum/unsubscribe/dedup/\r split/ANSI strip/getters/critical), ActivityMonitor.test.ts (+4, onActive cycle dedup), sendNotification.test.ts (4, null/destroyed/ptyId 분기), PTYBridge.notify.test.ts (5, METADATA_UPDATE + NOTIFICATION + try/catch + cleanup unsub), notify.rpc.test.ts (6, workspaceId optional + MCP path + type fallback + toast). IRON RULE 7 regression 중 6 cover, R7 (pushToast in renderer) 는 jsdom 필요해 manual.

Migration Notes

  • 자동. 사용자 액션 불필요.
  • Notification.surfaceId 를 optional 로 변경 — Pane.tsx 의 surfaceIds.has(n.surfaceId) 에 undefined guard 추가됨. 다른 consumer 없음.
  • AgentEvent.status enum 변경 ('completed' → 'complete') — wmux 내부에서 PTYBridge onCritical 만 consume 했고 onEvent 는 dead code 였으므로 외부 영향 없음.
  • IPC.METADATA_UPDATE payload shape 통일 — preload metadata.onUpdate 시그니처가 (payload) 단일 인자로 변경. renderer 의 useNotificationListener 가 호환 처리. 외부 MCP / CLI consumer 영향 없음.
  • notify RPC 의 workspaceId 는 optional 신규 param. CLI wmux notify --title X --body Y 는 그대로 동작. MCP 클라이언트가 mcp.claimWorkspace 의 workspaceId 를 함께 보내면 precise routing (active surface auto-select).

Deferred (follow-up issues)

  • DaemonNotificationRouter regression test suite — manual verification 으로 cover, daemon IPty pipeline mock 은 별도 작업.
  • session-restore sanitize regression test — session fixture builder 필요.
  • onExit elapsed=0 cosmetic (cleanupInstance 가 ptyCreatedAt 먼저 wipe 하는 path) — purely message-text, behavioural 영향 0.
  • DaemonClient.removeAllListeners on disconnect — pre-existing, 본 PR 범위 외.
  • TODOS.md 에 cherry-picked deferral 추가: E3 (transient dot flash animation, P3), E4 (per-workspace notification mute, P2), E5 (tray icon unread badge — cross-platform, P2), Phase 2 Eureka (Claude Code stop-hook → OSC 9 BEL emit, P3).

Review Trail

Pass Reviewer Findings Status
Plan 1 /plan-ceo-review 5 proposals SELECTIVE_EXPANSION, 2 accepted
Plan 1 Codex round 1 10 all addressed
Plan 1 /plan-eng-review 11, 1 critical all addressed
Plan 1 Codex round 2 8 all addressed (daemon mode wiring 6 파일 추가)
Code 2 Codex round 3 2 (P1+P2) all addressed in 5aee27f
Code 3 Codex round 4 3 (P2+P2+P3) all addressed in cddd3bd
Code 3 Claude subagent 7 (P2+P2+P3×5) 2 addressed, 5 deferred
Code 4 사용자 manual test 2 (resize/typing FP) addressed in 42f5bd3

7-round review pipeline 의 한계: AI review 가 PTY echo / SIGWINCH redraw 같은 runtime 동작 은 코드만 보고 모델링하기 어렵다. 사용자 manual test 가 마지막 안전망이 됐다는 점이 기록 가치 있음.

v2.8.3

License Bundling + Third-Party Notices Attribution

wmux 빌드 산출물에 부족했던 attribution 의무를 정리한 patch. THIRD_PARTY_NOTICES 가 Playwright 하나만 적혀 있었지만 실제 runtime 번들은 110 packages (16 직접 deps + Electron + ~93 transitive) 를 포함하고 있었다. MIT/ISC/BSD/Apache-2.0 의 "all copies or substantial portions" 조항을 모두 충족하도록 재구성. 코드 동작 변경 없음 — 사용자 가시 변경은 tray 메뉴에 라이선스 진입점 3 개 신설.

Added

  • 자동 생성 스크립트 scripts/generate-notices.mjs — npm run notices 로 production deps tree 전체를 walk 해서 THIRD_PARTY_NOTICES 를 재생성한다. 외부 의존성 0 개 (npm ls --prod --all --json + node:fs 만 사용). 추가 install 없이 CI 에서도 그대로 실행 가능. dependency 변경 시 즉시 갱신.
  • Tray 컨텍스트 메뉴 라이선스 진입점 3 개 — About wmux (네이티브 About 패널), License (wmux) (MIT 본문 직접 열기), Third-party licenses (THIRD_PARTY_NOTICES 직접 열기). shell.openPath 로 OS 기본 텍스트 앱에서 열고, 연결된 앱 없으면 showItemInFolder fallback. 그동안 wmux 는 application menu 자체가 없어서 사용자가 라이선스 파일에 도달할 경로가 0 이었다.
  • app.setAboutPanelOptions — 네이티브 About 다이얼로그에 wmux 버전 / MIT copyright pointer / project URL metadata 설정. macOS 는 앱 메뉴에서 자동 표시, Windows/Linux 는 신규 tray 항목 "About wmux" 가 트리거.

Fixed

  • THIRD_PARTY_NOTICES 의 109 packages 누락 — 이전 파일은 Playwright 1 개만 적혀 있어 사실상 MIT/ISC/BSD/Apache-2.0 attribution 의무 (carry copyright notice in "all copies") 가 부분 미준수 상태였다. 자동 생성으로 110 packages 모두 채움. 라이선스 분포: 98 MIT, 7 ISC, 2 Apache-2.0 (electron-squirrel-startup, playwright-core), 2 BSD-3-Clause, 1 BSD-2-Clause. Zero copyleft, zero unknown — 재배포 권리 위험 0.
  • wmux 자체 LICENSE 가 빌드 산출물에 누락 — forge.config.ts 의 extraResource 에 ./LICENSE 추가. 빌드 후 <install>/resources/LICENSE 에 위치하여 wmux 의 MIT 본문도 exe distribution 과 함께 carry. (Electron 본체 LICENSE — Chromium / V8 / Node 커버 — 는 electron-packager 가 install root 의 wmux.exe 옆에 자동 emit, 이미 충족됨.)

Migration Notes

  • 자동. 사용자 액션 불필요. 외부 MCP 통합 측에 변경 없음. 빌드 자체에 영향 없는 데이터 + UI 보조 작업.

v2.8.2

Session Cap Headroom + Silent-Failure Fix

@alphabeen 이 v2.8.1 출시 직후 PR #25 로 보고한 두 문제를 한 patch 에 묶는다. v2.8.1 의 startup brick 픽스 이후에도 runtime accumulation 시나리오 (X close 후 daemon 이 유지하는 detached 세션이 며칠에 걸쳐 누적) 에서는 hard cap 50 에 다시 도달했고, 더 나쁜 건 cap throw 가 renderer 의 Ctrl+T 핸들러에서 silent 하게 묻혀 단축키가 무반응처럼 보이던 결함이다. v2.8.1 사용자는 즉시 업그레이드 권장.

Fixed

  • 데몬 세션 hard cap 50 → 200 상향 — #25, @alphabeen. v2.8.0 의 세션 영속화 이후 cap 의 의미가 "한 세션 동안 최대 동시 PTY" → "lifetime 누적 detached PTY 총합" 으로 바뀐 결과, multi-workspace + 빈번한 split 사용자는 며칠 내 50 에 재도달. 50 자체는 commit 989dd8a 의 보안 하드닝 단계에서 정한 DoS 휴리스틱이었고 200 도 같은 카테고리 안. soft cap 40 (recovery) / 7-day suspended TTL 정책은 무변경. 헤드룸 10 → 160. 근본 해결 (orphan detached GC) 은 v2.9 트랙으로 별도 검토. 구현: src/daemon/DaemonSessionManager.ts, src/daemon/index.ts 주석 동기화.
  • pty.create rejection 이 묻혀 단축키 무반응처럼 보이던 회귀 — @alphabeen 이 PR #25 description 에서 짚어준 두 번째 문제. cap 도달 시 daemon 이 actionable 에러 (Cannot create new terminal: 200 active sessions already running. Close some panes (or restart wmux) and try again.) 를 throw 하는데 renderer 의 세 호출 지점 (useKeyboard Ctrl+T 핸들러 / AppLayout empty-leaf 자동 PTY / FloatingPane 첫 열림) 모두 .then() 만 달고 .catch() 누락 (또는 silent catch) 이라 rejection 이 묻히고 단축키가 무반응처럼 보였다. v2.8.1 Bug 1 의 actionable error 의도가 무력화되던 결함.
    • 신규 IPC 에러 코드 RESOURCE_EXHAUSTED — wrapHandler 의 classifyError 가 cap 메시지 패턴 (cannot create new terminal + active sessions already running) 을 감지해 분류. 메시지에 [RESOURCE_EXHAUSTED] prefix 가 stamp 되어 renderer 가 분기 가능.
    • useIpc 매핑 — DEFAULT_MESSAGES['RESOURCE_EXHAUSTED'] = "터미널 세션 한도에 도달했습니다. 일부 pane을 닫거나 wmux를 재시작한 뒤 다시 시도해주세요.", level 'warn'. UNKNOWN 으로 매핑되어 generic "알 수 없는 오류" 토스트가 뜨던 path 차단.
    • 세 호출 지점 모두 ipcInvoke wrap 으로 통일 — useKeyboard Ctrl+T (ref 패턴으로 once-on-mount effect 안에서 사용), AppLayout empty-leaf 자동 PTY effect, FloatingPane 첫 PTY 생성. 모두 result.ok 분기 + 실패 시 toast 자동 게재.
    • Electron invoke envelope wrap 처리 — codex P2 review 에서 잡힌 결함. ipcRenderer.invoke 가 main side 에러를 renderer 로 전달할 때 메시지를 Error invoking remote method 'X': Error: <orig> 형태로 감싸서, useIpc 의 MESSAGE_CODE_PREFIX 가 ^ anchor 였던 탓에 [RESOURCE_EXHAUSTED] stamp 가 envelope 뒤로 밀려 매칭 실패 → 모든 coded error 가 다시 UNKNOWN 으로 떨어지던 path 차단. renderer regex 만 anchor 제거 (main side 는 자기 raw output 매칭이라 anchor 유지). 알phabeen 이 PR #25 description 에서 짚어준 결함이 두 번 일어나지 않도록 회귀 테스트 추가.
    • 구현: src/main/ipc/wrapHandler.ts, src/renderer/hooks/useIpc.ts, src/renderer/hooks/useKeyboard.ts, src/renderer/components/Layout/AppLayout.tsx, src/renderer/components/Terminal/FloatingPane.tsx. 6 unit tests 추가 (wrapHandler RESOURCE_EXHAUSTED classification + message prefix stamping + useIpc default 매핑 + Electron-wrapped envelope classification).

Migration Notes

  • 자동. 클라이언트 / 외부 MCP 통합 측에 변경 없음. 신규 RESOURCE_EXHAUSTED 코드는 내부 IPC 경계 안쪽에서만 사용 (renderer ↔ main).

v2.8.1

Session Recovery Stability Hotfix

@alphabeen 이 v2.8.0 출시 직후 보고한 세 가지 회귀 — 시간이 갈수록 wmux 가 사용 불가 상태로 빠지던 critical, recovered pane 출력이 깨지던 high, 매 시작마다 generic 에러 토스트가 뜨던 medium — 을 한 릴리스에 묶어 수정한다. v2.8.0 사용자는 즉시 업그레이드 권장 — 자동 마이그레이션이 누적된 sessions.json 을 첫 실행 시 정리한다.

Fixed

  • 세션 누적으로 인한 brick 상태 (Critical) — v2.8.0 에서 도입된 데몬 세션 영속화는 사용자가 X 로 종료한 모든 live pane 을 suspended 로 저장하고 다음 시작 시 복구한다. 그런데 (1) 복구 횟수에 상한이 없었고, (2) 종료 시점에 사용자가 명시적으로 닫지 않은 세션은 영원히 sessions.json 에 남아 누적됐다. 4–5 회 재시작이면 데몬의 하드 PTY 캡 (MAX_SESSIONS=50) 을 모두 소진하여 startup recovery 가 새 pane 슬롯을 못 만들고, UI 는 Ctrl+T 도 안 먹히고 generic "알 수 없는 오류" 토스트만 도배되는 상태에 빠진다. 자가복구 불가능 (재시작해도 같은 시나리오 반복).

    • Suspended 7-day TTL — StateWriter.load 가 이제 dead 세션뿐 아니라 7 일 이상 inactive 한 suspended 도 함께 prune. v2.8.0 에서 누적된 기존 sessions.json 도 첫 v2.8.1 실행 시 자동 정리된다.
    • Recovery soft cap 40 — 신규 MAX_RECOVER_SESSIONS=40. 복구 후보를 lastActivity 내림차순 정렬해 상위 40 개만 PTY 로 재생성하고 나머지는 그대로 suspended 로 남는다. 다음 launch 에서 활성 카운트가 줄면 자동으로 복구 후보에 다시 들어오며, 7 일 TTL 이 그래도 정체된 것을 reap. 이로써 hard cap 50 에 도달해도 항상 신규 pane 헤드룸 10 슬롯이 보장된다.
    • createSession 에러 메시지 사용자 친화적 변경 — Maximum session limit (50) reached → Cannot create new terminal: 50 active sessions already running. Close some panes (or restart wmux) and try again.. RPC 응답으로 그대로 노출되어 향후 토스트가 generic 이 아닌 actionable 메시지로 보임.
    • 구현: src/daemon/StateWriter.ts, src/daemon/index.ts, src/daemon/DaemonSessionManager.ts, src/daemon/recoverySelector.ts (신규 — pure 함수로 cap 정책을 분리해 unit-test 가능). 9 unit tests 추가.
  • 복구된 pane 출력 interleave (High) — v2.8.0 은 종료 시점의 PTY cols/rows 를 저장하고 복구 시 그 값으로 ConPTY 를 spawn 한다. 사용자가 윈도우 사이즈를 바꾸고 재시작하면 ConPTY 는 옛 geometry 로 출력하는데 xterm 은 새 geometry 로 그려서 같은 줄에 두 paint 의 문자가 interleave 된다 (예: Accessing workspace: → Accessingwworkspace:).

    • Deferred output mode — DaemonPTYBridge 에 setMuted(bool) 추가. recovery 경로에서 createSession({deferOutput: true}) 면 bridge 가 muted 로 시작하여 PTY 데이터 path 가 ring buffer 에 쓰지 않는다 (exit 알림은 muted 와 무관하게 정상 동작). renderer 가 첫 daemon.resizeSession 을 호출하면 PTY 가 진짜 geometry 로 resize 되고 DEFERRED_UNMUTE_DELAY_MS=100 후 자동 unmute. ConPTY 가 옛 geometry 에서 큐잉했던 출력은 100 ms 동안 drain 되고 버려진다. 저장된 scrollback (buffer dump) 은 ring buffer 에 직접 pre-fill 되므로 muted path 와 무관하게 보존된다.
    • 구현: src/daemon/DaemonPTYBridge.ts, src/daemon/DaemonSessionManager.ts, src/daemon/index.ts (recoverSessions 의 createSession 호출 3 곳 모두 deferOutput: true). 5 unit tests 추가 (drop while muted / scrollback 보존 / resize-then-unmute / 비-deferred regression / muted 중 exit 발화).
  • 시작 시 generic 에러 토스트 폭주 (Medium) — main process 가 daemon connect 를 비동기로 시도하는 동안 renderer 가 이미 IPC 호출을 던져, handler swap (cleanupHandlers() → registerAllHandlers(...)) 의 sub-millisecond 무등록 윈도우에 떨어진 호출이 No handler registered for ... 로 실패해 useIpc 가 UNKNOWN → "알 수 없는 오류가 발생했습니다." 토스트를 5–10 회 띄우던 문제.

    • main 이 단일 IPC handler daemon:get-ready-state 를 등록 (registerAllHandlers swap cycle 바깥이라 무등록 race 불가). connect 시도가 끝나면 markDaemonReady() 가 그동안 큐잉된 invoke 를 해제. 이후 invoke 는 즉시 현재 daemonClient 상태로 응답.
    • preload 의 electronAPI.daemon.whenReady() 가 ipcRenderer.invoke('daemon:get-ready-state') 를 호출 (one-shot event 가 아니라 query). renderer crash recovery 의 mainWindow.reload() 로 새로 로드된 preload 인스턴스도 정상 응답을 받아 deadlock 안 됨 (codex review fix — 초기 event-based 설계의 P2 결함 보강).
    • AppLayout 의 첫 reconcile 이 daemon.whenReady() 를 await 하여 handler 가 안정된 뒤에야 pty.list / pty.reconnect 를 호출. 토스트 폭주 사라짐.
    • 구현: src/main/index.ts, src/preload/preload.ts, src/renderer/components/Layout/AppLayout.tsx.
  • Split 후 빈 pane 이 영구 placeholder 로 남던 문제 — AppLayout 의 auto-PTY effect 가 activeWorkspace.id 만 deps 로 가져 split 으로 추가된 새 leaf 가 surfaces=[] 인 채 effect 재실행을 유발하지 못했다. 결과적으로 분할된 새 pane 이 "빈 창" placeholder 로 굳어 PTY 가 영영 안 붙었다. collectEmptyLeaves 를 effect 바깥으로 끌어올리고 빈 leaf id 들의 join 키를 deps 에 추가해 split 이 즉시 PTY 생성을 트리거하도록 수정. paneSlice 에 회귀 테스트 추가 (src/renderer/stores/slices/__tests__/paneSlice.test.ts).

  • 한글 IME 상태에서 Ctrl+D / Ctrl+Shift+D split 단축키 미작동 — Hangul 레이아웃에서 e.key 가 'ㅇ' 또는 'Process' 가 되어 useKeyboard 의 key === 'd' 매칭이 빗나가고, useTerminal 의 xterm allowlist 도 같은 이유로 빠져 단축키가 xterm 에 흘러갔다. 두 곳 모두 e.code === 'KeyD' (물리 키 코드) 도 함께 매칭하도록 수정 — 기존 Ctrl+B / Ctrl+M 등의 cross-layout 패턴과 일관. 구현: src/renderer/hooks/useKeyboard.ts, src/renderer/hooks/useTerminal.ts.

  • 분할 pane 을 키보드/마우스로 닫을 수 없던 문제 — Ctrl+W 가 closeSurface 만 호출해 마지막 surface 닫혀도 pane 이 collapse 안 되고, 단일 surface pane 에서는 SurfaceTabs 가 strip 자체를 숨겨 X 버튼도 없었다. (1) Ctrl+W 가 마지막 surface 닫힐 때 closePane cascade 호출 (Pane.tsx X-button 동작 미러), (2) SurfaceTabs 가 surfaces.length === 1 이어도 strip 렌더, (3) 신규 Ctrl+Shift+Q (tmux kill-pane equivalent) 추가 + BUILTIN_KEYS 로 보호, (4) SettingsPanel 의 Ctrl+W 라벨이 실제 동작과 어긋났던 것을 closeSurface / closePane 두 줄로 분리해 i18n 4개 로케일 (en/ko/ja/zh) 모두 수정. 구현: src/renderer/hooks/useKeyboard.ts, src/renderer/components/Pane/SurfaceTabs.tsx, src/renderer/components/Settings/SettingsPanel.tsx.

  • Reconnect 후 출력이 두 줄로 중복되던 문제 — pty.handler.ts 의 PTY_CREATE 와 PTY_RECONNECT 가 매번 새 daemonClient.on('session:data', listener) 를 등록하면서 이전 listener 를 떼지 않아 누적됐다. 한 세션을 reconnect 한번만 해도 두 listener 가 같은 chunk 를 두 번 forward 해 renderer xterm 에 중복 출력. per-session listener map 으로 분리하여 같은 ptyId 의 이전 listener 를 항상 정리한 뒤에만 새 listener 등록. 구현: src/main/ipc/handlers/pty.handler.ts.

Migration Notes

  • 자동. 첫 v2.8.1 실행 시 StateWriter.load 가 7 일 이상 묵힌 suspended 세션을 prune 한다. 추가 액션 불필요. v2.8.0 에서 이미 brick 된 사용자도 업그레이드 후 첫 실행에서 정상 복구된다 (alphabeen 이 가이드한 수동 sessions.json/daemon-pipe/daemon.lock/daemon.pid 삭제 절차는 더 이상 필요 없음).
  • 외부 MCP 통합 측에 변경 없음 — 모든 변경은 daemon 내부 + main↔renderer IPC 가드.

v2.8.0

External Tooling Surface + Cross-Pane Search

외부 AI 도구(Claude Code, 서드파티 MCP)가 wmux 위에 워크플로우를 빌드할 수 있도록 세 개의 신규 surface를 동시 도입한 minor 릴리스다. @alphabeen 의 RFC #15 가 직접적인 트리거이며, 그 결과로 (1) pane 단위 metadata API, (2) cursor 기반 JSON-RPC event bus, (3) cross-pane search 가 묶음으로 들어온다. 모든 신규 필드는 optional 이라 기존 클라이언트는 영향 없으며, system.capabilities().features 의 새 키 (paneMetadata, events) 로 신규 표면을 감지할 수 있다.

릴리스 본문이 큰 만큼 데이터 마이그레이션은 없다. 다만 외부 MCP 통합 코드를 작성한 사람은 "Migration Notes" 의 bootId / asOfSeq 항목을 한 번 읽고 캐시 무효화 경로를 확인할 것.

Added

  • Pane metadata API — #16. PaneLeaf 에 optional PaneMetadata { label?, role?, status?, custom?: Record<string,string>, updatedAt? } 부착. RPC 3 개 (pane.setMetadata/getMetadata/clearMetadata) + MCP tool 2 개 (pane_set_metadata, pane_get_metadata). 8 KB 직렬화 캡, label ≤ 64, role ≤ 64, status ≤ 128, custom ≤ 32 entries × 64-char keys. 외부 MCP 의 cross-workspace 하이재킹은 workspaceId 자동 스코프 + slice 레벨 검증으로 차단 (v2.7.2 mcp.claimWorkspace fix 와 같은 클래스 패턴). custom 맵은 merge=true 일 때 1 단계 deep-merge — 협력하는 두 MCP 가 서로의 키를 덮어쓰지 않는다. 구현: src/shared/types.ts, src/shared/rpc.ts, src/main/pipe/handlers/pane.rpc.ts, src/renderer/stores/slices/paneSlice.ts, src/renderer/hooks/useRpcBridge.ts, src/mcp/index.ts.

  • JSON-RPC event bus — #21 (resubmit of #17, base-deleted artifact). WmuxEventType union: pane.created / pane.closed / pane.focused / pane.metadata.changed / workspace.metadata.changed / process.started / process.exited. In-memory ring (1024 events) + monotonic seq cursor. RPC events.poll({cursor, types?, workspaceId?, max?}) + MCP tool wmux_events_poll. 외부 도구는 자기 워크스페이스 이벤트만 자동 스코프. bootId (UUIDv4 / EventBus 인스턴스마다 변경) 가 events.poll / system.capabilities / pane.list 응답에 모두 노출되어 데몬 재시작 시 클라이언트 캐시(pane id, pty id, cursor) 를 깨끗이 무효화할 수 있다. pane.list 는 envelope {asOfSeq, bootId, panes} 로 변경되어 resync 후 reconcile 의 frame of reference 를 명확히 한다. polling 만 — push/SSE 는 stdio MCP transport 와 안 맞아 deferred. 구현: src/shared/events.ts, src/main/events/EventBus.ts, src/main/pipe/handlers/events.rpc.ts, src/renderer/events/publisher.ts, src/renderer/stores/slices/searchSlice.ts.

  • Cross-pane search — #20. wmux 의 첫 cross-pane primitive. Ctrl+F 의 "All Panes" 토글로 현재 워크스페이스 모든 live pane 의 xterm.js 버퍼를 on-demand grep 한다. 결과 ≤ 10 개는 search bar dropdown, > 10 개는 하단 panel 자동 확장 (progressive disclosure UX with hysteresis: open at > 10, close at ≤ 5, sticky bit until session reset). 결과 클릭 → 해당 pane focus + scrollToLine(physicalBaseY) 로 wrapped line 까지 정확히 jump. regex 모드 + 잘못된 패턴 visual error (red border + tooltip, no toast). MCP tool wmux_search_panes(query, regex?) 로 외부 AI 도 자율 추론 가능 ("JWT 에러 단 pane" 같은). 200-result cap, 20k lines/pane scan cap, 500-char line truncation. cross-workspace 검색은 v2 deferred (RPC-layer caller-identity gate 추가 설계 필요). 구현: src/renderer/utils/searchEngine.ts, src/renderer/components/Terminal/SearchBar.tsx, src/renderer/components/Search/SearchResultsPanel.tsx, src/renderer/stores/slices/searchSlice.ts, src/mcp/index.ts. i18n: en/ko/ja/zh 4 locale 모두 신규 키 추가.

Changed

  • pane.list 응답 형태 — PaneListEntry[] → {asOfSeq: number, bootId: string, panes: PaneListEntry[]} envelope. resync 시 클라이언트가 "이 스냅샷 이후 events" 를 정확히 결정할 수 있다. panes[] 는 기존 키 그대로 + 새 metadata?: PaneMetadata 필드 추가. 기존 클라이언트는 envelope unwrap 후 .panes 만 사용하면 되며, metadata 는 optional 이라 무시해도 됨.

  • system.capabilities 응답 확장 — methods: RpcMethod[] 만 있던 응답에 features: { paneMetadata: true, events: { types, maxRingSize, bootId } } 추가. 기존 methods 배열은 변경 없이 신규 method 들이 자동 추가된다 ('pane.setMetadata', 'pane.getMetadata', 'pane.clearMetadata', 'pane.search', 'events.poll').

Security

  • Cross-workspace pane.search 누출 차단 — RPC handler 가 caller 가 보낸 workspaceId 를 우선 사용하고 fallback 으로만 active workspace 를 쓴다. 외부 MCP 가 자기 ws 컨텍스트로 검색 호출 시, 사용자가 다른 ws 를 보고 있어도 caller 의 ws 결과만 받는다. v2.7.2 mcp.claimWorkspace fix 와 동일 클래스의 보안 게이트.
  • Pane metadata cross-ws 하이재킹 차단 — pane.setMetadata / pane.clearMetadata 도 workspaceId 스코프 강제. 외부 MCP 가 사용자 보는 ws 에 임의 metadata 작성 불가.

Fixed

  • Clipboard selection 잔존 fix — #19. v2.7.4 에서 도입한 selection-preserving fit 가드가 isVisible useEffect 와 document.fonts.ready 콜백 두 곳에 누락돼 워크스페이스 전환 직후나 폰트 로드 직후 selection 이 wipe 되던 문제. 또 selection 후 명시적 Ctrl+C 사이에 PTY 출력으로 selection 이 자연 클리어되어 SIGINT 가 가던 문제. fix: 두 가드 추가 + terminal.onSelectionChange 기반 자동 복사 (150 ms debounce, main-IPC 경유로 1 MB cap·Win32 lock retry·error toast 모두 보존). 해당 layer 9 unit tests 추가. 구현: src/renderer/hooks/useTerminal.ts, src/renderer/utils/autoSelectionCopy.ts (신규).

Migration Notes

  • 외부 MCP 통합 코드 는 wmux_search_panes / wmux_events_poll / pane_get_metadata 등 신규 도구를 즉시 사용할 수 있다. 신규 surface 감지는 system.capabilities().features.paneMetadata 와 features.events 키로.
  • pane.list 호출자 는 응답이 envelope 으로 바뀐 점을 반영해야 한다. 기존 코드가 panes[0].id 처럼 직접 인덱싱했다면 result.panes[0].id 로. 단, MCP pane_list tool 은 envelope 그대로 반환하므로 AI 에이전트는 자연어로 처리 가능.
  • 이벤트 폴링 클라이언트 는 매 응답의 bootId 를 비교하고, 변경됐다면 cached pane id / pty id / cursor 를 모두 폐기하고 pane.list 로 reconcile. cursor > latestSeq() 또는 resync: true 도 동일하게 처리.

v1 deferred → v2 candidates

다음 항목들은 본 릴리스 범위 밖으로 명시 deferred — 트래킹 #18 :

  • Cross-workspace search 및 metadata write (현재 caller ws 만 — explicit setting + RPC-layer caller-identity gate 설계 필요)
  • Push / SSE event delivery (stdio MCP 와 어울리지 않음, 폴링 latency 가 UX 문제 될 때 재검토)
  • Dead session scrollback dump 검색 (live pane 만 v1)
  • Optimistic concurrency (expectedVersion) on meta.set — 다중 도구 contention 시 last-writer-wins 를 깨끗이 분리