@songsid/agend 2.3.4-beta.7 → 2.3.5-alpha.3

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 (457) 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/auth-api.d.ts +21 -0
  8. package/dist/auth-api.js +89 -2
  9. package/dist/auth-api.js.map +1 -1
  10. package/dist/authority/web-authority.d.ts +12 -2
  11. package/dist/authority/web-authority.js +25 -4
  12. package/dist/authority/web-authority.js.map +1 -1
  13. package/dist/backend/claude-code.d.ts +15 -12
  14. package/dist/backend/claude-code.js +152 -133
  15. package/dist/backend/claude-code.js.map +1 -1
  16. package/dist/backend/codex-package.d.ts +5 -2
  17. package/dist/backend/codex-package.js +1 -5
  18. package/dist/backend/codex-package.js.map +1 -1
  19. package/dist/backend/codex.d.ts +136 -113
  20. package/dist/backend/codex.js +1281 -1219
  21. package/dist/backend/codex.js.map +1 -1
  22. package/dist/backend/grok-model-cache.d.ts +7 -0
  23. package/dist/backend/grok-model-cache.js +65 -0
  24. package/dist/backend/grok-model-cache.js.map +1 -0
  25. package/dist/backend/grok.d.ts +2 -0
  26. package/dist/backend/grok.js +22 -0
  27. package/dist/backend/grok.js.map +1 -1
  28. package/dist/backend/native-pane.d.ts +9 -0
  29. package/dist/backend/native-pane.js +59 -0
  30. package/dist/backend/native-pane.js.map +1 -0
  31. package/dist/backend/types.d.ts +7 -0
  32. package/dist/backend/types.js.map +1 -1
  33. package/dist/channel/ipc-bridge.d.ts +2 -0
  34. package/dist/channel/mcp-tools.js +1 -1
  35. package/dist/channel/mcp-tools.js.map +1 -1
  36. package/dist/cli.js +3 -0
  37. package/dist/cli.js.map +1 -1
  38. package/dist/config-validator.js +71 -1
  39. package/dist/config-validator.js.map +1 -1
  40. package/dist/config.js +16 -0
  41. package/dist/config.js.map +1 -1
  42. package/dist/daemon.d.ts +67 -5
  43. package/dist/daemon.js +823 -266
  44. package/dist/daemon.js.map +1 -1
  45. package/dist/delivery-consumed-watch.d.ts +15 -3
  46. package/dist/delivery-consumed-watch.js +23 -1
  47. package/dist/delivery-consumed-watch.js.map +1 -1
  48. package/dist/delivery-outbox.d.ts +5 -0
  49. package/dist/delivery-outbox.js +32 -4
  50. package/dist/delivery-outbox.js.map +1 -1
  51. package/dist/delivery-reconciliation.d.ts +2 -35
  52. package/dist/delivery-reconciliation.js +2 -156
  53. package/dist/delivery-reconciliation.js.map +1 -1
  54. package/dist/fleet-context.d.ts +2 -2
  55. package/dist/fleet-level-config.d.ts +1 -1
  56. package/dist/fleet-level-config.js +2 -0
  57. package/dist/fleet-level-config.js.map +1 -1
  58. package/dist/fleet-manager.d.ts +238 -3
  59. package/dist/fleet-manager.js +1826 -104
  60. package/dist/fleet-manager.js.map +1 -1
  61. package/dist/fleet-windows-runtime.d.ts +92 -0
  62. package/dist/fleet-windows-runtime.js +380 -0
  63. package/dist/fleet-windows-runtime.js.map +1 -0
  64. package/dist/general-binding.d.ts +85 -0
  65. package/dist/general-binding.js +298 -0
  66. package/dist/general-binding.js.map +1 -0
  67. package/dist/general-host-owner.d.ts +8 -0
  68. package/dist/general-host-owner.js +26 -0
  69. package/dist/general-host-owner.js.map +1 -0
  70. package/dist/general-instance.d.ts +7 -0
  71. package/dist/general-instance.js +20 -3
  72. package/dist/general-instance.js.map +1 -1
  73. package/dist/general-role.d.ts +5 -0
  74. package/dist/general-role.js +26 -0
  75. package/dist/general-role.js.map +1 -0
  76. package/dist/instance-config-impact.js +4 -0
  77. package/dist/instance-config-impact.js.map +1 -1
  78. package/dist/instance-lifecycle.d.ts +21 -3
  79. package/dist/instance-lifecycle.js +378 -162
  80. package/dist/instance-lifecycle.js.map +1 -1
  81. package/dist/instructions.d.ts +2 -0
  82. package/dist/instructions.js +6 -0
  83. package/dist/instructions.js.map +1 -1
  84. package/dist/local-pane-endpoint.d.ts +46 -0
  85. package/dist/local-pane-endpoint.js +189 -0
  86. package/dist/local-pane-endpoint.js.map +1 -0
  87. package/dist/local-pane-launcher.d.ts +2 -0
  88. package/dist/local-pane-launcher.js +38 -0
  89. package/dist/local-pane-launcher.js.map +1 -0
  90. package/dist/local-pane-stage.ps1 +101 -0
  91. package/dist/locale.js +85 -0
  92. package/dist/locale.js.map +1 -1
  93. package/dist/model-catalog-defaults.d.ts +14 -0
  94. package/dist/model-catalog-defaults.js +20 -0
  95. package/dist/model-catalog-defaults.js.map +1 -0
  96. package/dist/native-agent-admission.d.ts +7 -0
  97. package/dist/native-agent-admission.js +26 -0
  98. package/dist/native-agent-admission.js.map +1 -0
  99. package/dist/native-daemon-client.d.ts +13 -0
  100. package/dist/native-daemon-client.js +28 -0
  101. package/dist/native-daemon-client.js.map +1 -0
  102. package/dist/native-instance-api.d.ts +14 -0
  103. package/dist/native-instance-api.js +205 -0
  104. package/dist/native-instance-api.js.map +1 -0
  105. package/dist/native-instance-input.d.ts +84 -0
  106. package/dist/native-instance-input.js +54 -0
  107. package/dist/native-instance-input.js.map +1 -0
  108. package/dist/native-instance-registry.d.ts +32 -0
  109. package/dist/native-instance-registry.js +105 -0
  110. package/dist/native-instance-registry.js.map +1 -0
  111. package/dist/native-mcp.d.ts +40 -0
  112. package/dist/native-mcp.js +278 -0
  113. package/dist/native-mcp.js.map +1 -0
  114. package/dist/native-model-catalog.d.ts +5 -0
  115. package/dist/native-model-catalog.js +76 -0
  116. package/dist/native-model-catalog.js.map +1 -0
  117. package/dist/native-pane-crash-budget.d.ts +20 -0
  118. package/dist/native-pane-crash-budget.js +60 -0
  119. package/dist/native-pane-crash-budget.js.map +1 -0
  120. package/dist/native-pane-create.d.ts +26 -0
  121. package/dist/native-pane-create.js +53 -0
  122. package/dist/native-pane-create.js.map +1 -0
  123. package/dist/native-pane-lifecycle.d.ts +65 -0
  124. package/dist/native-pane-lifecycle.js +230 -0
  125. package/dist/native-pane-lifecycle.js.map +1 -0
  126. package/dist/native-pane-manager.d.ts +59 -0
  127. package/dist/native-pane-manager.js +584 -0
  128. package/dist/native-pane-manager.js.map +1 -0
  129. package/dist/native-pane-output-log.d.ts +15 -0
  130. package/dist/native-pane-output-log.js +108 -0
  131. package/dist/native-pane-output-log.js.map +1 -0
  132. package/dist/native-pane-reconciliation.d.ts +4 -0
  133. package/dist/native-pane-reconciliation.js +40 -0
  134. package/dist/native-pane-reconciliation.js.map +1 -0
  135. package/dist/native-pane-recovery.d.ts +12 -0
  136. package/dist/native-pane-recovery.js +10 -0
  137. package/dist/native-pane-recovery.js.map +1 -0
  138. package/dist/native-pane-tools.d.ts +2 -0
  139. package/dist/native-pane-tools.js +14 -0
  140. package/dist/native-pane-tools.js.map +1 -0
  141. package/dist/needs-you-hub.d.ts +2 -0
  142. package/dist/needs-you-hub.js +2 -1
  143. package/dist/needs-you-hub.js.map +1 -1
  144. package/dist/needs-you.d.ts +19 -1
  145. package/dist/needs-you.js +12 -2
  146. package/dist/needs-you.js.map +1 -1
  147. package/dist/outbound-handlers.d.ts +16 -2
  148. package/dist/outbound-handlers.js +67 -3
  149. package/dist/outbound-handlers.js.map +1 -1
  150. package/dist/outbound-schemas.d.ts +38 -0
  151. package/dist/outbound-schemas.js +12 -0
  152. package/dist/outbound-schemas.js.map +1 -1
  153. package/dist/pane-port.d.ts +118 -0
  154. package/dist/pane-port.js +2 -0
  155. package/dist/pane-port.js.map +1 -0
  156. package/dist/public-web-gateway.js +9 -0
  157. package/dist/public-web-gateway.js.map +1 -1
  158. package/dist/public-web-link.d.ts +5 -0
  159. package/dist/public-web-link.js +4 -0
  160. package/dist/public-web-link.js.map +1 -1
  161. package/dist/quickstart-api.js +9 -5
  162. package/dist/quickstart-api.js.map +1 -1
  163. package/dist/quickstart.d.ts +1 -0
  164. package/dist/quickstart.js +4 -1
  165. package/dist/quickstart.js.map +1 -1
  166. package/dist/runtime-api.d.ts +8 -0
  167. package/dist/runtime-api.js +96 -0
  168. package/dist/runtime-api.js.map +1 -0
  169. package/dist/runtime-assets/@xterm/addon-serialize/LICENSE +21 -0
  170. package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.js +2 -0
  171. package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.mjs +19 -0
  172. package/dist/runtime-assets/@xterm/addon-serialize/package.json +29 -0
  173. package/dist/runtime-assets/@xterm/headless/LICENSE +21 -0
  174. package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.js +2 -0
  175. package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.mjs +46 -0
  176. package/dist/runtime-assets/@xterm/headless/package.json +29 -0
  177. package/dist/runtime-assets/node-pty/CONPTY-LICENSE +21 -0
  178. package/dist/runtime-assets/node-pty/CONPTY-NOTICE.md +503 -0
  179. package/dist/runtime-assets/node-pty/LICENSE +69 -0
  180. package/dist/runtime-assets/node-pty/lib/conpty_console_list_agent.js +16 -0
  181. package/dist/runtime-assets/node-pty/lib/eventEmitter2.js +47 -0
  182. package/dist/runtime-assets/node-pty/lib/index.js +52 -0
  183. package/dist/runtime-assets/node-pty/lib/interfaces.js +7 -0
  184. package/dist/runtime-assets/node-pty/lib/shared/conout.js +11 -0
  185. package/dist/runtime-assets/node-pty/lib/terminal.js +190 -0
  186. package/dist/runtime-assets/node-pty/lib/types.js +7 -0
  187. package/dist/runtime-assets/node-pty/lib/unixTerminal.js +346 -0
  188. package/dist/runtime-assets/node-pty/lib/utils.js +39 -0
  189. package/dist/runtime-assets/node-pty/lib/windowsConoutConnection.js +125 -0
  190. package/dist/runtime-assets/node-pty/lib/windowsPtyAgent.js +320 -0
  191. package/dist/runtime-assets/node-pty/lib/windowsTerminal.js +199 -0
  192. package/dist/runtime-assets/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  193. package/dist/runtime-assets/node-pty/package.json +64 -0
  194. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  195. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  196. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  197. package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  198. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  199. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  200. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  201. package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  202. package/dist/runtime-bundle.d.ts +17 -0
  203. package/dist/runtime-bundle.js +150 -0
  204. package/dist/runtime-bundle.js.map +1 -0
  205. package/dist/runtime-cli.d.ts +2 -0
  206. package/dist/runtime-cli.js +117 -0
  207. package/dist/runtime-cli.js.map +1 -0
  208. package/dist/runtime-client-store.d.ts +49 -0
  209. package/dist/runtime-client-store.js +292 -0
  210. package/dist/runtime-client-store.js.map +1 -0
  211. package/dist/runtime-client.d.ts +100 -0
  212. package/dist/runtime-client.js +700 -0
  213. package/dist/runtime-client.js.map +1 -0
  214. package/dist/runtime-coordinator.d.ts +82 -0
  215. package/dist/runtime-coordinator.js +864 -0
  216. package/dist/runtime-coordinator.js.map +1 -0
  217. package/dist/runtime-general-protocol.d.ts +22 -0
  218. package/dist/runtime-general-protocol.js +57 -0
  219. package/dist/runtime-general-protocol.js.map +1 -0
  220. package/dist/runtime-general-workspace.d.ts +17 -0
  221. package/dist/runtime-general-workspace.js +213 -0
  222. package/dist/runtime-general-workspace.js.map +1 -0
  223. package/dist/runtime-pane-assets.d.ts +17 -0
  224. package/dist/runtime-pane-assets.js +23 -0
  225. package/dist/runtime-pane-assets.js.map +1 -0
  226. package/dist/runtime-pane-boot.d.ts +2 -0
  227. package/dist/runtime-pane-boot.js +24 -0
  228. package/dist/runtime-pane-boot.js.map +1 -0
  229. package/dist/runtime-pane-codex-index.d.ts +1 -0
  230. package/dist/runtime-pane-codex-index.js +47 -0
  231. package/dist/runtime-pane-codex-index.js.map +1 -0
  232. package/dist/runtime-pane-config.d.ts +16 -0
  233. package/dist/runtime-pane-config.js +54 -0
  234. package/dist/runtime-pane-config.js.map +1 -0
  235. package/dist/runtime-pane-host.d.ts +44 -0
  236. package/dist/runtime-pane-host.js +424 -0
  237. package/dist/runtime-pane-host.js.map +1 -0
  238. package/dist/runtime-pane-job-owner.d.ts +1 -0
  239. package/dist/runtime-pane-job-owner.js +29 -0
  240. package/dist/runtime-pane-job-owner.js.map +1 -0
  241. package/dist/runtime-pane-job.ps1 +116 -0
  242. package/dist/runtime-pane-link.d.ts +40 -0
  243. package/dist/runtime-pane-link.js +246 -0
  244. package/dist/runtime-pane-link.js.map +1 -0
  245. package/dist/runtime-pane-mcp-protocol.d.ts +32 -0
  246. package/dist/runtime-pane-mcp-protocol.js +39 -0
  247. package/dist/runtime-pane-mcp-protocol.js.map +1 -0
  248. package/dist/runtime-pane-mcp-relay.d.ts +17 -0
  249. package/dist/runtime-pane-mcp-relay.js +70 -0
  250. package/dist/runtime-pane-mcp-relay.js.map +1 -0
  251. package/dist/runtime-pane-mcp.d.ts +51 -0
  252. package/dist/runtime-pane-mcp.js +150 -0
  253. package/dist/runtime-pane-mcp.js.map +1 -0
  254. package/dist/runtime-pane-native.d.ts +45 -0
  255. package/dist/runtime-pane-native.js +65 -0
  256. package/dist/runtime-pane-native.js.map +1 -0
  257. package/dist/runtime-pane-output.d.ts +19 -0
  258. package/dist/runtime-pane-output.js +53 -0
  259. package/dist/runtime-pane-output.js.map +1 -0
  260. package/dist/runtime-pane-port.d.ts +139 -0
  261. package/dist/runtime-pane-port.js +558 -0
  262. package/dist/runtime-pane-port.js.map +1 -0
  263. package/dist/runtime-pane-private.d.ts +14 -0
  264. package/dist/runtime-pane-private.js +51 -0
  265. package/dist/runtime-pane-private.js.map +1 -0
  266. package/dist/runtime-pane-private.ps1 +14 -0
  267. package/dist/runtime-pane-process.d.ts +4 -0
  268. package/dist/runtime-pane-process.js +108 -0
  269. package/dist/runtime-pane-process.js.map +1 -0
  270. package/dist/runtime-pane-protocol.d.ts +172 -0
  271. package/dist/runtime-pane-protocol.js +195 -0
  272. package/dist/runtime-pane-protocol.js.map +1 -0
  273. package/dist/runtime-pane-screen.d.ts +19 -0
  274. package/dist/runtime-pane-screen.js +76 -0
  275. package/dist/runtime-pane-screen.js.map +1 -0
  276. package/dist/runtime-pane-service.d.ts +11 -0
  277. package/dist/runtime-pane-service.js +221 -0
  278. package/dist/runtime-pane-service.js.map +1 -0
  279. package/dist/runtime-pane-stdio-protocol.d.ts +54 -0
  280. package/dist/runtime-pane-stdio-protocol.js +94 -0
  281. package/dist/runtime-pane-stdio-protocol.js.map +1 -0
  282. package/dist/runtime-pane-stdio.d.ts +3 -0
  283. package/dist/runtime-pane-stdio.js +123 -0
  284. package/dist/runtime-pane-stdio.js.map +1 -0
  285. package/dist/runtime-pane-store.d.ts +33 -0
  286. package/dist/runtime-pane-store.js +164 -0
  287. package/dist/runtime-pane-store.js.map +1 -0
  288. package/dist/runtime-pane-transcript-protocol.d.ts +43 -0
  289. package/dist/runtime-pane-transcript-protocol.js +58 -0
  290. package/dist/runtime-pane-transcript-protocol.js.map +1 -0
  291. package/dist/runtime-pane-transcript.d.ts +45 -0
  292. package/dist/runtime-pane-transcript.js +605 -0
  293. package/dist/runtime-pane-transcript.js.map +1 -0
  294. package/dist/runtime-pane-worker.d.ts +1 -0
  295. package/dist/runtime-pane-worker.js +154 -0
  296. package/dist/runtime-pane-worker.js.map +1 -0
  297. package/dist/runtime-protocol.d.ts +149 -0
  298. package/dist/runtime-protocol.js +19 -0
  299. package/dist/runtime-protocol.js.map +1 -0
  300. package/dist/runtime-switches.d.ts +79 -0
  301. package/dist/runtime-switches.js +90 -0
  302. package/dist/runtime-switches.js.map +1 -0
  303. package/dist/runtime-transcript-delta-reader.d.ts +23 -0
  304. package/dist/runtime-transcript-delta-reader.js +74 -0
  305. package/dist/runtime-transcript-delta-reader.js.map +1 -0
  306. package/dist/runtime-transcript-source.d.ts +36 -0
  307. package/dist/runtime-transcript-source.js +189 -0
  308. package/dist/runtime-transcript-source.js.map +1 -0
  309. package/dist/runtime-windows-bridge.d.ts +41 -0
  310. package/dist/runtime-windows-bridge.js +157 -0
  311. package/dist/runtime-windows-bridge.js.map +1 -0
  312. package/dist/settings-api.d.ts +5 -0
  313. package/dist/settings-api.js +95 -1
  314. package/dist/settings-api.js.map +1 -1
  315. package/dist/settings-baseline.d.ts +1 -0
  316. package/dist/settings-baseline.js +1 -1
  317. package/dist/settings-baseline.js.map +1 -1
  318. package/dist/settings-change.js +28 -0
  319. package/dist/settings-change.js.map +1 -1
  320. package/dist/settings-confirmation.d.ts +16 -1
  321. package/dist/settings-confirmation.js +47 -9
  322. package/dist/settings-confirmation.js.map +1 -1
  323. package/dist/settings-effect.d.ts +4 -0
  324. package/dist/settings-effect.js +58 -1
  325. package/dist/settings-effect.js.map +1 -1
  326. package/dist/settings-http-confirmation.d.ts +1 -0
  327. package/dist/settings-http-confirmation.js +15 -2
  328. package/dist/settings-http-confirmation.js.map +1 -1
  329. package/dist/settings-request-capability.d.ts +2 -0
  330. package/dist/settings-request-capability.js +4 -0
  331. package/dist/settings-request-capability.js.map +1 -1
  332. package/dist/settings-route-lifetime.d.ts +14 -0
  333. package/dist/settings-route-lifetime.js +48 -0
  334. package/dist/settings-route-lifetime.js.map +1 -0
  335. package/dist/settings-transaction.d.ts +4 -0
  336. package/dist/settings-transaction.js +9 -0
  337. package/dist/settings-transaction.js.map +1 -1
  338. package/dist/setup-form.js +6 -3
  339. package/dist/setup-form.js.map +1 -1
  340. package/dist/setup-i18n.js +3 -0
  341. package/dist/setup-i18n.js.map +1 -1
  342. package/dist/tmux-control.d.ts +2 -1
  343. package/dist/tmux-control.js.map +1 -1
  344. package/dist/tmux-manager.d.ts +2 -1
  345. package/dist/tmux-manager.js.map +1 -1
  346. package/dist/tmux-pane-host.d.ts +26 -0
  347. package/dist/tmux-pane-host.js +25 -0
  348. package/dist/tmux-pane-host.js.map +1 -0
  349. package/dist/tool-permissions.js +2 -1
  350. package/dist/tool-permissions.js.map +1 -1
  351. package/dist/topic-commands.js +3 -0
  352. package/dist/topic-commands.js.map +1 -1
  353. package/dist/transcript-delta-reader.d.ts +1 -1
  354. package/dist/transcript-delta-reader.js +1 -1
  355. package/dist/transcript-delta-reader.js.map +1 -1
  356. package/dist/transcript-monitor.d.ts +3 -0
  357. package/dist/transcript-monitor.js +22 -46
  358. package/dist/transcript-monitor.js.map +1 -1
  359. package/dist/transcript-needles.d.ts +35 -0
  360. package/dist/transcript-needles.js +158 -0
  361. package/dist/transcript-needles.js.map +1 -0
  362. package/dist/transcript-records.d.ts +21 -0
  363. package/dist/transcript-records.js +140 -0
  364. package/dist/transcript-records.js.map +1 -0
  365. package/dist/transcript-sources.d.ts +5 -0
  366. package/dist/transcript-sources.js +8 -79
  367. package/dist/transcript-sources.js.map +1 -1
  368. package/dist/types.d.ts +23 -0
  369. package/dist/ui/agent-proposals.js +90 -0
  370. package/dist/ui/agent-runtime.js +106 -0
  371. package/dist/ui/chat-store.js +13 -2
  372. package/dist/ui/chat-strings.js +48 -0
  373. package/dist/ui/cli-picker.js +42 -0
  374. package/dist/ui/general-location.js +71 -0
  375. package/dist/ui/native-instance-settings.js +47 -0
  376. package/dist/ui/native-pane-recovery.js +45 -0
  377. package/dist/ui/panel-chat.js +63 -32
  378. package/dist/ui/panel-details.js +2 -1
  379. package/dist/ui/panel-fleet.js +99 -18
  380. package/dist/ui/panel-needs.js +154 -3
  381. package/dist/ui/panel-runtimes.js +231 -0
  382. package/dist/ui/panel-settings.js +19 -9
  383. package/dist/ui/panel-windows-agent.js +86 -0
  384. package/dist/ui/panel-windows-cli.js +143 -0
  385. package/dist/ui/provider-marks.js +83 -0
  386. package/dist/ui/runtime-catalog.js +55 -0
  387. package/dist/ui/settings-catalog.js +37 -10
  388. package/dist/ui/settings-confirm.js +3 -1
  389. package/dist/ui/settings-dialogs.js +22 -7
  390. package/dist/ui/settings-model.js +2 -1
  391. package/dist/ui/settings-outside.js +176 -0
  392. package/dist/ui/settings-registry.js +6 -0
  393. package/dist/ui/settings-strings.js +16 -0
  394. package/dist/ui/shared/app-i18n.js +2 -2
  395. package/dist/ui/shared/app-route.js +6 -2
  396. package/dist/ui/shared/app-shell.js +3 -2
  397. package/dist/ui/shared/app-store.js +1 -0
  398. package/dist/ui/shared/app-stream.js +3 -1
  399. package/dist/ui/shared/app.css +301 -0
  400. package/dist/ui/shared/app.js +4 -0
  401. package/dist/ui/shared/header-tools.js +6 -4
  402. package/dist/ui/shared/instance-nav.js +33 -7
  403. package/dist/ui/shared/instance-switch.js +4 -3
  404. package/dist/ui/shared/qrcode.module.js +2237 -0
  405. package/dist/ui/shared/signin.js +39 -5
  406. package/dist/ui/shared/ui-menu.js +28 -8
  407. package/dist/ui/shared/vendor/qrcode-generator.LICENSE +27 -0
  408. package/dist/ui/shared/vendor/vendor.json +19 -2
  409. package/dist/ui/shared/view-strings.js +8 -0
  410. package/dist/ui/windows-agents-nav.js +2 -0
  411. package/dist/ui/windows-cli-catalog.js +79 -0
  412. package/dist/web-api.d.ts +37 -0
  413. package/dist/web-api.js +248 -6
  414. package/dist/web-api.js.map +1 -1
  415. package/dist/web-chat-history.d.ts +19 -2
  416. package/dist/web-chat-history.js +72 -3
  417. package/dist/web-chat-history.js.map +1 -1
  418. package/dist/web-only-fleet.d.ts +20 -0
  419. package/dist/web-only-fleet.js +21 -0
  420. package/dist/web-only-fleet.js.map +1 -1
  421. package/dist/web-remote-access.d.ts +152 -0
  422. package/dist/web-remote-access.js +368 -0
  423. package/dist/web-remote-access.js.map +1 -0
  424. package/dist/web-session.d.ts +7 -0
  425. package/dist/web-session.js +2 -0
  426. package/dist/web-session.js.map +1 -1
  427. package/dist/web-shell-routes.d.ts +4 -1
  428. package/dist/web-shell-routes.js +7 -2
  429. package/dist/web-shell-routes.js.map +1 -1
  430. package/dist/windows-agents.d.ts +124 -0
  431. package/dist/windows-agents.js +487 -0
  432. package/dist/windows-agents.js.map +1 -0
  433. package/dist/windows-cli-api.d.ts +23 -0
  434. package/dist/windows-cli-api.js +158 -0
  435. package/dist/windows-cli-api.js.map +1 -0
  436. package/dist/windows-cli-providers.d.ts +115 -0
  437. package/dist/windows-cli-providers.js +429 -0
  438. package/dist/windows-cli-providers.js.map +1 -0
  439. package/dist/windows-cli-worker.d.ts +58 -0
  440. package/dist/windows-cli-worker.js +479 -0
  441. package/dist/windows-cli-worker.js.map +1 -0
  442. package/dist/windows-cli.d.ts +115 -0
  443. package/dist/windows-cli.js +402 -0
  444. package/dist/windows-cli.js.map +1 -0
  445. package/dist/windows-native-mcp.d.ts +19 -0
  446. package/dist/windows-native-mcp.js +143 -0
  447. package/dist/windows-native-mcp.js.map +1 -0
  448. package/dist/windows-pane-mcp.d.ts +26 -0
  449. package/dist/windows-pane-mcp.js +187 -0
  450. package/dist/windows-pane-mcp.js.map +1 -0
  451. package/package.json +9 -3
  452. package/templates/runtime-assets/conpty-LICENSE +21 -0
  453. package/templates/runtime-assets/conpty-NOTICE.md +503 -0
  454. package/templates/runtime-assets/local-pane-stage.ps1 +101 -0
  455. package/templates/runtime-assets/runtime-pane-job.ps1 +116 -0
  456. package/templates/runtime-assets/runtime-pane-private.ps1 +14 -0
  457. package/templates/runtime-assets/xterm-LICENSE +21 -0
@@ -1,3 +1,7 @@
1
+ import { NativePaneRecoveryError } from "./native-pane-recovery.js";
2
+ import { addsSecondGeneral } from "./web-only-fleet.js";
3
+ import { NativePaneCrashBudget } from "./native-pane-crash-budget.js";
4
+ import { localWindowsCliEnabled, remoteRuntimeEnabled } from "./runtime-switches.js";
1
5
  import { settingsUndo, undoSettingsPaths } from "./settings-transaction.js";
2
6
  import { measureSyncWork } from "./sync-work-attribution.js";
3
7
  import { REMOVED_BACKENDS, isRemovedBackend, removedBackendMessage } from "./backend/removed.js";
@@ -25,6 +29,7 @@ import { BACKEND_AUTH_CHECKS, checkAuthStatus, LOGIN_FLOWS } from "./login-flows
25
29
  import { CHAT_PROFILE_LOGIN_BACKENDS, credentialProfileEnv, credentialProfileLoginCommand } from "./backend/credential-profile.js";
26
30
  import { fetchClaudeUsage, fetchCodexUsage } from "./usage/providers.js";
27
31
  import { assertExplicitInstanceRemoval, authorizeExplicitInstanceRemoval } from "./instance-removal.js";
32
+ import { WINDOWS_CREATE_REFUSED, isNativePaneInstance } from "./native-instance-registry.js";
28
33
  import { capturePendingTargetReconciliation, finishTargetReconciliation, reconcileTargetBeforeStart, RECONCILIATION_WRITE_DRAIN_TIMEOUT_MS, retireCapturedTargetWindows, } from "./delivery-reconciliation.js";
29
34
  import { noPlaceMarkers, placeRef } from "./topic-link.js";
30
35
  export { isFleetStartCommandLine } from "./fleet-lock.js";
@@ -285,6 +290,7 @@ export class InstanceLifecycle {
285
290
  activeTransition = new Map();
286
291
  /** Bumped synchronously by stop/restart: a start begun under an older epoch must not publish. */
287
292
  lifecycleEpochs = new Map();
293
+ nativeExitOwners = new Map();
288
294
  /** Single wake flight per instance: concurrent wakes share it. */
289
295
  wakeFlights = new Map();
290
296
  /** Starts that begin their idle window now instead of at the persisted last inbound. */
@@ -682,7 +688,108 @@ export class InstanceLifecycle {
682
688
  this.ctx.notifyInstanceTopic(name, t("delivery.cancelled_unsent", messages.length, name));
683
689
  }, this.ctx.logger, `daemon.human_messages_cancelled[${name}]`));
684
690
  }
691
+ nativeRecoveryPending(name) { return this.nativeExitOwners.has(name); }
692
+ cancelNativeRecovery(name, owner) {
693
+ for (const [target, run] of this.nativeExitOwners) {
694
+ if (name !== undefined && target !== name || owner !== undefined && run.token !== owner)
695
+ continue;
696
+ clearTimeout(run.timer);
697
+ this.nativeExitOwners.delete(target);
698
+ }
699
+ }
700
+ resetNativeCrashBudget(name) {
701
+ this.cancelNativeRecovery(name);
702
+ const pin = this.ctx.nativePanePinnedSession?.(name);
703
+ if (pin) {
704
+ ensureInstanceDir(this.ctx.getInstanceDir(name));
705
+ new NativePaneCrashBudget(join(this.ctx.getInstanceDir(name), "native-crash-state.json")).reset(pin);
706
+ }
707
+ }
708
+ async nativePaneExited(name, daemon, data) {
709
+ const config = this.ctx.fleetConfig?.instances[name], epoch = this.epochOf(name), pin = data.snapshot.sessionId, profilePin = this.ctx.nativePanePinnedSession?.(name) ?? undefined;
710
+ if (!isNativePaneInstance(config) || data.name !== name || this.daemons.get(name) !== daemon || !daemon.nativePaneExitCurrent(data.snapshot, data.owner)
711
+ || pin !== undefined && profilePin !== undefined && profilePin !== pin || this.nativeExitOwners.has(name) || this.ctx.isPlannedRestart())
712
+ return;
713
+ const token = Symbol(name), run = { token, daemon, timer: undefined };
714
+ this.nativeExitOwners.set(name, run);
715
+ const current = () => this.nativeExitOwners.get(name) === run && this.daemons.get(name) === daemon && this.epochOf(name) === epoch
716
+ && this.ctx.fleetConfig?.instances[name] === config && ((this.ctx.nativePanePinnedSession?.(name) ?? undefined) === profilePin || profilePin === undefined && pin !== undefined && this.ctx.nativePanePinnedSession?.(name) === pin) && !this.isPaused(name) && !this.ctx.isPlannedRestart()
717
+ && (config.machine_id ? remoteRuntimeEnabled() : localWindowsCliEnabled());
718
+ daemon.fenceDeliveryWritesForStop();
719
+ try {
720
+ const decision = pin && profilePin ? new NativePaneCrashBudget(join(this.ctx.getInstanceDir(name), "native-crash-state.json")).observe(pin, data.snapshot.exitCode, config.restart_policy) : null;
721
+ await this.runTransition(name, async () => {
722
+ if (!current())
723
+ return;
724
+ if (!await daemon.waitForDeliveryWritesToDrain(RECONCILIATION_WRITE_DRAIN_TIMEOUT_MS) || !current())
725
+ throw Error("Native input writes have not drained");
726
+ if (this.ctx.deliveryOutbox) {
727
+ this.ctx.deliveryOutbox.markTargetReconciliationPending(name, daemon.bootId);
728
+ if (!await daemon.reconcileNativeDeliveries(this.ctx.deliveryOutbox, current) || !current())
729
+ throw Error("Native delivery reconciliation is unconfirmed");
730
+ }
731
+ // Close finalizes the exact positive exit; no PID lookup or signal exists on this path.
732
+ await daemon.stop({ nativePane: "close" });
733
+ if (!current())
734
+ return;
735
+ this.ctx.eventLog?.insert(name, "native_pane_exited", { exitCode: data.snapshot.exitCode ?? null, sessionId: pin, decision: decision?.kind ?? "no-conversation", count: decision?.count ?? 0 });
736
+ if (!decision) {
737
+ this.ctx.holdNativePaneRecovery?.(name, new NativePaneRecoveryError({ kind: "session-unconfirmed", launchId: data.snapshot.launchId, sessionId: this.ctx.nativePanePinnedSession?.(name) ?? undefined, ...(config.backend === "codex" ? { provider: "codex" } : {}) }));
738
+ this.ctx.setTopicIcon(name, "red");
739
+ return;
740
+ }
741
+ if (decision.kind !== "retry") {
742
+ if (decision.kind === "normal")
743
+ await this.ctx.notifyNormalExit(name);
744
+ else
745
+ this.notifyIncident(name, decision.kind === "loop" ? "crash_loop" : "supervision_ended", t("inst.respawn_paused", name));
746
+ this.ctx.setTopicIcon(name, "red");
747
+ return;
748
+ }
749
+ const timer = setTimeout(() => {
750
+ run.timer = undefined;
751
+ void this.runTransition(name, async () => {
752
+ if (!current())
753
+ return;
754
+ await this.stopInTransition(name, token);
755
+ // Our own stop advances once. An operator's concurrent stop/config change prevents publication.
756
+ if (this.nativeExitOwners.get(name) !== run || this.epochOf(name) !== epoch + 1 || this.daemons.has(name)
757
+ || this.ctx.fleetConfig?.instances[name] !== config || this.ctx.nativePanePinnedSession?.(name) !== pin || this.isPaused(name) || this.ctx.isPlannedRestart()
758
+ || !(config.machine_id ? remoteRuntimeEnabled() : localWindowsCliEnabled()))
759
+ return;
760
+ await this.startInTransition(name, config, true, { kind: "fleet-topic", backend: config.backend ?? "codex", model: config.model || "default" });
761
+ if (this.nativeExitOwners.get(name) !== run || this.epochOf(name) !== epoch + 1 || this.ctx.isPlannedRestart())
762
+ return;
763
+ await this.ctx.connectIpcToInstance(name);
764
+ }).catch(error => { if (this.nativeExitOwners.get(name) === run && !this.ctx.isPlannedRestart()) {
765
+ if (this.ctx.holdNativePaneRecovery?.(name, error))
766
+ return;
767
+ this.ctx.logger.error({ name, err: error }, "Native conversation resume failed; its pin was retained");
768
+ this.notifyIncident(name, "supervision_ended", t("inst.respawn_paused", name));
769
+ } })
770
+ .finally(() => { this.cancelNativeRecovery(name, token); this.ctx.onNativePaneRecoverySettled?.(name); });
771
+ }, decision.delay);
772
+ timer.unref?.();
773
+ run.timer = timer;
774
+ });
775
+ if (!run.timer)
776
+ this.cancelNativeRecovery(name, token);
777
+ }
778
+ catch (error) {
779
+ if (this.nativeExitOwners.get(name) === run && !this.ctx.isPlannedRestart()) {
780
+ this.ctx.logger.error({ name, err: error }, "Native crash recovery held without a fresh fallback");
781
+ this.notifyIncident(name, "supervision_ended", t("inst.respawn_paused", name));
782
+ }
783
+ this.cancelNativeRecovery(name, token);
784
+ }
785
+ finally {
786
+ if (!run.timer)
787
+ this.ctx.onNativePaneRecoverySettled?.(name);
788
+ }
789
+ }
685
790
  attachIncidentHandlers(name, daemon) {
791
+ if (daemon.usesNativePane?.())
792
+ daemon.on("native_pane_exited", safeHandler((data) => this.nativePaneExited(name, daemon, data), this.ctx.logger, `daemon.native_pane_exited[${name}]`));
686
793
  daemon.on("capacity_nudge_cancelled", safeHandler((data) => {
687
794
  const reason = data.reason === "the user cancelled" ? t("inst.codex_retry_cancel_reason_user")
688
795
  : data.reason === "the instance was stopped, paused or respawned" || data.reason === "the instance is not running" ? t("inst.codex_retry_cancel_reason_stopped")
@@ -981,19 +1088,22 @@ export class InstanceLifecycle {
981
1088
  // labels, summaries and every exhaustive match for one picker's text.)
982
1089
  // #1691: a prompt no table knows — what it asks (one sanitised, bounded line from the daemon, never the pane), and
983
1090
  // that AgEnD will not press anything.
1091
+ // S-35: a Windows agent's instance name is windows-<uuid>; people know it by its display name.
1092
+ const label = this.ctx.fleetConfig?.instances[name]?.display_name?.trim() || name;
1093
+ const place = placeRef({ kind: "instance", name }, label);
984
1094
  if (data.unrecognised) {
985
1095
  // #1990: the question is a pane line — it may not name a place of its own.
986
1096
  const asked = data.question ? t("inst.unrecognised_prompt_question", noPlaceMarkers(data.question)) : "";
987
- this.notifyParked(name, "dialog_parked", t("inst.unrecognised_prompt", name, asked), t("fleet.unrecognised_prompt", instancePlace(name), asked));
1097
+ this.notifyParked(name, "dialog_parked", t("inst.unrecognised_prompt", label, asked), t("fleet.unrecognised_prompt", place, asked));
988
1098
  return;
989
1099
  }
990
1100
  const category = data.kind && data.kind !== "dialog" ? interactionCategory(data.kind, data.backend) : null;
991
- const text = category ? t("inst.interaction_parked", name, category) : data.holdOnly
1101
+ const text = category ? t("inst.interaction_parked", label, category) : data.holdOnly
992
1102
  // #2872: the Claude session-resume advice only for that prompt (it read as nonsense under a grok or kiro dialog).
993
- ? t("inst.dialog_parked_hold", name, data.description) + (data.advice === "claude-resume" ? " " + t("inst.dialog_parked_hold_claude_resume") : "")
994
- : t("inst.dialog_parked_stuck", name, data.description) + (data.advice === "claude-resume" ? " " + t("inst.dialog_parked_stuck_claude_resume") : "");
1103
+ ? t("inst.dialog_parked_hold", label, data.description) + (data.advice === "claude-resume" ? " " + t("inst.dialog_parked_hold_claude_resume") : "")
1104
+ : t("inst.dialog_parked_stuck", label, data.description) + (data.advice === "claude-resume" ? " " + t("inst.dialog_parked_stuck_claude_resume") : "");
995
1105
  // #1990: in General, the instance's name links to its topic.
996
- this.notifyParked(name, "dialog_parked", text, category ? t("fleet.interaction_parked", instancePlace(name), category) : t("fleet.dialog_parked", instancePlace(name), data.description));
1106
+ this.notifyParked(name, "dialog_parked", text, category ? t("fleet.interaction_parked", place, category) : t("fleet.dialog_parked", place, data.description));
997
1107
  }, this.ctx.logger, `daemon.dialog_parked[${name}]`));
998
1108
  // #2897: the held dialog is gone (closed, answered, auto-answered) — its notices go with it.
999
1109
  daemon.on("dialog_unparked", safeHandler(() => this.ctx.resolveParkedNotice?.(name), this.ctx.logger, `daemon.dialog_unparked[${name}]`));
@@ -1030,6 +1140,21 @@ export class InstanceLifecycle {
1030
1140
  daemon.on("pty_error", safeHandler(async (data) => {
1031
1141
  this.ctx.eventLog?.insert(name, "pty_error", { type: data.type, action: data.action });
1032
1142
  this.ctx.logger.warn({ name, errorType: data.type, action: data.action }, `PTY error: ${data.message}`);
1143
+ if (daemon.usesNativePane?.()) {
1144
+ // Native credentials and quotas belong to its runner. Never probe a Linux login or group its peers with it.
1145
+ if (this.daemons.get(name) !== daemon)
1146
+ return;
1147
+ this.notifyIncident(name, data.type, data.notice ?? data.message);
1148
+ if (data.action === "pause") {
1149
+ if (data.type === "auth_error") {
1150
+ daemon.confirmAuthFailure?.();
1151
+ daemon.requestPauseWhenIdle({ reconfirmAuth: true, reason: "auth" });
1152
+ }
1153
+ else
1154
+ daemon.requestPauseWhenIdle({ reason: "error" });
1155
+ }
1156
+ return;
1157
+ }
1033
1158
  // Antigravity's account-level cap is visible ONLY here: the quota summary
1034
1159
  // API keeps reporting its buckets as barely used while the CLI is blocked
1035
1160
  // (verified live — 0% used and "Individual quota reached" in the same
@@ -1366,7 +1491,10 @@ export class InstanceLifecycle {
1366
1491
  return this.lifecycleEpochs.get(name) ?? 0;
1367
1492
  }
1368
1493
  /** Invalidate every start/wake already running for `name` (they will not publish). */
1369
- invalidate(name) {
1494
+ invalidate(name, nativeOwner) {
1495
+ const native = this.nativeExitOwners.get(name);
1496
+ if (native && native.token !== nativeOwner)
1497
+ this.cancelNativeRecovery(name);
1370
1498
  this.lifecycleEpochs.set(name, this.epochOf(name) + 1);
1371
1499
  }
1372
1500
  /** Whether `transition` is `name`'s transition and is the one running now. */
@@ -1434,170 +1562,194 @@ export class InstanceLifecycle {
1434
1562
  const check = () => { execution?.assert(); if (this.epochOf(name) !== epoch)
1435
1563
  throw new SupersededStartError(name); };
1436
1564
  check();
1565
+ this.ctx.assertGeneralBindingStart?.(name, config);
1437
1566
  assertNoSessionOomStop(this.ctx.getInstanceDir(name));
1438
1567
  const seedNow = this.activitySeedNow.delete(name);
1439
1568
  if (this.daemons.has(name)) {
1440
1569
  this.ctx.logger.info({ name }, "Instance already running, skipping");
1441
1570
  return;
1442
1571
  }
1443
- if (!existsSync(config.working_directory)) {
1444
- this.ctx.logger.info({ name, working_directory: config.working_directory }, "Working directory does not exist — creating it");
1445
- mkdirSync(config.working_directory, { recursive: true });
1446
- }
1447
- const instanceDir = this.ctx.getInstanceDir(name);
1448
- ensureInstanceDir(instanceDir);
1449
- // Daemon.start consumes the recovery marker after reading it. A start
1450
- // that fails before reaching the daemon must leave that intent intact.
1451
- const { Daemon } = await import("./daemon.js");
1452
- const { createBackend } = await import("./backend/factory.js");
1453
- check();
1454
- const backendName = config.backend ?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
1455
- // A removed backend (#1280): this instance refuses to start, says why and what to use instead, and the rest of
1456
- // the fleet is unaffected. Never a fallback to another backend.
1457
- if (isRemovedBackend(backendName)) {
1458
- const message = removedBackendMessage(backendName, name);
1459
- this.ctx.logger.error({ name, backend: backendName }, `Not starting: ${message}`);
1460
- this.ctx.notifyInstanceTopic(name, t("inst.backend_removed", name, backendName, REMOVED_BACKENDS[backendName].replacement));
1461
- return;
1462
- }
1463
- // Verify backend binary is in PATH before spawning. Off-loop resolve pins a
1464
- // hint so createBackend's resolveBinary does not run synchronous `which`
1465
- // (#1490 row 67). Missing still only logs; start continues.
1466
- const installation = BACKEND_INSTALLATION_INFO[backendName];
1467
- if (installation) {
1468
- const { resolveBackendBinary } = await import("./backend-availability.js");
1469
- const path = await resolveBackendBinary(backendName);
1470
- noteResolvedBinary(installation.binary, path ?? installation.binary);
1572
+ const native = isNativePaneInstance(config) ? await this.ctx.nativePaneFor?.(name, config, check) : undefined;
1573
+ let nativePublished = false;
1574
+ try {
1471
1575
  check();
1472
- if (!path) {
1473
- this.ctx.logger.error({ binary: installation.binary, backend: backendName, instance: name }, `Backend binary "${installation.binary}" not found in PATH`);
1576
+ if (isNativePaneInstance(config) && !native)
1577
+ throw Error("Native pane ownership is unavailable; no local CLI was started");
1578
+ if (native)
1579
+ config = { ...config, agent_mode: "mcp" };
1580
+ if (!native && !existsSync(config.working_directory)) {
1581
+ this.ctx.logger.info({ name, working_directory: config.working_directory }, "Working directory does not exist — creating it");
1582
+ mkdirSync(config.working_directory, { recursive: true });
1474
1583
  }
1475
- }
1476
- // claude-code refuses --dangerously-skip-permissions as root — fail fast
1477
- // Unless IS_SANDBOX=1 is set (recognized sandbox environment)
1478
- if (process.getuid?.() === 0 && backendName === "claude-code" && !process.env.IS_SANDBOX) {
1479
- const msg = `⚠️ claude-code cannot run with --dangerously-skip-permissions as root. Set IS_SANDBOX=1 or use a non-root user.`;
1480
- this.ctx.logger.error({ name, backend: backendName }, msg);
1481
- this.ctx.notifyInstanceTopic(name, t("inst.root_skip_perms"));
1482
- return;
1483
- }
1484
- const backend = createBackend(backendName, instanceDir);
1485
- const daemon = new Daemon(name, config, instanceDir, topicMode, backend, this.ctx.controlClient ?? undefined, this.ctx.logger, runtimeIdentity ?? {
1486
- kind: "fleet-topic",
1487
- backend: backendName,
1488
- model: config.model ?? "default",
1489
- }, this.ctx.spawnGate, this.ctx.stormWindow, this.ctx.backendOutage);
1490
- daemon.setDeliveryOutboxPort(this.ctx.deliveryOutbox ?? undefined);
1491
- daemon.setWorkLeaseCheck(() => this.hasWorkLease(name));
1492
- if (seedNow)
1493
- daemon.seedActivityNow();
1494
- // Read at each spawn so a fleet.yaml reload is honoured (#984).
1495
- daemon.setPeerWorkingDirectories(backendName === "kiro-cli"
1496
- ? this.ctx.kiroPeerWorkingDirectories ? () => this.ctx.kiroPeerWorkingDirectories(name) : undefined
1497
- : () => peerWorkingDirectories(this.ctx.fleetConfig, name, backendName));
1498
- daemon.setStatusEmojiAvoidList(() => this.ctx.statusEmojiAvoidList?.(name));
1499
- // Before start: the first spawn builds the command and emits its launch
1500
- // warning synchronously inside Daemon.start, long before
1501
- // attachIncidentHandlers runs (#984).
1502
- this.attachLaunchWarningHandler(name, daemon);
1503
- this.attachContextLostHandler(name, daemon);
1504
- daemon.on("durable_delivery_deferred", (data) => {
1505
- // A daemon without its outbox port cannot update the row directly. The
1506
- // lifecycle owns the same-process store and records a bounded pre-write
1507
- // retry instead of leaving the lane in delivering forever.
1508
- this.ctx.deliveryOutbox?.retryBeforeBegin(data.deliveryId, data.targetBootId, data.attemptNo, data.reason, 5_000);
1509
- });
1510
- daemon.on("mcp_tool_response_written", (data) => {
1511
- if (!data.operationId || data.daemonBootId !== daemon.bootId)
1584
+ const instanceDir = this.ctx.getInstanceDir(name);
1585
+ ensureInstanceDir(instanceDir);
1586
+ // Daemon.start consumes the recovery marker after reading it. A start
1587
+ // that fails before reaching the daemon must leave that intent intact.
1588
+ const { Daemon } = await import("./daemon.js");
1589
+ const { createBackend } = await import("./backend/factory.js");
1590
+ check();
1591
+ const backendName = config.backend ?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
1592
+ // A removed backend (#1280): this instance refuses to start, says why and what to use instead, and the rest of
1593
+ // the fleet is unaffected. Never a fallback to another backend.
1594
+ if (isRemovedBackend(backendName)) {
1595
+ const message = removedBackendMessage(backendName, name);
1596
+ this.ctx.logger.error({ name, backend: backendName }, `Not starting: ${message}`);
1597
+ this.ctx.notifyInstanceTopic(name, t("inst.backend_removed", name, backendName, REMOVED_BACKENDS[backendName].replacement));
1512
1598
  return;
1513
- try {
1514
- this.ctx.deliveryOutbox?.markResponseDelivered(name, data.operationId);
1515
1599
  }
1516
- catch (err) {
1517
- this.ctx.logger.error({ name, operationId: data.operationId, err }, "Could not persist MCP response-delivered marker");
1600
+ // Verify backend binary is in PATH before spawning. Off-loop resolve pins a
1601
+ // hint so createBackend's resolveBinary does not run synchronous `which`
1602
+ // (#1490 row 67). Missing still only logs; start continues.
1603
+ const installation = BACKEND_INSTALLATION_INFO[backendName];
1604
+ // alpha: a native Windows pane's CLI is on that PC — nothing to look for on this one.
1605
+ if (!native && installation) {
1606
+ const { resolveBackendBinary } = await import("./backend-availability.js");
1607
+ const path = await resolveBackendBinary(backendName);
1608
+ noteResolvedBinary(installation.binary, path ?? installation.binary);
1609
+ check();
1610
+ if (!path) {
1611
+ this.ctx.logger.error({ binary: installation.binary, backend: backendName, instance: name }, `Backend binary "${installation.binary}" not found in PATH`);
1612
+ }
1518
1613
  }
1519
- });
1520
- this.applyCapacityBackoffBaseline(name, backendName, daemon);
1521
- // Catch errors from daemon internals (e.g. IPC server) to prevent crashing the fleet process
1522
- daemon.on("error", (err) => {
1523
- this.ctx.logger.error({ err, name }, "Daemon emitted error — instance isolated");
1524
- });
1525
- if (this.ctx.deliveryOutbox) {
1526
- // Fence older-generation submissions and reconcile them before Daemon.start
1527
- // reaches Strategy A's unconditional old-window kill.
1528
- const recovered = this.ctx.deliveryOutbox.recoverTargetGeneration(name, daemon.bootId);
1529
- if (recovered.queued || recovered.reconciliationPending) {
1530
- this.ctx.logger.warn({ name, ...recovered }, "Preparing durable outbox rows before target generation start");
1531
- }
1532
- const reconciled = await reconcileTargetBeforeStart(this.ctx.deliveryOutbox, name, instanceDir, this.ctx.logger);
1533
- if (reconciled.uncertain > 0) {
1534
- this.ctx.logger.warn({ name, ...reconciled }, "Some durable submissions need operator reconciliation");
1535
- }
1536
- if (!reconciled.safeToStart) {
1537
- const message = t("delivery.restart_blocked", name);
1538
- this.ctx.logger.error({ name, ...reconciled }, message);
1539
- this.ctx.notifyFleetError?.(message);
1540
- await daemon.abortStartup().catch(err => this.ctx.logger.warn({ err, name }, "Failed to dispose daemon after reconciliation could not verify old CLI exit"));
1541
- throw new Error(message);
1542
- }
1543
- }
1544
- if (this.epochOf(name) !== epoch || execution && !execution.current()) {
1545
- // A stop/restart arrived while this start was reconciling: do not spawn.
1546
- await daemon.abortStartup().catch(() => { });
1547
- throw new SupersededStartError(name);
1548
- }
1549
- // The startup scan can raise an incident (sign-in screen, fatal modal)
1550
- // before the handlers below exist and before this daemon is registered;
1551
- // hold it and deliver it once both are true.
1552
- daemon.holdStartupIncidents?.();
1553
- await InstanceLifecycle.startOrDispose(daemon, name, this.ctx.logger, execution ? check : undefined);
1554
- // Publication fence (Phase 2a): the spawn cannot be cancelled, so a stop or
1555
- // restart requested meanwhile has been waiting behind this transition. A
1556
- // superseded start must not register, signal ready, or keep its window.
1557
- if (this.epochOf(name) !== epoch || execution && !execution.current()) {
1558
- await daemon.abortStartup().catch(err => this.ctx.logger.warn({ err, name }, "Failed to dispose a superseded start"));
1559
- throw new SupersededStartError(name);
1560
- }
1561
- this.capacityBackoffBaselines.delete(name);
1562
- this.daemons.set(name, daemon);
1563
- published?.(daemon); // Exact publication, synchronously after the ownership fence.
1564
- if (seedNow)
1565
- this.noteWokeForWork(name);
1566
- this.ctx.onDaemonReady?.(name, daemon.bootId);
1567
- daemon.on("auto_pause_requested", safeHandler(async (data) => {
1568
- await this.pause(name, data?.reason ?? "idle");
1569
- }, this.ctx.logger, `autoPause[${name}]`));
1570
- daemon.on("auto_paused", (data) => {
1571
- this.ctx.eventLog?.insert(name, "instance_paused", { reason: "idle", paused_at: data.pausedAt });
1572
- this.ctx.logger.info({ name, pausedAt: data.pausedAt }, "Instance auto-paused after idle timeout");
1573
- this.ctx.setTopicIcon(name, "remove");
1574
- this.ctx.onAttentionChanged?.(name);
1575
- });
1576
- daemon.on("auto_woke", () => {
1577
- this.ctx.onAttentionChanged?.(name);
1578
- this.ctx.eventLog?.insert(name, "instance_resumed", { reason: "message" });
1579
- this.ctx.logger.info({ name }, "Instance auto-woke for delivery");
1614
+ // claude-code refuses --dangerously-skip-permissions as root — fail fast
1615
+ // Unless IS_SANDBOX=1 is set (recognized sandbox environment)
1616
+ if (!native && process.getuid?.() === 0 && backendName === "claude-code" && !process.env.IS_SANDBOX) {
1617
+ const msg = `⚠️ claude-code cannot run with --dangerously-skip-permissions as root. Set IS_SANDBOX=1 or use a non-root user.`;
1618
+ this.ctx.logger.error({ name, backend: backendName }, msg);
1619
+ this.ctx.notifyInstanceTopic(name, t("inst.root_skip_perms"));
1620
+ return;
1621
+ }
1622
+ const backend = native?.backend ?? createBackend(backendName, instanceDir);
1623
+ const daemon = new Daemon(name, config, instanceDir, topicMode, backend, this.ctx.controlClient ?? undefined, this.ctx.logger, runtimeIdentity ?? {
1624
+ kind: "fleet-topic",
1625
+ backend: backendName,
1626
+ model: config.model ?? "default",
1627
+ }, this.ctx.spawnGate, this.ctx.stormWindow, this.ctx.backendOutage);
1628
+ if (native) {
1629
+ daemon.setNativePaneLifecycle(native);
1630
+ native.bindDaemon?.(daemon);
1631
+ }
1632
+ daemon.setDeliveryOutboxPort(this.ctx.deliveryOutbox ?? undefined);
1633
+ daemon.setWorkLeaseCheck(() => this.hasWorkLease(name));
1634
+ if (seedNow)
1635
+ daemon.seedActivityNow();
1636
+ // Read at each spawn so a fleet.yaml reload is honoured (#984).
1637
+ daemon.setPeerWorkingDirectories(backendName === "kiro-cli"
1638
+ ? this.ctx.kiroPeerWorkingDirectories ? () => this.ctx.kiroPeerWorkingDirectories(name) : undefined
1639
+ : () => peerWorkingDirectories(this.ctx.fleetConfig, name, backendName));
1640
+ daemon.setStatusEmojiAvoidList(() => this.ctx.statusEmojiAvoidList?.(name));
1641
+ // Before start: the first spawn builds the command and emits its launch
1642
+ // warning synchronously inside Daemon.start, long before
1643
+ // attachIncidentHandlers runs (#984).
1644
+ this.attachLaunchWarningHandler(name, daemon);
1645
+ this.attachContextLostHandler(name, daemon);
1646
+ daemon.on("durable_delivery_deferred", (data) => {
1647
+ // A daemon without its outbox port cannot update the row directly. The
1648
+ // lifecycle owns the same-process store and records a bounded pre-write
1649
+ // retry instead of leaving the lane in delivering forever.
1650
+ this.ctx.deliveryOutbox?.retryBeforeBegin(data.deliveryId, data.targetBootId, data.attemptNo, data.reason, 5_000);
1651
+ });
1652
+ daemon.on("mcp_tool_response_written", (data) => {
1653
+ if (!data.operationId || data.daemonBootId !== daemon.bootId)
1654
+ return;
1655
+ try {
1656
+ this.ctx.deliveryOutbox?.markResponseDelivered(name, data.operationId);
1657
+ }
1658
+ catch (err) {
1659
+ this.ctx.logger.error({ name, operationId: data.operationId, err }, "Could not persist MCP response-delivered marker");
1660
+ }
1661
+ });
1662
+ this.applyCapacityBackoffBaseline(name, backendName, daemon);
1663
+ // Catch errors from daemon internals (e.g. IPC server) to prevent crashing the fleet process
1664
+ daemon.on("error", (err) => {
1665
+ this.ctx.logger.error({ err, name }, "Daemon emitted error — instance isolated");
1666
+ });
1667
+ if (this.ctx.deliveryOutbox) {
1668
+ // Fence older-generation submissions and reconcile them before Daemon.start
1669
+ // reaches Strategy A's unconditional old-window kill.
1670
+ const recovered = this.ctx.deliveryOutbox.recoverTargetGeneration(name, daemon.bootId);
1671
+ if (recovered.queued || recovered.reconciliationPending) {
1672
+ this.ctx.logger.warn({ name, ...recovered }, "Preparing durable outbox rows before target generation start");
1673
+ }
1674
+ const reconciled = native
1675
+ ? { uncertain: 0, safeToStart: await this.ctx.reconcileNativePaneBeforeStart?.(name, daemon) === true }
1676
+ : await reconcileTargetBeforeStart(this.ctx.deliveryOutbox, name, instanceDir, this.ctx.logger);
1677
+ if (reconciled.uncertain > 0) {
1678
+ this.ctx.logger.warn({ name, ...reconciled }, "Some durable submissions need operator reconciliation");
1679
+ }
1680
+ if (!reconciled.safeToStart) {
1681
+ const message = t("delivery.restart_blocked", name);
1682
+ this.ctx.logger.error({ name, ...reconciled }, message);
1683
+ this.ctx.notifyFleetError?.(message);
1684
+ await daemon.abortStartup().catch(err => this.ctx.logger.warn({ err, name }, "Failed to dispose daemon after reconciliation could not verify old CLI exit"));
1685
+ throw new Error(message);
1686
+ }
1687
+ }
1688
+ if (this.epochOf(name) !== epoch || execution && !execution.current()) {
1689
+ // A stop/restart arrived while this start was reconciling: do not spawn.
1690
+ await daemon.abortStartup().catch(() => { });
1691
+ throw new SupersededStartError(name);
1692
+ }
1693
+ // The startup scan can raise an incident (sign-in screen, fatal modal)
1694
+ // before the handlers below exist and before this daemon is registered;
1695
+ // hold it and deliver it once both are true.
1696
+ daemon.holdStartupIncidents?.();
1697
+ await InstanceLifecycle.startOrDispose(daemon, name, this.ctx.logger, execution || native ? check : undefined);
1698
+ // Publication fence (Phase 2a): the spawn cannot be cancelled, so a stop or
1699
+ // restart requested meanwhile has been waiting behind this transition. A
1700
+ // superseded start must not register, signal ready, or keep its window.
1701
+ if (this.epochOf(name) !== epoch || execution && !execution.current()) {
1702
+ await daemon.abortStartup().catch(err => this.ctx.logger.warn({ err, name }, "Failed to dispose a superseded start"));
1703
+ throw new SupersededStartError(name);
1704
+ }
1705
+ this.capacityBackoffBaselines.delete(name);
1706
+ this.daemons.set(name, daemon);
1707
+ nativePublished = !!native;
1708
+ published?.(daemon); // Exact publication, synchronously after the ownership fence.
1709
+ if (seedNow)
1710
+ this.noteWokeForWork(name);
1711
+ this.ctx.onDaemonReady?.(name, daemon.bootId);
1712
+ daemon.on("auto_pause_requested", safeHandler(async (data) => {
1713
+ await this.pause(name, data?.reason ?? "idle");
1714
+ }, this.ctx.logger, `autoPause[${name}]`));
1715
+ daemon.on("auto_paused", (data) => {
1716
+ this.ctx.eventLog?.insert(name, "instance_paused", { reason: "idle", paused_at: data.pausedAt });
1717
+ this.ctx.logger.info({ name, pausedAt: data.pausedAt }, "Instance auto-paused after idle timeout");
1718
+ this.ctx.setTopicIcon(name, "remove");
1719
+ this.ctx.onAttentionChanged?.(name);
1720
+ });
1721
+ daemon.on("auto_woke", () => {
1722
+ this.ctx.onAttentionChanged?.(name);
1723
+ this.ctx.eventLog?.insert(name, "instance_resumed", { reason: "message" });
1724
+ this.ctx.logger.info({ name }, "Instance auto-woke for delivery");
1725
+ this.ctx.setTopicIcon(name, "green");
1726
+ this.ctx.touchActivity(name);
1727
+ });
1728
+ this.attachIncidentHandlers(name, daemon);
1729
+ // #2897: a new daemon starts with no dialog held; a notice the last one left (a crash, a respawn) is out of date.
1730
+ this.ctx.resolveParkedNotice?.(name);
1731
+ daemon.on("pty_recovered", safeHandler((data) => {
1732
+ const mins = Math.floor(data.downtime_s / 60);
1733
+ const secs = data.downtime_s % 60;
1734
+ const duration = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
1735
+ this.ctx.eventLog?.insert(name, "pty_recovered", { downtime_s: data.downtime_s });
1736
+ this.ctx.logger.info({ name, downtime_s: data.downtime_s }, "PTY error recovered");
1737
+ this.ctx.notifyInstanceTopic(name, t("inst.recovered", name, duration));
1738
+ this.ctx.webhookEmit("pty_recovered", name, { downtime_s: data.downtime_s });
1739
+ }, this.ctx.logger, `daemon.pty_recovered[${name}]`));
1740
+ this.attachDeliveryStatusHandlers(name, daemon);
1580
1741
  this.ctx.setTopicIcon(name, "green");
1581
1742
  this.ctx.touchActivity(name);
1582
- });
1583
- this.attachIncidentHandlers(name, daemon);
1584
- // #2897: a new daemon starts with no dialog held; a notice the last one left (a crash, a respawn) is out of date.
1585
- this.ctx.resolveParkedNotice?.(name);
1586
- daemon.on("pty_recovered", safeHandler((data) => {
1587
- const mins = Math.floor(data.downtime_s / 60);
1588
- const secs = data.downtime_s % 60;
1589
- const duration = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
1590
- this.ctx.eventLog?.insert(name, "pty_recovered", { downtime_s: data.downtime_s });
1591
- this.ctx.logger.info({ name, downtime_s: data.downtime_s }, "PTY error recovered");
1592
- this.ctx.notifyInstanceTopic(name, t("inst.recovered", name, duration));
1593
- this.ctx.webhookEmit("pty_recovered", name, { downtime_s: data.downtime_s });
1594
- }, this.ctx.logger, `daemon.pty_recovered[${name}]`));
1595
- this.attachDeliveryStatusHandlers(name, daemon);
1596
- this.ctx.setTopicIcon(name, "green");
1597
- this.ctx.touchActivity(name);
1598
- // Last: handlers attached and daemon registered, so a held startup incident
1599
- // (pause, notify) finds both.
1600
- daemon.releaseStartupIncidents?.();
1743
+ // Last: handlers attached and daemon registered, so a held startup incident
1744
+ // (pause, notify) finds both.
1745
+ daemon.releaseStartupIncidents?.();
1746
+ }
1747
+ catch (error) {
1748
+ // The factory registered helper authority before acquire(). A superseded pre-window start must release it too.
1749
+ if (native && !nativePublished)
1750
+ await native.release("detach").catch(err => this.ctx.logger.warn({ name, err }, "Prepared native authority could not detach"));
1751
+ throw error;
1752
+ }
1601
1753
  }
1602
1754
  isPaused(name) {
1603
1755
  return this.daemons.get(name)?.isPaused ?? hasPausedMarker(this.ctx.getInstanceDir(name));
@@ -1606,11 +1758,30 @@ export class InstanceLifecycle {
1606
1758
  return this.daemons.get(name)?.lastPausedAt ?? readPausedAt(this.ctx.getInstanceDir(name));
1607
1759
  }
1608
1760
  async pause(name, reason = "operator", admission) {
1761
+ this.cancelNativeRecovery(name);
1609
1762
  // Final backstop shared by slash, MCP, Settings, warm-cap, and any future
1610
1763
  // caller. General is the coordinator and must remain resident to route work.
1611
1764
  if (isGeneralInstance(this.ctx.fleetConfig, name)) {
1612
1765
  throw new Error(GENERAL_PAUSE_ERROR);
1613
1766
  }
1767
+ const nativeDaemon = this.daemons.get(name);
1768
+ if (nativeDaemon?.usesNativePane?.()) {
1769
+ if (admission && !admission.current())
1770
+ return;
1771
+ await this.runTransition(name, async () => {
1772
+ if (this.daemons.get(name) !== nativeDaemon || admission && !admission.current())
1773
+ return;
1774
+ if (!nativeDaemon.nativePaneHasExited() && (!await nativeDaemon.nativePauseReady(admission) || this.daemons.get(name) !== nativeDaemon || !nativeDaemon.nativePauseStillReady()))
1775
+ return;
1776
+ if (this.daemons.get(name) !== nativeDaemon || admission && !admission.current())
1777
+ return;
1778
+ await this.stopInTransition(name);
1779
+ this.ctx.setNativePanePaused?.(name, true);
1780
+ writePausedMarker(this.ctx.getInstanceDir(name), Date.now(), reason);
1781
+ this.ctx.onAttentionChanged?.(name);
1782
+ });
1783
+ return;
1784
+ }
1614
1785
  // B1: cancel any pending capacity backoff — a user-initiated pause should
1615
1786
  // not be undone by a queued restart 30–120s later.
1616
1787
  const pendingBackoff = this.capacityBackoffTimers.get(name);
@@ -1694,16 +1865,21 @@ export class InstanceLifecycle {
1694
1865
  clearPausedMarker(instanceDir);
1695
1866
  this.markActivitySeedNow(name);
1696
1867
  try {
1868
+ if (isNativePaneInstance(this.ctx.fleetConfig?.instances[name]))
1869
+ this.ctx.setNativePanePaused?.(name, false);
1697
1870
  await this.ctx.startPersistedPausedInstance(name, transition);
1698
1871
  }
1699
1872
  catch (err) {
1700
1873
  this.clearActivitySeedNow(name);
1701
1874
  writePausedMarker(instanceDir, pausedAt, reason);
1875
+ if (isNativePaneInstance(this.ctx.fleetConfig?.instances[name]))
1876
+ this.ctx.setNativePanePaused?.(name, true);
1702
1877
  throw err;
1703
1878
  }
1704
1879
  }
1705
1880
  this.noteWokeForWork(name);
1706
- this.ctx.startStatuslineWatcher(name);
1881
+ if (!isNativePaneInstance(this.ctx.fleetConfig?.instances[name]))
1882
+ this.ctx.startStatuslineWatcher(name);
1707
1883
  }
1708
1884
  /**
1709
1885
  * Ownership boundary for a daemon that is not yet registered: if start()
@@ -1737,8 +1913,8 @@ export class InstanceLifecycle {
1737
1913
  this.invalidate(name);
1738
1914
  return this.runTransition(name, () => this.stopInTransition(name), transition);
1739
1915
  }
1740
- async stopInTransition(name) {
1741
- this.invalidate(name);
1916
+ async stopInTransition(name, nativeOwner) {
1917
+ this.invalidate(name, nativeOwner);
1742
1918
  this.wokeAt.delete(name);
1743
1919
  this.ctx.setTopicIcon(name, "remove");
1744
1920
  // #2897: a stopped instance holds no dialog — its "needs a human" notices are out of date.
@@ -1763,11 +1939,20 @@ export class InstanceLifecycle {
1763
1939
  if (!drained)
1764
1940
  this.ctx.logger.warn({ name }, "Pane write did not drain before durable reconciliation capture; relying on the Enter write-ahead fence");
1765
1941
  this.ctx.deliveryOutbox.markTargetReconciliationPending(name, daemon.bootId);
1766
- captured = await capturePendingTargetReconciliation(this.ctx.deliveryOutbox, name, this.ctx.getInstanceDir(name), getTmuxSession(), this.ctx.logger);
1942
+ if (daemon.usesNativePane?.()) {
1943
+ const reconciled = await daemon.reconcileNativeDeliveries(this.ctx.deliveryOutbox, () => this.daemons.get(name) === daemon);
1944
+ if (!reconciled)
1945
+ this.ctx.logger.warn({ name }, "Native stop could not settle every old delivery; retained attempts will not be replayed");
1946
+ }
1947
+ if (!daemon.usesNativePane?.())
1948
+ captured = await capturePendingTargetReconciliation(this.ctx.deliveryOutbox, name, this.ctx.getInstanceDir(name), getTmuxSession(), this.ctx.logger);
1767
1949
  }
1768
1950
  let stopped = false;
1769
1951
  try {
1770
- await daemon.stop();
1952
+ if (daemon.usesNativePane?.())
1953
+ await daemon.stop({ nativePane: "close" });
1954
+ else
1955
+ await daemon.stop();
1771
1956
  stopped = true;
1772
1957
  }
1773
1958
  finally {
@@ -1797,6 +1982,11 @@ export class InstanceLifecycle {
1797
1982
  if (this.daemons.get(name) === daemon)
1798
1983
  this.daemons.delete(name);
1799
1984
  }
1985
+ else if (isNativePaneInstance(this.ctx.fleetConfig?.instances[name])) {
1986
+ if (!this.ctx.closeRetainedNativePane)
1987
+ throw Error("Retained native pane owner is unavailable; no local PID was signalled");
1988
+ await this.ctx.closeRetainedNativePane(name);
1989
+ }
1800
1990
  else {
1801
1991
  const instanceDir = this.ctx.getInstanceDir(name);
1802
1992
  const pidPath = join(instanceDir, "daemon.pid");
@@ -1983,6 +2173,27 @@ export class InstanceLifecycle {
1983
2173
  async handleCreate(args, respond, adapterId, execution) {
1984
2174
  const check = () => execution?.assert();
1985
2175
  check();
2176
+ if (args.runtime !== undefined && args.runtime !== "wsl" && args.runtime !== "windows") {
2177
+ respond(null, "Unknown instance runtime");
2178
+ return;
2179
+ }
2180
+ // M-2239 P1-1: a Windows agent is created only from the admin web dashboard (native-instance-api.ts), never through
2181
+ // create_instance, deploy_template or any other path that reaches here — an agent holding create_instance could
2182
+ // otherwise start one on the Windows host, in any drive path, with write access.
2183
+ if (args.runtime === "windows") {
2184
+ respond(null, WINDOWS_CREATE_REFUSED);
2185
+ return;
2186
+ }
2187
+ // #3021 review: on a fleet with no chat connection a new agent never becomes a second General (it would inherit
2188
+ // defaults.general_topic: true while the fleet has one).
2189
+ if (this.ctx.fleetConfig && addsSecondGeneral(this.ctx.fleetConfig)) {
2190
+ respond(null, "one_general: this fleet already has its General, and a new agent would inherit defaults.general_topic: true — set it to false in the defaults first");
2191
+ return;
2192
+ }
2193
+ if (args.sandbox !== undefined) {
2194
+ respond(null, "sandbox is supported only by the Windows runtime");
2195
+ return;
2196
+ }
1986
2197
  const rawDirectory = args.directory;
1987
2198
  const directory = rawDirectory ? rawDirectory.replace(/^~/, process.env.HOME || "~") : undefined;
1988
2199
  const topicName = args.topic_name || (directory ? basename(directory) : undefined);
@@ -2273,6 +2484,11 @@ export class InstanceLifecycle {
2273
2484
  createdConfig = instanceConfig;
2274
2485
  const beforeCreate = structuredClone(this.ctx.fleetConfig);
2275
2486
  const persist = () => {
2487
+ // #3021 review r2: asked again at the commit, after every await — two creates that both saw no General at entry
2488
+ // (defaults.general_topic: true) must not both become one.
2489
+ if (addsSecondGeneral(this.ctx.fleetConfig, { general_topic: instanceConfig.general_topic })) {
2490
+ throw new Error("one_general: this fleet already has its General, and a new agent would inherit defaults.general_topic: true — set it to false in the defaults first");
2491
+ }
2276
2492
  this.ctx.fleetConfig.instances[newInstanceName] = instanceConfig;
2277
2493
  if (createdTopicId != null)
2278
2494
  this.ctx.routing.register(createdTopicId, { kind: "instance", name: newInstanceName });