@ai-matrx/desktop-protocol 0.6.0 → 0.7.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/client.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { BackoffOptions } from '@ai-matrx/realtime';
2
2
  export { RECONNECT_ALARM_ATTEMPTS } from '@ai-matrx/realtime';
3
3
  import { z } from 'zod';
4
- import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-jTmq1CsI.cjs';
5
- export { i as isDesktopProtocolError } from './registry-jTmq1CsI.cjs';
4
+ import { W as Welcome, D as DesktopProtocolError, C as CloseOutcome, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, e as ClientType } from './registry-Duc_VFN8.cjs';
5
+ export { i as isDesktopProtocolError } from './registry-Duc_VFN8.cjs';
6
6
  import { FrameHeader } from './frame.cjs';
7
7
 
8
8
  /** The subset of the WHATWG WebSocket the client uses — browser, React Native, `ws` and Workers all fit. */
@@ -49,6 +49,8 @@ interface DesktopClientState {
49
49
  retryInMs: number | null;
50
50
  lastError: DesktopProtocolError | null;
51
51
  lastCloseCode: number | null;
52
+ /** Why dialing stopped, when the far side refused us for good (CLOSE_RULES); null otherwise. */
53
+ outcome: CloseOutcome | null;
52
54
  }
53
55
  type DesktopClientDiagnostic = {
54
56
  kind: "malformed_message";
package/dist/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { BackoffOptions } from '@ai-matrx/realtime';
2
2
  export { RECONNECT_ALARM_ATTEMPTS } from '@ai-matrx/realtime';
3
3
  import { z } from 'zod';
4
- import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-jTmq1CsI.js';
5
- export { i as isDesktopProtocolError } from './registry-jTmq1CsI.js';
4
+ import { W as Welcome, D as DesktopProtocolError, C as CloseOutcome, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, e as ClientType } from './registry-Duc_VFN8.js';
5
+ export { i as isDesktopProtocolError } from './registry-Duc_VFN8.js';
6
6
  import { FrameHeader } from './frame.js';
7
7
 
8
8
  /** The subset of the WHATWG WebSocket the client uses — browser, React Native, `ws` and Workers all fit. */
@@ -49,6 +49,8 @@ interface DesktopClientState {
49
49
  retryInMs: number | null;
50
50
  lastError: DesktopProtocolError | null;
51
51
  lastCloseCode: number | null;
52
+ /** Why dialing stopped, when the far side refused us for good (CLOSE_RULES); null otherwise. */
53
+ outcome: CloseOutcome | null;
52
54
  }
53
55
  type DesktopClientDiagnostic = {
54
56
  kind: "malformed_message";
package/dist/client.js CHANGED
@@ -281,6 +281,29 @@ var CloseCode = {
281
281
  };
282
282
  var CloseCodeValue = z3.literal([1e3, 1001, 1006, 1012, 1013, 4001, 4003, 4004, 4008, 4009, 4010, 4400, 4426]);
283
283
 
284
+ // src/close-policy.ts
285
+ var CLOSE_RULES = {
286
+ [CloseCode.NORMAL]: { action: "reconnect", error: ["DEVICE_OFFLINE", "Connection closed"] },
287
+ [CloseCode.GOING_AWAY]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Connection lost"] },
288
+ [CloseCode.ABNORMAL]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Connection lost"] },
289
+ [CloseCode.DEVICE_RECONNECTED]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Device reconnected"] },
290
+ [CloseCode.TRY_AGAIN_LATER]: { action: "reconnect", error: ["DEVICE_OFFLINE", "Relay busy; retrying"] },
291
+ [CloseCode.AUTH_INVALID]: { action: "reauth", outcome: "signed_out", error: ["AUTH_INVALID", "Sign-in was not accepted"] },
292
+ [CloseCode.AUTH_FORBIDDEN]: { action: "stop", outcome: "not_owned", error: ["AUTH_FORBIDDEN", "Not this device's owner"] },
293
+ [CloseCode.DEVICE_NOT_FOUND]: { action: "stop", outcome: "removed", error: ["NOT_FOUND", "Device not found"] },
294
+ [CloseCode.AUTH_EXPIRED]: { action: "reauth", outcome: "signed_out", error: ["AUTH_EXPIRED", "Sign-in expired"] },
295
+ [CloseCode.REPLACED]: { action: "stop", outcome: "replaced", error: ["CONFLICT", "Replaced by a newer connection"] },
296
+ [CloseCode.DEVICE_REVOKED]: { action: "stop", outcome: "revoked", error: ["AUTH_DEVICE_REVOKED", "This device was revoked"] },
297
+ // A malformed message, or a connection the relay lost track of: a fresh socket starts clean.
298
+ [CloseCode.PROTOCOL_ERROR]: { action: "reconnect", error: ["PROTOCOL_ERROR", "Connection reset by the relay"] },
299
+ [CloseCode.UPGRADE_REQUIRED]: { action: "stop", outcome: "upgrade_required", error: ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"] }
300
+ };
301
+ var UNKNOWN = { action: "reconnect", error: ["DEVICE_OFFLINE", "Connection lost"] };
302
+ function closeRule(code) {
303
+ if (code === null || code === void 0) return CLOSE_RULES[CloseCode.ABNORMAL];
304
+ return CLOSE_RULES[code] ?? UNKNOWN;
305
+ }
306
+
284
307
  // src/schema/frame.ts
285
308
  import { z as z4 } from "zod";
286
309
  var FrameHeader = z4.strictObject({
@@ -702,13 +725,6 @@ var EVENTS = {
702
725
 
703
726
  // src/client/client.ts
704
727
  var WS_OPEN = 1;
705
- var TERMINAL_CLOSE = /* @__PURE__ */ new Map([
706
- [CloseCode.DEVICE_REVOKED, ["AUTH_DEVICE_REVOKED", "This device was revoked"]],
707
- [CloseCode.AUTH_FORBIDDEN, ["AUTH_FORBIDDEN", "Not this device's owner"]],
708
- [CloseCode.DEVICE_NOT_FOUND, ["NOT_FOUND", "Device not found"]],
709
- [CloseCode.REPLACED, ["CONFLICT", "Replaced by a newer connection"]],
710
- [CloseCode.UPGRADE_REQUIRED, ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"]]
711
- ]);
712
728
  var defaultTimers = {
713
729
  setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
714
730
  clearTimeout: (handle) => globalThis.clearTimeout(handle),
@@ -751,8 +767,10 @@ function createDesktopClient(options) {
751
767
  attempts: 0,
752
768
  retryInMs: null,
753
769
  lastError: null,
754
- lastCloseCode: null
770
+ lastCloseCode: null,
771
+ outcome: null
755
772
  };
773
+ let authRefusals = 0;
756
774
  const listeners = /* @__PURE__ */ new Set();
757
775
  const readyWaiters = /* @__PURE__ */ new Set();
758
776
  const pending = /* @__PURE__ */ new Map();
@@ -787,6 +805,7 @@ function createDesktopClient(options) {
787
805
  }
788
806
  function connect() {
789
807
  if (state.status === "connecting" || state.status === "handshaking" || state.status === "open") return;
808
+ if (state.status === "closed" || state.status === "idle") authRefusals = 0;
790
809
  closedByUser = false;
791
810
  if (reconnectTimer !== null) {
792
811
  timers.clearTimeout(reconnectTimer);
@@ -868,8 +887,11 @@ function createDesktopClient(options) {
868
887
  timers.clearTimeout(probeTimer);
869
888
  probeTimer = null;
870
889
  }
871
- const terminal = code === null ? void 0 : TERMINAL_CLOSE.get(code);
872
- const lost = error ?? (terminal ? new DesktopProtocolError(terminal[0], terminal[1]) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
890
+ const rule = code === null ? null : closeRule(code);
891
+ if (rule?.action === "reauth") authRefusals++;
892
+ const stopped = rule !== null && (rule.action === "stop" || rule.action === "reauth" && authRefusals >= 2);
893
+ const terminal = stopped && rule ? rule : void 0;
894
+ const lost = error ?? (terminal ? new DesktopProtocolError(terminal.error[0], terminal.error[1]) : rule && rule.action === "reauth" ? new DesktopProtocolError(rule.error[0], rule.error[1], { retryable: true }) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
873
895
  for (const [id, p] of pending) {
874
896
  pending.delete(id);
875
897
  p.cleanup();
@@ -886,7 +908,14 @@ function createDesktopClient(options) {
886
908
  endStream(s, null, lost);
887
909
  }
888
910
  if (closedByUser || terminal) {
889
- setState({ status: "closed", welcome: null, lastError: closedByUser ? state.lastError : lost, lastCloseCode: code, retryInMs: null });
911
+ setState({
912
+ status: "closed",
913
+ welcome: null,
914
+ lastError: closedByUser ? state.lastError : lost,
915
+ lastCloseCode: code,
916
+ retryInMs: null,
917
+ outcome: closedByUser ? null : terminal?.outcome ?? null
918
+ });
890
919
  rejectReadyWaiters(lost);
891
920
  return;
892
921
  }
@@ -1040,7 +1069,8 @@ function createDesktopClient(options) {
1040
1069
  }
1041
1070
  helloId = null;
1042
1071
  backoff.markConnected();
1043
- setState({ status: "open", welcome: welcome.data, attempts: 0, retryInMs: null, lastError: null });
1072
+ authRefusals = 0;
1073
+ setState({ status: "open", welcome: welcome.data, attempts: 0, retryInMs: null, lastError: null, outcome: null });
1044
1074
  for (const w of [...readyWaiters]) {
1045
1075
  readyWaiters.delete(w);
1046
1076
  w.resolve(welcome.data);