@cryptiklemur/lattice 5.10.0 → 5.11.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-BDIcxkJq.js → angular-html-BoFzmWT8.js} +1 -1
- package/dist/client/assets/{angular-ts-Bt22ouNH.js → angular-ts-DZnI8rKE.js} +1 -1
- package/dist/client/assets/{apl-p8qkxzEK.js → apl-DstVmncE.js} +1 -1
- package/dist/client/assets/{astro-CIaMc49M.js → astro-DTPCjzEx.js} +1 -1
- package/dist/client/assets/{blade-BR56EAMD.js → blade-6q42Ss3F.js} +1 -1
- package/dist/client/assets/{c-Dli0HzAh.js → c-BQDGJ-nQ.js} +1 -1
- package/dist/client/assets/{cobol-Cad15ECy.js → cobol-Dlh0WvsZ.js} +1 -1
- package/dist/client/assets/{coffee-DpyATEbF.js → coffee-DdQv129j.js} +1 -1
- package/dist/client/assets/{cpp-KN8_NFsf.js → cpp-DhbQJIv4.js} +1 -1
- package/dist/client/assets/{crystal-CuyGv0kh.js → crystal-C22kERUB.js} +1 -1
- package/dist/client/assets/{css-Cm3q4bxn.js → css-n31O5kHj.js} +1 -1
- package/dist/client/assets/{dist-BjxsMc4u.js → dist-D8okl7lw.js} +2 -2
- package/dist/client/assets/{edge-B6S7CSbx.js → edge-Cgwx-o_7.js} +1 -1
- package/dist/client/assets/{elixir-CNUy9H8T.js → elixir-DAGM2WKD.js} +1 -1
- package/dist/client/assets/{elm-CNfcWmb9.js → elm-BLw_7oO9.js} +1 -1
- package/dist/client/assets/{erb-DWebzDaI.js → erb-DCaNhYa7.js} +1 -1
- package/dist/client/assets/{git-rebase-B_Pt2ZBK.js → git-rebase-CNNhb8-g.js} +1 -1
- package/dist/client/assets/{glimmer-js-CVwoOd72.js → glimmer-js-BnZd88Wi.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CjtFSxjz.js → glimmer-ts-DvFNbZu-.js} +1 -1
- package/dist/client/assets/{glsl-CP4rggAA.js → glsl-Dnrk_Jnx.js} +1 -1
- package/dist/client/assets/{graphql-Dbm6sAtp.js → graphql-DlWTPvCG.js} +1 -1
- package/dist/client/assets/{hack-Bj9y3SGf.js → hack-DQg1Ek33.js} +1 -1
- package/dist/client/assets/{haml-DRGrdf3f.js → haml-DSk45qIE.js} +1 -1
- package/dist/client/assets/{handlebars-CFKjcBMg.js → handlebars-DuLvATB2.js} +1 -1
- package/dist/client/assets/{html-Vcd4eHHg.js → html-D4DiUnLg.js} +1 -1
- package/dist/client/assets/{html-derivative-BF0YbD4L.js → html-derivative-CS5MZ6d9.js} +1 -1
- package/dist/client/assets/{http-CGVTa2NT.js → http-CkDncfer.js} +1 -1
- package/dist/client/assets/{hurl-B0GrsGqd.js → hurl-DU39oO3U.js} +1 -1
- package/dist/client/assets/{index-CX1tudsF.js → index-CHPfE1Zl.js} +129 -129
- package/dist/client/assets/index-DHUKmLLC.css +2 -0
- package/dist/client/assets/{java-BJHQqHsm.js → java-lntACKEu.js} +1 -1
- package/dist/client/assets/{javascript-CmuMsKrc.js → javascript-CxkFc6nV.js} +1 -1
- package/dist/client/assets/{jinja-JxCLeq1j.js → jinja-DolO2zO7.js} +1 -1
- package/dist/client/assets/{jison-BdgAUhei.js → jison-Cok5FPev.js} +1 -1
- package/dist/client/assets/{json-DtPissHL.js → json-BebuQPrq.js} +1 -1
- package/dist/client/assets/{jsx-DUAxxDkP.js → jsx-iLBaUyXr.js} +1 -1
- package/dist/client/assets/{julia-DxDlbL6e.js → julia-C5Dsc7cH.js} +1 -1
- package/dist/client/assets/{just-CVmAAx2R.js → just-DJYqq_9R.js} +1 -1
- package/dist/client/assets/{latex-uwxggTWA.js → latex-BTTYiKj1.js} +1 -1
- package/dist/client/assets/{liquid-xsETAJJy.js → liquid-DpAKCrOB.js} +1 -1
- package/dist/client/assets/{lua-B2Hh8PgD.js → lua-BZ6b1hko.js} +1 -1
- package/dist/client/assets/{marko-yDeGxD87.js → marko-D8VK6iGt.js} +1 -1
- package/dist/client/assets/{mdc-QMp4ieYR.js → mdc-Paa3XzwY.js} +1 -1
- package/dist/client/assets/{nginx-7gmRmcqz.js → nginx-C5k9mWtJ.js} +1 -1
- package/dist/client/assets/{nim-CA8SNY_7.js → nim-Dst6YSnE.js} +1 -1
- package/dist/client/assets/{perl-lx5nW4VC.js → perl-XhiCjgBp.js} +1 -1
- package/dist/client/assets/{php-DgHiW953.js → php-BcsPLnLU.js} +1 -1
- package/dist/client/assets/{pug-CbbB1vwb.js → pug-GLH9-eAJ.js} +1 -1
- package/dist/client/assets/{qml-COrzwCIh.js → qml-Cj_lJioE.js} +1 -1
- package/dist/client/assets/{r-Dv7pZJDH.js → r-B70aGYK5.js} +1 -1
- package/dist/client/assets/{razor-D2m8EDP5.js → razor-R3gub_zy.js} +1 -1
- package/dist/client/assets/{regexp-BXLT-jPc.js → regexp-itC0dIUJ.js} +1 -1
- package/dist/client/assets/{rst-_S6rrUYh.js → rst-DdyoV8E2.js} +1 -1
- package/dist/client/assets/{ruby-C3XO7tYY.js → ruby-BYBZsv66.js} +1 -1
- package/dist/client/assets/{sas-DP2k4iuN.js → sas-fqfqXqj1.js} +1 -1
- package/dist/client/assets/{scss-lhLFMXGn.js → scss-B-ELv6mu.js} +1 -1
- package/dist/client/assets/{shellscript-BYlBPHen.js → shellscript-BgB8TNw6.js} +1 -1
- package/dist/client/assets/{shellsession-CbVyQKWZ.js → shellsession-BLK2Dgkm.js} +1 -1
- package/dist/client/assets/{soy-Be8a0lHq.js → soy-C7_RmNrp.js} +1 -1
- package/dist/client/assets/{sql-2KxvU9YS.js → sql-AUgbUJq4.js} +1 -1
- package/dist/client/assets/{stata-BxlWftTS.js → stata-CIVqSIOr.js} +1 -1
- package/dist/client/assets/{surrealql-CJ-q86nR.js → surrealql-BzRQzc5S.js} +1 -1
- package/dist/client/assets/{svelte-Q1ml0OiY.js → svelte-BCIwEwtb.js} +1 -1
- package/dist/client/assets/{templ-BbfPZhtu.js → templ-C1hbwe4u.js} +1 -1
- package/dist/client/assets/{tex-Dcth4Gi6.js → tex-CI4tIsaP.js} +1 -1
- package/dist/client/assets/{ts-tags-BKhSOXI3.js → ts-tags-SUeikhEp.js} +1 -1
- package/dist/client/assets/{tsx-CS6iQ0XH.js → tsx-xkp7aIZs.js} +1 -1
- package/dist/client/assets/{twig-BHp31ZxS.js → twig-CGgBSAyc.js} +1 -1
- package/dist/client/assets/{typescript-16YJBTaO.js → typescript-O2YMTl_s.js} +1 -1
- package/dist/client/assets/{vue-CMKwTi4r.js → vue-DsNRxos1.js} +1 -1
- package/dist/client/assets/{vue-html-Dr8VUA2G.js → vue-html-CuY3t7bs.js} +1 -1
- package/dist/client/assets/{vue-vine-DZUqDerl.js → vue-vine-C6kSCKwY.js} +1 -1
- package/dist/client/assets/{xml-CBbBKKDC.js → xml-DafwzOLY.js} +1 -1
- package/dist/client/assets/{xsl-DWEX6PKX.js → xsl-1SGGZibr.js} +1 -1
- package/dist/client/assets/{yaml-DvKvvh3X.js → yaml-DSVhzmhr.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/analytics/engine.js +241 -241
- package/dist/server/assets.js +4 -4
- package/dist/server/auth/passphrase.js +13 -13
- package/dist/server/config.js +7 -7
- package/dist/server/daemon.js +93 -93
- package/dist/server/features/brainstorm.js +42 -42
- package/dist/server/features/ralph-loop.js +33 -33
- package/dist/server/features/scheduler.js +53 -53
- package/dist/server/features/specs.js +54 -54
- package/dist/server/features/sticky-notes.js +17 -17
- package/dist/server/features/superpowers.js +24 -24
- package/dist/server/handlers/analytics.js +1 -1
- package/dist/server/handlers/attachment.js +32 -32
- package/dist/server/handlers/bookmarks.js +4 -4
- package/dist/server/handlers/brainstorm.js +4 -4
- package/dist/server/handlers/chat.js +54 -54
- package/dist/server/handlers/editor.js +13 -13
- package/dist/server/handlers/fs.js +51 -51
- package/dist/server/handlers/hooks.js +20 -20
- package/dist/server/handlers/loop.js +6 -6
- package/dist/server/handlers/memory.js +44 -44
- package/dist/server/handlers/mesh.js +60 -60
- package/dist/server/handlers/notes.js +7 -7
- package/dist/server/handlers/plugins.js +174 -174
- package/dist/server/handlers/project-settings.js +26 -26
- package/dist/server/handlers/scheduler.js +6 -6
- package/dist/server/handlers/session.js +24 -24
- package/dist/server/handlers/settings.js +21 -21
- package/dist/server/handlers/skills.js +91 -91
- package/dist/server/handlers/specs.js +51 -28
- package/dist/server/handlers/terminal.js +13 -13
- package/dist/server/handlers/themes.js +21 -21
- package/dist/server/handlers/update.js +17 -17
- package/dist/server/hooks/event_forward.sh +34 -0
- package/dist/server/hooks/post_tool_use.sh +26 -0
- package/dist/server/hooks/statusline.sh +26 -0
- package/dist/server/identity.js +6 -6
- package/dist/server/index.js +111 -111
- package/dist/server/logger.js +1 -1
- package/dist/server/mesh/connector.js +78 -78
- package/dist/server/mesh/crypto.js +20 -20
- package/dist/server/mesh/discovery.js +14 -14
- package/dist/server/mesh/pairing.js +30 -30
- package/dist/server/mesh/peers.js +10 -10
- package/dist/server/mesh/proxy.js +14 -14
- package/dist/server/mesh/session-sync.js +23 -23
- package/dist/server/project/bookmarks.js +11 -11
- package/dist/server/project/context-breakdown.js +70 -70
- package/dist/server/project/file-browser.js +17 -17
- package/dist/server/project/project-files.js +68 -68
- package/dist/server/project/registry.js +10 -10
- package/dist/server/project/sdk-bridge.js +157 -157
- package/dist/server/project/session.js +201 -199
- package/dist/server/project/terminal.js +15 -15
- package/dist/server/project/warmup.js +37 -37
- package/dist/server/push.js +11 -11
- package/dist/server/runtime.js +1 -1
- package/dist/server/tls.js +15 -15
- package/dist/server/tui.js +15 -15
- package/dist/server/update-checker.js +21 -21
- package/dist/server/ws/broadcast.js +18 -18
- package/dist/server/ws/router.js +17 -17
- package/dist/shared/constants.js +8 -8
- package/package.json +2 -2
- package/dist/client/assets/index-DlfI20Gn.css +0 -2
|
@@ -5,26 +5,26 @@ function getPeersPath() {
|
|
|
5
5
|
return join(getLatticeHome(), "peers.json");
|
|
6
6
|
}
|
|
7
7
|
export function loadPeers() {
|
|
8
|
-
|
|
8
|
+
const path = getPeersPath();
|
|
9
9
|
if (!existsSync(path)) {
|
|
10
10
|
return [];
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const raw = readFileSync(path, "utf-8");
|
|
13
13
|
return JSON.parse(raw);
|
|
14
14
|
}
|
|
15
15
|
export function savePeers(peers) {
|
|
16
|
-
|
|
16
|
+
const path = getPeersPath();
|
|
17
17
|
writeFileSync(path, JSON.stringify(peers, null, 2), "utf-8");
|
|
18
18
|
}
|
|
19
19
|
export function addPeer(peer) {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const peers = loadPeers();
|
|
21
|
+
const idx = peers.findIndex(function (p) { return p.id === peer.id; });
|
|
22
22
|
if (idx >= 0) {
|
|
23
23
|
peers[idx] = peer;
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const addrSet = new Set(peer.addresses);
|
|
27
|
+
const dupeIdx = peers.findIndex(function (p) {
|
|
28
28
|
return p.addresses.some(function (a) { return addrSet.has(a); });
|
|
29
29
|
});
|
|
30
30
|
if (dupeIdx >= 0) {
|
|
@@ -37,8 +37,8 @@ export function addPeer(peer) {
|
|
|
37
37
|
savePeers(peers);
|
|
38
38
|
}
|
|
39
39
|
export function removePeer(nodeId) {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const peers = loadPeers();
|
|
41
|
+
const next = peers.filter(function (p) { return p.id !== nodeId; });
|
|
42
42
|
if (next.length === peers.length) {
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
@@ -46,7 +46,7 @@ export function removePeer(nodeId) {
|
|
|
46
46
|
return true;
|
|
47
47
|
}
|
|
48
48
|
export function getPeer(nodeId) {
|
|
49
|
-
|
|
49
|
+
const peers = loadPeers();
|
|
50
50
|
return peers.find(function (p) { return p.id === nodeId; });
|
|
51
51
|
}
|
|
52
52
|
export function isPaired(nodeId) {
|
|
@@ -3,19 +3,19 @@ import { getPeerConnection } from "./connector.js";
|
|
|
3
3
|
import { sendTo, registerVirtualClient, removeVirtualClient } from "../ws/broadcast.js";
|
|
4
4
|
import { routeMessage } from "../ws/router.js";
|
|
5
5
|
import { log } from "../logger.js";
|
|
6
|
-
|
|
6
|
+
const pendingRequests = new Map();
|
|
7
7
|
export function proxyToRemoteNode(nodeId, projectSlug, clientId, message) {
|
|
8
8
|
log.meshProxy("→ proxy %s to node %s for project %s", message.type, nodeId.slice(0, 8), projectSlug);
|
|
9
|
-
|
|
9
|
+
const ws = getPeerConnection(nodeId);
|
|
10
10
|
if (!ws) {
|
|
11
11
|
log.meshProxy(" ✗ no connection to node %s", nodeId.slice(0, 8));
|
|
12
12
|
sendTo(clientId, { type: "chat:error", message: "Remote node is not connected" });
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
const requestId = randomUUID();
|
|
16
16
|
pendingRequests.set(requestId, clientId);
|
|
17
17
|
log.meshProxy(" envelope requestId=%s", requestId.slice(0, 8));
|
|
18
|
-
|
|
18
|
+
const envelope = {
|
|
19
19
|
type: "mesh:proxy_request",
|
|
20
20
|
projectSlug: projectSlug,
|
|
21
21
|
requestId: requestId,
|
|
@@ -24,17 +24,17 @@ export function proxyToRemoteNode(nodeId, projectSlug, clientId, message) {
|
|
|
24
24
|
ws.send(JSON.stringify(envelope));
|
|
25
25
|
}
|
|
26
26
|
export function handleProxyRequest(sourceNodeId, msg) {
|
|
27
|
-
|
|
27
|
+
const proxyClientId = "mesh-proxy:" + sourceNodeId + ":" + msg.requestId;
|
|
28
28
|
log.meshProxy("← proxy_request from %s: %s for %s (reqId=%s)", sourceNodeId.slice(0, 8), msg.payload.type, msg.projectSlug, msg.requestId.slice(0, 8));
|
|
29
29
|
registerVirtualClient(proxyClientId, function (response) {
|
|
30
30
|
log.meshProxy(" → proxy_response %s back to %s", response.type, sourceNodeId.slice(0, 8));
|
|
31
|
-
|
|
31
|
+
const ws = getPeerConnection(sourceNodeId);
|
|
32
32
|
if (!ws) {
|
|
33
33
|
console.warn("[mesh/proxy] Cannot send response, no connection to: " + sourceNodeId);
|
|
34
34
|
removeVirtualClient(proxyClientId);
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
const envelope = {
|
|
38
38
|
type: "mesh:proxy_response",
|
|
39
39
|
projectSlug: msg.projectSlug,
|
|
40
40
|
requestId: msg.requestId,
|
|
@@ -47,7 +47,7 @@ export function handleProxyRequest(sourceNodeId, msg) {
|
|
|
47
47
|
}
|
|
48
48
|
export function handleProxyResponse(msg) {
|
|
49
49
|
log.meshProxy("← proxy_response %s (reqId=%s)", msg.payload.type, msg.requestId.slice(0, 8));
|
|
50
|
-
|
|
50
|
+
const clientId = pendingRequests.get(msg.requestId);
|
|
51
51
|
if (!clientId) {
|
|
52
52
|
log.meshProxy(" ✗ no pending request for %s", msg.requestId.slice(0, 8));
|
|
53
53
|
return;
|
|
@@ -55,13 +55,13 @@ export function handleProxyResponse(msg) {
|
|
|
55
55
|
pendingRequests.delete(msg.requestId);
|
|
56
56
|
sendTo(clientId, msg.payload);
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
const proxyHandlers = new Map();
|
|
59
59
|
export function registerProxyAwareHandler(prefix, handler) {
|
|
60
60
|
proxyHandlers.set(prefix, handler);
|
|
61
61
|
}
|
|
62
62
|
function proxyRouteMessage(clientId, message, sendToFn) {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const prefix = message.type.split(":")[0];
|
|
64
|
+
const proxyHandler = proxyHandlers.get(prefix);
|
|
65
65
|
if (proxyHandler) {
|
|
66
66
|
proxyHandler(clientId, message, sendToFn);
|
|
67
67
|
return;
|
|
@@ -69,15 +69,15 @@ function proxyRouteMessage(clientId, message, sendToFn) {
|
|
|
69
69
|
routeMessage(clientId, message);
|
|
70
70
|
}
|
|
71
71
|
export function getProxySendTo(requestId, sourceNodeId) {
|
|
72
|
-
|
|
72
|
+
const clientId = pendingRequests.get(requestId);
|
|
73
73
|
if (clientId !== undefined) {
|
|
74
|
-
|
|
74
|
+
const resolvedClientId = clientId;
|
|
75
75
|
return function (msg) {
|
|
76
76
|
sendTo(resolvedClientId, msg);
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
return function (msg) {
|
|
80
|
-
|
|
80
|
+
const ws = getPeerConnection(sourceNodeId);
|
|
81
81
|
if (!ws) {
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
@@ -3,45 +3,45 @@ import { join } from "node:path";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { getConnectedPeerIds, getPeerConnection } from "./connector.js";
|
|
5
5
|
import { getLatticeHome } from "../config.js";
|
|
6
|
-
|
|
6
|
+
const syncOffsets = new Map();
|
|
7
7
|
function getSyncKey(nodeId, sessionId) {
|
|
8
8
|
return nodeId + ":" + sessionId;
|
|
9
9
|
}
|
|
10
10
|
function getRemoteSessionsDir(nodeId, projectSlug) {
|
|
11
|
-
|
|
11
|
+
const dir = join(getLatticeHome(), "remote-sessions", nodeId, projectSlug);
|
|
12
12
|
if (!existsSync(dir)) {
|
|
13
13
|
mkdirSync(dir, { recursive: true });
|
|
14
14
|
}
|
|
15
15
|
return dir;
|
|
16
16
|
}
|
|
17
17
|
function getSessionFilePath(projectPath, sessionId) {
|
|
18
|
-
|
|
18
|
+
const hash = projectPath.replace(/\//g, "-");
|
|
19
19
|
return join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
|
|
20
20
|
}
|
|
21
21
|
export function syncSessionToPeers(projectPath, projectSlug, sessionId) {
|
|
22
|
-
|
|
22
|
+
const filePath = getSessionFilePath(projectPath, sessionId);
|
|
23
23
|
if (!existsSync(filePath)) {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
const content = readFileSync(filePath, "utf-8");
|
|
27
|
+
const peerIds = getConnectedPeerIds();
|
|
28
|
+
for (let i = 0; i < peerIds.length; i++) {
|
|
29
|
+
const nodeId = peerIds[i];
|
|
30
|
+
const key = getSyncKey(nodeId, sessionId);
|
|
31
|
+
const lastOffset = syncOffsets.get(key) || 0;
|
|
32
32
|
if (content.length <= lastOffset) {
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
const newContent = content.slice(lastOffset);
|
|
36
|
+
const newLines = newContent.split("\n").filter(function (l) { return l.trim().length > 0; });
|
|
37
37
|
if (newLines.length === 0) {
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
const ws = getPeerConnection(nodeId);
|
|
41
41
|
if (!ws) {
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
const msg = {
|
|
45
45
|
type: "mesh:session_sync",
|
|
46
46
|
projectSlug,
|
|
47
47
|
sessionId,
|
|
@@ -53,28 +53,28 @@ export function syncSessionToPeers(projectPath, projectSlug, sessionId) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
export function handleSessionSync(nodeId, msg) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
for (
|
|
56
|
+
const dir = getRemoteSessionsDir(nodeId, msg.projectSlug);
|
|
57
|
+
const filePath = join(dir, msg.sessionId + ".jsonl");
|
|
58
|
+
for (let i = 0; i < msg.lines.length; i++) {
|
|
59
59
|
appendFileSync(filePath, msg.lines[i] + "\n", "utf-8");
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
export function handleSessionRequest(nodeId, msg, projectPath) {
|
|
63
|
-
|
|
63
|
+
const filePath = getSessionFilePath(projectPath, msg.sessionId);
|
|
64
64
|
if (!existsSync(filePath)) {
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const content = readFileSync(filePath, "utf-8");
|
|
68
|
+
const newContent = content.slice(msg.fromOffset);
|
|
69
|
+
const lines = newContent.split("\n").filter(function (l) { return l.trim().length > 0; });
|
|
70
70
|
if (lines.length === 0) {
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
const ws = getPeerConnection(nodeId);
|
|
74
74
|
if (!ws) {
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
const syncMsg = {
|
|
78
78
|
type: "mesh:session_sync",
|
|
79
79
|
projectSlug: msg.projectSlug,
|
|
80
80
|
sessionId: msg.sessionId,
|
|
@@ -2,8 +2,8 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { getLatticeHome } from "../config.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
let bookmarksFile = "";
|
|
6
|
+
let bookmarks = [];
|
|
7
7
|
function getBookmarksPath() {
|
|
8
8
|
if (!bookmarksFile) {
|
|
9
9
|
bookmarksFile = join(getLatticeHome(), "bookmarks.json");
|
|
@@ -11,13 +11,13 @@ function getBookmarksPath() {
|
|
|
11
11
|
return bookmarksFile;
|
|
12
12
|
}
|
|
13
13
|
export function loadBookmarks() {
|
|
14
|
-
|
|
14
|
+
const path = getBookmarksPath();
|
|
15
15
|
if (!existsSync(path)) {
|
|
16
16
|
bookmarks = [];
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
|
-
|
|
20
|
+
const raw = readFileSync(path, "utf-8");
|
|
21
21
|
bookmarks = JSON.parse(raw);
|
|
22
22
|
}
|
|
23
23
|
catch (err) {
|
|
@@ -26,12 +26,12 @@ export function loadBookmarks() {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
function saveBookmarks() {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const path = getBookmarksPath();
|
|
30
|
+
const dir = join(path, "..");
|
|
31
31
|
if (!existsSync(dir)) {
|
|
32
32
|
mkdirSync(dir, { recursive: true });
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
const tmp = path + ".tmp";
|
|
35
35
|
try {
|
|
36
36
|
writeFileSync(tmp, JSON.stringify(bookmarks, null, 2));
|
|
37
37
|
renameSync(tmp, path);
|
|
@@ -41,7 +41,7 @@ function saveBookmarks() {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
export function listBookmarks(projectSlug, sessionId) {
|
|
44
|
-
|
|
44
|
+
let result = bookmarks;
|
|
45
45
|
if (projectSlug) {
|
|
46
46
|
result = result.filter(function (b) { return b.projectSlug === projectSlug; });
|
|
47
47
|
}
|
|
@@ -51,8 +51,8 @@ export function listBookmarks(projectSlug, sessionId) {
|
|
|
51
51
|
return result.slice();
|
|
52
52
|
}
|
|
53
53
|
export function addBookmark(bookmark) {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const entry = {
|
|
56
56
|
id: "bm_" + now + "_" + randomBytes(3).toString("hex"),
|
|
57
57
|
sessionId: bookmark.sessionId,
|
|
58
58
|
projectSlug: bookmark.projectSlug,
|
|
@@ -66,7 +66,7 @@ export function addBookmark(bookmark) {
|
|
|
66
66
|
return entry;
|
|
67
67
|
}
|
|
68
68
|
export function removeBookmark(id) {
|
|
69
|
-
for (
|
|
69
|
+
for (let i = 0; i < bookmarks.length; i++) {
|
|
70
70
|
if (bookmarks[i].id === id) {
|
|
71
71
|
bookmarks.splice(i, 1);
|
|
72
72
|
saveBookmarks();
|
|
@@ -5,7 +5,7 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { guessContextWindow } from "./session.js";
|
|
6
6
|
import { loadConfig } from "../config.js";
|
|
7
7
|
import { getInstalledPluginCount, getPluginSkillRuleTokenEstimate } from "../handlers/plugins.js";
|
|
8
|
-
|
|
8
|
+
const encoder = encodingForModel("gpt-4o");
|
|
9
9
|
function countTokens(text) {
|
|
10
10
|
if (!text)
|
|
11
11
|
return 0;
|
|
@@ -23,9 +23,9 @@ function readDirFiles(dirPath) {
|
|
|
23
23
|
try {
|
|
24
24
|
if (!existsSync(dirPath))
|
|
25
25
|
return "";
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for (
|
|
26
|
+
const files = readdirSync(dirPath, { withFileTypes: true });
|
|
27
|
+
let content = "";
|
|
28
|
+
for (let i = 0; i < files.length; i++) {
|
|
29
29
|
if (files[i].isFile()) {
|
|
30
30
|
content += readFileSafe(join(dirPath, files[i].name)) + "\n";
|
|
31
31
|
}
|
|
@@ -39,13 +39,13 @@ function projectPathToHash(projectPath) {
|
|
|
39
39
|
return projectPath.replace(/\//g, "-");
|
|
40
40
|
}
|
|
41
41
|
function getProjectPath(projectSlug) {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const config = loadConfig();
|
|
43
|
+
const project = config.projects.find(function (p) { return p.slug === projectSlug; });
|
|
44
44
|
return project ? project.path : null;
|
|
45
45
|
}
|
|
46
46
|
// Known built-in Claude Code tools with approximate per-tool token counts
|
|
47
47
|
// These are the tool definitions sent in every API request
|
|
48
|
-
|
|
48
|
+
const BUILTIN_TOOLS = {
|
|
49
49
|
"Read": 350,
|
|
50
50
|
"Write": 300,
|
|
51
51
|
"Edit": 400,
|
|
@@ -71,20 +71,20 @@ var BUILTIN_TOOLS = {
|
|
|
71
71
|
"ToolSearch": 250,
|
|
72
72
|
};
|
|
73
73
|
// Average tokens per MCP tool definition (name + description + JSON schema)
|
|
74
|
-
|
|
74
|
+
const MCP_TOOL_AVG_TOKENS = 250;
|
|
75
75
|
function extractToolsFromSession(lines) {
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
const builtinSet = new Set();
|
|
77
|
+
const mcpMap = new Map();
|
|
78
78
|
// First: check compact boundary metadata for preCompactDiscoveredTools
|
|
79
|
-
for (
|
|
80
|
-
|
|
79
|
+
for (let i = 0; i < lines.length; i++) {
|
|
80
|
+
const line = lines[i].trim();
|
|
81
81
|
if (!line || !line.includes("compactMetadata"))
|
|
82
82
|
continue;
|
|
83
83
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
const parsed = JSON.parse(line);
|
|
85
|
+
const tools = parsed.compactMetadata?.preCompactDiscoveredTools;
|
|
86
86
|
if (Array.isArray(tools)) {
|
|
87
|
-
for (
|
|
87
|
+
for (let t = 0; t < tools.length; t++) {
|
|
88
88
|
categorizeToolName(tools[t], builtinSet, mcpMap);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -92,15 +92,15 @@ function extractToolsFromSession(lines) {
|
|
|
92
92
|
catch { }
|
|
93
93
|
}
|
|
94
94
|
// Second: scan tool_use blocks from assistant messages for any we missed
|
|
95
|
-
for (
|
|
96
|
-
|
|
95
|
+
for (let j = 0; j < lines.length; j++) {
|
|
96
|
+
const aLine = lines[j].trim();
|
|
97
97
|
if (!aLine || !aLine.includes("tool_use"))
|
|
98
98
|
continue;
|
|
99
99
|
try {
|
|
100
|
-
|
|
100
|
+
const aParsed = JSON.parse(aLine);
|
|
101
101
|
if (aParsed.type === "assistant" && aParsed.message && Array.isArray(aParsed.message.content)) {
|
|
102
|
-
for (
|
|
103
|
-
|
|
102
|
+
for (let k = 0; k < aParsed.message.content.length; k++) {
|
|
103
|
+
const block = aParsed.message.content[k];
|
|
104
104
|
if (block.type === "tool_use" && block.name) {
|
|
105
105
|
categorizeToolName(block.name, builtinSet, mcpMap);
|
|
106
106
|
}
|
|
@@ -125,8 +125,8 @@ function extractToolsFromSession(lines) {
|
|
|
125
125
|
}
|
|
126
126
|
function categorizeToolName(name, builtinSet, mcpMap) {
|
|
127
127
|
if (name.startsWith("mcp__")) {
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
const parts = name.split("__");
|
|
129
|
+
const serverName = parts[1] || "unknown";
|
|
130
130
|
if (!mcpMap.has(serverName)) {
|
|
131
131
|
mcpMap.set(serverName, new Set());
|
|
132
132
|
}
|
|
@@ -137,13 +137,13 @@ function categorizeToolName(name, builtinSet, mcpMap) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
function estimateBuiltinToolTokens(toolNames) {
|
|
140
|
-
|
|
141
|
-
for (
|
|
140
|
+
let total = 0;
|
|
141
|
+
for (let i = 0; i < toolNames.length; i++) {
|
|
142
142
|
total += BUILTIN_TOOLS[toolNames[i]] || 300;
|
|
143
143
|
}
|
|
144
144
|
// Also include tools that are always sent but might not appear in usage
|
|
145
|
-
|
|
146
|
-
for (
|
|
145
|
+
const knownKeys = Object.keys(BUILTIN_TOOLS);
|
|
146
|
+
for (let j = 0; j < knownKeys.length; j++) {
|
|
147
147
|
if (toolNames.indexOf(knownKeys[j]) === -1) {
|
|
148
148
|
total += BUILTIN_TOOLS[knownKeys[j]];
|
|
149
149
|
}
|
|
@@ -151,55 +151,55 @@ function estimateBuiltinToolTokens(toolNames) {
|
|
|
151
151
|
return total;
|
|
152
152
|
}
|
|
153
153
|
export async function getContextBreakdown(projectSlug, sessionId) {
|
|
154
|
-
|
|
154
|
+
const projectPath = getProjectPath(projectSlug);
|
|
155
155
|
if (!projectPath)
|
|
156
156
|
return null;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
const home = homedir();
|
|
158
|
+
const hash = projectPathToHash(projectPath);
|
|
159
|
+
const sessionFile = join(home, ".claude", "projects", hash, sessionId + ".jsonl");
|
|
160
160
|
if (!existsSync(sessionFile))
|
|
161
161
|
return null;
|
|
162
162
|
// Read instruction files
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
163
|
+
const globalClaudeMd = readFileSafe(join(home, ".claude", "CLAUDE.md"));
|
|
164
|
+
const globalRulesContent = readDirFiles(join(home, ".claude", "rules"));
|
|
165
|
+
const projectClaudeMd = readFileSafe(join(projectPath, "CLAUDE.md"));
|
|
166
|
+
const projectLocalClaudeMd = readFileSafe(join(home, ".claude", "projects", hash, "CLAUDE.md"));
|
|
167
|
+
const memoryContent = readDirFiles(join(home, ".claude", "projects", hash, "memory"));
|
|
168
|
+
const memoryIndex = readFileSafe(join(home, ".claude", "projects", hash, "MEMORY.md"));
|
|
169
|
+
const instructionsTokens = countTokens(globalClaudeMd + globalRulesContent + projectClaudeMd + projectLocalClaudeMd);
|
|
170
|
+
const memoryTokens = countTokens(memoryContent + memoryIndex);
|
|
171
171
|
// Parse session — read last 2MB for recent context (avoids reading 35MB+ files)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
const fd = openSync(sessionFile, "r");
|
|
173
|
+
const fileStat = fstatSync(fd);
|
|
174
|
+
const readSize = Math.min(fileStat.size, 2 * 1024 * 1024);
|
|
175
|
+
const readBuf = Buffer.alloc(readSize);
|
|
176
176
|
readSync(fd, readBuf, 0, readSize, fileStat.size - readSize);
|
|
177
177
|
closeSync(fd);
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
const content = readBuf.toString("utf-8");
|
|
179
|
+
const lines = content.split("\n").filter(function (l) { return l.length > 0; });
|
|
180
180
|
// Extract tool info
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
const toolCounts = extractToolsFromSession(lines);
|
|
182
|
+
const builtinToolTokens = estimateBuiltinToolTokens(toolCounts.builtinTools);
|
|
183
183
|
// Parse conversation messages
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
for (
|
|
190
|
-
|
|
184
|
+
let userText = "";
|
|
185
|
+
let assistantText = "";
|
|
186
|
+
let toolResultText = "";
|
|
187
|
+
let contextWindow = 0;
|
|
188
|
+
let lastModel = "";
|
|
189
|
+
for (let i = 0; i < lines.length; i++) {
|
|
190
|
+
const line = lines[i].trim();
|
|
191
191
|
if (!line)
|
|
192
192
|
continue;
|
|
193
193
|
try {
|
|
194
|
-
|
|
194
|
+
const parsed = JSON.parse(line);
|
|
195
195
|
if (parsed.type === "user" && parsed.message) {
|
|
196
|
-
|
|
196
|
+
const userContent = parsed.message.content;
|
|
197
197
|
if (typeof userContent === "string") {
|
|
198
198
|
userText += userContent + "\n";
|
|
199
199
|
}
|
|
200
200
|
else if (Array.isArray(userContent)) {
|
|
201
|
-
for (
|
|
202
|
-
|
|
201
|
+
for (let j = 0; j < userContent.length; j++) {
|
|
202
|
+
const block = userContent[j];
|
|
203
203
|
if (block.type === "text" && block.text) {
|
|
204
204
|
userText += block.text + "\n";
|
|
205
205
|
}
|
|
@@ -208,7 +208,7 @@ export async function getContextBreakdown(projectSlug, sessionId) {
|
|
|
208
208
|
toolResultText += block.content + "\n";
|
|
209
209
|
}
|
|
210
210
|
else if (Array.isArray(block.content)) {
|
|
211
|
-
for (
|
|
211
|
+
for (let ri = 0; ri < block.content.length; ri++) {
|
|
212
212
|
if (block.content[ri].type === "text" && block.content[ri].text) {
|
|
213
213
|
toolResultText += block.content[ri].text + "\n";
|
|
214
214
|
}
|
|
@@ -219,13 +219,13 @@ export async function getContextBreakdown(projectSlug, sessionId) {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
else if (parsed.type === "assistant" && parsed.message) {
|
|
222
|
-
|
|
222
|
+
const aContent = parsed.message.content;
|
|
223
223
|
if (typeof aContent === "string") {
|
|
224
224
|
assistantText += aContent + "\n";
|
|
225
225
|
}
|
|
226
226
|
else if (Array.isArray(aContent)) {
|
|
227
|
-
for (
|
|
228
|
-
|
|
227
|
+
for (let k = 0; k < aContent.length; k++) {
|
|
228
|
+
const ab = aContent[k];
|
|
229
229
|
if (ab.type === "text" && ab.text) {
|
|
230
230
|
assistantText += ab.text + "\n";
|
|
231
231
|
}
|
|
@@ -244,19 +244,19 @@ export async function getContextBreakdown(projectSlug, sessionId) {
|
|
|
244
244
|
}
|
|
245
245
|
catch { }
|
|
246
246
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
const userTokens = countTokens(userText);
|
|
248
|
+
const assistantTokens = countTokens(assistantText);
|
|
249
|
+
const toolResultTokens = countTokens(toolResultText);
|
|
250
250
|
contextWindow = guessContextWindow(lastModel);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
const autocompactAt = Math.round(contextWindow * 0.9);
|
|
252
|
+
const systemPromptEstimate = 4500;
|
|
253
|
+
const segments = [
|
|
254
254
|
{ label: "System prompt", tokens: systemPromptEstimate, id: "system", estimated: true },
|
|
255
255
|
{ label: "Built-in tools (" + Object.keys(BUILTIN_TOOLS).length + ")", tokens: builtinToolTokens, id: "builtin_tools", estimated: true },
|
|
256
256
|
];
|
|
257
257
|
// Add per-MCP-server segments
|
|
258
258
|
toolCounts.mcpTools.forEach(function (tools, serverName) {
|
|
259
|
-
|
|
259
|
+
const mcpTokens = tools.length * MCP_TOOL_AVG_TOKENS;
|
|
260
260
|
segments.push({
|
|
261
261
|
label: serverName + " (" + tools.length + " tools)",
|
|
262
262
|
tokens: mcpTokens,
|
|
@@ -264,9 +264,9 @@ export async function getContextBreakdown(projectSlug, sessionId) {
|
|
|
264
264
|
estimated: true,
|
|
265
265
|
});
|
|
266
266
|
});
|
|
267
|
-
|
|
267
|
+
const pluginCount = getInstalledPluginCount();
|
|
268
268
|
if (pluginCount > 0) {
|
|
269
|
-
|
|
269
|
+
const pluginTokens = getPluginSkillRuleTokenEstimate();
|
|
270
270
|
segments.push({
|
|
271
271
|
label: "Plugins (" + pluginCount + ")",
|
|
272
272
|
tokens: pluginTokens,
|