@songsid/agend 2.3.5-alpha.2 → 2.3.5-alpha.4

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 (279) hide show
  1. package/dist/agend-home.d.ts +13 -0
  2. package/dist/agend-home.js +42 -0
  3. package/dist/agend-home.js.map +1 -1
  4. package/dist/agent-runtime-api.d.ts +4 -0
  5. package/dist/agent-runtime-api.js +59 -0
  6. package/dist/agent-runtime-api.js.map +1 -0
  7. package/dist/agent-runtime-change.d.ts +74 -0
  8. package/dist/agent-runtime-change.js +102 -0
  9. package/dist/agent-runtime-change.js.map +1 -0
  10. package/dist/auth-api.d.ts +21 -0
  11. package/dist/auth-api.js +95 -4
  12. package/dist/auth-api.js.map +1 -1
  13. package/dist/authority/web-authority.d.ts +12 -2
  14. package/dist/authority/web-authority.js +25 -4
  15. package/dist/authority/web-authority.js.map +1 -1
  16. package/dist/backend/claude-code.js +16 -1
  17. package/dist/backend/claude-code.js.map +1 -1
  18. package/dist/backend/codex-footer-hints.d.ts +4 -0
  19. package/dist/backend/codex-footer-hints.js +27 -0
  20. package/dist/backend/codex-footer-hints.js.map +1 -0
  21. package/dist/backend/codex.js +19 -11
  22. package/dist/backend/codex.js.map +1 -1
  23. package/dist/backend/kiro-identity.d.ts +2 -0
  24. package/dist/backend/kiro-identity.js +13 -6
  25. package/dist/backend/kiro-identity.js.map +1 -1
  26. package/dist/backend/kiro.js +18 -3
  27. package/dist/backend/kiro.js.map +1 -1
  28. package/dist/backend-availability.d.ts +6 -0
  29. package/dist/backend-availability.js +13 -5
  30. package/dist/backend-availability.js.map +1 -1
  31. package/dist/channel/mcp-tools.js +1 -1
  32. package/dist/channel/mcp-tools.js.map +1 -1
  33. package/dist/cli.js +6 -9
  34. package/dist/cli.js.map +1 -1
  35. package/dist/config-validator.js +12 -1
  36. package/dist/config-validator.js.map +1 -1
  37. package/dist/daemon.d.ts +5 -0
  38. package/dist/daemon.js +107 -30
  39. package/dist/daemon.js.map +1 -1
  40. package/dist/fleet-manager.d.ts +146 -8
  41. package/dist/fleet-manager.js +1030 -166
  42. package/dist/fleet-manager.js.map +1 -1
  43. package/dist/fleet-windows-runtime.d.ts +6 -1
  44. package/dist/fleet-windows-runtime.js +77 -1
  45. package/dist/fleet-windows-runtime.js.map +1 -1
  46. package/dist/fleet-yaml-patch.d.ts +3 -0
  47. package/dist/fleet-yaml-patch.js +103 -0
  48. package/dist/fleet-yaml-patch.js.map +1 -0
  49. package/dist/general-binding.d.ts +85 -0
  50. package/dist/general-binding.js +298 -0
  51. package/dist/general-binding.js.map +1 -0
  52. package/dist/general-host-owner.d.ts +8 -0
  53. package/dist/general-host-owner.js +26 -0
  54. package/dist/general-host-owner.js.map +1 -0
  55. package/dist/general-instance.d.ts +7 -0
  56. package/dist/general-instance.js +20 -3
  57. package/dist/general-instance.js.map +1 -1
  58. package/dist/general-knowledge/skills/model-discovery/SKILL.md +2 -0
  59. package/dist/general-role.d.ts +5 -0
  60. package/dist/general-role.js +26 -0
  61. package/dist/general-role.js.map +1 -0
  62. package/dist/instance-lifecycle.d.ts +1 -0
  63. package/dist/instance-lifecycle.js +26 -4
  64. package/dist/instance-lifecycle.js.map +1 -1
  65. package/dist/instructions.d.ts +2 -0
  66. package/dist/instructions.js +6 -0
  67. package/dist/instructions.js.map +1 -1
  68. package/dist/kiro-resume-summary.d.ts +37 -0
  69. package/dist/kiro-resume-summary.js +85 -0
  70. package/dist/kiro-resume-summary.js.map +1 -0
  71. package/dist/locale.js +59 -0
  72. package/dist/locale.js.map +1 -1
  73. package/dist/login-controller.js +9 -1
  74. package/dist/login-controller.js.map +1 -1
  75. package/dist/native-pane-lifecycle.js +4 -1
  76. package/dist/native-pane-lifecycle.js.map +1 -1
  77. package/dist/native-pane-manager.d.ts +1 -0
  78. package/dist/native-pane-manager.js +23 -2
  79. package/dist/native-pane-manager.js.map +1 -1
  80. package/dist/needs-you-hub.js +1 -1
  81. package/dist/needs-you-hub.js.map +1 -1
  82. package/dist/needs-you.d.ts +8 -1
  83. package/dist/needs-you.js +5 -2
  84. package/dist/needs-you.js.map +1 -1
  85. package/dist/office-share-page.js +1 -1
  86. package/dist/office-share-page.js.map +1 -1
  87. package/dist/package-preimage.js +27 -2
  88. package/dist/package-preimage.js.map +1 -1
  89. package/dist/pane-port.d.ts +11 -0
  90. package/dist/public-web-gateway.js +9 -0
  91. package/dist/public-web-gateway.js.map +1 -1
  92. package/dist/public-web-link.d.ts +11 -0
  93. package/dist/public-web-link.js +13 -0
  94. package/dist/public-web-link.js.map +1 -1
  95. package/dist/quickstart-api.js +9 -5
  96. package/dist/quickstart-api.js.map +1 -1
  97. package/dist/quickstart.d.ts +1 -0
  98. package/dist/quickstart.js +4 -1
  99. package/dist/quickstart.js.map +1 -1
  100. package/dist/reply-buttons-controller.js +10 -0
  101. package/dist/reply-buttons-controller.js.map +1 -1
  102. package/dist/runtime-api.js +84 -9
  103. package/dist/runtime-api.js.map +1 -1
  104. package/dist/runtime-build-info.d.ts +9 -0
  105. package/dist/runtime-build-info.js +57 -0
  106. package/dist/runtime-build-info.js.map +1 -0
  107. package/dist/runtime-bundle.d.ts +3 -1
  108. package/dist/runtime-bundle.js +17 -8
  109. package/dist/runtime-bundle.js.map +1 -1
  110. package/dist/runtime-cli.d.ts +6 -1
  111. package/dist/runtime-cli.js +46 -11
  112. package/dist/runtime-cli.js.map +1 -1
  113. package/dist/runtime-client-store.d.ts +20 -1
  114. package/dist/runtime-client-store.js +56 -4
  115. package/dist/runtime-client-store.js.map +1 -1
  116. package/dist/runtime-client.d.ts +12 -0
  117. package/dist/runtime-client.js +43 -7
  118. package/dist/runtime-client.js.map +1 -1
  119. package/dist/runtime-connection.d.ts +22 -0
  120. package/dist/runtime-connection.js +93 -0
  121. package/dist/runtime-connection.js.map +1 -0
  122. package/dist/runtime-coordinator.d.ts +9 -0
  123. package/dist/runtime-coordinator.js +81 -34
  124. package/dist/runtime-coordinator.js.map +1 -1
  125. package/dist/runtime-general-protocol.d.ts +22 -0
  126. package/dist/runtime-general-protocol.js +57 -0
  127. package/dist/runtime-general-protocol.js.map +1 -0
  128. package/dist/runtime-general-workspace.d.ts +17 -0
  129. package/dist/runtime-general-workspace.js +213 -0
  130. package/dist/runtime-general-workspace.js.map +1 -0
  131. package/dist/runtime-pane-assets.d.ts +1 -1
  132. package/dist/runtime-pane-assets.js +1 -1
  133. package/dist/runtime-pane-assets.js.map +1 -1
  134. package/dist/runtime-pane-boot.d.ts +7 -0
  135. package/dist/runtime-pane-boot.js +23 -0
  136. package/dist/runtime-pane-boot.js.map +1 -1
  137. package/dist/runtime-pane-host.d.ts +1 -0
  138. package/dist/runtime-pane-host.js +22 -1
  139. package/dist/runtime-pane-host.js.map +1 -1
  140. package/dist/runtime-pane-mcp.d.ts +8 -0
  141. package/dist/runtime-pane-mcp.js +15 -2
  142. package/dist/runtime-pane-mcp.js.map +1 -1
  143. package/dist/runtime-pane-port.d.ts +6 -1
  144. package/dist/runtime-pane-port.js +21 -0
  145. package/dist/runtime-pane-port.js.map +1 -1
  146. package/dist/runtime-pane-protocol.d.ts +4 -1
  147. package/dist/runtime-pane-protocol.js +7 -1
  148. package/dist/runtime-pane-protocol.js.map +1 -1
  149. package/dist/runtime-protocol.d.ts +13 -0
  150. package/dist/runtime-protocol.js +18 -0
  151. package/dist/runtime-protocol.js.map +1 -1
  152. package/dist/settings-api.d.ts +5 -0
  153. package/dist/settings-api.js +37 -1
  154. package/dist/settings-api.js.map +1 -1
  155. package/dist/settings-baseline.d.ts +1 -0
  156. package/dist/settings-baseline.js +1 -1
  157. package/dist/settings-baseline.js.map +1 -1
  158. package/dist/settings-confirmation.d.ts +1 -0
  159. package/dist/settings-confirmation.js +1 -0
  160. package/dist/settings-confirmation.js.map +1 -1
  161. package/dist/settings-effect.d.ts +4 -0
  162. package/dist/settings-effect.js +36 -1
  163. package/dist/settings-effect.js.map +1 -1
  164. package/dist/settings-http-confirmation.d.ts +4 -0
  165. package/dist/settings-http-confirmation.js +20 -1
  166. package/dist/settings-http-confirmation.js.map +1 -1
  167. package/dist/settings-request-capability.d.ts +2 -0
  168. package/dist/settings-request-capability.js +4 -0
  169. package/dist/settings-request-capability.js.map +1 -1
  170. package/dist/settings-route-lifetime.d.ts +14 -0
  171. package/dist/settings-route-lifetime.js +48 -0
  172. package/dist/settings-route-lifetime.js.map +1 -0
  173. package/dist/settings-transaction.d.ts +4 -0
  174. package/dist/settings-transaction.js +9 -0
  175. package/dist/settings-transaction.js.map +1 -1
  176. package/dist/setup-form.js +6 -3
  177. package/dist/setup-form.js.map +1 -1
  178. package/dist/setup-i18n.js +3 -0
  179. package/dist/setup-i18n.js.map +1 -1
  180. package/dist/stall-reporter.d.ts +21 -0
  181. package/dist/stall-reporter.js +82 -0
  182. package/dist/stall-reporter.js.map +1 -0
  183. package/dist/tmux-control.d.ts +2 -2
  184. package/dist/tmux-control.js +4 -2
  185. package/dist/tmux-control.js.map +1 -1
  186. package/dist/tmux-exec-helper.d.ts +6 -3
  187. package/dist/tmux-exec-helper.js +115 -53
  188. package/dist/tmux-exec-helper.js.map +1 -1
  189. package/dist/tool-permissions.js +2 -1
  190. package/dist/tool-permissions.js.map +1 -1
  191. package/dist/tunnel/cloudflared-install.d.ts +4 -0
  192. package/dist/tunnel/cloudflared-install.js +20 -1
  193. package/dist/tunnel/cloudflared-install.js.map +1 -1
  194. package/dist/tunnel/cloudflared.d.ts +8 -1
  195. package/dist/tunnel/cloudflared.js +34 -6
  196. package/dist/tunnel/cloudflared.js.map +1 -1
  197. package/dist/tunnel/lease.d.ts +3 -3
  198. package/dist/tunnel/lease.js +23 -19
  199. package/dist/tunnel/lease.js.map +1 -1
  200. package/dist/tunnel/manager.js +6 -7
  201. package/dist/tunnel/manager.js.map +1 -1
  202. package/dist/types.d.ts +2 -0
  203. package/dist/ui/agent-proposals.js +2 -2
  204. package/dist/ui/agent-runtime-settings.js +67 -0
  205. package/dist/ui/agent-runtime.js +20 -2
  206. package/dist/ui/backend-signin.js +36 -21
  207. package/dist/ui/general-location.js +71 -0
  208. package/dist/ui/native-instance-settings.js +5 -8
  209. package/dist/ui/native-pane-recovery.js +1 -1
  210. package/dist/ui/panel-chat.js +5 -2
  211. package/dist/ui/panel-fleet.js +4 -2
  212. package/dist/ui/panel-needs.js +2 -0
  213. package/dist/ui/panel-runtimes.js +136 -33
  214. package/dist/ui/panel-settings.js +227 -138
  215. package/dist/ui/panel-windows-cli.js +77 -41
  216. package/dist/ui/settings-agent-filters.js +94 -0
  217. package/dist/ui/settings-appearance.js +122 -23
  218. package/dist/ui/settings-dialogs.js +13 -7
  219. package/dist/ui/settings-guests.js +25 -11
  220. package/dist/ui/settings-layout.js +245 -0
  221. package/dist/ui/settings-model.js +10 -1
  222. package/dist/ui/settings-outside.js +176 -0
  223. package/dist/ui/settings-registry.js +6 -3
  224. package/dist/ui/settings-shortcuts.js +65 -0
  225. package/dist/ui/settings-strings.js +95 -0
  226. package/dist/ui/settings-web-authority.js +14 -11
  227. package/dist/ui/shared/app-i18n.js +2 -2
  228. package/dist/ui/shared/app-shell.js +17 -7
  229. package/dist/ui/shared/app.css +1038 -13
  230. package/dist/ui/shared/instance-nav.js +16 -5
  231. package/dist/ui/shared/machine-name.js +13 -0
  232. package/dist/ui/shared/panel-view.js +266 -17
  233. package/dist/ui/shared/qrcode.module.js +2237 -0
  234. package/dist/ui/shared/settings-width.js +31 -0
  235. package/dist/ui/shared/signin.js +39 -5
  236. package/dist/ui/shared/tokens.css +1 -0
  237. package/dist/ui/shared/ui-dialog.js +2 -2
  238. package/dist/ui/shared/ui-menu.js +3 -3
  239. package/dist/ui/shared/vendor/qrcode-generator.LICENSE +27 -0
  240. package/dist/ui/shared/vendor/vendor.json +19 -2
  241. package/dist/ui/shared/view-strings.js +28 -0
  242. package/dist/ui/windows-cli-catalog.js +41 -12
  243. package/dist/unattended-install.d.ts +1 -1
  244. package/dist/unattended-install.js +19 -5
  245. package/dist/unattended-install.js.map +1 -1
  246. package/dist/update-check.d.ts +2 -8
  247. package/dist/update-check.js +2 -5
  248. package/dist/update-check.js.map +1 -1
  249. package/dist/view-api.d.ts +15 -0
  250. package/dist/view-api.js +94 -33
  251. package/dist/view-api.js.map +1 -1
  252. package/dist/web-api.d.ts +13 -0
  253. package/dist/web-api.js +80 -0
  254. package/dist/web-api.js.map +1 -1
  255. package/dist/web-login.d.ts +4 -3
  256. package/dist/web-login.js +23 -14
  257. package/dist/web-login.js.map +1 -1
  258. package/dist/web-only-fleet.d.ts +20 -0
  259. package/dist/web-only-fleet.js +21 -0
  260. package/dist/web-only-fleet.js.map +1 -1
  261. package/dist/web-remote-access.d.ts +152 -0
  262. package/dist/web-remote-access.js +368 -0
  263. package/dist/web-remote-access.js.map +1 -0
  264. package/dist/web-session.d.ts +12 -0
  265. package/dist/web-session.js +22 -8
  266. package/dist/web-session.js.map +1 -1
  267. package/dist/windows-agents.d.ts +15 -1
  268. package/dist/windows-agents.js +37 -10
  269. package/dist/windows-agents.js.map +1 -1
  270. package/dist/windows-cli-api.js +2 -1
  271. package/dist/windows-cli-api.js.map +1 -1
  272. package/dist/windows-cli-worker.d.ts +1 -0
  273. package/dist/windows-cli-worker.js +2 -2
  274. package/dist/windows-cli-worker.js.map +1 -1
  275. package/dist/windows-cli.d.ts +1 -0
  276. package/dist/windows-cli.js +7 -2
  277. package/dist/windows-cli.js.map +1 -1
  278. package/launcher/agend-install.cjs +16 -2
  279. package/package.json +1 -1
@@ -2,12 +2,15 @@ import { html, useEffect, useRef, useState } from "/assets/app-html.js";
2
2
  import { register, t } from "/assets/app-i18n.js";
3
3
  import { useLease } from "/assets/app-ctx.js";
4
4
  import { Icon } from "/assets/ui-icons.js";
5
+ import { Dialog } from "/assets/ui-dialog.js";
6
+ import { Menu } from "/assets/ui-menu.js";
5
7
  import { appStore, useStore } from "/assets/app-store.js";
6
8
  import { confirmDialog } from "/assets/ui-confirm.js";
7
9
  import { confirmedWrite } from "./settings-confirm.js";
8
10
  import { CliProductMark, cliLabel } from "./cli-picker.js";
9
11
  import { WindowsCliPanel } from "./panel-windows-cli.js";
10
12
  import { useRuntimeCatalog } from "./runtime-catalog.js";
13
+ import { useWindowsCliCatalog, refreshWindowsCliCatalog, useWindowsCliCatalogSnapshot } from "./windows-cli-catalog.js";
11
14
 
12
15
  register("machines", {
13
16
  en: {
@@ -18,9 +21,9 @@ register("machines", {
18
21
  lastSeen:"Last seen: {0}", inventoryAt:"CLI inventory: {0}", noInventory:"CLI inventory has not been reported yet.", noClis:"No installed CLIs reported.",
19
22
  approve:"Approve computer", revoke:"Revoke connection", rename:"Rename", save:"Save name", cancel:"Cancel", name:"Computer name", probe:"Refresh CLI inventory",
20
23
  fingerprint:"Pairing fingerprint", fingerprintLabel:"Pairing fingerprint: ", approvalHint:"Compare this fingerprint with the one shown in the requesting Runtime before approving.", identity:"Computer identity", signedIn:"Signed in", signedOut:"Not signed in", authUnknown:"Sign-in unknown", detectionOnly:"Detection only", ready:"Tasks supported",
21
- pairing:"Connect a computer", pairingHint:"On the other computer, extract this version's portable Runtime bundle and run the command below from its folder. Paste the one-time code when asked, then approve the computer here. Keep that runtime window open.",
22
- pairingCode:"One-time pairing code", expires:"Expires: {0}", command:"Run on the other computer", pairingWait:"Waiting for the computer to request approval. This page updates automatically.",
23
- server:"AgEnD address", serverHint:"Use an address reachable from the other computer. A localhost address works only with a connection forwarded on that computer.", serverInvalid:"Enter an HTTPS origin, or a localhost HTTP origin with a forwarded connection. Leave out paths, credentials and query strings.", closeCode:"Hide pairing code", revokeAsk:"Revoke this computer's connection? New work will be refused. A task already running may need to be stopped on that computer.",
24
+ pairing:"Connect a computer",
25
+ pairingCode:"One-time pairing code", expires:"Expires: {0}", command:"Run on the other computer", pairingWait:"Waiting for the computer to connect",
26
+ server:"AgEnD address", serverHint:"Must be reachable from the other computer. localhost works only over a forwarded connection.", serverInvalid:"Enter an HTTPS origin, or a localhost HTTP origin with a forwarded connection. Leave out paths, credentials and query strings.", closeCode:"Hide pairing code", revokeAsk:"Revoke this computer's connection? New work will be refused. A task already running may need to be stopped on that computer.",
24
27
  failed:"Could not update Runtime", cliFailed:"CLI refresh failed; the last reported inventory is still shown.", retry:"Retry", seenCached:"Showing the last successfully loaded machine list.",
25
28
  machines:"Connected computers", cli:"CLI", version:"Runtime version", platform:"Platform",
26
29
  switches:"What this fleet may use", switchesNote:"Turning one on asks a fleet admin to confirm, like other access settings. Turning one off needs no fleet admin; it applies as soon as you confirm here.",
@@ -45,9 +48,9 @@ register("machines", {
45
48
  lastSeen:"最近連線:{0}", inventoryAt:"CLI 清單更新:{0}", noInventory:"尚未回報 CLI 清單。", noClis:"尚未回報已安裝的 CLI。",
46
49
  approve:"核准電腦", revoke:"撤銷連線", rename:"重新命名", save:"儲存名稱", cancel:"取消", name:"電腦名稱", probe:"更新 CLI 清單",
47
50
  fingerprint:"配對指紋", fingerprintLabel:"配對指紋:", approvalHint:"核准前,請比對提出要求的 Runtime 視窗所顯示的配對指紋。", identity:"電腦識別碼", signedIn:"已登入", signedOut:"尚未登入", authUnknown:"登入狀態未知", detectionOnly:"僅偵測", ready:"支援派工",
48
- pairing:"連接電腦", pairingHint:"在另一台電腦解壓縮此版本的可攜式 Runtime 套件,從其資料夾執行下方指令,在提示時貼上一次性配對碼,再回到這裡核准電腦。請保持 Runtime 視窗開啟。",
49
- pairingCode:"一次性配對碼", expires:"到期時間:{0}", command:"在另一台電腦執行", pairingWait:"正在等待電腦提出核准要求。此頁會自動更新。",
50
- server:"AgEnD 位址", serverHint:"請使用另一台電腦連得到的位址。localhost 只有在另一台電腦已建立連線轉發時才適用。", serverInvalid:"請填入 HTTPS 來源位址,或已轉發連線的 localhost HTTP 位址,不包含路徑、帳密或查詢參數。", closeCode:"隱藏配對碼", revokeAsk:"要撤銷這台電腦的連線嗎?新的派工會被拒絕;已在執行的任務可能需要在該電腦上停止。",
51
+ pairing:"連接電腦",
52
+ pairingCode:"一次性配對碼", expires:"到期時間:{0}", command:"在另一台電腦執行", pairingWait:"等待電腦連線",
53
+ server:"AgEnD 位址", serverHint:"須為另一台電腦連得到的位址。localhost 僅適用於已轉發的連線。", serverInvalid:"請填入 HTTPS 來源位址,或已轉發連線的 localhost HTTP 位址,不包含路徑、帳密或查詢參數。", closeCode:"隱藏配對碼", revokeAsk:"要撤銷這台電腦的連線嗎?新的派工會被拒絕;已在執行的任務可能需要在該電腦上停止。",
51
54
  failed:"無法更新 Runtime", cliFailed:"CLI 更新失敗,目前仍顯示上次回報的清單。", retry:"重試", seenCached:"目前顯示上次成功載入的機器清單。",
52
55
  machines:"已連接的電腦", cli:"CLI", version:"Runtime 版本", platform:"平台",
53
56
  switches:"這個 Fleet 可以使用", switchesNote:"開啟時,和其他存取設定一樣需要 Fleet 管理員確認;關閉不需 Fleet 管理員確認,你在這裡確認後就會立即生效。",
@@ -66,6 +69,34 @@ register("machines", {
66
69
  },
67
70
  });
68
71
  export const machineText = (key,...values) => t(`machines.${key}`,...values);
72
+ register("machines", {
73
+ en: { computersTitle:"Computers and CLIs", firstConnection:"Connecting for the first time?", stepDownload:"Download the Runtime", stepRun:"Run it on another computer", stepApprove:"Return here to approve", connectionNote:"Each computer keeps its own CLI sign-ins.", fleetScope:"Entire fleet", nativeWindows:"Native Windows", nativeHint:"CLIs and existing sign-ins on this computer's Windows installation.", inventoryHint:"Open diagnostics to inspect CLI availability and sign-ins." },
74
+ "zh-TW": { computersTitle:"電腦與 CLI", firstConnection:"初次連接?", stepDownload:"下載 Runtime", stepRun:"在另一台電腦執行", stepApprove:"回來核准", connectionNote:"每台電腦保留自己的 CLI 登入。", fleetScope:"整個 Fleet", nativeWindows:"Windows 本機", nativeHint:"使用這台電腦 Windows 上的 CLI 與既有登入。", inventoryHint:"前往診斷查看 CLI 可用性與登入狀態。" },
75
+ });
76
+ register("machines", {
77
+ en: { policies:"Runtime policies", policySummary:"Windows CLIs, remote pairing and per-computer limits", diagnostics:"CLI diagnostics and test tasks", diagnosticsHint:"Use this PC's Windows CLI sign-ins and inspect one-off task results.", openDiagnostics:"View local Windows CLIs", hostRuntime:"WSL / Linux", operationsNote:"Connections, approvals and test tasks are confirmed in their own flows." },
78
+ "zh-TW": { policies:"Runtime 政策", policySummary:"本機 Windows CLI、遠端配對與每台電腦的並行數", diagnostics:"CLI 診斷與測試", diagnosticsHint:"使用這台電腦的 Windows CLI 登入,查看診斷或一次性測試任務。", openDiagnostics:"查看本機 Windows CLI", hostRuntime:"WSL / Linux", operationsNote:"連接、核准與測試任務會在各自流程中確認。" },
79
+ });
80
+ register("machines", {
81
+ en: { diagnosticComputer:"Diagnostic computer", inspect:"Open CLI diagnostics", selectComputer:"Select a computer", targetUnavailable:"Selected Runtime is unavailable" },
82
+ "zh-TW": { diagnosticComputer:"診斷電腦", inspect:"查看 CLI 診斷", selectComputer:"選擇電腦", targetUnavailable:"所選 Runtime 無法使用" },
83
+ });
84
+ register("machines", {
85
+ en: { versionColumn:"Version", signInColumn:"Sign-in", authShort:"Unknown", noLocalClis:"No detected CLIs yet. Open diagnostics to detect installed CLIs.", lastSeenLabel:"Last connected", hostLocationNote:"WSL / Linux agents run on the AgEnD host. Windows agents use their selected computer.", connectionHelp:"Connection guide", localBadge:"This PC", pairedBadge:"Paired", available:"Available", cliCount:"{0} CLIs", machineCount:"{0} computers", diagnosticTitle:"CLI diagnostics", machineInfo:"Computer information", machineActions:"Actions for {0}", diagnosticClose:"Close", hostName:"Host name", machineName:"Name", home:"Home directory", neverDetected:"Not detected yet", probeAgain:"Detect again", probeBusy:"Detecting…", diagnosticHint:"Version, sign-in and executable paths on this computer. Expand Test CLI only when you need to check execution." },
86
+ "zh-TW": { versionColumn:"版本", signInColumn:"登入", authShort:"未確認", noLocalClis:"尚未偵測到 CLI,可開啟診斷進行偵測。", lastSeenLabel:"最近連線", hostLocationNote:"WSL / Linux Agent 在 AgEnD 主機上執行;Windows Agent 使用選定的電腦。", connectionHelp:"連線說明", localBadge:"本機", pairedBadge:"已配對", available:"可用", cliCount:"{0} 個 CLI", machineCount:"{0} 台電腦", diagnosticTitle:"CLI 診斷", machineInfo:"電腦資訊", machineActions:"{0} 的操作", diagnosticClose:"關閉", hostName:"主機名稱", machineName:"名稱", home:"使用者目錄", neverDetected:"尚未偵測", probeAgain:"重新偵測", probeBusy:"正在偵測…", diagnosticHint:"查看這台電腦的版本、登入與執行檔路徑;需要確認執行能力時,再展開「測試 CLI」。" },
87
+ });
88
+ register("machines", {
89
+ en: { revokedNote:"Revoked. It leaves this list when you refresh the page." },
90
+ "zh-TW": { revokedNote:"已撤銷,重新整理頁面後會從清單移除。" },
91
+ });
92
+ register("machines", {
93
+ en: { versionNotReported:"This Runtime does not provide version information", versionUnreadable:"Runtime version information is unavailable", buildId:"Code Build ID", buildUnavailable:"Build ID is unavailable", protocols:"Supported protocols", compatibility:"Protocol compatibility", compatible:"Compatible", legacy:"Connected using protocol v1; version metadata unavailable", incompatible:"No common reported protocol", protocolUnknown:"Not reported yet", capabilities:"Reported capabilities", capability_exec:"One-off execution", capability_mcp:"AgEnD tools (MCP)", capability_panes:"Persistent terminals" },
94
+ "zh-TW": { versionNotReported:"此 Runtime 未提供版本資訊", versionUnreadable:"無法讀取 Runtime 版本資訊", buildId:"程式碼 Build ID", buildUnavailable:"無法取得 Build ID", protocols:"支援的協定", compatibility:"協定相容狀態", compatible:"相容", legacy:"已使用協定 v1 連線,未提供版本資訊", incompatible:"回報的協定與主機不相容", protocolUnknown:"尚未回報協定資訊", capabilities:"回報功能", capability_exec:"單次任務", capability_mcp:"AgEnD 工具(MCP)", capability_panes:"持續終端" },
95
+ });
96
+ register("machines", {
97
+ en: { downloadBuilding:"Preparing ZIP…", stepZipHint:"Includes the address above. Extract it on the other computer.", stepLaunch:"Double-click connect-agend.bat there", stepLaunchHint:"Enter the pairing code when asked. A saved profile reconnects without it. If that computer is already paired to another AgEnD, this one gets its own profile and the existing pairing is kept. The window stays open and explains what to do if AgEnD disconnects it, including when jobs.json from another pairing must be deleted.",manualMultiHint:"Without agend-server.json, a profile paired to another AgEnD is never reused. Add --profile \"%USERPROFILE%\\.agend-runtime\\profiles\\<host>-<port>\" (any folder) to keep both, and pass the same --profile to run, status, rotate and disconnect.", stepApproveHint:"Keep the Runtime window open.", pairingApprove:"A computer is waiting for your approval", copyCode:"Copy", codeCopied:"Copied", newCode:"New code", manualCommand:"Manual connection command", downloadedFor:"Downloaded for: {0}", downloadFailed:"Could not download the Runtime", pairingHint:"Follow the four steps. This page updates automatically." },
98
+ "zh-TW": { downloadBuilding:"正在準備 ZIP…", stepZipHint:"已內含上方位址,請在另一台電腦解壓縮。", stepLaunch:"在那台電腦雙擊 connect-agend.bat", stepLaunchHint:"提示時輸入配對碼;已有配對資料時會直接重連。若那台電腦已配對到另一個 AgEnD,這次會自動使用獨立的資料夾,原本的配對不受影響。若 AgEnD 中斷連線,視窗會保持開啟並說明下一步,包括何時需要刪除另一個配對留下的 jobs.json。",manualMultiHint:"沒有 agend-server.json 時,已配對到其他 AgEnD 的資料夾不會被重複使用。請加上 --profile \"%USERPROFILE%\\.agend-runtime\\profiles\\<host>-<port>\"(任何資料夾皆可)讓兩個都保留,之後 run、status、rotate、disconnect 也要帶相同的 --profile。", stepApproveHint:"請保持 Runtime 視窗開啟。", pairingApprove:"有電腦正在等待你核准", copyCode:"複製", codeCopied:"已複製", newCode:"重新產生", manualCommand:"手動連線指令", downloadedFor:"已下載,連線位址:{0}", downloadFailed:"無法下載 Runtime", pairingHint:"依下列四步操作,此頁會自動更新。" },
99
+ });
69
100
  /** Only an origin composed of inert hostname/port characters is placed in a copyable shell command. */
70
101
  export function runtimeConnectCommand(server) {
71
102
  try {
@@ -77,20 +108,36 @@ export function runtimeConnectCommand(server) {
77
108
  } catch { return ""; }
78
109
  }
79
110
  export function machineTime(value) { const date=new Date(value); return value && Number.isFinite(date.getTime()) ? date.toLocaleString() : machineText("unknown"); }
80
- function MachineCard({ machine, busy, action }) {
81
- const [editing,setEditing] = useState(false), [name,setName] = useState(machine.name);
111
+ function MachineCard({ machine, busy, action, openDiagnostics, probe }) {
112
+ const [editing,setEditing] = useState(false), [name,setName] = useState(machine.name), [info,setInfo] = useState(false);
113
+ const detection = useWindowsCliCatalogSnapshot(machine.isLocal ? "" : machine.id);
114
+ const probing = machine.probing || detection.loading;
82
115
  const clis = (machine.inventory?.clis || []).filter(cli => cli.path);
83
- return html`<article class="runtime-machine" data-machine-id=${machine.id}>
84
- <header><span class="runtime-machine-icon"><${Icon} name="view" size=${22} /></span><div><h3>${machine.name}</h3><p>${[machine.platform || machineText("unknown"), machine.hostname, machine.version].filter(Boolean).join(" · ")}</p></div><span class=${`runtime-status is-${machine.status}`}>${machineText(["online","offline","pending","revoked"].includes(machine.status) ? machine.status : "unknown")}</span></header>
85
- <p class="note">${machineText("lastSeen",machineTime(machine.lastSeenAt))}</p><p class="note">${machine.inventoryUpdatedAt ? machineText("inventoryAt",machineTime(machine.inventoryUpdatedAt)) : machineText("noInventory")}</p>
86
- <details><summary>${machineText("identity")}</summary><code class="runtime-machine-id">${machine.id}</code></details>
87
- ${machine.status === "pending" ? html`<div><p>${machineText("fingerprintLabel")}<code class="runtime-pairing-fingerprint">${machine.pairingFingerprint || machineText("unknown")}</code></p><p class="note">${machineText("approvalHint")}</p></div>` : null}
88
- <ul class="runtime-cli-list" aria-label=${machineText("cli")}>${clis.map(cli => html`<li key=${cli.id}><${CliProductMark} id=${cli.id} size=${22} /><span><strong>${cliLabel(cli.id)}</strong><small>${cli.version || "—"}</small></span><span class="note">${machineText(cli.authenticated === true ? "signedIn" : cli.authenticated === false ? "signedOut" : "authUnknown")} · ${machineText(cli.supports?.execution === false ? "detectionOnly" : "ready")}</span></li>`)}</ul>
89
- ${!clis.length ? html`<p class="note">${machineText("noClis")}</p>` : null}
90
- ${editing ? html`<form class="runtime-rename" onSubmit=${event => {event.preventDefault();if(name.trim())void action(machine,"rename",{name:name.trim()}).then(ok => {if(ok)setEditing(false);});}}><label><span>${machineText("name")}</span><input value=${name} maxLength=${100} onInput=${event => setName(event.target.value)} /></label><button class="btn btn-sm" type="submit" disabled=${busy || !name.trim()}>${machineText("save")}</button><button class="btn btn-ghost btn-sm" type="button" onClick=${() => setEditing(false)}>${machineText("cancel")}</button></form>` : null}
91
- <div class="runtime-machine-actions">${machine.status === "pending" ? html`<button type="button" class="btn btn-primary btn-sm" disabled=${busy || !machine.pairingFingerprint} onClick=${() => action(machine,"approve",{fingerprint:machine.pairingFingerprint})}>${machineText("approve")}</button>` : null}${machine.status === "online" ? html`<button type="button" class="btn btn-sm" disabled=${busy} onClick=${() => action(machine,"refresh")}><${Icon} name="restart" size=${14} />${machineText("probe")}</button>` : null}${machine.status !== "revoked" ? html`<button type="button" class="btn btn-ghost btn-sm" disabled=${busy} onClick=${() => setEditing(true)}>${machineText("rename")}</button><button type="button" class="btn btn-ghost btn-sm danger" disabled=${busy} onClick=${() => action(machine,"revoke")}>${machineText("revoke")}</button>` : null}</div>
116
+ const local = machine.isLocal === true, status = local ? machine.inventory ? "available" : "unknown" : machine.status;
117
+ const ready = local || ["online","offline"].includes(status), revoked = !local && status === "revoked";
118
+ const menuItems = [{key:"info",label:machineText("machineInfo"),icon:"info",onSelect:()=>setInfo(true)},
119
+ !local && status !== "revoked" && {key:"rename",label:machineText("rename"),icon:"edit",onSelect:()=>setEditing(true)},
120
+ !local && status !== "revoked" && {key:"revoke",label:machineText("revoke"),icon:"close",danger:true,onSelect:()=>action(machine,"revoke")}];
121
+ return html`<article class=${`runtime-machine${local ? " runtime-native-local" : ""}${revoked ? " is-revoked" : ""}`} data-machine-id=${local ? "local" : machine.id}>
122
+ <header><span class="runtime-machine-icon"><${Icon} name="fleet" size=${22} /></span><div><h3>${machine.name}${revoked ? null : html`<span class="runtime-kind">${machineText(local ? "localBadge" : "pairedBadge")}</span>`}</h3><p>${machine.platform === "win32" ? "Windows" : machine.platform || machineText("unknown")}${machine.hostname && machine.hostname !== machine.name ? ` · ${machine.hostname}` : ""} · ${machineText("cliCount",clis.length)}</p></div><span class=${`runtime-status is-${status}`}><span class="windows-cli-dot"></span>${machineText(["available","online","offline","pending","revoked"].includes(status) ? status : "unknown")}</span></header>
123
+ ${revoked ? null : html`<p class="runtime-inventory-time note">${machine.inventoryUpdatedAt ? machineText("inventoryAt",machineTime(machine.inventoryUpdatedAt)) : machineText("neverDetected")}</p>`}
124
+ ${status === "pending" ? html`<div class="runtime-approval"><p>${machineText("fingerprintLabel")}<code class="runtime-pairing-fingerprint">${machine.pairingFingerprint || machineText("unknown")}</code></p><p class="note">${machineText("approvalHint")}</p></div>` : null}
125
+ ${revoked ? html`<p class="note runtime-revoked-note" role="status">${machineText("revokedNote")}</p>` : clis.length ? html`<table class="runtime-cli-table" aria-label=${machineText("cli")}><thead><tr><th>${machineText("cli")}</th><th>${machineText("versionColumn")}</th><th>${machineText("signInColumn")}</th></tr></thead><tbody>${clis.map(cli=>html`<tr key=${cli.id}><td><span class="runtime-cli-product"><${CliProductMark} id=${cli.id} size=${21} /><strong>${cliLabel(cli.id)}</strong></span>${cli.supports?.execution === false ? html`<small>${machineText("detectionOnly")}</small>` : null}</td><td><span class="runtime-cli-version" title=${cli.version || ""}>${cli.version || "—"}</span></td><td><span class=${`runtime-cli-auth ${cli.authenticated === true ? "is-signed-in" : ""}`}><span class="windows-cli-dot"></span>${machineText(cli.authenticated === true ? "signedIn" : cli.authenticated === false ? "signedOut" : "authShort")}</span></td></tr>`)}</tbody></table>` : html`<p class="note runtime-no-clis">${machineText(local ? "noLocalClis" : "noClis")}</p>`}
126
+ <div class="runtime-machine-actions">${ready ? html`<button type="button" class="btn btn-sm runtime-inspect" onClick=${() => openDiagnostics(local ? "" : machine.id)}>${machineText("diagnosticTitle")}</button><button type="button" class="btn btn-sm runtime-probe" disabled=${busy || probing || !local && status !== "online"} onClick=${() => probe(local ? "" : machine.id)}><${Icon} name="restart" size=${14} cls=${probing ? "out-spin" : ""} />${machineText(probing ? "probeBusy" : "probeAgain")}</button>` : null}
127
+ ${status === "pending" ? html`<button type="button" class="btn btn-primary btn-sm" disabled=${busy || !machine.pairingFingerprint} onClick=${() => action(machine,"approve",{fingerprint:machine.pairingFingerprint})}>${machineText("approve")}</button>` : null}
128
+ <${Menu} cls="runtime-machine-menu" label=${machineText("machineActions",machine.name)} disabled=${busy} items=${menuItems} />
129
+ </div>
130
+ ${info ? html`<${Dialog} title=${`${machineText("machineInfo")} · ${machine.name}`} onClose=${()=>setInfo(false)}><dl class="runtime-machine-facts"><dt>${machineText("machineName")}</dt><dd>${machine.name}</dd><dt>${machineText("hostName")}</dt><dd>${machine.hostname || machine.inventory?.hostname || machineText("unknown")}</dd><dt>${machineText("platform")}</dt><dd>${machine.platform === "win32" ? "Windows" : machine.platform}</dd>${local ? html`<dt>${machineText("home")}</dt><dd>${machine.inventory?.home || machineText("unknown")}</dd>` : html`<dt>${machineText("version")}</dt><dd class="runtime-reported-version">${machine.runtimeInfo?.version || machineText(machine.runtimeInfo ? "versionUnreadable" : "versionNotReported")}</dd>${machine.runtimeInfo ? html`<dt>${machineText("buildId")}</dt><dd class="runtime-reported-build"><code>${machine.runtimeInfo?.buildId || machineText("buildUnavailable")}</code></dd><dt>${machineText("protocols")}</dt><dd>${machine.runtimeInfo?.protocolVersions?.map(v=>`v${v}`).join(", ") || machineText("protocolUnknown")}</dd>` : null}<dt>${machineText("compatibility")}</dt><dd class="runtime-compatibility">${machineText(["compatible","legacy","incompatible"].includes(machine.runtimeCompatibility) ? machine.runtimeCompatibility : "protocolUnknown")}</dd>${machine.runtimeInfo ? html`<dt>${machineText("capabilities")}</dt><dd>${machine.runtimeInfo?.capabilities?.map(capability=>["exec","mcp","panes"].includes(capability) ? machineText(`capability_${capability}`) : capability).join(" · ") || machineText("unknown")}</dd>` : null}<dt>${machineText("lastSeenLabel")}</dt><dd>${machineTime(machine.lastSeenAt)}</dd><dt>${machineText("identity")}</dt><dd><code>${machine.id}</code></dd>`}</dl></${Dialog}>` : null}
131
+ ${editing ? html`<${Dialog} title=${machineText("rename")} onClose=${()=>setEditing(false)} busy=${busy}><form class="runtime-rename" onSubmit=${event=>{event.preventDefault();if(name.trim())void action(machine,"rename",{name:name.trim()}).then(ok=>{if(ok)setEditing(false);});}}><label><span>${machineText("name")}</span><input value=${name} maxLength=${100} onInput=${event=>setName(event.target.value)} /></label><button class="btn btn-primary btn-sm" type="submit" disabled=${busy || !name.trim()}>${machineText("save")}</button></form></${Dialog}>` : null}
92
132
  </article>`;
93
133
  }
134
+ function RuntimeDiagnosticsDialog({ navKey, machineId, machine, enabled, drafts, session, taskLease, onChanged, onClose }) {
135
+ const name = machine?.name || machineText("targetUnavailable");
136
+ return html`<${Dialog} title=${`${machineText("diagnosticTitle")} · ${name}`} cls="runtime-diagnostics-dialog" wide=${true} onClose=${onClose}
137
+ tools=${html`<span class=${`runtime-status is-${machine?.status || "offline"}`}>${machineText(machine?.isLocal ? "localBadge" : machine?.status || "offline")}</span>`}>
138
+ <div class="runtime-diagnostics"><p class="runtime-diagnostics-hint">${machineText("diagnosticHint")}</p><${WindowsCliPanel} navKey=${`${navKey}:diagnostics:${machineId || "local"}`} embedded=${true} diagnostic=${true} enabled=${enabled} machineId=${machineId} machine=${machine} drafts=${drafts} session=${session} taskLease=${taskLease} onInventoryChanged=${onChanged} /></div>
139
+ </${Dialog}>`;
140
+ }
94
141
  /** M-2255: the switches (fleet.yaml `runtimes`); `env` names the variable that overrides each. */
95
142
  const SWITCHES = [
96
143
  { name: "windows_cli", id: "runtimeWindowsCli", env: "AGEND_WINDOWS_CLI", label: "windowsCli" },
@@ -102,7 +149,7 @@ export const RUNTIME_DOCS = "https://github.com/songsid/AgEnD/blob/main/docs/rem
102
149
  * fleet.yaml like any setting: on is confirmed by a fleet admin, off applies at once (after asking here). A switch the
103
150
  * environment sets is shown as such and cannot be changed here.
104
151
  */
105
- function RuntimeSwitches({ lease, onChanged }) {
152
+ function RuntimeSwitches({ lease, onChanged, computers }) {
106
153
  const [view,setView]=useState(null), [busy,setBusy]=useState(""), [pending,setPending]=useState(""), [error,setError]=useState(""), [unavailable,setUnavailable]=useState(false);
107
154
  const [limit,setLimit]=useState(""), [limitNote,setLimitNote]=useState("");
108
155
  // A number being typed is the person's: a reload (a switch's answer) never replaces it; a save of it does.
@@ -150,28 +197,29 @@ function RuntimeSwitches({ lease, onChanged }) {
150
197
  const origin=view?.local_origin || "http://127.0.0.1:19280", port=(() => { try { return new URL(origin).port || "80"; } catch { return "19280"; } })();
151
198
  // Commands, not words: the same in every language (built here, never translated).
152
199
  const serveCommand=`tailscale serve --https=443 ${origin}`, sshCommand=`ssh -N -L 127.0.0.1:${port}:127.0.0.1:${port} user@this-computer`;
153
- const guide=html`<details class="runtime-guide" open=${switches?.remote_runtime?.on === true}>
200
+ const guide=html`<details class="runtime-connect-guide"><summary>${machineText("connectionHelp")}</summary><h3>${machineText("firstConnection")}</h3><ol><li>${machineText("stepDownload")}</li><li>${machineText("stepRun")}</li><li>${machineText("stepApprove")}</li></ol><details class="runtime-guide">
154
201
  <summary>${machineText("guide")}</summary>
155
202
  <p>${machineText("guideAddress")}</p><pre><code>${serveCommand}</code></pre>
156
203
  <p>${machineText("guideSsh",port)}</p><pre><code>${sshCommand}</code></pre>
157
204
  <p class="runtime-guide-warn">${machineText("guideFunnel")}</p><p class="runtime-guide-warn">${machineText("guideLan")}</p>
158
205
  <p>${machineText("guidePair")}</p><p>${machineText("guideApprove")}</p>
159
206
  <a class="runtime-guide-link" href=${RUNTIME_DOCS} target="_blank" rel="noopener noreferrer">${machineText("remoteRuntimeDocs")}<${Icon} name="external" size=${14} /></a>
160
- </details>`;
161
- if (unavailable) return html`<section class="runtime-switches" aria-label=${machineText("switches")}><h3>${machineText("switches")}</h3><p class="note">${machineText("switchesUnavailable")}</p></section>`;
162
- return html`<section class="runtime-switches" aria-label=${machineText("switches")}>
207
+ </details><p class="note">${machineText("connectionNote")}</p></details>`;
208
+ if (unavailable) return html`<div class="runtime-layout"><div class="runtime-main">${computers}<section class="runtime-switches" aria-label=${machineText("switches")}><div class="runtime-switches-main"><h3>${machineText("switches")}</h3><p class="note">${machineText("switchesUnavailable")}</p></div></section></div></div>`;
209
+ return html`<div class="runtime-layout"><div class="runtime-main">${computers}<section class="runtime-switches" aria-label=${machineText("switches")}>
210
+ <details class="runtime-policy s-ui-disclosure"><summary><${Icon} name="settings" size=${22} /><span><strong>${machineText("policies")}</strong><small>${machineText("policySummary")}</small></span></summary>
211
+ <div class="runtime-switches-main">
163
212
  <h3>${machineText("switches")}</h3>
164
213
  ${SWITCHES.map(item => {
165
214
  const state=switches?.[item.name];
166
215
  return html`<div key=${item.name} class="runtime-switch" data-switch=${item.name}>
167
216
  <label class="runtime-switch-row" for=${item.id}>
168
- <input id=${item.id} type="checkbox" role="switch" checked=${state?.on === true} aria-describedby=${`${item.id}Help`}
217
+ <input id=${item.id} class="s-ui-toggle" type="checkbox" role="switch" checked=${state?.on === true} aria-describedby=${`${item.id}Help`}
169
218
  disabled=${!state || !!state.environment || !!busy} onChange=${e => { const want=e.target.checked; e.target.checked=!want; void change(item,want); }} />
170
219
  <span><strong>${machineText(item.label)}</strong><small id=${`${item.id}Help`}>${machineText(`${item.label}Help`)}</small></span>
171
220
  </label>
172
221
  ${pending === item.name ? html`<p class="note runtime-switch-pending" role="status">${machineText("switchPending")}</p>` : null}
173
222
  ${state?.environment ? html`<p class="note runtime-switch-env">${machineText("setByEnv",item.env,state.environment === "on" ? "1" : "0")}</p>` : null}
174
- ${item.name === "remote_runtime" ? guide : null}
175
223
  </div>`;
176
224
  })}
177
225
  <form class="runtime-limit" onSubmit=${saveLimit}>
@@ -183,12 +231,21 @@ function RuntimeSwitches({ lease, onChanged }) {
183
231
  </form>
184
232
  <p class="note">${machineText("switchesNote")}</p>
185
233
  ${error ? html`<p class="runtime-error" role="alert">${error}</p>` : null}
186
- </section>`;
234
+ </div></details></section></div>
235
+ ${guide}
236
+ </div>`;
187
237
  }
188
238
  export function RuntimesPanel({navKey}) {
189
239
  const lease=useLease(`runtimes:${navKey}`), state=useRuntimeCatalog(), {windowsCli}=useStore(appStore);
240
+ const localInventory=useWindowsCliCatalog(windowsCli);
190
241
  const [pairing,setPairing]=useState(null), [busy,setBusy]=useState(""), [error,setError]=useState("");
191
242
  const [server,setServer]=useState(() => typeof location === "undefined" ? "" : location.origin);
243
+ const drafts=useRef(new Map()), taskSessions=useRef(new Map()), downloadClaim=useRef(false);
244
+ const [downloadedFor,setDownloadedFor]=useState(""), [copied,setCopied]=useState(false);
245
+ const [diagnosticMachineId,setDiagnosticMachineId]=useState(null);
246
+ // Computers revoked on this page stay on screen as revoked until the page is left or reloaded; the server keeps the record.
247
+ const [revokedHere,setRevokedHere]=useState(() => new Map());
248
+ const openDiagnostics=id=>setDiagnosticMachineId(id);
192
249
  useEffect(() => {
193
250
  if (!pairing) return;
194
251
  const remaining = new Date(pairing.expiresAt).getTime()-Date.now();
@@ -210,22 +267,68 @@ export function RuntimesPanel({navKey}) {
210
267
  if(operation === "revoke" && !await confirmDialog({message:machineText("revokeAsk"),confirmLabel:machineText("revoke"),danger:true}))return false;
211
268
  if(!lease.current())return false;
212
269
  setBusy(machine.id);setError("");
213
- try {await post(`/ui/runtimes/machines/${encodeURIComponent(machine.id)}/${operation}`,body);if(!lease.current())return false;await state.refresh();return lease.current();}
270
+ try {
271
+ await post(`/ui/runtimes/machines/${encodeURIComponent(machine.id)}/${operation}`,body);if(!lease.current())return false;
272
+ // Kept before the refresh, so the card is never dropped between the answer and the new list.
273
+ if(operation === "revoke")setRevokedHere(seen=>new Map(seen).set(machine.id,{...machine,status:"revoked"}));
274
+ await state.refresh();return lease.current();
275
+ }
214
276
  catch(e){if(lease.current())setError(operation === "refresh" ? `${machineText("cliFailed")} ${e.message}` : e.message);return false;}
215
277
  finally{if(lease.current())setBusy("");}
216
278
  };
217
279
  const machines=state.data?.machines || [];
280
+ const localMachine={id:"",isLocal:true,name:localInventory.data?.hostname || machineText("nativeWindows"),platform:"win32",status:windowsCli ? "online" : "offline",inventory:localInventory.data,inventoryUpdatedAt:localInventory.data?.probedAt,probing:localInventory.loading};
281
+ const listed=machines.filter(machine=>machine.status !== "revoked" || revokedHere.has(machine.id));
282
+ const gone=[...revokedHere.values()].filter(machine=>!machines.some(row=>row.id === machine.id));
283
+ const allMachines=[...(windowsCli ? [localMachine] : []),...[...listed,...gone].map(machine=>({...machine,...(revokedHere.has(machine.id) ? {status:"revoked"} : {}),probing:busy===machine.id}))];
284
+ const target=diagnosticMachineId === "" ? localMachine : machines.find(machine=>machine.id===diagnosticMachineId);
285
+ if(diagnosticMachineId !== null && !taskSessions.current.has(diagnosticMachineId)) taskSessions.current.set(diagnosticMachineId,{requestId:null,pending:false,error:""});
286
+ const probeTarget=async(id="")=>{
287
+ setBusy(id || "local");setError("");
288
+ try{const result=await refreshWindowsCliCatalog(id);if(!lease.current())return;if(!result)throw Error(machineText("cliFailed"));if(id)await state.refresh();}
289
+ catch(error){if(lease.current())setError(error.message);}
290
+ finally{if(lease.current())setBusy("");}
291
+ };
292
+ const copyCode=async() => {
293
+ try {await navigator.clipboard.writeText(pairing.code);if(!lease.current())return;setCopied(true);lease.timeout(() => setCopied(false),2000);}catch { /* the code is on screen to copy by hand */ }
294
+ };
218
295
  const command=runtimeConnectCommand(server);
219
- const download=()=>html`<a class="btn btn-sm runtime-bundle-download" href="/ui/runtimes/download/windows" download><${Icon} name="download" size=${15} />${machineText("download")}</a>`;
296
+ const awaitingApproval=machines.some(machine => machine.status === "pending"), currentStep=awaitingApproval ? 4 : downloadedFor ? 3 : 2;
297
+ const step=(n,title,body) => html`<li class=${`runtime-step${n < currentStep ? " is-done" : ""}`} aria-current=${n === currentStep ? "step" : undefined}><span class="runtime-step-n" aria-hidden="true">${n}</span><div><h4>${title}</h4>${body}</div></li>`;
298
+ const downloadRuntime=async()=>{
299
+ if(downloadClaim.current || !command)return;
300
+ const target=new URL(server).origin;downloadClaim.current=true;setBusy("download");setError("");setDownloadedFor("");
301
+ try{
302
+ const response=await lease.fetch("/ui/runtimes/download/windows",{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({server:target})});
303
+ if(!lease.current())return;
304
+ if(!response.ok){const body=await response.json();throw Error(body.error || machineText("downloadFailed"));}
305
+ const blob=await response.blob();if(!lease.current())return;
306
+ const url=URL.createObjectURL(blob),release=lease.hold(()=>URL.revokeObjectURL(url));
307
+ const link=document.createElement("a");link.href=url;link.download=/filename="([a-zA-Z0-9._-]+\.zip)"/.exec(response.headers.get("content-disposition") || "")?.[1] || "agend-runtime-windows.zip";
308
+ document.body.appendChild(link);link.click();link.remove();lease.timeout(release,1000);setDownloadedFor(target);
309
+ }catch(error){if(lease.current())setError(error.message || machineText("downloadFailed"));}
310
+ finally{downloadClaim.current=false;if(lease.current())setBusy("");}
311
+ };
312
+ const download=(setup=false)=>html`<button type="button" class="btn btn-sm runtime-bundle-download" disabled=${!!busy || !setup && !command} onClick=${setup ? connect : downloadRuntime}><${Icon} name="download" size=${15} />${machineText(busy === "download" ? "downloadBuilding" : "download")}</button>`;
313
+
314
+ const computers = html`<section class="runtime-computers"><div class="runtime-computers-heading"><h3>${machineText("computersTitle")}</h3><span class="note">${machineText("machineCount",allMachines.length)}</span></div>
315
+ <section class="runtime-machines" aria-label=${machineText("machines")}>${allMachines.map(machine=>html`<${MachineCard} key=${machine.isLocal ? "local" : machine.id} machine=${machine} busy=${!!busy || !!machine.probing} action=${action} openDiagnostics=${openDiagnostics} probe=${probeTarget} />`)}</section>
316
+ ${state.data && !allMachines.some(machine=>!machine.isLocal) ? html`<p class="note runtime-empty-remotes">${machineText("empty")}</p>` : null}
317
+ <article class="runtime-host-note"><${Icon} name="info" size=${18} /><span>${machineText("hostLocationNote")}</span></article></section>`;
318
+
220
319
  return html`<section class="runtime-settings">
221
- <header class="runtime-heading"><div><h2>${machineText("title")}</h2><p class="note">${machineText("lead")}</p></div><div>${state.data?.enabled === true && !pairing ? download() : null}<button type="button" class="btn btn-sm" disabled=${state.loading} onClick=${state.refresh}><${Icon} name="restart" size=${15} />${machineText("refresh")}</button><button type="button" class="btn btn-primary btn-sm" disabled=${!!busy || state.data?.enabled !== true} onClick=${connect}><${Icon} name="plus" size=${15} />${machineText("connect")}</button></div></header>
222
- <${RuntimeSwitches} lease=${lease} onChanged=${state.refresh} />
320
+ <header class="runtime-heading s-page-head"><div><h2>${machineText("title")}<span class="s-scope">${machineText("fleetScope")}</span></h2><p class="s-page-lead">${machineText("lead")}</p></div><div><button type="button" class="btn btn-primary btn-sm" disabled=${!!busy || state.data?.enabled !== true} onClick=${connect}><${Icon} name="plus" size=${15} />${machineText("connect")}</button>${state.data?.enabled === true && !pairing ? download(true) : null}<button type="button" class="btn btn-sm" disabled=${state.loading} onClick=${state.refresh}><${Icon} name="restart" size=${15} />${machineText("refresh")}</button></div></header>
223
321
  ${state.data?.enabled === false ? html`<p class="note">${machineText("disabled")}</p>` : null}
224
322
  ${state.loading && !state.data ? html`<p role="status" aria-busy="true"><span class="out-spin" aria-hidden="true"></span> ${machineText("loading")}</p>` : null}
225
323
  ${error || state.error ? html`<div class="runtime-error" role="alert"><p>${error || state.error}</p>${state.data && state.error ? html`<p>${machineText("seenCached")}</p>` : null}<button type="button" class="btn btn-sm" onClick=${state.refresh}>${machineText("retry")}</button></div>` : null}
226
- ${pairing ? html`<section class="runtime-pairing" aria-label=${machineText("pairing")}><h3>${machineText("pairing")}</h3><p>${machineText("pairingHint")}</p><div class="runtime-bundle-step">${download()}<p class="note">${machineText("downloadHint")}</p></div><label><span>${machineText("server")}</span><input type="url" value=${server} spellcheck=${false} onInput=${event => setServer(event.target.value)} /></label><p class="note">${machineText("serverHint")}</p>${command ? html`<label><span>${machineText("command")}</span><input class="runtime-command" value=${command} readOnly=${true} spellcheck=${false} /></label>` : html`<p role="alert">${machineText("serverInvalid")}</p>`}<label><span>${machineText("pairingCode")}</span><input class="runtime-pair-code" value=${pairing.code} readOnly=${true} spellcheck=${false} autocomplete="off" /></label><p class="note">${machineText("expires",machineTime(pairing.expiresAt))}</p><p role="status">${machineText("pairingWait")}</p><button type="button" class="btn btn-ghost btn-sm" onClick=${() => setPairing(null)}>${machineText("closeCode")}</button></section>` : null}
227
- <article class="runtime-local"><h3><${Icon} name="code" size=${18} />${machineText("local")}</h3><p class="note">${machineText("localHint")}</p></article>
228
- <section aria-label=${machineText("machines")}>${machines.map(machine => html`<${MachineCard} key=${machine.id} machine=${machine} busy=${!!busy} action=${action} />`)}${state.data && !machines.length ? html`<p class="note">${machineText("empty")}</p>` : null}</section>
229
- ${windowsCli ? html`<details class="runtime-local-diagnostics"><summary>${machineText("localWindows")}</summary><${WindowsCliPanel} navKey=${`${navKey}:local`} embedded=${true} /></details>` : null}
324
+ ${pairing ? html`<section class="runtime-pairing" aria-label=${machineText("pairing")}><header><h3>${machineText("pairing")}</h3><p class="note">${machineText("pairingHint")}</p></header><div class="runtime-pairing-body"><ol class="runtime-steps">
325
+ ${step(1,machineText("server"),html`<input type="url" aria-label=${machineText("server")} value=${server} disabled=${busy === "download"} spellcheck=${false} onInput=${event => setServer(event.target.value)} /><p class="note">${machineText("serverHint")}</p>${command ? null : html`<p role="alert">${machineText("serverInvalid")}</p>`}`)}
326
+ ${step(2,machineText("stepDownload"),html`<p class="note">${machineText("stepZipHint")}</p>${download()}${downloadedFor ? html`<p class="note" role="status">${machineText("downloadedFor",downloadedFor)}</p>` : null}`)}
327
+ ${step(3,machineText("stepLaunch"),html`<p class="note">${machineText("stepLaunchHint")}</p>${command ? html`<details class="runtime-manual-command"><summary>${machineText("manualCommand")}</summary><label><span>${machineText("command")}</span><input class="runtime-command" value=${command} readOnly=${true} spellcheck=${false} /></label><p class="note">${machineText("manualMultiHint")}</p></details>` : null}`)}
328
+ ${step(4,machineText("stepApprove"),html`<p class="note">${machineText("stepApproveHint")}</p>`)}
329
+ </ol><aside class=${`runtime-pair-card${awaitingApproval ? " is-pending" : ""}`}><label><span>${machineText("pairingCode")}</span><div class="runtime-pair-code-row"><input class="runtime-pair-code" value=${pairing.code} readOnly=${true} spellcheck=${false} autocomplete="off" /><button type="button" class="btn btn-sm" onClick=${copyCode}><${Icon} name="copy" size=${15} />${machineText(copied ? "codeCopied" : "copyCode")}</button></div></label><p class="note">${machineText("expires",machineTime(pairing.expiresAt))}</p><p class="runtime-pair-status" role="status"><span class="windows-cli-dot"></span>${machineText(awaitingApproval ? "pairingApprove" : "pairingWait")}</p><div class="runtime-pair-actions"><button type="button" class="btn btn-sm" disabled=${!!busy} onClick=${connect}>${machineText("newCode")}</button><button type="button" class="btn btn-ghost btn-sm" onClick=${() => setPairing(null)}>${machineText("closeCode")}</button></div></aside></div></section>` : null}
330
+ <${RuntimeSwitches} lease=${lease} onChanged=${state.refresh} computers=${computers} />
331
+ ${diagnosticMachineId !== null ? html`<${RuntimeDiagnosticsDialog} key=${diagnosticMachineId || "local"} navKey=${navKey} machineId=${diagnosticMachineId} machine=${target} enabled=${diagnosticMachineId !== "" || windowsCli} drafts=${drafts.current} session=${taskSessions.current.get(diagnosticMachineId)} taskLease=${lease} onChanged=${state.refresh} onClose=${()=>setDiagnosticMachineId(null)} />` : null}
332
+ <p class="note runtime-operations-note">${machineText("operationsNote")}</p>
230
333
  </section>`;
231
334
  }