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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/README.md +279 -68
  2. package/dist/account-origin.d.ts +2 -0
  3. package/dist/account-origin.js +6 -0
  4. package/dist/agent-ours/bridge.js +26 -9
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/file-rpc.d.ts +12 -0
  8. package/dist/agent-ours/file-rpc.js +18 -4
  9. package/dist/agent-ours/harness.js +9 -1
  10. package/dist/agent-ours/mcp-endpoint.d.ts +19 -0
  11. package/dist/agent-ours/mcp-endpoint.js +98 -25
  12. package/dist/agent-ours/runtime.d.ts +9 -1
  13. package/dist/agent-ours/runtime.js +50 -2
  14. package/dist/agent-ours/service.d.ts +24 -1
  15. package/dist/agent-ours/service.js +97 -13
  16. package/dist/application/codex-models.d.ts +15 -0
  17. package/dist/application/codex-models.js +56 -0
  18. package/dist/application/conversation-history.d.ts +34 -0
  19. package/dist/application/conversation-history.js +280 -0
  20. package/dist/application/conversation-role-observer.d.ts +62 -0
  21. package/dist/application/conversation-role-observer.js +206 -0
  22. package/dist/application/fleet-query-service.d.ts +1 -1
  23. package/dist/application/fleet-query-service.js +3 -3
  24. package/dist/application/log-service.js +8 -8
  25. package/dist/application/model-catalog.d.ts +1 -1
  26. package/dist/application/model-catalog.js +5 -2
  27. package/dist/application/preset-provenance.d.ts +19 -0
  28. package/dist/application/preset-provenance.js +59 -0
  29. package/dist/application/resource-observer.d.ts +40 -0
  30. package/dist/application/resource-observer.js +278 -0
  31. package/dist/application/role-creation-service.js +3 -2
  32. package/dist/application/role-removal-service.js +17 -3
  33. package/dist/application/role-repository.d.ts +1 -1
  34. package/dist/application/role-repository.js +30 -7
  35. package/dist/application/room-layout-definitions.d.ts +45 -0
  36. package/dist/application/room-layout-definitions.js +130 -0
  37. package/dist/application/session-control.d.ts +61 -17
  38. package/dist/application/session-control.js +207 -23
  39. package/dist/application/supervisor-ours-tools.js +5 -11
  40. package/dist/application/task-layouts.d.ts +91 -0
  41. package/dist/application/task-layouts.js +261 -0
  42. package/dist/application/task-room-service.d.ts +101 -3
  43. package/dist/application/task-room-service.js +286 -33
  44. package/dist/application/types.d.ts +2 -0
  45. package/dist/atomic-file.d.ts +34 -1
  46. package/dist/atomic-file.js +161 -18
  47. package/dist/briefing.d.ts +3 -1
  48. package/dist/briefing.js +97 -31
  49. package/dist/build-info.json +9 -4
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +400 -37
  53. package/dist/client-profile.d.ts +5 -17
  54. package/dist/client-profile.js +4 -127
  55. package/dist/config.d.ts +45 -0
  56. package/dist/config.js +57 -6
  57. package/dist/creation.d.ts +1 -1
  58. package/dist/creation.js +4 -4
  59. package/dist/daemon-recovery.d.ts +14 -9
  60. package/dist/daemon-recovery.js +124 -115
  61. package/dist/docs.d.ts +1 -1
  62. package/dist/docs.js +207 -19
  63. package/dist/doctor.d.ts +4 -1
  64. package/dist/doctor.js +75 -60
  65. package/dist/erased-resources.d.ts +9 -0
  66. package/dist/erased-resources.js +87 -0
  67. package/dist/file-delivery/reader.d.ts +15 -0
  68. package/dist/file-delivery/reader.js +63 -0
  69. package/dist/file-delivery/store.d.ts +15 -0
  70. package/dist/file-delivery/store.js +124 -0
  71. package/dist/file-delivery/types.d.ts +19 -0
  72. package/dist/file-delivery/types.js +1 -0
  73. package/dist/fleet-command-audit.d.ts +7 -0
  74. package/dist/fleet-command-audit.js +24 -8
  75. package/dist/harness/agent-session.d.ts +4 -0
  76. package/dist/harness/claude-code-session.js +1 -1
  77. package/dist/harness/claude-code.js +21 -11
  78. package/dist/harness/claude-runtime.d.ts +12 -0
  79. package/dist/harness/claude-runtime.js +43 -0
  80. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  81. package/dist/harness/codex-app-server-proxy.js +41 -5
  82. package/dist/harness/codex-reasoning.d.ts +5 -0
  83. package/dist/harness/codex-reasoning.js +12 -0
  84. package/dist/harness/codex-runtime.d.ts +11 -1
  85. package/dist/harness/codex-runtime.js +31 -4
  86. package/dist/harness/codex-session.js +2 -2
  87. package/dist/harness/codex.js +24 -12
  88. package/dist/init-wizard.d.ts +2 -0
  89. package/dist/init-wizard.js +38 -19
  90. package/dist/isolation/policy.js +6 -1
  91. package/dist/isolation/types.d.ts +5 -0
  92. package/dist/managed-cli.d.ts +253 -0
  93. package/dist/managed-cli.js +923 -0
  94. package/dist/managed-recovery.d.ts +13 -0
  95. package/dist/managed-recovery.js +31 -0
  96. package/dist/minimal-setup.d.ts +2 -0
  97. package/dist/minimal-setup.js +44 -0
  98. package/dist/monitor.d.ts +9 -34
  99. package/dist/monitor.js +90 -153
  100. package/dist/notifications/fleet-producer.d.ts +22 -0
  101. package/dist/notifications/fleet-producer.js +237 -0
  102. package/dist/notifications/outbox.d.ts +46 -0
  103. package/dist/notifications/outbox.js +158 -0
  104. package/dist/notifications/target-cleanup.d.ts +11 -0
  105. package/dist/notifications/target-cleanup.js +63 -0
  106. package/dist/notifications/task-producer.d.ts +20 -0
  107. package/dist/notifications/task-producer.js +100 -0
  108. package/dist/ops.d.ts +2 -0
  109. package/dist/ops.js +20 -3
  110. package/dist/owner-channel/channel.js +17 -6
  111. package/dist/owner-channel/commands.d.ts +4 -0
  112. package/dist/owner-channel/commands.js +8 -0
  113. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  114. package/dist/owner-channel/ours-client.js +2 -17
  115. package/dist/paths.d.ts +2 -0
  116. package/dist/paths.js +2 -0
  117. package/dist/preset-migration.js +43 -0
  118. package/dist/process-incarnation.d.ts +23 -0
  119. package/dist/process-incarnation.js +42 -0
  120. package/dist/reminders/manager.d.ts +75 -0
  121. package/dist/reminders/manager.js +278 -0
  122. package/dist/reminders/runtime.d.ts +27 -0
  123. package/dist/reminders/runtime.js +32 -0
  124. package/dist/reminders/schedule.d.ts +37 -0
  125. package/dist/reminders/schedule.js +104 -0
  126. package/dist/reminders/store.d.ts +341 -0
  127. package/dist/reminders/store.js +762 -0
  128. package/dist/reminders/tools.d.ts +20 -0
  129. package/dist/reminders/tools.js +114 -0
  130. package/dist/resolved-plan.js +1 -0
  131. package/dist/rooms-tasks/add-member.d.ts +48 -0
  132. package/dist/rooms-tasks/add-member.js +84 -0
  133. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  134. package/dist/rooms-tasks/archived-absence.js +48 -0
  135. package/dist/rooms-tasks/cli.js +65 -5
  136. package/dist/rooms-tasks/close.d.ts +6 -2
  137. package/dist/rooms-tasks/close.js +246 -37
  138. package/dist/rooms-tasks/config.d.ts +1 -1
  139. package/dist/rooms-tasks/config.js +11 -4
  140. package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
  141. package/dist/rooms-tasks/cowork-adapter.js +15 -121
  142. package/dist/rooms-tasks/cowork-http.js +56 -6
  143. package/dist/rooms-tasks/deletion.d.ts +1 -1
  144. package/dist/rooms-tasks/deletion.js +84 -5
  145. package/dist/rooms-tasks/erasure.d.ts +6 -0
  146. package/dist/rooms-tasks/erasure.js +173 -0
  147. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  148. package/dist/rooms-tasks/external-worker.js +3 -4
  149. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  150. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  151. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  152. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  153. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  154. package/dist/rooms-tasks/layout-cli.js +107 -0
  155. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  156. package/dist/rooms-tasks/layout-config.js +89 -0
  157. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  158. package/dist/rooms-tasks/layout-control.js +52 -0
  159. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  160. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  161. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  162. package/dist/rooms-tasks/layout-service.js +203 -0
  163. package/dist/rooms-tasks/layout.d.ts +121 -0
  164. package/dist/rooms-tasks/layout.js +286 -0
  165. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  166. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  167. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  168. package/dist/rooms-tasks/live-readiness.js +85 -0
  169. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  170. package/dist/rooms-tasks/member-ownership.js +40 -0
  171. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  172. package/dist/rooms-tasks/member-startup.js +2 -0
  173. package/dist/rooms-tasks/provision.d.ts +3 -0
  174. package/dist/rooms-tasks/provision.js +101 -49
  175. package/dist/rooms-tasks/recovery.d.ts +19 -0
  176. package/dist/rooms-tasks/recovery.js +110 -0
  177. package/dist/rooms-tasks/room-state.d.ts +4 -2
  178. package/dist/rooms-tasks/room-state.js +47 -8
  179. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  180. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  181. package/dist/rooms-tasks/task-state.d.ts +52 -8
  182. package/dist/rooms-tasks/task-state.js +251 -26
  183. package/dist/rooms-tasks/types.d.ts +40 -0
  184. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  185. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  186. package/dist/rooms-tasks/workspace.d.ts +35 -0
  187. package/dist/rooms-tasks/workspace.js +325 -0
  188. package/dist/runner.d.ts +33 -4
  189. package/dist/runner.js +410 -62
  190. package/dist/session/acp.d.ts +75 -2
  191. package/dist/session/acp.js +345 -82
  192. package/dist/session/arbiter.d.ts +98 -0
  193. package/dist/session/arbiter.js +272 -3
  194. package/dist/session/codex-app-server.d.ts +45 -1
  195. package/dist/session/codex-app-server.js +225 -32
  196. package/dist/session/control.d.ts +17 -3
  197. package/dist/session/control.js +213 -23
  198. package/dist/session/conversation-normalizer.d.ts +2 -1
  199. package/dist/session/conversation-normalizer.js +1 -1
  200. package/dist/session/conversation-observation.d.ts +25 -0
  201. package/dist/session/conversation-observation.js +100 -0
  202. package/dist/session/conversation-replay.d.ts +50 -0
  203. package/dist/session/conversation-replay.js +203 -0
  204. package/dist/session/conversation-store.d.ts +15 -1
  205. package/dist/session/conversation-store.js +206 -26
  206. package/dist/session/conversation-types.d.ts +57 -4
  207. package/dist/session/prompt-batch.d.ts +79 -0
  208. package/dist/session/prompt-batch.js +120 -0
  209. package/dist/session/task-notice.d.ts +14 -0
  210. package/dist/session/task-notice.js +13 -0
  211. package/dist/session/types.d.ts +42 -1
  212. package/dist/setup-tunnel-preflight.d.ts +34 -0
  213. package/dist/setup-tunnel-preflight.js +62 -0
  214. package/dist/socket-path.d.ts +2 -0
  215. package/dist/socket-path.js +22 -0
  216. package/dist/spawn.d.ts +5 -1
  217. package/dist/spawn.js +40 -4
  218. package/dist/startup-prompt.d.ts +15 -0
  219. package/dist/startup-prompt.js +71 -0
  220. package/dist/startup-readiness.d.ts +32 -0
  221. package/dist/startup-readiness.js +101 -0
  222. package/dist/startup-telemetry.d.ts +16 -0
  223. package/dist/startup-telemetry.js +46 -0
  224. package/dist/subscriptions/cli.d.ts +91 -0
  225. package/dist/subscriptions/cli.js +223 -0
  226. package/dist/subscriptions/launch.d.ts +37 -0
  227. package/dist/subscriptions/launch.js +93 -0
  228. package/dist/subscriptions/login.d.ts +77 -0
  229. package/dist/subscriptions/login.js +215 -0
  230. package/dist/subscriptions/observed.d.ts +7 -0
  231. package/dist/subscriptions/observed.js +44 -0
  232. package/dist/subscriptions/probe.d.ts +10 -0
  233. package/dist/subscriptions/probe.js +73 -0
  234. package/dist/subscriptions/service.d.ts +102 -0
  235. package/dist/subscriptions/service.js +364 -0
  236. package/dist/subscriptions/store.d.ts +71 -0
  237. package/dist/subscriptions/store.js +143 -0
  238. package/dist/supervisor/adoption.d.ts +8 -0
  239. package/dist/supervisor/adoption.js +78 -0
  240. package/dist/supervisor/catalog.d.ts +62 -0
  241. package/dist/supervisor/catalog.js +254 -0
  242. package/dist/supervisor/fleet.d.ts +8 -0
  243. package/dist/supervisor/fleet.js +89 -0
  244. package/dist/supervisor/index.js +3 -6
  245. package/dist/supervisor/launchd.d.ts +1 -1
  246. package/dist/supervisor/launchd.js +26 -13
  247. package/dist/supervisor/legacy.d.ts +35 -0
  248. package/dist/supervisor/legacy.js +524 -0
  249. package/dist/supervisor/manager.d.ts +15 -0
  250. package/dist/supervisor/manager.js +270 -0
  251. package/dist/supervisor/registration-refresh.d.ts +45 -0
  252. package/dist/supervisor/registration-refresh.js +266 -0
  253. package/dist/supervisor/scope.d.ts +4 -0
  254. package/dist/supervisor/scope.js +10 -0
  255. package/dist/supervisor/systemd.d.ts +2 -2
  256. package/dist/supervisor/systemd.js +19 -33
  257. package/dist/task-supervision.d.ts +9 -0
  258. package/dist/task-supervision.js +145 -0
  259. package/dist/temp-idle.d.ts +17 -0
  260. package/dist/temp-idle.js +42 -0
  261. package/dist/temp-lifecycle.d.ts +23 -8
  262. package/dist/temp-lifecycle.js +123 -66
  263. package/dist/temp-supervisor-recovery.d.ts +1 -1
  264. package/dist/temp-supervisor-recovery.js +2 -2
  265. package/dist/tunnel-setup-completion.d.ts +7 -0
  266. package/dist/tunnel-setup-completion.js +13 -0
  267. package/dist/tunnel-setup-input.d.ts +19 -0
  268. package/dist/tunnel-setup-input.js +124 -0
  269. package/dist/watchdog/query.d.ts +3 -1
  270. package/dist/watchdog/query.js +2 -1
  271. package/dist/web/agent-attachments.d.ts +29 -0
  272. package/dist/web/agent-attachments.js +162 -0
  273. package/dist/web/auth.d.ts +65 -2
  274. package/dist/web/auth.js +201 -11
  275. package/dist/web/available-port.d.ts +2 -0
  276. package/dist/web/available-port.js +23 -0
  277. package/dist/web/control.d.ts +3 -3
  278. package/dist/web/control.js +10 -8
  279. package/dist/web/conversation-stream-codec.d.ts +23 -0
  280. package/dist/web/conversation-stream-codec.js +110 -0
  281. package/dist/web/conversation-stream.d.ts +25 -0
  282. package/dist/web/conversation-stream.js +231 -0
  283. package/dist/web/events.d.ts +2 -0
  284. package/dist/web/events.js +9 -0
  285. package/dist/web/fleet-config-service.d.ts +4 -1
  286. package/dist/web/fleet-config-service.js +46 -15
  287. package/dist/web/prefix-gateway.d.ts +32 -0
  288. package/dist/web/prefix-gateway.js +248 -0
  289. package/dist/web/resource-views.d.ts +124 -0
  290. package/dist/web/resource-views.js +424 -0
  291. package/dist/web/runtime.d.ts +3 -1
  292. package/dist/web/runtime.js +132 -34
  293. package/dist/web/server.d.ts +19 -0
  294. package/dist/web/server.js +802 -67
  295. package/dist/web/service.d.ts +11 -2
  296. package/dist/web/service.js +30 -4
  297. package/dist/web/workspace-devices.d.ts +107 -0
  298. package/dist/web/workspace-devices.js +277 -0
  299. package/dist/web/workspace-tunnel.d.ts +1 -0
  300. package/dist/web/workspace-tunnel.js +34 -0
  301. package/dist/workspace-enrollment.d.ts +83 -0
  302. package/dist/workspace-enrollment.js +580 -0
  303. package/dist/workspace-replacement.d.ts +66 -0
  304. package/dist/workspace-replacement.js +220 -0
  305. package/docs/acp-files.md +146 -0
  306. package/docs/conversation-stream.md +196 -0
  307. package/docs/fleet-migration.md +54 -0
  308. package/docs/live-voice.md +13 -0
  309. package/docs/room-layouts.md +227 -0
  310. package/docs/self-reminders.md +374 -0
  311. package/docs/task-workspaces.md +225 -0
  312. package/docs/validation/managed-cli-permissions.md +421 -0
  313. package/examples/room-layouts/fresh.yaml +27 -0
  314. package/examples/room-layouts/scoped.yaml +21 -0
  315. package/examples/room-layouts/shared.yaml +19 -0
  316. package/package.json +24 -20
  317. package/presets/README.md +78 -0
  318. package/presets/brain-catalog.json +8 -2
  319. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  320. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  321. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  322. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -2
  323. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  324. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  325. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  326. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  327. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  328. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  329. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  330. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  331. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  332. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  333. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  334. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  335. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  336. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  337. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  338. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  339. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  340. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  341. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  342. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  343. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  344. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  345. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  351. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  352. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  353. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  354. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  384. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  385. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  386. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  387. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  390. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  391. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  392. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  393. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  394. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  396. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  397. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  398. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  399. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  400. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  406. package/presets/fleet/roles/Coordinator.yaml +12 -11
  407. package/presets/fleet/roles/Critic.yaml +13 -0
  408. package/presets/fleet/roles/Developer.yaml +13 -0
  409. package/presets/fleet/roles/Engineer.yaml +53 -0
  410. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  411. package/presets/fleet/room_layouts/pair.yaml +24 -0
  412. package/presets/fleet/room_layouts/single.yaml +17 -0
  413. package/presets/fleet/room_layouts/team.yaml +30 -0
  414. package/presets/fleet/room_templates/engineering.yaml +19 -0
  415. package/presets/manifest.json +2 -2
  416. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  417. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  418. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  419. package/dist/web-app/icons/ours-fleet.svg +0 -4
  420. package/dist/web-app/index.html +0 -17
  421. package/dist/web-app/manifest.webmanifest +0 -15
  422. package/dist/web-app/offline.html +0 -18
  423. package/dist/web-app/sw.js +0 -51
@@ -0,0 +1,237 @@
1
+ import { observeFleetResources } from '../application/resource-observer.js';
2
+ import { roomsDir } from '../rooms-tasks/room-state.js';
3
+ import { cleanupDir, drainNotificationTargetCleanup, notificationAgentId } from './target-cleanup.js';
4
+ import { closeSync, existsSync, lstatSync, openSync, readSync, readFileSync, readdirSync, statSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { createHash } from 'node:crypto';
7
+ import { NotificationOutbox } from './outbox.js';
8
+ /** Unlike a UI inventory, unread classification must not treat unreadable records as absence. */
9
+ function taskRoleAssociations() {
10
+ const dir = roomsDir(), result = new Map();
11
+ let files;
12
+ try {
13
+ files = readdirSync(dir);
14
+ }
15
+ catch (error) {
16
+ if (error.code === 'ENOENT')
17
+ return result;
18
+ throw error;
19
+ }
20
+ for (const name of files.filter(name => name.endsWith('.json'))) {
21
+ const file = join(dir, name), stat = lstatSync(file);
22
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024 * 1024)
23
+ throw Error('Unreadable room association');
24
+ const room = JSON.parse(readFileSync(file, 'utf8'));
25
+ if (!room || typeof room.room_id !== 'string' || !Array.isArray(room.member_seats))
26
+ throw Error('Invalid room association');
27
+ if (room.task_id === undefined)
28
+ continue;
29
+ if (typeof room.task_id !== 'string' || !/^[A-Za-z0-9_-]{1,120}$/.test(room.task_id))
30
+ throw Error('Invalid task association');
31
+ for (const seat of room.member_seats) {
32
+ if (typeof seat.role_name !== 'string')
33
+ throw Error('Invalid member association');
34
+ if (result.has(seat.role_name) && result.get(seat.role_name) !== room.task_id)
35
+ throw Error('Ambiguous task association');
36
+ result.set(seat.role_name, room.task_id);
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ /** Read durable supervisor ledgers without restarting agents or attaching a
42
+ * conversation controller. Each tick reads at most 256 KiB per role, from its
43
+ * checkpoint; it never loads a complete transcript or requests model history. */
44
+ export class FleetNotificationProducer {
45
+ roots;
46
+ stateDir;
47
+ config;
48
+ warn;
49
+ queues = new Map();
50
+ observation = new AbortController();
51
+ continuation;
52
+ stopped = false;
53
+ existing = new Set();
54
+ cleanup;
55
+ constructor(roots, stateDir, config, warn = () => { }) {
56
+ this.roots = roots;
57
+ this.stateDir = stateDir;
58
+ this.config = config;
59
+ this.warn = warn;
60
+ observeFleetResources({ retryWatchFailures: true, roots, paths: [...roots, roomsDir(), cleanupDir()] }, state => {
61
+ if (state.health === 'healthy')
62
+ this.poll();
63
+ else
64
+ this.warn('Notification discovery observation unavailable; checkpoint retained');
65
+ }, this.observation.signal);
66
+ for (const root of roots)
67
+ for (const name of this.names(root))
68
+ this.existing.add(join(root, name));
69
+ this.poll();
70
+ }
71
+ names(root) {
72
+ try {
73
+ return readdirSync(root, { withFileTypes: true }).filter(d => d.isDirectory() && /^[a-zA-Z0-9_-]+$/.test(d.name)).map(d => d.name);
74
+ }
75
+ catch {
76
+ return [];
77
+ }
78
+ }
79
+ poll() {
80
+ if (this.stopped)
81
+ return;
82
+ this.cleanup ??= drainNotificationTargetCleanup(this.config, undefined, undefined, { beforeDelete: async (url) => {
83
+ const name = notificationAgentId(url);
84
+ if (!name || !/^[A-Za-z0-9_-]+$/.test(name))
85
+ return false;
86
+ // A recreated or retained role still has a valid target. Filesystem errors cannot prove removal.
87
+ for (const root of this.roots) {
88
+ try {
89
+ lstatSync(join(root, name));
90
+ return false;
91
+ }
92
+ catch (error) {
93
+ if (error.code !== 'ENOENT')
94
+ throw error;
95
+ }
96
+ }
97
+ for (const root of this.roots) {
98
+ const roleDir = join(root, name), queue = this.queues.get(roleDir);
99
+ if (queue) {
100
+ await queue.retire();
101
+ this.queues.delete(roleDir);
102
+ }
103
+ else {
104
+ const file = join(this.stateDir, createHash('sha256').update(roleDir).digest('hex') + '.json');
105
+ if (existsSync(file)) {
106
+ const queue = new NotificationOutbox(file, this.config, async () => { throw Error('retired target'); });
107
+ try {
108
+ await queue.retire();
109
+ }
110
+ finally {
111
+ await queue.close();
112
+ }
113
+ }
114
+ }
115
+ }
116
+ return true;
117
+ } }).catch(() => { }).finally(() => {
118
+ this.cleanup = undefined;
119
+ try {
120
+ if (readdirSync(cleanupDir()).some(name => /^[a-f0-9]{64}\.json$/.test(name)))
121
+ this.resume(3000);
122
+ }
123
+ catch { }
124
+ });
125
+ let taskRoles;
126
+ try {
127
+ taskRoles = taskRoleAssociations();
128
+ }
129
+ catch {
130
+ this.warn('Notification task association unavailable; delivering unclassified events');
131
+ }
132
+ for (const root of this.roots)
133
+ for (const roleId of this.names(root)) {
134
+ const roleDir = join(root, roleId), dir = join(roleDir, '.conversation');
135
+ try {
136
+ if (!existsSync(dir) || lstatSync(dir).isSymbolicLink())
137
+ continue;
138
+ const segments = readdirSync(dir).filter(n => /^events-\d{6}\.jsonl$/.test(n) && !lstatSync(join(dir, n)).isSymbolicLink()).sort();
139
+ if (!segments.length)
140
+ continue;
141
+ let outbox = this.queues.get(roleDir);
142
+ if (!outbox) {
143
+ const key = createHash('sha256').update(roleDir).digest('hex');
144
+ outbox = new NotificationOutbox(join(this.stateDir, key + '.json'), this.config, async (value, eventId) => ({ eventId, title: `${roleId}: agent completed`, body: 'Your agent finished responding. Open the chat to view the result.',
145
+ url: `/fleet/chats/agents/${encodeURIComponent(roleId)}`, ...value }), this.warn);
146
+ if (!outbox.checkpoint) {
147
+ const segment = this.existing.has(roleDir) ? segments.at(-1) : segments[0];
148
+ outbox.saveCheckpoint({ segment, offset: this.existing.has(roleDir) ? statSync(join(dir, segment)).size : 0, prompts: [] });
149
+ }
150
+ this.queues.set(roleDir, outbox);
151
+ }
152
+ const saved = outbox.checkpoint;
153
+ const current = { ...saved, prompts: [...saved.prompts], messages: { ...saved.messages } };
154
+ let index = segments.indexOf(current.segment);
155
+ if (index < 0) {
156
+ this.warn('Notification ledger segment unavailable; checkpoint retained');
157
+ continue;
158
+ }
159
+ const file = join(dir, current.segment), size = statSync(file).size;
160
+ if (size < current.offset) {
161
+ this.warn('Notification ledger truncated; checkpoint retained');
162
+ continue;
163
+ }
164
+ if (size === current.offset && index < segments.length - 1) {
165
+ current.segment = segments[++index];
166
+ current.offset = 0;
167
+ }
168
+ const fd = openSync(join(dir, current.segment), 'r'), buffer = Buffer.alloc(256 * 1024);
169
+ let count;
170
+ try {
171
+ count = readSync(fd, buffer, 0, buffer.length, current.offset);
172
+ }
173
+ finally {
174
+ closeSync(fd);
175
+ }
176
+ if (current.offset + count < statSync(join(dir, current.segment)).size || index < segments.length - 1)
177
+ this.resume();
178
+ let position = 0;
179
+ while (position < count) {
180
+ const end = buffer.indexOf(10, position);
181
+ if (end < 0 || end >= count) {
182
+ // Oversized model/media events are irrelevant to prompt/completion
183
+ // correlation. Skip in bounded chunks; retain partial small lines.
184
+ if (current.discarding || (position === 0 && count === buffer.length)) {
185
+ current.offset += count - position;
186
+ current.discarding = true;
187
+ this.warn('Notification observer skipping oversized ledger record');
188
+ }
189
+ break;
190
+ }
191
+ if (current.discarding) {
192
+ current.offset += end - position + 1;
193
+ position = end + 1;
194
+ current.discarding = false;
195
+ continue;
196
+ }
197
+ const event = JSON.parse(buffer.subarray(position, end).toString('utf8'));
198
+ if (event.schemaVersion !== 1 || event.roleId !== roleId)
199
+ throw new Error('invalid ledger event');
200
+ const prompt = `${event.sessionGeneration}:${event.promptId}`;
201
+ if (event.kind === 'prompt.admitted' && event.promptId && ['browser', 'owner_admin_console', 'owner_channel'].includes(event.source ?? '')) {
202
+ if (!current.prompts.includes(prompt))
203
+ current.prompts.push(prompt);
204
+ }
205
+ if (['message.chunk', 'message.replace'].includes(event.kind) && event.promptId && event.messageId && current.prompts.includes(prompt))
206
+ current.messages[prompt] = event.messageId;
207
+ if (event.kind === 'turn.completed' && event.promptId && current.prompts.includes(prompt)) {
208
+ if (!outbox.enqueue(`${roleId}:${event.sessionGeneration}:${event.seq}`, { ...(taskRoles ? { taskId: taskRoles.get(roleId) ?? null } : {}), url: `/fleet/chats/agents/${encodeURIComponent(roleId)}#fleet-message-${encodeURIComponent(current.messages[prompt] ?? event.promptId)}` })) {
209
+ this.resume(1000);
210
+ break;
211
+ }
212
+ current.prompts = current.prompts.filter(p => p !== prompt);
213
+ delete current.messages[prompt];
214
+ }
215
+ current.offset += end - position + 1;
216
+ position = end + 1;
217
+ }
218
+ if (current.segment !== saved.segment || current.offset !== saved.offset)
219
+ outbox.saveCheckpoint(current);
220
+ }
221
+ catch {
222
+ this.warn('Notification ledger admission pending; checkpoint will replay');
223
+ // Retained, not-yet-admitted ledger work needs its own next-due wake;
224
+ // the outbox sender can retry only events that were already admitted.
225
+ this.resume(1000);
226
+ }
227
+ }
228
+ }
229
+ resume(delay = 0) {
230
+ if (this.continuation || this.stopped)
231
+ return;
232
+ this.continuation = setTimeout(() => { this.continuation = undefined; this.poll(); }, delay);
233
+ this.continuation.unref();
234
+ }
235
+ async drain() { await Promise.all([...this.queues.values()].map(q => q.drain())); }
236
+ async close() { this.stopped = true; this.observation.abort(); clearTimeout(this.continuation); await this.cleanup; await Promise.all([...this.queues.values()].map(q => q.close())); }
237
+ }
@@ -0,0 +1,46 @@
1
+ export interface NotificationInput {
2
+ eventId: string;
3
+ title: string;
4
+ body: string;
5
+ url: string;
6
+ taskId?: string | null;
7
+ }
8
+ export interface ProducerConfig {
9
+ /** Service base URL. With a gateway credential it is the server gateway's notifications route. */
10
+ origin: string;
11
+ token: string;
12
+ /** Daemon API credential that authenticates this client to the server gateway. */
13
+ gatewayCredential?: string;
14
+ }
15
+ type ProfileLocation = {
16
+ serverUrl: string;
17
+ expectedInstanceId: string;
18
+ credentialPath: string;
19
+ };
20
+ /** The installer delivers the producer credential beside the profile credential, bound to that server installation. */
21
+ export declare const PRODUCER_CREDENTIAL_FILE = "notifications-producer.json";
22
+ export declare function producerConfig(env?: NodeJS.ProcessEnv, profile?: () => ProfileLocation | undefined, warn?: (message: string) => void): ProducerConfig | undefined;
23
+ /** Durable producer queue. Service dedupe makes crash-after-acceptance replay safe. */
24
+ export declare class NotificationOutbox {
25
+ private readonly file;
26
+ private readonly config;
27
+ private readonly project;
28
+ private readonly warn;
29
+ private entries;
30
+ cursor?: number;
31
+ checkpoint?: unknown;
32
+ private running?;
33
+ private retired;
34
+ private timer;
35
+ constructor(file: string, config: ProducerConfig, project: (value: unknown, id: string) => Promise<NotificationInput>, warn?: (message: string) => void);
36
+ private persist;
37
+ advance(cursor: number): void;
38
+ saveCheckpoint(checkpoint: unknown): void;
39
+ enqueue(id: string, value: unknown, cursor?: number | undefined): boolean;
40
+ drain(): Promise<void>;
41
+ private drainOnce;
42
+ /** Wait for the current send before clearing queued sends and retiring its target. */
43
+ retire(): Promise<void>;
44
+ close(): Promise<void>;
45
+ }
46
+ export {};
@@ -0,0 +1,158 @@
1
+ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { readPrivateFile } from '@ours.network/sdk/connector';
4
+ import { readClientProfile } from '../client-profile.js';
5
+ function defaultProfile() {
6
+ try {
7
+ return readClientProfile();
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ }
13
+ /** The installer delivers the producer credential beside the profile credential, bound to that server installation. */
14
+ export const PRODUCER_CREDENTIAL_FILE = 'notifications-producer.json';
15
+ const loopback = (url) => ['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname);
16
+ export function producerConfig(env = process.env, profile = defaultProfile, warn = () => { }) {
17
+ const origin = env.OURS_NOTIFICATIONS_ORIGIN, token = env.OURS_NOTIFICATIONS_PRODUCER_TOKEN;
18
+ if (origin || token) {
19
+ if (!origin || !token || token.length < 32)
20
+ throw new Error('notifications producer requires origin and scoped token');
21
+ const url = new URL(origin);
22
+ if (!['http:', 'https:'].includes(url.protocol) || url.origin !== origin || url.username || url.password)
23
+ throw new Error('invalid notification service origin');
24
+ if (url.protocol === 'http:' && !loopback(url))
25
+ throw new Error('notification credentials require HTTPS or loopback');
26
+ return { origin, token };
27
+ }
28
+ const selected = profile();
29
+ if (!selected)
30
+ return;
31
+ const file = join(dirname(selected.credentialPath), PRODUCER_CREDENTIAL_FILE);
32
+ if (!existsSync(file))
33
+ return;
34
+ // Reasons are fixed text: parser and file errors can quote the secret-bearing contents.
35
+ let reason = 'it is not a private owner-only file';
36
+ try {
37
+ const text = readPrivateFile(file, 4096).toString('utf8');
38
+ reason = 'it is not valid producer JSON';
39
+ const bound = JSON.parse(text);
40
+ reason = 'it belongs to another server installation';
41
+ if (!bound || bound.schema !== 1 || bound.serverUrl !== selected.serverUrl || bound.expectedInstanceId !== selected.expectedInstanceId)
42
+ throw new Error();
43
+ reason = 'its credential is invalid';
44
+ if (typeof bound.token !== 'string' || bound.token.length < 32)
45
+ throw new Error();
46
+ reason = 'the server requires HTTPS or loopback';
47
+ const server = new URL(selected.serverUrl);
48
+ if (server.protocol !== 'https:' && !(server.protocol === 'http:' && loopback(server)))
49
+ throw new Error();
50
+ reason = 'the client credential is unreadable';
51
+ const gatewayCredential = readPrivateFile(selected.credentialPath, 4096).toString('utf8').trim();
52
+ return { origin: selected.serverUrl.replace(/\/$/, '') + '/notifications', token: bound.token, gatewayCredential };
53
+ }
54
+ catch {
55
+ warn(`Notifications are not produced: ${file} is unusable (${reason}). Re-run the installer to repair it.`);
56
+ return;
57
+ }
58
+ }
59
+ /** Durable producer queue. Service dedupe makes crash-after-acceptance replay safe. */
60
+ export class NotificationOutbox {
61
+ file;
62
+ config;
63
+ project;
64
+ warn;
65
+ entries;
66
+ cursor;
67
+ checkpoint;
68
+ running;
69
+ retired = false;
70
+ timer;
71
+ constructor(file, config, project, warn = () => { }) {
72
+ this.file = file;
73
+ this.config = config;
74
+ this.project = project;
75
+ this.warn = warn;
76
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
77
+ const saved = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { entries: [] };
78
+ this.entries = Array.isArray(saved) ? saved : saved.entries;
79
+ this.cursor = saved.cursor;
80
+ this.checkpoint = saved.checkpoint;
81
+ if (!Array.isArray(this.entries))
82
+ throw new Error('invalid notification outbox');
83
+ this.timer = setInterval(() => { void this.drain(); }, 3000);
84
+ this.timer.unref();
85
+ }
86
+ persist(entries, cursor = this.cursor, checkpoint = this.checkpoint) {
87
+ const tmp = this.file + '.tmp', fd = openSync(tmp, 'w', 0o600);
88
+ try {
89
+ writeFileSync(fd, JSON.stringify({ entries, cursor, checkpoint }));
90
+ fsyncSync(fd);
91
+ }
92
+ finally {
93
+ closeSync(fd);
94
+ }
95
+ renameSync(tmp, this.file);
96
+ const dir = openSync(dirname(this.file), 'r');
97
+ try {
98
+ fsyncSync(dir);
99
+ }
100
+ finally {
101
+ closeSync(dir);
102
+ }
103
+ this.entries = entries;
104
+ this.cursor = cursor;
105
+ this.checkpoint = checkpoint;
106
+ }
107
+ advance(cursor) { this.persist(this.entries, cursor); }
108
+ saveCheckpoint(checkpoint) { this.persist(this.entries, this.cursor, checkpoint); }
109
+ enqueue(id, value, cursor = this.cursor) {
110
+ if (this.retired)
111
+ return false;
112
+ if (this.entries.some(e => e.id === id)) {
113
+ if (cursor !== this.cursor)
114
+ this.advance(cursor);
115
+ return true;
116
+ }
117
+ if (this.entries.length >= 2000)
118
+ return false;
119
+ this.persist([...this.entries, { id, value }], cursor);
120
+ return true;
121
+ }
122
+ drain() {
123
+ if (this.retired)
124
+ return Promise.resolve();
125
+ return this.running ??= this.drainOnce().finally(() => { this.running = undefined; });
126
+ }
127
+ async drainOnce() {
128
+ for (const entry of [...this.entries].slice(0, 32)) {
129
+ if (this.retired)
130
+ break;
131
+ try {
132
+ const value = entry.payload ?? await this.project(entry.value, entry.id);
133
+ // Freeze the canonical projection before any network attempt. A crash
134
+ // after service acceptance must replay the identical dedupe payload.
135
+ if (!entry.payload)
136
+ this.persist(this.entries.map(e => e.id === entry.id ? { ...e, payload: value } : e));
137
+ const response = await fetch(this.config.origin + '/api/v1/send', {
138
+ method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10_000),
139
+ headers: { ...(this.config.gatewayCredential
140
+ ? { 'X-Ours-Api-Token': this.config.gatewayCredential, 'X-Ours-Notifications-Producer': this.config.token }
141
+ : { Authorization: `Bearer ${this.config.token}` }), 'Content-Type': 'application/json' },
142
+ body: JSON.stringify(value),
143
+ });
144
+ await response.body?.cancel();
145
+ if (!response.ok)
146
+ throw new Error('notification service rejected event');
147
+ this.persist(this.entries.filter(e => e.id !== entry.id));
148
+ }
149
+ catch {
150
+ this.warn('Notification producer delivery pending; retrying from durable outbox');
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ /** Wait for the current send before clearing queued sends and retiring its target. */
156
+ async retire() { this.retired = true; clearInterval(this.timer); await this.running; this.persist([]); }
157
+ async close() { clearInterval(this.timer); await this.running; }
158
+ }
@@ -0,0 +1,11 @@
1
+ import { type ProducerConfig } from './outbox.js';
2
+ export declare const cleanupDir: () => string;
3
+ /** Only the canonical agent namespace can authorize role-target cleanup. */
4
+ export declare function notificationAgentId(path: string): string | null;
5
+ /** Queue only after the owning lifecycle has established deletion, never on a failed inventory read. */
6
+ export declare function retireNotificationTarget(url: string, config?: ProducerConfig | undefined, dir?: string, request?: typeof fetch): Promise<void>;
7
+ /** Persisted retries also run from the ordinary web notification producer after restart. */
8
+ export declare function drainNotificationTargetCleanup(config: ProducerConfig, dir?: string, request?: typeof fetch, options?: {
9
+ retain?: boolean;
10
+ beforeDelete?: (url: string) => Promise<boolean>;
11
+ }): Promise<void>;
@@ -0,0 +1,63 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { replaceFileAtomically } from '../atomic-file.js';
5
+ import { stateRoot } from '../paths.js';
6
+ import { producerConfig } from './outbox.js';
7
+ export const cleanupDir = () => join(stateRoot(), 'notifications', 'removed-targets');
8
+ /** Only the canonical agent namespace can authorize role-target cleanup. */
9
+ export function notificationAgentId(path) {
10
+ try {
11
+ if (typeof path !== 'string' || /[\\\x00-\x20]|%(?:2f|5c|00|0[ad])/i.test(path))
12
+ return null;
13
+ const match = /^\/fleet\/chats\/agents\/([^/?#]+)(?:[?#]|$)/.exec(path);
14
+ if (!match)
15
+ return null;
16
+ const url = new URL(path, 'https://ours.invalid');
17
+ if (['source', 'chat', 'task', 'detail'].some(key => url.searchParams.has(key)))
18
+ return null;
19
+ const id = decodeURIComponent(match[1]);
20
+ return /^[A-Za-z0-9_-]+$/.test(id) ? id : null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ /** Queue only after the owning lifecycle has established deletion, never on a failed inventory read. */
27
+ export async function retireNotificationTarget(url, config = producerConfig(), dir = cleanupDir(), request = fetch) {
28
+ if (!config)
29
+ return;
30
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
31
+ replaceFileAtomically(join(dir, createHash('sha256').update(url).digest('hex') + '.json'), JSON.stringify({ url }) + '\n', 0o600);
32
+ await drainNotificationTargetCleanup(config, dir, request, { retain: true });
33
+ }
34
+ /** Persisted retries also run from the ordinary web notification producer after restart. */
35
+ export async function drainNotificationTargetCleanup(config, dir = cleanupDir(), request = fetch, options = {}) {
36
+ if (!existsSync(dir))
37
+ return;
38
+ let attempts = 0;
39
+ for (const file of readdirSync(dir).filter(file => /^[a-f0-9]{64}\.json$/.test(file))) {
40
+ if (attempts >= 32)
41
+ break;
42
+ try {
43
+ const path = join(dir, file), value = JSON.parse(readFileSync(path, 'utf8'));
44
+ if (!notificationAgentId(value.url))
45
+ continue;
46
+ attempts++;
47
+ if (options.beforeDelete && !(await options.beforeDelete(value.url))) {
48
+ rmSync(path, { force: true });
49
+ continue;
50
+ }
51
+ const response = await request(config.origin + '/api/v1/delete-target', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000),
52
+ headers: { ...(config.gatewayCredential ? { 'X-Ours-Api-Token': config.gatewayCredential, 'X-Ours-Notifications-Producer': config.token } : { Authorization: `Bearer ${config.token}` }), 'Content-Type': 'application/json' }, body: JSON.stringify(value) });
53
+ await response.body?.cancel();
54
+ if (!response.ok)
55
+ break;
56
+ if (!options.retain)
57
+ rmSync(path, { force: true });
58
+ }
59
+ catch {
60
+ break; /* No credentials or response bodies are included in logs. */
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,20 @@
1
+ import type { ProducerConfig } from './outbox.js';
2
+ /** The Fleet manager owns this sender even when the web console is stopped.
3
+ * Events are already durable in their owning task commit, so no second outbox
4
+ * admission/crash window is needed. A failed task does not block other tasks. */
5
+ export declare class TaskNotificationProducer {
6
+ private readonly config;
7
+ private readonly warn;
8
+ private readonly request;
9
+ private timer?;
10
+ private dirty;
11
+ private running?;
12
+ private offset;
13
+ private stopped;
14
+ private readonly shutdown;
15
+ constructor(config: ProducerConfig, warn?: (line: string) => void, request?: typeof fetch);
16
+ drain(): Promise<void>;
17
+ private retry;
18
+ private drainOnce;
19
+ close(): Promise<void>;
20
+ }
@@ -0,0 +1,100 @@
1
+ import { observeFleetResources } from '../application/resource-observer.js';
2
+ import { tasksDir } from '../rooms-tasks/task-state.js';
3
+ import { acknowledgeTaskNotification, pendingTaskNotifications } from '../rooms-tasks/task-state.js';
4
+ /** The Fleet manager owns this sender even when the web console is stopped.
5
+ * Events are already durable in their owning task commit, so no second outbox
6
+ * admission/crash window is needed. A failed task does not block other tasks. */
7
+ export class TaskNotificationProducer {
8
+ config;
9
+ warn;
10
+ request;
11
+ timer;
12
+ dirty = false;
13
+ running;
14
+ offset = 0;
15
+ stopped = false;
16
+ shutdown = new AbortController();
17
+ constructor(config, warn = () => { }, request = fetch) {
18
+ this.config = config;
19
+ this.warn = warn;
20
+ this.request = request;
21
+ // Subscribe before startup replay: a commit while fetch is pending needs a second pass.
22
+ observeFleetResources({ retryWatchFailures: true, paths: [tasksDir()], roots: [] }, state => {
23
+ if (state.health !== 'healthy')
24
+ this.warn('Task notification observation unavailable; committed events retained');
25
+ else {
26
+ this.dirty = true;
27
+ void this.drain();
28
+ }
29
+ }, this.shutdown.signal);
30
+ void this.drain();
31
+ }
32
+ drain() {
33
+ if (this.stopped)
34
+ return Promise.resolve();
35
+ if (this.running)
36
+ return this.running;
37
+ this.dirty = false;
38
+ clearTimeout(this.timer);
39
+ this.timer = undefined;
40
+ this.running = this.drainOnce().catch(() => {
41
+ this.warn('Task notification snapshot unavailable; committed events retained');
42
+ this.retry();
43
+ }).finally(() => {
44
+ this.running = undefined;
45
+ if (this.dirty && !this.stopped) {
46
+ this.dirty = false;
47
+ void this.drain();
48
+ }
49
+ });
50
+ return this.running;
51
+ }
52
+ retry() {
53
+ if (this.stopped || this.timer)
54
+ return;
55
+ this.timer = setTimeout(() => { this.timer = undefined; void this.drain(); }, 3000);
56
+ this.timer.unref();
57
+ }
58
+ async drainOnce() {
59
+ if (this.stopped)
60
+ return;
61
+ const pending = pendingTaskNotifications(this.warn);
62
+ if (!pending.length)
63
+ return;
64
+ const start = this.offset % pending.length;
65
+ const batch = [...pending.slice(start), ...pending.slice(0, start)].slice(0, 32);
66
+ this.offset = start + batch.length;
67
+ // Retry only outstanding work; never run an idle discovery timer.
68
+ this.retry();
69
+ for (let index = 0; index < batch.length; index += 4) {
70
+ if (this.stopped)
71
+ break;
72
+ await Promise.all(batch.slice(index, index + 4).map(async (task) => {
73
+ for (const event of task.events.slice(0, 32)) {
74
+ if (this.stopped)
75
+ break;
76
+ try {
77
+ const response = await this.request(this.config.origin + '/api/v1/send', {
78
+ method: 'POST', redirect: 'error', signal: AbortSignal.any([this.shutdown.signal, AbortSignal.timeout(10_000)]),
79
+ headers: { ...(this.config.gatewayCredential
80
+ ? { 'X-Ours-Api-Token': this.config.gatewayCredential, 'X-Ours-Notifications-Producer': this.config.token }
81
+ : { Authorization: `Bearer ${this.config.token}` }), 'Content-Type': 'application/json' },
82
+ body: JSON.stringify(event),
83
+ });
84
+ await response.body?.cancel();
85
+ if (!response.ok) {
86
+ this.warn(`Task notification delivery pending (HTTP ${response.status}); committed event retained`);
87
+ break;
88
+ }
89
+ acknowledgeTaskNotification(task.taskId, event.eventId);
90
+ }
91
+ catch {
92
+ this.warn('Task notification delivery pending; committed event retained');
93
+ break;
94
+ }
95
+ }
96
+ }));
97
+ }
98
+ }
99
+ async close() { this.stopped = true; clearTimeout(this.timer); this.shutdown.abort(); await this.running; }
100
+ }
package/dist/ops.d.ts CHANGED
@@ -54,5 +54,7 @@ export declare function up(cfg: FleetConfig, names: string[], deps: OpsDeps, con
54
54
  export declare function down(cfg: FleetConfig, names: string[], deps: OpsDeps): Promise<void>;
55
55
  /** Re-sync from config + bounce. mode 'keep' resumes context; 'fresh' wipes it. */
56
56
  export declare function restartRoles(cfg: FleetConfig, names: string[], deps: OpsDeps, mode: 'keep' | 'fresh', configPath?: string): Promise<void>;
57
+ /** Read-only admission check shared by CLI removal and web preview, before any archive. */
58
+ export declare function assertStandaloneRoleRemoval(cfg: FleetConfig, name: string): void;
57
59
  /** Stop + forget a role: unit, state dir, and its fleet.d file when spawned. */
58
60
  export declare function rmRole(cfg: FleetConfig, name: string, deps: OpsDeps): Promise<void>;