opentray 0.31.3 → 0.33.0

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/dist/index.mjs CHANGED
@@ -484,6 +484,37 @@ const resolveAccessibleLibrary = async (path, target, packageName) => {
484
484
  //#endregion
485
485
  //#region src/client.ts
486
486
  /**
487
+ * Default per-call deadline classes (W1, harden-transport-robustness;
488
+ * field-validated against the pnpm-pub silent-wedge incident): interactive
489
+ * calls settle or reject within 5 s, teardown-class frames within 2 s, and
490
+ * bootstrap-class round-trips (connection init and first bring-up) within
491
+ * 10 s. They live beside `OpenTrayTransport` because they are part of the
492
+ * generic transport call contract, not of one broker implementation.
493
+ */
494
+ const INTERACTIVE_CALL_DEADLINE_MS = 5e3;
495
+ const TEARDOWN_CALL_DEADLINE_MS = 2e3;
496
+ const BOOTSTRAP_CALL_DEADLINE_MS = 1e4;
497
+ /**
498
+ * Typed per-call deadline rejection: the transport's budget for one
499
+ * round-trip expired without a correlated reply. This is the third
500
+ * rejection class, distinct from transport-lost (the plain
501
+ * `BROKER_CONNECTION_CLOSED_MESSAGE` sentinel family, or the typed
502
+ * `extension_transport_closed` for accepted deferred operations) and from
503
+ * broker-rejected (`BrokerServerError` / `ExtensionOperationError`). An
504
+ * expiry settles exactly one call; it never declares the transport dead —
505
+ * death stays a socket-close/error (and, later, heartbeat) verdict.
506
+ */
507
+ var TransportTimeoutError = class extends Error {
508
+ requestId;
509
+ deadlineMs;
510
+ constructor(requestId, deadlineMs) {
511
+ super(`transport call deadline expired after ${deadlineMs}ms: ${requestId}`);
512
+ this.name = "TransportTimeoutError";
513
+ this.requestId = requestId;
514
+ this.deadlineMs = deadlineMs;
515
+ }
516
+ };
517
+ /**
487
518
  * Typed rejection for synchronous broker error frames: carries the wire
488
519
  * `code`, the structured `details` payload when the error family freezes one,
489
520
  * and the transport cause when relevant. Consumers match on `code`; the human
@@ -652,7 +683,7 @@ function createTrayHandle(transport, appId, trayId, nextRequestId = createReques
652
683
  requestId,
653
684
  appId,
654
685
  trayId
655
- });
686
+ }, { deadlineMs: TEARDOWN_CALL_DEADLINE_MS });
656
687
  expectResponse(response, requestId, "ack");
657
688
  }
658
689
  };
@@ -665,10 +696,15 @@ const attachEventfulTrayHandle = (handle, source, appId, nextMountId, sessionId)
665
696
  if (!isOwnedTrayEvent(frame.event, appId, handle.trayId)) return;
666
697
  handler(frame.event);
667
698
  });
668
- const onConnectionDead = isConnectionDeadSource(source) ? (handler) => source.onConnectionDead(handler) : void 0;
699
+ const onConnectionDead = isConnectionDeadSource$1(source) ? (handler) => source.onConnectionDead(handler) : void 0;
700
+ const transportSupervision = isTransportSupervisionSource(source) ? {
701
+ onTransportStateChange: (handler) => source.onTransportStateChange(handler),
702
+ registerTransportRebuild: (rebuild) => source.registerTransportRebuild(rebuild)
703
+ } : void 0;
669
704
  const eventful = {
670
705
  ...handle,
671
706
  ...onConnectionDead === void 0 ? {} : { onConnectionDead },
707
+ ...transportSupervision === void 0 ? {} : transportSupervision,
672
708
  listenExtension(ext, handler) {
673
709
  return source.onEvent((frame) => {
674
710
  if (frame.type !== "ext-event" || frame.ext !== ext) return;
@@ -708,7 +744,8 @@ const attachEventfulTrayHandle = (handle, source, appId, nextMountId, sessionId)
708
744
  };
709
745
  const isOwnedTrayEvent = (event, appId, trayId) => event.type !== "ready" && event.appId === appId && event.trayId === trayId;
710
746
  const isOpenTrayEventSource = (transport) => "onEvent" in transport && typeof transport.onEvent === "function";
711
- const isConnectionDeadSource = (transport) => "onConnectionDead" in transport && typeof transport.onConnectionDead === "function";
747
+ const isConnectionDeadSource$1 = (transport) => "onConnectionDead" in transport && typeof transport.onConnectionDead === "function";
748
+ const isTransportSupervisionSource = (transport) => "onTransportStateChange" in transport && typeof transport.onTransportStateChange === "function" && "registerTransportRebuild" in transport && typeof transport.registerTransportRebuild === "function";
712
749
  const resolveDefaultAppRef = async (transport, nextRequestId) => {
713
750
  const requestId = nextRequestId();
714
751
  const response = await transport.request({
@@ -1559,6 +1596,12 @@ var ExtensionOperationError = class extends Error {
1559
1596
  };
1560
1597
  const extensionTransportClosedError = (cause) => new ExtensionOperationError(EXTENSION_TRANSPORT_CLOSED_CODE, "the broker transport closed while a deferred extension operation was pending", { cause });
1561
1598
  /**
1599
+ * Correlation label the bootstrap deadline reports under: the init frame
1600
+ * carries no requestId on the wire, so the typed timeout names the call class
1601
+ * itself.
1602
+ */
1603
+ const INIT_REQUEST_ID = "init";
1604
+ /**
1562
1605
  * Typed handshake rejection for a Ready frame whose protocol version does not
1563
1606
  * equal the version this client sent in its Init frame: a structurally valid
1564
1607
  * Ready frame alone must never accept the session (add-ext-dialog batch A
@@ -1669,6 +1712,7 @@ var LocalBrokerConnection = class {
1669
1712
  /** operationId -> requestId of accepted-but-unsettled deferred operations. */
1670
1713
  deferredOperations = /* @__PURE__ */ new Map();
1671
1714
  deadListeners = /* @__PURE__ */ new Set();
1715
+ deadlineExpiryListeners = /* @__PURE__ */ new Set();
1672
1716
  deadError;
1673
1717
  ready;
1674
1718
  constructor(socket, endpoint, callerLabel) {
@@ -1695,6 +1739,12 @@ var LocalBrokerConnection = class {
1695
1739
  this.deadListeners.delete(listener);
1696
1740
  };
1697
1741
  }
1742
+ onDeadlineExpiry(listener) {
1743
+ this.deadlineExpiryListeners.add(listener);
1744
+ return () => {
1745
+ this.deadlineExpiryListeners.delete(listener);
1746
+ };
1747
+ }
1698
1748
  async init(clientVersion, protocolVersion, expectedBrokerArtifactIdentity) {
1699
1749
  const ready = new Promise((resolve, reject) => {
1700
1750
  this.ready = {
@@ -1707,18 +1757,24 @@ var LocalBrokerConnection = class {
1707
1757
  protocolVersion,
1708
1758
  clientVersion
1709
1759
  });
1710
- const frame = await ready;
1760
+ const frame = await this.raceBootstrapDeadline(ready);
1711
1761
  if (frame.protocolVersion !== protocolVersion) throw new BrokerProtocolVersionError(protocolVersion, frame.protocolVersion);
1712
1762
  if (!brokerArtifactIdentityEquals(frame.brokerArtifactIdentity, expectedBrokerArtifactIdentity)) throw new Error(`broker artifact identity mismatch: expected=${JSON.stringify(expectedBrokerArtifactIdentity)} actual=${JSON.stringify(frame.brokerArtifactIdentity)}`);
1713
1763
  this.sessionId = frame.sessionId;
1714
1764
  }
1715
- async request(frame) {
1765
+ async request(frame, options = {}) {
1716
1766
  if (this.pending.has(frame.requestId)) throw new Error(`duplicate requestId: ${frame.requestId}`);
1717
1767
  if (this.deadError !== void 0) throw new Error(this.deadError.message);
1768
+ const deadlineMs = options.deadlineMs ?? 5e3;
1718
1769
  const response = new Promise((resolve, reject) => {
1770
+ const deadlineTimer = setTimeout(() => {
1771
+ this.expireRequest(frame.requestId, deadlineMs);
1772
+ }, deadlineMs);
1773
+ deadlineTimer.unref();
1719
1774
  this.pending.set(frame.requestId, {
1720
1775
  resolve,
1721
- reject
1776
+ reject,
1777
+ deadlineTimer
1722
1778
  });
1723
1779
  });
1724
1780
  this.write(frame);
@@ -1734,7 +1790,15 @@ var LocalBrokerConnection = class {
1734
1790
  if (this.deadError !== void 0) return;
1735
1791
  this.write({ type: "exit" });
1736
1792
  await new Promise((resolve) => {
1737
- this.socket.end(resolve);
1793
+ const degrade = setTimeout(() => {
1794
+ this.socket.destroy();
1795
+ resolve();
1796
+ }, TEARDOWN_CALL_DEADLINE_MS);
1797
+ degrade.unref();
1798
+ this.socket.end(() => {
1799
+ clearTimeout(degrade);
1800
+ resolve();
1801
+ });
1738
1802
  });
1739
1803
  }
1740
1804
  consume(chunk) {
@@ -1764,8 +1828,7 @@ var LocalBrokerConnection = class {
1764
1828
  if (frame.type === "error") {
1765
1829
  const error = new BrokerServerError(frame.code, frame.message, { details: frame.details });
1766
1830
  if (frame.requestId !== void 0) {
1767
- this.pending.get(frame.requestId)?.reject(error);
1768
- this.pending.delete(frame.requestId);
1831
+ this.takePending(frame.requestId)?.reject(error);
1769
1832
  return;
1770
1833
  }
1771
1834
  if (this.ready) {
@@ -1786,8 +1849,7 @@ var LocalBrokerConnection = class {
1786
1849
  }
1787
1850
  const requestId = responseRequestId(frame);
1788
1851
  if (requestId !== void 0) {
1789
- this.pending.get(requestId)?.resolve(frame);
1790
- this.pending.delete(requestId);
1852
+ this.takePending(requestId)?.resolve(frame);
1791
1853
  return;
1792
1854
  }
1793
1855
  if (frame.type === "event" || frame.type === "app-event" || frame.type === "ext-event") for (const listener of this.listeners) listener(frame);
@@ -1800,7 +1862,10 @@ var LocalBrokerConnection = class {
1800
1862
  * settles the request. The broker's owner loop guarantees exactly one
1801
1863
  * terminal per operation; a second acceptance for the same request or an
1802
1864
  * already-bound operationId is defensively ignored, never a second
1803
- * registration.
1865
+ * registration. Acceptance also stops the call's deadline: dispatch was the
1866
+ * bounded transport phase the client owns, and the accepted operation now
1867
+ * awaits a native terminal that can be legitimately user-paced (a held modal
1868
+ * dialog), bounded only by transport liveness.
1804
1869
  */
1805
1870
  acceptDeferredOperation(requestId, operationId) {
1806
1871
  const entry = this.pending.get(requestId);
@@ -1808,6 +1873,7 @@ var LocalBrokerConnection = class {
1808
1873
  if (this.deferredOperations.has(operationId)) return;
1809
1874
  entry.deferredOperation = operationId;
1810
1875
  this.deferredOperations.set(operationId, requestId);
1876
+ this.clearDeadline(entry);
1811
1877
  }
1812
1878
  /**
1813
1879
  * The single terminal settlement: resolves the still-pending request with
@@ -1815,15 +1881,17 @@ var LocalBrokerConnection = class {
1815
1881
  * typed wire error (`"error"`). Terminals for unknown or already-settled
1816
1882
  * operationIds are silently dropped - the broker side already settles
1817
1883
  * through a one-shot CAS; the client mirrors that stateless drop instead of
1818
- * guessing an owner.
1884
+ * guessing an owner. A terminal arriving after the call's deadline expired
1885
+ * is the same stateless drop (W1).
1819
1886
  */
1820
1887
  settleDeferredOperation(operationId, payload) {
1821
1888
  const requestId = this.deferredOperations.get(operationId);
1822
1889
  if (requestId === void 0) return;
1823
- const entry = this.pending.get(requestId);
1824
- this.deferredOperations.delete(operationId);
1825
- this.pending.delete(requestId);
1826
- if (entry === void 0) return;
1890
+ const entry = this.takePending(requestId);
1891
+ if (entry === void 0) {
1892
+ this.deferredOperations.delete(operationId);
1893
+ return;
1894
+ }
1827
1895
  if (payload.kind === "result") {
1828
1896
  const terminal = {
1829
1897
  type: "ext-operation-terminal",
@@ -1836,10 +1904,83 @@ var LocalBrokerConnection = class {
1836
1904
  const wireError = payload.error;
1837
1905
  entry.reject(new ExtensionOperationError(wireError.code, wireError.message, { details: wireError.details }));
1838
1906
  }
1907
+ /**
1908
+ * Removes and fully cleans one pending entry: clears its deadline timer and
1909
+ * the deferred-operation reverse mapping, so every settle path (response,
1910
+ * correlated error, deferred terminal, expiry) leaves no timer or mapping
1911
+ * behind.
1912
+ */
1913
+ takePending(requestId) {
1914
+ const entry = this.pending.get(requestId);
1915
+ if (entry === void 0) return;
1916
+ this.pending.delete(requestId);
1917
+ this.clearDeadline(entry);
1918
+ if (entry.deferredOperation !== void 0) this.deferredOperations.delete(entry.deferredOperation);
1919
+ return entry;
1920
+ }
1921
+ clearDeadline(entry) {
1922
+ if (entry.deadlineTimer !== void 0) {
1923
+ clearTimeout(entry.deadlineTimer);
1924
+ entry.deadlineTimer = void 0;
1925
+ }
1926
+ }
1927
+ /**
1928
+ * Deadline expiry (W1): settles exactly this call with the typed timeout
1929
+ * rejection and removes its entries, so a late reply or terminal finds no
1930
+ * owner. Deliberately NOT `markDead` — an unanswered call is one liveness
1931
+ * signal, not a death verdict; the connection stays usable and the death
1932
+ * listeners stay silent. The expiry is reported through the internal seam
1933
+ * for the Phase C heartbeat's failure classification.
1934
+ */
1935
+ expireRequest(requestId, deadlineMs) {
1936
+ const entry = this.pending.get(requestId);
1937
+ if (entry === void 0) return;
1938
+ this.takePending(requestId);
1939
+ this.notifyDeadlineExpiry({
1940
+ requestId,
1941
+ deadlineMs
1942
+ });
1943
+ entry.reject(new TransportTimeoutError(requestId, deadlineMs));
1944
+ }
1945
+ /**
1946
+ * Bootstrap-class budget (W1): the Ready frame must arrive within
1947
+ * `BOOTSTRAP_CALL_DEADLINE_MS` or init settles with the typed timeout
1948
+ * rejection. Like per-call expiry this never marks the transport dead —
1949
+ * `connectLocalBroker`'s error path performs the bounded close.
1950
+ */
1951
+ raceBootstrapDeadline(ready) {
1952
+ return new Promise((resolve, reject) => {
1953
+ let bootstrapTimer = setTimeout(() => {
1954
+ bootstrapTimer = void 0;
1955
+ this.notifyDeadlineExpiry({
1956
+ requestId: INIT_REQUEST_ID,
1957
+ deadlineMs: BOOTSTRAP_CALL_DEADLINE_MS
1958
+ });
1959
+ reject(new TransportTimeoutError(INIT_REQUEST_ID, BOOTSTRAP_CALL_DEADLINE_MS));
1960
+ }, BOOTSTRAP_CALL_DEADLINE_MS);
1961
+ bootstrapTimer.unref();
1962
+ const settle = (settleFn) => {
1963
+ if (bootstrapTimer !== void 0) {
1964
+ clearTimeout(bootstrapTimer);
1965
+ bootstrapTimer = void 0;
1966
+ }
1967
+ settleFn();
1968
+ };
1969
+ ready.then((frame) => settle(() => resolve(frame)), (error) => settle(() => reject(error)));
1970
+ });
1971
+ }
1972
+ notifyDeadlineExpiry(expiry) {
1973
+ for (const listener of [...this.deadlineExpiryListeners]) try {
1974
+ listener(expiry);
1975
+ } catch {}
1976
+ }
1839
1977
  rejectAll(error) {
1840
1978
  this.ready?.reject(error);
1841
1979
  this.ready = void 0;
1842
- for (const pending of this.pending.values()) pending.reject(error);
1980
+ for (const pending of this.pending.values()) {
1981
+ this.clearDeadline(pending);
1982
+ pending.reject(error);
1983
+ }
1843
1984
  this.pending.clear();
1844
1985
  this.deferredOperations.clear();
1845
1986
  }
@@ -1858,7 +1999,10 @@ var LocalBrokerConnection = class {
1858
1999
  this.deadError = error;
1859
2000
  this.ready?.reject(error);
1860
2001
  this.ready = void 0;
1861
- for (const pending of this.pending.values()) pending.reject(pending.deferredOperation === void 0 ? error : extensionTransportClosedError(error));
2002
+ for (const pending of this.pending.values()) {
2003
+ this.clearDeadline(pending);
2004
+ pending.reject(pending.deferredOperation === void 0 ? error : extensionTransportClosedError(error));
2005
+ }
1862
2006
  this.pending.clear();
1863
2007
  this.deferredOperations.clear();
1864
2008
  for (const listener of [...this.deadListeners]) try {
@@ -1897,6 +2041,643 @@ const resolveCliEntrypoint = () => {
1897
2041
  const suffix = fileURLToPath(import.meta.url).endsWith(".ts") ? "cli.ts" : "cli.mjs";
1898
2042
  return fileURLToPath(new URL(`./${suffix}`, import.meta.url));
1899
2043
  };
2044
+ /**
2045
+ * Typed fail-fast rejection for every call issued after supervision reached
2046
+ * its terminal `abandoned` state: the recovery budget is exhausted and the
2047
+ * runtime stays headless by design (the consumer process keeps running).
2048
+ * Consumers match on `code` (`transport_abandoned`); `details` carries the
2049
+ * budget facts `{ recoveries, windowMs }`.
2050
+ */
2051
+ var TransportAbandonedError = class extends Error {
2052
+ code = "transport_abandoned";
2053
+ details;
2054
+ constructor(recoveries, windowMs) {
2055
+ super(`transport abandoned: ${recoveries} recovery attempts within ${windowMs}ms exhausted the budget; every later call fails fast`);
2056
+ this.name = "TransportAbandonedError";
2057
+ this.details = {
2058
+ recoveries,
2059
+ windowMs
2060
+ };
2061
+ }
2062
+ };
2063
+ const journalKeyOf = (entry) => {
2064
+ switch (entry.kind) {
2065
+ case "resolve-default-app": return "resolve-default-app";
2066
+ case "create-tray": return;
2067
+ case "set-tray-menu":
2068
+ case "set-tray-icon":
2069
+ case "set-tray-tooltip": return `${entry.kind}:${entry.appId}:${entry.trayId}`;
2070
+ case "set-app-name":
2071
+ case "set-app-icon":
2072
+ case "set-app-icon-variant": return `${entry.kind}:${entry.appId}`;
2073
+ case "load-ext": return `load-ext:${entry.appId}:${entry.mountId ?? entry.name}`;
2074
+ }
2075
+ };
2076
+ const isDeadlineExpirySource = (connection) => "onDeadlineExpiry" in connection && typeof connection.onDeadlineExpiry === "function";
2077
+ const isConnectionDeadSource = (connection) => "onConnectionDead" in connection && typeof connection.onConnectionDead === "function";
2078
+ const sleepUnref = (ms) => new Promise((resolve) => {
2079
+ setTimeout(resolve, ms).unref();
2080
+ });
2081
+ /**
2082
+ * Supervised transport (W2/W4/W5/W6): a stable `OpenTrayConnection`-shaped
2083
+ * proxy for the layers above (createClient, tray handles) that owns one
2084
+ * broker connection per generation underneath. Consumer event subscriptions
2085
+ * and the supervision state machine survive generation changes;
2086
+ * per-connection surfaces (`onConnectionDead`) stay per-generation truth.
2087
+ */
2088
+ var SupervisedLocalBrokerConnection = class {
2089
+ connectFactory;
2090
+ heartbeatIntervalMs;
2091
+ probeDeadlineMs;
2092
+ heartbeatFailureThreshold;
2093
+ recoveryEnabled;
2094
+ maxRestarts;
2095
+ recoveryWindowMs;
2096
+ cooldownMs;
2097
+ backoffFactor;
2098
+ backoffCapMs;
2099
+ restartApp;
2100
+ connection;
2101
+ generation = 0;
2102
+ state = "healthy";
2103
+ journal = [];
2104
+ eventListeners = /* @__PURE__ */ new Set();
2105
+ deadListeners = /* @__PURE__ */ new Set();
2106
+ stateListeners = /* @__PURE__ */ new Set();
2107
+ rebuildCallbacks = [];
2108
+ unwireGeneration = noop$1;
2109
+ heartbeatTimer;
2110
+ heartbeatOrdinal = 0;
2111
+ consecutiveHeartbeatFailures = 0;
2112
+ lastSettledAt = 0;
2113
+ restartTimestamps = [];
2114
+ callerInitiated = false;
2115
+ shutdownPromise;
2116
+ constructor(connectFactory, options) {
2117
+ this.connectFactory = connectFactory;
2118
+ this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? 3e4;
2119
+ this.probeDeadlineMs = options.probeDeadlineMs ?? 3e3;
2120
+ this.heartbeatFailureThreshold = options.heartbeatFailureThreshold ?? 3;
2121
+ const recovery = options.recovery ?? {};
2122
+ this.recoveryEnabled = recovery.enabled ?? true;
2123
+ this.maxRestarts = recovery.maxRestarts ?? 3;
2124
+ this.recoveryWindowMs = recovery.windowMs ?? 6e5;
2125
+ this.cooldownMs = recovery.cooldownMs ?? 1e3;
2126
+ this.backoffFactor = recovery.backoffFactor ?? 2;
2127
+ this.backoffCapMs = recovery.backoffCapMs ?? 3e4;
2128
+ this.restartApp = recovery.restartApp;
2129
+ }
2130
+ /** Current supervision state; edge events go to {@link onTransportStateChange}. */
2131
+ get transportState() {
2132
+ return this.state;
2133
+ }
2134
+ /** Monotonic generation counter; each respawn/replay increments it once. */
2135
+ get transportGeneration() {
2136
+ return this.generation;
2137
+ }
2138
+ /** Current generation's endpoint (proxy; absent before the first connect). */
2139
+ get endpoint() {
2140
+ return this.connection?.endpoint ?? "";
2141
+ }
2142
+ /** Current generation's caller label (proxy; absent before the first connect). */
2143
+ get callerLabel() {
2144
+ return this.connection?.callerLabel ?? "";
2145
+ }
2146
+ /**
2147
+ * Current generation's broker session id. Read dynamically so extension
2148
+ * contexts and rebuild callbacks can attribute session-owned state to the
2149
+ * live generation (the id changes on every respawn).
2150
+ */
2151
+ get sessionId() {
2152
+ return this.connection?.sessionId ?? "";
2153
+ }
2154
+ /** True once the current generation's connection died (or none exists). */
2155
+ get connectionDead() {
2156
+ return this.connection === void 0 || this.connection.connectionDead;
2157
+ }
2158
+ /**
2159
+ * Establishes generation 0. A failure here propagates to the caller
2160
+ * unchanged — supervision (heartbeat, recovery) only guards a connection
2161
+ * that once existed; an app that never started is a plain startup failure.
2162
+ */
2163
+ async connect() {
2164
+ const connection = await this.connectFactory();
2165
+ this.connection = connection;
2166
+ this.unwireGeneration = this.wireGeneration(connection);
2167
+ this.lastSettledAt = Date.now();
2168
+ this.consecutiveHeartbeatFailures = 0;
2169
+ this.startHeartbeat();
2170
+ }
2171
+ /**
2172
+ * Forwards one request to the current generation. After the terminal
2173
+ * `abandoned` state every call fails fast with {@link TransportAbandonedError}.
2174
+ * A teardown-budget call (`deadlineMs === TEARDOWN_CALL_DEADLINE_MS`) marks
2175
+ * the supervision caller-initiated: Phase A froze the budget class as the
2176
+ * caller-intent channel, so a socket close racing caller-initiated destroy
2177
+ * can never be misread as uninvited death.
2178
+ */
2179
+ async request(frame, options = {}) {
2180
+ if (this.state === "abandoned") throw new TransportAbandonedError(this.restartTimestamps.length, this.recoveryWindowMs);
2181
+ if (options.deadlineMs === 2e3) this.callerInitiated = true;
2182
+ const connection = this.connection;
2183
+ if (connection === void 0) throw new Error(BROKER_CONNECTION_CLOSED_MESSAGE);
2184
+ const candidate = journalCandidateFrom(frame);
2185
+ const response = await connection.request(frame, options);
2186
+ this.noteSuccessfulSettlement();
2187
+ if (candidate !== void 0) this.commitJournalEntry(candidate, response);
2188
+ if (frame.type === "destroy-tray") this.evictJournalTray(frame.appId, frame.trayId);
2189
+ return response;
2190
+ }
2191
+ /**
2192
+ * Consumer event subscription that survives generation changes: each new
2193
+ * generation's event stream fans out to the same listeners.
2194
+ */
2195
+ onEvent(listener) {
2196
+ this.eventListeners.add(listener);
2197
+ return () => {
2198
+ this.eventListeners.delete(listener);
2199
+ };
2200
+ }
2201
+ /**
2202
+ * Per-connection death notification, forwarded for every generation (D3
2203
+ * semantics preserved: consumers using it for per-connection cleanup keep
2204
+ * working; the supervision state machine is the new higher-level truth).
2205
+ */
2206
+ onConnectionDead(listener) {
2207
+ this.deadListeners.add(listener);
2208
+ return () => {
2209
+ this.deadListeners.delete(listener);
2210
+ };
2211
+ }
2212
+ /**
2213
+ * Tier 1 health projection (W6): edge-triggered `healthy | recovering |
2214
+ * abandoned` transitions. The initial `healthy` state never fires —
2215
+ * consumers reading the current state use `transportState`.
2216
+ */
2217
+ onTransportStateChange(listener) {
2218
+ this.stateListeners.add(listener);
2219
+ return () => {
2220
+ this.stateListeners.delete(listener);
2221
+ };
2222
+ }
2223
+ /**
2224
+ * Registers one facade rebuild callback (W4). Callbacks run in
2225
+ * registration order after the core journal replay on every successful
2226
+ * recovery, receiving the fresh generation number and session id so
2227
+ * session-attributed native state can be re-created against the live
2228
+ * broker session.
2229
+ */
2230
+ registerTransportRebuild(rebuild) {
2231
+ this.rebuildCallbacks.push(rebuild);
2232
+ }
2233
+ /**
2234
+ * Graceful teardown channel (W4/W3): idempotent, marks the supervision
2235
+ * caller-initiated synchronously (so the close-driven death event can
2236
+ * never trigger recovery), stops the heartbeat, then closes the current
2237
+ * generation within its bounded graceful-close budget.
2238
+ */
2239
+ async shutdown() {
2240
+ this.callerInitiated = true;
2241
+ this.shutdownPromise ??= (async () => {
2242
+ this.stopHeartbeat();
2243
+ this.unwireGeneration();
2244
+ this.unwireGeneration = noop$1;
2245
+ const connection = this.connection;
2246
+ this.connection = void 0;
2247
+ if (connection !== void 0) await connection.close().catch(noopAsync);
2248
+ })();
2249
+ await this.shutdownPromise;
2250
+ }
2251
+ /** LocalBrokerClient structural compatibility: graceful close == shutdown. */
2252
+ close() {
2253
+ return this.shutdown();
2254
+ }
2255
+ startHeartbeat() {
2256
+ if (this.heartbeatTimer !== void 0) return;
2257
+ this.heartbeatTimer = setInterval(() => {
2258
+ this.heartbeatTick();
2259
+ }, this.heartbeatIntervalMs);
2260
+ this.heartbeatTimer.unref();
2261
+ }
2262
+ stopHeartbeat() {
2263
+ if (this.heartbeatTimer !== void 0) {
2264
+ clearInterval(this.heartbeatTimer);
2265
+ this.heartbeatTimer = void 0;
2266
+ }
2267
+ }
2268
+ /**
2269
+ * Idle-gated heartbeat (W2): a transport that keeps settling requests
2270
+ * proves its own liveness, so the probe budget is spent only on silence.
2271
+ * A probe failure of any rejection class (typed timeout, transport-lost,
2272
+ * broker error frame) counts; a successful settlement resets the streak.
2273
+ */
2274
+ async heartbeatTick() {
2275
+ const connection = this.connection;
2276
+ if (connection === void 0 || this.state !== "healthy" || this.callerInitiated) return;
2277
+ if (Date.now() - this.lastSettledAt < this.heartbeatIntervalMs) return;
2278
+ const requestId = `supervisor-heartbeat-${this.generation}-${this.heartbeatOrdinal += 1}`;
2279
+ try {
2280
+ await connection.request({
2281
+ type: "health",
2282
+ requestId
2283
+ }, { deadlineMs: this.probeDeadlineMs });
2284
+ this.consecutiveHeartbeatFailures = 0;
2285
+ this.noteSuccessfulSettlement();
2286
+ } catch (error) {
2287
+ this.consecutiveHeartbeatFailures += 1;
2288
+ if (this.consecutiveHeartbeatFailures < this.heartbeatFailureThreshold) return;
2289
+ const death = new Error(BROKER_CONNECTION_CLOSED_MESSAGE, { cause: `heartbeat declared death after ${this.consecutiveHeartbeatFailures} consecutive probe failures (${String(error)})` });
2290
+ this.handleGenerationDeath(death);
2291
+ connection.close().catch(noopAsync);
2292
+ }
2293
+ }
2294
+ noteSuccessfulSettlement() {
2295
+ this.lastSettledAt = Date.now();
2296
+ this.consecutiveHeartbeatFailures = 0;
2297
+ }
2298
+ /**
2299
+ * Subscribes this supervisor's stable listener sets to one generation.
2300
+ * Event fanout and the death/expiry seams are wired before the generation
2301
+ * becomes `this.connection` (recovery candidates are wired before replay)
2302
+ * so no observable event window opens between adoption steps.
2303
+ */
2304
+ wireGeneration(connection) {
2305
+ const unwires = [];
2306
+ const unlistenEvents = connection.onEvent((frame) => {
2307
+ for (const listener of [...this.eventListeners]) try {
2308
+ listener(frame);
2309
+ } catch {}
2310
+ });
2311
+ unwires.push(unlistenEvents);
2312
+ if (isConnectionDeadSource(connection)) {
2313
+ const unlistenDead = connection.onConnectionDead((error) => {
2314
+ this.handleGenerationDeath(error);
2315
+ });
2316
+ unwires.push(unlistenDead);
2317
+ }
2318
+ if (isDeadlineExpirySource(connection)) {
2319
+ const unlistenExpiry = connection.onDeadlineExpiry((expiry) => {
2320
+ if (expiry.requestId.startsWith("supervisor-heartbeat-")) return;
2321
+ this.consecutiveHeartbeatFailures += 1;
2322
+ if (this.consecutiveHeartbeatFailures >= this.heartbeatFailureThreshold && this.state === "healthy" && !this.callerInitiated) {
2323
+ const connectionAtDeath = this.connection;
2324
+ this.handleGenerationDeath(new Error(BROKER_CONNECTION_CLOSED_MESSAGE, { cause: `heartbeat declared death after ${this.consecutiveHeartbeatFailures} consecutive deadline expiries` }));
2325
+ if (connectionAtDeath !== void 0) connectionAtDeath.close().catch(noopAsync);
2326
+ }
2327
+ });
2328
+ unwires.push(unlistenExpiry);
2329
+ }
2330
+ return () => {
2331
+ for (const unwire of unwires.splice(0)) unwire();
2332
+ };
2333
+ }
2334
+ /**
2335
+ * Death funnel: forwards the per-connection event first (D3 truth), stops
2336
+ * this generation's wiring, then decides the supervision reaction. The
2337
+ * state guard makes the reaction single-flight — deaths of candidates
2338
+ * during an ongoing recovery only forward, they never nest recoveries.
2339
+ */
2340
+ handleGenerationDeath(error) {
2341
+ for (const listener of [...this.deadListeners]) try {
2342
+ listener(error);
2343
+ } catch {}
2344
+ if (this.callerInitiated || this.state !== "healthy") return;
2345
+ this.unwireGeneration();
2346
+ this.unwireGeneration = noop$1;
2347
+ if (this.restartApp !== void 0) {
2348
+ this.handOverToRestartApp();
2349
+ return;
2350
+ }
2351
+ if (!this.recoveryEnabled) return;
2352
+ this.runRecovery();
2353
+ }
2354
+ /**
2355
+ * Tier 2 hand-over: bounded teardown of the current generation, the app's
2356
+ * restart vector invoked exactly once, then terminal. No in-process
2357
+ * rebuild, no loop — restart ownership belongs to the callback.
2358
+ */
2359
+ async handOverToRestartApp() {
2360
+ this.setState("recovering");
2361
+ this.callerInitiated = true;
2362
+ this.stopHeartbeat();
2363
+ const connection = this.connection;
2364
+ if (connection !== void 0 && !connection.connectionDead) await connection.close().catch(noopAsync);
2365
+ try {
2366
+ await this.restartApp?.();
2367
+ } catch (error) {
2368
+ console.error("OpenTray recovery.restartApp callback failed:", error);
2369
+ }
2370
+ this.setState("abandoned");
2371
+ }
2372
+ /**
2373
+ * In-process recovery (W4/W5): cooldown backoff → reconnect through the
2374
+ * original connect options → journal replay → facade rebuilds → healthy.
2375
+ * Every attempt consumes budget up front; a failed attempt (connect,
2376
+ * replay, or rebuild — including artifact identity mismatches) retries
2377
+ * inside the remaining budget until exhaustion abandons the runtime.
2378
+ */
2379
+ async runRecovery() {
2380
+ this.setState("recovering");
2381
+ let backoffOrdinal = 0;
2382
+ while (true) {
2383
+ if (this.callerInitiated) return;
2384
+ if (!this.consumeRestartBudget()) {
2385
+ this.setState("abandoned");
2386
+ return;
2387
+ }
2388
+ await sleepUnref(this.cooldownForBackoff(backoffOrdinal));
2389
+ if (this.callerInitiated) return;
2390
+ try {
2391
+ const candidate = await this.connectFactory();
2392
+ await this.adoptGeneration(candidate);
2393
+ this.setState("healthy");
2394
+ return;
2395
+ } catch {
2396
+ backoffOrdinal += 1;
2397
+ }
2398
+ }
2399
+ }
2400
+ cooldownForBackoff(backoffOrdinal) {
2401
+ return Math.min(this.cooldownMs * this.backoffFactor ** backoffOrdinal, this.backoffCapMs);
2402
+ }
2403
+ /**
2404
+ * Sliding-window budget: one timestamp per respawn attempt (successful or
2405
+ * not — both are respawns the consumer experienced). In-memory per
2406
+ * supervisor by ruling; a fresh `createTray` starts a fresh budget.
2407
+ */
2408
+ consumeRestartBudget() {
2409
+ const windowStart = Date.now() - this.recoveryWindowMs;
2410
+ this.restartTimestamps = this.restartTimestamps.filter((timestamp) => timestamp > windowStart);
2411
+ if (this.restartTimestamps.length >= this.maxRestarts) return false;
2412
+ this.restartTimestamps.push(Date.now());
2413
+ return true;
2414
+ }
2415
+ /**
2416
+ * Wires the candidate generation, replays the journal, and runs the
2417
+ * facade rebuild callbacks. `this.connection` is committed only after the
2418
+ * full sequence succeeds: consumer calls during recovery keep failing
2419
+ * fast against the dead generation instead of racing a half-replayed
2420
+ * broker. A failing candidate is unwired and closed (bounded) before the
2421
+ * attempt is reported failed.
2422
+ */
2423
+ async adoptGeneration(candidate) {
2424
+ this.generation += 1;
2425
+ this.unwireGeneration = this.wireGeneration(candidate);
2426
+ try {
2427
+ await this.replayJournal(candidate);
2428
+ for (const rebuild of [...this.rebuildCallbacks]) await rebuild({
2429
+ generation: this.generation,
2430
+ sessionId: candidate.sessionId.length > 0 ? candidate.sessionId : void 0
2431
+ });
2432
+ } catch (error) {
2433
+ this.unwireGeneration();
2434
+ this.unwireGeneration = noop$1;
2435
+ await candidate.close().catch(noopAsync);
2436
+ throw error;
2437
+ }
2438
+ this.connection = candidate;
2439
+ this.consecutiveHeartbeatFailures = 0;
2440
+ this.noteSuccessfulSettlement();
2441
+ }
2442
+ /**
2443
+ * Replays the declarative journal in record order with fresh request ids
2444
+ * under the bootstrap deadline class. The resolved default app of the new
2445
+ * generation becomes the appId authority: replayed frames are rewritten
2446
+ * onto it, so app-scoped state follows broker truth instead of a stale
2447
+ * generation's identity.
2448
+ */
2449
+ async replayJournal(connection) {
2450
+ let ordinal = 0;
2451
+ const appIdRewrites = /* @__PURE__ */ new Map();
2452
+ const rewriteAppId = (appId) => appIdRewrites.get(appId) ?? appId;
2453
+ for (const entry of this.journal) {
2454
+ ordinal += 1;
2455
+ const requestId = `supervisor-replay-${this.generation}-${ordinal}`;
2456
+ let frame;
2457
+ switch (entry.kind) {
2458
+ case "resolve-default-app":
2459
+ frame = {
2460
+ type: "resolve-default-app",
2461
+ requestId
2462
+ };
2463
+ break;
2464
+ case "create-tray":
2465
+ frame = {
2466
+ type: "create-tray",
2467
+ requestId,
2468
+ app: { appId: rewriteAppId(entry.app.appId) },
2469
+ tray: {
2470
+ ...entry.tray,
2471
+ id: entry.trayId
2472
+ }
2473
+ };
2474
+ break;
2475
+ case "set-tray-menu":
2476
+ frame = {
2477
+ type: "set-tray-menu",
2478
+ requestId,
2479
+ appId: rewriteAppId(entry.appId),
2480
+ trayId: entry.trayId,
2481
+ menu: entry.menu
2482
+ };
2483
+ break;
2484
+ case "set-tray-icon":
2485
+ frame = {
2486
+ type: "set-tray-icon",
2487
+ requestId,
2488
+ appId: rewriteAppId(entry.appId),
2489
+ trayId: entry.trayId,
2490
+ icon: entry.icon
2491
+ };
2492
+ break;
2493
+ case "set-tray-tooltip":
2494
+ frame = {
2495
+ type: "set-tray-tooltip",
2496
+ requestId,
2497
+ appId: rewriteAppId(entry.appId),
2498
+ trayId: entry.trayId,
2499
+ tooltip: entry.tooltip
2500
+ };
2501
+ break;
2502
+ case "set-app-name":
2503
+ frame = {
2504
+ type: "set-app-name",
2505
+ requestId,
2506
+ appId: rewriteAppId(entry.appId),
2507
+ name: entry.name
2508
+ };
2509
+ break;
2510
+ case "set-app-icon":
2511
+ frame = {
2512
+ type: "set-app-icon",
2513
+ requestId,
2514
+ appId: rewriteAppId(entry.appId),
2515
+ appIcon: entry.appIcon
2516
+ };
2517
+ break;
2518
+ case "set-app-icon-variant":
2519
+ frame = {
2520
+ type: "set-app-icon-variant",
2521
+ requestId,
2522
+ appId: rewriteAppId(entry.appId),
2523
+ variant: entry.variant
2524
+ };
2525
+ break;
2526
+ case "load-ext":
2527
+ frame = {
2528
+ type: "load-ext",
2529
+ requestId,
2530
+ appId: rewriteAppId(entry.appId),
2531
+ name: entry.name,
2532
+ path: entry.path,
2533
+ expectedIdentity: entry.expectedIdentity,
2534
+ ...entry.mountId === void 0 ? {} : { mountId: entry.mountId }
2535
+ };
2536
+ break;
2537
+ }
2538
+ const response = await connection.request(frame, { deadlineMs: BOOTSTRAP_CALL_DEADLINE_MS });
2539
+ if (response.type === "error") throw new BrokerServerError(response.code, response.message, { details: response.details });
2540
+ if (entry.kind === "resolve-default-app" && response.type === "default-app") appIdRewrites.set(entry.appId, response.app.appId);
2541
+ }
2542
+ }
2543
+ setState(state) {
2544
+ if (this.state === state) return;
2545
+ this.state = state;
2546
+ if (state === "abandoned") this.stopHeartbeat();
2547
+ for (const listener of [...this.stateListeners]) try {
2548
+ listener(state);
2549
+ } catch {}
2550
+ }
2551
+ commitJournalEntry(frame, response) {
2552
+ switch (frame.type) {
2553
+ case "resolve-default-app":
2554
+ if (response.type === "default-app") this.upsertJournal({
2555
+ kind: "resolve-default-app",
2556
+ appId: response.app.appId
2557
+ });
2558
+ return;
2559
+ case "create-tray": {
2560
+ const trayId = response.type === "tray-created" && typeof response.trayId === "string" ? response.trayId : frame.tray.id;
2561
+ this.journal.push({
2562
+ kind: "create-tray",
2563
+ app: frame.app,
2564
+ tray: frame.tray,
2565
+ trayId
2566
+ });
2567
+ return;
2568
+ }
2569
+ case "set-tray-menu":
2570
+ this.upsertJournal({
2571
+ kind: "set-tray-menu",
2572
+ appId: frame.appId,
2573
+ trayId: frame.trayId,
2574
+ menu: frame.menu
2575
+ });
2576
+ return;
2577
+ case "set-tray-icon":
2578
+ this.upsertJournal({
2579
+ kind: "set-tray-icon",
2580
+ appId: frame.appId,
2581
+ trayId: frame.trayId,
2582
+ icon: frame.icon
2583
+ });
2584
+ return;
2585
+ case "set-tray-tooltip":
2586
+ this.upsertJournal({
2587
+ kind: "set-tray-tooltip",
2588
+ appId: frame.appId,
2589
+ trayId: frame.trayId,
2590
+ tooltip: frame.tooltip
2591
+ });
2592
+ return;
2593
+ case "set-app-name":
2594
+ this.upsertJournal({
2595
+ kind: "set-app-name",
2596
+ appId: frame.appId,
2597
+ name: frame.name
2598
+ });
2599
+ return;
2600
+ case "set-app-icon":
2601
+ this.upsertJournal({
2602
+ kind: "set-app-icon",
2603
+ appId: frame.appId,
2604
+ appIcon: frame.appIcon
2605
+ });
2606
+ return;
2607
+ case "set-app-icon-variant":
2608
+ this.upsertJournal({
2609
+ kind: "set-app-icon-variant",
2610
+ appId: frame.appId,
2611
+ variant: frame.variant
2612
+ });
2613
+ return;
2614
+ case "load-ext":
2615
+ this.upsertJournal({
2616
+ kind: "load-ext",
2617
+ appId: frame.appId,
2618
+ name: frame.name,
2619
+ path: frame.path,
2620
+ expectedIdentity: frame.expectedIdentity,
2621
+ ...frame.mountId === void 0 ? {} : { mountId: frame.mountId }
2622
+ });
2623
+ return;
2624
+ default: return;
2625
+ }
2626
+ }
2627
+ /**
2628
+ * Keyed entries replace in place at first-occurrence position (last-write-
2629
+ * wins value, stable ordering); first occurrences append. Tray creations
2630
+ * and mount declarations keep their creation order by construction.
2631
+ */
2632
+ upsertJournal(entry) {
2633
+ const key = journalKeyOf(entry);
2634
+ if (key === void 0) {
2635
+ this.journal.push(entry);
2636
+ return;
2637
+ }
2638
+ const existing = this.journal.findIndex((candidate) => journalKeyOf(candidate) === key);
2639
+ if (existing < 0) {
2640
+ this.journal.push(entry);
2641
+ return;
2642
+ }
2643
+ this.journal[existing] = entry;
2644
+ }
2645
+ evictJournalTray(appId, trayId) {
2646
+ this.journal = this.journal.filter((entry) => {
2647
+ switch (entry.kind) {
2648
+ case "create-tray": return !(entry.app.appId === appId && entry.trayId === trayId);
2649
+ case "set-tray-menu":
2650
+ case "set-tray-icon":
2651
+ case "set-tray-tooltip": return !(entry.appId === appId && entry.trayId === trayId);
2652
+ default: return true;
2653
+ }
2654
+ });
2655
+ }
2656
+ };
2657
+ /**
2658
+ * Snapshot candidate for the declarative journal: a defensive clone of every
2659
+ * journalable mutating frame, taken before the await so later caller
2660
+ * mutation of the frame object cannot corrupt the recorded state. The entry
2661
+ * is committed only after the frame settles successfully.
2662
+ */
2663
+ const journalCandidateFrom = (frame) => {
2664
+ switch (frame.type) {
2665
+ case "resolve-default-app":
2666
+ case "create-tray":
2667
+ case "set-tray-menu":
2668
+ case "set-tray-icon":
2669
+ case "set-tray-tooltip":
2670
+ case "set-app-name":
2671
+ case "set-app-icon":
2672
+ case "set-app-icon-variant":
2673
+ case "load-ext": return structuredClone(frame);
2674
+ default: return;
2675
+ }
2676
+ };
2677
+ /** Constructs a supervised transport around one connect factory. */
2678
+ const createTransportSupervisor = (options) => new SupervisedLocalBrokerConnection(options.connect, options);
2679
+ const noop$1 = () => {};
2680
+ const noopAsync = () => {};
1900
2681
  //#endregion
1901
2682
  //#region src/menu-input.ts
1902
2683
  const normalizeCreateTrayMenu = (menu) => {
@@ -2054,23 +2835,28 @@ const createTray = async (options, runtimeOptions = {}) => {
2054
2835
  const appIcon = runtimeOptions.appIcon === void 0 ? void 0 : normalizeAppIcon(runtimeOptions.appIcon);
2055
2836
  const normalized = normalizeCreateTrayOptions(options);
2056
2837
  const appLaunch = normalizeAppLaunch(runtimeOptions.appLaunch);
2057
- const connection = await connectLocalBroker({
2058
- ...runtimeOptions,
2059
- ...appIcon === void 0 ? {} : { appIcon },
2060
- appLaunch
2838
+ const { recovery, ...brokerOptions } = runtimeOptions;
2839
+ const supervisor = createTransportSupervisor({
2840
+ connect: () => connectLocalBroker({
2841
+ ...brokerOptions,
2842
+ ...appIcon === void 0 ? {} : { appIcon },
2843
+ appLaunch
2844
+ }),
2845
+ ...recovery === void 0 ? {} : { recovery }
2061
2846
  });
2062
2847
  try {
2063
- const tray = await createClient(connection, { appOptions: {
2848
+ await supervisor.connect();
2849
+ const tray = await createClient(supervisor, { appOptions: {
2064
2850
  ...runtimeOptions.appName === void 0 ? {} : { name: runtimeOptions.appName },
2065
2851
  ...appIcon === void 0 ? {} : { appIcon }
2066
2852
  } }).createTray(normalized.options);
2067
2853
  return wrapCreateTrayHandle(tray, {
2068
- closeConnection: () => connection.close(),
2854
+ shutdown: () => supervisor.shutdown(),
2069
2855
  destroyPromise: void 0,
2070
2856
  menuUnsubscribe: bindMenuClickHandlers(tray, normalized.menuHandlers)
2071
2857
  });
2072
2858
  } catch (error) {
2073
- await connection.close().catch(noop);
2859
+ await supervisor.shutdown().catch(noop);
2074
2860
  throw error;
2075
2861
  }
2076
2862
  };
@@ -2117,7 +2903,7 @@ const wrapCreateTrayHandle = (tray, state) => {
2117
2903
  if (!isTransportClosed(error)) throw error;
2118
2904
  } finally {
2119
2905
  try {
2120
- await state.closeConnection();
2906
+ await state.shutdown();
2121
2907
  } catch (error) {
2122
2908
  if (!isTransportClosed(error)) throw error;
2123
2909
  }
@@ -2135,6 +2921,6 @@ const bindMenuClickHandlers = (tray, handlers) => {
2135
2921
  };
2136
2922
  const noop = () => {};
2137
2923
  //#endregion
2138
- export { AppIconVariantNotFoundError, BROKER_CONNECTION_CLOSED_MESSAGE, BrokerProtocolVersionError, BrokerServerError, DEFAULT_APP_ICON_VARIANT, EMBEDDED_STAGING_MANIFEST_PATH, EXTENSION_EVENT_RECORD_MAX_BYTES, EXTENSION_TRANSPORT_CLOSED_CODE, ExtensionOperationError, InvalidAppIconError, NATIVE_EXTENSION_EMBEDDED_ERROR_CODES, NATIVE_EXTENSION_EMBEDDED_TARGET_MATRIX, NativeExtensionArtifactResolutionError, NativeExtensionEmbeddedArtifactError, PROTOCOL_VERSION, createBrokerEndpointIdentity, createClient, createInitFrame, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion, resolveNativeExtensionArtifact, validateAppIcon };
2924
+ export { AppIconVariantNotFoundError, BOOTSTRAP_CALL_DEADLINE_MS, BROKER_CONNECTION_CLOSED_MESSAGE, BrokerProtocolVersionError, BrokerServerError, DEFAULT_APP_ICON_VARIANT, EMBEDDED_STAGING_MANIFEST_PATH, EXTENSION_EVENT_RECORD_MAX_BYTES, EXTENSION_TRANSPORT_CLOSED_CODE, ExtensionOperationError, INTERACTIVE_CALL_DEADLINE_MS, InvalidAppIconError, NATIVE_EXTENSION_EMBEDDED_ERROR_CODES, NATIVE_EXTENSION_EMBEDDED_TARGET_MATRIX, NativeExtensionArtifactResolutionError, NativeExtensionEmbeddedArtifactError, PROTOCOL_VERSION, TEARDOWN_CALL_DEADLINE_MS, TransportAbandonedError, TransportTimeoutError, createBrokerEndpointIdentity, createClient, createInitFrame, createTransportSupervisor, createTray, createTrayHandle, formatBrokerEndpointName, formatBrokerStateRoot, formatUnixSocketPath, formatWindowsPipeName, isSupportedProtocolVersion, resolveNativeExtensionArtifact, validateAppIcon };
2139
2925
 
2140
2926
  //# sourceMappingURL=index.mjs.map