@lensmcp/cluster 1.24.2 → 1.24.3

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.
@@ -35,6 +35,23 @@ export declare function probePodAlive(socketPath: string, timeoutMs?: number,
35
35
  /** The target service's east-west key. The pod refuses the route without it, so that a
36
36
  * client coming through the gateway cannot read its diagnostics. */
37
37
  serviceKey?: string): Promise<PodAliveness | undefined>;
38
+ /**
39
+ * Why a liveness probe found no evidence — the distinction a single `undefined` hid (issues/closed/foodguard-54):
40
+ *
41
+ * - `silent` — the socket took the connection and nothing answered in time. A dead event loop looks like this,
42
+ * and so does a LIVE one in the middle of a long synchronous stretch (a large parse, a GC pause on a
43
+ * big heap): the kernel accepts into the listen backlog either way. Only waiting tells them apart.
44
+ * - `gone` — nothing listens behind the file any more (`ECONNREFUSED`, `ENOENT`, a reset): the process is gone.
45
+ * - `no-route` — something answered, but not our liveness answer (an older pod with no such route, a refused key,
46
+ * another shape). No evidence either way; the caller keeps its evict-on-deadline behaviour.
47
+ */
48
+ export type PodProbeFailure = 'silent' | 'gone' | 'no-route';
49
+ export interface PodProbeVerdict {
50
+ alive?: PodAliveness;
51
+ failure?: PodProbeFailure;
52
+ }
53
+ /** {@link probePodAlive}, saying WHY when there is no answer. Never throws, never hangs past `timeoutMs`. */
54
+ export declare function probePodAliveVerdict(socketPath: string, timeoutMs?: number, serviceKey?: string): Promise<PodProbeVerdict>;
38
55
  /**
39
56
  * What one failed pod connection says about the POD.
40
57
  *
@@ -1 +1 @@
1
- "use strict";var h=Object.defineProperty;var p=(r,e)=>h(r,"name",{value:e,configurable:!0});var f=Object.defineProperty,u=p((r,e)=>f(r,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.parsePodWatcherTestimony=parsePodWatcherTestimony,exports.probePodAlive=probePodAlive,exports.podConnectionVerdict=podConnectionVerdict,exports.podSocketAccepts=podSocketAccepts;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),net=tslib_1.__importStar(require("node:net")),types_1=require("./types");function parsePodWatcherTestimony(r){if(!r||typeof r!="object")return;const e=r;if(e.schemaVersion!==1)return;const c=u(n=>typeof n=="number"&&Number.isFinite(n)?n:0,"num"),o=e.degraded;return{schemaVersion:1,lastWatchRunAt:c(e.lastWatchRunAt),lastBuildAt:c(e.lastBuildAt),buildsSinceSpawn:c(e.buildsSinceSpawn),degraded:typeof o=="string"&&o?o:o===!0?"degraded":!1,recentPaths:Array.isArray(e.recentPaths)?e.recentPaths.filter(n=>typeof n=="string").slice(-32):[]}}p(parsePodWatcherTestimony,"parsePodWatcherTestimony"),u(parsePodWatcherTestimony,"parsePodWatcherTestimony");async function probePodAlive(r,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS,c){return new Promise(o=>{let n=!1;const i=u(s=>{n||(n=!0,o(s))},"finish");let a;try{a=http.request({socketPath:r,path:types_1.POD_ALIVE_PATH,method:"GET",timeout:e,agent:!1,...c?{headers:{"x-internal-token":c}}:{}},s=>{if(s.statusCode!==200){s.resume(),i(void 0);return}let d="";s.setEncoding("utf8"),s.on("data",t=>{d+=t}),s.on("end",()=>{try{const t=JSON.parse(d);i(t.ok===!0?{inflight:Number(t.inflight??0),activeWork:Number(t.activeWork??0),swapping:t.swapping===!0,...typeof t.pid=="number"?{pid:t.pid}:{},...typeof t.keyFingerprint=="string"?{keyFingerprint:t.keyFingerprint}:{},...(()=>{const l=parsePodWatcherTestimony(t.watcher);return l?{watcher:l}:{}})(),...typeof t.podGeneration=="number"&&Number.isFinite(t.podGeneration)?{podGeneration:t.podGeneration}:{}}:void 0)}catch{i(void 0)}})})}catch{i(void 0);return}a.on("timeout",()=>{a.destroy(),i(void 0)}),a.on("error",()=>i(void 0)),a.end()})}p(probePodAlive,"probePodAlive"),u(probePodAlive,"probePodAlive");async function podConnectionVerdict(r,e,c=types_1.POD_ALIVE_PROBE_TIMEOUT_MS){const o=e.code??"";return o==="ECONNREFUSED"||o==="ENOENT"?"gone":await podSocketAccepts(r,c)?"alive":"gone"}p(podConnectionVerdict,"podConnectionVerdict"),u(podConnectionVerdict,"podConnectionVerdict");function podSocketAccepts(r,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS){return new Promise(c=>{let o=!1,n;const i=u(s=>{o||(o=!0,clearTimeout(a),n?.destroy(),c(s))},"finish"),a=setTimeout(()=>i(!1),e);a.unref?.();try{n=net.connect({path:r}),n.once("connect",()=>i(!0)),n.once("error",()=>i(!1))}catch{i(!1)}})}p(podSocketAccepts,"podSocketAccepts"),u(podSocketAccepts,"podSocketAccepts");
1
+ "use strict";var f=Object.defineProperty;var p=(t,e)=>f(t,"name",{value:e,configurable:!0});var l=Object.defineProperty,u=p((t,e)=>l(t,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.parsePodWatcherTestimony=parsePodWatcherTestimony,exports.probePodAlive=probePodAlive,exports.probePodAliveVerdict=probePodAliveVerdict,exports.podConnectionVerdict=podConnectionVerdict,exports.podSocketAccepts=podSocketAccepts;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),net=tslib_1.__importStar(require("node:net")),types_1=require("./types");function parsePodWatcherTestimony(t){if(!t||typeof t!="object")return;const e=t;if(e.schemaVersion!==1)return;const i=u(n=>typeof n=="number"&&Number.isFinite(n)?n:0,"num"),c=e.degraded;return{schemaVersion:1,lastWatchRunAt:i(e.lastWatchRunAt),lastBuildAt:i(e.lastBuildAt),buildsSinceSpawn:i(e.buildsSinceSpawn),degraded:typeof c=="string"&&c?c:c===!0?"degraded":!1,recentPaths:Array.isArray(e.recentPaths)?e.recentPaths.filter(n=>typeof n=="string").slice(-32):[]}}p(parsePodWatcherTestimony,"parsePodWatcherTestimony"),u(parsePodWatcherTestimony,"parsePodWatcherTestimony");async function probePodAlive(t,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS,i){return(await probePodAliveVerdict(t,e,i)).alive}p(probePodAlive,"probePodAlive"),u(probePodAlive,"probePodAlive");function probePodAliveVerdict(t,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS,i){return new Promise(c=>{let n=!1,s="no-route";const d=u(o=>{n||(n=!0,c(o?{alive:o}:{failure:s}))},"finish");let a;try{a=http.request({socketPath:t,path:types_1.POD_ALIVE_PATH,method:"GET",timeout:e,agent:!1,...i?{headers:{"x-internal-token":i}}:{}},o=>{if(o.statusCode!==200){o.resume(),d(void 0);return}let E="";o.setEncoding("utf8"),o.on("data",r=>{E+=r}),o.on("end",()=>{try{const r=JSON.parse(E);d(r.ok===!0?{inflight:Number(r.inflight??0),activeWork:Number(r.activeWork??0),swapping:r.swapping===!0,...typeof r.pid=="number"?{pid:r.pid}:{},...typeof r.keyFingerprint=="string"?{keyFingerprint:r.keyFingerprint}:{},...(()=>{const P=parsePodWatcherTestimony(r.watcher);return P?{watcher:P}:{}})(),...typeof r.podGeneration=="number"&&Number.isFinite(r.podGeneration)?{podGeneration:r.podGeneration}:{}}:void 0)}catch{d(void 0)}})})}catch{s="gone",d(void 0);return}a.on("timeout",()=>{s="silent",a.destroy(),d(void 0)}),a.on("error",o=>{s!=="silent"&&(s=o.code==="ECONNREFUSED"||o.code==="ENOENT"||o.code==="ECONNRESET"||o.code==="EPIPE"?"gone":"silent"),d(void 0)}),a.end()})}p(probePodAliveVerdict,"probePodAliveVerdict"),u(probePodAliveVerdict,"probePodAliveVerdict");async function podConnectionVerdict(t,e,i=types_1.POD_ALIVE_PROBE_TIMEOUT_MS){const c=e.code??"";return c==="ECONNREFUSED"||c==="ENOENT"?"gone":await podSocketAccepts(t,i)?"alive":"gone"}p(podConnectionVerdict,"podConnectionVerdict"),u(podConnectionVerdict,"podConnectionVerdict");function podSocketAccepts(t,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS){return new Promise(i=>{let c=!1,n;const s=u(a=>{c||(c=!0,clearTimeout(d),n?.destroy(),i(a))},"finish"),d=setTimeout(()=>s(!1),e);d.unref?.();try{n=net.connect({path:t}),n.once("connect",()=>s(!0)),n.once("error",()=>s(!1))}catch{s(!1)}})}p(podSocketAccepts,"podSocketAccepts"),u(podSocketAccepts,"podSocketAccepts");
@@ -1,6 +1,7 @@
1
1
  import * as http from 'node:http';
2
2
  import type * as stream from 'node:stream';
3
3
  import type { GatewayRuntime, Route } from './types';
4
+ import { type PodProbeFailure } from './pod-probe';
4
5
  import type { Observability } from './observability';
5
6
  import type { ServiceLayer } from './lifecycle';
6
7
  /**
@@ -44,4 +45,17 @@ export interface ProxyLayer {
44
45
  destroyAgents(): void;
45
46
  closeProxy(): void;
46
47
  }
48
+ /** What the guard knew when it condemned a pod — the incident's evidence (issues/closed/foodguard-54). */
49
+ export interface WedgeDetail {
50
+ /** How long the pod had held the whole request with nothing written back. */
51
+ heldMs: number;
52
+ /** How long its liveness route stayed silent before the verdict (0 when the verdict needed no confirming). */
53
+ silentMs: number;
54
+ /** Liveness probes that found no answer. */
55
+ probes: number;
56
+ /** Why the last probe found none — `deadline` when no probe is configured (`LENSMCP_POD_SLOW_FIRST_BYTE_MS=0`). */
57
+ failure: PodProbeFailure | 'deadline';
58
+ }
59
+ /** What the client reads in the 502 — what happened, and what it means for the call (issues/closed/foodguard-54). */
60
+ export declare function wedgedMessage(podName: string, project: string, d: WedgeDetail): string;
47
61
  export declare function createProxy(rt: GatewayRuntime, obs: Observability, svcLayer: ServiceLayer): ProxyLayer;
@@ -1 +1 @@
1
- "use strict";var G=Object.defineProperty;var j=(t,c)=>G(t,"name",{value:c,configurable:!0});var K=Object.defineProperty,s=j((t,c)=>K(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.createProxy=createProxy;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),edge_1=require("./edge"),gateway_errors_1=require("../gateway-errors"),body_stall_1=require("../body-stall"),types_1=require("./types"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys");function isConnError(t){const c=t.code??"";return c==="ECONNREFUSED"||c==="ECONNRESET"||c==="ETIMEDOUT"||c==="EPIPE"||c==="EHOSTUNREACH"||c==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(t.message)}j(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(t){if(!IDEMPOTENT_METHODS.has((t.method??"GET").toUpperCase())||t.headers["transfer-encoding"]!==void 0)return!1;const c=t.headers["content-length"];return!(c!==void 0&&String(c).trim()!=="0")}j(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(t,c){return c.headersSent||c.writableEnded||c.destroyed===!0||t.aborted===!0}j(edgeGone,"edgeGone"),s(edgeGone,"edgeGone");const HOP_BY_HOP=new Set(["connection","keep-alive","proxy-connection","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),UPSTREAM_CONNECTION_HEADERS=["connection","keep-alive","proxy-connection"],httpProxy=require("http-proxy");function declaredFirstByteMs(t){const c=Number(t);return Number.isFinite(c)&&c>0?c:types_1.POD_RESPONSE_TIMEOUT_MS}j(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");function armFirstByteGuard(t){let c,$=!1,q=!1,w=(0,body_stall_1.requestBodyArriving)(t.req)?void 0:Date.now();const O=s(()=>{w??=Date.now()},"onBodyEnd");w===void 0&&t.req.once("end",O);const U=s(()=>(w===void 0&&!(0,body_stall_1.requestBodyArriving)(t.req)&&(w=Date.now()),w===void 0?0:Date.now()-w),"heldMs"),D=s(S=>{c=setTimeout(A,S),c.unref?.()},"arm"),A=s(()=>{if($||!t.pending())return;const S=U();if(t.ceilingMs<=0){if(w!==void 0&&S>=t.firstByteMs){t.onWedged(`no response within ${S}ms (wedged socket) \u2014 evicted + respawning`);return}D(w===void 0?t.firstByteMs:t.firstByteMs-S);return}(0,pod_probe_1.probePodAlive)(t.socketPath,void 0,t.serviceKey).then(x=>{if($||!t.pending())return;const T=U();if(!x){t.onWedged(`no response within ${T}ms and no answer on ${types_1.POD_ALIVE_PATH} (wedged socket) \u2014 evicted + respawning`);return}if(T>=t.ceilingMs){t.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${T}ms (ceiling ${t.ceilingMs}ms) \u2014 giving up on this request, pod kept`,x);return}!q&&w!==void 0&&(q=!0,t.onSlow(T,x)),D(Math.max(t.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return D(t.firstByteMs),{clear:s(()=>{$=!0,c&&clearTimeout(c),t.req.removeListener("end",O)},"clear")}}j(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(t,c,$){const{recordEdge:q,finishTrace:w,traceStep:O,emit:U}=c,D=s(async e=>{if(!e.lens||!t.lensSupervisor)return"unsupported";let r;const o=new Promise(p=>{r=setTimeout(()=>p("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),g=t.lensSupervisor.recover(e.lens,"proxy-connection").then(p=>p?"ready":"failed").catch(()=>"failed"),h=await Promise.race([g,o]);r&&clearTimeout(r);const m=t.lensSupervisor.status(e.lens);return h==="failed"&&(!m||m.state==="ready")?"unsupported":h},"waitForManagedRecovery"),A=s((e,r,o,g,h,m)=>{if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}const p=e.lens?t.lensSupervisor?.status(e.lens):void 0,E=m==="timeout"||p?.state==="starting"||p?.state==="replacing",v=(0,gateway_errors_1.ensureRequestId)(r),d=e.lens?{project:e.project,reason:p?.reason??"connection-failure",generation:p?.generation??0,...E?{retryAfterMs:2e3}:{}}:void 0,n=E?503:502,a=E?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${v} project=${e.project} target=${String(g)} state=${p?.state??"unmanaged"} generation=${p?.generation??0}: ${h.message}`),U("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:v,project:e.project,target:String(g),state:p?.state??"unmanaged",generation:p?.generation,error:h.message}),(0,edge_1.sendError)(o,r,n,a,E?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,d,E?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),S=(0,body_stall_1.createBodyStallGuard)(t.bodyStallMs??types_1.BODY_STALL_MS),x={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},T=new http.Agent(x),I=new(require("node:https")).Agent(x),L=s(e=>typeof e=="string"&&e.startsWith("https:")?I:T,"agentForTarget"),C=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:T}),N=s((e,r,o)=>{const g=s(()=>{o()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",g),e.once("close",()=>r.removeListener("close",g))},"propagateEdgeCancel");C.on("proxyReq",(e,r,o)=>{for(const g of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(g);e.setHeader("connection","keep-alive"),N(e,o,()=>o.writableEnded)}),C.on("proxyRes",(e,r,o)=>{const g=r.__lensmcpPod;g&&(e.headers["x-lensmcp-pod"]=g),delete e.headers["x-powered-by"],e.headers.server="LensMCP",o&&N(e,o,()=>o.writableEnded),o&&S.armResponse(o)});const H=s((e,r,o,g)=>{(0,edge_1.stampForwardingHeaders)(t,r),S.arm(r);const h={},m=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[i,f]of Object.entries(r.headers))f===void 0||i.startsWith(":")||HOP_BY_HOP.has(i)||m&&(i==="content-length"||i==="transfer-encoding")||(h[i]=f);let p=http,E;if(typeof e=="object")E={socketPath:e.socketPath,path:r.url,method:r.method,headers:h,agent:T};else{const i=new URL(e),f=i.protocol==="https:";p=f?require("node:https"):http,h.host=i.host,E={protocol:i.protocol,hostname:i.hostname,port:i.port||(f?443:80),path:r.url,method:r.method,headers:h,agent:L(e)}}const v=o;v.__lensmcpErrContained||(v.__lensmcpErrContained=!0,o.on("error",()=>{}));let d;const n=s(()=>{o.writableEnded||u.destroy()},"onReqGone"),a=s(()=>{d&&!d.destroyed&&d.destroy()},"onEdgeClose"),l=s(()=>{r.removeListener("error",n),r.removeListener("aborted",n),r.removeListener("close",n),o.removeListener("close",a)},"detach"),u=p.request(E,i=>{if(d=i,g.onResponse(),o.headersSent||o.writableEnded||o.destroyed){i.destroy();return}const f={};for(const[P,y]of Object.entries(i.headers))y===void 0||HOP_BY_HOP.has(P)||(f[P]=y);delete f["x-powered-by"],f.server="LensMCP";const R=r.__lensmcpPod;R&&(f["x-lensmcp-pod"]=R);try{o.writeHead(i.statusCode??502,f)}catch(P){console.error("[gateway] h2 relay: bad response header, resetting stream:",P.message),i.destroy(),o.destroyed||o.destroy(P);return}i.pipe(o),S.armResponse(o);const k=s(()=>{!o.writableEnded&&!o.destroyed&&o.destroy()},"abortEdge");i.once("error",k),i.once("close",k),o.on("close",a)});u.once("error",i=>g.onError(i)),u.once("close",l),r.on("error",n),r.on("aborted",n),r.on("close",n),m?u.end():r.pipe(u)},"pipeUpstream"),W=s(async(e,r,o)=>{const g=r.__lensmcpTrace;if(e.pool){const d=e.svc;d&&(d.lastUsed=Date.now(),d.inflight+=1,o.on("close",()=>{d.inflight-=1,d.lastUsed=Date.now()}));let n=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!n&&d&&(O(g,"cold-start",{project:e.project}),await $.ensureUp(d)&&(n=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),d&&n&&$.markUp(d,"observed"),d&&$.scaleUp(d),!n){const y=d?.lastErrors?.length?` Service errors: ${d.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${y} The watcher recovers and the next request retries automatically.`);return}const a=path.basename(n,".sock");r.__lensmcpPod=a,O(g,"pod-select",{pod:a,pods:e.pool.socks.length,strategy:"round-robin"});let l=!1;const u=s((y,b="unlink")=>{if(e.pool.socks=e.pool.socks.filter(_=>_!==n),e.pool.idx=-1,b==="unlink")try{fs.unlinkSync(n)}catch{}console.error(`[gateway] pod ${a} of ${e.project}: ${y}`)},"evictPod"),i=armFirstByteGuard({socketPath:n,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!l&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s(y=>{l=!0,u(y),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${a} of ${e.project} was unresponsive \u2014 evicted, retry.`)},"onWedged"),onCeiling:s(y=>{l=!0,u(y,"keep"),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${a} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs.`)},"onCeiling"),onSlow:s((y,b)=>{console.log(`[gateway] pod ${a} of ${e.project}: slow first byte, pod alive (${y}ms, ${b.inflight} in-flight, ${b.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>i.clear());const f=n,R=isReplayableRequest(r);let k=0;const P=s(()=>{k+=1,H({socketPath:f},r,o,{onResponse:s(()=>{i.clear(),l=!0},"onResponse"),onError:s(y=>{if(!l){if(edgeGone(r,o)){i.clear(),l=!0;return}(0,pod_probe_1.podConnectionVerdict)(f,y).then(b=>{if(!l){if(b==="alive"&&k<2&&R&&!edgeGone(r,o)){console.log(`[gateway] pod ${a} of ${e.project}: ${y.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),P();return}if(i.clear(),l=!0,b==="gone"){u(y.message),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${a} of ${e.project} unavailable \u2014 retry.`);return}console.error(`[gateway] pod ${a} of ${e.project}: ${y.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${a} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");P();return}const h=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let m=0,p=!1;const E=isReplayableRequest(r),v=s(()=>{if(edgeGone(r,o))return;const d=e.target;H(d,r,o,{onResponse:s(()=>{},"onResponse"),onError:s(n=>{if(isConnError(n)&&(m+=1),e.lens&&isConnError(n)&&E&&m>=2&&!p&&!edgeGone(r,o)){p=!0,D(e).then(a=>{if(!edgeGone(r,o)){if(a==="ready"){v();return}if(a==="unsupported"&&Date.now()<h){setTimeout(v,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}A(e,r,o,e.target,n,a)}});return}if(isConnError(n)&&E&&Date.now()<h&&!edgeGone(r,o)){setTimeout(v,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}A(e,r,o,d,n)},"onError")})},"tryTcp");v()},"forwardNative");return{proxy:C,forward:s(async(e,r,o)=>{const g=Date.now(),h=r.__lensmcpTrace;if(o.on("close",()=>{q(e,Date.now()-g,o.statusCode??0,r.__lensmcpCaller),w(h,e.project,o.statusCode??0,{...r.__lensmcpPod?{pod:r.__lensmcpPod}:{},...r.__lensmcpCaller?{caller:r.__lensmcpCaller}:{}},e)}),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(t,r),S.arm(r),(r.httpVersionMajor??1)>=2){await W(e,r,o);return}if(e.pool){const n=e.svc;n&&(n.lastUsed=Date.now(),n.inflight+=1,o.on("close",()=>{n.inflight-=1,n.lastUsed=Date.now()}));let a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!a&&n&&(O(h,"cold-start",{project:e.project}),await $.ensureUp(n)&&(a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),n&&a&&$.markUp(n,"observed"),n&&$.scaleUp(n),!a){const _=n?.lastErrors?.length?` Service errors: ${n.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${_} The watcher recovers and the next request retries automatically.`);return}const l=path.basename(a,".sock");r.__lensmcpPod=l,O(h,"pod-select",{pod:l,pods:e.pool.socks.length,strategy:"round-robin"});let u=!1;const i=s((_,M="unlink")=>{if(e.pool.socks=e.pool.socks.filter(B=>B!==a),e.pool.idx=-1,M==="unlink")try{fs.unlinkSync(a)}catch{}console.error(`[gateway] pod ${l} of ${e.project}: ${_}`)},"evictPod"),f=armFirstByteGuard({socketPath:a,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!u&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s(_=>{u=!0,i(_),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${l} of ${e.project} was unresponsive \u2014 evicted, retry.`)},"onWedged"),onCeiling:s(_=>{u=!0,i(_,"keep"),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${l} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs.`)},"onCeiling"),onSlow:s((_,M)=>{console.log(`[gateway] pod ${l} of ${e.project}: slow first byte, pod alive (${_}ms, ${M.inflight} in-flight, ${M.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>f.clear());const R=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),k=a,P=isReplayableRequest(r);let y=0;const b=s(()=>{y+=1;const _=Date.now();C.web(r,o,{target:{socketPath:k},agent:T,proxyTimeout:R},M=>{if(!u){if(edgeGone(r,o)){f.clear(),u=!0;return}(0,pod_probe_1.podConnectionVerdict)(k,M).then(B=>{if(u)return;const F=Date.now()-_>=R-250;if(B==="alive"&&y<2&&P&&!F&&!edgeGone(r,o)){console.log(`[gateway] pod ${l} of ${e.project}: ${M.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),b();return}if(f.clear(),u=!0,B==="gone"){i(M.message),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${l} of ${e.project} unavailable \u2014 retry.`);return}console.error(`[gateway] pod ${l} of ${e.project}: ${M.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${l} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");b();return}const m=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let p=0,E=!1;const v=isReplayableRequest(r),d=s(()=>{if(edgeGone(r,o))return;const n=e.target,a={target:n,autoRewrite:typeof n=="string",changeOrigin:!0,agent:L(n)};C.web(r,o,a,l=>{if(isConnError(l)&&(p+=1),e.lens&&isConnError(l)&&v&&p>=2&&!E&&!edgeGone(r,o)){E=!0,D(e).then(u=>{if(!edgeGone(r,o)){if(u==="ready"){d();return}if(u==="unsupported"&&Date.now()<m){setTimeout(d,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}A(e,r,o,e.target,l,u)}});return}if(isConnError(l)&&v&&Date.now()<m&&!edgeGone(r,o)){setTimeout(d,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}o.destroyed||A(e,r,o,n,l)})},"tryTcp");d()},"forward"),agentForTarget:L,destroyAgents:s(()=>{S.stop(),T.destroy(),I.destroy()},"destroyAgents"),closeProxy:s(()=>{try{C.close?.()}catch{}},"closeProxy")}}j(createProxy,"createProxy"),s(createProxy,"createProxy");
1
+ "use strict";var K=Object.defineProperty;var x=(t,c)=>K(t,"name",{value:c,configurable:!0});var G=Object.defineProperty,s=x((t,c)=>G(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.wedgedMessage=wedgedMessage,exports.createProxy=createProxy;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),edge_1=require("./edge"),gateway_errors_1=require("../gateway-errors"),body_stall_1=require("../body-stall"),types_1=require("./types"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys");function isConnError(t){const c=t.code??"";return c==="ECONNREFUSED"||c==="ECONNRESET"||c==="ETIMEDOUT"||c==="EPIPE"||c==="EHOSTUNREACH"||c==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(t.message)}x(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(t){if(!IDEMPOTENT_METHODS.has((t.method??"GET").toUpperCase())||t.headers["transfer-encoding"]!==void 0)return!1;const c=t.headers["content-length"];return!(c!==void 0&&String(c).trim()!=="0")}x(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(t,c){return c.headersSent||c.writableEnded||c.destroyed===!0||t.aborted===!0}x(edgeGone,"edgeGone"),s(edgeGone,"edgeGone");const HOP_BY_HOP=new Set(["connection","keep-alive","proxy-connection","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),UPSTREAM_CONNECTION_HEADERS=["connection","keep-alive","proxy-connection"],httpProxy=require("http-proxy");function declaredFirstByteMs(t){const c=Number(t);return Number.isFinite(c)&&c>0?c:types_1.POD_RESPONSE_TIMEOUT_MS}x(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");const secs=s(t=>`${(t/1e3).toFixed(1)}s`,"secs");function wedgedMessage(t,c,E){const U=E.failure==="gone"?"its process is gone":E.failure==="silent"?`it did not answer its liveness check for ${secs(E.silentMs)}`:"it gave no liveness answer";return`Pod ${t} of ${c} sent no response for ${secs(E.heldMs)} and ${U} \u2014 taken out of rotation, and the service is being restarted, so work it was still doing for this request may have been cut short. Retry if the call is safe to repeat.`}x(wedgedMessage,"wedgedMessage"),s(wedgedMessage,"wedgedMessage");function armFirstByteGuard(t){let c,E=!1,U=!1,q,M=0,P=(0,body_stall_1.requestBodyArriving)(t.req)?void 0:Date.now();const b=s(()=>{P??=Date.now()},"onBodyEnd");P===void 0&&t.req.once("end",b);const N=s(()=>(P===void 0&&!(0,body_stall_1.requestBodyArriving)(t.req)&&(P=Date.now()),P===void 0?0:Date.now()-P),"heldMs"),k=s(j=>{c=setTimeout(C,j),c.unref?.()},"arm"),C=s(()=>{if(E||!t.pending())return;const j=N();if(t.ceilingMs<=0){if(P!==void 0&&j>=t.firstByteMs){t.onWedged(`no response within ${j}ms (wedged socket) \u2014 evicted + respawning`,{heldMs:j,silentMs:0,probes:0,failure:"deadline"});return}k(P===void 0?t.firstByteMs:t.firstByteMs-j);return}(0,pod_probe_1.probePodAliveVerdict)(t.socketPath,void 0,t.serviceKey).then(({alive:D,failure:I})=>{if(E||!t.pending())return;const R=N();if(!D){if(M+=1,I==="silent"&&types_1.POD_WEDGE_CONFIRM_MS>0&&(q??=Date.now(),Date.now()-q<types_1.POD_WEDGE_CONFIRM_MS)){k(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}const O=q===void 0?0:Date.now()-q,L=I==="gone"?"its socket no longer accepts (the pod process is gone)":I==="silent"?`nothing answered on ${types_1.POD_ALIVE_PATH} for ${O}ms (${M} probe${M===1?"":"s"})`:`no liveness answer on ${types_1.POD_ALIVE_PATH}`;t.onWedged(`no response within ${R}ms and ${L} (wedged socket) \u2014 evicted + respawning`,{heldMs:R,silentMs:O,probes:M,failure:I??"no-route"});return}if(q=void 0,R>=t.ceilingMs){t.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${R}ms (ceiling ${t.ceilingMs}ms) \u2014 giving up on this request, pod kept`,D);return}!U&&P!==void 0&&(U=!0,t.onSlow(R,D)),k(Math.max(t.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return k(t.firstByteMs),{clear:s(()=>{E=!0,c&&clearTimeout(c),t.req.removeListener("end",b)},"clear")}}x(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(t,c,E){const{recordEdge:U,finishTrace:q,traceStep:M,emit:P}=c,b=s((e,r,o,p)=>{const h=p.kind==="pod-evicted";P(h?"error":"warning",`${h?"pod evicted":"request given up"}: ${e.project} (${o}) \u2014 ${p.status}`,`cluster-pod-incident:${e.project}`,{kind:p.kind,project:e.project,pod:o,status:p.status,reason:p.reason,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0],...p.detail??{}})},"reportPodIncident"),N=s(async e=>{if(!e.lens||!t.lensSupervisor)return"unsupported";let r;const o=new Promise(g=>{r=setTimeout(()=>g("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),p=t.lensSupervisor.recover(e.lens,"proxy-connection").then(g=>g?"ready":"failed").catch(()=>"failed"),h=await Promise.race([p,o]);r&&clearTimeout(r);const w=t.lensSupervisor.status(e.lens);return h==="failed"&&(!w||w.state==="ready")?"unsupported":h},"waitForManagedRecovery"),k=s((e,r,o,p,h,w)=>{if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}const g=e.lens?t.lensSupervisor?.status(e.lens):void 0,v=w==="timeout"||g?.state==="starting"||g?.state==="replacing",S=(0,gateway_errors_1.ensureRequestId)(r),l=e.lens?{project:e.project,reason:g?.reason??"connection-failure",generation:g?.generation??0,...v?{retryAfterMs:2e3}:{}}:void 0,n=v?503:502,i=v?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${S} project=${e.project} target=${String(p)} state=${g?.state??"unmanaged"} generation=${g?.generation??0}: ${h.message}`),P("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:S,project:e.project,target:String(p),state:g?.state??"unmanaged",generation:g?.generation,error:h.message}),(0,edge_1.sendError)(o,r,n,i,v?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,l,v?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),C=(0,body_stall_1.createBodyStallGuard)(t.bodyStallMs??types_1.BODY_STALL_MS),j={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},D=new http.Agent(j),I=new(require("node:https")).Agent(j),R=s(e=>typeof e=="string"&&e.startsWith("https:")?I:D,"agentForTarget"),O=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:D}),L=s((e,r,o)=>{const p=s(()=>{o()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",p),e.once("close",()=>r.removeListener("close",p))},"propagateEdgeCancel");O.on("proxyReq",(e,r,o)=>{for(const p of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(p);e.setHeader("connection","keep-alive"),L(e,o,()=>o.writableEnded)}),O.on("proxyRes",(e,r,o)=>{const p=r.__lensmcpPod;p&&(e.headers["x-lensmcp-pod"]=p),delete e.headers["x-powered-by"],e.headers.server="LensMCP",o&&L(e,o,()=>o.writableEnded),o&&C.armResponse(o)});const W=s((e,r,o,p)=>{(0,edge_1.stampForwardingHeaders)(t,r),C.arm(r);const h={},w=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[a,f]of Object.entries(r.headers))f===void 0||a.startsWith(":")||HOP_BY_HOP.has(a)||w&&(a==="content-length"||a==="transfer-encoding")||(h[a]=f);let g=http,v;if(typeof e=="object")v={socketPath:e.socketPath,path:r.url,method:r.method,headers:h,agent:D};else{const a=new URL(e),f=a.protocol==="https:";g=f?require("node:https"):http,h.host=a.host,v={protocol:a.protocol,hostname:a.hostname,port:a.port||(f?443:80),path:r.url,method:r.method,headers:h,agent:R(e)}}const S=o;S.__lensmcpErrContained||(S.__lensmcpErrContained=!0,o.on("error",()=>{}));let l;const n=s(()=>{o.writableEnded||y.destroy()},"onReqGone"),i=s(()=>{l&&!l.destroyed&&l.destroy()},"onEdgeClose"),d=s(()=>{r.removeListener("error",n),r.removeListener("aborted",n),r.removeListener("close",n),o.removeListener("close",i)},"detach"),y=g.request(v,a=>{if(l=a,p.onResponse(),o.headersSent||o.writableEnded||o.destroyed){a.destroy();return}const f={};for(const[T,u]of Object.entries(a.headers))u===void 0||HOP_BY_HOP.has(T)||(f[T]=u);delete f["x-powered-by"],f.server="LensMCP";const B=r.__lensmcpPod;B&&(f["x-lensmcp-pod"]=B);try{o.writeHead(a.statusCode??502,f)}catch(T){console.error("[gateway] h2 relay: bad response header, resetting stream:",T.message),a.destroy(),o.destroyed||o.destroy(T);return}a.pipe(o),C.armResponse(o);const A=s(()=>{!o.writableEnded&&!o.destroyed&&o.destroy()},"abortEdge");a.once("error",A),a.once("close",A),o.on("close",i)});y.once("error",a=>p.onError(a)),y.once("close",d),r.on("error",n),r.on("aborted",n),r.on("close",n),w?y.end():r.pipe(y)},"pipeUpstream"),F=s(async(e,r,o)=>{const p=r.__lensmcpTrace;if(e.pool){const l=e.svc;l&&(l.lastUsed=Date.now(),l.inflight+=1,o.on("close",()=>{l.inflight-=1,l.lastUsed=Date.now()}));let n=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!n&&l&&(M(p,"cold-start",{project:e.project}),await E.ensureUp(l)&&(n=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),l&&n&&E.markUp(l,"observed"),l&&E.scaleUp(l),!n){const u=l?.lastErrors?.length?` Service errors: ${l.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${u} The watcher recovers and the next request retries automatically.`);return}const i=path.basename(n,".sock");r.__lensmcpPod=i,M(p,"pod-select",{pod:i,pods:e.pool.socks.length,strategy:"round-robin"});let d=!1;const y=s((u,$="unlink")=>{if(e.pool.socks=e.pool.socks.filter(m=>m!==n),e.pool.idx=-1,$==="unlink")try{fs.unlinkSync(n)}catch{}console.error(`[gateway] pod ${i} of ${e.project}: ${u}`)},"evictPod"),a=armFirstByteGuard({socketPath:n,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!d&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((u,$)=>{d=!0,y(u),b(e,r,i,{kind:"pod-evicted",status:502,reason:u,detail:$}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(i,e.project,$))},"onWedged"),onCeiling:s(u=>{d=!0,y(u,"keep"),b(e,r,i,{kind:"request-gave-up",status:504,reason:u}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${i} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((u,$)=>{console.log(`[gateway] pod ${i} of ${e.project}: slow first byte, pod alive (${u}ms, ${$.inflight} in-flight, ${$.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>a.clear());const f=n,B=isReplayableRequest(r);let A=0;const T=s(()=>{A+=1,W({socketPath:f},r,o,{onResponse:s(()=>{a.clear(),d=!0},"onResponse"),onError:s(u=>{if(!d){if(edgeGone(r,o)){a.clear(),d=!0;return}(0,pod_probe_1.podConnectionVerdict)(f,u).then($=>{if(!d){if($==="alive"&&A<2&&B&&!edgeGone(r,o)){console.log(`[gateway] pod ${i} of ${e.project}: ${u.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),T();return}if(a.clear(),d=!0,$==="gone"){y(u.message),b(e,r,i,{kind:"pod-evicted",status:502,reason:`${u.message} \u2014 its socket no longer accepts (the pod process is gone)`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} is gone (${u.message}) \u2014 retry.`);return}console.error(`[gateway] pod ${i} of ${e.project}: ${u.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${i} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");T();return}const h=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let w=0,g=!1;const v=isReplayableRequest(r),S=s(()=>{if(edgeGone(r,o))return;const l=e.target;W(l,r,o,{onResponse:s(()=>{},"onResponse"),onError:s(n=>{if(isConnError(n)&&(w+=1),e.lens&&isConnError(n)&&v&&w>=2&&!g&&!edgeGone(r,o)){g=!0,N(e).then(i=>{if(!edgeGone(r,o)){if(i==="ready"){S();return}if(i==="unsupported"&&Date.now()<h){setTimeout(S,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}k(e,r,o,e.target,n,i)}});return}if(isConnError(n)&&v&&Date.now()<h&&!edgeGone(r,o)){setTimeout(S,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}k(e,r,o,l,n)},"onError")})},"tryTcp");S()},"forwardNative");return{proxy:O,forward:s(async(e,r,o)=>{const p=Date.now(),h=r.__lensmcpTrace;if(o.on("close",()=>{U(e,Date.now()-p,o.statusCode??0,r.__lensmcpCaller),q(h,e.project,o.statusCode??0,{...r.__lensmcpPod?{pod:r.__lensmcpPod}:{},...r.__lensmcpCaller?{caller:r.__lensmcpCaller}:{}},e)}),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(t,r),C.arm(r),(r.httpVersionMajor??1)>=2){await F(e,r,o);return}if(e.pool){const n=e.svc;n&&(n.lastUsed=Date.now(),n.inflight+=1,o.on("close",()=>{n.inflight-=1,n.lastUsed=Date.now()}));let i=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!i&&n&&(M(h,"cold-start",{project:e.project}),await E.ensureUp(n)&&(i=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),n&&i&&E.markUp(n,"observed"),n&&E.scaleUp(n),!i){const m=n?.lastErrors?.length?` Service errors: ${n.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${m} The watcher recovers and the next request retries automatically.`);return}const d=path.basename(i,".sock");r.__lensmcpPod=d,M(h,"pod-select",{pod:d,pods:e.pool.socks.length,strategy:"round-robin"});let y=!1;const a=s((m,_="unlink")=>{if(e.pool.socks=e.pool.socks.filter(H=>H!==i),e.pool.idx=-1,_==="unlink")try{fs.unlinkSync(i)}catch{}console.error(`[gateway] pod ${d} of ${e.project}: ${m}`)},"evictPod"),f=armFirstByteGuard({socketPath:i,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!y&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s((m,_)=>{y=!0,a(m),b(e,r,d,{kind:"pod-evicted",status:502,reason:m,detail:_}),(0,edge_1.sendError)(o,r,502,"unavailable",wedgedMessage(d,e.project,_))},"onWedged"),onCeiling:s(m=>{y=!0,a(m,"keep"),b(e,r,d,{kind:"request-gave-up",status:504,reason:m}),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${d} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs. The pod was kept: the request may still complete there.`)},"onCeiling"),onSlow:s((m,_)=>{console.log(`[gateway] pod ${d} of ${e.project}: slow first byte, pod alive (${m}ms, ${_.inflight} in-flight, ${_.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>f.clear());const B=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),A=i,T=isReplayableRequest(r);let u=0;const $=s(()=>{u+=1;const m=Date.now();O.web(r,o,{target:{socketPath:A},agent:D,proxyTimeout:B},_=>{if(!y){if(edgeGone(r,o)){f.clear(),y=!0;return}(0,pod_probe_1.podConnectionVerdict)(A,_).then(H=>{if(y)return;const V=Date.now()-m>=B-250;if(H==="alive"&&u<2&&T&&!V&&!edgeGone(r,o)){console.log(`[gateway] pod ${d} of ${e.project}: ${_.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),$();return}if(f.clear(),y=!0,H==="gone"){a(_.message),b(e,r,d,{kind:"pod-evicted",status:502,reason:`${_.message} \u2014 its socket no longer accepts (the pod process is gone)`}),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${d} of ${e.project} is gone (${_.message}) \u2014 retry.`);return}console.error(`[gateway] pod ${d} of ${e.project}: ${_.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${d} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");$();return}const w=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let g=0,v=!1;const S=isReplayableRequest(r),l=s(()=>{if(edgeGone(r,o))return;const n=e.target,i={target:n,autoRewrite:typeof n=="string",changeOrigin:!0,agent:R(n)};O.web(r,o,i,d=>{if(isConnError(d)&&(g+=1),e.lens&&isConnError(d)&&S&&g>=2&&!v&&!edgeGone(r,o)){v=!0,N(e).then(y=>{if(!edgeGone(r,o)){if(y==="ready"){l();return}if(y==="unsupported"&&Date.now()<w){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}k(e,r,o,e.target,d,y)}});return}if(isConnError(d)&&S&&Date.now()<w&&!edgeGone(r,o)){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}o.destroyed||k(e,r,o,n,d)})},"tryTcp");l()},"forward"),agentForTarget:R,destroyAgents:s(()=>{C.stop(),D.destroy(),I.destroy()},"destroyAgents"),closeProxy:s(()=>{try{O.close?.()}catch{}},"closeProxy")}}x(createProxy,"createProxy"),s(createProxy,"createProxy");
@@ -1,3 +1,3 @@
1
- "use strict";var Se=Object.defineProperty;var T=(s,r)=>Se(s,"name",{value:r,configurable:!0});var _e=Object.defineProperty,n=T((s,r)=>_e(s,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=exports.BENIGN_SOCKET_ERRNOS=void 0,exports.buildEdgeVerifierPool=buildEdgeVerifierPool,exports.classifyClientError=classifyClientError,exports.createClientErrorLogGate=createClientErrorLogGate,exports.retirePreviousWorkspace=retirePreviousWorkspace,exports.resolveMaxConnections=resolveMaxConnections,exports.h2BudgetFromEnv=h2BudgetFromEnv,exports.createH2Governor=createH2Governor,exports.startGateway=startGateway;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),scope_1=require("./scope"),route_registry_1=require("./route-registry"),control_1=require("./control"),workspace_middleware_1=require("./workspace-middleware"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),observability_1=require("./observability"),lifecycle_1=require("./lifecycle"),process_names_1=require("./process-names"),project_graph_store_1=require("./project-graph-store"),serve_launch_1=require("./serve-launch"),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),lens_children_1=require("./lens-children"),proxy_1=require("./proxy"),auth_1=require("./auth"),hooks_1=require("./hooks"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),jwks_verify_1=require("../jwks-verify"),types_1=require("./types");function insecureLocalFetch(s,r){return new Promise((d,h)=>{const a=(s.startsWith("https:")?require("node:https"):require("node:http")).request(s,{method:"GET",headers:r?.headers,rejectUnauthorized:!1},y=>{const _=[];y.on("data",S=>_.push(S)),y.on("end",()=>{const S=y.statusCode??0,p=Buffer.concat(_).toString("utf8");d({ok:S>=200&&S<300,status:S,json:n(async()=>JSON.parse(p),"json")})})});a.on("error",h),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(s){const r=process.env.LENSMCP_GW_JWKS_URL,d=process.env.LENSMCP_GW_JWT_ISS,h=new Map;let a;const y=n((_,S)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${_}${S}`);const p=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:_,...d?{issuer:d}:{},fetchImpl:insecureLocalFetch});return p.refresh().catch(o=>console.warn("[gateway] edge JWKS warm failed (will retry):",o.message)),p},"make");return{for(_){if(r)return a??=y(r,"");const S=_.split(":")[0].toLowerCase();let p;for(const c of s()){const i=c.host;if(typeof i!="string"||!/^auth\./.test(i)||i.startsWith("internal."))continue;const A=i.slice(5);(S===A||S.endsWith(`.${A}`))&&(!p||A.length>p.length)&&(p=A)}if(!p)return;let o=h.get(p);return o||(o=y(`https://auth.${p}/jwks.json`,` (auto-derived for apex ${p})`),h.set(p,o)),o},stop(){a?.stop();for(const _ of h.values())_.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(s,r){return s&&exports.BENIGN_SOCKET_ERRNOS.has(s)?{status:null,report:!1,cause:"client-abort"}:s==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:s==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:s==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:s?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:s??r??"unknown"}}T(classifyClientError,"classifyClientError"),n(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(s=1e4){const r=new Map;return d=>{const h=Date.now(),a=r.get(d)??0;return h-a<s?!1:(r.set(d,h),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(s){s.on("error",r=>{exports.BENIGN_SOCKET_ERRNOS.has(r.code??"")||console.warn(`[gateway] inbound socket error (${r.code??r.message}) \u2014 dropping this connection only`),s.destroy()})}T(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(s,r,d){const h=s.get(r);if(!h)return!1;s.delete(r),d==="re-registered"&&console.log(`[gateway] workspace '${r}' re-registered \u2014 retiring the children of its previous registration first.`);try{h()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${r}' (${d}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),n(retirePreviousWorkspace,"retirePreviousWorkspace"),exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=512,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=64,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=4096,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=3e5;function parseBudget(s,r){if(s===void 0||s.trim()==="")return r;const d=Number(s);return!Number.isFinite(d)||d<0?r:Math.floor(d)}T(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(s){return parseBudget(s,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(s=process.env){return{maxSessions:parseBudget(s.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(s.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(s.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(s,r){const{NGHTTP2_ENHANCE_YOUR_CALM:d,NGHTTP2_REFUSED_STREAM:h}=require("node:http2").constants,a=new Map;let y=0;const _=n(o=>{o.idle&&(clearTimeout(o.idle),o.idle=void 0)},"disarm"),S=n((o,c)=>{_(c),!(s.idleSessionMs<=0||c.open>0)&&(c.idle=setTimeout(()=>{o.destroyed||o.closed||(r("session-reclaimed",{idleMs:s.idleSessionMs,sessions:a.size}),a.delete(o),o.close())},s.idleSessionMs),c.idle.unref?.())},"arm"),p=n(()=>{for(const[o,c]of a)if(!(c.open>0||o.destroyed))return _(c),a.delete(o),r("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:s.maxSessions}),o.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(o){if(s.maxSessions>0&&a.size>=s.maxSessions&&!p()){r("session-refused",{sessions:a.size,maxSessions:s.maxSessions,streams:y});try{o.goaway(d)}catch{}try{o.close()}catch{}return!1}const c={open:0};return a.set(o,c),S(o,c),o.once("close",()=>{_(c),a.delete(o)}),!0},admitStream(o,c){const i=a.get(o);if(!i)return!0;if(s.maxStreams>0&&y>=s.maxStreams){r("stream-refused",{streams:y,maxStreams:s.maxStreams,sessions:a.size});try{c.close(h)}catch{}return!1}return y+=1,i.open+=1,_(i),c.once("close",()=>{y=Math.max(0,y-1),i.open=Math.max(0,i.open-1),i.open===0&&a.has(o)&&S(o,i)}),!0},dispose(){for(const o of a.values())_(o);a.clear()}}}T(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(s,r){const d=s.https!==!1,h=s.ports?.length?s.ports:[443],a=s.scanTtlMs??1500,y=s.startupTimeoutMs??12e4,_=s.sweepMs??1e4,S=s.trafficFlushMs??5e3;let p=!1;const o=process.env.LENSMCP_EVENT_FILE??path.join(r.root,".lensmcp","events.jsonl"),c=(0,scope_1.readLensScope)(r.root),i=c.key,A=c.sockNs!==i?c.sockNs:void 0,Y=r.projectsConfigurations?.projects??{},B=(0,discovery_1.projectSourceRoots)(r.root,Y,r.projectDependencies),{routes:H,services:v,instance:X}=(0,discovery_1.discoverRoutes)(r.root,Y,void 0,i,B,A,c.instance);if(H.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const $=new route_registry_1.RouteRegistry;$.register({wsKey:i,...A?{sockNs:A}:{},root:r.root,routes:H,services:v,...X?{instance:X}:{}});const R=[];let J=n(()=>{},"refreshCert");const V=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,$),J()},"rebuildRoutes");V();const{serviceKeys:re,keyToProject:te,keyToWorkspace:oe}=(0,service_keys_1.loadServiceKeys)(r.root,v,i),ne=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...s.trustProxyHops!==void 0?{trustProxyHops:s.trustProxyHops}:{},...s.clientIpHeader?{clientIpHeader:s.clientIpHeader}:{},...s.clientIpTrustedProxies?{clientIpTrustedProxies:s.clientIpTrustedProxies}:{}}),m={root:r.root,wsKey:i,...A?{sockNs:A}:{},eventFile:o,routes:R,registry:$,rebuildRoutes:V,services:v,serviceKeys:re,keyToProject:te,keyToWorkspace:oe,clientIp:ne,bodyStallMs:s.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:y,sweepMs:_,trafficFlushMs:S,https:d,projectRoots:Object.fromEntries(Object.entries(r.projectsConfigurations?.projects??{}).map(([e,t])=>[e,path.resolve(r.root,t.root)])),projectSourceRoots:B,...r.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(r.projectGraphStore)}:{},...s.sharedCompiler!==void 0?{sharedCompiler:s.sharedCompiler}:{},stopped:n(()=>p,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(v,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!r.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,s.sharedCompiler),label:i||path.basename(r.root),lens:{key:i,dashboard:s.dashboard!==!1,mcp:s.mcp!==!1&&H.some(e=>!!e.lens)}}));const w=(0,observability_1.createObservability)(o,{trafficFlushMs:S}),ae=createClientErrorLogGate(),x=h2BudgetFromEnv(),ie=createClientErrorLogGate(3e4),G=createH2Governor(x,(e,t)=>{ie(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(t)}). These are the FRONT DOOR's ceilings, not an upstream's \u2014 raise/disable with LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS / LENSMCP_MAX_CONNECTIONS (0 = unbounded).`),w.emit(e==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${e}`,`gateway-h2-budget:${e}`,{kind:"h2-aggregate-budget",event:e,project:"gateway",...t})}),N=(0,lifecycle_1.createServiceLayer)(m,w),k=(0,lens_children_1.createLensChildren)(m,w,s),j=new Map;let q;if(s.middleware){const e=require(path.resolve(r.root,s.middleware));q=e.default??e}const W=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:i,...q?{owner:q}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()});let O;try{O=(0,control_1.startControlServer)({rt:m,killService:n((e,t)=>N.killSpawned(e,t),"killService"),onRegister:n(async e=>{retirePreviousWorkspace(j,e.wsKey,"re-registered"),W.set(e.wsKey,e.root,e.middleware);const t=await k.hostWorkspaceDashboard(e),f=await k.hostWorkspaceApps(e);j.set(e.wsKey,()=>{try{t?.()}finally{f()}})},"onRegister"),onUnregister:n(e=>{retirePreviousWorkspace(j,e.wsKey,"unregistered"),W.drop(e.wsKey),N.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const b=(0,proxy_1.createProxy)(m,w,N),z=buildEdgeVerifierPool(()=>m.routes),Q=(0,auth_1.createEdgeAuth)(m,w,z),ce=n(e=>{const t=e.route?.wsKey,f=W.for(t);if(f)try{const l=t?m.registry.get(t):void 0;f(e.req,{...t?{wsKey:t}:{},...l?{root:l.root}:{},env:l?.instance?.env??{}})}catch(l){w.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:l.message}),e.route&&w.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${l.message}`)}},"builtinMiddleware"),Z=s.hooks??{},ee=(0,hooks_1.createHooks)({...Z,afterAuth:[ce,...Z.afterAuth??[]]}),se=(0,handler_1.createHandler)({rt:m,obs:w,auth:Q,proxy:b,hooks:ee}),le=(0,upgrade_1.createUpgrade)({rt:m,auth:Q,proxy:b,hooks:ee,obs:w}),ue=w.startEdgeFlusher(),de=N.startSweeper();await k.bootSingletonsAndApps();for(const e of v)e.decl.eager&&N.ensureUp(e);let P,D,L;if(d){const e=require("../../../basic-ssl"),t=(0,service_keys_1.gatewayCacheDir)(r.root);L=n(()=>e.getCertificateSync(t,"gateway",m.routes.map(f=>f.host).filter(f=>!!f)),"mintCert"),P=L(),D=e.caCertPath()}const F=[],I=[],U=s.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of h){const t=P?require("node:http2").createSecureServer({key:P,cert:P,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:s.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:s.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(l,E)=>se(l,E)):http.createServer(se);t.on("upgrade",le),t.on("error",l=>console.error(`[gateway] port ${e}: ${l.code}`)),t.on("connection",guardInboundSocket),t.on("secureConnection",guardInboundSocket),t.on("tlsClientError",(l,E)=>E.destroy()),t.on("clientError",(l,E)=>{const u=l,g=classifyClientError(u.code,u.message);if(g.report&&ae(g.cause)){const C=E.remoteAddress??"unknown",M=g.status===null?"dropped":String(g.status);console.warn(`[gateway] inbound H1 clientError from ${C}: ${g.cause} (${u.code??u.message}${u.bytesParsed!==void 0?`, bytesParsed=${u.bytesParsed}`:""}) \u2192 ${M}. This status is the GATEWAY's, not the upstream service's.`),w.emit("warning",`inbound clientError: ${g.cause} \u2192 ${M}`,`gateway-client-error:${g.cause}`,{kind:"gateway-client-error",cause:g.cause,code:u.code??null,status:g.status,peer:C,bytesParsed:u.bytesParsed??null})}g.status!==null&&E.writable&&E.end(`HTTP/1.1 ${g.status} ${CLIENT_ERROR_REASON[g.status]}\r
1
+ "use strict";var Se=Object.defineProperty;var T=(s,r)=>Se(s,"name",{value:r,configurable:!0});var _e=Object.defineProperty,n=T((s,r)=>_e(s,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=exports.BENIGN_SOCKET_ERRNOS=void 0,exports.buildEdgeVerifierPool=buildEdgeVerifierPool,exports.classifyClientError=classifyClientError,exports.createClientErrorLogGate=createClientErrorLogGate,exports.retirePreviousWorkspace=retirePreviousWorkspace,exports.resolveMaxConnections=resolveMaxConnections,exports.h2BudgetFromEnv=h2BudgetFromEnv,exports.createH2Governor=createH2Governor,exports.startGateway=startGateway;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),scope_1=require("./scope"),route_registry_1=require("./route-registry"),control_1=require("./control"),workspace_middleware_1=require("./workspace-middleware"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),observability_1=require("./observability"),lifecycle_1=require("./lifecycle"),process_names_1=require("./process-names"),project_graph_store_1=require("./project-graph-store"),serve_launch_1=require("./serve-launch"),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),lens_children_1=require("./lens-children"),proxy_1=require("./proxy"),auth_1=require("./auth"),hooks_1=require("./hooks"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),jwks_verify_1=require("../jwks-verify"),types_1=require("./types");function insecureLocalFetch(s,r){return new Promise((d,h)=>{const a=(s.startsWith("https:")?require("node:https"):require("node:http")).request(s,{method:"GET",headers:r?.headers,rejectUnauthorized:!1},y=>{const _=[];y.on("data",S=>_.push(S)),y.on("end",()=>{const S=y.statusCode??0,p=Buffer.concat(_).toString("utf8");d({ok:S>=200&&S<300,status:S,json:n(async()=>JSON.parse(p),"json")})})});a.on("error",h),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(s){const r=process.env.LENSMCP_GW_JWKS_URL,d=process.env.LENSMCP_GW_JWT_ISS,h=new Map;let a;const y=n((_,S)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${_}${S}`);const p=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:_,...d?{issuer:d}:{},fetchImpl:insecureLocalFetch});return p.refresh().catch(o=>console.warn("[gateway] edge JWKS warm failed (will retry):",o.message)),p},"make");return{for(_){if(r)return a??=y(r,"");const S=_.split(":")[0].toLowerCase();let p;for(const c of s()){const i=c.host;if(typeof i!="string"||!/^auth\./.test(i)||i.startsWith("internal."))continue;const A=i.slice(5);(S===A||S.endsWith(`.${A}`))&&(!p||A.length>p.length)&&(p=A)}if(!p)return;let o=h.get(p);return o||(o=y(`https://auth.${p}/jwks.json`,` (auto-derived for apex ${p})`),h.set(p,o)),o},stop(){a?.stop();for(const _ of h.values())_.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(s,r){return s&&exports.BENIGN_SOCKET_ERRNOS.has(s)?{status:null,report:!1,cause:"client-abort"}:s==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:s==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:s==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:s?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:s??r??"unknown"}}T(classifyClientError,"classifyClientError"),n(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(s=1e4){const r=new Map;return d=>{const h=Date.now(),a=r.get(d)??0;return h-a<s?!1:(r.set(d,h),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(s){s.on("error",r=>{exports.BENIGN_SOCKET_ERRNOS.has(r.code??"")||console.warn(`[gateway] inbound socket error (${r.code??r.message}) \u2014 dropping this connection only`),s.destroy()})}T(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(s,r,d){const h=s.get(r);if(!h)return!1;s.delete(r),d==="re-registered"&&console.log(`[gateway] workspace '${r}' re-registered \u2014 retiring the children of its previous registration first.`);try{h()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${r}' (${d}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),n(retirePreviousWorkspace,"retirePreviousWorkspace"),exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=512,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=64,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=4096,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=3e5;function parseBudget(s,r){if(s===void 0||s.trim()==="")return r;const d=Number(s);return!Number.isFinite(d)||d<0?r:Math.floor(d)}T(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(s){return parseBudget(s,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(s=process.env){return{maxSessions:parseBudget(s.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(s.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(s.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(s,r){const{NGHTTP2_ENHANCE_YOUR_CALM:d,NGHTTP2_REFUSED_STREAM:h}=require("node:http2").constants,a=new Map;let y=0;const _=n(o=>{o.idle&&(clearTimeout(o.idle),o.idle=void 0)},"disarm"),S=n((o,c)=>{_(c),!(s.idleSessionMs<=0||c.open>0)&&(c.idle=setTimeout(()=>{o.destroyed||o.closed||(r("session-reclaimed",{idleMs:s.idleSessionMs,sessions:a.size}),a.delete(o),o.close())},s.idleSessionMs),c.idle.unref?.())},"arm"),p=n(()=>{for(const[o,c]of a)if(!(c.open>0||o.destroyed))return _(c),a.delete(o),r("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:s.maxSessions}),o.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(o){if(s.maxSessions>0&&a.size>=s.maxSessions&&!p()){r("session-refused",{sessions:a.size,maxSessions:s.maxSessions,streams:y});try{o.goaway(d)}catch{}try{o.close()}catch{}return!1}const c={open:0};return a.set(o,c),S(o,c),o.once("close",()=>{_(c),a.delete(o)}),!0},admitStream(o,c){const i=a.get(o);if(!i)return!0;if(s.maxStreams>0&&y>=s.maxStreams){r("stream-refused",{streams:y,maxStreams:s.maxStreams,sessions:a.size});try{c.close(h)}catch{}return!1}return y+=1,i.open+=1,_(i),c.once("close",()=>{y=Math.max(0,y-1),i.open=Math.max(0,i.open-1),i.open===0&&a.has(o)&&S(o,i)}),!0},dispose(){for(const o of a.values())_(o);a.clear()}}}T(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(s,r){const d=s.https!==!1,h=s.ports?.length?s.ports:[443],a=s.scanTtlMs??1500,y=s.startupTimeoutMs??12e4,_=s.sweepMs??1e4,S=s.trafficFlushMs??5e3;let p=!1;const o=process.env.LENSMCP_EVENT_FILE??path.join(r.root,".lensmcp","events.jsonl"),c=(0,scope_1.readLensScope)(r.root),i=c.key,A=c.sockNs!==i?c.sockNs:void 0,Y=r.projectsConfigurations?.projects??{},B=(0,discovery_1.projectSourceRoots)(r.root,Y,r.projectDependencies),{routes:H,services:v,instance:X}=(0,discovery_1.discoverRoutes)(r.root,Y,void 0,i,B,A,c.instance);if(H.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const $=new route_registry_1.RouteRegistry;$.register({wsKey:i,...A?{sockNs:A}:{},root:r.root,routes:H,services:v,...X?{instance:X}:{}});const R=[];let J=n(()=>{},"refreshCert");const V=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,$),J()},"rebuildRoutes");V();const{serviceKeys:te,keyToProject:oe,keyToWorkspace:ne}=(0,service_keys_1.loadServiceKeys)(r.root,v,i),ae=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...s.trustProxyHops!==void 0?{trustProxyHops:s.trustProxyHops}:{},...s.clientIpHeader?{clientIpHeader:s.clientIpHeader}:{},...s.clientIpTrustedProxies?{clientIpTrustedProxies:s.clientIpTrustedProxies}:{}}),m={root:r.root,wsKey:i,...A?{sockNs:A}:{},eventFile:o,routes:R,registry:$,rebuildRoutes:V,services:v,serviceKeys:te,keyToProject:oe,keyToWorkspace:ne,clientIp:ae,bodyStallMs:s.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:y,sweepMs:_,trafficFlushMs:S,https:d,projectRoots:Object.fromEntries(Object.entries(r.projectsConfigurations?.projects??{}).map(([e,t])=>[e,path.resolve(r.root,t.root)])),projectSourceRoots:B,...r.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(r.projectGraphStore)}:{},...s.sharedCompiler!==void 0?{sharedCompiler:s.sharedCompiler}:{},stopped:n(()=>p,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(v,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!r.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,s.sharedCompiler),label:i||path.basename(r.root),lens:{key:i,dashboard:s.dashboard!==!1,mcp:s.mcp!==!1&&H.some(e=>!!e.lens)}}));const w=(0,observability_1.createObservability)(o,{trafficFlushMs:S}),ie=createClientErrorLogGate(),x=h2BudgetFromEnv(),ce=createClientErrorLogGate(3e4),G=createH2Governor(x,(e,t)=>{ce(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(t)}). These are the FRONT DOOR's ceilings, not an upstream's \u2014 raise/disable with LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS / LENSMCP_MAX_CONNECTIONS (0 = unbounded).`),w.emit(e==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${e}`,`gateway-h2-budget:${e}`,{kind:"h2-aggregate-budget",event:e,project:"gateway",...t})}),N=(0,lifecycle_1.createServiceLayer)(m,w),k=(0,lens_children_1.createLensChildren)(m,w,s),j=new Map;let q;if(s.middleware){const e=require(path.resolve(r.root,s.middleware));q=e.default??e}const W=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:i,...q?{owner:q}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()});let O;try{O=(0,control_1.startControlServer)({rt:m,killService:n((e,t)=>N.killSpawned(e,t),"killService"),onRegister:n(async e=>{retirePreviousWorkspace(j,e.wsKey,"re-registered"),W.set(e.wsKey,e.root,e.middleware);const t=await k.hostWorkspaceDashboard(e),f=await k.hostWorkspaceApps(e);j.set(e.wsKey,()=>{try{t?.()}finally{f()}})},"onRegister"),onUnregister:n(e=>{retirePreviousWorkspace(j,e.wsKey,"unregistered"),W.drop(e.wsKey),N.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const b=(0,proxy_1.createProxy)(m,w,N),z=buildEdgeVerifierPool(()=>m.routes),Q=(0,auth_1.createEdgeAuth)(m,w,z),le=n(e=>{const t=e.route?.wsKey,f=W.for(t);if(f)try{const l=t?m.registry.get(t):void 0;f(e.req,{...t?{wsKey:t}:{},...l?{root:l.root}:{},env:l?.instance?.env??{}})}catch(l){w.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:l.message}),e.route&&w.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${l.message}`)}},"builtinMiddleware"),Z=s.hooks??{},ee=(0,hooks_1.createHooks)({...Z,afterAuth:[le,...Z.afterAuth??[]]}),se=(0,handler_1.createHandler)({rt:m,obs:w,auth:Q,proxy:b,hooks:ee}),re=(0,upgrade_1.createUpgrade)({rt:m,auth:Q,proxy:b,hooks:ee,obs:w}),ue=w.startEdgeFlusher(),de=N.startSweeper();await k.bootSingletonsAndApps();for(const e of v)e.decl.eager&&N.ensureUp(e);let P,D,L;if(d){const e=require("../../../basic-ssl"),t=(0,service_keys_1.gatewayCacheDir)(r.root);L=n(()=>e.getCertificateSync(t,"gateway",m.routes.map(f=>f.host).filter(f=>!!f)),"mintCert"),P=L(),D=e.caCertPath()}const F=[],I=[],U=s.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of h){const t=P?require("node:http2").createSecureServer({key:P,cert:P,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:s.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:s.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(l,E)=>se(l,E)):http.createServer(se);t.on("upgrade",re),t.on("error",l=>console.error(`[gateway] port ${e}: ${l.code}`)),t.on("connection",guardInboundSocket),t.on("secureConnection",guardInboundSocket),t.on("tlsClientError",(l,E)=>E.destroy()),t.on("clientError",(l,E)=>{const u=l,g=classifyClientError(u.code,u.message);if(g.report&&ie(g.cause)){const C=E.remoteAddress??"unknown",M=g.status===null?"dropped":String(g.status);console.warn(`[gateway] inbound H1 clientError from ${C}: ${g.cause} (${u.code??u.message}${u.bytesParsed!==void 0?`, bytesParsed=${u.bytesParsed}`:""}) \u2192 ${M}. This status is the GATEWAY's, not the upstream service's.`),w.emit("warning",`inbound clientError: ${g.cause} \u2192 ${M}`,`gateway-client-error:${g.cause}`,{kind:"gateway-client-error",cause:g.cause,code:u.code??null,status:g.status,peer:C,bytesParsed:u.bytesParsed??null})}g.status!==null&&E.writable&&E.end(`HTTP/1.1 ${g.status} ${CLIENT_ERROR_REASON[g.status]}\r
2
2
  \r
3
- `),E.destroy()}),t.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,t.headersTimeout=s.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,U>0&&(t.maxConnections=U);const f=s.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(f>0&&(t.connectionsCheckingInterval=f),P!=null){const l=t,{NGHTTP2_CANCEL:E}=require("node:http2").constants;l.on("session",u=>{if(!G.admitSession(u)){u.on("error",()=>{});return}let g=0,C=0;u.on("stream",M=>{G.admitStream(u,M)&&M.once("close",()=>{u.destroyed?C+=1:M.rstCode===E&&(g+=1)})}),u.once("close",()=>{C!==0&&(console.warn(`[gateway] h2 session closed with ${C} stream(s) in flight after ${g} client reset(s) \u2014 every in-flight fetch on that connection failed at once (ERR_HTTP2_PROTOCOL_ERROR in the browser). Reset budget: LENSMCP_H2_RESET_BURST/LENSMCP_H2_RESET_RATE (issues/closed/foodguard-14).`),w.emit("warning",`h2 session lost with ${C} stream(s) in flight`,"gateway-h2-session-lost",{kind:"h2-session-lost",project:"gateway",inflight:C,clientResets:g}))}),u.on("error",M=>{const pe=M.code??M.message;console.warn(`[gateway] h2 session error (${pe}) \u2014 dropping this session only`),u.destroyed||u.destroy()})}),l.on("sessionError",u=>{console.warn(`[gateway] h2 sessionError: ${u.code??u.message}`)})}await new Promise(l=>{t.listen(e,()=>{const E=t.address()?.port??e;I.push(E),console.log(`[gateway] Listening on ${P?"https":"http"}://localhost:${E}`),l()})}),F.push(t)}L&&(J=n(()=>{const e=L();for(const t of F)t.setSecureContext?.({key:e,cert:e})},"refreshCert")),console.log("[gateway] Routes:");for(const e of R){const t=e.pool?`pods@${e.pool.dir}`:e.target;console.log(`[gateway] ${e.host??"(default)"} \u2192 ${t}${e.prependPrefix?` (+${e.prependPrefix})`:""}${e.internal?" [internal: no middleware]":""} [${e.project}]`)}D&&console.log(`[gateway] CA: ${D} \u2014 one-time setup via the trust executor.`);const K=n(e=>e>0?String(e):"unbounded","unbounded");return console.log(`[gateway] front-door budget: \u2264${K(U)} connections, \u2264${K(x.maxSessions)} h2 sessions (each \u2264512 MB session memory, \u2264512 streams), \u2264${K(x.maxStreams)} open streams in total, idle h2 session reclaimed after ${x.idleSessionMs>0?`${Math.round(x.idleSessionMs/1e3)}s`:"never"}. Override: LENSMCP_MAX_CONNECTIONS / LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS (0 = unbounded).`),w.emit("info",`gateway up: ${R.length} routes on ${I.join("/")}`,"cluster-gateway",{kind:"gateway-up",project:"gateway",ports:I,routes:R.map(e=>({host:e.host??"(default)",project:e.project,mode:e.pool?"pods":"tcp",internal:!!e.internal,prependPrefix:e.prependPrefix}))}),O&&process.env.LENSMCP_GUEST_REPLAY!=="0"&&O.replay().catch(e=>console.warn("[gateway] guest replay failed:",e.message)),{ports:I,routes:R,services:v,stop:n(async()=>{if(!p){p=!0,clearInterval(ue),clearInterval(de),w.emit("info","gateway down","cluster-gateway",{kind:"gateway-down",project:"gateway"}),z.stop(),O?.close(),(0,workspace_registry_1.unregisterWorkspace)(i);for(const e of v)N.killSpawned(e,"gateway shutdown");await N.stopBuildHost(),await k.reapAll(),await Promise.all(F.map(e=>new Promise(t=>{let f=!1;const l=n(()=>{f||(f=!0,t())},"done"),E=e;e.close(()=>l());try{E.closeIdleConnections?.()}catch{}setTimeout(()=>{try{E.closeAllConnections?.()}catch{}l()},types_1.GATEWAY_STOP_GRACE_MS).unref?.()}))),b.closeProxy(),b.destroyAgents(),G.dispose(),(0,workspace_log_1.closeWorkspaceLogs)()}},"stop"),...m.lensSupervisor?{lensSupervisor:m.lensSupervisor}:{},...m.graphStores?{graphStores:m.graphStores}:{}}}T(startGateway,"startGateway"),n(startGateway,"startGateway");
3
+ `),E.destroy()}),t.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,t.headersTimeout=s.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,U>0&&(t.maxConnections=U);const f=s.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(f>0&&(t.connectionsCheckingInterval=f),P!=null){const l=t,{NGHTTP2_CANCEL:E}=require("node:http2").constants;l.on("session",u=>{if(!G.admitSession(u)){u.on("error",()=>{});return}let g=0,C=0;u.on("stream",M=>{G.admitStream(u,M)&&M.once("close",()=>{u.destroyed?C+=1:M.rstCode===E&&(g+=1)})}),u.once("close",()=>{C!==0&&(console.warn(`[gateway] h2 session closed with ${C} stream(s) in flight after ${g} client reset(s) \u2014 every in-flight fetch on that connection failed at once (ERR_HTTP2_PROTOCOL_ERROR in the browser). Reset budget: LENSMCP_H2_RESET_BURST/LENSMCP_H2_RESET_RATE (issues/closed/foodguard-14).`),w.emit("warning",`h2 session lost with ${C} stream(s) in flight`,"gateway-h2-session-lost",{kind:"h2-session-lost",project:"gateway",inflight:C,clientResets:g}))}),u.on("error",M=>{const pe=M.code??M.message;console.warn(`[gateway] h2 session error (${pe}) \u2014 dropping this session only`),u.destroyed||u.destroy()})}),l.on("sessionError",u=>{console.warn(`[gateway] h2 sessionError: ${u.code??u.message}`)})}await new Promise(l=>{t.listen(e,()=>{const E=t.address()?.port??e;I.push(E),console.log(`[gateway] Listening on ${P?"https":"http"}://localhost:${E}`),l()})}),F.push(t)}L&&(J=n(()=>{const e=L();for(const t of F)t.setSecureContext?.({key:e,cert:e})},"refreshCert")),console.log("[gateway] Routes:");for(const e of R){const t=e.pool?`pods@${e.pool.dir}`:e.target;console.log(`[gateway] ${e.host??"(default)"} \u2192 ${t}${e.prependPrefix?` (+${e.prependPrefix})`:""}${e.internal?" [internal: no middleware]":""} [${e.project}]`)}D&&console.log(`[gateway] CA: ${D} \u2014 one-time setup via the trust executor.`);const K=n(e=>e>0?String(e):"unbounded","unbounded");return console.log(`[gateway] front-door budget: \u2264${K(U)} connections, \u2264${K(x.maxSessions)} h2 sessions (each \u2264512 MB session memory, \u2264512 streams), \u2264${K(x.maxStreams)} open streams in total, idle h2 session reclaimed after ${x.idleSessionMs>0?`${Math.round(x.idleSessionMs/1e3)}s`:"never"}. Override: LENSMCP_MAX_CONNECTIONS / LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS (0 = unbounded).`),w.emit("info",`gateway up: ${R.length} routes on ${I.join("/")}`,"cluster-gateway",{kind:"gateway-up",project:"gateway",ports:I,routes:R.map(e=>({host:e.host??"(default)",project:e.project,mode:e.pool?"pods":"tcp",internal:!!e.internal,prependPrefix:e.prependPrefix}))}),O&&process.env.LENSMCP_GUEST_REPLAY!=="0"&&O.replay().catch(e=>console.warn("[gateway] guest replay failed:",e.message)),{ports:I,routes:R,services:v,stop:n(async()=>{if(!p){p=!0,clearInterval(ue),clearInterval(de),w.emit("info","gateway down","cluster-gateway",{kind:"gateway-down",project:"gateway"}),z.stop(),O?.close(),(0,workspace_registry_1.unregisterWorkspace)(i);for(const e of v)N.killSpawned(e,"gateway shutdown");await N.stopBuildHost(),await k.reapAll(),re.closeTunnels(),await Promise.all(F.map(e=>new Promise(t=>{let f=!1;const l=n(()=>{f||(f=!0,t())},"done"),E=e;e.close(()=>l());try{E.closeIdleConnections?.()}catch{}setTimeout(()=>{try{E.closeAllConnections?.()}catch{}l()},types_1.GATEWAY_STOP_GRACE_MS).unref?.()}))),b.closeProxy(),b.destroyAgents(),G.dispose(),(0,workspace_log_1.closeWorkspaceLogs)()}},"stop"),...m.lensSupervisor?{lensSupervisor:m.lensSupervisor}:{},...m.graphStores?{graphStores:m.graphStores}:{}}}T(startGateway,"startGateway"),n(startGateway,"startGateway");
@@ -341,6 +341,15 @@ export declare const POD_SLOW_FIRST_BYTE_MS: number;
341
341
  /** Per-probe timeout for `GET /__lensmcp/alive`. A live raw server answers a loopback
342
342
  * unix-socket request in microseconds, so anything slower is indistinguishable from dead. */
343
343
  export declare const POD_ALIVE_PROBE_TIMEOUT_MS: number;
344
+ /**
345
+ * How long a pod that ACCEPTS but stays silent on its liveness route is re-probed before the first-byte guard calls it
346
+ * wedged (issues/closed/foodguard-54). One missed 1 s probe used to be the verdict — and a LIVE pod in a long synchronous
347
+ * stretch (a large parse, a GC pause on a 650 MB heap) misses one exactly like a dead event loop does: the kernel accepts
348
+ * the connection either way. FoodGuard's engine was evicted mid-request at 30 s that way, its socket unlinked, the
349
+ * service recycled under the handler. A pod that answers within the window was busy; one silent for all of it is wedged.
350
+ * `0` restores the single-probe verdict.
351
+ */
352
+ export declare const POD_WEDGE_CONFIRM_MS: number;
344
353
  /** Internal liveness route served by the devserver child itself (`main.devserver.ts`). */
345
354
  export declare const POD_ALIVE_PATH = "/__lensmcp/alive";
346
355
  /**
@@ -1 +1 @@
1
- "use strict";var O=Object.defineProperty;var L=(_,E)=>O(_,"name",{value:E,configurable:!0});var e=Object.defineProperty,R=L((_,E)=>e(_,"name",{value:E,configurable:!0}),"R");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_RESET_RATE=exports.GATEWAY_H2_RESET_BURST=exports.BODY_STALL_MS=exports.GATEWAY_CONN_CHECK_MS=exports.GATEWAY_MAX_CONNECTIONS=exports.GATEWAY_STOP_GRACE_MS=exports.GATEWAY_HEADERS_TIMEOUT_MS=exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=exports.LENS_REQUEST_RECOVERY_WAIT_MS=exports.LENS_REPLACEMENT_DRAIN_MS=exports.LENS_REPLACEMENT_READY_MS=exports.LENS_RESTART_MODE=exports.LENS_RECYCLE_KILL_GRACE_MS=exports.LENS_PARKED_PORT_REPROBE_MS=exports.MAX_PORT_CONFLICT_RETRIES=exports.CHILD_START_FAIL_MS=exports.PORT_FREE_POLL_MS=exports.PORT_FREE_WAIT_MS=exports.LENS_PROBE_BOOT_GRACE_MS=exports.LENS_PROBE_COOLDOWN_MS=exports.LENS_PROBE_TIMEOUT_MS=exports.LENS_PROBE_FAILS=exports.LENS_PROBE_GRACE_MS=exports.MAX_COLD_STARTS=exports.POD_MAX_CONCURRENT_ROLLS=exports.POD_ROLL_READY_MS=exports.POD_RECYCLE_MODE=exports.POD_WATCHER_SILENT=exports.POD_WATCHER_UNVERIFIED=exports.LENS_WATCH_SCOPE_ENABLED=exports.LENS_WATCHER_UNVERIFIED=exports.LENS_WATCHER_PROBE_ENABLED=exports.LENS_PROBE_ENABLED=exports.POD_STALE_RECYCLE_COOLDOWN_MS=exports.POD_STALE_SCAN_MS=exports.POD_RECYCLE_SETTLE_MS=exports.POD_STALE_GRACE_MS=exports.POD_MAX_AGE_MS=exports.POD_RECYCLE_ENABLED=exports.WEDGE_RECYCLE_GRACE_MS=exports.POD_ALIVE_PATH=exports.POD_ALIVE_PROBE_TIMEOUT_MS=exports.POD_SLOW_FIRST_BYTE_MS=exports.POD_RESPONSE_TIMEOUT_MS=exports.UPSTREAM_HEAL_STEP_MS=exports.UPSTREAM_HEAL_WINDOW_MS=exports.SERVICE_RESTART_WINDOW_MS=exports.SERVICE_MAX_RESTARTS=exports.SERVICE_ERROR_THROTTLE_MS=void 0;const tslib_1=require("tslib"),os=tslib_1.__importStar(require("node:os"));function envInt(_,E,M=!1){const S=Number(process.env[_]);return!Number.isFinite(S)||S<0?E:S===0?M?0:E:S}L(envInt,"envInt"),R(envInt,"envInt"),exports.SERVICE_ERROR_THROTTLE_MS=5e3,exports.SERVICE_MAX_RESTARTS=5,exports.SERVICE_RESTART_WINDOW_MS=6e4,exports.UPSTREAM_HEAL_WINDOW_MS=8e3,exports.UPSTREAM_HEAL_STEP_MS=300,exports.POD_RESPONSE_TIMEOUT_MS=(()=>{const _=Number(process.env.LENSMCP_POD_RESPONSE_TIMEOUT_MS);return Number.isFinite(_)&&_>0?_:3e4})(),exports.POD_SLOW_FIRST_BYTE_MS=envInt("LENSMCP_POD_SLOW_FIRST_BYTE_MS",10*6e4,!0),exports.POD_ALIVE_PROBE_TIMEOUT_MS=envInt("LENSMCP_POD_ALIVE_PROBE_TIMEOUT_MS",1e3),exports.POD_ALIVE_PATH="/__lensmcp/alive",exports.WEDGE_RECYCLE_GRACE_MS=(()=>{const _=Number(process.env.LENSMCP_WEDGE_RECYCLE_GRACE_MS);return Number.isFinite(_)&&_>0?_:15e3})(),exports.POD_RECYCLE_ENABLED=process.env.LENSMCP_POD_RECYCLE!=="0",exports.POD_MAX_AGE_MS=envInt("LENSMCP_POD_MAX_AGE_MS",240*6e4,!0),exports.POD_STALE_GRACE_MS=envInt("LENSMCP_POD_STALE_GRACE_MS",1e4),exports.POD_RECYCLE_SETTLE_MS=envInt("LENSMCP_POD_RECYCLE_SETTLE_MS",8e3),exports.POD_STALE_SCAN_MS=envInt("LENSMCP_POD_STALE_SCAN_MS",3e4),exports.POD_STALE_RECYCLE_COOLDOWN_MS=envInt("LENSMCP_POD_STALE_RECYCLE_COOLDOWN_MS",6e4),exports.LENS_PROBE_ENABLED=process.env.LENSMCP_LENS_PROBE!=="0",exports.LENS_WATCHER_PROBE_ENABLED=process.env.LENSMCP_LENS_WATCHER_PROBE!=="0",exports.LENS_WATCHER_UNVERIFIED=process.env.LENSMCP_LENS_WATCHER_UNVERIFIED==="keep"?"keep":"recycle",exports.LENS_WATCH_SCOPE_ENABLED=process.env.LENSMCP_LENS_WATCH_SCOPE!=="0",exports.POD_WATCHER_UNVERIFIED=process.env.LENSMCP_POD_WATCHER_UNVERIFIED==="keep"?"keep":"recycle",exports.POD_WATCHER_SILENT=process.env.LENSMCP_POD_WATCHER_SILENT==="recycle"?"recycle":"keep",exports.POD_RECYCLE_MODE=process.env.LENSMCP_POD_RECYCLE_MODE==="in-place"?"in-place":"rolling",exports.POD_ROLL_READY_MS=envInt("LENSMCP_POD_ROLL_READY_MS",18e4),exports.POD_MAX_CONCURRENT_ROLLS=envInt("LENSMCP_POD_MAX_CONCURRENT_ROLLS",2,!0),exports.MAX_COLD_STARTS=envInt("LENSMCP_MAX_COLD_STARTS",Math.max(2,Math.floor(os.availableParallelism()/2)),!0),exports.LENS_PROBE_GRACE_MS=envInt("LENSMCP_LENS_PROBE_GRACE_MS",45e3),exports.LENS_PROBE_FAILS=envInt("LENSMCP_LENS_PROBE_FAILS",2),exports.LENS_PROBE_TIMEOUT_MS=envInt("LENSMCP_LENS_PROBE_TIMEOUT_MS",1e3),exports.LENS_PROBE_COOLDOWN_MS=envInt("LENSMCP_LENS_PROBE_COOLDOWN_MS",3e4),exports.LENS_PROBE_BOOT_GRACE_MS=envInt("LENSMCP_LENS_PROBE_BOOT_GRACE_MS",24e4),exports.PORT_FREE_WAIT_MS=envInt("LENSMCP_PORT_FREE_WAIT_MS",1e4),exports.PORT_FREE_POLL_MS=envInt("LENSMCP_PORT_FREE_POLL_MS",250),exports.CHILD_START_FAIL_MS=envInt("LENSMCP_CHILD_START_FAIL_MS",1e4),exports.MAX_PORT_CONFLICT_RETRIES=envInt("LENSMCP_MAX_PORT_CONFLICT_RETRIES",5),exports.LENS_PARKED_PORT_REPROBE_MS=envInt("LENSMCP_PARKED_PORT_REPROBE_MS",3e5),exports.LENS_RECYCLE_KILL_GRACE_MS=envInt("LENSMCP_RECYCLE_KILL_GRACE_MS",5e3),exports.LENS_RESTART_MODE=process.env.LENSMCP_LENS_RESTART_MODE==="in-place"?"in-place":"rolling",exports.LENS_REPLACEMENT_READY_MS=envInt("LENSMCP_LENS_REPLACEMENT_READY_MS",3e4),exports.LENS_REPLACEMENT_DRAIN_MS=envInt("LENSMCP_LENS_REPLACEMENT_DRAIN_MS",2e3,!0),exports.LENS_REQUEST_RECOVERY_WAIT_MS=envInt("LENSMCP_LENS_REQUEST_RECOVERY_WAIT_MS",15e3),exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=envInt("LENSMCP_KEEPALIVE_TIMEOUT_MS",76e3),exports.GATEWAY_HEADERS_TIMEOUT_MS=envInt("LENSMCP_HEADERS_TIMEOUT_MS",6e4),exports.GATEWAY_STOP_GRACE_MS=envInt("LENSMCP_STOP_GRACE_MS",3e3),exports.GATEWAY_MAX_CONNECTIONS=envInt("LENSMCP_MAX_CONNECTIONS",0,!0),exports.GATEWAY_CONN_CHECK_MS=envInt("LENSMCP_CONN_CHECK_MS",0,!0),exports.BODY_STALL_MS=envInt("LENSMCP_BODY_STALL_MS",6e4,!0),exports.GATEWAY_H2_RESET_BURST=envInt("LENSMCP_H2_RESET_BURST",1e5),exports.GATEWAY_H2_RESET_RATE=envInt("LENSMCP_H2_RESET_RATE",1e4);
1
+ "use strict";var O=Object.defineProperty;var e=(_,E)=>O(_,"name",{value:E,configurable:!0});var M=Object.defineProperty,R=e((_,E)=>M(_,"name",{value:E,configurable:!0}),"R");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_RESET_RATE=exports.GATEWAY_H2_RESET_BURST=exports.BODY_STALL_MS=exports.GATEWAY_CONN_CHECK_MS=exports.GATEWAY_MAX_CONNECTIONS=exports.GATEWAY_STOP_GRACE_MS=exports.GATEWAY_HEADERS_TIMEOUT_MS=exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=exports.LENS_REQUEST_RECOVERY_WAIT_MS=exports.LENS_REPLACEMENT_DRAIN_MS=exports.LENS_REPLACEMENT_READY_MS=exports.LENS_RESTART_MODE=exports.LENS_RECYCLE_KILL_GRACE_MS=exports.LENS_PARKED_PORT_REPROBE_MS=exports.MAX_PORT_CONFLICT_RETRIES=exports.CHILD_START_FAIL_MS=exports.PORT_FREE_POLL_MS=exports.PORT_FREE_WAIT_MS=exports.LENS_PROBE_BOOT_GRACE_MS=exports.LENS_PROBE_COOLDOWN_MS=exports.LENS_PROBE_TIMEOUT_MS=exports.LENS_PROBE_FAILS=exports.LENS_PROBE_GRACE_MS=exports.MAX_COLD_STARTS=exports.POD_MAX_CONCURRENT_ROLLS=exports.POD_ROLL_READY_MS=exports.POD_RECYCLE_MODE=exports.POD_WATCHER_SILENT=exports.POD_WATCHER_UNVERIFIED=exports.LENS_WATCH_SCOPE_ENABLED=exports.LENS_WATCHER_UNVERIFIED=exports.LENS_WATCHER_PROBE_ENABLED=exports.LENS_PROBE_ENABLED=exports.POD_STALE_RECYCLE_COOLDOWN_MS=exports.POD_STALE_SCAN_MS=exports.POD_RECYCLE_SETTLE_MS=exports.POD_STALE_GRACE_MS=exports.POD_MAX_AGE_MS=exports.POD_RECYCLE_ENABLED=exports.WEDGE_RECYCLE_GRACE_MS=exports.POD_ALIVE_PATH=exports.POD_WEDGE_CONFIRM_MS=exports.POD_ALIVE_PROBE_TIMEOUT_MS=exports.POD_SLOW_FIRST_BYTE_MS=exports.POD_RESPONSE_TIMEOUT_MS=exports.UPSTREAM_HEAL_STEP_MS=exports.UPSTREAM_HEAL_WINDOW_MS=exports.SERVICE_RESTART_WINDOW_MS=exports.SERVICE_MAX_RESTARTS=exports.SERVICE_ERROR_THROTTLE_MS=void 0;const tslib_1=require("tslib"),os=tslib_1.__importStar(require("node:os"));function envInt(_,E,L=!1){const S=Number(process.env[_]);return!Number.isFinite(S)||S<0?E:S===0?L?0:E:S}e(envInt,"envInt"),R(envInt,"envInt"),exports.SERVICE_ERROR_THROTTLE_MS=5e3,exports.SERVICE_MAX_RESTARTS=5,exports.SERVICE_RESTART_WINDOW_MS=6e4,exports.UPSTREAM_HEAL_WINDOW_MS=8e3,exports.UPSTREAM_HEAL_STEP_MS=300,exports.POD_RESPONSE_TIMEOUT_MS=(()=>{const _=Number(process.env.LENSMCP_POD_RESPONSE_TIMEOUT_MS);return Number.isFinite(_)&&_>0?_:3e4})(),exports.POD_SLOW_FIRST_BYTE_MS=envInt("LENSMCP_POD_SLOW_FIRST_BYTE_MS",10*6e4,!0),exports.POD_ALIVE_PROBE_TIMEOUT_MS=envInt("LENSMCP_POD_ALIVE_PROBE_TIMEOUT_MS",1e3),exports.POD_WEDGE_CONFIRM_MS=envInt("LENSMCP_POD_WEDGE_CONFIRM_MS",1e4,!0),exports.POD_ALIVE_PATH="/__lensmcp/alive",exports.WEDGE_RECYCLE_GRACE_MS=(()=>{const _=Number(process.env.LENSMCP_WEDGE_RECYCLE_GRACE_MS);return Number.isFinite(_)&&_>0?_:15e3})(),exports.POD_RECYCLE_ENABLED=process.env.LENSMCP_POD_RECYCLE!=="0",exports.POD_MAX_AGE_MS=envInt("LENSMCP_POD_MAX_AGE_MS",240*6e4,!0),exports.POD_STALE_GRACE_MS=envInt("LENSMCP_POD_STALE_GRACE_MS",1e4),exports.POD_RECYCLE_SETTLE_MS=envInt("LENSMCP_POD_RECYCLE_SETTLE_MS",8e3),exports.POD_STALE_SCAN_MS=envInt("LENSMCP_POD_STALE_SCAN_MS",3e4),exports.POD_STALE_RECYCLE_COOLDOWN_MS=envInt("LENSMCP_POD_STALE_RECYCLE_COOLDOWN_MS",6e4),exports.LENS_PROBE_ENABLED=process.env.LENSMCP_LENS_PROBE!=="0",exports.LENS_WATCHER_PROBE_ENABLED=process.env.LENSMCP_LENS_WATCHER_PROBE!=="0",exports.LENS_WATCHER_UNVERIFIED=process.env.LENSMCP_LENS_WATCHER_UNVERIFIED==="keep"?"keep":"recycle",exports.LENS_WATCH_SCOPE_ENABLED=process.env.LENSMCP_LENS_WATCH_SCOPE!=="0",exports.POD_WATCHER_UNVERIFIED=process.env.LENSMCP_POD_WATCHER_UNVERIFIED==="keep"?"keep":"recycle",exports.POD_WATCHER_SILENT=process.env.LENSMCP_POD_WATCHER_SILENT==="recycle"?"recycle":"keep",exports.POD_RECYCLE_MODE=process.env.LENSMCP_POD_RECYCLE_MODE==="in-place"?"in-place":"rolling",exports.POD_ROLL_READY_MS=envInt("LENSMCP_POD_ROLL_READY_MS",18e4),exports.POD_MAX_CONCURRENT_ROLLS=envInt("LENSMCP_POD_MAX_CONCURRENT_ROLLS",2,!0),exports.MAX_COLD_STARTS=envInt("LENSMCP_MAX_COLD_STARTS",Math.max(2,Math.floor(os.availableParallelism()/2)),!0),exports.LENS_PROBE_GRACE_MS=envInt("LENSMCP_LENS_PROBE_GRACE_MS",45e3),exports.LENS_PROBE_FAILS=envInt("LENSMCP_LENS_PROBE_FAILS",2),exports.LENS_PROBE_TIMEOUT_MS=envInt("LENSMCP_LENS_PROBE_TIMEOUT_MS",1e3),exports.LENS_PROBE_COOLDOWN_MS=envInt("LENSMCP_LENS_PROBE_COOLDOWN_MS",3e4),exports.LENS_PROBE_BOOT_GRACE_MS=envInt("LENSMCP_LENS_PROBE_BOOT_GRACE_MS",24e4),exports.PORT_FREE_WAIT_MS=envInt("LENSMCP_PORT_FREE_WAIT_MS",1e4),exports.PORT_FREE_POLL_MS=envInt("LENSMCP_PORT_FREE_POLL_MS",250),exports.CHILD_START_FAIL_MS=envInt("LENSMCP_CHILD_START_FAIL_MS",1e4),exports.MAX_PORT_CONFLICT_RETRIES=envInt("LENSMCP_MAX_PORT_CONFLICT_RETRIES",5),exports.LENS_PARKED_PORT_REPROBE_MS=envInt("LENSMCP_PARKED_PORT_REPROBE_MS",3e5),exports.LENS_RECYCLE_KILL_GRACE_MS=envInt("LENSMCP_RECYCLE_KILL_GRACE_MS",5e3),exports.LENS_RESTART_MODE=process.env.LENSMCP_LENS_RESTART_MODE==="in-place"?"in-place":"rolling",exports.LENS_REPLACEMENT_READY_MS=envInt("LENSMCP_LENS_REPLACEMENT_READY_MS",3e4),exports.LENS_REPLACEMENT_DRAIN_MS=envInt("LENSMCP_LENS_REPLACEMENT_DRAIN_MS",2e3,!0),exports.LENS_REQUEST_RECOVERY_WAIT_MS=envInt("LENSMCP_LENS_REQUEST_RECOVERY_WAIT_MS",15e3),exports.GATEWAY_KEEPALIVE_TIMEOUT_MS=envInt("LENSMCP_KEEPALIVE_TIMEOUT_MS",76e3),exports.GATEWAY_HEADERS_TIMEOUT_MS=envInt("LENSMCP_HEADERS_TIMEOUT_MS",6e4),exports.GATEWAY_STOP_GRACE_MS=envInt("LENSMCP_STOP_GRACE_MS",3e3),exports.GATEWAY_MAX_CONNECTIONS=envInt("LENSMCP_MAX_CONNECTIONS",0,!0),exports.GATEWAY_CONN_CHECK_MS=envInt("LENSMCP_CONN_CHECK_MS",0,!0),exports.BODY_STALL_MS=envInt("LENSMCP_BODY_STALL_MS",6e4,!0),exports.GATEWAY_H2_RESET_BURST=envInt("LENSMCP_H2_RESET_BURST",1e5),exports.GATEWAY_H2_RESET_RATE=envInt("LENSMCP_H2_RESET_RATE",1e4);
@@ -15,5 +15,12 @@ export interface UpgradeDeps {
15
15
  }
16
16
  /** One report per frontend per this window: a reload storm must not become a log storm. */
17
17
  export declare const LENS_HMR_REFUSAL_REPORT_MS = 10000;
18
- export type UpgradeListener = (req: http.IncomingMessage, socket: stream.Duplex, head: Buffer) => void;
18
+ export type UpgradeListener = ((req: http.IncomingMessage, socket: stream.Duplex, head: Buffer) => void) & {
19
+ /**
20
+ * Destroy every live tunnel — both its sockets — for the gateway's `stop()`. An upgraded socket leaves `http`'s
21
+ * connection tracking, so `closeAllConnections` never reaches it, and a tunnel whose upstream ignores a half-close
22
+ * stays open for good (issues/closed/test-infra-09). Returns how many sockets were destroyed.
23
+ */
24
+ closeTunnels(): number;
25
+ };
19
26
  export declare function createUpgrade(deps: UpgradeDeps): UpgradeListener;
@@ -1 +1 @@
1
- "use strict";var x=Object.defineProperty;var h=(c,s)=>x(c,"name",{value:s,configurable:!0});var b=Object.defineProperty,f=h((c,s)=>b(c,"name",{value:s,configurable:!0}),"f");Object.defineProperty(exports,"__esModule",{value:!0}),exports.LENS_HMR_REFUSAL_REPORT_MS=void 0,exports.createUpgrade=createUpgrade;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),manifest_1=require("../manifest"),discovery_1=require("./discovery"),edge_1=require("./edge"),hmr_reload_1=require("./hmr-reload"),service_keys_1=require("./service-keys");exports.LENS_HMR_REFUSAL_REPORT_MS=1e4;function accountUpgrade(c,s){const i=c.svc;if(!i||s.destroyed)return;i.inflight+=1,i.lastUsed=Date.now();let l=!1;s.once("close",()=>{l||(l=!0,i.inflight-=1,i.lastUsed=Date.now())})}h(accountUpgrade,"accountUpgrade"),f(accountUpgrade,"accountUpgrade");function createUpgrade(c){const{rt:s,auth:i,proxy:l,hooks:p,obs:R}=c,w=new WeakMap,v=new Map,_=f((e,r)=>{const n=Date.now();n-(v.get(e.frontendKey)??0)<exports.LENS_HMR_REFUSAL_REPORT_MS||(v.set(e.frontendKey,n),console.warn(`[gateway] lens HMR socket refused: ${e.frontendKey} at ${e.target} answered ${r} for a tab on ${e.host} \u2014 that tab loads but gets no hot updates; the lens runtime reloads it once if it had a socket before.`),R?.emit("warning",`lens HMR socket refused: ${e.frontendKey} (${r})`,`cluster-lens-hmr-refused:${e.frontendKey}`,{kind:"lens-hmr-refused",frontendKey:e.frontendKey,host:e.host,target:e.target,answer:r},void 0,e.busFile))},"reportRefusal");l.proxy.on?.("proxyReqWs",(e,r)=>{const n=w.get(r);if(!n)return;const a=e;a.once("upgrade",()=>{n.answered=!0}),a.once("response",y=>{n.answered=!0,_(n,`HTTP ${y.statusCode??"?"}`)})});const k=f(async(e,r,n)=>{(0,edge_1.stripOwnedHeaders)(e);const a=(0,edge_1.canonicalize)(e.url??"/");if(a===void 0){r.destroy();return}e.url=a.path+a.query;const y=(e.headers.host||"").split(":")[0],t=(0,manifest_1.matchRoute)(s.routes,y,a.policy+a.query);if(!t){r.destroy();return}(0,edge_1.resolveClientIp)(s,e);const d=p.upgradeContext(e,r,void 0);if(d.route=t,t.internal){const o=i.internal(e,t,void 0);if(!o.ok){r.destroy();return}d.caller=o.caller}else{const o=i.public(e,t,void 0,a.policy);if(!o.ok){r.destroy();return}if(d.claims=o.claims,p.has("afterAuth")&&(await p.run("afterAuth",d),d.responded()))return}const m=t.pool?s.serviceKeys[(0,service_keys_1.serviceKeyId)(t.svc?.wsKey,t.project)]:void 0;if(m&&(e.headers["x-internal-token"]=m),t.prependPrefix&&!e.url?.startsWith(t.prependPrefix)&&(e.url=t.prependPrefix+(e.url??"/")),(0,edge_1.stampForwardingHeaders)(s,e),p.has("onUpgrade")&&(await p.run("onUpgrade",d),d.responded()))return;const g=t.pool?(()=>{const o=(0,discovery_1.pickSock)(t.pool,s.scanTtlMs);return o?{socketPath:o}:void 0})():t.target;if(!g){r.destroy();return}if(t.lens&&String(e.headers["sec-websocket-protocol"]??"").split(",").map(o=>o.trim()).includes("vite-hmr")){const o=t.lens.workspaceKey?`${t.lens.workspaceKey}:${t.lens.project}`:t.lens.project;(0,hmr_reload_1.registerHmrClient)(s,o,r);const u=!t.lens.workspaceRoot||path.resolve(t.lens.workspaceRoot)===path.resolve(s.root??"");w.set(e,{frontendKey:o,host:y,target:String(g),answered:!1,...u?{}:{busFile:path.join(t.lens.workspaceRoot,".lensmcp","events.jsonl")}})}accountUpgrade(t,r),l.proxy.ws(e,r,n,{target:g,changeOrigin:!t.pool,agent:l.agentForTarget(g)},o=>{const u=w.get(e);u&&!u.answered&&_(u,o?.code??o?.message??"an error"),r.destroy()})},"run");return(e,r,n)=>{r.on("error",()=>r.destroy()),k(e,r,n).catch(()=>r.destroy())}}h(createUpgrade,"createUpgrade"),f(createUpgrade,"createUpgrade");
1
+ "use strict";var K=Object.defineProperty;var w=(c,s)=>K(c,"name",{value:s,configurable:!0});var M=Object.defineProperty,u=w((c,s)=>M(c,"name",{value:s,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.LENS_HMR_REFUSAL_REPORT_MS=void 0,exports.createUpgrade=createUpgrade;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),manifest_1=require("../manifest"),discovery_1=require("./discovery"),edge_1=require("./edge"),hmr_reload_1=require("./hmr-reload"),service_keys_1=require("./service-keys");exports.LENS_HMR_REFUSAL_REPORT_MS=1e4;function accountUpgrade(c,s){const i=c.svc;if(!i||s.destroyed)return;i.inflight+=1,i.lastUsed=Date.now();let d=!1;s.once("close",()=>{d||(d=!0,i.inflight-=1,i.lastUsed=Date.now())})}w(accountUpgrade,"accountUpgrade"),u(accountUpgrade,"accountUpgrade");function createUpgrade(c){const{rt:s,auth:i,proxy:d,hooks:p,obs:b}=c,y=new Set,_=u(e=>{e.destroyed||(y.add(e),e.once("close",()=>y.delete(e)))},"track");d.proxy.on?.("open",_);const v=new WeakMap,k=new Map,m=u((e,r)=>{const n=Date.now();n-(k.get(e.frontendKey)??0)<exports.LENS_HMR_REFUSAL_REPORT_MS||(k.set(e.frontendKey,n),console.warn(`[gateway] lens HMR socket refused: ${e.frontendKey} at ${e.target} answered ${r} for a tab on ${e.host} \u2014 that tab loads but gets no hot updates; the lens runtime reloads it once if it had a socket before.`),b?.emit("warning",`lens HMR socket refused: ${e.frontendKey} (${r})`,`cluster-lens-hmr-refused:${e.frontendKey}`,{kind:"lens-hmr-refused",frontendKey:e.frontendKey,host:e.host,target:e.target,answer:r},void 0,e.busFile))},"reportRefusal");d.proxy.on?.("proxyReqWs",(e,r)=>{const n=v.get(r);if(!n)return;const a=e;a.once("upgrade",()=>{n.answered=!0}),a.once("response",g=>{n.answered=!0,m(n,`HTTP ${g.statusCode??"?"}`)})});const S=u(async(e,r,n)=>{(0,edge_1.stripOwnedHeaders)(e);const a=(0,edge_1.canonicalize)(e.url??"/");if(a===void 0){r.destroy();return}e.url=a.path+a.query;const g=(e.headers.host||"").split(":")[0],t=(0,manifest_1.matchRoute)(s.routes,g,a.policy+a.query);if(!t){r.destroy();return}(0,edge_1.resolveClientIp)(s,e);const l=p.upgradeContext(e,r,void 0);if(l.route=t,t.internal){const o=i.internal(e,t,void 0);if(!o.ok){r.destroy();return}l.caller=o.caller}else{const o=i.public(e,t,void 0,a.policy);if(!o.ok){r.destroy();return}if(l.claims=o.claims,p.has("afterAuth")&&(await p.run("afterAuth",l),l.responded()))return}const x=t.pool?s.serviceKeys[(0,service_keys_1.serviceKeyId)(t.svc?.wsKey,t.project)]:void 0;if(x&&(e.headers["x-internal-token"]=x),t.prependPrefix&&!e.url?.startsWith(t.prependPrefix)&&(e.url=t.prependPrefix+(e.url??"/")),(0,edge_1.stampForwardingHeaders)(s,e),p.has("onUpgrade")&&(await p.run("onUpgrade",l),l.responded()))return;const h=t.pool?(()=>{const o=(0,discovery_1.pickSock)(t.pool,s.scanTtlMs);return o?{socketPath:o}:void 0})():t.target;if(!h){r.destroy();return}if(t.lens&&String(e.headers["sec-websocket-protocol"]??"").split(",").map(o=>o.trim()).includes("vite-hmr")){const o=t.lens.workspaceKey?`${t.lens.workspaceKey}:${t.lens.project}`:t.lens.project;(0,hmr_reload_1.registerHmrClient)(s,o,r);const f=!t.lens.workspaceRoot||path.resolve(t.lens.workspaceRoot)===path.resolve(s.root??"");v.set(e,{frontendKey:o,host:g,target:String(h),answered:!1,...f?{}:{busFile:path.join(t.lens.workspaceRoot,".lensmcp","events.jsonl")}})}accountUpgrade(t,r),d.proxy.ws(e,r,n,{target:h,changeOrigin:!t.pool,agent:d.agentForTarget(h)},o=>{const f=v.get(e);f&&!f.answered&&m(f,o?.code??o?.message??"an error"),r.destroy()})},"run"),R=u(((e,r,n)=>{r.on("error",()=>r.destroy()),_(r),S(e,r,n).catch(()=>r.destroy())}),"listener");return R.closeTunnels=()=>{const e=[...y];for(const r of e)r.destroy();return y.clear(),e.length},R}w(createUpgrade,"createUpgrade"),u(createUpgrade,"createUpgrade");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.24.2",
3
+ "version": "1.24.3",
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.24.2",
61
- "@lensmcp/node-instrumentation": "1.24.2",
62
- "@lensmcp/nx-plugin": "1.24.2",
60
+ "@lensmcp/core": "1.24.3",
61
+ "@lensmcp/node-instrumentation": "1.24.3",
62
+ "@lensmcp/nx-plugin": "1.24.3",
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",