@lensmcp/cluster 1.18.12 → 1.19.0

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.
@@ -11,6 +11,15 @@ export declare function probePortFree(port: number): Promise<boolean>;
11
11
  * server as wedged and SIGKILL the whole cluster in a loop. The v6 attempt only runs on the v4 failure
12
12
  * path, so the healthy case stays one connect. */
13
13
  export declare function probePortListening(port: number, timeoutMs?: number): Promise<boolean>;
14
+ /**
15
+ * Ask a lens vite's OWN watcher what it heard — `GET /__lensmcp/watcher-health`, served by
16
+ * `@lensmcp/vite-plugin` inside the pod. `null` on ANY failure (no endpoint = older plugin, refused,
17
+ * timeout, bad JSON): the caller treats `null` as "no testimony" and keeps the legacy kill-first path,
18
+ * so a mixed-version cluster degrades to exactly the old behavior rather than a stuck pod.
19
+ */
20
+ export declare function probeWatcherHealth(port: number, timeoutMs?: number): Promise<{
21
+ lastFsSetEventAt: number;
22
+ } | null>;
14
23
  /**
15
24
  * Poll until `port` is actually BINDABLE, up to `timeoutMs`. Resolves `true` when it frees, `false` on
16
25
  * timeout (the caller then spawns anyway and lets the normal heal handle the fallout).
@@ -56,4 +65,7 @@ export interface LensChildren {
56
65
  }
57
66
  export declare function createLensChildren(rt: GatewayRuntime, obs: Observability, options: GatewayRuntimeOptions, deps?: {
58
67
  isPortFree?: (port: number) => Promise<boolean>;
68
+ probeWatcherHealth?: (port: number) => Promise<{
69
+ lastFsSetEventAt: number;
70
+ } | null>;
59
71
  }): LensChildren;
@@ -1 +1 @@
1
- "use strict";var J=Object.defineProperty;var L=(r,p)=>J(r,"name",{value:p,configurable:!0});var z=Object.defineProperty,i=L((r,p)=>z(r,"name",{value:p,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,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"),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"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(r,p){return new Promise(c=>{const E=net.createServer();E.unref(),E.once("error",u=>{const h=u.code??"";c(h==="EADDRINUSE"||h==="EACCES"?"in-use":"unavailable")}),E.listen(p===void 0?{port:r}:{port:r,host:p},()=>E.close(()=>c("free")))})}L(tryBind,"tryBind"),i(tryBind,"tryBind");async function probePortFree(r){for(const p of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(r,p)==="in-use")return!1;return!0}L(probePortFree,"probePortFree"),i(probePortFree,"probePortFree");function probeConnect(r,p,c){return new Promise(E=>{let u=!1;const h=net.createConnection({host:r,port:p});h.unref();const S=setTimeout(()=>M(!1),c);S.unref?.();function M(C){u||(u=!0,clearTimeout(S),h.destroy(),E(C))}L(M,"M"),i(M,"finish"),h.once("connect",()=>M(!0)),h.once("error",()=>M(!1))})}L(probeConnect,"probeConnect"),i(probeConnect,"probeConnect");async function probePortListening(r,p=1e3){return await probeConnect("127.0.0.1",r,p)?!0:probeConnect("::1",r,p)}L(probePortListening,"probePortListening"),i(probePortListening,"probePortListening");const sleepUnref=i(r=>new Promise(p=>{setTimeout(p,r).unref?.()}),"sleepUnref");async function waitForPortFree(r,p,c={}){const E=c.isPortFree??probePortFree,u=Math.max(1,c.intervalMs??types_1.PORT_FREE_POLL_MS),h=Date.now()+Math.max(0,p);for(;;){if(await E(r))return!0;const S=h-Date.now();if(S<=0)return!1;await sleepUnref(Math.min(u,S))}}L(waitForPortFree,"waitForPortFree"),i(waitForPortFree,"waitForPortFree");function viteArgPort(r){const p=i(c=>Number.isInteger(c)&&c>0&&c<65536?c:void 0,"valid");for(let c=0;c<r.length;c+=1){const E=r[c];if(E==="--port")return p(Number(r[c+1]));if(E.startsWith("--port="))return p(Number(E.slice(7)))}}L(viteArgPort,"viteArgPort"),i(viteArgPort,"viteArgPort");function createLensChildren(r,p,c,E){const{emit:u}=p,h=E?.isPortFree??probePortFree,S=[],M=new Map,C=new Map,A=new Map;let $;const v=new Map,y=new Set,j=new Set,F=new Set,O=new Set,B=i((t,a,s)=>{if(O.has(t))return;O.add(t);const e=setInterval(()=>{(async()=>{if(r.stopped()||y.has(t)){clearInterval(e),O.delete(t);return}await h(a)&&(clearInterval(e),O.delete(t),console.log(`[gateway] lens child ${t}: :${a} freed \u2014 respawning.`),s())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);e.unref?.()},"parkUntilPortFree"),N=i(async(t,a)=>{let s=t;for(;a.has(s)||!await h(s);)s+=1;return a.add(s),s!==t&&console.warn(`[gateway] port ${t} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${s} instead.`),s},"claimFreePort"),b=i((t,a,s,e,n=!1,o,l,d)=>{const P=Date.now(),g=(0,node_child_process_1.spawn)(a,s,{cwd:l??r.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,r.root,l??r.root),...e??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});S.push(g),v.set(t,g);const m=t==="vite"||t.endsWith(":vite"),_=m?viteArgPort(s):d;if(m){const R=typeof e?.LENSMCP_EVENT_FILE=="string"?e.LENSMCP_EVENT_FILE:void 0;C.set(g,{project:e?.LENSMCP_PROJECT??"app",spawnedAt:P,port:_,probeFails:0,listening:!1,...R&&R!==r.eventFile?{bus:R}:{}})}return g.on("exit",(R,w)=>{const T=S.indexOf(g);if(T>=0&&S.splice(T,1),C.delete(g),r.stopped()||y.has(t))return;const I=Date.now()-P,f=M.get(t)??{attempts:0,portConflicts:0};(async()=>{const x=_!==void 0&&!await h(_),D=(0,lifecycle_1.isPortConflictExit)({code:R,signal:w,ranMs:I,portHeld:x},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(D&&f.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)f.portConflicts+=1,M.set(t,f),console.warn(`[gateway] lens child ${t} lost the :${_} bind race (EADDRINUSE, code=${R}); waiting for the port, then retrying (port-retry ${f.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(D){f.portConflicts=0,M.set(t,f),console.warn(`[gateway] lens child ${t}: :${_} 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.`),u("warning",`lens child parked: ${t} (:${_} held externally)`,`cluster-lens-child:${t}`,{kind:"lens-child-parked",label:t,port:_}),B(t,_,()=>b(t,a,s,e,n,o,l,d));return}else{f.portConflicts=0,I>=HEAL_HEALTHY_MS&&(f.attempts=0);const G=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**f.attempts);f.attempts+=1,M.set(t,f),console.warn(`[gateway] lens child ${t} exited (code=${R} sig=${w}); auto-healing in ${Math.round(G/1e3)}s (attempt ${f.attempts}).`),await sleepUnref(G)}r.stopped()||y.has(t)||(_!==void 0&&!await waitForPortFree(_,types_1.PORT_FREE_WAIT_MS,{isPortFree:h,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${t}: :${_} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(r.stopped()||y.has(t))&&b(t,a,s,e,n,o,l,d))})().catch(()=>{})}),g},"spawnManagedChild");let k=!1;const H=i(async(t,a)=>{if(!k){k=!0;try{for(const[s,e]of[...C]){if(r.stopped())return;if(e.port===void 0||s.exitCode!==null||s.signalCode!==null)continue;if(await probePortListening(e.port,types_1.LENS_PROBE_TIMEOUT_MS)){e.probeFails=0,e.listening||(e.listening=!0,u("info",`lens app up: ${e.project} (vite :${e.port} listening)`,`cluster-lens-app:${e.project}`,{kind:"service-up",project:e.project,port:e.port},void 0,e.bus));continue}const n=e.listening?t.graceMs:Math.max(t.graceMs,t.neverListenedGraceMs);if(Date.now()-e.spawnedAt<n){e.probeFails=0;continue}if(!a)continue;e.probeFails+=1;const o=(0,lifecycle_1.lensChildWedged)({spawnedAt:e.spawnedAt,probeFails:e.probeFails,hasListened:e.listening},A.get(e.project)??0,Date.now(),t);if(!o)continue;const l=e.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(n/1e3)}s boot allowance`;A.set(e.project,Date.now()),e.probeFails=0,e.listening=!1,console.error(`[gateway] wedge recycle: lens app ${e.project} is ALIVE but NOT LISTENING on :${e.port} after ${Math.max(2,t.failureThreshold)} probes \u2014 ${l} \u2014 SIGKILL; a fresh vite is coming.`),u("warning",`wedge recycle: ${e.project} (${o})`,`cluster-lens-app:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o,port:e.port},void 0,e.bus);try{s.kill("SIGKILL")}catch{}}}finally{k=!1}}},"probeLensChildren"),W=i(()=>{if($||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const t={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};$=setInterval(()=>{if(r.stopped()||C.size===0||(K(),!types_1.POD_RECYCLE_ENABLED))return;const a=r.sourceSetChangedAt??0;if(a<=0)return;const s=Date.now();for(const[e,n]of C){const o=r.sourceSetChangedFor?.get(n.project)??a,l=A.get(n.project)??0;if(!(0,lifecycle_1.lensFrontendStale)(n.spawnedAt,l,o,s,t))continue;A.set(n.project,s),n.listening=!1,console.log(`[gateway] zombie recycle: lens app ${n.project} (stale-source-set) \u2014 vite missed a file add; restarting it`),u("warning",`zombie recycle: ${n.project} (stale-source-set)`,`cluster-lens-app:${n.project}`,{kind:"pod-recycle",project:n.project,reason:"stale-source-set"},void 0,n.bus);const d=(0,hmr_reload_1.broadcastFullReload)(r,n.project);d>0&&console.log(`[gateway] \u2192 told ${d} open tab(s) to reload (stale optimize-dep generation)`);try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null){console.log(`[gateway] zombie recycle: lens app ${n.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`);try{e.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),$.unref?.()},"startLensSweeper"),q=i(async()=>{const t=[...new Map(r.routes.filter(e=>e.lens).map(e=>[e.project,e.lens])).values()],a=c.dashboard!==!1,s=c.mcp!==!1&&t.length>0;if(a){const e=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!e)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 n=(0,scope_1.readLensScope)(r.root),o=await N(c.dashboardPort??n.dashboardPort,j),l=c.lensHost??"lensmcp.local",d=n.basePath;r.registry.appendRoutes(r.wsKey,[{host:l,prefix:d,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${o}`}]),r.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:n.key,root:r.root,base:d,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${o} (routed at https://${l}${d}/)`),u("info",`lens dashboard up: ${l}${d} \u2192 :${o}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:l,port:o,base:d}),b("lens-dashboard",process.execPath,[e],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_DASHBOARD_PORT:String(o),LENSMCP_DASHBOARD_BASE:d},!1,void 0,void 0,o)}}if(s){const e=(0,lens_frontend_1.findMcpBundle)(r.root);if(!e)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 n=(0,scope_1.readLensScope)(r.root),o=Number(process.env.LENSMCP_PORT??n.mcpHttpPort),l=i(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${o} (${e})`),b("lens-mcp",process.execPath,[e],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(o),LENSMCP_WORKSPACE_KEY:n.key,LENSMCP_WORKSPACE_ROOT:r.root},!1,void 0,void 0,o)},"spawnMcp");await h(o)?l():(console.warn(`[gateway] lens MCP port :${o} 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.`),u("info",`lens MCP served externally on :${o} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-mcp-external",port:o}),B("lens-mcp",o,l))}}for(const e of t){if(e.started)continue;e.started=!0;const n=await N(e.port,F);if(n!==e.port){for(const o of r.routes)o.lens?.project===e.project&&(o.lens.port=n,o.target=`http://localhost:${n}`);e.port=n}try{(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:r.root,project:e.project,eventFile:r.eventFile,port:e.port,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project},log:i(o=>console.log(o),"log")},b),u("info",`lens app started: ${e.project} (vite :${e.port})`,`cluster-lens-app:${e.project}`,{kind:"lens-app-up",project:e.project,port:e.port})}catch(o){console.error(`[gateway] lens app ${e.project}: ${o.message}`)}}W()},"bootSingletonsAndApps"),Y=i(async t=>{const a=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!a)return;const s=(0,scope_1.readLensScope)(t.root),e=c.lensHost??"lensmcp.local",n=s.basePath,o=await N(s.dashboardPort,j),l=`lens-dashboard:${t.wsKey}`,d=path.join(t.root,".lensmcp","events.jsonl");return y.delete(l),r.registry.appendRoutes(t.wsKey,[{host:e,prefix:n,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${o}`}]),(0,workspace_registry_1.registerWorkspace)({key:t.wsKey,root:t.root,base:n,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] hosting dashboard for workspace '${t.wsKey}' \u2192 http://localhost:${o} (routed at https://${e}${n}/)`),u("info",`lens dashboard up: ${e}${n} \u2192 :${o}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:e,port:o,base:n}),b(l,process.execPath,[a],{LENSMCP_EVENT_FILE:d,LENSMCP_DASHBOARD_PORT:String(o),LENSMCP_DASHBOARD_BASE:n,LENSMCP_WS_KEY:t.wsKey},!1,void 0,t.root,o),()=>{y.add(l),j.delete(o),(0,workspace_registry_1.unregisterWorkspace)(t.wsKey);try{v.get(l)?.kill("SIGTERM")}catch{}v.delete(l)}},"hostWorkspaceDashboard"),V=i(async t=>{const a=new Map;for(const o of t.routes){if(!o.lens)continue;const l=a.get(o.lens.project)??[];l.push(o),a.set(o.lens.project,l)}if(a.size===0)return()=>{};const s=path.join(t.root,".lensmcp","events.jsonl"),e=[],n=[];for(const[o,l]of a){const d=l[0].lens;if(d.started)continue;const P=await N(d.port,F);n.push(P);for(const w of l)w.lens.started=!0,w.lens.port=P,w.target=`http://localhost:${P}`;const g=`${t.wsKey}:${o}:`,m=`${g}vite`,_=`${g}browser-capture`;e.push(m,_),y.delete(m),y.delete(_);const R=i((w,T,I,f,x,D)=>b(w,T,I,f,x,D,t.root),"spawnInRoot");console.log(`[gateway] hosting lens app '${o}' of '${t.wsKey}' \u2192 vite :${P}`);try{(0,lens_frontend_1.spawnLensFrontend)({projectRoot:d.projectRoot,workspaceRoot:t.root,project:o,eventFile:s,port:P,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:o},labelPrefix:g,log:i(w=>console.log(w),"log")},R),u("info",`lens app started: ${t.wsKey}/${o} (vite :${P})`,`cluster-lens-app:${t.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:P},void 0,s)}catch(w){console.error(`[gateway] lens app ${t.wsKey}/${o}: ${w.message}`)}}return()=>{for(const o of e){y.add(o);try{v.get(o)?.kill("SIGTERM")}catch{}v.delete(o)}for(const o of n)F.delete(o)}},"hostWorkspaceApps"),U=i(async(t=2500)=>{$&&(clearInterval($),$=void 0);const a=[...S];for(const n of a)try{n.kill("SIGTERM")}catch{}const s=i(n=>n.exitCode===null&&n.signalCode===null,"alive"),e=Date.now()+t;for(;Date.now()<e&&a.some(s);)await new Promise(n=>setTimeout(n,100));for(const n of a)if(s(n))try{n.kill("SIGKILL")}catch{}},"reapAll"),K=i(()=>H({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:b,bootSingletonsAndApps:q,hostWorkspaceDashboard:Y,hostWorkspaceApps:V,probeLensFrontends:K,reapAll:U}}L(createLensChildren,"createLensChildren"),i(createLensChildren,"createLensChildren");
1
+ "use strict";var Z=Object.defineProperty;var L=(n,p)=>Z(n,"name",{value:p,configurable:!0});var X=Object.defineProperty,l=L((n,p)=>X(n,"name",{value:p,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,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"),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"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(n,p){return new Promise(c=>{const h=net.createServer();h.unref(),h.once("error",d=>{const E=d.code??"";c(E==="EADDRINUSE"||E==="EACCES"?"in-use":"unavailable")}),h.listen(p===void 0?{port:n}:{port:n,host:p},()=>h.close(()=>c("free")))})}L(tryBind,"tryBind"),l(tryBind,"tryBind");async function probePortFree(n){for(const p of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(n,p)==="in-use")return!1;return!0}L(probePortFree,"probePortFree"),l(probePortFree,"probePortFree");function probeConnect(n,p,c){return new Promise(h=>{let d=!1;const E=net.createConnection({host:n,port:p});E.unref();const C=setTimeout(()=>P(!1),c);C.unref?.();function P(R){d||(d=!0,clearTimeout(C),E.destroy(),h(R))}L(P,"y"),l(P,"finish"),E.once("connect",()=>P(!0)),E.once("error",()=>P(!1))})}L(probeConnect,"probeConnect"),l(probeConnect,"probeConnect");async function probePortListening(n,p=1e3){return await probeConnect("127.0.0.1",n,p)?!0:probeConnect("::1",n,p)}L(probePortListening,"probePortListening"),l(probePortListening,"probePortListening");async function probeWatcherHealth(n,p=1500){try{const c=new AbortController,h=setTimeout(()=>c.abort(),p);h.unref?.();try{const d=await fetch(`http://127.0.0.1:${n}/__lensmcp/watcher-health`,{signal:c.signal});if(!d.ok)return null;const E=await d.json();return typeof E?.lastFsSetEventAt=="number"?{lastFsSetEventAt:E.lastFsSetEventAt}:null}finally{clearTimeout(h)}}catch{return null}}L(probeWatcherHealth,"probeWatcherHealth"),l(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=l(n=>new Promise(p=>{setTimeout(p,n).unref?.()}),"sleepUnref");async function waitForPortFree(n,p,c={}){const h=c.isPortFree??probePortFree,d=Math.max(1,c.intervalMs??types_1.PORT_FREE_POLL_MS),E=Date.now()+Math.max(0,p);for(;;){if(await h(n))return!0;const C=E-Date.now();if(C<=0)return!1;await sleepUnref(Math.min(d,C))}}L(waitForPortFree,"waitForPortFree"),l(waitForPortFree,"waitForPortFree");function viteArgPort(n){const p=l(c=>Number.isInteger(c)&&c>0&&c<65536?c:void 0,"valid");for(let c=0;c<n.length;c+=1){const h=n[c];if(h==="--port")return p(Number(n[c+1]));if(h.startsWith("--port="))return p(Number(h.slice(7)))}}L(viteArgPort,"viteArgPort"),l(viteArgPort,"viteArgPort");function createLensChildren(n,p,c,h){const{emit:d}=p,E=h?.isPortFree??probePortFree,C=h?.probeWatcherHealth??probeWatcherHealth,P=[],R=new Map,v=new Map,O=new Map,H=new Map,F=new Set;let $;const A=new Map,y=new Set,k=new Set,x=new Set,T=new Set,W=l((t,a,s)=>{if(T.has(t))return;T.add(t);const e=setInterval(()=>{(async()=>{if(n.stopped()||y.has(t)){clearInterval(e),T.delete(t);return}await E(a)&&(clearInterval(e),T.delete(t),console.log(`[gateway] lens child ${t}: :${a} freed \u2014 respawning.`),s())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);e.unref?.()},"parkUntilPortFree"),N=l(async(t,a)=>{let s=t;for(;a.has(s)||!await E(s);)s+=1;return a.add(s),s!==t&&console.warn(`[gateway] port ${t} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${s} instead.`),s},"claimFreePort"),b=l((t,a,s,e,r=!1,o,i,u)=>{const w=Date.now(),g=(0,node_child_process_1.spawn)(a,s,{cwd:i??n.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,n.root,i??n.root),...e??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});P.push(g),A.set(t,g);const m=t==="vite"||t.endsWith(":vite"),_=m?viteArgPort(s):u;if(m){const M=typeof e?.LENSMCP_EVENT_FILE=="string"?e.LENSMCP_EVENT_FILE:void 0;v.set(g,{project:e?.LENSMCP_PROJECT??"app",spawnedAt:w,port:_,probeFails:0,listening:!1,...M&&M!==n.eventFile?{bus:M}:{}})}return g.on("exit",(M,S)=>{const j=P.indexOf(g);if(j>=0&&P.splice(j,1),v.delete(g),n.stopped()||y.has(t))return;const D=Date.now()-w,f=R.get(t)??{attempts:0,portConflicts:0};(async()=>{const K=_!==void 0&&!await E(_),I=(0,lifecycle_1.isPortConflictExit)({code:M,signal:S,ranMs:D,portHeld:K},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(I&&f.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)f.portConflicts+=1,R.set(t,f),console.warn(`[gateway] lens child ${t} lost the :${_} bind race (EADDRINUSE, code=${M}); waiting for the port, then retrying (port-retry ${f.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(I){f.portConflicts=0,R.set(t,f),console.warn(`[gateway] lens child ${t}: :${_} 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.`),d("warning",`lens child parked: ${t} (:${_} held externally)`,`cluster-lens-child:${t}`,{kind:"lens-child-parked",label:t,port:_}),W(t,_,()=>b(t,a,s,e,r,o,i,u));return}else{f.portConflicts=0,D>=HEAL_HEALTHY_MS&&(f.attempts=0);const q=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**f.attempts);f.attempts+=1,R.set(t,f),console.warn(`[gateway] lens child ${t} exited (code=${M} sig=${S}); auto-healing in ${Math.round(q/1e3)}s (attempt ${f.attempts}).`),await sleepUnref(q)}n.stopped()||y.has(t)||(_!==void 0&&!await waitForPortFree(_,types_1.PORT_FREE_WAIT_MS,{isPortFree:E,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${t}: :${_} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(n.stopped()||y.has(t))&&b(t,a,s,e,r,o,i,u))})().catch(()=>{})}),g},"spawnManagedChild");let B=!1;const Y=l(async(t,a)=>{if(!B){B=!0;try{for(const[s,e]of[...v]){if(n.stopped())return;if(e.port===void 0||s.exitCode!==null||s.signalCode!==null)continue;if(await probePortListening(e.port,types_1.LENS_PROBE_TIMEOUT_MS)){e.probeFails=0,e.listening||(e.listening=!0,d("info",`lens app up: ${e.project} (vite :${e.port} listening)`,`cluster-lens-app:${e.project}`,{kind:"service-up",project:e.project,port:e.port},void 0,e.bus));continue}const r=e.listening?t.graceMs:Math.max(t.graceMs,t.neverListenedGraceMs);if(Date.now()-e.spawnedAt<r){e.probeFails=0;continue}if(!a)continue;e.probeFails+=1;const o=(0,lifecycle_1.lensChildWedged)({spawnedAt:e.spawnedAt,probeFails:e.probeFails,hasListened:e.listening},O.get(e.project)??0,Date.now(),t);if(!o)continue;const i=e.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(r/1e3)}s boot allowance`;O.set(e.project,Date.now()),e.probeFails=0,e.listening=!1,console.error(`[gateway] wedge recycle: lens app ${e.project} is ALIVE but NOT LISTENING on :${e.port} after ${Math.max(2,t.failureThreshold)} probes \u2014 ${i} \u2014 SIGKILL; a fresh vite is coming.`),d("warning",`wedge recycle: ${e.project} (${o})`,`cluster-lens-app:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o,port:e.port},void 0,e.bus);try{s.kill("SIGKILL")}catch{}}}finally{B=!1}}},"probeLensChildren"),V=l(()=>{if($||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const t={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};$=setInterval(()=>{if(n.stopped()||v.size===0||(G(),!types_1.POD_RECYCLE_ENABLED))return;const a=n.sourceSetChangedAt??0;if(a<=0)return;const s=Date.now();for(const[e,r]of v){const o=n.sourceSetChangedFor?.get(r.project)??a,i=O.get(r.project)??0;(0,lifecycle_1.lensFrontendStale)(r.spawnedAt,i,o,s,t)&&((H.get(r.project)??0)>=o||F.has(r.project)||(F.add(r.project),(async()=>{try{const u=types_1.LENS_WATCHER_PROBE_ENABLED&&r.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await C(r.port),o,Math.min(types_1.POD_STALE_SCAN_MS,15e3)+types_1.POD_RECYCLE_SETTLE_MS):"unknown";if(u==="heard"){H.set(r.project,o),console.log(`[gateway] stale source set: lens app ${r.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),d("info",`stale check: ${r.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${r.project}`,{kind:"pod-stale-verified",project:r.project},void 0,r.bus);return}if(e.exitCode!==null||e.signalCode!==null)return;O.set(r.project,Date.now()),r.listening=!1,console.log(`[gateway] zombie recycle: lens app ${r.project} (stale-source-set) \u2014 watcher ${u==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),d("warning",`zombie recycle: ${r.project} (stale-source-set)`,`cluster-lens-app:${r.project}`,{kind:"pod-recycle",project:r.project,reason:"stale-source-set",watcher:u},void 0,r.bus);const w=(0,hmr_reload_1.broadcastFullReload)(n,r.project);w>0&&console.log(`[gateway] \u2192 told ${w} open tab(s) to reload (stale optimize-dep generation)`);try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null){console.log(`[gateway] zombie recycle: lens app ${r.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`);try{e.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{F.delete(r.project)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),$.unref?.()},"startLensSweeper"),z=l(async()=>{const t=[...new Map(n.routes.filter(e=>e.lens).map(e=>[e.project,e.lens])).values()],a=c.dashboard!==!1,s=c.mcp!==!1&&t.length>0;if(a){const e=(0,lens_frontend_1.findDashboardBundle)(n.root);if(!e)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 r=(0,scope_1.readLensScope)(n.root),o=await N(c.dashboardPort??r.dashboardPort,k),i=c.lensHost??"lensmcp.local",u=r.basePath;n.registry.appendRoutes(n.wsKey,[{host:i,prefix:u,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${o}`}]),n.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:r.key,root:n.root,base:u,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${o} (routed at https://${i}${u}/)`),d("info",`lens dashboard up: ${i}${u} \u2192 :${o}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:o,base:u}),b("lens-dashboard",process.execPath,[e],{LENSMCP_EVENT_FILE:n.eventFile,LENSMCP_DASHBOARD_PORT:String(o),LENSMCP_DASHBOARD_BASE:u},!1,void 0,void 0,o)}}if(s){const e=(0,lens_frontend_1.findMcpBundle)(n.root);if(!e)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 r=(0,scope_1.readLensScope)(n.root),o=Number(process.env.LENSMCP_PORT??r.mcpHttpPort),i=l(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${o} (${e})`),b("lens-mcp",process.execPath,[e],{LENSMCP_EVENT_FILE:n.eventFile,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(o),LENSMCP_WORKSPACE_KEY:r.key,LENSMCP_WORKSPACE_ROOT:n.root},!1,void 0,void 0,o)},"spawnMcp");await E(o)?i():(console.warn(`[gateway] lens MCP port :${o} 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.`),d("info",`lens MCP served externally on :${o} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-mcp-external",port:o}),W("lens-mcp",o,i))}}for(const e of t){if(e.started)continue;e.started=!0;const r=await N(e.port,x);if(r!==e.port){for(const o of n.routes)o.lens?.project===e.project&&(o.lens.port=r,o.target=`http://localhost:${r}`);e.port=r}try{(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:n.root,project:e.project,eventFile:n.eventFile,port:e.port,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project},log:l(o=>console.log(o),"log")},b),d("info",`lens app started: ${e.project} (vite :${e.port})`,`cluster-lens-app:${e.project}`,{kind:"lens-app-up",project:e.project,port:e.port})}catch(o){console.error(`[gateway] lens app ${e.project}: ${o.message}`)}}V()},"bootSingletonsAndApps"),U=l(async t=>{const a=(0,lens_frontend_1.findDashboardBundle)(n.root);if(!a)return;const s=(0,scope_1.readLensScope)(t.root),e=c.lensHost??"lensmcp.local",r=s.basePath,o=await N(s.dashboardPort,k),i=`lens-dashboard:${t.wsKey}`,u=path.join(t.root,".lensmcp","events.jsonl");return y.delete(i),n.registry.appendRoutes(t.wsKey,[{host:e,prefix:r,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${o}`}]),(0,workspace_registry_1.registerWorkspace)({key:t.wsKey,root:t.root,base:r,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] hosting dashboard for workspace '${t.wsKey}' \u2192 http://localhost:${o} (routed at https://${e}${r}/)`),d("info",`lens dashboard up: ${e}${r} \u2192 :${o}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:e,port:o,base:r}),b(i,process.execPath,[a],{LENSMCP_EVENT_FILE:u,LENSMCP_DASHBOARD_PORT:String(o),LENSMCP_DASHBOARD_BASE:r,LENSMCP_WS_KEY:t.wsKey},!1,void 0,t.root,o),()=>{y.add(i),k.delete(o),(0,workspace_registry_1.unregisterWorkspace)(t.wsKey);try{A.get(i)?.kill("SIGTERM")}catch{}A.delete(i)}},"hostWorkspaceDashboard"),J=l(async t=>{const a=new Map;for(const o of t.routes){if(!o.lens)continue;const i=a.get(o.lens.project)??[];i.push(o),a.set(o.lens.project,i)}if(a.size===0)return()=>{};const s=path.join(t.root,".lensmcp","events.jsonl"),e=[],r=[];for(const[o,i]of a){const u=i[0].lens;if(u.started)continue;const w=await N(u.port,x);r.push(w);for(const S of i)S.lens.started=!0,S.lens.port=w,S.target=`http://localhost:${w}`;const g=`${t.wsKey}:${o}:`,m=`${g}vite`,_=`${g}browser-capture`;e.push(m,_),y.delete(m),y.delete(_);const M=l((S,j,D,f,K,I)=>b(S,j,D,f,K,I,t.root),"spawnInRoot");console.log(`[gateway] hosting lens app '${o}' of '${t.wsKey}' \u2192 vite :${w}`);try{(0,lens_frontend_1.spawnLensFrontend)({projectRoot:u.projectRoot,workspaceRoot:t.root,project:o,eventFile:s,port:w,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:o},labelPrefix:g,log:l(S=>console.log(S),"log")},M),d("info",`lens app started: ${t.wsKey}/${o} (vite :${w})`,`cluster-lens-app:${t.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:w},void 0,s)}catch(S){console.error(`[gateway] lens app ${t.wsKey}/${o}: ${S.message}`)}}return()=>{for(const o of e){y.add(o);try{A.get(o)?.kill("SIGTERM")}catch{}A.delete(o)}for(const o of r)x.delete(o)}},"hostWorkspaceApps"),Q=l(async(t=2500)=>{$&&(clearInterval($),$=void 0);const a=[...P];for(const r of a)try{r.kill("SIGTERM")}catch{}const s=l(r=>r.exitCode===null&&r.signalCode===null,"alive"),e=Date.now()+t;for(;Date.now()<e&&a.some(s);)await new Promise(r=>setTimeout(r,100));for(const r of a)if(s(r))try{r.kill("SIGKILL")}catch{}},"reapAll"),G=l(()=>Y({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:b,bootSingletonsAndApps:z,hostWorkspaceDashboard:U,hostWorkspaceApps:J,probeLensFrontends:G,reapAll:Q}}L(createLensChildren,"createLensChildren"),l(createLensChildren,"createLensChildren");
@@ -58,6 +58,28 @@ export declare function lensFrontendStale(spawnedAt: number, lastRecycleAt: numb
58
58
  settleMs: number;
59
59
  cooldownMs: number;
60
60
  }): boolean;
61
+ /** What the pod's own watcher said about a detected source-set change ({@link lensWatcherVerdict}). */
62
+ export type LensWatcherVerdict = 'heard' | 'missed' | 'unknown';
63
+ /**
64
+ * PURE verdict: did this vite's OWN fs-watcher hear the source-set change the gateway's scan detected?
65
+ *
66
+ * {@link lensFrontendStale} proves only that a file was ADDED/REMOVED in the app's scope — it cannot say
67
+ * whether the vite missed it, and a healthy chokidar almost never does. Killing on the set change alone
68
+ * therefore cost every open tab a full reload per add (the "every change reloads instead of HMR"
69
+ * complaint). This closes the gap with the pod's testimony: `@lensmcp/vite-plugin` stamps a clock on every
70
+ * chokidar `add`/`addDir`/`unlink`/`unlinkDir` and serves it at `/__lensmcp/watcher-health`; the sweeper
71
+ * probes it and only recycles a pod whose watcher was demonstrably SILENT across the change.
72
+ *
73
+ * The window: `changedAt` is stamped when the gateway's SCAN completed, but the actual fs event happened up
74
+ * to a full scan period earlier — and the watcher stamps at the EVENT. So "heard" accepts any watcher event
75
+ * at/after `changedAt - windowMs`, where the caller passes the scan period plus a margin. A stamp inside
76
+ * the window that happens to be a DIFFERENT file's event can, in principle, clear a pod that missed this
77
+ * one — accepted: the failure mode is exactly the pre-sweeper world for one scan cycle, the next set change
78
+ * re-arms the check, and the alternative (kill-first) taxes every healthy add to cover it.
79
+ */
80
+ export declare function lensWatcherVerdict(probe: {
81
+ lastFsSetEventAt: number;
82
+ } | null, changedAt: number, windowMs: number): LensWatcherVerdict;
61
83
  /** Config for the lens-child LIVENESS decision ({@link lensChildWedged}). */
62
84
  export interface LensChildProbeConfig {
63
85
  /** Spawn grace: no verdict until the child has had this long to BIND its port (a cold vite boot). */
@@ -1,3 +1,3 @@
1
- "use strict";var N=Object.defineProperty;var j=(t,c)=>N(t,"name",{value:c,configurable:!0});var I=Object.defineProperty,s=j((t,c)=>I(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.sourceSetSignature=sourceSetSignature,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),types_1=require("./types");function eastWestEnv(t,c){const p={},l=t.serviceKeys[c];if(l&&(p.LENSMCP_SERVICE_KEY=l),t.https){const a=require("../../../basic-ssl").caCertPath();fs.existsSync(a)&&(p.NODE_EXTRA_CA_CERTS=a)}return p}j(eastWestEnv,"eastWestEnv"),s(eastWestEnv,"eastWestEnv");const sleep=s(t=>new Promise(c=>setTimeout(c,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let c=2166136261;for(let p=0;p<t.length;p+=1)c^=t.charCodeAt(p),c=Math.imul(c,16777619);return c>>>0}j(fnv1a,"fnv1a"),s(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:c}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(c.split("\0").filter(Boolean))}catch{return new Set}}j(gitIgnoredPaths,"gitIgnoredPaths"),s(gitIgnoredPaths,"gitIgnoredPaths");async function sourceSetSignature(t,c,p){let l=0,a=0;const v=await gitIgnoredPaths(t),_=s(f=>path.relative(t,f).split(path.sep).join("/"),"rel"),S=s(async(f,k=!0)=>{if(l>SCAN_FILE_CAP)return;let M;try{M=await fs.promises.readdir(f,{withFileTypes:!0})}catch{return}for(const y of M){if(l>SCAN_FILE_CAP)return;const m=y.name,A=path.join(f,m);if(y.isDirectory()){if(!k||m.startsWith(".")||SRC_EXCLUDE.has(m)||p?.has(m)||m.endsWith("-devserver")||v.has(`${_(A)}/`))continue;await S(A)}else if(y.isFile()){const R=m.lastIndexOf(".");if(R<=0||!SRC_EXT.has(m.slice(R))||v.has(_(A)))continue;l+=1,a^=fnv1a(A)}}},"walk");if(c&&c.length>0){await S(t,!1);for(const f of c)await S(f)}else await S(t);return`${l}:${(a>>>0).toString(36)}`}j(sourceSetSignature,"sourceSetSignature"),s(sourceSetSignature,"sourceSetSignature");function sourceScopeDirs(t,c,p,l,a){const v=s(f=>{const k=path.relative(t,f).split(path.sep).filter(Boolean);return k.length===0||k[0].startsWith("..")?"":k[0]},"bucketOf")(c);if(!v)return[];const _=new Set(p.filter(Boolean)),S=new Set([v]);for(const f of l)f.startsWith(".")||SRC_EXCLUDE.has(f)||a?.has(f)||f.endsWith("-devserver")||_.has(f)||S.add(f);return[...S].sort().map(f=>path.join(t,f))}j(sourceScopeDirs,"sourceScopeDirs"),s(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,c,p,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||c-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&c-t.lastStaleRecycleAt<l.cooldownMs?null:p>0&&p-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&c-t.spawnAt>l.maxAgeMs?"max-age":null}j(podRecycleReason,"podRecycleReason"),s(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,c,p,l,a){return!(l-c<a.cooldownMs||p<=0||p-t<=a.graceMs||l-p<a.settleMs)}j(lensFrontendStale,"lensFrontendStale"),s(lensFrontendStale,"lensFrontendStale");function lensChildWedged(t,c,p,l){const a=t.hasListened?l.graceMs:Math.max(l.graceMs,l.neverListenedGraceMs);return p-t.spawnedAt<a||p-c<l.cooldownMs||t.probeFails<Math.max(2,l.failureThreshold)?null:"wedged-no-listener"}j(lensChildWedged,"lensChildWedged"),s(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,c){return t.signal||t.code===null||t.code===0||t.ranMs>c.fastFailMs?!1:t.portHeld}j(isPortConflictExit,"isPortConflictExit"),s(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,c){const{emit:p}=c,l=s(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),a=s((e,o,r,n,i)=>p(o,r,n,{wsKey:e.wsKey,...i},void 0,l(e)),"emitSvc"),v=s(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),_=s((e,o)=>{let r;try{r=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const n=e.proc?.pid;let i=0;for(const d of r.split(`
2
- `)){const w=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!w)continue;const g=Number(w[1]),u=Number(w[2]),h=w[3];if(h.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test(h)&&!(h.includes("/node_modules/nx/")&&!h.includes(e.root))&&!(g===process.pid||g===n)&&!(n!==void 0&&u===n)){try{process.kill(-u,"SIGKILL")}catch{try{process.kill(g,"SIGKILL")}catch{}}i+=1}}return i>0&&(console.log(`[gateway] ${o}: reaped ${i} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),a(e,"warning",`stale-runner reap: ${e.project} (${i} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:i})),i},"reapForeignRunners"),S=s(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,r=Math.max(1,e.decl.minPods??1);_(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${r})\u2026`),a(e,"info",`service starting: ${e.project} (${r} pod${r>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:r}),e.state="starting",e.spawnAt=Date.now();const n=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),[i,d]=fs.existsSync(n)?[process.execPath,[n,"run",o,`--childCount=${r}`]]:["yarn",["nx","run",o,`--childCount=${r}`]],w=(0,node_child_process_1.spawn)(i,d,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),...eastWestEnv(t,e.project)},stdio:["ignore","pipe","pipe"]});e.lastErrors=[],e.buildFailed=!1;const g=s(u=>{const h=u.toString();for(const E of h.split(`
3
- `)){if(/ERROR in |error TS\d+|Found \d+ error|Unhandled 'error' event|EADDRINUSE/.test(E)){const $=E.replace(/\x1b\[[0-9;]*m/g,"").trim();e.lastErrors.push($),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const x=Date.now();($!==e.lastErrorEmitSig||x-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=$,e.lastErrorEmitAt=x,a(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:$}))}/No typescript errors found|webpack compiled successfully|Child#\d+ ready/.test(E)&&(e.buildFailed=!1),!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(E)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${o}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),a(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(y(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),_(e,o),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&S(e)},1500).unref?.())},250).unref?.())}},"sniff");w.stdout?.on("data",u=>{t.stopped()||(g(u),process.stdout.write(`[${e.project}] ${u}`))}),w.stderr?.on("data",u=>{t.stopped()||(g(u),process.stderr.write(`[${e.project}] ${u}`))}),w.on("exit",u=>{if(e.proc=void 0,e.state="down",t.stopped()||(console.log(`[gateway] ${o} exited (${u})`),a(e,u===0?"info":"warning",`service down: ${e.project} (exit ${u})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:u}),u===0||e.buildFailed))return;const h=Date.now();if(e.restarts=(e.restarts??[]).filter($=>h-$<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){a(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(h);const E=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),a(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),S(e))},E)}),e.proc=w},"spawnService"),f=s((e,o)=>{if(e.state==="up")return!1;e.state="up";const r=e.pool.socks.length;return a(e,"info",`service up: ${e.project} (${r} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:r,adopted:o==="adopted"}),!0},"markUp"),k=s(async e=>{if(v(e.pool)>0)return f(e,"adopted"),!0;if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&S(e);const o=Date.now()+t.startupTimeoutMs,r=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>r)return!1;if(v(e.pool)>0){e.state="up",e.buildFailed=!1;const n=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${n?`, cold start ${n}ms`:""})`),a(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${n??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:n}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),M=s(e=>{const o=e.decl.maxPods??e.decl.minPods??1,r=e.pool.socks.length,n=e.decl.adminUrl??(e.decl.port?`http://localhost:${e.decl.port}`:void 0);if(!n||r===0||r>=o||e.inflight<=r||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();const i=(n.startsWith("https")?require("node:https"):http).request(`${n}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume());i.on("error",()=>{}),i.end(JSON.stringify({pods:r+1})),console.log(`[gateway] autoscale ${e.project}: ${r} \u2192 ${r+1} pods (inflight ${e.inflight})`),a(e,"info",`autoscale: ${e.project} ${r} \u2192 ${r+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:r,to:r+1,inflight:e.inflight})},"scaleUp"),y=s((e,o,r)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),r?.announce!==!1&&a(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const n=e.proc;try{n.stdout?.removeAllListeners("data"),n.stderr?.removeAllListeners("data"),n.removeAllListeners("exit")}catch{}try{process.kill(-n.pid,"SIGTERM")}catch{try{n.kill("SIGTERM")}catch{}}e.proc=void 0,e.state="down",setTimeout(()=>{try{for(const i of fs.readdirSync(e.pool.dir)){if(!i.endsWith(".sock"))continue;const d=path.join(e.pool.dir,i);try{const w=Number(fs.readFileSync(`${d}.owner`,"utf8").trim());if(Number.isFinite(w)&&w>0)try{process.kill(w,0);continue}catch{}}catch{}try{fs.unlinkSync(d)}catch{}try{fs.unlinkSync(`${d}.owner`)}catch{}}}catch{}e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),m=s(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(r=>o-r<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){a(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),y(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),a(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),y(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&S(e)},1500).unref?.()},"recycleWedged"),A={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let R;const D=new Map;let C=!1,T=0;const b=s(()=>{const e=t.projectRoots??{},o=new Set(t.services.map(n=>n.project));for(const n of t.routes)n.lens&&o.add(n.lens.project);const r=new Set;for(const n of o){const i=e[n];if(!i)continue;const d=path.relative(t.root,i).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&r.add(d)}return[...r]},"managedBuckets"),L=s((e,o,r,n)=>{const i=t.projectRoots?.[e];if(!i)return;const d=sourceScopeDirs(t.root,i,r,o,n);return d.length>0?d:void 0},"scopeFor"),P=s(()=>{C||Date.now()-T<types_1.POD_STALE_SCAN_MS||(C=!0,T=Date.now(),(async()=>{const e=(0,scope_1.readSourceSetExclude)(t.root),o=await sourceSetSignature(t.root,void 0,e);if(R!==void 0&&o!==R&&(t.sourceSetChangedAt=Date.now(),p("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed"})),R=o,!t.projectRoots)return;let r;try{r=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(g=>g.isDirectory()).map(g=>g.name)}catch{return}const n=b(),i=new Set(t.services.map(g=>g.project));for(const g of t.routes)g.lens&&i.add(g.lens.project);const d=new Map;for(const g of i){const u=L(g,r,n,e);if(!u)continue;const h=u.join("|"),E=d.get(h)??{dirs:u,projects:[]};E.projects.push(g),d.set(h,E)}const w=Date.now();for(const[g,{dirs:u,projects:h}]of d){const E=await sourceSetSignature(t.root,u,e),$=D.get(g);if(D.set(g,E),!($===void 0||E===$)){t.sourceSetChangedFor??=new Map;for(const x of h)t.sourceSetChangedFor.set(x,w);p("info",`source file set changed in ${h.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:h})}}})().catch(()=>{}).finally(()=>{C=!1}))},"maybeScanSourceSet"),F=s((e,o)=>{e.lastStaleRecycleAt=Date.now(),console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),a(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const r=!!e.decl.eager;y(e,`${o} \u2014 recycling stale pod`,{announce:!1}),r&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&S(e)},1500).unref?.()},"recyclePod");return{scanNow:v,spawnService:S,ensureUp:k,scaleUp:M,killSpawned:y,markUp:f,startSweeper:s(()=>{const e=setInterval(()=>{if(!t.stopped()){types_1.POD_RECYCLE_ENABLED&&P();for(const o of t.services){if(t.stopped()||!o.proc){o.wedgedSince=void 0;continue}const r=o.decl.idleKillSec;if(r&&o.inflight===0&&Date.now()-o.lastUsed>r*1e3){y(o,`idle ${r}s`),o.wedgedSince=void 0;continue}const n=o.state==="starting"&&o.spawnAt!==void 0&&Date.now()-o.spawnAt<t.startupTimeoutMs,i=v(o.pool)>0;if(i||n){if(o.wedgedSince=void 0,i&&f(o,"observed"),i&&!n&&types_1.POD_RECYCLE_ENABLED){const d=t.sourceSetChangedFor?.get(o.project)??t.sourceSetChangedAt??0,w=podRecycleReason(o,Date.now(),d,A);w&&F(o,w)}continue}o.wedgedSince??=Date.now(),Date.now()-o.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(o.wedgedSince=void 0,m(o))}}},t.sweepMs);return e.unref(),e},"startSweeper")}}j(createServiceLayer,"createServiceLayer"),s(createServiceLayer,"createServiceLayer");
1
+ "use strict";var W=Object.defineProperty;var E=(t,n)=>W(t,"name",{value:n,configurable:!0});var I=Object.defineProperty,i=E((t,n)=>I(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.sourceSetSignature=sourceSetSignature,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),types_1=require("./types");function eastWestEnv(t,n){const l={},p=t.serviceKeys[n];if(p&&(l.LENSMCP_SERVICE_KEY=p),t.https){const a=require("../../../basic-ssl").caCertPath();fs.existsSync(a)&&(l.NODE_EXTRA_CA_CERTS=a)}return l}E(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let l=0;l<t.length;l+=1)n^=t.charCodeAt(l),n=Math.imul(n,16777619);return n>>>0}E(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}E(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");async function sourceSetSignature(t,n,l){let p=0,a=0;const v=await gitIgnoredPaths(t),_=i(f=>path.relative(t,f).split(path.sep).join("/"),"rel"),S=i(async(f,k=!0)=>{if(p>SCAN_FILE_CAP)return;let M;try{M=await fs.promises.readdir(f,{withFileTypes:!0})}catch{return}for(const y of M){if(p>SCAN_FILE_CAP)return;const m=y.name,A=path.join(f,m);if(y.isDirectory()){if(!k||m.startsWith(".")||SRC_EXCLUDE.has(m)||l?.has(m)||m.endsWith("-devserver")||v.has(`${_(A)}/`))continue;await S(A)}else if(y.isFile()){const R=m.lastIndexOf(".");if(R<=0||!SRC_EXT.has(m.slice(R))||v.has(_(A)))continue;p+=1,a^=fnv1a(A)}}},"walk");if(n&&n.length>0){await S(t,!1);for(const f of n)await S(f)}else await S(t);return`${p}:${(a>>>0).toString(36)}`}E(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");function sourceScopeDirs(t,n,l,p,a){const v=i(f=>{const k=path.relative(t,f).split(path.sep).filter(Boolean);return k.length===0||k[0].startsWith("..")?"":k[0]},"bucketOf")(n);if(!v)return[];const _=new Set(l.filter(Boolean)),S=new Set([v]);for(const f of p)f.startsWith(".")||SRC_EXCLUDE.has(f)||a?.has(f)||f.endsWith("-devserver")||_.has(f)||S.add(f);return[...S].sort().map(f=>path.join(t,f))}E(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,l,p){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||n-t.lastUsed<p.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<p.cooldownMs?null:l>0&&l-t.spawnAt>p.graceMs?"stale-source-set":p.maxAgeMs>0&&n-t.spawnAt>p.maxAgeMs?"max-age":null}E(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,l,p,a){return!(p-n<a.cooldownMs||l<=0||l-t<=a.graceMs||p-l<a.settleMs)}E(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,l){return t?t.lastFsSetEventAt>=n-l?"heard":"missed":"unknown"}E(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function lensChildWedged(t,n,l,p){const a=t.hasListened?p.graceMs:Math.max(p.graceMs,p.neverListenedGraceMs);return l-t.spawnedAt<a||l-n<p.cooldownMs||t.probeFails<Math.max(2,p.failureThreshold)?null:"wedged-no-listener"}E(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}E(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n){const{emit:l}=n,p=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),a=i((e,o,r,s,c)=>l(o,r,s,{wsKey:e.wsKey,...c},void 0,p(e)),"emitSvc"),v=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),_=i((e,o)=>{let r;try{r=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const s=e.proc?.pid;let c=0;for(const d of r.split(`
2
+ `)){const w=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!w)continue;const g=Number(w[1]),u=Number(w[2]),h=w[3];if(h.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test(h)&&!(h.includes("/node_modules/nx/")&&!h.includes(e.root))&&!(g===process.pid||g===s)&&!(s!==void 0&&u===s)){try{process.kill(-u,"SIGKILL")}catch{try{process.kill(g,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${o}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),a(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),S=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,r=Math.max(1,e.decl.minPods??1);_(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${r})\u2026`),a(e,"info",`service starting: ${e.project} (${r} pod${r>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:r}),e.state="starting",e.spawnAt=Date.now();const s=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),[c,d]=fs.existsSync(s)?[process.execPath,[s,"run",o,`--childCount=${r}`]]:["yarn",["nx","run",o,`--childCount=${r}`]],w=(0,node_child_process_1.spawn)(c,d,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),...eastWestEnv(t,e.project)},stdio:["ignore","pipe","pipe"]});e.lastErrors=[],e.buildFailed=!1;const g=i(u=>{const h=u.toString();for(const j of h.split(`
3
+ `)){if(/ERROR in |error TS\d+|Found \d+ error|Unhandled 'error' event|EADDRINUSE/.test(j)){const $=j.replace(/\x1b\[[0-9;]*m/g,"").trim();e.lastErrors.push($),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const x=Date.now();($!==e.lastErrorEmitSig||x-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=$,e.lastErrorEmitAt=x,a(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:$}))}/No typescript errors found|webpack compiled successfully|Child#\d+ ready/.test(j)&&(e.buildFailed=!1),!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(j)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${o}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),a(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(y(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),_(e,o),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&S(e)},1500).unref?.())},250).unref?.())}},"sniff");w.stdout?.on("data",u=>{t.stopped()||(g(u),process.stdout.write(`[${e.project}] ${u}`))}),w.stderr?.on("data",u=>{t.stopped()||(g(u),process.stderr.write(`[${e.project}] ${u}`))}),w.on("exit",u=>{if(e.proc=void 0,e.state="down",t.stopped()||(console.log(`[gateway] ${o} exited (${u})`),a(e,u===0?"info":"warning",`service down: ${e.project} (exit ${u})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:u}),u===0||e.buildFailed))return;const h=Date.now();if(e.restarts=(e.restarts??[]).filter($=>h-$<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){a(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(h);const j=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),a(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),S(e))},j)}),e.proc=w},"spawnService"),f=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const r=e.pool.socks.length;return a(e,"info",`service up: ${e.project} (${r} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:r,adopted:o==="adopted"}),!0},"markUp"),k=i(async e=>{if(v(e.pool)>0)return f(e,"adopted"),!0;if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&S(e);const o=Date.now()+t.startupTimeoutMs,r=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>r)return!1;if(v(e.pool)>0){e.state="up",e.buildFailed=!1;const s=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${s?`, cold start ${s}ms`:""})`),a(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${s??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:s}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),M=i(e=>{const o=e.decl.maxPods??e.decl.minPods??1,r=e.pool.socks.length,s=e.decl.adminUrl??(e.decl.port?`http://localhost:${e.decl.port}`:void 0);if(!s||r===0||r>=o||e.inflight<=r||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();const c=(s.startsWith("https")?require("node:https"):http).request(`${s}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume());c.on("error",()=>{}),c.end(JSON.stringify({pods:r+1})),console.log(`[gateway] autoscale ${e.project}: ${r} \u2192 ${r+1} pods (inflight ${e.inflight})`),a(e,"info",`autoscale: ${e.project} ${r} \u2192 ${r+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:r,to:r+1,inflight:e.inflight})},"scaleUp"),y=i((e,o,r)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),r?.announce!==!1&&a(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const s=e.proc;try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}try{process.kill(-s.pid,"SIGTERM")}catch{try{s.kill("SIGTERM")}catch{}}e.proc=void 0,e.state="down",setTimeout(()=>{try{for(const c of fs.readdirSync(e.pool.dir)){if(!c.endsWith(".sock"))continue;const d=path.join(e.pool.dir,c);try{const w=Number(fs.readFileSync(`${d}.owner`,"utf8").trim());if(Number.isFinite(w)&&w>0)try{process.kill(w,0);continue}catch{}}catch{}try{fs.unlinkSync(d)}catch{}try{fs.unlinkSync(`${d}.owner`)}catch{}}}catch{}e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),m=i(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(r=>o-r<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){a(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),y(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),a(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),y(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&S(e)},1500).unref?.()},"recycleWedged"),A={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let R;const D=new Map;let C=!1,T=0;const b=i(()=>{const e=t.projectRoots??{},o=new Set(t.services.map(s=>s.project));for(const s of t.routes)s.lens&&o.add(s.lens.project);const r=new Set;for(const s of o){const c=e[s];if(!c)continue;const d=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&r.add(d)}return[...r]},"managedBuckets"),L=i((e,o,r,s)=>{const c=t.projectRoots?.[e];if(!c)return;const d=sourceScopeDirs(t.root,c,r,o,s);return d.length>0?d:void 0},"scopeFor"),P=i(()=>{C||Date.now()-T<types_1.POD_STALE_SCAN_MS||(C=!0,T=Date.now(),(async()=>{const e=(0,scope_1.readSourceSetExclude)(t.root),o=await sourceSetSignature(t.root,void 0,e);if(R!==void 0&&o!==R&&(t.sourceSetChangedAt=Date.now(),l("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed"})),R=o,!t.projectRoots)return;let r;try{r=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(g=>g.isDirectory()).map(g=>g.name)}catch{return}const s=b(),c=new Set(t.services.map(g=>g.project));for(const g of t.routes)g.lens&&c.add(g.lens.project);const d=new Map;for(const g of c){const u=L(g,r,s,e);if(!u)continue;const h=u.join("|"),j=d.get(h)??{dirs:u,projects:[]};j.projects.push(g),d.set(h,j)}const w=Date.now();for(const[g,{dirs:u,projects:h}]of d){const j=await sourceSetSignature(t.root,u,e),$=D.get(g);if(D.set(g,j),!($===void 0||j===$)){t.sourceSetChangedFor??=new Map;for(const x of h)t.sourceSetChangedFor.set(x,w);l("info",`source file set changed in ${h.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:h})}}})().catch(()=>{}).finally(()=>{C=!1}))},"maybeScanSourceSet"),F=i((e,o)=>{e.lastStaleRecycleAt=Date.now(),console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),a(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const r=!!e.decl.eager;y(e,`${o} \u2014 recycling stale pod`,{announce:!1}),r&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&S(e)},1500).unref?.()},"recyclePod");return{scanNow:v,spawnService:S,ensureUp:k,scaleUp:M,killSpawned:y,markUp:f,startSweeper:i(()=>{const e=setInterval(()=>{if(!t.stopped()){types_1.POD_RECYCLE_ENABLED&&P();for(const o of t.services){if(t.stopped()||!o.proc){o.wedgedSince=void 0;continue}const r=o.decl.idleKillSec;if(r&&o.inflight===0&&Date.now()-o.lastUsed>r*1e3){y(o,`idle ${r}s`),o.wedgedSince=void 0;continue}const s=o.state==="starting"&&o.spawnAt!==void 0&&Date.now()-o.spawnAt<t.startupTimeoutMs,c=v(o.pool)>0;if(c||s){if(o.wedgedSince=void 0,c&&f(o,"observed"),c&&!s&&types_1.POD_RECYCLE_ENABLED){const d=t.sourceSetChangedFor?.get(o.project)??t.sourceSetChangedAt??0,w=podRecycleReason(o,Date.now(),d,A);w&&F(o,w)}continue}o.wedgedSince??=Date.now(),Date.now()-o.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(o.wedgedSince=void 0,m(o))}}},t.sweepMs);return e.unref(),e},"startSweeper")}}E(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");
@@ -230,6 +230,15 @@ export declare const POD_STALE_RECYCLE_COOLDOWN_MS: number;
230
230
  * sets `LENSMCP_POD_RECYCLE=0` to stop staleness churn still wants a wedged 502 healed. Set
231
231
  * `LENSMCP_LENS_PROBE=0` to disable. Default ON (dev). */
232
232
  export declare const LENS_PROBE_ENABLED: boolean;
233
+ /** WATCHER-TESTIMONY gate on the stale-source-set recycle. The staleness signal only proves a file was
234
+ * ADDED/REMOVED in the app's scope — never that the vite MISSED it, and its own chokidar almost never
235
+ * does. Kill-first therefore cost every open tab a full reload (goodbye HMR) on each add an agent session
236
+ * made — the dominant "every change reloads the page" complaint (foodguard 2026-08-13). With this ON, the
237
+ * lens sweeper first asks the pod's `/__lensmcp/watcher-health` (served by `@lensmcp/vite-plugin`): a
238
+ * watcher that HEARD the change clears the pod — no recycle, HMR intact; a demonstrably SILENT one
239
+ * recycles exactly as before; a pod without the endpoint (older plugin) keeps the legacy kill-first path.
240
+ * Set `LENSMCP_LENS_WATCHER_PROBE=0` to restore kill-first unconditionally. */
241
+ export declare const LENS_WATCHER_PROBE_ENABLED: boolean;
233
242
  /** Spawn grace: a child is never probed (and so never recycled) until it has had this long to BIND its port
234
243
  * — a cold vite/nx boot in a big monorepo takes many seconds, and killing a still-booting dev server would
235
244
  * be a self-inflicted outage. Override `LENSMCP_LENS_PROBE_GRACE_MS`. Default 45s. */
@@ -1 +1 @@
1
- "use strict";var M=Object.defineProperty;var R=(_,E)=>M(_,"name",{value:E,configurable:!0});var O=Object.defineProperty,L=R((_,E)=>O(_,"name",{value:E,configurable:!0}),"L");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=exports.LENS_RECYCLE_KILL_GRACE_MS=exports.LENS_PARKED_PORT_REPROBE_MS=exports.MAX_PORT_CONFLICT_RETRIES=exports.CHILD_START_FAIL_MS=exports.PORT_FREE_POLL_MS=exports.PORT_FREE_WAIT_MS=exports.LENS_PROBE_BOOT_GRACE_MS=exports.LENS_PROBE_COOLDOWN_MS=exports.LENS_PROBE_TIMEOUT_MS=exports.LENS_PROBE_FAILS=exports.LENS_PROBE_GRACE_MS=exports.LENS_PROBE_ENABLED=exports.POD_STALE_RECYCLE_COOLDOWN_MS=exports.POD_STALE_SCAN_MS=exports.POD_RECYCLE_SETTLE_MS=exports.POD_STALE_GRACE_MS=exports.POD_MAX_AGE_MS=exports.POD_RECYCLE_ENABLED=exports.WEDGE_RECYCLE_GRACE_MS=exports.POD_RESPONSE_TIMEOUT_MS=exports.UPSTREAM_HEAL_STEP_MS=exports.UPSTREAM_HEAL_WINDOW_MS=exports.SERVICE_RESTART_WINDOW_MS=exports.SERVICE_MAX_RESTARTS=exports.SERVICE_ERROR_THROTTLE_MS=void 0;function envInt(_,E,e=!1){const S=Number(process.env[_]);return!Number.isFinite(S)||S<0?E:S===0?e?0:E:S}R(envInt,"envInt"),L(envInt,"envInt"),exports.SERVICE_ERROR_THROTTLE_MS=5e3,exports.SERVICE_MAX_RESTARTS=5,exports.SERVICE_RESTART_WINDOW_MS=6e4,exports.UPSTREAM_HEAL_WINDOW_MS=8e3,exports.UPSTREAM_HEAL_STEP_MS=300,exports.POD_RESPONSE_TIMEOUT_MS=(()=>{const _=Number(process.env.LENSMCP_POD_RESPONSE_TIMEOUT_MS);return Number.isFinite(_)&&_>0?_:3e4})(),exports.WEDGE_RECYCLE_GRACE_MS=(()=>{const _=Number(process.env.LENSMCP_WEDGE_RECYCLE_GRACE_MS);return Number.isFinite(_)&&_>0?_:15e3})(),exports.POD_RECYCLE_ENABLED=process.env.LENSMCP_POD_RECYCLE!=="0",exports.POD_MAX_AGE_MS=envInt("LENSMCP_POD_MAX_AGE_MS",240*6e4,!0),exports.POD_STALE_GRACE_MS=envInt("LENSMCP_POD_STALE_GRACE_MS",1e4),exports.POD_RECYCLE_SETTLE_MS=envInt("LENSMCP_POD_RECYCLE_SETTLE_MS",8e3),exports.POD_STALE_SCAN_MS=envInt("LENSMCP_POD_STALE_SCAN_MS",3e4),exports.POD_STALE_RECYCLE_COOLDOWN_MS=envInt("LENSMCP_POD_STALE_RECYCLE_COOLDOWN_MS",6e4),exports.LENS_PROBE_ENABLED=process.env.LENSMCP_LENS_PROBE!=="0",exports.LENS_PROBE_GRACE_MS=envInt("LENSMCP_LENS_PROBE_GRACE_MS",45e3),exports.LENS_PROBE_FAILS=envInt("LENSMCP_LENS_PROBE_FAILS",2),exports.LENS_PROBE_TIMEOUT_MS=envInt("LENSMCP_LENS_PROBE_TIMEOUT_MS",1e3),exports.LENS_PROBE_COOLDOWN_MS=envInt("LENSMCP_LENS_PROBE_COOLDOWN_MS",3e4),exports.LENS_PROBE_BOOT_GRACE_MS=envInt("LENSMCP_LENS_PROBE_BOOT_GRACE_MS",24e4),exports.PORT_FREE_WAIT_MS=envInt("LENSMCP_PORT_FREE_WAIT_MS",1e4),exports.PORT_FREE_POLL_MS=envInt("LENSMCP_PORT_FREE_POLL_MS",250),exports.CHILD_START_FAIL_MS=envInt("LENSMCP_CHILD_START_FAIL_MS",1e4),exports.MAX_PORT_CONFLICT_RETRIES=envInt("LENSMCP_MAX_PORT_CONFLICT_RETRIES",5),exports.LENS_PARKED_PORT_REPROBE_MS=envInt("LENSMCP_PARKED_PORT_REPROBE_MS",3e5),exports.LENS_RECYCLE_KILL_GRACE_MS=envInt("LENSMCP_RECYCLE_KILL_GRACE_MS",5e3),exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=envInt("LENSMCP_KEEPALIVE_TIMEOUT_MS",76e3);
1
+ "use strict";var M=Object.defineProperty;var R=(_,E)=>M(_,"name",{value:E,configurable:!0});var O=Object.defineProperty,L=R((_,E)=>O(_,"name",{value:E,configurable:!0}),"L");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=exports.LENS_RECYCLE_KILL_GRACE_MS=exports.LENS_PARKED_PORT_REPROBE_MS=exports.MAX_PORT_CONFLICT_RETRIES=exports.CHILD_START_FAIL_MS=exports.PORT_FREE_POLL_MS=exports.PORT_FREE_WAIT_MS=exports.LENS_PROBE_BOOT_GRACE_MS=exports.LENS_PROBE_COOLDOWN_MS=exports.LENS_PROBE_TIMEOUT_MS=exports.LENS_PROBE_FAILS=exports.LENS_PROBE_GRACE_MS=exports.LENS_WATCHER_PROBE_ENABLED=exports.LENS_PROBE_ENABLED=exports.POD_STALE_RECYCLE_COOLDOWN_MS=exports.POD_STALE_SCAN_MS=exports.POD_RECYCLE_SETTLE_MS=exports.POD_STALE_GRACE_MS=exports.POD_MAX_AGE_MS=exports.POD_RECYCLE_ENABLED=exports.WEDGE_RECYCLE_GRACE_MS=exports.POD_RESPONSE_TIMEOUT_MS=exports.UPSTREAM_HEAL_STEP_MS=exports.UPSTREAM_HEAL_WINDOW_MS=exports.SERVICE_RESTART_WINDOW_MS=exports.SERVICE_MAX_RESTARTS=exports.SERVICE_ERROR_THROTTLE_MS=void 0;function envInt(_,E,e=!1){const S=Number(process.env[_]);return!Number.isFinite(S)||S<0?E:S===0?e?0:E:S}R(envInt,"envInt"),L(envInt,"envInt"),exports.SERVICE_ERROR_THROTTLE_MS=5e3,exports.SERVICE_MAX_RESTARTS=5,exports.SERVICE_RESTART_WINDOW_MS=6e4,exports.UPSTREAM_HEAL_WINDOW_MS=8e3,exports.UPSTREAM_HEAL_STEP_MS=300,exports.POD_RESPONSE_TIMEOUT_MS=(()=>{const _=Number(process.env.LENSMCP_POD_RESPONSE_TIMEOUT_MS);return Number.isFinite(_)&&_>0?_:3e4})(),exports.WEDGE_RECYCLE_GRACE_MS=(()=>{const _=Number(process.env.LENSMCP_WEDGE_RECYCLE_GRACE_MS);return Number.isFinite(_)&&_>0?_:15e3})(),exports.POD_RECYCLE_ENABLED=process.env.LENSMCP_POD_RECYCLE!=="0",exports.POD_MAX_AGE_MS=envInt("LENSMCP_POD_MAX_AGE_MS",240*6e4,!0),exports.POD_STALE_GRACE_MS=envInt("LENSMCP_POD_STALE_GRACE_MS",1e4),exports.POD_RECYCLE_SETTLE_MS=envInt("LENSMCP_POD_RECYCLE_SETTLE_MS",8e3),exports.POD_STALE_SCAN_MS=envInt("LENSMCP_POD_STALE_SCAN_MS",3e4),exports.POD_STALE_RECYCLE_COOLDOWN_MS=envInt("LENSMCP_POD_STALE_RECYCLE_COOLDOWN_MS",6e4),exports.LENS_PROBE_ENABLED=process.env.LENSMCP_LENS_PROBE!=="0",exports.LENS_WATCHER_PROBE_ENABLED=process.env.LENSMCP_LENS_WATCHER_PROBE!=="0",exports.LENS_PROBE_GRACE_MS=envInt("LENSMCP_LENS_PROBE_GRACE_MS",45e3),exports.LENS_PROBE_FAILS=envInt("LENSMCP_LENS_PROBE_FAILS",2),exports.LENS_PROBE_TIMEOUT_MS=envInt("LENSMCP_LENS_PROBE_TIMEOUT_MS",1e3),exports.LENS_PROBE_COOLDOWN_MS=envInt("LENSMCP_LENS_PROBE_COOLDOWN_MS",3e4),exports.LENS_PROBE_BOOT_GRACE_MS=envInt("LENSMCP_LENS_PROBE_BOOT_GRACE_MS",24e4),exports.PORT_FREE_WAIT_MS=envInt("LENSMCP_PORT_FREE_WAIT_MS",1e4),exports.PORT_FREE_POLL_MS=envInt("LENSMCP_PORT_FREE_POLL_MS",250),exports.CHILD_START_FAIL_MS=envInt("LENSMCP_CHILD_START_FAIL_MS",1e4),exports.MAX_PORT_CONFLICT_RETRIES=envInt("LENSMCP_MAX_PORT_CONFLICT_RETRIES",5),exports.LENS_PARKED_PORT_REPROBE_MS=envInt("LENSMCP_PARKED_PORT_REPROBE_MS",3e5),exports.LENS_RECYCLE_KILL_GRACE_MS=envInt("LENSMCP_RECYCLE_KILL_GRACE_MS",5e3),exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=envInt("LENSMCP_KEEPALIVE_TIMEOUT_MS",76e3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.18.12",
3
+ "version": "1.19.0",
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",
@@ -65,9 +65,9 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@lensmcp/core": "1.18.12",
69
- "@lensmcp/node-instrumentation": "1.18.12",
70
- "@lensmcp/nx-plugin": "1.18.12",
68
+ "@lensmcp/core": "1.19.0",
69
+ "@lensmcp/node-instrumentation": "1.19.0",
70
+ "@lensmcp/nx-plugin": "1.19.0",
71
71
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",
72
72
  "fork-ts-checker-webpack-plugin": "^9.0.0",
73
73
  "glob": "^11.0.0",