@ours.network/fleet 1.2.0-nightly.4 → 1.2.0-nightly.41

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 (311) hide show
  1. package/README.md +165 -53
  2. package/dist/account-origin.d.ts +2 -0
  3. package/dist/account-origin.js +6 -0
  4. package/dist/agent-ours/bridge.js +9 -0
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/harness.js +8 -0
  8. package/dist/agent-ours/runtime.d.ts +6 -0
  9. package/dist/agent-ours/runtime.js +36 -0
  10. package/dist/agent-ours/service.js +33 -13
  11. package/dist/application/codex-models.d.ts +15 -0
  12. package/dist/application/codex-models.js +56 -0
  13. package/dist/application/conversation-history.d.ts +25 -0
  14. package/dist/application/conversation-history.js +189 -0
  15. package/dist/application/fleet-query-service.d.ts +1 -1
  16. package/dist/application/fleet-query-service.js +2 -2
  17. package/dist/application/legacy-supervisor-identity.d.ts +7 -0
  18. package/dist/application/legacy-supervisor-identity.js +43 -0
  19. package/dist/application/model-catalog.d.ts +1 -1
  20. package/dist/application/model-catalog.js +5 -2
  21. package/dist/application/preset-provenance.d.ts +19 -0
  22. package/dist/application/preset-provenance.js +59 -0
  23. package/dist/application/role-creation-service.js +1 -1
  24. package/dist/application/role-repository.js +2 -2
  25. package/dist/application/room-layout-definitions.d.ts +45 -0
  26. package/dist/application/room-layout-definitions.js +130 -0
  27. package/dist/application/session-control.d.ts +18 -0
  28. package/dist/application/session-control.js +54 -3
  29. package/dist/application/supervisor-ours-tools.d.ts +159 -0
  30. package/dist/application/supervisor-ours-tools.js +113 -0
  31. package/dist/application/task-layouts.d.ts +91 -0
  32. package/dist/application/task-layouts.js +249 -0
  33. package/dist/application/task-room-service.d.ts +99 -3
  34. package/dist/application/task-room-service.js +296 -31
  35. package/dist/atomic-file.d.ts +5 -0
  36. package/dist/atomic-file.js +62 -1
  37. package/dist/build-info.json +6 -4
  38. package/dist/capabilities.d.ts +3 -1
  39. package/dist/capabilities.js +4 -0
  40. package/dist/cli.js +186 -34
  41. package/dist/client-profile.d.ts +5 -16
  42. package/dist/client-profile.js +4 -112
  43. package/dist/config.d.ts +18 -0
  44. package/dist/config.js +28 -4
  45. package/dist/creation.js +2 -2
  46. package/dist/daemon-recovery.d.ts +1 -8
  47. package/dist/daemon-recovery.js +8 -113
  48. package/dist/docs.d.ts +3 -3
  49. package/dist/docs.js +44 -22
  50. package/dist/doctor.d.ts +1 -1
  51. package/dist/doctor.js +46 -59
  52. package/dist/erased-resources.d.ts +9 -0
  53. package/dist/erased-resources.js +87 -0
  54. package/dist/fleet-command-audit.d.ts +1 -0
  55. package/dist/fleet-command-audit.js +11 -6
  56. package/dist/harness/claude-code.js +9 -1
  57. package/dist/harness/claude-runtime.d.ts +12 -0
  58. package/dist/harness/claude-runtime.js +43 -0
  59. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  60. package/dist/harness/codex-app-server-proxy.js +41 -5
  61. package/dist/harness/codex-runtime.d.ts +11 -1
  62. package/dist/harness/codex-runtime.js +31 -4
  63. package/dist/harness/codex.d.ts +3 -2
  64. package/dist/harness/codex.js +8 -5
  65. package/dist/init-wizard.d.ts +2 -0
  66. package/dist/init-wizard.js +38 -19
  67. package/dist/isolation/policy.js +2 -0
  68. package/dist/isolation/types.d.ts +5 -0
  69. package/dist/minimal-setup.d.ts +2 -0
  70. package/dist/minimal-setup.js +44 -0
  71. package/dist/monitor.d.ts +1 -34
  72. package/dist/monitor.js +24 -151
  73. package/dist/notifications/fleet-producer.d.ts +18 -0
  74. package/dist/notifications/fleet-producer.js +129 -0
  75. package/dist/notifications/outbox.d.ts +42 -0
  76. package/dist/notifications/outbox.js +149 -0
  77. package/dist/owner-channel/channel.d.ts +3 -0
  78. package/dist/owner-channel/channel.js +34 -6
  79. package/dist/owner-channel/commands.d.ts +4 -0
  80. package/dist/owner-channel/commands.js +8 -0
  81. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  82. package/dist/owner-channel/ours-client.js +2 -17
  83. package/dist/paths.d.ts +2 -0
  84. package/dist/paths.js +2 -0
  85. package/dist/preset-migration.js +25 -0
  86. package/dist/rooms-tasks/add-member.d.ts +48 -0
  87. package/dist/rooms-tasks/add-member.js +83 -0
  88. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  89. package/dist/rooms-tasks/archived-absence.js +48 -0
  90. package/dist/rooms-tasks/cli.js +34 -5
  91. package/dist/rooms-tasks/close.d.ts +5 -2
  92. package/dist/rooms-tasks/close.js +208 -28
  93. package/dist/rooms-tasks/config.d.ts +1 -1
  94. package/dist/rooms-tasks/config.js +11 -4
  95. package/dist/rooms-tasks/cowork-adapter.d.ts +6 -4
  96. package/dist/rooms-tasks/cowork-adapter.js +11 -117
  97. package/dist/rooms-tasks/cowork-http.d.ts +3 -0
  98. package/dist/rooms-tasks/cowork-http.js +70 -0
  99. package/dist/rooms-tasks/deletion.d.ts +1 -1
  100. package/dist/rooms-tasks/deletion.js +74 -5
  101. package/dist/rooms-tasks/erasure.d.ts +6 -0
  102. package/dist/rooms-tasks/erasure.js +169 -0
  103. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  104. package/dist/rooms-tasks/external-worker.js +3 -4
  105. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  106. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  107. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  108. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  109. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  110. package/dist/rooms-tasks/layout-cli.js +107 -0
  111. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  112. package/dist/rooms-tasks/layout-config.js +89 -0
  113. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  114. package/dist/rooms-tasks/layout-control.js +52 -0
  115. package/dist/rooms-tasks/layout-service.d.ts +42 -0
  116. package/dist/rooms-tasks/layout-service.js +185 -0
  117. package/dist/rooms-tasks/layout.d.ts +114 -0
  118. package/dist/rooms-tasks/layout.js +250 -0
  119. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  120. package/dist/rooms-tasks/live-readiness.js +85 -0
  121. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  122. package/dist/rooms-tasks/member-startup.js +2 -0
  123. package/dist/rooms-tasks/provision.d.ts +14 -0
  124. package/dist/rooms-tasks/provision.js +173 -46
  125. package/dist/rooms-tasks/room-state.d.ts +4 -2
  126. package/dist/rooms-tasks/room-state.js +53 -8
  127. package/dist/rooms-tasks/task-state.d.ts +42 -7
  128. package/dist/rooms-tasks/task-state.js +153 -23
  129. package/dist/rooms-tasks/types.d.ts +28 -0
  130. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  131. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  132. package/dist/rooms-tasks/workspace.d.ts +26 -0
  133. package/dist/rooms-tasks/workspace.js +195 -0
  134. package/dist/runner.d.ts +19 -3
  135. package/dist/runner.js +86 -29
  136. package/dist/session/acp.d.ts +7 -1
  137. package/dist/session/acp.js +21 -6
  138. package/dist/session/arbiter.d.ts +3 -0
  139. package/dist/session/arbiter.js +20 -2
  140. package/dist/session/codex-app-server.d.ts +3 -0
  141. package/dist/session/codex-app-server.js +26 -3
  142. package/dist/session/control.d.ts +10 -1
  143. package/dist/session/control.js +37 -2
  144. package/dist/session/conversation-normalizer.d.ts +2 -1
  145. package/dist/session/conversation-normalizer.js +1 -1
  146. package/dist/session/conversation-types.d.ts +5 -3
  147. package/dist/session/task-notice.d.ts +14 -0
  148. package/dist/session/task-notice.js +13 -0
  149. package/dist/session/types.d.ts +3 -0
  150. package/dist/setup-tunnel-preflight.d.ts +32 -0
  151. package/dist/setup-tunnel-preflight.js +58 -0
  152. package/dist/socket-path.d.ts +2 -0
  153. package/dist/socket-path.js +22 -0
  154. package/dist/spawn.js +7 -0
  155. package/dist/startup-readiness.d.ts +13 -0
  156. package/dist/startup-readiness.js +38 -0
  157. package/dist/subscriptions/cli.d.ts +91 -0
  158. package/dist/subscriptions/cli.js +223 -0
  159. package/dist/subscriptions/launch.d.ts +37 -0
  160. package/dist/subscriptions/launch.js +93 -0
  161. package/dist/subscriptions/login.d.ts +75 -0
  162. package/dist/subscriptions/login.js +182 -0
  163. package/dist/subscriptions/observed.d.ts +7 -0
  164. package/dist/subscriptions/observed.js +44 -0
  165. package/dist/subscriptions/probe.d.ts +10 -0
  166. package/dist/subscriptions/probe.js +73 -0
  167. package/dist/subscriptions/service.d.ts +102 -0
  168. package/dist/subscriptions/service.js +364 -0
  169. package/dist/subscriptions/store.d.ts +71 -0
  170. package/dist/subscriptions/store.js +140 -0
  171. package/dist/supervisor/launchd.js +2 -1
  172. package/dist/supervisor/systemd.js +9 -23
  173. package/dist/temp-idle.d.ts +17 -0
  174. package/dist/temp-idle.js +42 -0
  175. package/dist/temp-lifecycle.d.ts +6 -1
  176. package/dist/temp-lifecycle.js +29 -13
  177. package/dist/web/agent-attachments.d.ts +29 -0
  178. package/dist/web/agent-attachments.js +162 -0
  179. package/dist/web/auth.d.ts +29 -1
  180. package/dist/web/auth.js +81 -7
  181. package/dist/web/available-port.d.ts +2 -0
  182. package/dist/web/available-port.js +23 -0
  183. package/dist/web/control.d.ts +3 -3
  184. package/dist/web/control.js +10 -8
  185. package/dist/web/fleet-config-service.d.ts +4 -1
  186. package/dist/web/fleet-config-service.js +46 -15
  187. package/dist/web/prefix-gateway.d.ts +29 -0
  188. package/dist/web/prefix-gateway.js +206 -0
  189. package/dist/web/runtime.d.ts +3 -1
  190. package/dist/web/runtime.js +116 -31
  191. package/dist/web/server.d.ts +16 -0
  192. package/dist/web/server.js +501 -53
  193. package/dist/web/service.d.ts +11 -2
  194. package/dist/web/service.js +30 -4
  195. package/dist/web/workspace-devices.d.ts +29 -0
  196. package/dist/web/workspace-devices.js +84 -0
  197. package/dist/web/workspace-tunnel.d.ts +1 -0
  198. package/dist/web/workspace-tunnel.js +32 -0
  199. package/dist/workspace-enrollment.d.ts +53 -0
  200. package/dist/workspace-enrollment.js +370 -0
  201. package/docs/room-layouts.md +227 -0
  202. package/docs/task-workspaces.md +130 -0
  203. package/examples/room-layouts/fresh.yaml +27 -0
  204. package/examples/room-layouts/scoped.yaml +21 -0
  205. package/examples/room-layouts/shared.yaml +19 -0
  206. package/package.json +13 -18
  207. package/presets/README.md +78 -0
  208. package/presets/brain-catalog.json +8 -2
  209. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  210. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  211. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  212. package/presets/fleet/agent_templates/LocalCoordinator.yaml +1 -1
  213. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  214. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  215. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  216. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  217. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  218. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  219. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  220. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  221. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  222. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  223. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  224. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  225. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  226. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  227. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  228. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  229. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  230. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  231. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  232. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  233. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  234. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  235. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  236. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  237. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  238. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  239. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  240. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  241. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  242. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  243. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  244. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  245. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  246. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  247. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  248. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  249. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  250. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  251. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  252. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  253. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  254. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  255. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  256. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  257. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  258. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  259. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  260. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  261. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  262. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  263. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  264. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  265. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  266. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  267. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  268. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  269. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  270. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  271. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  272. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  273. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  274. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  275. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  276. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  277. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  278. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  279. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  280. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  281. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  282. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  283. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  284. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  285. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  286. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  287. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  288. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  289. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  290. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  291. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  292. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  293. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  294. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  295. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  296. package/presets/fleet/roles/Coordinator.yaml +34 -11
  297. package/presets/fleet/roles/Engineer.yaml +40 -0
  298. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  299. package/presets/fleet/room_layouts/pair.yaml +24 -0
  300. package/presets/fleet/room_layouts/single.yaml +17 -0
  301. package/presets/fleet/room_layouts/team.yaml +30 -0
  302. package/presets/fleet/room_templates/engineering.yaml +19 -0
  303. package/presets/manifest.json +2 -2
  304. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  305. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  306. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  307. package/dist/web-app/icons/ours-fleet.svg +0 -4
  308. package/dist/web-app/index.html +0 -17
  309. package/dist/web-app/manifest.webmanifest +0 -15
  310. package/dist/web-app/offline.html +0 -18
  311. package/dist/web-app/sw.js +0 -51
package/README.md CHANGED
@@ -79,7 +79,7 @@ The state dir contract:
79
79
  |---|---|---|
80
80
  | Node ≥ 22 | runs `ours-fleet` and its maintained adapters | nodejs.org, `apt`, or `brew` |
81
81
  | a harness CLI, logged in | the agent itself | e.g. Claude Code (`claude`) or Codex CLI (`codex`) |
82
- | `ours` CLI + shared daemon | identity + agent-to-agent messaging | `npm i -g @ours.network/cli@2.8.1-nightly.7 @ours.network/daemon@3.8.1-nightly.1 && ours-daemon start` |
82
+ | `ours` CLI + shared daemon | identity + agent-to-agent messaging | `ours-install client --config /absolute/private/profile.json --integrations fleet --fleet-settings /absolute/fleet-settings.json` |
83
83
 
84
84
  Linux only: `ours-fleet init` enables *linger* so roles run without a login session
85
85
  and survive reboots. macOS: launchd agents start **at login** (no linger
@@ -139,72 +139,55 @@ the caller's owner channel with the caller and spawned-role details. This is an
139
139
  honest-actor convenience and attribution path, not a security boundary; host
140
140
  shells and deliberately bypassed absolute binaries retain direct behavior.
141
141
 
142
- ## Local web console
142
+ ## Local HTTP service
143
143
 
144
- The interactive console is packaged with `@ours.network/fleet` and binds to
145
- IPv4 loopback by default. Remote or proxy exposure is always explicit:
144
+ Fleet is an HTTP backend. It serves no pages, bundles no frontend and opens no
145
+ browser: every screen belongs to the App, which calls this API with a device
146
+ credential. Any address outside `/api/` answers JSON `404`.
146
147
 
147
148
  ```sh
148
- npm run build
149
- ours-fleet web
149
+ ours-fleet setup-tunnel # connect this host to your App workspace
150
+ ours-fleet link-device # connect another browser or device
150
151
  # choose a free port for an isolated test:
151
- ours-fleet web serve --port 0 --no-open
152
- # nginx/TLS terminates at the declared browser origin; fleet remains loopback-bound
152
+ ours-fleet web serve --port 0
153
+ # nginx/TLS terminates at the declared origin; fleet remains loopback-bound
153
154
  ours-fleet web install --public-origin https://fleet.example.com --password-file /secure/fleet-password
154
- # Intentional no-password mode, for example when nginx already authenticates:
155
- ours-fleet web install --public-origin https://fleet.example.com --no-password
156
155
  ```
157
156
 
158
- The normal `ours-fleet web` command installs or updates an owner-level native
159
- service, starts it on stable `127.0.0.1:49271`, and opens/re-pairs the browser.
160
- Linux uses a systemd user unit and macOS uses a LaunchAgent; both restart after
161
- process failures and send logs to the native supervisor. Service management is
162
- explicit through `ours-fleet web install|start|stop|restart|status|uninstall`.
163
- Use `ours-fleet web serve` for a foreground development process. Linux login
164
- persistence requires linger; the installer reports when it is missing but
157
+ `ours-fleet web` installs or updates an owner-level native service and starts
158
+ it on stable `127.0.0.1:49271`. Linux uses a systemd user unit and macOS uses a
159
+ LaunchAgent; both restart after process failures and send logs to the native
160
+ supervisor. Service management is explicit through
161
+ `ours-fleet web install|start|stop|restart|status|uninstall`. Use
162
+ `ours-fleet web serve` for a foreground development process. `--no-open` is
163
+ still accepted because installed service units pass it; it has no effect. Linux
164
+ login persistence requires linger; the installer reports when it is missing but
165
165
  never enables it or requests root privileges.
166
166
 
167
- The normal command opens a one-time five-minute fragment directly in the local
168
- browser; it does not print a reusable secret. If the browser needs pairing
169
- again while the server is running, use `ours-fleet web open`. The fragment is
170
- exchanged once and removed from history. A paired browser receives a rotating,
171
- 30-day `HttpOnly; SameSite=Strict` device credential and can return after a
172
- session idle timeout or server restart. Use **Sign out** to revoke the current
173
- browser, or `ours-fleet web revoke-all` to revoke every trusted browser and
174
- active web session.
175
-
176
- Only a domain-separated SHA-256 device-secret hash and bounded timestamps are
177
- stored in the owner-private fleet state directory (`0700` directory, `0600`
178
- atomic file). The re-pair and revoke controls use an owner-private Unix socket;
179
- local processes running as the same OS user are therefore inside the trust
180
- boundary.
167
+ Use `ours-fleet web revoke-all` to revoke every linked device and active
168
+ session. Only a domain-separated SHA-256 device-secret hash and bounded
169
+ timestamps are stored in the owner-private fleet state directory (`0700`
170
+ directory, `0600` atomic file). The link and revoke controls use an
171
+ owner-private Unix socket; local processes running as the same OS user are
172
+ therefore inside the trust boundary.
181
173
 
182
174
  On first setup, the CLI requires an explicit access choice: `--password-file`
183
175
  or `--pairing` for protected access, or `--no-password` for intentional
184
176
  unprotected access. `--password-file` stores only a salted scrypt verifier in
185
- the owner-private web state; the source file remains operator-managed. New
186
- browsers sign in and then receive the same rotating HttpOnly trusted-device
187
- credential. `--no-password` is deliberately named and prints a warning: anyone
188
- who can reach that origin can control the fleet.
177
+ the owner-private web state; the source file remains operator-managed.
178
+ `--no-password` is deliberately named and prints a warning: anyone who can
179
+ reach that origin can control the fleet.
189
180
 
190
181
  For nginx on a VPS, keep the default loopback bind and set the exact external
191
182
  `--public-origin` (scheme, hostname, optional port). nginx should proxy HTTP and
192
183
  WebSocket upgrades to `127.0.0.1:49271` and provide TLS; rewriting the upstream
193
- Host is not required because the declared browser Origin remains authoritative. To listen beyond
184
+ Host is not required because the declared Origin remains authoritative. To listen beyond
194
185
  loopback, add an explicit `--bind`; fleet refuses a non-loopback bind without a
195
186
  public origin. Host and Origin validation use that declaration rather than
196
187
  trusting forwarded headers. `localhost` and `127.0.0.1` both work in normal
197
- local mode; an unconfigured hostname gets a self-describing HTML page instead
198
- of raw internal Host-header JSON.
199
-
200
- The console is installable as a standalone PWA. Its service worker caches only
201
- the data-free offline page and successful content-hashed JavaScript/CSS assets.
202
- HTML stays network-first and no API, authentication, bootstrap, device,
203
- WebSocket, terminal, event, audit, log, session, query-bearing, or error response
204
- is cached. When the local daemon is unavailable, the PWA shows an explicit
205
- offline shell and no stale fleet state.
188
+ local mode; an unconfigured hostname is refused with a JSON error.
206
189
 
207
- The console provides evidence-separated inventory and status, ACP activity and
190
+ The API provides evidence-separated inventory and status, ACP activity and
208
191
  permission controls, redacted logs, typed text send, confirmed lifecycle
209
192
  actions, and transactional permanent/temporary creation. Identity is fixed to
210
193
  the role name. Creation uses the authenticated
@@ -376,9 +359,9 @@ preserve at most one recent late occurrence. The trusted authoring file and
376
359
  private sealed role snapshot retain exact prompt text; resolved launch, task,
377
360
  room, provenance, and audit presentation surfaces show only bytes and SHA-256.
378
361
 
379
- Every packaged `Developer`, `Critic`, and `LocalCoordinator` Agent Template uses
362
+ Every packaged `Developer`, `Engineer`, `Critic`, and `LocalCoordinator` Agent Template uses
380
363
  `monitor.mode: fleet` with `monitor.interrupt: after_tool`. Consequently every
381
- member of the standard `single`, `pair`, and `team` Room Templates resolves to
364
+ member of the standard `single`, `pair`, `team`, and `engineering` Room Templates resolves to
382
365
  that policy. A custom Agent Template or explicit per-member override remains
383
366
  authoritative and is merged key by key without rewriting unrelated values.
384
367
 
@@ -524,7 +507,7 @@ ours-fleet up
524
507
 
525
508
  ### Rooms and tasks
526
509
 
527
- Init installs editable `single`, `pair`, and `team` definitions under
510
+ Init installs editable `single`, `pair`, `team`, and `engineering` definitions under
528
511
  `~/fleet/room_templates/`, plus every exact-cased Agent, Role, and Brain they
529
512
  reference. Inspect them with `ours-fleet template list` and
530
513
  `ours-fleet template show team`. After configuring the authenticated owner below:
@@ -533,8 +516,25 @@ reference. Inspect them with `ours-fleet template list` and
533
516
  ours-fleet task create --title "Solo task" --template single
534
517
  ours-fleet task create --title "Reviewed change" --template pair
535
518
  ours-fleet task create --title "Phased delivery" --template team
519
+ ours-fleet task create --title "Gated feature build" --template engineering
536
520
  ```
537
521
 
522
+ `engineering` pairs a gate-pipeline `Engineer` with the standard `Critic`. The
523
+ Engineer works every non-trivial task through ordered gates — scope and a numbered
524
+ Definition of Done before code, an over-engineering check, spec before
525
+ implementation, test-first, review, a sized security pass, docs, and evidence-backed
526
+ DoD verification — invoking an installed gate-pipeline skill when one is present and
527
+ walking the same gates by hand otherwise. The seat is labelled `Engineer`, so a
528
+ gated seat is never mistaken for a plain `Developer`. To make it the default for
529
+ development tasks, set `tasks: { default_room_template: engineering }` in
530
+ `fleet.yaml`.
531
+
532
+ The `engineering` starter expresses a prompt-level workflow mandate; Fleet does not enforce
533
+ the gates at runtime or install pipeline skills. The Engineer label identifies that mandate,
534
+ not a completed review. When migrating an existing install, a newly added Engineer uses
535
+ the existing Developer template's brain selection and keeps the packaged restrictive permissions.
536
+ Existing customized Engineer files are preserved.
537
+
538
538
  The default `team` gives only its LocalCoordinator Agent Template a 15-minute
539
539
  continuity loop. Each idle-only pass uses authenticated assigned-room evidence,
540
540
  posts at most one missing interval update or status request, and reports `STALLED`
@@ -588,8 +588,9 @@ non-owner-controlled parent fail closed before host setup and are rechecked unde
588
588
  per-setup init lock before publication.
589
589
 
590
590
  The generated task experiences are fixed consequences, not extra questions: `single`
591
- uses `Developer`; `pair` uses `Developer` with an independent `Critic`; and `team` uses
592
- task-local `LocalCoordinator`, `Developer`, and `Critic`. The persistent
591
+ uses `Developer`; `pair` uses `Developer` with an independent `Critic`; `team` uses
592
+ task-local `LocalCoordinator`, `Developer`, and `Critic`; and `engineering` uses the
593
+ gate-pipeline `Engineer` with an independent `Critic`. The persistent
593
594
  `FleetCoordinator` uses the separate packaged `Coordinator` contract and coordination
594
595
  model.
595
596
 
@@ -666,6 +667,29 @@ continuation is serialized per Task and remains alive until convergence or an
666
667
  Owner-action blocker. Re-running `task start <id>` safely resumes the same durable
667
668
  provisioning operation after the blocker is corrected or a process restarts.
668
669
 
670
+ Live health is distinct from durable lifecycle. `task show TASK --json` adds a
671
+ `readiness` observation for active tasks (`ready`, `degraded`, or `unknown`); human
672
+ and authenticated Owner task-show output display the same observation. Task start/work
673
+ continue to reject unavailable health with `task_not_ready`. A same-role untracked
674
+ seat or a changed recorded launch cannot satisfy an original slot. Each control,
675
+ liveness, and production Cowork probe is bounded; members are checked sequentially,
676
+ so total latency grows with room size. These are point-in-time observations, not
677
+ proof that a timed-out session is dead or permission to respawn it.
678
+
679
+ Before deliberate recovery, Fleet Coordinator must inspect existing room seats and
680
+ the exact old launch's termination record and worklog under `recovery/temporary`.
681
+ Keep original slot records and archives intact; record verified old/new identity
682
+ and launch provenance privately. Never restore archived identity state or reuse
683
+ consumed invites. A same-role seat is only a possible replacement, not proven ownership.
684
+
685
+ A replacement's empty inbox does not establish that no work is pending. Paginate
686
+ scoped room/operator history to the end, retain the cursor privately, authenticate
687
+ historical Owner instructions from identity metadata, and compare replies, worklogs,
688
+ and already-completed actions before resuming. Ask the Owner when authority or
689
+ completion is uncertain. Transfer only necessary non-secret context; never replay
690
+ instructions blindly. Observation does not adopt seats, launch replacements, change
691
+ lifecycle, or modify retained recovery evidence.
692
+
669
693
  Set `room.anonymous: true` on a room template, or pass `--anonymous` to
670
694
  `task create`, `task start`, `task work`, or `room create`, to create an
671
695
  anonymous Cowork room. `--no-anonymous` explicitly overrides an anonymous
@@ -1348,8 +1372,9 @@ The command override is exact: Fleet does not append `--profile`, `--search`, or
1348
1372
 
1349
1373
  One-off/permanent spawn selects a Brain that owns model, reasoning, native permission,
1350
1374
  sandbox, profile, launcher, search, monitor consent, native config, and additional roots.
1351
- Use `env.OURS_PORT`/`env.OURS_CONFIG` for a
1352
- role-specific ours daemon, or configure the host default in `~/.ours/config.json`.
1375
+ All roles use the shared gateway profile `~/.ours-client/profile.json`.
1376
+ `OURS_CONFIG` selects an alternate whole profile. Legacy port/token/state-dir
1377
+ overrides are rejected. See the [gateway setup guide](https://github.com/adapt-toolkit/ours-network/blob/prerelease/packages/installer/GATEWAY_SETUP.md).
1353
1378
 
1354
1379
  ## Agent isolation
1355
1380
 
@@ -1694,3 +1719,90 @@ provisioning or automatic reverse-proxy configuration. Existing local HTTP and
1694
1719
  SSH-tunnel profiles continue to work.
1695
1720
 
1696
1721
  See [supervisor-owned ours and migration](docs/supervisor-ours.md) for the runtime contract and review build instructions.
1722
+
1723
+ ### One-server gateway profile
1724
+
1725
+ A private client profile may include `serverUrl`, for example
1726
+ `https://ours.example/base`, together with `endpoint` set to
1727
+ `https://ours.example/base/daemon`, the existing `expectedInstanceId`, and the
1728
+ absolute private `credentialPath`. Fleet preserves that daemon prefix for doctor
1729
+ and monitoring and derives `/cowork/management/rpc` from `serverUrl` for room
1730
+ management. Requests use the issued server credential, reject redirects, have
1731
+ bounded bodies/deadlines, and never replay mutations. This capability is declared
1732
+ as `cowork.http-management-v1` in the built artifact.
1733
+
1734
+ Explicit Cowork socket, config or state-directory overrides continue to select
1735
+ local Unix management. Profiles without `serverUrl` retain legacy behavior. The
1736
+ HTTP API grants operator room authority; use the installer's supported private
1737
+ or authenticated external gateway entry, and keep its backend ports private.
1738
+
1739
+ ### Agent ours tools through the supervisor
1740
+
1741
+ Operators can invoke the running agent's ordinary ours MCP tools without sending
1742
+ an LLM prompt or binding another session to its identity:
1743
+
1744
+ ```sh
1745
+ ours-fleet ours tools Critic1
1746
+ ours-fleet ours call Critic1 current_identity
1747
+ ours-fleet ours call Critic1 list_contacts
1748
+ ours-fleet ours call Critic1 generate_invite --args-file /private/invite-options.json
1749
+ ours-fleet ours call Critic1 add_contact --args-file /private/contact.json
1750
+ ```
1751
+
1752
+ `tools` returns the managed MCP tool names and argument schemas, plus the selected
1753
+ agent's identity name, CID, and supervisor generation. `call` returns the same
1754
+ identity metadata and the MCP result. The private JSON file contains the tool's
1755
+ arguments (for example `{"invite":"…"}` for `add_contact`); omitted arguments mean
1756
+ `{}`. Protect files containing invites and protect command output containing a
1757
+ new invite. Tool errors set a nonzero CLI exit status.
1758
+
1759
+ The authenticated REST equivalents are `GET /api/v1/roles/:id/ours/tools` and
1760
+ `POST /api/v1/roles/:id/ours/call`, with the normal Fleet session and CSRF token.
1761
+ The POST body is `{"tool":"list_contacts","arguments":{}}`. MCP tool errors
1762
+ remain in `result.isError`; transport failures use the normal Fleet error envelope.
1763
+ Request arguments and results are excluded from the Fleet audit log.
1764
+
1765
+ Both interfaces use the supervisor's existing fixed-identity MCP server and tool
1766
+ policy. Identity creation, removal, switching, and binding are not exposed. The
1767
+ supervisor must be running. Older descriptors are supported only when a unique
1768
+ existing runtime journal, instance record, and identity pin prove the selected
1769
+ agent and generation, and the same MCP connection confirms its identity and
1770
+ lifetime before the operation. This reads existing state without rewriting the
1771
+ descriptor or restarting the agent. Missing, ambiguous, stale, or mismatched
1772
+ proofs fail closed. Unknown identity descriptions also fail closed. Closing an operator call
1773
+ retains the supervisor's identity. An interrupted mutation can have an unknown
1774
+ outcome: inspect state before retrying, because there is no automatic retry or
1775
+ exactly-once guarantee. Contact acceptance alone does not prove peer verification
1776
+ or message delivery. File tools resolve paths in the invoking CLI process or
1777
+ Fleet web server's filesystem context, with that process's access permissions.
1778
+
1779
+ Task workspace ownership, retention, safe deletion and legacy migration are documented in [Task workspaces](docs/task-workspaces.md).
1780
+
1781
+ ### Active task readiness after interruption
1782
+
1783
+ Task and room list states, and `taskProvisioningOutcome`, describe durable lifecycle
1784
+ and provisioning progress; they are not live health checks. Retrying `task start`
1785
+ or `task work` on an active task checks the current Cowork room identity and seats,
1786
+ member launch ownership, supervisor liveness, and authenticated session status
1787
+ before reporting success. Waiting for provisioning completion also checks live
1788
+ readiness. This is a point-in-time observation, not proof of an agent-authored reply.
1789
+
1790
+ A `task_not_ready` result distinguishes confirmed degradation from unknown health
1791
+ (for example, an unavailable control socket). Ask Fleet Coordinator to inspect room
1792
+ and member status and choose supported recovery. These checks do not restart agents,
1793
+ recreate identities, redeem invites, or rewrite lifecycle state. Temporary room agents
1794
+ remain transient across host reboot; retained launch records do not restore them.
1795
+
1796
+ See [predefined Brain models and runtime requirements](presets/README.md) for the verified model/effort matrix and regeneration instructions.
1797
+
1798
+ ### YAML room layouts
1799
+
1800
+ Room layouts describe room composition with shared or fresh participant sessions and independent room closure.
1801
+ See [Room layouts](docs/room-layouts.md) for YAML examples and CLI commands.
1802
+
1803
+
1804
+ ### Dedicated notification service
1805
+
1806
+ Configure the web backend with `OURS_NOTIFICATIONS_ORIGIN` and a Fleet-scoped `OURS_NOTIFICATIONS_PRODUCER_TOKEN`. It reads each local supervisor ledger incrementally with persistent checkpoints under web state, correlating owner browser/channel prompts with terminal completion. Monitor/startup turns do not notify. First installation skips existing history; no agent restart or conversation controller is needed. Persist web state across deployment/restarts. Reads are bounded to 256 KiB per role per tick, and oversized model/media records are skipped across chunks. Expose `/notifications` through the generic authenticated prefix gateway with a server-side user token and `stripBrowserContext: true`; producer tokens stay server-side. All browser sessions of this single-owner console map to one stable notification account. See [ours-notifications](https://github.com/adapt-toolkit/ours-notifications) for configuration/API/release details.
1807
+
1808
+ Existing-host enrollment: `ours-fleet workspace-enroll --file <private-file> --preserve-profile` requires Messenger to advertise preservation support before any enrollment POST. It omits Name/Surname and leaves existing/absent profile untouched. Existing Fleet models and agents are retained; the installer must skip init when its configuration exists. Signed root proof, tunnel setup and device QR issuance remain the normal enrollment flow. Older Messenger fails closed and requires its preservation-capable release.
@@ -0,0 +1,2 @@
1
+ export declare const accountOrigins: readonly ["https://app.ours.network", "https://app.ours-tunnel.com"];
2
+ export declare function validateAccountOrigin(value: unknown): string;
@@ -0,0 +1,6 @@
1
+ export const accountOrigins = ['https://app.ours.network', 'https://app.ours-tunnel.com'];
2
+ export function validateAccountOrigin(value) {
3
+ if (typeof value !== 'string' || !accountOrigins.some(origin => origin === value))
4
+ throw Error('Unsupported account origin');
5
+ return value;
6
+ }
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createHash } from 'node:crypto';
2
3
  import { connect } from 'node:net';
3
4
  import { access, mkdir, open, readFile } from 'node:fs/promises';
4
5
  import { constants } from 'node:fs';
@@ -12,6 +13,14 @@ export async function runBridge(descriptorPath) {
12
13
  typeof descriptor.capability !== 'string' ||
13
14
  !Number.isInteger(descriptor.generation))
14
15
  throw Error('INVALID_BRIDGE_DESCRIPTOR');
16
+ if (process.env.FLEET_OURS_BRIDGE_EXPECTED) {
17
+ const expected = JSON.parse(process.env.FLEET_OURS_BRIDGE_EXPECTED);
18
+ const digest = createHash('sha256').update(JSON.stringify([descriptor.socket, descriptor.capability, descriptor.generation])).digest('hex');
19
+ if (expected.transportDigest !== digest)
20
+ throw Error('SUPERVISOR_TRANSPORT_CHANGED');
21
+ if (['role', 'identity', 'cid', 'generation'].some(key => descriptor[key] !== expected[key]))
22
+ throw Error('SUPERVISOR_SELECTION_CHANGED');
23
+ }
15
24
  const wire = new Wire(connect(descriptor.socket));
16
25
  const handles = new Map();
17
26
  const cleanup = async () => {
@@ -0,0 +1,10 @@
1
+ /** Read-only operations exposed by the identity-owning supervisor. */
2
+ export interface AgentHistoryQuery {
3
+ peer_cid: string;
4
+ limit?: number;
5
+ before_seq?: number;
6
+ }
7
+ export interface SupervisorCorrespondence {
8
+ contacts(): Promise<unknown>;
9
+ history(request: AgentHistoryQuery): Promise<unknown>;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -20,12 +20,19 @@ export function prepareManagedHarness(role, stateDir, cwd, descriptor, env) {
20
20
  args: [bridge],
21
21
  env: [{ name: 'FLEET_OURS_BRIDGE_DESCRIPTOR', value: descriptor }],
22
22
  };
23
+ // Portable allow covers bound identity messaging, not all tools on the server.
24
+ const tools = role.harness === 'codex' && role.permissions?.approval === 'allow'
25
+ ? Object.fromEntries([
26
+ 'current_identity', 'get_messages', 'list_history', 'list_contacts', 'send_message',
27
+ ].map(name => [name, { approval_mode: 'approve' }]))
28
+ : undefined;
23
29
  const native = {
24
30
  mcp_servers: {
25
31
  ours: {
26
32
  command: process.execPath,
27
33
  args: [bridge],
28
34
  env: { FLEET_OURS_BRIDGE_DESCRIPTOR: descriptor },
35
+ ...(tools ? { tools } : {}),
29
36
  },
30
37
  },
31
38
  plugins: { 'ours@ours-codex-marketplace': { enabled: false } },
@@ -35,6 +42,7 @@ export function prepareManagedHarness(role, stateDir, cwd, descriptor, env) {
35
42
  const config = child.CODEX_CONFIG ? JSON.parse(child.CODEX_CONFIG) : {};
36
43
  child.CODEX_CONFIG = JSON.stringify({
37
44
  ...config,
45
+ mcp_servers: { ...config.mcp_servers, ...native.mcp_servers },
38
46
  plugins: { ...config.plugins, ...native.plugins },
39
47
  });
40
48
  // codex-acp otherwise prefers a same-name connector from the user's config.
@@ -1,3 +1,4 @@
1
+ import type { AgentHistoryQuery } from './correspondence.js';
1
2
  import type { OursClient } from '@ours.network/sdk/client';
2
3
  import { RuntimeJournal, type RuntimeState } from './state.js';
3
4
  export interface AssignedIdentity {
@@ -47,6 +48,11 @@ export declare class AgentOursRuntime {
47
48
  prepare(room?: RoomAdmission, timeoutMs?: number): Promise<void>;
48
49
  /** Admission covers SDK calls, response rendering and agent filesystem callbacks. */
49
50
  admit(): Promise<() => void>;
51
+ /** Trusted supervisor-only admission for a later room; keeps the live harness
52
+ * and original startup assignment. Not exposed as an agent lifecycle tool. */
53
+ joinAdditionalRoom(invite: string, expectedRoomCid: string): Promise<void>;
54
+ readContacts(): Promise<unknown>;
55
+ readHistory(request: AgentHistoryQuery): Promise<unknown>;
50
56
  startHarness<T>(start: () => Promise<T>): Promise<T>;
51
57
  suspend(): Promise<void>;
52
58
  terminal(): Promise<void>;
@@ -201,6 +201,42 @@ export class AgentOursRuntime {
201
201
  throw error;
202
202
  }
203
203
  }
204
+ /** Trusted supervisor-only admission for a later room; keeps the live harness
205
+ * and original startup assignment. Not exposed as an agent lifecycle tool. */
206
+ async joinAdditionalRoom(invite, expectedRoomCid) {
207
+ const release = await this.admit();
208
+ try {
209
+ const contact = await this.deps.client.addContact({ invite });
210
+ if (contact.cid !== expectedRoomCid)
211
+ throw Error('ADDITIONAL_ROOM_CID_MISMATCH');
212
+ await this.verify();
213
+ }
214
+ finally {
215
+ release();
216
+ }
217
+ }
218
+ async readContacts() {
219
+ const release = await this.admit();
220
+ try {
221
+ const data = await this.deps.client.listContacts();
222
+ await this.verify();
223
+ return { identity: { name: this.state.name, cid: this.state.cid, generation: this.state.generation }, ...data };
224
+ }
225
+ finally {
226
+ release();
227
+ }
228
+ }
229
+ async readHistory(request) {
230
+ const release = await this.admit();
231
+ try {
232
+ const data = await this.deps.client.listHistory(request);
233
+ await this.verify();
234
+ return { identity: { name: this.state.name, cid: this.state.cid, generation: this.state.generation }, ...data };
235
+ }
236
+ finally {
237
+ release();
238
+ }
239
+ }
204
240
  async startHarness(start) {
205
241
  if (!this.accepting || this.state.phase !== 'READY')
206
242
  throw Error('RUNTIME_NOT_READY');
@@ -1,3 +1,4 @@
1
+ import { socketPath as privateSocketPath } from '../socket-path.js';
1
2
  import { randomBytes, randomUUID } from 'node:crypto';
2
3
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
3
4
  import { join } from 'node:path';
@@ -11,19 +12,31 @@ import { atomicPrivateWrite, binderKey } from './state.js';
11
12
  import { AgentOursRuntime } from './runtime.js';
12
13
  import { startMcpEndpoint } from './mcp-endpoint.js';
13
14
  export const privateRuntimeRoot = () => join(stateRoot(), 'private-ours');
15
+ function roomSecretPath(role) {
16
+ const startup = role.roomMemberStartup;
17
+ return join(privateRuntimeRoot(), 'room-inputs', binderKey(startup.room_identity_cid, JSON.stringify([role.name, startup.invite_id])) + '.json');
18
+ }
19
+ function matchesRoomSecret(secret, role) {
20
+ const startup = role.roomMemberStartup;
21
+ return secret.room_id === startup.room_id && secret.room_identity_cid === startup.room_identity_cid
22
+ && secret.invite_id === startup.invite_id && secret.identity_name === role.identity
23
+ && secret.role === startup.role;
24
+ }
14
25
  /** Only trusted launch orchestration may write this descriptor, never the child. */
15
26
  export function storeRoomSecret(role) {
16
27
  const startup = role.roomMemberStartup;
17
28
  if (!startup?.invite)
18
29
  return;
30
+ if (startup.identity_name !== role.identity)
31
+ throw Error('ROOM_SECRET_MISMATCH');
19
32
  const root = join(privateRuntimeRoot(), 'room-inputs');
20
33
  mkdirSync(root, { recursive: true, mode: 0o700 });
21
- const path = join(root, binderKey(startup.room_identity_cid, role.name) + '.json');
34
+ // Failed attempts retain private evidence. A new invite must never overwrite
35
+ // or consume a previous attempt's descriptor.
36
+ const path = roomSecretPath(role);
22
37
  if (existsSync(path)) {
23
38
  const old = JSON.parse(readFileSync(path, 'utf8'));
24
- if (old.room_id !== startup.room_id ||
25
- old.invite_id !== startup.invite_id ||
26
- old.identity_name !== startup.identity_name)
39
+ if (!matchesRoomSecret(old, role) || old.invite !== startup.invite)
27
40
  throw Error('ROOM_SECRET_COLLISION');
28
41
  return;
29
42
  }
@@ -144,7 +157,9 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
144
157
  let room;
145
158
  if (role.roomMemberStartup) {
146
159
  const startup = role.roomMemberStartup;
147
- const path = join(root, 'room-inputs', binderKey(startup.room_identity_cid, role.name) + '.json');
160
+ const legacyPath = join(root, 'room-inputs', binderKey(startup.room_identity_cid, role.name) + '.json');
161
+ const currentPath = roomSecretPath(role);
162
+ const path = existsSync(currentPath) ? currentPath : legacyPath;
148
163
  const cowork = createCoworkAdapter();
149
164
  room = {
150
165
  id: startup.room_id,
@@ -153,9 +168,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
153
168
  action: startup.invite_id,
154
169
  redeem: async (attached) => {
155
170
  const secret = JSON.parse(readFileSync(path, 'utf8'));
156
- if (secret.room_id !== startup.room_id ||
157
- secret.invite_id !== startup.invite_id ||
158
- secret.identity_name !== role.identity)
171
+ if (!matchesRoomSecret(secret, role))
159
172
  throw Error('ROOM_SECRET_MISMATCH');
160
173
  return attached.addContact({ invite: secret.invite });
161
174
  },
@@ -195,7 +208,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
195
208
  await identities.add(role.identity);
196
209
  const bridgeDir = join(stateDir, '.ours-bridge');
197
210
  mkdirSync(bridgeDir, { recursive: true, mode: 0o700 });
198
- const capability = randomBytes(32).toString('hex'), socket = join(bridgeDir, `g${generation}.sock`);
211
+ const capability = randomBytes(32).toString('hex'), socket = privateSocketPath(join(bridgeDir, `g${generation}.sock`));
199
212
  const endpoint = await startMcpEndpoint({
200
213
  socket,
201
214
  capability,
@@ -207,7 +220,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
207
220
  });
208
221
  partialEndpoint = endpoint;
209
222
  const descriptor = join(bridgeDir, 'descriptor.json');
210
- atomicPrivateWrite(descriptor, { socket, capability, generation });
223
+ atomicPrivateWrite(descriptor, { socket, capability, generation, role: role.name, identity: role.identity, cid: runtime.snapshot.cid });
211
224
  return {
212
225
  runtime,
213
226
  descriptor,
@@ -255,8 +268,10 @@ export async function preparePermanentAssignment(role) {
255
268
  endpoint: profile.endpoint,
256
269
  expectedInstanceId: profile.expectedInstanceId,
257
270
  credentialPath: profile.credentialPath,
258
- sessionMode: 'local',
259
- requiredCapabilities: ['local-pid-v1', 'root-first-identities-v1'],
271
+ sessionMode: 'external',
272
+ leaseToken: randomUUID(),
273
+ requiredCapabilities: ['external-sessions-v1', 'root-first-identities-v1'],
274
+ env: {},
260
275
  });
261
276
  try {
262
277
  const rows = await client.listIdentities(), row = rows.find((r) => r.name === role.identity);
@@ -283,7 +298,12 @@ export async function preparePermanentAssignment(role) {
283
298
  return 'verified';
284
299
  }
285
300
  finally {
286
- await client.close();
301
+ try {
302
+ await client.releaseLease();
303
+ }
304
+ finally {
305
+ await client.close();
306
+ }
287
307
  }
288
308
  }
289
309
  export function readRoomReadiness(roomCid, name) {
@@ -0,0 +1,15 @@
1
+ import { type OfferedModels } from '../subscriptions/cli.js';
2
+ export interface CodexModelDiscoveryDeps {
3
+ /** The Codex executable ACP sessions will run, and its version. */
4
+ runtime(): Promise<{
5
+ executable: string;
6
+ version: string;
7
+ }>;
8
+ ask(executable: string, env: NodeJS.ProcessEnv): Promise<OfferedModels>;
9
+ now(): number;
10
+ }
11
+ /**
12
+ * The models offered to the active Codex account by the runtime that sessions will actually use.
13
+ * An answer is reused only for the same executable, version, profile and sign-in.
14
+ */
15
+ export declare function createCodexModelDiscovery(overrides?: Partial<CodexModelDiscoveryDeps>): () => Promise<OfferedModels>;
@@ -0,0 +1,56 @@
1
+ import { statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { resolveBundledAcpAgent } from '../harness/acp-agent.js';
4
+ import { probeCodexRuntime } from '../harness/codex-runtime.js';
5
+ import { codexOfferedModels, profileEnv } from '../subscriptions/cli.js';
6
+ import { profileHome, readSubscriptionState } from '../subscriptions/store.js';
7
+ const KNOWN_FOR_MS = 5 * 60_000;
8
+ const UNKNOWN_FOR_MS = 10_000;
9
+ /** When the profile's sign-in last changed; a new sign-in must not reuse another account's answer. */
10
+ function signInStamp(home) {
11
+ try {
12
+ return statSync(join(home, 'auth.json')).mtimeMs;
13
+ }
14
+ catch {
15
+ return 0;
16
+ }
17
+ }
18
+ /**
19
+ * The models offered to the active Codex account by the runtime that sessions will actually use.
20
+ * An answer is reused only for the same executable, version, profile and sign-in.
21
+ */
22
+ export function createCodexModelDiscovery(overrides = {}) {
23
+ const deps = {
24
+ runtime: () => probeCodexRuntime(resolveBundledAcpAgent('@agentclientprotocol/codex-acp', 'codex-acp', 'codex-acp'), process.env),
25
+ ask: codexOfferedModels,
26
+ now: Date.now,
27
+ ...overrides,
28
+ };
29
+ let cached;
30
+ let pending;
31
+ return async () => {
32
+ let key, executable, env;
33
+ try {
34
+ const profileId = readSubscriptionState().providers.codex.activeProfileId;
35
+ const profile = profileHome('codex', profileId);
36
+ const runtime = await deps.runtime();
37
+ executable = runtime.executable;
38
+ env = profileEnv('CODEX_HOME', profileId, profile);
39
+ key = [runtime.executable, runtime.version, profileId, signInStamp(profile)].join('\n');
40
+ }
41
+ catch (error) {
42
+ return { state: 'unknown', reason: error instanceof Error ? error.message : String(error) };
43
+ }
44
+ if (cached && cached.key === key && cached.until > deps.now())
45
+ return cached.value;
46
+ if (pending?.key === key)
47
+ return pending.answer;
48
+ const answer = deps.ask(executable, env).then(value => {
49
+ cached = { key, until: deps.now() + (value.state === 'known' ? KNOWN_FOR_MS : UNKNOWN_FOR_MS), value };
50
+ return value;
51
+ }).finally(() => { if (pending?.key === key)
52
+ pending = undefined; });
53
+ pending = { key, answer };
54
+ return answer;
55
+ };
56
+ }