@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
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { WorkspaceDeviceAuthError } from './workspace-devices.js';
|
|
3
|
+
import { createServer, request as httpRequest } from 'node:http';
|
|
4
|
+
import { request as httpsRequest } from 'node:https';
|
|
5
|
+
/** The App offers exactly these two subprotocols; only the marker is ever answered or forwarded. */
|
|
6
|
+
const DEVICE_SOCKET_PROTOCOL = 'ours.workspace.v1';
|
|
7
|
+
const DEVICE_SOCKET_BEARER = 'ours.workspace.bearer.';
|
|
8
|
+
/** The device credential a WebSocket offered as a subprotocol; undefined when it offered none of ours. */
|
|
9
|
+
function deviceSocketCredential(req) {
|
|
10
|
+
const offered = String(req.headers['sec-websocket-protocol'] ?? '').split(',').map(value => value.trim()).filter(Boolean);
|
|
11
|
+
if (!offered.some(value => value === DEVICE_SOCKET_PROTOCOL || value.startsWith(DEVICE_SOCKET_BEARER)))
|
|
12
|
+
return undefined;
|
|
13
|
+
const credentials = offered.filter(value => value.startsWith(DEVICE_SOCKET_BEARER));
|
|
14
|
+
const token = credentials[0]?.slice(DEVICE_SOCKET_BEARER.length) ?? '';
|
|
15
|
+
if (offered.length !== 2 || credentials.length !== 1 || !offered.includes(DEVICE_SOCKET_PROTOCOL) || !/^[A-Za-z0-9_.-]{16,256}$/.test(token))
|
|
16
|
+
throw new Error('invalid workspace socket subprotocol');
|
|
17
|
+
return token;
|
|
18
|
+
}
|
|
19
|
+
const hopHeaders = new Set(['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer', 'transfer-encoding', 'upgrade']);
|
|
20
|
+
function headersFor(message) {
|
|
21
|
+
const denied = new Set([...hopHeaders, ...(message.headers.connection ?? '').split(',').map(x => x.trim().toLowerCase())]);
|
|
22
|
+
return Object.fromEntries(Object.entries(message.headers).filter(([key]) => !denied.has(key)));
|
|
23
|
+
}
|
|
24
|
+
/** Raw transport deliberately runs outside Fastify's JSON parser and body limits. */
|
|
25
|
+
export function createPrefixGateway(options) {
|
|
26
|
+
const auth = options.auth === 'backend' ? undefined : options.auth;
|
|
27
|
+
let auditWindow = Date.now(), auditCount = 0;
|
|
28
|
+
const rejected = (requestId, prefix, result) => {
|
|
29
|
+
if (Date.now() - auditWindow >= 60_000) {
|
|
30
|
+
auditWindow = Date.now();
|
|
31
|
+
auditCount = 0;
|
|
32
|
+
}
|
|
33
|
+
if (auditCount++ >= 60)
|
|
34
|
+
return;
|
|
35
|
+
void options.audit?.record({ requestId, action: 'gateway' + prefix.replaceAll('/', '.'), result, errorCode: 'unauthorized' }).catch(() => { });
|
|
36
|
+
};
|
|
37
|
+
const services = options.services.map(target => {
|
|
38
|
+
if (!/^\/[a-z][a-z0-9-]*$/.test(target.prefix) || target.prefix === '/fleet')
|
|
39
|
+
throw new Error('invalid service prefix');
|
|
40
|
+
const origin = new URL(target.origin);
|
|
41
|
+
if (target.upstreamPrefix && !/^\/(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]*$/.test(target.upstreamPrefix))
|
|
42
|
+
throw new Error('invalid upstream service prefix');
|
|
43
|
+
if (!['http:', 'https:'].includes(origin.protocol) || origin.pathname !== '/' || origin.search || origin.hash || origin.username || origin.password)
|
|
44
|
+
throw new Error('service target must be an HTTP origin');
|
|
45
|
+
return { ...target, origin: origin.origin };
|
|
46
|
+
});
|
|
47
|
+
if (new Set(services.map(x => x.prefix)).size !== services.length)
|
|
48
|
+
throw new Error('duplicate service prefix');
|
|
49
|
+
const select = (req) => {
|
|
50
|
+
const raw = req.url ?? '/';
|
|
51
|
+
if (!raw.startsWith('/') || raw.startsWith('//'))
|
|
52
|
+
throw new Error('invalid request target');
|
|
53
|
+
const path = raw.split('?')[0];
|
|
54
|
+
const service = services.find(x => path === x.prefix || path.startsWith(x.prefix + '/'));
|
|
55
|
+
const fleet = path === '/fleet/api' || path.startsWith('/fleet/api/');
|
|
56
|
+
return { service, origin: service?.origin ?? options.fleetOrigin,
|
|
57
|
+
path: service ? (service.upstreamPrefix || '') + (raw.slice(service.prefix.length) || '/').replace(/^\?/, '/?') : fleet ? raw.slice('/fleet'.length) : raw };
|
|
58
|
+
};
|
|
59
|
+
const prepare = (req, upgrade = false) => {
|
|
60
|
+
const route = select(req);
|
|
61
|
+
// The device credential of an App WebSocket: accepted only on an upgrade, from the exact
|
|
62
|
+
// account origin, on a path the service declares, and never beside another credential.
|
|
63
|
+
const socketCredential = upgrade && auth ? deviceSocketCredential(req) : undefined;
|
|
64
|
+
if (socketCredential !== undefined) {
|
|
65
|
+
const path = route.service ? (req.url ?? '/').split('?')[0].slice(route.service.prefix.length) : undefined;
|
|
66
|
+
if (!route.service?.deviceSocketPaths?.includes(path ?? '') || req.headers.authorization !== undefined || req.headers.cookie !== undefined
|
|
67
|
+
|| !req.headers.origin || auth.accountOrigin(req) !== req.headers.origin)
|
|
68
|
+
throw new Error('workspace socket credential is not accepted here');
|
|
69
|
+
}
|
|
70
|
+
// Authentication sees the credential as the bearer it is; the request's own headers are untouched.
|
|
71
|
+
const authHeaders = socketCredential !== undefined ? { ...req.headers, authorization: `Bearer ${socketCredential}` } : req.headers;
|
|
72
|
+
const request = { headers: authHeaders, method: req.method, url: route.path };
|
|
73
|
+
if (!auth && (req.headers.origin || req.headers['sec-fetch-site'] || !route.service))
|
|
74
|
+
throw new Error('machine-client listener rejects browser requests');
|
|
75
|
+
auth?.validateBoundary(request, upgrade);
|
|
76
|
+
const session = route.service && auth ? auth.authenticate(request, !upgrade && !['GET', 'HEAD', 'OPTIONS'].includes(req.method ?? 'GET')) : undefined;
|
|
77
|
+
const headers = headersFor(req);
|
|
78
|
+
// Forward only the explicit browser context, never an attacker-selected forwarding chain.
|
|
79
|
+
for (const key of Object.keys(headers))
|
|
80
|
+
if (key.startsWith('x-forwarded-') || key === 'forwarded')
|
|
81
|
+
delete headers[key];
|
|
82
|
+
delete headers['x-ours-workspace-auth'];
|
|
83
|
+
delete headers['x-ours-request-id'];
|
|
84
|
+
if (route.service) {
|
|
85
|
+
if (auth)
|
|
86
|
+
for (const name of ['cookie', 'authorization', 'x-csrf-token', 'x-ours-api-token'])
|
|
87
|
+
delete headers[name];
|
|
88
|
+
// The server gateway lets this header choose a producer credential; only machine producers send it directly there.
|
|
89
|
+
delete headers['x-ours-notifications-producer'];
|
|
90
|
+
if (route.service.stripBrowserContext) {
|
|
91
|
+
for (const name of ['origin', 'sec-fetch-site', 'sec-fetch-mode', 'sec-fetch-dest', 'sec-fetch-user'])
|
|
92
|
+
delete headers[name];
|
|
93
|
+
}
|
|
94
|
+
Object.assign(headers, route.service.headers);
|
|
95
|
+
headers.host = new URL(route.origin).host;
|
|
96
|
+
}
|
|
97
|
+
if (upgrade) {
|
|
98
|
+
headers.connection = 'Upgrade';
|
|
99
|
+
headers.upgrade = 'websocket';
|
|
100
|
+
}
|
|
101
|
+
// The credential subprotocol ends here: the service is not offered it, on any request.
|
|
102
|
+
if (socketCredential !== undefined || String(headers['sec-websocket-protocol'] ?? '').includes(DEVICE_SOCKET_BEARER))
|
|
103
|
+
delete headers['sec-websocket-protocol'];
|
|
104
|
+
return { ...route, headers, session, authHeaders, deviceSocket: socketCredential !== undefined };
|
|
105
|
+
};
|
|
106
|
+
const track = (authHeaders, session, close) => {
|
|
107
|
+
if (!session)
|
|
108
|
+
return () => { };
|
|
109
|
+
// The session authenticated for this request, with its own deadline, not whatever is current later.
|
|
110
|
+
const unbind = auth.bindTransport(session, close);
|
|
111
|
+
const timer = setInterval(() => { try {
|
|
112
|
+
auth.authenticate({ headers: authHeaders });
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
close();
|
|
116
|
+
} }, 30_000);
|
|
117
|
+
timer.unref();
|
|
118
|
+
return () => { clearInterval(timer); unbind(); };
|
|
119
|
+
};
|
|
120
|
+
// The account App calls workspace services directly from its own origin with a device
|
|
121
|
+
// credential. Only that exact configured origin is answered; the reply grants nothing by
|
|
122
|
+
// itself, every request is still authenticated below.
|
|
123
|
+
const accountCors = (req) => {
|
|
124
|
+
const origin = auth?.accountOrigin(req);
|
|
125
|
+
return origin ? { 'access-control-allow-origin': origin, vary: 'Origin',
|
|
126
|
+
'access-control-expose-headers': 'Accept-Ranges, Content-Disposition, Content-Length, Content-Range, Content-Type, ETag, X-Ours-Workspace-Auth, X-Ours-Request-Id' } : {};
|
|
127
|
+
};
|
|
128
|
+
const server = createServer((req, res) => {
|
|
129
|
+
const cors = accountCors(req);
|
|
130
|
+
const requestId = randomUUID();
|
|
131
|
+
let route;
|
|
132
|
+
try {
|
|
133
|
+
if (req.method === 'OPTIONS' && cors.vary && req.headers['access-control-request-method'] && select(req).service) {
|
|
134
|
+
auth.validateBoundary({ headers: req.headers, method: req.method, url: req.url }, false);
|
|
135
|
+
res.writeHead(204, { ...cors, 'cache-control': 'no-store', 'access-control-max-age': '600',
|
|
136
|
+
'access-control-allow-methods': 'GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
137
|
+
'access-control-allow-headers': 'Authorization, Content-Type, Idempotency-Key, If-None-Match, Range, X-CSRF-Token, X-Ours-Messenger-CSRF, X-Voice-Duration' });
|
|
138
|
+
res.end();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
route = prepare(req);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const code = error.code;
|
|
145
|
+
const deviceRejected = error instanceof WorkspaceDeviceAuthError;
|
|
146
|
+
if (code === 'unauthorized')
|
|
147
|
+
rejected(requestId, '/auth', deviceRejected ? 'device_rejected' : 'gateway_rejected');
|
|
148
|
+
res.writeHead(code === 'unauthorized' ? 401 : 403, { ...cors, 'content-type': 'application/json', 'cache-control': 'no-store',
|
|
149
|
+
...(code === 'unauthorized' ? { 'x-ours-request-id': requestId } : {}),
|
|
150
|
+
...(deviceRejected ? { 'x-ours-workspace-auth': 'rejected' } : {}) });
|
|
151
|
+
res.end(JSON.stringify({ error: { code: code ?? 'forbidden', message: error.message } }));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const upstream = (route.origin.startsWith('https:') ? httpsRequest : httpRequest)(route.origin, {
|
|
155
|
+
method: req.method, path: route.path, headers: route.headers,
|
|
156
|
+
}, response => {
|
|
157
|
+
const headers = headersFor(response);
|
|
158
|
+
if (route.service) {
|
|
159
|
+
delete headers['set-cookie'];
|
|
160
|
+
// Only Fleet may assert device rejection; never trust a service's marker or correlation id.
|
|
161
|
+
delete headers['x-ours-workspace-auth'];
|
|
162
|
+
delete headers['x-ours-request-id'];
|
|
163
|
+
if (response.statusCode === 401) {
|
|
164
|
+
headers['x-ours-request-id'] = requestId;
|
|
165
|
+
headers['x-ours-workspace-auth'] = 'service';
|
|
166
|
+
rejected(requestId, route.service.prefix, 'upstream_401');
|
|
167
|
+
}
|
|
168
|
+
for (const key of Object.keys(headers))
|
|
169
|
+
if (key.startsWith('access-control-'))
|
|
170
|
+
delete headers[key];
|
|
171
|
+
Object.assign(headers, cors);
|
|
172
|
+
}
|
|
173
|
+
res.writeHead(response.statusCode ?? 502, headers);
|
|
174
|
+
// Streaming headers, including exact-origin CORS and upstream errors,
|
|
175
|
+
// must reach the client before the first event or body byte arrives.
|
|
176
|
+
res.flushHeaders();
|
|
177
|
+
response.on('error', () => res.destroy());
|
|
178
|
+
response.on('aborted', () => res.destroy());
|
|
179
|
+
response.on('close', () => { if (!response.complete)
|
|
180
|
+
res.destroy(); });
|
|
181
|
+
response.pipe(res);
|
|
182
|
+
res.on('close', () => response.destroy());
|
|
183
|
+
});
|
|
184
|
+
upstream.on('error', () => {
|
|
185
|
+
if (!res.headersSent)
|
|
186
|
+
res.writeHead(502, { ...(route.service ? cors : {}), 'content-type': 'application/json' });
|
|
187
|
+
res.end(JSON.stringify({ error: { code: 'upstream_unavailable', message: 'Service is unavailable' } }));
|
|
188
|
+
});
|
|
189
|
+
const untrack = track(route.authHeaders, route.session, () => { upstream.destroy(); res.destroy(); });
|
|
190
|
+
res.on('close', untrack);
|
|
191
|
+
req.on('aborted', () => upstream.destroy());
|
|
192
|
+
res.on('close', () => upstream.destroy());
|
|
193
|
+
req.pipe(upstream);
|
|
194
|
+
});
|
|
195
|
+
const sockets = new Set();
|
|
196
|
+
server.on('connection', socket => { sockets.add(socket); socket.on('close', () => sockets.delete(socket)); });
|
|
197
|
+
server.on('upgrade', (req, socket, head) => {
|
|
198
|
+
let route;
|
|
199
|
+
try {
|
|
200
|
+
route = prepare(req, true);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
socket.end('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const upstream = (route.origin.startsWith('https:') ? httpsRequest : httpRequest)(route.origin, {
|
|
207
|
+
method: req.method, path: route.path, headers: route.headers,
|
|
208
|
+
});
|
|
209
|
+
upstream.on('upgrade', (response, peer, upstreamHead) => {
|
|
210
|
+
const headers = headersFor(response);
|
|
211
|
+
headers.connection = 'Upgrade';
|
|
212
|
+
headers.upgrade = 'websocket';
|
|
213
|
+
delete headers['set-cookie'];
|
|
214
|
+
// A browser that offered subprotocols needs one answered; only the marker, never the credential.
|
|
215
|
+
if (route.deviceSocket)
|
|
216
|
+
headers['sec-websocket-protocol'] = DEVICE_SOCKET_PROTOCOL;
|
|
217
|
+
socket.write(`HTTP/1.1 101 Switching Protocols\r\n${Object.entries(headers).flatMap(([k, v]) => (Array.isArray(v) ? v : [v]).map(value => `${k}: ${value}\r\n`)).join('')}\r\n`);
|
|
218
|
+
if (head.length)
|
|
219
|
+
peer.write(head);
|
|
220
|
+
if (upstreamHead.length)
|
|
221
|
+
socket.write(upstreamHead);
|
|
222
|
+
const untrack = track(route.authHeaders, route.session, () => { peer.destroy(); socket.destroy(); });
|
|
223
|
+
socket.on('close', untrack);
|
|
224
|
+
peer.pipe(socket);
|
|
225
|
+
socket.pipe(peer);
|
|
226
|
+
peer.on('error', () => socket.destroy());
|
|
227
|
+
socket.on('error', () => peer.destroy());
|
|
228
|
+
socket.on('close', () => peer.destroy());
|
|
229
|
+
peer.on('close', () => socket.destroy());
|
|
230
|
+
});
|
|
231
|
+
upstream.on('response', response => { socket.end(`HTTP/1.1 ${response.statusCode ?? 502} Rejected\r\nConnection: close\r\n\r\n`); response.destroy(); });
|
|
232
|
+
upstream.on('error', () => socket.destroy());
|
|
233
|
+
socket.on('close', () => upstream.destroy());
|
|
234
|
+
upstream.end();
|
|
235
|
+
});
|
|
236
|
+
return { server, async close() { for (const socket of sockets)
|
|
237
|
+
socket.destroy(); await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); } };
|
|
238
|
+
}
|
package/dist/web/runtime.d.ts
CHANGED
|
@@ -2,8 +2,10 @@ import { type WebServer } from './server.js';
|
|
|
2
2
|
import { type WebAccessConfig } from './access.js';
|
|
3
3
|
export interface StartWebOptions {
|
|
4
4
|
configPath?: string;
|
|
5
|
+
/** Separate console state for an independently launched gateway. */
|
|
6
|
+
webStateDir?: string;
|
|
7
|
+
control?: boolean;
|
|
5
8
|
port?: number;
|
|
6
|
-
open?: boolean;
|
|
7
9
|
binPath: string;
|
|
8
10
|
log?(line: string): void;
|
|
9
11
|
bind?: string;
|
package/dist/web/runtime.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ConversationRoleObserver } from '../application/conversation-role-observer.js';
|
|
2
|
+
import { replaceFileAtomically } from '../atomic-file.js';
|
|
3
|
+
import { validateAccountOrigin } from '../account-origin.js';
|
|
4
|
+
import { startWorkspaceTunnel } from './workspace-tunnel.js';
|
|
5
|
+
import { createPrefixGateway } from './prefix-gateway.js';
|
|
6
|
+
import { readClientProfile } from '../client-profile.js';
|
|
7
|
+
import { readPrivateFile } from '@ours.network/sdk/connector';
|
|
8
|
+
import { LayoutBindingGrants } from '../rooms-tasks/layout-binding-grants.js';
|
|
9
|
+
import { RoomLayoutService } from '../rooms-tasks/layout-service.js';
|
|
10
|
+
import { FleetNotificationProducer } from '../notifications/fleet-producer.js';
|
|
11
|
+
import { producerConfig } from '../notifications/outbox.js';
|
|
12
|
+
import { SubscriptionService } from '../subscriptions/service.js';
|
|
13
|
+
import { createCodexModelDiscovery } from '../application/codex-models.js';
|
|
14
|
+
import { existsSync, mkdirSync, realpathSync, readFileSync } from 'node:fs';
|
|
3
15
|
import { resolve } from 'node:path';
|
|
4
16
|
import { loadConfig } from '../config.js';
|
|
5
17
|
import { RoleRepository } from '../application/role-repository.js';
|
|
@@ -13,7 +25,8 @@ import { FleetError } from '../application/errors.js';
|
|
|
13
25
|
import { controlRequest, controlSocketPath } from '../session/control.js';
|
|
14
26
|
import { pickBackend } from '../supervisor/index.js';
|
|
15
27
|
import { realExec } from '../exec.js';
|
|
16
|
-
import { home, stateRoot } from '../paths.js';
|
|
28
|
+
import { home, stateRoot, agentsRoot, tmpRoot, logsRoot } from '../paths.js';
|
|
29
|
+
import { executeInitAnswers, publishSetup } from '../init-wizard.js';
|
|
17
30
|
import { AuditSink } from './audit.js';
|
|
18
31
|
import { FleetEventBus } from './events.js';
|
|
19
32
|
import { buildWebServer } from './server.js';
|
|
@@ -21,8 +34,11 @@ import { FleetConfigService } from './fleet-config-service.js';
|
|
|
21
34
|
import { mergeTopology } from './topology-model.js';
|
|
22
35
|
import { TopologyDraftStore } from './topology-draft-store.js';
|
|
23
36
|
import { TopologyPromoteService } from './topology-promote.js';
|
|
37
|
+
import { RoomLayoutDefinitions } from '../application/room-layout-definitions.js';
|
|
38
|
+
import { PresetProvenance } from '../application/preset-provenance.js';
|
|
24
39
|
import { doctor } from '../doctor.js';
|
|
25
40
|
import { acquireWebServerLock } from './lock.js';
|
|
41
|
+
import { WorkspaceDeviceStore } from './workspace-devices.js';
|
|
26
42
|
import { TrustedDeviceStore } from './device-store.js';
|
|
27
43
|
import { WebAuth } from './auth.js';
|
|
28
44
|
import { startWebControlServer } from './control.js';
|
|
@@ -47,19 +63,31 @@ function cachedConfigProvider(configPath) {
|
|
|
47
63
|
};
|
|
48
64
|
}
|
|
49
65
|
export async function startWebConsole(options) {
|
|
50
|
-
const
|
|
66
|
+
const notificationConfig = producerConfig(process.env, undefined, options.log);
|
|
67
|
+
let requestedPort = options.port ?? 49_271;
|
|
51
68
|
if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65_535)
|
|
52
69
|
throw new FleetError('invalid_request', 'port must be between 0 and 65535');
|
|
53
|
-
const
|
|
54
|
-
const
|
|
70
|
+
const webDir = options.webStateDir ?? resolve(stateRoot(), 'web');
|
|
71
|
+
const lock = acquireWebServerLock(webDir);
|
|
55
72
|
const bind = options.bind ?? '127.0.0.1';
|
|
56
73
|
const publicOrigin = options.publicOrigin ? validatePublicOrigin(options.publicOrigin) : undefined;
|
|
74
|
+
const workspaceGateway = Boolean(publicOrigin && existsSync(resolve(stateRoot(), 'workspace', 'binding.json')));
|
|
57
75
|
if (!isLoopback(bind) && !publicOrigin) {
|
|
58
76
|
lock.release();
|
|
59
77
|
throw new FleetError('forbidden', 'a non-loopback bind requires an explicit --public-origin');
|
|
60
78
|
}
|
|
61
79
|
const access = options.access ?? new WebAccessStore(webDir).read();
|
|
62
|
-
const
|
|
80
|
+
const bindingFile = resolve(stateRoot(), 'workspace', 'binding.json');
|
|
81
|
+
let appOrigin = 'https://app.ours.network';
|
|
82
|
+
try {
|
|
83
|
+
if (existsSync(bindingFile))
|
|
84
|
+
appOrigin = validateAccountOrigin(JSON.parse(readFileSync(bindingFile, 'utf8')).appOrigin ?? appOrigin);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
lock.release();
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
const auth = new WebAuth(publicOrigin?.origin ?? `http://127.0.0.1:${requestedPort}`, publicOrigin?.host ?? `127.0.0.1:${requestedPort}`, Date.now, new TrustedDeviceStore(webDir), access, new WorkspaceDeviceStore(webDir), appOrigin);
|
|
63
91
|
const backend = pickBackend();
|
|
64
92
|
const repository = new RoleRepository({
|
|
65
93
|
configPath: options.configPath,
|
|
@@ -83,6 +111,13 @@ export async function startWebConsole(options) {
|
|
|
83
111
|
},
|
|
84
112
|
});
|
|
85
113
|
const events = new FleetEventBus();
|
|
114
|
+
const roleMutations = new Set();
|
|
115
|
+
const signalRoleMutation = () => { for (const listener of roleMutations)
|
|
116
|
+
listener(); };
|
|
117
|
+
const observeRole = (roleId, listener) => new ConversationRoleObserver({
|
|
118
|
+
roleId, configPath: options.configPath,
|
|
119
|
+
subscribeMutations: changed => { roleMutations.add(changed); return () => { roleMutations.delete(changed); }; },
|
|
120
|
+
}, listener);
|
|
86
121
|
const audit = new AuditSink();
|
|
87
122
|
const watchdogConfigProvider = cachedConfigProvider(options.configPath);
|
|
88
123
|
const log = options.log ?? (() => { });
|
|
@@ -122,6 +157,7 @@ export async function startWebConsole(options) {
|
|
|
122
157
|
? 'ready' : detail?.status.overall === 'attention' ? 'attention' : 'unknown';
|
|
123
158
|
},
|
|
124
159
|
onProgress: action => {
|
|
160
|
+
signalRoleMutation();
|
|
125
161
|
events.publish('creation.changed', action, action.roleId);
|
|
126
162
|
void audit.record({
|
|
127
163
|
roleId: action.roleId, action: `creation.${action.state}`,
|
|
@@ -137,6 +173,7 @@ export async function startWebConsole(options) {
|
|
|
137
173
|
repository, ops, configPath: options.configPath,
|
|
138
174
|
status: async (roleId) => (await query.detail(roleId)).status,
|
|
139
175
|
onProgress: receipt => {
|
|
176
|
+
signalRoleMutation();
|
|
140
177
|
events.publish('action.changed', receipt, receipt.roleId);
|
|
141
178
|
void audit.record({
|
|
142
179
|
roleId: receipt.roleId, action: `lifecycle.${receipt.action}`,
|
|
@@ -158,9 +195,30 @@ export async function startWebConsole(options) {
|
|
|
158
195
|
let server;
|
|
159
196
|
try {
|
|
160
197
|
server = await buildWebServer({
|
|
161
|
-
query, repository, logs, commands, creation, removal, audit, events, watchdogs, configuration,
|
|
162
|
-
taskRooms: new TaskRoomApplicationService(options.configPath),
|
|
198
|
+
query, repository, logs, commands, creation, removal, audit, events, watchdogs, configuration, observeRole,
|
|
199
|
+
taskRooms: new TaskRoomApplicationService(options.configPath, { binPath: () => options.binPath }),
|
|
200
|
+
roomLayouts: new RoomLayoutDefinitions(options.configPath),
|
|
201
|
+
layoutBindings: new LayoutBindingGrants(new RoomLayoutService(options.configPath).supervisor()),
|
|
202
|
+
presetProvenance: new PresetProvenance(options.configPath),
|
|
163
203
|
topology: readTopology, topologyDrafts, topologyPromote,
|
|
204
|
+
// Same boundary as `ours-fleet init --settings`: existing files are preserved, missing defaults are added.
|
|
205
|
+
onboardingSetup: async (answers) => {
|
|
206
|
+
await executeInitAnswers(answers, configuration.path, {
|
|
207
|
+
async hostSetup() {
|
|
208
|
+
for (const directory of [agentsRoot(), tmpRoot(), logsRoot()])
|
|
209
|
+
mkdirSync(directory, { recursive: true });
|
|
210
|
+
await pickBackend().init(options.binPath);
|
|
211
|
+
},
|
|
212
|
+
publish: publishSetup,
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
codexModels: createCodexModelDiscovery(),
|
|
216
|
+
subscriptions: new SubscriptionService({
|
|
217
|
+
agents: async () => (await query.list(true)).map(({ role, status }) => ({
|
|
218
|
+
roleId: role.id, stateDir: repository.stateDir(role), running: status.supervisor.liveness === 'running',
|
|
219
|
+
})),
|
|
220
|
+
onChange: () => events.publish('subscription.changed', {}),
|
|
221
|
+
}),
|
|
164
222
|
async session(roleId) {
|
|
165
223
|
const role = await repository.get(roleId);
|
|
166
224
|
if (!role)
|
|
@@ -170,7 +228,7 @@ export async function startWebConsole(options) {
|
|
|
170
228
|
const dir = repository.stateDir(role);
|
|
171
229
|
if (!dir)
|
|
172
230
|
throw new FleetError('control_unavailable', 'role state directory is unavailable');
|
|
173
|
-
return new RoleSessionControlAdapter(dir);
|
|
231
|
+
return new RoleSessionControlAdapter(dir, undefined, undefined, listener => observeRole(roleId, listener));
|
|
174
232
|
}
|
|
175
233
|
throw new FleetError('capability_unavailable', 'role session backend is unavailable');
|
|
176
234
|
},
|
|
@@ -182,7 +240,7 @@ export async function startWebConsole(options) {
|
|
|
182
240
|
}
|
|
183
241
|
let address;
|
|
184
242
|
try {
|
|
185
|
-
address = await server.app.listen({ host: bind, port: requestedPort });
|
|
243
|
+
address = await server.app.listen({ host: workspaceGateway ? '127.0.0.1' : bind, port: workspaceGateway ? 0 : requestedPort });
|
|
186
244
|
}
|
|
187
245
|
catch (error) {
|
|
188
246
|
await server.close();
|
|
@@ -193,39 +251,84 @@ export async function startWebConsole(options) {
|
|
|
193
251
|
const localHost = `127.0.0.1:${actual.port}`;
|
|
194
252
|
const browserOrigin = publicOrigin?.origin ?? `http://${localHost}`;
|
|
195
253
|
const browserHost = publicOrigin?.host ?? localHost;
|
|
254
|
+
let gateway;
|
|
255
|
+
if (workspaceGateway)
|
|
256
|
+
try {
|
|
257
|
+
const profile = readClientProfile(), provider = new URL(profile.serverUrl), credential = readPrivateFile(profile.credentialPath, 4096).toString('utf8').trim();
|
|
258
|
+
const basePath = provider.pathname.replace(/\/$/, '');
|
|
259
|
+
const services = ['daemon', 'cowork', 'messenger', 'notifications'].map(name => ({ prefix: '/' + name, origin: provider.origin, upstreamPrefix: basePath + '/' + name, stripBrowserContext: true, headers: { 'X-Ours-Api-Token': credential, ...(name === 'messenger' ? { Origin: provider.origin, 'X-Ours-Messenger-CSRF': '1' } : name === 'cowork' ? { Origin: provider.origin } : {}) },
|
|
260
|
+
// The App reports a visible page to the notification service, so an open app is not sent a push.
|
|
261
|
+
...(name === 'notifications' ? { deviceSocketPaths: ['/api/v1/presence'] } : {}) }));
|
|
262
|
+
gateway = createPrefixGateway({ auth: server.auth, fleetOrigin: address, services, audit });
|
|
263
|
+
const listen = (port) => new Promise((resolve, reject) => { gateway.server.once('error', reject); gateway.server.listen(port, bind, () => { gateway.server.off('error', reject); resolve(); }); });
|
|
264
|
+
try {
|
|
265
|
+
await listen(requestedPort);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
const selection = resolve(webDir, 'port-selection.json');
|
|
269
|
+
if (error.code !== 'EADDRINUSE' || !existsSync(selection) || JSON.parse(readPrivateFile(selection, 4096).toString()).expiresAt <= Date.now())
|
|
270
|
+
throw error;
|
|
271
|
+
await listen(0);
|
|
272
|
+
}
|
|
273
|
+
const bound = gateway.server.address();
|
|
274
|
+
if (!bound || typeof bound === 'string')
|
|
275
|
+
throw Error('Workspace listener did not bind');
|
|
276
|
+
requestedPort = bound.port;
|
|
277
|
+
replaceFileAtomically(resolve(webDir, 'workspace-port.json'), JSON.stringify({ port: requestedPort }) + '\n', 0o600);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
await gateway?.close().catch(() => { });
|
|
281
|
+
await server.close();
|
|
282
|
+
lock.release();
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
196
285
|
server.auth.setBoundary(browserOrigin, browserHost, publicOrigin ? {
|
|
197
286
|
// nginx's safe default uses the loopback upstream as Host. The declared
|
|
198
287
|
// browser Origin remains mandatory for auth/mutations and WebSocket hello,
|
|
199
288
|
// so operators do not need a fragile Host-rewrite incantation.
|
|
200
|
-
hosts: [localHost, `localhost:${actual.port}`],
|
|
289
|
+
hosts: [localHost, `localhost:${actual.port}`, ...(workspaceGateway ? [`127.0.0.1:${requestedPort}`, `localhost:${requestedPort}`] : [])],
|
|
201
290
|
} : {
|
|
202
291
|
hosts: [`localhost:${actual.port}`], origins: [`http://localhost:${actual.port}`],
|
|
203
292
|
});
|
|
204
293
|
let control;
|
|
205
294
|
try {
|
|
206
|
-
control
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
},
|
|
213
|
-
onRevokeAll() { server.auth.revokeAllTrustedDevices(); },
|
|
214
|
-
});
|
|
295
|
+
if (options.control !== false)
|
|
296
|
+
control = await startWebControlServer({
|
|
297
|
+
dir: webDir,
|
|
298
|
+
onLinkDevice() { return { version: 1, origin: browserOrigin, ...server.auth.workspaceEnrollment() }; },
|
|
299
|
+
onRevokeAll() { server.auth.revokeAllTrustedDevices(); },
|
|
300
|
+
});
|
|
215
301
|
}
|
|
216
302
|
catch (error) {
|
|
303
|
+
await gateway?.close();
|
|
217
304
|
await server.close();
|
|
218
305
|
lock.release();
|
|
219
306
|
throw error;
|
|
220
307
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
308
|
+
let stopTunnel;
|
|
309
|
+
try {
|
|
310
|
+
stopTunnel = startWorkspaceTunnel(browserOrigin);
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
try {
|
|
314
|
+
await control?.close();
|
|
315
|
+
await gateway?.close();
|
|
316
|
+
await server.close();
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
lock.release();
|
|
320
|
+
}
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
const notifications = notificationConfig ? new FleetNotificationProducer([agentsRoot(), tmpRoot()], resolve(webDir, 'notification-outboxes'), notificationConfig, options.log) : undefined;
|
|
224
324
|
return {
|
|
225
325
|
...server, address: browserOrigin,
|
|
226
326
|
async close() {
|
|
227
327
|
try {
|
|
228
|
-
await
|
|
328
|
+
await stopTunnel();
|
|
329
|
+
await notifications?.close();
|
|
330
|
+
await control?.close();
|
|
331
|
+
await gateway?.close();
|
|
229
332
|
await server.close();
|
|
230
333
|
}
|
|
231
334
|
finally {
|
|
@@ -237,11 +340,3 @@ export async function startWebConsole(options) {
|
|
|
237
340
|
function isLoopback(host) {
|
|
238
341
|
return ['127.0.0.1', 'localhost', '::1'].includes(host);
|
|
239
342
|
}
|
|
240
|
-
function openBrowser(url) {
|
|
241
|
-
const command = process.platform === 'darwin' ? 'open'
|
|
242
|
-
: process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
243
|
-
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
244
|
-
const child = spawn(command, args, { stdio: 'ignore', detached: true, shell: false });
|
|
245
|
-
child.on('error', () => undefined);
|
|
246
|
-
child.unref();
|
|
247
|
-
}
|
package/dist/web/server.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ConversationRoleObservation, ConversationRoleState } from '../application/conversation-role-observer.js';
|
|
2
|
+
import { type InitAnswers } from '../init-wizard.js';
|
|
1
3
|
import { SupervisorOursTools } from '../application/supervisor-ours-tools.js';
|
|
2
4
|
import { type FastifyInstance } from 'fastify';
|
|
3
5
|
import type { FleetQueryService } from '../application/fleet-query-service.js';
|
|
@@ -16,7 +18,14 @@ import type { TopologyDraftStore } from './topology-draft-store.js';
|
|
|
16
18
|
import type { TopologyPromoteService } from './topology-promote.js';
|
|
17
19
|
import type { RoleRemovalService } from '../application/role-removal-service.js';
|
|
18
20
|
import type { TaskRoomApplicationService } from '../application/task-room-service.js';
|
|
21
|
+
import type { RoomLayoutDefinitions } from '../application/room-layout-definitions.js';
|
|
22
|
+
import type { PresetProvenance } from '../application/preset-provenance.js';
|
|
23
|
+
import type { OfferedModels } from '../subscriptions/cli.js';
|
|
24
|
+
import type { SubscriptionService } from '../subscriptions/service.js';
|
|
25
|
+
import type { LayoutBindingGrants } from '../rooms-tasks/layout-binding-grants.js';
|
|
19
26
|
export interface WebServices {
|
|
27
|
+
observeRole?(roleId: string, listener: (state: ConversationRoleState) => void): ConversationRoleObservation;
|
|
28
|
+
layoutBindings?: Pick<LayoutBindingGrants, 'control'>;
|
|
20
29
|
query: FleetQueryService;
|
|
21
30
|
repository: RoleRepository;
|
|
22
31
|
session(roleId: string): Promise<RoleSessionControl>;
|
|
@@ -32,7 +41,14 @@ export interface WebServices {
|
|
|
32
41
|
topologyPromote?: TopologyPromoteService;
|
|
33
42
|
removal?: RoleRemovalService;
|
|
34
43
|
taskRooms?: TaskRoomApplicationService;
|
|
44
|
+
roomLayouts?: RoomLayoutDefinitions;
|
|
45
|
+
presetProvenance?: PresetProvenance;
|
|
35
46
|
oursTools?: Pick<SupervisorOursTools, 'list' | 'call'>;
|
|
47
|
+
subscriptions?: SubscriptionService;
|
|
48
|
+
/** First-time setup from the packaged presets; the account App supplies only the model choices. */
|
|
49
|
+
onboardingSetup?(answers: InitAnswers): Promise<void>;
|
|
50
|
+
/** The Codex models the runtime that sessions will use offers the active account. Absent: this server cannot tell, and filters nothing. */
|
|
51
|
+
codexModels?(): Promise<OfferedModels>;
|
|
36
52
|
}
|
|
37
53
|
export interface WebServer {
|
|
38
54
|
app: FastifyInstance;
|