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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/README.md +279 -68
  2. package/dist/account-origin.d.ts +5 -0
  3. package/dist/account-origin.js +31 -0
  4. package/dist/agent-ours/bridge.js +26 -9
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/file-rpc.d.ts +12 -0
  8. package/dist/agent-ours/file-rpc.js +18 -4
  9. package/dist/agent-ours/harness.js +9 -1
  10. package/dist/agent-ours/mcp-endpoint.d.ts +19 -0
  11. package/dist/agent-ours/mcp-endpoint.js +98 -25
  12. package/dist/agent-ours/runtime.d.ts +9 -1
  13. package/dist/agent-ours/runtime.js +50 -2
  14. package/dist/agent-ours/service.d.ts +24 -1
  15. package/dist/agent-ours/service.js +97 -13
  16. package/dist/application/codex-models.d.ts +15 -0
  17. package/dist/application/codex-models.js +56 -0
  18. package/dist/application/conversation-history.d.ts +34 -0
  19. package/dist/application/conversation-history.js +294 -0
  20. package/dist/application/conversation-role-observer.d.ts +62 -0
  21. package/dist/application/conversation-role-observer.js +206 -0
  22. package/dist/application/fleet-query-service.d.ts +1 -1
  23. package/dist/application/fleet-query-service.js +3 -3
  24. package/dist/application/log-service.js +8 -8
  25. package/dist/application/model-catalog.d.ts +1 -1
  26. package/dist/application/model-catalog.js +5 -2
  27. package/dist/application/preset-provenance.d.ts +19 -0
  28. package/dist/application/preset-provenance.js +59 -0
  29. package/dist/application/resource-observer.d.ts +43 -0
  30. package/dist/application/resource-observer.js +323 -0
  31. package/dist/application/role-creation-service.js +3 -2
  32. package/dist/application/role-removal-service.js +17 -3
  33. package/dist/application/role-repository.d.ts +1 -1
  34. package/dist/application/role-repository.js +30 -7
  35. package/dist/application/room-layout-definitions.d.ts +45 -0
  36. package/dist/application/room-layout-definitions.js +130 -0
  37. package/dist/application/session-control.d.ts +63 -17
  38. package/dist/application/session-control.js +221 -23
  39. package/dist/application/supervisor-ours-tools.js +5 -11
  40. package/dist/application/task-layouts.d.ts +91 -0
  41. package/dist/application/task-layouts.js +261 -0
  42. package/dist/application/task-room-service.d.ts +101 -3
  43. package/dist/application/task-room-service.js +286 -33
  44. package/dist/application/types.d.ts +2 -0
  45. package/dist/atomic-file.d.ts +34 -1
  46. package/dist/atomic-file.js +161 -18
  47. package/dist/briefing.d.ts +3 -1
  48. package/dist/briefing.js +97 -31
  49. package/dist/build-info.json +9 -4
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +400 -37
  53. package/dist/client-profile.d.ts +5 -17
  54. package/dist/client-profile.js +4 -127
  55. package/dist/config.d.ts +45 -0
  56. package/dist/config.js +57 -6
  57. package/dist/creation.d.ts +1 -1
  58. package/dist/creation.js +4 -4
  59. package/dist/daemon-recovery.d.ts +14 -9
  60. package/dist/daemon-recovery.js +124 -115
  61. package/dist/docs.d.ts +1 -1
  62. package/dist/docs.js +207 -19
  63. package/dist/doctor.d.ts +4 -1
  64. package/dist/doctor.js +75 -60
  65. package/dist/erased-resources.d.ts +9 -0
  66. package/dist/erased-resources.js +87 -0
  67. package/dist/file-delivery/reader.d.ts +15 -0
  68. package/dist/file-delivery/reader.js +63 -0
  69. package/dist/file-delivery/store.d.ts +15 -0
  70. package/dist/file-delivery/store.js +124 -0
  71. package/dist/file-delivery/types.d.ts +19 -0
  72. package/dist/file-delivery/types.js +1 -0
  73. package/dist/fleet-command-audit.d.ts +7 -0
  74. package/dist/fleet-command-audit.js +24 -8
  75. package/dist/harness/agent-session.d.ts +4 -0
  76. package/dist/harness/claude-code-session.js +1 -1
  77. package/dist/harness/claude-code.js +21 -11
  78. package/dist/harness/claude-runtime.d.ts +12 -0
  79. package/dist/harness/claude-runtime.js +43 -0
  80. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  81. package/dist/harness/codex-app-server-proxy.js +41 -5
  82. package/dist/harness/codex-reasoning.d.ts +5 -0
  83. package/dist/harness/codex-reasoning.js +12 -0
  84. package/dist/harness/codex-runtime.d.ts +11 -1
  85. package/dist/harness/codex-runtime.js +31 -4
  86. package/dist/harness/codex-session.js +2 -2
  87. package/dist/harness/codex.js +24 -12
  88. package/dist/init-wizard.d.ts +2 -0
  89. package/dist/init-wizard.js +38 -19
  90. package/dist/isolation/policy.js +6 -1
  91. package/dist/isolation/types.d.ts +5 -0
  92. package/dist/managed-cli.d.ts +253 -0
  93. package/dist/managed-cli.js +923 -0
  94. package/dist/managed-recovery.d.ts +13 -0
  95. package/dist/managed-recovery.js +31 -0
  96. package/dist/minimal-setup.d.ts +2 -0
  97. package/dist/minimal-setup.js +44 -0
  98. package/dist/monitor.d.ts +9 -34
  99. package/dist/monitor.js +90 -153
  100. package/dist/notifications/fleet-producer.d.ts +31 -0
  101. package/dist/notifications/fleet-producer.js +351 -0
  102. package/dist/notifications/outbox.d.ts +59 -0
  103. package/dist/notifications/outbox.js +199 -0
  104. package/dist/notifications/target-cleanup.d.ts +16 -0
  105. package/dist/notifications/target-cleanup.js +75 -0
  106. package/dist/notifications/task-producer.d.ts +20 -0
  107. package/dist/notifications/task-producer.js +100 -0
  108. package/dist/ops.d.ts +2 -0
  109. package/dist/ops.js +20 -3
  110. package/dist/owner-channel/channel.js +17 -6
  111. package/dist/owner-channel/commands.d.ts +4 -0
  112. package/dist/owner-channel/commands.js +8 -0
  113. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  114. package/dist/owner-channel/ours-client.js +2 -17
  115. package/dist/paths.d.ts +5 -0
  116. package/dist/paths.js +5 -0
  117. package/dist/preset-migration.js +43 -0
  118. package/dist/process-incarnation.d.ts +23 -0
  119. package/dist/process-incarnation.js +42 -0
  120. package/dist/reminders/manager.d.ts +75 -0
  121. package/dist/reminders/manager.js +278 -0
  122. package/dist/reminders/runtime.d.ts +27 -0
  123. package/dist/reminders/runtime.js +32 -0
  124. package/dist/reminders/schedule.d.ts +37 -0
  125. package/dist/reminders/schedule.js +104 -0
  126. package/dist/reminders/store.d.ts +341 -0
  127. package/dist/reminders/store.js +762 -0
  128. package/dist/reminders/tools.d.ts +20 -0
  129. package/dist/reminders/tools.js +114 -0
  130. package/dist/resolved-plan.js +1 -0
  131. package/dist/rooms-tasks/add-member.d.ts +48 -0
  132. package/dist/rooms-tasks/add-member.js +84 -0
  133. package/dist/rooms-tasks/archived-absence.d.ts +4 -0
  134. package/dist/rooms-tasks/archived-absence.js +11 -0
  135. package/dist/rooms-tasks/cli.js +65 -5
  136. package/dist/rooms-tasks/close.d.ts +5 -2
  137. package/dist/rooms-tasks/close.js +224 -36
  138. package/dist/rooms-tasks/config.d.ts +1 -1
  139. package/dist/rooms-tasks/config.js +11 -4
  140. package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
  141. package/dist/rooms-tasks/cowork-adapter.js +15 -121
  142. package/dist/rooms-tasks/cowork-http.js +56 -6
  143. package/dist/rooms-tasks/deletion.d.ts +1 -1
  144. package/dist/rooms-tasks/deletion.js +63 -10
  145. package/dist/rooms-tasks/erasure.d.ts +6 -0
  146. package/dist/rooms-tasks/erasure.js +173 -0
  147. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  148. package/dist/rooms-tasks/external-worker.js +3 -4
  149. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  150. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  151. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  152. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  153. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  154. package/dist/rooms-tasks/layout-cli.js +107 -0
  155. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  156. package/dist/rooms-tasks/layout-config.js +89 -0
  157. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  158. package/dist/rooms-tasks/layout-control.js +52 -0
  159. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  160. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  161. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  162. package/dist/rooms-tasks/layout-service.js +203 -0
  163. package/dist/rooms-tasks/layout.d.ts +121 -0
  164. package/dist/rooms-tasks/layout.js +286 -0
  165. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  166. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  167. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  168. package/dist/rooms-tasks/live-readiness.js +85 -0
  169. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  170. package/dist/rooms-tasks/member-ownership.js +40 -0
  171. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  172. package/dist/rooms-tasks/member-startup.js +2 -0
  173. package/dist/rooms-tasks/provision.d.ts +3 -0
  174. package/dist/rooms-tasks/provision.js +101 -49
  175. package/dist/rooms-tasks/recovery.d.ts +19 -0
  176. package/dist/rooms-tasks/recovery.js +110 -0
  177. package/dist/rooms-tasks/room-state.d.ts +4 -2
  178. package/dist/rooms-tasks/room-state.js +47 -8
  179. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  180. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  181. package/dist/rooms-tasks/task-state.d.ts +43 -5
  182. package/dist/rooms-tasks/task-state.js +244 -22
  183. package/dist/rooms-tasks/types.d.ts +35 -0
  184. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  185. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  186. package/dist/rooms-tasks/workspace.d.ts +35 -0
  187. package/dist/rooms-tasks/workspace.js +325 -0
  188. package/dist/runner.d.ts +33 -4
  189. package/dist/runner.js +410 -62
  190. package/dist/session/acp.d.ts +75 -2
  191. package/dist/session/acp.js +345 -82
  192. package/dist/session/arbiter.d.ts +98 -0
  193. package/dist/session/arbiter.js +272 -3
  194. package/dist/session/codex-app-server.d.ts +45 -1
  195. package/dist/session/codex-app-server.js +225 -32
  196. package/dist/session/control.d.ts +17 -3
  197. package/dist/session/control.js +219 -23
  198. package/dist/session/conversation-normalizer.d.ts +2 -1
  199. package/dist/session/conversation-normalizer.js +1 -1
  200. package/dist/session/conversation-observation.d.ts +25 -0
  201. package/dist/session/conversation-observation.js +100 -0
  202. package/dist/session/conversation-replay.d.ts +50 -0
  203. package/dist/session/conversation-replay.js +203 -0
  204. package/dist/session/conversation-store.d.ts +15 -1
  205. package/dist/session/conversation-store.js +213 -26
  206. package/dist/session/conversation-types.d.ts +57 -4
  207. package/dist/session/prompt-batch.d.ts +79 -0
  208. package/dist/session/prompt-batch.js +120 -0
  209. package/dist/session/task-notice.d.ts +14 -0
  210. package/dist/session/task-notice.js +13 -0
  211. package/dist/session/types.d.ts +42 -1
  212. package/dist/setup-tunnel-preflight.d.ts +34 -0
  213. package/dist/setup-tunnel-preflight.js +62 -0
  214. package/dist/socket-path.d.ts +2 -0
  215. package/dist/socket-path.js +22 -0
  216. package/dist/spawn.d.ts +5 -1
  217. package/dist/spawn.js +40 -4
  218. package/dist/startup-prompt.d.ts +15 -0
  219. package/dist/startup-prompt.js +71 -0
  220. package/dist/startup-readiness.d.ts +32 -0
  221. package/dist/startup-readiness.js +101 -0
  222. package/dist/startup-telemetry.d.ts +16 -0
  223. package/dist/startup-telemetry.js +46 -0
  224. package/dist/subscriptions/cli.d.ts +91 -0
  225. package/dist/subscriptions/cli.js +223 -0
  226. package/dist/subscriptions/launch.d.ts +37 -0
  227. package/dist/subscriptions/launch.js +93 -0
  228. package/dist/subscriptions/login.d.ts +77 -0
  229. package/dist/subscriptions/login.js +215 -0
  230. package/dist/subscriptions/observed.d.ts +7 -0
  231. package/dist/subscriptions/observed.js +44 -0
  232. package/dist/subscriptions/probe.d.ts +10 -0
  233. package/dist/subscriptions/probe.js +73 -0
  234. package/dist/subscriptions/service.d.ts +102 -0
  235. package/dist/subscriptions/service.js +364 -0
  236. package/dist/subscriptions/store.d.ts +71 -0
  237. package/dist/subscriptions/store.js +143 -0
  238. package/dist/supervisor/adoption.d.ts +8 -0
  239. package/dist/supervisor/adoption.js +78 -0
  240. package/dist/supervisor/catalog.d.ts +62 -0
  241. package/dist/supervisor/catalog.js +254 -0
  242. package/dist/supervisor/fleet.d.ts +8 -0
  243. package/dist/supervisor/fleet.js +89 -0
  244. package/dist/supervisor/index.js +3 -6
  245. package/dist/supervisor/launchd.d.ts +1 -1
  246. package/dist/supervisor/launchd.js +26 -13
  247. package/dist/supervisor/legacy.d.ts +35 -0
  248. package/dist/supervisor/legacy.js +524 -0
  249. package/dist/supervisor/manager.d.ts +15 -0
  250. package/dist/supervisor/manager.js +270 -0
  251. package/dist/supervisor/registration-refresh.d.ts +45 -0
  252. package/dist/supervisor/registration-refresh.js +266 -0
  253. package/dist/supervisor/scope.d.ts +4 -0
  254. package/dist/supervisor/scope.js +10 -0
  255. package/dist/supervisor/systemd.d.ts +2 -2
  256. package/dist/supervisor/systemd.js +19 -33
  257. package/dist/task-supervision.d.ts +9 -0
  258. package/dist/task-supervision.js +145 -0
  259. package/dist/temp-idle.d.ts +17 -0
  260. package/dist/temp-idle.js +42 -0
  261. package/dist/temp-lifecycle.d.ts +23 -8
  262. package/dist/temp-lifecycle.js +123 -66
  263. package/dist/temp-supervisor-recovery.d.ts +1 -1
  264. package/dist/temp-supervisor-recovery.js +2 -2
  265. package/dist/tunnel-setup-completion.d.ts +7 -0
  266. package/dist/tunnel-setup-completion.js +13 -0
  267. package/dist/tunnel-setup-input.d.ts +19 -0
  268. package/dist/tunnel-setup-input.js +130 -0
  269. package/dist/watchdog/query.d.ts +3 -1
  270. package/dist/watchdog/query.js +2 -1
  271. package/dist/web/agent-attachments.d.ts +29 -0
  272. package/dist/web/agent-attachments.js +162 -0
  273. package/dist/web/auth.d.ts +65 -2
  274. package/dist/web/auth.js +201 -11
  275. package/dist/web/available-port.d.ts +2 -0
  276. package/dist/web/available-port.js +23 -0
  277. package/dist/web/control.d.ts +3 -3
  278. package/dist/web/control.js +10 -8
  279. package/dist/web/conversation-stream-codec.d.ts +23 -0
  280. package/dist/web/conversation-stream-codec.js +110 -0
  281. package/dist/web/conversation-stream.d.ts +26 -0
  282. package/dist/web/conversation-stream.js +239 -0
  283. package/dist/web/events.d.ts +2 -0
  284. package/dist/web/events.js +9 -0
  285. package/dist/web/fleet-config-service.d.ts +7 -1
  286. package/dist/web/fleet-config-service.js +61 -16
  287. package/dist/web/prefix-gateway.d.ts +32 -0
  288. package/dist/web/prefix-gateway.js +248 -0
  289. package/dist/web/resource-views.d.ts +135 -0
  290. package/dist/web/resource-views.js +486 -0
  291. package/dist/web/runtime.d.ts +3 -1
  292. package/dist/web/runtime.js +132 -34
  293. package/dist/web/server.d.ts +19 -0
  294. package/dist/web/server.js +821 -68
  295. package/dist/web/service.d.ts +11 -2
  296. package/dist/web/service.js +30 -4
  297. package/dist/web/workspace-devices.d.ts +107 -0
  298. package/dist/web/workspace-devices.js +277 -0
  299. package/dist/web/workspace-tunnel.d.ts +1 -0
  300. package/dist/web/workspace-tunnel.js +34 -0
  301. package/dist/workspace-enrollment.d.ts +83 -0
  302. package/dist/workspace-enrollment.js +605 -0
  303. package/dist/workspace-replacement.d.ts +66 -0
  304. package/dist/workspace-replacement.js +238 -0
  305. package/docs/acp-files.md +146 -0
  306. package/docs/conversation-stream.md +216 -0
  307. package/docs/fleet-migration.md +54 -0
  308. package/docs/live-voice.md +13 -0
  309. package/docs/room-layouts.md +227 -0
  310. package/docs/self-reminders.md +374 -0
  311. package/docs/task-workspaces.md +225 -0
  312. package/docs/validation/managed-cli-permissions.md +421 -0
  313. package/examples/room-layouts/fresh.yaml +27 -0
  314. package/examples/room-layouts/scoped.yaml +21 -0
  315. package/examples/room-layouts/shared.yaml +19 -0
  316. package/package.json +24 -20
  317. package/presets/README.md +78 -0
  318. package/presets/brain-catalog.json +8 -2
  319. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  320. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  321. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  322. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -2
  323. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  324. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  325. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  326. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  327. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  328. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  329. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  330. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  331. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  332. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  333. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  334. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  335. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  336. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  337. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  338. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  339. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  340. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  341. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  342. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  343. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  344. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  345. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  351. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  352. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  353. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  354. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  384. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  385. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  386. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  387. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  390. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  391. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  392. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  393. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  394. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  396. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  397. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  398. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  399. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  400. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  406. package/presets/fleet/roles/Coordinator.yaml +12 -11
  407. package/presets/fleet/roles/Critic.yaml +13 -0
  408. package/presets/fleet/roles/Developer.yaml +13 -0
  409. package/presets/fleet/roles/Engineer.yaml +53 -0
  410. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  411. package/presets/fleet/room_layouts/pair.yaml +24 -0
  412. package/presets/fleet/room_layouts/single.yaml +17 -0
  413. package/presets/fleet/room_layouts/team.yaml +30 -0
  414. package/presets/fleet/room_templates/engineering.yaml +19 -0
  415. package/presets/manifest.json +2 -2
  416. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  417. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  418. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  419. package/dist/web-app/icons/ours-fleet.svg +0 -4
  420. package/dist/web-app/index.html +0 -17
  421. package/dist/web-app/manifest.webmanifest +0 -15
  422. package/dist/web-app/offline.html +0 -18
  423. package/dist/web-app/sw.js +0 -51
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, } from 'node:fs';
3
- import { basename, dirname, extname, join } from 'node:path';
3
+ import { basename, dirname, extname, join, resolve } from 'node:path';
4
4
  import { parse, stringify } from 'yaml';
5
5
  import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
6
6
  import { loadConfig, ROLE_NAME_RE, splitRootFor } from '../config.js';
@@ -18,20 +18,34 @@ export class FleetConfigService {
18
18
  path;
19
19
  preflight;
20
20
  beforeMutation;
21
+ validationInputs = [];
22
+ /** Cached during canonical reads; observer filtering must never parse YAML. */
23
+ resourceValidationInputs() { return this.validationInputs; }
21
24
  constructor(options = {}) {
22
25
  this.path = options.configPath ?? defaultConfigPath();
23
26
  this.preflight = options.preflight ?? (async () => emptyReport());
24
27
  this.beforeMutation = options.beforeMutation;
25
28
  }
26
- read() {
29
+ read(includeDefinitions = false) {
27
30
  const sources = readSources(this.path);
31
+ let raw;
28
32
  try {
33
+ const manifest = parse(sources.documents.get(basename(sources.manifest)), { prettyErrors: false });
34
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
35
+ const vars = record(manifest.vars);
36
+ const expand = (value) => value.replace(/\$\{(\w+)\}/g, (match, key) => key in vars ? String(vars[key]) : match);
37
+ const invite = record(record(manifest.rooms).owner).public_invite_file;
38
+ const prompts = Object.values(record(manifest.watchdogs)).map(value => record(value).prompt_file);
39
+ // Record even invalid/missing references before validation, so repairing an
40
+ // already observed dependency can never retain a certified stale capture.
41
+ this.validationInputs = [...(typeof invite === 'string' ? [expand(expand(invite))] : []),
42
+ ...prompts.filter((value) => typeof value === 'string').map(expand)].map(value => resolve(value));
29
43
  loadConfig(this.path, { yamlMode: 'strict' });
44
+ raw = modelFrom(sources, includeDefinitions);
30
45
  }
31
46
  catch (error) {
32
47
  throw safeError(error, sourceSensitiveValues(sources));
33
48
  }
34
- const raw = modelFrom(sources);
35
49
  const redacted = redactModel(raw);
36
50
  return {
37
51
  path: basename(this.path), exists: true, firstRun: false,
@@ -41,7 +55,7 @@ export class FleetConfigService {
41
55
  async preview(baseRevision, input) {
42
56
  const current = readSources(this.path);
43
57
  assertRevision(baseRevision, current);
44
- const currentModel = modelFrom(current);
58
+ const currentModel = modelFrom(current, !!input && typeof input === 'object' && 'roles' in input);
45
59
  const next = restoreRedactions(assertModel(input), currentModel, new Set(redactModel(currentModel).paths));
46
60
  const proposal = renderProposal(current, next);
47
61
  const staged = stageProposal(this.path, current, proposal);
@@ -63,7 +77,7 @@ export class FleetConfigService {
63
77
  return withFileLock(`${splitRootFor(this.path)}.web-edit.lock`, async () => {
64
78
  const current = readSources(this.path);
65
79
  assertRevision(baseRevision, current);
66
- const currentModel = modelFrom(current);
80
+ const currentModel = modelFrom(current, !!input && typeof input === 'object' && 'roles' in input);
67
81
  const next = restoreRedactions(assertModel(input), currentModel, new Set(redactModel(currentModel).paths));
68
82
  const proposal = renderProposal(current, next);
69
83
  const changed = changedDocuments(current, proposal);
@@ -105,6 +119,8 @@ export class FleetConfigService {
105
119
  rmSync(target, { force: true });
106
120
  else {
107
121
  assertWritableTarget(target, current.documents.has(rel));
122
+ mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
123
+ trustedDirectory(dirname(target));
108
124
  replaceFileAtomically(target, contents, 0o600);
109
125
  chmodSync(target, 0o600);
110
126
  }
@@ -184,7 +200,7 @@ function readSources(manifest) {
184
200
  }
185
201
  return { manifest, root, documents };
186
202
  }
187
- function modelFrom(sources) {
203
+ function modelFrom(sources, includeDefinitions = false) {
188
204
  const manifestKey = basename(sources.manifest);
189
205
  const manifest = parseFleetDocument(sources.manifest, sources.documents.get(manifestKey), 'strict').value;
190
206
  const agents = {};
@@ -199,15 +215,24 @@ function modelFrom(sources) {
199
215
  const id = basename(rel, extname(rel));
200
216
  agent_templates[id] = parseFleetDocument(absoluteFor(sources.manifest, rel), source, 'strict').value;
201
217
  }
202
- return { manifest, agents, agent_templates };
218
+ const model = { manifest, agents, agent_templates };
219
+ if (includeDefinitions)
220
+ for (const kind of ['roles', 'brains', 'room_templates']) {
221
+ model[kind] = {};
222
+ for (const [rel, source] of sources.documents)
223
+ if (rel.startsWith(kind + '/') && ['.yaml', '.yml'].includes(extname(rel))) {
224
+ model[kind][basename(rel, extname(rel))] = parseFleetDocument(absoluteFor(sources.manifest, rel), source, 'strict').value;
225
+ }
226
+ }
227
+ return model;
203
228
  }
204
229
  function assertModel(value) {
205
230
  if (!value || typeof value !== 'object' || Array.isArray(value))
206
231
  throw new FleetError('invalid_request', 'model must be an object');
207
232
  const input = value;
208
- if (Object.keys(input).sort().join(',') !== 'agent_templates,agents,manifest')
209
- throw new FleetError('invalid_request', 'model allows exactly manifest, agents, and agent_templates');
210
- for (const key of ['manifest', 'agents', 'agent_templates'])
233
+ if (!['agent_templates,agents,manifest', 'agent_templates,agents,brains,manifest,roles,room_templates'].includes(Object.keys(input).sort().join(',')))
234
+ throw new FleetError('invalid_request', 'model requires manifest, agents, agent_templates and optionally all definition collections');
235
+ for (const key of Object.keys(input))
211
236
  if (!input[key] || typeof input[key] !== 'object' || Array.isArray(input[key]))
212
237
  throw new FleetError('invalid_request', `${key} must be an object`);
213
238
  for (const [id, doc] of Object.entries(input.agents)) {
@@ -222,6 +247,14 @@ function assertModel(value) {
222
247
  if (!doc || typeof doc !== 'object' || Array.isArray(doc))
223
248
  throw new FleetError('invalid_request', `Agent Template '${id}' must be an object`);
224
249
  }
250
+ for (const kind of ['roles', 'brains', 'room_templates']) {
251
+ for (const [id, doc] of Object.entries((input[kind] ?? {}))) {
252
+ if (!ROLE_NAME_RE.test(id))
253
+ throw new FleetError('invalid_request', `invalid definition ID '${id}'`);
254
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc))
255
+ throw new FleetError('invalid_request', `definition '${id}' must be an object`);
256
+ }
257
+ }
225
258
  return structuredClone(input);
226
259
  }
227
260
  function renderProposal(current, model) {
@@ -240,9 +273,21 @@ function renderProposal(current, model) {
240
273
  next.set(oldRel ?? join('agent_templates', `${id}.yaml`), oldRel
241
274
  ? render(current.documents.get(oldRel), doc) : stringify(doc));
242
275
  }
243
- for (const [rel, source] of current.documents)
244
- if (rel.startsWith('roles/') || rel.startsWith('brains/') || rel.startsWith('room_templates/'))
245
- next.set(rel, source);
276
+ for (const kind of ['roles', 'brains', 'room_templates']) {
277
+ if (model[kind] === undefined) {
278
+ for (const [rel, source] of current.documents)
279
+ if (rel.startsWith(kind + '/'))
280
+ next.set(rel, source);
281
+ continue;
282
+ }
283
+ for (const [rel, source] of current.documents)
284
+ if (rel.startsWith(kind + '/') && !['.yaml', '.yml'].includes(extname(rel)))
285
+ next.set(rel, source);
286
+ for (const [id, doc] of Object.entries(model[kind])) {
287
+ const oldRel = [...current.documents.keys()].find(rel => rel.startsWith(kind + '/') && ['.yaml', '.yml'].includes(extname(rel)) && basename(rel, extname(rel)) === id);
288
+ next.set(oldRel ?? join(kind, `${id}.yaml`), oldRel ? render(current.documents.get(oldRel), doc) : stringify(doc));
289
+ }
290
+ }
246
291
  return next;
247
292
  }
248
293
  function render(source, model) {
@@ -395,9 +440,9 @@ function sourceDiff(before, after, label, secrets) {
395
440
  ...left.slice(left.length - tail, left.length - tail + trailing).map(x => ` ${x}`), ''].join('\n');
396
441
  }
397
442
  function proposalDiff(current, proposal) {
398
- const secrets = sensitiveValues(modelFrom(current));
443
+ const secrets = sensitiveValues(modelFrom(current, true));
399
444
  try {
400
- secrets.push(...sensitiveValues(modelFrom({ ...current, documents: proposal })));
445
+ secrets.push(...sensitiveValues(modelFrom({ ...current, documents: proposal }, true)));
401
446
  }
402
447
  catch { /* validation reports later */ }
403
448
  return [...new Set([...current.documents.keys(), ...proposal.keys()])].sort().map(rel => sourceDiff(current.documents.get(rel) ?? '', proposal.get(rel) ?? '', rel, secrets)).filter(Boolean).join('\n');
@@ -405,7 +450,7 @@ function proposalDiff(current, proposal) {
405
450
  function splitLines(source) { return source.replace(/\n$/, '').split('\n'); }
406
451
  function restartImpact(before, after) {
407
452
  const roles = [...new Set([...Object.keys(before.agents), ...Object.keys(after.agents)])]
408
- .filter(id => JSON.stringify(before.agents[id]) !== JSON.stringify(after.agents[id])).sort();
453
+ .filter(id => JSON.stringify(before.agents[id]) !== JSON.stringify(after.agents[id]) || JSON.stringify(before.roles) !== JSON.stringify(after.roles) || JSON.stringify(before.brains) !== JSON.stringify(after.brains)).sort();
409
454
  const watchdogScheduler = JSON.stringify(before.manifest.watchdogs) !== JSON.stringify(after.manifest.watchdogs);
410
455
  const scheduledLoops = JSON.stringify(before.manifest.loops) !== JSON.stringify(after.manifest.loops);
411
456
  const required = roles.length > 0 || watchdogScheduler || scheduledLoops;
@@ -0,0 +1,32 @@
1
+ import type { AuditSink } from './audit.js';
2
+ import { type IncomingMessage } from 'node:http';
3
+ import type { WebAuth } from './auth.js';
4
+ /** Each entry exposes one dedicated backend listener. Endpoint dispatch stays upstream. */
5
+ export interface ServiceTarget {
6
+ prefix: string;
7
+ origin: string;
8
+ upstreamPrefix?: string;
9
+ /** Server-side service credentials; never taken from browser headers. */
10
+ headers?: Record<string, string>;
11
+ /** Dedicated credential-authenticated services reject direct browser context. */
12
+ stripBrowserContext?: boolean;
13
+ /**
14
+ * Service paths (below the prefix) where the account App may open a WebSocket with its device
15
+ * credential. A browser cannot set Authorization on a WebSocket, so only there it is accepted
16
+ * as a subprotocol.
17
+ */
18
+ deviceSocketPaths?: string[];
19
+ }
20
+ export interface GatewayOptions {
21
+ /** Backend mode is for a separate loopback machine-client listener; browsers are rejected. */
22
+ auth: WebAuth | 'backend';
23
+ fleetOrigin: string;
24
+ services: ServiceTarget[];
25
+ /** Bounded unauthorized-response diagnostics: fixed service/action enums only. */
26
+ audit?: Pick<AuditSink, 'record'>;
27
+ }
28
+ /** Raw transport deliberately runs outside Fastify's JSON parser and body limits. */
29
+ export declare function createPrefixGateway(options: GatewayOptions): {
30
+ server: import("http").Server<typeof IncomingMessage, typeof import("http").ServerResponse>;
31
+ close(): Promise<void>;
32
+ };
@@ -0,0 +1,248 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { WorkspaceDeviceAuthError } from './workspace-devices.js';
3
+ import { createServer, request as httpRequest } from 'node:http';
4
+ import { request as httpsRequest } from 'node:https';
5
+ /** The App offers exactly these two subprotocols; only the marker is ever answered or forwarded. */
6
+ const DEVICE_SOCKET_PROTOCOL = 'ours.workspace.v1';
7
+ const DEVICE_SOCKET_BEARER = 'ours.workspace.bearer.';
8
+ /** The device credential a WebSocket offered as a subprotocol; undefined when it offered none of ours. */
9
+ function deviceSocketCredential(req) {
10
+ const offered = String(req.headers['sec-websocket-protocol'] ?? '').split(',').map(value => value.trim()).filter(Boolean);
11
+ if (!offered.some(value => value === DEVICE_SOCKET_PROTOCOL || value.startsWith(DEVICE_SOCKET_BEARER)))
12
+ return undefined;
13
+ const credentials = offered.filter(value => value.startsWith(DEVICE_SOCKET_BEARER));
14
+ const token = credentials[0]?.slice(DEVICE_SOCKET_BEARER.length) ?? '';
15
+ if (offered.length !== 2 || credentials.length !== 1 || !offered.includes(DEVICE_SOCKET_PROTOCOL) || !/^[A-Za-z0-9_.-]{16,256}$/.test(token))
16
+ throw new Error('invalid workspace socket subprotocol');
17
+ return token;
18
+ }
19
+ const hopHeaders = new Set(['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer', 'transfer-encoding', 'upgrade']);
20
+ function headersFor(message) {
21
+ const denied = new Set([...hopHeaders, ...(message.headers.connection ?? '').split(',').map(x => x.trim().toLowerCase())]);
22
+ return Object.fromEntries(Object.entries(message.headers).filter(([key]) => !denied.has(key)));
23
+ }
24
+ /** Raw transport deliberately runs outside Fastify's JSON parser and body limits. */
25
+ export function createPrefixGateway(options) {
26
+ const auth = options.auth === 'backend' ? undefined : options.auth;
27
+ let auditWindow = Date.now(), auditCount = 0;
28
+ const rejected = (requestId, prefix, result) => {
29
+ if (Date.now() - auditWindow >= 60_000) {
30
+ auditWindow = Date.now();
31
+ auditCount = 0;
32
+ }
33
+ if (auditCount++ >= 60)
34
+ return;
35
+ void options.audit?.record({ requestId, action: 'gateway' + prefix.replaceAll('/', '.'), result, errorCode: 'unauthorized' }).catch(() => { });
36
+ };
37
+ const services = options.services.map(target => {
38
+ if (!/^\/[a-z][a-z0-9-]*$/.test(target.prefix) || target.prefix === '/fleet')
39
+ throw new Error('invalid service prefix');
40
+ const origin = new URL(target.origin);
41
+ if (target.upstreamPrefix && !/^\/(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]*$/.test(target.upstreamPrefix))
42
+ throw new Error('invalid upstream service prefix');
43
+ if (!['http:', 'https:'].includes(origin.protocol) || origin.pathname !== '/' || origin.search || origin.hash || origin.username || origin.password)
44
+ throw new Error('service target must be an HTTP origin');
45
+ return { ...target, origin: origin.origin };
46
+ });
47
+ if (new Set(services.map(x => x.prefix)).size !== services.length)
48
+ throw new Error('duplicate service prefix');
49
+ const select = (req) => {
50
+ const raw = req.url ?? '/';
51
+ if (!raw.startsWith('/') || raw.startsWith('//'))
52
+ throw new Error('invalid request target');
53
+ const path = raw.split('?')[0];
54
+ const service = services.find(x => path === x.prefix || path.startsWith(x.prefix + '/'));
55
+ const fleet = path === '/fleet/api' || path.startsWith('/fleet/api/');
56
+ return { service, origin: service?.origin ?? options.fleetOrigin,
57
+ path: service ? (service.upstreamPrefix || '') + (raw.slice(service.prefix.length) || '/').replace(/^\?/, '/?') : fleet ? raw.slice('/fleet'.length) : raw };
58
+ };
59
+ const prepare = (req, upgrade = false) => {
60
+ const route = select(req);
61
+ // The device credential of an App WebSocket: accepted only on an upgrade, from the exact
62
+ // account origin, on a path the service declares, and never beside another credential.
63
+ const socketCredential = upgrade && auth ? deviceSocketCredential(req) : undefined;
64
+ if (socketCredential !== undefined) {
65
+ const path = route.service ? (req.url ?? '/').split('?')[0].slice(route.service.prefix.length) : undefined;
66
+ if (!route.service?.deviceSocketPaths?.includes(path ?? '') || req.headers.authorization !== undefined || req.headers.cookie !== undefined
67
+ || !req.headers.origin || auth.accountOrigin(req) !== req.headers.origin)
68
+ throw new Error('workspace socket credential is not accepted here');
69
+ }
70
+ // Authentication sees the credential as the bearer it is; the request's own headers are untouched.
71
+ const authHeaders = socketCredential !== undefined ? { ...req.headers, authorization: `Bearer ${socketCredential}` } : req.headers;
72
+ const request = { headers: authHeaders, method: req.method, url: route.path };
73
+ if (!auth && (req.headers.origin || req.headers['sec-fetch-site'] || !route.service))
74
+ throw new Error('machine-client listener rejects browser requests');
75
+ auth?.validateBoundary(request, upgrade);
76
+ const session = route.service && auth ? auth.authenticate(request, !upgrade && !['GET', 'HEAD', 'OPTIONS'].includes(req.method ?? 'GET')) : undefined;
77
+ const headers = headersFor(req);
78
+ // Forward only the explicit browser context, never an attacker-selected forwarding chain.
79
+ for (const key of Object.keys(headers))
80
+ if (key.startsWith('x-forwarded-') || key === 'forwarded')
81
+ delete headers[key];
82
+ delete headers['x-ours-workspace-auth'];
83
+ delete headers['x-ours-request-id'];
84
+ if (route.service) {
85
+ if (auth)
86
+ for (const name of ['cookie', 'authorization', 'x-csrf-token', 'x-ours-api-token'])
87
+ delete headers[name];
88
+ // The server gateway lets this header choose a producer credential; only machine producers send it directly there.
89
+ delete headers['x-ours-notifications-producer'];
90
+ if (route.service.stripBrowserContext) {
91
+ for (const name of ['origin', 'sec-fetch-site', 'sec-fetch-mode', 'sec-fetch-dest', 'sec-fetch-user'])
92
+ delete headers[name];
93
+ }
94
+ Object.assign(headers, route.service.headers);
95
+ headers.host = new URL(route.origin).host;
96
+ }
97
+ if (upgrade) {
98
+ headers.connection = 'Upgrade';
99
+ headers.upgrade = 'websocket';
100
+ }
101
+ // The credential subprotocol ends here: the service is not offered it, on any request.
102
+ if (socketCredential !== undefined || String(headers['sec-websocket-protocol'] ?? '').includes(DEVICE_SOCKET_BEARER))
103
+ delete headers['sec-websocket-protocol'];
104
+ return { ...route, headers, session, authHeaders, deviceSocket: socketCredential !== undefined };
105
+ };
106
+ const track = (authHeaders, session, close, neutral = false) => {
107
+ if (!session)
108
+ return () => { };
109
+ // The session authenticated for this request, with its own deadline, not whatever is current later.
110
+ const unbind = auth.bindTransport(session, close);
111
+ const timer = setInterval(() => {
112
+ if (neutral) {
113
+ if (!auth.sessionValid(session))
114
+ close();
115
+ }
116
+ else
117
+ try {
118
+ auth.authenticate({ headers: authHeaders });
119
+ }
120
+ catch {
121
+ close();
122
+ }
123
+ }, neutral ? 1000 : 30_000);
124
+ timer.unref();
125
+ return () => { clearInterval(timer); unbind(); };
126
+ };
127
+ // The account App calls workspace services directly from its own origin with a device
128
+ // credential. Only that exact configured origin is answered; the reply grants nothing by
129
+ // itself, every request is still authenticated below.
130
+ const accountCors = (req) => {
131
+ const origin = auth?.accountOrigin(req);
132
+ return origin ? { 'access-control-allow-origin': origin, vary: 'Origin',
133
+ 'access-control-expose-headers': 'Accept-Ranges, Content-Disposition, Content-Length, Content-Range, Content-Type, ETag, X-Ours-Workspace-Auth, X-Ours-Request-Id, X-Resource-Producer, X-Resource-Revision, X-Resource-Source-Generation, X-Resource-Source-Position, X-Resource-Connection, X-Resource-View, X-Resource-Epoch, X-Resource-Baseline' } : {};
134
+ };
135
+ const server = createServer((req, res) => {
136
+ const cors = accountCors(req);
137
+ const requestId = randomUUID();
138
+ let route;
139
+ try {
140
+ if (req.method === 'OPTIONS' && cors.vary && req.headers['access-control-request-method'] && select(req).service) {
141
+ auth.validateBoundary({ headers: req.headers, method: req.method, url: req.url }, false);
142
+ res.writeHead(204, { ...cors, 'cache-control': 'no-store', 'access-control-max-age': '600',
143
+ 'access-control-allow-methods': 'GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS',
144
+ 'access-control-allow-headers': 'Authorization, Content-Type, Idempotency-Key, If-None-Match, Range, X-CSRF-Token, X-Ours-Messenger-CSRF, X-Voice-Duration, X-Resource-Connection, X-Resource-View, X-Resource-Epoch, X-Resource-Baseline' });
145
+ res.end();
146
+ return;
147
+ }
148
+ route = prepare(req);
149
+ }
150
+ catch (error) {
151
+ const code = error.code;
152
+ const deviceRejected = error instanceof WorkspaceDeviceAuthError;
153
+ if (code === 'unauthorized')
154
+ rejected(requestId, '/auth', deviceRejected ? 'device_rejected' : 'gateway_rejected');
155
+ res.writeHead(code === 'unauthorized' ? 401 : 403, { ...cors, 'content-type': 'application/json', 'cache-control': 'no-store',
156
+ ...(code === 'unauthorized' ? { 'x-ours-request-id': requestId } : {}),
157
+ ...(deviceRejected ? { 'x-ours-workspace-auth': 'rejected' } : {}) });
158
+ res.end(JSON.stringify({ error: { code: code ?? 'forbidden', message: error.message } }));
159
+ return;
160
+ }
161
+ const upstream = (route.origin.startsWith('https:') ? httpsRequest : httpRequest)(route.origin, {
162
+ method: req.method, path: route.path, headers: route.headers,
163
+ }, response => {
164
+ const headers = headersFor(response);
165
+ if (route.service) {
166
+ delete headers['set-cookie'];
167
+ // Only Fleet may assert device rejection; never trust a service's marker or correlation id.
168
+ delete headers['x-ours-workspace-auth'];
169
+ delete headers['x-ours-request-id'];
170
+ if (response.statusCode === 401) {
171
+ headers['x-ours-request-id'] = requestId;
172
+ headers['x-ours-workspace-auth'] = 'service';
173
+ rejected(requestId, route.service.prefix, 'upstream_401');
174
+ }
175
+ for (const key of Object.keys(headers))
176
+ if (key.startsWith('access-control-'))
177
+ delete headers[key];
178
+ Object.assign(headers, cors);
179
+ }
180
+ res.writeHead(response.statusCode ?? 502, headers);
181
+ // Streaming headers, including exact-origin CORS and upstream errors,
182
+ // must reach the client before the first event or body byte arrives.
183
+ res.flushHeaders();
184
+ response.on('error', () => res.destroy());
185
+ response.on('aborted', () => res.destroy());
186
+ response.on('close', () => { if (!response.complete)
187
+ res.destroy(); });
188
+ response.pipe(res);
189
+ res.on('close', () => response.destroy());
190
+ });
191
+ upstream.on('error', () => {
192
+ if (!res.headersSent)
193
+ res.writeHead(502, { ...(route.service ? cors : {}), 'content-type': 'application/json' });
194
+ res.end(JSON.stringify({ error: { code: 'upstream_unavailable', message: 'Service is unavailable' } }));
195
+ });
196
+ const neutral = req.method === 'GET' && route.service?.prefix === '/notifications'
197
+ && new URL(req.url ?? '/', 'http://gateway.invalid').searchParams.get('resources') === '1'
198
+ && new URL(req.url ?? '/', 'http://gateway.invalid').pathname === '/notifications/api/v1/events';
199
+ const untrack = track(route.authHeaders, route.session, () => { upstream.destroy(); res.destroy(); }, neutral);
200
+ res.on('close', untrack);
201
+ req.on('aborted', () => upstream.destroy());
202
+ res.on('close', () => upstream.destroy());
203
+ req.pipe(upstream);
204
+ });
205
+ const sockets = new Set();
206
+ server.on('connection', socket => { sockets.add(socket); socket.on('close', () => sockets.delete(socket)); });
207
+ server.on('upgrade', (req, socket, head) => {
208
+ let route;
209
+ try {
210
+ route = prepare(req, true);
211
+ }
212
+ catch {
213
+ socket.end('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
214
+ return;
215
+ }
216
+ const upstream = (route.origin.startsWith('https:') ? httpsRequest : httpRequest)(route.origin, {
217
+ method: req.method, path: route.path, headers: route.headers,
218
+ });
219
+ upstream.on('upgrade', (response, peer, upstreamHead) => {
220
+ const headers = headersFor(response);
221
+ headers.connection = 'Upgrade';
222
+ headers.upgrade = 'websocket';
223
+ delete headers['set-cookie'];
224
+ // A browser that offered subprotocols needs one answered; only the marker, never the credential.
225
+ if (route.deviceSocket)
226
+ headers['sec-websocket-protocol'] = DEVICE_SOCKET_PROTOCOL;
227
+ socket.write(`HTTP/1.1 101 Switching Protocols\r\n${Object.entries(headers).flatMap(([k, v]) => (Array.isArray(v) ? v : [v]).map(value => `${k}: ${value}\r\n`)).join('')}\r\n`);
228
+ if (head.length)
229
+ peer.write(head);
230
+ if (upstreamHead.length)
231
+ socket.write(upstreamHead);
232
+ const untrack = track(route.authHeaders, route.session, () => { peer.destroy(); socket.destroy(); });
233
+ socket.on('close', untrack);
234
+ peer.pipe(socket);
235
+ socket.pipe(peer);
236
+ peer.on('error', () => socket.destroy());
237
+ socket.on('error', () => peer.destroy());
238
+ socket.on('close', () => peer.destroy());
239
+ peer.on('close', () => socket.destroy());
240
+ });
241
+ upstream.on('response', response => { socket.end(`HTTP/1.1 ${response.statusCode ?? 502} Rejected\r\nConnection: close\r\n\r\n`); response.destroy(); });
242
+ upstream.on('error', () => socket.destroy());
243
+ socket.on('close', () => upstream.destroy());
244
+ upstream.end();
245
+ });
246
+ return { server, async close() { for (const socket of sockets)
247
+ socket.destroy(); await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); } };
248
+ }
@@ -0,0 +1,135 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ import { type FleetResourceScope, type FleetResourceState } from '../application/resource-observer.js';
3
+ export declare function abortableProjection<T>(read: Promise<T>, signal: AbortSignal): Promise<T>;
4
+ export interface ResourceFrameBinding {
5
+ readonly protocolVersion: 1;
6
+ readonly producerGeneration: string;
7
+ readonly subscriptionEpoch: number;
8
+ readonly viewId: string;
9
+ readonly sequence: number;
10
+ readonly revision: number;
11
+ readonly baselineId?: string;
12
+ readonly source?: FleetResourceState;
13
+ }
14
+ export type ResourceHealthReason = 'auth_required' | 'unsupported' | 'upstream_unavailable';
15
+ export type ResourceEnvelope = ResourceFrameBinding & ({
16
+ readonly kind: 'snapshot';
17
+ readonly payload: unknown;
18
+ } | {
19
+ readonly kind: 'upsert';
20
+ readonly payload: unknown;
21
+ } | {
22
+ readonly kind: 'delete';
23
+ readonly payload: {
24
+ readonly id: string;
25
+ readonly revision: number;
26
+ };
27
+ } | {
28
+ readonly kind: 'ready';
29
+ readonly payload: {
30
+ readonly baselineId: string;
31
+ readonly source: FleetResourceState;
32
+ };
33
+ } | {
34
+ readonly kind: 'coverage';
35
+ readonly payload: {
36
+ readonly source: FleetResourceState;
37
+ };
38
+ } | {
39
+ readonly kind: 'resync';
40
+ readonly payload: {
41
+ readonly reason: 'capacity' | 'projection_unavailable';
42
+ };
43
+ } | {
44
+ readonly kind: 'health';
45
+ readonly payload: {
46
+ readonly healthy: false;
47
+ readonly reason: ResourceHealthReason;
48
+ };
49
+ });
50
+ interface ResourceCoverage {
51
+ protocolVersion: 1;
52
+ producerGeneration: string;
53
+ views: Array<ResourceFrameBinding & {
54
+ source: FleetResourceState;
55
+ }>;
56
+ }
57
+ type ResourceOutputRecord = ResourceEnvelope | ResourceCoverage;
58
+ export interface ResourceWireFrame {
59
+ event: 'resource' | 'resource-chunk' | 'resource-coverage';
60
+ data: Record<string, unknown>;
61
+ }
62
+ export declare const RESOURCE_FRAME_BYTES: number;
63
+ export declare const RESOURCE_PENDING_BYTES: number;
64
+ export declare const RESOURCE_ASSEMBLY_BYTES: number;
65
+ /** Lazily yield byte chunks so backpressure never queues an entire large record.
66
+ * Base64 prevents splitting a Unicode scalar from corrupting JSON/SSE parsing. */
67
+ export declare function resourceFrames(envelope: ResourceEnvelope): Generator<ResourceWireFrame>;
68
+ /** One Fleet HTTP writer. The active record owns the assembly budget;
69
+ * waiting live records share the smaller pending-output budget. */
70
+ export declare class ResourceOutput {
71
+ #private;
72
+ readonly response: ServerResponse;
73
+ readonly signal: AbortSignal;
74
+ readonly failed: (error: Error) => void;
75
+ constructor(response: ServerResponse, signal: AbortSignal, failed: (error: Error) => void);
76
+ send(envelope: ResourceOutputRecord, admitted?: () => void): Promise<void>;
77
+ close(error?: Error): void;
78
+ }
79
+ export interface ResourceDescriptor {
80
+ viewId: string;
81
+ resource: 'directory' | 'roles' | 'tasks' | 'task-lists' | 'configuration' | 'previews' | 'task-layout' | 'creation-action' | 'action' | 'member-addition' | 'login' | 'onboarding-health' | 'subscriptions';
82
+ entityId?: string;
83
+ receiptId?: string;
84
+ provider?: 'claude' | 'codex';
85
+ includeTemporary?: boolean;
86
+ includeDefinitions?: boolean;
87
+ ids?: string[];
88
+ state?: string;
89
+ list?: string;
90
+ groupByList?: boolean;
91
+ includeDeleting?: boolean;
92
+ }
93
+ export declare function canonicalResourceDescriptor(input: unknown): ResourceDescriptor;
94
+ interface BoundProjection {
95
+ payload: unknown;
96
+ source: FleetResourceState;
97
+ producerGeneration: string;
98
+ revision: number;
99
+ baselineId: string;
100
+ descriptor: ResourceDescriptor;
101
+ }
102
+ export declare class ResourceBindingError extends Error {
103
+ readonly status: number;
104
+ constructor(status: number, message: string);
105
+ }
106
+ /** Same committed snapshot/ready/bound-read contract as Messenger. No controllers,
107
+ * notification reads or presence are acquired by this delivery owner. */
108
+ export declare class FleetResourceViews {
109
+ private scope;
110
+ private project;
111
+ private configurationInputs;
112
+ readonly producerGeneration: `${string}-${string}-${string}-${string}-${string}`;
113
+ private connections;
114
+ constructor(scope: FleetResourceScope, project: (descriptor: ResourceDescriptor, signal: AbortSignal, owner: string) => Promise<unknown>, configurationInputs?: () => readonly string[]);
115
+ open(response: ServerResponse, owner: string, authorized: () => boolean): {
116
+ connectionId: `${string}-${string}-${string}-${string}-${string}`;
117
+ close: () => void;
118
+ };
119
+ register(owner: string, connectionId: string, viewId: string, epoch: number, input: unknown): {
120
+ subscriptionEpoch: number;
121
+ baselineId: string;
122
+ };
123
+ remove(owner: string, connectionId: string, viewId: string, epoch: number): void;
124
+ bound(owner: string, connectionId: string, viewId: string, epoch: number, baselineId: string): Promise<BoundProjection>;
125
+ close(): void;
126
+ private get;
127
+ private current;
128
+ private retire;
129
+ private emit;
130
+ private coverage;
131
+ private affects;
132
+ private schedule;
133
+ private refresh;
134
+ }
135
+ export {};