@ours.network/fleet 1.2.0-nightly.9 → 2.0.1
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.
- package/README.md +280 -68
- package/dist/account-origin.d.ts +5 -0
- package/dist/account-origin.js +31 -0
- package/dist/agent-ours/bridge.js +26 -9
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/file-rpc.d.ts +12 -0
- package/dist/agent-ours/file-rpc.js +18 -4
- package/dist/agent-ours/harness.js +9 -1
- package/dist/agent-ours/mcp-endpoint.d.ts +22 -0
- package/dist/agent-ours/mcp-endpoint.js +101 -25
- package/dist/agent-ours/runtime.d.ts +9 -1
- package/dist/agent-ours/runtime.js +50 -2
- package/dist/agent-ours/service.d.ts +27 -1
- package/dist/agent-ours/service.js +107 -13
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +31 -0
- package/dist/application/conversation-history.js +339 -0
- package/dist/application/conversation-role-observer.d.ts +62 -0
- package/dist/application/conversation-role-observer.js +206 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +3 -3
- package/dist/application/log-service.js +8 -8
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/resource-observer.d.ts +43 -0
- package/dist/application/resource-observer.js +323 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +17 -3
- package/dist/application/role-repository.d.ts +3 -1
- package/dist/application/role-repository.js +89 -54
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +63 -17
- package/dist/application/session-control.js +221 -23
- package/dist/application/supervisor-ours-tools.js +5 -11
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +261 -0
- package/dist/application/task-room-service.d.ts +101 -3
- package/dist/application/task-room-service.js +286 -33
- package/dist/application/types.d.ts +2 -0
- package/dist/atomic-file.d.ts +34 -1
- package/dist/atomic-file.js +161 -18
- package/dist/briefing.d.ts +5 -1
- package/dist/briefing.js +138 -31
- package/dist/build-info.json +10 -5
- package/dist/capabilities.d.ts +9 -1
- package/dist/capabilities.js +13 -0
- package/dist/cli.js +391 -38
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +45 -0
- package/dist/config.js +57 -6
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +4 -4
- package/dist/daemon-recovery.d.ts +14 -9
- package/dist/daemon-recovery.js +124 -115
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +402 -20
- package/dist/doctor.d.ts +4 -1
- package/dist/doctor.js +75 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/file-delivery/reader.d.ts +15 -0
- package/dist/file-delivery/reader.js +63 -0
- package/dist/file-delivery/store.d.ts +15 -0
- package/dist/file-delivery/store.js +124 -0
- package/dist/file-delivery/types.d.ts +19 -0
- package/dist/file-delivery/types.js +1 -0
- package/dist/fleet-command-audit.d.ts +13 -0
- package/dist/fleet-command-audit.js +31 -9
- package/dist/harness/agent-session.d.ts +4 -0
- package/dist/harness/claude-code-session.js +1 -1
- package/dist/harness/claude-code.js +21 -11
- package/dist/harness/claude-runtime.d.ts +12 -0
- package/dist/harness/claude-runtime.js +43 -0
- package/dist/harness/codex-app-server-proxy.d.ts +4 -1
- package/dist/harness/codex-app-server-proxy.js +41 -5
- package/dist/harness/codex-reasoning.d.ts +5 -0
- package/dist/harness/codex-reasoning.js +12 -0
- package/dist/harness/codex-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex-session.js +2 -2
- package/dist/harness/codex.js +24 -12
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +6 -1
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/managed-recovery.d.ts +13 -0
- package/dist/managed-recovery.js +31 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +9 -34
- package/dist/monitor.js +90 -153
- package/dist/notifications/fleet-producer.d.ts +31 -0
- package/dist/notifications/fleet-producer.js +351 -0
- package/dist/notifications/outbox.d.ts +59 -0
- package/dist/notifications/outbox.js +199 -0
- package/dist/notifications/target-cleanup.d.ts +16 -0
- package/dist/notifications/target-cleanup.js +75 -0
- package/dist/notifications/task-producer.d.ts +20 -0
- package/dist/notifications/task-producer.js +100 -0
- package/dist/ops.d.ts +2 -0
- package/dist/ops.js +20 -3
- package/dist/owner-channel/channel.js +17 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +5 -0
- package/dist/preset-migration.js +55 -0
- package/dist/process-incarnation.d.ts +23 -0
- package/dist/process-incarnation.js +42 -0
- package/dist/reminders/manager.d.ts +75 -0
- package/dist/reminders/manager.js +278 -0
- package/dist/reminders/runtime.d.ts +27 -0
- package/dist/reminders/runtime.js +32 -0
- package/dist/reminders/schedule.d.ts +37 -0
- package/dist/reminders/schedule.js +104 -0
- package/dist/reminders/store.d.ts +341 -0
- package/dist/reminders/store.js +762 -0
- package/dist/reminders/tools.d.ts +20 -0
- package/dist/reminders/tools.js +114 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +84 -0
- package/dist/rooms-tasks/archived-absence.d.ts +4 -0
- package/dist/rooms-tasks/archived-absence.js +11 -0
- package/dist/rooms-tasks/cli.js +182 -5
- package/dist/rooms-tasks/close.d.ts +5 -2
- package/dist/rooms-tasks/close.js +224 -36
- package/dist/rooms-tasks/config.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
- package/dist/rooms-tasks/cowork-adapter.js +15 -121
- package/dist/rooms-tasks/cowork-http.js +56 -6
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +66 -10
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +173 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
- package/dist/rooms-tasks/layout-member-retirement.js +90 -0
- package/dist/rooms-tasks/layout-service.d.ts +43 -0
- package/dist/rooms-tasks/layout-service.js +203 -0
- package/dist/rooms-tasks/layout.d.ts +121 -0
- package/dist/rooms-tasks/layout.js +286 -0
- package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
- package/dist/rooms-tasks/legacy-task-member.js +116 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-ownership.d.ts +5 -0
- package/dist/rooms-tasks/member-ownership.js +40 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +3 -0
- package/dist/rooms-tasks/provision.js +101 -49
- package/dist/rooms-tasks/recovery.d.ts +19 -0
- package/dist/rooms-tasks/recovery.js +110 -0
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +47 -8
- package/dist/rooms-tasks/task-review.d.ts +113 -0
- package/dist/rooms-tasks/task-review.js +561 -0
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +51 -5
- package/dist/rooms-tasks/task-state.js +358 -50
- package/dist/rooms-tasks/task-tools.d.ts +35 -0
- package/dist/rooms-tasks/task-tools.js +134 -0
- package/dist/rooms-tasks/types.d.ts +78 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +35 -0
- package/dist/rooms-tasks/workspace.js +325 -0
- package/dist/runner.d.ts +23 -4
- package/dist/runner.js +409 -61
- package/dist/session/acp.d.ts +75 -2
- package/dist/session/acp.js +345 -82
- package/dist/session/arbiter.d.ts +98 -0
- package/dist/session/arbiter.js +272 -3
- package/dist/session/codex-app-server.d.ts +45 -1
- package/dist/session/codex-app-server.js +225 -32
- package/dist/session/control.d.ts +17 -3
- package/dist/session/control.js +219 -23
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-observation.d.ts +25 -0
- package/dist/session/conversation-observation.js +100 -0
- package/dist/session/conversation-replay.d.ts +50 -0
- package/dist/session/conversation-replay.js +203 -0
- package/dist/session/conversation-store.d.ts +15 -1
- package/dist/session/conversation-store.js +213 -26
- package/dist/session/conversation-types.d.ts +57 -4
- package/dist/session/prompt-batch.d.ts +79 -0
- package/dist/session/prompt-batch.js +120 -0
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +42 -1
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.d.ts +5 -1
- package/dist/spawn.js +40 -4
- package/dist/startup-prompt.d.ts +15 -0
- package/dist/startup-prompt.js +71 -0
- package/dist/startup-readiness.d.ts +20 -1
- package/dist/startup-readiness.js +66 -3
- package/dist/startup-telemetry.d.ts +16 -0
- package/dist/startup-telemetry.js +46 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +77 -0
- package/dist/subscriptions/login.js +215 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +143 -0
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +26 -13
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +270 -0
- package/dist/supervisor/registration-refresh.d.ts +45 -0
- package/dist/supervisor/registration-refresh.js +266 -0
- package/dist/supervisor/scope.d.ts +4 -0
- package/dist/supervisor/scope.js +10 -0
- package/dist/supervisor/systemd.d.ts +2 -2
- package/dist/supervisor/systemd.js +18 -37
- package/dist/task-supervision.d.ts +9 -0
- package/dist/task-supervision.js +145 -0
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +23 -8
- package/dist/temp-lifecycle.js +123 -66
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +130 -0
- package/dist/watchdog/query.d.ts +3 -1
- package/dist/watchdog/query.js +2 -1
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +65 -2
- package/dist/web/auth.js +201 -11
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- package/dist/web/conversation-stream-codec.d.ts +23 -0
- package/dist/web/conversation-stream-codec.js +110 -0
- package/dist/web/conversation-stream.d.ts +26 -0
- package/dist/web/conversation-stream.js +239 -0
- package/dist/web/events.d.ts +2 -0
- package/dist/web/events.js +9 -0
- package/dist/web/fleet-config-service.d.ts +7 -1
- package/dist/web/fleet-config-service.js +61 -16
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +248 -0
- package/dist/web/resource-views.d.ts +138 -0
- package/dist/web/resource-views.js +519 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +132 -34
- package/dist/web/server.d.ts +19 -0
- package/dist/web/server.js +893 -71
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +107 -0
- package/dist/web/workspace-devices.js +277 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +83 -0
- package/dist/workspace-enrollment.js +605 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +238 -0
- package/docs/acp-files.md +146 -0
- package/docs/conversation-stream.md +216 -0
- package/docs/fleet-migration.md +54 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/self-reminders.md +374 -0
- package/docs/task-progress-review.md +143 -0
- package/docs/task-workspaces.md +225 -0
- package/docs/validation/managed-cli-permissions.md +421 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/scoped.yaml +21 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +25 -20
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +6 -3
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +21 -13
- package/presets/fleet/roles/Critic.yaml +24 -0
- package/presets/fleet/roles/Developer.yaml +32 -0
- package/presets/fleet/roles/Engineer.yaml +72 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +17 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- 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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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 &&
|
|
288
|
+
if (event.schemaVersion === 1 && Number.isSafeInteger(event.seq) && event.seq > 0) {
|
|
212
289
|
events.push(event);
|
|
213
|
-
|
|
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.
|
|
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
|
-
//
|
|
306
|
-
//
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -324,6 +504,13 @@ export class ConversationEventStore {
|
|
|
324
504
|
return this.tail.filter(event => event.seq > after).slice(0, limit);
|
|
325
505
|
const events = [];
|
|
326
506
|
for (const segment of this.segments) {
|
|
507
|
+
// Recovery/append already indexed these single-writer segments. A page
|
|
508
|
+
// after a resumed generation's floor must not reread every older segment
|
|
509
|
+
// merely because the requested cursor is outside the memory tail.
|
|
510
|
+
// Degraded/unknown ranges retain the conservative recovery read path.
|
|
511
|
+
const range = this.segmentRanges.get(segment);
|
|
512
|
+
if (!this._degraded && range && !range.damaged && range.last !== undefined && range.last <= after)
|
|
513
|
+
continue;
|
|
327
514
|
for (const event of this.readSegment(segment)) {
|
|
328
515
|
if (event.seq <= after)
|
|
329
516
|
continue;
|