tabby-agentdeck 1.2.0 → 1.2.2
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/CHANGELOG.md +167 -150
- package/LICENSE +21 -21
- package/README.ko.md +52 -42
- package/README.md +52 -42
- package/dist/accountBrowser.d.ts +4 -0
- package/dist/accountSession.d.ts +22 -0
- package/dist/accounts.d.ts +55 -0
- package/dist/agents.d.ts +29 -0
- package/dist/codexKeys.d.ts +3 -0
- package/dist/config.d.ts +12 -20
- package/dist/deck.service.d.ts +32 -10
- package/dist/i18n/sidebar/af.d.ts +95 -0
- package/dist/i18n/sidebar/bg.d.ts +95 -0
- package/dist/i18n/sidebar/cs.d.ts +95 -0
- package/dist/i18n/sidebar/da.d.ts +95 -0
- package/dist/i18n/sidebar/de.d.ts +95 -0
- package/dist/i18n/sidebar/es.d.ts +95 -0
- package/dist/i18n/sidebar/fr.d.ts +95 -0
- package/dist/i18n/sidebar/hr.d.ts +95 -0
- package/dist/i18n/sidebar/id.d.ts +95 -0
- package/dist/i18n/sidebar/it.d.ts +95 -0
- package/dist/i18n/sidebar/ja.d.ts +95 -0
- package/dist/i18n/sidebar/pl.d.ts +95 -0
- package/dist/i18n/sidebar/pt.d.ts +95 -0
- package/dist/i18n/sidebar/ru.d.ts +95 -0
- package/dist/i18n/sidebar/sr.d.ts +95 -0
- package/dist/i18n/sidebar/sv.d.ts +95 -0
- package/dist/i18n/sidebar/tr.d.ts +95 -0
- package/dist/i18n/sidebar/uk.d.ts +95 -0
- package/dist/i18n/sidebar/zh-CN.d.ts +95 -0
- package/dist/i18n/sidebar/zh-TW.d.ts +95 -0
- package/dist/i18n.d.ts +20 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +109 -1
- package/dist/notify.service.d.ts +16 -0
- package/dist/performance.d.ts +13 -0
- package/dist/profile.service.d.ts +2 -0
- package/dist/runtimeMigration.d.ts +3 -0
- package/dist/sessionLedger.service.d.ts +6 -3
- package/dist/sessionMailbox.d.ts +31 -0
- package/dist/sessionSearch.d.ts +41 -0
- package/dist/sessionSearchPanel.d.ts +36 -0
- package/dist/sessionSlots.d.ts +9 -0
- package/dist/settings.component.d.ts +14 -1
- package/dist/sidebarI18n.d.ts +9 -0
- package/dist/sidebarLocales.d.ts +1 -0
- package/dist/storageEnvironment.d.ts +6 -0
- package/dist/storageI18n.d.ts +1 -0
- package/dist/storagePaths.d.ts +19 -0
- package/dist/storageSettings.d.ts +28 -0
- package/dist/viewer.d.ts +2 -0
- package/hooks/agentdeck-codex-notify.ps1 +69 -28
- package/hooks/agentdeck-hook-trace.ps1 +57 -0
- package/hooks/agentdeck-label.js +53 -53
- package/hooks/agentdeck-mailbox.mjs +143 -0
- package/hooks/agentdeck-notify.ps1 +87 -8
- package/hooks/agentdeck-runtime.cjs +20 -0
- package/hooks/agentdeck-status.ps1 +33 -33
- package/hooks/agentdeck-statusline.mjs +349 -351
- package/package.json +75 -70
- package/updater/agentdeck-update.ps1 +153 -153
package/dist/notify.service.d.ts
CHANGED
|
@@ -90,6 +90,17 @@ export declare class WorkNotifyService {
|
|
|
90
90
|
private platform;
|
|
91
91
|
private root;
|
|
92
92
|
private dir;
|
|
93
|
+
private mailboxRoot;
|
|
94
|
+
private mailbox;
|
|
95
|
+
private mailboxOwners;
|
|
96
|
+
private navigationContext;
|
|
97
|
+
private navigationRefresh;
|
|
98
|
+
setNavigationRefresh(refresh: () => void): void;
|
|
99
|
+
mailboxConnectionState(tab: BaseTabComponent): string;
|
|
100
|
+
publishNavigationContext(context: string): void;
|
|
101
|
+
sessionIdOf(tab: BaseTabComponent): string | null;
|
|
102
|
+
sessionIdsOf(tab: BaseTabComponent): string[];
|
|
103
|
+
private registerMailbox;
|
|
93
104
|
/** statusLine 래퍼(`hooks/agentdeck-statusline.mjs`)가 모델·계정·한도를 남기는 곳 */
|
|
94
105
|
private metaDir;
|
|
95
106
|
/** 훅이 접속할 포트를 알려주는 파일 */
|
|
@@ -204,6 +215,7 @@ export declare class WorkNotifyService {
|
|
|
204
215
|
* "다시 묻지 않기" 를 고르면 그 뒤로는 조용하고, 설정 창의 설치 버튼은 언제든 그대로 쓸 수 있다.
|
|
205
216
|
*/
|
|
206
217
|
private promptHooksOnce;
|
|
218
|
+
configureStorageRoots(): void;
|
|
207
219
|
init(): void;
|
|
208
220
|
/** 진단 한 줄 — deck.service 와 같은 파일(`~/.agentdeck-diag.log`)에 남긴다 */
|
|
209
221
|
private diag;
|
|
@@ -259,6 +271,7 @@ export declare class WorkNotifyService {
|
|
|
259
271
|
* 같은 id 가 두 루트에 있으면(복제 뒤 셸이 이미 뜬 경우) 어느 쪽인지 알 수 없으니 둘 다 표에서 빼고 계보 폴백에 맡긴다.
|
|
260
272
|
*/
|
|
261
273
|
private refreshTabIds;
|
|
274
|
+
private retireMissingMailboxPanes;
|
|
262
275
|
/** 진단용 */
|
|
263
276
|
get debugDir(): string;
|
|
264
277
|
/** 진단용 — 듣고 있는 포트 (0 = TCP 채널이 안 떴다) */
|
|
@@ -276,6 +289,7 @@ export declare class WorkNotifyService {
|
|
|
276
289
|
*/
|
|
277
290
|
private listen;
|
|
278
291
|
/** TCP 로 들어온 JSON 한 줄 */
|
|
292
|
+
private acceptMailbox;
|
|
279
293
|
private accept;
|
|
280
294
|
private poll;
|
|
281
295
|
/**
|
|
@@ -350,6 +364,8 @@ export declare class WorkNotifyService {
|
|
|
350
364
|
* 여기서 값을 꾸미지 않는다 — 화면 문구는 부르는 쪽(deck.service)이 만든다.
|
|
351
365
|
* 이 서비스는 "누가 무엇을 보고했나" 만 들고 있는다.
|
|
352
366
|
*/
|
|
367
|
+
private accountHomes;
|
|
368
|
+
setAccountHome(tab: BaseTabComponent, provider: string, home: string, email: string): void;
|
|
353
369
|
metaOf(tab: BaseTabComponent | null | undefined): AgentMeta | null;
|
|
354
370
|
/**
|
|
355
371
|
* statusLine 래퍼가 남긴 스냅샷을 읽는다 (`%LOCALAPPDATA%\tabby-agentdeck\meta`).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const perfNow: () => number;
|
|
2
|
+
/** Infrequent lifecycle operations only; never log terminal contents or recovery tokens. */
|
|
3
|
+
export declare class PerformanceTrace {
|
|
4
|
+
private operation;
|
|
5
|
+
private readonly id;
|
|
6
|
+
private readonly started;
|
|
7
|
+
private previous;
|
|
8
|
+
constructor(operation: string);
|
|
9
|
+
mark(stage: string, detail?: string): void;
|
|
10
|
+
step<T>(stage: string, action: () => T): T;
|
|
11
|
+
}
|
|
12
|
+
/** One timer per renderer; report stalls at most once every five seconds. */
|
|
13
|
+
export declare function monitorEventLoop(): void;
|
|
@@ -12,5 +12,7 @@ export declare class AgentDeckProfileService {
|
|
|
12
12
|
private config;
|
|
13
13
|
constructor(app: AppService, config: ConfigService);
|
|
14
14
|
init(): void;
|
|
15
|
+
/** Settings UI explicitly changed our profile; preserve unrelated shell options. */
|
|
16
|
+
applySettings(): void;
|
|
15
17
|
private ensureProfile;
|
|
16
18
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigService } from 'tabby-core';
|
|
2
|
+
import { HistorySource } from './sessionSearch';
|
|
2
3
|
import { SessionRecord } from './sessionLedger';
|
|
3
4
|
/**
|
|
4
5
|
* 지난 세션 원장의 **파일 담당** — 읽고, 쓰고, 기록 폴더를 훑는다.
|
|
@@ -10,10 +11,10 @@ import { SessionRecord } from './sessionLedger';
|
|
|
10
11
|
*/
|
|
11
12
|
export declare class SessionLedgerService {
|
|
12
13
|
private config;
|
|
13
|
-
private root;
|
|
14
|
-
private file;
|
|
14
|
+
private get root();
|
|
15
|
+
private get file();
|
|
15
16
|
/** Claude Code 대화기록이 사는 곳 (`notify.service` 와 같은 자리) */
|
|
16
|
-
private projectsDir;
|
|
17
|
+
private get projectsDir();
|
|
17
18
|
/** 세션 id -> 원장 한 줄. 사람이 친 라벨이 여기 남는다 */
|
|
18
19
|
private ledger;
|
|
19
20
|
/** 디스크 스캔 결과 (기록 파일에서 읽은 것) */
|
|
@@ -28,6 +29,7 @@ export declare class SessionLedgerService {
|
|
|
28
29
|
private saveTimer;
|
|
29
30
|
private scanning;
|
|
30
31
|
private lastScan;
|
|
32
|
+
private historyFiles;
|
|
31
33
|
/** 목록이 바뀌었을 때 호출부(사이드바)가 다시 그리도록 */
|
|
32
34
|
onChange: (() => void) | null;
|
|
33
35
|
constructor(config: ConfigService);
|
|
@@ -65,6 +67,7 @@ export declare class SessionLedgerService {
|
|
|
65
67
|
private readHeadOf;
|
|
66
68
|
/** 원장 + 디스크를 합친 전체 목록. 렌더 중에 불러도 막히지 않는다(캐시만 본다) */
|
|
67
69
|
records(): SessionRecord[];
|
|
70
|
+
historySources(): Promise<HistorySource[]>;
|
|
68
71
|
/** 진단용 — `__agentdeck.sessions()` 가 보여준다 */
|
|
69
72
|
snapshot(): {
|
|
70
73
|
ledger: number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface MailSession {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
name: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
active: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface MailMessage {
|
|
8
|
+
id: string;
|
|
9
|
+
fromSessionId: string;
|
|
10
|
+
toSessionId: string;
|
|
11
|
+
body: string;
|
|
12
|
+
requestKey: string;
|
|
13
|
+
replyTo?: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
readAt?: number;
|
|
16
|
+
completedAt?: number;
|
|
17
|
+
}
|
|
18
|
+
/** A single owner (the Tabby hook receiver) serializes durable mailbox writes. */
|
|
19
|
+
export declare class SessionMailbox {
|
|
20
|
+
private file;
|
|
21
|
+
private data;
|
|
22
|
+
private committed;
|
|
23
|
+
constructor(file: string);
|
|
24
|
+
private save;
|
|
25
|
+
register(sessionId: string, name: string, cwd: string): {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
token: string;
|
|
28
|
+
};
|
|
29
|
+
close(sessionId: string): void;
|
|
30
|
+
call(sessionId: string, token: string, method: string, args?: any): unknown;
|
|
31
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SessionRecord } from './sessionLedger';
|
|
2
|
+
export interface HistorySource {
|
|
3
|
+
file: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
agent: 'claude' | 'codex';
|
|
6
|
+
}
|
|
7
|
+
export interface HistoryMessage {
|
|
8
|
+
role: 'user' | 'assistant';
|
|
9
|
+
text: string;
|
|
10
|
+
line: number;
|
|
11
|
+
}
|
|
12
|
+
export interface HistoryHit {
|
|
13
|
+
source: HistorySource;
|
|
14
|
+
record: SessionRecord;
|
|
15
|
+
snippet: string;
|
|
16
|
+
count: number;
|
|
17
|
+
line: number;
|
|
18
|
+
}
|
|
19
|
+
export interface HistorySearchResult {
|
|
20
|
+
hits: HistoryHit[];
|
|
21
|
+
checked: number;
|
|
22
|
+
unreadable: number;
|
|
23
|
+
malformed: number;
|
|
24
|
+
}
|
|
25
|
+
export declare class SessionSearch {
|
|
26
|
+
private cacheDir;
|
|
27
|
+
private subprocess;
|
|
28
|
+
private worker;
|
|
29
|
+
private cancelPending;
|
|
30
|
+
constructor(cacheDir: string, subprocess?: boolean);
|
|
31
|
+
private stop;
|
|
32
|
+
cancel(): void;
|
|
33
|
+
private run;
|
|
34
|
+
search(sources: HistorySource[], query: string, progress?: (done: number, total: number) => void): Promise<HistorySearchResult>;
|
|
35
|
+
preview(source: HistorySource, line: number): Promise<{
|
|
36
|
+
messages: HistoryMessage[];
|
|
37
|
+
total: number;
|
|
38
|
+
previous: number | null;
|
|
39
|
+
next: number | null;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -4,6 +4,9 @@ import { KeyItem, BindingPlan } from './keybind';
|
|
|
4
4
|
import { AgentDeckRecoveryService } from './recovery.service';
|
|
5
5
|
import { AgentDeckUpdateService } from './update.service';
|
|
6
6
|
import { AgentDeckDevReloadService } from './devReload.service';
|
|
7
|
+
import { StoragePaths } from './storagePaths';
|
|
8
|
+
import { StorageSettings } from './storageSettings';
|
|
9
|
+
import { AgentDeckProfileService } from './profile.service';
|
|
7
10
|
/**
|
|
8
11
|
* 설정 > AgentDeck 탭.
|
|
9
12
|
*
|
|
@@ -49,6 +52,10 @@ export declare class AgentDeckSettingsTabComponent implements AfterViewInit, OnD
|
|
|
49
52
|
private locale;
|
|
50
53
|
private hotkeys;
|
|
51
54
|
private zone;
|
|
55
|
+
private rootProfiles;
|
|
56
|
+
storageKeys: readonly ["accountStorageDir", "claudeStorageDir", "codexStorageDir"];
|
|
57
|
+
storageDefault: StoragePaths;
|
|
58
|
+
storageModel: StorageSettings;
|
|
52
59
|
/** 이 환경에서 훅을 걸 수 있나 (통보 스크립트가 PowerShell 이라 Windows 전용) */
|
|
53
60
|
hooksSupported: boolean;
|
|
54
61
|
/** 지금 걸려 있나 — 파일을 매번 읽지 않도록 화면용으로 들고 있는다 */
|
|
@@ -119,7 +126,8 @@ export declare class AgentDeckSettingsTabComponent implements AfterViewInit, OnD
|
|
|
119
126
|
/** 지금 그릴 언어 — Tabby 의 `설정 > 응용 프로그램 > 언어` 를 따라간다 */
|
|
120
127
|
private lang;
|
|
121
128
|
private localeSub;
|
|
122
|
-
|
|
129
|
+
private hooksRefreshTimer;
|
|
130
|
+
constructor(config: ConfigService, recovery: AgentDeckRecoveryService, update: AgentDeckUpdateService, platform: PlatformService, devReload: AgentDeckDevReloadService, host: ElementRef<HTMLElement>, locale: LocaleService | null, hotkeys: HotkeysService | null, zone: NgZone, rootProfiles: AgentDeckProfileService);
|
|
123
131
|
/**
|
|
124
132
|
* 문구 하나. 표는 `i18n.ts` 에 있고 여기서는 언어만 얹는다.
|
|
125
133
|
*
|
|
@@ -233,4 +241,9 @@ export declare class AgentDeckSettingsTabComponent implements AfterViewInit, OnD
|
|
|
233
241
|
*/
|
|
234
242
|
expandGroups(): void;
|
|
235
243
|
pickCwd(): Promise<void>;
|
|
244
|
+
saveRootProfile(): void;
|
|
245
|
+
storageLabel(key: string): string;
|
|
246
|
+
pickStorage(key: keyof StoragePaths): Promise<void>;
|
|
247
|
+
resetStorage(): void;
|
|
248
|
+
saveStorage(): Promise<void>;
|
|
236
249
|
}
|
|
@@ -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>>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { StoragePaths } from './storagePaths';
|
|
2
|
+
type Values = Record<string, string | null>;
|
|
3
|
+
export declare function writeUserStorageEnvironment(values: Values): Promise<Values>;
|
|
4
|
+
/** Persist user defaults; return a compensating action if the settings file cannot be saved. */
|
|
5
|
+
export declare function syncStorageEnvironment(paths: StoragePaths): Promise<() => Promise<void>>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function storageText(key: string, lang: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface StoragePaths {
|
|
2
|
+
accountStorageDir: string;
|
|
3
|
+
claudeStorageDir: string;
|
|
4
|
+
codexStorageDir: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const STORAGE_KEYS: readonly ["accountStorageDir", "claudeStorageDir", "codexStorageDir"];
|
|
7
|
+
/** Snapshot at startup: saving settings must not redirect a running login or token refresh. */
|
|
8
|
+
export declare function configureStoragePaths(settings: Partial<StoragePaths>): void;
|
|
9
|
+
export declare function storageDefaults(): StoragePaths;
|
|
10
|
+
export declare function accountsFile(): string;
|
|
11
|
+
/** Runtime data belongs to the account storage setting, with separate writers per Tabby profile. */
|
|
12
|
+
export declare function runtimeRoot(): string;
|
|
13
|
+
export declare function runtimeEnvironment(): Record<string, string>;
|
|
14
|
+
export declare function agentHome(provider: 'claude' | 'codex'): string;
|
|
15
|
+
export declare function hasConfiguredAgentHome(provider: 'claude' | 'codex'): boolean;
|
|
16
|
+
/** Explicit per-account environments still win over the shared CLI home. */
|
|
17
|
+
export declare function storageEnvironment(env?: Record<string, string>): Record<string, string>;
|
|
18
|
+
/** Validate the complete draft before saving anything. Never create or move user data here. */
|
|
19
|
+
export declare function validateStoragePaths(draft: StoragePaths): StoragePaths;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StoragePaths } from './storagePaths';
|
|
2
|
+
import { syncStorageEnvironment } from './storageEnvironment';
|
|
3
|
+
interface StorageConfig {
|
|
4
|
+
store: {
|
|
5
|
+
agentDeck: Partial<StoragePaths>;
|
|
6
|
+
};
|
|
7
|
+
save: () => void | Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/** Shared for the lifetime of the settings service, including navigation away from the tab. */
|
|
10
|
+
export declare class StorageSettings {
|
|
11
|
+
private config;
|
|
12
|
+
private syncEnvironment;
|
|
13
|
+
draft: StoragePaths;
|
|
14
|
+
message: string;
|
|
15
|
+
error: boolean;
|
|
16
|
+
busy: boolean;
|
|
17
|
+
private revision;
|
|
18
|
+
private queuedRevision;
|
|
19
|
+
private pending;
|
|
20
|
+
private queue;
|
|
21
|
+
constructor(config: StorageConfig, syncEnvironment?: typeof syncStorageEnvironment);
|
|
22
|
+
private savedPaths;
|
|
23
|
+
change(key: keyof StoragePaths, value: string): void;
|
|
24
|
+
save(): Promise<void>;
|
|
25
|
+
flush(): void;
|
|
26
|
+
}
|
|
27
|
+
export declare function storageSettingsFor(config: StorageConfig): StorageSettings;
|
|
28
|
+
export {};
|
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,28 +1,69 @@
|
|
|
1
|
-
# Codex lifecycle -> existing AgentDeck file/TCP transport. Never emit model context.
|
|
2
|
-
$ErrorActionPreference = 'Stop'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
# Codex lifecycle -> existing AgentDeck file/TCP transport. Never emit model context.
|
|
2
|
+
$ErrorActionPreference = 'Stop'
|
|
3
|
+
# Step timing diagnostics. Codex kills a hook at its timeout (3s) and only prints "hook timed out",
|
|
4
|
+
# so every step appends a line immediately: the last line of a run shows where it was killed.
|
|
5
|
+
# Log: <accountRoot>\runtime\hook-diag\hook-YYYYMMDD.log (+ms = since powershell.exe process start)
|
|
6
|
+
$env:AGENTDECK_HOOK_RUN = "$PID-" + [DateTime]::Now.ToString('HHmmssfff')
|
|
7
|
+
function Write-HookDiag ([string]$Step) {
|
|
8
|
+
try {
|
|
9
|
+
if (-not $script:hookDiagFile) {
|
|
10
|
+
$acct = if ($env:AGENTDECK_ACCOUNTS_FILE) { Split-Path -Parent $env:AGENTDECK_ACCOUNTS_FILE } else { Join-Path $env:USERPROFILE '.agentdeck' }
|
|
11
|
+
$diagDir = Join-Path $acct 'runtime\hook-diag'
|
|
12
|
+
if (-not (Test-Path $diagDir)) { New-Item -ItemType Directory -Force -Path $diagDir | Out-Null }
|
|
13
|
+
$script:hookDiagFile = Join-Path $diagDir ('hook-' + [DateTime]::Now.ToString('yyyyMMdd') + '.log')
|
|
14
|
+
$script:hookDiagStart = [System.Diagnostics.Process]::GetCurrentProcess().StartTime
|
|
15
|
+
}
|
|
16
|
+
$ms = [int]([DateTime]::Now - $script:hookDiagStart).TotalMilliseconds
|
|
17
|
+
$line = '{0} run={1} codex-wrap +{2}ms {3}' -f [DateTime]::Now.ToString('HH:mm:ss.fff'), $env:AGENTDECK_HOOK_RUN, $ms, $Step
|
|
18
|
+
[System.IO.File]::AppendAllText($script:hookDiagFile, $line + "`r`n", (New-Object System.Text.UTF8Encoding($false)))
|
|
19
|
+
} catch { }
|
|
20
|
+
}
|
|
21
|
+
Write-HookDiag 'start'
|
|
22
|
+
try { . (Join-Path $PSScriptRoot 'agentdeck-hook-trace.ps1') } catch { }
|
|
23
|
+
# Keep status reporting operational even if the optional diagnostic helper cannot load.
|
|
24
|
+
if (-not (Get-Command Write-AgentDeckHookTrace -ErrorAction SilentlyContinue)) {
|
|
25
|
+
function Write-AgentDeckHookTrace { param($Stage, $Fields, $Failure) }
|
|
26
|
+
function Set-AgentDeckHookTraceIdentity { param($Hook) }
|
|
27
|
+
}
|
|
28
|
+
Write-AgentDeckHookTrace 'wrapper_enter'
|
|
29
|
+
try {
|
|
30
|
+
Write-AgentDeckHookTrace 'stdin_begin'
|
|
31
|
+
$reader = New-Object System.IO.StreamReader([Console]::OpenStandardInput(), (New-Object System.Text.UTF8Encoding($false)))
|
|
32
|
+
$raw = $reader.ReadToEnd()
|
|
33
|
+
$reader.Dispose()
|
|
34
|
+
Write-HookDiag "stdin-read bytes=$($raw.Length)"
|
|
35
|
+
Write-AgentDeckHookTrace 'stdin_end'
|
|
36
|
+
Write-AgentDeckHookTrace 'parse_begin'
|
|
37
|
+
$hook = $raw | ConvertFrom-Json
|
|
38
|
+
Set-AgentDeckHookTraceIdentity $hook
|
|
39
|
+
Write-AgentDeckHookTrace 'parse_end'
|
|
40
|
+
Write-HookDiag "json-parsed event=$($hook.hook_event_name) tool=$($hook.tool_name) session=$($hook.session_id)"
|
|
41
|
+
if (-not $hook.session_id) { Write-HookDiag 'exit no-session'; Write-AgentDeckHookTrace 'skip' @{ reason = 'missing_session' }; exit 0 }
|
|
42
|
+
$statusByEvent = @{
|
|
43
|
+
UserPromptSubmit = 'running'; PreToolUse = 'running'
|
|
44
|
+
PermissionRequest = 'waiting'; PostToolUse = 'running'
|
|
45
|
+
Stop = 'done'; Interrupt = 'idle'; SessionEnd = 'idle'
|
|
46
|
+
}
|
|
47
|
+
$status = $statusByEvent[[string]$hook.hook_event_name]
|
|
48
|
+
if (-not $status) { Write-HookDiag 'exit unknown-event'; Write-AgentDeckHookTrace 'skip' @{ reason = 'unsupported_event' }; exit 0 }
|
|
49
|
+
# The shared transport's Notification filter is Claude-specific. An explicit
|
|
50
|
+
# Codex PermissionRequest must be accepted even after the previous turn ended.
|
|
51
|
+
if ($status -eq 'waiting') {
|
|
52
|
+
$hook | Add-Member -NotePropertyName message -NotePropertyValue ('Approval: ' + [string]$hook.tool_name) -Force
|
|
53
|
+
}
|
|
54
|
+
# JSON is passed as -HookJson, not piped: a script that accepts pipeline input makes the host pre-read
|
|
55
|
+
# stdin in the console codepage (CP949), which corrupts non-ASCII text for external callers.
|
|
56
|
+
$json = $hook | ConvertTo-Json -Depth 30 -Compress
|
|
57
|
+
Write-HookDiag "json-reserialized bytes=$($json.Length)"
|
|
58
|
+
Write-AgentDeckHookTrace 'transport_begin'
|
|
59
|
+
& (Join-Path $PSScriptRoot 'agentdeck-notify.ps1') -Status $status -Agent codex -HookJson $json
|
|
60
|
+
Write-AgentDeckHookTrace 'transport_end' @{ exit_code = $LASTEXITCODE }
|
|
61
|
+
Write-HookDiag 'end'
|
|
62
|
+
} catch {
|
|
63
|
+
Write-HookDiag "exit error_type=$($_.Exception.GetType().FullName)"
|
|
64
|
+
Write-AgentDeckHookTrace 'wrapper_error' @{} $_
|
|
65
|
+
# Status reporting must not block the agent if Tabby is unavailable.
|
|
66
|
+
exit 0
|
|
67
|
+
} finally {
|
|
68
|
+
Write-AgentDeckHookTrace 'wrapper_end'
|
|
69
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Process-local diagnostics. Never serialize hook input, exception messages or credentials.
|
|
2
|
+
$global:AgentDeckHookTrace = $null
|
|
3
|
+
try {
|
|
4
|
+
$traceRoot = $env:AGENTDECK_RUNTIME_ROOT
|
|
5
|
+
if (-not $traceRoot) {
|
|
6
|
+
$accountRoot = if ($env:AGENTDECK_ACCOUNTS_FILE) { Split-Path -Parent $env:AGENTDECK_ACCOUNTS_FILE } else { Join-Path $env:USERPROFILE '.agentdeck' }
|
|
7
|
+
$traceRoot = Join-Path $accountRoot 'runtime'
|
|
8
|
+
if ($env:TABBY_CONFIG_DIRECTORY) {
|
|
9
|
+
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
10
|
+
# Match Node path.resolve without expanding Windows 8.3 aliases.
|
|
11
|
+
try { $hash = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($env:TABBY_CONFIG_DIRECTORY).TrimEnd('\').ToLowerInvariant())) }
|
|
12
|
+
finally { $sha.Dispose() }
|
|
13
|
+
$traceRoot = Join-Path $traceRoot (([BitConverter]::ToString($hash)).Replace('-', '').ToLowerInvariant().Substring(0,16))
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
$traceDir = Join-Path (Join-Path $traceRoot 'hook-timing') ([DateTime]::UtcNow.ToString('yyyy-MM-dd'))
|
|
17
|
+
[void][IO.Directory]::CreateDirectory($traceDir)
|
|
18
|
+
$global:AgentDeckHookTrace = @{
|
|
19
|
+
path = Join-Path $traceDir (('{0}-{1}-{2}.jsonl' -f [DateTime]::UtcNow.ToString('HHmmssfff'), $PID, [Guid]::NewGuid().ToString('N')))
|
|
20
|
+
clock = [Diagnostics.Stopwatch]::StartNew()
|
|
21
|
+
event = ''; session = ''
|
|
22
|
+
}
|
|
23
|
+
} catch { }
|
|
24
|
+
|
|
25
|
+
function global:Set-AgentDeckHookTraceIdentity($Hook) {
|
|
26
|
+
if (-not $global:AgentDeckHookTrace) { return }
|
|
27
|
+
$knownEvents = @('UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'PostToolUseFailure', 'PermissionRequest', 'PermissionDenied', 'Notification', 'Stop', 'StopFailure', 'Interrupt', 'SessionStart', 'SessionEnd', 'SubagentStart', 'SubagentStop')
|
|
28
|
+
if ($Hook -and $knownEvents -contains [string]$Hook.hook_event_name) {
|
|
29
|
+
$global:AgentDeckHookTrace.event = [string]$Hook.hook_event_name
|
|
30
|
+
}
|
|
31
|
+
if ($Hook -and [string]$Hook.session_id -match '^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$') {
|
|
32
|
+
$global:AgentDeckHookTrace.session = [string]$Hook.session_id
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function global:Write-AgentDeckHookTrace([string]$Stage, [hashtable]$Fields = @{}, $Failure = $null) {
|
|
37
|
+
if (-not $global:AgentDeckHookTrace) { return }
|
|
38
|
+
try {
|
|
39
|
+
$row = [ordered]@{
|
|
40
|
+
at = [DateTime]::UtcNow.ToString('o'); pid = $PID
|
|
41
|
+
stage = $Stage; elapsed_ms = $global:AgentDeckHookTrace.clock.ElapsedMilliseconds
|
|
42
|
+
event = $global:AgentDeckHookTrace.event; session = $global:AgentDeckHookTrace.session
|
|
43
|
+
}
|
|
44
|
+
# Callers pass only fixed status strings, numbers and booleans.
|
|
45
|
+
foreach ($key in $Fields.Keys) { $row[$key] = $Fields[$key] }
|
|
46
|
+
if ($Failure) {
|
|
47
|
+
$row.error_type = $Failure.Exception.GetType().FullName
|
|
48
|
+
$row.hresult = $Failure.Exception.HResult
|
|
49
|
+
$row.category = [string]$Failure.CategoryInfo.Category
|
|
50
|
+
$row.line = $Failure.InvocationInfo.ScriptLineNumber
|
|
51
|
+
}
|
|
52
|
+
$line = ($row | ConvertTo-Json -Compress -Depth 3) + "`n"
|
|
53
|
+
[IO.File]::AppendAllText($global:AgentDeckHookTrace.path, $line, (New-Object Text.UTF8Encoding($false)))
|
|
54
|
+
} catch {
|
|
55
|
+
# A diagnostic write must never change hook output or the exit status.
|
|
56
|
+
}
|
|
57
|
+
}
|
package/hooks/agentdeck-label.js
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 작업 라벨만 바꾼다 (상태는 건드리지 않는다).
|
|
4
|
-
*
|
|
5
|
-
* node agentdeck-label.js <sessionId> "결제 모듈 버그 수정"
|
|
6
|
-
* AGENTDECK_SESSION=<id> node agentdeck-label.js "빌드 중"
|
|
7
|
-
*
|
|
8
|
-
* 에이전트가 단계(분석→구현→빌드→검증)를 넘어갈 때마다 부르는 용도다.
|
|
9
|
-
* PowerShell 훅(agentdeck-notify.ps1)은 프로세스 기동만 100ms 대라 자주 부르기엔 무겁고,
|
|
10
|
-
* 그쪽은 status 파일을 읽어 직전 값과 합치는 read-modify-write 를 한다. 여기서는
|
|
11
|
-
* 아는 것(라벨)만 보내고 병합은 플러그인이 한다 (notify.service.ts apply 주석 참고).
|
|
12
|
-
*
|
|
13
|
-
* TCP(127.0.0.1:47500)로 한 줄 밀어넣고, Tabby 가 안 떠 있으면 status 파일로 폴백한다.
|
|
14
|
-
* 파일도 부분 갱신을 이해하므로 status 를 지어내지 않는다.
|
|
15
|
-
*/
|
|
16
|
-
const net = require('net')
|
|
17
|
-
const fs = require('fs')
|
|
18
|
-
const path = require('path')
|
|
19
|
-
|
|
20
|
-
const args = process.argv.slice(2)
|
|
21
|
-
const sessionId = args.length > 1 ? args[0] : process.env.AGENTDECK_SESSION
|
|
22
|
-
const label = (args.length > 1 ? args[1] : args[0] || '').trim()
|
|
23
|
-
const port = Number(process.env.AGENTDECK_PORT || 47500)
|
|
24
|
-
|
|
25
|
-
if (!sessionId || !label) {
|
|
26
|
-
console.error('usage: agentdeck-label.js <sessionId> "<label>" (or AGENTDECK_SESSION env)')
|
|
27
|
-
process.exit(2)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const payload = JSON.stringify({ sessionId, label, ts: Date.now() }) + '\n'
|
|
31
|
-
|
|
32
|
-
const fallback = () => {
|
|
33
|
-
const dir = path.join(
|
|
34
|
-
try {
|
|
35
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
36
|
-
// 직전 status 를 읽어 합치지 않는다 — 플러그인이 없는 필드를 지금 값으로 채운다
|
|
37
|
-
fs.writeFileSync(path.join(dir, sessionId + '.json'), payload.trim(), 'utf8')
|
|
38
|
-
console.log('label -> file')
|
|
39
|
-
} catch (e) {
|
|
40
|
-
console.error('label failed: ' + e.message)
|
|
41
|
-
process.exit(1)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const sock = net.connect({ port, host: '127.0.0.1' }, () => {
|
|
46
|
-
sock.end(payload, () => {
|
|
47
|
-
console.log('label -> tcp:' + port)
|
|
48
|
-
process.exit(0)
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
sock.setTimeout(700)
|
|
52
|
-
sock.on('timeout', () => { sock.destroy(); fallback() })
|
|
53
|
-
sock.on('error', () => fallback())
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 작업 라벨만 바꾼다 (상태는 건드리지 않는다).
|
|
4
|
+
*
|
|
5
|
+
* node agentdeck-label.js <sessionId> "결제 모듈 버그 수정"
|
|
6
|
+
* AGENTDECK_SESSION=<id> node agentdeck-label.js "빌드 중"
|
|
7
|
+
*
|
|
8
|
+
* 에이전트가 단계(분석→구현→빌드→검증)를 넘어갈 때마다 부르는 용도다.
|
|
9
|
+
* PowerShell 훅(agentdeck-notify.ps1)은 프로세스 기동만 100ms 대라 자주 부르기엔 무겁고,
|
|
10
|
+
* 그쪽은 status 파일을 읽어 직전 값과 합치는 read-modify-write 를 한다. 여기서는
|
|
11
|
+
* 아는 것(라벨)만 보내고 병합은 플러그인이 한다 (notify.service.ts apply 주석 참고).
|
|
12
|
+
*
|
|
13
|
+
* TCP(127.0.0.1:47500)로 한 줄 밀어넣고, Tabby 가 안 떠 있으면 status 파일로 폴백한다.
|
|
14
|
+
* 파일도 부분 갱신을 이해하므로 status 를 지어내지 않는다.
|
|
15
|
+
*/
|
|
16
|
+
const net = require('net')
|
|
17
|
+
const fs = require('fs')
|
|
18
|
+
const path = require('path')
|
|
19
|
+
|
|
20
|
+
const args = process.argv.slice(2)
|
|
21
|
+
const sessionId = args.length > 1 ? args[0] : process.env.AGENTDECK_SESSION
|
|
22
|
+
const label = (args.length > 1 ? args[1] : args[0] || '').trim()
|
|
23
|
+
const port = Number(process.env.AGENTDECK_PORT || 47500)
|
|
24
|
+
|
|
25
|
+
if (!sessionId || !label) {
|
|
26
|
+
console.error('usage: agentdeck-label.js <sessionId> "<label>" (or AGENTDECK_SESSION env)')
|
|
27
|
+
process.exit(2)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const payload = JSON.stringify({ sessionId, label, ts: Date.now() }) + '\n'
|
|
31
|
+
|
|
32
|
+
const fallback = () => {
|
|
33
|
+
const dir = path.join(require('./agentdeck-runtime.cjs').runtimeRoot(), 'status')
|
|
34
|
+
try {
|
|
35
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
36
|
+
// 직전 status 를 읽어 합치지 않는다 — 플러그인이 없는 필드를 지금 값으로 채운다
|
|
37
|
+
fs.writeFileSync(path.join(dir, sessionId + '.json'), payload.trim(), 'utf8')
|
|
38
|
+
console.log('label -> file')
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error('label failed: ' + e.message)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sock = net.connect({ port, host: '127.0.0.1' }, () => {
|
|
46
|
+
sock.end(payload, () => {
|
|
47
|
+
console.log('label -> tcp:' + port)
|
|
48
|
+
process.exit(0)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
sock.setTimeout(700)
|
|
52
|
+
sock.on('timeout', () => { sock.destroy(); fallback() })
|
|
53
|
+
sock.on('error', () => fallback())
|