@songsid/agend 2.3.4-beta.6 → 2.3.5-alpha.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 (386) hide show
  1. package/README.md +2 -0
  2. package/README.zh-TW.md +2 -0
  3. package/THIRD_PARTY_NOTICES.md +53 -0
  4. package/dist/agent-endpoint.d.ts +5 -1
  5. package/dist/agent-endpoint.js +12 -2
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/backend/claude-code.d.ts +15 -12
  8. package/dist/backend/claude-code.js +137 -133
  9. package/dist/backend/claude-code.js.map +1 -1
  10. package/dist/backend/codex-package.d.ts +5 -2
  11. package/dist/backend/codex-package.js +1 -5
  12. package/dist/backend/codex-package.js.map +1 -1
  13. package/dist/backend/codex.d.ts +136 -113
  14. package/dist/backend/codex.js +1271 -1219
  15. package/dist/backend/codex.js.map +1 -1
  16. package/dist/backend/grok-model-cache.d.ts +7 -0
  17. package/dist/backend/grok-model-cache.js +65 -0
  18. package/dist/backend/grok-model-cache.js.map +1 -0
  19. package/dist/backend/grok.d.ts +2 -0
  20. package/dist/backend/grok.js +22 -0
  21. package/dist/backend/grok.js.map +1 -1
  22. package/dist/backend/native-pane.d.ts +9 -0
  23. package/dist/backend/native-pane.js +59 -0
  24. package/dist/backend/native-pane.js.map +1 -0
  25. package/dist/backend/types.d.ts +7 -0
  26. package/dist/backend/types.js.map +1 -1
  27. package/dist/channel/ipc-bridge.d.ts +2 -0
  28. package/dist/channel/mcp-tools.js +1 -1
  29. package/dist/channel/mcp-tools.js.map +1 -1
  30. package/dist/cli.js +6 -2
  31. package/dist/cli.js.map +1 -1
  32. package/dist/config-validator.js +59 -0
  33. package/dist/config-validator.js.map +1 -1
  34. package/dist/config.js +16 -0
  35. package/dist/config.js.map +1 -1
  36. package/dist/daemon.d.ts +62 -5
  37. package/dist/daemon.js +749 -267
  38. package/dist/daemon.js.map +1 -1
  39. package/dist/delivery-consumed-watch.d.ts +15 -3
  40. package/dist/delivery-consumed-watch.js +23 -1
  41. package/dist/delivery-consumed-watch.js.map +1 -1
  42. package/dist/delivery-outbox.d.ts +5 -0
  43. package/dist/delivery-outbox.js +32 -4
  44. package/dist/delivery-outbox.js.map +1 -1
  45. package/dist/delivery-reconciliation.d.ts +2 -35
  46. package/dist/delivery-reconciliation.js +2 -156
  47. package/dist/delivery-reconciliation.js.map +1 -1
  48. package/dist/fleet-context.d.ts +2 -2
  49. package/dist/fleet-level-config.d.ts +1 -1
  50. package/dist/fleet-level-config.js +2 -0
  51. package/dist/fleet-level-config.js.map +1 -1
  52. package/dist/fleet-manager.d.ts +125 -2
  53. package/dist/fleet-manager.js +1157 -27
  54. package/dist/fleet-manager.js.map +1 -1
  55. package/dist/fleet-windows-runtime.d.ts +92 -0
  56. package/dist/fleet-windows-runtime.js +373 -0
  57. package/dist/fleet-windows-runtime.js.map +1 -0
  58. package/dist/instance-config-impact.js +4 -0
  59. package/dist/instance-config-impact.js.map +1 -1
  60. package/dist/instance-lifecycle.d.ts +20 -3
  61. package/dist/instance-lifecycle.js +358 -156
  62. package/dist/instance-lifecycle.js.map +1 -1
  63. package/dist/local-pane-endpoint.d.ts +46 -0
  64. package/dist/local-pane-endpoint.js +189 -0
  65. package/dist/local-pane-endpoint.js.map +1 -0
  66. package/dist/local-pane-launcher.d.ts +2 -0
  67. package/dist/local-pane-launcher.js +38 -0
  68. package/dist/local-pane-launcher.js.map +1 -0
  69. package/dist/local-pane-stage.ps1 +101 -0
  70. package/dist/locale.js +34 -0
  71. package/dist/locale.js.map +1 -1
  72. package/dist/model-catalog-defaults.d.ts +14 -0
  73. package/dist/model-catalog-defaults.js +20 -0
  74. package/dist/model-catalog-defaults.js.map +1 -0
  75. package/dist/native-agent-admission.d.ts +7 -0
  76. package/dist/native-agent-admission.js +26 -0
  77. package/dist/native-agent-admission.js.map +1 -0
  78. package/dist/native-daemon-client.d.ts +13 -0
  79. package/dist/native-daemon-client.js +28 -0
  80. package/dist/native-daemon-client.js.map +1 -0
  81. package/dist/native-instance-api.d.ts +14 -0
  82. package/dist/native-instance-api.js +205 -0
  83. package/dist/native-instance-api.js.map +1 -0
  84. package/dist/native-instance-input.d.ts +84 -0
  85. package/dist/native-instance-input.js +54 -0
  86. package/dist/native-instance-input.js.map +1 -0
  87. package/dist/native-instance-registry.d.ts +32 -0
  88. package/dist/native-instance-registry.js +105 -0
  89. package/dist/native-instance-registry.js.map +1 -0
  90. package/dist/native-mcp.d.ts +40 -0
  91. package/dist/native-mcp.js +278 -0
  92. package/dist/native-mcp.js.map +1 -0
  93. package/dist/native-model-catalog.d.ts +5 -0
  94. package/dist/native-model-catalog.js +76 -0
  95. package/dist/native-model-catalog.js.map +1 -0
  96. package/dist/native-pane-crash-budget.d.ts +20 -0
  97. package/dist/native-pane-crash-budget.js +60 -0
  98. package/dist/native-pane-crash-budget.js.map +1 -0
  99. package/dist/native-pane-create.d.ts +26 -0
  100. package/dist/native-pane-create.js +53 -0
  101. package/dist/native-pane-create.js.map +1 -0
  102. package/dist/native-pane-lifecycle.d.ts +65 -0
  103. package/dist/native-pane-lifecycle.js +227 -0
  104. package/dist/native-pane-lifecycle.js.map +1 -0
  105. package/dist/native-pane-manager.d.ts +58 -0
  106. package/dist/native-pane-manager.js +563 -0
  107. package/dist/native-pane-manager.js.map +1 -0
  108. package/dist/native-pane-output-log.d.ts +15 -0
  109. package/dist/native-pane-output-log.js +108 -0
  110. package/dist/native-pane-output-log.js.map +1 -0
  111. package/dist/native-pane-reconciliation.d.ts +4 -0
  112. package/dist/native-pane-reconciliation.js +40 -0
  113. package/dist/native-pane-reconciliation.js.map +1 -0
  114. package/dist/native-pane-recovery.d.ts +12 -0
  115. package/dist/native-pane-recovery.js +10 -0
  116. package/dist/native-pane-recovery.js.map +1 -0
  117. package/dist/native-pane-tools.d.ts +2 -0
  118. package/dist/native-pane-tools.js +14 -0
  119. package/dist/native-pane-tools.js.map +1 -0
  120. package/dist/needs-you-hub.d.ts +2 -0
  121. package/dist/needs-you-hub.js +1 -0
  122. package/dist/needs-you-hub.js.map +1 -1
  123. package/dist/needs-you.d.ts +12 -1
  124. package/dist/needs-you.js +8 -1
  125. package/dist/needs-you.js.map +1 -1
  126. package/dist/outbound-handlers.d.ts +16 -2
  127. package/dist/outbound-handlers.js +67 -3
  128. package/dist/outbound-handlers.js.map +1 -1
  129. package/dist/outbound-schemas.d.ts +38 -0
  130. package/dist/outbound-schemas.js +12 -0
  131. package/dist/outbound-schemas.js.map +1 -1
  132. package/dist/pane-port.d.ts +107 -0
  133. package/dist/pane-port.js +2 -0
  134. package/dist/pane-port.js.map +1 -0
  135. package/dist/runtime-api.d.ts +8 -0
  136. package/dist/runtime-api.js +96 -0
  137. package/dist/runtime-api.js.map +1 -0
  138. package/dist/runtime-assets/@xterm/addon-serialize/LICENSE +21 -0
  139. package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.js +2 -0
  140. package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.mjs +19 -0
  141. package/dist/runtime-assets/@xterm/addon-serialize/package.json +29 -0
  142. package/dist/runtime-assets/@xterm/headless/LICENSE +21 -0
  143. package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.js +2 -0
  144. package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.mjs +46 -0
  145. package/dist/runtime-assets/@xterm/headless/package.json +29 -0
  146. package/dist/runtime-assets/node-pty/CONPTY-LICENSE +21 -0
  147. package/dist/runtime-assets/node-pty/CONPTY-NOTICE.md +503 -0
  148. package/dist/runtime-assets/node-pty/LICENSE +69 -0
  149. package/dist/runtime-assets/node-pty/lib/conpty_console_list_agent.js +16 -0
  150. package/dist/runtime-assets/node-pty/lib/eventEmitter2.js +47 -0
  151. package/dist/runtime-assets/node-pty/lib/index.js +52 -0
  152. package/dist/runtime-assets/node-pty/lib/interfaces.js +7 -0
  153. package/dist/runtime-assets/node-pty/lib/shared/conout.js +11 -0
  154. package/dist/runtime-assets/node-pty/lib/terminal.js +190 -0
  155. package/dist/runtime-assets/node-pty/lib/types.js +7 -0
  156. package/dist/runtime-assets/node-pty/lib/unixTerminal.js +346 -0
  157. package/dist/runtime-assets/node-pty/lib/utils.js +39 -0
  158. package/dist/runtime-assets/node-pty/lib/windowsConoutConnection.js +125 -0
  159. package/dist/runtime-assets/node-pty/lib/windowsPtyAgent.js +320 -0
  160. package/dist/runtime-assets/node-pty/lib/windowsTerminal.js +199 -0
  161. package/dist/runtime-assets/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  162. package/dist/runtime-assets/node-pty/package.json +64 -0
  163. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  164. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  165. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  166. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  167. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  168. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  169. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  170. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  171. package/dist/runtime-bundle.d.ts +17 -0
  172. package/dist/runtime-bundle.js +147 -0
  173. package/dist/runtime-bundle.js.map +1 -0
  174. package/dist/runtime-cli.d.ts +2 -0
  175. package/dist/runtime-cli.js +117 -0
  176. package/dist/runtime-cli.js.map +1 -0
  177. package/dist/runtime-client-store.d.ts +49 -0
  178. package/dist/runtime-client-store.js +292 -0
  179. package/dist/runtime-client-store.js.map +1 -0
  180. package/dist/runtime-client.d.ts +100 -0
  181. package/dist/runtime-client.js +700 -0
  182. package/dist/runtime-client.js.map +1 -0
  183. package/dist/runtime-coordinator.d.ts +82 -0
  184. package/dist/runtime-coordinator.js +864 -0
  185. package/dist/runtime-coordinator.js.map +1 -0
  186. package/dist/runtime-pane-assets.d.ts +17 -0
  187. package/dist/runtime-pane-assets.js +23 -0
  188. package/dist/runtime-pane-assets.js.map +1 -0
  189. package/dist/runtime-pane-boot.d.ts +2 -0
  190. package/dist/runtime-pane-boot.js +24 -0
  191. package/dist/runtime-pane-boot.js.map +1 -0
  192. package/dist/runtime-pane-codex-index.d.ts +1 -0
  193. package/dist/runtime-pane-codex-index.js +47 -0
  194. package/dist/runtime-pane-codex-index.js.map +1 -0
  195. package/dist/runtime-pane-config.d.ts +16 -0
  196. package/dist/runtime-pane-config.js +54 -0
  197. package/dist/runtime-pane-config.js.map +1 -0
  198. package/dist/runtime-pane-host.d.ts +43 -0
  199. package/dist/runtime-pane-host.js +403 -0
  200. package/dist/runtime-pane-host.js.map +1 -0
  201. package/dist/runtime-pane-job-owner.d.ts +1 -0
  202. package/dist/runtime-pane-job-owner.js +29 -0
  203. package/dist/runtime-pane-job-owner.js.map +1 -0
  204. package/dist/runtime-pane-job.ps1 +116 -0
  205. package/dist/runtime-pane-link.d.ts +40 -0
  206. package/dist/runtime-pane-link.js +246 -0
  207. package/dist/runtime-pane-link.js.map +1 -0
  208. package/dist/runtime-pane-mcp-protocol.d.ts +32 -0
  209. package/dist/runtime-pane-mcp-protocol.js +39 -0
  210. package/dist/runtime-pane-mcp-protocol.js.map +1 -0
  211. package/dist/runtime-pane-mcp-relay.d.ts +17 -0
  212. package/dist/runtime-pane-mcp-relay.js +70 -0
  213. package/dist/runtime-pane-mcp-relay.js.map +1 -0
  214. package/dist/runtime-pane-mcp.d.ts +43 -0
  215. package/dist/runtime-pane-mcp.js +137 -0
  216. package/dist/runtime-pane-mcp.js.map +1 -0
  217. package/dist/runtime-pane-native.d.ts +45 -0
  218. package/dist/runtime-pane-native.js +65 -0
  219. package/dist/runtime-pane-native.js.map +1 -0
  220. package/dist/runtime-pane-output.d.ts +19 -0
  221. package/dist/runtime-pane-output.js +53 -0
  222. package/dist/runtime-pane-output.js.map +1 -0
  223. package/dist/runtime-pane-port.d.ts +134 -0
  224. package/dist/runtime-pane-port.js +537 -0
  225. package/dist/runtime-pane-port.js.map +1 -0
  226. package/dist/runtime-pane-private.d.ts +14 -0
  227. package/dist/runtime-pane-private.js +51 -0
  228. package/dist/runtime-pane-private.js.map +1 -0
  229. package/dist/runtime-pane-private.ps1 +14 -0
  230. package/dist/runtime-pane-process.d.ts +4 -0
  231. package/dist/runtime-pane-process.js +108 -0
  232. package/dist/runtime-pane-process.js.map +1 -0
  233. package/dist/runtime-pane-protocol.d.ts +169 -0
  234. package/dist/runtime-pane-protocol.js +189 -0
  235. package/dist/runtime-pane-protocol.js.map +1 -0
  236. package/dist/runtime-pane-screen.d.ts +19 -0
  237. package/dist/runtime-pane-screen.js +76 -0
  238. package/dist/runtime-pane-screen.js.map +1 -0
  239. package/dist/runtime-pane-service.d.ts +11 -0
  240. package/dist/runtime-pane-service.js +221 -0
  241. package/dist/runtime-pane-service.js.map +1 -0
  242. package/dist/runtime-pane-stdio-protocol.d.ts +54 -0
  243. package/dist/runtime-pane-stdio-protocol.js +94 -0
  244. package/dist/runtime-pane-stdio-protocol.js.map +1 -0
  245. package/dist/runtime-pane-stdio.d.ts +3 -0
  246. package/dist/runtime-pane-stdio.js +123 -0
  247. package/dist/runtime-pane-stdio.js.map +1 -0
  248. package/dist/runtime-pane-store.d.ts +33 -0
  249. package/dist/runtime-pane-store.js +164 -0
  250. package/dist/runtime-pane-store.js.map +1 -0
  251. package/dist/runtime-pane-transcript-protocol.d.ts +43 -0
  252. package/dist/runtime-pane-transcript-protocol.js +58 -0
  253. package/dist/runtime-pane-transcript-protocol.js.map +1 -0
  254. package/dist/runtime-pane-transcript.d.ts +45 -0
  255. package/dist/runtime-pane-transcript.js +605 -0
  256. package/dist/runtime-pane-transcript.js.map +1 -0
  257. package/dist/runtime-pane-worker.d.ts +1 -0
  258. package/dist/runtime-pane-worker.js +154 -0
  259. package/dist/runtime-pane-worker.js.map +1 -0
  260. package/dist/runtime-protocol.d.ts +149 -0
  261. package/dist/runtime-protocol.js +19 -0
  262. package/dist/runtime-protocol.js.map +1 -0
  263. package/dist/runtime-switches.d.ts +79 -0
  264. package/dist/runtime-switches.js +90 -0
  265. package/dist/runtime-switches.js.map +1 -0
  266. package/dist/runtime-transcript-delta-reader.d.ts +23 -0
  267. package/dist/runtime-transcript-delta-reader.js +74 -0
  268. package/dist/runtime-transcript-delta-reader.js.map +1 -0
  269. package/dist/runtime-transcript-source.d.ts +36 -0
  270. package/dist/runtime-transcript-source.js +189 -0
  271. package/dist/runtime-transcript-source.js.map +1 -0
  272. package/dist/runtime-windows-bridge.d.ts +41 -0
  273. package/dist/runtime-windows-bridge.js +157 -0
  274. package/dist/runtime-windows-bridge.js.map +1 -0
  275. package/dist/settings-api.js +58 -0
  276. package/dist/settings-api.js.map +1 -1
  277. package/dist/settings-change.js +28 -0
  278. package/dist/settings-change.js.map +1 -1
  279. package/dist/settings-confirmation.d.ts +15 -1
  280. package/dist/settings-confirmation.js +46 -9
  281. package/dist/settings-confirmation.js.map +1 -1
  282. package/dist/settings-effect.js +23 -1
  283. package/dist/settings-effect.js.map +1 -1
  284. package/dist/settings-http-confirmation.js +11 -2
  285. package/dist/settings-http-confirmation.js.map +1 -1
  286. package/dist/tmux-control.d.ts +2 -1
  287. package/dist/tmux-control.js.map +1 -1
  288. package/dist/tmux-manager.d.ts +2 -1
  289. package/dist/tmux-manager.js.map +1 -1
  290. package/dist/tmux-pane-host.d.ts +26 -0
  291. package/dist/tmux-pane-host.js +25 -0
  292. package/dist/tmux-pane-host.js.map +1 -0
  293. package/dist/topic-commands.js +3 -0
  294. package/dist/topic-commands.js.map +1 -1
  295. package/dist/transcript-delta-reader.d.ts +1 -1
  296. package/dist/transcript-delta-reader.js +1 -1
  297. package/dist/transcript-delta-reader.js.map +1 -1
  298. package/dist/transcript-monitor.d.ts +3 -0
  299. package/dist/transcript-monitor.js +22 -46
  300. package/dist/transcript-monitor.js.map +1 -1
  301. package/dist/transcript-needles.d.ts +35 -0
  302. package/dist/transcript-needles.js +158 -0
  303. package/dist/transcript-needles.js.map +1 -0
  304. package/dist/transcript-records.d.ts +21 -0
  305. package/dist/transcript-records.js +140 -0
  306. package/dist/transcript-records.js.map +1 -0
  307. package/dist/transcript-sources.d.ts +5 -0
  308. package/dist/transcript-sources.js +8 -79
  309. package/dist/transcript-sources.js.map +1 -1
  310. package/dist/types.d.ts +21 -0
  311. package/dist/ui/agent-proposals.js +90 -0
  312. package/dist/ui/agent-runtime.js +106 -0
  313. package/dist/ui/chat-store.js +13 -2
  314. package/dist/ui/chat-strings.js +48 -0
  315. package/dist/ui/cli-picker.js +42 -0
  316. package/dist/ui/native-instance-settings.js +47 -0
  317. package/dist/ui/native-pane-recovery.js +45 -0
  318. package/dist/ui/panel-chat.js +61 -32
  319. package/dist/ui/panel-details.js +2 -1
  320. package/dist/ui/panel-fleet.js +99 -18
  321. package/dist/ui/panel-needs.js +152 -3
  322. package/dist/ui/panel-runtimes.js +231 -0
  323. package/dist/ui/panel-settings.js +14 -8
  324. package/dist/ui/panel-windows-agent.js +86 -0
  325. package/dist/ui/panel-windows-cli.js +143 -0
  326. package/dist/ui/provider-marks.js +83 -0
  327. package/dist/ui/runtime-catalog.js +55 -0
  328. package/dist/ui/settings-catalog.js +37 -10
  329. package/dist/ui/settings-confirm.js +3 -1
  330. package/dist/ui/settings-dialogs.js +14 -5
  331. package/dist/ui/settings-registry.js +5 -0
  332. package/dist/ui/settings-strings.js +14 -0
  333. package/dist/ui/shared/app-i18n.js +2 -2
  334. package/dist/ui/shared/app-route.js +6 -2
  335. package/dist/ui/shared/app-shell.js +3 -2
  336. package/dist/ui/shared/app-store.js +1 -0
  337. package/dist/ui/shared/app-stream.js +3 -1
  338. package/dist/ui/shared/app.css +301 -0
  339. package/dist/ui/shared/app.js +4 -0
  340. package/dist/ui/shared/header-tools.js +6 -4
  341. package/dist/ui/shared/instance-nav.js +20 -4
  342. package/dist/ui/shared/instance-switch.js +4 -3
  343. package/dist/ui/shared/ui-menu.js +28 -8
  344. package/dist/ui/shared/view-strings.js +6 -0
  345. package/dist/ui/windows-agents-nav.js +2 -0
  346. package/dist/ui/windows-cli-catalog.js +79 -0
  347. package/dist/update-check.d.ts +8 -2
  348. package/dist/update-check.js +5 -2
  349. package/dist/update-check.js.map +1 -1
  350. package/dist/web-api.d.ts +25 -0
  351. package/dist/web-api.js +171 -6
  352. package/dist/web-api.js.map +1 -1
  353. package/dist/web-chat-history.d.ts +19 -2
  354. package/dist/web-chat-history.js +72 -3
  355. package/dist/web-chat-history.js.map +1 -1
  356. package/dist/web-shell-routes.d.ts +4 -1
  357. package/dist/web-shell-routes.js +7 -2
  358. package/dist/web-shell-routes.js.map +1 -1
  359. package/dist/windows-agents.d.ts +112 -0
  360. package/dist/windows-agents.js +463 -0
  361. package/dist/windows-agents.js.map +1 -0
  362. package/dist/windows-cli-api.d.ts +23 -0
  363. package/dist/windows-cli-api.js +158 -0
  364. package/dist/windows-cli-api.js.map +1 -0
  365. package/dist/windows-cli-providers.d.ts +115 -0
  366. package/dist/windows-cli-providers.js +429 -0
  367. package/dist/windows-cli-providers.js.map +1 -0
  368. package/dist/windows-cli-worker.d.ts +58 -0
  369. package/dist/windows-cli-worker.js +479 -0
  370. package/dist/windows-cli-worker.js.map +1 -0
  371. package/dist/windows-cli.d.ts +115 -0
  372. package/dist/windows-cli.js +402 -0
  373. package/dist/windows-cli.js.map +1 -0
  374. package/dist/windows-native-mcp.d.ts +19 -0
  375. package/dist/windows-native-mcp.js +143 -0
  376. package/dist/windows-native-mcp.js.map +1 -0
  377. package/dist/windows-pane-mcp.d.ts +26 -0
  378. package/dist/windows-pane-mcp.js +187 -0
  379. package/dist/windows-pane-mcp.js.map +1 -0
  380. package/package.json +9 -3
  381. package/templates/runtime-assets/conpty-LICENSE +21 -0
  382. package/templates/runtime-assets/conpty-NOTICE.md +503 -0
  383. package/templates/runtime-assets/local-pane-stage.ps1 +101 -0
  384. package/templates/runtime-assets/runtime-pane-job.ps1 +116 -0
  385. package/templates/runtime-assets/runtime-pane-private.ps1 +14 -0
  386. package/templates/runtime-assets/xterm-LICENSE +21 -0
@@ -9,6 +9,9 @@
9
9
  // - Acknowledge on a delivery (POST /ui/needs/ack), which clears it everywhere;
10
10
  // - Open: that instance's chat.
11
11
  // Something resolved anywhere (Discord, Telegram, the web) leaves the list by itself.
12
+ // alpha.2 (M-2300): an agent's Windows request waiting for a web-only fleet's owner is the app store's `proposals`, read
13
+ // by agent-proposals.js (owner only, on the stream's `agent_proposals` ping) — Approve / Reject POST its decision. Only
14
+ // on a tab opened before an upgrade (app.js never installed it) does this panel install it, once.
12
15
  import { html, useEffect, useState } from "/assets/app-html.js";
13
16
  import { t, register } from "/assets/app-i18n.js";
14
17
  import { appStore, createStore, useStore } from "/assets/app-store.js";
@@ -21,6 +24,7 @@ import { Icon } from "/assets/ui-icons.js";
21
24
  import { toast } from "/assets/ui-toast.js";
22
25
  import { notifyStore, notifySupport, configDetail, reasonText, refreshNotify, setNotify } from "/assets/app-needs.js";
23
26
  import { openSignIn, signInOffered } from "./backend-signin.js";
27
+ import { decideProposal, installProposals } from "./agent-proposals.js";
24
28
 
25
29
  register("needs", {
26
30
  en: {
@@ -35,6 +39,18 @@ register("needs", {
35
39
  notifyInsecure: "Notifications need a secure address (localhost or the HTTPS public link), not a plain-HTTP LAN address.",
36
40
  notifyUnsupported: "This browser cannot show notifications.", turnOn: "Turn on", turnOff: "Turn off",
37
41
  typePrompt: "Fleet prompt", typeAwaiting: "At its terminal", typeInstance: "Instance", typeDelivery: "Delivery",
42
+ proposals: "Agent requests", proposalFrom: "Request from {0}", proposalHint: "Nothing is created or changed until you approve. When you do, AgEnD checks the request, the target machine and your sign-in again, then runs it once.",
43
+ approve: "Approve", reject: "Reject", deciding: "Working…", expiresIn: "expires in {0} min", expiresSoon: "expires in under a minute",
44
+ applying: "Being applied…", openAgent: "Open the new agent's chat", decided: "Decided here", failedReason: "Reason: {0}",
45
+ f_runtime: "Runs on", f_machine: "Machine", f_thisPc: "This PC (Windows)", f_topic_name: "Name", f_backend: "CLI", f_model: "Model", f_directory: "Folder",
46
+ f_sandbox: "Access", f_description: "Description", f_display_name: "Display name", f_tags: "Tags", sb_read_only: "read-only", sb_workspace_write: "can write in its folder",
47
+ createWindows: "Create a Windows agent", changeAgent: "Change {0}",
48
+ r_applied: "Approved: the change is applied.", r_rejected: "Rejected: nothing was changed.", r_withdrawn: "Withdrawn by the agent: nothing was changed.",
49
+ r_expired: "This request ran out before it was decided: nothing was changed.", r_fleet_stopped: "The fleet stopped before it was applied: nothing was changed.",
50
+ r_request_no_longer_current: "The request is no longer current (the fleet or the Windows switch changed): nothing was changed.",
51
+ r_settings_changed: "The settings changed since the request: nothing was changed.", r_apply_failed: "It could not be applied.",
52
+ e_owner_required: "Only this fleet's owner, signed in on the AgEnD computer itself, can decide an agent's request.",
53
+ e_confirmation_not_pending: "This request was already decided or ran out.", e_not_found: "This request is no longer here.",
38
54
  },
39
55
  "zh-TW": {
40
56
  title: "等你處理", empty: "目前沒有需要你處理的事", emptyHint: "agent 在等你時,會出現在這裡——也會出現在聊天 app 的 General。", emptyHintWeb: "agent 在等你時,會出現在這裡。",
@@ -48,6 +64,18 @@ register("needs", {
48
64
  notifyInsecure: "通知需要安全的網址(localhost 或 HTTPS 公開連結),區網的一般 HTTP 網址不行。",
49
65
  notifyUnsupported: "這個瀏覽器無法顯示通知。", turnOn: "開啟", turnOff: "關閉",
50
66
  typePrompt: "Fleet 提示", typeAwaiting: "在終端機上", typeInstance: "Instance", typeDelivery: "傳送",
67
+ proposals: "代理人的請求", proposalFrom: "{0} 的請求", proposalHint: "在你核准之前,不會建立或修改任何東西。核准時,AgEnD 會再次檢查這個請求、目標電腦和你的登入,然後只執行一次。",
68
+ approve: "核准", reject: "拒絕", deciding: "處理中…", expiresIn: "{0} 分鐘後過期", expiresSoon: "不到一分鐘後過期",
69
+ applying: "正在套用…", openAgent: "開啟新 agent 的聊天", decided: "已在這裡決定", failedReason: "原因:{0}",
70
+ f_runtime: "執行於", f_machine: "電腦", f_thisPc: "這台電腦(Windows)", f_topic_name: "名稱", f_backend: "CLI", f_model: "模型", f_directory: "資料夾",
71
+ f_sandbox: "存取權限", f_description: "說明", f_display_name: "顯示名稱", f_tags: "標籤", sb_read_only: "唯讀", sb_workspace_write: "可寫入它的資料夾",
72
+ createWindows: "建立 Windows agent", changeAgent: "修改 {0}",
73
+ r_applied: "已核准:變更已套用。", r_rejected: "已拒絕:沒有任何變更。", r_withdrawn: "agent 已撤回:沒有任何變更。",
74
+ r_expired: "這個請求在決定前就過期了:沒有任何變更。", r_fleet_stopped: "套用前 fleet 已停止:沒有任何變更。",
75
+ r_request_no_longer_current: "這個請求已不再有效(fleet 或 Windows 開關已改變):沒有任何變更。",
76
+ r_settings_changed: "提出請求後設定已變更:沒有任何變更。", r_apply_failed: "無法套用。",
77
+ e_owner_required: "只有在 AgEnD 這台電腦上登入的 fleet 擁有者,才能決定 agent 的請求。",
78
+ e_confirmation_not_pending: "這個請求已經被決定或已過期。", e_not_found: "這個請求已不在這裡。",
51
79
  },
52
80
  });
53
81
  const tn = (k, ...v) => t(`needs.${k}`, ...v);
@@ -123,9 +151,21 @@ export function groupNeeds(items) {
123
151
  return [...groups.values()];
124
152
  }
125
153
 
154
+ /**
155
+ * alpha.2 (M-2300): what this page decided, kept for the life of the page — the request's id → "busy" while its POST is
156
+ * out, then the server's answer ({ view, agent, message }): it stays shown after the list drops the request.
157
+ */
158
+ export const decisions = createStore({});
159
+
126
160
  export function NeedsPanel({ navKey }) {
127
161
  const lease = useLease(navKey);
128
- const { needs, chatOwner: owner, instances, connections } = useStore(appStore);
162
+ const { needs, chatOwner: owner, instances, connections, proposals } = useStore(appStore);
163
+ // A tab opened before an upgrade has this panel but not the app.js that installs the list (it never set `proposals`):
164
+ // install it here then. On a current page app.js already did, and this panel reads nothing itself (#1374).
165
+ useEffect(() => { if (appStore.get().proposals === undefined) installProposals(); }, []);
166
+ const asks = Array.isArray(proposals) ? proposals : [];
167
+ const done = useStore(decisions);
168
+ const decidedHere = Object.entries(done).filter(([id, d]) => d && d !== "busy" && !asks.some((p) => p.id === id));
129
169
  // #1854: a fleet with no chat connection (web only, or not known yet) has no General to point at.
130
170
  const chatApp = connections > 0;
131
171
  const names = useStore(namesStore);
@@ -141,9 +181,14 @@ export function NeedsPanel({ navKey }) {
141
181
  const groups = groupNeeds(items);
142
182
  const nowMs = Date.now();
143
183
  return html`<div data-effect-panel="needs" class="panel p-needs">
144
- <${PanelHeader} title=${tn("title")} sub=${items.length ? t("app.needsCount", items.length) : null} />
184
+ <${PanelHeader} title=${tn("title")} sub=${items.length + asks.length ? t("app.needsCount", items.length + asks.length) : null} />
145
185
  <div data-trust="panel-content" class="panel-body"><div class="col">
146
- ${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn(chatApp ? "emptyHint" : "emptyHintWeb")} />`
186
+ ${asks.length || decidedHere.length ? html`<section class="n-group n-proposals" aria-label=${tn("proposals")}>
187
+ <h2 class="n-group-head">${tn("proposals")}</h2>
188
+ ${asks.map((p) => html`<${ProposalItem} key=${p.id} proposal=${p} nowMs=${nowMs} />`)}
189
+ ${decidedHere.map(([id, d]) => html`<${ProposalResult} key=${id} result=${d} />`)}
190
+ </section>` : null}
191
+ ${!items.length && !asks.length && !decidedHere.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn(chatApp ? "emptyHint" : "emptyHintWeb")} />`
147
192
  : groups.map((g) => html`<section key=${JSON.stringify([g.instance, g.items[0]?.type === "config"])} class="n-group" aria-label=${g.instance}>
148
193
  <h2 class="n-group-head">${g.instance ? html`<a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a>` : g.items[0]?.type === "config" ? t("app.settings") : tn(g.items.some(i => i.type === "backend") ? "fleetSignIns" : "noAssignee")}</h2>
149
194
  ${g.items.map((item) => html`<${NeedsItem} key=${item.id} item=${item} nowMs=${nowMs} owner=${owner} />`)}
@@ -210,6 +255,110 @@ function NeedsItem({ item, nowMs, owner }) {
210
255
  </article>`;
211
256
  }
212
257
 
258
+ /**
259
+ * alpha.2 (M-2300): the request's summary as the owner reads it (junsion's §6: the machine, the CLI, the model, the
260
+ * folder, the access). The server's lines are the settings diff, escaped for a chat's markdown ("new\_instance.directory:
261
+ * absent → C:\\work"): here, unescaped and named — a create's fields as "Folder: C:\work", "Machine: This PC" when it
262
+ * names none; a change's as "Model: a → b"; any other line as it is. Text only (rendered as text, never markup).
263
+ */
264
+ export function proposalLines(summary) {
265
+ const unescape = (s) => String(s).replace(/\\([\\`*_~[\]()<>|])/g, "$1").replace(/\[at\]/g, "@");
266
+ const lines = (Array.isArray(summary) ? summary : []).map(unescape);
267
+ const create = lines.some((l) => /^POST \/ui\/instances$/.test(l));
268
+ const out = [], label = (k) => { const key = `f_${k}`, s = tn(key); return s === `needs.${key}` ? k : s; };
269
+ // The access field's values, in words (matched by value: the two the request's schema allows).
270
+ const ACCESS = { "read-only": "sb_read_only", "workspace-write": "sb_workspace_write" };
271
+ const value = (_k, v) => (Object.hasOwn(ACCESS, v) ? tn(ACCESS[v]) : v);
272
+ let machine = false;
273
+ for (const l of lines) {
274
+ if (/^POST \/ui\/instances$/.test(l)) continue;
275
+ const m = /^new_instance\.([A-Za-z_]+): absent → (.*)$/.exec(l);
276
+ if (m) { if (m[1] === "machineId") machine = true; out.push(`${label(m[1] === "machineId" ? "machine" : m[1])}: ${value(m[1], m[2])}`); continue; }
277
+ const ch = /^(\S+) ([A-Za-z_]+): (.*) → (.*)$/.exec(l);
278
+ if (ch) { out.push(`${tn("changeAgent", ch[1])} · ${label(ch[2])}: ${ch[3]} → ${ch[4]}`); continue; }
279
+ out.push(l);
280
+ }
281
+ if (create && !machine) out.splice(1, 0, `${label("machine")}: ${tn("f_thisPc")}`);
282
+ return create ? [tn("createWindows"), ...out] : out;
283
+ }
284
+
285
+ /**
286
+ * alpha.2 polish: a decided request's outcome in the PAGE's language (the server says it in the fleet's — an English page
287
+ * read 「變更已套用。」): by its reason code, else its state; the server's words only for a code this page does not know.
288
+ */
289
+ export function outcomeText(outcome) {
290
+ if (!outcome) return t("app.failed");
291
+ for (const code of [outcome.reason_code, outcome.state]) {
292
+ if (typeof code !== "string") continue;
293
+ const key = `r_${code}`, s = tn(key);
294
+ if (s !== `needs.${key}`) return s;
295
+ }
296
+ return outcome.message || t("app.failed");
297
+ }
298
+ /** Why a click decided nothing, in the page's language (by the server's error code; its words only as a fallback). */
299
+ export function decideErrorText(r) {
300
+ const key = r && r.error ? `e_${r.error}` : "";
301
+ const s = key ? tn(key) : "";
302
+ return key && s !== `needs.${key}` ? s : (r && r.message) || t("app.failed");
303
+ }
304
+
305
+ /**
306
+ * alpha.2 (M-2300): one agent's request waiting for this web-only fleet's owner — what it would do (the server's own
307
+ * summary: the machine, the CLI, the model, the folder, the access), when it runs out, and Approve / Reject. One click
308
+ * per request on this page (claimed before the POST); the answer is the server's: applied, rejected, run out, already
309
+ * decided elsewhere, or failed with its reason.
310
+ */
311
+ function ProposalItem({ proposal: p, nowMs }) {
312
+ const d = useStore(decisions)[p.id];
313
+ const busy = d === "busy" || p.state === "applying";
314
+ const [failed, setFailed] = useState(null);
315
+ const decide = async (decision) => {
316
+ if (decisions.get()[p.id]) return; // one POST in flight per request on this page
317
+ setFailed(null);
318
+ decisions.set((s) => ({ ...s, [p.id]: "busy" }));
319
+ const r = await decideProposal(p.id, decision);
320
+ // #2997 review (Prism P2): nothing decided (the network failed, the owner was refused, a bad answer) and the request
321
+ // still waits — the claim is released and why is said here, so the next click is taken; a decided one is kept.
322
+ const decided = r.view && r.view.state !== "pending";
323
+ if (!decided) {
324
+ decisions.set((s) => { const next = { ...s }; delete next[p.id]; return next; });
325
+ setFailed(decideErrorText(r));
326
+ return;
327
+ }
328
+ decisions.set((s) => ({ ...s, [p.id]: { ...r, decision } }));
329
+ toast(r.view.outcome ? outcomeText(r.view.outcome) : decideErrorText(r), r.view.state === "applied");
330
+ };
331
+ const minutes = Math.floor((p.remaining_ms || 0) / 60_000);
332
+ return html`<article data-trust="permission" class="n-item">
333
+ <span class="n-icon"><${Icon} name="alert" size=${18} /></span>
334
+ <div class="n-body">
335
+ <div class="n-title"><strong>${tn("proposalFrom", p.requested_by)}</strong><span class="n-age">${age(p.requested_at, nowMs)} · ${minutes >= 1 ? tn("expiresIn", minutes) : tn("expiresSoon")}</span></div>
336
+ <ul class="n-detail">${proposalLines(p.summary).map((line, i) => html`<li key=${i}>${line}</li>`)}</ul>
337
+ <p class="note">${tn("proposalHint")}</p>
338
+ ${failed ? html`<p class="note n-proposal-error" role="alert">${failed}</p>` : null}
339
+ <div class="n-actions">
340
+ <button type="button" class="btn btn-sm btn-primary" disabled=${busy} onClick=${() => decide("confirm")}>${busy ? (p.state === "applying" ? tn("applying") : tn("deciding")) : tn("approve")}</button>
341
+ <button type="button" class="btn btn-sm" disabled=${busy} onClick=${() => decide("reject")}>${tn("reject")}</button>
342
+ </div>
343
+ </div>
344
+ </article>`;
345
+ }
346
+
347
+ /** A request decided on this page: the server's outcome (and its reason), and the new agent's chat when one was created. */
348
+ function ProposalResult({ result: r }) {
349
+ const v = r.view, outcome = v && v.outcome;
350
+ const ok = !!(v && v.state === "applied");
351
+ return html`<article data-trust="permission" class=${`n-item${ok || (v && v.state === "rejected") ? "" : " bad"}`}>
352
+ <span class="n-icon"><${Icon} name=${ok ? "check" : "alert"} size=${18} /></span>
353
+ <div class="n-body">
354
+ <div class="n-title"><strong>${outcome ? outcomeText(outcome) : decideErrorText(r)}</strong><span class="n-age">${tn("decided")}</span></div>
355
+ ${v ? html`<ul class="n-detail">${proposalLines(v.summary).map((line, i) => html`<li key=${i}>${line}</li>`)}</ul>` : null}
356
+ ${outcome && outcome.detail ? html`<p class="note">${tn("failedReason", outcome.detail)}</p>` : null}
357
+ ${ok && r.agent ? html`<div class="n-actions"><a class="btn btn-sm btn-primary" href=${chatPath(r.agent)} title=${r.agent}>${tn("openAgent")}</a></div>` : null}
358
+ </div>
359
+ </article>`;
360
+ }
361
+
213
362
  /** The device's choice, as the page knows it (app-needs.js notifyStore): every mounted toggle shows the same. */
214
363
  function NotifyToggle() {
215
364
  const support = notifySupport();
@@ -0,0 +1,231 @@
1
+ import { html, useEffect, useRef, useState } from "/assets/app-html.js";
2
+ import { register, t } from "/assets/app-i18n.js";
3
+ import { useLease } from "/assets/app-ctx.js";
4
+ import { Icon } from "/assets/ui-icons.js";
5
+ import { appStore, useStore } from "/assets/app-store.js";
6
+ import { confirmDialog } from "/assets/ui-confirm.js";
7
+ import { confirmedWrite } from "./settings-confirm.js";
8
+ import { CliProductMark, cliLabel } from "./cli-picker.js";
9
+ import { WindowsCliPanel } from "./panel-windows-cli.js";
10
+ import { useRuntimeCatalog } from "./runtime-catalog.js";
11
+
12
+ register("machines", {
13
+ en: {
14
+ title:"Runtime", lead:"Connect computers to run agents with each computer's own CLIs, sign-ins and files.",
15
+ connect:"Connect a computer", download:"Download Windows Runtime (ZIP)", downloadHint:"Download here, then transfer the ZIP to the Windows computer and extract it. Windows Node.js is required.", refresh:"Refresh machines", loading:"Loading machines…", empty:"No remote computers connected yet.",
16
+ disabled:"Other Windows PCs can connect once “Allow other Windows PCs to pair” is on.", local:"This computer", localHint:"WSL / Linux agents run on the AgEnD host. Native Windows diagnostics are below when enabled.",
17
+ localWindows:"Local Windows CLI diagnostics", online:"Online", offline:"Offline", pending:"Awaiting approval", revoked:"Revoked", unknown:"Not reported",
18
+ lastSeen:"Last seen: {0}", inventoryAt:"CLI inventory: {0}", noInventory:"CLI inventory has not been reported yet.", noClis:"No installed CLIs reported.",
19
+ approve:"Approve computer", revoke:"Revoke connection", rename:"Rename", save:"Save name", cancel:"Cancel", name:"Computer name", probe:"Refresh CLI inventory",
20
+ 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
+ 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
+ machines:"Connected computers", cli:"CLI", version:"Runtime version", platform:"Platform",
26
+ 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.",
27
+ windowsCli:"Use this PC's Windows CLIs", windowsCliHelp:"Agents can run the CLIs installed on Windows on this PC, such as Codex or Claude Code, with your Windows sign-ins. Windows Node.js is required.",
28
+ remoteRuntime:"Allow other Windows PCs to pair", remoteRuntimeHelp:"This computer stays your fleet. Another Windows PC runs only a small runner, not a fleet; once you approve it here, agents can use that PC's CLIs and files. That PC must reach this fleet at an HTTPS address, as the guide below explains.",
29
+ remoteRuntimeDocs:"Full guide", setByEnv:"Set by the environment ({0}={1}). To change it, remove it from AgEnD's environment and restart AgEnD.",
30
+ switchPending:"Waiting for a fleet admin to confirm. It turns on once they do.", switchFailed:"Could not change this setting: {0}", switchesUnavailable:"These switches can be changed only on this fleet's own dashboard.",
31
+ windowsCliOffAsk:"Turn off this PC's Windows CLIs? A Windows agent turn in progress on this PC stops, and these agents stay stopped until you turn it on again.",
32
+ remoteRuntimeOffAsk:"Stop accepting other Windows PCs? Paired PCs disconnect now and their agents stay stopped until you turn it on again. Approved PCs are kept.",
33
+ limit:"Agents at once on each Windows PC", limitHelp:"Each Windows PC, this one or a paired one, runs at most this many agents at the same time ({0}–{1}, default {2}). This limit applies to persistent terminal agents. Per-message execution keeps its existing admission limit.", limitSave:"Save", limitSaved:"Saved.",
34
+ guide:"How another PC connects", guideAddress:"1. Give this fleet an HTTPS address the other PC can reach. Recommended: Tailscale Serve, run on this computer (on Windows, if AgEnD runs in WSL). Then use the https://….ts.net address it prints:",
35
+ guideSsh:"Or: an SSH tunnel, opened on the other PC (replace user@this-computer with your SSH sign-in for this computer). Then use http://localhost:{0}:", guideFunnel:"Never use Tailscale Funnel: it puts this fleet on the internet.",
36
+ guideLan:"A plain http:// address on your network (such as http://192.168.1.20) is refused: it would send the pairing code unencrypted.",
37
+ guidePair:"2. Click Connect a computer, download the Runtime ZIP, and run the command it shows on the other PC.", guideApprove:"3. When that PC asks to join, approve it here.",
38
+ turnOff:"Turn off",
39
+ },
40
+ "zh-TW": {
41
+ title:"Runtime", lead:"連接電腦,讓 Agent 使用各台電腦上的 CLI、登入與檔案。",
42
+ connect:"連接電腦", download:"下載 Windows Runtime(ZIP)", downloadHint:"下載後,將 ZIP 傳到要執行 CLI 的 Windows 電腦並解壓縮。需要 Windows Node.js。", refresh:"更新機器清單", loading:"正在載入機器…", empty:"尚未連接其他電腦。",
43
+ disabled:"開啟「允許其他 Windows 電腦配對」後,其他 Windows 電腦才能連接。", local:"這台電腦", localHint:"WSL / Linux Agent 在 AgEnD 主機上執行。啟用後可在下方查看本機 Windows 診斷。",
44
+ localWindows:"本機 Windows CLI 診斷", online:"在線", offline:"離線", pending:"等待核准", revoked:"已撤銷", unknown:"尚未回報",
45
+ lastSeen:"最近連線:{0}", inventoryAt:"CLI 清單更新:{0}", noInventory:"尚未回報 CLI 清單。", noClis:"尚未回報已安裝的 CLI。",
46
+ approve:"核准電腦", revoke:"撤銷連線", rename:"重新命名", save:"儲存名稱", cancel:"取消", name:"電腦名稱", probe:"更新 CLI 清單",
47
+ 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
+ failed:"無法更新 Runtime", cliFailed:"CLI 更新失敗,目前仍顯示上次回報的清單。", retry:"重試", seenCached:"目前顯示上次成功載入的機器清單。",
52
+ machines:"已連接的電腦", cli:"CLI", version:"Runtime 版本", platform:"平台",
53
+ switches:"這個 Fleet 可以使用", switchesNote:"開啟時,和其他存取設定一樣需要 Fleet 管理員確認;關閉不需 Fleet 管理員確認,你在這裡確認後就會立即生效。",
54
+ windowsCli:"使用這台電腦 Windows 上的 CLI", windowsCliHelp:"Agent 可以使用這台電腦 Windows 上安裝的 CLI(例如 Codex 或 Claude Code),沿用你在 Windows 的登入。需要 Windows Node.js。",
55
+ remoteRuntime:"允許其他 Windows 電腦配對", remoteRuntimeHelp:"這台電腦仍是你的 Fleet。另一台 Windows 電腦只執行一個小 runner,不是 Fleet;你在這裡核准後,Agent 就能使用那台電腦的 CLI 與檔案。那台電腦必須能用 HTTPS 位址連到這個 Fleet,做法見下方說明。",
56
+ remoteRuntimeDocs:"完整說明", setByEnv:"由環境變數設定({0}={1})。若要變更,請從 AgEnD 的環境移除它並重新啟動 AgEnD。",
57
+ switchPending:"等待 Fleet 管理員確認,確認後就會開啟。", switchFailed:"無法變更此設定:{0}", switchesUnavailable:"這些開關只能在這個 Fleet 自己的儀表板上變更。",
58
+ windowsCliOffAsk:"要關閉這台電腦的 Windows CLI 嗎?這台電腦上進行中的 Windows Agent 回合會停止,這些 Agent 會保持停止,直到你再次開啟。",
59
+ remoteRuntimeOffAsk:"要停止接受其他 Windows 電腦嗎?已配對的電腦會立即中斷連線,它們的 Agent 會保持停止,直到你再次開啟。已核准的電腦會保留。",
60
+ limit:"每台 Windows 電腦同時執行的 Agent 數", limitHelp:"每台 Windows 電腦(這台或已配對的電腦)最多同時執行這麼多個 Agent({0}–{1},預設 {2})。此限制適用於持續執行的終端 Agent。每則訊息各自執行的模式維持原本的執行數限制。", limitSave:"儲存", limitSaved:"已儲存。",
61
+ guide:"另一台電腦如何連線", guideAddress:"1. 讓另一台電腦能用 HTTPS 位址連到這個 Fleet。建議在這台電腦上執行 Tailscale Serve(若 AgEnD 在 WSL 中執行,請在 Windows 上執行),再使用它顯示的 https://….ts.net 位址:",
62
+ guideSsh:"或:在另一台電腦上開啟 SSH 通道(把 user@this-computer 換成你登入這台電腦的 SSH 帳號),再使用 http://localhost:{0}:", guideFunnel:"請勿使用 Tailscale Funnel:它會把這個 Fleet 公開到網際網路。",
63
+ guideLan:"網路上的一般 http:// 位址(例如 http://192.168.1.20)會被拒絕:配對碼會以未加密的方式傳送。",
64
+ guidePair:"2. 按「連接電腦」,下載 Runtime ZIP,在另一台電腦執行頁面顯示的指令。", guideApprove:"3. 那台電腦要求加入時,在這裡核准它。",
65
+ turnOff:"關閉此功能",
66
+ },
67
+ });
68
+ export const machineText = (key,...values) => t(`machines.${key}`,...values);
69
+ /** Only an origin composed of inert hostname/port characters is placed in a copyable shell command. */
70
+ export function runtimeConnectCommand(server) {
71
+ try {
72
+ const url = new URL(server);
73
+ const local = ["localhost","127.0.0.1","[::1]"].includes(url.hostname);
74
+ if (!/^([a-z0-9.-]+|\[[a-f0-9:]+\])$/i.test(url.hostname) || url.username || url.password || url.search || url.hash || url.pathname !== "/"
75
+ || !(url.protocol === "https:" || url.protocol === "http:" && local)) return "";
76
+ return `.\\agend-runtime.cmd connect --server "${url.origin}"`;
77
+ } catch { return ""; }
78
+ }
79
+ 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);
82
+ 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>
92
+ </article>`;
93
+ }
94
+ /** M-2255: the switches (fleet.yaml `runtimes`); `env` names the variable that overrides each. */
95
+ const SWITCHES = [
96
+ { name: "windows_cli", id: "runtimeWindowsCli", env: "AGEND_WINDOWS_CLI", label: "windowsCli" },
97
+ { name: "remote_runtime", id: "runtimeRemoteRuntime", env: "AGEND_REMOTE_RUNTIME", label: "remoteRuntime" },
98
+ ];
99
+ export const RUNTIME_DOCS = "https://github.com/songsid/AgEnD/blob/main/docs/remote-runtime.md#connecting-over-a-private-network";
100
+ /**
101
+ * M-2255 (the user, hmsg-71c25c9b): "Use this PC's Windows CLIs" and "Allow other Windows computers to pair", saved in
102
+ * fleet.yaml like any setting: on is confirmed by a fleet admin, off applies at once (after asking here). A switch the
103
+ * environment sets is shown as such and cannot be changed here.
104
+ */
105
+ function RuntimeSwitches({ lease, onChanged }) {
106
+ const [view,setView]=useState(null), [busy,setBusy]=useState(""), [pending,setPending]=useState(""), [error,setError]=useState(""), [unavailable,setUnavailable]=useState(false);
107
+ const [limit,setLimit]=useState(""), [limitNote,setLimitNote]=useState("");
108
+ // A number being typed is the person's: a reload (a switch's answer) never replaces it; a save of it does.
109
+ const limitEdited=useRef(false);
110
+ const switches=view?.switches;
111
+ const load=async() => {
112
+ const response=await lease.fetch("/api/settings/runtimes",{credentials:"same-origin"});
113
+ if (!lease.current()) return;
114
+ if (response.status === 403) { setUnavailable(true); return; }
115
+ const value=await response.json().catch(() => ({}));
116
+ if (!response.ok || !value.switches) throw new Error(value.error || `HTTP ${response.status}`);
117
+ if (!lease.current()) return;
118
+ setView(value);
119
+ if (!limitEdited.current) setLimit(String(value.max_agents_per_machine?.value ?? ""));
120
+ };
121
+ useEffect(() => { load().catch(e => { if (lease.current()) setError(machineText("switchFailed", e.message)); }); }, [lease]);
122
+ const change=async(item,on) => {
123
+ if (busy) return;
124
+ if (!on && !await confirmDialog({message:machineText(`${item.label}OffAsk`),confirmLabel:machineText("turnOff"),danger:true})) return;
125
+ if (!lease.current()) return;
126
+ setBusy(item.name);setError("");
127
+ try {
128
+ const res=await confirmedWrite("/api/settings/fleet/runtimes",{method:"PUT",body:{[item.name]:on},label:machineText(item.label),onPending:() => { if (lease.current()) setPending(item.name); }});
129
+ if (!lease.current()) return;
130
+ if (!res.ok) setError(machineText("switchFailed",(res.body && res.body.error) || String(res.status)));
131
+ await load();
132
+ if (lease.current()) onChanged?.();
133
+ } catch (e) { if (lease.current()) setError(machineText("switchFailed",e.message)); }
134
+ finally { if (lease.current()) { setBusy("");setPending(""); } }
135
+ };
136
+ const bounds=view?.max_agents_per_machine;
137
+ const limitValue=Number(limit), limitValid=!!bounds && Number.isInteger(limitValue) && limitValue >= bounds.min && limitValue <= bounds.max;
138
+ const saveLimit=async(event) => {
139
+ event.preventDefault();
140
+ if (busy || !limitValid || limitValue === bounds.value) return;
141
+ setBusy("limit");setError("");setLimitNote("");
142
+ try {
143
+ const res=await confirmedWrite("/api/settings/fleet/runtimes",{method:"PUT",body:{max_agents_per_machine:limitValue},label:machineText("limit")});
144
+ if (!lease.current()) return;
145
+ if (res.ok) { limitEdited.current=false; setLimitNote(machineText("limitSaved")); } else setError(machineText("switchFailed",(res.body && res.body.error) || String(res.status)));
146
+ await load();
147
+ } catch (e) { if (lease.current()) setError(machineText("switchFailed",e.message)); }
148
+ finally { if (lease.current()) setBusy(""); }
149
+ };
150
+ const origin=view?.local_origin || "http://127.0.0.1:19280", port=(() => { try { return new URL(origin).port || "80"; } catch { return "19280"; } })();
151
+ // Commands, not words: the same in every language (built here, never translated).
152
+ 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}>
154
+ <summary>${machineText("guide")}</summary>
155
+ <p>${machineText("guideAddress")}</p><pre><code>${serveCommand}</code></pre>
156
+ <p>${machineText("guideSsh",port)}</p><pre><code>${sshCommand}</code></pre>
157
+ <p class="runtime-guide-warn">${machineText("guideFunnel")}</p><p class="runtime-guide-warn">${machineText("guideLan")}</p>
158
+ <p>${machineText("guidePair")}</p><p>${machineText("guideApprove")}</p>
159
+ <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")}>
163
+ <h3>${machineText("switches")}</h3>
164
+ ${SWITCHES.map(item => {
165
+ const state=switches?.[item.name];
166
+ return html`<div key=${item.name} class="runtime-switch" data-switch=${item.name}>
167
+ <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`}
169
+ disabled=${!state || !!state.environment || !!busy} onChange=${e => { const want=e.target.checked; e.target.checked=!want; void change(item,want); }} />
170
+ <span><strong>${machineText(item.label)}</strong><small id=${`${item.id}Help`}>${machineText(`${item.label}Help`)}</small></span>
171
+ </label>
172
+ ${pending === item.name ? html`<p class="note runtime-switch-pending" role="status">${machineText("switchPending")}</p>` : null}
173
+ ${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
+ </div>`;
176
+ })}
177
+ <form class="runtime-limit" onSubmit=${saveLimit}>
178
+ <label for="runtimeMaxAgents"><strong>${machineText("limit")}</strong><small id="runtimeMaxAgentsHelp">${bounds ? machineText("limitHelp",bounds.min,bounds.max,bounds.default) : ""}</small></label>
179
+ <div><input id="runtimeMaxAgents" type="number" inputMode="numeric" min=${bounds?.min ?? 1} max=${bounds?.max ?? 16} step="1" value=${limit} aria-describedby="runtimeMaxAgentsHelp"
180
+ disabled=${!bounds || !!busy} onInput=${e => { limitEdited.current=true; setLimit(e.target.value); setLimitNote(""); }} />
181
+ <button type="submit" class="btn btn-sm" disabled=${!limitValid || limitValue === bounds?.value || !!busy}>${machineText("limitSave")}</button>
182
+ ${limitNote ? html`<span class="note" role="status">${limitNote}</span>` : null}</div>
183
+ </form>
184
+ <p class="note">${machineText("switchesNote")}</p>
185
+ ${error ? html`<p class="runtime-error" role="alert">${error}</p>` : null}
186
+ </section>`;
187
+ }
188
+ export function RuntimesPanel({navKey}) {
189
+ const lease=useLease(`runtimes:${navKey}`), state=useRuntimeCatalog(), {windowsCli}=useStore(appStore);
190
+ const [pairing,setPairing]=useState(null), [busy,setBusy]=useState(""), [error,setError]=useState("");
191
+ const [server,setServer]=useState(() => typeof location === "undefined" ? "" : location.origin);
192
+ useEffect(() => {
193
+ if (!pairing) return;
194
+ const remaining = new Date(pairing.expiresAt).getTime()-Date.now();
195
+ if (!Number.isFinite(remaining) || remaining <= 0) {setPairing(null);return;}
196
+ const timer=lease.timeout(() => setPairing(null),remaining);
197
+ return () => lease.clear(timer);
198
+ },[lease,pairing]);
199
+ const post=async(path,body={}) => {
200
+ const response=await lease.fetch(path,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)});
201
+ const value=await response.json();
202
+ if (!response.ok) throw new Error(value.error || `HTTP ${response.status}`);
203
+ return value;
204
+ };
205
+ const connect=async() => {
206
+ setBusy("pairing");setError("");
207
+ try {const value=await post("/ui/runtimes/pairing");if(lease.current())setPairing(value);}catch(e){if(lease.current())setError(e.message);}finally{if(lease.current())setBusy("");}
208
+ };
209
+ const action=async(machine,operation,body={}) => {
210
+ if(operation === "revoke" && !await confirmDialog({message:machineText("revokeAsk"),confirmLabel:machineText("revoke"),danger:true}))return false;
211
+ if(!lease.current())return false;
212
+ 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();}
214
+ catch(e){if(lease.current())setError(operation === "refresh" ? `${machineText("cliFailed")} ${e.message}` : e.message);return false;}
215
+ finally{if(lease.current())setBusy("");}
216
+ };
217
+ const machines=state.data?.machines || [];
218
+ 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>`;
220
+ 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} />
223
+ ${state.data?.enabled === false ? html`<p class="note">${machineText("disabled")}</p>` : null}
224
+ ${state.loading && !state.data ? html`<p role="status" aria-busy="true"><span class="out-spin" aria-hidden="true"></span> ${machineText("loading")}</p>` : null}
225
+ ${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}
230
+ </section>`;
231
+ }
@@ -39,6 +39,8 @@ import {
39
39
  } from "./settings-dialogs.js";
40
40
  import { SetupWizard } from "./settings-wizard.js";
41
41
  import { CreateInstanceDialog } from "./panel-fleet.js";
42
+ import { RuntimesPanel } from "./panel-runtimes.js";
43
+ import { NativeInstanceSettings } from "./native-instance-settings.js";
42
44
  import { FirstRunCard, takeWizardRequest } from "./first-run.js";
43
45
  import { takeAgentRequest } from "./settings-request.js";
44
46
  // #1604: This device — the preview opt-in (one path: the app's confirm, then preview.js) and the tour, from the chat.
@@ -60,7 +62,7 @@ const APPLY_ERRORS = new Set(["unsupported_sensitive_effect", "confirmation_diff
60
62
  "request_no_longer_current", "too_many_pending_changes", "fleet_unavailable", "invalid_settings_effect", "settings_confirmation_unavailable", "failed"]);
61
63
  // #2726: a code or words of the setup host (the wizard's commit) in the page's language; anything else as it came.
62
64
  export const applyErrorText = (code) => (APPLY_ERRORS.has(code) ? tn(`applyError_${code}`) : tn("applyErrorOther", hostText(String(code), lang())));
63
- const ICONS = { preferences: "user", look: "palette", agents: "bot", "agent-defaults": "sliders", chat: "chat", "web-dashboard": "globe", access: "key", advanced: "code" };
65
+ const ICONS = { preferences: "user", look: "palette", agents: "bot", runtimes: "view", "agent-defaults": "sliders", chat: "chat", "web-dashboard": "globe", access: "key", advanced: "code" };
64
66
  const RELEASES = "https://github.com/songsid/AgEnD/releases";
65
67
 
66
68
  /** GET under the panel's lease → { ok, status, body }. */
@@ -103,7 +105,7 @@ async function loadLive(lease) {
103
105
  if (!lease.current()) return null;
104
106
  const live = {}, classicLive = {};
105
107
  if (f.ok && f.body && Array.isArray(f.body.instances)) for (const i of f.body.instances) {
106
- live[i.name] = { status: i.status, state: i.state, backend: i.backend, model: i.model, model_display: modelText(i, i.model_display), effort: i.effort, effort_supported: i.effort_supported };
108
+ live[i.name] = { status: i.status, state: i.state, backend: i.backend, model: i.model, model_display: modelText(i, i.model_display), effort: i.effort, effort_supported: i.effort_supported, runtime:i.runtime, capabilities:i.capabilities };
107
109
  if (i.classic) classicLive[i.name] = { status: i.status, state: i.state };
108
110
  }
109
111
  // /api/fleet carries live state for ClassicBot rooms; the profiles are the fallback for one not listed there yet.
@@ -379,6 +381,7 @@ export function SettingsPanel({ route, navKey }) {
379
381
  // defaults, Web) is one form with one draft, shown a part at a time.
380
382
  const sectionBody = section === "preferences" ? html`<${DeviceSection} key=${you.key} /><${AppearanceSection} ...${you} part="device" />`
381
383
  : section === "look" ? html`<${AppearanceSection} ...${you} part="look" />`
384
+ : section === "runtimes" ? html`<${RuntimesPanel} navKey=${navKey} />`
382
385
  : !data ? html`<${Skeleton} lines=${6} />`
383
386
  : data.error ? html`<${ErrorState} message=${tn("configLoadFailed")} onRetry=${reload} />`
384
387
  : section === "agents" ? html`<${YamlLock} staged=${staged}><${Agents} ctx=${ctx} search=${search} openDialog=${toDialog} /></${YamlLock}>${ctx.providerOk ? html`<${ProviderKeys} ctx=${ctx} />` : null}`
@@ -667,7 +670,7 @@ const opBusy = (op) => !!op && (["writing", "posting", "watching"].includes(op.p
667
670
  function dropSecrets(staged) { for (const c of staged.values()) if (c.connectionSecret) c.connectionSecret.secret = ""; }
668
671
 
669
672
  function renderDialog(d, ctx, close) {
670
- if (d.kind === "agent") return html`<${AgentDialog} name=${d.name} inst=${d.inst} ctx=${ctx} onClose=${close} />`;
673
+ if (d.kind === "agent") return d.inst.runtime === "windows" ? html`<${NativeInstanceSettings} name=${d.name} inst=${d.inst} ctx=${ctx} onClose=${close} />` : html`<${AgentDialog} name=${d.name} inst=${d.inst} ctx=${ctx} onClose=${close} />`;
671
674
  if (d.kind === "bot") return html`<${BotDialog} key=${d.id} id=${d.id} ctx=${ctx} onClose=${close} />`;
672
675
  if (d.kind === "newBot") return html`<${NewBotDialog} ctx=${ctx} onClose=${close} />`;
673
676
  if (d.kind === "classic") return html`<${ClassicDialog} room=${d.room} ctx=${ctx} onClose=${close} />`;
@@ -701,16 +704,17 @@ function makeCtx(data, setData, stage, unstage, reload, reloadLive, alive, extra
701
704
  });
702
705
  stage(`agent:${name}`, {
703
706
  label: tn("updateAgent", shortName(name, { ...inst, ...patch })),
704
- impact: batchImpact(schema, Object.keys(patch), (k) => impactOf(schema, `instance.${k}`)),
705
- request: { method: "PATCH", url: `/api/settings/fleet/instances/${encodeURIComponent(name)}`, body: patch },
707
+ impact: inst.runtime === "windows" ? "now" : batchImpact(schema, Object.keys(patch), (k) => impactOf(schema, `instance.${k}`)),
708
+ request: { method: "PATCH", url: inst.runtime === "windows" ? `/ui/instances/${encodeURIComponent(name)}` : `/api/settings/fleet/instances/${encodeURIComponent(name)}`, body: patch },
706
709
  });
707
710
  },
708
711
  async deleteAgent(name) {
709
712
  if (!(await ask(tn("deleteAgent", name), { confirmLabel: tn("deleteAgentMenu"), danger: true }))) return;
710
713
  if (!alive()) return; // the panel went while it asked: nothing staged, nothing said
714
+ const native = fleet.instances?.[name]?.runtime === "windows";
711
715
  edit((d) => { delete d.fleet.instances[name]; });
712
- stage(`agent:${name}`, { label: tn("deleteAgentLabel", name), impact: impactOf(schema, "instance.delete"),
713
- request: { method: "DELETE", url: `/api/settings/fleet/instances/${encodeURIComponent(name)}` } });
716
+ stage(`agent:${name}`, { label: tn("deleteAgentLabel", name), impact: native ? "now" : impactOf(schema, "instance.delete"),
717
+ request: { method: "DELETE", url: native ? `/ui/instances/${encodeURIComponent(name)}` : `/api/settings/fleet/instances/${encodeURIComponent(name)}` } });
714
718
  },
715
719
  stageChannels(key, nextChannels, meta) {
716
720
  edit((d) => { d.fleet.channels = nextChannels; delete d.fleet.channel; });
@@ -791,12 +795,14 @@ function Agents({ ctx, search, openDialog }) {
791
795
  return html`<div key=${name} class="s-row">
792
796
  <span class=${dotClass(st)} title=${tn(STATUS_KEY[st] || "stStopped")} aria-hidden="true"></span>
793
797
  <span class="s-name" title=${name}>${shortName(name, inst)}</span>
798
+ ${inst.runtime === "windows" ? html`<span class="tag">Windows</span>` : null}
794
799
  <span class="s-meta" title=${sum.backend}>${sum.model}</span>
795
800
  <span class="s-br" aria-hidden="true"></span>
796
- <span class="tag">${tn("effortTag", sum.effort)}</span>
801
+ ${inst.runtime !== "windows" || ctx.live[name]?.capabilities?.effort === true ? html`<span class="tag">${tn("effortTag", sum.effort)}</span>` : null}
797
802
  ${ch ? html`<span class=${`tag${ch.includes("persona") ? " persona" : ""}`}>${ch}</span>` : null}
798
803
  <span class="sr-only">${tn(STATUS_KEY[st] || "stStopped")}</span>
799
804
  <span class="s-actions">
805
+ ${inst.runtime === "windows" ? html`<a class="btn btn-sm" href=${chatPath(name)}>${t("app.chat")}</a>` : null}
800
806
  <button type="button" class="btn btn-sm" disabled=${working} onClick=${() => openDialog({ kind: "agent", name, inst })}>${tn("settingsButton")}</button>
801
807
  ${st === "running" ? html`<button type="button" class="btn btn-sm" disabled=${working} onClick=${() => run(name, "pause", () => ctx.pauseWake(name, "pause"))}>${label("pause", tn("pause"))}</button>` : null}
802
808
  ${st === "paused" ? html`<button type="button" class="btn btn-sm" disabled=${working} onClick=${() => run(name, "wake", () => ctx.pauseWake(name, "wake"))}>${label("wake", tn("wake"))}</button>` : null}