tabby-agentdeck 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -7
- package/dist/config.d.ts +52 -24
- package/dist/deck.service.d.ts +87 -4
- package/dist/group.d.ts +27 -21
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2256 -265
- package/dist/index.js.map +1 -1
- package/dist/notify.service.d.ts +10 -0
- package/dist/project-root.d.ts +63 -0
- package/dist/sessionLedger.d.ts +149 -0
- package/dist/sessionLedger.service.d.ts +75 -0
- package/dist/settings.component.d.ts +14 -4
- package/dist/update.d.ts +76 -0
- package/dist/update.service.d.ts +61 -0
- package/dist/viewPanel.d.ts +11 -1
- package/package.json +5 -2
- package/updater/agentdeck-update.ps1 +108 -0
package/dist/notify.service.d.ts
CHANGED
|
@@ -233,6 +233,16 @@ export declare class WorkNotifyService {
|
|
|
233
233
|
* 눈으로 갈린다. 시각의 원천은 호출 줄의 `timestamp`(`SubagentCall.startedAt`)다.
|
|
234
234
|
*/
|
|
235
235
|
private subagentTooltip;
|
|
236
|
+
/**
|
|
237
|
+
* 지금 탭이 들고 있는 세션들 — `세션id -> 탭`.
|
|
238
|
+
*
|
|
239
|
+
* 사이드바의 "지난 세션" 목록이 이걸 본다. 살아 있는 세션은 이어받는 대신 그 탭으로 보내야
|
|
240
|
+
* 하기 때문이다: 같은 세션을 두 탭에서 `--resume` 하면 두 프로세스가 같은 기록 파일에
|
|
241
|
+
* 덧쓴다. 매핑은 이 서비스가 훅 보고로 이미 만들어 두었으니 판정에 드는 비용이 없다.
|
|
242
|
+
*
|
|
243
|
+
* 사본을 돌려준다 — 호출부가 들고 있는 동안 이쪽에서 지워도(`sessionTab.delete`) 순회가 깨지지 않게.
|
|
244
|
+
*/
|
|
245
|
+
liveSessions(): Map<string, BaseTabComponent>;
|
|
236
246
|
/**
|
|
237
247
|
* 탭에 묶인 세션들의 대화기록을 **자란 만큼만** 읽어 개수를 갱신한다 (2초마다).
|
|
238
248
|
*
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cwd 가 속한 **프로젝트 루트**를 찾는다 — 사이드바 그룹 경계의 유일한 출처.
|
|
3
|
+
*
|
|
4
|
+
* 예전에는 사람이 설정(`groupRoot`)에 상위 폴더를 적어 두고 "그 직하 한 단계" 를 그룹으로
|
|
5
|
+
* 삼았다. 그 규칙은 프로젝트들이 한 폴더에 나란히 있을 때만 맞고, `D:/Project/SoloLV/Root` 와
|
|
6
|
+
* `D:/Project/tabby-agentdeck` 처럼 깊이가 다르면 어떤 값을 적어도 한쪽이 틀린다.
|
|
7
|
+
* 그래서 설정을 없애고 **저장소 루트를 직접 찾는다** — 사람이 프로젝트라고 부르는 경계는
|
|
8
|
+
* 사실상 버전관리의 경계이기 때문이다.
|
|
9
|
+
*
|
|
10
|
+
* 두 가지를 지킨다 —
|
|
11
|
+
* 1. **렌더 중에 디스크를 훑지 않는다.** 조회는 캐시를 보고 즉시 돌아오고(`projectRootOf`),
|
|
12
|
+
* 처음 보는 폴더는 백그라운드로 찾은 뒤 `onResolved` 로 다시 그리게 한다. cwd 는 탭이
|
|
13
|
+
* 사는 동안 안 바뀌므로(group.ts 주석) 탭당 한 번이면 끝난다.
|
|
14
|
+
* 2. **판정 규칙은 순수 함수로 둔다**(`walkUp`/`findProjectRoot`) — fs 없이 단위 테스트한다.
|
|
15
|
+
*
|
|
16
|
+
* 캐시는 프로세스 수명 동안 유지된다. 세션 도중 `git init` 을 한 폴더는 다음 기동에 잡힌다 —
|
|
17
|
+
* 그걸 따라가려면 폴더를 계속 다시 물어야 하는데, 얻는 것에 비해 비용이 크다.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* 프로젝트 루트임을 알리는 표식. 위로 올라가며 **가장 먼저** 만나는 것이 루트다
|
|
21
|
+
* (중첩 저장소·서브모듈에서는 가까운 쪽이 그 탭의 프로젝트다).
|
|
22
|
+
*
|
|
23
|
+
* `.git` 은 폴더가 아닐 수도 있다 — worktree·서브모듈에서는 파일이다. 그래서 존재만 본다.
|
|
24
|
+
* `package.json` 류는 넣지 않았다: 모노레포·하위 패키지마다 있어서 프로젝트를 잘게 쪼갠다.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PROJECT_MARKERS: readonly [".git", ".hg", ".svn", ".p4config"];
|
|
27
|
+
/**
|
|
28
|
+
* **홈 디렉토리는 프로젝트가 아니다 — 거기서 멈춘다.**
|
|
29
|
+
*
|
|
30
|
+
* dotfiles 를 git 으로 관리하면 `~/.git` 이 있고(이 PC 가 그렇다), 그러면 홈 아래에서 띄운
|
|
31
|
+
* 모든 탭 — `%LOCALAPPDATA%` 임시 폴더까지 — 이 `junggon` 이라는 한 그룹으로 빨려 들어간다.
|
|
32
|
+
* 2026-09-11 실측: 한글 경로 검증용으로 연 탭이 그 폴더 대신 홈 그룹에 들어갔다.
|
|
33
|
+
* 홈 위(`C:/Users`, `C:/`)도 같은 이유로 볼 필요가 없다.
|
|
34
|
+
*/
|
|
35
|
+
export declare function homeBoundary(): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* cwd 자신부터 루트까지 — `['D:/a/b', 'D:/a', 'D:/']`.
|
|
38
|
+
*
|
|
39
|
+
* `stopAt`(보통 홈)에 닿으면 **그 폴더를 빼고** 멈춘다 — 거기 있는 마커는 프로젝트 경계가 아니다.
|
|
40
|
+
*/
|
|
41
|
+
export declare function walkUp(cwd: string | null | undefined, stopAt?: string | null): string[];
|
|
42
|
+
/**
|
|
43
|
+
* 표식을 가진 가장 가까운 조상 — 없으면 null(그때는 cwd 자체가 그룹이다).
|
|
44
|
+
*
|
|
45
|
+
* `hasMarker` 를 주입받는 이유는 테스트 때문만이 아니다 — 실제 조회는 비동기라
|
|
46
|
+
* (`detect`) 이 함수는 "어느 순서로 무엇을 묻는가" 만 정하고 묻는 방법은 모른다.
|
|
47
|
+
*/
|
|
48
|
+
export declare function findProjectRoot(cwd: string | null | undefined, hasMarker: (dir: string) => boolean, stopAt?: string | null): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* 그룹 키 계산용 조회 — **절대 막히지 않는다.**
|
|
51
|
+
*
|
|
52
|
+
* 캐시에 있으면 그 값, 없으면 null 을 돌려주고 탐지를 백그라운드로 건다. 탐지가 끝나서
|
|
53
|
+
* 그룹이 달라질 때만(`root` 를 찾았을 때만) `onResolved` 를 부른다 — 못 찾은 경우는
|
|
54
|
+
* 키가 cwd 그대로라 화면이 바뀌지 않으므로 다시 그릴 이유가 없다.
|
|
55
|
+
*/
|
|
56
|
+
export declare function projectRootOf(cwd: string | null | undefined, onResolved?: () => void): string | null;
|
|
57
|
+
/** 테스트·진단용 — 캐시를 비운다 (다음 조회가 다시 훑는다) */
|
|
58
|
+
export declare function clearProjectRootCache(): void;
|
|
59
|
+
/** 진단용 — 지금까지 찾아 둔 것 (`__agentdeck.groups()` 가 보여준다) */
|
|
60
|
+
export declare function projectRootCacheSnapshot(): Array<{
|
|
61
|
+
cwd: string;
|
|
62
|
+
root: string | null;
|
|
63
|
+
}>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **지난 세션 원장** — 죽은 Claude Code 세션을 다시 찾아낼 수 있게 하는 순수 규칙.
|
|
3
|
+
*
|
|
4
|
+
* 왜 필요한가 — `recovery.service.ts` 가 적어 둔 대로 agentdeck 은 Tabby 의 순정 탭 복원을
|
|
5
|
+
* 의도적으로 껐다. 되살아나는 것은 셸뿐이고 그 안의 세션은 이미 죽어 있어 "주인 없는 탭" 만
|
|
6
|
+
* 남기 때문이다. 그 자리에 들어갈 진짜 복원이 `claude --resume <세션id>` 인데, 사람이 막히는
|
|
7
|
+
* 지점은 명령이 아니라 **UUID 목록에서 내 작업을 못 찾는 것** 이다.
|
|
8
|
+
*
|
|
9
|
+
* agentdeck 은 그 답을 이미 갖고 있다 — Enter 시점에 입력창에서 주운 프롬프트 원문(라벨).
|
|
10
|
+
* 문제는 그게 **메모리에만 있다는 것** 이다(`status.service` 의 `TabState.label`). 탭이 닫히면
|
|
11
|
+
* 같이 증발한다. 그래서 이 모듈이 라벨·상태·cwd 를 세션 id 에 묶어 남기고, 목록을 만든다.
|
|
12
|
+
*
|
|
13
|
+
* fs·타이머는 여기 없다 — `group.ts`·`subagents.ts` 와 같은 이유다. 파일을 읽고 주기를
|
|
14
|
+
* 관리하는 것은 호출부(서비스)의 일이고, 이 파일은 **무엇을 어떤 순서로 보여줄지** 만 정한다.
|
|
15
|
+
* 그래야 `npm test` 에서 돌릴 수 있다.
|
|
16
|
+
*/
|
|
17
|
+
/** 원장 한 줄 — 세션 하나가 무엇이었는지 */
|
|
18
|
+
export interface SessionRecord {
|
|
19
|
+
/** Claude Code 세션 id (기록 파일 이름이기도 하다) */
|
|
20
|
+
sessionId: string;
|
|
21
|
+
/** 그 세션이 돌던 작업 폴더. **프로젝트 루트가 아니라 cwd 다** (`--resume` 이 이걸로 세션을 묶는다) */
|
|
22
|
+
cwd: string | null;
|
|
23
|
+
/** 사람이 알아보는 이름 — 살아 있을 때 주운 라벨, 없으면 기록에서 읽은 첫 프롬프트 */
|
|
24
|
+
label: string;
|
|
25
|
+
/** 마지막으로 관측된 작업 상태. 화면에 흔적으로만 남는다 */
|
|
26
|
+
lastStatus: string | null;
|
|
27
|
+
/** 마지막 활동 시각 (epoch ms) */
|
|
28
|
+
lastSeen: number;
|
|
29
|
+
/** 라벨의 출처 — `live` 는 사람이 친 원문, `head` 는 기록 앞부분에서 주운 것 */
|
|
30
|
+
from: 'live' | 'head';
|
|
31
|
+
}
|
|
32
|
+
/** 화면에 그릴 한 줄 — `SessionRecord` 에 "지금 열려 있나" 를 더한 것 */
|
|
33
|
+
export interface ResumeRow extends SessionRecord {
|
|
34
|
+
/** 이 세션을 이미 들고 있는 탭 id. 있으면 이어받기 대신 그 탭으로 이동한다 */
|
|
35
|
+
openTabId: string | null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 기록 파일 앞부분을 얼마나 읽을지.
|
|
39
|
+
*
|
|
40
|
+
* 전부 파싱하면 안 된다 — 실측 세션이 2.6MB(1,021줄)·5.5MB 고, 목록 한 번에 수십 개를 훑는다.
|
|
41
|
+
* 필요한 것은 `cwd` 와 **첫 사람 프롬프트** 둘뿐인데 실측에서 `cwd` 는 5번째 줄,
|
|
42
|
+
* 프롬프트는 65번째 줄에 나왔다(2026-09-11, `f54b20b9`). 앞의 60여 줄은 훅 주입·모드·첨부라
|
|
43
|
+
* 여유를 크게 잡아도 이 정도면 닿는다. 못 찾으면 라벨 없이 두지, 파일을 더 파고들지 않는다.
|
|
44
|
+
*/
|
|
45
|
+
export declare const HEAD_LINE_LIMIT = 400;
|
|
46
|
+
/** 줄 수와 별개로 이 바이트를 넘기면 멈춘다 — 한 줄이 통째로 거대한 기록(대용량 첨부)이 있다 */
|
|
47
|
+
export declare const HEAD_BYTE_LIMIT: number;
|
|
48
|
+
/** 사람이 친 프롬프트로 볼 수 있는 문자열인가 */
|
|
49
|
+
export declare function isHumanPrompt(text: unknown): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 기록 줄(파싱된 JSON) 하나에서 사람이 친 프롬프트를 꺼낸다 — 아니면 null.
|
|
52
|
+
*
|
|
53
|
+
* `message.content` 는 문자열일 때도 있고 블록 배열일 때도 있다. 배열이면 `text` 블록만 잇는다
|
|
54
|
+
* (`tool_result` 가 섞여 들어오면 라벨이 도구 출력으로 오염된다).
|
|
55
|
+
*/
|
|
56
|
+
export declare function promptFromRecord(rec: any): string | null;
|
|
57
|
+
/** 라벨로 쓸 길이로 줄인다 — 사이드바 한 줄은 말줄임이 있지만 원장에 소설을 담을 이유는 없다 */
|
|
58
|
+
export declare const LABEL_MAX = 140;
|
|
59
|
+
export declare function clampLabel(text: string | null | undefined): string;
|
|
60
|
+
/** 기록 앞부분에서 읽어낸 것 */
|
|
61
|
+
export interface HeadInfo {
|
|
62
|
+
cwd: string | null;
|
|
63
|
+
label: string;
|
|
64
|
+
/** 앞부분에서 본 가장 이른 timestamp — 파일 mtime 이 없을 때의 폴백 */
|
|
65
|
+
startedAt: number | null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 기록 앞부분의 줄들에서 cwd·첫 프롬프트를 뽑는다.
|
|
69
|
+
*
|
|
70
|
+
* **폴더 이름 규칙을 흉내내지 않는다.** `~/.claude/projects/<폴더>` 의 이름은 작업 경로를 `-` 로
|
|
71
|
+
* 뭉갠 것이라 드라이브 문자·한글·특수문자에서 되돌릴 수 없다(`notify.service.ts:1006` 이 같은
|
|
72
|
+
* 이유로 폴더 이름 대신 파일명 매칭을 쓴다). 기록 안에 `cwd` 가 그대로 들어 있으니 그걸 읽는다.
|
|
73
|
+
*/
|
|
74
|
+
export declare function readHead(lines: readonly string[]): HeadInfo;
|
|
75
|
+
/**
|
|
76
|
+
* 원장과 디스크 스캔을 합친다 — **세션 id 가 키, 원장이 우선.**
|
|
77
|
+
*
|
|
78
|
+
* 두 경로가 다 필요하다. 원장에만 기대면 agentdeck 설치 이전 세션과 다른 터미널에서 띄운
|
|
79
|
+
* 세션이 통째로 안 보이고, 디스크에만 기대면 사람이 친 라벨(가장 좋은 단서)을 잃는다.
|
|
80
|
+
*
|
|
81
|
+
* 원장이 이기는 것은 **라벨과 상태** 뿐이다. `lastSeen` 은 둘 중 큰 값을 쓴다 — 원장은 탭이
|
|
82
|
+
* 닫힐 때 멈추는데 그 뒤 다른 터미널에서 같은 세션을 이어받았을 수 있고, 그때는 디스크가 맞다.
|
|
83
|
+
*/
|
|
84
|
+
export declare function mergeSessions(ledger: readonly SessionRecord[], scanned: readonly SessionRecord[]): SessionRecord[];
|
|
85
|
+
/**
|
|
86
|
+
* 기록 파일이 남아 있는 세션만 남긴다 — 원장이 무한히 부풀지 않게.
|
|
87
|
+
*
|
|
88
|
+
* 디스크를 진실로 삼는 이유: 사람이 `~/.claude/projects` 를 지우면 그 세션은 이어받을 수 없고,
|
|
89
|
+
* 그걸 원장이 계속 보여주면 눌렀을 때 빈 세션이 뜬다.
|
|
90
|
+
*/
|
|
91
|
+
export declare function pruneLedger(ledger: readonly SessionRecord[], aliveIds: ReadonlySet<string>): SessionRecord[];
|
|
92
|
+
export interface ResumeListOptions {
|
|
93
|
+
/**
|
|
94
|
+
* 이 줄 묶음이 **덮는 그룹 키들**. 보통 한 개지만 하나가 아닐 수 있다 —
|
|
95
|
+
* 사이드바는 그룹이 하나뿐이면 헤더를 그리지 않고 목록을 **평면 한 덩이**로 만드는데
|
|
96
|
+
* (deck.service `renderPlan`), 그때 그 덩이의 `key` 는 `null`(기타)이 된다.
|
|
97
|
+
* 거기에 `null` 하나만 넘기면 어떤 세션도 안 걸려 **가장 흔한 화면(프로젝트 하나)에서
|
|
98
|
+
* 목록이 영영 비어 있다**. 그래서 키가 아니라 키의 집합을 받는다.
|
|
99
|
+
*/
|
|
100
|
+
groupKeys: readonly (string | null)[];
|
|
101
|
+
/** cwd -> 그룹 키. 호출부가 `groupKeyOf` + `projectRootOf` 를 엮어 넘긴다 */
|
|
102
|
+
keyOf: (cwd: string | null) => string | null;
|
|
103
|
+
/** 키 비교용 폴딩 (대소문자·구분자) — `group.ts` 의 `foldGroupKey` */
|
|
104
|
+
fold: (key: string | null | undefined) => string;
|
|
105
|
+
/** 지금 탭이 들고 있는 세션 — sessionId -> tabId */
|
|
106
|
+
live: ReadonlyMap<string, string>;
|
|
107
|
+
/** 며칠치까지 보여줄지 (0 이하면 제한 없음) */
|
|
108
|
+
days: number;
|
|
109
|
+
/** 몇 줄까지 (0 이하면 제한 없음) */
|
|
110
|
+
limit: number;
|
|
111
|
+
/** 사람이 숨긴 세션 id */
|
|
112
|
+
hidden: ReadonlySet<string>;
|
|
113
|
+
/** 기준 시각 — 테스트가 고정할 수 있게 주입받는다 */
|
|
114
|
+
now: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 한 그룹에 그릴 지난 세션 줄.
|
|
118
|
+
*
|
|
119
|
+
* **살아 있는 세션을 빼지 않는다** — `openTabId` 를 달아 그대로 둔다. 지우면 "내가 방금 보던
|
|
120
|
+
* 그 작업이 목록에 없다" 가 되어 사람이 같은 세션을 다시 찾아 헤맨다. 대신 눌렀을 때
|
|
121
|
+
* 이어받지 않고 그 탭으로 보낸다(같은 세션을 두 번 이어받으면 두 프로세스가 한 기록에 덧쓴다).
|
|
122
|
+
*
|
|
123
|
+
* 정렬은 **마지막 활동 내림차순** 하나뿐이다. 상태별로 묶고 싶어질 수 있는데, 지난 세션에서
|
|
124
|
+
* 사람이 찾는 것은 "방금 하던 것" 이지 "완료된 것" 이 아니다.
|
|
125
|
+
*/
|
|
126
|
+
export declare function resumeRowsFor(records: readonly SessionRecord[], opts: ResumeListOptions): ResumeRow[];
|
|
127
|
+
/**
|
|
128
|
+
* 마지막 활동 시각을 사람이 읽는 짧은 꼴로.
|
|
129
|
+
*
|
|
130
|
+
* 오늘이면 시각(`14:12`), 어제면 `어제`, 그보다 오래면 `9/10`. 사이드바 줄의 오른쪽 끝에
|
|
131
|
+
* 들어가므로 길면 제목 폭을 먹는다 — 연도는 넣지 않는다(제한 기간이 기본 7일이라 못 만난다).
|
|
132
|
+
*/
|
|
133
|
+
export declare function formatWhen(ts: number, now: number): string;
|
|
134
|
+
/** 파일 이름에서 세션 id 를 꺼낸다 (아니면 null) */
|
|
135
|
+
export declare function sessionIdFromFile(name: string): string | null;
|
|
136
|
+
/**
|
|
137
|
+
* 셸에 보낼 이어받기 명령.
|
|
138
|
+
*
|
|
139
|
+
* **프로필의 `command` 를 갈아끼우지 않고 셸에 문자열로 보낸다.** 이유 둘 —
|
|
140
|
+
* ① 기존 `agentdeck:root` 프로필(`-NoLogo -ExecutionPolicy Bypass`, COLORTERM/FORCE_COLOR)을
|
|
141
|
+
* 그대로 쓴다. npm 전역 `.ps1` 래퍼가 실행정책에 막히는 문제를 그 인자가 이미 풀어 둔다
|
|
142
|
+
* (`profile.service.ts:58`).
|
|
143
|
+
* ② claude 가 끝나도 셸이 남아 사람이 이어서 쓸 수 있다. `command` 로 박으면
|
|
144
|
+
* `behaviorOnSessionEnd` 에 따라 탭이 닫힌다.
|
|
145
|
+
*
|
|
146
|
+
* 세션 id 는 우리가 파일 이름에서 읽은 값이고 `SESSION_FILE_RE` 를 통과한 것만 여기 온다 —
|
|
147
|
+
* 그래도 명령을 만드는 자리이므로 한 번 더 막는다. 통과 못 하면 빈 문자열이고 호출부가 아무것도 보내지 않는다.
|
|
148
|
+
*/
|
|
149
|
+
export declare function resumeCommand(sessionId: string, fork: boolean): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ConfigService } from 'tabby-core';
|
|
2
|
+
import { SessionRecord } from './sessionLedger';
|
|
3
|
+
/**
|
|
4
|
+
* 지난 세션 원장의 **파일 담당** — 읽고, 쓰고, 기록 폴더를 훑는다.
|
|
5
|
+
* 무엇을 보여줄지 정하는 규칙은 전부 `sessionLedger.ts` 에 있다(fs 없이 테스트되는 쪽).
|
|
6
|
+
*
|
|
7
|
+
* 저장 위치는 `%LOCALAPPDATA%\tabby-agentdeck\sessions.json` — 훅 상태 파일 폴더 옆이다.
|
|
8
|
+
* **Tabby 설정(`config.yaml`)에 넣지 않는다.** 세션은 계속 쌓이는데 거기 넣으면 남의 설정 파일이
|
|
9
|
+
* 수백 줄 불어나고, 라벨이 바뀔 때마다 `config.save()` 가 돌아 관계없는 설정까지 다시 쓴다.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SessionLedgerService {
|
|
12
|
+
private config;
|
|
13
|
+
private root;
|
|
14
|
+
private file;
|
|
15
|
+
/** Claude Code 대화기록이 사는 곳 (`notify.service` 와 같은 자리) */
|
|
16
|
+
private projectsDir;
|
|
17
|
+
/** 세션 id -> 원장 한 줄. 사람이 친 라벨이 여기 남는다 */
|
|
18
|
+
private ledger;
|
|
19
|
+
/** 디스크 스캔 결과 (기록 파일에서 읽은 것) */
|
|
20
|
+
private scanned;
|
|
21
|
+
/**
|
|
22
|
+
* 기록 앞부분 파싱 캐시 — `세션id:mtime` 키. 파일이 자라도 **앞부분은 안 바뀐다**.
|
|
23
|
+
* 그래도 mtime 을 키에 넣는 이유는 세션이 이어받기로 되살아나 새 줄이 앞에 붙는 경우가
|
|
24
|
+
* 아니라, 파일이 통째로 갈렸을 때(삭제 후 재생성) 옛 값을 물고 있지 않기 위해서다.
|
|
25
|
+
*/
|
|
26
|
+
private headCache;
|
|
27
|
+
private loaded;
|
|
28
|
+
private saveTimer;
|
|
29
|
+
private scanning;
|
|
30
|
+
private lastScan;
|
|
31
|
+
/** 목록이 바뀌었을 때 호출부(사이드바)가 다시 그리도록 */
|
|
32
|
+
onChange: (() => void) | null;
|
|
33
|
+
constructor(config: ConfigService);
|
|
34
|
+
private load;
|
|
35
|
+
/**
|
|
36
|
+
* 저장을 한 번으로 모은다. 라벨은 사람이 Enter 를 칠 때마다 갱신되므로
|
|
37
|
+
* 그때마다 파일을 쓰면 타자 속도로 디스크를 때린다.
|
|
38
|
+
*/
|
|
39
|
+
private scheduleSave;
|
|
40
|
+
private save;
|
|
41
|
+
/**
|
|
42
|
+
* 세션 하나의 지금을 원장에 적는다.
|
|
43
|
+
*
|
|
44
|
+
* 라벨은 **비어 있지 않을 때만** 덮는다 — 탭이 막 열려 아직 프롬프트가 없을 때 빈 문자열로
|
|
45
|
+
* 덮으면 지난 실행에서 적어 둔 좋은 라벨을 지우게 된다.
|
|
46
|
+
*/
|
|
47
|
+
note(sessionId: string, patch: {
|
|
48
|
+
cwd?: string | null;
|
|
49
|
+
label?: string | null;
|
|
50
|
+
status?: string | null;
|
|
51
|
+
}): void;
|
|
52
|
+
/**
|
|
53
|
+
* 기록 폴더를 훑어 세션 목록을 다시 만든다.
|
|
54
|
+
*
|
|
55
|
+
* **폴링하지 않는다.** `notify.service.ts:64` 가 적어 둔 대로 `~/.claude/projects` 전체를
|
|
56
|
+
* 2초마다 readdir 하면 안 된다. 목록을 펼칠 때와 탭이 닫힐 때만 부른다.
|
|
57
|
+
*/
|
|
58
|
+
refresh(force?: boolean): Promise<void>;
|
|
59
|
+
private scan;
|
|
60
|
+
/**
|
|
61
|
+
* 기록 파일 앞부분만 읽는다 — 전체를 읽지 않는 이유는 `HEAD_LINE_LIMIT` 주석에 있다.
|
|
62
|
+
* 마지막 조각은 줄이 잘려 있을 수 있으니 버린다(파싱 실패로 조용히 넘어가긴 하지만,
|
|
63
|
+
* 버리는 편이 "왜 라벨이 가끔 이상한가" 를 애초에 만들지 않는다).
|
|
64
|
+
*/
|
|
65
|
+
private readHeadOf;
|
|
66
|
+
/** 원장 + 디스크를 합친 전체 목록. 렌더 중에 불러도 막히지 않는다(캐시만 본다) */
|
|
67
|
+
records(): SessionRecord[];
|
|
68
|
+
/** 진단용 — `__agentdeck.sessions()` 가 보여준다 */
|
|
69
|
+
snapshot(): {
|
|
70
|
+
ledger: number;
|
|
71
|
+
scanned: number;
|
|
72
|
+
file: string;
|
|
73
|
+
lastScan: number;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ConfigService, PlatformService } from 'tabby-core';
|
|
2
2
|
import { AgentDeckRecoveryService } from './recovery.service';
|
|
3
|
+
import { AgentDeckUpdateService } from './update.service';
|
|
3
4
|
/**
|
|
4
5
|
* 설정 > AgentDeck 탭.
|
|
5
6
|
*
|
|
@@ -20,8 +21,9 @@ import { AgentDeckRecoveryService } from './recovery.service';
|
|
|
20
21
|
* 설정 창에 자리가 없어 `config.yaml` 을 손으로 고쳐야만 바꿀 수 있었다.
|
|
21
22
|
*
|
|
22
23
|
* `keyboardNavWrap` 과 `keyboardCloseTab`(0.19.0) 은 `keyboardNav` 를 끄면 아무 뜻이 없으므로 그때는 줄을 아예 감춘다 —
|
|
23
|
-
* `groupRoot`·`collapsedGroups` 를 `groupByProject` 에 매달아 둔 것과 같은 방식이고,
|
|
24
24
|
* 꺼진 값을 회색으로 남겨 두는 것보다 "지금 만질 수 있는 것" 이 짧아진다.
|
|
25
|
+
* 세션 그룹 절에 값이 하나도 없는 것(0.20.0)도 같은 기준이다 — 끌 이유가 없는 스위치와
|
|
26
|
+
* 자동으로 알아낼 수 있는 경로는 설정이 아니라 동작이어야 한다.
|
|
25
27
|
* 이 세 값은 deck.service/notify.service 가 쓸 때마다 store 를 다시 읽으므로
|
|
26
28
|
* (deck.service.ts `navCanFocus`/`moveNav`, notify.service `subagentEnabled`)
|
|
27
29
|
* 토글하면 바로 듣는다 — 그래서 재시작 안내를 붙이지 않았다.
|
|
@@ -29,6 +31,7 @@ import { AgentDeckRecoveryService } from './recovery.service';
|
|
|
29
31
|
export declare class AgentDeckSettingsTabComponent {
|
|
30
32
|
config: ConfigService;
|
|
31
33
|
recovery: AgentDeckRecoveryService;
|
|
34
|
+
private update;
|
|
32
35
|
private platform;
|
|
33
36
|
/** 이 환경에서 훅을 걸 수 있나 (통보 스크립트가 PowerShell 이라 Windows 전용) */
|
|
34
37
|
hooksSupported: boolean;
|
|
@@ -46,7 +49,16 @@ export declare class AgentDeckSettingsTabComponent {
|
|
|
46
49
|
diagBundlePath: string;
|
|
47
50
|
/** 묶음을 만들다 실패한 이유 */
|
|
48
51
|
diagError: string;
|
|
49
|
-
|
|
52
|
+
/** `지금 확인` 이 도는 동안 버튼을 잠근다 (레지스트리 응답까지 몇 초) */
|
|
53
|
+
updateBusy: boolean;
|
|
54
|
+
/** 마지막 확인 결과 한 줄 */
|
|
55
|
+
updateMsg: string;
|
|
56
|
+
constructor(config: ConfigService, recovery: AgentDeckRecoveryService, update: AgentDeckUpdateService, platform: PlatformService);
|
|
57
|
+
/**
|
|
58
|
+
* 사람이 직접 누른 확인. `force` 라 간격과 `다시 묻지 않기` 를 건너뛴다 —
|
|
59
|
+
* 눌렀다는 것 자체가 "지금 알고 싶다" 는 뜻이다.
|
|
60
|
+
*/
|
|
61
|
+
checkUpdate(): Promise<void>;
|
|
50
62
|
private refreshHooks;
|
|
51
63
|
/**
|
|
52
64
|
* 훅을 걸거나 뗀다.
|
|
@@ -92,7 +104,5 @@ export declare class AgentDeckSettingsTabComponent {
|
|
|
92
104
|
* 쌓일 수 있고, 그때 되돌릴 방법이 config 파일 편집뿐이었다.
|
|
93
105
|
*/
|
|
94
106
|
expandGroups(): void;
|
|
95
|
-
/** 프로젝트 기준 폴더를 OS 다이얼로그로 고른다 (작업 폴더와 같은 방식) */
|
|
96
|
-
pickGroupRoot(): Promise<void>;
|
|
97
107
|
pickCwd(): Promise<void>;
|
|
98
108
|
}
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **자동 업데이트의 판단 규칙** — 언제 확인할지, 새 버전인지, 업데이트해도 되는지.
|
|
3
|
+
*
|
|
4
|
+
* 네트워크·fs·타이머는 여기 없다 (`group.ts`·`sessionLedger.ts` 와 같은 이유). 이 파일은
|
|
5
|
+
* "무엇을 근거로 결정하는가" 만 정하고, 실제로 묻고 설치하는 것은 `update.service.ts` 다.
|
|
6
|
+
*
|
|
7
|
+
* ## 왜 "받아 두고 다음 기동에 적용" 인가
|
|
8
|
+
*
|
|
9
|
+
* Tabby 는 플러그인을 **기동할 때 한 번** 읽는다. 돌고 있는 플러그인을 갈아끼울 방법이 없으므로
|
|
10
|
+
* 어떤 방식으로 업데이트하든 재시작이 필요하다. 그래서 이 기능은 조용히 받아만 두고
|
|
11
|
+
* "재시작하면 적용된다" 고 한 번 알린다 — 사람의 작업을 끊지 않는 유일한 모양이다.
|
|
12
|
+
*/
|
|
13
|
+
/** npm 레지스트리 기본값. 사내 미러를 쓰는 사람은 설정으로 바꾼다 */
|
|
14
|
+
export declare const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
15
|
+
/** 이 패키지 이름 — 레지스트리에 물어볼 대상이자 `installPlugin` 에 넘길 이름 */
|
|
16
|
+
export declare const PACKAGE_NAME = "tabby-agentdeck";
|
|
17
|
+
/** 확인 간격 기본값 (시간). 기동마다 묻지 않는다 — 레지스트리에도 사람 네트워크에도 예의가 아니다 */
|
|
18
|
+
export declare const DEFAULT_INTERVAL_HOURS = 6;
|
|
19
|
+
/**
|
|
20
|
+
* 버전 문자열을 숫자 3개 + 프리릴리스로 가른다.
|
|
21
|
+
* semver 가 아니면 `null` — 그런 값이 오면 비교하지 않고 "모른다" 로 둔다(업데이트를 강행하지 않는다).
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseVersion(v: unknown): {
|
|
24
|
+
nums: number[];
|
|
25
|
+
pre: string | null;
|
|
26
|
+
} | null;
|
|
27
|
+
/**
|
|
28
|
+
* semver 비교 — `a < b` 면 -1, 같으면 0, `a > b` 면 1. 못 읽으면 `null`.
|
|
29
|
+
*
|
|
30
|
+
* 프리릴리스는 같은 숫자의 정식판보다 **낮다**(`1.1.0-beta` < `1.1.0`). 프리릴리스끼리는
|
|
31
|
+
* 점으로 끊어 숫자는 수로, 그 외는 문자열로 비교한다 (semver 규칙 그대로).
|
|
32
|
+
*/
|
|
33
|
+
export declare function compareVersions(a: unknown, b: unknown): number | null;
|
|
34
|
+
/** 레지스트리의 것이 지금 것보다 새로운가. 못 읽으면 `false` — 모르면 건드리지 않는다 */
|
|
35
|
+
export declare function isNewer(current: unknown, latest: unknown): boolean;
|
|
36
|
+
/** 레지스트리 응답(JSON 문자열)에서 버전을 꺼낸다 — `<registry>/<pkg>/latest` 의 모양 */
|
|
37
|
+
export declare function parseLatestVersion(body: string): string | null;
|
|
38
|
+
/** 물어볼 주소. 레지스트리 끝의 `/` 가 있든 없든 같은 주소가 나오게 한다 */
|
|
39
|
+
export declare function latestUrl(registry: string | null | undefined, pkg?: string): string;
|
|
40
|
+
/** 지금 확인할 때가 됐나. `lastCheckAt` 이 미래면(시계가 뒤로 갔다) 확인한다 */
|
|
41
|
+
export declare function shouldCheck(lastCheckAt: unknown, now: number, intervalHours: number): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* 이 설치본을 자동으로 갈아끼워도 되나.
|
|
44
|
+
*
|
|
45
|
+
* **개발 설치(소스 트리로의 junction)에는 절대 하면 안 된다.** Tabby 플러그인 폴더의
|
|
46
|
+
* `tabby-agentdeck` 은 개발 중에는 `D:\Project\tabby-agentdeck` 을 가리키는 링크이고
|
|
47
|
+
* (`install.ps1`), 거기에 `npm install` 이 돌면 **작업 중인 소스 트리를 덮어쓴다.**
|
|
48
|
+
*
|
|
49
|
+
* 판정은 두 가지를 본다 — 둘 다 배포본에는 없는 것들이다
|
|
50
|
+
* (`package.json` 의 `files` 는 `dist`·`hooks`·`install.ps1` 뿐이다):
|
|
51
|
+
* - `src/` 나 `webpack.config.js` 가 있다 = 소스 트리
|
|
52
|
+
* - `.git` 이 있다 = 저장소
|
|
53
|
+
* 하나라도 걸리면 확인만 하고 설치는 하지 않는다.
|
|
54
|
+
*/
|
|
55
|
+
export declare function canSelfUpdate(marks: {
|
|
56
|
+
hasSrc: boolean;
|
|
57
|
+
hasGit: boolean;
|
|
58
|
+
hasWebpackConfig: boolean;
|
|
59
|
+
isLink: boolean;
|
|
60
|
+
}): boolean;
|
|
61
|
+
export type UpdateOutcome = 'disabled' | 'too-soon' | 'dev-install' | 'unknown' | 'up-to-date' | 'update-ready' | 'skipped' | 'postponed' | 'installing' | 'failed';
|
|
62
|
+
/**
|
|
63
|
+
* 확인 결과로 무엇을 할지 — 순수 결정.
|
|
64
|
+
*
|
|
65
|
+
* 설치 성공/실패는 이 함수가 모르므로(`installPlugin` 이 비동기다) 여기서는
|
|
66
|
+
* `update-ready` 직전까지만 정하고, 실제 설치는 서비스가 하고 실패하면 `failed` 로 덮는다.
|
|
67
|
+
*/
|
|
68
|
+
export declare function decide(input: {
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
lastCheckAt: unknown;
|
|
71
|
+
now: number;
|
|
72
|
+
intervalHours: number;
|
|
73
|
+
devInstall: boolean;
|
|
74
|
+
current: string;
|
|
75
|
+
latest: string | null;
|
|
76
|
+
}): UpdateOutcome;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AppService, ConfigService, NotificationsService, PlatformService } from 'tabby-core';
|
|
2
|
+
import { UpdateOutcome } from './update';
|
|
3
|
+
/**
|
|
4
|
+
* npm 레지스트리를 보고 **Tabby 를 껐다 켜면서** 새 버전을 깐다.
|
|
5
|
+
*
|
|
6
|
+
* 흐름은 넷이다 —
|
|
7
|
+
* ① 기동하고 잠시 뒤 레지스트리에 묻는다
|
|
8
|
+
* ② 새 버전이 있으면 **물어본다** (`지금 업데이트` / `나중에` / `다시 묻지 않기`)
|
|
9
|
+
* ③ 승낙하면 설치 스크립트를 **밖으로 띄우고** Tabby 를 닫는다
|
|
10
|
+
* ④ 스크립트가 Tabby 종료를 기다렸다가 설치하고 다시 띄운다
|
|
11
|
+
*
|
|
12
|
+
* **왜 밖에서 까나.** 돌고 있는 플러그인은 자기를 갈아끼울 수 없다 — Tabby 는 기동할 때
|
|
13
|
+
* `dist/index.js` 를 한 번 읽어 메모리에 올리므로 디스크를 바꿔도 돌던 코드는 옛것이다.
|
|
14
|
+
* 게다가 Windows 는 로드된 파일을 잡고 있어서, 떠 있는 동안 npm 이 그 폴더를 지우려 들면
|
|
15
|
+
* 반쯤 지운 채 실패할 수 있다 — 플러그인이 깨진 채 남는 것이 가장 나쁜 결과다.
|
|
16
|
+
*
|
|
17
|
+
* **개발 설치에는 설치하지 않는다.** 플러그인 폴더의 `tabby-agentdeck` 은 개발 중에는 소스
|
|
18
|
+
* 트리를 가리키는 링크이고(`plugins/package.json` 에 `file:D:/Project/tabby-agentdeck` 로 적힌다),
|
|
19
|
+
* 거기에 `npm install` 이 돌면 작업 중인 트리를 덮어쓴다. 판정은 `update.ts` 의 `canSelfUpdate`.
|
|
20
|
+
*/
|
|
21
|
+
export declare class AgentDeckUpdateService {
|
|
22
|
+
private app;
|
|
23
|
+
private config;
|
|
24
|
+
private platform;
|
|
25
|
+
private notifications;
|
|
26
|
+
constructor(app: AppService, config: ConfigService, platform: PlatformService, notifications: NotificationsService);
|
|
27
|
+
init(): void;
|
|
28
|
+
/**
|
|
29
|
+
* 한 번 확인하고, 새 버전이면 물어본 뒤 설치를 건다.
|
|
30
|
+
*
|
|
31
|
+
* `force` 는 설정 창의 `지금 확인` 용 — 간격 제한과 `다시 묻지 않기` 를 건너뛴다
|
|
32
|
+
* (사람이 직접 눌렀으니 그 순간만큼은 묻는 게 맞다). 기능 자체를 끈 것은 존중한다.
|
|
33
|
+
*/
|
|
34
|
+
run(reason: string, force?: boolean): Promise<UpdateOutcome>;
|
|
35
|
+
/** ② 물어보고 ③ 설치 스크립트를 띄운 뒤 Tabby 를 닫는다 */
|
|
36
|
+
private confirmAndInstall;
|
|
37
|
+
/**
|
|
38
|
+
* 설치 스크립트를 **패키지 밖에서** 떼어 띄운다.
|
|
39
|
+
*
|
|
40
|
+
* 임시 폴더로 복사하는 이유: 이 스크립트는 곧 npm 이 통째로 갈아엎을 폴더 안에 산다.
|
|
41
|
+
* 제자리에서 돌리면 자기 발밑이 사라진다.
|
|
42
|
+
*
|
|
43
|
+
* `detached` + `unref` 라야 Tabby 가 죽어도 살아남는다 — 부모가 죽을 때 같이 끌려가면
|
|
44
|
+
* 설치는커녕 Tabby 를 다시 띄우지도 못한다.
|
|
45
|
+
*/
|
|
46
|
+
private launchUpdater;
|
|
47
|
+
/** `<Tabby 설정폴더>/plugins` — npm 이 `--prefix` 로 받을 경로 */
|
|
48
|
+
private pluginsDir;
|
|
49
|
+
/**
|
|
50
|
+
* 이 설치본이 개발 설치인지 가리는 표식.
|
|
51
|
+
*
|
|
52
|
+
* 배포본에는 `dist`·`hooks`·`updater`·`install.ps1` 만 들어간다(`package.json` 의 `files`).
|
|
53
|
+
* `src/`·`webpack.config.js`·`.git` 중 하나라도 있으면 소스 트리이고, 플러그인 폴더의
|
|
54
|
+
* 항목이 링크면 junction 설치다 — 어느 쪽이든 건드리면 안 된다.
|
|
55
|
+
*/
|
|
56
|
+
private installMarks;
|
|
57
|
+
/** Tabby 플러그인 폴더의 우리 항목이 링크인가 — junction 설치의 가장 직접적인 증거 */
|
|
58
|
+
private pluginEntryIsLink;
|
|
59
|
+
/** GET 한 번. 리다이렉트는 따라가지 않는다 — 레지스트리 `/latest` 는 200 으로 답한다 */
|
|
60
|
+
private fetch;
|
|
61
|
+
}
|
package/dist/viewPanel.d.ts
CHANGED
|
@@ -171,7 +171,17 @@ export declare class ViewPanel {
|
|
|
171
171
|
install(): void;
|
|
172
172
|
/** 파일을 끌어다 놓으면 그걸 연다 — 탐색기·VS Code 탭 어느 쪽에서 끌어도 된다 */
|
|
173
173
|
private installDrop;
|
|
174
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* 터미널과 닿는 쪽 경계선을 끌어 폭을 바꾼다.
|
|
176
|
+
*
|
|
177
|
+
* **폭은 "잡은 순간의 폭 + 끈 거리" 로 잰다 — 창 가장자리부터 재면 안 된다.**
|
|
178
|
+
* 사이드바와 패널이 같은 쪽에 붙으면 패널은 창 가장자리에서 사이드바 폭만큼 안쪽에 앉는데
|
|
179
|
+
* (`deck.service.ts` relayout 의 `outerOffset`), `rect.right - clientX` 는 그 몫까지
|
|
180
|
+
* 폭으로 세어 버린다. 그래서 잡자마자 사이드바 폭(200~560px)만큼 한 번 커지고, 사용자는
|
|
181
|
+
* 그걸 도로 줄이는 것으로 조작을 시작하게 된다 (2026-09-11 유저 지적).
|
|
182
|
+
* 델타 방식은 그 몫도, 손잡이 안에서 어디를 잡았는지도 상쇄되어 **튐이 아예 없다.**
|
|
183
|
+
* (사이드바 `dock.ts` 가 절대 거리로 재는 것은 거기서만 맞다 — 사이드바는 창에 딱 붙는다.)
|
|
184
|
+
*/
|
|
175
185
|
private installResize;
|
|
176
186
|
/** deck 의 relayout 이 정해 준 자리에 앉는다 (사이드바와 같은 방식으로 절대 배치) */
|
|
177
187
|
layout(side: ViewSide, width: number, outerOffset: number): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tabby-agentdeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Run AI coding agents side by side in Tabby - a live deck of agent sessions with status, a preview panel for the files they touch, agent-friendly hotkeys and image paste",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tabby-plugin",
|
|
@@ -16,11 +16,14 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "webpack --progress --color",
|
|
18
18
|
"watch": "webpack --progress --color --watch",
|
|
19
|
-
"test": "tsc src/prompt.ts src/detect.ts src/claudeHooks.ts src/screen.ts src/bind.ts src/order.ts src/reason.ts src/tabenv.ts src/viewer.ts src/markdown.ts src/ime.ts src/gitDiff.ts src/agents.ts src/group.ts src/gitCommit.ts src/diag.ts src/subagents.ts src/nav.ts src/api.ts src/dock.ts src/status.service.ts src/profile.service.ts src/recovery.service.ts --outDir .tmp --module commonjs --target es2019 --skipLibCheck --experimentalDecorators --esModuleInterop && node test/prompt.test.js && node test/detect.test.js && node test/claudeHooks.test.js && node test/screen.test.js && node test/bind.test.js && node test/order.test.js && node test/reason.test.js && node test/tabenv.test.js && node test/viewer.test.js && node test/markdown.test.js && node test/ime.test.js && node test/gitDiff.test.js && node test/agents.test.js && node test/group.test.js && node test/gitCommit.test.js && node test/diag.test.js && node test/subagents.test.js && node test/nav.test.js && node test/api.test.js && node test/dock.test.js && node test/status.service.test.js && node test/profile.service.test.js && node test/recovery.service.test.js"
|
|
19
|
+
"test": "tsc src/prompt.ts src/detect.ts src/claudeHooks.ts src/screen.ts src/bind.ts src/order.ts src/reason.ts src/tabenv.ts src/viewer.ts src/markdown.ts src/ime.ts src/gitDiff.ts src/agents.ts src/group.ts src/project-root.ts src/gitCommit.ts src/diag.ts src/subagents.ts src/nav.ts src/sessionLedger.ts src/update.ts src/api.ts src/dock.ts src/status.service.ts src/profile.service.ts src/recovery.service.ts --outDir .tmp --module commonjs --target es2019 --skipLibCheck --experimentalDecorators --esModuleInterop && node test/version.test.js && node test/prompt.test.js && node test/detect.test.js && node test/claudeHooks.test.js && node test/screen.test.js && node test/bind.test.js && node test/order.test.js && node test/reason.test.js && node test/tabenv.test.js && node test/viewer.test.js && node test/markdown.test.js && node test/ime.test.js && node test/gitDiff.test.js && node test/agents.test.js && node test/group.test.js && node test/gitCommit.test.js && node test/diag.test.js && node test/subagents.test.js && node test/nav.test.js && node test/sessionLedger.test.js && node test/update.test.js && node test/api.test.js && node test/dock.test.js && node test/status.service.test.js && node test/profile.service.test.js && node test/recovery.service.test.js",
|
|
20
|
+
"prebuild": "node tools/sync-version.js",
|
|
21
|
+
"sync-version": "node tools/sync-version.js"
|
|
20
22
|
},
|
|
21
23
|
"files": [
|
|
22
24
|
"dist",
|
|
23
25
|
"hooks",
|
|
26
|
+
"updater",
|
|
24
27
|
"install.ps1"
|
|
25
28
|
],
|
|
26
29
|
"license": "MIT",
|