@cryptiklemur/lattice 1.29.0 → 1.29.2

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.
@@ -57,11 +57,20 @@ export var PairingDialog = memo(function PairingDialog(props: PairingDialogProps
57
57
  }
58
58
  }
59
59
 
60
+ function handlePairFailed(msg: ServerMessage) {
61
+ if (msg.type !== "mesh:pair_failed") return;
62
+ var data = msg as { type: string; message: string };
63
+ setPairStatus("failed");
64
+ setPairError(data.message);
65
+ }
66
+
60
67
  ws.subscribe("mesh:invite_code", handleInvite);
61
68
  ws.subscribe("mesh:paired", handlePaired);
69
+ ws.subscribe("mesh:pair_failed", handlePairFailed);
62
70
  return function () {
63
71
  ws.unsubscribe("mesh:invite_code", handleInvite);
64
72
  ws.unsubscribe("mesh:paired", handlePaired);
73
+ ws.unsubscribe("mesh:pair_failed", handlePairFailed);
65
74
  };
66
75
  }, []);
67
76
 
@@ -220,8 +229,9 @@ export var PairingDialog = memo(function PairingDialog(props: PairingDialogProps
220
229
  type="text"
221
230
  value={pairCode}
222
231
  onChange={function (e) {
223
- var raw = e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, "");
224
- if (raw.startsWith("LTCE")) raw = raw.slice(4);
232
+ var raw = e.target.value.replace(/[^A-Za-z0-9]/g, "");
233
+ var upper = raw.toUpperCase();
234
+ if (upper.startsWith("LTCE")) raw = raw.slice(4);
225
235
  if (raw.length > 16) raw = raw.slice(0, 16);
226
236
  var chunks: string[] = [];
227
237
  for (var i = 0; i < raw.length; i += 4) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.29.0",
3
+ "version": "1.29.2",
4
4
  "description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
5
5
  "license": "MIT",
6
6
  "author": "Aaron Scherer <me@aaronscherer.me>",
@@ -4,6 +4,8 @@ import { sendTo, broadcast } from "../ws/broadcast";
4
4
  import { loadConfig } from "../config";
5
5
  import { loadOrCreateIdentity } from "../identity";
6
6
  import { generateInviteCode, parseInviteCode, validatePairingToken, consumePairingToken } from "../mesh/pairing";
7
+ import { addPeer, removePeer, loadPeers } from "../mesh/peers";
8
+ import type { PeerInfo } from "@lattice/shared";
7
9
  import { networkInterfaces } from "node:os";
8
10
 
9
11
  function getLocalAddress(): string {
@@ -20,8 +22,6 @@ function getLocalAddress(): string {
20
22
  }
21
23
  return "localhost";
22
24
  }
23
- import { addPeer, removePeer, loadPeers } from "../mesh/peers";
24
- import type { PeerInfo } from "@lattice/shared";
25
25
 
26
26
  export function buildNodesMessage(): NodeInfo[] {
27
27
  var peers = loadPeers();
@@ -75,30 +75,41 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
75
75
  var pairMsg = message as MeshPairMessage;
76
76
  var parsed = parseInviteCode(pairMsg.code);
77
77
  if (!parsed) {
78
- sendTo(clientId, { type: "chat:error", message: "Invalid invite code format" });
79
- return;
80
- }
81
- if (!validatePairingToken(parsed.token)) {
82
- sendTo(clientId, { type: "chat:error", message: "Invite code is invalid or expired" });
78
+ sendTo(clientId, { type: "mesh:pair_failed", message: "Invalid invite code format" });
83
79
  return;
84
80
  }
85
- consumePairingToken(parsed.token);
86
81
 
87
82
  var wsUrl = "ws://" + parsed.address + ":" + parsed.port + "/ws";
88
- var ws = new WebSocket(wsUrl);
89
- ws.addEventListener("open", function () {
83
+ var pairWs = new WebSocket(wsUrl);
84
+ var pairTimeout = setTimeout(function () {
85
+ pairWs.close();
86
+ sendTo(clientId, { type: "mesh:pair_failed", message: "Connection timed out" });
87
+ }, 15000);
88
+
89
+ pairWs.addEventListener("open", function () {
90
90
  var identity = loadOrCreateIdentity();
91
- ws.send(JSON.stringify({
91
+ pairWs.send(JSON.stringify({
92
92
  type: "mesh:hello",
93
93
  nodeId: identity.id,
94
94
  name: loadConfig().name,
95
+ token: parsed!.token,
95
96
  projects: [],
96
97
  }));
97
98
  });
98
- ws.addEventListener("message", function (event: MessageEvent) {
99
+
100
+ pairWs.addEventListener("message", function (event: MessageEvent) {
99
101
  try {
100
- var data = JSON.parse(event.data as string) as { type: string; nodeId?: string; name?: string };
102
+ var data = JSON.parse(event.data as string) as { type: string; nodeId?: string; name?: string; error?: string };
103
+
104
+ if (data.type === "mesh:hello_rejected") {
105
+ clearTimeout(pairTimeout);
106
+ pairWs.close();
107
+ sendTo(clientId, { type: "mesh:pair_failed", message: data.error ?? "Pairing rejected by remote node" });
108
+ return;
109
+ }
110
+
101
111
  if (data.type === "mesh:hello" && data.nodeId && data.name) {
112
+ clearTimeout(pairTimeout);
102
113
  var peer: PeerInfo = {
103
114
  id: data.nodeId,
104
115
  name: data.name,
@@ -107,7 +118,7 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
107
118
  pairedAt: Date.now(),
108
119
  };
109
120
  addPeer(peer);
110
- ws.close();
121
+ pairWs.close();
111
122
 
112
123
  var nodeInfo: NodeInfo = {
113
124
  id: peer.id,
@@ -125,10 +136,41 @@ registerHandler("mesh", function (clientId: string, message: ClientMessage) {
125
136
  console.error("[lattice] mesh:pair — invalid handshake response");
126
137
  }
127
138
  });
128
- ws.addEventListener("error", function () {
129
- console.error("[lattice] mesh:pair — failed to connect to", wsUrl);
130
- sendTo(clientId, { type: "chat:error", message: "Failed to connect to " + parsed!.address + ":" + parsed!.port });
139
+
140
+ pairWs.addEventListener("error", function () {
141
+ clearTimeout(pairTimeout);
142
+ sendTo(clientId, { type: "mesh:pair_failed", message: "Failed to connect to " + parsed!.address + ":" + parsed!.port });
143
+ });
144
+ return;
145
+ }
146
+
147
+ if ((message as any).type === "mesh:hello") {
148
+ var hello = message as any as { type: "mesh:hello"; nodeId: string; name: string; token?: string; projects: Array<{ slug: string; title: string }> };
149
+
150
+ if (!hello.token || !validatePairingToken(hello.token)) {
151
+ sendTo(clientId, { type: "mesh:hello_rejected" as any, error: "Invalid or expired invite code" });
152
+ return;
153
+ }
154
+ consumePairingToken(hello.token);
155
+
156
+ var peer: PeerInfo = {
157
+ id: hello.nodeId,
158
+ name: hello.name,
159
+ addresses: [],
160
+ publicKey: "",
161
+ pairedAt: Date.now(),
162
+ };
163
+ addPeer(peer);
164
+
165
+ var identity = loadOrCreateIdentity();
166
+ sendTo(clientId, {
167
+ type: "mesh:hello" as any,
168
+ nodeId: identity.id,
169
+ name: loadConfig().name,
170
+ projects: [],
131
171
  });
172
+
173
+ broadcast({ type: "mesh:nodes", nodes: buildNodesMessage() });
132
174
  return;
133
175
  }
134
176
 
@@ -761,6 +761,11 @@ export interface MeshPairedMessage {
761
761
  node: NodeInfo;
762
762
  }
763
763
 
764
+ export interface MeshPairFailedMessage {
765
+ type: "mesh:pair_failed";
766
+ message: string;
767
+ }
768
+
764
769
  export interface MeshNodeOnlineMessage {
765
770
  type: "mesh:node_online";
766
771
  nodeId: string;
@@ -1022,6 +1027,7 @@ export type ServerMessage =
1022
1027
  | MeshNodesMessage
1023
1028
  | MeshInviteCodeMessage
1024
1029
  | MeshPairedMessage
1030
+ | MeshPairFailedMessage
1025
1031
  | MeshNodeOnlineMessage
1026
1032
  | MeshNodeOfflineMessage
1027
1033
  | ProjectsListMessage