@lensmcp/cluster 1.25.29 → 1.25.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ export declare const DEVSERVER_IN_PROCESS_ENV = "LENSMCP_DEVSERVER_IN_PROCESS";
2
+ /** The conventional exit status of a process ended by `signal` (nx reports a signalled task the same way). */
3
+ export declare function exitCodeForSignal(signal: NodeJS.Signals): number;
4
+ /** The serve runner marks its process: an executor in it may load the devserver. */
5
+ export declare function markServeRunner(): void;
6
+ /** May the serve executor load its devserver into this process? */
7
+ export declare function devserverInProcessOffered(env?: NodeJS.ProcessEnv): boolean;
8
+ /** The executor loaded the devserver here: from now on it ends the process. */
9
+ export declare function markDevserverInProcess(): void;
10
+ /** Does a devserver loaded into this process own its exit? The runner's and the executor's stop handlers then leave it. */
11
+ export declare function devserverOwnsExit(): boolean;
12
+ /**
13
+ * The status a devserver that stopped for `reason` ends its process with. In a process of its own, 0, as always: its
14
+ * runner's status is the one the gateway reads. In the runner's process it is that status — `128 + n` for a signal —
15
+ * so a tree stopped by a signal nobody in the gateway sent is still read as one, and healed.
16
+ */
17
+ export declare function devserverStopExitCode(reason: string): number;
18
+ /** Tests only. */
19
+ export declare function resetDevserverHostForTests(): void;
@@ -0,0 +1 @@
1
+ "use strict";var n=Object.defineProperty;var s=(r,o)=>n(r,"name",{value:o,configurable:!0});var t=Object.defineProperty,e=s((r,o)=>t(r,"name",{value:o,configurable:!0}),"e");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEVSERVER_IN_PROCESS_ENV=void 0,exports.exitCodeForSignal=exitCodeForSignal,exports.markServeRunner=markServeRunner,exports.devserverInProcessOffered=devserverInProcessOffered,exports.markDevserverInProcess=markDevserverInProcess,exports.devserverOwnsExit=devserverOwnsExit,exports.devserverStopExitCode=devserverStopExitCode,exports.resetDevserverHostForTests=resetDevserverHostForTests;const tslib_1=require("tslib"),os=tslib_1.__importStar(require("node:os"));exports.DEVSERVER_IN_PROCESS_ENV="LENSMCP_DEVSERVER_IN_PROCESS";const RUNNER_KEY="__lensmcpServeRunner",IN_PROCESS_KEY="__lensmcpDevserverInProcess",flags=globalThis;function exitCodeForSignal(r){return 128+(os.constants.signals[r]??0)}s(exitCodeForSignal,"exitCodeForSignal"),e(exitCodeForSignal,"exitCodeForSignal");function markServeRunner(){flags[RUNNER_KEY]=!0}s(markServeRunner,"markServeRunner"),e(markServeRunner,"markServeRunner");function devserverInProcessOffered(r=process.env){return flags[RUNNER_KEY]===!0&&r[exports.DEVSERVER_IN_PROCESS_ENV]?.trim()!=="0"}s(devserverInProcessOffered,"devserverInProcessOffered"),e(devserverInProcessOffered,"devserverInProcessOffered");function markDevserverInProcess(){flags[IN_PROCESS_KEY]=!0}s(markDevserverInProcess,"markDevserverInProcess"),e(markDevserverInProcess,"markDevserverInProcess");function devserverOwnsExit(){return flags[IN_PROCESS_KEY]===!0}s(devserverOwnsExit,"devserverOwnsExit"),e(devserverOwnsExit,"devserverOwnsExit");function devserverStopExitCode(r){if(!devserverOwnsExit())return 0;const o=os.constants.signals[r];return o===void 0?0:128+o}s(devserverStopExitCode,"devserverStopExitCode"),e(devserverStopExitCode,"devserverStopExitCode");function resetDevserverHostForTests(){delete flags[RUNNER_KEY],delete flags[IN_PROCESS_KEY]}s(resetDevserverHostForTests,"resetDevserverHostForTests"),e(resetDevserverHostForTests,"resetDevserverHostForTests");
@@ -1 +1 @@
1
- "use strict";var O=Object.defineProperty;var y=(f,n)=>O(f,"name",{value:n,configurable:!0});var R=Object.defineProperty,p=y((f,n)=>R(f,"name",{value:n,configurable:!0}),"p");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHandler=createHandler;const manifest_1=require("../manifest"),service_keys_1=require("./service-keys"),gateway_errors_1=require("../gateway-errors"),discovery_1=require("./discovery"),edge_1=require("./edge"),observability_1=require("./observability"),chooser_1=require("./chooser");function createHandler(f){const{rt:n,obs:d,auth:v,proxy:g,hooks:i}=f,E=p(e=>(e.headers.host||"").split(":")[0],"hostOf"),x=p((e,t)=>{if((0,edge_1.cors)(e,t,n.routes),e.method==="OPTIONS"){t.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_NOROUTE,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*"),"access-control-max-age":edge_1.PREFLIGHT_MAX_AGE}),t.end();return}const o=(e.headers.host||"").split(":")[0],u=n.routes.filter(r=>r.host&&(0,manifest_1.hostMatches)(r.host,o)&&r.prefix),s=(e.url??"/").split("?")[0];if((s==="/"||s==="/index.html"||s==="")&&n.routes.some(r=>r.host&&(0,manifest_1.hostMatches)(r.host,o)&&r.project===observability_1.LENS_DASHBOARD_PROJECT)){(0,chooser_1.serveChooser)(t,n.wsKey,e);return}if(u.length>0&&(s==="/health"||s==="/healthz")){const r=u.map(l=>({project:l.project,prefix:l.prefix,pods:l.pool?((0,discovery_1.pickSock)(l.pool,0),l.pool.socks.length):void 0,state:l.svc?.state??(l.target?"up":"unknown")})),h=r.every(l=>l.state!=="down"||!0);t.writeHead(h?200:503,{"content-type":"application/json"}),t.end(JSON.stringify({status:"ok",host:o,services:r},null,2));return}if(u.length>0&&s==="/.well-known/services"){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({host:o,services:u.map(r=>({prefix:r.prefix,project:r.project,internal:!!r.internal}))},null,2));return}(0,edge_1.sendError)(t,e,404,"not_found",u.length>0?`No service mounted at ${s}. Mounted on ${o}: ${[...new Set(u.map(r=>r.prefix))].join(", ")} (+ /health, /.well-known/services)`:"No gateway route for this hostname.")},"handleNoRoute"),H=p(async(e,t)=>{if(!e.headers.host){const r=e.authority??e.headers[":authority"];r&&(e.headers.host=r)}(0,edge_1.stripOwnedHeaders)(e);let o=d.beginTrace(e);const u=Date.now();(0,gateway_errors_1.ensureRequestId)(e),o&&(d.traceStep(o,"received",{host:o.host,method:o.method,url:o.url}),e.__lensmcpTrace=o);const s=i.httpContext(e,t,o);try{const r=e.url??"/",h=(0,edge_1.canonicalize)(r);if(h===void 0){d.traceStep(o,"bad-path",{}),d.finishTrace(o,"gateway",400),(0,edge_1.sendError)(t,e,400,"bad_request","Bad Request");return}const l=h.policy,m=h.path+h.query;if(m!==r&&(0,edge_1.rememberRawTarget)(e,r),e.url=m,(0,edge_1.resolveClientIp)(n,e),i.has("onRequest")&&(await i.run("onRequest",s),s.responded()))return;const a=(0,manifest_1.matchRoute)(n.routes,E(e),l+h.query);if(!a){x(e,t);return}s.route=a;const w=d.startHeadlessFlow(e,a,o,u);if(w!==o&&(o=w,s.trace=w,e.__lensmcpTrace=w),d.traceStep(o,"route-match",{project:a.project,...a.prefix?{prefix:a.prefix}:{},...a.host?{routeHost:a.host}:{},...a.internal?{internal:!0}:{}}),e.method==="OPTIONS"){(0,edge_1.cors)(e,t,n.routes),t.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_ROUTED,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*"),"access-control-max-age":edge_1.PREFLIGHT_MAX_AGE}),t.end();return}if((0,edge_1.cors)(e,t,n.routes),i.has("beforeAuth")&&(await i.run("beforeAuth",s),s.responded()))return;if(a.internal){const c=v.internal(e,a,o);if(!c.ok){d.finishTrace(o,a.project,c.status,void 0,a),(0,edge_1.sendError)(t,e,c.status,c.reason,c.detail);return}s.caller=c.caller}else{const c=v.public(e,a,o,l);if(!c.ok){d.finishTrace(o,a.project,c.status,void 0,a),(0,edge_1.sendError)(t,e,c.status,c.reason,c.detail);return}if(s.claims=c.claims,i.has("afterAuth")&&(await i.run("afterAuth",s),s.responded()))return}const _=a.pool?n.serviceKeys[(0,service_keys_1.serviceKeyId)(a.svc?.wsKey,a.project)]:void 0;if(_&&(e.headers["x-internal-token"]=_),(0,edge_1.stampForwardingHeaders)(n,e),i.has("beforeForward")&&(await i.run("beforeForward",s),s.responded()))return;i.has("onResponse")&&t.on("close",()=>{s.status=t.statusCode??0,i.run("onResponse",s)}),g.forward(a,e,t)}catch(r){s.error=r,console.error(`[gateway] ${new Date().toISOString()} pipeline error traceId=${(0,gateway_errors_1.ensureRequestId)(e)}: ${r?.stack??String(r)}`),i.has("onError")&&await i.run("onError",s),s.responded()||(0,edge_1.sendError)(t,e,500,"internal","Internal gateway error.")}},"runPipeline");return(e,t)=>{H(e,t)}}y(createHandler,"createHandler"),p(createHandler,"createHandler");
1
+ "use strict";var T=Object.defineProperty;var y=(f,n)=>T(f,"name",{value:n,configurable:!0});var O=Object.defineProperty,p=y((f,n)=>O(f,"name",{value:n,configurable:!0}),"p");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHandler=createHandler;const manifest_1=require("../manifest"),service_keys_1=require("./service-keys"),gateway_errors_1=require("../gateway-errors"),discovery_1=require("./discovery"),edge_1=require("./edge"),observability_1=require("./observability"),chooser_1=require("./chooser");function createHandler(f){const{rt:n,obs:u,auth:v,proxy:E,hooks:i}=f,R=p(e=>(e.headers.host||"").split(":")[0],"hostOf"),S=p((e,t)=>{if((0,edge_1.cors)(e,t,n.routes),e.method==="OPTIONS"){t.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_NOROUTE,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*"),"access-control-max-age":edge_1.PREFLIGHT_MAX_AGE}),t.end();return}const o=(e.headers.host||"").split(":")[0],h=n.routes.filter(r=>r.host&&(0,manifest_1.hostMatches)(r.host,o)&&r.prefix),s=(e.url??"/").split("?")[0];if((s==="/"||s==="/index.html"||s==="")&&n.routes.some(r=>r.host&&(0,manifest_1.hostMatches)(r.host,o)&&r.project===observability_1.LENS_DASHBOARD_PROJECT)){(0,chooser_1.serveChooser)(t,n.wsKey,e);return}if(h.length>0&&(s==="/health"||s==="/healthz")){const r=h.map(l=>({project:l.project,prefix:l.prefix,pods:l.pool?((0,discovery_1.pickSock)(l.pool,0),l.pool.socks.length):void 0,state:l.svc?.state??(l.target?"up":"unknown")})),d=r.every(l=>l.state!=="down"||!0);t.writeHead(d?200:503,{"content-type":"application/json"}),t.end(JSON.stringify({status:"ok",host:o,services:r},null,2));return}if(h.length>0&&s==="/.well-known/services"){t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({host:o,services:h.map(r=>({prefix:r.prefix,project:r.project,internal:!!r.internal}))},null,2));return}(0,edge_1.sendError)(t,e,404,"not_found",h.length>0?`No service mounted at ${s}. Mounted on ${o}: ${[...new Set(h.map(r=>r.prefix))].join(", ")} (+ /health, /.well-known/services)`:"No gateway route for this hostname.")},"handleNoRoute"),H=p(async(e,t)=>{if(!e.headers.host){const r=e.authority??e.headers[":authority"];r&&(e.headers.host=r)}(0,edge_1.stripOwnedHeaders)(e);let o=u.beginTrace(e);const h=Date.now();(0,gateway_errors_1.ensureRequestId)(e),o&&(u.traceStep(o,"received",{host:o.host,method:o.method,url:o.url}),e.__lensmcpTrace=o);const s=i.httpContext(e,t,o);try{const r=e.url??"/",d=(0,edge_1.canonicalize)(r);if(d===void 0){u.traceStep(o,"bad-path",{}),u.finishTrace(o,"gateway",400),(0,edge_1.sendError)(t,e,400,"bad_request","Bad Request");return}const l=d.policy,_=d.path+d.query;if(_!==r&&(0,edge_1.rememberRawTarget)(e,r),e.url=_,(0,edge_1.resolveClientIp)(n,e),i.has("onRequest")&&(await i.run("onRequest",s),s.responded()))return;const a=(0,manifest_1.matchRoute)(n.routes,R(e),l+d.query);if(!a){S(e,t);return}s.route=a;const w=u.startHeadlessFlow(e,a,o,h);if(w!==o&&(o=w,s.trace=w,e.__lensmcpTrace=w),u.traceStep(o,"route-match",{project:a.project,...a.prefix?{prefix:a.prefix}:{},...a.host?{routeHost:a.host}:{},...a.internal?{internal:!0}:{}}),e.method==="OPTIONS"){(0,edge_1.cors)(e,t,n.routes),t.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_ROUTED,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*"),"access-control-max-age":edge_1.PREFLIGHT_MAX_AGE}),t.end();return}if((0,edge_1.cors)(e,t,n.routes),i.has("beforeAuth")&&(await i.run("beforeAuth",s),s.responded()))return;if(a.internal){const c=v.internal(e,a,o);if(!c.ok){u.finishTrace(o,a.project,c.status,void 0,a),(0,edge_1.sendError)(t,e,c.status,c.reason,c.detail);return}s.caller=c.caller}else{const c=v.public(e,a,o,l);if(!c.ok){u.finishTrace(o,a.project,c.status,void 0,a),(0,edge_1.sendError)(t,e,c.status,c.reason,c.detail);return}if(s.claims=c.claims,i.has("afterAuth")&&(await i.run("afterAuth",s),s.responded()))return}const g=a.pool?n.serviceKeys[(0,service_keys_1.serviceKeyId)(a.svc?.wsKey,a.project)]:void 0;if(g&&(e.headers["x-internal-token"]=g),(0,edge_1.stampForwardingHeaders)(n,e),i.has("beforeForward")&&(await i.run("beforeForward",s),s.responded()))return;i.has("onResponse")&&t.on("close",()=>{s.status=t.statusCode??0,i.run("onResponse",s)});const m=a.project===observability_1.LENS_DASHBOARD_PROJECT&&a.target?n.hostedDashboards?.get(a.target):void 0;m&&await m.serve(t),E.forward(a,e,t)}catch(r){s.error=r,console.error(`[gateway] ${new Date().toISOString()} pipeline error traceId=${(0,gateway_errors_1.ensureRequestId)(e)}: ${r?.stack??String(r)}`),i.has("onError")&&await i.run("onError",s),s.responded()||(0,edge_1.sendError)(t,e,500,"internal","Internal gateway error.")}},"runPipeline");return(e,t)=>{H(e,t)}}y(createHandler,"createHandler"),p(createHandler,"createHandler");
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A REGISTERED workspace's dashboard runs only while something uses it.
3
+ *
4
+ * The daemon hosts a dashboard for every workspace that registers with it — each worktree instance and each guest
5
+ * (lens-children.ts `hostWorkspaceDashboard`). It ran from registration until unregister, whether anyone looked at it
6
+ * or not: ~150 MB for one nobody had opened, and on FoodGuard 1.5–1.8 GB once it had ingested a few hours of traffic
7
+ * (2026-10-27). With five or six worktrees that was gigabytes for pages nobody had open.
8
+ *
9
+ * So a hosted dashboard is parked until it is wanted, and parked again when it is not:
10
+ * - It starts on a request to its route — the gateway holds that request until the dashboard answers
11
+ * ({@link HostedDashboard.serve}) — or when one of its workspace's services comes up: the dashboard also runs the
12
+ * workspace's automatic reviews and writes its catalog, which matter while the workspace has traffic.
13
+ * - It stops once no response of its own is open (an open tab holds its event stream), none has been for
14
+ * `LENSMCP_DASHBOARD_IDLE_SEC` (600 s), and no service of its workspace is up.
15
+ *
16
+ * The daemon's OWN dashboard is not managed here: it runs as long as the daemon does.
17
+ */
18
+ /** How long a parked dashboard may take to answer before the held request goes on to the proxy anyway. */
19
+ export declare const DASHBOARD_READY_MS = 20000;
20
+ export declare const DASHBOARD_IDLE_ENV = "LENSMCP_DASHBOARD_IDLE_SEC";
21
+ export declare const DEFAULT_DASHBOARD_IDLE_SEC = 600;
22
+ /** The idle window, in ms — `undefined` when parking is off (`LENSMCP_DASHBOARD_IDLE_SEC=0`). */
23
+ export declare function dashboardIdleMs(env?: NodeJS.ProcessEnv): number | undefined;
24
+ export type HostedDashboardState = 'parked' | 'starting' | 'up';
25
+ export interface HostedDashboardDeps {
26
+ /** Start the dashboard child (its auto-heal included). */
27
+ start(): void | Promise<void>;
28
+ /** Stop it ON PURPOSE: no auto-heal brings it back. */
29
+ stop(): void;
30
+ /** Does its port answer? */
31
+ listening(): Promise<boolean>;
32
+ /** Is a service of its workspace up or starting? */
33
+ servicesUp(): boolean;
34
+ /** Parking off: it starts at once and stays up, as before. */
35
+ idleMs: number | undefined;
36
+ readyMs?: number;
37
+ now?: () => number;
38
+ sleep?: (ms: number) => Promise<void>;
39
+ log?: (line: string) => void;
40
+ }
41
+ /** What a held response is to the dashboard: it is in use until its `close`. */
42
+ export interface ClosingResponse {
43
+ once(event: 'close', listener: () => void): unknown;
44
+ }
45
+ export interface HostedDashboard {
46
+ /** A request for it: start it when parked and wait (bounded) until it answers; it stays up until `res` closes. */
47
+ serve(res: ClosingResponse): Promise<void>;
48
+ /** One sweep: park it when idle, start it when its workspace has a service up. */
49
+ tick(): void;
50
+ state(): HostedDashboardState;
51
+ /** Its workspace left: nothing starts it again. */
52
+ dispose(): void;
53
+ }
54
+ export declare function createHostedDashboard(name: string, deps: HostedDashboardDeps): HostedDashboard;
@@ -0,0 +1 @@
1
+ "use strict";var f=Object.defineProperty;var c=(s,e)=>f(s,"name",{value:e,configurable:!0});var p=Object.defineProperty,a=c((s,e)=>p(s,"name",{value:e,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEFAULT_DASHBOARD_IDLE_SEC=exports.DASHBOARD_IDLE_ENV=exports.DASHBOARD_READY_MS=void 0,exports.dashboardIdleMs=dashboardIdleMs,exports.createHostedDashboard=createHostedDashboard,exports.DASHBOARD_READY_MS=2e4;const READY_POLL_MS=150;exports.DASHBOARD_IDLE_ENV="LENSMCP_DASHBOARD_IDLE_SEC",exports.DEFAULT_DASHBOARD_IDLE_SEC=600;function dashboardIdleMs(s=process.env){const e=s[exports.DASHBOARD_IDLE_ENV]?.trim(),t=e?Number(e):NaN;return!Number.isFinite(t)||t<0?exports.DEFAULT_DASHBOARD_IDLE_SEC*1e3:t>0?t*1e3:void 0}c(dashboardIdleMs,"dashboardIdleMs"),a(dashboardIdleMs,"dashboardIdleMs");const defaultSleep=a(s=>new Promise(e=>{setTimeout(e,s).unref?.()}),"defaultSleep");function createHostedDashboard(s,e){const t=e.now??Date.now,l=e.sleep??defaultSleep,_=e.log??(o=>console.log(o)),E=e.readyMs??exports.DASHBOARD_READY_MS;let r="parked",i,n=0,d=t(),D=!1;const A=a(o=>r!=="parked"||D?i??Promise.resolve():(r="starting",d=t(),o&&_(`[gateway] dashboard of '${s}' starting (${o})`),i=(async()=>{try{await e.start();const u=t()+E;for(;!D&&t()<u&&!await e.listening();)await l(READY_POLL_MS)}catch(u){_(`[gateway] dashboard of '${s}' did not start: ${u.message}`)}r==="starting"&&(r="up"),d=t()})(),i),"start"),S=a(()=>{r="parked",i=void 0,e.stop(),_(`[gateway] dashboard of '${s}' stopped \u2014 nobody has it open and no service of it runs (it starts on the next request)`)},"park");return e.idleMs===void 0&&A(),{async serve(o){n+=1,d=t(),o.once("close",()=>{n=Math.max(0,n-1),d=t()}),r!=="up"&&await A("a request for it")},tick(){if(D)return;const o=e.servicesUp();if(o&&(d=t()),r==="parked"){o&&A("a service of it is up");return}e.idleMs===void 0||r!=="up"||n>0||o||t()-d>=e.idleMs&&S()},state:a(()=>r,"state"),dispose(){D=!0}}}c(createHostedDashboard,"createHostedDashboard"),a(createHostedDashboard,"createHostedDashboard");
@@ -1 +1 @@
1
- "use strict";var Ce=Object.defineProperty;var y=(s,g)=>Ce(s,"name",{value:g,configurable:!0});var Re=Object.defineProperty,d=y((s,g)=>Re(s,"name",{value:g,configurable:!0}),"d");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"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(s,g){return new Promise(f=>{const _=net.createServer();_.unref(),_.once("error",p=>{const w=p.code??"";f(w==="EADDRINUSE"||w==="EACCES"?"in-use":"unavailable")}),_.listen(g===void 0?{port:s}:{port:s,host:g},()=>_.close(()=>f("free")))})}y(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(s){for(const g of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(s,g)==="in-use")return!1;return!0}y(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(s,g,f){return new Promise(_=>{let p=!1;const w=net.createConnection({host:s,port:g});w.unref();const N=setTimeout(()=>v(!1),f);N.unref?.();function v(A){p||(p=!0,clearTimeout(N),w.destroy(),_(A))}y(v,"R"),d(v,"finish"),w.once("connect",()=>v(!0)),w.once("error",()=>v(!1))})}y(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(s,g=1e3){return await probeConnect("127.0.0.1",s,g)?!0:probeConnect("::1",s,g)}y(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(s,g=1500){try{const f=new AbortController,_=setTimeout(()=>f.abort(),g);_.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(v=>v&&typeof v.path=="string"&&typeof v.at=="number"&&typeof v.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(v.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}}y(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(s=>new Promise(g=>{setTimeout(g,s).unref?.()}),"sleepUnref");async function waitForPortFree(s,g,f={}){const _=f.isPortFree??probePortFree,p=Math.max(1,f.intervalMs??types_1.PORT_FREE_POLL_MS),w=Date.now()+Math.max(0,g);for(;;){if(await _(s))return!0;const N=w-Date.now();if(N<=0)return!1;await sleepUnref(Math.min(p,N))}}y(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(s){const g=d(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 g(Number(s[f+1]));if(_.startsWith("--port="))return g(Number(_.slice(7)))}}y(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(s){return s.signalCode?`killed by ${s.signalCode}`:s.exitCode===null?"exit status unknown":`exit code ${s.exitCode}`}y(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");function signalChild(s,g){if(s.pid===void 0)return!1;try{return s.kill(g)}catch{return!1}}y(signalChild,"signalChild"),d(signalChild,"signalChild");async function claimPort(s,g,f,_){let p=s;for(;g.has(p)||_?.has(p)||!await f(p);)p+=1;return g.add(p),p}y(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(s,g,f,_){const{emit:p}=g,w=_?.isPortFree??probePortFree,N=(f.ports?.length?f.ports:[443]).join(","),v=_?.probeWatcherHealth??probeWatcherHealth,A=[],B=new Map,T=new Map,K=new Map,q=new Map,G=new Set;let F;const b=new Map,M=new Set,V=new Set,U=new Set,k=new Set,L=new Map,I=new WeakMap,H=new Set,ee=d((e,r,n)=>{if(H.has(e))return;H.add(e);const t=setInterval(()=>{(async()=>{if(s.stopped()||M.has(e)){clearInterval(t),H.delete(e);return}await w(r)&&(clearInterval(t),H.delete(e),console.log(`[gateway] lens child ${e}: :${r} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),x=d(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"),de=d((e,r,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...L.get(t)?.generations??[]].find(i=>i.generation===o);a&&(e.endsWith(":vite")?a.vite=r:e.endsWith(":browser-capture")&&(a.capture=r))},"attachRespawnedGenerationChild"),Y=d(e=>{e.exitCode!==null||e.signalCode!==null||e.pid===void 0||(V.add(e),signalChild(e,"SIGTERM"),setTimeout(()=>{e.exitCode===null&&e.signalCode===null&&signalChild(e,"SIGKILL")},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.())},"retireChild"),pe=d((e,r)=>{const n=b.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??"?"}).`),Y(n))},"retireLabelHolder"),D=d((e,r,n,t,o=!1,a,i,l)=>{const u=Date.now(),h=(0,own_log_1.gatewayLogTakeover)()!==void 0,c=(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:h?["inherit","pipe","pipe"]:"inherit"});h&&(c.stdout?.on("data",S=>process.stdout.write(S)),c.stderr?.on("data",S=>process.stderr.write(S))),A.push(c),pe(e,c),b.set(e,c),de(e,c,t);const m=e==="vite"||e.endsWith(":vite"),$=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,C=$&&$!==s.eventFile?$:void 0,E=m?viteArgPort(n):l;m&&T.set(c,{key:t?.LENSMCP_FRONTEND_KEY??`${i??s.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??s.root,spawnedAt:u,port:E,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...C?{bus:C}:{}});const P=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return P&&E!==void 0&&(p("info",`lens child starting: ${P} (:${E})`,`cluster-lens-child:${P}`,{kind:"lens-child-starting",project:P,label:e,pid:c.pid,port:E},void 0,C),(async()=>{const S=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<S;){if(c.exitCode!==null||c.signalCode!==null||M.has(e))return;if(await probePortListening(E,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${P} (:${E})`,`cluster-lens-child:${P}`,{kind:"lens-child-up",project:P,label:e,pid:c.pid,port:E},void 0,C);return}await sleepUnref(150)}})().catch(()=>{})),c.on("error",S=>{if(c.pid!==void 0){console.warn(`[gateway] lens child ${e}: ${S.message}`);return}const j=A.indexOf(c);j>=0&&A.splice(j,1),b.get(e)===c&&b.delete(e),T.delete(c),V.delete(c),console.error(`[gateway] lens child ${e} failed to spawn (${S.message}) \u2014 not started, not retried.`)}),c.on("exit",(S,j)=>{const ae=A.indexOf(c);ae>=0&&A.splice(ae,1),b.get(e)===c&&b.delete(e);const O=T.get(c);if(T.delete(c),V.delete(c))return;const Le=M.delete(e);if(s.stopped()||Le||(P?p("warning",`lens child exited: ${P}`,`cluster-lens-child:${P}`,{kind:"lens-child-exit",project:P,label:e,pid:c.pid,code:S,signal:j},void 0,C):O&&p("warning",`lens frontend exited: ${O.project}`,`cluster-lens-child:${O.project}`,{kind:"lens-child-exit",project:O.project,label:e,pid:c.pid,code:S,signal:j,generation:O.generation,port:O.port,ranMs:Date.now()-u},void 0,C),O&&fe(c,O)))return;const ie=Date.now()-u,R=B.get(e)??{attempts:0,portConflicts:0};(async()=>{const Pe=E!==void 0&&!await w(E),le=(0,lifecycle_1.isPortConflictExit)({code:S,signal:j,ranMs:ie,portHeld:Pe},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(le&&R.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)R.portConflicts+=1,B.set(e,R),console.warn(`[gateway] lens child ${e} lost the :${E} bind race (EADDRINUSE, code=${S}); waiting for the port, then retrying (port-retry ${R.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(le){R.portConflicts=0,B.set(e,R),console.warn(`[gateway] lens child ${e}: :${E} 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} (:${E} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:E}),ee(e,E,()=>D(e,r,n,t,o,a,i,l));return}else{R.portConflicts=0,ie>=HEAL_HEALTHY_MS&&(R.attempts=0);const ce=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**R.attempts);R.attempts+=1,B.set(e,R),console.warn(`[gateway] lens child ${e} exited (code=${S} sig=${j}); auto-healing in ${Math.round(ce/1e3)}s (attempt ${R.attempts}).`),await sleepUnref(ce)}s.stopped()||M.has(e)||(E!==void 0&&!await waitForPortFree(E,types_1.PORT_FREE_WAIT_MS,{isPortFree:w,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${E} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(s.stopped()||M.has(e))&&D(e,r,n,t,o,a,i,l))})().catch(()=>{})}),c},"spawnManagedChild"),te=d(e=>{const r=`${e.wsKey}:${e.project}`,n=L.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};L.set(r,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,I.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,I.set(e.frontend,t),t},"registerFrontend"),ue=d(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"),oe=d((e,r,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;M.delete(a),M.delete(i);const l=d((c,m,$,C,E,P)=>D(c,m,$,C,E,P,e.workspaceRoot),"spawnInRoot"),u=(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,...ue(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:d(c=>console.log(c),"log")},l),h={ownerKey:e.key,generation:t,port:r,labelPrefix:o,vite:u?.vite??b.get(a),capture:b.get(i)};return e.generations.add(h),h},"spawnFrontendGeneration"),W=d((e,r)=>{if(!e)return;L.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)M.add(t),o&&Y(o);r&&k.delete(e.port)},"terminateGeneration"),Ee=d(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 v(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),z=d(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"),X=d(e=>!e.disposed&&!s.stopped()&&L.get(e.key)===e,"frontendIsCurrent"),J=d((e,r)=>{if(!X(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 E of L.values())E!==e&&l.add(E.desiredPort);const u=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,h=await x(e.desiredPort,k,{reserved:l,heldBy:u});if(a=h,!X(e))return k.delete(h),!1;o=oe(e,h,"replacement"),e.candidate=o;const c=await z(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${h} 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:h,oldServing:c},void 0,e.eventFile===s.eventFile?void 0:e.eventFile);const m=await Ee(o);if(!X(e))return W(o,!0),e.candidate=void 0,!1;if(!m){W(o,!0),e.candidate=void 0;const E=await z(n);return e.state=E?"ready":"failed",E&&(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:h,durationMs:Date.now()-i,oldKept:E},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}for(const E of e.routes)E.target=`http://localhost:${o.port}`,E.lens&&(E.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 $=o.vite?T.get(o.vite):void 0;$&&($.role="active",$.listening=!0);const C=(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}${C>0?`; reloading ${C} 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:C},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),n){const E=`${n.labelPrefix}browser-capture`;M.add(E),n.capture&&Y(n.capture),setTimeout(()=>W(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){W(o,!0),!o&&a!==void 0&&k.delete(a),e.candidate=void 0;const u=await z(n);e.state=u?"ready":"failed",u&&(e.reason=void 0,e.startedAt=void 0);const h=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${h}`),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:u,error:h},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),ne=d((e,r)=>{e.active=oe(e,r,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),fe=d((e,r)=>{const n=L.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.`),J(n,"child-exit")),!0)},"handleManagedViteExit");s.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const r=I.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=I.get(e);return n?J(n,r):Promise.resolve(!1)}}:void 0;let Z=!1,re=0;const ge=d(()=>{Z||Date.now()-re<types_1.POD_STALE_SCAN_MS||(Z=!0,re=Date.now(),(async()=>{const e=new Map;for(const r of[...L.values()]){if(s.stopped())return;let n=e.get(r.workspaceRoot);if(!n){const h=(0,scope_1.readSourceSetOptions)(r.workspaceRoot);n={pass:(0,lifecycle_1.sourceScanPass)(r.workspaceRoot,h.exclude,{ignoreTests:h.ignoreTests}),startedAt:Date.now()},e.set(r.workspaceRoot,n)}const t=n.startedAt,o=await n.pass.snapshot(r.sourceDirs);if(L.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(),u=(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));u&&(r.sourceChange=u,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:u.added,removed:u.removed,truncated:u.truncated},void 0,r.eventFile===s.eventFile?void 0:r.eventFile))}})().catch(()=>{}).finally(()=>{Z=!1}))},"scanFrontendSourceSets");let Q=!1;const he=d(async(e,r)=>{if(!Q){Q=!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=L.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},K.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`;K.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{Q=!1}}},"probeLensChildren"),we=d(()=>{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||(se(),!types_1.POD_RECYCLE_ENABLED))return;ge();const r=Date.now();for(const[n,t]of T){const o=L.get(t.key);if(o?.frontend.frozen)continue;const a=o?.sourceChange?.changedAt??0,i=K.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,r,e)&&((q.get(t.key)??0)>=a||G.has(t.key)||(G.add(t.key),(async()=>{try{const l=o?.sourceChange,u=l&&!l.truncated?[...l.added,...l.removed].map($=>path.resolve(t.workspaceRoot,$)):[],h=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await v(t.port),a,l?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,u):"unknown";if(h==="heard"){q.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(h==="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;K.set(t.key,Date.now());const c=L.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&c){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${h==="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:c.wsKey,project:t.project,reason:"stale-source-set",watcher:h,added:l?.added,removed:l?.removed},void 0,t.bus),await J(c,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${h==="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:h},void 0,t.bus);const m=(0,hmr_reload_1.broadcastFullReload)(s,t.key);m>0&&console.log(`[gateway] \u2192 told ${m} 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{G.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),F.unref?.()},"startLensSweeper"),_e=d(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 x(f.dashboardPort??o.dashboardPort,U),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=d(()=>{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}),ee("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(u=>u.lens?.project===t.project),i=await x(t.port,k);for(const u of a)u.lens&&(u.lens.port=i),u.target=`http://localhost:${i}`;t.port=i;const l=te({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{ne(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(u){k.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${u.message}`)}}we()},"bootSingletonsAndApps"),Se=d(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 x(n.dashboardPort,U),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");return M.delete(i),s.registry.appendRoutes(e.wsKey,[{host:t,prefix:o,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),(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}),D(i,(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),[r],{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},!1,void 0,e.root,a),()=>{M.add(i),U.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const u=b.get(i);u&&Y(u),b.delete(i)}},"hostWorkspaceDashboard"),ye=d(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 x(i.port,k),u=i.port;for(const c of a)c.lens.started=!0,c.lens.port=l,c.target=`http://localhost:${l}`;const h=te({wsKey:e.wsKey,replayWorkspace:e.instance?.baseKey??e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:u});t.push(h),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{ne(h,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(c){k.delete(l),h.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${c.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])W(a,!0);for(const a of o.routes)a.lens&&I.delete(a.lens);I.delete(o.frontend),K.delete(o.key),q.delete(o.key),G.delete(o.key),L.delete(o.key)}}},"hostWorkspaceApps"),ve=d(async(e=2500)=>{F&&(clearInterval(F),F=void 0);const r=[...A];for(const o of r)signalChild(o,"SIGTERM");const n=d(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"),se=d(()=>he({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:_e,hostWorkspaceDashboard:Se,hostWorkspaceApps:ye,probeLensFrontends:se,reapAll:ve}}y(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
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");
@@ -9,7 +9,9 @@
9
9
  * (`<project>#g<generation>`), starts the host if it is not up, and forwards the join.
10
10
  * - The host answers per member; each answer goes to that member's runner.
11
11
  * - A tree that dies or is killed leaves ({@link SharedBuildRelay.detach}) — the host pauses the project's
12
- * entries once no tree holds it.
12
+ * entries once no tree holds it. Once NO tree is left for `LENSMCP_BUILD_HOST_IDLE_SEC` (300 s), the host itself
13
+ * stops — its watchers close and store the cache — and the next join starts it again. It held 1.6–1.8 GB on
14
+ * FoodGuard, and a worktree whose services had all idled out kept its host for as long as it stayed registered.
13
15
  * - A host that exits unexpectedly is started again and every live join is sent to it again; runners keep
14
16
  * their devservers serving the last good bundle meanwhile. A host that keeps dying (3 exits in a minute) is
15
17
  * given up on: every member is refused, and each runner compiles on its own, as before.
@@ -30,6 +32,10 @@ import type { RunnerScript } from './serve-launch';
30
32
  import type { GatewayRuntime, ServiceCtl } from './types';
31
33
  /** Exits within {@link CRASH_WINDOW_MS} after which the host is given up on. */
32
34
  export declare const HOST_MAX_RESTARTS = 3;
35
+ export declare const BUILD_HOST_IDLE_ENV = "LENSMCP_BUILD_HOST_IDLE_SEC";
36
+ export declare const DEFAULT_BUILD_HOST_IDLE_SEC = 300;
37
+ /** How long a host with no member keeps running, in ms — `undefined` when it never stops for that (`…_IDLE_SEC=0`). */
38
+ export declare function buildHostIdleMs(env?: NodeJS.ProcessEnv): number | undefined;
33
39
  /** The workspace a service belongs to, as the relay needs it: its root, and its key (a registered one's). */
34
40
  export type BuildMember = Pick<ServiceCtl, 'root'> & Partial<Pick<ServiceCtl, 'wsKey' | 'sockNs'>>;
35
41
  export interface SharedBuildRelay {
@@ -56,6 +62,8 @@ export interface SharedBuildRelayDeps {
56
62
  gatewayEnv?: NodeJS.ProcessEnv;
57
63
  /** Skip the stale-host reap (tests). */
58
64
  reapStale?: boolean;
65
+ /** How long a host with no member keeps running. Default {@link buildHostIdleMs}. */
66
+ idleMs?: number;
59
67
  log?: (line: string) => void;
60
68
  }
61
69
  /** The built `build-host.js` at the package root, or the `.ts` beside this source tree. */
@@ -1,4 +1,4 @@
1
- "use strict";var A=Object.defineProperty;var y=(r,o)=>A(r,"name",{value:o,configurable:!0});var j=Object.defineProperty,i=y((r,o)=>j(r,"name",{value:o,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.HOST_MAX_RESTARTS=void 0,exports.buildHostScript=buildHostScript,exports.workspaceBuildHostScript=workspaceBuildHostScript,exports.createSharedBuildRelay=createSharedBuildRelay;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),node_module_1=require("node:module"),path=tslib_1.__importStar(require("node:path")),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),pod_env_1=require("./pod-env"),registration_guards_1=require("./registration-guards");exports.HOST_MAX_RESTARTS=3;const CRASH_WINDOW_MS=6e4;function buildHostScript(r=path.join(__dirname,"../../..")){const o=path.join(r,"build-host.js");if(fs.existsSync(o))return{path:o,execArgv:[]};const u=path.join(r,"build-host.ts");if(fs.existsSync(u))try{return{path:u,execArgv:["--require",require.resolve("@swc-node/register")]}}catch{return}}y(buildHostScript,"buildHostScript"),i(buildHostScript,"buildHostScript");function workspaceBuildHostScript(r){try{return{path:(0,node_module_1.createRequire)(path.join(r,"package.json")).resolve("@lensmcp/cluster/build-host.js"),execArgv:[]}}catch{return}}y(workspaceBuildHostScript,"workspaceBuildHostScript"),i(workspaceBuildHostScript,"workspaceBuildHostScript");function createSharedBuildRelay(r,o={}){const u=o.script===void 0?buildHostScript():o.script??void 0,_=o.scriptFor??workspaceBuildHostScript,h=new Map,f=new WeakMap,d=(0,registration_guards_1.canonicalRoot)(r.root),v=i(e=>{const a=(0,registration_guards_1.canonicalRoot)(e.root);let n=h.get(a);return n||(n=a===d?createWorkspaceRelay(r,{root:r.root,...r.wsKey?{wsKey:r.wsKey}:{},...r.sockNs?{sockNs:r.sockNs}:{},eventFile:r.eventFile},u,o):createWorkspaceRelay(r,{root:e.root,...e.wsKey?{wsKey:e.wsKey}:{},...e.sockNs?{sockNs:e.sockNs}:{},eventFile:path.join(e.root,".lensmcp","events.jsonl")},_(e.root),o),h.set(a,n)),n},"relayFor");return{offeredTo:i(e=>(0,protocol_1.sharedCompilerEnabled)(o.gatewayEnv??process.env,r.sharedCompiler)&&v(e).offered(),"offeredTo"),attach(e,a,n){const S=v(e);f.set(n,S),S.attach(`${e.project}#g${a}`,n)},detach:i(e=>f.get(e)?.detach(e),"detach"),hostPid:i((e=r.root)=>h.get((0,registration_guards_1.canonicalRoot)(e))?.hostPid(),"hostPid"),release(e){const a=(0,registration_guards_1.canonicalRoot)(e),n=h.get(a);return h.delete(a),n?.stop()??Promise.resolve()},async stop(){const e=[...h.values()];h.clear(),await Promise.all(e.map(a=>a.stop()))}}}y(createSharedBuildRelay,"createSharedBuildRelay"),i(createSharedBuildRelay,"createSharedBuildRelay");function createWorkspaceRelay(r,o,u,_){const h=_.spawnProcess??node_child_process_1.spawn,f=_.log??(t=>console.log(t)),d=new Map,v=new WeakMap;let e,a=[],n,S=!1;const E=(0,registration_guards_1.canonicalRoot)(o.root)===(0,registration_guards_1.canonicalRoot)(r.root)?"this workspace":`'${o.wsKey??path.basename(o.root)}'`,b=i((t,s)=>{if(!t.connected)return;const c={channel:protocol_1.SHARED_BUILD_CHANNEL,event:s};try{t.send(c,void 0,void 0,()=>{})}catch{}},"toRunner"),R=i(t=>{if(e?.connected)try{e.send(t,void 0,void 0,()=>{})}catch{}},"toHost"),x=i(()=>{if(_.reapStale===!1)return;let t;try{t=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return}for(const s of t.split(`
2
- `)){const c=s.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!c||!(0,protocol_1.isBuildHostCommandOf)(c[3],o.root))continue;const l=Number(c[1]),p=Number(c[2]);if(!(l===e?.pid||l===process.pid)){try{process.kill(-p,"SIGKILL")}catch{try{process.kill(l,"SIGKILL")}catch{}}f(`[gateway] reaped a stale build host (pid ${l}) of ${E}`)}}},"reapStaleHosts"),N=i(()=>{if(e||S||n||!u)return e;x();const t=o.wsKey||path.basename(o.root),s={...(0,pod_env_1.podBaseEnv)(process.env,r.root,o.root),NX_DAEMON:"false",NODE_ENV:"development",NX_WORKSPACE_ROOT:o.root,...o.wsKey?{LENSMCP_WS_KEY:o.wsKey}:{},...o.sockNs?{LENSMCP_SOCK_NS:o.sockNs}:{},LENSMCP_EVENT_FILE:o.eventFile,LENSMCP_GATEWAY_PID:String(process.pid)},c=h((0,process_name_1.namedExecPath)((0,process_name_1.processName)(t,protocol_1.BUILD_HOST_ROLE)),[...u.execArgv,u.path,...(0,protocol_1.buildHostArgs)(o.root)],{cwd:o.root,detached:!0,env:s,stdio:["ignore","pipe","pipe","ipc"]});e=c,f(`[gateway] build host starting (one compilation for the services of ${E} \u2014 LENSMCP_SHARED_COMPILER)`);const l=i(p=>m=>{if(!r.stopped())for(const g of m.toString().split(`
3
- `))g.trim()&&p.write(`[build] ${g}
4
- `)},"relayOut");return c.stdout?.on("data",l(process.stdout)),c.stderr?.on("data",l(process.stderr)),c.on("message",p=>{if(p?.type==="member"){const m=d.get(p.member);m&&b(m.proc,p.event)}}),c.on("exit",(p,m)=>{if(e===c&&(e=void 0),S||r.stopped())return;const g=Date.now();if(a=[...a.filter(w=>g-w<CRASH_WINDOW_MS),g],a.length>=exports.HOST_MAX_RESTARTS){n=`the build host exited ${a.length} times in a minute (last: ${p??m})`,f(`[gateway] ${n} \u2014 every service compiles on its own from now on`);for(const w of d.values())b(w.proc,{type:"refused",reason:n});d.clear();return}if(d.size!==0&&(f(`[gateway] build host exited (${p??m}) \u2014 restarting it for ${d.size} service tree(s)`),N()))for(const[w,{join:P}]of d)R({type:"join",member:w,join:P})}),c},"start"),k=i(t=>{const s=v.get(t);!s||d.get(s)?.proc!==t||(d.delete(s),R({type:"leave",member:s}))},"detach");return{offered:i(()=>!!u&&!n,"offered"),attach:i((t,s)=>{v.set(s,t),s.on("message",c=>{if((0,protocol_1.isRunnerJoinMessage)(c)){if(n||!N()){b(s,{type:"refused",reason:n??"the build host could not be started"});return}d.set(t,{proc:s,join:c.join}),R({type:"join",member:t,join:c.join})}}),s.once("exit",()=>k(s))},"attach"),detach:k,hostPid:i(()=>e?.pid,"hostPid"),stop(){S=!0,d.clear();const t=e;return e=void 0,!t||t.exitCode!==null||t.signalCode!==null?Promise.resolve():new Promise(s=>{const c=i(()=>{clearTimeout(l),s()},"done");t.once("exit",c);try{process.kill(-t.pid,"SIGTERM")}catch{try{t.kill("SIGTERM")}catch{}}const l=setTimeout(()=>{try{process.kill(-t.pid,"SIGKILL")}catch{try{t.kill("SIGKILL")}catch{}}s()},6e3);l.unref?.()})}}}y(createWorkspaceRelay,"createWorkspaceRelay"),i(createWorkspaceRelay,"createWorkspaceRelay");
1
+ "use strict";var k=Object.defineProperty;var y=(s,o)=>k(s,"name",{value:o,configurable:!0});var j=Object.defineProperty,r=y((s,o)=>j(s,"name",{value:o,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEFAULT_BUILD_HOST_IDLE_SEC=exports.BUILD_HOST_IDLE_ENV=exports.HOST_MAX_RESTARTS=void 0,exports.buildHostIdleMs=buildHostIdleMs,exports.buildHostScript=buildHostScript,exports.workspaceBuildHostScript=workspaceBuildHostScript,exports.createSharedBuildRelay=createSharedBuildRelay;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),node_module_1=require("node:module"),path=tslib_1.__importStar(require("node:path")),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),pod_env_1=require("./pod-env"),registration_guards_1=require("./registration-guards");exports.HOST_MAX_RESTARTS=3;const CRASH_WINDOW_MS=6e4,HOST_STOP_GRACE_MS=6e3;exports.BUILD_HOST_IDLE_ENV="LENSMCP_BUILD_HOST_IDLE_SEC",exports.DEFAULT_BUILD_HOST_IDLE_SEC=300;function buildHostIdleMs(s=process.env){const o=s[exports.BUILD_HOST_IDLE_ENV]?.trim(),l=o?Number(o):NaN;return!Number.isFinite(l)||l<0?exports.DEFAULT_BUILD_HOST_IDLE_SEC*1e3:l>0?l*1e3:void 0}y(buildHostIdleMs,"buildHostIdleMs"),r(buildHostIdleMs,"buildHostIdleMs");function buildHostScript(s=path.join(__dirname,"../../..")){const o=path.join(s,"build-host.js");if(fs.existsSync(o))return{path:o,execArgv:[]};const l=path.join(s,"build-host.ts");if(fs.existsSync(l))try{return{path:l,execArgv:["--require",require.resolve("@swc-node/register")]}}catch{return}}y(buildHostScript,"buildHostScript"),r(buildHostScript,"buildHostScript");function workspaceBuildHostScript(s){try{return{path:(0,node_module_1.createRequire)(path.join(s,"package.json")).resolve("@lensmcp/cluster/build-host.js"),execArgv:[]}}catch{return}}y(workspaceBuildHostScript,"workspaceBuildHostScript"),r(workspaceBuildHostScript,"workspaceBuildHostScript");function createSharedBuildRelay(s,o={}){const l=o.script===void 0?buildHostScript():o.script??void 0,f=o.scriptFor??workspaceBuildHostScript,_=new Map,S=new WeakMap,c=(0,registration_guards_1.canonicalRoot)(s.root),w=r(e=>{const a=(0,registration_guards_1.canonicalRoot)(e.root);let d=_.get(a);return d||(d=a===c?createWorkspaceRelay(s,{root:s.root,...s.wsKey?{wsKey:s.wsKey}:{},...s.sockNs?{sockNs:s.sockNs}:{},eventFile:s.eventFile},l,o):createWorkspaceRelay(s,{root:e.root,...e.wsKey?{wsKey:e.wsKey}:{},...e.sockNs?{sockNs:e.sockNs}:{},eventFile:path.join(e.root,".lensmcp","events.jsonl")},f(e.root),o),_.set(a,d)),d},"relayFor");return{offeredTo:r(e=>(0,protocol_1.sharedCompilerEnabled)(o.gatewayEnv??process.env,s.sharedCompiler)&&w(e).offered(),"offeredTo"),attach(e,a,d){const E=w(e);S.set(d,E),E.attach(`${e.project}#g${a}`,d)},detach:r(e=>S.get(e)?.detach(e),"detach"),hostPid:r((e=s.root)=>_.get((0,registration_guards_1.canonicalRoot)(e))?.hostPid(),"hostPid"),release(e){const a=(0,registration_guards_1.canonicalRoot)(e),d=_.get(a);return _.delete(a),d?.stop()??Promise.resolve()},async stop(){const e=[..._.values()];_.clear(),await Promise.all(e.map(a=>a.stop()))}}}y(createSharedBuildRelay,"createSharedBuildRelay"),r(createSharedBuildRelay,"createSharedBuildRelay");function createWorkspaceRelay(s,o,l,f){const _=f.spawnProcess??node_child_process_1.spawn,S=f.log??(t=>console.log(t)),c=new Map,w=new WeakMap;let e,a,d;const E="idleMs"in f?f.idleMs:buildHostIdleMs(f.gatewayEnv??process.env);let b=[],h,g=!1;const I=(0,registration_guards_1.canonicalRoot)(o.root)===(0,registration_guards_1.canonicalRoot)(s.root)?"this workspace":`'${o.wsKey??path.basename(o.root)}'`,T=r((t,i)=>{if(!t.connected)return;const n={channel:protocol_1.SHARED_BUILD_CHANNEL,event:i};try{t.send(n,void 0,void 0,()=>{})}catch{}},"toRunner"),N=r(t=>{if(e?.connected)try{e.send(t,void 0,void 0,()=>{})}catch{}},"toHost"),D=r(()=>{if(f.reapStale===!1)return;let t;try{t=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return}for(const i of t.split(`
2
+ `)){const n=i.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!n||!(0,protocol_1.isBuildHostCommandOf)(n[3],o.root))continue;const p=Number(n[1]),u=Number(n[2]);if(!(p===e?.pid||p===a?.pid||p===process.pid)){try{process.kill(-u,"SIGKILL")}catch{try{process.kill(p,"SIGKILL")}catch{}}S(`[gateway] reaped a stale build host (pid ${p}) of ${I}`)}}},"reapStaleHosts"),x=r(()=>{if(e||g||h||!l)return e;D();const t=o.wsKey||path.basename(o.root),i={...(0,pod_env_1.podBaseEnv)(process.env,s.root,o.root),NX_DAEMON:"false",NODE_ENV:"development",NX_WORKSPACE_ROOT:o.root,...o.wsKey?{LENSMCP_WS_KEY:o.wsKey}:{},...o.sockNs?{LENSMCP_SOCK_NS:o.sockNs}:{},LENSMCP_EVENT_FILE:o.eventFile,LENSMCP_GATEWAY_PID:String(process.pid)},n=_((0,process_name_1.namedExecPath)((0,process_name_1.processName)(t,protocol_1.BUILD_HOST_ROLE)),[...l.execArgv,l.path,...(0,protocol_1.buildHostArgs)(o.root)],{cwd:o.root,detached:!0,env:i,stdio:["ignore","pipe","pipe","ipc"]});e=n,S(`[gateway] build host starting (one compilation for the services of ${I} \u2014 LENSMCP_SHARED_COMPILER)`);const p=r(u=>m=>{if(!s.stopped())for(const L of m.toString().split(`
3
+ `))L.trim()&&u.write(`[build] ${L}
4
+ `)},"relayOut");return n.stdout?.on("data",p(process.stdout)),n.stderr?.on("data",p(process.stderr)),n.on("message",u=>{if(u?.type==="member"){const m=c.get(u.member);m&&T(m.proc,u.event)}}),n.on("exit",(u,m)=>{if(e===n&&(e=void 0),a===n){if(a=void 0,g||s.stopped()||c.size===0)return;if(x())for(const[v,{join:H}]of c)N({type:"join",member:v,join:H});return}if(g||s.stopped())return;const L=Date.now();if(b=[...b.filter(v=>L-v<CRASH_WINDOW_MS),L],b.length>=exports.HOST_MAX_RESTARTS){h=`the build host exited ${b.length} times in a minute (last: ${u??m})`,S(`[gateway] ${h} \u2014 every service compiles on its own from now on`);for(const v of c.values())T(v.proc,{type:"refused",reason:h});c.clear();return}if(c.size!==0&&(S(`[gateway] build host exited (${u??m}) \u2014 restarting it for ${c.size} service tree(s)`),x()))for(const[v,{join:H}]of c)N({type:"join",member:v,join:H})}),n},"start"),M=r(t=>t.exitCode!==null||t.signalCode!==null?Promise.resolve():new Promise(i=>{const n=r(()=>{clearTimeout(p),i()},"done");t.once("exit",n);try{process.kill(-t.pid,"SIGTERM")}catch{try{t.kill("SIGTERM")}catch{}}const p=setTimeout(()=>{try{process.kill(-t.pid,"SIGKILL")}catch{try{t.kill("SIGKILL")}catch{}}i()},HOST_STOP_GRACE_MS);p.unref?.()}),"terminate"),R=r(()=>{d&&clearTimeout(d),d=void 0},"cancelIdle"),P=r(()=>{R(),!(E===void 0||g||!e)&&(d=setTimeout(()=>{d=void 0;const t=e;c.size>0||g||!t||(e=void 0,a=t,S(`[gateway] build host of ${I} stopped \u2014 no service of it has run for ${Math.round(E/1e3)}s (the next one starts it again)`),M(t))},E),d.unref?.())},"idleOut"),O=r(t=>{const i=w.get(t);!i||c.get(i)?.proc!==t||(c.delete(i),N({type:"leave",member:i}),c.size===0&&P())},"detach");return{offered:r(()=>!!l&&!h,"offered"),attach:r((t,i)=>{w.set(i,t),i.on("message",n=>{if((0,protocol_1.isRunnerJoinMessage)(n)){if(R(),a&&!h){c.set(t,{proc:i,join:n.join});return}if(h||!x()){T(i,{type:"refused",reason:h??"the build host could not be started"});return}c.set(t,{proc:i,join:n.join}),N({type:"join",member:t,join:n.join})}}),i.once("exit",()=>O(i))},"attach"),detach:O,hostPid:r(()=>e?.pid,"hostPid"),stop(){g=!0,R(),c.clear();const t=[e,a].filter(i=>!!i);return e=void 0,Promise.all(t.map(M)).then(()=>{})}}}y(createWorkspaceRelay,"createWorkspaceRelay"),r(createWorkspaceRelay,"createWorkspaceRelay");
@@ -15,6 +15,7 @@ import type { GatewayErrorReason } from '../gateway-errors';
15
15
  import type { RouteRegistry } from './route-registry';
16
16
  import type { PodFaults } from './pod-output';
17
17
  import type { ProjectGraphStore, ProjectGraphStores } from './project-graph-store';
18
+ import type { HostedDashboard } from './hosted-dashboard';
18
19
  export type { Attrs };
19
20
  export interface GatewayContext {
20
21
  root: string;
@@ -865,6 +866,9 @@ export interface GatewayRuntime {
865
866
  * tab is left pinned to a dead optimize-dep generation that no retry can repair. Mutable; self-cleaning
866
867
  * (each socket drops itself on close/error). */
867
868
  hmrClients?: Map<string, Set<HmrClientSocket>>;
869
+ /** Registered workspaces' dashboards, by their route's `target` (hosted-dashboard.ts): the pipeline starts a parked
870
+ * one before it forwards to it, and holds it awake while the response is open. Installed by lens-children. */
871
+ hostedDashboards?: Map<string, HostedDashboard>;
868
872
  /** Teardown flag — a GETTER, never a captured boolean, so every layer reads
869
873
  * the LIVE value (an in-flight cold start / auto-heal must not act after stop). */
870
874
  stopped(): boolean;
@@ -20,7 +20,8 @@ interface ServeResult {
20
20
  * Owns the full devserver lifecycle:
21
21
  * 1. Starts webpack in watch mode — or, under a gateway running the shared compiler, joins the workspace's ONE
22
22
  * compilation (shared-build/attach.ts, issues/open/pod-memory-05) and is told about each build of its bundle
23
- * 2. On first successful build, forks main.devserver.js with proper env vars
23
+ * 2. On first successful build, starts main.devserver.js with proper env vars — in this process under the gateway's
24
+ * serve runner (devserver-host.ts), forked otherwise
24
25
  * 3. On subsequent builds, POSTs /webpack/reload to devserver — attached: only when ITS bundle changed
25
26
  * 4. On shutdown, cleans up devserver and webpack watcher
26
27
  *
@@ -1 +1 @@
1
- "use strict";var le=Object.defineProperty;var i=(e,o)=>le(e,"name",{value:o,configurable:!0});var ie=Object.defineProperty,s=i((e,o)=>ie(e,"name",{value:o,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),child_process_env_1=require("../../child-process-env"),dev_devtool_1=require("../../dev-devtool"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),worker_supervisor_1=require("../../worker-supervisor"),attach_1=require("../../shared-build/attach"),stop_on_signal_1=require("./stop-on-signal"),compile_retry_1=require("../../compile-retry");function definedEnv(e){const o={};for(const[w,n]of Object.entries(e))typeof n=="string"&&(o[w]=n);return o}i(definedEnv,"definedEnv"),s(definedEnv,"definedEnv");const frozen=s(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,o){process.env.NODE_ENV="development";const w=o.projectsConfigurations.projects[o.projectName],n=o.root,E=path.join(n,w.root),G=e.outputPath?path.join(n,e.outputPath):path.join(n,"dist",w.root),q=e.configEnv||"development",k=path.join(n,"config",`config.${q}.yaml`),C=3050;let d=C,a=o.projectName,S={};fs.existsSync(k)?(S=yaml.load(fs.readFileSync(k,"utf8")),d=Number(S.port)||d,a=S.serviceName||a):console.warn(`[serve] ${o.projectName}: no config file at ${k} \u2014 public devserver port defaults to ${C}. If another service also lacks its config, both bind ${C} \u2192 EADDRINUSE. Add config/config.${q}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(a=e.serviceName);const O=e.servePrefix??"",f=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),K=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),p=e.logFile?path.resolve(n,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(p){fs.mkdirSync(path.dirname(p),{recursive:!0}),fs.writeFileSync(p,"",{mode:384});try{fs.chmodSync(p,384)}catch{}}let D,T;e.gateway?.middleware&&(D=path.resolve(E,e.gateway.middleware),T=JSON.stringify(S));const Z=e.entryFile||"./src/deployments/service/main.ts",Q=e.tsConfigFile||"./tsconfig.app.json",ee=path.resolve(E,Z),B=path.resolve(E,Q),oe=(e.workers||[]).map(t=>({entryName:t.name,entryPath:path.resolve(E,t.entryPath)})),W={appName:o.projectName,appRoot:E,outputDir:G,main:ee,tsConfig:B,assets:e.assets||[],additionalEntryPoints:oe,port:d,serviceName:a,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:n,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let c=null,x=!1,A=!1,v=!1;const V=path.join(__dirname,"../../main.devserver.js"),Y=path.join(__dirname,"../../main.devserver.ts"),H=!fs.existsSync(V)&&fs.existsSync(Y),M=H?Y:V,te=path.join(G,"main.js");function F(){c&&!c.killed&&(c.kill("SIGTERM"),c=null)}i(F,"H"),s(F,"cleanup");function P(){v=!0,F()}i(P,"F"),s(P,"stop");const re=(0,stop_on_signal_1.stopOnSignal)({stop:P,close:s(()=>y(),"close")});process.on("exit",P);function _(){if(v)return;if(!fs.existsSync(M)){console.error(`[serve] devserver script not found at ${M}`);return}const t=["--enable-source-maps"];H&&t.unshift("--require","@swc-node/register");const u=(0,node_child_process_1.fork)(M,{cwd:n,env:(0,child_process_env_1.childProcessEnv)({...process.env,PORT:String(d),SERVICE_NAME:a,SERVE_PREFIX:O,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||o.projectName||a,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(n,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:te,...D&&{GATEWAY_MIDDLEWARE:D},...T&&{GATEWAY_CONFIG:T},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...f&&{LENSMCP_DEBUG_PORT:String(f)},...p&&{LENSMCP_LOG_FILE:p},...e.workers?.length&&{WORKERS:(0,worker_supervisor_1.serializeWorkerSpecs)(e.workers)},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:A?"1":"0"}),stdio:["inherit","inherit","inherit","ipc"],execArgv:t,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(n),a,"dev"))});c=u,u.on("exit",(l,g)=>{c===u&&(c=null),!(g||l===0||v)&&(console.error(`[serve] devserver exited unexpectedly (code=${l}, signal=${g}) \u2014 respawning in 1.5s`),setTimeout(()=>{!v&&!c&&x&&_()},1500).unref?.())}),console.log(`[serve] Devserver started on port ${d} (service: ${a}${O?`, prefix: /${O}`:""})`),f?(console.warn(`[serve] DEBUGGER ENABLED for ${a}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),K?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${f}+slot (workers ${f}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${f}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${a}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${a}\` opens pod inspectors without a restart`),p&&console.log(`[serve] Logs teed to ${p} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}i(_,"w"),s(_,"startDevServer");function se(){if(!c||c.killed){console.log("[serve] Devserver not running, restarting..."),_();return}fetch(`http://localhost:${d}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(t=>{console.warn("[serve] Failed to notify devserver, restarting...",t.message),F(),_()})}i(se,"ce"),s(se,"_notifyDevServerReload");let b=null;const I=[];function h(t){if(b){const u=b;b=null,u(t)}else I.push(t)}i(h,"v"),s(h,"pushResult");function J(){return I.length>0?Promise.resolve(I.shift()):new Promise(t=>{b=t})}i(J,"re"),s(J,"waitForNext");let y=s(()=>{},"closeBuild");function U(){return!x||!c?(x=!0,_(),!0):!1}i(U,"J"),s(U,"builtOk");function j(){if(v)return;const{createDevWebpackConfig:t}=require("../../create-webpack-dev"),u=require("webpack"),l=t(W),{watch:g,...$}=l,r=u($);A=(0,dev_devtool_1.bundleCarriesSourceMaps)(r.options.devtool);let m;const N=(0,compile_retry_1.createFailureRetry)(()=>{v||!m||(console.log(`[serve] ${o.projectName}: compiling again after the failure`),m.invalidate())}),z=s((L,R)=>{if(L){if(console.error("[serve] Webpack error:",L),m&&!v){const{delayMs:ae,retry:ce}=N.failed();console.log(`[serve] ${o.projectName}: compiling again in ${Math.round(ae/1e3)} s (retry ${ce}) \u2014 webpack does not after a failure like this`)}h({success:!1});return}N.succeeded();const ne=R.hasErrors();if(console.info(R.toString({colors:!0,chunks:!1,assets:!0})),ne){h({success:!1});return}U(),h({success:!0,baseUrl:`http://localhost:${d}`})},"onBuild");if(frozen()){console.log(`[serve] ${o.projectName}: frozen \u2014 compiling once, no file watcher`),r.run((L,R)=>{z(L,R),r.close(()=>{})});return}m=r.watch(l.watchOptions||{},z),y=s(()=>{N.dispose(),m.close(()=>{})},"closeBuild")}i(j,"M"),s(j,"compileOnItsOwn");function X(t){const u=t.accepted.projects.filter(r=>r!==o.projectName);console.log(`[serve] ${o.projectName}: compiled by the workspace's shared compilation ${t.accepted.group.slice(0,8)}${u.length?` with ${u.join(", ")}`:""} \u2014 no webpack in this process`);const l=new devserver_notifier_1.DevserverNotifier(d,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(a)),g=(0,attach_1.createAttachedTypecheck)({appName:o.projectName,tsConfig:B,workspaceRoot:n,memoryLimit:e.memoryLimit||8192});let $;y=s(()=>{t.close(),g.dispose()},"closeBuild"),t.listen(r=>{if(!v)switch(r.type){case"watch-run":l.watchRun(r.paths);return;case"watch-closed":l.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${r.message}`),l.failed(`the shared compiler failed: ${r.message}`),h({success:!1});return;case"refused":console.log(`[serve] ${o.projectName}: compiling on its own from now on \u2014 ${r.reason}`),y(),j();return;case"built":{if(console.info(r.text),A=r.sourceMaps,g.built(r),!r.ok){l.failed(),h({success:!1});return}const m=r.hash!==$;$=r.hash;const N=U();l.built(m&&!N),h({success:!0,baseUrl:`http://localhost:${d}`});return}default:return}})}if(i(X,"te"),s(X,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const t=await(0,attach_1.joinSharedBuild)({project:o.projectName,target:o.targetName??"serve-hmr",...o.configurationName?{configuration:o.configurationName}:{},options:W,targetOptions:{...e},env:definedEnv(process.env)});t.kind==="attached"?X(t.session):(console.log(`[serve] ${o.projectName}: compiling on its own \u2014 ${t.reason}`),j())}else j();try{for(;;)yield await J()}finally{P(),y(),re()}}i(serveExecutor,"serveExecutor"),s(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
1
+ "use strict";var de=Object.defineProperty;var n=(e,o)=>de(e,"name",{value:o,configurable:!0});var ae=Object.defineProperty,s=n((e,o)=>ae(e,"name",{value:o,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),node_child_process_1=require("node:child_process"),yaml=tslib_1.__importStar(require("js-yaml")),child_process_env_1=require("../../child-process-env"),dev_devtool_1=require("../../dev-devtool"),devserver_host_1=require("../../devserver-host"),devserver_notifier_1=require("../../devserver-notifier"),process_name_1=require("../../process-name"),worker_supervisor_1=require("../../worker-supervisor"),attach_1=require("../../shared-build/attach"),stop_on_signal_1=require("./stop-on-signal"),compile_retry_1=require("../../compile-retry");function definedEnv(e){const o={};for(const[w,i]of Object.entries(e))typeof i=="string"&&(o[w]=i);return o}n(definedEnv,"definedEnv"),s(definedEnv,"definedEnv");const frozen=s(()=>process.env.LENSMCP_FREEZE==="1","frozen");async function*serveExecutor(e,o){process.env.NODE_ENV="development";const w=o.projectsConfigurations.projects[o.projectName],i=o.root,_=path.join(i,w.root),W=e.outputPath?path.join(i,e.outputPath):path.join(i,"dist",w.root),V=e.configEnv||"development",R=path.join(i,"config",`config.${V}.yaml`),D=3050;let d=D,c=o.projectName,b={};fs.existsSync(R)?(b=yaml.load(fs.readFileSync(R,"utf8")),d=Number(b.port)||d,c=b.serviceName||c):console.warn(`[serve] ${o.projectName}: no config file at ${R} \u2014 public devserver port defaults to ${D}. If another service also lacks its config, both bind ${D} \u2192 EADDRINUSE. Add config/config.${V}.yaml with a distinct \`port:\` (matching this project's cluster.port).`),e.serviceName&&(c=e.serviceName);const C=e.servePrefix??"",E=e.debugPort??(Number(process.env.LENSMCP_DEBUG_PORT)||void 0),Q=/^(1|true|yes|on)$/i.test((process.env.LENSMCP_DEBUG_FIXED_PORTS??"").trim()),v=e.logFile?path.resolve(i,e.logFile):process.env.LENSMCP_LOG_FILE||void 0;if(v){fs.mkdirSync(path.dirname(v),{recursive:!0}),fs.writeFileSync(v,"",{mode:384});try{fs.chmodSync(v,384)}catch{}}let T,x;e.gateway?.middleware&&(T=path.resolve(_,e.gateway.middleware),x=JSON.stringify(b));const ee=e.entryFile||"./src/deployments/service/main.ts",oe=e.tsConfigFile||"./tsconfig.app.json",re=path.resolve(_,ee),Y=path.resolve(_,oe),te=(e.workers||[]).map(r=>({entryName:r.name,entryPath:path.resolve(_,r.entryPath)})),H={appName:o.projectName,appRoot:_,outputDir:W,main:re,tsConfig:Y,assets:e.assets||[],additionalEntryPoints:te,port:d,serviceName:c,memoryLimit:e.memoryLimit||8192,buildLibsFromSource:e.buildLibsFromSource!==!1,workspaceRoot:i,orgScopes:e.orgScopes||[],bundlePackages:e.bundlePackages||[],nodeExternalsConfig:e.nodeExternalsConfig,webpackConfigPath:e.webpackConfigPath,httpsReload:e.gateway?.https===!0,...e.devtool!==void 0&&{devtool:e.devtool}};let l=null,A=!1,I=!1,M=!1,h=!1;const J=path.join(__dirname,"../../main.devserver.js"),X=path.join(__dirname,"../../main.devserver.ts"),F=!fs.existsSync(J)&&fs.existsSync(X),P=F?X:J,se=path.join(W,"main.js");function q(){l&&!l.killed&&(l.kill("SIGTERM"),l=null)}n(q,"J"),s(q,"cleanup");function $(){h=!0,q()}n($,"M"),s($,"stop");const ne=(0,stop_on_signal_1.stopOnSignal)({stop:$,close:s(()=>S(),"close")});process.on("exit",$);function y(){if(h||A)return;if(!fs.existsSync(P)){console.error(`[serve] devserver script not found at ${P}`);return}const r=(0,child_process_env_1.childProcessEnv)({...process.env,PORT:String(d),SERVICE_NAME:c,SERVE_PREFIX:C,LENSMCP_PROJECT:process.env.LENSMCP_PROJECT||o.projectName||c,LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE||path.join(i,".lensmcp","events.jsonl"),CHILD_COUNT:String(e.childCount||process.env.CHILD_COUNT||1),BUNDLE_PATH:se,...T&&{GATEWAY_MIDDLEWARE:T},...x&&{GATEWAY_CONFIG:x},...e.gateway?.routes?.length&&{GATEWAY_ROUTES:JSON.stringify(e.gateway.routes)},...e.gateway?.https&&{GATEWAY_HTTPS:"1"},...e.gateway?.extraPorts?.length&&{GATEWAY_EXTRA_PORTS:JSON.stringify(e.gateway.extraPorts)},...e.internalPort&&{INTERNAL_PORT:String(e.internalPort)},...E&&{LENSMCP_DEBUG_PORT:String(E)},...v&&{LENSMCP_LOG_FILE:v},...e.workers?.length&&{WORKERS:(0,worker_supervisor_1.serializeWorkerSpecs)(e.workers)},[dev_devtool_1.DEV_SOURCE_MAPS_ENV]:M?"1":"0"});if((0,devserver_host_1.devserverInProcessOffered)()&&(!F||require.extensions[".ts"])){A=!0;for(const[f,u]of Object.entries(r))u!==void 0&&(process.env[f]=u);(0,devserver_host_1.markDevserverInProcess)(),require(P),U("in this process");return}const p=["--enable-source-maps"];F&&p.unshift("--require","@swc-node/register");const a=(0,node_child_process_1.fork)(P,{cwd:i,env:r,stdio:["inherit","inherit","inherit","ipc"],execArgv:p,execPath:(0,process_name_1.namedExecPath)((0,process_name_1.processName)(process.env.LENSMCP_WS_KEY||path.basename(i),c,"dev"))});l=a,a.on("exit",(f,u)=>{l===a&&(l=null),!(u||f===0||h)&&(console.error(`[serve] devserver exited unexpectedly (code=${f}, signal=${u}) \u2014 respawning in 1.5s`),setTimeout(()=>{!h&&!l&&I&&y()},1500).unref?.())}),U()}n(y,"P"),s(y,"startDevServer");function U(r){console.log(`[serve] Devserver started on port ${d} (service: ${c}${C?`, prefix: /${C}`:""}${r?`, ${r}`:""})`),E?(console.warn(`[serve] DEBUGGER ENABLED for ${c}. Node's inspector has NO authentication \u2014 the ws:// UUID is the whole credential, and any account on this host that obtains it runs code as you. Do not enable this on a shared machine.`),Q?console.warn(`[serve] LENSMCP_DEBUG_FIXED_PORTS=1 \u2014 pod inspectors bind PREDICTABLE ports 127.0.0.1:${E}+slot (workers ${E}+20+i), reachable by any account on this host. Attach WebStorm (Attach to Node.js/Chrome) to ${E}. Unset it to bind ephemeral ports instead.`):console.log(`[serve] Debug: pod inspectors bind an EPHEMERAL 127.0.0.1 port. Get the ws:// URLs with \`lensmcp debug ${c}\` (the owner-only control socket); a devserver run in your own terminal also prints them there. Set LENSMCP_DEBUG_FIXED_PORTS=1 if your IDE needs a fixed attach port.`)):console.log(`[serve] Debug on demand: \`lensmcp debug ${c}\` opens pod inspectors without a restart`),v&&console.log(`[serve] Logs teed to ${v} \u2014 tail it in WebStorm via Run/Debug configuration \u2192 Logs tab`)}n(U,"V"),s(U,"announceDevServer");function ie(){if(!l||l.killed){console.log("[serve] Devserver not running, restarting..."),y();return}fetch(`http://localhost:${d}/webpack/reload`,{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(()=>console.log("[serve] Notified devserver to reload")).catch(r=>{console.warn("[serve] Failed to notify devserver, restarting...",r.message),q(),y()})}n(ie,"le"),s(ie,"_notifyDevServerReload");let j=null;const G=[];function m(r){if(j){const p=j;j=null,p(r)}else G.push(r)}n(m,"g"),s(m,"pushResult");function z(){return G.length>0?Promise.resolve(G.shift()):new Promise(r=>{j=r})}n(z,"se"),s(z,"waitForNext");let S=s(()=>{},"closeBuild");function B(){return!I||!l&&!A?(I=!0,y(),!0):!1}n(B,"Y"),s(B,"builtOk");function k(){if(h)return;const{createDevWebpackConfig:r}=require("../../create-webpack-dev"),p=require("webpack"),a=r(H),{watch:f,...u}=a,t=p(u);M=(0,dev_devtool_1.bundleCarriesSourceMaps)(t.options.devtool);let g;const N=(0,compile_retry_1.createFailureRetry)(()=>{h||!g||(console.log(`[serve] ${o.projectName}: compiling again after the failure`),g.invalidate())}),Z=s((L,O)=>{if(L){if(console.error("[serve] Webpack error:",L),g&&!h){const{delayMs:le,retry:ue}=N.failed();console.log(`[serve] ${o.projectName}: compiling again in ${Math.round(le/1e3)} s (retry ${ue}) \u2014 webpack does not after a failure like this`)}m({success:!1});return}N.succeeded();const ce=O.hasErrors();if(console.info(O.toString({colors:!0,chunks:!1,assets:!0})),ce){m({success:!1});return}B(),m({success:!0,baseUrl:`http://localhost:${d}`})},"onBuild");if(frozen()){console.log(`[serve] ${o.projectName}: frozen \u2014 compiling once, no file watcher`),t.run((L,O)=>{Z(L,O),t.close(()=>{})});return}g=t.watch(a.watchOptions||{},Z),S=s(()=>{N.dispose(),g.close(()=>{})},"closeBuild")}n(k,"U"),s(k,"compileOnItsOwn");function K(r){const p=r.accepted.projects.filter(t=>t!==o.projectName);console.log(`[serve] ${o.projectName}: compiled by the workspace's shared compilation ${r.accepted.group.slice(0,8)}${p.length?` with ${p.join(", ")}`:""} \u2014 no webpack in this process`);const a=new devserver_notifier_1.DevserverNotifier(d,e.gateway?.https===!0,(0,devserver_notifier_1.devserverControlSock)(c)),f=(0,attach_1.createAttachedTypecheck)({appName:o.projectName,tsConfig:Y,workspaceRoot:i,memoryLimit:e.memoryLimit||8192});let u;S=s(()=>{r.close(),f.dispose()},"closeBuild"),r.listen(t=>{if(!h)switch(t.type){case"watch-run":a.watchRun(t.paths);return;case"watch-closed":a.degrade("watch closed");return;case"failed":console.error(`[serve] the shared compiler failed: ${t.message}`),a.failed(`the shared compiler failed: ${t.message}`),m({success:!1});return;case"refused":console.log(`[serve] ${o.projectName}: compiling on its own from now on \u2014 ${t.reason}`),S(),k();return;case"built":{if(console.info(t.text),M=t.sourceMaps,f.built(t),!t.ok){a.failed(),m({success:!1});return}const g=t.hash!==u;u=t.hash;const N=B();a.built(g&&!N),m({success:!0,baseUrl:`http://localhost:${d}`});return}default:return}})}if(n(K,"oe"),s(K,"attachToSharedBuild"),(0,attach_1.sharedBuildOffered)()&&!frozen()){const r=await(0,attach_1.joinSharedBuild)({project:o.projectName,target:o.targetName??"serve-hmr",...o.configurationName?{configuration:o.configurationName}:{},options:H,targetOptions:{...e},env:definedEnv(process.env)});r.kind==="attached"?K(r.session):(console.log(`[serve] ${o.projectName}: compiling on its own \u2014 ${r.reason}`),k())}else k();try{for(;;)yield await z()}finally{$(),S(),ne()}}n(serveExecutor,"serveExecutor"),s(serveExecutor,"serveExecutor"),exports.default=serveExecutor,module.exports=serveExecutor,module.exports.default=serveExecutor;
@@ -13,6 +13,8 @@ export interface StopOnSignalOptions {
13
13
  /** Close whatever builds the service — its watcher, or its membership of the shared compilation. */
14
14
  close(): void;
15
15
  proc?: StoppableProcess;
16
+ /** A devserver running in this process ends it (devserver-host.ts) — then this does not. Default {@link devserverOwnsExit}. */
17
+ devserverOwnsExit?: () => boolean;
16
18
  log?: (line: string) => void;
17
19
  }
18
20
  /** Install the handlers; the returned function removes them (the executor's generator returned on its own). */
@@ -1 +1 @@
1
- "use strict";var S=Object.defineProperty;var c=(r,n)=>S(r,"name",{value:n,configurable:!0});var l=Object.defineProperty,o=c((r,n)=>l(r,"name",{value:n,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.STOP_SIGNALS=void 0,exports.stopOnSignal=stopOnSignal;const serve_runner_1=require("../../serve-runner");exports.STOP_SIGNALS=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"];function stopOnSignal(r){const n=r.proc??process,a=r.log??(e=>console.log(e));let s=!1;const i=o(e=>{if(r.stop(),!s){s=!0;try{r.close()}catch{}setImmediate(()=>n.exit((0,serve_runner_1.exitCodeForSignal)(e)))}},"end"),p=exports.STOP_SIGNALS.map(e=>{const t=o(()=>i(e),"handler");return n.on(e,t),{event:e,handler:t}});if(typeof n.send=="function"){const e=o(()=>{s||(a("[serve] the parent process is gone (its IPC channel closed) \u2014 stopping the service"),i("SIGTERM"))},"onDisconnect");n.on("disconnect",e),p.push({event:"disconnect",handler:e})}return()=>{for(const{event:e,handler:t}of p)n.removeListener(e,t)}}c(stopOnSignal,"stopOnSignal"),o(stopOnSignal,"stopOnSignal");
1
+ "use strict";var a=Object.defineProperty;var c=(t,o)=>a(t,"name",{value:o,configurable:!0});var S=Object.defineProperty,r=c((t,o)=>S(t,"name",{value:o,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.STOP_SIGNALS=void 0,exports.stopOnSignal=stopOnSignal;const devserver_host_1=require("../../devserver-host");exports.STOP_SIGNALS=["SIGINT","SIGTERM","SIGHUP","SIGQUIT"];function stopOnSignal(t){const o=t.proc??process,p=t.log??(e=>console.log(e));let s=!1;const i=r(e=>{if(t.stop(),!s){s=!0;try{t.close()}catch{}(t.devserverOwnsExit??devserver_host_1.devserverOwnsExit)()||setImmediate(()=>o.exit((0,devserver_host_1.exitCodeForSignal)(e)))}},"end"),d=exports.STOP_SIGNALS.map(e=>{const n=r(()=>i(e),"handler");return o.on(e,n),{event:e,handler:n}});if(typeof o.send=="function"){const e=r(()=>{s||(p("[serve] the parent process is gone (its IPC channel closed) \u2014 stopping the service"),i("SIGTERM"))},"onDisconnect");o.on("disconnect",e),d.push({event:"disconnect",handler:e})}return()=>{for(const{event:e,handler:n}of d)o.removeListener(e,n)}}c(stopOnSignal,"stopOnSignal"),r(stopOnSignal,"stopOnSignal");
package/main.devserver.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var yt=Object.defineProperty;var u=(d,_)=>yt(d,"name",{value:_,configurable:!0});var gt=Object.defineProperty,a=u((d,_)=>gt(d,"name",{value:_,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.redactInspectorUrls=exports.inspectorPortOf=exports.DEBUG_FIXED_PORTS_ENV=exports.mayDriveThePool=exports.isOwnerOnlyTransport=exports.gatewayManaged=void 0,exports.tell=tell,exports.usesFixedInspectorPorts=usesFixedInspectorPorts,exports.inspectorPortFor=inspectorPortFor;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),node_child_process_1=require("node:child_process"),http=tslib_1.__importStar(require("node:http")),readline=tslib_1.__importStar(require("node:readline")),inspector=tslib_1.__importStar(require("node:inspector")),util=tslib_1.__importStar(require("node:util")),v8=tslib_1.__importStar(require("node:v8")),child_process_env_1=require("./child-process-env"),exit_reason_1=require("./exit-reason"),worker_supervisor_1=require("./worker-supervisor"),hot_swap_registries_1=require("./hot-swap-registries"),process_name_1=require("./process-name"),inspector_secret_1=require("./inspector-secret");Object.defineProperty(exports,"inspectorPortOf",{enumerable:!0,get:a(function(){return inspector_secret_1.inspectorPortOf},"get")}),Object.defineProperty(exports,"redactInspectorUrls",{enumerable:!0,get:a(function(){return inspector_secret_1.redactInspectorUrls},"get")});const devserver_transport_1=require("./devserver-transport");Object.defineProperty(exports,"gatewayManaged",{enumerable:!0,get:a(function(){return devserver_transport_1.gatewayManaged},"get")}),Object.defineProperty(exports,"isOwnerOnlyTransport",{enumerable:!0,get:a(function(){return devserver_transport_1.isOwnerOnlyTransport},"get")}),Object.defineProperty(exports,"mayDriveThePool",{enumerable:!0,get:a(function(){return devserver_transport_1.mayDriveThePool},"get")});const schema_first_instance_1=require("./schema-first-instance"),redis_disconnect_fix_1=require("./redis-disconnect-fix"),error_stack_release_1=require("./error-stack-release"),swap_recycle_1=require("./swap-recycle"),swap_mode_1=require("./swap-mode"),handoff_retire_1=require("./handoff-retire"),gc_notice_1=require("./gc-notice"),pod_sock_1=require("./pod-sock"),notice_generation_1=require("./notice-generation"),service_build_events_1=require("./service-build-events"),stdio_guard_1=require("./stdio-guard"),bundle_parent_1=require("./bundle-parent"),checkout_cache_1=require("./checkout-cache"),dev_devtool_1=require("./dev-devtool"),pod_memory_1=require("./pod-memory"),pod_memory_events_1=require("./pod-memory-events"),heap_snapshot_1=require("./heap-snapshot"),httpProxy=require("http-proxy"),{prettyFactory}=require("pino-pretty"),BUNDLE_PATH=process.env.BUNDLE_PATH?.trim()||"./main.js",SERVICE_NAME=process.env.SERVICE_NAME||"",SERVICE_PREFIX=process.env.SERVE_PREFIX?`/${process.env.SERVE_PREFIX}`:"",GATEWAY_MIDDLEWARE_PATH=process.env.GATEWAY_MIDDLEWARE||"",GATEWAY_CONFIG=(()=>{const d=process.env.GATEWAY_CONFIG;if(!d)return{};try{return JSON.parse(d)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const d=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof d=="function"?d:typeof d.default=="function"?d.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(d){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,d)}function applyGatewayMiddleware(d){if(gatewayMiddleware)try{gatewayMiddleware(d,GATEWAY_CONFIG)}catch(_){console.error("[gateway] Middleware error:",_)}}u(applyGatewayMiddleware,"applyGatewayMiddleware"),a(applyGatewayMiddleware,"applyGatewayMiddleware");const GATEWAY_ROUTES=(()=>{try{return JSON.parse(process.env.GATEWAY_ROUTES||"[]")}catch{return[]}})(),GATEWAY_HTTPS=process.env.GATEWAY_HTTPS==="1";function hostMatches(d,_){if(d.startsWith("*.")){const f=d.slice(1);return _.endsWith(f)||_===d.slice(2)}return _===d}u(hostMatches,"hostMatches"),a(hostMatches,"hostMatches");function matchGatewayRoute(d){if(GATEWAY_ROUTES.length===0)return;const _=(d.headers.host||"").split(":")[0],f=d.url||"/";for(const m of GATEWAY_ROUTES)if(!(m.host&&!hostMatches(m.host,_))&&!(m.prefix&&!f.startsWith(m.prefix)))return m}u(matchGatewayRoute,"matchGatewayRoute"),a(matchGatewayRoute,"matchGatewayRoute");const{specs:WORKER_SPECS,problems:WORKER_PROBLEMS}=(0,worker_supervisor_1.parseWorkerSpecs)(process.env.WORKERS),WORKER_NAMES=WORKER_SPECS.map(d=>d.name),LENSMCP_WS_KEY=process.env.LENSMCP_WS_KEY||"",SOCK_NS=process.env.LENSMCP_SOCK_NS||LENSMCP_WS_KEY,SOCK_DIR=SOCK_NS?path.join(os.tmpdir(),SOCK_NS,`${SERVICE_NAME||"lensmcp-cluster"}-devserver`):path.join(os.tmpdir(),`${SERVICE_NAME||"lensmcp-cluster"}-devserver`),SOCKET_ONLY=process.env.LENSMCP_SOCKET_ONLY==="1",POD_GENERATION=(()=>{const d=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(d)&&d>1?Math.floor(d):1})();function childSockPath(d){return path.join(SOCK_DIR,(0,pod_sock_1.podSockName)(d,POD_GENERATION))}u(childSockPath,"childSockPath"),a(childSockPath,"childSockPath");function sockOwnerPath(d){return`${d}.owner`}u(sockOwnerPath,"sockOwnerPath"),a(sockOwnerPath,"sockOwnerPath");function sockRetiringPath(d){return`${d}.retiring`}u(sockRetiringPath,"sockRetiringPath"),a(sockRetiringPath,"sockRetiringPath");function tell(d,_){if(!d.connected||typeof d.send!="function")return!1;try{return d.send(_,()=>{})}catch{return!1}}u(tell,"tell"),a(tell,"tell");function refusePoolCommand(d,_){console.warn(`[devserver] refused ${d.method} ${d.url} on the public port \u2014 under a gateway the pool is driven on the control socket only.`),_.writeHead(404,{"content-type":"application/json"}),_.end(JSON.stringify({error:"not found"}))}u(refusePoolCommand,"refusePoolCommand"),a(refusePoolCommand,"refusePoolCommand"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(d=process.env){const _=(d[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return _==="1"||_==="true"||_==="yes"||_==="on"}u(usesFixedInspectorPorts,"usesFixedInspectorPorts"),a(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(d,_,f=process.env){if(!usesFixedInspectorPorts(f))return 0;const m=Math.trunc(d)+Math.trunc(_);return Number.isInteger(m)&&m>1024&&m<=65535?m:0}u(inspectorPortFor,"inspectorPortFor"),a(inspectorPortFor,"inspectorPortFor");function ensureOwnerOnlyDir(d){try{fs.mkdirSync(d,{recursive:!0,mode:448})}catch{}try{const _=fs.lstatSync(d);_.isDirectory()&&(_.mode&63)!==0&&fs.chmodSync(d,448)}catch{}}u(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),a(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(d,_=process.pid){ensureOwnerOnlyDir(path.dirname(d));try{const f=sockOwnerPath(d);try{fs.unlinkSync(f)}catch{}fs.writeFileSync(f,String(_),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(d)}catch{}try{fs.unlinkSync(sockRetiringPath(d))}catch{}}u(claimSock,"claimSock"),a(claimSock,"claimSock");function sockOwnedByAnother(d,_=process.pid){try{const f=fs.readFileSync(sockOwnerPath(d),"utf8").trim();return!!f&&f!==String(_)}catch{return!1}}u(sockOwnedByAnother,"sockOwnedByAnother"),a(sockOwnedByAnother,"sockOwnedByAnother");function cleanupSock(d,_=process.pid){if(d){try{const f=fs.readFileSync(sockOwnerPath(d),"utf8").trim();if(f&&f!==String(_))return}catch{}try{fs.unlinkSync(d)}catch{}try{fs.unlinkSync(sockOwnerPath(d))}catch{}try{fs.unlinkSync(sockRetiringPath(d))}catch{}(0,gc_notice_1.clearGcNotice)(d)}}u(cleanupSock,"cleanupSock"),a(cleanupSock,"cleanupSock");function postRetiringNotice(d,_){if(!(!d||!_)){try{const f=fs.readFileSync(sockOwnerPath(d),"utf8").trim();if(f&&f!==String(_))return}catch{}try{fs.writeFileSync(sockRetiringPath(d),String(_),{flag:"w",mode:384})}catch{}}}u(postRetiringNotice,"postRetiringNotice"),a(postRetiringNotice,"postRetiringNotice");function serviceKeyFingerprint(){const d=process.env.LENSMCP_SERVICE_KEY;if(!d)return;const{createHash:_}=require("node:crypto");return _("sha256").update(d).digest("hex").slice(0,8)}u(serviceKeyFingerprint,"serviceKeyFingerprint"),a(serviceKeyFingerprint,"serviceKeyFingerprint");function rootModuleOf(d){let _;for(const f of d.getModules?.()?.values()??[])typeof f?.metatype!="function"||typeof f.distance!="number"||!Number.isFinite(f.distance)||(!_||f.distance<_.distance)&&(_=f);return _?.metatype}u(rootModuleOf,"rootModuleOf"),a(rootModuleOf,"rootModuleOf");let lastImportedModule;function takeImportedModule(){const d=lastImportedModule;return lastImportedModule=void 0,d}u(takeImportedModule,"takeImportedModule"),a(takeImportedModule,"takeImportedModule");async function importFresh(spec){process.env.DEVSERVER_MODE="1";const requested=path.isAbsolute(spec)?spec:path.join(__dirname,spec);let resolved=requested;try{resolved=fs.realpathSync(requested)}catch{}eval(`(() => {
1
+ "use strict";var yt=Object.defineProperty;var u=(d,_)=>yt(d,"name",{value:_,configurable:!0});var gt=Object.defineProperty,a=u((d,_)=>gt(d,"name",{value:_,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.redactInspectorUrls=exports.inspectorPortOf=exports.DEBUG_FIXED_PORTS_ENV=exports.mayDriveThePool=exports.isOwnerOnlyTransport=exports.gatewayManaged=void 0,exports.tell=tell,exports.usesFixedInspectorPorts=usesFixedInspectorPorts,exports.inspectorPortFor=inspectorPortFor;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),node_child_process_1=require("node:child_process"),http=tslib_1.__importStar(require("node:http")),readline=tslib_1.__importStar(require("node:readline")),inspector=tslib_1.__importStar(require("node:inspector")),util=tslib_1.__importStar(require("node:util")),v8=tslib_1.__importStar(require("node:v8")),child_process_env_1=require("./child-process-env"),exit_reason_1=require("./exit-reason"),worker_supervisor_1=require("./worker-supervisor"),hot_swap_registries_1=require("./hot-swap-registries"),process_name_1=require("./process-name"),inspector_secret_1=require("./inspector-secret");Object.defineProperty(exports,"inspectorPortOf",{enumerable:!0,get:a(function(){return inspector_secret_1.inspectorPortOf},"get")}),Object.defineProperty(exports,"redactInspectorUrls",{enumerable:!0,get:a(function(){return inspector_secret_1.redactInspectorUrls},"get")});const devserver_transport_1=require("./devserver-transport");Object.defineProperty(exports,"gatewayManaged",{enumerable:!0,get:a(function(){return devserver_transport_1.gatewayManaged},"get")}),Object.defineProperty(exports,"isOwnerOnlyTransport",{enumerable:!0,get:a(function(){return devserver_transport_1.isOwnerOnlyTransport},"get")}),Object.defineProperty(exports,"mayDriveThePool",{enumerable:!0,get:a(function(){return devserver_transport_1.mayDriveThePool},"get")});const schema_first_instance_1=require("./schema-first-instance"),redis_disconnect_fix_1=require("./redis-disconnect-fix"),error_stack_release_1=require("./error-stack-release"),swap_recycle_1=require("./swap-recycle"),swap_mode_1=require("./swap-mode"),handoff_retire_1=require("./handoff-retire"),gc_notice_1=require("./gc-notice"),pod_sock_1=require("./pod-sock"),notice_generation_1=require("./notice-generation"),service_build_events_1=require("./service-build-events"),stdio_guard_1=require("./stdio-guard"),bundle_parent_1=require("./bundle-parent"),checkout_cache_1=require("./checkout-cache"),dev_devtool_1=require("./dev-devtool"),worker_retire_1=require("./worker-retire"),devserver_host_1=require("./devserver-host"),pod_memory_1=require("./pod-memory"),pod_memory_events_1=require("./pod-memory-events"),heap_snapshot_1=require("./heap-snapshot"),httpProxy=require("http-proxy"),{prettyFactory}=require("pino-pretty"),BUNDLE_PATH=process.env.BUNDLE_PATH?.trim()||"./main.js",SERVICE_NAME=process.env.SERVICE_NAME||"",SERVICE_PREFIX=process.env.SERVE_PREFIX?`/${process.env.SERVE_PREFIX}`:"",GATEWAY_MIDDLEWARE_PATH=process.env.GATEWAY_MIDDLEWARE||"",GATEWAY_CONFIG=(()=>{const d=process.env.GATEWAY_CONFIG;if(!d)return{};try{return JSON.parse(d)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const d=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof d=="function"?d:typeof d.default=="function"?d.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(d){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,d)}function applyGatewayMiddleware(d){if(gatewayMiddleware)try{gatewayMiddleware(d,GATEWAY_CONFIG)}catch(_){console.error("[gateway] Middleware error:",_)}}u(applyGatewayMiddleware,"applyGatewayMiddleware"),a(applyGatewayMiddleware,"applyGatewayMiddleware");const GATEWAY_ROUTES=(()=>{try{return JSON.parse(process.env.GATEWAY_ROUTES||"[]")}catch{return[]}})(),GATEWAY_HTTPS=process.env.GATEWAY_HTTPS==="1";function hostMatches(d,_){if(d.startsWith("*.")){const f=d.slice(1);return _.endsWith(f)||_===d.slice(2)}return _===d}u(hostMatches,"hostMatches"),a(hostMatches,"hostMatches");function matchGatewayRoute(d){if(GATEWAY_ROUTES.length===0)return;const _=(d.headers.host||"").split(":")[0],f=d.url||"/";for(const m of GATEWAY_ROUTES)if(!(m.host&&!hostMatches(m.host,_))&&!(m.prefix&&!f.startsWith(m.prefix)))return m}u(matchGatewayRoute,"matchGatewayRoute"),a(matchGatewayRoute,"matchGatewayRoute");const{specs:WORKER_SPECS,problems:WORKER_PROBLEMS}=(0,worker_supervisor_1.parseWorkerSpecs)(process.env.WORKERS),WORKER_NAMES=WORKER_SPECS.map(d=>d.name),LENSMCP_WS_KEY=process.env.LENSMCP_WS_KEY||"",SOCK_NS=process.env.LENSMCP_SOCK_NS||LENSMCP_WS_KEY,SOCK_DIR=SOCK_NS?path.join(os.tmpdir(),SOCK_NS,`${SERVICE_NAME||"lensmcp-cluster"}-devserver`):path.join(os.tmpdir(),`${SERVICE_NAME||"lensmcp-cluster"}-devserver`),SOCKET_ONLY=process.env.LENSMCP_SOCKET_ONLY==="1",POD_GENERATION=(()=>{const d=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(d)&&d>1?Math.floor(d):1})();function childSockPath(d){return path.join(SOCK_DIR,(0,pod_sock_1.podSockName)(d,POD_GENERATION))}u(childSockPath,"childSockPath"),a(childSockPath,"childSockPath");function sockOwnerPath(d){return`${d}.owner`}u(sockOwnerPath,"sockOwnerPath"),a(sockOwnerPath,"sockOwnerPath");function sockRetiringPath(d){return`${d}.retiring`}u(sockRetiringPath,"sockRetiringPath"),a(sockRetiringPath,"sockRetiringPath");function tell(d,_){if(!d.connected||typeof d.send!="function")return!1;try{return d.send(_,()=>{})}catch{return!1}}u(tell,"tell"),a(tell,"tell");function refusePoolCommand(d,_){console.warn(`[devserver] refused ${d.method} ${d.url} on the public port \u2014 under a gateway the pool is driven on the control socket only.`),_.writeHead(404,{"content-type":"application/json"}),_.end(JSON.stringify({error:"not found"}))}u(refusePoolCommand,"refusePoolCommand"),a(refusePoolCommand,"refusePoolCommand"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(d=process.env){const _=(d[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return _==="1"||_==="true"||_==="yes"||_==="on"}u(usesFixedInspectorPorts,"usesFixedInspectorPorts"),a(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(d,_,f=process.env){if(!usesFixedInspectorPorts(f))return 0;const m=Math.trunc(d)+Math.trunc(_);return Number.isInteger(m)&&m>1024&&m<=65535?m:0}u(inspectorPortFor,"inspectorPortFor"),a(inspectorPortFor,"inspectorPortFor");function ensureOwnerOnlyDir(d){try{fs.mkdirSync(d,{recursive:!0,mode:448})}catch{}try{const _=fs.lstatSync(d);_.isDirectory()&&(_.mode&63)!==0&&fs.chmodSync(d,448)}catch{}}u(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),a(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(d,_=process.pid){ensureOwnerOnlyDir(path.dirname(d));try{const f=sockOwnerPath(d);try{fs.unlinkSync(f)}catch{}fs.writeFileSync(f,String(_),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(d)}catch{}try{fs.unlinkSync(sockRetiringPath(d))}catch{}}u(claimSock,"claimSock"),a(claimSock,"claimSock");function sockOwnedByAnother(d,_=process.pid){try{const f=fs.readFileSync(sockOwnerPath(d),"utf8").trim();return!!f&&f!==String(_)}catch{return!1}}u(sockOwnedByAnother,"sockOwnedByAnother"),a(sockOwnedByAnother,"sockOwnedByAnother");function cleanupSock(d,_=process.pid){if(d){try{const f=fs.readFileSync(sockOwnerPath(d),"utf8").trim();if(f&&f!==String(_))return}catch{}try{fs.unlinkSync(d)}catch{}try{fs.unlinkSync(sockOwnerPath(d))}catch{}try{fs.unlinkSync(sockRetiringPath(d))}catch{}(0,gc_notice_1.clearGcNotice)(d)}}u(cleanupSock,"cleanupSock"),a(cleanupSock,"cleanupSock");function postRetiringNotice(d,_){if(!(!d||!_)){try{const f=fs.readFileSync(sockOwnerPath(d),"utf8").trim();if(f&&f!==String(_))return}catch{}try{fs.writeFileSync(sockRetiringPath(d),String(_),{flag:"w",mode:384})}catch{}}}u(postRetiringNotice,"postRetiringNotice"),a(postRetiringNotice,"postRetiringNotice");function serviceKeyFingerprint(){const d=process.env.LENSMCP_SERVICE_KEY;if(!d)return;const{createHash:_}=require("node:crypto");return _("sha256").update(d).digest("hex").slice(0,8)}u(serviceKeyFingerprint,"serviceKeyFingerprint"),a(serviceKeyFingerprint,"serviceKeyFingerprint");function rootModuleOf(d){let _;for(const f of d.getModules?.()?.values()??[])typeof f?.metatype!="function"||typeof f.distance!="number"||!Number.isFinite(f.distance)||(!_||f.distance<_.distance)&&(_=f);return _?.metatype}u(rootModuleOf,"rootModuleOf"),a(rootModuleOf,"rootModuleOf");let lastImportedModule;function takeImportedModule(){const d=lastImportedModule;return lastImportedModule=void 0,d}u(takeImportedModule,"takeImportedModule"),a(takeImportedModule,"takeImportedModule");async function importFresh(spec){process.env.DEVSERVER_MODE="1";const requested=path.isAbsolute(spec)?spec:path.join(__dirname,spec);let resolved=requested;try{resolved=fs.realpathSync(requested)}catch{}eval(`(() => {
2
2
  const key = ${JSON.stringify(resolved)};
3
3
  const previous = require.cache[key];
4
4
  delete require.cache[key]; // delete from CJS cache
@@ -9,16 +9,16 @@
9
9
  const at = owner && Array.isArray(owner.children) ? owner.children.indexOf(previous) : -1;
10
10
  if (at >= 0) owner.children.splice(at, 1);
11
11
  }
12
- })()`),(0,bundle_parent_1.routeBundleRequires)(requested,resolved);const exports=eval(`require(${JSON.stringify(resolved)})`);try{const mod=eval("require.cache")[resolved];lastImportedModule=mod&&typeof mod=="object"?mod:void 0}catch{}return exports}u(importFresh,"importFresh"),a(importFresh,"importFresh");function debounce(d,_){let f=null;return(...m)=>{f&&clearTimeout(f),f=setTimeout(()=>d(...m),_)}}if(u(debounce,"debounce"),a(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const d=a(f=>{if(!inspector.url())try{inspector.open(f,"127.0.0.1",!1)}catch(W){console.warn(`[child] inspector.open(${f}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,W.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const m=inspector.url();m&&tell(process,{type:"inspector-url",url:m})},"openInspector"),_=process.env.CHILD_DEBUG_PORT;if(_!==void 0&&_!==""){const f=Number(_);Number.isInteger(f)&&(f===0||f>1024)&&d(f)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let f=null,m=!1,W,re=!1;const Ve=(0,hot_swap_registries_1.registryPruningEnabled)(),fe=a((r,c)=>{const l=(0,hot_swap_registries_1.describePrune)(r,c);l&&console.log(l)},"logPrune");let j={app:null,handler:a((r,c)=>{c.statusCode=503,c.end("starting")},"handler"),inflight:0};const _e=(()=>{const r=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(r)&&r>=0?r:15*6e4})(),C=a(r=>{const c=globalThis.__lensmcpActiveWork;if(typeof c!="function")return{count:0};try{const l=c(r);return{count:Number(l?.count??0),...l?.oldestStartedAt?{oldestStartedAt:l.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),$e=a(r=>{const c=globalThis.__lensmcpStopWorkIntake;if(typeof c=="function")try{return c(r)}catch{return}},"stopWorkIntake"),ge=a(r=>{const c=globalThis.__lensmcpWorkIntakeReport;if(!(r===void 0||typeof c!="function"))try{return c(r)??void 0}catch{return}},"workIntakeReport"),ke=(()=>{const r=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(r)&&r>=1?Math.floor(r):1})(),F=new Set,Z=(()=>{const r=Number(process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS);return process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==void 0&&process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==""&&Number.isFinite(r)&&r>=0?r:3e4})(),V=a(r=>{if(!r.abandoned?.size)return 0;const c=Date.now();for(const[l,h]of r.abandoned)(l.writableEnded||c-h>=Z)&&r.abandoned.delete(l);return r.abandoned.size},"abandonedRunning"),oe=a(r=>{let c=0;for(const l of r.open??[])l.headersSent||(c+=1);return{unanswered:c+V(r),streams:Math.max(0,r.inflight-c),work:C(r.workGen).count}},"owedBy"),ne=a((r,c,l=`${c}s`)=>`${r} ${r===1?c:l}`,"plural");async function We(){if(Z<=0)return;const r=Date.now();let c=!1;for(;!(F.size<ke);){const l=F.values().next().value;if(!l||l.closed)break;const h=oe(l);if(h.unanswered===0&&h.work===0)break;const E=(l.drainingSince??r)+Z-Date.now();if(E<=0)break;c||(c=!0,console.log(`[child] swap held \u2014 the app replaced ${Math.round((Date.now()-(l.drainingSince??r))/100)/10}s ago is still `+[h.unanswered?`answering ${ne(h.unanswered,"request")}`:"",h.work?`running ${ne(h.work,"background job")}`:""].filter(Boolean).join(" and ")+`, and this pod keeps at most ${ke} replaced app(s) alive (LENSMCP_SWAP_MAX_DRAINING). The new build is applied when it is done \u2014 in ${Math.ceil(E/1e3)}s at the latest (LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS).`)),await new Promise(S=>{setTimeout(S,Math.min(100,E)).unref?.()})}c&&console.log(`[child] swap resumed after ${Date.now()-r}ms`)}u(We,"He"),a(We,"holdForOwedWork");const O=(0,swap_recycle_1.swapRecyclePolicy)();let we,se=0;const Je=(()=>{const r=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(r)&&r>=0?r:3500})();let g=!1,b=!1;const v=(0,pod_memory_1.podMemoryPolicy)();let K=0;const x=new Set;let me=!1;const B=a(()=>{if(!me)return;let r=0;for(const c of x)c.deref()===void 0?x.delete(c):r+=1;return r},"liveContainers"),k=(0,pod_memory_1.createPodMemoryMonitor)({policy:v,state:a(()=>({gen:j.gen??0,swaps:se,...we===void 0?{}:{baseline:we},busy:m||re||J.size>0,lastBusyAt:D}),"state"),send:a(r=>{tell(process,r)},"send"),containers:B,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:a(()=>Oe(),"onRetained"),gc:a(()=>(0,gc_notice_1.withGcNotice)(N,pod_memory_1.fullGc),"gc")}),H=a(()=>((0,gc_notice_1.withGcNotice)(N,pod_memory_1.fullGc)&&k.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),U=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,c=`${path.sep}node_modules${path.sep}`,l=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:a(h=>l.has(h)||h.startsWith(r)&&!h.slice(r.length).includes(c)&&!h.slice(r.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let ie=!1;function Te(){if(!(!U||ie))try{const r=U();r.errors>0&&console.log(`[child] read the stack of ${r.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(r){console.error("[child] could not release library error frames:",r),ie=!0}}u(Te,"De"),a(Te,"releaseLibraryErrorFrames");const J=new Map;let D=Date.now();const Fe=a(()=>O.settleMs<=0||!m&&!re&&J.size===0&&Date.now()-D>=O.settleMs,"settled");let Y;function ae(r){if(!(!O.enabled||g||b)){if(O.settleMs<=0){X(r);return}Y&&clearTimeout(Y),Y=setTimeout(()=>{Y=void 0,Fe()?X(r):ae(r)},O.settleMs),Y.unref?.()}}u(ae,"he"),a(ae,"recycleCheckWhenSettled");const be=a(()=>{if(O.stuckCloseMs<=0)return 0;const r=Date.now();let c=0;for(const l of J.values())r-l>=O.stuckCloseMs&&(c+=1);return c},"stuckCloses");function X(r){if(!O.enabled||g||b||!process.send)return;const c=v8.getHeapStatistics().heap_size_limit,l=a(E=>{const S=k.oldestRetained();return{swaps:se,usedBytes:E,limitBytes:c,stuckCloses:be(),settled:Fe(),...we===void 0?{}:{baselineBytes:we},...S?{retained:{generation:S.gen,ageMs:Date.now()-S.closedAt,gcs:S.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(O,l(process.memoryUsage().heapUsed)))return;const h=(0,swap_recycle_1.recycleReason)(O,l(H()));h&&(g=!0,console.log(`[child] asking for a fresh pod ${r} \u2014 ${h}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB / _RETAINED_MS, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),tell(process,{type:"recycle-request",reason:h}))}u(X,"Q"),a(X,"considerRecycle");let ye;function Oe(){if(!O.enabled||O.retainedMs<=0||g||b)return;const r=k.oldestRetained();if(!r)return;const c=r.closedAt+O.retainedMs-Date.now();if(c<=0){X("with a closed generation still in memory");return}ye||(ye=setTimeout(()=>{ye=void 0,k.measure("retention-check").catch(()=>{})},c),ye.unref?.())}u(Oe,"je"),a(Oe,"retainedRecycleCheck");function Ee(r,c){const l={};c&&(l.module=c),r&&(l.app=r);try{const h=r?.app;if(h&&typeof h=="object"&&(l.nestApp=h,h.container&&typeof h.container=="object")){l.container=h.container;const E=rootModuleOf(h.container);E&&(l.rootModule=E)}}catch{}return l}u(Ee,"Ne"),a(Ee,"generationTargets");async function ce(r,c){if(r.closed||(r.closed=!0,F.delete(r),c&&console.error(`[child] ${c}`),!r.app))return;J.set(r,Date.now());const l=O.enabled&&O.stuckCloseMs>0?setTimeout(()=>{J.has(r)&&(console.error(`[child] a previous app has been closing for ${Math.round(O.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),X("with a previous app stuck in close()"))},O.stuckCloseMs):void 0;l?.unref?.();const h=r.app;try{await r.app.close()}catch(E){console.error("[child] error closing a previous app:",E)}finally{J.delete(r),D=Date.now(),l&&clearTimeout(l),r.app=null,r.abandoned=void 0,r.handler=(E,S)=>{S.statusCode=503,S.end("closed")},r.gen!==void 0&&k.closed(r.gen,se,Ee(h,r.module)),r.module=void 0}if(r.superseded){try{fe((0,hot_swap_registries_1.pruneRegistries)(r.superseded,"serve"),"after closing the previous app")}catch(E){console.error("[child] could not release the previous app's class registrations:",E)}r.superseded=void 0}ae("after closing the previous app")}u(ce,"me"),a(ce,"closeGeneration");async function le(r){const c=Date.now();r.drainingSince=c;const l=a(()=>({inflight:r.inflight+V(r),work:C(r.workGen).count}),"busy"),h=l();if(h.inflight>0||h.work>0){for(;F.size>=ke;){const I=F.values().next().value;if(!I)break;const $=oe(I),z=Math.round((Date.now()-(I.drainingSince??c))/1e3);if($.unanswered===0&&$.streams===0&&$.work===0){await ce(I);continue}await ce(I,`closing a still-draining generation early \u2014 ${F.size} were pending and the cap is ${ke} (each holds a whole app: DI container, DB pool, queue workers). `+($.unanswered>0||$.work>0?`It was replaced ${z}s ago and had still not answered ${ne($.unanswered,"request")}`+($.work?` or finished ${ne($.work,"background job")}`:"")+` \u2014 those fail now (a queue job keeps its lock until the TTL lapses). Raise LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS (currently ${Z}ms) to wait longer for them. `:`It held only ${ne($.streams,"open stream")}; their clients reconnect to the new app. `)+"Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.")}F.add(r)}for(;_e>0&&Date.now()-c<_e;){if(r.closed)return;const I=l();if(I.inflight===0&&I.work===0)break;await new Promise($=>{setTimeout($,250).unref?.()})}if(r.closed)return;const E=l(),S=ge(r.workGen);E.inflight>0||E.work>0?console.error(`[child] swap drain expired after ${Date.now()-c}ms \u2014 closing the previous app with ${E.inflight} in-flight request(s) and ${E.work} background job(s) still running. `+(S?`Previous app still processing ${S.running} queue job(s); ${S.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${_e}ms) if this is legitimate work.`):(Date.now()-c>250||S?.runningAtStop||S?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-c}ms \u2014 closing it`+(S?` (it finished ${S.finishedAfterStop} queue job(s) after the swap; ${S.takenAfterStop} were taken after the swap)`:"")),await ce(r)}u(le,"ye"),a(le,"drainThenClose");async function de(){if(m)return re=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),f;m=!0,D=Date.now(),await We(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(P,ee)=>{console.error(`[child] uncaughtException (${ee}):`,P)}),process.on("unhandledRejection",P=>{const ee=P instanceof Error?P.stack||P.message:String(P);ee.includes("FronteggContextScope")||ee.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",P)});let r;if(Ve)try{r=(0,hot_swap_registries_1.captureRegistries)()}catch{r=void 0}const c=global.__lensmcpBeginWorkGeneration,l=typeof c=="function"?c():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(P){if(!(P instanceof SyntaxError))throw P;console.warn(`[child] bundle was unreadable mid-write (${P.message}) \u2014 retrying the swap once`),await new Promise(ee=>setTimeout(ee,400)),await importFresh(BUNDLE_PATH)}}catch(P){throw m=!1,D=Date.now(),P}if(typeof global.createChildApp!="function")throw m=!1,D=Date.now(),new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);let h;if(r)try{h=(0,hot_swap_registries_1.supersededSince)(r),fe((0,hot_swap_registries_1.pruneRegistries)(h,"build"),"at the swap")}catch(P){console.error("[child] could not release superseded class registrations:",P)}let E;try{E=await global.createChildApp()}catch(P){throw m=!1,D=Date.now(),takeImportedModule(),P}const S=j;h?.size&&(S.app?S.superseded=h:fe((0,hot_swap_registries_1.pruneRegistries)(h,"serve"),"at the swap")),f=E;const I=takeImportedModule();j={app:E,handler:E.handler,inflight:0,gen:++K,...I?{module:I}:{},...l===void 0?{}:{workGen:l}};const $=Ee(E,void 0).container;$&&(x.add(new WeakRef($)),me=!0);const z=S.app&&l!==void 0?$e(l):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(z?.workers?`; its ${z.workers} queue worker(s) stopped taking new jobs, previous app still processing ${z.runningAtStop} job(s)`:"")),Te(),S.app&&(se+=1,le(S).catch(P=>console.error("[child] background close error:",P)),setTimeout(()=>X("30s after a hot swap"),3e4).unref?.(),k.swapped(S.gen,()=>S.closed===!0&&!J.has(S))),m=!1,D=Date.now(),re?(re=!1,de()):E}u(de,"Ee"),a(de,"swapNow");const Pe=debounce(async()=>{try{console.log("[child] /webpack/reload"),await de()}catch(r){console.error("[child] reload failed:",r)}},250),pe=http.createServer((r,c)=>{if(r.method==="POST"&&r.url==="/webpack/reload"){c.writeHead(200,{"content-type":"application/json"}),c.end(JSON.stringify({ok:!0})),Pe();return}if(r.method==="GET"&&r.url==="/__lensmcp/alive"){const h=r.headers["x-internal-token"],E=process.env.LENSMCP_SERVICE_KEY;if(E&&h!==E){c.writeHead(404,{"content-type":"text/plain"}),c.end("not found");return}const S=C();c.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),c.end(JSON.stringify({ok:!0,pid:process.pid,swapping:m,inflight:j.inflight,oldestInflightAt:j.oldestAt,activeWork:S.count,oldestWorkAt:S.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...W?{watcher:W}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const l=j;l.inflight+=1,(l.open??=new Set).add(c),l.oldestAt===void 0&&(l.oldestAt=Date.now()),c.on("close",()=>{l.inflight-=1,l.open?.delete(c),Z>0&&!l.closed&&!c.headersSent&&!c.writableEnded&&((l.abandoned??=new Map).set(c,Date.now()),l.abandoned.size>256&&V(l)),l.inflight===0&&(l.oldestAt=void 0),k.activity()}),l.handler(r,c)});pe.keepAliveTimeout=0;const N=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(N),process.exit(0)});try{const r=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const l=(0,schema_first_instance_1.prewarmZodFor)(r);l?.created&&console.log(`[child] zod${l.version?` ${l.version}`:""}: built ${l.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const l=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(r);l?.patched&&console.log(`[child] ioredis${l.versions.length?` ${l.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await de(),we=(await k.booted()).live,console.log(`[child] Using bundle: ${r}`),claimSock(N);let c=!1;pe.on("error",l=>{if(c){console.error("[child] server error:",l);return}console.error(`[child] could not listen on ${N} (${l.code??l.message}) \u2014 exiting; the devserver starts another pod`),tell(process,{type:"boot-error",error:`could not listen on ${N}: ${String(l?.stack||l)}`}),cleanupSock(N),process.exitCode=1,setTimeout(()=>process.exit(1),100)}),pe.listen(N,()=>{c=!0,console.log(`[child] Listening on ${N}`),tell(process,{type:"ready",socketPath:N})})}catch(r){console.error("[child] Startup error:",r),tell(process,{type:"boot-error",error:String(r?.stack||r)}),process.exitCode=1;return}process.on("message",async r=>{if(!(!r||typeof r!="object")){if(r.type==="debug-open"){const c=Number(r.port);d(Number.isInteger(c)&&c>1024?c:0);return}if(r.type==="watcher"){const c=r.testimony;c&&typeof c=="object"&&(W=c);return}if(r.type==="heap-snapshot"){Me(r);return}if(r.type==="recycle-declined"){g=!1;return}if(r.type==="retire"){q();return}if(r.type==="reload"){if(b)return;try{const c=Date.now();await de(),tell(process,{type:"reloaded",socketPath:N,swap:{generation:K,swaps:se,swapMs:Date.now()-c,draining:F.size}})}catch(c){console.error("[child] reload error:",c),tell(process,{type:"reload-error",error:String(c?.stack||c)})}}}});let Se=!1;async function Me(r){const c=a(l=>{tell(process,{type:"heap-snapshot-result",id:r.id,...l})},"reply");if(Se)return c({ok:!1,refused:"a snapshot of this pod is already being written"});Se=!0;try{const l=H(),h=k.tracker.views().map($=>$.gen),E=(0,heap_snapshot_1.heapSnapshotPolicy)();r.maxHeapBytes&&(E.maxHeapBytes=r.maxHeapBytes);const S=(0,heap_snapshot_1.heapSnapshotRefusal)(E,{live:l,...r.available?{available:r.available}:{}});if(S)return c({ok:!1,refused:S,live:l});console.log(`[child] writing a heap snapshot (${Math.round(l/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const I=await(0,heap_snapshot_1.writeHeapSnapshot)(r.file);c({ok:!0,file:r.file,bytes:I.bytes,ms:I.ms,live:l,gen:j.gen??0,closedAlive:h})}catch(l){c({ok:!1,error:l?.message??String(l)})}finally{Se=!1}}u(Me,"Ye"),a(Me,"takeHeapSnapshot");async function q(r="a fresh pod has taken over"){if(b)return;b=!0,k.stop(),console.log(`[child] retiring \u2014 ${r}; ${path.basename(N,".sock")} (pid ${process.pid}) is finishing in-flight work, then exiting`);const c=$e(Number.POSITIVE_INFINITY);c?.workers&&console.log(`[child] its ${c.workers} queue worker(s) stopped taking new jobs, still processing ${c.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath(N),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(h=>{setTimeout(h,Je).unref?.()}),cleanupSock(N),pe.close();const l=Date.now();for(;Date.now()-l<_e;){pe.closeIdleConnections?.();let h=0;for(const E of F)h+=oe(E).unanswered;if(j.inflight+V(j)===0&&h===0&&C().count===0)break;await new Promise(E=>{setTimeout(E,250).unref?.()})}setTimeout(()=>process.exit(0),3e4).unref?.();try{await f?.close()}catch(h){console.error("[child] close error while retiring:",h)}process.exit(0)}u(q,"B"),a(q,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const ue=a(async r=>{console.log(`[child ${r}] shutting down\u2026`);const c=f?.close().catch(h=>console.error("[child] close error:",h)),l=new Promise(h=>pe.close(()=>h()));cleanupSock(N),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([c,l]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(r=>{process.on(r,()=>{if(r==="SIGTERM"&&!b&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){q("this generation was handed over to a newer one");return}inspector?.close?.(),ue(r)})})})()}else{let d=u(function(g){V.push(g),V.length>Z&&V.splice(0,V.length-Z),ke?.write((0,inspector_secret_1.redactInspectorUrls)(g.replace(F,"")));for(const b of oe)try{const v=b.plain?g.replace(F,""):g;b.res.write(b.redact?(0,inspector_secret_1.redactInspectorUrls)(v):v)}catch{oe.delete(b)}},"we");a(d,"emitLogLine");const _=12,f=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",m=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),W=a(g=>({project:f,service:SERVICE_NAME||f,pod:g.id,pid:g.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),re=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],Ve="\x1B[0m",fe=Math.max(1,Number(process.env.CHILD_COUNT||1)),j=LENSMCP_WS_KEY||path.basename(process.cwd()),_e=a(g=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(j,SERVICE_NAME||"service",g)),"execPathFor");let C=(()=>{const g=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(g)&&g>1024?g:0})();const $e=20;C&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/closed/codex-security-06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${C}+slot, workers ${C}+${$e}+i). Any account on this host can connect to 127.0.0.1:${C} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const ge=process.env.LENSMCP_LOG_FILE||"",ke=ge?(()=>{try{fs.mkdirSync(path.dirname(ge),{recursive:!0});const g=fs.createWriteStream(ge,{flags:"a",mode:384});try{fs.chmodSync(ge,384)}catch{}return g}catch(g){return console.warn(`[parent] cannot open log file ${ge}:`,g.message),null}})():null,F=/\x1b\[[0-9;]*m/g,Z=2e3,V=[],oe=new Set,ne=(0,devserver_transport_1.gatewayManaged)();for(const g of["log","info","warn","error"]){const b=console[g].bind(console),v=g==="log"||g==="info"?process.stdout:process.stderr;console[g]=(...K)=>{b(...K.map(x=>typeof x=="string"?(0,inspector_secret_1.forStream)(v,ne,x):x)),d(K.map(x=>typeof x=="string"?x:util.inspect(x)).join(" ")+`
13
- `)}}const We=a(g=>re[(g-1)%re.length],"colorFor"),O=a(g=>{if(fe===1)return"";const b=`[child#${g}] `;return`${We(g)}${b}${Ve}`},"tagFor"),we=a(g=>g.startsWith("{")&&g.endsWith("}"),"isJsonLog"),se=a((g,b,v)=>{const K=prettyFactory({sync:!0,colorize:!0,crlf:!0,messageKey:"message",errorLikeObjectKeys:["err","error"],errorProps:"type,message,stack",ignore:["logContext","context","hostname","req","res","err.driverError","module","cloudEnvironment","frontegg-application-id","frontegg-tenant-id","frontegg-trace-id","frontegg-vendor-id","host","service","version","err","error"].join(","),messageFormat:"{if module}[{module}] {end}{if context}[{context}] {end}{if logContext}[{logContext}] {end}{message}",customPrettifiers:{stack:a(B=>`
14
- `+String(B),"stack")}}),x=a((B,k)=>{const H=B==="stdout"?process.stdout:process.stderr;try{v?.(k)}catch{}const U=we(k)?K(k):`${k}
15
- `;H.write((0,inspector_secret_1.forStream)(H,ne,`${b}${U}`)),d(`${b}${U}`)},"write"),me=a((B,k)=>{if(!B)return;const H=readline.createInterface({input:B});H.on("line",U=>x(k,U)),H.on("close",()=>{})},"attach");me(g.stdout,"stdout"),me(g.stderr,"stderr")},"wireProcLogging"),Je=a(g=>se(g.proc,O(g.id),b=>g.tail.push(b)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const g=httpProxy.createProxyServer({});let b=Number(process.env.PORT)||0;b||(b=9090);const v=[],K=new Set;function x(){let e=0;for(;K.has(e);)e++;return K.add(e),e}u(x,"D"),a(x,"allocDebugSlot");let me=1,B=0,k=!1,H=fe,U=[],ie=0;const Te=(0,swap_mode_1.swapMode)(),J=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),D=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),Fe=a(e=>e.booting&&!D(e)&&Date.now()-e.spawnedAt<J,"stillBooting");function Y(){return v.filter(e=>e.healthy&&typeof e.socketPath=="string")}u(Y,"J"),a(Y,"healthyChildren");let ae,be=0,X;const ye={project:f,service:SERVICE_NAME||f,podGeneration:POD_GENERATION};function Oe(e){const t=(0,service_build_events_1.buildAccountOf)(e.building);t&&t.startedAt!==be&&(be=t.startedAt,m.publish((0,service_build_events_1.buildServiceBuildEvent)(ye,"started",t,{root:process.cwd()})));const n=(0,service_build_events_1.buildAccountOf)(e.failed);n&&(be=0,m.publish((0,service_build_events_1.buildServiceBuildEvent)(ye,"failed",n,{root:process.cwd()})))}u(Oe,"je"),a(Oe,"recordBuildProgress");function Ee(e){if(!e||typeof e!="object")return;const t=e;if(t.schemaVersion===1){Oe(t),"failed"in t&&delete t.failed,ae=t;for(const n of v)tell(n.proc,{type:"watcher",testimony:t})}}u(Ee,"Ne"),a(Ee,"recordWatcherTestimony");function ce(e,t=256*1024){return new Promise(n=>{let i="",s=!1;e.on("data",o=>{s||(i+=o,i.length>t&&(s=!0))}),e.on("end",()=>{if(s||!i)return n(void 0);try{n(JSON.parse(i))}catch{n(void 0)}}),e.on("error",()=>n(void 0))})}u(ce,"me"),a(ce,"readJsonBody");function le(){const e=Y();if(e.length===0)return null;const t=B%e.length;return B=(B+1)%e.length,e[t]}u(le,"ye"),a(le,"pickChild");function de(e,t){const n=`Child#${e.id}`,i=SERVICE_NAME||f,s=Date.now();if(t.type==="pod-memory"){const o=t.report;if(!o||typeof o.live!="number")return;e.memory.latest=o,e.memory.history.push(o),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(n,o)}`),m.publish((0,pod_memory_events_1.buildPodMemoryEvent)(W(e),o))}else if(t.type==="pod-memory-retained"){const o=t.retained;if(!o||typeof o.gen!="number")return;e.memory.retained.set(o.gen,{...o,at:s}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(n,i,o)}`),m.publish((0,pod_memory_events_1.buildPodRetainedEvent)(W(e),o,s))}else{const o=t.released;if(!o||typeof o.gen!="number")return;e.memory.retained.delete(o.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(n,i,o)}`),m.publish((0,pod_memory_events_1.buildPodReleasedEvent)(W(e),o,s))}}u(de,"Ee"),a(de,"recordPodMemory");const Pe=new Map;let pe=0;async function N(){const e=me++,t=childSockPath(e);cleanupSock(t);const n=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&n.unshift("--require","@swc-node/register"),n.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const i=C?x():-1,s=(0,node_child_process_1.fork)(__filename,{env:(0,child_process_env_1.childProcessEnv)({...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:t,...i>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(C,i))}}),stdio:["inherit","pipe","pipe","ipc"],execArgv:n,execPath:_e("pod")}),o={id:e,proc:s,healthy:!1,lastError:null,debugSlot:i,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:ie,memory:{history:[],retained:new Map}};return s.on("error",y=>console.error(`[parent] Child#${e}: ${y.message}`)),s.on("message",y=>{if(!y||typeof y!="object")return;const p=y;if(p.type==="ready")o.socketPath=String(p.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),ae&&tell(s,{type:"watcher",testimony:ae}),ie!==o.bootReloadSeq&&(Te==="process"?P():tell(s,{type:"reload"}));else if(p.type==="recycle-request")S(o,String(p.reason||"it has outgrown its bound"));else if(p.type==="inspector-url")o.inspectorUrl=String(p.url),console.log(`[parent] Child#${o.id} Debugger listening on ${p.url}`);else if(p.type==="reloaded"){if(q.has(o)){console.log(`[parent] Child#${o.id} finished a swap it began before it was retired \u2014 not serving it: a fresh pod has taken over`);return}o.socketPath=String(p.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`);const w=p.swap;w&&typeof w.generation=="number"&&typeof w.swaps=="number"&&m.publish((0,service_build_events_1.buildServiceSwapEvent)(W(o),{generation:w.generation,swaps:w.swaps,swapMs:typeof w.swapMs=="number"?w.swapMs:0,draining:typeof w.draining=="number"?w.draining:0},X,{root:process.cwd()}))}else p.type==="reload-error"?(o.lastError=String(p.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):p.type==="boot-error"?(o.lastError=String(p.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1):p.type==="pod-memory"||p.type==="pod-memory-retained"||p.type==="pod-memory-released"?de(o,p):p.type==="heap-snapshot-result"&&Pe.get(Number(p.id))?.(p)}),s.on("exit",(y,p)=>{o.booting=!1;for(const A of o.memory.retained.values()){const M={gen:A.gen,closedAt:A.closedAt,ageMs:Date.now()-A.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${o.id}`,SERVICE_NAME||f,M)}`),m.publish((0,pod_memory_events_1.buildPodReleasedEvent)(W(o),M,Date.now()))}o.memory.retained.clear(),o.memory.latest&&m.publish((0,pod_memory_events_1.buildPodExitedEvent)(W(o),{code:y,signal:p},Date.now())),o.debugSlot>=0&&(K.delete(o.debugSlot),o.debugSlot=-1);const w=p==="SIGINT"||p==="SIGTERM"||y===0;if(w?console.log(`[parent] Child#${o.id} exited cleanly (code=${y}, signal=${p??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${y}, signal=${p??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,y,p,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${y}, signal=${p??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!w&&!k){const A=v.indexOf(o);A>=0&&v.splice(A,1);const M=Date.now();if(U=U.filter(T=>M-T<3e4),U.length>=5){console.error(`[parent] crash-loop detected (${U.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}U.push(M),setTimeout(()=>{const T=v.filter(Q=>Q.proc.killed===!1).length;!k&&T<H&&(console.log(`[parent] respawning a crashed pod (${T}/${H} live)`),N().catch(()=>{}))},1500).unref?.()}}),v.push(o),Je(o),o}u(N,"N"),a(N,"spawnChild");async function Se(e){H=Math.max(H,e);const t=v.filter(i=>i.proc.killed===!1),n=e-t.length;for(let i=0;i<n;i++)await N()}u(Se,"be"),a(Se,"ensurePoolSize");async function Me(){const e=v.filter(t=>(!t.healthy||typeof t.socketPath!="string")&&!Fe(t));await Promise.all(e.map(async t=>{try{t.proc.kill("SIGTERM")}catch{}cleanupSock(t.socketPath,t.proc.pid),t.socketPath=void 0;const n=v.indexOf(t);n>=0&&v.splice(n,1),await N()}))}u(Me,"Ye"),a(Me,"respawnUnhealthy");const q=new Set;let ue=!1,r=0;const c=3e4,l=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function h(e,t={}){const n=Date.now(),i=await N(),s=Date.now()+J;for(;!i.healthy&&!i.lastError&&!D(i)&&Date.now()<s;){if(t.superseded?.()){const o=v.indexOf(i);o>=0&&v.splice(o,1);try{i.proc.kill("SIGTERM")}catch{}return{why:`a newer build landed while Child#${i.id} booted`,superseded:!0}}await new Promise(o=>{setTimeout(o,100).unref?.()})}if(k)return{why:"shutting down"};if(!i.healthy){const o=v.indexOf(i);o>=0&&v.splice(o,1);try{i.proc.kill("SIGTERM")}catch{}return{why:`its successor Child#${i.id} did not come up (${i.lastError??"no answer in time"})`}}return E(e),{fresh:i,readyMs:Date.now()-n}}u(h,"_"),a(h,"replacePod");function E(e){const t=v.indexOf(e);if(t>=0&&v.splice(t,1),e.healthy=!1,D(e))return;q.add(e),postRetiringNotice(e.socketPath,e.proc.pid);const n=setTimeout(()=>{if(!D(e)){console.error(`[parent] Child#${e.id} was still running ${l}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},l);if(n.unref?.(),e.proc.once("exit",()=>{clearTimeout(n),q.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}}u(E,"m"),a(E,"retirePod");async function S(e,t){const n=a(s=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${t}) \u2014 not now: ${s}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(k||!v.includes(e))return;if(ue)return n("another pod is already being replaced");const i=Date.now()-r;if(i<c)return n(`a pod was replaced ${Math.round(i/1e3)}s ago`);ue=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${t}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const s=await h(e);if("why"in s){s.why!=="shutting down"&&n(s.why);return}r=Date.now(),console.log(`[parent] Child#${s.fresh.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${s.fresh.proc.pid})`)}catch(s){n(`could not start a successor: ${s?.message??String(s)}`)}finally{ue=!1}}u(S,"y"),a(S,"recyclePod");let I,$=!1,z=0;function P(){if(I){$=!0;return}I=(async()=>{do{$=!1;const e=ie;for(;ue&&!k;)await new Promise(n=>{setTimeout(n,100).unref?.()});let t=!1;for(const n of Y().filter(i=>i.bootReloadSeq<e)){if(k)break;if(!(!v.includes(n)||!n.healthy)){ue=!0;try{ee(),console.log(`[parent] A new build \u2014 starting a fresh pod for it; Child#${n.id} keeps serving the previous build until it is ready.`);const i=await h(n,{superseded:a(()=>ie!==e&&(0,swap_mode_1.abandonSuperseded)(z),"superseded")});if("why"in i){if(i.superseded){z+=1,console.log(`[parent] ${i.why} \u2014 abandoned; one fresh pod for the newest build instead (${z} in a row). Child#${n.id} keeps serving.`),t=!0;break}i.why!=="shutting down"&&console.error(`[parent] Child#${n.id} keeps serving the previous build \u2014 ${i.why}`);continue}z=0,console.log(`[parent] Child#${i.fresh.id} took over from Child#${n.id} with the new build (pid ${n.proc.pid} \u2192 ${i.fresh.proc.pid}, ready in ${(i.readyMs/1e3).toFixed(1)}s while Child#${n.id} served)`),m.publish((0,service_build_events_1.buildServiceSwapEvent)(W(i.fresh),{generation:1,swaps:0,swapMs:i.readyMs,draining:q.size,handoff:{fromPid:n.proc.pid??0}},X,{root:process.cwd()}))}catch(i){console.error(`[parent] Child#${n.id} keeps serving the previous build \u2014 could not start a successor: ${i?.message??String(i)}`)}finally{ue=!1}}}t&&($=!0)}while($&&!k)})().finally(()=>{I=void 0})}u(P,"A"),a(P,"handoffForReload");function ee(){const e=(0,swap_mode_1.maxRetiring)(),t=[...q].filter(n=>!D(n));for(;t.length>=e;){const n=t.shift();console.error(`[parent] Child#${n.id} (pid ${n.proc.pid}) was still finishing its work when a newer build replaced its successor too \u2014 stopping it (LENSMCP_SWAP_MAX_RETIRING=${e})`);try{n.proc.kill("SIGTERM")}catch{}}}u(ee,"X"),a(ee,"capRetiring");const G=[],He=new Map,Ue=a(e=>{let t=He.get(e);return t||He.set(e,t={crashes:0,restarts:0}),t},"watchOf"),Xe=(0,worker_supervisor_1.workerBackoff)(),Ae=a((e,t)=>({project:f,service:SERVICE_NAME||f,worker:e,pid:t,podGeneration:POD_GENERATION}),"workerWho"),tt=[path.join(__dirname,"worker-guard.js"),path.join(__dirname,"worker-guard.ts")].find(e=>fs.existsSync(e));for(const e of WORKER_PROBLEMS)console.error(`[parent] ${e}`);function ze(e,t){const n=e.name,i=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),s=path.join(i,`${n}.js`),o=Ue(n);if(!fs.existsSync(s))return console.error(`[parent] Worker "${n}" bundle not found: ${s}`),o.missing=s,m.publish((0,worker_supervisor_1.buildWorkerExitedEvent)(Ae(n,0),{ranMs:0,reason:`its bundle was not built (${path.basename(s)} is not in ${i})`})),null;o.missing=void 0;const y=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&y.unshift("--require","@swc-node/register"),tt&&y.push("--require",tt);try{y.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const p=inspectorPortFor(C||9229,$e+Math.max(0,WORKER_NAMES.indexOf(n)));y.push(C?`--inspect=127.0.0.1:${p}`:`--inspect-port=127.0.0.1:${p}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const w=(0,node_child_process_1.fork)(s,{env:(0,worker_supervisor_1.workerEnv)(process.env,e),stdio:["inherit","pipe","pipe","ipc"],execArgv:y,execPath:_e(`w-${n}`)}),A={name:n,spec:e,proc:w,debugPort:p,startedAt:Date.now(),tail:new exit_reason_1.TailBuffer,stopping:!1};G.push(A),w.on("error",T=>console.error(`[parent] Worker "${n}": ${T.message}`)),se(w,`\x1B[35m[worker:${n}] \x1B[0m`,T=>{A.tail.push(T);const Q=/Debugger listening on (wss?:\/\/\S+)/.exec(T);Q&&(A.inspectorUrl=Q[1])});const M=setTimeout(()=>{if(!(Re(A)||A.stopping)){if(o.crashes>0){const T=Date.now()-A.startedAt;console.log(`[parent] Worker "${n}" (pid=${w.pid}) is up again \u2014 ${(0,worker_supervisor_1.spanOf)(T)} without a crash, after ${o.crashes} in a row`),m.publish((0,worker_supervisor_1.buildWorkerUpEvent)(Ae(n,w.pid??0),{ranMs:T,afterCrashes:o.crashes}))}o.crashes=0}},Xe.stableMs);return M.unref?.(),w.on("exit",(T,Q)=>{clearTimeout(M);const ve=G.indexOf(A);ve>=0&&G.splice(ve,1),rt(A,T,Q)}),console.log(`[parent] Worker "${n}" started (pid=${w.pid}, PORT=${(0,worker_supervisor_1.workerEnv)(process.env,e).PORT}${t==="respawn"?`, restart ${o.restarts}`:""})`),m.publish((0,worker_supervisor_1.buildWorkerStartedEvent)(Ae(n,w.pid??0),{why:t,port:e.port,restarts:o.restarts})),A}u(ze,"Ze"),a(ze,"spawnWorkerProcess");function rt(e,t,n){const{name:i}=e,s=e.proc.pid??0,o=Date.now()-e.startedAt,y=(0,worker_supervisor_1.workerExitVerdict)(t,n,e.stopping||k);if(y.kind==="stopped")return;const p=Ue(i);if(y.kind==="exited"){p.lastEnd={at:Date.now(),pid:s,code:t,signal:n,ranMs:o,verdict:"exited"},console.warn(`[parent] Worker "${i}" (pid=${s}) exited on its own (code 0) after ${(0,worker_supervisor_1.spanOf)(o)} \u2014 not started again until the next build: a worker that ends is taken to have finished its work`),m.publish((0,worker_supervisor_1.buildWorkerExitedEvent)(Ae(i,s),{ranMs:o}));return}p.crashes+=1,p.lastEnd={at:Date.now(),pid:s,code:t,signal:n,ranMs:o,verdict:"crashed"};const w=(0,worker_supervisor_1.respawnDelay)(p.crashes,Xe),A=p.crashes>=worker_supervisor_1.CRASH_LOOP_AFTER;console.error((0,exit_reason_1.crashReport)(`[parent] Worker "${i}" crashed (code=${t}, signal=${n??"none"}, pid=${s}, after ${(0,worker_supervisor_1.spanOf)(o)})${y.external?" \u2014 stopped by a signal from outside its devserver":""}; starting it again in ${(0,worker_supervisor_1.spanOf)(w)}${A?` \u2014 ${p.crashes} crashes in a row: it is crash-looping (the wait doubles, up to ${(0,worker_supervisor_1.spanOf)(Xe.maxMs)})`:""}`,t,n,e.tail.snapshot())),m.publish((0,worker_supervisor_1.buildWorkerCrashedEvent)(Ae(i,s),{code:t,signal:n,ranMs:o,crashes:p.crashes,restarts:p.restarts,nextInMs:w,external:y.external,lines:e.tail.snapshot()})),clearTimeout(p.pending),p.nextAt=Date.now()+w,p.pending=setTimeout(()=>{p.pending=void 0,p.nextAt=void 0,!(k||G.some(M=>M.name===i))&&(p.restarts+=1,qe(e.spec,"respawn"))},w),p.pending.unref?.()}u(rt,"ct"),a(rt,"workerEnded");function qe(e,t){const n=Ue(e.name);n.waiting=void 0;const i=e.port>0?[...te].find(o=>o.name===e.name&&!Re(o)):void 0;if(!i){ze(e,t);return}console.log(`[parent] Worker "${e.name}" starts once its predecessor (pid=${i.proc.pid}) has finished its work \u2014 it holds port ${e.port}`);const s=a(()=>{n.waiting===s&&(n.waiting=void 0),!k&&!G.some(o=>o.name===e.name)&&ze(e,t)},"go");n.waiting=s,i.proc.once("exit",s)}u(qe,"Ve"),a(qe,"startWorker");const ot=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),nt=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),te=new Set,Re=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function Be(e,t){if(te.delete(e),!Re(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${t} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}u(Be,"Je"),a(Be,"killDrainingWorker");function Qe(e){e.stopping=!0;try{e.proc.kill("SIGTERM")}catch{}if(Re(e))return;const t=[...te].filter(i=>i.name===e.name);for(;t.length>=nt;)Be(t.shift(),`was still running when a later reload replaced its successor (cap ${nt})`);te.add(e);const n=setTimeout(()=>Be(e,`was still running ${ot}ms after SIGTERM`),ot);n.unref?.(),e.proc.once("exit",()=>{clearTimeout(n),te.delete(e)})}u(Qe,"rt"),a(Qe,"retireWorker");function je(){for(const e of He.values())clearTimeout(e.pending),e.pending=void 0,e.nextAt=void 0,e.waiting=void 0}u(je,"Xe"),a(je,"cancelWorkerStarts");function st(){for(const e of[...G])Qe(e);G.length=0,je();for(const e of WORKER_SPECS)Ue(e.name).crashes=0,qe(e,"reload")}u(st,"lt"),a(st,"restartWorkers");function it(){const e=Date.now();return{workers:WORKER_SPECS.map(t=>{const n=G.find(o=>o.name===t.name),i=He.get(t.name),s=n?"running":i?.pending?(i.crashes??0)>=worker_supervisor_1.CRASH_LOOP_AFTER?"crash-looping":"restarting":i?.waiting?"waiting":i?.missing?"missing":i?.lastEnd?.verdict==="exited"?"exited":"stopped";return{name:t.name,state:s,port:t.port,env:Object.keys(t.env),...n?{pid:n.proc.pid,upMs:e-n.startedAt}:{},crashes:i?.crashes??0,restarts:i?.restarts??0,...i?.nextAt?{nextInMs:Math.max(0,i.nextAt-e)}:{},...i?.lastEnd?{lastEnd:i.lastEnd}:{},...i?.missing?{missing:i.missing}:{}}}),draining:[...te].filter(t=>!Re(t)).map(t=>({name:t.name,pid:t.proc.pid}))}}u(it,"dt"),a(it,"workersView");const ht=debounce(async()=>{ie+=1;const e=Y();if(e.length>0)if(Te==="process")P();else{console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const t of e)tell(t.proc,{type:"reload"})}await Me(),WORKER_NAMES.length>0&&st()},200),at=a((e,t)=>{const n=(0,notice_generation_1.noticeGeneration)(e.headers);return n===void 0||n===POD_GENERATION?!1:(e.resume(),t.writeHead(409,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!1,generation:POD_GENERATION,sentFor:n})),e.url==="/webpack/reload"&&console.log(`[parent] a build of generation g${n} reached generation g${POD_GENERATION} \u2014 not reloaded: it is not this generation's build`),!0)},"refusedForAnotherGeneration"),Ce=a(async(e,t)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);if(at(e,t))return;t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0})),ce(e).then(s=>{const o=s;X=(0,service_build_events_1.buildAccountOf)(o?.build),be=0,Ee(o?.watcher)}),ht();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);if(at(e,t))return;const s=await ce(e);Ee(s),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,relayed:ae!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);let s="";e.on("data",o=>{s+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(s||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await Se(o),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const s=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(s.get("tail")??"100")||0,0),Z),y=s.get("follow")!=="0",p=s.get("plain")==="1",w=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);t.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const A=o===0?"":V.slice(-o).join(""),M=p?A.replace(F,""):A;if(t.write(w?(0,inspector_secret_1.redactInspectorUrls)(M):M),!y){t.end();return}const T={res:t,plain:p,redact:w};oe.add(T),e.on("close",()=>oe.delete(T));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const s=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(s)&&s>1024&&(C=s),C||(C=9229);const o=v.filter(p=>p.proc.exitCode===null&&p.proc.signalCode===null);for(const p of o)p.debugSlot<0&&(p.debugSlot=x()),tell(p.proc,{type:"debug-open",port:inspectorPortFor(C,p.debugSlot)});for(const p of G)try{p.proc.kill("SIGUSR1")}catch{}const y=Date.now()+2e3;for(;Date.now()<y&&o.some(p=>!p.inspectorUrl);)await new Promise(p=>setTimeout(p,50));t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,basePort:C,fixedPorts:usesFixedInspectorPorts(),pods:o.map(p=>({id:p.id,inspectorUrl:p.inspectorUrl??null})),workers:G.map(p=>({name:p.name,port:p.debugPort||(0,inspector_secret_1.inspectorPortOf)(p.inspectorUrl),inspectorUrl:p.inspectorUrl??null}))},null,2)+`
12
+ })()`),(0,bundle_parent_1.routeBundleRequires)(requested,resolved);const exports=eval(`require(${JSON.stringify(resolved)})`);try{const mod=eval("require.cache")[resolved];lastImportedModule=mod&&typeof mod=="object"?mod:void 0}catch{}return exports}u(importFresh,"importFresh"),a(importFresh,"importFresh");function debounce(d,_){let f=null;return(...m)=>{f&&clearTimeout(f),f=setTimeout(()=>d(...m),_)}}if(u(debounce,"debounce"),a(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const d=a(f=>{if(!inspector.url())try{inspector.open(f,"127.0.0.1",!1)}catch(W){console.warn(`[child] inspector.open(${f}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,W.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const m=inspector.url();m&&tell(process,{type:"inspector-url",url:m})},"openInspector"),_=process.env.CHILD_DEBUG_PORT;if(_!==void 0&&_!==""){const f=Number(_);Number.isInteger(f)&&(f===0||f>1024)&&d(f)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let f=null,m=!1,W,re=!1;const Ve=(0,hot_swap_registries_1.registryPruningEnabled)(),fe=a((r,c)=>{const l=(0,hot_swap_registries_1.describePrune)(r,c);l&&console.log(l)},"logPrune");let j={app:null,handler:a((r,c)=>{c.statusCode=503,c.end("starting")},"handler"),inflight:0};const _e=(()=>{const r=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(r)&&r>=0?r:15*6e4})(),C=a(r=>{const c=globalThis.__lensmcpActiveWork;if(typeof c!="function")return{count:0};try{const l=c(r);return{count:Number(l?.count??0),...l?.oldestStartedAt?{oldestStartedAt:l.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),$e=a(r=>{const c=globalThis.__lensmcpStopWorkIntake;if(typeof c=="function")try{return c(r)}catch{return}},"stopWorkIntake"),ge=a(r=>{const c=globalThis.__lensmcpWorkIntakeReport;if(!(r===void 0||typeof c!="function"))try{return c(r)??void 0}catch{return}},"workIntakeReport"),ke=(()=>{const r=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(r)&&r>=1?Math.floor(r):1})(),F=new Set,Z=(()=>{const r=Number(process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS);return process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==void 0&&process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==""&&Number.isFinite(r)&&r>=0?r:3e4})(),V=a(r=>{if(!r.abandoned?.size)return 0;const c=Date.now();for(const[l,h]of r.abandoned)(l.writableEnded||c-h>=Z)&&r.abandoned.delete(l);return r.abandoned.size},"abandonedRunning"),oe=a(r=>{let c=0;for(const l of r.open??[])l.headersSent||(c+=1);return{unanswered:c+V(r),streams:Math.max(0,r.inflight-c),work:C(r.workGen).count}},"owedBy"),ne=a((r,c,l=`${c}s`)=>`${r} ${r===1?c:l}`,"plural");async function We(){if(Z<=0)return;const r=Date.now();let c=!1;for(;!(F.size<ke);){const l=F.values().next().value;if(!l||l.closed)break;const h=oe(l);if(h.unanswered===0&&h.work===0)break;const E=(l.drainingSince??r)+Z-Date.now();if(E<=0)break;c||(c=!0,console.log(`[child] swap held \u2014 the app replaced ${Math.round((Date.now()-(l.drainingSince??r))/100)/10}s ago is still `+[h.unanswered?`answering ${ne(h.unanswered,"request")}`:"",h.work?`running ${ne(h.work,"background job")}`:""].filter(Boolean).join(" and ")+`, and this pod keeps at most ${ke} replaced app(s) alive (LENSMCP_SWAP_MAX_DRAINING). The new build is applied when it is done \u2014 in ${Math.ceil(E/1e3)}s at the latest (LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS).`)),await new Promise(v=>{setTimeout(v,Math.min(100,E)).unref?.()})}c&&console.log(`[child] swap resumed after ${Date.now()-r}ms`)}u(We,"He"),a(We,"holdForOwedWork");const O=(0,swap_recycle_1.swapRecyclePolicy)();let we,se=0;const Je=(()=>{const r=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(r)&&r>=0?r:3500})();let g=!1,b=!1;const S=(0,pod_memory_1.podMemoryPolicy)();let K=0;const x=new Set;let me=!1;const B=a(()=>{if(!me)return;let r=0;for(const c of x)c.deref()===void 0?x.delete(c):r+=1;return r},"liveContainers"),k=(0,pod_memory_1.createPodMemoryMonitor)({policy:S,state:a(()=>({gen:j.gen??0,swaps:se,...we===void 0?{}:{baseline:we},busy:m||re||J.size>0,lastBusyAt:D}),"state"),send:a(r=>{tell(process,r)},"send"),containers:B,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:a(()=>Oe(),"onRetained"),gc:a(()=>(0,gc_notice_1.withGcNotice)(N,pod_memory_1.fullGc),"gc")}),H=a(()=>((0,gc_notice_1.withGcNotice)(N,pod_memory_1.fullGc)&&k.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),U=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,c=`${path.sep}node_modules${path.sep}`,l=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:a(h=>l.has(h)||h.startsWith(r)&&!h.slice(r.length).includes(c)&&!h.slice(r.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let ie=!1;function Te(){if(!(!U||ie))try{const r=U();r.errors>0&&console.log(`[child] read the stack of ${r.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(r){console.error("[child] could not release library error frames:",r),ie=!0}}u(Te,"De"),a(Te,"releaseLibraryErrorFrames");const J=new Map;let D=Date.now();const Fe=a(()=>O.settleMs<=0||!m&&!re&&J.size===0&&Date.now()-D>=O.settleMs,"settled");let Y;function ae(r){if(!(!O.enabled||g||b)){if(O.settleMs<=0){X(r);return}Y&&clearTimeout(Y),Y=setTimeout(()=>{Y=void 0,Fe()?X(r):ae(r)},O.settleMs),Y.unref?.()}}u(ae,"he"),a(ae,"recycleCheckWhenSettled");const be=a(()=>{if(O.stuckCloseMs<=0)return 0;const r=Date.now();let c=0;for(const l of J.values())r-l>=O.stuckCloseMs&&(c+=1);return c},"stuckCloses");function X(r){if(!O.enabled||g||b||!process.send)return;const c=v8.getHeapStatistics().heap_size_limit,l=a(E=>{const v=k.oldestRetained();return{swaps:se,usedBytes:E,limitBytes:c,stuckCloses:be(),settled:Fe(),...we===void 0?{}:{baselineBytes:we},...v?{retained:{generation:v.gen,ageMs:Date.now()-v.closedAt,gcs:v.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(O,l(process.memoryUsage().heapUsed)))return;const h=(0,swap_recycle_1.recycleReason)(O,l(H()));h&&(g=!0,console.log(`[child] asking for a fresh pod ${r} \u2014 ${h}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB / _RETAINED_MS, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),tell(process,{type:"recycle-request",reason:h}))}u(X,"Q"),a(X,"considerRecycle");let ye;function Oe(){if(!O.enabled||O.retainedMs<=0||g||b)return;const r=k.oldestRetained();if(!r)return;const c=r.closedAt+O.retainedMs-Date.now();if(c<=0){X("with a closed generation still in memory");return}ye||(ye=setTimeout(()=>{ye=void 0,k.measure("retention-check").catch(()=>{})},c),ye.unref?.())}u(Oe,"je"),a(Oe,"retainedRecycleCheck");function Ee(r,c){const l={};c&&(l.module=c),r&&(l.app=r);try{const h=r?.app;if(h&&typeof h=="object"&&(l.nestApp=h,h.container&&typeof h.container=="object")){l.container=h.container;const E=rootModuleOf(h.container);E&&(l.rootModule=E)}}catch{}return l}u(Ee,"Ne"),a(Ee,"generationTargets");async function ce(r,c){if(r.closed||(r.closed=!0,F.delete(r),c&&console.error(`[child] ${c}`),!r.app))return;J.set(r,Date.now());const l=O.enabled&&O.stuckCloseMs>0?setTimeout(()=>{J.has(r)&&(console.error(`[child] a previous app has been closing for ${Math.round(O.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),X("with a previous app stuck in close()"))},O.stuckCloseMs):void 0;l?.unref?.();const h=r.app;try{await r.app.close()}catch(E){console.error("[child] error closing a previous app:",E)}finally{J.delete(r),D=Date.now(),l&&clearTimeout(l),r.app=null,r.abandoned=void 0,r.handler=(E,v)=>{v.statusCode=503,v.end("closed")},r.gen!==void 0&&k.closed(r.gen,se,Ee(h,r.module)),r.module=void 0}if(r.superseded){try{fe((0,hot_swap_registries_1.pruneRegistries)(r.superseded,"serve"),"after closing the previous app")}catch(E){console.error("[child] could not release the previous app's class registrations:",E)}r.superseded=void 0}ae("after closing the previous app")}u(ce,"me"),a(ce,"closeGeneration");async function le(r){const c=Date.now();r.drainingSince=c;const l=a(()=>({inflight:r.inflight+V(r),work:C(r.workGen).count}),"busy"),h=l();if(h.inflight>0||h.work>0){for(;F.size>=ke;){const I=F.values().next().value;if(!I)break;const $=oe(I),z=Math.round((Date.now()-(I.drainingSince??c))/1e3);if($.unanswered===0&&$.streams===0&&$.work===0){await ce(I);continue}await ce(I,`closing a still-draining generation early \u2014 ${F.size} were pending and the cap is ${ke} (each holds a whole app: DI container, DB pool, queue workers). `+($.unanswered>0||$.work>0?`It was replaced ${z}s ago and had still not answered ${ne($.unanswered,"request")}`+($.work?` or finished ${ne($.work,"background job")}`:"")+` \u2014 those fail now (a queue job keeps its lock until the TTL lapses). Raise LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS (currently ${Z}ms) to wait longer for them. `:`It held only ${ne($.streams,"open stream")}; their clients reconnect to the new app. `)+"Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.")}F.add(r)}for(;_e>0&&Date.now()-c<_e;){if(r.closed)return;const I=l();if(I.inflight===0&&I.work===0)break;await new Promise($=>{setTimeout($,250).unref?.()})}if(r.closed)return;const E=l(),v=ge(r.workGen);E.inflight>0||E.work>0?console.error(`[child] swap drain expired after ${Date.now()-c}ms \u2014 closing the previous app with ${E.inflight} in-flight request(s) and ${E.work} background job(s) still running. `+(v?`Previous app still processing ${v.running} queue job(s); ${v.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${_e}ms) if this is legitimate work.`):(Date.now()-c>250||v?.runningAtStop||v?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-c}ms \u2014 closing it`+(v?` (it finished ${v.finishedAfterStop} queue job(s) after the swap; ${v.takenAfterStop} were taken after the swap)`:"")),await ce(r)}u(le,"ye"),a(le,"drainThenClose");async function de(){if(m)return re=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),f;m=!0,D=Date.now(),await We(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(P,ee)=>{console.error(`[child] uncaughtException (${ee}):`,P)}),process.on("unhandledRejection",P=>{const ee=P instanceof Error?P.stack||P.message:String(P);ee.includes("FronteggContextScope")||ee.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",P)});let r;if(Ve)try{r=(0,hot_swap_registries_1.captureRegistries)()}catch{r=void 0}const c=global.__lensmcpBeginWorkGeneration,l=typeof c=="function"?c():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(P){if(!(P instanceof SyntaxError))throw P;console.warn(`[child] bundle was unreadable mid-write (${P.message}) \u2014 retrying the swap once`),await new Promise(ee=>setTimeout(ee,400)),await importFresh(BUNDLE_PATH)}}catch(P){throw m=!1,D=Date.now(),P}if(typeof global.createChildApp!="function")throw m=!1,D=Date.now(),new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);let h;if(r)try{h=(0,hot_swap_registries_1.supersededSince)(r),fe((0,hot_swap_registries_1.pruneRegistries)(h,"build"),"at the swap")}catch(P){console.error("[child] could not release superseded class registrations:",P)}let E;try{E=await global.createChildApp()}catch(P){throw m=!1,D=Date.now(),takeImportedModule(),P}const v=j;h?.size&&(v.app?v.superseded=h:fe((0,hot_swap_registries_1.pruneRegistries)(h,"serve"),"at the swap")),f=E;const I=takeImportedModule();j={app:E,handler:E.handler,inflight:0,gen:++K,...I?{module:I}:{},...l===void 0?{}:{workGen:l}};const $=Ee(E,void 0).container;$&&(x.add(new WeakRef($)),me=!0);const z=v.app&&l!==void 0?$e(l):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(z?.workers?`; its ${z.workers} queue worker(s) stopped taking new jobs, previous app still processing ${z.runningAtStop} job(s)`:"")),Te(),v.app&&(se+=1,le(v).catch(P=>console.error("[child] background close error:",P)),setTimeout(()=>X("30s after a hot swap"),3e4).unref?.(),k.swapped(v.gen,()=>v.closed===!0&&!J.has(v))),m=!1,D=Date.now(),re?(re=!1,de()):E}u(de,"Ee"),a(de,"swapNow");const Pe=debounce(async()=>{try{console.log("[child] /webpack/reload"),await de()}catch(r){console.error("[child] reload failed:",r)}},250),pe=http.createServer((r,c)=>{if(r.method==="POST"&&r.url==="/webpack/reload"){c.writeHead(200,{"content-type":"application/json"}),c.end(JSON.stringify({ok:!0})),Pe();return}if(r.method==="GET"&&r.url==="/__lensmcp/alive"){const h=r.headers["x-internal-token"],E=process.env.LENSMCP_SERVICE_KEY;if(E&&h!==E){c.writeHead(404,{"content-type":"text/plain"}),c.end("not found");return}const v=C();c.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),c.end(JSON.stringify({ok:!0,pid:process.pid,swapping:m,inflight:j.inflight,oldestInflightAt:j.oldestAt,activeWork:v.count,oldestWorkAt:v.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...W?{watcher:W}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const l=j;l.inflight+=1,(l.open??=new Set).add(c),l.oldestAt===void 0&&(l.oldestAt=Date.now()),c.on("close",()=>{l.inflight-=1,l.open?.delete(c),Z>0&&!l.closed&&!c.headersSent&&!c.writableEnded&&((l.abandoned??=new Map).set(c,Date.now()),l.abandoned.size>256&&V(l)),l.inflight===0&&(l.oldestAt=void 0),k.activity()}),l.handler(r,c)});pe.keepAliveTimeout=0;const N=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock(N),process.exit(0)});try{const r=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const l=(0,schema_first_instance_1.prewarmZodFor)(r);l?.created&&console.log(`[child] zod${l.version?` ${l.version}`:""}: built ${l.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const l=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(r);l?.patched&&console.log(`[child] ioredis${l.versions.length?` ${l.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await de(),we=(await k.booted()).live,console.log(`[child] Using bundle: ${r}`),claimSock(N);let c=!1;pe.on("error",l=>{if(c){console.error("[child] server error:",l);return}console.error(`[child] could not listen on ${N} (${l.code??l.message}) \u2014 exiting; the devserver starts another pod`),tell(process,{type:"boot-error",error:`could not listen on ${N}: ${String(l?.stack||l)}`}),cleanupSock(N),process.exitCode=1,setTimeout(()=>process.exit(1),100)}),pe.listen(N,()=>{c=!0,console.log(`[child] Listening on ${N}`),tell(process,{type:"ready",socketPath:N})})}catch(r){console.error("[child] Startup error:",r),tell(process,{type:"boot-error",error:String(r?.stack||r)}),process.exitCode=1;return}process.on("message",async r=>{if(!(!r||typeof r!="object")){if(r.type==="debug-open"){const c=Number(r.port);d(Number.isInteger(c)&&c>1024?c:0);return}if(r.type==="watcher"){const c=r.testimony;c&&typeof c=="object"&&(W=c);return}if(r.type==="heap-snapshot"){Me(r);return}if(r.type==="recycle-declined"){g=!1;return}if(r.type==="retire"){q();return}if(r.type==="reload"){if(b)return;try{const c=Date.now();await de(),tell(process,{type:"reloaded",socketPath:N,swap:{generation:K,swaps:se,swapMs:Date.now()-c,draining:F.size}})}catch(c){console.error("[child] reload error:",c),tell(process,{type:"reload-error",error:String(c?.stack||c)})}}}});let ve=!1;async function Me(r){const c=a(l=>{tell(process,{type:"heap-snapshot-result",id:r.id,...l})},"reply");if(ve)return c({ok:!1,refused:"a snapshot of this pod is already being written"});ve=!0;try{const l=H(),h=k.tracker.views().map($=>$.gen),E=(0,heap_snapshot_1.heapSnapshotPolicy)();r.maxHeapBytes&&(E.maxHeapBytes=r.maxHeapBytes);const v=(0,heap_snapshot_1.heapSnapshotRefusal)(E,{live:l,...r.available?{available:r.available}:{}});if(v)return c({ok:!1,refused:v,live:l});console.log(`[child] writing a heap snapshot (${Math.round(l/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const I=await(0,heap_snapshot_1.writeHeapSnapshot)(r.file);c({ok:!0,file:r.file,bytes:I.bytes,ms:I.ms,live:l,gen:j.gen??0,closedAlive:h})}catch(l){c({ok:!1,error:l?.message??String(l)})}finally{ve=!1}}u(Me,"Ye"),a(Me,"takeHeapSnapshot");async function q(r="a fresh pod has taken over"){if(b)return;b=!0,k.stop(),console.log(`[child] retiring \u2014 ${r}; ${path.basename(N,".sock")} (pid ${process.pid}) is finishing in-flight work, then exiting`);const c=$e(Number.POSITIVE_INFINITY);c?.workers&&console.log(`[child] its ${c.workers} queue worker(s) stopped taking new jobs, still processing ${c.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath(N),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(h=>{setTimeout(h,Je).unref?.()}),cleanupSock(N),pe.close();const l=Date.now();for(;Date.now()-l<_e;){pe.closeIdleConnections?.();let h=0;for(const E of F)h+=oe(E).unanswered;if(j.inflight+V(j)===0&&h===0&&C().count===0)break;await new Promise(E=>{setTimeout(E,250).unref?.()})}setTimeout(()=>process.exit(0),3e4).unref?.();try{await f?.close()}catch(h){console.error("[child] close error while retiring:",h)}process.exit(0)}u(q,"B"),a(q,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const ue=a(async r=>{console.log(`[child ${r}] shutting down\u2026`);const c=f?.close().catch(h=>console.error("[child] close error:",h)),l=new Promise(h=>pe.close(()=>h()));cleanupSock(N),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([c,l]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(r=>{process.on(r,()=>{if(r==="SIGTERM"&&!b&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){q("this generation was handed over to a newer one");return}inspector?.close?.(),ue(r)})})})()}else{let d=u(function(g){V.push(g),V.length>Z&&V.splice(0,V.length-Z),ke?.write((0,inspector_secret_1.redactInspectorUrls)(g.replace(F,"")));for(const b of oe)try{const S=b.plain?g.replace(F,""):g;b.res.write(b.redact?(0,inspector_secret_1.redactInspectorUrls)(S):S)}catch{oe.delete(b)}},"we");a(d,"emitLogLine");const _=12,f=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",m=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),W=a(g=>({project:f,service:SERVICE_NAME||f,pod:g.id,pid:g.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),re=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],Ve="\x1B[0m",fe=Math.max(1,Number(process.env.CHILD_COUNT||1)),j=LENSMCP_WS_KEY||path.basename(process.cwd()),_e=a(g=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(j,SERVICE_NAME||"service",g)),"execPathFor");let C=(()=>{const g=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(g)&&g>1024?g:0})();const $e=20;C&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/closed/codex-security-06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${C}+slot, workers ${C}+${$e}+i). Any account on this host can connect to 127.0.0.1:${C} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const ge=process.env.LENSMCP_LOG_FILE||"",ke=ge?(()=>{try{fs.mkdirSync(path.dirname(ge),{recursive:!0});const g=fs.createWriteStream(ge,{flags:"a",mode:384});try{fs.chmodSync(ge,384)}catch{}return g}catch(g){return console.warn(`[parent] cannot open log file ${ge}:`,g.message),null}})():null,F=/\x1b\[[0-9;]*m/g,Z=2e3,V=[],oe=new Set,ne=(0,devserver_transport_1.gatewayManaged)();for(const g of["log","info","warn","error"]){const b=console[g].bind(console),S=g==="log"||g==="info"?process.stdout:process.stderr;console[g]=(...K)=>{b(...K.map(x=>typeof x=="string"?(0,inspector_secret_1.forStream)(S,ne,x):x)),d(K.map(x=>typeof x=="string"?x:util.inspect(x)).join(" ")+`
13
+ `)}}const We=a(g=>re[(g-1)%re.length],"colorFor"),O=a(g=>{if(fe===1)return"";const b=`[child#${g}] `;return`${We(g)}${b}${Ve}`},"tagFor"),we=a(g=>g.startsWith("{")&&g.endsWith("}"),"isJsonLog"),se=a((g,b,S)=>{const K=prettyFactory({sync:!0,colorize:!0,crlf:!0,messageKey:"message",errorLikeObjectKeys:["err","error"],errorProps:"type,message,stack",ignore:["logContext","context","hostname","req","res","err.driverError","module","cloudEnvironment","frontegg-application-id","frontegg-tenant-id","frontegg-trace-id","frontegg-vendor-id","host","service","version","err","error"].join(","),messageFormat:"{if module}[{module}] {end}{if context}[{context}] {end}{if logContext}[{logContext}] {end}{message}",customPrettifiers:{stack:a(B=>`
14
+ `+String(B),"stack")}}),x=a((B,k)=>{const H=B==="stdout"?process.stdout:process.stderr;try{S?.(k)}catch{}const U=we(k)?K(k):`${k}
15
+ `;H.write((0,inspector_secret_1.forStream)(H,ne,`${b}${U}`)),d(`${b}${U}`)},"write"),me=a((B,k)=>{if(!B)return;const H=readline.createInterface({input:B});H.on("line",U=>x(k,U)),H.on("close",()=>{})},"attach");me(g.stdout,"stdout"),me(g.stderr,"stderr")},"wireProcLogging"),Je=a(g=>se(g.proc,O(g.id),b=>g.tail.push(b)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const g=httpProxy.createProxyServer({});let b=Number(process.env.PORT)||0;b||(b=9090);const S=[],K=new Set;function x(){let e=0;for(;K.has(e);)e++;return K.add(e),e}u(x,"D"),a(x,"allocDebugSlot");let me=1,B=0,k=!1,H=fe,U=[],ie=0;const Te=(0,swap_mode_1.swapMode)(),J=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),D=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),Fe=a(e=>e.booting&&!D(e)&&Date.now()-e.spawnedAt<J,"stillBooting");function Y(){return S.filter(e=>e.healthy&&typeof e.socketPath=="string")}u(Y,"J"),a(Y,"healthyChildren");let ae,be=0,X;const ye={project:f,service:SERVICE_NAME||f,podGeneration:POD_GENERATION};function Oe(e){const t=(0,service_build_events_1.buildAccountOf)(e.building);t&&t.startedAt!==be&&(be=t.startedAt,m.publish((0,service_build_events_1.buildServiceBuildEvent)(ye,"started",t,{root:process.cwd()})));const s=(0,service_build_events_1.buildAccountOf)(e.failed);s&&(be=0,m.publish((0,service_build_events_1.buildServiceBuildEvent)(ye,"failed",s,{root:process.cwd()})))}u(Oe,"je"),a(Oe,"recordBuildProgress");function Ee(e){if(!e||typeof e!="object")return;const t=e;if(t.schemaVersion===1){Oe(t),"failed"in t&&delete t.failed,ae=t;for(const s of S)tell(s.proc,{type:"watcher",testimony:t})}}u(Ee,"Ne"),a(Ee,"recordWatcherTestimony");function ce(e,t=256*1024){return new Promise(s=>{let n="",i=!1;e.on("data",o=>{i||(n+=o,n.length>t&&(i=!0))}),e.on("end",()=>{if(i||!n)return s(void 0);try{s(JSON.parse(n))}catch{s(void 0)}}),e.on("error",()=>s(void 0))})}u(ce,"me"),a(ce,"readJsonBody");function le(){const e=Y();if(e.length===0)return null;const t=B%e.length;return B=(B+1)%e.length,e[t]}u(le,"ye"),a(le,"pickChild");function de(e,t){const s=`Child#${e.id}`,n=SERVICE_NAME||f,i=Date.now();if(t.type==="pod-memory"){const o=t.report;if(!o||typeof o.live!="number")return;e.memory.latest=o,e.memory.history.push(o),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(s,o)}`),m.publish((0,pod_memory_events_1.buildPodMemoryEvent)(W(e),o))}else if(t.type==="pod-memory-retained"){const o=t.retained;if(!o||typeof o.gen!="number")return;e.memory.retained.set(o.gen,{...o,at:i}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(s,n,o)}`),m.publish((0,pod_memory_events_1.buildPodRetainedEvent)(W(e),o,i))}else{const o=t.released;if(!o||typeof o.gen!="number")return;e.memory.retained.delete(o.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(s,n,o)}`),m.publish((0,pod_memory_events_1.buildPodReleasedEvent)(W(e),o,i))}}u(de,"Ee"),a(de,"recordPodMemory");const Pe=new Map;let pe=0;async function N(){const e=me++,t=childSockPath(e);cleanupSock(t);const s=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&s.unshift("--require","@swc-node/register"),s.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const n=C?x():-1,i=(0,node_child_process_1.fork)(__filename,{env:(0,child_process_env_1.childProcessEnv)({...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:t,...n>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(C,n))}}),stdio:["inherit","pipe","pipe","ipc"],execArgv:s,execPath:_e("pod")}),o={id:e,proc:i,healthy:!1,lastError:null,debugSlot:n,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:ie,memory:{history:[],retained:new Map}};return i.on("error",y=>console.error(`[parent] Child#${e}: ${y.message}`)),i.on("message",y=>{if(!y||typeof y!="object")return;const p=y;if(p.type==="ready")o.socketPath=String(p.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),ae&&tell(i,{type:"watcher",testimony:ae}),ie!==o.bootReloadSeq&&(Te==="process"?P():tell(i,{type:"reload"}));else if(p.type==="recycle-request")v(o,String(p.reason||"it has outgrown its bound"));else if(p.type==="inspector-url")o.inspectorUrl=String(p.url),console.log(`[parent] Child#${o.id} Debugger listening on ${p.url}`);else if(p.type==="reloaded"){if(q.has(o)){console.log(`[parent] Child#${o.id} finished a swap it began before it was retired \u2014 not serving it: a fresh pod has taken over`);return}o.socketPath=String(p.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`);const w=p.swap;w&&typeof w.generation=="number"&&typeof w.swaps=="number"&&m.publish((0,service_build_events_1.buildServiceSwapEvent)(W(o),{generation:w.generation,swaps:w.swaps,swapMs:typeof w.swapMs=="number"?w.swapMs:0,draining:typeof w.draining=="number"?w.draining:0},X,{root:process.cwd()}))}else p.type==="reload-error"?(o.lastError=String(p.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):p.type==="boot-error"?(o.lastError=String(p.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1):p.type==="pod-memory"||p.type==="pod-memory-retained"||p.type==="pod-memory-released"?de(o,p):p.type==="heap-snapshot-result"&&Pe.get(Number(p.id))?.(p)}),i.on("exit",(y,p)=>{o.booting=!1;for(const R of o.memory.retained.values()){const M={gen:R.gen,closedAt:R.closedAt,ageMs:Date.now()-R.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${o.id}`,SERVICE_NAME||f,M)}`),m.publish((0,pod_memory_events_1.buildPodReleasedEvent)(W(o),M,Date.now()))}o.memory.retained.clear(),o.memory.latest&&m.publish((0,pod_memory_events_1.buildPodExitedEvent)(W(o),{code:y,signal:p},Date.now())),o.debugSlot>=0&&(K.delete(o.debugSlot),o.debugSlot=-1);const w=p==="SIGINT"||p==="SIGTERM"||y===0;if(w?console.log(`[parent] Child#${o.id} exited cleanly (code=${y}, signal=${p??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${y}, signal=${p??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,y,p,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${y}, signal=${p??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!w&&!k){const R=S.indexOf(o);R>=0&&S.splice(R,1);const M=Date.now();if(U=U.filter(T=>M-T<3e4),U.length>=5){console.error(`[parent] crash-loop detected (${U.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}U.push(M),setTimeout(()=>{const T=S.filter(Q=>Q.proc.killed===!1).length;!k&&T<H&&(console.log(`[parent] respawning a crashed pod (${T}/${H} live)`),N().catch(()=>{}))},1500).unref?.()}}),S.push(o),Je(o),o}u(N,"N"),a(N,"spawnChild");async function ve(e){H=Math.max(H,e);const t=S.filter(n=>n.proc.killed===!1),s=e-t.length;for(let n=0;n<s;n++)await N()}u(ve,"be"),a(ve,"ensurePoolSize");async function Me(){const e=S.filter(t=>(!t.healthy||typeof t.socketPath!="string")&&!Fe(t));await Promise.all(e.map(async t=>{try{t.proc.kill("SIGTERM")}catch{}cleanupSock(t.socketPath,t.proc.pid),t.socketPath=void 0;const s=S.indexOf(t);s>=0&&S.splice(s,1),await N()}))}u(Me,"Ye"),a(Me,"respawnUnhealthy");const q=new Set;let ue=!1,r=0;const c=3e4,l=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function h(e,t={}){const s=Date.now(),n=await N(),i=Date.now()+J;for(;!n.healthy&&!n.lastError&&!D(n)&&Date.now()<i;){if(t.superseded?.()){const o=S.indexOf(n);o>=0&&S.splice(o,1);try{n.proc.kill("SIGTERM")}catch{}return{why:`a newer build landed while Child#${n.id} booted`,superseded:!0}}await new Promise(o=>{setTimeout(o,100).unref?.()})}if(k)return{why:"shutting down"};if(!n.healthy){const o=S.indexOf(n);o>=0&&S.splice(o,1);try{n.proc.kill("SIGTERM")}catch{}return{why:`its successor Child#${n.id} did not come up (${n.lastError??"no answer in time"})`}}return E(e),{fresh:n,readyMs:Date.now()-s}}u(h,"_"),a(h,"replacePod");function E(e){const t=S.indexOf(e);if(t>=0&&S.splice(t,1),e.healthy=!1,D(e))return;q.add(e),postRetiringNotice(e.socketPath,e.proc.pid);const s=setTimeout(()=>{if(!D(e)){console.error(`[parent] Child#${e.id} was still running ${l}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},l);if(s.unref?.(),e.proc.once("exit",()=>{clearTimeout(s),q.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}}u(E,"m"),a(E,"retirePod");async function v(e,t){const s=a(i=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${t}) \u2014 not now: ${i}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(k||!S.includes(e))return;if(ue)return s("another pod is already being replaced");const n=Date.now()-r;if(n<c)return s(`a pod was replaced ${Math.round(n/1e3)}s ago`);ue=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${t}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const i=await h(e);if("why"in i){i.why!=="shutting down"&&s(i.why);return}r=Date.now(),console.log(`[parent] Child#${i.fresh.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${i.fresh.proc.pid})`)}catch(i){s(`could not start a successor: ${i?.message??String(i)}`)}finally{ue=!1}}u(v,"y"),a(v,"recyclePod");let I,$=!1,z=0;function P(){if(I){$=!0;return}I=(async()=>{do{$=!1;const e=ie;for(;ue&&!k;)await new Promise(s=>{setTimeout(s,100).unref?.()});let t=!1;for(const s of Y().filter(n=>n.bootReloadSeq<e)){if(k)break;if(!(!S.includes(s)||!s.healthy)){ue=!0;try{ee(),console.log(`[parent] A new build \u2014 starting a fresh pod for it; Child#${s.id} keeps serving the previous build until it is ready.`);const n=await h(s,{superseded:a(()=>ie!==e&&(0,swap_mode_1.abandonSuperseded)(z),"superseded")});if("why"in n){if(n.superseded){z+=1,console.log(`[parent] ${n.why} \u2014 abandoned; one fresh pod for the newest build instead (${z} in a row). Child#${s.id} keeps serving.`),t=!0;break}n.why!=="shutting down"&&console.error(`[parent] Child#${s.id} keeps serving the previous build \u2014 ${n.why}`);continue}z=0,console.log(`[parent] Child#${n.fresh.id} took over from Child#${s.id} with the new build (pid ${s.proc.pid} \u2192 ${n.fresh.proc.pid}, ready in ${(n.readyMs/1e3).toFixed(1)}s while Child#${s.id} served)`),m.publish((0,service_build_events_1.buildServiceSwapEvent)(W(n.fresh),{generation:1,swaps:0,swapMs:n.readyMs,draining:q.size,handoff:{fromPid:s.proc.pid??0}},X,{root:process.cwd()}))}catch(n){console.error(`[parent] Child#${s.id} keeps serving the previous build \u2014 could not start a successor: ${n?.message??String(n)}`)}finally{ue=!1}}}t&&($=!0)}while($&&!k)})().finally(()=>{I=void 0})}u(P,"A"),a(P,"handoffForReload");function ee(){const e=(0,swap_mode_1.maxRetiring)(),t=[...q].filter(s=>!D(s));for(;t.length>=e;){const s=t.shift();console.error(`[parent] Child#${s.id} (pid ${s.proc.pid}) was still finishing its work when a newer build replaced its successor too \u2014 stopping it (LENSMCP_SWAP_MAX_RETIRING=${e})`);try{s.proc.kill("SIGTERM")}catch{}}}u(ee,"q"),a(ee,"capRetiring");const G=[],He=new Map,Ue=a(e=>{let t=He.get(e);return t||He.set(e,t={crashes:0,restarts:0}),t},"watchOf"),Xe=(0,worker_supervisor_1.workerBackoff)(),Re=a((e,t)=>({project:f,service:SERVICE_NAME||f,worker:e,pid:t,podGeneration:POD_GENERATION}),"workerWho"),tt=[path.join(__dirname,"worker-guard.js"),path.join(__dirname,"worker-guard.ts")].find(e=>fs.existsSync(e));for(const e of WORKER_PROBLEMS)console.error(`[parent] ${e}`);function ze(e,t){const s=e.name,n=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),i=path.join(n,`${s}.js`),o=Ue(s);if(!fs.existsSync(i))return console.error(`[parent] Worker "${s}" bundle not found: ${i}`),o.missing=i,m.publish((0,worker_supervisor_1.buildWorkerExitedEvent)(Re(s,0),{ranMs:0,reason:`its bundle was not built (${path.basename(i)} is not in ${n})`})),null;o.missing=void 0;const y=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&y.unshift("--require","@swc-node/register"),tt&&y.push("--require",tt);try{y.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const p=inspectorPortFor(C||9229,$e+Math.max(0,WORKER_NAMES.indexOf(s)));y.push(C?`--inspect=127.0.0.1:${p}`:`--inspect-port=127.0.0.1:${p}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const w=(0,node_child_process_1.fork)(i,{env:(0,worker_supervisor_1.workerEnv)(process.env,e),stdio:["inherit","pipe","pipe","ipc"],execArgv:y,execPath:_e(`w-${s}`)}),R={name:s,spec:e,proc:w,debugPort:p,startedAt:Date.now(),tail:new exit_reason_1.TailBuffer,stopping:!1};G.push(R),w.on("error",T=>console.error(`[parent] Worker "${s}": ${T.message}`)),se(w,`\x1B[35m[worker:${s}] \x1B[0m`,T=>{R.tail.push(T);const Q=/Debugger listening on (wss?:\/\/\S+)/.exec(T);Q&&(R.inspectorUrl=Q[1])});const M=setTimeout(()=>{if(!(Ae(R)||R.stopping)){if(o.crashes>0){const T=Date.now()-R.startedAt;console.log(`[parent] Worker "${s}" (pid=${w.pid}) is up again \u2014 ${(0,worker_supervisor_1.spanOf)(T)} without a crash, after ${o.crashes} in a row`),m.publish((0,worker_supervisor_1.buildWorkerUpEvent)(Re(s,w.pid??0),{ranMs:T,afterCrashes:o.crashes}))}o.crashes=0}},Xe.stableMs);return M.unref?.(),w.on("exit",(T,Q)=>{clearTimeout(M);const Se=G.indexOf(R);Se>=0&&G.splice(Se,1),rt(R,T,Q)}),console.log(`[parent] Worker "${s}" started (pid=${w.pid}, PORT=${(0,worker_supervisor_1.workerEnv)(process.env,e).PORT}${t==="respawn"?`, restart ${o.restarts}`:""})`),m.publish((0,worker_supervisor_1.buildWorkerStartedEvent)(Re(s,w.pid??0),{why:t,port:e.port,restarts:o.restarts})),R}u(ze,"Ze"),a(ze,"spawnWorkerProcess");function rt(e,t,s){const{name:n}=e,i=e.proc.pid??0,o=Date.now()-e.startedAt,y=(0,worker_supervisor_1.workerExitVerdict)(t,s,e.stopping||k);if(y.kind==="stopped")return;const p=Ue(n);if(y.kind==="exited"){p.lastEnd={at:Date.now(),pid:i,code:t,signal:s,ranMs:o,verdict:"exited"},console.warn(`[parent] Worker "${n}" (pid=${i}) exited on its own (code 0) after ${(0,worker_supervisor_1.spanOf)(o)} \u2014 not started again until the next build: a worker that ends is taken to have finished its work`),m.publish((0,worker_supervisor_1.buildWorkerExitedEvent)(Re(n,i),{ranMs:o}));return}p.crashes+=1,p.lastEnd={at:Date.now(),pid:i,code:t,signal:s,ranMs:o,verdict:"crashed"};const w=(0,worker_supervisor_1.respawnDelay)(p.crashes,Xe),R=p.crashes>=worker_supervisor_1.CRASH_LOOP_AFTER;console.error((0,exit_reason_1.crashReport)(`[parent] Worker "${n}" crashed (code=${t}, signal=${s??"none"}, pid=${i}, after ${(0,worker_supervisor_1.spanOf)(o)})${y.external?" \u2014 stopped by a signal from outside its devserver":""}; starting it again in ${(0,worker_supervisor_1.spanOf)(w)}${R?` \u2014 ${p.crashes} crashes in a row: it is crash-looping (the wait doubles, up to ${(0,worker_supervisor_1.spanOf)(Xe.maxMs)})`:""}`,t,s,e.tail.snapshot())),m.publish((0,worker_supervisor_1.buildWorkerCrashedEvent)(Re(n,i),{code:t,signal:s,ranMs:o,crashes:p.crashes,restarts:p.restarts,nextInMs:w,external:y.external,lines:e.tail.snapshot()})),clearTimeout(p.pending),p.nextAt=Date.now()+w,p.pending=setTimeout(()=>{p.pending=void 0,p.nextAt=void 0,!(k||G.some(M=>M.name===n))&&(p.restarts+=1,qe(e.spec,"respawn"))},w),p.pending.unref?.()}u(rt,"ct"),a(rt,"workerEnded");function qe(e,t){const s=Ue(e.name);s.waiting=void 0;const n=e.port>0?[...te].find(o=>o.name===e.name&&!Ae(o)):void 0;if(!n){ze(e,t);return}console.log(`[parent] Worker "${e.name}" starts once its predecessor (pid=${n.proc.pid}) has finished its work \u2014 it holds port ${e.port}`);const i=a(()=>{s.waiting===i&&(s.waiting=void 0),!k&&!G.some(o=>o.name===e.name)&&ze(e,t)},"go");s.waiting=i,n.proc.once("exit",i)}u(qe,"Ve"),a(qe,"startWorker");const ot=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),nt=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),te=new Set,Ae=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function Be(e,t){if(te.delete(e),!Ae(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${t} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}u(Be,"Je"),a(Be,"killDrainingWorker");function Qe(e){e.stopping=!0,tell(e.proc,{type:worker_retire_1.RETIRE_MESSAGE});try{e.proc.kill("SIGTERM")}catch{}if(Ae(e))return;const t=[...te].filter(n=>n.name===e.name);for(;t.length>=nt;)Be(t.shift(),`was still running when a later reload replaced its successor (cap ${nt})`);te.add(e);const s=setTimeout(()=>Be(e,`was still running ${ot}ms after SIGTERM`),ot);s.unref?.(),e.proc.once("exit",()=>{clearTimeout(s),te.delete(e)})}u(Qe,"rt"),a(Qe,"retireWorker");function je(){for(const e of He.values())clearTimeout(e.pending),e.pending=void 0,e.nextAt=void 0,e.waiting=void 0}u(je,"qe"),a(je,"cancelWorkerStarts");function st(){for(const e of[...G])Qe(e);G.length=0,je();for(const e of WORKER_SPECS)Ue(e.name).crashes=0,qe(e,"reload")}u(st,"lt"),a(st,"restartWorkers");function it(){const e=Date.now();return{workers:WORKER_SPECS.map(t=>{const s=G.find(o=>o.name===t.name),n=He.get(t.name),i=s?"running":n?.pending?(n.crashes??0)>=worker_supervisor_1.CRASH_LOOP_AFTER?"crash-looping":"restarting":n?.waiting?"waiting":n?.missing?"missing":n?.lastEnd?.verdict==="exited"?"exited":"stopped";return{name:t.name,state:i,port:t.port,env:Object.keys(t.env),...s?{pid:s.proc.pid,upMs:e-s.startedAt}:{},crashes:n?.crashes??0,restarts:n?.restarts??0,...n?.nextAt?{nextInMs:Math.max(0,n.nextAt-e)}:{},...n?.lastEnd?{lastEnd:n.lastEnd}:{},...n?.missing?{missing:n.missing}:{}}}),draining:[...te].filter(t=>!Ae(t)).map(t=>({name:t.name,pid:t.proc.pid}))}}u(it,"dt"),a(it,"workersView");const ht=debounce(async()=>{ie+=1;const e=Y();if(e.length>0)if(Te==="process")P();else{console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const t of e)tell(t.proc,{type:"reload"})}await Me(),WORKER_NAMES.length>0&&st()},200),at=a((e,t)=>{const s=(0,notice_generation_1.noticeGeneration)(e.headers);return s===void 0||s===POD_GENERATION?!1:(e.resume(),t.writeHead(409,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!1,generation:POD_GENERATION,sentFor:s})),e.url==="/webpack/reload"&&console.log(`[parent] a build of generation g${s} reached generation g${POD_GENERATION} \u2014 not reloaded: it is not this generation's build`),!0)},"refusedForAnotherGeneration"),Ce=a(async(e,t)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);if(at(e,t))return;t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0})),ce(e).then(i=>{const o=i;X=(0,service_build_events_1.buildAccountOf)(o?.build),be=0,Ee(o?.watcher)}),ht();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);if(at(e,t))return;const i=await ce(e);Ee(i),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,relayed:ae!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,t);let i="";e.on("data",o=>{i+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(i||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await ve(o),t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const i=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(i.get("tail")??"100")||0,0),Z),y=i.get("follow")!=="0",p=i.get("plain")==="1",w=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);t.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const R=o===0?"":V.slice(-o).join(""),M=p?R.replace(F,""):R;if(t.write(w?(0,inspector_secret_1.redactInspectorUrls)(M):M),!y){t.end();return}const T={res:t,plain:p,redact:w};oe.add(T),e.on("close",()=>oe.delete(T));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const i=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(i)&&i>1024&&(C=i),C||(C=9229);const o=S.filter(p=>p.proc.exitCode===null&&p.proc.signalCode===null);for(const p of o)p.debugSlot<0&&(p.debugSlot=x()),tell(p.proc,{type:"debug-open",port:inspectorPortFor(C,p.debugSlot)});for(const p of G)try{p.proc.kill("SIGUSR1")}catch{}const y=Date.now()+2e3;for(;Date.now()<y&&o.some(p=>!p.inspectorUrl);)await new Promise(p=>setTimeout(p,50));t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify({ok:!0,basePort:C,fixedPorts:usesFixedInspectorPorts(),pods:o.map(p=>({id:p.id,inspectorUrl:p.inspectorUrl??null})),workers:G.map(p=>({name:p.name,port:p.debugPort||(0,inspector_secret_1.inspectorPortOf)(p.inspectorUrl),inspectorUrl:p.inspectorUrl??null}))},null,2)+`
16
16
  `);return}if(e.method==="GET"&&e.url==="/webpack/workers"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}t.writeHead(200,{"content-type":"application/json"}),t.end(JSON.stringify(it(),null,2)+`
17
- `);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const s=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),o=[...v,...q].map(y=>({id:y.id,pid:y.proc.pid??0,podGeneration:POD_GENERATION,healthy:y.healthy,retiring:q.has(y),latest:y.memory.latest??null,history:y.memory.history,retained:[...y.memory.retained.values()]}));t.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),t.end(JSON.stringify({ok:!0,service:SERVICE_NAME,project:f,bundle:{path:s},pods:o},null,2)+`
18
- `);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const s=a((L,Le)=>{t.writeHead(L,{"content-type":"application/json"}),t.end(JSON.stringify(Le,null,2)+`
19
- `)},"reply"),o=new URL(e.url,"http://localhost").searchParams,y=Number(o.get("pod")||""),p=o.has("maxMb")?Number(o.get("maxMb")):void 0;if(p!==void 0&&!(Number.isFinite(p)&&p>0)){s(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const w=Number.isInteger(y)&&y>0?v.find(L=>L.id===y):Y()[0];if(!w||D(w)){s(404,{ok:!1,error:y?`no pod #${y} \u2014 running: ${v.map(L=>`#${L.id}`).join(", ")||"none"}`:"no pod is serving"});return}if(Pe.size>0){s(409,{ok:!1,refused:"a heap snapshot is already being written for this service"});return}const A=(0,heap_snapshot_1.heapSnapshotPolicy)(),M=(0,heap_snapshot_1.heapSnapshotDir)();let T;try{(0,heap_snapshot_1.ensureHeapDir)(M);const L=(0,heap_snapshot_1.pruneHeapSnapshots)(M,A);L.length&&console.log(`[parent] removed ${L.length} older heap snapshot(s) from ${M}`),T=path.join(M,(0,heap_snapshot_1.heapSnapshotFileName)(SERVICE_NAME||f,w.id,w.proc.pid??0,w.memory.latest?.gen??0,Date.now()))}catch(L){s(500,{ok:!1,error:`cannot prepare ${M}: ${L.message}`});return}const Q=++pe,ve=(0,heap_snapshot_1.availableSystemMemory)();let Ge;const R=await new Promise(L=>{const Le=setTimeout(()=>L(void 0),6e5);Le.unref?.(),Pe.set(Q,mt=>{clearTimeout(Le),L(mt)}),Ge=a(()=>{clearTimeout(Le),L(void 0)},"onExit"),w.proc.once("exit",Ge);const wt={type:"heap-snapshot",id:Q,file:T,...ve?{available:ve}:{},...p!==void 0?{maxHeapBytes:Math.floor(p*1048576)}:{}};tell(w.proc,wt)||Ge()}).finally(()=>{Pe.delete(Q),Ge&&w.proc.off("exit",Ge)});if(!R){s(504,{ok:!1,pod:w.id,error:"the pod did not finish the snapshot (it exited, or took longer than 10 minutes)"});return}R.ok?console.log(`[parent] Child#${w.id} wrote a ${Math.round((R.bytes??0)/1048576)} MB heap snapshot in ${R.ms} ms \u2192 ${R.file}`):console.warn(`[parent] Child#${w.id} heap snapshot not taken: ${R.refused??R.error}`),s(R.ok?200:R.refused?409:500,{ok:R.ok,pod:w.id,pid:w.proc.pid??0,service:SERVICE_NAME,bundle:path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),...ve?{available:ve}:{},...R.file?{file:R.file,bytes:R.bytes,ms:R.ms}:{},...R.live!==void 0?{live:R.live}:{},...R.gen!==void 0?{gen:R.gen,closedAlive:R.closedAlive??[]}:{},...R.refused?{refused:R.refused}:{},...R.error?{error:R.error}:{}});return}const n=matchGatewayRoute(e);if(n?.prependPrefix&&!e.url?.startsWith(n.prependPrefix)&&(e.url=n.prependPrefix+(e.url??"/")),n?.target){applyGatewayMiddleware(e),g.web(e,t,{target:n.target,autoRewrite:typeof n.target=="string",changeOrigin:!0},s=>{console.error(`[parent] proxy error to ${n.target}:`,s),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from ${n.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){t.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),t.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const i=le();if(!i){t.statusCode=503,t.setHeader("content-type","text/plain; charset=utf-8");const s=v.map(o=>`#${o.id}: ${o.lastError??"no error recorded"}`).join(`
17
+ `);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const i=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),o=[...S,...q].map(y=>({id:y.id,pid:y.proc.pid??0,podGeneration:POD_GENERATION,healthy:y.healthy,retiring:q.has(y),latest:y.memory.latest??null,history:y.memory.history,retained:[...y.memory.retained.values()]}));t.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),t.end(JSON.stringify({ok:!0,service:SERVICE_NAME,project:f,bundle:{path:i},pods:o},null,2)+`
18
+ `);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){t.writeHead(404,{"content-type":"application/json"}),t.end(JSON.stringify({error:"not found"}));return}const i=a((L,Le)=>{t.writeHead(L,{"content-type":"application/json"}),t.end(JSON.stringify(Le,null,2)+`
19
+ `)},"reply"),o=new URL(e.url,"http://localhost").searchParams,y=Number(o.get("pod")||""),p=o.has("maxMb")?Number(o.get("maxMb")):void 0;if(p!==void 0&&!(Number.isFinite(p)&&p>0)){i(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const w=Number.isInteger(y)&&y>0?S.find(L=>L.id===y):Y()[0];if(!w||D(w)){i(404,{ok:!1,error:y?`no pod #${y} \u2014 running: ${S.map(L=>`#${L.id}`).join(", ")||"none"}`:"no pod is serving"});return}if(Pe.size>0){i(409,{ok:!1,refused:"a heap snapshot is already being written for this service"});return}const R=(0,heap_snapshot_1.heapSnapshotPolicy)(),M=(0,heap_snapshot_1.heapSnapshotDir)();let T;try{(0,heap_snapshot_1.ensureHeapDir)(M);const L=(0,heap_snapshot_1.pruneHeapSnapshots)(M,R);L.length&&console.log(`[parent] removed ${L.length} older heap snapshot(s) from ${M}`),T=path.join(M,(0,heap_snapshot_1.heapSnapshotFileName)(SERVICE_NAME||f,w.id,w.proc.pid??0,w.memory.latest?.gen??0,Date.now()))}catch(L){i(500,{ok:!1,error:`cannot prepare ${M}: ${L.message}`});return}const Q=++pe,Se=(0,heap_snapshot_1.availableSystemMemory)();let Ge;const A=await new Promise(L=>{const Le=setTimeout(()=>L(void 0),6e5);Le.unref?.(),Pe.set(Q,mt=>{clearTimeout(Le),L(mt)}),Ge=a(()=>{clearTimeout(Le),L(void 0)},"onExit"),w.proc.once("exit",Ge);const wt={type:"heap-snapshot",id:Q,file:T,...Se?{available:Se}:{},...p!==void 0?{maxHeapBytes:Math.floor(p*1048576)}:{}};tell(w.proc,wt)||Ge()}).finally(()=>{Pe.delete(Q),Ge&&w.proc.off("exit",Ge)});if(!A){i(504,{ok:!1,pod:w.id,error:"the pod did not finish the snapshot (it exited, or took longer than 10 minutes)"});return}A.ok?console.log(`[parent] Child#${w.id} wrote a ${Math.round((A.bytes??0)/1048576)} MB heap snapshot in ${A.ms} ms \u2192 ${A.file}`):console.warn(`[parent] Child#${w.id} heap snapshot not taken: ${A.refused??A.error}`),i(A.ok?200:A.refused?409:500,{ok:A.ok,pod:w.id,pid:w.proc.pid??0,service:SERVICE_NAME,bundle:path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),...Se?{available:Se}:{},...A.file?{file:A.file,bytes:A.bytes,ms:A.ms}:{},...A.live!==void 0?{live:A.live}:{},...A.gen!==void 0?{gen:A.gen,closedAlive:A.closedAlive??[]}:{},...A.refused?{refused:A.refused}:{},...A.error?{error:A.error}:{}});return}const s=matchGatewayRoute(e);if(s?.prependPrefix&&!e.url?.startsWith(s.prependPrefix)&&(e.url=s.prependPrefix+(e.url??"/")),s?.target){applyGatewayMiddleware(e),g.web(e,t,{target:s.target,autoRewrite:typeof s.target=="string",changeOrigin:!0},i=>{console.error(`[parent] proxy error to ${s.target}:`,i),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from ${s.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){t.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),t.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const n=le();if(!n){t.statusCode=503,t.setHeader("content-type","text/plain; charset=utf-8");const i=S.map(o=>`#${o.id}: ${o.lastError??"no error recorded"}`).join(`
20
20
  `);t.end(`No healthy children available. POST /webpack/reload to recover.
21
21
 
22
22
  Last errors:
23
- ${s}`);return}g.web(e,t,{target:{socketPath:i.socketPath},headers:{"x-proxy-child-id":String(i.id)}},s=>{console.error(`[parent] proxy error to Child#${i.id}:`,s),i.healthy=!1,i.lastError=String(s?.stack||s),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from Child#${i.id}. Try /webpack/reload.
24
- ${i.lastError}`)})},"requestHandler");let he,Ie,Ze;const xe=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),t=GATEWAY_ROUTES.map(i=>i.host).filter(i=>!!i),n=(0,checkout_cache_1.checkoutCacheDir)(process.cwd(),"davnx-webpack");Ie=e.getCertificateSync(n,SERVICE_NAME||"lensmcp.dev",t),Ze=e.caCertPath(),he=require("node:https").createServer({key:Ie,cert:Ie},Ce)}else he=http.createServer(Ce);const ct=a((e,t,n)=>{const i=matchGatewayRoute(e);if(i?.prependPrefix&&!e.url?.startsWith(i.prependPrefix)&&(e.url=i.prependPrefix+(e.url??"/")),i?.target){g.ws(e,t,n,{target:i.target,changeOrigin:!0},()=>t.destroy());return}const s=le();if(!s){t.destroy();return}g.ws(e,t,n,{target:{socketPath:s.socketPath}},()=>t.destroy())},"upgradeHandler");he.on("upgrade",ct);const lt=2e3;let dt=!1;he.on("error",e=>{if(e.code==="EADDRINUSE"){dt||(dt=!0,console.warn(`[parent] public port ${b} already in use \u2014 continuing on the unix-socket pool only (gateway routing is unaffected) and retrying the bind every ${lt/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!k&&!he.listening&&he.listen(b)},lt).unref?.();return}console.error("[parent] public server error:",e)}),await Se(fe);for(const e of WORKER_SPECS)qe(e,"boot");SOCKET_ONLY?console.log(`[parent] socket-only: no TCP listener (port ${b} is not bound \u2014 the gateway reaches this service through its socket pool)`):he.listen(b,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${b}`),SERVICE_PREFIX&&console.log(`[parent] Service prefix: "${SERVICE_PREFIX}" (enforced \u2014 requests without it will get 404)`),gatewayMiddleware&&console.log(`[parent] Gateway middleware: ACTIVE (${GATEWAY_MIDDLEWARE_PATH})`),GATEWAY_ROUTES.length>0&&console.log(`[parent] Gateway routes: ${GATEWAY_ROUTES.map(t=>`${t.host??"*"}${t.prefix??"/"}\u2192${t.target??"children"}`).join(" ")}`),Ze&&(console.log("[parent] HTTPS dev CA (trust ONCE, then rotations/new hostnames stay green):"),console.log(`[parent] sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${Ze}`)),console.log(`POST ${e}://localhost:${b}/webpack/reload to trigger rolling swap/respawn`)});const Ke=Number(process.env.INTERNAL_PORT||0);if(Ke>0&&!SOCKET_ONLY){const e=a((n,i)=>{const s=le();if(!s){i.statusCode=503,i.setHeader("content-type","text/plain; charset=utf-8"),i.end("No healthy children available.");return}g.web(n,i,{target:{socketPath:s.socketPath}},o=>{s.healthy=!1,s.lastError=String(o?.stack||o),i.statusCode=502,i.end(`Upstream error from Child#${s.id}.`)})},"internalHandler"),t=http.createServer(e);t.on("upgrade",(n,i,s)=>{const o=le();if(!o){i.destroy();return}g.ws(n,i,s,{target:{socketPath:o.socketPath}},()=>i.destroy())}),t.on("error",n=>{console.warn(`[parent] internal port ${Ke} unavailable (${n.code}).`)}),t.listen(Ke,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${Ke}`)}),xe.push(t)}const ft=SOCKET_ONLY?[]:(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of ft){if(!Number.isInteger(e)||e===Number(b))continue;const t=GATEWAY_HTTPS?require("node:https").createServer({key:Ie,cert:Ie},Ce):http.createServer(Ce);t.on("upgrade",ct),t.on("error",n=>{console.warn(`[parent] extra port ${e} unavailable (${n.code}) \u2014 main port still up.`)}),t.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),xe.push(t)}const Ne=path.join(SOCK_DIR,"parent.sock");claimSock(Ne),(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);const De=http.createServer(Ce);De.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),De.listen(Ne),xe.push(De);function pt(){k=!0;const e=[...v,...q];console.log(`[SIGTERM] [parent] generation g${POD_GENERATION} was handed over to a newer one \u2014 ${e.length} pod(s) finish their in-flight work, then it exits`);const t=sockOwnedByAnother(Ne);cleanupSock(Ne);for(const s of xe)if(!(s===De&&t))try{s.close()}catch{}try{he.close()}catch{}je();for(const s of[...G])Qe(s);G.length=0;for(const s of v)if(!tell(s.proc,{type:"retire"}))try{s.proc.kill("SIGTERM")}catch{}const n=setTimeout(()=>{for(const s of e)if(!D(s)){console.error(`[parent] Child#${s.id} was still running ${l}ms after its generation was retired \u2014 killing it`);try{s.proc.kill("SIGKILL")}catch{}}},l);n.unref?.();const i=a(()=>{if(!(e.some(s=>!D(s))||[...te].some(s=>!Re(s)))){clearTimeout(n);for(const s of e)s.socketPath&&cleanupSock(s.socketPath,s.proc.pid);(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);try{fs.rmdirSync(SOCK_DIR)}catch{}console.log(`[parent] generation g${POD_GENERATION} retired`),process.exit(0)}},"finish");for(const s of e)s.proc.once("exit",i);for(const s of te)s.proc.once("exit",i);i()}u(pt,"ht"),a(pt,"retireGeneration");const ut=a(async e=>{k=!0,console.log(`[${e}] [parent] shutting down\u2026`);const t=sockOwnedByAnother(Ne);cleanupSock(Ne);for(const n of xe)if(!(n===De&&t))try{n.close()}catch{}je();for(const n of G){n.stopping=!0;try{n.proc.kill("SIGTERM")}catch{}}for(const n of WORKER_SPECS){const i=G.find(s=>s.name===n.name)?.proc.pid??0;m.publish((0,worker_supervisor_1.buildWorkerStoppedEvent)(Ae(n.name,i),{reason:`its devserver stopped (${e})`}))}for(const n of[...te])Be(n,"was still draining when its devserver shut down");for(const n of q)try{n.proc.kill("SIGTERM")}catch{}for(const n of v){try{n.proc.kill("SIGTERM")}catch{}n.socketPath&&cleanupSock(n.socketPath,n.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}setTimeout(()=>process.exit(0),700).unref()},"shutdown");let et=0;const _t=2e3;["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{if(e==="SIGTERM"){if(!k&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){et=Date.now(),pt();return}if(et>0&&Date.now()-et<_t)return}ut(e)}));const Ye=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(Ye)&&Ye>0){const e=setInterval(()=>{try{process.kill(Ye,0)}catch(t){if(t.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${Ye}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),ut("gateway-gone")}},5e3);e.unref()}})()}
23
+ ${i}`);return}g.web(e,t,{target:{socketPath:n.socketPath},headers:{"x-proxy-child-id":String(n.id)}},i=>{console.error(`[parent] proxy error to Child#${n.id}:`,i),n.healthy=!1,n.lastError=String(i?.stack||i),t.statusCode=502,t.setHeader("content-type","text/plain; charset=utf-8"),t.end(`Upstream error from Child#${n.id}. Try /webpack/reload.
24
+ ${n.lastError}`)})},"requestHandler");let he,Ie,Ze;const xe=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),t=GATEWAY_ROUTES.map(n=>n.host).filter(n=>!!n),s=(0,checkout_cache_1.checkoutCacheDir)(process.cwd(),"davnx-webpack");Ie=e.getCertificateSync(s,SERVICE_NAME||"lensmcp.dev",t),Ze=e.caCertPath(),he=require("node:https").createServer({key:Ie,cert:Ie},Ce)}else he=http.createServer(Ce);const ct=a((e,t,s)=>{const n=matchGatewayRoute(e);if(n?.prependPrefix&&!e.url?.startsWith(n.prependPrefix)&&(e.url=n.prependPrefix+(e.url??"/")),n?.target){g.ws(e,t,s,{target:n.target,changeOrigin:!0},()=>t.destroy());return}const i=le();if(!i){t.destroy();return}g.ws(e,t,s,{target:{socketPath:i.socketPath}},()=>t.destroy())},"upgradeHandler");he.on("upgrade",ct);const lt=2e3;let dt=!1;he.on("error",e=>{if(e.code==="EADDRINUSE"){dt||(dt=!0,console.warn(`[parent] public port ${b} already in use \u2014 continuing on the unix-socket pool only (gateway routing is unaffected) and retrying the bind every ${lt/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!k&&!he.listening&&he.listen(b)},lt).unref?.();return}console.error("[parent] public server error:",e)}),await ve(fe);for(const e of WORKER_SPECS)qe(e,"boot");SOCKET_ONLY?console.log(`[parent] socket-only: no TCP listener (port ${b} is not bound \u2014 the gateway reaches this service through its socket pool)`):he.listen(b,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${b}`),SERVICE_PREFIX&&console.log(`[parent] Service prefix: "${SERVICE_PREFIX}" (enforced \u2014 requests without it will get 404)`),gatewayMiddleware&&console.log(`[parent] Gateway middleware: ACTIVE (${GATEWAY_MIDDLEWARE_PATH})`),GATEWAY_ROUTES.length>0&&console.log(`[parent] Gateway routes: ${GATEWAY_ROUTES.map(t=>`${t.host??"*"}${t.prefix??"/"}\u2192${t.target??"children"}`).join(" ")}`),Ze&&(console.log("[parent] HTTPS dev CA (trust ONCE, then rotations/new hostnames stay green):"),console.log(`[parent] sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${Ze}`)),console.log(`POST ${e}://localhost:${b}/webpack/reload to trigger rolling swap/respawn`)});const Ke=Number(process.env.INTERNAL_PORT||0);if(Ke>0&&!SOCKET_ONLY){const e=a((s,n)=>{const i=le();if(!i){n.statusCode=503,n.setHeader("content-type","text/plain; charset=utf-8"),n.end("No healthy children available.");return}g.web(s,n,{target:{socketPath:i.socketPath}},o=>{i.healthy=!1,i.lastError=String(o?.stack||o),n.statusCode=502,n.end(`Upstream error from Child#${i.id}.`)})},"internalHandler"),t=http.createServer(e);t.on("upgrade",(s,n,i)=>{const o=le();if(!o){n.destroy();return}g.ws(s,n,i,{target:{socketPath:o.socketPath}},()=>n.destroy())}),t.on("error",s=>{console.warn(`[parent] internal port ${Ke} unavailable (${s.code}).`)}),t.listen(Ke,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${Ke}`)}),xe.push(t)}const ft=SOCKET_ONLY?[]:(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of ft){if(!Number.isInteger(e)||e===Number(b))continue;const t=GATEWAY_HTTPS?require("node:https").createServer({key:Ie,cert:Ie},Ce):http.createServer(Ce);t.on("upgrade",ct),t.on("error",s=>{console.warn(`[parent] extra port ${e} unavailable (${s.code}) \u2014 main port still up.`)}),t.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),xe.push(t)}const Ne=path.join(SOCK_DIR,"parent.sock");claimSock(Ne),(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);const De=http.createServer(Ce);De.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),De.listen(Ne),xe.push(De);function pt(){k=!0;const e=[...S,...q];console.log(`[SIGTERM] [parent] generation g${POD_GENERATION} was handed over to a newer one \u2014 ${e.length} pod(s) finish their in-flight work, then it exits`);const t=sockOwnedByAnother(Ne);cleanupSock(Ne);for(const i of xe)if(!(i===De&&t))try{i.close()}catch{}try{he.close()}catch{}je();for(const i of[...G])Qe(i);G.length=0;for(const i of S)if(!tell(i.proc,{type:"retire"}))try{i.proc.kill("SIGTERM")}catch{}const s=setTimeout(()=>{for(const i of e)if(!D(i)){console.error(`[parent] Child#${i.id} was still running ${l}ms after its generation was retired \u2014 killing it`);try{i.proc.kill("SIGKILL")}catch{}}},l);s.unref?.();const n=a(()=>{if(!(e.some(i=>!D(i))||[...te].some(i=>!Ae(i)))){clearTimeout(s);for(const i of e)i.socketPath&&cleanupSock(i.socketPath,i.proc.pid);(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);try{fs.rmdirSync(SOCK_DIR)}catch{}console.log(`[parent] generation g${POD_GENERATION} retired`),process.exit(0)}},"finish");for(const i of e)i.proc.once("exit",n);for(const i of te)i.proc.once("exit",n);n()}u(pt,"ht"),a(pt,"retireGeneration");const ut=a(async e=>{k=!0,console.log(`[${e}] [parent] shutting down\u2026`);const t=sockOwnedByAnother(Ne);cleanupSock(Ne);for(const n of xe)if(!(n===De&&t))try{n.close()}catch{}je();for(const n of G){n.stopping=!0;try{n.proc.kill("SIGTERM")}catch{}}for(const n of WORKER_SPECS){const i=G.find(o=>o.name===n.name)?.proc.pid??0;m.publish((0,worker_supervisor_1.buildWorkerStoppedEvent)(Re(n.name,i),{reason:`its devserver stopped (${e})`}))}for(const n of[...te])Be(n,"was still draining when its devserver shut down");for(const n of q)try{n.proc.kill("SIGTERM")}catch{}for(const n of S){try{n.proc.kill("SIGTERM")}catch{}n.socketPath&&cleanupSock(n.socketPath,n.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}const s=(0,devserver_host_1.devserverStopExitCode)(e);setTimeout(()=>process.exit(s),700).unref()},"shutdown");let et=0;const _t=2e3;["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{if(e==="SIGTERM"){if(!k&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){et=Date.now(),pt();return}if(et>0&&Date.now()-et<_t)return}ut(e)}));const Ye=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(Ye)&&Ye>0){const e=setInterval(()=>{try{process.kill(Ye,0)}catch(t){if(t.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${Ye}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),ut("gateway-gone")}},5e3);e.unref()}})()}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.25.29",
3
+ "version": "1.25.31",
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.29",
62
- "@lensmcp/node-instrumentation": "1.25.29",
63
- "@lensmcp/nx-plugin": "1.25.29",
61
+ "@lensmcp/core": "1.25.31",
62
+ "@lensmcp/node-instrumentation": "1.25.31",
63
+ "@lensmcp/nx-plugin": "1.25.31",
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",
package/serve-runner.d.ts CHANGED
@@ -1,2 +1,26 @@
1
- /** The conventional exit status of a process ended by `signal` (nx reports a signalled task the same way). */
2
- export declare function exitCodeForSignal(signal: NodeJS.Signals): number;
1
+ /**
2
+ * serve-runner — the process the dev gateway starts for a service's `@lensmcp/cluster:serve` target
3
+ * in place of `nx run <project>:<target>` (issues/closed/foodguard-11).
4
+ *
5
+ * `nx run` is two processes: a CLIENT that computes the project graph, the task graph and the task's
6
+ * environment, then forks `nx/bin/run-executor.js`, which receives the task over IPC and runs it with
7
+ * nx's `run()`. The client then sits idle for the life of the service. The gateway already holds the
8
+ * graph, so it does the client's part itself (executors/gateway/runtime/serve-launch.ts) and starts
9
+ * THIS — the run-executor half: the same IPC message, the same `run()` from the workspace's own nx, so
10
+ * the executor gets the options, context and environment it gets under `nx run`.
11
+ *
12
+ * Two things differ from run-executor, both about stopping:
13
+ * - SIGTERM / SIGINT / SIGHUP END this process, after every other listener for the signal ran.
14
+ * run-executor installs no handler, so once the serve executor had registered its own (which only
15
+ * stops the devserver) a group SIGTERM left the webpack watcher running under no parent until its
16
+ * next write met a closed pipe.
17
+ * - Losing the gateway — the IPC channel closing — ends it the same way. The devserver's own watchdog
18
+ * already stops the pods then; nothing should keep compiling for a gateway that is gone.
19
+ *
20
+ * Since 2026-10-27 the executor also runs the service's devserver in this process (devserver-host.ts): once it has,
21
+ * the devserver's own handlers end the process, and a stop signal here leaves the exit to them.
22
+ *
23
+ * Node built-ins only until the task arrives: everything else this process holds is the executor's.
24
+ */
25
+ import { exitCodeForSignal } from './devserver-host';
26
+ export { exitCodeForSignal };
package/serve-runner.js CHANGED
@@ -1 +1 @@
1
- "use strict";var p=Object.defineProperty;var t=(r,s)=>p(r,"name",{value:s,configurable:!0});var a=Object.defineProperty,o=t((r,s)=>a(r,"name",{value:s,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.exitCodeForSignal=exitCodeForSignal;const tslib_1=require("tslib"),os=tslib_1.__importStar(require("node:os")),TASK_WAIT_MS=3e4,STOP_SIGNALS=["SIGTERM","SIGINT","SIGHUP"];function exitCodeForSignal(r){return 128+(os.constants.signals[r]??0)}t(exitCodeForSignal,"exitCodeForSignal"),o(exitCodeForSignal,"exitCodeForSignal");function main(){process.env.NX_CLI_SET="true";const r=process.env.NX_WORKSPACE_ROOT;(!r||typeof process.send!="function")&&(console.error("[serve-runner] started without a task channel \u2014 only the dev gateway starts this process"),process.exit(1));let s=!1;const i=o(e=>{s||(s=!0,setImmediate(()=>process.exit(exitCodeForSignal(e))))},"stop");for(const e of STOP_SIGNALS)process.on(e,()=>i(e));process.on("disconnect",()=>{s||(console.error("[serve-runner] the gateway is gone \u2014 stopping the service"),process.kill(process.pid,"SIGTERM"))});const c=setTimeout(()=>{console.error(`[serve-runner] no task arrived within ${TASK_WAIT_MS/1e3}s \u2014 exiting`),process.exit(1)},TASK_WAIT_MS);process.once("message",e=>{clearTimeout(c),(async()=>{let n=1;try{n=await require(e.nxRunModule).run(process.cwd(),r,e.targetDescription,e.overrides,e.isVerbose,e.taskGraph)}catch(u){console.error(u)}process.exit(n)})()})}t(main,"main"),o(main,"main"),require.main===module&&main();
1
+ "use strict";var p=Object.defineProperty;var t=(s,r)=>p(s,"name",{value:r,configurable:!0});var u=Object.defineProperty,o=t((s,r)=>u(s,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.exitCodeForSignal=void 0;const devserver_host_1=require("./devserver-host");Object.defineProperty(exports,"exitCodeForSignal",{enumerable:!0,get:o(function(){return devserver_host_1.exitCodeForSignal},"get")});const TASK_WAIT_MS=3e4,STOP_SIGNALS=["SIGTERM","SIGINT","SIGHUP"];function main(){process.env.NX_CLI_SET="true";const s=process.env.NX_WORKSPACE_ROOT;(!s||typeof process.send!="function")&&(console.error("[serve-runner] started without a task channel \u2014 only the dev gateway starts this process"),process.exit(1)),(0,devserver_host_1.markServeRunner)();let r=!1;const i=o(e=>{r||(r=!0,!(0,devserver_host_1.devserverOwnsExit)()&&setImmediate(()=>process.exit((0,devserver_host_1.exitCodeForSignal)(e))))},"stop");for(const e of STOP_SIGNALS)process.on(e,()=>i(e));process.on("disconnect",()=>{r||(console.error("[serve-runner] the gateway is gone \u2014 stopping the service"),process.kill(process.pid,"SIGTERM"))});const c=setTimeout(()=>{console.error(`[serve-runner] no task arrived within ${TASK_WAIT_MS/1e3}s \u2014 exiting`),process.exit(1)},TASK_WAIT_MS);process.once("message",e=>{clearTimeout(c),(async()=>{let n=1;try{n=await require(e.nxRunModule).run(process.cwd(),s,e.targetDescription,e.overrides,e.isVerbose,e.taskGraph)}catch(a){console.error(a)}process.exit(n)})()})}t(main,"main"),o(main,"main"),require.main===module&&main();
package/worker-guard.js CHANGED
@@ -1 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const orphanMs=(()=>{const e=Number(process.env.LENSMCP_WORKER_ORPHAN_MS);return Number.isFinite(e)&&e>=0&&process.env.LENSMCP_WORKER_ORPHAN_MS!==""?e:1e4})();typeof process.send=="function"&&process.channel&&(process.once("disconnect",()=>{for(const e of[process.stdout,process.stderr])e.on("error",()=>{});setTimeout(()=>process.exit(0),orphanMs).unref(),process.kill(process.pid,"SIGTERM")}),process.channel.unref());
1
+ "use strict";var n=Object.defineProperty;var o=(e,r)=>n(e,"name",{value:r,configurable:!0});var t=Object.defineProperty,s=o((e,r)=>t(e,"name",{value:r,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0});const worker_retire_1=require("./worker-retire"),orphanMs=(()=>{const e=Number(process.env.LENSMCP_WORKER_ORPHAN_MS);return Number.isFinite(e)&&e>=0&&process.env.LENSMCP_WORKER_ORPHAN_MS!==""?e:1e4})();if(typeof process.send=="function"&&process.channel){process.once("disconnect",()=>{for(const r of[process.stdout,process.stderr])r.on("error",()=>{});setTimeout(()=>process.exit(0),orphanMs).unref(),process.kill(process.pid,"SIGTERM")});const e=s(r=>{(0,worker_retire_1.isRetireMessage)(r)&&(process.off("message",e),(0,worker_retire_1.exitWhenIdle)({graceMs:(0,worker_retire_1.retireGraceMs)(),exit:s(c=>{try{process.stderr.write(`[worker-guard] ${c}
2
+ `)}catch{}process.exit(0)},"exit")}))},"onRetire");process.on("message",e),process.channel.unref()}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A worker a reload replaced ends once it has no job in flight — not when the devserver's drain bound runs out.
3
+ *
4
+ * The devserver SIGTERMs a replaced worker and kills it at `LENSMCP_SWAP_DRAIN_MS` (15 min), so a job in flight can
5
+ * finish (main.devserver.ts `retireWorker`). But a worker whose shutdown returns with a handle still open never exits
6
+ * on its own — BullMQ does not close a Redis client it was handed — so after every reload the old worker held its
7
+ * whole heap beside its successor for the full bound: FoodGuard's engine-worker, ~900 MB for 15 minutes (2026-10-27).
8
+ *
9
+ * The worker loads the instrumentation's taps (`@lensmcp/node-instrumentation/register`), which count the jobs it runs
10
+ * (`__lensmcpActiveWork`, active-work.ts) and can stop it taking new ones (`__lensmcpStopWorkIntake`, tap-bullmq.ts).
11
+ * Told it is retired ({@link RETIRE_MESSAGE}, sent before the SIGTERM), it stops taking work, and once nothing has
12
+ * been in flight for `LENSMCP_WORKER_RETIRE_GRACE_MS` (5 s — its own shutdown hooks run meanwhile) it exits. A worker
13
+ * without the taps cannot say, and keeps the devserver's bound.
14
+ */
15
+ /** What the devserver sends a worker it has replaced, just before its SIGTERM. */
16
+ export declare const RETIRE_MESSAGE = "lensmcp-worker-retire";
17
+ export declare const RETIRE_GRACE_ENV = "LENSMCP_WORKER_RETIRE_GRACE_MS";
18
+ export declare const DEFAULT_RETIRE_GRACE_MS = 5000;
19
+ export declare function isRetireMessage(m: unknown): boolean;
20
+ export declare function retireGraceMs(env?: NodeJS.ProcessEnv): number;
21
+ export interface RetireDeps {
22
+ graceMs: number;
23
+ /** Ends the process; `why` is the line it leaves in the log. */
24
+ exit(why: string): void;
25
+ /** Where the taps publish their probes. Default `globalThis`. */
26
+ globals?: Record<string, unknown>;
27
+ now?: () => number;
28
+ }
29
+ /**
30
+ * Stop taking work and exit once none has been in flight for `graceMs`. Returns the watch (for tests), or `undefined`
31
+ * when this worker has no way to tell — it was started without the instrumentation.
32
+ */
33
+ export declare function exitWhenIdle(deps: RetireDeps): {
34
+ cancel(): void;
35
+ } | undefined;
@@ -0,0 +1 @@
1
+ "use strict";var _=Object.defineProperty;var s=(e,t)=>_(e,"name",{value:t,configurable:!0});var R=Object.defineProperty,n=s((e,t)=>R(e,"name",{value:t,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEFAULT_RETIRE_GRACE_MS=exports.RETIRE_GRACE_ENV=exports.RETIRE_MESSAGE=void 0,exports.isRetireMessage=isRetireMessage,exports.retireGraceMs=retireGraceMs,exports.exitWhenIdle=exitWhenIdle,exports.RETIRE_MESSAGE="lensmcp-worker-retire",exports.RETIRE_GRACE_ENV="LENSMCP_WORKER_RETIRE_GRACE_MS",exports.DEFAULT_RETIRE_GRACE_MS=5e3;const POLL_MS=250;function isRetireMessage(e){return!!e&&typeof e=="object"&&e.type===exports.RETIRE_MESSAGE}s(isRetireMessage,"isRetireMessage"),n(isRetireMessage,"isRetireMessage");function retireGraceMs(e=process.env){const t=e[exports.RETIRE_GRACE_ENV]?.trim(),r=t?Number(t):NaN;return Number.isFinite(r)&&r>=0?r:exports.DEFAULT_RETIRE_GRACE_MS}s(retireGraceMs,"retireGraceMs"),n(retireGraceMs,"retireGraceMs");function exitWhenIdle(e){const t=e.globals??globalThis,r=t.__lensmcpActiveWork;if(typeof r!="function")return;const c=t.__lensmcpStopWorkIntake;if(typeof c=="function")try{c(Number.MAX_SAFE_INTEGER)}catch{}const a=e.now??Date.now;let o;const i=setInterval(()=>{let E;try{E=Number(r()?.count)}catch{return}if(Number.isFinite(E)){if(E>0){o=void 0;return}o??=a(),!(a()-o<e.graceMs)&&(clearInterval(i),e.exit(`retired: no job in flight for ${e.graceMs}ms and still running \u2014 exiting (its shutdown left a handle open)`))}},POLL_MS);return i.unref?.(),{cancel:n(()=>clearInterval(i),"cancel")}}s(exitWhenIdle,"exitWhenIdle"),n(exitWhenIdle,"exitWhenIdle");