@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.
- package/README.md +279 -68
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -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 +19 -0
- package/dist/agent-ours/mcp-endpoint.js +98 -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 +24 -1
- package/dist/agent-ours/service.js +97 -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 +27 -0
- package/dist/application/conversation-history.js +215 -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 +2 -2
- 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/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +17 -3
- package/dist/application/role-repository.js +19 -2
- 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 +61 -17
- package/dist/application/session-control.js +207 -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 +259 -0
- package/dist/application/task-room-service.d.ts +99 -3
- package/dist/application/task-room-service.js +285 -32
- 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 +3 -1
- package/dist/briefing.js +97 -31
- package/dist/build-info.json +9 -4
- package/dist/capabilities.d.ts +9 -1
- package/dist/capabilities.js +13 -0
- package/dist/cli.js +400 -37
- 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 +1 -8
- package/dist/daemon-recovery.js +8 -113
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +207 -19
- 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 +7 -0
- package/dist/fleet-command-audit.js +24 -8
- 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 +8 -34
- package/dist/monitor.js +89 -152
- package/dist/notifications/fleet-producer.d.ts +19 -0
- package/dist/notifications/fleet-producer.js +208 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +10 -0
- package/dist/notifications/target-cleanup.js +63 -0
- package/dist/notifications/task-producer.d.ts +18 -0
- package/dist/notifications/task-producer.js +67 -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 +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +43 -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 +83 -0
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +65 -5
- package/dist/rooms-tasks/close.d.ts +6 -2
- package/dist/rooms-tasks/close.js +246 -37
- 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 +84 -5
- 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 +285 -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-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 -8
- package/dist/rooms-tasks/task-state.js +245 -25
- package/dist/rooms-tasks/types.d.ts +40 -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 +33 -4
- package/dist/runner.js +389 -56
- 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 +213 -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 +206 -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 +32 -0
- package/dist/startup-readiness.js +101 -0
- 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 +76 -0
- package/dist/subscriptions/login.js +207 -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 +140 -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 +19 -33
- 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 +124 -0
- 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 +25 -0
- package/dist/web/conversation-stream.js +231 -0
- package/dist/web/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +238 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +128 -33
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +639 -55
- 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 +580 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +220 -0
- package/docs/acp-files.md +146 -0
- package/docs/conversation-stream.md +190 -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-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 +24 -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 +4 -2
- 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 +12 -11
- package/presets/fleet/roles/Critic.yaml +13 -0
- package/presets/fleet/roles/Developer.yaml +13 -0
- package/presets/fleet/roles/Engineer.yaml +53 -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
package/dist/web/control.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { socketPath as privateSocketPath } from '../socket-path.js';
|
|
1
2
|
import { chmodSync, mkdirSync, rmSync } from 'node:fs';
|
|
2
3
|
import { createConnection, createServer } from 'node:net';
|
|
3
4
|
import { join } from 'node:path';
|
|
4
5
|
import { FleetError, safeLine } from '../application/errors.js';
|
|
5
6
|
import { stateRoot } from '../paths.js';
|
|
6
|
-
export const webControlPath = (dir = join(stateRoot(), 'web')) => join(dir, 'control.sock');
|
|
7
|
+
export const webControlPath = (dir = join(stateRoot(), 'web')) => privateSocketPath(join(dir, 'control.sock'));
|
|
7
8
|
export async function startWebControlServer(options) {
|
|
8
9
|
const dir = options.dir ?? join(stateRoot(), 'web');
|
|
9
10
|
const path = webControlPath(dir);
|
|
@@ -34,13 +35,14 @@ export async function startWebControlServer(options) {
|
|
|
34
35
|
if (used > rateLimit)
|
|
35
36
|
throw new FleetError('rate_limited', 'local web control rate limit exceeded');
|
|
36
37
|
const parsed = JSON.parse(body.slice(0, body.indexOf('\n')));
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
let result;
|
|
39
|
+
if (parsed.command === 'link-device' && options.onLinkDevice)
|
|
40
|
+
result = await options.onLinkDevice();
|
|
39
41
|
else if (parsed.command === 'revoke-all')
|
|
40
42
|
await options.onRevokeAll();
|
|
41
43
|
else
|
|
42
44
|
throw new FleetError('invalid_request', 'unknown local web control command');
|
|
43
|
-
socket.end(JSON.stringify({ ok: true }) + '\n');
|
|
45
|
+
socket.end(JSON.stringify({ ok: true, result }) + '\n');
|
|
44
46
|
}
|
|
45
47
|
catch (error) {
|
|
46
48
|
const code = error instanceof FleetError ? error.code : 'internal';
|
|
@@ -65,7 +67,7 @@ export async function startWebControlServer(options) {
|
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
export async function requestWebControl(command, path = webControlPath(), timeoutMs = 5_000) {
|
|
68
|
-
await new Promise((resolve, reject) => {
|
|
70
|
+
return await new Promise((resolve, reject) => {
|
|
69
71
|
const socket = createConnection(path);
|
|
70
72
|
let response = '';
|
|
71
73
|
let settled = false;
|
|
@@ -73,7 +75,7 @@ export async function requestWebControl(command, path = webControlPath(), timeou
|
|
|
73
75
|
socket.destroy();
|
|
74
76
|
reject(new FleetError('timeout', 'local web control request timed out'));
|
|
75
77
|
}, timeoutMs);
|
|
76
|
-
const finish = (error) => {
|
|
78
|
+
const finish = (error, result) => {
|
|
77
79
|
if (settled)
|
|
78
80
|
return;
|
|
79
81
|
settled = true;
|
|
@@ -81,7 +83,7 @@ export async function requestWebControl(command, path = webControlPath(), timeou
|
|
|
81
83
|
if (error)
|
|
82
84
|
reject(error);
|
|
83
85
|
else
|
|
84
|
-
resolve();
|
|
86
|
+
resolve(result);
|
|
85
87
|
};
|
|
86
88
|
socket.once('connect', () => socket.write(JSON.stringify({ command }) + '\n'));
|
|
87
89
|
socket.on('data', chunk => { response += chunk.toString('utf8'); });
|
|
@@ -91,7 +93,7 @@ export async function requestWebControl(command, path = webControlPath(), timeou
|
|
|
91
93
|
const parsed = JSON.parse(response);
|
|
92
94
|
if (!parsed.ok)
|
|
93
95
|
throw new FleetError(parsed.error?.code === 'rate_limited' ? 'rate_limited' : 'rejected', parsed.error?.message ?? 'local web control request was rejected');
|
|
94
|
-
finish();
|
|
96
|
+
finish(undefined, parsed.result);
|
|
95
97
|
}
|
|
96
98
|
catch (error) {
|
|
97
99
|
finish(error);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Public v1 wire contract. Kept free of Node APIs so browser consumers can share it. */
|
|
2
|
+
export declare const MAX_FRAME_BYTES: number;
|
|
3
|
+
export declare const MAX_ASSEMBLY_BYTES: number;
|
|
4
|
+
export declare const MAX_PENDING_BYTES: number;
|
|
5
|
+
export type ConversationRecordType = 'ready' | 'ledger' | 'state' | 'role' | 'resync' | 'keepalive';
|
|
6
|
+
export interface ConversationStreamRecord {
|
|
7
|
+
version: 1;
|
|
8
|
+
type: ConversationRecordType;
|
|
9
|
+
payload: unknown;
|
|
10
|
+
}
|
|
11
|
+
/** A payload is the logical record; SSE/JSON envelopes do not consume its assembly budget. */
|
|
12
|
+
export declare function encodeConversationRecord(type: ConversationRecordType, payload: unknown, id: string): Generator<string>;
|
|
13
|
+
/** Strict incremental decoder: one assembly, no partial publication, UTF-8 split safe. */
|
|
14
|
+
export declare class ConversationStreamDecoder {
|
|
15
|
+
private readonly publish;
|
|
16
|
+
private decoder;
|
|
17
|
+
private pending;
|
|
18
|
+
private assembly?;
|
|
19
|
+
constructor(publish: (record: ConversationStreamRecord) => void);
|
|
20
|
+
push(bytes: Uint8Array): void;
|
|
21
|
+
finish(): void;
|
|
22
|
+
private accept;
|
|
23
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** Public v1 wire contract. Kept free of Node APIs so browser consumers can share it. */
|
|
2
|
+
export const MAX_FRAME_BYTES = 512 * 1024;
|
|
3
|
+
export const MAX_ASSEMBLY_BYTES = 16 * 1024 * 1024;
|
|
4
|
+
export const MAX_PENDING_BYTES = 8 * 1024 * 1024;
|
|
5
|
+
const utf8 = new TextEncoder();
|
|
6
|
+
const types = new Set(['ready', 'ledger', 'state', 'role', 'resync', 'keepalive']);
|
|
7
|
+
const frame = (value) => `event: conversation\ndata: ${JSON.stringify(value)}\n\n`;
|
|
8
|
+
const identity = (payload) => ({ generation: payload?.sessionGeneration ?? payload?.snapshot?.sessionGeneration,
|
|
9
|
+
revision: payload?.stateRevision ?? payload?.snapshot?.stateRevision ?? payload?.roleRevision });
|
|
10
|
+
/** A payload is the logical record; SSE/JSON envelopes do not consume its assembly budget. */
|
|
11
|
+
export function* encodeConversationRecord(type, payload, id) {
|
|
12
|
+
const json = JSON.stringify(payload), totalBytes = utf8.encode(json).byteLength;
|
|
13
|
+
if (totalBytes > MAX_ASSEMBLY_BYTES)
|
|
14
|
+
throw new Error('conversation record exceeds assembly limit');
|
|
15
|
+
const direct = frame({ version: 1, type, payload });
|
|
16
|
+
if (utf8.encode(direct).byteLength <= MAX_FRAME_BYTES) {
|
|
17
|
+
yield direct;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const position = identity(payload);
|
|
21
|
+
yield frame({ version: 1, type: 'start', id, recordType: type, totalBytes, ...position });
|
|
22
|
+
// At most 48K UTF-16 units: even worst-case JSON escapes fit a 512 KiB frame.
|
|
23
|
+
let index = 0;
|
|
24
|
+
for (let offset = 0; offset < json.length;) {
|
|
25
|
+
let end = Math.min(json.length, offset + 48 * 1024);
|
|
26
|
+
if (end < json.length && /[\uD800-\uDBFF]/.test(json[end - 1]))
|
|
27
|
+
end--;
|
|
28
|
+
yield frame({ version: 1, type: 'chunk', id, index: index++, data: json.slice(offset, end) });
|
|
29
|
+
offset = end;
|
|
30
|
+
}
|
|
31
|
+
yield frame({ version: 1, type: 'commit', id, totalBytes, ...position });
|
|
32
|
+
}
|
|
33
|
+
/** Strict incremental decoder: one assembly, no partial publication, UTF-8 split safe. */
|
|
34
|
+
export class ConversationStreamDecoder {
|
|
35
|
+
publish;
|
|
36
|
+
decoder = new TextDecoder('utf-8', { fatal: true });
|
|
37
|
+
pending = '';
|
|
38
|
+
assembly;
|
|
39
|
+
constructor(publish) {
|
|
40
|
+
this.publish = publish;
|
|
41
|
+
}
|
|
42
|
+
push(bytes) {
|
|
43
|
+
try {
|
|
44
|
+
this.pending += this.decoder.decode(bytes, { stream: true });
|
|
45
|
+
for (;;) {
|
|
46
|
+
const end = this.pending.indexOf('\n\n');
|
|
47
|
+
if (end < 0)
|
|
48
|
+
break;
|
|
49
|
+
const text = this.pending.slice(0, end + 2);
|
|
50
|
+
this.pending = this.pending.slice(end + 2);
|
|
51
|
+
if (utf8.encode(text).byteLength > MAX_FRAME_BYTES)
|
|
52
|
+
throw new Error('frame limit exceeded');
|
|
53
|
+
if (!text.startsWith('event: conversation\ndata: '))
|
|
54
|
+
throw new Error('invalid conversation frame');
|
|
55
|
+
this.accept(JSON.parse(text.slice(26, -2)));
|
|
56
|
+
}
|
|
57
|
+
if (utf8.encode(this.pending).byteLength > MAX_FRAME_BYTES)
|
|
58
|
+
throw new Error('frame limit exceeded');
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
this.pending = '';
|
|
62
|
+
this.assembly = undefined;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
finish() {
|
|
67
|
+
const rest = this.decoder.decode();
|
|
68
|
+
if (rest || this.pending || this.assembly)
|
|
69
|
+
throw new Error('incomplete conversation stream');
|
|
70
|
+
}
|
|
71
|
+
accept(value) {
|
|
72
|
+
if (value?.version !== 1)
|
|
73
|
+
throw new Error('unsupported conversation version');
|
|
74
|
+
if (types.has(value.type)) {
|
|
75
|
+
if (this.assembly)
|
|
76
|
+
throw new Error('overlapping assembly');
|
|
77
|
+
this.publish(value);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (value.type === 'start') {
|
|
81
|
+
if (this.assembly)
|
|
82
|
+
throw new Error('overlapping assembly');
|
|
83
|
+
if (typeof value.id !== 'string' || !types.has(value.recordType) || !Number.isSafeInteger(value.totalBytes) || value.totalBytes < 1 || value.totalBytes > MAX_ASSEMBLY_BYTES)
|
|
84
|
+
throw new Error('invalid assembly length/type');
|
|
85
|
+
this.assembly = { id: value.id, type: value.recordType, total: value.totalBytes, bytes: 0, index: 0, parts: [], generation: value.generation, revision: value.revision };
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const assembly = this.assembly;
|
|
89
|
+
if (!assembly || value.id !== assembly.id)
|
|
90
|
+
throw new Error('assembly identity mismatch');
|
|
91
|
+
if (value.type === 'chunk') {
|
|
92
|
+
if (value.index !== assembly.index++ || typeof value.data !== 'string')
|
|
93
|
+
throw new Error('assembly chunk order');
|
|
94
|
+
if (!value.data.length)
|
|
95
|
+
throw new Error('assembly empty chunk');
|
|
96
|
+
assembly.bytes += utf8.encode(value.data).byteLength;
|
|
97
|
+
if (assembly.bytes > assembly.total)
|
|
98
|
+
throw new Error('assembly length exceeded');
|
|
99
|
+
assembly.parts.push(value.data);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (value.type !== 'commit' || value.totalBytes !== assembly.total || assembly.bytes !== assembly.total)
|
|
103
|
+
throw new Error('assembly length mismatch');
|
|
104
|
+
const payload = JSON.parse(assembly.parts.join('')), position = identity(payload);
|
|
105
|
+
if (value.generation !== assembly.generation || value.revision !== assembly.revision || position.generation !== assembly.generation || position.revision !== assembly.revision)
|
|
106
|
+
throw new Error('assembly revision mismatch');
|
|
107
|
+
this.assembly = undefined;
|
|
108
|
+
this.publish({ version: 1, type: assembly.type, payload });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Writable } from 'node:stream';
|
|
2
|
+
import { type ConversationRecordType } from './conversation-stream-codec.js';
|
|
3
|
+
/** One active logical record; only waiting encoded output consumes the pending budget. */
|
|
4
|
+
export declare class ConversationStreamWriter {
|
|
5
|
+
private readonly output;
|
|
6
|
+
private readonly failed;
|
|
7
|
+
private queue;
|
|
8
|
+
private active?;
|
|
9
|
+
private waiting;
|
|
10
|
+
private ended;
|
|
11
|
+
private sequence;
|
|
12
|
+
pendingBytes: number;
|
|
13
|
+
get busy(): boolean;
|
|
14
|
+
constructor(output: Writable, failed: (reason: string) => void);
|
|
15
|
+
private error;
|
|
16
|
+
private drain;
|
|
17
|
+
send(type: ConversationRecordType, payload: unknown): boolean;
|
|
18
|
+
private flush;
|
|
19
|
+
private fail;
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
import type { FastifyInstance } from 'fastify';
|
|
23
|
+
import type { WebServices } from './server.js';
|
|
24
|
+
import type { WebAuth } from './auth.js';
|
|
25
|
+
export declare function registerConversationStream(app: FastifyInstance, services: WebServices, auth: WebAuth): void;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { encodeConversationRecord, MAX_PENDING_BYTES } from './conversation-stream-codec.js';
|
|
2
|
+
/** One active logical record; only waiting encoded output consumes the pending budget. */
|
|
3
|
+
export class ConversationStreamWriter {
|
|
4
|
+
output;
|
|
5
|
+
failed;
|
|
6
|
+
queue = [];
|
|
7
|
+
active;
|
|
8
|
+
waiting = false;
|
|
9
|
+
ended = false;
|
|
10
|
+
sequence = 0;
|
|
11
|
+
pendingBytes = 0;
|
|
12
|
+
get busy() { return Boolean(this.active); }
|
|
13
|
+
constructor(output, failed) {
|
|
14
|
+
this.output = output;
|
|
15
|
+
this.failed = failed;
|
|
16
|
+
output.on('drain', this.drain);
|
|
17
|
+
output.once('error', this.error);
|
|
18
|
+
}
|
|
19
|
+
error = () => this.fail('transport-error');
|
|
20
|
+
drain = () => { this.waiting = false; this.flush(); };
|
|
21
|
+
send(type, payload) {
|
|
22
|
+
if (this.ended)
|
|
23
|
+
return false;
|
|
24
|
+
const id = String(++this.sequence);
|
|
25
|
+
try {
|
|
26
|
+
if (!this.active) {
|
|
27
|
+
this.active = encodeConversationRecord(type, payload, id);
|
|
28
|
+
this.flush();
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
let bytes = 0;
|
|
32
|
+
for (const frame of encodeConversationRecord(type, payload, id))
|
|
33
|
+
bytes += Buffer.byteLength(frame);
|
|
34
|
+
if (this.pendingBytes + bytes > MAX_PENDING_BYTES) {
|
|
35
|
+
this.fail('output-overflow');
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
this.queue.push({ type, payload, id, bytes });
|
|
39
|
+
this.pendingBytes += bytes;
|
|
40
|
+
}
|
|
41
|
+
return !this.ended;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
this.fail('record-too-large');
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
flush() {
|
|
49
|
+
if (this.ended || this.waiting)
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
while (this.active) {
|
|
53
|
+
const next = this.active.next();
|
|
54
|
+
if (!next.done) {
|
|
55
|
+
if (!this.output.write(next.value)) {
|
|
56
|
+
this.waiting = true;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const queued = this.queue.shift();
|
|
62
|
+
this.active = queued ? encodeConversationRecord(queued.type, queued.payload, queued.id) : undefined;
|
|
63
|
+
if (queued)
|
|
64
|
+
this.pendingBytes -= queued.bytes;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
this.fail('record-too-large');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
fail(reason) {
|
|
73
|
+
if (this.ended)
|
|
74
|
+
return;
|
|
75
|
+
// A resync is only useful at a frame boundary with writable capacity.
|
|
76
|
+
if (!this.waiting && !this.output.destroyed) {
|
|
77
|
+
const frame = encodeConversationRecord('resync', { reason, capability: 'degraded', retry: reason !== 'record-too-large' }, 'terminal').next().value;
|
|
78
|
+
if (frame)
|
|
79
|
+
this.output.write(frame);
|
|
80
|
+
}
|
|
81
|
+
const blocked = this.waiting;
|
|
82
|
+
this.close();
|
|
83
|
+
this.failed(reason);
|
|
84
|
+
if (blocked)
|
|
85
|
+
this.output.destroy();
|
|
86
|
+
}
|
|
87
|
+
close() {
|
|
88
|
+
if (this.ended)
|
|
89
|
+
return;
|
|
90
|
+
this.ended = true;
|
|
91
|
+
this.active = undefined;
|
|
92
|
+
this.queue = [];
|
|
93
|
+
this.pendingBytes = 0;
|
|
94
|
+
this.output.off('drain', this.drain);
|
|
95
|
+
this.output.off('error', this.error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
import { FleetError } from '../application/errors.js';
|
|
99
|
+
export function registerConversationStream(app, services, auth) {
|
|
100
|
+
const connections = new Set();
|
|
101
|
+
app.addHook('preClose', async () => { for (const close of connections)
|
|
102
|
+
close(); });
|
|
103
|
+
app.get('/api/v1/roles/:id/conversation/events', async (request, reply) => {
|
|
104
|
+
const browser = auth.authenticate(request);
|
|
105
|
+
const generation = request.query.expectedGeneration, rawAfter = request.query.after ?? '0', after = Number(rawAfter);
|
|
106
|
+
if (typeof generation !== 'string' || !generation.trim() || generation.length > 512 || !/^\d+$/.test(rawAfter) || !Number.isSafeInteger(after))
|
|
107
|
+
throw new FleetError('invalid_request', 'expectedGeneration and a nonnegative integer after cursor are required');
|
|
108
|
+
if (!services.observeRole)
|
|
109
|
+
throw new FleetError('capability_unavailable', 'Complete conversation observation is unavailable');
|
|
110
|
+
let ended = false, owner, follow;
|
|
111
|
+
let binding, timer;
|
|
112
|
+
let epoch = 0, revision = -1, lifetime, available = false;
|
|
113
|
+
let abort = new AbortController();
|
|
114
|
+
const close = () => {
|
|
115
|
+
if (ended)
|
|
116
|
+
return;
|
|
117
|
+
ended = true;
|
|
118
|
+
epoch++;
|
|
119
|
+
abort.abort();
|
|
120
|
+
if (timer)
|
|
121
|
+
clearInterval(timer);
|
|
122
|
+
binding?.();
|
|
123
|
+
writer.close();
|
|
124
|
+
follow?.close();
|
|
125
|
+
follow = undefined;
|
|
126
|
+
owner?.dispose();
|
|
127
|
+
connections.delete(close);
|
|
128
|
+
reply.raw.end();
|
|
129
|
+
};
|
|
130
|
+
const writer = new ConversationStreamWriter(reply.raw, close);
|
|
131
|
+
const resync = (reason, retry = true) => writer.send('resync', { reason, retry, capability: 'degraded' });
|
|
132
|
+
const valid = () => { if (auth.sessionValid(browser))
|
|
133
|
+
return true; writer.send('resync', { reason: 'auth-expired', retry: browser.rotating ? 'authenticate' : 'reauthenticate', capability: 'auth-expired' }); close(); return false; };
|
|
134
|
+
const stopFollow = () => { epoch++; abort.abort(); abort = new AbortController(); follow?.close(); follow = undefined; };
|
|
135
|
+
const open = async () => {
|
|
136
|
+
const current = ++epoch, signal = abort.signal;
|
|
137
|
+
const active = () => !ended && current === epoch && !signal.aborted;
|
|
138
|
+
try {
|
|
139
|
+
const control = await services.session(request.params.id);
|
|
140
|
+
if (!active() || !valid())
|
|
141
|
+
return;
|
|
142
|
+
const descriptor = await control.describe(owner);
|
|
143
|
+
if (!active() || !valid())
|
|
144
|
+
return;
|
|
145
|
+
if (!control.followConversation || !descriptor.features.includes('conversation_live_complete')) {
|
|
146
|
+
resync('capability-unavailable', false);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const handle = await control.followConversation({ controller: false, expectedGeneration: generation, after, signal, roleObservation: owner,
|
|
150
|
+
onReplay: page => { if (active() && valid()) {
|
|
151
|
+
for (const event of page.events)
|
|
152
|
+
writer.send('ledger', event);
|
|
153
|
+
writer.send('ledger', { coveredThrough: page.coveredThrough });
|
|
154
|
+
} },
|
|
155
|
+
onReady: ready => {
|
|
156
|
+
if (active() && valid()) {
|
|
157
|
+
writer.send('state', ready.snapshot);
|
|
158
|
+
writer.send('ready', { sessionGeneration: ready.snapshot.sessionGeneration, stateRevision: ready.snapshot.stateRevision,
|
|
159
|
+
ledgerBarrier: ready.snapshot.ledgerBarrier, coveredThrough: ready.coveredThrough, capability: 'complete', authorizedUntil: browser.absoluteExpiresAt });
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
onEvent: event => { if (active() && valid()) {
|
|
163
|
+
writer.send('ledger', event);
|
|
164
|
+
writer.send('ledger', { coveredThrough: event.seq });
|
|
165
|
+
} },
|
|
166
|
+
onState: state => { if (active() && valid())
|
|
167
|
+
writer.send('state', state); },
|
|
168
|
+
onClose: reason => { if (active()) {
|
|
169
|
+
epoch++;
|
|
170
|
+
abort.abort();
|
|
171
|
+
follow = undefined;
|
|
172
|
+
resync(reason ?? 'control-closed', !reason?.includes('assembly limit'));
|
|
173
|
+
} },
|
|
174
|
+
});
|
|
175
|
+
if (!active())
|
|
176
|
+
handle.close();
|
|
177
|
+
else
|
|
178
|
+
follow = handle;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
if (active())
|
|
182
|
+
resync('observation-unavailable');
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const changed = (state) => {
|
|
186
|
+
if (ended || state.roleRevision <= revision)
|
|
187
|
+
return;
|
|
188
|
+
revision = state.roleRevision;
|
|
189
|
+
const { stateDir: _private, ...publicState } = state;
|
|
190
|
+
writer.send('role', publicState);
|
|
191
|
+
const replaced = lifetime !== undefined && lifetime !== state.roleLifetime;
|
|
192
|
+
const nextAvailable = state.availability === 'available' && state.health === 'healthy';
|
|
193
|
+
if (replaced || !nextAvailable)
|
|
194
|
+
stopFollow();
|
|
195
|
+
if (state.health !== 'healthy')
|
|
196
|
+
resync(state.failure ?? 'role-observation-unavailable', state.health !== 'unsupported');
|
|
197
|
+
else if (replaced)
|
|
198
|
+
resync('role-replaced');
|
|
199
|
+
const shouldOpen = nextAvailable && (!available || replaced);
|
|
200
|
+
lifetime = state.roleLifetime;
|
|
201
|
+
available = nextAvailable;
|
|
202
|
+
if (shouldOpen)
|
|
203
|
+
void open();
|
|
204
|
+
};
|
|
205
|
+
reply.hijack();
|
|
206
|
+
for (const [name, value] of Object.entries(reply.getHeaders()))
|
|
207
|
+
if (value !== undefined)
|
|
208
|
+
reply.raw.setHeader(name, Array.isArray(value) ? value : String(value));
|
|
209
|
+
reply.raw.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-store', 'X-Accel-Buffering': 'no' });
|
|
210
|
+
reply.raw.flushHeaders();
|
|
211
|
+
reply.raw.once('close', close);
|
|
212
|
+
connections.add(close);
|
|
213
|
+
binding = auth.bindTransport(browser, () => { writer.send('resync', { reason: 'auth-expired', capability: 'auth-expired', retry: browser.rotating ? 'authenticate' : 'reauthenticate' }); close(); });
|
|
214
|
+
try {
|
|
215
|
+
// Factory callbacks can be synchronous; defer handling until the owner can be borrowed.
|
|
216
|
+
owner = services.observeRole(request.params.id, state => queueMicrotask(() => changed(state)));
|
|
217
|
+
if (ended) {
|
|
218
|
+
owner.dispose();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
changed(owner.snapshot());
|
|
222
|
+
timer = setInterval(() => { if (valid())
|
|
223
|
+
writer.send('keepalive', { at: Date.now() }); }, 1_000);
|
|
224
|
+
timer.unref();
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
resync('role-observation-unavailable');
|
|
228
|
+
close();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
@@ -4,6 +4,9 @@ export interface EditableFleetModel {
|
|
|
4
4
|
manifest: Record<string, unknown>;
|
|
5
5
|
agents: Record<string, Record<string, unknown>>;
|
|
6
6
|
agent_templates: Record<string, Record<string, unknown>>;
|
|
7
|
+
roles?: Record<string, Record<string, unknown>>;
|
|
8
|
+
brains?: Record<string, Record<string, unknown>>;
|
|
9
|
+
room_templates?: Record<string, Record<string, unknown>>;
|
|
7
10
|
}
|
|
8
11
|
export interface ConfigReadResult {
|
|
9
12
|
path: string;
|
|
@@ -45,7 +48,7 @@ export declare class FleetConfigService {
|
|
|
45
48
|
private readonly preflight;
|
|
46
49
|
private readonly beforeMutation?;
|
|
47
50
|
constructor(options?: FleetConfigServiceOptions);
|
|
48
|
-
read(): ConfigReadResult;
|
|
51
|
+
read(includeDefinitions?: boolean): ConfigReadResult;
|
|
49
52
|
preview(baseRevision: string, input: unknown): Promise<ConfigPreviewResult>;
|
|
50
53
|
write(baseRevision: string, input: unknown): Promise<ConfigWriteResult>;
|
|
51
54
|
}
|
|
@@ -23,7 +23,7 @@ export class FleetConfigService {
|
|
|
23
23
|
this.preflight = options.preflight ?? (async () => emptyReport());
|
|
24
24
|
this.beforeMutation = options.beforeMutation;
|
|
25
25
|
}
|
|
26
|
-
read() {
|
|
26
|
+
read(includeDefinitions = false) {
|
|
27
27
|
const sources = readSources(this.path);
|
|
28
28
|
try {
|
|
29
29
|
loadConfig(this.path, { yamlMode: 'strict' });
|
|
@@ -31,7 +31,7 @@ export class FleetConfigService {
|
|
|
31
31
|
catch (error) {
|
|
32
32
|
throw safeError(error, sourceSensitiveValues(sources));
|
|
33
33
|
}
|
|
34
|
-
const raw = modelFrom(sources);
|
|
34
|
+
const raw = modelFrom(sources, includeDefinitions);
|
|
35
35
|
const redacted = redactModel(raw);
|
|
36
36
|
return {
|
|
37
37
|
path: basename(this.path), exists: true, firstRun: false,
|
|
@@ -41,7 +41,7 @@ export class FleetConfigService {
|
|
|
41
41
|
async preview(baseRevision, input) {
|
|
42
42
|
const current = readSources(this.path);
|
|
43
43
|
assertRevision(baseRevision, current);
|
|
44
|
-
const currentModel = modelFrom(current);
|
|
44
|
+
const currentModel = modelFrom(current, !!input && typeof input === 'object' && 'roles' in input);
|
|
45
45
|
const next = restoreRedactions(assertModel(input), currentModel, new Set(redactModel(currentModel).paths));
|
|
46
46
|
const proposal = renderProposal(current, next);
|
|
47
47
|
const staged = stageProposal(this.path, current, proposal);
|
|
@@ -63,7 +63,7 @@ export class FleetConfigService {
|
|
|
63
63
|
return withFileLock(`${splitRootFor(this.path)}.web-edit.lock`, async () => {
|
|
64
64
|
const current = readSources(this.path);
|
|
65
65
|
assertRevision(baseRevision, current);
|
|
66
|
-
const currentModel = modelFrom(current);
|
|
66
|
+
const currentModel = modelFrom(current, !!input && typeof input === 'object' && 'roles' in input);
|
|
67
67
|
const next = restoreRedactions(assertModel(input), currentModel, new Set(redactModel(currentModel).paths));
|
|
68
68
|
const proposal = renderProposal(current, next);
|
|
69
69
|
const changed = changedDocuments(current, proposal);
|
|
@@ -105,6 +105,8 @@ export class FleetConfigService {
|
|
|
105
105
|
rmSync(target, { force: true });
|
|
106
106
|
else {
|
|
107
107
|
assertWritableTarget(target, current.documents.has(rel));
|
|
108
|
+
mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
|
|
109
|
+
trustedDirectory(dirname(target));
|
|
108
110
|
replaceFileAtomically(target, contents, 0o600);
|
|
109
111
|
chmodSync(target, 0o600);
|
|
110
112
|
}
|
|
@@ -184,7 +186,7 @@ function readSources(manifest) {
|
|
|
184
186
|
}
|
|
185
187
|
return { manifest, root, documents };
|
|
186
188
|
}
|
|
187
|
-
function modelFrom(sources) {
|
|
189
|
+
function modelFrom(sources, includeDefinitions = false) {
|
|
188
190
|
const manifestKey = basename(sources.manifest);
|
|
189
191
|
const manifest = parseFleetDocument(sources.manifest, sources.documents.get(manifestKey), 'strict').value;
|
|
190
192
|
const agents = {};
|
|
@@ -199,15 +201,24 @@ function modelFrom(sources) {
|
|
|
199
201
|
const id = basename(rel, extname(rel));
|
|
200
202
|
agent_templates[id] = parseFleetDocument(absoluteFor(sources.manifest, rel), source, 'strict').value;
|
|
201
203
|
}
|
|
202
|
-
|
|
204
|
+
const model = { manifest, agents, agent_templates };
|
|
205
|
+
if (includeDefinitions)
|
|
206
|
+
for (const kind of ['roles', 'brains', 'room_templates']) {
|
|
207
|
+
model[kind] = {};
|
|
208
|
+
for (const [rel, source] of sources.documents)
|
|
209
|
+
if (rel.startsWith(kind + '/') && ['.yaml', '.yml'].includes(extname(rel))) {
|
|
210
|
+
model[kind][basename(rel, extname(rel))] = parseFleetDocument(absoluteFor(sources.manifest, rel), source, 'strict').value;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return model;
|
|
203
214
|
}
|
|
204
215
|
function assertModel(value) {
|
|
205
216
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
206
217
|
throw new FleetError('invalid_request', 'model must be an object');
|
|
207
218
|
const input = value;
|
|
208
|
-
if (Object.keys(input).sort().join(',')
|
|
209
|
-
throw new FleetError('invalid_request', 'model
|
|
210
|
-
for (const key of
|
|
219
|
+
if (!['agent_templates,agents,manifest', 'agent_templates,agents,brains,manifest,roles,room_templates'].includes(Object.keys(input).sort().join(',')))
|
|
220
|
+
throw new FleetError('invalid_request', 'model requires manifest, agents, agent_templates and optionally all definition collections');
|
|
221
|
+
for (const key of Object.keys(input))
|
|
211
222
|
if (!input[key] || typeof input[key] !== 'object' || Array.isArray(input[key]))
|
|
212
223
|
throw new FleetError('invalid_request', `${key} must be an object`);
|
|
213
224
|
for (const [id, doc] of Object.entries(input.agents)) {
|
|
@@ -222,6 +233,14 @@ function assertModel(value) {
|
|
|
222
233
|
if (!doc || typeof doc !== 'object' || Array.isArray(doc))
|
|
223
234
|
throw new FleetError('invalid_request', `Agent Template '${id}' must be an object`);
|
|
224
235
|
}
|
|
236
|
+
for (const kind of ['roles', 'brains', 'room_templates']) {
|
|
237
|
+
for (const [id, doc] of Object.entries((input[kind] ?? {}))) {
|
|
238
|
+
if (!ROLE_NAME_RE.test(id))
|
|
239
|
+
throw new FleetError('invalid_request', `invalid definition ID '${id}'`);
|
|
240
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc))
|
|
241
|
+
throw new FleetError('invalid_request', `definition '${id}' must be an object`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
225
244
|
return structuredClone(input);
|
|
226
245
|
}
|
|
227
246
|
function renderProposal(current, model) {
|
|
@@ -240,9 +259,21 @@ function renderProposal(current, model) {
|
|
|
240
259
|
next.set(oldRel ?? join('agent_templates', `${id}.yaml`), oldRel
|
|
241
260
|
? render(current.documents.get(oldRel), doc) : stringify(doc));
|
|
242
261
|
}
|
|
243
|
-
for (const [
|
|
244
|
-
if (
|
|
245
|
-
|
|
262
|
+
for (const kind of ['roles', 'brains', 'room_templates']) {
|
|
263
|
+
if (model[kind] === undefined) {
|
|
264
|
+
for (const [rel, source] of current.documents)
|
|
265
|
+
if (rel.startsWith(kind + '/'))
|
|
266
|
+
next.set(rel, source);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
for (const [rel, source] of current.documents)
|
|
270
|
+
if (rel.startsWith(kind + '/') && !['.yaml', '.yml'].includes(extname(rel)))
|
|
271
|
+
next.set(rel, source);
|
|
272
|
+
for (const [id, doc] of Object.entries(model[kind])) {
|
|
273
|
+
const oldRel = [...current.documents.keys()].find(rel => rel.startsWith(kind + '/') && ['.yaml', '.yml'].includes(extname(rel)) && basename(rel, extname(rel)) === id);
|
|
274
|
+
next.set(oldRel ?? join(kind, `${id}.yaml`), oldRel ? render(current.documents.get(oldRel), doc) : stringify(doc));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
246
277
|
return next;
|
|
247
278
|
}
|
|
248
279
|
function render(source, model) {
|
|
@@ -395,9 +426,9 @@ function sourceDiff(before, after, label, secrets) {
|
|
|
395
426
|
...left.slice(left.length - tail, left.length - tail + trailing).map(x => ` ${x}`), ''].join('\n');
|
|
396
427
|
}
|
|
397
428
|
function proposalDiff(current, proposal) {
|
|
398
|
-
const secrets = sensitiveValues(modelFrom(current));
|
|
429
|
+
const secrets = sensitiveValues(modelFrom(current, true));
|
|
399
430
|
try {
|
|
400
|
-
secrets.push(...sensitiveValues(modelFrom({ ...current, documents: proposal })));
|
|
431
|
+
secrets.push(...sensitiveValues(modelFrom({ ...current, documents: proposal }, true)));
|
|
401
432
|
}
|
|
402
433
|
catch { /* validation reports later */ }
|
|
403
434
|
return [...new Set([...current.documents.keys(), ...proposal.keys()])].sort().map(rel => sourceDiff(current.documents.get(rel) ?? '', proposal.get(rel) ?? '', rel, secrets)).filter(Boolean).join('\n');
|
|
@@ -405,7 +436,7 @@ function proposalDiff(current, proposal) {
|
|
|
405
436
|
function splitLines(source) { return source.replace(/\n$/, '').split('\n'); }
|
|
406
437
|
function restartImpact(before, after) {
|
|
407
438
|
const roles = [...new Set([...Object.keys(before.agents), ...Object.keys(after.agents)])]
|
|
408
|
-
.filter(id => JSON.stringify(before.agents[id]) !== JSON.stringify(after.agents[id])).sort();
|
|
439
|
+
.filter(id => JSON.stringify(before.agents[id]) !== JSON.stringify(after.agents[id]) || JSON.stringify(before.roles) !== JSON.stringify(after.roles) || JSON.stringify(before.brains) !== JSON.stringify(after.brains)).sort();
|
|
409
440
|
const watchdogScheduler = JSON.stringify(before.manifest.watchdogs) !== JSON.stringify(after.manifest.watchdogs);
|
|
410
441
|
const scheduledLoops = JSON.stringify(before.manifest.loops) !== JSON.stringify(after.manifest.loops);
|
|
411
442
|
const required = roles.length > 0 || watchdogScheduler || scheduledLoops;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AuditSink } from './audit.js';
|
|
2
|
+
import { type IncomingMessage } from 'node:http';
|
|
3
|
+
import type { WebAuth } from './auth.js';
|
|
4
|
+
/** Each entry exposes one dedicated backend listener. Endpoint dispatch stays upstream. */
|
|
5
|
+
export interface ServiceTarget {
|
|
6
|
+
prefix: string;
|
|
7
|
+
origin: string;
|
|
8
|
+
upstreamPrefix?: string;
|
|
9
|
+
/** Server-side service credentials; never taken from browser headers. */
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
/** Dedicated credential-authenticated services reject direct browser context. */
|
|
12
|
+
stripBrowserContext?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Service paths (below the prefix) where the account App may open a WebSocket with its device
|
|
15
|
+
* credential. A browser cannot set Authorization on a WebSocket, so only there it is accepted
|
|
16
|
+
* as a subprotocol.
|
|
17
|
+
*/
|
|
18
|
+
deviceSocketPaths?: string[];
|
|
19
|
+
}
|
|
20
|
+
export interface GatewayOptions {
|
|
21
|
+
/** Backend mode is for a separate loopback machine-client listener; browsers are rejected. */
|
|
22
|
+
auth: WebAuth | 'backend';
|
|
23
|
+
fleetOrigin: string;
|
|
24
|
+
services: ServiceTarget[];
|
|
25
|
+
/** Bounded unauthorized-response diagnostics: fixed service/action enums only. */
|
|
26
|
+
audit?: Pick<AuditSink, 'record'>;
|
|
27
|
+
}
|
|
28
|
+
/** Raw transport deliberately runs outside Fastify's JSON parser and body limits. */
|
|
29
|
+
export declare function createPrefixGateway(options: GatewayOptions): {
|
|
30
|
+
server: import("http").Server<typeof IncomingMessage, typeof import("http").ServerResponse>;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
};
|