@ours.network/fleet 1.2.0-nightly.9 → 2.0.1

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 +31 -0
  19. package/dist/application/conversation-history.js +339 -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 +3 -1
  34. package/dist/application/role-repository.js +89 -54
  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 +5 -1
  48. package/dist/briefing.js +138 -31
  49. package/dist/build-info.json +10 -5
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +391 -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 +402 -20
  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 +23 -4
  193. package/dist/runner.js +409 -61
  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 +20 -1
  225. package/dist/startup-readiness.js +66 -3
  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 +18 -37
  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 +138 -0
  294. package/dist/web/resource-views.js +519 -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
@@ -0,0 +1,66 @@
1
+ import { enrollWorkspace, unregisterWorkspace, type AttachDaemonClient, type WorkspacePayload } from './workspace-enrollment.js';
2
+ export interface WorkspaceBinding {
3
+ workspaceId: string;
4
+ hostWorkspaceId: string;
5
+ appOrigin: string;
6
+ serverCid?: string;
7
+ proofRootCid?: string;
8
+ }
9
+ export declare function readWorkspaceBinding(): WorkspaceBinding | undefined;
10
+ export declare function sameWorkspaceBinding(previous: WorkspaceBinding, payload: WorkspacePayload): boolean;
11
+ /** A workspace ID is never retired to rerun or migrate that same workspace. */
12
+ export declare function requiresWorkspaceReplacement(previous: WorkspaceBinding, target: {
13
+ workspaceId: string;
14
+ appOrigin: string;
15
+ serverCid: string;
16
+ }, migrateAppOrigin?: boolean): boolean;
17
+ /** Ask before redeeming the one-use grant, whose destination is deliberately opaque. */
18
+ export declare function confirmWorkspaceReplacement(previous: WorkspaceBinding, explicit?: boolean, effects?: {
19
+ write?: (value: string) => void;
20
+ interactive?: boolean;
21
+ ask?: () => Promise<string>;
22
+ }): Promise<boolean>;
23
+ export interface PendingWorkspaceReplacement {
24
+ version: 1 | 2;
25
+ previous: WorkspaceBinding & {
26
+ serverCid: string;
27
+ };
28
+ payload: WorkspacePayload;
29
+ configuration: string;
30
+ operationNonce: string;
31
+ rootCid?: string;
32
+ retired?: boolean;
33
+ cleaned?: boolean;
34
+ dispatchAttempted?: boolean;
35
+ operationExpiresAt?: number;
36
+ }
37
+ export declare function saveWorkspaceReplacement(record: PendingWorkspaceReplacement): void;
38
+ export declare function readWorkspaceReplacement(): PendingWorkspaceReplacement | undefined;
39
+ export declare function clearWorkspaceReplacement(): void;
40
+ /** Explicit recovery after the user removes an unsuccessful successor in the App. */
41
+ export declare function discardRetiredWorkspaceReplacement(): void;
42
+ export declare function beginWorkspaceReplacement(previous: WorkspaceBinding, payload: WorkspacePayload, configuration: string, effects?: {
43
+ preflight?: typeof unregisterWorkspace;
44
+ attach?: AttachDaemonClient;
45
+ }): Promise<PendingWorkspaceReplacement>;
46
+ /** Abandon only after a private receipt confirms no deletion and dispatch was unsent or terminally rejected. */
47
+ export declare function abandonWorkspaceReplacement(effects?: {
48
+ inspect?: typeof unregisterWorkspace;
49
+ attach?: AttachDaemonClient;
50
+ }): Promise<void>;
51
+ /** A failed/expired one-time command can be renewed only for its pinned successor. */
52
+ export declare function refreshWorkspaceReplacement(record: PendingWorkspaceReplacement, payload: WorkspacePayload, configuration: string): void;
53
+ /** Only tunnel files and account-issued device capabilities belong to this registration. */
54
+ export declare function clearReplacedWorkspaceLocalState(): Promise<void>;
55
+ /** Persist every irreversible boundary; the original binding stays until enrollment writes its successor. */
56
+ export declare function finishWorkspaceReplacement(record: PendingWorkspaceReplacement, effects?: {
57
+ unregister?: typeof unregisterWorkspace;
58
+ inspect?: typeof unregisterWorkspace;
59
+ cleanup?: () => Promise<void>;
60
+ enroll?: typeof enrollWorkspace;
61
+ attach?: AttachDaemonClient;
62
+ }): Promise<{
63
+ origin: string;
64
+ hostWorkspaceId: string;
65
+ rootCid: string;
66
+ }>;
@@ -0,0 +1,238 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { existsSync, lstatSync, readFileSync, rmSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { validateAccountOrigin } from './account-origin.js';
6
+ import { replaceFileAtomically, withSynchronousFileLock } from './atomic-file.js';
7
+ import { stateRoot, workspaceBindingLock, workspaceReplacementFile } from './paths.js';
8
+ import { decodeWorkspacePayload, enrollWorkspace, unregisterWorkspace, checkWorkspaceConnector } from './workspace-enrollment.js';
9
+ import { WorkspaceDeviceStore } from './web/workspace-devices.js';
10
+ import { webControlPath } from './web/control.js';
11
+ import { WebServiceManager } from './web/service.js';
12
+ const workspaceDir = () => join(stateRoot(), 'workspace');
13
+ const replacementPath = workspaceReplacementFile;
14
+ const opaque = /^[\w-]{43}$/;
15
+ function binding(value) {
16
+ const text = (id, pattern) => typeof id === 'string' && pattern.test(id);
17
+ if (!value || typeof value !== 'object' || !text(value.workspaceId, opaque) || !text(value.hostWorkspaceId, opaque) || (value.serverCid !== undefined && !text(value.serverCid, /^[a-f0-9]{64}$/i))
18
+ || (value.proofRootCid !== undefined && !text(value.proofRootCid, /^[a-f0-9]{64}$/i)))
19
+ throw Error('Existing workspace binding is invalid; restore it before tunnel setup');
20
+ return { ...value, appOrigin: validateAccountOrigin(value.appOrigin) };
21
+ }
22
+ export function readWorkspaceBinding() {
23
+ const file = join(workspaceDir(), 'binding.json');
24
+ if (!existsSync(file))
25
+ return;
26
+ let value;
27
+ try {
28
+ value = JSON.parse(readFileSync(file, 'utf8'));
29
+ }
30
+ catch {
31
+ throw Error('Existing workspace binding is invalid');
32
+ }
33
+ return binding(value);
34
+ }
35
+ export function sameWorkspaceBinding(previous, payload) {
36
+ return previous.workspaceId === payload.challenge.workspaceId && previous.appOrigin === payload.appOrigin && (!previous.serverCid || previous.serverCid.toUpperCase() === payload.serverCid.toUpperCase());
37
+ }
38
+ /** A workspace ID is never retired to rerun or migrate that same workspace. */
39
+ export function requiresWorkspaceReplacement(previous, target, migrateAppOrigin = false) {
40
+ if (previous.workspaceId !== target.workspaceId) {
41
+ if (!previous.serverCid)
42
+ throw Error('Legacy registration has no recorded server identity. Rerun its own workspace setup before replacing it');
43
+ return true;
44
+ }
45
+ if (previous.serverCid && previous.serverCid.toUpperCase() !== target.serverCid.toUpperCase())
46
+ throw Error('Same-workspace setup must retain the enrollment server identity; existing setup preserved');
47
+ if (previous.appOrigin !== target.appOrigin && !migrateAppOrigin)
48
+ throw Error('Account origin change requires explicit --migrate-app-origin; existing setup preserved');
49
+ return false;
50
+ }
51
+ /** Ask before redeeming the one-use grant, whose destination is deliberately opaque. */
52
+ export async function confirmWorkspaceReplacement(previous, explicit = false, effects = {}) {
53
+ const write = effects.write ?? (value => { process.stderr.write(value); });
54
+ let hostname = '';
55
+ try {
56
+ const tunnel = JSON.parse(readFileSync(join(workspaceDir(), 'tunnel.json'), 'utf8'));
57
+ if (typeof tunnel.origin === 'string' && validateAccountOrigin(tunnel.origin))
58
+ hostname = ` (${new URL(tunnel.origin).hostname})`;
59
+ }
60
+ catch { }
61
+ write(`This host is registered to workspace ${previous.workspaceId}${hostname} at ${previous.appOrigin}.\nReplacing it removes the old account registration and revokes its linked devices. The account service retires tunnel resources it knows; a migrated database may have no old resources to delete. Installation identities, agents and data are preserved.\n`);
62
+ if (explicit)
63
+ return true;
64
+ if (!(effects.interactive ?? process.stdin.isTTY)) {
65
+ write('Existing setup preserved. Run in a terminal to confirm, or pass --replace-registration to confirm explicitly.\n');
66
+ return false;
67
+ }
68
+ const ask = effects.ask ?? (async () => {
69
+ const terminal = createInterface({ input: process.stdin, output: process.stderr });
70
+ try {
71
+ return await terminal.question('Replace the existing registration if this command names another workspace? [y/N] ');
72
+ }
73
+ finally {
74
+ terminal.close();
75
+ }
76
+ });
77
+ let answer = '';
78
+ try {
79
+ answer = await ask();
80
+ }
81
+ catch { /* EOF, cancellation and input failure mean no consent. */ }
82
+ if (/^(y|yes)$/i.test(answer.trim()))
83
+ return true;
84
+ write('Existing setup preserved.\n');
85
+ return false;
86
+ }
87
+ export function saveWorkspaceReplacement(record) {
88
+ withSynchronousFileLock(workspaceBindingLock(), () => replaceFileAtomically(replacementPath(), JSON.stringify(record) + '\n', 0o600));
89
+ }
90
+ export function readWorkspaceReplacement() {
91
+ const file = replacementPath();
92
+ if (!existsSync(file))
93
+ return;
94
+ const stat = lstatSync(file);
95
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0 || stat.size > 65536)
96
+ throw Error('Unfinished replacement must be an owned private regular file');
97
+ let record;
98
+ try {
99
+ record = JSON.parse(readFileSync(file, 'utf8'));
100
+ }
101
+ catch {
102
+ throw Error('Unfinished replacement record is invalid');
103
+ }
104
+ if (![1, 2].includes(record.version) || !opaque.test(record.operationNonce) || typeof record.configuration !== 'string' || resolve(record.configuration) !== record.configuration
105
+ || (record.rootCid !== undefined && !/^[a-f0-9]{64}$/i.test(record.rootCid)) || (record.retired !== undefined && typeof record.retired !== 'boolean')
106
+ || (record.cleaned !== undefined && typeof record.cleaned !== 'boolean') || (record.dispatchAttempted !== undefined && typeof record.dispatchAttempted !== 'boolean') || (record.version === 2 && (record.dispatchAttempted === undefined || (!Number.isSafeInteger(record.operationExpiresAt) || record.operationExpiresAt <= 0 || record.operationExpiresAt > Date.now() + 86400000 + 60000))) || (record.cleaned && !record.retired) || (record.retired && !record.rootCid))
107
+ throw Error('Unfinished replacement record is invalid');
108
+ record.previous = binding(record.previous);
109
+ if (!record.previous.serverCid)
110
+ throw Error('Unfinished replacement has no pinned server identity');
111
+ record.payload = decodeWorkspacePayload(Buffer.from(JSON.stringify(record.payload)).toString('base64url'), { allowExpired: true });
112
+ return record;
113
+ }
114
+ export function clearWorkspaceReplacement() { withSynchronousFileLock(workspaceBindingLock(), () => rmSync(replacementPath(), { force: true })); }
115
+ /** Explicit recovery after the user removes an unsuccessful successor in the App. */
116
+ export function discardRetiredWorkspaceReplacement() {
117
+ const record = readWorkspaceReplacement(), current = readWorkspaceBinding();
118
+ if (!record?.retired || !record.cleaned)
119
+ throw Error('Only a fully retired and locally cleaned replacement can be discarded; resume or safely abandon this operation');
120
+ if (!current || current.workspaceId !== record.previous.workspaceId || current.hostWorkspaceId !== record.previous.hostWorkspaceId || current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase())
121
+ throw Error('The successor binding was committed or the binding changed; resume its setup instead of discarding recovery');
122
+ clearWorkspaceReplacement();
123
+ }
124
+ function checkReplacementService(configuration) {
125
+ const manager = new WebServiceManager(), service = manager.readMetadata();
126
+ if (service?.configuration && resolve(service.configuration) !== resolve(configuration))
127
+ throw Error('Installed web service uses another configuration; existing setup preserved');
128
+ if (!service && existsSync(webControlPath()))
129
+ throw Error('Stop the foreground Fleet web service before replacing its registration; existing setup preserved');
130
+ }
131
+ export async function beginWorkspaceReplacement(previous, payload, configuration, effects = {}) {
132
+ checkWorkspaceConnector(payload);
133
+ if (!requiresWorkspaceReplacement(previous, { workspaceId: payload.challenge.workspaceId, appOrigin: payload.appOrigin, serverCid: payload.serverCid }))
134
+ throw Error('The same workspace must never be retired');
135
+ checkReplacementService(configuration);
136
+ // The record is created only for the binding this decision was made on, and only if no other
137
+ // replacement or setup changed the binding or the record meanwhile; enrollment checks the same pair.
138
+ const bindingFile = join(workspaceDir(), 'binding.json'), current = () => existsSync(bindingFile) ? readFileSync(bindingFile, 'utf8') : null;
139
+ const unchanged = (bytes) => {
140
+ if (current() !== bytes)
141
+ throw Error('The workspace binding changed during setup; existing setup preserved. Run the setup command again');
142
+ if (existsSync(replacementPath()))
143
+ throw Error('Another replacement started during setup; existing setup preserved. Review it with setup-tunnel --resume');
144
+ };
145
+ const before = withSynchronousFileLock(workspaceBindingLock(), () => {
146
+ const bytes = current();
147
+ unchanged(bytes);
148
+ const now = readWorkspaceBinding();
149
+ if (!now || now.workspaceId !== previous.workspaceId || now.hostWorkspaceId !== previous.hostWorkspaceId || now.appOrigin !== previous.appOrigin || now.serverCid?.toUpperCase() !== previous.serverCid?.toUpperCase())
150
+ throw Error('The workspace binding changed during setup; existing setup preserved. Run the setup command again');
151
+ return bytes;
152
+ });
153
+ const record = { version: 2, previous: previous, payload, configuration: resolve(configuration), operationNonce: randomBytes(32).toString('base64url'), dispatchAttempted: false, operationExpiresAt: Date.now() + 86400000 - 5 * 60000 };
154
+ record.rootCid = await (effects.preflight ?? unregisterWorkspace)(record.previous, payload, record.operationNonce, effects.attach, { checkOnly: true, operationExpiresAt: record.operationExpiresAt });
155
+ withSynchronousFileLock(workspaceBindingLock(), () => { unchanged(before); replaceFileAtomically(replacementPath(), JSON.stringify(record) + '\n', 0o600); });
156
+ return record;
157
+ }
158
+ /** Abandon only after a private receipt confirms no deletion and dispatch was unsent or terminally rejected. */
159
+ export async function abandonWorkspaceReplacement(effects = {}) {
160
+ const record = readWorkspaceReplacement();
161
+ if (!record)
162
+ throw Error('There is no unfinished replacement');
163
+ if (record.retired || record.cleaned || record.version !== 2)
164
+ throw Error('Retirement may already have completed; resume this replacement instead of abandoning it');
165
+ const current = readWorkspaceBinding();
166
+ if (!current || current.workspaceId !== record.previous.workspaceId || current.hostWorkspaceId !== record.previous.hostWorkspaceId || current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase())
167
+ throw Error('Workspace binding changed; replacement was not abandoned');
168
+ let deleted, rejected = false, expired = false, deadlineEnforced = false, oldBindingPresent = false;
169
+ const root = await (effects.inspect ?? unregisterWorkspace)(record.previous, record.payload, record.operationNonce, effects.attach, { checkOnly: true, operationExpiresAt: record.operationExpiresAt, onReceipt: status => { deleted = status.deleted; rejected = status.rejected === true; expired = status.expired === true; deadlineEnforced = status.deadlineEnforced === true; oldBindingPresent = status.oldBindingPresent === true; } });
170
+ if (root.toUpperCase() !== record.rootCid?.toUpperCase() || deleted !== false || (record.dispatchAttempted && !(rejected && !expired && Date.now() <= record.operationExpiresAt || oldBindingPresent && deadlineEnforced && Date.now() > record.operationExpiresAt + 5 * 60000)))
171
+ throw Error('Retirement may already have been sent; run --resume or abandon only after the account service confirms terminal rejection');
172
+ clearWorkspaceReplacement();
173
+ }
174
+ /** A failed/expired one-time command can be renewed only for its pinned successor. */
175
+ export function refreshWorkspaceReplacement(record, payload, configuration) {
176
+ if (resolve(configuration) !== record.configuration)
177
+ throw Error('Replacement requires its original configuration');
178
+ if (payload.challenge.workspaceId !== record.payload.challenge.workspaceId || payload.challenge.accountId !== record.payload.challenge.accountId
179
+ || payload.appOrigin !== record.payload.appOrigin || payload.serverCid.toUpperCase() !== record.payload.serverCid.toUpperCase()
180
+ || payload.hostname !== record.payload.hostname)
181
+ throw Error('Unfinished replacement requires a fresh command for the same target workspace');
182
+ record.payload = payload.connectorToken === undefined && record.payload.connectorToken !== undefined ? { ...payload, connectorToken: record.payload.connectorToken } : payload;
183
+ saveWorkspaceReplacement(record);
184
+ }
185
+ /** Only tunnel files and account-issued device capabilities belong to this registration. */
186
+ export async function clearReplacedWorkspaceLocalState() {
187
+ const manager = new WebServiceManager();
188
+ if (manager.readMetadata())
189
+ await manager.stop();
190
+ const devices = new WorkspaceDeviceStore();
191
+ try {
192
+ devices.revokeAll();
193
+ }
194
+ finally {
195
+ devices.close();
196
+ }
197
+ for (const file of ['tunnel.json', 'connector', 'pending-setup.json'])
198
+ rmSync(join(workspaceDir(), file), { force: true });
199
+ }
200
+ /** Persist every irreversible boundary; the original binding stays until enrollment writes its successor. */
201
+ export async function finishWorkspaceReplacement(record, effects = {}) {
202
+ const current = readWorkspaceBinding();
203
+ if (!current || (current.workspaceId === record.previous.workspaceId ? (current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase() || current.hostWorkspaceId !== record.previous.hostWorkspaceId) : !sameWorkspaceBinding(current, record.payload)))
204
+ throw Error('Workspace binding changed during replacement; review it before continuing');
205
+ // A saved successor binding may predate a refreshed challenge. Enrollment
206
+ // checks the exact proof receipt: it reuses confirmed proof, or signs the new
207
+ // challenge, rather than assuming every challenge for this binding is done.
208
+ if (!record.retired) {
209
+ checkReplacementService(record.configuration);
210
+ if (record.version === 2 && Date.now() > record.operationExpiresAt + 5 * 60000) {
211
+ if (record.payload.challenge.expiresAt <= Date.now())
212
+ throw Error('Retirement deadline passed. Obtain a fresh App command for the same target and pass --replace-registration to continue');
213
+ let deleted, enforced = false;
214
+ const root = await (effects.inspect ?? unregisterWorkspace)(record.previous, record.payload, record.operationNonce, effects.attach, { checkOnly: true, operationExpiresAt: record.operationExpiresAt, onReceipt: status => { deleted = status.deleted; enforced = status.deadlineEnforced === true; } });
215
+ if (root.toUpperCase() !== record.rootCid?.toUpperCase())
216
+ throw Error('Original proof root changed; existing recovery preserved');
217
+ if (deleted === false && enforced) {
218
+ // The expired command cannot act. Existing consent covers this exact
219
+ // previous/successor pair, including signed absence after a lost receipt.
220
+ record.operationNonce = randomBytes(32).toString('base64url');
221
+ record.operationExpiresAt = Date.now() + 86400000 - 5 * 60000;
222
+ record.dispatchAttempted = false;
223
+ saveWorkspaceReplacement(record);
224
+ }
225
+ }
226
+ record.rootCid = await (effects.unregister ?? unregisterWorkspace)(record.previous, record.payload, record.operationNonce, effects.attach, { operationExpiresAt: record.operationExpiresAt, beforeSend: () => { record.dispatchAttempted = true; saveWorkspaceReplacement(record); } });
227
+ record.retired = true;
228
+ saveWorkspaceReplacement(record);
229
+ }
230
+ if (!record.cleaned) {
231
+ await (effects.cleanup ?? clearReplacedWorkspaceLocalState)();
232
+ record.cleaned = true;
233
+ saveWorkspaceReplacement(record);
234
+ }
235
+ if (record.payload.challenge.expiresAt <= Date.now())
236
+ throw Error('Old registration was retired, but the replacement setup window expired. Get a fresh App command and pass --replace-registration; the retirement will not be repeated');
237
+ return (effects.enroll ?? enrollWorkspace)(record.payload, record.configuration, { preserveProfile: true, replacingWorkspace: record.previous.workspaceId, attach: effects.attach });
238
+ }
@@ -0,0 +1,146 @@
1
+ # Sending files in an ACP chat
2
+
3
+ Every managed Agent can attach a file to its current ACP chat with the existing
4
+ Ours `send_file` tool. No Agent setting turns this on and no export directory is
5
+ configured or created:
6
+
7
+ ```json
8
+ {"path":"/home/me/project/build/report.pdf"}
9
+ ```
10
+
11
+ Omitting `contact` sends to the current ACP chat. Supplying a non-empty contact
12
+ keeps ordinary Ours delivery:
13
+
14
+ ```json
15
+ {"contact":"Alice","path":"build/report.pdf"}
16
+ ```
17
+
18
+ Only an absent contact chooses the chat. Null, empty or whitespace-only contacts,
19
+ unknown fields and unknown recipients fail without falling back to chat.
20
+ Current-chat sending accepts `path`, optional `filename` and `mime`; inline
21
+ base64 and Ours reply fields are unsupported.
22
+
23
+ Each call makes one attempt. Failures are returned to the agent. There is no
24
+ request ID, deduplication, automatic retry or recovery of failed sends. If a user
25
+ asks to resend, another tool call makes a new attempt and can produce another
26
+ attachment. UUIDs identify stored attachments; the stored SHA-256 checks integrity.
27
+ Neither is a retry key.
28
+
29
+ ## Paths
30
+
31
+ The Agent passes the path of the file it wants to send. Relative paths resolve
32
+ from the Agent's working directory; `..`, absolute paths, hidden directories and
33
+ symlinked parent directories are all accepted. The file does not have to be
34
+ inside the working directory or any particular subtree. It is opened by the
35
+ Fleet bridge inside the Agent's own OS sandbox, so the Agent can send exactly
36
+ what it can already read.
37
+
38
+ The file itself must pass ordinary integrity checks, each with its own result:
39
+
40
+ | Result | Meaning |
41
+ | --- | --- |
42
+ | `CHAT_FILE_NOT_FOUND` | nothing exists at the path |
43
+ | `CHAT_FILE_NOT_READABLE` | the Agent's OS user or sandbox cannot read it |
44
+ | `CHAT_FILE_IS_SYMLINK` | the named file is itself a symlink; pass its target |
45
+ | `CHAT_FILE_HARDLINKED` | the file has more than one hard link |
46
+ | `CHAT_FILE_NOT_REGULAR` | a directory, FIFO, socket or device |
47
+ | `FILE_EXCEEDS_20_MIB` | larger than the 20 MiB limit |
48
+ | `CHAT_FILE_CHANGED` / `SOURCE_SIZE_CHANGED` | modified while it was being read; finish writing first |
49
+ | `CHAT_FILE_REQUIRES_LINUX` | the Agent's bridge is not running on Linux |
50
+ | `PRIVATE_STATE_CANNOT_BE_SENT` | Fleet's own credentials, connector profile or stored attachments |
51
+
52
+ `PRIVATE_STATE_CANNOT_BE_SENT` covers only Fleet's private Ours runtime
53
+ directory, its attachment store and the connector profile and credential files,
54
+ checked on the provided path before approval and on the opened file's real path
55
+ afterwards. It is not configurable and is not a general path policy.
56
+
57
+ ## Supported sessions
58
+
59
+ Current-chat delivery needs Linux, the managed Ours connector and a Codex or
60
+ Claude ACP session with a tracked active turn. Those Agents need no special
61
+ `harness_options`, MCP restriction or clean configuration home: inherited and
62
+ additional MCP connectors stay as they are, and Fleet's bound bridge is the
63
+ `ours` connector as it already is for every managed Agent.
64
+
65
+ Any other backend still sees the optional `contact`, and omitting it returns a
66
+ precise result rather than a missing tool:
67
+
68
+ ```
69
+ CURRENT_CHAT_DELIVERY_UNAVAILABLE: sending a file to this chat requires a Codex or Claude ACP session; this Agent runs <harness>/<session>. Pass contact to send through Ours instead
70
+ ```
71
+
72
+ Idle, scheduled, steered, cancelled and replaced turns return
73
+ `CURRENT_CHAT_DELIVERY_UNAVAILABLE`; a second send while one is in progress
74
+ returns `FILE_DELIVERY_BUSY`.
75
+
76
+ A file is published only for a call that arrives over the Agent's
77
+ capability-authenticated bridge and binds to the turn that is live in that
78
+ Agent's own session. Connector inventory is not consulted, so unrelated or
79
+ similarly named connectors neither enable nor block delivery.
80
+
81
+ ## Permissions
82
+
83
+ Existing native tool approval and the neutral role permission policy both apply
84
+ before bytes are read. With `approval: allow`, a path inside the role's
85
+ `filesystem` boundary is allowed automatically; a path outside it is put to the
86
+ attached controller like any other out-of-boundary request, and is denied when
87
+ nobody is attached and `unattended: deny`. An `ask` decision needs an attached
88
+ controller; `deny` refuses with `CURRENT_CHAT_DELIVERY_UNAVAILABLE`, and a
89
+ declined request returns `FILE_SEND_DENIED`. A native policy that refuses MCP
90
+ execution (including Codex `never` without a native tool allowance) can still
91
+ refuse before Fleet receives the call.
92
+
93
+ ## Retired configuration
94
+
95
+ `file_delivery` is retired. An existing block still loads so manifests need no
96
+ edit, and has no effect:
97
+
98
+ ```yaml
99
+ file_delivery:
100
+ enabled: false # ignored: delivery stays available
101
+ directory: deliverables # ignored: not created, not enforced
102
+ ```
103
+
104
+ Remove it at your convenience. Unknown keys inside it are still rejected.
105
+
106
+ ## Storage and download
107
+
108
+ Successful sends copy complete bytes into private immutable storage and append a
109
+ server-origin `file.attached` event containing the actual session generation,
110
+ ACP session and managed turn. One export can run at a time. Cancellation or
111
+ steering before publication revokes it; cancellation after publication does not
112
+ undo it. Unpublished stored bytes are never replayed or republished. Storage is
113
+ bounded to 100 files and 200 MiB per role incarnation, including incomplete and
114
+ orphaned copies. Exhausted storage fails; no automatic recovery is performed.
115
+
116
+ The App renders persisted file cards from these typed events. HTML and images
117
+ also download as files. Downloads require authentication, the matching role
118
+ incarnation, a published conversation event and intact bytes. Deleted, missing,
119
+ changed or orphaned artifacts return unavailable. Remote workspaces fetch bytes
120
+ using their existing device credential, limit the read to 20 MiB and prevent
121
+ saving after a workspace change.
122
+
123
+ ## Release prerequisite and qualification
124
+
125
+ This source requires the companion MCP implementation's
126
+ `MANAGED_FILE_DELIVERY_VERSION = 1`. Fleet pins published
127
+ `@ours.network/mcp@1.2.0-nightly.14`, which includes it and already treats the
128
+ export-directory hint as optional, so default delivery needs no MCP change. An
129
+ installed MCP package without the capability fails Agent startup with
130
+ `INCOMPATIBLE_MANAGED_FILE_DELIVERY` instead of quietly requiring a contact.
131
+ Changes to the MCP pin require clean installed-package qualification without a
132
+ built MCP overlay.
133
+
134
+ The deterministic native fixture (`test/file-delivery-native.integration.mjs`)
135
+ uses real ACP adapters, the real managed connector and an isolated daemon. Its
136
+ Agent has no `file_delivery` block and no MCP restriction, and it sends a file
137
+ from a hidden directory outside the working directory. It then downloads the
138
+ generated attachment over the authenticated HTTP route and compares bytes and
139
+ SHA-256, after checking that an anonymous and a forged-cookie request are
140
+ refused. `FLEET_FILE_NATIVE_VARIANT=connectors` adds unrelated MCP connectors
141
+ and requires them to stay usable. A local scripted provider supplies only tool
142
+ intents; approval, read, copy, publication and bytes come from the
143
+ implementation. It does not qualify external-model inference, deployment or
144
+ arbitrary native MCP request-to-ACP tool-call ID mapping. Correlation is the
145
+ authenticated bridge and the actual active Fleet turn, which is revoked by
146
+ cancellation/steering.