@cryptiklemur/lattice 5.5.0 → 5.6.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.
Files changed (87) hide show
  1. package/dist/client/assets/{angular-html-Bmjy4gNc.js → angular-html-B_LrO4d_.js} +1 -1
  2. package/dist/client/assets/{angular-ts-10l13C4C.js → angular-ts-DG2-AEn3.js} +1 -1
  3. package/dist/client/assets/{apl--Bn7yH0T.js → apl-D9AV-2KJ.js} +1 -1
  4. package/dist/client/assets/{astro-C8P9TQkP.js → astro-D55z-bYO.js} +1 -1
  5. package/dist/client/assets/{blade-DQiHYnIS.js → blade-Bs-tcMis.js} +1 -1
  6. package/dist/client/assets/{c-BFny-WnE.js → c-CAs-oCwZ.js} +1 -1
  7. package/dist/client/assets/{cobol-hGEexMYH.js → cobol-0B2WKsi0.js} +1 -1
  8. package/dist/client/assets/{coffee-j3W4ZALV.js → coffee-BLi0y75x.js} +1 -1
  9. package/dist/client/assets/{cpp-DquWJNps.js → cpp-CTXpwK-o.js} +1 -1
  10. package/dist/client/assets/{crystal-CWL31dIV.js → crystal-B_OPrNIv.js} +1 -1
  11. package/dist/client/assets/{css-Di4GSq8V.js → css-C9nlz4Z2.js} +1 -1
  12. package/dist/client/assets/{dist-CaJdL3Va.js → dist-CzUlrFrL.js} +2 -2
  13. package/dist/client/assets/{edge-rK4d050l.js → edge-DE3SfmPJ.js} +1 -1
  14. package/dist/client/assets/{elixir-BMFKKu5z.js → elixir-BS_1fIPf.js} +1 -1
  15. package/dist/client/assets/{elm-B0QVlW3-.js → elm-CrfkxZpb.js} +1 -1
  16. package/dist/client/assets/{erb-C9w9QbhL.js → erb-B6Ab2Y07.js} +1 -1
  17. package/dist/client/assets/{git-rebase-FaylS7Vl.js → git-rebase-RkfMNUtm.js} +1 -1
  18. package/dist/client/assets/{glimmer-js-Bi2XnbHi.js → glimmer-js-CBA96B3V.js} +1 -1
  19. package/dist/client/assets/{glimmer-ts-DOdxK6E6.js → glimmer-ts-WPYNrOak.js} +1 -1
  20. package/dist/client/assets/{glsl-BiGDIGbZ.js → glsl-BVPbkPwC.js} +1 -1
  21. package/dist/client/assets/{graphql-CZJB8KIP.js → graphql-CplPyf-J.js} +1 -1
  22. package/dist/client/assets/{hack-CNGYBFqH.js → hack-Bqnr1D44.js} +1 -1
  23. package/dist/client/assets/{haml-pf3Zh5MH.js → haml-Bk9H5oqC.js} +1 -1
  24. package/dist/client/assets/{handlebars-BsQwsvPC.js → handlebars-Dho-7JSJ.js} +1 -1
  25. package/dist/client/assets/{html-Dj4Hvv69.js → html-Dx1dAJ5K.js} +1 -1
  26. package/dist/client/assets/{html-derivative-CE7dQ_mI.js → html-derivative-DMEkbacz.js} +1 -1
  27. package/dist/client/assets/{http-DjOuMI1u.js → http-yzZRblhh.js} +1 -1
  28. package/dist/client/assets/{hurl-BH_MQrQo.js → hurl-DwA-puXE.js} +1 -1
  29. package/dist/client/assets/{index-DA8ZBSYW.js → index-BV9isded.js} +2 -2
  30. package/dist/client/assets/{java-CnpykAva.js → java-da4cNL_S.js} +1 -1
  31. package/dist/client/assets/{javascript-BbqQ1sQM.js → javascript-BRBtsTGZ.js} +1 -1
  32. package/dist/client/assets/{jinja-DddpvWwk.js → jinja-KYkTqvLF.js} +1 -1
  33. package/dist/client/assets/{jison-Bd3G2n4x.js → jison-CpJZ2qu3.js} +1 -1
  34. package/dist/client/assets/{json-F0eTFQKP.js → json-1ZKXsOcj.js} +1 -1
  35. package/dist/client/assets/{jsx-B-wB5nUs.js → jsx-Ci3IutCK.js} +1 -1
  36. package/dist/client/assets/{julia-9Lfz4b5s.js → julia-8BIB33ff.js} +1 -1
  37. package/dist/client/assets/{just-BVE1sBJx.js → just-BlxIBqAl.js} +1 -1
  38. package/dist/client/assets/{latex-SF3xMSWG.js → latex-Dt6aIDIN.js} +1 -1
  39. package/dist/client/assets/{liquid-CLkbZ0ZV.js → liquid-rNU0f2fO.js} +1 -1
  40. package/dist/client/assets/{lua-DNph6TL8.js → lua-9O59duWh.js} +1 -1
  41. package/dist/client/assets/{marko-CGqdEtbF.js → marko-cSK6Q4M9.js} +1 -1
  42. package/dist/client/assets/{mdc-BLpzcq82.js → mdc-DD0DONjK.js} +1 -1
  43. package/dist/client/assets/{nginx-s7ciyZD1.js → nginx-Bfzt7qPl.js} +1 -1
  44. package/dist/client/assets/{nim-C4FeZI3R.js → nim-DOD72OhS.js} +1 -1
  45. package/dist/client/assets/{perl-GDfD5AIi.js → perl-1cg42R6O.js} +1 -1
  46. package/dist/client/assets/{php-CusAuy6y.js → php-DfG0eJE0.js} +1 -1
  47. package/dist/client/assets/{pug-DCKuXO7x.js → pug-BJ4ktrjh.js} +1 -1
  48. package/dist/client/assets/{qml-rp1ZHoeo.js → qml-BwGhenQ4.js} +1 -1
  49. package/dist/client/assets/{r-CwNbYaVb.js → r-DSKy_gZ9.js} +1 -1
  50. package/dist/client/assets/{razor-CinY5yf4.js → razor-morGoSMw.js} +1 -1
  51. package/dist/client/assets/{regexp-CxVI4rKV.js → regexp-DSQzd7Gw.js} +1 -1
  52. package/dist/client/assets/{rst-BrBmBgUr.js → rst-CQ4DB61w.js} +1 -1
  53. package/dist/client/assets/{ruby-BzNCpRio.js → ruby-dcUoNsdX.js} +1 -1
  54. package/dist/client/assets/{sas-B4IAap7m.js → sas-BckOIwmR.js} +1 -1
  55. package/dist/client/assets/{scss-eNAsUEA1.js → scss-CIDQomQQ.js} +1 -1
  56. package/dist/client/assets/{shellscript-BwH0aChW.js → shellscript-DscXCB_p.js} +1 -1
  57. package/dist/client/assets/{shellsession-B5ibJBbh.js → shellsession-C9EjZURj.js} +1 -1
  58. package/dist/client/assets/{soy-BrqReQP3.js → soy-CVzyKHIE.js} +1 -1
  59. package/dist/client/assets/{sql-KIDgTieS.js → sql-Ddk-vRG3.js} +1 -1
  60. package/dist/client/assets/{stata-D6yim7Rr.js → stata-BGl5cxb8.js} +1 -1
  61. package/dist/client/assets/{surrealql-C51iMPEA.js → surrealql-3IZgL_Ze.js} +1 -1
  62. package/dist/client/assets/{svelte-DYs5QSVt.js → svelte-BRM0niF5.js} +1 -1
  63. package/dist/client/assets/{templ-CLDdaEXl.js → templ-qbgb30u-.js} +1 -1
  64. package/dist/client/assets/{tex-D16HBCoj.js → tex-Ct_-hpJo.js} +1 -1
  65. package/dist/client/assets/{ts-tags-DTcB2bBd.js → ts-tags-C85B12Rc.js} +1 -1
  66. package/dist/client/assets/{tsx-BxGfVt8a.js → tsx-d401eVG9.js} +1 -1
  67. package/dist/client/assets/{twig-BUC08jwe.js → twig-CEvQ_MDP.js} +1 -1
  68. package/dist/client/assets/{typescript-CuPGT2MR.js → typescript-y2NNcyKH.js} +1 -1
  69. package/dist/client/assets/{vue-_ip8-SIk.js → vue-CiTZbygT.js} +1 -1
  70. package/dist/client/assets/{vue-html-D-I9-U-x.js → vue-html-B4nYUWKJ.js} +1 -1
  71. package/dist/client/assets/{vue-vine-7AONB1tt.js → vue-vine-DdfbYtGV.js} +1 -1
  72. package/dist/client/assets/{xml-D2ZUDsPU.js → xml-1B2HtnyO.js} +1 -1
  73. package/dist/client/assets/{xsl-GlFKKlLy.js → xsl-DoRYSDxo.js} +1 -1
  74. package/dist/client/assets/{yaml-BnuTxWck.js → yaml-DpbRFfH9.js} +1 -1
  75. package/dist/client/index.html +1 -1
  76. package/dist/client/sw.js +1 -1
  77. package/dist/server/daemon.js +60 -27
  78. package/dist/server/handlers/attachment.js +66 -29
  79. package/dist/server/handlers/fs.js +58 -72
  80. package/dist/server/handlers/memory.js +37 -52
  81. package/dist/server/handlers/mesh.js +4 -1
  82. package/dist/server/handlers/session.js +38 -57
  83. package/dist/server/mesh/connector.js +85 -1
  84. package/dist/server/project/file-browser.js +14 -14
  85. package/dist/server/project/session.js +7 -4
  86. package/dist/server/ws/broadcast.js +60 -0
  87. package/package.json +1 -1
@@ -18,6 +18,77 @@ var CIRCUIT_BREAKER_THRESHOLD = 5;
18
18
  var CIRCUIT_BREAKER_COOLDOWN = 60000;
19
19
  var circuitBreakers = new Map();
20
20
  var RECONNECT_INTERVAL_MS = 15000;
21
+ var HEALTH_CHECK_INTERVAL_MS = 30000;
22
+ var HEALTH_MISS_THRESHOLD = 3;
23
+ var healthStates = new Map();
24
+ export function getPeerHealth(nodeId) {
25
+ var state = healthStates.get(nodeId);
26
+ if (!state)
27
+ return undefined;
28
+ return { latencyMs: state.latencyMs, healthy: state.healthy };
29
+ }
30
+ export function getAllPeerHealth() {
31
+ var result = new Map();
32
+ for (var [nodeId, state] of healthStates) {
33
+ result.set(nodeId, { latencyMs: state.latencyMs, healthy: state.healthy });
34
+ }
35
+ return result;
36
+ }
37
+ function startHealthCheck(conn) {
38
+ var state = healthStates.get(conn.nodeId);
39
+ if (state && state.pingTimer)
40
+ return;
41
+ var healthState = {
42
+ lastPongAt: Date.now(),
43
+ latencyMs: 0,
44
+ missedPongs: 0,
45
+ healthy: true,
46
+ pingTimer: null,
47
+ };
48
+ healthStates.set(conn.nodeId, healthState);
49
+ var lastPingSentAt = 0;
50
+ healthState.pingTimer = setInterval(function () {
51
+ if (conn.dead || !isWebSocketOpen(conn.ws)) {
52
+ stopHealthCheck(conn.nodeId);
53
+ return;
54
+ }
55
+ if (lastPingSentAt > 0 && healthState.lastPongAt < lastPingSentAt) {
56
+ healthState.missedPongs++;
57
+ if (healthState.missedPongs >= HEALTH_MISS_THRESHOLD && healthState.healthy) {
58
+ healthState.healthy = false;
59
+ log.mesh("Health check: peer %s marked unhealthy (%d missed pongs)", conn.nodeId.slice(0, 8), healthState.missedPongs);
60
+ }
61
+ }
62
+ lastPingSentAt = Date.now();
63
+ try {
64
+ conn.ws.send(JSON.stringify({ type: "mesh:ping", timestamp: lastPingSentAt }));
65
+ }
66
+ catch {
67
+ healthState.missedPongs++;
68
+ }
69
+ }, HEALTH_CHECK_INTERVAL_MS);
70
+ healthState.pingTimer.unref?.();
71
+ }
72
+ function stopHealthCheck(nodeId) {
73
+ var state = healthStates.get(nodeId);
74
+ if (state && state.pingTimer) {
75
+ clearInterval(state.pingTimer);
76
+ state.pingTimer = null;
77
+ }
78
+ healthStates.delete(nodeId);
79
+ }
80
+ function handlePong(nodeId, timestamp) {
81
+ var state = healthStates.get(nodeId);
82
+ if (!state)
83
+ return;
84
+ state.lastPongAt = Date.now();
85
+ state.latencyMs = Date.now() - timestamp;
86
+ state.missedPongs = 0;
87
+ if (!state.healthy) {
88
+ state.healthy = true;
89
+ log.mesh("Health check: peer %s recovered (latency: %dms)", nodeId.slice(0, 8), state.latencyMs);
90
+ }
91
+ }
21
92
  export function startMeshConnections() {
22
93
  reconcilePeers();
23
94
  setInterval(reconcilePeers, RECONNECT_INTERVAL_MS);
@@ -45,12 +116,13 @@ function reconcilePeers() {
45
116
  }
46
117
  }
47
118
  export function stopMeshConnections() {
48
- for (var [, conn] of connections) {
119
+ for (var [nodeId, conn] of connections) {
49
120
  conn.dead = true;
50
121
  if (conn.retryTimer !== null) {
51
122
  clearTimeout(conn.retryTimer);
52
123
  conn.retryTimer = null;
53
124
  }
125
+ stopHealthCheck(nodeId);
54
126
  conn.ws.close();
55
127
  }
56
128
  connections.clear();
@@ -142,6 +214,17 @@ function openConnection(conn, url) {
142
214
  if (msg.projects.length > 0) {
143
215
  lastKnownProjects.set(conn.nodeId, msg.projects);
144
216
  }
217
+ startHealthCheck(conn);
218
+ }
219
+ else if (msg.type === "mesh:ping") {
220
+ var pingMsg = msg;
221
+ conn.ws.send(JSON.stringify({ type: "mesh:pong", timestamp: pingMsg.timestamp }));
222
+ return;
223
+ }
224
+ else if (msg.type === "mesh:pong") {
225
+ var pongMsg = msg;
226
+ handlePong(conn.nodeId, pongMsg.timestamp);
227
+ return;
145
228
  }
146
229
  else if (msg.type === "mesh:session_sync") {
147
230
  handleSessionSync(conn.nodeId, msg);
@@ -264,6 +347,7 @@ export function disconnectPeer(nodeId) {
264
347
  existing.ws.close();
265
348
  connections.delete(nodeId);
266
349
  }
350
+ stopHealthCheck(nodeId);
267
351
  circuitBreakers.delete(nodeId);
268
352
  }
269
353
  export function reconnectPeer(nodeId) {
@@ -1,4 +1,4 @@
1
- import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
1
+ import { readdir, readFile as fsReadFile, stat, writeFile as fsWriteFile } from "node:fs/promises";
2
2
  import { join, resolve, relative } from "node:path";
3
3
  var MAX_FILE_SIZE = 512 * 1024;
4
4
  export function validatePath(projectPath, relativePath) {
@@ -9,7 +9,7 @@ export function validatePath(projectPath, relativePath) {
9
9
  }
10
10
  return resolved;
11
11
  }
12
- export function listDirectory(projectPath, relativePath) {
12
+ export async function listDirectory(projectPath, relativePath) {
13
13
  var fullPath = validatePath(projectPath, relativePath);
14
14
  if (!fullPath) {
15
15
  return [];
@@ -17,7 +17,7 @@ export function listDirectory(projectPath, relativePath) {
17
17
  var entries = [];
18
18
  var names;
19
19
  try {
20
- names = readdirSync(fullPath);
20
+ names = await readdir(fullPath);
21
21
  }
22
22
  catch {
23
23
  return [];
@@ -29,14 +29,14 @@ export function listDirectory(projectPath, relativePath) {
29
29
  }
30
30
  var entryFull = join(fullPath, name);
31
31
  try {
32
- var stat = statSync(entryFull);
32
+ var entryStat = await stat(entryFull);
33
33
  var entryRelative = relative(projectPath, entryFull);
34
34
  entries.push({
35
35
  name: name,
36
36
  path: entryRelative,
37
- isDirectory: stat.isDirectory(),
38
- size: stat.isDirectory() ? 0 : stat.size,
39
- modifiedAt: stat.mtimeMs,
37
+ isDirectory: entryStat.isDirectory(),
38
+ size: entryStat.isDirectory() ? 0 : entryStat.size,
39
+ modifiedAt: entryStat.mtimeMs,
40
40
  });
41
41
  }
42
42
  catch {
@@ -52,24 +52,24 @@ export function listDirectory(projectPath, relativePath) {
52
52
  });
53
53
  return entries;
54
54
  }
55
- export function readFile(projectPath, relativePath) {
55
+ export async function readFile(projectPath, relativePath) {
56
56
  var fullPath = validatePath(projectPath, relativePath);
57
57
  if (!fullPath) {
58
58
  return null;
59
59
  }
60
- var stat;
60
+ var fileStat;
61
61
  try {
62
- stat = statSync(fullPath);
62
+ fileStat = await stat(fullPath);
63
63
  }
64
64
  catch {
65
65
  return null;
66
66
  }
67
- if (stat.size > MAX_FILE_SIZE) {
67
+ if (fileStat.size > MAX_FILE_SIZE) {
68
68
  return null;
69
69
  }
70
70
  var buf;
71
71
  try {
72
- buf = readFileSync(fullPath);
72
+ buf = await fsReadFile(fullPath);
73
73
  }
74
74
  catch {
75
75
  return null;
@@ -82,13 +82,13 @@ export function readFile(projectPath, relativePath) {
82
82
  }
83
83
  return buf.toString("utf-8");
84
84
  }
85
- export function writeFile(projectPath, relativePath, content) {
85
+ export async function writeFile(projectPath, relativePath, content) {
86
86
  var fullPath = validatePath(projectPath, relativePath);
87
87
  if (!fullPath) {
88
88
  return false;
89
89
  }
90
90
  try {
91
- writeFileSync(fullPath, content, "utf-8");
91
+ await fsWriteFile(fullPath, content, "utf-8");
92
92
  return true;
93
93
  }
94
94
  catch {
@@ -1,5 +1,5 @@
1
1
  import { listSessions as sdkListSessions, getSessionInfo, getSessionMessages, renameSession as sdkRenameSession, } from "@anthropic-ai/claude-agent-sdk";
2
- import { existsSync, unlinkSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import { existsSync, unlinkSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import * as fsPromises from "node:fs/promises";
4
4
  import { join } from "node:path";
5
5
  import { randomUUID } from "node:crypto";
@@ -55,7 +55,9 @@ export function loadPricing() {
55
55
  };
56
56
  pricingCache[key] = pricingCache[modelId];
57
57
  }
58
- }).catch(function () { });
58
+ }).catch(function (err) {
59
+ log.session("Failed to load pricing data: %O", err);
60
+ });
59
61
  }
60
62
  loadPricing();
61
63
  export function getPricing(model) {
@@ -595,12 +597,13 @@ function getSessionFilePath(projectSlug, sessionId) {
595
597
  var filePath = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
596
598
  return existsSync(filePath) ? filePath : null;
597
599
  }
598
- export function getSessionFileSizeBytes(projectSlug, sessionId) {
600
+ export async function getSessionFileSizeBytes(projectSlug, sessionId) {
599
601
  var filePath = getSessionFilePath(projectSlug, sessionId);
600
602
  if (!filePath)
601
603
  return null;
602
604
  try {
603
- return statSync(filePath).size;
605
+ var fileStat = await fsPromises.stat(filePath);
606
+ return fileStat.size;
604
607
  }
605
608
  catch {
606
609
  return null;
@@ -1,5 +1,7 @@
1
1
  import { log } from "../logger.js";
2
2
  var clients = new Map();
3
+ var clientAlive = new Map();
4
+ var clientProjects = new Map();
3
5
  var virtualSendHandlers = new Map();
4
6
  export function registerVirtualClient(id, handler) {
5
7
  virtualSendHandlers.set(id, handler);
@@ -9,9 +11,34 @@ export function removeVirtualClient(id) {
9
11
  }
10
12
  export function addClient(id, ws) {
11
13
  clients.set(id, ws);
14
+ clientAlive.set(id, true);
12
15
  }
13
16
  export function removeClient(id) {
14
17
  clients.delete(id);
18
+ clientAlive.delete(id);
19
+ clientProjects.delete(id);
20
+ }
21
+ export function markClientAlive(id) {
22
+ clientAlive.set(id, true);
23
+ }
24
+ export function subscribeClientToProject(clientId, projectSlug) {
25
+ var projects = clientProjects.get(clientId);
26
+ if (!projects) {
27
+ projects = new Set();
28
+ clientProjects.set(clientId, projects);
29
+ }
30
+ projects.add(projectSlug);
31
+ }
32
+ export function broadcastToProject(projectSlug, message, excludeId) {
33
+ var text = JSON.stringify(message);
34
+ for (var [id, ws] of clients) {
35
+ if (id !== excludeId && ws.readyState === ws.OPEN) {
36
+ var projects = clientProjects.get(id);
37
+ if (projects && projects.has(projectSlug)) {
38
+ ws.send(text);
39
+ }
40
+ }
41
+ }
15
42
  }
16
43
  export function broadcast(message, excludeId) {
17
44
  var text = JSON.stringify(message);
@@ -47,4 +74,37 @@ export function closeAllClients() {
47
74
  ws.close();
48
75
  }
49
76
  clients.clear();
77
+ clientAlive.clear();
78
+ clientProjects.clear();
79
+ }
80
+ var HEARTBEAT_INTERVAL_MS = 30000;
81
+ var heartbeatTimer = null;
82
+ export function startHeartbeat(onDead) {
83
+ if (heartbeatTimer)
84
+ return;
85
+ heartbeatTimer = setInterval(function () {
86
+ var dead = [];
87
+ for (var [id, ws] of clients) {
88
+ if (!clientAlive.get(id)) {
89
+ dead.push([id, ws]);
90
+ continue;
91
+ }
92
+ clientAlive.set(id, false);
93
+ if (ws.readyState === ws.OPEN) {
94
+ ws.ping();
95
+ }
96
+ }
97
+ for (var i = 0; i < dead.length; i++) {
98
+ log.ws("Heartbeat: client %s unresponsive, terminating", dead[i][0].slice(0, 8));
99
+ onDead(dead[i][0], dead[i][1]);
100
+ dead[i][1].terminate();
101
+ }
102
+ }, HEARTBEAT_INTERVAL_MS);
103
+ heartbeatTimer.unref();
104
+ }
105
+ export function stopHeartbeat() {
106
+ if (heartbeatTimer) {
107
+ clearInterval(heartbeatTimer);
108
+ heartbeatTimer = null;
109
+ }
50
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
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>",