@songsid/agend 2.3.2-beta.5 → 2.3.2-beta.7

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 (79) hide show
  1. package/dist/backend/kiro-identity.d.ts +11 -0
  2. package/dist/backend/kiro-identity.js +16 -0
  3. package/dist/backend/kiro-identity.js.map +1 -1
  4. package/dist/backend/kiro-retired-model.d.ts +62 -0
  5. package/dist/backend/kiro-retired-model.js +160 -0
  6. package/dist/backend/kiro-retired-model.js.map +1 -0
  7. package/dist/backend/kiro.d.ts +17 -0
  8. package/dist/backend/kiro.js +77 -1
  9. package/dist/backend/kiro.js.map +1 -1
  10. package/dist/channel/adapters/discord.d.ts +11 -0
  11. package/dist/channel/adapters/discord.js +64 -10
  12. package/dist/channel/adapters/discord.js.map +1 -1
  13. package/dist/config-validator.js +25 -3
  14. package/dist/config-validator.js.map +1 -1
  15. package/dist/daemon.d.ts +2 -0
  16. package/dist/daemon.js +5 -0
  17. package/dist/daemon.js.map +1 -1
  18. package/dist/fleet-manager.d.ts +21 -5
  19. package/dist/fleet-manager.js +154 -133
  20. package/dist/fleet-manager.js.map +1 -1
  21. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +14 -1
  22. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +14 -1
  23. package/dist/instance-lifecycle.d.ts +2 -0
  24. package/dist/instance-lifecycle.js +23 -8
  25. package/dist/instance-lifecycle.js.map +1 -1
  26. package/dist/locale.js +16 -0
  27. package/dist/locale.js.map +1 -1
  28. package/dist/notice-delivery.d.ts +7 -6
  29. package/dist/notice-delivery.js +16 -18
  30. package/dist/notice-delivery.js.map +1 -1
  31. package/dist/office-share-page.d.ts +13 -0
  32. package/dist/office-share-page.js +82 -0
  33. package/dist/office-share-page.js.map +1 -0
  34. package/dist/office-share-service.d.ts +89 -0
  35. package/dist/office-share-service.js +172 -0
  36. package/dist/office-share-service.js.map +1 -0
  37. package/dist/office-share.d.ts +228 -0
  38. package/dist/office-share.js +489 -0
  39. package/dist/office-share.js.map +1 -0
  40. package/dist/public-web-gateway.js +6 -0
  41. package/dist/public-web-gateway.js.map +1 -1
  42. package/dist/quickstart.js +0 -2
  43. package/dist/quickstart.js.map +1 -1
  44. package/dist/settings-api.js +1 -1
  45. package/dist/settings-api.js.map +1 -1
  46. package/dist/setup-form.d.ts +1 -1
  47. package/dist/setup-form.js +48 -5
  48. package/dist/setup-form.js.map +1 -1
  49. package/dist/setup-host.js +4 -3
  50. package/dist/setup-host.js.map +1 -1
  51. package/dist/setup-wizard.js +0 -2
  52. package/dist/setup-wizard.js.map +1 -1
  53. package/dist/types.d.ts +12 -3
  54. package/dist/ui/fleet-office-pack.js +7 -2
  55. package/dist/ui/fleet-office-skin.js +5 -4
  56. package/dist/ui/fleet-office-zones.js +17 -6
  57. package/dist/ui/fleet-office.js +20 -10
  58. package/dist/ui/office-share-dialog.js +145 -0
  59. package/dist/ui/office-share-page.js +116 -0
  60. package/dist/ui/panel-settings.js +12 -8
  61. package/dist/ui/settings-registry.js +2 -2
  62. package/dist/ui/settings-strings.js +6 -6
  63. package/dist/ui/shared/app.css +28 -0
  64. package/dist/web-api.d.ts +14 -0
  65. package/dist/web-api.js +91 -0
  66. package/dist/web-api.js.map +1 -1
  67. package/dist/web-office-skins.d.ts +2 -1
  68. package/dist/web-office-skins.js +2 -1
  69. package/dist/web-office-skins.js.map +1 -1
  70. package/dist/web-settings-patch.js +1 -1
  71. package/dist/web-settings-patch.js.map +1 -1
  72. package/dist/working-recovery.d.ts +2 -2
  73. package/dist/working-recovery.js +43 -8
  74. package/dist/working-recovery.js.map +1 -1
  75. package/dist/working-state-journal.d.ts +2 -0
  76. package/dist/working-state-journal.js +16 -1
  77. package/dist/working-state-journal.js.map +1 -1
  78. package/package.json +1 -1
  79. package/templates/windows-shortcuts.ps1 +151 -147
@@ -3,190 +3,194 @@
3
3
  # (tests/windows-shortcuts-cli-1853.test.ts 檢查):改的時候兩邊一起改。
4
4
 
5
5
  $WSL_COMMAND_FN = @'
6
+ function Invoke-BoundedWsl {
7
+ param([string[]]$Arguments, [int]$TimeoutSeconds = 30)
8
+ $sep = [array]::IndexOf($Arguments, '--')
9
+ if ($sep -ge 0 -and $Arguments.Length -gt ($sep + 1) -and $Arguments[$sep + 1] -eq 'bash') {
10
+ $Arguments = @($Arguments[0..$sep]) + @('timeout', '--signal=TERM', '--kill-after=2s', [string]([Math]::Max(1, $TimeoutSeconds - 5))) + @($Arguments[($sep + 1)..($Arguments.Length - 1)])
11
+ }
12
+ $p = New-Object Diagnostics.Process
13
+ $p.StartInfo.FileName = 'wsl.exe'
14
+ $p.StartInfo.Arguments = (@($Arguments | ForEach-Object { if ($_ -match '[\s"]') { '"' + ($_ -replace '"', '\"') + '"' } else { $_ } }) -join ' ')
15
+ $p.StartInfo.UseShellExecute = $false; $p.StartInfo.CreateNoWindow = $true
16
+ $p.StartInfo.StandardOutputEncoding = [Text.Encoding]::UTF8; $p.StartInfo.StandardErrorEncoding = [Text.Encoding]::UTF8
17
+ $p.StartInfo.RedirectStandardOutput = $true; $p.StartInfo.RedirectStandardError = $true; $p.StartInfo.RedirectStandardInput = $true
18
+ try {
19
+ $null = $p.Start(); $p.StandardInput.Close()
20
+ $out = $p.StandardOutput.ReadToEndAsync(); $err = $p.StandardError.ReadToEndAsync()
21
+ if (-not $p.WaitForExit($TimeoutSeconds * 1000)) {
22
+ $p.Kill(); $null = $p.WaitForExit(10000)
23
+ return @{Code=124;Text='WSL timed out; check it before retrying.'}
24
+ }
25
+ if (-not $out.Wait(1000) -or -not $err.Wait(1000)) { return @{Code=1;Text='WSL output did not close.'} }
26
+ $text = $out.Result + $err.Result
27
+ if ($text.Length -gt 1MB) { return @{Code=1;Text='WSL output exceeded its limit.'} }
28
+ return @{Code=$p.ExitCode;Text=$text}
29
+ } catch { return @{Code=-1;Text=[string]$_} }
30
+ finally { $p.Dispose() }
31
+ }
6
32
  function Get-WslAgendCommand([string]$command) {
7
- return ('export PATH=$HOME/.local/bin:$PATH; source $HOME/.nvm/nvm.sh 2>/dev/null; ' + $command)
33
+ return ('export PATH=$HOME/.local/bin:$PATH; source $HOME/.nvm/nvm.sh 2>/dev/null; ' + $command)
8
34
  }
9
- # 放進 PowerShell 文字裡的值(Linux、使用者名稱)一律用這個轉成字面字串:名稱裡的單引號(例如 o'neil)不會讓產生的
10
- # 腳本或貼上的指令壞掉(#2260 審查)。印給人看的指令,名稱沒有特殊字元時維持原樣、不加引號。
11
35
  function ConvertTo-PsLiteral([string]$value) { return ("'" + ($value -replace "'", "''") + "'") }
12
36
  function ConvertTo-PsArgument([string]$value) { if ($value -match '^[\w.-]+$') { return $value } else { return (ConvertTo-PsLiteral $value) } }
13
37
  function Get-AgendStartHint([string]$distro, [string]$user) {
14
- return ("wsl -d {0} -u {1} -- bash -lc '{2}'" -f (ConvertTo-PsArgument $distro), (ConvertTo-PsArgument $user), (Get-WslAgendCommand 'agend start'))
38
+ return ("wsl -d {0} -u {1} -- bash -lc '{2}'" -f (ConvertTo-PsArgument $distro), (ConvertTo-PsArgument $user), (Get-WslAgendCommand 'agend start'))
15
39
  }
16
40
  '@
17
41
  . ([scriptblock]::Create($WSL_COMMAND_FN))
18
-
19
42
  $DASHBOARD_OPENER_FN = @'
20
43
  function Get-DashboardSignIn([string]$text) {
21
- $url = [regex]::Match($text, 'Sign-in page:\s*(https?://\S+)')
22
- $code = [regex]::Match($text, 'Code:\s*([A-Z2-7]{4}-?[A-Z2-7]{4})\b')
23
- if (-not $url.Success -or -not $code.Success) { return $null }
24
- return @{ Url = $url.Groups[1].Value; Code = $code.Groups[1].Value }
44
+ $url = [regex]::Match($text, 'Sign-in page:\s*(https?://\S+)')
45
+ $code = [regex]::Match($text, 'Code:\s*([A-Z2-7]{4}-?[A-Z2-7]{4})\b')
46
+ if (-not $url.Success -or -not $code.Success) { return $null }
47
+ return @{ Url = $url.Groups[1].Value; Code = $code.Groups[1].Value }
25
48
  }
26
49
  function Invoke-OpenDashboard([string]$distro, [string]$user = 'root', [switch]$Wait, [string]$BackendSignins = '') {
27
- $command = 'agend web --code'
28
- if ($BackendSignins) {
29
- if ($BackendSignins -cnotmatch '^(kiro-cli|claude-code|codex|antigravity)(,(kiro-cli|claude-code|codex|antigravity)){0,3}\z') { throw 'Invalid backend selection' }
30
- $command += ' --backend-signins ' + $BackendSignins
31
- }
32
- $text = ''; $exit = 1
33
- $prevEAP = $ErrorActionPreference
34
- try {
35
- $ErrorActionPreference = 'Continue'
36
- $text = ((& wsl.exe -d $distro -u $user -- bash -lc (Get-WslAgendCommand $command) 2>&1) | ForEach-Object { "$_" } | Out-String)
37
- $exit = $LASTEXITCODE
38
- } catch {
39
- $text = "$_"; $exit = 1
40
- } finally {
41
- $ErrorActionPreference = $prevEAP
42
- }
43
- $signIn = if ($exit -eq 0) { Get-DashboardSignIn $text } else { $null }
44
- if (-not $signIn) {
45
- Write-Host " [!!] 現在打不開 AgEnD 儀表板:AgEnD 可能還沒啟動。" -ForegroundColor Yellow
46
- Write-Host (" 稍等一下再試一次;或在 PowerShell 執行:" + (Get-AgendStartHint $distro $user))
47
- if ($Wait) { Read-Host "按 Enter 關閉" }
48
- return $false
49
- }
50
- $copied = $false; try { Set-Clipboard -Value $signIn.Code; $copied = $true } catch { }
51
- $opened = $false; try { Start-Process $signIn.Url; $opened = $true } catch { }
52
- $spaced = ($signIn.Code.ToCharArray() | ForEach-Object { [string]$_ }) -join ' '
53
- Write-Host ""
54
- Write-Host (" ┌──────────────────────────────────────┐") -ForegroundColor Green
55
- Write-Host (" │ 儀表板登入碼: " + $spaced + " │") -ForegroundColor Green
56
- Write-Host (" └──────────────────────────────────────┘") -ForegroundColor Green
57
- if ($opened) { Write-Host " [OK] 登入頁已在瀏覽器打開。" -ForegroundColor Green } else { Write-Host (" 請自己在瀏覽器打開:" + $signIn.Url) }
58
- if ($copied) { Write-Host " [OK] 登入碼已複製:在登入頁的輸入框按 Ctrl+V。這組登入碼只能用一次,幾分鐘內有效。" -ForegroundColor Green }
59
- else { Write-Host " 在登入頁輸入上面的登入碼。這組登入碼只能用一次,幾分鐘內有效。" }
60
- if ($Wait) { Read-Host "按 Enter 關閉" }
61
- return $true
50
+ $command = 'agend web --code'
51
+ if ($BackendSignins) {
52
+ if ($BackendSignins -cnotmatch '^(kiro-cli|claude-code|codex|antigravity)(,(kiro-cli|claude-code|codex|antigravity)){0,3}\z') { throw 'Invalid backend selection' }
53
+ $command += ' --backend-signins ' + $BackendSignins
54
+ }
55
+ $text = ''; $exit = 1
56
+ $prevEAP = $ErrorActionPreference
57
+ try {
58
+ $ErrorActionPreference = 'Continue'
59
+ $r = Invoke-BoundedWsl -Arguments @('-d', $distro, '-u', $user, '--', 'bash', '-lc', (Get-WslAgendCommand $command))
60
+ $text = $r.Text; $exit = $r.Code
61
+ } catch {
62
+ $text = "$_"; $exit = 1
63
+ } finally {
64
+ $ErrorActionPreference = $prevEAP
65
+ }
66
+ $signIn = if ($exit -eq 0) { Get-DashboardSignIn $text } else { $null }
67
+ if (-not $signIn) {
68
+ Write-Host " [!!] 現在打不開 AgEnD 儀表板:AgEnD 可能還沒啟動。" -ForegroundColor Yellow
69
+ Write-Host (" 稍等一下再試一次;或在 PowerShell 執行:" + (Get-AgendStartHint $distro $user))
70
+ if ($Wait) { Read-Host "按 Enter 關閉" }
71
+ return $false
72
+ }
73
+ $copied = $false; try { Set-Clipboard -Value $signIn.Code; $copied = $true } catch { }
74
+ $opened = $false; try { Start-Process $signIn.Url; $opened = $true } catch { }
75
+ $spaced = ($signIn.Code.ToCharArray() | ForEach-Object { [string]$_ }) -join ' '
76
+ Write-Host ""
77
+ Write-Host (" ┌──────────────────────────────────────┐") -ForegroundColor Green
78
+ Write-Host (" │ 儀表板登入碼: " + $spaced + " │") -ForegroundColor Green
79
+ Write-Host (" └──────────────────────────────────────┘") -ForegroundColor Green
80
+ if ($opened) { Write-Host " [OK] 登入頁已在瀏覽器打開。" -ForegroundColor Green }
81
+ Write-Host (" 登入頁網址:" + $signIn.Url)
82
+ if ($copied) { Write-Host " [OK] 登入碼已複製:在登入頁的輸入框按 Ctrl+V。這組登入碼只能用一次,幾分鐘內有效。" -ForegroundColor Green }
83
+ else { Write-Host " 在登入頁輸入上面的登入碼。這組登入碼只能用一次,幾分鐘內有效。" }
84
+ if ($Wait) { Read-Host "按 Enter 關閉" }
85
+ return $true
62
86
  }
63
87
  '@
64
88
  . ([scriptblock]::Create($DASHBOARD_OPENER_FN))
65
-
66
- # 「AgEnD 重新啟動」捷徑(#1853,使用者 2026-10-20 核准):先問,預設不重啟;確定才在安裝時記下的 Linux 和使用者裡執行
67
- # agend restart,接著顯示 agend status,等你按 Enter。WSL 或 AgEnD 不在時直接說明;絕不執行 wsl --shutdown。
68
- # 這段函式的文字同時是捷徑執行的腳本(和儀表板捷徑一樣,安裝程式與捷徑共用同一份)。
69
89
  $RESTART_FN = @'
70
90
  function Invoke-AgendInWsl([string]$distro, [string]$user, [string]$command) {
71
- # 在 WSL 裡以登入 shell 執行一個 agend 指令,回傳 @{ Code; Text }。Linux 的輸出是 UTF-8;stderr 不讓 5.1 中止(#2242)。
72
- $prevEAP = $ErrorActionPreference; $prevEnc = [Console]::OutputEncoding
73
- try {
74
- $ErrorActionPreference = 'Continue'
75
- try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch { }
76
- $text = ((& wsl.exe -d $distro -u $user -- bash -lc (Get-WslAgendCommand $command) 2>&1) | ForEach-Object { "$_" } | Out-String)
77
- return @{ Code = $LASTEXITCODE; Text = $text }
78
- } catch {
79
- return @{ Code = -1; Text = "$_" }
80
- } finally {
81
- $ErrorActionPreference = $prevEAP
82
- try { [Console]::OutputEncoding = $prevEnc } catch { }
83
- }
91
+ $prevEAP = $ErrorActionPreference; $prevEnc = [Console]::OutputEncoding
92
+ try {
93
+ $ErrorActionPreference = 'Continue'
94
+ try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch { }
95
+ $limit = if ($command -match '^agend (restart|start)( |$)') { 360 } else { 30 }
96
+ return (Invoke-BoundedWsl -TimeoutSeconds $limit -Arguments @('-d', $distro, '-u', $user, '--', 'bash', '-lc', (Get-WslAgendCommand $command)))
97
+ } catch {
98
+ return @{ Code = -1; Text = "$_" }
99
+ } finally {
100
+ $ErrorActionPreference = $prevEAP
101
+ try { [Console]::OutputEncoding = $prevEnc } catch { }
102
+ }
84
103
  }
85
104
  function Invoke-RestartAgend([string]$distro, [string]$user, [switch]$Wait, [int]$SettleSeconds = 3) {
86
- Write-Host ""
87
- $answer = Read-Host " 確定要重啟 AgEnD 嗎?所有 agent 會重新啟動,正在進行的工作會中斷。(y/N)"
88
- # 只有一個 y 才算同意(y/N、yes 都不算);[string]:沒有輸入時 Read-Host 回傳 $null,而 $null -notmatch '…' 不是 $true。
89
- if (([string]$answer) -notmatch '^\s*[yY]\s*$') {
90
- Write-Host " 沒有重啟。"
91
- if ($Wait) { $null = Read-Host "按 Enter 關閉" }
92
- return 'cancelled'
93
- }
94
- $r = Invoke-AgendInWsl $distro $user 'agend restart'
95
- $result = 'restarted'
96
- if ($r.Code -eq 0) {
97
- Write-Host " [OK] AgEnD 正在重新啟動。" -ForegroundColor Green
98
- } else {
99
- $result = 'failed'
100
- if ($r.Code -eq -1) { Write-Host " [!!] 找不到 WSL(wsl.exe),所以沒有重啟。" -ForegroundColor Yellow }
101
- elseif ($r.Text -match 'Fleet not running') { Write-Host (" [!!] AgEnD 沒有在執行,所以沒有可以重啟的。要啟動它:" + (Get-AgendStartHint $distro $user)) -ForegroundColor Yellow }
102
- elseif ($r.Text -match 'command not found|agend: not found|no such file') { Write-Host (" [!!] 在 {0} 裡找不到 AgEnD(agend 指令),所以沒有重啟。" -f $distro) -ForegroundColor Yellow }
103
- else { Write-Host (" [!!] 沒有重啟成功:{0} 不存在、WSL 沒有啟動,或 AgEnD 回報了錯誤。" -f $distro) -ForegroundColor Yellow }
104
- if ($r.Text.Trim()) { Write-Host (" " + ($r.Text.Trim() -replace "`r?`n", "`n ")) }
105
- }
106
- if ($result -eq 'restarted') {
107
- Start-Sleep -Seconds $SettleSeconds
108
- Write-Host ""
109
- $s = Invoke-AgendInWsl $distro $user 'agend status'
110
- if ($s.Text.Trim()) { Write-Host (" " + ($s.Text.Trim() -replace "`r?`n", "`n ")) }
111
- }
112
- if ($Wait) { $null = Read-Host "按 Enter 關閉" }
113
- return $result
105
+ Write-Host ""
106
+ $answer = Read-Host " 確定要重啟 AgEnD 嗎?所有 agent 會重新啟動,正在進行的工作會中斷。(y/N)"
107
+ if (([string]$answer) -notmatch '^\s*[yY]\s*$') {
108
+ Write-Host " 沒有重啟。"
109
+ if ($Wait) { $null = Read-Host "按 Enter 關閉" }
110
+ return 'cancelled'
111
+ }
112
+ $r = Invoke-AgendInWsl $distro $user 'agend restart'
113
+ $result = 'restarted'
114
+ if ($r.Code -eq 0) {
115
+ Write-Host " [OK] AgEnD 正在重新啟動。" -ForegroundColor Green
116
+ } else {
117
+ $result = 'failed'
118
+ if ($r.Code -eq -1) { Write-Host " [!!] 找不到 WSL(wsl.exe),所以沒有重啟。" -ForegroundColor Yellow }
119
+ elseif ($r.Text -match 'Fleet not running') { Write-Host (" [!!] AgEnD 沒有在執行,所以沒有可以重啟的。要啟動它:" + (Get-AgendStartHint $distro $user)) -ForegroundColor Yellow }
120
+ elseif ($r.Text -match 'command not found|agend: not found|no such file') { Write-Host (" [!!] 在 {0} 裡找不到 AgEnD(agend 指令),所以沒有重啟。" -f $distro) -ForegroundColor Yellow }
121
+ else { Write-Host (" [!!] 沒有重啟成功:{0} 不存在、WSL 沒有啟動,或 AgEnD 回報了錯誤。" -f $distro) -ForegroundColor Yellow }
122
+ if ($r.Text.Trim()) { Write-Host (" " + ($r.Text.Trim() -replace "`r?`n", "`n ")) }
123
+ }
124
+ if ($result -eq 'restarted') {
125
+ Start-Sleep -Seconds $SettleSeconds
126
+ Write-Host ""
127
+ $s = Invoke-AgendInWsl $distro $user 'agend status'
128
+ if ($s.Text.Trim()) { Write-Host (" " + ($s.Text.Trim() -replace "`r?`n", "`n ")) }
129
+ }
130
+ if ($Wait) { $null = Read-Host "按 Enter 關閉" }
131
+ return $result
114
132
  }
115
133
  '@
116
134
  . ([scriptblock]::Create($RESTART_FN))
117
-
118
- # 捷徑執行的腳本:共用的函式加上一行呼叫(UTF-8 有 BOM:Windows PowerShell 5.1 才會正確顯示中文)。Linux 與使用者是安裝時記下的。
119
- # 捷徑腳本印出的第一行:它要打開的 Linux 和使用者,指錯了一眼就看得出來。
120
135
  function Get-ShortcutTargetLine([string]$distro, [string]$user) {
121
- return ('Write-Host ("AgEnD(Linux:{0},使用者:{1})" -f ' + (ConvertTo-PsLiteral $distro) + ', ' + (ConvertTo-PsLiteral $user) + ')')
136
+ return ('Write-Host ("AgEnD(Linux:{0},使用者:{1})" -f ' + (ConvertTo-PsLiteral $distro) + ', ' + (ConvertTo-PsLiteral $user) + ')')
122
137
  }
123
138
  function Get-DashboardOpenerScript([string]$distro, [string]$user) {
124
- return ("# AgEnD 儀表板(AgEnD 安裝程式建立)`n" + (Get-ShortcutTargetLine $distro $user) + "`n" + $WSL_COMMAND_FN + "`n" + $DASHBOARD_OPENER_FN + "`n" + ("Invoke-OpenDashboard {0} {1} -Wait | Out-Null" -f (ConvertTo-PsLiteral $distro), (ConvertTo-PsLiteral $user)) + "`n")
139
+ return ("# AgEnD 儀表板(AgEnD 安裝程式建立)`n" + (Get-ShortcutTargetLine $distro $user) + "`n" + $WSL_COMMAND_FN + "`n" + $DASHBOARD_OPENER_FN + "`n" + ("Invoke-OpenDashboard {0} {1} -Wait | Out-Null" -f (ConvertTo-PsLiteral $distro), (ConvertTo-PsLiteral $user)) + "`n")
125
140
  }
126
141
  function Get-RestartScript([string]$distro, [string]$user) {
127
- return ("# AgEnD 重新啟動(AgEnD 安裝程式建立)`n" + (Get-ShortcutTargetLine $distro $user) + "`n" + $WSL_COMMAND_FN + "`n" + $RESTART_FN + "`n" + ("Invoke-RestartAgend {0} {1} -Wait | Out-Null" -f (ConvertTo-PsLiteral $distro), (ConvertTo-PsLiteral $user)) + "`n")
142
+ return ("# AgEnD 重新啟動(AgEnD 安裝程式建立)`n" + (Get-ShortcutTargetLine $distro $user) + "`n" + $WSL_COMMAND_FN + "`n" + $RESTART_FN + "`n" + ("Invoke-RestartAgend {0} {1} -Wait | Out-Null" -f (ConvertTo-PsLiteral $distro), (ConvertTo-PsLiteral $user)) + "`n")
128
143
  }
129
- # 安裝時記下的 Linux 與使用者(%LOCALAPPDATA%\AgEnD\install.json):捷徑、重新執行安裝指令時的快速路徑都讀它。
130
144
  function Get-AgendShortcutDir { return (Join-Path $env:LOCALAPPDATA 'AgEnD') }
131
145
  function Read-InstallRecord {
132
- try {
133
- $r = Get-Content -Raw -LiteralPath (Join-Path (Get-AgendShortcutDir) 'install.json') -ErrorAction Stop | ConvertFrom-Json
134
- if ($r.distro -and $r.user) { return @{ Distro = [string]$r.distro; User = [string]$r.user } }
135
- } catch { }
136
- return $null
137
- }
138
- # install.json 已經指向另一個 Linux 或使用者時回傳那筆紀錄(例如 AgEnD 專用的 Linux 和原本的 Ubuntu),否則 $null。
139
- # 捷徑絕不悄悄改指向:agend shortcuts 遇到這種情況會先問。
146
+ try {
147
+ $r = Get-Content -Raw -LiteralPath (Join-Path (Get-AgendShortcutDir) 'install.json') -ErrorAction Stop | ConvertFrom-Json
148
+ if ($r.distro -and $r.user) { return @{ Distro = [string]$r.distro; User = [string]$r.user } }
149
+ } catch { }
150
+ return $null
151
+ }
140
152
  function Get-InstallRecordConflict([string]$distro, [string]$user) {
141
- $record = Read-InstallRecord
142
- # WSL 的名稱不分大小寫;Linux 的使用者名稱分(Han 和 han 是兩個人,#2279 審查)。
143
- if ($record -and ($record.Distro -ne $distro -or $record.User -cne $user)) { return $record }
144
- return $null
153
+ $record = Read-InstallRecord
154
+ if ($record -and ($record.Distro -ne $distro -or $record.User -cne $user)) { return $record }
155
+ return $null
145
156
  }
146
- # 真正寫入前再讀一次:詢問時看到的是 $seen($null:當時沒有衝突)。現在沒有衝突,或衝突的還是同一個,才可以寫;
147
- # 在等回答的時候被別的安裝程式或終端機改掉了,就不寫(#2279 審查)。
148
157
  function Test-InstallRecordAsSeen($seen, [string]$distro, [string]$user) {
149
- $now = Get-InstallRecordConflict $distro $user
150
- if ($null -eq $now) { return $true }
151
- return ($null -ne $seen -and $now.Distro -eq $seen.Distro -and $now.User -ceq $seen.User)
158
+ $now = Get-InstallRecordConflict $distro $user
159
+ if ($null -eq $now) { return $true }
160
+ return ($null -ne $seen -and $now.Distro -eq $seen.Distro -and $now.User -ceq $seen.User)
152
161
  }
153
- # 有 Windows Terminal 時捷徑在它裡面開(中文顯示較好);沒有或 AGEND_INSTALL_TERMINAL=conhost 時直接開 PowerShell。
154
162
  function Get-ShortcutCommand([string]$file) {
155
- $ps = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
156
- $a = '-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f $file
157
- $wt = $null; if ($env:AGEND_INSTALL_TERMINAL -ne 'conhost') { $wt = Get-Command wt.exe -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 }
158
- if ($wt) { return @{ Target = $wt.Source; Arguments = '-w new "{0}" {1}' -f $ps, ($a -replace ';', '\;') } }
159
- return @{ Target = $ps; Arguments = $a }
163
+ $ps = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
164
+ $a = '-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f $file
165
+ $wt = $null; if ($env:AGEND_INSTALL_TERMINAL -ne 'conhost') { $wt = Get-Command wt.exe -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 }
166
+ if ($wt) { return @{ Target = $wt.Source; Arguments = '-w new "{0}" {1}' -f $ps, ($a -replace ';', '\;') } }
167
+ return @{ Target = $ps; Arguments = $a }
160
168
  }
161
- # 一個桌面捷徑:腳本寫在 %LOCALAPPDATA%\AgEnD,捷徑以 Windows PowerShell 執行它(-ExecutionPolicy Bypass 只對那個行程)。
162
169
  function New-AgendShortcut([string]$name, [string]$scriptName, [string]$scriptText, [string]$description) {
163
- $dir = Get-AgendShortcutDir
164
- New-Item -ItemType Directory -Path $dir -Force | Out-Null
165
- $file = Join-Path $dir $scriptName
166
- [IO.File]::WriteAllText($file, $scriptText, (New-Object System.Text.UTF8Encoding $true))
167
- $lnk = Join-Path ([Environment]::GetFolderPath('Desktop')) ($name + '.lnk')
168
- $shell = New-Object -ComObject WScript.Shell
169
- $sc = $shell.CreateShortcut($lnk)
170
- $cmd = Get-ShortcutCommand $file
171
- $sc.TargetPath = $cmd.Target
172
- $sc.Arguments = $cmd.Arguments
173
- $sc.Description = $description
174
- $sc.Save()
175
- return $lnk
176
- }
177
- # 建立(或更新)兩個桌面捷徑、記下 Linux 與使用者。兩個各自建立,一個失敗不會擋住另一個;回傳建立好的捷徑路徑,
178
- # 呼叫端用 Write-ShortcutResult 分清楚兩個都好、只好一個、都沒好(#2252 審查)。兩個都好才在 WSL 裡留記號(agend update
179
- # 之後的提示就不會再出現)。不是 Windows 時回傳空陣列。
170
+ $dir = Get-AgendShortcutDir
171
+ New-Item -ItemType Directory -Path $dir -Force | Out-Null
172
+ $file = Join-Path $dir $scriptName
173
+ [IO.File]::WriteAllText($file, $scriptText, (New-Object System.Text.UTF8Encoding $true))
174
+ $lnk = Join-Path ([Environment]::GetFolderPath('Desktop')) ($name + '.lnk')
175
+ $shell = New-Object -ComObject WScript.Shell
176
+ $sc = $shell.CreateShortcut($lnk)
177
+ $cmd = Get-ShortcutCommand $file
178
+ $sc.TargetPath = $cmd.Target
179
+ $sc.Arguments = $cmd.Arguments
180
+ $sc.Description = $description
181
+ $sc.Save()
182
+ return $lnk
183
+ }
180
184
  function Install-AgendShortcuts([string]$distro, [string]$user) {
181
- if (-not $script:OnWindows) { return @() }
182
- $made = @()
183
- try { $made += New-AgendShortcut 'AgEnD 儀表板' 'open-dashboard.ps1' (Get-DashboardOpenerScript $distro $user) 'AgEnD 網頁儀表板(自動帶入登入碼)' } catch { }
184
- try { $made += New-AgendShortcut 'AgEnD 重新啟動' 'restart-agend.ps1' (Get-RestartScript $distro $user) '重新啟動 AgEnD(會先詢問)' } catch { }
185
- try {
186
- New-Item -ItemType Directory -Path (Get-AgendShortcutDir) -Force | Out-Null
187
- $record = @{ distro = $distro; user = $user } | ConvertTo-Json -Compress
188
- [IO.File]::WriteAllText((Join-Path (Get-AgendShortcutDir) 'install.json'), $record, (New-Object System.Text.UTF8Encoding $false))
189
- if ($made.Count -eq 2) { $null = Invoke-AgendInWsl $distro $user 'set -f; IFS=; d=${AGEND_HOME:-$HOME/.agend}; umask 077; mkdir -p -m 700 -- $d && date -u +%FT%TZ > $d/windows-shortcuts' }
190
- } catch { }
185
+ if (-not $script:OnWindows) { return @() }
186
+ $made = @()
187
+ try { $made += New-AgendShortcut 'AgEnD 儀表板' 'open-dashboard.ps1' (Get-DashboardOpenerScript $distro $user) 'AgEnD 網頁儀表板(自動帶入登入碼)' } catch { }
188
+ try { $made += New-AgendShortcut 'AgEnD 重新啟動' 'restart-agend.ps1' (Get-RestartScript $distro $user) '重新啟動 AgEnD(會先詢問)' } catch { }
189
+ try {
190
+ New-Item -ItemType Directory -Path (Get-AgendShortcutDir) -Force | Out-Null
191
+ $record = @{ distro = $distro; user = $user } | ConvertTo-Json -Compress
192
+ [IO.File]::WriteAllText((Join-Path (Get-AgendShortcutDir) 'install.json'), $record, (New-Object System.Text.UTF8Encoding $false))
193
+ if ($made.Count -eq 2) { $null = Invoke-AgendInWsl $distro $user 'set -f; IFS=; d=${AGEND_HOME:-$HOME/.agend}; umask 077; mkdir -p -m 700 -- $d && date -u +%FT%TZ > $d/windows-shortcuts' }
194
+ } catch { }
191
195
  return $made
192
196
  }