tabby-agentdeck 0.3.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ /**
2
+ * 에이전트 프로필 — "클로드든 코덱스든 제미나이든 같은 포맷으로 쓴다" 를 위한 단일 출처.
3
+ *
4
+ * 상태 감지(detect.ts)·이미지 붙여넣기 키(deck.service.ts)·입력창 머리글자(prompt.ts)가
5
+ * 원래 각자 Claude Code 를 전제로 하드코딩돼 있었다. 새 CLI 를 하나 지원하려면 세 파일을
6
+ * 따로 고쳐야 했고, 어떤 값이 실측이고 어떤 값이 짐작인지도 코드에서 구분되지 않았다.
7
+ * 그래서 "무엇을 어떻게 알아보나" 를 프로필 한 덩이로 모았다 — 새 CLI 는 아래 배열에
8
+ * 항목 하나를 더하는 것으로 끝난다.
9
+ *
10
+ * **이 파일은 순수하다** — DOM·fs·tabby 를 건드리지 않는다. 그래야 노드에서 바로 테스트된다.
11
+ *
12
+ * 값의 출처 표기 규칙: 실측된 값에는 근거(날짜·파일)를 적고, 관측이 없는 값에는
13
+ * `근거 미확인` 을 적는다. 짐작을 실측처럼 적어두면 다음 사람이 그것을 근거로 또 짐작한다.
14
+ */
15
+ export type AgentId = 'claude' | 'codex' | 'gemini' | 'unknown';
16
+ /**
17
+ * 이미지 전용 클립보드일 때 흘려보낼 키.
18
+ *
19
+ * `config` 는 "이 에이전트가 무엇을 받는지 관측된 바가 없다" 는 뜻이다 — 그때는
20
+ * 예전처럼 사용자 설정(`agentDeck.imagePasteKey`)을 쓴다. 관측 없는 키를 단정해 보내면
21
+ * 아무 일도 안 일어나거나(Claude 에 0x16) 엉뚱한 문자가 입력창에 박힌다.
22
+ */
23
+ export type ImagePasteKey = 'alt-v' | 'ctrl-v' | 'config';
24
+ export interface AgentProfile {
25
+ id: AgentId;
26
+ /** 사람에게 보여줄 이름 (사이드바·진단에 쓴다) */
27
+ label: string;
28
+ /** 프로세스 이름/명령줄에서 이 에이전트를 알아보는 조각 (소문자 비교) */
29
+ processHints: string[];
30
+ /** 탭 제목에서 알아보는 조각 (소문자 비교) */
31
+ titleHints: string[];
32
+ /** "사람 입력을 기다린다" 로 볼 화면 문구 */
33
+ waitingPatterns: RegExp[];
34
+ /** "지금 일하는 중" 으로 볼 화면 문구 */
35
+ busyPatterns: RegExp[];
36
+ /**
37
+ * "사용량 한도에 걸려 턴이 끊겼다" 로 볼 화면 문구.
38
+ *
39
+ * 왜 대기·작업중과 따로 두나 — 한도는 둘 중 어느 것도 아니다. 사람이 승인할 것이 없고
40
+ * 리셋 시각까지 기다려야 하는 상태라 배지·정렬·알림이 모두 다르게 취급한다
41
+ * (`api.ts` `limited`, `order.ts`, `alert.service.ts`).
42
+ *
43
+ * 왜 필요했나 — 0.9.0 까지 `limited` 는 훅(`claudeHooks.ts` 의 `StopFailure`
44
+ * `error=rate_limit`)으로만 왔고 **화면 문구 경로가 아예 없었다.** 그래서 훅이 없는
45
+ * CLI(codex·gemini)는 한도에 걸려도 배지가 `running`/`error` 로 남았다.
46
+ *
47
+ * **`patternsProven` 규칙과의 관계** — 이것도 화면 문구이므로 위 세 필드와 똑같이 다룬다.
48
+ * 실측이 없으면 `[]` 로 비우고, 실측이 없는 프로필의 판정에는 합집합 것이 쓰인다
49
+ * (`detectProfileFor`). 합집합은 모든 프로필의 값을 모으므로(`buildUnion`), 한 프로필에서
50
+ * 실측된 한도 문구는 `patternsProven: false` 인 프로필에서도 그대로 쓰인다 —
51
+ * codex 가 정확히 그 경우다(한도 문구만 실측, 대기·작업중은 미관측).
52
+ */
53
+ limitedPatterns: RegExp[];
54
+ /** 콘솔 제목에 넣는 작업중 표식(스피너 등) */
55
+ busyTitleMarks: string[];
56
+ /** 이미지 전용 클립보드일 때 흘려보낼 키 */
57
+ imagePasteKey: ImagePasteKey;
58
+ /** 입력창 머리글자 후보 — 라벨을 읽는 쪽(prompt.ts)이 쓸 값. 여기서는 데이터만 들고 있는다 */
59
+ promptHeads: string[];
60
+ /**
61
+ * 입력창에서 프롬프트를 뽑을 때 쓸 화면 모양 (`prompt.ts` 의 `PromptShape` 와 구조가 같다).
62
+ *
63
+ * **타입을 `prompt.ts` 에서 import 하지 않는다** — 그쪽은 순수 모듈이고 여기서 역참조하면
64
+ * 의존이 한 바퀴 돈다. 구조만 맞으면 TS 가 받아준다.
65
+ *
66
+ * 생략하면 `prompt.ts` 의 기본값이 쓰인다. **claude 는 일부러 생략한다** — 그 기본값 자체가
67
+ * Claude Code 2.1.x 실측으로 만들어진 값이라(`prompt.ts` `DEFAULT_PROMPT_SHAPE` 주석),
68
+ * 여기서 좁혀 적으면 같은 값을 두 곳에 두게 되고 한쪽만 고쳐질 위험만 생긴다.
69
+ */
70
+ promptShape?: {
71
+ heads?: string[];
72
+ choiceWords?: string[];
73
+ ruleChars?: string[];
74
+ };
75
+ /** 화면 깨짐 판정에 쓸 모양 (`screen.ts` 의 `ScreenShape` 와 구조가 같다). 위와 같은 이유로 claude 는 생략 */
76
+ screenShape?: {
77
+ ruleChars?: string[];
78
+ heads?: string[];
79
+ choiceWords?: string[];
80
+ };
81
+ /**
82
+ * 화면 문구(`waitingPatterns` · `busyPatterns` · `limitedPatterns` · `busyTitleMarks`)가
83
+ * **실측된** 프로필인가.
84
+ *
85
+ * false 면 상태 판정에는 이 프로필 대신 합집합의 문구를 쓴다(`detectProfileFor`).
86
+ * 왜 이 갈래가 필요한가 — 프로필을 붙이는 순간 판정이 그 프로필 안으로 좁아지는데,
87
+ * 관측이 없는 에이전트에서 그건 **개선이 아니라 퇴행**이다. 예를 들어 Codex 가 실제로
88
+ * `esc to interrupt` 를 쓰거나 제목에 스피너를 넣는다면, 0.5.0 은 (합집합 하나로 모든 탭을
89
+ * 판정했으므로) 그걸 잡았는데 프로필을 붙인 0.6.0 은 놓친다.
90
+ * 그래서 **실측이 있는 값만 좁게 쓴다** — 실측되면 이 값을 true 로 올리면 된다.
91
+ *
92
+ * `imagePasteKey` 처럼 이미 실측된 필드는 이 값과 무관하게 프로필 것을 쓴다.
93
+ */
94
+ patternsProven: boolean;
95
+ }
96
+ /** 지원하는 에이전트 목록 — 새 CLI 는 여기에 한 덩이 추가하면 된다 */
97
+ export declare const AGENT_PROFILES: AgentProfile[];
98
+ export declare const UNKNOWN_PROFILE: AgentProfile;
99
+ /** 프로필을 모르는 상황(감지 전/실패)에서 쓰는 합집합 — 0.5.0 까지의 동작과 같다 */
100
+ export declare function unionProfile(): AgentProfile;
101
+ /** 아이디로 프로필을 집는다 — 모르는 아이디면 합집합을 준다(호출부에 null 검사를 강요하지 않는다) */
102
+ export declare function profileFor(id: AgentId | undefined | null): AgentProfile;
103
+ /**
104
+ * **상태 판정(detect.ts)에 넘길** 프로필. 화면 문구가 실측되지 않은 에이전트는 그 세 필드만
105
+ * 합집합으로 갈아 준다 — 나머지 필드(이미지 키·머리글자·라벨)는 프로필 것을 그대로 쓴다.
106
+ *
107
+ * 왜 이 함수가 따로 있나 — 프로필을 붙이는 것만으로 판정이 좁아지면 관측이 없는 에이전트에서는
108
+ * **퇴행**이다. 0.5.0 은 합집합 하나로 모든 탭을 판정했으므로, 예컨대 Codex 가 실제로
109
+ * `esc to interrupt` 를 찍고 있었다면 그때는 잡혔는데 프로필을 붙인 뒤로는 놓치게 된다
110
+ * (유닛 C 가 그 리스크를 보고했고, 그래서 배리어에서 이 갈래를 넣었다).
111
+ *
112
+ * 즉 **좁히는 것은 실측이 있을 때만** 한다. Codex/Gemini 화면 문구가 관측되면 그 프로필의
113
+ * `patternsProven` 을 true 로 올리면 이 함수가 자동으로 프로필 값을 쓴다.
114
+ */
115
+ export declare function detectProfileFor(id: AgentId | undefined | null): AgentProfile;
116
+ /**
117
+ * 이 탭에서 도는 에이전트가 뭔지 — 프로세스 이름/명령줄을 먼저 보고, 없으면 탭 제목으로 본다.
118
+ *
119
+ * 프로세스를 먼저 보는 이유: npm 전역 래퍼를 거치면 명령줄이 node/pwsh 로만 보일 수 있어
120
+ * 제목이 보조 근거가 되지만, 제목은 셸이 아무 문자열이나 넣을 수 있어 신뢰도가 낮다.
121
+ * 두 근거 안에서의 에이전트 판정 순서는 `MATCH_ORDER` — claude 가 맨 뒤다.
122
+ *
123
+ * @param processNames 프로세스 트리에서 모은 `command name` 을 이어붙인 문자열
124
+ * @param title 탭 제목(+ 사용자 지정 제목)
125
+ */
126
+ export declare function identifyAgent(processNames: string, title: string): AgentId;
@@ -82,6 +82,6 @@ export declare class AgentDeckAlertService {
82
82
  private toast;
83
83
  /** 토스트 tag 용 탭 식별자 — 객체 자체는 문자열이 안 되니 인덱스로 대신한다 */
84
84
  private tabKey;
85
- /** deck.service 와 같은 파일(`~/.agentdeck-diag.log`)에 남긴다. 실패해도 동작에는 영향 없다 */
85
+ /** deck.service 와 같은 파일(`~/.agentdeck-diag.log`)에 남긴다 — 회전·시각 표기는 `diag.ts` 가 한다 */
86
86
  private diag;
87
87
  }
package/dist/config.d.ts CHANGED
@@ -17,11 +17,89 @@ export declare const LAYOUT_DEFAULTS: {
17
17
  sidebarWidth: number;
18
18
  /** 상/하 도킹일 때의 사이드바 높이 (px). 경계선 드래그로 바뀐다 */
19
19
  sidebarHeight: number;
20
+ /**
21
+ * 미리보기 패널이 붙는 방향 — 'right' | 'left'.
22
+ * 사이드바와 같은 쪽이면 사이드바가 창 가장자리(바깥), 패널이 터미널 쪽(안)에 앉는다.
23
+ */
24
+ viewerDock: string;
25
+ /** 미리보기 패널 폭 (px). 경계선 드래그로 바뀐다 */
26
+ viewerWidth: number;
20
27
  };
21
28
  /** @hidden */
22
29
  export declare class AgentDeckConfigProvider extends ConfigProvider {
23
30
  defaults: {
24
31
  agentDeck: {
32
+ /**
33
+ * 결과물 미리보기 패널(viewPanel.ts)이 지금 열려 있나.
34
+ *
35
+ * 사이드바 `▤` 버튼이나 `agentdeck-view` 핫키로 토글하면 이 값이 바뀌고,
36
+ * 다음 기동 때 그대로 복원한다 — 문서를 띄워 놓고 쓰는 사람에게는 그게 기본 화면이다.
37
+ */
38
+ viewerOpen: boolean;
39
+ /**
40
+ * 화면에 찍힌 파일 경로를 주워 최근 목록에 쌓는다.
41
+ *
42
+ * 에이전트를 가리지 않는 유일한 경로다 — Claude Code 처럼 훅이 있는 CLI 만 되는 게
43
+ * 아니라 Codex·Gemini 도 만진 파일을 화면에 찍기 때문이다(viewer.ts 주석).
44
+ * 비용은 탭당 400ms 마다 정규식 한 번이고, 끄면 수동으로 열 때만 쓰게 된다.
45
+ */
46
+ viewerScrape: boolean;
47
+ /**
48
+ * 에이전트가 만진 파일을 **패널에 미리 얹어 둔다** (닫혀 있어도).
49
+ *
50
+ * 기본 켬. 패널이 닫혀 있는 동안에도 최근 목록과 "다음에 열면 보여줄 파일" 을
51
+ * 계속 갈아 두므로, 작업이 한창일 때 `▤` 를 누르면 **그때까지 만진 파일들이
52
+ * 칩으로 쌓여 있고 본문은 가장 최근 파일**이다 (`변경` 모드였으면 그 자리에서
53
+ * diff 를 다시 읽는다). 끄면 아무것도 자동으로 하지 않는다 — 경로 수집만 남고
54
+ * 무엇을 볼지는 사람이 칩·`+`·드롭으로 고른다.
55
+ *
56
+ * 이 값이 `false` 면 `viewerAutoOpen` 은 의미가 없다 (읽어두지 않으니 열 것도 없다).
57
+ */
58
+ viewerPreload: boolean;
59
+ /**
60
+ * 새 파일을 주우면 **닫힌 패널을 스스로 연다.**
61
+ *
62
+ * **기본 끔** (2026-09-10 유저 결정). 여닫기는 사람 몫이라는 뜻이다 — 작업 중에
63
+ * 패널이 저절로 열리면 터미널이 좁아지고 읽던 화면이 밀린다. 대신 위 `viewerPreload`
64
+ * 가 내용을 계속 채워 두므로, 열고 싶을 때 열면 이미 최신이다.
65
+ * 켜면 결과물이 나오는 순간 패널이 열리면서 그 파일을 띄운다.
66
+ */
67
+ viewerAutoOpen: boolean;
68
+ /**
69
+ * @deprecated 0.18.0 부터 `viewerPreload` + `viewerAutoOpen` 두 토글로 갈랐다.
70
+ *
71
+ * 목록(`open`/`show`/`manual`) 하나로는 "읽어두기" 와 "자동으로 열기" 가 한 축에
72
+ * 얹혀 있어서, 사람이 고를 때 두 결정을 한꺼번에 해야 했다. 남겨 두는 이유는
73
+ * 이미 값을 적어 둔 설정 파일 때문이고, 기동 때 한 번 두 토글로 옮긴 뒤 비운다
74
+ * (`migrateFollow`, deck.service 의 `migrateFollowConfig`).
75
+ */
76
+ viewerFollowMode: "" | "open" | "show" | "manual";
77
+ /**
78
+ * @deprecated 0.17.0 부터 `viewerFollowMode` 로, 0.18.0 부터 위 두 토글로 옮겼다.
79
+ *
80
+ * boolean 하나가 "자동으로 열기" 와 "새 파일로 바꾸기" 를 같이 쥐고 있어서,
81
+ * **"패널은 내가 열고 닫되 열어 둔 동안에는 따라와라"** 를 표현할 수 없었다.
82
+ * 마이그레이션이 `false` 를 읽고 나면 기본값(`true`)으로 되돌려 다시 읽지 않는다.
83
+ */
84
+ viewerFollow: boolean;
85
+ /**
86
+ * 패널 **밖**(터미널 위)에 파일을 끌어다 놓았을 때 무엇을 할지.
87
+ *
88
+ * 순정 Tabby 는 그 경로를 터미널에 붙여넣는다(그래서 `cat <경로>` 가 쉽다). 그 동작은
89
+ * 그대로 두고, **미리보기로 열지를 물어본다** — 자동으로 화면을 바꾸면 붙여넣기만
90
+ * 원했던 사람의 화면을 빼앗는다 (2026-09-09 유저: "한번 물어보긴 해야 할 듯").
91
+ *
92
+ * - `ask` : 확인 배너를 띄워 `패널에 띄우기` / `경로 붙여넣기` 를 고르게 한다 (기본)
93
+ * - `always` : 묻지 않고 패널에 띄운다
94
+ * - `paste` : 묻지 않고 경로만 터미널에 붙여넣는다
95
+ * - `never` : 우리가 개입하지 않는다 — 순정 Tabby 동작 그대로
96
+ *
97
+ * 배너의 `다시 묻지 않기` 를 체크하고 버튼을 누르면 그 선택이 `always`/`paste` 로
98
+ * 저장된다. 되돌리려면 설정 창에서 `물어본다` 로 바꾼다.
99
+ */
100
+ viewerDropOpen: "ask" | "always" | "paste" | "never";
101
+ /** 최근 목록(패널 위쪽 칩)에 남겨 두는 파일 수 */
102
+ viewerRecentMax: number;
25
103
  /**
26
104
  * 훅이 상태를 밀어 넣는 TCP 채널의 포트 (127.0.0.1 전용).
27
105
  *
@@ -56,18 +134,155 @@ export declare class AgentDeckConfigProvider extends ConfigProvider {
56
134
  * 설정 창에서 토글한다 (recovery.service.ts).
57
135
  */
58
136
  noTabRecovery: boolean;
137
+ /**
138
+ * 기동할 때 npm 레지스트리를 보고 **새 버전을 받아 둔다** (적용은 다음 기동).
139
+ *
140
+ * Tabby 는 플러그인을 기동할 때 한 번 읽으므로 돌고 있는 것을 갈아끼울 수 없다 —
141
+ * 그래서 받아만 두고 한 번 알린다. 기동 20초 뒤에 한 번, 그 뒤로는 아래 간격마다.
142
+ *
143
+ * **개발 설치(소스 트리 junction)에는 설치하지 않는다.** 거기에 `npm install` 이 돌면
144
+ * 작업 중인 소스를 덮어쓴다 — 새 버전이 있다는 것만 알리고 손대지 않는다
145
+ * (판정은 `update.ts` 의 `canSelfUpdate`).
146
+ */
147
+ autoUpdate: boolean;
148
+ /** 확인 간격 (시간). 기동마다 묻지 않는다 */
149
+ updateCheckIntervalHours: number;
150
+ /** 마지막으로 레지스트리에 물어본 시각 (epoch ms) — 설정이 아니라 상태다 */
151
+ lastUpdateCheck: number;
152
+ /** 레지스트리 주소. 사내 미러를 쓰면 여기를 바꾼다 */
153
+ updateRegistry: string;
59
154
  /** PTY 출력이 이 시간(ms) 이상 없으면 running -> idle */
60
155
  idleAfterMs: number;
61
156
  /** 출력 패턴으로 상태를 자동 추론할지 */
62
157
  autoDetect: boolean;
63
158
  /** 사이드바에 경과 시간 표시 */
64
159
  showElapsed: boolean;
160
+ /**
161
+ * 세션이 지금 돌리고 있는 **백그라운드 서브에이전트 개수**를 줄에 보인다
162
+ * (`❖ 3`, 툴팁에 무엇을 시켜 놨는지 + 가장 오래된 것의 경과 시간).
163
+ *
164
+ * 기본으로 켠다 — 얻는 것이 "저 탭이 사람 없이도 일이 도는 중인지" 이고 그것을 볼
165
+ * 다른 수단이 없다(훅은 세션 **하나**의 상태만 알려 준다, `subagents.ts` 주석).
166
+ * 0 개면 아무것도 그리지 않으므로(deck.service `renderTab`) 서브에이전트를 안 쓰는
167
+ * 사람의 화면은 0.12.0 까지와 똑같다 — 켜 둬서 잃는 것이 없다.
168
+ *
169
+ * 비용은 세션당 **2초에 `stat` 한 번**이고, 파일이 자란 만큼만 비동기로 읽는다
170
+ * (실측 대화기록 13MB — 동기로 읽으면 UI 가 멈춘다).
171
+ * 끄면 읽기도 표시도 멈춘다: 끈 기능이 대화기록 파일을 계속 여는 일은 없다
172
+ * (`notify.service` `scanSubagents`).
173
+ */
174
+ subagentCount: boolean;
65
175
  /**
66
176
  * 사이드바 탭을 상태 우선순위(waiting → error → running → done → idle)로 정렬한다.
67
177
  * 같은 상태끼리는 탭 순서를 지킨다. 기본은 끔 — 순정 탭바와 같은 탭 순서로 보인다.
68
178
  * 헤더의 상태별 집계는 이 값과 무관하게 항상 보인다.
69
179
  */
70
180
  sortByStatus: boolean;
181
+ /**
182
+ * 사이드바 헤더 아래에 **세션 검색 줄**을 둔다 (제목 · 작업이름 · 작업 폴더로 좁히기,
183
+ * 헤더의 상태 칩을 눌러 그 상태만 보기).
184
+ *
185
+ * 기본으로 켠다 — 탭이 몇 개뿐일 때도 잃는 것은 26px 한 줄이고, 탭이 많아지는 순간
186
+ * 목록을 눈으로 훑는 비용이 그보다 훨씬 크다. 세로가 짧은 상/하 도킹에서 그 한 줄이
187
+ * 아까운 사람만 끄면 된다.
188
+ *
189
+ * **여기 저장하는 것은 기능 on/off 뿐이다.** 검색어와 상태 필터는 설정에 남기지 않는다
190
+ * (deck.service `searchQuery` 주석) — 다음 기동에 필터가 살아 있으면 목록이 좁혀진
191
+ * 채로 떠서 "탭이 사라졌다" 로 읽힌다. 그 오해는 이 기능이 주는 이득보다 비싸고,
192
+ * 되살릴 값어치도 없다(검색은 "지금 이것을 찾는다" 는 한순간의 행위다).
193
+ * 이 값을 끄면 걸려 있던 필터도 같이 지운다 — 감춰진 필터가 남는 것이 가장 위험하다.
194
+ */
195
+ searchBox: boolean;
196
+ /**
197
+ * 사이드바를 **키보드로** 다룬다 — 핫키(`agentdeck-focus-list`, 기본 `Ctrl-Shift-L`)로
198
+ * 목록에 들어가 ↑↓ 로 훑고, Enter 로 그 탭으로 전환, Esc 로 터미널에 돌아온다.
199
+ * 검색창에서 ↓ 를 누르면 결과 목록으로 내려가고, 첫 줄에서 ↑ 면 검색창으로 돌아온다.
200
+ * 그룹 헤더도 지나가며 Enter 로 접었다 펼 수 있다.
201
+ *
202
+ * **이동만으로는 탭이 바뀌지 않는다** (포커스 ≠ 선택). 여섯 개쯤 띄워 두고 훑을 때
203
+ * 줄마다 활성 탭이 바뀌면 터미널 화면이 계속 튀어 훑기 자체가 불가능하다
204
+ * (`deck.service.ts` `navFocus` 주석).
205
+ *
206
+ * 기본으로 켠다 — **포커스가 사이드바에 없는 동안은 키를 한 개도 가로채지 않는다.**
207
+ * 리스너가 document 가 아니라 목록 엘리먼트(`.ad-list`)에 걸려 있어서, 핫키를 누르거나
208
+ * 검색창에 들어가지 않으면 이벤트가 아예 오지 않는다(`wireListKeys` 주석).
209
+ * 끄면 핫키·검색창 ↓ 가 모두 no-op 이 되고 링도 그리지 않는다.
210
+ */
211
+ keyboardNav: boolean;
212
+ /**
213
+ * 목록 끝에서 ↑↓ 를 한 번 더 누르면 반대쪽 끝으로 감싼다.
214
+ *
215
+ * 기본은 끔 — 감싸면 "맨 아래에 왔다" 는 신호가 사라지고, 목록이 한 번에 위로 튀어
216
+ * 화면이 다시 그려진 것처럼 보인다. 끝에서 끝으로 갈 일은 Home/End 가 받는다.
217
+ *
218
+ * 켜도 **맨 위의 ↑ 는 검색창으로 나간다**(검색 줄이 보일 때). 그 경계를 감싸기에
219
+ * 내주면 목록에서 검색창으로 돌아갈 키가 없어진다 — 검색↔목록 왕복이 이 기능의
220
+ * 요구사항이라 감싸기보다 앞에 둔다 (`deck.service.ts` `moveNav`).
221
+ */
222
+ keyboardNavWrap: boolean;
223
+ /**
224
+ * `Ctrl+W` 로 지금 탭을 닫는다 (터미널에서 누르면 활성 탭, 목록이 키보드를 갖고
225
+ * 있으면 포커스 줄의 탭 — `nav.ts` `pickCloseTarget`).
226
+ *
227
+ * **`keyboardNav` 에 매달아 둔다** — 키보드로 목록을 훑다가 버릴 탭을 만나는 것이
228
+ * 이 키의 쓰임새이고, 키보드 조작을 끈 사람에게 터미널 키를 빼앗을 이유가 없다.
229
+ * 그래서 `keyboardNav` 가 꺼져 있으면 이 값이 `true` 여도 아무 일도 하지 않는다
230
+ * (설정 창에서도 줄이 사라진다 — `keyboardNavWrap` 과 같은 방식).
231
+ *
232
+ * 끄고 싶을 만한 이유가 분명히 있다: 셸·에이전트 CLI 에서 `Ctrl+W` 는 **앞 단어
233
+ * 지우기**(0x17)다. 그걸 그대로 쓰려면 이 값만 끄면 되고, 목록 키보드 조작은 남는다.
234
+ * 켠 상태에서도 우리가 키를 먹는 것은 **닫을 탭이 실제로 정해졌을 때뿐**이다 —
235
+ * 그룹 헤더에 포커스가 있거나 설정 화면 입력창이면 키를 그대로 흘린다.
236
+ * 닫기는 사이드바 `✕` 와 같은 경로라(`closeTab(tab, true)`) 돌아가는 프로세스가
237
+ * 있으면 Tabby 가 평소처럼 한 번 물어본다.
238
+ */
239
+ keyboardCloseTab: boolean;
240
+ /**
241
+ * 줄을 끌어 순서를 바꾸는 동안 커서를 **목록 끝에 대면 목록이 따라 스크롤된다.**
242
+ *
243
+ * 기본으로 켠다 — 없으면 탭이 많아 목록이 넘치는 순간 "화면 밖으로 옮기기" 가
244
+ * 아예 불가능해진다(손을 떼고 스크롤한 뒤 다시 끄는 왕복이 필요하다). 그게
245
+ * 0.12.0 의 드래그 재정렬에 남아 있던 구멍이다.
246
+ *
247
+ * 끄면 0.12.0 과 똑같이 동작한다 — 목록은 가만히 있고, 보이는 줄 사이에서만
248
+ * 옮긴다. 자동 스크롤을 꺼서 얻는 것이 있다: 가장자리 띠 안에서 화면이 흐르지
249
+ * 않으므로 **맨 끝 줄의 뒤에 놓는** 조작이 절대 미끄러지지 않는다. 띠를 줄 하나
250
+ * 두께로 좁혀 두긴 했지만(deck.service `DRAG_SCROLL_BAND_PX`) 그 조작을 자주
251
+ * 하는 사람에게는 이 스위치가 답이다.
252
+ *
253
+ * 속도·띠 두께는 설정으로 내지 않았다 — px/s 는 사람이 판단할 수 있는 숫자가
254
+ * 아니고, 띠 두께는 줄 높이에서 나온 값인데 줄 높이가 설정이 아니다.
255
+ * 이 값은 **드래그 중에만** 읽는다: 끌지 않을 때는 커서가 목록 끝에 얹혀 있어도
256
+ * 아무 일도 일어나지 않는다(hover 리스너를 두지 않았다).
257
+ */
258
+ dragAutoScroll: boolean;
259
+ /**
260
+ * 접어 둔 그룹 키 목록. 그룹 헤더를 클릭할 때마다 갱신되고 다음 기동에 복원된다.
261
+ *
262
+ * 키는 `group.ts` 가 정규화한 경로를 **소문자로 접은** 값이다(`foldGroupKey`) —
263
+ * 셸이 알려주는 경로의 대소문자·구분자가 기동마다 달라도 접힘이 유지돼야 하기 때문이다.
264
+ */
265
+ collapsedGroups: any[];
266
+ /**
267
+ * 그룹 안에 **지난 세션**(죽은 Claude Code 세션) 줄을 그린다.
268
+ *
269
+ * 이 기능이 있는 이유는 `recovery.service.ts` 주석에 있다 — agentdeck 은 Tabby 의 순정
270
+ * 탭 복원을 껐다(셸만 살아나고 세션은 죽어 있어 "주인 없는 탭" 이 남는다).
271
+ * `claude --resume` 이 그 자리에 들어갈 진짜 복원이고, 사람이 막히는 곳은 명령이 아니라
272
+ * UUID 목록에서 자기 작업을 찾는 것이라 **라벨을 붙여 보여주는 것** 이 이 기능의 전부다.
273
+ */
274
+ resumeList: boolean;
275
+ /** 그룹당 몇 줄까지 보여줄지. 0 이면 제한 없음 (목록이 길어지면 살아 있는 탭이 밀린다) */
276
+ resumeListLimit: number;
277
+ /**
278
+ * 며칠치까지. 기간 밖 세션은 **화면에서만** 빠지고 원장·기록은 그대로다.
279
+ * stat 단계에서 잘라내므로 이 값이 작을수록 펼치는 순간이 싸다.
280
+ */
281
+ resumeListDays: number;
282
+ /** 지난 세션 줄을 펼쳐 둔 그룹 키 (`collapsedGroups` 와 반대로 **펼침**을 적는다 — 기본이 접힘이라서) */
283
+ resumeExpanded: any[];
284
+ /** 사람이 목록에서 숨긴 세션 id */
285
+ resumeHidden: any[];
71
286
  /**
72
287
  * 창이 가려져 있어도 사이드바를 계속 갱신한다 (Electron backgroundThrottling 해제).
73
288
  *
@@ -110,6 +325,18 @@ export declare class AgentDeckConfigProvider extends ConfigProvider {
110
325
  * 방향키는 핫키가 아니라서 xterm 이 조합을 먼저 확정하고 키를 보낸다.
111
326
  */
112
327
  releaseHomeEnd: boolean;
328
+ /**
329
+ * pty 에 바이트를 쓰기 전에 조합 중인 한글 음절을 먼저 확정시킨다.
330
+ *
331
+ * 핫키를 놓아주는 것(`releaseHomeEnd`)만으로는 부족했다 — 실사용 config.yaml 이
332
+ * 이미 `home: []` / `end: []` 인데도 조합 중 음절이 커서를 따라가는 증상이 재발했다
333
+ * (2026-09-08 사용자 재보고). 원인은 핫키가 아니라 **xterm 을 거치지 않는 pty 직접
334
+ * 쓰기**여서, 이 옵션은 그 쓰기 지점 전부를 한 진입점으로 모아 순서를 맞춘다
335
+ * (deck.service.ts `sendToPane` / `guardHomeEndComposition`, 기전은 `ime.ts`).
336
+ *
337
+ * false 로 두면 0.4.0 까지의 동작 그대로 — 조합 상태를 보지 않고 바로 쓴다.
338
+ */
339
+ imeOrderGuard: boolean;
113
340
  /**
114
341
  * 클립보드에 이미지만 있으면 Ctrl+V 로도 이미지가 들어가게 한다.
115
342
  *
@@ -224,6 +451,33 @@ export declare class AgentDeckConfigProvider extends ConfigProvider {
224
451
  * 항목 28 의 반전은 그 표본에는 해당하지 않았다.
225
452
  * 되살리려면 "앱이 pty 폭을 다시 읽게 만드는" 수단부터 찾을 것. 감지·채증(`screenWatch`)은 계속 돈다.
226
453
  */
454
+ /**
455
+ * **다시 켬 (2026-09-09).** 위 두 주석의 판단 근거가 오늘 채증으로 갈렸다.
456
+ *
457
+ * 껐던 이유는 "복구 수단이 이 유형에 안 듣는다" 였는데, 그 유형이 무엇이었는지가
458
+ * 오늘 실사용 8건으로 확정됐다 — **`xterm.cols != pty` 인 리사이즈 중간 프레임**이다
459
+ * (4건 전부, 0~3초 만에 스스로 복구, 화면 덤프는 앱 기준 정상). 거기에 `fit`+`nudge`
460
+ * 를 때렸으니 고쳐질 리가 없고 화면만 흔들렸다.
461
+ *
462
+ * 크기가 맞는데 깨진 나머지 4건(02:07:45~02:08:01, 자식 프로세스 stdout 이 TUI 를
463
+ * 덮어씀)은 26초를 버텼고, 이 유형은 `clearInputArea` + `Ctrl+L` 로 실제로 고쳐진다
464
+ * (회귀 PR8·PR9 가 격리 인스턴스에서 100~160ms 해소를 잰다).
465
+ *
466
+ * 그래서 **손댈 유형만 손대도록** `checkScreen` 에 크기 가드를 넣고 기본값을 켠다.
467
+ * 자동 경로가 하는 일은 사이드바 `↻` 한 번과 같다(잔상 지우기 + Ctrl+L, **흔들기 없음**).
468
+ *
469
+ * 켜기 직전에 "켜면 격리 인스턴스가 죽는다" 로 한 번 막혔는데, **그 죽음은 우리 코드가
470
+ * 아니었다** (2026-09-09 확정). `tools/test-instance.ps1 -Kill` 이 커맨드라인에
471
+ * `tabby-agentdeck-test` 가 든 Tabby 를 **전부** 죽이고 기동은 그 `ud` 폴더를 지운다 —
472
+ * 같은 저장소에서 두 세션이 동시에 회귀를 돌리면 서로의 인스턴스를 죽인다. 그때 CDP 는
473
+ * ECONNRESET 으로 끊기고 진단로그에 상대의 `setup start tabs=0` 이 이어 붙어 크래시처럼
474
+ * 보인다. 판별법 — **Crashpad 리포트가 비어 있으면 크래시가 아니라 누가 죽인 것이다**
475
+ * (`<ud>\Crashpad\reports`). 이름이 겹치지 않는 전용 인스턴스에서 다시 재니
476
+ * 기본 `true` 로 `probe-profile` 3회 연속 8 PASS / 0 FAIL 이었다.
477
+ *
478
+ * 끄고 싶으면 `%APPDATA%\tabby\config.yaml` 의 `agentDeck.autoRepairScreen: false`
479
+ * 한 줄이면 되고, 감지·채증(`screenWatch`)은 어느 쪽이든 돈다.
480
+ */
227
481
  autoRepairScreen: boolean;
228
482
  /**
229
483
  * 화면을 **보고 기록만** 한다 (복구는 하지 않는다).
@@ -256,8 +510,22 @@ export declare class AgentDeckConfigProvider extends ConfigProvider {
256
510
  */
257
511
  repairHard: boolean;
258
512
  /**
259
- * 복구 마지막에 `Ctrl+L`(0x0C)을 보내 TUI 에게 전체 재렌더를 시킨다.
260
- * 입력 중이던 글자는 유지된다. 이걸 다른 뜻으로 쓰는 앱을 만나면 끈다.
513
+ * 복구 마지막에 `Ctrl+L`(0x0C)을 보낸다.
514
+ *
515
+ * **기본 끔 (2026-09-11 전환).** 0x0C 를 "전부 다시 그려라" 로 받는 앱을 전제했는데,
516
+ * **Claude Code 는 "화면을 지워라" 로 받는다** — 채증으로 확정했다.
517
+ * `↻` 를 누른 뒤 스냅샷(`.agentdeck-screen.log` 06:04:52→54)에서 78행 중 **0~72행이
518
+ * 통째로 비고** 입력창(73~77행)만 남았고, 진단 로그의 순서가 원인을 못 박는다:
519
+ * `clear input area row=73/78`(73~77행만 지움) → `repair redraw-key ctrl-l` → 화면 전멸.
520
+ * 사용자에게는 "새로고침하면 출력이 한참 위로 올라가 버린다" 로 보인다(유저 지적).
521
+ *
522
+ * 끄고도 복구는 된다 — 실제로 고치는 것은 잔상 지우기(`clearInputArea`)와
523
+ * 크기 정합·흔들기(SIGWINCH)이고, 0x0C 는 그 위에 얹은 "확실히 해두기" 였다.
524
+ * 지운 자리의 입력창은 앱이 자기 렌더 주기에 다시 그린다(Claude Code 는 상태줄을
525
+ * 매초 갱신하므로 즉시 돌아온다). 수동 복구는 흔들기까지 하므로 그 자리에서 돌아온다.
526
+ *
527
+ * 켜는 경우 — Ctrl+L 이 실제로 재렌더인 앱만 쓰거나, 셸에서 화면이 지워져도
528
+ * 상관없을 때. 켜면 자동복구에도 적용되므로 오탐 한 번에 보고 있던 화면이 밀린다.
261
529
  */
262
530
  repairSendRedrawKey: boolean;
263
531
  /**
@@ -318,6 +586,13 @@ export declare class AgentDeckConfigProvider extends ConfigProvider {
318
586
  sidebarWidth: number;
319
587
  /** 상/하 도킹일 때의 사이드바 높이 (px). 경계선 드래그로 바뀐다 */
320
588
  sidebarHeight: number;
589
+ /**
590
+ * 미리보기 패널이 붙는 방향 — 'right' | 'left'.
591
+ * 사이드바와 같은 쪽이면 사이드바가 창 가장자리(바깥), 패널이 터미널 쪽(안)에 앉는다.
592
+ */
593
+ viewerDock: string;
594
+ /** 미리보기 패널 폭 (px). 경계선 드래그로 바뀐다 */
595
+ viewerWidth: number;
321
596
  /** 레이아웃/사이드바 전체 on-off */
322
597
  enabled: boolean;
323
598
  /** 터미널 뷰포트 가로:세로 비율 */
@@ -343,6 +618,17 @@ export declare class AgentDeckConfigProvider extends ConfigProvider {
343
618
  * 갈라야 하는데 순정 paste 는 텍스트만 알기 때문
344
619
  */
345
620
  'agentdeck-paste': string[];
621
+ /**
622
+ * 사이드바 목록에 키보드 포커스 (다시 누르면 터미널로 복귀).
623
+ *
624
+ * 기본값을 비워 두지 않은 이유 — 이것이 사이드바를 마우스 없이 만지는 **유일한
625
+ * 진입점**이라(목록은 tabindex=-1, `wireListKeys`) 안 묶여 있으면 기능이 없는 것과
626
+ * 같다. `Ctrl-Shift-L` 은 Tabby 순정 기본 핫키와 겹치지 않는다 — core·terminal·
627
+ * local·settings 네 패키지의 기본 조합을 훑어 확인했다(2026-09-09):
628
+ * 그쪽이 쓰는 것은 `Ctrl-Shift-{A,C,D,E,F,I,P,R,S,T,V,W,Z}`·방향키·Tab·PageUp/Down·
629
+ * Backspace·`-` 이다. 다른 플러그인과 부딪히면 설정 창에서 바꾼다.
630
+ */
631
+ 'agentdeck-focus-list': string[];
346
632
  };
347
633
  };
348
634
  }