@lensmcp/core 1.25.31 → 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 +2 -0
- package/index.js +1 -1
- package/lib/event-loop-history.d.ts +74 -0
- package/lib/event-loop-history.js +1 -0
- package/lib/event-loop.d.ts +9 -2
- package/lib/event-loop.js +1 -1
- package/lib/flow-shape.d.ts +12 -6
- package/lib/flow-shape.js +1 -1
- package/lib/http-exchange.d.ts +7 -2
- package/lib/http-exchange.js +1 -1
- package/lib/lens-dir.d.ts +1 -0
- package/lib/lens-dir.js +1 -1
- package/lib/service-hop.d.ts +20 -0
- package/lib/service-hop.js +1 -0
- package/package.json +2 -2
package/index.d.ts
CHANGED
|
@@ -15,8 +15,10 @@ export * from './lib/flow-shape.js';
|
|
|
15
15
|
export * from './lib/flow-segments.js';
|
|
16
16
|
export * from './lib/flow-categories.js';
|
|
17
17
|
export * from './lib/headless-flow.js';
|
|
18
|
+
export * from './lib/service-hop.js';
|
|
18
19
|
export * from './lib/flows-config.js';
|
|
19
20
|
export * from './lib/patterns.js';
|
|
20
21
|
export * from './lib/thresholds.js';
|
|
21
22
|
export * from './lib/event-loop.js';
|
|
23
|
+
export * from './lib/event-loop-history.js';
|
|
22
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/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)}}}
|
package/lib/event-loop.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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");
|
package/lib/flow-shape.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare function areaOf(event: {
|
|
|
10
10
|
category: string;
|
|
11
11
|
title: string;
|
|
12
12
|
kind?: string | undefined;
|
|
13
|
+
/** A call out's host: one under `.localhost` is a hop to the cluster's own services (service-hop.ts). */
|
|
14
|
+
host?: string | undefined;
|
|
13
15
|
}): FlowArea;
|
|
14
16
|
/** One chart-ready node per flow event: area-typed, titled, with start/duration/payload. */
|
|
15
17
|
export interface FlowShapeOptions {
|
|
@@ -70,9 +72,11 @@ export declare function trimFlowEvent(event: BaseEvent, opts?: FlowShapeOptions)
|
|
|
70
72
|
captured?: string[] | undefined;
|
|
71
73
|
error?: string | undefined;
|
|
72
74
|
outcome?: "returned" | "threw" | undefined;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
calleeRequestId?: string | undefined;
|
|
76
|
+
hop?: true | undefined;
|
|
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
|
+
severity: "error" | "debug" | "info" | "warning" | "fatal";
|
|
76
80
|
title: string;
|
|
77
81
|
kind: string | undefined;
|
|
78
82
|
area: FlowArea;
|
|
@@ -142,9 +146,11 @@ export declare function buildFlowDetail(rawEvents: BaseEvent[], opts?: FlowShape
|
|
|
142
146
|
captured?: string[] | undefined;
|
|
143
147
|
error?: string | undefined;
|
|
144
148
|
outcome?: "returned" | "threw" | undefined;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
calleeRequestId?: string | undefined;
|
|
150
|
+
hop?: true | undefined;
|
|
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
|
+
severity: "error" | "debug" | "info" | "warning" | "fatal";
|
|
148
154
|
title: string;
|
|
149
155
|
kind: string | undefined;
|
|
150
156
|
area: FlowArea;
|
package/lib/flow-shape.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var qe=Object.defineProperty;var C=(i,m)=>qe(i,"name",{value:m,configurable:!0});var we=Object.defineProperty,v=C((i,m)=>we(i,"name",{value:m,configurable:!0}),"$");import{redactDeep as Me,redactText as oe,redactUrlParams as Ie}from"./redact.js";import{templatisePath as pe}from"./route-template.js";import{SQL_ACCOUNT_KIND as be,sqlAccountingOf as xe}from"./flow-segments.js";import{boundedClone as je}from"@lensmcp/protocol-types";export function areaOf(i){const{source:m,category:o,title:q,kind:c}=i;return m==="ai"?"ai":m==="perf"&&c==="event-loop-stall"?"services":c?.startsWith("memory")||c==="db-query"?"data":m==="gateway"||c==="gateway-request"?"gateway":c==="queue-process"||c==="queue-active"||c==="queue-completed"||c==="queue-failed"?"services":m==="db"||m==="redis"||m==="queue"||c==="redis-op"||c?.startsWith("queue-")||m==="fs"||c==="fs-ops"?"data":m==="exec"||c==="exec"?"services":m==="external"||c==="egress"?"network":m==="nestjs"||m==="nest"?/(?:Repository|Repo|Dao|Store|EntityManager|DataSource|QueryRunner|OrmClient)\./.test(q)?"data":"services":m==="valtio"?"state":o==="network"?"network":"browser"}C(areaOf,"areaOf"),v(areaOf,"areaOf");const fe=240,Se=new Set(["systemPrompt","prompt","prompts","messages","completion","completions","input","output","draft","text","content","result","arguments","params"]),Be=new Set(["tenantId","caller","userId","accountId","email","subject"]),Ae=8;function me(i){const m=ee(i,0);return je(Me({redacted:!0,...m}),{rawByteLimit:16*1024}).value}C(me,"vt"),v(me,"aiProjection");function ee(i,m){if(typeof i=="string")return he(i);if(i===null||typeof i!="object")return i;if(m>=Ae)return;if(Array.isArray(i))return i.slice(0,32).map(q=>ee(q,m+1));const o={};for(const[q,c]of Object.entries(i))if(!Be.has(q)){if(Se.has(q)){typeof c=="string"?o[`${q}Chars`]=c.length:Array.isArray(c)?o[`${q}Count`]=c.length:c!=null&&(o[`${q}Present`]=!0);continue}o[q]=ee(c,m+1)}return o}C(ee,"rt"),v(ee,"projectAi");function he(i){return i.length<=fe?oe(i):`${oe(i.slice(0,fe))}\u2026`}C(he,"jt"),v(he,"clipText");export function httpFacts(i,m){const o=v(M=>typeof M=="string"&&M.length>0?M:void 0,"str"),q=typeof m.status=="number"?m.status:void 0,c=v(M=>Ie(M.split("?")[0]??M)||"/","pathOf"),D=v((M,y,x={})=>({...M?{method:M.toUpperCase()}:{},...y?{rawPath:y,route:pe(y)}:{},...q!==void 0?{status:q}:{},...x}),"facts");if(i==="gateway-request"){const M=o(m.url);return D(o(m.method),M?c(M):void 0,{...o(m.host)?{host:o(m.host)}:{},...o(m.caller)?{caller:o(m.caller)}:{}})}if(i==="egress"){const M=o(m.path),y=o(m.exchangeId);return D(o(m.method)??"GET",M?c(M):void 0,{...o(m.host)?{host:o(m.host)}:{},...y?{exchangeId:y}:{}})}if(i==="fetch-start"||i==="fetch-done"||i==="fetch-error"){const M=o(m.href)??o(m.url);let y,x;if(M)try{const p=new URL(M,"http://relative.invalid");y=c(p.pathname),p.hostname!=="relative.invalid"&&(x=p.hostname)}catch{}return D(o(m.method)??"GET",y,x?{host:x}:{})}if(i==="ai-call")return{...q!==void 0?{status:q}:{},...m.stream===!0?{streamed:!0}:{}};const g=m.request;if(g&&typeof g=="object"){const M=g,y=o(M.route),x=o(M.method);if(!y&&!x)return{};const p=typeof M.status=="number"?M.status:void 0,R=y?c(y):void 0;return{...x?{method:x.toUpperCase()}:{},...R?{rawPath:R,route:R.includes(":")?R:pe(R)}:{},...p!==void 0?{status:p}:{}}}return{}}C(httpFacts,"httpFacts"),v(httpFacts,"httpFacts");export function trimFlowEvent(i,m={}){const o=i.raw??{},q=o.render??{},c=o.span??{},D=o.request??{},g=v(d=>typeof d=="number"?d:void 0,"num"),M=o.loop??{},y=o.effect??{},x=g(o.durationMs)??g(c.durationMs)??g(D.durationMs)??g(M.durationMs)??g(q.actualDurationMs)??g(y.durationMs),p=typeof o.kind=="string"?o.kind:void 0,R=p==="fetch-done"||p==="fetch-error",_=g(q.commitTime),z=p==="render"&&_!==void 0&&Math.abs(_-i.timestamp)<6e4,te=R?i.timestamp:z?_:g(o.startedAt)??g(c.startTime)??g(D.startedAt)??g(M.startedAt)??(x!==void 0?i.timestamp-x:i.timestamp);let j;if(typeof o.query=="string"){const d=Array.isArray(o.params)?` [${o.params.join(", ")}]`:"",f=typeof o.result=="string"?` \u2192 ${o.result}`:"";j=o.query+d+f}else if(typeof o.command=="string"){const d=typeof o.value=="string"?` \u2192 ${o.value}`:o.hit===!1?" \u2192 (miss)":"";j=o.command+d}else if(p==="state-update"){const d=o.update??{};let f="";try{f=JSON.stringify(d.preview)}catch{}j=`${d.storeId??""}.${d.path??""} = ${f}`}else if(p==="fs-ops"&&Array.isArray(o.paths))j=o.paths.slice(0,8).map(d=>{const f=d??{},B=typeof f.n=="number"&&f.n>1?` \xD7${f.n}`:"",T=typeof f.bytes=="number"&&f.bytes>0?` (${f.bytes>=1024?`${(f.bytes/1024).toFixed(1)} KB`:`${f.bytes} B`})`:"";return`${String(f.op??"?")} ${String(f.path??"?")}${B}${T}`}).join(" \xB7 ");else if(p==="exec"&&typeof o.cmd=="string")j=`${o.cmd} \u2192 ${o.code??o.signal??o.error??"?"}`;else if(p==="queue-completed"&&typeof o.returned=="string")j=`returned ${o.returned}`;else if(p==="queue-enqueued")j=`${o.job??"job"}#${o.jobId??"?"} \u2192 ${o.queue??""}${typeof o.data=="string"?" "+o.data:""}`;else if(p==="log"){const d=D.error??{},f=typeof d.stack=="string"?d.stack:typeof o.text=="string"?o.text:void 0;f&&f!==i.title&&(j=f)}let P,G,H,Q;if(p==="span"){const d=c.outcome;if(P=d==="threw"||d==="returned"?d:c.status==="error"?"threw":c.status==="ok"?"returned":void 0,P==="threw"){const T=c.error??{},L=typeof T.name=="string"?T.name:void 0,K=typeof T.message=="string"?T.message:typeof i.message=="string"?i.message:void 0;H=L,G=oe([L,K].filter(Boolean).join(": "))||void 0}const f=typeof c.args=="string"?c.args:void 0,B=typeof c.result=="string"?c.result:void 0;if(f!==void 0||B!==void 0){Q=[...f!==void 0?["args"]:[],...B!==void 0?["result"]:[]];const T=v(L=>L.length>ge?`${L.slice(0,ge)}\u2026`:L,"short");j=[f!==void 0?`args ${T(f)}`:"",B!==void 0?`returned ${T(B)}`:""].filter(Boolean).join(" \u2192 ")}}let U;if(p==="gateway-request")U=`${o.host??""}${String(o.url??"").split("?")[0]}`;else if(p==="fetch-start"||p==="fetch-done"||p==="fetch-error")try{const d=new URL(String(o.href??o.url??""));U=`${d.hostname}${d.pathname}`}catch{}let W;if(p==="gateway-request"&&Array.isArray(o.steps))W=o.steps.map(d=>{const f=[String(d.step)];return d.project&&f.push(String(d.project)),d.prefix&&f.push(String(d.prefix)),d.mode&&f.push(String(d.mode)+(d.ok===!1?" \u2717":d.ok===!0?" \u2713":"")),d.caller&&f.push("by "+String(d.caller)),d.pod&&f.push(String(d.pod)),typeof d.atMs=="number"&&f.push(`@${d.atMs}ms`),f.join(" ")}),j=W.map(d=>d.replace(/ @\d+ms$/,"")).join(" \u2192 ");else if(p==="egress")j=`${o.method??"GET"} ${o.host??""} \u2192 ${o.status??"?"}`;else if(i.source==="ai"){if(p==="ai-call"){const d=o.usage??{},f=d.promptTokens!==void 0||d.completionTokens!==void 0?` \xB7 ${g(d.promptTokens)??"?"}\u2192${g(d.completionTokens)??"?"} tok`:"",B=o.status!==void 0?` \xB7 ${String(o.status)}`:o.error?" \xB7 failed":"",T=g(o.toolCallCount)?` \xB7 ${g(o.toolCallCount)} tool call${g(o.toolCallCount)===1?"":"s"}`:"",L=g(o.durationMs)!==void 0?` \xB7 ${(g(o.durationMs)/1e3).toFixed(1)}s`:"";j=`${o.provider??""}/${o.model??"?"}${f}${B}${T}${L}${o.stream===!0?" \xB7 stream":""}`}else if(p==="ai-round"){const d=o.model??{},f=o.usage??{},B=Array.isArray(o.toolCalls)?o.toolCalls:[],T=`${g(f.promptTokens)??"?"}\u2192${g(f.completionTokens)??"?"} tok`,L=B.length?` \xB7 ${B.length} tool call${B.length===1?"":"s"}`:"",K=g(o.durationMs)!==void 0?` \xB7 ${(g(o.durationMs)/1e3).toFixed(1)}s`:"";j=`${d.provider??""}/${d.model??""} \xB7 ${T}${L}${K}`}else if(p==="ai-turn-start"){const d=Array.isArray(o.tools)?o.tools:[];j=`skill ${o.skill??""} \xB7 ${o.output??""}${d.length?" \xB7 tools: "+d.join(", "):""}`}else if(p==="ai-tool-call"){const d=o.call??{},f=o.result??{},B=g(o.durationMs)!==void 0?` \xB7 ${(g(o.durationMs)/1e3).toFixed(1)}s`:"";j=`${d.name??"tool"} ${f.error!=null?"\u2717 "+String(f.error):"\u2713"}${B}`}else if(p==="ai-turn-end"){const d=o.usage??{},f=g(o.rounds)!==void 0?` \xB7 ${g(o.rounds)} round${g(o.rounds)===1?"":"s"}`:"",B=g(o.durationMs)!==void 0?` \xB7 ${(g(o.durationMs)/1e3).toFixed(1)}s`:"";j=`${o.ok===!1?"FAILED":"ok"} \xB7 ${g(d.promptTokens)??"?"}+${g(d.completionTokens)??"?"} tok${f}${B}`}}let F;p==="db-query"&&typeof o.query=="string"?F=o.query:p==="redis-op"?F=`${o.op??""} ${String(o.key??"").replace(/\d+/g,"#")}`:p==="egress"?F=`${o.method??"GET"} ${o.host??""}`:p==="ai-call"?F=`ai ${o.provider??""} ${o.model??""} ${o.api??""}`:p==="queue-enqueued"?F=`enqueue ${o.queue??""}`:p==="span"&&typeof c.name=="string"?F=c.name:p==="memory-mutation"&&(F=i.title);const N=i.context??{},w=q.commitId??y.commitId??o.commitId,V=Array.isArray(q.why)?q.why.map(d=>typeof d?.type=="string"?d.type:void 0).filter(d=>!!d):[];return{id:i.id,t:i.timestamp,start:te,...F?{sig:F}:{},...U?{urlKey:U}:{},...httpFacts(p,o),...(p==="db-query"||p==="redis-op")&&typeof o.valuesId=="string"?{valuesId:o.valuesId}:{},...typeof o.queue=="string"?{queue:o.queue}:{},...o.jobId!=null?{jobId:String(o.jobId)}:{},source:i.source,category:i.category,severity:i.severity,title:P==="threw"&&typeof c.name=="string"?`${c.name} \u2014 threw${H?` ${H}`:""}${x!==void 0?` after ${x}ms`:""}`:i.title,kind:p,area:areaOf({source:i.source,category:i.category,title:i.title,kind:p}),...P?{outcome:P}:{},...G?{error:G}:{},...Q?{captured:Q}:{},...typeof c.role=="string"?{role:c.role}:{},...typeof c.spanId=="string"?{spanId:c.spanId}:{},...typeof c.parentSpanId=="string"?{parentSpanId:c.parentSpanId}:{},...p==="log"&&typeof o.level=="string"?{level:o.level}:{},...p==="log"&&typeof o.logger=="string"?{logger:o.logger}:{},...p==="log"&&typeof o.stream=="string"?{stream:o.stream}:{},...typeof N.requestId=="string"?{requestId:N.requestId}:{},...typeof N.parentRequestId=="string"?{parentRequestId:N.parentRequestId}:{},...typeof w=="string"?{commitId:w}:{},...typeof q.componentName=="string"?{component:q.componentName}:{},...typeof q.phase=="string"?{phase:q.phase}:{},...V.length?{why:V}:{},...p==="effect-run"&&typeof y.hookLogicalId=="string"?{hook:y.hookLogicalId}:{},...p==="effect-run"&&y.cleanupRan===!0?{cleanup:!0}:{},...p==="render-commit-truncated"?{renderTotal:g(o.total),renderPublished:g(o.published),...g(o.walkStoppedAt)!==void 0?{renderWalkStoppedAt:g(o.walkStoppedAt)}:{}}:{},...typeof N.projectName=="string"?{project:N.projectName}:typeof o.project=="string"?{project:o.project}:{},...x!==void 0?{durationMs:x}:{},...R&&g(o.startedAt)!==void 0?{fetchStartedAt:g(o.startedAt)}:{},...i.source==="ai"?{ai:m.includeRaw?o:me(o)}:{},...j?{detail:j}:{},...W?{steps:W}:{}}}C(trimFlowEvent,"trimFlowEvent"),v(trimFlowEvent,"trimFlowEvent");const ge=300;export const OUTLIVED_PARENT_MIN_MS=250;const Te=new Set(["egress","db-query","redis-op","ai-call","span","gateway-request","server-request"]),$e=1;function ye(i){return i==="flow-origin"?0:i?.endsWith("-start")?1:i==="gateway-request"?2:i==="server-request"?3:i==="span"?4:i==="render"||i==="render-commit-truncated"?6:i==="effect-run"?7:5}C(ye,"Rt"),v(ye,"kindRank");function J(i){return i.kind==="span"&&i.role==="controller"?3.5:i.kind==="queue-process"?3:ye(i.kind)}C(J,"Z"),v(J,"causalRank");export function buildFlowDetail(i,m={}){const o=xe(i),q=i.slice().filter(e=>{const r=e.raw??{},t=typeof r.kind=="string"?r.kind:"";if(t==="subscription-added"||t==="subscription-removed"||t==="hook-run"||t==="store-created"||t==="singleton-instance"||t===be)return!1;const n=r.span;return!(typeof n?.name=="string"&&n.name.startsWith("nest.request "))}).map(e=>trimFlowEvent(e,m)),c=new Set;for(let e=q.length-1;e>=0;e--)c.has(q[e].id)?q.splice(e,1):c.add(q[e].id);const D=new Map;for(const e of q)(e.kind==="gateway-request"||e.kind==="queue-process")&&e.requestId&&!D.has(e.requestId)&&D.set(e.requestId,e.start);const g=v(e=>e.source!=="react"&&e.source!=="valtio"&&e.requestId&&D.has(e.requestId)?D.get(e.requestId):e.start,"anchorOf");q.sort((e,r)=>g(e)-g(r)||e.start-r.start||J(e)-J(r)||(r.durationMs??0)-(e.durationMs??0)||e.t-r.t);const M=v(e=>(e.kind==="span"||e.kind==="server-request"||e.kind==="gateway-request"||e.kind==="queue-process")&&(e.durationMs??0)>0,"spanLike"),y=q;for(const e of y)e.group=e.source==="react"||e.source==="valtio"||e.source==="client-runtime"||e.source==="visual"?"browser":"server";const x=new Map;for(const e of y)if((e.kind==="gateway-request"||e.kind==="queue-process")&&e.requestId&&e.project){const r=x.get(e.requestId)??[];r.push({project:e.project,start:e.start,end:e.start+(e.durationMs??0)}),x.set(e.requestId,r)}const p=v((e,r)=>{const t=x.get(e);if(t?.length)return t.every(n=>n.project===t[0].project)?t[0].project:t.filter(n=>n.start<=r+1&&n.end>=r-1).sort((n,s)=>s.start-n.start)[0]?.project},"projectOf");for(const e of y)if(!e.project&&e.source==="nestjs"&&e.requestId){const r=p(e.requestId,e.start);r&&(e.project=r)}const R=new Map;for(const e of y)e.spanId&&R.set(e.spanId,e);const _=v((e,r)=>{let t=e.parentSpanId?R.get(e.parentSpanId):void 0;for(let n=0;t&&n<64;n++){if(t===r)return!0;t=t.parentSpanId?R.get(t.parentSpanId):void 0}return!1},"inside");for(const e of y){const r=e.parentSpanId?R.get(e.parentSpanId):void 0;if(r&&r!==e&&r.group===e.group&&r.requestId===e.requestId){e.parent=r.id;continue}let t;const n=e.requestId;for(const s of y){if(s===e||!M(s)||s.group!==e.group||s.requestId!==n)continue;const a=s.project,$=e.project;if(s.kind!=="gateway-request"&&e.kind!=="gateway-request"&&a!==void 0&&$!==void 0&&a!==$)continue;const k=s.durationMs??0,u=e.durationMs??0,l=Math.abs(s.start-e.start)<=$e,h=J(s),I=J(e),A=s.start<=e.start;if(!(s.start+k>e.start&&(A||l&&h<I)&&(!l||(h!==I?h<I:A&&k>u))))continue;const b=t?J(t):-1,O=t?Math.abs(s.start-t.start)<=$e:!1,E=t&&O&&h===b?_(s,t)?1:_(t,s)?-1:0:0;(!t||!O&&s.start>t.start||O&&h>b||E>0||E===0&&O&&h===b&&(s.durationMs??0)<(t.durationMs??0))&&(t=s)}t&&(e.parent=t.id)}for(const e of y)delete e.spanId,delete e.parentSpanId;{const e=new Set(y.map(a=>a.id)),r=new Set,t=new Map,n=[],s=v(a=>{const $=[a];for(;$.length;){const k=$.pop();n.push(k),r.add(k.id);const u=t.get(k.id);if(u){t.delete(k.id);for(let l=u.length-1;l>=0;l--)$.push(u[l])}}},"place");for(const a of y)if(a.parent&&a.parent!==a.id&&e.has(a.parent)&&!r.has(a.parent)){const $=t.get(a.parent)??[];$.push(a),t.set(a.parent,$)}else s(a);for(const a of y)r.has(a.id)||(n.push(a),r.add(a.id));for(let a=0;a<n.length;a++)y[a]=n[a]}const z=new Map;for(const e of y){if(!e.requestId)continue;const r=z.get(e.requestId);r?r.push(e):z.set(e.requestId,[e])}const te=new Map(y.map(e=>[e.id,e])),j=v((e,r)=>{const t=(z.get(e)??[]).filter(n=>M(n)&&n.start<=r&&n.start+(n.durationMs??0)>=r);if(t.length!==0)return t.sort((n,s)=>s.start-n.start||(n.durationMs??0)-(s.durationMs??0)),t[0]},"callerNodeFor");for(const e of y){if(!e.parentRequestId||e.kind!=="server-request"&&e.kind!=="gateway-request")continue;const r=e.parent?te.get(e.parent):void 0;if(r&&r.requestId===e.requestId)continue;const t=j(e.parentRequestId,e.start);t&&t!==e&&t.start<=e.start&&(e.parent=t.id)}const P=y,G=new Map;for(const e of P)e.kind==="gateway-request"&&e.requestId&&!G.has(e.requestId)&&G.set(e.requestId,e);for(const e of P)if(e.group==="server"&&!e.parent&&e.requestId&&e.kind!=="gateway-request"){const r=G.get(e.requestId);r&&r!==e&&r.start<=e.start&&(e.parent=r.id)}const H=new Set,Q=new Set,U=[];for(const e of P){if(e.kind!=="gateway-request")continue;if(!e.requestId){U.push(e);continue}const r=z.get(e.requestId)??[];if(r.filter(s=>s.kind==="gateway-request").sort((s,a)=>s.start-a.start)[0]!==e)continue;const t=r.find(s=>s.kind==="fetch-start"),n=r.find(s=>s.kind==="fetch-done"||s.kind==="fetch-error");if(!t&&!n){U.push(e);continue}t&&(e.fetchStartId=t.id,H.add(t.id)),n&&(e.fetchDoneId=n.id,Q.add(n.id))}const W=new Map;for(const e of P){if(!e.urlKey)continue;const r=W.get(e.urlKey)??{gws:[],starts:[],dones:[]};e.kind==="gateway-request"?U.includes(e)&&r.gws.push(e):e.kind==="fetch-start"?H.has(e.id)||r.starts.push(e):(e.kind==="fetch-done"||e.kind==="fetch-error")&&(Q.has(e.id)||r.dones.push(e)),W.set(e.urlKey,r)}for(const{gws:e,starts:r,dones:t}of W.values()){e.sort((s,a)=>s.start-a.start),r.sort((s,a)=>s.start-a.start);const n=new Set;e.forEach((s,a)=>{const $=r[a];$&&(s.fetchStartId=$.id);const k=$?$.t:s.start;let u,l=1/0;for(const h of t){if(n.has(h.id))continue;const I=h.fetchStartedAt??h.start-(h.durationMs??0),A=Math.abs(I-k);A<l&&(l=A,u=h)}u&&(s.fetchDoneId=u.id,n.add(u.id))})}const F=new Map,N=new Map;for(const e of P)!e.queue||!e.jobId||(e.kind==="queue-enqueued"&&F.set(`${e.queue}#${e.jobId}`,e),e.kind==="queue-process"&&N.set(`${e.queue}#${e.jobId}`,e));for(const e of P){if(!e.queue||!e.jobId)continue;const r=`${e.queue}#${e.jobId}`;if(e.kind==="queue-process"&&!e.parent){const t=F.get(r);t&&t!==e&&t.start<=e.start&&(e.parent=t.id)}else if(e.kind==="queue-active"||e.kind==="queue-completed"||e.kind==="queue-failed"){const t=N.get(r);t&&t!==e&&t.start<=e.start&&(e.parent=t.id)}}{const e=new Map(y.map(r=>[r.id,r]));for(const r of y){if(!r.parent||!Te.has(r.kind??"")||r.durationMs===void 0)continue;const t=e.get(r.parent);if(!t||t.kind!=="span"||!((t.durationMs??0)>0))continue;const n=Math.round(r.start+r.durationMs-(t.start+t.durationMs));if(n<OUTLIVED_PARENT_MIN_MS)continue;r.outlivedParentMs=n;const s=t.leftRunning??{count:0,longestMs:0};t.leftRunning={count:s.count+1,longestMs:Math.max(s.longestMs,n)}}for(const r of y){const t=[];r.leftRunning&&t.push(`ended before ${r.leftRunning.count===1?"a call":`${r.leftRunning.count} calls`} it started had finished \u2014 ${r.leftRunning.count===1?"it":"the longest"} ran on ${r.leftRunning.longestMs}ms (a timeout that gave up, or work not awaited)`),r.outlivedParentMs!==void 0&&t.push(`ran on ${r.outlivedParentMs}ms after the span that started it had ended`),t.length>0&&(r.detail=[r.detail,...t].filter(Boolean).join(" \xB7 "))}}let w=y;{const e=new Map;for(const t of w)t.kind==="fetch-start"&&e.set(t.id,t);const r=new Map;for(const t of w){if(t.kind!=="fetch-done"&&t.kind!=="fetch-error")continue;const n=[...e.values()].find($=>$.requestId&&$.requestId===t.requestId)?.id,s=n?e.get(n):void 0;if(!s)continue;const a=t.fetchStartedAt??s.start;s.title=t.title,s.severity=t.severity,s.durationMs=t.durationMs??Math.max(0,t.t-a),s.kind="fetch",t.detail&&!s.detail&&(s.detail=t.detail),r.set(t.id,s.id)}if(r.size>0){w=w.filter(t=>!r.has(t.id));for(const t of w)for(t.fetchDoneId&&r.has(t.fetchDoneId)&&(t.fetchDoneId=r.get(t.fetchDoneId));t.parent&&r.has(t.parent);)t.parent=r.get(t.parent)}}const V=v(e=>!!e.commitId&&(e.kind==="render"||e.kind==="effect-run"||e.kind==="render-commit-truncated"),"commitMember"),d=v(e=>[...new Set(e)],"uniq"),f=v(e=>d(e.map(r=>r.component).filter(r=>!!r)),"named"),B=v(e=>e.length<=2?e.join(" + "):`${e[0]} + ${e.length-1} more`,"nameList"),T=v(e=>{const r=new Map;for(const t of e)t.component&&r.set(t.component,(r.get(t.component)??0)+1);return[...r].map(([t,n])=>n>1?`${t} \xD7${n}`:t).join(", ")},"tally"),L=v(e=>{if(!e)return"effect";const r=/([^/\\]+?):(\d+)(?::\d+)?$/.exec(e);return r?`${r[1]}:${r[2]}`:e},"shortHook"),K=v(e=>`${L(e.hook)}${e.cleanup?" (cleanup)":""}`,"hookLabel"),ne=v(e=>{const r=Math.min(...e.map(n=>n.start)),t=Math.max(...e.map(n=>n.start+(n.durationMs??0)));return{start:r,durationMs:t-r}},"extent"),se=v(e=>(delete e.component,delete e.why,delete e.phase,delete e.hook,delete e.cleanup,delete e.renderTotal,delete e.renderPublished,e),"asGroup");function ae(e){const r=e[0],t=e.filter(S=>S.kind==="render"),n=e.filter(S=>S.kind==="effect-run"),s=e.find(S=>S.kind==="render-commit-truncated"),a=[...t].sort((S,Y)=>(Y.durationMs??0)-(S.durationMs??0)),$=a.filter(S=>(S.why??[]).some(Y=>Y!=="parent-render")),k=d($.flatMap(S=>(S.why??[]).filter(Y=>Y!=="parent-render"))),u=f($),l=t.filter(S=>S.phase==="mount"),h=t.filter(S=>S.phase!=="mount"&&!$.includes(S)),I=a[0]?.component??"view",A=t.length>0&&l.length===t.length,b=k.length?k.join(" + "):void 0,O=A?"mount":"update",E=u.length?`view update \xB7 ${B(u)} \u2190 ${b}`:A?`view mount \xB7 ${I}`:`view update \xB7 ${I}`,re=s?.renderTotal??t.length,ke=[l.length?`mounted ${T(l)}`:"",h.length?`re-rendered ${T(h)}`:"",!u.length&&!A&&t.length?"trigger not recorded \u2014 every captured render came from its parent":"",s?.renderWalkStoppedAt!==void 0?`at least ${re} renders (the walk stopped at ${s.renderWalkStoppedAt} fibers), ${s.renderPublished??t.length} recorded`:s?`${re} renders, ${s.renderPublished??t.length} recorded (per-commit cap)`:`${t.length} render${t.length===1?"":"s"}`,n.length?`${n.length} effect${n.length===1?"":"s"}: ${n.map(K).join(", ")}`:""].filter(Boolean).join(" \xB7 "),ce=f(t);return se({...r,...ne(e),kind:"view-update",title:E,detail:ke,commitId:r.commitId,viewPhase:O,...b?{cause:b}:{},causeComponents:u,components:ce,mounted:f(l),renderCount:re,...s?.renderWalkStoppedAt!==void 0?{renderCountAtLeast:!0}:{},recordedRenders:t.length,effects:n.length,effectHooks:n.map(K),memberIds:e.map(S=>S.id),sig:`commit:${O}:${b??"-"}:${u.join(",")}:${ce.slice().sort().join(",")}`,severity:e.some(S=>S.severity==="error")?"error":r.severity})}C(ae,"pt"),v(ae,"viewUpdateNode");function ie(e){const r=e[0],t=e.map(K);return se({...r,...ne(e),kind:"commit-effects",title:e.length===1?`effect \xB7 ${t[0]}`:`effects \xB7 ${t[0]} + ${e.length-1} more`,detail:t.join(", "),commitId:r.commitId,effects:e.length,effectHooks:t,memberIds:e.map(n=>n.id),sig:`effects:${t.slice().sort().join(",")}`})}C(ie,"mt"),v(ie,"effectsNode");const ve=v(e=>d(e.map(r=>r==="state-update"?"state update":r==="fetch-start"?"request":r==="flow-origin"?"interaction":"response")).join(" + "),"describeInputs");function de(e,r){const t=e[0],n=e[e.length-1],s=e.length,a=t.detail,$=Math.min(...e.map(h=>h.start)),k=Math.max(...e.map(h=>h.start+(h.durationMs??0))),u=Math.round(e.reduce((h,I)=>h+(I.durationMs??0),0)/s*10)/10,l=Math.round((k-$)*10)/10;t.count=s,t.avgMs=u,t.wallMs=l,t.durationMs=k-$,t.items=e.slice(0,12).map(h=>({start:h.start-$,...h.durationMs!=null?{durationMs:h.durationMs}:{}})),t.memberIds=e.flatMap(h=>h.memberIds??[h.id]),t.title=`\xD7${s} ${t.title}`,e.some(h=>h.severity==="error")&&(t.severity="error"),t.kind==="view-update"?t.detail=[`${s} commits, ${r.length?`one per ${ve(r)}`:"back to back"}`,`avg ${u}ms \xB7 wall ${l}ms`,a??""].filter(Boolean).join(" \xB7 "):t.kind==="state-update"&&a&&n.detail?t.detail=`${s} updates in a row \xB7 wall ${l}ms \xB7 first ${a} \xB7 last ${n.detail}`:t.detail=[`${s} in a row \xB7 wall ${l}ms`,a??""].filter(Boolean).join(" \xB7 ")}C(de,"ht"),v(de,"collapseRepeat");const Z=[],X=new Map,ue=new Set;for(let e=0;e<w.length;e++){if(ue.has(e))continue;const r=w[e];if(!V(r)){Z.push(r);continue}const t=[e];for(let a=e+1;a<w.length;a++){const $=w[a];if($.group==="browser"){if(!V($)||$.commitId!==r.commitId)break;t.push(a)}}for(const a of t)ue.add(a);const n=t.map(a=>w[a]),s=n.some(a=>a.kind!=="effect-run")?ae(n):ie(n);for(const a of n)a.id!==s.id&&X.set(a.id,s.id);Z.push(s)}if(X.size>0||Z.some(e=>e.kind==="view-update"||e.kind==="commit-effects")){w=Z;for(const e of w)for(;e.parent&&X.has(e.parent);)e.parent=X.get(e.parent)}{const e=new Map;for(const r of w)r.kind==="view-update"&&r.commitId&&!e.has(r.commitId)&&e.set(r.commitId,r);for(const r of w){if(r.kind!=="commit-effects"||r.parent||!r.commitId)continue;const t=e.get(r.commitId);t&&t.start<=r.start&&(r.parent=t.id)}}{const e=new Set(["state-update","fetch","fetch-start","fetch-done","fetch-error","flow-origin"]),r=w.map((u,l)=>u.group==="browser"?l:-1).filter(u=>u>=0),t=v(u=>`${u.kind}|${u.sig??u.title}`,"keyOf"),n=r.map(u=>t(w[u])),s=v((u,l,h)=>{for(let I=0;I<h;I++)if(n[u+I]!==n[l+I])return!1;return!0},"sameBlock"),a=v(u=>n[u].split("|")[0],"kindAt"),$=v(u=>{let l=0,h=1;for(let I=1;I<=4&&u+I<=n.length;I++){let A=1;for(;u+(A+1)*I<=n.length&&s(u,u+A*I,I);)A+=1;A>=3&&I*A>l*h&&(l=I,h=A)}return{k:l,r:h}},"bestBlockAt"),k=new Map;for(let u=0;u<n.length;){const{k:l,r:h}=$(u);if(l===0){u+=1;continue}const I=[...Array(l).keys()].find(b=>e.has(a(u+b)));if(I!==void 0&&I>0){const b=$(u+I);if(b.k===l&&b.r>=3&&b.r>=h-1){u+=I;continue}}const A=n.slice(u,u+l).map(b=>b.split("|")[0]).filter(b=>e.has(b));for(let b=0;b<l;b++){const O=[];for(let E=0;E<h;E++)O.push(w[r[u+b+E*l]]);de(O,A);for(const E of O.slice(1))k.set(E.id,O[0].id)}u+=l*h}if(k.size>0){w=w.filter(u=>!k.has(u.id));for(const u of w)for(;u.parent&&k.has(u.parent);)u.parent=k.get(u.parent)}}{const e=new Map(w.map(r=>[r.id,r]));for(const r of w){const t=new Set([r.id]);let n=r;for(;n?.parent;){if(t.has(n.parent)){n.parent=void 0;break}t.add(n.parent),n=e.get(n.parent)}}}const le=v(e=>{const r=new Map;for(const n of w){if(!n.sig||M(n)!==e||n.kind==="server-request"||n.kind==="gateway-request"||n.group==="browser")continue;const s=`${n.parent??"root"}|${n.kind}|${n.sig}`,a=r.get(s);a?a.push(n):r.set(s,[n])}const t=new Map;for(const n of r.values()){if(n.length<3)continue;n.sort((l,h)=>l.start-h.start);const s=n[0];let a=!1;for(let l=0;l+1<n.length;l++)if(n[l+1].start<n[l].start+(n[l].durationMs??0)){a=!0;break}const $=s.start,k=Math.max(...n.map(l=>l.start+(l.durationMs??0))),u=n.reduce((l,h)=>l+(h.durationMs??0),0);s.count=n.length,s.parallel=a,s.avgMs=Math.round(u/n.length*10)/10,s.wallMs=k-$,s.durationMs=k-$,s.title=`\xD7${n.length} ${s.title.replace(/\s*\((?:\d+ rows?, )?\d+ms\)$/,"")}`,s.detail=`${s.sig} \xD7${n.length} ${a?"\u2016 parallel":"\u21BB sequential loop"} \xB7 avg ${s.avgMs}ms \xB7 wall ${s.wallMs}ms`,s.items=n.slice(0,12).map(l=>({start:l.start-$,...l.durationMs!=null?{durationMs:l.durationMs}:{}})),s.memberIds=n.flatMap(l=>l.memberIds??[l.id]),n.some(l=>l.severity==="error")&&(s.severity="error");for(const l of n.slice(1))t.set(l.id,s.id)}if(t.size!==0){w=w.filter(n=>!t.has(n.id));for(const n of w)for(;n.parent&&t.has(n.parent);)n.parent=t.get(n.parent)}},"aggregatePass");le(!0),le(!1);for(const e of w){if(!M(e))continue;const r=e.durationMs??0;if(r<40)continue;const t=w.filter(k=>k.parent===e.id&&(k.durationMs??0)>0).map(k=>[k.start,k.start+(k.durationMs??0)]).sort((k,u)=>k[0]-u[0]);let n=0,s=-1,a=-1;for(const[k,u]of t)k>a?(a>s&&(n+=a-s),s=k,a=u):u>a&&(a=u);a>s&&(n+=a-s);const $=Math.max(0,r-n);($>=50||$/r>=.7&&r>=40)&&(e.selfMs=$,e.detail=`\u26A0 cpu-heavy: ${$}ms self-time of ${r}ms${e.detail?" \xB7 "+e.detail:""}`)}return{events:w,...o?{sql:o}:{}}}C(buildFlowDetail,"buildFlowDetail"),v(buildFlowDetail,"buildFlowDetail");
|
|
1
|
+
"use strict";var jt=Object.defineProperty;var R=(i,f)=>jt(i,"name",{value:f,configurable:!0});var St=Object.defineProperty,k=R((i,f)=>St(i,"name",{value:f,configurable:!0}),"I");import{redactDeep as Bt,redactText as nt,redactUrlParams as At}from"./redact.js";import{templatisePath as ht}from"./route-template.js";import{SQL_ACCOUNT_KIND as Rt,sqlAccountingOf as Tt}from"./flow-segments.js";import{isServiceHopHost as $t}from"./service-hop.js";import{boundedClone as Ct}from"@lensmcp/protocol-types";export function areaOf(i){const{source:f,category:s,title:M,kind:l}=i;return f==="ai"?"ai":f==="perf"&&l==="event-loop-stall"?"services":l?.startsWith("memory")||l==="db-query"?"data":f==="gateway"||l==="gateway-request"?"gateway":l==="queue-process"||l==="queue-active"||l==="queue-completed"||l==="queue-failed"?"services":f==="db"||f==="redis"||f==="queue"||l==="redis-op"||l?.startsWith("queue-")||f==="fs"||l==="fs-ops"?"data":f==="exec"||l==="exec"||l==="egress"&&$t(i.host)?"services":f==="external"||l==="egress"?"network":f==="nestjs"||f==="nest"?/(?:Repository|Repo|Dao|Store|EntityManager|DataSource|QueryRunner|OrmClient)\./.test(M)?"data":"services":f==="valtio"?"state":s==="network"?"network":"browser"}R(areaOf,"areaOf"),k(areaOf,"areaOf");const yt=240,Dt=new Set(["systemPrompt","prompt","prompts","messages","completion","completions","input","output","draft","text","content","result","arguments","params"]),Pt=new Set(["tenantId","caller","userId","accountId","email","subject"]),Nt=8;function vt(i){const f=rt(i,0);return Ct(Bt({redacted:!0,...f}),{rawByteLimit:16*1024}).value}R(vt,"Ct"),k(vt,"aiProjection");function rt(i,f){if(typeof i=="string")return kt(i);if(i===null||typeof i!="object")return i;if(f>=Nt)return;if(Array.isArray(i))return i.slice(0,32).map(M=>rt(M,f+1));const s={};for(const[M,l]of Object.entries(i))if(!Pt.has(M)){if(Dt.has(M)){typeof l=="string"?s[`${M}Chars`]=l.length:Array.isArray(l)?s[`${M}Count`]=l.length:l!=null&&(s[`${M}Present`]=!0);continue}s[M]=rt(l,f+1)}return s}R(rt,"it"),k(rt,"projectAi");function kt(i){return i.length<=yt?nt(i):`${nt(i.slice(0,yt))}\u2026`}R(kt,"Et"),k(kt,"clipText");export function httpFacts(i,f){const s=k(I=>typeof I=="string"&&I.length>0?I:void 0,"str"),M=typeof f.status=="number"?f.status:void 0,l=k(I=>At(I.split("?")[0]??I)||"/","pathOf"),P=k((I,y,j={})=>({...I?{method:I.toUpperCase()}:{},...y?{rawPath:y,route:ht(y)}:{},...M!==void 0?{status:M}:{},...j}),"facts");if(i==="gateway-request"){const I=s(f.url);return P(s(f.method),I?l(I):void 0,{...s(f.host)?{host:s(f.host)}:{},...s(f.caller)?{caller:s(f.caller)}:{}})}if(i==="egress"){const I=s(f.path),y=s(f.exchangeId);return P(s(f.method)??"GET",I?l(I):void 0,{...s(f.host)?{host:s(f.host)}:{},...y?{exchangeId:y}:{}})}if(i==="fetch-start"||i==="fetch-done"||i==="fetch-error"){const I=s(f.href)??s(f.url);let y,j;if(I)try{const p=new URL(I,"http://relative.invalid");y=l(p.pathname),p.hostname!=="relative.invalid"&&(j=p.hostname)}catch{}return P(s(f.method)??"GET",y,j?{host:j}:{})}if(i==="ai-call")return{...M!==void 0?{status:M}:{},...f.stream===!0?{streamed:!0}:{}};const g=f.request;if(g&&typeof g=="object"){const I=g,y=s(I.route),j=s(I.method);if(!y&&!j)return{};const p=typeof I.status=="number"?I.status:void 0,T=y?l(y):void 0;return{...j?{method:j.toUpperCase()}:{},...T?{rawPath:T,route:T.includes(":")?T:ht(T)}:{},...p!==void 0?{status:p}:{}}}return{}}R(httpFacts,"httpFacts"),k(httpFacts,"httpFacts");export function trimFlowEvent(i,f={}){const s=i.raw??{},M=s.render??{},l=s.span??{},P=s.request??{},g=k(d=>typeof d=="number"?d:void 0,"num"),I=s.loop??{},y=s.effect??{},j=g(s.durationMs)??g(l.durationMs)??g(P.durationMs)??g(I.durationMs)??g(M.actualDurationMs)??g(y.durationMs),p=typeof s.kind=="string"?s.kind:void 0,T=p==="fetch-done"||p==="fetch-error",G=g(M.commitTime),H=p==="render"&&G!==void 0&&Math.abs(G-i.timestamp)<6e4,V=T?i.timestamp:H?G:g(s.startedAt)??g(l.startTime)??g(P.startedAt)??g(I.startedAt)??(j!==void 0?i.timestamp-j:i.timestamp);let b;if(typeof s.query=="string"){const d=Array.isArray(s.params)?` [${s.params.join(", ")}]`:"",m=typeof s.result=="string"?` \u2192 ${s.result}`:"";b=s.query+d+m}else if(typeof s.command=="string"){const d=typeof s.value=="string"?` \u2192 ${s.value}`:s.hit===!1?" \u2192 (miss)":"";b=s.command+d}else if(p==="state-update"){const d=s.update??{};let m="";try{m=JSON.stringify(d.preview)}catch{}b=`${d.storeId??""}.${d.path??""} = ${m}`}else if(p==="fs-ops"&&Array.isArray(s.paths))b=s.paths.slice(0,8).map(d=>{const m=d??{},B=typeof m.n=="number"&&m.n>1?` \xD7${m.n}`:"",h=typeof m.bytes=="number"&&m.bytes>0?` (${m.bytes>=1024?`${(m.bytes/1024).toFixed(1)} KB`:`${m.bytes} B`})`:"";return`${String(m.op??"?")} ${String(m.path??"?")}${B}${h}`}).join(" \xB7 ");else if(p==="exec"&&typeof s.cmd=="string")b=`${s.cmd} \u2192 ${s.code??s.signal??s.error??"?"}`;else if(p==="queue-completed"&&typeof s.returned=="string")b=`returned ${s.returned}`;else if(p==="queue-enqueued")b=`${s.job??"job"}#${s.jobId??"?"} \u2192 ${s.queue??""}${typeof s.data=="string"?" "+s.data:""}`;else if(p==="log"){const d=P.error??{},m=typeof d.stack=="string"?d.stack:typeof s.text=="string"?s.text:void 0;m&&m!==i.title&&(b=m)}let L,W,Y,U;if(p==="span"){const d=l.outcome;if(L=d==="threw"||d==="returned"?d:l.status==="error"?"threw":l.status==="ok"?"returned":void 0,L==="threw"){const h=l.error??{},D=typeof h.name=="string"?h.name:void 0,K=typeof h.message=="string"?h.message:typeof i.message=="string"?i.message:void 0;Y=D,W=nt([D,K].filter(Boolean).join(": "))||void 0}const m=typeof l.args=="string"?l.args:void 0,B=typeof l.result=="string"?l.result:void 0;if(m!==void 0||B!==void 0){U=[...m!==void 0?["args"]:[],...B!==void 0?["result"]:[]];const h=k(D=>D.length>wt?`${D.slice(0,wt)}\u2026`:D,"short");b=[m!==void 0?`args ${h(m)}`:"",B!==void 0?`returned ${h(B)}`:""].filter(Boolean).join(" \u2192 ")}}let _;if(p==="gateway-request")_=`${s.host??""}${String(s.url??"").split("?")[0]}`;else if(p==="fetch-start"||p==="fetch-done"||p==="fetch-error")try{const d=new URL(String(s.href??s.url??""));_=`${d.hostname}${d.pathname}`}catch{}let O;if(p==="gateway-request"&&Array.isArray(s.steps))O=s.steps.map(d=>{const m=[String(d.step)];return d.project&&m.push(String(d.project)),d.prefix&&m.push(String(d.prefix)),d.mode&&m.push(String(d.mode)+(d.ok===!1?" \u2717":d.ok===!0?" \u2713":"")),d.caller&&m.push("by "+String(d.caller)),d.pod&&m.push(String(d.pod)),typeof d.atMs=="number"&&m.push(`@${d.atMs}ms`),m.join(" ")}),b=O.map(d=>d.replace(/ @\d+ms$/,"")).join(" \u2192 ");else if(p==="egress")b=`${s.method??"GET"} ${s.host??""} \u2192 ${s.status??"?"}`;else if(i.source==="ai"){if(p==="ai-call"){const d=s.usage??{},m=d.promptTokens!==void 0||d.completionTokens!==void 0?` \xB7 ${g(d.promptTokens)??"?"}\u2192${g(d.completionTokens)??"?"} tok`:"",B=s.status!==void 0?` \xB7 ${String(s.status)}`:s.error?" \xB7 failed":"",h=g(s.toolCallCount)?` \xB7 ${g(s.toolCallCount)} tool call${g(s.toolCallCount)===1?"":"s"}`:"",D=g(s.durationMs)!==void 0?` \xB7 ${(g(s.durationMs)/1e3).toFixed(1)}s`:"";b=`${s.provider??""}/${s.model??"?"}${m}${B}${h}${D}${s.stream===!0?" \xB7 stream":""}`}else if(p==="ai-round"){const d=s.model??{},m=s.usage??{},B=Array.isArray(s.toolCalls)?s.toolCalls:[],h=`${g(m.promptTokens)??"?"}\u2192${g(m.completionTokens)??"?"} tok`,D=B.length?` \xB7 ${B.length} tool call${B.length===1?"":"s"}`:"",K=g(s.durationMs)!==void 0?` \xB7 ${(g(s.durationMs)/1e3).toFixed(1)}s`:"";b=`${d.provider??""}/${d.model??""} \xB7 ${h}${D}${K}`}else if(p==="ai-turn-start"){const d=Array.isArray(s.tools)?s.tools:[];b=`skill ${s.skill??""} \xB7 ${s.output??""}${d.length?" \xB7 tools: "+d.join(", "):""}`}else if(p==="ai-tool-call"){const d=s.call??{},m=s.result??{},B=g(s.durationMs)!==void 0?` \xB7 ${(g(s.durationMs)/1e3).toFixed(1)}s`:"";b=`${d.name??"tool"} ${m.error!=null?"\u2717 "+String(m.error):"\u2713"}${B}`}else if(p==="ai-turn-end"){const d=s.usage??{},m=g(s.rounds)!==void 0?` \xB7 ${g(s.rounds)} round${g(s.rounds)===1?"":"s"}`:"",B=g(s.durationMs)!==void 0?` \xB7 ${(g(s.durationMs)/1e3).toFixed(1)}s`:"";b=`${s.ok===!1?"FAILED":"ok"} \xB7 ${g(d.promptTokens)??"?"}+${g(d.completionTokens)??"?"} tok${m}${B}`}}let C;p==="db-query"&&typeof s.query=="string"?C=s.query:p==="redis-op"?C=`${s.op??""} ${String(s.key??"").replace(/\d+/g,"#")}`:p==="egress"?C=`${s.method??"GET"} ${s.host??""}`:p==="ai-call"?C=`ai ${s.provider??""} ${s.model??""} ${s.api??""}`:p==="queue-enqueued"?C=`enqueue ${s.queue??""}`:p==="span"&&typeof l.name=="string"?C=l.name:p==="memory-mutation"&&(C=i.title);const E=i.context??{},Z=M.commitId??y.commitId??s.commitId,z=Array.isArray(M.why)?M.why.map(d=>typeof d?.type=="string"?d.type:void 0).filter(d=>!!d):[];return{id:i.id,t:i.timestamp,start:V,...C?{sig:C}:{},..._?{urlKey:_}:{},...httpFacts(p,s),...(p==="db-query"||p==="redis-op")&&typeof s.valuesId=="string"?{valuesId:s.valuesId}:{},...typeof s.queue=="string"?{queue:s.queue}:{},...s.jobId!=null?{jobId:String(s.jobId)}:{},source:i.source,category:i.category,severity:i.severity,title:L==="threw"&&typeof l.name=="string"?`${l.name} \u2014 threw${Y?` ${Y}`:""}${j!==void 0?` after ${j}ms`:""}`:i.title,kind:p,area:areaOf({source:i.source,category:i.category,title:i.title,kind:p,...typeof s.host=="string"?{host:s.host}:{}}),...p==="egress"&&$t(typeof s.host=="string"?s.host:void 0)?{hop:!0}:{},...p==="egress"&&typeof s.calleeRequestId=="string"?{calleeRequestId:s.calleeRequestId}:{},...L?{outcome:L}:{},...W?{error:W}:{},...U?{captured:U}:{},...typeof l.role=="string"?{role:l.role}:{},...typeof l.spanId=="string"?{spanId:l.spanId}:{},...typeof l.parentSpanId=="string"?{parentSpanId:l.parentSpanId}:{},...p==="log"&&typeof s.level=="string"?{level:s.level}:{},...p==="log"&&typeof s.logger=="string"?{logger:s.logger}:{},...p==="log"&&typeof s.stream=="string"?{stream:s.stream}:{},...typeof E.requestId=="string"?{requestId:E.requestId}:{},...typeof E.parentRequestId=="string"?{parentRequestId:E.parentRequestId}:{},...typeof Z=="string"?{commitId:Z}:{},...typeof M.componentName=="string"?{component:M.componentName}:{},...typeof M.phase=="string"?{phase:M.phase}:{},...z.length?{why:z}:{},...p==="effect-run"&&typeof y.hookLogicalId=="string"?{hook:y.hookLogicalId}:{},...p==="effect-run"&&y.cleanupRan===!0?{cleanup:!0}:{},...p==="render-commit-truncated"?{renderTotal:g(s.total),renderPublished:g(s.published),...g(s.walkStoppedAt)!==void 0?{renderWalkStoppedAt:g(s.walkStoppedAt)}:{}}:{},...typeof E.projectName=="string"?{project:E.projectName}:typeof s.project=="string"?{project:s.project}:{},...j!==void 0?{durationMs:j}:{},...T&&g(s.startedAt)!==void 0?{fetchStartedAt:g(s.startedAt)}:{},...i.source==="ai"?{ai:f.includeRaw?s:vt(s)}:{},...b?{detail:b}:{},...O?{steps:O}:{}}}R(trimFlowEvent,"trimFlowEvent"),k(trimFlowEvent,"trimFlowEvent");const wt=300;export const OUTLIVED_PARENT_MIN_MS=250;const Ot=new Set(["egress","db-query","redis-op","ai-call","span","gateway-request","server-request"]),at=1;function Mt(i){return i==="flow-origin"?0:i?.endsWith("-start")?1:i==="gateway-request"?2:i==="server-request"?3:i==="span"?4:i==="render"||i==="render-commit-truncated"?6:i==="effect-run"?7:5}R(Mt,"Nt"),k(Mt,"kindRank");function J(i){return i.kind==="span"&&i.role==="controller"?3.5:i.kind==="queue-process"?3:Mt(i.kind)}R(J,"Z"),k(J,"causalRank");export function buildFlowDetail(i,f={}){const s=Tt(i),M=i.slice().filter(t=>{const r=t.raw??{},e=typeof r.kind=="string"?r.kind:"";if(e==="subscription-added"||e==="subscription-removed"||e==="hook-run"||e==="store-created"||e==="singleton-instance"||e===Rt)return!1;const o=r.span;return!(typeof o?.name=="string"&&o.name.startsWith("nest.request "))}).map(t=>trimFlowEvent(t,f)),l=new Set;for(let t=M.length-1;t>=0;t--)l.has(M[t].id)?M.splice(t,1):l.add(M[t].id);const P=new Map;for(const t of M)(t.kind==="gateway-request"||t.kind==="queue-process")&&t.requestId&&!P.has(t.requestId)&&P.set(t.requestId,t.start);const g=k(t=>t.source!=="react"&&t.source!=="valtio"&&t.requestId&&P.has(t.requestId)?P.get(t.requestId):t.start,"anchorOf");M.sort((t,r)=>g(t)-g(r)||t.start-r.start||J(t)-J(r)||(r.durationMs??0)-(t.durationMs??0)||t.t-r.t);const I=k(t=>(t.kind==="span"||t.kind==="server-request"||t.kind==="gateway-request"||t.kind==="queue-process")&&(t.durationMs??0)>0,"spanLike"),y=M;for(const t of y)t.group=t.source==="react"||t.source==="valtio"||t.source==="client-runtime"||t.source==="visual"?"browser":"server";const j=new Map;for(const t of y)if((t.kind==="gateway-request"||t.kind==="queue-process")&&t.requestId&&t.project){const r=j.get(t.requestId)??[];r.push({project:t.project,start:t.start,end:t.start+(t.durationMs??0)}),j.set(t.requestId,r)}const p=k((t,r)=>{const e=j.get(t);if(e?.length)return e.every(o=>o.project===e[0].project)?e[0].project:e.filter(o=>o.start<=r+1&&o.end>=r-1).sort((o,n)=>n.start-o.start)[0]?.project},"projectOf");for(const t of y)if(!t.project&&t.source==="nestjs"&&t.requestId){const r=p(t.requestId,t.start);r&&(t.project=r)}const T=new Map;for(const t of y)t.spanId&&T.set(t.spanId,t);const G=k((t,r)=>{let e=t.parentSpanId?T.get(t.parentSpanId):void 0;for(let o=0;e&&o<64;o++){if(e===r)return!0;e=e.parentSpanId?T.get(e.parentSpanId):void 0}return!1},"inside"),H=new Map(y.map((t,r)=>[t,r])),V=new Map,b=k(t=>{let r=V.get(t.group);r||V.set(t.group,r=new Map);const e=t.requestId;let o=r.get(e);return o||r.set(e,o={spans:[],next:0,alive:[]}),o},"legOf");for(const t of y)I(t)&&Number.isFinite(t.start)&&b(t).spans.push(t);for(const t of V.values())for(const r of t.values())r.spans.sort((e,o)=>e.start-o.start||H.get(e)-H.get(o));const L=k(t=>Number.isFinite(t.start)?t.start:1/0,"startOf");for(const t of y.slice().sort((r,e)=>L(r)-L(e))){const r=t.parentSpanId?T.get(t.parentSpanId):void 0;if(r&&r!==t&&r.group===t.group&&r.requestId===t.requestId){t.parent=r.id;continue}if(!Number.isFinite(t.start))continue;const e=b(t);for(;e.next<e.spans.length&&e.spans[e.next].start<=t.start+at;)e.alive.push(e.spans[e.next++]);e.alive=e.alive.filter(a=>a.start+(a.durationMs??0)>t.start);const o=e.alive.length>1?e.alive.slice().sort((a,$)=>H.get(a)-H.get($)):e.alive;let n;for(const a of o){if(a===t)continue;const $=a.project,w=t.project;if(a.kind!=="gateway-request"&&t.kind!=="gateway-request"&&$!==void 0&&w!==void 0&&$!==w)continue;const u=a.durationMs??0,c=t.durationMs??0,v=Math.abs(a.start-t.start)<=at,q=J(a),A=J(t),x=a.start<=t.start;if(!(a.start+u>t.start&&(x||v&&q<A)&&(!v||(q!==A?q<A:x&&u>c))))continue;const F=n?J(n):-1,N=n?Math.abs(a.start-n.start)<=at:!1,Q=n&&N&&q===F?G(a,n)?1:G(n,a)?-1:0:0;(!n||!N&&a.start>n.start||N&&q>F||Q>0||Q===0&&N&&q===F&&(a.durationMs??0)<(n.durationMs??0))&&(n=a)}n&&(t.parent=n.id)}for(const t of y)delete t.spanId,delete t.parentSpanId;{const t=new Set(y.map(a=>a.id)),r=new Set,e=new Map,o=[],n=k(a=>{const $=[a];for(;$.length;){const w=$.pop();o.push(w),r.add(w.id);const u=e.get(w.id);if(u){e.delete(w.id);for(let c=u.length-1;c>=0;c--)$.push(u[c])}}},"place");for(const a of y)if(a.parent&&a.parent!==a.id&&t.has(a.parent)&&!r.has(a.parent)){const $=e.get(a.parent)??[];$.push(a),e.set(a.parent,$)}else n(a);for(const a of y)r.has(a.id)||(o.push(a),r.add(a.id));for(let a=0;a<o.length;a++)y[a]=o[a]}const W=new Map;for(const t of y){if(!t.requestId)continue;const r=W.get(t.requestId);r?r.push(t):W.set(t.requestId,[t])}const Y=new Map(y.map(t=>[t.id,t])),U=new Map;for(const t of y)t.calleeRequestId&&!U.has(t.calleeRequestId)&&U.set(t.calleeRequestId,t);const _=k((t,r)=>{const e=(W.get(t)??[]).filter(o=>I(o)&&o.start<=r&&o.start+(o.durationMs??0)>=r);if(e.length!==0)return e.sort((o,n)=>n.start-o.start||(o.durationMs??0)-(n.durationMs??0)),e[0]},"callerNodeFor");for(const t of y){if(!t.parentRequestId||t.kind!=="server-request"&&t.kind!=="gateway-request")continue;const r=t.parent?Y.get(t.parent):void 0;if(r&&r.requestId===t.requestId)continue;const e=t.requestId?U.get(t.requestId):void 0,o=e&&e.start<=t.start?e:_(t.parentRequestId,t.start);o&&o!==t&&o.start<=t.start&&(t.parent=o.id)}const O=y,C=new Map;for(const t of O)t.kind==="gateway-request"&&t.requestId&&!C.has(t.requestId)&&C.set(t.requestId,t);for(const t of O)if(t.group==="server"&&!t.parent&&t.requestId&&t.kind!=="gateway-request"){const r=C.get(t.requestId);r&&r!==t&&r.start<=t.start&&(t.parent=r.id)}const E=new Set,Z=new Set,z=[];for(const t of O){if(t.kind!=="gateway-request")continue;if(!t.requestId){z.push(t);continue}const r=W.get(t.requestId)??[];if(r.filter(n=>n.kind==="gateway-request").sort((n,a)=>n.start-a.start)[0]!==t)continue;const e=r.find(n=>n.kind==="fetch-start"),o=r.find(n=>n.kind==="fetch-done"||n.kind==="fetch-error");if(!e&&!o){z.push(t);continue}e&&(t.fetchStartId=e.id,E.add(e.id)),o&&(t.fetchDoneId=o.id,Z.add(o.id))}const d=new Map;for(const t of O){if(!t.urlKey)continue;const r=d.get(t.urlKey)??{gws:[],starts:[],dones:[]};t.kind==="gateway-request"?z.includes(t)&&r.gws.push(t):t.kind==="fetch-start"?E.has(t.id)||r.starts.push(t):(t.kind==="fetch-done"||t.kind==="fetch-error")&&(Z.has(t.id)||r.dones.push(t)),d.set(t.urlKey,r)}for(const{gws:t,starts:r,dones:e}of d.values()){t.sort((n,a)=>n.start-a.start),r.sort((n,a)=>n.start-a.start);const o=new Set;t.forEach((n,a)=>{const $=r[a];$&&(n.fetchStartId=$.id);const w=$?$.t:n.start;let u,c=1/0;for(const v of e){if(o.has(v.id))continue;const q=v.fetchStartedAt??v.start-(v.durationMs??0),A=Math.abs(q-w);A<c&&(c=A,u=v)}u&&(n.fetchDoneId=u.id,o.add(u.id))})}const m=new Map,B=new Map;for(const t of O)!t.queue||!t.jobId||(t.kind==="queue-enqueued"&&m.set(`${t.queue}#${t.jobId}`,t),t.kind==="queue-process"&&B.set(`${t.queue}#${t.jobId}`,t));for(const t of O){if(!t.queue||!t.jobId)continue;const r=`${t.queue}#${t.jobId}`;if(t.kind==="queue-process"&&!t.parent){const e=m.get(r);e&&e!==t&&e.start<=t.start&&(t.parent=e.id)}else if(t.kind==="queue-active"||t.kind==="queue-completed"||t.kind==="queue-failed"){const e=B.get(r);e&&e!==t&&e.start<=t.start&&(t.parent=e.id)}}{const t=new Map(y.map(r=>[r.id,r]));for(const r of y){if(!r.parent||!Ot.has(r.kind??"")||r.durationMs===void 0)continue;const e=t.get(r.parent);if(!e||e.kind!=="span"||!((e.durationMs??0)>0))continue;const o=Math.round(r.start+r.durationMs-(e.start+e.durationMs));if(o<OUTLIVED_PARENT_MIN_MS)continue;r.outlivedParentMs=o;const n=e.leftRunning??{count:0,longestMs:0};e.leftRunning={count:n.count+1,longestMs:Math.max(n.longestMs,o)}}for(const r of y){const e=[];r.leftRunning&&e.push(`ended before ${r.leftRunning.count===1?"a call":`${r.leftRunning.count} calls`} it started had finished \u2014 ${r.leftRunning.count===1?"it":"the longest"} ran on ${r.leftRunning.longestMs}ms (a timeout that gave up, or work not awaited)`),r.outlivedParentMs!==void 0&&e.push(`ran on ${r.outlivedParentMs}ms after the span that started it had ended`),e.length>0&&(r.detail=[r.detail,...e].filter(Boolean).join(" \xB7 "))}}let h=y;{const t=new Map;for(const e of h)e.kind==="fetch-start"&&t.set(e.id,e);const r=new Map;for(const e of h){if(e.kind!=="fetch-done"&&e.kind!=="fetch-error")continue;const o=[...t.values()].find($=>$.requestId&&$.requestId===e.requestId)?.id,n=o?t.get(o):void 0;if(!n)continue;const a=e.fetchStartedAt??n.start;n.title=e.title,n.severity=e.severity,n.durationMs=e.durationMs??Math.max(0,e.t-a),n.kind="fetch",e.detail&&!n.detail&&(n.detail=e.detail),r.set(e.id,n.id)}if(r.size>0){h=h.filter(e=>!r.has(e.id));for(const e of h)for(e.fetchDoneId&&r.has(e.fetchDoneId)&&(e.fetchDoneId=r.get(e.fetchDoneId));e.parent&&r.has(e.parent);)e.parent=r.get(e.parent)}}const D=k(t=>!!t.commitId&&(t.kind==="render"||t.kind==="effect-run"||t.kind==="render-commit-truncated"),"commitMember"),K=k(t=>[...new Set(t)],"uniq"),st=k(t=>K(t.map(r=>r.component).filter(r=>!!r)),"named"),qt=k(t=>t.length<=2?t.join(" + "):`${t[0]} + ${t.length-1} more`,"nameList"),it=k(t=>{const r=new Map;for(const e of t)e.component&&r.set(e.component,(r.get(e.component)??0)+1);return[...r].map(([e,o])=>o>1?`${e} \xD7${o}`:e).join(", ")},"tally"),It=k(t=>{if(!t)return"effect";const r=/([^/\\]+?):(\d+)(?::\d+)?$/.exec(t);return r?`${r[1]}:${r[2]}`:t},"shortHook"),ot=k(t=>`${It(t.hook)}${t.cleanup?" (cleanup)":""}`,"hookLabel"),dt=k(t=>{const r=Math.min(...t.map(o=>o.start)),e=Math.max(...t.map(o=>o.start+(o.durationMs??0)));return{start:r,durationMs:e-r}},"extent"),ut=k(t=>(delete t.component,delete t.why,delete t.phase,delete t.hook,delete t.cleanup,delete t.renderTotal,delete t.renderPublished,t),"asGroup");function ct(t){const r=t[0],e=t.filter(S=>S.kind==="render"),o=t.filter(S=>S.kind==="effect-run"),n=t.find(S=>S.kind==="render-commit-truncated"),a=[...e].sort((S,X)=>(X.durationMs??0)-(S.durationMs??0)),$=a.filter(S=>(S.why??[]).some(X=>X!=="parent-render")),w=K($.flatMap(S=>(S.why??[]).filter(X=>X!=="parent-render"))),u=st($),c=e.filter(S=>S.phase==="mount"),v=e.filter(S=>S.phase!=="mount"&&!$.includes(S)),q=a[0]?.component??"view",A=e.length>0&&c.length===e.length,x=w.length?w.join(" + "):void 0,F=A?"mount":"update",N=u.length?`view update \xB7 ${qt(u)} \u2190 ${x}`:A?`view mount \xB7 ${q}`:`view update \xB7 ${q}`,Q=n?.renderTotal??e.length,xt=[c.length?`mounted ${it(c)}`:"",v.length?`re-rendered ${it(v)}`:"",!u.length&&!A&&e.length?"trigger not recorded \u2014 every captured render came from its parent":"",n?.renderWalkStoppedAt!==void 0?`at least ${Q} renders (the walk stopped at ${n.renderWalkStoppedAt} fibers), ${n.renderPublished??e.length} recorded`:n?`${Q} renders, ${n.renderPublished??e.length} recorded (per-commit cap)`:`${e.length} render${e.length===1?"":"s"}`,o.length?`${o.length} effect${o.length===1?"":"s"}: ${o.map(ot).join(", ")}`:""].filter(Boolean).join(" \xB7 "),gt=st(e);return ut({...r,...dt(t),kind:"view-update",title:N,detail:xt,commitId:r.commitId,viewPhase:F,...x?{cause:x}:{},causeComponents:u,components:gt,mounted:st(c),renderCount:Q,...n?.renderWalkStoppedAt!==void 0?{renderCountAtLeast:!0}:{},recordedRenders:e.length,effects:o.length,effectHooks:o.map(ot),memberIds:t.map(S=>S.id),sig:`commit:${F}:${x??"-"}:${u.join(",")}:${gt.slice().sort().join(",")}`,severity:t.some(S=>S.severity==="error")?"error":r.severity})}R(ct,"It"),k(ct,"viewUpdateNode");function lt(t){const r=t[0],e=t.map(ot);return ut({...r,...dt(t),kind:"commit-effects",title:t.length===1?`effect \xB7 ${e[0]}`:`effects \xB7 ${e[0]} + ${t.length-1} more`,detail:e.join(", "),commitId:r.commitId,effects:t.length,effectHooks:e,memberIds:t.map(o=>o.id),sig:`effects:${e.slice().sort().join(",")}`})}R(lt,"kt"),k(lt,"effectsNode");const bt=k(t=>K(t.map(r=>r==="state-update"?"state update":r==="fetch-start"?"request":r==="flow-origin"?"interaction":"response")).join(" + "),"describeInputs");function pt(t,r){const e=t[0],o=t[t.length-1],n=t.length,a=e.detail,$=Math.min(...t.map(v=>v.start)),w=Math.max(...t.map(v=>v.start+(v.durationMs??0))),u=Math.round(t.reduce((v,q)=>v+(q.durationMs??0),0)/n*10)/10,c=Math.round((w-$)*10)/10;e.count=n,e.avgMs=u,e.wallMs=c,e.durationMs=w-$,e.items=t.slice(0,12).map(v=>({start:v.start-$,...v.durationMs!=null?{durationMs:v.durationMs}:{}})),e.memberIds=t.flatMap(v=>v.memberIds??[v.id]),e.title=`\xD7${n} ${e.title}`,t.some(v=>v.severity==="error")&&(e.severity="error"),e.kind==="view-update"?e.detail=[`${n} commits, ${r.length?`one per ${bt(r)}`:"back to back"}`,`avg ${u}ms \xB7 wall ${c}ms`,a??""].filter(Boolean).join(" \xB7 "):e.kind==="state-update"&&a&&o.detail?e.detail=`${n} updates in a row \xB7 wall ${c}ms \xB7 first ${a} \xB7 last ${o.detail}`:e.detail=[`${n} in a row \xB7 wall ${c}ms`,a??""].filter(Boolean).join(" \xB7 ")}R(pt,"qt"),k(pt,"collapseRepeat");const tt=[],et=new Map,ft=new Set;for(let t=0;t<h.length;t++){if(ft.has(t))continue;const r=h[t];if(!D(r)){tt.push(r);continue}const e=[t];for(let a=t+1;a<h.length;a++){const $=h[a];if($.group==="browser"){if(!D($)||$.commitId!==r.commitId)break;e.push(a)}}for(const a of e)ft.add(a);const o=e.map(a=>h[a]),n=o.some(a=>a.kind!=="effect-run")?ct(o):lt(o);for(const a of o)a.id!==n.id&&et.set(a.id,n.id);tt.push(n)}if(et.size>0||tt.some(t=>t.kind==="view-update"||t.kind==="commit-effects")){h=tt;for(const t of h)for(;t.parent&&et.has(t.parent);)t.parent=et.get(t.parent)}{const t=new Map;for(const r of h)r.kind==="view-update"&&r.commitId&&!t.has(r.commitId)&&t.set(r.commitId,r);for(const r of h){if(r.kind!=="commit-effects"||r.parent||!r.commitId)continue;const e=t.get(r.commitId);e&&e.start<=r.start&&(r.parent=e.id)}}{const t=new Set(["state-update","fetch","fetch-start","fetch-done","fetch-error","flow-origin"]),r=h.map((u,c)=>u.group==="browser"?c:-1).filter(u=>u>=0),e=k(u=>`${u.kind}|${u.sig??u.title}`,"keyOf"),o=r.map(u=>e(h[u])),n=k((u,c,v)=>{for(let q=0;q<v;q++)if(o[u+q]!==o[c+q])return!1;return!0},"sameBlock"),a=k(u=>o[u].split("|")[0],"kindAt"),$=k(u=>{let c=0,v=1;for(let q=1;q<=4&&u+q<=o.length;q++){let A=1;for(;u+(A+1)*q<=o.length&&n(u,u+A*q,q);)A+=1;A>=3&&q*A>c*v&&(c=q,v=A)}return{k:c,r:v}},"bestBlockAt"),w=new Map;for(let u=0;u<o.length;){const{k:c,r:v}=$(u);if(c===0){u+=1;continue}const q=[...Array(c).keys()].find(x=>t.has(a(u+x)));if(q!==void 0&&q>0){const x=$(u+q);if(x.k===c&&x.r>=3&&x.r>=v-1){u+=q;continue}}const A=o.slice(u,u+c).map(x=>x.split("|")[0]).filter(x=>t.has(x));for(let x=0;x<c;x++){const F=[];for(let N=0;N<v;N++)F.push(h[r[u+x+N*c]]);pt(F,A);for(const N of F.slice(1))w.set(N.id,F[0].id)}u+=c*v}if(w.size>0){h=h.filter(u=>!w.has(u.id));for(const u of h)for(;u.parent&&w.has(u.parent);)u.parent=w.get(u.parent)}}{const t=new Map(h.map(r=>[r.id,r]));for(const r of h){const e=new Set([r.id]);let o=r;for(;o?.parent;){if(e.has(o.parent)){o.parent=void 0;break}e.add(o.parent),o=t.get(o.parent)}}}const mt=k(t=>{const r=new Map;for(const o of h){if(!o.sig||I(o)!==t||o.kind==="server-request"||o.kind==="gateway-request"||o.group==="browser")continue;const n=`${o.parent??"root"}|${o.kind}|${o.sig}`,a=r.get(n);a?a.push(o):r.set(n,[o])}const e=new Map;for(const o of r.values()){if(o.length<3)continue;o.sort((c,v)=>c.start-v.start);const n=o[0];let a=!1;for(let c=0;c+1<o.length;c++)if(o[c+1].start<o[c].start+(o[c].durationMs??0)){a=!0;break}const $=n.start,w=Math.max(...o.map(c=>c.start+(c.durationMs??0))),u=o.reduce((c,v)=>c+(v.durationMs??0),0);n.count=o.length,n.parallel=a,n.avgMs=Math.round(u/o.length*10)/10,n.wallMs=w-$,n.durationMs=w-$,n.title=`\xD7${o.length} ${n.title.replace(/\s*\((?:\d+ rows?, )?\d+ms\)$/,"")}`,n.detail=`${n.sig} \xD7${o.length} ${a?"\u2016 parallel":"\u21BB sequential loop"} \xB7 avg ${n.avgMs}ms \xB7 wall ${n.wallMs}ms`,n.items=o.slice(0,12).map(c=>({start:c.start-$,...c.durationMs!=null?{durationMs:c.durationMs}:{}})),n.memberIds=o.flatMap(c=>c.memberIds??[c.id]),o.some(c=>c.severity==="error")&&(n.severity="error");for(const c of o.slice(1))e.set(c.id,n.id)}if(e.size!==0){h=h.filter(o=>!e.has(o.id));for(const o of h)for(;o.parent&&e.has(o.parent);)o.parent=e.get(o.parent)}},"aggregatePass");mt(!0),mt(!1);for(const t of h){if(!I(t))continue;const r=t.durationMs??0;if(r<40)continue;const e=h.filter(w=>w.parent===t.id&&(w.durationMs??0)>0).map(w=>[w.start,w.start+(w.durationMs??0)]).sort((w,u)=>w[0]-u[0]);let o=0,n=-1,a=-1;for(const[w,u]of e)w>a?(a>n&&(o+=a-n),n=w,a=u):u>a&&(a=u);a>n&&(o+=a-n);const $=Math.max(0,r-o);($>=50||$/r>=.7&&r>=40)&&(t.selfMs=$,t.detail=`\u26A0 cpu-heavy: ${$}ms self-time of ${r}ms${t.detail?" \xB7 "+t.detail:""}`)}return{events:h,...s?{sql:s}:{}}}R(buildFlowDetail,"buildFlowDetail"),k(buildFlowDetail,"buildFlowDetail");
|
package/lib/http-exchange.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import type { HttpExchange } from '@lensmcp/protocol-types';
|
|
2
2
|
export type { HttpExchange, HttpExchangeBody, HttpExchangeStreamEvent } from '@lensmcp/protocol-types';
|
|
3
|
+
export { isServiceHopHost, LENS_REQUEST_ID_HEADER } from './service-hop.js';
|
|
3
4
|
export declare const HTTP_EXCHANGE_DIR = "http";
|
|
4
5
|
/**
|
|
5
6
|
* Where an exchange is kept, each place pruned on its own budget: a third-party call (`http/`), a request a service
|
|
6
7
|
* served (`http/in/`), and a call between the cluster's own services through the gateway (`http/hops/`, a host under
|
|
7
8
|
* `.localhost`). One budget for all let FoodGuard's ~2,000 service-to-service calls an hour push its OpenAI calls out.
|
|
9
|
+
* An AI provider's call (`http/ai/`) is kept, not pruned — the prompts and answers reviews are made of — in a place of
|
|
10
|
+
* its own, so pruning the others never has to open a file to tell one apart.
|
|
8
11
|
*/
|
|
9
|
-
export type HttpExchangePlace = 'calls' | 'in' | 'hops';
|
|
12
|
+
export type HttpExchangePlace = 'calls' | 'in' | 'hops' | 'ai';
|
|
10
13
|
export declare const HTTP_EXCHANGE_PLACES: readonly HttpExchangePlace[];
|
|
11
14
|
/** The place an exchange belongs in. */
|
|
12
|
-
export declare function exchangePlace(x: Pick<HttpExchange, 'side' | 'host'>
|
|
15
|
+
export declare function exchangePlace(x: Pick<HttpExchange, 'side' | 'host'> & {
|
|
16
|
+
ai?: unknown;
|
|
17
|
+
}): HttpExchangePlace;
|
|
13
18
|
export declare const isHttpExchangeId: (value: unknown) => value is string;
|
|
14
19
|
export declare function httpExchangeDir(busFile: string, place?: HttpExchangePlace): string;
|
|
15
20
|
export declare function httpExchangeFile(busFile: string, id: string, place?: HttpExchangePlace): string;
|
package/lib/http-exchange.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var i=Object.defineProperty;var n=(t,e)=>i(t,"name",{value:e,configurable:!0});var s=Object.defineProperty,a=n((t,e)=>s(t,"name",{value:e,configurable:!0}),"
|
|
1
|
+
"use strict";var i=Object.defineProperty;var n=(t,e)=>i(t,"name",{value:e,configurable:!0});var s=Object.defineProperty,a=n((t,e)=>s(t,"name",{value:e,configurable:!0}),"n");import*as p from"node:fs";import{dirname as h,join as c}from"node:path";export{isServiceHopHost,LENS_REQUEST_ID_HEADER}from"./service-hop.js";export const HTTP_EXCHANGE_DIR="http",HTTP_EXCHANGE_PLACES=["calls","ai","in","hops"];export function exchangePlace(t){return t.side==="ingress"?"in":t.ai?"ai":/(^|\.)localhost$/i.test(t.host)?"hops":"calls"}n(exchangePlace,"exchangePlace"),a(exchangePlace,"exchangePlace");const l=/^hx_[0-9a-z]{8,40}$/;export const isHttpExchangeId=a(t=>typeof t=="string"&&l.test(t),"isHttpExchangeId");export function httpExchangeDir(t,e="calls"){const r=c(h(t),HTTP_EXCHANGE_DIR);return e==="calls"?r:c(r,e)}n(httpExchangeDir,"httpExchangeDir"),a(httpExchangeDir,"httpExchangeDir");export function httpExchangeFile(t,e,r="calls"){if(!isHttpExchangeId(e))throw new Error("invalid http exchange id");return c(httpExchangeDir(t,r),`${e}.json`)}n(httpExchangeFile,"httpExchangeFile"),a(httpExchangeFile,"httpExchangeFile");export function readHttpExchange(t,e){if(isHttpExchangeId(e))for(const r of HTTP_EXCHANGE_PLACES)try{const o=JSON.parse(p.readFileSync(httpExchangeFile(t,e,r),"utf8"));if(o&&o.schemaVersion===1&&o.id===e)return o}catch{}}n(readHttpExchange,"readHttpExchange"),a(readHttpExchange,"readHttpExchange");
|
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,
|
|
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");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A call to one of the cluster's own services, not a third party: a host under `.localhost` — a name below it, never
|
|
3
|
+
* bare `localhost`, which is any process on a port (a local model's `localhost:11434` is still a provider of its own).
|
|
4
|
+
*
|
|
5
|
+
* Every `*.localhost` name is loopback by definition (RFC 6761), and it is where the dev gateway serves the cluster —
|
|
6
|
+
* `app.foodguard.localhost`, `internal.api.foodguard.localhost`, a worktree's `internal.api.gate1.foodguard.localhost`.
|
|
7
|
+
* So a service calling one is a hop through the gateway to another service (or to itself): the gateway routes it and
|
|
8
|
+
* the callee serves it. It was drawn as an External API — in a flow's «External APIs» row, on its Map as an external
|
|
9
|
+
* box — beside OpenAI and Stripe.
|
|
10
|
+
*
|
|
11
|
+
* No dependencies: the egress tap, the flow build and the catalogs share it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isServiceHopHost(host: string | undefined): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The lens's own id for the request a hop opens, minted by the caller's egress tap (`<caller's request id>.<n>`). The
|
|
16
|
+
* gateway and the callee's lens prefer it to `x-request-id`, which belongs to the app: one that forwards its own id to
|
|
17
|
+
* the services it calls — a trace id it keeps for a whole chain of requests — gave every hop the same id, and the lens
|
|
18
|
+
* filed the callee's work under whichever request first carried it.
|
|
19
|
+
*/
|
|
20
|
+
export declare const LENS_REQUEST_ID_HEADER = "x-lensmcp-request-id";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var o=Object.defineProperty;var r=(e,t)=>o(e,"name",{value:t,configurable:!0});var s=Object.defineProperty,c=r((e,t)=>s(e,"name",{value:t,configurable:!0}),"t");export function isServiceHopHost(e){return e?/.\.localhost$/i.test(e.replace(/\.$/,"")):!1}r(isServiceHopHost,"isServiceHopHost"),c(isServiceHopHost,"isServiceHopHost");export const LENS_REQUEST_ID_HEADER="x-lensmcp-request-id";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/core",
|
|
3
|
-
"version": "1.25.
|
|
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.
|
|
87
|
+
"@lensmcp/protocol-types": "1.25.33",
|
|
88
88
|
"tslib": "^2.3.0"
|
|
89
89
|
},
|
|
90
90
|
"license": "Apache-2.0",
|