@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.
- package/dist/client/assets/{angular-html-Bmjy4gNc.js → angular-html-B_LrO4d_.js} +1 -1
- package/dist/client/assets/{angular-ts-10l13C4C.js → angular-ts-DG2-AEn3.js} +1 -1
- package/dist/client/assets/{apl--Bn7yH0T.js → apl-D9AV-2KJ.js} +1 -1
- package/dist/client/assets/{astro-C8P9TQkP.js → astro-D55z-bYO.js} +1 -1
- package/dist/client/assets/{blade-DQiHYnIS.js → blade-Bs-tcMis.js} +1 -1
- package/dist/client/assets/{c-BFny-WnE.js → c-CAs-oCwZ.js} +1 -1
- package/dist/client/assets/{cobol-hGEexMYH.js → cobol-0B2WKsi0.js} +1 -1
- package/dist/client/assets/{coffee-j3W4ZALV.js → coffee-BLi0y75x.js} +1 -1
- package/dist/client/assets/{cpp-DquWJNps.js → cpp-CTXpwK-o.js} +1 -1
- package/dist/client/assets/{crystal-CWL31dIV.js → crystal-B_OPrNIv.js} +1 -1
- package/dist/client/assets/{css-Di4GSq8V.js → css-C9nlz4Z2.js} +1 -1
- package/dist/client/assets/{dist-CaJdL3Va.js → dist-CzUlrFrL.js} +2 -2
- package/dist/client/assets/{edge-rK4d050l.js → edge-DE3SfmPJ.js} +1 -1
- package/dist/client/assets/{elixir-BMFKKu5z.js → elixir-BS_1fIPf.js} +1 -1
- package/dist/client/assets/{elm-B0QVlW3-.js → elm-CrfkxZpb.js} +1 -1
- package/dist/client/assets/{erb-C9w9QbhL.js → erb-B6Ab2Y07.js} +1 -1
- package/dist/client/assets/{git-rebase-FaylS7Vl.js → git-rebase-RkfMNUtm.js} +1 -1
- package/dist/client/assets/{glimmer-js-Bi2XnbHi.js → glimmer-js-CBA96B3V.js} +1 -1
- package/dist/client/assets/{glimmer-ts-DOdxK6E6.js → glimmer-ts-WPYNrOak.js} +1 -1
- package/dist/client/assets/{glsl-BiGDIGbZ.js → glsl-BVPbkPwC.js} +1 -1
- package/dist/client/assets/{graphql-CZJB8KIP.js → graphql-CplPyf-J.js} +1 -1
- package/dist/client/assets/{hack-CNGYBFqH.js → hack-Bqnr1D44.js} +1 -1
- package/dist/client/assets/{haml-pf3Zh5MH.js → haml-Bk9H5oqC.js} +1 -1
- package/dist/client/assets/{handlebars-BsQwsvPC.js → handlebars-Dho-7JSJ.js} +1 -1
- package/dist/client/assets/{html-Dj4Hvv69.js → html-Dx1dAJ5K.js} +1 -1
- package/dist/client/assets/{html-derivative-CE7dQ_mI.js → html-derivative-DMEkbacz.js} +1 -1
- package/dist/client/assets/{http-DjOuMI1u.js → http-yzZRblhh.js} +1 -1
- package/dist/client/assets/{hurl-BH_MQrQo.js → hurl-DwA-puXE.js} +1 -1
- package/dist/client/assets/{index-DA8ZBSYW.js → index-BV9isded.js} +2 -2
- package/dist/client/assets/{java-CnpykAva.js → java-da4cNL_S.js} +1 -1
- package/dist/client/assets/{javascript-BbqQ1sQM.js → javascript-BRBtsTGZ.js} +1 -1
- package/dist/client/assets/{jinja-DddpvWwk.js → jinja-KYkTqvLF.js} +1 -1
- package/dist/client/assets/{jison-Bd3G2n4x.js → jison-CpJZ2qu3.js} +1 -1
- package/dist/client/assets/{json-F0eTFQKP.js → json-1ZKXsOcj.js} +1 -1
- package/dist/client/assets/{jsx-B-wB5nUs.js → jsx-Ci3IutCK.js} +1 -1
- package/dist/client/assets/{julia-9Lfz4b5s.js → julia-8BIB33ff.js} +1 -1
- package/dist/client/assets/{just-BVE1sBJx.js → just-BlxIBqAl.js} +1 -1
- package/dist/client/assets/{latex-SF3xMSWG.js → latex-Dt6aIDIN.js} +1 -1
- package/dist/client/assets/{liquid-CLkbZ0ZV.js → liquid-rNU0f2fO.js} +1 -1
- package/dist/client/assets/{lua-DNph6TL8.js → lua-9O59duWh.js} +1 -1
- package/dist/client/assets/{marko-CGqdEtbF.js → marko-cSK6Q4M9.js} +1 -1
- package/dist/client/assets/{mdc-BLpzcq82.js → mdc-DD0DONjK.js} +1 -1
- package/dist/client/assets/{nginx-s7ciyZD1.js → nginx-Bfzt7qPl.js} +1 -1
- package/dist/client/assets/{nim-C4FeZI3R.js → nim-DOD72OhS.js} +1 -1
- package/dist/client/assets/{perl-GDfD5AIi.js → perl-1cg42R6O.js} +1 -1
- package/dist/client/assets/{php-CusAuy6y.js → php-DfG0eJE0.js} +1 -1
- package/dist/client/assets/{pug-DCKuXO7x.js → pug-BJ4ktrjh.js} +1 -1
- package/dist/client/assets/{qml-rp1ZHoeo.js → qml-BwGhenQ4.js} +1 -1
- package/dist/client/assets/{r-CwNbYaVb.js → r-DSKy_gZ9.js} +1 -1
- package/dist/client/assets/{razor-CinY5yf4.js → razor-morGoSMw.js} +1 -1
- package/dist/client/assets/{regexp-CxVI4rKV.js → regexp-DSQzd7Gw.js} +1 -1
- package/dist/client/assets/{rst-BrBmBgUr.js → rst-CQ4DB61w.js} +1 -1
- package/dist/client/assets/{ruby-BzNCpRio.js → ruby-dcUoNsdX.js} +1 -1
- package/dist/client/assets/{sas-B4IAap7m.js → sas-BckOIwmR.js} +1 -1
- package/dist/client/assets/{scss-eNAsUEA1.js → scss-CIDQomQQ.js} +1 -1
- package/dist/client/assets/{shellscript-BwH0aChW.js → shellscript-DscXCB_p.js} +1 -1
- package/dist/client/assets/{shellsession-B5ibJBbh.js → shellsession-C9EjZURj.js} +1 -1
- package/dist/client/assets/{soy-BrqReQP3.js → soy-CVzyKHIE.js} +1 -1
- package/dist/client/assets/{sql-KIDgTieS.js → sql-Ddk-vRG3.js} +1 -1
- package/dist/client/assets/{stata-D6yim7Rr.js → stata-BGl5cxb8.js} +1 -1
- package/dist/client/assets/{surrealql-C51iMPEA.js → surrealql-3IZgL_Ze.js} +1 -1
- package/dist/client/assets/{svelte-DYs5QSVt.js → svelte-BRM0niF5.js} +1 -1
- package/dist/client/assets/{templ-CLDdaEXl.js → templ-qbgb30u-.js} +1 -1
- package/dist/client/assets/{tex-D16HBCoj.js → tex-Ct_-hpJo.js} +1 -1
- package/dist/client/assets/{ts-tags-DTcB2bBd.js → ts-tags-C85B12Rc.js} +1 -1
- package/dist/client/assets/{tsx-BxGfVt8a.js → tsx-d401eVG9.js} +1 -1
- package/dist/client/assets/{twig-BUC08jwe.js → twig-CEvQ_MDP.js} +1 -1
- package/dist/client/assets/{typescript-CuPGT2MR.js → typescript-y2NNcyKH.js} +1 -1
- package/dist/client/assets/{vue-_ip8-SIk.js → vue-CiTZbygT.js} +1 -1
- package/dist/client/assets/{vue-html-D-I9-U-x.js → vue-html-B4nYUWKJ.js} +1 -1
- package/dist/client/assets/{vue-vine-7AONB1tt.js → vue-vine-DdfbYtGV.js} +1 -1
- package/dist/client/assets/{xml-D2ZUDsPU.js → xml-1B2HtnyO.js} +1 -1
- package/dist/client/assets/{xsl-GlFKKlLy.js → xsl-DoRYSDxo.js} +1 -1
- package/dist/client/assets/{yaml-BnuTxWck.js → yaml-DpbRFfH9.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/client/sw.js +1 -1
- package/dist/server/daemon.js +60 -27
- package/dist/server/handlers/attachment.js +66 -29
- package/dist/server/handlers/fs.js +58 -72
- package/dist/server/handlers/memory.js +37 -52
- package/dist/server/handlers/mesh.js +4 -1
- package/dist/server/handlers/session.js +38 -57
- package/dist/server/mesh/connector.js +85 -1
- package/dist/server/project/file-browser.js +14 -14
- package/dist/server/project/session.js +7 -4
- package/dist/server/ws/broadcast.js +60 -0
- 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 {
|
|
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 =
|
|
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
|
|
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:
|
|
38
|
-
size:
|
|
39
|
-
modifiedAt:
|
|
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
|
|
60
|
+
var fileStat;
|
|
61
61
|
try {
|
|
62
|
-
|
|
62
|
+
fileStat = await stat(fullPath);
|
|
63
63
|
}
|
|
64
64
|
catch {
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
|
-
if (
|
|
67
|
+
if (fileStat.size > MAX_FILE_SIZE) {
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
70
|
var buf;
|
|
71
71
|
try {
|
|
72
|
-
buf =
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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>",
|