tabby-agentdeck 1.1.3 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +150 -127
  2. package/LICENSE +21 -21
  3. package/README.ko.md +52 -590
  4. package/README.md +52 -562
  5. package/dist/accountBrowser.d.ts +4 -0
  6. package/dist/accountSession.d.ts +22 -0
  7. package/dist/accounts.d.ts +50 -0
  8. package/dist/agents.d.ts +15 -0
  9. package/dist/codexHooks.d.ts +23 -0
  10. package/dist/codexMeta.d.ts +87 -0
  11. package/dist/config.d.ts +110 -4
  12. package/dist/cursorVisibility.d.ts +33 -1
  13. package/dist/deck.service.d.ts +197 -20
  14. package/dist/devReload.service.d.ts +48 -0
  15. package/dist/devReload.stub.d.ts +19 -0
  16. package/dist/dock.d.ts +15 -0
  17. package/dist/i18n/af.d.ts +83 -0
  18. package/dist/i18n/bg.d.ts +83 -0
  19. package/dist/i18n/cs.d.ts +83 -0
  20. package/dist/i18n/da.d.ts +83 -0
  21. package/dist/i18n/de.d.ts +83 -0
  22. package/dist/i18n/en.d.ts +83 -0
  23. package/dist/i18n/es.d.ts +83 -0
  24. package/dist/i18n/fr.d.ts +83 -0
  25. package/dist/i18n/hr.d.ts +83 -0
  26. package/dist/i18n/id.d.ts +83 -0
  27. package/dist/i18n/it.d.ts +83 -0
  28. package/dist/i18n/ja.d.ts +83 -0
  29. package/dist/i18n/ko.d.ts +83 -0
  30. package/dist/i18n/pl.d.ts +83 -0
  31. package/dist/i18n/pt.d.ts +83 -0
  32. package/dist/i18n/ru.d.ts +83 -0
  33. package/dist/i18n/sidebar/af.d.ts +80 -0
  34. package/dist/i18n/sidebar/bg.d.ts +80 -0
  35. package/dist/i18n/sidebar/cs.d.ts +80 -0
  36. package/dist/i18n/sidebar/da.d.ts +80 -0
  37. package/dist/i18n/sidebar/de.d.ts +80 -0
  38. package/dist/i18n/sidebar/es.d.ts +80 -0
  39. package/dist/i18n/sidebar/fr.d.ts +80 -0
  40. package/dist/i18n/sidebar/hr.d.ts +80 -0
  41. package/dist/i18n/sidebar/id.d.ts +80 -0
  42. package/dist/i18n/sidebar/it.d.ts +80 -0
  43. package/dist/i18n/sidebar/ja.d.ts +80 -0
  44. package/dist/i18n/sidebar/pl.d.ts +80 -0
  45. package/dist/i18n/sidebar/pt.d.ts +80 -0
  46. package/dist/i18n/sidebar/ru.d.ts +80 -0
  47. package/dist/i18n/sidebar/sr.d.ts +80 -0
  48. package/dist/i18n/sidebar/sv.d.ts +80 -0
  49. package/dist/i18n/sidebar/tr.d.ts +80 -0
  50. package/dist/i18n/sidebar/uk.d.ts +80 -0
  51. package/dist/i18n/sidebar/zh-CN.d.ts +80 -0
  52. package/dist/i18n/sidebar/zh-TW.d.ts +80 -0
  53. package/dist/i18n/sr.d.ts +83 -0
  54. package/dist/i18n/sv.d.ts +83 -0
  55. package/dist/i18n/tr.d.ts +83 -0
  56. package/dist/i18n/uk.d.ts +83 -0
  57. package/dist/i18n/zh-CN.d.ts +83 -0
  58. package/dist/i18n/zh-TW.d.ts +83 -0
  59. package/dist/i18n.d.ts +3634 -0
  60. package/dist/index.d.ts +3 -1
  61. package/dist/index.js +109 -2
  62. package/dist/keybind.d.ts +121 -0
  63. package/dist/meta.d.ts +83 -0
  64. package/dist/nav.d.ts +29 -0
  65. package/dist/notify.service.d.ts +161 -1
  66. package/dist/project-root.d.ts +1 -1
  67. package/dist/reload.service.d.ts +57 -0
  68. package/dist/sessionLedger.d.ts +11 -6
  69. package/dist/sessionLedger.service.d.ts +3 -0
  70. package/dist/sessionMailbox.d.ts +31 -0
  71. package/dist/sessionSearch.d.ts +41 -0
  72. package/dist/sessionSearchPanel.d.ts +36 -0
  73. package/dist/sessionSlots.d.ts +9 -0
  74. package/dist/settings.component.d.ts +134 -8
  75. package/dist/sidebarI18n.d.ts +9 -0
  76. package/dist/sidebarLocales.d.ts +1 -0
  77. package/dist/status.service.d.ts +30 -2
  78. package/dist/statusLine.d.ts +27 -0
  79. package/dist/update.d.ts +19 -5
  80. package/dist/update.service.d.ts +29 -20
  81. package/dist/viewPanel.d.ts +13 -3
  82. package/dist/viewer.d.ts +2 -0
  83. package/hooks/agentdeck-codex-notify.ps1 +28 -25
  84. package/hooks/agentdeck-label.js +53 -53
  85. package/hooks/agentdeck-mailbox.mjs +103 -0
  86. package/hooks/agentdeck-notify.ps1 +43 -14
  87. package/hooks/agentdeck-status.ps1 +33 -33
  88. package/hooks/agentdeck-statusline.mjs +351 -0
  89. package/package.json +72 -68
  90. package/updater/agentdeck-update.ps1 +153 -130
  91. package/dist/index.js.map +0 -1
@@ -0,0 +1,36 @@
1
+ import { HistorySource } from './sessionSearch';
2
+ import { ResumeRow } from './sessionLedger';
3
+ interface HistoryHost {
4
+ sources(): Promise<HistorySource[]>;
5
+ resume(row: ResumeRow): void;
6
+ changed(): void;
7
+ korean(): boolean;
8
+ searchLabel(): string;
9
+ }
10
+ /** Local history UI. It has no dependency on the session messaging/MCP service. */
11
+ export declare class SessionSearchPanel {
12
+ private host;
13
+ readonly element: HTMLDivElement;
14
+ private input;
15
+ private status;
16
+ private results;
17
+ private engine;
18
+ private previewEngine;
19
+ private generation;
20
+ private timer;
21
+ private hits;
22
+ private visible;
23
+ private dialog;
24
+ private previewGeneration;
25
+ private errors;
26
+ private tr;
27
+ constructor(host: HistoryHost);
28
+ get active(): boolean;
29
+ update(expanded: boolean): void;
30
+ private search;
31
+ private highlight;
32
+ private renderResults;
33
+ private openPreview;
34
+ dispose(): void;
35
+ }
36
+ export {};
@@ -0,0 +1,9 @@
1
+ /** Human navigation only. Never pass a slot to a messaging transport. */
2
+ export declare class SessionSlots<T> {
3
+ private slots;
4
+ reconcile(sessions: readonly T[], preferred?: (session: T) => number | null): void;
5
+ numberOf(session: T): number | null;
6
+ get(number: number): T | null;
7
+ get full(): boolean;
8
+ get occupied(): number;
9
+ }
@@ -1,11 +1,22 @@
1
- import { ConfigService, PlatformService } from 'tabby-core';
1
+ import { AfterViewInit, ElementRef, NgZone, OnDestroy } from '@angular/core';
2
+ import { ConfigService, HotkeysService, LocaleService, PlatformService } from 'tabby-core';
3
+ import { KeyItem, BindingPlan } from './keybind';
2
4
  import { AgentDeckRecoveryService } from './recovery.service';
3
5
  import { AgentDeckUpdateService } from './update.service';
6
+ import { AgentDeckDevReloadService } from './devReload.service';
4
7
  /**
5
8
  * 설정 > AgentDeck 탭.
6
9
  *
7
10
  * 사용자가 실제로 바꿀 일이 있는 것만 노출한다 — 레이아웃 on/off, 기동 시 탭 복원 끄기,
8
- * 사이드바 목록(키보드 조작 · 서브에이전트 개수), 세션 그룹, 미리보기 패널, 작업 루트 프로필.
11
+ * 지난 세션 이어받기, 에이전트 상태 연동, 작업 루트 프로필, 문제 신고, (개발 설치) 개발자 옵션.
12
+ * 2026-09-14 유저 지시로 자동 업데이트·이어받기 개수·사이드바 목록·세션 그룹·미리보기 패널 절을
13
+ * 템플릿에서 주석 처리했다 — "알아서 세팅해 두고, 고치고 싶으면 설정 파일을 직접 고칠 일".
14
+ * 같은 날 `"지금 이 탭" 줄`(metaLine)과 `통보 채널`(notifyChannel) 스위치도 걷어냈다 —
15
+ * 앞의 것은 statusLine 을 연결해 놓고 줄만 끄는 조합에 뜻이 없고, 뒤의 것은 훅 설치/제거가
16
+ * 정답을 이미 알고 있다(`syncNotifyChannel`). 두 값 모두 config 에는 남아 있다.
17
+ * 상태 훅과 statusLine 을 따로 걸던 줄 셋도 `Claude 연동` · `Codex 연동` 둘로 합쳤다
18
+ * (`toggleClaude`) — 사용자가 정하는 것은 "이 에이전트를 사이드바에 붙일까" 하나뿐이다.
19
+ * 값은 `config.ts` 기본값이 그대로 쓰인다. 아래 옛 설명은 그 절들이 있던 시절 기준이다.
9
20
  * 나머지(화면비, 사이드바 폭, 투명도, 상태 감지, 붙여넣기/줄바꿈 가로채기, 렌더 보정)는
10
21
  * 손댈 이유가 거의 없고 잘못 만지면 화면이 깨지는 값이라 `config.ts` 의 기본값으로 고정하고
11
22
  * 설정 창에서는 뺐다. 정말 필요하면 설정 파일의 `agentDeck.*` 를 직접 고친다.
@@ -28,19 +39,37 @@ import { AgentDeckUpdateService } from './update.service';
28
39
  * (deck.service.ts `navCanFocus`/`moveNav`, notify.service `subagentEnabled`)
29
40
  * 토글하면 바로 듣는다 — 그래서 재시작 안내를 붙이지 않았다.
30
41
  */
31
- export declare class AgentDeckSettingsTabComponent {
42
+ export declare class AgentDeckSettingsTabComponent implements AfterViewInit, OnDestroy {
32
43
  config: ConfigService;
33
44
  recovery: AgentDeckRecoveryService;
34
45
  private update;
35
46
  private platform;
47
+ devReload: AgentDeckDevReloadService;
48
+ private host;
49
+ private locale;
50
+ private hotkeys;
51
+ private zone;
36
52
  /** 이 환경에서 훅을 걸 수 있나 (통보 스크립트가 PowerShell 이라 Windows 전용) */
37
53
  hooksSupported: boolean;
38
54
  /** 지금 걸려 있나 — 파일을 매번 읽지 않도록 화면용으로 들고 있는다 */
39
55
  hooksOn: boolean;
40
56
  codexHooksOn: boolean;
41
57
  codexHookError: string;
58
+ /**
59
+ * Codex 가 `/hooks` 에서 **꺼 둔** 우리 훅 (`config.toml` 의 `[hooks.state]`).
60
+ *
61
+ * 설치 여부와 따로 읽는다 — `hooks.json` 에 우리 항목이 있어도 Codex 가 꺼 두면
62
+ * 한 건도 오지 않는데, 그때 화면이 `연동됨` 이라고 말하면 사용자는 원인을 볼 수 없다
63
+ * (2026-09-14 실측: 상태 보고 폴더에 codex 파일 0건).
64
+ */
65
+ codexOff: string[];
42
66
  /** 훅을 쓰다 실패한 이유 — 조용히 삼키면 사용자는 버튼이 먹통인 줄 안다 */
43
67
  hookError: string;
68
+ /** statusLine 래퍼를 걸 수 있나 (스크립트가 패키지에 들어 있나) */
69
+ statusLineSupported: boolean;
70
+ /** 지금 감싸져 있나 */
71
+ statusLineOn: boolean;
72
+ statusLineError: string;
44
73
  /** 신고 안내에 그대로 보여줄 진단 로그 경로 */
45
74
  diagPath: string;
46
75
  /** 신고 본문에 적을 버전 — 사용자가 어느 버전을 쓰는지 스스로 알 방법이 없었다 */
@@ -51,11 +80,93 @@ export declare class AgentDeckSettingsTabComponent {
51
80
  diagBundlePath: string;
52
81
  /** 묶음을 만들다 실패한 이유 */
53
82
  diagError: string;
83
+ /**
84
+ * Claude 연동이 **온전히** 걸려 있나 — 상태 훅 + statusLine 둘 다.
85
+ *
86
+ * 반쪽(훅만 걸림)은 `false` 다. 그래야 버튼이 `설치` 로 남아 한 번 더 누르면 모자란 쪽이
87
+ * 채워진다 — `연동됨` 이라고 해 놓고 막대가 안 뜨면 사용자가 손댈 곳이 없어진다.
88
+ * statusLine 스크립트가 없는 설치본에서는 훅만으로 판정한다.
89
+ */
90
+ get claudeOn(): boolean;
91
+ /** 꺼진 훅 이름을 사람이 `/hooks` 에서 찾을 수 있는 표기로 */
92
+ get codexOffLabels(): string;
93
+ /** 두 갈래(훅·statusLine) 중 실패한 것만 모아 한 줄로 */
94
+ get claudeError(): string;
54
95
  /** `지금 확인` 이 도는 동안 버튼을 잠근다 (레지스트리 응답까지 몇 초) */
55
96
  updateBusy: boolean;
56
97
  /** 마지막 확인 결과 한 줄 */
57
98
  updateMsg: string;
58
- constructor(config: ConfigService, recovery: AgentDeckRecoveryService, update: AgentDeckUpdateService, platform: PlatformService);
99
+ /** 늘어놓는 항목 — README 첫 화면의 표와 같은 순서 */
100
+ keyItems: KeyItem[];
101
+ /** 지금 키를 받고 있는 항목 id */
102
+ capturing: string | null;
103
+ /** 겹침이 나와 확인을 기다리는 계획 */
104
+ pending: {
105
+ plan: BindingPlan;
106
+ names: string;
107
+ } | null;
108
+ /** 한 줄 안내(숫자로 끝나야 한다 등) 와 그 대상 */
109
+ keyMsg: string;
110
+ keyMsgFor: string;
111
+ /** id → 사람이 읽는 이름. Tabby 순정·다른 플러그인 것까지 HotkeysService 가 모아 준다 */
112
+ private hotkeyNames;
113
+ private captureHandler;
114
+ private readonly keyPlatform;
115
+ /** 600px 제한을 풀어 둔 Tabby 의 본문 엘리먼트 (나갈 때 되돌린다) */
116
+ private widened;
117
+ /** 풀기 전의 값 — 인라인으로 없던 상태면 빈 문자열이라 그대로 되돌려진다 */
118
+ private prevMaxWidth;
119
+ /** 지금 그릴 언어 — Tabby 의 `설정 > 응용 프로그램 > 언어` 를 따라간다 */
120
+ private lang;
121
+ private localeSub;
122
+ private hooksRefreshTimer;
123
+ constructor(config: ConfigService, recovery: AgentDeckRecoveryService, update: AgentDeckUpdateService, platform: PlatformService, devReload: AgentDeckDevReloadService, host: ElementRef<HTMLElement>, locale: LocaleService | null, hotkeys: HotkeysService | null, zone: NgZone);
124
+ /**
125
+ * 문구 하나. 표는 `i18n.ts` 에 있고 여기서는 언어만 얹는다.
126
+ *
127
+ * 템플릿에서 매 변경감지마다 불리는 함수다 — 표 조회 + 정규식 치환뿐이라 값이 싸고,
128
+ * 캐시를 두면 언어가 바뀌었을 때 무효화할 자리가 하나 더 생긴다.
129
+ */
130
+ t(key: string, params?: Record<string, string | number>): string;
131
+ /**
132
+ * 설정 본문의 600px 제한을 **이 탭에서만** 푼다.
133
+ *
134
+ * Tabby 는 모든 설정 탭 본문에 `settings-tab-body { max-width: 600px }` 를 건다
135
+ * (tabby-settings 컴포넌트 styles). 넓은 창에서는 오른쪽이 통째로 놀게 되는데,
136
+ * 그 규칙은 컴포넌트 스타일이라 우리 스타일시트로는 못 이긴다(우선순위가 아니라 `:host`
137
+ * 범위 문제다 — 우리 CSS 는 그 엘리먼트를 선택할 수는 있어도 어느 탭의 것인지 모른다).
138
+ * 그래서 **우리 엘리먼트에서 위로 찾아** 인라인으로 푼다. 화면을 어떻게 채울지는
139
+ * `styles.scss` 의 `agentdeck-settings-tab` 격자가 정한다.
140
+ *
141
+ * 되돌리기가 필요한 이유 — 이 엘리먼트는 다른 설정 탭과 **돌려 쓰일 수 있다**.
142
+ * 풀어 둔 채 나가면 남의 탭이 우리 때문에 넓어진다.
143
+ */
144
+ ngAfterViewInit(): void;
145
+ ngOnDestroy(): void;
146
+ /** 이 항목이 지금 쓰는 키들 (사람 표기) */
147
+ keysOf(it: KeyItem): string[];
148
+ isPending(it: KeyItem): boolean;
149
+ isDefault(it: KeyItem): boolean;
150
+ /**
151
+ * 키 받기 시작. **window 캡처 단계**에 건다 — 그래야 document 에 걸린 Tabby 의 HotkeysService
152
+ * 리스너와 우리 deck.service 의 캡처 리스너(Ctrl+V · Shift+Enter)보다 먼저 받고, 전파를 끊어
153
+ * 그 키가 진짜로 실행되는 일(검색창이 열리거나 터미널에 붙여지는 일)을 막는다.
154
+ * 수식키만 눌린 이벤트는 그냥 흘린다 — 아직 조합 중이고, 막으면 Shift 추적이 어긋난다.
155
+ */
156
+ startCapture(it: KeyItem): void;
157
+ stopCapture(): void;
158
+ /** 겹침을 알고도 쓰기 — 겹치는 쪽에서 그 키를 뗀다 (한 번 눌러 둘이 도는 상태를 남기지 않는다) */
159
+ confirmPending(): void;
160
+ resetKeys(it: KeyItem): void;
161
+ private commit;
162
+ /**
163
+ * 기본값 표 — 순정 항목(split-right · split-bottom …)은 Tabby 의 플랫폼 기본표에서,
164
+ * 우리 항목과 close-pane 은 `defaultHotkeys()` 에서. 순정 기본표는 ConfigService 가 들고 있다
165
+ * (`getDefaults()`), 없으면 우리 표만으로 간다(그때 순정 항목의 `기본값` 은 비운다).
166
+ */
167
+ private keyDefaults;
168
+ private nameOf;
169
+ private loadHotkeyNames;
59
170
  /**
60
171
  * 사람이 직접 누른 확인. `force` 라 간격과 `다시 묻지 않기` 를 건너뛴다 —
61
172
  * 눌렀다는 것 자체가 "지금 알고 싶다" 는 뜻이다.
@@ -63,13 +174,28 @@ export declare class AgentDeckSettingsTabComponent {
63
174
  checkUpdate(): Promise<void>;
64
175
  private refreshHooks;
65
176
  /**
66
- * 훅을 걸거나 뗀다.
177
+ * Claude 연동을 걸거나 뗀다 — **상태 훅과 statusLine 을 한 덩이로** 다룬다.
67
178
  *
68
- * 걸 때는 통보 채널도 같이 켠다 — 훅만 걸고 채널이 닫혀 있으면 보고가 어디에도 닿지 않는데,
69
- * 사용자 입장에서는 "설치했는데 아무 일도 안 일어난다" 로만 보인다.
179
+ * 따로 두었더니 "훅만 걸고 statusLine 은 안 건 상태" 가 흔하게 나왔고, 그 화면은
180
+ * 상태는 뜨는데 모델·한도 막대만 없어서 사용자가 원인을 못 짚는다 (2026-09-14 유저 지시:
181
+ * "status line 은 같은 취급 해"). 사용자가 정하는 것은 **이 에이전트를 사이드바에 붙일까**
182
+ * 하나뿐이고, 그걸 이루는 파일이 둘인 것은 우리 사정이다.
183
+ *
184
+ * 해제는 **원래 statusLine 을 제자리에 돌려놓는다** — 우리 것이 아니면 아무것도 건드리지
185
+ * 않는다(`unwrapStatusLine`). 되돌릴 것이 없던 사용자는 statusLine 키 자체가 지워진다.
186
+ *
187
+ * 한쪽만 실패해도 나머지는 계속 간다 — 둘 다 시도한 뒤 모아서 한 줄로 알린다.
188
+ * 그래야 "훅은 걸렸는데 statusLine 이 안 걸린" 반쪽 상태를 사용자가 볼 수 있다.
70
189
  */
71
- toggleHooks(): void;
190
+ toggleClaude(): void;
72
191
  toggleCodexHooks(): void;
192
+ /**
193
+ * 통보 채널을 훅 설치 상태에 맞춘다 — 하나라도 걸려 있으면 열고, 하나도 없으면 닫는다.
194
+ *
195
+ * `refreshHooks()` **뒤에** 부른다. 실패해서 설치가 안 됐을 수도 있으므로 버튼을 누른
196
+ * 의도가 아니라 **파일을 다시 읽은 결과**를 기준으로 삼는다.
197
+ */
198
+ private syncNotifyChannel;
73
199
  /**
74
200
  * OS 폴더 선택 창을 띄워 작업 폴더를 고른다.
75
201
  *
@@ -0,0 +1,9 @@
1
+ import { Lang } from './i18n';
2
+ export declare const SIDEBAR_KEYS: string[];
3
+ export declare function sidebarText(key: string, lang: Lang, params?: Record<string, string | number>): string;
4
+ /** Only fixed markup authored by this plugin; user content is inserted separately via textContent. */
5
+ export declare function sidebarMarkup(html: string, lang: Lang): string;
6
+ /** Translate only the known forms emitted by reason.ts; preserve unrecognized hook content. */
7
+ export declare function sidebarReason(reason: string, lang: Lang): string;
8
+ export declare function sidebarReset(text: string, lang: Lang): string;
9
+ export declare const RESET_COPY: Record<string, string[]>;
@@ -0,0 +1 @@
1
+ export declare const SIDEBAR_LOCALES: Record<string, Record<string, string>>;
@@ -62,6 +62,34 @@ export declare class WorkStatusService {
62
62
  * 탭 목록 변화는 사람이 만드는 빈도(초당 몇 번이 최대)라 문제가 되지 않는다.
63
63
  */
64
64
  sweep(alive: readonly BaseTabComponent[]): number;
65
- /** 사이드바가 매 틱 호출 — idle 전환만 담당하고 화면 갱신 필요 여부를 돌려준다 */
66
- tickIdle(tab: BaseTabComponent, idleAfterMs: number): boolean;
65
+ /**
66
+ * 사이드바가 매 틱 호출 — idle 전환만 담당하고 화면 갱신 필요 여부를 돌려준다.
67
+ *
68
+ * `staleAfterMs` 는 **고정(pinned)된 진행중 탭의 유예 시간**이다. 0 이면 예전과 같다.
69
+ *
70
+ * 왜 필요한가 — 훅에는 "세션이 끝났다" 가 없다. 설치하는 이벤트는 `UserPromptSubmit`
71
+ * `PostToolUse` `Notification` `Stop` 뿐이라(`claudeHooks.ts` PLAN), 세션이 정상 종료
72
+ * 밖으로 사라지면(Ctrl+C 로 끊음 · 창을 닫음 · CLI 크래시 · Tabby 가 탭을 복원했는데 그
73
+ * 세션은 이미 죽음) **마지막으로 온 `running` 이 영원히 남는다.** `setManual` 이 pinned 를
74
+ * 세우고 여기서 pinned 를 그냥 건너뛰었으므로, 그 탭에는 배지가 내려올 길이 한 개도 없었다
75
+ * (`setAuto` 는 pinned 를 무시하고 `resume` 은 waiting 전용이며 `unpin` 은 사람이 메뉴에서
76
+ * 고르는 것뿐이다). 사이드바에 "작업중" 이 박혀 있는 탭이 실은 아무것도 안 하고 있는 모양이
77
+ * 이 구멍이다.
78
+ *
79
+ * **살아 있다는 증거는 셋 다 인정한다** — 훅 보고(`since` 는 `setManual` 이 매 보고마다
80
+ * 새로 찍는다) · PTY 출력(`lastOutput`) · 작업중 신호(`lastBusy`). 셋 중 가장 최근 것에서
81
+ * 잰다. 그래서 오래 도는 빌드처럼 화면이 조용해 보이는 작업도, 스피너든 훅이든 하나만
82
+ * 살아 있으면 내려가지 않는다.
83
+ *
84
+ * **대상은 `running`·`done`·`error` 뿐이다**(`STALE_STATUSES`). 푸는 것은 **고정**이고,
85
+ * `진행중` 일 때만 이어서 `idle` 로 내린다 — 기척이 끊긴 진행중은 거짓말이지만, `완료`·`오류`
86
+ * 는 실제로 있었던 일이라 글자를 지울 이유가 없다. 그 둘에서 필요한 것은 **다음 출력이
87
+ * 배지를 되살릴 수 있게 하는 것**뿐이다: CLI 를 끄고 같은 탭에서 셸을 쓰면 `setAuto` 가
88
+ * 계속 들어오는데 pinned 라 전부 무시돼 배지가 `완료` 에 박혀 있었다.
89
+ *
90
+ * 고정을 반드시 푸는 이유도 같다. idle 로만 내리고 pinned 를 남기면 그 탭은 이어지는
91
+ * 자동 감지를 계속 무시한다 — 구멍을 반만 막는 셈이다. 다음 훅 보고가 오면 `setManual`
92
+ * 이 다시 고정한다.
93
+ */
94
+ tickIdle(tab: BaseTabComponent, idleAfterMs: number, staleAfterMs?: number): boolean;
67
95
  }
@@ -0,0 +1,27 @@
1
+ /** 원래 걸려 있던 statusLine 을 옮겨 두는 자리 (스크립트도 같은 경로를 읽는다) */
2
+ export declare function innerConfigPath(): string;
3
+ /** statusLine 스크립트의 실제 경로. dist/index.js 기준으로 한 단계 위가 패키지 뿌리다 */
4
+ export declare function statusLineScriptPath(): string;
5
+ export declare function statusLineSupported(): boolean;
6
+ /** 우리 래퍼가 걸려 있나 — 파일을 읽지 않는 순수 판정 */
7
+ export declare function isWrapped(settings: any): boolean;
8
+ /**
9
+ * 설정 객체의 statusLine 을 우리 래퍼로 갈아 끼운 새 객체를 돌려준다 (원본은 건드리지 않는다).
10
+ *
11
+ * 이미 우리 것이면 명령줄만 지금 경로로 갱신한다 — 그때 안쪽 설정은 **그대로 둔다**
12
+ * (다시 옮기면 안쪽에 우리 자신이 들어가 고리가 된다).
13
+ */
14
+ export declare function wrapStatusLine(settings: any, script: string): {
15
+ next: any;
16
+ inner: any | null;
17
+ };
18
+ /**
19
+ * 우리 래퍼를 걷어내고 안쪽 설정을 제자리에 돌려놓은 새 객체.
20
+ * 안쪽이 없었으면(원래 statusLine 이 없던 사용자) 키 자체를 지운다.
21
+ */
22
+ export declare function unwrapStatusLine(settings: any, inner: any | null): any;
23
+ export declare function statusLineInstalled(): boolean;
24
+ /** 지금 안쪽에 무엇이 있는지 (설정 창이 사용자에게 보여 준다) */
25
+ export declare function innerCommand(): string;
26
+ export declare function installStatusLine(): void;
27
+ export declare function uninstallStatusLine(): void;
package/dist/update.d.ts CHANGED
@@ -4,11 +4,17 @@
4
4
  * 네트워크·fs·타이머는 여기 없다 (`group.ts`·`sessionLedger.ts` 와 같은 이유). 이 파일은
5
5
  * "무엇을 근거로 결정하는가" 만 정하고, 실제로 묻고 설치하는 것은 `update.service.ts` 다.
6
6
  *
7
- * ## 왜 "받아 두고 다음 기동에 적용" 인가
7
+ * ## 적용은 **창만 새로 고쳐서** 한다 (Tabby 를 닫지 않는다)
8
8
  *
9
- * Tabby 는 플러그인을 **기동할 때 한 번** 읽는다. 돌고 있는 플러그인을 갈아끼울 방법이 없으므로
10
- * 어떤 방식으로 업데이트하든 재시작이 필요하다. 그래서 이 기능은 조용히 받아만 두고
11
- * "재시작하면 적용된다" 고 한 번 알린다 — 사람의 작업을 끊지 않는 유일한 모양이다.
9
+ * Tabby 는 플러그인을 기동할 때 `dist/index.js` 를 한 번 읽는다. 그래서 예전에는
10
+ * "닫았다가 설치하고 다시 띄운다" 였는데, 그 사이 열린 탭과 돌고 있는 세션이 전부 끊겼다.
11
+ * 지금은 `reload.service.ts` 가 **renderer 만 리로드**하면서 탭을 복구 토큰으로 되살린다 —
12
+ * pty 는 main 프로세스가 쥐고 있어 창이 다시 떠도 살아 있다. 리로드는 디스크를 다시 읽으므로
13
+ * 새로 깔린 코드가 그대로 뜬다.
14
+ *
15
+ * 그래서 설치도 **Tabby 가 떠 있는 채로** 한다. 로드된 `.js` 는 Windows 가 잡고 있지 않으므로
16
+ * (require 는 읽고 바로 닫는다 — 2026-09-14 실측: 요구된 파일이 든 폴더를 그대로 지울 수 있었다)
17
+ * npm 이 플러그인 폴더를 갈아끼워도 돌고 있는 코드는 메모리에 그대로다.
12
18
  */
13
19
  /** npm 레지스트리 기본값. 사내 미러를 쓰는 사람은 설정으로 바꾼다 */
14
20
  export declare const DEFAULT_REGISTRY = "https://registry.npmjs.org";
@@ -58,7 +64,15 @@ export declare function canSelfUpdate(marks: {
58
64
  hasWebpackConfig: boolean;
59
65
  isLink: boolean;
60
66
  }): boolean;
61
- export type UpdateOutcome = 'disabled' | 'too-soon' | 'dev-install' | 'unknown' | 'up-to-date' | 'update-ready' | 'skipped' | 'postponed' | 'installing' | 'failed';
67
+ /**
68
+ * 설치가 실제로 끝났나 — **디스크의 버전**으로 판정한다.
69
+ *
70
+ * npm 이 0 으로 끝나도 폴더가 옛것 그대로일 수 있다(캐시·권한·프록시). 그 상태로 창을
71
+ * 리로드하면 "업데이트했다" 고 해 놓고 같은 버전이 다시 뜬다. 그래서 갈아끼우기 직전에
72
+ * `<plugins>/node_modules/<pkg>/package.json` 의 버전을 읽어 기대값과 맞춰 본다.
73
+ */
74
+ export declare function installSucceeded(expected: string, onDisk: unknown): boolean;
75
+ export type UpdateOutcome = 'disabled' | 'too-soon' | 'dev-install' | 'unknown' | 'up-to-date' | 'update-ready' | 'skipped' | 'postponed' | 'installing' | 'installed' | 'failed';
62
76
  /**
63
77
  * 확인 결과로 무엇을 할지 — 순수 결정.
64
78
  *
@@ -1,18 +1,20 @@
1
1
  import { AppService, ConfigService, NotificationsService, PlatformService } from 'tabby-core';
2
+ import { AgentDeckReloadService } from './reload.service';
2
3
  import { UpdateOutcome } from './update';
3
4
  /**
4
- * npm 레지스트리를 보고 **Tabby 를 껐다 켜면서** 새 버전을 깐다.
5
+ * npm 레지스트리를 보고 **Tabby 를 띄운 채로** 새 버전을 깐다.
5
6
  *
6
7
  * 흐름은 넷이다 —
7
8
  * ① 기동하고 잠시 뒤 레지스트리에 묻는다
8
9
  * ② 새 버전이 있으면 **물어본다** (`지금 업데이트` / `나중에` / `다시 묻지 않기`)
9
- * ③ 승낙하면 설치 스크립트를 **밖으로 띄우고** Tabby 를 닫는다
10
- * ④ 스크립트가 Tabby 종료를 기다렸다가 설치하고 다시 띄운다
10
+ * ③ 승낙하면 설치 스크립트를 임시 폴더에서 돌린다 — **Tabby 는 그대로 떠 있다**
11
+ * ④ 디스크 버전을 확인하고 **창만 새로 고친다**(`reload.service`) — 탭과 세션은 살아남는다
11
12
  *
12
- * **왜 밖에서 까나.** 돌고 있는 플러그인은 자기를 갈아끼울 수 없다 — Tabby 는 기동할 때
13
- * `dist/index.js` 를 한 번 읽어 메모리에 올리므로 디스크를 바꿔도 돌던 코드는 옛것이다.
14
- * 게다가 Windows 는 로드된 파일을 잡고 있어서, 떠 있는 동안 npm 이 그 폴더를 지우려 들면
15
- * 반쯤 지운 채 실패할 수 있다 — 플러그인이 깨진 채 남는 것이 가장 나쁜 결과다.
13
+ * **예전에는 닫았다 켰다.** 돌고 있는 플러그인은 자기를 갈아끼울 수 없고(모듈이 이미
14
+ * 메모리에 있다) Windows 가 파일을 잡고 있을까 걱정했기 때문인데, 둘 다 지금은 해결됐다 —
15
+ * 적용은 renderer 리로드가 하고(pty 는 main 이 쥐고 있어 살아남는다), 로드된 `.js` 는
16
+ * 잠기지 않는다(2026-09-14 실측: require 한 파일이 든 폴더를 그대로 지울 수 있었다).
17
+ * 남은 조건은 하나 — **설치 스크립트는 패키지 밖에서 돌려야 한다**(제 발밑을 npm 이 갈아엎는다).
16
18
  *
17
19
  * **개발 설치에는 설치하지 않는다.** 플러그인 폴더의 `tabby-agentdeck` 은 개발 중에는 소스
18
20
  * 트리를 가리키는 링크이고(`plugins/package.json` 에 `file:D:/Project/tabby-agentdeck` 로 적힌다),
@@ -23,7 +25,8 @@ export declare class AgentDeckUpdateService {
23
25
  private config;
24
26
  private platform;
25
27
  private notifications;
26
- constructor(app: AppService, config: ConfigService, platform: PlatformService, notifications: NotificationsService);
28
+ private reloader;
29
+ constructor(app: AppService, config: ConfigService, platform: PlatformService, notifications: NotificationsService, reloader: AgentDeckReloadService);
27
30
  init(): void;
28
31
  /**
29
32
  * 한 번 확인하고, 새 버전이면 물어본 뒤 설치를 건다.
@@ -32,26 +35,32 @@ export declare class AgentDeckUpdateService {
32
35
  * (사람이 직접 눌렀으니 그 순간만큼은 묻는 게 맞다). 기능 자체를 끈 것은 존중한다.
33
36
  */
34
37
  run(reason: string, force?: boolean): Promise<UpdateOutcome>;
35
- /** ② 물어보고 ③ 설치 스크립트를 띄운 뒤 Tabby 를 닫는다 */
38
+ /** ② 물어보고 ③ 제자리에 깐 뒤 ④ 창만 새로 고친다 */
36
39
  private confirmAndInstall;
37
40
  /**
38
- * 설치 스크립트를 **패키지 밖에서** 떼어 띄운다.
41
+ * ③ 제자리에 깔고 ④ 창을 새로 고친다 — **묻는 단계는 이미 지났다.**
39
42
  *
40
- * 임시 폴더로 복사하는 이유: 이 스크립트는 곧 npm 이 통째로 갈아엎을 폴더 안에 산다.
41
- * 제자리에서 돌리면 자기 발밑이 사라진다.
43
+ * 따로 떼어 둔 이유는 회귀 때문이다. `showMessageBox` 는 OS 의 모달이라 CDP 로 누를 수
44
+ * 없어서, 묻기와 한 덩어리면 이 길을 자동으로 한 번도 못 밟아 본다
45
+ * (`tools/regress-update.js` 가 여기를 부른다).
42
46
  *
43
- * `detached` + `unref` 라야 Tabby 가 죽어도 살아남는다 — 부모가 죽을 때 같이 끌려가면
44
- * 설치는커녕 Tabby 를 다시 띄우지도 못한다.
47
+ * `spec` 은 npm 에 건넬 대상을 바꾼다 — **회귀 전용**이다. 기본값(`tabby-agentdeck@<버전>`)은
48
+ * 레지스트리를 보지만, 회귀는 *지금 이 소스로 만든 tgz* 를 깔아야 한다. 이미 배포된 버전을
49
+ * 깔면 그 안에는 이 코드가 없어서, 리로드 뒤 복구를 맡을 쪽이 옛 코드가 된다.
45
50
  */
46
- private launchUpdater;
51
+ installAndReload(current: string, latest: string, spec?: string): Promise<UpdateOutcome>;
47
52
  /**
48
- * 이 Tabby 인스턴스의 **메인 프로세스 PID**.
53
+ * 설치 스크립트를 **패키지 밖에서** 돌리고 끝날 때까지 기다린다.
54
+ *
55
+ * 임시 폴더로 복사하는 이유: 이 스크립트는 곧 npm 이 통째로 갈아엎을 폴더 안에 산다.
56
+ * 제자리에서 돌리면 자기 발밑이 사라진다.
49
57
  *
50
- * 플러그인은 렌더러에서 돌고, Electron 렌더러의 부모가 메인이다. 메인이 죽으면 렌더러·gpu 도
51
- * 함께 내려가므로 "이 인스턴스가 완전히 꺼졌나" 를 재는 기준점으로 이것이 맞다.
52
- * `ppid` 를 못 읽는 환경(테스트 하네스 등)에서는 0 을 주고, 스크립트가 옛 방식으로 물러난다.
58
+ * `-InPlace` 는 "Tabby 종료를 기다리지도, 다시 띄우지도 말라" 는 뜻이다. 기다릴 이유가
59
+ * 없어졌고(파일이 잠기지 않는다), 다시 띄울 이유도 없다(닫지 않는다).
53
60
  */
54
- private mainProcessPid;
61
+ private runUpdater;
62
+ /** 플러그인 폴더에 **실제로 깔려 있는** 버전 — 설치 성공 판정의 근거 */
63
+ private installedVersion;
55
64
  /** `<Tabby 설정폴더>/plugins` — npm 이 `--prefix` 로 받을 경로 */
56
65
  private pluginsDir;
57
66
  /**
@@ -77,7 +77,7 @@ export declare class ViewPanel {
77
77
  * `변경` 목록의 범위. `session` = 이번 세션이 만진 파일만, `all` = 저장소의 커밋 안 된 전부.
78
78
  *
79
79
  * **기본이 `session` 인 이유** — `git diff HEAD` 는 저장소 사정에 따라 수백 개가 나온다
80
- * (P4 워크스페이스 위의 git 오버레이에서 405개 실측, 2026-09-11). 그 안에서 방금 에이전트가
80
+ * (큰 워크스페이스 위의 git 오버레이에서는 흔한 일이다). 그 안에서 방금 에이전트가
81
81
  * 고친 것을 찾을 수는 없다. 설정에 저장하지 않는다 — 모드(`file`/`diff`)와 같은 이유로
82
82
  * 기동 때는 늘 기본값에서 시작한다.
83
83
  */
@@ -204,8 +204,18 @@ export declare class ViewPanel {
204
204
  * (사이드바 `dock.ts` 가 절대 거리로 재는 것은 거기서만 맞다 — 사이드바는 창에 딱 붙는다.)
205
205
  */
206
206
  private installResize;
207
- /** deck 의 relayout 이 정해 준 자리에 앉는다 (사이드바와 같은 방식으로 절대 배치) */
208
- layout(side: ViewSide, width: number, outerOffset: number): void;
207
+ /**
208
+ * deck 의 relayout 이 정해 준 자리에 앉는다 (사이드바와 같은 방식으로 절대 배치).
209
+ *
210
+ * 네 값 모두 **컨테이닝 블록 좌표로 이미 풀린 실좌표**다 — 예전처럼 `right: outerOffset` 으로
211
+ * 가장자리에 붙이면 그 가장자리가 화면 밖일 때 패널도 같이 밀려난다 (deck.service anchorOrigin).
212
+ */
213
+ layout(side: ViewSide, rect: {
214
+ left: number;
215
+ top: number;
216
+ width: number;
217
+ height: number;
218
+ }): void;
209
219
  private togglePathRow;
210
220
  private setPathNote;
211
221
  /**
package/dist/viewer.d.ts CHANGED
@@ -30,6 +30,8 @@ export declare function stripAnsi(data: string): string;
30
30
  * 걸러내는 것: 확장자를 모르는 것 / 잘려서 `…` 이 들어간 것 / URL / 버전 번호처럼 보이는 것.
31
31
  */
32
32
  export declare function extractPaths(data: string): string[];
33
+ /** Agent instructions are background context, not artifacts merely because a read prints their path. */
34
+ export declare function isInstructionFile(file: string): boolean;
33
35
  /**
34
36
  * 이 파일을 **스스로 띄워도 되는가** (`false` = 칩에만 쌓고 화면은 그대로 둔다).
35
37
  */
@@ -1,25 +1,28 @@
1
- # Codex lifecycle -> existing AgentDeck file/TCP transport. Never emit model context.
2
- $ErrorActionPreference = 'Stop'
3
- try {
4
- $reader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), (New-Object System.Text.UTF8Encoding($false)))
5
- $raw = $reader.ReadToEnd()
6
- $reader.Dispose()
7
- $hook = $raw | ConvertFrom-Json
8
- if (-not $hook.session_id) { exit 0 }
9
- $statusByEvent = @{
10
- UserPromptSubmit = 'running'; PreToolUse = 'running'
11
- PermissionRequest = 'waiting'; PostToolUse = 'running'
12
- Stop = 'done'; Interrupt = 'idle'; SessionEnd = 'idle'
13
- }
14
- $status = $statusByEvent[[string]$hook.hook_event_name]
15
- if (-not $status) { exit 0 }
16
- # The shared transport's Notification filter is Claude-specific. An explicit
17
- # Codex PermissionRequest must be accepted even after the previous turn ended.
18
- if ($status -eq 'waiting') {
19
- $hook | Add-Member -NotePropertyName message -NotePropertyValue ('Approval: ' + [string]$hook.tool_name) -Force
20
- }
21
- $hook | ConvertTo-Json -Depth 30 -Compress | & (Join-Path $PSScriptRoot 'agentdeck-notify.ps1') -Status $status -Agent codex
22
- } catch {
23
- # Status reporting must not block the agent if Tabby is unavailable.
24
- exit 0
25
- }
1
+ # Codex lifecycle -> existing AgentDeck file/TCP transport. Never emit model context.
2
+ $ErrorActionPreference = 'Stop'
3
+ try {
4
+ $reader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), (New-Object System.Text.UTF8Encoding($false)))
5
+ $raw = $reader.ReadToEnd()
6
+ $reader.Dispose()
7
+ $hook = $raw | ConvertFrom-Json
8
+ if (-not $hook.session_id) { exit 0 }
9
+ $statusByEvent = @{
10
+ UserPromptSubmit = 'running'; PreToolUse = 'running'
11
+ PermissionRequest = 'waiting'; PostToolUse = 'running'
12
+ Stop = 'done'; Interrupt = 'idle'; SessionEnd = 'idle'
13
+ }
14
+ $status = $statusByEvent[[string]$hook.hook_event_name]
15
+ if (-not $status) { exit 0 }
16
+ # The shared transport's Notification filter is Claude-specific. An explicit
17
+ # Codex PermissionRequest must be accepted even after the previous turn ended.
18
+ if ($status -eq 'waiting') {
19
+ $hook | Add-Member -NotePropertyName message -NotePropertyValue ('Approval: ' + [string]$hook.tool_name) -Force
20
+ }
21
+ # JSON is passed as -HookJson, not piped: a script that accepts pipeline input makes the host pre-read
22
+ # stdin in the console codepage (CP949), which corrupts non-ASCII text for external callers.
23
+ $json = $hook | ConvertTo-Json -Depth 30 -Compress
24
+ & (Join-Path $PSScriptRoot 'agentdeck-notify.ps1') -Status $status -Agent codex -HookJson $json
25
+ } catch {
26
+ # Status reporting must not block the agent if Tabby is unavailable.
27
+ exit 0
28
+ }