@ours.network/fleet 1.2.0-nightly.4 → 1.2.0-nightly.41
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 +165 -53
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.js +9 -0
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/harness.js +8 -0
- package/dist/agent-ours/runtime.d.ts +6 -0
- package/dist/agent-ours/runtime.js +36 -0
- package/dist/agent-ours/service.js +33 -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 +25 -0
- package/dist/application/conversation-history.js +189 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +2 -2
- package/dist/application/legacy-supervisor-identity.d.ts +7 -0
- package/dist/application/legacy-supervisor-identity.js +43 -0
- 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 +1 -1
- package/dist/application/role-repository.js +2 -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 +18 -0
- package/dist/application/session-control.js +54 -3
- package/dist/application/supervisor-ours-tools.d.ts +159 -0
- package/dist/application/supervisor-ours-tools.js +113 -0
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +249 -0
- package/dist/application/task-room-service.d.ts +99 -3
- package/dist/application/task-room-service.js +296 -31
- package/dist/atomic-file.d.ts +5 -0
- package/dist/atomic-file.js +62 -1
- package/dist/build-info.json +6 -4
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +4 -0
- package/dist/cli.js +186 -34
- package/dist/client-profile.d.ts +5 -16
- package/dist/client-profile.js +4 -112
- package/dist/config.d.ts +18 -0
- package/dist/config.js +28 -4
- package/dist/creation.js +2 -2
- package/dist/daemon-recovery.d.ts +1 -8
- package/dist/daemon-recovery.js +8 -113
- package/dist/docs.d.ts +3 -3
- package/dist/docs.js +44 -22
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +46 -59
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/fleet-command-audit.d.ts +1 -0
- package/dist/fleet-command-audit.js +11 -6
- package/dist/harness/claude-code.js +9 -1
- 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-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex.d.ts +3 -2
- package/dist/harness/codex.js +8 -5
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +2 -0
- package/dist/isolation/types.d.ts +5 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +1 -34
- package/dist/monitor.js +24 -151
- package/dist/notifications/fleet-producer.d.ts +18 -0
- package/dist/notifications/fleet-producer.js +129 -0
- package/dist/notifications/outbox.d.ts +42 -0
- package/dist/notifications/outbox.js +149 -0
- package/dist/owner-channel/channel.d.ts +3 -0
- package/dist/owner-channel/channel.js +34 -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 +25 -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 +34 -5
- package/dist/rooms-tasks/close.d.ts +5 -2
- package/dist/rooms-tasks/close.js +208 -28
- 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 +6 -4
- package/dist/rooms-tasks/cowork-adapter.js +11 -117
- package/dist/rooms-tasks/cowork-http.d.ts +3 -0
- package/dist/rooms-tasks/cowork-http.js +70 -0
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +74 -5
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +169 -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-service.d.ts +42 -0
- package/dist/rooms-tasks/layout-service.js +185 -0
- package/dist/rooms-tasks/layout.d.ts +114 -0
- package/dist/rooms-tasks/layout.js +250 -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-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +14 -0
- package/dist/rooms-tasks/provision.js +173 -46
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +53 -8
- package/dist/rooms-tasks/task-state.d.ts +42 -7
- package/dist/rooms-tasks/task-state.js +153 -23
- package/dist/rooms-tasks/types.d.ts +28 -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 +26 -0
- package/dist/rooms-tasks/workspace.js +195 -0
- package/dist/runner.d.ts +19 -3
- package/dist/runner.js +86 -29
- package/dist/session/acp.d.ts +7 -1
- package/dist/session/acp.js +21 -6
- package/dist/session/arbiter.d.ts +3 -0
- package/dist/session/arbiter.js +20 -2
- package/dist/session/codex-app-server.d.ts +3 -0
- package/dist/session/codex-app-server.js +26 -3
- package/dist/session/control.d.ts +10 -1
- package/dist/session/control.js +37 -2
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-types.d.ts +5 -3
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +3 -0
- package/dist/setup-tunnel-preflight.d.ts +32 -0
- package/dist/setup-tunnel-preflight.js +58 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.js +7 -0
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -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 +75 -0
- package/dist/subscriptions/login.js +182 -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/launchd.js +2 -1
- package/dist/supervisor/systemd.js +9 -23
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +6 -1
- package/dist/temp-lifecycle.js +29 -13
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +29 -1
- package/dist/web/auth.js +81 -7
- 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/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +29 -0
- package/dist/web/prefix-gateway.js +206 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +116 -31
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +501 -53
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +29 -0
- package/dist/web/workspace-devices.js +84 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +32 -0
- package/dist/workspace-enrollment.d.ts +53 -0
- package/dist/workspace-enrollment.js +370 -0
- package/docs/room-layouts.md +227 -0
- package/docs/task-workspaces.md +130 -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 +13 -18
- 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 +1 -1
- 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 +34 -11
- package/presets/fleet/roles/Engineer.yaml +40 -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/auth.js
CHANGED
|
@@ -14,6 +14,8 @@ export class WebAuth {
|
|
|
14
14
|
now;
|
|
15
15
|
devices;
|
|
16
16
|
access;
|
|
17
|
+
workspaceDevices;
|
|
18
|
+
appOrigin;
|
|
17
19
|
_bootstrapSecret = token(32);
|
|
18
20
|
bootstrapExpiresAt = Date.now() + 5 * 60_000;
|
|
19
21
|
bootstrapUsed = false;
|
|
@@ -21,13 +23,16 @@ export class WebAuth {
|
|
|
21
23
|
sessionDevices = new Map();
|
|
22
24
|
tickets = new Map();
|
|
23
25
|
rates = new Map();
|
|
26
|
+
transports = new Map();
|
|
24
27
|
sockets = new Map();
|
|
25
|
-
constructor(_origin, _host, now = Date.now, devices = new TrustedDeviceStore(), access = { version: 1, mode: 'pairing' }) {
|
|
28
|
+
constructor(_origin, _host, now = Date.now, devices = new TrustedDeviceStore(), access = { version: 1, mode: 'pairing' }, workspaceDevices, appOrigin = 'https://app.ours.network') {
|
|
26
29
|
this._origin = _origin;
|
|
27
30
|
this._host = _host;
|
|
28
31
|
this.now = now;
|
|
29
32
|
this.devices = devices;
|
|
30
33
|
this.access = access;
|
|
34
|
+
this.workspaceDevices = workspaceDevices;
|
|
35
|
+
this.appOrigin = appOrigin;
|
|
31
36
|
}
|
|
32
37
|
get bootstrapSecret() { return this._bootstrapSecret; }
|
|
33
38
|
get origin() { return this._origin; }
|
|
@@ -49,16 +54,24 @@ export class WebAuth {
|
|
|
49
54
|
this.bootstrapUsed = false;
|
|
50
55
|
return this._bootstrapSecret;
|
|
51
56
|
}
|
|
57
|
+
/** The configured account origin, when this request comes from it and workspace devices are enabled. */
|
|
58
|
+
accountOrigin(request) {
|
|
59
|
+
return this.workspaceDevices && request.headers.origin === this.appOrigin ? this.appOrigin : undefined;
|
|
60
|
+
}
|
|
52
61
|
validateBoundary(request, requireOrigin) {
|
|
53
62
|
const host = request.headers.host;
|
|
54
63
|
const hosts = this.allowedHosts.size ? this.allowedHosts : new Set([this.host]);
|
|
55
64
|
const origins = this.allowedOrigins.size ? this.allowedOrigins : new Set([this.origin]);
|
|
56
65
|
if (!host || !hosts.has(host))
|
|
57
66
|
throw new FleetError('forbidden', `This address is not configured for the fleet console. Open ${this.origin} or set --public-origin.`);
|
|
58
|
-
if (
|
|
67
|
+
if (this.workspaceDevices && String(request.headers.authorization ?? '').startsWith('Bearer ') && request.headers.origin && request.headers.origin !== this.appOrigin && !origins.has(request.headers.origin))
|
|
68
|
+
throw new FleetError('forbidden', 'workspace request Origin does not match configured account origin');
|
|
69
|
+
const workspaceRequest = Boolean(this.workspaceDevices) && request.headers.origin === this.appOrigin
|
|
70
|
+
&& (String(request.headers.authorization ?? '').startsWith('Bearer ') || (request.url ?? '').split('?')[0] === '/api/v1/devices/enroll' || request.method === 'OPTIONS');
|
|
71
|
+
if (requireOrigin && !workspaceRequest && (!request.headers.origin || !origins.has(request.headers.origin)))
|
|
59
72
|
throw new FleetError('forbidden', 'request Origin does not match the configured control-panel origin');
|
|
60
73
|
const fetchSite = request.headers['sec-fetch-site'];
|
|
61
|
-
if (fetchSite && !['same-origin', 'none'].includes(String(fetchSite)))
|
|
74
|
+
if (!workspaceRequest && fetchSite && !['same-origin', 'none'].includes(String(fetchSite)))
|
|
62
75
|
throw new FleetError('forbidden', 'cross-site request rejected');
|
|
63
76
|
}
|
|
64
77
|
exchange(request) {
|
|
@@ -100,7 +113,23 @@ export class WebAuth {
|
|
|
100
113
|
return { session: this.createSession(device.id), device };
|
|
101
114
|
}
|
|
102
115
|
authenticate(request, mutation = false) {
|
|
116
|
+
const authorization = String(request.headers.authorization ?? '');
|
|
117
|
+
if (authorization.startsWith('Bearer ')) {
|
|
118
|
+
this.validateBoundary(request, mutation);
|
|
119
|
+
if (!this.workspaceDevices)
|
|
120
|
+
throw new FleetError('unauthorized', 'workspace bearer credential is invalid');
|
|
121
|
+
const device = this.requireWorkspaceDevices().authenticate(authorization.slice(7));
|
|
122
|
+
const id = `workspace:${device.id}`;
|
|
123
|
+
const session = { id, csrf: '', createdAt: device.createdAt, lastSeenAt: this.now(), absoluteExpiresAt: device.expiresAt };
|
|
124
|
+
this.sessions.set(id, session);
|
|
125
|
+
this.sessionDevices.set(id, id);
|
|
126
|
+
if (mutation)
|
|
127
|
+
this.consumeRate(`mutation:${id}`, 120, 60000);
|
|
128
|
+
return session;
|
|
129
|
+
}
|
|
103
130
|
this.validateBoundary(request, mutation);
|
|
131
|
+
if (request.headers.origin === this.appOrigin && !this.allowedOrigins.has(this.appOrigin))
|
|
132
|
+
throw new FleetError('unauthorized', 'workspace bearer credential required');
|
|
104
133
|
const id = parseCookies(request.headers.cookie ?? '').ofs_session;
|
|
105
134
|
const session = id ? this.sessions.get(id) : undefined;
|
|
106
135
|
const now = this.now();
|
|
@@ -121,7 +150,9 @@ export class WebAuth {
|
|
|
121
150
|
logout(request) {
|
|
122
151
|
const session = this.authenticate(request, true);
|
|
123
152
|
const deviceId = this.sessionDevices.get(session.id);
|
|
124
|
-
if (
|
|
153
|
+
if (session.id.startsWith('workspace:'))
|
|
154
|
+
this.workspaceDevices?.revoke(session.id.slice(10));
|
|
155
|
+
else if (deviceId && deviceId !== 'unprotected')
|
|
125
156
|
this.devices.revokeId(deviceId);
|
|
126
157
|
this.removeSession(session.id);
|
|
127
158
|
}
|
|
@@ -143,7 +174,7 @@ export class WebAuth {
|
|
|
143
174
|
|| ticket.roleId !== roleId)
|
|
144
175
|
throw new FleetError('unauthorized', 'WebSocket ticket is invalid, expired, or already used');
|
|
145
176
|
const session = this.sessions.get(ticket.sessionId);
|
|
146
|
-
if (!session)
|
|
177
|
+
if (!session || (session.id.startsWith('workspace:') && !this.workspaceDevices?.valid(session.id.slice(10))))
|
|
147
178
|
throw new FleetError('unauthorized', 'browser session expired');
|
|
148
179
|
return session;
|
|
149
180
|
}
|
|
@@ -154,9 +185,30 @@ export class WebAuth {
|
|
|
154
185
|
this.sockets.set(sessionId, sockets);
|
|
155
186
|
}
|
|
156
187
|
sockets.add(socket);
|
|
157
|
-
|
|
188
|
+
const expiry = setInterval(() => { const session = this.sessions.get(sessionId); if (!session || session.absoluteExpiresAt <= this.now() || (sessionId.startsWith('workspace:') && !this.workspaceDevices?.valid(sessionId.slice(10))))
|
|
189
|
+
this.removeSession(sessionId); }, 1000);
|
|
190
|
+
expiry.unref();
|
|
191
|
+
socket.once('close', () => { clearInterval(expiry); sockets?.delete(socket); });
|
|
192
|
+
}
|
|
193
|
+
/** Raw service streams share the same logout/revocation boundary as native sockets. */
|
|
194
|
+
bindTransport(sessionId, close) {
|
|
195
|
+
let transports = this.transports.get(sessionId);
|
|
196
|
+
if (!transports) {
|
|
197
|
+
transports = new Set();
|
|
198
|
+
this.transports.set(sessionId, transports);
|
|
199
|
+
}
|
|
200
|
+
transports.add(close);
|
|
201
|
+
const expiry = setInterval(() => { const session = this.sessions.get(sessionId); if (!session || session.absoluteExpiresAt <= this.now() || (sessionId.startsWith('workspace:') && !this.workspaceDevices?.valid(sessionId.slice(10))))
|
|
202
|
+
this.removeSession(sessionId); }, 1000);
|
|
203
|
+
expiry.unref();
|
|
204
|
+
return () => { clearInterval(expiry); transports.delete(close); if (!transports.size)
|
|
205
|
+
this.transports.delete(sessionId); };
|
|
158
206
|
}
|
|
159
207
|
clearSessions() {
|
|
208
|
+
for (const transports of this.transports.values())
|
|
209
|
+
for (const close of transports)
|
|
210
|
+
close();
|
|
211
|
+
this.transports.clear();
|
|
160
212
|
for (const sockets of this.sockets.values())
|
|
161
213
|
for (const socket of sockets)
|
|
162
214
|
socket.close(4401, 'authentication revoked');
|
|
@@ -167,10 +219,29 @@ export class WebAuth {
|
|
|
167
219
|
}
|
|
168
220
|
revokeAllTrustedDevices() {
|
|
169
221
|
const count = this.devices.revokeAll();
|
|
222
|
+
for (const device of this.workspaceDevices?.list() ?? [])
|
|
223
|
+
this.workspaceDevices?.revoke(device.id);
|
|
170
224
|
this.clearSessions();
|
|
171
225
|
return count;
|
|
172
226
|
}
|
|
173
|
-
|
|
227
|
+
workspaceIdentity() { return { workspaceId: this.requireWorkspaceDevices().workspaceId }; }
|
|
228
|
+
workspaceEnrollment(issuer = null) { return this.requireWorkspaceDevices().mint(issuer?.startsWith('workspace:') ? issuer.slice(10) : null); }
|
|
229
|
+
enrollWorkspace(request, input) {
|
|
230
|
+
this.validateBoundary(request, true);
|
|
231
|
+
this.consumeRate('workspace-enrollment', 30, 60000);
|
|
232
|
+
return this.requireWorkspaceDevices().enroll(input.enrollment, input.workspaceId, input.label);
|
|
233
|
+
}
|
|
234
|
+
listWorkspaceDevices() { return this.requireWorkspaceDevices().list(); }
|
|
235
|
+
revokeWorkspaceDevice(id) {
|
|
236
|
+
this.requireWorkspaceDevices().revoke(id);
|
|
237
|
+
this.removeSession(`workspace:${id}`);
|
|
238
|
+
}
|
|
239
|
+
requireWorkspaceDevices() {
|
|
240
|
+
if (!this.workspaceDevices)
|
|
241
|
+
throw new FleetError('capability_unavailable', 'workspace device access is not configured');
|
|
242
|
+
return this.workspaceDevices;
|
|
243
|
+
}
|
|
244
|
+
shutdown() { this.clearSessions(); this.workspaceDevices?.close(); }
|
|
174
245
|
createSession(deviceId) {
|
|
175
246
|
const now = this.now();
|
|
176
247
|
const session = {
|
|
@@ -182,6 +253,9 @@ export class WebAuth {
|
|
|
182
253
|
return session;
|
|
183
254
|
}
|
|
184
255
|
removeSession(id) {
|
|
256
|
+
for (const close of this.transports.get(id) ?? [])
|
|
257
|
+
close();
|
|
258
|
+
this.transports.delete(id);
|
|
185
259
|
this.sessions.delete(id);
|
|
186
260
|
this.sessionDevices.delete(id);
|
|
187
261
|
for (const [ticket, value] of this.tickets)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
/** Bind the preferred loopback port; only EADDRINUSE permits an OS-selected replacement. */
|
|
3
|
+
export async function availableWebPort(preferred) {
|
|
4
|
+
const server = createServer();
|
|
5
|
+
const listen = (port) => new Promise((resolve, reject) => {
|
|
6
|
+
server.once('error', reject);
|
|
7
|
+
server.listen(port, '127.0.0.1', () => { server.removeListener('error', reject); resolve(); });
|
|
8
|
+
});
|
|
9
|
+
try {
|
|
10
|
+
await listen(preferred);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code !== 'EADDRINUSE')
|
|
14
|
+
throw error;
|
|
15
|
+
await listen(0);
|
|
16
|
+
}
|
|
17
|
+
const address = server.address();
|
|
18
|
+
if (!address || typeof address === 'string')
|
|
19
|
+
throw Error('No loopback port selected');
|
|
20
|
+
const port = address.port;
|
|
21
|
+
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
|
|
22
|
+
return port;
|
|
23
|
+
}
|
package/dist/web/control.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type WebControlCommand = '
|
|
1
|
+
export type WebControlCommand = 'revoke-all' | 'link-device';
|
|
2
2
|
export declare const webControlPath: (dir?: string) => string;
|
|
3
3
|
export interface WebControlServer {
|
|
4
4
|
path: string;
|
|
@@ -6,9 +6,9 @@ export interface WebControlServer {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function startWebControlServer(options: {
|
|
8
8
|
dir?: string;
|
|
9
|
-
|
|
9
|
+
onLinkDevice?(): Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
10
10
|
onRevokeAll(): void | Promise<void>;
|
|
11
11
|
now?: () => number;
|
|
12
12
|
rateLimit?: number;
|
|
13
13
|
}): Promise<WebControlServer>;
|
|
14
|
-
export declare function requestWebControl(command: WebControlCommand, path?: string, timeoutMs?: number): Promise<void>;
|
|
14
|
+
export declare function requestWebControl(command: WebControlCommand, path?: string, timeoutMs?: number): Promise<Record<string, unknown> | void>;
|
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);
|
|
@@ -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,29 @@
|
|
|
1
|
+
import { type IncomingMessage } from 'node:http';
|
|
2
|
+
import type { WebAuth } from './auth.js';
|
|
3
|
+
/** Each entry exposes one dedicated backend listener. Endpoint dispatch stays upstream. */
|
|
4
|
+
export interface ServiceTarget {
|
|
5
|
+
prefix: string;
|
|
6
|
+
origin: string;
|
|
7
|
+
upstreamPrefix?: string;
|
|
8
|
+
/** Server-side service credentials; never taken from browser headers. */
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
/** Dedicated credential-authenticated services reject direct browser context. */
|
|
11
|
+
stripBrowserContext?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Service paths (below the prefix) where the account App may open a WebSocket with its device
|
|
14
|
+
* credential. A browser cannot set Authorization on a WebSocket, so only there it is accepted
|
|
15
|
+
* as a subprotocol.
|
|
16
|
+
*/
|
|
17
|
+
deviceSocketPaths?: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface GatewayOptions {
|
|
20
|
+
/** Backend mode is for a separate loopback machine-client listener; browsers are rejected. */
|
|
21
|
+
auth: WebAuth | 'backend';
|
|
22
|
+
fleetOrigin: string;
|
|
23
|
+
services: ServiceTarget[];
|
|
24
|
+
}
|
|
25
|
+
/** Raw transport deliberately runs outside Fastify's JSON parser and body limits. */
|
|
26
|
+
export declare function createPrefixGateway(options: GatewayOptions): {
|
|
27
|
+
server: import("http").Server<typeof IncomingMessage, typeof import("http").ServerResponse>;
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
};
|