@ours.network/fleet 1.2.0-nightly.7 → 1.2.0-nightly.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/README.md +279 -68
  2. package/dist/account-origin.d.ts +2 -0
  3. package/dist/account-origin.js +6 -0
  4. package/dist/agent-ours/bridge.js +26 -9
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/file-rpc.d.ts +12 -0
  8. package/dist/agent-ours/file-rpc.js +18 -4
  9. package/dist/agent-ours/harness.js +9 -1
  10. package/dist/agent-ours/mcp-endpoint.d.ts +19 -0
  11. package/dist/agent-ours/mcp-endpoint.js +98 -25
  12. package/dist/agent-ours/runtime.d.ts +9 -1
  13. package/dist/agent-ours/runtime.js +50 -2
  14. package/dist/agent-ours/service.d.ts +24 -1
  15. package/dist/agent-ours/service.js +97 -13
  16. package/dist/application/codex-models.d.ts +15 -0
  17. package/dist/application/codex-models.js +56 -0
  18. package/dist/application/conversation-history.d.ts +34 -0
  19. package/dist/application/conversation-history.js +280 -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 +40 -0
  30. package/dist/application/resource-observer.js +278 -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 +61 -17
  38. package/dist/application/session-control.js +207 -23
  39. package/dist/application/supervisor-ours-tools.js +5 -11
  40. package/dist/application/task-layouts.d.ts +91 -0
  41. package/dist/application/task-layouts.js +261 -0
  42. package/dist/application/task-room-service.d.ts +101 -3
  43. package/dist/application/task-room-service.js +286 -33
  44. package/dist/application/types.d.ts +2 -0
  45. package/dist/atomic-file.d.ts +34 -1
  46. package/dist/atomic-file.js +161 -18
  47. package/dist/briefing.d.ts +3 -1
  48. package/dist/briefing.js +97 -31
  49. package/dist/build-info.json +9 -4
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +400 -37
  53. package/dist/client-profile.d.ts +5 -17
  54. package/dist/client-profile.js +4 -127
  55. package/dist/config.d.ts +45 -0
  56. package/dist/config.js +57 -6
  57. package/dist/creation.d.ts +1 -1
  58. package/dist/creation.js +4 -4
  59. package/dist/daemon-recovery.d.ts +14 -9
  60. package/dist/daemon-recovery.js +124 -115
  61. package/dist/docs.d.ts +1 -1
  62. package/dist/docs.js +207 -19
  63. package/dist/doctor.d.ts +4 -1
  64. package/dist/doctor.js +75 -60
  65. package/dist/erased-resources.d.ts +9 -0
  66. package/dist/erased-resources.js +87 -0
  67. package/dist/file-delivery/reader.d.ts +15 -0
  68. package/dist/file-delivery/reader.js +63 -0
  69. package/dist/file-delivery/store.d.ts +15 -0
  70. package/dist/file-delivery/store.js +124 -0
  71. package/dist/file-delivery/types.d.ts +19 -0
  72. package/dist/file-delivery/types.js +1 -0
  73. package/dist/fleet-command-audit.d.ts +7 -0
  74. package/dist/fleet-command-audit.js +24 -8
  75. package/dist/harness/agent-session.d.ts +4 -0
  76. package/dist/harness/claude-code-session.js +1 -1
  77. package/dist/harness/claude-code.js +21 -11
  78. package/dist/harness/claude-runtime.d.ts +12 -0
  79. package/dist/harness/claude-runtime.js +43 -0
  80. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  81. package/dist/harness/codex-app-server-proxy.js +41 -5
  82. package/dist/harness/codex-reasoning.d.ts +5 -0
  83. package/dist/harness/codex-reasoning.js +12 -0
  84. package/dist/harness/codex-runtime.d.ts +11 -1
  85. package/dist/harness/codex-runtime.js +31 -4
  86. package/dist/harness/codex-session.js +2 -2
  87. package/dist/harness/codex.js +24 -12
  88. package/dist/init-wizard.d.ts +2 -0
  89. package/dist/init-wizard.js +38 -19
  90. package/dist/isolation/policy.js +6 -1
  91. package/dist/isolation/types.d.ts +5 -0
  92. package/dist/managed-cli.d.ts +253 -0
  93. package/dist/managed-cli.js +923 -0
  94. package/dist/managed-recovery.d.ts +13 -0
  95. package/dist/managed-recovery.js +31 -0
  96. package/dist/minimal-setup.d.ts +2 -0
  97. package/dist/minimal-setup.js +44 -0
  98. package/dist/monitor.d.ts +9 -34
  99. package/dist/monitor.js +90 -153
  100. package/dist/notifications/fleet-producer.d.ts +22 -0
  101. package/dist/notifications/fleet-producer.js +237 -0
  102. package/dist/notifications/outbox.d.ts +46 -0
  103. package/dist/notifications/outbox.js +158 -0
  104. package/dist/notifications/target-cleanup.d.ts +11 -0
  105. package/dist/notifications/target-cleanup.js +63 -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 +2 -0
  116. package/dist/paths.js +2 -0
  117. package/dist/preset-migration.js +43 -0
  118. package/dist/process-incarnation.d.ts +23 -0
  119. package/dist/process-incarnation.js +42 -0
  120. package/dist/reminders/manager.d.ts +75 -0
  121. package/dist/reminders/manager.js +278 -0
  122. package/dist/reminders/runtime.d.ts +27 -0
  123. package/dist/reminders/runtime.js +32 -0
  124. package/dist/reminders/schedule.d.ts +37 -0
  125. package/dist/reminders/schedule.js +104 -0
  126. package/dist/reminders/store.d.ts +341 -0
  127. package/dist/reminders/store.js +762 -0
  128. package/dist/reminders/tools.d.ts +20 -0
  129. package/dist/reminders/tools.js +114 -0
  130. package/dist/resolved-plan.js +1 -0
  131. package/dist/rooms-tasks/add-member.d.ts +48 -0
  132. package/dist/rooms-tasks/add-member.js +84 -0
  133. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  134. package/dist/rooms-tasks/archived-absence.js +48 -0
  135. package/dist/rooms-tasks/cli.js +65 -5
  136. package/dist/rooms-tasks/close.d.ts +6 -2
  137. package/dist/rooms-tasks/close.js +246 -37
  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 +84 -5
  145. package/dist/rooms-tasks/erasure.d.ts +6 -0
  146. package/dist/rooms-tasks/erasure.js +173 -0
  147. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  148. package/dist/rooms-tasks/external-worker.js +3 -4
  149. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  150. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  151. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  152. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  153. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  154. package/dist/rooms-tasks/layout-cli.js +107 -0
  155. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  156. package/dist/rooms-tasks/layout-config.js +89 -0
  157. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  158. package/dist/rooms-tasks/layout-control.js +52 -0
  159. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  160. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  161. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  162. package/dist/rooms-tasks/layout-service.js +203 -0
  163. package/dist/rooms-tasks/layout.d.ts +121 -0
  164. package/dist/rooms-tasks/layout.js +286 -0
  165. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  166. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  167. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  168. package/dist/rooms-tasks/live-readiness.js +85 -0
  169. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  170. package/dist/rooms-tasks/member-ownership.js +40 -0
  171. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  172. package/dist/rooms-tasks/member-startup.js +2 -0
  173. package/dist/rooms-tasks/provision.d.ts +3 -0
  174. package/dist/rooms-tasks/provision.js +101 -49
  175. package/dist/rooms-tasks/recovery.d.ts +19 -0
  176. package/dist/rooms-tasks/recovery.js +110 -0
  177. package/dist/rooms-tasks/room-state.d.ts +4 -2
  178. package/dist/rooms-tasks/room-state.js +47 -8
  179. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  180. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  181. package/dist/rooms-tasks/task-state.d.ts +52 -8
  182. package/dist/rooms-tasks/task-state.js +251 -26
  183. package/dist/rooms-tasks/types.d.ts +40 -0
  184. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  185. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  186. package/dist/rooms-tasks/workspace.d.ts +35 -0
  187. package/dist/rooms-tasks/workspace.js +325 -0
  188. package/dist/runner.d.ts +33 -4
  189. package/dist/runner.js +410 -62
  190. package/dist/session/acp.d.ts +75 -2
  191. package/dist/session/acp.js +345 -82
  192. package/dist/session/arbiter.d.ts +98 -0
  193. package/dist/session/arbiter.js +272 -3
  194. package/dist/session/codex-app-server.d.ts +45 -1
  195. package/dist/session/codex-app-server.js +225 -32
  196. package/dist/session/control.d.ts +17 -3
  197. package/dist/session/control.js +213 -23
  198. package/dist/session/conversation-normalizer.d.ts +2 -1
  199. package/dist/session/conversation-normalizer.js +1 -1
  200. package/dist/session/conversation-observation.d.ts +25 -0
  201. package/dist/session/conversation-observation.js +100 -0
  202. package/dist/session/conversation-replay.d.ts +50 -0
  203. package/dist/session/conversation-replay.js +203 -0
  204. package/dist/session/conversation-store.d.ts +15 -1
  205. package/dist/session/conversation-store.js +206 -26
  206. package/dist/session/conversation-types.d.ts +57 -4
  207. package/dist/session/prompt-batch.d.ts +79 -0
  208. package/dist/session/prompt-batch.js +120 -0
  209. package/dist/session/task-notice.d.ts +14 -0
  210. package/dist/session/task-notice.js +13 -0
  211. package/dist/session/types.d.ts +42 -1
  212. package/dist/setup-tunnel-preflight.d.ts +34 -0
  213. package/dist/setup-tunnel-preflight.js +62 -0
  214. package/dist/socket-path.d.ts +2 -0
  215. package/dist/socket-path.js +22 -0
  216. package/dist/spawn.d.ts +5 -1
  217. package/dist/spawn.js +40 -4
  218. package/dist/startup-prompt.d.ts +15 -0
  219. package/dist/startup-prompt.js +71 -0
  220. package/dist/startup-readiness.d.ts +32 -0
  221. package/dist/startup-readiness.js +101 -0
  222. package/dist/startup-telemetry.d.ts +16 -0
  223. package/dist/startup-telemetry.js +46 -0
  224. package/dist/subscriptions/cli.d.ts +91 -0
  225. package/dist/subscriptions/cli.js +223 -0
  226. package/dist/subscriptions/launch.d.ts +37 -0
  227. package/dist/subscriptions/launch.js +93 -0
  228. package/dist/subscriptions/login.d.ts +77 -0
  229. package/dist/subscriptions/login.js +215 -0
  230. package/dist/subscriptions/observed.d.ts +7 -0
  231. package/dist/subscriptions/observed.js +44 -0
  232. package/dist/subscriptions/probe.d.ts +10 -0
  233. package/dist/subscriptions/probe.js +73 -0
  234. package/dist/subscriptions/service.d.ts +102 -0
  235. package/dist/subscriptions/service.js +364 -0
  236. package/dist/subscriptions/store.d.ts +71 -0
  237. package/dist/subscriptions/store.js +143 -0
  238. package/dist/supervisor/adoption.d.ts +8 -0
  239. package/dist/supervisor/adoption.js +78 -0
  240. package/dist/supervisor/catalog.d.ts +62 -0
  241. package/dist/supervisor/catalog.js +254 -0
  242. package/dist/supervisor/fleet.d.ts +8 -0
  243. package/dist/supervisor/fleet.js +89 -0
  244. package/dist/supervisor/index.js +3 -6
  245. package/dist/supervisor/launchd.d.ts +1 -1
  246. package/dist/supervisor/launchd.js +26 -13
  247. package/dist/supervisor/legacy.d.ts +35 -0
  248. package/dist/supervisor/legacy.js +524 -0
  249. package/dist/supervisor/manager.d.ts +15 -0
  250. package/dist/supervisor/manager.js +270 -0
  251. package/dist/supervisor/registration-refresh.d.ts +45 -0
  252. package/dist/supervisor/registration-refresh.js +266 -0
  253. package/dist/supervisor/scope.d.ts +4 -0
  254. package/dist/supervisor/scope.js +10 -0
  255. package/dist/supervisor/systemd.d.ts +2 -2
  256. package/dist/supervisor/systemd.js +19 -33
  257. package/dist/task-supervision.d.ts +9 -0
  258. package/dist/task-supervision.js +145 -0
  259. package/dist/temp-idle.d.ts +17 -0
  260. package/dist/temp-idle.js +42 -0
  261. package/dist/temp-lifecycle.d.ts +23 -8
  262. package/dist/temp-lifecycle.js +123 -66
  263. package/dist/temp-supervisor-recovery.d.ts +1 -1
  264. package/dist/temp-supervisor-recovery.js +2 -2
  265. package/dist/tunnel-setup-completion.d.ts +7 -0
  266. package/dist/tunnel-setup-completion.js +13 -0
  267. package/dist/tunnel-setup-input.d.ts +19 -0
  268. package/dist/tunnel-setup-input.js +124 -0
  269. package/dist/watchdog/query.d.ts +3 -1
  270. package/dist/watchdog/query.js +2 -1
  271. package/dist/web/agent-attachments.d.ts +29 -0
  272. package/dist/web/agent-attachments.js +162 -0
  273. package/dist/web/auth.d.ts +65 -2
  274. package/dist/web/auth.js +201 -11
  275. package/dist/web/available-port.d.ts +2 -0
  276. package/dist/web/available-port.js +23 -0
  277. package/dist/web/control.d.ts +3 -3
  278. package/dist/web/control.js +10 -8
  279. package/dist/web/conversation-stream-codec.d.ts +23 -0
  280. package/dist/web/conversation-stream-codec.js +110 -0
  281. package/dist/web/conversation-stream.d.ts +25 -0
  282. package/dist/web/conversation-stream.js +231 -0
  283. package/dist/web/events.d.ts +2 -0
  284. package/dist/web/events.js +9 -0
  285. package/dist/web/fleet-config-service.d.ts +4 -1
  286. package/dist/web/fleet-config-service.js +46 -15
  287. package/dist/web/prefix-gateway.d.ts +32 -0
  288. package/dist/web/prefix-gateway.js +248 -0
  289. package/dist/web/resource-views.d.ts +124 -0
  290. package/dist/web/resource-views.js +424 -0
  291. package/dist/web/runtime.d.ts +3 -1
  292. package/dist/web/runtime.js +132 -34
  293. package/dist/web/server.d.ts +19 -0
  294. package/dist/web/server.js +802 -67
  295. package/dist/web/service.d.ts +11 -2
  296. package/dist/web/service.js +30 -4
  297. package/dist/web/workspace-devices.d.ts +107 -0
  298. package/dist/web/workspace-devices.js +277 -0
  299. package/dist/web/workspace-tunnel.d.ts +1 -0
  300. package/dist/web/workspace-tunnel.js +34 -0
  301. package/dist/workspace-enrollment.d.ts +83 -0
  302. package/dist/workspace-enrollment.js +580 -0
  303. package/dist/workspace-replacement.d.ts +66 -0
  304. package/dist/workspace-replacement.js +220 -0
  305. package/docs/acp-files.md +146 -0
  306. package/docs/conversation-stream.md +196 -0
  307. package/docs/fleet-migration.md +54 -0
  308. package/docs/live-voice.md +13 -0
  309. package/docs/room-layouts.md +227 -0
  310. package/docs/self-reminders.md +374 -0
  311. package/docs/task-workspaces.md +225 -0
  312. package/docs/validation/managed-cli-permissions.md +421 -0
  313. package/examples/room-layouts/fresh.yaml +27 -0
  314. package/examples/room-layouts/scoped.yaml +21 -0
  315. package/examples/room-layouts/shared.yaml +19 -0
  316. package/package.json +24 -20
  317. package/presets/README.md +78 -0
  318. package/presets/brain-catalog.json +8 -2
  319. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  320. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  321. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  322. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -2
  323. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  324. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  325. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  326. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  327. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  328. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  329. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  330. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  331. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  332. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  333. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  334. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  335. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  336. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  337. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  338. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  339. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  340. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  341. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  342. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  343. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  344. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  345. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  351. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  352. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  353. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  354. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  384. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  385. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  386. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  387. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  390. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  391. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  392. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  393. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  394. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  396. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  397. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  398. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  399. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  400. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  406. package/presets/fleet/roles/Coordinator.yaml +12 -11
  407. package/presets/fleet/roles/Critic.yaml +13 -0
  408. package/presets/fleet/roles/Developer.yaml +13 -0
  409. package/presets/fleet/roles/Engineer.yaml +53 -0
  410. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  411. package/presets/fleet/room_layouts/pair.yaml +24 -0
  412. package/presets/fleet/room_layouts/single.yaml +17 -0
  413. package/presets/fleet/room_layouts/team.yaml +30 -0
  414. package/presets/fleet/room_templates/engineering.yaml +19 -0
  415. package/presets/manifest.json +2 -2
  416. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  417. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  418. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  419. package/dist/web-app/icons/ours-fleet.svg +0 -4
  420. package/dist/web-app/index.html +0 -17
  421. package/dist/web-app/manifest.webmanifest +0 -15
  422. package/dist/web-app/offline.html +0 -18
  423. package/dist/web-app/sw.js +0 -51
@@ -0,0 +1,220 @@
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 } from './atomic-file.js';
7
+ import { stateRoot } 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 = () => join(workspaceDir(), 'replacement.json');
14
+ const opaque = /^[\w-]{43}$/;
15
+ function binding(value) {
16
+ if (!value || !opaque.test(value.workspaceId) || !opaque.test(value.hostWorkspaceId) || (value.serverCid !== undefined && !/^[a-f0-9]{64}$/i.test(value.serverCid))
17
+ || (value.proofRootCid !== undefined && !/^[a-f0-9]{64}$/i.test(value.proofRootCid)))
18
+ throw Error('Existing workspace binding is invalid; restore it before tunnel setup');
19
+ return { ...value, appOrigin: validateAccountOrigin(value.appOrigin ?? 'https://app.ours.network') };
20
+ }
21
+ export function readWorkspaceBinding() {
22
+ const file = join(workspaceDir(), 'binding.json');
23
+ if (!existsSync(file))
24
+ return;
25
+ let value;
26
+ try {
27
+ value = JSON.parse(readFileSync(file, 'utf8'));
28
+ }
29
+ catch {
30
+ throw Error('Existing workspace binding is invalid');
31
+ }
32
+ return binding(value);
33
+ }
34
+ export function sameWorkspaceBinding(previous, payload) {
35
+ return previous.workspaceId === payload.challenge.workspaceId && previous.appOrigin === payload.appOrigin && (!previous.serverCid || previous.serverCid.toUpperCase() === payload.serverCid.toUpperCase());
36
+ }
37
+ /** A workspace ID is never retired to rerun or migrate that same workspace. */
38
+ export function requiresWorkspaceReplacement(previous, target, migrateAppOrigin = false) {
39
+ if (previous.workspaceId !== target.workspaceId) {
40
+ if (!previous.serverCid)
41
+ throw Error('Legacy registration has no recorded server identity. Rerun its own workspace setup before replacing it');
42
+ return true;
43
+ }
44
+ if (previous.serverCid && previous.serverCid.toUpperCase() !== target.serverCid.toUpperCase())
45
+ throw Error('Same-workspace setup must retain the enrollment server identity; existing setup preserved');
46
+ if (previous.appOrigin !== target.appOrigin && !migrateAppOrigin)
47
+ throw Error('Account origin change requires explicit --migrate-app-origin; existing setup preserved');
48
+ return false;
49
+ }
50
+ /** Ask before redeeming the one-use grant, whose destination is deliberately opaque. */
51
+ export async function confirmWorkspaceReplacement(previous, explicit = false, effects = {}) {
52
+ const write = effects.write ?? (value => { process.stderr.write(value); });
53
+ let hostname = '';
54
+ try {
55
+ const tunnel = JSON.parse(readFileSync(join(workspaceDir(), 'tunnel.json'), 'utf8'));
56
+ if (typeof tunnel.origin === 'string' && /^https:\/\/[a-z0-9][a-z0-9-]{2,60}\.ours-tunnel\.com$/.test(tunnel.origin))
57
+ hostname = ` (${new URL(tunnel.origin).hostname})`;
58
+ }
59
+ catch { }
60
+ 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`);
61
+ if (explicit)
62
+ return true;
63
+ if (!(effects.interactive ?? process.stdin.isTTY)) {
64
+ write('Existing setup preserved. Run in a terminal to confirm, or pass --replace-registration to confirm explicitly.\n');
65
+ return false;
66
+ }
67
+ const ask = effects.ask ?? (async () => {
68
+ const terminal = createInterface({ input: process.stdin, output: process.stderr });
69
+ try {
70
+ return await terminal.question('Replace the existing registration if this command names another workspace? [y/N] ');
71
+ }
72
+ finally {
73
+ terminal.close();
74
+ }
75
+ });
76
+ let answer = '';
77
+ try {
78
+ answer = await ask();
79
+ }
80
+ catch { /* EOF, cancellation and input failure mean no consent. */ }
81
+ if (/^(y|yes)$/i.test(answer.trim()))
82
+ return true;
83
+ write('Existing setup preserved.\n');
84
+ return false;
85
+ }
86
+ export function saveWorkspaceReplacement(record) {
87
+ replaceFileAtomically(replacementPath(), JSON.stringify(record) + '\n', 0o600);
88
+ }
89
+ export function readWorkspaceReplacement() {
90
+ const file = replacementPath();
91
+ if (!existsSync(file))
92
+ return;
93
+ const stat = lstatSync(file);
94
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0 || stat.size > 65536)
95
+ throw Error('Unfinished replacement must be an owned private regular file');
96
+ let record;
97
+ try {
98
+ record = JSON.parse(readFileSync(file, 'utf8'));
99
+ }
100
+ catch {
101
+ throw Error('Unfinished replacement record is invalid');
102
+ }
103
+ if (![1, 2].includes(record.version) || !opaque.test(record.operationNonce) || typeof record.configuration !== 'string' || resolve(record.configuration) !== record.configuration
104
+ || (record.rootCid !== undefined && !/^[a-f0-9]{64}$/i.test(record.rootCid)) || (record.retired !== undefined && typeof record.retired !== 'boolean')
105
+ || (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))
106
+ throw Error('Unfinished replacement record is invalid');
107
+ record.previous = binding(record.previous);
108
+ if (!record.previous.serverCid)
109
+ throw Error('Unfinished replacement has no pinned server identity');
110
+ record.payload = decodeWorkspacePayload(Buffer.from(JSON.stringify(record.payload)).toString('base64url'), { allowExpired: true });
111
+ return record;
112
+ }
113
+ export function clearWorkspaceReplacement() { rmSync(replacementPath(), { force: true }); }
114
+ /** Explicit recovery after the user removes an unsuccessful successor in the App. */
115
+ export function discardRetiredWorkspaceReplacement() {
116
+ const record = readWorkspaceReplacement(), current = readWorkspaceBinding();
117
+ if (!record?.retired || !record.cleaned)
118
+ throw Error('Only a fully retired and locally cleaned replacement can be discarded; resume or safely abandon this operation');
119
+ if (!current || current.workspaceId !== record.previous.workspaceId || current.hostWorkspaceId !== record.previous.hostWorkspaceId || current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase())
120
+ throw Error('The successor binding was committed or the binding changed; resume its setup instead of discarding recovery');
121
+ clearWorkspaceReplacement();
122
+ }
123
+ function checkReplacementService(configuration) {
124
+ const manager = new WebServiceManager(), service = manager.readMetadata();
125
+ if (service?.configuration && resolve(service.configuration) !== resolve(configuration))
126
+ throw Error('Installed web service uses another configuration; existing setup preserved');
127
+ if (!service && existsSync(webControlPath()))
128
+ throw Error('Stop the foreground Fleet web service before replacing its registration; existing setup preserved');
129
+ }
130
+ export async function beginWorkspaceReplacement(previous, payload, configuration, effects = {}) {
131
+ checkWorkspaceConnector(payload);
132
+ if (!requiresWorkspaceReplacement(previous, { workspaceId: payload.challenge.workspaceId, appOrigin: payload.appOrigin, serverCid: payload.serverCid }))
133
+ throw Error('The same workspace must never be retired');
134
+ checkReplacementService(configuration);
135
+ const record = { version: 2, previous: previous, payload, configuration: resolve(configuration), operationNonce: randomBytes(32).toString('base64url'), dispatchAttempted: false, operationExpiresAt: Date.now() + 86400000 - 5 * 60000 };
136
+ record.rootCid = await (effects.preflight ?? unregisterWorkspace)(record.previous, payload, record.operationNonce, effects.attach, { checkOnly: true, operationExpiresAt: record.operationExpiresAt });
137
+ saveWorkspaceReplacement(record);
138
+ return record;
139
+ }
140
+ /** Abandon only after a private receipt confirms no deletion and dispatch was unsent or terminally rejected. */
141
+ export async function abandonWorkspaceReplacement(effects = {}) {
142
+ const record = readWorkspaceReplacement();
143
+ if (!record)
144
+ throw Error('There is no unfinished replacement');
145
+ if (record.retired || record.cleaned || record.version !== 2)
146
+ throw Error('Retirement may already have completed; resume this replacement instead of abandoning it');
147
+ const current = readWorkspaceBinding();
148
+ if (!current || current.workspaceId !== record.previous.workspaceId || current.hostWorkspaceId !== record.previous.hostWorkspaceId || current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase())
149
+ throw Error('Workspace binding changed; replacement was not abandoned');
150
+ let deleted, rejected = false, expired = false, deadlineEnforced = false, oldBindingPresent = false;
151
+ 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; } });
152
+ if (root.toUpperCase() !== record.rootCid?.toUpperCase() || deleted !== false || (record.dispatchAttempted && !(rejected && !expired && Date.now() <= record.operationExpiresAt || oldBindingPresent && deadlineEnforced && Date.now() > record.operationExpiresAt + 5 * 60000)))
153
+ throw Error('Retirement may already have been sent; run --resume or abandon only after the account service confirms terminal rejection');
154
+ clearWorkspaceReplacement();
155
+ }
156
+ /** A failed/expired one-time command can be renewed only for its pinned successor. */
157
+ export function refreshWorkspaceReplacement(record, payload, configuration) {
158
+ if (resolve(configuration) !== record.configuration)
159
+ throw Error('Replacement requires its original configuration');
160
+ if (payload.challenge.workspaceId !== record.payload.challenge.workspaceId || payload.challenge.accountId !== record.payload.challenge.accountId
161
+ || payload.appOrigin !== record.payload.appOrigin || payload.serverCid.toUpperCase() !== record.payload.serverCid.toUpperCase()
162
+ || payload.hostname !== record.payload.hostname)
163
+ throw Error('Unfinished replacement requires a fresh command for the same target workspace');
164
+ record.payload = payload.connectorToken === undefined && record.payload.connectorToken !== undefined ? { ...payload, connectorToken: record.payload.connectorToken } : payload;
165
+ saveWorkspaceReplacement(record);
166
+ }
167
+ /** Only tunnel files and account-issued device capabilities belong to this registration. */
168
+ export async function clearReplacedWorkspaceLocalState() {
169
+ const manager = new WebServiceManager();
170
+ if (manager.readMetadata())
171
+ await manager.stop();
172
+ const devices = new WorkspaceDeviceStore();
173
+ try {
174
+ devices.revokeAll();
175
+ }
176
+ finally {
177
+ devices.close();
178
+ }
179
+ for (const file of ['tunnel.json', 'connector', 'pending-setup.json'])
180
+ rmSync(join(workspaceDir(), file), { force: true });
181
+ }
182
+ /** Persist every irreversible boundary; the original binding stays until enrollment writes its successor. */
183
+ export async function finishWorkspaceReplacement(record, effects = {}) {
184
+ const current = readWorkspaceBinding();
185
+ 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)))
186
+ throw Error('Workspace binding changed during replacement; review it before continuing');
187
+ // A saved successor binding may predate a refreshed challenge. Enrollment
188
+ // checks the exact proof receipt: it reuses confirmed proof, or signs the new
189
+ // challenge, rather than assuming every challenge for this binding is done.
190
+ if (!record.retired) {
191
+ checkReplacementService(record.configuration);
192
+ if (record.version === 2 && Date.now() > record.operationExpiresAt + 5 * 60000) {
193
+ if (record.payload.challenge.expiresAt <= Date.now())
194
+ throw Error('Retirement deadline passed. Obtain a fresh App command for the same target and pass --replace-registration to continue');
195
+ let deleted, enforced = false;
196
+ 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; } });
197
+ if (root.toUpperCase() !== record.rootCid?.toUpperCase())
198
+ throw Error('Original proof root changed; existing recovery preserved');
199
+ if (deleted === false && enforced) {
200
+ // The expired command cannot act. Existing consent covers this exact
201
+ // previous/successor pair, including signed absence after a lost receipt.
202
+ record.operationNonce = randomBytes(32).toString('base64url');
203
+ record.operationExpiresAt = Date.now() + 86400000 - 5 * 60000;
204
+ record.dispatchAttempted = false;
205
+ saveWorkspaceReplacement(record);
206
+ }
207
+ }
208
+ record.rootCid = await (effects.unregister ?? unregisterWorkspace)(record.previous, record.payload, record.operationNonce, effects.attach, { operationExpiresAt: record.operationExpiresAt, beforeSend: () => { record.dispatchAttempted = true; saveWorkspaceReplacement(record); } });
209
+ record.retired = true;
210
+ saveWorkspaceReplacement(record);
211
+ }
212
+ if (!record.cleaned) {
213
+ await (effects.cleanup ?? clearReplacedWorkspaceLocalState)();
214
+ record.cleaned = true;
215
+ saveWorkspaceReplacement(record);
216
+ }
217
+ if (record.payload.challenge.expiresAt <= Date.now())
218
+ 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');
219
+ return (effects.enroll ?? enrollWorkspace)(record.payload, record.configuration, { preserveProfile: true, replacingWorkspace: record.previous.workspaceId, attach: effects.attach });
220
+ }
@@ -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.
@@ -0,0 +1,196 @@
1
+ # Conversation event stream (wire version 1)
2
+
3
+ `GET /api/v1/roles/:id/conversation/events?expectedGeneration=GENERATION&after=CURSOR`
4
+ uses the existing Fleet route boundary, including `/fleet/api/v1/...` through the
5
+ prefix gateway. Use authenticated `fetch`, then read the response body. Cookies
6
+ work for standalone and same-origin frame access; the hosted App sends its current
7
+ workspace access bearer in `Authorization`. Query parameters are data, never
8
+ credentials. `after` is a nonnegative safe integer; generation is required.
9
+ No new WebSocket path, controller lease, notification acknowledgement, work
10
+ submission, or read receipt is introduced.
11
+
12
+ ## Framing
13
+
14
+ Content type is `text/event-stream; charset=utf-8`, with `Cache-Control: no-store`
15
+ and `X-Accel-Buffering: no`. Every frame is exactly:
16
+
17
+ ```text
18
+ event: conversation
19
+ data: {"version":1,"type":"TYPE","payload":...}
20
+
21
+ ```
22
+
23
+ The six logical types are:
24
+
25
+ - `role`: complete public role replacement (`roleId`, `roleRevision`,
26
+ `roleLifetime`, `availability`, `health`, optional backend/fallback/failure).
27
+ Local state paths are omitted. This ordering is independent of session state.
28
+ - `ledger`: either a single durable conversation event, or a coverage marker
29
+ `{coveredThrough:number}`. All events covered by a marker precede it. Replay
30
+ can legitimately advance coverage without visible events. Never advance a
31
+ recovery cursor from maximum rendered event sequence alone.
32
+ - `ready`: `{sessionGeneration,stateRevision,ledgerBarrier,coveredThrough,
33
+ capability:"complete",authorizedUntil}`. The coherent initial snapshot arrives
34
+ as a preceding `state` record, keeping its full 16 MiB payload budget. Hold
35
+ initial state/replay as provisional until matching ready; validate generation,
36
+ stateRevision and ledgerBarrier against the staged state and coverage against
37
+ delivered replay. Only successful private replay certifies `coveredThrough`;
38
+ the snapshot barrier alone does not. Disconnect between state and ready must
39
+ not publish live initial state or advance the certified cursor. Reconnect with
40
+ same-generation certified coverage. State revision and event sequence differ.
41
+ - `state`: complete conversation observation replacement, including
42
+ `sessionGeneration`, `stateRevision` and `ledgerBarrier`. Do not merge it as a
43
+ patch or use its barrier as coverage.
44
+ - `resync`: `{reason,retry,capability}`. Clear partial assembly and resynchronize
45
+ explicitly or enter supported fallback. Temporary role read/watch failure is
46
+ retryable; a fresh observer can recover after repair. `retry:false` alone does
47
+ not establish permanent incapability on older servers. Explicit unsupported
48
+ capability or persistent oversize remains supported fallback without tight reconnects. `capability` is
49
+ `degraded`, except `auth-expired` below.
50
+ - `keepalive`: `{at:number}`. Transport liveness only; it cannot certify role,
51
+ session, replay, or private-control health.
52
+
53
+ A large logical payload uses `start`, ordered `chunk`, then `commit` frames,
54
+ without the `payload` field:
55
+
56
+ ```json
57
+ {"version":1,"type":"start","id":"3","recordType":"state","totalBytes":1048576,"generation":"g","revision":7}
58
+ {"version":1,"type":"chunk","id":"3","index":0,"data":"a slice of the serialized payload JSON"}
59
+ {"version":1,"type":"commit","id":"3","totalBytes":1048576,"generation":"g","revision":7}
60
+ ```
61
+
62
+ Chunks contain JSON text, **not base64**. Concatenate `data`, UTF-8 count it,
63
+ then JSON-parse only after commit. The optional generation/revision are derived
64
+ from payload sessionGeneration/stateRevision or roleRevision;
65
+ commit and reconstructed payload must match start. Reject duplicate/out-of-order
66
+ chunks, empty chunks, mismatched identities/revisions/lengths, overlapping assemblies, invalid
67
+ UTF-8, over-budget frames, and an incomplete end. Never publish partial snapshots.
68
+ `ConversationStreamDecoder` implements these framing checks without Node APIs.
69
+ It is not a domain reducer: consumers must additionally enforce current workspace,
70
+ generation and monotonic state/role revisions before applying records.
71
+
72
+ ## Limits and ownership
73
+
74
+ Encoded frames, including SSE and JSON overhead, are at most 512 KiB. Payload JSON
75
+ is at most 16 MiB; one assembly is allowed at a time. The encoder splits at safe
76
+ UTF-16 boundaries into at most 48 Ki code units, so JSON escaping still fits the
77
+ encoded-frame bound. The active logical record is streamed progressively while
78
+ waiting encoded records are capped at 8 MiB per web connection. A valid 16 MiB
79
+ ledger event is sent independently of its coverage marker so the page envelope
80
+ does not consume its logical-record budget. Oversize records explicitly degrade.
81
+ The writer waits for socket drain before writing another frame; overflow sends a
82
+ resync when writable, otherwise closes. Ledger delivery has no batching delay
83
+ (therefore no intentional wait beyond the required 50 ms); network backpressure
84
+ can delay actual receipt.
85
+
86
+ The private supervisor separately owns its 8 MiB live replay backlog and writes
87
+ 64 KiB slices under socket backpressure. The adapter bounds each raw NDJSON line
88
+ at 16 MiB plus the maximum computed serialized replay/ready/state/event envelope
89
+ (replay has an empty events array; ready omits its null placeholder; coverage uses
90
+ `Number.MAX_SAFE_INTEGER`). This admits one exact 16 MiB event;
91
+ malformed or oversized passive input closes the stream. The receiving browser
92
+ owns its 16 MiB assembly limit. A parsed record, serialized UTF-16 string, encoded
93
+ frame, and socket buffers can coexist: these are logical budgets, not an RSS cap.
94
+ Aggregate memory scales with connections, as do role watchers and private sockets.
95
+ There is no global subscriber cap. HTTP/1 browsers commonly share about six
96
+ connections per origin; multiple tabs, pane consumers and voice can exhaust that
97
+ pool. Dispose surplus consumers; HTTP/2 deployment and browser pooling vary.
98
+
99
+ Each web connection owns one role observer, including while the role is missing.
100
+ Session describe/follow borrow it. Missing/replaced roles stop the old follow;
101
+ reappearance can open another follow, still bound to the requested generation.
102
+ Abort, revocation, server shutdown and late connector completion release owners
103
+ idempotently. Pane observation does not acquire or release voice's independent
104
+ controller. Private heartbeats remain serialized every two seconds; six seconds
105
+ without one ends private health even if SSE keepalives continue.
106
+
107
+ Fleet emits browser keepalives every second. Each App pane/voice attempt independently
108
+ bounds opening through complete ready to ten seconds, regardless of incoming activity.
109
+ After ready, nonempty byte chunks (including keepalives/comments and partial records)
110
+ reset a ten-second silence deadline. Elapsed checks before activity and on browser
111
+ visibility/pageshow resume give no hidden grace; suspended code expires at its first
112
+ subsequent check. Expiry retires health and aborts the old attempt before bounded
113
+ recovery/fallback. These browser clocks do not renew or replace credential deadlines.
114
+ Comment-only/blank SSE frames are transport activity and publish no domain record.
115
+
116
+ ## Authentication, fallback and filesystem assumptions
117
+
118
+ The stream is bound to its **opening** credential deadline. Rotating access lasts
119
+ at most five minutes; it is not renewable in place. On `auth-expired`, obtain the
120
+ renewed access credential and reconnect using only same-generation certified
121
+ coverage. Count this normal auth reconnect separately from degraded operation.
122
+ Fixed credentials use their device/sunset deadline. Cookie idle expiry is checked
123
+ without updating lastSeenAt, including the exact 30-minute boundary; ordinary
124
+ requests still update activity. Absolute expiry and revocation also end streams.
125
+ The terminal resync uses `capability:"auth-expired"`, with retry `"authenticate"`
126
+ for rotating credentials or `"reauthenticate"` for cookies/fixed credentials.
127
+ Actions after expiry must reauthenticate; heartbeats cannot refresh session policy.
128
+
129
+ `conversation_live_complete` describes current intersection of healthy role
130
+ observation and private replay/heartbeat capability. It never certifies requested
131
+ history. REST remains for initial latest history, user-requested older pages,
132
+ explicit recovery, and older/unsupported/degraded server fallback. Healthy active
133
+ consumers must not periodically fetch role details or history.
134
+
135
+ Active pane and independent voice consumers recover transient transport, temporary observer or
136
+ bootstrap failures after their bounded fast attempts: one delayed attempt after 10 seconds,
137
+ then 20 seconds, then capped 30 seconds between failed settled attempts. Fallback refresh ticks
138
+ do not start probes. Retire/abort the fallback request and timer before recovery takes cursor
139
+ ownership; fence late responses without waiting indefinitely for cancellation. Resume only
140
+ same-generation certified coverage; otherwise establish a canonical latest baseline. Ordinary
141
+ REST progress and identical unsuccessful resync never reset the fast restart budget. Complete
142
+ ready ends fallback polling and resets delayed backoff. Pane generation replacement may load a
143
+ new baseline; voice generation replacement stops that session until explicit Start, without
144
+ transferring controller/provider leases. Auth-required/expired ends automatic reads/probes.
145
+
146
+
147
+ Complete role delivery relies on native directory notifications for fleet config,
148
+ role manifests and session files. Qualified native host filesystems are the
149
+ intended deployment (macOS local APFS or Linux local filesystems with functional
150
+ inotify). Watch errors, unexpected closure, unsupported watching, or invalid
151
+ current role reads withdraw completeness. Silent event loss is not detectable
152
+ without polling and is not promised away. Docker must expose the same state files
153
+ and private socket to the Fleet process, with working directory-change delivery
154
+ across the selected bind mount/volume. Docker Desktop host-to-VM mounts, network
155
+ filesystems and remote/FUSE mounts require deployment-specific notification tests;
156
+ use degraded REST fallback when those assumptions cannot be established. No ACP
157
+ library changes are required.
158
+
159
+ ## Admission receipt provenance
160
+
161
+ Prompt admission responses additionally expose optional `sessionGeneration`: the
162
+ original durable admission generation. Both backend session generations are immutable
163
+ for the admitting lifetime. Cached receipts retain that generation, and reopened command
164
+ indexes derive it from the original `prompt.admitted` event, never a replacement session.
165
+ Historical receipts without provable generation leave the field absent.
166
+
167
+ This permits an explicitly retried pre-change `{text, commandId}` browser draft to settle
168
+ only when matching receipt identity and original generation validate against its captured
169
+ current owner. Missing/old provenance keeps a visible unresolved draft and its idempotency
170
+ ID. Existing generation-bound drafts retain their original fences. `eventCursor` and causal
171
+ coverage/inventory settlement remain unchanged; provenance is not a new settlement watermark.
172
+
173
+ ## Cross-repository qualification
174
+
175
+ `test/web/fixtures/conversation-stack.ts` shares the real HTTP and private-control
176
+ stack with the legacy conversation tests and the matching App checkout's optional
177
+ `tests/fleet-live-data.integration.mjs`. ACP uses the existing child-process protocol
178
+ fixture; native Codex uses the existing app-server protocol driver. Both exercise
179
+ actual session, ledger, control adapter, HTTP framing and App decoder/reconciler/UI.
180
+ Role availability changes are controlled fixture inputs; this does not qualify native
181
+ filesystem event delivery on an arbitrary mount.
182
+
183
+ Run Fleet's full tests and typecheck after integrated changes. Retain the SSE,
184
+ workspace-auth-provenance and workspace-tunnel suites for frame/hosted authentication,
185
+ expiry/revocation, delayed-open disposal and bounded slow consumers. App reports
186
+ healthy content requests separately from bootstrap, paging, credential reconnect,
187
+ explicit recovery and unsupported-host fallback. DOM mutation and fixture latency
188
+ measurements have no claim about real-provider inference or physical-device audio.
189
+ The App and Fleet changes require coordinated upstream delivery; source qualification
190
+ does not establish that installed or serving packages contain them.
191
+
192
+ ## Full resource delivery qualification
193
+
194
+ Directory, configuration/definitions, previews, provider/status, layout and admitted-operation views share the authenticated ephemeral resource connection with per-view epochs. Complete ready requires the canonical subscribed payload; a heartbeat does not certify a failed upstream watcher. User commands remain HTTP. Controller, voice and selected-daemon health are independent owners. The selected-daemon health deadline passes its cancellation signal through the public SDK selection/request API, cancelling underlying HTTP I/O as well as retiring the result.
195
+
196
+ The matching App source qualification composes the existing ACP/Codex browser fixture and records sixty-second healthy windows separately from initial/history/recovery/command/health/lease/long-poll traffic. Unit and HTTP producer checks are not proof of every rendered resource combination or installed deployment. Preserve existing room-layout suite sources and qualify released service/dependency versions separately.
@@ -0,0 +1,54 @@
1
+ # Upgrade to one Fleet service
2
+
3
+ Permanent and task agents share one systemd user service on Linux or one
4
+ LaunchAgent on macOS. Install the package and explicitly run `ours-fleet init`;
5
+ package installation alone does not migrate services. New task/temporary launches
6
+ refuse `FLEET_SERVICE_NOT_INSTALLED` until init succeeds. Native supervision uses
7
+ the OS user's home; a custom `OURS_FLEET_HOME` is refused. Manual/isolated fleets
8
+ can use `OURS_FLEET_SUPERVISOR=none` without boot recovery.
9
+
10
+ Before init, back up Fleet configuration, private member/runtime/session state,
11
+ task/room records and native units/plists. Preserve operator readiness commands,
12
+ timeout/restart settings and environment in the common parent's explicit
13
+ systemd drop-ins (`~/.config/systemd/user/ours-fleet.service.d/`). Conflicting
14
+ per-agent settings need operator resolution. Migration does not copy scripts or
15
+ silently discard hooks. Resolve `OPERATOR_DROPIN_REQUIRED` or `UNSUPPORTED_DROPIN`,
16
+ reload the user manager, verify its effective settings, then retry.
17
+
18
+ Init preflights configured permanent agents, retires only their proven old native
19
+ services and registers them after exact old-process absence. `up NAME` migrates
20
+ only NAME. Existing identity and conversation files remain in place. On Linux,
21
+ enabled legacy roles transfer running boot intent, including enabled inactive
22
+ roles; disabled inactive roles remain untouched. macOS stopped roles stay stopped.
23
+ Interrupted transfers retain receipts; retry with the original state instead of
24
+ deleting proof or starting old and new supervisors together.
25
+
26
+ The parent starts existing agent runners. Adding a member does not restart it.
27
+ Restarting the parent affects every managed agent. Permanent agents resume their
28
+ conversation with their existing readiness/continuity prompts and may receive a
29
+ new private runtime instance after orderly release. Task agents retain their
30
+ original identity, runtime owner and conversation. Managed stop persists across
31
+ parent restart/reboot. Non-resumable temporary agents retire on parent shutdown.
32
+
33
+ ## Existing tasks
34
+
35
+ Running pre-upgrade transient task members remain untouched and report
36
+ `migration_pending`; init does not make them durable. Clean legacy shutdown or
37
+ explicit temporary stop releases/archives them. After abrupt process loss,
38
+ `ours-fleet task recover-members TASK_ID` can adopt a stopped single-room member
39
+ only with its original owner, identity, launch, workspace, conversation and room
40
+ admission intact and exact process absence proven. Missing/mismatched evidence
41
+ and unmarked legacy layouts require reconciliation. There is no automatic live
42
+ handover: let old work retire normally, or preserve and inspect its original
43
+ state before a controlled transition. Do not create replacement identities,
44
+ redeem invitations again or fabricate ownership records to force recovery.
45
+
46
+ ## Rollback
47
+
48
+ Stop the common parent and prove all managed process groups absent. Preserve an
49
+ offline backup of catalog, receipts, private state and operator drop-ins. Restore
50
+ the prior Fleet release and reviewed legacy units/plists, enabling only intended
51
+ running agents. Keep the common parent disabled while legacy supervision runs.
52
+ Re-entering central mode requires retained-proof reconciliation and old-process
53
+ absence. Physical reboot/boot ordering, real macOS launchd and live host migration
54
+ have not been qualified by the isolated process and injected native tests.
@@ -0,0 +1,13 @@
1
+ # Live voice managed-session boundary
2
+
3
+ App Live voice sends transcripts through existing ACP managed sessions and reads the actual agent conversation. Updated ACP and native Codex supervisors advertise `generation_bound_prompts`, `idle_bound_prompts` and `targeted_voice_interrupt`; `/api/v1/roles/:id/voice-capabilities` authenticates the browser and reports whether all are present.
4
+
5
+ `submit_voice_prompt` accepts `requireIdle: true`. Receipt replay precedes the idle check so retrying a lost acknowledgment does not create a second prompt or fail just because the first is running. New admission checks generation and idle/queue/tool/permission state inside the existing turn arbiter before queuing. Unrelated work is never interrupted to make room.
6
+
7
+ `interrupt_voice_v2` requires a command ID, expected session generation and exact prompt ID. A matching active prompt uses normal managed-session cancellation; a settled or different prompt is a harmless no-op. A stale generation is rejected. Receipts are idempotent and conflicting reuse of an interrupt command is rejected. The browser's `/interrupt` route selects this path only when the complete target is present, with no legacy unbound fallback.
8
+
9
+ Authenticated CSRF-protected `POST /api/v1/roles/:id/live-presence` takes `{ expectedSessionGeneration }`, validates the current generation before attaching `followConversation`, and streams NDJSON readiness/heartbeats. The HTTP connection owns one attended controller until disconnect. Close, generation change, auth expiry, slow consumer or attach failure detach it. The browser's ordinary workspace fetch supports this stream without provider credentials in a URL. Existing ACP permission expiry and controller detach grace remain in force.
10
+
11
+ The daemon/App changes and configuration instructions live in the corresponding `ours-app/docs/live-voice.md` and `ours-sdk` Live voice documentation. The daemon owns the OpenAI key and 60-second Realtime credential minting; Fleet never receives the provider key.
12
+
13
+ Coverage includes actual ACP/control-socket busy/retry/targeted-cancel tests, native Codex transport tests, authenticated presence-boundary tests and the App native browser/audio integration with separate Codex/Claude ACP protocol fixtures. Provider inference and physical-device acceptance are separate operator checks.