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

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 (429) hide show
  1. package/README.md +280 -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 +22 -0
  11. package/dist/agent-ours/mcp-endpoint.js +101 -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 +27 -1
  15. package/dist/agent-ours/service.js +107 -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 +120 -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 +402 -38
  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 +227 -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 +13 -0
  74. package/dist/fleet-command-audit.js +31 -9
  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 +55 -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 +182 -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 +66 -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-review.d.ts +113 -0
  180. package/dist/rooms-tasks/task-review.js +561 -0
  181. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  182. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  183. package/dist/rooms-tasks/task-state.d.ts +51 -5
  184. package/dist/rooms-tasks/task-state.js +358 -50
  185. package/dist/rooms-tasks/task-tools.d.ts +35 -0
  186. package/dist/rooms-tasks/task-tools.js +134 -0
  187. package/dist/rooms-tasks/types.d.ts +78 -0
  188. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  189. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  190. package/dist/rooms-tasks/workspace.d.ts +35 -0
  191. package/dist/rooms-tasks/workspace.js +325 -0
  192. package/dist/runner.d.ts +33 -4
  193. package/dist/runner.js +410 -62
  194. package/dist/session/acp.d.ts +75 -2
  195. package/dist/session/acp.js +345 -82
  196. package/dist/session/arbiter.d.ts +98 -0
  197. package/dist/session/arbiter.js +272 -3
  198. package/dist/session/codex-app-server.d.ts +45 -1
  199. package/dist/session/codex-app-server.js +225 -32
  200. package/dist/session/control.d.ts +17 -3
  201. package/dist/session/control.js +219 -23
  202. package/dist/session/conversation-normalizer.d.ts +2 -1
  203. package/dist/session/conversation-normalizer.js +1 -1
  204. package/dist/session/conversation-observation.d.ts +25 -0
  205. package/dist/session/conversation-observation.js +100 -0
  206. package/dist/session/conversation-replay.d.ts +50 -0
  207. package/dist/session/conversation-replay.js +203 -0
  208. package/dist/session/conversation-store.d.ts +15 -1
  209. package/dist/session/conversation-store.js +213 -26
  210. package/dist/session/conversation-types.d.ts +57 -4
  211. package/dist/session/prompt-batch.d.ts +79 -0
  212. package/dist/session/prompt-batch.js +120 -0
  213. package/dist/session/task-notice.d.ts +14 -0
  214. package/dist/session/task-notice.js +13 -0
  215. package/dist/session/types.d.ts +42 -1
  216. package/dist/setup-tunnel-preflight.d.ts +34 -0
  217. package/dist/setup-tunnel-preflight.js +62 -0
  218. package/dist/socket-path.d.ts +2 -0
  219. package/dist/socket-path.js +22 -0
  220. package/dist/spawn.d.ts +5 -1
  221. package/dist/spawn.js +40 -4
  222. package/dist/startup-prompt.d.ts +15 -0
  223. package/dist/startup-prompt.js +71 -0
  224. package/dist/startup-readiness.d.ts +32 -0
  225. package/dist/startup-readiness.js +101 -0
  226. package/dist/startup-telemetry.d.ts +16 -0
  227. package/dist/startup-telemetry.js +46 -0
  228. package/dist/subscriptions/cli.d.ts +91 -0
  229. package/dist/subscriptions/cli.js +223 -0
  230. package/dist/subscriptions/launch.d.ts +37 -0
  231. package/dist/subscriptions/launch.js +93 -0
  232. package/dist/subscriptions/login.d.ts +77 -0
  233. package/dist/subscriptions/login.js +215 -0
  234. package/dist/subscriptions/observed.d.ts +7 -0
  235. package/dist/subscriptions/observed.js +44 -0
  236. package/dist/subscriptions/probe.d.ts +10 -0
  237. package/dist/subscriptions/probe.js +73 -0
  238. package/dist/subscriptions/service.d.ts +102 -0
  239. package/dist/subscriptions/service.js +364 -0
  240. package/dist/subscriptions/store.d.ts +71 -0
  241. package/dist/subscriptions/store.js +143 -0
  242. package/dist/supervisor/adoption.d.ts +8 -0
  243. package/dist/supervisor/adoption.js +78 -0
  244. package/dist/supervisor/catalog.d.ts +62 -0
  245. package/dist/supervisor/catalog.js +254 -0
  246. package/dist/supervisor/fleet.d.ts +8 -0
  247. package/dist/supervisor/fleet.js +89 -0
  248. package/dist/supervisor/index.js +3 -6
  249. package/dist/supervisor/launchd.d.ts +1 -1
  250. package/dist/supervisor/launchd.js +26 -13
  251. package/dist/supervisor/legacy.d.ts +35 -0
  252. package/dist/supervisor/legacy.js +524 -0
  253. package/dist/supervisor/manager.d.ts +15 -0
  254. package/dist/supervisor/manager.js +270 -0
  255. package/dist/supervisor/registration-refresh.d.ts +45 -0
  256. package/dist/supervisor/registration-refresh.js +266 -0
  257. package/dist/supervisor/scope.d.ts +4 -0
  258. package/dist/supervisor/scope.js +10 -0
  259. package/dist/supervisor/systemd.d.ts +2 -2
  260. package/dist/supervisor/systemd.js +19 -33
  261. package/dist/task-supervision.d.ts +9 -0
  262. package/dist/task-supervision.js +145 -0
  263. package/dist/temp-idle.d.ts +17 -0
  264. package/dist/temp-idle.js +42 -0
  265. package/dist/temp-lifecycle.d.ts +23 -8
  266. package/dist/temp-lifecycle.js +123 -66
  267. package/dist/temp-supervisor-recovery.d.ts +1 -1
  268. package/dist/temp-supervisor-recovery.js +2 -2
  269. package/dist/tunnel-setup-completion.d.ts +7 -0
  270. package/dist/tunnel-setup-completion.js +13 -0
  271. package/dist/tunnel-setup-input.d.ts +19 -0
  272. package/dist/tunnel-setup-input.js +130 -0
  273. package/dist/watchdog/query.d.ts +3 -1
  274. package/dist/watchdog/query.js +2 -1
  275. package/dist/web/agent-attachments.d.ts +29 -0
  276. package/dist/web/agent-attachments.js +162 -0
  277. package/dist/web/auth.d.ts +65 -2
  278. package/dist/web/auth.js +201 -11
  279. package/dist/web/available-port.d.ts +2 -0
  280. package/dist/web/available-port.js +23 -0
  281. package/dist/web/control.d.ts +3 -3
  282. package/dist/web/control.js +10 -8
  283. package/dist/web/conversation-stream-codec.d.ts +23 -0
  284. package/dist/web/conversation-stream-codec.js +110 -0
  285. package/dist/web/conversation-stream.d.ts +26 -0
  286. package/dist/web/conversation-stream.js +239 -0
  287. package/dist/web/events.d.ts +2 -0
  288. package/dist/web/events.js +9 -0
  289. package/dist/web/fleet-config-service.d.ts +7 -1
  290. package/dist/web/fleet-config-service.js +61 -16
  291. package/dist/web/prefix-gateway.d.ts +32 -0
  292. package/dist/web/prefix-gateway.js +248 -0
  293. package/dist/web/resource-views.d.ts +135 -0
  294. package/dist/web/resource-views.js +494 -0
  295. package/dist/web/runtime.d.ts +3 -1
  296. package/dist/web/runtime.js +132 -34
  297. package/dist/web/server.d.ts +19 -0
  298. package/dist/web/server.js +893 -71
  299. package/dist/web/service.d.ts +11 -2
  300. package/dist/web/service.js +30 -4
  301. package/dist/web/workspace-devices.d.ts +107 -0
  302. package/dist/web/workspace-devices.js +277 -0
  303. package/dist/web/workspace-tunnel.d.ts +1 -0
  304. package/dist/web/workspace-tunnel.js +34 -0
  305. package/dist/workspace-enrollment.d.ts +83 -0
  306. package/dist/workspace-enrollment.js +605 -0
  307. package/dist/workspace-replacement.d.ts +66 -0
  308. package/dist/workspace-replacement.js +238 -0
  309. package/docs/acp-files.md +146 -0
  310. package/docs/conversation-stream.md +216 -0
  311. package/docs/fleet-migration.md +54 -0
  312. package/docs/live-voice.md +13 -0
  313. package/docs/room-layouts.md +227 -0
  314. package/docs/self-reminders.md +374 -0
  315. package/docs/task-progress-review.md +143 -0
  316. package/docs/task-workspaces.md +225 -0
  317. package/docs/validation/managed-cli-permissions.md +421 -0
  318. package/examples/room-layouts/fresh.yaml +27 -0
  319. package/examples/room-layouts/scoped.yaml +21 -0
  320. package/examples/room-layouts/shared.yaml +19 -0
  321. package/package.json +25 -20
  322. package/presets/README.md +78 -0
  323. package/presets/brain-catalog.json +8 -2
  324. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  325. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  326. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  327. package/presets/fleet/agent_templates/LocalCoordinator.yaml +6 -3
  328. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  329. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  330. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  331. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  332. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  333. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  334. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  335. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  336. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  337. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  338. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  339. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  340. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  341. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  342. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  343. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  344. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  345. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  351. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  352. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  353. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  354. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  384. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  385. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  386. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  387. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  390. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  391. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  392. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  393. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  394. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  396. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  397. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  398. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  399. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  400. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  406. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  407. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  408. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  409. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  410. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  411. package/presets/fleet/roles/Coordinator.yaml +21 -13
  412. package/presets/fleet/roles/Critic.yaml +24 -0
  413. package/presets/fleet/roles/Developer.yaml +32 -0
  414. package/presets/fleet/roles/Engineer.yaml +72 -0
  415. package/presets/fleet/roles/LocalCoordinator.yaml +17 -0
  416. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  417. package/presets/fleet/room_layouts/pair.yaml +24 -0
  418. package/presets/fleet/room_layouts/single.yaml +17 -0
  419. package/presets/fleet/room_layouts/team.yaml +30 -0
  420. package/presets/fleet/room_templates/engineering.yaml +19 -0
  421. package/presets/manifest.json +2 -2
  422. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  423. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  424. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  425. package/dist/web-app/icons/ours-fleet.svg +0 -4
  426. package/dist/web-app/index.html +0 -17
  427. package/dist/web-app/manifest.webmanifest +0 -15
  428. package/dist/web-app/offline.html +0 -18
  429. package/dist/web-app/sw.js +0 -51
@@ -2,6 +2,39 @@ import { randomUUID } from 'node:crypto';
2
2
  import { attachOursClient } from '@ours.network/sdk/client';
3
3
  import { readPrivateFile } from '@ours.network/sdk/connector';
4
4
  import { CoworkProtocolError, CoworkUnavailableError } from './cowork-adapter.js';
5
+ const TRANSPORT_CODES = new Set(['EACCES', 'ENOENT', 'ECONNREFUSED', 'ECONNRESET',
6
+ 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET',
7
+ 'CERT_HAS_EXPIRED', 'ERR_TLS_CERT_ALTNAME_INVALID']);
8
+ /** Failure kinds a dependency still starting (or restarting) produces. */
9
+ const RETRYABLE_KINDS = new Set(['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'EAI_AGAIN',
10
+ 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET', 'timeout']);
11
+ /** Statuses a reverse proxy answers with while its upstream is not serving. */
12
+ const GATEWAY_UNAVAILABLE = new Set([502, 503, 504]);
13
+ /** Media type only, never parameters; anything unusual collapses to a fixed label. */
14
+ function safeContentType(response) {
15
+ const value = (response.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();
16
+ return /^[a-z0-9.+-]{1,40}\/[a-z0-9.+-]{1,40}$/.test(value) ? value : 'unknown type';
17
+ }
18
+ function safeFailureKind(error, signal) {
19
+ if (signal.aborted)
20
+ return 'timeout';
21
+ for (const value of [error, error instanceof Error ? error.cause : undefined]) {
22
+ if (!value || typeof value !== 'object')
23
+ continue;
24
+ const code = value.code;
25
+ if (typeof code === 'string' && TRANSPORT_CODES.has(code))
26
+ return code;
27
+ const name = value.name;
28
+ if (name === 'TimeoutError')
29
+ return 'timeout';
30
+ if (name === 'AbortError')
31
+ return 'aborted';
32
+ }
33
+ return 'transport_error';
34
+ }
35
+ function safeOperation(method) {
36
+ return /^[a-z][a-z0-9_.-]{0,79}$/.test(method) ? method : 'management';
37
+ }
5
38
  /** Same RPC protocol as the local socket; never replay a possibly delivered mutation. */
6
39
  export async function coworkHttpCall(profile, method, params, timeoutMs) {
7
40
  const id = randomUUID();
@@ -9,11 +42,15 @@ export async function coworkHttpCall(profile, method, params, timeoutMs) {
9
42
  const body = JSON.stringify({ version: 1, id, method, params });
10
43
  if (Buffer.byteLength(body) > 1024 * 1024)
11
44
  throw new CoworkProtocolError(method, 'request exceeded 1 MiB');
45
+ let stage = 'server verification';
12
46
  try {
13
47
  const client = await attachOursClient({ endpoint: profile.endpoint, expectedInstanceId: profile.expectedInstanceId,
14
48
  credentialPath: profile.credentialPath, sessionMode: 'external', leaseToken: `fleet-cowork-check-${id}`, env: {}, requestSignal: signal });
49
+ stage = 'verification cleanup';
15
50
  await client.close();
51
+ stage = 'credential read';
16
52
  const token = readPrivateFile(profile.credentialPath, 4096).toString('utf8').trim();
53
+ stage = 'management request';
17
54
  const response = await fetch(profile.serverUrl + '/cowork/management/rpc', {
18
55
  method: 'POST', redirect: 'error', signal,
19
56
  headers: { 'content-type': 'application/json', 'x-ours-api-token': token },
@@ -26,6 +63,7 @@ export async function coworkHttpCall(profile, method, params, timeoutMs) {
26
63
  const reader = response.body?.getReader();
27
64
  if (!reader)
28
65
  throw new CoworkProtocolError(method, 'empty HTTP response');
66
+ stage = 'management response';
29
67
  const chunks = [];
30
68
  let size = 0;
31
69
  try {
@@ -42,16 +80,25 @@ export async function coworkHttpCall(profile, method, params, timeoutMs) {
42
80
  finally {
43
81
  await reader.cancel();
44
82
  }
83
+ // Status and media type, never the body: a proxy page may echo request data.
84
+ const shape = `HTTP ${response.status}, ${safeContentType(response)}`;
45
85
  let rpc;
86
+ let malformed = false;
46
87
  try {
47
88
  rpc = JSON.parse(Buffer.concat(chunks).toString('utf8'));
48
89
  }
49
90
  catch {
50
- throw new CoworkProtocolError(method, 'server returned malformed JSON');
91
+ malformed = true;
92
+ }
93
+ if (malformed || !rpc || Array.isArray(rpc) || rpc.version !== 1 || rpc.id !== id
94
+ || Object.hasOwn(rpc, 'result') === Object.hasOwn(rpc, 'error')) {
95
+ // A gateway whose Cowork upstream is still starting answers with its own
96
+ // error page. That is unavailability, not a Cowork protocol violation; a
97
+ // well-formed RPC error under the same status keeps its meaning below.
98
+ if (GATEWAY_UNAVAILABLE.has(response.status))
99
+ throw new CoworkUnavailableError(`Cowork HTTP ${safeOperation(method)} failed: gateway answered ${shape} without an RPC response`, { retryable: true });
100
+ throw new CoworkProtocolError(method, `server returned ${malformed ? 'malformed JSON' : 'an invalid RPC response'} (${shape})`);
51
101
  }
52
- if (!rpc || Array.isArray(rpc) || rpc.version !== 1 || rpc.id !== id
53
- || Object.hasOwn(rpc, 'result') === Object.hasOwn(rpc, 'error'))
54
- throw new CoworkProtocolError(method, 'server returned an invalid RPC response');
55
102
  if (Object.hasOwn(rpc, 'error')) {
56
103
  const error = rpc.error;
57
104
  if (!error || typeof error.code !== 'string' || typeof error.message !== 'string')
@@ -63,8 +110,11 @@ export async function coworkHttpCall(profile, method, params, timeoutMs) {
63
110
  return rpc.result;
64
111
  }
65
112
  catch (error) {
66
- if (error instanceof CoworkProtocolError)
113
+ if (error instanceof CoworkProtocolError || error instanceof CoworkUnavailableError)
67
114
  throw error;
68
- throw new CoworkUnavailableError('Cowork HTTP management failed; check the selected server and credential', { cause: error });
115
+ // Durable cleanup records retain only .message. Keep the RPC step and a
116
+ // bounded allowlisted category there, never raw URLs, credentials or cause text.
117
+ const kind = safeFailureKind(error, signal);
118
+ throw new CoworkUnavailableError(`Cowork HTTP ${safeOperation(method)} failed during ${stage} (${kind})`, { cause: error, retryable: stage !== 'credential read' && RETRYABLE_KINDS.has(kind) });
69
119
  }
70
120
  }
@@ -3,7 +3,7 @@ import { type CoworkAdapter } from './cowork-adapter.js';
3
3
  import { type TaskDeletionAcceptance } from './task-state.js';
4
4
  import type { TaskDeletionActor, TaskRecord } from './types.js';
5
5
  export { DELETION_MEMBER_ABSENT_VERIFIED } from './task-state.js';
6
- type DeletionCowork = Pick<CoworkAdapter, 'closeRoom' | 'deleteRoom'>;
6
+ type DeletionCowork = Pick<CoworkAdapter, 'closeRoom' | 'deleteRoom'> & Partial<Pick<CoworkAdapter, 'getRoom'>>;
7
7
  export interface TaskDeletionSettleDeps {
8
8
  roomClose?: RoomCloseDeps;
9
9
  /** Test seam for the temp-state existence proof. */
@@ -1,14 +1,21 @@
1
- import { proveArchivedAbsence, verifyArchivedAbsence } from './archived-absence.js';
1
+ import { eraseMemberArtifacts } from './erasure.js';
2
+ import { assertMemberNotPermanent } from './member-ownership.js';
3
+ import { retireTaskMemberService } from './task-service-retirement.js';
4
+ import { assertTaskRegistrationsAbsent } from '../supervisor/catalog.js';
5
+ import { proveArchivedAbsence, verifyArchivedAbsence, verifyArchivedMemberStillAbsent } from './archived-absence.js';
2
6
  import { existsSync } from 'node:fs';
3
7
  import { withFileLock } from '../atomic-file.js';
4
8
  import { agentDir } from '../paths.js';
9
+ import { deleteWorkspace, assertWorkspaceDeletable } from './workspace.js';
10
+ import { collectWorkspaceArchives } from './workspace-artifacts.js';
5
11
  import { secureStoppedTempArchive, stopTempSupervisor } from '../temp-lifecycle.js';
6
- import { closeManagedRoom, identityCidPresent, inspectMember, removeExactMemberIdentity, waitForLivenessAbsent, } from './close.js';
12
+ import { closeManagedRoom, identityCidPresent, inspectMember, assertMemberIdentityAbsent, removeExactMemberIdentity, waitForLivenessAbsent, } from './close.js';
7
13
  import { CoworkProtocolError } from './cowork-adapter.js';
8
14
  import { deleteRoomRecord, getRoomRecord, listRoomRecords } from './room-state.js';
9
15
  import { acquireLaunchSnapshotLock, releaseLaunchSnapshotForDeletingTask } from './launch-snapshot.js';
10
16
  import { TASK_OPERATION_LOCK_STALE_MS, taskOperationLockPath } from './terminal.js';
11
- import { advanceTaskDeletionMember, beginTaskDeletionIntent, completeTaskDeletionReceipt, ensureTaskDeletionReceipt, getDeletingTask, importTaskDeletionRetirementEvidence, setTaskDeletionError, TaskStateError, unlinkDeletedTask, upsertTaskDeletionMembersFromSeats, } from './task-state.js';
17
+ import { advanceTaskDeletionMember, settleAbsentTaskDeletionMember, beginTaskDeletionIntent, completeTaskDeletionReceipt, ensureTaskDeletionReceipt, getDeletingTask, importTaskDeletionRetirementEvidence, setTaskDeletionError, TaskStateError, beginTaskArchiveCleanup, unlinkDeletedTask, upsertTaskDeletionMembersFromSeats, } from './task-state.js';
18
+ import { deleteTaskReviewStore } from './task-review.js';
12
19
  export { DELETION_MEMBER_ABSENT_VERIFIED } from './task-state.js';
13
20
  import { DELETION_MEMBER_ABSENT_VERIFIED } from './task-state.js';
14
21
  /**
@@ -41,6 +48,7 @@ function tolerantCowork(cowork) {
41
48
  }
42
49
  };
43
50
  return {
51
+ ...(cowork.getRoom ? { getRoom: cowork.getRoom.bind(cowork) } : {}),
44
52
  closeRoom: roomId => tolerate(() => cowork.closeRoom(roomId)),
45
53
  deleteRoom: roomId => tolerate(() => cowork.deleteRoom(roomId)),
46
54
  };
@@ -49,6 +57,7 @@ function cursorSeat(cursor) {
49
57
  return {
50
58
  role_name: cursor.name, identity_cid: cursor.identity_cid,
51
59
  slot: cursor.name, cowork_role: 'member', seat_state: 'active',
60
+ ...(cursor.launch_id ? { retirement: { phase: 'identity_absent', launch_id: cursor.launch_id, archive_path: cursor.archive_path, updated_at: cursor.updated_at } } : {}),
52
61
  };
53
62
  }
54
63
  /**
@@ -59,10 +68,23 @@ function cursorSeat(cursor) {
59
68
  * the recorded CID surviving under any name blocks settlement.
60
69
  */
61
70
  async function retireCursorMember(taskId, cursor, deps) {
71
+ assertMemberNotPermanent(cursor.name);
72
+ await (deps.roomClose?.retireTaskService ?? retireTaskMemberService)(cursor.name, { taskId,
73
+ creationActionId: cursor.action_id,
74
+ taskSupervised: cursor.task_supervised,
75
+ ...((cursor.launch_id && !['never-launched', 'absent-verified'].includes(cursor.launch_id)) ? { launchId: cursor.launch_id } : {}),
76
+ });
62
77
  let phase = cursor.phase;
63
78
  let launchId = cursor.launch_id;
64
- if (phase === 'identity_absent')
79
+ if (phase === 'identity_absent') {
80
+ if (!deps.roomClose?.inspectMember) {
81
+ if ((deps.hasTempState ?? (name => existsSync(agentDir(name, true))))(cursor.name))
82
+ throw new Error('Retired deletion member has replacement live state');
83
+ if (await (deps.identityCidPresent ?? identityCidPresent)(cursor.identity_cid))
84
+ throw new Error('Retired deletion member identity reappeared');
85
+ }
65
86
  return;
87
+ }
66
88
  const seat = cursorSeat(cursor);
67
89
  const roomClose = deps.roomClose ?? {};
68
90
  if (phase === 'pending') {
@@ -86,7 +108,16 @@ async function retireCursorMember(taskId, cursor, deps) {
86
108
  phase = 'stop_requested';
87
109
  launchId = ownership.launchId;
88
110
  }
111
+ if (!roomClose.inspectMember && !existsSync(agentDir(cursor.name, true))) {
112
+ await (roomClose.removeIdentity ?? removeExactMemberIdentity)(seat);
113
+ if (existsSync(agentDir(cursor.name, true)))
114
+ throw new Error('Deletion member acquired replacement state');
115
+ settleAbsentTaskDeletionMember(taskId, cursor.name);
116
+ return;
117
+ }
89
118
  if (phase === 'stop_requested') {
119
+ if (!roomClose.inspectMember && (await inspectMember(seat)).launchId !== launchId)
120
+ throw new Error('Deletion member launch changed before stop');
90
121
  await (roomClose.requestStop
91
122
  ?? (async (role) => { await stopTempSupervisor(role); }))(cursor.name);
92
123
  await (roomClose.waitForLivenessAbsent ?? waitForLivenessAbsent)(cursor.name, launchId);
@@ -149,8 +180,15 @@ export async function settleTaskDeletion(input) {
149
180
  const seats = closed?.member_seats ?? record.member_seats;
150
181
  const proofs = [];
151
182
  for (const seat of seats) {
152
- if (!seat.identity_cid && seat.retirement?.launch_id !== 'never-launched')
153
- proofs.push(await proveArchivedAbsence(seat));
183
+ if (!seat.identity_cid && !seat.retirement?.absence_verified && !['never-launched', 'absent-verified'].includes(seat.retirement?.launch_id ?? '')) {
184
+ const prior = task.deletion.archived_absences?.find(proof => proof.name === seat.role_name);
185
+ if (task.deletion.workspace_cleanup_started_at && prior && !existsSync(prior.archive_path)) {
186
+ await verifyArchivedMemberStillAbsent(prior);
187
+ proofs.push(prior);
188
+ }
189
+ else
190
+ proofs.push(await proveArchivedAbsence(seat));
191
+ }
154
192
  }
155
193
  // No await between the last live-state fence and durable checkpoint.
156
194
  for (const proof of proofs) {
@@ -162,14 +200,17 @@ export async function settleTaskDeletion(input) {
162
200
  importTaskDeletionRetirementEvidence(taskId, seats, proofs);
163
201
  await cowork.deleteRoom(record.room_id);
164
202
  for (const proof of proofs)
165
- await verifyArchivedAbsence(proof);
203
+ await (task.deletion.workspace_cleanup_started_at && !existsSync(proof.archive_path) ? verifyArchivedMemberStillAbsent : verifyArchivedAbsence)(proof);
204
+ beginTaskArchiveCleanup(taskId);
205
+ await eraseMemberArtifacts('room', record.room_id, seats, [record.room_id]);
166
206
  for (const proof of proofs)
167
207
  if (existsSync(agentDir(proof.name, true)))
168
208
  throw new Error('Archived member replacement before room unlink');
169
209
  deleteRoomRecord(record.room_id);
170
210
  }
171
- for (const proof of getDeletingTask(taskId).deletion.archived_absences ?? [])
172
- await verifyArchivedAbsence(proof);
211
+ const deletion = getDeletingTask(taskId).deletion;
212
+ for (const proof of deletion.archived_absences ?? [])
213
+ await (deletion.workspace_cleanup_started_at && !existsSync(proof.archive_path) ? verifyArchivedMemberStillAbsent : verifyArchivedAbsence)(proof);
173
214
  // Members whose room record is gone (crash after record deletion, or
174
215
  // legacy state): resume from the durable cursors.
175
216
  for (const cursor of getDeletingTask(taskId).deletion.members) {
@@ -207,12 +248,27 @@ export async function settleTaskDeletion(input) {
207
248
  if (task.deletion.members.some(member => member.phase !== 'identity_absent'))
208
249
  throw new TaskStateError(`task ${taskId} has unretired members at deletion finalization`);
209
250
  for (const proof of task.deletion.archived_absences ?? [])
210
- await verifyArchivedAbsence(proof);
251
+ await (task.deletion.workspace_cleanup_started_at && !existsSync(proof.archive_path) ? verifyArchivedMemberStillAbsent : verifyArchivedAbsence)(proof);
211
252
  for (const proof of task.deletion.archived_absences ?? [])
212
253
  if (existsSync(agentDir(proof.name, true)))
213
254
  throw new Error('Archived member replacement before task unlink');
255
+ for (const name of task.deletion.absent_members ?? []) {
256
+ if (existsSync(agentDir(name, true)))
257
+ throw new Error('Absent member acquired replacement state');
258
+ await assertMemberIdentityAbsent({ role_name: name, slot: name, cowork_role: 'member', seat_state: 'removed' });
259
+ }
214
260
  if (cleanup.snapshotHash)
215
261
  releaseLaunchSnapshotForDeletingTask(cleanup.snapshotHash, taskId);
262
+ assertTaskRegistrationsAbsent(taskId);
263
+ await eraseMemberArtifacts('task', taskId, task.deletion.members.map(cursorSeat), task.deletion.room_id ? [task.deletion.room_id] : []);
264
+ if (task.workspace) {
265
+ assertWorkspaceDeletable(task.workspace, 'task', taskId);
266
+ beginTaskArchiveCleanup(taskId);
267
+ collectWorkspaceArchives(task.workspace);
268
+ deleteWorkspace(task.workspace, 'task', taskId);
269
+ }
270
+ // Retained To review files go with the task; the pending deletion already refuses new attachments.
271
+ deleteTaskReviewStore(taskId);
216
272
  unlinkDeletedTask(taskId);
217
273
  }
218
274
  catch (error) {
@@ -0,0 +1,6 @@
1
+ import type { RoomMemberSeat } from './types.js';
2
+ /** Erase only proven launch artifacts. Each atomic rename transfers ownership to
3
+ * a random, durable tombstone before recursive removal can destroy its proof.
4
+ * The caller holds the task/room lifecycle lock and has retired all writers.
5
+ */
6
+ export declare function eraseMemberArtifacts(owner: 'room' | 'task', id: string, seats: readonly RoomMemberSeat[], roomIds: readonly string[]): Promise<void>;
@@ -0,0 +1,173 @@
1
+ import { eraseResourcePresentations } from '../erased-resources.js';
2
+ import { assertMemberNotPermanent } from './member-ownership.js';
3
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync } from 'node:fs';
4
+ import { createHash, randomUUID } from 'node:crypto';
5
+ import { dirname, join, relative } from 'node:path';
6
+ import { parse } from 'yaml';
7
+ import { replaceFileAtomically } from '../atomic-file.js';
8
+ import { agentDir, stateRoot } from '../paths.js';
9
+ import { readTempSupervisorForScan, tempSupervisorLiveness, eraseTerminationEvents } from '../temp-lifecycle.js';
10
+ import { assertSafeAncestors, auditWorkspaceGit, prepareOwnedDeletionTree } from './workspace.js';
11
+ function entries(path) {
12
+ try {
13
+ return readdirSync(path);
14
+ }
15
+ catch (error) {
16
+ if (error.code === 'ENOENT')
17
+ return [];
18
+ throw error;
19
+ }
20
+ }
21
+ function json(path) {
22
+ try {
23
+ if (!lstatSync(path).isFile() || lstatSync(path).isSymbolicLink())
24
+ return undefined;
25
+ return JSON.parse(readFileSync(path, 'utf8'));
26
+ }
27
+ catch (error) {
28
+ if (error.code === 'ENOENT')
29
+ return undefined;
30
+ throw error;
31
+ }
32
+ }
33
+ function fingerprint(path) {
34
+ const stat = lstatSync(path);
35
+ const files = stat.isDirectory() ? ['creation.json', '.temp-supervisor.json', '.identity', 'role.yaml', 'state.json', 'instance.json'] : [''];
36
+ const hash = createHash('sha256');
37
+ for (const file of files) {
38
+ const target = file ? join(path, file) : path;
39
+ if (existsSync(target)) {
40
+ if (!lstatSync(target).isFile() || lstatSync(target).isSymbolicLink())
41
+ throw new Error('Unsafe erasure proof');
42
+ hash.update(file).update(readFileSync(target));
43
+ }
44
+ }
45
+ return hash.digest('hex');
46
+ }
47
+ /** Erase only proven launch artifacts. Each atomic rename transfers ownership to
48
+ * a random, durable tombstone before recursive removal can destroy its proof.
49
+ * The caller holds the task/room lifecycle lock and has retired all writers.
50
+ */
51
+ export async function eraseMemberArtifacts(owner, id, seats, roomIds) {
52
+ if (!/^[a-zA-Z0-9_-]{1,80}$/.test(id))
53
+ throw new Error('Invalid erasure owner');
54
+ const root = stateRoot();
55
+ const manifestPath = join(root, 'erasure', `${owner}-${id}.json`);
56
+ assertSafeAncestors(dirname(manifestPath));
57
+ let manifest = json(manifestPath);
58
+ const names = new Set(seats.map(s => s.role_name));
59
+ for (const name of names) {
60
+ if (!/^[a-zA-Z0-9_-]{1,120}$/.test(name))
61
+ throw new Error('Invalid erasure member');
62
+ assertMemberNotPermanent(name);
63
+ if (existsSync(agentDir(name, true)))
64
+ throw new Error(`Member '${name}' still has live state during erasure`);
65
+ }
66
+ if (!manifest) {
67
+ const paths = [];
68
+ const launches = seats.flatMap(seat => {
69
+ const launchId = seat.launch?.launch_id ?? seat.retirement?.launch_id;
70
+ return launchId ? [{ role: seat.role_name, launchId }] : [];
71
+ });
72
+ const actions = new Map(seats.map(s => [s.role_name, new Set([s.launch?.action_id].filter((v) => !!v))]));
73
+ const recovery = join(root, 'recovery', 'temporary');
74
+ assertSafeAncestors(recovery);
75
+ for (const entry of entries(recovery)) {
76
+ const path = join(recovery, entry);
77
+ const stat = lstatSync(path);
78
+ if (!stat.isDirectory() || stat.isSymbolicLink())
79
+ continue;
80
+ const supervisor = readTempSupervisorForScan(path);
81
+ if (!supervisor || !names.has(supervisor.role))
82
+ continue;
83
+ const seat = seats.find(s => s.role_name === supervisor.role);
84
+ const creation = json(join(path, 'creation.json'));
85
+ let room;
86
+ const roleFile = join(path, 'role.yaml');
87
+ if (existsSync(roleFile) && lstatSync(roleFile).isFile() && !lstatSync(roleFile).isSymbolicLink()) {
88
+ const role = parse(readFileSync(roleFile, 'utf8'));
89
+ if (role?.name === supervisor.role)
90
+ room = role.roomMemberStartup?.room_id;
91
+ }
92
+ const exactLaunch = seat.launch?.launch_id === supervisor.launchId || seat.retirement?.launch_id === supervisor.launchId;
93
+ const exactAction = creation?.role === supervisor.role && creation.creationActionId === seat.launch?.action_id;
94
+ // Older attempts belong only when the launch descriptor pins the room.
95
+ if (!(exactLaunch && (exactAction || !seat.launch?.action_id)) && !(room && roomIds.includes(room)))
96
+ continue;
97
+ if (existsSync(join(path, '.identity')) && readFileSync(join(path, '.identity'), 'utf8').trim() !== supervisor.role)
98
+ throw new Error('Archive identity mismatch during erasure');
99
+ if (await tempSupervisorLiveness(path) !== 'stopped')
100
+ throw new Error('Archive writer is not stopped');
101
+ if (creation?.role === supervisor.role && typeof creation.creationActionId === 'string')
102
+ actions.get(supervisor.role).add(creation.creationActionId);
103
+ auditWorkspaceGit(path, { forDeletion: true });
104
+ launches.push({ role: supervisor.role, launchId: supervisor.launchId });
105
+ paths.push(path);
106
+ }
107
+ const privateRoot = join(root, 'private-ours');
108
+ assertSafeAncestors(privateRoot);
109
+ for (const entry of entries(privateRoot)) {
110
+ const path = join(privateRoot, entry);
111
+ if (!lstatSync(path).isDirectory() || lstatSync(path).isSymbolicLink())
112
+ continue;
113
+ if (entry === 'launches' || entry === 'room-inputs') {
114
+ for (const file of entries(path)) {
115
+ const child = join(path, file), value = json(child);
116
+ if (!value)
117
+ continue;
118
+ const launchOwned = entry === 'launches' && value.role === value.identity
119
+ && actions.get(value.role)?.has(value.action);
120
+ const roomOwned = entry === 'room-inputs' && roomIds.includes(value.room_id)
121
+ && names.has(value.identity_name);
122
+ const readyOwned = entry === 'room-inputs' && file.endsWith('.ready.json')
123
+ && roomIds.includes(value.room) && seats.some(seat => seat.invite_id === value.invite && (!seat.identity_cid || seat.identity_cid === value.cid));
124
+ if (launchOwned || roomOwned || readyOwned)
125
+ paths.push(child);
126
+ }
127
+ continue;
128
+ }
129
+ const state = json(join(path, 'state.json')), instance = json(join(path, 'instance.json'));
130
+ if (state?.lifetime === 'temporary' && actions.get(state.name)?.has(state.action)
131
+ && instance?.role === state.name && instance?.temporary === true && instance?.instance === state.instance) {
132
+ auditWorkspaceGit(path, { forDeletion: true });
133
+ paths.push(path);
134
+ }
135
+ }
136
+ manifest = { launches, version: 1, token: randomUUID(), paths, phases: {}, stamps: Object.fromEntries(paths.map(path => {
137
+ const stat = lstatSync(path);
138
+ return [path, { dev: stat.dev, ino: stat.ino, proof: fingerprint(path) }];
139
+ })) };
140
+ mkdirSync(dirname(manifestPath), { recursive: true, mode: 0o700 });
141
+ replaceFileAtomically(manifestPath, JSON.stringify(manifest));
142
+ }
143
+ if (manifest.version !== 1 || !/^[a-f0-9-]{36}$/.test(manifest.token) || !Array.isArray(manifest.paths))
144
+ throw new Error('Invalid erasure manifest');
145
+ for (const path of manifest.paths) {
146
+ const rel = relative(root, path);
147
+ if (!rel.startsWith('recovery/temporary/') && !rel.startsWith('private-ours/'))
148
+ throw new Error('Invalid erasure artifact path');
149
+ if (rel.split('/').includes('..'))
150
+ throw new Error('Invalid erasure artifact traversal');
151
+ assertSafeAncestors(dirname(path));
152
+ const tombstone = `${path}.erasing-${manifest.token}`;
153
+ if (manifest.phases[path] === 'removed')
154
+ continue;
155
+ if (existsSync(path) && !existsSync(tombstone) && manifest.phases[path] !== 'renamed') {
156
+ const stat = lstatSync(path), stamp = manifest.stamps[path];
157
+ if (stat.isSymbolicLink() || !stamp || stamp.dev !== stat.dev || stamp.ino !== stat.ino || stamp.proof !== fingerprint(path))
158
+ throw new Error('Erasure source ownership changed');
159
+ renameSync(path, tombstone);
160
+ }
161
+ manifest.phases[path] = 'renamed';
162
+ replaceFileAtomically(manifestPath, JSON.stringify(manifest));
163
+ if (existsSync(tombstone) && lstatSync(tombstone).isDirectory())
164
+ prepareOwnedDeletionTree(tombstone);
165
+ rmSync(tombstone, { recursive: true, force: true });
166
+ manifest.phases[path] = 'removed';
167
+ replaceFileAtomically(manifestPath, JSON.stringify(manifest));
168
+ }
169
+ eraseResourcePresentations([{ kind: owner, id }, ...roomIds.map(id => ({ kind: 'room', id })),
170
+ ...seats.map(seat => ({ kind: 'agent', id: seat.role_name }))]);
171
+ eraseTerminationEvents(manifest.launches);
172
+ rmSync(manifestPath, { force: true });
173
+ }
@@ -8,4 +8,4 @@ export declare function presentFleetWorkerLifecycle(presentations: FleetAuditPre
8
8
  sleep(ms: number): Promise<void>;
9
9
  }): Promise<void>;
10
10
  /** Launch a Fleet CLI operation outside the caller's supervisor/session lifecycle. */
11
- export declare function launchFleetWorker(args: string[], operation: string, configPath?: string): Promise<void>;
11
+ export declare function launchFleetWorker(args: string[], operation: string, configPath?: string, binPath?: string): Promise<void>;
@@ -9,8 +9,7 @@ const LIFECYCLE_ACCEPT_TIMEOUT_MS = 10 * 60_000;
9
9
  const LIFECYCLE_RETRY_MS = 250;
10
10
  export function fleetWorkerEnv(source = process.env) {
11
11
  const inheritedKeys = [
12
- 'HOME', 'PATH', 'XDG_RUNTIME_DIR', 'OURS_FLEET_HOME', 'OURS_CONFIG',
13
- 'OURS_PORT', 'OURS_STATE_DIR', 'OURS_API_TOKEN', 'OURS_COWORK_CONFIG',
12
+ 'HOME', 'PATH', 'XDG_RUNTIME_DIR', 'OURS_FLEET_HOME', 'OURS_FLEET_SOCKET_ROOT', 'OURS_CONFIG',
14
13
  ];
15
14
  const env = Object.fromEntries(inheritedKeys.flatMap(key => source[key] === undefined ? [] : [[key, source[key]]]));
16
15
  const lifecycleStateDir = source[FLEET_WORKER_LIFECYCLE_STATE_DIR_ENV]
@@ -52,8 +51,8 @@ export async function presentFleetWorkerLifecycle(presentations, deps = {
52
51
  }
53
52
  }
54
53
  /** Launch a Fleet CLI operation outside the caller's supervisor/session lifecycle. */
55
- export function launchFleetWorker(args, operation, configPath) {
56
- const cli = [process.argv[1], ...args, ...(configPath ? ['-c', configPath] : [])];
54
+ export function launchFleetWorker(args, operation, configPath, binPath = process.argv[1]) {
55
+ const cli = [binPath, ...args, ...(configPath ? ['-c', configPath] : [])];
57
56
  const env = fleetWorkerEnv();
58
57
  const inherited = Object.entries(env).map(([key, value]) => `${key}=${value}`);
59
58
  const suffix = randomUUID().slice(0, 8);
@@ -0,0 +1,11 @@
1
+ import type { LayoutInstance } from './layout.js';
2
+ import type { LayoutControlRequest } from './layout-control.js';
3
+ export type LayoutBindingRequest = Omit<LayoutControlRequest, 'instance'> & {
4
+ roomId?: string;
5
+ roomRole?: string;
6
+ };
7
+ export declare const LAYOUT_BINDING_ACTIONS: readonly ["verify", "join", "assign"];
8
+ export declare const localLayoutReference: ({ remote: _remote, ...instance }: LayoutInstance) => LayoutInstance;
9
+ /** Initial support is the same-host, separate-OS-user use case from #194. */
10
+ export declare function layoutBindingOrigin(raw: string): string;
11
+ export declare function callRemoteLayoutBinding(instance: LayoutInstance, request: LayoutBindingRequest, daemonInstanceId: string, fetchImpl?: typeof fetch): Promise<unknown>;
@@ -0,0 +1,56 @@
1
+ import { lstatSync, readFileSync } from 'node:fs';
2
+ import { isAbsolute } from 'node:path';
3
+ import { canonicalJson } from '../canonical-json.js';
4
+ export const LAYOUT_BINDING_ACTIONS = ['verify', 'join', 'assign'];
5
+ export const localLayoutReference = ({ remote: _remote, ...instance }) => instance;
6
+ /** Initial support is the same-host, separate-OS-user use case from #194. */
7
+ export function layoutBindingOrigin(raw) {
8
+ const url = new URL(raw);
9
+ if (url.protocol !== 'http:' || !['127.0.0.1'].includes(url.hostname)
10
+ || url.username || url.password || url.pathname !== '/' || url.search || url.hash)
11
+ throw Error('cross-Fleet bindings require an explicit loopback HTTP origin');
12
+ return url.origin;
13
+ }
14
+ export async function callRemoteLayoutBinding(instance, request, daemonInstanceId, fetchImpl = fetch) {
15
+ const remote = instance.remote;
16
+ if (!LAYOUT_BINDING_ACTIONS.includes(request.action))
17
+ throw Error('cross-Fleet bindings allow only verify, join and assign');
18
+ if (remote.daemon_instance_id !== daemonInstanceId)
19
+ throw Error('cross-Fleet binding requires the same daemon instance');
20
+ const origin = layoutBindingOrigin(remote.url);
21
+ if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(remote.grant_id) || !isAbsolute(remote.credential_file))
22
+ throw Error('invalid cross-Fleet binding');
23
+ const stat = lstatSync(remote.credential_file);
24
+ if (!stat.isFile() || stat.isSymbolicLink() || (stat.mode & 0o077)
25
+ || (process.getuid && stat.uid !== process.getuid()) || stat.size > 256)
26
+ throw Error('untrusted cross-Fleet credential file');
27
+ const token = readFileSync(remote.credential_file, 'utf8').trim();
28
+ if (!/^[A-Za-z0-9_-]{43}$/.test(token))
29
+ throw Error('invalid cross-Fleet credential');
30
+ let response;
31
+ try {
32
+ response = await fetchImpl(`${origin}/api/v1/layout-bindings/${remote.grant_id}/control`, {
33
+ method: 'POST', redirect: 'error', signal: AbortSignal.timeout(60_000),
34
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
35
+ body: JSON.stringify({ ...request, instance: localLayoutReference(instance), daemonInstanceId }),
36
+ });
37
+ }
38
+ catch {
39
+ throw Error('cross-Fleet control unavailable; operation outcome may be unknown');
40
+ }
41
+ // Remote errors are not trusted presentation data and may contain private request bytes.
42
+ if (!response.ok)
43
+ throw Error(`cross-Fleet binding rejected (${response.status})`);
44
+ let result;
45
+ try {
46
+ result = await response.json();
47
+ }
48
+ catch {
49
+ throw Error('invalid cross-Fleet control response');
50
+ }
51
+ if (!result || typeof result !== 'object')
52
+ throw Error('invalid cross-Fleet control response');
53
+ if (canonicalJson(result.instance) !== canonicalJson(localLayoutReference(instance)))
54
+ throw Error('cross-Fleet instance proof changed');
55
+ return result.result;
56
+ }
@@ -0,0 +1,21 @@
1
+ import type { LayoutInstance, LayoutSupervisor } from './layout.js';
2
+ import { type CoworkAdapter } from './cowork-adapter.js';
3
+ /** Private authorization records in the existing Fleet state; no foreign lifecycle port. */
4
+ export declare class LayoutBindingGrants {
5
+ private supervisor;
6
+ private options;
7
+ constructor(supervisor: LayoutSupervisor, options?: {
8
+ root?: string;
9
+ daemonInstanceId?: () => string;
10
+ cowork?: () => CoworkAdapter;
11
+ });
12
+ private get root();
13
+ private daemonInstanceId;
14
+ private path;
15
+ share(instance: LayoutInstance, origin: string, output: string, participant: string): Promise<{
16
+ grant_id: string;
17
+ file: string;
18
+ }>;
19
+ revoke(id: string): Promise<void>;
20
+ control(id: string, authorization: string | undefined, raw: unknown): Promise<unknown>;
21
+ }