@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
@@ -0,0 +1,47 @@
1
+ // Windows uses the same staged instance settings flow, with its runtime's supported fields.
2
+ import { html, useState } from "/assets/app-html.js";
3
+ import { t, register } from "/assets/app-i18n.js";
4
+ import { Dialog } from "/assets/ui-dialog.js";
5
+ import { ChipList, ModelSelect } from "./settings-dialogs.js";
6
+ import { CliProductMark, cliLabel } from "./cli-picker.js";
7
+ import { useWindowsCliCatalog } from "./windows-cli-catalog.js";
8
+ import { useRuntimeCatalog, instanceMachineId } from "./runtime-catalog.js";
9
+ import { NativeAgendToolsHint, tr } from "./agent-runtime.js";
10
+ register("nativeInstance", {
11
+ "zh-TW": { fixed:"Runtime 與 CLI 隨此對話保留。", workspace:"此 Agent 的對話會沿用這個工作區。" },
12
+ en: { fixed:"This conversation keeps its runtime and CLI.", workspace:"This agent's conversation keeps this workspace." },
13
+ });
14
+ const tn = key => t(`settings.${key}`);
15
+ export function NativeInstanceSettings({ name, inst, ctx, onClose }) {
16
+ const machineId = instanceMachineId(inst);
17
+ const catalog = useWindowsCliCatalog(!machineId), machines = useRuntimeCatalog(!!machineId);
18
+ const machine = machines.data?.machines?.find(item => item.id === machineId);
19
+ const native = (machineId ? machine?.inventory : catalog.data)?.clis?.find(cli => cli.id === inst.backend);
20
+ const [form,setForm] = useState(() => ({display_name:inst.display_name || name,description:inst.description || "",model:inst.model || "",systemPrompt:inst.systemPrompt || "",native_sandbox:inst.native_sandbox || "read-only",tags:Array.isArray(inst.tags) ? [...inst.tags] : []}));
21
+ const set = key => value => setForm(current => ({...current,[key]:value}));
22
+ const ready = !!form.display_name.trim() && !(form.native_sandbox === "read-only" && native?.readOnlySupport === false);
23
+ const stage = () => {
24
+ if (!ready) return;
25
+ const patch = {};
26
+ for (const [key,value] of Object.entries(form)) {
27
+ const before = key === "display_name" ? inst.display_name || name : key === "native_sandbox" ? inst.native_sandbox || "read-only" : key === "tags" ? inst.tags || [] : inst[key] || "";
28
+ const next = typeof value === "string" ? value.trim() : value;
29
+ if (JSON.stringify(next) !== JSON.stringify(before)) patch[key] = next;
30
+ }
31
+ if (Object.keys(patch).length) ctx.stageAgent(name,inst,patch);
32
+ onClose();
33
+ };
34
+ return html`<${Dialog} title=${ctx.shortName(name,inst)} onClose=${onClose} wide=${true} actions=${html`<button type="button" class="btn" onClick=${onClose}>${tn("cancel")}</button><button type="button" class="btn btn-primary" disabled=${!ready} onClick=${stage}>${tn("modalDone")}</button>`}>
35
+ <div class="form native-instance-settings"><p class="note"><${CliProductMark} id=${inst.backend} size=${20} /> Windows · ${cliLabel(inst.backend)} — ${t("nativeInstance.fixed")}</p>${machineId ? html`<p class="note runtime-instance-machine">${tr("machine")}: ${machine?.name || inst.machine_name || machineId}</p>` : null}
36
+ <label class="field"><span>${tn("displayName")}</span><input value=${form.display_name} maxLength=${64} onInput=${event => set("display_name")(event.target.value)} /></label>
37
+ <label class="field"><span>${tn("description")}</span><input value=${form.description} maxLength=${512} onInput=${event => set("description")(event.target.value)} /></label>
38
+ <div class="field"><label for="native-instance-model">${tn("model")}</label><${ModelSelect} id="native-instance-model" value=${form.model} onChange=${set("model")} backend=${inst.backend} instance=${name} runtime="windows" machineId=${machineId} inherit=${tr("model")} /></div>
39
+ <label class="field"><span>${tn("workingDir")}</span><input value=${inst.working_directory || ""} readOnly=${true} aria-readonly="true" /><small>${t("nativeInstance.workspace")}</small></label>
40
+ <label class="field"><span>${tr("access")}</span><select value=${form.native_sandbox} onChange=${event => set("native_sandbox")(event.target.value)}><option value="read-only" disabled=${native?.readOnlySupport === false}>${tr("read")}</option><option value="workspace-write">${tr("write")}</option></select></label>
41
+ <${NativeAgendToolsHint} cli=${native} sandbox=${form.native_sandbox} />
42
+ ${!ready && form.native_sandbox === "read-only" && native?.readOnlySupport === false ? html`<p class="note">${tr("noReadOnly")}</p>` : null}
43
+ <label class="field"><span>${tn("systemPrompt")}</span><textarea rows="5" maxLength=${8000} value=${form.systemPrompt} onInput=${event => set("systemPrompt")(event.target.value)} /></label>
44
+ <div class="field"><span>${tn("tags")}</span><${ChipList} items=${form.tags} onChange=${set("tags")} placeholder=${tn("tagPlaceholder")} label=${tn("tags")} /></div>
45
+ </div>
46
+ </${Dialog}>`;
47
+ }
@@ -0,0 +1,45 @@
1
+ /** Retained-pane recovery uses an explicit admin attestation; it never resubmits a chat message. */
2
+ import { html, useEffect, useRef, useState } from "/assets/app-html.js";
3
+ import { t } from "/assets/app-i18n.js";
4
+ import { guestPage } from "/assets/app-store.js";
5
+ import { Dialog } from "/assets/ui-dialog.js";
6
+ import { toast } from "/assets/ui-toast.js";
7
+ import "./chat-strings.js";
8
+ export function NativePaneRecovery({ name, inst, lease }) {
9
+ const [dialog, setDialog] = useState(false), [checked, setChecked] = useState(false), [busy, setBusy] = useState(false);
10
+ const confirmation = useRef(null), inFlight = useRef(false), recovery = inst.native_recovery;
11
+ const latest = useRef(null); latest.current = {name,recovery};
12
+ useEffect(() => { confirmation.current=null; setDialog(false); setChecked(false); }, [recovery?.launchId, recovery?.sessionId, recovery?.kind]);
13
+ const act = async (path, body) => {
14
+ if (inFlight.current || !lease.current()) return;
15
+ inFlight.current = true; setBusy(true);
16
+ try {
17
+ const response = await lease.fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, ...(body ? { body: JSON.stringify(body) } : {}) });
18
+ const result = await response.json();
19
+ if (!lease.current()) return;
20
+ if (!response.ok || result.error) toast(result.error || t("chat.disconnected"), false);
21
+ else { setDialog(false); setChecked(false); }
22
+ } catch (error) { if (lease.current()) toast(error.message || t("chat.disconnected"), false); }
23
+ finally { inFlight.current = false; if (lease.current()) setBusy(false); }
24
+ };
25
+ if (guestPage() || inst.native_adapter !== "pane") return null;
26
+ if (!recovery) return ["stopped", "crashed"].includes(inst.status)
27
+ ? html`<div class="windows-agent-blocked"><button type="button" class="btn" disabled=${busy} onClick=${() => act(`/ui/start/${encodeURIComponent(name)}`)}>${t("chat.start")}</button></div>` : null;
28
+ if (recovery.kind === "session-unconfirmed") return html`<div class="windows-agent-blocked" role="alert">
29
+ <p>${t(recovery.provider === "codex" ? "chat.paneCodexSessionUnconfirmed" : "chat.paneSessionUnconfirmed")}</p>
30
+ <button type="button" class="btn" disabled=${busy || !recovery.launchId} onClick=${() => { confirmation.current = {name,launchId:recovery.launchId,sessionId:recovery.sessionId??null}; setDialog(true); }}>${t("chat.paneNewConversation")}</button>
31
+ ${dialog ? html`<${Dialog} title=${t("chat.paneNewConversation")} onClose=${() => { if (!busy) setDialog(false); }}>
32
+ <p>${t("chat.paneNewConversationConfirm",inst.display_name || name)}</p>
33
+ <button type="button" class="btn" disabled=${busy || confirmation.current?.name !== name || confirmation.current?.launchId !== recovery.launchId || confirmation.current?.sessionId !== (recovery.sessionId??null)} onClick=${() => { const target=confirmation.current, now=latest.current; if(target?.name===now.name && target.launchId===now.recovery?.launchId && target.sessionId===(now.recovery?.sessionId??null)) act(`/ui/instances/${encodeURIComponent(name)}/new-conversation`,{confirmedNewConversation:true,launchId:target.launchId,sessionId:target.sessionId}); }}>${t("chat.paneNewConversation")}</button>
34
+ </${Dialog}>` : null}
35
+ </div>`;
36
+ return html`<div class="windows-agent-blocked" role="alert"><p>${t(recovery.kind === "host-upgrade" ? "chat.paneHostUpgrade" : "chat.paneRecoveryNeeded")}</p>
37
+ ${recovery.kind === "host-upgrade" ? html`<button type="button" class="btn" disabled=${busy} onClick=${() => act(`/ui/stop/${encodeURIComponent(name)}`)}>${t("chat.stopInstance")}</button>` : recovery.launchId ? html`<button type="button" class="btn" disabled=${busy} onClick=${() => { confirmation.current = {name,launchId:recovery.launchId}; setChecked(false); setDialog(true); }}>${t("chat.paneRecover")}</button>` : null}
38
+ <button type="button" class="btn" disabled=${busy} onClick=${() => act(`/ui/start/${encodeURIComponent(name)}`)}>${t("chat.start")}</button>
39
+ ${dialog ? html`<${Dialog} title=${t("chat.paneRecover")} onClose=${() => { if (!busy) setDialog(false); }}>
40
+ <p>${t("chat.paneRecoverConfirm", inst.display_name || name)}</p>
41
+ <label><input type="checkbox" checked=${checked} disabled=${busy} onChange=${event => setChecked(event.target.checked)} />${t("chat.paneProcessesStopped")}</label>
42
+ <button type="button" class="btn" disabled=${!checked || busy || confirmation.current?.name !== name || confirmation.current?.launchId !== recovery.launchId} onClick=${() => { if (checked && confirmation.current?.name === name && confirmation.current?.launchId === recovery.launchId) act(`/ui/instances/${encodeURIComponent(name)}/recover-pane`, { confirmedStopped: true, launchId: confirmation.current.launchId }); }}>${t("chat.paneRecoverConfirmed")}</button>
43
+ </${Dialog}>` : null}
44
+ </div>`;
45
+ }
@@ -1,3 +1,4 @@
1
+ import { NativePaneRecovery } from "./native-pane-recovery.js";
1
2
  // #1408 step 1: the chat panel. Behind the session gate (/ui/js/), loaded by the app with a dynamic import in "full"
2
3
  // mode only. boot() runs once per page (the chat store must hear the stream from the first frame); <ChatPanel> mounts
3
4
  // for /ui and /ui/chat/<name>.
@@ -11,9 +12,9 @@ import { lang, t } from "/assets/app-i18n.js";
11
12
  import { appStore, createStore, guestPage, guestTrusted, useStore } from "/assets/app-store.js";
12
13
  import { ComposerAuth } from "/assets/web-authorize.js";
13
14
  import { useLease } from "/assets/app-ctx.js";
14
- import { modelText, namesOf, namesStore, navStore as instanceNavStore } from "/assets/instance-nav.js";
15
+ import { modelText, namesOf, namesStore, navStore as instanceNavStore, supportsInstanceFeature } from "/assets/instance-nav.js";
15
16
  import { PanelHeader, onPanelKey, setTitle, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
16
- import { detailsPath, textLinkSegments } from "/assets/app-route.js";
17
+ import { detailsPath, textLinkSegments, sectionPath } from "/assets/app-route.js";
17
18
  import { InstanceSwitch } from "/assets/instance-switch.js";
18
19
  import { HeaderTools } from "/assets/header-tools.js";
19
20
  import { navigate, navStore } from "/assets/app-nav.js";
@@ -29,7 +30,6 @@ import { applyTheme, clearAmbient, resolvedMode, themeFor, themeImageUrl, themeM
29
30
  import { colorMenuItem } from "/assets/identity-color.js";
30
31
  import { brokenPictures, cardFoldable, IdentityCard, useCard } from "/assets/identity-card.js";
31
32
  import { AgentWidget, instanceBinds, widgetFor } from "/assets/agent-widget.js";
32
-
33
33
  // #1629 r4 §1: the identity card under the chat's header — hidden by default; shown or not is this device's choice.
34
34
  // #2217 (the user, 2026-10-19): no row of its own; its picture and bio can be folded away (the banner stays) — this
35
35
  // device's choice too, kept the same way. The user (2026-10-20): no control on the card — one card button in the header
@@ -214,7 +214,7 @@ export function ChatPanel({ route, navKey }) {
214
214
  store.setCurrent(name && inst ? name : null);
215
215
  return () => store.setCurrent(null);
216
216
  }, [name, !!inst]);
217
- useEffect(() => { setTitle(name || t("app.chat")); }, [name, navKey]);
217
+ useEffect(() => { setTitle(inst?.runtime === "windows" ? inst.display_name || name : name || t("app.chat")); }, [name, navKey, inst?.runtime, inst?.display_name]);
218
218
  if (!name) {
219
219
  // #1854 (rank 7): one instruction — pick an agent, or with none at all, the one way to make one. Connecting a chat
220
220
  // app (the first-run card, #1519 P7) is offered in Settings, not here: it competed with the first agent.
@@ -259,6 +259,7 @@ function useResolvedMode(lease) {
259
259
  }
260
260
 
261
261
  function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
262
+ const attachmentsSupported = supportsInstanceFeature(inst,"attachments");
262
263
  const names = useStore(namesStore); // #1722: which name leads (the title)
263
264
  // #1629: this agent's theme, on this surface only; redrawn when the themes, the viewer's switch or the mode move.
264
265
  useStore(themesStore);
@@ -267,6 +268,17 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
267
268
  const themeKey = JSON.stringify(theme);
268
269
  const card = useCard(name, inst.card_v);
269
270
  const [dialog, setDialog] = useState(null); // "delete" | null
271
+ const [recovering,setRecovering] = useState(false);
272
+ const recoverNative = async () => {
273
+ if (recovering) return;
274
+ setRecovering(true);
275
+ try {
276
+ const response = await lease.fetch(`/ui/instances/${encodeURIComponent(name)}/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({confirmedStopped:true})});
277
+ const result = await response.json();
278
+ if (lease.current() && (!response.ok || result.error)) toast(result.error || t("chat.disconnected"),false);
279
+ } catch(error) { if (lease.current()) toast(error.message || t("chat.disconnected"),false); }
280
+ finally { if (lease.current()) setRecovering(false); }
281
+ };
270
282
  const [wrap, setWrap] = useState(codeWrap);
271
283
  const wide = useStore(chatWide).on;
272
284
  const view = useRef(null), split = useRef(null), thread = useRef(null), bg = useRef(null), effectSurface = useRef(null);
@@ -289,12 +301,14 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
289
301
  const el = view.current; if (!el) return undefined;
290
302
  let depth = 0;
291
303
  const hasFiles = (e) => !!(e.dataTransfer && [...e.dataTransfer.types].includes("Files"));
292
- lease.on(el, "dragenter", (e) => { if (hasFiles(e)) { depth++; el.classList.add("dragging"); } });
293
- lease.on(el, "dragover", (e) => { if (hasFiles(e)) e.preventDefault(); });
294
- lease.on(el, "dragleave", (e) => { if (hasFiles(e) && --depth <= 0) { depth = 0; el.classList.remove("dragging"); } });
295
- lease.on(el, "drop", (e) => { depth = 0; el.classList.remove("dragging"); if (e.dataTransfer && e.dataTransfer.files.length) { e.preventDefault(); store.addFiles(name, e.dataTransfer.files); } });
296
- return undefined;
297
- }, [lease]);
304
+ const offs = [
305
+ lease.on(el, "dragenter", (e) => { if (attachmentsSupported && hasFiles(e)) { depth++; el.classList.add("dragging"); } }),
306
+ lease.on(el, "dragover", (e) => { if (hasFiles(e)) e.preventDefault(); }),
307
+ lease.on(el, "dragleave", (e) => { if (hasFiles(e) && --depth <= 0) { depth = 0; el.classList.remove("dragging"); } }),
308
+ lease.on(el, "drop", (e) => { depth = 0; el.classList.remove("dragging"); if (e.dataTransfer && e.dataTransfer.files.length) { e.preventDefault(); if (attachmentsSupported) store.addFiles(name, e.dataTransfer.files); else toast(t("chat.runtimeNoAttachments"),false); } }),
309
+ ];
310
+ return () => offs.forEach(off => off());
311
+ }, [lease,attachmentsSupported]);
298
312
  // Esc stops the agent's reply (as Esc in its terminal would): from the chat, while it works, and only when nothing
299
313
  // else that Esc would close is open (a dialog, a menu, the Session menu).
300
314
  useEffect(() => lease.hold(onPanelKey((e) => {
@@ -317,10 +331,11 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
317
331
  };
318
332
  const items = [
319
333
  // #1523 N2: Details is a page now (the Fleet side of this instance), not a dialog.
320
- { key: "details", label: t("chat.details"), icon: "info", onSelect: () => navigate(detailsPath(name)) },
321
- running ? null : { key: "start", label: t("chat.start"), icon: "play", onSelect: () => action("start") },
322
- running ? { key: "restart", label: t("chat.restart"), icon: "restart", onSelect: () => action("restart") } : null,
323
- running ? { key: "stop", label: t("chat.stopInstance"), icon: "stop", onSelect: () => action("stop") } : null,
334
+ supportsInstanceFeature(inst,"details") ? { key: "details", label: t("chat.details"), icon: "info", onSelect: () => navigate(detailsPath(name)) } : null,
335
+ inst.runtime === "windows" ? {key:"settings",label:t("app.settings"),icon:"settings",onSelect:() => navigate(sectionPath("agents"))} : null,
336
+ running || !supportsInstanceFeature(inst,"lifecycle") ? null : { key: "start", label: t("chat.start"), icon: "play", onSelect: () => action("start") },
337
+ running && supportsInstanceFeature(inst,"lifecycle") ? { key: "restart", label: t("chat.restart"), icon: "restart", onSelect: () => action("restart") } : null,
338
+ running && supportsInstanceFeature(inst,"lifecycle") ? { key: "stop", label: t("chat.stopInstance"), icon: "stop", onSelect: () => action("stop") } : null,
324
339
  // #1958: its identity colour and where it shows (the public link too, as profile edits).
325
340
  colorMenuItem(name, inst),
326
341
  // #1629: a person's control over this agent's theme — not over the public link (it only views themes).
@@ -337,10 +352,12 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
337
352
  // #1269 quick actions: the model and effort it runs, each a way to /model or /effort (the same pick list).
338
353
  const pick = (command) => store.runCommand(name, command, "", { alive: () => lease.current() });
339
354
  const sub = html`<span class="status"><span class=${`dot ${cls}`} aria-hidden="true"></span>${statusLabel(inst, exec, awaiting)}</span>
340
- ${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", modelText(inst))} aria-label=${t("chat.chipModel", modelText(inst))} onClick=${() => pick("model")}>${modelText(inst)}</button>` : null}
341
- ${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
355
+ ${inst.runtime === "windows" ? html`<span class="tag">Windows</span>` : null}
356
+ ${inst.machine_id ? html`<span class="tag runtime-machine-tag" title=${t("chat.machineName",inst.machine_name || inst.machine_id)}>${inst.machine_name || inst.machine_id}</span>${inst.machine_status !== "online" ? html`<span class="tag runtime-machine-offline">${t(inst.machine_status === "revoked" ? "chat.machineRevoked" : "chat.machineOffline")}</span>` : null}` : null}
357
+ ${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", modelText(inst))} aria-label=${t("chat.chipModel", modelText(inst))} onClick=${() => supportsInstanceFeature(inst,"commands") ? pick("model") : navigate(sectionPath("agents"))}>${modelText(inst)}</button>` : null}
358
+ ${inst.effort && supportsInstanceFeature(inst,"effort") ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
342
359
  return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} data-width=${wide ? "full" : undefined} ref=${view}>
343
- <${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? (guestTrusted() ? html`<${InstanceSwitch} name=${name} current="chat" guest=${true} />` : null) : html`<${InstanceSwitch} name=${name} current="chat" />`}><${CardMenu} card=${card} /><${HeaderTools} navKey=${navKey} />${guest ? null : html`<${Menu} items=${items} label=${t("app.more")} />`}</${PanelHeader}>
360
+ <${PanelHeader} title=${inst.runtime === "windows" ? inst.display_name || name : headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? (guestTrusted() ? html`<${InstanceSwitch} name=${name} instance=${inst} current="chat" guest=${true} />` : null) : html`<${InstanceSwitch} name=${name} instance=${inst} current="chat" />`}><${CardMenu} card=${card} /><${HeaderTools} navKey=${navKey} usage=${supportsInstanceFeature(inst,"usage")} />${guest ? null : html`<${Menu} items=${items} label=${t("app.more")} />`}</${PanelHeader}>
344
361
  <${ChatCard} key=${name} name=${name} card=${card} />
345
362
  <${AgentWidget} slot="chat_header" widget=${widgetFor(name, "chat_header")} binds=${instanceBinds(inst, exec, awaiting)} />
346
363
  <div class="chat-split" ref=${split}>
@@ -348,7 +365,9 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
348
365
  ${theme && theme.background ? html`<img ref=${bg} class=${`th-bg th-bg-${theme.background.fit}`} src=${themeImageUrl(name, theme.background.image)} alt="" aria-hidden="true" decoding="async" />` : null}
349
366
  <${ChatAmbient} name=${name} surface=${effectSurface} part="layer" />
350
367
  <${AmbientMotionNote} name=${name} />
351
- <${Thread} name=${name} th=${thread} />
368
+ <${NativePaneRecovery} name=${name} inst=${inst} lease=${lease} />
369
+ ${inst.native_blocked ? html`<div class="windows-agent-blocked" role="alert"><p>${t("chat.nativeBlocked")}</p><button type="button" class="btn" disabled=${recovering} onClick=${recoverNative}>${t("chat.nativeRecover")}</button></div>` : null}
370
+ <${Thread} name=${name} label=${inst.runtime === "windows" ? inst.display_name || name : name} th=${thread} />
352
371
  <${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${theme ? theme.workingLabel : undefined} />
353
372
  <${ChatAmbient} name=${name} surface=${effectSurface} part="front" />
354
373
  </div>
@@ -356,8 +375,8 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
356
375
  keyOf=${msgKey} reveal=${(k) => thread.current && thread.current.reveal(k)} download=${downloadHtml} allow=${(commit) => setPreviewOptIn(true, commit)} />
357
376
  </div>
358
377
  <${LightboxHost} />
359
- <div data-trust="drop" class="drop-overlay" aria-hidden="true"><div class="drop-card"><${Icon} name="attach" size=${32} /><span>${t("chat.dropHere")}</span></div></div>
360
- ${dialog === "delete" ? html`<${DeleteDialog} name=${name} onClose=${() => setDialog(null)} />` : null}
378
+ ${attachmentsSupported ? html`<div data-trust="drop" class="drop-overlay" aria-hidden="true"><div class="drop-card"><${Icon} name="attach" size=${32} /><span>${t("chat.dropHere")}</span></div></div>` : null}
379
+ ${dialog === "delete" ? html`<${DeleteDialog} name=${name} inst=${inst} onClose=${() => setDialog(null)} />` : null}
361
380
  </div>`;
362
381
  }
363
382
 
@@ -467,10 +486,11 @@ function follow(name, thread) {
467
486
  }
468
487
 
469
488
  /** The thread's view: Preact owns the scroller and the jump pill; the kept list (chat-thread.js) is put inside it. */
470
- function Thread({ name, th }) {
489
+ function Thread({ name, label = name, th }) {
471
490
  const scroller = useRef(null), host = useRef(null);
472
491
  const [jump, setJump] = useState({ show: false, unseen: 0 });
473
492
  const [empty, setEmpty] = useState(false);
493
+ useLayoutEffect(() => { keptThreads.get(name)?.list.setAttribute("aria-label",t("chat.conversationWith",label)); },[name,label]);
474
494
  useLayoutEffect(() => {
475
495
  const doc = host.current.ownerDocument;
476
496
  let e = keptThreads.get(name);
@@ -484,6 +504,7 @@ function Thread({ name, th }) {
484
504
  if (kept) trimKept(name);
485
505
  const shownAt = now();
486
506
  const thread = e.thread, sc = scroller.current, ls = e.list;
507
+ ls.setAttribute("aria-label",t("chat.conversationWith",label));
487
508
  e.sink = { setJump, setEmpty };
488
509
  host.current.append(ls);
489
510
  thread.attach(sc);
@@ -544,9 +565,9 @@ function Dock({ name, inst, lease, exec, awaiting, workingLabel }) {
544
565
  <${CommandCard} name=${name} lease=${lease} />
545
566
  <${QuickActions} name=${name} inst=${inst} lease=${lease} />
546
567
  ${s.waking[name] ? html`<p class="send-waking" role="status">${t("chat.waking", name)}</p>` : null}
547
- <${Composer} name=${name} label=${composerLabel(inst, name)} lease=${lease} busy=${R().isBusy(exec)} />
568
+ <${Composer} name=${name} inst=${inst} label=${composerLabel(inst, name)} lease=${lease} busy=${R().isBusy(exec)} />
548
569
  <div class="composer-row">${guestPage() ? html`<span class="guest-note" title=${t("chat.guestNote")}>${t("chat.guestNote")}</span>` : html`<${ComposerAuth} />`}<span
549
- class="hint" title=${t(guestPage() ? "chat.guestMore" : "chat.composerMore")}>${t(touchScreen() ? "chat.composerHintTouch" : "chat.composerHint")}</span></div>
570
+ class="hint" title=${t(guestPage() || !supportsInstanceFeature(inst,"commands") ? "chat.guestMore" : "chat.composerMore")}>${t(supportsInstanceFeature(inst,"commands") ? touchScreen() ? "chat.composerHintTouch" : "chat.composerHint" : touchScreen() ? "chat.nativeComposerHintTouch" : "chat.nativeComposerHint")}</span></div>
550
571
  </div></div>`;
551
572
  }
552
573
 
@@ -641,7 +662,7 @@ function replySnippet(r) {
641
662
  return text || (r.media === "photo" ? t("chat.replyPhoto") : r.media === "document" ? t("chat.replyFile") : "");
642
663
  }
643
664
 
644
- function Composer({ name, label = name, lease, busy }) {
665
+ function Composer({ name, inst, label = name, lease, busy }) {
645
666
  const s = store.state;
646
667
  const input = useRef(null), fileIn = useRef(null);
647
668
  const [, tick] = useState(0);
@@ -662,7 +683,10 @@ function Composer({ name, label = name, lease, busy }) {
662
683
  const guest = guestPage();
663
684
  const app = useStore(appStore);
664
685
  const guestAllowed = guest ? (app.instances.find((i) => i.name === name)?.commands ?? []) : null;
665
- const palette = files.length || closedFor === value || (guest && !guestAllowed.length) ? null : paletteFor(value, guestAllowed || undefined);
686
+ const attachmentsSupported = supportsInstanceFeature(inst,"attachments"), commandsSupported = supportsInstanceFeature(inst,"commands");
687
+ const machineUnavailable = !!inst?.machine_id && inst.machine_status !== "online";
688
+ const sendingBlocked = machineUnavailable || inst?.native_blocked || inst?.native_busy || (busy && !supportsInstanceFeature(inst,"send_while_busy"));
689
+ const palette = !commandsSupported || files.length || closedFor === value || (guest && !guestAllowed.length) ? null : paletteFor(value, guestAllowed || undefined);
666
690
  const pick = palette && palette.length ? palette[Math.min(sel, palette.length - 1)] : null;
667
691
  // Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300). Empty, it is one line
668
692
  // (#2478 — a long name's placeholder made the box two lines tall).
@@ -684,6 +708,9 @@ function Composer({ name, label = name, lease, busy }) {
684
708
  // #2342: a completion is a step Ctrl+Z goes back from (to what was typed); a command that runs consumes the line.
685
709
  const complete = (c) => { recordEdit(input.current); setValue(`/${c.name}${c.arg ? " " : ""}`); };
686
710
  const submit = () => {
711
+ if (sendingBlocked) return;
712
+ if (!attachmentsSupported && files.length) { toast(t("chat.runtimeNoAttachments"),false); return; }
713
+ if (!commandsSupported && parseCommandLine(value)) { toast(t("chat.runtimeNoCommands"),false); return; }
687
714
  if (pick) {
688
715
  if (value === `/${pick.name}` && !needsArgument(pick.name)) runLine({ command: pick.name, args: "" });
689
716
  else complete(pick);
@@ -719,11 +746,13 @@ function Composer({ name, label = name, lease, busy }) {
719
746
  const onPaste = (e) => {
720
747
  const list = e.clipboardData && e.clipboardData.files;
721
748
  const text = e.clipboardData ? e.clipboardData.getData("text") : "";
722
- if (list && list.length) { store.addFiles(name, list); if (!text) e.preventDefault(); return; }
723
- if (R().isLongPaste(text) && store.attachPastedText(name, text)) e.preventDefault();
749
+ if (list && list.length) { if (!attachmentsSupported) { if (!text) e.preventDefault(); toast(t("chat.runtimeNoAttachments"),false); return; } store.addFiles(name, list); if (!text) e.preventDefault(); return; }
750
+ if (attachmentsSupported && R().isLongPaste(text) && store.attachPastedText(name, text)) e.preventDefault();
724
751
  };
725
752
  const optId = (c) => `cmd-${c.name}`;
726
753
  return html`<div class="composer-box">
754
+ ${machineUnavailable ? html`<p class="note runtime-machine-unavailable" role="status">${t(inst.machine_status === "revoked" ? "chat.machineRevokedHint" : "chat.machineUnavailable",inst.machine_name || inst.machine_id)}</p>` : null}
755
+ ${inst?.native_busy && !busy && !inst?.native_blocked ? html`<p class="note" role="status">${t("chat.nativeBusy")}</p>` : null}
727
756
  ${palette ? html`<ul data-trust="palette" class="cmd-palette" id="cmdPalette" role="listbox" aria-label=${t("chat.cmdCommands")}>
728
757
  ${palette.length ? palette.map((c) => html`<li key=${c.name} id=${optId(c)} role="option" aria-selected=${c === pick ? "true" : "false"}
729
758
  class=${c === pick ? "on" : ""} onMouseDown=${(e) => { e.preventDefault(); complete(c); }}>
@@ -737,9 +766,9 @@ function Composer({ name, label = name, lease, busy }) {
737
766
  </div>` : null}
738
767
  ${files.length ? html`<div class="pending-files">${files.map((f, i) => html`<${FileChip} key=${`${f.name}-${i}`} f=${f} i=${i} name=${name} />`)}</div>` : null}
739
768
  <div class="composer">
740
- <button id="attachBtn" type="button" class="icon-btn" title=${t("chat.attachTitle")} aria-label=${t("chat.attachTitle")} onClick=${() => fileIn.current && fileIn.current.click()}><${Icon} name="attach" /></button>
769
+ ${attachmentsSupported ? html`<button id="attachBtn" type="button" class="icon-btn" title=${t("chat.attachTitle")} aria-label=${t("chat.attachTitle")} onClick=${() => fileIn.current && fileIn.current.click()}><${Icon} name="attach" /></button>
741
770
  <input ref=${fileIn} type="file" multiple hidden
742
- onChange=${(e) => { store.addFiles(name, e.target.files); e.target.value = ""; }} />
771
+ onChange=${(e) => { store.addFiles(name, e.target.files); e.target.value = ""; }} />` : null}
743
772
  ${/* #2478: the placeholder is drawn beside the box, one line ending in "…" — a textarea's own placeholder is clipped,
744
773
  never ellipsized, and a long name wrapped it to two lines. The box keeps the words as its accessible name. Its own
745
774
  placeholder is one space: :placeholder-shown hides the drawn one (app.css) the moment the box holds text, an
@@ -751,7 +780,7 @@ function Composer({ name, label = name, lease, busy }) {
751
780
  placeholder=" "></textarea><span class="composer-ph" aria-hidden="true">${t("chat.composerPlaceholder", label)}</span></span>
752
781
  <button id="stopBtn" type="button" class="btn btn-stop" hidden=${!busy} title=${t("chat.stopReplyTitle")}
753
782
  disabled=${!!s.cancelling[name]} onClick=${() => store.cancelReply(name)}><${Icon} name="stop" size=${14} /><span class="stop-label">${t("chat.stopReply")}</span></button>
754
- <button id="sendBtn" type="button" class="btn btn-primary btn-send" hidden=${busy && !content} disabled=${!content}
783
+ <button id="sendBtn" type="button" class="btn btn-primary btn-send" hidden=${busy && !content} disabled=${!content || sendingBlocked}
755
784
  onMouseDown=${(e) => e.preventDefault()}
756
785
  aria-label=${t("chat.send")} title=${t("chat.send")} onClick=${submit}><${Icon} name="send" size=${16} /><span class="send-label">${t("chat.send")}</span></button>
757
786
  </div></div>`;
@@ -775,7 +804,7 @@ function FileChip({ f, i, name }) {
775
804
 
776
805
  // ── Dialogs ──
777
806
 
778
- function DeleteDialog({ name, onClose }) {
807
+ function DeleteDialog({ name, inst, onClose }) {
779
808
  // The dialog's own lease: it ends when the dialog goes (closed, or the chat left for another page). The delete
780
809
  // may still finish on the server, and says so, but a dialog that is gone never closes or navigates the page the
781
810
  // person is on now (#1425 review).
@@ -791,7 +820,7 @@ function DeleteDialog({ name, onClose }) {
791
820
  // follows the request. From then on the dialog is done: the decision is said in a toast, and it never closes or
792
821
  // navigates anything (the person may be anywhere by then).
793
822
  try {
794
- const res = await confirmedWrite(`/ui/instances/${encodeURIComponent(name)}/delete`, { method: "POST", body: { confirm: typed }, label: t("chat.deleteTitle", name),
823
+ const res = await confirmedWrite(inst?.runtime === "windows" ? `/ui/instances/${encodeURIComponent(name)}` : `/ui/instances/${encodeURIComponent(name)}/delete`, { method: inst?.runtime === "windows" ? "DELETE" : "POST", body: inst?.runtime === "windows" ? {} : { confirm: typed }, label: t("chat.deleteTitle", name),
795
824
  onPending: (_id, view) => { handedOver = true; toast(t(selfConfirmed(view) ? "app.pendingSelfSent" : "app.pendingSent")); if (lease.current()) { setBusy(false); onClose(); } } });
796
825
  r = res.ok ? res.body || {} : { error: (res.body && res.body.error) || `HTTP ${res.status}` };
797
826
  } catch (err) { r = { error: err && err.message ? err.message : t("chat.disconnected") }; }
@@ -1,3 +1,4 @@
1
+ import { NativePaneRecovery } from "./native-pane-recovery.js";
1
2
  // #1523 N2 (§3.3, Q1 = B): Details — "the Fleet side of one instance", at /ui/fleet/agent/<name>. Behind the session
2
3
  // gate (/ui/js/). What it holds:
3
4
  // - runtime and recent activity (what Chat's details dialog showed, now a page);
@@ -158,7 +159,7 @@ function DetailsView({ name, inst, navKey, exec, awaiting }) {
158
159
  }
159
160
  return html`<div data-effect-panel="details" class="panel p-details">
160
161
  <${PanelHeader} title=${name} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="details" />`}><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
161
- <div data-trust="panel-content" class="panel-body"><div class="col" ref=${body}>${busy ? html`<p class="note" role="status">${t("chat.dWorking")}</p>` : null}<${IdentityCard} name=${name} card=${card} />
162
+ <div data-trust="panel-content" class="panel-body"><div class="col" ref=${body}><${NativePaneRecovery} name=${name} inst=${inst} lease=${lease} />${busy ? html`<p class="note" role="status">${t("chat.dWorking")}</p>` : null}<${IdentityCard} name=${name} card=${card} />
162
163
  <${AgentWidget} slot="details" widget=${widgetFor(name, "details")} binds=${instanceBinds(inst, exec, awaiting)} />${content}</div></div>
163
164
  </div>`;
164
165
  }
@@ -11,7 +11,11 @@ import { appStore, useStore } from "/assets/app-store.js";
11
11
  import { useLease } from "/assets/app-ctx.js";
12
12
  import { PanelHeader, setTitle } from "/assets/app-shell.js";
13
13
  import { HeaderTools } from "/assets/header-tools.js";
14
- import { renavigate } from "/assets/app-nav.js";
14
+ import { navigate, renavigate } from "/assets/app-nav.js";
15
+ import { RuntimePicker, AgentDraftAssistant, NativeAgendToolsHint, tr } from "./agent-runtime.js";
16
+ import { CliPicker, cliLabel as productLabel } from "./cli-picker.js";
17
+ import { useWindowsCliCatalog, WindowsCliDiscoveryStatus, detectionText } from "./windows-cli-catalog.js";
18
+ import { useRuntimeCatalog } from "./runtime-catalog.js";
15
19
  import { chatPath, fleetPath, FLEET_TABS, rememberFleetTab } from "/assets/app-route.js";
16
20
  import { Dialog } from "/assets/ui-dialog.js";
17
21
  import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
@@ -266,7 +270,7 @@ function Cache({ lease }) {
266
270
  * that finishes after that still reports its toast (it may have created something), but never closes or refreshes
267
271
  * whatever is on screen now (#1425 review). `ready` false keeps Create off until the form has what it needs.
268
272
  */
269
- export function FormDialog({ title, onClose, submit, children, ready = true }) {
273
+ export function FormDialog({ title, onClose, submit, children, ready = true, renderActions, onCreated, onSubmit, wide = false }) {
270
274
  const lease = useLease("form-dialog");
271
275
  const [busy, setBusy] = useState(false);
272
276
  async function go() {
@@ -287,13 +291,15 @@ export function FormDialog({ title, onClose, submit, children, ready = true }) {
287
291
  toast(submit.done);
288
292
  if (handedOver || !lease.current()) return;
289
293
  setBusy(false);
294
+ if (onCreated) { onCreated(r); return; }
290
295
  onClose();
291
296
  if (submit.after) submit.after();
297
+ if (submit.afterResult) submit.afterResult(r);
292
298
  }
293
- return html`<${Dialog} title=${title} onClose=${onClose} busy=${busy}
294
- actions=${html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${t("fleet.cancel")}</button>
299
+ return html`<${Dialog} title=${title} onClose=${onClose} busy=${busy} wide=${wide}
300
+ actions=${renderActions ? renderActions({ busy, go }) : html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${t("fleet.cancel")}</button>
295
301
  <button type="button" class="btn btn-primary" onClick=${go} disabled=${busy || !ready}>${busy ? t("fleet.creating") : submit.label || t("fleet.create")}</button>`}>
296
- <form class="form" onSubmit=${(e) => { e.preventDefault(); go(); }}><fieldset class="form" disabled=${busy}>${children}</fieldset></form></${Dialog}>`;
302
+ <form class="form" onSubmit=${(e) => { e.preventDefault(); if (onSubmit) onSubmit({ busy, go }); else go(); }}><fieldset class="form" disabled=${busy}>${children}</fieldset></form></${Dialog}>`;
297
303
  }
298
304
  const field = (label, input) => html`<label class="field"><span>${label}</span>${input}</label>`;
299
305
 
@@ -418,9 +424,26 @@ function CreateTeam({ onClose, onDone, team = null }) {
418
424
  export function CreateInstanceDialog({ onClose }) {
419
425
  const lease = useLease("create-instance");
420
426
  const [backends, setBackends] = useState(null);
427
+ const { windowsCli } = useStore(appStore);
428
+ const [runtime,setRuntime] = useState("");
429
+ const [machineId,setMachineId] = useState("");
430
+ const [nativeAdapter,setNativeAdapter]=useState("exec");
431
+ const machineCatalog = useRuntimeCatalog();
432
+ const machines = machineCatalog.data?.machines || [];
433
+ const machine = machines.find(item => item.id === machineId);
434
+ const [step,setStep] = useState(1), [created,setCreated] = useState(null);
435
+ const drafts = useRef({}), draftGoals = useRef({}), heading = useRef(null);
436
+ const discovery = useWindowsCliCatalog(windowsCli === true);
437
+ const native = machineId ? machine?.inventory : discovery.data;
438
+ const [assistBusy,setAssistBusy] = useState(false);
439
+ const windows = runtime === "windows";
421
440
  // The backend "fleet default" means (#2098 review P3-3): its models are the ones the model list offers then.
422
441
  const [defaultBackend, setDefaultBackend] = useState("claude-code");
423
442
  const [f, setF] = useState({ dir: "", topic: "", desc: "", backend: "", model: "", branch: "", tags: "", prompt: "" });
443
+ useEffect(() => {
444
+ heading.current?.focus({ preventScroll:true });
445
+ heading.current?.closest(".dlg-body")?.scrollTo?.({ top:0 });
446
+ },[step]);
424
447
  useEffect(() => {
425
448
  (async () => {
426
449
  try {
@@ -430,15 +453,43 @@ export function CreateInstanceDialog({ onClose }) {
430
453
  const first = list.find(b => b.installed && !b.deprecated) || list.find(b => b.installed);
431
454
  if (typeof bd.defaultBackend === "string" && bd.defaultBackend) setDefaultBackend(bd.defaultBackend);
432
455
  setBackends(list);
433
- setF(x => ({ ...x, backend: first ? first.name : "" }));
456
+ setF(x => ({ ...x, backend: x.backend || (first ? first.name : "") }));
434
457
  } catch {
435
458
  // The list could not be read: offer the known backends, and keep the explicit choice "fleet default" selected.
436
- if (lease.current()) { setBackends(BACKENDS.map(name => ({ name, installed: true }))); setF(x => ({ ...x, backend: "" })); }
459
+ if (lease.current()) { setBackends(BACKENDS.map(name => ({ name, installed: true, fallback: true }))); setF(x => ({ ...x, backend: "" })); }
437
460
  }
438
461
  })();
439
462
  }, [lease]);
440
463
  const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
441
- const submit = { path: "/ui/instances", done: t("fleet.instanceCreated"), collect: () => {
464
+ const targetKey = `${runtime}:${machineId || "local"}`;
465
+ const chooseRuntime = (value, nextMachineId = "") => {
466
+ if (value === runtime && nextMachineId === machineId) return;
467
+ if (runtime) drafts.current[targetKey] = { form:{...f}, sandbox,nativeAdapter };
468
+ const saved = drafts.current[`${value}:${nextMachineId || "local"}`];
469
+ const nextInventory = nextMachineId ? machines.find(item => item.id === nextMachineId)?.inventory : discovery.data;
470
+ const { sandbox: savedFileAccess } = saved || {};
471
+ setRuntime(value);
472
+ setMachineId(nextMachineId);
473
+ setF(saved?.form || {dir:"",topic:"",desc:"",prompt:"",tags:"",branch:"",model:"",backend:value === "windows" ? nextInventory?.clis?.find(b => b.path && b.runnable)?.id || "codex" : backends?.find(b => b.installed && !b.deprecated && !b.fallback)?.name || ""});
474
+ setSandbox(savedFileAccess || "read-only");setNativeAdapter(saved?.nativeAdapter||"exec");
475
+ };
476
+ const [sandbox,setSandbox] = useState("read-only");
477
+ const nativeCli = native?.clis?.find(cli => cli.id === f.backend);
478
+ const nativeChoices = (native?.clis || []).filter(cli => cli.path && cli.supports?.execution !== false && cli.supports?.resume !== false).map(cli => ({value:cli.id,label:productLabel(cli.id),disabled:!cli.runnable,description:cli.authenticated === false ? t("fleet.notSignedIn") : cli.version || "Windows"}));
479
+ useEffect(() => {
480
+ if(nativeAdapter==="pane"&&!['codex','claude-code'].includes(f.backend))setNativeAdapter("exec");
481
+ if (windows && native && !nativeChoices.some(choice => choice.value === f.backend)) {
482
+ const first = nativeChoices.find(choice => !choice.disabled) || nativeChoices[0];
483
+ if (first) setF(current => ({...current,backend:first.value,model:""}));
484
+ }
485
+ },[native,runtime,machineId,f.backend,nativeAdapter]);
486
+ const submit = { path: "/ui/instances", done: windows ? tr("created") : t("fleet.instanceCreated"),
487
+ collect: () => {
488
+ if (windows) {
489
+ const name = f.topic.trim() || f.dir.trim().replace(/[\\/]+$/, "").split(/[\\/]/).pop();
490
+ if (!name) { toast(tr("required"),false); return null; }
491
+ return {runtime:"windows",...(nativeAdapter==="pane"?{native_adapter:"pane"}:{}),...(machineId ? {machineId} : {}),topic_name:name,backend:f.backend,...(f.model.trim() ? {model:f.model.trim()} : {}),...(f.dir.trim() ? {directory:f.dir.trim()} : {}),description:f.desc.trim(),systemPrompt:f.prompt.trim(),sandbox};
492
+ }
442
493
  if (!f.dir.trim() && !f.topic.trim()) { toast(t("fleet.topicRequired"), false); return null; }
443
494
  const body = {};
444
495
  if (f.dir.trim()) body.directory = f.dir.trim();
@@ -451,18 +502,48 @@ export function CreateInstanceDialog({ onClose }) {
451
502
  if (f.prompt.trim()) body.systemPrompt = f.prompt.trim();
452
503
  return body;
453
504
  } };
454
- // Create waits for the backend list: a submission before it would go without a backend, and the list arriving
455
- // afterwards would change the shown choice under a request already sent (#1425 review).
456
- return html`<${FormDialog} title=${t("fleet.newInstance")} onClose=${onClose} submit=${submit} ready=${backends !== null}>
457
- ${field(t("fleet.directory"), html`<input value=${f.dir} onInput=${set("dir")} placeholder=${t("fleet.directoryHint")} />`)}
458
- ${field(`${t("fleet.topic")}${f.dir.trim() ? "" : " *"}`, html`<input value=${f.topic} onInput=${set("topic")} placeholder=${t("fleet.topicHint")} />`)}
505
+ const canNext = !!runtime && (!machineId || machine?.status === "online") && (windows ? nativeCli?.runnable === true : backends !== null);
506
+ const permissionReady = !windows || sandbox !== "read-only" || nativeCli?.readOnlySupport !== false;
507
+ const canCreate = step === 2 && canNext && permissionReady && !assistBusy && (!windows || !!(f.topic.trim() || f.dir.trim()));
508
+ const advance = () => { if (canNext && !assistBusy) setStep(2); };
509
+ const destination = created?.name ? chatPath(created.name) : created?.agent?.id ? `/ui/windows-agents/${created.agent.id}` : null;
510
+ const createdName = created?.agent?.name || created?.name || f.topic || f.dir.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
511
+ const runtimeLabel = machineId ? `${machine?.name || machineId} · ${machine?.platform || tr(runtime)}` : runtime ? tr(runtime) : "";
512
+ const cliLabel = productLabel(f.backend) || `${t("fleet.fleetDefault")} (${productLabel(defaultBackend)})`;
513
+ const backendField = windows ? html`<div class="field"><div class="cli-discovery-heading"><span>${tr("backend")}</span>${native && !machineId ? html`<button type="button" class="btn btn-ghost btn-sm" disabled=${discovery.loading} onClick=${discovery.refresh}><${Icon} name="restart" size=${14} />${detectionText("refresh")}</button>` : null}</div>
514
+ <div aria-busy=${!machineId && discovery.loading ? "true" : "false"}>${native ? html`<${CliPicker} label=${tr("backend")} value=${f.backend} choices=${nativeChoices} onChange=${value => setF(x => ({...x,backend:value,model:""}))} disabled=${!nativeChoices.length || !!machineId && machine?.status !== "online"} />` : null}</div>
515
+ ${machineId ? html`<p class="note">${tr(!nativeChoices.length ? "noRemoteCli" : "remotePolicy")}</p>${machine?.inventoryUpdatedAt ? html`<p class="note">${t("runtime.inventoryAt",new Date(machine.inventoryUpdatedAt).toLocaleString())}</p>` : null}` : html`<${WindowsCliDiscoveryStatus} state=${discovery} />`}
516
+ ${!machineId && native && !discovery.loading && !discovery.error ? html`<p class="note">${!nativeChoices.length ? tr("noCli") : tr("nativeHint")}</p>` : null}</div>`
517
+ : field(tr("backend"),backends ? html`<${CliPicker} label=${tr("backend")} value=${f.backend} choices=${[{value:"",label:t("fleet.fleetDefault")},...backends.filter(b => backendState(b) !== "not_installed" && !b.deprecated).map(b => ({value:b.name,label:productLabel(b.name),disabled:backendBlocked(b),description:backendNote(b)}))]} onChange=${value => setF(x => ({...x,backend:value,model:""}))} />` : html`<div role="status" aria-busy="true"><${Skeleton} lines=${1} /><span class="note">${tr("loadingClis")}</span></div>`);
518
+ const actions = ({busy,go}) => step === 3 ? html`<button type="button" class="btn" onClick=${onClose}>${tr("finish")}</button><button type="button" class="btn btn-primary" onClick=${() => { onClose(); navigate(destination || "/settings/agents"); }}>${tr(destination ? "openChat" : "viewAgents")}<${Icon} name="forward" size=${16} /></button>`
519
+ : html`<button type="button" class="btn" disabled=${busy || assistBusy} onClick=${step === 1 ? onClose : () => setStep(1)}>${step === 1 ? t("fleet.cancel") : tr("back")}</button>
520
+ <button type="button" class="btn btn-primary" disabled=${busy || (step === 1 ? !canNext : !canCreate)} onClick=${step === 1 ? advance : go}>${step === 1 ? tr("next") : busy ? t("fleet.creating") : t("fleet.create")}${step === 1 ? html`<${Icon} name="forward" size=${16} />` : null}</button>`;
521
+ return html`<${FormDialog} title=${tr("title")} onClose=${onClose} submit=${submit} ready=${canCreate} wide=${true} renderActions=${actions}
522
+ onSubmit=${({busy,go}) => { if (!busy) { if (step === 1) advance(); else if (step === 2) go(); } }}
523
+ onCreated=${result => { setCreated(result); setStep(3); }}>
524
+ <div class="agent-create-wizard" data-step=${step}>
525
+ <ol class="agent-create-steps" aria-label=${tr("progress")}>${["runtimeStep","configureStep","doneStep"].map((label,index) => html`<li key=${label} class=${index+1 === step ? "current" : index+1 < step ? "complete" : ""} aria-current=${index+1 === step ? "step" : undefined}><span class="agent-create-step-number">${index+1 < step ? html`<${Icon} name="check" size=${13} />` : index+1}</span><span>${tr(label)}</span></li>`)}</ol>
526
+ ${step === 1 ? html`<section class="agent-create-stage"><h3 ref=${heading} tabindex="-1">${tr("where")}</h3><p class="note">${tr("runtimeLead")}</p>
527
+ <${RuntimePicker} value=${runtime} onChange=${chooseRuntime} windowsEnabled=${windowsCli} legend=${tr("environment")} machineId=${machineId} machines=${machines} onMachineChange=${id => chooseRuntime("windows",id)} />
528
+ ${machineCatalog.loading && !machineCatalog.data ? html`<p class="note" role="status">${tr("machineLoading")}</p>` : null}
529
+ ${machineCatalog.error ? html`<p class="note" role="alert">${tr("machineFailed")} <button type="button" class="btn btn-ghost btn-sm" onClick=${machineCatalog.refresh}>${tr("machineRetry")}</button></p>` : null}
530
+ <a class="note runtime-manage-link" href="/settings/runtimes">${tr("manage")}</a>
531
+ ${runtime ? html`<div class="agent-create-cli">${backendField}</div>` : null}
532
+ </section>` : step === 2 ? html`<section class="agent-create-stage"><h3 ref=${heading} tabindex="-1">${tr("configureStep")}</h3><p class="note">${tr("configureLead")}</p>
533
+ <div class="agent-create-selection"><${Icon} name=${windows ? "view" : "code"} size=${19} /><div><strong>${runtimeLabel}</strong><span>${cliLabel}</span></div><button type="button" class="btn btn-ghost btn-sm" disabled=${assistBusy} onClick=${() => setStep(1)}>${tr("changeRuntime")}</button></div>
534
+ ${machineId ? html`<p class="note">${tr("remoteDraft")}</p>` : windowsCli ? html`<${AgentDraftAssistant} key=${targetKey} runtime=${runtime} probe=${native} initialGoal=${draftGoals.current[targetKey] || ""} onGoalChange=${value => { draftGoals.current[targetKey] = value; }} onBusy=${setAssistBusy} onDraft=${draft => setF(x => ({...x,topic:draft.name,dir:draft.cwd,desc:draft.description,prompt:draft.instructions}))} />` : null}
535
+ <fieldset class="form agent-runtime-fields" disabled=${assistBusy}>
536
+ ${field(machineId ? t("runtime.remoteDirectory",machine?.name || machineId) : windows ? tr("directory") : t("fleet.directory"), html`<input value=${f.dir} onInput=${set("dir")} placeholder=${windows ? tr("directoryHint") : t("fleet.directoryHint")} />`)}
537
+ ${field(`${t("fleet.topic")}${f.dir.trim() ? "" : " *"}`, html`<input value=${f.topic} onInput=${set("topic")} placeholder=${windows ? tr("nameHint") : t("fleet.topicHint")} />`)}
459
538
  ${field(t("fleet.description"), html`<input value=${f.desc} onInput=${set("desc")} placeholder=${t("fleet.descriptionHint")} />`)}
460
- ${field(t("fleet.backend"), backends ? html`<select value=${f.backend} onChange=${set("backend")}><option value="">${t("fleet.fleetDefault")}</option>
461
- ${backends.map(b => html`<option key=${b.name} value=${b.name} disabled=${backendBlocked(b)}>${b.name}${b.deprecated ? ` ${t("fleet.deprecated")}` : ""}${backendNote(b)}</option>`)}</select>` : html`<${Skeleton} lines=${1} />`)}
462
- ${field(t("fleet.model"), html`<${ModelSelect} value=${f.model} onChange=${(v) => setF(x => ({ ...x, model: v }))} backend=${f.backend || defaultBackend} inherit=${t("fleet.fleetDefault")} />`)}
463
- ${field(t("fleet.branch"), html`<input value=${f.branch} onInput=${set("branch")} placeholder=${t("fleet.branchHint")} />`)}
464
- ${field(t("fleet.tags"), html`<input value=${f.tags} onInput=${set("tags")} placeholder=${t("fleet.tagsHint")} />`)}
539
+ ${windows ? html`${field(tr("adapter"),html`<select id="native-create-adapter" value=${nativeAdapter} onChange=${e=>setNativeAdapter(e.target.value)}><option value="exec">${tr("adapterExec")}</option><option value="pane" disabled=${!["codex","claude-code"].includes(f.backend)}>${tr("adapterPane")}</option></select>`)}<p class="note">${tr(nativeAdapter==="pane"?"paneTopic":"execTopic")}</p>${field(t("fleet.model"),html`<${ModelSelect} key=${`${targetKey}:${f.backend}`} value=${f.model} onChange=${value => setF(x => ({...x,model:value}))} backend=${f.backend} runtime="windows" machineId=${machineId} inherit=${tr("model")} />`)}${field(tr("access"),html`<select id="native-create-sandbox" value=${sandbox} onChange=${e => setSandbox(e.target.value)}><option value="read-only" disabled=${nativeCli?.readOnlySupport === false}>${tr("read")}</option><option value="workspace-write">${tr("write")}</option></select>`)}<p class="note">${!permissionReady ? tr("noReadOnly") : sandbox === "workspace-write" ? tr(nativeAdapter==="pane"?"panePolicy":nativeCli?.id === "claude-code" ? "nativePolicyClaude" : "nativePolicy") : ""}</p><${NativeAgendToolsHint} cli=${nativeCli} sandbox=${sandbox} />` : html`
540
+ ${field(t("fleet.model"), html`<${ModelSelect} value=${f.model} onChange=${(v) => setF(x => ({ ...x, model: v }))} backend=${f.backend || defaultBackend} inherit=${t("fleet.fleetDefault")} />`)}
541
+ ${field(t("fleet.branch"), html`<input value=${f.branch} onInput=${set("branch")} placeholder=${t("fleet.branchHint")} />`)}
542
+ ${field(t("fleet.tags"), html`<input value=${f.tags} onInput=${set("tags")} placeholder=${t("fleet.tagsHint")} />`)}
543
+ `}
465
544
  ${field(t("fleet.systemPrompt"), html`<textarea rows="3" value=${f.prompt} onInput=${set("prompt")} placeholder=${t("fleet.systemPromptHint")}></textarea>`)}
545
+ </fieldset></section>` : html`<section class="agent-create-stage agent-create-success"><span class="agent-create-success-icon"><${Icon} name="check" size=${28} /></span><h3 ref=${heading} tabindex="-1">${tr("success")}</h3><p class="note">${tr("successLead")}</p><dl><dt>${t("fleet.topic")}</dt><dd>${createdName}</dd><dt>Runtime</dt><dd>${runtimeLabel} · ${cliLabel}</dd><dt>${t("fleet.directory")}</dt><dd><code>${created?.agent?.cwd || created?.worktree_path || f.dir || tr("autoWorkspace")}</code></dd></dl></section>`}
546
+ </div>
466
547
  </${FormDialog}>`;
467
548
  }
468
549