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.
Files changed (61) hide show
  1. package/CHANGELOG.md +167 -150
  2. package/LICENSE +21 -21
  3. package/README.ko.md +52 -42
  4. package/README.md +52 -42
  5. package/dist/accountBrowser.d.ts +4 -0
  6. package/dist/accountSession.d.ts +22 -0
  7. package/dist/accounts.d.ts +55 -0
  8. package/dist/agents.d.ts +29 -0
  9. package/dist/codexKeys.d.ts +3 -0
  10. package/dist/config.d.ts +12 -20
  11. package/dist/deck.service.d.ts +32 -10
  12. package/dist/i18n/sidebar/af.d.ts +95 -0
  13. package/dist/i18n/sidebar/bg.d.ts +95 -0
  14. package/dist/i18n/sidebar/cs.d.ts +95 -0
  15. package/dist/i18n/sidebar/da.d.ts +95 -0
  16. package/dist/i18n/sidebar/de.d.ts +95 -0
  17. package/dist/i18n/sidebar/es.d.ts +95 -0
  18. package/dist/i18n/sidebar/fr.d.ts +95 -0
  19. package/dist/i18n/sidebar/hr.d.ts +95 -0
  20. package/dist/i18n/sidebar/id.d.ts +95 -0
  21. package/dist/i18n/sidebar/it.d.ts +95 -0
  22. package/dist/i18n/sidebar/ja.d.ts +95 -0
  23. package/dist/i18n/sidebar/pl.d.ts +95 -0
  24. package/dist/i18n/sidebar/pt.d.ts +95 -0
  25. package/dist/i18n/sidebar/ru.d.ts +95 -0
  26. package/dist/i18n/sidebar/sr.d.ts +95 -0
  27. package/dist/i18n/sidebar/sv.d.ts +95 -0
  28. package/dist/i18n/sidebar/tr.d.ts +95 -0
  29. package/dist/i18n/sidebar/uk.d.ts +95 -0
  30. package/dist/i18n/sidebar/zh-CN.d.ts +95 -0
  31. package/dist/i18n/sidebar/zh-TW.d.ts +95 -0
  32. package/dist/i18n.d.ts +20 -4
  33. package/dist/index.d.ts +2 -1
  34. package/dist/index.js +109 -1
  35. package/dist/notify.service.d.ts +16 -0
  36. package/dist/performance.d.ts +13 -0
  37. package/dist/profile.service.d.ts +2 -0
  38. package/dist/runtimeMigration.d.ts +3 -0
  39. package/dist/sessionLedger.service.d.ts +6 -3
  40. package/dist/sessionMailbox.d.ts +31 -0
  41. package/dist/sessionSearch.d.ts +41 -0
  42. package/dist/sessionSearchPanel.d.ts +36 -0
  43. package/dist/sessionSlots.d.ts +9 -0
  44. package/dist/settings.component.d.ts +14 -1
  45. package/dist/sidebarI18n.d.ts +9 -0
  46. package/dist/sidebarLocales.d.ts +1 -0
  47. package/dist/storageEnvironment.d.ts +6 -0
  48. package/dist/storageI18n.d.ts +1 -0
  49. package/dist/storagePaths.d.ts +19 -0
  50. package/dist/storageSettings.d.ts +28 -0
  51. package/dist/viewer.d.ts +2 -0
  52. package/hooks/agentdeck-codex-notify.ps1 +69 -28
  53. package/hooks/agentdeck-hook-trace.ps1 +57 -0
  54. package/hooks/agentdeck-label.js +53 -53
  55. package/hooks/agentdeck-mailbox.mjs +143 -0
  56. package/hooks/agentdeck-notify.ps1 +87 -8
  57. package/hooks/agentdeck-runtime.cjs +20 -0
  58. package/hooks/agentdeck-status.ps1 +33 -33
  59. package/hooks/agentdeck-statusline.mjs +349 -351
  60. package/package.json +75 -70
  61. package/updater/agentdeck-update.ps1 +153 -153
package/package.json CHANGED
@@ -1,70 +1,75 @@
1
- {
2
- "name": "tabby-agentdeck",
3
- "version": "1.2.0",
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
- "keywords": [
6
- "tabby-plugin",
7
- "tabby",
8
- "ai",
9
- "agent",
10
- "claude-code",
11
- "codex",
12
- "terminal"
13
- ],
14
- "main": "dist/index.js",
15
- "typings": "dist/index.d.ts",
16
- "scripts": {
17
- "build": "webpack --mode production",
18
- "build:release": "node tools/sync-version.js && webpack --mode production --env release",
19
- "prepack": "npm run build:release && node tools/check-release.js",
20
- "watch": "webpack --progress --color --watch",
21
- "test": "npm run test:existing && npm run test:codex",
22
- "prebuild": "node tools/sync-version.js",
23
- "sync-version": "node tools/sync-version.js",
24
- "release": "node tools/release.js",
25
- "test:existing": "tsc src/codexHooks.ts src/i18n.ts 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/keybind.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 src/meta.ts src/statusLine.ts src/codexMeta.ts --outDir .tmp --module commonjs --target es2019 --skipLibCheck --experimentalDecorators --esModuleInterop && node test/version.test.js && node test/tags.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 && node test/meta.test.js && node test/i18n.test.js && node test/statusLine.test.js && node test/hotkeys.test.js && node test/keybind.test.js && node test/codexMeta.test.js && node test/codexHooks.test.js",
26
- "test:codex": "node test/regression.cjs && node test/resume.cjs && node test/cursor.cjs && node test/wheel.cjs",
27
- "typecheck": "tsc --noEmit"
28
- },
29
- "files": [
30
- "dist",
31
- "hooks",
32
- "updater",
33
- "install.ps1",
34
- "CHANGELOG.md",
35
- "README.ko.md"
36
- ],
37
- "license": "MIT",
38
- "devDependencies": {
39
- "@angular/common": "^15.2.6",
40
- "@angular/core": "^15.2.6",
41
- "@angular/forms": "^15.2.6",
42
- "@types/node": "^20.19.43",
43
- "@types/webpack-env": "^1.18.4",
44
- "@xterm/headless": "^5.5.0",
45
- "css-loader": "^6.10.0",
46
- "rxjs": "^7.5.7",
47
- "sass": "^1.71.1",
48
- "sass-loader": "^14.1.1",
49
- "style-loader": "^3.3.4",
50
- "tabby-core": "^1.0.231-nightly.0",
51
- "tabby-local": "^1.0.231-nightly.0",
52
- "tabby-settings": "^1.0.231-nightly.0",
53
- "tabby-terminal": "^1.0.231-nightly.0",
54
- "ts-loader": "^9.5.1",
55
- "typescript": "^4.9.5",
56
- "webpack": "^5.90.0",
57
- "webpack-cli": "^5.1.4"
58
- },
59
- "author": {
60
- "name": "junggon"
61
- },
62
- "repository": {
63
- "type": "git",
64
- "url": "git+https://github.com/jghwang2/tabby-agentdeck.git"
65
- },
66
- "homepage": "https://jghwang2.github.io/tabby-agentdeck/",
67
- "bugs": {
68
- "url": "https://github.com/jghwang2/tabby-agentdeck/issues"
69
- }
70
- }
1
+ {
2
+ "name": "tabby-agentdeck",
3
+ "version": "1.2.2",
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
+ "keywords": [
6
+ "tabby-plugin",
7
+ "tabby",
8
+ "ai",
9
+ "agent",
10
+ "claude-code",
11
+ "codex",
12
+ "terminal"
13
+ ],
14
+ "main": "dist/index.js",
15
+ "typings": "dist/index.d.ts",
16
+ "scripts": {
17
+ "build": "webpack --mode production",
18
+ "build:release": "node tools/sync-version.js && webpack --mode production --env release",
19
+ "prepack": "npm run build:release && node tools/check-release.js",
20
+ "watch": "webpack --progress --color --watch",
21
+ "test": "npm run test:existing && npm run test:codex && npm run test:accounts && node test/sidebarI18n.cjs && node test/accountSession.cjs && npm run test:sessions && npm run test:storage && npm run test:performance && npm run test:hook-trace",
22
+ "test:hook-trace": "node test/hookTrace.cjs",
23
+ "test:performance": "node test/performance.cjs",
24
+ "test:storage": "tsc src/storagePaths.ts src/accounts.ts src/storageSettings.ts src/runtimeMigration.ts --outDir .tmp --module commonjs --target es2019 --skipLibCheck --esModuleInterop && node test/storagePaths.cjs && node test/storageSettings.cjs && node test/runtimePaths.cjs",
25
+ "test:sessions": "tsc src/sessionSearch.ts src/sessionMailbox.ts src/sessionSlots.ts --outDir .tmp --module commonjs --target es2019 --skipLibCheck --esModuleInterop && node test/sessionSearch.cjs && node test/sessionCommunication.cjs && node test/mailboxBridge.cjs",
26
+ "test:accounts": "tsc src/accounts.ts src/accountBrowser.ts --outDir .tmp --module commonjs --target es2019 --skipLibCheck --esModuleInterop && node test/accounts.test.js && node test/accountSecrets.cjs && node test/accountFiles.cjs && node test/accountCliRoots.cjs",
27
+ "prebuild": "node tools/sync-version.js",
28
+ "sync-version": "node tools/sync-version.js",
29
+ "release": "node tools/release.js",
30
+ "test:existing": "tsc src/codexHooks.ts src/i18n.ts 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/keybind.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 src/meta.ts src/statusLine.ts src/codexMeta.ts --outDir .tmp --module commonjs --target es2019 --skipLibCheck --experimentalDecorators --esModuleInterop && node test/version.test.js && node test/tags.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 && node test/meta.test.js && node test/i18n.test.js && node test/statusLine.test.js && node test/hotkeys.test.js && node test/keybind.test.js && node test/codexMeta.test.js && node test/codexHooks.test.js",
31
+ "test:codex": "node test/regression.cjs && node test/resume.cjs && node test/cursor.cjs && node test/wheel.cjs && node test/codexKeys.cjs && node test/accountSwitch.cjs && node test/repairLifecycle.cjs",
32
+ "typecheck": "tsc --noEmit"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "hooks",
37
+ "updater",
38
+ "install.ps1",
39
+ "CHANGELOG.md",
40
+ "README.ko.md"
41
+ ],
42
+ "license": "MIT",
43
+ "devDependencies": {
44
+ "@angular/common": "^15.2.6",
45
+ "@angular/core": "^15.2.6",
46
+ "@angular/forms": "^15.2.6",
47
+ "@types/node": "^20.19.43",
48
+ "@types/webpack-env": "^1.18.4",
49
+ "@xterm/headless": "^5.5.0",
50
+ "css-loader": "^6.10.0",
51
+ "rxjs": "^7.5.7",
52
+ "sass": "^1.71.1",
53
+ "sass-loader": "^14.1.1",
54
+ "style-loader": "^3.3.4",
55
+ "tabby-core": "^1.0.231-nightly.0",
56
+ "tabby-local": "^1.0.231-nightly.0",
57
+ "tabby-settings": "^1.0.231-nightly.0",
58
+ "tabby-terminal": "^1.0.231-nightly.0",
59
+ "ts-loader": "^9.5.1",
60
+ "typescript": "^4.9.5",
61
+ "webpack": "^5.90.0",
62
+ "webpack-cli": "^5.1.4"
63
+ },
64
+ "author": {
65
+ "name": "junggon"
66
+ },
67
+ "repository": {
68
+ "type": "git",
69
+ "url": "git+https://github.com/jghwang2/tabby-agentdeck.git"
70
+ },
71
+ "homepage": "https://jghwang2.github.io/tabby-agentdeck/",
72
+ "bugs": {
73
+ "url": "https://github.com/jghwang2/tabby-agentdeck/issues"
74
+ }
75
+ }
@@ -1,153 +1,153 @@
1
- # AgentDeck 자동 업데이트 — 플러그인 폴더에 새 버전을 깐다.
2
- #
3
- # powershell -NoProfile -ExecutionPolicy Bypass -File agentdeck-update.ps1 `
4
- # -Version 1.1.1 -PluginsDir "<APPDATA>\tabby\plugins" -TabbyExe "<...>\Tabby.exe" -InPlace
5
- #
6
- # 모드가 둘이다.
7
- #
8
- # **-InPlace (지금 쓰는 길)** — Tabby 가 **떠 있는 채로** 깐다. 기다리지도, 다시 띄우지도 않는다.
9
- # 적용은 플러그인이 창(renderer)만 리로드해서 한다(`src/reload.service.ts`) — 탭과 세션이 산다.
10
- # 로드된 `.js` 는 Windows 가 잡고 있지 않으므로(require 는 읽고 바로 닫는다) 폴더를 갈아끼워도 된다.
11
- # 이 모드에서는 부른 쪽이 **종료 코드를 기다린다** — 끝나야 리로드할 수 있기 때문이다.
12
- #
13
- # **기본(옛 길)** — Tabby 종료를 기다린다 → 설치한다 → 다시 띄운다. 옛 버전이 띄운 스크립트나
14
- # 손으로 돌릴 때를 위해 남겨 둔다.
15
- #
16
- # 어느 쪽이든 이 스크립트는 **패키지 밖(임시 폴더)에서** 실행돼야 한다. 자기가 사는 폴더를
17
- # npm 이 통째로 갈아엎기 때문이다 (`update.service.ts` 가 임시 폴더로 복사해 띄운다).
18
- param(
19
- [Parameter(Mandatory = $true)][string]$Version,
20
- [Parameter(Mandatory = $true)][string]$PluginsDir,
21
- [Parameter(Mandatory = $true)][string]$TabbyExe,
22
- # 우리를 띄운 그 Tabby 의 **메인 프로세스 PID**. 이것만 기다린다.
23
- # 0 이면 옛 방식(이름으로 전부)으로 물러난다 — 옛 버전이 이 인자를 안 보낸다.
24
- [int]$TabbyPid = 0,
25
- [string]$LogFile = '',
26
- # Tabby 가 꺼지기를 기다리는 최대 시간(초). 넘으면 설치하지 않고 그냥 끝낸다 —
27
- # 떠 있는 상태로 설치하면 반쯤 지워진 플러그인이 남는다
28
- [int]$WaitSeconds = 60,
29
- # Tabby 를 껐다 켜지 않는다 — 기다리지도 않고 다시 띄우지도 않는다
30
- [switch]$InPlace,
31
- # npm 에 건넬 대상. 기본은 `tabby-agentdeck@<Version>`(레지스트리)이고,
32
- # **회귀는 지금 소스로 만든 tgz 경로를 준다** — 이미 배포된 버전에는 검증할 코드가 없다
33
- [string]$Spec = ''
34
- )
35
-
36
- $ErrorActionPreference = 'Stop'
37
- $pkg = 'tabby-agentdeck'
38
-
39
- if (-not $LogFile) {
40
- $LogFile = Join-Path $env:LOCALAPPDATA 'tabby-agentdeck\update.log'
41
- }
42
- $logDir = Split-Path -Parent $LogFile
43
- if ($logDir -and -not (Test-Path $logDir)) {
44
- New-Item -ItemType Directory -Force -Path $logDir | Out-Null
45
- }
46
-
47
- function Write-Log ([string]$msg) {
48
- $line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-ddTHH:mm:ss'), $msg
49
- Add-Content -Path $LogFile -Value $line -Encoding UTF8
50
- }
51
-
52
- # 로그는 지난 실행을 덮지 않고 쌓되, 커지면 한 번 잘라낸다 (실패를 나중에 읽을 수 있어야 한다)
53
- try {
54
- if ((Test-Path $LogFile) -and ((Get-Item $LogFile).Length -gt 512KB)) {
55
- Move-Item -Force $LogFile "$LogFile.1"
56
- }
57
- } catch { }
58
-
59
- Write-Log "=== update 시작 version=$Version pluginsDir=$PluginsDir"
60
-
61
- # ── 1) Tabby 가 꺼지기를 기다린다 ────────────────────────────────────────────
62
- #
63
- # **우리를 띄운 그 인스턴스만 기다린다** (`-TabbyPid` = Electron 메인 프로세스).
64
- # 이름(`Tabby.exe`)으로 전부 세면 **다른 Tabby 창 하나 때문에 업데이트가 영영 안 된다** —
65
- # 그쪽은 다른 user-data-dir 을 쓰는 별개 인스턴스라 우리 플러그인 파일과 아무 상관이 없다
66
- # (2026-09-11: 격리 인스턴스로 검증하려 했더니 실사용 Tabby 가 떠 있어 60초 타임아웃).
67
- #
68
- # 파일 잠금을 쥔 것은 플러그인을 require 한 **렌더러**지만, 메인이 죽으면 자식(renderer·gpu)도
69
- # 함께 내려간다. 그래서 메인 PID 가 사라지고 그 자식들이 정리될 때까지를 한 조건으로 본다.
70
- function Test-TabbyAlive ([int]$mainPid) {
71
- if ($mainPid -le 0) {
72
- # 옛 방식 — 이름으로 전부
73
- return @(Get-Process -Name 'Tabby' -ErrorAction SilentlyContinue).Count
74
- }
75
- $n = 0
76
- if (Get-Process -Id $mainPid -ErrorAction SilentlyContinue) { $n++ }
77
- # 메인이 사라져도 자식이 잠깐 남을 수 있다 — 그 자식들까지 없어져야 잠금이 풀린다
78
- $n += @(Get-CimInstance Win32_Process -Filter "Name='Tabby.exe' AND ParentProcessId=$mainPid" -ErrorAction SilentlyContinue).Count
79
- return $n
80
- }
81
-
82
- if ($InPlace) {
83
- Write-Log 'InPlace — Tabby 종료를 기다리지 않는다 (적용은 플러그인이 창 리로드로 한다)'
84
- } else {
85
- Write-Log "대기 대상: $(if ($TabbyPid -gt 0) { "pid=$TabbyPid (+자식)" } else { '이름으로 전부 (옛 방식)' })"
86
- $waited = 0
87
- while ($true) {
88
- $alive = Test-TabbyAlive $TabbyPid
89
- if ($alive -eq 0) { break }
90
- if ($waited -ge $WaitSeconds) {
91
- Write-Log "FAIL Tabby 가 ${WaitSeconds}초 안에 꺼지지 않았다 (남은 프로세스 ${alive}개) — 설치하지 않는다"
92
- exit 2
93
- }
94
- Start-Sleep -Milliseconds 500
95
- $waited += 0.5
96
- }
97
- Write-Log "Tabby 종료 확인 (${waited}초 대기)"
98
- # 핸들이 완전히 풀릴 여유를 조금 더 준다
99
- Start-Sleep -Milliseconds 800
100
- }
101
-
102
- # ── 2) 설치 ─────────────────────────────────────────────────────────────────
103
- # Tabby 의 플러그인 설치와 같은 방식이다 — `plugins/package.json` 에 의존성으로 적히고
104
- # `plugins/node_modules` 아래로 풀린다. `--prefix` 로 그 폴더를 가리킨다.
105
- $installed = $false
106
- $npm = (Get-Command npm.cmd -ErrorAction SilentlyContinue)
107
- if (-not $npm) { $npm = (Get-Command npm -ErrorAction SilentlyContinue) }
108
- if (-not $npm) {
109
- Write-Log 'FAIL npm 을 찾지 못했다 (PATH 에 node/npm 이 없다)'
110
- # npm 이 없어도 Tabby 는 다시 띄워 준다 — 사람의 창을 빼앗은 채 끝내면 안 된다
111
- Write-Log '설치를 건너뛴다'
112
- } else {
113
- Write-Log "npm=$($npm.Source)"
114
- # `$args` 는 PowerShell 자동 변수다 — 이름을 겹치면 조용히 이상하게 돈다
115
- $target = if ($Spec) { $Spec } else { "$pkg@$Version" }
116
- Write-Log "설치 대상: $target"
117
- $npmArgs = @('install', '--prefix', $PluginsDir, '--no-audit', '--no-fund', $target)
118
- try {
119
- $out = & $npm.Source @npmArgs 2>&1
120
- $code = $LASTEXITCODE
121
- foreach ($line in $out) { Write-Log "npm| $line" }
122
- if ($code -eq 0) {
123
- Write-Log "OK 설치 완료 $pkg@$Version"
124
- $installed = $true
125
- } else {
126
- Write-Log "FAIL npm exit=$code — 옛 버전이 그대로 남아 있을 수 있다"
127
- }
128
- } catch {
129
- Write-Log "FAIL npm 실행 예외: $($_.Exception.Message)"
130
- }
131
- }
132
-
133
- # ── 3) 다시 띄운다 ──────────────────────────────────────────────────────────
134
- # InPlace 면 애초에 닫지 않았으므로 띄울 것이 없다 — 부른 쪽이 창만 새로 고친다.
135
- # 옛 길에서는 **설치가 실패했어도 반드시 띄운다**. 업데이트를 고르느라 터미널을 잃는 일은 없어야 한다.
136
- if ($InPlace) {
137
- Write-Log '=== update 끝 (InPlace — 적용은 플러그인이 창 리로드로 한다)'
138
- # 부른 쪽이 이 코드를 본다. 다만 최종 판정은 디스크의 버전이다 (`installedVersion`)
139
- exit $(if ($installed) { 0 } else { 1 })
140
- }
141
-
142
- try {
143
- if (Test-Path $TabbyExe) {
144
- Start-Process -FilePath $TabbyExe
145
- Write-Log "Tabby 재시작: $TabbyExe"
146
- } else {
147
- Write-Log "FAIL Tabby 실행파일을 찾지 못했다: $TabbyExe"
148
- }
149
- } catch {
150
- Write-Log "FAIL Tabby 재시작 예외: $($_.Exception.Message)"
151
- }
152
-
153
- Write-Log '=== update 끝'
1
+ # AgentDeck 자동 업데이트 — 플러그인 폴더에 새 버전을 깐다.
2
+ #
3
+ # powershell -NoProfile -ExecutionPolicy Bypass -File agentdeck-update.ps1 `
4
+ # -Version 1.1.1 -PluginsDir "<APPDATA>\tabby\plugins" -TabbyExe "<...>\Tabby.exe" -InPlace
5
+ #
6
+ # 모드가 둘이다.
7
+ #
8
+ # **-InPlace (지금 쓰는 길)** — Tabby 가 **떠 있는 채로** 깐다. 기다리지도, 다시 띄우지도 않는다.
9
+ # 적용은 플러그인이 창(renderer)만 리로드해서 한다(`src/reload.service.ts`) — 탭과 세션이 산다.
10
+ # 로드된 `.js` 는 Windows 가 잡고 있지 않으므로(require 는 읽고 바로 닫는다) 폴더를 갈아끼워도 된다.
11
+ # 이 모드에서는 부른 쪽이 **종료 코드를 기다린다** — 끝나야 리로드할 수 있기 때문이다.
12
+ #
13
+ # **기본(옛 길)** — Tabby 종료를 기다린다 → 설치한다 → 다시 띄운다. 옛 버전이 띄운 스크립트나
14
+ # 손으로 돌릴 때를 위해 남겨 둔다.
15
+ #
16
+ # 어느 쪽이든 이 스크립트는 **패키지 밖(임시 폴더)에서** 실행돼야 한다. 자기가 사는 폴더를
17
+ # npm 이 통째로 갈아엎기 때문이다 (`update.service.ts` 가 임시 폴더로 복사해 띄운다).
18
+ param(
19
+ [Parameter(Mandatory = $true)][string]$Version,
20
+ [Parameter(Mandatory = $true)][string]$PluginsDir,
21
+ [Parameter(Mandatory = $true)][string]$TabbyExe,
22
+ # 우리를 띄운 그 Tabby 의 **메인 프로세스 PID**. 이것만 기다린다.
23
+ # 0 이면 옛 방식(이름으로 전부)으로 물러난다 — 옛 버전이 이 인자를 안 보낸다.
24
+ [int]$TabbyPid = 0,
25
+ [string]$LogFile = '',
26
+ # Tabby 가 꺼지기를 기다리는 최대 시간(초). 넘으면 설치하지 않고 그냥 끝낸다 —
27
+ # 떠 있는 상태로 설치하면 반쯤 지워진 플러그인이 남는다
28
+ [int]$WaitSeconds = 60,
29
+ # Tabby 를 껐다 켜지 않는다 — 기다리지도 않고 다시 띄우지도 않는다
30
+ [switch]$InPlace,
31
+ # npm 에 건넬 대상. 기본은 `tabby-agentdeck@<Version>`(레지스트리)이고,
32
+ # **회귀는 지금 소스로 만든 tgz 경로를 준다** — 이미 배포된 버전에는 검증할 코드가 없다
33
+ [string]$Spec = ''
34
+ )
35
+
36
+ $ErrorActionPreference = 'Stop'
37
+ $pkg = 'tabby-agentdeck'
38
+
39
+ if (-not $LogFile) {
40
+ $LogFile = Join-Path $env:LOCALAPPDATA 'tabby-agentdeck\update.log'
41
+ }
42
+ $logDir = Split-Path -Parent $LogFile
43
+ if ($logDir -and -not (Test-Path $logDir)) {
44
+ New-Item -ItemType Directory -Force -Path $logDir | Out-Null
45
+ }
46
+
47
+ function Write-Log ([string]$msg) {
48
+ $line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-ddTHH:mm:ss'), $msg
49
+ Add-Content -Path $LogFile -Value $line -Encoding UTF8
50
+ }
51
+
52
+ # 로그는 지난 실행을 덮지 않고 쌓되, 커지면 한 번 잘라낸다 (실패를 나중에 읽을 수 있어야 한다)
53
+ try {
54
+ if ((Test-Path $LogFile) -and ((Get-Item $LogFile).Length -gt 512KB)) {
55
+ Move-Item -Force $LogFile "$LogFile.1"
56
+ }
57
+ } catch { }
58
+
59
+ Write-Log "=== update 시작 version=$Version pluginsDir=$PluginsDir"
60
+
61
+ # ── 1) Tabby 가 꺼지기를 기다린다 ────────────────────────────────────────────
62
+ #
63
+ # **우리를 띄운 그 인스턴스만 기다린다** (`-TabbyPid` = Electron 메인 프로세스).
64
+ # 이름(`Tabby.exe`)으로 전부 세면 **다른 Tabby 창 하나 때문에 업데이트가 영영 안 된다** —
65
+ # 그쪽은 다른 user-data-dir 을 쓰는 별개 인스턴스라 우리 플러그인 파일과 아무 상관이 없다
66
+ # (2026-09-11: 격리 인스턴스로 검증하려 했더니 실사용 Tabby 가 떠 있어 60초 타임아웃).
67
+ #
68
+ # 파일 잠금을 쥔 것은 플러그인을 require 한 **렌더러**지만, 메인이 죽으면 자식(renderer·gpu)도
69
+ # 함께 내려간다. 그래서 메인 PID 가 사라지고 그 자식들이 정리될 때까지를 한 조건으로 본다.
70
+ function Test-TabbyAlive ([int]$mainPid) {
71
+ if ($mainPid -le 0) {
72
+ # 옛 방식 — 이름으로 전부
73
+ return @(Get-Process -Name 'Tabby' -ErrorAction SilentlyContinue).Count
74
+ }
75
+ $n = 0
76
+ if (Get-Process -Id $mainPid -ErrorAction SilentlyContinue) { $n++ }
77
+ # 메인이 사라져도 자식이 잠깐 남을 수 있다 — 그 자식들까지 없어져야 잠금이 풀린다
78
+ $n += @(Get-CimInstance Win32_Process -Filter "Name='Tabby.exe' AND ParentProcessId=$mainPid" -ErrorAction SilentlyContinue).Count
79
+ return $n
80
+ }
81
+
82
+ if ($InPlace) {
83
+ Write-Log 'InPlace — Tabby 종료를 기다리지 않는다 (적용은 플러그인이 창 리로드로 한다)'
84
+ } else {
85
+ Write-Log "대기 대상: $(if ($TabbyPid -gt 0) { "pid=$TabbyPid (+자식)" } else { '이름으로 전부 (옛 방식)' })"
86
+ $waited = 0
87
+ while ($true) {
88
+ $alive = Test-TabbyAlive $TabbyPid
89
+ if ($alive -eq 0) { break }
90
+ if ($waited -ge $WaitSeconds) {
91
+ Write-Log "FAIL Tabby 가 ${WaitSeconds}초 안에 꺼지지 않았다 (남은 프로세스 ${alive}개) — 설치하지 않는다"
92
+ exit 2
93
+ }
94
+ Start-Sleep -Milliseconds 500
95
+ $waited += 0.5
96
+ }
97
+ Write-Log "Tabby 종료 확인 (${waited}초 대기)"
98
+ # 핸들이 완전히 풀릴 여유를 조금 더 준다
99
+ Start-Sleep -Milliseconds 800
100
+ }
101
+
102
+ # ── 2) 설치 ─────────────────────────────────────────────────────────────────
103
+ # Tabby 의 플러그인 설치와 같은 방식이다 — `plugins/package.json` 에 의존성으로 적히고
104
+ # `plugins/node_modules` 아래로 풀린다. `--prefix` 로 그 폴더를 가리킨다.
105
+ $installed = $false
106
+ $npm = (Get-Command npm.cmd -ErrorAction SilentlyContinue)
107
+ if (-not $npm) { $npm = (Get-Command npm -ErrorAction SilentlyContinue) }
108
+ if (-not $npm) {
109
+ Write-Log 'FAIL npm 을 찾지 못했다 (PATH 에 node/npm 이 없다)'
110
+ # npm 이 없어도 Tabby 는 다시 띄워 준다 — 사람의 창을 빼앗은 채 끝내면 안 된다
111
+ Write-Log '설치를 건너뛴다'
112
+ } else {
113
+ Write-Log "npm=$($npm.Source)"
114
+ # `$args` 는 PowerShell 자동 변수다 — 이름을 겹치면 조용히 이상하게 돈다
115
+ $target = if ($Spec) { $Spec } else { "$pkg@$Version" }
116
+ Write-Log "설치 대상: $target"
117
+ $npmArgs = @('install', '--prefix', $PluginsDir, '--no-audit', '--no-fund', $target)
118
+ try {
119
+ $out = & $npm.Source @npmArgs 2>&1
120
+ $code = $LASTEXITCODE
121
+ foreach ($line in $out) { Write-Log "npm| $line" }
122
+ if ($code -eq 0) {
123
+ Write-Log "OK 설치 완료 $pkg@$Version"
124
+ $installed = $true
125
+ } else {
126
+ Write-Log "FAIL npm exit=$code — 옛 버전이 그대로 남아 있을 수 있다"
127
+ }
128
+ } catch {
129
+ Write-Log "FAIL npm 실행 예외: $($_.Exception.Message)"
130
+ }
131
+ }
132
+
133
+ # ── 3) 다시 띄운다 ──────────────────────────────────────────────────────────
134
+ # InPlace 면 애초에 닫지 않았으므로 띄울 것이 없다 — 부른 쪽이 창만 새로 고친다.
135
+ # 옛 길에서는 **설치가 실패했어도 반드시 띄운다**. 업데이트를 고르느라 터미널을 잃는 일은 없어야 한다.
136
+ if ($InPlace) {
137
+ Write-Log '=== update 끝 (InPlace — 적용은 플러그인이 창 리로드로 한다)'
138
+ # 부른 쪽이 이 코드를 본다. 다만 최종 판정은 디스크의 버전이다 (`installedVersion`)
139
+ exit $(if ($installed) { 0 } else { 1 })
140
+ }
141
+
142
+ try {
143
+ if (Test-Path $TabbyExe) {
144
+ Start-Process -FilePath $TabbyExe
145
+ Write-Log "Tabby 재시작: $TabbyExe"
146
+ } else {
147
+ Write-Log "FAIL Tabby 실행파일을 찾지 못했다: $TabbyExe"
148
+ }
149
+ } catch {
150
+ Write-Log "FAIL Tabby 재시작 예외: $($_.Exception.Message)"
151
+ }
152
+
153
+ Write-Log '=== update 끝'