@xfey/tutti 0.1.28 → 0.1.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 +1 -1
- package/dist/collaboration-state/index.d.ts +1 -1
- package/dist/collaboration-state/index.js +1 -1
- package/dist/collaboration-state/messages.d.ts +1 -0
- package/dist/collaboration-state/messages.js +12 -0
- package/dist/control-plane/index.d.ts +0 -1
- package/dist/control-plane/index.js +16 -49
- package/dist/control-plane/task-compile-output.js +2 -54
- package/dist/control-plane/worklist-terminal-feedback.d.ts +9 -1
- package/dist/control-plane/worklist-terminal-feedback.js +99 -1
- package/dist/server-shell/cli/args.d.ts +9 -0
- package/dist/server-shell/cli/args.js +55 -5
- package/dist/server-shell/cli/cli.js +46 -7
- package/dist/server-shell/cli/errors.d.ts +1 -1
- package/dist/server-shell/cli/errors.js +6 -1
- package/dist/server-shell/cli/host-lifecycle.d.ts +1 -0
- package/dist/server-shell/cli/host-lifecycle.js +155 -41
- package/dist/server-shell/cli/host-relay-connection-manager.d.ts +50 -0
- package/dist/server-shell/cli/host-relay-connection-manager.js +447 -0
- package/dist/server-shell/cli/host-relay-status.d.ts +4 -0
- package/dist/server-shell/cli/host-relay-status.js +74 -15
- package/dist/server-shell/cli/host-runtime-endpoint.js +26 -0
- package/dist/server-shell/cli/host-server-runtime.d.ts +2 -2
- package/dist/server-shell/cli/host-server-runtime.js +19 -15
- package/dist/server-shell/cli/launch-command.d.ts +1 -0
- package/dist/server-shell/cli/launch-command.js +8 -10
- package/dist/server-shell/cli/launch.d.ts +1 -0
- package/dist/server-shell/cli/launch.js +7 -3
- package/dist/server-shell/cli/local-control-client.d.ts +5 -0
- package/dist/server-shell/cli/local-control-client.js +9 -0
- package/dist/server-shell/cli/machine-local.d.ts +8 -0
- package/dist/server-shell/cli/machine-local.js +29 -0
- package/dist/server-shell/cli/machine-project-inspector.d.ts +81 -0
- package/dist/server-shell/cli/machine-project-inspector.js +205 -0
- package/dist/server-shell/cli/project-resolver.js +22 -32
- package/dist/server-shell/cli/relay-registration.d.ts +5 -2
- package/dist/server-shell/cli/relay-registration.js +7 -3
- package/dist/server-shell/cli/runtime-commands.d.ts +30 -4
- package/dist/server-shell/cli/runtime-commands.js +230 -113
- package/dist/server-shell/http/routes/local-control.d.ts +10 -0
- package/dist/server-shell/http/routes/local-control.js +29 -0
- package/migrations/README.md +1 -1
- package/node_modules/@tutti/relay-client/dist/host-control.d.ts +23 -0
- package/node_modules/@tutti/relay-client/dist/host-control.js +70 -3
- package/node_modules/@tutti/relay-client/dist/tunnel-types.d.ts +6 -2
- package/node_modules/@tutti/relay-client/dist/tunnel.js +13 -2
- package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +21 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +105 -0
- package/node_modules/@tutti/shared/dist/schemas/api/messages.js +16 -0
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +2 -1
- package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +21 -0
- package/package.json +1 -1
- package/prompts/prompt-flow-map.md +1 -1
- package/prompts/runs/README.md +1 -1
- package/prompts/runs/task-continuation.md +1 -1
- package/prompts/runs/task-retry.md +1 -1
- package/prompts/runs/task-run.md +1 -1
- package/web/assets/index-BHRlhFwi.css +1 -0
- package/web/assets/index-DPU2uqZl.js +29 -0
- package/web/index.html +2 -2
- package/web/assets/index-CeGTRkQn.css +0 -1
- package/web/assets/index-DGnDFv9O.js +0 -29
|
@@ -4,15 +4,19 @@ import { createHostServer } from "../http/create-server.js";
|
|
|
4
4
|
import { createHostProjectApiTunnelRequestHandler, createHostProjectApiTunnelStreamRequestHandler, } from "../http/host-tunnel.js";
|
|
5
5
|
import { LaunchError } from "./errors.js";
|
|
6
6
|
import { TUTTI_MAINLINE_BRANCH } from "./git-bootstrap.js";
|
|
7
|
-
import {
|
|
7
|
+
import { hostLocalLaunchStatusFromConnectionManagerState, relayStateFromConnectionManagerState, relayStatusFromLocalStatus, } from "./host-relay-status.js";
|
|
8
|
+
import { createHostRelayConnectionManager, } from "./host-relay-connection-manager.js";
|
|
8
9
|
import { createRuntimeEndpointProbe, readExistingHostLaunchStatus, requestExistingHostShutdown, } from "./host-runtime-endpoint.js";
|
|
10
|
+
import { reconnectHostLocalRelay } from "./local-control-client.js";
|
|
9
11
|
import { HOST_SERVER_VERSION, startForegroundHostServer, } from "./host-server-runtime.js";
|
|
10
12
|
import { deleteMachineRuntimeEndpoint, readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "./machine-local.js";
|
|
13
|
+
import { inspectMachineProject, inspectMachineProjectInventories, normalizeMachineWorkspacePath, } from "./machine-project-inspector.js";
|
|
11
14
|
import { prepareLaunchProject, resolveLaunchLocalContext, } from "./launch.js";
|
|
12
15
|
import { registerHostWithRelay, relayErrorToLaunchError, } from "./relay-registration.js";
|
|
13
16
|
import { formatJoinLinkValidity, formatTerminalLink } from "./terminal-qr.js";
|
|
14
17
|
export { createRuntimeEndpointProbe } from "./host-runtime-endpoint.js";
|
|
15
18
|
const DEFAULT_SHUTDOWN_WAIT_MS = 2_000;
|
|
19
|
+
const DEFAULT_RECONNECT_WAIT_MS = 8_000;
|
|
16
20
|
function urlLine(label, value, options) {
|
|
17
21
|
return `${label}: ${formatTerminalLink(value, options)}`;
|
|
18
22
|
}
|
|
@@ -72,21 +76,106 @@ function createSummary(options) {
|
|
|
72
76
|
}
|
|
73
77
|
return summary;
|
|
74
78
|
}
|
|
79
|
+
async function reconcileReplacedWorkspaceHosts(options) {
|
|
80
|
+
const normalizedWorkspaceRoot = normalizeMachineWorkspacePath(options.workspaceRoot);
|
|
81
|
+
const replacedProjects = inspectMachineProjectInventories(options.tuttiHome).filter((inspection) => inspection.project_id !== options.projectId &&
|
|
82
|
+
inspection.normalized_workspace_root === normalizedWorkspaceRoot &&
|
|
83
|
+
inspection.workspace.kind === "replaced" &&
|
|
84
|
+
inspection.workspace.actual_project_id === options.projectId &&
|
|
85
|
+
inspection.endpoint.kind === "valid" &&
|
|
86
|
+
normalizeMachineWorkspacePath(inspection.endpoint.record.workspace_root) ===
|
|
87
|
+
normalizedWorkspaceRoot);
|
|
88
|
+
for (const replacedProject of replacedProjects) {
|
|
89
|
+
const inspection = await inspectMachineProject(replacedProject.project_id, {
|
|
90
|
+
tuttiHome: options.tuttiHome,
|
|
91
|
+
fetchImpl: options.fetchImpl,
|
|
92
|
+
probeRuntimeEndpoint: options.probeRuntimeEndpoint,
|
|
93
|
+
});
|
|
94
|
+
if (inspection.endpoint.kind !== "valid") {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if ((inspection.host.kind === "running" && !inspection.host.identity_verified) ||
|
|
98
|
+
(inspection.host.kind === "stale" &&
|
|
99
|
+
inspection.host.reason_code === "local_control_unavailable")) {
|
|
100
|
+
throw new LaunchError("takeover_failed", "The replaced project host identity could not be verified before shutdown", "Stop the old host manually, then run `tutti launch` again.");
|
|
101
|
+
}
|
|
102
|
+
const shouldRequestShutdown = inspection.host.kind === "running";
|
|
103
|
+
if (shouldRequestShutdown) {
|
|
104
|
+
await requestExistingHostShutdown({
|
|
105
|
+
endpoint: inspection.endpoint.record,
|
|
106
|
+
fetchImpl: options.fetchImpl,
|
|
107
|
+
probeRuntimeEndpoint: options.probeRuntimeEndpoint,
|
|
108
|
+
shutdownWaitMs: options.shutdownWaitMs,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (shouldRequestShutdown ||
|
|
112
|
+
inspection.host.kind === "stale" ||
|
|
113
|
+
inspection.host.kind === "identity_mismatch") {
|
|
114
|
+
deleteMachineRuntimeEndpoint({
|
|
115
|
+
tuttiHome: options.tuttiHome,
|
|
116
|
+
projectId: inspection.project_id,
|
|
117
|
+
expectedToken: inspection.endpoint.record.token,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
75
122
|
export async function startLaunchProject(options) {
|
|
76
123
|
const context = resolveLaunchLocalContext(options);
|
|
77
124
|
const fetchImpl = options.fetch ?? fetch;
|
|
78
125
|
const probeRuntimeEndpoint = options.probeRuntimeEndpoint ?? createRuntimeEndpointProbe(fetchImpl);
|
|
79
126
|
const shutdownWaitMs = options.shutdownWaitMs ?? DEFAULT_SHUTDOWN_WAIT_MS;
|
|
127
|
+
const reconnectWaitMs = options.reconnectWaitMs ?? DEFAULT_RECONNECT_WAIT_MS;
|
|
80
128
|
let startupStatus = context.existing_identity.kind === "missing" ? "created" : "recovered";
|
|
81
129
|
let allowWorkspaceRootTakeover = false;
|
|
82
130
|
if (context.existing_identity.kind === "present") {
|
|
83
131
|
const projectId = context.existing_identity.project_id;
|
|
132
|
+
await reconcileReplacedWorkspaceHosts({
|
|
133
|
+
tuttiHome: context.tutti_home,
|
|
134
|
+
workspaceRoot: context.workspace_root,
|
|
135
|
+
projectId,
|
|
136
|
+
fetchImpl,
|
|
137
|
+
probeRuntimeEndpoint,
|
|
138
|
+
shutdownWaitMs,
|
|
139
|
+
});
|
|
84
140
|
const endpoint = readMachineRuntimeEndpoint(context.tutti_home, projectId);
|
|
85
141
|
if (endpoint !== null) {
|
|
86
142
|
const sameWorkspace = resolve(endpoint.workspace_root) === context.workspace_root;
|
|
87
143
|
const probe = await probeRuntimeEndpoint(endpoint);
|
|
88
144
|
if (probe.kind === "alive" && sameWorkspace) {
|
|
89
|
-
|
|
145
|
+
let launchStatus = await readExistingHostLaunchStatus({ endpoint, fetchImpl });
|
|
146
|
+
if (launchStatus.project_id !== projectId) {
|
|
147
|
+
throw new LaunchError("existing_server_unhealthy", "The existing Tutti host identity could not be verified for this project", "Stop the existing host manually, then run `tutti launch` again.");
|
|
148
|
+
}
|
|
149
|
+
const relayConnected = launchStatus.relay_connection?.status === "connected" ||
|
|
150
|
+
(launchStatus.relay_connection === undefined && probe.readiness?.relay === "connected");
|
|
151
|
+
if (!relayConnected) {
|
|
152
|
+
try {
|
|
153
|
+
launchStatus = await reconnectHostLocalRelay({
|
|
154
|
+
endpoint,
|
|
155
|
+
fetchImpl,
|
|
156
|
+
timeoutMs: reconnectWaitMs,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
throw new LaunchError("relay_unavailable", "The existing Tutti host could not complete a bounded Relay reconnect", "The host is still running. Check Relay connectivity, then run `tutti launch` again.");
|
|
161
|
+
}
|
|
162
|
+
if (launchStatus.project_id !== projectId) {
|
|
163
|
+
throw new LaunchError("existing_server_unhealthy", "The reconnected Tutti host identity does not match this project", "The existing host was preserved. Stop it manually, then run `tutti launch` again.");
|
|
164
|
+
}
|
|
165
|
+
const reconnectedProbe = await probeRuntimeEndpoint(endpoint);
|
|
166
|
+
const reconnected = launchStatus.relay_connection?.status === "connected" ||
|
|
167
|
+
(launchStatus.relay_connection === undefined &&
|
|
168
|
+
reconnectedProbe.kind === "alive" &&
|
|
169
|
+
reconnectedProbe.readiness?.relay === "connected");
|
|
170
|
+
if (!reconnected) {
|
|
171
|
+
throw new LaunchError("relay_unavailable", "The existing Tutti host is running but Relay reconnection is not complete", "The host was preserved. Check `tutti ps` or `tutti doctor`, then retry launch.", {
|
|
172
|
+
relay_status: launchStatus.relay_connection?.status ?? "not_connected",
|
|
173
|
+
...(launchStatus.relay_connection?.reason_code === undefined
|
|
174
|
+
? {}
|
|
175
|
+
: { reason_code: launchStatus.relay_connection.reason_code }),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
90
179
|
const relay = relayStatusFromLocalStatus(launchStatus);
|
|
91
180
|
return {
|
|
92
181
|
kind: "connected",
|
|
@@ -154,58 +243,55 @@ export async function startLaunchProject(options) {
|
|
|
154
243
|
...options,
|
|
155
244
|
allowWorkspaceRootTakeover,
|
|
156
245
|
});
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
const hostSecret = readHostRegistrationSecret(preparation.tutti_home, preparation.project_id);
|
|
163
|
-
const response = await refreshRelayJoinTokenWithRelay({
|
|
164
|
-
relayUrl: preparation.relay_url,
|
|
246
|
+
if (context.existing_identity.kind === "missing") {
|
|
247
|
+
await reconcileReplacedWorkspaceHosts({
|
|
248
|
+
tuttiHome: preparation.tutti_home,
|
|
249
|
+
workspaceRoot: preparation.workspace_root,
|
|
165
250
|
projectId: preparation.project_id,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
joinToken: { mode: "rotate" },
|
|
170
|
-
fetch: fetchImpl,
|
|
251
|
+
fetchImpl,
|
|
252
|
+
probeRuntimeEndpoint,
|
|
253
|
+
shutdownWaitMs,
|
|
171
254
|
});
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
255
|
+
}
|
|
256
|
+
const relayRuntime = {};
|
|
257
|
+
const readConnectionState = () => relayRuntime.connectionManager?.getState() ?? {
|
|
258
|
+
status: "unavailable",
|
|
259
|
+
reason_code: "not_started",
|
|
260
|
+
};
|
|
261
|
+
const getLaunchStatus = () => hostLocalLaunchStatusFromConnectionManagerState(preparation.project_id, readConnectionState());
|
|
262
|
+
const refreshRelayJoinUrl = async () => {
|
|
263
|
+
if (relayRuntime.connectionManager === undefined) {
|
|
264
|
+
return relayStateFromConnectionManagerState(preparation.relay_url, readConnectionState());
|
|
265
|
+
}
|
|
266
|
+
await relayRuntime.connectionManager.refreshJoinUrl();
|
|
267
|
+
return relayStateFromConnectionManagerState(preparation.relay_url, relayRuntime.connectionManager.getState());
|
|
184
268
|
};
|
|
185
269
|
const host = await startForegroundHostServer({
|
|
186
270
|
preparation,
|
|
187
271
|
endpointHost: context.local_diagnostic_endpoint.host,
|
|
188
272
|
endpointPort: context.local_diagnostic_endpoint.port,
|
|
189
273
|
createServer: options.createServer ?? createHostServer,
|
|
190
|
-
getLaunchStatus
|
|
274
|
+
getLaunchStatus,
|
|
191
275
|
refreshRelayJoinUrl,
|
|
192
276
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
193
277
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
278
|
+
const connectionManager = createHostRelayConnectionManager({
|
|
279
|
+
tuttiHome: preparation.tutti_home,
|
|
280
|
+
projectId: preparation.project_id,
|
|
281
|
+
workspaceRoot: preparation.workspace_root,
|
|
282
|
+
registerConnection: async ({ joinTokenMode, signal }) => await registerHostWithRelay({
|
|
197
283
|
preparation,
|
|
198
284
|
fetchImpl,
|
|
199
285
|
registerRelayHostConnection: options.registerRelayHostConnection ?? registerRelayHostConnectionWithRelay,
|
|
200
286
|
serverVersion: HOST_SERVER_VERSION,
|
|
287
|
+
joinTokenMode,
|
|
288
|
+
signal,
|
|
201
289
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
relayProjectRef: relay.relay_project_ref,
|
|
208
|
-
hostConnectionRef: relay.host_connection_ref,
|
|
290
|
+
}),
|
|
291
|
+
connectTunnel: (registered) => (options.connectRelayHostTunnel ?? connectRelayHostTunnel)({
|
|
292
|
+
tunnelUrl: registered.summary.tunnel_url,
|
|
293
|
+
relayProjectRef: registered.summary.relay_project_ref,
|
|
294
|
+
hostConnectionRef: registered.summary.host_connection_ref,
|
|
209
295
|
hostConnectionToken: registered.hostConnectionToken,
|
|
210
296
|
handleRequest: createHostProjectApiTunnelRequestHandler({
|
|
211
297
|
app: host.app,
|
|
@@ -215,9 +301,37 @@ export async function startLaunchProject(options) {
|
|
|
215
301
|
baseUrl: host.runtime_endpoint.base_url,
|
|
216
302
|
trustedMetadataStore: host.trusted_relay_metadata_store,
|
|
217
303
|
}),
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
|
|
304
|
+
}),
|
|
305
|
+
refreshJoinUrl: async (current) => {
|
|
306
|
+
const hostSecret = readHostRegistrationSecret(preparation.tutti_home, preparation.project_id);
|
|
307
|
+
const response = await refreshRelayJoinTokenWithRelay({
|
|
308
|
+
relayUrl: preparation.relay_url,
|
|
309
|
+
projectId: preparation.project_id,
|
|
310
|
+
relayProjectRef: current.relay_project_ref,
|
|
311
|
+
hostConnectionRef: current.host_connection_ref,
|
|
312
|
+
hostRegistrationSecret: hostSecret.secret,
|
|
313
|
+
joinToken: { mode: "rotate" },
|
|
314
|
+
fetch: fetchImpl,
|
|
315
|
+
});
|
|
316
|
+
const withoutJoinUrl = { ...current };
|
|
317
|
+
delete withoutJoinUrl.join_url;
|
|
318
|
+
return {
|
|
319
|
+
...withoutJoinUrl,
|
|
320
|
+
...(response.join_token.join_url === undefined
|
|
321
|
+
? {}
|
|
322
|
+
: { join_url: response.join_token.join_url }),
|
|
323
|
+
join_url_visibility: response.join_token.visibility,
|
|
324
|
+
join_token_expires_at: response.join_token.expires_at,
|
|
325
|
+
join_token_reusable: response.join_token.reusable,
|
|
326
|
+
};
|
|
327
|
+
},
|
|
328
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
329
|
+
});
|
|
330
|
+
relayRuntime.connectionManager = connectionManager;
|
|
331
|
+
host.attachRelayConnectionManager(connectionManager);
|
|
332
|
+
let relay;
|
|
333
|
+
try {
|
|
334
|
+
relay = await connectionManager.start();
|
|
221
335
|
}
|
|
222
336
|
catch (error) {
|
|
223
337
|
const launchError = error instanceof LaunchError || error instanceof RelayHostControlClientError
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type RelayHostTunnelHandle } from "@tutti/relay-client";
|
|
2
|
+
import type { ProjectId } from "@tutti/shared/ids";
|
|
3
|
+
import type { HostLaunchRelaySummary, RegisteredRelayHostConnection } from "./relay-registration.js";
|
|
4
|
+
export type HostRelayConnectionManagerState = {
|
|
5
|
+
status: "unavailable";
|
|
6
|
+
reason_code: "not_started" | "relay_unavailable" | "workspace_missing" | "workspace_replaced" | "workspace_unreadable" | "fatal_error";
|
|
7
|
+
error_code?: string;
|
|
8
|
+
next_retry_at?: string;
|
|
9
|
+
last_connection?: HostLaunchRelaySummary;
|
|
10
|
+
} | {
|
|
11
|
+
status: "connecting";
|
|
12
|
+
attempt: 0;
|
|
13
|
+
} | {
|
|
14
|
+
status: "connected";
|
|
15
|
+
connection: HostLaunchRelaySummary;
|
|
16
|
+
} | {
|
|
17
|
+
status: "reconnecting";
|
|
18
|
+
attempt: number;
|
|
19
|
+
next_retry_at?: string;
|
|
20
|
+
last_connected_at?: string;
|
|
21
|
+
last_connection?: HostLaunchRelaySummary;
|
|
22
|
+
} | {
|
|
23
|
+
status: "stopped";
|
|
24
|
+
};
|
|
25
|
+
export type HostRelayConnectionManager = {
|
|
26
|
+
start: () => Promise<HostLaunchRelaySummary>;
|
|
27
|
+
reconnectNow: () => Promise<HostRelayConnectionManagerState>;
|
|
28
|
+
refreshJoinUrl: () => Promise<HostLaunchRelaySummary>;
|
|
29
|
+
getState: () => HostRelayConnectionManagerState;
|
|
30
|
+
stop: () => Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
export type CreateHostRelayConnectionManagerOptions = {
|
|
33
|
+
tuttiHome: string;
|
|
34
|
+
projectId: ProjectId;
|
|
35
|
+
workspaceRoot: string;
|
|
36
|
+
registerConnection: (options: {
|
|
37
|
+
joinTokenMode: "reuse_active" | "rotate";
|
|
38
|
+
signal: AbortSignal;
|
|
39
|
+
}) => Promise<RegisteredRelayHostConnection>;
|
|
40
|
+
connectTunnel: (registered: RegisteredRelayHostConnection) => RelayHostTunnelHandle;
|
|
41
|
+
refreshJoinUrl?: (connection: HostLaunchRelaySummary) => Promise<HostLaunchRelaySummary>;
|
|
42
|
+
onStateChange?: (state: HostRelayConnectionManagerState) => void;
|
|
43
|
+
retryDelaysMs?: readonly number[];
|
|
44
|
+
jitterRatio?: number;
|
|
45
|
+
workspaceCheckIntervalMs?: number;
|
|
46
|
+
now?: () => Date;
|
|
47
|
+
random?: () => number;
|
|
48
|
+
};
|
|
49
|
+
export declare function createHostRelayConnectionManager(options: CreateHostRelayConnectionManagerOptions): HostRelayConnectionManager;
|
|
50
|
+
//# sourceMappingURL=host-relay-connection-manager.d.ts.map
|