@lensmcp/cluster 1.25.38 → 1.25.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config-files.d.ts +20 -0
- package/config-files.js +1 -1
- package/devserver-notifier.d.ts +1 -1
- package/executors/gateway/prod-runtime/handler.js +1 -1
- package/executors/gateway/prod-runtime/observability.d.ts +4 -2
- package/executors/gateway/prod-runtime/observability.js +1 -1
- package/executors/gateway/runtime/awake.d.ts +42 -0
- package/executors/gateway/runtime/awake.js +1 -0
- package/executors/gateway/runtime/control.d.ts +11 -1
- package/executors/gateway/runtime/control.js +2 -2
- package/executors/gateway/runtime/discovery.js +1 -1
- package/executors/gateway/runtime/instance.d.ts +19 -3
- package/executors/gateway/runtime/instance.js +3 -2
- package/executors/gateway/runtime/lifecycle.d.ts +4 -1
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/observability.d.ts +8 -3
- package/executors/gateway/runtime/observability.js +3 -3
- package/executors/gateway/runtime/proxy.js +1 -1
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/shared-build.d.ts +1 -1
- package/executors/gateway/runtime/types.d.ts +46 -2
- package/executors/gateway/runtime/warm-start.d.ts +18 -0
- package/executors/gateway/runtime/warm-start.js +1 -1
- package/executors/serve/serve.impl.d.ts +1 -1
- package/executors/serve/serve.impl.js +1 -1
- package/package.json +4 -4
- package/perf/heavy-barrel-import.d.ts +1 -1
- package/shared-build/attach.d.ts +9 -0
- package/shared-build/attach.js +3 -3
- package/shared-build/protocol.d.ts +1 -1
- package/tsgo-check-plugin.d.ts +15 -0
- package/tsgo-check-plugin.js +4 -4
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var it=Object.defineProperty;var m=(t,c)=>it(t,"name",{value:c,configurable:!0});var at=Object.defineProperty,a=m((t,c)=>at(t,"name",{value:c,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isLoopbackHostname=isLoopbackHostname,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isRunnerOf=isRunnerOf,exports.isTestSourcePath=isTestSourcePath,exports.sourceScanPass=sourceScanPass,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.podChangeClock=podChangeClock,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),pod_sock_1=require("../../../pod-sock"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),own_log_1=require("./own-log"),inspector_secret_1=require("../../../inspector-secret"),pod_output_1=require("./pod-output"),serve_launch_1=require("./serve-launch"),shared_build_1=require("./shared-build"),warm_start_1=require("./warm-start"),protocol_1=require("../../../shared-build/protocol"),types_1=require("./types"),exit_reason_1=require("../../../exit-reason"),handoff_retire_1=require("../../../handoff-retire"),process_name_1=require("../../../process-name"),orphan_reaper_1=require("./orphan-reaper");function eastWestEnv(t,c){const l={},p=t.serviceKeys[(0,service_keys_1.serviceKeyId)(c.wsKey,c.project)];if(p&&(l.LENSMCP_SERVICE_KEY=p),t.https){const f=require("../../../basic-ssl").caCertPath();fs.existsSync(f)&&(l.NODE_EXTRA_CA_CERTS=f)}return l}m(eastWestEnv,"eastWestEnv"),a(eastWestEnv,"eastWestEnv");const sleep=a(t=>new Promise(c=>setTimeout(c,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let c=2166136261;for(let l=0;l<t.length;l+=1)c^=t.charCodeAt(l),c=Math.imul(c,16777619);return c>>>0}m(fnv1a,"fnv1a"),a(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:c}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(c.split("\0").filter(Boolean))}catch{return new Set}}m(gitIgnoredPaths,"gitIgnoredPaths"),a(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,c=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const l=path.join(t.pool.dir,"parent.sock");return c(l)?{socketPath:l}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}m(scaleTarget,"scaleTarget"),a(scaleTarget,"scaleTarget");function isLoopbackHostname(t){const c=t.toLowerCase().replace(/^\[(.*)\]$/,"$1").replace(/\.$/,"");return c==="localhost"||c.endsWith(".localhost")?!0:net.isIPv4(c)?c.split(".")[0]==="127":net.isIPv6(c)&&new URL(`http://[${c}]/`).hostname==="[::1]"}m(isLoopbackHostname,"isLoopbackHostname"),a(isLoopbackHostname,"isLoopbackHostname");function postScale(t,c,l){let p;if("socketPath"in t)p=http.request({socketPath:t.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},f=>f.resume());else{const f=t.url.startsWith("https"),T=f?require("node:https"):http,_={method:"POST"};f&&isLoopbackHostname(new URL(t.url).hostname)&&(_.rejectUnauthorized=!1),l&&(_.lookup=l),p=T.request(`${t.url}/webpack/scale`,_,A=>A.resume())}return p.on("error",()=>{}),p.end(JSON.stringify({pods:c})),p}m(postScale,"postScale"),a(postScale,"postScale");function isOwnRunnerCommand(t,c){return(0,serve_launch_1.isServeRunnerCommand)(t)?(0,serve_launch_1.isServeRunnerCommandOf)(t,c):t.includes("/node_modules/nx/")?t.includes(`${c.replace(/\/+$/,"")}/node_modules/nx/`):!0}m(isOwnRunnerCommand,"isOwnRunnerCommand"),a(isOwnRunnerCommand,"isOwnRunnerCommand");function isRunnerOf(t,c,l){const p=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return!new RegExp(`(^|\\s)run ${p}(:|\\s|$)`).test(t)||!(0,serve_launch_1.isServeRunnerCommand)(t)&&!/\bnx(\.js)?\b/.test(t)?!1:isOwnRunnerCommand(t,l)}m(isRunnerOf,"isRunnerOf"),a(isRunnerOf,"isRunnerOf");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const c=t.split("/"),l=c[c.length-1]??"";if(TEST_FILE_RE.test(l))return!0;for(let p=0;p<c.length-1;p+=1)if(TEST_DIR_SEGMENTS.has(c[p]))return!0;return!1}m(isTestSourcePath,"isTestSourcePath"),a(isTestSourcePath,"isTestSourcePath");function sourceScanPass(t,c,l){const p=l?.ignoreTests??!0;let f;const T=new Map,_=`tree:${path.resolve(t)}`,A=a(y=>path.relative(t,y).split(path.sep).join("/"),"rel"),C=a(async(y,$)=>{const E=await(f??=gitIgnoredPaths(t)),b=[],F=new Set;let D=!1;const H=a(async(te,S)=>{if(b.length>=SCAN_FILE_CAP){D=!0;return}let O;try{O=await fs.promises.readdir(te,{withFileTypes:!0})}catch{return}S&&F.add(A(te));for(const z of O){if(b.length>=SCAN_FILE_CAP){D=!0;return}const W=z.name,re=path.join(te,W);if(z.isDirectory()){if(!S||W.startsWith(".")||SRC_EXCLUDE.has(W)||c?.has(W)||W.endsWith("-devserver")||E.has(`${A(re)}/`))continue;await H(re,!0)}else if(z.isFile()){const oe=W.lastIndexOf(".");if(oe<=0||!SRC_EXT.has(W.slice(oe)))continue;const J=A(re);if(E.has(J)||p&&isTestSourcePath(J))continue;b.push(J)}}},"walk");return await H(y,$),{paths:b,entered:F,truncated:D}},"walkDir"),L=a((y,$)=>{let E=T.get(y);return E||(E=$(),T.set(y,E)),E},"memo"),ie=a(async()=>{const y=T.get(_);if(!y)return;const $=await y;return $.truncated?void 0:$},"wideWalk"),B=new Set,le=a(()=>L(`files:${path.resolve(t)}`,async()=>{const y=await ie();return y?{paths:y.paths.filter($=>!$.includes("/")),entered:B,truncated:!1}:C(t,!1)}),"rootFiles"),de=a(y=>L(`tree:${path.resolve(y)}`,async()=>{const $=await ie(),E=A(y);if($?.entered.has(E)){const b=`${E}/`;return{paths:E===""?$.paths:$.paths.filter(F=>F.startsWith(b)),entered:B,truncated:!1}}return C(y,!0)}),"subtree"),X=a(y=>{const $=new Set;let E=0,b=y.some(D=>D.truncated);const F=a(D=>$.has(D)?!0:$.size>=SCAN_FILE_CAP?(b=!0,!1):($.add(D),E^=fnv1a(D),!0),"add");for(const D of y)if(!D.paths.every(F))break;return{signature:`${$.size}:${(E>>>0).toString(36)}`,paths:$,truncated:b}},"compose");return{async snapshot(y){if(!y||y.length===0)return X([await L(_,()=>C(t,!0))]);const $=[await le()];for(const E of y)$.push(await de(E));return X($)}}}m(sourceScanPass,"sourceScanPass"),a(sourceScanPass,"sourceScanPass");async function sourceSetSnapshot(t,c,l,p){return sourceScanPass(t,l,p).snapshot(c)}m(sourceSetSnapshot,"sourceSetSnapshot"),a(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,c,l,p){return(await sourceSetSnapshot(t,c,l,p)).signature}m(sourceSetSignature,"sourceSetSignature"),a(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,c,l,p){if(t.paths.size===c.paths.size){let A=!0;for(const C of t.paths)if(!c.paths.has(C)){A=!1;break}if(A)return null}const f=[],T=[];let _=t.truncated||c.truncated;for(const A of c.paths)t.paths.has(A)||(f.length+T.length<SOURCE_DIFF_PATH_CAP?f.push(A):_=!0);for(const A of t.paths)c.paths.has(A)||(f.length+T.length<SOURCE_DIFF_PATH_CAP?T.push(A):_=!0);return{changedAt:l,added:f,removed:T,truncated:_,...p!==void 0?{windowMs:p}:{}}}m(diffSourceSets,"diffSourceSets"),a(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,c,l,p){const f=c&&c>0?t-c:l;return Math.max(f,l)+p}m(sourceChangeWindowMs,"sourceChangeWindowMs"),a(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,c,l,p,f){const T=a(C=>{const L=path.relative(t,C).split(path.sep).filter(Boolean);return L.length===0||L[0].startsWith("..")?"":L[0]},"bucketOf")(c);if(!T)return[];const _=new Set(l.filter(Boolean)),A=new Set([T]);for(const C of p)C.startsWith(".")||SRC_EXCLUDE.has(C)||f?.has(C)||C.endsWith("-devserver")||_.has(C)||A.add(C);return[...A].sort().map(C=>path.join(t,C))}m(sourceScopeDirs,"sourceScopeDirs"),a(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,c,l,p){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||c-t.lastUsed<p.settleMs||t.lastStaleRecycleAt!=null&&c-t.lastStaleRecycleAt<p.cooldownMs?null:l>0&&l-t.spawnAt>p.graceMs?"stale-source-set":p.maxAgeMs>0&&c-t.spawnAt>p.maxAgeMs?"max-age":null}m(podRecycleReason,"podRecycleReason"),a(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,c,l,p,f){return!(p-c<f.cooldownMs||l<=0||l-t<=f.graceMs||p-l<f.settleMs)}m(lensFrontendStale,"lensFrontendStale"),a(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,c,l,p=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&p.length>0){const f=c-l,T=new Set(t.recentEvents.filter(_=>_.at>=f).map(_=>path.resolve(_.path)));return p.every(_=>T.has(path.resolve(_)))?"heard":"missed"}return t.lastFsSetEventAt>=c-l?"heard":"missed"}m(lensWatcherVerdict,"lensWatcherVerdict"),a(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,c,l){if(!t)return"unknown";if(t.degraded)return"degraded";const p=c-l;return t.lastWatchRunAt>=p||t.lastBuildAt>=p?"heard":"silent"}m(podWatcherVerdict,"podWatcherVerdict"),a(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,c,l,p){const f=t.hasListened?p.graceMs:Math.max(p.graceMs,p.neverListenedGraceMs);return l-t.spawnedAt<f||l-c<p.cooldownMs||t.probeFails<Math.max(2,p.failureThreshold)?null:"wedged-no-listener"}m(lensChildWedged,"lensChildWedged"),a(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,c){return t.signal||t.code===null||t.code===0||t.ranMs>c.fastFailMs?!1:t.portHeld}m(isPortConflictExit,"isPortConflictExit"),a(isPortConflictExit,"isPortConflictExit");function podChangeClock(t,c){const l=t.sourceSetChangedFor?.get(c);if(l!==void 0){const p=t.sourceSetChangesFor?.get(c);return{at:l,...p?{change:p}:{},scope:"project"}}return t.sourceSetScoped?.has(c)?{at:0,scope:"none"}:{at:t.sourceSetChangedAt??0,...t.sourceSetChange?{change:t.sourceSetChange}:{},scope:"workspace"}}m(podChangeClock,"podChangeClock"),a(podChangeClock,"podChangeClock");const changeEvidence=a(t=>{if(!t)return"no path evidence";const c=[...t.added.map(l=>`+${l}`),...t.removed.map(l=>`-${l}`)];return c.length?`${c.slice(0,3).join(", ")}${c.length>3?` (+${c.length-3} more)`:""}`:"no path evidence"},"changeEvidence");function createServiceLayer(t,c,l={}){const p=l.spawnProcess??node_child_process_1.spawn,f=l.probeAlive??pod_probe_1.probePodAlive,T=l.killTree??((e,r="SIGTERM")=>{try{process.kill(-e.pid,r)}catch{try{e.kill(r)}catch{}}}),_=l.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),A=a((e,r="SIGTERM")=>{_.detach(e),T(e,r)},"killTree"),C=l.handoffDrainMs??(0,handoff_retire_1.handoffDrainBoundMs)(),L=l.treeAlive??(e=>{if(!e.pid||!(e instanceof node_child_process_1.ChildProcess))return!1;try{return process.kill(-e.pid,0),!0}catch(r){return r.code==="EPERM"}}),ie=l.retirePollMs??1e3,B=l.rollReadyMs??types_1.POD_ROLL_READY_MS,le=l.rollPollMs??750,de=l.maxConcurrentRolls??types_1.POD_MAX_CONCURRENT_ROLLS,X=de>0?de:Number.POSITIVE_INFINITY,y=l.maxConcurrentColdStarts??types_1.MAX_COLD_STARTS,$=y>0?y:Number.POSITIVE_INFINITY;let E=0;const b=[],F=a(()=>E<$?(E+=1,Promise.resolve()):new Promise(e=>{b.push(()=>{E+=1,e()})}),"acquireColdSlot"),D=a(()=>{E-=1,b.shift()?.()},"releaseColdSlot"),{emit:H}=c,te=a(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),S=a((e,r,o,s,i)=>H(r,o,s,{wsKey:e.wsKey,...i},void 0,te(e)),"emitSvc"),O=a(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),z=a((e,r)=>{let o;try{o=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const s=e.proc?.pid;let i=0;for(const n of o.split(`
|
|
2
|
-
`)){const
|
|
3
|
-
`)){const R=(0,pod_output_1.classifyPodLine)(g),{clean:M,broken:G,healthy:ne}=R,h=M;if(G){e.lastErrors.push(M),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const x=Date.now();(M!==e.lastErrorEmitSig||x-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=M,e.lastErrorEmitAt=x,S(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:M,fault:R.fault}))}ne&&(e.buildFailed=!1);const P=(e.faults??=new pod_output_1.PodFaults).observe(R);if(P){e.lastErrorEmitSig=void 0;const x=P.open.length===0;S(e,"info",`${x?"service recovered":`${P.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:x?"service-recovered":"service-fault-closed",project:e.project,line:M,closed:P.closed,open:P.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(h)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${s}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),S(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(V(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),z(e,s),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.())},250).unref?.())}},"sniff"),w=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,k=a(j=>{const v=(0,own_log_1.linePrefixer)(e.project);return g=>{if(t.stopped())return;const R=(0,inspector_secret_1.redactInspectorUrls)(g.toString());u(R);const M=v(R);j.write(M),w?.(M)}},"relay");return n.stdout?.on("data",k(process.stdout)),n.stderr?.on("data",k(process.stderr)),n.on("exit",(j,v)=>{if(e.proc!==n||(e.proc=void 0,e.state="down",t.stopped()))return;const g=(0,exit_reason_1.describeExit)(j,v);if(console.log(`[gateway] ${s} exited (${j??"no code"}${v?`, ${v}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${g?` \u2014 ${g}`:""}`),S(e,j===0?"info":"warning",`service down: ${e.project} (exit ${j})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:j,...v?{signal:v}:{}}),j===0||e.buildFailed)return;const R=Date.now();if(e.restarts=(e.restarts??[]).filter(G=>R-G<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){S(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(R);const M=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),S(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),Y(e))},M)}),n},"launchServiceProcess"),Y=a(e=>{if(t.stopped()||e.manualStopped)return;const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,o=Math.max(1,e.decl.minPods??1);l.reapForeign!==!1&&z(e,r),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${r} (pods=${o})\u2026`),S(e,"info",`service starting: ${e.project} (${o} pod${o>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:o}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=J(e,e.generation,"active")},"spawnService"),be=a(async e=>{const r=e.pool.socks[0];if(!r)return;const o=await(0,pod_probe_1.probePodAlive)(r,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);o&&(e.activeWork=o.activeWork,o.watcher&&(e.watcher=o.watcher))},"refreshActiveWork"),Le=a(e=>{try{const r=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(r)&&r>1?r:void 0}catch{return}},"readOwnerSidecar"),Ie=a(async e=>{const r=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!r)return[];const o=(0,node_crypto_1.createHash)("sha256").update(r).digest("hex").slice(0,8),s=[];for(const i of e.pool.socks){const n=await(0,pod_probe_1.probePodAlive)(i,void 0,r);n&&(!n.keyFingerprint||n.keyFingerprint!==o)&&s.push({sock:i,pid:n.pid})}return s},"staleAdoptedSocks"),Oe=a((e,r)=>{const o=r.map(s=>s.sock);for(const{sock:s,pid:i}of r){const n=chooseStalePodKillTarget(i,Le(s),process.pid);if(n!==void 0)try{process.kill(n,"SIGTERM")}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(s=>!o.includes(s)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),pe=a((e,r)=>{if(e.state==="up")return!1;e.state="up",e.wedgeRecycles=0;const o=e.pool.socks.length;return S(e,"info",`service up: ${e.project} (${o} pods, ${r})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:o,adopted:r==="adopted"}),!0},"markUp"),we=a(async e=>{if(e.manualStopped)return!1;if(O(e.pool)>0){const s=await Ie(e);if(e.manualStopped)return!1;if(s.length>0&&(console.error(`[gateway] ${e.project}: ${s.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),S(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:s.length}),e.proc&&V(e,"stale adoption (foreign east-west key)"),Oe(e,s)),e.pool.socks.length>0)return pe(e,"adopted"),!0}if(t.stopped())return!1;let r=!1;const o=a(()=>e.state!=="starting"&&!e.proc,"notStarted");if(o())if(e.coldWait)await e.coldWait;else{E>=$&&(console.log(`[gateway] cold start queued: ${e.project}${e.wsKey&&e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${E} starting, ${b.length} ahead (LENSMCP_MAX_COLD_STARTS=${$}).`),S(e,"info",`cold start queued: ${e.project}`,`cluster-service:${e.project}`,{kind:"cold-start-queued",project:e.project,inFlight:E,ahead:b.length}));const s=F();if(e.coldWait=s,await s,e.coldWait=void 0,r=!0,t.stopped()||e.manualStopped)return D(),!1;o()&&Y(e)}try{return await Ne(e)}finally{r&&D()}},"ensureUp"),Ne=a(async e=>{const r=Date.now()+t.startupTimeoutMs,o=Date.now()+12e3;for(;!t.stopped()&&!e.manualStopped&&Date.now()<r;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.manualStopped||e.buildFailed&&Date.now()>o)return!1;if(O(e.pool)>0){e.state="up",e.buildFailed=!1;const s=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${s?`, cold start ${s}ms`:""})`),S(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${s??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:s}),!0}if(e.state==="down")return!1}return!1},"waitForPods"),Ke=a(e=>{const r=e.decl.maxPods??e.decl.minPods??1,o=e.pool.socks.length,s=scaleTarget(e);!s||o===0||o>=r||e.inflight<=o||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(s,o+1,l.scaleLookup),console.log(`[gateway] autoscale ${e.project}: ${o} \u2192 ${o+1} pods (inflight ${e.inflight})`),S(e,"info",`autoscale: ${e.project} ${o} \u2192 ${o+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:o,to:o+1,inflight:e.inflight}))},"scaleUp"),ue=l.killGraceMs??types_1.TREE_KILL_GRACE_MS,N=new Map,Ce=a(e=>{const r=N.get(e);if(!r||(N.delete(e),clearTimeout(r.timer),!L(e)))return;const{svc:o,why:s}=r;console.log(`[gateway] ${o.project}: its tree (pid ${e.pid}) was still running ${Math.round(ue/1e3)}s after it was told to stop (${s}) \u2014 killing it (SIGKILL).`),S(o,"warning",`tree force-killed: ${o.project} (it ignored SIGTERM)`,`cluster-service:${o.project}`,{kind:"tree-force-killed",project:o.project,why:s,pid:e.pid,graceMs:ue}),T(e,"SIGKILL")},"settleDyingTree"),Se=a((e,r,o)=>{if(A(r,"SIGTERM"),ue<=0||N.has(r))return;const s=setTimeout(()=>Ce(r),ue);s.unref?.(),N.set(r,{svc:e,why:o,timer:s})},"stopTree"),Fe=a(async(e=3e3)=>{const r=Date.now()+e;for(;N.size>0;){for(const[o,s]of[...N])L(o)||(clearTimeout(s.timer),N.delete(o));if(N.size===0||Date.now()>=r)break;await sleep(100)}for(const o of[...N.keys()])Ce(o)},"finishDyingTrees"),V=a((e,r,o)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${r} \u2014 scaling to zero.`),o?.announce!==!1&&S(e,"info",`idle kill: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:r});const s=e.proc;try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}(0,discovery_1.stopRoutingTo)(e.pool),Se(e,s,r),e.proc=void 0,e.state="down",setTimeout(()=>{ge(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ge=a(e=>{try{for(const r of fs.readdirSync(e.pool.dir)){if(!r.endsWith(".sock"))continue;const o=path.join(e.pool.dir,r);try{const s=Number(fs.readFileSync(`${o}.owner`,"utf8").trim());if(Number.isFinite(s)&&s>0)try{process.kill(s,0);continue}catch{}}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let Q=0;const ve=[],We=a(()=>Q<X?(Q+=1,Promise.resolve()):new Promise(e=>{ve.push(()=>{Q+=1,e()})}),"acquireRollSlot"),Ge=a(()=>{Q-=1,ve.shift()?.()},"releaseRollSlot"),ye=a(async(e,r,o)=>{O(e.pool);for(const s of e.pool.socks){const i=await f(s,void 0,o);if(i&&r(i.podGeneration))return!0}return!1},"anyPodAnswers"),he=new Map,qe=a((e,r,o)=>{const s=C>0&&(0,handoff_retire_1.requestHandoffRetire)(e.pool.dir,o);if(A(r,"SIGTERM"),!s)return;const i=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,n=a(ne=>h=>{if(t.stopped())return;const P=(0,inspector_secret_1.redactInspectorUrls)(h.toString()).replace(/^(?=.)/gm,`[${e.project} g${o} retiring] `);ne.write(P),i?.(P)},"say"),d=n(process.stdout),u=n(process.stderr);r.stdout?.on("data",d),r.stderr?.on("data",u);let w,k=!1;const j=a(()=>{k=!0,clearTimeout(w),r.stdout?.removeListener("data",d),r.stderr?.removeListener("data",u),r.removeListener("close",j)},"releaseLogs");r.once("close",j);let v=!1;const g=a(()=>{v||(v=!0,clearTimeout(R),clearInterval(G),t.stopped()?j():k||(w=setTimeout(j,1e3),w.unref?.()),he.delete(r),(0,handoff_retire_1.clearHandoffRetire)(e.pool.dir,o),ge(e),e.pool.scannedAt=0)},"end"),R=setTimeout(()=>{console.error(`[gateway] pod handoff: ${e.project} g${o} was still running ${C}ms after it was retired \u2014 killing it.`),T(r,"SIGKILL"),g()},C);R.unref?.();const M=a(()=>{if(!v){if(t.stopped()){g();return}L(r)||(console.log(`[gateway] pod handoff: ${e.project} g${o} finished its in-flight work and exited.`),g())}},"look"),G=setInterval(M,ie);G.unref?.(),r.once("exit",M),he.set(r,()=>{g(),Se(e,r,"the gateway is stopping")})},"retireReplacedTree"),Ve=l.standbyGraceMs??3e4,I=new Map,Ue=a((e,r,o,s,i)=>{const n=i+B+Ve,d=setTimeout(()=>Me(r),Math.max(0,n-Date.now()));d.unref?.(),I.set(r,{svc:e,generation:o,reason:s,beganAt:i,deadlineAt:n,timer:d})},"holdStandby"),Be=a(e=>{const r=I.get(e);r&&(clearTimeout(r.timer),I.delete(e))},"releaseStandby"),Z=a((e,r)=>{const o=I.get(e);if(!o)return!1;clearTimeout(o.timer),I.delete(e);const{svc:s,generation:i}=o;try{e.stdout?.removeAllListeners("data"),e.stderr?.removeAllListeners("data")}catch{}return(0,discovery_1.stopRoutingTo)(s.pool,n=>(0,pod_sock_1.podSockGeneration)(n)===i),Se(s,e,`standby g${i}: ${r}`),setTimeout(()=>ge(s),1200).unref?.(),!0},"endStandby"),Te=a((e,r)=>{const o=r.oldServing?`old generation g${r.fromGen} kept serving`:`and the old generation g${r.fromGen} is NOT serving (${r.oldGone?"its tree is gone":"none of its pods answers"}) \u2014 the service is down until it recovers`;console.error(`[gateway] pod handoff failed: ${e.project} g${r.generation}: ${r.why} \u2014 ${o}.`),e.rollFailure=`${r.why} \u2014 ${o}`,S(e,r.oldServing?"warning":"error",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:r.reason,generation:r.generation,error:r.why,durationMs:Date.now()-r.beganAt,oldServing:r.oldServing})},"reportStandbyFailed"),Me=a(e=>{const r=I.get(e);if(!r)return;const{svc:o,generation:s,reason:i,beganAt:n}=r,d=`the standby outlived its roll (${Math.round((Date.now()-n)/1e3)}s; a roll waits ${Math.round(B/1e3)}s for it)`;Z(e,d);const u=o.proc,w=t.serviceKeys[(0,service_keys_1.serviceKeyId)(o.wsKey,o.project)];(async()=>{const k=!u||u.exitCode!==null||u.signalCode!==null,j=k?!1:await Promise.race([ye(o,g=>g!==s,w).catch(()=>!1),sleep(2e3).then(()=>{})]),v=!t.stopped()&&!k&&(j??O(o.pool)>0);Te(o,{reason:i,generation:s,fromGen:o.generation??s-1,why:d,beganAt:n,oldServing:v,oldGone:k})})()},"outlivedRoll"),He=a(()=>{for(const[e,r]of[...I])Date.now()>=r.deadlineAt?Me(e):!r.svc.rolling&&r.svc.proc!==e&&(console.error(`[gateway] pod handoff: ${r.svc.project} g${r.generation} is a standby whose roll is over \u2014 stopping it.`),Z(e,"its roll is over"))},"sweepStandbys"),Ye=a(()=>{for(const e of[...he.values()])e();for(const e of[...I.keys()])Z(e,"the gateway is stopping")},"stopRetiringTrees"),me=a((e,r)=>{if(e.rolling)return e.rolling;const o=e.proc;if(!o||t.stopped())return Promise.resolve(!1);if(e.launcher==="nx")return console.log(`[gateway] pod handoff skipped: ${e.project} runs through nx run, which starts one instance of a target at a time \u2014 no standby can overlap it (reason=${r}).`),Promise.resolve(!1);Q>=X&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${r} \u2014 ${Q} roll(s) in flight, ${ve.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${X}).`);const s=(async()=>{await We();try{return await Xe(e,o,r)}finally{Ge()}})().finally(()=>{e.rolling=void 0});return e.rolling=s,s},"rollPod"),Xe=a(async(e,r,o)=>{const s=e.proc;if(s!==r||t.stopped())return!1;const i=e.generation??0,n=i+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${i}\u2192g${n} reason=${o} \u2014 standby spawning, old generation keeps serving.`),S(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:n,fromGeneration:i});const u=J(e,n,"standby"),w=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];Ue(e,u,n,o,d);let k=!1;try{const j=d+B;let v;for(;!t.stopped()&&Date.now()<j&&I.has(u)&&!(u.exitCode!==null||u.signalCode!==null);){O(e.pool);for(const g of e.pool.socks)if((await f(g,void 0,w))?.podGeneration===n){v=g;break}if(v)break;await sleep(le)}if(!I.has(u))return!1;if(!v||t.stopped()){const g=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(B/1e3)}s`;Z(u,g);const R=!t.stopped()&&e.proc===s&&s.exitCode===null&&s.signalCode===null&&await ye(e,M=>M!==n,w);return Te(e,{reason:o,generation:n,fromGen:i,why:g,beganAt:d,oldServing:R,oldGone:e.proc!==s||s.exitCode!==null||s.signalCode!==null}),!1}if(e.proc!==s)return Z(u,"the service was replaced underneath the roll"),!1;Be(u),k=!0,e.proc=u,e.launcher=oe.get(u),e.generation=n,e.spawnAt=d,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}return(0,discovery_1.stopRoutingTo)(e.pool,g=>(0,pod_sock_1.podSockGeneration)(g)!==n&&path.join(e.pool.dir,g)!==v),qe(e,s,i),setTimeout(()=>{ge(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${n} answered on ${path.basename(v)} in ${Date.now()-d}ms; retiring g${i}.`),S(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:n,fromGeneration:i,durationMs:Date.now()-d}),!0}finally{k||Z(u,"its roll ended without it")}},"runRoll"),ze=a((e,r)=>{if(t.stopped())return{refused:"the gateway is stopping"};if(e.manualStopped=!1,e.actionFailure=void 0,e.restart&&!e.restart.done)return{restart:e.restart,already:!0};if(!e.proc&&e.state!=="down")return{refused:`${e.project} is served by pods this gateway adopted from an earlier one \u2014 it has no tree of its own to replace. \`lensmcp gateway restart\` starts every service under this gateway.`};const o=e.proc?r.hard||e.launcher==="nx"||e.state!=="up"?"hard":"rolling":"start",s={how:o,reason:r.reason,startedAt:Date.now(),...e.proc&&e.generation!==void 0?{fromGeneration:e.generation}:{}};return e.restart=s,e.lastUsed=s.startedAt,console.log(`[gateway] restart asked: ${e.project}${e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${o} (${r.reason}).`),S(e,"info",`restart asked: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"service-restart",phase:"started",project:e.project,how:o,reason:r.reason,...s.fromGeneration!==void 0?{fromGeneration:s.fromGeneration}:{}}),a(async()=>{if(o==="start")return await we(e)?{ok:!0}:{ok:!1,error:$e(e)};if(o==="rolling")return e.rolling&&await e.rolling,e.proc?(e.rollFailure=void 0,await me(e,r.reason)?{ok:!0}:{ok:!1,error:e.rollFailure??"the new tree did not take over"}):await we(e)?{ok:!0}:{ok:!1,error:$e(e)};if(V(e,`restart asked (${r.reason})`,{announce:!1}),await sleep(1500),t.stopped())return{ok:!1,error:"the gateway is stopping"};let i=!1;e.proc||(await F(),i=!0,!e.proc&&e.state!=="starting"&&Y(e));try{const n=e.proc,d=e.generation,u=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],w=Date.now()+t.startupTimeoutMs;for(;!t.stopped()&&Date.now()<w;){if(!n||e.proc!==n||n.exitCode!==null||n.signalCode!==null)return{ok:!1,error:$e(e)};if(await ye(e,k=>k===d,u))return pe(e,"observed"),e.buildFailed=!1,{ok:!0};await sleep(le)}return{ok:!1,error:t.stopped()?"the gateway is stopping":`no pod of the new tree answered within ${Math.round(t.startupTimeoutMs/1e3)}s`}}finally{i&&D()}},"run")().catch(i=>({ok:!1,error:i.message})).then(i=>{s.done={ok:i.ok,at:Date.now(),...e.generation!==void 0?{generation:e.generation}:{},...i.error?{error:i.error}:{}},i.ok&&(e.lastUsed=s.done.at);const n=Math.round((s.done.at-s.startedAt)/100)/10;i.ok?console.log(`[gateway] restarted: ${e.project} \u2014 g${s.done.generation??"?"} serving after ${n}s (${o}).`):console.error(`[gateway] restart failed: ${e.project} \u2014 ${i.error} (${o}, after ${n}s).`),S(e,i.ok?"info":"error",i.ok?`restarted: ${e.project}`:`restart failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-restart",phase:i.ok?"done":"failed",project:e.project,how:o,reason:r.reason,...s.fromGeneration!==void 0?{fromGeneration:s.fromGeneration}:{},...s.done.generation!==void 0?{generation:s.done.generation}:{},durationMs:s.done.at-s.startedAt,...i.error?{error:i.error}:{}})}),{restart:s,already:!1}},"restartService"),$e=a(e=>e.buildFailed?`its build failed${e.lastErrors?.length?`: ${e.lastErrors[e.lastErrors.length-1]}`:""}`:e.proc&&(e.proc.exitCode!==null||e.proc.signalCode!==null)?`its tree exited (${e.proc.exitCode??e.proc.signalCode})`:!e.proc&&e.state==="down"?"its tree exited before a pod answered":`no pod answered within ${Math.round(t.startupTimeoutMs/1e3)}s`,"startFailure"),xe=a(e=>{const r=Date.now();e.restarts=(e.restarts??[]).filter(s=>r-s<types_1.SERVICE_RESTART_WINDOW_MS);const o=(e.wedgeRecycles??0)+1;if(e.restarts.length>=types_1.SERVICE_MAX_RESTARTS||o>types_1.SERVICE_MAX_RESTARTS){const s=e.restarts.length>=types_1.SERVICE_MAX_RESTARTS?`wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min`:`recycled ${types_1.SERVICE_MAX_RESTARTS}\xD7 in a row and no pod came up`;S(e,"error",`service crash-looping: ${e.project} \u2014 ${s}, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project,recycles:o-1}),V(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.wedgeRecycles=o,e.restarts.push(r),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; attempt ${o}/${types_1.SERVICE_MAX_RESTARTS} with no pod up since the first, ${e.restarts.length} in the last minute; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),S(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:o,inWindow:e.restarts.length}),V(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.()},"recycleWedged"),Je={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let _e;const se=new Map,Ee=new Map;let ke=!1,je=0,fe=0;const Qe=a(()=>{const e=t.projectRoots??{},r=new Set(t.services.map(s=>s.project));for(const s of t.routes)s.lens&&r.add(s.lens.project);const o=new Set;for(const s of r){const i=e[s];if(!i)continue;const n=path.relative(t.root,i).split(path.sep).filter(Boolean)[0];n&&!n.startsWith("..")&&o.add(n)}return[...o]},"managedBuckets"),Ze=a((e,r,o,s)=>{const i=t.projectSourceRoots?.[e];if(i?.length)return i;const n=t.projectRoots?.[e];if(!n)return;const d=sourceScopeDirs(t.root,n,o,r,s);return d.length>0?d:void 0},"scopeFor"),et=a(()=>{if(ke||Date.now()-je<types_1.POD_STALE_SCAN_MS)return;ke=!0,je=Date.now();const e=je;(async()=>{const r=(0,scope_1.readSourceSetOptions)(t.root),o=r.exclude,s={ignoreTests:r.ignoreTests},i=sourceScanPass(t.root,o,s),n=await i.snapshot(),d=Date.now(),u=sourceChangeWindowMs(d,fe,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),w=_e?diffSourceSets(_e,n,d,u):null;if(w&&(t.sourceSetChangedAt=d,t.sourceSetChange=w,H("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:w.added,removed:w.removed,truncated:w.truncated})),_e=n,!t.projectRoots){fe=e;return}let k;try{k=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(h=>h.isDirectory()).map(h=>h.name)}catch{return}const j=Qe(),v=new Set(t.services.map(h=>h.project));for(const h of t.routes)h.lens&&v.add(h.lens.project);const g=new Map;for(const h of v){const P=Ze(h,k,j,o);if(!P)continue;const x=P.join("|"),q=g.get(x)??{dirs:P,projects:[]};q.projects.push(h),g.set(x,q)}const R=Date.now(),M=sourceChangeWindowMs(R,fe,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);fe=e;const G=a((h,P,x)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const q of h)t.sourceSetChangedFor.set(q,P),t.sourceSetChangesFor.set(q,x)},"stampScoped"),ne=new Set([...g.values()].flatMap(h=>h.projects));for(const h of[...t.sourceSetScoped??[]])ne.has(h)||t.sourceSetScoped.delete(h);for(const[h,{dirs:P,projects:x}]of g){const q=await i.snapshot(P),Re=se.get(h);if(Re||x.some(K=>Ee.has(K))){t.sourceSetScoped??=new Set;for(const K of x)t.sourceSetScoped.add(K)}se.set(h,q);const Ae=new Map;for(const K of x){const U=Ee.get(K);if(U===void 0||U===h)continue;const ee=Ae.get(U)??[];ee.push(K),Ae.set(U,ee)}for(const K of x)Ee.set(K,h);for(const[K,U]of Ae){const ee=se.get(K),ce=ee?diffSourceSets(ee,q,R,M):w;ce&&(G(U,ee?R:d,ce),H("info",`source file set changed \u2014 ${U.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:U,added:ce.added,removed:ce.removed,truncated:ce.truncated}))}if(!Re)continue;const ae=diffSourceSets(Re,q,R,M);ae&&(G(x,R,ae),H("info",`source file set changed in ${x.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:x,added:ae.added,removed:ae.removed,truncated:ae.truncated}))}for(const h of[...se.keys()])g.has(h)||se.delete(h)})().catch(()=>{}).finally(()=>{ke=!1})},"maybeScanSourceSet"),tt=a((e,r)=>{const o=podChangeClock(t,e.project),s=o.change,i=`${o.scope==="project"?"in its scope":"in the workspace (its scope is not known)"}: ${changeEvidence(s)}`,n=s?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,d=podWatcherVerdict(e.watcher,r,n),u=a(k=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${k} \u2014 no recycle.`)),!1),"keep");if(d==="heard")return e.staleVerifiedAt=r,S(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:d}),u(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`);if(d==="degraded")return console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0;const w=a(k=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${k}; the change ${i} \u2014 recycling.`)),!0),"recycling");return d==="silent"?types_1.POD_WATCHER_SILENT==="keep"?u("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):w("watcher alive but quiet across the change"):types_1.POD_WATCHER_UNVERIFIED==="keep"?u("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):w("watcher unverifiable (no testimony yet \u2014 a pod that never rebuilt, or an older devserver bundle)")},"podStaleRecycleAllowed"),rt=a((e,r)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){me(e,r);return}console.log(`[gateway] zombie recycle: ${e.project} (${r}) \u2014 stale fs view; fresh pod incoming`),S(e,"warning",`zombie recycle: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:r});const o=!!e.decl.eager;V(e,`${r} \u2014 recycling stale pod`,{announce:!1}),o&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.()},"recyclePod"),ot=a(()=>{const e=setInterval(()=>{ct().catch(r=>console.error("[gateway] sweep error:",r))},t.sweepMs);return e.unref(),e},"startSweeper"),st=l.listProcesses??(()=>{try{return(0,orphan_reaper_1.parsePsRows)((0,node_child_process_1.execFileSync)("ps",orphan_reaper_1.PS_ROW_ARGS,{encoding:"utf8",timeout:5e3}))}catch{return[]}}),Pe=l.signalProcess??((e,r)=>{process.kill(e,r)});let De=0;const nt=a(()=>{if(typeof process.getuid!="function")return 0;const e=t.registry?t.registry.allServices():t.services;if(e.length===0)return 0;const r=e.map(n=>({label:n.wsKey||path.basename(n.root),project:n.project,root:n.root,svc:n})),o=new Set;for(const n of e)n.proc?.pid&&o.add(n.proc.pid);for(const n of[...he.keys(),...N.keys(),...I.keys()])n.pid&&o.add(n.pid);const s=[...new Set((0,process_name_1.defaultLinkDirs)().flatMap(n=>{try{return[n,fs.realpathSync(n)]}catch{return[n]}}))];let i=0;for(const n of(0,orphan_reaper_1.findOrphanExecutors)(st(),r,{linkDirs:s,uid:process.getuid(),selfPid:process.pid,ownGroups:o})){const{row:d}=n;try{Pe(n.group?-d.pgid:d.pid,"SIGKILL")}catch(w){if(w.code!=="ESRCH"||!n.group)continue;try{Pe(d.pid,"SIGKILL")}catch{continue}}i+=1;const u=n.owner.svc;console.log(`[gateway] reaped an orphaned executor of ${u.project} (${n.role}, pid ${d.pid}${n.group?`, group ${d.pgid}`:""}, parent gone) \u2014 a stopped tree it outlived: ${d.command.slice(0,160)}`),S(u,"warning",`orphan reap: ${u.project} (pid ${d.pid})`,`cluster-service:${u.project}`,{kind:"orphan-executor-reap",project:u.project,pid:d.pid,pgid:d.pgid,role:n.role,group:n.group})}return i},"reapOrphanExecutors"),ct=a(async()=>{{if(t.stopped())return;He(),l.reapForeign!==!1&&types_1.ORPHAN_REAP_EVERY_MS>0&&Date.now()-De>=types_1.ORPHAN_REAP_EVERY_MS&&(De=Date.now(),nt());for(const e of t.graphStores?.all()??[])e.refreshIfStale();types_1.POD_RECYCLE_ENABLED&&et();for(const e of t.registry?t.registry.allServices():t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const r=path.resolve(e.root)===path.resolve(t.root),o=(0,warm_start_1.instanceIdleSec)(e,!r&&!!t.registry?.get(e.wsKey)?.instance);if(o&&e.state==="up"&&(!e.restart||e.restart.done)&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){V(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,i=O(e.pool)>0;if(i||s){if(e.wedgedSince=void 0,i&&pe(e,"observed"),i&&!s&&types_1.POD_RECYCLE_ENABLED&&r){const n=podChangeClock(t,e.project).at;await be(e);const d=(e.staleVerifiedAt??0)>=n&&n>0,u=podRecycleReason(e,Date.now(),d?0:n,Je);if(u==="stale-source-set"&&!tt(e,n))continue;u&&rt(e,u)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,xe(e))}}},"sweepOnce");return{scanNow:O,spawnService:Y,ensureUp:we,scaleUp:Ke,killSpawned:V,markUp:pe,startSweeper:ot,rollPod:me,restartService:ze,recycleWedged:xe,stopRetiringTrees:Ye,finishDyingTrees:Fe,stopBuildHost:a(e=>e===void 0?_.stop():_.release(e),"stopBuildHost")}}m(createServiceLayer,"createServiceLayer"),a(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,c,l){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(c!==void 0&&c!==t)&&t!==l)return t}m(chooseStalePodKillTarget,"chooseStalePodKillTarget"),a(chooseStalePodKillTarget,"chooseStalePodKillTarget");
|
|
1
|
+
"use strict";var lt=Object.defineProperty;var _=(t,c)=>lt(t,"name",{value:c,configurable:!0});var it=Object.defineProperty,s=_((t,c)=>it(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isLoopbackHostname=isLoopbackHostname,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isRunnerOf=isRunnerOf,exports.isTestSourcePath=isTestSourcePath,exports.sourceScanPass=sourceScanPass,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.podChangeClock=podChangeClock,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),instance_1=require("./instance"),pod_sock_1=require("../../../pod-sock"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),own_log_1=require("./own-log"),inspector_secret_1=require("../../../inspector-secret"),pod_output_1=require("./pod-output"),serve_launch_1=require("./serve-launch"),shared_build_1=require("./shared-build"),awake_1=require("./awake"),warm_start_1=require("./warm-start"),protocol_1=require("../../../shared-build/protocol"),types_1=require("./types"),exit_reason_1=require("../../../exit-reason"),handoff_retire_1=require("../../../handoff-retire"),process_name_1=require("../../../process-name"),orphan_reaper_1=require("./orphan-reaper");function eastWestEnv(t,c){const l={},p=t.serviceKeys[(0,service_keys_1.serviceKeyId)(c.wsKey,c.project)];if(p&&(l.LENSMCP_SERVICE_KEY=p),t.https){const y=require("../../../basic-ssl").caCertPath();fs.existsSync(y)&&(l.NODE_EXTRA_CA_CERTS=y)}return l}_(eastWestEnv,"eastWestEnv"),s(eastWestEnv,"eastWestEnv");const sleep=s(t=>new Promise(c=>setTimeout(c,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let c=2166136261;for(let l=0;l<t.length;l+=1)c^=t.charCodeAt(l),c=Math.imul(c,16777619);return c>>>0}_(fnv1a,"fnv1a"),s(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:c}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(c.split("\0").filter(Boolean))}catch{return new Set}}_(gitIgnoredPaths,"gitIgnoredPaths"),s(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,c=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const l=path.join(t.pool.dir,"parent.sock");return c(l)?{socketPath:l}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}_(scaleTarget,"scaleTarget"),s(scaleTarget,"scaleTarget");function isLoopbackHostname(t){const c=t.toLowerCase().replace(/^\[(.*)\]$/,"$1").replace(/\.$/,"");return c==="localhost"||c.endsWith(".localhost")?!0:net.isIPv4(c)?c.split(".")[0]==="127":net.isIPv6(c)&&new URL(`http://[${c}]/`).hostname==="[::1]"}_(isLoopbackHostname,"isLoopbackHostname"),s(isLoopbackHostname,"isLoopbackHostname");function postScale(t,c,l){let p;if("socketPath"in t)p=http.request({socketPath:t.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},y=>y.resume());else{const y=t.url.startsWith("https"),M=y?require("node:https"):http,j={method:"POST"};y&&isLoopbackHostname(new URL(t.url).hostname)&&(j.rejectUnauthorized=!1),l&&(j.lookup=l),p=M.request(`${t.url}/webpack/scale`,j,A=>A.resume())}return p.on("error",()=>{}),p.end(JSON.stringify({pods:c})),p}_(postScale,"postScale"),s(postScale,"postScale");function isOwnRunnerCommand(t,c){return(0,serve_launch_1.isServeRunnerCommand)(t)?(0,serve_launch_1.isServeRunnerCommandOf)(t,c):t.includes("/node_modules/nx/")?t.includes(`${c.replace(/\/+$/,"")}/node_modules/nx/`):!0}_(isOwnRunnerCommand,"isOwnRunnerCommand"),s(isOwnRunnerCommand,"isOwnRunnerCommand");function isRunnerOf(t,c,l){const p=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return!new RegExp(`(^|\\s)run ${p}(:|\\s|$)`).test(t)||!(0,serve_launch_1.isServeRunnerCommand)(t)&&!/\bnx(\.js)?\b/.test(t)?!1:isOwnRunnerCommand(t,l)}_(isRunnerOf,"isRunnerOf"),s(isRunnerOf,"isRunnerOf");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const c=t.split("/"),l=c[c.length-1]??"";if(TEST_FILE_RE.test(l))return!0;for(let p=0;p<c.length-1;p+=1)if(TEST_DIR_SEGMENTS.has(c[p]))return!0;return!1}_(isTestSourcePath,"isTestSourcePath"),s(isTestSourcePath,"isTestSourcePath");function sourceScanPass(t,c,l){const p=l?.ignoreTests??!0;let y;const M=new Map,j=`tree:${path.resolve(t)}`,A=s(m=>path.relative(t,m).split(path.sep).join("/"),"rel"),T=s(async(m,E)=>{const R=await(y??=gitIgnoredPaths(t)),D=[],F=new Set;let b=!1;const H=s(async(te,v)=>{if(D.length>=SCAN_FILE_CAP){b=!0;return}let O;try{O=await fs.promises.readdir(te,{withFileTypes:!0})}catch{return}v&&F.add(A(te));for(const z of O){if(D.length>=SCAN_FILE_CAP){b=!0;return}const W=z.name,re=path.join(te,W);if(z.isDirectory()){if(!v||W.startsWith(".")||SRC_EXCLUDE.has(W)||c?.has(W)||W.endsWith("-devserver")||R.has(`${A(re)}/`))continue;await H(re,!0)}else if(z.isFile()){const oe=W.lastIndexOf(".");if(oe<=0||!SRC_EXT.has(W.slice(oe)))continue;const J=A(re);if(R.has(J)||p&&isTestSourcePath(J))continue;D.push(J)}}},"walk");return await H(m,E),{paths:D,entered:F,truncated:b}},"walkDir"),L=s((m,E)=>{let R=M.get(m);return R||(R=E(),M.set(m,R)),R},"memo"),ce=s(async()=>{const m=M.get(j);if(!m)return;const E=await m;return E.truncated?void 0:E},"wideWalk"),B=new Set,le=s(()=>L(`files:${path.resolve(t)}`,async()=>{const m=await ce();return m?{paths:m.paths.filter(E=>!E.includes("/")),entered:B,truncated:!1}:T(t,!1)}),"rootFiles"),de=s(m=>L(`tree:${path.resolve(m)}`,async()=>{const E=await ce(),R=A(m);if(E?.entered.has(R)){const D=`${R}/`;return{paths:R===""?E.paths:E.paths.filter(F=>F.startsWith(D)),entered:B,truncated:!1}}return T(m,!0)}),"subtree"),X=s(m=>{const E=new Set;let R=0,D=m.some(b=>b.truncated);const F=s(b=>E.has(b)?!0:E.size>=SCAN_FILE_CAP?(D=!0,!1):(E.add(b),R^=fnv1a(b),!0),"add");for(const b of m)if(!b.paths.every(F))break;return{signature:`${E.size}:${(R>>>0).toString(36)}`,paths:E,truncated:D}},"compose");return{async snapshot(m){if(!m||m.length===0)return X([await L(j,()=>T(t,!0))]);const E=[await le()];for(const R of m)E.push(await de(R));return X(E)}}}_(sourceScanPass,"sourceScanPass"),s(sourceScanPass,"sourceScanPass");async function sourceSetSnapshot(t,c,l,p){return sourceScanPass(t,l,p).snapshot(c)}_(sourceSetSnapshot,"sourceSetSnapshot"),s(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,c,l,p){return(await sourceSetSnapshot(t,c,l,p)).signature}_(sourceSetSignature,"sourceSetSignature"),s(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,c,l,p){if(t.paths.size===c.paths.size){let A=!0;for(const T of t.paths)if(!c.paths.has(T)){A=!1;break}if(A)return null}const y=[],M=[];let j=t.truncated||c.truncated;for(const A of c.paths)t.paths.has(A)||(y.length+M.length<SOURCE_DIFF_PATH_CAP?y.push(A):j=!0);for(const A of t.paths)c.paths.has(A)||(y.length+M.length<SOURCE_DIFF_PATH_CAP?M.push(A):j=!0);return{changedAt:l,added:y,removed:M,truncated:j,...p!==void 0?{windowMs:p}:{}}}_(diffSourceSets,"diffSourceSets"),s(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,c,l,p){const y=c&&c>0?t-c:l;return Math.max(y,l)+p}_(sourceChangeWindowMs,"sourceChangeWindowMs"),s(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,c,l,p,y){const M=s(T=>{const L=path.relative(t,T).split(path.sep).filter(Boolean);return L.length===0||L[0].startsWith("..")?"":L[0]},"bucketOf")(c);if(!M)return[];const j=new Set(l.filter(Boolean)),A=new Set([M]);for(const T of p)T.startsWith(".")||SRC_EXCLUDE.has(T)||y?.has(T)||T.endsWith("-devserver")||j.has(T)||A.add(T);return[...A].sort().map(T=>path.join(t,T))}_(sourceScopeDirs,"sourceScopeDirs"),s(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,c,l,p){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||c-t.lastUsed<p.settleMs||t.lastStaleRecycleAt!=null&&c-t.lastStaleRecycleAt<p.cooldownMs?null:l>0&&l-t.spawnAt>p.graceMs?"stale-source-set":p.maxAgeMs>0&&c-t.spawnAt>p.maxAgeMs?"max-age":null}_(podRecycleReason,"podRecycleReason"),s(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,c,l,p,y){return!(p-c<y.cooldownMs||l<=0||l-t<=y.graceMs||p-l<y.settleMs)}_(lensFrontendStale,"lensFrontendStale"),s(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,c,l,p=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&p.length>0){const y=c-l,M=new Set(t.recentEvents.filter(j=>j.at>=y).map(j=>path.resolve(j.path)));return p.every(j=>M.has(path.resolve(j)))?"heard":"missed"}return t.lastFsSetEventAt>=c-l?"heard":"missed"}_(lensWatcherVerdict,"lensWatcherVerdict"),s(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,c,l){if(!t)return"unknown";if(t.degraded)return"degraded";const p=c-l;return t.lastWatchRunAt>=p||t.lastBuildAt>=p?"heard":"silent"}_(podWatcherVerdict,"podWatcherVerdict"),s(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,c,l,p){const y=t.hasListened?p.graceMs:Math.max(p.graceMs,p.neverListenedGraceMs);return l-t.spawnedAt<y||l-c<p.cooldownMs||t.probeFails<Math.max(2,p.failureThreshold)?null:"wedged-no-listener"}_(lensChildWedged,"lensChildWedged"),s(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,c){return t.signal||t.code===null||t.code===0||t.ranMs>c.fastFailMs?!1:t.portHeld}_(isPortConflictExit,"isPortConflictExit"),s(isPortConflictExit,"isPortConflictExit");function podChangeClock(t,c){const l=t.sourceSetChangedFor?.get(c);if(l!==void 0){const p=t.sourceSetChangesFor?.get(c);return{at:l,...p?{change:p}:{},scope:"project"}}return t.sourceSetScoped?.has(c)?{at:0,scope:"none"}:{at:t.sourceSetChangedAt??0,...t.sourceSetChange?{change:t.sourceSetChange}:{},scope:"workspace"}}_(podChangeClock,"podChangeClock"),s(podChangeClock,"podChangeClock");const changeEvidence=s(t=>{if(!t)return"no path evidence";const c=[...t.added.map(l=>`+${l}`),...t.removed.map(l=>`-${l}`)];return c.length?`${c.slice(0,3).join(", ")}${c.length>3?` (+${c.length-3} more)`:""}`:"no path evidence"},"changeEvidence");function createServiceLayer(t,c,l={}){const p=l.spawnProcess??node_child_process_1.spawn,y=l.probeAlive??pod_probe_1.probePodAlive,M=l.killTree??((e,r="SIGTERM")=>{try{process.kill(-e.pid,r)}catch{try{e.kill(r)}catch{}}}),j=l.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),A=s((e,r="SIGTERM")=>{j.detach(e),M(e,r)},"killTree"),T=l.handoffDrainMs??(0,handoff_retire_1.handoffDrainBoundMs)(),L=l.treeAlive??(e=>{if(!e.pid||!(e instanceof node_child_process_1.ChildProcess))return!1;try{return process.kill(-e.pid,0),!0}catch(r){return r.code==="EPERM"}}),ce=l.retirePollMs??1e3,B=l.rollReadyMs??types_1.POD_ROLL_READY_MS,le=l.rollPollMs??750,de=l.maxConcurrentRolls??types_1.POD_MAX_CONCURRENT_ROLLS,X=de>0?de:Number.POSITIVE_INFINITY,m=l.maxConcurrentColdStarts??types_1.MAX_COLD_STARTS,E=m>0?m:Number.POSITIVE_INFINITY;let R=0;const D=[],F=s(()=>R<E?(R+=1,Promise.resolve()):new Promise(e=>{D.push(()=>{R+=1,e()})}),"acquireColdSlot"),b=s(()=>{R-=1,D.shift()?.()},"releaseColdSlot"),{emit:H}=c,te=s(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),v=s((e,r,o,n,d)=>H(r,o,n,{wsKey:e.wsKey,...d},void 0,te(e)),"emitSvc"),O=s(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),z=s((e,r)=>{let o;try{o=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const n=e.proc?.pid;let d=0;for(const a of o.split(`
|
|
2
|
+
`)){const i=a.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!i)continue;const u=Number(i[1]),f=Number(i[2]);if(isRunnerOf(i[3],r,e.root)&&!(u===process.pid||u===n)&&!(n!==void 0&&f===n)){try{process.kill(-f,"SIGKILL")}catch{try{process.kill(u,"SIGKILL")}catch{}}d+=1}}return d>0&&(console.log(`[gateway] ${r}: reaped ${d} stale runner tree(s) \u2014 a foreign generation of this service (holding nx's task lock, or its pool)`),v(e,"warning",`stale-runner reap: ${e.project} (${d} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:d})),d},"reapForeignRunners"),W=s((e,r)=>({...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),...e.instanceEnv?{...e.instanceEnv,LENSMCP_SOCKET_ONLY:"1"}:{},...e.frozen?{LENSMCP_FREEZE:"1",LENSMCP_TYPECHECK:"off"}:{},NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(r),...eastWestEnv(t,e)}),"serviceEnv"),re=s((e,r,o,n)=>{const d=W(e,r),a=e.wsKey||path.basename(e.root),i=(0,serve_launch_1.planServeLaunch)({stores:t.graphStores,root:e.root,project:e.project,target:e.decl.serveTarget??"serve-hmr",childCount:n,baseEnv:d});if(i.kind==="runner"){const $=j.offeredTo(e),h=p((0,process_name_1.namedExecPath)((0,process_name_1.processName)(a,e.project,serve_launch_1.SERVE_RUNNER_ROLE)),[...i.script.execArgv,i.script.path,...(0,serve_launch_1.serveRunnerArgs)(o,n,e.root)],{cwd:e.root,detached:!0,env:$?{...i.env,[protocol_1.SHARED_BUILD_ENV]:"1"}:i.env,stdio:["ignore","pipe","pipe","ipc"]});return $&&j.attach(e,r,h),h.send?.(i.message,k=>{k&&console.error(`[gateway] ${o}: could not hand the task to its runner (${k.message})`)}),{proc:h,launcher:"runner"}}console.log(`[gateway] ${o}: starting through nx run \u2014 ${i.reason}`);const u=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),f=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(a,e.project,"nx")),[g,w]=fs.existsSync(u)?[f,[u,"run",o,`--childCount=${n}`]]:["yarn",["nx","run",o,`--childCount=${n}`]];return{proc:p(g,w,{cwd:e.root,detached:!0,env:d,stdio:["ignore","pipe","pipe"]}),launcher:"nx"}},"startServiceTree"),oe=new WeakMap,J=s((e,r,o)=>{const n=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,d=Math.max(1,e.decl.minPods??1),{proc:a,launcher:i}=re(e,r,n,d);oe.set(a,i),o==="active"&&(e.launcher=i),o==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const u=s(w=>{const $=w.toString();for(const h of $.split(`
|
|
3
|
+
`)){const k=(0,pod_output_1.classifyPodLine)(h),{clean:C,broken:G,healthy:ne}=k,S=C;if(G){e.lastErrors.push(C),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const x=Date.now();(C!==e.lastErrorEmitSig||x-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=C,e.lastErrorEmitAt=x,v(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:C,fault:k.fault}))}ne&&(e.buildFailed=!1);const P=(e.faults??=new pod_output_1.PodFaults).observe(k);if(P){e.lastErrorEmitSig=void 0;const x=P.open.length===0;v(e,"info",`${x?"service recovered":`${P.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:x?"service-recovered":"service-fault-closed",project:e.project,line:C,closed:P.closed,open:P.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(S)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${n}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),v(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(V(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),z(e,n),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.())},250).unref?.())}},"sniff"),f=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,g=s(w=>{const $=(0,own_log_1.linePrefixer)(e.project);return h=>{if(t.stopped())return;const k=(0,inspector_secret_1.redactInspectorUrls)(h.toString());u(k);const C=$(k);w.write(C),f?.(C)}},"relay");return a.stdout?.on("data",g(process.stdout)),a.stderr?.on("data",g(process.stderr)),a.on("exit",(w,$)=>{if(e.proc!==a||(e.proc=void 0,e.state="down",t.stopped()))return;const h=(0,exit_reason_1.describeExit)(w,$);if(console.log(`[gateway] ${n} exited (${w??"no code"}${$?`, ${$}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${h?` \u2014 ${h}`:""}`),v(e,w===0?"info":"warning",`service down: ${e.project} (exit ${w})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:w,...$?{signal:$}:{}}),w===0||e.buildFailed)return;const k=Date.now();if(e.restarts=(e.restarts??[]).filter(G=>k-G<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){v(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(k);const C=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),v(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),Y(e))},C)}),a},"launchServiceProcess"),Y=s(e=>{if(t.stopped()||e.manualStopped)return;const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,o=Math.max(1,e.decl.minPods??1);l.reapForeign!==!1&&z(e,r),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${r} (pods=${o})\u2026`),v(e,"info",`service starting: ${e.project} (${o} pod${o>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:o}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=J(e,e.generation,"active")},"spawnService"),De=s(async e=>{const r=e.pool.socks[0];if(!r)return;const o=await(0,pod_probe_1.probePodAlive)(r,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);o&&(e.activeWork=o.activeWork,o.watcher&&(e.watcher=o.watcher))},"refreshActiveWork"),Le=s(e=>{try{const r=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(r)&&r>1?r:void 0}catch{return}},"readOwnerSidecar"),Ie=s(async e=>{const r=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!r)return[];const o=(0,node_crypto_1.createHash)("sha256").update(r).digest("hex").slice(0,8),n=[];for(const d of e.pool.socks){const a=await(0,pod_probe_1.probePodAlive)(d,void 0,r);a&&(!a.keyFingerprint||a.keyFingerprint!==o)&&n.push({sock:d,pid:a.pid})}return n},"staleAdoptedSocks"),Oe=s((e,r)=>{const o=r.map(n=>n.sock);for(const{sock:n,pid:d}of r){const a=chooseStalePodKillTarget(d,Le(n),process.pid);if(a!==void 0)try{process.kill(a,"SIGTERM")}catch{}try{fs.unlinkSync(n)}catch{}try{fs.unlinkSync(`${n}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(n=>!o.includes(n)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),pe=s((e,r)=>{if(e.state==="up")return!1;e.state="up",e.wedgeRecycles=0;const o=e.pool.socks.length;return v(e,"info",`service up: ${e.project} (${o} pods, ${r})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:o,adopted:r==="adopted"}),!0},"markUp"),we=s(async e=>{if(e.manualStopped)return!1;if(O(e.pool)>0){const n=await Ie(e);if(e.manualStopped)return!1;if(n.length>0&&(console.error(`[gateway] ${e.project}: ${n.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),v(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:n.length}),e.proc&&V(e,"stale adoption (foreign east-west key)"),Oe(e,n)),e.pool.socks.length>0)return pe(e,"adopted"),!0}if(t.stopped())return!1;let r=!1;const o=s(()=>e.state!=="starting"&&!e.proc,"notStarted");if(o())if(e.coldWait)await e.coldWait;else{R>=E&&(console.log(`[gateway] cold start queued: ${e.project}${e.wsKey&&e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${R} starting, ${D.length} ahead (LENSMCP_MAX_COLD_STARTS=${E}).`),v(e,"info",`cold start queued: ${e.project}`,`cluster-service:${e.project}`,{kind:"cold-start-queued",project:e.project,inFlight:R,ahead:D.length}));const n=F();if(e.coldWait=n,await n,e.coldWait=void 0,r=!0,t.stopped()||e.manualStopped)return b(),!1;o()&&Y(e)}try{return await Ne(e)}finally{r&&b()}},"ensureUp"),Ne=s(async e=>{const r=Date.now()+t.startupTimeoutMs,o=Date.now()+12e3;for(;!t.stopped()&&!e.manualStopped&&Date.now()<r;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.manualStopped||e.buildFailed&&Date.now()>o)return!1;if(O(e.pool)>0){e.state="up",e.buildFailed=!1;const n=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${n?`, cold start ${n}ms`:""})`),v(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${n??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:n}),!0}if(e.state==="down")return!1}return!1},"waitForPods"),Ke=s(e=>{const r=e.decl.maxPods??e.decl.minPods??1,o=e.pool.socks.length,n=scaleTarget(e);!n||o===0||o>=r||e.inflight<=o||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(n,o+1,l.scaleLookup),console.log(`[gateway] autoscale ${e.project}: ${o} \u2192 ${o+1} pods (inflight ${e.inflight})`),v(e,"info",`autoscale: ${e.project} ${o} \u2192 ${o+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:o,to:o+1,inflight:e.inflight}))},"scaleUp"),ue=l.killGraceMs??types_1.TREE_KILL_GRACE_MS,N=new Map,Ce=s(e=>{const r=N.get(e);if(!r||(N.delete(e),clearTimeout(r.timer),!L(e)))return;const{svc:o,why:n}=r;console.log(`[gateway] ${o.project}: its tree (pid ${e.pid}) was still running ${Math.round(ue/1e3)}s after it was told to stop (${n}) \u2014 killing it (SIGKILL).`),v(o,"warning",`tree force-killed: ${o.project} (it ignored SIGTERM)`,`cluster-service:${o.project}`,{kind:"tree-force-killed",project:o.project,why:n,pid:e.pid,graceMs:ue}),M(e,"SIGKILL")},"settleDyingTree"),ve=s((e,r,o)=>{if(A(r,"SIGTERM"),ue<=0||N.has(r))return;const n=setTimeout(()=>Ce(r),ue);n.unref?.(),N.set(r,{svc:e,why:o,timer:n})},"stopTree"),Fe=s(async(e=3e3)=>{const r=Date.now()+e;for(;N.size>0;){for(const[o,n]of[...N])L(o)||(clearTimeout(n.timer),N.delete(o));if(N.size===0||Date.now()>=r)break;await sleep(100)}for(const o of[...N.keys()])Ce(o)},"finishDyingTrees"),V=s((e,r,o)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${r} \u2014 scaling to zero.`),o?.announce!==!1&&v(e,"info",`idle kill: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:r});const n=e.proc;try{n.stdout?.removeAllListeners("data"),n.stderr?.removeAllListeners("data"),n.removeAllListeners("exit")}catch{}(0,discovery_1.stopRoutingTo)(e.pool),ve(e,n,r),e.proc=void 0,e.state="down",setTimeout(()=>{ge(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ge=s(e=>{try{for(const r of fs.readdirSync(e.pool.dir)){if(!r.endsWith(".sock"))continue;const o=path.join(e.pool.dir,r);try{const n=Number(fs.readFileSync(`${o}.owner`,"utf8").trim());if(Number.isFinite(n)&&n>0)try{process.kill(n,0);continue}catch{}}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let Q=0;const Se=[],We=s(()=>Q<X?(Q+=1,Promise.resolve()):new Promise(e=>{Se.push(()=>{Q+=1,e()})}),"acquireRollSlot"),Ge=s(()=>{Q-=1,Se.shift()?.()},"releaseRollSlot"),ye=s(async(e,r,o)=>{O(e.pool);for(const n of e.pool.socks){const d=await y(n,void 0,o);if(d&&r(d.podGeneration))return!0}return!1},"anyPodAnswers"),he=new Map,qe=s((e,r,o)=>{const n=T>0&&(0,handoff_retire_1.requestHandoffRetire)(e.pool.dir,o);if(A(r,"SIGTERM"),!n)return;const d=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,a=s(ne=>S=>{if(t.stopped())return;const P=(0,inspector_secret_1.redactInspectorUrls)(S.toString()).replace(/^(?=.)/gm,`[${e.project} g${o} retiring] `);ne.write(P),d?.(P)},"say"),i=a(process.stdout),u=a(process.stderr);r.stdout?.on("data",i),r.stderr?.on("data",u);let f,g=!1;const w=s(()=>{g=!0,clearTimeout(f),r.stdout?.removeListener("data",i),r.stderr?.removeListener("data",u),r.removeListener("close",w)},"releaseLogs");r.once("close",w);let $=!1;const h=s(()=>{$||($=!0,clearTimeout(k),clearInterval(G),t.stopped()?w():g||(f=setTimeout(w,1e3),f.unref?.()),he.delete(r),(0,handoff_retire_1.clearHandoffRetire)(e.pool.dir,o),ge(e),e.pool.scannedAt=0)},"end"),k=setTimeout(()=>{console.error(`[gateway] pod handoff: ${e.project} g${o} was still running ${T}ms after it was retired \u2014 killing it.`),M(r,"SIGKILL"),h()},T);k.unref?.();const C=s(()=>{if(!$){if(t.stopped()){h();return}L(r)||(console.log(`[gateway] pod handoff: ${e.project} g${o} finished its in-flight work and exited.`),h())}},"look"),G=setInterval(C,ce);G.unref?.(),r.once("exit",C),he.set(r,()=>{h(),ve(e,r,"the gateway is stopping")})},"retireReplacedTree"),Ve=l.standbyGraceMs??3e4,I=new Map,Ue=s((e,r,o,n,d)=>{const a=d+B+Ve,i=setTimeout(()=>Me(r),Math.max(0,a-Date.now()));i.unref?.(),I.set(r,{svc:e,generation:o,reason:n,beganAt:d,deadlineAt:a,timer:i})},"holdStandby"),Be=s(e=>{const r=I.get(e);r&&(clearTimeout(r.timer),I.delete(e))},"releaseStandby"),Z=s((e,r)=>{const o=I.get(e);if(!o)return!1;clearTimeout(o.timer),I.delete(e);const{svc:n,generation:d}=o;try{e.stdout?.removeAllListeners("data"),e.stderr?.removeAllListeners("data")}catch{}return(0,discovery_1.stopRoutingTo)(n.pool,a=>(0,pod_sock_1.podSockGeneration)(a)===d),ve(n,e,`standby g${d}: ${r}`),setTimeout(()=>ge(n),1200).unref?.(),!0},"endStandby"),Te=s((e,r)=>{const o=r.oldServing?`old generation g${r.fromGen} kept serving`:`and the old generation g${r.fromGen} is NOT serving (${r.oldGone?"its tree is gone":"none of its pods answers"}) \u2014 the service is down until it recovers`;console.error(`[gateway] pod handoff failed: ${e.project} g${r.generation}: ${r.why} \u2014 ${o}.`),e.rollFailure=`${r.why} \u2014 ${o}`,v(e,r.oldServing?"warning":"error",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:r.reason,generation:r.generation,error:r.why,durationMs:Date.now()-r.beganAt,oldServing:r.oldServing})},"reportStandbyFailed"),Me=s(e=>{const r=I.get(e);if(!r)return;const{svc:o,generation:n,reason:d,beganAt:a}=r,i=`the standby outlived its roll (${Math.round((Date.now()-a)/1e3)}s; a roll waits ${Math.round(B/1e3)}s for it)`;Z(e,i);const u=o.proc,f=t.serviceKeys[(0,service_keys_1.serviceKeyId)(o.wsKey,o.project)];(async()=>{const g=!u||u.exitCode!==null||u.signalCode!==null,w=g?!1:await Promise.race([ye(o,h=>h!==n,f).catch(()=>!1),sleep(2e3).then(()=>{})]),$=!t.stopped()&&!g&&(w??O(o.pool)>0);Te(o,{reason:d,generation:n,fromGen:o.generation??n-1,why:i,beganAt:a,oldServing:$,oldGone:g})})()},"outlivedRoll"),He=s(()=>{for(const[e,r]of[...I])Date.now()>=r.deadlineAt?Me(e):!r.svc.rolling&&r.svc.proc!==e&&(console.error(`[gateway] pod handoff: ${r.svc.project} g${r.generation} is a standby whose roll is over \u2014 stopping it.`),Z(e,"its roll is over"))},"sweepStandbys"),Ye=s(()=>{for(const e of[...he.values()])e();for(const e of[...I.keys()])Z(e,"the gateway is stopping")},"stopRetiringTrees"),$e=s((e,r)=>{if(e.rolling)return e.rolling;const o=e.proc;if(!o||t.stopped())return Promise.resolve(!1);if(e.launcher==="nx")return console.log(`[gateway] pod handoff skipped: ${e.project} runs through nx run, which starts one instance of a target at a time \u2014 no standby can overlap it (reason=${r}).`),Promise.resolve(!1);Q>=X&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${r} \u2014 ${Q} roll(s) in flight, ${Se.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${X}).`);const n=(async()=>{await We();try{return await Xe(e,o,r)}finally{Ge()}})().finally(()=>{e.rolling=void 0});return e.rolling=n,n},"rollPod"),Xe=s(async(e,r,o)=>{const n=e.proc;if(n!==r||t.stopped())return!1;const d=e.generation??0,a=d+1,i=Date.now();e.lastStaleRecycleAt=i,console.log(`[gateway] pod handoff start: ${e.project} g${d}\u2192g${a} reason=${o} \u2014 standby spawning, old generation keeps serving.`),v(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:a,fromGeneration:d});const u=J(e,a,"standby"),f=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];Ue(e,u,a,o,i);let g=!1;try{const w=i+B;let $;for(;!t.stopped()&&Date.now()<w&&I.has(u)&&!(u.exitCode!==null||u.signalCode!==null);){O(e.pool);for(const h of e.pool.socks)if((await y(h,void 0,f))?.podGeneration===a){$=h;break}if($)break;await sleep(le)}if(!I.has(u))return!1;if(!$||t.stopped()){const h=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(B/1e3)}s`;Z(u,h);const k=!t.stopped()&&e.proc===n&&n.exitCode===null&&n.signalCode===null&&await ye(e,C=>C!==a,f);return Te(e,{reason:o,generation:a,fromGen:d,why:h,beganAt:i,oldServing:k,oldGone:e.proc!==n||n.exitCode!==null||n.signalCode!==null}),!1}if(e.proc!==n)return Z(u,"the service was replaced underneath the roll"),!1;Be(u),g=!0,e.proc=u,e.launcher=oe.get(u),e.generation=a,e.spawnAt=i,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{n.stdout?.removeAllListeners("data"),n.stderr?.removeAllListeners("data"),n.removeAllListeners("exit")}catch{}return(0,discovery_1.stopRoutingTo)(e.pool,h=>(0,pod_sock_1.podSockGeneration)(h)!==a&&path.join(e.pool.dir,h)!==$),qe(e,n,d),setTimeout(()=>{ge(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${a} answered on ${path.basename($)} in ${Date.now()-i}ms; retiring g${d}.`),v(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:a,fromGeneration:d,durationMs:Date.now()-i}),!0}finally{g||Z(u,"its roll ended without it")}},"runRoll"),ze=s((e,r)=>{if(t.stopped())return{refused:"the gateway is stopping"};const o=e.restart&&!e.restart.done?e.restart:void 0,n=o?void 0:Je(e);if(n&&"refused"in n)return console.error(`[gateway] restart refused: ${e.project}${e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${n.refused}`),v(e,"warning",`restart refused: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-restart",phase:"refused",project:e.project,reason:r.reason,error:n.refused}),{refused:n.refused};const d=n&&"applied"in n?n:void 0;if(e.manualStopped=!1,e.actionFailure=void 0,o)return{restart:o,already:!0};if(!e.proc&&e.state!=="down")return{refused:`${e.project} is served by pods this gateway adopted from an earlier one \u2014 it has no tree of its own to replace. \`lensmcp gateway restart\` starts every service under this gateway.`};const a=e.proc?r.hard||e.launcher==="nx"||e.state!=="up"?"hard":"rolling":"start",i={how:a,reason:r.reason,startedAt:Date.now(),...e.proc&&e.generation!==void 0?{fromGeneration:e.generation}:{},...d?{env:d.applied}:{}};d&&(e.instanceEnv=d.env),e.restart=i,e.lastUsed=i.startedAt;const u=i.env?{envFingerprint:i.env.fingerprint,envChanged:i.env.changed}:{},f=i.env?` Instance variables ${i.env.fingerprint}: ${i.env.changed.length?`changed ${i.env.changed.join(", ")}`:"unchanged"}.`:"";return console.log(`[gateway] restart asked: ${e.project}${e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${a} (${r.reason}).${f}`),v(e,"info",`restart asked: ${e.project} (${a})`,`cluster-service:${e.project}`,{kind:"service-restart",phase:"started",project:e.project,how:a,reason:r.reason,...i.fromGeneration!==void 0?{fromGeneration:i.fromGeneration}:{},...u}),s(async()=>{if(a==="start")return await we(e)?{ok:!0}:{ok:!1,error:me(e)};if(a==="rolling")return e.rolling&&await e.rolling,e.proc?(e.rollFailure=void 0,await $e(e,r.reason)?{ok:!0}:{ok:!1,error:e.rollFailure??"the new tree did not take over"}):await we(e)?{ok:!0}:{ok:!1,error:me(e)};if(V(e,`restart asked (${r.reason})`,{announce:!1}),await sleep(1500),t.stopped())return{ok:!1,error:"the gateway is stopping"};let g=!1;e.proc||(await F(),g=!0,!e.proc&&e.state!=="starting"&&Y(e));try{const w=e.proc,$=e.generation,h=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],k=Date.now()+t.startupTimeoutMs;for(;!t.stopped()&&Date.now()<k;){if(!w||e.proc!==w||w.exitCode!==null||w.signalCode!==null)return{ok:!1,error:me(e)};if(await ye(e,C=>C===$,h))return pe(e,"observed"),e.buildFailed=!1,{ok:!0};await sleep(le)}return{ok:!1,error:t.stopped()?"the gateway is stopping":`no pod of the new tree answered within ${Math.round(t.startupTimeoutMs/1e3)}s`}}finally{g&&b()}},"run")().catch(g=>({ok:!1,error:g.message})).then(g=>{i.done={ok:g.ok,at:Date.now(),...e.generation!==void 0?{generation:e.generation}:{},...g.error?{error:g.error}:{}},g.ok&&(e.lastUsed=i.done.at);const w=Math.round((i.done.at-i.startedAt)/100)/10;g.ok?console.log(`[gateway] restarted: ${e.project} \u2014 g${i.done.generation??"?"} serving after ${w}s (${a}).`):console.error(`[gateway] restart failed: ${e.project} \u2014 ${g.error} (${a}, after ${w}s).`),v(e,g.ok?"info":"error",g.ok?`restarted: ${e.project}`:`restart failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-restart",phase:g.ok?"done":"failed",project:e.project,how:a,reason:r.reason,...i.fromGeneration!==void 0?{fromGeneration:i.fromGeneration}:{},...i.done.generation!==void 0?{generation:i.done.generation}:{},durationMs:i.done.at-i.startedAt,...g.error?{error:g.error}:{},...u})}),{restart:i,already:!1}},"restartService"),Je=s(e=>{if(!e.instanceBuiltins)return;const r=(0,instance_1.readInstanceEnvStrict)(e.root,e.instanceBuiltins);if("reason"in r)return{refused:`the instance variables of ${e.project} could not be read again \u2014 ${r.reason}. Nothing was restarted: the running tree keeps what it has. Fix the file, then restart it again.`};const o=e.instanceEnv??{};return{env:r.env,applied:{fingerprint:(0,instance_1.instanceEnvFingerprint)(r.env),fromFingerprint:(0,instance_1.instanceEnvFingerprint)(o),changed:(0,instance_1.changedInstanceEnvNames)(o,r.env)}}},"restartEnv"),me=s(e=>e.buildFailed?`its build failed${e.lastErrors?.length?`: ${e.lastErrors[e.lastErrors.length-1]}`:""}`:e.proc&&(e.proc.exitCode!==null||e.proc.signalCode!==null)?`its tree exited (${e.proc.exitCode??e.proc.signalCode})`:!e.proc&&e.state==="down"?"its tree exited before a pod answered":`no pod answered within ${Math.round(t.startupTimeoutMs/1e3)}s`,"startFailure"),xe=s(e=>{const r=Date.now();e.restarts=(e.restarts??[]).filter(n=>r-n<types_1.SERVICE_RESTART_WINDOW_MS);const o=(e.wedgeRecycles??0)+1;if(e.restarts.length>=types_1.SERVICE_MAX_RESTARTS||o>types_1.SERVICE_MAX_RESTARTS){const n=e.restarts.length>=types_1.SERVICE_MAX_RESTARTS?`wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min`:`recycled ${types_1.SERVICE_MAX_RESTARTS}\xD7 in a row and no pod came up`;v(e,"error",`service crash-looping: ${e.project} \u2014 ${n}, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project,recycles:o-1}),V(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.wedgeRecycles=o,e.restarts.push(r),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; attempt ${o}/${types_1.SERVICE_MAX_RESTARTS} with no pod up since the first, ${e.restarts.length} in the last minute; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),v(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:o,inWindow:e.restarts.length}),V(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.()},"recycleWedged"),Qe={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let _e;const se=new Map,Ee=new Map;let ke=!1,je=0,fe=0;const Ze=s(()=>{const e=t.projectRoots??{},r=new Set(t.services.map(n=>n.project));for(const n of t.routes)n.lens&&r.add(n.lens.project);const o=new Set;for(const n of r){const d=e[n];if(!d)continue;const a=path.relative(t.root,d).split(path.sep).filter(Boolean)[0];a&&!a.startsWith("..")&&o.add(a)}return[...o]},"managedBuckets"),et=s((e,r,o,n)=>{const d=t.projectSourceRoots?.[e];if(d?.length)return d;const a=t.projectRoots?.[e];if(!a)return;const i=sourceScopeDirs(t.root,a,o,r,n);return i.length>0?i:void 0},"scopeFor"),tt=s(()=>{if(ke||Date.now()-je<types_1.POD_STALE_SCAN_MS)return;ke=!0,je=Date.now();const e=je;(async()=>{const r=(0,scope_1.readSourceSetOptions)(t.root),o=r.exclude,n={ignoreTests:r.ignoreTests},d=sourceScanPass(t.root,o,n),a=await d.snapshot(),i=Date.now(),u=sourceChangeWindowMs(i,fe,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),f=_e?diffSourceSets(_e,a,i,u):null;if(f&&(t.sourceSetChangedAt=i,t.sourceSetChange=f,H("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:f.added,removed:f.removed,truncated:f.truncated})),_e=a,!t.projectRoots){fe=e;return}let g;try{g=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(S=>S.isDirectory()).map(S=>S.name)}catch{return}const w=Ze(),$=new Set(t.services.map(S=>S.project));for(const S of t.routes)S.lens&&$.add(S.lens.project);const h=new Map;for(const S of $){const P=et(S,g,w,o);if(!P)continue;const x=P.join("|"),q=h.get(x)??{dirs:P,projects:[]};q.projects.push(S),h.set(x,q)}const k=Date.now(),C=sourceChangeWindowMs(k,fe,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);fe=e;const G=s((S,P,x)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const q of S)t.sourceSetChangedFor.set(q,P),t.sourceSetChangesFor.set(q,x)},"stampScoped"),ne=new Set([...h.values()].flatMap(S=>S.projects));for(const S of[...t.sourceSetScoped??[]])ne.has(S)||t.sourceSetScoped.delete(S);for(const[S,{dirs:P,projects:x}]of h){const q=await d.snapshot(P),Re=se.get(S);if(Re||x.some(K=>Ee.has(K))){t.sourceSetScoped??=new Set;for(const K of x)t.sourceSetScoped.add(K)}se.set(S,q);const Ae=new Map;for(const K of x){const U=Ee.get(K);if(U===void 0||U===S)continue;const ee=Ae.get(U)??[];ee.push(K),Ae.set(U,ee)}for(const K of x)Ee.set(K,S);for(const[K,U]of Ae){const ee=se.get(K),ie=ee?diffSourceSets(ee,q,k,C):f;ie&&(G(U,ee?k:i,ie),H("info",`source file set changed \u2014 ${U.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:U,added:ie.added,removed:ie.removed,truncated:ie.truncated}))}if(!Re)continue;const ae=diffSourceSets(Re,q,k,C);ae&&(G(x,k,ae),H("info",`source file set changed in ${x.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:x,added:ae.added,removed:ae.removed,truncated:ae.truncated}))}for(const S of[...se.keys()])h.has(S)||se.delete(S)})().catch(()=>{}).finally(()=>{ke=!1})},"maybeScanSourceSet"),rt=s((e,r)=>{const o=podChangeClock(t,e.project),n=o.change,d=`${o.scope==="project"?"in its scope":"in the workspace (its scope is not known)"}: ${changeEvidence(n)}`,a=n?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,i=podWatcherVerdict(e.watcher,r,a),u=s(g=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${g} \u2014 no recycle.`)),!1),"keep");if(i==="heard")return e.staleVerifiedAt=r,v(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:i}),u(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`);if(i==="degraded")return console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0;const f=s(g=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${g}; the change ${d} \u2014 recycling.`)),!0),"recycling");return i==="silent"?types_1.POD_WATCHER_SILENT==="keep"?u("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):f("watcher alive but quiet across the change"):types_1.POD_WATCHER_UNVERIFIED==="keep"?u("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):f("watcher unverifiable (no testimony yet \u2014 a pod that never rebuilt, or an older devserver bundle)")},"podStaleRecycleAllowed"),ot=s((e,r)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){$e(e,r);return}console.log(`[gateway] zombie recycle: ${e.project} (${r}) \u2014 stale fs view; fresh pod incoming`),v(e,"warning",`zombie recycle: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:r});const o=!!e.decl.eager;V(e,`${r} \u2014 recycling stale pod`,{announce:!1}),o&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&Y(e)},1500).unref?.()},"recyclePod"),st=s(()=>{const e=setInterval(()=>{ct().catch(r=>console.error("[gateway] sweep error:",r))},t.sweepMs);return e.unref(),e},"startSweeper"),nt=l.listProcesses??(()=>{try{return(0,orphan_reaper_1.parsePsRows)((0,node_child_process_1.execFileSync)("ps",orphan_reaper_1.PS_ROW_ARGS,{encoding:"utf8",timeout:5e3}))}catch{return[]}}),Pe=l.signalProcess??((e,r)=>{process.kill(e,r)});let be=0;const at=s(()=>{if(typeof process.getuid!="function")return 0;const e=t.registry?t.registry.allServices():t.services;if(e.length===0)return 0;const r=e.map(a=>({label:a.wsKey||path.basename(a.root),project:a.project,root:a.root,svc:a})),o=new Set;for(const a of e)a.proc?.pid&&o.add(a.proc.pid);for(const a of[...he.keys(),...N.keys(),...I.keys()])a.pid&&o.add(a.pid);const n=[...new Set((0,process_name_1.defaultLinkDirs)().flatMap(a=>{try{return[a,fs.realpathSync(a)]}catch{return[a]}}))];let d=0;for(const a of(0,orphan_reaper_1.findOrphanExecutors)(nt(),r,{linkDirs:n,uid:process.getuid(),selfPid:process.pid,ownGroups:o})){const{row:i}=a;try{Pe(a.group?-i.pgid:i.pid,"SIGKILL")}catch(f){if(f.code!=="ESRCH"||!a.group)continue;try{Pe(i.pid,"SIGKILL")}catch{continue}}d+=1;const u=a.owner.svc;console.log(`[gateway] reaped an orphaned executor of ${u.project} (${a.role}, pid ${i.pid}${a.group?`, group ${i.pgid}`:""}, parent gone) \u2014 a stopped tree it outlived: ${i.command.slice(0,160)}`),v(u,"warning",`orphan reap: ${u.project} (pid ${i.pid})`,`cluster-service:${u.project}`,{kind:"orphan-executor-reap",project:u.project,pid:i.pid,pgid:i.pgid,role:a.role,group:a.group})}return d},"reapOrphanExecutors"),ct=s(async()=>{{if(t.stopped())return;He(),l.reapForeign!==!1&&types_1.ORPHAN_REAP_EVERY_MS>0&&Date.now()-be>=types_1.ORPHAN_REAP_EVERY_MS&&(be=Date.now(),at());for(const e of t.graphStores?.all()??[])e.refreshIfStale();types_1.POD_RECYCLE_ENABLED&&tt();for(const e of t.registry?t.registry.allServices():t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const r=path.resolve(e.root)===path.resolve(t.root),o=(0,warm_start_1.serviceIdleSec)(t,e),n=(0,awake_1.idleStopAt)(e,o);if(n!==void 0&&e.state==="up"&&(!e.restart||e.restart.done)&&e.inflight===0&&Date.now()>n){e.lastIdleStop={at:Date.now(),why:`no request for ${o}s${(e.awakeUntil??0)>e.lastUsed?" after its hold ended":""}`},V(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const d=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,a=O(e.pool)>0;if(a||d){if(e.wedgedSince=void 0,a&&pe(e,"observed"),a&&!d&&types_1.POD_RECYCLE_ENABLED&&r){const i=podChangeClock(t,e.project).at;await De(e);const u=(e.staleVerifiedAt??0)>=i&&i>0,f=podRecycleReason(e,Date.now(),u?0:i,Qe);if(f==="stale-source-set"&&!rt(e,i))continue;f&&ot(e,f)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,xe(e))}}},"sweepOnce");return{scanNow:O,spawnService:Y,ensureUp:we,scaleUp:Ke,killSpawned:V,markUp:pe,emitSvc:v,startSweeper:st,rollPod:$e,restartService:ze,recycleWedged:xe,stopRetiringTrees:Ye,finishDyingTrees:Fe,stopBuildHost:s(e=>e===void 0?j.stop():j.release(e),"stopBuildHost")}}_(createServiceLayer,"createServiceLayer"),s(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,c,l){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(c!==void 0&&c!==t)&&t!==l)return t}_(chooseStalePodKillTarget,"chooseStalePodKillTarget"),s(chooseStalePodKillTarget,"chooseStalePodKillTarget");
|
|
@@ -25,9 +25,14 @@ export interface Observability {
|
|
|
25
25
|
*/
|
|
26
26
|
startHeadlessFlow(req: http.IncomingMessage, route: Route, trace: GwTrace | undefined, receivedAt: number): GwTrace | undefined;
|
|
27
27
|
traceStep(t: GwTrace | undefined, stepName: string, info?: Record<string, unknown>): void;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
/**
|
|
29
|
+
* `route` identifies the OWNING workspace, so a guest's request narration goes to its own bus. `status` is what the
|
|
30
|
+
* caller was SENT — `undefined` when it went away before any response head (foodguard-118): the span then reads
|
|
31
|
+
* «no response», never the 200 an unsent response defaults to.
|
|
32
|
+
*/
|
|
33
|
+
finishTrace(t: GwTrace | undefined, project: string, status: number | undefined, extra?: Record<string, unknown>, route?: Route): void;
|
|
34
|
+
/** `status` `undefined`: the caller went away before any response — timed, but not an upstream error. */
|
|
35
|
+
recordEdge(route: Route, ms: number, status: number | undefined, caller?: string): void;
|
|
31
36
|
/** Start the periodic traffic-edge flush; returns the interval (already `.unref()`'d). */
|
|
32
37
|
startEdgeFlusher(): NodeJS.Timeout;
|
|
33
38
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`),
|
|
3
|
-
`;busHygiene?.maybeRotateEventFile(n,h.length),E(n,h)}catch{}},"emit"),S=d(e=>{const
|
|
1
|
+
"use strict";var b=Object.defineProperty;var g=(s,u)=>b(s,"name",{value:u,configurable:!0});var D=Object.defineProperty,d=g((s,u)=>D(s,"name",{value:u,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EDGE_DURATION_SAMPLES=exports.LENS_DASHBOARD_PROJECT=void 0,exports.createObservability=createObservability;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),headless_flow_1=require("@lensmcp/core/headless-flow"),http_exchange_1=require("@lensmcp/core/http-exchange"),redact_1=require("@lensmcp/core/redact"),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})();exports.LENS_DASHBOARD_PROJECT="(lens-dashboard)",exports.EDGE_DURATION_SAMPLES=64;function ownerOf(s){return s?.svc?{root:s.svc.root,wsKey:s.svc.wsKey}:s?.lens?{...s.lens.workspaceRoot?{root:s.lens.workspaceRoot}:{},...s.lens.workspaceKey?{wsKey:s.lens.workspaceKey}:{}}:{}}g(ownerOf,"ownerOf"),d(ownerOf,"ownerOf");function busFileFor(s,u){const{root:l}=ownerOf(s);if(!l)return;const w=path.join(l,".lensmcp","events.jsonl");return w===u?void 0:w}g(busFileFor,"busFileFor"),d(busFileFor,"busFileFor");const hdr=d(s=>typeof s=="string"&&s!==""?s:Array.isArray(s)?s[0]:void 0,"hdr"),lensRequestId=d(s=>hdr(s.headers[http_exchange_1.LENS_REQUEST_ID_HEADER])??hdr(s.headers["x-request-id"]),"lensRequestId");function createObservability(s,u){const l=d(()=>Date.now().toString(36)+Math.random().toString(36).slice(2,12),"eid"),w=busHygiene?.createEventFloodGuard({burst:30,perSec:5}),f=new Map,E=d((e,r)=>{try{const t=f.get(e);t&&(fs.appendFileSync(e,JSON.stringify({id:l(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:"warning",context:{sessionId:"pending"},fingerprint:"cluster-gateway:bus-dropped",title:`lens bus: ${t.count} event${t.count===1?"":"s"} could not be written since ${new Date(t.since).toISOString()} (${t.code}) \u2014 they are lost`,raw:{kind:"bus-events-dropped",project:"gateway",dropped:t.count,since:t.since,until:Date.now(),code:t.code}})+`
|
|
2
|
+
`),f.delete(e),console.warn(`[gateway] the lens bus ${e} takes events again \u2014 ${t.count} were lost (${t.code})`)),fs.appendFileSync(e,r)}catch(t){const a=f.get(e);if(a){a.count+=1;return}const o=t?.code??"EIO";f.set(e,{count:1,since:Date.now(),code:o}),console.warn(`[gateway] the lens bus ${e} cannot be written (${o}) \u2014 counting the events lost until it can`)}},"appendCounted"),y=d((e,r,t,a,o,i)=>{const n=i??s;try{const c=w?w.admit(t):{ok:!0,suppressed:0};if(!c.ok)return;fs.mkdirSync(path.dirname(n),{recursive:!0});const p=a&&(0,redact_1.redactDeep)(a),h=JSON.stringify({id:l(),sessionId:"pending",timestamp:Date.now(),source:"gateway",category:"cluster",severity:e,context:(0,redact_1.redactDeep)({sessionId:"pending",...o}),fingerprint:t,title:(0,redact_1.safeTitle)(r),raw:c.suppressed>0?{...p,floodSuppressed:c.suppressed}:p})+`
|
|
3
|
+
`;busHygiene?.maybeRotateEventFile(n,h.length),E(n,h)}catch{}},"emit"),S=d(e=>{const r=hdr(e.headers["x-lensmcp-flow-id"]);let t=lensRequestId(e);if(!(!r&&!t&&!hdr(e.headers.traceparent)))return t||(t=l(),e.headers["x-request-id"]=t),{startedAt:Date.now(),method:(e.method??"GET").toUpperCase(),host:(e.headers.host||"").split(":")[0],url:e.url??"/",steps:[],...r?{flowId:r}:{},...t?{requestId:t}:{},...hdr(e.headers["x-lensmcp-parent-request-id"])?{parentRequestId:hdr(e.headers["x-lensmcp-parent-request-id"])}:{},...hdr(e.headers["x-lensmcp-origin-node-id"])?{originNodeId:hdr(e.headers["x-lensmcp-origin-node-id"])}:{}}},"beginTrace"),v=d((e,r,t,a)=>{if(t?.flowId||!r.pool||r.internal||r.project===exports.LENS_DASHBOARD_PROJECT||!(0,headless_flow_1.headlessFlowsEnabled)())return t;const o=e.url??"/";if((0,headless_flow_1.isProbePath)(o))return t;const i=lensRequestId(e);if(!i)return t;const n=(0,headless_flow_1.headlessFlowId)(i);e.headers["x-lensmcp-flow-id"]=n;const c=(e.method??"GET").toUpperCase(),p=(e.headers.host||"").split(":")[0],h=`request:${c} ${o.split("?")[0]}`,I=t??{startedAt:a,method:c,host:p,url:o,steps:[{step:"received",atMs:0,host:p,method:c,url:o}],requestId:i};return I.flowId=n,I.originNodeId=h,y("info",`${c} ${o.split("?")[0]}`,`gateway-origin:${r.project}`,{kind:"flow-origin",originNodeId:h,project:r.project,host:p,method:c,url:o,...ownerOf(r).wsKey?{wsKey:ownerOf(r).wsKey}:{}},{flowId:n,requestId:i,originType:"request",originNodeId:h},busFileFor(r,s)),I},"startHeadlessFlow"),_=d((e,r,t)=>{e&&e.steps.push({step:r,atMs:Date.now()-e.startedAt,...t})},"traceStep"),$=d((e,r,t,a,o)=>{if(!e||e.done||(e.done=!0,r===exports.LENS_DASHBOARD_PROJECT&&(t===void 0||t<500)))return;const i=Date.now()-e.startedAt;y(t===void 0?"warning":t>=500?"error":"info",t===void 0?`gateway ${e.method} ${e.host}${e.url} \u2192 no response: the caller went away after ${i}ms`:`gateway ${e.method} ${e.host}${e.url} \u2192 ${t} (${i}ms)`,`gateway-request:${r}`,{kind:"gateway-request",project:r,host:e.host,method:e.method,url:e.url,...t===void 0?{}:{status:t},durationMs:i,startedAt:e.startedAt,steps:e.steps,...ownerOf(o).wsKey?{wsKey:ownerOf(o).wsKey}:{},...a},{...e.flowId?{flowId:e.flowId}:{},...e.requestId?{requestId:e.requestId}:{},...e.parentRequestId?{parentRequestId:e.parentRequestId}:{},...e.originNodeId?{originNodeId:e.originNodeId}:{}},busFileFor(o,s))},"finishTrace"),m=new Map;return{emit:y,eid:l,beginTrace:S,startHeadlessFlow:v,traceStep:_,finishTrace:$,recordEdge:d((e,r,t,a)=>{if(e.project===exports.LENS_DASHBOARD_PROJECT&&(t===void 0||t<500&&t!==0))return;const o=e.host??"(default)",i=(a?a+"\u21D2":"")+o+"\u2192"+e.project,n=m.get(i)??{host:o,project:e.project,caller:a,count:0,totalMs:0,errors:0,durations:[],...busFileFor(e,s)?{busFile:busFileFor(e,s)}:{},...ownerOf(e).wsKey?{wsKey:ownerOf(e).wsKey}:{}};if(n.count+=1,n.totalMs+=r,n.durations.length<exports.EDGE_DURATION_SAMPLES)n.durations.push(Math.round(r));else{const c=Math.floor(Math.random()*n.count);c<exports.EDGE_DURATION_SAMPLES&&(n.durations[c]=Math.round(r))}t!==void 0&&(t>=500||t===0)&&(n.errors+=1),m.set(i,n)},"recordEdge"),startEdgeFlusher:d(()=>{const e=setInterval(()=>{for(const r of m.values()){const t=Math.round(r.totalMs/r.count);y(r.errors?"warning":"info",`traffic ${r.host} \u2192 ${r.project}: ${r.count} req, ~${t}ms${r.errors?`, ${r.errors} errors`:""}`,`gateway-traffic:${r.host}->${r.project}`,{kind:"gateway-traffic",host:r.host,project:r.project,caller:r.caller,count:r.count,avgMs:t,errors:r.errors,durationsMs:r.durations,...r.wsKey?{wsKey:r.wsKey}:{}},void 0,r.busFile)}m.clear()},u.trafficFlushMs);return e.unref(),e},"startEdgeFlusher")}}g(createObservability,"createObservability"),d(createObservability,"createObservability");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ee=Object.defineProperty;var D=(o,c)=>ee(o,"name",{value:c,configurable:!0});var Z=Object.defineProperty,s=D((o,c)=>Z(o,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.upstreamOpen=upstreamOpen,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"),gc_notice_1=require("../../../gc-notice"),service_keys_1=require("./service-keys");function isConnError(o){const c=o.code??"";return c==="ECONNREFUSED"||c==="ECONNRESET"||c==="ETIMEDOUT"||c==="EPIPE"||c==="EHOSTUNREACH"||c==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(o.message)}D(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(o){if(!IDEMPOTENT_METHODS.has((o.method??"GET").toUpperCase())||o.headers["transfer-encoding"]!==void 0)return!1;const c=o.headers["content-length"];return!(c!==void 0&&String(c).trim()!=="0")}D(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(o,c){return c.headersSent||c.writableEnded||c.destroyed===!0||o.aborted===!0}D(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"];function returnRequestId(o,c){if(o["x-request-id"]!==void 0)return;const u=c.headers["x-request-id"],q=Array.isArray(u)?u[0]:u;typeof q=="string"&&q!==""&&(o["x-request-id"]=q)}D(returnRequestId,"returnRequestId"),s(returnRequestId,"returnRequestId");const httpProxy=require("http-proxy");function declaredFirstByteMs(o){const c=Number(o);return Number.isFinite(c)&&c>0?c:types_1.POD_RESPONSE_TIMEOUT_MS}D(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");const secs=s(o=>`${(o/1e3).toFixed(1)}s`,"secs"),upstreams=new WeakMap;function upstreamOpen(o){const c=upstreams.get(o),u=c?.socket;return!!c&&!c.destroyed&&!!u&&!u.destroyed}D(upstreamOpen,"upstreamOpen"),s(upstreamOpen,"upstreamOpen");function wedgedMessage(o,c,u){const q=u.failure==="gone"?"its process is gone":u.failure==="silent"?`it did not answer its liveness check for ${secs(u.silentMs)}`:"it gave no liveness answer";return`Pod ${o} of ${c} sent no response for ${secs(u.heldMs)} and ${q} \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.`}D(wedgedMessage,"wedgedMessage"),s(wedgedMessage,"wedgedMessage");function armFirstByteGuard(o){let c,u=!1,q=!1,V=!1,k,x=0,E=(0,body_stall_1.requestBodyArriving)(o.req)?void 0:Date.now();const N=s(()=>{E??=Date.now()},"onBodyEnd");E===void 0&&o.req.once("end",N);const H=s(()=>(E===void 0&&!(0,body_stall_1.requestBodyArriving)(o.req)&&(E=Date.now()),E===void 0?0:Date.now()-E),"heldMs"),A=s(T=>{c=setTimeout(C,T),c.unref?.()},"arm"),C=s(()=>{if(u||!o.pending())return;const T=H();if(o.ceilingMs<=0){if(E!==void 0&&T>=o.firstByteMs){o.onWedged(`no response within ${T}ms (wedged socket) \u2014 evicted + respawning`,{heldMs:T,silentMs:0,probes:0,failure:"deadline"});return}A(E===void 0?o.firstByteMs:o.firstByteMs-T);return}(0,pod_probe_1.probePodAliveVerdict)(o.socketPath,void 0,o.serviceKey).then(({alive:L,failure:b})=>{if(u||!o.pending())return;const j=H();if(!L){if(b==="gone"&&o.upstreamOpen?.()){if(j>=o.ceilingMs){o.onCeiling(`retiring (its socket is gone) and no response within ${j}ms (ceiling ${o.ceilingMs}ms) \u2014 giving up on this request, pod kept`);return}V||(V=!0,o.onRetiring?.(j)),A(Math.max(o.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS));return}if(x+=1,b==="silent"&&(0,gc_notice_1.inAnnouncedGc)(o.socketPath)){k=void 0,A(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}if(b==="silent"&&types_1.POD_WEDGE_CONFIRM_MS>0&&(k??=Date.now(),Date.now()-k<types_1.POD_WEDGE_CONFIRM_MS)){A(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}const B=k===void 0?0:Date.now()-k,I=b==="gone"?"its socket no longer accepts (the pod process is gone)":b==="silent"?`nothing answered on ${types_1.POD_ALIVE_PATH} for ${B}ms (${x} probe${x===1?"":"s"})`:`no liveness answer on ${types_1.POD_ALIVE_PATH}`;o.onWedged(`no response within ${j}ms and ${I} (wedged socket) \u2014 evicted + respawning`,{heldMs:j,silentMs:B,probes:x,failure:b??"no-route"});return}if(k=void 0,j>=o.ceilingMs){o.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${j}ms (ceiling ${o.ceilingMs}ms) \u2014 giving up on this request, pod kept`,L);return}!q&&E!==void 0&&(q=!0,o.onSlow(j,L)),A(Math.max(o.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return A(o.firstByteMs),{clear:s(()=>{u=!0,c&&clearTimeout(c),o.req.removeListener("end",N)},"clear")}}D(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(o,c,u){const{recordEdge:q,finishTrace:V,traceStep:k,emit:x}=c,E=s((e,r,t,d)=>{const g=d.kind==="pod-evicted";x(g?"error":"warning",`${g?"pod evicted":"request given up"}: ${e.project} (${t}) \u2014 ${d.status}`,`cluster-pod-incident:${e.project}`,{kind:d.kind,project:e.project,pod:t,status:d.status,reason:d.reason,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0],...d.detail??{}})},"reportPodIncident"),N=s(async(e,r)=>{const t=e.pool;if(!t)return;t.scannedAt=0;let d=(0,discovery_1.pickSock)(t,o.scanTtlMs);return!d&&e.svc&&(k(r,"cold-start",{project:e.project}),await u.ensureUp(e.svc)&&(t.scannedAt=0,d=(0,discovery_1.pickSock)(t,o.scanTtlMs))),d},"anotherPod"),H=s((e,r,t,d,g)=>{console.log(`[gateway] pod ${t} of ${e.project} is gone \u2014 its ${r.method} was not answered, sending it to ${d}`),x("info",`request moved to another pod: ${e.project} (${t} \u2192 ${d})`,`cluster-pod-moved:${e.project}`,{kind:"pod-gone-rerouted",project:e.project,pod:t,to:d,reason:g,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0]})},"reportPodMoved"),A=s(async e=>{if(!e.lens||!o.lensSupervisor)return"unsupported";let r;const t=new Promise(f=>{r=setTimeout(()=>f("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),d=o.lensSupervisor.recover(e.lens,"proxy-connection").then(f=>f?"ready":"failed").catch(()=>"failed"),g=await Promise.race([d,t]);r&&clearTimeout(r);const S=o.lensSupervisor.status(e.lens);return g==="failed"&&(!S||S.state==="ready")?"unsupported":g},"waitForManagedRecovery"),C=s((e,r,t,d,g,S)=>{if(t.headersSent||t.writableEnded||t.destroyed){t.destroyed||t.destroy();return}const f=e.lens?o.lensSupervisor?.status(e.lens):void 0,$=S==="timeout"||f?.state==="starting"||f?.state==="replacing",P=(0,gateway_errors_1.ensureRequestId)(r),l=e.lens?{project:e.project,reason:f?.reason??"connection-failure",generation:f?.generation??0,...$?{retryAfterMs:2e3}:{}}:void 0,i=$?503:502,y=$?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${P} project=${e.project} target=${String(d)} state=${f?.state??"unmanaged"} generation=${f?.generation??0}: ${g.message}`),x("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:P,project:e.project,target:String(d),state:f?.state??"unmanaged",generation:f?.generation,error:g.message}),(0,edge_1.sendError)(t,r,i,y,$?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,l,$?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),T=(0,body_stall_1.createBodyStallGuard)(o.bodyStallMs??types_1.BODY_STALL_MS),L={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},b=new http.Agent(L),j=new(require("node:https")).Agent(L),B=s(e=>typeof e=="string"&&e.startsWith("https:")?j:b,"agentForTarget"),I=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:b}),Y=s((e,r,t)=>{const d=s(()=>{t()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",d),e.once("close",()=>r.removeListener("close",d))},"propagateEdgeCancel");I.on("proxyReq",(e,r,t)=>{upstreams.set(r,e);for(const d of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(d);e.setHeader("connection","keep-alive"),Y(e,t,()=>t.writableEnded)}),I.on("proxyRes",(e,r,t)=>{const d=r.__lensmcpPod;d&&(e.headers["x-lensmcp-pod"]=d),delete e.headers["x-powered-by"],e.headers.server="LensMCP",returnRequestId(e.headers,r),t&&Y(e,t,()=>t.writableEnded),t&&T.armResponse(t)});const Q=s((e,r,t,d)=>{(0,edge_1.stampForwardingHeaders)(o,r),T.arm(r);const g={},S=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[n,a]of Object.entries(r.headers))a===void 0||n.startsWith(":")||HOP_BY_HOP.has(n)||S&&(n==="content-length"||n==="transfer-encoding")||(g[n]=a);let f=http,$;if(typeof e=="object")$={socketPath:e.socketPath,path:r.url,method:r.method,headers:g,agent:b};else{const n=new URL(e),a=n.protocol==="https:";f=a?require("node:https"):http,g.host=n.host,$={protocol:n.protocol,hostname:n.hostname,port:n.port||(a?443:80),path:r.url,method:r.method,headers:g,agent:B(e)}}const P=t;P.__lensmcpErrContained||(P.__lensmcpErrContained=!0,t.on("error",()=>{}));let l;const i=s(()=>{t.writableEnded||p.destroy()},"onReqGone"),y=s(()=>{l&&!l.destroyed&&l.destroy()},"onEdgeClose"),_=s(()=>{r.removeListener("error",i),r.removeListener("aborted",i),r.removeListener("close",i),t.removeListener("close",y)},"detach"),p=f.request($,n=>{if(l=n,d.onResponse(),t.headersSent||t.writableEnded||t.destroyed){n.destroy();return}const a={};for(const[O,U]of Object.entries(n.headers))U===void 0||HOP_BY_HOP.has(O)||(a[O]=U);delete a["x-powered-by"],a.server="LensMCP",returnRequestId(a,r);const w=r.__lensmcpPod;w&&(a["x-lensmcp-pod"]=w);try{t.writeHead(n.statusCode??502,a)}catch(O){console.error("[gateway] h2 relay: bad response header, resetting stream:",O.message),n.destroy(),t.destroyed||t.destroy(O);return}n.pipe(t),T.armResponse(t);const R=s(()=>{!t.writableEnded&&!t.destroyed&&t.destroy()},"abortEdge");n.once("error",R),n.once("close",R),t.on("close",y)});p.once("error",n=>d.onError(n)),upstreams.set(r,p),p.once("close",_),r.on("error",i),r.on("aborted",i),r.on("close",i),S?p.end():r.pipe(p)},"pipeUpstream"),X=s(async(e,r,t)=>{const d=r.__lensmcpTrace;if(e.pool){const l=e.svc;l&&(l.lastUsed=Date.now(),l.inflight+=1,t.on("close",()=>{l.inflight-=1,l.lastUsed=Date.now()}));let i=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs);if(!i&&l&&(k(d,"cold-start",{project:e.project}),await u.ensureUp(l)&&(i=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs))),l&&i&&u.markUp(l,"observed"),l&&u.scaleUp(l),!i){const _=l?.lastErrors?.length?` Service errors: ${l.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(t,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 y=s((_,p)=>{const n=path.basename(_,".sock");r.__lensmcpPod=n,k(d,"pod-select",{pod:n,pods:e.pool.socks.length,strategy:"round-robin",...p?{movedFrom:p}:{}});let a=!1;const w=s((h,M="unlink")=>{if(e.pool.socks=e.pool.socks.filter(m=>m!==_),e.pool.idx=-1,M==="unlink")try{fs.unlinkSync(_)}catch{}console.error(`[gateway] pod ${n} of ${e.project}: ${h}`)},"evictPod"),R=armFirstByteGuard({socketPath:_,serviceKey:o.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(()=>!a&&!t.headersSent&&!t.writableEnded,"pending"),upstreamOpen:s(()=>upstreamOpen(r),"upstreamOpen"),onRetiring:s(h=>{console.log(`[gateway] pod ${n} of ${e.project}: retiring (its socket is gone) but still holds this ${r.method} after ${h}ms \u2014 waiting for it, not evicting`)},"onRetiring"),onWedged:s((h,M)=>{a=!0,w(h),E(e,r,n,{kind:"pod-evicted",status:502,reason:h,detail:M}),(0,edge_1.sendError)(t,r,502,"unavailable",wedgedMessage(n,e.project,M))},"onWedged"),onCeiling:s(h=>{a=!0,w(h,"keep"),E(e,r,n,{kind:"request-gave-up",status:504,reason:h}),(0,edge_1.sendError)(t,r,504,"unavailable",`Pod ${n} 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((h,M)=>{console.log(`[gateway] pod ${n} of ${e.project}: slow first byte, pod alive (${h}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")});t.on("close",()=>R.clear());const O=_,U=isReplayableRequest(r);let W=0;const F=s(()=>{W+=1,Q({socketPath:O},r,t,{onResponse:s(()=>{R.clear(),a=!0},"onResponse"),onError:s(h=>{if(!a){if(edgeGone(r,t)){R.clear(),a=!0;return}(0,pod_probe_1.podConnectionVerdict)(O,h).then(M=>{if(!a){if(M==="alive"&&W<2&&U&&!edgeGone(r,t)){console.log(`[gateway] pod ${n} of ${e.project}: ${h.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),F();return}if(R.clear(),a=!0,M==="gone"){w(h.message);const m=s(()=>{E(e,r,n,{kind:"pod-evicted",status:502,reason:`${h.message} \u2014 its socket no longer accepts (the pod process is gone)${p?`; the request had already been moved here from ${p}`:""}`}),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${n} of ${e.project} is gone (${h.message}) \u2014 retry.`)},"giveUp");if(p||!U){m();return}N(e,d).then(v=>{if(!edgeGone(r,t)){if(!v){m();return}H(e,r,n,path.basename(v,".sock"),h.message),y(v,n)}},m);return}console.error(`[gateway] pod ${n} of ${e.project}: ${h.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${n} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");F()},"onPod");y(i);return}const g=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let S=0,f=!1;const $=isReplayableRequest(r),P=s(()=>{if(edgeGone(r,t))return;const l=e.target;Q(l,r,t,{onResponse:s(()=>{},"onResponse"),onError:s(i=>{if(isConnError(i)&&(S+=1),e.lens&&isConnError(i)&&$&&S>=2&&!f&&!edgeGone(r,t)){f=!0,A(e).then(y=>{if(!edgeGone(r,t)){if(y==="ready"){P();return}if(y==="unsupported"&&Date.now()<g){setTimeout(P,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}C(e,r,t,e.target,i,y)}});return}if(isConnError(i)&&$&&Date.now()<g&&!edgeGone(r,t)){setTimeout(P,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(t.headersSent||t.writableEnded||t.destroyed){t.destroyed||t.destroy();return}C(e,r,t,l,i)},"onError")})},"tryTcp");P()},"forwardNative");return{proxy:I,forward:s(async(e,r,t)=>{const d=Date.now(),g=r.__lensmcpTrace;if(t.on("close",()=>{q(e,Date.now()-d,t.statusCode??0,r.__lensmcpCaller),V(g,e.project,t.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)(o,r),T.arm(r),(r.httpVersionMajor??1)>=2){await X(e,r,t);return}if(e.pool){const i=e.svc;i&&(i.lastUsed=Date.now(),i.inflight+=1,t.on("close",()=>{i.inflight-=1,i.lastUsed=Date.now()}));let y=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs);if(!y&&i&&(k(g,"cold-start",{project:e.project}),await u.ensureUp(i)&&(y=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs))),i&&y&&u.markUp(i,"observed"),i&&u.scaleUp(i),!y){const p=i?.lastErrors?.length?` Service errors: ${i.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(t,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${p} The watcher recovers and the next request retries automatically.`);return}const _=s((p,n)=>{const a=path.basename(p,".sock");r.__lensmcpPod=a,k(g,"pod-select",{pod:a,pods:e.pool.socks.length,strategy:"round-robin",...n?{movedFrom:n}:{}});let w=!1;const R=s((m,v="unlink")=>{if(e.pool.socks=e.pool.socks.filter(G=>G!==p),e.pool.idx=-1,v==="unlink")try{fs.unlinkSync(p)}catch{}console.error(`[gateway] pod ${a} of ${e.project}: ${m}`)},"evictPod"),O=armFirstByteGuard({socketPath:p,serviceKey:o.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(()=>!w&&!t.headersSent&&!t.writableEnded,"pending"),upstreamOpen:s(()=>upstreamOpen(r),"upstreamOpen"),onRetiring:s(m=>{console.log(`[gateway] pod ${a} of ${e.project}: retiring (its socket is gone) but still holds this ${r.method} after ${m}ms \u2014 waiting for it, not evicting`)},"onRetiring"),onWedged:s((m,v)=>{w=!0,R(m),E(e,r,a,{kind:"pod-evicted",status:502,reason:m,detail:v}),(0,edge_1.sendError)(t,r,502,"unavailable",wedgedMessage(a,e.project,v))},"onWedged"),onCeiling:s(m=>{w=!0,R(m,"keep"),E(e,r,a,{kind:"request-gave-up",status:504,reason:m}),(0,edge_1.sendError)(t,r,504,"unavailable",`Pod ${a} 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,v)=>{console.log(`[gateway] pod ${a} of ${e.project}: slow first byte, pod alive (${m}ms, ${v.inflight} in-flight, ${v.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});t.on("close",()=>O.clear());const U=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),W=p,F=isReplayableRequest(r);let h=0;const M=s(()=>{h+=1;const m=Date.now();I.web(r,t,{target:{socketPath:W},agent:b,proxyTimeout:U},v=>{if(!w){if(edgeGone(r,t)){O.clear(),w=!0;return}(0,pod_probe_1.podConnectionVerdict)(W,v).then(G=>{if(w)return;const J=Date.now()-m>=U-250;if(G==="alive"&&h<2&&F&&!J&&!edgeGone(r,t)){console.log(`[gateway] pod ${a} of ${e.project}: ${v.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),M();return}if(O.clear(),w=!0,G==="gone"){R(v.message);const K=s(()=>{E(e,r,a,{kind:"pod-evicted",status:502,reason:`${v.message} \u2014 its socket no longer accepts (the pod process is gone)${n?`; the request had already been moved here from ${n}`:""}`}),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${a} of ${e.project} is gone (${v.message}) \u2014 retry.`)},"giveUp");if(n||!F||J){K();return}N(e,g).then(z=>{if(!edgeGone(r,t)){if(!z){K();return}H(e,r,a,path.basename(z,".sock"),v.message),_(z,a)}},K);return}console.error(`[gateway] pod ${a} of ${e.project}: ${v.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${a} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");M()},"onPod");_(y);return}const S=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let f=0,$=!1;const P=isReplayableRequest(r),l=s(()=>{if(edgeGone(r,t))return;const i=e.target,y={target:i,autoRewrite:typeof i=="string",changeOrigin:!0,agent:B(i)};I.web(r,t,y,_=>{if(isConnError(_)&&(f+=1),e.lens&&isConnError(_)&&P&&f>=2&&!$&&!edgeGone(r,t)){$=!0,A(e).then(p=>{if(!edgeGone(r,t)){if(p==="ready"){l();return}if(p==="unsupported"&&Date.now()<S){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}C(e,r,t,e.target,_,p)}});return}if(isConnError(_)&&P&&Date.now()<S&&!edgeGone(r,t)){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}t.destroyed||C(e,r,t,i,_)})},"tryTcp");l()},"forward"),agentForTarget:B,destroyAgents:s(()=>{T.stop(),b.destroy(),j.destroy()},"destroyAgents"),closeProxy:s(()=>{try{I.close?.()}catch{}},"closeProxy")}}D(createProxy,"createProxy"),s(createProxy,"createProxy");
|
|
1
|
+
"use strict";var re=Object.defineProperty;var R=(o,i)=>re(o,"name",{value:i,configurable:!0});var q=Object.defineProperty,s=R((o,i)=>q(o,"name",{value:i,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.upstreamOpen=upstreamOpen,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"),gc_notice_1=require("../../../gc-notice"),service_keys_1=require("./service-keys");function isConnError(o){const i=o.code??"";return i==="ECONNREFUSED"||i==="ECONNRESET"||i==="ETIMEDOUT"||i==="EPIPE"||i==="EHOSTUNREACH"||i==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(o.message)}R(isConnError,"isConnError"),s(isConnError,"isConnError");function isDroppedConnection(o){const i=o.code??"";return i==="ECONNRESET"||i==="EPIPE"||/ECONNRESET|socket hang up|EPIPE/i.test(o.message)}R(isDroppedConnection,"isDroppedConnection"),s(isDroppedConnection,"isDroppedConnection");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(o){if(!IDEMPOTENT_METHODS.has((o.method??"GET").toUpperCase())||o.headers["transfer-encoding"]!==void 0)return!1;const i=o.headers["content-length"];return!(i!==void 0&&String(i).trim()!=="0")}R(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(o,i){return i.headersSent||i.writableEnded||i.destroyed===!0||o.aborted===!0}R(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"];function returnRequestId(o,i){if(o["x-request-id"]!==void 0)return;const g=i.headers["x-request-id"],x=Array.isArray(g)?g[0]:g;typeof x=="string"&&x!==""&&(o["x-request-id"]=x)}R(returnRequestId,"returnRequestId"),s(returnRequestId,"returnRequestId");const httpProxy=require("http-proxy");function declaredFirstByteMs(o){const i=Number(o);return Number.isFinite(i)&&i>0?i:types_1.POD_RESPONSE_TIMEOUT_MS}R(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");const secs=s(o=>`${(o/1e3).toFixed(1)}s`,"secs"),upstreams=new WeakMap;function upstreamOpen(o){const i=upstreams.get(o),g=i?.socket;return!!i&&!i.destroyed&&!!g&&!g.destroyed}R(upstreamOpen,"upstreamOpen"),s(upstreamOpen,"upstreamOpen");function wedgedMessage(o,i,g){const x=g.failure==="gone"?"its process is gone":g.failure==="silent"?`it did not answer its liveness check for ${secs(g.silentMs)}`:"it gave no liveness answer";return`Pod ${o} of ${i} sent no response for ${secs(g.heldMs)} and ${x} \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.`}R(wedgedMessage,"wedgedMessage"),s(wedgedMessage,"wedgedMessage");function armFirstByteGuard(o){let i,g=!1,x=!1,K=!1,k,C=0,_=(0,body_stall_1.requestBodyArriving)(o.req)?void 0:Date.now();const H=s(()=>{_??=Date.now()},"onBodyEnd");_===void 0&&o.req.once("end",H);const W=s(()=>(_===void 0&&!(0,body_stall_1.requestBodyArriving)(o.req)&&(_=Date.now()),_===void 0?0:Date.now()-_),"heldMs"),I=s(b=>{i=setTimeout(L,b),i.unref?.()},"arm"),L=s(()=>{if(g||!o.pending())return;const b=W();if(o.ceilingMs<=0){if(_!==void 0&&b>=o.firstByteMs){o.onWedged(`no response within ${b}ms (wedged socket) \u2014 evicted + respawning`,{heldMs:b,silentMs:0,probes:0,failure:"deadline"});return}I(_===void 0?o.firstByteMs:o.firstByteMs-b);return}(0,pod_probe_1.probePodAliveVerdict)(o.socketPath,void 0,o.serviceKey).then(({alive:B,failure:j})=>{if(g||!o.pending())return;const D=W();if(!B){if(j==="gone"&&o.upstreamOpen?.()){if(D>=o.ceilingMs){o.onCeiling(`retiring (its socket is gone) and no response within ${D}ms (ceiling ${o.ceilingMs}ms) \u2014 giving up on this request, pod kept`);return}K||(K=!0,o.onRetiring?.(D)),I(Math.max(o.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS));return}if(C+=1,j==="silent"&&(0,gc_notice_1.inAnnouncedGc)(o.socketPath)){k=void 0,I(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}if(j==="silent"&&types_1.POD_WEDGE_CONFIRM_MS>0&&(k??=Date.now(),Date.now()-k<types_1.POD_WEDGE_CONFIRM_MS)){I(types_1.POD_ALIVE_PROBE_TIMEOUT_MS);return}const F=k===void 0?0:Date.now()-k,U=j==="gone"?"its socket no longer accepts (the pod process is gone)":j==="silent"?`nothing answered on ${types_1.POD_ALIVE_PATH} for ${F}ms (${C} probe${C===1?"":"s"})`:`no liveness answer on ${types_1.POD_ALIVE_PATH}`;o.onWedged(`no response within ${D}ms and ${U} (wedged socket) \u2014 evicted + respawning`,{heldMs:D,silentMs:F,probes:C,failure:j??"no-route"});return}if(k=void 0,D>=o.ceilingMs){o.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${D}ms (ceiling ${o.ceilingMs}ms) \u2014 giving up on this request, pod kept`,B);return}!x&&_!==void 0&&(x=!0,o.onSlow(D,B)),I(Math.max(o.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return I(o.firstByteMs),{clear:s(()=>{g=!0,i&&clearTimeout(i),o.req.removeListener("end",H)},"clear")}}R(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(o,i,g){const{recordEdge:x,finishTrace:K,traceStep:k,emit:C}=i,_=s((e,r,t,c)=>{const h=c.kind==="pod-evicted";C(h?"error":"warning",`${h?"pod evicted":"request given up"}: ${e.project} (${t}) \u2014 ${c.status}`,`cluster-pod-incident:${e.project}`,{kind:c.kind,project:e.project,pod:t,status:c.status,reason:c.reason,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0],...c.detail??{}})},"reportPodIncident"),H=s(async(e,r)=>{const t=e.pool;if(!t)return;t.scannedAt=0;let c=(0,discovery_1.pickSock)(t,o.scanTtlMs);return!c&&e.svc&&(k(r,"cold-start",{project:e.project}),await g.ensureUp(e.svc)&&(t.scannedAt=0,c=(0,discovery_1.pickSock)(t,o.scanTtlMs))),c},"anotherPod"),W=s((e,r,t,c,h)=>{console.log(`[gateway] pod ${t} of ${e.project} is gone \u2014 its ${r.method} was not answered, sending it to ${c}`),C("info",`request moved to another pod: ${e.project} (${t} \u2192 ${c})`,`cluster-pod-moved:${e.project}`,{kind:"pod-gone-rerouted",project:e.project,pod:t,to:c,reason:h,traceId:(0,gateway_errors_1.ensureRequestId)(r),method:r.method,path:(r.url??"").split("?")[0]})},"reportPodMoved"),I=s(async e=>{if(!e.lens||!o.lensSupervisor)return"unsupported";let r;const t=new Promise(y=>{r=setTimeout(()=>y("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),c=o.lensSupervisor.recover(e.lens,"proxy-connection").then(y=>y?"ready":"failed").catch(()=>"failed"),h=await Promise.race([c,t]);r&&clearTimeout(r);const $=o.lensSupervisor.status(e.lens);return h==="failed"&&(!$||$.state==="ready")?"unsupported":h},"waitForManagedRecovery"),L=s((e,r,t,c,h,$)=>{if(t.headersSent||t.writableEnded||t.destroyed){t.destroyed||t.destroy();return}const y=e.lens?o.lensSupervisor?.status(e.lens):void 0,w=$==="timeout"||y?.state==="starting"||y?.state==="replacing",P=(0,gateway_errors_1.ensureRequestId)(r),M=e.lens?{project:e.project,reason:y?.reason??"connection-failure",generation:y?.generation??0,...w?{retryAfterMs:2e3}:{}}:void 0,l=w?503:502,d=w?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${P} project=${e.project} target=${String(c)} state=${y?.state??"unmanaged"} generation=${y?.generation??0}: ${h.message}`),C("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:P,project:e.project,target:String(c),state:y?.state??"unmanaged",generation:y?.generation,error:h.message}),(0,edge_1.sendError)(t,r,l,d,w?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,M,w?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),b=(0,body_stall_1.createBodyStallGuard)(o.bodyStallMs??types_1.BODY_STALL_MS),B={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},j=new http.Agent(B),D=new(require("node:https")).Agent(B),F=s(e=>typeof e=="string"&&e.startsWith("https:")?D:j,"agentForTarget"),U=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:j}),J=s((e,r,t)=>{const c=s(()=>{t()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",c),e.once("close",()=>r.removeListener("close",c))},"propagateEdgeCancel");U.on("proxyReq",(e,r,t)=>{upstreams.set(r,e);for(const h of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(h);const c=e.agent?.keepAlive!==!1;e.setHeader("connection",c?"keep-alive":"close"),J(e,t,()=>t.writableEnded)}),U.on("proxyRes",(e,r,t)=>{const c=r.__lensmcpPod;c&&(e.headers["x-lensmcp-pod"]=c),delete e.headers["x-powered-by"],e.headers.server="LensMCP",returnRequestId(e.headers,r),t&&J(e,t,()=>t.writableEnded),t&&b.armResponse(t)});const X=s((e,r,t,c,h=!1)=>{(0,edge_1.stampForwardingHeaders)(o,r),b.arm(r);const $={},y=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[n,a]of Object.entries(r.headers))a===void 0||n.startsWith(":")||HOP_BY_HOP.has(n)||y&&(n==="content-length"||n==="transfer-encoding")||($[n]=a);let w=http,P;if(typeof e=="object")P={socketPath:e.socketPath,path:r.url,method:r.method,headers:$,agent:j};else{const n=new URL(e),a=n.protocol==="https:";w=a?require("node:https"):http,$.host=n.host,P={protocol:n.protocol,hostname:n.hostname,port:n.port||(a?443:80),path:r.url,method:r.method,headers:$,agent:h?!1:F(e)}}const M=t;M.__lensmcpErrContained||(M.__lensmcpErrContained=!0,t.on("error",()=>{}));let l;const d=s(()=>{t.writableEnded||p.destroy()},"onReqGone"),u=s(()=>{l&&!l.destroyed&&l.destroy()},"onEdgeClose"),m=s(()=>{r.removeListener("error",d),r.removeListener("aborted",d),r.removeListener("close",d),t.removeListener("close",u)},"detach"),p=w.request(P,n=>{if(l=n,c.onResponse(),t.headersSent||t.writableEnded||t.destroyed){n.destroy();return}const a={};for(const[O,N]of Object.entries(n.headers))N===void 0||HOP_BY_HOP.has(O)||(a[O]=N);delete a["x-powered-by"],a.server="LensMCP",returnRequestId(a,r);const S=r.__lensmcpPod;S&&(a["x-lensmcp-pod"]=S);try{t.writeHead(n.statusCode??502,a)}catch(O){console.error("[gateway] h2 relay: bad response header, resetting stream:",O.message),n.destroy(),t.destroyed||t.destroy(O);return}n.pipe(t),b.armResponse(t);const A=s(()=>{!t.writableEnded&&!t.destroyed&&t.destroy()},"abortEdge");n.once("error",A),n.once("close",A),t.on("close",u)});p.once("error",n=>c.onError(n)),upstreams.set(r,p),p.once("close",m),r.on("error",d),r.on("aborted",d),r.on("close",d),y?p.end():r.pipe(p)},"pipeUpstream"),ee=s(async(e,r,t)=>{const c=r.__lensmcpTrace;if(e.pool){const l=e.svc;l&&(l.lastUsed=Date.now(),l.inflight+=1,t.on("close",()=>{l.inflight-=1,l.lastUsed=Date.now()}));let d=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs);if(!d&&l&&(k(c,"cold-start",{project:e.project}),await g.ensureUp(l)&&(d=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs))),l&&d&&g.markUp(l,"observed"),l&&g.scaleUp(l),!d){const m=l?.lastErrors?.length?` Service errors: ${l.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(t,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 u=s((m,p)=>{const n=path.basename(m,".sock");r.__lensmcpPod=n,k(c,"pod-select",{pod:n,pods:e.pool.socks.length,strategy:"round-robin",...p?{movedFrom:p}:{}});let a=!1;const S=s((f,T="unlink")=>{if(e.pool.socks=e.pool.socks.filter(v=>v!==m),e.pool.idx=-1,T==="unlink")try{fs.unlinkSync(m)}catch{}console.error(`[gateway] pod ${n} of ${e.project}: ${f}`)},"evictPod"),A=armFirstByteGuard({socketPath:m,serviceKey:o.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(()=>!a&&!t.headersSent&&!t.writableEnded,"pending"),upstreamOpen:s(()=>upstreamOpen(r),"upstreamOpen"),onRetiring:s(f=>{console.log(`[gateway] pod ${n} of ${e.project}: retiring (its socket is gone) but still holds this ${r.method} after ${f}ms \u2014 waiting for it, not evicting`)},"onRetiring"),onWedged:s((f,T)=>{a=!0,S(f),_(e,r,n,{kind:"pod-evicted",status:502,reason:f,detail:T}),(0,edge_1.sendError)(t,r,502,"unavailable",wedgedMessage(n,e.project,T))},"onWedged"),onCeiling:s(f=>{a=!0,S(f,"keep"),_(e,r,n,{kind:"request-gave-up",status:504,reason:f}),(0,edge_1.sendError)(t,r,504,"unavailable",`Pod ${n} 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((f,T)=>{console.log(`[gateway] pod ${n} of ${e.project}: slow first byte, pod alive (${f}ms, ${T.inflight} in-flight, ${T.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});t.on("close",()=>A.clear());const O=m,N=isReplayableRequest(r);let G=0;const V=s(()=>{G+=1,X({socketPath:O},r,t,{onResponse:s(()=>{A.clear(),a=!0},"onResponse"),onError:s(f=>{if(!a){if(edgeGone(r,t)){A.clear(),a=!0;return}(0,pod_probe_1.podConnectionVerdict)(O,f).then(T=>{if(!a){if(T==="alive"&&G<2&&N&&!edgeGone(r,t)){console.log(`[gateway] pod ${n} of ${e.project}: ${f.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),V();return}if(A.clear(),a=!0,T==="gone"){S(f.message);const v=s(()=>{_(e,r,n,{kind:"pod-evicted",status:502,reason:`${f.message} \u2014 its socket no longer accepts (the pod process is gone)${p?`; the request had already been moved here from ${p}`:""}`}),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${n} of ${e.project} is gone (${f.message}) \u2014 retry.`)},"giveUp");if(p||!N){v();return}H(e,c).then(E=>{if(!edgeGone(r,t)){if(!E){v();return}W(e,r,n,path.basename(E,".sock"),f.message),u(E,n)}},v);return}console.error(`[gateway] pod ${n} of ${e.project}: ${f.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${n} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");V()},"onPod");u(d);return}const h=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let $=0,y=!1;const w=isReplayableRequest(r);let P=!1;const M=s((l=!1)=>{if(edgeGone(r,t))return;const d=e.target;X(d,r,t,{onResponse:s(()=>{},"onResponse"),onError:s(u=>{if(isConnError(u)&&($+=1),e.lens&&isConnError(u)&&w&&$>=2&&!y&&!edgeGone(r,t)){y=!0,I(e).then(m=>{if(!edgeGone(r,t)){if(m==="ready"){M();return}if(m==="unsupported"&&Date.now()<h){setTimeout(M,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}L(e,r,t,e.target,u,m)}});return}if(isConnError(u)&&w&&Date.now()<h&&!edgeGone(r,t)){setTimeout(M,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(isDroppedConnection(u)&&w&&!P&&!t.headersSent&&!edgeGone(r,t)){P=!0,console.log(`[gateway] ${e.project}: ${u.message} after the heal window \u2014 sending the ${r.method} again on a fresh connection`),M(!0);return}if(t.headersSent||t.writableEnded||t.destroyed){t.destroyed||t.destroy();return}L(e,r,t,d,u)},"onError")},l)},"tryTcp");M()},"forwardNative");return{proxy:U,forward:s(async(e,r,t)=>{const c=Date.now(),h=r.__lensmcpTrace;if(t.on("close",()=>{const d=t.headersSent,u=d?t.statusCode??0:void 0,m=d?t.writableFinished?{}:{aborted:"during-response",error:"the caller went away before the response ended"}:{aborted:"before-response",error:"the caller went away before any response was sent"};x(e,Date.now()-c,u,r.__lensmcpCaller),K(h,e.project,u,{...m,...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)(o,r),b.arm(r),(r.httpVersionMajor??1)>=2){await ee(e,r,t);return}if(e.pool){const d=e.svc;d&&(d.lastUsed=Date.now(),d.inflight+=1,t.on("close",()=>{d.inflight-=1,d.lastUsed=Date.now()}));let u=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs);if(!u&&d&&(k(h,"cold-start",{project:e.project}),await g.ensureUp(d)&&(u=(0,discovery_1.pickSock)(e.pool,o.scanTtlMs))),d&&u&&g.markUp(d,"observed"),d&&g.scaleUp(d),!u){const p=d?.lastErrors?.length?` Service errors: ${d.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(t,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${p} The watcher recovers and the next request retries automatically.`);return}const m=s((p,n)=>{const a=path.basename(p,".sock");r.__lensmcpPod=a,k(h,"pod-select",{pod:a,pods:e.pool.socks.length,strategy:"round-robin",...n?{movedFrom:n}:{}});let S=!1;const A=s((v,E="unlink")=>{if(e.pool.socks=e.pool.socks.filter(Y=>Y!==p),e.pool.idx=-1,E==="unlink")try{fs.unlinkSync(p)}catch{}console.error(`[gateway] pod ${a} of ${e.project}: ${v}`)},"evictPod"),O=armFirstByteGuard({socketPath:p,serviceKey:o.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(()=>!S&&!t.headersSent&&!t.writableEnded,"pending"),upstreamOpen:s(()=>upstreamOpen(r),"upstreamOpen"),onRetiring:s(v=>{console.log(`[gateway] pod ${a} of ${e.project}: retiring (its socket is gone) but still holds this ${r.method} after ${v}ms \u2014 waiting for it, not evicting`)},"onRetiring"),onWedged:s((v,E)=>{S=!0,A(v),_(e,r,a,{kind:"pod-evicted",status:502,reason:v,detail:E}),(0,edge_1.sendError)(t,r,502,"unavailable",wedgedMessage(a,e.project,E))},"onWedged"),onCeiling:s(v=>{S=!0,A(v,"keep"),_(e,r,a,{kind:"request-gave-up",status:504,reason:v}),(0,edge_1.sendError)(t,r,504,"unavailable",`Pod ${a} 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((v,E)=>{console.log(`[gateway] pod ${a} of ${e.project}: slow first byte, pod alive (${v}ms, ${E.inflight} in-flight, ${E.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});t.on("close",()=>O.clear());const N=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),G=p,V=isReplayableRequest(r);let f=0;const T=s(()=>{f+=1;const v=Date.now();U.web(r,t,{target:{socketPath:G},agent:j,proxyTimeout:N},E=>{if(!S){if(edgeGone(r,t)){O.clear(),S=!0;return}(0,pod_probe_1.podConnectionVerdict)(G,E).then(Y=>{if(S)return;const Z=Date.now()-v>=N-250;if(Y==="alive"&&f<2&&V&&!Z&&!edgeGone(r,t)){console.log(`[gateway] pod ${a} of ${e.project}: ${E.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),T();return}if(O.clear(),S=!0,Y==="gone"){A(E.message);const Q=s(()=>{_(e,r,a,{kind:"pod-evicted",status:502,reason:`${E.message} \u2014 its socket no longer accepts (the pod process is gone)${n?`; the request had already been moved here from ${n}`:""}`}),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${a} of ${e.project} is gone (${E.message}) \u2014 retry.`)},"giveUp");if(n||!V||Z){Q();return}H(e,h).then(z=>{if(!edgeGone(r,t)){if(!z){Q();return}W(e,r,a,path.basename(z,".sock"),E.message),m(z,a)}},Q);return}console.error(`[gateway] pod ${a} of ${e.project}: ${E.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${a} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");T()},"onPod");m(u);return}const $=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let y=0,w=!1;const P=isReplayableRequest(r);let M=!1;const l=s((d=!1)=>{if(edgeGone(r,t))return;const u=e.target,m={target:u,autoRewrite:typeof u=="string",changeOrigin:!0,agent:d?!1:F(u)};U.web(r,t,m,p=>{if(isConnError(p)&&(y+=1),e.lens&&isConnError(p)&&P&&y>=2&&!w&&!edgeGone(r,t)){w=!0,I(e).then(n=>{if(!edgeGone(r,t)){if(n==="ready"){l();return}if(n==="unsupported"&&Date.now()<$){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}L(e,r,t,e.target,p,n)}});return}if(isConnError(p)&&P&&Date.now()<$&&!edgeGone(r,t)){setTimeout(l,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(isDroppedConnection(p)&&P&&!M&&!t.headersSent&&!edgeGone(r,t)){M=!0,console.log(`[gateway] ${e.project}: ${p.message} after the heal window \u2014 sending the ${r.method} again on a fresh connection`),l(!0);return}t.destroyed||L(e,r,t,u,p)})},"tryTcp");l()},"forward"),agentForTarget:F,destroyAgents:s(()=>{b.stop(),j.destroy(),D.destroy()},"destroyAgents"),closeProxy:s(()=>{try{U.close?.()}catch{}},"closeProxy")}}R(createProxy,"createProxy"),s(createProxy,"createProxy");
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var fe=Object.defineProperty;var T=(r,t)=>fe(r,"name",{value:t,configurable:!0});var ye=Object.defineProperty,o=T((r,t)=>ye(r,"name",{value:t,configurable:!0}),"o");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.edgeKeyWait=edgeKeyWait,exports.withBuiltinHooks=withBuiltinHooks,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"),act_1=require("./act"),control_1=require("./control"),workspace_middleware_1=require("./workspace-middleware"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),source_poller_1=require("./source-poller"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),own_log_1=require("./own-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"),manifest_1=require("../manifest"),edge_1=require("./edge"),warm_start_1=require("./warm-start"),types_1=require("./types");function insecureLocalFetch(r,t){return new Promise((i,c)=>{const a=(r.startsWith("https:")?require("node:https"):require("node:http")).request(r,{method:"GET",headers:t?.headers,rejectUnauthorized:!1},_=>{const y=[];_.on("data",h=>y.push(h)),_.on("error",c),_.on("end",()=>{const h=_.statusCode??0,v=Buffer.concat(y).toString("utf8");i({ok:h>=200&&h<300,status:h,json:o(async()=>JSON.parse(v),"json")})})});a.on("error",c),t?.signal?.addEventListener("abort",()=>a.destroy(new Error(`JWKS ${r} \u2192 aborted`)),{once:!0}),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),o(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(r,t={}){const i=process.env.LENSMCP_GW_JWKS_URL,c=process.env.LENSMCP_GW_JWT_ISS,a=new Map;let _,y=!1;const h=o((s,l)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${s}${l}`);const d=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:s,...c?{issuer:c}:{},fetchImpl:t.fetchImpl??insecureLocalFetch});let E=!1;return d.warm({...t.warm,onError:o((M,x,b)=>{E=!0,x===1&&b!==void 0&&console.warn(`[gateway] edge JWKS ${s} not loaded yet (${M.message}) \u2014 retrying in the background`),b===void 0&&console.warn(`[gateway] edge JWKS ${s} still not loaded after ${x} attempts (${M.message}) \u2014 the first token that needs it fetches it`)},"onError")}).then(M=>{M&&E&&console.log(`[gateway] edge JWKS ${s} loaded`)}),d},"make"),v=o(s=>{let l=a.get(s);return l||(l=h(`https://auth.${s}/jwks.json`,` (auto-derived for apex ${s})`),a.set(s,l)),l},"forApex");return{for(s){if(i)return _??=h(i,"");const l=s.split(":")[0].toLowerCase();let d;for(const E of idpApexes(r()))(l===E||l.endsWith(`.${E}`))&&(!d||E.length>d.length)&&(d=E);return d?v(d):void 0},warm(){if(!y){if(i){_??=h(i,"");return}for(const s of idpApexes(r()))v(s)}},stop(){y=!0,_?.stop();for(const s of a.values())s.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),o(buildEdgeVerifierPool,"buildEdgeVerifierPool");function idpApexes(r){const t=new Set;for(const i of r){const c=i.host;typeof c!="string"||!/^auth\./.test(c)||c.startsWith("internal.")||t.add(c.slice(5))}return t}T(idpApexes,"idpApexes"),o(idpApexes,"idpApexes");function edgeKeyWait(r){return async t=>{const i=t.route;if(!i?.auth||i.internal)return;const{req:c}=t,a=Array.isArray(c.headers.authorization)?c.headers.authorization[0]:c.headers.authorization,_=String(a??"");if(!_.startsWith("Bearer "))return;const y=c.url??"/";if((0,manifest_1.authRuleFor)(i.auth,(0,edge_1.canonicalPath)(y)??y.split("?")[0],(c.method??"GET").toUpperCase()).mode!=="jwt")return;const h=String(c.headers.host??"").split(":")[0].toLowerCase();await r.for(h)?.awaitKey(_.slice(7))}}T(edgeKeyWait,"edgeKeyWait"),o(edgeKeyWait,"edgeKeyWait");function withBuiltinHooks(r,t){return{...r,beforeAuth:[...r.beforeAuth??[],t.keyWait],afterAuth:[t.middleware,...r.afterAuth??[]]}}T(withBuiltinHooks,"withBuiltinHooks"),o(withBuiltinHooks,"withBuiltinHooks"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(r,t){return r&&exports.BENIGN_SOCKET_ERRNOS.has(r)?{status:null,report:!1,cause:"client-abort"}:r==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:r==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:r==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:r?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:r??t??"unknown"}}T(classifyClientError,"classifyClientError"),o(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(r=1e4){const t=new Map;return i=>{const c=Date.now(),a=t.get(i)??0;return c-a<r?!1:(t.set(i,c),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),o(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(r){r.on("error",t=>{exports.BENIGN_SOCKET_ERRNOS.has(t.code??"")||console.warn(`[gateway] inbound socket error (${t.code??t.message}) \u2014 dropping this connection only`),r.destroy()})}T(guardInboundSocket,"guardInboundSocket"),o(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(r,t,i){const c=r.get(t);if(!c)return!1;r.delete(t),i==="re-registered"&&console.log(`[gateway] workspace '${t}' re-registered \u2014 retiring the children of its previous registration first.`);try{c()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${t}' (${i}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),o(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(r,t){if(r===void 0||r.trim()==="")return t;const i=Number(r);return!Number.isFinite(i)||i<0?t:Math.floor(i)}T(parseBudget,"parseBudget"),o(parseBudget,"parseBudget");function resolveMaxConnections(r){return parseBudget(r,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),o(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(r=process.env){return{maxSessions:parseBudget(r.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(r.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(r.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),o(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(r,t){const{NGHTTP2_ENHANCE_YOUR_CALM:i,NGHTTP2_REFUSED_STREAM:c}=require("node:http2").constants,a=new Map;let _=0;const y=o(s=>{s.idle&&(clearTimeout(s.idle),s.idle=void 0)},"disarm"),h=o((s,l)=>{y(l),!(r.idleSessionMs<=0||l.open>0)&&(l.idle=setTimeout(()=>{s.destroyed||s.closed||(t("session-reclaimed",{idleMs:r.idleSessionMs,sessions:a.size}),a.delete(s),s.close())},r.idleSessionMs),l.idle.unref?.())},"arm"),v=o(()=>{for(const[s,l]of a)if(!(l.open>0||s.destroyed))return y(l),a.delete(s),t("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:r.maxSessions}),s.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(s){if(r.maxSessions>0&&a.size>=r.maxSessions&&!v()){t("session-refused",{sessions:a.size,maxSessions:r.maxSessions,streams:_});try{s.goaway(i)}catch{}try{s.close()}catch{}return!1}const l={open:0};return a.set(s,l),h(s,l),s.once("close",()=>{y(l),a.delete(s)}),!0},admitStream(s,l){const d=a.get(s);if(!d)return!0;if(r.maxStreams>0&&_>=r.maxStreams){t("stream-refused",{streams:_,maxStreams:r.maxStreams,sessions:a.size});try{l.close(c)}catch{}return!1}return _+=1,d.open+=1,y(d),l.once("close",()=>{_=Math.max(0,_-1),d.open=Math.max(0,d.open-1),d.open===0&&a.has(s)&&h(s,d)}),!0},dispose(){for(const s of a.values())y(s);a.clear()}}}T(createH2Governor,"createH2Governor"),o(createH2Governor,"createH2Governor");async function startGateway(r,t){const i=r.https!==!1,c=r.ports?.length?r.ports:[443],a=r.scanTtlMs??1500,_=r.startupTimeoutMs??12e4,y=r.sweepMs??1e4,h=r.trafficFlushMs??5e3;let v=!1;const s=process.env.LENSMCP_EVENT_FILE??path.join(t.root,".lensmcp","events.jsonl"),l=(0,own_log_1.startOwnLogRotation)(t.root),d=(0,scope_1.readLensScope)(t.root),E=d.key,M=d.sockNs!==E?d.sockNs:void 0,x=t.projectsConfigurations?.projects??{},b=(0,discovery_1.projectSourceRoots)(t.root,x,t.projectDependencies),{routes:j,services:P,instance:Q}=(0,discovery_1.discoverRoutes)(t.root,x,void 0,E,b,M,d.instance);if(j.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const K=new route_registry_1.RouteRegistry;K.register({wsKey:E,...M?{sockNs:M}:{},root:t.root,routes:j,services:P,...Q?{instance:Q}:{}});const R=[];let Z=o(()=>{},"refreshCert"),U=o(()=>{},"warmEdgeVerifiers");const ee=o(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,K),Z(),U()},"rebuildRoutes");ee();const{serviceKeys:le,keyToProject:ue,keyToWorkspace:de}=(0,service_keys_1.loadServiceKeys)(t.root,P,E),pe=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...r.trustProxyHops!==void 0?{trustProxyHops:r.trustProxyHops}:{},...r.clientIpHeader?{clientIpHeader:r.clientIpHeader}:{},...r.clientIpTrustedProxies?{clientIpTrustedProxies:r.clientIpTrustedProxies}:{}}),g={root:t.root,wsKey:E,...M?{sockNs:M}:{},eventFile:s,routes:R,registry:K,rebuildRoutes:ee,services:P,serviceKeys:le,keyToProject:ue,keyToWorkspace:de,clientIp:pe,bodyStallMs:r.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:_,sweepMs:y,trafficFlushMs:h,https:i,projectRoots:Object.fromEntries(Object.entries(t.projectsConfigurations?.projects??{}).map(([e,n])=>[e,path.resolve(t.root,n.root)])),projectSourceRoots:b,...t.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(t.projectGraphStore)}:{},...r.sharedCompiler!==void 0?{sharedCompiler:r.sharedCompiler}:{},stopped:o(()=>v,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(P,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!t.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,r.sharedCompiler),label:E||path.basename(t.root),lens:{key:E,dashboard:r.dashboard!==!1,mcp:r.mcp!==!1&&j.some(e=>!!e.lens)}}));const m=(0,observability_1.createObservability)(s,{trafficFlushMs:h}),_e=createClientErrorLogGate(),L=h2BudgetFromEnv(),ge=createClientErrorLogGate(3e4),D=createH2Governor(L,(e,n)=>{ge(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(n)}). 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).`),m.emit(e==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${e}`,`gateway-h2-budget:${e}`,{kind:"h2-aggregate-budget",event:e,project:"gateway",...n})}),A=(0,lifecycle_1.createServiceLayer)(g,m),O=(0,lens_children_1.createLensChildren)(g,m,r),F=new Map;let re=o(()=>{},"ownBooted");const Se=new Promise(e=>re=e),te=(0,warm_start_1.createWorktreeWarmer)({rt:g,obs:m,ready:Se,ensureUp:o(e=>A.ensureUp(e),"ensureUp")});let B;if(r.middleware){const e=require(path.resolve(t.root,r.middleware));B=e.default??e}const Y=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:E,...B?{owner:B}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()}),I=process.env.LENSMCP_GW_ACT==="0"?void 0:(0,act_1.createActStore)({dir:path.join(path.dirname((0,control_1.controlSocketPath)()),"identities")});let $;try{$=(0,control_1.startControlServer)({rt:g,killService:o((e,n)=>A.killSpawned(e,n),"killService"),restartService:o((e,n)=>A.restartService(e,n),"restartService"),ensureService:o(e=>A.ensureUp(e),"ensureService"),...I?{act:I}:{},onRegister:o(async e=>{retirePreviousWorkspace(F,e.wsKey,"re-registered"),Y.set(e.wsKey,e.root,e.middleware);const n=await O.hostWorkspaceDashboard(e),w=await O.hostWorkspaceApps(e);F.set(e.wsKey,()=>{try{n?.()}finally{w()}}),te.onRegister(e)},"onRegister"),onUnregister:o(e=>{te.onUnregister(e.wsKey),retirePreviousWorkspace(F,e.wsKey,"unregistered"),Y.drop(e.wsKey),A.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const H=(0,proxy_1.createProxy)(g,m,A),W=buildEdgeVerifierPool(()=>g.routes),se=(0,auth_1.createEdgeAuth)(g,m,W,I),he=o(e=>{const n=e.route?.wsKey,w=Y.for(n);if(w)try{const u=n?g.registry.get(n):void 0;w(e.req,{...n?{wsKey:n}:{},...u?{root:u.root}:{},env:u?.instance?.env??{}})}catch(u){m.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:u.message}),e.route&&m.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${u.message}`)}},"builtinMiddleware"),oe=(0,hooks_1.createHooks)(withBuiltinHooks(r.hooks??{},{keyWait:edgeKeyWait(W),middleware:he})),ne=(0,handler_1.createHandler)({rt:g,obs:m,auth:se,proxy:H,hooks:oe}),ae=(0,upgrade_1.createUpgrade)({rt:g,auth:se,proxy:H,hooks:oe,obs:m}),Ee=m.startEdgeFlusher(),me=A.startSweeper(),ie=(0,source_poller_1.createSourcePoller)({services:o(()=>g.registry.allServices(),"services"),serviceKey:o(e=>g.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],"serviceKey"),emit:o((e,n,w,u,S)=>m.emit(n,w,u,{wsKey:e.wsKey,...S},void 0,e.wsKey===g.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl")),"emit"),scanTtlMs:a,stopped:o(()=>v,"stopped")});(0,source_poller_1.sourcesEnabled)()&&ie.start(),await O.bootSingletonsAndApps();const ce=P.filter(e=>e.decl.eager).map(e=>A.ensureUp(e));Promise.allSettled(ce).then(()=>setTimeout(re,ce.length?2e4:0).unref());let k,X,q;if(i){const e=require("../../../basic-ssl"),n=(0,service_keys_1.gatewayCacheDir)(t.root);q=o(()=>e.getCertificateSync(n,"gateway",g.routes.map(w=>w.host).filter(w=>!!w)),"mintCert"),k=q(),X=e.caCertPath()}const J=[],G=[],z=r.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of c){const n=k?require("node:http2").createSecureServer({key:k,cert:k,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:r.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:r.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(u,S)=>ne(u,S)):http.createServer(ne);n.on("upgrade",ae),n.on("error",u=>console.error(`[gateway] port ${e}: ${u.code}`)),n.on("connection",guardInboundSocket),n.on("secureConnection",guardInboundSocket),n.on("tlsClientError",(u,S)=>S.destroy()),n.on("clientError",(u,S)=>{const p=u,f=classifyClientError(p.code,p.message);if(f.report&&_e(f.cause)){const N=S.remoteAddress??"unknown",C=f.status===null?"dropped":String(f.status);console.warn(`[gateway] inbound H1 clientError from ${N}: ${f.cause} (${p.code??p.message}${p.bytesParsed!==void 0?`, bytesParsed=${p.bytesParsed}`:""}) \u2192 ${C}. This status is the GATEWAY's, not the upstream service's.`),m.emit("warning",`inbound clientError: ${f.cause} \u2192 ${C}`,`gateway-client-error:${f.cause}`,{kind:"gateway-client-error",cause:f.cause,code:p.code??null,status:f.status,peer:N,bytesParsed:p.bytesParsed??null})}f.status!==null&&S.writable&&S.end(`HTTP/1.1 ${f.status} ${CLIENT_ERROR_REASON[f.status]}\r
|
|
1
|
+
"use strict";var fe=Object.defineProperty;var T=(r,t)=>fe(r,"name",{value:t,configurable:!0});var ye=Object.defineProperty,o=T((r,t)=>ye(r,"name",{value:t,configurable:!0}),"o");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.edgeKeyWait=edgeKeyWait,exports.withBuiltinHooks=withBuiltinHooks,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"),act_1=require("./act"),control_1=require("./control"),workspace_middleware_1=require("./workspace-middleware"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),source_poller_1=require("./source-poller"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),own_log_1=require("./own-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"),manifest_1=require("../manifest"),edge_1=require("./edge"),warm_start_1=require("./warm-start"),types_1=require("./types");function insecureLocalFetch(r,t){return new Promise((i,c)=>{const a=(r.startsWith("https:")?require("node:https"):require("node:http")).request(r,{method:"GET",headers:t?.headers,rejectUnauthorized:!1},_=>{const m=[];_.on("data",E=>m.push(E)),_.on("error",c),_.on("end",()=>{const E=_.statusCode??0,A=Buffer.concat(m).toString("utf8");i({ok:E>=200&&E<300,status:E,json:o(async()=>JSON.parse(A),"json")})})});a.on("error",c),t?.signal?.addEventListener("abort",()=>a.destroy(new Error(`JWKS ${r} \u2192 aborted`)),{once:!0}),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),o(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(r,t={}){const i=process.env.LENSMCP_GW_JWKS_URL,c=process.env.LENSMCP_GW_JWT_ISS,a=new Map;let _,m=!1;const E=o((s,l)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${s}${l}`);const d=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:s,...c?{issuer:c}:{},fetchImpl:t.fetchImpl??insecureLocalFetch});let y=!1;return d.warm({...t.warm,onError:o((M,x,b)=>{y=!0,x===1&&b!==void 0&&console.warn(`[gateway] edge JWKS ${s} not loaded yet (${M.message}) \u2014 retrying in the background`),b===void 0&&console.warn(`[gateway] edge JWKS ${s} still not loaded after ${x} attempts (${M.message}) \u2014 the first token that needs it fetches it`)},"onError")}).then(M=>{M&&y&&console.log(`[gateway] edge JWKS ${s} loaded`)}),d},"make"),A=o(s=>{let l=a.get(s);return l||(l=E(`https://auth.${s}/jwks.json`,` (auto-derived for apex ${s})`),a.set(s,l)),l},"forApex");return{for(s){if(i)return _??=E(i,"");const l=s.split(":")[0].toLowerCase();let d;for(const y of idpApexes(r()))(l===y||l.endsWith(`.${y}`))&&(!d||y.length>d.length)&&(d=y);return d?A(d):void 0},warm(){if(!m){if(i){_??=E(i,"");return}for(const s of idpApexes(r()))A(s)}},stop(){m=!0,_?.stop();for(const s of a.values())s.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),o(buildEdgeVerifierPool,"buildEdgeVerifierPool");function idpApexes(r){const t=new Set;for(const i of r){const c=i.host;typeof c!="string"||!/^auth\./.test(c)||c.startsWith("internal.")||t.add(c.slice(5))}return t}T(idpApexes,"idpApexes"),o(idpApexes,"idpApexes");function edgeKeyWait(r){return async t=>{const i=t.route;if(!i?.auth||i.internal)return;const{req:c}=t,a=Array.isArray(c.headers.authorization)?c.headers.authorization[0]:c.headers.authorization,_=String(a??"");if(!_.startsWith("Bearer "))return;const m=c.url??"/";if((0,manifest_1.authRuleFor)(i.auth,(0,edge_1.canonicalPath)(m)??m.split("?")[0],(c.method??"GET").toUpperCase()).mode!=="jwt")return;const E=String(c.headers.host??"").split(":")[0].toLowerCase();await r.for(E)?.awaitKey(_.slice(7))}}T(edgeKeyWait,"edgeKeyWait"),o(edgeKeyWait,"edgeKeyWait");function withBuiltinHooks(r,t){return{...r,beforeAuth:[...r.beforeAuth??[],t.keyWait],afterAuth:[t.middleware,...r.afterAuth??[]]}}T(withBuiltinHooks,"withBuiltinHooks"),o(withBuiltinHooks,"withBuiltinHooks"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(r,t){return r&&exports.BENIGN_SOCKET_ERRNOS.has(r)?{status:null,report:!1,cause:"client-abort"}:r==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:r==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:r==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:r?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:r??t??"unknown"}}T(classifyClientError,"classifyClientError"),o(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(r=1e4){const t=new Map;return i=>{const c=Date.now(),a=t.get(i)??0;return c-a<r?!1:(t.set(i,c),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),o(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(r){r.on("error",t=>{exports.BENIGN_SOCKET_ERRNOS.has(t.code??"")||console.warn(`[gateway] inbound socket error (${t.code??t.message}) \u2014 dropping this connection only`),r.destroy()})}T(guardInboundSocket,"guardInboundSocket"),o(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(r,t,i){const c=r.get(t);if(!c)return!1;r.delete(t),i==="re-registered"&&console.log(`[gateway] workspace '${t}' re-registered \u2014 retiring the children of its previous registration first.`);try{c()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${t}' (${i}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),o(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(r,t){if(r===void 0||r.trim()==="")return t;const i=Number(r);return!Number.isFinite(i)||i<0?t:Math.floor(i)}T(parseBudget,"parseBudget"),o(parseBudget,"parseBudget");function resolveMaxConnections(r){return parseBudget(r,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),o(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(r=process.env){return{maxSessions:parseBudget(r.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(r.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(r.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),o(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(r,t){const{NGHTTP2_ENHANCE_YOUR_CALM:i,NGHTTP2_REFUSED_STREAM:c}=require("node:http2").constants,a=new Map;let _=0;const m=o(s=>{s.idle&&(clearTimeout(s.idle),s.idle=void 0)},"disarm"),E=o((s,l)=>{m(l),!(r.idleSessionMs<=0||l.open>0)&&(l.idle=setTimeout(()=>{s.destroyed||s.closed||(t("session-reclaimed",{idleMs:r.idleSessionMs,sessions:a.size}),a.delete(s),s.close())},r.idleSessionMs),l.idle.unref?.())},"arm"),A=o(()=>{for(const[s,l]of a)if(!(l.open>0||s.destroyed))return m(l),a.delete(s),t("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:r.maxSessions}),s.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(s){if(r.maxSessions>0&&a.size>=r.maxSessions&&!A()){t("session-refused",{sessions:a.size,maxSessions:r.maxSessions,streams:_});try{s.goaway(i)}catch{}try{s.close()}catch{}return!1}const l={open:0};return a.set(s,l),E(s,l),s.once("close",()=>{m(l),a.delete(s)}),!0},admitStream(s,l){const d=a.get(s);if(!d)return!0;if(r.maxStreams>0&&_>=r.maxStreams){t("stream-refused",{streams:_,maxStreams:r.maxStreams,sessions:a.size});try{l.close(c)}catch{}return!1}return _+=1,d.open+=1,m(d),l.once("close",()=>{_=Math.max(0,_-1),d.open=Math.max(0,d.open-1),d.open===0&&a.has(s)&&E(s,d)}),!0},dispose(){for(const s of a.values())m(s);a.clear()}}}T(createH2Governor,"createH2Governor"),o(createH2Governor,"createH2Governor");async function startGateway(r,t){const i=r.https!==!1,c=r.ports?.length?r.ports:[443],a=r.scanTtlMs??1500,_=r.startupTimeoutMs??12e4,m=r.sweepMs??1e4,E=r.trafficFlushMs??5e3;let A=!1;const s=process.env.LENSMCP_EVENT_FILE??path.join(t.root,".lensmcp","events.jsonl"),l=(0,own_log_1.startOwnLogRotation)(t.root),d=(0,scope_1.readLensScope)(t.root),y=d.key,M=d.sockNs!==y?d.sockNs:void 0,x=t.projectsConfigurations?.projects??{},b=(0,discovery_1.projectSourceRoots)(t.root,x,t.projectDependencies),{routes:G,services:P,instance:Z}=(0,discovery_1.discoverRoutes)(t.root,x,void 0,y,b,M,d.instance);if(G.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const K=new route_registry_1.RouteRegistry;K.register({wsKey:y,...M?{sockNs:M}:{},root:t.root,routes:G,services:P,...Z?{instance:Z}:{}});const R=[];let ee=o(()=>{},"refreshCert"),U=o(()=>{},"warmEdgeVerifiers");const re=o(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,K),ee(),U()},"rebuildRoutes");re();const{serviceKeys:le,keyToProject:ue,keyToWorkspace:de}=(0,service_keys_1.loadServiceKeys)(t.root,P,y),pe=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...r.trustProxyHops!==void 0?{trustProxyHops:r.trustProxyHops}:{},...r.clientIpHeader?{clientIpHeader:r.clientIpHeader}:{},...r.clientIpTrustedProxies?{clientIpTrustedProxies:r.clientIpTrustedProxies}:{}}),S={root:t.root,wsKey:y,...M?{sockNs:M}:{},eventFile:s,routes:R,registry:K,rebuildRoutes:re,services:P,serviceKeys:le,keyToProject:ue,keyToWorkspace:de,clientIp:pe,bodyStallMs:r.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:_,sweepMs:m,trafficFlushMs:E,https:i,projectRoots:Object.fromEntries(Object.entries(t.projectsConfigurations?.projects??{}).map(([e,n])=>[e,path.resolve(t.root,n.root)])),projectSourceRoots:b,...t.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(t.projectGraphStore)}:{},...r.sharedCompiler!==void 0?{sharedCompiler:r.sharedCompiler}:{},stopped:o(()=>A,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(P,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!t.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,r.sharedCompiler),label:y||path.basename(t.root),lens:{key:y,dashboard:r.dashboard!==!1,mcp:r.mcp!==!1&&G.some(e=>!!e.lens)}}));const w=(0,observability_1.createObservability)(s,{trafficFlushMs:E}),_e=createClientErrorLogGate(),L=h2BudgetFromEnv(),Se=createClientErrorLogGate(3e4),D=createH2Governor(L,(e,n)=>{Se(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(n)}). 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",...n})}),v=(0,lifecycle_1.createServiceLayer)(S,w),O=(0,lens_children_1.createLensChildren)(S,w,r),F=new Map;let te=o(()=>{},"ownBooted");const ge=new Promise(e=>te=e),B=(0,warm_start_1.createWorktreeWarmer)({rt:S,obs:w,ready:ge,ensureUp:o(e=>v.ensureUp(e),"ensureUp")});let Y;if(r.middleware){const e=require(path.resolve(t.root,r.middleware));Y=e.default??e}const X=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:y,...Y?{owner:Y}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()}),I=process.env.LENSMCP_GW_ACT==="0"?void 0:(0,act_1.createActStore)({dir:path.join(path.dirname((0,control_1.controlSocketPath)()),"identities")});let $;try{$=(0,control_1.startControlServer)({rt:S,killService:o((e,n)=>v.killSpawned(e,n),"killService"),restartService:o((e,n)=>v.restartService(e,n),"restartService"),ensureService:o(e=>v.ensureUp(e),"ensureService"),startTurn:o(e=>B.take(e),"startTurn"),emitService:o((e,n,g,u)=>v.emitSvc(e,n,g,`cluster-service:${e.project}`,u),"emitService"),...I?{act:I}:{},onRegister:o(async e=>{retirePreviousWorkspace(F,e.wsKey,"re-registered"),X.set(e.wsKey,e.root,e.middleware);const n=await O.hostWorkspaceDashboard(e),g=await O.hostWorkspaceApps(e);F.set(e.wsKey,()=>{try{n?.()}finally{g()}}),B.onRegister(e)},"onRegister"),onUnregister:o(e=>{B.onUnregister(e.wsKey),retirePreviousWorkspace(F,e.wsKey,"unregistered"),X.drop(e.wsKey),v.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const H=(0,proxy_1.createProxy)(S,w,v),W=buildEdgeVerifierPool(()=>S.routes),se=(0,auth_1.createEdgeAuth)(S,w,W,I),he=o(e=>{const n=e.route?.wsKey,g=X.for(n);if(g)try{const u=n?S.registry.get(n):void 0;g(e.req,{...n?{wsKey:n}:{},...u?{root:u.root}:{},env:u?.instance?.env??{}})}catch(u){w.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:u.message}),e.route&&w.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${u.message}`)}},"builtinMiddleware"),oe=(0,hooks_1.createHooks)(withBuiltinHooks(r.hooks??{},{keyWait:edgeKeyWait(W),middleware:he})),ne=(0,handler_1.createHandler)({rt:S,obs:w,auth:se,proxy:H,hooks:oe}),ae=(0,upgrade_1.createUpgrade)({rt:S,auth:se,proxy:H,hooks:oe,obs:w}),Ee=w.startEdgeFlusher(),me=v.startSweeper(),ie=(0,source_poller_1.createSourcePoller)({services:o(()=>S.registry.allServices(),"services"),serviceKey:o(e=>S.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],"serviceKey"),emit:o((e,n,g,u,h)=>w.emit(n,g,u,{wsKey:e.wsKey,...h},void 0,e.wsKey===S.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl")),"emit"),scanTtlMs:a,stopped:o(()=>A,"stopped")});(0,source_poller_1.sourcesEnabled)()&&ie.start(),await O.bootSingletonsAndApps();const ce=P.filter(e=>e.decl.eager).map(e=>v.ensureUp(e));Promise.allSettled(ce).then(()=>setTimeout(te,ce.length?2e4:0).unref());let k,J,q;if(i){const e=require("../../../basic-ssl"),n=(0,service_keys_1.gatewayCacheDir)(t.root);q=o(()=>e.getCertificateSync(n,"gateway",S.routes.map(g=>g.host).filter(g=>!!g)),"mintCert"),k=q(),J=e.caCertPath()}const z=[],j=[],V=r.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of c){const n=k?require("node:http2").createSecureServer({key:k,cert:k,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:r.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:r.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(u,h)=>ne(u,h)):http.createServer(ne);n.on("upgrade",ae),n.on("error",u=>console.error(`[gateway] port ${e}: ${u.code}`)),n.on("connection",guardInboundSocket),n.on("secureConnection",guardInboundSocket),n.on("tlsClientError",(u,h)=>h.destroy()),n.on("clientError",(u,h)=>{const p=u,f=classifyClientError(p.code,p.message);if(f.report&&_e(f.cause)){const N=h.remoteAddress??"unknown",C=f.status===null?"dropped":String(f.status);console.warn(`[gateway] inbound H1 clientError from ${N}: ${f.cause} (${p.code??p.message}${p.bytesParsed!==void 0?`, bytesParsed=${p.bytesParsed}`:""}) \u2192 ${C}. This status is the GATEWAY's, not the upstream service's.`),w.emit("warning",`inbound clientError: ${f.cause} \u2192 ${C}`,`gateway-client-error:${f.cause}`,{kind:"gateway-client-error",cause:f.cause,code:p.code??null,status:f.status,peer:N,bytesParsed:p.bytesParsed??null})}f.status!==null&&h.writable&&h.end(`HTTP/1.1 ${f.status} ${CLIENT_ERROR_REASON[f.status]}\r
|
|
2
2
|
\r
|
|
3
|
-
`),
|
|
3
|
+
`),h.destroy()}),n.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,n.headersTimeout=r.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,V>0&&(n.maxConnections=V);const g=r.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(g>0&&(n.connectionsCheckingInterval=g),k!=null){const u=n,{NGHTTP2_CANCEL:h}=require("node:http2").constants;u.on("session",p=>{if(!D.admitSession(p)){p.on("error",()=>{});return}let f=0,N=0;p.on("stream",C=>{D.admitStream(p,C)&&C.once("close",()=>{p.destroyed?N+=1:C.rstCode===h&&(f+=1)})}),p.once("close",()=>{N!==0&&(console.warn(`[gateway] h2 session closed with ${N} stream(s) in flight after ${f} 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 ${N} stream(s) in flight`,"gateway-h2-session-lost",{kind:"h2-session-lost",project:"gateway",inflight:N,clientResets:f}))}),p.on("error",C=>{const we=C.code??C.message;console.warn(`[gateway] h2 session error (${we}) \u2014 dropping this session only`),p.destroyed||p.destroy()})}),u.on("sessionError",p=>{console.warn(`[gateway] h2 sessionError: ${p.code??p.message}`)})}await new Promise(u=>{n.listen(e,()=>{const h=n.address()?.port??e;j.push(h),console.log(`[gateway] Listening on ${k?"https":"http"}://localhost:${h}`),u()})}),z.push(n)}q&&(ee=o(()=>{const e=q();for(const n of z)n.setSecureContext?.({key:e,cert:e})},"refreshCert")),console.log("[gateway] Routes:");for(const e of R){const n=e.pool?`pods@${e.pool.dir}`:e.target;console.log(`[gateway] ${e.host??"(default)"} \u2192 ${n}${e.prependPrefix?` (+${e.prependPrefix})`:""}${e.internal?" [internal: no middleware]":""} [${e.project}]`)}J&&console.log(`[gateway] CA: ${J} \u2014 one-time setup via the trust executor.`),U=o(()=>W.warm(),"warmEdgeVerifiers"),U();const Q=o(e=>e>0?String(e):"unbounded","unbounded");return console.log(`[gateway] front-door budget: \u2264${Q(V)} connections, \u2264${Q(L.maxSessions)} h2 sessions (each \u2264512 MB session memory, \u2264512 streams), \u2264${Q(L.maxStreams)} open streams in total, idle h2 session reclaimed after ${L.idleSessionMs>0?`${Math.round(L.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 ${j.join("/")}`,"cluster-gateway",{kind:"gateway-up",project:"gateway",ports:j,routes:R.map(e=>({host:e.host??"(default)",project:e.project,mode:e.pool?"pods":"tcp",internal:!!e.internal,prependPrefix:e.prependPrefix}))}),$&&process.env.LENSMCP_GUEST_REPLAY!=="0"&&$.replay().catch(e=>console.warn("[gateway] guest replay failed:",e.message)),{ports:j,routes:R,services:P,stop:o(async()=>{if(!A){A=!0,clearInterval(Ee),clearInterval(me),ie.stop(),w.emit("info","gateway down","cluster-gateway",{kind:"gateway-down",project:"gateway"}),W.stop(),$?.close(),I?.flush(),(0,workspace_registry_1.unregisterWorkspace)(y);for(const e of P)v.killSpawned(e,"gateway shutdown");v.stopRetiringTrees(),await v.stopBuildHost(),await v.finishDyingTrees(),await O.reapAll(),ae.closeTunnels(),await Promise.all(z.map(e=>new Promise(n=>{let g=!1;const u=o(()=>{g||(g=!0,n())},"done"),h=e;e.close(()=>u());try{h.closeIdleConnections?.()}catch{}setTimeout(()=>{try{h.closeAllConnections?.()}catch{}u()},types_1.GATEWAY_STOP_GRACE_MS).unref?.()}))),H.closeProxy(),H.destroyAgents(),D.dispose(),(0,workspace_log_1.closeWorkspaceLogs)(),l()}},"stop"),...S.lensSupervisor?{lensSupervisor:S.lensSupervisor}:{},...S.graphStores?{graphStores:S.graphStores}:{}}}T(startGateway,"startGateway"),o(startGateway,"startGateway");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The gateway's half of the shared compiler (issues/
|
|
2
|
+
* The gateway's half of the shared compiler (issues/closed/pod-memory-05): it starts the workspace's BUILD HOST
|
|
3
3
|
* (build-host.ts — one webpack process for every service that can share it) and relays between it and each
|
|
4
4
|
* service's runner, over the two IPC channels it already owns. The runners never see the host and the host
|
|
5
5
|
* never sees a runner: no socket, no address, nothing another account could connect to.
|