@lensmcp/core 1.25.32 → 1.25.33

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/index.d.ts CHANGED
@@ -20,4 +20,5 @@ export * from './lib/flows-config.js';
20
20
  export * from './lib/patterns.js';
21
21
  export * from './lib/thresholds.js';
22
22
  export * from './lib/event-loop.js';
23
+ export * from './lib/event-loop-history.js';
23
24
  export * from './lib/graph-from-events.js';
package/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";export*from"./lib/common.js";export*from"./lib/ulid.js";export*from"./lib/event-bus.js";export*from"./lib/event-redactor.js";export*from"./lib/resource-store.js";export*from"./lib/answer-budget.js";export*from"./lib/graph-store.js";export*from"./lib/fingerprint.js";export*from"./lib/reducer-registry.js";export*from"./lib/story.js";export*from"./lib/route-template.js";export*from"./lib/sql-shape.js";export*from"./lib/http-shape.js";export*from"./lib/flow-shape.js";export*from"./lib/flow-segments.js";export*from"./lib/flow-categories.js";export*from"./lib/headless-flow.js";export*from"./lib/service-hop.js";export*from"./lib/flows-config.js";export*from"./lib/patterns.js";export*from"./lib/thresholds.js";export*from"./lib/event-loop.js";export*from"./lib/graph-from-events.js";
1
+ "use strict";export*from"./lib/common.js";export*from"./lib/ulid.js";export*from"./lib/event-bus.js";export*from"./lib/event-redactor.js";export*from"./lib/resource-store.js";export*from"./lib/answer-budget.js";export*from"./lib/graph-store.js";export*from"./lib/fingerprint.js";export*from"./lib/reducer-registry.js";export*from"./lib/story.js";export*from"./lib/route-template.js";export*from"./lib/sql-shape.js";export*from"./lib/http-shape.js";export*from"./lib/flow-shape.js";export*from"./lib/flow-segments.js";export*from"./lib/flow-categories.js";export*from"./lib/headless-flow.js";export*from"./lib/service-hop.js";export*from"./lib/flows-config.js";export*from"./lib/patterns.js";export*from"./lib/thresholds.js";export*from"./lib/event-loop.js";export*from"./lib/event-loop-history.js";export*from"./lib/graph-from-events.js";
@@ -0,0 +1,74 @@
1
+ import type { EventLoopIdentity, EventLoopPoint, EventLoopSample, EventLoopStallMark } from '@lensmcp/protocol-types';
2
+ /**
3
+ * A loop's delay over time: the samples a Node pod carries in its `event-loop-lag` events, held for the last hour, and
4
+ * the arithmetic every reader of them shares — bucketing that keeps the spikes, and the bucket width for a window.
5
+ * Pure, so the Event loops charts and the store agree on every number.
6
+ */
7
+ /** How long one sample covers, at the producer's default (`LENSMCP_EVENT_LOOP_STEP_MS`). */
8
+ export declare const LOOP_SAMPLE_MS = 5000;
9
+ /** The rollups kept for the whole retention: five minutes of samples each. */
10
+ export declare const LOOP_ROLLUP_MS = 300000;
11
+ /** How long the 5 s samples are kept on disk; past it, the rollups answer. */
12
+ export declare const LOOP_SAMPLE_HOURS = 6;
13
+ /** The samples an event carries, checked one by one: oldest first, one per instant, at most an hour of them. */
14
+ export declare function parseLoopSamples(v: unknown): EventLoopSample[];
15
+ /** A run of samples summed: one sample, a bucket of them, or a stored rollup. */
16
+ export interface LoopRollup {
17
+ at: number;
18
+ n: number;
19
+ meanSum: number;
20
+ p99: number;
21
+ max: number;
22
+ utilSum: number;
23
+ }
24
+ export declare const rollupOf: (s: EventLoopSample) => LoopRollup;
25
+ /**
26
+ * Rows bucketed by `stepMs`, each bucket starting at a multiple of it from the epoch — the same buckets on every read,
27
+ * so a chart polled again does not shift. max and p99 are the bucket's highest (a 3 s stall in a 30-minute bucket still
28
+ * reads 3 s); mean and utilization are averaged over the samples in it.
29
+ */
30
+ export declare function bucketRollups(rows: Iterable<LoopRollup>, stepMs: number): EventLoopPoint[];
31
+ /**
32
+ * The bucket width for a window of `spanMs`: the narrowest of {@link STEPS} that keeps it under `maxPoints` buckets,
33
+ * no narrower than the source's resolution (`minMs`) nor than one asked for. 15 min → 5 s, 1 h → 10 s, 6 h → 1 min,
34
+ * 24 h → 5 min, 7 d → 30 min.
35
+ */
36
+ export declare function historyStep(spanMs: number, opts: {
37
+ minMs: number;
38
+ requestedMs?: number;
39
+ maxPoints?: number;
40
+ }): number;
41
+ /** The 5 s samples answer a window that starts within their retention; an older one is read from the rollups. */
42
+ export declare function historySource(from: number, now: number, sampleHours?: number): 'samples' | 'rollups';
43
+ /** A stall as a marker needs: which loop, when, how long, and whether it was still stuck. */
44
+ export interface LoopStallRow {
45
+ loopId: string;
46
+ project: string;
47
+ runtime: EventLoopIdentity['runtime'];
48
+ startAt: number;
49
+ durationMs: number;
50
+ unresponsive: boolean;
51
+ }
52
+ /** Stalls grouped per loop and bucket — one marker each — counted, the longest kept. */
53
+ export declare function bucketStalls(rows: Iterable<LoopStallRow>, stepMs: number): EventLoopStallMark[];
54
+ /**
55
+ * The last hour of every loop's samples, in memory: what the charts read when the lens has no history file, and the
56
+ * seconds a write to it has not reached yet. Bounded by age and by a total — the loops seen longest ago go first.
57
+ */
58
+ export declare class LoopSampleRing {
59
+ private readonly keepMs;
60
+ private readonly maxSamples;
61
+ private readonly rings;
62
+ private total;
63
+ private touches;
64
+ constructor(keepMs?: number, maxSamples?: number);
65
+ /** Adds what is new of `samples` (oldest first); returns how many were. */
66
+ add(loop: EventLoopIdentity, samples: readonly EventLoopSample[], now?: number): number;
67
+ /** Each loop's samples in [from, to). */
68
+ read(from: number, to: number, now?: number): Array<{
69
+ loop: EventLoopIdentity;
70
+ samples: EventLoopSample[];
71
+ }>;
72
+ get size(): number;
73
+ private trim;
74
+ }
@@ -0,0 +1 @@
1
+ "use strict";var S=Object.defineProperty;var p=(n,e)=>S(n,"name",{value:e,configurable:!0});var d=Object.defineProperty,l=p((n,e)=>d(n,"name",{value:e,configurable:!0}),"l");export const LOOP_SAMPLE_MS=5e3,LOOP_ROLLUP_MS=3e5,LOOP_SAMPLE_HOURS=6;const v=720,m=l(n=>typeof n=="number"&&Number.isFinite(n)&&n>=0,"finite");export function parseLoopSamples(n){if(!Array.isArray(n))return[];const e=new Map;for(const i of n.slice(-720)){if(!Array.isArray(i)||i.length<5)continue;const[o,s,t,r,a]=i;!m(o)||o<=0||!m(s)||!m(t)||!m(r)||!m(a)||e.set(o,[o,s,t,r,Math.min(1,a)])}return[...e.values()].sort((i,o)=>i[0]-o[0])}p(parseLoopSamples,"parseLoopSamples"),l(parseLoopSamples,"parseLoopSamples");export const rollupOf=l(n=>({at:n[0],n:1,meanSum:n[1],p99:n[2],max:n[3],utilSum:n[4]}),"rollupOf");export function bucketRollups(n,e){const i=Math.max(1,Math.floor(e)),o=new Map;for(const t of n){if(!(t.n>0))continue;const r=Math.floor(t.at/i)*i,a=o.get(r);a?(a.n+=t.n,a.meanSum+=t.meanSum,a.utilSum+=t.utilSum,a.p99=Math.max(a.p99,t.p99),a.max=Math.max(a.max,t.max)):o.set(r,{...t,at:r})}const s=l((t,r)=>Number(t.toFixed(r)),"round");return[...o.values()].sort((t,r)=>t.at-r.at).map(t=>[t.at,s(t.meanSum/t.n,2),s(t.p99,2),s(t.max,2),s(t.utilSum/t.n,4)])}p(bucketRollups,"bucketRollups"),l(bucketRollups,"bucketRollups");const M=[5,10,15,30,60,120,300,600,900,1800,3600,7200,21600,43200,86400].map(n=>n*1e3);export function historyStep(n,e){const i=Math.max(e.minMs,e.requestedMs??0,n/(e.maxPoints??400));return M.find(o=>o>=i&&o%e.minMs===0)??M[M.length-1]}p(historyStep,"historyStep"),l(historyStep,"historyStep");export function historySource(n,e,i=6){return n>=e-i*36e5-6e4?"samples":"rollups"}p(historySource,"historySource"),l(historySource,"historySource");export function bucketStalls(n,e){const i=Math.max(1,Math.floor(e)),o=new Map;for(const s of n){const t=Math.floor(s.startAt/i)*i,r=`${s.loopId}\0${t}`,a=o.get(r);a?(a.count++,a.longestMs=Math.max(a.longestMs,s.durationMs),a.unresponsive||=s.unresponsive):o.set(r,{loopId:s.loopId,project:s.project,runtime:s.runtime,at:t,count:1,longestMs:s.durationMs,unresponsive:s.unresponsive})}return[...o.values()].sort((s,t)=>s.at-t.at||s.loopId.localeCompare(t.loopId))}p(bucketStalls,"bucketStalls"),l(bucketStalls,"bucketStalls");export class LoopSampleRing{static{p(this,"LoopSampleRing")}static{l(this,"LoopSampleRing")}constructor(e=36e5,i=4e4){this.keepMs=e,this.maxSamples=i,this.rings=new Map,this.total=0,this.touches=0}add(e,i,o=Date.now()){const s=o-this.keepMs;let t=this.rings.get(e.id),r=0;for(const a of i){if(a[0]<s)continue;t||this.rings.set(e.id,t={loop:e,samples:[],touched:0});const u=t.samples,g=u[u.length-1];if(!g||a[0]>g[0])u.push(a);else{let c=0,h=u.length;for(;c<h;){const f=c+h>>1;u[f][0]<a[0]?c=f+1:h=f}if(u[c]?.[0]===a[0])continue;u.splice(c,0,a)}r++}return t&&r&&(t.loop=e,t.touched=++this.touches,this.total+=r),this.trim(s),r}read(e,i,o=Date.now()){this.trim(o-this.keepMs);const s=[];for(const t of this.rings.values()){const r=t.samples.filter(a=>a[0]>=e&&a[0]<i);r.length&&s.push({loop:t.loop,samples:r})}return s}get size(){return this.total}trim(e){for(const[i,o]of this.rings){let s=0;for(;s<o.samples.length&&o.samples[s][0]<e;)s++;s&&(o.samples.splice(0,s),this.total-=s),o.samples.length||this.rings.delete(i)}if(!(this.total<=this.maxSamples))for(const i of[...this.rings.values()].sort((o,s)=>o.touched-s.touched)){if(this.total<=this.maxSamples)break;this.total-=i.samples.length,this.rings.delete(i.loop.id)}}}
@@ -1,14 +1,21 @@
1
- import type { BaseEvent, EventLoopIdentity, EventLoopIncident, EventLoopStatus } from '@lensmcp/protocol-types';
1
+ import type { BaseEvent, EventLoopIdentity, EventLoopIncident, EventLoopSample, EventLoopStatus } from '@lensmcp/protocol-types';
2
+ import { LoopSampleRing } from './event-loop-history.js';
2
3
  export declare function eventLoopIdentity(v: unknown): EventLoopIdentity | undefined;
3
4
  /** Strictly project producer data. No raw request/response or AI content is copied into incident evidence. */
4
5
  export declare function eventLoopIncident(e: BaseEvent): EventLoopIncident | undefined;
5
6
  /** Bounded live index. Persist complete incident envelopes through onChange, not references to expiring events. */
6
7
  export declare class EventLoopIndex {
7
8
  private readonly onChange;
9
+ /** Every sample an event carried, to be kept: the store skips what it holds already. */
10
+ private readonly onSamples;
8
11
  private readonly incidents;
9
12
  private readonly loops;
10
13
  private recent;
11
- constructor(onChange?: (incident: EventLoopIncident) => void);
14
+ /** The last hour of each loop's 5 s samples (`raw.samples` of its `event-loop-lag` events). */
15
+ readonly history: LoopSampleRing;
16
+ constructor(onChange?: (incident: EventLoopIncident) => void,
17
+ /** Every sample an event carried, to be kept: the store skips what it holds already. */
18
+ onSamples?: (loop: EventLoopIdentity, samples: EventLoopSample[]) => void);
12
19
  ingest(e: BaseEvent): void;
13
20
  private attach;
14
21
  statuses(now?: number): EventLoopStatus[];
package/lib/event-loop.js CHANGED
@@ -1 +1 @@
1
- "use strict";var M=Object.defineProperty;var p=(n,e)=>M(n,"name",{value:e,configurable:!0});var k=Object.defineProperty,l=p((n,e)=>k(n,"name",{value:e,configurable:!0}),"u");const v=l(n=>n&&typeof n=="object"&&!Array.isArray(n)?n:{},"object"),o=l(n=>typeof n=="number"&&Number.isFinite(n)&&n>=0?n:void 0,"number"),s=l((n,e=250)=>typeof n=="string"&&n?n.slice(0,e):void 0,"text"),T=40;export function eventLoopIdentity(n){const e=v(n),t=s(e.id),r=s(e.project);if(!(!t||!r||e.runtime!=="node"&&e.runtime!=="browser"))return{id:t,project:r,runtime:e.runtime,...o(e.pid)!==void 0?{pid:o(e.pid)}:{},...o(e.threadId)!==void 0?{threadId:o(e.threadId)}:{},...s(e.tabId)?{tabId:s(e.tabId)}:{},...s(e.loadId)?{loadId:s(e.loadId)}:{}}}p(eventLoopIdentity,"eventLoopIdentity"),l(eventLoopIdentity,"eventLoopIdentity");export function eventLoopIncident(n){const e=v(n.raw);if(e.kind!=="event-loop-stall")return;const t=eventLoopIdentity(e.loop),r=o(e.startedAt),d=o(e.durationMs),i=e.mechanism;if(!t||r===void 0||d===void 0||!["callback","heartbeat","long-animation-frame","longtask"].includes(String(i)))return;const h=e.state==="unresponsive"?"unresponsive":e.state==="unknown"?"unknown":"recovered",u=[];for(const I of Array.isArray(e.scripts)?e.scripts.slice(0,8):[]){const c=v(I),m=s(c.url,512);m&&u.push({url:m,durationMs:o(c.durationMs)??0,startAt:o(c.startAt)??r,...s(c.functionName)?{functionName:s(c.functionName)}:{},...s(c.invoker)?{invoker:s(c.invoker)}:{}})}const f=i==="callback"?s(n.context?.flowId):void 0;return{id:s(e.incidentId)??n.id,loop:t,startAt:r,durationMs:d,observedAt:n.timestamp,...h==="recovered"?{endAt:r+d}:{},state:h,mechanism:i,severity:d>=1e3?"error":"warning",...f?{flowId:f}:{},...s(n.context?.requestId)?{requestId:s(n.context?.requestId)}:{},...s(e.callbackType)?{callbackType:s(e.callbackType)}:{},scripts:u,evidence:[],evidenceTruncated:e.scriptsTruncated===!0||(o(e.droppedIncidents)??0)>0||(o(e.floodSuppressed)??0)>0,explanation:i==="callback"?"Synchronous callback wall time, including any GC or process descheduling. Execution context identifies the flow; overlapping operations are not proven causes.":i==="heartbeat"?"The loop missed its heartbeat. The observer cannot identify executing code; overlapping work may have been delayed.":"Browser-measured main-thread work. Script timings identify contributing scripts where supported; overlapping flows are associated by time only."}}p(eventLoopIncident,"eventLoopIncident"),l(eventLoopIncident,"eventLoopIncident");export class EventLoopIndex{static{p(this,"EventLoopIndex")}static{l(this,"EventLoopIndex")}constructor(e=()=>{}){this.onChange=e,this.incidents=new Map,this.loops=new Map,this.recent=[]}ingest(e){const t=v(e.raw),r=s(t.kind)??e.source,d=eventLoopIdentity(t.loop);if(d){const a=this.loops.get(d.id);if(!a||e.timestamp>=a.lastSeen)for(this.loops.set(d.id,{loop:d,lastSeen:e.timestamp,state:t.state==="unresponsive"?"unresponsive":t.state==="unknown"?"unknown":"observed",coverage:s(t.coverage,500)??a?.coverage??(d.runtime==="node"?"Instrumented Node thread":"Browser main thread; supported PerformanceObserver entries"),...a?{meanMs:a.meanMs,maxMs:a.maxMs,p99Ms:a.p99Ms,utilization:a.utilization}:{},...o(t.lagMs)!==void 0?{meanMs:o(t.lagMs),maxMs:o(t.maxMs),p99Ms:o(t.p99Ms),utilization:o(t.utilization)}:{}});this.loops.size>200;)this.loops.delete(this.loops.keys().next().value)}const i=eventLoopIncident(e);if(i){const a=this.incidents.get(i.id);if(a&&a.observedAt>i.observedAt)return;a&&(i.evidence=a.evidence.slice(),i.evidenceTruncated||=a.evidenceTruncated);for(const A of this.recent)this.attach(i,A);for(this.incidents.set(i.id,i);this.incidents.size>200;)this.incidents.delete(this.incidents.keys().next().value);this.onChange(i);return}if(r.startsWith("event-loop-"))return;const h=s(t.loopId);if(!h)return;const u=v(t.span),f=v(t.request),I=v(t.render),c=o(t.durationMs)??o(u.durationMs)??o(f.durationMs)??o(I.actualDurationMs)??0,m=o(t.startedAt)??o(u.startTime)??o(f.startedAt)??e.timestamp-c,b=o(u.syncDurationMs),w=o(u.syncStartedAt),g=b!==void 0&&b>=50&&w!==void 0,x={loopId:h,eventId:e.id,title:(e.title??r).slice(0,250),kind:r,startAt:g?w:m,endAt:g?w+b:m+c,...e.context?.flowId?{flowId:e.context.flowId}:{},...e.context?.requestId?{requestId:e.context.requestId}:{},relation:g?"executing":"overlap"};this.recent.push(x),this.recent.length>2e3&&this.recent.splice(0,this.recent.length-2e3);for(const a of this.incidents.values())this.attach(a,x)&&this.onChange(a)}attach(e,t){if(t.loopId!==e.loop.id||t.endAt<e.startAt||t.startAt>(e.endAt??e.observedAt)||e.evidence.some(i=>i.eventId===t.eventId))return!1;if(e.evidence.length>=T){const i=!e.evidenceTruncated;return e.evidenceTruncated=!0,i}const{loopId:r,...d}=t;return e.evidence.push(d),!0}statuses(e=Date.now()){return[...this.loops.values()].map(t=>({...t,state:e-t.lastSeen>9e4?"unknown":t.state})).sort((t,r)=>r.lastSeen-t.lastSeen)}list(e){return[...this.incidents.values()].filter(t=>!e||t.flowId===e||t.evidence.some(r=>r.flowId===e)).sort((t,r)=>r.startAt-t.startAt)}}let y=l(()=>[],"readIncidents");export function setFlowLoopIncidents(n){y=n}p(setFlowLoopIncidents,"setFlowLoopIncidents"),l(setFlowLoopIncidents,"setFlowLoopIncidents");export function flowLoopIncidents(n){return y(n)}p(flowLoopIncidents,"flowLoopIncidents"),l(flowLoopIncidents,"flowLoopIncidents");
1
+ "use strict";var M=Object.defineProperty;var u=(n,e)=>M(n,"name",{value:e,configurable:!0});var k=Object.defineProperty,l=u((n,e)=>k(n,"name",{value:e,configurable:!0}),"u");import{LoopSampleRing as S,parseLoopSamples as L}from"./event-loop-history.js";const v=l(n=>n&&typeof n=="object"&&!Array.isArray(n)?n:{},"object"),o=l(n=>typeof n=="number"&&Number.isFinite(n)&&n>=0?n:void 0,"number"),s=l((n,e=250)=>typeof n=="string"&&n?n.slice(0,e):void 0,"text"),T=40;export function eventLoopIdentity(n){const e=v(n),t=s(e.id),i=s(e.project);if(!(!t||!i||e.runtime!=="node"&&e.runtime!=="browser"))return{id:t,project:i,runtime:e.runtime,...o(e.pid)!==void 0?{pid:o(e.pid)}:{},...o(e.threadId)!==void 0?{threadId:o(e.threadId)}:{},...s(e.tabId)?{tabId:s(e.tabId)}:{},...s(e.loadId)?{loadId:s(e.loadId)}:{}}}u(eventLoopIdentity,"eventLoopIdentity"),l(eventLoopIdentity,"eventLoopIdentity");export function eventLoopIncident(n){const e=v(n.raw);if(e.kind!=="event-loop-stall")return;const t=eventLoopIdentity(e.loop),i=o(e.startedAt),d=o(e.durationMs),r=e.mechanism;if(!t||i===void 0||d===void 0||!["callback","heartbeat","long-animation-frame","longtask"].includes(String(r)))return;const h=e.state==="unresponsive"?"unresponsive":e.state==="unknown"?"unknown":"recovered",p=[];for(const g of Array.isArray(e.scripts)?e.scripts.slice(0,8):[]){const c=v(g),f=s(c.url,512);f&&p.push({url:f,durationMs:o(c.durationMs)??0,startAt:o(c.startAt)??i,...s(c.functionName)?{functionName:s(c.functionName)}:{},...s(c.invoker)?{invoker:s(c.invoker)}:{}})}const m=r==="callback"?s(n.context?.flowId):void 0;return{id:s(e.incidentId)??n.id,loop:t,startAt:i,durationMs:d,observedAt:n.timestamp,...h==="recovered"?{endAt:i+d}:{},state:h,mechanism:r,severity:d>=1e3?"error":"warning",...m?{flowId:m}:{},...s(n.context?.requestId)?{requestId:s(n.context?.requestId)}:{},...s(e.callbackType)?{callbackType:s(e.callbackType)}:{},scripts:p,evidence:[],evidenceTruncated:e.scriptsTruncated===!0||(o(e.droppedIncidents)??0)>0||(o(e.floodSuppressed)??0)>0,explanation:r==="callback"?"Synchronous callback wall time, including any GC or process descheduling. Execution context identifies the flow; overlapping operations are not proven causes.":r==="heartbeat"?"The loop missed its heartbeat. The observer cannot identify executing code; overlapping work may have been delayed.":"Browser-measured main-thread work. Script timings identify contributing scripts where supported; overlapping flows are associated by time only."}}u(eventLoopIncident,"eventLoopIncident"),l(eventLoopIncident,"eventLoopIncident");export class EventLoopIndex{static{u(this,"EventLoopIndex")}static{l(this,"EventLoopIndex")}constructor(e=()=>{},t=()=>{}){this.onChange=e,this.onSamples=t,this.incidents=new Map,this.loops=new Map,this.recent=[],this.history=new S}ingest(e){const t=v(e.raw),i=s(t.kind)??e.source,d=eventLoopIdentity(t.loop);if(d){const a=this.loops.get(d.id);if(!a||e.timestamp>=a.lastSeen)for(this.loops.set(d.id,{loop:d,lastSeen:e.timestamp,state:t.state==="unresponsive"?"unresponsive":t.state==="unknown"?"unknown":"observed",coverage:s(t.coverage,500)??a?.coverage??(d.runtime==="node"?"Instrumented Node thread":"Browser main thread; supported PerformanceObserver entries"),...a?{meanMs:a.meanMs,maxMs:a.maxMs,p99Ms:a.p99Ms,utilization:a.utilization}:{},...o(t.lagMs)!==void 0?{meanMs:o(t.lagMs),maxMs:o(t.maxMs),p99Ms:o(t.p99Ms),utilization:o(t.utilization)}:{}});this.loops.size>200;)this.loops.delete(this.loops.keys().next().value);if(i==="event-loop-lag"){const I=L(t.samples);I.length&&(this.history.add(d,I),this.onSamples(d,I))}}const r=eventLoopIncident(e);if(r){const a=this.incidents.get(r.id);if(a&&a.observedAt>r.observedAt)return;a&&(r.evidence=a.evidence.slice(),r.evidenceTruncated||=a.evidenceTruncated);for(const I of this.recent)this.attach(r,I);for(this.incidents.set(r.id,r);this.incidents.size>200;)this.incidents.delete(this.incidents.keys().next().value);this.onChange(r);return}if(i.startsWith("event-loop-"))return;const h=s(t.loopId);if(!h)return;const p=v(t.span),m=v(t.request),g=v(t.render),c=o(t.durationMs)??o(p.durationMs)??o(m.durationMs)??o(g.actualDurationMs)??0,f=o(t.startedAt)??o(p.startTime)??o(m.startedAt)??e.timestamp-c,w=o(p.syncDurationMs),b=o(p.syncStartedAt),y=w!==void 0&&w>=50&&b!==void 0,x={loopId:h,eventId:e.id,title:(e.title??i).slice(0,250),kind:i,startAt:y?b:f,endAt:y?b+w:f+c,...e.context?.flowId?{flowId:e.context.flowId}:{},...e.context?.requestId?{requestId:e.context.requestId}:{},relation:y?"executing":"overlap"};this.recent.push(x),this.recent.length>2e3&&this.recent.splice(0,this.recent.length-2e3);for(const a of this.incidents.values())this.attach(a,x)&&this.onChange(a)}attach(e,t){if(t.loopId!==e.loop.id||t.endAt<e.startAt||t.startAt>(e.endAt??e.observedAt)||e.evidence.some(r=>r.eventId===t.eventId))return!1;if(e.evidence.length>=T){const r=!e.evidenceTruncated;return e.evidenceTruncated=!0,r}const{loopId:i,...d}=t;return e.evidence.push(d),!0}statuses(e=Date.now()){return[...this.loops.values()].map(t=>({...t,state:e-t.lastSeen>9e4?"unknown":t.state})).sort((t,i)=>i.lastSeen-t.lastSeen)}list(e){return[...this.incidents.values()].filter(t=>!e||t.flowId===e||t.evidence.some(i=>i.flowId===e)).sort((t,i)=>i.startAt-t.startAt)}}let A=l(()=>[],"readIncidents");export function setFlowLoopIncidents(n){A=n}u(setFlowLoopIncidents,"setFlowLoopIncidents"),l(setFlowLoopIncidents,"setFlowLoopIncidents");export function flowLoopIncidents(n){return A(n)}u(flowLoopIncidents,"flowLoopIncidents"),l(flowLoopIncidents,"flowLoopIncidents");
@@ -74,8 +74,8 @@ export declare function trimFlowEvent(event: BaseEvent, opts?: FlowShapeOptions)
74
74
  outcome?: "returned" | "threw" | undefined;
75
75
  calleeRequestId?: string | undefined;
76
76
  hop?: true | undefined;
77
- source: "ai" | "gateway" | "perf" | "memory" | "db" | "redis" | "queue" | "external" | "nestjs" | "valtio" | "nx" | "vite" | "typescript" | "eslint" | "rollup" | "client-runtime" | "chrome" | "react" | "nextjs" | "visual" | "security" | "lens" | "custom";
78
- category: "ai" | "state" | "network" | "memory" | "db" | "visual" | "security" | "build" | "lint" | "typecheck" | "test" | "runtime" | "render" | "trace" | "backend" | "performance" | "deps" | "cluster" | "agent";
77
+ source: "nx" | "vite" | "typescript" | "eslint" | "rollup" | "client-runtime" | "chrome" | "react" | "valtio" | "nestjs" | "nextjs" | "db" | "redis" | "queue" | "memory" | "visual" | "perf" | "security" | "gateway" | "external" | "ai" | "lens" | "custom";
78
+ category: "db" | "memory" | "visual" | "security" | "ai" | "build" | "lint" | "typecheck" | "test" | "runtime" | "render" | "trace" | "state" | "network" | "backend" | "performance" | "deps" | "cluster" | "agent";
79
79
  severity: "error" | "debug" | "info" | "warning" | "fatal";
80
80
  title: string;
81
81
  kind: string | undefined;
@@ -148,8 +148,8 @@ export declare function buildFlowDetail(rawEvents: BaseEvent[], opts?: FlowShape
148
148
  outcome?: "returned" | "threw" | undefined;
149
149
  calleeRequestId?: string | undefined;
150
150
  hop?: true | undefined;
151
- source: "ai" | "gateway" | "perf" | "memory" | "db" | "redis" | "queue" | "external" | "nestjs" | "valtio" | "nx" | "vite" | "typescript" | "eslint" | "rollup" | "client-runtime" | "chrome" | "react" | "nextjs" | "visual" | "security" | "lens" | "custom";
152
- category: "ai" | "state" | "network" | "memory" | "db" | "visual" | "security" | "build" | "lint" | "typecheck" | "test" | "runtime" | "render" | "trace" | "backend" | "performance" | "deps" | "cluster" | "agent";
151
+ source: "nx" | "vite" | "typescript" | "eslint" | "rollup" | "client-runtime" | "chrome" | "react" | "valtio" | "nestjs" | "nextjs" | "db" | "redis" | "queue" | "memory" | "visual" | "perf" | "security" | "gateway" | "external" | "ai" | "lens" | "custom";
152
+ category: "db" | "memory" | "visual" | "security" | "ai" | "build" | "lint" | "typecheck" | "test" | "runtime" | "render" | "trace" | "state" | "network" | "backend" | "performance" | "deps" | "cluster" | "agent";
153
153
  severity: "error" | "debug" | "info" | "warning" | "fatal";
154
154
  title: string;
155
155
  kind: string | undefined;
package/lib/lens-dir.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * The team's parts are the folders listed here — today each analyzer's instruction folder, the Markdown its project
9
9
  * instructions are kept in (`@lensmcp/ai` files.ts): `.lensmcp/sql-review/`. A new analyzer adds its folder here.
10
+ * And `.lensmcp/ai-names/`: the names people gave the AI calls page's agents and actions, so the team sees the same.
10
11
  *
11
12
  * The .gitignore that keeps both right ignores the folder's children — at any depth, as `.lensmcp/` did — and lets
12
13
  * the team's back in:
package/lib/lens-dir.js CHANGED
@@ -1 +1 @@
1
- "use strict";var n=Object.defineProperty;var i=(e,r)=>n(e,"name",{value:r,configurable:!0});var t=Object.defineProperty,o=i((e,r)=>t(e,"name",{value:r,configurable:!0}),"i");export const LENS_SHARED_DIRS=["sql-review","route-review","flow-review","ui-review","cache-review","queue-review"];export function lensGitignoreLines(e="**/"){return[`${e}.lensmcp/*`,...LENS_SHARED_DIRS.map(r=>`!${e}.lensmcp/${r}/`)]}i(lensGitignoreLines,"lensGitignoreLines"),o(lensGitignoreLines,"lensGitignoreLines");
1
+ "use strict";var n=Object.defineProperty;var i=(e,r)=>n(e,"name",{value:r,configurable:!0});var t=Object.defineProperty,s=i((e,r)=>t(e,"name",{value:r,configurable:!0}),"r");export const LENS_SHARED_DIRS=["sql-review","route-review","flow-review","ui-review","cache-review","queue-review","ai-names"];export function lensGitignoreLines(e="**/"){return[`${e}.lensmcp/*`,...LENS_SHARED_DIRS.map(r=>`!${e}.lensmcp/${r}/`)]}i(lensGitignoreLines,"lensGitignoreLines"),s(lensGitignoreLines,"lensGitignoreLines");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/core",
3
- "version": "1.25.32",
3
+ "version": "1.25.33",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -84,7 +84,7 @@
84
84
  }
85
85
  },
86
86
  "dependencies": {
87
- "@lensmcp/protocol-types": "1.25.32",
87
+ "@lensmcp/protocol-types": "1.25.33",
88
88
  "tslib": "^2.3.0"
89
89
  },
90
90
  "license": "Apache-2.0",