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 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
- appendFileSync(filePath, JSON.stringify(event) + "\n");
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
- let reaped = 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 {
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 : 256 * 1024 * 1024;
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 > MAX_EVENT_BYTES) renameSync(file, `${file}.1`);
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
  }
@@ -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
- state2.latestUpdateByPath.set(`${u.storeId}::${u.path}`, rec);
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.flowIds.add(event.context.flowId);
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
- state2.leaks.push({
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
- state2.leaks.push({
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 lines = readFileSync2(file2, "utf8").trim().split("\n").slice(-25e3);
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
- state2.latestUpdateByPath.set(`${u.storeId}::${u.path}`, rec);
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.flowIds.add(event.context.flowId);
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
- state2.leaks.push({
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
- state2.leaks.push({
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":"AAQA,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;AAynBD,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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lensmcp",
3
- "version": "1.16.30",
3
+ "version": "1.16.31",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -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.30",
5
+ "version": "1.16.31",
6
6
  "author": {
7
7
  "name": "David Antoon",
8
8
  "email": "davidmantoon@gmail.com"