@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
@@ -1,3 +1,7 @@
1
+ import { NativePaneRecoveryError } from "./native-pane-recovery.js";
2
+ import { nativePaneToolAvailable, nativePaneToolRefusal } from "./native-pane-tools.js";
3
+ import { windowsAgentsFor } from "./windows-agents.js";
4
+ import { buildFleetInstructions } from "./instructions.js";
1
5
  import { supportsVerifiedKiroMigration, getCachedKiroCliCompatibility, kiroV1DbPath, kiroV2SessionsDir } from "./backend/kiro.js";
2
6
  import { listKiroV1Sessions, readKiroClassicLinks } from "./backend/kiro-identity.js";
3
7
  import { readKiroV1SavedModel } from "./backend/kiro-retired-model.js";
@@ -9,10 +13,12 @@ import { WorkingRecoveryCoordinator, WORKING_RECOVERY_KIND, buildWorkingRecovery
9
13
  import { PUBLIC_TUNNEL_TOKEN_ENV, PUBLIC_TUNNEL_TOKEN_HINT, normalizePublicTunnelToken } from "./public-tunnel-config.js";
10
14
  import { humanOwnerObjectId, webHumanOrigin } from "./human-delivery-receipts.js";
11
15
  import { SettingsBaselines } from "./settings-baseline.js";
12
- import { SettingsConfirmationStore, settingsSourceLabel } from "./settings-confirmation.js";
16
+ import { prepareSettingsEffect } from "./settings-effect.js";
17
+ import { settingsDisplay } from "./settings-change.js";
18
+ import { SettingsConfirmationError, SettingsConfirmationStore, settingsSourceLabel } from "./settings-confirmation.js";
13
19
  import { SettingsHttpConfirmation } from "./settings-http-confirmation.js";
14
20
  import { SettingsControlServer } from "./settings-control.js";
15
- import { SettingsExecution, settingsRevision, noteSettingsWrite, settingsFileResource, assertSettingsLease, trySettingsLease, waitSettingsLease, settingsUndo, undoSettingsPaths } from "./settings-transaction.js";
21
+ import { settingsFingerprint, SettingsExecution, settingsRevision, noteSettingsWrite, settingsFileResource, assertSettingsLease, trySettingsLease, waitSettingsLease, settingsUndo, undoSettingsPaths } from "./settings-transaction.js";
16
22
  import { performance } from "node:perf_hooks";
17
23
  import { withSpawnHoldReply } from "./spawn-hold-reply.js";
18
24
  import { gatewayRequestContext } from "./web-request-context.js";
@@ -83,7 +89,8 @@ import { startEventLoopWatch } from "./event-loop-watch.js";
83
89
  import { TmuxManager } from "./tmux-manager.js";
84
90
  import { AccessManager } from "./channel/access-manager.js";
85
91
  import { IpcClient } from "./channel/ipc-bridge.js";
86
- import { usesChannelAddress } from "./channel/mcp-tools.js";
92
+ import { NativeDaemonClient } from "./native-daemon-client.js";
93
+ import { usesChannelAddress, TOOLS } from "./channel/mcp-tools.js";
87
94
  import { ParkedNotices, PARKED_NOTICE_LEDGER_FILE } from "./parked-notices.js";
88
95
  import { formatFleetTime } from "./tz-utils.js";
89
96
  import { createAdapter } from "./channel/factory.js";
@@ -93,6 +100,7 @@ import { createBackend } from "./backend/factory.js";
93
100
  import { readEffortMetadata } from "./backend/effort-metadata.js";
94
101
  import { codexEffort, flagEffort } from "./backend/launch-effort.js";
95
102
  import { codexMetadataHomes, readCodexModelEffortLevels, resolveCodexLaunchModel } from "./backend/codex-metadata.js";
103
+ import { readGrokModelCache } from "./backend/grok-model-cache.js";
96
104
  import { CLI_ENV_TTL_MS, isModelCompatible, noteResolvedBinary, resolveBinary, SYSINFO_BACKEND_IDS, UnsupportedCliError, ResumeBlockedError } from "./backend/types.js";
97
105
  import { createLogger, rotateLogIfNeeded } from "./logger.js";
98
106
  import { processAttachments } from "./channel/attachment-handler.js";
@@ -167,6 +175,16 @@ export function applyTaskListCap(tasks, filterAssignee, filterStatus) {
167
175
  return tasks;
168
176
  }
169
177
  import { handleWebRequest, webPeerOf, broadcastSseEvent, broadcastToGuests, SSE_HEARTBEAT_MS } from "./web-api.js";
178
+ import { windowsCliEnabled, closeWindowsBridge, inventoryProbed, LOCAL_WINDOWS_CLI_OFF } from "./windows-cli.js";
179
+ import { localWindowsCliEnabled, remoteRuntimeEnabled, setConfiguredRuntimeSwitches, maxAgentsPerMachine } from "./runtime-switches.js";
180
+ import { RuntimeCoordinator } from "./runtime-coordinator.js";
181
+ import { RuntimeWindowsBridge } from "./runtime-windows-bridge.js";
182
+ import { FleetWindowsRuntime } from "./fleet-windows-runtime.js";
183
+ import { NativeMcpSessions } from "./native-mcp.js";
184
+ import { nativeProviderMcpSupported } from "./windows-cli-providers.js";
185
+ import { isWindowsInstance, isNativePaneInstance, isNativeExecInstance, WINDOWS_CONFIG_REFUSED } from "./native-instance-registry.js";
186
+ import { createNativeWindowsInstance, updateNativeWindowsInstance } from "./native-instance-api.js";
187
+ import { NativeInstanceConfigEdit, parseNativeInstanceCreate } from "./native-instance-input.js";
170
188
  import { LogService } from "./log-service.js";
171
189
  import { WebChatHistory, WEB_CHAT_TEXT_MAX, isWebMessageId, newWebMessageId, webChatFrom } from "./web-chat-history.js";
172
190
  import { WEB_ECHO_COPIES_MAX, WEB_MIRROR_TIMEOUT_MS, webReactionEmoji, webReactionProblem } from "./web-reactions.js";
@@ -728,6 +746,518 @@ export class FleetManager {
728
746
  /** Live view of lifecycle.daemons — used throughout; not deprecated. */
729
747
  get daemons() { return this.lifecycle.daemons; }
730
748
  fleetConfig = null;
749
+ fleetWindowsRuntime = null;
750
+ nativeMcpStore = null;
751
+ paneManager = null;
752
+ localPaneRelay = null;
753
+ openingLocalPane = null;
754
+ runtimeHub = null;
755
+ runtimeExecutor = null;
756
+ runtimeStatusScheduled = false;
757
+ reconciledRemoteResults = new Set();
758
+ /** Machine credentials are separate from dashboard sessions and per-turn agent capabilities. */
759
+ runtimeCoordinator() {
760
+ if (!remoteRuntimeEnabled())
761
+ throw new Error("Remote runtimes are not enabled");
762
+ return this.runtimeHub ??= new RuntimeCoordinator(this.dataDir, {
763
+ handleMcp: (machineId, runId, body) => this.runtimeWindowsExecutor().mcp(machineId, runId, body),
764
+ handlePaneMcp: async (machineId, body, current) => (await this.nativePanes()).handle(machineId, body, current),
765
+ changed: event => {
766
+ if (event?.type === "machine" && this.runtimeHub?.machine(event.machineId)?.status !== "online") {
767
+ for (const [name, config] of Object.entries(this.fleetConfig?.instances ?? {}))
768
+ if (config.machine_id === event.machineId)
769
+ this.nativeMcpStore?.closeInstance(name);
770
+ }
771
+ if (this.runtimeStatusScheduled || this.shuttingDown)
772
+ return;
773
+ this.runtimeStatusScheduled = true;
774
+ queueMicrotask(() => {
775
+ this.runtimeStatusScheduled = false;
776
+ if (this.shuttingDown)
777
+ return;
778
+ try {
779
+ this.fleetWindowsRuntime?.importHistory();
780
+ for (const snapshot of this.runtimeHub?.tasks() ?? [])
781
+ this.reconcileRemoteDelivery(snapshot.machineId, snapshot.task);
782
+ this.emitSseEvent("status", this.getUiStatus());
783
+ this.scheduleDeliveryOutboxPump();
784
+ }
785
+ catch (error) {
786
+ this.logger.warn({ err: error }, "Runtime status reconciliation failed");
787
+ }
788
+ });
789
+ },
790
+ });
791
+ }
792
+ runtimeWindowsExecutor() {
793
+ return this.runtimeExecutor ??= new RuntimeWindowsBridge(this, () => remoteRuntimeEnabled() && !this.shuttingDown ? this.runtimeCoordinator() : null);
794
+ }
795
+ /** Per-turn capabilities for native MCP, separate from browser and Linux-agent credentials. */
796
+ nativeMcpSessions() {
797
+ return this.nativeMcpStore ??= new NativeMcpSessions(this);
798
+ }
799
+ /** The same instance registry and delivery entry point, with a native Windows execution adapter. */
800
+ nativeInstanceRuntime() {
801
+ return this.fleetWindowsRuntime ??= new FleetWindowsRuntime({
802
+ owner: this, fleet: () => this.fleetConfig, save: () => this.saveFleetConfig(), stopping: () => this.shuttingDown, history: this.webChatHistory,
803
+ openMcp: (instance, requestId, sandbox, stillCurrent) => this.healthServerListening
804
+ ? this.nativeMcpSessions().grant({ instance, requestId, sandbox, stillCurrent, endpoint: `http://127.0.0.1:${this.fleetConfig?.health_port ?? 19280}/agent` }) : null,
805
+ warn: (message, error) => this.logger.warn({ err: error }, message),
806
+ removed: name => this.nativeInstanceRemoved(name),
807
+ paneCurrent: name => this.daemons.get(name)?.nativePaneCurrent?.() === true,
808
+ machineAvailable: id => remoteRuntimeEnabled() && this.runtimeCoordinator().machine(id)?.status === "online",
809
+ validateMachine: (id, cli, sandbox) => {
810
+ const machine = this.runtimeCoordinator().machine(id);
811
+ const provider = machine?.inventory?.clis.find(row => row.id === cli && row.runnable && row.supports.resume);
812
+ if (!machine || machine.status !== "online" || machine.platform !== "win32" || !provider)
813
+ throw new Error("Choose an online runtime and an available CLI");
814
+ if (sandbox === "read-only" && !provider.readOnlySupport)
815
+ throw new Error("This runtime CLI cannot enforce read-only execution");
816
+ },
817
+ emit: (event, data) => {
818
+ this.emitSseEvent(event, event === "status" ? this.getUiStatus() : data);
819
+ if (event === "activity")
820
+ this.scheduleDeliveryOutboxPump();
821
+ },
822
+ }, { bridge: this.runtimeWindowsExecutor() });
823
+ }
824
+ /** Explicit dashboard opt-in, with the same General confirmation and ordinary topic/Daemon routing. */
825
+ async createNativePaneInstance(profile, channelId, authorized, execution) {
826
+ const { createNativePane } = await import("./native-pane-create.js");
827
+ return createNativePane({ fleet: () => this.fleetConfig, runtime: this.nativeInstanceRuntime(),
828
+ topic: target => {
829
+ const world = this.worlds.get(target.id), adapter = world?.adapter;
830
+ if (!world || !adapter?.createTopic || !adapter.deleteTopic)
831
+ throw Error("The approved connection cannot create and compensate a topic");
832
+ const same = () => {
833
+ const channels = this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : []);
834
+ const matches = channels.filter(row => (row.id ?? row.type) === target.id);
835
+ return !this.shuttingDown && this.worlds.get(target.id) === world && world.adapter === adapter && matches.length === 1 && matches[0].type === target.type && matches[0].group_id === target.group_id;
836
+ };
837
+ return { current: same, create: name => adapter.createTopic(name), remove: id => adapter.deleteTopic(id) };
838
+ },
839
+ epoch: name => this.lifecycle.epochOf(name),
840
+ start: async (name, config, current) => {
841
+ if (!current())
842
+ throw Error("Native creation expired before start");
843
+ await this.startInstance(name, config, true);
844
+ if (!current())
845
+ throw Error("Native creation expired during start");
846
+ await this.connectIpcToInstance(name);
847
+ if (!current())
848
+ throw Error("Native creation expired during channel connection");
849
+ },
850
+ close: (name, config, epoch) => this.lifecycle.runTransition(name, async (transition) => {
851
+ if (this.fleetConfig?.instances[name] !== config || this.lifecycle.epochOf(name) !== epoch)
852
+ return false;
853
+ await this.stopInstance(name, transition);
854
+ return this.fleetConfig?.instances[name] === config && this.lifecycle.epochOf(name) === epoch + 1;
855
+ }),
856
+ routes: () => { if (this.fleetConfig)
857
+ this.routing.rebuild(this.fleetConfig); },
858
+ warn: error => this.logger.warn({ err: error }, "Native creation cleanup is held; retained identity and topic require attention"), }, profile, channelId, () => !this.shuttingDown && authorized(), execution);
859
+ }
860
+ /** Admin-only settings path: validate first, then close the exact old launch before changing cold fields. */
861
+ async updateNativePaneConfig(name, patch, authorized, transaction) {
862
+ return this.lifecycle.runTransition(name, async (transition) => {
863
+ const before = this.fleetConfig?.instances[name];
864
+ if (!isNativePaneInstance(before))
865
+ throw Error("Persistent Windows instance not found");
866
+ const runtime = this.nativeInstanceRuntime(), generation = this.explicitStopGeneration.get(name) ?? 0;
867
+ let expectedGeneration = generation;
868
+ const current = () => authorized() && !this.shuttingDown && this.fleetConfig?.instances[name] === before
869
+ && (this.explicitStopGeneration.get(name) ?? 0) === expectedGeneration
870
+ && (before.machine_id ? remoteRuntimeEnabled() : localWindowsCliEnabled());
871
+ if (!current())
872
+ throw Error("Native settings authority expired");
873
+ runtime.previewUpdate(name, patch);
874
+ const cold = ["working_directory", "model", "systemPrompt", "native_sandbox"].some(key => Object.hasOwn(patch, key) && (patch[key] ?? "") !== (before[key] ?? ""));
875
+ const running = this.daemons.get(name);
876
+ if (cold) {
877
+ if (running && !running.nativePaneHasExited() && (!await running.nativePauseReady({ current, accepts: () => true, revalidate: async () => current() }) || !running.nativePauseStillReady()))
878
+ throw Error("Wait for this Windows agent to be idle before changing its launch settings");
879
+ if (!current())
880
+ throw Error("Native settings authority expired before closing its old pane");
881
+ await this.stopInstance(name, transition);
882
+ expectedGeneration = generation + 1;
883
+ if (!current())
884
+ throw Error("Native settings authority expired after closing its old pane");
885
+ if (runtime.get(name)?.activeId)
886
+ throw Error("Native pane exit is unconfirmed; its settings were not changed");
887
+ }
888
+ const result = runtime.update(name, patch, current, transaction), next = this.fleetConfig.instances[name];
889
+ if (cold && running) {
890
+ if (!authorized() || this.shuttingDown || this.fleetConfig?.instances[name] !== next || (this.explicitStopGeneration.get(name) ?? 0) !== expectedGeneration)
891
+ throw Error("Native settings saved; restart authority expired");
892
+ await this.startInstance(name, next, true, "fleet-topic", false, transition);
893
+ }
894
+ return result;
895
+ });
896
+ }
897
+ async localNativePaneEndpoint() {
898
+ const admit = () => { if (this.shuttingDown || this.localPaneDrain || !localWindowsCliEnabled())
899
+ throw Error("Local Windows runtime is switched off or draining"); };
900
+ admit();
901
+ if (this.localPaneDrain)
902
+ throw Error("Local Windows runtime cleanup is pending");
903
+ if (this.localPaneRelay?.online())
904
+ return this.localPaneRelay;
905
+ if (this.openingLocalPane)
906
+ return this.openingLocalPane;
907
+ const opening = (async () => {
908
+ const [{ LocalPaneEndpoint }, { launchLocalPaneRelay, localPaneScope }, inventory] = await Promise.all([
909
+ import("./local-pane-endpoint.js"), import("./local-pane-launcher.js"), this.runtimeWindowsExecutor().probe({ force: false }),
910
+ ]);
911
+ admit();
912
+ this.localPaneRelay?.stop();
913
+ const relay = await LocalPaneEndpoint.open({ scope: localPaneScope(this.dataDir), admit, maxAgents: maxAgentsPerMachine,
914
+ diagnostic: text => this.logger.warn({ stderr: text }, "Local Windows pane relay wrote to stderr"),
915
+ version: provider => inventory.clis.find(row => row.id === provider && row.runnable)?.version ?? null,
916
+ launch: () => launchLocalPaneRelay(this.dataDir, admit),
917
+ mcp: async (body, current) => (await this.nativePanes()).handle("local", body, current),
918
+ });
919
+ try {
920
+ admit();
921
+ this.localPaneRelay = relay;
922
+ return relay;
923
+ }
924
+ catch (error) {
925
+ relay.stop();
926
+ throw error;
927
+ }
928
+ })();
929
+ this.openingLocalPane = opening;
930
+ try {
931
+ return await opening;
932
+ }
933
+ finally {
934
+ if (this.openingLocalPane === opening)
935
+ this.openingLocalPane = null;
936
+ }
937
+ }
938
+ /** Persistent ownership is independent of the exec worker's per-turn capabilities. */
939
+ async nativePanes() {
940
+ if (this.paneManager)
941
+ return this.paneManager;
942
+ const { NativePaneManager } = await import("./native-pane-manager.js");
943
+ if (this.paneManager)
944
+ return this.paneManager;
945
+ const store = windowsAgentsFor(this, this.runtimeWindowsExecutor());
946
+ const allowed = (name, tool) => nativePaneToolAvailable(tool) && mayUseTool(resolveToolSet(this.fleetConfig?.instances[name], name), tool)
947
+ && !dashboardRefusalIn(this.fleetConfig, name, tool);
948
+ return this.paneManager = new NativePaneManager({
949
+ home: this.dataDir, store, config: name => this.fleetConfig?.instances[name],
950
+ freshNotice: async (name, provider) => {
951
+ const message = t(provider === "codex" ? "native_pane.codex_fresh_started" : "native_pane.fresh_started");
952
+ this.eventLog?.insert(name, "native_pane_fresh_started", {});
953
+ this.logger.info({ name }, message);
954
+ await this.notifyInstanceTopic(name, message);
955
+ },
956
+ admit: (name, config) => {
957
+ const now = this.fleetConfig?.instances[name];
958
+ if (this.shuttingDown || !isNativePaneInstance(now) || now?.native_agent_id !== config.native_agent_id
959
+ || now?.machine_id !== config.machine_id || now?.working_directory !== config.working_directory || now?.backend !== config.backend
960
+ || now?.model !== config.model || now?.native_sandbox !== config.native_sandbox || now?.systemPrompt !== config.systemPrompt || now?.workflow !== config.workflow)
961
+ throw Error("Native pane configuration or lifetime changed");
962
+ if (config.machine_id ? this.remotePaneDrain || !remoteRuntimeEnabled() : this.localPaneDrain || !localWindowsCliEnabled())
963
+ throw Error("Native runtime is switched off");
964
+ },
965
+ endpoint: async (agent, cleanup = false) => {
966
+ if (!agent.machineId) {
967
+ if (!cleanup)
968
+ return this.localNativePaneEndpoint();
969
+ if (!this.localPaneRelay)
970
+ throw Error("Local cleanup transport is unavailable; pane remains reserved");
971
+ return this.localPaneRelay;
972
+ }
973
+ const hub = cleanup ? this.runtimeHub : this.runtimeCoordinator(), machineId = agent.machineId;
974
+ if (!hub)
975
+ throw Error("Remote cleanup transport is unavailable; pane remains reserved");
976
+ const draining = () => !!this.remotePaneDrain;
977
+ return { machineId, online: () => !this.shuttingDown && (draining() || remoteRuntimeEnabled()) && this.runtimeHub === hub && hub.machine(machineId)?.status === "online",
978
+ call: request => {
979
+ if (this.runtimeHub !== hub || !(draining() || remoteRuntimeEnabled()) || draining() && request.action !== "snapshot" && request.action !== "close")
980
+ return Promise.reject(Error("Remote pane transport only permits owned cleanup"));
981
+ return hub.callPane(machineId, request, maxAgentsPerMachine());
982
+ }, subscribe: listener => hub.subscribe(listener),
983
+ epoch: () => hub.paneTransportEpoch(machineId), version: provider => hub.machine(machineId)?.inventory?.clis.find(row => row.id === provider && row.runnable)?.version ?? null };
984
+ },
985
+ instructions: (name, config) => buildFleetInstructions({ instanceName: name, workingDirectory: config.working_directory,
986
+ displayName: config.display_name, description: config.description, customPrompt: config.systemPrompt,
987
+ workflow: config.workflow, taskBoard: !dashboardRefusalIn(this.fleetConfig, name, "task"),
988
+ runtimeIdentity: { kind: "fleet-topic", backend: config.backend ?? "codex", model: config.model || "default" } }),
989
+ menu: name => TOOLS.filter(tool => allowed(name, tool.name)).map(tool => ({ ...tool })),
990
+ allowed: (name, tool, args) => allowed(name, tool) && !nativePaneToolRefusal(tool, args), published: (name, daemon) => this.daemons.get(name) === daemon,
991
+ invoke: async (daemon, input, current, name) => {
992
+ if (input.tool === "list_models") {
993
+ const { ListModelsArgs } = await import("./outbound-schemas.js");
994
+ const parsed = ListModelsArgs.safeParse(input.args);
995
+ if (!parsed.success || !current())
996
+ return { result: null, error: "Invalid native model request or expired owner" };
997
+ const target = parsed.data.instance_name ?? name;
998
+ if (!isWindowsInstance(this.fleetConfig?.instances[target]))
999
+ return { result: null, error: "Native panes can only read Windows runtime model inventories" };
1000
+ return { result: await this.listModelCatalog({ instanceName: target }) };
1001
+ }
1002
+ const ops = { list_instances: "list", describe_instance: "describe", task: "task", send_to_instance: "send",
1003
+ delegate_task: "delegate", request_information: "ask", report_result: "report", delivery_status: "delivery-status" };
1004
+ const { nativeAgentAdmissionContext } = await import("./native-agent-admission.js");
1005
+ const { dispatchAgentOperation } = await import("./agent-endpoint.js");
1006
+ return daemon.handlePaneToolCall(input, { current, allowed: tool => allowed(name, tool),
1007
+ dispatch: (tool, args) => ops[tool] ? dispatchAgentOperation(nativeAgentAdmissionContext(this, {
1008
+ instance: name, current: () => current() && allowed(name, tool), taskAllowed: () => current() && allowed(name, "task"),
1009
+ }), name, ops[tool], args, input.operationId) : undefined });
1010
+ },
1011
+ });
1012
+ }
1013
+ async nativePaneFor(name, config, assertCurrent) {
1014
+ return (await this.nativePanes()).prepare(name, config, assertCurrent);
1015
+ }
1016
+ async recoverNativePane(name, launchId, authorized) {
1017
+ const config = this.fleetConfig?.instances[name];
1018
+ if (!isNativePaneInstance(config))
1019
+ throw Error("Persistent Windows pane is not configured");
1020
+ const generation = this.explicitStopGeneration.get(name) ?? 0;
1021
+ await this.lifecycle.runTransition(name, async (transition) => {
1022
+ const current = () => { if (!authorized() || this.shuttingDown || this.fleetConfig?.instances[name] !== config || (this.explicitStopGeneration.get(name) ?? 0) !== generation)
1023
+ throw Error(t("native_pane.authority_expired")); };
1024
+ current();
1025
+ if (this.daemons.get(name)?.nativePaneCurrent())
1026
+ throw Error(t("native_pane.still_running"));
1027
+ this.cancelStartupRetry(name);
1028
+ this.lifecycle.cancelNativeRecovery(name);
1029
+ await (await this.nativePanes()).recover(name, config, launchId, true, current);
1030
+ current();
1031
+ // The host supplied a positive exit before ordinary lifecycle cleanup. No signal or input is required.
1032
+ await this.lifecycle.stop(name, transition);
1033
+ current();
1034
+ this.instanceProcessStatus.set(name, "stopped");
1035
+ this.eventLog?.insert(name, "native_pane_recovered", { launchId, confirmedStopped: true });
1036
+ this.logger.info({ name, launchId }, "Windows saved pane recovered by an administrator; waiting for Start");
1037
+ this.emitSseEvent("status", this.getUiStatus());
1038
+ });
1039
+ }
1040
+ holdNativePaneRecovery(name, error) {
1041
+ if (!(error instanceof NativePaneRecoveryError))
1042
+ return false;
1043
+ this.instanceProcessStatus.set(name, "stopped");
1044
+ this.logger.warn({ name, recovery: error.recovery }, error.message);
1045
+ this.eventLog?.insert(name, "native_pane_recovery_needed", { ...error.recovery });
1046
+ this.needsYou?.poke();
1047
+ this.emitSseEvent("status", this.getUiStatus());
1048
+ return true;
1049
+ }
1050
+ async startFreshNativePane(name, launchId, sessionId, authorized) {
1051
+ const config = this.fleetConfig?.instances[name];
1052
+ if (!isNativePaneInstance(config))
1053
+ throw Error("Persistent Windows pane is not configured");
1054
+ const generation = this.explicitStopGeneration.get(name) ?? 0;
1055
+ await this.lifecycle.runTransition(name, async (transition) => {
1056
+ const current = () => { if (!authorized() || this.shuttingDown || this.fleetConfig?.instances[name] !== config || (this.explicitStopGeneration.get(name) ?? 0) !== generation)
1057
+ throw Error(t("native_pane.authority_expired")); };
1058
+ current();
1059
+ const held = windowsAgentsFor(this, this.runtimeWindowsExecutor()).get(config.native_agent_id).paneRecovery;
1060
+ if (held?.kind !== "session-unconfirmed" || held.launchId !== launchId || (held.sessionId ?? null) !== sessionId)
1061
+ throw Error(t("native_pane.changed"));
1062
+ if (this.daemons.get(name)?.nativePaneCurrent())
1063
+ throw Error(t("native_pane.still_running"));
1064
+ this.cancelStartupRetry(name);
1065
+ this.lifecycle.cancelNativeRecovery(name);
1066
+ await this.lifecycle.stop(name, transition);
1067
+ current();
1068
+ try {
1069
+ await (await this.nativePanes()).startFresh(name, config, launchId, sessionId, current);
1070
+ current();
1071
+ }
1072
+ catch (error) {
1073
+ current();
1074
+ this.needsYou?.poke();
1075
+ this.emitSseEvent("status", this.getUiStatus());
1076
+ throw error;
1077
+ }
1078
+ this.eventLog?.insert(name, "native_pane_new_conversation", { launchId, sessionId });
1079
+ await this.startInstance(name, config, true, "fleet-topic", true, transition);
1080
+ current();
1081
+ this.needsYou?.poke();
1082
+ this.emitSseEvent("status", this.getUiStatus());
1083
+ });
1084
+ }
1085
+ nativePanePinnedSession(name) {
1086
+ const config = this.fleetConfig?.instances[name];
1087
+ if (!isNativePaneInstance(config) || !config.native_agent_id)
1088
+ return null;
1089
+ const agent = windowsAgentsFor(this, this.runtimeWindowsExecutor()).get(config.native_agent_id);
1090
+ return agent.fleet_instance === name && agent.machineId === config.machine_id ? agent.threadId ?? null : null;
1091
+ }
1092
+ onNativePaneRecoverySettled(name) {
1093
+ if (this.daemons.get(name)?.nativePaneCurrent())
1094
+ this.instanceProcessStatus.delete(name);
1095
+ this.onAttentionChanged(name);
1096
+ this.scheduleDeliveryOutboxPump();
1097
+ }
1098
+ setNativePanePaused(name, paused) {
1099
+ const id = this.fleetConfig?.instances[name]?.native_agent_id;
1100
+ if (!id)
1101
+ throw Error("Native profile is missing");
1102
+ windowsAgentsFor(this, this.runtimeWindowsExecutor()).setPaused(id, paused);
1103
+ }
1104
+ async closeRetainedNativePane(name) {
1105
+ const config = this.fleetConfig?.instances[name];
1106
+ if (!config)
1107
+ throw Error("Native instance configuration is missing");
1108
+ await (await this.nativePanes()).closeRetained(name, config, () => { if (this.shuttingDown)
1109
+ throw Error("Fleet is stopping"); });
1110
+ }
1111
+ async reconcileNativePaneBeforeStart(name, daemon) {
1112
+ const outbox = this.deliveryOutbox;
1113
+ if (!outbox)
1114
+ return true;
1115
+ const epoch = this.lifecycle.epochOf(name);
1116
+ return daemon.reconcileNativeDeliveries(outbox, () => !this.shuttingDown && this.lifecycle.epochOf(name) === epoch);
1117
+ }
1118
+ /** M-2255: the Windows runtimes' switches the fleet's own state reflects (null until the start-up has applied them). */
1119
+ runtimeEffects = null;
1120
+ runtimeEffectsScheduled = false;
1121
+ localPaneDrain = null;
1122
+ remotePaneDrain = null;
1123
+ /** Revoke admissions synchronously; keep the captured transport only long enough for exact owned closes. */
1124
+ drainNativeRuntime(remote) {
1125
+ const existing = remote ? this.remotePaneDrain : this.localPaneDrain;
1126
+ if (existing)
1127
+ return existing;
1128
+ const hub = this.runtimeHub, relay = this.localPaneRelay;
1129
+ if (!remote)
1130
+ relay?.beginCleanup();
1131
+ const targets = Object.entries(this.fleetConfig?.instances ?? {}).filter(([, config]) => isWindowsInstance(config) && !!config.machine_id === remote);
1132
+ for (const [name, config] of targets) {
1133
+ this.nativeMcpStore?.closeInstance(name);
1134
+ if (isNativePaneInstance(config)) {
1135
+ this.lifecycle.cancelNativeRecovery(name);
1136
+ this.lifecycle.invalidate(name);
1137
+ this.daemons.get(name)?.fenceDeliveryWritesForStop();
1138
+ }
1139
+ }
1140
+ const run = Promise.resolve().then(async () => {
1141
+ const results = await Promise.allSettled(targets.filter(([, config]) => isNativePaneInstance(config)).map(([name]) => this.stopInstance(name)));
1142
+ results.forEach((result, index) => { if (result.status === "rejected")
1143
+ this.logger.warn({ err: result.reason, remote, index }, "Windows pane cleanup unconfirmed; its reservation is retained"); });
1144
+ try {
1145
+ if (remote)
1146
+ await hub?.shutdownPaneHosts();
1147
+ else
1148
+ await relay?.shutdownHost();
1149
+ }
1150
+ catch (error) {
1151
+ this.logger.warn({ err: error, remote }, "Windows pane host shutdown unconfirmed; owned state is retained");
1152
+ }
1153
+ }).finally(() => {
1154
+ if (remote) {
1155
+ hub?.close();
1156
+ if (this.runtimeHub === hub)
1157
+ this.runtimeHub = null;
1158
+ }
1159
+ else {
1160
+ relay?.stop();
1161
+ if (this.localPaneRelay === relay)
1162
+ this.localPaneRelay = null;
1163
+ closeWindowsBridge(this);
1164
+ }
1165
+ });
1166
+ if (remote)
1167
+ this.remotePaneDrain = run;
1168
+ else
1169
+ this.localPaneDrain = run;
1170
+ void run.finally(() => {
1171
+ if (remote) {
1172
+ if (this.remotePaneDrain === run)
1173
+ this.remotePaneDrain = null;
1174
+ }
1175
+ else if (this.localPaneDrain === run)
1176
+ this.localPaneDrain = null;
1177
+ if (!this.shuttingDown && (remote ? remoteRuntimeEnabled() : localWindowsCliEnabled())) {
1178
+ this.initializeNativeInstances();
1179
+ this.initializeNativeInstances(true);
1180
+ }
1181
+ try {
1182
+ this.emitSseEvent("status", this.getUiStatus());
1183
+ }
1184
+ catch { }
1185
+ }).catch(error => this.logger.warn({ err: error }, "Windows runtime cleanup failed; retained ownership requires reconciliation"));
1186
+ return run;
1187
+ }
1188
+ /**
1189
+ * M-2255: Settings → Runtime's switches (fleet.yaml `runtimes`, or their environment override) in force — set each time
1190
+ * the configuration in force changes (a load, a save, a reload's rollback), next to the web authority's. Every gate reads
1191
+ * them at once; what the fleet itself holds (paired computers' connections, this PC's Windows process, agents' tool
1192
+ * sessions) follows just after, outside the save that changed them (applyRuntimeSwitches): no restart is needed.
1193
+ */
1194
+ observeRuntimeSwitches() {
1195
+ setConfiguredRuntimeSwitches(this.fleetConfig?.runtimes);
1196
+ if (!this.runtimeEffects || this.runtimeEffectsScheduled)
1197
+ return;
1198
+ this.runtimeEffectsScheduled = true;
1199
+ queueMicrotask(() => { this.runtimeEffectsScheduled = false; this.applyRuntimeSwitches(); });
1200
+ }
1201
+ /**
1202
+ * Turned off: what it allowed ends now — the paired computers' connections (their agents show as offline) or this PC's
1203
+ * Windows process (a turn in progress is cancelled, as when the fleet stops), and those agents' tool sessions.
1204
+ * Turned on: the Windows agents are registered and their chat history read, as at start-up.
1205
+ */
1206
+ applyRuntimeSwitches() {
1207
+ const was = this.runtimeEffects;
1208
+ if (!was || this.shuttingDown)
1209
+ return;
1210
+ const now = { windows_cli: localWindowsCliEnabled(), remote_runtime: remoteRuntimeEnabled() };
1211
+ if (was.windows_cli === now.windows_cli && was.remote_runtime === now.remote_runtime)
1212
+ return;
1213
+ this.runtimeEffects = now;
1214
+ try {
1215
+ if (was.remote_runtime && !now.remote_runtime) {
1216
+ void this.drainNativeRuntime(true);
1217
+ }
1218
+ if (was.windows_cli && !now.windows_cli) {
1219
+ void this.drainNativeRuntime(false);
1220
+ }
1221
+ if (!was.windows_cli && !was.remote_runtime && !this.localPaneDrain && !this.remotePaneDrain) {
1222
+ this.initializeNativeInstances();
1223
+ this.initializeNativeInstances(true);
1224
+ }
1225
+ this.logger.info({ windows_cli: now.windows_cli, remote_runtime: now.remote_runtime }, "Windows runtime switches applied");
1226
+ }
1227
+ catch (error) {
1228
+ this.logger.warn({ err: error }, "Windows runtime switches could not be applied in full");
1229
+ }
1230
+ try {
1231
+ this.emitSseEvent("status", this.getUiStatus());
1232
+ }
1233
+ catch { /* the next status frame carries it */ }
1234
+ }
1235
+ /** An unavailable opt-in runtime must not prevent the Linux fleet from starting or restoring its own history. */
1236
+ initializeNativeInstances(history = false) {
1237
+ if (!windowsCliEnabled())
1238
+ return;
1239
+ try {
1240
+ const runtime = this.nativeInstanceRuntime();
1241
+ if (history)
1242
+ runtime.importHistory();
1243
+ else
1244
+ runtime.register();
1245
+ }
1246
+ catch (error) {
1247
+ this.logger.warn({ err: error }, "Windows runtime state could not be loaded; affected native instances remain unavailable");
1248
+ }
1249
+ }
1250
+ /** Native CLIs are headless: the manager owns the durable delivery generation instead of a tmux daemon. */
1251
+ deliveryBootId(name) {
1252
+ const config = this.fleetConfig?.instances[name];
1253
+ if (isNativeExecInstance(config)) {
1254
+ if (!this.nativeInstanceRuntime().available(name))
1255
+ return null;
1256
+ const machine = config.machine_id ? this.runtimeCoordinator().machine(config.machine_id) : null;
1257
+ return machine ? `runtime:${machine.id}:${machine.bootId ?? machine.generation}` : this.managerBootId;
1258
+ }
1259
+ return this.daemons.get(name)?.bootId ?? null;
1260
+ }
731
1261
  rawFleetConfig = {};
732
1262
  rawFleetDocument = null;
733
1263
  /** #2208: the paths a saveFleetConfig() wrote, for its check that nothing else changed. */
@@ -1495,6 +2025,8 @@ export class FleetManager {
1495
2025
  * authorizing revoked links and cookies until the fleet restarted.
1496
2026
  */
1497
2027
  settingsGeneration = 0;
2028
+ /** #2942: the settings baseline the agent-requested Windows changes are checked against (set with the gate). */
2029
+ settingsBaselineReader = null;
1498
2030
  settingsRuntimeRevision = 0;
1499
2031
  settingsConfirmation = null;
1500
2032
  settingsControl = null;
@@ -1551,7 +2083,7 @@ export class FleetManager {
1551
2083
  return this.settingsConfirmation;
1552
2084
  const generation = this.settingsGeneration;
1553
2085
  const current = () => !this.shuttingDown && this.settingsGeneration === generation;
1554
- const baselines = new SettingsBaselines({ dataDir: this.dataDir, configPath: () => this.configPath,
2086
+ const baselines = this.settingsBaselineReader = new SettingsBaselines({ dataDir: this.dataDir, configPath: () => this.configPath,
1555
2087
  config: () => this.fleetConfig, current: () => [this.settingsGeneration, this.settingsRuntimeRevision, this.shuttingDown],
1556
2088
  proof: (kind, target, id, binding, key) => {
1557
2089
  const map = kind === "provider" ? this.providerSecretChallenges : kind === "binding" ? this.connectionBindingChallenges : this.connectionSecretChallenges;
@@ -1573,6 +2105,17 @@ export class FleetManager {
1573
2105
  const store = new SettingsConfirmationStore({
1574
2106
  audit: (event, fields) => this.logger.info({ event: `settings_confirmation.${event}`, ...fields }, "Settings confirmation audit"),
1575
2107
  notify: view => view.state === "pending" ? this.promptSettingsChange(store, view) : Promise.resolve(),
2108
+ // alpha.2 (M-2300): an agent's request came, was decided or ran out — the open pages read the list again (a ping
2109
+ // with nothing in it: only a web-only fleet's verified owner may read it, GET /ui/agent-proposals).
2110
+ changed: view => {
2111
+ if (view.source !== "agent_request")
2112
+ return;
2113
+ this.agentProposalsRev++;
2114
+ broadcastSseEvent(this.sseClients, "agent_proposals", {}, err => this.logger.debug({ err }, "agent_proposals event failed"));
2115
+ // …and a status frame with the new revision: a tab from before the upgrade (its stream knows no `agent_proposals`)
2116
+ // applies status frames, and a polling page reads the same frame at its next /ui/poll.
2117
+ this.emitSseEvent("status", this.getUiStatus());
2118
+ },
1576
2119
  });
1577
2120
  const gate = new SettingsHttpConfirmation(store, {
1578
2121
  principal: req => {
@@ -1664,6 +2207,76 @@ export class FleetManager {
1664
2207
  done: view => this.settingsWebConfirmedNotice(view, affected, name, chat.platform),
1665
2208
  };
1666
2209
  }
2210
+ /**
2211
+ * alpha.2 (M-2300; #2973's agent requests, junsion's §6): on a fleet with no chat connection there is no General to
2212
+ * post an agent's Windows request in — it waited for `agend settings confirm` on the host. Its owner (decision
2213
+ * 28c2d3a3: the `agend web --code` session on this computer, web-verified) now sees and decides it from the web's
2214
+ * Needs you. The authority is asked from the request each time, and again at execution by the store (`current`):
2215
+ * still that owner now (this computer, not revoked, signed out or run out), still a fleet with no chat connection,
2216
+ * never over the public link. Anyone else — a guest, a LAN or public-link session, a chat-connected fleet's admin —
2217
+ * gets nothing (a chat fleet keeps General's card as its way).
2218
+ */
2219
+ /**
2220
+ * #2997 review (Prism P2): how many times an agent's request came or was decided — in a web-only fleet's status frame,
2221
+ * so a page that never hears the stream's ping (one polling /ui/poll, one reconnecting, a tab from before the upgrade)
2222
+ * reads the list again when it moves. A count, nothing more.
2223
+ */
2224
+ agentProposalsRev = 0;
2225
+ agentProposalOwner(req) {
2226
+ if (!this.isWebOnlyFleet() || gatewayRequestContext(req))
2227
+ return null;
2228
+ const auth = authorizeSession(req, this.webToken, this.webSessions, { touch: false });
2229
+ if (auth.kind !== "ok")
2230
+ return null;
2231
+ // Each check twice over: here, and in the authority itself (it refuses the public link and a chat fleet's session).
2232
+ // #2997 review (Prism P2): asked of the LIVE session each time — the request's own cookie authenticated again, the
2233
+ // same handle — never a record captured at the click: a sign-out, an expiry or a revocation while the request is
2234
+ // being applied fences it.
2235
+ const handle = auth.session.handle, peer = webPeerOf(req), gateway = !!gatewayRequestContext(req);
2236
+ const owner = () => {
2237
+ if (!this.isWebOnlyFleet())
2238
+ return false;
2239
+ const live = authorizeSession(req, this.webToken, this.webSessions, { touch: false });
2240
+ if (live.kind !== "ok" || live.session.handle !== handle)
2241
+ return false;
2242
+ const now = this.webAuthority({ session: live.session, gateway, peer });
2243
+ const a = now.authority.kind === "web-verified" && now.authority.session?.handle === handle ? now.authority.actor : null;
2244
+ return !!a && a.adapter_id === HOST_OPERATOR.adapterId && a.user_id === HOST_OPERATOR.userId;
2245
+ };
2246
+ return owner() ? { label: t("settings_confirm.host_operator_label"), current: owner } : null;
2247
+ }
2248
+ /** alpha.2: the agents' requests waiting for this web-only fleet's owner, or null when the reader is not that owner. */
2249
+ webAgentProposals(req) {
2250
+ if (!this.agentProposalOwner(req))
2251
+ return null;
2252
+ return this.settingsGate().store.listBySource("agent_request");
2253
+ }
2254
+ /**
2255
+ * alpha.2: the owner's Approve or Reject of an agent's request. The store claims it before its first await (a second
2256
+ * click, from here or the host, finds it no longer pending) and asks the owner, the request (its fleet generation and
2257
+ * Windows switch) and the target machine again before and while it runs the same creation code the web uses. The
2258
+ * answer is always the request as it now stands: applied (with the new agent's name), rejected, expired, stale or
2259
+ * failed (with its reason), or already decided.
2260
+ */
2261
+ async webDecideAgentProposal(req, id, decision) {
2262
+ const owner = this.agentProposalOwner(req);
2263
+ if (!owner)
2264
+ return { status: 403, body: { error: "owner_required", message: t("settings_confirm.web_owner_required") } };
2265
+ const store = this.settingsGate().store, before = store.inspect(id);
2266
+ if (!before || before.source !== "agent_request")
2267
+ return { status: 404, body: { error: "not_found", message: t("settings_confirm.no_longer_pending") } };
2268
+ try {
2269
+ const view = await store.decide(id, decision, { ...owner, via: "web" });
2270
+ this.logger.info({ id, decision, state: view.state }, "Agent request decided from the web by this computer's admin");
2271
+ return { status: 200, body: { view, agent: agentNameOf(view) } };
2272
+ }
2273
+ catch (err) {
2274
+ const code = err instanceof SettingsConfirmationError ? err.code : "decide_failed";
2275
+ const status = err instanceof SettingsConfirmationError ? err.status : 500;
2276
+ // Already decided (a double click, the host's command), run out or withdrawn: say what it is now.
2277
+ return { status, body: { error: code, message: t(code === "admin_required" ? "settings_confirm.web_owner_required" : "settings_confirm.no_longer_pending"), view: store.inspect(id) } };
2278
+ }
2279
+ }
1667
2280
  /** #2170: the Generals a chat card could be posted in (promptSettingsChange's rule): connected, on the platform, unaffected. */
1668
2281
  settingsNoticeGenerals(affected, platform) {
1669
2282
  const excluded = new Set(affected), found = [];
@@ -1736,6 +2349,119 @@ export class FleetManager {
1736
2349
  return { platform, current, admin: userId => current() && !!userId
1737
2350
  && identities.every(owner => this.isFleetAdmin(userId, owner.id)) };
1738
2351
  }
2352
+ /**
2353
+ * #2942: a Windows create or settings change an AGENT asked for. Nothing is created or written here: it becomes a
2354
+ * pending settings change whose General card a fleet admin must confirm (promptSettingsChange — the same admin rule
2355
+ * as every card). Its apply runs the same creation/patch code the web uses, inside the store's SettingsExecution, with
2356
+ * the confirming admin, the Windows switch and the target machine asked again at that moment.
2357
+ */
2358
+ async proposeAgentWindowsChange(input) {
2359
+ const gate = this.settingsGate(), baselines = this.settingsBaselineReader;
2360
+ if (!baselines || !this.fleetConfig)
2361
+ throw new Error("Fleet configuration is unavailable");
2362
+ const generation = this.settingsGeneration, machineId = input.machineId;
2363
+ const switchOn = () => machineId ? remoteRuntimeEnabled() : localWindowsCliEnabled();
2364
+ const online = () => !machineId || (remoteRuntimeEnabled() && this.runtimeCoordinator().machine(machineId)?.status === "online");
2365
+ if (!switchOn())
2366
+ throw new Error(machineId ? "Remote runtime is not enabled" : LOCAL_WINDOWS_CLI_OFF);
2367
+ const baseline = await baselines.read(), snapshot = settingsFingerprint(baselines.snapshot());
2368
+ const { view } = gate.store.propose({ session: `agent:${input.requester}`, key: `agent-windows:${input.key}`,
2369
+ requestFingerprint: input.fingerprint, fingerprint: input.fingerprint, section: "channel", requestedBy: input.requester,
2370
+ source: "agent_request", summary: input.summary, bytes: input.bytes, authority: input.authority, affectedConnections: [],
2371
+ current: () => !this.shuttingDown && this.settingsGeneration === generation && switchOn(),
2372
+ snapshot: () => baselines.snapshot(),
2373
+ unchanged: async () => (await baselines.read()).fingerprint === baseline.fingerprint && settingsFingerprint(baselines.snapshot()) === snapshot,
2374
+ // The store's own fence (this confirming admin, the session, the state) plus the switch and machine now.
2375
+ apply: async (actorCurrent, execution) => {
2376
+ execution.assert();
2377
+ // alpha.2 polish: a paired machine whose CLI inventory has not arrived yet (just paired) is asked for its own
2378
+ // report first (it runs on that PC), so the creation validates against what is installed there — never a refusal
2379
+ // that only a manual Refresh would undo. Its answer or failure changes nothing else; the fence is asked again.
2380
+ if (machineId && online() && !inventoryProbed(this.runtimeCoordinator().machine(machineId)?.inventory)) {
2381
+ // `force`: the runner's cached answer on a fresh profile is the placeholder itself (#3005 rig recheck).
2382
+ try {
2383
+ await this.runtimeCoordinator().call(machineId, "probe", { force: true });
2384
+ }
2385
+ catch { /* validation says what is missing */ }
2386
+ execution.assert();
2387
+ }
2388
+ return input.apply(() => actorCurrent() && online(), execution);
2389
+ } });
2390
+ return view;
2391
+ }
2392
+ /** #2942: create_instance with runtime "windows" from an agent: validated like the web, then held for General. */
2393
+ async requestWindowsCreate(requester, rawArgs) {
2394
+ const cfg = this.fleetConfig;
2395
+ if (!cfg)
2396
+ throw new Error("Fleet configuration is unavailable");
2397
+ let parsed;
2398
+ try {
2399
+ parsed = parseNativeInstanceCreate(rawArgs);
2400
+ }
2401
+ catch {
2402
+ throw new Error("Invalid Windows agent request: check the directory, sandbox, backend and model");
2403
+ }
2404
+ // The web's own projection: the same validation, topic target and diff the admin's dashboard confirmation shows.
2405
+ let diff;
2406
+ try {
2407
+ diff = prepareSettingsEffect("POST", "/ui/instances", rawArgs, { config: cfg, classic: {} }).diff;
2408
+ }
2409
+ catch {
2410
+ throw new Error("Invalid Windows agent request: its topic connection is not one this fleet can use");
2411
+ }
2412
+ if (!diff)
2413
+ throw new Error("Invalid Windows agent request");
2414
+ return this.proposeAgentWindowsChange({ requester, key: diff.fingerprint, fingerprint: diff.fingerprint, summary: diff.summary,
2415
+ machineId: parsed.input.machineId, bytes: JSON.stringify(rawArgs).length,
2416
+ // The General card is the confirmation for an agent's request, whatever the requested connection (a topic or none).
2417
+ authority: { connections: diff.authority?.connections ?? [], primaryGeneral: true, unknown: false },
2418
+ apply: (allowed, execution) => createNativeWindowsInstance(this, parsed, allowed, execution) });
2419
+ }
2420
+ /** #2942: update_instance_config on a Windows agent: only the fields the web editor allows, held for General. */
2421
+ async requestWindowsConfig(requester, name, patch) {
2422
+ const inst = this.fleetConfig?.instances[name];
2423
+ if (!isWindowsInstance(inst))
2424
+ throw new Error("Windows agent not found");
2425
+ let edit;
2426
+ try {
2427
+ edit = NativeInstanceConfigEdit.parse(patch);
2428
+ }
2429
+ catch {
2430
+ throw new Error("Invalid Windows agent settings: display_name, description and model only");
2431
+ }
2432
+ const before = { display_name: inst.display_name ?? "", description: inst.description ?? "", model: inst.model ?? "" };
2433
+ const changed = Object.keys(edit).filter(key => edit[key] !== before[key]);
2434
+ if (!changed.length)
2435
+ throw new Error("Nothing to change: these settings already have these values");
2436
+ const summary = changed.map(key => `${name} ${key}: ${settingsDisplay(before[key])} → ${settingsDisplay(edit[key])}`);
2437
+ const machineId = typeof inst.machine_id === "string" ? inst.machine_id : undefined;
2438
+ // #2973 review (Prism P1): a persistent pane is bound to a chat connection; changing it (a model change restarts it)
2439
+ // needs that connection's admins as well as the primary General's, exactly as its creation did. An instance whose
2440
+ // binding names no single configured connection is refused (fail closed), never confirmed by the General alone.
2441
+ const connections = [];
2442
+ if (isNativePaneInstance(inst) || inst.channel_id !== undefined) {
2443
+ const bound = inst.channel_id, channels = this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : []);
2444
+ if (typeof bound !== "string" || channels.filter(ch => (ch.id ?? ch.type) === bound).length !== 1)
2445
+ throw new Error("This Windows agent's chat connection cannot be resolved; change its settings in the web dashboard");
2446
+ connections.push(bound);
2447
+ }
2448
+ return this.proposeAgentWindowsChange({ requester, key: `${name}:${settingsFingerprint(edit)}`, fingerprint: settingsFingerprint([name, edit]),
2449
+ summary, machineId, bytes: JSON.stringify(edit).length, authority: { connections, primaryGeneral: true, unknown: false },
2450
+ // #2973 review (Prism P2): the update may wait for its lifecycle turn; if the agent is rebound meanwhile (B → C), the
2451
+ // approval for B must not stop or change it on C. Pin the approved target and require the execution's snapshot to be
2452
+ // unchanged before every pre-commit effect; the post-commit restart keeps its own (refreshed) checks.
2453
+ apply: (allowed, execution) => {
2454
+ const pinned = { channel: inst.channel_id, machine: inst.machine_id, adapter: inst.native_adapter, runtime: inst.runtime };
2455
+ const sameTarget = () => {
2456
+ const now = this.fleetConfig?.instances[name];
2457
+ return !!now && now.channel_id === pinned.channel && now.machine_id === pinned.machine && now.native_adapter === pinned.adapter && now.runtime === pinned.runtime;
2458
+ };
2459
+ // Before the commit: the exact target and an unchanged snapshot. After runtime.update completes the transaction,
2460
+ // current() is false by design; the committed operation's restart receipt then needs the same actor, target and
2461
+ // switch checks plus `completed` — a completed execution can never authorize another write (mutate refuses).
2462
+ return updateNativeWindowsInstance(this, name, edit, () => allowed() && sameTarget() && (execution.completed || execution.current()), execution);
2463
+ } });
2464
+ }
1739
2465
  async promptSettingsChange(store, view) {
1740
2466
  const generation = this.settingsGeneration;
1741
2467
  const authority = this.settingsChatAuthority(store, view.id);
@@ -2417,9 +3143,9 @@ export class FleetManager {
2417
3143
  const outbox = this.deliveryOutbox;
2418
3144
  if (!outbox)
2419
3145
  throw new Error("Durable delivery store is unavailable");
2420
- const source = this.daemons.get(input.sourceInstance);
2421
- const sourceDaemonBootId = input.sourceDaemonBootId ?? source?.bootId;
2422
- if (!source || !sourceDaemonBootId || source.bootId !== sourceDaemonBootId) {
3146
+ const currentSourceBootId = this.getDaemonBootId(input.sourceInstance);
3147
+ const sourceDaemonBootId = input.sourceDaemonBootId ?? currentSourceBootId;
3148
+ if (!currentSourceBootId || !sourceDaemonBootId || currentSourceBootId !== sourceDaemonBootId) {
2423
3149
  throw new Error("Source daemon generation is no longer current; outcome is unknown");
2424
3150
  }
2425
3151
  const sourceKey = `mcp:${input.sourceInstance}:${input.operationId}:${input.targetInstance}:${input.targetSession ?? ""}:${input.kind}`;
@@ -2482,6 +3208,9 @@ export class FleetManager {
2482
3208
  return this.daemons.get(target)?.notAcceptingReason?.() ?? null;
2483
3209
  }
2484
3210
  getDaemonBootId(instanceName) {
3211
+ if (isNativeExecInstance(this.fleetConfig?.instances[instanceName])) {
3212
+ return !this.shuttingDown && windowsCliEnabled() && this.nativeMcpStore?.hasActive(instanceName) ? this.managerBootId : undefined;
3213
+ }
2485
3214
  return this.daemons.get(instanceName)?.bootId;
2486
3215
  }
2487
3216
  markDurableResponseDelivered(sourceInstance, operationId) {
@@ -2635,6 +3364,15 @@ export class FleetManager {
2635
3364
  this.deliveryPumpRunning = true;
2636
3365
  this.deliveryPumpScheduled = false;
2637
3366
  try {
3367
+ // Native workers have no resident daemon to wake. Pending durable work resumes their logical instance;
3368
+ // the Windows bridge still owns the single physical execution slot.
3369
+ for (const target of new Set(outbox.listPending().map(row => row.targetInstance))) {
3370
+ if (isNativeExecInstance(this.fleetConfig?.instances[target])) {
3371
+ const runtime = this.nativeInstanceRuntime(), agent = runtime.get(target);
3372
+ if (agent?.paused && !agent.blocked)
3373
+ runtime.setPaused(target, false);
3374
+ }
3375
+ }
2638
3376
  outbox.expireStale(undefined, undefined, target => {
2639
3377
  const failure = this.wakeCoordinator?.wakeFailure(target);
2640
3378
  return failure ? `target could not be woken (${failure})` : undefined;
@@ -2647,7 +3385,7 @@ export class FleetManager {
2647
3385
  // Phase 2c: a lane a queue worker owns is never claimed here.
2648
3386
  target => this.queueWorkers.has(target) || this.isInstanceRestarting(target) || this.wakeCoordinator?.blocksClaim(target)
2649
3387
  ? null
2650
- : this.daemons.get(target)?.bootId ?? null, this.activeDurableTargets, row => this.recoveryDeliveryEligible(row));
3388
+ : this.deliveryBootId(target), this.activeDurableTargets, row => this.recoveryDeliveryEligible(row));
2651
3389
  if (!claimed)
2652
3390
  break;
2653
3391
  this.activeDurableTargets.add(claimed.targetInstance);
@@ -2708,7 +3446,7 @@ export class FleetManager {
2708
3446
  wanted: () => this.deliveryWorkerMode(target) === "on",
2709
3447
  available: () => !this.shuttingDown && this.deliveryOutbox?.isOpen === true,
2710
3448
  blocked: () => this.isInstanceRestarting(target) || this.wakeCoordinator?.blocksClaim(target) === true,
2711
- daemonBootId: () => this.daemons.get(target)?.bootId ?? null,
3449
+ daemonBootId: () => this.deliveryBootId(target),
2712
3450
  claim: bootId => this.deliveryOutbox?.claimNext(this.managerBootId, candidate => candidate === target ? bootId : null, new Set(), row => this.recoveryDeliveryEligible(row)),
2713
3451
  tryAcquireBudget: () => {
2714
3452
  if (this.activeDurableTargets.size >= 8)
@@ -2739,6 +3477,10 @@ export class FleetManager {
2739
3477
  const outbox = this.deliveryOutbox;
2740
3478
  if (!outbox)
2741
3479
  return;
3480
+ if (isNativeExecInstance(this.fleetConfig?.instances[claimed.targetInstance])) {
3481
+ await this.dispatchNativeDurableDelivery(claimed);
3482
+ return;
3483
+ }
2742
3484
  const target = claimed.targetInstance;
2743
3485
  const attempt = claimed.attemptNo;
2744
3486
  const payload = { ...claimed.payload };
@@ -2820,6 +3562,85 @@ export class FleetManager {
2820
3562
  }
2821
3563
  await laneReleased;
2822
3564
  }
3565
+ /** The ordinary durable lane, with provider completion proof instead of terminal/IPC submission proof. */
3566
+ async dispatchNativeDurableDelivery(claimed) {
3567
+ const outbox = this.deliveryOutbox;
3568
+ if (!outbox)
3569
+ return;
3570
+ const { deliveryId, targetDaemonBootId, attemptNo, targetInstance: target } = claimed;
3571
+ let begun = false;
3572
+ try {
3573
+ if (["raw_paste", "steer"].includes(claimed.kind)) {
3574
+ if (outbox.begin(deliveryId, targetDaemonBootId, attemptNo) === "begun")
3575
+ outbox.complete(deliveryId, targetDaemonBootId, attemptNo, "failed", "Windows runtime does not support terminal input or steering");
3576
+ return;
3577
+ }
3578
+ const meta = claimed.payload.meta && typeof claimed.payload.meta === "object" ? claimed.payload.meta : {};
3579
+ const task = await this.nativeInstanceRuntime().execute(target, { ...claimed.payload, requestId: deliveryId, meta: { ...meta, from_instance: claimed.sourceInstance, correlation_id: claimed.correlationId } }, {
3580
+ stillCurrent: () => !this.shuttingDown,
3581
+ beforeStart: () => { if (outbox.begin(deliveryId, targetDaemonBootId, attemptNo) !== "begun")
3582
+ throw new Error("Windows delivery attempt is no longer current"); begun = true; },
3583
+ });
3584
+ const outcome = task.outcomeConfirmed === false ? "uncertain" : task.status === "completed" ? "delivered" : "failed";
3585
+ outbox.complete(deliveryId, targetDaemonBootId, attemptNo, outcome, task.error || task.status);
3586
+ // Return a requested peer report when the provider did not explicitly report it through scoped MCP.
3587
+ if (meta.requires_reply === "true" && claimed.correlationId && !task.reportedCorrelationIds?.includes(claimed.correlationId) && this.fleetConfig?.instances[claimed.sourceInstance]) {
3588
+ const text = (task.status === "completed" ? task.output : `Windows task ${task.status}: ${task.error || task.output}`).slice(0, 8000);
3589
+ outbox.admit({ sourceKey: `native-result:${deliveryId}`, operationId: `native-result:${deliveryId}`, sourceInstance: target, sourceDaemonBootId: this.managerBootId,
3590
+ targetInstance: claimed.sourceInstance, kind: "fleet_inbound", correlationId: claimed.correlationId,
3591
+ payload: { type: "fleet_inbound", content: text || "Windows task completed without text output", meta: { from_instance: target, correlation_id: claimed.correlationId, requires_reply: "false", source: "native_runtime" } } });
3592
+ this.scheduleDeliveryOutboxPump();
3593
+ }
3594
+ }
3595
+ catch (error) {
3596
+ const reason = error instanceof Error ? error.message : String(error);
3597
+ if (begun)
3598
+ outbox.complete(deliveryId, targetDaemonBootId, attemptNo, "uncertain", reason);
3599
+ else
3600
+ outbox.retryBeforeBegin(deliveryId, targetDaemonBootId, attemptNo, reason, 5000);
3601
+ this.logger.warn({ target, deliveryId, err: reason }, "Windows durable delivery did not complete");
3602
+ }
3603
+ }
3604
+ /** A runtime's persisted completion can outlive this manager. Resolve its original delivery fence, never send
3605
+ * the prompt again, and reconstruct a missing peer report using the same durable operation ID. */
3606
+ reconcileRemoteDelivery(machineId, task) {
3607
+ const outbox = this.deliveryOutbox;
3608
+ if (!outbox?.isOpen || !task.finishedAt || task.outcomeConfirmed !== true || !["completed", "failed", "cancelled"].includes(task.status))
3609
+ return;
3610
+ const key = `${task.id}:${task.finishedAt}:${task.status}`;
3611
+ if (this.reconciledRemoteResults.has(key))
3612
+ return;
3613
+ const row = outbox.get(task.id);
3614
+ if (!row || !isNativeExecInstance(this.fleetConfig?.instances[row.targetInstance]) || this.fleetConfig?.instances[row.targetInstance]?.machine_id !== machineId
3615
+ || !row.targetDaemonBootId || this.activeDurableTargets.has(row.targetInstance))
3616
+ return;
3617
+ if (row.state === "submission_started" || row.state === "reconciliation_pending") {
3618
+ if (!outbox.complete(row.deliveryId, row.targetDaemonBootId, row.attemptNo, task.status === "completed" ? "delivered" : "failed", task.error || "Runtime completion recovered after reconnect"))
3619
+ return;
3620
+ }
3621
+ else if (row.state === "uncertain" && task.status === "completed") {
3622
+ if (outbox.markConsumed(row.deliveryId, row.targetDaemonBootId, row.attemptNo, "turn", "Paired runtime journal confirmed this exact execution completed") === "ignored")
3623
+ return;
3624
+ }
3625
+ else if (row.state !== "delivered" && row.state !== "failed")
3626
+ return;
3627
+ const meta = row.payload.meta && typeof row.payload.meta === "object" ? row.payload.meta : {};
3628
+ if (meta.requires_reply !== "true" || !row.correlationId || !this.fleetConfig?.instances[row.sourceInstance]) {
3629
+ this.reconciledRemoteResults.add(key);
3630
+ return;
3631
+ }
3632
+ const reports = outbox.queryStatusForInstance(row.targetInstance, { correlationId: row.correlationId, limit: 100 }).items;
3633
+ if (reports.some(report => report.source_instance === row.targetInstance && report.target_instance === row.sourceInstance
3634
+ && report.kind === "report" && !["failed", "cancelled"].includes(report.state))) {
3635
+ this.reconciledRemoteResults.add(key);
3636
+ return;
3637
+ }
3638
+ const text = (task.status === "completed" ? task.output : `Windows task ${task.status}: ${task.error || task.output}`).slice(0, 8000);
3639
+ outbox.admit({ sourceKey: `native-result:${row.deliveryId}`, operationId: `native-result:${row.deliveryId}`, sourceInstance: row.targetInstance, sourceDaemonBootId: this.managerBootId,
3640
+ targetInstance: row.sourceInstance, kind: "fleet_inbound", correlationId: row.correlationId,
3641
+ payload: { type: "fleet_inbound", content: text || "Windows task completed without text output", meta: { from_instance: row.targetInstance, correlation_id: row.correlationId, requires_reply: "false", source: "native_runtime" } } });
3642
+ this.reconciledRemoteResults.add(key);
3643
+ }
2823
3644
  async waitForDurableLaneRelease(claimed) {
2824
3645
  const outbox = this.deliveryOutbox;
2825
3646
  if (!outbox)
@@ -3276,6 +4097,7 @@ export class FleetManager {
3276
4097
  this.warnAboutRemovedLoginMode(loaded);
3277
4098
  this.enforceGuestSwitch();
3278
4099
  this.observeWebAuthority(); // #2453 review: a reload's web-authority switches are seen at once
4100
+ this.observeRuntimeSwitches(); // M-2255: so are the Windows runtimes' switches
3279
4101
  // Child tool/guidance readers must follow this fleet's actual config, including --config.
3280
4102
  // Scope the path to its owning data directory so another inherited home's override cannot enable CW.
3281
4103
  process.env.AGEND_FLEET_DATA_DIR = resolvePath(this.dataDir);
@@ -3704,6 +4526,8 @@ export class FleetManager {
3704
4526
  }
3705
4527
  /** Get the adapter bound to an instance, falling back to primary adapter */
3706
4528
  getAdapterForInstance(name) {
4529
+ if (isNativeExecInstance(this.fleetConfig?.instances[name]))
4530
+ return null;
3707
4531
  const worldId = this.getInstanceAdapterId(name);
3708
4532
  if (worldId)
3709
4533
  return this.worlds.get(worldId)?.adapter ?? this.adapter;
@@ -3766,6 +4590,8 @@ export class FleetManager {
3766
4590
  }
3767
4591
  /** Get the group_id for an instance's bound adapter */
3768
4592
  getGroupIdForInstance(name) {
4593
+ if (isNativeExecInstance(this.fleetConfig?.instances[name]))
4594
+ return "";
3769
4595
  const adapterId = this.getInstanceAdapterId(name);
3770
4596
  const world = adapterId ? this.worlds.get(adapterId) : undefined;
3771
4597
  return world?.groupId ?? String(this.getChannelConfig(adapterId)?.group_id ?? "");
@@ -3802,6 +4628,8 @@ export class FleetManager {
3802
4628
  */
3803
4629
  getInstanceAdapterId(name) {
3804
4630
  const cfg = this.fleetConfig?.instances[name];
4631
+ if (isNativeExecInstance(cfg))
4632
+ return undefined;
3805
4633
  if (cfg)
3806
4634
  return cfg.channel_id ?? this.getPrimaryAdapterId();
3807
4635
  if (this.classicChannels?.getChannelIdByInstance(name) !== undefined) {
@@ -3829,9 +4657,32 @@ export class FleetManager {
3829
4657
  this.instanceWorldBinding.set(name, adapterId);
3830
4658
  }
3831
4659
  getInstanceStatus(name) {
4660
+ if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
4661
+ try {
4662
+ return this.nativeInstanceRuntime().status(name);
4663
+ }
4664
+ catch {
4665
+ return "crashed";
4666
+ }
4667
+ }
3832
4668
  if (this.lifecycle.isPaused(name))
3833
4669
  return "paused";
3834
4670
  const daemon = this.lifecycle.daemons.get(name);
4671
+ if (isNativePaneInstance(this.fleetConfig?.instances[name])) {
4672
+ const config = this.fleetConfig.instances[name];
4673
+ if (config.machine_id ? !remoteRuntimeEnabled() : !localWindowsCliEnabled())
4674
+ return "stopped";
4675
+ if (daemon) {
4676
+ const native = daemon, status = native.getProcessStatus?.() ?? "crashed";
4677
+ return status === "running" && native.nativePaneCurrent?.() !== true ? "crashed" : status;
4678
+ }
4679
+ try {
4680
+ return this.nativeInstanceRuntime().get(name)?.activeId ? "crashed" : "stopped";
4681
+ }
4682
+ catch {
4683
+ return "crashed";
4684
+ }
4685
+ }
3835
4686
  const processStatus = this.instanceProcessStatus.get(name);
3836
4687
  // IPC can be disconnected during a respawn, so the event which announces
3837
4688
  // the new live pane may be missed. The in-process daemon is authoritative
@@ -3865,6 +4716,14 @@ export class FleetManager {
3865
4716
  }
3866
4717
  }
3867
4718
  getInstanceExecutionState(name) {
4719
+ if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
4720
+ try {
4721
+ return this.nativeInstanceRuntime().execution(name);
4722
+ }
4723
+ catch {
4724
+ return null;
4725
+ }
4726
+ }
3868
4727
  if (this.lifecycle.isPaused(name))
3869
4728
  return null;
3870
4729
  // Process status wins over a stale pane snapshot. A dead remain-on-exit pane
@@ -3875,6 +4734,8 @@ export class FleetManager {
3875
4734
  }
3876
4735
  /** Pure read of the single daemon-owned observation; no probe/capture or second tracker. */
3877
4736
  getInstanceInteraction(name) {
4737
+ if (isNativeExecInstance(this.fleetConfig?.instances[name]))
4738
+ return null;
3878
4739
  if (this.lifecycle.isPaused(name) || this.instanceProcessStatus.has(name))
3879
4740
  return null;
3880
4741
  const daemon = this.lifecycle.daemons.get(name);
@@ -3962,6 +4823,8 @@ export class FleetManager {
3962
4823
  bindings.push([name, backend, profile]);
3963
4824
  };
3964
4825
  for (const [name, config] of Object.entries(this.fleetConfig?.instances ?? {})) {
4826
+ if (isWindowsInstance(config))
4827
+ continue; // Windows sign-ins and usage do not belong to the Linux account.
3965
4828
  // loadFleetConfig() has already merged the fleet default into each row.
3966
4829
  const backend = config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
3967
4830
  add(name, backend, instanceCredentialProfile(config, this.fleetConfig?.defaults, backend));
@@ -4568,6 +5431,8 @@ export class FleetManager {
4568
5431
  * id is no platform message, and the echo is the fleet's own post. The web send calls this on the echo's lane.
4569
5432
  */
4570
5433
  startWebChatProgress(instanceName) {
5434
+ if (isNativeExecInstance(this.fleetConfig?.instances[instanceName]))
5435
+ return Promise.resolve();
4571
5436
  if (this.shuttingDown)
4572
5437
  return Promise.resolve();
4573
5438
  return this.sendCancelButton(instanceName);
@@ -4833,6 +5698,10 @@ export class FleetManager {
4833
5698
  }
4834
5699
  /** Single delivery facade: wake paused CLIs and serialize non-user work behind idle. */
4835
5700
  async deliverToInstance(instanceName, payload, options = {}) {
5701
+ if (isNativeExecInstance(this.fleetConfig?.instances[instanceName])) {
5702
+ return this.nativeInstanceRuntime().deliver(instanceName, payload, { stillCurrent: options.stillCurrent,
5703
+ ...(options.authorityCurrent ? { authorityCurrent: options.authorityCurrent } : {}) });
5704
+ }
4836
5705
  const deliveryEpoch = this.getDeliveryEpoch(instanceName);
4837
5706
  const deliveryPayload = { ...payload, delivery_epoch: deliveryEpoch };
4838
5707
  const meta = payload.meta && typeof payload.meta === "object"
@@ -5390,6 +6259,10 @@ export class FleetManager {
5390
6259
  * slot admission and joins any wake already in flight for the target.
5391
6260
  */
5392
6261
  async explicitWake(name, timeoutMs = 30_000, retryStoppedSession = false) {
6262
+ if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
6263
+ this.nativeInstanceRuntime().setPaused(name, false);
6264
+ return;
6265
+ }
5393
6266
  if (this.sessionOomStopped(name)) {
5394
6267
  if (!retryStoppedSession)
5395
6268
  throw new SessionOomStoppedError(false, readSessionOomStop(this.getInstanceDir(name)));
@@ -5414,6 +6287,10 @@ export class FleetManager {
5414
6287
  .sort((a, b) => (readLastInboundAt(this.getInstanceDir(a)) ?? 0) - (readLastInboundAt(this.getInstanceDir(b)) ?? 0))[0];
5415
6288
  }
5416
6289
  async changeInstancePauseState(name, action) {
6290
+ if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
6291
+ this.nativeInstanceRuntime().setPaused(name, action === "pause");
6292
+ return action === "pause" ? "paused" : "awake";
6293
+ }
5417
6294
  if (action === "wake") {
5418
6295
  await this.explicitWake(name, 30_000, true);
5419
6296
  this.enforceWarmCap(name); // manual wake still respects the fleet warm cap
@@ -5591,6 +6468,14 @@ export class FleetManager {
5591
6468
  resumePaused = false,
5592
6469
  /** Phase 2a: the caller's transition, for a start made from inside a restart or wake. */
5593
6470
  transition, execution, kiroMigration) {
6471
+ if (isNativeExecInstance(config)) {
6472
+ execution?.assert();
6473
+ if (!windowsCliEnabled())
6474
+ throw new Error("Windows runtime is disabled");
6475
+ if (resumePaused)
6476
+ this.nativeInstanceRuntime().setPaused(name, false);
6477
+ return;
6478
+ }
5594
6479
  const migration = this.kiroMigrationRuns?.get(name);
5595
6480
  if (migration && migration.token !== kiroMigration)
5596
6481
  throw new Error(t("kiro_migrate.in_progress"));
@@ -5641,10 +6526,10 @@ export class FleetManager {
5641
6526
  if (resumePaused)
5642
6527
  this.lifecycle.markActivitySeedNow(name);
5643
6528
  const backend = config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
5644
- if (config.general_topic) {
6529
+ if (!isWindowsInstance(config) && config.general_topic) {
5645
6530
  this.ensureGeneralInstructions(config.working_directory, backend, name);
5646
6531
  }
5647
- else if (kind === "fleet-topic") {
6532
+ else if (!isWindowsInstance(config) && kind === "fleet-topic") {
5648
6533
  // Workers get only role-eligible on-demand skills. Classic instances are
5649
6534
  // deliberately excluded: their workspace and conversation lifecycle are
5650
6535
  // managed independently from fleet-topic workers.
@@ -5659,7 +6544,7 @@ export class FleetManager {
5659
6544
  this.logger.warn({ err, name, backend }, "Failed to sync worker skills — continuing startup");
5660
6545
  }
5661
6546
  }
5662
- else if (kind === "classic") {
6547
+ else if (!isWindowsInstance(config) && kind === "classic") {
5663
6548
  // #2163: a Classic bot gets only the skills marked `classic` (its own look, the dashboard) — never the fleet's
5664
6549
  // coordination playbooks (#557 kept those out of end-user bots) — and only into a workspace AgEnD manages: the
5665
6550
  // room's own folder under <AGEND_HOME>/workspaces/. Anywhere else, nothing is written; `agend-agent skill <name>`
@@ -5676,7 +6561,7 @@ export class FleetManager {
5676
6561
  }
5677
6562
  }
5678
6563
  const receipt = execution ? this.classicSettingsOwners.get(execution) : undefined;
5679
- const identity = { kind, backend, model: this.resolveInstanceModel(name).display };
6564
+ const identity = { kind, backend, model: isWindowsInstance(config) ? config.model || "default" : this.resolveInstanceModel(name).display };
5680
6565
  if (receipt) {
5681
6566
  await this.lifecycle.start(name, config, topicMode, identity, transition, execution, daemon => {
5682
6567
  // Only this transition's actual publication can acquire its replacement.
@@ -5746,7 +6631,7 @@ export class FleetManager {
5746
6631
  // them before grouping/staggering: startInstance() retains its own guard as
5747
6632
  // a final backstop, but putting a no-op entry in this queue still consumes a
5748
6633
  // full stagger slot for every distinct working directory.
5749
- const runnableEntries = entries.filter(([name]) => !this.lifecycle.isPaused(name));
6634
+ const runnableEntries = entries.filter(([name, config]) => !isNativeExecInstance(config) && !this.lifecycle.isPaused(name));
5750
6635
  // ClassicBot rooms queue with the fleet instances, at the same pressure pacing (#2642 follow-up): they used to start
5751
6636
  // only after the whole fleet, the chat connections, topics and icons. A name the fleet also configures is the fleet's.
5752
6637
  const fleetNames = new Set(entries.map(([name]) => name));
@@ -5812,6 +6697,8 @@ export class FleetManager {
5812
6697
  return false;
5813
6698
  if (err instanceof SessionOomStoppedError)
5814
6699
  await this.notifySessionOomStop(name);
6700
+ if (this.holdNativePaneRecovery(name, err))
6701
+ return false;
5815
6702
  if (this.stopOnUnsupportedCli(name, err, what))
5816
6703
  return false;
5817
6704
  this.logger.error({ err, name }, `Failed to start ${what}`);
@@ -5953,6 +6840,8 @@ export class FleetManager {
5953
6840
  if (err instanceof SupersededStartError)
5954
6841
  return;
5955
6842
  // An earlier attempt could not tell (unknown probe); this one could.
6843
+ if (this.holdNativePaneRecovery(name, err))
6844
+ return;
5956
6845
  if (this.stopOnUnsupportedCli(name, err, "instance"))
5957
6846
  return;
5958
6847
  this.logger.error({ err, name, attempt: attempt + 1 }, "Automatic startup retry failed");
@@ -6203,7 +7092,7 @@ export class FleetManager {
6203
7092
  return count;
6204
7093
  }
6205
7094
  configuredStartupInstanceNames(fleet, includeClassic) {
6206
- const names = new Set(Object.keys(fleet.instances));
7095
+ const names = new Set(Object.entries(fleet.instances).filter(([, config]) => !isNativeExecInstance(config)).map(([name]) => name));
6207
7096
  if (includeClassic) {
6208
7097
  for (const channel of this.classicChannels?.getAll() ?? [])
6209
7098
  names.add(channel.instanceName);
@@ -6254,6 +7143,10 @@ export class FleetManager {
6254
7143
  return poster.postCompletion({ resolveAdapter: () => this.readyProgressAdapter(adapterId), chatId, threadId }, text);
6255
7144
  }
6256
7145
  async stopInstance(name, transition) {
7146
+ if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
7147
+ this.nativeInstanceRuntime().setPaused(name, true);
7148
+ return { backgroundWorkEnded: false };
7149
+ }
6257
7150
  this.explicitStopGeneration.set(name, (this.explicitStopGeneration.get(name) ?? 0) + 1);
6258
7151
  this.cancelStartupRetry(name);
6259
7152
  this.failoverActive.delete(name);
@@ -6291,6 +7184,16 @@ export class FleetManager {
6291
7184
  }
6292
7185
  /** Restart a single instance, reloading fleet.yaml first to pick up config changes. */
6293
7186
  async restartSingleInstance(name, opts) {
7187
+ if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
7188
+ const runtime = this.nativeInstanceRuntime();
7189
+ if (runtime.get(name)?.blocked)
7190
+ throw new Error("Verify the disconnected Windows process stopped and recover this agent before restarting");
7191
+ runtime.cancel(name);
7192
+ runtime.setPaused(name, false);
7193
+ return;
7194
+ }
7195
+ if (opts?.explicit && isNativePaneInstance(this.fleetConfig?.instances[name]))
7196
+ this.lifecycle.resetNativeCrashBudget(name);
6294
7197
  const migration = this.kiroMigrationRuns?.get(name);
6295
7198
  if (migration && migration.token !== opts?.kiroMigration)
6296
7199
  throw new Error(t("kiro_migrate.in_progress"));
@@ -6354,7 +7257,7 @@ export class FleetManager {
6354
7257
  return run;
6355
7258
  }
6356
7259
  isInstanceRestarting(name) {
6357
- return this.restartsInFlight.has(name) || this.kiroMigrationRuns?.has(name) === true;
7260
+ return this.restartsInFlight.has(name) || this.kiroMigrationRuns?.has(name) === true || this.lifecycle?.nativeRecoveryPending?.(name) === true;
6358
7261
  }
6359
7262
  /** InstanceLifecycleContext: an explicit wake joins the restart in flight. */
6360
7263
  restartInFlight(name) {
@@ -8125,6 +9028,9 @@ export class FleetManager {
8125
9028
  const fleet = this.loadConfig(configPath, { startup: true });
8126
9029
  this.slimFleetConfigAtStartup();
8127
9030
  this.logConfigWarningsAtStartup();
9031
+ this.initializeNativeInstances();
9032
+ // M-2255: from here on a change to the Windows runtimes' switches applies live (observeRuntimeSwitches).
9033
+ this.runtimeEffects = { windows_cli: localWindowsCliEnabled(), remote_runtime: remoteRuntimeEnabled() };
8128
9034
  setLocale(detectLocale(fleet)); // user-facing text language (fleet.yaml defaults.locale / timezone)
8129
9035
  // #1737 review: the marker this boot adopts — the startup clear may only ever clear this one.
8130
9036
  const bootUpdateMarker = updateMarkerIdentity(this.dataDir);
@@ -8156,7 +9062,7 @@ export class FleetManager {
8156
9062
  // Pre-flight (advisory, fire-and-forget): warm the per-backend auth cache
8157
9063
  // before instances spawn, so a CLI that comes up on its sign-in screen gets
8158
9064
  // an instant 🔑 verdict instead of decaying into crash/MCP-died noise.
8159
- this.lifecycle.primeAuthVerification(Object.values(fleet.instances).map(config => {
9065
+ this.lifecycle.primeAuthVerification(Object.values(fleet.instances).filter(config => !isWindowsInstance(config)).map(config => {
8160
9066
  const backend = config.backend ?? fleet.defaults?.backend ?? "claude-code";
8161
9067
  // #1699 follow-up: each instance's own login — a profile is probed with its own environment.
8162
9068
  return { backend, profile: credentialHomeSpec(backend) ? instanceCredentialProfile(config, fleet.defaults, backend) : null };
@@ -8218,6 +9124,7 @@ export class FleetManager {
8218
9124
  // #1565: the web chat as it was before this process — the configured instances' only (fleet.yaml and ClassicBot
8219
9125
  // rooms), before the dashboard serves anything; from now on every change is written back (off the loop).
8220
9126
  this.restoreWebChat(fleet);
9127
+ this.initializeNativeInstances(true);
8221
9128
  this.startSearchIndex(fleet);
8222
9129
  // Parse-only source monitoring; it does not apply hand-edited Fleet settings.
8223
9130
  this.configHistoryTimer = setInterval(() => this.configHistory?.check(), 1_000);
@@ -8396,7 +9303,7 @@ export class FleetManager {
8396
9303
  }
8397
9304
  }
8398
9305
  // Phase 1: Start general instances first and wait for them
8399
- const allEntries = Object.entries(fleet.instances);
9306
+ const allEntries = Object.entries(fleet.instances).filter(([, config]) => !isNativeExecInstance(config));
8400
9307
  const generals = allEntries.filter(([_, cfg]) => cfg.general_topic);
8401
9308
  const others = allEntries.filter(([_, cfg]) => !cfg.general_topic);
8402
9309
  const startupProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), pendingUpdateProgress?.startedAt ?? startupStartedAt, this.logger, { mode: pendingProgressOperation === "full-restart" ? "reload" : pendingUpdateProgress ? "update" : "restart" });
@@ -9398,10 +10305,14 @@ export class FleetManager {
9398
10305
  }
9399
10306
  }
9400
10307
  }
10308
+ const daemon = this.daemons.get(name);
10309
+ const native = daemon?.usesNativePane?.();
9401
10310
  const sockPath = join(this.getInstanceDir(name), "channel.sock");
9402
- if (!existsSync(sockPath))
10311
+ if (!native && !existsSync(sockPath))
9403
10312
  return;
9404
- const ipc = new IpcClient(sockPath);
10313
+ const ipc = native
10314
+ ? new NativeDaemonClient(daemon, () => this.daemons.get(name) === daemon && (!this.instanceIpcClients.has(name) || this.instanceIpcClients.get(name) === ipc))
10315
+ : new IpcClient(sockPath);
9405
10316
  try {
9406
10317
  await ipc.connect();
9407
10318
  this.instanceIpcClients.set(name, ipc);
@@ -9517,7 +10428,7 @@ export class FleetManager {
9517
10428
  // connected, so seed the cache instead of waiting for another transition.
9518
10429
  ipc.send({ type: "query_instance_state", requestId: `fleet-state-${Date.now()}` });
9519
10430
  this.logger.debug({ name }, "Connected to instance IPC");
9520
- if (!this.statuslineWatcher.has(name)) {
10431
+ if (!native && !this.statuslineWatcher.has(name)) {
9521
10432
  this.statuslineWatcher.watch(name);
9522
10433
  }
9523
10434
  // Auto-reconnect on disconnect (unless intentionally stopping)
@@ -12580,6 +13491,7 @@ export class FleetManager {
12580
13491
  this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
12581
13492
  this.refreshSavedDashboardPermissions();
12582
13493
  this.logger.info({ path: this.configPath, strippedDefaults: redundantPaths.length }, "Saved fleet config (lossless patch)");
13494
+ this.observeRuntimeSwitches(); // M-2255: a saved Windows runtime switch applies now
12583
13495
  }
12584
13496
  /** A permission tightening is effective when its save returns, independently of the later apply job or IPC. */
12585
13497
  refreshSavedDashboardPermissions() {
@@ -12813,10 +13725,43 @@ export class FleetManager {
12813
13725
  }
12814
13726
  getSettingsConfigPath() { return this.configPath; }
12815
13727
  async removeInstance(name, authorization, execution) {
13728
+ if (isWindowsInstance(this.fleetConfig?.instances[name])) {
13729
+ execution?.assert();
13730
+ assertExplicitInstanceRemoval(authorization);
13731
+ if (isNativePaneInstance(this.fleetConfig?.instances[name])) {
13732
+ await this.stopInstance(name);
13733
+ execution?.assert();
13734
+ }
13735
+ this.nativeInstanceRuntime().remove(name, () => !this.shuttingDown && (!execution || execution.current()), execution);
13736
+ return;
13737
+ }
12816
13738
  if (execution)
12817
13739
  return this.lifecycle.runTransition(name, transition => this.removeInstanceOwned(name, authorization, execution, transition));
12818
13740
  return this.removeInstanceOwned(name, authorization);
12819
13741
  }
13742
+ /** Shared instance metadata goes with a native identity; its Windows workspace and provider sessions stay intact. */
13743
+ nativeInstanceRemoved(name) {
13744
+ const clean = (what, run) => { try {
13745
+ run();
13746
+ }
13747
+ catch (error) {
13748
+ this.logger.warn({ name, err: error }, `Native instance ${what} cleanup failed`);
13749
+ } };
13750
+ clean("delivery", () => { this.cancelPendingDeliveries(name); });
13751
+ forgetInstanceContext(name);
13752
+ clean("schedules", () => { for (const schedule of this.scheduler?.list(name) ?? [])
13753
+ this.scheduler.delete(schedule.id); });
13754
+ this.lastActivity.delete(name);
13755
+ this.lastInboundUser.delete(name);
13756
+ this.rateLimitWarnedAt.delete(name);
13757
+ this.lastInboundMsg.delete(name);
13758
+ clean("history", () => this.webChatHistory.forget(name));
13759
+ clean("search", () => this.searchIndex?.removeInstance(name));
13760
+ clean("files", () => this.webFiles.forget(name));
13761
+ clean("compact", () => this.compactWatchService?.forget(name));
13762
+ clean("preferences", () => this.webPrefs.forgetInstance(name));
13763
+ clean("appearance", () => { this.themeStore.forget(name); this.webAppearance.forget(name); this.appearanceTools.forget(name); this.mods.forgetInstance(name); this.officeActsInstance?.forget(name); this.officePackToolsInstance?.forget(name); });
13764
+ }
12820
13765
  async removeInstanceOwned(name, authorization, execution, transition) {
12821
13766
  execution?.assert();
12822
13767
  assertExplicitInstanceRemoval(authorization);
@@ -12939,6 +13884,8 @@ export class FleetManager {
12939
13884
  execution?.complete();
12940
13885
  }
12941
13886
  startStatuslineWatcher(name) {
13887
+ if (isWindowsInstance(this.fleetConfig?.instances[name]))
13888
+ return;
12942
13889
  if (this.lifecycle.isPaused(name))
12943
13890
  return;
12944
13891
  this.statuslineWatcher.watch(name);
@@ -14387,6 +15334,10 @@ export class FleetManager {
14387
15334
  * would have to make this async and change every caller.
14388
15335
  */
14389
15336
  notifyInstanceTopic(instanceName, text, extraOpts) {
15337
+ if (isNativeExecInstance(this.fleetConfig?.instances[instanceName])) {
15338
+ this.emitSseEvent("message", { instance: instanceName, role: "status", sender: "AgEnD", text, ts: new Date().toISOString() });
15339
+ return true;
15340
+ }
14390
15341
  // #2910 D2: a web-only fleet has no topics: what the agent's topic would have been told is a Notice about it.
14391
15342
  if (this.webOnlyPromptPlace())
14392
15343
  return this.recordWebNotice("instance", text, instanceName);
@@ -14866,6 +15817,8 @@ export class FleetManager {
14866
15817
  * (also for a backend that has no profiles, where the option is ignored).
14867
15818
  */
14868
15819
  credentialProfileOf(name, backend) {
15820
+ if (isWindowsInstance(this.fleetConfig?.instances[name]))
15821
+ return null;
14869
15822
  if (!credentialHomeSpec(backend))
14870
15823
  return null;
14871
15824
  const cfg = this.fleetConfig?.instances?.[name];
@@ -14888,7 +15841,7 @@ export class FleetManager {
14888
15841
  }
14889
15842
  /** The instances the cache analysis reads: fleet.yaml's, then the ClassicBot rooms (their workspace under the AgEnD home, their own backend). */
14890
15843
  cacheInstances() {
14891
- const fleet = Object.entries(this.fleetConfig?.instances ?? {}).map(([name, cfg]) => ({
15844
+ const fleet = Object.entries(this.fleetConfig?.instances ?? {}).filter(([, cfg]) => !isWindowsInstance(cfg)).map(([name, cfg]) => ({
14892
15845
  name, backend: this.backendNameOf(name), workingDirectory: cfg.working_directory, ledgerPath: join(this.getInstanceDir(name), "cache-ledger.json"),
14893
15846
  claudeConfigDir: this.claudeConfigDirOf(name), profile: this.usageProfileOf(name, this.backendNameOf(name)),
14894
15847
  kiroDbPath: this.kiroDbPathOf(name, this.backendNameOf(name)),
@@ -15067,6 +16020,19 @@ export class FleetManager {
15067
16020
  crashedAt: crashed ? (this.needsCrashedAt.get(name) ?? null) : null,
15068
16021
  signIn: pauseReason === "auth" ? this.signInFor(name) : null,
15069
16022
  usageHold: this.daemons.get(name)?.usageHold?.() ?? null,
16023
+ nativeDeliveryHold: this.daemons.get(name)?.nativeDeliveryHold?.() ?? null,
16024
+ paneRecovery: (() => {
16025
+ const config = this.fleetConfig?.instances[name];
16026
+ if (!isNativePaneInstance(config) || !config.native_agent_id)
16027
+ return null;
16028
+ try {
16029
+ const profile = windowsAgentsFor(this, this.runtimeWindowsExecutor()).get(config.native_agent_id);
16030
+ return profile.paneRecovery ? { key: `${profile.paneRecovery.kind}:${profile.paneRecovery.launchId ?? "-"}`, detail: new NativePaneRecoveryError(profile.paneRecovery).message, since: profile.paneRecoveryAt ?? profile.createdAt } : null;
16031
+ }
16032
+ catch {
16033
+ return null;
16034
+ }
16035
+ })(),
15070
16036
  });
15071
16037
  }
15072
16038
  return out;
@@ -15122,6 +16088,8 @@ export class FleetManager {
15122
16088
  const target = this.fleetNoticeTarget(world);
15123
16089
  return target ? { chatId: target.chatId, ...(target.opts.threadId !== undefined ? { threadId: String(target.opts.threadId) } : {}) } : null;
15124
16090
  },
16091
+ // The name the web page shows for the instance (its View alias, else its own name), never the raw id alone.
16092
+ instanceName: instance => resolveInstanceIdentity({ cfg: this.fleetConfig?.instances[instance], profile: profileIdentities(this.dataDir).get(instance) }).display_name || instance,
15125
16093
  instanceTopic: instance => {
15126
16094
  const topic = this.fleetConfig?.instances[instance]?.topic_id;
15127
16095
  if (topic != null)
@@ -16755,6 +17723,8 @@ export class FleetManager {
16755
17723
  publication.retirePending = true;
16756
17724
  }
16757
17725
  async sendCancelButton(instanceName, correlationId, preserveProgress = false) {
17726
+ if (isNativeExecInstance(this.fleetConfig?.instances[instanceName]))
17727
+ return;
16758
17728
  // Post first, retire after (see the tail of this method). Retiring up front
16759
17729
  // meant that from the delete until the new message came back — a chat API
16760
17730
  // round trip, and every reply goes through here — the instance had NO live
@@ -17287,6 +18257,14 @@ export class FleetManager {
17287
18257
  }
17288
18258
  /** Interrupt an instance's current generation (cancel button / /cancel). */
17289
18259
  cancelInstance(instanceName) {
18260
+ if (isNativeExecInstance(this.fleetConfig?.instances[instanceName])) {
18261
+ this.cancelPendingDeliveries(instanceName);
18262
+ const cancelled = this.nativeInstanceRuntime().cancel(instanceName);
18263
+ this.lastInboundMsg.delete(instanceName);
18264
+ for (const message of this.webChatHistory.cancelPending(instanceName))
18265
+ this.emitSseEvent("delivery", { instance: instanceName, messageId: message.messageId, delivery: message.delivery });
18266
+ return cancelled !== null;
18267
+ }
17290
18268
  const daemon = this.daemons.get(instanceName);
17291
18269
  if (!daemon)
17292
18270
  return false;
@@ -19547,6 +20525,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19547
20525
  : model,
19548
20526
  });
19549
20527
  const fleetInstance = this.fleetConfig?.instances[instanceName];
20528
+ if (isWindowsInstance(fleetInstance))
20529
+ return fleetInstance.model?.trim() ? done(fleetInstance.model.trim(), "instance") : done("default", "unresolved", "this CLI does not report a default");
19550
20530
  if (fleetInstance) {
19551
20531
  if (fleetInstance.model?.trim())
19552
20532
  return done(fleetInstance.model.trim(), "instance");
@@ -19628,7 +20608,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19628
20608
  if (this.fleetConfig?.defaults?.backend)
19629
20609
  backends.add(this.fleetConfig.defaults.backend);
19630
20610
  for (const inst of Object.values(this.fleetConfig?.instances ?? {}))
19631
- if (inst.backend)
20611
+ if (!isWindowsInstance(inst) && inst.backend)
19632
20612
  backends.add(inst.backend);
19633
20613
  for (const ch of this.classicChannels?.getAll() ?? [])
19634
20614
  if (ch.backend)
@@ -19643,6 +20623,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19643
20623
  // here; a ClassicBot room on a profile is probed on its first /model (catalogProfile knows rooms too).
19644
20624
  const profiles = new Set();
19645
20625
  for (const name of Object.keys(this.fleetConfig?.instances ?? {})) {
20626
+ if (isWindowsInstance(this.fleetConfig?.instances[name]))
20627
+ continue;
19646
20628
  if (this.backendNameForInstance(name) !== "claude-code")
19647
20629
  continue;
19648
20630
  const profile = this.catalogProfile("claude-code", name);
@@ -19663,6 +20645,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19663
20645
  * instead of presenting an old list as a fresh one.
19664
20646
  */
19665
20647
  async getModelOptionsWithSource(instanceName, opts = {}) {
20648
+ if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
20649
+ return { models: (await this.listModelCatalog({ instanceName })).models, source: "cache" };
19666
20650
  const backendName = this.backendNameForInstance(instanceName);
19667
20651
  const profile = this.catalogProfile(backendName, instanceName);
19668
20652
  const cached = this.readCliEnv(backendName, profile);
@@ -19718,6 +20702,21 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19718
20702
  */
19719
20703
  async listModelCatalog(opts = {}) {
19720
20704
  const { instanceName } = opts;
20705
+ if (instanceName && isWindowsInstance(this.fleetConfig?.instances[instanceName])) {
20706
+ const config = this.fleetConfig.instances[instanceName], backend = config.backend ?? "codex";
20707
+ // Detection is admin-owned. A read never launches a provider or falls back to this host's account.
20708
+ let inventory = null;
20709
+ try {
20710
+ inventory = await this.runtimeWindowsExecutor().probe({ force: false, machineId: config.machine_id });
20711
+ }
20712
+ catch { /* offline remains unavailable */ }
20713
+ const row = inventory?.clis.find(value => value.id === backend);
20714
+ return { backend, scope: "instance", instance: instanceName, current_model: config.model || null,
20715
+ models: (row?.models ?? []).map(value => ({ id: value.id, label: value.name ?? value.id, ...(value.description ? { description: value.description } : {}) })),
20716
+ source: row?.models?.length ? "cache" : "fallback",
20717
+ ...(inventory?.probedAt ? { probed_at: new Date(inventory.probedAt).toISOString() } : {}),
20718
+ note: "Windows runtime's last detected inventory; no coordinator account or live probe is used." };
20719
+ }
19721
20720
  if (instanceName) {
19722
20721
  const backend = this.backendNameForInstance(instanceName);
19723
20722
  const resolved = this.resolveInstanceModel(instanceName);
@@ -19753,6 +20752,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
19753
20752
  }
19754
20753
  /** Ask a backend for its catalog using ONE instance's real config. Never throws. */
19755
20754
  async instanceScopedModels(instanceName, backend) {
20755
+ if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
20756
+ return [];
19756
20757
  try {
19757
20758
  const inst = this.fleetConfig?.instances?.[instanceName];
19758
20759
  const instanceDir = this.getInstanceDir(instanceName);
@@ -20372,6 +21373,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
20372
21373
  }
20373
21374
  /** The instance's backend's effort metadata; agy's levels are its model's (#1825), the one the instance runs. */
20374
21375
  instanceEffortMetadata(instanceName) {
21376
+ if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
21377
+ return { strategy: "unsupported", levels: [] };
20375
21378
  const backend = this.backendNameForInstance(instanceName);
20376
21379
  let model = null;
20377
21380
  if (backend === "antigravity") {
@@ -20382,6 +21385,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
20382
21385
  }
20383
21386
  /** Configured effort for an instance: per-instance, else fleet default, else none. */
20384
21387
  resolveInstanceEffort(instanceName) {
21388
+ if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
21389
+ return { effort: null, source: "unset" };
20385
21390
  const own = this.fleetConfig?.instances[instanceName]?.effort;
20386
21391
  if (own)
20387
21392
  return { effort: own, source: "instance" };
@@ -20419,6 +21424,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
20419
21424
  * instance respawns.
20420
21425
  */
20421
21426
  async applyEffort(instanceName, requested) {
21427
+ if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
21428
+ return t("effort.unsupported", this.backendNameForInstance(instanceName));
20422
21429
  const level = requested.trim().toLowerCase();
20423
21430
  const backendName = this.backendNameForInstance(instanceName);
20424
21431
  let strategy = "unsupported";
@@ -20462,6 +21469,20 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
20462
21469
  if (/[\x00-\x1f\x7f]/.test(model)) {
20463
21470
  return t("model.invalid_chars");
20464
21471
  }
21472
+ const native = this.fleetConfig?.instances[instanceName];
21473
+ if (isWindowsInstance(native)) {
21474
+ if (!isNativePaneInstance(native))
21475
+ return WINDOWS_CONFIG_REFUSED;
21476
+ const catalog = await this.listModelCatalog({ instanceName });
21477
+ if (!catalog.models.some(row => row.id === model))
21478
+ return t("model.unknown_name", model, catalog.models.map(row => row.id).join(", "));
21479
+ const id = native.native_agent_id, machine = native.machine_id;
21480
+ await this.updateNativePaneConfig(instanceName, { model }, () => {
21481
+ const now = this.fleetConfig?.instances[instanceName];
21482
+ return isNativePaneInstance(now) && now.native_agent_id === id && now.machine_id === machine;
21483
+ });
21484
+ return t("model.restart_success", instanceName, model);
21485
+ }
20465
21486
  const backendName = this.backendNameForInstance(instanceName);
20466
21487
  let strategy = "restart";
20467
21488
  try {
@@ -20648,6 +21669,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
20648
21669
  * profile's, when it is named and has one), claude's account catalog included. Read only: never a probe.
20649
21670
  */
20650
21671
  modelCatalogFor(backend, instance) {
21672
+ if (instance && isWindowsInstance(this.fleetConfig?.instances[instance]))
21673
+ return [];
20651
21674
  if (!KNOWN_BACKENDS.includes(backend) || backend === "mock")
20652
21675
  return [];
20653
21676
  const own = instance && Object.prototype.hasOwnProperty.call(this.fleetConfig?.instances ?? {}, instance) ? instance
@@ -20661,6 +21684,40 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
20661
21684
  }
20662
21685
  return out;
20663
21686
  }
21687
+ /** Host-default fallback only: Grok has no modeled profile home. Never construct a backend or start a probe. */
21688
+ async cliModelCatalogFor(backend, instance) {
21689
+ if (backend !== "grok")
21690
+ return [];
21691
+ const fleet = this.fleetConfig;
21692
+ const configured = instance && Object.prototype.hasOwnProperty.call(fleet?.instances ?? {}, instance) ? fleet.instances[instance] : undefined;
21693
+ const classic = this.classicChannels;
21694
+ const room = instance && !configured ? classic?.getAll().find(channel => channel.instanceName === instance) : undefined;
21695
+ if (instance && !configured && !room)
21696
+ return [];
21697
+ const daemon = room ? this.daemons.get(instance) : undefined;
21698
+ const grokHome = process.env.GROK_HOME?.trim() || join(homedir(), ".grok");
21699
+ const stillHostDefault = () => {
21700
+ if (this.fleetConfig !== fleet || (process.env.GROK_HOME?.trim() || join(homedir(), ".grok")) !== grokHome)
21701
+ return false;
21702
+ if (configured && (isWindowsInstance(configured) || !Object.prototype.hasOwnProperty.call(fleet?.instances ?? {}, instance) || fleet.instances[instance] !== configured))
21703
+ return false;
21704
+ if (room && (this.classicChannels !== classic || classic?.getAll().find(channel => channel.instanceName === instance) !== room || this.daemons.get(instance) !== daemon))
21705
+ return false;
21706
+ // The ordinary profile helpers deliberately ignore Grok: it has no credential-home mapping yet.
21707
+ // Check raw declarations, including invalid ones, rather than mistaking them for the host login.
21708
+ const defaults = fleet?.defaults?.backend_options;
21709
+ const profiles = configured
21710
+ ? [configured.backend_options?.grok?.credential_profile, defaults?.grok?.credential_profile]
21711
+ : room
21712
+ ? [mergeBackendOptions(defaults, room.backendOptions)?.grok?.credential_profile, daemon?.getConfigSnapshot?.().backend_options?.grok?.credential_profile]
21713
+ : [defaults?.grok?.credential_profile];
21714
+ return profiles.every(profile => profile === undefined || profile === null || profile === "");
21715
+ };
21716
+ if (!stillHostDefault())
21717
+ return [];
21718
+ const models = await readGrokModelCache(grokHome);
21719
+ return stillHostDefault() ? models : [];
21720
+ }
20664
21721
  /**
20665
21722
  * #2072 (#2078 review): the snapshot as THIS instance would see it. Its sign-in on a backend is its own credential
20666
21723
  * profile's when it has one there — the host's default login says nothing about it. A profile's login is a file check
@@ -21319,6 +22376,16 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21319
22376
  * settles, so a later genuine stop (after a restart) still does the work.
21320
22377
  */
21321
22378
  stopAll() {
22379
+ this.lifecycle.cancelNativeRecovery?.();
22380
+ this.nativeMcpStore?.closeAll();
22381
+ this.runtimeExecutor?.close();
22382
+ this.runtimeExecutor = null;
22383
+ this.fleetWindowsRuntime = null;
22384
+ this.runtimeHub?.close();
22385
+ this.runtimeHub = null;
22386
+ this.localPaneRelay?.stop();
22387
+ this.localPaneRelay = null;
22388
+ closeWindowsBridge(this);
21322
22389
  this.stopAllInFlight ??= this.doStopAll().finally(() => {
21323
22390
  // #1956: after the stop's own tmux work; anything later goes direct. A later startAll starts it again.
21324
22391
  if (this.tmuxExecHelperTimer) {
@@ -21777,6 +22844,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21777
22844
  this.savedFleetConfigSnapshot = previousSavedSnapshot;
21778
22845
  this.loadedFleetSource = previousSource;
21779
22846
  this.observeWebAuthority(); // #2453 review: the configuration back in force is the one seen
22847
+ this.observeRuntimeSwitches(); // M-2255: and the Windows runtimes' switches
21780
22848
  this.logger.error({
21781
22849
  oldCount,
21782
22850
  newCount,
@@ -23412,7 +24480,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
23412
24480
  const topicMode = daemonTopicMode(fleet);
23413
24481
  const restartProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), restartStartedAt, this.logger);
23414
24482
  // Phase 1: generals first
23415
- const restartEntries = Object.entries(fleet.instances);
24483
+ const restartEntries = Object.entries(fleet.instances).filter(([, config]) => !isNativeExecInstance(config));
23416
24484
  const restartGenerals = restartEntries.filter(([_, cfg]) => cfg.general_topic);
23417
24485
  const restartOthers = restartEntries.filter(([_, cfg]) => !cfg.general_topic);
23418
24486
  for (const [name, cfg] of restartGenerals) {
@@ -23866,6 +24934,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
23866
24934
  return;
23867
24935
  }
23868
24936
  const requestPath = new URL(req.url ?? "/", `http://localhost:${port}`).pathname;
24937
+ if (remoteRuntimeEnabled() && !gatewayRequestContext(req)
24938
+ && this.runtimeCoordinator().handlePublicRequest(req, res, new URL(req.url ?? "/", `http://localhost:${port}`)))
24939
+ return;
23869
24940
  // Branding is public, after Host admission: automatic favicon requests
23870
24941
  // must not become noisy web-token 401s in the browser console.
23871
24942
  // #2308: the icon routes and the pages' icon links follow the app icon set in Settings.
@@ -24249,6 +25320,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24249
25320
  if (!this.webToken || !this.webSessions)
24250
25321
  this.initializeWebAuthTokens();
24251
25322
  const server = createServer((req, res) => this.dispatchWebHttp(req, res, port, drain.closing));
25323
+ server.on("upgrade", (req, socket, head) => {
25324
+ if (!this.shuttingDown && remoteRuntimeEnabled() && isHostAllowed(req.headers.host, allowedHostNames(this.fleetConfig))
25325
+ && this.runtimeCoordinator().handleUpgrade(req, socket, head))
25326
+ return;
25327
+ socket.destroy();
25328
+ });
24252
25329
  // #1937: Node's default (300 s to receive a request) would cut a large upload on a slow link. The listener allows
24253
25330
  // more; the upload route keeps its own clocks (no byte for a minute, or 30 minutes in all), and every other request
24254
25331
  // keeps the old 300 s (limitRequestReceive in dispatchWebHttp).
@@ -24473,9 +25550,36 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24473
25550
  this.logger.debug({ err }, "bots unreadable (getUiStatus)");
24474
25551
  }
24475
25552
  const instances = names.map(name => {
25553
+ const nativeConfig = this.fleetConfig?.instances[name];
25554
+ if (isNativeExecInstance(nativeConfig)) {
25555
+ let runtime = null;
25556
+ let agent = null;
25557
+ try {
25558
+ runtime = this.nativeInstanceRuntime();
25559
+ agent = runtime.get(name);
25560
+ }
25561
+ catch { /* A damaged native profile is a crashed native row, never a Linux spawn. */ }
25562
+ const display_name = nativeConfig.display_name || agent?.name || name;
25563
+ const backend = nativeConfig.backend || agent?.cli || "codex";
25564
+ return { name, display_name, description: nativeConfig.description || undefined, tags: nativeConfig.tags || [],
25565
+ teams: instanceTeams(this.fleetConfig?.teams, name), runtime: "windows", native_agent_id: nativeConfig.native_agent_id,
25566
+ ...(nativeConfig.machine_id ? { machine_id: nativeConfig.machine_id, machine_name: this.runtimeHub?.machine(nativeConfig.machine_id)?.name,
25567
+ machine_status: this.runtimeHub?.machine(nativeConfig.machine_id)?.status ?? "offline" } : {}),
25568
+ native_blocked: !agent || agent.blocked === true,
25569
+ native_busy: runtime?.busy(name) ?? false,
25570
+ backend,
25571
+ model: nativeConfig.model || "default", model_source: nativeConfig.model ? "instance" : "cli-default",
25572
+ status: agent && runtime ? runtime.status(name) : "crashed", state: agent && runtime ? runtime.execution(name) : null, execution_state: agent && runtime ? runtime.execution(name) : null,
25573
+ interaction: null, interaction_summary: null, context_pct: null, cost: null,
25574
+ last_reply: this.webChatHistory.lastAgentReply(name) ?? undefined,
25575
+ avatar: avatarFor({ name, displayName: display_name }),
25576
+ capabilities: { chat: true, settings: true, pause: true, cancel: true, teams: true, schedules: true, delegation: true, mcp: nativeProviderMcpSupported(backend), channels: false, terminal: false, attachments: false, usage: false, send_while_busy: false },
25577
+ ...(!agent ? { runtime_error: "Windows profile is missing or invalid; restore its saved conversation before using this instance" }
25578
+ : agent.blocked ? { runtime_error: "Windows process outcome is unknown; verify it stopped before recovery" } : {}) };
25579
+ }
24476
25580
  // #1956: read once for this instance; its cost, context and model come from it.
24477
- const statusline = readStatuslineJson(this.dataDir, name);
24478
- const cost = statusline?.cost?.total_cost_usd ?? 0;
25581
+ const statusline = isNativePaneInstance(nativeConfig) ? undefined : readStatuslineJson(this.dataDir, name);
25582
+ const cost = isNativePaneInstance(nativeConfig) ? null : statusline?.cost?.total_cost_usd ?? 0;
24479
25583
  // Align with /ctx: statusline for claude-code, pane scrape for kiro/grok/codex.
24480
25584
  const classic = classicOnly.includes(name);
24481
25585
  const backend = classic
@@ -24483,7 +25587,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24483
25587
  : (this.fleetConfig?.instances[name]?.backend
24484
25588
  ?? this.fleetConfig?.defaults?.backend
24485
25589
  ?? "claude-code");
24486
- const { context } = resolveInstanceContext(this.dataDir, name, backend, { source: this.getPaneContextSource(name), statusline });
25590
+ const { context } = isNativePaneInstance(nativeConfig) ? { context: null } : resolveInstanceContext(this.dataDir, name, backend, { source: this.getPaneContextSource(name), statusline });
24487
25591
  // context_pct: null when unavailable, not 0
24488
25592
  const context_pct = context ?? null;
24489
25593
  // Model: Only Claude Code has live statusline; others use the effective resolver.
@@ -24514,6 +25618,21 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24514
25618
  }
24515
25619
  return {
24516
25620
  name,
25621
+ ...(isNativePaneInstance(nativeConfig) ? { runtime: "windows", native_adapter: "pane", native_agent_id: nativeConfig.native_agent_id,
25622
+ ...(() => {
25623
+ try {
25624
+ const profile = this.nativeInstanceRuntime().get(name), bound = this.daemons.get(name);
25625
+ return { native_recovery: profile?.paneRecovery ?? (profile?.paneLaunchId && bound?.usesNativePane?.() && !bound.nativePaneCurrent()
25626
+ ? { kind: "exit-unconfirmed", launchId: profile.paneLaunchId } : null) };
25627
+ }
25628
+ catch {
25629
+ return { native_recovery: null };
25630
+ }
25631
+ })(),
25632
+ capabilities: { chat: true, settings: true, pause: true, cancel: true, teams: true, schedules: true, delegation: true, mcp: true,
25633
+ commands: true, effort: false, log: true, details: true, channels: false, terminal: false, attachments: false, usage: false, send_while_busy: true },
25634
+ ...(nativeConfig.machine_id ? { machine_id: nativeConfig.machine_id, machine_name: this.runtimeHub?.machine(nativeConfig.machine_id)?.name,
25635
+ machine_status: this.runtimeHub?.machine(nativeConfig.machine_id)?.status ?? "offline" } : {}) } : {}),
24517
25636
  display_name: identity.display_name || undefined,
24518
25637
  avatar: image.avatar,
24519
25638
  ...(image.accent ? { accent: image.accent } : {}),
@@ -24565,6 +25684,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24565
25684
  uptime: Math.floor((Date.now() - this.startedAt) / 1000),
24566
25685
  // #1519 P7: how many chat connections are configured — 0 is a web-only fleet, which gets the first-run card.
24567
25686
  connections: (this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : [])).length,
25687
+ windows_cli: localWindowsCliEnabled(),
24568
25688
  // #1665: the host preferences' revision — a page that has another re-reads /ui/prefs (no values in this frame).
24569
25689
  prefs_rev: this.webPrefs.rev(),
24570
25690
  // #1629: the themes' revision — the page re-reads GET /ui/themes when it moves.
@@ -24574,6 +25694,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
24574
25694
  host_memory: this.hostMemoryIndicator(),
24575
25695
  // #2910 D2: a web-only fleet's newest Notice — a polling page (no SSE `notice`) reads the list again when it moves.
24576
25696
  ...(this.webOnlyPromptPlace() ? { notices_newest: this.webNoticeStore()?.newest() ?? 0 } : {}),
25697
+ // alpha.2 (M-2300, #2997 review): agents' requests changed — the owner's page reads GET /ui/agent-proposals again.
25698
+ ...(this.isWebOnlyFleet() ? { agent_proposals_rev: this.agentProposalsRev } : {}),
24577
25699
  // #2015: the largest processes while it lasts (name, pid, RSS, and whose: an instance, the fleet or tmux).
24578
25700
  ...(this.hostMemoryIndicator() && this.memoryTop ? { host_memory_top: this.memoryTop.processes } : {}),
24579
25701
  };
@@ -25016,4 +26138,12 @@ load();
25016
26138
  </script>
25017
26139
  </body>
25018
26140
  </html>`;
26141
+ /** alpha.2 (M-2300): the agent an applied Windows request created (its name, for the page's "Open chat"), or null. */
26142
+ export function agentNameOf(view) {
26143
+ const r = view.state === "applied" ? view.outcome?.result : null;
26144
+ if (!r || typeof r !== "object")
26145
+ return null;
26146
+ const o = r, name = o.name ?? o.instance_name ?? (o.agent && typeof o.agent === "object" ? o.agent.name : undefined);
26147
+ return typeof name === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name) ? name : null;
26148
+ }
25019
26149
  //# sourceMappingURL=fleet-manager.js.map