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

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,116 @@
1
+ import { lstatSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { parse, stringify } from 'yaml';
5
+ import { attachOursClient } from '@ours.network/sdk/client';
6
+ import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
7
+ import { binderKey } from '../agent-ours/state.js';
8
+ import { readClientProfile } from '../client-profile.js';
9
+ import { readProvenance } from '../creation.js';
10
+ import { agentDir, stateRoot } from '../paths.js';
11
+ import { readTempSupervisor, tempSupervisorLiveness, TEMP_SUPERVISOR_FILE } from '../temp-lifecycle.js';
12
+ import { getTask } from './task-state.js';
13
+ import { getRoomRecord, updateMemberStartup } from './room-state.js';
14
+ import { assertMemberNotPermanent } from './member-ownership.js';
15
+ function bounded(path) {
16
+ const stat = lstatSync(path);
17
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024)
18
+ throw Error('LEGACY_TASK_MEMBER_UNSAFE_PROOF');
19
+ return readFileSync(path, 'utf8');
20
+ }
21
+ async function verifyIdentity(name, cid, env) {
22
+ const profile = readClientProfile(env);
23
+ const client = await attachOursClient({ endpoint: profile.endpoint, expectedInstanceId: profile.expectedInstanceId,
24
+ credentialPath: profile.credentialPath, sessionMode: 'external', env: {}, leaseToken: `task-migration-${randomUUID()}` });
25
+ try {
26
+ const identities = await client.listIdentities();
27
+ const found = identities.find(value => value.name === name);
28
+ if (!found || !('cid' in found) || found.cid.toLowerCase() !== cid.toLowerCase())
29
+ throw Error('LEGACY_TASK_MEMBER_IDENTITY_MISMATCH');
30
+ }
31
+ finally {
32
+ try {
33
+ await client.releaseLease();
34
+ }
35
+ finally {
36
+ await client.close();
37
+ }
38
+ }
39
+ }
40
+ /** Caller holds task and room operation locks. No service is started here.
41
+ * Running legacy supervisors remain untouched; stopped exact state receives
42
+ * only ownership metadata, preserving launch, identity and conversation.
43
+ */
44
+ export async function adoptLegacyTaskMember(owner, name, deps = {}) {
45
+ return withFileLock(join(stateRoot(), 'locks', 'temp-supervisors', encodeURIComponent(name)), async () => {
46
+ assertMemberNotPermanent(name);
47
+ const dir = agentDir(name, true), task = getTask(owner.taskId), room = getRoomRecord(owner.roomId);
48
+ const seat = room?.member_seats.find(value => value.role_name === name);
49
+ const supervisor = readTempSupervisor(dir);
50
+ const rolePath = join(dir, 'role.yaml'), roleBytes = bounded(rolePath);
51
+ const role = parse(roleBytes);
52
+ const startup = role?.roomMemberStartup, provenance = readProvenance(dir);
53
+ if (task.deletion || task.terminal_intent || !['provisioning', 'active', 'review'].includes(task.state)
54
+ || !room || room.close || !['provisioning', 'active'].includes(room.state) || room.task_id !== owner.taskId
55
+ || task.room_id !== owner.roomId || room.room_identity_cid !== owner.roomIdentityCid
56
+ || task.room_identity_cid !== owner.roomIdentityCid || !seat || seat.retirement
57
+ || !supervisor || supervisor.role !== name || seat.launch?.launch_id !== supervisor.launchId
58
+ || seat.launch.action_id !== owner.creationActionId || provenance?.creationActionId !== owner.creationActionId
59
+ || provenance.role !== name || role.name !== name || role.identity !== name
60
+ || startup?.identity_name !== name || startup.room_id !== owner.roomId
61
+ || startup.room_identity_cid !== owner.roomIdentityCid || startup.invite_id !== seat.invite_id
62
+ || startup.role !== seat.cowork_role || startup.invite !== ''
63
+ || (startup.task_id !== undefined && startup.task_id !== owner.taskId)
64
+ || (room.workspace && (role.cwd !== room.workspace.path || JSON.stringify(startup.workspace) !== JSON.stringify(room.workspace)))
65
+ || bounded(join(dir, '.identity')).trim() !== name)
66
+ throw Error('LEGACY_TASK_MEMBER_OWNERSHIP_MISMATCH');
67
+ if (supervisor.taskOwner) {
68
+ if (Object.entries(owner).some(([key, value]) => supervisor.taskOwner[key] !== value)
69
+ || startup.task_id !== owner.taskId)
70
+ throw Error('LEGACY_TASK_MEMBER_OWNER_MISMATCH');
71
+ if (supervisor.kind !== 'detached' && process.env.OURS_FLEET_SUPERVISOR !== 'none')
72
+ updateMemberStartup(owner.roomId, name, { launch: { ...seat.launch, task_supervised: true } });
73
+ return 'already-durable';
74
+ }
75
+ const live = await (deps.liveness ?? tempSupervisorLiveness)(dir);
76
+ if (live === 'running')
77
+ return 'running-legacy';
78
+ if (live !== 'stopped')
79
+ throw Error('LEGACY_TASK_MEMBER_LIVENESS_UNKNOWN');
80
+ const env = { ...process.env, ...role.env };
81
+ const profile = readClientProfile(env);
82
+ const privateDir = join(stateRoot(), 'private-ours', binderKey(profile.expectedInstanceId, name));
83
+ const state = JSON.parse(bounded(join(privateDir, 'state.json')));
84
+ const instance = JSON.parse(bounded(join(privateDir, 'instance.json')));
85
+ const externalOwner = JSON.parse(bounded(join(privateDir, 'owner.json')));
86
+ const launch = JSON.parse(bounded(join(stateRoot(), 'private-ours', 'launches', binderKey('temporary', name) + '.json')));
87
+ if (!seat.identity_cid || state.name !== name || state.daemon !== profile.expectedInstanceId
88
+ || state.lifetime !== 'temporary' || state.action !== owner.creationActionId
89
+ || state.cid?.toLowerCase() !== seat.identity_cid.toLowerCase()
90
+ || !['READY', 'SERVING', 'RECOVERING'].includes(state.phase)
91
+ || instance.role !== name || instance.temporary !== true || instance.instance !== state.instance
92
+ || externalOwner.instance !== state.instance || typeof externalOwner.token !== 'string' || !externalOwner.token
93
+ || state.room?.id !== owner.roomId || state.room.cid !== owner.roomIdentityCid
94
+ || state.room.agentCid.toLowerCase() !== seat.identity_cid.toLowerCase() || state.room.action !== seat.invite_id
95
+ || launch.role !== name || launch.identity !== name || launch.action !== owner.creationActionId)
96
+ throw Error('LEGACY_TASK_MEMBER_RUNTIME_NOT_RESUMABLE');
97
+ const sessionPath = join(dir, role.session === 'codex-app-server' ? '.session-id' : '.acp-session-id');
98
+ if (!bounded(sessionPath).trim())
99
+ throw Error('LEGACY_TASK_MEMBER_CONVERSATION_MISSING');
100
+ if (deps.verifyIdentity)
101
+ await deps.verifyIdentity(name, seat.identity_cid);
102
+ else
103
+ await verifyIdentity(name, seat.identity_cid, env);
104
+ assertMemberNotPermanent(name);
105
+ if (bounded(rolePath) !== roleBytes || JSON.stringify(readTempSupervisor(dir)) !== JSON.stringify(supervisor))
106
+ throw Error('LEGACY_TASK_MEMBER_STATE_CHANGED');
107
+ // Crash after this write is healed by retrying the same stopped adoption.
108
+ startup.task_id = owner.taskId;
109
+ replaceFileAtomically(rolePath, stringify(role));
110
+ if (supervisor.kind !== 'detached' && process.env.OURS_FLEET_SUPERVISOR !== 'none')
111
+ updateMemberStartup(owner.roomId, name, { launch: { ...seat.launch, task_supervised: true } });
112
+ deps.beforeOwnerCommit?.();
113
+ replaceFileAtomically(join(dir, TEMP_SUPERVISOR_FILE), JSON.stringify({ ...supervisor, taskOwner: owner }, null, 2) + '\n');
114
+ return 'adopted';
115
+ });
116
+ }
@@ -0,0 +1,18 @@
1
+ import { readRoomReadiness } from '../agent-ours/service.js';
2
+ import { controlRequest } from '../session/control.js';
3
+ import { readTempSupervisor, tempSupervisorLiveness } from '../temp-lifecycle.js';
4
+ import type { CoworkAdapter } from './cowork-adapter.js';
5
+ import type { RoomOrchestrationRecord, TaskRecord } from './types.js';
6
+ export type TaskReadinessIssue = {
7
+ state: 'degraded' | 'unknown';
8
+ reason: string;
9
+ member?: string;
10
+ };
11
+ export interface LiveReadinessDeps {
12
+ supervisor?: typeof readTempSupervisor;
13
+ liveness?: typeof tempSupervisorLiveness;
14
+ readiness?: typeof readRoomReadiness;
15
+ control?: typeof controlRequest;
16
+ }
17
+ /** Read-only observation. A failed probe never grants authority to replace a seat. */
18
+ export declare function taskLiveReadiness(task: TaskRecord, room: RoomOrchestrationRecord | undefined, cowork: Pick<CoworkAdapter, 'getRoom'>, deps?: LiveReadinessDeps): Promise<TaskReadinessIssue | undefined>;
@@ -0,0 +1,85 @@
1
+ import { realExec } from '../exec.js';
2
+ import { agentDir } from '../paths.js';
3
+ import { readRoomReadiness } from '../agent-ours/service.js';
4
+ import { controlRequest } from '../session/control.js';
5
+ import { readTempSupervisor, tempSupervisorLiveness } from '../temp-lifecycle.js';
6
+ /** Read-only observation. A failed probe never grants authority to replace a seat. */
7
+ export async function taskLiveReadiness(task, room, cowork, deps = {}) {
8
+ const degraded = (reason, member) => ({ state: 'degraded', reason, member });
9
+ const unknown = (reason, member) => ({ state: 'unknown', reason, member });
10
+ if (!room || task.state !== 'active' || task.room_id !== room.room_id || room.state !== 'active' || room.close || task.terminal_intent
11
+ || room.task_id !== task.task_id || !room.room_identity_cid
12
+ || task.room_identity_cid !== room.room_identity_cid)
13
+ return degraded('room_record_mismatch');
14
+ const baseExpected = room.template_snapshot?.members.reduce((n, member) => n + member.count, 0);
15
+ const expected = baseExpected === undefined ? undefined : baseExpected + (room.additional_member_templates ?? []).reduce((n, t) => n + t.members.reduce((m, s) => m + s.count, 0), 0);
16
+ if (expected === undefined || room.member_seats.length !== expected
17
+ || new Set(room.member_seats.map(seat => seat.role_name)).size !== expected
18
+ || new Set(room.member_seats.map(seat => seat.identity_cid)).size !== expected)
19
+ return degraded('member_roster_mismatch');
20
+ let remote;
21
+ try {
22
+ remote = await cowork.getRoom(room.room_id);
23
+ }
24
+ catch {
25
+ return unknown('room_probe_unavailable');
26
+ }
27
+ if (!remote || remote.room_id !== room.room_id || remote.identity_cid !== room.room_identity_cid
28
+ || remote.state !== 'active')
29
+ return degraded('room_identity_or_state_mismatch');
30
+ if (room.owner_seat_cid && remote.seats.filter(seat => seat.identity_cid.toLowerCase() === room.owner_seat_cid.toLowerCase() && seat.seat_state === 'active').length !== 1)
31
+ return degraded('owner_seat_missing');
32
+ for (const seat of room.member_seats) {
33
+ const member = seat.role_name;
34
+ if (!/^[A-Za-z0-9_-]+$/.test(member) || seat.seat_state !== 'active' || !seat.identity_cid || !seat.invite_id
35
+ || seat.launch?.state !== 'launched' || !seat.launch.launch_id || seat.retirement)
36
+ return degraded('member_record_incomplete', member);
37
+ const matches = remote.seats.filter(item => item.display_name === member && item.seat_state !== 'removed');
38
+ if (matches.length !== 1 || matches[0].identity_cid !== seat.identity_cid
39
+ || matches[0].invite_id !== seat.invite_id || matches[0].role !== seat.cowork_role
40
+ || matches[0].seat_state !== 'active')
41
+ return degraded('member_seat_mismatch', member);
42
+ const dir = agentDir(member, true);
43
+ try {
44
+ const supervisor = (deps.supervisor ?? readTempSupervisor)(dir);
45
+ if (!supervisor)
46
+ return unknown('supervisor_record_missing', member);
47
+ if (supervisor.role !== member || supervisor.launchId !== seat.launch.launch_id)
48
+ return degraded('supervisor_launch_mismatch', member);
49
+ const live = await (deps.liveness ?? tempSupervisorLiveness)(dir, {
50
+ exec: (cmd, args, opts) => realExec(cmd, args, { ...opts, timeout: 2_000 }),
51
+ });
52
+ if (live === 'stopped')
53
+ return degraded('supervisor_stopped', member);
54
+ if (live !== 'running')
55
+ return unknown('supervisor_liveness_unknown', member);
56
+ const ready = (deps.readiness ?? readRoomReadiness)(room.room_identity_cid, member);
57
+ if (!ready || ready.room !== room.room_id || ready.invite !== seat.invite_id || ready.cid !== seat.identity_cid)
58
+ return degraded('member_readiness_mismatch', member);
59
+ }
60
+ catch {
61
+ return unknown('supervisor_probe_unavailable', member);
62
+ }
63
+ try {
64
+ const response = await (deps.control ?? controlRequest)(dir, { command: 'status', controller: false }, 2_000);
65
+ if (!response.ok)
66
+ return unknown('control_unavailable', member);
67
+ const status = response.result;
68
+ if (status?.alive === false || status?.readiness === 'failed')
69
+ return degraded('agent_not_ready', member);
70
+ if (status?.alive !== true || !['idle', 'running', 'awaiting_permission'].includes(status.readiness ?? ''))
71
+ return unknown('agent_readiness_unknown', member);
72
+ const after = (deps.supervisor ?? readTempSupervisor)(dir);
73
+ if (!after || after.role !== member || after.launchId !== seat.launch.launch_id)
74
+ return degraded('supervisor_launch_changed', member);
75
+ }
76
+ catch {
77
+ return unknown('control_unavailable', member);
78
+ }
79
+ }
80
+ const memberCids = new Set(room.member_seats.map(seat => seat.identity_cid));
81
+ const memberRoles = new Set(room.member_seats.map(seat => seat.cowork_role));
82
+ if (remote.seats.some(seat => seat.seat_state === 'active' && memberRoles.has(seat.role)
83
+ && !memberCids.has(seat.identity_cid) && seat.identity_cid.toLowerCase() !== room.owner_seat_cid?.toLowerCase()))
84
+ return degraded('untracked_member_seats');
85
+ }
@@ -0,0 +1,5 @@
1
+ /** A room seat is never authority to retire a standalone persistent agent.
2
+ * Check both role-name collisions and a permanent role using the seat's
3
+ * identity name. Missing legacy task state must not bypass this fence.
4
+ */
5
+ export declare function assertMemberNotPermanent(name: string): void;
@@ -0,0 +1,40 @@
1
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { agentDir, agentsRoot } from '../paths.js';
4
+ /** A room seat is never authority to retire a standalone persistent agent.
5
+ * Check both role-name collisions and a permanent role using the seat's
6
+ * identity name. Missing legacy task state must not bypass this fence.
7
+ */
8
+ export function assertMemberNotPermanent(name) {
9
+ if (!/^[a-zA-Z0-9_-]{1,120}$/.test(name))
10
+ throw new Error('Invalid retirement member name');
11
+ if (existsSync(agentDir(name)))
12
+ throw new Error(`room member '${name}' conflicts with permanent agent state; refusing retirement`);
13
+ let entries;
14
+ try {
15
+ entries = readdirSync(agentsRoot(), { withFileTypes: true });
16
+ }
17
+ catch (error) {
18
+ if (error.code === 'ENOENT')
19
+ return;
20
+ throw error;
21
+ }
22
+ for (const entry of entries) {
23
+ if (!entry.isDirectory() || entry.isSymbolicLink())
24
+ continue;
25
+ const path = join(agentsRoot(), entry.name, '.identity');
26
+ let stat;
27
+ try {
28
+ stat = lstatSync(path);
29
+ }
30
+ catch (error) {
31
+ if (error.code === 'ENOENT')
32
+ continue;
33
+ throw error;
34
+ }
35
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024)
36
+ throw new Error('Permanent identity ownership proof is unsafe; refusing member retirement');
37
+ if (readFileSync(path, 'utf8').trim() === name)
38
+ throw new Error(`room member '${name}' identity belongs to permanent agent '${entry.name}'; refusing retirement`);
39
+ }
40
+ }
@@ -6,6 +6,7 @@ export interface RoomTaskMember {
6
6
  }
7
7
  /** Build the complete task text Fleet places directly in a temporary member's briefing. */
8
8
  export declare function buildRoomMemberTask(input: {
9
+ workspace?: string;
9
10
  taskId?: string;
10
11
  roomId: string;
11
12
  roomIdentityCid: string;
@@ -7,6 +7,8 @@ export function buildRoomMemberTask(input) {
7
7
  `Fleet Task ${input.taskId ?? '(standalone)'} — ${member.cowork_role} in room ${input.roomId}`,
8
8
  '',
9
9
  ];
10
+ if (input.workspace)
11
+ lines.push(`Task-owned workspace: ${input.workspace}`, 'Start and keep all execution artifacts, dependency checkouts, and Git worktrees inside this workspace.', 'Clone repositories inside this workspace before adding worktrees; never attach to an external Git common directory.', 'Use distinct per-agent branches/directories for concurrent edits. Configured cwd is source context, not artifact ownership.', 'Completion, cancellation and room retirement retain artifacts. Only explicit owner deletion removes them.', 'Do not delete the workspace or its ownership marker yourself.', '');
10
12
  if (input.goal)
11
13
  lines.push(`Goal: ${input.goal}`);
12
14
  if (input.brief)
@@ -3,6 +3,7 @@ import type { RoomOrchestrationRecord, TemplateSnapshot } from './types.js';
3
3
  import { type FleetConfig } from '../config.js';
4
4
  export declare function getBinPath(): string;
5
5
  export interface ProvisionMembersInput {
6
+ configPath?: string;
6
7
  cfg: FleetConfig;
7
8
  cowork: CoworkAdapter;
8
9
  roomId: string;
@@ -12,6 +13,8 @@ export interface ProvisionMembersInput {
12
13
  brief?: string;
13
14
  goal?: string;
14
15
  startupWait?: Partial<StartupWaitPolicy>;
16
+ /** Append only the supplied new slots to an already active room. */
17
+ append?: boolean;
15
18
  }
16
19
  export interface StartupWaitPolicy {
17
20
  timeoutMs: number;
@@ -1,4 +1,5 @@
1
1
  import { readRoomReadiness } from '../agent-ours/service.js';
2
+ import { ensureWorkspace, validateWorkspace } from './workspace.js';
2
3
  import { createHash, randomUUID } from 'node:crypto';
3
4
  import { existsSync, readFileSync, realpathSync } from 'node:fs';
4
5
  import { join } from 'node:path';
@@ -6,7 +7,8 @@ import { parse } from 'yaml';
6
7
  import { advanceSaga, setSagaError, updateMemberSeats, updateMemberStartup, activateRoom, getRoomRecord, } from './room-state.js';
7
8
  import { activateTask, updateTaskMembers, blockTask, unblockTask, getTask, } from './task-state.js';
8
9
  import { storedRoomLaunchPolicy } from './types.js';
9
- import { spawnTemp } from '../spawn.js';
10
+ import { spawnTemp, resumeTaskSupervisor } from '../spawn.js';
11
+ import { adoptLegacyTaskMember } from './legacy-task-member.js';
10
12
  import { effectivePermissionMode } from '../permissions.js';
11
13
  import { selectionOrigin, summarizeResolvedLaunch, } from '../lifecycle-summary.js';
12
14
  import { canonicalJson } from '../canonical-json.js';
@@ -14,7 +16,7 @@ import { readLaunchSnapshot, redactLaunchDefinition } from './launch-snapshot.js
14
16
  import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from '../fleet-proxy.js';
15
17
  import { controlRequest } from '../session/control.js';
16
18
  import { SessionControlError } from '../session/types.js';
17
- import { closeManagedRoom, roomCloseLockPath } from './close.js';
19
+ import { closeManagedRoom, roomCloseLockPath, CLOSE_LOCK_STALE_MS } from './close.js';
18
20
  import { withFileLock } from '../atomic-file.js';
19
21
  import { TASK_OPERATION_LOCK_STALE_MS, taskOperationLockPath } from './terminal.js';
20
22
  import { TaskStateError, taskDeletionState } from './task-state.js';
@@ -116,6 +118,7 @@ function settingsFor(member, cfg, sealed) {
116
118
  }
117
119
  function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCid, anonymous) {
118
120
  return buildRoomMemberTask({
121
+ workspace: getRoomRecord(input.roomId)?.workspace?.path,
119
122
  taskId: input.taskId,
120
123
  roomId: input.roomId,
121
124
  roomIdentityCid,
@@ -129,7 +132,7 @@ function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCi
129
132
  cowork_role: member.coworkRole,
130
133
  persona: settings.persona,
131
134
  },
132
- roster: members.map(candidate => ({
135
+ roster: [...(input.append ? (getRoomRecord(input.roomId)?.member_seats ?? []).filter(seat => !members.some(m => m.name === seat.role_name)).map(seat => ({ name: seat.role_name, coworkRole: seat.cowork_role })) : []), ...members].map(candidate => ({
133
136
  role_name: candidate.name,
134
137
  cowork_role: candidate.coworkRole,
135
138
  })),
@@ -142,6 +145,10 @@ function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid,
142
145
  try {
143
146
  const role = parse(readFileSync(`${dir}/role.yaml`, 'utf8'));
144
147
  const startup = role.roomMemberStartup;
148
+ const workspace = getRoomRecord(roomId)?.workspace;
149
+ if (workspace && (role.cwd !== workspace.path
150
+ || JSON.stringify(startup?.workspace) !== JSON.stringify(workspace)))
151
+ return false;
145
152
  return role.identity === member.name
146
153
  && startup?.room_id === roomId
147
154
  && startup.room_identity_cid === roomIdentityCid
@@ -201,6 +208,22 @@ async function retainRunningLaunch(input) {
201
208
  } });
202
209
  return true;
203
210
  }
211
+ if (supervisor.taskOwner) {
212
+ await resumeTaskSupervisor(member.name, provision.binPath);
213
+ updateMemberStartup(provision.roomId, member.name, { launch: {
214
+ ...retainedLaunch, state: 'launched', launch_id: supervisor.launchId,
215
+ presentation: retainedLaunch.presentation ?? presentationFromStatePath(dir, settings, member.coworkRole),
216
+ updated_at: new Date().toISOString(),
217
+ } });
218
+ return true;
219
+ }
220
+ if (provision.taskId) {
221
+ const adopted = await adoptLegacyTaskMember({ taskId: provision.taskId, roomId: provision.roomId,
222
+ roomIdentityCid, creationActionId: retainedLaunch.action_id }, member.name);
223
+ if (adopted !== 'running-legacy')
224
+ await resumeTaskSupervisor(member.name, provision.binPath);
225
+ return true;
226
+ }
204
227
  await secureStoppedTempArchive(member.name, supervisor.launchId);
205
228
  updateMemberStartup(provision.roomId, member.name, { launch: {
206
229
  ...retainedLaunch, state: 'stopped', launch_id: supervisor.launchId,
@@ -209,6 +232,8 @@ async function retainRunningLaunch(input) {
209
232
  return false;
210
233
  }
211
234
  if (seat.launch?.state === 'launched' && !existsSync(dir)) {
235
+ if (provision.taskId)
236
+ throw Error(`retained task member ${member.name} is missing; explicit reconciliation required`);
212
237
  if (!seat.launch.launch_id || !seat.launch.action_id)
213
238
  throw new Error(`missing durable launch identity for disappeared ${member.name}`);
214
239
  const archive = await secureStoppedTempArchive(member.name, seat.launch.launch_id);
@@ -234,35 +259,16 @@ async function retainRunningLaunch(input) {
234
259
  }
235
260
  return false;
236
261
  }
237
- /**
238
- * Member-launch publication window: for a task-bound room, the durable launch
239
- * intent, spawn, and launch record publish under the common task-operation
240
- * lock so an accepted deletion linearizes strictly before (bounded abort — no
241
- * spawn happens) or strictly after (the seat's launch record is visible to
242
- * deletion retirement). The lock is held per member only, never across
243
- * seat-wait loops.
244
- */
245
262
  async function launchMember(input) {
246
- const taskId = input.provision.taskId;
247
- const launch = () => withFileLock(roomCloseLockPath(input.provision.roomId), () => {
248
- assertProvisioningOpen(input.provision);
249
- return launchMemberUnlocked(input);
250
- }, {}, TASK_OPERATION_LOCK_STALE_MS);
251
- if (!taskId)
252
- return launch();
253
- return withFileLock(taskOperationLockPath(taskId), () => {
254
- if (taskDeletionState(taskId) !== 'none')
255
- throw new Error(`task ${taskId} is pending deletion; aborting member launch for ${input.member.name}`);
256
- return launch();
257
- }, {}, TASK_OPERATION_LOCK_STALE_MS);
258
- }
259
- async function launchMemberUnlocked(input) {
263
+ // provisionMembers holds the task and room lifecycle locks through every launch.
264
+ assertProvisioningOpen(input.provision);
260
265
  const { provision, member, settings, startup } = input;
261
266
  const seat = getRoomRecord(provision.roomId).member_seats
262
267
  .find(candidate => candidate.role_name === member.name);
263
268
  const actionId = randomUUID();
264
269
  let effectiveActionId = actionId;
265
270
  const attempt = (seat.launch?.attempt ?? 0) + 1;
271
+ const taskSupervised = Boolean(provision.taskId) && process.env.OURS_FLEET_SUPERVISOR !== 'none';
266
272
  const taskSha = sha256Text(startup.task);
267
273
  const effectiveAgentDefinition = structuredClone(settings.definition);
268
274
  const { projection: agentDefinition, fingerprint: agentFingerprint } = launchDefinition(effectiveAgentDefinition);
@@ -270,6 +276,7 @@ async function launchMemberUnlocked(input) {
270
276
  ? process.env[FLEET_PROXY_CALLER_ENV] : undefined;
271
277
  updateMemberStartup(provision.roomId, member.name, { launch: {
272
278
  state: 'intent', attempt, action_id: actionId, mission_sha256: taskSha,
279
+ ...(taskSupervised ? { task_supervised: true } : {}),
273
280
  agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
274
281
  agent_template: settings.template, agent_template_hash: settings.templateHash,
275
282
  ...(proxyCaller ? { caller_role: proxyCaller } : {}),
@@ -278,6 +285,7 @@ async function launchMemberUnlocked(input) {
278
285
  try {
279
286
  const launched = await spawnRoomMember({
280
287
  name: member.name,
288
+ configPath: provision.configPath,
281
289
  temp: true,
282
290
  identity: member.name,
283
291
  agentDefinition: settings.definition,
@@ -293,6 +301,7 @@ async function launchMemberUnlocked(input) {
293
301
  if (launched.creationActionId !== actionId) {
294
302
  updateMemberStartup(provision.roomId, member.name, { launch: {
295
303
  state: 'intent', attempt, action_id: launched.creationActionId,
304
+ ...(taskSupervised ? { task_supervised: true } : {}),
296
305
  mission_sha256: taskSha, agent_definition: agentDefinition,
297
306
  agent_fingerprint: agentFingerprint,
298
307
  agent_template: settings.template, agent_template_hash: settings.templateHash,
@@ -310,6 +319,7 @@ async function launchMemberUnlocked(input) {
310
319
  : presentationFromStatePath(launchedDir, settings, member.coworkRole);
311
320
  updateMemberStartup(provision.roomId, member.name, { launch: {
312
321
  state: 'launched', attempt, action_id: launched.creationActionId, mission_sha256: taskSha,
322
+ ...(taskSupervised ? { task_supervised: true } : {}),
313
323
  agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
314
324
  agent_template: settings.template, agent_template_hash: settings.templateHash,
315
325
  presentation,
@@ -320,6 +330,7 @@ async function launchMemberUnlocked(input) {
320
330
  catch (error) {
321
331
  updateMemberStartup(provision.roomId, member.name, { launch: {
322
332
  state: 'failed', attempt, action_id: effectiveActionId, mission_sha256: taskSha,
333
+ ...(taskSupervised ? { task_supervised: true } : {}),
323
334
  agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
324
335
  agent_template: settings.template, agent_template_hash: settings.templateHash,
325
336
  ...(proxyCaller ? { caller_role: proxyCaller } : {}),
@@ -349,6 +360,8 @@ function reconcileMemberSeats(roomId, members, observed, ownerSeatCid) {
349
360
  && seat.seat_state === 'active');
350
361
  const seats = current.member_seats.map(seat => {
351
362
  const member = members.find(candidate => candidate.name === seat.role_name);
363
+ if (!member)
364
+ return seat;
352
365
  const readiness = readRoomReadiness(current.room_identity_cid, member.name);
353
366
  if (!readiness || readiness.room !== roomId || readiness.invite !== seat.invite_id) {
354
367
  complete = false;
@@ -383,9 +396,33 @@ function assertProvisioningOpen(input) {
383
396
  throw new Error(`room ${input.roomId} is ${room.state}; refusing to provision members`);
384
397
  }
385
398
  export async function provisionMembers(input) {
399
+ // Same lock order as terminal/deletion settlement. Serialize concurrent retries
400
+ // and standalone retirement as well as member publication. Seat waits are bounded.
401
+ const run = () => withFileLock(roomCloseLockPath(input.roomId), () => {
402
+ assertProvisioningOpen(input);
403
+ if (input.append) {
404
+ const task = input.taskId ? getTask(input.taskId) : undefined;
405
+ const room = getRoomRecord(input.roomId);
406
+ if (!task || !['active', 'review'].includes(task.state) || room?.state !== 'active'
407
+ || room.task_id !== task.task_id || room.room_identity_cid !== task.room_identity_cid)
408
+ throw new Error('Adding an agent requires the exact active task room');
409
+ const names = expandMembers(input.template, shortId(task.task_id)).map(m => m.name);
410
+ if (room.member_seats.some(seat => names.includes(seat.role_name)))
411
+ throw new Error('Member slot is already recorded; inspect its existing launch');
412
+ if (room.member_seats.some(seat => seat.seat_state !== 'active'))
413
+ throw new Error('Existing member admission must finish before adding an agent');
414
+ }
415
+ if (!getRoomRecord(input.roomId))
416
+ throw new Error(`room ${input.roomId} is missing; refusing provisioning`);
417
+ return provisionMembersUnlocked(input);
418
+ }, {}, CLOSE_LOCK_STALE_MS);
419
+ return input.taskId
420
+ ? withFileLock(taskOperationLockPath(input.taskId), run, {}, TASK_OPERATION_LOCK_STALE_MS)
421
+ : run();
422
+ }
423
+ async function provisionMembersUnlocked(input) {
386
424
  const { cfg, cowork, roomId, taskId, template } = input;
387
- assertProvisioningOpen(input);
388
- // Deletion-epoch pre-check; each member launch re-checks under the lock.
425
+ // The common task-operation lock protects this epoch through all member launches.
389
426
  if (taskId && taskDeletionState(taskId) !== 'none')
390
427
  throw new Error(`task ${taskId} is pending deletion; refusing to provision members`);
391
428
  const prefix = taskId ? shortId(taskId) : `room-${shortId(roomId)}`;
@@ -397,6 +434,12 @@ export async function provisionMembers(input) {
397
434
  const existing = getRoomRecord(roomId);
398
435
  if (!existing?.room_identity_cid)
399
436
  throw new Error(`room ${roomId} has no pinned room identity CID`);
437
+ if (existing.workspace) {
438
+ validateWorkspace(existing.workspace, taskId ? 'task' : 'room', taskId ?? roomId);
439
+ ensureWorkspace(existing.workspace);
440
+ for (const setting of settings.values())
441
+ setting.definition.cwd = existing.workspace.path;
442
+ }
400
443
  const roomIdentityCid = existing.room_identity_cid;
401
444
  const ownerSeatCid = existing.owner_seat_cid ?? null;
402
445
  const roomPolicy = storedRoomLaunchPolicy(existing.room_policy);
@@ -404,22 +447,23 @@ export async function provisionMembers(input) {
404
447
  const resuming = members.length > 0
405
448
  && members.every(member => persistedNames.has(member.name));
406
449
  if (!resuming) {
407
- advanceSaga(roomId, 'create_members', 3);
408
- updateMemberSeats(roomId, members.map(member => {
409
- const memberSettings = settings.get(member.name);
410
- const evidence = launchDefinition(memberSettings.definition);
411
- return ({
412
- role_name: member.name,
413
- slot: member.slot,
414
- cowork_role: member.coworkRole,
415
- seat_state: 'pending',
416
- launch: { state: 'pending', attempt: 0,
417
- agent_definition: evidence.projection, agent_fingerprint: evidence.fingerprint,
418
- agent_template: memberSettings.template,
419
- agent_template_hash: memberSettings.templateHash,
420
- updated_at: new Date().toISOString() },
421
- });
422
- }));
450
+ if (!input.append)
451
+ advanceSaga(roomId, 'create_members', 3);
452
+ updateMemberSeats(roomId, [...(input.append ? existing.member_seats : []), ...members.map(member => {
453
+ const memberSettings = settings.get(member.name);
454
+ const evidence = launchDefinition(memberSettings.definition);
455
+ return ({
456
+ role_name: member.name,
457
+ slot: member.slot,
458
+ cowork_role: member.coworkRole,
459
+ seat_state: 'pending',
460
+ launch: { state: 'pending', attempt: 0,
461
+ agent_definition: evidence.projection, agent_fingerprint: evidence.fingerprint,
462
+ agent_template: memberSettings.template,
463
+ agent_template_hash: memberSettings.templateHash,
464
+ updated_at: new Date().toISOString() },
465
+ });
466
+ })], input.append ? template : undefined);
423
467
  }
424
468
  else {
425
469
  for (const member of members) {
@@ -437,7 +481,8 @@ export async function provisionMembers(input) {
437
481
  now: input.startupWait?.now ?? Date.now,
438
482
  sleep: input.startupWait?.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms))),
439
483
  };
440
- advanceSaga(roomId, 'join_role_groups', 4);
484
+ if (!input.append)
485
+ advanceSaga(roomId, 'join_role_groups', 4);
441
486
  try {
442
487
  const initialRoom = await cowork.recoverRoom(roomId);
443
488
  assertCoworkRoomPolicy(initialRoom, roomPolicy.anonymous);
@@ -482,6 +527,8 @@ export async function provisionMembers(input) {
482
527
  member,
483
528
  settings: settings.get(member.name),
484
529
  startup: {
530
+ ...(taskId ? { task_id: taskId } : {}),
531
+ workspace: existing.workspace,
485
532
  room_id: roomId,
486
533
  room_identity_cid: roomIdentityCid,
487
534
  identity_name: member.name,
@@ -521,7 +568,8 @@ export async function provisionMembers(input) {
521
568
  }
522
569
  throw error;
523
570
  }
524
- advanceSaga(roomId, 'wait_seats', 5);
571
+ if (!input.append)
572
+ advanceSaga(roomId, 'wait_seats', 5);
525
573
  const deadline = policy.now() + policy.timeoutMs;
526
574
  let delay = policy.initialDelayMs;
527
575
  for (;;) {
@@ -531,10 +579,12 @@ export async function provisionMembers(input) {
531
579
  if (reconciled.complete && remote.state === 'active')
532
580
  break;
533
581
  if (policy.now() >= deadline) {
534
- advanceSaga(roomId, 'wait_seats', 5, 'waiting_seats');
582
+ if (!input.append)
583
+ advanceSaga(roomId, 'wait_seats', 5, 'waiting_seats');
535
584
  return getRoomRecord(roomId);
536
585
  }
537
- advanceSaga(roomId, 'wait_seats', 5, 'waiting_seats');
586
+ if (!input.append)
587
+ advanceSaga(roomId, 'wait_seats', 5, 'waiting_seats');
538
588
  await policy.sleep(delay);
539
589
  delay = Math.min(policy.maxDelayMs, Math.max(delay + 1, delay * 2));
540
590
  }
@@ -559,9 +609,11 @@ export async function provisionMembers(input) {
559
609
  };
560
610
  });
561
611
  updateTaskMembers(taskId, taskMembers);
562
- if (getTask(taskId).blocked)
612
+ if (!input.append && getTask(taskId).blocked)
563
613
  unblockTask(taskId);
564
614
  }
615
+ if (input.append)
616
+ return getRoomRecord(roomId);
565
617
  advanceSaga(roomId, 'activate', 6);
566
618
  const record = activateRoom(roomId);
567
619
  if (taskId)
@@ -592,7 +644,7 @@ export async function reconcileExistingTaskMembers(input) {
592
644
  || remote.state !== 'active')
593
645
  throw new Error('Cowork room is not the exact active room');
594
646
  assertCoworkRoomPolicy(remote, storedRoomLaunchPolicy(current.room_policy).anonymous);
595
- if (current.owner_seat_cid && !remote.seats.some(seat => seat.identity_cid === current.owner_seat_cid && seat.seat_state === 'active'))
647
+ if (current.owner_seat_cid && !remote.seats.some(seat => seat.identity_cid.toLowerCase() === current.owner_seat_cid.toLowerCase() && seat.seat_state === 'active'))
596
648
  throw new Error('expected Owner seat is not active');
597
649
  const seats = [];
598
650
  for (const seat of current.member_seats) {
@@ -0,0 +1,19 @@
1
+ import { tempSupervisorLiveness } from '../temp-lifecycle.js';
2
+ import { resumeTaskSupervisor } from '../spawn.js';
3
+ import type { CoworkAdapter } from './cowork-adapter.js';
4
+ export type MemberRecovery = {
5
+ name: string;
6
+ status: 'running' | 'resumed' | 'migration_pending' | 'recovery_reset';
7
+ };
8
+ export interface TaskMemberRecoveryDeps {
9
+ liveness?: typeof tempSupervisorLiveness;
10
+ resume?: typeof resumeTaskSupervisor;
11
+ adopt?(taskId: string, roomId: string, name: string): Promise<void>;
12
+ }
13
+ /** Explicit supported recovery; never creates an identity, seat, invite, or session ID. */
14
+ export declare function recoverTaskMembers(input: {
15
+ taskId: string;
16
+ binPath: string;
17
+ cowork?: Pick<CoworkAdapter, 'getRoom'>;
18
+ deps?: TaskMemberRecoveryDeps;
19
+ }): Promise<MemberRecovery[]>;