oysterun 1.5.6 → 1.5.7-beta.1

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.
Files changed (67) hide show
  1. package/README.md +5 -3
  2. package/bin/oysterun.mjs +3 -0
  3. package/dev/client/connectivity/data-channel-adapter.mjs +146 -0
  4. package/dev/client/connectivity/logical-host-connection.mjs +358 -0
  5. package/dev/client/connectivity/managed-host-browser.d.mts +29 -0
  6. package/dev/client/connectivity/managed-host-browser.mjs +838 -0
  7. package/dev/client/connectivity/random-id.mjs +7 -0
  8. package/dev/client/connectivity/webrtc-frame.mjs +87 -0
  9. package/dev/client/connectivity/webrtc-logical-transport.mjs +576 -0
  10. package/dev/client/web/index.html +4075 -105
  11. package/dev/client/web-chat/dist/assets/{ReactPrism-isi6q-rt.js → ReactPrism-DoLvCMrh.js} +1 -1
  12. package/dev/client/web-chat/dist/assets/{ReactPrism-isi6q-rt.js.map → ReactPrism-DoLvCMrh.js.map} +1 -1
  13. package/dev/client/web-chat/dist/assets/{index-4B1xHv-h.css → index-BFaINJYC.css} +1 -1
  14. package/dev/client/web-chat/dist/assets/{index-Chh0JkSx.js → index-C678hy3a.js} +2 -2
  15. package/dev/client/web-chat/dist/assets/{index-Chh0JkSx.js.map → index-C678hy3a.js.map} +1 -1
  16. package/dev/client/web-chat/dist/assets/{index-CS0AuJg_.js → index-DV33g1cz.js} +91 -91
  17. package/dev/client/web-chat/dist/assets/index-DV33g1cz.js.map +1 -0
  18. package/dev/client/web-chat/dist/assets/{index-CLXxZ4tL.js → index-Dt_GUcgO.js} +1 -1
  19. package/dev/client/web-chat/dist/assets/{index-CLXxZ4tL.js.map → index-Dt_GUcgO.js.map} +1 -1
  20. package/dev/client/web-chat/dist/assets/{pdf-BHndlpDv.js → pdf-B2g0_7C_.js} +3 -3
  21. package/dev/client/web-chat/dist/assets/{pdf-BHndlpDv.js.map → pdf-B2g0_7C_.js.map} +1 -1
  22. package/dev/client/web-chat/dist/assets/{web-DL4-fcjv.js → web-CpWiQAsH.js} +1 -1
  23. package/dev/client/web-chat/dist/assets/{web-DL4-fcjv.js.map → web-CpWiQAsH.js.map} +1 -1
  24. package/dev/client/web-chat/dist/assets/{web-DD_lXOHn.js → web-DAiMK09O.js} +1 -1
  25. package/dev/client/web-chat/dist/assets/{web-DD_lXOHn.js.map → web-DAiMK09O.js.map} +1 -1
  26. package/dev/client/web-chat/dist/index.html +2 -2
  27. package/docs/routec/config_hierachy.md +5 -0
  28. package/docs/routec/dev/cloud_deplyments.md +315 -5
  29. package/docs/routec/dev/npm_native_dependency_install.md +26 -13
  30. package/docs/routec/dev/npmjs_publish.md +48 -15
  31. package/docs/routec/dev/provider_catalog_readiness.md +71 -0
  32. package/docs/routec/dev/scheduler_mail_skill.md +3 -0
  33. package/docs/routec/form_UI_fields.md +9 -4
  34. package/docs/routec/host_cloud_app_auth.md +5 -0
  35. package/docs/routec/host_setup.md +94 -64
  36. package/host-service/assets/voice-input/D1.wav +0 -0
  37. package/host-service/browser-root-project-index.mjs +0 -2
  38. package/host-service/config.mjs +111 -0
  39. package/host-service/connectivity/data-channel-adapter.mjs +1 -0
  40. package/host-service/connectivity/logical-host-connection.mjs +1 -0
  41. package/host-service/connectivity/node-webrtc-runtime.mjs +181 -0
  42. package/host-service/connectivity/random-id.mjs +1 -0
  43. package/host-service/connectivity/webrtc-frame.mjs +1 -0
  44. package/host-service/connectivity/webrtc-host-dispatcher.mjs +153 -0
  45. package/host-service/connectivity/webrtc-logical-transport.mjs +1 -0
  46. package/host-service/connectivity/webrtc-signaling-service.mjs +1101 -0
  47. package/host-service/folder-browser.mjs +8 -9
  48. package/host-service/frp-agent.mjs +848 -408
  49. package/host-service/package.json +10 -4
  50. package/host-service/portable-scheduler-definitions.mjs +16 -15
  51. package/host-service/provider-auth.mjs +10 -1
  52. package/host-service/routec-matrix-storage-adapter.mjs +4 -0
  53. package/host-service/server.mjs +873 -153
  54. package/host-service/setup.mjs +592 -96
  55. package/host-service/templates/demo-agents/oysterun-github-tracker/.oysterun/schedulers.json +0 -24
  56. package/host-service/voice-input-owner-capture.mjs +225 -0
  57. package/host-service/voice-input-recording-collector.mjs +369 -0
  58. package/host-service/voice-input-service.mjs +755 -0
  59. package/host-service/voice-input-usage-contract.mjs +63 -0
  60. package/host-service/voice-input-usage-store.mjs +409 -0
  61. package/package.json +6 -2
  62. package/tool_scripts/oysterun_npm_package_manifest.mjs +87 -0
  63. package/tool_scripts/oysterun_release_receipt_lib.mjs +47 -0
  64. package/tool_scripts/verify_oysterun_package_layout.mjs +172 -0
  65. package/tool_scripts/verify_oysterun_release_receipt.mjs +96 -0
  66. package/tool_scripts/verify_oysterun_runtime.mjs +94 -7
  67. package/dev/client/web-chat/dist/assets/index-CS0AuJg_.js.map +0 -1
package/README.md CHANGED
@@ -248,8 +248,9 @@ npm start
248
248
  Useful commands:
249
249
 
250
250
  ```bash
251
- npm run setup # first-run direct Host setup
252
- npm run setup -- --enable-cloud # optional Cloud registration
251
+ npm run setup # first-run Managed Tunnel Host setup
252
+ node host-service/setup.mjs # source-local Direct Host setup
253
+ node bin/oysterun.mjs setup --local # explicit local-only Direct setup
253
254
  npm start # plain start, no watch mode
254
255
  npm run dev # watch mode for Host JS files
255
256
  npm run cli # local CLI client
@@ -272,7 +273,8 @@ Port notes:
272
273
  - direct mode can also store an optional `public_base_url` there for manual client connection
273
274
  - fresh Session Setup runtime defaults, including interface style plus Claude/Codex provider defaults, are stored there under `session_defaults`
274
275
  - Host Preferences shows the active config path, so `OYSTERUN_CONFIG_DIR` overrides are visible in the UI
275
- - Host now defaults to direct Host mode unless Cloud registration is explicitly enabled
276
+ - release setup defaults to Oysterun Managed Tunnel
277
+ - source-local setup and `oysterun setup --local` retain explicit Direct mode
276
278
 
277
279
  ### Optional: start backend + Host together for repo development
278
280
 
package/bin/oysterun.mjs CHANGED
@@ -191,6 +191,9 @@ Product modules:
191
191
 
192
192
  First run:
193
193
  If Host config is missing or incomplete, 'oysterun' enters setup.
194
+ Release setup uses Oysterun Managed Tunnel by default.
195
+ Use 'oysterun setup --local' for local-only Direct Host setup.
196
+ Use '--enable-cloud-direct' only for advanced Direct Cloud registration.
194
197
 
195
198
  Install contract:
196
199
  npm install -g oysterun installs files only. It does not run setup or start
@@ -0,0 +1,146 @@
1
+ function toUint8Array(value) {
2
+ if (value instanceof Uint8Array) return value;
3
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
4
+ if (globalThis.Buffer?.isBuffer(value)) {
5
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
6
+ }
7
+ throw new Error("DataChannel message must be binary");
8
+ }
9
+
10
+ export function adaptBrowserDataChannel(channel) {
11
+ const messageListeners = new Set();
12
+ const openListeners = new Set();
13
+ const bufferedLowListeners = new Set();
14
+ const closeListeners = new Set();
15
+ const errorListeners = new Set();
16
+ channel.binaryType = "arraybuffer";
17
+
18
+ channel.addEventListener("message", (event) => {
19
+ try {
20
+ const bytes = toUint8Array(event.data);
21
+ for (const listener of messageListeners) listener(bytes);
22
+ } catch (error) {
23
+ for (const listener of errorListeners) listener(error);
24
+ }
25
+ });
26
+ channel.addEventListener("open", () => {
27
+ for (const listener of openListeners) listener();
28
+ });
29
+ channel.addEventListener("bufferedamountlow", () => {
30
+ for (const listener of bufferedLowListeners) listener();
31
+ });
32
+ channel.addEventListener("close", () => {
33
+ for (const listener of closeListeners) listener();
34
+ });
35
+ channel.addEventListener("error", (event) => {
36
+ const error = new Error(event?.error?.message || "Browser DataChannel error");
37
+ for (const listener of errorListeners) listener(error);
38
+ });
39
+
40
+ return {
41
+ label: channel.label,
42
+ isOpen: () => channel.readyState === "open",
43
+ bufferedAmount: () => channel.bufferedAmount,
44
+ setBufferedAmountLowThreshold(bytes) {
45
+ channel.bufferedAmountLowThreshold = bytes;
46
+ },
47
+ sendBinary(bytes) {
48
+ channel.send(bytes);
49
+ return { buffered: channel.bufferedAmount > 0 };
50
+ },
51
+ onMessage(listener) {
52
+ messageListeners.add(listener);
53
+ return () => messageListeners.delete(listener);
54
+ },
55
+ onOpen(listener) {
56
+ openListeners.add(listener);
57
+ return () => openListeners.delete(listener);
58
+ },
59
+ onBufferedAmountLow(listener) {
60
+ bufferedLowListeners.add(listener);
61
+ return () => bufferedLowListeners.delete(listener);
62
+ },
63
+ onClose(listener) {
64
+ closeListeners.add(listener);
65
+ return () => closeListeners.delete(listener);
66
+ },
67
+ onError(listener) {
68
+ errorListeners.add(listener);
69
+ return () => errorListeners.delete(listener);
70
+ },
71
+ close() {
72
+ channel.close();
73
+ },
74
+ };
75
+ }
76
+
77
+ export function adaptNodeDataChannel(channel) {
78
+ const messageListeners = new Set();
79
+ const openListeners = new Set();
80
+ const bufferedLowListeners = new Set();
81
+ const closeListeners = new Set();
82
+ const errorListeners = new Set();
83
+
84
+ channel.onMessage((message) => {
85
+ if (typeof message === "string") {
86
+ for (const listener of errorListeners) {
87
+ listener(new Error("Oysterun transport rejects text DataChannel frames"));
88
+ }
89
+ return;
90
+ }
91
+ const bytes = toUint8Array(message);
92
+ for (const listener of messageListeners) listener(bytes);
93
+ });
94
+ channel.onOpen(() => {
95
+ for (const listener of openListeners) listener();
96
+ });
97
+ channel.onBufferedAmountLow(() => {
98
+ for (const listener of bufferedLowListeners) listener();
99
+ });
100
+ channel.onClosed(() => {
101
+ for (const listener of closeListeners) listener();
102
+ });
103
+ channel.onError((message) => {
104
+ const error = new Error(String(message));
105
+ for (const listener of errorListeners) listener(error);
106
+ });
107
+
108
+ return {
109
+ label: channel.getLabel(),
110
+ isOpen: () => channel.isOpen(),
111
+ bufferedAmount: () => channel.bufferedAmount(),
112
+ setBufferedAmountLowThreshold: (bytes) =>
113
+ channel.setBufferedAmountLowThreshold(bytes),
114
+ sendBinary(bytes) {
115
+ // False means the message was accepted into the native send buffer.
116
+ // Retrying on false duplicates the frame.
117
+ return {
118
+ buffered:
119
+ channel.sendMessageBinary(globalThis.Buffer.from(bytes)) === false,
120
+ };
121
+ },
122
+ onMessage(listener) {
123
+ messageListeners.add(listener);
124
+ return () => messageListeners.delete(listener);
125
+ },
126
+ onOpen(listener) {
127
+ openListeners.add(listener);
128
+ return () => openListeners.delete(listener);
129
+ },
130
+ onBufferedAmountLow(listener) {
131
+ bufferedLowListeners.add(listener);
132
+ return () => bufferedLowListeners.delete(listener);
133
+ },
134
+ onClose(listener) {
135
+ closeListeners.add(listener);
136
+ return () => closeListeners.delete(listener);
137
+ },
138
+ onError(listener) {
139
+ errorListeners.add(listener);
140
+ return () => errorListeners.delete(listener);
141
+ },
142
+ close() {
143
+ channel.close();
144
+ },
145
+ };
146
+ }
@@ -0,0 +1,358 @@
1
+ import { createConnectivityRandomId } from "./random-id.mjs";
2
+
3
+ const MATRIX_SYNC_PATH = /^\/_matrix\/client\/[^/]+\/sync(?:\?|$)/;
4
+ const DEFAULT_TRANSPORT_RETIRE_TIMEOUT_MS = 35_000;
5
+
6
+ function deferred() {
7
+ let resolve;
8
+ const promise = new Promise((resolvePromise) => {
9
+ resolve = resolvePromise;
10
+ });
11
+ return { promise, resolve };
12
+ }
13
+
14
+ async function withTimeout(promise, timeoutMs, message) {
15
+ let timeoutId;
16
+ const timeout = new Promise((_, reject) => {
17
+ timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
18
+ });
19
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timeoutId));
20
+ }
21
+
22
+ function headersToObject(headers) {
23
+ return Object.fromEntries([...headers.entries()]);
24
+ }
25
+
26
+ export class BootstrapFetchTransport {
27
+ constructor({ fetchImpl = globalThis.fetch, origin = "", diagnostic } = {}) {
28
+ if (typeof fetchImpl !== "function") {
29
+ throw new Error("Bootstrap fetch implementation is required");
30
+ }
31
+ this.fetchImpl = fetchImpl;
32
+ this.origin = origin.replace(/\/+$/, "");
33
+ this.diagnostic = diagnostic;
34
+ this.pathKind = "bootstrap";
35
+ }
36
+
37
+ async request(request, { requestId }) {
38
+ const headers = new Headers(request.headers || {});
39
+ headers.set("X-Oysterun-Request-Id", requestId);
40
+ const response = await this.fetchImpl(`${this.origin}${request.path}`, {
41
+ method: request.method,
42
+ headers,
43
+ body: request.body,
44
+ credentials: "same-origin",
45
+ });
46
+ const body = new Uint8Array(await response.arrayBuffer());
47
+ this.diagnostic?.({
48
+ event_type: "bootstrap_request_completed",
49
+ request_id: requestId,
50
+ status: response.status,
51
+ response_bytes: body.byteLength,
52
+ });
53
+ return {
54
+ ok: true,
55
+ response: {
56
+ status: response.status,
57
+ status_text: response.statusText,
58
+ headers: headersToObject(response.headers),
59
+ body,
60
+ },
61
+ };
62
+ }
63
+
64
+ async close() {}
65
+ }
66
+
67
+ export class OysterunLogicalHostConnection {
68
+ constructor({
69
+ bootstrapTransport,
70
+ diagnostic,
71
+ transportRetireTimeoutMs = DEFAULT_TRANSPORT_RETIRE_TIMEOUT_MS,
72
+ }) {
73
+ if (!bootstrapTransport) {
74
+ throw new Error("Bootstrap transport is required");
75
+ }
76
+ this.bootstrapTransport = bootstrapTransport;
77
+ this.activeTransport = bootstrapTransport;
78
+ this.activePathKind = "bootstrap";
79
+ this.activeConnectionId = null;
80
+ this.diagnostic = diagnostic;
81
+ this.transportRetireTimeoutMs = transportRetireTimeoutMs;
82
+ this.transportStates = new Map();
83
+ this.retiringTransports = new Set();
84
+ this.transitionGeneration = 0;
85
+ this.transitionState = "stable";
86
+ this.syncOwnerId = null;
87
+ this.closed = false;
88
+ this.ensureTransportState(bootstrapTransport);
89
+ }
90
+
91
+ emitDiagnostic(event) {
92
+ this.diagnostic?.({
93
+ schema: "oysterun.connectivity.logical_host_connection.v1",
94
+ at: new Date().toISOString(),
95
+ active_path_kind: this.activePathKind,
96
+ active_connection_id: this.activeConnectionId,
97
+ ...event,
98
+ });
99
+ }
100
+
101
+ acquireSyncOwner(ownerId) {
102
+ if (this.closed) throw new Error("Logical Host connection is closed");
103
+ if (typeof ownerId !== "string" || !ownerId) {
104
+ throw new Error("sync owner id is required");
105
+ }
106
+ if (this.syncOwnerId && this.syncOwnerId !== ownerId) {
107
+ throw new Error(
108
+ `Matrix sync is already owned by ${this.syncOwnerId}; duplicate stream rejected`
109
+ );
110
+ }
111
+ this.syncOwnerId = ownerId;
112
+ this.emitDiagnostic({
113
+ event_type: "matrix_sync_owner_acquired",
114
+ sync_owner_id: ownerId,
115
+ });
116
+ let released = false;
117
+ return () => {
118
+ if (released) return;
119
+ released = true;
120
+ if (this.syncOwnerId === ownerId) this.syncOwnerId = null;
121
+ this.emitDiagnostic({
122
+ event_type: "matrix_sync_owner_released",
123
+ sync_owner_id: ownerId,
124
+ });
125
+ };
126
+ }
127
+
128
+ ensureTransportState(transport) {
129
+ let state = this.transportStates.get(transport);
130
+ if (!state) {
131
+ state = {
132
+ inFlight: 0,
133
+ idleWaiters: new Set(),
134
+ retirePromise: null,
135
+ };
136
+ this.transportStates.set(transport, state);
137
+ }
138
+ return state;
139
+ }
140
+
141
+ acquireTransportLease(transport) {
142
+ const state = this.ensureTransportState(transport);
143
+ state.inFlight += 1;
144
+ let released = false;
145
+ return () => {
146
+ if (released) return;
147
+ released = true;
148
+ state.inFlight -= 1;
149
+ if (state.inFlight !== 0) return;
150
+ for (const waiter of state.idleWaiters) waiter.resolve();
151
+ state.idleWaiters.clear();
152
+ };
153
+ }
154
+
155
+ async waitForTransportIdle(transport) {
156
+ const state = this.ensureTransportState(transport);
157
+ if (state.inFlight === 0) return;
158
+ const waiter = deferred();
159
+ state.idleWaiters.add(waiter);
160
+ try {
161
+ await withTimeout(
162
+ waiter.promise,
163
+ this.transportRetireTimeoutMs,
164
+ "Previous logical transport did not become idle"
165
+ );
166
+ } finally {
167
+ state.idleWaiters.delete(waiter);
168
+ }
169
+ }
170
+
171
+ retireTransport(transport, { transitionGeneration, reason }) {
172
+ if (transport === this.bootstrapTransport) return Promise.resolve();
173
+ const state = this.ensureTransportState(transport);
174
+ if (state.retirePromise) return state.retirePromise;
175
+
176
+ const retirePromise = (async () => {
177
+ this.emitDiagnostic({
178
+ event_type: "logical_transport_retirement_started",
179
+ transition_generation: transitionGeneration,
180
+ reason,
181
+ in_flight_request_count: state.inFlight,
182
+ });
183
+ let graceful = true;
184
+ try {
185
+ await this.waitForTransportIdle(transport);
186
+ } catch (error) {
187
+ graceful = false;
188
+ this.emitDiagnostic({
189
+ event_type: "logical_transport_retirement_timed_out",
190
+ transition_generation: transitionGeneration,
191
+ reason,
192
+ in_flight_request_count: state.inFlight,
193
+ error_code: "logical_transport_retirement_timeout",
194
+ });
195
+ }
196
+ await transport.close?.({ graceful });
197
+ this.transportStates.delete(transport);
198
+ this.emitDiagnostic({
199
+ event_type: "logical_transport_retired",
200
+ transition_generation: transitionGeneration,
201
+ reason,
202
+ graceful,
203
+ });
204
+ })();
205
+ state.retirePromise = retirePromise;
206
+ this.retiringTransports.add(retirePromise);
207
+ void retirePromise.finally(() => {
208
+ this.retiringTransports.delete(retirePromise);
209
+ if (this.transitionGeneration === transitionGeneration) {
210
+ this.transitionState = "stable";
211
+ }
212
+ });
213
+ return retirePromise;
214
+ }
215
+
216
+ changeActiveTransport({
217
+ transport,
218
+ pathKind,
219
+ connectionId,
220
+ reason,
221
+ }) {
222
+ const previousTransport = this.activeTransport;
223
+ const previousPathKind = this.activePathKind;
224
+ if (
225
+ previousTransport === transport &&
226
+ previousPathKind === pathKind &&
227
+ this.activeConnectionId === connectionId
228
+ ) {
229
+ return Promise.resolve();
230
+ }
231
+
232
+ const transitionGeneration = ++this.transitionGeneration;
233
+ this.transitionState =
234
+ pathKind === "webrtc" ? "promoting_webrtc" : "falling_back_bootstrap";
235
+ this.activeTransport = transport;
236
+ this.activePathKind = pathKind;
237
+ this.activeConnectionId = connectionId;
238
+ this.ensureTransportState(transport);
239
+ this.emitDiagnostic({
240
+ event_type: "active_path_changed",
241
+ transition_generation: transitionGeneration,
242
+ previous_path_kind: previousPathKind,
243
+ next_path_kind: pathKind,
244
+ reason,
245
+ });
246
+
247
+ if (
248
+ previousPathKind === "webrtc" &&
249
+ previousTransport !== transport
250
+ ) {
251
+ return this.retireTransport(previousTransport, {
252
+ transitionGeneration,
253
+ reason,
254
+ });
255
+ }
256
+ this.transitionState = "stable";
257
+ return Promise.resolve();
258
+ }
259
+
260
+ async request(
261
+ request,
262
+ { requestId = createConnectivityRandomId(), syncOwnerId = null } = {}
263
+ ) {
264
+ if (this.closed) throw new Error("Logical Host connection is closed");
265
+ if (MATRIX_SYNC_PATH.test(request.path)) {
266
+ if (!syncOwnerId || syncOwnerId !== this.syncOwnerId) {
267
+ throw new Error(
268
+ "Matrix sync request requires the single active sync owner"
269
+ );
270
+ }
271
+ }
272
+ const transport = this.activeTransport;
273
+ const pathKind = this.activePathKind;
274
+ const releaseTransport = this.acquireTransportLease(transport);
275
+ this.emitDiagnostic({
276
+ event_type: "logical_request_started",
277
+ request_id: requestId,
278
+ path_kind: pathKind,
279
+ });
280
+ try {
281
+ const result = await transport.request(request, { requestId });
282
+ this.emitDiagnostic({
283
+ event_type: "logical_request_completed",
284
+ request_id: requestId,
285
+ path_kind: pathKind,
286
+ });
287
+ return result;
288
+ } catch (error) {
289
+ this.emitDiagnostic({
290
+ event_type: "logical_request_failed",
291
+ request_id: requestId,
292
+ path_kind: pathKind,
293
+ error_code: error?.code || "logical_request_failed",
294
+ });
295
+ // Never retry an in-flight mutation on another path. The caller may
296
+ // resolve its request id before deciding whether a retry is safe.
297
+ throw error;
298
+ } finally {
299
+ releaseTransport();
300
+ }
301
+ }
302
+
303
+ async activateWebRtc({ transport, connectionId }) {
304
+ if (this.closed) throw new Error("Logical Host connection is closed");
305
+ if (!transport || typeof transport.request !== "function") {
306
+ throw new Error("Ready WebRTC transport is required");
307
+ }
308
+ if (typeof connectionId !== "string" || !connectionId) {
309
+ throw new Error("WebRTC connection id is required");
310
+ }
311
+ await this.changeActiveTransport({
312
+ transport,
313
+ pathKind: "webrtc",
314
+ connectionId,
315
+ reason: "webrtc_ready",
316
+ });
317
+ }
318
+
319
+ async activateBootstrap({ reason = "webrtc_unavailable" } = {}) {
320
+ if (this.closed) throw new Error("Logical Host connection is closed");
321
+ await this.changeActiveTransport({
322
+ transport: this.bootstrapTransport,
323
+ pathKind: "bootstrap",
324
+ connectionId: null,
325
+ reason,
326
+ });
327
+ }
328
+
329
+ status() {
330
+ return {
331
+ active_path_kind: this.activePathKind,
332
+ active_connection_id: this.activeConnectionId,
333
+ transition_generation: this.transitionGeneration,
334
+ transition_state: this.transitionState,
335
+ active_in_flight_request_count:
336
+ this.ensureTransportState(this.activeTransport).inFlight,
337
+ retiring_transport_count: this.retiringTransports.size,
338
+ matrix_sync_owner_id: this.syncOwnerId,
339
+ closed: this.closed,
340
+ };
341
+ }
342
+
343
+ async close() {
344
+ if (this.closed) return;
345
+ this.closed = true;
346
+ if (
347
+ this.activeTransport !== this.bootstrapTransport &&
348
+ typeof this.activeTransport.close === "function"
349
+ ) {
350
+ await this.waitForTransportIdle(this.activeTransport).catch(() => {});
351
+ await this.activeTransport.close({ graceful: true });
352
+ }
353
+ await Promise.allSettled([...this.retiringTransports]);
354
+ await this.bootstrapTransport.close?.();
355
+ this.syncOwnerId = null;
356
+ this.emitDiagnostic({ event_type: "logical_host_connection_closed" });
357
+ }
358
+ }
@@ -0,0 +1,29 @@
1
+ export class ManagedHostBrowserConnection {
2
+ start(): Promise<void> | null;
3
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
4
+ status(): Record<string, unknown>;
5
+ close(): Promise<void>;
6
+ setTicketProvider(
7
+ ticketProvider: ManagedHostTicketProvider | null
8
+ ): void;
9
+ }
10
+
11
+ export type ManagedHostTicketProvider = (input: {
12
+ configuration: Record<string, unknown>;
13
+ connectionAttemptId: string;
14
+ dtlsFingerprintSha256: string;
15
+ }) => Promise<{
16
+ credential: string;
17
+ client_authority_kind: "app_installation" | "host_owner_browser";
18
+ }>;
19
+
20
+ export function getManagedHostBrowserConnection(): ManagedHostBrowserConnection;
21
+ export function configureManagedHostTicketProvider(
22
+ ticketProvider: ManagedHostTicketProvider | null
23
+ ): void;
24
+ export function startManagedHostConnection(): Promise<void> | null;
25
+ export function managedHostFetch(
26
+ input: RequestInfo | URL,
27
+ init?: RequestInit
28
+ ): Promise<Response>;
29
+ export function getManagedHostConnectionStatus(): Record<string, unknown>;