@ours.network/fleet 1.2.0-nightly.8 → 1.2.0-nightly.80

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 (423) hide show
  1. package/README.md +279 -68
  2. package/dist/account-origin.d.ts +5 -0
  3. package/dist/account-origin.js +31 -0
  4. package/dist/agent-ours/bridge.js +26 -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 +19 -0
  11. package/dist/agent-ours/mcp-endpoint.js +98 -25
  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 +24 -1
  15. package/dist/agent-ours/service.js +97 -13
  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 +34 -0
  19. package/dist/application/conversation-history.js +294 -0
  20. package/dist/application/conversation-role-observer.d.ts +62 -0
  21. package/dist/application/conversation-role-observer.js +206 -0
  22. package/dist/application/fleet-query-service.d.ts +1 -1
  23. package/dist/application/fleet-query-service.js +3 -3
  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/resource-observer.d.ts +43 -0
  30. package/dist/application/resource-observer.js +323 -0
  31. package/dist/application/role-creation-service.js +3 -2
  32. package/dist/application/role-removal-service.js +17 -3
  33. package/dist/application/role-repository.d.ts +1 -1
  34. package/dist/application/role-repository.js +30 -7
  35. package/dist/application/room-layout-definitions.d.ts +45 -0
  36. package/dist/application/room-layout-definitions.js +130 -0
  37. package/dist/application/session-control.d.ts +63 -17
  38. package/dist/application/session-control.js +221 -23
  39. package/dist/application/supervisor-ours-tools.js +5 -11
  40. package/dist/application/task-layouts.d.ts +91 -0
  41. package/dist/application/task-layouts.js +261 -0
  42. package/dist/application/task-room-service.d.ts +101 -3
  43. package/dist/application/task-room-service.js +286 -33
  44. package/dist/application/types.d.ts +2 -0
  45. package/dist/atomic-file.d.ts +34 -1
  46. package/dist/atomic-file.js +161 -18
  47. package/dist/briefing.d.ts +3 -1
  48. package/dist/briefing.js +97 -31
  49. package/dist/build-info.json +9 -4
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +400 -37
  53. package/dist/client-profile.d.ts +5 -17
  54. package/dist/client-profile.js +4 -127
  55. package/dist/config.d.ts +45 -0
  56. package/dist/config.js +57 -6
  57. package/dist/creation.d.ts +1 -1
  58. package/dist/creation.js +4 -4
  59. package/dist/daemon-recovery.d.ts +14 -9
  60. package/dist/daemon-recovery.js +124 -115
  61. package/dist/docs.d.ts +1 -1
  62. package/dist/docs.js +207 -19
  63. package/dist/doctor.d.ts +4 -1
  64. package/dist/doctor.js +75 -60
  65. package/dist/erased-resources.d.ts +9 -0
  66. package/dist/erased-resources.js +87 -0
  67. package/dist/file-delivery/reader.d.ts +15 -0
  68. package/dist/file-delivery/reader.js +63 -0
  69. package/dist/file-delivery/store.d.ts +15 -0
  70. package/dist/file-delivery/store.js +124 -0
  71. package/dist/file-delivery/types.d.ts +19 -0
  72. package/dist/file-delivery/types.js +1 -0
  73. package/dist/fleet-command-audit.d.ts +7 -0
  74. package/dist/fleet-command-audit.js +24 -8
  75. package/dist/harness/agent-session.d.ts +4 -0
  76. package/dist/harness/claude-code-session.js +1 -1
  77. package/dist/harness/claude-code.js +21 -11
  78. package/dist/harness/claude-runtime.d.ts +12 -0
  79. package/dist/harness/claude-runtime.js +43 -0
  80. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  81. package/dist/harness/codex-app-server-proxy.js +41 -5
  82. package/dist/harness/codex-reasoning.d.ts +5 -0
  83. package/dist/harness/codex-reasoning.js +12 -0
  84. package/dist/harness/codex-runtime.d.ts +11 -1
  85. package/dist/harness/codex-runtime.js +31 -4
  86. package/dist/harness/codex-session.js +2 -2
  87. package/dist/harness/codex.js +24 -12
  88. package/dist/init-wizard.d.ts +2 -0
  89. package/dist/init-wizard.js +38 -19
  90. package/dist/isolation/policy.js +6 -1
  91. package/dist/isolation/types.d.ts +5 -0
  92. package/dist/managed-cli.d.ts +253 -0
  93. package/dist/managed-cli.js +923 -0
  94. package/dist/managed-recovery.d.ts +13 -0
  95. package/dist/managed-recovery.js +31 -0
  96. package/dist/minimal-setup.d.ts +2 -0
  97. package/dist/minimal-setup.js +44 -0
  98. package/dist/monitor.d.ts +9 -34
  99. package/dist/monitor.js +90 -153
  100. package/dist/notifications/fleet-producer.d.ts +31 -0
  101. package/dist/notifications/fleet-producer.js +351 -0
  102. package/dist/notifications/outbox.d.ts +59 -0
  103. package/dist/notifications/outbox.js +199 -0
  104. package/dist/notifications/target-cleanup.d.ts +16 -0
  105. package/dist/notifications/target-cleanup.js +75 -0
  106. package/dist/notifications/task-producer.d.ts +20 -0
  107. package/dist/notifications/task-producer.js +100 -0
  108. package/dist/ops.d.ts +2 -0
  109. package/dist/ops.js +20 -3
  110. package/dist/owner-channel/channel.js +17 -6
  111. package/dist/owner-channel/commands.d.ts +4 -0
  112. package/dist/owner-channel/commands.js +8 -0
  113. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  114. package/dist/owner-channel/ours-client.js +2 -17
  115. package/dist/paths.d.ts +5 -0
  116. package/dist/paths.js +5 -0
  117. package/dist/preset-migration.js +43 -0
  118. package/dist/process-incarnation.d.ts +23 -0
  119. package/dist/process-incarnation.js +42 -0
  120. package/dist/reminders/manager.d.ts +75 -0
  121. package/dist/reminders/manager.js +278 -0
  122. package/dist/reminders/runtime.d.ts +27 -0
  123. package/dist/reminders/runtime.js +32 -0
  124. package/dist/reminders/schedule.d.ts +37 -0
  125. package/dist/reminders/schedule.js +104 -0
  126. package/dist/reminders/store.d.ts +341 -0
  127. package/dist/reminders/store.js +762 -0
  128. package/dist/reminders/tools.d.ts +20 -0
  129. package/dist/reminders/tools.js +114 -0
  130. package/dist/resolved-plan.js +1 -0
  131. package/dist/rooms-tasks/add-member.d.ts +48 -0
  132. package/dist/rooms-tasks/add-member.js +84 -0
  133. package/dist/rooms-tasks/archived-absence.d.ts +4 -0
  134. package/dist/rooms-tasks/archived-absence.js +11 -0
  135. package/dist/rooms-tasks/cli.js +65 -5
  136. package/dist/rooms-tasks/close.d.ts +5 -2
  137. package/dist/rooms-tasks/close.js +224 -36
  138. package/dist/rooms-tasks/config.d.ts +1 -1
  139. package/dist/rooms-tasks/config.js +11 -4
  140. package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
  141. package/dist/rooms-tasks/cowork-adapter.js +15 -121
  142. package/dist/rooms-tasks/cowork-http.js +56 -6
  143. package/dist/rooms-tasks/deletion.d.ts +1 -1
  144. package/dist/rooms-tasks/deletion.js +63 -10
  145. package/dist/rooms-tasks/erasure.d.ts +6 -0
  146. package/dist/rooms-tasks/erasure.js +173 -0
  147. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  148. package/dist/rooms-tasks/external-worker.js +3 -4
  149. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  150. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  151. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  152. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  153. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  154. package/dist/rooms-tasks/layout-cli.js +107 -0
  155. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  156. package/dist/rooms-tasks/layout-config.js +89 -0
  157. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  158. package/dist/rooms-tasks/layout-control.js +52 -0
  159. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  160. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  161. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  162. package/dist/rooms-tasks/layout-service.js +203 -0
  163. package/dist/rooms-tasks/layout.d.ts +121 -0
  164. package/dist/rooms-tasks/layout.js +286 -0
  165. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  166. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  167. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  168. package/dist/rooms-tasks/live-readiness.js +85 -0
  169. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  170. package/dist/rooms-tasks/member-ownership.js +40 -0
  171. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  172. package/dist/rooms-tasks/member-startup.js +2 -0
  173. package/dist/rooms-tasks/provision.d.ts +3 -0
  174. package/dist/rooms-tasks/provision.js +101 -49
  175. package/dist/rooms-tasks/recovery.d.ts +19 -0
  176. package/dist/rooms-tasks/recovery.js +110 -0
  177. package/dist/rooms-tasks/room-state.d.ts +4 -2
  178. package/dist/rooms-tasks/room-state.js +47 -8
  179. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  180. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  181. package/dist/rooms-tasks/task-state.d.ts +43 -5
  182. package/dist/rooms-tasks/task-state.js +244 -22
  183. package/dist/rooms-tasks/types.d.ts +35 -0
  184. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  185. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  186. package/dist/rooms-tasks/workspace.d.ts +35 -0
  187. package/dist/rooms-tasks/workspace.js +325 -0
  188. package/dist/runner.d.ts +33 -4
  189. package/dist/runner.js +410 -62
  190. package/dist/session/acp.d.ts +75 -2
  191. package/dist/session/acp.js +345 -82
  192. package/dist/session/arbiter.d.ts +98 -0
  193. package/dist/session/arbiter.js +272 -3
  194. package/dist/session/codex-app-server.d.ts +45 -1
  195. package/dist/session/codex-app-server.js +225 -32
  196. package/dist/session/control.d.ts +17 -3
  197. package/dist/session/control.js +219 -23
  198. package/dist/session/conversation-normalizer.d.ts +2 -1
  199. package/dist/session/conversation-normalizer.js +1 -1
  200. package/dist/session/conversation-observation.d.ts +25 -0
  201. package/dist/session/conversation-observation.js +100 -0
  202. package/dist/session/conversation-replay.d.ts +50 -0
  203. package/dist/session/conversation-replay.js +203 -0
  204. package/dist/session/conversation-store.d.ts +15 -1
  205. package/dist/session/conversation-store.js +213 -26
  206. package/dist/session/conversation-types.d.ts +57 -4
  207. package/dist/session/prompt-batch.d.ts +79 -0
  208. package/dist/session/prompt-batch.js +120 -0
  209. package/dist/session/task-notice.d.ts +14 -0
  210. package/dist/session/task-notice.js +13 -0
  211. package/dist/session/types.d.ts +42 -1
  212. package/dist/setup-tunnel-preflight.d.ts +34 -0
  213. package/dist/setup-tunnel-preflight.js +62 -0
  214. package/dist/socket-path.d.ts +2 -0
  215. package/dist/socket-path.js +22 -0
  216. package/dist/spawn.d.ts +5 -1
  217. package/dist/spawn.js +40 -4
  218. package/dist/startup-prompt.d.ts +15 -0
  219. package/dist/startup-prompt.js +71 -0
  220. package/dist/startup-readiness.d.ts +32 -0
  221. package/dist/startup-readiness.js +101 -0
  222. package/dist/startup-telemetry.d.ts +16 -0
  223. package/dist/startup-telemetry.js +46 -0
  224. package/dist/subscriptions/cli.d.ts +91 -0
  225. package/dist/subscriptions/cli.js +223 -0
  226. package/dist/subscriptions/launch.d.ts +37 -0
  227. package/dist/subscriptions/launch.js +93 -0
  228. package/dist/subscriptions/login.d.ts +77 -0
  229. package/dist/subscriptions/login.js +215 -0
  230. package/dist/subscriptions/observed.d.ts +7 -0
  231. package/dist/subscriptions/observed.js +44 -0
  232. package/dist/subscriptions/probe.d.ts +10 -0
  233. package/dist/subscriptions/probe.js +73 -0
  234. package/dist/subscriptions/service.d.ts +102 -0
  235. package/dist/subscriptions/service.js +364 -0
  236. package/dist/subscriptions/store.d.ts +71 -0
  237. package/dist/subscriptions/store.js +143 -0
  238. package/dist/supervisor/adoption.d.ts +8 -0
  239. package/dist/supervisor/adoption.js +78 -0
  240. package/dist/supervisor/catalog.d.ts +62 -0
  241. package/dist/supervisor/catalog.js +254 -0
  242. package/dist/supervisor/fleet.d.ts +8 -0
  243. package/dist/supervisor/fleet.js +89 -0
  244. package/dist/supervisor/index.js +3 -6
  245. package/dist/supervisor/launchd.d.ts +1 -1
  246. package/dist/supervisor/launchd.js +26 -13
  247. package/dist/supervisor/legacy.d.ts +35 -0
  248. package/dist/supervisor/legacy.js +524 -0
  249. package/dist/supervisor/manager.d.ts +15 -0
  250. package/dist/supervisor/manager.js +270 -0
  251. package/dist/supervisor/registration-refresh.d.ts +45 -0
  252. package/dist/supervisor/registration-refresh.js +266 -0
  253. package/dist/supervisor/scope.d.ts +4 -0
  254. package/dist/supervisor/scope.js +10 -0
  255. package/dist/supervisor/systemd.d.ts +2 -2
  256. package/dist/supervisor/systemd.js +19 -33
  257. package/dist/task-supervision.d.ts +9 -0
  258. package/dist/task-supervision.js +145 -0
  259. package/dist/temp-idle.d.ts +17 -0
  260. package/dist/temp-idle.js +42 -0
  261. package/dist/temp-lifecycle.d.ts +23 -8
  262. package/dist/temp-lifecycle.js +123 -66
  263. package/dist/temp-supervisor-recovery.d.ts +1 -1
  264. package/dist/temp-supervisor-recovery.js +2 -2
  265. package/dist/tunnel-setup-completion.d.ts +7 -0
  266. package/dist/tunnel-setup-completion.js +13 -0
  267. package/dist/tunnel-setup-input.d.ts +19 -0
  268. package/dist/tunnel-setup-input.js +130 -0
  269. package/dist/watchdog/query.d.ts +3 -1
  270. package/dist/watchdog/query.js +2 -1
  271. package/dist/web/agent-attachments.d.ts +29 -0
  272. package/dist/web/agent-attachments.js +162 -0
  273. package/dist/web/auth.d.ts +65 -2
  274. package/dist/web/auth.js +201 -11
  275. package/dist/web/available-port.d.ts +2 -0
  276. package/dist/web/available-port.js +23 -0
  277. package/dist/web/control.d.ts +3 -3
  278. package/dist/web/control.js +10 -8
  279. package/dist/web/conversation-stream-codec.d.ts +23 -0
  280. package/dist/web/conversation-stream-codec.js +110 -0
  281. package/dist/web/conversation-stream.d.ts +26 -0
  282. package/dist/web/conversation-stream.js +239 -0
  283. package/dist/web/events.d.ts +2 -0
  284. package/dist/web/events.js +9 -0
  285. package/dist/web/fleet-config-service.d.ts +7 -1
  286. package/dist/web/fleet-config-service.js +61 -16
  287. package/dist/web/prefix-gateway.d.ts +32 -0
  288. package/dist/web/prefix-gateway.js +248 -0
  289. package/dist/web/resource-views.d.ts +135 -0
  290. package/dist/web/resource-views.js +486 -0
  291. package/dist/web/runtime.d.ts +3 -1
  292. package/dist/web/runtime.js +132 -34
  293. package/dist/web/server.d.ts +19 -0
  294. package/dist/web/server.js +821 -68
  295. package/dist/web/service.d.ts +11 -2
  296. package/dist/web/service.js +30 -4
  297. package/dist/web/workspace-devices.d.ts +107 -0
  298. package/dist/web/workspace-devices.js +277 -0
  299. package/dist/web/workspace-tunnel.d.ts +1 -0
  300. package/dist/web/workspace-tunnel.js +34 -0
  301. package/dist/workspace-enrollment.d.ts +83 -0
  302. package/dist/workspace-enrollment.js +605 -0
  303. package/dist/workspace-replacement.d.ts +66 -0
  304. package/dist/workspace-replacement.js +238 -0
  305. package/docs/acp-files.md +146 -0
  306. package/docs/conversation-stream.md +216 -0
  307. package/docs/fleet-migration.md +54 -0
  308. package/docs/live-voice.md +13 -0
  309. package/docs/room-layouts.md +227 -0
  310. package/docs/self-reminders.md +374 -0
  311. package/docs/task-workspaces.md +225 -0
  312. package/docs/validation/managed-cli-permissions.md +421 -0
  313. package/examples/room-layouts/fresh.yaml +27 -0
  314. package/examples/room-layouts/scoped.yaml +21 -0
  315. package/examples/room-layouts/shared.yaml +19 -0
  316. package/package.json +24 -20
  317. package/presets/README.md +78 -0
  318. package/presets/brain-catalog.json +8 -2
  319. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  320. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  321. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  322. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -2
  323. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  324. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  325. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  326. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  327. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  328. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  329. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  330. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  331. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  332. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  333. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  334. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  335. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  336. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  337. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  338. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  339. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  340. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  341. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  342. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  343. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  344. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  345. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  351. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  352. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  353. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  354. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  384. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  385. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  386. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  387. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  390. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  391. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  392. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  393. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  394. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  396. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  397. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  398. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  399. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  400. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  406. package/presets/fleet/roles/Coordinator.yaml +12 -11
  407. package/presets/fleet/roles/Critic.yaml +13 -0
  408. package/presets/fleet/roles/Developer.yaml +13 -0
  409. package/presets/fleet/roles/Engineer.yaml +53 -0
  410. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  411. package/presets/fleet/room_layouts/pair.yaml +24 -0
  412. package/presets/fleet/room_layouts/single.yaml +17 -0
  413. package/presets/fleet/room_layouts/team.yaml +30 -0
  414. package/presets/fleet/room_templates/engineering.yaml +19 -0
  415. package/presets/manifest.json +2 -2
  416. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  417. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  418. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  419. package/dist/web-app/icons/ours-fleet.svg +0 -4
  420. package/dist/web-app/index.html +0 -17
  421. package/dist/web-app/manifest.webmanifest +0 -15
  422. package/dist/web-app/offline.html +0 -18
  423. package/dist/web-app/sw.js +0 -51
@@ -1,8 +1,10 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, rmSync, renameSync } from 'node:fs';
1
+ import { publishFleetResourceCommit } from '../application/resource-observer.js';
2
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, rmSync, renameSync } from 'node:fs';
2
3
  import { join } from 'node:path';
3
4
  import { randomUUID } from 'node:crypto';
4
5
  import { replaceFileAtomically } from '../atomic-file.js';
5
6
  import { stateRoot } from '../paths.js';
7
+ import { planWorkspace, ensureWorkspace } from './workspace.js';
6
8
  import { storedRoomLaunchPolicy, TASK_TERMINAL_STATES, TASK_CANCELLABLE_STATES } from './types.js';
7
9
  import { DEFAULT_TASK_LIST_ID, readTaskLists } from './task-lists.js';
8
10
  export const tasksDir = () => join(stateRoot(), 'tasks');
@@ -88,7 +90,88 @@ function writeTask(record) {
88
90
  mkdirSync(tasksDir(), { recursive: true });
89
91
  const persisted = { ...record };
90
92
  delete persisted.list_name;
93
+ const path = taskPath(record.task_id);
94
+ const previous = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : undefined;
95
+ const status = (task) => task.deletion?.status === 'pending' ? 'deleting' : task.blocked ? `${task.state} (blocked)` : task.state;
96
+ // Read pending events under the same task lock as every mutation. Metadata edits
97
+ // and retries preserve the queue; acknowledgement cannot be resurrected by stale input.
98
+ const pending = [...(previous?.notification_events ?? [])];
99
+ if (!previous || status(previous) !== status(record)) {
100
+ const next = status(record), before = previous ? status(previous) : undefined;
101
+ pending.push({ eventId: `task:${record.task_id}:${randomUUID()}`, taskId: record.task_id,
102
+ title: `Task ${!previous ? 'created' : 'updated'}: ${record.title}`.slice(0, 160),
103
+ body: `Task ${record.task_id}: ${before ? `${before} → ` : 'created in '}${next}.`,
104
+ target: { kind: 'task', taskId: record.task_id } });
105
+ }
106
+ if (pending.length)
107
+ persisted.notification_events = pending;
108
+ else
109
+ delete persisted.notification_events;
91
110
  replaceFileAtomically(taskPath(record.task_id), JSON.stringify(persisted, null, 2) + '\n');
111
+ publishFleetResourceCommit(taskPath(record.task_id));
112
+ }
113
+ const retiredNotificationsDir = () => join(tasksDir(), '.notification-retired');
114
+ function typedTaskNotification(value, id) {
115
+ if (!value || typeof value !== 'object' || Object.keys(value).some(key => !['eventId', 'title', 'body', 'target', 'taskId'].includes(key)))
116
+ return false;
117
+ const target = value.target;
118
+ return typeof value.eventId === 'string' && value.eventId.startsWith(`task:${id}:`) && value.eventId.length <= 512
119
+ && value.taskId === id && !!target && typeof target === 'object' && !Array.isArray(target)
120
+ && Object.keys(target).every(key => ['kind', 'taskId'].includes(key)) && target.taskId === id
121
+ && (target.kind === 'task' || (target.kind === 'task-deleted' && value.eventId === `task:${id}:deleted`))
122
+ && typeof value.title === 'string' && !!value.title.trim() && value.title.length <= 160
123
+ && typeof value.body === 'string' && !!value.body.trim() && value.body.length <= 512;
124
+ }
125
+ /** Atomic snapshots contain the events, including intermediate states between polls. */
126
+ export function pendingTaskNotifications(warn = () => { }) {
127
+ const result = [];
128
+ for (const dir of [tasksDir(), retiredNotificationsDir()]) {
129
+ if (!existsSync(dir))
130
+ continue;
131
+ for (const file of readdirSync(dir).filter(file => TASK_ID_PATTERN.test(file.slice(0, -5)) && file.endsWith('.json'))) {
132
+ try {
133
+ const path = join(dir, file), stat = lstatSync(path);
134
+ if (!stat.isFile() || stat.isSymbolicLink())
135
+ throw new Error('invalid notification source');
136
+ const value = JSON.parse(readFileSync(path, 'utf8'));
137
+ const id = file.slice(0, -5);
138
+ // A prepared deletion copy has no authority while the original still exists.
139
+ if (dir === retiredNotificationsDir() && existsSync(taskPath(id)))
140
+ continue;
141
+ if (value.notification_events === undefined)
142
+ continue;
143
+ if (!Array.isArray(value.notification_events))
144
+ throw new Error('invalid notification events');
145
+ // Only typed task events are delivered. Unsupported entries are never sent or converted,
146
+ // and they do not block later lifecycle events.
147
+ const events = value.notification_events.filter(event => typedTaskNotification(event, id));
148
+ if (events.length)
149
+ result.push({ taskId: id, events });
150
+ }
151
+ catch (error) {
152
+ if (!isNotFoundError(error))
153
+ warn('Task notification record unavailable; other task deliveries continue');
154
+ }
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+ /** Service event-id dedupe covers acceptance followed by a crash before this acknowledgement. */
160
+ export function acknowledgeTaskNotification(id, eventId) {
161
+ assertCanonicalTaskId(id);
162
+ withTaskLock(id, () => {
163
+ for (const path of [taskPath(id), join(retiredNotificationsDir(), `${id}.json`)]) {
164
+ if (!existsSync(path))
165
+ continue;
166
+ const value = JSON.parse(readFileSync(path, 'utf8'));
167
+ if (!value.notification_events?.some(event => typedTaskNotification(event, id) && event.eventId === eventId))
168
+ continue;
169
+ value.notification_events = value.notification_events.filter(event => !typedTaskNotification(event, id) || event.eventId !== eventId);
170
+ replaceFileAtomically(path, JSON.stringify(value, null, 2) + '\n');
171
+ if (path.startsWith(retiredNotificationsDir() + '/') && !value.notification_events.length)
172
+ unlinkSync(path);
173
+ }
174
+ });
92
175
  }
93
176
  function presentTask(record) {
94
177
  const listId = record.list_id ?? DEFAULT_TASK_LIST_ID;
@@ -125,21 +208,32 @@ export function assertNoPendingDeletion(task) {
125
208
  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
209
  }
127
210
  }
211
+ function creationPlan(plan, layout) {
212
+ return JSON.stringify([plan?.plan_hash ?? null, storedRoomLaunchPolicy(plan?.room_policy) ?? null,
213
+ layout ? `${layout.name}@${layout.definition_hash}` : null]);
214
+ }
128
215
  export function createTask(input) {
129
216
  const key = input.idempotency_key ?? randomUUID();
130
- const existing = findByIdempotencyKey(key);
217
+ const existing = listTasks({ includeDeleting: true }).find(task => task.idempotency_key === key);
218
+ const requestedPlan = creationPlan(input.execution_plan, input.layout);
131
219
  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))
220
+ // After a Backlog plan edit the current plan differs from the one created; retries compare the original request.
221
+ if ((existing.creation_plan ?? creationPlan(existing.execution_plan, existing.layout)) !== requestedPlan)
137
222
  throw new TaskStateError(`idempotency key '${key}' was already used with a different execution plan`);
138
- return existing;
223
+ return withTaskLock(existing.task_id, () => {
224
+ const fresh = readTask(existing.task_id);
225
+ assertNoPendingDeletion(fresh);
226
+ if (fresh.workspace)
227
+ ensureWorkspace(fresh.workspace);
228
+ return fresh;
229
+ });
139
230
  }
231
+ if (input.layout && (input.execution_plan || input.no_room || input.room_id))
232
+ throw new TaskStateError('a layout task cannot also use a room template, no-room mode or a room');
140
233
  const state = input.start === false ? 'backlog' : 'provisioning';
234
+ const taskId = generateTaskId();
141
235
  const record = {
142
- task_id: generateTaskId(),
236
+ task_id: taskId,
143
237
  list_id: input.listId ?? DEFAULT_TASK_LIST_ID,
144
238
  title: input.title,
145
239
  brief: input.brief,
@@ -148,14 +242,18 @@ export function createTask(input) {
148
242
  template: input.template,
149
243
  execution_plan: input.execution_plan,
150
244
  no_room: input.no_room || undefined,
245
+ ...(input.layout ? { layout: { name: input.layout.name, run_id: `task-${taskId}`, definition_hash: input.layout.definition_hash } } : {}),
151
246
  room_id: input.room_id,
152
247
  member_roles: [],
153
248
  origin: input.origin,
154
249
  idempotency_key: key,
250
+ creation_plan: requestedPlan,
155
251
  created_at: new Date().toISOString(),
156
252
  started_at: state === 'provisioning' ? new Date().toISOString() : undefined,
157
253
  };
254
+ record.workspace = planWorkspace('task', record.task_id);
158
255
  writeTask(record);
256
+ ensureWorkspace(record.workspace);
159
257
  return readTask(record.task_id);
160
258
  }
161
259
  export function getTask(id) { return withTaskLock(id, () => readTask(id)); }
@@ -195,7 +293,10 @@ export function listTasks(filter) {
195
293
  if ((!states || states.includes(t.state)) && (!filter?.listId || t.list_id === filter.listId))
196
294
  tasks.push(t);
197
295
  }
198
- catch { /* corrupt file, skip */ }
296
+ catch (error) {
297
+ if (filter?.strict)
298
+ throw error; /* legacy CLI inventory tolerates corrupt files */
299
+ }
199
300
  }
200
301
  const order = new Map(readTaskLists().map((item, index) => [item.list_id, index]));
201
302
  return tasks.sort((a, b) => (order.get(a.list_id) - order.get(b.list_id))
@@ -526,14 +627,10 @@ export function ensureTaskDeletionReceipt(id) {
526
627
  writeDeletionReceiptForIntent(stored);
527
628
  });
528
629
  }
529
- /** Record settlement on the receipt; idempotent, tolerant of legacy absence. */
630
+ /** Erase the in-flight receipt; also heals a crash after task unlink. */
530
631
  export function completeTaskDeletionReceipt(id) {
531
- const receipt = readTaskDeletionReceipt(id);
532
- if (!receipt || receipt.settled_at)
533
- return;
534
- receipt.settled_at = new Date().toISOString();
535
- receipt.result = 'deleted';
536
- replaceFileAtomically(deletionReceiptPath(id), JSON.stringify(receipt, null, 2) + '\n');
632
+ assertCanonicalTaskId(id);
633
+ rmSync(deletionReceiptPath(id), { force: true });
537
634
  }
538
635
  /**
539
636
  * Persist the first-wins durable deletion intent. Accepts every lifecycle
@@ -552,8 +649,10 @@ export function beginTaskDeletionIntent(id, actor) {
552
649
  stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
553
650
  }
554
651
  catch (error) {
555
- if (isNotFoundError(error))
652
+ if (isNotFoundError(error)) {
653
+ completeTaskDeletionReceipt(id);
556
654
  return { status: 'already_absent' };
655
+ }
557
656
  throw error;
558
657
  }
559
658
  if (stored.deletion?.status === 'pending') {
@@ -650,6 +749,8 @@ function findCursorForSeat(id, stored, seat) {
650
749
  if (cursor && seat.identity_cid
651
750
  && cursor.identity_cid.toLowerCase() !== seat.identity_cid.toLowerCase())
652
751
  throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' identity CID mismatch between cursor and room seat`);
752
+ if (cursor?.action_id && seat.launch?.action_id && cursor.action_id !== seat.launch.action_id)
753
+ throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' creation ownership proof changed`);
653
754
  return cursor;
654
755
  }
655
756
  /**
@@ -670,11 +771,20 @@ export function upsertTaskDeletionMembersFromSeats(id, seats) {
670
771
  for (const seat of seats) {
671
772
  if (!seat.identity_cid)
672
773
  continue;
673
- if (!findCursorForSeat(id, stored, seat)) {
774
+ const cursor = findCursorForSeat(id, stored, seat);
775
+ if (!cursor) {
674
776
  stored.deletion.members.push({
675
777
  name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now,
778
+ ...(seat.launch?.action_id ? { action_id: seat.launch.action_id } : {}),
779
+ ...(seat.launch?.task_supervised ? { task_supervised: true } : {}),
676
780
  });
677
781
  }
782
+ else {
783
+ if (seat.launch?.action_id)
784
+ cursor.action_id ??= seat.launch.action_id;
785
+ if (seat.launch?.task_supervised)
786
+ cursor.task_supervised = true;
787
+ }
678
788
  }
679
789
  writeTask(stored);
680
790
  return presentTaskLenient(stored);
@@ -703,12 +813,17 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
703
813
  const evidence = seat.retirement;
704
814
  if (evidence?.phase !== 'identity_absent')
705
815
  throw new TaskStateError(`task ${id} deletion cannot checkpoint member '${seat.role_name}' before completed retirement`);
706
- const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED;
816
+ const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
817
+ || evidence.launch_id === DELETION_MEMBER_ABSENT_VERIFIED || evidence.absence_verified === true;
707
818
  if (!neverLaunched && evidence.archive_path === undefined)
708
819
  throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' retirement evidence lacks its archive`);
709
820
  if (!seat.identity_cid) {
710
- if (neverLaunched)
711
- continue; // provably never held a managed identity
821
+ if (neverLaunched) {
822
+ stored.deletion.absent_members ??= [];
823
+ if (!stored.deletion.absent_members.includes(seat.role_name))
824
+ stored.deletion.absent_members.push(seat.role_name);
825
+ continue;
826
+ }
712
827
  const proof = proofs.find(value => value.name === seat.role_name);
713
828
  if (proof && proof.launch_id === evidence.launch_id && proof.archive_path === evidence.archive_path
714
829
  && proof.launch_id === seat.launch?.launch_id && proof.action_id === seat.launch?.action_id) {
@@ -728,6 +843,10 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
728
843
  cursor = { name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now };
729
844
  stored.deletion.members.push(cursor);
730
845
  }
846
+ if (seat.launch?.action_id)
847
+ cursor.action_id ??= seat.launch.action_id;
848
+ if (seat.launch?.task_supervised)
849
+ cursor.task_supervised = true;
731
850
  if (DELETION_MEMBER_PHASE_ORDER[evidence.phase] < DELETION_MEMBER_PHASE_ORDER[cursor.phase])
732
851
  continue;
733
852
  if (cursor.launch_id !== undefined && cursor.launch_id !== evidence.launch_id)
@@ -746,6 +865,19 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
746
865
  return presentTaskLenient(stored);
747
866
  });
748
867
  }
868
+ /** Settlement-only checkpoint, under the task-operation lock after fresh archive verification.
869
+ * From here retries still verify live absence, but owned archives may be consumed.
870
+ */
871
+ export function beginTaskWorkspaceCleanup(id) {
872
+ withTaskLock(id, () => {
873
+ const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
874
+ if (stored.deletion?.status !== 'pending'
875
+ || stored.deletion.members.some(member => member.phase !== 'identity_absent'))
876
+ throw new TaskStateError('Task is not ready for workspace cleanup');
877
+ stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
878
+ writeTask(stored);
879
+ });
880
+ }
749
881
  /**
750
882
  * Physically remove a deletion-pending task record. Settlement-only: callers
751
883
  * must have completed member retirement and room cleanup first. Missing
@@ -765,6 +897,12 @@ export function unlinkDeletedTask(id) {
765
897
  }
766
898
  if (stored.deletion?.status !== 'pending')
767
899
  throw new TaskStateError(`task ${id} has no pending deletion`);
900
+ // Preserve committed deliveries outside the record before deletion. A crash
901
+ // before unlink may replay two copies; both carry identical service dedupe IDs.
902
+ const removed = { eventId: `task:${id}:deleted`, taskId: id,
903
+ title: `Task deleted: ${stored.title}`.slice(0, 160), body: `Task ${id}: deleting → deleted.`,
904
+ target: { kind: 'task-deleted', taskId: id } };
905
+ replaceFileAtomically(join(retiredNotificationsDir(), `${id}.json`), JSON.stringify({ notification_events: [...(stored.notification_events ?? []), removed] }) + '\n');
768
906
  try {
769
907
  unlinkSync(taskPath(id));
770
908
  }
@@ -776,3 +914,87 @@ export function unlinkDeletedTask(id) {
776
914
  return true;
777
915
  });
778
916
  }
917
+ /** Called only after current temp and exact identity absence have been checked. */
918
+ export function settleAbsentTaskDeletionMember(id, name) {
919
+ withTaskLock(id, () => {
920
+ const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
921
+ if (stored.deletion?.status !== 'pending')
922
+ throw new TaskStateError('Task is not deleting');
923
+ const member = stored.deletion.members.find(value => value.name === name);
924
+ if (!member)
925
+ throw new TaskStateError('Unknown deletion member');
926
+ member.phase = 'identity_absent';
927
+ member.launch_id ??= DELETION_MEMBER_ABSENT_VERIFIED;
928
+ member.updated_at = new Date().toISOString();
929
+ writeTask(stored);
930
+ });
931
+ }
932
+ /** Room-only erasure keeps the parent Task but removes its dead room/member links. */
933
+ export function detachDeletedRoom(taskId, roomId) {
934
+ withTaskLock(taskId, () => {
935
+ if (!existsSync(taskPath(taskId)))
936
+ return;
937
+ const stored = JSON.parse(readFileSync(taskPath(taskId), 'utf8'));
938
+ if (stored.room_id !== roomId || stored.deletion?.status === 'pending')
939
+ return;
940
+ delete stored.room_id;
941
+ delete stored.room_identity_cid;
942
+ stored.member_roles = [];
943
+ writeTask(stored);
944
+ });
945
+ }
946
+ /** A completed room may consume its archives while other task members still retire. */
947
+ export function beginTaskArchiveCleanup(id) {
948
+ withTaskLock(id, () => {
949
+ const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
950
+ if (stored.deletion?.status !== 'pending')
951
+ throw new TaskStateError('Task is not deleting');
952
+ stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
953
+ writeTask(stored);
954
+ });
955
+ }
956
+ /** Metadata-only edit; compare under the task lock to prevent lost updates. */
957
+ /**
958
+ * Choose, change or clear a Backlog task's Room Layout before it starts.
959
+ * `expected` is the layout name the caller saw (null = none); a different
960
+ * current choice is a stale edit. Choosing a layout drops the sealed room
961
+ * template plan; the caller releases the returned launch snapshot hash.
962
+ */
963
+ export function updateTaskLayout(id, expected, next) {
964
+ return withTaskLock(id, () => {
965
+ const task = readTask(id);
966
+ assertNoPendingDeletion(task);
967
+ assertNoPendingTerminalIntent(task);
968
+ if (task.state !== 'backlog' || task.room_id)
969
+ throw new TaskStateError(`task ${id} has started; its room layout is fixed (state '${task.state}')`);
970
+ const current = task.layout?.name ?? null;
971
+ // A replay of the same choice is idempotent (and refreshes the pinned source hash).
972
+ if (current !== expected && current !== (next?.name ?? null))
973
+ throw new TaskStateError(`task ${id} room layout changed to '${current ?? 'one room'}'; reload before changing it`);
974
+ const releasedSnapshot = next ? task.execution_plan?.snapshot.launch_snapshot_hash : undefined;
975
+ if (next) {
976
+ task.layout = { name: next.name, run_id: `task-${task.task_id}`, definition_hash: next.definition_hash };
977
+ delete task.execution_plan;
978
+ delete task.template;
979
+ delete task.no_room;
980
+ }
981
+ else
982
+ delete task.layout;
983
+ task.room_plan_changed_at = new Date().toISOString();
984
+ writeTask(task);
985
+ return { task: readTask(id), ...(releasedSnapshot ? { releasedSnapshot } : {}) };
986
+ });
987
+ }
988
+ export function updateTaskBrief(id, brief, expectedBrief) {
989
+ assertCanonicalTaskId(id);
990
+ return withTaskLock(id, () => {
991
+ const task = readTask(id);
992
+ assertNoPendingDeletion(task);
993
+ assertNoPendingTerminalIntent(task);
994
+ if ((task.brief ?? '') !== expectedBrief && task.brief !== brief)
995
+ throw new TaskStateError('Description changed elsewhere. Reload the task before saving.');
996
+ task.brief = brief;
997
+ writeTask(task);
998
+ return task;
999
+ });
1000
+ }
@@ -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;
@@ -88,13 +90,36 @@ export interface TaskDeletionIntent {
88
90
  /** Snapshot of managed members at acceptance; the missing-room retirement evidence. */
89
91
  members: TaskDeletionMemberCursor[];
90
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;
91
96
  error?: string;
92
97
  error_at?: string;
93
98
  recovery_hint?: string;
94
99
  first_failure?: string;
95
100
  first_recovery_hint?: string;
96
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
+ target: {
114
+ kind: 'task' | 'task-deleted';
115
+ taskId: string;
116
+ };
117
+ taskId: string;
118
+ }
97
119
  export interface TaskRecord {
120
+ /** Pending lifecycle events, committed atomically with their task change. */
121
+ notification_events?: TaskNotificationEvent[];
122
+ workspace?: import('./workspace.js').OwnedWorkspace;
98
123
  task_id: string;
99
124
  /** Stable organizational list identifier. Missing legacy values mean `default`. */
100
125
  list_id: string;
@@ -115,6 +140,12 @@ export interface TaskRecord {
115
140
  plan_hash: string;
116
141
  };
117
142
  no_room?: boolean;
143
+ /** Multi-room task: rooms and participants come from a Room Layout run. */
144
+ layout?: TaskLayoutLink;
145
+ /** Set when the room plan (layout or one-room template) was changed after creation, before start. */
146
+ room_plan_changed_at?: string;
147
+ /** Fingerprint of the room plan requested at creation; idempotent retries compare against it after later plan edits. */
148
+ creation_plan?: string;
118
149
  room_id?: string;
119
150
  room_identity_cid?: string;
120
151
  member_roles: TaskMemberRole[];
@@ -153,6 +184,7 @@ export interface RoomRoleBriefingDefinition {
153
184
  last_error?: string;
154
185
  }
155
186
  export interface RoomMemberLaunchState {
187
+ task_supervised?: boolean;
156
188
  state: 'pending' | 'intent' | 'launched' | 'stopped' | 'failed';
157
189
  attempt: number;
158
190
  action_id?: string;
@@ -209,6 +241,7 @@ export type RoomHistoryEvidence = {
209
241
  };
210
242
  export type MemberRetirementPhase = 'stop_requested' | 'liveness_absent' | 'archive_secured' | 'identity_absent';
211
243
  export interface MemberRetirement {
244
+ absence_verified?: boolean;
212
245
  phase: MemberRetirementPhase;
213
246
  launch_id: string;
214
247
  updated_at: string;
@@ -235,12 +268,14 @@ export interface RoomMemberSeat {
235
268
  retirement?: MemberRetirement;
236
269
  }
237
270
  export interface RoomOrchestrationRecord {
271
+ workspace?: import('./workspace.js').OwnedWorkspace;
238
272
  room_id: string;
239
273
  room_identity_cid?: string;
240
274
  room_name: string;
241
275
  goal?: string;
242
276
  task_id?: string;
243
277
  template_snapshot?: TemplateSnapshot;
278
+ additional_member_templates?: TemplateSnapshot[];
244
279
  /** Resolved once at launch; legacy absence means non-anonymous. */
245
280
  room_policy?: RoomLaunchPolicy;
246
281
  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;