lensmcp 1.16.30 → 1.16.31
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/bundled/bridge.js +75 -2
- package/bundled/capture-runner.js +45 -12
- package/bundled/dashboard.js +71 -6
- package/bundled/main.js +57 -4
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +2 -0
- package/lib/sweep.d.ts +12 -0
- package/lib/sweep.d.ts.map +1 -0
- package/lib/sweep.js +60 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/bundled/bridge.js
CHANGED
|
@@ -3733,7 +3733,74 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
3733
3733
|
// libs/bridge/src/lib/env-sink.ts
|
|
3734
3734
|
import { connect } from "node:net";
|
|
3735
3735
|
import { createSocket } from "node:dgram";
|
|
3736
|
-
import { appendFileSync } from "node:fs";
|
|
3736
|
+
import { appendFileSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
3737
|
+
var MAX_EVENT_BYTES = (() => {
|
|
3738
|
+
const n = Number(process.env["LENSMCP_EVENT_MAX_BYTES"]);
|
|
3739
|
+
return Number.isFinite(n) && n > 0 ? n : 50 * 1024 * 1024;
|
|
3740
|
+
})();
|
|
3741
|
+
var MAX_EVENT_FILES = (() => {
|
|
3742
|
+
const n = Number(process.env["LENSMCP_EVENT_MAX_FILES"]);
|
|
3743
|
+
return Number.isFinite(n) && n >= 1 && n <= 20 ? Math.floor(n) : 5;
|
|
3744
|
+
})();
|
|
3745
|
+
var ROTATE_CHECK_BYTES = 1024 * 1024;
|
|
3746
|
+
var bytesSinceCheck = 0;
|
|
3747
|
+
function rotateIfLarge(file, addedBytes) {
|
|
3748
|
+
bytesSinceCheck += addedBytes;
|
|
3749
|
+
if (bytesSinceCheck < ROTATE_CHECK_BYTES) return;
|
|
3750
|
+
bytesSinceCheck = 0;
|
|
3751
|
+
try {
|
|
3752
|
+
if (statSync(file).size <= MAX_EVENT_BYTES) return;
|
|
3753
|
+
} catch {
|
|
3754
|
+
return;
|
|
3755
|
+
}
|
|
3756
|
+
try {
|
|
3757
|
+
unlinkSync(`${file}.${MAX_EVENT_FILES}`);
|
|
3758
|
+
} catch {
|
|
3759
|
+
}
|
|
3760
|
+
for (let gen = MAX_EVENT_FILES - 1; gen >= 1; gen--) {
|
|
3761
|
+
try {
|
|
3762
|
+
renameSync(`${file}.${gen}`, `${file}.${gen + 1}`);
|
|
3763
|
+
} catch {
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
try {
|
|
3767
|
+
renameSync(file, `${file}.1`);
|
|
3768
|
+
} catch {
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
var FLOOD_PER_SEC = (() => {
|
|
3772
|
+
const raw = process.env["LENSMCP_EVENT_MAX_EPS"];
|
|
3773
|
+
if (raw === "off") return Infinity;
|
|
3774
|
+
const n = Number(raw);
|
|
3775
|
+
if (raw !== void 0 && raw !== "" && Number.isFinite(n)) return n <= 0 ? Infinity : n;
|
|
3776
|
+
return 5;
|
|
3777
|
+
})();
|
|
3778
|
+
var FLOOD_BURST = Math.max(30, FLOOD_PER_SEC === Infinity ? 0 : FLOOD_PER_SEC * 4);
|
|
3779
|
+
var floodBuckets = /* @__PURE__ */ new Map();
|
|
3780
|
+
function floodAdmit(fingerprint2) {
|
|
3781
|
+
if (FLOOD_PER_SEC === Infinity) return 0;
|
|
3782
|
+
const now = Date.now();
|
|
3783
|
+
let b = floodBuckets.get(fingerprint2);
|
|
3784
|
+
if (!b) {
|
|
3785
|
+
if (floodBuckets.size >= 2048) {
|
|
3786
|
+
for (const [k, v] of floodBuckets) if (now - v.lastSeenAt > 6e4) floodBuckets.delete(k);
|
|
3787
|
+
if (floodBuckets.size >= 2048) floodBuckets.clear();
|
|
3788
|
+
}
|
|
3789
|
+
b = { tokens: FLOOD_BURST, lastRefillAt: now, suppressed: 0, lastSeenAt: now };
|
|
3790
|
+
floodBuckets.set(fingerprint2, b);
|
|
3791
|
+
}
|
|
3792
|
+
b.lastSeenAt = now;
|
|
3793
|
+
b.tokens = Math.min(FLOOD_BURST, b.tokens + (now - b.lastRefillAt) / 1e3 * FLOOD_PER_SEC);
|
|
3794
|
+
b.lastRefillAt = now;
|
|
3795
|
+
if (b.tokens >= 1) {
|
|
3796
|
+
b.tokens -= 1;
|
|
3797
|
+
const suppressed = b.suppressed;
|
|
3798
|
+
b.suppressed = 0;
|
|
3799
|
+
return suppressed;
|
|
3800
|
+
}
|
|
3801
|
+
b.suppressed += 1;
|
|
3802
|
+
return -1;
|
|
3803
|
+
}
|
|
3737
3804
|
var cachedEnvSink;
|
|
3738
3805
|
function resolveEnvSink() {
|
|
3739
3806
|
if (cachedEnvSink !== void 0) return cachedEnvSink;
|
|
@@ -3741,7 +3808,13 @@ function resolveEnvSink() {
|
|
|
3741
3808
|
if (filePath) {
|
|
3742
3809
|
cachedEnvSink = (event) => {
|
|
3743
3810
|
try {
|
|
3744
|
-
|
|
3811
|
+
const suppressed = floodAdmit(event.fingerprint);
|
|
3812
|
+
if (suppressed < 0) return;
|
|
3813
|
+
const line = JSON.stringify(
|
|
3814
|
+
suppressed > 0 ? { ...event, raw: { ...event.raw ?? {}, floodSuppressed: suppressed } } : event
|
|
3815
|
+
) + "\n";
|
|
3816
|
+
rotateIfLarge(filePath, line.length);
|
|
3817
|
+
appendFileSync(filePath, line);
|
|
3745
3818
|
} catch {
|
|
3746
3819
|
}
|
|
3747
3820
|
};
|
|
@@ -33558,7 +33558,7 @@ var require_chrome_remote_interface = __commonJS({
|
|
|
33558
33558
|
});
|
|
33559
33559
|
|
|
33560
33560
|
// libs/browser-capture/src/capture-runner.ts
|
|
33561
|
-
import { appendFileSync, readFileSync, renameSync, statSync } from "node:fs";
|
|
33561
|
+
import { appendFileSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
33562
33562
|
|
|
33563
33563
|
// libs/core/dist/lib/ulid.js
|
|
33564
33564
|
var ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
@@ -33826,6 +33826,7 @@ function planOrphanReap(psLines, isOwnerAlive, selfPid) {
|
|
|
33826
33826
|
function reapOrphanCaptureChrome(log) {
|
|
33827
33827
|
if (process.platform === "win32")
|
|
33828
33828
|
return 0;
|
|
33829
|
+
let reaped = 0;
|
|
33829
33830
|
let out;
|
|
33830
33831
|
try {
|
|
33831
33832
|
out = execFileSync2("ps", ["ax", "-ww", "-o", "pid=,command="], {
|
|
@@ -33834,20 +33835,33 @@ function reapOrphanCaptureChrome(log) {
|
|
|
33834
33835
|
maxBuffer: 64 * 1024 * 1024
|
|
33835
33836
|
});
|
|
33836
33837
|
} catch {
|
|
33837
|
-
return 0;
|
|
33838
33838
|
}
|
|
33839
|
-
|
|
33840
|
-
|
|
33841
|
-
|
|
33842
|
-
|
|
33843
|
-
|
|
33844
|
-
|
|
33845
|
-
|
|
33839
|
+
if (out !== void 0) {
|
|
33840
|
+
for (const orphan of planOrphanReap(out.split("\n"), isAlive, process.pid)) {
|
|
33841
|
+
try {
|
|
33842
|
+
process.kill(orphan.pid, "SIGKILL");
|
|
33843
|
+
reaped += 1;
|
|
33844
|
+
log?.(`[browser-capture] reaped orphaned capture Chrome pid=${orphan.pid} (owner ${orphan.ownerPid} is gone)`);
|
|
33845
|
+
} catch {
|
|
33846
|
+
}
|
|
33847
|
+
removeUserDataDir(orphan.userDataDir);
|
|
33846
33848
|
}
|
|
33847
|
-
removeUserDataDir(orphan.userDataDir);
|
|
33848
33849
|
}
|
|
33850
|
+
sweepStaleMarkerDirs();
|
|
33849
33851
|
return reaped;
|
|
33850
33852
|
}
|
|
33853
|
+
function sweepStaleMarkerDirs() {
|
|
33854
|
+
const ownerRe = new RegExp(`^${CAPTURE_DIR_MARKER}(\\d+)-`);
|
|
33855
|
+
try {
|
|
33856
|
+
for (const entry of readdirSync(tmpdir())) {
|
|
33857
|
+
const owner = Number(ownerRe.exec(entry)?.[1]);
|
|
33858
|
+
if (!Number.isFinite(owner) || owner === process.pid || isAlive(owner))
|
|
33859
|
+
continue;
|
|
33860
|
+
removeUserDataDir(join2(tmpdir(), entry));
|
|
33861
|
+
}
|
|
33862
|
+
} catch {
|
|
33863
|
+
}
|
|
33864
|
+
}
|
|
33851
33865
|
function isAlive(pid) {
|
|
33852
33866
|
try {
|
|
33853
33867
|
process.kill(pid, 0);
|
|
@@ -34883,7 +34897,11 @@ reapOrphanCaptureChrome((m) => console.error(m));
|
|
|
34883
34897
|
setInterval(() => reapOrphanCaptureChrome((m) => console.error(m)), 6e4).unref();
|
|
34884
34898
|
var MAX_EVENT_BYTES = (() => {
|
|
34885
34899
|
const n = Number(process.env["LENSMCP_EVENT_MAX_BYTES"]);
|
|
34886
|
-
return Number.isFinite(n) && n > 0 ? n :
|
|
34900
|
+
return Number.isFinite(n) && n > 0 ? n : 50 * 1024 * 1024;
|
|
34901
|
+
})();
|
|
34902
|
+
var MAX_EVENT_FILES = (() => {
|
|
34903
|
+
const n = Number(process.env["LENSMCP_EVENT_MAX_FILES"]);
|
|
34904
|
+
return Number.isFinite(n) && n >= 1 && n <= 20 ? Math.floor(n) : 5;
|
|
34887
34905
|
})();
|
|
34888
34906
|
var ROTATE_CHECK_BYTES = 1024 * 1024;
|
|
34889
34907
|
var bytesSinceCheck = 0;
|
|
@@ -34892,7 +34910,22 @@ function rotateIfLarge(file, lineBytes) {
|
|
|
34892
34910
|
if (bytesSinceCheck < ROTATE_CHECK_BYTES) return;
|
|
34893
34911
|
bytesSinceCheck = 0;
|
|
34894
34912
|
try {
|
|
34895
|
-
if (statSync(file).size
|
|
34913
|
+
if (statSync(file).size <= MAX_EVENT_BYTES) return;
|
|
34914
|
+
} catch {
|
|
34915
|
+
return;
|
|
34916
|
+
}
|
|
34917
|
+
try {
|
|
34918
|
+
unlinkSync(`${file}.${MAX_EVENT_FILES}`);
|
|
34919
|
+
} catch {
|
|
34920
|
+
}
|
|
34921
|
+
for (let gen = MAX_EVENT_FILES - 1; gen >= 1; gen--) {
|
|
34922
|
+
try {
|
|
34923
|
+
renameSync(`${file}.${gen}`, `${file}.${gen + 1}`);
|
|
34924
|
+
} catch {
|
|
34925
|
+
}
|
|
34926
|
+
}
|
|
34927
|
+
try {
|
|
34928
|
+
renameSync(file, `${file}.1`);
|
|
34896
34929
|
} catch {
|
|
34897
34930
|
}
|
|
34898
34931
|
}
|
package/bundled/dashboard.js
CHANGED
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
|
|
7
7
|
// servers/lensmcp-mcp/dist/dashboard.js
|
|
8
8
|
import "reflect-metadata";
|
|
9
|
-
import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
9
|
+
import { closeSync as closeSync2, openSync as openSync2, readdirSync, readFileSync as readFileSync2, readSync as readSync2, statSync as statSync2 } from "node:fs";
|
|
10
10
|
import { createServer as createServer2 } from "node:http";
|
|
11
11
|
|
|
12
12
|
// libs/core/dist/lib/ulid.js
|
|
@@ -352,6 +352,10 @@ function compileStory(events, options = {}) {
|
|
|
352
352
|
if (pool.length === 0)
|
|
353
353
|
return void 0;
|
|
354
354
|
pool.sort((a, b) => a.timestamp - b.timestamp);
|
|
355
|
+
const MAX_STORY_EVENTS = 1e3;
|
|
356
|
+
if (pool.length > MAX_STORY_EVENTS) {
|
|
357
|
+
pool = [pool[0], ...pool.slice(pool.length - (MAX_STORY_EVENTS - 1))];
|
|
358
|
+
}
|
|
355
359
|
const origin = pool[0];
|
|
356
360
|
const phases = [];
|
|
357
361
|
let finalRender;
|
|
@@ -16428,6 +16432,7 @@ var URI_CURRENT = "flow://current";
|
|
|
16428
16432
|
var URI_ORIGINS = "flow://origins/recent";
|
|
16429
16433
|
var MAX_FLOWS = 50;
|
|
16430
16434
|
var MAX_ORIGINS = 50;
|
|
16435
|
+
var MAX_EVENTS_PER_FLOW = 2e3;
|
|
16431
16436
|
function installFlowReducer(args) {
|
|
16432
16437
|
const state2 = {
|
|
16433
16438
|
flows: /* @__PURE__ */ new Map(),
|
|
@@ -16470,6 +16475,9 @@ function installFlowReducer(args) {
|
|
|
16470
16475
|
void args.resources.markDirty(URI_ORIGINS);
|
|
16471
16476
|
}
|
|
16472
16477
|
entry.events.push(event);
|
|
16478
|
+
if (entry.events.length > MAX_EVENTS_PER_FLOW) {
|
|
16479
|
+
entry.events.splice(0, entry.events.length - MAX_EVENTS_PER_FLOW);
|
|
16480
|
+
}
|
|
16473
16481
|
entry.summary.eventCount += 1;
|
|
16474
16482
|
entry.summary.lastSeenAt = event.timestamp;
|
|
16475
16483
|
const idx = state2.recentOrder.indexOf(flowId);
|
|
@@ -17677,6 +17685,7 @@ NestApp = __decorate([
|
|
|
17677
17685
|
var URI_PROVIDERS = "nest://providers";
|
|
17678
17686
|
var URI_REQUESTS = "nest://requests/recent";
|
|
17679
17687
|
var MAX_RECENT2 = 50;
|
|
17688
|
+
var MAX_PROVIDERS = 500;
|
|
17680
17689
|
function installNestReducer(args) {
|
|
17681
17690
|
const state2 = { providers: /* @__PURE__ */ new Map(), recentRequests: [] };
|
|
17682
17691
|
const handler = (event) => {
|
|
@@ -17685,7 +17694,15 @@ function installNestReducer(args) {
|
|
|
17685
17694
|
const raw = event.raw ?? null;
|
|
17686
17695
|
if (raw?.kind === "singleton-instance" && raw.provider) {
|
|
17687
17696
|
const p = raw.provider;
|
|
17697
|
+
state2.providers.delete(p.instanceId);
|
|
17688
17698
|
state2.providers.set(p.instanceId, { ...p });
|
|
17699
|
+
if (state2.providers.size > MAX_PROVIDERS) {
|
|
17700
|
+
for (const k of state2.providers.keys()) {
|
|
17701
|
+
state2.providers.delete(k);
|
|
17702
|
+
if (state2.providers.size <= MAX_PROVIDERS)
|
|
17703
|
+
break;
|
|
17704
|
+
}
|
|
17705
|
+
}
|
|
17689
17706
|
void args.resources.markDirty(URI_PROVIDERS);
|
|
17690
17707
|
return;
|
|
17691
17708
|
}
|
|
@@ -18582,6 +18599,7 @@ var URI_MEMOS_RECOMPUTED = "react://memos/recomputed";
|
|
|
18582
18599
|
var URI_HOOKS_RECENT = "react://hooks/recent";
|
|
18583
18600
|
var MAX_RECENT4 = 100;
|
|
18584
18601
|
var MAX_SLOW2 = 50;
|
|
18602
|
+
var MAX_INSTANCES = 500;
|
|
18585
18603
|
function installReactReducer(args) {
|
|
18586
18604
|
const slowRenderMs = resolveThresholds(args.thresholds).slowRenderMs;
|
|
18587
18605
|
const state2 = {
|
|
@@ -18605,7 +18623,15 @@ function installReactReducer(args) {
|
|
|
18605
18623
|
if (!r)
|
|
18606
18624
|
return;
|
|
18607
18625
|
pushBounded3(state2.rendersRecent, r, MAX_RECENT4);
|
|
18626
|
+
state2.rendersByInstance.delete(r.componentInstanceId);
|
|
18608
18627
|
state2.rendersByInstance.set(r.componentInstanceId, r);
|
|
18628
|
+
if (state2.rendersByInstance.size > MAX_INSTANCES) {
|
|
18629
|
+
for (const k of state2.rendersByInstance.keys()) {
|
|
18630
|
+
state2.rendersByInstance.delete(k);
|
|
18631
|
+
if (state2.rendersByInstance.size <= MAX_INSTANCES)
|
|
18632
|
+
break;
|
|
18633
|
+
}
|
|
18634
|
+
}
|
|
18609
18635
|
if (r.actualDurationMs >= slowRenderMs) {
|
|
18610
18636
|
pushBounded3(state2.rendersSlow, r, MAX_SLOW2);
|
|
18611
18637
|
state2.rendersSlow.sort((a, b) => b.actualDurationMs - a.actualDurationMs);
|
|
@@ -18836,6 +18862,7 @@ var URI_UPDATES = "valtio://updates/recent";
|
|
|
18836
18862
|
var URI_STORES = "valtio://stores";
|
|
18837
18863
|
var URI_SUBSCRIBERS = "valtio://subscribers";
|
|
18838
18864
|
var MAX_UPDATES = 100;
|
|
18865
|
+
var MAX_PATHS = 1e3;
|
|
18839
18866
|
function installValtioReducer(args) {
|
|
18840
18867
|
const state2 = {
|
|
18841
18868
|
updates: [],
|
|
@@ -18859,7 +18886,16 @@ function installValtioReducer(args) {
|
|
|
18859
18886
|
if (state2.updates.length > MAX_UPDATES) {
|
|
18860
18887
|
state2.updates.splice(0, state2.updates.length - MAX_UPDATES);
|
|
18861
18888
|
}
|
|
18862
|
-
|
|
18889
|
+
const pathKey = `${u.storeId}::${u.path}`;
|
|
18890
|
+
state2.latestUpdateByPath.delete(pathKey);
|
|
18891
|
+
state2.latestUpdateByPath.set(pathKey, rec);
|
|
18892
|
+
if (state2.latestUpdateByPath.size > MAX_PATHS) {
|
|
18893
|
+
for (const k of state2.latestUpdateByPath.keys()) {
|
|
18894
|
+
state2.latestUpdateByPath.delete(k);
|
|
18895
|
+
if (state2.latestUpdateByPath.size <= MAX_PATHS)
|
|
18896
|
+
break;
|
|
18897
|
+
}
|
|
18898
|
+
}
|
|
18863
18899
|
void args.resources.markDirty(URI_UPDATES);
|
|
18864
18900
|
return;
|
|
18865
18901
|
}
|
|
@@ -19547,8 +19583,25 @@ var URI_CURRENT7 = "memory://current";
|
|
|
19547
19583
|
var URI_OWNERS = "memory://owners";
|
|
19548
19584
|
var URI_GROWING = "memory://owners/growing";
|
|
19549
19585
|
var URI_LEAKS = "memory://leaks/suspected";
|
|
19586
|
+
var MAX_LEAKS = 200;
|
|
19587
|
+
var MAX_FLOWIDS_PER_OWNER = 50;
|
|
19550
19588
|
function installMemoryReducer(args) {
|
|
19551
19589
|
const state2 = { owners: /* @__PURE__ */ new Map(), leaks: [] };
|
|
19590
|
+
const pushLeak = (leak) => {
|
|
19591
|
+
state2.leaks.push(leak);
|
|
19592
|
+
if (state2.leaks.length > MAX_LEAKS)
|
|
19593
|
+
state2.leaks.splice(0, state2.leaks.length - MAX_LEAKS);
|
|
19594
|
+
};
|
|
19595
|
+
const noteFlow = (o, flowId) => {
|
|
19596
|
+
o.flowIds.add(flowId);
|
|
19597
|
+
if (o.flowIds.size > MAX_FLOWIDS_PER_OWNER) {
|
|
19598
|
+
for (const id of o.flowIds) {
|
|
19599
|
+
o.flowIds.delete(id);
|
|
19600
|
+
if (o.flowIds.size <= MAX_FLOWIDS_PER_OWNER)
|
|
19601
|
+
break;
|
|
19602
|
+
}
|
|
19603
|
+
}
|
|
19604
|
+
};
|
|
19552
19605
|
const ensure = (ownerId) => {
|
|
19553
19606
|
let o = state2.owners.get(ownerId);
|
|
19554
19607
|
if (!o) {
|
|
@@ -19579,7 +19632,7 @@ function installMemoryReducer(args) {
|
|
|
19579
19632
|
o.estimatedBytes = raw.mutation.afterCount * 64;
|
|
19580
19633
|
o.mutations += 1;
|
|
19581
19634
|
if (event.context.flowId)
|
|
19582
|
-
o
|
|
19635
|
+
noteFlow(o, event.context.flowId);
|
|
19583
19636
|
void args.resources.markDirty(URI_OWNERS);
|
|
19584
19637
|
void args.resources.markDirty(URI_GROWING);
|
|
19585
19638
|
return;
|
|
@@ -19587,7 +19640,7 @@ function installMemoryReducer(args) {
|
|
|
19587
19640
|
case "memory-retention": {
|
|
19588
19641
|
if (!raw.retention)
|
|
19589
19642
|
return;
|
|
19590
|
-
|
|
19643
|
+
pushLeak({
|
|
19591
19644
|
kind: "retention",
|
|
19592
19645
|
ownerId: raw.retention.ownerId,
|
|
19593
19646
|
flowId: raw.retention.flowId,
|
|
@@ -19600,7 +19653,7 @@ function installMemoryReducer(args) {
|
|
|
19600
19653
|
case "singleton-stale-generation": {
|
|
19601
19654
|
if (!raw.staleGeneration)
|
|
19602
19655
|
return;
|
|
19603
|
-
|
|
19656
|
+
pushLeak({
|
|
19604
19657
|
kind: "stale-generation",
|
|
19605
19658
|
logicalId: raw.staleGeneration.logicalId,
|
|
19606
19659
|
detail: { ...raw.staleGeneration },
|
|
@@ -20358,7 +20411,19 @@ session.bus.subscribe(appendLog);
|
|
|
20358
20411
|
(() => {
|
|
20359
20412
|
try {
|
|
20360
20413
|
const file2 = process.env["LENSMCP_EVENT_FILE"] ?? `${process.cwd()}/.lensmcp/events.jsonl`;
|
|
20361
|
-
const
|
|
20414
|
+
const REPLAY_BYTES = 8 * 1024 * 1024;
|
|
20415
|
+
const size = statSync2(file2).size;
|
|
20416
|
+
const start = Math.max(0, size - REPLAY_BYTES);
|
|
20417
|
+
const buf = Buffer.alloc(size - start);
|
|
20418
|
+
const fd = openSync2(file2, "r");
|
|
20419
|
+
try {
|
|
20420
|
+
readSync2(fd, buf, 0, buf.length, start);
|
|
20421
|
+
} finally {
|
|
20422
|
+
closeSync2(fd);
|
|
20423
|
+
}
|
|
20424
|
+
const lines = buf.toString("utf8").trim().split("\n").slice(-25e3);
|
|
20425
|
+
if (start > 0)
|
|
20426
|
+
lines.shift();
|
|
20362
20427
|
for (const line of lines) {
|
|
20363
20428
|
try {
|
|
20364
20429
|
const e = JSON.parse(line);
|
package/bundled/main.js
CHANGED
|
@@ -362,6 +362,10 @@ function compileStory(events, options = {}) {
|
|
|
362
362
|
if (pool.length === 0)
|
|
363
363
|
return void 0;
|
|
364
364
|
pool.sort((a, b) => a.timestamp - b.timestamp);
|
|
365
|
+
const MAX_STORY_EVENTS = 1e3;
|
|
366
|
+
if (pool.length > MAX_STORY_EVENTS) {
|
|
367
|
+
pool = [pool[0], ...pool.slice(pool.length - (MAX_STORY_EVENTS - 1))];
|
|
368
|
+
}
|
|
365
369
|
const origin = pool[0];
|
|
366
370
|
const phases = [];
|
|
367
371
|
let finalRender;
|
|
@@ -16430,6 +16434,7 @@ var URI_CURRENT = "flow://current";
|
|
|
16430
16434
|
var URI_ORIGINS = "flow://origins/recent";
|
|
16431
16435
|
var MAX_FLOWS = 50;
|
|
16432
16436
|
var MAX_ORIGINS = 50;
|
|
16437
|
+
var MAX_EVENTS_PER_FLOW = 2e3;
|
|
16433
16438
|
function installFlowReducer(args) {
|
|
16434
16439
|
const state2 = {
|
|
16435
16440
|
flows: /* @__PURE__ */ new Map(),
|
|
@@ -16472,6 +16477,9 @@ function installFlowReducer(args) {
|
|
|
16472
16477
|
void args.resources.markDirty(URI_ORIGINS);
|
|
16473
16478
|
}
|
|
16474
16479
|
entry.events.push(event);
|
|
16480
|
+
if (entry.events.length > MAX_EVENTS_PER_FLOW) {
|
|
16481
|
+
entry.events.splice(0, entry.events.length - MAX_EVENTS_PER_FLOW);
|
|
16482
|
+
}
|
|
16475
16483
|
entry.summary.eventCount += 1;
|
|
16476
16484
|
entry.summary.lastSeenAt = event.timestamp;
|
|
16477
16485
|
const idx = state2.recentOrder.indexOf(flowId);
|
|
@@ -17679,6 +17687,7 @@ NestApp = __decorate([
|
|
|
17679
17687
|
var URI_PROVIDERS = "nest://providers";
|
|
17680
17688
|
var URI_REQUESTS = "nest://requests/recent";
|
|
17681
17689
|
var MAX_RECENT2 = 50;
|
|
17690
|
+
var MAX_PROVIDERS = 500;
|
|
17682
17691
|
function installNestReducer(args) {
|
|
17683
17692
|
const state2 = { providers: /* @__PURE__ */ new Map(), recentRequests: [] };
|
|
17684
17693
|
const handler = (event) => {
|
|
@@ -17687,7 +17696,15 @@ function installNestReducer(args) {
|
|
|
17687
17696
|
const raw = event.raw ?? null;
|
|
17688
17697
|
if (raw?.kind === "singleton-instance" && raw.provider) {
|
|
17689
17698
|
const p = raw.provider;
|
|
17699
|
+
state2.providers.delete(p.instanceId);
|
|
17690
17700
|
state2.providers.set(p.instanceId, { ...p });
|
|
17701
|
+
if (state2.providers.size > MAX_PROVIDERS) {
|
|
17702
|
+
for (const k of state2.providers.keys()) {
|
|
17703
|
+
state2.providers.delete(k);
|
|
17704
|
+
if (state2.providers.size <= MAX_PROVIDERS)
|
|
17705
|
+
break;
|
|
17706
|
+
}
|
|
17707
|
+
}
|
|
17691
17708
|
void args.resources.markDirty(URI_PROVIDERS);
|
|
17692
17709
|
return;
|
|
17693
17710
|
}
|
|
@@ -18584,6 +18601,7 @@ var URI_MEMOS_RECOMPUTED = "react://memos/recomputed";
|
|
|
18584
18601
|
var URI_HOOKS_RECENT = "react://hooks/recent";
|
|
18585
18602
|
var MAX_RECENT4 = 100;
|
|
18586
18603
|
var MAX_SLOW2 = 50;
|
|
18604
|
+
var MAX_INSTANCES = 500;
|
|
18587
18605
|
function installReactReducer(args) {
|
|
18588
18606
|
const slowRenderMs = resolveThresholds(args.thresholds).slowRenderMs;
|
|
18589
18607
|
const state2 = {
|
|
@@ -18607,7 +18625,15 @@ function installReactReducer(args) {
|
|
|
18607
18625
|
if (!r)
|
|
18608
18626
|
return;
|
|
18609
18627
|
pushBounded3(state2.rendersRecent, r, MAX_RECENT4);
|
|
18628
|
+
state2.rendersByInstance.delete(r.componentInstanceId);
|
|
18610
18629
|
state2.rendersByInstance.set(r.componentInstanceId, r);
|
|
18630
|
+
if (state2.rendersByInstance.size > MAX_INSTANCES) {
|
|
18631
|
+
for (const k of state2.rendersByInstance.keys()) {
|
|
18632
|
+
state2.rendersByInstance.delete(k);
|
|
18633
|
+
if (state2.rendersByInstance.size <= MAX_INSTANCES)
|
|
18634
|
+
break;
|
|
18635
|
+
}
|
|
18636
|
+
}
|
|
18611
18637
|
if (r.actualDurationMs >= slowRenderMs) {
|
|
18612
18638
|
pushBounded3(state2.rendersSlow, r, MAX_SLOW2);
|
|
18613
18639
|
state2.rendersSlow.sort((a, b) => b.actualDurationMs - a.actualDurationMs);
|
|
@@ -18838,6 +18864,7 @@ var URI_UPDATES = "valtio://updates/recent";
|
|
|
18838
18864
|
var URI_STORES = "valtio://stores";
|
|
18839
18865
|
var URI_SUBSCRIBERS = "valtio://subscribers";
|
|
18840
18866
|
var MAX_UPDATES = 100;
|
|
18867
|
+
var MAX_PATHS = 1e3;
|
|
18841
18868
|
function installValtioReducer(args) {
|
|
18842
18869
|
const state2 = {
|
|
18843
18870
|
updates: [],
|
|
@@ -18861,7 +18888,16 @@ function installValtioReducer(args) {
|
|
|
18861
18888
|
if (state2.updates.length > MAX_UPDATES) {
|
|
18862
18889
|
state2.updates.splice(0, state2.updates.length - MAX_UPDATES);
|
|
18863
18890
|
}
|
|
18864
|
-
|
|
18891
|
+
const pathKey = `${u.storeId}::${u.path}`;
|
|
18892
|
+
state2.latestUpdateByPath.delete(pathKey);
|
|
18893
|
+
state2.latestUpdateByPath.set(pathKey, rec);
|
|
18894
|
+
if (state2.latestUpdateByPath.size > MAX_PATHS) {
|
|
18895
|
+
for (const k of state2.latestUpdateByPath.keys()) {
|
|
18896
|
+
state2.latestUpdateByPath.delete(k);
|
|
18897
|
+
if (state2.latestUpdateByPath.size <= MAX_PATHS)
|
|
18898
|
+
break;
|
|
18899
|
+
}
|
|
18900
|
+
}
|
|
18865
18901
|
void args.resources.markDirty(URI_UPDATES);
|
|
18866
18902
|
return;
|
|
18867
18903
|
}
|
|
@@ -19549,8 +19585,25 @@ var URI_CURRENT7 = "memory://current";
|
|
|
19549
19585
|
var URI_OWNERS = "memory://owners";
|
|
19550
19586
|
var URI_GROWING = "memory://owners/growing";
|
|
19551
19587
|
var URI_LEAKS = "memory://leaks/suspected";
|
|
19588
|
+
var MAX_LEAKS = 200;
|
|
19589
|
+
var MAX_FLOWIDS_PER_OWNER = 50;
|
|
19552
19590
|
function installMemoryReducer(args) {
|
|
19553
19591
|
const state2 = { owners: /* @__PURE__ */ new Map(), leaks: [] };
|
|
19592
|
+
const pushLeak = (leak) => {
|
|
19593
|
+
state2.leaks.push(leak);
|
|
19594
|
+
if (state2.leaks.length > MAX_LEAKS)
|
|
19595
|
+
state2.leaks.splice(0, state2.leaks.length - MAX_LEAKS);
|
|
19596
|
+
};
|
|
19597
|
+
const noteFlow = (o, flowId) => {
|
|
19598
|
+
o.flowIds.add(flowId);
|
|
19599
|
+
if (o.flowIds.size > MAX_FLOWIDS_PER_OWNER) {
|
|
19600
|
+
for (const id of o.flowIds) {
|
|
19601
|
+
o.flowIds.delete(id);
|
|
19602
|
+
if (o.flowIds.size <= MAX_FLOWIDS_PER_OWNER)
|
|
19603
|
+
break;
|
|
19604
|
+
}
|
|
19605
|
+
}
|
|
19606
|
+
};
|
|
19554
19607
|
const ensure = (ownerId) => {
|
|
19555
19608
|
let o = state2.owners.get(ownerId);
|
|
19556
19609
|
if (!o) {
|
|
@@ -19581,7 +19634,7 @@ function installMemoryReducer(args) {
|
|
|
19581
19634
|
o.estimatedBytes = raw.mutation.afterCount * 64;
|
|
19582
19635
|
o.mutations += 1;
|
|
19583
19636
|
if (event.context.flowId)
|
|
19584
|
-
o
|
|
19637
|
+
noteFlow(o, event.context.flowId);
|
|
19585
19638
|
void args.resources.markDirty(URI_OWNERS);
|
|
19586
19639
|
void args.resources.markDirty(URI_GROWING);
|
|
19587
19640
|
return;
|
|
@@ -19589,7 +19642,7 @@ function installMemoryReducer(args) {
|
|
|
19589
19642
|
case "memory-retention": {
|
|
19590
19643
|
if (!raw.retention)
|
|
19591
19644
|
return;
|
|
19592
|
-
|
|
19645
|
+
pushLeak({
|
|
19593
19646
|
kind: "retention",
|
|
19594
19647
|
ownerId: raw.retention.ownerId,
|
|
19595
19648
|
flowId: raw.retention.flowId,
|
|
@@ -19602,7 +19655,7 @@ function installMemoryReducer(args) {
|
|
|
19602
19655
|
case "singleton-stale-generation": {
|
|
19603
19656
|
if (!raw.staleGeneration)
|
|
19604
19657
|
return;
|
|
19605
|
-
|
|
19658
|
+
pushLeak({
|
|
19606
19659
|
kind: "stale-generation",
|
|
19607
19660
|
logicalId: raw.staleGeneration.logicalId,
|
|
19608
19661
|
detail: { ...raw.staleGeneration },
|
package/lib/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,UAAU;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,gEAAgE;IAChE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AA6FD,wBAAsB,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA4ChE;AA0nBD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEnE;;;;;;;;iFAQiF;AACjF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,mBAAmB,CAItB"}
|
package/lib/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { ensureLensConfig } from './workspace-scope.js';
|
|
8
|
+
import { sweepLensRuntimeFiles } from './sweep.js';
|
|
8
9
|
const HELP = `lensmcp — CLI for LensMCP (FrontMCP-based observability for coding agents)
|
|
9
10
|
|
|
10
11
|
Usage:
|
|
@@ -371,6 +372,7 @@ function runGateway(ctx, args, out, err) {
|
|
|
371
372
|
err('Could not find the `nx` binary in the workspace. Install Nx first: `yarn add -D nx`.');
|
|
372
373
|
return { exitCode: 1 };
|
|
373
374
|
}
|
|
375
|
+
sweepLensRuntimeFiles(cwd, logFile);
|
|
374
376
|
const fd = openSync(logFile, 'a');
|
|
375
377
|
const child = spawn(nxBin, ['run', `${target.project}:${target.target}`], {
|
|
376
378
|
cwd,
|
package/lib/sweep.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retention sweep at gateway start — the one moment we KNOW no gateway is writing these files.
|
|
3
|
+
* The bus rotates round-robin (`events.jsonl.1` … `.N`, 50 MB per generation, oldest dropped —
|
|
4
|
+
* emit.ts's `LENSMCP_EVENT_MAX_BYTES`/`LENSMCP_EVENT_MAX_FILES` contract); this prunes what that
|
|
5
|
+
* scheme can't reach: legacy `.old` files older schemes left behind (a tetros workspace carried an
|
|
6
|
+
* 8.6 GB `events.jsonl.old` from July), generations beyond the horizon, oversized generations from
|
|
7
|
+
* the pre-cap era, and a grown `gateway.log` (rotated to `.1` so the append-only log doesn't creep
|
|
8
|
+
* to hundreds of MB across restarts). Lives outside cli.ts so it stays CJS-jestable (cli.ts uses
|
|
9
|
+
* import.meta).
|
|
10
|
+
*/
|
|
11
|
+
export declare function sweepLensRuntimeFiles(cwd: string, logFile: string): void;
|
|
12
|
+
//# sourceMappingURL=sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sweep.d.ts","sourceRoot":"","sources":["../../src/lib/sweep.ts"],"names":[],"mappings":"AAWA;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAgCxE"}
|
package/lib/sweep.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readdirSync, renameSync, rmSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const removeQuietly = (path) => {
|
|
4
|
+
try {
|
|
5
|
+
rmSync(path, { force: true });
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
/* best-effort — a vanished file or racing sweep must never block a start */
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Retention sweep at gateway start — the one moment we KNOW no gateway is writing these files.
|
|
13
|
+
* The bus rotates round-robin (`events.jsonl.1` … `.N`, 50 MB per generation, oldest dropped —
|
|
14
|
+
* emit.ts's `LENSMCP_EVENT_MAX_BYTES`/`LENSMCP_EVENT_MAX_FILES` contract); this prunes what that
|
|
15
|
+
* scheme can't reach: legacy `.old` files older schemes left behind (a tetros workspace carried an
|
|
16
|
+
* 8.6 GB `events.jsonl.old` from July), generations beyond the horizon, oversized generations from
|
|
17
|
+
* the pre-cap era, and a grown `gateway.log` (rotated to `.1` so the append-only log doesn't creep
|
|
18
|
+
* to hundreds of MB across restarts). Lives outside cli.ts so it stays CJS-jestable (cli.ts uses
|
|
19
|
+
* import.meta).
|
|
20
|
+
*/
|
|
21
|
+
export function sweepLensRuntimeFiles(cwd, logFile) {
|
|
22
|
+
const dir = join(cwd, '.lensmcp');
|
|
23
|
+
const eventFile = join(dir, 'events.jsonl');
|
|
24
|
+
removeQuietly(`${eventFile}.old`);
|
|
25
|
+
removeQuietly(`${logFile}.old`);
|
|
26
|
+
const maxFiles = (() => {
|
|
27
|
+
const n = Number(process.env['LENSMCP_EVENT_MAX_FILES']);
|
|
28
|
+
return Number.isFinite(n) && n >= 1 && n <= 20 ? Math.floor(n) : 5;
|
|
29
|
+
})();
|
|
30
|
+
const MAX_GENERATION_BYTES = 64 * 1024 * 1024; // legit generations are ≤ ~50 MB; bigger = pre-cap era litter
|
|
31
|
+
try {
|
|
32
|
+
for (const name of readdirSync(dir)) {
|
|
33
|
+
const m = /^events\.jsonl\.(\d+)$/.exec(name);
|
|
34
|
+
if (!m)
|
|
35
|
+
continue;
|
|
36
|
+
const gen = Number(m[1]);
|
|
37
|
+
const file = join(dir, name);
|
|
38
|
+
if (gen < 1 || gen > maxFiles)
|
|
39
|
+
removeQuietly(file);
|
|
40
|
+
else {
|
|
41
|
+
try {
|
|
42
|
+
if (statSync(file).size > MAX_GENERATION_BYTES)
|
|
43
|
+
removeQuietly(file);
|
|
44
|
+
}
|
|
45
|
+
catch { /* raced away */ }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* no .lensmcp dir yet */
|
|
51
|
+
}
|
|
52
|
+
const MAX_GATEWAY_LOG_BYTES = 16 * 1024 * 1024;
|
|
53
|
+
try {
|
|
54
|
+
if (statSync(logFile).size > MAX_GATEWAY_LOG_BYTES)
|
|
55
|
+
renameSync(logFile, `${logFile}.1`);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* no log yet */
|
|
59
|
+
}
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "lensmcp",
|
|
3
3
|
"displayName": "LensMCP",
|
|
4
4
|
"description": "The observability lens for coding agents. One command brings up the dev cluster gateway (every project.json `cluster` decl → its host on :443), the per-project lens dashboard at https://lensmcp.local/<project>/, and the MCP server your agent connects to — scoped automatically to whatever project you opened Claude Code in.",
|
|
5
|
-
"version": "1.16.
|
|
5
|
+
"version": "1.16.31",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "David Antoon",
|
|
8
8
|
"email": "davidmantoon@gmail.com"
|