@lensmcp/cluster 1.22.6 → 1.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,7 +98,7 @@ A declaration must route *somewhere*: either a pod `service` or a TCP `upstream`
98
98
 
99
99
  ### Key options
100
100
 
101
- **`serve`** — `entryFile`, `tsConfigFile` (required); `childCount` (pods in the pool); `internalPort` (plain-HTTP listener straight to children, no gateway middleware — for service-to-service calls); `workers` (extra entry bundles compiled in watch mode); `servePrefix`, `serviceName`, `configEnv`; `orgScopes` / `bundlePackages` / `nodeExternalsConfig` (control what's bundled vs externalized); an embedded `gateway` block (`middleware`, `routes`, `https`, `extraPorts`) for single-service setups.
101
+ **`serve`** — `entryFile`, `tsConfigFile` (required); `childCount` (pods in the pool); `internalPort` (plain-HTTP listener straight to children, no gateway middleware — loopback diagnostics; service-to-service calls go through `internal.<host>`); `workers` (extra entry bundles compiled in watch mode); `servePrefix`, `serviceName`, `configEnv`; `orgScopes` / `bundlePackages` / `nodeExternalsConfig` (control what's bundled vs externalized); an embedded `gateway` block (`middleware`, `routes`, `https`, `extraPorts`) for single-service setups.
102
102
 
103
103
  > Pass `minPods` via the `--childCount` option — the Nx daemon drops spawner env, so don't pin `childCount` in `serve-hmr` options expecting `minPods` to flow through.
104
104
 
@@ -115,7 +115,7 @@ One front door, two transports sharing one routing brain:
115
115
  - **Routing** — every `cluster` declaration becomes a route; requests match by hostname (exact beats `*.suffix` wildcard) then by most-specific path prefix. A *claimed* host's unmatched root is served by the gateway itself (`/health` aggregate, `/.well-known/...`, CORS + preflights); an *unclaimed* host can only reach the `default` route or 404.
116
116
  - **Dev transport** — `node:http` + `http-proxy`. It owns the unix-socket pods: **scale-from-zero** on first request, autoscale while requests overlap, idle-kill, supervised reforks, and ghost-socket sweeping.
117
117
  - **Prod transport** — the same `matchRoute` composed over **Fastify + @fastify/reply-from (undici)** (connection-reusing, HTTP/1.1 pipelined; benchmarks materially lower CPU/req than http-proxy), shipped as a **Node 26 distroless** image. No process management — prod pods are always-on (k8s keeps them up).
118
- - **Edge auth + identity** — for `jwt` routes the gateway verifies the end-user token (dev: HS256 shared secret; prod: RS256 via JWKS) and stamps trusted identity headers (`x-user-id`, `x-tenant-id`, roles, perms) onto the forwarded request; `internal.<host>` routes require `x-api-key`. Per-path **ABAC** rules (a `permission` or full predicate over verified claims + IP/headers, scoped by HTTP method) are enforced at the edge → 403.
118
+ - **Edge auth + identity** — for `jwt` routes the gateway verifies the end-user token (dev: HS256 shared secret; prod: RS256 via JWKS) and stamps trusted identity headers (`x-user-id`, `x-tenant-id`, roles, perms) onto the forwarded request; `internal.<host>` routes require the CALLER's own `x-api-key`, and once it is validated the `x-tenant-id` the caller named is forwarded to the callee — production's service-to-service path is direct, so the callee reads it there too; every other identity header stays the gateway's. Per-path **ABAC** rules (a `permission` or full predicate over verified claims + IP/headers, scoped by HTTP method) are enforced at the edge → 403.
119
119
  - **Rollout** — per-request upstream selection supports **weighted** cohorts (smooth nginx-style split for canary / blue-green) and **attribute-based targeting** (route by user / tenant / IP / header / sticky device key, consistent-hashed so a ramp moves a key across the boundary at most once). The serving version rides on `x-lensmcp-version` and the trace.
120
120
 
121
121
  ## How it fits
@@ -1 +1 @@
1
- "use strict";var y=Object.defineProperty;var p=(i,s)=>y(i,"name",{value:s,configurable:!0});var f=Object.defineProperty,d=p((i,s)=>f(i,"name",{value:s,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createEdgeAuth=createEdgeAuth;const manifest_1=require("../manifest"),dev_auth_1=require("./dev-auth"),edge_1=require("./edge");function createEdgeAuth(i,s,h){const{traceStep:o}=s;return{internal:d((e,k,r)=>{const u=String(e.headers["x-api-key"]??""),a=i.keyToProject.get(u);if(!a)return o(r,"auth",{mode:"x-api-key",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."};const t=i.keyToWorkspace?.get(u),n=k.svc?.wsKey;return t&&n&&t!==n?(o(r,"auth",{mode:"x-api-key",ok:!1,reason:"cross-workspace",caller:a,callerWorkspace:t,routeWorkspace:n}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."}):(e.headers["x-api-key-id"]=a,t?e.headers["x-api-key-workspace"]=t:delete e.headers["x-api-key-workspace"],e.__lensmcpCaller=a,delete e.headers["x-api-key"],o(r,"auth",{mode:"x-api-key",ok:!0,caller:a,...t?{callerWorkspace:t}:{}}),{ok:!0,caller:a})},"internal"),public:d((e,k,r,u)=>{const a=k.auth;if(a){const t=u??(0,edge_1.canonicalPath)(e.url??"/")??(e.url??"/").split("?")[0],n=(e.method??"GET").toUpperCase(),l=(0,manifest_1.authRuleFor)(a,t,n);if(l.mode==="jwt"){const m=String(e.headers.host??"").split(":")[0].toLowerCase(),c=(0,dev_auth_1.verifyDevJwt)(e,h?.for(m));return c?l.rule&&!(0,manifest_1.evalRule)(l.rule,(0,dev_auth_1.devAttrs)(e,c,t))?(o(r,"authz",{ok:!1}),{ok:!1,status:403,reason:"forbidden",detail:"Forbidden: insufficient permission"}):((0,dev_auth_1.stampIdentity)(e,c),o(r,"auth",{mode:"jwt",ok:!0}),{ok:!0,claims:c}):(o(r,"auth",{mode:"jwt",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Unauthorized: invalid or missing token"})}return o(r,"auth",{mode:l.mode}),{ok:!0}}return o(r,"auth",{mode:"public",unconfigured:!0}),{ok:!0}},"publicAuth")}}p(createEdgeAuth,"createEdgeAuth"),d(createEdgeAuth,"createEdgeAuth");
1
+ "use strict";var y=Object.defineProperty;var p=(n,s)=>y(n,"name",{value:s,configurable:!0});var h=Object.defineProperty,d=p((n,s)=>h(n,"name",{value:s,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createEdgeAuth=createEdgeAuth;const manifest_1=require("../manifest"),dev_auth_1=require("./dev-auth"),edge_1=require("./edge");function createEdgeAuth(n,s,f){const{traceStep:o}=s;return{internal:d((e,k,r)=>{const l=String(e.headers["x-api-key"]??""),a=n.keyToProject.get(l);if(!a)return o(r,"auth",{mode:"x-api-key",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."};const t=n.keyToWorkspace?.get(l),u=k.svc?.wsKey;if(t&&u&&t!==u)return o(r,"auth",{mode:"x-api-key",ok:!1,reason:"cross-workspace",caller:a,callerWorkspace:t,routeWorkspace:u}),{ok:!1,status:401,reason:"unauthorized",detail:"Invalid or missing x-api-key for internal route."};e.headers["x-api-key-id"]=a,t?e.headers["x-api-key-workspace"]=t:delete e.headers["x-api-key-workspace"],e.__lensmcpCaller=a,delete e.headers["x-api-key"];const i=(0,edge_1.callerTenantOf)(e);return i!==void 0&&(e.headers["x-tenant-id"]=i),o(r,"auth",{mode:"x-api-key",ok:!0,caller:a,...t?{callerWorkspace:t}:{},...i!==void 0?{callerTenant:!0}:{}}),{ok:!0,caller:a}},"internal"),public:d((e,k,r,l)=>{const a=k.auth;if(a){const t=l??(0,edge_1.canonicalPath)(e.url??"/")??(e.url??"/").split("?")[0],u=(e.method??"GET").toUpperCase(),i=(0,manifest_1.authRuleFor)(a,t,u);if(i.mode==="jwt"){const m=String(e.headers.host??"").split(":")[0].toLowerCase(),c=(0,dev_auth_1.verifyDevJwt)(e,f?.for(m));return c?i.rule&&!(0,manifest_1.evalRule)(i.rule,(0,dev_auth_1.devAttrs)(e,c,t))?(o(r,"authz",{ok:!1}),{ok:!1,status:403,reason:"forbidden",detail:"Forbidden: insufficient permission"}):((0,dev_auth_1.stampIdentity)(e,c),o(r,"auth",{mode:"jwt",ok:!0}),{ok:!0,claims:c}):(o(r,"auth",{mode:"jwt",ok:!1}),{ok:!1,status:401,reason:"unauthorized",detail:"Unauthorized: invalid or missing token"})}return o(r,"auth",{mode:i.mode}),{ok:!0}}return o(r,"auth",{mode:"public",unconfigured:!0}),{ok:!0}},"publicAuth")}}p(createEdgeAuth,"createEdgeAuth"),d(createEdgeAuth,"createEdgeAuth");
@@ -33,9 +33,29 @@ export declare const sendError: (res: http.ServerResponse, req: http.IncomingMes
33
33
  /**
34
34
  * The gateway OWNS these headers — strip any client-supplied copy at ingress so identity + trust can
35
35
  * only originate from the gateway (mirrors the prod gateway's stripTrust). Applied at the very top of
36
- * BOTH the HTTP handler and the WS upgrade.
36
+ * BOTH the HTTP handler and the WS upgrade, through {@link stripOwnedHeaders}.
37
37
  */
38
38
  export declare const GATEWAY_OWNED_HEADERS: string[];
39
+ /**
40
+ * Where {@link stripOwnedHeaders} sets the caller's `x-tenant-id` aside. Read back ONLY by the internal-route
41
+ * auth, once the calling service's own key has been validated ({@link callerTenantOf}).
42
+ */
43
+ export declare const CALLER_TENANT: unique symbol;
44
+ /**
45
+ * Strip every gateway-owned header at ingress — the HTTP handler and the WS upgrade both call this —
46
+ * keeping the caller's `x-tenant-id` aside on the request.
47
+ *
48
+ * Why that one header survives for an internal route (and nothing else does): service-to-service traffic
49
+ * goes DIRECT in production — the caller's pod calls the callee's cluster service, with no gateway in
50
+ * between — so the callee receives the tenant its caller named and authorizes the call itself (a
51
+ * per-purpose secret, for one). Deleting it on the dev `internal.<host>` made dev refuse calls prod
52
+ * accepts, and pushed services onto each other's pod ports to get around the gateway. It comes back only
53
+ * after the caller has proven which service it is; the `x-user-*` identity never does, and a public
54
+ * route's `x-tenant-id` is only ever the one the verified token carries.
55
+ */
56
+ export declare function stripOwnedHeaders(req: http.IncomingMessage): void;
57
+ /** The tenant the caller named — undefined when it sent none, or a value not worth handing back. */
58
+ export declare const callerTenantOf: (req: http.IncomingMessage) => string | undefined;
39
59
  /**
40
60
  * Is this `Origin` one of the hosts THIS gateway routes?
41
61
  *
@@ -1 +1 @@
1
- "use strict";var l=Object.defineProperty;var i=(e,r)=>l(e,"name",{value:r,configurable:!0});var p=Object.defineProperty,o=i((e,r)=>p(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PREFLIGHT_METHODS_ROUTED=exports.PREFLIGHT_METHODS_NOROUTE=exports.corsPreflight=exports.cors=exports.isRoutedOrigin=exports.GATEWAY_OWNED_HEADERS=exports.sendError=exports.stampForwardingHeaders=exports.clientIpOf=exports.resolveClientIp=exports.CLIENT_IP=exports.rememberRawTarget=exports.rawTargetOf=exports.RAW_TARGET=exports.canonicalPath=exports.canonicalize=exports.ENCODED_STRUCTURE=void 0;const gateway_errors_1=require("../gateway-errors"),client_ip_1=require("../client-ip"),manifest_1=require("../manifest");var canonical_path_1=require("../canonical-path");Object.defineProperty(exports,"ENCODED_STRUCTURE",{enumerable:!0,get:o(function(){return canonical_path_1.ENCODED_STRUCTURE},"get")}),Object.defineProperty(exports,"canonicalize",{enumerable:!0,get:o(function(){return canonical_path_1.canonicalize},"get")}),Object.defineProperty(exports,"canonicalPath",{enumerable:!0,get:o(function(){return canonical_path_1.canonicalPath},"get")}),Object.defineProperty(exports,"RAW_TARGET",{enumerable:!0,get:o(function(){return canonical_path_1.RAW_TARGET},"get")}),Object.defineProperty(exports,"rawTargetOf",{enumerable:!0,get:o(function(){return canonical_path_1.rawTargetOf},"get")}),Object.defineProperty(exports,"rememberRawTarget",{enumerable:!0,get:o(function(){return canonical_path_1.rememberRawTarget},"get")}),exports.CLIENT_IP=Symbol.for("lensmcp.gateway.clientIp");const FORWARDED=Symbol.for("lensmcp.gateway.forwarded"),peerOnly=(0,client_ip_1.createClientIpResolver)({}),resolverOf=o(e=>e.clientIp??peerOnly,"resolverOf"),peerOf=o(e=>e.socket?.remoteAddress??"","peerOf"),resolveClientIp=o((e,r)=>{const t=resolverOf(e).clientIpOf(r,peerOf(r));return r[exports.CLIENT_IP]=t,t},"resolveClientIp");exports.resolveClientIp=resolveClientIp;const clientIpOf=o(e=>e[exports.CLIENT_IP]??peerOf(e),"clientIpOf");exports.clientIpOf=clientIpOf;const stampForwardingHeaders=o((e,r)=>{const t=r;if(t[FORWARDED])return;t[FORWARDED]=!0;const s=peerOf(r);r.headers["x-forwarded-for"]=resolverOf(e).forwardedFor(r,s),r.headers["x-forwarded-proto"]=e.https?"https":"http";const n=r.headers.host;(resolverOf(e).trustProxyHops===0||!r.headers["x-forwarded-host"])&&(n?r.headers["x-forwarded-host"]=n:delete r.headers["x-forwarded-host"])},"stampForwardingHeaders");exports.stampForwardingHeaders=stampForwardingHeaders;const sendError=o((e,r,t,s,n,c,d)=>{const a=(0,gateway_errors_1.ensureRequestId)(r);return e.headersSent||e.writeHead(t,{"content-type":gateway_errors_1.GATEWAY_ERROR_CONTENT_TYPE,"x-request-id":a,...d??{}}),e.end((0,gateway_errors_1.gatewayErrorBody)(t,s,a,n,c)),a},"sendError");exports.sendError=sendError,exports.GATEWAY_OWNED_HEADERS=["x-internal-token","x-api-key-id","x-api-key-workspace","x-user-id","x-tenant-id","x-user-roles","x-user-permissions"];const isRoutedOrigin=o((e,r)=>{if(!e||e==="null")return!1;let t;try{t=new URL(e).hostname.toLowerCase()}catch{return!1}return t?t==="localhost"||t==="127.0.0.1"||t==="::1"?!0:r.some(s=>!!s.host&&(0,manifest_1.hostMatches)(s.host,t)):!1},"isRoutedOrigin");exports.isRoutedOrigin=isRoutedOrigin;const cors=o((e,r,t)=>{const s=e.headers.origin;typeof s!="string"||s===""||(r.setHeader("vary","Origin"),(0,exports.isRoutedOrigin)(s,t)&&(r.setHeader("access-control-allow-origin",s),r.setHeader("access-control-allow-credentials","true")))},"cors");exports.cors=cors;const corsPreflight=o((e,r,t,s)=>{(0,exports.cors)(e,r,s),r.writeHead(204,{"access-control-allow-methods":t,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),r.end()},"corsPreflight");exports.corsPreflight=corsPreflight,exports.PREFLIGHT_METHODS_NOROUTE="GET,HEAD,OPTIONS",exports.PREFLIGHT_METHODS_ROUTED="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS";
1
+ "use strict";var d=Object.defineProperty;var i=(e,r)=>d(e,"name",{value:r,configurable:!0});var p=Object.defineProperty,o=i((e,r)=>p(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PREFLIGHT_METHODS_ROUTED=exports.PREFLIGHT_METHODS_NOROUTE=exports.corsPreflight=exports.cors=exports.isRoutedOrigin=exports.callerTenantOf=exports.CALLER_TENANT=exports.GATEWAY_OWNED_HEADERS=exports.sendError=exports.stampForwardingHeaders=exports.clientIpOf=exports.resolveClientIp=exports.CLIENT_IP=exports.rememberRawTarget=exports.rawTargetOf=exports.RAW_TARGET=exports.canonicalPath=exports.canonicalize=exports.ENCODED_STRUCTURE=void 0,exports.stripOwnedHeaders=stripOwnedHeaders;const gateway_errors_1=require("../gateway-errors"),client_ip_1=require("../client-ip"),manifest_1=require("../manifest");var canonical_path_1=require("../canonical-path");Object.defineProperty(exports,"ENCODED_STRUCTURE",{enumerable:!0,get:o(function(){return canonical_path_1.ENCODED_STRUCTURE},"get")}),Object.defineProperty(exports,"canonicalize",{enumerable:!0,get:o(function(){return canonical_path_1.canonicalize},"get")}),Object.defineProperty(exports,"canonicalPath",{enumerable:!0,get:o(function(){return canonical_path_1.canonicalPath},"get")}),Object.defineProperty(exports,"RAW_TARGET",{enumerable:!0,get:o(function(){return canonical_path_1.RAW_TARGET},"get")}),Object.defineProperty(exports,"rawTargetOf",{enumerable:!0,get:o(function(){return canonical_path_1.rawTargetOf},"get")}),Object.defineProperty(exports,"rememberRawTarget",{enumerable:!0,get:o(function(){return canonical_path_1.rememberRawTarget},"get")}),exports.CLIENT_IP=Symbol.for("lensmcp.gateway.clientIp");const FORWARDED=Symbol.for("lensmcp.gateway.forwarded"),peerOnly=(0,client_ip_1.createClientIpResolver)({}),resolverOf=o(e=>e.clientIp??peerOnly,"resolverOf"),peerOf=o(e=>e.socket?.remoteAddress??"","peerOf"),resolveClientIp=o((e,r)=>{const t=resolverOf(e).clientIpOf(r,peerOf(r));return r[exports.CLIENT_IP]=t,t},"resolveClientIp");exports.resolveClientIp=resolveClientIp;const clientIpOf=o(e=>e[exports.CLIENT_IP]??peerOf(e),"clientIpOf");exports.clientIpOf=clientIpOf;const stampForwardingHeaders=o((e,r)=>{const t=r;if(t[FORWARDED])return;t[FORWARDED]=!0;const s=peerOf(r);r.headers["x-forwarded-for"]=resolverOf(e).forwardedFor(r,s),r.headers["x-forwarded-proto"]=e.https?"https":"http";const n=r.headers.host;(resolverOf(e).trustProxyHops===0||!r.headers["x-forwarded-host"])&&(n?r.headers["x-forwarded-host"]=n:delete r.headers["x-forwarded-host"])},"stampForwardingHeaders");exports.stampForwardingHeaders=stampForwardingHeaders;const sendError=o((e,r,t,s,n,c,l)=>{const a=(0,gateway_errors_1.ensureRequestId)(r);return e.headersSent||e.writeHead(t,{"content-type":gateway_errors_1.GATEWAY_ERROR_CONTENT_TYPE,"x-request-id":a,...l??{}}),e.end((0,gateway_errors_1.gatewayErrorBody)(t,s,a,n,c)),a},"sendError");exports.sendError=sendError,exports.GATEWAY_OWNED_HEADERS=["x-internal-token","x-api-key-id","x-api-key-workspace","x-user-id","x-tenant-id","x-user-roles","x-user-permissions"],exports.CALLER_TENANT=Symbol.for("lensmcp.gateway.callerTenant");const CALLER_TENANT_VALUE=/^[\x21-\x2b\x2d-\x7e]{1,128}$/;function stripOwnedHeaders(e){const r=e.headers["x-tenant-id"];e[exports.CALLER_TENANT]=typeof r=="string"&&CALLER_TENANT_VALUE.test(r)?r:void 0;for(const t of exports.GATEWAY_OWNED_HEADERS)delete e.headers[t]}i(stripOwnedHeaders,"stripOwnedHeaders"),o(stripOwnedHeaders,"stripOwnedHeaders");const callerTenantOf=o(e=>e[exports.CALLER_TENANT],"callerTenantOf");exports.callerTenantOf=callerTenantOf;const isRoutedOrigin=o((e,r)=>{if(!e||e==="null")return!1;let t;try{t=new URL(e).hostname.toLowerCase()}catch{return!1}return t?t==="localhost"||t==="127.0.0.1"||t==="::1"?!0:r.some(s=>!!s.host&&(0,manifest_1.hostMatches)(s.host,t)):!1},"isRoutedOrigin");exports.isRoutedOrigin=isRoutedOrigin;const cors=o((e,r,t)=>{const s=e.headers.origin;typeof s!="string"||s===""||(r.setHeader("vary","Origin"),(0,exports.isRoutedOrigin)(s,t)&&(r.setHeader("access-control-allow-origin",s),r.setHeader("access-control-allow-credentials","true")))},"cors");exports.cors=cors;const corsPreflight=o((e,r,t,s)=>{(0,exports.cors)(e,r,s),r.writeHead(204,{"access-control-allow-methods":t,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),r.end()},"corsPreflight");exports.corsPreflight=corsPreflight,exports.PREFLIGHT_METHODS_NOROUTE="GET,HEAD,OPTIONS",exports.PREFLIGHT_METHODS_ROUTED="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS";
@@ -1 +1 @@
1
- "use strict";var O=Object.defineProperty;var w=(p,i)=>O(p,"name",{value:i,configurable:!0});var E=Object.defineProperty,h=w((p,i)=>E(p,"name",{value:i,configurable:!0}),"h");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(p){const{rt:i,obs:d,auth:y,proxy:m,hooks:c}=p,g=h(e=>(e.headers.host||"").split(":")[0],"hostOf"),_=h((e,r)=>{if((0,edge_1.cors)(e,r,i.routes),e.method==="OPTIONS"){r.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_NOROUTE,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),r.end();return}const s=(e.headers.host||"").split(":")[0],t=i.routes.filter(o=>o.host&&(0,manifest_1.hostMatches)(o.host,s)&&o.prefix),n=(e.url??"/").split("?")[0];if((n==="/"||n==="/index.html"||n==="")&&i.routes.some(o=>o.host&&(0,manifest_1.hostMatches)(o.host,s)&&o.project===observability_1.LENS_DASHBOARD_PROJECT)){(0,chooser_1.serveChooser)(r,i.wsKey,e);return}if(t.length>0&&(n==="/health"||n==="/healthz")){const o=t.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")})),f=o.every(l=>l.state!=="down"||!0);r.writeHead(f?200:503,{"content-type":"application/json"}),r.end(JSON.stringify({status:"ok",host:s,services:o},null,2));return}if(t.length>0&&n==="/.well-known/services"){r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({host:s,services:t.map(o=>({prefix:o.prefix,project:o.project,internal:!!o.internal}))},null,2));return}(0,edge_1.sendError)(r,e,404,"not_found",t.length>0?`No service mounted at ${n}. Mounted on ${s}: ${[...new Set(t.map(o=>o.prefix))].join(", ")} (+ /health, /.well-known/services)`:"No gateway route for this hostname.")},"handleNoRoute"),S=h(async(e,r)=>{if(!e.headers.host){const n=e.authority??e.headers[":authority"];n&&(e.headers.host=n)}for(const n of edge_1.GATEWAY_OWNED_HEADERS)delete e.headers[n];(0,gateway_errors_1.ensureRequestId)(e);const s=d.beginTrace(e);s&&(d.traceStep(s,"received",{host:s.host,method:s.method,url:s.url}),e.__lensmcpTrace=s);const t=c.httpContext(e,r,s);try{const n=e.url??"/",o=(0,edge_1.canonicalize)(n);if(o===void 0){d.traceStep(s,"bad-path",{}),d.finishTrace(s,"gateway",400),(0,edge_1.sendError)(r,e,400,"bad_request","Bad Request");return}const f=o.policy,l=o.path+o.query;if(l!==n&&(0,edge_1.rememberRawTarget)(e,n),e.url=l,(0,edge_1.resolveClientIp)(i,e),c.has("onRequest")&&(await c.run("onRequest",t),t.responded()))return;const a=(0,manifest_1.matchRoute)(i.routes,g(e),f+o.query);if(!a){_(e,r);return}if(t.route=a,d.traceStep(s,"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,r,i.routes),r.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_ROUTED,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),r.end();return}if((0,edge_1.cors)(e,r,i.routes),c.has("beforeAuth")&&(await c.run("beforeAuth",t),t.responded()))return;if(a.internal){const u=y.internal(e,a,s);if(!u.ok){d.finishTrace(s,a.project,u.status,void 0,a),(0,edge_1.sendError)(r,e,u.status,u.reason,u.detail);return}t.caller=u.caller}else{const u=y.public(e,a,s,f);if(!u.ok){d.finishTrace(s,a.project,u.status,void 0,a),(0,edge_1.sendError)(r,e,u.status,u.reason,u.detail);return}if(t.claims=u.claims,c.has("afterAuth")&&(await c.run("afterAuth",t),t.responded()))return}const v=a.pool?i.serviceKeys[(0,service_keys_1.serviceKeyId)(a.svc?.wsKey,a.project)]:void 0;if(v&&(e.headers["x-internal-token"]=v),(0,edge_1.stampForwardingHeaders)(i,e),c.has("beforeForward")&&(await c.run("beforeForward",t),t.responded()))return;c.has("onResponse")&&r.on("close",()=>{t.status=r.statusCode??0,c.run("onResponse",t)}),m.forward(a,e,r)}catch(n){t.error=n,console.error(`[gateway] ${new Date().toISOString()} pipeline error traceId=${(0,gateway_errors_1.ensureRequestId)(e)}: ${n?.stack??String(n)}`),c.has("onError")&&await c.run("onError",t),t.responded()||(0,edge_1.sendError)(r,e,500,"internal","Internal gateway error.")}},"runPipeline");return(e,r)=>{S(e,r)}}w(createHandler,"createHandler"),h(createHandler,"createHandler");
1
+ "use strict";var b=Object.defineProperty;var w=(p,i)=>b(p,"name",{value:i,configurable:!0});var S=Object.defineProperty,h=w((p,i)=>S(p,"name",{value:i,configurable:!0}),"h");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(p){const{rt:i,obs:d,auth:y,proxy:m,hooks:c}=p,g=h(e=>(e.headers.host||"").split(":")[0],"hostOf"),_=h((e,r)=>{if((0,edge_1.cors)(e,r,i.routes),e.method==="OPTIONS"){r.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_NOROUTE,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),r.end();return}const s=(e.headers.host||"").split(":")[0],t=i.routes.filter(o=>o.host&&(0,manifest_1.hostMatches)(o.host,s)&&o.prefix),n=(e.url??"/").split("?")[0];if((n==="/"||n==="/index.html"||n==="")&&i.routes.some(o=>o.host&&(0,manifest_1.hostMatches)(o.host,s)&&o.project===observability_1.LENS_DASHBOARD_PROJECT)){(0,chooser_1.serveChooser)(r,i.wsKey,e);return}if(t.length>0&&(n==="/health"||n==="/healthz")){const o=t.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")})),f=o.every(l=>l.state!=="down"||!0);r.writeHead(f?200:503,{"content-type":"application/json"}),r.end(JSON.stringify({status:"ok",host:s,services:o},null,2));return}if(t.length>0&&n==="/.well-known/services"){r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({host:s,services:t.map(o=>({prefix:o.prefix,project:o.project,internal:!!o.internal}))},null,2));return}(0,edge_1.sendError)(r,e,404,"not_found",t.length>0?`No service mounted at ${n}. Mounted on ${s}: ${[...new Set(t.map(o=>o.prefix))].join(", ")} (+ /health, /.well-known/services)`:"No gateway route for this hostname.")},"handleNoRoute"),O=h(async(e,r)=>{if(!e.headers.host){const n=e.authority??e.headers[":authority"];n&&(e.headers.host=n)}(0,edge_1.stripOwnedHeaders)(e);const s=d.beginTrace(e);(0,gateway_errors_1.ensureRequestId)(e),s&&(d.traceStep(s,"received",{host:s.host,method:s.method,url:s.url}),e.__lensmcpTrace=s);const t=c.httpContext(e,r,s);try{const n=e.url??"/",o=(0,edge_1.canonicalize)(n);if(o===void 0){d.traceStep(s,"bad-path",{}),d.finishTrace(s,"gateway",400),(0,edge_1.sendError)(r,e,400,"bad_request","Bad Request");return}const f=o.policy,l=o.path+o.query;if(l!==n&&(0,edge_1.rememberRawTarget)(e,n),e.url=l,(0,edge_1.resolveClientIp)(i,e),c.has("onRequest")&&(await c.run("onRequest",t),t.responded()))return;const a=(0,manifest_1.matchRoute)(i.routes,g(e),f+o.query);if(!a){_(e,r);return}if(t.route=a,d.traceStep(s,"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,r,i.routes),r.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_ROUTED,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),r.end();return}if((0,edge_1.cors)(e,r,i.routes),c.has("beforeAuth")&&(await c.run("beforeAuth",t),t.responded()))return;if(a.internal){const u=y.internal(e,a,s);if(!u.ok){d.finishTrace(s,a.project,u.status,void 0,a),(0,edge_1.sendError)(r,e,u.status,u.reason,u.detail);return}t.caller=u.caller}else{const u=y.public(e,a,s,f);if(!u.ok){d.finishTrace(s,a.project,u.status,void 0,a),(0,edge_1.sendError)(r,e,u.status,u.reason,u.detail);return}if(t.claims=u.claims,c.has("afterAuth")&&(await c.run("afterAuth",t),t.responded()))return}const v=a.pool?i.serviceKeys[(0,service_keys_1.serviceKeyId)(a.svc?.wsKey,a.project)]:void 0;if(v&&(e.headers["x-internal-token"]=v),(0,edge_1.stampForwardingHeaders)(i,e),c.has("beforeForward")&&(await c.run("beforeForward",t),t.responded()))return;c.has("onResponse")&&r.on("close",()=>{t.status=r.statusCode??0,c.run("onResponse",t)}),m.forward(a,e,r)}catch(n){t.error=n,console.error(`[gateway] ${new Date().toISOString()} pipeline error traceId=${(0,gateway_errors_1.ensureRequestId)(e)}: ${n?.stack??String(n)}`),c.has("onError")&&await c.run("onError",t),t.responded()||(0,edge_1.sendError)(r,e,500,"internal","Internal gateway error.")}},"runPipeline");return(e,r)=>{O(e,r)}}w(createHandler,"createHandler"),h(createHandler,"createHandler");
@@ -1 +1 @@
1
- "use strict";var Re=Object.defineProperty;var R=(r,E)=>Re(r,"name",{value:E,configurable:!0});var Le=Object.defineProperty,d=R((r,E)=>Le(r,"name",{value:E,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.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"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(r,E){return new Promise(u=>{const _=net.createServer();_.unref(),_.once("error",p=>{const f=p.code??"";u(f==="EADDRINUSE"||f==="EACCES"?"in-use":"unavailable")}),_.listen(E===void 0?{port:r}:{port:r,host:E},()=>_.close(()=>u("free")))})}R(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(r){for(const E of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(r,E)==="in-use")return!1;return!0}R(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(r,E,u){return new Promise(_=>{let p=!1;const f=net.createConnection({host:r,port:E});f.unref();const C=setTimeout(()=>y(!1),u);C.unref?.();function y(O){p||(p=!0,clearTimeout(C),f.destroy(),_(O))}R(y,"y"),d(y,"finish"),f.once("connect",()=>y(!0)),f.once("error",()=>y(!1))})}R(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(r,E=1e3){return await probeConnect("127.0.0.1",r,E)?!0:probeConnect("::1",r,E)}R(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(r,E=1500){try{const u=new AbortController,_=setTimeout(()=>u.abort(),E);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${r}/__lensmcp/watcher-health`,{signal:u.signal});if(!p.ok)return null;const f=await p.json();if(typeof f?.lastFsSetEventAt!="number")return null;const C=Array.isArray(f.recentEvents)?f.recentEvents.filter(y=>y&&typeof y.path=="string"&&typeof y.at=="number"&&typeof y.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(y.kind)):[];return{schemaVersion:f.schemaVersion===2?2:1,ready:f.ready!==!1,sequence:typeof f.sequence=="number"?f.sequence:0,lastFsSetEventAt:f.lastFsSetEventAt,fsSetEvents:typeof f.fsSetEvents=="number"?f.fsSetEvents:0,recentEvents:C}}finally{clearTimeout(_)}}catch{return null}}R(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(r=>new Promise(E=>{setTimeout(E,r).unref?.()}),"sleepUnref");async function waitForPortFree(r,E,u={}){const _=u.isPortFree??probePortFree,p=Math.max(1,u.intervalMs??types_1.PORT_FREE_POLL_MS),f=Date.now()+Math.max(0,E);for(;;){if(await _(r))return!0;const C=f-Date.now();if(C<=0)return!1;await sleepUnref(Math.min(p,C))}}R(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(r){const E=d(u=>Number.isInteger(u)&&u>0&&u<65536?u:void 0,"valid");for(let u=0;u<r.length;u+=1){const _=r[u];if(_==="--port")return E(Number(r[u+1]));if(_.startsWith("--port="))return E(Number(_.slice(7)))}}R(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(r){return r.signalCode?`killed by ${r.signalCode}`:r.exitCode===null?"exit status unknown":`exit code ${r.exitCode}`}R(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");async function claimPort(r,E,u,_){let p=r;for(;E.has(p)||_?.has(p)||!await u(p);)p+=1;return E.add(p),p}R(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(r,E,u,_){const{emit:p}=E,f=_?.isPortFree??probePortFree,C=_?.probeWatcherHealth??probeWatcherHealth,y=[],O=new Map,T=new Map,F=new Map,H=new Map,K=new Set;let D;const N=new Map,P=new Set,J=new Set,Y=new Set,m=new Set,L=new Map,j=new WeakMap,B=new Set,Q=d((e,s,n)=>{if(B.has(e))return;B.add(e);const t=setInterval(()=>{(async()=>{if(r.stopped()||P.has(e)){clearInterval(t),B.delete(e);return}await f(s)&&(clearInterval(t),B.delete(e),console.log(`[gateway] lens child ${e}: :${s} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),I=d(async(e,s,n)=>{const t=await claimPort(e,s,f,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"),le=d((e,s,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=s:e.endsWith(":browser-capture")&&(a.capture=s))},"attachRespawnedGenerationChild"),W=d(e=>{if(!(e.exitCode!==null||e.signalCode!==null)){J.add(e);try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{e.kill("SIGKILL")}catch{}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}},"retireChild"),ce=d((e,s)=>{const n=N.get(e);!n||n===s||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??"?"}).`),W(n))},"retireLabelHolder"),k=d((e,s,n,t,o=!1,a,i,l)=>{const h=Date.now(),c=(0,node_child_process_1.spawn)(s,n,{cwd:i??r.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,r.root,i??r.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});y.push(c),ce(e,c),N.set(e,c),le(e,c,t);const S=e==="vite"||e.endsWith(":vite"),M=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,v=M&&M!==r.eventFile?M:void 0,w=S?viteArgPort(n):l;S&&T.set(c,{key:t?.LENSMCP_FRONTEND_KEY??`${i??r.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??r.root,spawnedAt:h,port:w,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...v?{bus:v}:{}});const g=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return g&&w!==void 0&&(p("info",`lens child starting: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-starting",project:g,label:e,pid:c.pid,port:w},void 0,v),(async()=>{const b=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<b;){if(c.exitCode!==null||c.signalCode!==null||P.has(e))return;if(await probePortListening(w,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-up",project:g,label:e,pid:c.pid,port:w},void 0,v);return}await sleepUnref(150)}})().catch(()=>{})),c.on("exit",(b,G)=>{const re=y.indexOf(c);re>=0&&y.splice(re,1),N.get(e)===c&&N.delete(e);const A=T.get(c);if(T.delete(c),J.delete(c))return;const ye=P.delete(e);if(r.stopped()||ye||(g?p("warning",`lens child exited: ${g}`,`cluster-lens-child:${g}`,{kind:"lens-child-exit",project:g,label:e,pid:c.pid,code:b,signal:G},void 0,v):A&&p("warning",`lens frontend exited: ${A.project}`,`cluster-lens-child:${A.project}`,{kind:"lens-child-exit",project:A.project,label:e,pid:c.pid,code:b,signal:G,generation:A.generation,port:A.port,ranMs:Date.now()-h},void 0,v),A&&ue(c,A)))return;const se=Date.now()-h,$=O.get(e)??{attempts:0,portConflicts:0};(async()=>{const ve=w!==void 0&&!await f(w),ae=(0,lifecycle_1.isPortConflictExit)({code:b,signal:G,ranMs:se,portHeld:ve},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ae&&$.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)$.portConflicts+=1,O.set(e,$),console.warn(`[gateway] lens child ${e} lost the :${w} bind race (EADDRINUSE, code=${b}); waiting for the port, then retrying (port-retry ${$.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(ae){$.portConflicts=0,O.set(e,$),console.warn(`[gateway] lens child ${e}: :${w} 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} (:${w} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:w}),Q(e,w,()=>k(e,s,n,t,o,a,i,l));return}else{$.portConflicts=0,se>=HEAL_HEALTHY_MS&&($.attempts=0);const ie=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**$.attempts);$.attempts+=1,O.set(e,$),console.warn(`[gateway] lens child ${e} exited (code=${b} sig=${G}); auto-healing in ${Math.round(ie/1e3)}s (attempt ${$.attempts}).`),await sleepUnref(ie)}r.stopped()||P.has(e)||(w!==void 0&&!await waitForPortFree(w,types_1.PORT_FREE_WAIT_MS,{isPortFree:f,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${w} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(r.stopped()||P.has(e))&&k(e,s,n,t,o,a,i,l))})().catch(()=>{})}),c},"spawnManagedChild"),Z=d(e=>{const s=`${e.wsKey}:${e.project}`,n=L.get(s);if(n)return n;const t={key:s,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};L.set(s,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,j.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,j.set(e.frontend,t),t},"registerFrontend"),de=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const s=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==s);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),ee=d((e,s,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;P.delete(a),P.delete(i);const l=d((S,M,v,w,g,b)=>k(S,M,v,w,g,b,e.workspaceRoot),"spawnInRoot"),h=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:s,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project,LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n,...de(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:d(S=>console.log(S),"log")},l),c={ownerKey:e.key,generation:t,port:s,labelPrefix:o,vite:h?.vite??N.get(a),capture:N.get(i)};return e.generations.add(c),c},"spawnFrontendGeneration"),x=d((e,s)=>{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)P.add(t),o&&W(o);s&&m.delete(e.port)},"terminateGeneration"),pe=d(async e=>{const s=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<s;){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 C(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),q=d(async e=>{const s=e?.vite;return!!(e&&s&&s.exitCode===null&&s.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),V=d(e=>!e.disposed&&!r.stopped()&&L.get(e.key)===e,"frontendIsCurrent"),U=d((e,s)=>{if(!V(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=s,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const l=new Set;for(const g of L.values())g!==e&&l.add(g.desiredPort);const h=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,c=await I(e.desiredPort,m,{reserved:l,heldBy:h});if(a=c,!V(e))return m.delete(c),!1;o=ee(e,c,"replacement"),e.candidate=o;const S=await q(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${c} reason=${s}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,oldPort:n?.port,newPort:c,oldServing:S},void 0,e.eventFile===r.eventFile?void 0:e.eventFile);const M=await pe(o);if(!V(e))return x(o,!0),e.candidate=void 0,!1;if(!M){x(o,!0),e.candidate=void 0;const g=await q(n);return e.state=g?"ready":"failed",g&&(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:s,generation:o.generation,port:c,durationMs:Date.now()-i,oldKept:g},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}for(const g of e.routes)g.target=`http://localhost:${o.port}`,g.lens&&(g.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 w=(0,hmr_reload_1.broadcastFullReload)(r,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}${w>0?`; reloading ${w} 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:s,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:w},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),n){const g=`${n.labelPrefix}browser-capture`;P.add(g),n.capture&&W(n.capture),setTimeout(()=>x(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){x(o,!0),!o&&a!==void 0&&m.delete(a),e.candidate=void 0;const h=await q(n);e.state=h?"ready":"failed",h&&(e.reason=void 0,e.startedAt=void 0);const c=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${c}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:s,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:h,error:c},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),te=d((e,s)=>{e.active=ee(e,s,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),ue=d((e,s)=>{const n=L.get(s.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${s.generation} :${s.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),U(n,"child-exit")),!0)},"handleManagedViteExit");r.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const s=j.get(e);return s?{state:s.state,generation:s.candidate?.generation??s.active?.generation??s.generation,reason:s.reason,startedAt:s.startedAt}:void 0},recover(e,s){const n=j.get(e);return n?U(n,s):Promise.resolve(!1)}}:void 0;let z=!1,oe=0;const Ee=d(()=>{z||Date.now()-oe<types_1.POD_STALE_SCAN_MS||(z=!0,oe=Date.now(),(async()=>{for(const e of[...L.values()]){if(r.stopped())return;const s=Date.now(),n=(0,scope_1.readSourceSetOptions)(e.workspaceRoot),t=await(0,lifecycle_1.sourceSetSnapshot)(e.workspaceRoot,e.sourceDirs,n.exclude,{ignoreTests:n.ignoreTests});if(L.get(e.key)!==e)continue;const o=e.sourceSnapshot,a=e.sourceScanStartedAt;if(e.sourceSnapshot=t,e.sourceScanStartedAt=s,!o)continue;const i=Date.now(),l=(0,lifecycle_1.diffSourceSets)(o,t,i,(0,lifecycle_1.sourceChangeWindowMs)(i,a,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));l&&(e.sourceChange=l,p("info",`source file set changed for ${e.key}`,`cluster-lens-app:${e.project}`,{kind:"source-set-changed",scoped:!0,wsKey:e.wsKey,project:e.project,added:l.added,removed:l.removed,truncated:l.truncated},void 0,e.eventFile===r.eventFile?void 0:e.eventFile))}})().catch(()=>{}).finally(()=>{z=!1}))},"scanFrontendSourceSets");let X=!1;const fe=d(async(e,s)=>{if(!X){X=!0;try{for(const[n,t]of[...T]){if(r.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(!s)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},F.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`;F.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);try{n.kill("SIGKILL")}catch{}}}finally{X=!1}}},"probeLensChildren"),he=d(()=>{if(D||!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};D=setInterval(()=>{if(r.stopped()||T.size===0||(ne(),!types_1.POD_RECYCLE_ENABLED))return;Ee();const s=Date.now();for(const[n,t]of T){const o=L.get(t.key),a=o?.sourceChange?.changedAt??0,i=F.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,s,e)&&((H.get(t.key)??0)>=a||K.has(t.key)||(K.add(t.key),(async()=>{try{const l=o?.sourceChange,h=l&&!l.truncated?[...l.added,...l.removed].map(v=>path.resolve(t.workspaceRoot,v)):[],c=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await C(t.port),a,l?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,h):"unknown";if(c==="heard"){H.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(c==="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;F.set(t.key,Date.now());const S=L.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&S){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${c==="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:S.wsKey,project:t.project,reason:"stale-source-set",watcher:c,added:l?.added,removed:l?.removed},void 0,t.bus),await U(S,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${c==="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:c},void 0,t.bus);const M=(0,hmr_reload_1.broadcastFullReload)(r,t.key);M>0&&console.log(`[gateway] \u2192 told ${M} open tab(s) to reload (stale optimize-dep generation)`);try{n.kill("SIGTERM")}catch{}setTimeout(()=>{if(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.`);try{n.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{K.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),D.unref?.()},"startLensSweeper"),ge=d(async()=>{const e=[...new Map(r.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],s=u.dashboard!==!1,n=u.mcp!==!1&&e.length>0;if(s){const t=(0,lens_frontend_1.findDashboardBundle)(r.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)(r.root),a=await I(u.dashboardPort??o.dashboardPort,Y),i=u.lensHost??"lensmcp.local",l=o.basePath;r.registry.appendRoutes(r.wsKey,[{host:i,prefix:l,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),r.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:r.root,base:l,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${i}${l}/)`),p("info",`lens dashboard up: ${i}${l} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:l}),k("lens-dashboard",process.execPath,[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:l},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(r.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)(r.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})`),k("lens-mcp",process.execPath,[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_WORKSPACE_ROOT:r.root},!1,void 0,void 0,a)},"spawnMcp");await f(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}),Q("lens-mcp",a,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(r.registry.get(r.wsKey)?.routes??r.routes).filter(h=>h.lens?.project===t.project),i=await I(t.port,m);for(const h of a)h.lens&&(h.lens.port=i),h.target=`http://localhost:${i}`;t.port=i;const l=Z({wsKey:r.wsKey,project:t.project,workspaceRoot:r.root,projectRoot:t.projectRoot,eventFile:r.eventFile,routes:a,frontend:t,desiredPort:o});try{te(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(h){m.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${h.message}`)}}he()},"bootSingletonsAndApps"),we=d(async e=>{if(u.dashboard===!1)return;const s=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!s)return;const n=(0,scope_1.readLensScope)(e.root),t=u.lensHost??"lensmcp.local",o=n.basePath,a=await I(n.dashboardPort,Y),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");return P.delete(i),r.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()}),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}),k(i,process.execPath,[s],{LENSMCP_EVENT_FILE:l,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey},!1,void 0,e.root,a),()=>{P.add(i),Y.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const h=N.get(i);h&&W(h),N.delete(i)}},"hostWorkspaceDashboard"),_e=d(async e=>{const s=new Map;for(const o of e.routes){if(!o.lens)continue;const a=s.get(o.lens.project)??[];a.push(o),s.set(o.lens.project,a)}if(s.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of s){const i=a[0].lens;if(i.started)continue;const l=await I(i.port,m),h=i.port;for(const S of a)S.lens.started=!0,S.lens.port=l,S.target=`http://localhost:${l}`;const c=Z({wsKey:e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:h});t.push(c),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{te(c,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(S){m.delete(l),c.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${S.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])x(a,!0);for(const a of o.routes)a.lens&&j.delete(a.lens);j.delete(o.frontend),F.delete(o.key),H.delete(o.key),K.delete(o.key),L.delete(o.key)}}},"hostWorkspaceApps"),Se=d(async(e=2500)=>{D&&(clearInterval(D),D=void 0);const s=[...y];for(const o of s)try{o.kill("SIGTERM")}catch{}const n=d(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&s.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of s)if(n(o))try{o.kill("SIGKILL")}catch{}},"reapAll"),ne=d(()=>fe({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:k,bootSingletonsAndApps:ge,hostWorkspaceDashboard:we,hostWorkspaceApps:_e,probeLensFrontends:ne,reapAll:Se}}R(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
1
+ "use strict";var Re=Object.defineProperty;var P=(r,E)=>Re(r,"name",{value:E,configurable:!0});var Pe=Object.defineProperty,d=P((r,E)=>Pe(r,"name",{value:E,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.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"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(r,E){return new Promise(u=>{const _=net.createServer();_.unref(),_.once("error",p=>{const f=p.code??"";u(f==="EADDRINUSE"||f==="EACCES"?"in-use":"unavailable")}),_.listen(E===void 0?{port:r}:{port:r,host:E},()=>_.close(()=>u("free")))})}P(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(r){for(const E of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(r,E)==="in-use")return!1;return!0}P(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(r,E,u){return new Promise(_=>{let p=!1;const f=net.createConnection({host:r,port:E});f.unref();const C=setTimeout(()=>v(!1),u);C.unref?.();function v(O){p||(p=!0,clearTimeout(C),f.destroy(),_(O))}P(v,"P"),d(v,"finish"),f.once("connect",()=>v(!0)),f.once("error",()=>v(!1))})}P(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(r,E=1e3){return await probeConnect("127.0.0.1",r,E)?!0:probeConnect("::1",r,E)}P(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(r,E=1500){try{const u=new AbortController,_=setTimeout(()=>u.abort(),E);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${r}/__lensmcp/watcher-health`,{signal:u.signal});if(!p.ok)return null;const f=await p.json();if(typeof f?.lastFsSetEventAt!="number")return null;const C=Array.isArray(f.recentEvents)?f.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:f.schemaVersion===2?2:1,ready:f.ready!==!1,sequence:typeof f.sequence=="number"?f.sequence:0,lastFsSetEventAt:f.lastFsSetEventAt,fsSetEvents:typeof f.fsSetEvents=="number"?f.fsSetEvents:0,recentEvents:C}}finally{clearTimeout(_)}}catch{return null}}P(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(r=>new Promise(E=>{setTimeout(E,r).unref?.()}),"sleepUnref");async function waitForPortFree(r,E,u={}){const _=u.isPortFree??probePortFree,p=Math.max(1,u.intervalMs??types_1.PORT_FREE_POLL_MS),f=Date.now()+Math.max(0,E);for(;;){if(await _(r))return!0;const C=f-Date.now();if(C<=0)return!1;await sleepUnref(Math.min(p,C))}}P(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(r){const E=d(u=>Number.isInteger(u)&&u>0&&u<65536?u:void 0,"valid");for(let u=0;u<r.length;u+=1){const _=r[u];if(_==="--port")return E(Number(r[u+1]));if(_.startsWith("--port="))return E(Number(_.slice(7)))}}P(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(r){return r.signalCode?`killed by ${r.signalCode}`:r.exitCode===null?"exit status unknown":`exit code ${r.exitCode}`}P(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");async function claimPort(r,E,u,_){let p=r;for(;E.has(p)||_?.has(p)||!await u(p);)p+=1;return E.add(p),p}P(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(r,E,u,_){const{emit:p}=E,f=_?.isPortFree??probePortFree,C=(u.ports?.length?u.ports:[443]).join(","),v=_?.probeWatcherHealth??probeWatcherHealth,O=[],K=new Map,T=new Map,F=new Map,Y=new Map,B=new Set;let D;const N=new Map,$=new Set,Q=new Set,q=new Set,m=new Set,L=new Map,j=new WeakMap,W=new Set,Z=d((e,s,n)=>{if(W.has(e))return;W.add(e);const t=setInterval(()=>{(async()=>{if(r.stopped()||$.has(e)){clearInterval(t),W.delete(e);return}await f(s)&&(clearInterval(t),W.delete(e),console.log(`[gateway] lens child ${e}: :${s} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),I=d(async(e,s,n)=>{const t=await claimPort(e,s,f,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"),ce=d((e,s,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=s:e.endsWith(":browser-capture")&&(a.capture=s))},"attachRespawnedGenerationChild"),G=d(e=>{if(!(e.exitCode!==null||e.signalCode!==null)){Q.add(e);try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{e.kill("SIGKILL")}catch{}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}},"retireChild"),de=d((e,s)=>{const n=N.get(e);!n||n===s||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??"?"}).`),G(n))},"retireLabelHolder"),k=d((e,s,n,t,o=!1,a,i,l)=>{const h=Date.now(),c=(0,node_child_process_1.spawn)(s,n,{cwd:i??r.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,r.root,i??r.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});O.push(c),de(e,c),N.set(e,c),ce(e,c,t);const S=e==="vite"||e.endsWith(":vite"),M=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=M&&M!==r.eventFile?M:void 0,w=S?viteArgPort(n):l;S&&T.set(c,{key:t?.LENSMCP_FRONTEND_KEY??`${i??r.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??r.root,spawnedAt:h,port:w,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...y?{bus:y}:{}});const g=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return g&&w!==void 0&&(p("info",`lens child starting: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-starting",project:g,label:e,pid:c.pid,port:w},void 0,y),(async()=>{const b=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<b;){if(c.exitCode!==null||c.signalCode!==null||$.has(e))return;if(await probePortListening(w,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-up",project:g,label:e,pid:c.pid,port:w},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),c.on("exit",(b,H)=>{const se=O.indexOf(c);se>=0&&O.splice(se,1),N.get(e)===c&&N.delete(e);const A=T.get(c);if(T.delete(c),Q.delete(c))return;const ve=$.delete(e);if(r.stopped()||ve||(g?p("warning",`lens child exited: ${g}`,`cluster-lens-child:${g}`,{kind:"lens-child-exit",project:g,label:e,pid:c.pid,code:b,signal:H},void 0,y):A&&p("warning",`lens frontend exited: ${A.project}`,`cluster-lens-child:${A.project}`,{kind:"lens-child-exit",project:A.project,label:e,pid:c.pid,code:b,signal:H,generation:A.generation,port:A.port,ranMs:Date.now()-h},void 0,y),A&&Ee(c,A)))return;const ae=Date.now()-h,R=K.get(e)??{attempts:0,portConflicts:0};(async()=>{const Le=w!==void 0&&!await f(w),ie=(0,lifecycle_1.isPortConflictExit)({code:b,signal:H,ranMs:ae,portHeld:Le},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ie&&R.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)R.portConflicts+=1,K.set(e,R),console.warn(`[gateway] lens child ${e} lost the :${w} bind race (EADDRINUSE, code=${b}); waiting for the port, then retrying (port-retry ${R.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(ie){R.portConflicts=0,K.set(e,R),console.warn(`[gateway] lens child ${e}: :${w} 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} (:${w} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:w}),Z(e,w,()=>k(e,s,n,t,o,a,i,l));return}else{R.portConflicts=0,ae>=HEAL_HEALTHY_MS&&(R.attempts=0);const le=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**R.attempts);R.attempts+=1,K.set(e,R),console.warn(`[gateway] lens child ${e} exited (code=${b} sig=${H}); auto-healing in ${Math.round(le/1e3)}s (attempt ${R.attempts}).`),await sleepUnref(le)}r.stopped()||$.has(e)||(w!==void 0&&!await waitForPortFree(w,types_1.PORT_FREE_WAIT_MS,{isPortFree:f,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${w} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(r.stopped()||$.has(e))&&k(e,s,n,t,o,a,i,l))})().catch(()=>{})}),c},"spawnManagedChild"),ee=d(e=>{const s=`${e.wsKey}:${e.project}`,n=L.get(s);if(n)return n;const t={key:s,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};L.set(s,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,j.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,j.set(e.frontend,t),t},"registerFrontend"),pe=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const s=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==s);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),te=d((e,s,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;$.delete(a),$.delete(i);const l=d((S,M,y,w,g,b)=>k(S,M,y,w,g,b,e.workspaceRoot),"spawnInRoot"),h=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:s,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project,LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n,...pe(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:d(S=>console.log(S),"log")},l),c={ownerKey:e.key,generation:t,port:s,labelPrefix:o,vite:h?.vite??N.get(a),capture:N.get(i)};return e.generations.add(c),c},"spawnFrontendGeneration"),x=d((e,s)=>{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)$.add(t),o&&G(o);s&&m.delete(e.port)},"terminateGeneration"),ue=d(async e=>{const s=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<s;){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"),V=d(async e=>{const s=e?.vite;return!!(e&&s&&s.exitCode===null&&s.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),U=d(e=>!e.disposed&&!r.stopped()&&L.get(e.key)===e,"frontendIsCurrent"),z=d((e,s)=>{if(!U(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=s,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const l=new Set;for(const g of L.values())g!==e&&l.add(g.desiredPort);const h=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,c=await I(e.desiredPort,m,{reserved:l,heldBy:h});if(a=c,!U(e))return m.delete(c),!1;o=te(e,c,"replacement"),e.candidate=o;const S=await V(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${c} reason=${s}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,oldPort:n?.port,newPort:c,oldServing:S},void 0,e.eventFile===r.eventFile?void 0:e.eventFile);const M=await ue(o);if(!U(e))return x(o,!0),e.candidate=void 0,!1;if(!M){x(o,!0),e.candidate=void 0;const g=await V(n);return e.state=g?"ready":"failed",g&&(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:s,generation:o.generation,port:c,durationMs:Date.now()-i,oldKept:g},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}for(const g of e.routes)g.target=`http://localhost:${o.port}`,g.lens&&(g.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 y=o.vite?T.get(o.vite):void 0;y&&(y.role="active",y.listening=!0);const w=(0,hmr_reload_1.broadcastFullReload)(r,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}${w>0?`; reloading ${w} 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:s,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:w},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),n){const g=`${n.labelPrefix}browser-capture`;$.add(g),n.capture&&G(n.capture),setTimeout(()=>x(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){x(o,!0),!o&&a!==void 0&&m.delete(a),e.candidate=void 0;const h=await V(n);e.state=h?"ready":"failed",h&&(e.reason=void 0,e.startedAt=void 0);const c=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${c}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:s,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:h,error:c},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),oe=d((e,s)=>{e.active=te(e,s,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),Ee=d((e,s)=>{const n=L.get(s.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${s.generation} :${s.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),z(n,"child-exit")),!0)},"handleManagedViteExit");r.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const s=j.get(e);return s?{state:s.state,generation:s.candidate?.generation??s.active?.generation??s.generation,reason:s.reason,startedAt:s.startedAt}:void 0},recover(e,s){const n=j.get(e);return n?z(n,s):Promise.resolve(!1)}}:void 0;let X=!1,ne=0;const fe=d(()=>{X||Date.now()-ne<types_1.POD_STALE_SCAN_MS||(X=!0,ne=Date.now(),(async()=>{for(const e of[...L.values()]){if(r.stopped())return;const s=Date.now(),n=(0,scope_1.readSourceSetOptions)(e.workspaceRoot),t=await(0,lifecycle_1.sourceSetSnapshot)(e.workspaceRoot,e.sourceDirs,n.exclude,{ignoreTests:n.ignoreTests});if(L.get(e.key)!==e)continue;const o=e.sourceSnapshot,a=e.sourceScanStartedAt;if(e.sourceSnapshot=t,e.sourceScanStartedAt=s,!o)continue;const i=Date.now(),l=(0,lifecycle_1.diffSourceSets)(o,t,i,(0,lifecycle_1.sourceChangeWindowMs)(i,a,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));l&&(e.sourceChange=l,p("info",`source file set changed for ${e.key}`,`cluster-lens-app:${e.project}`,{kind:"source-set-changed",scoped:!0,wsKey:e.wsKey,project:e.project,added:l.added,removed:l.removed,truncated:l.truncated},void 0,e.eventFile===r.eventFile?void 0:e.eventFile))}})().catch(()=>{}).finally(()=>{X=!1}))},"scanFrontendSourceSets");let J=!1;const he=d(async(e,s)=>{if(!J){J=!0;try{for(const[n,t]of[...T]){if(r.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(!s)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},F.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`;F.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);try{n.kill("SIGKILL")}catch{}}}finally{J=!1}}},"probeLensChildren"),ge=d(()=>{if(D||!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};D=setInterval(()=>{if(r.stopped()||T.size===0||(re(),!types_1.POD_RECYCLE_ENABLED))return;fe();const s=Date.now();for(const[n,t]of T){const o=L.get(t.key),a=o?.sourceChange?.changedAt??0,i=F.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,s,e)&&((Y.get(t.key)??0)>=a||B.has(t.key)||(B.add(t.key),(async()=>{try{const l=o?.sourceChange,h=l&&!l.truncated?[...l.added,...l.removed].map(y=>path.resolve(t.workspaceRoot,y)):[],c=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,h):"unknown";if(c==="heard"){Y.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(c==="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;F.set(t.key,Date.now());const S=L.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&S){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${c==="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:S.wsKey,project:t.project,reason:"stale-source-set",watcher:c,added:l?.added,removed:l?.removed},void 0,t.bus),await z(S,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${c==="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:c},void 0,t.bus);const M=(0,hmr_reload_1.broadcastFullReload)(r,t.key);M>0&&console.log(`[gateway] \u2192 told ${M} open tab(s) to reload (stale optimize-dep generation)`);try{n.kill("SIGTERM")}catch{}setTimeout(()=>{if(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.`);try{n.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{B.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),D.unref?.()},"startLensSweeper"),we=d(async()=>{const e=[...new Map(r.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],s=u.dashboard!==!1,n=u.mcp!==!1&&e.length>0;if(s){const t=(0,lens_frontend_1.findDashboardBundle)(r.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)(r.root),a=await I(u.dashboardPort??o.dashboardPort,q),i=u.lensHost??"lensmcp.local",l=o.basePath;r.registry.appendRoutes(r.wsKey,[{host:i,prefix:l,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),r.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:r.root,base:l,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${i}${l}/)`),p("info",`lens dashboard up: ${i}${l} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:l}),k("lens-dashboard",process.execPath,[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:l,LENSMCP_GATEWAY_PORTS:C},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(r.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)(r.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})`),k("lens-mcp",process.execPath,[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_WORKSPACE_ROOT:r.root},!1,void 0,void 0,a)},"spawnMcp");await f(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}),Z("lens-mcp",a,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(r.registry.get(r.wsKey)?.routes??r.routes).filter(h=>h.lens?.project===t.project),i=await I(t.port,m);for(const h of a)h.lens&&(h.lens.port=i),h.target=`http://localhost:${i}`;t.port=i;const l=ee({wsKey:r.wsKey,project:t.project,workspaceRoot:r.root,projectRoot:t.projectRoot,eventFile:r.eventFile,routes:a,frontend:t,desiredPort:o});try{oe(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(h){m.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${h.message}`)}}ge()},"bootSingletonsAndApps"),_e=d(async e=>{if(u.dashboard===!1)return;const s=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!s)return;const n=(0,scope_1.readLensScope)(e.root),t=u.lensHost??"lensmcp.local",o=n.basePath,a=await I(n.dashboardPort,q),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");return $.delete(i),r.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()}),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}),k(i,process.execPath,[s],{LENSMCP_EVENT_FILE:l,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey,LENSMCP_GATEWAY_PORTS:C},!1,void 0,e.root,a),()=>{$.add(i),q.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const h=N.get(i);h&&G(h),N.delete(i)}},"hostWorkspaceDashboard"),Se=d(async e=>{const s=new Map;for(const o of e.routes){if(!o.lens)continue;const a=s.get(o.lens.project)??[];a.push(o),s.set(o.lens.project,a)}if(s.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of s){const i=a[0].lens;if(i.started)continue;const l=await I(i.port,m),h=i.port;for(const S of a)S.lens.started=!0,S.lens.port=l,S.target=`http://localhost:${l}`;const c=ee({wsKey:e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:h});t.push(c),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{oe(c,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(S){m.delete(l),c.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${S.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])x(a,!0);for(const a of o.routes)a.lens&&j.delete(a.lens);j.delete(o.frontend),F.delete(o.key),Y.delete(o.key),B.delete(o.key),L.delete(o.key)}}},"hostWorkspaceApps"),ye=d(async(e=2500)=>{D&&(clearInterval(D),D=void 0);const s=[...O];for(const o of s)try{o.kill("SIGTERM")}catch{}const n=d(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&s.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of s)if(n(o))try{o.kill("SIGKILL")}catch{}},"reapAll"),re=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:k,bootSingletonsAndApps:we,hostWorkspaceDashboard:_e,hostWorkspaceApps:Se,probeLensFrontends:re,reapAll:ye}}P(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
@@ -1,3 +1,3 @@
1
- "use strict";var he=Object.defineProperty;var v=(t,n)=>he(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=v((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isTestSourcePath=isTestSourcePath,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),types_1=require("./types");function eastWestEnv(t,n){const a={},d=t.serviceKeys[(0,service_keys_1.serviceKeyId)(n.wsKey,n.project)];if(d&&(a.LENSMCP_SERVICE_KEY=d),t.https){const f=require("../../../basic-ssl").caCertPath();fs.existsSync(f)&&(a.NODE_EXTRA_CA_CERTS=f)}return a}v(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let a=0;a<t.length;a+=1)n^=t.charCodeAt(a),n=Math.imul(n,16777619);return n>>>0}v(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}v(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const n=t.split("/"),a=n[n.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let d=0;d<n.length-1;d+=1)if(TEST_DIR_SEGMENTS.has(n[d]))return!0;return!1}v(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,d){const f=d?.ignoreTests??!0;let S=0,y=0,_=!1;const w=new Set,L=await gitIgnoredPaths(t),h=i(T=>path.relative(t,T).split(path.sep).join("/"),"rel"),k=i(async(T,B=!0)=>{if(S>=SCAN_FILE_CAP){_=!0;return}let C;try{C=await fs.promises.readdir(T,{withFileTypes:!0})}catch{return}for(const K of C){if(S>=SCAN_FILE_CAP){_=!0;return}const R=K.name,V=path.join(T,R);if(K.isDirectory()){if(!B||R.startsWith(".")||SRC_EXCLUDE.has(R)||a?.has(R)||R.endsWith("-devserver")||L.has(`${h(V)}/`))continue;await k(V)}else if(K.isFile()){const Y=R.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(R.slice(Y)))continue;const D=h(V);if(L.has(D)||f&&isTestSourcePath(D)||w.has(D))continue;w.add(D),S+=1,y^=fnv1a(D)}}},"walk");if(n&&n.length>0){await k(t,!1);for(const T of n)await k(T)}else await k(t);return{signature:`${S}:${(y>>>0).toString(36)}`,paths:w,truncated:_}}v(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,d){return(await sourceSetSnapshot(t,n,a,d)).signature}v(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,d){if(t.paths.size===n.paths.size){let _=!0;for(const w of t.paths)if(!n.paths.has(w)){_=!1;break}if(_)return null}const f=[],S=[];let y=t.truncated||n.truncated;for(const _ of n.paths)t.paths.has(_)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?f.push(_):y=!0);for(const _ of t.paths)n.paths.has(_)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?S.push(_):y=!0);return{changedAt:a,added:f,removed:S,truncated:y,...d!==void 0?{windowMs:d}:{}}}v(diffSourceSets,"diffSourceSets"),i(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,n,a,d){const f=n&&n>0?t-n:a;return Math.max(f,a)+d}v(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,d,f){const S=i(w=>{const L=path.relative(t,w).split(path.sep).filter(Boolean);return L.length===0||L[0].startsWith("..")?"":L[0]},"bucketOf")(n);if(!S)return[];const y=new Set(a.filter(Boolean)),_=new Set([S]);for(const w of d)w.startsWith(".")||SRC_EXCLUDE.has(w)||f?.has(w)||w.endsWith("-devserver")||y.has(w)||_.add(w);return[..._].sort().map(w=>path.join(t,w))}v(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,a,d){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<d.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<d.cooldownMs?null:a>0&&a-t.spawnAt>d.graceMs?"stale-source-set":d.maxAgeMs>0&&n-t.spawnAt>d.maxAgeMs?"max-age":null}v(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,a,d,f){return!(d-n<f.cooldownMs||a<=0||a-t<=f.graceMs||d-a<f.settleMs)}v(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,a,d=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&d.length>0){const f=n-a,S=new Set(t.recentEvents.filter(y=>y.at>=f).map(y=>path.resolve(y.path)));return d.every(y=>S.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}v(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,a){if(!t)return"unknown";if(t.degraded)return"degraded";const d=n-a;return t.lastWatchRunAt>=d||t.lastBuildAt>=d?"heard":"silent"}v(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,a,d){const f=t.hasListened?d.graceMs:Math.max(d.graceMs,d.neverListenedGraceMs);return a-t.spawnedAt<f||a-n<d.cooldownMs||t.probeFails<Math.max(2,d.failureThreshold)?null:"wedged-no-listener"}v(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}v(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,a={}){const d=a.spawnProcess??node_child_process_1.spawn,f=a.probeAlive??pod_probe_1.probePodAlive,S=a.killTree??((e,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),y=a.rollReadyMs??types_1.POD_ROLL_READY_MS,_=a.rollPollMs??750,{emit:w}=n,L=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,o,s,r,c)=>w(o,s,r,{wsKey:e.wsKey,...c},void 0,L(e)),"emitSvc"),k=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),T=i((e,o)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const r=e.proc?.pid;let c=0;for(const l of s.split(`
2
- `)){const p=l.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const j=Number(p[1]),m=Number(p[2]),$=p[3];if($.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test($)&&!($.includes("/node_modules/nx/")&&!$.includes(e.root))&&!(j===process.pid||j===r)&&!(r!==void 0&&m===r)){try{process.kill(-m,"SIGKILL")}catch{try{process.kill(j,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${o}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),h(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),B=i((e,o,s)=>{const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),l=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),[p,j]=fs.existsSync(l)?[process.execPath,[l,"run",r,`--childCount=${c}`]]:["yarn",["nx","run",r,`--childCount=${c}`]],m=d(p,j,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(o),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1);const $=i(u=>{const E=u.toString();for(const x of E.split(`
3
- `)){if(/ERROR in |error TS\d+|Found \d+ error|Unhandled 'error' event|EADDRINUSE/.test(x)){const P=x.replace(/\x1b\[[0-9;]*m/g,"").trim();e.lastErrors.push(P),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const g=Date.now();(P!==e.lastErrorEmitSig||g-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=P,e.lastErrorEmitAt=g,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:P}))}/No typescript errors found|webpack compiled successfully|Child#\d+ ready/.test(x)&&(e.buildFailed=!1),!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(x)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),h(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(O(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),T(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&C(e)},1500).unref?.())},250).unref?.())}},"sniff"),A=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return m.stdout?.on("data",u=>{if(t.stopped())return;$(u);const E=`[${e.project}] ${u}`;process.stdout.write(E),A?.(E)}),m.stderr?.on("data",u=>{if(t.stopped())return;$(u);const E=`[${e.project}] ${u}`;process.stderr.write(E),A?.(E)}),m.on("exit",u=>{if(e.proc!==m||(e.proc=void 0,e.state="down",t.stopped())||(console.log(`[gateway] ${r} exited (${u})`),h(e,u===0?"info":"warning",`service down: ${e.project} (exit ${u})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:u}),u===0||e.buildFailed))return;const E=Date.now();if(e.restarts=(e.restarts??[]).filter(P=>E-P<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(E);const x=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),h(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),C(e))},x)}),m},"launchServiceProcess"),C=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&T(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${s})\u2026`),h(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=B(e,e.generation,"active")},"spawnService"),K=i(async e=>{const o=e.pool.socks[0];if(!o)return;const s=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),R=i(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),V=i(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const s=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const c of e.pool.socks){const l=await(0,pod_probe_1.probePodAlive)(c,void 0,o);l&&(!l.keyFingerprint||l.keyFingerprint!==s)&&r.push({sock:c,pid:l.pid})}return r},"staleAdoptedSocks"),Y=i((e,o)=>{const s=o.map(r=>r.sock);for(const{sock:r,pid:c}of o){const l=chooseStalePodKillTarget(c,R(r),process.pid);if(l!==void 0)try{process.kill(l,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!s.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),D=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return h(e,"info",`service up: ${e.project} (${s} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:o==="adopted"}),!0},"markUp"),re=i(async e=>{if(k(e.pool)>0){const r=await V(e);if(r.length>0&&(console.error(`[gateway] ${e.project}: ${r.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),h(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:r.length}),e.proc&&O(e,"stale adoption (foreign east-west key)"),Y(e,r)),e.pool.socks.length>0)return D(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&C(e);const o=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(k(e.pool)>0){e.state="up",e.buildFailed=!1;const r=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${r?`, cold start ${r}ms`:""})`),h(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${r??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:r}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const o=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,r=e.decl.adminUrl??(e.decl.port?`http://localhost:${e.decl.port}`:void 0);if(!r||s===0||s>=o||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();const c=(r.startsWith("https")?require("node:https"):http).request(`${r}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},l=>l.resume());c.on("error",()=>{}),c.end(JSON.stringify({pods:s+1})),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),h(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight})},"scaleUp"),O=i((e,o,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),s?.announce!==!1&&h(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const r=e.proc;try{r.stdout?.removeAllListeners("data"),r.stderr?.removeAllListeners("data"),r.removeAllListeners("exit")}catch{}S(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{H(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),H=i(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const s=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),ee=i((e,o)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const r=e.generation??0,c=r+1,l=Date.now();e.lastStaleRecycleAt=l,console.log(`[gateway] pod handoff start: ${e.project} g${r}\u2192g${c} reason=${o} \u2014 standby spawning, old generation keeps serving.`),h(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:c,fromGeneration:r});const p=B(e,c,"standby"),j=(async()=>{const m=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],$=l+y;let A;for(;!t.stopped()&&Date.now()<$&&!(p.exitCode!==null||p.signalCode!==null);){k(e.pool);for(const u of e.pool.socks)if((await f(u,void 0,m))?.podGeneration===c){A=u;break}if(A)break;await sleep(_)}if(!A||t.stopped()){const u=t.stopped()?"gateway stopping":p.exitCode!==null||p.signalCode!==null?`standby exited (${p.exitCode??p.signalCode})`:`standby not ready within ${Math.round(y/1e3)}s`;try{p.stdout?.removeAllListeners("data"),p.stderr?.removeAllListeners("data")}catch{}return S(p,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${u} \u2014 old generation g${r} kept serving.`),h(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:o,generation:c,error:u,durationMs:Date.now()-l}),setTimeout(()=>H(e),1200).unref?.(),!1}if(e.proc!==s)return S(p,"SIGTERM"),!1;e.proc=p,e.generation=c,e.spawnAt=l,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}return S(s,"SIGTERM"),setTimeout(()=>{H(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(A)} in ${Date.now()-l}ms; retiring g${r}.`),h(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:c,fromGeneration:r,durationMs:Date.now()-l}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=j,j},"rollPod"),ne=i(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>o-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),O(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),h(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),O(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&C(e)},1500).unref?.()},"recycleWedged"),ce={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let z;const G=new Map,te=new Map;let J=!1,Q=0,X=0;const ae=i(()=>{const e=t.projectRoots??{},o=new Set(t.services.map(r=>r.project));for(const r of t.routes)r.lens&&o.add(r.lens.project);const s=new Set;for(const r of o){const c=e[r];if(!c)continue;const l=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];l&&!l.startsWith("..")&&s.add(l)}return[...s]},"managedBuckets"),ie=i((e,o,s,r)=>{const c=t.projectSourceRoots?.[e];if(c?.length)return c;const l=t.projectRoots?.[e];if(!l)return;const p=sourceScopeDirs(t.root,l,s,o,r);return p.length>0?p:void 0},"scopeFor"),de=i(()=>{if(J||Date.now()-Q<types_1.POD_STALE_SCAN_MS)return;J=!0,Q=Date.now();const e=Q;(async()=>{const o=(0,scope_1.readSourceSetOptions)(t.root),s=o.exclude,r={ignoreTests:o.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,r),l=Date.now(),p=sourceChangeWindowMs(l,X,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),j=z?diffSourceSets(z,c,l,p):null;if(j&&(t.sourceSetChangedAt=l,t.sourceSetChange=j,w("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:j.added,removed:j.removed,truncated:j.truncated})),z=c,!t.projectRoots){X=e;return}let m;try{m=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(g=>g.isDirectory()).map(g=>g.name)}catch{return}const $=ae(),A=new Set(t.services.map(g=>g.project));for(const g of t.routes)g.lens&&A.add(g.lens.project);const u=new Map;for(const g of A){const N=ie(g,m,$,s);if(!N)continue;const M=N.join("|"),b=u.get(M)??{dirs:N,projects:[]};b.projects.push(g),u.set(M,b)}const E=Date.now(),x=sourceChangeWindowMs(E,X,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);X=e;const P=i((g,N,M)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const b of g)t.sourceSetChangedFor.set(b,N),t.sourceSetChangesFor.set(b,M)},"stampScoped");for(const[g,{dirs:N,projects:M}]of u){const b=await sourceSetSnapshot(t.root,N,s,r),oe=G.get(g);G.set(g,b);const Z=new Map;for(const F of M){const I=te.get(F);if(I===void 0||I===g)continue;const W=Z.get(I)??[];W.push(F),Z.set(I,W)}for(const F of M)te.set(F,g);for(const[F,I]of Z){const W=G.get(F),U=W?diffSourceSets(W,b,E,x):j;U&&(P(I,W?E:l,U),w("info",`source file set changed \u2014 ${I.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:I,added:U.added,removed:U.removed,truncated:U.truncated}))}if(!oe)continue;const q=diffSourceSets(oe,b,E,x);q&&(P(M,E,q),w("info",`source file set changed in ${M.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:M,added:q.added,removed:q.removed,truncated:q.truncated}))}for(const g of[...G.keys()])u.has(g)||G.delete(g)})().catch(()=>{}).finally(()=>{J=!1})},"maybeScanSourceSet"),le=i((e,o)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,r=podWatcherVerdict(e.watcher,o,s),c=i(l=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${l} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,h(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):r==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):r==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),pe=i((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){ee(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),h(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const s=!!e.decl.eager;O(e,`${o} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&C(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{ge().catch(o=>console.error("[gateway] sweep error:",o))},t.sweepMs);return e.unref(),e},"startSweeper"),ge=i(async()=>{{if(t.stopped())return;types_1.POD_RECYCLE_ENABLED&&de();for(const e of t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const o=e.decl.idleKillSec;if(o&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){O(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,r=k(e.pool)>0;if(r||s){if(e.wedgedSince=void 0,r&&D(e,"observed"),r&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await K(e);const l=(e.staleVerifiedAt??0)>=c&&c>0,p=podRecycleReason(e,Date.now(),l?0:c,ce);if(p==="stale-source-set"&&!le(e,c))continue;p&&pe(e,p)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ne(e))}}},"sweepOnce");return{scanNow:k,spawnService:C,ensureUp:re,scaleUp:se,killSpawned:O,markUp:D,startSweeper:ue,rollPod:ee}}v(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==a)return t}v(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
1
+ "use strict";var he=Object.defineProperty;var v=(t,n)=>he(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=v((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isTestSourcePath=isTestSourcePath,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),pod_output_1=require("./pod-output"),types_1=require("./types");function eastWestEnv(t,n){const a={},l=t.serviceKeys[(0,service_keys_1.serviceKeyId)(n.wsKey,n.project)];if(l&&(a.LENSMCP_SERVICE_KEY=l),t.https){const g=require("../../../basic-ssl").caCertPath();fs.existsSync(g)&&(a.NODE_EXTRA_CA_CERTS=g)}return a}v(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let a=0;a<t.length;a+=1)n^=t.charCodeAt(a),n=Math.imul(n,16777619);return n>>>0}v(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}v(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const n=t.split("/"),a=n[n.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let l=0;l<n.length-1;l+=1)if(TEST_DIR_SEGMENTS.has(n[l]))return!0;return!1}v(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,l){const g=l?.ignoreTests??!0;let S=0,y=0,_=!1;const w=new Set,b=await gitIgnoredPaths(t),h=i(R=>path.relative(t,R).split(path.sep).join("/"),"rel"),M=i(async(R,U=!0)=>{if(S>=SCAN_FILE_CAP){_=!0;return}let D;try{D=await fs.promises.readdir(R,{withFileTypes:!0})}catch{return}for(const K of D){if(S>=SCAN_FILE_CAP){_=!0;return}const x=K.name,V=path.join(R,x);if(K.isDirectory()){if(!U||x.startsWith(".")||SRC_EXCLUDE.has(x)||a?.has(x)||x.endsWith("-devserver")||b.has(`${h(V)}/`))continue;await M(V)}else if(K.isFile()){const Y=x.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(x.slice(Y)))continue;const P=h(V);if(b.has(P)||g&&isTestSourcePath(P)||w.has(P))continue;w.add(P),S+=1,y^=fnv1a(P)}}},"walk");if(n&&n.length>0){await M(t,!1);for(const R of n)await M(R)}else await M(t);return{signature:`${S}:${(y>>>0).toString(36)}`,paths:w,truncated:_}}v(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,l){return(await sourceSetSnapshot(t,n,a,l)).signature}v(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,l){if(t.paths.size===n.paths.size){let _=!0;for(const w of t.paths)if(!n.paths.has(w)){_=!1;break}if(_)return null}const g=[],S=[];let y=t.truncated||n.truncated;for(const _ of n.paths)t.paths.has(_)||(g.length+S.length<SOURCE_DIFF_PATH_CAP?g.push(_):y=!0);for(const _ of t.paths)n.paths.has(_)||(g.length+S.length<SOURCE_DIFF_PATH_CAP?S.push(_):y=!0);return{changedAt:a,added:g,removed:S,truncated:y,...l!==void 0?{windowMs:l}:{}}}v(diffSourceSets,"diffSourceSets"),i(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,n,a,l){const g=n&&n>0?t-n:a;return Math.max(g,a)+l}v(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,g){const S=i(w=>{const b=path.relative(t,w).split(path.sep).filter(Boolean);return b.length===0||b[0].startsWith("..")?"":b[0]},"bucketOf")(n);if(!S)return[];const y=new Set(a.filter(Boolean)),_=new Set([S]);for(const w of l)w.startsWith(".")||SRC_EXCLUDE.has(w)||g?.has(w)||w.endsWith("-devserver")||y.has(w)||_.add(w);return[..._].sort().map(w=>path.join(t,w))}v(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,a,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<l.cooldownMs?null:a>0&&a-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&n-t.spawnAt>l.maxAgeMs?"max-age":null}v(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,a,l,g){return!(l-n<g.cooldownMs||a<=0||a-t<=g.graceMs||l-a<g.settleMs)}v(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,a,l=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&l.length>0){const g=n-a,S=new Set(t.recentEvents.filter(y=>y.at>=g).map(y=>path.resolve(y.path)));return l.every(y=>S.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}v(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,a){if(!t)return"unknown";if(t.degraded)return"degraded";const l=n-a;return t.lastWatchRunAt>=l||t.lastBuildAt>=l?"heard":"silent"}v(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,a,l){const g=t.hasListened?l.graceMs:Math.max(l.graceMs,l.neverListenedGraceMs);return a-t.spawnedAt<g||a-n<l.cooldownMs||t.probeFails<Math.max(2,l.failureThreshold)?null:"wedged-no-listener"}v(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}v(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,a={}){const l=a.spawnProcess??node_child_process_1.spawn,g=a.probeAlive??pod_probe_1.probePodAlive,S=a.killTree??((e,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),y=a.rollReadyMs??types_1.POD_ROLL_READY_MS,_=a.rollPollMs??750,{emit:w}=n,b=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,o,s,r,c)=>w(o,s,r,{wsKey:e.wsKey,...c},void 0,b(e)),"emitSvc"),M=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),R=i((e,o)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const r=e.proc?.pid;let c=0;for(const d of s.split(`
2
+ `)){const p=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const j=Number(p[1]),m=Number(p[2]),A=p[3];if(A.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test(A)&&!(A.includes("/node_modules/nx/")&&!A.includes(e.root))&&!(j===process.pid||j===r)&&!(r!==void 0&&m===r)){try{process.kill(-m,"SIGKILL")}catch{try{process.kill(j,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${o}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),h(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),U=i((e,o,s)=>{const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),d=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),[p,j]=fs.existsSync(d)?[process.execPath,[d,"run",r,`--childCount=${c}`]]:["yarn",["nx","run",r,`--childCount=${c}`]],m=l(p,j,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(o),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1);const A=i(u=>{const E=u.toString();for(const F of E.split(`
3
+ `)){const{clean:C,broken:f,healthy:L}=(0,pod_output_1.classifyPodLine)(F),$=C;if(f){e.lastErrors.push(C),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const k=Date.now();(C!==e.lastErrorEmitSig||k-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=C,e.lastErrorEmitAt=k,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:C}))}L&&(e.buildFailed=!1),!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test($)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),h(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(O(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),R(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.())},250).unref?.())}},"sniff"),T=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return m.stdout?.on("data",u=>{if(t.stopped())return;A(u);const E=`[${e.project}] ${u}`;process.stdout.write(E),T?.(E)}),m.stderr?.on("data",u=>{if(t.stopped())return;A(u);const E=`[${e.project}] ${u}`;process.stderr.write(E),T?.(E)}),m.on("exit",u=>{if(e.proc!==m||(e.proc=void 0,e.state="down",t.stopped())||(console.log(`[gateway] ${r} exited (${u})`),h(e,u===0?"info":"warning",`service down: ${e.project} (exit ${u})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:u}),u===0||e.buildFailed))return;const E=Date.now();if(e.restarts=(e.restarts??[]).filter(C=>E-C<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(E);const F=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),h(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),D(e))},F)}),m},"launchServiceProcess"),D=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&R(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${s})\u2026`),h(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=U(e,e.generation,"active")},"spawnService"),K=i(async e=>{const o=e.pool.socks[0];if(!o)return;const s=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),x=i(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),V=i(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const s=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,o);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&r.push({sock:c,pid:d.pid})}return r},"staleAdoptedSocks"),Y=i((e,o)=>{const s=o.map(r=>r.sock);for(const{sock:r,pid:c}of o){const d=chooseStalePodKillTarget(c,x(r),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!s.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),P=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return h(e,"info",`service up: ${e.project} (${s} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:o==="adopted"}),!0},"markUp"),re=i(async e=>{if(M(e.pool)>0){const r=await V(e);if(r.length>0&&(console.error(`[gateway] ${e.project}: ${r.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),h(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:r.length}),e.proc&&O(e,"stale adoption (foreign east-west key)"),Y(e,r)),e.pool.socks.length>0)return P(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&D(e);const o=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(M(e.pool)>0){e.state="up",e.buildFailed=!1;const r=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${r?`, cold start ${r}ms`:""})`),h(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${r??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:r}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const o=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,r=e.decl.adminUrl??(e.decl.port?`http://localhost:${e.decl.port}`:void 0);if(!r||s===0||s>=o||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();const c=(r.startsWith("https")?require("node:https"):http).request(`${r}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume());c.on("error",()=>{}),c.end(JSON.stringify({pods:s+1})),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),h(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight})},"scaleUp"),O=i((e,o,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),s?.announce!==!1&&h(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const r=e.proc;try{r.stdout?.removeAllListeners("data"),r.stderr?.removeAllListeners("data"),r.removeAllListeners("exit")}catch{}S(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{H(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),H=i(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const s=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),ee=i((e,o)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const r=e.generation??0,c=r+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${r}\u2192g${c} reason=${o} \u2014 standby spawning, old generation keeps serving.`),h(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:c,fromGeneration:r});const p=U(e,c,"standby"),j=(async()=>{const m=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],A=d+y;let T;for(;!t.stopped()&&Date.now()<A&&!(p.exitCode!==null||p.signalCode!==null);){M(e.pool);for(const u of e.pool.socks)if((await g(u,void 0,m))?.podGeneration===c){T=u;break}if(T)break;await sleep(_)}if(!T||t.stopped()){const u=t.stopped()?"gateway stopping":p.exitCode!==null||p.signalCode!==null?`standby exited (${p.exitCode??p.signalCode})`:`standby not ready within ${Math.round(y/1e3)}s`;try{p.stdout?.removeAllListeners("data"),p.stderr?.removeAllListeners("data")}catch{}return S(p,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${u} \u2014 old generation g${r} kept serving.`),h(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:o,generation:c,error:u,durationMs:Date.now()-d}),setTimeout(()=>H(e),1200).unref?.(),!1}if(e.proc!==s)return S(p,"SIGTERM"),!1;e.proc=p,e.generation=c,e.spawnAt=d,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}return S(s,"SIGTERM"),setTimeout(()=>{H(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(T)} in ${Date.now()-d}ms; retiring g${r}.`),h(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:c,fromGeneration:r,durationMs:Date.now()-d}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=j,j},"rollPod"),ne=i(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>o-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),O(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),h(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),O(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recycleWedged"),ce={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let z;const q=new Map,te=new Map;let J=!1,Q=0,X=0;const ae=i(()=>{const e=t.projectRoots??{},o=new Set(t.services.map(r=>r.project));for(const r of t.routes)r.lens&&o.add(r.lens.project);const s=new Set;for(const r of o){const c=e[r];if(!c)continue;const d=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&s.add(d)}return[...s]},"managedBuckets"),ie=i((e,o,s,r)=>{const c=t.projectSourceRoots?.[e];if(c?.length)return c;const d=t.projectRoots?.[e];if(!d)return;const p=sourceScopeDirs(t.root,d,s,o,r);return p.length>0?p:void 0},"scopeFor"),le=i(()=>{if(J||Date.now()-Q<types_1.POD_STALE_SCAN_MS)return;J=!0,Q=Date.now();const e=Q;(async()=>{const o=(0,scope_1.readSourceSetOptions)(t.root),s=o.exclude,r={ignoreTests:o.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,r),d=Date.now(),p=sourceChangeWindowMs(d,X,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),j=z?diffSourceSets(z,c,d,p):null;if(j&&(t.sourceSetChangedAt=d,t.sourceSetChange=j,w("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:j.added,removed:j.removed,truncated:j.truncated})),z=c,!t.projectRoots){X=e;return}let m;try{m=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(f=>f.isDirectory()).map(f=>f.name)}catch{return}const A=ae(),T=new Set(t.services.map(f=>f.project));for(const f of t.routes)f.lens&&T.add(f.lens.project);const u=new Map;for(const f of T){const L=ie(f,m,A,s);if(!L)continue;const $=L.join("|"),k=u.get($)??{dirs:L,projects:[]};k.projects.push(f),u.set($,k)}const E=Date.now(),F=sourceChangeWindowMs(E,X,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);X=e;const C=i((f,L,$)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const k of f)t.sourceSetChangedFor.set(k,L),t.sourceSetChangesFor.set(k,$)},"stampScoped");for(const[f,{dirs:L,projects:$}]of u){const k=await sourceSetSnapshot(t.root,L,s,r),oe=q.get(f);q.set(f,k);const Z=new Map;for(const W of $){const I=te.get(W);if(I===void 0||I===f)continue;const N=Z.get(I)??[];N.push(W),Z.set(I,N)}for(const W of $)te.set(W,f);for(const[W,I]of Z){const N=q.get(W),B=N?diffSourceSets(N,k,E,F):j;B&&(C(I,N?E:d,B),w("info",`source file set changed \u2014 ${I.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:I,added:B.added,removed:B.removed,truncated:B.truncated}))}if(!oe)continue;const G=diffSourceSets(oe,k,E,F);G&&(C($,E,G),w("info",`source file set changed in ${$.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:$,added:G.added,removed:G.removed,truncated:G.truncated}))}for(const f of[...q.keys()])u.has(f)||q.delete(f)})().catch(()=>{}).finally(()=>{J=!1})},"maybeScanSourceSet"),de=i((e,o)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,r=podWatcherVerdict(e.watcher,o,s),c=i(d=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,h(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):r==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):r==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),pe=i((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){ee(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),h(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const s=!!e.decl.eager;O(e,`${o} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{ge().catch(o=>console.error("[gateway] sweep error:",o))},t.sweepMs);return e.unref(),e},"startSweeper"),ge=i(async()=>{{if(t.stopped())return;types_1.POD_RECYCLE_ENABLED&&le();for(const e of t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const o=e.decl.idleKillSec;if(o&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){O(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,r=M(e.pool)>0;if(r||s){if(e.wedgedSince=void 0,r&&P(e,"observed"),r&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await K(e);const d=(e.staleVerifiedAt??0)>=c&&c>0,p=podRecycleReason(e,Date.now(),d?0:c,ce);if(p==="stale-source-set"&&!de(e,c))continue;p&&pe(e,p)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ne(e))}}},"sweepOnce");return{scanNow:M,spawnService:D,ensureUp:re,scaleUp:se,killSpawned:O,markUp:P,startSweeper:ue,rollPod:ee}}v(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==a)return t}v(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
@@ -3,13 +3,15 @@ import type { GwTrace, Route } from './types';
3
3
  export type Severity = 'info' | 'warning' | 'error';
4
4
  /**
5
5
  * The lens's OWN dashboard route project (the value `lens-children.ts` gives the `lensmcp.local/<key>/`
6
- * route). Traffic to it is the lens UI polling its own control-plane API (`/<key>/api/cluster|logs|snapshot`)
7
- * ~1×/s — pure self-observation with NO app-diagnostic value. Its SUCCESSFUL request narration + traffic
6
+ * route). Traffic to it is the lens UI talking to its own control-plane API (`/<key>/api/stream` and
7
+ * on-demand `/<key>/api/*` reads) — pure self-observation with NO app-diagnostic value. Its SUCCESSFUL request narration + traffic
8
8
  * edges are suppressed from the bus (the same "don't bloat the bus" rationale as header-less traffic); a 5xx
9
9
  * still surfaces so a genuine lens-dashboard failure isn't hidden. One home for the literal (imported by
10
10
  * `lens-children.ts` for the route + this layer for the filter, so they can't drift).
11
11
  */
12
12
  export declare const LENS_DASHBOARD_PROJECT = "(lens-dashboard)";
13
+ /** Per-edge, per-flush cap on the request durations a traffic event carries. */
14
+ export declare const EDGE_DURATION_SAMPLES = 64;
13
15
  export interface Observability {
14
16
  /** Append a cluster event to the bus. `busFile` overrides the default daemon bus for THIS event — used to
15
17
  * route a REGISTERED workspace's service-lifecycle events to ITS OWN bus, so each hosted dashboard's
@@ -1,2 +1,2 @@
1
- "use strict";var q=Object.defineProperty;var l=(t,d)=>q(t,"name",{value:d,configurable:!0});var S=Object.defineProperty,n=l((t,d)=>S(t,"name",{value:d,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.LENS_DASHBOARD_PROJECT=void 0,exports.createObservability=createObservability;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),redact_1=require("@lensmcp/core/redact"),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})();exports.LENS_DASHBOARD_PROJECT="(lens-dashboard)";function ownerOf(t){return t?.svc?{root:t.svc.root,wsKey:t.svc.wsKey}:t?.lens?{...t.lens.workspaceRoot?{root:t.lens.workspaceRoot}:{},...t.lens.workspaceKey?{wsKey:t.lens.workspaceKey}:{}}:{}}l(ownerOf,"ownerOf"),n(ownerOf,"ownerOf");function busFileFor(t,d){const{root:u}=ownerOf(t);if(!u)return;const p=path.join(u,".lensmcp","events.jsonl");return p===d?void 0:p}l(busFileFor,"busFileFor"),n(busFileFor,"busFileFor");const hdr=n(t=>typeof t=="string"&&t!==""?t:Array.isArray(t)?t[0]:void 0,"hdr");function createObservability(t,d){const u=n(()=>Date.now().toString(36)+Math.random().toString(36).slice(2,12),"eid"),p=busHygiene?.createEventFloodGuard({burst:30,perSec:5}),h=n((e,r,s,o,a,c)=>{const i=c??t;try{const w=p?p.admit(s):{ok:!0,suppressed:0};if(!w.ok)return;fs.mkdirSync(path.dirname(i),{recursive:!0});const y=o&&(0,redact_1.redactDeep)(o),g=JSON.stringify({id:u(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:e,context:(0,redact_1.redactDeep)({sessionId:"pending",...a}),fingerprint:s,title:(0,redact_1.safeTitle)(r),raw:w.suppressed>0?{...y,floodSuppressed:w.suppressed}:y})+`
2
- `;busHygiene?.maybeRotateEventFile(i,g.length),fs.appendFileSync(i,g)}catch{}},"emit"),m=n(e=>{const r=hdr(e.headers["x-lensmcp-flow-id"]);let s=hdr(e.headers["x-request-id"]);if(!(!r&&!s&&!hdr(e.headers.traceparent)))return s||(s=u(),e.headers["x-request-id"]=s),{startedAt:Date.now(),method:(e.method??"GET").toUpperCase(),host:(e.headers.host||"").split(":")[0],url:e.url??"/",steps:[],...r?{flowId:r}:{},...s?{requestId:s}:{},...hdr(e.headers["x-lensmcp-parent-request-id"])?{parentRequestId:hdr(e.headers["x-lensmcp-parent-request-id"])}:{},...hdr(e.headers["x-lensmcp-origin-node-id"])?{originNodeId:hdr(e.headers["x-lensmcp-origin-node-id"])}:{}}},"beginTrace"),v=n((e,r,s)=>{e&&e.steps.push({step:r,atMs:Date.now()-e.startedAt,...s})},"traceStep"),b=n((e,r,s,o,a)=>{if(!e||e.done||(e.done=!0,r===exports.LENS_DASHBOARD_PROJECT&&s<500))return;const c=Date.now()-e.startedAt;h(s>=500?"error":"info",`gateway ${e.method} ${e.host}${e.url} \u2192 ${s} (${c}ms)`,`gateway-request:${r}`,{kind:"gateway-request",project:r,host:e.host,method:e.method,url:e.url,status:s,durationMs:c,startedAt:e.startedAt,steps:e.steps,...ownerOf(a).wsKey?{wsKey:ownerOf(a).wsKey}:{},...o},{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},...e.originNodeId?{originNodeId:e.originNodeId}:{}},busFileFor(a,t))},"finishTrace"),f=new Map;return{emit:h,eid:u,beginTrace:m,traceStep:v,finishTrace:b,recordEdge:n((e,r,s,o)=>{if(e.project===exports.LENS_DASHBOARD_PROJECT&&s<500&&s!==0)return;const a=e.host??"(default)",c=(o?o+"\u21D2":"")+a+"\u2192"+e.project,i=f.get(c)??{host:a,project:e.project,caller:o,count:0,totalMs:0,errors:0,...busFileFor(e,t)?{busFile:busFileFor(e,t)}:{},...ownerOf(e).wsKey?{wsKey:ownerOf(e).wsKey}:{}};i.count+=1,i.totalMs+=r,(s>=500||s===0)&&(i.errors+=1),f.set(c,i)},"recordEdge"),startEdgeFlusher:n(()=>{const e=setInterval(()=>{for(const r of f.values()){const s=Math.round(r.totalMs/r.count);h(r.errors?"warning":"info",`traffic ${r.host} \u2192 ${r.project}: ${r.count} req, ~${s}ms${r.errors?`, ${r.errors} errors`:""}`,`gateway-traffic:${r.host}->${r.project}`,{kind:"gateway-traffic",host:r.host,project:r.project,caller:r.caller,count:r.count,avgMs:s,errors:r.errors,...r.wsKey?{wsKey:r.wsKey}:{}},void 0,r.busFile)}f.clear()},d.trafficFlushMs);return e.unref(),e},"startEdgeFlusher")}}l(createObservability,"createObservability"),n(createObservability,"createObservability");
1
+ "use strict";var _=Object.defineProperty;var h=(t,d)=>_(t,"name",{value:d,configurable:!0});var S=Object.defineProperty,n=h((t,d)=>S(t,"name",{value:d,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EDGE_DURATION_SAMPLES=exports.LENS_DASHBOARD_PROJECT=void 0,exports.createObservability=createObservability;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),redact_1=require("@lensmcp/core/redact"),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})();exports.LENS_DASHBOARD_PROJECT="(lens-dashboard)",exports.EDGE_DURATION_SAMPLES=64;function ownerOf(t){return t?.svc?{root:t.svc.root,wsKey:t.svc.wsKey}:t?.lens?{...t.lens.workspaceRoot?{root:t.lens.workspaceRoot}:{},...t.lens.workspaceKey?{wsKey:t.lens.workspaceKey}:{}}:{}}h(ownerOf,"ownerOf"),n(ownerOf,"ownerOf");function busFileFor(t,d){const{root:u}=ownerOf(t);if(!u)return;const l=path.join(u,".lensmcp","events.jsonl");return l===d?void 0:l}h(busFileFor,"busFileFor"),n(busFileFor,"busFileFor");const hdr=n(t=>typeof t=="string"&&t!==""?t:Array.isArray(t)?t[0]:void 0,"hdr");function createObservability(t,d){const u=n(()=>Date.now().toString(36)+Math.random().toString(36).slice(2,12),"eid"),l=busHygiene?.createEventFloodGuard({burst:30,perSec:5}),w=n((e,r,s,a,i,c)=>{const o=c??t;try{const p=l?l.admit(s):{ok:!0,suppressed:0};if(!p.ok)return;fs.mkdirSync(path.dirname(o),{recursive:!0});const y=a&&(0,redact_1.redactDeep)(a),g=JSON.stringify({id:u(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:e,context:(0,redact_1.redactDeep)({sessionId:"pending",...i}),fingerprint:s,title:(0,redact_1.safeTitle)(r),raw:p.suppressed>0?{...y,floodSuppressed:p.suppressed}:y})+`
2
+ `;busHygiene?.maybeRotateEventFile(o,g.length),fs.appendFileSync(o,g)}catch{}},"emit"),m=n(e=>{const r=hdr(e.headers["x-lensmcp-flow-id"]);let s=hdr(e.headers["x-request-id"]);if(!(!r&&!s&&!hdr(e.headers.traceparent)))return s||(s=u(),e.headers["x-request-id"]=s),{startedAt:Date.now(),method:(e.method??"GET").toUpperCase(),host:(e.headers.host||"").split(":")[0],url:e.url??"/",steps:[],...r?{flowId:r}:{},...s?{requestId:s}:{},...hdr(e.headers["x-lensmcp-parent-request-id"])?{parentRequestId:hdr(e.headers["x-lensmcp-parent-request-id"])}:{},...hdr(e.headers["x-lensmcp-origin-node-id"])?{originNodeId:hdr(e.headers["x-lensmcp-origin-node-id"])}:{}}},"beginTrace"),E=n((e,r,s)=>{e&&e.steps.push({step:r,atMs:Date.now()-e.startedAt,...s})},"traceStep"),D=n((e,r,s,a,i)=>{if(!e||e.done||(e.done=!0,r===exports.LENS_DASHBOARD_PROJECT&&s<500))return;const c=Date.now()-e.startedAt;w(s>=500?"error":"info",`gateway ${e.method} ${e.host}${e.url} \u2192 ${s} (${c}ms)`,`gateway-request:${r}`,{kind:"gateway-request",project:r,host:e.host,method:e.method,url:e.url,status:s,durationMs:c,startedAt:e.startedAt,steps:e.steps,...ownerOf(i).wsKey?{wsKey:ownerOf(i).wsKey}:{},...a},{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},...e.originNodeId?{originNodeId:e.originNodeId}:{}},busFileFor(i,t))},"finishTrace"),f=new Map;return{emit:w,eid:u,beginTrace:m,traceStep:E,finishTrace:D,recordEdge:n((e,r,s,a)=>{if(e.project===exports.LENS_DASHBOARD_PROJECT&&s<500&&s!==0)return;const i=e.host??"(default)",c=(a?a+"\u21D2":"")+i+"\u2192"+e.project,o=f.get(c)??{host:i,project:e.project,caller:a,count:0,totalMs:0,errors:0,durations:[],...busFileFor(e,t)?{busFile:busFileFor(e,t)}:{},...ownerOf(e).wsKey?{wsKey:ownerOf(e).wsKey}:{}};if(o.count+=1,o.totalMs+=r,o.durations.length<exports.EDGE_DURATION_SAMPLES)o.durations.push(Math.round(r));else{const p=Math.floor(Math.random()*o.count);p<exports.EDGE_DURATION_SAMPLES&&(o.durations[p]=Math.round(r))}(s>=500||s===0)&&(o.errors+=1),f.set(c,o)},"recordEdge"),startEdgeFlusher:n(()=>{const e=setInterval(()=>{for(const r of f.values()){const s=Math.round(r.totalMs/r.count);w(r.errors?"warning":"info",`traffic ${r.host} \u2192 ${r.project}: ${r.count} req, ~${s}ms${r.errors?`, ${r.errors} errors`:""}`,`gateway-traffic:${r.host}->${r.project}`,{kind:"gateway-traffic",host:r.host,project:r.project,caller:r.caller,count:r.count,avgMs:s,errors:r.errors,durationsMs:r.durations,...r.wsKey?{wsKey:r.wsKey}:{}},void 0,r.busFile)}f.clear()},d.trafficFlushMs);return e.unref(),e},"startEdgeFlusher")}}h(createObservability,"createObservability"),n(createObservability,"createObservability");
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Reading a pod's build output for the two facts the gateway acts on: "the build is broken"
3
+ * and "the build is healthy again".
4
+ *
5
+ * Both used to be regexes run against the RAW child output, and both were dead in practice:
6
+ *
7
+ * - webpack colours the word it prints, so a build error arrives as
8
+ * `ESC[1mESC[31mERRORESC[39mESC[22m in ./server/libs/…` — there is an escape sequence
9
+ * between `ERROR` and ` in`, and `/ERROR in /` never matched. No `service-error` was
10
+ * emitted, `buildFailed` stayed false, and the gateway treated a broken build as a slow
11
+ * cold start: measured live, bff sat in `starting` for 12.5 minutes with every request
12
+ * parked in `ensureUp` until it 503'd at 120 s, and the lens showed nothing at all.
13
+ * - the recovery pattern was `webpack compiled successfully`, but webpack 5 prints its
14
+ * version in between: `webpack 5.108.4 compiled successfully in 812 ms`.
15
+ *
16
+ * So the line is de-coloured FIRST and matched after, and the patterns are written against
17
+ * what the tools actually print.
18
+ */
19
+ export interface PodLine {
20
+ /** The line with colour codes removed and whitespace trimmed — what gets reported. */
21
+ clean: string;
22
+ /** The build or the process is broken. */
23
+ broken: boolean;
24
+ /** The build is healthy again. */
25
+ healthy: boolean;
26
+ }
27
+ export declare function classifyPodLine(raw: string): PodLine;
@@ -0,0 +1 @@
1
+ "use strict";var s=Object.defineProperty;var n=(r,e)=>s(r,"name",{value:e,configurable:!0});var t=Object.defineProperty,o=n((r,e)=>t(r,"name",{value:e,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.classifyPodLine=classifyPodLine;const ANSI_SGR=/\x1b\[[0-9;]*m/g,BUILD_BROKEN=/ERROR in |Module not found|error TS\d+|Found \d+ errors?|Unhandled 'error' event|EADDRINUSE/,BUILD_HEALTHY=/No typescript errors found|compiled successfully|Child#\d+ ready/;function classifyPodLine(r){const e=r.replace(ANSI_SGR,"").trim();return{clean:e,broken:BUILD_BROKEN.test(e),healthy:BUILD_HEALTHY.test(e)}}n(classifyPodLine,"classifyPodLine"),o(classifyPodLine,"classifyPodLine");
@@ -1 +1 @@
1
- "use strict";var k=Object.defineProperty;var p=(a,s)=>k(a,"name",{value:s,configurable:!0});var m=Object.defineProperty,l=p((a,s)=>m(a,"name",{value:s,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpgrade=createUpgrade;const manifest_1=require("../manifest"),discovery_1=require("./discovery"),edge_1=require("./edge"),hmr_reload_1=require("./hmr-reload"),service_keys_1=require("./service-keys");function accountUpgrade(a,s){const n=a.svc;if(!n||s.destroyed)return;n.inflight+=1,n.lastUsed=Date.now();let d=!1;s.once("close",()=>{d||(d=!0,n.inflight-=1,n.lastUsed=Date.now())})}p(accountUpgrade,"accountUpgrade"),l(accountUpgrade,"accountUpgrade");function createUpgrade(a){const{rt:s,auth:n,proxy:d,hooks:u}=a,h=l(async(r,o,y)=>{for(const t of edge_1.GATEWAY_OWNED_HEADERS)delete r.headers[t];const c=(0,edge_1.canonicalize)(r.url??"/");if(c===void 0){o.destroy();return}r.url=c.path+c.query;const v=(r.headers.host||"").split(":")[0],e=(0,manifest_1.matchRoute)(s.routes,v,c.policy+c.query);if(!e){o.destroy();return}(0,edge_1.resolveClientIp)(s,r);const i=u.upgradeContext(r,o,void 0);if(i.route=e,e.internal){const t=n.internal(r,e,void 0);if(!t.ok){o.destroy();return}i.caller=t.caller}else{const t=n.public(r,e,void 0,c.policy);if(!t.ok){o.destroy();return}if(i.claims=t.claims,u.has("afterAuth")&&(await u.run("afterAuth",i),i.responded()))return}const g=e.pool?s.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)]:void 0;if(g&&(r.headers["x-internal-token"]=g),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(s,r),u.has("onUpgrade")&&(await u.run("onUpgrade",i),i.responded()))return;const f=e.pool?(()=>{const t=(0,discovery_1.pickSock)(e.pool,s.scanTtlMs);return t?{socketPath:t}:void 0})():e.target;if(!f){o.destroy();return}if(e.lens&&String(r.headers["sec-websocket-protocol"]??"").split(",").map(t=>t.trim()).includes("vite-hmr")){const t=e.lens.workspaceKey?`${e.lens.workspaceKey}:${e.lens.project}`:e.lens.project;(0,hmr_reload_1.registerHmrClient)(s,t,o)}accountUpgrade(e,o),d.proxy.ws(r,o,y,{target:f,changeOrigin:!e.pool,agent:d.agentForTarget(f)},()=>o.destroy())},"run");return(r,o,y)=>{o.on("error",()=>o.destroy()),h(r,o,y).catch(()=>o.destroy())}}p(createUpgrade,"createUpgrade"),l(createUpgrade,"createUpgrade");
1
+ "use strict";var k=Object.defineProperty;var p=(i,o)=>k(i,"name",{value:o,configurable:!0});var m=Object.defineProperty,l=p((i,o)=>m(i,"name",{value:o,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createUpgrade=createUpgrade;const manifest_1=require("../manifest"),discovery_1=require("./discovery"),edge_1=require("./edge"),hmr_reload_1=require("./hmr-reload"),service_keys_1=require("./service-keys");function accountUpgrade(i,o){const n=i.svc;if(!n||o.destroyed)return;n.inflight+=1,n.lastUsed=Date.now();let d=!1;o.once("close",()=>{d||(d=!0,n.inflight-=1,n.lastUsed=Date.now())})}p(accountUpgrade,"accountUpgrade"),l(accountUpgrade,"accountUpgrade");function createUpgrade(i){const{rt:o,auth:n,proxy:d,hooks:u}=i,h=l(async(r,t,y)=>{(0,edge_1.stripOwnedHeaders)(r);const c=(0,edge_1.canonicalize)(r.url??"/");if(c===void 0){t.destroy();return}r.url=c.path+c.query;const v=(r.headers.host||"").split(":")[0],e=(0,manifest_1.matchRoute)(o.routes,v,c.policy+c.query);if(!e){t.destroy();return}(0,edge_1.resolveClientIp)(o,r);const a=u.upgradeContext(r,t,void 0);if(a.route=e,e.internal){const s=n.internal(r,e,void 0);if(!s.ok){t.destroy();return}a.caller=s.caller}else{const s=n.public(r,e,void 0,c.policy);if(!s.ok){t.destroy();return}if(a.claims=s.claims,u.has("afterAuth")&&(await u.run("afterAuth",a),a.responded()))return}const g=e.pool?o.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)]:void 0;if(g&&(r.headers["x-internal-token"]=g),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(o,r),u.has("onUpgrade")&&(await u.run("onUpgrade",a),a.responded()))return;const f=e.pool?(()=>{const s=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs);return s?{socketPath:s}:void 0})():e.target;if(!f){t.destroy();return}if(e.lens&&String(r.headers["sec-websocket-protocol"]??"").split(",").map(s=>s.trim()).includes("vite-hmr")){const s=e.lens.workspaceKey?`${e.lens.workspaceKey}:${e.lens.project}`:e.lens.project;(0,hmr_reload_1.registerHmrClient)(o,s,t)}accountUpgrade(e,t),d.proxy.ws(r,t,y,{target:f,changeOrigin:!e.pool,agent:d.agentForTarget(f)},()=>t.destroy())},"run");return(r,t,y)=>{t.on("error",()=>t.destroy()),h(r,t,y).catch(()=>t.destroy())}}p(createUpgrade,"createUpgrade"),l(createUpgrade,"createUpgrade");
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/schema",
3
3
  "version": 2,
4
4
  "title": "Gateway executor",
5
- "description": "Workspace https front door. Reads every project's top-level `davnx` declaration ({ host, port|upstream, prependPrefix?, default? }), builds the hostname route table, serves a local-CA TLS cert with SANs for every host, and proxies requests + websocket upgrades. The optional middleware hook (JWT etc.) runs on every request — service-to-service traffic should use each service's internalPort instead and skip the gateway entirely.",
5
+ "description": "Workspace https front door. Reads every project's top-level `davnx` declaration ({ host, port|upstream, prependPrefix?, default? }), builds the hostname route table, serves a local-CA TLS cert with SANs for every host, and proxies requests + websocket upgrades. The optional middleware hook (JWT etc.) runs on every public request. Service-to-service traffic goes through each service's internal.<host>: the caller presents its own x-api-key, the middleware is skipped, and the x-tenant-id the caller names reaches the callee (as it does over production's direct service-to-service path). A service's internalPort is a loopback diagnostics listener, not the service-to-service path.",
6
6
  "type": "object",
7
7
  "properties": {
8
8
  "ports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.22.6",
3
+ "version": "1.23.1",
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",
@@ -57,9 +57,9 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@lensmcp/core": "1.22.6",
61
- "@lensmcp/node-instrumentation": "1.22.6",
62
- "@lensmcp/nx-plugin": "1.22.6",
60
+ "@lensmcp/core": "1.23.1",
61
+ "@lensmcp/node-instrumentation": "1.23.1",
62
+ "@lensmcp/nx-plugin": "1.23.1",
63
63
  "fastify": "^5.8.5",
64
64
  "@fastify/reply-from": "^12.6.2",
65
65
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",