@lensmcp/cluster 1.25.30 → 1.25.32

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.
@@ -1 +1 @@
1
- "use strict";var be=Object.defineProperty;var L=(s,h)=>be(s,"name",{value:h,configurable:!0});var $e=Object.defineProperty,c=L((s,h)=>$e(s,"name",{value:h,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.describeChildExit=describeChildExit,exports.signalChild=signalChild,exports.claimPort=claimPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),own_log_1=require("./own-log"),process_name_1=require("../../../process-name"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),hosted_dashboard_1=require("./hosted-dashboard"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4,DASHBOARD_SWEEP_MS=15e3;function tryBind(s,h){return new Promise(f=>{const _=net.createServer();_.unref(),_.once("error",p=>{const w=p.code??"";f(w==="EADDRINUSE"||w==="EACCES"?"in-use":"unavailable")}),_.listen(h===void 0?{port:s}:{port:s,host:h},()=>_.close(()=>f("free")))})}L(tryBind,"tryBind"),c(tryBind,"tryBind");async function probePortFree(s){for(const h of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(s,h)==="in-use")return!1;return!0}L(probePortFree,"probePortFree"),c(probePortFree,"probePortFree");function probeConnect(s,h,f){return new Promise(_=>{let p=!1;const w=net.createConnection({host:s,port:h});w.unref();const N=setTimeout(()=>R(!1),f);N.unref?.();function R(A){p||(p=!0,clearTimeout(N),w.destroy(),_(A))}L(R,"M"),c(R,"finish"),w.once("connect",()=>R(!0)),w.once("error",()=>R(!1))})}L(probeConnect,"probeConnect"),c(probeConnect,"probeConnect");async function probePortListening(s,h=1e3){return await probeConnect("127.0.0.1",s,h)?!0:probeConnect("::1",s,h)}L(probePortListening,"probePortListening"),c(probePortListening,"probePortListening");async function probeWatcherHealth(s,h=1500){try{const f=new AbortController,_=setTimeout(()=>f.abort(),h);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${s}/__lensmcp/watcher-health`,{signal:f.signal});if(!p.ok)return null;const w=await p.json();if(typeof w?.lastFsSetEventAt!="number")return null;const N=Array.isArray(w.recentEvents)?w.recentEvents.filter(R=>R&&typeof R.path=="string"&&typeof R.at=="number"&&typeof R.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(R.kind)):[];return{schemaVersion:w.schemaVersion===2?2:1,ready:w.ready!==!1,sequence:typeof w.sequence=="number"?w.sequence:0,lastFsSetEventAt:w.lastFsSetEventAt,fsSetEvents:typeof w.fsSetEvents=="number"?w.fsSetEvents:0,recentEvents:N}}finally{clearTimeout(_)}}catch{return null}}L(probeWatcherHealth,"probeWatcherHealth"),c(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=c(s=>new Promise(h=>{setTimeout(h,s).unref?.()}),"sleepUnref");async function waitForPortFree(s,h,f={}){const _=f.isPortFree??probePortFree,p=Math.max(1,f.intervalMs??types_1.PORT_FREE_POLL_MS),w=Date.now()+Math.max(0,h);for(;;){if(await _(s))return!0;const N=w-Date.now();if(N<=0)return!1;await sleepUnref(Math.min(p,N))}}L(waitForPortFree,"waitForPortFree"),c(waitForPortFree,"waitForPortFree");function viteArgPort(s){const h=c(f=>Number.isInteger(f)&&f>0&&f<65536?f:void 0,"valid");for(let f=0;f<s.length;f+=1){const _=s[f];if(_==="--port")return h(Number(s[f+1]));if(_.startsWith("--port="))return h(Number(_.slice(7)))}}L(viteArgPort,"viteArgPort"),c(viteArgPort,"viteArgPort");function describeChildExit(s){return s.signalCode?`killed by ${s.signalCode}`:s.exitCode===null?"exit status unknown":`exit code ${s.exitCode}`}L(describeChildExit,"describeChildExit"),c(describeChildExit,"describeChildExit");function signalChild(s,h){if(s.pid===void 0)return!1;try{return s.kill(h)}catch{return!1}}L(signalChild,"signalChild"),c(signalChild,"signalChild");async function claimPort(s,h,f,_){let p=s;for(;h.has(p)||_?.has(p)||!await f(p);)p+=1;return h.add(p),p}L(claimPort,"claimPort"),c(claimPort,"claimPort");function createLensChildren(s,h,f,_){const{emit:p}=h,w=_?.isPortFree??probePortFree,N=(f.ports?.length?f.ports:[443]).join(","),R=_?.probeWatcherHealth??probeWatcherHealth,A=[],G=new Map,T=new Map,x=new Map,V=new Map,Y=new Set;let F;const m=new Map,C=new Set,z=new Set,X=new Set,W=new Map;s.hostedDashboards=W;let I;const ue=c(()=>{I||(I=setInterval(()=>{if(!s.stopped())for(const e of W.values())e.tick()},DASHBOARD_SWEEP_MS),I.unref?.())},"startDashboardSweeper"),O=new Set,M=new Map,K=new WeakMap,q=new Set,oe=c((e,r,n)=>{if(q.has(e))return;q.add(e);const t=setInterval(()=>{(async()=>{if(s.stopped()||C.has(e)){clearInterval(t),q.delete(e);return}await w(r)&&(clearInterval(t),q.delete(e),console.log(`[gateway] lens child ${e}: :${r} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),B=c(async(e,r,n)=>{const t=await claimPort(e,r,w,n?.reserved);return t!==e&&(n?.heldBy?console.log(`[gateway] handoff: :${e} is held by ${n.heldBy} \u2014 replacement on :${t}.`):console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${t} instead.`)),t},"claimFreePort"),Ee=c((e,r,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...M.get(t)?.generations??[]].find(i=>i.generation===o);a&&(e.endsWith(":vite")?a.vite=r:e.endsWith(":browser-capture")&&(a.capture=r))},"attachRespawnedGenerationChild"),U=c(e=>{e.exitCode!==null||e.signalCode!==null||e.pid===void 0||(z.add(e),signalChild(e,"SIGTERM"),setTimeout(()=>{e.exitCode===null&&e.signalCode===null&&signalChild(e,"SIGKILL")},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.())},"retireChild"),fe=c((e,r)=>{const n=m.get(e);!n||n===r||n.exitCode!==null||n.signalCode!==null||(console.warn(`[gateway] lens child ${e}: a replacement took the label \u2014 retiring the previous child (pid ${n.pid??"?"}).`),U(n))},"retireLabelHolder"),D=c((e,r,n,t,o=!1,a,i,l)=>{const E=Date.now(),g=(0,own_log_1.gatewayLogTakeover)()!==void 0,d=(0,node_child_process_1.spawn)(r,n,{cwd:i??s.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,s.root,i??s.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:g?["inherit","pipe","pipe"]:"inherit"});g&&(d.stdout?.on("data",P=>process.stdout.write(P)),d.stderr?.on("data",P=>process.stderr.write(P))),A.push(d),fe(e,d),m.set(e,d),Ee(e,d,t);const $=e==="vite"||e.endsWith(":vite"),v=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=v&&v!==s.eventFile?v:void 0,u=$?viteArgPort(n):l;$&&T.set(d,{key:t?.LENSMCP_FRONTEND_KEY??`${i??s.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??s.root,spawnedAt:E,port:u,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...y?{bus:y}:{}});const S=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return S&&u!==void 0&&(p("info",`lens child starting: ${S} (:${u})`,`cluster-lens-child:${S}`,{kind:"lens-child-starting",project:S,label:e,pid:d.pid,port:u},void 0,y),(async()=>{const P=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<P;){if(d.exitCode!==null||d.signalCode!==null||C.has(e))return;if(await probePortListening(u,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${S} (:${u})`,`cluster-lens-child:${S}`,{kind:"lens-child-up",project:S,label:e,pid:d.pid,port:u},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),d.on("error",P=>{if(d.pid!==void 0){console.warn(`[gateway] lens child ${e}: ${P.message}`);return}const j=A.indexOf(d);j>=0&&A.splice(j,1),m.get(e)===d&&m.delete(e),T.delete(d),z.delete(d),console.error(`[gateway] lens child ${e} failed to spawn (${P.message}) \u2014 not started, not retried.`)}),d.on("exit",(P,j)=>{const le=A.indexOf(d);le>=0&&A.splice(le,1),m.get(e)===d&&m.delete(e);const k=T.get(d);if(T.delete(d),z.delete(d))return;const Ce=C.delete(e);if(s.stopped()||Ce||(S?p("warning",`lens child exited: ${S}`,`cluster-lens-child:${S}`,{kind:"lens-child-exit",project:S,label:e,pid:d.pid,code:P,signal:j},void 0,y):k&&p("warning",`lens frontend exited: ${k.project}`,`cluster-lens-child:${k.project}`,{kind:"lens-child-exit",project:k.project,label:e,pid:d.pid,code:P,signal:j,generation:k.generation,port:k.port,ranMs:Date.now()-E},void 0,y),k&&we(d,k)))return;const ce=Date.now()-E,b=G.get(e)??{attempts:0,portConflicts:0};(async()=>{const Me=u!==void 0&&!await w(u),de=(0,lifecycle_1.isPortConflictExit)({code:P,signal:j,ranMs:ce,portHeld:Me},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(de&&b.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)b.portConflicts+=1,G.set(e,b),console.warn(`[gateway] lens child ${e} lost the :${u} bind race (EADDRINUSE, code=${P}); waiting for the port, then retrying (port-retry ${b.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(de){b.portConflicts=0,G.set(e,b),console.warn(`[gateway] lens child ${e}: :${u} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${u} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:u}),oe(e,u,()=>D(e,r,n,t,o,a,i,l));return}else{b.portConflicts=0,ce>=HEAL_HEALTHY_MS&&(b.attempts=0);const pe=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**b.attempts);b.attempts+=1,G.set(e,b),console.warn(`[gateway] lens child ${e} exited (code=${P} sig=${j}); auto-healing in ${Math.round(pe/1e3)}s (attempt ${b.attempts}).`),await sleepUnref(pe)}s.stopped()||C.has(e)||(u!==void 0&&!await waitForPortFree(u,types_1.PORT_FREE_WAIT_MS,{isPortFree:w,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${u} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(s.stopped()||C.has(e))&&D(e,r,n,t,o,a,i,l))})().catch(()=>{})}),d},"spawnManagedChild"),ne=c(e=>{const r=`${e.wsKey}:${e.project}`,n=M.get(r);if(n)return n;const t={key:r,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};M.set(r,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,K.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,K.set(e.frontend,t),t},"registerFrontend"),ge=c(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const r=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==r);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),re=c((e,r,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;C.delete(a),C.delete(i);const l=c((d,$,v,y,u,S)=>D(d,$,v,y,u,S,e.workspaceRoot),"spawnInRoot"),E=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:r,chrome:!0,headless:!0,viteEnv:{...e.frontend.env??{},...e.frontend.frozen?{LENSMCP_FREEZE:"1"}:{},LENSMCP_PROJECT:e.project,...e.replayWorkspace?{LENSMCP_REPLAY_WORKSPACE:e.replayWorkspace}:{},LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n,...ge(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:c(d=>console.log(d),"log")},l),g={ownerKey:e.key,generation:t,port:r,labelPrefix:o,vite:E?.vite??m.get(a),capture:m.get(i)};return e.generations.add(g),g},"spawnFrontendGeneration"),H=c((e,r)=>{if(!e)return;M.get(e.ownerKey)?.generations.delete(e);const n=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,o]of n)C.add(t),o&&U(o);r&&O.delete(e.port)},"terminateGeneration"),he=c(async e=>{const r=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<r;){const n=e.vite;if(!n||n.exitCode!==null||n.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await R(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),J=c(async e=>{const r=e?.vite;return!!(e&&r&&r.exitCode===null&&r.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),Z=c(e=>!e.disposed&&!s.stopped()&&M.get(e.key)===e,"frontendIsCurrent"),Q=c((e,r)=>{if(!Z(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=r,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const l=new Set;for(const u of M.values())u!==e&&l.add(u.desiredPort);const E=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,g=await B(e.desiredPort,O,{reserved:l,heldBy:E});if(a=g,!Z(e))return O.delete(g),!1;o=re(e,g,"replacement"),e.candidate=o;const d=await J(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${g} reason=${r}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:g,oldServing:d},void 0,e.eventFile===s.eventFile?void 0:e.eventFile);const $=await he(o);if(!Z(e))return H(o,!0),e.candidate=void 0,!1;if(!$){H(o,!0),e.candidate=void 0;const u=await J(n);return e.state=u?"ready":"failed",u&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${o.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,port:g,durationMs:Date.now()-i,oldKept:u},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}for(const u of e.routes)u.target=`http://localhost:${o.port}`,u.lens&&(u.lens.port=o.port);e.frontend.port=o.port,e.active=o,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const v=o.vite?T.get(o.vite):void 0;v&&(v.role="active",v.listening=!0);const y=(0,hmr_reload_1.broadcastFullReload)(s,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${o.generation} :${o.port} in ${Date.now()-i}ms; draining g${n?.generation??0}${y>0?`; reloading ${y} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:y},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),n){const u=`${n.labelPrefix}browser-capture`;C.add(u),n.capture&&U(n.capture),setTimeout(()=>H(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){H(o,!0),!o&&a!==void 0&&O.delete(a),e.candidate=void 0;const E=await J(n);e.state=E?"ready":"failed",E&&(e.reason=void 0,e.startedAt=void 0);const g=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${g}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:E,error:g},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),se=c((e,r)=>{e.active=re(e,r,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),we=c((e,r)=>{const n=M.get(r.key);return!n||types_1.LENS_RESTART_MODE==="in-place"?!1:(n.candidate?.vite===e||n.active?.vite!==e||(n.state="starting",n.reason="child-exit",n.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${n.key} g${r.generation} :${r.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),Q(n,"child-exit")),!0)},"handleManagedViteExit");s.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const r=K.get(e);return r?{state:r.state,generation:r.candidate?.generation??r.active?.generation??r.generation,reason:r.reason,startedAt:r.startedAt}:void 0},recover(e,r){const n=K.get(e);return n?Q(n,r):Promise.resolve(!1)}}:void 0;let ee=!1,ae=0;const _e=c(()=>{ee||Date.now()-ae<types_1.POD_STALE_SCAN_MS||(ee=!0,ae=Date.now(),(async()=>{const e=new Map;for(const r of[...M.values()]){if(s.stopped())return;let n=e.get(r.workspaceRoot);if(!n){const g=(0,scope_1.readSourceSetOptions)(r.workspaceRoot);n={pass:(0,lifecycle_1.sourceScanPass)(r.workspaceRoot,g.exclude,{ignoreTests:g.ignoreTests}),startedAt:Date.now()},e.set(r.workspaceRoot,n)}const t=n.startedAt,o=await n.pass.snapshot(r.sourceDirs);if(M.get(r.key)!==r)continue;const a=r.sourceSnapshot,i=r.sourceScanStartedAt;if(r.sourceSnapshot=o,r.sourceScanStartedAt=t,!a)continue;const l=Date.now(),E=(0,lifecycle_1.diffSourceSets)(a,o,l,(0,lifecycle_1.sourceChangeWindowMs)(l,i,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));E&&(r.sourceChange=E,p("info",`source file set changed for ${r.key}`,`cluster-lens-app:${r.project}`,{kind:"source-set-changed",scoped:!0,wsKey:r.wsKey,project:r.project,added:E.added,removed:E.removed,truncated:E.truncated},void 0,r.eventFile===s.eventFile?void 0:r.eventFile))}})().catch(()=>{}).finally(()=>{ee=!1}))},"scanFrontendSourceSets");let te=!1;const Se=c(async(e,r)=>{if(!te){te=!0;try{for(const[n,t]of[...T]){if(s.stopped())return;if(t.port===void 0||n.exitCode!==null||n.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const l=M.get(t.key);l?.active?.vite===n&&(l.state="ready",l.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const o=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<o){t.probeFails=0;continue}if(!r)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},x.get(t.key)??0,Date.now(),e);if(!a)continue;const i=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(o/1e3)}s boot allowance`;x.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${i} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus),signalChild(n,"SIGKILL")}}finally{te=!1}}},"probeLensChildren"),ye=c(()=>{if(F||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};F=setInterval(()=>{if(s.stopped()||T.size===0||(ie(),!types_1.POD_RECYCLE_ENABLED))return;_e();const r=Date.now();for(const[n,t]of T){const o=M.get(t.key);if(o?.frontend.frozen)continue;const a=o?.sourceChange?.changedAt??0,i=x.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,r,e)&&((V.get(t.key)??0)>=a||Y.has(t.key)||(Y.add(t.key),(async()=>{try{const l=o?.sourceChange,E=l&&!l.truncated?[...l.added,...l.removed].map(v=>path.resolve(t.workspaceRoot,v)):[],g=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await R(t.port),a,l?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,E):"unknown";if(g==="heard"){V.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(g==="unknown"&&types_1.LENS_WATCHER_UNVERIFIED==="keep"){console.log(`[gateway] stale source set: lens app ${t.project} unverifiable (no watcher testimony) \u2014 keeping it (LENSMCP_LENS_WATCHER_UNVERIFIED=keep).`);return}if(n.exitCode!==null||n.signalCode!==null)return;x.set(t.key,Date.now());const d=M.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&d){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${g==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:d.wsKey,project:t.project,reason:"stale-source-set",watcher:g,added:l?.added,removed:l?.removed},void 0,t.bus),await Q(d,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${g==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:g},void 0,t.bus);const $=(0,hmr_reload_1.broadcastFullReload)(s,t.key);$>0&&console.log(`[gateway] \u2192 told ${$} open tab(s) to reload (stale optimize-dep generation)`),signalChild(n,"SIGTERM"),setTimeout(()=>{n.exitCode===null&&n.signalCode===null&&(console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`),signalChild(n,"SIGKILL"))},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{Y.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),F.unref?.()},"startLensSweeper"),ve=c(async()=>{const e=[...new Map(s.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],r=f.dashboard!==!1,n=f.mcp!==!1&&e.length>0;if(r){const t=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const o=(0,scope_1.readLensScope)(s.root),a=await B(f.dashboardPort??o.dashboardPort,X),i=f.lensHost??"lensmcp.local",l=o.basePath;s.registry.appendRoutes(s.wsKey,[{host:i,prefix:l,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),s.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:s.root,base:l,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${i}${l}/)`),p("info",`lens dashboard up: ${i}${l} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:l}),D("lens-dashboard",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"dashboard")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:l,LENSMCP_REPLAY_WORKSPACE:o.key,LENSMCP_GATEWAY_PORTS:N},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(s.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const o=(0,scope_1.readLensScope)(s.root),a=Number(process.env.LENSMCP_PORT??o.mcpHttpPort),i=c(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),D("lens-mcp",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"mcp")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_REPLAY_WORKSPACE:o.key,LENSMCP_WORKSPACE_ROOT:s.root},!1,void 0,void 0,a)},"spawnMcp");await w(a)?i():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),oe("lens-mcp",a,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(s.registry.get(s.wsKey)?.routes??s.routes).filter(E=>E.lens?.project===t.project),i=await B(t.port,O);for(const E of a)E.lens&&(E.lens.port=i),E.target=`http://localhost:${i}`;t.port=i;const l=ne({wsKey:s.wsKey,replayWorkspace:s.wsKey,project:t.project,workspaceRoot:s.root,projectRoot:t.projectRoot,eventFile:s.eventFile,routes:a,frontend:t,desiredPort:o});try{se(l,i),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(E){O.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${E.message}`)}}ye()},"bootSingletonsAndApps"),Pe=c(async e=>{if(f.dashboard===!1)return;const r=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!r)return;const n=(0,scope_1.readLensScope)(e.root),t=f.lensHost??"lensmcp.local",o=n.basePath,a=await B(n.dashboardPort,X),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");C.delete(i);const E=`http://localhost:${a}`;s.registry.appendRoutes(e.wsKey,[{host:t,prefix:o,project:observability_1.LENS_DASHBOARD_PROJECT,target:E}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:o,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString(),...e.instance?{instance:{name:e.instance.name,baseKey:e.instance.baseKey,apex:e.instance.apex}}:{}}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${o}/)`),p("info",`lens dashboard up: ${t}${o} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:o});const g=(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),d={LENSMCP_EVENT_FILE:l,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey,LENSMCP_HOSTED_GUEST:"1",LENSMCP_REPLAY_WORKSPACE:e.instance?.baseKey??e.wsKey,...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},LENSMCP_GATEWAY_PORTS:N};let $=!1,v=!1;const y=c(()=>{const S=m.get(i);S&&U(S),m.delete(i)},"retireHolder"),u=(0,hosted_dashboard_1.createHostedDashboard)(e.wsKey,{idleMs:(0,hosted_dashboard_1.dashboardIdleMs)(),start:c(async()=>{C.delete(i),v&&await waitForPortFree(a,types_1.PORT_FREE_WAIT_MS,{isPortFree:w,intervalMs:types_1.PORT_FREE_POLL_MS}),!($||s.stopped())&&D(i,g,[r],d,!1,void 0,e.root,a)},"start"),stop:c(()=>{v=!0,C.add(i),y()},"stop"),listening:c(()=>probePortListening(a,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)),"listening"),servicesUp:c(()=>(s.registry.get(e.wsKey)?.services??[]).some(S=>S.state!=="down"),"servicesUp")});return W.set(E,u),ue(),u.tick(),()=>{$=!0,u.dispose(),W.get(E)===u&&W.delete(E),C.add(i),X.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey),y()}},"hostWorkspaceDashboard"),Le=c(async e=>{const r=new Map;for(const o of e.routes){if(!o.lens)continue;const a=r.get(o.lens.project)??[];a.push(o),r.set(o.lens.project,a)}if(r.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of r){const i=a[0].lens;if(i.started)continue;const l=await B(i.port,O),E=i.port;for(const d of a)d.lens.started=!0,d.lens.port=l,d.target=`http://localhost:${l}`;const g=ne({wsKey:e.wsKey,replayWorkspace:e.instance?.baseKey??e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:E});t.push(g),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{se(g,l),p("info",`lens app started: ${e.wsKey}/${o} (vite :${l})`,`cluster-lens-app:${e.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:l},void 0,n)}catch(d){O.delete(l),g.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${d.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])H(a,!0);for(const a of o.routes)a.lens&&K.delete(a.lens);K.delete(o.frontend),x.delete(o.key),V.delete(o.key),Y.delete(o.key),M.delete(o.key)}}},"hostWorkspaceApps"),Re=c(async(e=2500)=>{F&&(clearInterval(F),F=void 0),I&&(clearInterval(I),I=void 0);const r=[...A];for(const o of r)signalChild(o,"SIGTERM");const n=c(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&r.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of r)n(o)&&signalChild(o,"SIGKILL")},"reapAll"),ie=c(()=>Se({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:D,bootSingletonsAndApps:ve,hostWorkspaceDashboard:Pe,hostWorkspaceApps:Le,probeLensFrontends:ie,reapAll:Re}}L(createLensChildren,"createLensChildren"),c(createLensChildren,"createLensChildren");
1
+ "use strict";var me=Object.defineProperty;var L=(s,h)=>me(s,"name",{value:h,configurable:!0});var ve=Object.defineProperty,c=L((s,h)=>ve(s,"name",{value:h,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.describeChildExit=describeChildExit,exports.signalChild=signalChild,exports.claimPort=claimPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),own_log_1=require("./own-log"),process_name_1=require("../../../process-name"),pod_env_1=require("./pod-env"),control_1=require("./control"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),hosted_dashboard_1=require("./hosted-dashboard"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4,DASHBOARD_SWEEP_MS=15e3;function tryBind(s,h){return new Promise(f=>{const _=net.createServer();_.unref(),_.once("error",p=>{const w=p.code??"";f(w==="EADDRINUSE"||w==="EACCES"?"in-use":"unavailable")}),_.listen(h===void 0?{port:s}:{port:s,host:h},()=>_.close(()=>f("free")))})}L(tryBind,"tryBind"),c(tryBind,"tryBind");async function probePortFree(s){for(const h of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(s,h)==="in-use")return!1;return!0}L(probePortFree,"probePortFree"),c(probePortFree,"probePortFree");function probeConnect(s,h,f){return new Promise(_=>{let p=!1;const w=net.createConnection({host:s,port:h});w.unref();const b=setTimeout(()=>R(!1),f);b.unref?.();function R(H){p||(p=!0,clearTimeout(b),w.destroy(),_(H))}L(R,"M"),c(R,"finish"),w.once("connect",()=>R(!0)),w.once("error",()=>R(!1))})}L(probeConnect,"probeConnect"),c(probeConnect,"probeConnect");async function probePortListening(s,h=1e3){return await probeConnect("127.0.0.1",s,h)?!0:probeConnect("::1",s,h)}L(probePortListening,"probePortListening"),c(probePortListening,"probePortListening");async function probeWatcherHealth(s,h=1500){try{const f=new AbortController,_=setTimeout(()=>f.abort(),h);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${s}/__lensmcp/watcher-health`,{signal:f.signal});if(!p.ok)return null;const w=await p.json();if(typeof w?.lastFsSetEventAt!="number")return null;const b=Array.isArray(w.recentEvents)?w.recentEvents.filter(R=>R&&typeof R.path=="string"&&typeof R.at=="number"&&typeof R.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(R.kind)):[];return{schemaVersion:w.schemaVersion===2?2:1,ready:w.ready!==!1,sequence:typeof w.sequence=="number"?w.sequence:0,lastFsSetEventAt:w.lastFsSetEventAt,fsSetEvents:typeof w.fsSetEvents=="number"?w.fsSetEvents:0,recentEvents:b}}finally{clearTimeout(_)}}catch{return null}}L(probeWatcherHealth,"probeWatcherHealth"),c(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=c(s=>new Promise(h=>{setTimeout(h,s).unref?.()}),"sleepUnref");async function waitForPortFree(s,h,f={}){const _=f.isPortFree??probePortFree,p=Math.max(1,f.intervalMs??types_1.PORT_FREE_POLL_MS),w=Date.now()+Math.max(0,h);for(;;){if(await _(s))return!0;const b=w-Date.now();if(b<=0)return!1;await sleepUnref(Math.min(p,b))}}L(waitForPortFree,"waitForPortFree"),c(waitForPortFree,"waitForPortFree");function viteArgPort(s){const h=c(f=>Number.isInteger(f)&&f>0&&f<65536?f:void 0,"valid");for(let f=0;f<s.length;f+=1){const _=s[f];if(_==="--port")return h(Number(s[f+1]));if(_.startsWith("--port="))return h(Number(_.slice(7)))}}L(viteArgPort,"viteArgPort"),c(viteArgPort,"viteArgPort");function describeChildExit(s){return s.signalCode?`killed by ${s.signalCode}`:s.exitCode===null?"exit status unknown":`exit code ${s.exitCode}`}L(describeChildExit,"describeChildExit"),c(describeChildExit,"describeChildExit");function signalChild(s,h){if(s.pid===void 0)return!1;try{return s.kill(h)}catch{return!1}}L(signalChild,"signalChild"),c(signalChild,"signalChild");async function claimPort(s,h,f,_){let p=s;for(;h.has(p)||_?.has(p)||!await f(p);)p+=1;return h.add(p),p}L(claimPort,"claimPort"),c(claimPort,"claimPort");function createLensChildren(s,h,f,_){const{emit:p}=h,w=_?.isPortFree??probePortFree,b=(f.ports?.length?f.ports:[443]).join(","),R=(0,control_1.clusterVersion)(),H={LENSMCP_GATEWAY_PID:String(process.pid),...R?{LENSMCP_GATEWAY_VERSION:R}:{}},ne=_?.probeWatcherHealth??probeWatcherHealth,j=[],Y=new Map,A=new Map,x=new Map,z=new Map,q=new Set;let F;const m=new Map,C=new Set,X=new Set,J=new Set,W=new Map;s.hostedDashboards=W;let I;const fe=c(()=>{I||(I=setInterval(()=>{if(!s.stopped())for(const e of W.values())e.tick()},DASHBOARD_SWEEP_MS),I.unref?.())},"startDashboardSweeper"),T=new Set,M=new Map,K=new WeakMap,U=new Set,re=c((e,r,n)=>{if(U.has(e))return;U.add(e);const t=setInterval(()=>{(async()=>{if(s.stopped()||C.has(e)){clearInterval(t),U.delete(e);return}await w(r)&&(clearInterval(t),U.delete(e),console.log(`[gateway] lens child ${e}: :${r} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),B=c(async(e,r,n)=>{const t=await claimPort(e,r,w,n?.reserved);return t!==e&&(n?.heldBy?console.log(`[gateway] handoff: :${e} is held by ${n.heldBy} \u2014 replacement on :${t}.`):console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${t} instead.`)),t},"claimFreePort"),ge=c((e,r,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...M.get(t)?.generations??[]].find(i=>i.generation===o);a&&(e.endsWith(":vite")?a.vite=r:e.endsWith(":browser-capture")&&(a.capture=r))},"attachRespawnedGenerationChild"),V=c(e=>{e.exitCode!==null||e.signalCode!==null||e.pid===void 0||(X.add(e),signalChild(e,"SIGTERM"),setTimeout(()=>{e.exitCode===null&&e.signalCode===null&&signalChild(e,"SIGKILL")},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.())},"retireChild"),he=c((e,r)=>{const n=m.get(e);!n||n===r||n.exitCode!==null||n.signalCode!==null||(console.warn(`[gateway] lens child ${e}: a replacement took the label \u2014 retiring the previous child (pid ${n.pid??"?"}).`),V(n))},"retireLabelHolder"),k=c((e,r,n,t,o=!1,a,i,l)=>{const E=Date.now(),g=(0,own_log_1.gatewayLogTakeover)()!==void 0,d=(0,node_child_process_1.spawn)(r,n,{cwd:i??s.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,s.root,i??s.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:g?["inherit","pipe","pipe"]:"inherit"});g&&(d.stdout?.on("data",P=>process.stdout.write(P)),d.stderr?.on("data",P=>process.stderr.write(P))),j.push(d),he(e,d),m.set(e,d),ge(e,d,t);const $=e==="vite"||e.endsWith(":vite"),v=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=v&&v!==s.eventFile?v:void 0,u=$?viteArgPort(n):l;$&&A.set(d,{key:t?.LENSMCP_FRONTEND_KEY??`${i??s.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??s.root,spawnedAt:E,port:u,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...y?{bus:y}:{}});const S=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return S&&u!==void 0&&(p("info",`lens child starting: ${S} (:${u})`,`cluster-lens-child:${S}`,{kind:"lens-child-starting",project:S,label:e,pid:d.pid,port:u},void 0,y),(async()=>{const P=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<P;){if(d.exitCode!==null||d.signalCode!==null||C.has(e))return;if(await probePortListening(u,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${S} (:${u})`,`cluster-lens-child:${S}`,{kind:"lens-child-up",project:S,label:e,pid:d.pid,port:u},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),d.on("error",P=>{if(d.pid!==void 0){console.warn(`[gateway] lens child ${e}: ${P.message}`);return}const D=j.indexOf(d);D>=0&&j.splice(D,1),m.get(e)===d&&m.delete(e),A.delete(d),X.delete(d),console.error(`[gateway] lens child ${e} failed to spawn (${P.message}) \u2014 not started, not retried.`)}),d.on("exit",(P,D)=>{const de=j.indexOf(d);de>=0&&j.splice(de,1),m.get(e)===d&&m.delete(e);const O=A.get(d);if(A.delete(d),X.delete(d))return;const Ne=C.delete(e);if(s.stopped()||Ne||(S?p("warning",`lens child exited: ${S}`,`cluster-lens-child:${S}`,{kind:"lens-child-exit",project:S,label:e,pid:d.pid,code:P,signal:D},void 0,y):O&&p("warning",`lens frontend exited: ${O.project}`,`cluster-lens-child:${O.project}`,{kind:"lens-child-exit",project:O.project,label:e,pid:d.pid,code:P,signal:D,generation:O.generation,port:O.port,ranMs:Date.now()-E},void 0,y),O&&Se(d,O)))return;const pe=Date.now()-E,N=Y.get(e)??{attempts:0,portConflicts:0};(async()=>{const be=u!==void 0&&!await w(u),ue=(0,lifecycle_1.isPortConflictExit)({code:P,signal:D,ranMs:pe,portHeld:be},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ue&&N.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)N.portConflicts+=1,Y.set(e,N),console.warn(`[gateway] lens child ${e} lost the :${u} bind race (EADDRINUSE, code=${P}); waiting for the port, then retrying (port-retry ${N.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(ue){N.portConflicts=0,Y.set(e,N),console.warn(`[gateway] lens child ${e}: :${u} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${u} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:u}),re(e,u,()=>k(e,r,n,t,o,a,i,l));return}else{N.portConflicts=0,pe>=HEAL_HEALTHY_MS&&(N.attempts=0);const Ee=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**N.attempts);N.attempts+=1,Y.set(e,N),console.warn(`[gateway] lens child ${e} exited (code=${P} sig=${D}); auto-healing in ${Math.round(Ee/1e3)}s (attempt ${N.attempts}).`),await sleepUnref(Ee)}s.stopped()||C.has(e)||(u!==void 0&&!await waitForPortFree(u,types_1.PORT_FREE_WAIT_MS,{isPortFree:w,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${u} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(s.stopped()||C.has(e))&&k(e,r,n,t,o,a,i,l))})().catch(()=>{})}),d},"spawnManagedChild"),se=c(e=>{const r=`${e.wsKey}:${e.project}`,n=M.get(r);if(n)return n;const t={key:r,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};M.set(r,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,K.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,K.set(e.frontend,t),t},"registerFrontend"),we=c(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const r=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==r);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),ae=c((e,r,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;C.delete(a),C.delete(i);const l=c((d,$,v,y,u,S)=>k(d,$,v,y,u,S,e.workspaceRoot),"spawnInRoot"),E=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:r,chrome:!0,headless:!0,viteEnv:{...e.frontend.env??{},...e.frontend.frozen?{LENSMCP_FREEZE:"1"}:{},LENSMCP_PROJECT:e.project,...e.replayWorkspace?{LENSMCP_REPLAY_WORKSPACE:e.replayWorkspace}:{},LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n,...we(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:c(d=>console.log(d),"log")},l),g={ownerKey:e.key,generation:t,port:r,labelPrefix:o,vite:E?.vite??m.get(a),capture:m.get(i)};return e.generations.add(g),g},"spawnFrontendGeneration"),G=c((e,r)=>{if(!e)return;M.get(e.ownerKey)?.generations.delete(e);const n=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,o]of n)C.add(t),o&&V(o);r&&T.delete(e.port)},"terminateGeneration"),_e=c(async e=>{const r=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<r;){const n=e.vite;if(!n||n.exitCode!==null||n.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await ne(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),Z=c(async e=>{const r=e?.vite;return!!(e&&r&&r.exitCode===null&&r.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),Q=c(e=>!e.disposed&&!s.stopped()&&M.get(e.key)===e,"frontendIsCurrent"),ee=c((e,r)=>{if(!Q(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=r,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const l=new Set;for(const u of M.values())u!==e&&l.add(u.desiredPort);const E=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,g=await B(e.desiredPort,T,{reserved:l,heldBy:E});if(a=g,!Q(e))return T.delete(g),!1;o=ae(e,g,"replacement"),e.candidate=o;const d=await Z(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${g} reason=${r}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:g,oldServing:d},void 0,e.eventFile===s.eventFile?void 0:e.eventFile);const $=await _e(o);if(!Q(e))return G(o,!0),e.candidate=void 0,!1;if(!$){G(o,!0),e.candidate=void 0;const u=await Z(n);return e.state=u?"ready":"failed",u&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${o.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,port:g,durationMs:Date.now()-i,oldKept:u},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}for(const u of e.routes)u.target=`http://localhost:${o.port}`,u.lens&&(u.lens.port=o.port);e.frontend.port=o.port,e.active=o,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const v=o.vite?A.get(o.vite):void 0;v&&(v.role="active",v.listening=!0);const y=(0,hmr_reload_1.broadcastFullReload)(s,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${o.generation} :${o.port} in ${Date.now()-i}ms; draining g${n?.generation??0}${y>0?`; reloading ${y} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:y},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),n){const u=`${n.labelPrefix}browser-capture`;C.add(u),n.capture&&V(n.capture),setTimeout(()=>G(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){G(o,!0),!o&&a!==void 0&&T.delete(a),e.candidate=void 0;const E=await Z(n);e.state=E?"ready":"failed",E&&(e.reason=void 0,e.startedAt=void 0);const g=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${g}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:E,error:g},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),ie=c((e,r)=>{e.active=ae(e,r,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),Se=c((e,r)=>{const n=M.get(r.key);return!n||types_1.LENS_RESTART_MODE==="in-place"?!1:(n.candidate?.vite===e||n.active?.vite!==e||(n.state="starting",n.reason="child-exit",n.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${n.key} g${r.generation} :${r.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),ee(n,"child-exit")),!0)},"handleManagedViteExit");s.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const r=K.get(e);return r?{state:r.state,generation:r.candidate?.generation??r.active?.generation??r.generation,reason:r.reason,startedAt:r.startedAt}:void 0},recover(e,r){const n=K.get(e);return n?ee(n,r):Promise.resolve(!1)}}:void 0;let te=!1,le=0;const ye=c(()=>{te||Date.now()-le<types_1.POD_STALE_SCAN_MS||(te=!0,le=Date.now(),(async()=>{const e=new Map;for(const r of[...M.values()]){if(s.stopped())return;let n=e.get(r.workspaceRoot);if(!n){const g=(0,scope_1.readSourceSetOptions)(r.workspaceRoot);n={pass:(0,lifecycle_1.sourceScanPass)(r.workspaceRoot,g.exclude,{ignoreTests:g.ignoreTests}),startedAt:Date.now()},e.set(r.workspaceRoot,n)}const t=n.startedAt,o=await n.pass.snapshot(r.sourceDirs);if(M.get(r.key)!==r)continue;const a=r.sourceSnapshot,i=r.sourceScanStartedAt;if(r.sourceSnapshot=o,r.sourceScanStartedAt=t,!a)continue;const l=Date.now(),E=(0,lifecycle_1.diffSourceSets)(a,o,l,(0,lifecycle_1.sourceChangeWindowMs)(l,i,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));E&&(r.sourceChange=E,p("info",`source file set changed for ${r.key}`,`cluster-lens-app:${r.project}`,{kind:"source-set-changed",scoped:!0,wsKey:r.wsKey,project:r.project,added:E.added,removed:E.removed,truncated:E.truncated},void 0,r.eventFile===s.eventFile?void 0:r.eventFile))}})().catch(()=>{}).finally(()=>{te=!1}))},"scanFrontendSourceSets");let oe=!1;const Pe=c(async(e,r)=>{if(!oe){oe=!0;try{for(const[n,t]of[...A]){if(s.stopped())return;if(t.port===void 0||n.exitCode!==null||n.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const l=M.get(t.key);l?.active?.vite===n&&(l.state="ready",l.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const o=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<o){t.probeFails=0;continue}if(!r)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},x.get(t.key)??0,Date.now(),e);if(!a)continue;const i=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(o/1e3)}s boot allowance`;x.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${i} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus),signalChild(n,"SIGKILL")}}finally{oe=!1}}},"probeLensChildren"),Le=c(()=>{if(F||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};F=setInterval(()=>{if(s.stopped()||A.size===0||(ce(),!types_1.POD_RECYCLE_ENABLED))return;ye();const r=Date.now();for(const[n,t]of A){const o=M.get(t.key);if(o?.frontend.frozen)continue;const a=o?.sourceChange?.changedAt??0,i=x.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,r,e)&&((z.get(t.key)??0)>=a||q.has(t.key)||(q.add(t.key),(async()=>{try{const l=o?.sourceChange,E=l&&!l.truncated?[...l.added,...l.removed].map(v=>path.resolve(t.workspaceRoot,v)):[],g=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await ne(t.port),a,l?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,E):"unknown";if(g==="heard"){z.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(g==="unknown"&&types_1.LENS_WATCHER_UNVERIFIED==="keep"){console.log(`[gateway] stale source set: lens app ${t.project} unverifiable (no watcher testimony) \u2014 keeping it (LENSMCP_LENS_WATCHER_UNVERIFIED=keep).`);return}if(n.exitCode!==null||n.signalCode!==null)return;x.set(t.key,Date.now());const d=M.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&d){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${g==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:d.wsKey,project:t.project,reason:"stale-source-set",watcher:g,added:l?.added,removed:l?.removed},void 0,t.bus),await ee(d,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${g==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:g},void 0,t.bus);const $=(0,hmr_reload_1.broadcastFullReload)(s,t.key);$>0&&console.log(`[gateway] \u2192 told ${$} open tab(s) to reload (stale optimize-dep generation)`),signalChild(n,"SIGTERM"),setTimeout(()=>{n.exitCode===null&&n.signalCode===null&&(console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`),signalChild(n,"SIGKILL"))},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{q.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),F.unref?.()},"startLensSweeper"),Re=c(async()=>{const e=[...new Map(s.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],r=f.dashboard!==!1,n=f.mcp!==!1&&e.length>0;if(r){const t=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const o=(0,scope_1.readLensScope)(s.root),a=await B(f.dashboardPort??o.dashboardPort,J),i=f.lensHost??"lensmcp.local",l=o.basePath;s.registry.appendRoutes(s.wsKey,[{host:i,prefix:l,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),s.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:s.root,base:l,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${i}${l}/)`),p("info",`lens dashboard up: ${i}${l} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:l}),k("lens-dashboard",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"dashboard")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:l,LENSMCP_REPLAY_WORKSPACE:o.key,LENSMCP_GATEWAY_PORTS:b,...H},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(s.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const o=(0,scope_1.readLensScope)(s.root),a=Number(process.env.LENSMCP_PORT??o.mcpHttpPort),i=c(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),k("lens-mcp",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"mcp")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_REPLAY_WORKSPACE:o.key,LENSMCP_WORKSPACE_ROOT:s.root},!1,void 0,void 0,a)},"spawnMcp");await w(a)?i():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),re("lens-mcp",a,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(s.registry.get(s.wsKey)?.routes??s.routes).filter(E=>E.lens?.project===t.project),i=await B(t.port,T);for(const E of a)E.lens&&(E.lens.port=i),E.target=`http://localhost:${i}`;t.port=i;const l=se({wsKey:s.wsKey,replayWorkspace:s.wsKey,project:t.project,workspaceRoot:s.root,projectRoot:t.projectRoot,eventFile:s.eventFile,routes:a,frontend:t,desiredPort:o});try{ie(l,i),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(E){T.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${E.message}`)}}Le()},"bootSingletonsAndApps"),Ce=c(async e=>{if(f.dashboard===!1)return;const r=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!r)return;const n=(0,scope_1.readLensScope)(e.root),t=f.lensHost??"lensmcp.local",o=n.basePath,a=await B(n.dashboardPort,J),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");C.delete(i);const E=`http://localhost:${a}`;s.registry.appendRoutes(e.wsKey,[{host:t,prefix:o,project:observability_1.LENS_DASHBOARD_PROJECT,target:E}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:o,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString(),...e.instance?{instance:{name:e.instance.name,baseKey:e.instance.baseKey,apex:e.instance.apex}}:{}}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${o}/)`),p("info",`lens dashboard up: ${t}${o} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:o});const g=(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),d={LENSMCP_EVENT_FILE:l,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey,LENSMCP_HOSTED_GUEST:"1",LENSMCP_REPLAY_WORKSPACE:e.instance?.baseKey??e.wsKey,...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},LENSMCP_GATEWAY_PORTS:b,...H};let $=!1,v=!1;const y=c(()=>{const S=m.get(i);S&&V(S),m.delete(i)},"retireHolder"),u=(0,hosted_dashboard_1.createHostedDashboard)(e.wsKey,{idleMs:(0,hosted_dashboard_1.dashboardIdleMs)(),start:c(async()=>{C.delete(i),v&&await waitForPortFree(a,types_1.PORT_FREE_WAIT_MS,{isPortFree:w,intervalMs:types_1.PORT_FREE_POLL_MS}),!($||s.stopped())&&k(i,g,[r],d,!1,void 0,e.root,a)},"start"),stop:c(()=>{v=!0,C.add(i),y()},"stop"),listening:c(()=>probePortListening(a,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)),"listening"),servicesUp:c(()=>(s.registry.get(e.wsKey)?.services??[]).some(S=>S.state!=="down"),"servicesUp")});return W.set(E,u),fe(),u.tick(),()=>{$=!0,u.dispose(),W.get(E)===u&&W.delete(E),C.add(i),J.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey),y()}},"hostWorkspaceDashboard"),Me=c(async e=>{const r=new Map;for(const o of e.routes){if(!o.lens)continue;const a=r.get(o.lens.project)??[];a.push(o),r.set(o.lens.project,a)}if(r.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of r){const i=a[0].lens;if(i.started)continue;const l=await B(i.port,T),E=i.port;for(const d of a)d.lens.started=!0,d.lens.port=l,d.target=`http://localhost:${l}`;const g=se({wsKey:e.wsKey,replayWorkspace:e.instance?.baseKey??e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:E});t.push(g),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{ie(g,l),p("info",`lens app started: ${e.wsKey}/${o} (vite :${l})`,`cluster-lens-app:${e.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:l},void 0,n)}catch(d){T.delete(l),g.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${d.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])G(a,!0);for(const a of o.routes)a.lens&&K.delete(a.lens);K.delete(o.frontend),x.delete(o.key),z.delete(o.key),q.delete(o.key),M.delete(o.key)}}},"hostWorkspaceApps"),$e=c(async(e=2500)=>{F&&(clearInterval(F),F=void 0),I&&(clearInterval(I),I=void 0);const r=[...j];for(const o of r)signalChild(o,"SIGTERM");const n=c(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&r.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of r)n(o)&&signalChild(o,"SIGKILL")},"reapAll"),ce=c(()=>Pe({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:k,bootSingletonsAndApps:Re,hostWorkspaceDashboard:Ce,hostWorkspaceApps:Me,probeLensFrontends:ce,reapAll:$e}}L(createLensChildren,"createLensChildren"),c(createLensChildren,"createLensChildren");
@@ -1,3 +1,3 @@
1
- "use strict";var v=Object.defineProperty;var f=(s,u)=>v(s,"name",{value:u,configurable:!0});var $=Object.defineProperty,i=f((s,u)=>$(s,"name",{value:u,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EDGE_DURATION_SAMPLES=exports.LENS_DASHBOARD_PROJECT=void 0,exports.createObservability=createObservability;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),headless_flow_1=require("@lensmcp/core/headless-flow"),redact_1=require("@lensmcp/core/redact"),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})();exports.LENS_DASHBOARD_PROJECT="(lens-dashboard)",exports.EDGE_DURATION_SAMPLES=64;function ownerOf(s){return s?.svc?{root:s.svc.root,wsKey:s.svc.wsKey}:s?.lens?{...s.lens.workspaceRoot?{root:s.lens.workspaceRoot}:{},...s.lens.workspaceKey?{wsKey:s.lens.workspaceKey}:{}}:{}}f(ownerOf,"ownerOf"),i(ownerOf,"ownerOf");function busFileFor(s,u){const{root:l}=ownerOf(s);if(!l)return;const w=path.join(l,".lensmcp","events.jsonl");return w===u?void 0:w}f(busFileFor,"busFileFor"),i(busFileFor,"busFileFor");const hdr=i(s=>typeof s=="string"&&s!==""?s:Array.isArray(s)?s[0]:void 0,"hdr");function createObservability(s,u){const l=i(()=>Date.now().toString(36)+Math.random().toString(36).slice(2,12),"eid"),w=busHygiene?.createEventFloodGuard({burst:30,perSec:5}),g=new Map,S=i((e,t)=>{try{const r=g.get(e);r&&(fs.appendFileSync(e,JSON.stringify({id:l(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:"warning",context:{sessionId:"pending"},fingerprint:"cluster-gateway:bus-dropped",title:`lens bus: ${r.count} event${r.count===1?"":"s"} could not be written since ${new Date(r.since).toISOString()} (${r.code}) \u2014 they are lost`,raw:{kind:"bus-events-dropped",project:"gateway",dropped:r.count,since:r.since,until:Date.now(),code:r.code}})+`
2
- `),g.delete(e),console.warn(`[gateway] the lens bus ${e} takes events again \u2014 ${r.count} were lost (${r.code})`)),fs.appendFileSync(e,t)}catch(r){const a=g.get(e);if(a){a.count+=1;return}const o=r?.code??"EIO";g.set(e,{count:1,since:Date.now(),code:o}),console.warn(`[gateway] the lens bus ${e} cannot be written (${o}) \u2014 counting the events lost until it can`)}},"appendCounted"),y=i((e,t,r,a,o,d)=>{const n=d??s;try{const c=w?w.admit(r):{ok:!0,suppressed:0};if(!c.ok)return;fs.mkdirSync(path.dirname(n),{recursive:!0});const p=a&&(0,redact_1.redactDeep)(a),h=JSON.stringify({id:l(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:e,context:(0,redact_1.redactDeep)({sessionId:"pending",...o}),fingerprint:r,title:(0,redact_1.safeTitle)(t),raw:c.suppressed>0?{...p,floodSuppressed:c.suppressed}:p})+`
3
- `;busHygiene?.maybeRotateEventFile(n,h.length),S(n,h)}catch{}},"emit"),E=i(e=>{const t=hdr(e.headers["x-lensmcp-flow-id"]);let r=hdr(e.headers["x-request-id"]);if(!(!t&&!r&&!hdr(e.headers.traceparent)))return r||(r=l(),e.headers["x-request-id"]=r),{startedAt:Date.now(),method:(e.method??"GET").toUpperCase(),host:(e.headers.host||"").split(":")[0],url:e.url??"/",steps:[],...t?{flowId:t}:{},...r?{requestId:r}:{},...hdr(e.headers["x-lensmcp-parent-request-id"])?{parentRequestId:hdr(e.headers["x-lensmcp-parent-request-id"])}:{},...hdr(e.headers["x-lensmcp-origin-node-id"])?{originNodeId:hdr(e.headers["x-lensmcp-origin-node-id"])}:{}}},"beginTrace"),D=i((e,t,r,a)=>{if(r?.flowId||!t.pool||t.internal||t.project===exports.LENS_DASHBOARD_PROJECT||!(0,headless_flow_1.headlessFlowsEnabled)())return r;const o=e.url??"/";if((0,headless_flow_1.isProbePath)(o))return r;const d=hdr(e.headers["x-request-id"]);if(!d)return r;const n=(0,headless_flow_1.headlessFlowId)(d);e.headers["x-lensmcp-flow-id"]=n;const c=(e.method??"GET").toUpperCase(),p=(e.headers.host||"").split(":")[0],h=`request:${c} ${o.split("?")[0]}`,I=r??{startedAt:a,method:c,host:p,url:o,steps:[{step:"received",atMs:0,host:p,method:c,url:o}],requestId:d};return I.flowId=n,I.originNodeId=h,y("info",`${c} ${o.split("?")[0]}`,`gateway-origin:${t.project}`,{kind:"flow-origin",originNodeId:h,project:t.project,host:p,method:c,url:o,...ownerOf(t).wsKey?{wsKey:ownerOf(t).wsKey}:{}},{flowId:n,requestId:d,originType:"request",originNodeId:h},busFileFor(t,s)),I},"startHeadlessFlow"),b=i((e,t,r)=>{e&&e.steps.push({step:t,atMs:Date.now()-e.startedAt,...r})},"traceStep"),_=i((e,t,r,a,o)=>{if(!e||e.done||(e.done=!0,t===exports.LENS_DASHBOARD_PROJECT&&r<500))return;const d=Date.now()-e.startedAt;y(r>=500?"error":"info",`gateway ${e.method} ${e.host}${e.url} \u2192 ${r} (${d}ms)`,`gateway-request:${t}`,{kind:"gateway-request",project:t,host:e.host,method:e.method,url:e.url,status:r,durationMs:d,startedAt:e.startedAt,steps:e.steps,...ownerOf(o).wsKey?{wsKey:ownerOf(o).wsKey}:{},...a},{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},...e.originNodeId?{originNodeId:e.originNodeId}:{}},busFileFor(o,s))},"finishTrace"),m=new Map;return{emit:y,eid:l,beginTrace:E,startHeadlessFlow:D,traceStep:b,finishTrace:_,recordEdge:i((e,t,r,a)=>{if(e.project===exports.LENS_DASHBOARD_PROJECT&&r<500&&r!==0)return;const o=e.host??"(default)",d=(a?a+"\u21D2":"")+o+"\u2192"+e.project,n=m.get(d)??{host:o,project:e.project,caller:a,count:0,totalMs:0,errors:0,durations:[],...busFileFor(e,s)?{busFile:busFileFor(e,s)}:{},...ownerOf(e).wsKey?{wsKey:ownerOf(e).wsKey}:{}};if(n.count+=1,n.totalMs+=t,n.durations.length<exports.EDGE_DURATION_SAMPLES)n.durations.push(Math.round(t));else{const c=Math.floor(Math.random()*n.count);c<exports.EDGE_DURATION_SAMPLES&&(n.durations[c]=Math.round(t))}(r>=500||r===0)&&(n.errors+=1),m.set(d,n)},"recordEdge"),startEdgeFlusher:i(()=>{const e=setInterval(()=>{for(const t of m.values()){const r=Math.round(t.totalMs/t.count);y(t.errors?"warning":"info",`traffic ${t.host} \u2192 ${t.project}: ${t.count} req, ~${r}ms${t.errors?`, ${t.errors} errors`:""}`,`gateway-traffic:${t.host}->${t.project}`,{kind:"gateway-traffic",host:t.host,project:t.project,caller:t.caller,count:t.count,avgMs:r,errors:t.errors,durationsMs:t.durations,...t.wsKey?{wsKey:t.wsKey}:{}},void 0,t.busFile)}m.clear()},u.trafficFlushMs);return e.unref(),e},"startEdgeFlusher")}}f(createObservability,"createObservability"),i(createObservability,"createObservability");
1
+ "use strict";var q=Object.defineProperty;var f=(s,u)=>q(s,"name",{value:u,configurable:!0});var $=Object.defineProperty,d=f((s,u)=>$(s,"name",{value:u,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EDGE_DURATION_SAMPLES=exports.LENS_DASHBOARD_PROJECT=void 0,exports.createObservability=createObservability;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),headless_flow_1=require("@lensmcp/core/headless-flow"),http_exchange_1=require("@lensmcp/core/http-exchange"),redact_1=require("@lensmcp/core/redact"),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})();exports.LENS_DASHBOARD_PROJECT="(lens-dashboard)",exports.EDGE_DURATION_SAMPLES=64;function ownerOf(s){return s?.svc?{root:s.svc.root,wsKey:s.svc.wsKey}:s?.lens?{...s.lens.workspaceRoot?{root:s.lens.workspaceRoot}:{},...s.lens.workspaceKey?{wsKey:s.lens.workspaceKey}:{}}:{}}f(ownerOf,"ownerOf"),d(ownerOf,"ownerOf");function busFileFor(s,u){const{root:l}=ownerOf(s);if(!l)return;const w=path.join(l,".lensmcp","events.jsonl");return w===u?void 0:w}f(busFileFor,"busFileFor"),d(busFileFor,"busFileFor");const hdr=d(s=>typeof s=="string"&&s!==""?s:Array.isArray(s)?s[0]:void 0,"hdr"),lensRequestId=d(s=>hdr(s.headers[http_exchange_1.LENS_REQUEST_ID_HEADER])??hdr(s.headers["x-request-id"]),"lensRequestId");function createObservability(s,u){const l=d(()=>Date.now().toString(36)+Math.random().toString(36).slice(2,12),"eid"),w=busHygiene?.createEventFloodGuard({burst:30,perSec:5}),g=new Map,E=d((e,t)=>{try{const r=g.get(e);r&&(fs.appendFileSync(e,JSON.stringify({id:l(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:"warning",context:{sessionId:"pending"},fingerprint:"cluster-gateway:bus-dropped",title:`lens bus: ${r.count} event${r.count===1?"":"s"} could not be written since ${new Date(r.since).toISOString()} (${r.code}) \u2014 they are lost`,raw:{kind:"bus-events-dropped",project:"gateway",dropped:r.count,since:r.since,until:Date.now(),code:r.code}})+`
2
+ `),g.delete(e),console.warn(`[gateway] the lens bus ${e} takes events again \u2014 ${r.count} were lost (${r.code})`)),fs.appendFileSync(e,t)}catch(r){const a=g.get(e);if(a){a.count+=1;return}const o=r?.code??"EIO";g.set(e,{count:1,since:Date.now(),code:o}),console.warn(`[gateway] the lens bus ${e} cannot be written (${o}) \u2014 counting the events lost until it can`)}},"appendCounted"),y=d((e,t,r,a,o,i)=>{const n=i??s;try{const c=w?w.admit(r):{ok:!0,suppressed:0};if(!c.ok)return;fs.mkdirSync(path.dirname(n),{recursive:!0});const p=a&&(0,redact_1.redactDeep)(a),h=JSON.stringify({id:l(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:e,context:(0,redact_1.redactDeep)({sessionId:"pending",...o}),fingerprint:r,title:(0,redact_1.safeTitle)(t),raw:c.suppressed>0?{...p,floodSuppressed:c.suppressed}:p})+`
3
+ `;busHygiene?.maybeRotateEventFile(n,h.length),E(n,h)}catch{}},"emit"),S=d(e=>{const t=hdr(e.headers["x-lensmcp-flow-id"]);let r=lensRequestId(e);if(!(!t&&!r&&!hdr(e.headers.traceparent)))return r||(r=l(),e.headers["x-request-id"]=r),{startedAt:Date.now(),method:(e.method??"GET").toUpperCase(),host:(e.headers.host||"").split(":")[0],url:e.url??"/",steps:[],...t?{flowId:t}:{},...r?{requestId:r}:{},...hdr(e.headers["x-lensmcp-parent-request-id"])?{parentRequestId:hdr(e.headers["x-lensmcp-parent-request-id"])}:{},...hdr(e.headers["x-lensmcp-origin-node-id"])?{originNodeId:hdr(e.headers["x-lensmcp-origin-node-id"])}:{}}},"beginTrace"),_=d((e,t,r,a)=>{if(r?.flowId||!t.pool||t.internal||t.project===exports.LENS_DASHBOARD_PROJECT||!(0,headless_flow_1.headlessFlowsEnabled)())return r;const o=e.url??"/";if((0,headless_flow_1.isProbePath)(o))return r;const i=lensRequestId(e);if(!i)return r;const n=(0,headless_flow_1.headlessFlowId)(i);e.headers["x-lensmcp-flow-id"]=n;const c=(e.method??"GET").toUpperCase(),p=(e.headers.host||"").split(":")[0],h=`request:${c} ${o.split("?")[0]}`,m=r??{startedAt:a,method:c,host:p,url:o,steps:[{step:"received",atMs:0,host:p,method:c,url:o}],requestId:i};return m.flowId=n,m.originNodeId=h,y("info",`${c} ${o.split("?")[0]}`,`gateway-origin:${t.project}`,{kind:"flow-origin",originNodeId:h,project:t.project,host:p,method:c,url:o,...ownerOf(t).wsKey?{wsKey:ownerOf(t).wsKey}:{}},{flowId:n,requestId:i,originType:"request",originNodeId:h},busFileFor(t,s)),m},"startHeadlessFlow"),D=d((e,t,r)=>{e&&e.steps.push({step:t,atMs:Date.now()-e.startedAt,...r})},"traceStep"),b=d((e,t,r,a,o)=>{if(!e||e.done||(e.done=!0,t===exports.LENS_DASHBOARD_PROJECT&&r<500))return;const i=Date.now()-e.startedAt;y(r>=500?"error":"info",`gateway ${e.method} ${e.host}${e.url} \u2192 ${r} (${i}ms)`,`gateway-request:${t}`,{kind:"gateway-request",project:t,host:e.host,method:e.method,url:e.url,status:r,durationMs:i,startedAt:e.startedAt,steps:e.steps,...ownerOf(o).wsKey?{wsKey:ownerOf(o).wsKey}:{},...a},{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},...e.originNodeId?{originNodeId:e.originNodeId}:{}},busFileFor(o,s))},"finishTrace"),I=new Map;return{emit:y,eid:l,beginTrace:S,startHeadlessFlow:_,traceStep:D,finishTrace:b,recordEdge:d((e,t,r,a)=>{if(e.project===exports.LENS_DASHBOARD_PROJECT&&r<500&&r!==0)return;const o=e.host??"(default)",i=(a?a+"\u21D2":"")+o+"\u2192"+e.project,n=I.get(i)??{host:o,project:e.project,caller:a,count:0,totalMs:0,errors:0,durations:[],...busFileFor(e,s)?{busFile:busFileFor(e,s)}:{},...ownerOf(e).wsKey?{wsKey:ownerOf(e).wsKey}:{}};if(n.count+=1,n.totalMs+=t,n.durations.length<exports.EDGE_DURATION_SAMPLES)n.durations.push(Math.round(t));else{const c=Math.floor(Math.random()*n.count);c<exports.EDGE_DURATION_SAMPLES&&(n.durations[c]=Math.round(t))}(r>=500||r===0)&&(n.errors+=1),I.set(i,n)},"recordEdge"),startEdgeFlusher:d(()=>{const e=setInterval(()=>{for(const t of I.values()){const r=Math.round(t.totalMs/t.count);y(t.errors?"warning":"info",`traffic ${t.host} \u2192 ${t.project}: ${t.count} req, ~${r}ms${t.errors?`, ${t.errors} errors`:""}`,`gateway-traffic:${t.host}->${t.project}`,{kind:"gateway-traffic",host:t.host,project:t.project,caller:t.caller,count:t.count,avgMs:r,errors:t.errors,durationsMs:t.durations,...t.wsKey?{wsKey:t.wsKey}:{}},void 0,t.busFile)}I.clear()},u.trafficFlushMs);return e.unref(),e},"startEdgeFlusher")}}f(createObservability,"createObservability"),d(createObservability,"createObservability");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.25.30",
3
+ "version": "1.25.32",
4
4
  "description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -58,9 +58,9 @@
58
58
  }
59
59
  },
60
60
  "dependencies": {
61
- "@lensmcp/core": "1.25.30",
62
- "@lensmcp/node-instrumentation": "1.25.30",
63
- "@lensmcp/nx-plugin": "1.25.30",
61
+ "@lensmcp/core": "1.25.32",
62
+ "@lensmcp/node-instrumentation": "1.25.32",
63
+ "@lensmcp/nx-plugin": "1.25.32",
64
64
  "fastify": "^5.8.5",
65
65
  "@fastify/reply-from": "^12.6.2",
66
66
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",