@songsid/agend 2.3.4-beta.5 → 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 +18 -12
  8. package/dist/backend/claude-code.js +225 -136
  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 +760 -240
  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,6 @@
1
+ import { NativePaneRecoveryError } from "./native-pane-recovery.js";
2
+ import { NativePaneCrashBudget } from "./native-pane-crash-budget.js";
3
+ import { localWindowsCliEnabled, remoteRuntimeEnabled } from "./runtime-switches.js";
1
4
  import { settingsUndo, undoSettingsPaths } from "./settings-transaction.js";
2
5
  import { measureSyncWork } from "./sync-work-attribution.js";
3
6
  import { REMOVED_BACKENDS, isRemovedBackend, removedBackendMessage } from "./backend/removed.js";
@@ -25,6 +28,7 @@ import { BACKEND_AUTH_CHECKS, checkAuthStatus, LOGIN_FLOWS } from "./login-flows
25
28
  import { CHAT_PROFILE_LOGIN_BACKENDS, credentialProfileEnv, credentialProfileLoginCommand } from "./backend/credential-profile.js";
26
29
  import { fetchClaudeUsage, fetchCodexUsage } from "./usage/providers.js";
27
30
  import { assertExplicitInstanceRemoval, authorizeExplicitInstanceRemoval } from "./instance-removal.js";
31
+ import { WINDOWS_CREATE_REFUSED, isNativePaneInstance } from "./native-instance-registry.js";
28
32
  import { capturePendingTargetReconciliation, finishTargetReconciliation, reconcileTargetBeforeStart, RECONCILIATION_WRITE_DRAIN_TIMEOUT_MS, retireCapturedTargetWindows, } from "./delivery-reconciliation.js";
29
33
  import { noPlaceMarkers, placeRef } from "./topic-link.js";
30
34
  export { isFleetStartCommandLine } from "./fleet-lock.js";
@@ -285,6 +289,7 @@ export class InstanceLifecycle {
285
289
  activeTransition = new Map();
286
290
  /** Bumped synchronously by stop/restart: a start begun under an older epoch must not publish. */
287
291
  lifecycleEpochs = new Map();
292
+ nativeExitOwners = new Map();
288
293
  /** Single wake flight per instance: concurrent wakes share it. */
289
294
  wakeFlights = new Map();
290
295
  /** Starts that begin their idle window now instead of at the persisted last inbound. */
@@ -682,7 +687,108 @@ export class InstanceLifecycle {
682
687
  this.ctx.notifyInstanceTopic(name, t("delivery.cancelled_unsent", messages.length, name));
683
688
  }, this.ctx.logger, `daemon.human_messages_cancelled[${name}]`));
684
689
  }
690
+ nativeRecoveryPending(name) { return this.nativeExitOwners.has(name); }
691
+ cancelNativeRecovery(name, owner) {
692
+ for (const [target, run] of this.nativeExitOwners) {
693
+ if (name !== undefined && target !== name || owner !== undefined && run.token !== owner)
694
+ continue;
695
+ clearTimeout(run.timer);
696
+ this.nativeExitOwners.delete(target);
697
+ }
698
+ }
699
+ resetNativeCrashBudget(name) {
700
+ this.cancelNativeRecovery(name);
701
+ const pin = this.ctx.nativePanePinnedSession?.(name);
702
+ if (pin) {
703
+ ensureInstanceDir(this.ctx.getInstanceDir(name));
704
+ new NativePaneCrashBudget(join(this.ctx.getInstanceDir(name), "native-crash-state.json")).reset(pin);
705
+ }
706
+ }
707
+ async nativePaneExited(name, daemon, data) {
708
+ const config = this.ctx.fleetConfig?.instances[name], epoch = this.epochOf(name), pin = data.snapshot.sessionId, profilePin = this.ctx.nativePanePinnedSession?.(name) ?? undefined;
709
+ if (!isNativePaneInstance(config) || data.name !== name || this.daemons.get(name) !== daemon || !daemon.nativePaneExitCurrent(data.snapshot, data.owner)
710
+ || pin !== undefined && profilePin !== undefined && profilePin !== pin || this.nativeExitOwners.has(name) || this.ctx.isPlannedRestart())
711
+ return;
712
+ const token = Symbol(name), run = { token, daemon, timer: undefined };
713
+ this.nativeExitOwners.set(name, run);
714
+ const current = () => this.nativeExitOwners.get(name) === run && this.daemons.get(name) === daemon && this.epochOf(name) === epoch
715
+ && 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()
716
+ && (config.machine_id ? remoteRuntimeEnabled() : localWindowsCliEnabled());
717
+ daemon.fenceDeliveryWritesForStop();
718
+ try {
719
+ const decision = pin && profilePin ? new NativePaneCrashBudget(join(this.ctx.getInstanceDir(name), "native-crash-state.json")).observe(pin, data.snapshot.exitCode, config.restart_policy) : null;
720
+ await this.runTransition(name, async () => {
721
+ if (!current())
722
+ return;
723
+ if (!await daemon.waitForDeliveryWritesToDrain(RECONCILIATION_WRITE_DRAIN_TIMEOUT_MS) || !current())
724
+ throw Error("Native input writes have not drained");
725
+ if (this.ctx.deliveryOutbox) {
726
+ this.ctx.deliveryOutbox.markTargetReconciliationPending(name, daemon.bootId);
727
+ if (!await daemon.reconcileNativeDeliveries(this.ctx.deliveryOutbox, current) || !current())
728
+ throw Error("Native delivery reconciliation is unconfirmed");
729
+ }
730
+ // Close finalizes the exact positive exit; no PID lookup or signal exists on this path.
731
+ await daemon.stop({ nativePane: "close" });
732
+ if (!current())
733
+ return;
734
+ this.ctx.eventLog?.insert(name, "native_pane_exited", { exitCode: data.snapshot.exitCode ?? null, sessionId: pin, decision: decision?.kind ?? "no-conversation", count: decision?.count ?? 0 });
735
+ if (!decision) {
736
+ this.ctx.holdNativePaneRecovery?.(name, new NativePaneRecoveryError({ kind: "session-unconfirmed", launchId: data.snapshot.launchId, sessionId: this.ctx.nativePanePinnedSession?.(name) ?? undefined, ...(config.backend === "codex" ? { provider: "codex" } : {}) }));
737
+ this.ctx.setTopicIcon(name, "red");
738
+ return;
739
+ }
740
+ if (decision.kind !== "retry") {
741
+ if (decision.kind === "normal")
742
+ await this.ctx.notifyNormalExit(name);
743
+ else
744
+ this.notifyIncident(name, decision.kind === "loop" ? "crash_loop" : "supervision_ended", t("inst.respawn_paused", name));
745
+ this.ctx.setTopicIcon(name, "red");
746
+ return;
747
+ }
748
+ const timer = setTimeout(() => {
749
+ run.timer = undefined;
750
+ void this.runTransition(name, async () => {
751
+ if (!current())
752
+ return;
753
+ await this.stopInTransition(name, token);
754
+ // Our own stop advances once. An operator's concurrent stop/config change prevents publication.
755
+ if (this.nativeExitOwners.get(name) !== run || this.epochOf(name) !== epoch + 1 || this.daemons.has(name)
756
+ || this.ctx.fleetConfig?.instances[name] !== config || this.ctx.nativePanePinnedSession?.(name) !== pin || this.isPaused(name) || this.ctx.isPlannedRestart()
757
+ || !(config.machine_id ? remoteRuntimeEnabled() : localWindowsCliEnabled()))
758
+ return;
759
+ await this.startInTransition(name, config, true, { kind: "fleet-topic", backend: config.backend ?? "codex", model: config.model || "default" });
760
+ if (this.nativeExitOwners.get(name) !== run || this.epochOf(name) !== epoch + 1 || this.ctx.isPlannedRestart())
761
+ return;
762
+ await this.ctx.connectIpcToInstance(name);
763
+ }).catch(error => { if (this.nativeExitOwners.get(name) === run && !this.ctx.isPlannedRestart()) {
764
+ if (this.ctx.holdNativePaneRecovery?.(name, error))
765
+ return;
766
+ this.ctx.logger.error({ name, err: error }, "Native conversation resume failed; its pin was retained");
767
+ this.notifyIncident(name, "supervision_ended", t("inst.respawn_paused", name));
768
+ } })
769
+ .finally(() => { this.cancelNativeRecovery(name, token); this.ctx.onNativePaneRecoverySettled?.(name); });
770
+ }, decision.delay);
771
+ timer.unref?.();
772
+ run.timer = timer;
773
+ });
774
+ if (!run.timer)
775
+ this.cancelNativeRecovery(name, token);
776
+ }
777
+ catch (error) {
778
+ if (this.nativeExitOwners.get(name) === run && !this.ctx.isPlannedRestart()) {
779
+ this.ctx.logger.error({ name, err: error }, "Native crash recovery held without a fresh fallback");
780
+ this.notifyIncident(name, "supervision_ended", t("inst.respawn_paused", name));
781
+ }
782
+ this.cancelNativeRecovery(name, token);
783
+ }
784
+ finally {
785
+ if (!run.timer)
786
+ this.ctx.onNativePaneRecoverySettled?.(name);
787
+ }
788
+ }
685
789
  attachIncidentHandlers(name, daemon) {
790
+ if (daemon.usesNativePane?.())
791
+ daemon.on("native_pane_exited", safeHandler((data) => this.nativePaneExited(name, daemon, data), this.ctx.logger, `daemon.native_pane_exited[${name}]`));
686
792
  daemon.on("capacity_nudge_cancelled", safeHandler((data) => {
687
793
  const reason = data.reason === "the user cancelled" ? t("inst.codex_retry_cancel_reason_user")
688
794
  : 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 +1087,22 @@ export class InstanceLifecycle {
981
1087
  // labels, summaries and every exhaustive match for one picker's text.)
982
1088
  // #1691: a prompt no table knows — what it asks (one sanitised, bounded line from the daemon, never the pane), and
983
1089
  // that AgEnD will not press anything.
1090
+ // S-35: a Windows agent's instance name is windows-<uuid>; people know it by its display name.
1091
+ const label = this.ctx.fleetConfig?.instances[name]?.display_name?.trim() || name;
1092
+ const place = placeRef({ kind: "instance", name }, label);
984
1093
  if (data.unrecognised) {
985
1094
  // #1990: the question is a pane line — it may not name a place of its own.
986
1095
  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));
1096
+ this.notifyParked(name, "dialog_parked", t("inst.unrecognised_prompt", label, asked), t("fleet.unrecognised_prompt", place, asked));
988
1097
  return;
989
1098
  }
990
1099
  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
1100
+ const text = category ? t("inst.interaction_parked", label, category) : data.holdOnly
992
1101
  // #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") : "");
1102
+ ? t("inst.dialog_parked_hold", label, data.description) + (data.advice === "claude-resume" ? " " + t("inst.dialog_parked_hold_claude_resume") : "")
1103
+ : t("inst.dialog_parked_stuck", label, data.description) + (data.advice === "claude-resume" ? " " + t("inst.dialog_parked_stuck_claude_resume") : "");
995
1104
  // #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));
1105
+ this.notifyParked(name, "dialog_parked", text, category ? t("fleet.interaction_parked", place, category) : t("fleet.dialog_parked", place, data.description));
997
1106
  }, this.ctx.logger, `daemon.dialog_parked[${name}]`));
998
1107
  // #2897: the held dialog is gone (closed, answered, auto-answered) — its notices go with it.
999
1108
  daemon.on("dialog_unparked", safeHandler(() => this.ctx.resolveParkedNotice?.(name), this.ctx.logger, `daemon.dialog_unparked[${name}]`));
@@ -1030,6 +1139,21 @@ export class InstanceLifecycle {
1030
1139
  daemon.on("pty_error", safeHandler(async (data) => {
1031
1140
  this.ctx.eventLog?.insert(name, "pty_error", { type: data.type, action: data.action });
1032
1141
  this.ctx.logger.warn({ name, errorType: data.type, action: data.action }, `PTY error: ${data.message}`);
1142
+ if (daemon.usesNativePane?.()) {
1143
+ // Native credentials and quotas belong to its runner. Never probe a Linux login or group its peers with it.
1144
+ if (this.daemons.get(name) !== daemon)
1145
+ return;
1146
+ this.notifyIncident(name, data.type, data.notice ?? data.message);
1147
+ if (data.action === "pause") {
1148
+ if (data.type === "auth_error") {
1149
+ daemon.confirmAuthFailure?.();
1150
+ daemon.requestPauseWhenIdle({ reconfirmAuth: true, reason: "auth" });
1151
+ }
1152
+ else
1153
+ daemon.requestPauseWhenIdle({ reason: "error" });
1154
+ }
1155
+ return;
1156
+ }
1033
1157
  // Antigravity's account-level cap is visible ONLY here: the quota summary
1034
1158
  // API keeps reporting its buckets as barely used while the CLI is blocked
1035
1159
  // (verified live — 0% used and "Individual quota reached" in the same
@@ -1366,7 +1490,10 @@ export class InstanceLifecycle {
1366
1490
  return this.lifecycleEpochs.get(name) ?? 0;
1367
1491
  }
1368
1492
  /** Invalidate every start/wake already running for `name` (they will not publish). */
1369
- invalidate(name) {
1493
+ invalidate(name, nativeOwner) {
1494
+ const native = this.nativeExitOwners.get(name);
1495
+ if (native && native.token !== nativeOwner)
1496
+ this.cancelNativeRecovery(name);
1370
1497
  this.lifecycleEpochs.set(name, this.epochOf(name) + 1);
1371
1498
  }
1372
1499
  /** Whether `transition` is `name`'s transition and is the one running now. */
@@ -1440,156 +1567,178 @@ export class InstanceLifecycle {
1440
1567
  this.ctx.logger.info({ name }, "Instance already running, skipping");
1441
1568
  return;
1442
1569
  }
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
1464
- const installation = BACKEND_INSTALLATION_INFO[backendName];
1465
- if (installation && !checkBinaryInstalled(installation.binary)) {
1466
- this.ctx.logger.error({ binary: installation.binary, backend: backendName, instance: name }, `Backend binary "${installation.binary}" not found in PATH`);
1467
- }
1468
- // claude-code refuses --dangerously-skip-permissions as root — fail fast
1469
- // Unless IS_SANDBOX=1 is set (recognized sandbox environment)
1470
- if (process.getuid?.() === 0 && backendName === "claude-code" && !process.env.IS_SANDBOX) {
1471
- const msg = `⚠️ claude-code cannot run with --dangerously-skip-permissions as root. Set IS_SANDBOX=1 or use a non-root user.`;
1472
- this.ctx.logger.error({ name, backend: backendName }, msg);
1473
- this.ctx.notifyInstanceTopic(name, t("inst.root_skip_perms"));
1474
- return;
1475
- }
1476
- const backend = createBackend(backendName, instanceDir);
1477
- const daemon = new Daemon(name, config, instanceDir, topicMode, backend, this.ctx.controlClient ?? undefined, this.ctx.logger, runtimeIdentity ?? {
1478
- kind: "fleet-topic",
1479
- backend: backendName,
1480
- model: config.model ?? "default",
1481
- }, this.ctx.spawnGate, this.ctx.stormWindow, this.ctx.backendOutage);
1482
- daemon.setDeliveryOutboxPort(this.ctx.deliveryOutbox ?? undefined);
1483
- daemon.setWorkLeaseCheck(() => this.hasWorkLease(name));
1484
- if (seedNow)
1485
- daemon.seedActivityNow();
1486
- // Read at each spawn so a fleet.yaml reload is honoured (#984).
1487
- daemon.setPeerWorkingDirectories(backendName === "kiro-cli"
1488
- ? this.ctx.kiroPeerWorkingDirectories ? () => this.ctx.kiroPeerWorkingDirectories(name) : undefined
1489
- : () => peerWorkingDirectories(this.ctx.fleetConfig, name, backendName));
1490
- daemon.setStatusEmojiAvoidList(() => this.ctx.statusEmojiAvoidList?.(name));
1491
- // Before start: the first spawn builds the command and emits its launch
1492
- // warning synchronously inside Daemon.start, long before
1493
- // attachIncidentHandlers runs (#984).
1494
- this.attachLaunchWarningHandler(name, daemon);
1495
- this.attachContextLostHandler(name, daemon);
1496
- daemon.on("durable_delivery_deferred", (data) => {
1497
- // A daemon without its outbox port cannot update the row directly. The
1498
- // lifecycle owns the same-process store and records a bounded pre-write
1499
- // retry instead of leaving the lane in delivering forever.
1500
- this.ctx.deliveryOutbox?.retryBeforeBegin(data.deliveryId, data.targetBootId, data.attemptNo, data.reason, 5_000);
1501
- });
1502
- daemon.on("mcp_tool_response_written", (data) => {
1503
- if (!data.operationId || data.daemonBootId !== daemon.bootId)
1570
+ const native = isNativePaneInstance(config) ? await this.ctx.nativePaneFor?.(name, config, check) : undefined;
1571
+ let nativePublished = false;
1572
+ try {
1573
+ check();
1574
+ if (isNativePaneInstance(config) && !native)
1575
+ throw Error("Native pane ownership is unavailable; no local CLI was started");
1576
+ if (native)
1577
+ config = { ...config, agent_mode: "mcp" };
1578
+ if (!native && !existsSync(config.working_directory)) {
1579
+ this.ctx.logger.info({ name, working_directory: config.working_directory }, "Working directory does not exist — creating it");
1580
+ mkdirSync(config.working_directory, { recursive: true });
1581
+ }
1582
+ const instanceDir = this.ctx.getInstanceDir(name);
1583
+ ensureInstanceDir(instanceDir);
1584
+ // Daemon.start consumes the recovery marker after reading it. A start
1585
+ // that fails before reaching the daemon must leave that intent intact.
1586
+ const { Daemon } = await import("./daemon.js");
1587
+ const { createBackend } = await import("./backend/factory.js");
1588
+ check();
1589
+ const backendName = config.backend ?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
1590
+ // A removed backend (#1280): this instance refuses to start, says why and what to use instead, and the rest of
1591
+ // the fleet is unaffected. Never a fallback to another backend.
1592
+ if (isRemovedBackend(backendName)) {
1593
+ const message = removedBackendMessage(backendName, name);
1594
+ this.ctx.logger.error({ name, backend: backendName }, `Not starting: ${message}`);
1595
+ this.ctx.notifyInstanceTopic(name, t("inst.backend_removed", name, backendName, REMOVED_BACKENDS[backendName].replacement));
1504
1596
  return;
1505
- try {
1506
- this.ctx.deliveryOutbox?.markResponseDelivered(name, data.operationId);
1507
1597
  }
1508
- catch (err) {
1509
- this.ctx.logger.error({ name, operationId: data.operationId, err }, "Could not persist MCP response-delivered marker");
1598
+ // Verify backend binary is in PATH before spawning
1599
+ const installation = BACKEND_INSTALLATION_INFO[backendName];
1600
+ if (!native && installation && !checkBinaryInstalled(installation.binary)) {
1601
+ this.ctx.logger.error({ binary: installation.binary, backend: backendName, instance: name }, `Backend binary "${installation.binary}" not found in PATH`);
1602
+ }
1603
+ // claude-code refuses --dangerously-skip-permissions as root — fail fast
1604
+ // Unless IS_SANDBOX=1 is set (recognized sandbox environment)
1605
+ if (!native && process.getuid?.() === 0 && backendName === "claude-code" && !process.env.IS_SANDBOX) {
1606
+ const msg = `⚠️ claude-code cannot run with --dangerously-skip-permissions as root. Set IS_SANDBOX=1 or use a non-root user.`;
1607
+ this.ctx.logger.error({ name, backend: backendName }, msg);
1608
+ this.ctx.notifyInstanceTopic(name, t("inst.root_skip_perms"));
1609
+ return;
1510
1610
  }
1511
- });
1512
- this.applyCapacityBackoffBaseline(name, backendName, daemon);
1513
- // Catch errors from daemon internals (e.g. IPC server) to prevent crashing the fleet process
1514
- daemon.on("error", (err) => {
1515
- this.ctx.logger.error({ err, name }, "Daemon emitted error — instance isolated");
1516
- });
1517
- if (this.ctx.deliveryOutbox) {
1518
- // Fence older-generation submissions and reconcile them before Daemon.start
1519
- // reaches Strategy A's unconditional old-window kill.
1520
- const recovered = this.ctx.deliveryOutbox.recoverTargetGeneration(name, daemon.bootId);
1521
- if (recovered.queued || recovered.reconciliationPending) {
1522
- this.ctx.logger.warn({ name, ...recovered }, "Preparing durable outbox rows before target generation start");
1523
- }
1524
- const reconciled = await reconcileTargetBeforeStart(this.ctx.deliveryOutbox, name, instanceDir, this.ctx.logger);
1525
- if (reconciled.uncertain > 0) {
1526
- this.ctx.logger.warn({ name, ...reconciled }, "Some durable submissions need operator reconciliation");
1527
- }
1528
- if (!reconciled.safeToStart) {
1529
- const message = t("delivery.restart_blocked", name);
1530
- this.ctx.logger.error({ name, ...reconciled }, message);
1531
- this.ctx.notifyFleetError?.(message);
1532
- await daemon.abortStartup().catch(err => this.ctx.logger.warn({ err, name }, "Failed to dispose daemon after reconciliation could not verify old CLI exit"));
1533
- throw new Error(message);
1534
- }
1535
- }
1536
- if (this.epochOf(name) !== epoch || execution && !execution.current()) {
1537
- // A stop/restart arrived while this start was reconciling: do not spawn.
1538
- await daemon.abortStartup().catch(() => { });
1539
- throw new SupersededStartError(name);
1540
- }
1541
- // The startup scan can raise an incident (sign-in screen, fatal modal)
1542
- // before the handlers below exist and before this daemon is registered;
1543
- // hold it and deliver it once both are true.
1544
- daemon.holdStartupIncidents?.();
1545
- await InstanceLifecycle.startOrDispose(daemon, name, this.ctx.logger, execution ? check : undefined);
1546
- // Publication fence (Phase 2a): the spawn cannot be cancelled, so a stop or
1547
- // restart requested meanwhile has been waiting behind this transition. A
1548
- // superseded start must not register, signal ready, or keep its window.
1549
- if (this.epochOf(name) !== epoch || execution && !execution.current()) {
1550
- await daemon.abortStartup().catch(err => this.ctx.logger.warn({ err, name }, "Failed to dispose a superseded start"));
1551
- throw new SupersededStartError(name);
1552
- }
1553
- this.capacityBackoffBaselines.delete(name);
1554
- this.daemons.set(name, daemon);
1555
- published?.(daemon); // Exact publication, synchronously after the ownership fence.
1556
- if (seedNow)
1557
- this.noteWokeForWork(name);
1558
- this.ctx.onDaemonReady?.(name, daemon.bootId);
1559
- daemon.on("auto_pause_requested", safeHandler(async (data) => {
1560
- await this.pause(name, data?.reason ?? "idle");
1561
- }, this.ctx.logger, `autoPause[${name}]`));
1562
- daemon.on("auto_paused", (data) => {
1563
- this.ctx.eventLog?.insert(name, "instance_paused", { reason: "idle", paused_at: data.pausedAt });
1564
- this.ctx.logger.info({ name, pausedAt: data.pausedAt }, "Instance auto-paused after idle timeout");
1565
- this.ctx.setTopicIcon(name, "remove");
1566
- this.ctx.onAttentionChanged?.(name);
1567
- });
1568
- daemon.on("auto_woke", () => {
1569
- this.ctx.onAttentionChanged?.(name);
1570
- this.ctx.eventLog?.insert(name, "instance_resumed", { reason: "message" });
1571
- this.ctx.logger.info({ name }, "Instance auto-woke for delivery");
1611
+ const backend = native?.backend ?? createBackend(backendName, instanceDir);
1612
+ const daemon = new Daemon(name, config, instanceDir, topicMode, backend, this.ctx.controlClient ?? undefined, this.ctx.logger, runtimeIdentity ?? {
1613
+ kind: "fleet-topic",
1614
+ backend: backendName,
1615
+ model: config.model ?? "default",
1616
+ }, this.ctx.spawnGate, this.ctx.stormWindow, this.ctx.backendOutage);
1617
+ if (native) {
1618
+ daemon.setNativePaneLifecycle(native);
1619
+ native.bindDaemon?.(daemon);
1620
+ }
1621
+ daemon.setDeliveryOutboxPort(this.ctx.deliveryOutbox ?? undefined);
1622
+ daemon.setWorkLeaseCheck(() => this.hasWorkLease(name));
1623
+ if (seedNow)
1624
+ daemon.seedActivityNow();
1625
+ // Read at each spawn so a fleet.yaml reload is honoured (#984).
1626
+ daemon.setPeerWorkingDirectories(backendName === "kiro-cli"
1627
+ ? this.ctx.kiroPeerWorkingDirectories ? () => this.ctx.kiroPeerWorkingDirectories(name) : undefined
1628
+ : () => peerWorkingDirectories(this.ctx.fleetConfig, name, backendName));
1629
+ daemon.setStatusEmojiAvoidList(() => this.ctx.statusEmojiAvoidList?.(name));
1630
+ // Before start: the first spawn builds the command and emits its launch
1631
+ // warning synchronously inside Daemon.start, long before
1632
+ // attachIncidentHandlers runs (#984).
1633
+ this.attachLaunchWarningHandler(name, daemon);
1634
+ this.attachContextLostHandler(name, daemon);
1635
+ daemon.on("durable_delivery_deferred", (data) => {
1636
+ // A daemon without its outbox port cannot update the row directly. The
1637
+ // lifecycle owns the same-process store and records a bounded pre-write
1638
+ // retry instead of leaving the lane in delivering forever.
1639
+ this.ctx.deliveryOutbox?.retryBeforeBegin(data.deliveryId, data.targetBootId, data.attemptNo, data.reason, 5_000);
1640
+ });
1641
+ daemon.on("mcp_tool_response_written", (data) => {
1642
+ if (!data.operationId || data.daemonBootId !== daemon.bootId)
1643
+ return;
1644
+ try {
1645
+ this.ctx.deliveryOutbox?.markResponseDelivered(name, data.operationId);
1646
+ }
1647
+ catch (err) {
1648
+ this.ctx.logger.error({ name, operationId: data.operationId, err }, "Could not persist MCP response-delivered marker");
1649
+ }
1650
+ });
1651
+ this.applyCapacityBackoffBaseline(name, backendName, daemon);
1652
+ // Catch errors from daemon internals (e.g. IPC server) to prevent crashing the fleet process
1653
+ daemon.on("error", (err) => {
1654
+ this.ctx.logger.error({ err, name }, "Daemon emitted error — instance isolated");
1655
+ });
1656
+ if (this.ctx.deliveryOutbox) {
1657
+ // Fence older-generation submissions and reconcile them before Daemon.start
1658
+ // reaches Strategy A's unconditional old-window kill.
1659
+ const recovered = this.ctx.deliveryOutbox.recoverTargetGeneration(name, daemon.bootId);
1660
+ if (recovered.queued || recovered.reconciliationPending) {
1661
+ this.ctx.logger.warn({ name, ...recovered }, "Preparing durable outbox rows before target generation start");
1662
+ }
1663
+ const reconciled = native
1664
+ ? { uncertain: 0, safeToStart: await this.ctx.reconcileNativePaneBeforeStart?.(name, daemon) === true }
1665
+ : await reconcileTargetBeforeStart(this.ctx.deliveryOutbox, name, instanceDir, this.ctx.logger);
1666
+ if (reconciled.uncertain > 0) {
1667
+ this.ctx.logger.warn({ name, ...reconciled }, "Some durable submissions need operator reconciliation");
1668
+ }
1669
+ if (!reconciled.safeToStart) {
1670
+ const message = t("delivery.restart_blocked", name);
1671
+ this.ctx.logger.error({ name, ...reconciled }, message);
1672
+ this.ctx.notifyFleetError?.(message);
1673
+ await daemon.abortStartup().catch(err => this.ctx.logger.warn({ err, name }, "Failed to dispose daemon after reconciliation could not verify old CLI exit"));
1674
+ throw new Error(message);
1675
+ }
1676
+ }
1677
+ if (this.epochOf(name) !== epoch || execution && !execution.current()) {
1678
+ // A stop/restart arrived while this start was reconciling: do not spawn.
1679
+ await daemon.abortStartup().catch(() => { });
1680
+ throw new SupersededStartError(name);
1681
+ }
1682
+ // The startup scan can raise an incident (sign-in screen, fatal modal)
1683
+ // before the handlers below exist and before this daemon is registered;
1684
+ // hold it and deliver it once both are true.
1685
+ daemon.holdStartupIncidents?.();
1686
+ await InstanceLifecycle.startOrDispose(daemon, name, this.ctx.logger, execution || native ? check : undefined);
1687
+ // Publication fence (Phase 2a): the spawn cannot be cancelled, so a stop or
1688
+ // restart requested meanwhile has been waiting behind this transition. A
1689
+ // superseded start must not register, signal ready, or keep its window.
1690
+ if (this.epochOf(name) !== epoch || execution && !execution.current()) {
1691
+ await daemon.abortStartup().catch(err => this.ctx.logger.warn({ err, name }, "Failed to dispose a superseded start"));
1692
+ throw new SupersededStartError(name);
1693
+ }
1694
+ this.capacityBackoffBaselines.delete(name);
1695
+ this.daemons.set(name, daemon);
1696
+ nativePublished = !!native;
1697
+ published?.(daemon); // Exact publication, synchronously after the ownership fence.
1698
+ if (seedNow)
1699
+ this.noteWokeForWork(name);
1700
+ this.ctx.onDaemonReady?.(name, daemon.bootId);
1701
+ daemon.on("auto_pause_requested", safeHandler(async (data) => {
1702
+ await this.pause(name, data?.reason ?? "idle");
1703
+ }, this.ctx.logger, `autoPause[${name}]`));
1704
+ daemon.on("auto_paused", (data) => {
1705
+ this.ctx.eventLog?.insert(name, "instance_paused", { reason: "idle", paused_at: data.pausedAt });
1706
+ this.ctx.logger.info({ name, pausedAt: data.pausedAt }, "Instance auto-paused after idle timeout");
1707
+ this.ctx.setTopicIcon(name, "remove");
1708
+ this.ctx.onAttentionChanged?.(name);
1709
+ });
1710
+ daemon.on("auto_woke", () => {
1711
+ this.ctx.onAttentionChanged?.(name);
1712
+ this.ctx.eventLog?.insert(name, "instance_resumed", { reason: "message" });
1713
+ this.ctx.logger.info({ name }, "Instance auto-woke for delivery");
1714
+ this.ctx.setTopicIcon(name, "green");
1715
+ this.ctx.touchActivity(name);
1716
+ });
1717
+ this.attachIncidentHandlers(name, daemon);
1718
+ // #2897: a new daemon starts with no dialog held; a notice the last one left (a crash, a respawn) is out of date.
1719
+ this.ctx.resolveParkedNotice?.(name);
1720
+ daemon.on("pty_recovered", safeHandler((data) => {
1721
+ const mins = Math.floor(data.downtime_s / 60);
1722
+ const secs = data.downtime_s % 60;
1723
+ const duration = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
1724
+ this.ctx.eventLog?.insert(name, "pty_recovered", { downtime_s: data.downtime_s });
1725
+ this.ctx.logger.info({ name, downtime_s: data.downtime_s }, "PTY error recovered");
1726
+ this.ctx.notifyInstanceTopic(name, t("inst.recovered", name, duration));
1727
+ this.ctx.webhookEmit("pty_recovered", name, { downtime_s: data.downtime_s });
1728
+ }, this.ctx.logger, `daemon.pty_recovered[${name}]`));
1729
+ this.attachDeliveryStatusHandlers(name, daemon);
1572
1730
  this.ctx.setTopicIcon(name, "green");
1573
1731
  this.ctx.touchActivity(name);
1574
- });
1575
- this.attachIncidentHandlers(name, daemon);
1576
- // #2897: a new daemon starts with no dialog held; a notice the last one left (a crash, a respawn) is out of date.
1577
- this.ctx.resolveParkedNotice?.(name);
1578
- daemon.on("pty_recovered", safeHandler((data) => {
1579
- const mins = Math.floor(data.downtime_s / 60);
1580
- const secs = data.downtime_s % 60;
1581
- const duration = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
1582
- this.ctx.eventLog?.insert(name, "pty_recovered", { downtime_s: data.downtime_s });
1583
- this.ctx.logger.info({ name, downtime_s: data.downtime_s }, "PTY error recovered");
1584
- this.ctx.notifyInstanceTopic(name, t("inst.recovered", name, duration));
1585
- this.ctx.webhookEmit("pty_recovered", name, { downtime_s: data.downtime_s });
1586
- }, this.ctx.logger, `daemon.pty_recovered[${name}]`));
1587
- this.attachDeliveryStatusHandlers(name, daemon);
1588
- this.ctx.setTopicIcon(name, "green");
1589
- this.ctx.touchActivity(name);
1590
- // Last: handlers attached and daemon registered, so a held startup incident
1591
- // (pause, notify) finds both.
1592
- daemon.releaseStartupIncidents?.();
1732
+ // Last: handlers attached and daemon registered, so a held startup incident
1733
+ // (pause, notify) finds both.
1734
+ daemon.releaseStartupIncidents?.();
1735
+ }
1736
+ catch (error) {
1737
+ // The factory registered helper authority before acquire(). A superseded pre-window start must release it too.
1738
+ if (native && !nativePublished)
1739
+ await native.release("detach").catch(err => this.ctx.logger.warn({ name, err }, "Prepared native authority could not detach"));
1740
+ throw error;
1741
+ }
1593
1742
  }
1594
1743
  isPaused(name) {
1595
1744
  return this.daemons.get(name)?.isPaused ?? hasPausedMarker(this.ctx.getInstanceDir(name));
@@ -1598,11 +1747,30 @@ export class InstanceLifecycle {
1598
1747
  return this.daemons.get(name)?.lastPausedAt ?? readPausedAt(this.ctx.getInstanceDir(name));
1599
1748
  }
1600
1749
  async pause(name, reason = "operator", admission) {
1750
+ this.cancelNativeRecovery(name);
1601
1751
  // Final backstop shared by slash, MCP, Settings, warm-cap, and any future
1602
1752
  // caller. General is the coordinator and must remain resident to route work.
1603
1753
  if (isGeneralInstance(this.ctx.fleetConfig, name)) {
1604
1754
  throw new Error(GENERAL_PAUSE_ERROR);
1605
1755
  }
1756
+ const nativeDaemon = this.daemons.get(name);
1757
+ if (nativeDaemon?.usesNativePane?.()) {
1758
+ if (admission && !admission.current())
1759
+ return;
1760
+ await this.runTransition(name, async () => {
1761
+ if (this.daemons.get(name) !== nativeDaemon || admission && !admission.current())
1762
+ return;
1763
+ if (!nativeDaemon.nativePaneHasExited() && (!await nativeDaemon.nativePauseReady(admission) || this.daemons.get(name) !== nativeDaemon || !nativeDaemon.nativePauseStillReady()))
1764
+ return;
1765
+ if (this.daemons.get(name) !== nativeDaemon || admission && !admission.current())
1766
+ return;
1767
+ await this.stopInTransition(name);
1768
+ this.ctx.setNativePanePaused?.(name, true);
1769
+ writePausedMarker(this.ctx.getInstanceDir(name), Date.now(), reason);
1770
+ this.ctx.onAttentionChanged?.(name);
1771
+ });
1772
+ return;
1773
+ }
1606
1774
  // B1: cancel any pending capacity backoff — a user-initiated pause should
1607
1775
  // not be undone by a queued restart 30–120s later.
1608
1776
  const pendingBackoff = this.capacityBackoffTimers.get(name);
@@ -1686,16 +1854,21 @@ export class InstanceLifecycle {
1686
1854
  clearPausedMarker(instanceDir);
1687
1855
  this.markActivitySeedNow(name);
1688
1856
  try {
1857
+ if (isNativePaneInstance(this.ctx.fleetConfig?.instances[name]))
1858
+ this.ctx.setNativePanePaused?.(name, false);
1689
1859
  await this.ctx.startPersistedPausedInstance(name, transition);
1690
1860
  }
1691
1861
  catch (err) {
1692
1862
  this.clearActivitySeedNow(name);
1693
1863
  writePausedMarker(instanceDir, pausedAt, reason);
1864
+ if (isNativePaneInstance(this.ctx.fleetConfig?.instances[name]))
1865
+ this.ctx.setNativePanePaused?.(name, true);
1694
1866
  throw err;
1695
1867
  }
1696
1868
  }
1697
1869
  this.noteWokeForWork(name);
1698
- this.ctx.startStatuslineWatcher(name);
1870
+ if (!isNativePaneInstance(this.ctx.fleetConfig?.instances[name]))
1871
+ this.ctx.startStatuslineWatcher(name);
1699
1872
  }
1700
1873
  /**
1701
1874
  * Ownership boundary for a daemon that is not yet registered: if start()
@@ -1729,8 +1902,8 @@ export class InstanceLifecycle {
1729
1902
  this.invalidate(name);
1730
1903
  return this.runTransition(name, () => this.stopInTransition(name), transition);
1731
1904
  }
1732
- async stopInTransition(name) {
1733
- this.invalidate(name);
1905
+ async stopInTransition(name, nativeOwner) {
1906
+ this.invalidate(name, nativeOwner);
1734
1907
  this.wokeAt.delete(name);
1735
1908
  this.ctx.setTopicIcon(name, "remove");
1736
1909
  // #2897: a stopped instance holds no dialog — its "needs a human" notices are out of date.
@@ -1755,11 +1928,20 @@ export class InstanceLifecycle {
1755
1928
  if (!drained)
1756
1929
  this.ctx.logger.warn({ name }, "Pane write did not drain before durable reconciliation capture; relying on the Enter write-ahead fence");
1757
1930
  this.ctx.deliveryOutbox.markTargetReconciliationPending(name, daemon.bootId);
1758
- captured = await capturePendingTargetReconciliation(this.ctx.deliveryOutbox, name, this.ctx.getInstanceDir(name), getTmuxSession(), this.ctx.logger);
1931
+ if (daemon.usesNativePane?.()) {
1932
+ const reconciled = await daemon.reconcileNativeDeliveries(this.ctx.deliveryOutbox, () => this.daemons.get(name) === daemon);
1933
+ if (!reconciled)
1934
+ this.ctx.logger.warn({ name }, "Native stop could not settle every old delivery; retained attempts will not be replayed");
1935
+ }
1936
+ if (!daemon.usesNativePane?.())
1937
+ captured = await capturePendingTargetReconciliation(this.ctx.deliveryOutbox, name, this.ctx.getInstanceDir(name), getTmuxSession(), this.ctx.logger);
1759
1938
  }
1760
1939
  let stopped = false;
1761
1940
  try {
1762
- await daemon.stop();
1941
+ if (daemon.usesNativePane?.())
1942
+ await daemon.stop({ nativePane: "close" });
1943
+ else
1944
+ await daemon.stop();
1763
1945
  stopped = true;
1764
1946
  }
1765
1947
  finally {
@@ -1789,6 +1971,11 @@ export class InstanceLifecycle {
1789
1971
  if (this.daemons.get(name) === daemon)
1790
1972
  this.daemons.delete(name);
1791
1973
  }
1974
+ else if (isNativePaneInstance(this.ctx.fleetConfig?.instances[name])) {
1975
+ if (!this.ctx.closeRetainedNativePane)
1976
+ throw Error("Retained native pane owner is unavailable; no local PID was signalled");
1977
+ await this.ctx.closeRetainedNativePane(name);
1978
+ }
1792
1979
  else {
1793
1980
  const instanceDir = this.ctx.getInstanceDir(name);
1794
1981
  const pidPath = join(instanceDir, "daemon.pid");
@@ -1975,6 +2162,21 @@ export class InstanceLifecycle {
1975
2162
  async handleCreate(args, respond, adapterId, execution) {
1976
2163
  const check = () => execution?.assert();
1977
2164
  check();
2165
+ if (args.runtime !== undefined && args.runtime !== "wsl" && args.runtime !== "windows") {
2166
+ respond(null, "Unknown instance runtime");
2167
+ return;
2168
+ }
2169
+ // M-2239 P1-1: a Windows agent is created only from the admin web dashboard (native-instance-api.ts), never through
2170
+ // create_instance, deploy_template or any other path that reaches here — an agent holding create_instance could
2171
+ // otherwise start one on the Windows host, in any drive path, with write access.
2172
+ if (args.runtime === "windows") {
2173
+ respond(null, WINDOWS_CREATE_REFUSED);
2174
+ return;
2175
+ }
2176
+ if (args.sandbox !== undefined) {
2177
+ respond(null, "sandbox is supported only by the Windows runtime");
2178
+ return;
2179
+ }
1978
2180
  const rawDirectory = args.directory;
1979
2181
  const directory = rawDirectory ? rawDirectory.replace(/^~/, process.env.HOME || "~") : undefined;
1980
2182
  const topicName = args.topic_name || (directory ? basename(directory) : undefined);