@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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ - `CLOSE_RULES` / `closeRule(code)` / `isTerminalClose(code)` (`src/close-policy.ts`): the one table
6
+ of what a dialer does after each close — `reconnect_now`, `reconnect`, `reauth` (one retry with a
7
+ fresh token; a second refusal before any welcome is terminal, outcome `signed_out`), or `stop`
8
+ with an `outcome` (`not_owned` 4003, `removed` 4004, `replaced` 4009, `revoked` 4010,
9
+ `upgrade_required` 4426). A CloseCode without a rule fails to compile; unknown codes reconnect.
10
+ - The client reads that table (its private terminal list is gone) and reports
11
+ `DesktopClientState.outcome` when it stops; 4001/4008 no longer redial forever.
12
+
13
+ Consumer action: never keep a local list of terminal close codes; branch on `closeRule(code)` and
14
+ show `state.outcome`.
15
+
3
16
  ## 0.6.0
4
17
 
5
18
  - `RelayDeviceStatusEvent.since_ms` is `null` for a device that has never connected (protocol 1.1;
package/dist/client.cjs CHANGED
@@ -311,6 +311,29 @@ var CloseCode = {
311
311
  };
312
312
  var CloseCodeValue = import_zod3.z.literal([1e3, 1001, 1006, 1012, 1013, 4001, 4003, 4004, 4008, 4009, 4010, 4400, 4426]);
313
313
 
314
+ // src/close-policy.ts
315
+ var CLOSE_RULES = {
316
+ [CloseCode.NORMAL]: { action: "reconnect", error: ["DEVICE_OFFLINE", "Connection closed"] },
317
+ [CloseCode.GOING_AWAY]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Connection lost"] },
318
+ [CloseCode.ABNORMAL]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Connection lost"] },
319
+ [CloseCode.DEVICE_RECONNECTED]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Device reconnected"] },
320
+ [CloseCode.TRY_AGAIN_LATER]: { action: "reconnect", error: ["DEVICE_OFFLINE", "Relay busy; retrying"] },
321
+ [CloseCode.AUTH_INVALID]: { action: "reauth", outcome: "signed_out", error: ["AUTH_INVALID", "Sign-in was not accepted"] },
322
+ [CloseCode.AUTH_FORBIDDEN]: { action: "stop", outcome: "not_owned", error: ["AUTH_FORBIDDEN", "Not this device's owner"] },
323
+ [CloseCode.DEVICE_NOT_FOUND]: { action: "stop", outcome: "removed", error: ["NOT_FOUND", "Device not found"] },
324
+ [CloseCode.AUTH_EXPIRED]: { action: "reauth", outcome: "signed_out", error: ["AUTH_EXPIRED", "Sign-in expired"] },
325
+ [CloseCode.REPLACED]: { action: "stop", outcome: "replaced", error: ["CONFLICT", "Replaced by a newer connection"] },
326
+ [CloseCode.DEVICE_REVOKED]: { action: "stop", outcome: "revoked", error: ["AUTH_DEVICE_REVOKED", "This device was revoked"] },
327
+ // A malformed message, or a connection the relay lost track of: a fresh socket starts clean.
328
+ [CloseCode.PROTOCOL_ERROR]: { action: "reconnect", error: ["PROTOCOL_ERROR", "Connection reset by the relay"] },
329
+ [CloseCode.UPGRADE_REQUIRED]: { action: "stop", outcome: "upgrade_required", error: ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"] }
330
+ };
331
+ var UNKNOWN = { action: "reconnect", error: ["DEVICE_OFFLINE", "Connection lost"] };
332
+ function closeRule(code) {
333
+ if (code === null || code === void 0) return CLOSE_RULES[CloseCode.ABNORMAL];
334
+ return CLOSE_RULES[code] ?? UNKNOWN;
335
+ }
336
+
314
337
  // src/schema/frame.ts
315
338
  var import_zod4 = require("zod");
316
339
  var FrameHeader = import_zod4.z.strictObject({
@@ -732,13 +755,6 @@ var EVENTS = {
732
755
 
733
756
  // src/client/client.ts
734
757
  var WS_OPEN = 1;
735
- var TERMINAL_CLOSE = /* @__PURE__ */ new Map([
736
- [CloseCode.DEVICE_REVOKED, ["AUTH_DEVICE_REVOKED", "This device was revoked"]],
737
- [CloseCode.AUTH_FORBIDDEN, ["AUTH_FORBIDDEN", "Not this device's owner"]],
738
- [CloseCode.DEVICE_NOT_FOUND, ["NOT_FOUND", "Device not found"]],
739
- [CloseCode.REPLACED, ["CONFLICT", "Replaced by a newer connection"]],
740
- [CloseCode.UPGRADE_REQUIRED, ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"]]
741
- ]);
742
758
  var defaultTimers = {
743
759
  setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
744
760
  clearTimeout: (handle) => globalThis.clearTimeout(handle),
@@ -781,8 +797,10 @@ function createDesktopClient(options) {
781
797
  attempts: 0,
782
798
  retryInMs: null,
783
799
  lastError: null,
784
- lastCloseCode: null
800
+ lastCloseCode: null,
801
+ outcome: null
785
802
  };
803
+ let authRefusals = 0;
786
804
  const listeners = /* @__PURE__ */ new Set();
787
805
  const readyWaiters = /* @__PURE__ */ new Set();
788
806
  const pending = /* @__PURE__ */ new Map();
@@ -817,6 +835,7 @@ function createDesktopClient(options) {
817
835
  }
818
836
  function connect() {
819
837
  if (state.status === "connecting" || state.status === "handshaking" || state.status === "open") return;
838
+ if (state.status === "closed" || state.status === "idle") authRefusals = 0;
820
839
  closedByUser = false;
821
840
  if (reconnectTimer !== null) {
822
841
  timers.clearTimeout(reconnectTimer);
@@ -898,8 +917,11 @@ function createDesktopClient(options) {
898
917
  timers.clearTimeout(probeTimer);
899
918
  probeTimer = null;
900
919
  }
901
- const terminal = code === null ? void 0 : TERMINAL_CLOSE.get(code);
902
- const lost = error ?? (terminal ? new DesktopProtocolError(terminal[0], terminal[1]) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
920
+ const rule = code === null ? null : closeRule(code);
921
+ if (rule?.action === "reauth") authRefusals++;
922
+ const stopped = rule !== null && (rule.action === "stop" || rule.action === "reauth" && authRefusals >= 2);
923
+ const terminal = stopped && rule ? rule : void 0;
924
+ 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 }));
903
925
  for (const [id, p] of pending) {
904
926
  pending.delete(id);
905
927
  p.cleanup();
@@ -916,7 +938,14 @@ function createDesktopClient(options) {
916
938
  endStream(s, null, lost);
917
939
  }
918
940
  if (closedByUser || terminal) {
919
- setState({ status: "closed", welcome: null, lastError: closedByUser ? state.lastError : lost, lastCloseCode: code, retryInMs: null });
941
+ setState({
942
+ status: "closed",
943
+ welcome: null,
944
+ lastError: closedByUser ? state.lastError : lost,
945
+ lastCloseCode: code,
946
+ retryInMs: null,
947
+ outcome: closedByUser ? null : terminal?.outcome ?? null
948
+ });
920
949
  rejectReadyWaiters(lost);
921
950
  return;
922
951
  }
@@ -1070,7 +1099,8 @@ function createDesktopClient(options) {
1070
1099
  }
1071
1100
  helloId = null;
1072
1101
  backoff.markConnected();
1073
- setState({ status: "open", welcome: welcome.data, attempts: 0, retryInMs: null, lastError: null });
1102
+ authRefusals = 0;
1103
+ setState({ status: "open", welcome: welcome.data, attempts: 0, retryInMs: null, lastError: null, outcome: null });
1074
1104
  for (const w of [...readyWaiters]) {
1075
1105
  readyWaiters.delete(w);
1076
1106
  w.resolve(welcome.data);