@ours.network/fleet 1.2.0-nightly.3 → 1.2.0-nightly.30
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 +158 -28
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.d.ts +3 -0
- package/dist/agent-ours/bridge.js +178 -0
- package/dist/agent-ours/controller.d.ts +14 -0
- package/dist/agent-ours/controller.js +60 -0
- 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 +23 -0
- package/dist/agent-ours/file-rpc.js +123 -0
- package/dist/agent-ours/harness.d.ts +14 -0
- package/dist/agent-ours/harness.js +72 -0
- package/dist/agent-ours/mcp-endpoint.d.ts +15 -0
- package/dist/agent-ours/mcp-endpoint.js +111 -0
- package/dist/agent-ours/runtime.d.ts +59 -0
- package/dist/agent-ours/runtime.js +283 -0
- package/dist/agent-ours/service.d.ts +23 -0
- package/dist/agent-ours/service.js +372 -0
- package/dist/agent-ours/state.d.ts +45 -0
- package/dist/agent-ours/state.js +95 -0
- package/dist/agent-ours/wire.d.ts +14 -0
- package/dist/agent-ours/wire.js +55 -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/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/briefing.js +10 -59
- package/dist/build-info.json +6 -4
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +4 -0
- package/dist/cli.js +109 -3
- package/dist/client-profile.d.ts +5 -16
- package/dist/client-profile.js +4 -112
- package/dist/config.d.ts +4 -0
- package/dist/config.js +7 -0
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +5 -16
- 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 +43 -23
- 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 +12 -7
- package/dist/harness/agent-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +4 -2
- 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-session.js +9 -2
- package/dist/harness/codex.d.ts +3 -2
- package/dist/harness/codex.js +4 -3
- package/dist/harness/hermes-session.js +7 -1
- package/dist/init-wizard.js +20 -9
- package/dist/isolation/policy.js +2 -0
- package/dist/isolation/types.d.ts +5 -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 +31 -0
- package/dist/notifications/outbox.js +104 -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/preset-migration.js +15 -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.js +3 -1
- 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 +182 -48
- 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 +23 -3
- package/dist/runner.js +581 -494
- package/dist/session/acp.d.ts +3 -0
- package/dist/session/acp.js +14 -0
- package/dist/session/arbiter.d.ts +3 -0
- package/dist/session/arbiter.js +20 -2
- package/dist/session/codex-app-server-transport.js +1 -1
- package/dist/session/codex-app-server.d.ts +3 -0
- package/dist/session/codex-app-server.js +25 -1
- 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/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.js +19 -16
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -0
- package/dist/subscriptions/cli.d.ts +78 -0
- package/dist/subscriptions/cli.js +186 -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/temp-supervisor-recovery.d.ts +3 -0
- package/dist/temp-supervisor-recovery.js +46 -0
- package/dist/watchdog/briefing.js +3 -16
- package/dist/watchdog/run.js +19 -5
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +28 -2
- package/dist/web/auth.js +81 -8
- package/dist/web/control.d.ts +3 -2
- package/dist/web/control.js +11 -7
- 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 +23 -0
- package/dist/web/prefix-gateway.js +156 -0
- package/dist/web/runtime.d.ts +4 -0
- package/dist/web/runtime.js +90 -19
- package/dist/web/server.d.ts +11 -0
- package/dist/web/server.js +445 -31
- package/dist/web/service.d.ts +6 -2
- package/dist/web/service.js +22 -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 +25 -0
- package/dist/workspace-enrollment.js +112 -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 +14 -17
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- 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 +22 -0
- 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
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
export interface GatewayOptions {
|
|
14
|
+
/** Backend mode is for a separate loopback machine-client listener; browsers are rejected. */
|
|
15
|
+
auth: WebAuth | 'backend';
|
|
16
|
+
fleetOrigin: string;
|
|
17
|
+
services: ServiceTarget[];
|
|
18
|
+
}
|
|
19
|
+
/** Raw transport deliberately runs outside Fastify's JSON parser and body limits. */
|
|
20
|
+
export declare function createPrefixGateway(options: GatewayOptions): {
|
|
21
|
+
server: import("http").Server<typeof IncomingMessage, typeof import("http").ServerResponse>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createServer, request as httpRequest } from 'node:http';
|
|
2
|
+
import { request as httpsRequest } from 'node:https';
|
|
3
|
+
const hopHeaders = new Set(['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer', 'transfer-encoding', 'upgrade']);
|
|
4
|
+
function headersFor(message) {
|
|
5
|
+
const denied = new Set([...hopHeaders, ...(message.headers.connection ?? '').split(',').map(x => x.trim().toLowerCase())]);
|
|
6
|
+
return Object.fromEntries(Object.entries(message.headers).filter(([key]) => !denied.has(key)));
|
|
7
|
+
}
|
|
8
|
+
/** Raw transport deliberately runs outside Fastify's JSON parser and body limits. */
|
|
9
|
+
export function createPrefixGateway(options) {
|
|
10
|
+
const auth = options.auth === 'backend' ? undefined : options.auth;
|
|
11
|
+
const services = options.services.map(target => {
|
|
12
|
+
if (!/^\/[a-z][a-z0-9-]*$/.test(target.prefix) || target.prefix === '/fleet')
|
|
13
|
+
throw new Error('invalid service prefix');
|
|
14
|
+
const origin = new URL(target.origin);
|
|
15
|
+
if (target.upstreamPrefix && !/^\/(?:[A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]*$/.test(target.upstreamPrefix))
|
|
16
|
+
throw new Error('invalid upstream service prefix');
|
|
17
|
+
if (!['http:', 'https:'].includes(origin.protocol) || origin.pathname !== '/' || origin.search || origin.hash || origin.username || origin.password)
|
|
18
|
+
throw new Error('service target must be an HTTP origin');
|
|
19
|
+
return { ...target, origin: origin.origin };
|
|
20
|
+
});
|
|
21
|
+
if (new Set(services.map(x => x.prefix)).size !== services.length)
|
|
22
|
+
throw new Error('duplicate service prefix');
|
|
23
|
+
const select = (req) => {
|
|
24
|
+
const raw = req.url ?? '/';
|
|
25
|
+
if (!raw.startsWith('/') || raw.startsWith('//'))
|
|
26
|
+
throw new Error('invalid request target');
|
|
27
|
+
const path = raw.split('?')[0];
|
|
28
|
+
const service = services.find(x => path === x.prefix || path.startsWith(x.prefix + '/'));
|
|
29
|
+
const fleet = path === '/fleet/api' || path.startsWith('/fleet/api/');
|
|
30
|
+
return { service, page: !service && !fleet && !path.startsWith('/api/'), origin: service?.origin ?? options.fleetOrigin,
|
|
31
|
+
path: service ? (service.upstreamPrefix || '') + (raw.slice(service.prefix.length) || '/').replace(/^\?/, '/?') : fleet ? raw.slice('/fleet'.length) : raw };
|
|
32
|
+
};
|
|
33
|
+
const prepare = (req, upgrade = false) => {
|
|
34
|
+
const route = select(req);
|
|
35
|
+
const request = { headers: req.headers, method: req.method, url: route.path };
|
|
36
|
+
if (!auth && (req.headers.origin || req.headers['sec-fetch-site'] || !route.service))
|
|
37
|
+
throw new Error('machine-client listener rejects browser requests');
|
|
38
|
+
auth?.validateBoundary(request, upgrade, route.page);
|
|
39
|
+
const session = route.service && auth ? auth.authenticate(request, !upgrade && !['GET', 'HEAD', 'OPTIONS'].includes(req.method ?? 'GET')) : undefined;
|
|
40
|
+
const headers = headersFor(req);
|
|
41
|
+
// Forward only the explicit browser context, never an attacker-selected forwarding chain.
|
|
42
|
+
for (const key of Object.keys(headers))
|
|
43
|
+
if (key.startsWith('x-forwarded-') || key === 'forwarded')
|
|
44
|
+
delete headers[key];
|
|
45
|
+
if (route.service) {
|
|
46
|
+
if (auth)
|
|
47
|
+
for (const name of ['cookie', 'authorization', 'x-csrf-token', 'x-ours-api-token'])
|
|
48
|
+
delete headers[name];
|
|
49
|
+
if (route.service.stripBrowserContext) {
|
|
50
|
+
for (const name of ['origin', 'sec-fetch-site', 'sec-fetch-mode', 'sec-fetch-dest', 'sec-fetch-user'])
|
|
51
|
+
delete headers[name];
|
|
52
|
+
}
|
|
53
|
+
Object.assign(headers, route.service.headers);
|
|
54
|
+
headers.host = new URL(route.origin).host;
|
|
55
|
+
}
|
|
56
|
+
if (upgrade) {
|
|
57
|
+
headers.connection = 'Upgrade';
|
|
58
|
+
headers.upgrade = 'websocket';
|
|
59
|
+
}
|
|
60
|
+
return { ...route, headers, session };
|
|
61
|
+
};
|
|
62
|
+
const track = (req, session, close) => {
|
|
63
|
+
if (!session)
|
|
64
|
+
return () => { };
|
|
65
|
+
const unbind = auth.bindTransport(session.id, close);
|
|
66
|
+
const timer = setInterval(() => { try {
|
|
67
|
+
auth.authenticate({ headers: req.headers });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
close();
|
|
71
|
+
} }, 30_000);
|
|
72
|
+
timer.unref();
|
|
73
|
+
return () => { clearInterval(timer); unbind(); };
|
|
74
|
+
};
|
|
75
|
+
const server = createServer((req, res) => {
|
|
76
|
+
let route;
|
|
77
|
+
try {
|
|
78
|
+
route = prepare(req);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const code = error.code;
|
|
82
|
+
res.writeHead(code === 'unauthorized' ? 401 : 403, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
83
|
+
res.end(JSON.stringify({ error: { code: code ?? 'forbidden', message: error.message } }));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (req.url === '/') {
|
|
87
|
+
res.writeHead(302, { location: '/fleet' });
|
|
88
|
+
res.end();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const upstream = (route.origin.startsWith('https:') ? httpsRequest : httpRequest)(route.origin, {
|
|
92
|
+
method: req.method, path: route.path, headers: route.headers,
|
|
93
|
+
}, response => {
|
|
94
|
+
const headers = headersFor(response);
|
|
95
|
+
if (route.service)
|
|
96
|
+
delete headers['set-cookie'];
|
|
97
|
+
res.writeHead(response.statusCode ?? 502, headers);
|
|
98
|
+
response.on('error', () => res.destroy());
|
|
99
|
+
response.on('aborted', () => res.destroy());
|
|
100
|
+
response.on('close', () => { if (!response.complete)
|
|
101
|
+
res.destroy(); });
|
|
102
|
+
response.pipe(res);
|
|
103
|
+
res.on('close', () => response.destroy());
|
|
104
|
+
});
|
|
105
|
+
upstream.on('error', () => {
|
|
106
|
+
if (!res.headersSent)
|
|
107
|
+
res.writeHead(502, { 'content-type': 'application/json' });
|
|
108
|
+
res.end(JSON.stringify({ error: { code: 'upstream_unavailable', message: 'Service is unavailable' } }));
|
|
109
|
+
});
|
|
110
|
+
const untrack = track(req, route.session, () => { upstream.destroy(); res.destroy(); });
|
|
111
|
+
res.on('close', untrack);
|
|
112
|
+
req.on('aborted', () => upstream.destroy());
|
|
113
|
+
res.on('close', () => upstream.destroy());
|
|
114
|
+
req.pipe(upstream);
|
|
115
|
+
});
|
|
116
|
+
const sockets = new Set();
|
|
117
|
+
server.on('connection', socket => { sockets.add(socket); socket.on('close', () => sockets.delete(socket)); });
|
|
118
|
+
server.on('upgrade', (req, socket, head) => {
|
|
119
|
+
let route;
|
|
120
|
+
try {
|
|
121
|
+
route = prepare(req, true);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
socket.end('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const upstream = (route.origin.startsWith('https:') ? httpsRequest : httpRequest)(route.origin, {
|
|
128
|
+
method: req.method, path: route.path, headers: route.headers,
|
|
129
|
+
});
|
|
130
|
+
upstream.on('upgrade', (response, peer, upstreamHead) => {
|
|
131
|
+
const headers = headersFor(response);
|
|
132
|
+
headers.connection = 'Upgrade';
|
|
133
|
+
headers.upgrade = 'websocket';
|
|
134
|
+
delete headers['set-cookie'];
|
|
135
|
+
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`);
|
|
136
|
+
if (head.length)
|
|
137
|
+
peer.write(head);
|
|
138
|
+
if (upstreamHead.length)
|
|
139
|
+
socket.write(upstreamHead);
|
|
140
|
+
const untrack = track(req, route.session, () => { peer.destroy(); socket.destroy(); });
|
|
141
|
+
socket.on('close', untrack);
|
|
142
|
+
peer.pipe(socket);
|
|
143
|
+
socket.pipe(peer);
|
|
144
|
+
peer.on('error', () => socket.destroy());
|
|
145
|
+
socket.on('error', () => peer.destroy());
|
|
146
|
+
socket.on('close', () => peer.destroy());
|
|
147
|
+
peer.on('close', () => socket.destroy());
|
|
148
|
+
});
|
|
149
|
+
upstream.on('response', response => { socket.end(`HTTP/1.1 ${response.statusCode ?? 502} Rejected\r\nConnection: close\r\n\r\n`); response.destroy(); });
|
|
150
|
+
upstream.on('error', () => socket.destroy());
|
|
151
|
+
socket.on('close', () => upstream.destroy());
|
|
152
|
+
upstream.end();
|
|
153
|
+
});
|
|
154
|
+
return { server, async close() { for (const socket of sockets)
|
|
155
|
+
socket.destroy(); await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); } };
|
|
156
|
+
}
|
package/dist/web/runtime.d.ts
CHANGED
|
@@ -2,6 +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;
|
|
8
|
+
staticRoot?: string;
|
|
5
9
|
port?: number;
|
|
6
10
|
open?: boolean;
|
|
7
11
|
binPath: string;
|
package/dist/web/runtime.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import { validateAccountOrigin } from '../account-origin.js';
|
|
2
|
+
import { startWorkspaceTunnel } from './workspace-tunnel.js';
|
|
3
|
+
import { createPrefixGateway } from './prefix-gateway.js';
|
|
4
|
+
import { readClientProfile } from '../client-profile.js';
|
|
5
|
+
import { readPrivateFile } from '@ours.network/sdk/connector';
|
|
6
|
+
import { LayoutBindingGrants } from '../rooms-tasks/layout-binding-grants.js';
|
|
7
|
+
import { RoomLayoutService } from '../rooms-tasks/layout-service.js';
|
|
1
8
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
9
|
+
import { FleetNotificationProducer } from '../notifications/fleet-producer.js';
|
|
10
|
+
import { producerConfig } from '../notifications/outbox.js';
|
|
11
|
+
import { SubscriptionService } from '../subscriptions/service.js';
|
|
12
|
+
import { existsSync, realpathSync, readFileSync } from 'node:fs';
|
|
3
13
|
import { resolve } from 'node:path';
|
|
4
14
|
import { loadConfig } from '../config.js';
|
|
5
15
|
import { RoleRepository } from '../application/role-repository.js';
|
|
@@ -13,7 +23,7 @@ import { FleetError } from '../application/errors.js';
|
|
|
13
23
|
import { controlRequest, controlSocketPath } from '../session/control.js';
|
|
14
24
|
import { pickBackend } from '../supervisor/index.js';
|
|
15
25
|
import { realExec } from '../exec.js';
|
|
16
|
-
import { home, stateRoot } from '../paths.js';
|
|
26
|
+
import { home, stateRoot, agentsRoot, tmpRoot } from '../paths.js';
|
|
17
27
|
import { AuditSink } from './audit.js';
|
|
18
28
|
import { FleetEventBus } from './events.js';
|
|
19
29
|
import { buildWebServer } from './server.js';
|
|
@@ -21,8 +31,11 @@ import { FleetConfigService } from './fleet-config-service.js';
|
|
|
21
31
|
import { mergeTopology } from './topology-model.js';
|
|
22
32
|
import { TopologyDraftStore } from './topology-draft-store.js';
|
|
23
33
|
import { TopologyPromoteService } from './topology-promote.js';
|
|
34
|
+
import { RoomLayoutDefinitions } from '../application/room-layout-definitions.js';
|
|
35
|
+
import { PresetProvenance } from '../application/preset-provenance.js';
|
|
24
36
|
import { doctor } from '../doctor.js';
|
|
25
37
|
import { acquireWebServerLock } from './lock.js';
|
|
38
|
+
import { WorkspaceDeviceStore } from './workspace-devices.js';
|
|
26
39
|
import { TrustedDeviceStore } from './device-store.js';
|
|
27
40
|
import { WebAuth } from './auth.js';
|
|
28
41
|
import { startWebControlServer } from './control.js';
|
|
@@ -47,19 +60,31 @@ function cachedConfigProvider(configPath) {
|
|
|
47
60
|
};
|
|
48
61
|
}
|
|
49
62
|
export async function startWebConsole(options) {
|
|
63
|
+
const notificationConfig = producerConfig();
|
|
50
64
|
const requestedPort = options.port ?? 49_271;
|
|
51
65
|
if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65_535)
|
|
52
66
|
throw new FleetError('invalid_request', 'port must be between 0 and 65535');
|
|
53
|
-
const
|
|
54
|
-
const
|
|
67
|
+
const webDir = options.webStateDir ?? resolve(stateRoot(), 'web');
|
|
68
|
+
const lock = acquireWebServerLock(webDir);
|
|
55
69
|
const bind = options.bind ?? '127.0.0.1';
|
|
56
70
|
const publicOrigin = options.publicOrigin ? validatePublicOrigin(options.publicOrigin) : undefined;
|
|
71
|
+
const workspaceGateway = Boolean(publicOrigin && existsSync(resolve(stateRoot(), 'workspace', 'binding.json')));
|
|
57
72
|
if (!isLoopback(bind) && !publicOrigin) {
|
|
58
73
|
lock.release();
|
|
59
74
|
throw new FleetError('forbidden', 'a non-loopback bind requires an explicit --public-origin');
|
|
60
75
|
}
|
|
61
76
|
const access = options.access ?? new WebAccessStore(webDir).read();
|
|
62
|
-
const
|
|
77
|
+
const bindingFile = resolve(stateRoot(), 'workspace', 'binding.json');
|
|
78
|
+
let appOrigin = 'https://app.ours.network';
|
|
79
|
+
try {
|
|
80
|
+
if (existsSync(bindingFile))
|
|
81
|
+
appOrigin = validateAccountOrigin(JSON.parse(readFileSync(bindingFile, 'utf8')).appOrigin ?? appOrigin);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
lock.release();
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
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
88
|
const backend = pickBackend();
|
|
64
89
|
const repository = new RoleRepository({
|
|
65
90
|
configPath: options.configPath,
|
|
@@ -159,8 +184,17 @@ export async function startWebConsole(options) {
|
|
|
159
184
|
try {
|
|
160
185
|
server = await buildWebServer({
|
|
161
186
|
query, repository, logs, commands, creation, removal, audit, events, watchdogs, configuration,
|
|
162
|
-
taskRooms: new TaskRoomApplicationService(options.configPath),
|
|
187
|
+
taskRooms: new TaskRoomApplicationService(options.configPath, { binPath: () => options.binPath }),
|
|
188
|
+
roomLayouts: new RoomLayoutDefinitions(options.configPath),
|
|
189
|
+
layoutBindings: new LayoutBindingGrants(new RoomLayoutService(options.configPath).supervisor()),
|
|
190
|
+
presetProvenance: new PresetProvenance(options.configPath),
|
|
163
191
|
topology: readTopology, topologyDrafts, topologyPromote,
|
|
192
|
+
subscriptions: new SubscriptionService({
|
|
193
|
+
agents: async () => (await query.list(true)).map(({ role, status }) => ({
|
|
194
|
+
roleId: role.id, stateDir: repository.stateDir(role), running: status.supervisor.liveness === 'running',
|
|
195
|
+
})),
|
|
196
|
+
onChange: () => events.publish('subscription.changed', {}),
|
|
197
|
+
}),
|
|
164
198
|
async session(roleId) {
|
|
165
199
|
const role = await repository.get(roleId);
|
|
166
200
|
if (!role)
|
|
@@ -174,7 +208,7 @@ export async function startWebConsole(options) {
|
|
|
174
208
|
}
|
|
175
209
|
throw new FleetError('capability_unavailable', 'role session backend is unavailable');
|
|
176
210
|
},
|
|
177
|
-
}, { origin: `http://127.0.0.1:${requestedPort}`, host: `127.0.0.1:${requestedPort}` }, { auth });
|
|
211
|
+
}, { origin: `http://127.0.0.1:${requestedPort}`, host: `127.0.0.1:${requestedPort}` }, { auth, staticRoot: options.staticRoot });
|
|
178
212
|
}
|
|
179
213
|
catch (error) {
|
|
180
214
|
lock.release();
|
|
@@ -182,7 +216,7 @@ export async function startWebConsole(options) {
|
|
|
182
216
|
}
|
|
183
217
|
let address;
|
|
184
218
|
try {
|
|
185
|
-
address = await server.app.listen({ host: bind, port: requestedPort });
|
|
219
|
+
address = await server.app.listen({ host: workspaceGateway ? '127.0.0.1' : bind, port: workspaceGateway ? 0 : requestedPort });
|
|
186
220
|
}
|
|
187
221
|
catch (error) {
|
|
188
222
|
await server.close();
|
|
@@ -193,27 +227,45 @@ export async function startWebConsole(options) {
|
|
|
193
227
|
const localHost = `127.0.0.1:${actual.port}`;
|
|
194
228
|
const browserOrigin = publicOrigin?.origin ?? `http://${localHost}`;
|
|
195
229
|
const browserHost = publicOrigin?.host ?? localHost;
|
|
230
|
+
let gateway;
|
|
231
|
+
if (workspaceGateway)
|
|
232
|
+
try {
|
|
233
|
+
const profile = readClientProfile(), provider = new URL(profile.serverUrl), credential = readPrivateFile(profile.credentialPath, 4096).toString('utf8').trim();
|
|
234
|
+
const basePath = provider.pathname.replace(/\/$/, '');
|
|
235
|
+
const services = ['daemon', 'cowork', 'messenger'].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 } : {}) } }));
|
|
236
|
+
gateway = createPrefixGateway({ auth: server.auth, fleetOrigin: address, services });
|
|
237
|
+
await new Promise((resolve, reject) => { gateway.server.once('error', reject); gateway.server.listen(requestedPort, bind, () => { gateway.server.off('error', reject); resolve(); }); });
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
await gateway?.close().catch(() => { });
|
|
241
|
+
await server.close();
|
|
242
|
+
lock.release();
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
196
245
|
server.auth.setBoundary(browserOrigin, browserHost, publicOrigin ? {
|
|
197
246
|
// nginx's safe default uses the loopback upstream as Host. The declared
|
|
198
247
|
// browser Origin remains mandatory for auth/mutations and WebSocket hello,
|
|
199
248
|
// so operators do not need a fragile Host-rewrite incantation.
|
|
200
|
-
hosts: [localHost, `localhost:${actual.port}`],
|
|
249
|
+
hosts: [localHost, `localhost:${actual.port}`, ...(workspaceGateway ? [`127.0.0.1:${requestedPort}`, `localhost:${requestedPort}`] : [])],
|
|
201
250
|
} : {
|
|
202
251
|
hosts: [`localhost:${actual.port}`], origins: [`http://localhost:${actual.port}`],
|
|
203
252
|
});
|
|
204
253
|
let control;
|
|
205
254
|
try {
|
|
206
|
-
control
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
255
|
+
if (options.control !== false)
|
|
256
|
+
control = await startWebControlServer({
|
|
257
|
+
dir: webDir,
|
|
258
|
+
onOpen() {
|
|
259
|
+
const url = access.mode === 'pairing'
|
|
260
|
+
? `${browserOrigin}/#bootstrap=${server.auth.mintBootstrap()}` : `${browserOrigin}/`;
|
|
261
|
+
openBrowser(url);
|
|
262
|
+
},
|
|
263
|
+
onLinkDevice() { return { version: 1, origin: browserOrigin, ...server.auth.workspaceEnrollment() }; },
|
|
264
|
+
onRevokeAll() { server.auth.revokeAllTrustedDevices(); },
|
|
265
|
+
});
|
|
215
266
|
}
|
|
216
267
|
catch (error) {
|
|
268
|
+
await gateway?.close();
|
|
217
269
|
await server.close();
|
|
218
270
|
lock.release();
|
|
219
271
|
throw error;
|
|
@@ -221,11 +273,30 @@ export async function startWebConsole(options) {
|
|
|
221
273
|
if (options.open !== false)
|
|
222
274
|
openBrowser(access.mode === 'pairing'
|
|
223
275
|
? `${browserOrigin}/#bootstrap=${server.auth.bootstrapSecret}` : `${browserOrigin}/`);
|
|
276
|
+
let stopTunnel;
|
|
277
|
+
try {
|
|
278
|
+
stopTunnel = startWorkspaceTunnel(browserOrigin);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
try {
|
|
282
|
+
await control?.close();
|
|
283
|
+
await gateway?.close();
|
|
284
|
+
await server.close();
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
lock.release();
|
|
288
|
+
}
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
const notifications = notificationConfig ? new FleetNotificationProducer([agentsRoot(), tmpRoot()], resolve(webDir, 'notification-outboxes'), notificationConfig, options.log) : undefined;
|
|
224
292
|
return {
|
|
225
293
|
...server, address: browserOrigin,
|
|
226
294
|
async close() {
|
|
227
295
|
try {
|
|
228
|
-
await
|
|
296
|
+
await stopTunnel();
|
|
297
|
+
await notifications?.close();
|
|
298
|
+
await control?.close();
|
|
299
|
+
await gateway?.close();
|
|
229
300
|
await server.close();
|
|
230
301
|
}
|
|
231
302
|
finally {
|
package/dist/web/server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SupervisorOursTools } from '../application/supervisor-ours-tools.js';
|
|
1
2
|
import { type FastifyInstance } from 'fastify';
|
|
2
3
|
import type { FleetQueryService } from '../application/fleet-query-service.js';
|
|
3
4
|
import type { RoleRepository } from '../application/role-repository.js';
|
|
@@ -15,7 +16,12 @@ import type { TopologyDraftStore } from './topology-draft-store.js';
|
|
|
15
16
|
import type { TopologyPromoteService } from './topology-promote.js';
|
|
16
17
|
import type { RoleRemovalService } from '../application/role-removal-service.js';
|
|
17
18
|
import type { TaskRoomApplicationService } from '../application/task-room-service.js';
|
|
19
|
+
import type { RoomLayoutDefinitions } from '../application/room-layout-definitions.js';
|
|
20
|
+
import type { PresetProvenance } from '../application/preset-provenance.js';
|
|
21
|
+
import type { SubscriptionService } from '../subscriptions/service.js';
|
|
22
|
+
import type { LayoutBindingGrants } from '../rooms-tasks/layout-binding-grants.js';
|
|
18
23
|
export interface WebServices {
|
|
24
|
+
layoutBindings?: Pick<LayoutBindingGrants, 'control'>;
|
|
19
25
|
query: FleetQueryService;
|
|
20
26
|
repository: RoleRepository;
|
|
21
27
|
session(roleId: string): Promise<RoleSessionControl>;
|
|
@@ -31,6 +37,10 @@ export interface WebServices {
|
|
|
31
37
|
topologyPromote?: TopologyPromoteService;
|
|
32
38
|
removal?: RoleRemovalService;
|
|
33
39
|
taskRooms?: TaskRoomApplicationService;
|
|
40
|
+
roomLayouts?: RoomLayoutDefinitions;
|
|
41
|
+
presetProvenance?: PresetProvenance;
|
|
42
|
+
oursTools?: Pick<SupervisorOursTools, 'list' | 'call'>;
|
|
43
|
+
subscriptions?: SubscriptionService;
|
|
34
44
|
}
|
|
35
45
|
export interface WebServer {
|
|
36
46
|
app: FastifyInstance;
|
|
@@ -44,4 +54,5 @@ export declare function buildWebServer(services: WebServices, boundary: {
|
|
|
44
54
|
host: string;
|
|
45
55
|
}, options?: {
|
|
46
56
|
auth?: WebAuth;
|
|
57
|
+
staticRoot?: string;
|
|
47
58
|
}): Promise<WebServer>;
|