@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
@@ -1,8 +1,9 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, rmSync, renameSync } from 'node:fs';
1
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, rmSync, renameSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { replaceFileAtomically } from '../atomic-file.js';
5
5
  import { stateRoot } from '../paths.js';
6
+ import { planWorkspace, ensureWorkspace } from './workspace.js';
6
7
  import { storedRoomLaunchPolicy, TASK_TERMINAL_STATES, TASK_CANCELLABLE_STATES } from './types.js';
7
8
  import { DEFAULT_TASK_LIST_ID, readTaskLists } from './task-lists.js';
8
9
  export const tasksDir = () => join(stateRoot(), 'tasks');
@@ -88,8 +89,77 @@ function writeTask(record) {
88
89
  mkdirSync(tasksDir(), { recursive: true });
89
90
  const persisted = { ...record };
90
91
  delete persisted.list_name;
92
+ const path = taskPath(record.task_id);
93
+ const previous = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : undefined;
94
+ const status = (task) => task.deletion?.status === 'pending' ? 'deleting' : task.blocked ? `${task.state} (blocked)` : task.state;
95
+ // Read pending events under the same task lock as every mutation. Metadata edits
96
+ // and retries preserve the queue; acknowledgement cannot be resurrected by stale input.
97
+ const pending = [...(previous?.notification_events ?? [])];
98
+ if (!previous || status(previous) !== status(record)) {
99
+ const next = status(record), before = previous ? status(previous) : undefined;
100
+ pending.push({ eventId: `task:${record.task_id}:${randomUUID()}`, taskId: record.task_id,
101
+ title: `Task ${!previous ? 'created' : 'updated'}: ${record.title}`.slice(0, 160),
102
+ body: `Task ${record.task_id}: ${before ? `${before} → ` : 'created in '}${next}.`,
103
+ url: `/fleet/tasks/${record.task_id}` });
104
+ }
105
+ if (pending.length)
106
+ persisted.notification_events = pending;
107
+ else
108
+ delete persisted.notification_events;
91
109
  replaceFileAtomically(taskPath(record.task_id), JSON.stringify(persisted, null, 2) + '\n');
92
110
  }
111
+ const retiredNotificationsDir = () => join(tasksDir(), '.notification-retired');
112
+ /** Atomic snapshots contain the events, including intermediate states between polls. */
113
+ export function pendingTaskNotifications(warn = () => { }) {
114
+ const result = [];
115
+ for (const dir of [tasksDir(), retiredNotificationsDir()]) {
116
+ if (!existsSync(dir))
117
+ continue;
118
+ for (const file of readdirSync(dir).filter(file => TASK_ID_PATTERN.test(file.slice(0, -5)) && file.endsWith('.json'))) {
119
+ try {
120
+ const path = join(dir, file), stat = lstatSync(path);
121
+ if (!stat.isFile() || stat.isSymbolicLink())
122
+ throw new Error('invalid notification source');
123
+ const value = JSON.parse(readFileSync(path, 'utf8'));
124
+ const id = file.slice(0, -5);
125
+ // A prepared deletion copy has no authority while the original still exists.
126
+ if (dir === retiredNotificationsDir() && existsSync(taskPath(id)))
127
+ continue;
128
+ if (value.notification_events === undefined)
129
+ continue;
130
+ if (!Array.isArray(value.notification_events) || value.notification_events.some(e => !e
131
+ || typeof e.eventId !== 'string' || !e.eventId.startsWith(`task:${id}:`) || e.eventId.length > 512
132
+ || e.taskId !== id || (e.url !== `/fleet/tasks/${id}` && !(e.eventId === `task:${id}:deleted` && e.url === `/fleet/tasks?deletedTask=${id}`)) || typeof e.title !== 'string' || !e.title.trim() || e.title.length > 160
133
+ || typeof e.body !== 'string' || !e.body.trim() || e.body.length > 512))
134
+ throw new Error('invalid notification events');
135
+ if (value.notification_events.length)
136
+ result.push({ taskId: id, events: value.notification_events });
137
+ }
138
+ catch (error) {
139
+ if (!isNotFoundError(error))
140
+ warn('Task notification record unavailable; other task deliveries continue');
141
+ }
142
+ }
143
+ }
144
+ return result;
145
+ }
146
+ /** Service event-id dedupe covers acceptance followed by a crash before this acknowledgement. */
147
+ export function acknowledgeTaskNotification(id, eventId) {
148
+ assertCanonicalTaskId(id);
149
+ withTaskLock(id, () => {
150
+ for (const path of [taskPath(id), join(retiredNotificationsDir(), `${id}.json`)]) {
151
+ if (!existsSync(path))
152
+ continue;
153
+ const value = JSON.parse(readFileSync(path, 'utf8'));
154
+ if (!value.notification_events?.some(event => event.eventId === eventId))
155
+ continue;
156
+ value.notification_events = value.notification_events.filter(event => event.eventId !== eventId);
157
+ replaceFileAtomically(path, JSON.stringify(value, null, 2) + '\n');
158
+ if (path.startsWith(retiredNotificationsDir() + '/') && !value.notification_events.length)
159
+ unlinkSync(path);
160
+ }
161
+ });
162
+ }
93
163
  function presentTask(record) {
94
164
  const listId = record.list_id ?? DEFAULT_TASK_LIST_ID;
95
165
  const list = readTaskLists().find(item => item.list_id === listId);
@@ -125,21 +195,32 @@ export function assertNoPendingDeletion(task) {
125
195
  throw new TaskStateError(`task ${task.task_id} is pending deletion; run 'ours-fleet task delete ${task.task_id} ${task.task_id}' to retry cleanup`);
126
196
  }
127
197
  }
198
+ function creationPlan(plan, layout) {
199
+ return JSON.stringify([plan?.plan_hash ?? null, storedRoomLaunchPolicy(plan?.room_policy) ?? null,
200
+ layout ? `${layout.name}@${layout.definition_hash}` : null]);
201
+ }
128
202
  export function createTask(input) {
129
203
  const key = input.idempotency_key ?? randomUUID();
130
- const existing = findByIdempotencyKey(key);
204
+ const existing = listTasks({ includeDeleting: true }).find(task => task.idempotency_key === key);
205
+ const requestedPlan = creationPlan(input.execution_plan, input.layout);
131
206
  if (existing) {
132
- const existingPlan = existing.execution_plan?.plan_hash;
133
- const requestedPlan = input.execution_plan?.plan_hash;
134
- const existingPolicy = storedRoomLaunchPolicy(existing.execution_plan?.room_policy);
135
- const requestedPolicy = storedRoomLaunchPolicy(input.execution_plan?.room_policy);
136
- if (existingPlan !== requestedPlan || JSON.stringify(existingPolicy) !== JSON.stringify(requestedPolicy))
207
+ // After a Backlog plan edit the current plan differs from the one created; retries compare the original request.
208
+ if ((existing.creation_plan ?? creationPlan(existing.execution_plan, existing.layout)) !== requestedPlan)
137
209
  throw new TaskStateError(`idempotency key '${key}' was already used with a different execution plan`);
138
- return existing;
210
+ return withTaskLock(existing.task_id, () => {
211
+ const fresh = readTask(existing.task_id);
212
+ assertNoPendingDeletion(fresh);
213
+ if (fresh.workspace)
214
+ ensureWorkspace(fresh.workspace);
215
+ return fresh;
216
+ });
139
217
  }
218
+ if (input.layout && (input.execution_plan || input.no_room || input.room_id))
219
+ throw new TaskStateError('a layout task cannot also use a room template, no-room mode or a room');
140
220
  const state = input.start === false ? 'backlog' : 'provisioning';
221
+ const taskId = generateTaskId();
141
222
  const record = {
142
- task_id: generateTaskId(),
223
+ task_id: taskId,
143
224
  list_id: input.listId ?? DEFAULT_TASK_LIST_ID,
144
225
  title: input.title,
145
226
  brief: input.brief,
@@ -148,14 +229,18 @@ export function createTask(input) {
148
229
  template: input.template,
149
230
  execution_plan: input.execution_plan,
150
231
  no_room: input.no_room || undefined,
232
+ ...(input.layout ? { layout: { name: input.layout.name, run_id: `task-${taskId}`, definition_hash: input.layout.definition_hash } } : {}),
151
233
  room_id: input.room_id,
152
234
  member_roles: [],
153
235
  origin: input.origin,
154
236
  idempotency_key: key,
237
+ creation_plan: requestedPlan,
155
238
  created_at: new Date().toISOString(),
156
239
  started_at: state === 'provisioning' ? new Date().toISOString() : undefined,
157
240
  };
241
+ record.workspace = planWorkspace('task', record.task_id);
158
242
  writeTask(record);
243
+ ensureWorkspace(record.workspace);
159
244
  return readTask(record.task_id);
160
245
  }
161
246
  export function getTask(id) { return withTaskLock(id, () => readTask(id)); }
@@ -505,7 +590,8 @@ function writeDeletionReceiptForIntent(stored) {
505
590
  actor: deletion.actor,
506
591
  original_state: stored.state,
507
592
  room_id: deletion.room_id,
508
- member_count: deletion.members.length,
593
+ member_count: deletion.members.length + (deletion.archived_absences?.length ?? 0),
594
+ ...(deletion.archived_absences?.length ? { archived_absences: deletion.archived_absences } : {}),
509
595
  };
510
596
  replaceFileAtomically(deletionReceiptPath(stored.task_id), JSON.stringify(receipt, null, 2) + '\n');
511
597
  }
@@ -525,14 +611,10 @@ export function ensureTaskDeletionReceipt(id) {
525
611
  writeDeletionReceiptForIntent(stored);
526
612
  });
527
613
  }
528
- /** Record settlement on the receipt; idempotent, tolerant of legacy absence. */
614
+ /** Erase the in-flight receipt; also heals a crash after task unlink. */
529
615
  export function completeTaskDeletionReceipt(id) {
530
- const receipt = readTaskDeletionReceipt(id);
531
- if (!receipt || receipt.settled_at)
532
- return;
533
- receipt.settled_at = new Date().toISOString();
534
- receipt.result = 'deleted';
535
- replaceFileAtomically(deletionReceiptPath(id), JSON.stringify(receipt, null, 2) + '\n');
616
+ assertCanonicalTaskId(id);
617
+ rmSync(deletionReceiptPath(id), { force: true });
536
618
  }
537
619
  /**
538
620
  * Persist the first-wins durable deletion intent. Accepts every lifecycle
@@ -551,8 +633,10 @@ export function beginTaskDeletionIntent(id, actor) {
551
633
  stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
552
634
  }
553
635
  catch (error) {
554
- if (isNotFoundError(error))
636
+ if (isNotFoundError(error)) {
637
+ completeTaskDeletionReceipt(id);
555
638
  return { status: 'already_absent' };
639
+ }
556
640
  throw error;
557
641
  }
558
642
  if (stored.deletion?.status === 'pending') {
@@ -649,6 +733,8 @@ function findCursorForSeat(id, stored, seat) {
649
733
  if (cursor && seat.identity_cid
650
734
  && cursor.identity_cid.toLowerCase() !== seat.identity_cid.toLowerCase())
651
735
  throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' identity CID mismatch between cursor and room seat`);
736
+ if (cursor?.action_id && seat.launch?.action_id && cursor.action_id !== seat.launch.action_id)
737
+ throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' creation ownership proof changed`);
652
738
  return cursor;
653
739
  }
654
740
  /**
@@ -669,11 +755,20 @@ export function upsertTaskDeletionMembersFromSeats(id, seats) {
669
755
  for (const seat of seats) {
670
756
  if (!seat.identity_cid)
671
757
  continue;
672
- if (!findCursorForSeat(id, stored, seat)) {
758
+ const cursor = findCursorForSeat(id, stored, seat);
759
+ if (!cursor) {
673
760
  stored.deletion.members.push({
674
761
  name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now,
762
+ ...(seat.launch?.action_id ? { action_id: seat.launch.action_id } : {}),
763
+ ...(seat.launch?.task_supervised ? { task_supervised: true } : {}),
675
764
  });
676
765
  }
766
+ else {
767
+ if (seat.launch?.action_id)
768
+ cursor.action_id ??= seat.launch.action_id;
769
+ if (seat.launch?.task_supervised)
770
+ cursor.task_supervised = true;
771
+ }
677
772
  }
678
773
  writeTask(stored);
679
774
  return presentTaskLenient(stored);
@@ -688,10 +783,10 @@ export function upsertTaskDeletionMembersFromSeats(id, seats) {
688
783
  *
689
784
  * The room saga is trusted for phase jumps, but partial or corrupt retained
690
785
  * records must not become false success: every seat must be identity_absent;
691
- * a real launch requires archive evidence; an identity-less seat is accepted
692
- * only via the never-launched proof.
786
+ * a real launch requires archive evidence; an identity-less seat requires
787
+ * never-launched proof or a freshly verified archived-absence checkpoint.
693
788
  */
694
- export function importTaskDeletionRetirementEvidence(id, seats) {
789
+ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
695
790
  return withTaskLock(id, () => {
696
791
  assertCanonicalTaskId(id);
697
792
  const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
@@ -702,12 +797,29 @@ export function importTaskDeletionRetirementEvidence(id, seats) {
702
797
  const evidence = seat.retirement;
703
798
  if (evidence?.phase !== 'identity_absent')
704
799
  throw new TaskStateError(`task ${id} deletion cannot checkpoint member '${seat.role_name}' before completed retirement`);
705
- const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED;
800
+ const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
801
+ || evidence.launch_id === DELETION_MEMBER_ABSENT_VERIFIED || evidence.absence_verified === true;
706
802
  if (!neverLaunched && evidence.archive_path === undefined)
707
803
  throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' retirement evidence lacks its archive`);
708
804
  if (!seat.identity_cid) {
709
- if (neverLaunched)
710
- continue; // provably never held a managed identity
805
+ if (neverLaunched) {
806
+ stored.deletion.absent_members ??= [];
807
+ if (!stored.deletion.absent_members.includes(seat.role_name))
808
+ stored.deletion.absent_members.push(seat.role_name);
809
+ continue;
810
+ }
811
+ const proof = proofs.find(value => value.name === seat.role_name);
812
+ if (proof && proof.launch_id === evidence.launch_id && proof.archive_path === evidence.archive_path
813
+ && proof.launch_id === seat.launch?.launch_id && proof.action_id === seat.launch?.action_id) {
814
+ const existing = stored.deletion.archived_absences?.find(value => value.name === proof.name);
815
+ if (existing && (existing.launch_id !== proof.launch_id || existing.action_id !== proof.action_id
816
+ || existing.archive_path !== proof.archive_path))
817
+ throw new TaskStateError('Archived absence checkpoint ownership is immutable');
818
+ stored.deletion.archived_absences ??= [];
819
+ if (!existing)
820
+ stored.deletion.archived_absences.push({ ...proof });
821
+ continue;
822
+ }
711
823
  throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' has retirement evidence but no identity CID`);
712
824
  }
713
825
  let cursor = findCursorForSeat(id, stored, seat);
@@ -715,6 +827,10 @@ export function importTaskDeletionRetirementEvidence(id, seats) {
715
827
  cursor = { name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now };
716
828
  stored.deletion.members.push(cursor);
717
829
  }
830
+ if (seat.launch?.action_id)
831
+ cursor.action_id ??= seat.launch.action_id;
832
+ if (seat.launch?.task_supervised)
833
+ cursor.task_supervised = true;
718
834
  if (DELETION_MEMBER_PHASE_ORDER[evidence.phase] < DELETION_MEMBER_PHASE_ORDER[cursor.phase])
719
835
  continue;
720
836
  if (cursor.launch_id !== undefined && cursor.launch_id !== evidence.launch_id)
@@ -729,9 +845,23 @@ export function importTaskDeletionRetirementEvidence(id, seats) {
729
845
  cursor.updated_at = now;
730
846
  }
731
847
  writeTask(stored);
848
+ writeDeletionReceiptForIntent(stored);
732
849
  return presentTaskLenient(stored);
733
850
  });
734
851
  }
852
+ /** Settlement-only checkpoint, under the task-operation lock after fresh archive verification.
853
+ * From here retries still verify live absence, but owned archives may be consumed.
854
+ */
855
+ export function beginTaskWorkspaceCleanup(id) {
856
+ withTaskLock(id, () => {
857
+ const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
858
+ if (stored.deletion?.status !== 'pending'
859
+ || stored.deletion.members.some(member => member.phase !== 'identity_absent'))
860
+ throw new TaskStateError('Task is not ready for workspace cleanup');
861
+ stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
862
+ writeTask(stored);
863
+ });
864
+ }
735
865
  /**
736
866
  * Physically remove a deletion-pending task record. Settlement-only: callers
737
867
  * must have completed member retirement and room cleanup first. Missing
@@ -751,6 +881,12 @@ export function unlinkDeletedTask(id) {
751
881
  }
752
882
  if (stored.deletion?.status !== 'pending')
753
883
  throw new TaskStateError(`task ${id} has no pending deletion`);
884
+ // Preserve committed deliveries outside the record before deletion. A crash
885
+ // before unlink may replay two copies; both carry identical service dedupe IDs.
886
+ const removed = { eventId: `task:${id}:deleted`, taskId: id,
887
+ title: `Task deleted: ${stored.title}`.slice(0, 160), body: `Task ${id}: deleting → deleted.`,
888
+ url: `/fleet/tasks?deletedTask=${id}` };
889
+ replaceFileAtomically(join(retiredNotificationsDir(), `${id}.json`), JSON.stringify({ notification_events: [...(stored.notification_events ?? []), removed] }) + '\n');
754
890
  try {
755
891
  unlinkSync(taskPath(id));
756
892
  }
@@ -762,3 +898,87 @@ export function unlinkDeletedTask(id) {
762
898
  return true;
763
899
  });
764
900
  }
901
+ /** Called only after current temp and exact identity absence have been checked. */
902
+ export function settleAbsentTaskDeletionMember(id, name) {
903
+ withTaskLock(id, () => {
904
+ const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
905
+ if (stored.deletion?.status !== 'pending')
906
+ throw new TaskStateError('Task is not deleting');
907
+ const member = stored.deletion.members.find(value => value.name === name);
908
+ if (!member)
909
+ throw new TaskStateError('Unknown deletion member');
910
+ member.phase = 'identity_absent';
911
+ member.launch_id ??= DELETION_MEMBER_ABSENT_VERIFIED;
912
+ member.updated_at = new Date().toISOString();
913
+ writeTask(stored);
914
+ });
915
+ }
916
+ /** Room-only erasure keeps the parent Task but removes its dead room/member links. */
917
+ export function detachDeletedRoom(taskId, roomId) {
918
+ withTaskLock(taskId, () => {
919
+ if (!existsSync(taskPath(taskId)))
920
+ return;
921
+ const stored = JSON.parse(readFileSync(taskPath(taskId), 'utf8'));
922
+ if (stored.room_id !== roomId || stored.deletion?.status === 'pending')
923
+ return;
924
+ delete stored.room_id;
925
+ delete stored.room_identity_cid;
926
+ stored.member_roles = [];
927
+ writeTask(stored);
928
+ });
929
+ }
930
+ /** A completed room may consume its archives while other task members still retire. */
931
+ export function beginTaskArchiveCleanup(id) {
932
+ withTaskLock(id, () => {
933
+ const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
934
+ if (stored.deletion?.status !== 'pending')
935
+ throw new TaskStateError('Task is not deleting');
936
+ stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
937
+ writeTask(stored);
938
+ });
939
+ }
940
+ /** Metadata-only edit; compare under the task lock to prevent lost updates. */
941
+ /**
942
+ * Choose, change or clear a Backlog task's Room Layout before it starts.
943
+ * `expected` is the layout name the caller saw (null = none); a different
944
+ * current choice is a stale edit. Choosing a layout drops the sealed room
945
+ * template plan; the caller releases the returned launch snapshot hash.
946
+ */
947
+ export function updateTaskLayout(id, expected, next) {
948
+ return withTaskLock(id, () => {
949
+ const task = readTask(id);
950
+ assertNoPendingDeletion(task);
951
+ assertNoPendingTerminalIntent(task);
952
+ if (task.state !== 'backlog' || task.room_id)
953
+ throw new TaskStateError(`task ${id} has started; its room layout is fixed (state '${task.state}')`);
954
+ const current = task.layout?.name ?? null;
955
+ // A replay of the same choice is idempotent (and refreshes the pinned source hash).
956
+ if (current !== expected && current !== (next?.name ?? null))
957
+ throw new TaskStateError(`task ${id} room layout changed to '${current ?? 'one room'}'; reload before changing it`);
958
+ const releasedSnapshot = next ? task.execution_plan?.snapshot.launch_snapshot_hash : undefined;
959
+ if (next) {
960
+ task.layout = { name: next.name, run_id: `task-${task.task_id}`, definition_hash: next.definition_hash };
961
+ delete task.execution_plan;
962
+ delete task.template;
963
+ delete task.no_room;
964
+ }
965
+ else
966
+ delete task.layout;
967
+ task.room_plan_changed_at = new Date().toISOString();
968
+ writeTask(task);
969
+ return { task: readTask(id), ...(releasedSnapshot ? { releasedSnapshot } : {}) };
970
+ });
971
+ }
972
+ export function updateTaskBrief(id, brief, expectedBrief) {
973
+ assertCanonicalTaskId(id);
974
+ return withTaskLock(id, () => {
975
+ const task = readTask(id);
976
+ assertNoPendingDeletion(task);
977
+ assertNoPendingTerminalIntent(task);
978
+ if ((task.brief ?? '') !== expectedBrief && task.brief !== brief)
979
+ throw new TaskStateError('Description changed elsewhere. Reload the task before saving.');
980
+ task.brief = brief;
981
+ writeTask(task);
982
+ return task;
983
+ });
984
+ }
@@ -62,6 +62,8 @@ export type TaskDeletionMemberPhase = 'pending' | 'stop_requested' | 'liveness_a
62
62
  export interface TaskDeletionMemberCursor {
63
63
  name: string;
64
64
  identity_cid: string;
65
+ action_id?: string;
66
+ task_supervised?: boolean;
65
67
  phase: TaskDeletionMemberPhase;
66
68
  launch_id?: string;
67
69
  archive_path?: string;
@@ -73,6 +75,13 @@ export interface TaskDeletionMemberCursor {
73
75
  * evidence. While pending, the task is hidden from normal operation and every
74
76
  * lifecycle mutation or room publication is rejected.
75
77
  */
78
+ export interface ArchivedMemberAbsence {
79
+ name: string;
80
+ launch_id: string;
81
+ action_id: string;
82
+ archive_path: string;
83
+ checked_at: string;
84
+ }
76
85
  export interface TaskDeletionIntent {
77
86
  status: 'pending';
78
87
  accepted_at: string;
@@ -80,13 +89,34 @@ export interface TaskDeletionIntent {
80
89
  room_id?: string;
81
90
  /** Snapshot of managed members at acceptance; the missing-room retirement evidence. */
82
91
  members: TaskDeletionMemberCursor[];
92
+ archived_absences?: ArchivedMemberAbsence[];
93
+ absent_members?: string[];
94
+ /** Archive proofs were verified before owned workspace cleanup may consume them. */
95
+ workspace_cleanup_started_at?: string;
83
96
  error?: string;
84
97
  error_at?: string;
85
98
  recovery_hint?: string;
86
99
  first_failure?: string;
87
100
  first_recovery_hint?: string;
88
101
  }
102
+ export interface TaskLayoutLink {
103
+ name: string;
104
+ /** Deterministic `task-<task_id>` Room Layout run ID. */
105
+ run_id: string;
106
+ /** sha256 of the canonical source layout definition the run must snapshot. */
107
+ definition_hash: string;
108
+ }
109
+ export interface TaskNotificationEvent {
110
+ eventId: string;
111
+ title: string;
112
+ body: string;
113
+ url: string;
114
+ taskId: string;
115
+ }
89
116
  export interface TaskRecord {
117
+ /** Pending lifecycle events, committed atomically with their task change. */
118
+ notification_events?: TaskNotificationEvent[];
119
+ workspace?: import('./workspace.js').OwnedWorkspace;
90
120
  task_id: string;
91
121
  /** Stable organizational list identifier. Missing legacy values mean `default`. */
92
122
  list_id: string;
@@ -107,6 +137,12 @@ export interface TaskRecord {
107
137
  plan_hash: string;
108
138
  };
109
139
  no_room?: boolean;
140
+ /** Multi-room task: rooms and participants come from a Room Layout run. */
141
+ layout?: TaskLayoutLink;
142
+ /** Set when the room plan (layout or one-room template) was changed after creation, before start. */
143
+ room_plan_changed_at?: string;
144
+ /** Fingerprint of the room plan requested at creation; idempotent retries compare against it after later plan edits. */
145
+ creation_plan?: string;
110
146
  room_id?: string;
111
147
  room_identity_cid?: string;
112
148
  member_roles: TaskMemberRole[];
@@ -145,6 +181,7 @@ export interface RoomRoleBriefingDefinition {
145
181
  last_error?: string;
146
182
  }
147
183
  export interface RoomMemberLaunchState {
184
+ task_supervised?: boolean;
148
185
  state: 'pending' | 'intent' | 'launched' | 'stopped' | 'failed';
149
186
  attempt: number;
150
187
  action_id?: string;
@@ -201,6 +238,7 @@ export type RoomHistoryEvidence = {
201
238
  };
202
239
  export type MemberRetirementPhase = 'stop_requested' | 'liveness_absent' | 'archive_secured' | 'identity_absent';
203
240
  export interface MemberRetirement {
241
+ absence_verified?: boolean;
204
242
  phase: MemberRetirementPhase;
205
243
  launch_id: string;
206
244
  updated_at: string;
@@ -227,12 +265,14 @@ export interface RoomMemberSeat {
227
265
  retirement?: MemberRetirement;
228
266
  }
229
267
  export interface RoomOrchestrationRecord {
268
+ workspace?: import('./workspace.js').OwnedWorkspace;
230
269
  room_id: string;
231
270
  room_identity_cid?: string;
232
271
  room_name: string;
233
272
  goal?: string;
234
273
  task_id?: string;
235
274
  template_snapshot?: TemplateSnapshot;
275
+ additional_member_templates?: TemplateSnapshot[];
236
276
  /** Resolved once at launch; legacy absence means non-anonymous. */
237
277
  room_policy?: RoomLaunchPolicy;
238
278
  saga: SagaCursor;
@@ -0,0 +1,6 @@
1
+ import { type OwnedWorkspace } from './workspace.js';
2
+ /** Runtime control state stays in Fleet's live roster and recovery store. Its
3
+ * durable launch descriptor, not a name prefix or caller-supplied path, proves
4
+ * artifact ownership. Audit receipts/journals intentionally outlive deletion.
5
+ */
6
+ export declare function collectWorkspaceArchives(workspace: OwnedWorkspace): void;
@@ -0,0 +1,93 @@
1
+ import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
3
+ import { parse } from 'yaml';
4
+ import { stateRoot } from '../paths.js';
5
+ import { assertSafeAncestors, auditWorkspaceGit, validateWorkspace, ensureWorkspace, WorkspaceError } from './workspace.js';
6
+ /** Runtime control state stays in Fleet's live roster and recovery store. Its
7
+ * durable launch descriptor, not a name prefix or caller-supplied path, proves
8
+ * artifact ownership. Audit receipts/journals intentionally outlive deletion.
9
+ */
10
+ export function collectWorkspaceArchives(workspace) {
11
+ validateWorkspace(workspace);
12
+ const root = join(stateRoot(), 'recovery', 'temporary');
13
+ assertSafeAncestors(root);
14
+ let entries;
15
+ try {
16
+ entries = readdirSync(root, { withFileTypes: true });
17
+ }
18
+ catch (e) {
19
+ if (e.code === 'ENOENT')
20
+ return;
21
+ throw e;
22
+ }
23
+ const owned = [];
24
+ for (const entry of entries) {
25
+ if (!entry.isDirectory() || entry.isSymbolicLink())
26
+ continue;
27
+ const path = join(root, entry.name);
28
+ const roleFile = join(path, 'role.yaml');
29
+ let role;
30
+ try {
31
+ const s = lstatSync(roleFile);
32
+ if (!s.isFile() || s.isSymbolicLink())
33
+ continue;
34
+ role = parse(readFileSync(roleFile, 'utf8'));
35
+ }
36
+ catch {
37
+ continue;
38
+ } // no ownership proof: never adopt a foreign archive
39
+ const recorded = role?.roomMemberStartup?.workspace;
40
+ if (recorded?.owner !== workspace.owner || recorded.id !== workspace.id)
41
+ continue;
42
+ if (JSON.stringify(recorded) !== JSON.stringify(workspace))
43
+ throw new WorkspaceError('workspace archive ownership mismatch');
44
+ assertSafeAncestors(path);
45
+ const termination = join(path, 'termination.jsonl');
46
+ if (!lstatSync(termination).isFile() || lstatSync(termination).isSymbolicLink())
47
+ throw new WorkspaceError('unsafe workspace archive termination evidence');
48
+ const supervisorPath = join(path, '.temp-supervisor.json');
49
+ let supervisor;
50
+ try {
51
+ if (!lstatSync(supervisorPath).isFile() || lstatSync(supervisorPath).isSymbolicLink())
52
+ throw new WorkspaceError('unsafe workspace archive supervisor evidence');
53
+ supervisor = JSON.parse(readFileSync(supervisorPath, 'utf8'));
54
+ }
55
+ catch (e) {
56
+ if (e.code !== 'ENOENT')
57
+ throw e;
58
+ }
59
+ if (supervisor && (supervisor.role !== role.name || !supervisor.launchId))
60
+ throw new WorkspaceError('workspace archive launch ownership mismatch');
61
+ const terminated = readFileSync(termination, 'utf8').trim().split('\n').some(line => {
62
+ const value = JSON.parse(line);
63
+ return value.version === 1 && value.role === role.name
64
+ && value.launchId === supervisor?.launchId
65
+ && ['retired', 'reclaimed', 'failed'].includes(value.outcome);
66
+ });
67
+ if (!terminated || entry.name.endsWith('.retiring'))
68
+ throw new WorkspaceError('workspace archive retirement is incomplete');
69
+ auditWorkspaceGit(path, { forDeletion: true });
70
+ owned.push(path);
71
+ }
72
+ // Preflight the entire set before mutation; task/room record remains until all succeed.
73
+ if (!owned.length)
74
+ return;
75
+ ensureWorkspace(workspace);
76
+ const destination = join(workspace.path, '.fleet-retired-agents');
77
+ assertSafeAncestors(destination);
78
+ mkdirSync(destination, { recursive: true, mode: 0o700 });
79
+ for (const path of owned) {
80
+ const target = join(destination, basename(path));
81
+ try {
82
+ lstatSync(target);
83
+ throw new WorkspaceError('archive collection destination exists');
84
+ }
85
+ catch (e) {
86
+ if (e.code !== 'ENOENT')
87
+ throw e;
88
+ }
89
+ // Atomic transfer before workspace deletion: a crash never loses ownership
90
+ // evidence halfway through recursively removing a global recovery archive.
91
+ renameSync(path, target);
92
+ }
93
+ }
@@ -0,0 +1,35 @@
1
+ /** Persisted before allocation. Never infer ownership from an Agent's cwd. */
2
+ export interface OwnedWorkspace {
3
+ version: 1;
4
+ owner: 'task' | 'room';
5
+ id: string;
6
+ path: string;
7
+ token: string;
8
+ }
9
+ export declare class WorkspaceError extends Error {
10
+ }
11
+ export declare function assertSafeAncestors(path: string): void;
12
+ export declare function planWorkspace(owner: OwnedWorkspace['owner'], id: string): OwnedWorkspace;
13
+ export declare function validateWorkspace(w: OwnedWorkspace, owner?: "task" | "room", id?: string): void;
14
+ /** Read-only recovery proof; never allocate a replacement for a missing workspace. */
15
+ export declare function assertWorkspacePresent(w: OwnedWorkspace, owner: OwnedWorkspace['owner'], id: string): void;
16
+ /** Caller must first prove the exact workspace marker or retired archive
17
+ * provenance, and hold its lifecycle lock with all managed writers retired.
18
+ * Directories need owner rwx for recursive removal; ordinary files do not.
19
+ */
20
+ export declare function prepareOwnedDeletionTree(path: string): void;
21
+ /** Atomic directory publication; retry uses the durable token, never adopts another folder. */
22
+ export declare function ensureWorkspace(w: OwnedWorkspace): string;
23
+ /** Audit both directions of Git worktree registration, without executing repository config/hooks.
24
+ * All registrations and common dirs must be inside this workspace. Removing the complete
25
+ * workspace then removes their registries atomically with the owned repositories; never prune.
26
+ */
27
+ export declare function auditWorkspaceGit(path: string, options?: {
28
+ forDeletion?: boolean;
29
+ }): void;
30
+ /** Called only after all managed writers retire, under the owner's lifecycle lock.
31
+ * The tombstone is derived from the durable unguessable token. A crash during rm
32
+ * resumes that exact tombstone even if rm already removed its marker.
33
+ */
34
+ export declare function assertWorkspaceDeletable(w: OwnedWorkspace, owner: OwnedWorkspace['owner'], id: string): void;
35
+ export declare function deleteWorkspace(w: OwnedWorkspace, owner: OwnedWorkspace['owner'], id: string): void;