tabby-agentdeck 1.0.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.
package/dist/index.js CHANGED
@@ -312,6 +312,106 @@ ___CSS_LOADER_EXPORT___.push([module.id, `body.agentdeck-active.ad-bg-clip .cont
312
312
  font-variant-numeric: tabular-nums;
313
313
  opacity: 0.55;
314
314
  }
315
+ #agentdeck-sidebar .ad-resume-head {
316
+ display: flex;
317
+ align-items: center;
318
+ gap: 5px;
319
+ margin: 5px 0 2px;
320
+ padding: 2px 4px 2px 11px;
321
+ border-radius: 4px;
322
+ color: rgba(255, 255, 255, 0.34);
323
+ font-size: 10px;
324
+ font-weight: 700;
325
+ letter-spacing: 0.06em;
326
+ cursor: pointer;
327
+ transition: background 0.1s ease, color 0.1s ease;
328
+ }
329
+ #agentdeck-sidebar .ad-resume-head:hover {
330
+ background: rgba(255, 255, 255, 0.05);
331
+ color: rgba(255, 255, 255, 0.6);
332
+ }
333
+ #agentdeck-sidebar .ad-resume-head .ad-group-count {
334
+ background: rgba(255, 255, 255, 0.06);
335
+ color: rgba(255, 255, 255, 0.45);
336
+ }
337
+ #agentdeck-sidebar .ad-resume-head .ad-group-caret {
338
+ margin-left: auto;
339
+ }
340
+ #agentdeck-sidebar .ad-resume {
341
+ display: flex;
342
+ align-items: stretch;
343
+ gap: 8px;
344
+ margin: 0 0 3px 11px;
345
+ padding: 5px 6px 5px 0;
346
+ border-radius: 5px;
347
+ cursor: pointer;
348
+ opacity: 0.66;
349
+ transition: background 0.1s ease, opacity 0.1s ease;
350
+ }
351
+ #agentdeck-sidebar .ad-resume:hover {
352
+ background: rgba(255, 255, 255, 0.05);
353
+ opacity: 1;
354
+ }
355
+ #agentdeck-sidebar .ad-resume .ad-bar {
356
+ background: #3a404b;
357
+ background-image: linear-gradient(var(--ad-panel-bg, var(--bs-body-bg, #1e2127)) 50%, transparent 0);
358
+ background-size: 100% 5px;
359
+ }
360
+ #agentdeck-sidebar .ad-resume .ad-title {
361
+ font-size: 12px;
362
+ color: rgba(255, 255, 255, 0.72);
363
+ }
364
+ #agentdeck-sidebar .ad-resume .ad-meta {
365
+ gap: 5px;
366
+ margin-top: 1px;
367
+ font-size: 10px;
368
+ color: rgba(255, 255, 255, 0.36);
369
+ }
370
+ #agentdeck-sidebar .re-was {
371
+ flex: 0 1 auto;
372
+ min-width: 0;
373
+ overflow: hidden;
374
+ text-overflow: ellipsis;
375
+ white-space: nowrap;
376
+ font-weight: 600;
377
+ }
378
+ #agentdeck-sidebar .re-was.s-done {
379
+ color: rgba(152, 195, 121, 0.62);
380
+ }
381
+ #agentdeck-sidebar .re-was.s-error {
382
+ color: rgba(224, 108, 117, 0.68);
383
+ }
384
+ #agentdeck-sidebar .re-was.s-limited {
385
+ color: rgba(198, 120, 221, 0.62);
386
+ }
387
+ #agentdeck-sidebar .re-was.s-waiting {
388
+ color: rgba(97, 175, 239, 0.62);
389
+ }
390
+ #agentdeck-sidebar .re-was.s-running {
391
+ color: rgba(229, 192, 123, 0.62);
392
+ }
393
+ #agentdeck-sidebar .re-was.s-idle {
394
+ color: rgba(255, 255, 255, 0.3);
395
+ }
396
+ #agentdeck-sidebar .re-was.live {
397
+ padding: 0 4px;
398
+ border-radius: 7px;
399
+ background: rgba(229, 192, 123, 0.16);
400
+ color: #e5c07b;
401
+ }
402
+ #agentdeck-sidebar .re-when {
403
+ flex: 0 0 auto;
404
+ margin-left: auto;
405
+ padding-left: 6px;
406
+ font-variant-numeric: tabular-nums;
407
+ }
408
+ #agentdeck-sidebar .ad-resume.open {
409
+ opacity: 0.9;
410
+ }
411
+ #agentdeck-sidebar .ad-resume.open .ad-bar {
412
+ background: #e5c07b;
413
+ background-image: none;
414
+ }
315
415
  #agentdeck-sidebar .ad-label-input {
316
416
  flex: 1 1 auto;
317
417
  min-width: 0;
@@ -1431,7 +1531,7 @@ body.agentdeck-active .window {
1431
1531
  color: rgba(255, 255, 255, 0.5);
1432
1532
  font-size: 11px;
1433
1533
  cursor: pointer;
1434
- }`, "",{"version":3,"sources":["webpack://./src/styles.scss"],"names":[],"mappings":"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","sourcesContent":["// AgentDeck 사이드바 스타일\r\n// Tabby 테마 변수(--bs-*)를 우선 쓰되, 없을 때를 대비해 폴백 색을 함께 준다.\r\n\r\n// tabby-background 의 배경 이미지 레이어(.content-tab-active::before)를 터미널 영역으로 자른다.\r\n// 좌표 변수(--ad-bg-*)는 deck.service 의 syncBackgroundClip 이 relayout 마다 내려준다.\r\n// 그 플러그인은 런타임에 <style> 을 만들어 head 뒤쪽에 붙이므로 순서로는 못 이긴다 — !important 필수.\r\nbody.agentdeck-active.ad-bg-clip .content-tab-active::before {\r\n left: var(--ad-bg-left) !important;\r\n top: var(--ad-bg-top) !important;\r\n right: auto !important;\r\n bottom: auto !important;\r\n width: var(--ad-bg-width) !important;\r\n height: var(--ad-bg-height) !important;\r\n}\r\n\r\n#agentdeck-sidebar {\r\n display: flex;\r\n flex-direction: column;\r\n // 실제 위치와 크기는 deck.service 의 relayout 이 인라인으로 준다 (도킹 방향에 따라 달라짐)\r\n z-index: 5;\r\n overflow: hidden;\r\n // --ad-panel-bg 는 agentdeck 이 sidebarOpacity 로 내려준다 (배경 이미지가 비치게)\r\n background: var(--ad-panel-bg, var(--bs-body-bg, #1e2127));\r\n font-size: 12px;\r\n user-select: none;\r\n -webkit-app-region: no-drag;\r\n\r\n // 붙어 있는 쪽 반대편에만 경계선을 그린다\r\n &.ad-dock-right { border-left: 1px solid rgba(255, 255, 255, 0.08); }\r\n &.ad-dock-left { border-right: 1px solid rgba(255, 255, 255, 0.08); }\r\n &.ad-dock-top { border-bottom: 1px solid rgba(255, 255, 255, 0.08); }\r\n &.ad-dock-bottom { border-top: 1px solid rgba(255, 255, 255, 0.08); }\r\n\r\n // 상/하 도킹은 세로가 짧으므로 목록을 가로로 흘린다\r\n &.ad-dock-top,\r\n &.ad-dock-bottom {\r\n .ad-list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-content: flex-start;\r\n gap: 0 6px;\r\n overflow-y: auto;\r\n }\r\n\r\n // 한 줄에 여러 탭이 나란히 서므로 폭을 220px 로 못박는다 — 제목 길이에 따라\r\n // 들쭉날쭉하면 어느 칸이 어느 세션인지 눈으로 못 따라가고, 제목이 바뀔 때마다 자리가 밀린다.\r\n //\r\n // **`min-width: 0` 이 있어야 `flex-basis` 가 살아남는다.** flex 아이템은\r\n // `min-width: auto`(초깃값) + `overflow: visible` 이면 **자동 최소 크기**가 걸려\r\n // 최소내용폭(min-content) 밑으로는 줄지 않는다. 그래서 제목이 긴 탭은 220 을 밀어올렸다\r\n // (2026-09-09 CDP 실측: 1700 창·탭 6개에서 220 지정인데 384.7px, 한 줄에 4개 +\r\n // 둘째 줄이 반쯤 잘림). 폭을 못박아도 제목은 읽힌다 — `.ad-title` 이 이미 말줄임을\r\n // 갖고 있고, 220 은 좌/우 도킹의 평소 줄 폭(사이드바 기본 200px)보다 오히려 넓다.\r\n .ad-tab {\r\n flex: 0 0 220px;\r\n min-width: 0;\r\n }\r\n\r\n // 그룹 헤더는 한 줄을 다 차지해 가로 흘림을 끊는다 — 그래야 헤더 아래 줄부터 그 그룹의\r\n // 탭이 흐른다. 헤더를 div 로 감싸는 대신 형제로 두는 이유가 이것이다(deck.service renderGroupHead)\r\n .ad-group-head {\r\n flex: 0 0 100%;\r\n margin-top: 2px;\r\n }\r\n }\r\n\r\n .ad-head {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 10px 12px 8px;\r\n letter-spacing: 0.12em;\r\n font-size: 10px;\r\n font-weight: 700;\r\n color: rgba(255, 255, 255, 0.4);\r\n // 헤더를 잡아 끌면 도킹 방향을 바꾼다 (OS 창 이동에 뺏기면 안 되므로 no-drag)\r\n -webkit-app-region: no-drag;\r\n cursor: grab;\r\n\r\n &:active {\r\n cursor: grabbing;\r\n }\r\n }\r\n\r\n // 경계선 — 잡아 끌면 크기가 바뀐다. 붙어 있는 쪽 반대편 가장자리에 놓는다\r\n .ad-resize {\r\n position: absolute;\r\n z-index: 6;\r\n background: transparent;\r\n transition: background 0.12s ease;\r\n\r\n &:hover,\r\n body.ad-resizing & {\r\n background: rgba(97, 175, 239, 0.5);\r\n }\r\n }\r\n\r\n .ad-resize-right,\r\n .ad-resize-left {\r\n top: 0;\r\n bottom: 0;\r\n width: 6px;\r\n cursor: col-resize;\r\n }\r\n\r\n .ad-resize-right { left: 0; }\r\n .ad-resize-left { right: 0; }\r\n\r\n .ad-resize-top,\r\n .ad-resize-bottom {\r\n left: 0;\r\n right: 0;\r\n height: 6px;\r\n cursor: row-resize;\r\n }\r\n\r\n .ad-resize-bottom { top: 0; }\r\n .ad-resize-top { bottom: 0; }\r\n\r\n // 상태별 집계 칩 한 줄 — `⏸ 2 ● 3 ○ 1`. 헤더 높이는 총 개수만 있던 때와 같게 유지한다\r\n .ad-head-count {\r\n -webkit-app-region: no-drag;\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n flex: 0 0 auto;\r\n white-space: nowrap;\r\n letter-spacing: 0;\r\n color: rgba(255, 255, 255, 0.55);\r\n }\r\n\r\n .ad-cnt {\r\n padding: 1px 6px;\r\n border-radius: 8px;\r\n background: rgba(255, 255, 255, 0.08);\r\n color: var(--ad-color, rgba(255, 255, 255, 0.55));\r\n font-size: 10px;\r\n line-height: 1.3;\r\n font-variant-numeric: tabular-nums;\r\n }\r\n\r\n // 요구 2-1: 탭이 많아지면 이 영역만 스크롤된다\r\n .ad-list {\r\n flex: 1 1 auto;\r\n overflow-y: auto;\r\n overflow-x: hidden;\r\n padding: 2px 6px 8px;\r\n // **못박아 둔다** — 드래그 자동 스크롤(deck.service `dragScrollTick`)이 매 프레임\r\n // `scrollTop` 에 몇 px 를 더하는 식으로 목록을 흘린다. 여기가 `smooth` 면 그 대입마다\r\n // 새 애니메이션이 시작돼 서로를 덮어써서 목록이 기어가거나 아예 멈춘다.\r\n // 지금 이 값을 `smooth` 로 켜는 곳은 없지만(전역 검색 0건), 나중에 누가 켜면\r\n // 그 기능이 **조용히** 못 쓰게 되는 자리라 의도를 여기 남긴다.\r\n scroll-behavior: auto;\r\n\r\n &::-webkit-scrollbar {\r\n width: 8px;\r\n }\r\n\r\n &::-webkit-scrollbar-thumb {\r\n background: rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.24);\r\n }\r\n }\r\n }\r\n\r\n // 프로젝트(작업 폴더) 그룹 헤더 — `▾ tabby-agentdeck 3`. 그룹이 둘 이상일 때만 그린다.\r\n // 헤더 자체는 탭보다 작고 어둡게 둔다 — 세션 목록이 주인공이고 이건 칸막이다.\r\n .ad-group-head {\r\n display: flex;\r\n align-items: center;\r\n gap: 5px;\r\n margin: 7px 0 2px;\r\n padding: 2px 4px;\r\n border-radius: 4px;\r\n color: rgba(255, 255, 255, 0.42);\r\n font-size: 10px;\r\n font-weight: 700;\r\n letter-spacing: 0.06em;\r\n cursor: pointer;\r\n transition: background 0.1s ease, color 0.1s ease;\r\n\r\n // 첫 그룹은 목록 맨 위에 붙어야 자연스럽다 (.ad-list 가 이미 위쪽 여백을 준다)\r\n &:first-child {\r\n margin-top: 1px;\r\n }\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.05);\r\n color: rgba(255, 255, 255, 0.7);\r\n }\r\n\r\n // 접힌 그룹은 \"여기 뭔가 숨어 있다\" 가 보여야 한다 — 개수 칩을 살짝 밝게\r\n &.collapsed .ad-group-count {\r\n background: rgba(255, 255, 255, 0.14);\r\n color: rgba(255, 255, 255, 0.75);\r\n }\r\n }\r\n\r\n .ad-group-caret {\r\n flex: 0 0 auto;\r\n width: 9px;\r\n font-size: 9px;\r\n line-height: 1;\r\n opacity: 0.75;\r\n }\r\n\r\n .ad-group-label {\r\n // 폴더 이름은 길 수 있다(사이드바는 200~560px) — 개수 칩을 밀어내지 않게 줄이고 말줄임\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n // 검색 때문에 임시로 펴 둔 그룹 — 캐럿만 흐리게 해서 \"내가 접어 둔 그룹인데 지금은\r\n // 검색 때문에 보이는 중\" 임을 알린다. 이유 전문은 헤더 title 에 있다(renderGroupHead).\r\n // 줄 배경이나 라벨 색을 바꾸지 않는 이유: 검색 결과 자체가 이미 강조된 화면이라\r\n // 여기서 또 강조하면 무엇이 걸린 줄인지가 흐려진다.\r\n .ad-group-head.filter-open .ad-group-caret {\r\n opacity: 0.45;\r\n }\r\n\r\n .ad-group-count {\r\n flex: 0 0 auto;\r\n padding: 0 5px;\r\n border-radius: 8px;\r\n background: rgba(255, 255, 255, 0.08);\r\n color: rgba(255, 255, 255, 0.55);\r\n font-size: 9.5px;\r\n line-height: 1.5;\r\n letter-spacing: 0;\r\n font-variant-numeric: tabular-nums;\r\n }\r\n\r\n .ad-tab {\r\n position: relative;\r\n display: flex;\r\n align-items: stretch;\r\n gap: 8px;\r\n margin-bottom: 3px;\r\n padding: 7px 6px 7px 0;\r\n border-radius: 5px;\r\n cursor: pointer;\r\n transition: background 0.1s ease;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.05);\r\n\r\n .ad-close {\r\n opacity: 0.65;\r\n }\r\n }\r\n\r\n &.active {\r\n background: rgba(255, 255, 255, 0.1);\r\n\r\n .ad-title {\r\n color: #fff;\r\n }\r\n }\r\n }\r\n\r\n // 상태색 표시 바\r\n .ad-bar {\r\n flex: 0 0 3px;\r\n border-radius: 2px;\r\n background: var(--ad-color, #6b727d);\r\n }\r\n\r\n .ad-body {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n }\r\n\r\n .ad-title {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: rgba(255, 255, 255, 0.82);\r\n font-size: 12.5px;\r\n line-height: 1.35;\r\n }\r\n\r\n .ad-meta {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n margin-top: 2px;\r\n font-size: 10.5px;\r\n line-height: 1.4;\r\n color: rgba(255, 255, 255, 0.45);\r\n }\r\n\r\n .ad-badge {\r\n // 이유가 붙으면(승인대기 · Bash 권한) 길어질 수 있다 — 경과시간을 밀어내지 않게 줄이고 말줄임\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: var(--ad-color, #6b727d);\r\n font-weight: 600;\r\n\r\n // 수동/훅으로 고정된 상태는 밑줄로 구분\r\n &.pinned {\r\n text-decoration: underline dotted;\r\n text-underline-offset: 2px;\r\n }\r\n }\r\n\r\n // 도는 서브에이전트 개수 (`❖3`) — 상태 배지 바로 뒤. 자리 선정 이유는 deck.service `renderTab` 주석\r\n .ad-subagents {\r\n // 배지가 길어져도 이건 줄이지 않는다 — 개수는 한두 글자고, 줄어들면 읽을 수가 없다\r\n flex: 0 0 auto;\r\n padding: 0 4px;\r\n border-radius: 7px;\r\n // 상태색(`--ad-color`)을 쓰지 않는다 — 상태와 다른 축의 정보라 같은 색이면 배지의\r\n // 일부로 읽힌다. 배경까지 넣어 숫자가 상태 문구에 섞이지 않게 칩으로 떼어 둔다\r\n background: rgba(86, 182, 194, 0.18);\r\n color: #56b6c2;\r\n font-weight: 600;\r\n font-variant-numeric: tabular-nums;\r\n }\r\n\r\n // 두 번째 줄 — 마지막으로 보낸 프롬프트\r\n .ad-prompt {\r\n display: flex;\r\n align-items: center;\r\n margin-top: 2px;\r\n min-width: 0;\r\n font-size: 11px;\r\n line-height: 1.4;\r\n }\r\n\r\n .ad-label {\r\n flex: 1 1 auto;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: rgba(255, 255, 255, 0.6);\r\n\r\n // 아직 프롬프트가 없는 탭 — 자리는 지키되 눈에 덜 걸리게\r\n &.empty {\r\n color: rgba(255, 255, 255, 0.25);\r\n }\r\n }\r\n\r\n .ad-elapsed {\r\n flex: 0 0 auto;\r\n margin-left: auto;\r\n font-variant-numeric: tabular-nums;\r\n opacity: 0.55;\r\n }\r\n\r\n .ad-label-input {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n padding: 0 4px;\r\n border: 1px solid rgba(255, 255, 255, 0.25);\r\n border-radius: 3px;\r\n background: rgba(0, 0, 0, 0.35);\r\n color: #fff;\r\n font-size: 10.5px;\r\n outline: none;\r\n\r\n &:focus {\r\n border-color: var(--ad-color, #61afef);\r\n }\r\n }\r\n\r\n .ad-close {\r\n flex: 0 0 auto;\r\n align-self: center;\r\n width: 18px;\r\n height: 18px;\r\n padding: 0;\r\n border: 0;\r\n border-radius: 3px;\r\n opacity: 0;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.8);\r\n font-size: 15px;\r\n line-height: 1;\r\n cursor: pointer;\r\n transition: opacity 0.1s ease, background 0.1s ease;\r\n\r\n &:hover {\r\n opacity: 1 !important;\r\n background: rgba(255, 80, 80, 0.35);\r\n }\r\n }\r\n\r\n .ad-foot {\r\n display: flex;\r\n gap: 6px;\r\n padding: 8px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.07);\r\n }\r\n\r\n .ad-btn {\r\n flex: 1 1 auto;\r\n padding: 5px 8px;\r\n border: 1px solid rgba(255, 255, 255, 0.12);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.6);\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.08);\r\n color: #fff;\r\n }\r\n }\r\n\r\n // 글자가 한 자뿐인 버튼들은 나머지와 같은 비율로 늘어나면 허전하다\r\n .ad-repair,\r\n .ad-viewtoggle {\r\n flex: 0 0 auto;\r\n min-width: 28px;\r\n font-size: 13px;\r\n line-height: 1;\r\n }\r\n // ---------- 세션 검색 / 상태 필터 (deck.service buildSidebar · wireSearch) ----------\r\n // 유닛 T 가 넘긴 CSS — 그 유닛은 이 파일을 소유하지 않아 배리어에서 메인이 넣는다.\r\n //\r\n // 검색 줄은 헤더와 목록 사이의 별도 flex 아이템이다. `.ad-list` 안에 두면 `render()` 의\r\n // innerHTML 초기화에 입력창이 매 글자마다 새로 만들어져 포커스·IME 조합이 날아간다.\r\n .ad-search {\r\n display: flex;\r\n flex: 0 0 auto;\r\n gap: 4px;\r\n align-items: center;\r\n padding: 0 8px 6px;\r\n -webkit-app-region: no-drag;\r\n }\r\n\r\n .ad-search-input {\r\n // 사이드바는 200~560px — 입력창이 남는 폭을 다 쓰되 줄어들 때 버튼을 밀지 않게\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n padding: 3px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n outline: none;\r\n background: rgba(0, 0, 0, 0.28);\r\n color: rgba(255, 255, 255, 0.85);\r\n font-size: 11px;\r\n line-height: 1.4;\r\n\r\n &::placeholder {\r\n color: rgba(255, 255, 255, 0.3);\r\n }\r\n\r\n &:focus {\r\n border-color: rgba(97, 175, 239, 0.7);\r\n color: #fff;\r\n }\r\n }\r\n\r\n // 지우기 버튼은 **걸린 필터가 있을 때만** 보인다 (deck.service syncSearchUi 가 `.has-query`).\r\n // 늘 자리를 잡으면 입력창이 그만큼 좁아지고, 빈 칸 옆의 ✕ 는 무엇을 지우는지 알 수 없다.\r\n .ad-search-clear {\r\n display: none;\r\n flex: 0 0 auto;\r\n width: 18px;\r\n height: 18px;\r\n padding: 0;\r\n border: 0;\r\n border-radius: 3px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.45);\r\n font-size: 14px;\r\n line-height: 1;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.12);\r\n color: #fff;\r\n }\r\n }\r\n\r\n .ad-search.has-query .ad-search-clear {\r\n display: block;\r\n }\r\n\r\n // 집계 칩은 상태 필터의 조작부다 (renderHeadCount → toggleStatusFilter).\r\n // 위쪽 `.ad-cnt` 규칙에 \"누를 수 있다\" 와 \"지금 걸려 있다\" 만 덧붙인다.\r\n .ad-cnt {\r\n cursor: pointer;\r\n // 헤더는 도킹 드래그 핸들이라 글자가 잡히면 드래그가 지저분해진다\r\n user-select: none;\r\n transition: background 0.1s ease, color 0.1s ease;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.16);\r\n }\r\n\r\n // 걸려 있는 필터 — 배경 알파는 SCSS 가 var() 를 계산할 수 없으니\r\n // 상태색은 안쪽 테두리와 글자색으로만 쓴다\r\n &.active {\r\n box-shadow: inset 0 0 0 1px var(--ad-color, #61afef);\r\n background: rgba(255, 255, 255, 0.2);\r\n color: var(--ad-color, #fff);\r\n }\r\n }\r\n\r\n // 필터에 아무것도 안 걸렸을 때의 한 줄 (deck.service renderEmpty).\r\n // 빈 목록만 남기면 \"탭이 다 닫혔다\" 로 읽히므로 이유를 글로 남긴다.\r\n .ad-empty {\r\n padding: 10px 8px;\r\n color: rgba(255, 255, 255, 0.42);\r\n text-align: center;\r\n word-break: break-all;\r\n font-size: 11px;\r\n line-height: 1.5;\r\n }\r\n\r\n // 키보드 포커스 링 (deck.service `isNavFocused`) — **활성 탭 표시와 다른 것이어야 한다.**\r\n // 활성(선택)은 배경으로, 키보드 포커스는 테두리로 말한다. 둘을 같은 모양으로 그리면\r\n // \"지금 보고 있는 줄\" 과 \"지금 쓰고 있는 탭\" 이 화면에서 구별되지 않고, 그 구별이 이\r\n // 기능의 전부다(이동만으로는 탭이 바뀌지 않는다, `navFocus` 주석).\r\n //\r\n // outline 대신 inset box-shadow 를 쓰는 이유 둘 — outline 은 `.ad-list` 의\r\n // `overflow-y: auto` 경계에서 잘리고, border 는 줄 높이를 1px 밀어 포커스가 지나갈 때마다\r\n // 목록 전체가 흔들린다. 상태색(`--ad-color`)을 쓰지 않는 것도 일부러다: 포커스는 세션 상태와\r\n // 무관한 \"내 손이 어디 있나\" 라서, 상태색으로 그리면 배지 색과 섞여 읽힌다.\r\n .ad-tab.ad-nav-focus,\r\n .ad-group-head.ad-nav-focus {\r\n box-shadow: inset 0 0 0 1px rgba(97, 175, 239, 0.85);\r\n background: rgba(97, 175, 239, 0.1);\r\n }\r\n\r\n // 포커스가 활성 탭 위에 있을 때 — 배경은 활성 쪽(밝은 회색)을 유지하고 테두리만 얹는다.\r\n // 그래야 \"이 줄이 지금 쓰는 탭이고, 포커스도 여기 있다\" 가 한 줄에서 다 읽힌다\r\n .ad-tab.active.ad-nav-focus {\r\n background: rgba(255, 255, 255, 0.14);\r\n }\r\n\r\n // 목록 자체가 포커스를 받는다(tabindex=-1) — 브라우저 기본 outline 은 지운다.\r\n // 사각형이 목록 전체에 둘러쳐지면 어느 **줄**에 있는지가 오히려 안 보인다\r\n .ad-list:focus,\r\n .ad-list:focus-visible {\r\n outline: none;\r\n }\r\n\r\n // 끌고 있는 줄 — 원래 자리에 흐릿하게 남긴다. 줄을 실제로 옮겨 그리지 않는 이유는\r\n // deck.service `render` 의 드래그 가드에 있다(목록을 다시 만들면 끌던 DOM 이 사라진다).\r\n // \"무엇을 들고 있나\" 는 이 흐릿한 줄이, \"어디에 놓이나\" 는 삽입선(.ad-reorder-line)이 말한다.\r\n .ad-tab.ad-dragging {\r\n opacity: 0.45;\r\n background: rgba(255, 255, 255, 0.06);\r\n outline: 1px dashed rgba(255, 255, 255, 0.28);\r\n outline-offset: -1px;\r\n }\r\n\r\n // 순서를 바꿀 수 없는 상태에서 끌었을 때의 한 줄 (deck.service noteReorderBlocked).\r\n // 목록 위에 겹쳐 띄운다 — flex 자식으로 흐르면 줄들이 밀려 드래그하던 자리가 움직인다.\r\n .ad-reorder-note {\r\n position: absolute;\r\n left: 8px;\r\n right: 8px;\r\n bottom: 34px;\r\n z-index: 3;\r\n padding: 7px 9px;\r\n border: 1px solid rgba(255, 255, 255, 0.12);\r\n border-radius: 5px;\r\n background: rgba(38, 42, 49, 0.96);\r\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);\r\n color: rgba(255, 255, 255, 0.78);\r\n font-size: 11px;\r\n line-height: 1.45;\r\n // 안내를 누르려는 손짓이 아래 줄의 클릭(탭 선택)으로 가지 않게\r\n pointer-events: none;\r\n }\r\n\r\n // 상/하 도킹 — 세로가 짧다(기본 sidebarHeight 200px). 검색 줄 높이를 줄이고,\r\n // 가로로 흐르는 목록에서 안내 한 줄은 폭을 다 차지하게 한다 (`.ad-group-head` 와 같은 이유)\r\n &.ad-dock-top .ad-search,\r\n &.ad-dock-bottom .ad-search {\r\n padding: 0 8px 4px;\r\n }\r\n\r\n &.ad-dock-top .ad-list .ad-empty,\r\n &.ad-dock-bottom .ad-list .ad-empty {\r\n flex: 0 0 100%;\r\n }\r\n}\r\n\r\n// ---------- 결과물 미리보기 패널 (viewPanel.ts) ----------\r\n// 사이드바와 같은 규칙을 따른다 — 위치·폭은 deck.service 의 relayout 이 인라인으로 준다.\r\n#agentdeck-view {\r\n display: flex;\r\n flex-direction: column;\r\n z-index: 5;\r\n overflow: hidden;\r\n background: var(--ad-panel-bg, var(--bs-body-bg, #1e2127));\r\n font-size: 12px;\r\n -webkit-app-region: no-drag;\r\n\r\n // 터미널과 닿는 쪽에만 경계선\r\n &.ad-view-right { border-left: 1px solid rgba(255, 255, 255, 0.08); }\r\n &.ad-view-left { border-right: 1px solid rgba(255, 255, 255, 0.08); }\r\n\r\n &.ad-view-dropping {\r\n outline: 1px dashed rgba(97, 175, 239, 0.8);\r\n outline-offset: -3px;\r\n }\r\n\r\n .ad-view-head {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 6px;\r\n padding: 8px 8px 6px 12px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n user-select: none;\r\n }\r\n\r\n .ad-view-title {\r\n overflow: hidden;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n color: rgba(255, 255, 255, 0.82);\r\n font-weight: 600;\r\n }\r\n\r\n .ad-view-tools {\r\n display: flex;\r\n flex: 0 0 auto;\r\n gap: 2px;\r\n }\r\n\r\n .ad-view-btn {\r\n min-width: 24px;\r\n padding: 2px 5px;\r\n border: 0;\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.5);\r\n font-size: 12px;\r\n line-height: 1.2;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.1);\r\n color: #fff;\r\n }\r\n\r\n // 켜져 있는 도구(편집 중의 ✎) — 지금 어떤 상태인지 버튼만 보고 알 수 있어야 한다\r\n &.ad-view-btn-on {\r\n background: rgba(97, 175, 239, 0.22);\r\n color: #9ecbff;\r\n }\r\n }\r\n\r\n .ad-view-path {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n padding: 6px 8px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n\r\n input {\r\n width: 100%;\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: rgba(0, 0, 0, 0.25);\r\n color: #e6e6e6;\r\n font-family: monospace;\r\n font-size: 11px;\r\n\r\n &:focus {\r\n outline: 0;\r\n border-color: rgba(97, 175, 239, 0.7);\r\n }\r\n }\r\n }\r\n\r\n // 경로 줄 안내 — \"없는 파일이다, 한 번 더 Enter\" 같은 말이 여기 뜬다\r\n .ad-view-path-note {\r\n width: 100%;\r\n overflow: hidden;\r\n color: rgba(229, 192, 123, 0.9);\r\n font-size: 11px;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n\r\n &:empty {\r\n display: none;\r\n }\r\n }\r\n\r\n // 찾기 줄 (Ctrl+F). 경로 줄과 같은 자리·같은 옷을 입힌다 — 둘 다 헤더 아래 한 줄짜리 도구다\r\n .ad-view-find {\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 6px 8px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n\r\n input {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: rgba(0, 0, 0, 0.25);\r\n color: #e6e6e6;\r\n font-family: monospace;\r\n font-size: 11px;\r\n\r\n &:focus {\r\n outline: 0;\r\n border-color: rgba(97, 175, 239, 0.7);\r\n }\r\n }\r\n }\r\n\r\n .ad-view-find-count {\r\n flex: 0 0 auto;\r\n min-width: 44px;\r\n color: rgba(255, 255, 255, 0.45);\r\n font-size: 11px;\r\n text-align: right;\r\n }\r\n\r\n // 일치 표시. 지금 보고 있는 것 하나만 진하게 — 개수가 많아도 어디에 있는지 눈에 든다\r\n mark.ad-find-hit {\r\n border-radius: 2px;\r\n background: rgba(229, 192, 123, 0.35);\r\n color: inherit;\r\n }\r\n\r\n mark.ad-find-cur {\r\n background: rgba(229, 192, 123, 0.85);\r\n color: #1a1a1a;\r\n }\r\n\r\n // 편집 줄 — 저장/취소는 본문 밖에 둔다 (본문은 갱신마다 다시 그려진다)\r\n .ad-view-editbar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n padding: 5px 8px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.06);\r\n background: rgba(0, 0, 0, 0.18);\r\n }\r\n\r\n .ad-view-edit-note {\r\n overflow: hidden;\r\n color: rgba(255, 255, 255, 0.5);\r\n font-size: 11px;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n .ad-view-edit-btns {\r\n display: flex;\r\n flex: 0 0 auto;\r\n gap: 4px;\r\n }\r\n\r\n .ad-view-edit-save,\r\n .ad-view-edit-cancel {\r\n padding: 2px 10px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.75);\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover:not(:disabled) {\r\n border-color: rgba(255, 255, 255, 0.3);\r\n background: rgba(255, 255, 255, 0.08);\r\n color: #fff;\r\n }\r\n\r\n &:disabled {\r\n opacity: 0.35;\r\n cursor: default;\r\n }\r\n }\r\n\r\n .ad-view-edit-save {\r\n border-color: rgba(97, 175, 239, 0.5);\r\n color: #9ecbff;\r\n }\r\n\r\n // 편집기 — 본문 자리를 그대로 채운다. 글꼴·줄간격을 코드 보기와 맞춰야 편집 전후로\r\n // 글자가 튀지 않는다\r\n textarea.ad-view-edit {\r\n display: block;\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 100%;\r\n min-height: 200px;\r\n padding: 6px 8px;\r\n border: 0;\r\n background: rgba(0, 0, 0, 0.22);\r\n color: #e6e6e6;\r\n font-family: monospace;\r\n font-size: 12px;\r\n line-height: 1.5;\r\n resize: none;\r\n tab-size: 4;\r\n\r\n &:focus {\r\n outline: 0;\r\n }\r\n }\r\n\r\n // 최근 파일 칩 — Codex 앱의 결과물 탭 자리\r\n .ad-view-tabs {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n padding: 6px 8px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n user-select: none;\r\n }\r\n\r\n .ad-view-tab {\r\n max-width: 100%;\r\n overflow: hidden;\r\n padding: 2px 7px;\r\n border: 1px solid rgba(255, 255, 255, 0.1);\r\n border-radius: 10px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.55);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n color: #fff;\r\n border-color: rgba(255, 255, 255, 0.25);\r\n }\r\n\r\n &.ad-view-tab-on {\r\n border-color: rgba(97, 175, 239, 0.6);\r\n background: rgba(97, 175, 239, 0.14);\r\n color: #cfe6ff;\r\n }\r\n }\r\n\r\n .ad-view-body {\r\n flex: 1 1 auto;\r\n overflow: auto;\r\n padding: 10px 12px 16px;\r\n color: rgba(255, 255, 255, 0.85);\r\n // 문서를 읽는 자리라 글자 선택은 열어 둔다 (사이드바는 막아 둔다)\r\n user-select: text;\r\n }\r\n\r\n .ad-view-foot {\r\n flex: 0 0 auto;\r\n overflow: hidden;\r\n padding: 5px 10px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.06);\r\n color: rgba(255, 255, 255, 0.35);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-size: 10px;\r\n }\r\n\r\n // 폭 조절 손잡이 — 터미널과 닿는 쪽 경계선\r\n .ad-view-resize {\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n width: 6px;\r\n cursor: col-resize;\r\n z-index: 6;\r\n }\r\n\r\n &.ad-view-right .ad-view-resize { left: -2px; }\r\n &.ad-view-left .ad-view-resize { right: -2px; }\r\n\r\n .ad-view-empty {\r\n padding: 8px 2px;\r\n color: rgba(255, 255, 255, 0.55);\r\n line-height: 1.7;\r\n\r\n ul { padding-left: 18px; margin: 6px 0 0; }\r\n li { margin: 2px 0; }\r\n }\r\n\r\n .ad-view-dim {\r\n color: rgba(255, 255, 255, 0.35);\r\n font-size: 11px;\r\n }\r\n\r\n // ---------- 종류별 본문 ----------\r\n\r\n .ad-view-image {\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: center;\r\n\r\n img {\r\n max-width: 100%;\r\n // 원본보다 키우지 않는다 — 확대하면 스크린샷 글자가 뭉개진다\r\n height: auto;\r\n background: rgba(255, 255, 255, 0.04);\r\n }\r\n }\r\n\r\n .ad-view-code {\r\n display: flex;\r\n gap: 8px;\r\n font-family: monospace;\r\n font-size: 11px;\r\n line-height: 1.5;\r\n\r\n pre {\r\n margin: 0;\r\n white-space: pre;\r\n }\r\n\r\n .ad-view-gutter {\r\n flex: 0 0 auto;\r\n color: rgba(255, 255, 255, 0.25);\r\n text-align: right;\r\n user-select: none;\r\n }\r\n\r\n .ad-view-src {\r\n flex: 1 1 auto;\r\n overflow-x: auto;\r\n color: rgba(255, 255, 255, 0.85);\r\n }\r\n }\r\n\r\n .ad-view-table {\r\n overflow-x: auto;\r\n\r\n table {\r\n border-collapse: collapse;\r\n font-size: 11px;\r\n }\r\n\r\n th, td {\r\n max-width: 260px;\r\n overflow: hidden;\r\n padding: 3px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.08);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n }\r\n\r\n th {\r\n position: sticky;\r\n top: 0;\r\n background: #262a31;\r\n color: rgba(255, 255, 255, 0.7);\r\n }\r\n\r\n .ad-view-rownum {\r\n color: rgba(255, 255, 255, 0.3);\r\n text-align: right;\r\n }\r\n }\r\n\r\n // ---------- 마크다운 ----------\r\n .ad-view-md {\r\n line-height: 1.65;\r\n word-break: break-word;\r\n\r\n h1, h2, h3, h4, h5, h6 {\r\n margin: 1.1em 0 0.5em;\r\n color: #fff;\r\n line-height: 1.3;\r\n }\r\n h1 { font-size: 18px; }\r\n h2 { font-size: 16px; padding-bottom: 3px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }\r\n h3 { font-size: 14px; }\r\n h4, h5, h6 { font-size: 13px; }\r\n\r\n p { margin: 0.6em 0; }\r\n\r\n a {\r\n color: #61afef;\r\n text-decoration: none;\r\n cursor: pointer;\r\n\r\n &:hover { text-decoration: underline; }\r\n }\r\n\r\n code {\r\n padding: 1px 4px;\r\n border-radius: 3px;\r\n background: rgba(255, 255, 255, 0.08);\r\n font-family: monospace;\r\n font-size: 11px;\r\n }\r\n\r\n pre {\r\n overflow-x: auto;\r\n margin: 0.7em 0;\r\n padding: 8px 10px;\r\n border: 1px solid rgba(255, 255, 255, 0.07);\r\n border-radius: 5px;\r\n background: rgba(0, 0, 0, 0.28);\r\n\r\n code {\r\n padding: 0;\r\n background: transparent;\r\n line-height: 1.5;\r\n }\r\n }\r\n\r\n ul, ol { padding-left: 20px; margin: 0.5em 0; }\r\n li { margin: 0.15em 0; }\r\n\r\n .ad-md-task { list-style: none; margin-left: -14px; }\r\n .ad-md-check { color: rgba(255, 255, 255, 0.6); }\r\n\r\n blockquote {\r\n margin: 0.7em 0;\r\n padding: 2px 0 2px 10px;\r\n border-left: 3px solid rgba(255, 255, 255, 0.18);\r\n color: rgba(255, 255, 255, 0.6);\r\n }\r\n\r\n hr {\r\n margin: 1.2em 0;\r\n border: 0;\r\n border-top: 1px solid rgba(255, 255, 255, 0.12);\r\n }\r\n\r\n table {\r\n display: block;\r\n overflow-x: auto;\r\n border-collapse: collapse;\r\n margin: 0.7em 0;\r\n font-size: 11px;\r\n }\r\n\r\n th, td {\r\n padding: 4px 8px;\r\n border: 1px solid rgba(255, 255, 255, 0.1);\r\n text-align: left;\r\n }\r\n\r\n th { background: rgba(255, 255, 255, 0.05); color: #fff; }\r\n\r\n img {\r\n max-width: 100%;\r\n border-radius: 4px;\r\n }\r\n\r\n .ad-md-noimg {\r\n padding: 1px 5px;\r\n border-radius: 3px;\r\n background: rgba(255, 255, 255, 0.06);\r\n color: rgba(255, 255, 255, 0.45);\r\n font-size: 11px;\r\n }\r\n }\r\n\r\n // ---------- 변경 탭 (파일/변경 스위치 + git diff) ----------\r\n // 최근 파일 칩과 같은 모양을 쓴다 — 같은 자리에서 같은 방식으로 고르는 것이라서.\r\n .ad-view-modes {\r\n display: flex;\r\n flex: 0 0 auto;\r\n gap: 4px;\r\n padding: 6px 8px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n user-select: none;\r\n }\r\n\r\n .ad-view-mode {\r\n flex: 0 0 auto;\r\n padding: 3px 12px;\r\n border: 1px solid rgba(255, 255, 255, 0.1);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.55);\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n border-color: rgba(255, 255, 255, 0.25);\r\n color: #fff;\r\n }\r\n\r\n &.ad-view-mode-on {\r\n border-color: rgba(97, 175, 239, 0.6);\r\n background: rgba(97, 175, 239, 0.14);\r\n color: #cfe6ff;\r\n }\r\n }\r\n\r\n .ad-view-diff {\r\n .ad-plus { color: #98c379; }\r\n .ad-minus { color: #e06c75; }\r\n }\r\n\r\n // `2 files changed +9 -6` 자리\r\n .ad-view-diff-sum {\r\n padding-bottom: 6px;\r\n color: rgba(255, 255, 255, 0.8);\r\n font-weight: 600;\r\n }\r\n\r\n .ad-view-diff-list {\r\n margin-bottom: 10px;\r\n padding-bottom: 6px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\r\n }\r\n\r\n .ad-view-diff-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n width: 100%;\r\n padding: 2px 4px;\r\n border: 0;\r\n border-radius: 3px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.7);\r\n text-align: left;\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover { background: rgba(255, 255, 255, 0.07); }\r\n\r\n .ad-view-diff-st {\r\n flex: 0 0 auto;\r\n min-width: 40px;\r\n color: rgba(255, 255, 255, 0.4);\r\n text-align: center;\r\n font-size: 10px;\r\n }\r\n\r\n .ad-view-diff-st-added { color: #98c379; }\r\n .ad-view-diff-st-deleted { color: #e06c75; }\r\n .ad-view-diff-st-renamed { color: #61afef; }\r\n\r\n .ad-view-diff-name {\r\n flex: 1 1 auto;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n // 경로는 등폭이 읽기 쉽다 — 같은 폴더가 세로로 줄을 맞춘다\r\n font-family: monospace;\r\n }\r\n\r\n .ad-view-diff-num {\r\n flex: 0 0 auto;\r\n\r\n b {\r\n margin-left: 5px;\r\n font-weight: 500;\r\n }\r\n }\r\n }\r\n\r\n .ad-view-diff-sub {\r\n margin: 4px 0;\r\n color: rgba(255, 255, 255, 0.4);\r\n font-size: 11px;\r\n }\r\n\r\n .ad-view-diff-untracked {\r\n margin-bottom: 10px;\r\n padding-bottom: 6px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\r\n\r\n .ad-view-diff-u {\r\n overflow: hidden;\r\n padding: 1px 4px;\r\n color: rgba(255, 255, 255, 0.5);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-family: monospace;\r\n font-size: 11px;\r\n\r\n &[data-ad-open] {\r\n cursor: pointer;\r\n\r\n &:hover { background: rgba(255, 255, 255, 0.07); color: #fff; }\r\n }\r\n }\r\n }\r\n\r\n .ad-view-diff-file {\r\n margin-bottom: 14px;\r\n }\r\n\r\n // 파일명은 스크롤해도 남아 있어야 한다 — 긴 diff 를 내려가다 보면 어느 파일인지 잊는다\r\n .ad-view-diff-fhead {\r\n position: sticky;\r\n top: 0;\r\n z-index: 1;\r\n overflow: hidden;\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.08);\r\n border-radius: 4px 4px 0 0;\r\n background: #262a31;\r\n color: rgba(255, 255, 255, 0.8);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-family: monospace;\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover { color: #fff; }\r\n }\r\n\r\n .ad-view-diff-note {\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.08);\r\n border-top: 0;\r\n color: rgba(255, 255, 255, 0.35);\r\n font-size: 11px;\r\n }\r\n\r\n .ad-view-diff-lines {\r\n overflow-x: auto;\r\n border: 1px solid rgba(255, 255, 255, 0.08);\r\n border-top: 0;\r\n font-family: monospace;\r\n font-size: 11px;\r\n line-height: 1.5;\r\n }\r\n\r\n // 한 줄 = 구 번호 · 신 번호 · 본문. 배경이 줄 전체를 덮어야 추가/삭제가 눈에 띈다\r\n .ad-dl {\r\n display: flex;\r\n // 가로 스크롤 안에서도 배경이 끊기지 않게 (본문이 넘치면 줄이 넓어진다)\r\n min-width: 100%;\r\n min-height: 17px;\r\n white-space: pre;\r\n\r\n .ad-dl-no {\r\n flex: 0 0 auto;\r\n width: 38px;\r\n padding-right: 6px;\r\n color: rgba(255, 255, 255, 0.25);\r\n text-align: right;\r\n user-select: none;\r\n }\r\n\r\n .ad-dl-tx {\r\n flex: 1 0 auto;\r\n padding-right: 8px;\r\n color: rgba(255, 255, 255, 0.8);\r\n }\r\n }\r\n\r\n .ad-dl-add {\r\n background: rgba(152, 195, 121, 0.12);\r\n\r\n .ad-dl-tx { color: #b7d99b; }\r\n }\r\n\r\n .ad-dl-del {\r\n background: rgba(224, 108, 117, 0.12);\r\n\r\n .ad-dl-tx { color: #eda3a9; }\r\n }\r\n\r\n .ad-dl-hunk {\r\n background: rgba(97, 175, 239, 0.1);\r\n\r\n .ad-dl-tx { color: rgba(97, 175, 239, 0.85); }\r\n }\r\n\r\n // ---------- 스테이지 딱지 (파일 목록 / 추적 안 됨 줄) ----------\r\n // 유닛 L 이 넘긴 CSS — 그 유닛은 이 파일을 소유하지 않아 배리어에서 메인이 넣는다.\r\n // 상태 색은 `.ad-view-diff-st-*` 와 같은 팔레트를 쓴다 (같은 줄에서 두 딱지가 싸우지 않게).\r\n .ad-view-stage {\r\n flex: 0 0 auto;\r\n min-width: 44px;\r\n padding: 0 4px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 3px;\r\n color: rgba(255, 255, 255, 0.45);\r\n text-align: center;\r\n font-size: 10px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n border-color: rgba(255, 255, 255, 0.4);\r\n color: #fff;\r\n }\r\n }\r\n\r\n .ad-view-stage-staged {\r\n border-color: rgba(152, 195, 121, 0.5);\r\n background: rgba(152, 195, 121, 0.14);\r\n color: #b7d99b;\r\n }\r\n\r\n .ad-view-stage-both {\r\n border-color: rgba(229, 192, 123, 0.5);\r\n background: rgba(229, 192, 123, 0.12);\r\n color: #e5c07b;\r\n }\r\n\r\n .ad-view-stage-conflict {\r\n border-color: rgba(224, 108, 117, 0.6);\r\n background: rgba(224, 108, 117, 0.14);\r\n color: #eda3a9;\r\n }\r\n\r\n // ---------- `파일:라인` 참조 ----------\r\n // 유닛 U 가 넘긴 CSS — 그 유닛은 이 파일을 소유하지 않아 배리어에서 메인이 넣는다.\r\n //\r\n // 누를 수 있는 줄은 커서와 왼쪽 선으로 알린다. 줄번호 칸은 `user-select: none` 이라\r\n // 거기를 눌러도 글자 선택이 시작되지 않는다 (본문 복사와 싸우지 않게)\r\n .ad-dl[data-ad-ref-line],\r\n .ad-dl[data-ad-ref-del],\r\n .ad-cl[data-ad-ref-line] {\r\n cursor: pointer;\r\n }\r\n\r\n .ad-dl[data-ad-ref-line]:hover,\r\n .ad-cl[data-ad-ref-line]:hover {\r\n box-shadow: inset 2px 0 0 rgba(97, 175, 239, 0.55);\r\n }\r\n\r\n // 고른 줄. 추가/삭제 배경(`.ad-dl-add`/`.ad-dl-del`)을 덮어야 하므로 두 클래스로 특정한다\r\n .ad-dl.ad-ref-sel,\r\n .ad-cl.ad-ref-sel {\r\n box-shadow: inset 2px 0 0 #61afef;\r\n background: rgba(97, 175, 239, 0.22);\r\n\r\n .ad-dl-no,\r\n .ad-cl-no {\r\n color: rgba(255, 255, 255, 0.6);\r\n }\r\n\r\n .ad-dl-tx,\r\n .ad-cl-tx {\r\n color: #fff;\r\n }\r\n }\r\n\r\n // ---------- `파일` 모드 코드 보기: 줄마다 한 칸 ----------\r\n // 원래는 `<pre>` 두 장을 좌우로 놓는 flex 였다. 줄 단위로 그리게 되면서(어느 줄을 눌렀는지\r\n // 알아야 한다) 세로로 쌓아야 하므로 배치만 되돌린다 — 글꼴·크기·줄높이는 그대로 쓴다\r\n .ad-view-code.ad-view-code-rows {\r\n display: block;\r\n overflow-x: auto;\r\n }\r\n\r\n .ad-cl {\r\n display: flex;\r\n // 가로 스크롤 안에서도 강조 배경이 끊기지 않게 (`.ad-dl` 과 같은 사정)\r\n min-width: 100%;\r\n min-height: 17px;\r\n white-space: pre;\r\n\r\n .ad-cl-no {\r\n flex: 0 0 auto;\r\n width: 44px;\r\n padding-right: 8px;\r\n color: rgba(255, 255, 255, 0.25);\r\n text-align: right;\r\n user-select: none;\r\n }\r\n\r\n .ad-cl-tx {\r\n flex: 1 0 auto;\r\n padding-right: 8px;\r\n color: rgba(255, 255, 255, 0.85);\r\n }\r\n }\r\n\r\n // ---------- 참조 안내줄 (본문 밖 — 자동 갱신에 지워지지 않는 자리) ----------\r\n .ad-view-ref {\r\n flex: 0 0 auto;\r\n overflow: hidden;\r\n padding: 4px 10px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.06);\r\n color: rgba(255, 255, 255, 0.5);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-size: 10px;\r\n }\r\n\r\n // 방금 넣었다 — REF_FLASH_MS(2.5초)만 켜진다\r\n .ad-view-ref-on {\r\n background: rgba(97, 175, 239, 0.14);\r\n color: #cfe6ff;\r\n }\r\n\r\n // ---------- 커밋 줄 (본문 밖 — 자동 갱신에 지워지지 않는 자리) ----------\r\n .ad-view-commit {\r\n flex: 0 0 auto;\r\n padding: 6px 8px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.06);\r\n }\r\n\r\n .ad-view-commit-sum {\r\n margin-bottom: 4px;\r\n color: rgba(255, 255, 255, 0.55);\r\n font-size: 11px;\r\n }\r\n\r\n .ad-view-commit-row {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n }\r\n\r\n .ad-view-commit-msg {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: rgba(0, 0, 0, 0.25);\r\n color: #e6e6e6;\r\n font-family: monospace;\r\n font-size: 11px;\r\n\r\n &:focus {\r\n border-color: rgba(97, 175, 239, 0.6);\r\n outline: 0;\r\n }\r\n }\r\n\r\n .ad-view-commit-go {\r\n flex: 0 0 auto;\r\n padding: 4px 10px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.7);\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover:not(:disabled) {\r\n border-color: rgba(255, 255, 255, 0.35);\r\n color: #fff;\r\n }\r\n\r\n // 메시지가 비면 잠긴다 — 잠긴 것이 눈에 보여야 \"왜 안 눌리나\" 를 묻지 않는다\r\n &:disabled {\r\n opacity: 0.45;\r\n cursor: default;\r\n }\r\n\r\n // 확인 대기: 다음 누름이 실제 커밋이라는 것을 색으로 경고한다\r\n &.ad-view-commit-armed {\r\n border-color: rgba(224, 108, 117, 0.7);\r\n background: rgba(224, 108, 117, 0.18);\r\n color: #ffd7da;\r\n }\r\n }\r\n\r\n .ad-view-commit-note {\r\n margin-top: 4px;\r\n overflow: hidden;\r\n color: rgba(255, 255, 255, 0.4);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-size: 11px;\r\n }\r\n}\r\n\r\n// 우클릭 상태 메뉴\r\n.ad-menu {\r\n position: fixed;\r\n z-index: 10000;\r\n min-width: 130px;\r\n padding: 4px;\r\n border: 1px solid rgba(255, 255, 255, 0.12);\r\n border-radius: 6px;\r\n background: #262a31;\r\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);\r\n font-size: 12px;\r\n user-select: none;\r\n\r\n .ad-menu-item {\r\n padding: 6px 10px;\r\n border-radius: 4px;\r\n color: rgba(255, 255, 255, 0.75);\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.1);\r\n color: #fff;\r\n }\r\n }\r\n}\r\n\r\n// 도킹 가이드 — 헤더를 끌고 있는 동안만 창 위에 뜬다\r\n.ad-dock-overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: 9998;\r\n // 커서 판정은 좌표로 직접 하므로 오버레이가 이벤트를 가로채면 안 된다\r\n pointer-events: none;\r\n}\r\n\r\n// 놓으면 사이드바가 자리잡을 영역\r\n.ad-dock-preview {\r\n position: fixed;\r\n display: none;\r\n border: 1px solid rgba(97, 175, 239, 0.9);\r\n background: rgba(97, 175, 239, 0.22);\r\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.18);\r\n transition: left 0.08s ease, top 0.08s ease, width 0.08s ease, height 0.08s ease;\r\n}\r\n\r\n// 네 가장자리에 뜨는 도킹 표적\r\n.ad-dock-zone {\r\n position: fixed;\r\n width: 46px;\r\n height: 46px;\r\n margin: -23px 0 0 -23px;\r\n border: 1px solid rgba(255, 255, 255, 0.35);\r\n border-radius: 6px;\r\n background: rgba(38, 42, 49, 0.92);\r\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n\r\n .ad-dock-mark {\r\n width: 26px;\r\n height: 26px;\r\n border: 1px solid rgba(255, 255, 255, 0.45);\r\n border-radius: 3px;\r\n }\r\n\r\n &.active {\r\n border-color: rgba(97, 175, 239, 0.95);\r\n background: rgba(97, 175, 239, 0.35);\r\n }\r\n\r\n // 표적 안의 작은 사각형이 \"어느 쪽 절반에 붙는지\" 를 그대로 보여준다\r\n &.ad-dock-zone-left .ad-dock-mark { border-left: 4px solid rgba(97, 175, 239, 0.95); }\r\n &.ad-dock-zone-right .ad-dock-mark { border-right: 4px solid rgba(97, 175, 239, 0.95); }\r\n &.ad-dock-zone-top .ad-dock-mark { border-top: 4px solid rgba(97, 175, 239, 0.95); }\r\n &.ad-dock-zone-bottom .ad-dock-mark { border-bottom: 4px solid rgba(97, 175, 239, 0.95); }\r\n}\r\n\r\n// 놓일 자리를 가리키는 삽입선 — 줄을 끌고 있는 동안만 뜬다 (deck.service drawReorderLine).\r\n// 좌표는 인라인으로 준다(가로 도킹은 세로선, 상/하 도킹은 가로선). `.ad-list` 안이 아니라\r\n// body 에 붙는 이유는 그 함수 주석에 있다 — 목록을 다시 그릴 때 날아가지 않게, 그리고\r\n// 회귀 프로브가 목록의 자식만 훑어 그룹을 읽으므로 낯선 노드를 끼워 넣지 않게.\r\n.ad-reorder-line {\r\n position: fixed;\r\n z-index: 9997;\r\n border-radius: 1px;\r\n background: rgba(97, 175, 239, 0.95);\r\n box-shadow: 0 0 5px rgba(97, 175, 239, 0.6);\r\n // 좌표 판정은 elementFromPoint 로 한다 — 선이 이벤트를 가로채면 자기 자신을 짚는다\r\n pointer-events: none;\r\n}\r\n\r\n// 줄을 끄는 동안 — 커서를 옮기기 모양으로 두고, 커서가 터미널 위로 넘어가도 모양이 유지되게\r\nbody.ad-reordering {\r\n cursor: grabbing;\r\n\r\n // 줄 자신은 `cursor: pointer`(클릭=탭 선택)라 body 것으로는 덮이지 않는다.\r\n // 끌고 있는 동안만 사이드바 안쪽을 통째로 덮어써 손 모양이 도중에 바뀌지 않게 한다.\r\n #agentdeck-sidebar,\r\n #agentdeck-sidebar * {\r\n cursor: grabbing !important;\r\n }\r\n\r\n .content.main {\r\n pointer-events: none;\r\n }\r\n}\r\n\r\n// 경계선을 끄는 동안에는 터미널이 커서를 뺏지 않게 한다\r\nbody.ad-resizing {\r\n cursor: inherit;\r\n\r\n .content.main {\r\n pointer-events: none;\r\n }\r\n}\r\n\r\n// AgentDeck 이 켜져 있을 때만 순정 탭바를 감춘다 (사이드바가 그 역할을 대신함)\r\nbody.agentdeck-active {\r\n .content.main > .tab-bar {\r\n display: none !important;\r\n }\r\n\r\n .window {\r\n display: flex;\r\n flex-direction: row;\r\n // 사이드바를 창 안에 절대 배치하므로 기준점이 필요하다\r\n position: relative;\r\n // 터미널 옆에 남는 여백이 밝은 기본 배경으로 튀지 않게 한다\r\n // (--ad-panel-bg 가 있으면 배경 이미지가 이 여백으로도 비친다)\r\n background: var(--ad-panel-bg, var(--bs-body-bg, #1e2127));\r\n }\r\n}\r\n\r\n// ---------- 드롭 확인 배너 (터미널 위에 파일을 끌어다 놓았을 때) ----------\r\n// OS 모달이 아니라 인라인 카드다 — 모달은 터미널 흐름을 끊고 CDP 로 검증할 수 없다.\r\n// 화면 오른쪽 아래에 띄운다: 입력창(아래 중앙)과 사이드바(좌/우 가장자리)를 둘 다 피하려면\r\n// 그 모서리가 가장 덜 겹친다. `z-index` 는 사이드바(#agentdeck-sidebar)보다 위여야 한다.\r\n.ad-drop-ask {\r\n position: fixed;\r\n right: 16px;\r\n bottom: 16px;\r\n z-index: 10000;\r\n min-width: 260px;\r\n max-width: 420px;\r\n padding: 10px 12px;\r\n border: 1px solid rgba(255, 255, 255, 0.16);\r\n border-radius: 6px;\r\n background: rgba(24, 26, 30, 0.97);\r\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);\r\n color: #e6e6e6;\r\n font-size: 12px;\r\n\r\n .ad-drop-ask-msg {\r\n overflow: hidden;\r\n margin-bottom: 8px;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-weight: 600;\r\n }\r\n\r\n .ad-drop-ask-row {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n }\r\n\r\n button {\r\n flex: 1 1 auto;\r\n padding: 5px 8px;\r\n border: 1px solid rgba(255, 255, 255, 0.16);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.85);\r\n font-size: 12px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n border-color: rgba(97, 175, 239, 0.6);\r\n background: rgba(97, 175, 239, 0.14);\r\n color: #fff;\r\n }\r\n }\r\n\r\n // 닫기는 강조하지 않는다 — 기본 선택이 아니다\r\n .ad-drop-ask-close {\r\n flex: 0 0 auto;\r\n min-width: 26px;\r\n padding: 5px 6px;\r\n color: rgba(255, 255, 255, 0.45);\r\n }\r\n\r\n .ad-drop-ask-again {\r\n display: flex;\r\n gap: 5px;\r\n align-items: center;\r\n margin-top: 8px;\r\n margin-bottom: 0;\r\n color: rgba(255, 255, 255, 0.5);\r\n font-size: 11px;\r\n cursor: pointer;\r\n }\r\n}\r\n"],"sourceRoot":""}]);
1534
+ }`, "",{"version":3,"sources":["webpack://./src/styles.scss"],"names":[],"mappings":"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","sourcesContent":["// AgentDeck 사이드바 스타일\r\n// Tabby 테마 변수(--bs-*)를 우선 쓰되, 없을 때를 대비해 폴백 색을 함께 준다.\r\n\r\n// tabby-background 의 배경 이미지 레이어(.content-tab-active::before)를 터미널 영역으로 자른다.\r\n// 좌표 변수(--ad-bg-*)는 deck.service 의 syncBackgroundClip 이 relayout 마다 내려준다.\r\n// 그 플러그인은 런타임에 <style> 을 만들어 head 뒤쪽에 붙이므로 순서로는 못 이긴다 — !important 필수.\r\nbody.agentdeck-active.ad-bg-clip .content-tab-active::before {\r\n left: var(--ad-bg-left) !important;\r\n top: var(--ad-bg-top) !important;\r\n right: auto !important;\r\n bottom: auto !important;\r\n width: var(--ad-bg-width) !important;\r\n height: var(--ad-bg-height) !important;\r\n}\r\n\r\n#agentdeck-sidebar {\r\n display: flex;\r\n flex-direction: column;\r\n // 실제 위치와 크기는 deck.service 의 relayout 이 인라인으로 준다 (도킹 방향에 따라 달라짐)\r\n z-index: 5;\r\n overflow: hidden;\r\n // --ad-panel-bg 는 agentdeck 이 sidebarOpacity 로 내려준다 (배경 이미지가 비치게)\r\n background: var(--ad-panel-bg, var(--bs-body-bg, #1e2127));\r\n font-size: 12px;\r\n user-select: none;\r\n -webkit-app-region: no-drag;\r\n\r\n // 붙어 있는 쪽 반대편에만 경계선을 그린다\r\n &.ad-dock-right { border-left: 1px solid rgba(255, 255, 255, 0.08); }\r\n &.ad-dock-left { border-right: 1px solid rgba(255, 255, 255, 0.08); }\r\n &.ad-dock-top { border-bottom: 1px solid rgba(255, 255, 255, 0.08); }\r\n &.ad-dock-bottom { border-top: 1px solid rgba(255, 255, 255, 0.08); }\r\n\r\n // 상/하 도킹은 세로가 짧으므로 목록을 가로로 흘린다\r\n &.ad-dock-top,\r\n &.ad-dock-bottom {\r\n .ad-list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-content: flex-start;\r\n gap: 0 6px;\r\n overflow-y: auto;\r\n }\r\n\r\n // 한 줄에 여러 탭이 나란히 서므로 폭을 220px 로 못박는다 — 제목 길이에 따라\r\n // 들쭉날쭉하면 어느 칸이 어느 세션인지 눈으로 못 따라가고, 제목이 바뀔 때마다 자리가 밀린다.\r\n //\r\n // **`min-width: 0` 이 있어야 `flex-basis` 가 살아남는다.** flex 아이템은\r\n // `min-width: auto`(초깃값) + `overflow: visible` 이면 **자동 최소 크기**가 걸려\r\n // 최소내용폭(min-content) 밑으로는 줄지 않는다. 그래서 제목이 긴 탭은 220 을 밀어올렸다\r\n // (2026-09-09 CDP 실측: 1700 창·탭 6개에서 220 지정인데 384.7px, 한 줄에 4개 +\r\n // 둘째 줄이 반쯤 잘림). 폭을 못박아도 제목은 읽힌다 — `.ad-title` 이 이미 말줄임을\r\n // 갖고 있고, 220 은 좌/우 도킹의 평소 줄 폭(사이드바 기본 200px)보다 오히려 넓다.\r\n .ad-tab {\r\n flex: 0 0 220px;\r\n min-width: 0;\r\n }\r\n\r\n // 그룹 헤더는 한 줄을 다 차지해 가로 흘림을 끊는다 — 그래야 헤더 아래 줄부터 그 그룹의\r\n // 탭이 흐른다. 헤더를 div 로 감싸는 대신 형제로 두는 이유가 이것이다(deck.service renderGroupHead)\r\n .ad-group-head {\r\n flex: 0 0 100%;\r\n margin-top: 2px;\r\n }\r\n }\r\n\r\n .ad-head {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 10px 12px 8px;\r\n letter-spacing: 0.12em;\r\n font-size: 10px;\r\n font-weight: 700;\r\n color: rgba(255, 255, 255, 0.4);\r\n // 헤더를 잡아 끌면 도킹 방향을 바꾼다 (OS 창 이동에 뺏기면 안 되므로 no-drag)\r\n -webkit-app-region: no-drag;\r\n cursor: grab;\r\n\r\n &:active {\r\n cursor: grabbing;\r\n }\r\n }\r\n\r\n // 경계선 — 잡아 끌면 크기가 바뀐다. 붙어 있는 쪽 반대편 가장자리에 놓는다\r\n .ad-resize {\r\n position: absolute;\r\n z-index: 6;\r\n background: transparent;\r\n transition: background 0.12s ease;\r\n\r\n &:hover,\r\n body.ad-resizing & {\r\n background: rgba(97, 175, 239, 0.5);\r\n }\r\n }\r\n\r\n .ad-resize-right,\r\n .ad-resize-left {\r\n top: 0;\r\n bottom: 0;\r\n width: 6px;\r\n cursor: col-resize;\r\n }\r\n\r\n .ad-resize-right { left: 0; }\r\n .ad-resize-left { right: 0; }\r\n\r\n .ad-resize-top,\r\n .ad-resize-bottom {\r\n left: 0;\r\n right: 0;\r\n height: 6px;\r\n cursor: row-resize;\r\n }\r\n\r\n .ad-resize-bottom { top: 0; }\r\n .ad-resize-top { bottom: 0; }\r\n\r\n // 상태별 집계 칩 한 줄 — `⏸ 2 ● 3 ○ 1`. 헤더 높이는 총 개수만 있던 때와 같게 유지한다\r\n .ad-head-count {\r\n -webkit-app-region: no-drag;\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n flex: 0 0 auto;\r\n white-space: nowrap;\r\n letter-spacing: 0;\r\n color: rgba(255, 255, 255, 0.55);\r\n }\r\n\r\n .ad-cnt {\r\n padding: 1px 6px;\r\n border-radius: 8px;\r\n background: rgba(255, 255, 255, 0.08);\r\n color: var(--ad-color, rgba(255, 255, 255, 0.55));\r\n font-size: 10px;\r\n line-height: 1.3;\r\n font-variant-numeric: tabular-nums;\r\n }\r\n\r\n // 요구 2-1: 탭이 많아지면 이 영역만 스크롤된다\r\n .ad-list {\r\n flex: 1 1 auto;\r\n overflow-y: auto;\r\n overflow-x: hidden;\r\n padding: 2px 6px 8px;\r\n // **못박아 둔다** — 드래그 자동 스크롤(deck.service `dragScrollTick`)이 매 프레임\r\n // `scrollTop` 에 몇 px 를 더하는 식으로 목록을 흘린다. 여기가 `smooth` 면 그 대입마다\r\n // 새 애니메이션이 시작돼 서로를 덮어써서 목록이 기어가거나 아예 멈춘다.\r\n // 지금 이 값을 `smooth` 로 켜는 곳은 없지만(전역 검색 0건), 나중에 누가 켜면\r\n // 그 기능이 **조용히** 못 쓰게 되는 자리라 의도를 여기 남긴다.\r\n scroll-behavior: auto;\r\n\r\n &::-webkit-scrollbar {\r\n width: 8px;\r\n }\r\n\r\n &::-webkit-scrollbar-thumb {\r\n background: rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.24);\r\n }\r\n }\r\n }\r\n\r\n // 프로젝트(작업 폴더) 그룹 헤더 — `▾ tabby-agentdeck 3`. 그룹이 둘 이상일 때만 그린다.\r\n // 헤더 자체는 탭보다 작고 어둡게 둔다 — 세션 목록이 주인공이고 이건 칸막이다.\r\n .ad-group-head {\r\n display: flex;\r\n align-items: center;\r\n gap: 5px;\r\n margin: 7px 0 2px;\r\n padding: 2px 4px;\r\n border-radius: 4px;\r\n color: rgba(255, 255, 255, 0.42);\r\n font-size: 10px;\r\n font-weight: 700;\r\n letter-spacing: 0.06em;\r\n cursor: pointer;\r\n transition: background 0.1s ease, color 0.1s ease;\r\n\r\n // 첫 그룹은 목록 맨 위에 붙어야 자연스럽다 (.ad-list 가 이미 위쪽 여백을 준다)\r\n &:first-child {\r\n margin-top: 1px;\r\n }\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.05);\r\n color: rgba(255, 255, 255, 0.7);\r\n }\r\n\r\n // 접힌 그룹은 \"여기 뭔가 숨어 있다\" 가 보여야 한다 — 개수 칩을 살짝 밝게\r\n &.collapsed .ad-group-count {\r\n background: rgba(255, 255, 255, 0.14);\r\n color: rgba(255, 255, 255, 0.75);\r\n }\r\n }\r\n\r\n .ad-group-caret {\r\n flex: 0 0 auto;\r\n width: 9px;\r\n font-size: 9px;\r\n line-height: 1;\r\n opacity: 0.75;\r\n }\r\n\r\n .ad-group-label {\r\n // 폴더 이름은 길 수 있다(사이드바는 200~560px) — 개수 칩을 밀어내지 않게 줄이고 말줄임\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n // 검색 때문에 임시로 펴 둔 그룹 — 캐럿만 흐리게 해서 \"내가 접어 둔 그룹인데 지금은\r\n // 검색 때문에 보이는 중\" 임을 알린다. 이유 전문은 헤더 title 에 있다(renderGroupHead).\r\n // 줄 배경이나 라벨 색을 바꾸지 않는 이유: 검색 결과 자체가 이미 강조된 화면이라\r\n // 여기서 또 강조하면 무엇이 걸린 줄인지가 흐려진다.\r\n .ad-group-head.filter-open .ad-group-caret {\r\n opacity: 0.45;\r\n }\r\n\r\n .ad-group-count {\r\n flex: 0 0 auto;\r\n padding: 0 5px;\r\n border-radius: 8px;\r\n background: rgba(255, 255, 255, 0.08);\r\n color: rgba(255, 255, 255, 0.55);\r\n font-size: 9.5px;\r\n line-height: 1.5;\r\n letter-spacing: 0;\r\n font-variant-numeric: tabular-nums;\r\n }\r\n\r\n .ad-tab {\r\n position: relative;\r\n display: flex;\r\n align-items: stretch;\r\n gap: 8px;\r\n margin-bottom: 3px;\r\n padding: 7px 6px 7px 0;\r\n border-radius: 5px;\r\n cursor: pointer;\r\n transition: background 0.1s ease;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.05);\r\n\r\n .ad-close {\r\n opacity: 0.65;\r\n }\r\n }\r\n\r\n &.active {\r\n background: rgba(255, 255, 255, 0.1);\r\n\r\n .ad-title {\r\n color: #fff;\r\n }\r\n }\r\n }\r\n\r\n // 상태색 표시 바\r\n .ad-bar {\r\n flex: 0 0 3px;\r\n border-radius: 2px;\r\n background: var(--ad-color, #6b727d);\r\n }\r\n\r\n .ad-body {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n }\r\n\r\n .ad-title {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: rgba(255, 255, 255, 0.82);\r\n font-size: 12.5px;\r\n line-height: 1.35;\r\n }\r\n\r\n .ad-meta {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n margin-top: 2px;\r\n font-size: 10.5px;\r\n line-height: 1.4;\r\n color: rgba(255, 255, 255, 0.45);\r\n }\r\n\r\n .ad-badge {\r\n // 이유가 붙으면(승인대기 · Bash 권한) 길어질 수 있다 — 경과시간을 밀어내지 않게 줄이고 말줄임\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: var(--ad-color, #6b727d);\r\n font-weight: 600;\r\n\r\n // 수동/훅으로 고정된 상태는 밑줄로 구분\r\n &.pinned {\r\n text-decoration: underline dotted;\r\n text-underline-offset: 2px;\r\n }\r\n }\r\n\r\n // 도는 서브에이전트 개수 (`❖3`) — 상태 배지 바로 뒤. 자리 선정 이유는 deck.service `renderTab` 주석\r\n .ad-subagents {\r\n // 배지가 길어져도 이건 줄이지 않는다 — 개수는 한두 글자고, 줄어들면 읽을 수가 없다\r\n flex: 0 0 auto;\r\n padding: 0 4px;\r\n border-radius: 7px;\r\n // 상태색(`--ad-color`)을 쓰지 않는다 — 상태와 다른 축의 정보라 같은 색이면 배지의\r\n // 일부로 읽힌다. 배경까지 넣어 숫자가 상태 문구에 섞이지 않게 칩으로 떼어 둔다\r\n background: rgba(86, 182, 194, 0.18);\r\n color: #56b6c2;\r\n font-weight: 600;\r\n font-variant-numeric: tabular-nums;\r\n }\r\n\r\n // 두 번째 줄 — 마지막으로 보낸 프롬프트\r\n .ad-prompt {\r\n display: flex;\r\n align-items: center;\r\n margin-top: 2px;\r\n min-width: 0;\r\n font-size: 11px;\r\n line-height: 1.4;\r\n }\r\n\r\n .ad-label {\r\n flex: 1 1 auto;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: rgba(255, 255, 255, 0.6);\r\n\r\n // 아직 프롬프트가 없는 탭 — 자리는 지키되 눈에 덜 걸리게\r\n &.empty {\r\n color: rgba(255, 255, 255, 0.25);\r\n }\r\n }\r\n\r\n .ad-elapsed {\r\n flex: 0 0 auto;\r\n margin-left: auto;\r\n font-variant-numeric: tabular-nums;\r\n opacity: 0.55;\r\n }\r\n\r\n // ── 지난 세션 (닫힌 Claude Code 세션 이어받기) ─────────────────────────\r\n //\r\n // 그룹의 **서랍**이다 — 살아 있는 탭 뒤에 오고 기본은 접혀 있다.\r\n // 들여쓰기(11px)로 그룹 헤더보다 한 단계 안쪽임을 보인다: 이 줄들은 그룹에 속한\r\n // 목록이지 또 하나의 그룹이 아니다.\r\n .ad-resume-head {\r\n display: flex;\r\n align-items: center;\r\n gap: 5px;\r\n margin: 5px 0 2px;\r\n padding: 2px 4px 2px 11px;\r\n border-radius: 4px;\r\n // 그룹 헤더(0.42)보다 더 죽인다 — 살아 있는 목록이 먼저 읽혀야 한다\r\n color: rgba(255, 255, 255, 0.34);\r\n font-size: 10px;\r\n font-weight: 700;\r\n letter-spacing: 0.06em;\r\n cursor: pointer;\r\n transition: background 0.1s ease, color 0.1s ease;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.05);\r\n color: rgba(255, 255, 255, 0.6);\r\n }\r\n\r\n .ad-group-count {\r\n background: rgba(255, 255, 255, 0.06);\r\n color: rgba(255, 255, 255, 0.45);\r\n }\r\n\r\n // 캐럿은 오른쪽 끝으로 — 그룹 헤더는 캐럿이 앞에 있는데, 이 줄은 그룹의 하위라\r\n // 같은 자리에 두면 두 헤더가 같은 층으로 읽힌다\r\n .ad-group-caret {\r\n margin-left: auto;\r\n }\r\n }\r\n\r\n .ad-resume {\r\n display: flex;\r\n align-items: stretch;\r\n gap: 8px;\r\n margin: 0 0 3px 11px;\r\n padding: 5px 6px 5px 0;\r\n border-radius: 5px;\r\n cursor: pointer;\r\n // 죽은 세션은 **색이 아니라 형태**로 구분한다. 상태 6색(api.ts STATUS_STYLES)이 이미\r\n // 꽉 차 있어 일곱 번째 색을 들이면 배지가 뜻하는 바가 흐려진다\r\n opacity: 0.66;\r\n transition: background 0.1s ease, opacity 0.1s ease;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.05);\r\n opacity: 1;\r\n }\r\n\r\n // 좌측 바를 점선으로 끊는다 — 살아 있는 줄의 실선 상태바와 한눈에 갈린다\r\n .ad-bar {\r\n background: #3a404b;\r\n background-image: linear-gradient(var(--ad-panel-bg, var(--bs-body-bg, #1e2127)) 50%, transparent 0);\r\n background-size: 100% 5px;\r\n }\r\n\r\n .ad-title {\r\n font-size: 12px;\r\n color: rgba(255, 255, 255, 0.72);\r\n }\r\n\r\n .ad-meta {\r\n gap: 5px;\r\n margin-top: 1px;\r\n font-size: 10px;\r\n color: rgba(255, 255, 255, 0.36);\r\n }\r\n }\r\n\r\n // 끝난 상태의 흔적 — 살아 있는 배지와 같은 세기로 칠하지 않는다(그러면 도는 줄로 읽힌다)\r\n .re-was {\r\n flex: 0 1 auto;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n font-weight: 600;\r\n\r\n &.s-done { color: rgba(152, 195, 121, 0.62); }\r\n &.s-error { color: rgba(224, 108, 117, 0.68); }\r\n &.s-limited { color: rgba(198, 120, 221, 0.62); }\r\n &.s-waiting { color: rgba(97, 175, 239, 0.62); }\r\n &.s-running { color: rgba(229, 192, 123, 0.62); }\r\n &.s-idle { color: rgba(255, 255, 255, 0.3); }\r\n\r\n // 이미 열려 있는 세션 — 이건 죽지 않았으므로 상태색을 그대로 쓴다\r\n &.live {\r\n padding: 0 4px;\r\n border-radius: 7px;\r\n background: rgba(229, 192, 123, 0.16);\r\n color: #e5c07b;\r\n }\r\n }\r\n\r\n .re-when {\r\n flex: 0 0 auto;\r\n margin-left: auto;\r\n padding-left: 6px;\r\n font-variant-numeric: tabular-nums;\r\n }\r\n\r\n // 열려 있는 세션은 흐리게 두지 않는다 — 지금 도는 작업이라 목록에서도 살아 보여야 한다\r\n .ad-resume.open {\r\n opacity: 0.9;\r\n\r\n .ad-bar {\r\n background: #e5c07b;\r\n background-image: none;\r\n }\r\n }\r\n\r\n .ad-label-input {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n padding: 0 4px;\r\n border: 1px solid rgba(255, 255, 255, 0.25);\r\n border-radius: 3px;\r\n background: rgba(0, 0, 0, 0.35);\r\n color: #fff;\r\n font-size: 10.5px;\r\n outline: none;\r\n\r\n &:focus {\r\n border-color: var(--ad-color, #61afef);\r\n }\r\n }\r\n\r\n .ad-close {\r\n flex: 0 0 auto;\r\n align-self: center;\r\n width: 18px;\r\n height: 18px;\r\n padding: 0;\r\n border: 0;\r\n border-radius: 3px;\r\n opacity: 0;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.8);\r\n font-size: 15px;\r\n line-height: 1;\r\n cursor: pointer;\r\n transition: opacity 0.1s ease, background 0.1s ease;\r\n\r\n &:hover {\r\n opacity: 1 !important;\r\n background: rgba(255, 80, 80, 0.35);\r\n }\r\n }\r\n\r\n .ad-foot {\r\n display: flex;\r\n gap: 6px;\r\n padding: 8px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.07);\r\n }\r\n\r\n .ad-btn {\r\n flex: 1 1 auto;\r\n padding: 5px 8px;\r\n border: 1px solid rgba(255, 255, 255, 0.12);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.6);\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.08);\r\n color: #fff;\r\n }\r\n }\r\n\r\n // 글자가 한 자뿐인 버튼들은 나머지와 같은 비율로 늘어나면 허전하다\r\n .ad-repair,\r\n .ad-viewtoggle {\r\n flex: 0 0 auto;\r\n min-width: 28px;\r\n font-size: 13px;\r\n line-height: 1;\r\n }\r\n // ---------- 세션 검색 / 상태 필터 (deck.service buildSidebar · wireSearch) ----------\r\n // 유닛 T 가 넘긴 CSS — 그 유닛은 이 파일을 소유하지 않아 배리어에서 메인이 넣는다.\r\n //\r\n // 검색 줄은 헤더와 목록 사이의 별도 flex 아이템이다. `.ad-list` 안에 두면 `render()` 의\r\n // innerHTML 초기화에 입력창이 매 글자마다 새로 만들어져 포커스·IME 조합이 날아간다.\r\n .ad-search {\r\n display: flex;\r\n flex: 0 0 auto;\r\n gap: 4px;\r\n align-items: center;\r\n padding: 0 8px 6px;\r\n -webkit-app-region: no-drag;\r\n }\r\n\r\n .ad-search-input {\r\n // 사이드바는 200~560px — 입력창이 남는 폭을 다 쓰되 줄어들 때 버튼을 밀지 않게\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n padding: 3px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n outline: none;\r\n background: rgba(0, 0, 0, 0.28);\r\n color: rgba(255, 255, 255, 0.85);\r\n font-size: 11px;\r\n line-height: 1.4;\r\n\r\n &::placeholder {\r\n color: rgba(255, 255, 255, 0.3);\r\n }\r\n\r\n &:focus {\r\n border-color: rgba(97, 175, 239, 0.7);\r\n color: #fff;\r\n }\r\n }\r\n\r\n // 지우기 버튼은 **걸린 필터가 있을 때만** 보인다 (deck.service syncSearchUi 가 `.has-query`).\r\n // 늘 자리를 잡으면 입력창이 그만큼 좁아지고, 빈 칸 옆의 ✕ 는 무엇을 지우는지 알 수 없다.\r\n .ad-search-clear {\r\n display: none;\r\n flex: 0 0 auto;\r\n width: 18px;\r\n height: 18px;\r\n padding: 0;\r\n border: 0;\r\n border-radius: 3px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.45);\r\n font-size: 14px;\r\n line-height: 1;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.12);\r\n color: #fff;\r\n }\r\n }\r\n\r\n .ad-search.has-query .ad-search-clear {\r\n display: block;\r\n }\r\n\r\n // 집계 칩은 상태 필터의 조작부다 (renderHeadCount → toggleStatusFilter).\r\n // 위쪽 `.ad-cnt` 규칙에 \"누를 수 있다\" 와 \"지금 걸려 있다\" 만 덧붙인다.\r\n .ad-cnt {\r\n cursor: pointer;\r\n // 헤더는 도킹 드래그 핸들이라 글자가 잡히면 드래그가 지저분해진다\r\n user-select: none;\r\n transition: background 0.1s ease, color 0.1s ease;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.16);\r\n }\r\n\r\n // 걸려 있는 필터 — 배경 알파는 SCSS 가 var() 를 계산할 수 없으니\r\n // 상태색은 안쪽 테두리와 글자색으로만 쓴다\r\n &.active {\r\n box-shadow: inset 0 0 0 1px var(--ad-color, #61afef);\r\n background: rgba(255, 255, 255, 0.2);\r\n color: var(--ad-color, #fff);\r\n }\r\n }\r\n\r\n // 필터에 아무것도 안 걸렸을 때의 한 줄 (deck.service renderEmpty).\r\n // 빈 목록만 남기면 \"탭이 다 닫혔다\" 로 읽히므로 이유를 글로 남긴다.\r\n .ad-empty {\r\n padding: 10px 8px;\r\n color: rgba(255, 255, 255, 0.42);\r\n text-align: center;\r\n word-break: break-all;\r\n font-size: 11px;\r\n line-height: 1.5;\r\n }\r\n\r\n // 키보드 포커스 링 (deck.service `isNavFocused`) — **활성 탭 표시와 다른 것이어야 한다.**\r\n // 활성(선택)은 배경으로, 키보드 포커스는 테두리로 말한다. 둘을 같은 모양으로 그리면\r\n // \"지금 보고 있는 줄\" 과 \"지금 쓰고 있는 탭\" 이 화면에서 구별되지 않고, 그 구별이 이\r\n // 기능의 전부다(이동만으로는 탭이 바뀌지 않는다, `navFocus` 주석).\r\n //\r\n // outline 대신 inset box-shadow 를 쓰는 이유 둘 — outline 은 `.ad-list` 의\r\n // `overflow-y: auto` 경계에서 잘리고, border 는 줄 높이를 1px 밀어 포커스가 지나갈 때마다\r\n // 목록 전체가 흔들린다. 상태색(`--ad-color`)을 쓰지 않는 것도 일부러다: 포커스는 세션 상태와\r\n // 무관한 \"내 손이 어디 있나\" 라서, 상태색으로 그리면 배지 색과 섞여 읽힌다.\r\n .ad-tab.ad-nav-focus,\r\n .ad-group-head.ad-nav-focus {\r\n box-shadow: inset 0 0 0 1px rgba(97, 175, 239, 0.85);\r\n background: rgba(97, 175, 239, 0.1);\r\n }\r\n\r\n // 포커스가 활성 탭 위에 있을 때 — 배경은 활성 쪽(밝은 회색)을 유지하고 테두리만 얹는다.\r\n // 그래야 \"이 줄이 지금 쓰는 탭이고, 포커스도 여기 있다\" 가 한 줄에서 다 읽힌다\r\n .ad-tab.active.ad-nav-focus {\r\n background: rgba(255, 255, 255, 0.14);\r\n }\r\n\r\n // 목록 자체가 포커스를 받는다(tabindex=-1) — 브라우저 기본 outline 은 지운다.\r\n // 사각형이 목록 전체에 둘러쳐지면 어느 **줄**에 있는지가 오히려 안 보인다\r\n .ad-list:focus,\r\n .ad-list:focus-visible {\r\n outline: none;\r\n }\r\n\r\n // 끌고 있는 줄 — 원래 자리에 흐릿하게 남긴다. 줄을 실제로 옮겨 그리지 않는 이유는\r\n // deck.service `render` 의 드래그 가드에 있다(목록을 다시 만들면 끌던 DOM 이 사라진다).\r\n // \"무엇을 들고 있나\" 는 이 흐릿한 줄이, \"어디에 놓이나\" 는 삽입선(.ad-reorder-line)이 말한다.\r\n .ad-tab.ad-dragging {\r\n opacity: 0.45;\r\n background: rgba(255, 255, 255, 0.06);\r\n outline: 1px dashed rgba(255, 255, 255, 0.28);\r\n outline-offset: -1px;\r\n }\r\n\r\n // 순서를 바꿀 수 없는 상태에서 끌었을 때의 한 줄 (deck.service noteReorderBlocked).\r\n // 목록 위에 겹쳐 띄운다 — flex 자식으로 흐르면 줄들이 밀려 드래그하던 자리가 움직인다.\r\n .ad-reorder-note {\r\n position: absolute;\r\n left: 8px;\r\n right: 8px;\r\n bottom: 34px;\r\n z-index: 3;\r\n padding: 7px 9px;\r\n border: 1px solid rgba(255, 255, 255, 0.12);\r\n border-radius: 5px;\r\n background: rgba(38, 42, 49, 0.96);\r\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);\r\n color: rgba(255, 255, 255, 0.78);\r\n font-size: 11px;\r\n line-height: 1.45;\r\n // 안내를 누르려는 손짓이 아래 줄의 클릭(탭 선택)으로 가지 않게\r\n pointer-events: none;\r\n }\r\n\r\n // 상/하 도킹 — 세로가 짧다(기본 sidebarHeight 200px). 검색 줄 높이를 줄이고,\r\n // 가로로 흐르는 목록에서 안내 한 줄은 폭을 다 차지하게 한다 (`.ad-group-head` 와 같은 이유)\r\n &.ad-dock-top .ad-search,\r\n &.ad-dock-bottom .ad-search {\r\n padding: 0 8px 4px;\r\n }\r\n\r\n &.ad-dock-top .ad-list .ad-empty,\r\n &.ad-dock-bottom .ad-list .ad-empty {\r\n flex: 0 0 100%;\r\n }\r\n}\r\n\r\n// ---------- 결과물 미리보기 패널 (viewPanel.ts) ----------\r\n// 사이드바와 같은 규칙을 따른다 — 위치·폭은 deck.service 의 relayout 이 인라인으로 준다.\r\n#agentdeck-view {\r\n display: flex;\r\n flex-direction: column;\r\n z-index: 5;\r\n overflow: hidden;\r\n background: var(--ad-panel-bg, var(--bs-body-bg, #1e2127));\r\n font-size: 12px;\r\n -webkit-app-region: no-drag;\r\n\r\n // 터미널과 닿는 쪽에만 경계선\r\n &.ad-view-right { border-left: 1px solid rgba(255, 255, 255, 0.08); }\r\n &.ad-view-left { border-right: 1px solid rgba(255, 255, 255, 0.08); }\r\n\r\n &.ad-view-dropping {\r\n outline: 1px dashed rgba(97, 175, 239, 0.8);\r\n outline-offset: -3px;\r\n }\r\n\r\n .ad-view-head {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 6px;\r\n padding: 8px 8px 6px 12px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n user-select: none;\r\n }\r\n\r\n .ad-view-title {\r\n overflow: hidden;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n color: rgba(255, 255, 255, 0.82);\r\n font-weight: 600;\r\n }\r\n\r\n .ad-view-tools {\r\n display: flex;\r\n flex: 0 0 auto;\r\n gap: 2px;\r\n }\r\n\r\n .ad-view-btn {\r\n min-width: 24px;\r\n padding: 2px 5px;\r\n border: 0;\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.5);\r\n font-size: 12px;\r\n line-height: 1.2;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.1);\r\n color: #fff;\r\n }\r\n\r\n // 켜져 있는 도구(편집 중의 ✎) — 지금 어떤 상태인지 버튼만 보고 알 수 있어야 한다\r\n &.ad-view-btn-on {\r\n background: rgba(97, 175, 239, 0.22);\r\n color: #9ecbff;\r\n }\r\n }\r\n\r\n .ad-view-path {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n padding: 6px 8px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n\r\n input {\r\n width: 100%;\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: rgba(0, 0, 0, 0.25);\r\n color: #e6e6e6;\r\n font-family: monospace;\r\n font-size: 11px;\r\n\r\n &:focus {\r\n outline: 0;\r\n border-color: rgba(97, 175, 239, 0.7);\r\n }\r\n }\r\n }\r\n\r\n // 경로 줄 안내 — \"없는 파일이다, 한 번 더 Enter\" 같은 말이 여기 뜬다\r\n .ad-view-path-note {\r\n width: 100%;\r\n overflow: hidden;\r\n color: rgba(229, 192, 123, 0.9);\r\n font-size: 11px;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n\r\n &:empty {\r\n display: none;\r\n }\r\n }\r\n\r\n // 찾기 줄 (Ctrl+F). 경로 줄과 같은 자리·같은 옷을 입힌다 — 둘 다 헤더 아래 한 줄짜리 도구다\r\n .ad-view-find {\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 6px 8px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n\r\n input {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: rgba(0, 0, 0, 0.25);\r\n color: #e6e6e6;\r\n font-family: monospace;\r\n font-size: 11px;\r\n\r\n &:focus {\r\n outline: 0;\r\n border-color: rgba(97, 175, 239, 0.7);\r\n }\r\n }\r\n }\r\n\r\n .ad-view-find-count {\r\n flex: 0 0 auto;\r\n min-width: 44px;\r\n color: rgba(255, 255, 255, 0.45);\r\n font-size: 11px;\r\n text-align: right;\r\n }\r\n\r\n // 일치 표시. 지금 보고 있는 것 하나만 진하게 — 개수가 많아도 어디에 있는지 눈에 든다\r\n mark.ad-find-hit {\r\n border-radius: 2px;\r\n background: rgba(229, 192, 123, 0.35);\r\n color: inherit;\r\n }\r\n\r\n mark.ad-find-cur {\r\n background: rgba(229, 192, 123, 0.85);\r\n color: #1a1a1a;\r\n }\r\n\r\n // 편집 줄 — 저장/취소는 본문 밖에 둔다 (본문은 갱신마다 다시 그려진다)\r\n .ad-view-editbar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n padding: 5px 8px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.06);\r\n background: rgba(0, 0, 0, 0.18);\r\n }\r\n\r\n .ad-view-edit-note {\r\n overflow: hidden;\r\n color: rgba(255, 255, 255, 0.5);\r\n font-size: 11px;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n }\r\n\r\n .ad-view-edit-btns {\r\n display: flex;\r\n flex: 0 0 auto;\r\n gap: 4px;\r\n }\r\n\r\n .ad-view-edit-save,\r\n .ad-view-edit-cancel {\r\n padding: 2px 10px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.75);\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover:not(:disabled) {\r\n border-color: rgba(255, 255, 255, 0.3);\r\n background: rgba(255, 255, 255, 0.08);\r\n color: #fff;\r\n }\r\n\r\n &:disabled {\r\n opacity: 0.35;\r\n cursor: default;\r\n }\r\n }\r\n\r\n .ad-view-edit-save {\r\n border-color: rgba(97, 175, 239, 0.5);\r\n color: #9ecbff;\r\n }\r\n\r\n // 편집기 — 본문 자리를 그대로 채운다. 글꼴·줄간격을 코드 보기와 맞춰야 편집 전후로\r\n // 글자가 튀지 않는다\r\n textarea.ad-view-edit {\r\n display: block;\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 100%;\r\n min-height: 200px;\r\n padding: 6px 8px;\r\n border: 0;\r\n background: rgba(0, 0, 0, 0.22);\r\n color: #e6e6e6;\r\n font-family: monospace;\r\n font-size: 12px;\r\n line-height: 1.5;\r\n resize: none;\r\n tab-size: 4;\r\n\r\n &:focus {\r\n outline: 0;\r\n }\r\n }\r\n\r\n // 최근 파일 칩 — Codex 앱의 결과물 탭 자리\r\n .ad-view-tabs {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 4px;\r\n padding: 6px 8px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n user-select: none;\r\n }\r\n\r\n .ad-view-tab {\r\n max-width: 100%;\r\n overflow: hidden;\r\n padding: 2px 7px;\r\n border: 1px solid rgba(255, 255, 255, 0.1);\r\n border-radius: 10px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.55);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n color: #fff;\r\n border-color: rgba(255, 255, 255, 0.25);\r\n }\r\n\r\n &.ad-view-tab-on {\r\n border-color: rgba(97, 175, 239, 0.6);\r\n background: rgba(97, 175, 239, 0.14);\r\n color: #cfe6ff;\r\n }\r\n }\r\n\r\n .ad-view-body {\r\n flex: 1 1 auto;\r\n overflow: auto;\r\n padding: 10px 12px 16px;\r\n color: rgba(255, 255, 255, 0.85);\r\n // 문서를 읽는 자리라 글자 선택은 열어 둔다 (사이드바는 막아 둔다)\r\n user-select: text;\r\n }\r\n\r\n .ad-view-foot {\r\n flex: 0 0 auto;\r\n overflow: hidden;\r\n padding: 5px 10px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.06);\r\n color: rgba(255, 255, 255, 0.35);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-size: 10px;\r\n }\r\n\r\n // 폭 조절 손잡이 — 터미널과 닿는 쪽 경계선\r\n .ad-view-resize {\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n width: 6px;\r\n cursor: col-resize;\r\n z-index: 6;\r\n }\r\n\r\n &.ad-view-right .ad-view-resize { left: -2px; }\r\n &.ad-view-left .ad-view-resize { right: -2px; }\r\n\r\n .ad-view-empty {\r\n padding: 8px 2px;\r\n color: rgba(255, 255, 255, 0.55);\r\n line-height: 1.7;\r\n\r\n ul { padding-left: 18px; margin: 6px 0 0; }\r\n li { margin: 2px 0; }\r\n }\r\n\r\n .ad-view-dim {\r\n color: rgba(255, 255, 255, 0.35);\r\n font-size: 11px;\r\n }\r\n\r\n // ---------- 종류별 본문 ----------\r\n\r\n .ad-view-image {\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: center;\r\n\r\n img {\r\n max-width: 100%;\r\n // 원본보다 키우지 않는다 — 확대하면 스크린샷 글자가 뭉개진다\r\n height: auto;\r\n background: rgba(255, 255, 255, 0.04);\r\n }\r\n }\r\n\r\n .ad-view-code {\r\n display: flex;\r\n gap: 8px;\r\n font-family: monospace;\r\n font-size: 11px;\r\n line-height: 1.5;\r\n\r\n pre {\r\n margin: 0;\r\n white-space: pre;\r\n }\r\n\r\n .ad-view-gutter {\r\n flex: 0 0 auto;\r\n color: rgba(255, 255, 255, 0.25);\r\n text-align: right;\r\n user-select: none;\r\n }\r\n\r\n .ad-view-src {\r\n flex: 1 1 auto;\r\n overflow-x: auto;\r\n color: rgba(255, 255, 255, 0.85);\r\n }\r\n }\r\n\r\n .ad-view-table {\r\n overflow-x: auto;\r\n\r\n table {\r\n border-collapse: collapse;\r\n font-size: 11px;\r\n }\r\n\r\n th, td {\r\n max-width: 260px;\r\n overflow: hidden;\r\n padding: 3px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.08);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n }\r\n\r\n th {\r\n position: sticky;\r\n top: 0;\r\n background: #262a31;\r\n color: rgba(255, 255, 255, 0.7);\r\n }\r\n\r\n .ad-view-rownum {\r\n color: rgba(255, 255, 255, 0.3);\r\n text-align: right;\r\n }\r\n }\r\n\r\n // ---------- 마크다운 ----------\r\n .ad-view-md {\r\n line-height: 1.65;\r\n word-break: break-word;\r\n\r\n h1, h2, h3, h4, h5, h6 {\r\n margin: 1.1em 0 0.5em;\r\n color: #fff;\r\n line-height: 1.3;\r\n }\r\n h1 { font-size: 18px; }\r\n h2 { font-size: 16px; padding-bottom: 3px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }\r\n h3 { font-size: 14px; }\r\n h4, h5, h6 { font-size: 13px; }\r\n\r\n p { margin: 0.6em 0; }\r\n\r\n a {\r\n color: #61afef;\r\n text-decoration: none;\r\n cursor: pointer;\r\n\r\n &:hover { text-decoration: underline; }\r\n }\r\n\r\n code {\r\n padding: 1px 4px;\r\n border-radius: 3px;\r\n background: rgba(255, 255, 255, 0.08);\r\n font-family: monospace;\r\n font-size: 11px;\r\n }\r\n\r\n pre {\r\n overflow-x: auto;\r\n margin: 0.7em 0;\r\n padding: 8px 10px;\r\n border: 1px solid rgba(255, 255, 255, 0.07);\r\n border-radius: 5px;\r\n background: rgba(0, 0, 0, 0.28);\r\n\r\n code {\r\n padding: 0;\r\n background: transparent;\r\n line-height: 1.5;\r\n }\r\n }\r\n\r\n ul, ol { padding-left: 20px; margin: 0.5em 0; }\r\n li { margin: 0.15em 0; }\r\n\r\n .ad-md-task { list-style: none; margin-left: -14px; }\r\n .ad-md-check { color: rgba(255, 255, 255, 0.6); }\r\n\r\n blockquote {\r\n margin: 0.7em 0;\r\n padding: 2px 0 2px 10px;\r\n border-left: 3px solid rgba(255, 255, 255, 0.18);\r\n color: rgba(255, 255, 255, 0.6);\r\n }\r\n\r\n hr {\r\n margin: 1.2em 0;\r\n border: 0;\r\n border-top: 1px solid rgba(255, 255, 255, 0.12);\r\n }\r\n\r\n table {\r\n display: block;\r\n overflow-x: auto;\r\n border-collapse: collapse;\r\n margin: 0.7em 0;\r\n font-size: 11px;\r\n }\r\n\r\n th, td {\r\n padding: 4px 8px;\r\n border: 1px solid rgba(255, 255, 255, 0.1);\r\n text-align: left;\r\n }\r\n\r\n th { background: rgba(255, 255, 255, 0.05); color: #fff; }\r\n\r\n img {\r\n max-width: 100%;\r\n border-radius: 4px;\r\n }\r\n\r\n .ad-md-noimg {\r\n padding: 1px 5px;\r\n border-radius: 3px;\r\n background: rgba(255, 255, 255, 0.06);\r\n color: rgba(255, 255, 255, 0.45);\r\n font-size: 11px;\r\n }\r\n }\r\n\r\n // ---------- 변경 탭 (파일/변경 스위치 + git diff) ----------\r\n // 최근 파일 칩과 같은 모양을 쓴다 — 같은 자리에서 같은 방식으로 고르는 것이라서.\r\n .ad-view-modes {\r\n display: flex;\r\n flex: 0 0 auto;\r\n gap: 4px;\r\n padding: 6px 8px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n user-select: none;\r\n }\r\n\r\n .ad-view-mode {\r\n flex: 0 0 auto;\r\n padding: 3px 12px;\r\n border: 1px solid rgba(255, 255, 255, 0.1);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.55);\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n border-color: rgba(255, 255, 255, 0.25);\r\n color: #fff;\r\n }\r\n\r\n &.ad-view-mode-on {\r\n border-color: rgba(97, 175, 239, 0.6);\r\n background: rgba(97, 175, 239, 0.14);\r\n color: #cfe6ff;\r\n }\r\n }\r\n\r\n .ad-view-diff {\r\n .ad-plus { color: #98c379; }\r\n .ad-minus { color: #e06c75; }\r\n }\r\n\r\n // `2 files changed +9 -6` 자리\r\n .ad-view-diff-sum {\r\n padding-bottom: 6px;\r\n color: rgba(255, 255, 255, 0.8);\r\n font-weight: 600;\r\n }\r\n\r\n .ad-view-diff-list {\r\n margin-bottom: 10px;\r\n padding-bottom: 6px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\r\n }\r\n\r\n .ad-view-diff-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n width: 100%;\r\n padding: 2px 4px;\r\n border: 0;\r\n border-radius: 3px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.7);\r\n text-align: left;\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover { background: rgba(255, 255, 255, 0.07); }\r\n\r\n .ad-view-diff-st {\r\n flex: 0 0 auto;\r\n min-width: 40px;\r\n color: rgba(255, 255, 255, 0.4);\r\n text-align: center;\r\n font-size: 10px;\r\n }\r\n\r\n .ad-view-diff-st-added { color: #98c379; }\r\n .ad-view-diff-st-deleted { color: #e06c75; }\r\n .ad-view-diff-st-renamed { color: #61afef; }\r\n\r\n .ad-view-diff-name {\r\n flex: 1 1 auto;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n // 경로는 등폭이 읽기 쉽다 — 같은 폴더가 세로로 줄을 맞춘다\r\n font-family: monospace;\r\n }\r\n\r\n .ad-view-diff-num {\r\n flex: 0 0 auto;\r\n\r\n b {\r\n margin-left: 5px;\r\n font-weight: 500;\r\n }\r\n }\r\n }\r\n\r\n .ad-view-diff-sub {\r\n margin: 4px 0;\r\n color: rgba(255, 255, 255, 0.4);\r\n font-size: 11px;\r\n }\r\n\r\n .ad-view-diff-untracked {\r\n margin-bottom: 10px;\r\n padding-bottom: 6px;\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\r\n\r\n .ad-view-diff-u {\r\n overflow: hidden;\r\n padding: 1px 4px;\r\n color: rgba(255, 255, 255, 0.5);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-family: monospace;\r\n font-size: 11px;\r\n\r\n &[data-ad-open] {\r\n cursor: pointer;\r\n\r\n &:hover { background: rgba(255, 255, 255, 0.07); color: #fff; }\r\n }\r\n }\r\n }\r\n\r\n .ad-view-diff-file {\r\n margin-bottom: 14px;\r\n }\r\n\r\n // 파일명은 스크롤해도 남아 있어야 한다 — 긴 diff 를 내려가다 보면 어느 파일인지 잊는다\r\n .ad-view-diff-fhead {\r\n position: sticky;\r\n top: 0;\r\n z-index: 1;\r\n overflow: hidden;\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.08);\r\n border-radius: 4px 4px 0 0;\r\n background: #262a31;\r\n color: rgba(255, 255, 255, 0.8);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-family: monospace;\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover { color: #fff; }\r\n }\r\n\r\n .ad-view-diff-note {\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.08);\r\n border-top: 0;\r\n color: rgba(255, 255, 255, 0.35);\r\n font-size: 11px;\r\n }\r\n\r\n .ad-view-diff-lines {\r\n overflow-x: auto;\r\n border: 1px solid rgba(255, 255, 255, 0.08);\r\n border-top: 0;\r\n font-family: monospace;\r\n font-size: 11px;\r\n line-height: 1.5;\r\n }\r\n\r\n // 한 줄 = 구 번호 · 신 번호 · 본문. 배경이 줄 전체를 덮어야 추가/삭제가 눈에 띈다\r\n .ad-dl {\r\n display: flex;\r\n // 가로 스크롤 안에서도 배경이 끊기지 않게 (본문이 넘치면 줄이 넓어진다)\r\n min-width: 100%;\r\n min-height: 17px;\r\n white-space: pre;\r\n\r\n .ad-dl-no {\r\n flex: 0 0 auto;\r\n width: 38px;\r\n padding-right: 6px;\r\n color: rgba(255, 255, 255, 0.25);\r\n text-align: right;\r\n user-select: none;\r\n }\r\n\r\n .ad-dl-tx {\r\n flex: 1 0 auto;\r\n padding-right: 8px;\r\n color: rgba(255, 255, 255, 0.8);\r\n }\r\n }\r\n\r\n .ad-dl-add {\r\n background: rgba(152, 195, 121, 0.12);\r\n\r\n .ad-dl-tx { color: #b7d99b; }\r\n }\r\n\r\n .ad-dl-del {\r\n background: rgba(224, 108, 117, 0.12);\r\n\r\n .ad-dl-tx { color: #eda3a9; }\r\n }\r\n\r\n .ad-dl-hunk {\r\n background: rgba(97, 175, 239, 0.1);\r\n\r\n .ad-dl-tx { color: rgba(97, 175, 239, 0.85); }\r\n }\r\n\r\n // ---------- 스테이지 딱지 (파일 목록 / 추적 안 됨 줄) ----------\r\n // 유닛 L 이 넘긴 CSS — 그 유닛은 이 파일을 소유하지 않아 배리어에서 메인이 넣는다.\r\n // 상태 색은 `.ad-view-diff-st-*` 와 같은 팔레트를 쓴다 (같은 줄에서 두 딱지가 싸우지 않게).\r\n .ad-view-stage {\r\n flex: 0 0 auto;\r\n min-width: 44px;\r\n padding: 0 4px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 3px;\r\n color: rgba(255, 255, 255, 0.45);\r\n text-align: center;\r\n font-size: 10px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n border-color: rgba(255, 255, 255, 0.4);\r\n color: #fff;\r\n }\r\n }\r\n\r\n .ad-view-stage-staged {\r\n border-color: rgba(152, 195, 121, 0.5);\r\n background: rgba(152, 195, 121, 0.14);\r\n color: #b7d99b;\r\n }\r\n\r\n .ad-view-stage-both {\r\n border-color: rgba(229, 192, 123, 0.5);\r\n background: rgba(229, 192, 123, 0.12);\r\n color: #e5c07b;\r\n }\r\n\r\n .ad-view-stage-conflict {\r\n border-color: rgba(224, 108, 117, 0.6);\r\n background: rgba(224, 108, 117, 0.14);\r\n color: #eda3a9;\r\n }\r\n\r\n // ---------- `파일:라인` 참조 ----------\r\n // 유닛 U 가 넘긴 CSS — 그 유닛은 이 파일을 소유하지 않아 배리어에서 메인이 넣는다.\r\n //\r\n // 누를 수 있는 줄은 커서와 왼쪽 선으로 알린다. 줄번호 칸은 `user-select: none` 이라\r\n // 거기를 눌러도 글자 선택이 시작되지 않는다 (본문 복사와 싸우지 않게)\r\n .ad-dl[data-ad-ref-line],\r\n .ad-dl[data-ad-ref-del],\r\n .ad-cl[data-ad-ref-line] {\r\n cursor: pointer;\r\n }\r\n\r\n .ad-dl[data-ad-ref-line]:hover,\r\n .ad-cl[data-ad-ref-line]:hover {\r\n box-shadow: inset 2px 0 0 rgba(97, 175, 239, 0.55);\r\n }\r\n\r\n // 고른 줄. 추가/삭제 배경(`.ad-dl-add`/`.ad-dl-del`)을 덮어야 하므로 두 클래스로 특정한다\r\n .ad-dl.ad-ref-sel,\r\n .ad-cl.ad-ref-sel {\r\n box-shadow: inset 2px 0 0 #61afef;\r\n background: rgba(97, 175, 239, 0.22);\r\n\r\n .ad-dl-no,\r\n .ad-cl-no {\r\n color: rgba(255, 255, 255, 0.6);\r\n }\r\n\r\n .ad-dl-tx,\r\n .ad-cl-tx {\r\n color: #fff;\r\n }\r\n }\r\n\r\n // ---------- `파일` 모드 코드 보기: 줄마다 한 칸 ----------\r\n // 원래는 `<pre>` 두 장을 좌우로 놓는 flex 였다. 줄 단위로 그리게 되면서(어느 줄을 눌렀는지\r\n // 알아야 한다) 세로로 쌓아야 하므로 배치만 되돌린다 — 글꼴·크기·줄높이는 그대로 쓴다\r\n .ad-view-code.ad-view-code-rows {\r\n display: block;\r\n overflow-x: auto;\r\n }\r\n\r\n .ad-cl {\r\n display: flex;\r\n // 가로 스크롤 안에서도 강조 배경이 끊기지 않게 (`.ad-dl` 과 같은 사정)\r\n min-width: 100%;\r\n min-height: 17px;\r\n white-space: pre;\r\n\r\n .ad-cl-no {\r\n flex: 0 0 auto;\r\n width: 44px;\r\n padding-right: 8px;\r\n color: rgba(255, 255, 255, 0.25);\r\n text-align: right;\r\n user-select: none;\r\n }\r\n\r\n .ad-cl-tx {\r\n flex: 1 0 auto;\r\n padding-right: 8px;\r\n color: rgba(255, 255, 255, 0.85);\r\n }\r\n }\r\n\r\n // ---------- 참조 안내줄 (본문 밖 — 자동 갱신에 지워지지 않는 자리) ----------\r\n .ad-view-ref {\r\n flex: 0 0 auto;\r\n overflow: hidden;\r\n padding: 4px 10px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.06);\r\n color: rgba(255, 255, 255, 0.5);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-size: 10px;\r\n }\r\n\r\n // 방금 넣었다 — REF_FLASH_MS(2.5초)만 켜진다\r\n .ad-view-ref-on {\r\n background: rgba(97, 175, 239, 0.14);\r\n color: #cfe6ff;\r\n }\r\n\r\n // ---------- 커밋 줄 (본문 밖 — 자동 갱신에 지워지지 않는 자리) ----------\r\n .ad-view-commit {\r\n flex: 0 0 auto;\r\n padding: 6px 8px;\r\n border-top: 1px solid rgba(255, 255, 255, 0.06);\r\n }\r\n\r\n .ad-view-commit-sum {\r\n margin-bottom: 4px;\r\n color: rgba(255, 255, 255, 0.55);\r\n font-size: 11px;\r\n }\r\n\r\n .ad-view-commit-row {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n }\r\n\r\n .ad-view-commit-msg {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n padding: 4px 6px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: rgba(0, 0, 0, 0.25);\r\n color: #e6e6e6;\r\n font-family: monospace;\r\n font-size: 11px;\r\n\r\n &:focus {\r\n border-color: rgba(97, 175, 239, 0.6);\r\n outline: 0;\r\n }\r\n }\r\n\r\n .ad-view-commit-go {\r\n flex: 0 0 auto;\r\n padding: 4px 10px;\r\n border: 1px solid rgba(255, 255, 255, 0.14);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.7);\r\n font-size: 11px;\r\n cursor: pointer;\r\n\r\n &:hover:not(:disabled) {\r\n border-color: rgba(255, 255, 255, 0.35);\r\n color: #fff;\r\n }\r\n\r\n // 메시지가 비면 잠긴다 — 잠긴 것이 눈에 보여야 \"왜 안 눌리나\" 를 묻지 않는다\r\n &:disabled {\r\n opacity: 0.45;\r\n cursor: default;\r\n }\r\n\r\n // 확인 대기: 다음 누름이 실제 커밋이라는 것을 색으로 경고한다\r\n &.ad-view-commit-armed {\r\n border-color: rgba(224, 108, 117, 0.7);\r\n background: rgba(224, 108, 117, 0.18);\r\n color: #ffd7da;\r\n }\r\n }\r\n\r\n .ad-view-commit-note {\r\n margin-top: 4px;\r\n overflow: hidden;\r\n color: rgba(255, 255, 255, 0.4);\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-size: 11px;\r\n }\r\n}\r\n\r\n// 우클릭 상태 메뉴\r\n.ad-menu {\r\n position: fixed;\r\n z-index: 10000;\r\n min-width: 130px;\r\n padding: 4px;\r\n border: 1px solid rgba(255, 255, 255, 0.12);\r\n border-radius: 6px;\r\n background: #262a31;\r\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);\r\n font-size: 12px;\r\n user-select: none;\r\n\r\n .ad-menu-item {\r\n padding: 6px 10px;\r\n border-radius: 4px;\r\n color: rgba(255, 255, 255, 0.75);\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: rgba(255, 255, 255, 0.1);\r\n color: #fff;\r\n }\r\n }\r\n}\r\n\r\n// 도킹 가이드 — 헤더를 끌고 있는 동안만 창 위에 뜬다\r\n.ad-dock-overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: 9998;\r\n // 커서 판정은 좌표로 직접 하므로 오버레이가 이벤트를 가로채면 안 된다\r\n pointer-events: none;\r\n}\r\n\r\n// 놓으면 사이드바가 자리잡을 영역\r\n.ad-dock-preview {\r\n position: fixed;\r\n display: none;\r\n border: 1px solid rgba(97, 175, 239, 0.9);\r\n background: rgba(97, 175, 239, 0.22);\r\n box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.18);\r\n transition: left 0.08s ease, top 0.08s ease, width 0.08s ease, height 0.08s ease;\r\n}\r\n\r\n// 네 가장자리에 뜨는 도킹 표적\r\n.ad-dock-zone {\r\n position: fixed;\r\n width: 46px;\r\n height: 46px;\r\n margin: -23px 0 0 -23px;\r\n border: 1px solid rgba(255, 255, 255, 0.35);\r\n border-radius: 6px;\r\n background: rgba(38, 42, 49, 0.92);\r\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n\r\n .ad-dock-mark {\r\n width: 26px;\r\n height: 26px;\r\n border: 1px solid rgba(255, 255, 255, 0.45);\r\n border-radius: 3px;\r\n }\r\n\r\n &.active {\r\n border-color: rgba(97, 175, 239, 0.95);\r\n background: rgba(97, 175, 239, 0.35);\r\n }\r\n\r\n // 표적 안의 작은 사각형이 \"어느 쪽 절반에 붙는지\" 를 그대로 보여준다\r\n &.ad-dock-zone-left .ad-dock-mark { border-left: 4px solid rgba(97, 175, 239, 0.95); }\r\n &.ad-dock-zone-right .ad-dock-mark { border-right: 4px solid rgba(97, 175, 239, 0.95); }\r\n &.ad-dock-zone-top .ad-dock-mark { border-top: 4px solid rgba(97, 175, 239, 0.95); }\r\n &.ad-dock-zone-bottom .ad-dock-mark { border-bottom: 4px solid rgba(97, 175, 239, 0.95); }\r\n}\r\n\r\n// 놓일 자리를 가리키는 삽입선 — 줄을 끌고 있는 동안만 뜬다 (deck.service drawReorderLine).\r\n// 좌표는 인라인으로 준다(가로 도킹은 세로선, 상/하 도킹은 가로선). `.ad-list` 안이 아니라\r\n// body 에 붙는 이유는 그 함수 주석에 있다 — 목록을 다시 그릴 때 날아가지 않게, 그리고\r\n// 회귀 프로브가 목록의 자식만 훑어 그룹을 읽으므로 낯선 노드를 끼워 넣지 않게.\r\n.ad-reorder-line {\r\n position: fixed;\r\n z-index: 9997;\r\n border-radius: 1px;\r\n background: rgba(97, 175, 239, 0.95);\r\n box-shadow: 0 0 5px rgba(97, 175, 239, 0.6);\r\n // 좌표 판정은 elementFromPoint 로 한다 — 선이 이벤트를 가로채면 자기 자신을 짚는다\r\n pointer-events: none;\r\n}\r\n\r\n// 줄을 끄는 동안 — 커서를 옮기기 모양으로 두고, 커서가 터미널 위로 넘어가도 모양이 유지되게\r\nbody.ad-reordering {\r\n cursor: grabbing;\r\n\r\n // 줄 자신은 `cursor: pointer`(클릭=탭 선택)라 body 것으로는 덮이지 않는다.\r\n // 끌고 있는 동안만 사이드바 안쪽을 통째로 덮어써 손 모양이 도중에 바뀌지 않게 한다.\r\n #agentdeck-sidebar,\r\n #agentdeck-sidebar * {\r\n cursor: grabbing !important;\r\n }\r\n\r\n .content.main {\r\n pointer-events: none;\r\n }\r\n}\r\n\r\n// 경계선을 끄는 동안에는 터미널이 커서를 뺏지 않게 한다\r\nbody.ad-resizing {\r\n cursor: inherit;\r\n\r\n .content.main {\r\n pointer-events: none;\r\n }\r\n}\r\n\r\n// AgentDeck 이 켜져 있을 때만 순정 탭바를 감춘다 (사이드바가 그 역할을 대신함)\r\nbody.agentdeck-active {\r\n .content.main > .tab-bar {\r\n display: none !important;\r\n }\r\n\r\n .window {\r\n display: flex;\r\n flex-direction: row;\r\n // 사이드바를 창 안에 절대 배치하므로 기준점이 필요하다\r\n position: relative;\r\n // 터미널 옆에 남는 여백이 밝은 기본 배경으로 튀지 않게 한다\r\n // (--ad-panel-bg 가 있으면 배경 이미지가 이 여백으로도 비친다)\r\n background: var(--ad-panel-bg, var(--bs-body-bg, #1e2127));\r\n }\r\n}\r\n\r\n// ---------- 드롭 확인 배너 (터미널 위에 파일을 끌어다 놓았을 때) ----------\r\n// OS 모달이 아니라 인라인 카드다 — 모달은 터미널 흐름을 끊고 CDP 로 검증할 수 없다.\r\n// 화면 오른쪽 아래에 띄운다: 입력창(아래 중앙)과 사이드바(좌/우 가장자리)를 둘 다 피하려면\r\n// 그 모서리가 가장 덜 겹친다. `z-index` 는 사이드바(#agentdeck-sidebar)보다 위여야 한다.\r\n.ad-drop-ask {\r\n position: fixed;\r\n right: 16px;\r\n bottom: 16px;\r\n z-index: 10000;\r\n min-width: 260px;\r\n max-width: 420px;\r\n padding: 10px 12px;\r\n border: 1px solid rgba(255, 255, 255, 0.16);\r\n border-radius: 6px;\r\n background: rgba(24, 26, 30, 0.97);\r\n box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);\r\n color: #e6e6e6;\r\n font-size: 12px;\r\n\r\n .ad-drop-ask-msg {\r\n overflow: hidden;\r\n margin-bottom: 8px;\r\n white-space: nowrap;\r\n text-overflow: ellipsis;\r\n font-weight: 600;\r\n }\r\n\r\n .ad-drop-ask-row {\r\n display: flex;\r\n gap: 6px;\r\n align-items: center;\r\n }\r\n\r\n button {\r\n flex: 1 1 auto;\r\n padding: 5px 8px;\r\n border: 1px solid rgba(255, 255, 255, 0.16);\r\n border-radius: 4px;\r\n background: transparent;\r\n color: rgba(255, 255, 255, 0.85);\r\n font-size: 12px;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n border-color: rgba(97, 175, 239, 0.6);\r\n background: rgba(97, 175, 239, 0.14);\r\n color: #fff;\r\n }\r\n }\r\n\r\n // 닫기는 강조하지 않는다 — 기본 선택이 아니다\r\n .ad-drop-ask-close {\r\n flex: 0 0 auto;\r\n min-width: 26px;\r\n padding: 5px 6px;\r\n color: rgba(255, 255, 255, 0.45);\r\n }\r\n\r\n .ad-drop-ask-again {\r\n display: flex;\r\n gap: 5px;\r\n align-items: center;\r\n margin-top: 8px;\r\n margin-bottom: 0;\r\n color: rgba(255, 255, 255, 0.5);\r\n font-size: 11px;\r\n cursor: pointer;\r\n }\r\n}\r\n"],"sourceRoot":""}]);
1435
1535
  // Exports
1436
1536
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
1437
1537
 
@@ -3039,6 +3139,23 @@ class AgentDeckConfigProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_0__.Co
3039
3139
  * 설정 창에서 토글한다 (recovery.service.ts).
3040
3140
  */
3041
3141
  noTabRecovery: false,
3142
+ /**
3143
+ * 기동할 때 npm 레지스트리를 보고 **새 버전을 받아 둔다** (적용은 다음 기동).
3144
+ *
3145
+ * Tabby 는 플러그인을 기동할 때 한 번 읽으므로 돌고 있는 것을 갈아끼울 수 없다 —
3146
+ * 그래서 받아만 두고 한 번 알린다. 기동 20초 뒤에 한 번, 그 뒤로는 아래 간격마다.
3147
+ *
3148
+ * **개발 설치(소스 트리 junction)에는 설치하지 않는다.** 거기에 `npm install` 이 돌면
3149
+ * 작업 중인 소스를 덮어쓴다 — 새 버전이 있다는 것만 알리고 손대지 않는다
3150
+ * (판정은 `update.ts` 의 `canSelfUpdate`).
3151
+ */
3152
+ autoUpdate: true,
3153
+ /** 확인 간격 (시간). 기동마다 묻지 않는다 */
3154
+ updateCheckIntervalHours: 6,
3155
+ /** 마지막으로 레지스트리에 물어본 시각 (epoch ms) — 설정이 아니라 상태다 */
3156
+ lastUpdateCheck: 0,
3157
+ /** 레지스트리 주소. 사내 미러를 쓰면 여기를 바꾼다 */
3158
+ updateRegistry: 'https://registry.npmjs.org',
3042
3159
  /** PTY 출력이 이 시간(ms) 이상 없으면 running -> idle */
3043
3160
  idleAfterMs: 2500,
3044
3161
  /** 출력 패턴으로 상태를 자동 추론할지 */
@@ -3144,36 +3261,43 @@ class AgentDeckConfigProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_0__.Co
3144
3261
  * 아무 일도 일어나지 않는다(hover 리스너를 두지 않았다).
3145
3262
  */
3146
3263
  dragAutoScroll: true,
3264
+ // 세션 그룹에는 설정이 없다 — **항상 켜져 있고 경계도 자동으로 잡는다.**
3265
+ //
3266
+ // 스위치(`groupByProject`)를 없앤 근거: 그룹이 하나뿐이면 헤더를 아예 그리지 않고
3267
+ // 순서도 건드리지 않으므로(deck.service `renderPlan`), 한 프로젝트만 쓰는 사람에게는
3268
+ // 켜 둔 화면과 끈 화면이 **같다.** 끌 이유가 없는 스위치는 설정 창에서 고를 것만 늘린다.
3269
+ // 경계를 적는 값(`groupRoot`)도 없앴다 — `project-root.ts` 가 저장소 루트(`.git` 등)를
3270
+ // 직접 찾는다. 사람이 상위 폴더를 적는 방식은 프로젝트들이 한 폴더에 나란히 있을 때만
3271
+ // 맞고, 깊이가 다르면 어떤 값을 적어도 한쪽이 틀렸다.
3272
+ // 묶는 기준은 **탭이 태어난 cwd** 다 — 셸에서 `cd` 를 해도 따라오지 않으므로
3273
+ // (docs/DEVELOPMENT.md 0.5.0 실측) 세션이 사는 동안 그룹이 흔들리지 않는다.
3147
3274
  /**
3148
- * 사이드바 탭을 **작업 폴더(프로젝트) 단위로 묶고** 그룹 헤더로 접을 수 있게 한다.
3275
+ * 접어 둔 그룹 키 목록. 그룹 헤더를 클릭할 때마다 갱신되고 다음 기동에 복원된다.
3149
3276
  *
3150
- * 기본으로 켠다 — 그룹이 하나뿐이면 헤더를 아예 그리지 않으므로(deck.service `render`)
3151
- * 한 프로젝트만 쓰는 사람에게는 0.6.0 까지와 화면이 똑같고, 탭이 열 개를 넘겨
3152
- * 여러 프로젝트가 섞이는 순간에만 헤더가 생긴다. 즉 켜 둬서 잃는 것이 없다.
3153
- * 묶는 기준은 **탭이 태어난 cwd** 다 — 셸에서 `cd` 를 해도 따라오지 않으므로
3154
- * (docs/DEVELOPMENT.md 0.5.0 실측) 세션이 사는 동안 그룹이 흔들리지 않는다.
3155
- * 그룹 안의 탭 순서는 `sortByStatus` 를 그대로 따른다.
3277
+ * 키는 `group.ts` 가 정규화한 경로를 **소문자로 접은** 값이다(`foldGroupKey`) —
3278
+ * 셸이 알려주는 경로의 대소문자·구분자가 기동마다 달라도 접힘이 유지돼야 하기 때문이다.
3156
3279
  */
3157
- groupByProject: true,
3280
+ collapsedGroups: [],
3158
3281
  /**
3159
- * 프로젝트 경계의 기준 폴더. 비우면 `rootProfileCwd` 를 쓴다.
3282
+ * 그룹 안에 **지난 세션**(죽은 Claude Code 세션) 줄을 그린다.
3160
3283
  *
3161
- * 이게 필요한 이유 — cwd 를 그대로 키로 쓰면 `D:\Project\a` 에서 띄운 탭과
3162
- * `D:\Project\a\sub` 에서 띄운 탭이 다른 그룹으로 갈린다(서브폴더에서 에이전트를
3163
- * 띄우는 건 흔하다). 이 값을 `D:\Project` 로 채워 두면 그 **직하 한 단계**가 그룹이
3164
- * 되어 둘이 같은 그룹에 들어온다(`group.ts` `groupKeyOf`).
3165
- * 둘 다 비면 cwd 자체가 키다 — 프로젝트들이 한 상위 폴더에 모여 있지 않은 경우다.
3166
- * 저장소 루트(`.git`)를 찾아 자동으로 정하지는 않는다 — 렌더는 초당 여러 번 불리므로
3167
- * 그때마다 디스크를 훑을 수 없다.
3284
+ * 이 기능이 있는 이유는 `recovery.service.ts` 주석에 있다 — agentdeck 은 Tabby 의 순정
3285
+ * 탭 복원을 껐다(셸만 살아나고 세션은 죽어 있어 "주인 없는 탭" 이 남는다).
3286
+ * `claude --resume` 이 그 자리에 들어갈 진짜 복원이고, 사람이 막히는 곳은 명령이 아니라
3287
+ * UUID 목록에서 자기 작업을 찾는 것이라 **라벨을 붙여 보여주는 것** 이 이 기능의 전부다.
3168
3288
  */
3169
- groupRoot: '',
3289
+ resumeList: true,
3290
+ /** 그룹당 몇 줄까지 보여줄지. 0 이면 제한 없음 (목록이 길어지면 살아 있는 탭이 밀린다) */
3291
+ resumeListLimit: 5,
3170
3292
  /**
3171
- * 접어 둔 그룹 키 목록. 그룹 헤더를 클릭할 때마다 갱신되고 다음 기동에 복원된다.
3172
- *
3173
- * 키는 `group.ts` 가 정규화한 경로를 **소문자로 접은** 값이다(`foldGroupKey`) —
3174
- * 셸이 알려주는 경로의 대소문자·구분자가 기동마다 달라도 접힘이 유지돼야 하기 때문이다.
3293
+ * 며칠치까지. 기간 밖 세션은 **화면에서만** 빠지고 원장·기록은 그대로다.
3294
+ * stat 단계에서 잘라내므로 이 값이 작을수록 펼치는 순간이 싸다.
3175
3295
  */
3176
- collapsedGroups: [],
3296
+ resumeListDays: 7,
3297
+ /** 지난 세션 줄을 펼쳐 둔 그룹 키 (`collapsedGroups` 와 반대로 **펼침**을 적는다 — 기본이 접힘이라서) */
3298
+ resumeExpanded: [],
3299
+ /** 사람이 목록에서 숨긴 세션 id */
3300
+ resumeHidden: [],
3177
3301
  /**
3178
3302
  * 창이 가려져 있어도 사이드바를 계속 갱신한다 (Electron backgroundThrottling 해제).
3179
3303
  *
@@ -3408,10 +3532,24 @@ class AgentDeckConfigProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_0__.Co
3408
3532
  */
3409
3533
  repairHard: false,
3410
3534
  /**
3411
- * 복구 마지막에 `Ctrl+L`(0x0C)을 보내 TUI 에게 전체 재렌더를 시킨다.
3412
- * 입력 중이던 글자는 유지된다. 이걸 다른 뜻으로 쓰는 앱을 만나면 끈다.
3535
+ * 복구 마지막에 `Ctrl+L`(0x0C)을 보낸다.
3536
+ *
3537
+ * **기본 끔 (2026-09-11 전환).** 0x0C 를 "전부 다시 그려라" 로 받는 앱을 전제했는데,
3538
+ * **Claude Code 는 "화면을 지워라" 로 받는다** — 채증으로 확정했다.
3539
+ * `↻` 를 누른 뒤 스냅샷(`.agentdeck-screen.log` 06:04:52→54)에서 78행 중 **0~72행이
3540
+ * 통째로 비고** 입력창(73~77행)만 남았고, 진단 로그의 순서가 원인을 못 박는다:
3541
+ * `clear input area row=73/78`(73~77행만 지움) → `repair redraw-key ctrl-l` → 화면 전멸.
3542
+ * 사용자에게는 "새로고침하면 출력이 한참 위로 올라가 버린다" 로 보인다(유저 지적).
3543
+ *
3544
+ * 끄고도 복구는 된다 — 실제로 고치는 것은 잔상 지우기(`clearInputArea`)와
3545
+ * 크기 정합·흔들기(SIGWINCH)이고, 0x0C 는 그 위에 얹은 "확실히 해두기" 였다.
3546
+ * 지운 자리의 입력창은 앱이 자기 렌더 주기에 다시 그린다(Claude Code 는 상태줄을
3547
+ * 매초 갱신하므로 즉시 돌아온다). 수동 복구는 흔들기까지 하므로 그 자리에서 돌아온다.
3548
+ *
3549
+ * 켜는 경우 — Ctrl+L 이 실제로 재렌더인 앱만 쓰거나, 셸에서 화면이 지워져도
3550
+ * 상관없을 때. 켜면 자동복구에도 적용되므로 오탐 한 번에 보고 있던 화면이 밀린다.
3413
3551
  */
3414
- repairSendRedrawKey: true,
3552
+ repairSendRedrawKey: false,
3415
3553
  /**
3416
3554
  * 작업 루트 전용 프로필을 만들고 기본 프로필로 삼는다.
3417
3555
  *
@@ -3521,10 +3659,13 @@ __webpack_require__.r(__webpack_exports__);
3521
3659
  /* harmony import */ var _dock__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./dock */ "./src/dock.ts");
3522
3660
  /* harmony import */ var _order__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./order */ "./src/order.ts");
3523
3661
  /* harmony import */ var _group__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./group */ "./src/group.ts");
3524
- /* harmony import */ var _nav__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./nav */ "./src/nav.ts");
3525
- /* harmony import */ var _viewPanel__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./viewPanel */ "./src/viewPanel.ts");
3526
- /* harmony import */ var _viewer__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./viewer */ "./src/viewer.ts");
3527
- /* harmony import */ var _diag__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./diag */ "./src/diag.ts");
3662
+ /* harmony import */ var _project_root__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./project-root */ "./src/project-root.ts");
3663
+ /* harmony import */ var _nav__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./nav */ "./src/nav.ts");
3664
+ /* harmony import */ var _sessionLedger_service__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./sessionLedger.service */ "./src/sessionLedger.service.ts");
3665
+ /* harmony import */ var _sessionLedger__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./sessionLedger */ "./src/sessionLedger.ts");
3666
+ /* harmony import */ var _viewPanel__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./viewPanel */ "./src/viewPanel.ts");
3667
+ /* harmony import */ var _viewer__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./viewer */ "./src/viewer.ts");
3668
+ /* harmony import */ var _diag__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! ./diag */ "./src/diag.ts");
3528
3669
  var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
3529
3670
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3530
3671
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -3551,6 +3692,9 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
3551
3692
 
3552
3693
 
3553
3694
 
3695
+
3696
+
3697
+
3554
3698
 
3555
3699
  const SIDEBAR_ID = 'agentdeck-sidebar';
3556
3700
  const BODY_CLASS = 'agentdeck-active';
@@ -3743,7 +3887,7 @@ function getClipboard() {
3743
3887
  }
3744
3888
  catch (e) {
3745
3889
  cachedClipboard = null;
3746
- (0,_diag__WEBPACK_IMPORTED_MODULE_17__.diagCatch)('clipboard require(electron)', e);
3890
+ (0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagCatch)('clipboard require(electron)', e);
3747
3891
  }
3748
3892
  if (!cachedClipboard) {
3749
3893
  try {
@@ -3752,12 +3896,12 @@ function getClipboard() {
3752
3896
  }
3753
3897
  catch (e) {
3754
3898
  cachedClipboard = null;
3755
- (0,_diag__WEBPACK_IMPORTED_MODULE_17__.diagCatch)('clipboard window.require', e);
3899
+ (0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagCatch)('clipboard window.require', e);
3756
3900
  }
3757
3901
  }
3758
3902
  if (!cachedClipboard) {
3759
3903
  // 이미지 붙여넣기가 통째로 안 되는 상태다. 사유 없이는 "붙여넣기가 안 된다" 제보를 가를 수 없다
3760
- (0,_diag__WEBPACK_IMPORTED_MODULE_17__.diagOnce)('clipboard-missing', 'clipboard 없음 — 이미지 붙여넣기 불가');
3904
+ (0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagOnce)('clipboard-missing', 'clipboard 없음 — 이미지 붙여넣기 불가');
3761
3905
  }
3762
3906
  return cachedClipboard;
3763
3907
  }
@@ -3848,7 +3992,9 @@ let AgentDeckService = class AgentDeckService {
3848
3992
  constructor(app, config, status, hotkeys, profiles, hostWindow, platform,
3849
3993
  // 서브에이전트 개수를 물어보는 창구(`subagentsOf`). 순환 의존이 아니다 —
3850
3994
  // notify 쪽은 deck 을 모른다(상태는 `status.service` 를 거쳐 만난다)
3851
- notify, zone) {
3995
+ notify,
3996
+ // 지난 세션 원장. deck 이 라벨·cwd 를 아는 유일한 자리라 적는 쪽도 여기다
3997
+ ledger, zone) {
3852
3998
  this.app = app;
3853
3999
  this.config = config;
3854
4000
  this.status = status;
@@ -3857,6 +4003,7 @@ let AgentDeckService = class AgentDeckService {
3857
4003
  this.hostWindow = hostWindow;
3858
4004
  this.platform = platform;
3859
4005
  this.notify = notify;
4006
+ this.ledger = ledger;
3860
4007
  this.zone = zone;
3861
4008
  this.sidebar = null;
3862
4009
  this.listEl = null;
@@ -3947,6 +4094,11 @@ let AgentDeckService = class AgentDeckService {
3947
4094
  * `CWD_TTL_MS` 동안 재사용한다 (cd 로 옮겨 다녀도 그 정도면 따라간다).
3948
4095
  */
3949
4096
  this.cwdCache = new WeakMap();
4097
+ /**
4098
+ * 지난 세션 목록에서 "이미 열림" 으로 그린 줄이 가리키는 탭 — `세션id -> 탭`.
4099
+ * 매 렌더마다 `liveSessionIds()` 가 다시 채운다(탭이 닫히면 그 줄은 이어받기로 돌아간다).
4100
+ */
4101
+ this.resumeTabs = new Map();
3950
4102
  /**
3951
4103
  * 이 서비스의 출력 구독이 실제로 몇 번 돌았나 (진단용).
3952
4104
  *
@@ -3996,12 +4148,14 @@ let AgentDeckService = class AgentDeckService {
3996
4148
  var _a, _b, _c, _d, _e;
3997
4149
  // 진단 로그는 setup 보다 먼저 연다 — `.window` 를 못 찾아 배치를 포기하는 경로도
3998
4150
  // 로그에 남아야 하고, 세션 스탬프(버전·환경)는 그 판정보다 앞에 찍혀야 한다
3999
- (0,_diag__WEBPACK_IMPORTED_MODULE_17__.startDiagSession)({
4151
+ (0,_diag__WEBPACK_IMPORTED_MODULE_20__.startDiagSession)({
4000
4152
  tabby: (_b = (_a = this.platform).getAppVersion) === null || _b === void 0 ? void 0 : _b.call(_a),
4001
4153
  osRelease: (_d = (_c = this.platform).getOSRelease) === null || _d === void 0 ? void 0 : _d.call(_c),
4002
4154
  config: this.diagConfig(),
4003
4155
  });
4004
- (0,_diag__WEBPACK_IMPORTED_MODULE_17__.installErrorCapture)();
4156
+ (0,_diag__WEBPACK_IMPORTED_MODULE_20__.installErrorCapture)();
4157
+ // 원장 스캔이 끝나면 다시 그린다 — 스캔은 비동기라 첫 렌더에는 목록이 비어 있다
4158
+ this.ledger.onChange = () => this.scheduleRender();
4005
4159
  // setup 도중 예외가 나면 그 뒤 배선(탭 감시, 흔들기 예약)이 통째로 죽는데
4006
4160
  // 콘솔을 안 보면 알 길이 없다. 진단 파일에 남겨 밖에서 읽을 수 있게 한다
4007
4161
  try {
@@ -4018,15 +4172,15 @@ let AgentDeckService = class AgentDeckService {
4018
4172
  * relayout 처럼 초당 여러 번 불리는 자리에서 조기 return 사유를 남길 때 쓴다
4019
4173
  */
4020
4174
  diagOnce(key, line) {
4021
- ;(0,_diag__WEBPACK_IMPORTED_MODULE_17__.diagOnce)(key, line);
4175
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagOnce)(key, line);
4022
4176
  }
4023
4177
  /** 진단 한 줄 — 회전·시각 표기·메모리 보관은 `diag.ts` 가 한다 */
4024
4178
  diag(line) {
4025
- ;(0,_diag__WEBPACK_IMPORTED_MODULE_17__.diag)(line);
4179
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_20__.diag)(line);
4026
4180
  }
4027
4181
  /** 세션 머리말에 넣을 설정 — 고르는 기준은 `diag.ts` 의 `configStamp` 에 적혀 있다 */
4028
4182
  diagConfig() {
4029
- return (0,_diag__WEBPACK_IMPORTED_MODULE_17__.configStamp)(this.config.store);
4183
+ return (0,_diag__WEBPACK_IMPORTED_MODULE_20__.configStamp)(this.config.store);
4030
4184
  }
4031
4185
  setup() {
4032
4186
  var _a;
@@ -4350,15 +4504,19 @@ let AgentDeckService = class AgentDeckService {
4350
4504
  const idx = (g) => g.tabs.map(t => all.indexOf(t));
4351
4505
  return {
4352
4506
  cwdCache: all.map((t, i) => {
4353
- var _a, _b, _c, _d, _e;
4507
+ var _a, _b, _c, _d, _e, _f, _g;
4354
4508
  return ({
4355
4509
  index: i,
4356
4510
  title: String((_a = t.title) !== null && _a !== void 0 ? _a : '').slice(0, 40),
4357
4511
  dir: (_c = (_b = this.cwdCache.get(t)) === null || _b === void 0 ? void 0 : _b.dir) !== null && _c !== void 0 ? _c : null,
4358
4512
  at: (_e = (_d = this.cwdCache.get(t)) === null || _d === void 0 ? void 0 : _d.at) !== null && _e !== void 0 ? _e : null,
4513
+ // 이 탭의 cwd 에서 찾아낸 프로젝트 루트 (`null` = 못 찾음 → cwd 자체가 키)
4514
+ projectRoot: (0,_project_root__WEBPACK_IMPORTED_MODULE_14__.projectRootOf)((_g = (_f = this.cwdCache.get(t)) === null || _f === void 0 ? void 0 : _f.dir) !== null && _g !== void 0 ? _g : null),
4359
4515
  });
4360
4516
  }),
4361
- groupRoot: this.config.store.agentDeck.groupRoot || this.config.store.agentDeck.rootProfileCwd || null,
4517
+ // 지금까지 탐지해 둔 폴더→루트 — 그룹이 안 갈릴 때 "탐지가 아직" 인지
4518
+ // "루트가 같아서" 인지 이것 없이는 가를 수 없다 (cwd 를 같이 여는 것과 같은 이유)
4519
+ projectRoots: (0,_project_root__WEBPACK_IMPORTED_MODULE_14__.projectRootCacheSnapshot)(),
4362
4520
  /**
4363
4521
  * 그룹 계산 결과 (헤더 생략과 무관하게 `groupsFor` 가 낸 것).
4364
4522
  *
@@ -4402,6 +4560,46 @@ let AgentDeckService = class AgentDeckService {
4402
4560
  fold: (k) => (0,_group__WEBPACK_IMPORTED_MODULE_13__.foldGroupKey)(k),
4403
4561
  };
4404
4562
  },
4563
+ /**
4564
+ * 지난 세션 목록을 **제품 계산 그대로** 돌려준다 (`resumeRowsFor` + 원장 상태).
4565
+ *
4566
+ * `groups()` 와 같은 이유로 연다 — 검증 도구가 "어느 세션이 어느 그룹에 붙나" 를
4567
+ * 사본으로 재계산하면 그 사본이 낡는다. 화면에 그려졌는지는 프로브가 DOM
4568
+ * (`.ad-resume`)과 대조해 판정한다.
4569
+ */
4570
+ sessions: () => {
4571
+ const cfg = this.config.store.agentDeck;
4572
+ const rp = this.renderPlan();
4573
+ const live = this.liveSessionIds();
4574
+ const records = this.ledger.records();
4575
+ return {
4576
+ ledger: this.ledger.snapshot(),
4577
+ /** 원장+디스크를 합친 전체 (그룹 필터 전) */
4578
+ records: records.map(r => ({ ...r })),
4579
+ /** 지금 탭이 들고 있는 세션 */
4580
+ live: [...live.keys()],
4581
+ enabled: cfg.resumeList !== false,
4582
+ /** 그룹별로 실제 그릴 줄 — 화면 순서의 정답이다 */
4583
+ byGroup: rp.plan.map(g => ({
4584
+ key: g.key,
4585
+ label: g.label,
4586
+ expanded: this.isResumeExpanded(g.key),
4587
+ rows: (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_17__.resumeRowsFor)(records, {
4588
+ groupKeys: this.resumeKeysOf(g),
4589
+ keyOf: cwd => (0,_group__WEBPACK_IMPORTED_MODULE_13__.groupKeyOf)(cwd, _project_root__WEBPACK_IMPORTED_MODULE_14__.projectRootOf),
4590
+ fold: _group__WEBPACK_IMPORTED_MODULE_13__.foldGroupKey,
4591
+ live,
4592
+ days: Number(cfg.resumeListDays) || 0,
4593
+ limit: Number(cfg.resumeListLimit) || 0,
4594
+ hidden: new Set(Array.isArray(cfg.resumeHidden) ? cfg.resumeHidden : []),
4595
+ now: Date.now(),
4596
+ }).map(r => ({ sessionId: r.sessionId, label: r.label, cwd: r.cwd,
4597
+ lastStatus: r.lastStatus, openTabId: r.openTabId })),
4598
+ })),
4599
+ };
4600
+ },
4601
+ /** 원장 스캔을 지금 한 번 돌린다 (프로브가 기다릴 수 있게 Promise 를 준다) */
4602
+ rescanSessions: () => this.ledger.refresh(true),
4405
4603
  // 에이전트 판정 상태 — 진단 로그는 값이 바뀔 때만 찍히므로 "조회가 돌았는지" 를
4406
4604
  // 로그만으로는 가를 수 없다 (2026-09-08 검증 중 실제로 막혔다)
4407
4605
  debug: () => ({ outputHits: this.outputHits, decorator: this.status.debug }),
@@ -4506,7 +4704,7 @@ let AgentDeckService = class AgentDeckService {
4506
4704
  void this.probeAgent(root, 'manual');
4507
4705
  }
4508
4706
  },
4509
- kickLog: (0,_diag__WEBPACK_IMPORTED_MODULE_17__.diagMemory)(),
4707
+ kickLog: (0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagMemory)(),
4510
4708
  /**
4511
4709
  * 진단 묶음을 만들고 경로를 돌려준다 — 설정 창의 "진단 로그 모으기" 와 같은 동작.
4512
4710
  * 콘솔에서 쓰는 경로도 남겨 두는 이유: 설정 창을 열 수 없을 만큼 깨진 상태에서도 채증이 돼야 한다
@@ -4519,15 +4717,15 @@ let AgentDeckService = class AgentDeckService {
4519
4717
  * "아무 줄도 안 남았다" 로 거짓 실패한다 — 2026-09-09 에 IN9·PR4·PR5 가 그렇게
4520
4718
  * 한꺼번에 뒤집혔다. 경로는 제품에게 물어야 한다.
4521
4719
  */
4522
- diagPaths: () => ({ diag: _diag__WEBPACK_IMPORTED_MODULE_17__.DIAG_PATH, prev: _diag__WEBPACK_IMPORTED_MODULE_17__.DIAG_PREV_PATH, screen: _diag__WEBPACK_IMPORTED_MODULE_17__.SCREEN_PATH }),
4720
+ diagPaths: () => ({ diag: _diag__WEBPACK_IMPORTED_MODULE_20__.DIAG_PATH, prev: _diag__WEBPACK_IMPORTED_MODULE_20__.DIAG_PREV_PATH, screen: _diag__WEBPACK_IMPORTED_MODULE_20__.SCREEN_PATH }),
4523
4721
  collectDiag: (includeScreen = false) => {
4524
4722
  var _a, _b, _c, _d;
4525
- return (0,_diag__WEBPACK_IMPORTED_MODULE_17__.collectDiagBundle)({
4723
+ return (0,_diag__WEBPACK_IMPORTED_MODULE_20__.collectDiagBundle)({
4526
4724
  includeScreen,
4527
4725
  stamp: {
4528
4726
  tabby: (_b = (_a = this.platform).getAppVersion) === null || _b === void 0 ? void 0 : _b.call(_a),
4529
4727
  osRelease: (_d = (_c = this.platform).getOSRelease) === null || _d === void 0 ? void 0 : _d.call(_c),
4530
- config: (0,_diag__WEBPACK_IMPORTED_MODULE_17__.configStamp)(this.config.store),
4728
+ config: (0,_diag__WEBPACK_IMPORTED_MODULE_20__.configStamp)(this.config.store),
4531
4729
  },
4532
4730
  });
4533
4731
  },
@@ -4632,9 +4830,11 @@ let AgentDeckService = class AgentDeckService {
4632
4830
  catch { }
4633
4831
  // ③ 크기가 이미 맞아도 무조건 흔든다 — 이 함수를 부르는 이유 자체가 "맞는데 깨졌다" 다
4634
4832
  this.nudgePtyRedraw(pane);
4635
- // ④ 앱에게 직접 "다시 그려라". 흔들기가 도착해 앱이 새 폭을 받은 뒤에 보내야 하므로
4636
- // nudge 의 되돌리기(60ms)보다 확실히 뒤에 둔다.
4637
- if (cfg.repairSendRedrawKey !== false) {
4833
+ // ④ `Ctrl+L` — **기본으로 보내지 않는다** (`repairSendRedrawKey` 기본 끔).
4834
+ // Claude Code 가 이걸 "화면 지우기" 로 받아 보고 있던 대화를 스크롤백으로 밀어내는 것을
4835
+ // 2026-09-11 채증으로 확정했다(`sendRedrawKey` 주석). ①~③ 이 이미 고치므로 없어도 된다.
4836
+ // 켜 둔 경우에만 보내고, 흔들기가 도착한 뒤여야 하므로 nudge 되돌리기(60ms)보다 뒤에 둔다.
4837
+ if (cfg.repairSendRedrawKey === true) {
4638
4838
  setTimeout(() => this.sendRedrawKey(pane), REPAIR_REDRAW_DELAY_MS);
4639
4839
  }
4640
4840
  // ⑤ 채증 — "↻ 를 눌렀는데 안 고쳐진다" 를 감이 아니라 파일로 가른다.
@@ -4647,10 +4847,17 @@ let AgentDeckService = class AgentDeckService {
4647
4847
  }, SNAP_AFTER_MS);
4648
4848
  }
4649
4849
  /**
4650
- * TUI 에 "화면을 전부 다시 그려라" 를 보낸다 — `Ctrl+L`(0x0C).
4850
+ * `Ctrl+L`(0x0C) 을 보낸다 — **기본 경로가 아니다** (`repairSendRedrawKey`, 기본 끔).
4651
4851
  *
4652
- * Claude Code·Codex 를 포함해 거의 모든 TUI 와 셸이 이걸 전체 재렌더로 받는다.
4653
- * 입력 중이던 글자는 건드리지 않는다(readline·TUI 모두 입력 버퍼는 유지하고 화면만 다시 그린다).
4852
+ * **"거의 모든 TUI 가 이걸 전체 재렌더로 받는다" 는 틀린 전제였다** (2026-09-11 채증으로 확정).
4853
+ * Claude Code 는 이걸 **화면 지우기**로 받는다 — 지운 뒤 자기 입력창만 다시 그리므로
4854
+ * 보고 있던 대화가 통째로 스크롤백으로 밀려난다. `.agentdeck-screen.log` 06:04:52→54 의
4855
+ * `manual-before`(0~77행 전부 내용) → `manual-after`(73~77행만 남음) 가 그 증거이고,
4856
+ * `clear input area row=73/78` 이 73행 아래만 지웠음을 진단 로그가 확인해 준다.
4857
+ * 즉 0~72행을 날린 것은 우리 지우기가 아니라 이 키다.
4858
+ *
4859
+ * 그래서 복구의 기본 수단은 이 키가 아니라 **잔상 지우기 + 크기 정합/흔들기**다.
4860
+ * 자세한 근거와 켜는 기준은 `config.ts` 의 `repairSendRedrawKey` 주석에 있다.
4654
4861
  */
4655
4862
  sendRedrawKey(pane) {
4656
4863
  var _a;
@@ -4847,10 +5054,12 @@ let AgentDeckService = class AgentDeckService {
4847
5054
  // 스크롤백을 지우는 hard 복구도 하지 않는다 — 자동 경로에서 오탐 한 번에 지난 대화가
4848
5055
  // 날아가면 손해가 크다(`repairHard` 는 사람이 켤 때만).
4849
5056
  //
4850
- // 잔상을 **먼저** 지우고 그 다음에 앱에게 다시 그리라고 한다.
4851
- // 앱은 자기가 쓰는 자리만 덮으므로, 지우지 않으면 옛 줄이 `❯` 자리를 계속 차지한다.
5057
+ // 잔상을 지우면 앱이 **자기 렌더 주기에** 그 자리를 다시 그린다 — 앱은 자기가 쓰는
5058
+ // 자리만 덮으므로, 지우지 않으면 옛 줄이 `❯` 자리를 계속 차지한다.
5059
+ // `Ctrl+L` 로 재렌더를 재촉하던 것은 기본에서 뺐다 — Claude Code 가 그걸 화면 지우기로
5060
+ // 받아 대화를 밀어냈다(`sendRedrawKey` 주석). 자동 경로에서 그게 터지면 더 나쁘다.
4852
5061
  this.clearInputArea(pane, read);
4853
- if (cfg.repairSendRedrawKey !== false) {
5062
+ if (cfg.repairSendRedrawKey === true) {
4854
5063
  setTimeout(() => this.sendRedrawKey(pane), REPAIR_REDRAW_DELAY_MS);
4855
5064
  }
4856
5065
  // 복구가 실제로 먹혔는지 같은 파일에 이어 남긴다 — 앱이 다시 그릴 시간을 준다
@@ -4885,7 +5094,7 @@ let AgentDeckService = class AgentDeckService {
4885
5094
  }
4886
5095
  this.autoRepairCount.delete(pane);
4887
5096
  this.autoRepairHealthySince.delete(pane);
4888
- (0,_diag__WEBPACK_IMPORTED_MODULE_17__.resetDiagOnce)('auto-repair-cap');
5097
+ (0,_diag__WEBPACK_IMPORTED_MODULE_20__.resetDiagOnce)('auto-repair-cap');
4889
5098
  this.diag(`screen auto-repair 예산 회복`
4890
5099
  + ` (화면이 ${Math.round((now - since) / 1000)}초 동안 온전했다)`);
4891
5100
  }
@@ -4960,7 +5169,7 @@ let AgentDeckService = class AgentDeckService {
4960
5169
  .map((l, i) => ({ i, t: l.text.replace(/\s+$/, ''), w: l.wrapped }))
4961
5170
  .filter(l => l.t)
4962
5171
  .map(l => `${String(l.i).padStart(3)} w=${l.w ? 1 : 0} len=${l.t.length} ${JSON.stringify(l.t)}`);
4963
- (0,_diag__WEBPACK_IMPORTED_MODULE_17__.appendScreenLog)([head, ...body, ''].join('\n'));
5172
+ (0,_diag__WEBPACK_IMPORTED_MODULE_20__.appendScreenLog)([head, ...body, ''].join('\n'));
4964
5173
  }
4965
5174
  // ---------- 세션 감시 ----------
4966
5175
  /**
@@ -5119,7 +5328,7 @@ let AgentDeckService = class AgentDeckService {
5119
5328
  }
5120
5329
  catch (e) {
5121
5330
  // 세션이 막 닫혔다 — 흔한 사유지만 그 밖의 실패도 여기로 오므로 남긴다
5122
- ;(0,_diag__WEBPACK_IMPORTED_MODULE_17__.diagCatch)('announce-size resize', e);
5331
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagCatch)('announce-size resize', e);
5123
5332
  }
5124
5333
  }, delay);
5125
5334
  }
@@ -5267,7 +5476,7 @@ let AgentDeckService = class AgentDeckService {
5267
5476
  if (!inList && this.isDeckUiKey(event.target)) {
5268
5477
  return;
5269
5478
  }
5270
- const tab = (0,_nav__WEBPACK_IMPORTED_MODULE_14__.pickCloseTarget)({
5479
+ const tab = (0,_nav__WEBPACK_IMPORTED_MODULE_15__.pickCloseTarget)({
5271
5480
  inList,
5272
5481
  focus: this.navFocus,
5273
5482
  active: (_a = this.app.activeTab) !== null && _a !== void 0 ? _a : null,
@@ -6688,7 +6897,7 @@ let AgentDeckService = class AgentDeckService {
6688
6897
  }
6689
6898
  catch (e) {
6690
6899
  // 세션이 막 닫혔다 — 다음 refit 때 다시 잡힌다
6691
- ;(0,_diag__WEBPACK_IMPORTED_MODULE_17__.diagCatch)('pty resize', e);
6900
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagCatch)('pty resize', e);
6692
6901
  return;
6693
6902
  }
6694
6903
  // **보냈다고 도착한 것이 아니다.**
@@ -6716,7 +6925,7 @@ let AgentDeckService = class AgentDeckService {
6716
6925
  }
6717
6926
  catch (e) {
6718
6927
  // 세션이 닫혔다
6719
- ;(0,_diag__WEBPACK_IMPORTED_MODULE_17__.diagCatch)('pty resize retry', e);
6928
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagCatch)('pty resize retry', e);
6720
6929
  }
6721
6930
  }, delay);
6722
6931
  }
@@ -6994,7 +7203,7 @@ let AgentDeckService = class AgentDeckService {
6994
7203
  */
6995
7204
  migrateFollowConfig() {
6996
7205
  const cfg = this.config.store.agentDeck;
6997
- const moved = (0,_viewer__WEBPACK_IMPORTED_MODULE_16__.migrateFollow)(cfg.viewerFollowMode, cfg.viewerFollow);
7206
+ const moved = (0,_viewer__WEBPACK_IMPORTED_MODULE_19__.migrateFollow)(cfg.viewerFollowMode, cfg.viewerFollow);
6998
7207
  if (!moved) {
6999
7208
  return;
7000
7209
  }
@@ -7018,7 +7227,7 @@ let AgentDeckService = class AgentDeckService {
7018
7227
  return;
7019
7228
  }
7020
7229
  const cfg = () => this.config.store.agentDeck;
7021
- this.view = new _viewPanel__WEBPACK_IMPORTED_MODULE_15__.ViewPanel({
7230
+ this.view = new _viewPanel__WEBPACK_IMPORTED_MODULE_18__.ViewPanel({
7022
7231
  windowEl: this.windowEl,
7023
7232
  getSide: () => this.viewSide,
7024
7233
  getWidth: () => cfg().viewerWidth || 420,
@@ -7028,7 +7237,7 @@ let AgentDeckService = class AgentDeckService {
7028
7237
  getRecentMax: () => cfg().viewerRecentMax || 12,
7029
7238
  // 토글 두 개(`viewerPreload`/`viewerAutoOpen`)를 모드로 접는다.
7030
7239
  // 옛 목록값은 기동 때 `migrateFollowConfig` 가 이 둘로 옮기고 비운다
7031
- getFollowMode: () => (0,_viewer__WEBPACK_IMPORTED_MODULE_16__.followModeOf)(cfg().viewerPreload, cfg().viewerAutoOpen),
7240
+ getFollowMode: () => (0,_viewer__WEBPACK_IMPORTED_MODULE_19__.followModeOf)(cfg().viewerPreload, cfg().viewerAutoOpen),
7032
7241
  // 상대경로는 그 탭의 cwd 로 푼다. 아직 못 물어봤으면 작업 루트라도 써 본다
7033
7242
  cwdFor: tab => { var _a; return ((_a = this.cwdCache.get(tab)) === null || _a === void 0 ? void 0 : _a.dir) || cfg().rootProfileCwd || null; },
7034
7243
  commit: () => this.zone.run(() => this.config.save()),
@@ -7216,6 +7425,7 @@ let AgentDeckService = class AgentDeckService {
7216
7425
  }
7217
7426
  }
7218
7427
  }
7428
+ this.syncLedger();
7219
7429
  // 서브에이전트 개수는 `notify.service` 의 2초 스캔이 갱신하는데 렌더를 부르는 경로가
7220
7430
  // 없다 — 경과시간을 끈 사람에게도 숫자가 흐르게 여기서 같이 그린다. 비용은 이미
7221
7431
  // 기본값(`showElapsed: true`)으로 매초 그리던 것과 같다
@@ -7223,6 +7433,39 @@ let AgentDeckService = class AgentDeckService {
7223
7433
  this.render();
7224
7434
  }
7225
7435
  }
7436
+ /**
7437
+ * 살아 있는 세션의 라벨·cwd·상태를 원장에 적는다 (매초).
7438
+ *
7439
+ * **여기가 적는 유일한 자리다.** 세 조각이 각각 다른 곳에 있기 때문이다 —
7440
+ * 세션↔탭은 `notify`, 라벨·상태는 `status.service`, cwd 는 이 서비스의 `cwdCache`.
7441
+ * 한 곳에서 모아 적지 않으면 셋이 어긋난 원장이 남는다.
7442
+ *
7443
+ * 매초 파일을 쓰지는 않는다 — `note` 가 값이 그대로면 저장을 예약하지 않고, 예약도 2초로 모은다.
7444
+ */
7445
+ syncLedger() {
7446
+ var _a, _b;
7447
+ if (this.config.store.agentDeck.resumeList === false) {
7448
+ return;
7449
+ }
7450
+ let live;
7451
+ try {
7452
+ live = this.notify.liveSessions();
7453
+ }
7454
+ catch {
7455
+ return;
7456
+ }
7457
+ for (const [sid, tab] of live) {
7458
+ if (!this.app.tabs.includes(tab)) {
7459
+ continue;
7460
+ }
7461
+ const st = this.status.get(tab);
7462
+ this.ledger.note(sid, {
7463
+ cwd: (_b = (_a = this.cwdCache.get(tab)) === null || _a === void 0 ? void 0 : _a.dir) !== null && _b !== void 0 ? _b : null,
7464
+ label: st.label,
7465
+ status: st.status,
7466
+ });
7467
+ }
7468
+ }
7226
7469
  /**
7227
7470
  * 사이드바를 **무엇을 어떤 순서로** 그릴지 한 번에 계산한다 (그리지는 않는다).
7228
7471
  *
@@ -7250,7 +7493,7 @@ let AgentDeckService = class AgentDeckService {
7250
7493
  : tabs;
7251
7494
  const groups = this.groupsFor(visible);
7252
7495
  // 그룹이 하나뿐이면 헤더를 그리지 않는다 — 단일 프로젝트에서는 아무것도 가르지 못하면서
7253
- // 세로 공간만 먹는다. `groupByProject` 를 끈 경우도 여기로 떨어진다(그룹이 1개니까).
7496
+ // 세로 공간만 먹는다 — 그룹을 항상 계산해도 한 프로젝트만 쓰는 화면이 예전과 같은 이유다.
7254
7497
  //
7255
7498
  // **세는 것은 "작업 폴더를 아는 그룹" 뿐이다.** cwd 를 모르는 탭(Welcome 탭, 조회 실패)은
7256
7499
  // `기타` 그룹으로 모이는데, 그것까지 세면 Welcome 탭 하나 때문에 항상 2그룹이 되어
@@ -7318,7 +7561,174 @@ let AgentDeckService = class AgentDeckService {
7318
7561
  for (const tab of group.tabs) {
7319
7562
  this.listEl.appendChild(this.renderTab(tab));
7320
7563
  }
7564
+ this.renderResume(group, filtering);
7565
+ }
7566
+ }
7567
+ /**
7568
+ * 그룹 끝에 **지난 세션** 줄을 붙인다 — `⟲ 지난 세션 5` 한 줄(기본 접힘)과, 펴면 그 목록.
7569
+ *
7570
+ * 살아 있는 탭 뒤에 오는 이유: 사이드바의 주인공은 지금 도는 작업이고 이건 그 밑의 서랍이다.
7571
+ * 검색·상태필터가 걸려 있을 때는 아예 그리지 않는다 — 그 둘은 **탭**을 좁히는 장치라
7572
+ * (`tabMatchesFilter`) 죽은 세션에는 적용되지 않는데, 걸러진 화면에 안 걸러진 줄이 섞이면
7573
+ * "검색 결과" 라는 말이 깨진다.
7574
+ */
7575
+ renderResume(group, filtering) {
7576
+ const cfg = this.config.store.agentDeck;
7577
+ if (cfg.resumeList === false || !this.listEl || filtering) {
7578
+ return;
7579
+ }
7580
+ // 목록은 캐시에서 즉시 나온다. 스캔은 비동기로 돌고 끝나면 `onChange` 가 다시 그린다
7581
+ void this.ledger.refresh();
7582
+ const rows = (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_17__.resumeRowsFor)(this.ledger.records(), {
7583
+ groupKeys: this.resumeKeysOf(group),
7584
+ keyOf: cwd => (0,_group__WEBPACK_IMPORTED_MODULE_13__.groupKeyOf)(cwd, _project_root__WEBPACK_IMPORTED_MODULE_14__.projectRootOf),
7585
+ fold: _group__WEBPACK_IMPORTED_MODULE_13__.foldGroupKey,
7586
+ live: this.liveSessionIds(),
7587
+ days: Number(cfg.resumeListDays) || 0,
7588
+ limit: Number(cfg.resumeListLimit) || 0,
7589
+ hidden: new Set(Array.isArray(cfg.resumeHidden) ? cfg.resumeHidden : []),
7590
+ now: Date.now(),
7591
+ });
7592
+ if (!rows.length) {
7593
+ // 지난 세션이 없는 그룹에는 줄 자체를 그리지 않는다 — Claude Code 가 아닌 탭
7594
+ // (Codex·Gemini)만 있는 그룹에서는 이 서랍이 늘 비어 있는데, 빈 서랍이 매번
7595
+ // 보이면 사이드바에 읽을 것 없는 줄이 하나 더 생길 뿐이다
7596
+ return;
7597
+ }
7598
+ const expanded = this.isResumeExpanded(group.key);
7599
+ this.listEl.appendChild(this.renderResumeHead(group, rows.length, expanded));
7600
+ if (!expanded) {
7601
+ return;
7602
+ }
7603
+ for (const row of rows) {
7604
+ this.listEl.appendChild(this.renderResumeRow(row));
7605
+ }
7606
+ }
7607
+ /**
7608
+ * 이 줄 묶음이 덮는 그룹 키들.
7609
+ *
7610
+ * 헤더를 그리는 화면에서는 `group.key` 하나지만, **그룹이 하나뿐이면 사이드바는 헤더를
7611
+ * 안 그리고 목록을 평면 한 덩이로 만든다**(`renderPlan`) — 그때 `group.key` 는 `null`(기타)이
7612
+ * 되어 어떤 세션도 안 걸린다. 프로젝트를 하나만 열어 둔 화면이 가장 흔하므로 그대로 두면
7613
+ * 기능이 평소에 안 보인다. 그래서 **그 덩이에 실제로 들어 있는 탭들의 키**를 모아 쓴다.
7614
+ */
7615
+ resumeKeysOf(group) {
7616
+ var _a, _b;
7617
+ if (group.key !== null) {
7618
+ return [group.key];
7619
+ }
7620
+ const keys = new Set();
7621
+ for (const tab of group.tabs) {
7622
+ keys.add((0,_group__WEBPACK_IMPORTED_MODULE_13__.groupKeyOf)((_b = (_a = this.cwdCache.get(tab)) === null || _a === void 0 ? void 0 : _a.dir) !== null && _b !== void 0 ? _b : null, _project_root__WEBPACK_IMPORTED_MODULE_14__.projectRootOf));
7623
+ }
7624
+ // 탭이 하나도 없으면(있을 수 없지만) 빈 목록 — `null` 을 넣으면 cwd 를 모르는 세션이 딸려 온다
7625
+ return [...keys];
7626
+ }
7627
+ /** 지금 탭이 들고 있는 세션 — `세션id -> 탭 식별용 문자열`. 되돌아올 탭은 `resumeTabs` 가 들고 있다 */
7628
+ liveSessionIds() {
7629
+ this.resumeTabs.clear();
7630
+ const out = new Map();
7631
+ let live;
7632
+ try {
7633
+ live = this.notify.liveSessions();
7634
+ }
7635
+ catch {
7636
+ return out;
7637
+ }
7638
+ for (const [sid, tab] of live) {
7639
+ if (!this.app.tabs.includes(tab)) {
7640
+ continue;
7641
+ }
7642
+ out.set(sid, sid);
7643
+ this.resumeTabs.set(sid, tab);
7644
+ }
7645
+ return out;
7646
+ }
7647
+ /** 지난 세션 서랍이 펴져 있나. `collapsedGroups` 와 **반대로 펼침을 적는다** — 기본이 접힘이라서 */
7648
+ isResumeExpanded(key) {
7649
+ const stored = this.config.store.agentDeck.resumeExpanded;
7650
+ if (!Array.isArray(stored)) {
7651
+ return false;
7652
+ }
7653
+ const id = (0,_group__WEBPACK_IMPORTED_MODULE_13__.collapseIdOf)(key);
7654
+ return stored.some((k) => typeof k === 'string' && (0,_group__WEBPACK_IMPORTED_MODULE_13__.foldGroupKey)(k) === (0,_group__WEBPACK_IMPORTED_MODULE_13__.foldGroupKey)(id));
7655
+ }
7656
+ toggleResumeExpanded(key) {
7657
+ const cfg = this.config.store.agentDeck;
7658
+ const id = (0,_group__WEBPACK_IMPORTED_MODULE_13__.collapseIdOf)(key);
7659
+ const stored = Array.isArray(cfg.resumeExpanded)
7660
+ ? cfg.resumeExpanded.filter((k) => typeof k === 'string')
7661
+ : [];
7662
+ const kept = stored.filter(k => (0,_group__WEBPACK_IMPORTED_MODULE_13__.foldGroupKey)(k) !== (0,_group__WEBPACK_IMPORTED_MODULE_13__.foldGroupKey)(id));
7663
+ cfg.resumeExpanded = kept.length === stored.length ? [...stored, id] : kept;
7664
+ this.config.save();
7665
+ this.render();
7666
+ }
7667
+ renderResumeHead(group, count, expanded) {
7668
+ const head = document.createElement('div');
7669
+ head.className = 'ad-resume-head' + (expanded ? '' : ' collapsed');
7670
+ head.innerHTML = [
7671
+ '<span class="ad-resume-glyph">⟲</span>',
7672
+ '<span class="ad-group-label"></span>',
7673
+ '<span class="ad-group-count"></span>',
7674
+ '<span class="ad-group-caret"></span>',
7675
+ ].join('');
7676
+ head.querySelector('.ad-group-label').textContent = '지난 세션';
7677
+ head.querySelector('.ad-group-count').textContent = String(count);
7678
+ head.querySelector('.ad-group-caret').textContent = expanded ? '▾' : '▸';
7679
+ head.title = '닫힌 Claude Code 세션 — 누르면 그 대화를 이어받는다';
7680
+ head.addEventListener('click', e => {
7681
+ e.preventDefault();
7682
+ e.stopPropagation();
7683
+ this.toggleResumeExpanded(group.key);
7684
+ });
7685
+ return head;
7686
+ }
7687
+ renderResumeRow(row) {
7688
+ const el = document.createElement('div');
7689
+ el.className = 'ad-resume' + (row.openTabId ? ' open' : '');
7690
+ el.innerHTML = [
7691
+ '<div class="ad-bar"></div>',
7692
+ '<div class="ad-body">',
7693
+ ' <div class="ad-title"></div>',
7694
+ ' <div class="ad-meta">',
7695
+ ' <span class="re-was"></span>',
7696
+ ' <span class="re-when"></span>',
7697
+ ' </div>',
7698
+ '</div>',
7699
+ ].join('\n');
7700
+ // 라벨이 비면 세션 id 앞자리로 대신한다 — 줄을 지우면 "이어받을 게 있는데 안 보인다" 가 된다
7701
+ const title = row.label || `세션 ${row.sessionId.slice(0, 8)}`;
7702
+ el.querySelector('.ad-title').textContent = title;
7703
+ const was = el.querySelector('.re-was');
7704
+ if (row.openTabId) {
7705
+ was.textContent = '● 열림';
7706
+ was.className = 're-was live';
7321
7707
  }
7708
+ else {
7709
+ const style = _api__WEBPACK_IMPORTED_MODULE_7__.STATUS_STYLES[row.lastStatus];
7710
+ // 끝난 상태는 **흔적으로만** 남긴다 — 살아 있는 배지와 같은 세기로 칠하면
7711
+ // 죽은 줄이 도는 줄처럼 읽힌다. 색 대신 형태(점선 바 + 흐림)가 죽음을 말한다
7712
+ was.textContent = style ? style.label : '';
7713
+ was.className = 're-was' + (row.lastStatus ? ' s-' + row.lastStatus : '');
7714
+ }
7715
+ el.querySelector('.re-when').textContent = (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_17__.formatWhen)(row.lastSeen, Date.now());
7716
+ el.title = row.openTabId
7717
+ ? `${title}\n이미 열려 있다 — 누르면 그 탭으로 이동`
7718
+ : `${title}\n누르면 새 탭에서 이어받는다 (${row.sessionId})`
7719
+ + '\n작업 폴더: ' + (row.cwd || '모름')
7720
+ + '\n※ 대화만 돌아온다 — 그때 띄워 둔 서버·백그라운드 프로세스는 되살아나지 않는다';
7721
+ el.addEventListener('click', e => {
7722
+ e.preventDefault();
7723
+ e.stopPropagation();
7724
+ this.zone.run(() => { void this.activateResume(row, false); });
7725
+ });
7726
+ el.addEventListener('contextmenu', e => {
7727
+ e.preventDefault();
7728
+ e.stopPropagation();
7729
+ this.zone.run(() => this.showResumeMenu(row, e));
7730
+ });
7731
+ return el;
7322
7732
  }
7323
7733
  /**
7324
7734
  * 필터에 걸린 것이 하나도 없을 때의 한 줄.
@@ -7349,19 +7759,16 @@ let AgentDeckService = class AgentDeckService {
7349
7759
  * cwd 는 **이미 채워져 있는 `cwdCache`** 만 본다 — 여기서 `getWorkingDirectory()` 를
7350
7760
  * 새로 부르면 렌더(초당 여러 번, `scheduleRender`)마다 OS 호출이 탭 수만큼 늘어난다.
7351
7761
  * 아직 캐시가 빈 탭은 `기타` 에 있다가, 출력이 흐를 때 `touchCwd` 가 채우면 제 그룹으로 옮겨온다.
7762
+ * 프로젝트 루트(`projectRootOf`)도 같은 성질이다 — 처음 보는 폴더는 그 순간엔 cwd 자체가
7763
+ * 키였다가, 탐지가 끝나면 저장소 루트로 합쳐진다.
7352
7764
  *
7353
7765
  * 비용은 탭 수만큼의 맵 조작 + 그룹 수²의 라벨 대조뿐이라(group.ts `groupTabs` 주석)
7354
7766
  * 캐시를 두지 않는다 — 캐시 무효화용 시그니처를 만드는 비용이 계산 비용과 같기 때문이다.
7355
7767
  */
7356
7768
  groupsFor(tabs) {
7357
- const cfg = this.config.store.agentDeck;
7358
- if (!cfg.groupByProject) {
7359
- // 한 덩어리로 돌려주면 아래에서 헤더 없이 예전과 똑같은 평면 목록이 된다
7360
- return [{ key: null, label: _group__WEBPACK_IMPORTED_MODULE_13__.UNGROUPED_LABEL, tabs: [...tabs] }];
7361
- }
7362
- // 프로젝트 경계 기준은 설정에서 받는다 — 저장소 루트를 찾으려면 디스크를 훑어야 해서
7363
- const root = cfg.groupRoot || cfg.rootProfileCwd || null;
7364
- return (0,_group__WEBPACK_IMPORTED_MODULE_13__.groupTabs)(tabs, tab => { var _a, _b; return (_b = (_a = this.cwdCache.get(tab)) === null || _a === void 0 ? void 0 : _a.dir) !== null && _b !== void 0 ? _b : null; }, { root });
7769
+ // 프로젝트 경계는 **자동으로** 찾는다 (`project-root.ts`) — 조회는 캐시라 렌더를 막지
7770
+ // 않고, 처음 보는 폴더는 백그라운드 탐지가 끝나면 다시 그린다.
7771
+ return (0,_group__WEBPACK_IMPORTED_MODULE_13__.groupTabs)(tabs, tab => { var _a, _b; return (_b = (_a = this.cwdCache.get(tab)) === null || _a === void 0 ? void 0 : _a.dir) !== null && _b !== void 0 ? _b : null; }, cwd => (0,_project_root__WEBPACK_IMPORTED_MODULE_14__.projectRootOf)(cwd, () => this.scheduleRender()));
7365
7772
  }
7366
7773
  /** 설정에 남아 있는 접힌 그룹 키들 — 저장·비교는 항상 폴딩한 값으로 (group.ts `collapseIdOf`) */
7367
7774
  collapsedKeys() {
@@ -7638,7 +8045,7 @@ let AgentDeckService = class AgentDeckService {
7638
8045
  */
7639
8046
  navPlan() {
7640
8047
  const { withHeads, plan, collapseOf } = this.renderPlan();
7641
- const rows = (0,_nav__WEBPACK_IMPORTED_MODULE_14__.navRowsOf)(plan.map(group => ({
8048
+ const rows = (0,_nav__WEBPACK_IMPORTED_MODULE_15__.navRowsOf)(plan.map(group => ({
7642
8049
  key: group.key,
7643
8050
  tabs: group.tabs,
7644
8051
  head: withHeads,
@@ -7792,7 +8199,7 @@ let AgentDeckService = class AgentDeckService {
7792
8199
  ? 0
7793
8200
  : to === 'last'
7794
8201
  ? rows.length - 1
7795
- : (0,_nav__WEBPACK_IMPORTED_MODULE_14__.stepNavIndex)(rows.length, index, step, wrap);
8202
+ : (0,_nav__WEBPACK_IMPORTED_MODULE_15__.stepNavIndex)(rows.length, index, step, wrap);
7796
8203
  if (next < 0) {
7797
8204
  return;
7798
8205
  }
@@ -8502,26 +8909,160 @@ let AgentDeckService = class AgentDeckService {
8502
8909
  });
8503
8910
  input.addEventListener('blur', () => finish(true));
8504
8911
  }
8505
- /** 우클릭 -> 상태 직접 지정 */
8506
- showStatusMenu(ev, tab) {
8912
+ /**
8913
+ * 지난 세션 한 줄을 누른 결과 — **이미 열려 있으면 이어받지 않고 그 탭으로 보낸다.**
8914
+ *
8915
+ * 같은 세션을 두 탭에서 `--resume` 하면 두 프로세스가 같은 기록 파일에 덧쓴다.
8916
+ * 세션↔탭 매핑은 `notify` 가 이미 들고 있어 판정에 드는 비용이 없으니 막는다.
8917
+ */
8918
+ async activateResume(row, fork) {
8919
+ if (row.openTabId) {
8920
+ const tab = this.resumeTabs.get(row.openTabId);
8921
+ if (tab && this.app.tabs.includes(tab)) {
8922
+ this.app.selectTab(tab);
8923
+ return;
8924
+ }
8925
+ // 그 사이 닫혔다 — 이어받기로 흘려보낸다
8926
+ }
8927
+ const command = (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_17__.resumeCommand)(row.sessionId, fork);
8928
+ if (!command) {
8929
+ this.diag(`resume 거부 sid=${row.sessionId}`);
8930
+ return;
8931
+ }
8932
+ await this.openResumeTab(row, command);
8933
+ }
8934
+ /**
8935
+ * 이어받기용 새 탭.
8936
+ *
8937
+ * **프로필의 `command` 를 갈아끼우지 않고, 평소 프로필로 셸을 띄운 뒤 명령을 써 넣는다.**
8938
+ * 이유는 `sessionLedger.ts` 의 `resumeCommand` 주석에 있다 — 기존 `agentdeck:root` 프로필의
8939
+ * 인자(`-ExecutionPolicy Bypass`)가 npm 전역 `.ps1` 래퍼 문제를 이미 풀어 두었고,
8940
+ * claude 가 끝나도 셸이 남아 사람이 이어서 쓸 수 있다.
8941
+ *
8942
+ * **cwd 는 그 세션의 것으로 덮는다.** `--resume` 은 cwd 로 세션을 묶기 때문에(같은 세션도
8943
+ * 다른 폴더에서 띄우면 목록에 없다) 여기를 틀리면 이어받기 자체가 실패한다.
8944
+ */
8945
+ async openResumeTab(row, command) {
8946
+ var _a, _b, _c, _d;
8947
+ try {
8948
+ const wanted = this.config.store.terminal.profile;
8949
+ const all = await this.profiles.getProfiles();
8950
+ const base = (_a = all.find(p => p.id === wanted)) !== null && _a !== void 0 ? _a : all[0];
8951
+ if (!base) {
8952
+ this.diag('resume 프로필 없음');
8953
+ return;
8954
+ }
8955
+ // 원본 프로필을 건드리지 않는다 — 설정에 저장되는 객체라 cwd 를 박으면 그 뒤
8956
+ // 새 탭이 전부 그 폴더에서 열린다
8957
+ const profile = {
8958
+ ...base,
8959
+ options: { ...base.options, cwd: row.cwd || ((_b = base.options) === null || _b === void 0 ? void 0 : _b.cwd) },
8960
+ };
8961
+ const tab = await this.profiles.openNewTabForProfile(profile);
8962
+ this.diag(`resume 탭 sid=${row.sessionId.slice(0, 8)} cwd=${(_d = (_c = profile.options) === null || _c === void 0 ? void 0 : _c.cwd) !== null && _d !== void 0 ? _d : '?'} tab=${tab ? 'yes' : 'null'}`);
8963
+ if (!tab) {
8964
+ return;
8965
+ }
8966
+ // 라벨을 미리 물려준다 — 셸이 뜨고 claude 가 붙기까지 몇 초 동안 줄이 `—` 로 비어
8967
+ // 있으면 방금 무엇을 눌렀는지 사라진다. 사람이 Enter 를 치면 그때 덮인다
8968
+ if (row.label) {
8969
+ this.status.setLabel(tab, row.label);
8970
+ }
8971
+ this.sendResumeCommand(tab, command);
8972
+ }
8973
+ catch (e) {
8974
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagCatch)('resume 탭 열기', e);
8975
+ }
8976
+ }
8977
+ /**
8978
+ * 갓 뜬 셸에 명령을 써 넣는다.
8979
+ *
8980
+ * **바로 쏘면 삼켜진다** — pty 는 열렸는데 셸이 아직 프롬프트를 안 그린 상태에서 쓴 바이트는
8981
+ * 어디에도 안 남는다(같은 이유로 `sendInput` 을 쓰는 다른 경로들도 프론트엔드 준비를 기다린다).
8982
+ * 그래서 출력이 한 번이라도 흐른 뒤에 보내고, 끝내 안 오면 정해진 시각에 한 번은 보낸다 —
8983
+ * 아무것도 안 보내고 조용히 끝나는 것이 제일 나쁜 결과다.
8984
+ */
8985
+ sendResumeCommand(tab, command) {
8986
+ /** 첫 출력(=셸이 프롬프트를 그렸다)을 보고 나서 더 기다리는 시간 */
8987
+ const RESUME_SETTLE_MS = 700;
8988
+ /** 출력이 끝내 안 와도 여기서는 한 번 쏜다 — 아무것도 안 보내고 조용히 끝나는 게 제일 나쁘다 */
8989
+ const RESUME_GIVEUP_MS = 6000;
8990
+ /** 세션이 붙기를 기다리는 간격 */
8991
+ const RESUME_POLL_MS = 200;
8992
+ let sent = false;
8993
+ let sub = null;
8994
+ const fire = () => {
8995
+ var _a;
8996
+ if (sent) {
8997
+ return;
8998
+ }
8999
+ const anyPane = this.firstPane(tab);
9000
+ if (!anyPane || typeof anyPane.sendInput !== 'function') {
9001
+ return;
9002
+ }
9003
+ sent = true;
9004
+ (_a = sub === null || sub === void 0 ? void 0 : sub.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(sub);
9005
+ this.sendToPane(anyPane, 'resume', () => anyPane.sendInput(command + '\r'));
9006
+ this.diag('resume 명령 전송');
9007
+ };
9008
+ /**
9009
+ * **세션은 탭이 만들어진 직후엔 아직 없다** (2026-09-11 실측: `openNewTabForProfile` 이
9010
+ * 돌아온 시점에 `pane.session` 이 `undefined`). 그때 한 번만 보고 포기하면 구독이 영영
9011
+ * 안 걸려 매번 6초를 꽉 채우고서야 명령이 나간다. 그래서 붙을 때까지 짧게 되물어본다.
9012
+ */
9013
+ const t0 = Date.now();
9014
+ const arm = () => {
9015
+ var _a, _b, _c;
9016
+ if (sent || sub) {
9017
+ return;
9018
+ }
9019
+ let pane = null;
9020
+ try {
9021
+ pane = this.firstPane(tab);
9022
+ sub = (_c = (_b = (_a = pane === null || pane === void 0 ? void 0 : pane.session) === null || _a === void 0 ? void 0 : _a.output$) === null || _b === void 0 ? void 0 : _b.subscribe) === null || _c === void 0 ? void 0 : _c.call(_b, () => setTimeout(fire, RESUME_SETTLE_MS));
9023
+ }
9024
+ catch (e) {
9025
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagCatch)('resume 출력 구독', e);
9026
+ return;
9027
+ }
9028
+ if (sub) {
9029
+ this.diag(`resume 구독 armed after=${Date.now() - t0}ms`);
9030
+ return;
9031
+ }
9032
+ if (Date.now() - t0 < RESUME_GIVEUP_MS) {
9033
+ setTimeout(arm, RESUME_POLL_MS);
9034
+ }
9035
+ };
9036
+ arm();
9037
+ setTimeout(fire, RESUME_GIVEUP_MS);
9038
+ }
9039
+ /** 지난 세션 줄 우클릭 */
9040
+ showResumeMenu(row, ev) {
8507
9041
  const old = document.querySelector('.ad-menu');
8508
9042
  if (old) {
8509
9043
  old.remove();
8510
9044
  }
8511
9045
  const menu = document.createElement('div');
8512
9046
  menu.className = 'ad-menu';
8513
- const statuses = ['running', 'waiting', 'limited', 'done', 'error', 'idle'];
8514
- const items = statuses.map(s => {
8515
- const label = _api__WEBPACK_IMPORTED_MODULE_7__.STATUS_STYLES[s].icon + ' ' + _api__WEBPACK_IMPORTED_MODULE_7__.STATUS_STYLES[s].label;
8516
- const action = () => this.status.setManual(tab, s);
8517
- return [label, action];
8518
- });
8519
- items.push(['↺ 자동 감지로', () => this.status.unpin(tab)]);
8520
- // 이 탭만 다시 그리기 — 사이드바 아래 ↻ 는 모든 탭이 대상이라 무거울 때가 있다
8521
- items.push(['↻ 화면 복구', () => {
8522
- this.app.selectTab(tab);
8523
- this.repair('active');
9047
+ const items = [];
9048
+ if (row.openTabId) {
9049
+ items.push(['이 탭으로 이동', () => { void this.activateResume(row, false); }]);
9050
+ }
9051
+ else {
9052
+ items.push(['새 탭에서 이어받기', () => { void this.activateResume(row, false); }]);
9053
+ // 분기는 기록을 복제한다 — 기본 동작으로 두면 목록이 비슷한 이름으로 불어난다
9054
+ items.push(['분기해서 이어받기', () => { void this.activateResume(row, true); }]);
9055
+ }
9056
+ items.push(['세션 ID 복사', () => {
9057
+ var _a, _b;
9058
+ try {
9059
+ (_b = (_a = this.platform).setClipboard) === null || _b === void 0 ? void 0 : _b.call(_a, { text: row.sessionId });
9060
+ }
9061
+ catch (e) {
9062
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_20__.diagCatch)('세션 ID 복사', e);
9063
+ }
8524
9064
  }]);
9065
+ items.push(['목록에서 숨기기', () => this.hideResumeRow(row.sessionId)]);
8525
9066
  for (const [label, action] of items) {
8526
9067
  const item = document.createElement('div');
8527
9068
  item.className = 'ad-menu-item';
@@ -8543,10 +9084,63 @@ let AgentDeckService = class AgentDeckService {
8543
9084
  };
8544
9085
  setTimeout(() => document.addEventListener('mousedown', close), 0);
8545
9086
  }
8546
- formatElapsed(ms) {
8547
- const s = Math.floor(ms / 1000);
8548
- if (s < 60) {
8549
- return s + '초';
9087
+ /** 목록에서만 감춘다 — 기록 파일은 건드리지 않는다(그건 사람이 지울 일이다) */
9088
+ hideResumeRow(sessionId) {
9089
+ const cfg = this.config.store.agentDeck;
9090
+ const stored = Array.isArray(cfg.resumeHidden)
9091
+ ? cfg.resumeHidden.filter((k) => typeof k === 'string')
9092
+ : [];
9093
+ if (!stored.includes(sessionId)) {
9094
+ cfg.resumeHidden = [...stored, sessionId];
9095
+ this.config.save();
9096
+ }
9097
+ this.render();
9098
+ }
9099
+ /** 우클릭 -> 상태 직접 지정 */
9100
+ showStatusMenu(ev, tab) {
9101
+ const old = document.querySelector('.ad-menu');
9102
+ if (old) {
9103
+ old.remove();
9104
+ }
9105
+ const menu = document.createElement('div');
9106
+ menu.className = 'ad-menu';
9107
+ const statuses = ['running', 'waiting', 'limited', 'done', 'error', 'idle'];
9108
+ const items = statuses.map(s => {
9109
+ const label = _api__WEBPACK_IMPORTED_MODULE_7__.STATUS_STYLES[s].icon + ' ' + _api__WEBPACK_IMPORTED_MODULE_7__.STATUS_STYLES[s].label;
9110
+ const action = () => this.status.setManual(tab, s);
9111
+ return [label, action];
9112
+ });
9113
+ items.push(['↺ 자동 감지로', () => this.status.unpin(tab)]);
9114
+ // 이 탭만 다시 그리기 — 사이드바 아래 ↻ 는 모든 탭이 대상이라 무거울 때가 있다
9115
+ items.push(['↻ 화면 복구', () => {
9116
+ this.app.selectTab(tab);
9117
+ this.repair('active');
9118
+ }]);
9119
+ for (const [label, action] of items) {
9120
+ const item = document.createElement('div');
9121
+ item.className = 'ad-menu-item';
9122
+ item.textContent = label;
9123
+ item.addEventListener('click', () => {
9124
+ action();
9125
+ menu.remove();
9126
+ });
9127
+ menu.appendChild(item);
9128
+ }
9129
+ document.body.appendChild(menu);
9130
+ menu.style.left = ev.clientX + 'px';
9131
+ menu.style.top = Math.min(ev.clientY, window.innerHeight - menu.offsetHeight - 8) + 'px';
9132
+ const close = (e) => {
9133
+ if (!menu.contains(e.target)) {
9134
+ menu.remove();
9135
+ document.removeEventListener('mousedown', close);
9136
+ }
9137
+ };
9138
+ setTimeout(() => document.addEventListener('mousedown', close), 0);
9139
+ }
9140
+ formatElapsed(ms) {
9141
+ const s = Math.floor(ms / 1000);
9142
+ if (s < 60) {
9143
+ return s + '초';
8550
9144
  }
8551
9145
  const m = Math.floor(s / 60);
8552
9146
  if (m < 60) {
@@ -8565,6 +9159,7 @@ AgentDeckService = __decorate([
8565
9159
  tabby_core__WEBPACK_IMPORTED_MODULE_1__.HostWindowService,
8566
9160
  tabby_core__WEBPACK_IMPORTED_MODULE_1__.PlatformService,
8567
9161
  _notify_service__WEBPACK_IMPORTED_MODULE_4__.WorkNotifyService,
9162
+ _sessionLedger_service__WEBPACK_IMPORTED_MODULE_16__.SessionLedgerService,
8568
9163
  _angular_core__WEBPACK_IMPORTED_MODULE_0__.NgZone])
8569
9164
  ], AgentDeckService);
8570
9165
 
@@ -10543,6 +11138,7 @@ __webpack_require__.r(__webpack_exports__);
10543
11138
  /* harmony export */ groupTabs: () => (/* binding */ groupTabs),
10544
11139
  /* harmony export */ isStoredCollapsed: () => (/* binding */ isStoredCollapsed),
10545
11140
  /* harmony export */ normalizeCwd: () => (/* binding */ normalizeCwd),
11141
+ /* harmony export */ parentPath: () => (/* binding */ parentPath),
10546
11142
  /* harmony export */ resolveGroupCollapse: () => (/* binding */ resolveGroupCollapse),
10547
11143
  /* harmony export */ resolveGroupCollapseFor: () => (/* binding */ resolveGroupCollapseFor)
10548
11144
  /* harmony export */ });
@@ -10555,8 +11151,8 @@ __webpack_require__.r(__webpack_exports__);
10555
11151
  * 따라오지 않아 사실상 "탭이 태어난 폴더" 를 준다(docs/DEVELOPMENT.md 0.5.0 실측).
10556
11152
  * 즉 이 값은 세션이 사는 동안 안 바뀌는 = 그룹 키로 쓸 만한 유일한 값이다.
10557
11153
  *
10558
- * 이 모듈은 **git 저장소 루트를 모른다** (fs 접근이 없다). 프로젝트 경계는 호출부가 넘기는
10559
- * `root`(설정 `groupRoot` / `rootProfileCwd`) 로만 정한다 — `groupKeyOf` 주석 참고.
11154
+ * 이 모듈은 **여전히 fs 를 모른다.** 프로젝트 경계(저장소 루트)를 실제로 찾는 것은
11155
+ * `project-root.ts` 이고, 여기는 호출부가 넘긴 `rootOf` 조회 함수를 부를 뿐이다 —
10560
11156
  * 경계를 알아내려고 여기서 `.git` 을 찾기 시작하면 렌더마다 디스크를 때리게 된다
10561
11157
  * (렌더는 초당 여러 번 불린다 — deck.service `scheduleRender`).
10562
11158
  */
@@ -10620,7 +11216,11 @@ function segsOf(rest) {
10620
11216
  }
10621
11217
  function joinPath(s, segs) {
10622
11218
  if (!segs.length) {
10623
- // 루트 자체 — `C:` / `//server/share` 는 그대로, POSIX 루트는 `/`
11219
+ // 루트 자체 — 드라이브는 `C:/` 로 되돌린다(`normalizeCwd` 가 남기는 꼴과 같아야
11220
+ // 같은 폴더가 `C:` 와 `C:/` 두 값으로 갈리지 않는다). UNC 공유 루트는 그대로, POSIX 는 `/`
11221
+ if (/^[A-Za-z]:$/.test(s.prefix)) {
11222
+ return s.prefix + '/';
11223
+ }
10624
11224
  return s.prefix || (s.abs ? '/' : '');
10625
11225
  }
10626
11226
  return s.prefix + (s.abs ? '/' : '') + segs.join('/');
@@ -10635,34 +11235,48 @@ function startsWithSegs(segs, prefix) {
10635
11235
  /**
10636
11236
  * cwd 를 그룹 키로 — 없으면 null(그룹 없음).
10637
11237
  *
10638
- * 깊이 규칙이 이 함수의 핵심이다. cwd 를 그대로 쓰면 `D:\Project\a\sub` 에서 띄운 탭과
10639
- * `D:\Project\a` 에서 띄운 탭이 다른 그룹으로 갈린다(에이전트를 서브폴더에서 띄우는 건 흔하다).
10640
- * 그래서 `root` 를 받으면 그 **직하 한 단계**를 키로 삼는다 — `root = D:/Project` 면
10641
- * `D:/Project/a/sub` 도 `D:/Project/a` 도 키가 `D:/Project/a` 다.
10642
- * `root` 가 없거나 cwd 가 그 밖이면 cwd 자체를 키로 쓴다(그때는 `maxDepth` 로만 줄인다).
11238
+ * cwd 를 그대로 키로 쓰면 `D:\Project\a\sub` 에서 띄운 탭과 `D:\Project\a` 에서 띄운 탭이
11239
+ * 다른 그룹으로 갈린다(에이전트를 서브폴더에서 띄우는 건 흔하다). 그래서 **그 폴더가 속한
11240
+ * 프로젝트 루트**를 키로 삼는다 — 루트는 `rootOf` 가 알려주고(보통 `.git` 이 있는 폴더),
11241
+ * 모르면 cwd 자체가 키다(예전 동작 그대로).
11242
+ *
11243
+ * `rootOf` 가 준 값이 cwd 의 조상이 아니면 **버린다.** 엉뚱한 값 하나가 상관없는 탭들을
11244
+ * 한 그룹으로 빨아들이는 것이 "안 묶임" 보다 훨씬 나쁘기 때문이다.
10643
11245
  */
10644
- function groupKeyOf(cwd, opts) {
10645
- var _a;
11246
+ function groupKeyOf(cwd, rootOf) {
10646
11247
  const p = normalizeCwd(cwd);
10647
11248
  if (!p) {
10648
11249
  return null;
10649
11250
  }
10650
- const root = normalizeCwd(opts === null || opts === void 0 ? void 0 : opts.root);
10651
- const pp = splitPath(p);
10652
- if (root) {
10653
- const rp = splitPath(root);
10654
- if (foldGroupKey(rp.prefix) === foldGroupKey(pp.prefix) && startsWithSegs(pp.segs, rp.segs)) {
10655
- // cwd 가 root 자체면 root 가 키다 (min 이 그걸 처리한다)
10656
- const depth = Math.min(pp.segs.length, rp.segs.length + 1);
10657
- return joinPath(pp, pp.segs.slice(0, depth));
10658
- }
11251
+ const root = rootOf ? normalizeCwd(rootOf(p)) : null;
11252
+ if (!root) {
11253
+ return p;
10659
11254
  }
10660
- const max = (_a = opts === null || opts === void 0 ? void 0 : opts.maxDepth) !== null && _a !== void 0 ? _a : 0;
10661
- if (max > 0 && pp.segs.length > max) {
10662
- return joinPath(pp, pp.segs.slice(0, max));
11255
+ const pp = splitPath(p);
11256
+ const rp = splitPath(root);
11257
+ if (foldGroupKey(rp.prefix) === foldGroupKey(pp.prefix) && startsWithSegs(pp.segs, rp.segs)) {
11258
+ return root;
10663
11259
  }
10664
11260
  return p;
10665
11261
  }
11262
+ /**
11263
+ * 한 단계 위 폴더 — 더 올라갈 데가 없으면 null.
11264
+ *
11265
+ * `project-root.ts` 가 마커를 찾아 올라갈 때 쓴다. 여기 두는 이유는 드라이브(`C:/`)·UNC
11266
+ * (`//server/share`) 경계를 아는 것이 `splitPath` 뿐이라서다 — `path.dirname` 은 UNC 공유
11267
+ * 루트에서 호스트만 남기는 등 경로가 아닌 값을 만든다.
11268
+ */
11269
+ function parentPath(p) {
11270
+ const norm = normalizeCwd(p);
11271
+ if (!norm) {
11272
+ return null;
11273
+ }
11274
+ const s = splitPath(norm);
11275
+ if (!s.segs.length) {
11276
+ return null;
11277
+ }
11278
+ return joinPath(s, s.segs.slice(0, -1));
11279
+ }
10666
11280
  /**
10667
11281
  * 그룹에 붙일 짧은 이름 — 경로 마지막 조각. 다른 그룹과 겹치면 상위를 한 단계씩 더 붙인다.
10668
11282
  *
@@ -10719,12 +11333,12 @@ function compareGroups(a, b) {
10719
11333
  * 반환 길이가 1 이면 호출부는 그룹 헤더를 생략한다 — 단일 프로젝트에서 세로 공간을 낭비하지 않게.
10720
11334
  * 비용은 탭 수만큼의 맵 조작 + 그룹 수²의 라벨 대조뿐이라 렌더마다 불러도 된다.
10721
11335
  */
10722
- function groupTabs(tabs, cwdOf, opts) {
11336
+ function groupTabs(tabs, cwdOf, rootOf) {
10723
11337
  const order = [];
10724
11338
  const byFold = new Map();
10725
11339
  const ungrouped = [];
10726
11340
  for (const tab of tabs) {
10727
- const key = groupKeyOf(cwdOf(tab), opts);
11341
+ const key = groupKeyOf(cwdOf(tab), rootOf);
10728
11342
  if (!key) {
10729
11343
  ungrouped.push(tab);
10730
11344
  continue;
@@ -12303,6 +12917,18 @@ let WorkNotifyService = class WorkNotifyService {
12303
12917
  return `${head}\n가장 오래된 것: ${formatAge(Date.now() - oldest)} 전 시작`
12304
12918
  + ' (종료 알림을 못 받은 것이면 이 값만 계속 늘어난다)';
12305
12919
  }
12920
+ /**
12921
+ * 지금 탭이 들고 있는 세션들 — `세션id -> 탭`.
12922
+ *
12923
+ * 사이드바의 "지난 세션" 목록이 이걸 본다. 살아 있는 세션은 이어받는 대신 그 탭으로 보내야
12924
+ * 하기 때문이다: 같은 세션을 두 탭에서 `--resume` 하면 두 프로세스가 같은 기록 파일에
12925
+ * 덧쓴다. 매핑은 이 서비스가 훅 보고로 이미 만들어 두었으니 판정에 드는 비용이 없다.
12926
+ *
12927
+ * 사본을 돌려준다 — 호출부가 들고 있는 동안 이쪽에서 지워도(`sessionTab.delete`) 순회가 깨지지 않게.
12928
+ */
12929
+ liveSessions() {
12930
+ return new Map(this.sessionTab);
12931
+ }
12306
12932
  /**
12307
12933
  * 탭에 묶인 세션들의 대화기록을 **자란 만큼만** 읽어 개수를 갱신한다 (2초마다).
12308
12934
  *
@@ -12783,6 +13409,181 @@ AgentDeckProfileService = __decorate([
12783
13409
 
12784
13410
 
12785
13411
 
13412
+ /***/ },
13413
+
13414
+ /***/ "./src/project-root.ts"
13415
+ /*!*****************************!*\
13416
+ !*** ./src/project-root.ts ***!
13417
+ \*****************************/
13418
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
13419
+
13420
+ __webpack_require__.r(__webpack_exports__);
13421
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
13422
+ /* harmony export */ PROJECT_MARKERS: () => (/* binding */ PROJECT_MARKERS),
13423
+ /* harmony export */ clearProjectRootCache: () => (/* binding */ clearProjectRootCache),
13424
+ /* harmony export */ findProjectRoot: () => (/* binding */ findProjectRoot),
13425
+ /* harmony export */ homeBoundary: () => (/* binding */ homeBoundary),
13426
+ /* harmony export */ projectRootCacheSnapshot: () => (/* binding */ projectRootCacheSnapshot),
13427
+ /* harmony export */ projectRootOf: () => (/* binding */ projectRootOf),
13428
+ /* harmony export */ walkUp: () => (/* binding */ walkUp)
13429
+ /* harmony export */ });
13430
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! fs */ "fs");
13431
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__);
13432
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! os */ "os");
13433
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_1__);
13434
+ /* harmony import */ var _group__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./group */ "./src/group.ts");
13435
+ /**
13436
+ * cwd 가 속한 **프로젝트 루트**를 찾는다 — 사이드바 그룹 경계의 유일한 출처.
13437
+ *
13438
+ * 예전에는 사람이 설정(`groupRoot`)에 상위 폴더를 적어 두고 "그 직하 한 단계" 를 그룹으로
13439
+ * 삼았다. 그 규칙은 프로젝트들이 한 폴더에 나란히 있을 때만 맞고, `D:/Project/SoloLV/Root` 와
13440
+ * `D:/Project/tabby-agentdeck` 처럼 깊이가 다르면 어떤 값을 적어도 한쪽이 틀린다.
13441
+ * 그래서 설정을 없애고 **저장소 루트를 직접 찾는다** — 사람이 프로젝트라고 부르는 경계는
13442
+ * 사실상 버전관리의 경계이기 때문이다.
13443
+ *
13444
+ * 두 가지를 지킨다 —
13445
+ * 1. **렌더 중에 디스크를 훑지 않는다.** 조회는 캐시를 보고 즉시 돌아오고(`projectRootOf`),
13446
+ * 처음 보는 폴더는 백그라운드로 찾은 뒤 `onResolved` 로 다시 그리게 한다. cwd 는 탭이
13447
+ * 사는 동안 안 바뀌므로(group.ts 주석) 탭당 한 번이면 끝난다.
13448
+ * 2. **판정 규칙은 순수 함수로 둔다**(`walkUp`/`findProjectRoot`) — fs 없이 단위 테스트한다.
13449
+ *
13450
+ * 캐시는 프로세스 수명 동안 유지된다. 세션 도중 `git init` 을 한 폴더는 다음 기동에 잡힌다 —
13451
+ * 그걸 따라가려면 폴더를 계속 다시 물어야 하는데, 얻는 것에 비해 비용이 크다.
13452
+ */
13453
+
13454
+
13455
+
13456
+ /**
13457
+ * 프로젝트 루트임을 알리는 표식. 위로 올라가며 **가장 먼저** 만나는 것이 루트다
13458
+ * (중첩 저장소·서브모듈에서는 가까운 쪽이 그 탭의 프로젝트다).
13459
+ *
13460
+ * `.git` 은 폴더가 아닐 수도 있다 — worktree·서브모듈에서는 파일이다. 그래서 존재만 본다.
13461
+ * `package.json` 류는 넣지 않았다: 모노레포·하위 패키지마다 있어서 프로젝트를 잘게 쪼갠다.
13462
+ */
13463
+ const PROJECT_MARKERS = ['.git', '.hg', '.svn', '.p4config'];
13464
+ /** 위로 올라가는 최대 단계 — 경로가 이상하게 길어도 여기서 멈춘다 */
13465
+ const MAX_UP = 64;
13466
+ /**
13467
+ * **홈 디렉토리는 프로젝트가 아니다 — 거기서 멈춘다.**
13468
+ *
13469
+ * dotfiles 를 git 으로 관리하면 `~/.git` 이 있고(이 PC 가 그렇다), 그러면 홈 아래에서 띄운
13470
+ * 모든 탭 — `%LOCALAPPDATA%` 임시 폴더까지 — 이 `junggon` 이라는 한 그룹으로 빨려 들어간다.
13471
+ * 2026-09-11 실측: 한글 경로 검증용으로 연 탭이 그 폴더 대신 홈 그룹에 들어갔다.
13472
+ * 홈 위(`C:/Users`, `C:/`)도 같은 이유로 볼 필요가 없다.
13473
+ */
13474
+ function homeBoundary() {
13475
+ try {
13476
+ return (0,_group__WEBPACK_IMPORTED_MODULE_2__.normalizeCwd)(os__WEBPACK_IMPORTED_MODULE_1__.homedir());
13477
+ }
13478
+ catch {
13479
+ return null;
13480
+ }
13481
+ }
13482
+ /**
13483
+ * cwd 자신부터 루트까지 — `['D:/a/b', 'D:/a', 'D:/']`.
13484
+ *
13485
+ * `stopAt`(보통 홈)에 닿으면 **그 폴더를 빼고** 멈춘다 — 거기 있는 마커는 프로젝트 경계가 아니다.
13486
+ */
13487
+ function walkUp(cwd, stopAt) {
13488
+ let dir = (0,_group__WEBPACK_IMPORTED_MODULE_2__.normalizeCwd)(cwd);
13489
+ const stop = (0,_group__WEBPACK_IMPORTED_MODULE_2__.normalizeCwd)(stopAt);
13490
+ const out = [];
13491
+ for (let i = 0; dir && i < MAX_UP; i++) {
13492
+ if (stop && (0,_group__WEBPACK_IMPORTED_MODULE_2__.foldGroupKey)(dir) === (0,_group__WEBPACK_IMPORTED_MODULE_2__.foldGroupKey)(stop)) {
13493
+ break;
13494
+ }
13495
+ out.push(dir);
13496
+ const up = (0,_group__WEBPACK_IMPORTED_MODULE_2__.parentPath)(dir);
13497
+ if (!up || (0,_group__WEBPACK_IMPORTED_MODULE_2__.foldGroupKey)(up) === (0,_group__WEBPACK_IMPORTED_MODULE_2__.foldGroupKey)(dir)) {
13498
+ break;
13499
+ }
13500
+ dir = up;
13501
+ }
13502
+ return out;
13503
+ }
13504
+ /**
13505
+ * 표식을 가진 가장 가까운 조상 — 없으면 null(그때는 cwd 자체가 그룹이다).
13506
+ *
13507
+ * `hasMarker` 를 주입받는 이유는 테스트 때문만이 아니다 — 실제 조회는 비동기라
13508
+ * (`detect`) 이 함수는 "어느 순서로 무엇을 묻는가" 만 정하고 묻는 방법은 모른다.
13509
+ */
13510
+ function findProjectRoot(cwd, hasMarker, stopAt) {
13511
+ for (const dir of walkUp(cwd, stopAt)) {
13512
+ if (hasMarker(dir)) {
13513
+ return dir;
13514
+ }
13515
+ }
13516
+ return null;
13517
+ }
13518
+ /** 폴딩한 cwd → 루트(없으면 null). `undefined` 는 "아직 안 찾아봤다" 와 구별하려고 안 넣는다 */
13519
+ const cache = new Map();
13520
+ /** 지금 찾고 있는 폴더 — 같은 cwd 로 탭이 여럿이어도 한 번만 훑는다 */
13521
+ const pending = new Set();
13522
+ /** 마커 하나가 있나 — 접근 실패(권한·끊긴 네트워크)는 "없다" 로 본다 */
13523
+ async function hasMarkerAsync(dir) {
13524
+ for (const marker of PROJECT_MARKERS) {
13525
+ try {
13526
+ await fs__WEBPACK_IMPORTED_MODULE_0__.promises.access(dir + '/' + marker);
13527
+ return true;
13528
+ }
13529
+ catch {
13530
+ // 다음 마커로
13531
+ }
13532
+ }
13533
+ return false;
13534
+ }
13535
+ async function detect(cwd) {
13536
+ for (const dir of walkUp(cwd, homeBoundary())) {
13537
+ if (await hasMarkerAsync(dir)) {
13538
+ return dir;
13539
+ }
13540
+ }
13541
+ return null;
13542
+ }
13543
+ /**
13544
+ * 그룹 키 계산용 조회 — **절대 막히지 않는다.**
13545
+ *
13546
+ * 캐시에 있으면 그 값, 없으면 null 을 돌려주고 탐지를 백그라운드로 건다. 탐지가 끝나서
13547
+ * 그룹이 달라질 때만(`root` 를 찾았을 때만) `onResolved` 를 부른다 — 못 찾은 경우는
13548
+ * 키가 cwd 그대로라 화면이 바뀌지 않으므로 다시 그릴 이유가 없다.
13549
+ */
13550
+ function projectRootOf(cwd, onResolved) {
13551
+ const p = (0,_group__WEBPACK_IMPORTED_MODULE_2__.normalizeCwd)(cwd);
13552
+ if (!p) {
13553
+ return null;
13554
+ }
13555
+ const id = (0,_group__WEBPACK_IMPORTED_MODULE_2__.foldGroupKey)(p);
13556
+ const hit = cache.get(id);
13557
+ if (hit !== undefined) {
13558
+ return hit;
13559
+ }
13560
+ if (!pending.has(id)) {
13561
+ pending.add(id);
13562
+ detect(p).then(root => {
13563
+ cache.set(id, root);
13564
+ pending.delete(id);
13565
+ if (root) {
13566
+ onResolved === null || onResolved === void 0 ? void 0 : onResolved();
13567
+ }
13568
+ }).catch(() => {
13569
+ // 못 찾은 것으로 굳힌다 — 실패한 경로를 매 렌더마다 다시 훑지 않게
13570
+ cache.set(id, null);
13571
+ pending.delete(id);
13572
+ });
13573
+ }
13574
+ return null;
13575
+ }
13576
+ /** 테스트·진단용 — 캐시를 비운다 (다음 조회가 다시 훑는다) */
13577
+ function clearProjectRootCache() {
13578
+ cache.clear();
13579
+ pending.clear();
13580
+ }
13581
+ /** 진단용 — 지금까지 찾아 둔 것 (`__agentdeck.groups()` 가 보여준다) */
13582
+ function projectRootCacheSnapshot() {
13583
+ return [...cache.entries()].map(([cwd, root]) => ({ cwd, root }));
13584
+ }
13585
+
13586
+
12786
13587
  /***/ },
12787
13588
 
12788
13589
  /***/ "./src/prompt.ts"
@@ -13477,24 +14278,28 @@ function judgeScreen(lines, cols, shape) {
13477
14278
 
13478
14279
  /***/ },
13479
14280
 
13480
- /***/ "./src/settings.component.ts"
13481
- /*!***********************************!*\
13482
- !*** ./src/settings.component.ts ***!
13483
- \***********************************/
14281
+ /***/ "./src/sessionLedger.service.ts"
14282
+ /*!**************************************!*\
14283
+ !*** ./src/sessionLedger.service.ts ***!
14284
+ \**************************************/
13484
14285
  (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
13485
14286
 
13486
14287
  __webpack_require__.r(__webpack_exports__);
13487
14288
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
13488
- /* harmony export */ AgentDeckSettingsTabComponent: () => (/* binding */ AgentDeckSettingsTabComponent)
14289
+ /* harmony export */ SessionLedgerService: () => (/* binding */ SessionLedgerService)
13489
14290
  /* harmony export */ });
13490
14291
  /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
13491
14292
  /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
13492
14293
  /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
13493
14294
  /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
13494
- /* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./config */ "./src/config.ts");
13495
- /* harmony import */ var _claudeHooks__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./claudeHooks */ "./src/claudeHooks.ts");
13496
- /* harmony import */ var _recovery_service__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./recovery.service */ "./src/recovery.service.ts");
14295
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! fs */ "fs");
14296
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
14297
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! os */ "os");
14298
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_3__);
14299
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! path */ "path");
14300
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_4__);
13497
14301
  /* harmony import */ var _diag__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./diag */ "./src/diag.ts");
14302
+ /* harmony import */ var _sessionLedger__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./sessionLedger */ "./src/sessionLedger.ts");
13498
14303
  var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
13499
14304
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
13500
14305
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -13510,108 +14315,737 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
13510
14315
 
13511
14316
 
13512
14317
 
13513
- const DOCK_LABELS = {
13514
- left: '왼쪽',
13515
- right: '오른쪽',
13516
- top: '위쪽',
13517
- bottom: '아래쪽',
13518
- };
14318
+
13519
14319
  /**
13520
- * 설정 > AgentDeck 탭.
13521
- *
13522
- * 사용자가 실제로 바꿀 일이 있는 것만 노출한다 — 레이아웃 on/off, 기동 시 탭 복원 끄기,
13523
- * 사이드바 목록(키보드 조작 · 서브에이전트 개수), 세션 그룹, 미리보기 패널, 작업 루트 프로필.
13524
- * 나머지(화면비, 사이드바 폭, 투명도, 상태 감지, 붙여넣기/줄바꿈 가로채기, 렌더 보정)는
13525
- * 손댈 이유가 거의 없고 잘못 만지면 화면이 깨지는 값이라 `config.ts` 의 기본값으로 고정하고
13526
- * 설정 창에서는 뺐다. 정말 필요하면 설정 파일의 `agentDeck.*` 를 직접 고친다.
13527
- *
13528
- * `enabled` 는 deck.service 가 `config.changed$` 를 구독해 즉시 반영한다(deck.service.ts:256).
13529
- * 루트 프로필은 기동 시 한 번만 읽으므로 재시작이 필요하다고 적어 둔다.
14320
+ * 지난 세션 원장의 **파일 담당** — 읽고, 쓰고, 기록 폴더를 훑는다.
14321
+ * 무엇을 보여줄지 정하는 규칙은 전부 `sessionLedger.ts` 에 있다(fs 없이 테스트되는 쪽).
13530
14322
  *
13531
- * `사이드바 목록` 절을 `세션 그룹` **앞**에 둔 이유 — 그룹도 결국 이 목록의 세부 규칙이라
13532
- * 목록 전체에 걸리는 것(키보드 조작 · 줄에 무엇을 보일지)을 먼저 읽는 편이 순서가 맞는다.
13533
- * 0.14.0 의 `dragAutoScroll` 도 같은 절에 뒀다 — 정하는 것이 "목록을 다루는 방법" 이라
13534
- * 키보드 조작과 성질이 같고, 따로 절을 만들면 항목 하나뿐인 절이 생긴다.
13535
- * 0.13.0 에서 생긴 세 값(`keyboardNav` / `keyboardNavWrap` / `subagentCount`)은
13536
- * 설정 창에 자리가 없어 `config.yaml` 을 손으로 고쳐야만 바꿀 수 있었다.
13537
- *
13538
- * `keyboardNavWrap` 과 `keyboardCloseTab`(0.19.0) 은 `keyboardNav` 를 끄면 아무 뜻이 없으므로 그때는 줄을 아예 감춘다 —
13539
- * `groupRoot`·`collapsedGroups` 를 `groupByProject` 에 매달아 둔 것과 같은 방식이고,
13540
- * 꺼진 값을 회색으로 남겨 두는 것보다 "지금 만질 수 있는 것" 이 짧아진다.
13541
- * 이 세 값은 deck.service/notify.service 가 쓸 때마다 store 를 다시 읽으므로
13542
- * (deck.service.ts `navCanFocus`/`moveNav`, notify.service `subagentEnabled`)
13543
- * 토글하면 바로 듣는다 — 그래서 재시작 안내를 붙이지 않았다.
14323
+ * 저장 위치는 `%LOCALAPPDATA%\tabby-agentdeck\sessions.json` — 훅 상태 파일 폴더 옆이다.
14324
+ * **Tabby 설정(`config.yaml`)에 넣지 않는다.** 세션은 계속 쌓이는데 거기 넣으면 남의 설정 파일이
14325
+ * 수백 줄 불어나고, 라벨이 바뀔 때마다 `config.save()` 가 돌아 관계없는 설정까지 다시 쓴다.
13544
14326
  */
13545
- let AgentDeckSettingsTabComponent = class AgentDeckSettingsTabComponent {
13546
- constructor(config, recovery, platform) {
14327
+ let SessionLedgerService = class SessionLedgerService {
14328
+ constructor(config) {
13547
14329
  this.config = config;
13548
- this.recovery = recovery;
13549
- this.platform = platform;
13550
- /** 이 환경에서 훅을 걸 수 있나 (통보 스크립트가 PowerShell 이라 Windows 전용) */
13551
- this.hooksSupported = (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.hooksSupported)();
13552
- /** 지금 걸려 있나 — 파일을 매번 읽지 않도록 화면용으로 들고 있는다 */
13553
- this.hooksOn = false;
13554
- /** 훅을 쓰다 실패한 이유 — 조용히 삼키면 사용자는 버튼이 먹통인 줄 안다 */
13555
- this.hookError = '';
13556
- /** 신고 안내에 그대로 보여줄 진단 로그 경로 */
13557
- this.diagPath = _diag__WEBPACK_IMPORTED_MODULE_5__.DIAG_PATH;
13558
- /** 신고 본문에 적을 버전 — 사용자가 어느 버전을 쓰는지 스스로 알 방법이 없었다 */
13559
- this.diagVersion = (0,_diag__WEBPACK_IMPORTED_MODULE_5__.pluginVersion)();
13560
- /** 묶음에 화면 원문까지 넣나 (기본 제외 — 터미널에 찍힌 내용이 그대로 들어간다) */
13561
- this.diagIncludeScreen = false;
13562
- /** 마지막으로 만든 묶음 경로 */
13563
- this.diagBundlePath = '';
13564
- /** 묶음을 만들다 실패한 이유 */
13565
- this.diagError = '';
13566
- this.refreshHooks();
13567
- }
13568
- refreshHooks() {
13569
- var _a;
14330
+ this.root = path__WEBPACK_IMPORTED_MODULE_4__.join(process.env.LOCALAPPDATA || path__WEBPACK_IMPORTED_MODULE_4__.join(os__WEBPACK_IMPORTED_MODULE_3__.homedir(), 'AppData', 'Local'), 'tabby-agentdeck');
14331
+ this.file = path__WEBPACK_IMPORTED_MODULE_4__.join(this.root, 'sessions.json');
14332
+ /** Claude Code 대화기록이 사는 곳 (`notify.service` 와 같은 자리) */
14333
+ this.projectsDir = path__WEBPACK_IMPORTED_MODULE_4__.join(os__WEBPACK_IMPORTED_MODULE_3__.homedir(), '.claude', 'projects');
14334
+ /** 세션 id -> 원장 한 줄. 사람이 친 라벨이 여기 남는다 */
14335
+ this.ledger = new Map();
14336
+ /** 디스크 스캔 결과 (기록 파일에서 읽은 것) */
14337
+ this.scanned = [];
14338
+ /**
14339
+ * 기록 앞부분 파싱 캐시 — `세션id:mtime` 키. 파일이 자라도 **앞부분은 안 바뀐다**.
14340
+ * 그래도 mtime 을 키에 넣는 이유는 세션이 이어받기로 되살아나 새 줄이 앞에 붙는 경우가
14341
+ * 아니라, 파일이 통째로 갈렸을 때(삭제 후 재생성) 옛 값을 물고 있지 않기 위해서다.
14342
+ */
14343
+ this.headCache = new Map();
14344
+ this.loaded = false;
14345
+ this.saveTimer = null;
14346
+ this.scanning = null;
14347
+ this.lastScan = 0;
14348
+ /** 목록이 바뀌었을 때 호출부(사이드바)가 다시 그리도록 */
14349
+ this.onChange = null;
14350
+ }
14351
+ // ── 원장 ────────────────────────────────────────────────────────────────
14352
+ load() {
14353
+ if (this.loaded) {
14354
+ return;
14355
+ }
14356
+ this.loaded = true;
13570
14357
  try {
13571
- this.hooksOn = this.hooksSupported && (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.hooksInstalled)();
14358
+ const raw = fs__WEBPACK_IMPORTED_MODULE_2__.readFileSync(this.file, 'utf8');
14359
+ const parsed = JSON.parse(raw);
14360
+ const rows = Array.isArray(parsed === null || parsed === void 0 ? void 0 : parsed.records) ? parsed.records : [];
14361
+ for (const r of rows) {
14362
+ if (r && typeof r.sessionId === 'string' && r.sessionId) {
14363
+ this.ledger.set(r.sessionId, {
14364
+ sessionId: r.sessionId,
14365
+ cwd: typeof r.cwd === 'string' ? r.cwd : null,
14366
+ label: (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_6__.clampLabel)(r.label),
14367
+ lastStatus: typeof r.lastStatus === 'string' ? r.lastStatus : null,
14368
+ lastSeen: Number(r.lastSeen) || 0,
14369
+ from: r.from === 'head' ? 'head' : 'live',
14370
+ });
14371
+ }
14372
+ }
13572
14373
  }
13573
- catch (e) {
13574
- this.hooksOn = false;
13575
- this.hookError = `설정을 읽지 못했다: ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`;
14374
+ catch {
14375
+ // 아직 없다 / 깨졌다 — 빈 원장으로 시작한다. 디스크 스캔만으로도 목록은 선다
13576
14376
  }
13577
14377
  }
13578
14378
  /**
13579
- * 훅을 걸거나 뗀다.
13580
- *
13581
- * 걸 때는 통보 채널도 같이 켠다 — 훅만 걸고 채널이 닫혀 있으면 보고가 어디에도 닿지 않는데,
13582
- * 사용자 입장에서는 "설치했는데 아무 일도 안 일어난다" 로만 보인다.
14379
+ * 저장을 한 번으로 모은다. 라벨은 사람이 Enter 를 칠 때마다 갱신되므로
14380
+ * 그때마다 파일을 쓰면 타자 속도로 디스크를 때린다.
13583
14381
  */
13584
- toggleHooks() {
13585
- var _a;
13586
- this.hookError = '';
14382
+ scheduleSave() {
14383
+ if (this.saveTimer) {
14384
+ return;
14385
+ }
14386
+ this.saveTimer = setTimeout(() => {
14387
+ this.saveTimer = null;
14388
+ this.save();
14389
+ }, 2000);
14390
+ }
14391
+ save() {
13587
14392
  try {
13588
- if (this.hooksOn) {
13589
- (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.uninstallHooks)();
13590
- }
13591
- else {
13592
- (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.installHooks)();
13593
- if (!this.config.store.agentDeck.notifyChannel) {
13594
- this.config.store.agentDeck.notifyChannel = true;
13595
- this.config.save();
13596
- }
13597
- }
14393
+ fs__WEBPACK_IMPORTED_MODULE_2__.mkdirSync(this.root, { recursive: true });
14394
+ const records = [...this.ledger.values()];
14395
+ fs__WEBPACK_IMPORTED_MODULE_2__.writeFileSync(this.file, JSON.stringify({ version: 1, records }, null, 1) + '\n', 'utf8');
13598
14396
  }
13599
14397
  catch (e) {
13600
- this.hookError = `${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`;
14398
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_5__.diagCatch)('세션 원장 저장', e);
13601
14399
  }
13602
- this.refreshHooks();
13603
14400
  }
13604
14401
  /**
13605
- * OS 폴더 선택 창을 띄워 작업 폴더를 고른다.
14402
+ * 세션 하나의 지금을 원장에 적는다.
13606
14403
  *
13607
- * `pickDirectory` 는 취소하면 null/undefined 를 주므로 그때는 기존 값을 건드리지 않는다.
13608
- * 다이얼로그 제목은 Tabby 버전에 따라 인자를 받기도 하고 안 받기도 해서 any 로 넘긴다 —
13609
- * 안 받는 쪽은 그냥 무시하므로 어느 쪽에서도 깨지지 않는다.
13610
- * 돌려받은 경로는 백슬래시를 슬래시로 바꿔 저장한다. 설정 파일에 그대로 적히는 값이라
13611
- * 손으로 고칠 때 이스케이프 사고가 나지 않게 하려는 것이고, Windows 도 슬래시 경로를 받는다.
14404
+ * 라벨은 **비어 있지 않을 때만** 덮는다 — 탭이 막 열려 아직 프롬프트가 없을 때 빈 문자열로
14405
+ * 덮으면 지난 실행에서 적어 둔 좋은 라벨을 지우게 된다.
13612
14406
  */
14407
+ note(sessionId, patch) {
14408
+ var _a, _b, _c;
14409
+ if (!sessionId) {
14410
+ return;
14411
+ }
14412
+ this.load();
14413
+ const prev = this.ledger.get(sessionId);
14414
+ const label = (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_6__.clampLabel)(patch.label);
14415
+ const next = {
14416
+ sessionId,
14417
+ cwd: patch.cwd || (prev === null || prev === void 0 ? void 0 : prev.cwd) || null,
14418
+ label: label || (prev === null || prev === void 0 ? void 0 : prev.label) || '',
14419
+ lastStatus: (_b = (_a = patch.status) !== null && _a !== void 0 ? _a : prev === null || prev === void 0 ? void 0 : prev.lastStatus) !== null && _b !== void 0 ? _b : null,
14420
+ lastSeen: Date.now(),
14421
+ from: label ? 'live' : ((_c = prev === null || prev === void 0 ? void 0 : prev.from) !== null && _c !== void 0 ? _c : 'live'),
14422
+ };
14423
+ const same = prev
14424
+ && prev.cwd === next.cwd
14425
+ && prev.label === next.label
14426
+ && prev.lastStatus === next.lastStatus;
14427
+ this.ledger.set(sessionId, next);
14428
+ // 값이 그대로면 저장만 미룬다 — `lastSeen` 하나 때문에 2초마다 쓰지 않게
14429
+ if (!same) {
14430
+ this.scheduleSave();
14431
+ }
14432
+ }
14433
+ // ── 디스크 스캔 ─────────────────────────────────────────────────────────
13613
14434
  /**
13614
- * 진단 묶음을 만들고 그 파일이 있는 폴더를 연다.
14435
+ * 기록 폴더를 훑어 세션 목록을 다시 만든다.
14436
+ *
14437
+ * **폴링하지 않는다.** `notify.service.ts:64` 가 적어 둔 대로 `~/.claude/projects` 전체를
14438
+ * 2초마다 readdir 하면 안 된다. 목록을 펼칠 때와 탭이 닫힐 때만 부른다.
14439
+ */
14440
+ async refresh(force = false) {
14441
+ const now = Date.now();
14442
+ if (!force && now - this.lastScan < 15000) {
14443
+ return;
14444
+ }
14445
+ if (this.scanning) {
14446
+ return this.scanning;
14447
+ }
14448
+ this.scanning = this.scan().finally(() => {
14449
+ this.scanning = null;
14450
+ this.lastScan = Date.now();
14451
+ });
14452
+ return this.scanning;
14453
+ }
14454
+ async scan() {
14455
+ var _a;
14456
+ this.load();
14457
+ const cfg = this.config.store.agentDeck;
14458
+ const days = Number(cfg.resumeListDays) || 0;
14459
+ const cutoff = days > 0 ? Date.now() - days * 86400000 : 0;
14460
+ const found = [];
14461
+ /**
14462
+ * 기간과 **무관하게** 디스크에 파일이 있는 세션 전부. 원장 청소는 이걸로 판정한다 —
14463
+ * `found` 는 기간 필터를 통과한 것만이라 그걸로 지우면 오래된(그러나 멀쩡한) 기록의
14464
+ * 라벨이 통째로 날아간다. 기간은 "화면에 안 보인다" 이지 "없다" 가 아니다.
14465
+ */
14466
+ const onDisk = new Set();
14467
+ try {
14468
+ const dirs = await fs__WEBPACK_IMPORTED_MODULE_2__.promises.readdir(this.projectsDir, { withFileTypes: true });
14469
+ for (const entry of dirs) {
14470
+ if (!entry.isDirectory()) {
14471
+ continue;
14472
+ }
14473
+ const dir = path__WEBPACK_IMPORTED_MODULE_4__.join(this.projectsDir, entry.name);
14474
+ let names;
14475
+ try {
14476
+ names = await fs__WEBPACK_IMPORTED_MODULE_2__.promises.readdir(dir);
14477
+ }
14478
+ catch {
14479
+ // 권한·경합으로 못 읽는 폴더 하나 때문에 나머지를 포기하지 않는다
14480
+ continue;
14481
+ }
14482
+ for (const name of names) {
14483
+ const sid = (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_6__.sessionIdFromFile)(name);
14484
+ if (!sid) {
14485
+ continue;
14486
+ }
14487
+ const full = path__WEBPACK_IMPORTED_MODULE_4__.join(dir, name);
14488
+ onDisk.add(sid);
14489
+ try {
14490
+ const st = await fs__WEBPACK_IMPORTED_MODULE_2__.promises.stat(full);
14491
+ // 기간 밖은 **앞부분을 읽기 전에** 버린다 — 파싱이 아니라 stat 이 필터다
14492
+ if (cutoff && st.mtimeMs < cutoff) {
14493
+ continue;
14494
+ }
14495
+ found.push({ sessionId: sid, file: full, mtime: st.mtimeMs });
14496
+ }
14497
+ catch {
14498
+ // 방금 지워졌다 — 다음 스캔에서 사라진다
14499
+ }
14500
+ }
14501
+ }
14502
+ }
14503
+ catch {
14504
+ // `~/.claude/projects` 자체가 없다 — Claude Code 를 안 쓰는 환경. 목록은 비고 화면엔 아무것도 안 그려진다
14505
+ this.scanned = [];
14506
+ return;
14507
+ }
14508
+ found.sort((a, b) => b.mtime - a.mtime);
14509
+ // 앞부분 읽기에 상한을 둔다 — 기록이 수백 개인 사람에게도 펼치는 순간이 멈추지 않게.
14510
+ // 그룹당 몇 줄만 그리므로 최신 200개면 화면에 닿는 것은 다 들어온다
14511
+ const HEAD_SCAN_MAX = 200;
14512
+ const slice = found.slice(0, HEAD_SCAN_MAX);
14513
+ const out = [];
14514
+ for (const f of slice) {
14515
+ const key = `${f.sessionId}:${Math.round(f.mtime)}`;
14516
+ let head = this.headCache.get(key);
14517
+ if (!head) {
14518
+ head = await this.readHeadOf(f.file);
14519
+ this.headCache.set(key, head);
14520
+ }
14521
+ out.push({
14522
+ sessionId: f.sessionId,
14523
+ cwd: head.cwd,
14524
+ label: head.label,
14525
+ lastStatus: null,
14526
+ lastSeen: f.mtime,
14527
+ from: 'head',
14528
+ });
14529
+ }
14530
+ this.scanned = out;
14531
+ // 기록이 사라진 세션은 원장에서 지운다 — 이어받아도 복원할 것이 없다
14532
+ const before = this.ledger.size;
14533
+ const kept = (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_6__.pruneLedger)([...this.ledger.values()], onDisk);
14534
+ if (kept.length !== before) {
14535
+ this.ledger = new Map(kept.map(r => [r.sessionId, r]));
14536
+ this.scheduleSave();
14537
+ }
14538
+ (_a = this.onChange) === null || _a === void 0 ? void 0 : _a.call(this);
14539
+ }
14540
+ /**
14541
+ * 기록 파일 앞부분만 읽는다 — 전체를 읽지 않는 이유는 `HEAD_LINE_LIMIT` 주석에 있다.
14542
+ * 마지막 조각은 줄이 잘려 있을 수 있으니 버린다(파싱 실패로 조용히 넘어가긴 하지만,
14543
+ * 버리는 편이 "왜 라벨이 가끔 이상한가" 를 애초에 만들지 않는다).
14544
+ */
14545
+ async readHeadOf(file) {
14546
+ let fh = null;
14547
+ try {
14548
+ fh = await fs__WEBPACK_IMPORTED_MODULE_2__.promises.open(file, 'r');
14549
+ const buf = Buffer.alloc(_sessionLedger__WEBPACK_IMPORTED_MODULE_6__.HEAD_BYTE_LIMIT);
14550
+ const { bytesRead } = await fh.read(buf, 0, _sessionLedger__WEBPACK_IMPORTED_MODULE_6__.HEAD_BYTE_LIMIT, 0);
14551
+ const text = buf.slice(0, bytesRead).toString('utf8');
14552
+ const lines = text.split('\n');
14553
+ if (bytesRead === _sessionLedger__WEBPACK_IMPORTED_MODULE_6__.HEAD_BYTE_LIMIT) {
14554
+ lines.pop();
14555
+ }
14556
+ return (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_6__.readHead)(lines.slice(0, _sessionLedger__WEBPACK_IMPORTED_MODULE_6__.HEAD_LINE_LIMIT));
14557
+ }
14558
+ catch (e) {
14559
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_5__.diagCatch)(`세션 기록 앞부분 읽기 ${path__WEBPACK_IMPORTED_MODULE_4__.basename(file)}`, e);
14560
+ return { cwd: null, label: '', startedAt: null };
14561
+ }
14562
+ finally {
14563
+ await (fh === null || fh === void 0 ? void 0 : fh.close().catch(() => { }));
14564
+ }
14565
+ }
14566
+ // ── 조회 ────────────────────────────────────────────────────────────────
14567
+ /** 원장 + 디스크를 합친 전체 목록. 렌더 중에 불러도 막히지 않는다(캐시만 본다) */
14568
+ records() {
14569
+ this.load();
14570
+ return (0,_sessionLedger__WEBPACK_IMPORTED_MODULE_6__.mergeSessions)([...this.ledger.values()], this.scanned);
14571
+ }
14572
+ /** 진단용 — `__agentdeck.sessions()` 가 보여준다 */
14573
+ snapshot() {
14574
+ return {
14575
+ ledger: this.ledger.size,
14576
+ scanned: this.scanned.length,
14577
+ file: this.file,
14578
+ lastScan: this.lastScan,
14579
+ };
14580
+ }
14581
+ };
14582
+ SessionLedgerService = __decorate([
14583
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)({ providedIn: 'root' }),
14584
+ __metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.ConfigService])
14585
+ ], SessionLedgerService);
14586
+
14587
+
14588
+
14589
+ /***/ },
14590
+
14591
+ /***/ "./src/sessionLedger.ts"
14592
+ /*!******************************!*\
14593
+ !*** ./src/sessionLedger.ts ***!
14594
+ \******************************/
14595
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
14596
+
14597
+ __webpack_require__.r(__webpack_exports__);
14598
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
14599
+ /* harmony export */ HEAD_BYTE_LIMIT: () => (/* binding */ HEAD_BYTE_LIMIT),
14600
+ /* harmony export */ HEAD_LINE_LIMIT: () => (/* binding */ HEAD_LINE_LIMIT),
14601
+ /* harmony export */ LABEL_MAX: () => (/* binding */ LABEL_MAX),
14602
+ /* harmony export */ clampLabel: () => (/* binding */ clampLabel),
14603
+ /* harmony export */ formatWhen: () => (/* binding */ formatWhen),
14604
+ /* harmony export */ isHumanPrompt: () => (/* binding */ isHumanPrompt),
14605
+ /* harmony export */ mergeSessions: () => (/* binding */ mergeSessions),
14606
+ /* harmony export */ promptFromRecord: () => (/* binding */ promptFromRecord),
14607
+ /* harmony export */ pruneLedger: () => (/* binding */ pruneLedger),
14608
+ /* harmony export */ readHead: () => (/* binding */ readHead),
14609
+ /* harmony export */ resumeCommand: () => (/* binding */ resumeCommand),
14610
+ /* harmony export */ resumeRowsFor: () => (/* binding */ resumeRowsFor),
14611
+ /* harmony export */ sessionIdFromFile: () => (/* binding */ sessionIdFromFile)
14612
+ /* harmony export */ });
14613
+ /**
14614
+ * **지난 세션 원장** — 죽은 Claude Code 세션을 다시 찾아낼 수 있게 하는 순수 규칙.
14615
+ *
14616
+ * 왜 필요한가 — `recovery.service.ts` 가 적어 둔 대로 agentdeck 은 Tabby 의 순정 탭 복원을
14617
+ * 의도적으로 껐다. 되살아나는 것은 셸뿐이고 그 안의 세션은 이미 죽어 있어 "주인 없는 탭" 만
14618
+ * 남기 때문이다. 그 자리에 들어갈 진짜 복원이 `claude --resume <세션id>` 인데, 사람이 막히는
14619
+ * 지점은 명령이 아니라 **UUID 목록에서 내 작업을 못 찾는 것** 이다.
14620
+ *
14621
+ * agentdeck 은 그 답을 이미 갖고 있다 — Enter 시점에 입력창에서 주운 프롬프트 원문(라벨).
14622
+ * 문제는 그게 **메모리에만 있다는 것** 이다(`status.service` 의 `TabState.label`). 탭이 닫히면
14623
+ * 같이 증발한다. 그래서 이 모듈이 라벨·상태·cwd 를 세션 id 에 묶어 남기고, 목록을 만든다.
14624
+ *
14625
+ * fs·타이머는 여기 없다 — `group.ts`·`subagents.ts` 와 같은 이유다. 파일을 읽고 주기를
14626
+ * 관리하는 것은 호출부(서비스)의 일이고, 이 파일은 **무엇을 어떤 순서로 보여줄지** 만 정한다.
14627
+ * 그래야 `npm test` 에서 돌릴 수 있다.
14628
+ */
14629
+ /**
14630
+ * 기록 파일 앞부분을 얼마나 읽을지.
14631
+ *
14632
+ * 전부 파싱하면 안 된다 — 실측 세션이 2.6MB(1,021줄)·5.5MB 고, 목록 한 번에 수십 개를 훑는다.
14633
+ * 필요한 것은 `cwd` 와 **첫 사람 프롬프트** 둘뿐인데 실측에서 `cwd` 는 5번째 줄,
14634
+ * 프롬프트는 65번째 줄에 나왔다(2026-09-11, `f54b20b9`). 앞의 60여 줄은 훅 주입·모드·첨부라
14635
+ * 여유를 크게 잡아도 이 정도면 닿는다. 못 찾으면 라벨 없이 두지, 파일을 더 파고들지 않는다.
14636
+ */
14637
+ const HEAD_LINE_LIMIT = 400;
14638
+ /** 줄 수와 별개로 이 바이트를 넘기면 멈춘다 — 한 줄이 통째로 거대한 기록(대용량 첨부)이 있다 */
14639
+ const HEAD_BYTE_LIMIT = 512 * 1024;
14640
+ /**
14641
+ * 첫 프롬프트로 치지 않는 것들.
14642
+ *
14643
+ * 기록의 `type:"user"` 줄에는 사람이 친 것만 오지 않는다 — 훅이 주입한 컨텍스트, 한도 리셋
14644
+ * 안내, 서브에이전트 종료 알림, 슬래시 명령의 출력이 같은 모양으로 들어온다(실측: `9e0c954f`
14645
+ * 의 마지막 user 줄 8개가 전부 `Your claude.ai usage limit has reset…` 이었다).
14646
+ * 그걸 라벨로 올리면 목록의 모든 줄이 같은 문장이 되어 아무것도 구별하지 못한다.
14647
+ */
14648
+ const NOISE_PREFIXES = [
14649
+ '<',
14650
+ 'Caveat:',
14651
+ '[Request interrupted',
14652
+ 'Your claude.ai usage limit',
14653
+ ];
14654
+ /** 사람이 친 프롬프트로 볼 수 있는 문자열인가 */
14655
+ function isHumanPrompt(text) {
14656
+ if (typeof text !== 'string') {
14657
+ return false;
14658
+ }
14659
+ const t = text.trim();
14660
+ if (!t) {
14661
+ return false;
14662
+ }
14663
+ return !NOISE_PREFIXES.some(p => t.startsWith(p));
14664
+ }
14665
+ /**
14666
+ * 기록 줄(파싱된 JSON) 하나에서 사람이 친 프롬프트를 꺼낸다 — 아니면 null.
14667
+ *
14668
+ * `message.content` 는 문자열일 때도 있고 블록 배열일 때도 있다. 배열이면 `text` 블록만 잇는다
14669
+ * (`tool_result` 가 섞여 들어오면 라벨이 도구 출력으로 오염된다).
14670
+ */
14671
+ function promptFromRecord(rec) {
14672
+ var _a;
14673
+ if (!rec || rec.type !== 'user' || rec.isMeta === true) {
14674
+ return null;
14675
+ }
14676
+ const content = (_a = rec.message) === null || _a === void 0 ? void 0 : _a.content;
14677
+ let text = null;
14678
+ if (typeof content === 'string') {
14679
+ text = content;
14680
+ }
14681
+ else if (Array.isArray(content)) {
14682
+ const parts = content
14683
+ .filter((b) => b && b.type === 'text' && typeof b.text === 'string')
14684
+ .map((b) => b.text);
14685
+ text = parts.length ? parts.join(' ') : null;
14686
+ }
14687
+ return isHumanPrompt(text) ? text.trim() : null;
14688
+ }
14689
+ /** 라벨로 쓸 길이로 줄인다 — 사이드바 한 줄은 말줄임이 있지만 원장에 소설을 담을 이유는 없다 */
14690
+ const LABEL_MAX = 140;
14691
+ function clampLabel(text) {
14692
+ if (!text) {
14693
+ return '';
14694
+ }
14695
+ // 줄바꿈이 들어오면 한 줄로 — 여러 줄 프롬프트가 그대로 오면 목록 높이가 튄다
14696
+ const one = text.replace(/\s+/g, ' ').trim();
14697
+ return one.length > LABEL_MAX ? one.slice(0, LABEL_MAX - 1) + '…' : one;
14698
+ }
14699
+ /**
14700
+ * 기록 앞부분의 줄들에서 cwd·첫 프롬프트를 뽑는다.
14701
+ *
14702
+ * **폴더 이름 규칙을 흉내내지 않는다.** `~/.claude/projects/<폴더>` 의 이름은 작업 경로를 `-` 로
14703
+ * 뭉갠 것이라 드라이브 문자·한글·특수문자에서 되돌릴 수 없다(`notify.service.ts:1006` 이 같은
14704
+ * 이유로 폴더 이름 대신 파일명 매칭을 쓴다). 기록 안에 `cwd` 가 그대로 들어 있으니 그걸 읽는다.
14705
+ */
14706
+ function readHead(lines) {
14707
+ let cwd = null;
14708
+ let label = '';
14709
+ let startedAt = null;
14710
+ for (const line of lines) {
14711
+ if (!line) {
14712
+ continue;
14713
+ }
14714
+ let rec;
14715
+ try {
14716
+ rec = JSON.parse(line);
14717
+ }
14718
+ catch {
14719
+ // 잘린 줄 하나 때문에 나머지를 포기하지 않는다 (증분 읽기와 같은 태도)
14720
+ continue;
14721
+ }
14722
+ if (!cwd && typeof (rec === null || rec === void 0 ? void 0 : rec.cwd) === 'string' && rec.cwd) {
14723
+ cwd = rec.cwd;
14724
+ }
14725
+ if (startedAt === null && typeof (rec === null || rec === void 0 ? void 0 : rec.timestamp) === 'string') {
14726
+ const t = Date.parse(rec.timestamp);
14727
+ if (!Number.isNaN(t)) {
14728
+ startedAt = t;
14729
+ }
14730
+ }
14731
+ if (!label) {
14732
+ const p = promptFromRecord(rec);
14733
+ if (p) {
14734
+ label = clampLabel(p);
14735
+ }
14736
+ }
14737
+ if (cwd && label && startedAt !== null) {
14738
+ break;
14739
+ }
14740
+ }
14741
+ return { cwd, label, startedAt };
14742
+ }
14743
+ /**
14744
+ * 원장과 디스크 스캔을 합친다 — **세션 id 가 키, 원장이 우선.**
14745
+ *
14746
+ * 두 경로가 다 필요하다. 원장에만 기대면 agentdeck 설치 이전 세션과 다른 터미널에서 띄운
14747
+ * 세션이 통째로 안 보이고, 디스크에만 기대면 사람이 친 라벨(가장 좋은 단서)을 잃는다.
14748
+ *
14749
+ * 원장이 이기는 것은 **라벨과 상태** 뿐이다. `lastSeen` 은 둘 중 큰 값을 쓴다 — 원장은 탭이
14750
+ * 닫힐 때 멈추는데 그 뒤 다른 터미널에서 같은 세션을 이어받았을 수 있고, 그때는 디스크가 맞다.
14751
+ */
14752
+ function mergeSessions(ledger, scanned) {
14753
+ var _a;
14754
+ const out = new Map();
14755
+ for (const rec of scanned) {
14756
+ if (rec.sessionId) {
14757
+ out.set(rec.sessionId, { ...rec });
14758
+ }
14759
+ }
14760
+ for (const rec of ledger) {
14761
+ if (!rec.sessionId) {
14762
+ continue;
14763
+ }
14764
+ const disk = out.get(rec.sessionId);
14765
+ if (!disk) {
14766
+ // 기록 파일이 사라진 세션. **목록에 넣지 않는다** — 이어받아도 복원할 것이 없다.
14767
+ // 원장 청소(`pruneLedger`)가 같은 판정으로 지운다
14768
+ continue;
14769
+ }
14770
+ out.set(rec.sessionId, {
14771
+ ...disk,
14772
+ label: rec.label || disk.label,
14773
+ from: rec.label ? rec.from : disk.from,
14774
+ lastStatus: (_a = rec.lastStatus) !== null && _a !== void 0 ? _a : disk.lastStatus,
14775
+ cwd: disk.cwd || rec.cwd,
14776
+ lastSeen: Math.max(rec.lastSeen || 0, disk.lastSeen || 0),
14777
+ });
14778
+ }
14779
+ return [...out.values()];
14780
+ }
14781
+ /**
14782
+ * 기록 파일이 남아 있는 세션만 남긴다 — 원장이 무한히 부풀지 않게.
14783
+ *
14784
+ * 디스크를 진실로 삼는 이유: 사람이 `~/.claude/projects` 를 지우면 그 세션은 이어받을 수 없고,
14785
+ * 그걸 원장이 계속 보여주면 눌렀을 때 빈 세션이 뜬다.
14786
+ */
14787
+ function pruneLedger(ledger, aliveIds) {
14788
+ return ledger.filter(r => r.sessionId && aliveIds.has(r.sessionId));
14789
+ }
14790
+ /**
14791
+ * 한 그룹에 그릴 지난 세션 줄.
14792
+ *
14793
+ * **살아 있는 세션을 빼지 않는다** — `openTabId` 를 달아 그대로 둔다. 지우면 "내가 방금 보던
14794
+ * 그 작업이 목록에 없다" 가 되어 사람이 같은 세션을 다시 찾아 헤맨다. 대신 눌렀을 때
14795
+ * 이어받지 않고 그 탭으로 보낸다(같은 세션을 두 번 이어받으면 두 프로세스가 한 기록에 덧쓴다).
14796
+ *
14797
+ * 정렬은 **마지막 활동 내림차순** 하나뿐이다. 상태별로 묶고 싶어질 수 있는데, 지난 세션에서
14798
+ * 사람이 찾는 것은 "방금 하던 것" 이지 "완료된 것" 이 아니다.
14799
+ */
14800
+ function resumeRowsFor(records, opts) {
14801
+ var _a;
14802
+ const want = new Set(opts.groupKeys.map(k => opts.fold(k)));
14803
+ const cutoff = opts.days > 0 ? opts.now - opts.days * 86400000 : null;
14804
+ const rows = [];
14805
+ for (const rec of records) {
14806
+ if (!rec.sessionId || opts.hidden.has(rec.sessionId)) {
14807
+ continue;
14808
+ }
14809
+ if (!want.has(opts.fold(opts.keyOf(rec.cwd)))) {
14810
+ continue;
14811
+ }
14812
+ if (cutoff !== null && rec.lastSeen < cutoff) {
14813
+ continue;
14814
+ }
14815
+ rows.push({ ...rec, openTabId: (_a = opts.live.get(rec.sessionId)) !== null && _a !== void 0 ? _a : null });
14816
+ }
14817
+ rows.sort((a, b) => b.lastSeen - a.lastSeen || a.sessionId.localeCompare(b.sessionId));
14818
+ return opts.limit > 0 ? rows.slice(0, opts.limit) : rows;
14819
+ }
14820
+ /**
14821
+ * 마지막 활동 시각을 사람이 읽는 짧은 꼴로.
14822
+ *
14823
+ * 오늘이면 시각(`14:12`), 어제면 `어제`, 그보다 오래면 `9/10`. 사이드바 줄의 오른쪽 끝에
14824
+ * 들어가므로 길면 제목 폭을 먹는다 — 연도는 넣지 않는다(제한 기간이 기본 7일이라 못 만난다).
14825
+ */
14826
+ function formatWhen(ts, now) {
14827
+ if (!ts || !Number.isFinite(ts)) {
14828
+ return '';
14829
+ }
14830
+ const d = new Date(ts);
14831
+ const n = new Date(now);
14832
+ const sameDay = d.getFullYear() === n.getFullYear()
14833
+ && d.getMonth() === n.getMonth()
14834
+ && d.getDate() === n.getDate();
14835
+ if (sameDay) {
14836
+ return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
14837
+ }
14838
+ const y = new Date(now);
14839
+ y.setDate(y.getDate() - 1);
14840
+ if (d.getFullYear() === y.getFullYear() && d.getMonth() === y.getMonth() && d.getDate() === y.getDate()) {
14841
+ return '어제';
14842
+ }
14843
+ return `${d.getMonth() + 1}/${d.getDate()}`;
14844
+ }
14845
+ /** 세션 id 처럼 생겼나 — 기록 폴더에는 우리 것이 아닌 파일도 있을 수 있다 */
14846
+ const SESSION_FILE_RE = /^([0-9a-fA-F-]{8,64})\.jsonl$/;
14847
+ /** 파일 이름에서 세션 id 를 꺼낸다 (아니면 null) */
14848
+ function sessionIdFromFile(name) {
14849
+ const m = SESSION_FILE_RE.exec(name || '');
14850
+ return m ? m[1] : null;
14851
+ }
14852
+ /**
14853
+ * 셸에 보낼 이어받기 명령.
14854
+ *
14855
+ * **프로필의 `command` 를 갈아끼우지 않고 셸에 문자열로 보낸다.** 이유 둘 —
14856
+ * ① 기존 `agentdeck:root` 프로필(`-NoLogo -ExecutionPolicy Bypass`, COLORTERM/FORCE_COLOR)을
14857
+ * 그대로 쓴다. npm 전역 `.ps1` 래퍼가 실행정책에 막히는 문제를 그 인자가 이미 풀어 둔다
14858
+ * (`profile.service.ts:58`).
14859
+ * ② claude 가 끝나도 셸이 남아 사람이 이어서 쓸 수 있다. `command` 로 박으면
14860
+ * `behaviorOnSessionEnd` 에 따라 탭이 닫힌다.
14861
+ *
14862
+ * 세션 id 는 우리가 파일 이름에서 읽은 값이고 `SESSION_FILE_RE` 를 통과한 것만 여기 온다 —
14863
+ * 그래도 명령을 만드는 자리이므로 한 번 더 막는다. 통과 못 하면 빈 문자열이고 호출부가 아무것도 보내지 않는다.
14864
+ */
14865
+ function resumeCommand(sessionId, fork) {
14866
+ if (!/^[0-9a-fA-F-]{8,64}$/.test(sessionId || '')) {
14867
+ return '';
14868
+ }
14869
+ return `claude --resume ${sessionId}${fork ? ' --fork-session' : ''}`;
14870
+ }
14871
+
14872
+
14873
+ /***/ },
14874
+
14875
+ /***/ "./src/settings.component.ts"
14876
+ /*!***********************************!*\
14877
+ !*** ./src/settings.component.ts ***!
14878
+ \***********************************/
14879
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
14880
+
14881
+ __webpack_require__.r(__webpack_exports__);
14882
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
14883
+ /* harmony export */ AgentDeckSettingsTabComponent: () => (/* binding */ AgentDeckSettingsTabComponent)
14884
+ /* harmony export */ });
14885
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
14886
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
14887
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
14888
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
14889
+ /* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./config */ "./src/config.ts");
14890
+ /* harmony import */ var _claudeHooks__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./claudeHooks */ "./src/claudeHooks.ts");
14891
+ /* harmony import */ var _recovery_service__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./recovery.service */ "./src/recovery.service.ts");
14892
+ /* harmony import */ var _update_service__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./update.service */ "./src/update.service.ts");
14893
+ /* harmony import */ var _diag__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./diag */ "./src/diag.ts");
14894
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
14895
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
14896
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
14897
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14898
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
14899
+ };
14900
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
14901
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
14902
+ };
14903
+
14904
+
14905
+
14906
+
14907
+
14908
+
14909
+
14910
+ /** 확인 결과를 사람 말로 — 설정 창의 한 줄 안내 */
14911
+ const UPDATE_OUTCOME_TEXT = {
14912
+ 'disabled': '자동 업데이트가 꺼져 있다',
14913
+ 'too-soon': '방금 확인했다',
14914
+ 'dev-install': '새 버전이 있지만 개발 설치라 건드리지 않는다',
14915
+ 'unknown': 'npm 에 물어보지 못했다 (네트워크·프록시)',
14916
+ 'up-to-date': '최신이다',
14917
+ 'update-ready': '새 버전이 있다',
14918
+ 'skipped': '이 버전은 건너뛰기로 했다',
14919
+ 'postponed': '나중에 하기로 했다',
14920
+ 'installing': '설치를 시작했다 — Tabby 가 곧 닫혔다가 다시 켜진다',
14921
+ 'failed': '업데이트를 시작하지 못했다',
14922
+ };
14923
+ const DOCK_LABELS = {
14924
+ left: '왼쪽',
14925
+ right: '오른쪽',
14926
+ top: '위쪽',
14927
+ bottom: '아래쪽',
14928
+ };
14929
+ /**
14930
+ * 설정 > AgentDeck 탭.
14931
+ *
14932
+ * 사용자가 실제로 바꿀 일이 있는 것만 노출한다 — 레이아웃 on/off, 기동 시 탭 복원 끄기,
14933
+ * 사이드바 목록(키보드 조작 · 서브에이전트 개수), 세션 그룹, 미리보기 패널, 작업 루트 프로필.
14934
+ * 나머지(화면비, 사이드바 폭, 투명도, 상태 감지, 붙여넣기/줄바꿈 가로채기, 렌더 보정)는
14935
+ * 손댈 이유가 거의 없고 잘못 만지면 화면이 깨지는 값이라 `config.ts` 의 기본값으로 고정하고
14936
+ * 설정 창에서는 뺐다. 정말 필요하면 설정 파일의 `agentDeck.*` 를 직접 고친다.
14937
+ *
14938
+ * `enabled` 는 deck.service 가 `config.changed$` 를 구독해 즉시 반영한다(deck.service.ts:256).
14939
+ * 루트 프로필은 기동 시 한 번만 읽으므로 재시작이 필요하다고 적어 둔다.
14940
+ *
14941
+ * `사이드바 목록` 절을 `세션 그룹` **앞**에 둔 이유 — 그룹도 결국 이 목록의 세부 규칙이라
14942
+ * 목록 전체에 걸리는 것(키보드 조작 · 줄에 무엇을 보일지)을 먼저 읽는 편이 순서가 맞는다.
14943
+ * 0.14.0 의 `dragAutoScroll` 도 같은 절에 뒀다 — 정하는 것이 "목록을 다루는 방법" 이라
14944
+ * 키보드 조작과 성질이 같고, 따로 절을 만들면 항목 하나뿐인 절이 생긴다.
14945
+ * 0.13.0 에서 생긴 세 값(`keyboardNav` / `keyboardNavWrap` / `subagentCount`)은
14946
+ * 설정 창에 자리가 없어 `config.yaml` 을 손으로 고쳐야만 바꿀 수 있었다.
14947
+ *
14948
+ * `keyboardNavWrap` 과 `keyboardCloseTab`(0.19.0) 은 `keyboardNav` 를 끄면 아무 뜻이 없으므로 그때는 줄을 아예 감춘다 —
14949
+ * 꺼진 값을 회색으로 남겨 두는 것보다 "지금 만질 수 있는 것" 이 짧아진다.
14950
+ * 세션 그룹 절에 값이 하나도 없는 것(0.20.0)도 같은 기준이다 — 끌 이유가 없는 스위치와
14951
+ * 자동으로 알아낼 수 있는 경로는 설정이 아니라 동작이어야 한다.
14952
+ * 이 세 값은 deck.service/notify.service 가 쓸 때마다 store 를 다시 읽으므로
14953
+ * (deck.service.ts `navCanFocus`/`moveNav`, notify.service `subagentEnabled`)
14954
+ * 토글하면 바로 듣는다 — 그래서 재시작 안내를 붙이지 않았다.
14955
+ */
14956
+ let AgentDeckSettingsTabComponent = class AgentDeckSettingsTabComponent {
14957
+ constructor(config, recovery, update, platform) {
14958
+ this.config = config;
14959
+ this.recovery = recovery;
14960
+ this.update = update;
14961
+ this.platform = platform;
14962
+ /** 이 환경에서 훅을 걸 수 있나 (통보 스크립트가 PowerShell 이라 Windows 전용) */
14963
+ this.hooksSupported = (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.hooksSupported)();
14964
+ /** 지금 걸려 있나 — 파일을 매번 읽지 않도록 화면용으로 들고 있는다 */
14965
+ this.hooksOn = false;
14966
+ /** 훅을 쓰다 실패한 이유 — 조용히 삼키면 사용자는 버튼이 먹통인 줄 안다 */
14967
+ this.hookError = '';
14968
+ /** 신고 안내에 그대로 보여줄 진단 로그 경로 */
14969
+ this.diagPath = _diag__WEBPACK_IMPORTED_MODULE_6__.DIAG_PATH;
14970
+ /** 신고 본문에 적을 버전 — 사용자가 어느 버전을 쓰는지 스스로 알 방법이 없었다 */
14971
+ this.diagVersion = (0,_diag__WEBPACK_IMPORTED_MODULE_6__.pluginVersion)();
14972
+ /** 묶음에 화면 원문까지 넣나 (기본 제외 — 터미널에 찍힌 내용이 그대로 들어간다) */
14973
+ this.diagIncludeScreen = false;
14974
+ /** 마지막으로 만든 묶음 경로 */
14975
+ this.diagBundlePath = '';
14976
+ /** 묶음을 만들다 실패한 이유 */
14977
+ this.diagError = '';
14978
+ /** `지금 확인` 이 도는 동안 버튼을 잠근다 (레지스트리 응답까지 몇 초) */
14979
+ this.updateBusy = false;
14980
+ /** 마지막 확인 결과 한 줄 */
14981
+ this.updateMsg = '';
14982
+ this.refreshHooks();
14983
+ }
14984
+ /**
14985
+ * 사람이 직접 누른 확인. `force` 라 간격과 `다시 묻지 않기` 를 건너뛴다 —
14986
+ * 눌렀다는 것 자체가 "지금 알고 싶다" 는 뜻이다.
14987
+ */
14988
+ async checkUpdate() {
14989
+ var _a, _b;
14990
+ this.updateBusy = true;
14991
+ this.updateMsg = '';
14992
+ try {
14993
+ this.updateMsg = (_a = UPDATE_OUTCOME_TEXT[await this.update.run('settings', true)]) !== null && _a !== void 0 ? _a : '';
14994
+ }
14995
+ catch (e) {
14996
+ this.updateMsg = `확인하지 못했다: ${(_b = e === null || e === void 0 ? void 0 : e.message) !== null && _b !== void 0 ? _b : e}`;
14997
+ }
14998
+ finally {
14999
+ this.updateBusy = false;
15000
+ }
15001
+ }
15002
+ refreshHooks() {
15003
+ var _a;
15004
+ try {
15005
+ this.hooksOn = this.hooksSupported && (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.hooksInstalled)();
15006
+ }
15007
+ catch (e) {
15008
+ this.hooksOn = false;
15009
+ this.hookError = `설정을 읽지 못했다: ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`;
15010
+ }
15011
+ }
15012
+ /**
15013
+ * 훅을 걸거나 뗀다.
15014
+ *
15015
+ * 걸 때는 통보 채널도 같이 켠다 — 훅만 걸고 채널이 닫혀 있으면 보고가 어디에도 닿지 않는데,
15016
+ * 사용자 입장에서는 "설치했는데 아무 일도 안 일어난다" 로만 보인다.
15017
+ */
15018
+ toggleHooks() {
15019
+ var _a;
15020
+ this.hookError = '';
15021
+ try {
15022
+ if (this.hooksOn) {
15023
+ (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.uninstallHooks)();
15024
+ }
15025
+ else {
15026
+ (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.installHooks)();
15027
+ if (!this.config.store.agentDeck.notifyChannel) {
15028
+ this.config.store.agentDeck.notifyChannel = true;
15029
+ this.config.save();
15030
+ }
15031
+ }
15032
+ }
15033
+ catch (e) {
15034
+ this.hookError = `${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`;
15035
+ }
15036
+ this.refreshHooks();
15037
+ }
15038
+ /**
15039
+ * OS 폴더 선택 창을 띄워 작업 폴더를 고른다.
15040
+ *
15041
+ * `pickDirectory` 는 취소하면 null/undefined 를 주므로 그때는 기존 값을 건드리지 않는다.
15042
+ * 다이얼로그 제목은 Tabby 버전에 따라 인자를 받기도 하고 안 받기도 해서 any 로 넘긴다 —
15043
+ * 안 받는 쪽은 그냥 무시하므로 어느 쪽에서도 깨지지 않는다.
15044
+ * 돌려받은 경로는 백슬래시를 슬래시로 바꿔 저장한다. 설정 파일에 그대로 적히는 값이라
15045
+ * 손으로 고칠 때 이스케이프 사고가 나지 않게 하려는 것이고, Windows 도 슬래시 경로를 받는다.
15046
+ */
15047
+ /**
15048
+ * 진단 묶음을 만들고 그 파일이 있는 폴더를 연다.
13615
15049
  *
13616
15050
  * 폴더를 여는 것까지가 한 동작인 이유 — 경로만 알려 주면 "그 폴더가 어디냐" 가 다시 질문으로
13617
15051
  * 돌아온다. 압축이 안 되는 환경에서는 폴더 경로가 나오므로 그대로 열어 주면 된다.
@@ -13621,12 +15055,12 @@ let AgentDeckSettingsTabComponent = class AgentDeckSettingsTabComponent {
13621
15055
  this.diagError = '';
13622
15056
  this.diagBundlePath = '';
13623
15057
  try {
13624
- const bundle = (0,_diag__WEBPACK_IMPORTED_MODULE_5__.collectDiagBundle)({
15058
+ const bundle = (0,_diag__WEBPACK_IMPORTED_MODULE_6__.collectDiagBundle)({
13625
15059
  includeScreen: this.diagIncludeScreen,
13626
15060
  stamp: {
13627
15061
  tabby: (_b = (_a = this.platform).getAppVersion) === null || _b === void 0 ? void 0 : _b.call(_a),
13628
15062
  osRelease: (_d = (_c = this.platform).getOSRelease) === null || _d === void 0 ? void 0 : _d.call(_c),
13629
- config: (0,_diag__WEBPACK_IMPORTED_MODULE_5__.configStamp)(this.config.store),
15063
+ config: (0,_diag__WEBPACK_IMPORTED_MODULE_6__.configStamp)(this.config.store),
13630
15064
  },
13631
15065
  });
13632
15066
  this.diagBundlePath = bundle.path;
@@ -13678,15 +15112,6 @@ let AgentDeckSettingsTabComponent = class AgentDeckSettingsTabComponent {
13678
15112
  this.config.store.agentDeck.collapsedGroups = [];
13679
15113
  this.config.save();
13680
15114
  }
13681
- /** 프로젝트 기준 폴더를 OS 다이얼로그로 고른다 (작업 폴더와 같은 방식) */
13682
- async pickGroupRoot() {
13683
- const picked = await this.platform.pickDirectory('프로젝트 기준 폴더 선택');
13684
- if (!picked) {
13685
- return;
13686
- }
13687
- this.config.store.agentDeck.groupRoot = String(picked).replace(/\\/g, '/');
13688
- this.config.save();
13689
- }
13690
15115
  async pickCwd() {
13691
15116
  const picked = await this.platform.pickDirectory('작업 폴더 선택');
13692
15117
  if (!picked) {
@@ -13737,6 +15162,64 @@ AgentDeckSettingsTabComponent = __decorate([
13737
15162
  <toggle [ngModel]="config.store.agentDeck.noTabRecovery" (ngModelChange)="recovery.setNoTabRecovery($event)"></toggle>
13738
15163
  </div>
13739
15164
 
15165
+ <div class="form-line">
15166
+ <div class="header">
15167
+ <div class="title">자동 업데이트</div>
15168
+ <div class="description">
15169
+ 기동할 때 npm 을 보고 새 버전이 있으면 <b>물어본 뒤</b> 업데이트한다.
15170
+ 승낙하면 Tabby 가 닫히고 → 설치하고 → <b>스스로 다시 켜진다</b> (사람이 할 일은 없다).
15171
+ 돌고 있는 플러그인은 갈아끼울 수 없어서 이 방법뿐이다.
15172
+ 끊긴 Claude Code 대화는 <code>⟲ 지난 세션</code> 에서 이어받으면 된다.
15173
+ <b>개발 설치(소스 링크)에는 설치하지 않는다</b> — 작업 중인 소스를 덮어쓰기 때문이다.
15174
+ </div>
15175
+ </div>
15176
+ <toggle [(ngModel)]="config.store.agentDeck.autoUpdate" (ngModelChange)="config.save()"></toggle>
15177
+ </div>
15178
+
15179
+ <div class="form-line" *ngIf="config.store.agentDeck.autoUpdate">
15180
+ <div class="header">
15181
+ <div class="title">지금 확인</div>
15182
+ <div class="description">
15183
+ 간격({{config.store.agentDeck.updateCheckIntervalHours}}시간)과 <code>다시 묻지 않기</code> 를 무시하고 한 번 확인한다.
15184
+ <span *ngIf="updateMsg">— {{updateMsg}}</span>
15185
+ </div>
15186
+ </div>
15187
+ <button class="btn btn-secondary" type="button" [disabled]="updateBusy" (click)="checkUpdate()">
15188
+ <i class="fas fa-rotate me-2"></i>{{updateBusy ? '확인 중…' : '확인'}}
15189
+ </button>
15190
+ </div>
15191
+
15192
+ <div class="form-line">
15193
+ <div class="header">
15194
+ <div class="title">지난 세션 이어받기</div>
15195
+ <div class="description">
15196
+ 그룹 끝에 <code>⟲ 지난 세션</code> 서랍을 둔다 (기본 접힘). 펴면 <b>닫힌 Claude Code 세션</b>이
15197
+ 그때 친 프롬프트로 보이고, 누르면 그 세션의 작업 폴더에서 <code>claude --resume</code> 으로
15198
+ 대화가 되살아난다. 위의 <b>탭 복원 끄기</b> 와 한 쌍이다 — 순정 복원은 셸만 되살리지만
15199
+ 이건 대화를 되살린다. <b>다만 그때 띄워 둔 서버·백그라운드 프로세스는 돌아오지 않는다.</b>
15200
+ </div>
15201
+ </div>
15202
+ <toggle [(ngModel)]="config.store.agentDeck.resumeList" (ngModelChange)="config.save()"></toggle>
15203
+ </div>
15204
+
15205
+ <div class="form-line" *ngIf="config.store.agentDeck.resumeList">
15206
+ <div class="header">
15207
+ <div class="title">몇 줄까지 · 며칠치</div>
15208
+ <div class="description">
15209
+ 그룹마다 최근 <b>{{config.store.agentDeck.resumeListLimit}}줄</b>까지,
15210
+ 최근 <b>{{config.store.agentDeck.resumeListDays}}일</b> 안의 세션만 보여준다.
15211
+ 길게 잡으면 살아 있는 탭이 아래로 밀린다. 숨긴 세션은 줄 우클릭으로 되돌릴 수 없고
15212
+ <code>config.yaml</code> 의 <code>resumeHidden</code> 을 비우면 다시 나온다.
15213
+ </div>
15214
+ </div>
15215
+ <div class="d-flex align-items-center">
15216
+ <input class="form-control text-end me-2" style="width: 5rem" type="number" min="0" max="50"
15217
+ [(ngModel)]="config.store.agentDeck.resumeListLimit" (ngModelChange)="config.save()">
15218
+ <input class="form-control text-end" style="width: 5rem" type="number" min="0" max="365"
15219
+ [(ngModel)]="config.store.agentDeck.resumeListDays" (ngModelChange)="config.save()">
15220
+ </div>
15221
+ </div>
15222
+
13740
15223
  <h3 class="mt-4 mb-3">사이드바 목록</h3>
13741
15224
  <div class="text-muted mb-3">
13742
15225
  세션 목록을 마우스 없이 다루는 방법과, 줄에 무엇까지 보일지를 정한다. 바꾸면 곧바로 적용된다.
@@ -13815,41 +15298,16 @@ AgentDeckSettingsTabComponent = __decorate([
13815
15298
 
13816
15299
  <h3 class="mt-4 mb-3">세션 그룹</h3>
13817
15300
  <div class="text-muted mb-3">
13818
- 사이드바의 세션을 <b>작업 폴더(프로젝트) 단위</b>로 묶고 헤더를 눌러 접는다.
13819
- 그룹이 하나뿐이면 헤더를 그리지 않으므로 한 프로젝트만 쓸 때는 화면이 예전과 같다.
15301
+ 사이드바의 세션을 <b>프로젝트 단위</b>로 묶고 헤더를 눌러 접는다. 끄고 켜는 값이 없다 —
15302
+ 그룹이 하나뿐이면 헤더도 순서도 건드리지 않으므로, 한 프로젝트만 쓸 때는 묶지 않은 화면과 같다.
15303
+ <br>
15304
+ 묶는 기준은 <b>탭이 태어난 작업 폴더</b>다 — 셸에서 <code>cd</code> 를 해도 따라오지 않으므로
15305
+ 세션이 사는 동안 그룹이 흔들리지 않는다. 그룹 안의 순서는 상태 정렬을 그대로 따른다.
15306
+ 경계는 <b>저장소 루트</b>(<code>.git</code> 등)를 위로 훑어 자동으로 잡으므로 하위 폴더에서 띄운
15307
+ 세션도 같은 프로젝트로 들어오고, 저장소가 아닌 폴더는 그 폴더 자체가 그룹이다.
13820
15308
  </div>
13821
15309
 
13822
15310
  <div class="form-line">
13823
- <div class="header">
13824
- <div class="title">프로젝트로 묶기</div>
13825
- <div class="description">
13826
- 묶는 기준은 <b>탭이 태어난 작업 폴더</b>다 — 셸에서 <code>cd</code> 를 해도 따라오지 않으므로
13827
- 세션이 사는 동안 그룹이 흔들리지 않는다. 그룹 안의 순서는 상태 정렬을 그대로 따른다.
13828
- </div>
13829
- </div>
13830
- <toggle [(ngModel)]="config.store.agentDeck.groupByProject" (ngModelChange)="config.save()"></toggle>
13831
- </div>
13832
-
13833
- <div class="form-line" *ngIf="config.store.agentDeck.groupByProject">
13834
- <div class="header">
13835
- <div class="title">프로젝트 기준 폴더</div>
13836
- <div class="description">
13837
- 비우면 위의 <b>작업 루트</b>(<code>rootProfileCwd</code>)를 쓴다.
13838
- 이걸 채우면 그 <b>직하 한 단계</b>가 그룹이 되어, 하위 폴더에서 띄운 세션도 같은 그룹에 들어온다
13839
- (예: <code>D:/Project</code> → <code>D:/Project/a</code> 와 <code>D:/Project/a/sub</code> 가 한 그룹).
13840
- 둘 다 비면 작업 폴더 자체가 그룹이다. 슬래시로 적는다.
13841
- </div>
13842
- </div>
13843
- <div class="input-group w-50">
13844
- <input class="form-control" type="text" placeholder="D:/Project"
13845
- [(ngModel)]="config.store.agentDeck.groupRoot" (change)="config.save()">
13846
- <button class="btn btn-secondary" type="button" (click)="pickGroupRoot()">
13847
- <i class="fas fa-folder-open me-2"></i>찾아보기
13848
- </button>
13849
- </div>
13850
- </div>
13851
-
13852
- <div class="form-line" *ngIf="config.store.agentDeck.groupByProject">
13853
15311
  <div class="header">
13854
15312
  <div class="title">접어 둔 그룹 펼치기</div>
13855
15313
  <div class="description">
@@ -14066,6 +15524,7 @@ AgentDeckSettingsTabComponent = __decorate([
14066
15524
  }),
14067
15525
  __metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.ConfigService,
14068
15526
  _recovery_service__WEBPACK_IMPORTED_MODULE_4__.AgentDeckRecoveryService,
15527
+ _update_service__WEBPACK_IMPORTED_MODULE_5__.AgentDeckUpdateService,
14069
15528
  tabby_core__WEBPACK_IMPORTED_MODULE_1__.PlatformService])
14070
15529
  ], AgentDeckSettingsTabComponent);
14071
15530
 
@@ -14653,6 +16112,498 @@ function pickTabByTabId(tabId, tabIds) {
14653
16112
  }
14654
16113
 
14655
16114
 
16115
+ /***/ },
16116
+
16117
+ /***/ "./src/update.service.ts"
16118
+ /*!*******************************!*\
16119
+ !*** ./src/update.service.ts ***!
16120
+ \*******************************/
16121
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
16122
+
16123
+ __webpack_require__.r(__webpack_exports__);
16124
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
16125
+ /* harmony export */ AgentDeckUpdateService: () => (/* binding */ AgentDeckUpdateService)
16126
+ /* harmony export */ });
16127
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
16128
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
16129
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
16130
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
16131
+ /* harmony import */ var child_process__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! child_process */ "child_process");
16132
+ /* harmony import */ var child_process__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(child_process__WEBPACK_IMPORTED_MODULE_2__);
16133
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! fs */ "fs");
16134
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_3__);
16135
+ /* harmony import */ var http__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! http */ "http");
16136
+ /* harmony import */ var http__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(http__WEBPACK_IMPORTED_MODULE_4__);
16137
+ /* harmony import */ var https__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! https */ "https");
16138
+ /* harmony import */ var https__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(https__WEBPACK_IMPORTED_MODULE_5__);
16139
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! os */ "os");
16140
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_6__);
16141
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! path */ "path");
16142
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_7__);
16143
+ /* harmony import */ var _diag__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./diag */ "./src/diag.ts");
16144
+ /* harmony import */ var _update__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./update */ "./src/update.ts");
16145
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
16146
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16147
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
16148
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
16149
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
16150
+ };
16151
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
16152
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
16153
+ };
16154
+
16155
+
16156
+
16157
+
16158
+
16159
+
16160
+
16161
+
16162
+
16163
+
16164
+ /** 레지스트리 응답을 기다리는 시간 — 기동 직후라 오래 붙들면 안 된다 */
16165
+ const FETCH_TIMEOUT_MS = 6000;
16166
+ /** 기동하고 이만큼 뒤에 확인한다 — 창이 뜨는 순간에 네트워크를 건드리지 않는다 */
16167
+ const STARTUP_DELAY_MS = 8000;
16168
+ /** 응답 본문 상한 — 레지스트리가 아닌 무언가가 답할 때 메모리를 먹지 않게 */
16169
+ const MAX_BODY_BYTES = 512 * 1024;
16170
+ /**
16171
+ * npm 레지스트리를 보고 **Tabby 를 껐다 켜면서** 새 버전을 깐다.
16172
+ *
16173
+ * 흐름은 넷이다 —
16174
+ * ① 기동하고 잠시 뒤 레지스트리에 묻는다
16175
+ * ② 새 버전이 있으면 **물어본다** (`지금 업데이트` / `나중에` / `다시 묻지 않기`)
16176
+ * ③ 승낙하면 설치 스크립트를 **밖으로 띄우고** Tabby 를 닫는다
16177
+ * ④ 스크립트가 Tabby 종료를 기다렸다가 설치하고 다시 띄운다
16178
+ *
16179
+ * **왜 밖에서 까나.** 돌고 있는 플러그인은 자기를 갈아끼울 수 없다 — Tabby 는 기동할 때
16180
+ * `dist/index.js` 를 한 번 읽어 메모리에 올리므로 디스크를 바꿔도 돌던 코드는 옛것이다.
16181
+ * 게다가 Windows 는 로드된 파일을 잡고 있어서, 떠 있는 동안 npm 이 그 폴더를 지우려 들면
16182
+ * 반쯤 지운 채 실패할 수 있다 — 플러그인이 깨진 채 남는 것이 가장 나쁜 결과다.
16183
+ *
16184
+ * **개발 설치에는 설치하지 않는다.** 플러그인 폴더의 `tabby-agentdeck` 은 개발 중에는 소스
16185
+ * 트리를 가리키는 링크이고(`plugins/package.json` 에 `file:D:/Project/tabby-agentdeck` 로 적힌다),
16186
+ * 거기에 `npm install` 이 돌면 작업 중인 트리를 덮어쓴다. 판정은 `update.ts` 의 `canSelfUpdate`.
16187
+ */
16188
+ let AgentDeckUpdateService = class AgentDeckUpdateService {
16189
+ constructor(app, config, platform, notifications) {
16190
+ this.app = app;
16191
+ this.config = config;
16192
+ this.platform = platform;
16193
+ this.notifications = notifications;
16194
+ }
16195
+ init() {
16196
+ this.app.ready$.subscribe(() => {
16197
+ var _a;
16198
+ // 개발/검증용 — deck.service 가 만든 `__agentdeck` 에 붙는다 (없으면 만든다).
16199
+ // `run` 은 **설치까지 간다**(승낙하면 Tabby 가 닫힌다) — 회귀에서 부를 때는
16200
+ // 개발 설치라 `dev-install` 에서 멈추는 것을 전제로 한다
16201
+ const g = globalThis;
16202
+ g.__agentdeck = (_a = g.__agentdeck) !== null && _a !== void 0 ? _a : {};
16203
+ g.__agentdeck.update = {
16204
+ run: (force = true) => this.run('diag', force),
16205
+ marks: () => this.installMarks(),
16206
+ canSelfUpdate: () => (0,_update__WEBPACK_IMPORTED_MODULE_9__.canSelfUpdate)(this.installMarks()),
16207
+ pluginsDir: () => this.pluginsDir(),
16208
+ version: () => (0,_diag__WEBPACK_IMPORTED_MODULE_8__.pluginVersion)(),
16209
+ };
16210
+ setTimeout(() => { void this.run('startup'); }, STARTUP_DELAY_MS);
16211
+ });
16212
+ }
16213
+ /**
16214
+ * 한 번 확인하고, 새 버전이면 물어본 뒤 설치를 건다.
16215
+ *
16216
+ * `force` 는 설정 창의 `지금 확인` 용 — 간격 제한과 `다시 묻지 않기` 를 건너뛴다
16217
+ * (사람이 직접 눌렀으니 그 순간만큼은 묻는 게 맞다). 기능 자체를 끈 것은 존중한다.
16218
+ */
16219
+ async run(reason, force = false) {
16220
+ const cfg = this.config.store.agentDeck;
16221
+ const current = (0,_diag__WEBPACK_IMPORTED_MODULE_8__.pluginVersion)();
16222
+ const marks = this.installMarks();
16223
+ const devInstall = !(0,_update__WEBPACK_IMPORTED_MODULE_9__.canSelfUpdate)(marks);
16224
+ if (cfg.autoUpdate === false) {
16225
+ (0,_diag__WEBPACK_IMPORTED_MODULE_8__.diag)(`update skip=disabled reason=${reason}`);
16226
+ return 'disabled';
16227
+ }
16228
+ if (!force && !(0,_update__WEBPACK_IMPORTED_MODULE_9__.shouldCheck)(cfg.lastUpdateCheck, Date.now(), Number(cfg.updateCheckIntervalHours) || _update__WEBPACK_IMPORTED_MODULE_9__.DEFAULT_INTERVAL_HOURS)) {
16229
+ (0,_diag__WEBPACK_IMPORTED_MODULE_8__.diag)(`update skip=too-soon reason=${reason}`);
16230
+ return 'too-soon';
16231
+ }
16232
+ let latest = null;
16233
+ try {
16234
+ latest = (0,_update__WEBPACK_IMPORTED_MODULE_9__.parseLatestVersion)(await this.fetch((0,_update__WEBPACK_IMPORTED_MODULE_9__.latestUrl)(cfg.updateRegistry)));
16235
+ }
16236
+ catch (e) {
16237
+ // 오프라인·사내망·프록시 — 흔한 일이고 사람에게 알릴 것이 아니다
16238
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_8__.diagCatch)('update 레지스트리 조회', e);
16239
+ }
16240
+ // 물어본 것 자체를 기록한다(성공이든 아니든) — 실패할 때마다 기동마다 다시 묻지 않게
16241
+ cfg.lastUpdateCheck = Date.now();
16242
+ this.config.save();
16243
+ const outcome = (0,_update__WEBPACK_IMPORTED_MODULE_9__.decide)({
16244
+ enabled: true,
16245
+ lastCheckAt: 0,
16246
+ now: Date.now(),
16247
+ intervalHours: 0,
16248
+ devInstall,
16249
+ current,
16250
+ latest,
16251
+ });
16252
+ (0,_diag__WEBPACK_IMPORTED_MODULE_8__.diag)(`update check=${reason} current=${current} latest=${latest !== null && latest !== void 0 ? latest : '?'} outcome=${outcome}`
16253
+ + ` dev=${devInstall} marks=${JSON.stringify(marks)}`);
16254
+ if (outcome === 'dev-install') {
16255
+ // 개발 중인 사람에게는 알려 줄 값이 있다 — 다만 손대지 않는다
16256
+ this.notifications.info(`AgentDeck ${latest} 가 나왔다 (지금 ${current})`, '개발 설치(소스 링크)라 자동으로 갈아끼우지 않는다. 소스에서 직접 올릴 것.');
16257
+ return outcome;
16258
+ }
16259
+ if (outcome !== 'update-ready') {
16260
+ return outcome;
16261
+ }
16262
+ // 이 버전은 건너뛰기로 한 적이 있나 — `다시 묻지 않기` 는 **그 버전에만** 건다.
16263
+ // 영구히 끄는 것은 설정의 `autoUpdate` 쪽이고, 여기서 영구히 막으면 다음 버전도 못 받는다
16264
+ if (!force && cfg.updateSkipVersion === latest) {
16265
+ (0,_diag__WEBPACK_IMPORTED_MODULE_8__.diag)(`update skip=asked-not-to version=${latest}`);
16266
+ return 'skipped';
16267
+ }
16268
+ return this.confirmAndInstall(current, latest);
16269
+ }
16270
+ /** ② 물어보고 ③ 설치 스크립트를 띄운 뒤 Tabby 를 닫는다 */
16271
+ async confirmAndInstall(current, latest) {
16272
+ var _a;
16273
+ const r = await this.platform.showMessageBox({
16274
+ type: 'warning',
16275
+ message: `AgentDeck ${latest} 로 업데이트할까요? (지금 ${current})`,
16276
+ detail: [
16277
+ '업데이트하려면 Tabby 를 닫아야 합니다 — 돌고 있는 플러그인은 갈아끼울 수 없습니다.',
16278
+ '',
16279
+ '누르면 이렇게 됩니다:',
16280
+ ' 1. Tabby 가 닫힙니다 (열린 탭과 돌고 있는 세션이 끊깁니다)',
16281
+ ' 2. 백그라운드에서 새 버전을 내려받아 설치합니다',
16282
+ ' 3. Tabby 가 다시 켜집니다',
16283
+ '',
16284
+ '끊긴 Claude Code 대화는 사이드바의 `⟲ 지난 세션` 에서 이어받을 수 있습니다.',
16285
+ ].join('\n'),
16286
+ buttons: ['지금 업데이트', '나중에', '다시 묻지 않기'],
16287
+ defaultId: 1,
16288
+ cancelId: 1,
16289
+ });
16290
+ if (r.response === 2) {
16291
+ // 이 버전만 건너뛴다 — 다음 버전이 나오면 다시 묻는다
16292
+ this.config.store.agentDeck.updateSkipVersion = latest;
16293
+ this.config.save();
16294
+ (0,_diag__WEBPACK_IMPORTED_MODULE_8__.diag)(`update skip-version=${latest}`);
16295
+ return 'skipped';
16296
+ }
16297
+ if (r.response !== 0) {
16298
+ return 'postponed';
16299
+ }
16300
+ try {
16301
+ this.launchUpdater(latest);
16302
+ }
16303
+ catch (e) {
16304
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_8__.diagCatch)('update 설치 스크립트 기동', e);
16305
+ this.notifications.error('업데이트를 시작하지 못했다', `${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e} — 설정 → 플러그인 에서 직접 업데이트할 수 있다.`);
16306
+ return 'failed';
16307
+ }
16308
+ // 스크립트가 우리 종료를 기다리고 있다. 여기서 닫아 줘야 설치가 시작된다
16309
+ ;(0,_diag__WEBPACK_IMPORTED_MODULE_8__.diag)(`update quit for install ${current} -> ${latest}`);
16310
+ setTimeout(() => this.platform.quit(), 300);
16311
+ return 'installing';
16312
+ }
16313
+ /**
16314
+ * 설치 스크립트를 **패키지 밖에서** 떼어 띄운다.
16315
+ *
16316
+ * 임시 폴더로 복사하는 이유: 이 스크립트는 곧 npm 이 통째로 갈아엎을 폴더 안에 산다.
16317
+ * 제자리에서 돌리면 자기 발밑이 사라진다.
16318
+ *
16319
+ * `detached` + `unref` 라야 Tabby 가 죽어도 살아남는다 — 부모가 죽을 때 같이 끌려가면
16320
+ * 설치는커녕 Tabby 를 다시 띄우지도 못한다.
16321
+ */
16322
+ launchUpdater(version) {
16323
+ const src = path__WEBPACK_IMPORTED_MODULE_7__.join(__dirname, '..', 'updater', 'agentdeck-update.ps1');
16324
+ if (!fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(src)) {
16325
+ throw new Error(`설치 스크립트를 찾지 못했다: ${src}`);
16326
+ }
16327
+ const dir = fs__WEBPACK_IMPORTED_MODULE_3__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_7__.join(os__WEBPACK_IMPORTED_MODULE_6__.tmpdir(), 'agentdeck-up-'));
16328
+ const script = path__WEBPACK_IMPORTED_MODULE_7__.join(dir, 'agentdeck-update.ps1');
16329
+ fs__WEBPACK_IMPORTED_MODULE_3__.copyFileSync(src, script);
16330
+ const pluginsDir = this.pluginsDir();
16331
+ if (!pluginsDir) {
16332
+ throw new Error('Tabby 플러그인 폴더를 찾지 못했다');
16333
+ }
16334
+ const child = (0,child_process__WEBPACK_IMPORTED_MODULE_2__.spawn)('powershell.exe', [
16335
+ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-WindowStyle', 'Hidden',
16336
+ '-File', script,
16337
+ '-Version', version,
16338
+ '-PluginsDir', pluginsDir,
16339
+ // Electron 에서 `process.execPath` 가 곧 Tabby.exe 다 — 경로를 짐작하지 않는다
16340
+ '-TabbyExe', process.execPath,
16341
+ ], { detached: true, stdio: 'ignore' });
16342
+ child.unref();
16343
+ (0,_diag__WEBPACK_IMPORTED_MODULE_8__.diag)(`update updater spawned pid=${child.pid} script=${script}`);
16344
+ }
16345
+ /** `<Tabby 설정폴더>/plugins` — npm 이 `--prefix` 로 받을 경로 */
16346
+ pluginsDir() {
16347
+ var _a, _b;
16348
+ try {
16349
+ const configPath = (_b = (_a = this.platform).getConfigPath) === null || _b === void 0 ? void 0 : _b.call(_a);
16350
+ return configPath ? path__WEBPACK_IMPORTED_MODULE_7__.join(path__WEBPACK_IMPORTED_MODULE_7__.dirname(configPath), 'plugins') : null;
16351
+ }
16352
+ catch {
16353
+ return null;
16354
+ }
16355
+ }
16356
+ /**
16357
+ * 이 설치본이 개발 설치인지 가리는 표식.
16358
+ *
16359
+ * 배포본에는 `dist`·`hooks`·`updater`·`install.ps1` 만 들어간다(`package.json` 의 `files`).
16360
+ * `src/`·`webpack.config.js`·`.git` 중 하나라도 있으면 소스 트리이고, 플러그인 폴더의
16361
+ * 항목이 링크면 junction 설치다 — 어느 쪽이든 건드리면 안 된다.
16362
+ */
16363
+ installMarks() {
16364
+ // `__dirname` 은 `<플러그인>/dist` 다. junction 으로 깔려 있으면 node 가 실경로로 풀어 주므로
16365
+ // 이 값 자체가 이미 소스 트리를 가리킨다 — 그래서 아래 파일 표식이 곧바로 걸린다
16366
+ const root = path__WEBPACK_IMPORTED_MODULE_7__.join(__dirname, '..');
16367
+ const has = (rel) => {
16368
+ try {
16369
+ return fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(path__WEBPACK_IMPORTED_MODULE_7__.join(root, rel));
16370
+ }
16371
+ catch {
16372
+ return false;
16373
+ }
16374
+ };
16375
+ return {
16376
+ hasSrc: has('src'),
16377
+ hasGit: has('.git'),
16378
+ hasWebpackConfig: has('webpack.config.js'),
16379
+ isLink: this.pluginEntryIsLink(),
16380
+ };
16381
+ }
16382
+ /** Tabby 플러그인 폴더의 우리 항목이 링크인가 — junction 설치의 가장 직접적인 증거 */
16383
+ pluginEntryIsLink() {
16384
+ try {
16385
+ const dir = this.pluginsDir();
16386
+ if (!dir) {
16387
+ return false;
16388
+ }
16389
+ return fs__WEBPACK_IMPORTED_MODULE_3__.lstatSync(path__WEBPACK_IMPORTED_MODULE_7__.join(dir, 'node_modules', _update__WEBPACK_IMPORTED_MODULE_9__.PACKAGE_NAME)).isSymbolicLink();
16390
+ }
16391
+ catch {
16392
+ // 경로를 못 찾거나 항목이 없다 — 링크라고 단정하지 않는다(파일 표식이 따로 본다)
16393
+ return false;
16394
+ }
16395
+ }
16396
+ /** GET 한 번. 리다이렉트는 따라가지 않는다 — 레지스트리 `/latest` 는 200 으로 답한다 */
16397
+ fetch(url) {
16398
+ return new Promise((resolve, reject) => {
16399
+ const mod = url.startsWith('http://') ? http__WEBPACK_IMPORTED_MODULE_4__ : https__WEBPACK_IMPORTED_MODULE_5__;
16400
+ const req = mod.get(url, { headers: { accept: 'application/json' } }, (res) => {
16401
+ if (res.statusCode !== 200) {
16402
+ res.resume();
16403
+ reject(new Error(`HTTP ${res.statusCode}`));
16404
+ return;
16405
+ }
16406
+ let body = '';
16407
+ res.setEncoding('utf8');
16408
+ res.on('data', (chunk) => {
16409
+ body += chunk;
16410
+ if (body.length > MAX_BODY_BYTES) {
16411
+ req.destroy(new Error('응답이 너무 크다'));
16412
+ }
16413
+ });
16414
+ res.on('end', () => resolve(body));
16415
+ });
16416
+ req.setTimeout(FETCH_TIMEOUT_MS, () => req.destroy(new Error('시간 초과')));
16417
+ req.on('error', reject);
16418
+ });
16419
+ }
16420
+ };
16421
+ AgentDeckUpdateService = __decorate([
16422
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)({ providedIn: 'root' }),
16423
+ __metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.AppService,
16424
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.ConfigService,
16425
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.PlatformService,
16426
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.NotificationsService])
16427
+ ], AgentDeckUpdateService);
16428
+
16429
+
16430
+
16431
+ /***/ },
16432
+
16433
+ /***/ "./src/update.ts"
16434
+ /*!***********************!*\
16435
+ !*** ./src/update.ts ***!
16436
+ \***********************/
16437
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
16438
+
16439
+ __webpack_require__.r(__webpack_exports__);
16440
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
16441
+ /* harmony export */ DEFAULT_INTERVAL_HOURS: () => (/* binding */ DEFAULT_INTERVAL_HOURS),
16442
+ /* harmony export */ DEFAULT_REGISTRY: () => (/* binding */ DEFAULT_REGISTRY),
16443
+ /* harmony export */ PACKAGE_NAME: () => (/* binding */ PACKAGE_NAME),
16444
+ /* harmony export */ canSelfUpdate: () => (/* binding */ canSelfUpdate),
16445
+ /* harmony export */ compareVersions: () => (/* binding */ compareVersions),
16446
+ /* harmony export */ decide: () => (/* binding */ decide),
16447
+ /* harmony export */ isNewer: () => (/* binding */ isNewer),
16448
+ /* harmony export */ latestUrl: () => (/* binding */ latestUrl),
16449
+ /* harmony export */ parseLatestVersion: () => (/* binding */ parseLatestVersion),
16450
+ /* harmony export */ parseVersion: () => (/* binding */ parseVersion),
16451
+ /* harmony export */ shouldCheck: () => (/* binding */ shouldCheck)
16452
+ /* harmony export */ });
16453
+ /**
16454
+ * **자동 업데이트의 판단 규칙** — 언제 확인할지, 새 버전인지, 업데이트해도 되는지.
16455
+ *
16456
+ * 네트워크·fs·타이머는 여기 없다 (`group.ts`·`sessionLedger.ts` 와 같은 이유). 이 파일은
16457
+ * "무엇을 근거로 결정하는가" 만 정하고, 실제로 묻고 설치하는 것은 `update.service.ts` 다.
16458
+ *
16459
+ * ## 왜 "받아 두고 다음 기동에 적용" 인가
16460
+ *
16461
+ * Tabby 는 플러그인을 **기동할 때 한 번** 읽는다. 돌고 있는 플러그인을 갈아끼울 방법이 없으므로
16462
+ * 어떤 방식으로 업데이트하든 재시작이 필요하다. 그래서 이 기능은 조용히 받아만 두고
16463
+ * "재시작하면 적용된다" 고 한 번 알린다 — 사람의 작업을 끊지 않는 유일한 모양이다.
16464
+ */
16465
+ /** npm 레지스트리 기본값. 사내 미러를 쓰는 사람은 설정으로 바꾼다 */
16466
+ const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
16467
+ /** 이 패키지 이름 — 레지스트리에 물어볼 대상이자 `installPlugin` 에 넘길 이름 */
16468
+ const PACKAGE_NAME = 'tabby-agentdeck';
16469
+ /** 확인 간격 기본값 (시간). 기동마다 묻지 않는다 — 레지스트리에도 사람 네트워크에도 예의가 아니다 */
16470
+ const DEFAULT_INTERVAL_HOURS = 6;
16471
+ /**
16472
+ * 버전 문자열을 숫자 3개 + 프리릴리스로 가른다.
16473
+ * semver 가 아니면 `null` — 그런 값이 오면 비교하지 않고 "모른다" 로 둔다(업데이트를 강행하지 않는다).
16474
+ */
16475
+ function parseVersion(v) {
16476
+ var _a;
16477
+ if (typeof v !== 'string') {
16478
+ return null;
16479
+ }
16480
+ const m = /^\s*v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?\s*$/.exec(v);
16481
+ if (!m) {
16482
+ return null;
16483
+ }
16484
+ return { nums: [Number(m[1]), Number(m[2]), Number(m[3])], pre: (_a = m[4]) !== null && _a !== void 0 ? _a : null };
16485
+ }
16486
+ /**
16487
+ * semver 비교 — `a < b` 면 -1, 같으면 0, `a > b` 면 1. 못 읽으면 `null`.
16488
+ *
16489
+ * 프리릴리스는 같은 숫자의 정식판보다 **낮다**(`1.1.0-beta` < `1.1.0`). 프리릴리스끼리는
16490
+ * 점으로 끊어 숫자는 수로, 그 외는 문자열로 비교한다 (semver 규칙 그대로).
16491
+ */
16492
+ function compareVersions(a, b) {
16493
+ const pa = parseVersion(a);
16494
+ const pb = parseVersion(b);
16495
+ if (!pa || !pb) {
16496
+ return null;
16497
+ }
16498
+ for (let i = 0; i < 3; i++) {
16499
+ if (pa.nums[i] !== pb.nums[i]) {
16500
+ return pa.nums[i] < pb.nums[i] ? -1 : 1;
16501
+ }
16502
+ }
16503
+ if (pa.pre === pb.pre) {
16504
+ return 0;
16505
+ }
16506
+ // 한쪽만 프리릴리스면 그쪽이 낮다
16507
+ if (pa.pre === null) {
16508
+ return 1;
16509
+ }
16510
+ if (pb.pre === null) {
16511
+ return -1;
16512
+ }
16513
+ const xa = pa.pre.split('.');
16514
+ const xb = pb.pre.split('.');
16515
+ for (let i = 0; i < Math.max(xa.length, xb.length); i++) {
16516
+ const ta = xa[i];
16517
+ const tb = xb[i];
16518
+ if (ta === undefined) {
16519
+ return -1;
16520
+ }
16521
+ if (tb === undefined) {
16522
+ return 1;
16523
+ }
16524
+ const na = /^\d+$/.test(ta);
16525
+ const nb = /^\d+$/.test(tb);
16526
+ if (na && nb) {
16527
+ if (Number(ta) !== Number(tb)) {
16528
+ return Number(ta) < Number(tb) ? -1 : 1;
16529
+ }
16530
+ }
16531
+ else if (ta !== tb) {
16532
+ return ta < tb ? -1 : 1;
16533
+ }
16534
+ }
16535
+ return 0;
16536
+ }
16537
+ /** 레지스트리의 것이 지금 것보다 새로운가. 못 읽으면 `false` — 모르면 건드리지 않는다 */
16538
+ function isNewer(current, latest) {
16539
+ return compareVersions(current, latest) === -1;
16540
+ }
16541
+ /** 레지스트리 응답(JSON 문자열)에서 버전을 꺼낸다 — `<registry>/<pkg>/latest` 의 모양 */
16542
+ function parseLatestVersion(body) {
16543
+ var _a;
16544
+ try {
16545
+ const v = (_a = JSON.parse(body)) === null || _a === void 0 ? void 0 : _a.version;
16546
+ return parseVersion(v) ? String(v).trim() : null;
16547
+ }
16548
+ catch {
16549
+ // HTML 오류 페이지·프록시 응답 — 버전을 모르는 것으로 본다
16550
+ return null;
16551
+ }
16552
+ }
16553
+ /** 물어볼 주소. 레지스트리 끝의 `/` 가 있든 없든 같은 주소가 나오게 한다 */
16554
+ function latestUrl(registry, pkg = PACKAGE_NAME) {
16555
+ const base = (registry || DEFAULT_REGISTRY).replace(/\/+$/, '');
16556
+ return `${base}/${encodeURIComponent(pkg)}/latest`;
16557
+ }
16558
+ /** 지금 확인할 때가 됐나. `lastCheckAt` 이 미래면(시계가 뒤로 갔다) 확인한다 */
16559
+ function shouldCheck(lastCheckAt, now, intervalHours) {
16560
+ const last = Number(lastCheckAt) || 0;
16561
+ if (last > now) {
16562
+ return true;
16563
+ }
16564
+ const interval = Math.max(0, Number(intervalHours) || 0) * 3600000;
16565
+ return now - last >= interval;
16566
+ }
16567
+ /**
16568
+ * 이 설치본을 자동으로 갈아끼워도 되나.
16569
+ *
16570
+ * **개발 설치(소스 트리로의 junction)에는 절대 하면 안 된다.** Tabby 플러그인 폴더의
16571
+ * `tabby-agentdeck` 은 개발 중에는 `D:\Project\tabby-agentdeck` 을 가리키는 링크이고
16572
+ * (`install.ps1`), 거기에 `npm install` 이 돌면 **작업 중인 소스 트리를 덮어쓴다.**
16573
+ *
16574
+ * 판정은 두 가지를 본다 — 둘 다 배포본에는 없는 것들이다
16575
+ * (`package.json` 의 `files` 는 `dist`·`hooks`·`install.ps1` 뿐이다):
16576
+ * - `src/` 나 `webpack.config.js` 가 있다 = 소스 트리
16577
+ * - `.git` 이 있다 = 저장소
16578
+ * 하나라도 걸리면 확인만 하고 설치는 하지 않는다.
16579
+ */
16580
+ function canSelfUpdate(marks) {
16581
+ return !(marks.hasSrc || marks.hasGit || marks.hasWebpackConfig || marks.isLink);
16582
+ }
16583
+ /**
16584
+ * 확인 결과로 무엇을 할지 — 순수 결정.
16585
+ *
16586
+ * 설치 성공/실패는 이 함수가 모르므로(`installPlugin` 이 비동기다) 여기서는
16587
+ * `update-ready` 직전까지만 정하고, 실제 설치는 서비스가 하고 실패하면 `failed` 로 덮는다.
16588
+ */
16589
+ function decide(input) {
16590
+ if (!input.enabled) {
16591
+ return 'disabled';
16592
+ }
16593
+ if (!shouldCheck(input.lastCheckAt, input.now, input.intervalHours)) {
16594
+ return 'too-soon';
16595
+ }
16596
+ if (input.latest === null) {
16597
+ return 'unknown';
16598
+ }
16599
+ if (!isNewer(input.current, input.latest)) {
16600
+ return 'up-to-date';
16601
+ }
16602
+ // 새 버전이 있는데 개발 설치다 — 알려만 주고 손대지 않는다
16603
+ return input.devInstall ? 'dev-install' : 'update-ready';
16604
+ }
16605
+
16606
+
14656
16607
  /***/ },
14657
16608
 
14658
16609
  /***/ "./src/viewPanel.ts"
@@ -15281,22 +17232,37 @@ class ViewPanel {
15281
17232
  }
15282
17233
  });
15283
17234
  }
15284
- /** 터미널과 닿는 쪽 경계선을 끌어 폭을 바꾼다 (사이드바의 dock.ts 와 같은 조작) */
17235
+ /**
17236
+ * 터미널과 닿는 쪽 경계선을 끌어 폭을 바꾼다.
17237
+ *
17238
+ * **폭은 "잡은 순간의 폭 + 끈 거리" 로 잰다 — 창 가장자리부터 재면 안 된다.**
17239
+ * 사이드바와 패널이 같은 쪽에 붙으면 패널은 창 가장자리에서 사이드바 폭만큼 안쪽에 앉는데
17240
+ * (`deck.service.ts` relayout 의 `outerOffset`), `rect.right - clientX` 는 그 몫까지
17241
+ * 폭으로 세어 버린다. 그래서 잡자마자 사이드바 폭(200~560px)만큼 한 번 커지고, 사용자는
17242
+ * 그걸 도로 줄이는 것으로 조작을 시작하게 된다 (2026-09-11 유저 지적).
17243
+ * 델타 방식은 그 몫도, 손잡이 안에서 어디를 잡았는지도 상쇄되어 **튐이 아예 없다.**
17244
+ * (사이드바 `dock.ts` 가 절대 거리로 재는 것은 거기서만 맞다 — 사이드바는 창에 딱 붙는다.)
17245
+ */
15285
17246
  installResize(handle) {
15286
17247
  handle.addEventListener('mousedown', (e) => {
17248
+ var _a;
15287
17249
  e.preventDefault();
15288
17250
  e.stopPropagation();
15289
17251
  this.resizing = true;
15290
17252
  document.body.classList.add('ad-resizing');
17253
+ const startX = e.clientX;
17254
+ // 기준은 **화면에 실제로 그려져 있는 폭**이다. 설정값(`getWidth`)을 쓰면 좁은 창에서
17255
+ // 또 튄다 — `extent()` 가 `room` 으로 깎아 그리는데 설정에는 안 깎인 값이 남아 있어서다.
17256
+ const startW = ((_a = this.el) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || this.host.getWidth();
15291
17257
  const move = (ev) => {
15292
17258
  if (!this.resizing) {
15293
17259
  return;
15294
17260
  }
15295
- const rect = this.host.windowEl.getBoundingClientRect();
15296
- const px = this.host.getSide() === 'right'
15297
- ? rect.right - ev.clientX
15298
- : ev.clientX - rect.left;
15299
- this.host.setWidth(Math.max(MIN_VIEW_W, Math.round(px)));
17261
+ // 오른쪽 패널은 커서가 왼쪽으로 갈수록 넓어지고, 왼쪽 패널은 그 반대다
17262
+ const delta = this.host.getSide() === 'right'
17263
+ ? startX - ev.clientX
17264
+ : ev.clientX - startX;
17265
+ this.host.setWidth(Math.max(MIN_VIEW_W, Math.round(startW + delta)));
15300
17266
  this.host.relayout();
15301
17267
  };
15302
17268
  const up = () => {
@@ -17266,6 +19232,26 @@ function planFollow(mode, opened, editing) {
17266
19232
  }
17267
19233
 
17268
19234
 
19235
+ /***/ },
19236
+
19237
+ /***/ "http"
19238
+ /*!***********************!*\
19239
+ !*** external "http" ***!
19240
+ \***********************/
19241
+ (module) {
19242
+
19243
+ module.exports = require("http");
19244
+
19245
+ /***/ },
19246
+
19247
+ /***/ "https"
19248
+ /*!************************!*\
19249
+ !*** external "https" ***!
19250
+ \************************/
19251
+ (module) {
19252
+
19253
+ module.exports = require("https");
19254
+
17269
19255
  /***/ },
17270
19256
 
17271
19257
  /***/ "net"
@@ -17485,7 +19471,7 @@ let __webpack_exports__ = {};
17485
19471
  \**********************/
17486
19472
  __webpack_require__.r(__webpack_exports__);
17487
19473
  /* harmony export */ __webpack_require__.d(__webpack_exports__, {
17488
- /* harmony export */ STATUS_STYLES: () => (/* reexport safe */ _api__WEBPACK_IMPORTED_MODULE_17__.STATUS_STYLES),
19474
+ /* harmony export */ STATUS_STYLES: () => (/* reexport safe */ _api__WEBPACK_IMPORTED_MODULE_18__.STATUS_STYLES),
17489
19475
  /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
17490
19476
  /* harmony export */ });
17491
19477
  /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
@@ -17508,10 +19494,11 @@ __webpack_require__.r(__webpack_exports__);
17508
19494
  /* harmony import */ var _notify_service__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./notify.service */ "./src/notify.service.ts");
17509
19495
  /* harmony import */ var _alert_service__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./alert.service */ "./src/alert.service.ts");
17510
19496
  /* harmony import */ var _recovery_service__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./recovery.service */ "./src/recovery.service.ts");
17511
- /* harmony import */ var _settings__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./settings */ "./src/settings.ts");
17512
- /* harmony import */ var _settings_component__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./settings.component */ "./src/settings.component.ts");
17513
- /* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./styles.scss */ "./src/styles.scss");
17514
- /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./api */ "./src/api.ts");
19497
+ /* harmony import */ var _update_service__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./update.service */ "./src/update.service.ts");
19498
+ /* harmony import */ var _settings__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./settings */ "./src/settings.ts");
19499
+ /* harmony import */ var _settings_component__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./settings.component */ "./src/settings.component.ts");
19500
+ /* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./styles.scss */ "./src/styles.scss");
19501
+ /* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./api */ "./src/api.ts");
17515
19502
  var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
17516
19503
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
17517
19504
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -17539,15 +19526,18 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
17539
19526
 
17540
19527
 
17541
19528
 
19529
+
17542
19530
  let AgentDeckModule = class AgentDeckModule {
17543
- constructor(deck, profiles, notify, alert, recovery) {
17544
- // 다섯 서비스 모두 app.ready$ 를 기다렸다가 스스로 붙는다.
19531
+ constructor(deck, profiles, notify, alert, recovery, update) {
19532
+ // 여섯 서비스 모두 app.ready$ 를 기다렸다가 스스로 붙는다.
17545
19533
  // alert 는 deck 이 만드는 `__agentdeck` 에 붙으므로 deck 뒤에 둔다
17546
19534
  deck.init();
17547
19535
  profiles.init();
17548
19536
  notify.init();
17549
19537
  alert.init();
17550
19538
  recovery.init();
19539
+ // 자동 업데이트는 기동 20초 뒤에 한 번 움직인다 — 창이 뜨는 순간에 네트워크를 건드리지 않는다
19540
+ update.init();
17551
19541
  }
17552
19542
  };
17553
19543
  AgentDeckModule = __decorate([
@@ -17562,17 +19552,18 @@ AgentDeckModule = __decorate([
17562
19552
  { provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.ConfigProvider, useClass: _config__WEBPACK_IMPORTED_MODULE_6__.AgentDeckConfigProvider, multi: true },
17563
19553
  { provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.HotkeyProvider, useClass: _hotkeys__WEBPACK_IMPORTED_MODULE_7__.AgentDeckHotkeyProvider, multi: true },
17564
19554
  { provide: tabby_terminal__WEBPACK_IMPORTED_MODULE_5__.TerminalDecorator, useClass: _decorator__WEBPACK_IMPORTED_MODULE_8__.AgentDeckDecorator, multi: true },
17565
- { provide: tabby_settings__WEBPACK_IMPORTED_MODULE_4__.SettingsTabProvider, useClass: _settings__WEBPACK_IMPORTED_MODULE_14__.AgentDeckSettingsTabProvider, multi: true },
19555
+ { provide: tabby_settings__WEBPACK_IMPORTED_MODULE_4__.SettingsTabProvider, useClass: _settings__WEBPACK_IMPORTED_MODULE_15__.AgentDeckSettingsTabProvider, multi: true },
17566
19556
  ],
17567
19557
  declarations: [
17568
- _settings_component__WEBPACK_IMPORTED_MODULE_15__.AgentDeckSettingsTabComponent,
19558
+ _settings_component__WEBPACK_IMPORTED_MODULE_16__.AgentDeckSettingsTabComponent,
17569
19559
  ],
17570
19560
  }),
17571
19561
  __metadata("design:paramtypes", [_deck_service__WEBPACK_IMPORTED_MODULE_9__.AgentDeckService,
17572
19562
  _profile_service__WEBPACK_IMPORTED_MODULE_10__.AgentDeckProfileService,
17573
19563
  _notify_service__WEBPACK_IMPORTED_MODULE_11__.WorkNotifyService,
17574
19564
  _alert_service__WEBPACK_IMPORTED_MODULE_12__.AgentDeckAlertService,
17575
- _recovery_service__WEBPACK_IMPORTED_MODULE_13__.AgentDeckRecoveryService])
19565
+ _recovery_service__WEBPACK_IMPORTED_MODULE_13__.AgentDeckRecoveryService,
19566
+ _update_service__WEBPACK_IMPORTED_MODULE_14__.AgentDeckUpdateService])
17576
19567
  ], AgentDeckModule);
17577
19568
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (AgentDeckModule);
17578
19569