@ours.network/fleet 1.2.0-nightly.6 → 1.2.0-nightly.60

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 (389) hide show
  1. package/README.md +287 -65
  2. package/dist/account-origin.d.ts +2 -0
  3. package/dist/account-origin.js +6 -0
  4. package/dist/agent-ours/bridge.js +35 -9
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/file-rpc.d.ts +12 -0
  8. package/dist/agent-ours/file-rpc.js +18 -4
  9. package/dist/agent-ours/harness.js +9 -1
  10. package/dist/agent-ours/mcp-endpoint.d.ts +4 -0
  11. package/dist/agent-ours/mcp-endpoint.js +5 -0
  12. package/dist/agent-ours/runtime.d.ts +9 -1
  13. package/dist/agent-ours/runtime.js +50 -2
  14. package/dist/agent-ours/service.d.ts +21 -1
  15. package/dist/agent-ours/service.js +94 -14
  16. package/dist/application/codex-models.d.ts +15 -0
  17. package/dist/application/codex-models.js +56 -0
  18. package/dist/application/conversation-history.d.ts +27 -0
  19. package/dist/application/conversation-history.js +212 -0
  20. package/dist/application/fleet-query-service.d.ts +1 -1
  21. package/dist/application/fleet-query-service.js +2 -2
  22. package/dist/application/legacy-supervisor-identity.d.ts +7 -0
  23. package/dist/application/legacy-supervisor-identity.js +43 -0
  24. package/dist/application/log-service.js +8 -8
  25. package/dist/application/model-catalog.d.ts +1 -1
  26. package/dist/application/model-catalog.js +5 -2
  27. package/dist/application/preset-provenance.d.ts +19 -0
  28. package/dist/application/preset-provenance.js +59 -0
  29. package/dist/application/role-creation-service.js +3 -2
  30. package/dist/application/role-removal-service.js +9 -1
  31. package/dist/application/role-repository.js +19 -2
  32. package/dist/application/room-layout-definitions.d.ts +45 -0
  33. package/dist/application/room-layout-definitions.js +130 -0
  34. package/dist/application/session-control.d.ts +30 -0
  35. package/dist/application/session-control.js +72 -3
  36. package/dist/application/supervisor-ours-tools.d.ts +159 -0
  37. package/dist/application/supervisor-ours-tools.js +115 -0
  38. package/dist/application/task-layouts.d.ts +91 -0
  39. package/dist/application/task-layouts.js +259 -0
  40. package/dist/application/task-room-service.d.ts +99 -3
  41. package/dist/application/task-room-service.js +285 -32
  42. package/dist/application/types.d.ts +2 -0
  43. package/dist/atomic-file.d.ts +5 -0
  44. package/dist/atomic-file.js +62 -1
  45. package/dist/briefing.d.ts +3 -1
  46. package/dist/briefing.js +70 -30
  47. package/dist/build-info.json +7 -4
  48. package/dist/capabilities.d.ts +6 -1
  49. package/dist/capabilities.js +8 -0
  50. package/dist/cli.js +407 -37
  51. package/dist/client-profile.d.ts +5 -17
  52. package/dist/client-profile.js +4 -127
  53. package/dist/config.d.ts +45 -0
  54. package/dist/config.js +57 -6
  55. package/dist/creation.d.ts +1 -1
  56. package/dist/creation.js +4 -4
  57. package/dist/daemon-recovery.d.ts +1 -8
  58. package/dist/daemon-recovery.js +8 -113
  59. package/dist/docs.d.ts +1 -1
  60. package/dist/docs.js +123 -19
  61. package/dist/doctor.d.ts +1 -1
  62. package/dist/doctor.js +55 -60
  63. package/dist/erased-resources.d.ts +9 -0
  64. package/dist/erased-resources.js +87 -0
  65. package/dist/file-delivery/reader.d.ts +15 -0
  66. package/dist/file-delivery/reader.js +63 -0
  67. package/dist/file-delivery/store.d.ts +15 -0
  68. package/dist/file-delivery/store.js +124 -0
  69. package/dist/file-delivery/types.d.ts +19 -0
  70. package/dist/file-delivery/types.js +1 -0
  71. package/dist/fleet-command-audit.d.ts +7 -0
  72. package/dist/fleet-command-audit.js +26 -9
  73. package/dist/harness/agent-session.d.ts +2 -0
  74. package/dist/harness/claude-code-session.js +1 -1
  75. package/dist/harness/claude-code.js +20 -11
  76. package/dist/harness/claude-runtime.d.ts +12 -0
  77. package/dist/harness/claude-runtime.js +43 -0
  78. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  79. package/dist/harness/codex-app-server-proxy.js +41 -5
  80. package/dist/harness/codex-reasoning.d.ts +5 -0
  81. package/dist/harness/codex-reasoning.js +12 -0
  82. package/dist/harness/codex-runtime.d.ts +11 -1
  83. package/dist/harness/codex-runtime.js +31 -4
  84. package/dist/harness/codex-session.js +2 -2
  85. package/dist/harness/codex.js +23 -12
  86. package/dist/init-wizard.d.ts +2 -0
  87. package/dist/init-wizard.js +38 -19
  88. package/dist/isolation/policy.js +6 -1
  89. package/dist/isolation/types.d.ts +5 -0
  90. package/dist/managed-cli.d.ts +253 -0
  91. package/dist/managed-cli.js +923 -0
  92. package/dist/managed-recovery.d.ts +13 -0
  93. package/dist/managed-recovery.js +31 -0
  94. package/dist/minimal-setup.d.ts +2 -0
  95. package/dist/minimal-setup.js +44 -0
  96. package/dist/monitor.d.ts +8 -34
  97. package/dist/monitor.js +89 -152
  98. package/dist/notifications/fleet-producer.d.ts +19 -0
  99. package/dist/notifications/fleet-producer.js +208 -0
  100. package/dist/notifications/outbox.d.ts +46 -0
  101. package/dist/notifications/outbox.js +158 -0
  102. package/dist/notifications/target-cleanup.d.ts +8 -0
  103. package/dist/notifications/target-cleanup.js +41 -0
  104. package/dist/notifications/task-producer.d.ts +18 -0
  105. package/dist/notifications/task-producer.js +67 -0
  106. package/dist/ops.d.ts +2 -0
  107. package/dist/ops.js +20 -3
  108. package/dist/owner-channel/channel.js +17 -6
  109. package/dist/owner-channel/commands.d.ts +4 -0
  110. package/dist/owner-channel/commands.js +8 -0
  111. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  112. package/dist/owner-channel/ours-client.js +2 -17
  113. package/dist/paths.d.ts +2 -0
  114. package/dist/paths.js +2 -0
  115. package/dist/preset-migration.js +25 -0
  116. package/dist/resolved-plan.js +1 -0
  117. package/dist/rooms-tasks/add-member.d.ts +48 -0
  118. package/dist/rooms-tasks/add-member.js +83 -0
  119. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  120. package/dist/rooms-tasks/archived-absence.js +48 -0
  121. package/dist/rooms-tasks/cli.js +65 -5
  122. package/dist/rooms-tasks/close.d.ts +6 -2
  123. package/dist/rooms-tasks/close.js +246 -37
  124. package/dist/rooms-tasks/config.d.ts +1 -1
  125. package/dist/rooms-tasks/config.js +11 -4
  126. package/dist/rooms-tasks/cowork-adapter.d.ts +2 -4
  127. package/dist/rooms-tasks/cowork-adapter.js +7 -121
  128. package/dist/rooms-tasks/cowork-http.js +29 -1
  129. package/dist/rooms-tasks/deletion.d.ts +1 -1
  130. package/dist/rooms-tasks/deletion.js +84 -5
  131. package/dist/rooms-tasks/erasure.d.ts +6 -0
  132. package/dist/rooms-tasks/erasure.js +173 -0
  133. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  134. package/dist/rooms-tasks/external-worker.js +3 -4
  135. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  136. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  137. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  138. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  139. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  140. package/dist/rooms-tasks/layout-cli.js +107 -0
  141. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  142. package/dist/rooms-tasks/layout-config.js +89 -0
  143. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  144. package/dist/rooms-tasks/layout-control.js +52 -0
  145. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  146. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  147. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  148. package/dist/rooms-tasks/layout-service.js +203 -0
  149. package/dist/rooms-tasks/layout.d.ts +121 -0
  150. package/dist/rooms-tasks/layout.js +285 -0
  151. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  152. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  153. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  154. package/dist/rooms-tasks/live-readiness.js +85 -0
  155. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  156. package/dist/rooms-tasks/member-ownership.js +40 -0
  157. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  158. package/dist/rooms-tasks/member-startup.js +2 -0
  159. package/dist/rooms-tasks/provision.d.ts +3 -0
  160. package/dist/rooms-tasks/provision.js +101 -49
  161. package/dist/rooms-tasks/recovery.d.ts +19 -0
  162. package/dist/rooms-tasks/recovery.js +110 -0
  163. package/dist/rooms-tasks/room-state.d.ts +4 -2
  164. package/dist/rooms-tasks/room-state.js +47 -8
  165. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  166. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  167. package/dist/rooms-tasks/task-state.d.ts +51 -8
  168. package/dist/rooms-tasks/task-state.js +245 -25
  169. package/dist/rooms-tasks/types.d.ts +40 -0
  170. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  171. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  172. package/dist/rooms-tasks/workspace.d.ts +35 -0
  173. package/dist/rooms-tasks/workspace.js +325 -0
  174. package/dist/runner.d.ts +25 -3
  175. package/dist/runner.js +291 -49
  176. package/dist/session/acp.d.ts +53 -1
  177. package/dist/session/acp.js +216 -40
  178. package/dist/session/arbiter.d.ts +11 -0
  179. package/dist/session/arbiter.js +43 -2
  180. package/dist/session/codex-app-server.d.ts +32 -0
  181. package/dist/session/codex-app-server.js +137 -23
  182. package/dist/session/control.d.ts +12 -1
  183. package/dist/session/control.js +68 -3
  184. package/dist/session/conversation-normalizer.d.ts +2 -1
  185. package/dist/session/conversation-normalizer.js +1 -1
  186. package/dist/session/conversation-store.d.ts +1 -1
  187. package/dist/session/conversation-store.js +7 -11
  188. package/dist/session/conversation-types.d.ts +28 -3
  189. package/dist/session/prompt-batch.d.ts +63 -0
  190. package/dist/session/prompt-batch.js +86 -0
  191. package/dist/session/task-notice.d.ts +14 -0
  192. package/dist/session/task-notice.js +13 -0
  193. package/dist/session/types.d.ts +14 -0
  194. package/dist/setup-tunnel-preflight.d.ts +34 -0
  195. package/dist/setup-tunnel-preflight.js +62 -0
  196. package/dist/socket-path.d.ts +2 -0
  197. package/dist/socket-path.js +22 -0
  198. package/dist/spawn.d.ts +5 -1
  199. package/dist/spawn.js +40 -4
  200. package/dist/startup-prompt.d.ts +4 -0
  201. package/dist/startup-prompt.js +36 -0
  202. package/dist/startup-readiness.d.ts +13 -0
  203. package/dist/startup-readiness.js +38 -0
  204. package/dist/startup-telemetry.d.ts +16 -0
  205. package/dist/startup-telemetry.js +46 -0
  206. package/dist/subscriptions/cli.d.ts +91 -0
  207. package/dist/subscriptions/cli.js +223 -0
  208. package/dist/subscriptions/launch.d.ts +37 -0
  209. package/dist/subscriptions/launch.js +93 -0
  210. package/dist/subscriptions/login.d.ts +75 -0
  211. package/dist/subscriptions/login.js +182 -0
  212. package/dist/subscriptions/observed.d.ts +7 -0
  213. package/dist/subscriptions/observed.js +44 -0
  214. package/dist/subscriptions/probe.d.ts +10 -0
  215. package/dist/subscriptions/probe.js +73 -0
  216. package/dist/subscriptions/service.d.ts +102 -0
  217. package/dist/subscriptions/service.js +364 -0
  218. package/dist/subscriptions/store.d.ts +71 -0
  219. package/dist/subscriptions/store.js +140 -0
  220. package/dist/supervisor/adoption.d.ts +8 -0
  221. package/dist/supervisor/adoption.js +78 -0
  222. package/dist/supervisor/catalog.d.ts +62 -0
  223. package/dist/supervisor/catalog.js +254 -0
  224. package/dist/supervisor/fleet.d.ts +8 -0
  225. package/dist/supervisor/fleet.js +89 -0
  226. package/dist/supervisor/index.js +3 -6
  227. package/dist/supervisor/launchd.d.ts +1 -1
  228. package/dist/supervisor/launchd.js +26 -13
  229. package/dist/supervisor/legacy.d.ts +35 -0
  230. package/dist/supervisor/legacy.js +524 -0
  231. package/dist/supervisor/manager.d.ts +15 -0
  232. package/dist/supervisor/manager.js +270 -0
  233. package/dist/supervisor/scope.d.ts +4 -0
  234. package/dist/supervisor/scope.js +10 -0
  235. package/dist/supervisor/systemd.d.ts +2 -2
  236. package/dist/supervisor/systemd.js +19 -33
  237. package/dist/task-supervision.d.ts +9 -0
  238. package/dist/task-supervision.js +145 -0
  239. package/dist/temp-idle.d.ts +17 -0
  240. package/dist/temp-idle.js +42 -0
  241. package/dist/temp-lifecycle.d.ts +23 -8
  242. package/dist/temp-lifecycle.js +113 -65
  243. package/dist/temp-supervisor-recovery.d.ts +1 -1
  244. package/dist/temp-supervisor-recovery.js +2 -2
  245. package/dist/tunnel-setup-completion.d.ts +7 -0
  246. package/dist/tunnel-setup-completion.js +13 -0
  247. package/dist/tunnel-setup-input.d.ts +19 -0
  248. package/dist/tunnel-setup-input.js +124 -0
  249. package/dist/web/agent-attachments.d.ts +29 -0
  250. package/dist/web/agent-attachments.js +162 -0
  251. package/dist/web/auth.d.ts +63 -2
  252. package/dist/web/auth.js +191 -10
  253. package/dist/web/available-port.d.ts +2 -0
  254. package/dist/web/available-port.js +23 -0
  255. package/dist/web/control.d.ts +3 -3
  256. package/dist/web/control.js +10 -8
  257. package/dist/web/fleet-config-service.d.ts +4 -1
  258. package/dist/web/fleet-config-service.js +46 -15
  259. package/dist/web/prefix-gateway.d.ts +32 -0
  260. package/dist/web/prefix-gateway.js +238 -0
  261. package/dist/web/runtime.d.ts +3 -1
  262. package/dist/web/runtime.js +116 -31
  263. package/dist/web/server.d.ts +16 -0
  264. package/dist/web/server.js +654 -55
  265. package/dist/web/service.d.ts +11 -2
  266. package/dist/web/service.js +30 -4
  267. package/dist/web/workspace-devices.d.ts +107 -0
  268. package/dist/web/workspace-devices.js +277 -0
  269. package/dist/web/workspace-tunnel.d.ts +1 -0
  270. package/dist/web/workspace-tunnel.js +34 -0
  271. package/dist/workspace-enrollment.d.ts +82 -0
  272. package/dist/workspace-enrollment.js +566 -0
  273. package/dist/workspace-replacement.d.ts +66 -0
  274. package/dist/workspace-replacement.js +220 -0
  275. package/docs/acp-files.md +146 -0
  276. package/docs/fleet-migration.md +54 -0
  277. package/docs/live-voice.md +13 -0
  278. package/docs/room-layouts.md +227 -0
  279. package/docs/task-workspaces.md +225 -0
  280. package/docs/validation/managed-cli-permissions.md +421 -0
  281. package/examples/room-layouts/fresh.yaml +27 -0
  282. package/examples/room-layouts/scoped.yaml +21 -0
  283. package/examples/room-layouts/shared.yaml +19 -0
  284. package/package.json +20 -19
  285. package/presets/README.md +78 -0
  286. package/presets/brain-catalog.json +8 -2
  287. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  288. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  289. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  290. package/presets/fleet/agent_templates/LocalCoordinator.yaml +1 -1
  291. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  292. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  293. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  294. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  295. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  296. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  297. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  298. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  299. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  300. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  301. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  302. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  303. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  304. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  305. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  306. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  307. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  308. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  309. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  310. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  311. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  312. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  313. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  314. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  315. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  316. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  317. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  318. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  319. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  320. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  321. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  322. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  323. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  324. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  325. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  326. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  327. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  328. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  329. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  330. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  331. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  332. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  333. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  334. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  335. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  336. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  337. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  338. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  339. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  340. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  341. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  342. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  343. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  344. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  345. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  346. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  347. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  348. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  349. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  350. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  351. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  352. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  353. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  354. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  355. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  356. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  357. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  364. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  365. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  366. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  367. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  368. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  369. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  370. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  371. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  372. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  373. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  374. package/presets/fleet/roles/Coordinator.yaml +34 -11
  375. package/presets/fleet/roles/Engineer.yaml +40 -0
  376. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  377. package/presets/fleet/room_layouts/pair.yaml +24 -0
  378. package/presets/fleet/room_layouts/single.yaml +17 -0
  379. package/presets/fleet/room_layouts/team.yaml +30 -0
  380. package/presets/fleet/room_templates/engineering.yaml +19 -0
  381. package/presets/manifest.json +2 -2
  382. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  383. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  384. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  385. package/dist/web-app/icons/ours-fleet.svg +0 -4
  386. package/dist/web-app/index.html +0 -17
  387. package/dist/web-app/manifest.webmanifest +0 -15
  388. package/dist/web-app/offline.html +0 -18
  389. package/dist/web-app/sw.js +0 -51
@@ -0,0 +1,524 @@
1
+ import { existsSync, lstatSync, readFileSync, readlinkSync, readdirSync, rmSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+ import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
6
+ import { agentDir, home, stateRoot } from '../paths.js';
7
+ import { realExec } from '../exec.js';
8
+ import { assertSafeAncestors } from '../rooms-tasks/workspace.js';
9
+ const digest = (bytes) => createHash('sha256').update(bytes).digest('hex');
10
+ function proof(path) {
11
+ assertSafeAncestors(dirname(path));
12
+ const stat = lstatSync(path);
13
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024)
14
+ throw Error('LEGACY_PERMANENT_UNSAFE_PROOF');
15
+ return readFileSync(path, 'utf8');
16
+ }
17
+ function assertReceiptGates(receipt) {
18
+ if (receipt.readinessGates !== undefined && !Array.isArray(receipt.readinessGates))
19
+ throw Error('LEGACY_PERMANENT_RECEIPT_MISMATCH');
20
+ for (const gate of receipt.readinessGates ?? []) {
21
+ if (gate.path !== resolve(gate.path) || !Array.isArray(gate.argv) || gate.argv[0] !== gate.path
22
+ || gate.argv.some(arg => typeof arg !== 'string' || /[\r\n\0]/.test(arg))
23
+ || !Number.isSafeInteger(gate.timeoutStartSec) || gate.timeoutStartSec < 1
24
+ || !Number.isSafeInteger(gate.restartSec) || gate.restartSec < 0)
25
+ throw Error('LEGACY_PERMANENT_RECEIPT_MISMATCH');
26
+ }
27
+ if (receipt.operatorFiles !== undefined && !Array.isArray(receipt.operatorFiles))
28
+ throw Error('LEGACY_PERMANENT_RECEIPT_MISMATCH');
29
+ for (const file of receipt.operatorFiles ?? []) {
30
+ if (dirname(file.path) !== join(home(), '.config/systemd/user/ours-fleet.service.d')
31
+ || !/^[a-f0-9]{64}$/.test(file.hash) || digest(proof(file.path)) !== file.hash)
32
+ throw Error('LEGACY_PERMANENT_OPERATOR_DROPIN_CHANGED');
33
+ }
34
+ }
35
+ function xml(value) {
36
+ return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')
37
+ .replaceAll('"', '&quot;').replaceAll("'", '&apos;');
38
+ }
39
+ function assertLaunchdFile(bytes, name, binPath) {
40
+ const label = `<key>Label</key><string>network.ours.fleet.${name}</string>`;
41
+ const argumentsBlock = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/g;
42
+ const matches = [...bytes.matchAll(argumentsBlock)];
43
+ const expected = `<string>${xml(binPath)}</string><string>_run</string><string>${name}</string>`;
44
+ if (!bytes.includes(label) || (bytes.match(/<key>Label<\/key>/g)?.length ?? 0) !== 1
45
+ || matches.length !== 1 || matches[0][1].replace(/\s+(?=<)/g, '').trim() !== expected
46
+ || /<key>(?:Program|WorkingDirectory)<\/key>/.test(bytes))
47
+ throw Error('LEGACY_PERMANENT_NATIVE_OWNER_MISMATCH');
48
+ }
49
+ function properties(text) {
50
+ const out = {};
51
+ for (const line of text.trim().split('\n')) {
52
+ const index = line.indexOf('=');
53
+ if (index < 1 || Object.hasOwn(out, line.slice(0, index)))
54
+ throw Error('LEGACY_PERMANENT_NATIVE_PROBE_UNKNOWN');
55
+ out[line.slice(0, index)] = line.slice(index + 1);
56
+ }
57
+ return out;
58
+ }
59
+ const runningStates = ['active', 'activating', 'reloading', 'deactivating'];
60
+ const stoppedStates = ['inactive', 'failed'];
61
+ function argv(value) {
62
+ // systemctl's structured command property is not shell syntax. Reject
63
+ // ambiguous escaped/quoted paths rather than guessing token boundaries.
64
+ const match = /^\{ path=([^;]+?) ; argv\[\]=(.*?) ; ignore_errors=no ; [^{}]*\}$/.exec(value);
65
+ if (!match || /["'\\\n\r]/.test(match[2]))
66
+ throw Error('LEGACY_PERMANENT_NATIVE_OWNER_MISMATCH');
67
+ const args = match[2].trim().split(/\s+/);
68
+ if (args[0] !== match[1].trim())
69
+ throw Error('LEGACY_PERMANENT_NATIVE_OWNER_MISMATCH');
70
+ return args;
71
+ }
72
+ function legacyCli(args, name, inputBin) {
73
+ let command = args;
74
+ if (args.length === 4) {
75
+ if (args[0] !== resolve(args[0]) || !/^node(?:js)?$/.test(basename(args[0])))
76
+ throw Error('LEGACY_PERMANENT_NODE_PROOF_REQUIRED: use the native unit Node or review its executable');
77
+ const node = lstatSync(args[0]);
78
+ if (!node.isFile() || node.isSymbolicLink() || !(node.mode & 0o111))
79
+ throw Error('LEGACY_PERMANENT_NODE_PROOF_REQUIRED');
80
+ command = args.slice(1);
81
+ }
82
+ if (command.length !== 3 || command[1] !== '_run' || command[2] !== name
83
+ || command[0] !== resolve(command[0]))
84
+ throw Error('LEGACY_PERMANENT_NATIVE_OWNER_MISMATCH');
85
+ let cliPath = command[0];
86
+ if (existsSync(cliPath) && lstatSync(cliPath).isSymbolicLink())
87
+ cliPath = resolve(dirname(cliPath), readlinkSync(cliPath));
88
+ if (command[0] !== inputBin || cliPath !== command[0]) {
89
+ const packageDir = dirname(dirname(cliPath));
90
+ const pkg = JSON.parse(proof(join(packageDir, 'package.json')));
91
+ if (pkg.name !== '@ours.network/fleet' || pkg.bin?.['ours-fleet'] !== 'dist/cli.js'
92
+ || cliPath !== join(packageDir, 'dist/cli.js'))
93
+ throw Error('LEGACY_PERMANENT_NATIVE_OWNER_MISMATCH');
94
+ // The installed CLI can exceed the small proof limit. Only its regular
95
+ // non-symlink shape is needed; package metadata binds the entry point.
96
+ assertSafeAncestors(dirname(cliPath));
97
+ const stat = lstatSync(cliPath);
98
+ if (!stat.isFile() || stat.isSymbolicLink())
99
+ throw Error('LEGACY_PERMANENT_UNSAFE_PROOF');
100
+ }
101
+ return command[0];
102
+ }
103
+ function seconds(value) {
104
+ if (value === '0')
105
+ return 0;
106
+ if (/^\d+s$/.test(value))
107
+ return Number(value.slice(0, -1));
108
+ const parts = /^(?:(\d+)min )?(\d+)s$/.exec(value);
109
+ if (!parts)
110
+ throw Error('LEGACY_PERMANENT_NATIVE_PROBE_UNKNOWN');
111
+ return Number(parts[1] ?? 0) * 60 + Number(parts[2]);
112
+ }
113
+ function directives(bytes) {
114
+ const result = new Map();
115
+ let section = '';
116
+ for (const raw of bytes.split('\n')) {
117
+ const line = raw.trim();
118
+ if (!line || /^[#;]/.test(line))
119
+ continue;
120
+ if (/^\[[A-Za-z]+\]$/.test(line)) {
121
+ section = line;
122
+ continue;
123
+ }
124
+ const match = /^([A-Za-z]+)\s*=(.*)$/.exec(line);
125
+ if (!match || !section || /\\$/.test(line))
126
+ throw Error('LEGACY_PERMANENT_UNSUPPORTED_DROPIN');
127
+ const key = `${section}${match[1]}`;
128
+ result.set(key, [...result.get(key) ?? [], match[2].trim()]);
129
+ }
130
+ return result;
131
+ }
132
+ /** We never copy or execute operator hooks. Explicit parent drop-ins must
133
+ * preserve non-ExecStart settings; generated parent init leaves them alone. */
134
+ function assertOperatorDropins(paths) {
135
+ const required = new Map();
136
+ for (const path of paths) {
137
+ for (const [key, values] of directives(proof(path))) {
138
+ if (key === '[Service]ExecStart')
139
+ continue;
140
+ if (!['[Service]ExecStartPre', '[Service]TimeoutStartSec', '[Service]RestartSec', '[Service]Environment',
141
+ '[Unit]StartLimitIntervalSec', '[Service]Restart'].includes(key))
142
+ throw Error(`LEGACY_PERMANENT_UNSUPPORTED_DROPIN: operator review required for ${key}`);
143
+ if (key === '[Service]Environment' && values.some(value => !value || /["'\\]/.test(value)
144
+ || value.split(/\s+/).some(field => !/^[A-Za-z_][A-Za-z0-9_]*=\S+$/.test(field))))
145
+ throw Error('LEGACY_PERMANENT_UNSUPPORTED_DROPIN');
146
+ if (['[Service]TimeoutStartSec', '[Service]RestartSec'].includes(key)
147
+ && values.some(value => !/^\d+$/.test(value)))
148
+ throw Error('LEGACY_PERMANENT_UNSUPPORTED_DROPIN');
149
+ const previous = required.get(key);
150
+ if (previous && JSON.stringify(previous) !== JSON.stringify(values))
151
+ throw Error('LEGACY_PERMANENT_OPERATOR_DROPIN_CONFLICT');
152
+ required.set(key, values);
153
+ }
154
+ }
155
+ if (!required.size)
156
+ return [];
157
+ const dir = join(home(), '.config/systemd/user/ours-fleet.service.d');
158
+ if (!existsSync(dir))
159
+ throw Error('LEGACY_PERMANENT_OPERATOR_DROPIN_REQUIRED: preserve legacy directives in ours-fleet.service.d before retry');
160
+ assertSafeAncestors(dir);
161
+ const files = readdirSync(dir).filter(name => name.endsWith('.conf')).sort();
162
+ if (!files.length || files.length > 32)
163
+ throw Error('LEGACY_PERMANENT_OPERATOR_DROPIN_REQUIRED');
164
+ const actual = new Map(), proofs = [];
165
+ for (const name of files) {
166
+ const path = join(dir, name), bytes = proof(path);
167
+ proofs.push({ path, hash: digest(bytes) });
168
+ for (const [key, values] of directives(bytes))
169
+ actual.set(key, [...actual.get(key) ?? [], ...values]);
170
+ }
171
+ for (const [key, values] of required) {
172
+ if (JSON.stringify(actual.get(key)) !== JSON.stringify(values))
173
+ throw Error(`LEGACY_PERMANENT_OPERATOR_DROPIN_REQUIRED: preserve ${key} in ours-fleet.service.d before retry`);
174
+ }
175
+ return proofs;
176
+ }
177
+ async function assertParentSettings(nativePaths, exec) {
178
+ const required = new Map();
179
+ for (const path of nativePaths)
180
+ for (const [key, values] of directives(proof(path)))
181
+ if (key !== '[Service]ExecStart')
182
+ required.set(key, values);
183
+ if (!required.size)
184
+ return;
185
+ const result = await exec('systemctl', ['--user', 'show', '-p', 'LoadState', '-p', 'ActiveState',
186
+ '-p', 'ExecStartPre', '-p', 'TimeoutStartUSec', '-p', 'RestartUSec', '-p', 'Environment',
187
+ '-p', 'StartLimitIntervalUSec', '-p', 'Restart', 'ours-fleet.service']);
188
+ if (result.code !== 0)
189
+ throw Error('LEGACY_PERMANENT_PARENT_SETTINGS_UNPROVEN');
190
+ const value = properties(result.stdout);
191
+ value.ExecStartPre ??= '';
192
+ value.Environment ??= '';
193
+ if (value.LoadState !== 'loaded' || value.ActiveState !== 'active')
194
+ throw Error('LEGACY_PERMANENT_PARENT_NOT_READY');
195
+ const pre = required.get('[Service]ExecStartPre');
196
+ if (pre) {
197
+ const commands = [];
198
+ for (const command of pre) {
199
+ if (!command)
200
+ commands.length = 0;
201
+ else
202
+ commands.push(command);
203
+ }
204
+ if (commands.length !== 1 || !value.ExecStartPre
205
+ || JSON.stringify(argv(value.ExecStartPre)) !== JSON.stringify(commands[0].split(/\s+/)))
206
+ throw Error('LEGACY_PERMANENT_PARENT_SETTINGS_UNPROVEN');
207
+ }
208
+ for (const [key, property] of [['[Service]TimeoutStartSec', 'TimeoutStartUSec'], ['[Service]RestartSec', 'RestartUSec']]) {
209
+ const wanted = required.get(key)?.at(-1);
210
+ if (wanted && seconds(value[property]) !== Number(wanted))
211
+ throw Error('LEGACY_PERMANENT_PARENT_SETTINGS_UNPROVEN');
212
+ }
213
+ for (const line of required.get('[Service]Environment') ?? []) {
214
+ if (!line || /["'\\]/.test(line))
215
+ throw Error('LEGACY_PERMANENT_UNSUPPORTED_DROPIN');
216
+ for (const variable of line.split(/\s+/))
217
+ if (!(value.Environment ?? '').split(/\s+/).includes(variable))
218
+ throw Error('LEGACY_PERMANENT_PARENT_SETTINGS_UNPROVEN');
219
+ }
220
+ if (required.has('[Unit]StartLimitIntervalSec')
221
+ && seconds(value.StartLimitIntervalUSec) !== Number(required.get('[Unit]StartLimitIntervalSec')?.at(-1)))
222
+ throw Error('LEGACY_PERMANENT_PARENT_SETTINGS_UNPROVEN');
223
+ if (required.has('[Service]Restart') && value.Restart !== required.get('[Service]Restart')?.at(-1))
224
+ throw Error('LEGACY_PERMANENT_PARENT_SETTINGS_UNPROVEN');
225
+ }
226
+ async function systemdState(name, path, exec) {
227
+ const result = await exec('systemctl', ['--user', 'show', '-p', 'LoadState', '-p', 'FragmentPath',
228
+ '-p', 'DropInPaths', '-p', 'UnitFileState', '-p', 'ActiveState', '-p', 'ExecStart', '-p', 'ExecStartPre',
229
+ '-p', 'ExecStartPost', '-p', 'ExecStop', '-p', 'ExecStopPost', '-p', 'ExecReload', '-p', 'ExecCondition',
230
+ '-p', 'Environment', '-p', 'MainPID', '-p', 'ControlGroup', '-p', 'TimeoutStartUSec', '-p', 'RestartUSec',
231
+ `ours-fleet-agent@${name}.service`]);
232
+ if (result.code !== 0)
233
+ throw Error('LEGACY_PERMANENT_NATIVE_PROBE_UNKNOWN');
234
+ const value = properties(result.stdout);
235
+ // systemctl omits unset properties unless --all is used. Required state
236
+ // fields stay mandatory; omitted optional hooks/environment mean empty.
237
+ for (const key of ['DropInPaths', 'ExecStartPre', 'ExecStartPost', 'ExecStop', 'ExecStopPost', 'ExecReload',
238
+ 'ExecCondition', 'Environment', 'ControlGroup'])
239
+ value[key] ??= '';
240
+ if (value.LoadState === 'not-found' && !value.FragmentPath && !value.DropInPaths && value.MainPID === '0')
241
+ return { enabled: false, live: false, absent: true, value };
242
+ if (value.LoadState !== 'loaded' || value.FragmentPath !== path)
243
+ throw Error('LEGACY_PERMANENT_NATIVE_OWNER_MISMATCH');
244
+ if (!['enabled', 'disabled'].includes(value.UnitFileState)
245
+ || ![...runningStates, ...stoppedStates].includes(value.ActiveState))
246
+ throw Error('LEGACY_PERMANENT_NATIVE_PROBE_UNKNOWN');
247
+ return { enabled: value.UnitFileState === 'enabled', live: runningStates.includes(value.ActiveState), absent: false, value };
248
+ }
249
+ function systemdProof(name, path, value, inputBin) {
250
+ const paths = [path, ...(value.DropInPaths ? value.DropInPaths.split(' ') : [])];
251
+ for (const drop of paths.slice(1)) {
252
+ if (![`${path}.d`, join(dirname(path), `ours-fleet-agent@${name}.service.d`)].includes(dirname(drop))
253
+ || !/^[A-Za-z0-9_.-]+\.conf$/.test(drop.slice(dirname(drop).length + 1)))
254
+ throw Error('LEGACY_PERMANENT_NATIVE_OWNER_MISMATCH');
255
+ }
256
+ const files = paths.map(file => ({ path: file, hash: digest(proof(file)) }));
257
+ const operatorFiles = assertOperatorDropins(paths.slice(1));
258
+ // Validate the effective command, not an overridden template ExecStart.
259
+ const legacyBinPath = legacyCli(argv(value.ExecStart), name, inputBin);
260
+ const cliLinkTarget = existsSync(legacyBinPath) && lstatSync(legacyBinPath).isSymbolicLink() ? readlinkSync(legacyBinPath) : undefined;
261
+ const cliRealPath = cliLinkTarget === undefined ? legacyBinPath : resolve(dirname(legacyBinPath), cliLinkTarget);
262
+ for (const hook of ['ExecStartPost', 'ExecStop', 'ExecStopPost', 'ExecReload', 'ExecCondition'])
263
+ if (value[hook] !== '')
264
+ throw Error('LEGACY_PERMANENT_UNSUPPORTED_HOOK');
265
+ if (value.Environment === undefined || /(?:^|\s)OURS_FLEET_HOME=/.test(value.Environment)
266
+ && !value.Environment.split(' ').includes(`OURS_FLEET_HOME=${home()}`))
267
+ throw Error('LEGACY_PERMANENT_CONFIG_MISMATCH');
268
+ if (!/^(0|[1-9]\d*)$/.test(value.MainPID) || value.ControlGroup === undefined)
269
+ throw Error('LEGACY_PERMANENT_NATIVE_PROBE_UNKNOWN');
270
+ const readinessGates = [];
271
+ if (value.ExecStartPre) {
272
+ const pre = argv(value.ExecStartPre);
273
+ const internal = pre.length === 4 && pre[1] === legacyBinPath
274
+ && pre[2] === '_wait-daemon' && pre[3] === name;
275
+ if (!internal) {
276
+ if (pre[0] !== resolve(pre[0]))
277
+ throw Error('LEGACY_PERMANENT_UNSUPPORTED_READINESS_GATE');
278
+ readinessGates.push({ path: pre[0], argv: pre,
279
+ timeoutStartSec: seconds(value.TimeoutStartUSec), restartSec: seconds(value.RestartUSec) });
280
+ }
281
+ }
282
+ else if (value.ExecStartPre !== '')
283
+ throw Error('LEGACY_PERMANENT_NATIVE_PROBE_UNKNOWN');
284
+ return { files, legacyBinPath, cliLinkTarget, cliRealPath, readinessGates, operatorFiles };
285
+ }
286
+ async function launchdState(name, path, binPath, exec) {
287
+ const result = await exec('launchctl', ['print', `gui/${process.getuid?.() ?? 501}/network.ours.fleet.${name}`]);
288
+ if (result.code !== 0) {
289
+ if (/could not find service|no such process/i.test(`${result.stdout}\n${result.stderr}`))
290
+ return { loaded: false, live: false };
291
+ throw Error('LEGACY_PERMANENT_NATIVE_PROBE_UNKNOWN');
292
+ }
293
+ const pathValue = /^\s*path\s*=\s*(.+)\s*$/m.exec(result.stdout)?.[1]?.trim();
294
+ const program = /^\s*program\s*=\s*(.+)\s*$/m.exec(result.stdout)?.[1]?.trim();
295
+ const args = /^\s*arguments\s*=\s*\{([\s\S]*?)^\s*\}/m.exec(result.stdout)?.[1]
296
+ .split('\n').map(value => value.trim()).filter(Boolean);
297
+ if (pathValue !== path || program !== binPath || JSON.stringify(args) !== JSON.stringify([binPath, '_run', name]))
298
+ throw Error('LEGACY_PERMANENT_NATIVE_OWNER_MISMATCH');
299
+ const state = /^\s*state\s*=\s*(.+)\s*$/m.exec(result.stdout)?.[1]?.trim();
300
+ if (!state || !['running', 'waiting', 'not running'].includes(state))
301
+ throw Error('LEGACY_PERMANENT_NATIVE_PROBE_UNKNOWN');
302
+ return { loaded: true, live: state !== 'not running' };
303
+ }
304
+ async function assertLegacyProcessAbsent(name, binPath, input) {
305
+ const exec = input.exec ?? realExec;
306
+ const result = await exec('ps', ['-ax', '-o', 'pid=', '-o', 'command=']);
307
+ if (result.code !== 0)
308
+ throw Error('LEGACY_PERMANENT_PROCESS_ABSENCE_UNKNOWN');
309
+ const exactRole = new RegExp(`(?:^|\\s)_run\\s+${name}(?:\\s|$)`);
310
+ if (result.stdout.split('\n').some(line => {
311
+ const row = /^\s*(\d+)\s+(.*)$/.exec(line);
312
+ if (!row || !exactRole.test(row[2]) || !row[2].split(/\s+/).includes(binPath))
313
+ return false;
314
+ if (input.platform !== 'linux')
315
+ return true;
316
+ let fleetHome;
317
+ try {
318
+ fleetHome = input.processHome ? input.processHome(Number(row[1]))
319
+ : readFileSync(`/proc/${row[1]}/environ`, 'utf8').split('\0')
320
+ .find(field => field.startsWith('OURS_FLEET_HOME='))?.slice('OURS_FLEET_HOME='.length) ?? homedir();
321
+ }
322
+ catch {
323
+ throw Error('LEGACY_PERMANENT_PROCESS_ABSENCE_UNKNOWN');
324
+ }
325
+ return resolve(fleetHome) === resolve(home());
326
+ }))
327
+ throw Error('LEGACY_PERMANENT_PROCESS_STILL_RUNNING');
328
+ }
329
+ /** Start the common parent after all-role preflight and before retirement. Only configured,
330
+ * identity-proven legacy registrations transfer; no identity/session writes.
331
+ * A receipt survives removal of the old native job and preserves its original
332
+ * running/stopped intent across retries. Native services are never inferred
333
+ * from role names alone. Uncertain or conflicting proof fails closed.
334
+ */
335
+ export async function migrateLegacyPermanentMembers(input) {
336
+ if (!['linux', 'darwin'].includes(input.platform))
337
+ return [];
338
+ if (!input.binPath || /[\r\n\0]/.test(input.binPath) || input.binPath !== resolve(input.binPath))
339
+ throw Error('LEGACY_PERMANENT_INVALID_INPUT');
340
+ const names = new Set(), results = [];
341
+ for (const role of input.roles) {
342
+ if (!/^[A-Za-z0-9_-]{1,120}$/.test(role.name) || !/^[A-Za-z0-9_-]{1,120}$/.test(role.identity) || names.has(role.name))
343
+ throw Error('LEGACY_PERMANENT_INVALID_INPUT');
344
+ names.add(role.name);
345
+ }
346
+ const exec = input.exec ?? realExec;
347
+ const gates = new Map();
348
+ const execute = async (role, dryRun) => withFileLock(join(stateRoot(), 'locks', 'legacy-permanent', role.name), async () => {
349
+ const receiptPath = join(stateRoot(), 'supervisor', 'legacy-permanent', `${role.name}.json`);
350
+ const platform = input.platform;
351
+ const nativePath = platform === 'linux'
352
+ ? join(home(), '.config', 'systemd', 'user', 'ours-fleet-agent@.service')
353
+ : join(home(), 'Library', 'LaunchAgents', `network.ours.fleet.${role.name}.plist`);
354
+ let receipt = existsSync(receiptPath) ? JSON.parse(proof(receiptPath)) : undefined;
355
+ if (!receipt && !existsSync(nativePath))
356
+ return { name: role.name, status: 'not-installed' };
357
+ if (!receipt && platform === 'linux') {
358
+ const state = await systemdState(role.name, nativePath, exec);
359
+ if (state.absent)
360
+ return { name: role.name, status: 'not-installed' };
361
+ if (!state.enabled && !state.live)
362
+ return { name: role.name, status: 'disabled' };
363
+ }
364
+ if (proof(join(agentDir(role.name), '.identity')).trim() !== role.identity)
365
+ throw Error('LEGACY_PERMANENT_IDENTITY_MISMATCH');
366
+ const recordedConfig = proof(join(agentDir(role.name), '.config-path')).trim() || undefined;
367
+ if (recordedConfig && input.configPath && resolve(recordedConfig) !== resolve(input.configPath))
368
+ throw Error('LEGACY_PERMANENT_CONFIG_MISMATCH');
369
+ const configPath = recordedConfig ?? input.configPath;
370
+ if (receipt) {
371
+ if (receipt.version !== 1 || receipt.name !== role.name || receipt.identity !== role.identity
372
+ || receipt.platform !== platform || receipt.nativePath !== nativePath || !/^[a-f0-9]{64}$/.test(receipt.fileHash)
373
+ || !['running', 'stopped'].includes(receipt.desired)
374
+ || !['prepared', 'native-retired', 'registered'].includes(receipt.phase))
375
+ throw Error('LEGACY_PERMANENT_RECEIPT_MISMATCH');
376
+ if (receipt.phase === 'registered') {
377
+ if (platform === 'linux') {
378
+ const state = await systemdState(role.name, nativePath, exec);
379
+ if (state.enabled || state.live)
380
+ throw Error('LEGACY_PERMANENT_NATIVE_REAPPEARED');
381
+ }
382
+ else if (existsSync(nativePath) || (await launchdState(role.name, nativePath, receipt.binPath, exec)).loaded)
383
+ throw Error('LEGACY_PERMANENT_NATIVE_REAPPEARED');
384
+ for (const gate of receipt.readinessGates ?? [])
385
+ gates.set(gate.path, gate);
386
+ return { name: role.name, status: 'already-migrated' };
387
+ }
388
+ assertReceiptGates(receipt);
389
+ // A new CLI may finish an interrupted adoption of the same role. The
390
+ // receipt pins old native artifacts, not the invoking release path.
391
+ if (receipt.configPath !== configPath)
392
+ throw Error('LEGACY_PERMANENT_RECEIPT_MISMATCH');
393
+ }
394
+ let linuxProof;
395
+ if (existsSync(nativePath)) {
396
+ const bytes = proof(nativePath);
397
+ if (platform === 'linux')
398
+ linuxProof = systemdProof(role.name, nativePath, (await systemdState(role.name, nativePath, exec)).value, receipt?.legacyBinPath ?? input.binPath);
399
+ else
400
+ assertLaunchdFile(bytes, role.name, receipt?.legacyBinPath ?? receipt?.binPath ?? input.binPath);
401
+ if (receipt && digest(bytes) !== receipt.fileHash)
402
+ throw Error('LEGACY_PERMANENT_NATIVE_FILE_CHANGED');
403
+ if (receipt?.files && JSON.stringify(linuxProof?.files) !== JSON.stringify(receipt.files))
404
+ throw Error('LEGACY_PERMANENT_NATIVE_FILE_CHANGED');
405
+ if (receipt?.operatorFiles && JSON.stringify(linuxProof?.operatorFiles) !== JSON.stringify(receipt.operatorFiles))
406
+ throw Error('LEGACY_PERMANENT_OPERATOR_DROPIN_CHANGED');
407
+ if (receipt && (receipt.cliRealPath !== undefined && receipt.cliRealPath !== linuxProof?.cliRealPath
408
+ || receipt.cliLinkTarget !== undefined && receipt.cliLinkTarget !== linuxProof?.cliLinkTarget))
409
+ throw Error('LEGACY_PERMANENT_NATIVE_FILE_CHANGED');
410
+ }
411
+ else if (!receipt || receipt.phase === 'prepared')
412
+ throw Error('LEGACY_PERMANENT_NATIVE_PROOF_MISSING');
413
+ if (!receipt) {
414
+ let desired;
415
+ if (platform === 'linux') {
416
+ const state = await systemdState(role.name, nativePath, exec);
417
+ if (state.absent || (!state.enabled && !state.live))
418
+ return { name: role.name, status: 'disabled' };
419
+ if (!state.enabled)
420
+ throw Error('LEGACY_PERMANENT_DISABLED_BUT_RUNNING');
421
+ // Enabled units express boot intent, including failed/inactive units.
422
+ desired = 'running';
423
+ }
424
+ else {
425
+ const state = await launchdState(role.name, nativePath, input.binPath, exec);
426
+ desired = state.live ? 'running' : 'stopped';
427
+ }
428
+ receipt = { version: 1, name: role.name, identity: role.identity, platform, binPath: input.binPath,
429
+ configPath, nativePath, fileHash: digest(proof(nativePath)), desired, phase: 'prepared',
430
+ ...linuxProof, legacyBinPath: linuxProof?.legacyBinPath ?? input.binPath };
431
+ }
432
+ for (const gate of receipt.readinessGates ?? []) {
433
+ const existing = gates.get(gate.path);
434
+ if (existing && JSON.stringify(existing) !== JSON.stringify(gate))
435
+ throw Error('LEGACY_PERMANENT_READINESS_GATE_CONFLICT');
436
+ gates.set(gate.path, gate);
437
+ }
438
+ if (dryRun)
439
+ return { name: role.name, status: 'migrated' };
440
+ if (platform === 'linux' && receipt.operatorFiles?.length)
441
+ await assertParentSettings((receipt.files ?? []).slice(1).map(file => file.path), exec);
442
+ if (!existsSync(receiptPath))
443
+ replaceFileAtomically(receiptPath, JSON.stringify(receipt));
444
+ if (receipt.phase === 'prepared') {
445
+ if (platform === 'linux') {
446
+ const state = await systemdState(role.name, nativePath, exec);
447
+ if (state.enabled || state.live) {
448
+ const result = await exec('systemctl', ['--user', 'disable', '--now', `ours-fleet-agent@${role.name}.service`]);
449
+ if (result.code !== 0)
450
+ throw Error('LEGACY_PERMANENT_NATIVE_RETIRE_FAILED');
451
+ }
452
+ const after = await systemdState(role.name, nativePath, exec);
453
+ if (after.enabled || after.live)
454
+ throw Error('LEGACY_PERMANENT_NATIVE_STOP_UNPROVEN');
455
+ }
456
+ else {
457
+ const state = await launchdState(role.name, nativePath, receipt.legacyBinPath ?? receipt.binPath, exec);
458
+ if (state.loaded) {
459
+ const result = await exec('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}/network.ours.fleet.${role.name}`]);
460
+ if (result.code !== 0)
461
+ throw Error('LEGACY_PERMANENT_NATIVE_RETIRE_FAILED');
462
+ }
463
+ if ((await launchdState(role.name, nativePath, receipt.legacyBinPath ?? receipt.binPath, exec)).loaded)
464
+ throw Error('LEGACY_PERMANENT_NATIVE_STOP_UNPROVEN');
465
+ if (digest(proof(nativePath)) !== receipt.fileHash)
466
+ throw Error('LEGACY_PERMANENT_NATIVE_FILE_CHANGED');
467
+ // Mark native retirement before removing the file so a crash after
468
+ // unlink still resumes from proof outside the old service path.
469
+ }
470
+ receipt.phase = 'native-retired';
471
+ replaceFileAtomically(receiptPath, JSON.stringify(receipt));
472
+ }
473
+ if (platform === 'linux') {
474
+ if (digest(proof(nativePath)) !== receipt.fileHash)
475
+ throw Error('LEGACY_PERMANENT_NATIVE_FILE_CHANGED');
476
+ const after = await systemdState(role.name, nativePath, exec);
477
+ if (after.enabled || after.live || after.value.MainPID !== '0')
478
+ throw Error('LEGACY_PERMANENT_NATIVE_STOP_UNPROVEN');
479
+ const postProof = systemdProof(role.name, nativePath, after.value, receipt.legacyBinPath ?? receipt.binPath);
480
+ if (receipt.files && JSON.stringify(postProof.files) !== JSON.stringify(receipt.files))
481
+ throw Error('LEGACY_PERMANENT_NATIVE_FILE_CHANGED');
482
+ assertReceiptGates(receipt);
483
+ }
484
+ else {
485
+ if ((await launchdState(role.name, nativePath, receipt.legacyBinPath ?? receipt.binPath, exec)).loaded)
486
+ throw Error('LEGACY_PERMANENT_NATIVE_STOP_UNPROVEN');
487
+ if (existsSync(nativePath)) {
488
+ if (digest(proof(nativePath)) !== receipt.fileHash)
489
+ throw Error('LEGACY_PERMANENT_NATIVE_FILE_CHANGED');
490
+ rmSync(nativePath);
491
+ }
492
+ }
493
+ if (proof(join(agentDir(role.name), '.identity')).trim() !== role.identity
494
+ || (proof(join(agentDir(role.name), '.config-path')).trim() || input.configPath) !== configPath)
495
+ throw Error('LEGACY_PERMANENT_STATE_CHANGED');
496
+ // Native job absence alone does not prove that a late exiting runner or
497
+ // an escaped process is gone. Block adoption rather than overlap it.
498
+ await assertLegacyProcessAbsent(role.name, receipt.legacyBinPath ?? receipt.binPath, input);
499
+ await input.register(role.name, receipt.desired, configPath);
500
+ receipt.phase = 'registered';
501
+ replaceFileAtomically(receiptPath, JSON.stringify(receipt));
502
+ return { name: role.name, status: 'migrated' };
503
+ });
504
+ // No receipt writes, native changes or registrations until every requested
505
+ // role passes inspection. Revalidate under its lock immediately before use.
506
+ const checkedExecute = async (role, dryRun) => {
507
+ try {
508
+ return await execute(role, dryRun);
509
+ }
510
+ catch (error) {
511
+ if (error.code)
512
+ throw Error(`LEGACY_PERMANENT_PROOF_UNAVAILABLE: ${role.name}: ${error.code}`);
513
+ throw error;
514
+ }
515
+ };
516
+ for (const role of input.roles)
517
+ await checkedExecute(role, true);
518
+ if (!input.prepareParent)
519
+ throw Error('LEGACY_PERMANENT_PARENT_PREPARATION_REQUIRED');
520
+ await input.prepareParent([...gates.values()]);
521
+ for (const role of input.roles)
522
+ results.push(await checkedExecute(role, false));
523
+ return results;
524
+ }
@@ -0,0 +1,15 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import { type Exec } from '../exec.js';
3
+ import { type FleetMember } from './catalog.js';
4
+ export interface ManagerDeps {
5
+ exec?: Exec;
6
+ now?(): number;
7
+ sleep?(ms: number): Promise<void>;
8
+ shouldStop?(): boolean;
9
+ log?(message: string): void;
10
+ spawnChild?(member: FleetMember, generation: string): ChildProcess;
11
+ }
12
+ /** One parent owns process creation for every kind of Fleet member. */
13
+ export declare function runFleetManager(entrypoint: string, deps?: ManagerDeps): Promise<void>;
14
+ /** Workers cannot outlive their parent or overlap another generation of this member. */
15
+ export declare function runManagedMember(key: string, generation: string): Promise<void>;