tabby-agentdeck 0.2.1 → 0.3.0
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/README.md +193 -178
- package/dist/alert.service.d.ts +87 -0
- package/dist/api.d.ts +16 -2
- package/dist/claudeHooks.d.ts +2 -1
- package/dist/config.d.ts +47 -0
- package/dist/deck.service.d.ts +58 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1225 -50
- package/dist/index.js.map +1 -1
- package/dist/notify.service.d.ts +66 -8
- package/dist/order.d.ts +26 -0
- package/dist/reason.d.ts +16 -0
- package/dist/recovery.service.d.ts +32 -0
- package/dist/settings.component.d.ts +4 -2
- package/dist/status.service.d.ts +14 -2
- package/dist/tabenv.d.ts +26 -0
- package/hooks/agentdeck-notify.ps1 +76 -17
- package/hooks/agentdeck-status.ps1 +2 -2
- package/install.ps1 +2 -31
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -124,10 +124,22 @@ ___CSS_LOADER_EXPORT___.push([module.id, `body.agentdeck-active.ad-bg-clip .cont
|
|
|
124
124
|
}
|
|
125
125
|
#agentdeck-sidebar .ad-head-count {
|
|
126
126
|
-webkit-app-region: no-drag;
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
gap: 6px;
|
|
130
|
+
flex: 0 0 auto;
|
|
131
|
+
white-space: nowrap;
|
|
132
|
+
letter-spacing: 0;
|
|
133
|
+
color: rgba(255, 255, 255, 0.55);
|
|
134
|
+
}
|
|
135
|
+
#agentdeck-sidebar .ad-cnt {
|
|
127
136
|
padding: 1px 6px;
|
|
128
137
|
border-radius: 8px;
|
|
129
138
|
background: rgba(255, 255, 255, 0.08);
|
|
130
|
-
color: rgba(255, 255, 255, 0.55);
|
|
139
|
+
color: var(--ad-color, rgba(255, 255, 255, 0.55));
|
|
140
|
+
font-size: 10px;
|
|
141
|
+
line-height: 1.3;
|
|
142
|
+
font-variant-numeric: tabular-nums;
|
|
131
143
|
}
|
|
132
144
|
#agentdeck-sidebar .ad-list {
|
|
133
145
|
flex: 1 1 auto;
|
|
@@ -195,7 +207,12 @@ ___CSS_LOADER_EXPORT___.push([module.id, `body.agentdeck-active.ad-bg-clip .cont
|
|
|
195
207
|
color: rgba(255, 255, 255, 0.45);
|
|
196
208
|
}
|
|
197
209
|
#agentdeck-sidebar .ad-badge {
|
|
198
|
-
flex: 0
|
|
210
|
+
flex: 0 1 auto;
|
|
211
|
+
min-width: 0;
|
|
212
|
+
max-width: 100%;
|
|
213
|
+
overflow: hidden;
|
|
214
|
+
text-overflow: ellipsis;
|
|
215
|
+
white-space: nowrap;
|
|
199
216
|
color: var(--ad-color, #6b727d);
|
|
200
217
|
font-weight: 600;
|
|
201
218
|
}
|
|
@@ -378,7 +395,7 @@ body.agentdeck-active .window {
|
|
|
378
395
|
flex-direction: row;
|
|
379
396
|
position: relative;
|
|
380
397
|
background: var(--ad-panel-bg, var(--bs-body-bg, #1e2127));
|
|
381
|
-
}`, "",{"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 .ad-tab {\r\n flex: 0 0 220px;\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 .ad-head-count {\r\n -webkit-app-region: no-drag;\r\n padding: 1px 6px;\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 }\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\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 .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 flex: 0 0 auto;\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 // 두 번째 줄 — 마지막으로 보낸 프롬프트\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 flex: 0 0 auto;\r\n min-width: 28px;\r\n font-size: 13px;\r\n line-height: 1;\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// 경계선을 끄는 동안에는 터미널이 커서를 뺏지 않게 한다\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"],"sourceRoot":""}]);
|
|
398
|
+
}`, "",{"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 .ad-tab {\r\n flex: 0 0 220px;\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\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 .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 // 두 번째 줄 — 마지막으로 보낸 프롬프트\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 flex: 0 0 auto;\r\n min-width: 28px;\r\n font-size: 13px;\r\n line-height: 1;\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// 경계선을 끄는 동안에는 터미널이 커서를 뺏지 않게 한다\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"],"sourceRoot":""}]);
|
|
382
399
|
// Exports
|
|
383
400
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
|
|
384
401
|
|
|
@@ -823,6 +840,424 @@ function styleTagTransform(css, styleElement) {
|
|
|
823
840
|
}
|
|
824
841
|
module.exports = styleTagTransform;
|
|
825
842
|
|
|
843
|
+
/***/ },
|
|
844
|
+
|
|
845
|
+
/***/ "./src/alert.service.ts"
|
|
846
|
+
/*!******************************!*\
|
|
847
|
+
!*** ./src/alert.service.ts ***!
|
|
848
|
+
\******************************/
|
|
849
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
850
|
+
|
|
851
|
+
__webpack_require__.r(__webpack_exports__);
|
|
852
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
853
|
+
/* harmony export */ AgentDeckAlertService: () => (/* binding */ AgentDeckAlertService)
|
|
854
|
+
/* harmony export */ });
|
|
855
|
+
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
|
|
856
|
+
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
|
|
857
|
+
/* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
|
|
858
|
+
/* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
|
|
859
|
+
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! fs */ "fs");
|
|
860
|
+
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
|
|
861
|
+
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! os */ "os");
|
|
862
|
+
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_3__);
|
|
863
|
+
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! path */ "path");
|
|
864
|
+
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_4__);
|
|
865
|
+
/* harmony import */ var _status_service__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./status.service */ "./src/status.service.ts");
|
|
866
|
+
/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./api */ "./src/api.ts");
|
|
867
|
+
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
868
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
869
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
870
|
+
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;
|
|
871
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
872
|
+
};
|
|
873
|
+
var __metadata = (undefined && undefined.__metadata) || function (k, v) {
|
|
874
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
const DIAG_PATH = path__WEBPACK_IMPORTED_MODULE_4__.join(os__WEBPACK_IMPORTED_MODULE_3__.homedir(), '.agentdeck-diag.log');
|
|
884
|
+
/** 창 밖으로 알려야 하는 상태 — 사람이 손을 대야 진행되는 것들 */
|
|
885
|
+
/** 창이 뒤에 있을 때 알릴 상태 — 사람이 봐야 하는 것: 승인 / 오류 / 한도에 걸려 멈춤 */
|
|
886
|
+
const ALERT_STATUSES = ['waiting', 'error', 'limited'];
|
|
887
|
+
/**
|
|
888
|
+
* 창이 뒤로 가 있을 때의 OS 알림.
|
|
889
|
+
*
|
|
890
|
+
* 사이드바는 창이 보일 때만 의미가 있다. 에이전트 탭을 여섯 개쯤 굴리면서 Slack·VS 를 보고
|
|
891
|
+
* 있으면 어느 탭이 `승인대기` 로 멈춰 있어도 알 길이 없어서, 돌아와 보면 몇 분씩 놀고 있던
|
|
892
|
+
* 일이 잦았다. 그래서 상태가 **`waiting` / `error` 로 넘어가는 순간** 창이 포커스를 잃은
|
|
893
|
+
* 상태면 OS 쪽에 세 가지로 알린다.
|
|
894
|
+
*
|
|
895
|
+
* 1. **작업표시줄 깜빡임** (`flashFrame`) — 창이 포커스를 받으면 끈다. `alertFlash`
|
|
896
|
+
* 2. **작업표시줄 뱃지** — 지금 `waiting`+`error` 인 탭 수를 아이콘 위에 얹는다. Windows 에는
|
|
897
|
+
* `setBadgeCount` 가 없어서 `setOverlayIcon` 에 캔버스로 그린 원+숫자를 넣는다.
|
|
898
|
+
* 전이 순간만이 아니라 상태가 바뀔 때마다 갱신해 실제 개수를 따라간다. `alertBadge`
|
|
899
|
+
* 3. **OS 토스트** (`Notification`) — 기본은 끔. 누르면 창을 앞으로 가져오고 그 탭을 고른다. `alertToast`
|
|
900
|
+
*
|
|
901
|
+
* 창이 앞에 있을 때는 아무것도 하지 않는다 — 사이드바가 이미 보이고 있다.
|
|
902
|
+
* Electron 을 만지는 모든 곳은 try/catch 로 감싼다. 알림이 안 되는 것은 불편이지만
|
|
903
|
+
* 예외가 새어 나가 다른 배선을 죽이는 것은 사고다.
|
|
904
|
+
*/
|
|
905
|
+
let AgentDeckAlertService = class AgentDeckAlertService {
|
|
906
|
+
constructor(app, config, zone, status) {
|
|
907
|
+
this.app = app;
|
|
908
|
+
this.config = config;
|
|
909
|
+
this.zone = zone;
|
|
910
|
+
this.status = status;
|
|
911
|
+
/** 탭별로 마지막에 본 상태 — "전이" 를 판정하려고 들고 있는다 */
|
|
912
|
+
this.last = new WeakMap();
|
|
913
|
+
/** flashFrame(true) 를 걸어 둔 상태인가 — 포커스가 오면 끈다 */
|
|
914
|
+
this.flashing = false;
|
|
915
|
+
/** 마지막으로 그린 뱃지 숫자 (0 = 안 그림). 같은 숫자면 다시 그리지 않는다 */
|
|
916
|
+
this.badgeShown = 0;
|
|
917
|
+
/** 뱃지 그림 캐시 — key 는 `${count}:${color}` */
|
|
918
|
+
this.badgeCache = new Map();
|
|
919
|
+
/**
|
|
920
|
+
* 진단/검증용 카운터 — CDP 에서 `__agentdeck.alert` 로 읽는다.
|
|
921
|
+
* `forceUnfocused` 를 true 로 두면 포커스 판정을 건너뛰고 "창이 뒤에 있다" 로 친다
|
|
922
|
+
* (테스트 인스턴스가 앞에 떠 있는 상태에서 알림 경로를 밟아 보려고)
|
|
923
|
+
*/
|
|
924
|
+
this.debug = {
|
|
925
|
+
flashes: 0,
|
|
926
|
+
badgeCount: 0,
|
|
927
|
+
badgeDraws: 0,
|
|
928
|
+
toasts: 0,
|
|
929
|
+
transitions: 0,
|
|
930
|
+
lastFocused: null,
|
|
931
|
+
forceUnfocused: false,
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
init() {
|
|
935
|
+
this.app.ready$.subscribe(() => {
|
|
936
|
+
var _a;
|
|
937
|
+
try {
|
|
938
|
+
this.setup();
|
|
939
|
+
}
|
|
940
|
+
catch (e) {
|
|
941
|
+
this.diag(`alert setup ERROR ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
setup() {
|
|
946
|
+
var _a;
|
|
947
|
+
// 기동 시점의 상태를 기준선으로 삼는다 — 이미 waiting 인 탭은 "전이" 가 아니다
|
|
948
|
+
for (const tab of this.app.tabs) {
|
|
949
|
+
this.last.set(tab, this.status.get(tab).status);
|
|
950
|
+
}
|
|
951
|
+
this.status.changed$.subscribe(() => {
|
|
952
|
+
var _a;
|
|
953
|
+
try {
|
|
954
|
+
this.onChanged();
|
|
955
|
+
}
|
|
956
|
+
catch (e) {
|
|
957
|
+
this.diag(`alert onChanged ERROR ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
// 닫힌 탭은 개수에서 빠져야 한다 — status.changed$ 는 탭 닫힘에 안 울린다
|
|
961
|
+
this.app.tabClosed$.subscribe(tab => {
|
|
962
|
+
var _a;
|
|
963
|
+
this.last.delete(tab);
|
|
964
|
+
try {
|
|
965
|
+
this.updateBadge(this.countAlerting());
|
|
966
|
+
}
|
|
967
|
+
catch (e) {
|
|
968
|
+
this.diag(`alert tabClosed ERROR ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
// 사람이 돌아왔다 — 깜빡임은 여기서 끝난다. 뱃지는 상태가 풀릴 때 따로 지워진다
|
|
972
|
+
window.addEventListener('focus', () => this.stopFlash());
|
|
973
|
+
// 개발/진단용 — deck.service 가 만든 __agentdeck 에 붙는다 (없으면 만든다)
|
|
974
|
+
const g = window;
|
|
975
|
+
g.__agentdeck = (_a = g.__agentdeck) !== null && _a !== void 0 ? _a : {};
|
|
976
|
+
g.__agentdeck.alert = this.debug;
|
|
977
|
+
this.diag(`alert ready tabs=${this.app.tabs.length}`);
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* 상태가 바뀔 때마다 — 뱃지는 항상 실제 개수로 맞추고,
|
|
981
|
+
* 알림 상태로 **새로 들어온** 탭이 있고 창이 뒤에 있으면 깜빡임/토스트를 낸다
|
|
982
|
+
*/
|
|
983
|
+
onChanged() {
|
|
984
|
+
const entered = [];
|
|
985
|
+
let count = 0;
|
|
986
|
+
for (const tab of this.app.tabs) {
|
|
987
|
+
const now = this.status.get(tab).status;
|
|
988
|
+
const prev = this.last.get(tab);
|
|
989
|
+
this.last.set(tab, now);
|
|
990
|
+
if (!ALERT_STATUSES.includes(now)) {
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
count++;
|
|
994
|
+
// waiting -> error 도 전이로 친다 — 성격이 다른 일이 새로 생긴 것이다
|
|
995
|
+
if (prev !== now) {
|
|
996
|
+
entered.push(tab);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
this.updateBadge(count);
|
|
1000
|
+
if (entered.length === 0) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
this.debug.transitions += entered.length;
|
|
1004
|
+
const focused = this.isFocused();
|
|
1005
|
+
this.debug.lastFocused = focused;
|
|
1006
|
+
if (focused) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
this.diag(`alert enter=${entered.length} count=${count} focused=${focused}`);
|
|
1010
|
+
if (this.config.store.agentDeck.alertFlash) {
|
|
1011
|
+
this.flash();
|
|
1012
|
+
}
|
|
1013
|
+
if (this.config.store.agentDeck.alertToast) {
|
|
1014
|
+
for (const tab of entered) {
|
|
1015
|
+
this.toast(tab);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
countAlerting() {
|
|
1020
|
+
let n = 0;
|
|
1021
|
+
for (const tab of this.app.tabs) {
|
|
1022
|
+
if (ALERT_STATUSES.includes(this.status.get(tab).status)) {
|
|
1023
|
+
n++;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return n;
|
|
1027
|
+
}
|
|
1028
|
+
// ---------- Electron 접근 ----------
|
|
1029
|
+
/** `@electron/remote` 의 현재 창. 못 잡으면 null — 호출부는 전부 null 을 감안한다 */
|
|
1030
|
+
win() {
|
|
1031
|
+
var _a, _b, _c, _d;
|
|
1032
|
+
try {
|
|
1033
|
+
const req = (_a = window.require) !== null && _a !== void 0 ? _a : globalThis.require;
|
|
1034
|
+
return (_d = (_c = (_b = req === null || req === void 0 ? void 0 : req('@electron/remote')) === null || _b === void 0 ? void 0 : _b.getCurrentWindow) === null || _c === void 0 ? void 0 : _c.call(_b)) !== null && _d !== void 0 ? _d : null;
|
|
1035
|
+
}
|
|
1036
|
+
catch {
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
nativeImage() {
|
|
1041
|
+
var _a, _b, _c;
|
|
1042
|
+
try {
|
|
1043
|
+
const req = (_a = window.require) !== null && _a !== void 0 ? _a : globalThis.require;
|
|
1044
|
+
return (_c = (_b = req === null || req === void 0 ? void 0 : req('electron')) === null || _b === void 0 ? void 0 : _b.nativeImage) !== null && _c !== void 0 ? _c : null;
|
|
1045
|
+
}
|
|
1046
|
+
catch {
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* 창이 앞에 있나. Electron 의 판정을 우선하고(devtools 를 따로 띄워도 본창 기준으로 본다),
|
|
1052
|
+
* remote 를 못 잡으면 DOM 의 `document.hasFocus()` 로 대신한다
|
|
1053
|
+
*/
|
|
1054
|
+
isFocused() {
|
|
1055
|
+
if (this.debug.forceUnfocused) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
try {
|
|
1059
|
+
const win = this.win();
|
|
1060
|
+
if (win === null || win === void 0 ? void 0 : win.isFocused) {
|
|
1061
|
+
return !!win.isFocused();
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
catch {
|
|
1065
|
+
// 아래 DOM 판정으로
|
|
1066
|
+
}
|
|
1067
|
+
return document.hasFocus();
|
|
1068
|
+
}
|
|
1069
|
+
// ---------- 깜빡임 ----------
|
|
1070
|
+
flash() {
|
|
1071
|
+
var _a;
|
|
1072
|
+
try {
|
|
1073
|
+
const win = this.win();
|
|
1074
|
+
if (!(win === null || win === void 0 ? void 0 : win.flashFrame)) {
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
win.flashFrame(true);
|
|
1078
|
+
this.flashing = true;
|
|
1079
|
+
this.debug.flashes++;
|
|
1080
|
+
}
|
|
1081
|
+
catch (e) {
|
|
1082
|
+
this.diag(`alert flash ERROR ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
stopFlash() {
|
|
1086
|
+
var _a, _b;
|
|
1087
|
+
if (!this.flashing) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
this.flashing = false;
|
|
1091
|
+
try {
|
|
1092
|
+
(_b = (_a = this.win()) === null || _a === void 0 ? void 0 : _a.flashFrame) === null || _b === void 0 ? void 0 : _b.call(_a, false);
|
|
1093
|
+
}
|
|
1094
|
+
catch {
|
|
1095
|
+
// 포커스가 오면 Windows 가 알아서 멈추므로 실패해도 남는 건 없다
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
// ---------- 뱃지 ----------
|
|
1099
|
+
/**
|
|
1100
|
+
* 작업표시줄 아이콘 위의 숫자를 지금 개수로 맞춘다. 0 이면 지운다.
|
|
1101
|
+
* `alertBadge` 가 꺼져 있으면 그려 둔 것이 있을 때만 지우고 끝낸다
|
|
1102
|
+
*/
|
|
1103
|
+
updateBadge(count) {
|
|
1104
|
+
var _a;
|
|
1105
|
+
this.debug.badgeCount = count;
|
|
1106
|
+
const enabled = !!this.config.store.agentDeck.alertBadge;
|
|
1107
|
+
const want = enabled ? count : 0;
|
|
1108
|
+
if (want === this.badgeShown) {
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
try {
|
|
1112
|
+
const win = this.win();
|
|
1113
|
+
if (!(win === null || win === void 0 ? void 0 : win.setOverlayIcon)) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
if (want === 0) {
|
|
1117
|
+
win.setOverlayIcon(null, '');
|
|
1118
|
+
this.badgeShown = 0;
|
|
1119
|
+
// 개수가 0 으로 내려왔으면 깜빡일 이유도 사라졌다
|
|
1120
|
+
this.stopFlash();
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const img = this.badgeImage(want);
|
|
1124
|
+
if (!img) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
win.setOverlayIcon(img, `승인대기/오류 ${want}개`);
|
|
1128
|
+
this.badgeShown = want;
|
|
1129
|
+
this.debug.badgeDraws++;
|
|
1130
|
+
}
|
|
1131
|
+
catch (e) {
|
|
1132
|
+
this.diag(`alert badge ERROR ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* 원 위에 숫자를 얹은 오버레이 이미지. 오류 탭이 하나라도 있으면 오류색, 아니면 승인대기색.
|
|
1137
|
+
* Windows 오버레이는 16px 기준이라 16@1x + 32@2x 두 표현을 넣는다 (고배율에서 흐려지지 않게).
|
|
1138
|
+
* `addRepresentation` 을 못 쓰는 빌드면 32px 하나로 물러난다
|
|
1139
|
+
*/
|
|
1140
|
+
badgeImage(count) {
|
|
1141
|
+
var _a;
|
|
1142
|
+
const hasError = this.app.tabs.some(t => this.status.get(t).status === 'error');
|
|
1143
|
+
const color = hasError ? _api__WEBPACK_IMPORTED_MODULE_6__.STATUS_STYLES.error.color : _api__WEBPACK_IMPORTED_MODULE_6__.STATUS_STYLES.waiting.color;
|
|
1144
|
+
const key = `${count}:${color}`;
|
|
1145
|
+
const cached = this.badgeCache.get(key);
|
|
1146
|
+
if (cached) {
|
|
1147
|
+
return cached;
|
|
1148
|
+
}
|
|
1149
|
+
const nativeImage = this.nativeImage();
|
|
1150
|
+
if (!nativeImage) {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
const text = count > 9 ? '9+' : String(count);
|
|
1154
|
+
let img;
|
|
1155
|
+
try {
|
|
1156
|
+
img = nativeImage.createEmpty();
|
|
1157
|
+
img.addRepresentation({ scaleFactor: 1, width: 16, height: 16, dataURL: this.drawBadge(16, text, color) });
|
|
1158
|
+
img.addRepresentation({ scaleFactor: 2, width: 32, height: 32, dataURL: this.drawBadge(32, text, color) });
|
|
1159
|
+
if ((_a = img.isEmpty) === null || _a === void 0 ? void 0 : _a.call(img)) {
|
|
1160
|
+
throw new Error('empty after addRepresentation');
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
catch {
|
|
1164
|
+
img = nativeImage.createFromDataURL(this.drawBadge(32, text, color));
|
|
1165
|
+
}
|
|
1166
|
+
this.badgeCache.set(key, img);
|
|
1167
|
+
return img;
|
|
1168
|
+
}
|
|
1169
|
+
/** 오프스크린 canvas 에 원+숫자를 그려 data URL 로 돌려준다 */
|
|
1170
|
+
drawBadge(size, text, color) {
|
|
1171
|
+
const canvas = document.createElement('canvas');
|
|
1172
|
+
canvas.width = size;
|
|
1173
|
+
canvas.height = size;
|
|
1174
|
+
const ctx = canvas.getContext('2d');
|
|
1175
|
+
if (!ctx) {
|
|
1176
|
+
throw new Error('canvas 2d 미지원');
|
|
1177
|
+
}
|
|
1178
|
+
const r = size / 2;
|
|
1179
|
+
ctx.clearRect(0, 0, size, size);
|
|
1180
|
+
ctx.beginPath();
|
|
1181
|
+
ctx.arc(r, r, r - 0.5, 0, Math.PI * 2);
|
|
1182
|
+
ctx.fillStyle = color;
|
|
1183
|
+
ctx.fill();
|
|
1184
|
+
// 어두운 작업표시줄 위에서 원 테두리가 살게 얇은 흰 선
|
|
1185
|
+
ctx.lineWidth = Math.max(1, size / 16);
|
|
1186
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.85)';
|
|
1187
|
+
ctx.stroke();
|
|
1188
|
+
ctx.fillStyle = '#ffffff';
|
|
1189
|
+
ctx.font = `bold ${Math.round(size * (text.length > 1 ? 0.55 : 0.7))}px "Segoe UI", sans-serif`;
|
|
1190
|
+
ctx.textAlign = 'center';
|
|
1191
|
+
ctx.textBaseline = 'middle';
|
|
1192
|
+
// 글리프의 시각적 중심이 살짝 위로 치우쳐서 조금 내린다
|
|
1193
|
+
ctx.fillText(text, r, r + size * 0.04);
|
|
1194
|
+
return canvas.toDataURL('image/png');
|
|
1195
|
+
}
|
|
1196
|
+
// ---------- 토스트 ----------
|
|
1197
|
+
/** OS 토스트. 누르면 창을 앞으로 가져오고 그 탭을 고른다 */
|
|
1198
|
+
toast(tab) {
|
|
1199
|
+
var _a;
|
|
1200
|
+
try {
|
|
1201
|
+
if (typeof Notification === 'undefined') {
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const s = this.status.get(tab);
|
|
1205
|
+
const name = s.label || tab.title || '(제목 없음)';
|
|
1206
|
+
const style = _api__WEBPACK_IMPORTED_MODULE_6__.STATUS_STYLES[s.status];
|
|
1207
|
+
const n = new Notification('AgentDeck', {
|
|
1208
|
+
body: `${name} — ${style.label}`,
|
|
1209
|
+
// 같은 탭의 토스트는 하나로 겹친다 (waiting -> error 가 잇달아 와도 두 장이 쌓이지 않게)
|
|
1210
|
+
tag: `agentdeck-${this.tabKey(tab)}`,
|
|
1211
|
+
});
|
|
1212
|
+
this.debug.toasts++;
|
|
1213
|
+
n.onclick = () => {
|
|
1214
|
+
var _a, _b;
|
|
1215
|
+
try {
|
|
1216
|
+
const win = this.win();
|
|
1217
|
+
(_a = win === null || win === void 0 ? void 0 : win.show) === null || _a === void 0 ? void 0 : _a.call(win);
|
|
1218
|
+
(_b = win === null || win === void 0 ? void 0 : win.focus) === null || _b === void 0 ? void 0 : _b.call(win);
|
|
1219
|
+
}
|
|
1220
|
+
catch {
|
|
1221
|
+
// 창을 못 올려도 탭 선택은 해 둔다
|
|
1222
|
+
}
|
|
1223
|
+
this.zone.run(() => {
|
|
1224
|
+
if (this.app.tabs.includes(tab)) {
|
|
1225
|
+
this.app.selectTab(tab);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
n.close();
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
catch (e) {
|
|
1232
|
+
this.diag(`alert toast ERROR ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
/** 토스트 tag 용 탭 식별자 — 객체 자체는 문자열이 안 되니 인덱스로 대신한다 */
|
|
1236
|
+
tabKey(tab) {
|
|
1237
|
+
const i = this.app.tabs.indexOf(tab);
|
|
1238
|
+
return i >= 0 ? String(i) : 'x';
|
|
1239
|
+
}
|
|
1240
|
+
// ---------- 진단 ----------
|
|
1241
|
+
/** deck.service 와 같은 파일(`~/.agentdeck-diag.log`)에 남긴다. 실패해도 동작에는 영향 없다 */
|
|
1242
|
+
diag(line) {
|
|
1243
|
+
try {
|
|
1244
|
+
fs__WEBPACK_IMPORTED_MODULE_2__.appendFileSync(DIAG_PATH, `${new Date().toISOString()} ${line}\n`);
|
|
1245
|
+
}
|
|
1246
|
+
catch {
|
|
1247
|
+
console.debug('[agentdeck]', line);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
AgentDeckAlertService = __decorate([
|
|
1252
|
+
(0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)({ providedIn: 'root' }),
|
|
1253
|
+
__metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.AppService,
|
|
1254
|
+
tabby_core__WEBPACK_IMPORTED_MODULE_1__.ConfigService,
|
|
1255
|
+
_angular_core__WEBPACK_IMPORTED_MODULE_0__.NgZone,
|
|
1256
|
+
_status_service__WEBPACK_IMPORTED_MODULE_5__.WorkStatusService])
|
|
1257
|
+
], AgentDeckAlertService);
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
|
|
826
1261
|
/***/ },
|
|
827
1262
|
|
|
828
1263
|
/***/ "./src/api.ts"
|
|
@@ -839,6 +1274,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
839
1274
|
const STATUS_STYLES = {
|
|
840
1275
|
running: { icon: '●', label: '진행중', color: '#e5c07b' },
|
|
841
1276
|
waiting: { icon: '⏸', label: '승인대기', color: '#61afef' },
|
|
1277
|
+
limited: { icon: '⛔', label: '한도 도달', color: '#c678dd' },
|
|
842
1278
|
done: { icon: '✔', label: '완료', color: '#98c379' },
|
|
843
1279
|
error: { icon: '✖', label: '오류', color: '#e06c75' },
|
|
844
1280
|
idle: { icon: '○', label: '대기', color: '#6b727d' },
|
|
@@ -935,8 +1371,16 @@ const PLAN = [
|
|
|
935
1371
|
{ event: 'UserPromptSubmit', args: '-Status running' },
|
|
936
1372
|
// 승인을 기다린다
|
|
937
1373
|
{ event: 'Notification', args: '-Status waiting' },
|
|
1374
|
+
// 승인 뒤 도구가 돌았다 / 실패했다 / 사람이 거부했다 — 어느 쪽이든 에이전트는 다시 일하는 중이다.
|
|
1375
|
+
// 이 셋이 없으면 승인대기에서 내려올 훅이 Stop 밖에 없어 승인을 해 줘도 사이드바가 승인대기에
|
|
1376
|
+
// 박혀 있었다 (2026-09-07 실측). 스크립트는 이미 running 이면 아무것도 쓰지 않아 도구마다 도는 비용이 없다
|
|
1377
|
+
{ event: 'PostToolUse', args: '-Status running' },
|
|
1378
|
+
{ event: 'PostToolUseFailure', args: '-Status running' },
|
|
1379
|
+
{ event: 'PermissionDenied', args: '-Status running' },
|
|
938
1380
|
// 답이 끝났다
|
|
939
1381
|
{ event: 'Stop', args: '-Status done' },
|
|
1382
|
+
// 턴이 실패로 끝났다 — 스크립트가 `error` 를 보고 rate_limit(사용량 한도)이면 limited 로 바꿔 보낸다
|
|
1383
|
+
{ event: 'StopFailure', args: '-Status error' },
|
|
940
1384
|
];
|
|
941
1385
|
function claudeSettingsPath() {
|
|
942
1386
|
return path__WEBPACK_IMPORTED_MODULE_2__.join(os__WEBPACK_IMPORTED_MODULE_1__.homedir(), '.claude', 'settings.json');
|
|
@@ -965,7 +1409,8 @@ function withoutOurs(entries) {
|
|
|
965
1409
|
.filter(e => !JSON.stringify(e !== null && e !== void 0 ? e : '').includes(MARK));
|
|
966
1410
|
}
|
|
967
1411
|
/**
|
|
968
|
-
* 우리 훅이
|
|
1412
|
+
* 우리 훅이 PLAN 의 모든 이벤트에 걸려 있나 — 파일을 읽지 않는 순수 판정.
|
|
1413
|
+
* 이벤트가 늘어난 버전으로 올라오면 옛 설치는 "설치 안 됨" 으로 보인다 — 설정 창에서 설치를 다시 누르면 된다.
|
|
969
1414
|
* 파일 접근과 분리해 둔 이유는 이 판단을 테스트로 묶어 두기 위해서다.
|
|
970
1415
|
*/
|
|
971
1416
|
function isMerged(settings) {
|
|
@@ -1118,12 +1563,26 @@ class AgentDeckConfigProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_0__.Co
|
|
|
1118
1563
|
* 설정 창의 설치 버튼은 이 값과 무관하게 언제든 쓸 수 있다.
|
|
1119
1564
|
*/
|
|
1120
1565
|
hookPromptDismissed: false,
|
|
1566
|
+
/**
|
|
1567
|
+
* 기동 시 지난 탭을 복원하지 않는다 (Tabby `recoverTabs` 를 false 로 강제).
|
|
1568
|
+
*
|
|
1569
|
+
* 복원되는 건 셸뿐이고 그 안의 Claude Code 세션은 이미 죽어 있어 의미가 없다.
|
|
1570
|
+
* 켜면 항상 빈 상태(기본 프로필 새 탭)로 시작하고, 끄면 Tabby 순정 동작으로 돌아간다.
|
|
1571
|
+
* 설정 창에서 토글한다 (recovery.service.ts).
|
|
1572
|
+
*/
|
|
1573
|
+
noTabRecovery: false,
|
|
1121
1574
|
/** PTY 출력이 이 시간(ms) 이상 없으면 running -> idle */
|
|
1122
1575
|
idleAfterMs: 2500,
|
|
1123
1576
|
/** 출력 패턴으로 상태를 자동 추론할지 */
|
|
1124
1577
|
autoDetect: true,
|
|
1125
1578
|
/** 사이드바에 경과 시간 표시 */
|
|
1126
1579
|
showElapsed: true,
|
|
1580
|
+
/**
|
|
1581
|
+
* 사이드바 탭을 상태 우선순위(waiting → error → running → done → idle)로 정렬한다.
|
|
1582
|
+
* 같은 상태끼리는 탭 순서를 지킨다. 기본은 끔 — 순정 탭바와 같은 탭 순서로 보인다.
|
|
1583
|
+
* 헤더의 상태별 집계는 이 값과 무관하게 항상 보인다.
|
|
1584
|
+
*/
|
|
1585
|
+
sortByStatus: false,
|
|
1127
1586
|
/**
|
|
1128
1587
|
* 창이 가려져 있어도 사이드바를 계속 갱신한다 (Electron backgroundThrottling 해제).
|
|
1129
1588
|
*
|
|
@@ -1152,6 +1611,22 @@ class AgentDeckConfigProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_0__.Co
|
|
|
1152
1611
|
* false 로 두면 터미널 앱에 리터럴 Ctrl+V(0x16, quoted-insert)를 보낼 수 있다
|
|
1153
1612
|
*/
|
|
1154
1613
|
claimCtrlV: true,
|
|
1614
|
+
/**
|
|
1615
|
+
* Shift+Enter / Ctrl+Enter 를 캡처 단계에서 직접 줄바꿈으로 처리한다.
|
|
1616
|
+
*
|
|
1617
|
+
* 핫키(agentdeck-newline)에만 맡기면 한글 조합 중에 눌렀을 때 Shift 가 실리지
|
|
1618
|
+
* 않은 Enter 로 도착해 전송돼 버린다 (deck.service.ts `claimShiftEnterKey` 주석).
|
|
1619
|
+
* false 로 두면 예전처럼 핫키 경로만 쓴다.
|
|
1620
|
+
*/
|
|
1621
|
+
claimShiftEnter: true,
|
|
1622
|
+
/**
|
|
1623
|
+
* Home / End 를 Tabby 핫키에서 걷어내 xterm 순정 경로로 돌려준다.
|
|
1624
|
+
*
|
|
1625
|
+
* 핫키로 두면 한글 조합 중에 눌렀을 때 조합 음절이 커서를 따라간다
|
|
1626
|
+
* (deck.service.ts `releaseHomeEndHotkey` 주석에 원인). 방향키가 멀쩡한 이유도 같다 —
|
|
1627
|
+
* 방향키는 핫키가 아니라서 xterm 이 조합을 먼저 확정하고 키를 보낸다.
|
|
1628
|
+
*/
|
|
1629
|
+
releaseHomeEnd: true,
|
|
1155
1630
|
/**
|
|
1156
1631
|
* 클립보드에 이미지만 있으면 Ctrl+V 로도 이미지가 들어가게 한다.
|
|
1157
1632
|
*
|
|
@@ -1337,6 +1812,23 @@ class AgentDeckConfigProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_0__.Co
|
|
|
1337
1812
|
},
|
|
1338
1813
|
/** 기본 프로필 지정을 이미 했는지 — 사용자 선택을 매번 덮어쓰지 않기 위한 플래그 */
|
|
1339
1814
|
rootProfileClaimed: false,
|
|
1815
|
+
/**
|
|
1816
|
+
* 창이 뒤에 있을 때(포커스 없음) 탭이 `승인대기`/`오류` 로 넘어가면 OS 쪽에 알린다.
|
|
1817
|
+
* 창이 앞에 있으면 세 항목 모두 아무 일도 하지 않는다 — 사이드바가 이미 보이고 있다.
|
|
1818
|
+
* 작업표시줄 아이콘을 깜빡인다(`flashFrame`). 창이 포커스를 받으면 멈춘다
|
|
1819
|
+
*/
|
|
1820
|
+
alertFlash: true,
|
|
1821
|
+
/**
|
|
1822
|
+
* 작업표시줄 아이콘 위에 `승인대기`+`오류` 탭 개수를 뱃지로 얹는다.
|
|
1823
|
+
* Windows 는 `setBadgeCount` 가 없어 오버레이 아이콘(원+숫자)으로 그린다.
|
|
1824
|
+
* 이 값은 전이 순간이 아니라 상태가 바뀔 때마다 실제 개수로 갱신되고, 0 이면 지운다
|
|
1825
|
+
*/
|
|
1826
|
+
alertBadge: true,
|
|
1827
|
+
/**
|
|
1828
|
+
* OS 토스트(`Notification`)까지 띄운다. 기본은 끔 — 탭이 여럿이면 시끄럽다.
|
|
1829
|
+
* 토스트를 누르면 창을 앞으로 가져오고 그 탭을 고른다
|
|
1830
|
+
*/
|
|
1831
|
+
alertToast: false,
|
|
1340
1832
|
},
|
|
1341
1833
|
hotkeys: {
|
|
1342
1834
|
'agentdeck-toggle': [],
|
|
@@ -1386,6 +1878,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1386
1878
|
/* harmony import */ var _prompt__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./prompt */ "./src/prompt.ts");
|
|
1387
1879
|
/* harmony import */ var _screen__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./screen */ "./src/screen.ts");
|
|
1388
1880
|
/* harmony import */ var _dock__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./dock */ "./src/dock.ts");
|
|
1881
|
+
/* harmony import */ var _order__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./order */ "./src/order.ts");
|
|
1389
1882
|
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
1390
1883
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1391
1884
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1407,6 +1900,7 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
|
|
|
1407
1900
|
|
|
1408
1901
|
|
|
1409
1902
|
|
|
1903
|
+
|
|
1410
1904
|
const SIDEBAR_ID = 'agentdeck-sidebar';
|
|
1411
1905
|
const BODY_CLASS = 'agentdeck-active';
|
|
1412
1906
|
/** 배경 이미지 레이어를 터미널 영역으로 자르는 중일 때 body 에 붙는 클래스 (styles.scss 참조) */
|
|
@@ -1562,6 +2056,8 @@ let AgentDeckService = class AgentDeckService {
|
|
|
1562
2056
|
this.observer = null;
|
|
1563
2057
|
this.editing = null;
|
|
1564
2058
|
this.renderQueued = false;
|
|
2059
|
+
/** Shift 물리 키가 눌려 있나 — IME 조합을 거친 Enter 는 shiftKey 가 지워져 온다 */
|
|
2060
|
+
this.shiftDownAt = 0;
|
|
1565
2061
|
/** rAF 가 멈춘 상황(창 가려짐)을 대비한 렌더 폴백 타이머 */
|
|
1566
2062
|
this.renderTimer = null;
|
|
1567
2063
|
/** 직전에 적용한 폭 — 같은 값이면 DOM 을 다시 건드리지 않는다 */
|
|
@@ -1655,7 +2151,9 @@ let AgentDeckService = class AgentDeckService {
|
|
|
1655
2151
|
this.disableBackgroundThrottling();
|
|
1656
2152
|
this.ensurePasteHotkey();
|
|
1657
2153
|
this.ensureNewlineHotkey();
|
|
2154
|
+
this.releaseHomeEndHotkey();
|
|
1658
2155
|
this.claimCtrlVKey();
|
|
2156
|
+
this.claimShiftEnterKey();
|
|
1659
2157
|
this.claimEnterLabel();
|
|
1660
2158
|
this.claimRightClick();
|
|
1661
2159
|
// xterm 의 네이티브 paste 를 관측만 한다 (doPaste 의 이중 입력 방지용).
|
|
@@ -2335,6 +2833,124 @@ let AgentDeckService = class AgentDeckService {
|
|
|
2335
2833
|
this.zone.runOutsideAngular(() => this.doPaste('ctrl-v'));
|
|
2336
2834
|
}, true);
|
|
2337
2835
|
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Home / End 를 Tabby 핫키 테이블에서 걷어내 xterm 순정 경로로 돌려준다.
|
|
2838
|
+
*
|
|
2839
|
+
* 증상: 한글 마지막 음절이 조합 중일 때 Home/End 를 누르면 그 음절이 **커서를 따라간다**
|
|
2840
|
+
* (2026-09-08 사용자 보고). 방향키는 멀쩡하다 — 그 차이가 곧 원인이다.
|
|
2841
|
+
*
|
|
2842
|
+
* 방향키는 핫키가 아니라서 xterm 의 키 경로를 그대로 탄다. 거기서는 조합 중에 아무 키나
|
|
2843
|
+
* 들어오면 `CompositionHelper.keydown` 이 `_finalizeComposition(false)` 로 조합을
|
|
2844
|
+
* **동기 전송**하고(`tabby-terminal/dist/index.js:44098`, `:44120` triggerDataEvent) 그다음
|
|
2845
|
+
* 방향키를 보낸다 — 글자 먼저, 이동 나중이라 순서가 맞는다.
|
|
2846
|
+
*
|
|
2847
|
+
* Home/End 는 그 경로를 못 탄다. Tabby 가 `hotkeys.home` / `hotkeys.end` 로 선점해서
|
|
2848
|
+
* `tabby-local/dist/index.js:613` 이 `sendInput('[H')` 를 **즉시** 쏘기 때문이다.
|
|
2849
|
+
* xterm 을 거치지 않으니 조합 확정은 `compositionend` 경로로 밀리는데, 그쪽
|
|
2850
|
+
* `_finalizeComposition(true)` 는 `setTimeout(..., 0)` 로 **비동기 전송**이다
|
|
2851
|
+
* (`tabby-terminal/dist/index.js:44110`). 결국 커서 이동이 먼저 나가고 음절이 한 틱 뒤에
|
|
2852
|
+
* 도착한다 — 앱은 옮겨간 자리에 글자를 찍는다.
|
|
2853
|
+
*
|
|
2854
|
+
* 그래서 고치는 방법은 핸들러를 더 얹는 게 아니라 **핫키를 놓아주는 것**이다. 비워두면
|
|
2855
|
+
* xterm 이 Home/End 를 방향키와 똑같이 처리한다(조합 확정 → 키 전송). 되돌리려면
|
|
2856
|
+
* `agentDeck.releaseHomeEnd: false` 후 설정에서 다시 지정하면 된다.
|
|
2857
|
+
*/
|
|
2858
|
+
releaseHomeEndHotkey() {
|
|
2859
|
+
if (!this.config.store.agentDeck.releaseHomeEnd) {
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
const hotkeys = this.config.store.hotkeys;
|
|
2863
|
+
if (!hotkeys) {
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
let changed = false;
|
|
2867
|
+
for (const id of ['home', 'end']) {
|
|
2868
|
+
const keys = Array.isArray(hotkeys[id]) ? hotkeys[id] : [];
|
|
2869
|
+
const kept = keys.filter(k => k !== 'Home' && k !== 'End');
|
|
2870
|
+
if (kept.length !== keys.length) {
|
|
2871
|
+
hotkeys[id] = kept;
|
|
2872
|
+
changed = true;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
if (changed) {
|
|
2876
|
+
this.config.save();
|
|
2877
|
+
}
|
|
2878
|
+
this.diag(`${new Date().toISOString()} hotkeys home=${JSON.stringify(hotkeys.home)} `
|
|
2879
|
+
+ `end=${JSON.stringify(hotkeys.end)}`);
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Shift+Enter / Ctrl+Enter 를 document **캡처 단계**에서 직접 줄바꿈으로 바꾼다.
|
|
2883
|
+
*
|
|
2884
|
+
* 핫키(`agentdeck-newline`)에만 맡겼더니 한글을 치다 누르면 절반쯤 줄바꿈이 아니라
|
|
2885
|
+
* **전송**이 됐다 (2026-09-08 사용자 보고: "50% 정도는 그냥 enter 로 들어간다").
|
|
2886
|
+
* 진단로그에 증거가 남아 있다 — 오발신 순간 `enter-label` 이 발화했는데
|
|
2887
|
+
* (`.agentdeck-diag.log` 00:54:59 `enter-label text="지금도 그랬"`), 그 핸들러는
|
|
2888
|
+
* 수식키가 하나라도 눌려 있으면 맨 앞에서 빠져나간다. 즉 그 Enter 는 **shiftKey 가
|
|
2889
|
+
* 지워진 채** 도착했다. 한글 IME 가 조합을 확정하며 Enter 를 한 번 삼키고, 뒤이어
|
|
2890
|
+
* 내보내는 이벤트에는 Shift 상태를 싣지 않는 것이다. Shift 가 없으니 `Shift-Enter`
|
|
2891
|
+
* 핫키에 매칭될 리 없고, xterm 은 평범한 Enter 로 0x0D 를 PTY 에 흘린다 —
|
|
2892
|
+
* 쓰다 만 문장이 그대로 전송된다.
|
|
2893
|
+
*
|
|
2894
|
+
* 그래서 이벤트가 알려주는 수식키를 믿지 않고 **Shift 눌림을 우리가 따로 센다**.
|
|
2895
|
+
* Shift 자체의 keydown/keyup 은 조합과 무관하게 그대로 오므로 IME 를 타지 않는다.
|
|
2896
|
+
* 세어 둔 값이 썩는 것(키업 유실)을 막으려고 창 포커스가 나가거나, Shift 없이 평범한
|
|
2897
|
+
* 글자가 들어오거나, 오래 묵으면(STALE_MS) 스스로 잊는다.
|
|
2898
|
+
*
|
|
2899
|
+
* 조합 중(`isComposing`/keyCode 229)인 Enter 는 손대지 않는다. 그건 "이 음절을
|
|
2900
|
+
* 확정해라" 는 신호고 여기서 막으면 마지막 글자가 확정되지 못한 채 줄만 넘어간다.
|
|
2901
|
+
* 확정이 끝나면 Enter 가 한 번 더 오니 그때 잡으면 글자 순서도 어긋나지 않는다.
|
|
2902
|
+
*
|
|
2903
|
+
* 캡처에서 전파를 끊는 이유는 claimCtrlVKey 와 같다 — 뒤에 걸린 경로(xterm 커스텀 키
|
|
2904
|
+
* 핸들러 → pushKeyEvent, core 의 document 리스너, xterm 이 Enter 를 0x0D 로 흘리는
|
|
2905
|
+
* 기본 동작)를 통째로 죽여야 줄바꿈이 정확히 한 번이 된다.
|
|
2906
|
+
*/
|
|
2907
|
+
claimShiftEnterKey() {
|
|
2908
|
+
const STALE_MS = 5000;
|
|
2909
|
+
const forget = () => {
|
|
2910
|
+
this.shiftDownAt = 0;
|
|
2911
|
+
};
|
|
2912
|
+
const isShiftKey = (event) => event.key === 'Shift' || (event.code || '').startsWith('Shift');
|
|
2913
|
+
window.addEventListener('blur', forget);
|
|
2914
|
+
document.addEventListener('keyup', (event) => {
|
|
2915
|
+
if (isShiftKey(event)) {
|
|
2916
|
+
forget();
|
|
2917
|
+
}
|
|
2918
|
+
}, true);
|
|
2919
|
+
document.addEventListener('keydown', (event) => {
|
|
2920
|
+
if (isShiftKey(event)) {
|
|
2921
|
+
this.shiftDownAt = Date.now();
|
|
2922
|
+
return;
|
|
2923
|
+
}
|
|
2924
|
+
if (!this.config.store.agentDeck.claimShiftEnter) {
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
const isEnter = event.key === 'Enter' || event.code === 'Enter' || event.code === 'NumpadEnter';
|
|
2928
|
+
// Shift 없이 평범한 글자가 들어왔다 = Shift 는 확실히 떼어져 있다 (키업 유실 자가복구)
|
|
2929
|
+
if (!isEnter && !event.shiftKey && !event.getModifierState('Shift')) {
|
|
2930
|
+
forget();
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
if (!isEnter || event.altKey || event.metaKey) {
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (event.isComposing || event.keyCode === 229) {
|
|
2937
|
+
return; // 조합 확정용 Enter — 확정된 뒤에 한 번 더 온다
|
|
2938
|
+
}
|
|
2939
|
+
const tracked = this.shiftDownAt > 0 && Date.now() - this.shiftDownAt < STALE_MS;
|
|
2940
|
+
const shift = event.shiftKey || event.getModifierState('Shift') || tracked;
|
|
2941
|
+
if (!shift && !event.ctrlKey) {
|
|
2942
|
+
return; // 맨 Enter 는 전송이다 — 건드리지 않는다
|
|
2943
|
+
}
|
|
2944
|
+
if (this.isPlainInput(event.target)) {
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
event.preventDefault();
|
|
2948
|
+
event.stopPropagation();
|
|
2949
|
+
this.diag(`${new Date().toISOString()} newline (capture) shiftKey=${event.shiftKey} `
|
|
2950
|
+
+ `modState=${event.getModifierState('Shift')} tracked=${tracked} ctrl=${event.ctrlKey}`);
|
|
2951
|
+
this.sendNewline();
|
|
2952
|
+
}, true);
|
|
2953
|
+
}
|
|
2338
2954
|
/**
|
|
2339
2955
|
* Enter 를 눌러 프롬프트를 보낼 때, 그 프롬프트 원문을 작업 라벨로 삼는다.
|
|
2340
2956
|
*
|
|
@@ -3348,14 +3964,42 @@ let AgentDeckService = class AgentDeckService {
|
|
|
3348
3964
|
if (!this.listEl || !this.enabled || this.editing) {
|
|
3349
3965
|
return;
|
|
3350
3966
|
}
|
|
3351
|
-
const
|
|
3352
|
-
|
|
3353
|
-
|
|
3967
|
+
const statusOf = (tab) => this.status.get(tab).status;
|
|
3968
|
+
// 정렬은 복사본에만 — app.tabs 의 순서(순정 탭바)는 그대로 둔다
|
|
3969
|
+
const tabs = this.config.store.agentDeck.sortByStatus
|
|
3970
|
+
? (0,_order__WEBPACK_IMPORTED_MODULE_12__.sortTabsByStatus)(this.app.tabs, statusOf)
|
|
3971
|
+
: this.app.tabs;
|
|
3972
|
+
this.renderHeadCount(tabs, statusOf);
|
|
3354
3973
|
this.listEl.innerHTML = '';
|
|
3355
3974
|
for (const tab of tabs) {
|
|
3356
3975
|
this.listEl.appendChild(this.renderTab(tab));
|
|
3357
3976
|
}
|
|
3358
3977
|
}
|
|
3978
|
+
/**
|
|
3979
|
+
* 헤더 오른쪽의 상태별 집계 — `⏸ 2 ● 3 ○ 1` 처럼 0 이 아닌 상태만 급한 순서로 놓는다.
|
|
3980
|
+
* 총 개수만 보이던 자리다. 세션을 여럿 띄우면 "몇 개가 나를 기다리나" 가 먼저 보여야 한다.
|
|
3981
|
+
* 동적 텍스트는 textContent 로만 넣는다 (innerHTML 금지).
|
|
3982
|
+
*/
|
|
3983
|
+
renderHeadCount(tabs, statusOf) {
|
|
3984
|
+
const countEl = this.sidebar.querySelector('.ad-head-count');
|
|
3985
|
+
if (!countEl) {
|
|
3986
|
+
return;
|
|
3987
|
+
}
|
|
3988
|
+
countEl.innerHTML = '';
|
|
3989
|
+
countEl.title = `탭 ${tabs.length}개`;
|
|
3990
|
+
for (const { status, count } of (0,_order__WEBPACK_IMPORTED_MODULE_12__.countByStatus)(tabs, statusOf)) {
|
|
3991
|
+
const style = _api__WEBPACK_IMPORTED_MODULE_8__.STATUS_STYLES[status];
|
|
3992
|
+
const chip = document.createElement('span');
|
|
3993
|
+
chip.className = 'ad-cnt';
|
|
3994
|
+
chip.style.setProperty('--ad-color', style.color);
|
|
3995
|
+
chip.title = `${style.label} ${count}`;
|
|
3996
|
+
chip.textContent = `${style.icon} ${count}`;
|
|
3997
|
+
countEl.appendChild(chip);
|
|
3998
|
+
}
|
|
3999
|
+
if (!tabs.length) {
|
|
4000
|
+
countEl.textContent = '0';
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
3359
4003
|
renderTab(tab) {
|
|
3360
4004
|
const st = this.status.get(tab);
|
|
3361
4005
|
const style = _api__WEBPACK_IMPORTED_MODULE_8__.STATUS_STYLES[st.status];
|
|
@@ -3398,7 +4042,10 @@ let AgentDeckService = class AgentDeckService {
|
|
|
3398
4042
|
labelEl.classList.toggle('empty', !st.label);
|
|
3399
4043
|
row.querySelector('.ad-elapsed').textContent = elapsed;
|
|
3400
4044
|
const badge = row.querySelector('.ad-badge');
|
|
3401
|
-
|
|
4045
|
+
// 승인대기 이유가 있으면 뒤에 붙인다 — "⏸ 승인대기 · Bash 권한". 여러 세션을 띄워 둔 사람이
|
|
4046
|
+
// 사이드바만 보고 지금 가서 승인할지 정할 수 있게. 원문은 title 로 마우스를 올리면 보인다
|
|
4047
|
+
badge.textContent = style.icon + ' ' + style.label + (st.reason ? ' · ' + st.reason : '');
|
|
4048
|
+
badge.title = st.reason;
|
|
3402
4049
|
if (st.pinned) {
|
|
3403
4050
|
badge.classList.add('pinned');
|
|
3404
4051
|
}
|
|
@@ -3463,7 +4110,7 @@ let AgentDeckService = class AgentDeckService {
|
|
|
3463
4110
|
}
|
|
3464
4111
|
const menu = document.createElement('div');
|
|
3465
4112
|
menu.className = 'ad-menu';
|
|
3466
|
-
const statuses = ['running', 'waiting', 'done', 'error', 'idle'];
|
|
4113
|
+
const statuses = ['running', 'waiting', 'limited', 'done', 'error', 'idle'];
|
|
3467
4114
|
const items = statuses.map(s => {
|
|
3468
4115
|
const label = _api__WEBPACK_IMPORTED_MODULE_8__.STATUS_STYLES[s].icon + ' ' + _api__WEBPACK_IMPORTED_MODULE_8__.STATUS_STYLES[s].label;
|
|
3469
4116
|
const action = () => this.status.setManual(tab, s);
|
|
@@ -3652,7 +4299,7 @@ const BUSY_RE = [
|
|
|
3652
4299
|
/ctrl\+c to (?:interrupt|cancel|stop)/i,
|
|
3653
4300
|
/\btokens?\b.{0,20}\besc\b/i,
|
|
3654
4301
|
];
|
|
3655
|
-
const VALID = ['idle', 'running', 'waiting', 'done', 'error'];
|
|
4302
|
+
const VALID = ['idle', 'running', 'waiting', 'limited', 'done', 'error'];
|
|
3656
4303
|
/** OSC 가 청크 경계에 걸리는 것을 막기 위한 잔여 버퍼 */
|
|
3657
4304
|
const tails = new WeakMap();
|
|
3658
4305
|
/**
|
|
@@ -3695,6 +4342,16 @@ function applyOutput(status, root, owner, data, autoDetect) {
|
|
|
3695
4342
|
status.setAuto(root, 'waiting');
|
|
3696
4343
|
return;
|
|
3697
4344
|
}
|
|
4345
|
+
// 승인대기였던 탭에 작업 중 문구가 다시 보이면 사람이 승인한 것이다 — 진행중으로 올린다.
|
|
4346
|
+
// 훅은 이 순간을 알려주지 않는다: 승인 뒤 첫 훅(PostToolUse)은 도구가 끝난 뒤라, 2분짜리 빌드를
|
|
4347
|
+
// 승인하면 그동안 사이드바가 승인대기에 박혀 있다 (2026-09-07 실측 — "승인했는데 진행중으로 안 돌아온다").
|
|
4348
|
+
// 훅이 고정(pinned)한 탭에도 적용해야 하므로 setAuto 가 아니라 resume 을 쓴다.
|
|
4349
|
+
// 제목 스피너(BUSY_MARKS)는 근거로 삼지 않는다 — 승인 대화상자가 떠 있는 동안에도 제목에 남을 수 있다
|
|
4350
|
+
if (status.get(root).status === 'waiting' && BUSY_RE.some(re => re.test(data))) {
|
|
4351
|
+
status.markBusy(root);
|
|
4352
|
+
status.resume(root);
|
|
4353
|
+
return;
|
|
4354
|
+
}
|
|
3698
4355
|
// 에이전트가 "지금 일하는 중" 이라고 스스로 밝히는 표시를 찾는다
|
|
3699
4356
|
if (BUSY_MARKS.some(mark => data.includes(mark)) || BUSY_RE.some(re => re.test(data))) {
|
|
3700
4357
|
status.markBusy(root);
|
|
@@ -4129,6 +4786,8 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
4129
4786
|
/* harmony import */ var _status_service__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./status.service */ "./src/status.service.ts");
|
|
4130
4787
|
/* harmony import */ var _claudeHooks__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./claudeHooks */ "./src/claudeHooks.ts");
|
|
4131
4788
|
/* harmony import */ var _bind__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./bind */ "./src/bind.ts");
|
|
4789
|
+
/* harmony import */ var _reason__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./reason */ "./src/reason.ts");
|
|
4790
|
+
/* harmony import */ var _tabenv__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./tabenv */ "./src/tabenv.ts");
|
|
4132
4791
|
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
4133
4792
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4134
4793
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -4147,6 +4806,8 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
|
|
|
4147
4806
|
|
|
4148
4807
|
|
|
4149
4808
|
|
|
4809
|
+
|
|
4810
|
+
|
|
4150
4811
|
const DIAG_PATH = path__WEBPACK_IMPORTED_MODULE_5__.join(os__WEBPACK_IMPORTED_MODULE_4__.homedir(), '.agentdeck-diag.log');
|
|
4151
4812
|
/**
|
|
4152
4813
|
* 에이전트 훅 -> 사이드바 상태 채널. **기본으로 켜져 있다** (`notifyChannel`).
|
|
@@ -4205,6 +4866,16 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4205
4866
|
*/
|
|
4206
4867
|
this.tabPids = new Map();
|
|
4207
4868
|
this.pidRefreshing = false;
|
|
4869
|
+
/**
|
|
4870
|
+
* 탭(루트) -> 그 안 패널 셸에 심은 `AGENTDECK_TAB` 값들(tabenv.ts). 훅이 tabId 로 보내면 여기서 바로
|
|
4871
|
+
* 확정하고, 프로세스 계보(tabPids)는 이 표에 없을 때의 폴백이다. 패널의 `profile.options.env` 를 읽어
|
|
4872
|
+
* 다시 채우므로 재시작으로 복원된 탭(복원 토큰에 profile 이 통째로 들어간다, tabby-local
|
|
4873
|
+
* dist/index.js:639-648)도 지난 실행의 id 를 그대로 되찾는다 — 살아 있는 pty 에 다시 붙든, 복원이
|
|
4874
|
+
* 실패해 같은 options 로 새 셸을 띄우든 셸 환경은 그 값이다
|
|
4875
|
+
*/
|
|
4876
|
+
this.tabIds = new Map();
|
|
4877
|
+
/** tabAdded$ 를 이미 구독한 분할 루트 — 루트가 사라지면 함께 사라진다 */
|
|
4878
|
+
this.watchedSplits = new WeakSet();
|
|
4208
4879
|
this.pollCount = 0;
|
|
4209
4880
|
/** 같은 매칭 실패를 폴링마다 diag 에 찍지 않기 위한 마지막 메시지 */
|
|
4210
4881
|
this.lastBindLog = new Map();
|
|
@@ -4272,6 +4943,11 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4272
4943
|
}
|
|
4273
4944
|
init() {
|
|
4274
4945
|
this.app.ready$.subscribe(() => {
|
|
4946
|
+
// 이미 열려 있는 탭 중 셸이 아직 안 뜬 패널이 있으면 지금 심는다 (기동 직후의 첫 탭).
|
|
4947
|
+
// 셸이 이미 뜬 패널은 못 심고 계보 폴백으로 남는다
|
|
4948
|
+
for (const root of [...this.app.tabs]) {
|
|
4949
|
+
this.stampRoot(root);
|
|
4950
|
+
}
|
|
4275
4951
|
// 훅이 안 걸려 있으면 기동할 때 한 번 물어본다. 통로만 열려 있고 보고자가 없으면
|
|
4276
4952
|
// 아무 일도 일어나지 않는데, 사용자 눈에는 그냥 "상태가 이상하다" 로만 보인다.
|
|
4277
4953
|
// 기동 직후는 화면이 정신없으니 조금 뒤에 띄운다
|
|
@@ -4286,13 +4962,18 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4286
4962
|
this.timer = setInterval(() => this.poll(), 400);
|
|
4287
4963
|
void this.refreshPids();
|
|
4288
4964
|
});
|
|
4289
|
-
|
|
4290
|
-
|
|
4965
|
+
this.app.tabOpened$.subscribe(tab => {
|
|
4966
|
+
// 셸은 아직 안 떴다 — `openNewTab` 은 `tabsService.create` 직후 `addTabRaw` 에서 동기로 tabOpened 를
|
|
4967
|
+
// 내고(tabby-core dist/index.js:5029), 셸은 그 뒤 프런트엔드가 준비된 다음에야 뜬다(tabby-local
|
|
4968
|
+
// dist/index.js:623-635). 여기서 profile.options.env 에 심어야 셸이 물려받는다
|
|
4969
|
+
this.stampRoot(tab);
|
|
4970
|
+
// 새 탭의 셸 PID 는 세션이 붙은 뒤에야 나온다 — 조금 뒤에 한 번 더 채운다 (2초 주기 폴링도 있다)
|
|
4291
4971
|
setTimeout(() => { void this.refreshPids(); }, 1500);
|
|
4292
4972
|
});
|
|
4293
4973
|
// 탭이 닫히면 점유를 풀어야 그 자리를 다음 세션이 쓸 수 있다
|
|
4294
4974
|
this.app.tabClosed$.subscribe(tab => {
|
|
4295
4975
|
this.tabPids.delete(tab);
|
|
4976
|
+
this.tabIds.delete(tab);
|
|
4296
4977
|
const sid = this.tabSession.get(tab);
|
|
4297
4978
|
if (sid) {
|
|
4298
4979
|
this.tabSession.delete(tab);
|
|
@@ -4325,10 +5006,8 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4325
5006
|
this.pidRefreshing = true;
|
|
4326
5007
|
try {
|
|
4327
5008
|
for (const root of [...this.app.tabs]) {
|
|
4328
|
-
const anyRoot = root;
|
|
4329
|
-
const panes = typeof anyRoot.getAllTabs === 'function' ? anyRoot.getAllTabs() : [root];
|
|
4330
5009
|
const pids = [];
|
|
4331
|
-
for (const pane of
|
|
5010
|
+
for (const pane of this.panesOf(root)) {
|
|
4332
5011
|
const pty = (_a = pane === null || pane === void 0 ? void 0 : pane.session) === null || _a === void 0 ? void 0 : _a.pty;
|
|
4333
5012
|
if (!pty || typeof pty.getPID !== 'function') {
|
|
4334
5013
|
continue;
|
|
@@ -4354,6 +5033,160 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4354
5033
|
this.pidRefreshing = false;
|
|
4355
5034
|
}
|
|
4356
5035
|
}
|
|
5036
|
+
/** 루트 하나의 터미널 패널들 — 분할 래퍼(`app.tabs` 의 원소는 SplitTabComponent)면 자식들, 아니면 자기 자신 */
|
|
5037
|
+
panesOf(root) {
|
|
5038
|
+
const anyRoot = root;
|
|
5039
|
+
return typeof anyRoot.getAllTabs === 'function' ? anyRoot.getAllTabs() : [root];
|
|
5040
|
+
}
|
|
5041
|
+
isLocalProfile(profile) {
|
|
5042
|
+
return !!profile && profile.type === 'local' && !!profile.options && typeof profile.options === 'object';
|
|
5043
|
+
}
|
|
5044
|
+
/**
|
|
5045
|
+
* 새 루트 탭의 셸에 `AGENTDECK_TAB` 을 심는다 — 지금 있는 패널, 복원 토큰 안의 패널, 나중에 분할로 붙는 패널.
|
|
5046
|
+
*
|
|
5047
|
+
* 심는 자리가 맞는 근거 (tabby-local/dist/index.js, 1.0.231):
|
|
5048
|
+
* - 셸은 `onFrontendReady -> initializeSession` 이 `session.start({ ...this.profile.options, width, height })`
|
|
5049
|
+
* 로 띄운다(623-635). 자식 env 는 `mergeEnv(process.env, {TERM_PROGRAM: 'Tabby', …}, substituteEnv(options.env),
|
|
5050
|
+
* config.store.terminal.environment)` 다(1158-1162). 즉 **spawn 시점의 `pane.profile.options.env`** 가 셸 환경이다.
|
|
5051
|
+
* - `ngOnInit` 의 `this.sessionOptions = this.profile.options`(604-605) 는 같은 객체를 가리키는 참조일 뿐
|
|
5052
|
+
* spawn 에 쓰이지 않는다. profile 을 새 객체로 바꾸므로 그것도 같이 맞춰 둔다.
|
|
5053
|
+
* - `tab.profile` 은 `getNewTabParameters` 가 deep clone 한 것(898)이라 저장된 프로필과 다른 객체다. 그래도
|
|
5054
|
+
* 여기서 다시 얕게 복제해 넣는다 — 다른 경로(복원·복제 토큰)로 들어온 profile 이 공유 객체일 가능성을 막고,
|
|
5055
|
+
* 탭 하나의 id 가 설정 파일의 프로필로 새는 일이 없게 한다.
|
|
5056
|
+
*
|
|
5057
|
+
* 타이밍 (tabby-core/dist/index.js): `openNewTab`(5075) 은 `wrapAndAddTab`(5086) 에서 `splitTab.addTab(tab)` 을
|
|
5058
|
+
* 부르고 — `add()` 는 첫 await(2620 `initialized$`) 앞에서 자식을 `children` 에 넣는다(2618) — 그 다음
|
|
5059
|
+
* `addTabRaw` 가 `tabOpened.next(splitTab)` 를 낸다(5029). 그래서 tabOpened 시점에 `getAllTabs()` 에 자식이
|
|
5060
|
+
* 있고 `session` 은 아직 null 이다.
|
|
5061
|
+
*/
|
|
5062
|
+
stampRoot(root) {
|
|
5063
|
+
this.stampPanes(root);
|
|
5064
|
+
const anyRoot = root;
|
|
5065
|
+
// 복제 탭(duplicateTab, 5245-5249)과 재시작 복원 탭(5003-5006)은 이 시점에 자식이 없고 복원 토큰
|
|
5066
|
+
// (`_recoveredState`)만 있다 — 자식은 ngAfterViewInit 의 recoverContainer 가 비동기로 만들고(2488-2491,
|
|
5067
|
+
// 3093 `tabsService.create(recovered)`) tabAdded 도 내지 않는다. 토큰 안의 profile 을 지금 고치면 그것이
|
|
5068
|
+
// 그대로 자식의 profile 이 된다 (recoverTab -> inputs.profile)
|
|
5069
|
+
if (anyRoot._recoveredState && typeof anyRoot._recoveredState === 'object') {
|
|
5070
|
+
this.stampRecoveryState(anyRoot._recoveredState, root);
|
|
5071
|
+
}
|
|
5072
|
+
// 나중에 분할로 붙는 패널 — tabAdded 는 onAfterTabAdded 의 setImmediate 에서 나오는데(2935-2939),
|
|
5073
|
+
// 셸은 그보다 뒤인 프런트엔드 준비 후에 뜬다. 혹시 이미 떠 있으면 stampPane 이 건너뛴다.
|
|
5074
|
+
// Subject 는 루트가 destroy 될 때 complete 되므로(2510) 구독을 따로 풀 필요가 없다
|
|
5075
|
+
if (anyRoot.tabAdded$ && typeof anyRoot.tabAdded$.subscribe === 'function' && !this.watchedSplits.has(root)) {
|
|
5076
|
+
this.watchedSplits.add(root);
|
|
5077
|
+
anyRoot.tabAdded$.subscribe(() => this.stampPanes(root));
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
stampPanes(root) {
|
|
5081
|
+
for (const pane of this.panesOf(root)) {
|
|
5082
|
+
this.stampPane(pane, root);
|
|
5083
|
+
}
|
|
5084
|
+
this.refreshTabIds();
|
|
5085
|
+
}
|
|
5086
|
+
/**
|
|
5087
|
+
* 패널 하나에 심는다. **아직 셸이 안 뜬**(`session` 없음) 로컬 패널만 — 이미 뜬 셸의 환경은 바꿀 수 없다.
|
|
5088
|
+
* 복원 패널(`restoreFromPTYID`)은 살아 있는 pty 에 다시 붙으므로 건드리지 않는다 — 토큰의 profile 에
|
|
5089
|
+
* 지난 실행의 id 가 있으면 그게 곧 그 셸의 환경이고 refreshTabIds 가 읽어 간다.
|
|
5090
|
+
* 이미 id 가 있어도 다른 패널이 같은 값을 갖고 있으면(복제) 새로 뽑는다 — 같은 id 가 두 셸에 있으면 어느 쪽인지 알 수 없다.
|
|
5091
|
+
*/
|
|
5092
|
+
stampPane(pane, root) {
|
|
5093
|
+
var _a;
|
|
5094
|
+
if (!pane || pane.session || !this.isLocalProfile(pane.profile) || pane.profile.options.restoreFromPTYID) {
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
const env = (_a = pane.profile.options.env) !== null && _a !== void 0 ? _a : {};
|
|
5098
|
+
const cur = typeof env[_tabenv__WEBPACK_IMPORTED_MODULE_10__.TAB_ENV] === 'string' ? env[_tabenv__WEBPACK_IMPORTED_MODULE_10__.TAB_ENV] : '';
|
|
5099
|
+
if (cur && !this.idTakenElsewhere(cur, pane)) {
|
|
5100
|
+
return;
|
|
5101
|
+
}
|
|
5102
|
+
const id = (0,_tabenv__WEBPACK_IMPORTED_MODULE_10__.newTabId)();
|
|
5103
|
+
pane.profile = { ...pane.profile, options: { ...pane.profile.options, env: { ...env, [_tabenv__WEBPACK_IMPORTED_MODULE_10__.TAB_ENV]: id } } };
|
|
5104
|
+
if (pane.sessionOptions !== undefined) {
|
|
5105
|
+
pane.sessionOptions = pane.profile.options;
|
|
5106
|
+
}
|
|
5107
|
+
this.diag(`tabid stamp tab=${this.tabName(root)} id=${id}${cur ? ` (was ${cur}: duplicate)` : ''}`);
|
|
5108
|
+
}
|
|
5109
|
+
/**
|
|
5110
|
+
* 복원 토큰 트리(`app:split-tab` 은 children, 잎은 profile 을 가진다)의 로컬 패널에 심는다.
|
|
5111
|
+
* `restoreFromPTYID` 가 있는 잎은 재시작 복원(saveTabs 가 includeState 로 만든 토큰, tabby-core 7700) —
|
|
5112
|
+
* 살아 있는 pty 의 환경은 이미 정해져 있으니 두고, 없는 잎은 새 셸이 뜰 것이므로(복제 탭 —
|
|
5113
|
+
* `tabsService.duplicate` 는 options 없이 토큰을 만들어 restoreFromPTYID 가 비어 있다, 7819-7830) 항상 새 id 를 준다.
|
|
5114
|
+
* 복제 원본의 id 가 토큰에 그대로 실려 오는데 그걸 두면 두 탭이 같은 id 를 갖게 된다
|
|
5115
|
+
*/
|
|
5116
|
+
stampRecoveryState(state, root) {
|
|
5117
|
+
var _a;
|
|
5118
|
+
if (Array.isArray(state.children)) {
|
|
5119
|
+
for (const child of state.children) {
|
|
5120
|
+
if (child && typeof child === 'object') {
|
|
5121
|
+
this.stampRecoveryState(child, root);
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
return;
|
|
5125
|
+
}
|
|
5126
|
+
const profile = state.profile;
|
|
5127
|
+
if (!this.isLocalProfile(profile) || profile.options.restoreFromPTYID) {
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
5130
|
+
const id = (0,_tabenv__WEBPACK_IMPORTED_MODULE_10__.newTabId)();
|
|
5131
|
+
state.profile = { ...profile, options: { ...profile.options, env: { ...((_a = profile.options.env) !== null && _a !== void 0 ? _a : {}), [_tabenv__WEBPACK_IMPORTED_MODULE_10__.TAB_ENV]: id } } };
|
|
5132
|
+
this.diag(`tabid stamp tab=${this.tabName(root)} id=${id} (recovery token)`);
|
|
5133
|
+
}
|
|
5134
|
+
/** 같은 id 를 다른 패널(어느 루트든)이 이미 갖고 있나 */
|
|
5135
|
+
idTakenElsewhere(id, self) {
|
|
5136
|
+
var _a, _b, _c;
|
|
5137
|
+
for (const root of this.app.tabs) {
|
|
5138
|
+
for (const pane of this.panesOf(root)) {
|
|
5139
|
+
if (pane !== self && ((_c = (_b = (_a = pane === null || pane === void 0 ? void 0 : pane.profile) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.env) === null || _c === void 0 ? void 0 : _c[_tabenv__WEBPACK_IMPORTED_MODULE_10__.TAB_ENV]) === id) {
|
|
5140
|
+
return true;
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
return false;
|
|
5145
|
+
}
|
|
5146
|
+
/**
|
|
5147
|
+
* 탭별 tabId 표를 패널의 `profile.options.env` 에서 다시 읽는다. 동기·저비용이라 폴링(400ms)마다 돈다 —
|
|
5148
|
+
* 복원 탭의 자식은 tabOpened 뒤에 비동기로 생기고(tabby-core 3093) 이벤트도 없어서 읽는 쪽이 주기적으로 봐야 한다.
|
|
5149
|
+
* 같은 id 가 두 루트에 있으면(복제 뒤 셸이 이미 뜬 경우) 어느 쪽인지 알 수 없으니 둘 다 표에서 빼고 계보 폴백에 맡긴다.
|
|
5150
|
+
*/
|
|
5151
|
+
refreshTabIds() {
|
|
5152
|
+
var _a, _b, _c;
|
|
5153
|
+
const owner = new Map();
|
|
5154
|
+
const dup = new Set();
|
|
5155
|
+
const next = new Map();
|
|
5156
|
+
for (const root of [...this.app.tabs]) {
|
|
5157
|
+
const ids = [];
|
|
5158
|
+
for (const pane of this.panesOf(root)) {
|
|
5159
|
+
const v = (_c = (_b = (_a = pane === null || pane === void 0 ? void 0 : pane.profile) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.env) === null || _c === void 0 ? void 0 : _c[_tabenv__WEBPACK_IMPORTED_MODULE_10__.TAB_ENV];
|
|
5160
|
+
if (typeof v !== 'string' || !v || ids.includes(v)) {
|
|
5161
|
+
continue;
|
|
5162
|
+
}
|
|
5163
|
+
ids.push(v);
|
|
5164
|
+
const prevOwner = owner.get(v);
|
|
5165
|
+
if (prevOwner && prevOwner !== root) {
|
|
5166
|
+
dup.add(v);
|
|
5167
|
+
}
|
|
5168
|
+
else {
|
|
5169
|
+
owner.set(v, root);
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
next.set(root, ids);
|
|
5173
|
+
}
|
|
5174
|
+
for (const root of [...this.tabIds.keys()]) {
|
|
5175
|
+
if (!next.has(root)) {
|
|
5176
|
+
this.tabIds.delete(root);
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
for (const [root, ids] of next) {
|
|
5180
|
+
const clean = ids.filter(v => !dup.has(v));
|
|
5181
|
+
const prev = this.tabIds.get(root);
|
|
5182
|
+
if (!prev || prev.join(',') !== clean.join(',')) {
|
|
5183
|
+
this.tabIds.set(root, clean);
|
|
5184
|
+
const dropped = ids.filter(v => dup.has(v));
|
|
5185
|
+
this.diag(`tabid-map tab=${this.tabName(root)} ids=[${clean.join(',')}]`
|
|
5186
|
+
+ (dropped.length ? ` dup-dropped=[${dropped.join(',')}]` : ''));
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
4357
5190
|
/** 진단용 */
|
|
4358
5191
|
get debugDir() { return this.dir; }
|
|
4359
5192
|
/** 진단용 — 듣고 있는 포트 (0 = TCP 채널이 안 떴다) */
|
|
@@ -4435,7 +5268,8 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4435
5268
|
}
|
|
4436
5269
|
}
|
|
4437
5270
|
poll() {
|
|
4438
|
-
// 셸 PID 표는 2초마다 — 탭 안에서 셸을 다시 띄우거나 분할하면 바뀐다
|
|
5271
|
+
// tabId 표는 동기라 매번, 셸 PID 표는 pty 에 물어야 해서 2초마다 — 탭 안에서 셸을 다시 띄우거나 분할하면 바뀐다
|
|
5272
|
+
this.refreshTabIds();
|
|
4439
5273
|
if (++this.pollCount % 5 === 0) {
|
|
4440
5274
|
void this.refreshPids();
|
|
4441
5275
|
}
|
|
@@ -4496,14 +5330,14 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4496
5330
|
if (this.applied.get(data.sessionId) === data.ts) {
|
|
4497
5331
|
return;
|
|
4498
5332
|
}
|
|
4499
|
-
const tab = this.resolveTab(data.sessionId, data.pids);
|
|
5333
|
+
const tab = this.resolveTab(data.sessionId, data.tabId, data.pids);
|
|
4500
5334
|
if (!tab) {
|
|
4501
|
-
// pids 없는 보고(옛 훅·수동 호출)는 다시 봐도 결과가 같다. 그런데 폴링이 400ms 마다
|
|
5335
|
+
// tabId 도 pids 도 없는 보고(옛 훅·수동 호출)는 다시 봐도 결과가 같다. 그런데 폴링이 400ms 마다
|
|
4502
5336
|
// 되풀이하면 "활성 탭에 묶는다" 규칙이 다음에 열리는 빈 탭을 붙잡는다 — 끝난 세션의
|
|
4503
5337
|
// done 이 새 탭에 박히고, 그 탭의 진짜 세션은 주인이 있다며 거절됐다 (2026-09-02 실측).
|
|
4504
5338
|
// 적용한 것으로 쳐서 멈춘다. 다음 보고(새 ts)가 오면 그때 다시 시도한다.
|
|
4505
|
-
// pids 가 있는 보고는
|
|
4506
|
-
if (!((_a = data.pids) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
5339
|
+
// tabId / pids 가 있는 보고는 표가 채워지면 맞을 수 있으니 다음 폴링에 다시 본다.
|
|
5340
|
+
if (!((_a = data.pids) === null || _a === void 0 ? void 0 : _a.length) && !data.tabId) {
|
|
4507
5341
|
this.applied.set(data.sessionId, data.ts);
|
|
4508
5342
|
}
|
|
4509
5343
|
return;
|
|
@@ -4512,7 +5346,9 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4512
5346
|
const label = (data.label || '').trim();
|
|
4513
5347
|
if (hasStatus) {
|
|
4514
5348
|
// 라벨이 안 왔으면 undefined 를 넘겨 기존 라벨을 지키게 한다 (setManual 규약)
|
|
4515
|
-
|
|
5349
|
+
// 이유는 waiting 에서만 의미가 있고 setManual 이 다른 상태에서는 비운다
|
|
5350
|
+
const reason = (0,_reason__WEBPACK_IMPORTED_MODULE_9__.shortenReason)(typeof data.reason === 'string' ? data.reason : '');
|
|
5351
|
+
this.status.setManual(tab, data.status, hasLabel ? label : undefined, reason);
|
|
4516
5352
|
}
|
|
4517
5353
|
else {
|
|
4518
5354
|
// 라벨만 온 보고 — 상태도 고정 여부도 건드리지 않는다
|
|
@@ -4527,22 +5363,31 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4527
5363
|
/**
|
|
4528
5364
|
* 세션에 묶인 탭을 찾는다.
|
|
4529
5365
|
*
|
|
4530
|
-
* 1순위는
|
|
4531
|
-
*
|
|
4532
|
-
*
|
|
4533
|
-
*
|
|
5366
|
+
* 1순위는 **셸에 심어 둔 `AGENTDECK_TAB`**(tabId, tabenv.ts) — 플러그인이 탭을 열 때 심은 값을 훅이 그대로
|
|
5367
|
+
* 돌려보낸 것이라 표에 있으면 곧 그 탭이다. 2순위는 **프로세스 계보**(pids) — 훅이 보낸 조상 PID(claude.exe →
|
|
5368
|
+
* 탭 셸 → Tabby) 에 어떤 탭의 셸 PID 가 들어 있으면 그 탭이다(bind.ts). 이 버전 이전에 열린 탭·복원된 탭이
|
|
5369
|
+
* 여기로 온다. 둘 다 이미 다른 세션이 그 탭을 갖고 있어도 넘겨받는다: 같은 탭에서 claude 를 다시 띄우면
|
|
5370
|
+
* 세션 id 가 바뀌는 게 정상이고, 이전 주인은 이미 죽은 세션이다. 알던 탭과 다른 탭이 맞으면 옮긴다
|
|
5371
|
+
* (`claude --resume` 을 다른 탭에서 띄운 경우).
|
|
4534
5372
|
*
|
|
4535
|
-
* pids 가 왔는데 맞는 탭이 없으면 **추측하지 않고** null 이다 —
|
|
4536
|
-
*
|
|
4537
|
-
* "끝난 세션의 done 이 새 탭에 박힘" 이었다 (2026-09-02 실측).
|
|
5373
|
+
* tabId 나 pids 가 왔는데 맞는 탭이 없으면 **추측하지 않고** null 이다 — 표가 아직 안 채워진 것일 수
|
|
5374
|
+
* 있고(복원 탭의 자식은 늦게 생긴다), 그 경우 다음 폴링(400ms)에 다시 온다. 활성 탭으로 떨어지던 예전 규칙이
|
|
5375
|
+
* 낸 사고가 "끝난 세션의 done 이 새 탭에 박힘" 이었다 (2026-09-02 실측).
|
|
4538
5376
|
*
|
|
4539
|
-
*
|
|
5377
|
+
* 둘 다 없는 보고(옛 훅·셸에서 사람이 부른 것)만 예전 규칙을 탄다 — 처음 보는 세션이면 지금 활성 탭.
|
|
4540
5378
|
* 단 그 탭을 이미 다른 세션이 점유했으면 묶지 않는다 (여러 세션이 한 탭에 겹쳐 붙어 서로의
|
|
4541
5379
|
* 상태를 덮어쓰던 2026-08-28 사고의 방지책).
|
|
4542
5380
|
*/
|
|
4543
|
-
resolveTab(sessionId, pids) {
|
|
5381
|
+
resolveTab(sessionId, tabId, pids) {
|
|
4544
5382
|
const known = this.sessionTab.get(sessionId);
|
|
4545
5383
|
const alive = known && this.app.tabs.includes(known) ? known : null;
|
|
5384
|
+
const byId = (0,_tabenv__WEBPACK_IMPORTED_MODULE_10__.pickTabByTabId)(tabId, this.tabIds);
|
|
5385
|
+
if (byId) {
|
|
5386
|
+
if (byId !== alive) {
|
|
5387
|
+
this.bind(sessionId, byId, alive ? 'tabid-move' : 'tabid');
|
|
5388
|
+
}
|
|
5389
|
+
return byId;
|
|
5390
|
+
}
|
|
4546
5391
|
const pick = (0,_bind__WEBPACK_IMPORTED_MODULE_8__.pickTabByPids)(pids, this.tabPids);
|
|
4547
5392
|
if (pick.mode === 'pid' && pick.tab) {
|
|
4548
5393
|
if (pick.tab !== alive) {
|
|
@@ -4553,6 +5398,14 @@ let WorkNotifyService = class WorkNotifyService {
|
|
|
4553
5398
|
if (alive) {
|
|
4554
5399
|
return alive;
|
|
4555
5400
|
}
|
|
5401
|
+
if (tabId) {
|
|
5402
|
+
// 훅은 tabId 가 있으면 pids 를 보내지 않으므로 여기서 멈춘다 — 계보처럼 추측 없이 다음 폴링을 기다린다
|
|
5403
|
+
const table = [...this.tabIds.entries()]
|
|
5404
|
+
.map(([tab, list]) => `${this.tabName(tab)}:[${list.join(',')}]`)
|
|
5405
|
+
.join(' ');
|
|
5406
|
+
this.bindLog(sessionId, `unbound (tabid-miss) tabId=${tabId} tabs=${table}`);
|
|
5407
|
+
return null;
|
|
5408
|
+
}
|
|
4556
5409
|
if (pick.mode === 'pid-miss') {
|
|
4557
5410
|
const table = [...this.tabPids.entries()]
|
|
4558
5411
|
.map(([tab, list]) => `${this.tabName(tab)}:[${list.join(',')}]`)
|
|
@@ -4607,6 +5460,59 @@ WorkNotifyService = __decorate([
|
|
|
4607
5460
|
|
|
4608
5461
|
|
|
4609
5462
|
|
|
5463
|
+
/***/ },
|
|
5464
|
+
|
|
5465
|
+
/***/ "./src/order.ts"
|
|
5466
|
+
/*!**********************!*\
|
|
5467
|
+
!*** ./src/order.ts ***!
|
|
5468
|
+
\**********************/
|
|
5469
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
5470
|
+
|
|
5471
|
+
__webpack_require__.r(__webpack_exports__);
|
|
5472
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
5473
|
+
/* harmony export */ STATUS_ORDER: () => (/* binding */ STATUS_ORDER),
|
|
5474
|
+
/* harmony export */ countByStatus: () => (/* binding */ countByStatus),
|
|
5475
|
+
/* harmony export */ sortTabsByStatus: () => (/* binding */ sortTabsByStatus),
|
|
5476
|
+
/* harmony export */ statusRank: () => (/* binding */ statusRank)
|
|
5477
|
+
/* harmony export */ });
|
|
5478
|
+
/** 표시·정렬 우선순위. 앞이 더 급하다 */
|
|
5479
|
+
const STATUS_ORDER = ['waiting', 'error', 'limited', 'running', 'done', 'idle'];
|
|
5480
|
+
/** 우선순위 숫자 — 작을수록 앞. 모르는 값은 맨 뒤로 보낸다 */
|
|
5481
|
+
function statusRank(status) {
|
|
5482
|
+
const i = STATUS_ORDER.indexOf(status);
|
|
5483
|
+
return i < 0 ? STATUS_ORDER.length : i;
|
|
5484
|
+
}
|
|
5485
|
+
/**
|
|
5486
|
+
* 상태별 개수. `STATUS_ORDER` 순서로, 0 인 상태는 빼고 돌려준다 — 헤더에 그대로 그리면 된다.
|
|
5487
|
+
*/
|
|
5488
|
+
function countByStatus(tabs, statusOf) {
|
|
5489
|
+
var _a, _b;
|
|
5490
|
+
const counts = new Map();
|
|
5491
|
+
for (const tab of tabs) {
|
|
5492
|
+
const s = statusOf(tab);
|
|
5493
|
+
counts.set(s, ((_a = counts.get(s)) !== null && _a !== void 0 ? _a : 0) + 1);
|
|
5494
|
+
}
|
|
5495
|
+
const out = [];
|
|
5496
|
+
for (const s of STATUS_ORDER) {
|
|
5497
|
+
const n = (_b = counts.get(s)) !== null && _b !== void 0 ? _b : 0;
|
|
5498
|
+
if (n > 0) {
|
|
5499
|
+
out.push({ status: s, count: n });
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
return out;
|
|
5503
|
+
}
|
|
5504
|
+
/**
|
|
5505
|
+
* 상태 우선순위로 정렬한 **복사본**. 같은 상태끼리는 원래 탭 순서를 지킨다(안정 정렬).
|
|
5506
|
+
* 원본 배열(`app.tabs`)은 건드리지 않는다 — Tabby 의 탭 순서는 사용자의 것이다.
|
|
5507
|
+
*/
|
|
5508
|
+
function sortTabsByStatus(tabs, statusOf) {
|
|
5509
|
+
return tabs
|
|
5510
|
+
.map((tab, index) => ({ tab, index, rank: statusRank(statusOf(tab)) }))
|
|
5511
|
+
.sort((a, b) => a.rank - b.rank || a.index - b.index)
|
|
5512
|
+
.map(x => x.tab);
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
|
|
4610
5516
|
/***/ },
|
|
4611
5517
|
|
|
4612
5518
|
/***/ "./src/profile.service.ts"
|
|
@@ -4668,9 +5574,7 @@ let AgentDeckProfileService = class AgentDeckProfileService {
|
|
|
4668
5574
|
args: [...(cfg.rootProfileArgs || [])],
|
|
4669
5575
|
cwd: cfg.rootProfileCwd,
|
|
4670
5576
|
env: { ...(cfg.rootProfileEnv || {}) },
|
|
4671
|
-
// 권한
|
|
4672
|
-
// 띄울지는 사용자의 몫이다. 단 프로필에 runAsAdministrator 를 켜면 Tabby 가 셸을
|
|
4673
|
-
// UAC.exe 릴레이로 감싸고 그 릴레이가 폭 1칸을 잃어 TUI 입력창이 깨진다(README 알려진 문제).
|
|
5577
|
+
// 권한 승격은 이 플러그인이 다루지 않는다 — Tabby 를 어떤 권한으로 띄울지는 사용자의 몫이다.
|
|
4674
5578
|
},
|
|
4675
5579
|
};
|
|
4676
5580
|
if (existing) {
|
|
@@ -4812,6 +5716,153 @@ function extractPrompt(lines) {
|
|
|
4812
5716
|
}
|
|
4813
5717
|
|
|
4814
5718
|
|
|
5719
|
+
/***/ },
|
|
5720
|
+
|
|
5721
|
+
/***/ "./src/reason.ts"
|
|
5722
|
+
/*!***********************!*\
|
|
5723
|
+
!*** ./src/reason.ts ***!
|
|
5724
|
+
\***********************/
|
|
5725
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
5726
|
+
|
|
5727
|
+
__webpack_require__.r(__webpack_exports__);
|
|
5728
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
5729
|
+
/* harmony export */ shortenReason: () => (/* binding */ shortenReason)
|
|
5730
|
+
/* harmony export */ });
|
|
5731
|
+
/**
|
|
5732
|
+
* 승인대기 이유 줄이기 — Notification 훅의 message 원문을 배지에 붙일 짧은 한국어로 바꾼다.
|
|
5733
|
+
*
|
|
5734
|
+
* 훅(hooks/agentdeck-notify.ps1)은 원문을 그대로 보내고, 줄이는 규칙은 여기 한 곳에만 둔다.
|
|
5735
|
+
* Claude Code 가 문구를 바꾸면 이 파일만 고치면 되고, 훅을 다시 설치할 필요가 없다.
|
|
5736
|
+
*
|
|
5737
|
+
* 알려진 문구 (2026-09 기준):
|
|
5738
|
+
* "Claude needs your permission to use Bash" -> "Bash 권한"
|
|
5739
|
+
* "Claude is waiting for your input" -> "입력 대기"
|
|
5740
|
+
* 플랜 승인 / 질문 응답도 같은 요령으로 잡는다.
|
|
5741
|
+
* 한도 도달(StopFailure error=rate_limit 의 last_assistant_message):
|
|
5742
|
+
* "You've hit your session limit · resets 12pm (Asia/Seoul)" -> "12pm 리셋"
|
|
5743
|
+
* 리셋 시각이 없으면 빈 값 — 배지가 이미 `⛔ 한도 도달` 이라 같은 말을 되풀이하지 않는다.
|
|
5744
|
+
* 모르는 문구는 앞 24자만 남긴다 — 배지 한 줄을 넘기지 않기 위해서다.
|
|
5745
|
+
*/
|
|
5746
|
+
/** 모르는 문구를 잘라 낼 길이 */
|
|
5747
|
+
const UNKNOWN_MAX = 24;
|
|
5748
|
+
function shortenReason(msg) {
|
|
5749
|
+
const m = (msg || '').replace(/\s+/g, ' ').trim();
|
|
5750
|
+
if (!m) {
|
|
5751
|
+
return '';
|
|
5752
|
+
}
|
|
5753
|
+
// 도구 승인 — 도구 이름만 남긴다 (Bash, Edit, Write, mcp__vs-mcp__Xxx …). MCP 이름의 '-' 도 포함
|
|
5754
|
+
const tool = /permission to use ([\w-]+)/i.exec(m);
|
|
5755
|
+
if (tool) {
|
|
5756
|
+
return tool[1] + ' 권한';
|
|
5757
|
+
}
|
|
5758
|
+
if (/waiting for your input/i.test(m)) {
|
|
5759
|
+
return '입력 대기';
|
|
5760
|
+
}
|
|
5761
|
+
// 한도 도달 — 리셋 시각만 남긴다. 실측 문구 3형:
|
|
5762
|
+
// "You've hit your session limit · resets 12pm (Asia/Seoul)"
|
|
5763
|
+
// "You've reached your weekly limit · resets Monday 9am"
|
|
5764
|
+
// "Claude usage limit reached. Your limit will reset at 7pm (Asia/Seoul)."
|
|
5765
|
+
if (/\b(?:hit|reached|exceeded)\b.*\blimit\b|usage limit|limit reached|limit will reset/i.test(m)) {
|
|
5766
|
+
const reset = /\breset(?:s)?\s+(?:at\s+)?(.+?)\s*(?:\(|\.\s*$|$)/i.exec(m);
|
|
5767
|
+
return reset ? reset[1].trim() + ' 리셋' : '';
|
|
5768
|
+
}
|
|
5769
|
+
if (/plan/i.test(m)) {
|
|
5770
|
+
return '플랜 승인';
|
|
5771
|
+
}
|
|
5772
|
+
if (/question|answer/i.test(m)) {
|
|
5773
|
+
return '질문 응답';
|
|
5774
|
+
}
|
|
5775
|
+
return m.slice(0, UNKNOWN_MAX).trimEnd();
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
|
|
5779
|
+
/***/ },
|
|
5780
|
+
|
|
5781
|
+
/***/ "./src/recovery.service.ts"
|
|
5782
|
+
/*!*********************************!*\
|
|
5783
|
+
!*** ./src/recovery.service.ts ***!
|
|
5784
|
+
\*********************************/
|
|
5785
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
5786
|
+
|
|
5787
|
+
__webpack_require__.r(__webpack_exports__);
|
|
5788
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
5789
|
+
/* harmony export */ AgentDeckRecoveryService: () => (/* binding */ AgentDeckRecoveryService)
|
|
5790
|
+
/* harmony export */ });
|
|
5791
|
+
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
|
|
5792
|
+
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
|
|
5793
|
+
/* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
|
|
5794
|
+
/* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
|
|
5795
|
+
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
5796
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
5797
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5798
|
+
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;
|
|
5799
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
5800
|
+
};
|
|
5801
|
+
var __metadata = (undefined && undefined.__metadata) || function (k, v) {
|
|
5802
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
5803
|
+
};
|
|
5804
|
+
|
|
5805
|
+
|
|
5806
|
+
/**
|
|
5807
|
+
* "기동 시 탭 복원 끄기" — Tabby 의 `recoverTabs` 를 강제로 false 에 묶는다.
|
|
5808
|
+
*
|
|
5809
|
+
* 왜 따로 있나: Tabby 는 종료 때 열려 있던 탭을 `localStorage.tabsRecovery` 에 저장하고
|
|
5810
|
+
* 다음 기동에서 `config.store.recoverTabs` 가 참이면 되살린다(tabby-core/dist/index.js:5003).
|
|
5811
|
+
* 그런데 복원되는 것은 **셸만**이다 — 그 안에서 돌던 Claude Code 세션은 이미 죽어 있어
|
|
5812
|
+
* 되살아난 탭은 빈 프롬프트일 뿐이고, 사이드바에는 주인 없는 탭이 줄줄이 남는다.
|
|
5813
|
+
* 그래서 사용자가 고르면 "무조건 빈 상태로 시작" 을 보장한다.
|
|
5814
|
+
*
|
|
5815
|
+
* 두 상태만 있다:
|
|
5816
|
+
* - `agentDeck.noTabRecovery = false` : 순정. Tabby 의 `recoverTabs` 값을 건드리지 않는다.
|
|
5817
|
+
* - `agentDeck.noTabRecovery = true` : 무조건 끔. Tabby 설정 창에서 `recoverTabs` 를 다시 켜도
|
|
5818
|
+
* 기동 때와 설정이 바뀔 때마다 false 로 되돌린다(`enforce`).
|
|
5819
|
+
*
|
|
5820
|
+
* 기동 분기(5003)는 `config.ready$` 직후라 플러그인보다 먼저 지나간다 — 그래서 값은 토글 순간에
|
|
5821
|
+
* config.yaml 에 써 두고, 여기서는 그 뒤로 누가 뒤집어 놓는 경우만 막는다.
|
|
5822
|
+
*/
|
|
5823
|
+
let AgentDeckRecoveryService = class AgentDeckRecoveryService {
|
|
5824
|
+
constructor(app, config) {
|
|
5825
|
+
this.app = app;
|
|
5826
|
+
this.config = config;
|
|
5827
|
+
}
|
|
5828
|
+
init() {
|
|
5829
|
+
this.app.ready$.subscribe(() => this.enforce());
|
|
5830
|
+
this.config.changed$.subscribe(() => this.enforce());
|
|
5831
|
+
}
|
|
5832
|
+
/**
|
|
5833
|
+
* 토글 진입점. 켜면 `recoverTabs=false` + 이미 저장된 복원 목록도 지운다 —
|
|
5834
|
+
* 남겨 두면 나중에 순정으로 되돌렸을 때 그때의 낡은 탭들이 되살아난다.
|
|
5835
|
+
* 끄면 Tabby 기본값(true)으로 되돌린다.
|
|
5836
|
+
*/
|
|
5837
|
+
setNoTabRecovery(on) {
|
|
5838
|
+
this.config.store.agentDeck.noTabRecovery = on;
|
|
5839
|
+
this.config.store.recoverTabs = !on;
|
|
5840
|
+
if (on) {
|
|
5841
|
+
try {
|
|
5842
|
+
delete window.localStorage.tabsRecovery;
|
|
5843
|
+
}
|
|
5844
|
+
catch { /* 격리/비브라우저 환경 — 없으면 지울 것도 없다 */ }
|
|
5845
|
+
}
|
|
5846
|
+
this.config.save();
|
|
5847
|
+
}
|
|
5848
|
+
/** noTabRecovery 가 켜져 있는데 recoverTabs 가 참으로 돌아와 있으면 다시 끈다. 필요할 때만 저장한다 */
|
|
5849
|
+
enforce() {
|
|
5850
|
+
var _a;
|
|
5851
|
+
if (!((_a = this.config.store.agentDeck) === null || _a === void 0 ? void 0 : _a.noTabRecovery) || this.config.store.recoverTabs === false) {
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
this.config.store.recoverTabs = false;
|
|
5855
|
+
this.config.save();
|
|
5856
|
+
}
|
|
5857
|
+
};
|
|
5858
|
+
AgentDeckRecoveryService = __decorate([
|
|
5859
|
+
(0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)({ providedIn: 'root' }),
|
|
5860
|
+
__metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.AppService,
|
|
5861
|
+
tabby_core__WEBPACK_IMPORTED_MODULE_1__.ConfigService])
|
|
5862
|
+
], AgentDeckRecoveryService);
|
|
5863
|
+
|
|
5864
|
+
|
|
5865
|
+
|
|
4815
5866
|
/***/ },
|
|
4816
5867
|
|
|
4817
5868
|
/***/ "./src/screen.ts"
|
|
@@ -4988,6 +6039,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
4988
6039
|
/* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
|
|
4989
6040
|
/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./config */ "./src/config.ts");
|
|
4990
6041
|
/* harmony import */ var _claudeHooks__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./claudeHooks */ "./src/claudeHooks.ts");
|
|
6042
|
+
/* harmony import */ var _recovery_service__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./recovery.service */ "./src/recovery.service.ts");
|
|
4991
6043
|
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
4992
6044
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4993
6045
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -5001,6 +6053,7 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
|
|
|
5001
6053
|
|
|
5002
6054
|
|
|
5003
6055
|
|
|
6056
|
+
|
|
5004
6057
|
const DOCK_LABELS = {
|
|
5005
6058
|
left: '왼쪽',
|
|
5006
6059
|
right: '오른쪽',
|
|
@@ -5010,7 +6063,7 @@ const DOCK_LABELS = {
|
|
|
5010
6063
|
/**
|
|
5011
6064
|
* 설정 > AgentDeck 탭.
|
|
5012
6065
|
*
|
|
5013
|
-
* 사용자가 실제로 바꿀 일이 있는
|
|
6066
|
+
* 사용자가 실제로 바꿀 일이 있는 것만 노출한다 — 레이아웃 on/off, 기동 시 탭 복원 끄기, 작업 루트 프로필.
|
|
5014
6067
|
* 나머지(화면비, 사이드바 폭, 투명도, 상태 감지, 입력 가로채기, 렌더 보정, 통보 채널)는
|
|
5015
6068
|
* 손댈 이유가 거의 없고 잘못 만지면 화면이 깨지는 값이라 `config.ts` 의 기본값으로 고정하고
|
|
5016
6069
|
* 설정 창에서는 뺐다. 정말 필요하면 설정 파일의 `agentDeck.*` 를 직접 고친다.
|
|
@@ -5019,8 +6072,9 @@ const DOCK_LABELS = {
|
|
|
5019
6072
|
* 루트 프로필은 기동 시 한 번만 읽으므로 재시작이 필요하다고 적어 둔다.
|
|
5020
6073
|
*/
|
|
5021
6074
|
let AgentDeckSettingsTabComponent = class AgentDeckSettingsTabComponent {
|
|
5022
|
-
constructor(config, platform) {
|
|
6075
|
+
constructor(config, recovery, platform) {
|
|
5023
6076
|
this.config = config;
|
|
6077
|
+
this.recovery = recovery;
|
|
5024
6078
|
this.platform = platform;
|
|
5025
6079
|
/** 이 환경에서 훅을 걸 수 있나 (통보 스크립트가 PowerShell 이라 Windows 전용) */
|
|
5026
6080
|
this.hooksSupported = (0,_claudeHooks__WEBPACK_IMPORTED_MODULE_3__.hooksSupported)();
|
|
@@ -5139,6 +6193,18 @@ AgentDeckSettingsTabComponent = __decorate([
|
|
|
5139
6193
|
</button>
|
|
5140
6194
|
</div>
|
|
5141
6195
|
|
|
6196
|
+
<div class="form-line">
|
|
6197
|
+
<div class="header">
|
|
6198
|
+
<div class="title">기동 시 탭 복원 끄기</div>
|
|
6199
|
+
<div class="description">
|
|
6200
|
+
켜면 Tabby 를 다시 열 때 지난 탭을 되살리지 않고 빈 상태로 시작한다 —
|
|
6201
|
+
복원되는 건 셸뿐이고 그 안의 Claude 세션은 이미 끝나 있어 의미가 없다.
|
|
6202
|
+
끄면 Tabby 순정 동작(<code>recoverTabs</code>)으로 돌아간다. 다음 기동부터 적용.
|
|
6203
|
+
</div>
|
|
6204
|
+
</div>
|
|
6205
|
+
<toggle [ngModel]="config.store.agentDeck.noTabRecovery" (ngModelChange)="recovery.setNoTabRecovery($event)"></toggle>
|
|
6206
|
+
</div>
|
|
6207
|
+
|
|
5142
6208
|
<h3 class="mt-4 mb-3">Claude Code 연동</h3>
|
|
5143
6209
|
<div class="text-muted mb-3">
|
|
5144
6210
|
에이전트가 "지시를 받았다 / 승인을 기다린다 / 끝났다" 를 직접 알려주게 한다.
|
|
@@ -5158,8 +6224,8 @@ AgentDeckSettingsTabComponent = __decorate([
|
|
|
5158
6224
|
<div class="title">상태 통보 훅</div>
|
|
5159
6225
|
<div class="description">
|
|
5160
6226
|
{{hooksOn ? '설치됨' : '설치 안 됨'}} — <code>~/.claude/settings.json</code> 의
|
|
5161
|
-
UserPromptSubmit / Notification /
|
|
5162
|
-
쓰기 전에 같은 폴더에 백업을 남긴다.
|
|
6227
|
+
UserPromptSubmit / Notification / PostToolUse / PostToolUseFailure / PermissionDenied / Stop / StopFailure 에 걸린다.
|
|
6228
|
+
다른 훅은 건드리지 않고, 쓰기 전에 같은 폴더에 백업을 남긴다.
|
|
5163
6229
|
<span *ngIf="hookError" class="text-danger d-block mt-1">{{hookError}}</span>
|
|
5164
6230
|
</div>
|
|
5165
6231
|
</div>
|
|
@@ -5231,6 +6297,7 @@ AgentDeckSettingsTabComponent = __decorate([
|
|
|
5231
6297
|
`,
|
|
5232
6298
|
}),
|
|
5233
6299
|
__metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.ConfigService,
|
|
6300
|
+
_recovery_service__WEBPACK_IMPORTED_MODULE_4__.AgentDeckRecoveryService,
|
|
5234
6301
|
tabby_core__WEBPACK_IMPORTED_MODULE_1__.PlatformService])
|
|
5235
6302
|
], AgentDeckSettingsTabComponent);
|
|
5236
6303
|
|
|
@@ -5307,6 +6374,11 @@ var __decorate = (undefined && undefined.__decorate) || function (decorators, ta
|
|
|
5307
6374
|
};
|
|
5308
6375
|
|
|
5309
6376
|
|
|
6377
|
+
/**
|
|
6378
|
+
* 부연(reason)을 들고 있을 수 있는 상태. 나머지 상태로 바뀌면 무조건 비운다 —
|
|
6379
|
+
* 끝난 뒤에도 "Bash 권한" 이 남아 있으면 지금 승인을 기다리는 것처럼 읽히기 때문이다
|
|
6380
|
+
*/
|
|
6381
|
+
const REASON_STATUSES = ['waiting', 'limited', 'error'];
|
|
5310
6382
|
/**
|
|
5311
6383
|
* 탭별 작업 상태 보관소.
|
|
5312
6384
|
* 상태의 출처는 3가지 — 자동 감지(decorator), OSC 시퀀스(Claude Code 훅), 사이드바에서의 수동 지정.
|
|
@@ -5329,7 +6401,7 @@ let WorkStatusService = class WorkStatusService {
|
|
|
5329
6401
|
let s = this.states.get(tab);
|
|
5330
6402
|
if (!s) {
|
|
5331
6403
|
s = {
|
|
5332
|
-
status: 'idle', label: '', since: Date.now(), pinned: false,
|
|
6404
|
+
status: 'idle', label: '', reason: '', since: Date.now(), pinned: false,
|
|
5333
6405
|
lastOutput: 0, busyMode: false, lastBusy: 0,
|
|
5334
6406
|
};
|
|
5335
6407
|
this.states.set(tab, s);
|
|
@@ -5344,10 +6416,14 @@ let WorkStatusService = class WorkStatusService {
|
|
|
5344
6416
|
}
|
|
5345
6417
|
s.status = status;
|
|
5346
6418
|
s.since = Date.now();
|
|
6419
|
+
this.clearReasonUnlessWaiting(s);
|
|
5347
6420
|
this.changed.next();
|
|
5348
6421
|
}
|
|
5349
|
-
/**
|
|
5350
|
-
|
|
6422
|
+
/**
|
|
6423
|
+
* 수동/훅 지정 — 이후 자동 감지보다 우선한다.
|
|
6424
|
+
* `reason` 은 상태의 부연(승인 이유 / 한도 리셋 시각 / 오류 코드) — REASON_STATUSES 일 때만 담는다
|
|
6425
|
+
*/
|
|
6426
|
+
setManual(tab, status, label, reason) {
|
|
5351
6427
|
const s = this.get(tab);
|
|
5352
6428
|
s.status = status;
|
|
5353
6429
|
s.since = Date.now();
|
|
@@ -5355,6 +6431,23 @@ let WorkStatusService = class WorkStatusService {
|
|
|
5355
6431
|
if (label !== undefined) {
|
|
5356
6432
|
s.label = label;
|
|
5357
6433
|
}
|
|
6434
|
+
s.reason = REASON_STATUSES.includes(status) ? (reason !== null && reason !== void 0 ? reason : '') : '';
|
|
6435
|
+
this.changed.next();
|
|
6436
|
+
}
|
|
6437
|
+
/**
|
|
6438
|
+
* 승인대기 → 진행중. 사람이 승인한 순간을 훅은 알려주지 않는다 — 다음 훅(PostToolUse)은
|
|
6439
|
+
* 도구가 **끝난 뒤**라 긴 빌드 동안 사이드바가 승인대기에 박혀 있다. 그래서 화면에 "esc to interrupt"
|
|
6440
|
+
* 같은 작업 중 표시가 다시 보이면 여기로 올린다 (detect.ts). pinned·label 은 그대로 둔다 —
|
|
6441
|
+
* 훅이 고정한 탭도 이 전이만은 허용해야 하기 때문이다. waiting 이 아니면 아무것도 하지 않는다
|
|
6442
|
+
*/
|
|
6443
|
+
resume(tab) {
|
|
6444
|
+
const s = this.get(tab);
|
|
6445
|
+
if (s.status !== 'waiting') {
|
|
6446
|
+
return;
|
|
6447
|
+
}
|
|
6448
|
+
s.status = 'running';
|
|
6449
|
+
s.since = Date.now();
|
|
6450
|
+
s.reason = '';
|
|
5358
6451
|
this.changed.next();
|
|
5359
6452
|
}
|
|
5360
6453
|
setLabel(tab, label) {
|
|
@@ -5367,8 +6460,15 @@ let WorkStatusService = class WorkStatusService {
|
|
|
5367
6460
|
s.pinned = false;
|
|
5368
6461
|
s.status = 'idle';
|
|
5369
6462
|
s.since = Date.now();
|
|
6463
|
+
this.clearReasonUnlessWaiting(s);
|
|
5370
6464
|
this.changed.next();
|
|
5371
6465
|
}
|
|
6466
|
+
/** 부연을 가질 수 없는 상태에는 이유가 남아 있을 수 없다 */
|
|
6467
|
+
clearReasonUnlessWaiting(s) {
|
|
6468
|
+
if (!REASON_STATUSES.includes(s.status)) {
|
|
6469
|
+
s.reason = '';
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
5372
6472
|
markOutput(tab) {
|
|
5373
6473
|
this.get(tab).lastOutput = Date.now();
|
|
5374
6474
|
}
|
|
@@ -5403,6 +6503,7 @@ let WorkStatusService = class WorkStatusService {
|
|
|
5403
6503
|
if (from && Date.now() - from > idleAfterMs) {
|
|
5404
6504
|
s.status = 'idle';
|
|
5405
6505
|
s.since = Date.now();
|
|
6506
|
+
this.clearReasonUnlessWaiting(s);
|
|
5406
6507
|
return true;
|
|
5407
6508
|
}
|
|
5408
6509
|
return false;
|
|
@@ -5414,6 +6515,71 @@ WorkStatusService = __decorate([
|
|
|
5414
6515
|
|
|
5415
6516
|
|
|
5416
6517
|
|
|
6518
|
+
/***/ },
|
|
6519
|
+
|
|
6520
|
+
/***/ "./src/tabenv.ts"
|
|
6521
|
+
/*!***********************!*\
|
|
6522
|
+
!*** ./src/tabenv.ts ***!
|
|
6523
|
+
\***********************/
|
|
6524
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
6525
|
+
|
|
6526
|
+
__webpack_require__.r(__webpack_exports__);
|
|
6527
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
6528
|
+
/* harmony export */ TAB_ENV: () => (/* binding */ TAB_ENV),
|
|
6529
|
+
/* harmony export */ newTabId: () => (/* binding */ newTabId),
|
|
6530
|
+
/* harmony export */ pickTabByTabId: () => (/* binding */ pickTabByTabId)
|
|
6531
|
+
/* harmony export */ });
|
|
6532
|
+
/**
|
|
6533
|
+
* 훅 보고를 탭에 직결하는 열쇠 — 탭 셸에 심는 환경변수와 그것으로 탭을 고르는 순수 규칙.
|
|
6534
|
+
* Angular/Tabby 에 기대지 않아 단위 테스트가 된다 (test/tabenv.test.js).
|
|
6535
|
+
*
|
|
6536
|
+
* 왜 환경변수인가 — 0.2.1 의 프로세스 계보(bind.ts)는 훅이 매 세션 `Win32_Process` 전체를
|
|
6537
|
+
* 훑어(~450ms) 조상 PID 를 보내고 플러그인이 탭별 셸 PID 와 맞추는 방식이다. 맞기는 하지만
|
|
6538
|
+
* 훅이 느리고, 셸 PID 를 pty 에 비동기로 물어야 해서 표가 늦게 채워지는 창이 있다.
|
|
6539
|
+
* 플러그인이 탭을 열 때 셸 환경에 `AGENTDECK_TAB=<id>` 를 심어 두면 그 셸에서 뜬 claude 와
|
|
6540
|
+
* 그 훅은 환경을 그대로 물려받으므로, 훅은 `$env:AGENTDECK_TAB` 을 읽어 보내기만 하면 되고
|
|
6541
|
+
* 플러그인은 표에서 바로 찾는다. 계보 조회가 통째로 빠진다.
|
|
6542
|
+
*
|
|
6543
|
+
* 계보는 폴백으로 남는다 — 이 버전 이전에 열린 탭, 재시작으로 복원된 탭(셸이 이미 떠 있어
|
|
6544
|
+
* 새 값을 심을 수 없다), 사람이 셸 밖에서 부른 훅은 환경변수가 없다.
|
|
6545
|
+
*/
|
|
6546
|
+
const TAB_ENV = 'AGENTDECK_TAB';
|
|
6547
|
+
/**
|
|
6548
|
+
* 탭 하나를 가리키는 짧은 무작위 id — 12 hex 자리(48비트). 같은 Tabby 안에서만 유일하면 되므로
|
|
6549
|
+
* 이 길이로 충분하고, 셸 프롬프트나 진단 로그에 찍혀도 거슬리지 않는다.
|
|
6550
|
+
* Electron 렌더러와 Node 20 은 `globalThis.crypto.getRandomValues` 를 둘 다 갖고 있다.
|
|
6551
|
+
*/
|
|
6552
|
+
function newTabId() {
|
|
6553
|
+
const bytes = new Uint8Array(6);
|
|
6554
|
+
const c = globalThis.crypto;
|
|
6555
|
+
if (c && typeof c.getRandomValues === 'function') {
|
|
6556
|
+
c.getRandomValues(bytes);
|
|
6557
|
+
}
|
|
6558
|
+
else {
|
|
6559
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
6560
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
6561
|
+
}
|
|
6562
|
+
}
|
|
6563
|
+
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
6564
|
+
}
|
|
6565
|
+
/**
|
|
6566
|
+
* 훅이 보낸 tabId 를 가진 탭(루트)을 찾는다. 분할 탭은 패널마다 id 가 다르므로 루트 하나에
|
|
6567
|
+
* 여러 id 가 달린다. 없으면 null — 추측하지 않는다 (호출자가 계보/폴백으로 넘어간다).
|
|
6568
|
+
*/
|
|
6569
|
+
function pickTabByTabId(tabId, tabIds) {
|
|
6570
|
+
const want = typeof tabId === 'string' ? tabId.trim() : '';
|
|
6571
|
+
if (!want) {
|
|
6572
|
+
return null;
|
|
6573
|
+
}
|
|
6574
|
+
for (const [tab, ids] of tabIds) {
|
|
6575
|
+
if (ids.includes(want)) {
|
|
6576
|
+
return tab;
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
return null;
|
|
6580
|
+
}
|
|
6581
|
+
|
|
6582
|
+
|
|
5417
6583
|
/***/ },
|
|
5418
6584
|
|
|
5419
6585
|
/***/ "net"
|
|
@@ -5613,7 +6779,7 @@ let __webpack_exports__ = {};
|
|
|
5613
6779
|
\**********************/
|
|
5614
6780
|
__webpack_require__.r(__webpack_exports__);
|
|
5615
6781
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
5616
|
-
/* harmony export */ STATUS_STYLES: () => (/* reexport safe */
|
|
6782
|
+
/* harmony export */ STATUS_STYLES: () => (/* reexport safe */ _api__WEBPACK_IMPORTED_MODULE_17__.STATUS_STYLES),
|
|
5617
6783
|
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
|
|
5618
6784
|
/* harmony export */ });
|
|
5619
6785
|
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
|
|
@@ -5634,10 +6800,12 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
5634
6800
|
/* harmony import */ var _deck_service__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./deck.service */ "./src/deck.service.ts");
|
|
5635
6801
|
/* harmony import */ var _profile_service__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./profile.service */ "./src/profile.service.ts");
|
|
5636
6802
|
/* harmony import */ var _notify_service__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./notify.service */ "./src/notify.service.ts");
|
|
5637
|
-
/* harmony import */ var
|
|
5638
|
-
/* harmony import */ var
|
|
5639
|
-
/* harmony import */ var
|
|
5640
|
-
/* harmony import */ var
|
|
6803
|
+
/* harmony import */ var _alert_service__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./alert.service */ "./src/alert.service.ts");
|
|
6804
|
+
/* harmony import */ var _recovery_service__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./recovery.service */ "./src/recovery.service.ts");
|
|
6805
|
+
/* harmony import */ var _settings__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./settings */ "./src/settings.ts");
|
|
6806
|
+
/* harmony import */ var _settings_component__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./settings.component */ "./src/settings.component.ts");
|
|
6807
|
+
/* harmony import */ var _styles_scss__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./styles.scss */ "./src/styles.scss");
|
|
6808
|
+
/* harmony import */ var _api__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./api */ "./src/api.ts");
|
|
5641
6809
|
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
5642
6810
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
5643
6811
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -5663,12 +6831,17 @@ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
|
|
|
5663
6831
|
|
|
5664
6832
|
|
|
5665
6833
|
|
|
6834
|
+
|
|
6835
|
+
|
|
5666
6836
|
let AgentDeckModule = class AgentDeckModule {
|
|
5667
|
-
constructor(deck, profiles, notify) {
|
|
5668
|
-
//
|
|
6837
|
+
constructor(deck, profiles, notify, alert, recovery) {
|
|
6838
|
+
// 다섯 서비스 모두 app.ready$ 를 기다렸다가 스스로 붙는다.
|
|
6839
|
+
// alert 는 deck 이 만드는 `__agentdeck` 에 붙으므로 deck 뒤에 둔다
|
|
5669
6840
|
deck.init();
|
|
5670
6841
|
profiles.init();
|
|
5671
6842
|
notify.init();
|
|
6843
|
+
alert.init();
|
|
6844
|
+
recovery.init();
|
|
5672
6845
|
}
|
|
5673
6846
|
};
|
|
5674
6847
|
AgentDeckModule = __decorate([
|
|
@@ -5683,15 +6856,17 @@ AgentDeckModule = __decorate([
|
|
|
5683
6856
|
{ provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.ConfigProvider, useClass: _config__WEBPACK_IMPORTED_MODULE_6__.AgentDeckConfigProvider, multi: true },
|
|
5684
6857
|
{ provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.HotkeyProvider, useClass: _hotkeys__WEBPACK_IMPORTED_MODULE_7__.AgentDeckHotkeyProvider, multi: true },
|
|
5685
6858
|
{ provide: tabby_terminal__WEBPACK_IMPORTED_MODULE_5__.TerminalDecorator, useClass: _decorator__WEBPACK_IMPORTED_MODULE_8__.AgentDeckDecorator, multi: true },
|
|
5686
|
-
{ provide: tabby_settings__WEBPACK_IMPORTED_MODULE_4__.SettingsTabProvider, useClass:
|
|
6859
|
+
{ provide: tabby_settings__WEBPACK_IMPORTED_MODULE_4__.SettingsTabProvider, useClass: _settings__WEBPACK_IMPORTED_MODULE_14__.AgentDeckSettingsTabProvider, multi: true },
|
|
5687
6860
|
],
|
|
5688
6861
|
declarations: [
|
|
5689
|
-
|
|
6862
|
+
_settings_component__WEBPACK_IMPORTED_MODULE_15__.AgentDeckSettingsTabComponent,
|
|
5690
6863
|
],
|
|
5691
6864
|
}),
|
|
5692
6865
|
__metadata("design:paramtypes", [_deck_service__WEBPACK_IMPORTED_MODULE_9__.AgentDeckService,
|
|
5693
6866
|
_profile_service__WEBPACK_IMPORTED_MODULE_10__.AgentDeckProfileService,
|
|
5694
|
-
_notify_service__WEBPACK_IMPORTED_MODULE_11__.WorkNotifyService
|
|
6867
|
+
_notify_service__WEBPACK_IMPORTED_MODULE_11__.WorkNotifyService,
|
|
6868
|
+
_alert_service__WEBPACK_IMPORTED_MODULE_12__.AgentDeckAlertService,
|
|
6869
|
+
_recovery_service__WEBPACK_IMPORTED_MODULE_13__.AgentDeckRecoveryService])
|
|
5695
6870
|
], AgentDeckModule);
|
|
5696
6871
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (AgentDeckModule);
|
|
5697
6872
|
|