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

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 (414) 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 +27 -0
  19. package/dist/application/conversation-history.js +215 -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 +2 -2
  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/role-creation-service.js +3 -2
  30. package/dist/application/role-removal-service.js +17 -3
  31. package/dist/application/role-repository.js +19 -2
  32. package/dist/application/room-layout-definitions.d.ts +45 -0
  33. package/dist/application/room-layout-definitions.js +130 -0
  34. package/dist/application/session-control.d.ts +61 -17
  35. package/dist/application/session-control.js +207 -23
  36. package/dist/application/supervisor-ours-tools.js +5 -11
  37. package/dist/application/task-layouts.d.ts +91 -0
  38. package/dist/application/task-layouts.js +259 -0
  39. package/dist/application/task-room-service.d.ts +99 -3
  40. package/dist/application/task-room-service.js +285 -32
  41. package/dist/application/types.d.ts +2 -0
  42. package/dist/atomic-file.d.ts +34 -1
  43. package/dist/atomic-file.js +161 -18
  44. package/dist/briefing.d.ts +3 -1
  45. package/dist/briefing.js +97 -31
  46. package/dist/build-info.json +9 -4
  47. package/dist/capabilities.d.ts +9 -1
  48. package/dist/capabilities.js +13 -0
  49. package/dist/cli.js +400 -37
  50. package/dist/client-profile.d.ts +5 -17
  51. package/dist/client-profile.js +4 -127
  52. package/dist/config.d.ts +45 -0
  53. package/dist/config.js +57 -6
  54. package/dist/creation.d.ts +1 -1
  55. package/dist/creation.js +4 -4
  56. package/dist/daemon-recovery.d.ts +1 -8
  57. package/dist/daemon-recovery.js +8 -113
  58. package/dist/docs.d.ts +1 -1
  59. package/dist/docs.js +207 -19
  60. package/dist/doctor.d.ts +4 -1
  61. package/dist/doctor.js +75 -60
  62. package/dist/erased-resources.d.ts +9 -0
  63. package/dist/erased-resources.js +87 -0
  64. package/dist/file-delivery/reader.d.ts +15 -0
  65. package/dist/file-delivery/reader.js +63 -0
  66. package/dist/file-delivery/store.d.ts +15 -0
  67. package/dist/file-delivery/store.js +124 -0
  68. package/dist/file-delivery/types.d.ts +19 -0
  69. package/dist/file-delivery/types.js +1 -0
  70. package/dist/fleet-command-audit.d.ts +7 -0
  71. package/dist/fleet-command-audit.js +24 -8
  72. package/dist/harness/agent-session.d.ts +4 -0
  73. package/dist/harness/claude-code-session.js +1 -1
  74. package/dist/harness/claude-code.js +21 -11
  75. package/dist/harness/claude-runtime.d.ts +12 -0
  76. package/dist/harness/claude-runtime.js +43 -0
  77. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  78. package/dist/harness/codex-app-server-proxy.js +41 -5
  79. package/dist/harness/codex-reasoning.d.ts +5 -0
  80. package/dist/harness/codex-reasoning.js +12 -0
  81. package/dist/harness/codex-runtime.d.ts +11 -1
  82. package/dist/harness/codex-runtime.js +31 -4
  83. package/dist/harness/codex-session.js +2 -2
  84. package/dist/harness/codex.js +24 -12
  85. package/dist/init-wizard.d.ts +2 -0
  86. package/dist/init-wizard.js +38 -19
  87. package/dist/isolation/policy.js +6 -1
  88. package/dist/isolation/types.d.ts +5 -0
  89. package/dist/managed-cli.d.ts +253 -0
  90. package/dist/managed-cli.js +923 -0
  91. package/dist/managed-recovery.d.ts +13 -0
  92. package/dist/managed-recovery.js +31 -0
  93. package/dist/minimal-setup.d.ts +2 -0
  94. package/dist/minimal-setup.js +44 -0
  95. package/dist/monitor.d.ts +8 -34
  96. package/dist/monitor.js +89 -152
  97. package/dist/notifications/fleet-producer.d.ts +19 -0
  98. package/dist/notifications/fleet-producer.js +208 -0
  99. package/dist/notifications/outbox.d.ts +46 -0
  100. package/dist/notifications/outbox.js +158 -0
  101. package/dist/notifications/target-cleanup.d.ts +10 -0
  102. package/dist/notifications/target-cleanup.js +63 -0
  103. package/dist/notifications/task-producer.d.ts +18 -0
  104. package/dist/notifications/task-producer.js +67 -0
  105. package/dist/ops.d.ts +2 -0
  106. package/dist/ops.js +20 -3
  107. package/dist/owner-channel/channel.js +17 -6
  108. package/dist/owner-channel/commands.d.ts +4 -0
  109. package/dist/owner-channel/commands.js +8 -0
  110. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  111. package/dist/owner-channel/ours-client.js +2 -17
  112. package/dist/paths.d.ts +2 -0
  113. package/dist/paths.js +2 -0
  114. package/dist/preset-migration.js +43 -0
  115. package/dist/process-incarnation.d.ts +23 -0
  116. package/dist/process-incarnation.js +42 -0
  117. package/dist/reminders/manager.d.ts +75 -0
  118. package/dist/reminders/manager.js +278 -0
  119. package/dist/reminders/runtime.d.ts +27 -0
  120. package/dist/reminders/runtime.js +32 -0
  121. package/dist/reminders/schedule.d.ts +37 -0
  122. package/dist/reminders/schedule.js +104 -0
  123. package/dist/reminders/store.d.ts +341 -0
  124. package/dist/reminders/store.js +762 -0
  125. package/dist/reminders/tools.d.ts +20 -0
  126. package/dist/reminders/tools.js +114 -0
  127. package/dist/resolved-plan.js +1 -0
  128. package/dist/rooms-tasks/add-member.d.ts +48 -0
  129. package/dist/rooms-tasks/add-member.js +83 -0
  130. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  131. package/dist/rooms-tasks/archived-absence.js +48 -0
  132. package/dist/rooms-tasks/cli.js +65 -5
  133. package/dist/rooms-tasks/close.d.ts +6 -2
  134. package/dist/rooms-tasks/close.js +246 -37
  135. package/dist/rooms-tasks/config.d.ts +1 -1
  136. package/dist/rooms-tasks/config.js +11 -4
  137. package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
  138. package/dist/rooms-tasks/cowork-adapter.js +15 -121
  139. package/dist/rooms-tasks/cowork-http.js +56 -6
  140. package/dist/rooms-tasks/deletion.d.ts +1 -1
  141. package/dist/rooms-tasks/deletion.js +84 -5
  142. package/dist/rooms-tasks/erasure.d.ts +6 -0
  143. package/dist/rooms-tasks/erasure.js +173 -0
  144. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  145. package/dist/rooms-tasks/external-worker.js +3 -4
  146. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  147. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  148. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  149. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  150. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  151. package/dist/rooms-tasks/layout-cli.js +107 -0
  152. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  153. package/dist/rooms-tasks/layout-config.js +89 -0
  154. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  155. package/dist/rooms-tasks/layout-control.js +52 -0
  156. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  157. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  158. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  159. package/dist/rooms-tasks/layout-service.js +203 -0
  160. package/dist/rooms-tasks/layout.d.ts +121 -0
  161. package/dist/rooms-tasks/layout.js +285 -0
  162. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  163. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  164. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  165. package/dist/rooms-tasks/live-readiness.js +85 -0
  166. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  167. package/dist/rooms-tasks/member-ownership.js +40 -0
  168. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  169. package/dist/rooms-tasks/member-startup.js +2 -0
  170. package/dist/rooms-tasks/provision.d.ts +3 -0
  171. package/dist/rooms-tasks/provision.js +101 -49
  172. package/dist/rooms-tasks/recovery.d.ts +19 -0
  173. package/dist/rooms-tasks/recovery.js +110 -0
  174. package/dist/rooms-tasks/room-state.d.ts +4 -2
  175. package/dist/rooms-tasks/room-state.js +47 -8
  176. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  177. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  178. package/dist/rooms-tasks/task-state.d.ts +51 -8
  179. package/dist/rooms-tasks/task-state.js +245 -25
  180. package/dist/rooms-tasks/types.d.ts +40 -0
  181. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  182. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  183. package/dist/rooms-tasks/workspace.d.ts +35 -0
  184. package/dist/rooms-tasks/workspace.js +325 -0
  185. package/dist/runner.d.ts +33 -4
  186. package/dist/runner.js +389 -56
  187. package/dist/session/acp.d.ts +75 -2
  188. package/dist/session/acp.js +345 -82
  189. package/dist/session/arbiter.d.ts +98 -0
  190. package/dist/session/arbiter.js +272 -3
  191. package/dist/session/codex-app-server.d.ts +45 -1
  192. package/dist/session/codex-app-server.js +225 -32
  193. package/dist/session/control.d.ts +17 -3
  194. package/dist/session/control.js +213 -23
  195. package/dist/session/conversation-normalizer.d.ts +2 -1
  196. package/dist/session/conversation-normalizer.js +1 -1
  197. package/dist/session/conversation-observation.d.ts +25 -0
  198. package/dist/session/conversation-observation.js +100 -0
  199. package/dist/session/conversation-replay.d.ts +50 -0
  200. package/dist/session/conversation-replay.js +203 -0
  201. package/dist/session/conversation-store.d.ts +15 -1
  202. package/dist/session/conversation-store.js +206 -26
  203. package/dist/session/conversation-types.d.ts +57 -4
  204. package/dist/session/prompt-batch.d.ts +79 -0
  205. package/dist/session/prompt-batch.js +120 -0
  206. package/dist/session/task-notice.d.ts +14 -0
  207. package/dist/session/task-notice.js +13 -0
  208. package/dist/session/types.d.ts +42 -1
  209. package/dist/setup-tunnel-preflight.d.ts +34 -0
  210. package/dist/setup-tunnel-preflight.js +62 -0
  211. package/dist/socket-path.d.ts +2 -0
  212. package/dist/socket-path.js +22 -0
  213. package/dist/spawn.d.ts +5 -1
  214. package/dist/spawn.js +40 -4
  215. package/dist/startup-prompt.d.ts +15 -0
  216. package/dist/startup-prompt.js +71 -0
  217. package/dist/startup-readiness.d.ts +32 -0
  218. package/dist/startup-readiness.js +101 -0
  219. package/dist/startup-telemetry.d.ts +16 -0
  220. package/dist/startup-telemetry.js +46 -0
  221. package/dist/subscriptions/cli.d.ts +91 -0
  222. package/dist/subscriptions/cli.js +223 -0
  223. package/dist/subscriptions/launch.d.ts +37 -0
  224. package/dist/subscriptions/launch.js +93 -0
  225. package/dist/subscriptions/login.d.ts +76 -0
  226. package/dist/subscriptions/login.js +207 -0
  227. package/dist/subscriptions/observed.d.ts +7 -0
  228. package/dist/subscriptions/observed.js +44 -0
  229. package/dist/subscriptions/probe.d.ts +10 -0
  230. package/dist/subscriptions/probe.js +73 -0
  231. package/dist/subscriptions/service.d.ts +102 -0
  232. package/dist/subscriptions/service.js +364 -0
  233. package/dist/subscriptions/store.d.ts +71 -0
  234. package/dist/subscriptions/store.js +140 -0
  235. package/dist/supervisor/adoption.d.ts +8 -0
  236. package/dist/supervisor/adoption.js +78 -0
  237. package/dist/supervisor/catalog.d.ts +62 -0
  238. package/dist/supervisor/catalog.js +254 -0
  239. package/dist/supervisor/fleet.d.ts +8 -0
  240. package/dist/supervisor/fleet.js +89 -0
  241. package/dist/supervisor/index.js +3 -6
  242. package/dist/supervisor/launchd.d.ts +1 -1
  243. package/dist/supervisor/launchd.js +26 -13
  244. package/dist/supervisor/legacy.d.ts +35 -0
  245. package/dist/supervisor/legacy.js +524 -0
  246. package/dist/supervisor/manager.d.ts +15 -0
  247. package/dist/supervisor/manager.js +270 -0
  248. package/dist/supervisor/registration-refresh.d.ts +45 -0
  249. package/dist/supervisor/registration-refresh.js +266 -0
  250. package/dist/supervisor/scope.d.ts +4 -0
  251. package/dist/supervisor/scope.js +10 -0
  252. package/dist/supervisor/systemd.d.ts +2 -2
  253. package/dist/supervisor/systemd.js +19 -33
  254. package/dist/task-supervision.d.ts +9 -0
  255. package/dist/task-supervision.js +145 -0
  256. package/dist/temp-idle.d.ts +17 -0
  257. package/dist/temp-idle.js +42 -0
  258. package/dist/temp-lifecycle.d.ts +23 -8
  259. package/dist/temp-lifecycle.js +123 -66
  260. package/dist/temp-supervisor-recovery.d.ts +1 -1
  261. package/dist/temp-supervisor-recovery.js +2 -2
  262. package/dist/tunnel-setup-completion.d.ts +7 -0
  263. package/dist/tunnel-setup-completion.js +13 -0
  264. package/dist/tunnel-setup-input.d.ts +19 -0
  265. package/dist/tunnel-setup-input.js +124 -0
  266. package/dist/web/agent-attachments.d.ts +29 -0
  267. package/dist/web/agent-attachments.js +162 -0
  268. package/dist/web/auth.d.ts +65 -2
  269. package/dist/web/auth.js +201 -11
  270. package/dist/web/available-port.d.ts +2 -0
  271. package/dist/web/available-port.js +23 -0
  272. package/dist/web/control.d.ts +3 -3
  273. package/dist/web/control.js +10 -8
  274. package/dist/web/conversation-stream-codec.d.ts +23 -0
  275. package/dist/web/conversation-stream-codec.js +110 -0
  276. package/dist/web/conversation-stream.d.ts +25 -0
  277. package/dist/web/conversation-stream.js +231 -0
  278. package/dist/web/fleet-config-service.d.ts +4 -1
  279. package/dist/web/fleet-config-service.js +46 -15
  280. package/dist/web/prefix-gateway.d.ts +32 -0
  281. package/dist/web/prefix-gateway.js +238 -0
  282. package/dist/web/runtime.d.ts +3 -1
  283. package/dist/web/runtime.js +128 -33
  284. package/dist/web/server.d.ts +16 -0
  285. package/dist/web/server.js +639 -55
  286. package/dist/web/service.d.ts +11 -2
  287. package/dist/web/service.js +30 -4
  288. package/dist/web/workspace-devices.d.ts +107 -0
  289. package/dist/web/workspace-devices.js +277 -0
  290. package/dist/web/workspace-tunnel.d.ts +1 -0
  291. package/dist/web/workspace-tunnel.js +34 -0
  292. package/dist/workspace-enrollment.d.ts +83 -0
  293. package/dist/workspace-enrollment.js +580 -0
  294. package/dist/workspace-replacement.d.ts +66 -0
  295. package/dist/workspace-replacement.js +220 -0
  296. package/docs/acp-files.md +146 -0
  297. package/docs/conversation-stream.md +190 -0
  298. package/docs/fleet-migration.md +54 -0
  299. package/docs/live-voice.md +13 -0
  300. package/docs/room-layouts.md +227 -0
  301. package/docs/self-reminders.md +374 -0
  302. package/docs/task-workspaces.md +225 -0
  303. package/docs/validation/managed-cli-permissions.md +421 -0
  304. package/examples/room-layouts/fresh.yaml +27 -0
  305. package/examples/room-layouts/scoped.yaml +21 -0
  306. package/examples/room-layouts/shared.yaml +19 -0
  307. package/package.json +24 -20
  308. package/presets/README.md +78 -0
  309. package/presets/brain-catalog.json +8 -2
  310. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  311. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  312. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  313. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -2
  314. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  315. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  316. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  317. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  318. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  319. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  320. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  321. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  322. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  323. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  324. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  325. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  326. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  327. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  328. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  329. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  330. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  331. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  332. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  333. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  334. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  335. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  336. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  337. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  338. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  339. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  340. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  341. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  342. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  343. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  344. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  345. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  346. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  347. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  348. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  349. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  350. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  351. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  352. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  353. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  354. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  375. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  376. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  377. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  378. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  379. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  380. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  384. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  385. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  386. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  387. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  388. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  390. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  391. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  392. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  393. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  394. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  395. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  396. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  397. package/presets/fleet/roles/Coordinator.yaml +12 -11
  398. package/presets/fleet/roles/Critic.yaml +13 -0
  399. package/presets/fleet/roles/Developer.yaml +13 -0
  400. package/presets/fleet/roles/Engineer.yaml +53 -0
  401. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  402. package/presets/fleet/room_layouts/pair.yaml +24 -0
  403. package/presets/fleet/room_layouts/single.yaml +17 -0
  404. package/presets/fleet/room_layouts/team.yaml +30 -0
  405. package/presets/fleet/room_templates/engineering.yaml +19 -0
  406. package/presets/manifest.json +2 -2
  407. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  408. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  409. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  410. package/dist/web-app/icons/ours-fleet.svg +0 -4
  411. package/dist/web-app/index.html +0 -17
  412. package/dist/web-app/manifest.webmanifest +0 -15
  413. package/dist/web-app/offline.html +0 -18
  414. package/dist/web-app/sw.js +0 -51
@@ -0,0 +1,203 @@
1
+ import { open } from 'node:fs/promises';
2
+ import { setImmediate as yieldTurn } from 'node:timers/promises';
3
+ export const MAX_REPLAY_BUFFER_BYTES = 8 * 1024 * 1024;
4
+ export const MAX_CONVERSATION_RECORD_BYTES = 16 * 1024 * 1024;
5
+ export class ReplayCoverageError extends Error {
6
+ constructor(detail) { super(`replay coverage unavailable: ${detail}`); }
7
+ }
8
+ /** Bounded reads and explicit scheduling even when a whole segment is cached by the OS. */
9
+ export async function* replayLines(path, signal) {
10
+ const file = await open(path, 'r');
11
+ try {
12
+ const chunk = Buffer.alloc(64 * 1024);
13
+ let pending = Buffer.alloc(0);
14
+ for (;;) {
15
+ signal?.throwIfAborted();
16
+ const { bytesRead } = await file.read(chunk, 0, chunk.length, null);
17
+ if (!bytesRead)
18
+ break;
19
+ const data = Buffer.concat([pending, chunk.subarray(0, bytesRead)]);
20
+ let start = 0;
21
+ for (let i = 0; i < data.length; i++) {
22
+ if (data[i] !== 10)
23
+ continue;
24
+ if (i - start > MAX_CONVERSATION_RECORD_BYTES)
25
+ throw new ReplayCoverageError('record exceeds assembly limit');
26
+ yield data.toString('utf8', start, i);
27
+ start = i + 1;
28
+ }
29
+ pending = Buffer.from(data.subarray(start));
30
+ if (pending.length > MAX_CONVERSATION_RECORD_BYTES)
31
+ throw new ReplayCoverageError('record exceeds assembly limit');
32
+ await yieldTurn();
33
+ }
34
+ if (pending.length)
35
+ throw new ReplayCoverageError('unterminated record');
36
+ }
37
+ finally {
38
+ await file.close();
39
+ }
40
+ }
41
+ /** Passive owner-turn registration, complete replay, ready, then buffered post-barrier delivery. */
42
+ export function startConversationReplay(owner, request, sink) {
43
+ if (request.controller !== false || !owner.observeConversation || !owner.subscribeConversation || !owner.subscribeConversationFailure || !owner.replayConversation)
44
+ throw new ReplayCoverageError('passive observation is unavailable');
45
+ const after = request.after ?? 0;
46
+ if (!request.expectedGeneration || !Number.isSafeInteger(after) || after < 0)
47
+ throw new ReplayCoverageError('invalid cursor or generation');
48
+ const abort = new AbortController();
49
+ const disposers = [];
50
+ const pending = [];
51
+ let pendingBytes = 0;
52
+ let closed = false;
53
+ let committed = false;
54
+ let draining = false;
55
+ let snapshot;
56
+ let resolveReady;
57
+ let rejectReady;
58
+ const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
59
+ // Consumers can attach later without an unhandled rejection during synchronous registration.
60
+ void ready.catch(() => { });
61
+ const close = (reason) => {
62
+ if (closed)
63
+ return;
64
+ closed = true;
65
+ abort.abort(new Error(reason ?? 'conversation follow closed'));
66
+ for (const dispose of disposers.splice(0))
67
+ dispose();
68
+ pending.length = 0;
69
+ pendingBytes = 0;
70
+ rejectReady(new Error(reason ?? 'conversation follow closed'));
71
+ sink.close(reason);
72
+ };
73
+ const own = (dispose) => { if (closed)
74
+ dispose();
75
+ else
76
+ disposers.push(dispose); };
77
+ const drain = async () => {
78
+ if (!committed || draining || closed)
79
+ return;
80
+ draining = true;
81
+ try {
82
+ while (pending.length && !closed) {
83
+ const next = pending[0];
84
+ if (next.value.event) {
85
+ if (next.value.event.seq > snapshot.ledgerBarrier)
86
+ await sink.event(next.value.event);
87
+ }
88
+ else if (next.value.state.stateRevision > snapshot.stateRevision)
89
+ await sink.state(next.value.state);
90
+ if (!closed) {
91
+ pending.shift();
92
+ pendingBytes -= next.bytes;
93
+ }
94
+ }
95
+ }
96
+ catch (error) {
97
+ close(error.message);
98
+ }
99
+ finally {
100
+ draining = false;
101
+ }
102
+ };
103
+ const enqueue = (value) => {
104
+ if (closed)
105
+ return;
106
+ if (value.state?.observationFailure) {
107
+ close('conversation observation failed');
108
+ return;
109
+ }
110
+ if ((value.event ?? value.state).sessionGeneration !== request.expectedGeneration) {
111
+ close('session generation changed');
112
+ return;
113
+ }
114
+ const recordBytes = Buffer.byteLength(JSON.stringify(value.event ?? value.state));
115
+ const bytes = recordBytes + 10; // {"event":...} or {"state":...} queued envelope
116
+ if (recordBytes > MAX_CONVERSATION_RECORD_BYTES) {
117
+ close('conversation record exceeds assembly limit');
118
+ return;
119
+ }
120
+ // One active record goes straight to the progressive writer. Only waiting
121
+ // records count against the 8 MiB queue; the active assembly is bounded separately.
122
+ if (committed && !draining && pending.length === 0) {
123
+ draining = true;
124
+ void (async () => {
125
+ try {
126
+ if (value.event) {
127
+ if (value.event.seq > snapshot.ledgerBarrier)
128
+ await sink.event(value.event);
129
+ }
130
+ else if (value.state.stateRevision > snapshot.stateRevision)
131
+ await sink.state(value.state);
132
+ }
133
+ catch (error) {
134
+ close(error.message);
135
+ }
136
+ finally {
137
+ draining = false;
138
+ void drain();
139
+ }
140
+ })();
141
+ return;
142
+ }
143
+ if (pendingBytes + bytes > MAX_REPLAY_BUFFER_BYTES) {
144
+ close('conversation live replay buffer exceeded');
145
+ return;
146
+ }
147
+ pending.push({ value, bytes });
148
+ pendingBytes += bytes;
149
+ void drain();
150
+ };
151
+ try {
152
+ // No await between registration and the owner's coherent snapshot/barrier capture.
153
+ own(owner.subscribeConversation(event => enqueue({ event })));
154
+ own(owner.subscribeConversationFailure(generation => {
155
+ if (generation === request.expectedGeneration)
156
+ close('conversation generation has non-durable events');
157
+ }));
158
+ const observation = owner.observeConversation(state => enqueue({ state }));
159
+ own(observation.unsubscribe);
160
+ snapshot = observation.snapshot;
161
+ if (snapshot.observationFailure)
162
+ throw new ReplayCoverageError('conversation observation failed');
163
+ if (snapshot.sessionGeneration !== request.expectedGeneration)
164
+ throw new ReplayCoverageError('session generation changed');
165
+ if (after > snapshot.ledgerBarrier)
166
+ throw new ReplayCoverageError('cursor exceeds barrier');
167
+ if (request.signal) {
168
+ const onAbort = () => close('conversation follow aborted');
169
+ request.signal.addEventListener('abort', onAbort, { once: true });
170
+ own(() => request.signal.removeEventListener('abort', onAbort));
171
+ if (request.signal.aborted)
172
+ onAbort();
173
+ }
174
+ }
175
+ catch (error) {
176
+ close(error.message);
177
+ }
178
+ if (!closed)
179
+ void (async () => {
180
+ try {
181
+ let coveredThrough = after;
182
+ for await (const page of owner.replayConversation({ expectedGeneration: request.expectedGeneration, after, through: snapshot.ledgerBarrier, signal: abort.signal })) {
183
+ abort.signal.throwIfAborted();
184
+ if (page.coveredThrough <= coveredThrough || page.coveredThrough > snapshot.ledgerBarrier)
185
+ throw new ReplayCoverageError('non-progressing page');
186
+ await sink.replay(page);
187
+ coveredThrough = page.coveredThrough;
188
+ }
189
+ abort.signal.throwIfAborted();
190
+ if (coveredThrough !== snapshot.ledgerBarrier)
191
+ throw new ReplayCoverageError('incomplete replay');
192
+ await sink.ready({ snapshot, coveredThrough });
193
+ abort.signal.throwIfAborted();
194
+ committed = true;
195
+ resolveReady();
196
+ await drain();
197
+ }
198
+ catch (error) {
199
+ close(error.message);
200
+ }
201
+ })();
202
+ return { ready, close: () => close() };
203
+ }
@@ -1,3 +1,4 @@
1
+ import { type ConversationReplayRequest, type ConversationReplayPage } from './conversation-replay.js';
1
2
  import type { ConversationEventV1, PromptReceipt } from './conversation-types.js';
2
3
  export interface ConversationStoreOptions {
3
4
  roleId: string;
@@ -29,6 +30,14 @@ type EventDraft = Omit<ConversationEventV1, 'schemaVersion' | 'roleId' | 'eventI
29
30
  export declare class ConversationEventStore {
30
31
  private readonly dir;
31
32
  private nextSeq;
33
+ private lastDurableSeq;
34
+ private readonly segmentRanges;
35
+ private readonly generationFloors;
36
+ private readonly failedGenerations;
37
+ private readonly failureListeners;
38
+ private unavailable;
39
+ private rotateBeforeAppend;
40
+ private firstAvailableSeq?;
32
41
  private segments;
33
42
  private tail;
34
43
  private readonly listeners;
@@ -44,6 +53,8 @@ export declare class ConversationEventStore {
44
53
  constructor(dir: string, options: ConversationStoreOptions);
45
54
  /** First 24 hex chars of sha-256; the idempotency body-digest convention. */
46
55
  static bodyDigest(body: string): string;
56
+ /** Current delivery eligibility only; requested historical ranges still need replay verification. */
57
+ generationDurable(generation: string): boolean;
47
58
  get degraded(): boolean;
48
59
  get degradedDetail(): string | undefined;
49
60
  /**
@@ -58,6 +69,8 @@ export declare class ConversationEventStore {
58
69
  appendSafe(draft: EventDraft): ConversationEventV1 | undefined;
59
70
  page(request?: ConversationPageRequest): ConversationStorePage;
60
71
  subscribe(listener: (event: ConversationEventV1) => void): () => void;
72
+ /** A current-generation failed append invalidates even a follow already past ready. */
73
+ subscribeFailure(listener: (generation: string) => void): () => void;
61
74
  /** Store the receipt a repeated command must get back. */
62
75
  recordReceipt(commandId: string, receipt: PromptReceipt, bodyDigest: string): void;
63
76
  /**
@@ -81,7 +94,8 @@ export declare class ConversationEventStore {
81
94
  private trackPromptState;
82
95
  private segmentFd;
83
96
  private writeManifest;
84
- private firstStoredSeq;
97
+ /** One forward iterator owns the scan; each segment is opened at most once. */
98
+ replay(request: ConversationReplayRequest): AsyncGenerator<ConversationReplayPage>;
85
99
  private eventsAfter;
86
100
  private markDegraded;
87
101
  }
@@ -1,6 +1,8 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, statSync, writeFileSync, writeSync, } from 'node:fs';
3
3
  import { join } from 'node:path';
4
+ import { setImmediate as yieldTurn } from 'node:timers/promises';
5
+ import { replayLines, ReplayCoverageError } from './conversation-replay.js';
4
6
  /**
5
7
  * Durable, append-only, single-writer conversation ledger for one role
6
8
  * The per-role runner is the only writer; readers page by seq.
@@ -24,6 +26,14 @@ export class IdempotencyConflictError extends Error {
24
26
  export class ConversationEventStore {
25
27
  dir;
26
28
  nextSeq = 1;
29
+ lastDurableSeq = 0;
30
+ segmentRanges = new Map();
31
+ generationFloors = new Map();
32
+ failedGenerations = new Set();
33
+ failureListeners = new Set();
34
+ unavailable = false;
35
+ rotateBeforeAppend = false;
36
+ firstAvailableSeq;
27
37
  segments = [];
28
38
  tail = [];
29
39
  listeners = new Set();
@@ -46,6 +56,7 @@ export class ConversationEventStore {
46
56
  this.recover();
47
57
  }
48
58
  catch (error) {
59
+ this.unavailable = true;
49
60
  this.markDegraded(`store unavailable: ${error.message}`);
50
61
  }
51
62
  }
@@ -53,6 +64,10 @@ export class ConversationEventStore {
53
64
  static bodyDigest(body) {
54
65
  return createHash('sha256').update(body).digest('hex').slice(0, 24);
55
66
  }
67
+ /** Current delivery eligibility only; requested historical ranges still need replay verification. */
68
+ generationDurable(generation) {
69
+ return Boolean(generation) && !this.unavailable && !this.failedGenerations.has(generation);
70
+ }
56
71
  get degraded() { return this._degraded; }
57
72
  get degradedDetail() { return this.degradedReason; }
58
73
  /**
@@ -68,11 +83,47 @@ export class ConversationEventStore {
68
83
  at: new Date().toISOString(),
69
84
  ...draft,
70
85
  };
71
- const line = JSON.stringify(event) + '\n';
72
- const fd = this.segmentFd(Buffer.byteLength(line));
73
- writeSync(fd, line);
74
- fsyncSync(fd);
75
- this.nextSeq++;
86
+ let line;
87
+ try {
88
+ line = JSON.stringify(event) + '\n';
89
+ const fd = this.segmentFd(Buffer.byteLength(line));
90
+ // From this point writes may have reached disk even if write/fsync throws.
91
+ // Never let a later generation reuse that identity; gaps are safer than collisions.
92
+ this.nextSeq++;
93
+ const bytes = Buffer.from(line);
94
+ let offset = 0;
95
+ while (offset < bytes.length) {
96
+ const written = writeSync(fd, bytes, offset, bytes.length - offset);
97
+ if (!written)
98
+ throw new Error('short ledger write');
99
+ offset += written;
100
+ }
101
+ fsyncSync(fd);
102
+ }
103
+ catch (error) {
104
+ const firstFailure = !this.failedGenerations.has(draft.sessionGeneration);
105
+ this.failedGenerations.add(draft.sessionGeneration);
106
+ this.rotateBeforeAppend = true;
107
+ this.writeManifest();
108
+ this.markDegraded(`event append failed: ${error.message}`);
109
+ if (firstFailure)
110
+ for (const listener of this.failureListeners) {
111
+ try {
112
+ listener(draft.sessionGeneration);
113
+ }
114
+ catch { /* observers cannot interrupt owner failure handling */ }
115
+ }
116
+ throw error;
117
+ }
118
+ if (!this.generationFloors.has(event.sessionGeneration))
119
+ this.generationFloors.set(event.sessionGeneration, event.seq - 1);
120
+ const segment = this.segments.at(-1);
121
+ const range = this.segmentRanges.get(segment) ?? { damaged: false };
122
+ range.first ??= event.seq;
123
+ range.last = event.seq;
124
+ this.segmentRanges.set(segment, range);
125
+ this.firstAvailableSeq ??= event.seq;
126
+ this.lastDurableSeq = event.seq;
76
127
  this.activeBytes += Buffer.byteLength(line);
77
128
  this.tail.push(event);
78
129
  if (this.tail.length > TAIL_EVENTS)
@@ -101,7 +152,10 @@ export class ConversationEventStore {
101
152
  const events = this.eventsAfter(after, limit + 1);
102
153
  const hasMore = events.length > limit;
103
154
  const pageEvents = hasMore ? events.slice(0, limit) : events;
104
- const firstStored = this.firstStoredSeq();
155
+ // This single-writer store never removes retained segments. Recovery and
156
+ // append already know the first readable sequence; paging the memory tail
157
+ // must not parse the first segment again, especially while skipping replay.
158
+ const firstStored = this.firstAvailableSeq;
105
159
  return {
106
160
  events: pageEvents,
107
161
  ...(firstStored !== undefined ? { firstAvailableCursor: String(firstStored) } : {}),
@@ -113,6 +167,11 @@ export class ConversationEventStore {
113
167
  this.listeners.add(listener);
114
168
  return () => this.listeners.delete(listener);
115
169
  }
170
+ /** A current-generation failed append invalidates even a follow already past ready. */
171
+ subscribeFailure(listener) {
172
+ this.failureListeners.add(listener);
173
+ return () => this.failureListeners.delete(listener);
174
+ }
116
175
  /** Store the receipt a repeated command must get back. */
117
176
  recordReceipt(commandId, receipt, bodyDigest) {
118
177
  this.commands.set(commandId, { receipt, bodyDigest });
@@ -138,7 +197,7 @@ export class ConversationEventStore {
138
197
  return [...this.promptStates.values()];
139
198
  }
140
199
  lastCursor() {
141
- return this.nextSeq > 1 ? String(this.nextSeq - 1) : undefined;
200
+ return this.lastDurableSeq > 0 ? String(this.lastDurableSeq) : undefined;
142
201
  }
143
202
  close() {
144
203
  if (this.activeFd !== undefined) {
@@ -149,16 +208,27 @@ export class ConversationEventStore {
149
208
  this.activeFd = undefined;
150
209
  }
151
210
  this.listeners.clear();
211
+ this.failureListeners.clear();
152
212
  }
153
213
  // ── recovery ───────────────────────────────────────────────────────────────
154
214
  recover() {
155
215
  const manifest = this.readManifest();
156
- this.segments = manifest?.segments.filter(name => existsSync(join(this.dir, name))) ?? this.discoverSegments();
157
- if (!this.segments.length)
158
- this.segments = this.discoverSegments();
216
+ for (const generation of manifest?.failedGenerations ?? [])
217
+ this.failedGenerations.add(generation);
218
+ // Preserve missing manifest entries: filtering them would erase proof of a gap on reopen.
219
+ this.segments = [...new Set([...(manifest?.segments ?? []), ...this.discoverSegments()])].sort();
159
220
  let maxSeq = 0;
160
221
  for (const segment of this.segments) {
161
222
  for (const event of this.readSegment(segment)) {
223
+ this.firstAvailableSeq ??= event.seq;
224
+ if (event.seq <= maxSeq) {
225
+ // Pre-fix or corrupt ledgers may already contain colliding identities.
226
+ // A cached generation floor must never certify the later duplicate.
227
+ this.failedGenerations.add(event.sessionGeneration);
228
+ this.markDegraded(`segment ${segment} has non-monotonic sequence ${event.seq}`);
229
+ }
230
+ if (!this.generationFloors.has(event.sessionGeneration))
231
+ this.generationFloors.set(event.sessionGeneration, maxSeq);
162
232
  maxSeq = Math.max(maxSeq, event.seq);
163
233
  this.tail.push(event);
164
234
  if (this.tail.length > TAIL_EVENTS)
@@ -167,10 +237,12 @@ export class ConversationEventStore {
167
237
  this.rebuildCommandIndex(event);
168
238
  }
169
239
  }
170
- this.nextSeq = maxSeq + 1;
240
+ this.nextSeq = Math.max(maxSeq + 1, manifest?.nextSeq ?? 1);
241
+ this.lastDurableSeq = maxSeq;
171
242
  if (this.segments.length) {
172
243
  const active = join(this.dir, this.segments.at(-1));
173
244
  this.activeBytes = existsSync(active) ? statSync(active).size : 0;
245
+ this.rotateBeforeAppend = this.segmentRanges.get(this.segments.at(-1))?.damaged ?? true;
174
246
  }
175
247
  this.writeManifest();
176
248
  }
@@ -195,25 +267,36 @@ export class ConversationEventStore {
195
267
  }
196
268
  readSegment(name) {
197
269
  const events = [];
270
+ const range = { damaged: false };
271
+ this.segmentRanges.set(name, range);
198
272
  let raw;
199
273
  try {
200
274
  raw = readFileSync(join(this.dir, name), 'utf8');
201
275
  }
202
276
  catch (error) {
277
+ range.damaged = true;
203
278
  this.markDegraded(`segment ${name} unreadable: ${error.message}`);
204
279
  return events;
205
280
  }
281
+ if (raw.length && !raw.endsWith('\n'))
282
+ range.damaged = true;
206
283
  for (const line of raw.split('\n')) {
207
284
  if (!line.trim())
208
285
  continue;
209
286
  try {
210
287
  const event = JSON.parse(line);
211
- if (event.schemaVersion === 1 && typeof event.seq === 'number')
288
+ if (event.schemaVersion === 1 && Number.isSafeInteger(event.seq) && event.seq > 0) {
212
289
  events.push(event);
213
- else
290
+ range.first ??= event.seq;
291
+ range.last = event.seq;
292
+ }
293
+ else {
294
+ range.damaged = true;
214
295
  this.markDegraded(`segment ${name} holds a record of an unknown schema`);
296
+ }
215
297
  }
216
298
  catch {
299
+ range.damaged = true;
217
300
  // A torn final line after a crash: everything before it stays readable.
218
301
  this.markDegraded(`segment ${name} ends in a torn or corrupt line`);
219
302
  }
@@ -231,6 +314,8 @@ export class ConversationEventStore {
231
314
  bodyDigest,
232
315
  receipt: {
233
316
  commandId: event.commandId,
317
+ ...(typeof event.sessionGeneration === 'string' && event.sessionGeneration
318
+ ? { sessionGeneration: event.sessionGeneration } : {}),
234
319
  promptId: event.promptId,
235
320
  state: 'queued',
236
321
  queuedBehind: payload.queuedBehind ?? 0,
@@ -269,7 +354,7 @@ export class ConversationEventStore {
269
354
  // ── segment management ─────────────────────────────────────────────────────
270
355
  segmentFd(incomingBytes) {
271
356
  if (this.activeFd !== undefined
272
- && this.activeBytes + incomingBytes > this.segmentBytes) {
357
+ && (this.rotateBeforeAppend || this.activeBytes + incomingBytes > this.segmentBytes)) {
273
358
  try {
274
359
  closeSync(this.activeFd);
275
360
  }
@@ -277,11 +362,12 @@ export class ConversationEventStore {
277
362
  this.activeFd = undefined;
278
363
  }
279
364
  if (this.activeFd === undefined) {
280
- const needsNew = !this.segments.length
365
+ const needsNew = this.rotateBeforeAppend || !this.segments.length
281
366
  || this.activeBytes + incomingBytes > this.segmentBytes;
282
367
  if (needsNew) {
283
- const name = `events-${String(this.segments.length + 1).padStart(6, '0')}.jsonl`;
368
+ const name = `events-${String(Number(this.segments.at(-1)?.match(/\d+/)?.[0] ?? 0) + 1).padStart(6, '0')}.jsonl`;
284
369
  this.segments.push(name);
370
+ this.rotateBeforeAppend = false;
285
371
  this.activeBytes = 0;
286
372
  this.writeManifest();
287
373
  }
@@ -295,6 +381,7 @@ export class ConversationEventStore {
295
381
  writeManifest() {
296
382
  const manifest = {
297
383
  schemaVersion: 1, nextSeq: this.nextSeq, segments: this.segments,
384
+ ...(this.failedGenerations.size ? { failedGenerations: [...this.failedGenerations] } : {}),
298
385
  };
299
386
  const tmp = join(this.dir, MANIFEST + '.tmp');
300
387
  try {
@@ -302,21 +389,114 @@ export class ConversationEventStore {
302
389
  renameSync(tmp, join(this.dir, MANIFEST));
303
390
  }
304
391
  catch (error) {
305
- // The manifest is a recovery accelerator, not the source of truth;
306
- // segments alone can always rebuild it.
392
+ // Segments reconstruct durable records, but cannot reconstruct reserved
393
+ // identities or failed-generation latches retained only in the manifest.
307
394
  this.log(`conversation manifest write failed: ${error.message}`);
308
395
  }
309
396
  }
310
397
  // ── reads ──────────────────────────────────────────────────────────────────
311
- firstStoredSeq() {
312
- if (this.tail.length && this.tail[0].seq === 1)
313
- return 1;
314
- for (const segment of this.segments) {
315
- const events = this.readSegment(segment);
316
- if (events.length)
317
- return events[0].seq;
398
+ /** One forward iterator owns the scan; each segment is opened at most once. */
399
+ async *replay(request) {
400
+ const { expectedGeneration, after, through, signal } = request;
401
+ const available = Number(this.lastCursor() ?? 0);
402
+ if (!expectedGeneration || !Number.isSafeInteger(after) || after < 0 ||
403
+ !Number.isSafeInteger(through) || through < after || through > available)
404
+ throw new ReplayCoverageError('invalid cursor or barrier');
405
+ const check = () => {
406
+ signal?.throwIfAborted();
407
+ if (this.unavailable || this.failedGenerations.has(expectedGeneration))
408
+ throw new ReplayCoverageError('generation has non-durable events');
409
+ };
410
+ check();
411
+ const floor = this.generationFloors.get(expectedGeneration);
412
+ if (floor === undefined && through > after)
413
+ throw new ReplayCoverageError('generation is not retained');
414
+ let coveredThrough = Math.max(after, Math.min(floor ?? after, through));
415
+ let events = [];
416
+ let count = 0;
417
+ let bytes = 0;
418
+ const pageSize = Math.min(1000, Math.max(1, request.pageSize ?? 128));
419
+ const segments = this.segments.map(name => ({ name, ...this.segmentRanges.get(name) }));
420
+ const nextFirst = new Array(segments.length);
421
+ let following;
422
+ for (let i = segments.length - 1; i >= 0; i--) {
423
+ nextFirst[i] = following;
424
+ following = segments[i].first ?? following;
425
+ }
426
+ let corruptPrefix = false;
427
+ for (let index = 0; index < segments.length && coveredThrough < through; index++) {
428
+ check();
429
+ const segment = segments[index];
430
+ const followingFirst = nextFirst[index];
431
+ // A verified later old record localizes earlier damage outside the requested range.
432
+ if ((segment.last !== undefined && segment.last <= coveredThrough && !segment.damaged) ||
433
+ (followingFirst !== undefined && followingFirst <= coveredThrough))
434
+ continue;
435
+ try {
436
+ for await (const line of replayLines(join(this.dir, segment.name), signal)) {
437
+ check();
438
+ if (!line.trim())
439
+ continue;
440
+ let event;
441
+ try {
442
+ event = JSON.parse(line);
443
+ }
444
+ catch {
445
+ corruptPrefix = true;
446
+ continue;
447
+ }
448
+ if (event.schemaVersion !== 1 || !Number.isSafeInteger(event.seq) || event.seq < 1) {
449
+ corruptPrefix = true;
450
+ continue;
451
+ }
452
+ if (event.seq <= Math.max(after, floor ?? after)) {
453
+ corruptPrefix = false;
454
+ continue;
455
+ }
456
+ if (corruptPrefix)
457
+ throw new ReplayCoverageError('corrupt record in requested range');
458
+ if (event.seq <= coveredThrough)
459
+ throw new ReplayCoverageError('non-monotonic sequence');
460
+ if (event.seq !== coveredThrough + 1)
461
+ throw new ReplayCoverageError('missing sequence');
462
+ const visible = event.sessionGeneration === expectedGeneration && event.source !== 'agent_replay';
463
+ const eventBytes = visible ? Buffer.byteLength(line) : 0;
464
+ if (count && bytes + eventBytes > 256 * 1024) {
465
+ yield { events, coveredThrough };
466
+ events = [];
467
+ count = 0;
468
+ bytes = 0;
469
+ await yieldTurn();
470
+ check();
471
+ }
472
+ coveredThrough = event.seq;
473
+ count++;
474
+ if (visible) {
475
+ events.push(event);
476
+ bytes += eventBytes;
477
+ }
478
+ if (count >= pageSize || bytes >= 256 * 1024 || coveredThrough === through) {
479
+ yield { events, coveredThrough };
480
+ events = [];
481
+ count = 0;
482
+ bytes = 0;
483
+ await yieldTurn();
484
+ }
485
+ if (coveredThrough === through)
486
+ break;
487
+ }
488
+ }
489
+ catch (error) {
490
+ if (signal?.aborted)
491
+ throw error;
492
+ throw new ReplayCoverageError(error.message);
493
+ }
318
494
  }
319
- return this.tail[0]?.seq;
495
+ check();
496
+ if (coveredThrough !== through)
497
+ throw new ReplayCoverageError('barrier is not retained');
498
+ if (count || (through > after && floor !== undefined && floor >= through))
499
+ yield { events, coveredThrough };
320
500
  }
321
501
  eventsAfter(after, limit) {
322
502
  // Serve from the in-memory tail whenever the range allows it.