@lensmcp/cluster 1.23.13 → 1.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/build-host.d.ts +1 -0
  2. package/build-host.js +1 -0
  3. package/bundle-parent.d.ts +8 -0
  4. package/bundle-parent.js +1 -0
  5. package/create-webpack-dev.d.ts +74 -40
  6. package/create-webpack-dev.js +1 -1
  7. package/dev-devtool.d.ts +45 -0
  8. package/dev-devtool.js +1 -0
  9. package/devserver-notifier.d.ts +57 -0
  10. package/devserver-notifier.js +1 -0
  11. package/executors/gateway/body-stall.d.ts +13 -1
  12. package/executors/gateway/body-stall.js +1 -1
  13. package/executors/gateway/canonical-path.d.ts +1 -1
  14. package/executors/gateway/client-ip.d.ts +1 -1
  15. package/executors/gateway/gateway.impl.js +1 -1
  16. package/executors/gateway/jwt-claims.d.ts +1 -1
  17. package/executors/gateway/prod-config.d.ts +1 -1
  18. package/executors/gateway/prod-runtime/body-stall.d.ts +1 -1
  19. package/executors/gateway/rate-limit.d.ts +1 -1
  20. package/executors/gateway/runtime/auth.d.ts +1 -1
  21. package/executors/gateway/runtime/chooser.d.ts +2 -2
  22. package/executors/gateway/runtime/control.d.ts +3 -3
  23. package/executors/gateway/runtime/control.js +2 -2
  24. package/executors/gateway/runtime/dev-auth.d.ts +1 -1
  25. package/executors/gateway/runtime/edge.d.ts +3 -3
  26. package/executors/gateway/runtime/guest-graph.d.ts +113 -0
  27. package/executors/gateway/runtime/guest-graph.js +2 -0
  28. package/executors/gateway/runtime/lens-children.d.ts +11 -1
  29. package/executors/gateway/runtime/lens-children.js +1 -1
  30. package/executors/gateway/runtime/lifecycle.d.ts +56 -8
  31. package/executors/gateway/runtime/lifecycle.js +3 -3
  32. package/executors/gateway/runtime/process-names.d.ts +18 -0
  33. package/executors/gateway/runtime/process-names.js +1 -0
  34. package/executors/gateway/runtime/project-graph-store.d.ts +74 -0
  35. package/executors/gateway/runtime/project-graph-store.js +1 -0
  36. package/executors/gateway/runtime/proxy.d.ts +2 -2
  37. package/executors/gateway/runtime/proxy.js +1 -1
  38. package/executors/gateway/runtime/scope.d.ts +1 -1
  39. package/executors/gateway/runtime/serve-launch.d.ts +118 -0
  40. package/executors/gateway/runtime/serve-launch.js +3 -0
  41. package/executors/gateway/runtime/server.d.ts +3 -3
  42. package/executors/gateway/runtime/server.js +2 -2
  43. package/executors/gateway/runtime/shared-build.d.ts +65 -0
  44. package/executors/gateway/runtime/shared-build.js +4 -0
  45. package/executors/gateway/runtime/types.d.ts +39 -9
  46. package/executors/gateway/runtime/types.js +1 -1
  47. package/executors/gateway/runtime/workspace-log.js +1 -1
  48. package/executors/gateway/schema.d.ts +3 -0
  49. package/executors/gateway/schema.json +4 -0
  50. package/executors/serve/schema.d.ts +2 -0
  51. package/executors/serve/schema.json +4 -0
  52. package/executors/serve/serve.impl.d.ts +12 -6
  53. package/executors/serve/serve.impl.js +1 -1
  54. package/executors/trust/hostname.d.ts +1 -1
  55. package/exit-reason.d.ts +1 -1
  56. package/fork-ts-check-reporter.d.ts +12 -0
  57. package/fork-ts-check-reporter.js +1 -1
  58. package/graph-snapshot.d.ts +1 -0
  59. package/graph-snapshot.js +1 -0
  60. package/heap-snapshot.d.ts +66 -0
  61. package/heap-snapshot.js +1 -0
  62. package/hot-swap-registries.d.ts +1 -1
  63. package/inspector-secret.d.ts +39 -0
  64. package/inspector-secret.js +1 -0
  65. package/main.devserver.d.ts +33 -5
  66. package/main.devserver.js +11 -9
  67. package/package.json +4 -4
  68. package/perf/heavy-barrel-import.d.ts +135 -0
  69. package/perf/heavy-barrel-import.js +1 -0
  70. package/perf/perf-config.d.ts +29 -0
  71. package/perf/perf-config.js +1 -0
  72. package/perf/perf-rule-events.d.ts +71 -0
  73. package/perf/perf-rule-events.js +2 -0
  74. package/perf/perf-rules-analysis.d.ts +28 -0
  75. package/perf/perf-rules-analysis.js +1 -0
  76. package/perf/perf-rules-plugin.d.ts +71 -0
  77. package/perf/perf-rules-plugin.js +1 -0
  78. package/perf/perf-rules-worker.d.ts +1 -0
  79. package/perf/perf-rules-worker.js +1 -0
  80. package/perf/source-scan.d.ts +59 -0
  81. package/perf/source-scan.js +5 -0
  82. package/perf/workspace-libraries.d.ts +52 -0
  83. package/perf/workspace-libraries.js +2 -0
  84. package/pod-memory-events.d.ts +71 -0
  85. package/pod-memory-events.js +2 -0
  86. package/pod-memory.d.ts +264 -0
  87. package/pod-memory.js +1 -0
  88. package/process-name.d.ts +45 -0
  89. package/process-name.js +1 -1
  90. package/serve-runner.d.ts +2 -0
  91. package/serve-runner.js +1 -0
  92. package/shared-build/attach.d.ts +56 -0
  93. package/shared-build/attach.js +3 -0
  94. package/shared-build/host.d.ts +52 -0
  95. package/shared-build/host.js +4 -0
  96. package/shared-build/protocol.d.ts +118 -0
  97. package/shared-build/protocol.js +1 -0
  98. package/shared-build/shared-compilation.d.ts +91 -0
  99. package/shared-build/shared-compilation.js +3 -0
  100. package/shared-build/signature.d.ts +52 -0
  101. package/shared-build/signature.js +1 -0
  102. package/stdio-guard.d.ts +1 -1
  103. package/swap-recycle.d.ts +21 -0
  104. package/swap-recycle.js +1 -1
  105. package/tsgo-check-plugin.d.ts +1 -1
  106. package/workspace-paths.d.ts +76 -0
  107. package/workspace-paths.js +1 -0
@@ -1,3 +1,3 @@
1
- "use strict";var ge=Object.defineProperty;var w=(t,n)=>ge(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=w((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isTestSourcePath=isTestSourcePath,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),pod_output_1=require("./pod-output"),types_1=require("./types"),exit_reason_1=require("../../../exit-reason"),process_name_1=require("../../../process-name");function eastWestEnv(t,n){const a={},l=t.serviceKeys[(0,service_keys_1.serviceKeyId)(n.wsKey,n.project)];if(l&&(a.LENSMCP_SERVICE_KEY=l),t.https){const f=require("../../../basic-ssl").caCertPath();fs.existsSync(f)&&(a.NODE_EXTRA_CA_CERTS=f)}return a}w(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let a=0;a<t.length;a+=1)n^=t.charCodeAt(a),n=Math.imul(n,16777619);return n>>>0}w(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}w(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,n=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const a=path.join(t.pool.dir,"parent.sock");return n(a)?{socketPath:a}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}w(scaleTarget,"scaleTarget"),i(scaleTarget,"scaleTarget");function isOwnRunnerCommand(t,n){return t.includes("/node_modules/nx/")?t.includes(`${n.replace(/\/+$/,"")}/node_modules/nx/`):!0}w(isOwnRunnerCommand,"isOwnRunnerCommand"),i(isOwnRunnerCommand,"isOwnRunnerCommand");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const n=t.split("/"),a=n[n.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let l=0;l<n.length-1;l+=1)if(TEST_DIR_SEGMENTS.has(n[l]))return!0;return!1}w(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,l){const f=l?.ignoreTests??!0;let v=0,y=0,_=!1;const g=new Set,I=await gitIgnoredPaths(t),h=i(x=>path.relative(t,x).split(path.sep).join("/"),"rel"),C=i(async(x,X=!0)=>{if(v>=SCAN_FILE_CAP){_=!0;return}let D;try{D=await fs.promises.readdir(x,{withFileTypes:!0})}catch{return}for(const V of D){if(v>=SCAN_FILE_CAP){_=!0;return}const P=V.name,G=path.join(x,P);if(V.isDirectory()){if(!X||P.startsWith(".")||SRC_EXCLUDE.has(P)||a?.has(P)||P.endsWith("-devserver")||I.has(`${h(G)}/`))continue;await C(G)}else if(V.isFile()){const Y=P.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(P.slice(Y)))continue;const b=h(G);if(I.has(b)||f&&isTestSourcePath(b)||g.has(b))continue;g.add(b),v+=1,y^=fnv1a(b)}}},"walk");if(n&&n.length>0){await C(t,!1);for(const x of n)await C(x)}else await C(t);return{signature:`${v}:${(y>>>0).toString(36)}`,paths:g,truncated:_}}w(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,l){return(await sourceSetSnapshot(t,n,a,l)).signature}w(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,l){if(t.paths.size===n.paths.size){let _=!0;for(const g of t.paths)if(!n.paths.has(g)){_=!1;break}if(_)return null}const f=[],v=[];let y=t.truncated||n.truncated;for(const _ of n.paths)t.paths.has(_)||(f.length+v.length<SOURCE_DIFF_PATH_CAP?f.push(_):y=!0);for(const _ of t.paths)n.paths.has(_)||(f.length+v.length<SOURCE_DIFF_PATH_CAP?v.push(_):y=!0);return{changedAt:a,added:f,removed:v,truncated:y,...l!==void 0?{windowMs:l}:{}}}w(diffSourceSets,"diffSourceSets"),i(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,n,a,l){const f=n&&n>0?t-n:a;return Math.max(f,a)+l}w(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,f){const v=i(g=>{const I=path.relative(t,g).split(path.sep).filter(Boolean);return I.length===0||I[0].startsWith("..")?"":I[0]},"bucketOf")(n);if(!v)return[];const y=new Set(a.filter(Boolean)),_=new Set([v]);for(const g of l)g.startsWith(".")||SRC_EXCLUDE.has(g)||f?.has(g)||g.endsWith("-devserver")||y.has(g)||_.add(g);return[..._].sort().map(g=>path.join(t,g))}w(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,a,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<l.cooldownMs?null:a>0&&a-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&n-t.spawnAt>l.maxAgeMs?"max-age":null}w(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,a,l,f){return!(l-n<f.cooldownMs||a<=0||a-t<=f.graceMs||l-a<f.settleMs)}w(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,a,l=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&l.length>0){const f=n-a,v=new Set(t.recentEvents.filter(y=>y.at>=f).map(y=>path.resolve(y.path)));return l.every(y=>v.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}w(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,a){if(!t)return"unknown";if(t.degraded)return"degraded";const l=n-a;return t.lastWatchRunAt>=l||t.lastBuildAt>=l?"heard":"silent"}w(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,a,l){const f=t.hasListened?l.graceMs:Math.max(l.graceMs,l.neverListenedGraceMs);return a-t.spawnedAt<f||a-n<l.cooldownMs||t.probeFails<Math.max(2,l.failureThreshold)?null:"wedged-no-listener"}w(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}w(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,a={}){const l=a.spawnProcess??node_child_process_1.spawn,f=a.probeAlive??pod_probe_1.probePodAlive,v=a.killTree??((e,r="SIGTERM")=>{try{process.kill(-e.pid,r)}catch{try{e.kill(r)}catch{}}}),y=a.rollReadyMs??types_1.POD_ROLL_READY_MS,_=a.rollPollMs??750,{emit:g}=n,I=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,r,s,o,c)=>g(r,s,o,{wsKey:e.wsKey,...c},void 0,I(e)),"emitSvc"),C=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),x=i((e,r)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const o=e.proc?.pid;let c=0;for(const d of s.split(`
2
- `)){const u=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!u)continue;const m=Number(u[1]),L=Number(u[2]),k=u[3];if(k.includes(`run ${r}`)&&/\bnx(\.js)?\b/.test(k)&&isOwnRunnerCommand(k,e.root)&&!(m===process.pid||m===o)&&!(o!==void 0&&L===o)){try{process.kill(-L,"SIGKILL")}catch{try{process.kill(m,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${r}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),h(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),X=i((e,r,s)=>{const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),d=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),u=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(e.wsKey||path.basename(e.root),e.project,"nx")),[m,L]=fs.existsSync(d)?[u,[d,"run",o,`--childCount=${c}`]]:["yarn",["nx","run",o,`--childCount=${c}`]],k=l(m,L,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(r),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const R=i(S=>{const E=S.toString();for(const N of E.split(`
3
- `)){const p=(0,pod_output_1.classifyPodLine)(N),{clean:j,broken:$,healthy:T}=p,z=j;if($){e.lastErrors.push(j),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const M=Date.now();(j!==e.lastErrorEmitSig||M-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=j,e.lastErrorEmitAt=M,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:j,fault:p.fault}))}T&&(e.buildFailed=!1);const O=(e.faults??=new pod_output_1.PodFaults).observe(p);if(O){e.lastErrorEmitSig=void 0;const M=O.open.length===0;h(e,"info",`${M?"service recovered":`${O.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:M?"service-recovered":"service-fault-closed",project:e.project,line:j,closed:O.closed,open:O.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(z)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${o}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),h(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(W(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),x(e,o),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.())},250).unref?.())}},"sniff"),A=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return k.stdout?.on("data",S=>{if(t.stopped())return;R(S);const E=`[${e.project}] ${S}`;process.stdout.write(E),A?.(E)}),k.stderr?.on("data",S=>{if(t.stopped())return;R(S);const E=`[${e.project}] ${S}`;process.stderr.write(E),A?.(E)}),k.on("exit",(S,E)=>{if(e.proc!==k||(e.proc=void 0,e.state="down",t.stopped()))return;const N=(0,exit_reason_1.describeExit)(S,E);if(console.log(`[gateway] ${o} exited (${S??"no code"}${E?`, ${E}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${N?` \u2014 ${N}`:""}`),h(e,S===0?"info":"warning",`service down: ${e.project} (exit ${S})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:S,...E?{signal:E}:{}}),S===0||e.buildFailed)return;const p=Date.now();if(e.restarts=(e.restarts??[]).filter($=>p-$<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(p);const j=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),h(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),D(e))},j)}),k},"launchServiceProcess"),D=i(e=>{if(t.stopped())return;const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&x(e,r),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${r} (pods=${s})\u2026`),h(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=X(e,e.generation,"active")},"spawnService"),V=i(async e=>{const r=e.pool.socks[0];if(!r)return;const s=await(0,pod_probe_1.probePodAlive)(r,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),P=i(e=>{try{const r=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(r)&&r>1?r:void 0}catch{return}},"readOwnerSidecar"),G=i(async e=>{const r=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!r)return[];const s=(0,node_crypto_1.createHash)("sha256").update(r).digest("hex").slice(0,8),o=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,r);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&o.push({sock:c,pid:d.pid})}return o},"staleAdoptedSocks"),Y=i((e,r)=>{const s=r.map(o=>o.sock);for(const{sock:o,pid:c}of r){const d=chooseStalePodKillTarget(c,P(o),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(o=>!s.includes(o)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),b=i((e,r)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return h(e,"info",`service up: ${e.project} (${s} pods, ${r})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:r==="adopted"}),!0},"markUp"),re=i(async e=>{if(C(e.pool)>0){const o=await G(e);if(o.length>0&&(console.error(`[gateway] ${e.project}: ${o.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),h(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:o.length}),e.proc&&W(e,"stale adoption (foreign east-west key)"),Y(e,o)),e.pool.socks.length>0)return b(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&D(e);const r=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<r;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(C(e.pool)>0){e.state="up",e.buildFailed=!1;const o=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${o?`, cold start ${o}ms`:""})`),h(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${o??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:o}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const r=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,o=scaleTarget(e);if(!o||s===0||s>=r||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();let c;"socketPath"in o?c=http.request({socketPath:o.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},d=>d.resume()):c=(o.url.startsWith("https")?require("node:https"):http).request(`${o.url}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume()),c.on("error",()=>{}),c.end(JSON.stringify({pods:s+1})),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),h(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight})},"scaleUp"),W=i((e,r,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${r} \u2014 scaling to zero.`),s?.announce!==!1&&h(e,"info",`idle kill: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:r});const o=e.proc;try{o.stdout?.removeAllListeners("data"),o.stderr?.removeAllListeners("data"),o.removeAllListeners("exit")}catch{}v(o,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{J(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),J=i(e=>{try{for(const r of fs.readdirSync(e.pool.dir)){if(!r.endsWith(".sock"))continue;const s=path.join(e.pool.dir,r);try{const o=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(o)&&o>0)try{process.kill(o,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),te=i((e,r)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const o=e.generation??0,c=o+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${o}\u2192g${c} reason=${r} \u2014 standby spawning, old generation keeps serving.`),h(e,"info",`pod handoff starting: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:r,generation:c,fromGeneration:o});const u=X(e,c,"standby"),m=(async()=>{const L=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],k=d+y;let R;for(;!t.stopped()&&Date.now()<k&&!(u.exitCode!==null||u.signalCode!==null);){C(e.pool);for(const A of e.pool.socks)if((await f(A,void 0,L))?.podGeneration===c){R=A;break}if(R)break;await sleep(_)}if(!R||t.stopped()){const A=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(y/1e3)}s`;try{u.stdout?.removeAllListeners("data"),u.stderr?.removeAllListeners("data")}catch{}return v(u,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${A} \u2014 old generation g${o} kept serving.`),h(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:r,generation:c,error:A,durationMs:Date.now()-d}),setTimeout(()=>J(e),1200).unref?.(),!1}if(e.proc!==s)return v(u,"SIGTERM"),!1;e.proc=u,e.generation=c,e.spawnAt=d,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}return v(s,"SIGTERM"),setTimeout(()=>{J(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(R)} in ${Date.now()-d}ms; retiring g${o}.`),h(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:r,generation:c,fromGeneration:o,durationMs:Date.now()-d}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=m,m},"rollPod"),ne=i(e=>{const r=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>r-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),W(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(r),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS}; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),h(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),W(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recycleWedged"),ce={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let Q;const B=new Map,oe=new Map;let Z=!1,ee=0,H=0;const ie=i(()=>{const e=t.projectRoots??{},r=new Set(t.services.map(o=>o.project));for(const o of t.routes)o.lens&&r.add(o.lens.project);const s=new Set;for(const o of r){const c=e[o];if(!c)continue;const d=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&s.add(d)}return[...s]},"managedBuckets"),ae=i((e,r,s,o)=>{const c=t.projectSourceRoots?.[e];if(c?.length)return c;const d=t.projectRoots?.[e];if(!d)return;const u=sourceScopeDirs(t.root,d,s,r,o);return u.length>0?u:void 0},"scopeFor"),le=i(()=>{if(Z||Date.now()-ee<types_1.POD_STALE_SCAN_MS)return;Z=!0,ee=Date.now();const e=ee;(async()=>{const r=(0,scope_1.readSourceSetOptions)(t.root),s=r.exclude,o={ignoreTests:r.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,o),d=Date.now(),u=sourceChangeWindowMs(d,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),m=Q?diffSourceSets(Q,c,d,u):null;if(m&&(t.sourceSetChangedAt=d,t.sourceSetChange=m,g("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:m.added,removed:m.removed,truncated:m.truncated})),Q=c,!t.projectRoots){H=e;return}let L;try{L=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(p=>p.isDirectory()).map(p=>p.name)}catch{return}const k=ie(),R=new Set(t.services.map(p=>p.project));for(const p of t.routes)p.lens&&R.add(p.lens.project);const A=new Map;for(const p of R){const j=ae(p,L,k,s);if(!j)continue;const $=j.join("|"),T=A.get($)??{dirs:j,projects:[]};T.projects.push(p),A.set($,T)}const S=Date.now(),E=sourceChangeWindowMs(S,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);H=e;const N=i((p,j,$)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const T of p)t.sourceSetChangedFor.set(T,j),t.sourceSetChangesFor.set(T,$)},"stampScoped");for(const[p,{dirs:j,projects:$}]of A){const T=await sourceSetSnapshot(t.root,j,s,o),z=B.get(p);B.set(p,T);const O=new Map;for(const K of $){const F=oe.get(K);if(F===void 0||F===p)continue;const q=O.get(F)??[];q.push(K),O.set(F,q)}for(const K of $)oe.set(K,p);for(const[K,F]of O){const q=B.get(K),U=q?diffSourceSets(q,T,S,E):m;U&&(N(F,q?S:d,U),g("info",`source file set changed \u2014 ${F.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:F,added:U.added,removed:U.removed,truncated:U.truncated}))}if(!z)continue;const M=diffSourceSets(z,T,S,E);M&&(N($,S,M),g("info",`source file set changed in ${$.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:$,added:M.added,removed:M.removed,truncated:M.truncated}))}for(const p of[...B.keys()])A.has(p)||B.delete(p)})().catch(()=>{}).finally(()=>{Z=!1})},"maybeScanSourceSet"),de=i((e,r)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,o=podWatcherVerdict(e.watcher,r,s),c=i(d=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return o==="heard"?(e.staleVerifiedAt=r,h(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:o}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):o==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):o==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),pe=i((e,r)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){te(e,r);return}console.log(`[gateway] zombie recycle: ${e.project} (${r}) \u2014 stale fs view; fresh pod incoming`),h(e,"warning",`zombie recycle: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:r});const s=!!e.decl.eager;W(e,`${r} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{he().catch(r=>console.error("[gateway] sweep error:",r))},t.sweepMs);return e.unref(),e},"startSweeper"),he=i(async()=>{{if(t.stopped())return;types_1.POD_RECYCLE_ENABLED&&le();for(const e of t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const r=e.decl.idleKillSec;if(r&&e.inflight===0&&Date.now()-e.lastUsed>r*1e3){W(e,`idle ${r}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,o=C(e.pool)>0;if(o||s){if(e.wedgedSince=void 0,o&&b(e,"observed"),o&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await V(e);const d=(e.staleVerifiedAt??0)>=c&&c>0,u=podRecycleReason(e,Date.now(),d?0:c,ce);if(u==="stale-source-set"&&!de(e,c))continue;u&&pe(e,u)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ne(e))}}},"sweepOnce");return{scanNow:C,spawnService:D,ensureUp:re,scaleUp:se,killSpawned:W,markUp:b,startSweeper:ue,rollPod:te}}w(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==a)return t}w(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
1
+ "use strict";var Te=Object.defineProperty;var f=(t,n)=>Te(t,"name",{value:n,configurable:!0});var Ce=Object.defineProperty,i=f((t,n)=>Ce(t,"name",{value:n,configurable:!0}),"i");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.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"),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"),inspector_secret_1=require("../../../inspector-secret"),pod_output_1=require("./pod-output"),serve_launch_1=require("./serve-launch"),shared_build_1=require("./shared-build"),protocol_1=require("../../../shared-build/protocol"),types_1=require("./types"),exit_reason_1=require("../../../exit-reason"),process_name_1=require("../../../process-name");function eastWestEnv(t,n){const c={},a=t.serviceKeys[(0,service_keys_1.serviceKeyId)(n.wsKey,n.project)];if(a&&(c.LENSMCP_SERVICE_KEY=a),t.https){const h=require("../../../basic-ssl").caCertPath();fs.existsSync(h)&&(c.NODE_EXTRA_CA_CERTS=h)}return c}f(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let c=0;c<t.length;c+=1)n^=t.charCodeAt(c),n=Math.imul(n,16777619);return n>>>0}f(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}f(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,n=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const c=path.join(t.pool.dir,"parent.sock");return n(c)?{socketPath:c}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}f(scaleTarget,"scaleTarget"),i(scaleTarget,"scaleTarget");function isLoopbackHostname(t){const n=t.toLowerCase().replace(/^\[(.*)\]$/,"$1").replace(/\.$/,"");return n==="localhost"||n.endsWith(".localhost")?!0:net.isIPv4(n)?n.split(".")[0]==="127":net.isIPv6(n)&&new URL(`http://[${n}]/`).hostname==="[::1]"}f(isLoopbackHostname,"isLoopbackHostname"),i(isLoopbackHostname,"isLoopbackHostname");function postScale(t,n,c){let a;if("socketPath"in t)a=http.request({socketPath:t.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},h=>h.resume());else{const h=t.url.startsWith("https"),A=h?require("node:https"):http,g={method:"POST"};h&&isLoopbackHostname(new URL(t.url).hostname)&&(g.rejectUnauthorized=!1),c&&(g.lookup=c),a=A.request(`${t.url}/webpack/scale`,g,S=>S.resume())}return a.on("error",()=>{}),a.end(JSON.stringify({pods:n})),a}f(postScale,"postScale"),i(postScale,"postScale");function isOwnRunnerCommand(t,n){return(0,serve_launch_1.isServeRunnerCommand)(t)?(0,serve_launch_1.isServeRunnerCommandOf)(t,n):t.includes("/node_modules/nx/")?t.includes(`${n.replace(/\/+$/,"")}/node_modules/nx/`):!0}f(isOwnRunnerCommand,"isOwnRunnerCommand"),i(isOwnRunnerCommand,"isOwnRunnerCommand");function isRunnerOf(t,n,c){const a=n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return!new RegExp(`(^|\\s)run ${a}(:|\\s|$)`).test(t)||!(0,serve_launch_1.isServeRunnerCommand)(t)&&!/\bnx(\.js)?\b/.test(t)?!1:isOwnRunnerCommand(t,c)}f(isRunnerOf,"isRunnerOf"),i(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 n=t.split("/"),c=n[n.length-1]??"";if(TEST_FILE_RE.test(c))return!0;for(let a=0;a<n.length-1;a+=1)if(TEST_DIR_SEGMENTS.has(n[a]))return!0;return!1}f(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");function sourceScanPass(t,n,c){const a=c?.ignoreTests??!0;let h;const A=new Map,g=`tree:${path.resolve(t)}`,S=i(u=>path.relative(t,u).split(path.sep).join("/"),"rel"),j=i(async(u,w)=>{const R=await(h??=gitIgnoredPaths(t)),D=[],I=new Set;let x=!1;const N=i(async(H,te)=>{if(D.length>=SCAN_FILE_CAP){x=!0;return}let oe;try{oe=await fs.promises.readdir(H,{withFileTypes:!0})}catch{return}te&&I.add(S(H));for(const z of oe){if(D.length>=SCAN_FILE_CAP){x=!0;return}const b=z.name,J=path.join(H,b);if(z.isDirectory()){if(!te||b.startsWith(".")||SRC_EXCLUDE.has(b)||n?.has(b)||b.endsWith("-devserver")||R.has(`${S(J)}/`))continue;await N(J,!0)}else if(z.isFile()){const re=b.lastIndexOf(".");if(re<=0||!SRC_EXT.has(b.slice(re)))continue;const L=S(J);if(R.has(L)||a&&isTestSourcePath(L))continue;D.push(L)}}},"walk");return await N(u,w),{paths:D,entered:I,truncated:x}},"walkDir"),O=i((u,w)=>{let R=A.get(u);return R||(R=w(),A.set(u,R)),R},"memo"),Y=i(async()=>{const u=A.get(g);if(!u)return;const w=await u;return w.truncated?void 0:w},"wideWalk"),q=new Set,V=i(()=>O(`files:${path.resolve(t)}`,async()=>{const u=await Y();return u?{paths:u.paths.filter(w=>!w.includes("/")),entered:q,truncated:!1}:j(t,!1)}),"rootFiles"),ne=i(u=>O(`tree:${path.resolve(u)}`,async()=>{const w=await Y(),R=S(u);if(w?.entered.has(R)){const D=`${R}/`;return{paths:R===""?w.paths:w.paths.filter(I=>I.startsWith(D)),entered:q,truncated:!1}}return j(u,!0)}),"subtree"),v=i(u=>{const w=new Set;let R=0,D=u.some(x=>x.truncated);const I=i(x=>w.has(x)?!0:w.size>=SCAN_FILE_CAP?(D=!0,!1):(w.add(x),R^=fnv1a(x),!0),"add");for(const x of u)if(!x.paths.every(I))break;return{signature:`${w.size}:${(R>>>0).toString(36)}`,paths:w,truncated:D}},"compose");return{async snapshot(u){if(!u||u.length===0)return v([await O(g,()=>j(t,!0))]);const w=[await V()];for(const R of u)w.push(await ne(R));return v(w)}}}f(sourceScanPass,"sourceScanPass"),i(sourceScanPass,"sourceScanPass");async function sourceSetSnapshot(t,n,c,a){return sourceScanPass(t,c,a).snapshot(n)}f(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,c,a){return(await sourceSetSnapshot(t,n,c,a)).signature}f(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,c,a){if(t.paths.size===n.paths.size){let S=!0;for(const j of t.paths)if(!n.paths.has(j)){S=!1;break}if(S)return null}const h=[],A=[];let g=t.truncated||n.truncated;for(const S of n.paths)t.paths.has(S)||(h.length+A.length<SOURCE_DIFF_PATH_CAP?h.push(S):g=!0);for(const S of t.paths)n.paths.has(S)||(h.length+A.length<SOURCE_DIFF_PATH_CAP?A.push(S):g=!0);return{changedAt:c,added:h,removed:A,truncated:g,...a!==void 0?{windowMs:a}:{}}}f(diffSourceSets,"diffSourceSets"),i(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,n,c,a){const h=n&&n>0?t-n:c;return Math.max(h,c)+a}f(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,c,a,h){const A=i(j=>{const O=path.relative(t,j).split(path.sep).filter(Boolean);return O.length===0||O[0].startsWith("..")?"":O[0]},"bucketOf")(n);if(!A)return[];const g=new Set(c.filter(Boolean)),S=new Set([A]);for(const j of a)j.startsWith(".")||SRC_EXCLUDE.has(j)||h?.has(j)||j.endsWith("-devserver")||g.has(j)||S.add(j);return[...S].sort().map(j=>path.join(t,j))}f(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,c,a){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<a.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<a.cooldownMs?null:c>0&&c-t.spawnAt>a.graceMs?"stale-source-set":a.maxAgeMs>0&&n-t.spawnAt>a.maxAgeMs?"max-age":null}f(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,c,a,h){return!(a-n<h.cooldownMs||c<=0||c-t<=h.graceMs||a-c<h.settleMs)}f(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,c,a=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&a.length>0){const h=n-c,A=new Set(t.recentEvents.filter(g=>g.at>=h).map(g=>path.resolve(g.path)));return a.every(g=>A.has(path.resolve(g)))?"heard":"missed"}return t.lastFsSetEventAt>=n-c?"heard":"missed"}f(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,c){if(!t)return"unknown";if(t.degraded)return"degraded";const a=n-c;return t.lastWatchRunAt>=a||t.lastBuildAt>=a?"heard":"silent"}f(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,c,a){const h=t.hasListened?a.graceMs:Math.max(a.graceMs,a.neverListenedGraceMs);return c-t.spawnedAt<h||c-n<a.cooldownMs||t.probeFails<Math.max(2,a.failureThreshold)?null:"wedged-no-listener"}f(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}f(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,c={}){const a=c.spawnProcess??node_child_process_1.spawn,h=c.probeAlive??pod_probe_1.probePodAlive,A=c.killTree??((e,r="SIGTERM")=>{try{process.kill(-e.pid,r)}catch{try{e.kill(r)}catch{}}}),g=c.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),S=i((e,r="SIGTERM")=>{g.detach(e),A(e,r)},"killTree"),j=c.rollReadyMs??types_1.POD_ROLL_READY_MS,O=c.rollPollMs??750,Y=c.maxConcurrentRolls??types_1.POD_MAX_CONCURRENT_ROLLS,q=Y>0?Y:Number.POSITIVE_INFINITY,{emit:V}=n,ne=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),v=i((e,r,s,o,l)=>V(r,s,o,{wsKey:e.wsKey,...l},void 0,ne(e)),"emitSvc"),u=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),w=i((e,r)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const o=e.proc?.pid;let l=0;for(const d of s.split(`
2
+ `)){const p=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const y=Number(p[1]),C=Number(p[2]);if(isRunnerOf(p[3],r,e.root)&&!(y===process.pid||y===o)&&!(o!==void 0&&C===o)){try{process.kill(-C,"SIGKILL")}catch{try{process.kill(y,"SIGKILL")}catch{}}l+=1}}return l>0&&(console.log(`[gateway] ${r}: reaped ${l} 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} (${l} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:l})),l},"reapForeignRunners"),R=i((e,r)=>({...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(r),...eastWestEnv(t,e)}),"serviceEnv"),D=i((e,r,s,o)=>{const l=R(e,r),d=e.wsKey||path.basename(e.root),p=(0,serve_launch_1.planServeLaunch)({stores:t.graphStores,root:e.root,project:e.project,target:e.decl.serveTarget??"serve-hmr",childCount:o,baseEnv:l});if(p.kind==="runner"){const m=g.offeredTo(e),E=a((0,process_name_1.namedExecPath)((0,process_name_1.processName)(d,e.project,serve_launch_1.SERVE_RUNNER_ROLE)),[...p.script.execArgv,p.script.path,...(0,serve_launch_1.serveRunnerArgs)(s,o,e.root)],{cwd:e.root,detached:!0,env:m?{...p.env,[protocol_1.SHARED_BUILD_ENV]:"1"}:p.env,stdio:["ignore","pipe","pipe","ipc"]});return m&&g.attach(e,r,E),E.send?.(p.message,$=>{$&&console.error(`[gateway] ${s}: could not hand the task to its runner (${$.message})`)}),{proc:E,launcher:"runner"}}console.log(`[gateway] ${s}: starting through nx run \u2014 ${p.reason}`);const y=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),C=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(d,e.project,"nx")),[W,k]=fs.existsSync(y)?[C,[y,"run",s,`--childCount=${o}`]]:["yarn",["nx","run",s,`--childCount=${o}`]];return{proc:a(W,k,{cwd:e.root,detached:!0,env:l,stdio:["ignore","pipe","pipe"]}),launcher:"nx"}},"startServiceTree"),I=new WeakMap,x=i((e,r,s)=>{const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,l=Math.max(1,e.decl.minPods??1),{proc:d,launcher:p}=D(e,r,o,l);I.set(d,p),s==="active"&&(e.launcher=p),s==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const y=i(k=>{const m=k.toString();for(const E of m.split(`
3
+ `)){const $=(0,pod_output_1.classifyPodLine)(E),{clean:P,broken:U,healthy:_}=$,F=P;if(U){e.lastErrors.push(P),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const M=Date.now();(P!==e.lastErrorEmitSig||M-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=P,e.lastErrorEmitAt=M,v(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:P,fault:$.fault}))}_&&(e.buildFailed=!1);const T=(e.faults??=new pod_output_1.PodFaults).observe($);if(T){e.lastErrorEmitSig=void 0;const M=T.open.length===0;v(e,"info",`${M?"service recovered":`${T.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:M?"service-recovered":"service-fault-closed",project:e.project,line:P,closed:T.closed,open:T.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(F)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${o}: 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||(L(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),w(e,o),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&N(e)},1500).unref?.())},250).unref?.())}},"sniff"),C=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,W=i(k=>m=>{if(t.stopped())return;const E=(0,inspector_secret_1.redactInspectorUrls)(m.toString());y(E);const $=`[${e.project}] ${E}`;k.write($),C?.($)},"relay");return d.stdout?.on("data",W(process.stdout)),d.stderr?.on("data",W(process.stderr)),d.on("exit",(k,m)=>{if(e.proc!==d||(e.proc=void 0,e.state="down",t.stopped()))return;const E=(0,exit_reason_1.describeExit)(k,m);if(console.log(`[gateway] ${o} exited (${k??"no code"}${m?`, ${m}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${E?` \u2014 ${E}`:""}`),v(e,k===0?"info":"warning",`service down: ${e.project} (exit ${k})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:k,...m?{signal:m}:{}}),k===0||e.buildFailed)return;const $=Date.now();if(e.restarts=(e.restarts??[]).filter(U=>$-U<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($);const P=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}),N(e))},P)}),d},"launchServiceProcess"),N=i(e=>{if(t.stopped())return;const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);c.reapForeign!==!1&&w(e,r),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${r} (pods=${s})\u2026`),v(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=x(e,e.generation,"active")},"spawnService"),H=i(async e=>{const r=e.pool.socks[0];if(!r)return;const s=await(0,pod_probe_1.probePodAlive)(r,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),te=i(e=>{try{const r=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(r)&&r>1?r:void 0}catch{return}},"readOwnerSidecar"),oe=i(async e=>{const r=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!r)return[];const s=(0,node_crypto_1.createHash)("sha256").update(r).digest("hex").slice(0,8),o=[];for(const l of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(l,void 0,r);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&o.push({sock:l,pid:d.pid})}return o},"staleAdoptedSocks"),z=i((e,r)=>{const s=r.map(o=>o.sock);for(const{sock:o,pid:l}of r){const d=chooseStalePodKillTarget(l,te(o),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(o=>!s.includes(o)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),b=i((e,r)=>{if(e.state==="up")return!1;e.state="up",e.wedgeRecycles=0;const s=e.pool.socks.length;return v(e,"info",`service up: ${e.project} (${s} pods, ${r})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:r==="adopted"}),!0},"markUp"),J=i(async e=>{if(u(e.pool)>0){const o=await oe(e);if(o.length>0&&(console.error(`[gateway] ${e.project}: ${o.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:o.length}),e.proc&&L(e,"stale adoption (foreign east-west key)"),z(e,o)),e.pool.socks.length>0)return b(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&N(e);const r=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<r;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(u(e.pool)>0){e.state="up",e.buildFailed=!1;const o=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${o?`, cold start ${o}ms`:""})`),v(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${o??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:o}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),re=i(e=>{const r=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,o=scaleTarget(e);!o||s===0||s>=r||e.inflight<=s||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(o,s+1,c.scaleLookup),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),v(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight}))},"scaleUp"),L=i((e,r,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${r} \u2014 scaling to zero.`),s?.announce!==!1&&v(e,"info",`idle kill: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:r});const o=e.proc;try{o.stdout?.removeAllListeners("data"),o.stderr?.removeAllListeners("data"),o.removeAllListeners("exit")}catch{}S(o,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{ce(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ce=i(e=>{try{for(const r of fs.readdirSync(e.pool.dir)){if(!r.endsWith(".sock"))continue;const s=path.join(e.pool.dir,r);try{const o=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(o)&&o>0)try{process.kill(o,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let G=0;const ae=[],we=i(()=>G<q?(G+=1,Promise.resolve()):new Promise(e=>{ae.push(()=>{G+=1,e()})}),"acquireRollSlot"),Se=i(()=>{G-=1,ae.shift()?.()},"releaseRollSlot"),ve=i(async(e,r,s)=>{u(e.pool);for(const o of e.pool.socks){const l=await h(o,void 0,s);if(l&&r(l.podGeneration))return!0}return!1},"anyPodAnswers"),ue=i((e,r)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||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);G>=q&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${r} \u2014 ${G} roll(s) in flight, ${ae.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${q}).`);const o=(async()=>{await we();try{return await ye(e,s,r)}finally{Se()}})().finally(()=>{e.rolling=void 0});return e.rolling=o,o},"rollPod"),ye=i(async(e,r,s)=>{const o=e.proc;if(o!==r||t.stopped())return!1;const l=e.generation??0,d=l+1,p=Date.now();e.lastStaleRecycleAt=p,console.log(`[gateway] pod handoff start: ${e.project} g${l}\u2192g${d} reason=${s} \u2014 standby spawning, old generation keeps serving.`),v(e,"info",`pod handoff starting: ${e.project} (${s})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:s,generation:d,fromGeneration:l});const y=x(e,d,"standby"),C=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],W=p+j;let k;for(;!t.stopped()&&Date.now()<W&&!(y.exitCode!==null||y.signalCode!==null);){u(e.pool);for(const m of e.pool.socks)if((await h(m,void 0,C))?.podGeneration===d){k=m;break}if(k)break;await sleep(O)}if(!k||t.stopped()){const m=t.stopped()?"gateway stopping":y.exitCode!==null||y.signalCode!==null?`standby exited (${y.exitCode??y.signalCode})`:`standby not ready within ${Math.round(j/1e3)}s`;try{y.stdout?.removeAllListeners("data"),y.stderr?.removeAllListeners("data")}catch{}S(y,"SIGTERM");const E=!t.stopped()&&e.proc===o&&o.exitCode===null&&o.signalCode===null&&await ve(e,P=>P!==d,C),$=E?`old generation g${l} kept serving`:`and the old generation g${l} is NOT serving (${e.proc!==o||o.exitCode!==null||o.signalCode!==null?"its tree is gone":"none of its pods answers"}) \u2014 the service is down until it recovers`;return console.error(`[gateway] pod handoff failed: ${e.project} g${d}: ${m} \u2014 ${$}.`),v(e,E?"warning":"error",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:s,generation:d,error:m,durationMs:Date.now()-p,oldServing:E}),setTimeout(()=>ce(e),1200).unref?.(),!1}if(e.proc!==o)return S(y,"SIGTERM"),!1;e.proc=y,e.launcher=I.get(y),e.generation=d,e.spawnAt=p,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{o.stdout?.removeAllListeners("data"),o.stderr?.removeAllListeners("data"),o.removeAllListeners("exit")}catch{}return S(o,"SIGTERM"),setTimeout(()=>{ce(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${d} answered on ${path.basename(k)} in ${Date.now()-p}ms; retiring g${l}.`),v(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:s,generation:d,fromGeneration:l,durationMs:Date.now()-p}),!0},"runRoll"),he=i(e=>{const r=Date.now();e.restarts=(e.restarts??[]).filter(o=>r-o<types_1.SERVICE_RESTART_WINDOW_MS);const s=(e.wedgeRecycles??0)+1;if(e.restarts.length>=types_1.SERVICE_MAX_RESTARTS||s>types_1.SERVICE_MAX_RESTARTS){const o=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 ${o}, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project,recycles:s-1}),L(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.wedgeRecycles=s,e.restarts.push(r),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; attempt ${s}/${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:s,inWindow:e.restarts.length}),L(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&N(e)},1500).unref?.()},"recycleWedged"),_e={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 ie;const Q=new Map,fe=new Map;let le=!1,de=0,se=0;const me=i(()=>{const e=t.projectRoots??{},r=new Set(t.services.map(o=>o.project));for(const o of t.routes)o.lens&&r.add(o.lens.project);const s=new Set;for(const o of r){const l=e[o];if(!l)continue;const d=path.relative(t.root,l).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&s.add(d)}return[...s]},"managedBuckets"),Ee=i((e,r,s,o)=>{const l=t.projectSourceRoots?.[e];if(l?.length)return l;const d=t.projectRoots?.[e];if(!d)return;const p=sourceScopeDirs(t.root,d,s,r,o);return p.length>0?p:void 0},"scopeFor"),$e=i(()=>{if(le||Date.now()-de<types_1.POD_STALE_SCAN_MS)return;le=!0,de=Date.now();const e=de;(async()=>{const r=(0,scope_1.readSourceSetOptions)(t.root),s=r.exclude,o={ignoreTests:r.ignoreTests},l=sourceScanPass(t.root,s,o),d=await l.snapshot(),p=Date.now(),y=sourceChangeWindowMs(p,se,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),C=ie?diffSourceSets(ie,d,p,y):null;if(C&&(t.sourceSetChangedAt=p,t.sourceSetChange=C,V("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:C.added,removed:C.removed,truncated:C.truncated})),ie=d,!t.projectRoots){se=e;return}let W;try{W=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(_=>_.isDirectory()).map(_=>_.name)}catch{return}const k=me(),m=new Set(t.services.map(_=>_.project));for(const _ of t.routes)_.lens&&m.add(_.lens.project);const E=new Map;for(const _ of m){const F=Ee(_,W,k,s);if(!F)continue;const T=F.join("|"),M=E.get(T)??{dirs:F,projects:[]};M.projects.push(_),E.set(T,M)}const $=Date.now(),P=sourceChangeWindowMs($,se,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);se=e;const U=i((_,F,T)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const M of _)t.sourceSetChangedFor.set(M,F),t.sourceSetChangesFor.set(M,T)},"stampScoped");for(const[_,{dirs:F,projects:T}]of E){const M=await l.snapshot(F),ge=Q.get(_);Q.set(_,M);const pe=new Map;for(const B of T){const K=fe.get(B);if(K===void 0||K===_)continue;const X=pe.get(K)??[];X.push(B),pe.set(K,X)}for(const B of T)fe.set(B,_);for(const[B,K]of pe){const X=Q.get(B),ee=X?diffSourceSets(X,M,$,P):C;ee&&(U(K,X?$:p,ee),V("info",`source file set changed \u2014 ${K.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:K,added:ee.added,removed:ee.removed,truncated:ee.truncated}))}if(!ge)continue;const Z=diffSourceSets(ge,M,$,P);Z&&(U(T,$,Z),V("info",`source file set changed in ${T.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:T,added:Z.added,removed:Z.removed,truncated:Z.truncated}))}for(const _ of[...Q.keys()])E.has(_)||Q.delete(_)})().catch(()=>{}).finally(()=>{le=!1})},"maybeScanSourceSet"),je=i((e,r)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,o=podWatcherVerdict(e.watcher,r,s),l=i(d=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return o==="heard"?(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:o}),l(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):o==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):o==="silent"?types_1.POD_WATCHER_SILENT==="keep"?l("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?l("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),ke=i((e,r)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){ue(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 s=!!e.decl.eager;L(e,`${r} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&N(e)},1500).unref?.()},"recyclePod"),Re=i(()=>{const e=setInterval(()=>{Ae().catch(r=>console.error("[gateway] sweep error:",r))},t.sweepMs);return e.unref(),e},"startSweeper"),Ae=i(async()=>{{if(t.stopped())return;for(const e of t.graphStores?.all()??[])e.refreshIfStale();types_1.POD_RECYCLE_ENABLED&&$e();for(const e of t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const r=e.decl.idleKillSec;if(r&&e.inflight===0&&Date.now()-e.lastUsed>r*1e3){L(e,`idle ${r}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,o=u(e.pool)>0;if(o||s){if(e.wedgedSince=void 0,o&&b(e,"observed"),o&&!s&&types_1.POD_RECYCLE_ENABLED){const l=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await H(e);const d=(e.staleVerifiedAt??0)>=l&&l>0,p=podRecycleReason(e,Date.now(),d?0:l,_e);if(p==="stale-source-set"&&!je(e,l))continue;p&&ke(e,p)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,he(e))}}},"sweepOnce");return{scanNow:u,spawnService:N,ensureUp:J,scaleUp:re,killSpawned:L,markUp:b,startSweeper:Re,rollPod:ue,recycleWedged:he,stopBuildHost:i(e=>e===void 0?g.stop():g.release(e),"stopBuildHost")}}f(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,c){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==c)return t}f(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
@@ -0,0 +1,18 @@
1
+ import type { ServiceCtl } from './types';
2
+ export interface GatewayProcessNamesOptions {
3
+ /** Services may start through the serve runner (it is not off, and the gateway holds a project graph). A service
4
+ * the runner cannot start still goes through `nx run`, so both names are linked; `false`: `nx run` only. */
5
+ runner: boolean;
6
+ /** The shared compiler is on: the workspace's build host will run. */
7
+ sharedBuild: boolean;
8
+ /** The gateway's own workspace label (its key, else its directory name) — the build host's name. */
9
+ label: string;
10
+ /** The lens scope key: the managed dashboard / MCP children's names. Absent: neither runs. */
11
+ lens?: {
12
+ key: string;
13
+ dashboard: boolean;
14
+ mcp: boolean;
15
+ };
16
+ }
17
+ /** The names, as the code that spawns each child derives them (lifecycle.ts, serve.impl.ts, main.devserver.ts, lens-children.ts). */
18
+ export declare function gatewayProcessNames(services: ReadonlyArray<Pick<ServiceCtl, 'project' | 'wsKey' | 'root' | 'decl'>>, opts: GatewayProcessNamesOptions): string[];
@@ -0,0 +1 @@
1
+ "use strict";var l=Object.defineProperty;var c=(o,e)=>l(o,"name",{value:e,configurable:!0});var t=Object.defineProperty,n=c((o,e)=>t(o,"name",{value:e,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.gatewayProcessNames=gatewayProcessNames;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),serve_launch_1=require("./serve-launch");function gatewayProcessNames(o,e){const s=[];for(const r of o){const a=r.wsKey||path.basename(r.root),p=r.decl.service??r.project;e.runner&&s.push((0,process_name_1.processName)(a,r.project,serve_launch_1.SERVE_RUNNER_ROLE)),s.push((0,process_name_1.processName)(a,r.project,"nx"),(0,process_name_1.processName)(a,p,"dev"),(0,process_name_1.processName)(a,p,"pod"))}return e.sharedBuild&&s.push((0,process_name_1.processName)(e.label,protocol_1.BUILD_HOST_ROLE)),e.lens?.dashboard&&s.push((0,process_name_1.processName)("lensmcp",e.lens.key,"dashboard")),e.lens?.mcp&&s.push((0,process_name_1.processName)("lensmcp",e.lens.key,"mcp")),[...new Set(s.filter(Boolean))]}c(gatewayProcessNames,"gatewayProcessNames"),n(gatewayProcessNames,"gatewayProcessNames");
@@ -0,0 +1,74 @@
1
+ import type { ProjectGraph } from '@nx/devkit';
2
+ /** nx's configuration source maps: project root → property path (`targets.serve-hmr.options.x`) →
3
+ * `[file that declared it, plugin that read it]`. */
4
+ export type NxSourceMaps = Record<string, Record<string, [file: string | null, plugin: string]>>;
5
+ /** What one graph computation returns. `sourceMaps` is absent from an nx too old to report them. */
6
+ export interface ComputedProjectGraph {
7
+ projectGraph: ProjectGraph;
8
+ sourceMaps?: NxSourceMaps;
9
+ }
10
+ /** Compute the workspace's project graph — the same call `nx run` makes before it starts a task. */
11
+ export type ComputeProjectGraph = () => Promise<ComputedProjectGraph>;
12
+ export interface ProjectGraphSnapshot extends ComputedProjectGraph {
13
+ /** The workspace root the graph describes (absolute). */
14
+ readonly root: string;
15
+ /** When the computation that produced it started (ms epoch). */
16
+ readonly startedAt: number;
17
+ /** Declaring file (absolute) → its stat signature when the computation started. */
18
+ readonly files: ReadonlyMap<string, string>;
19
+ }
20
+ export interface ProjectGraphStore {
21
+ readonly root: string;
22
+ current(): ProjectGraphSnapshot | undefined;
23
+ /** Why the current snapshot no longer describes the files on disk (`undefined` = it still does). One
24
+ * `stat` per declaring file — a few hundred on a large workspace, a few milliseconds. */
25
+ staleReason(): string | undefined;
26
+ /** Recompute now. Concurrent callers share one computation; a failure keeps the previous snapshot. */
27
+ refresh(): Promise<ProjectGraphSnapshot | undefined>;
28
+ /** Kick a refresh when the snapshot is stale. Never awaits, never throws — the sweeper calls it every tick. */
29
+ refreshIfStale(): void;
30
+ /** The last computation's error message, until one succeeds. */
31
+ lastError(): string | undefined;
32
+ }
33
+ export interface ProjectGraphStoreOptions {
34
+ /** A snapshot computed somewhere else, with its own fingerprint — what a registering workspace forwards. */
35
+ seed?: ProjectGraphSnapshot;
36
+ /** Why there is no snapshot yet (`lastError`), until a computation succeeds. */
37
+ error?: string;
38
+ /** After a failed computation, `refreshIfStale` waits this long before computing again. Default 0: every call
39
+ * may retry (an in-process computation); a child process per sweep tick is too much to retry that often. */
40
+ retryAfterFailureMs?: number;
41
+ /** Called after every computation that succeeded. */
42
+ onRefreshed?: (snapshot: ProjectGraphSnapshot) => void;
43
+ }
44
+ /** The gateway's project graphs, one store per workspace root it serves — its own and each registered one's. */
45
+ export interface ProjectGraphStores {
46
+ /** The store for this workspace root (compared as the filesystem names it), if the gateway holds one. */
47
+ get(root: string): ProjectGraphStore | undefined;
48
+ /** Every store — the sweeper keeps each one current. */
49
+ all(): ProjectGraphStore[];
50
+ /** Hold `store` for its root, replacing any store that root had (a re-registration). */
51
+ set(store: ProjectGraphStore): void;
52
+ /** Forget the store of a workspace that left. */
53
+ delete(root: string): void;
54
+ }
55
+ export declare function createProjectGraphStores(...initial: ProjectGraphStore[]): ProjectGraphStores;
56
+ /** `mtime:size:inode` of a file, or `-` when it does not exist. */
57
+ export declare function statSignature(file: string): string;
58
+ /**
59
+ * "Now" on the filesystem's clock, read by writing a file — so a later write compares like with like.
60
+ * `Date.now()` is whole milliseconds while an mtime has a fraction, so a file written a moment BEFORE the
61
+ * computation began read as written during it.
62
+ */
63
+ export declare function filesystemNow(): number;
64
+ /** The files whose contents DECLARE the graph's configuration: `nx.json` plus every project's
65
+ * `project.json` and `package.json` (nx merges both into the project's targets). */
66
+ export declare function declaringFiles(root: string, graph: ProjectGraph): string[];
67
+ /**
68
+ * The graph computation the gateway runs in its own process, through the workspace's own nx (the one
69
+ * `nx run` there would use). With the nx daemon on this is one IPC round trip; with it off nx
70
+ * recomputes in-process, reusing its file-hash and plugin caches, and rewrites the on-disk graph cache
71
+ * the executor reads.
72
+ */
73
+ export declare function computeProjectGraphInProcess(root: string): Promise<ComputedProjectGraph>;
74
+ export declare function createProjectGraphStore(root: string, compute: ComputeProjectGraph, options?: ProjectGraphStoreOptions): ProjectGraphStore;
@@ -0,0 +1 @@
1
+ "use strict";var m=Object.defineProperty;var s=(t,e)=>m(t,"name",{value:e,configurable:!0});var g=Object.defineProperty,o=s((t,e)=>g(t,"name",{value:e,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createProjectGraphStores=createProjectGraphStores,exports.statSignature=statSignature,exports.filesystemNow=filesystemNow,exports.declaringFiles=declaringFiles,exports.computeProjectGraphInProcess=computeProjectGraphInProcess,exports.createProjectGraphStore=createProjectGraphStore;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),node_module_1=require("node:module"),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),registration_guards_1=require("./registration-guards");function createProjectGraphStores(...t){const e=new Map,n=o(r=>{e.set((0,registration_guards_1.canonicalRoot)(r.root),r)},"set");for(const r of t)n(r);return{get:o(r=>e.get((0,registration_guards_1.canonicalRoot)(r)),"get"),all:o(()=>[...e.values()],"all"),set:n,delete:o(r=>{e.delete((0,registration_guards_1.canonicalRoot)(r))},"delete")}}s(createProjectGraphStores,"createProjectGraphStores"),o(createProjectGraphStores,"createProjectGraphStores");function statSignature(t){const e=fs.statSync(t,{throwIfNoEntry:!1});return e?`${e.mtimeMs}:${e.size}:${e.ino}`:"-"}s(statSignature,"statSignature"),o(statSignature,"statSignature");const CHANGED_DURING_COMPUTE="?";function filesystemNow(){const t=path.join(os.tmpdir(),`lensmcp-graph-clock-${process.pid}`);try{fs.writeFileSync(t,"");const e=fs.statSync(t).mtimeMs;return fs.unlinkSync(t),e}catch{return Date.now()}}s(filesystemNow,"filesystemNow"),o(filesystemNow,"filesystemNow");function modifiedAfter(t,e){const n=fs.statSync(t,{throwIfNoEntry:!1});return!!n&&n.mtimeMs>e}s(modifiedAfter,"modifiedAfter"),o(modifiedAfter,"modifiedAfter");function declaringFiles(t,e){const n=[path.join(t,"nx.json")];for(const r of Object.values(e.nodes)){const c=path.join(t,r.data.root);n.push(path.join(c,"project.json"),path.join(c,"package.json"))}return[...new Set(n)]}s(declaringFiles,"declaringFiles"),o(declaringFiles,"declaringFiles");async function computeProjectGraphInProcess(t){const e=(0,node_module_1.createRequire)(path.join(t,"package.json"))("nx/src/project-graph/project-graph");return e.createProjectGraphAndSourceMapsAsync?e.createProjectGraphAndSourceMapsAsync():{projectGraph:await e.createProjectGraphAsync()}}s(computeProjectGraphInProcess,"computeProjectGraphInProcess"),o(computeProjectGraphInProcess,"computeProjectGraphInProcess");function createProjectGraphStore(t,e,n={}){let r=n.seed,c,p=n.seed?void 0:n.error,f=0;const h=o(()=>{if(!r)return"no project graph yet";for(const[u,d]of r.files)if(statSignature(u)!==d)return`${path.relative(t,u)} changed since the gateway read the project graph`},"staleReason"),j=o(()=>(c??=(async()=>{const u=Date.now(),d=filesystemNow(),y=new Map;if(r)for(const a of r.files.keys())y.set(a,statSignature(a));try{const a=await e(),i=new Map;for(const l of declaringFiles(t,a.projectGraph))i.set(l,y.get(l)??(modifiedAfter(l,d)?CHANGED_DURING_COMPUTE:statSignature(l)));r={root:t,startedAt:u,files:i,...a},p=void 0,f=0,n.onRefreshed?.(r)}catch(a){const i=a?.message??String(a);i!==p&&console.warn(`[gateway] project graph refresh failed for ${t} (its services start through \`nx run\` until it succeeds): ${i}`),p=i,f=Date.now()+(n.retryAfterFailureMs??0)}return r})().finally(()=>{c=void 0}),c),"refresh");return{root:t,current:o(()=>r,"current"),staleReason:h,refresh:j,refreshIfStale:o(()=>{c||Date.now()<f||!h()||j()},"refreshIfStale"),lastError:o(()=>p,"lastError")}}s(createProjectGraphStore,"createProjectGraphStore"),o(createProjectGraphStore,"createProjectGraphStore");
@@ -13,7 +13,7 @@ import type { ServiceLayer } from './lifecycle';
13
13
  * then re-sent the same method, the same URL and the same credentials — the caller's cookies, its
14
14
  * `authorization`, the gateway's own stamped `x-internal-token` and `x-forwarded-*` identity — every
15
15
  * 300ms for the whole 8s heal window, i.e. up to ~26 replays of a side-effecting call the origin had
16
- * already applied (issues/codex-security/28). Node flushes a `ClientRequest`'s header block the moment
16
+ * already applied (issues/closed/codex-security-28). Node flushes a `ClientRequest`'s header block the moment
17
17
  * its socket connects, so those replays reach the upstream's request handler even though the retry can
18
18
  * no longer produce a body — which is the second half of the same defect:
19
19
  *
@@ -25,7 +25,7 @@ import type { ServiceLayer } from './lifecycle';
25
25
  * byte-for-byte.
26
26
  *
27
27
  * The prod gateway reached the same place in round 2 by cutting its retry count to 1
28
- * (issues/prod-gateway-2/07); the dev path never inherited it — the *two gateways, two codebases* rule.
28
+ * (issues/closed/prod-gateway-2-07); the dev path never inherited it — the *two gateways, two codebases* rule.
29
29
  */
30
30
  export declare function isReplayableRequest(req: Pick<http.IncomingMessage, 'method' | 'headers'>): boolean;
31
31
  export interface ProxyServer {
@@ -1 +1 @@
1
- "use strict";var G=Object.defineProperty;var P=(n,c)=>G(n,"name",{value:c,configurable:!0});var K=Object.defineProperty,s=P((n,c)=>K(n,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.createProxy=createProxy;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),edge_1=require("./edge"),gateway_errors_1=require("../gateway-errors"),body_stall_1=require("../body-stall"),types_1=require("./types"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys");function isConnError(n){const c=n.code??"";return c==="ECONNREFUSED"||c==="ECONNRESET"||c==="ETIMEDOUT"||c==="EPIPE"||c==="EHOSTUNREACH"||c==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(n.message)}P(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(n){if(!IDEMPOTENT_METHODS.has((n.method??"GET").toUpperCase())||n.headers["transfer-encoding"]!==void 0)return!1;const c=n.headers["content-length"];return!(c!==void 0&&String(c).trim()!=="0")}P(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(n,c){return c.headersSent||c.writableEnded||c.destroyed===!0||n.aborted===!0}P(edgeGone,"edgeGone"),s(edgeGone,"edgeGone");const HOP_BY_HOP=new Set(["connection","keep-alive","proxy-connection","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),UPSTREAM_CONNECTION_HEADERS=["connection","keep-alive","proxy-connection"],httpProxy=require("http-proxy");function declaredFirstByteMs(n){const c=Number(n);return Number.isFinite(c)&&c>0?c:types_1.POD_RESPONSE_TIMEOUT_MS}P(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");function armFirstByteGuard(n){let c,v=0,O=!1,A=!1;const M=s(k=>{c=setTimeout(C,k),c.unref?.()},"arm"),C=s(()=>{if(!(O||!n.pending())){if(v+=n.firstByteMs,n.ceilingMs<=0||v>=n.ceilingMs){n.onWedged(`no response within ${v}ms (wedged socket) \u2014 evicted + respawning`);return}(0,pod_probe_1.probePodAlive)(n.socketPath,void 0,n.serviceKey).then(k=>{if(!(O||!n.pending())){if(!k){n.onWedged(`no response within ${v}ms and no answer on ${types_1.POD_ALIVE_PATH} (wedged socket) \u2014 evicted + respawning`);return}A||(A=!0,n.onSlow(v,k)),M(Math.max(n.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))}})}},"onDeadline");return M(n.firstByteMs),{clear:s(()=>{O=!0,c&&clearTimeout(c)},"clear")}}P(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(n,c,v){const{recordEdge:O,finishTrace:A,traceStep:M,emit:C}=c,k=s(async e=>{if(!e.lens||!n.lensSupervisor)return"unsupported";let r;const t=new Promise(p=>{r=setTimeout(()=>p("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),u=n.lensSupervisor.recover(e.lens,"proxy-connection").then(p=>p?"ready":"failed").catch(()=>"failed"),h=await Promise.race([u,t]);r&&clearTimeout(r);const m=n.lensSupervisor.status(e.lens);return h==="failed"&&(!m||m.state==="ready")?"unsupported":h},"waitForManagedRecovery"),U=s((e,r,t,u,h,m)=>{if(t.headersSent||t.writableEnded||t.destroyed){t.destroyed||t.destroy();return}const p=e.lens?n.lensSupervisor?.status(e.lens):void 0,E=m==="timeout"||p?.state==="starting"||p?.state==="replacing",_=(0,gateway_errors_1.ensureRequestId)(r),d=e.lens?{project:e.project,reason:p?.reason??"connection-failure",generation:p?.generation??0,...E?{retryAfterMs:2e3}:{}}:void 0,o=E?503:502,a=E?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${_} project=${e.project} target=${String(u)} state=${p?.state??"unmanaged"} generation=${p?.generation??0}: ${h.message}`),C("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:_,project:e.project,target:String(u),state:p?.state??"unmanaged",generation:p?.generation,error:h.message}),(0,edge_1.sendError)(t,r,o,a,E?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,d,E?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),x=(0,body_stall_1.createBodyStallGuard)(n.bodyStallMs??types_1.BODY_STALL_MS),I={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},D=new http.Agent(I),q=new(require("node:https")).Agent(I),N=s(e=>typeof e=="string"&&e.startsWith("https:")?q:D,"agentForTarget"),R=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:D}),L=s((e,r,t)=>{const u=s(()=>{t()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",u),e.once("close",()=>r.removeListener("close",u))},"propagateEdgeCancel");R.on("proxyReq",(e,r,t)=>{for(const u of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(u);e.setHeader("connection","keep-alive"),L(e,t,()=>t.writableEnded)}),R.on("proxyRes",(e,r,t)=>{const u=r.__lensmcpPod;u&&(e.headers["x-lensmcp-pod"]=u),delete e.headers["x-powered-by"],e.headers.server="LensMCP",t&&L(e,t,()=>t.writableEnded),t&&x.armResponse(t)});const B=s((e,r,t,u)=>{(0,edge_1.stampForwardingHeaders)(n,r),x.arm(r);const h={},m=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[i,y]of Object.entries(r.headers))y===void 0||i.startsWith(":")||HOP_BY_HOP.has(i)||m&&(i==="content-length"||i==="transfer-encoding")||(h[i]=y);let p=http,E;if(typeof e=="object")E={socketPath:e.socketPath,path:r.url,method:r.method,headers:h,agent:D};else{const i=new URL(e),y=i.protocol==="https:";p=y?require("node:https"):http,h.host=i.host,E={protocol:i.protocol,hostname:i.hostname,port:i.port||(y?443:80),path:r.url,method:r.method,headers:h,agent:N(e)}}const _=t;_.__lensmcpErrContained||(_.__lensmcpErrContained=!0,t.on("error",()=>{}));let d;const o=s(()=>{t.writableEnded||g.destroy()},"onReqGone"),a=s(()=>{d&&!d.destroyed&&d.destroy()},"onEdgeClose"),l=s(()=>{r.removeListener("error",o),r.removeListener("aborted",o),r.removeListener("close",o),t.removeListener("close",a)},"detach"),g=p.request(E,i=>{if(d=i,u.onResponse(),t.headersSent||t.writableEnded||t.destroyed){i.destroy();return}const y={};for(const[w,f]of Object.entries(i.headers))f===void 0||HOP_BY_HOP.has(w)||(y[w]=f);delete y["x-powered-by"],y.server="LensMCP";const j=r.__lensmcpPod;j&&(y["x-lensmcp-pod"]=j);try{t.writeHead(i.statusCode??502,y)}catch(w){console.error("[gateway] h2 relay: bad response header, resetting stream:",w.message),i.destroy(),t.destroyed||t.destroy(w);return}i.pipe(t),x.armResponse(t);const $=s(()=>{!t.writableEnded&&!t.destroyed&&t.destroy()},"abortEdge");i.once("error",$),i.once("close",$),t.on("close",a)});g.once("error",i=>u.onError(i)),g.once("close",l),r.on("error",o),r.on("aborted",o),r.on("close",o),m?g.end():r.pipe(g)},"pipeUpstream"),W=s(async(e,r,t)=>{const u=r.__lensmcpTrace;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 o=(0,discovery_1.pickSock)(e.pool,n.scanTtlMs);if(!o&&d&&(M(u,"cold-start",{project:e.project}),await v.ensureUp(d)&&(o=(0,discovery_1.pickSock)(e.pool,n.scanTtlMs))),d&&o&&v.markUp(d,"observed"),d&&v.scaleUp(d),!o){const f=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}).${f} The watcher recovers and the next request retries automatically.`);return}const a=path.basename(o,".sock");r.__lensmcpPod=a,M(u,"pod-select",{pod:a,pods:e.pool.socks.length,strategy:"round-robin"});let l=!1;const g=s(f=>{e.pool.socks=e.pool.socks.filter(S=>S!==o),e.pool.idx=-1;try{fs.unlinkSync(o)}catch{}console.error(`[gateway] pod ${a} of ${e.project}: ${f}`)},"evictPod"),i=armFirstByteGuard({socketPath:o,serviceKey:n.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!l&&!t.headersSent&&!t.writableEnded,"pending"),onWedged:s(f=>{l=!0,g(f),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${a} of ${e.project} was unresponsive \u2014 evicted, retry.`)},"onWedged"),onSlow:s((f,S)=>{console.log(`[gateway] pod ${a} of ${e.project}: slow first byte, pod alive (${f}ms, ${S.inflight} in-flight, ${S.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});t.on("close",()=>i.clear());const y=o,j=isReplayableRequest(r);let $=0;const w=s(()=>{$+=1,B({socketPath:y},r,t,{onResponse:s(()=>{i.clear(),l=!0},"onResponse"),onError:s(f=>{if(!l){if(edgeGone(r,t)){i.clear(),l=!0;return}(0,pod_probe_1.podConnectionVerdict)(y,f).then(S=>{if(!l){if(S==="alive"&&$<2&&j&&!edgeGone(r,t)){console.log(`[gateway] pod ${a} of ${e.project}: ${f.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),w();return}if(i.clear(),l=!0,S==="gone"){g(f.message),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${a} of ${e.project} unavailable \u2014 retry.`);return}console.error(`[gateway] pod ${a} 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 ${a} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");w();return}const h=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let m=0,p=!1;const E=isReplayableRequest(r),_=s(()=>{if(edgeGone(r,t))return;const d=e.target;B(d,r,t,{onResponse:s(()=>{},"onResponse"),onError:s(o=>{if(isConnError(o)&&(m+=1),e.lens&&isConnError(o)&&E&&m>=2&&!p&&!edgeGone(r,t)){p=!0,k(e).then(a=>{if(!edgeGone(r,t)){if(a==="ready"){_();return}if(a==="unsupported"&&Date.now()<h){setTimeout(_,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}U(e,r,t,e.target,o,a)}});return}if(isConnError(o)&&E&&Date.now()<h&&!edgeGone(r,t)){setTimeout(_,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(t.headersSent||t.writableEnded||t.destroyed){t.destroyed||t.destroy();return}U(e,r,t,d,o)},"onError")})},"tryTcp");_()},"forwardNative");return{proxy:R,forward:s(async(e,r,t)=>{const u=Date.now(),h=r.__lensmcpTrace;if(t.on("close",()=>{O(e,Date.now()-u,t.statusCode??0,r.__lensmcpCaller),A(h,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)(n,r),x.arm(r),(r.httpVersionMajor??1)>=2){await W(e,r,t);return}if(e.pool){const o=e.svc;o&&(o.lastUsed=Date.now(),o.inflight+=1,t.on("close",()=>{o.inflight-=1,o.lastUsed=Date.now()}));let a=(0,discovery_1.pickSock)(e.pool,n.scanTtlMs);if(!a&&o&&(M(h,"cold-start",{project:e.project}),await v.ensureUp(o)&&(a=(0,discovery_1.pickSock)(e.pool,n.scanTtlMs))),o&&a&&v.markUp(o,"observed"),o&&v.scaleUp(o),!a){const T=o?.lastErrors?.length?` Service errors: ${o.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(t,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${T} The watcher recovers and the next request retries automatically.`);return}const l=path.basename(a,".sock");r.__lensmcpPod=l,M(h,"pod-select",{pod:l,pods:e.pool.socks.length,strategy:"round-robin"});let g=!1;const i=s(T=>{e.pool.socks=e.pool.socks.filter(b=>b!==a),e.pool.idx=-1;try{fs.unlinkSync(a)}catch{}console.error(`[gateway] pod ${l} of ${e.project}: ${T}`)},"evictPod"),y=armFirstByteGuard({socketPath:a,serviceKey:n.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!g&&!t.headersSent&&!t.writableEnded,"pending"),onWedged:s(T=>{g=!0,i(T),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${l} of ${e.project} was unresponsive \u2014 evicted, retry.`)},"onWedged"),onSlow:s((T,b)=>{console.log(`[gateway] pod ${l} of ${e.project}: slow first byte, pod alive (${T}ms, ${b.inflight} in-flight, ${b.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});t.on("close",()=>y.clear());const j=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),$=a,w=isReplayableRequest(r);let f=0;const S=s(()=>{f+=1;const T=Date.now();R.web(r,t,{target:{socketPath:$},agent:D,proxyTimeout:j},b=>{if(!g){if(edgeGone(r,t)){y.clear(),g=!0;return}(0,pod_probe_1.podConnectionVerdict)($,b).then(H=>{if(g)return;const F=Date.now()-T>=j-250;if(H==="alive"&&f<2&&w&&!F&&!edgeGone(r,t)){console.log(`[gateway] pod ${l} of ${e.project}: ${b.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),S();return}if(y.clear(),g=!0,H==="gone"){i(b.message),(0,edge_1.sendError)(t,r,502,"unavailable",`Pod ${l} of ${e.project} unavailable \u2014 retry.`);return}console.error(`[gateway] pod ${l} of ${e.project}: ${b.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 ${l} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");S();return}const m=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let p=0,E=!1;const _=isReplayableRequest(r),d=s(()=>{if(edgeGone(r,t))return;const o=e.target,a={target:o,autoRewrite:typeof o=="string",changeOrigin:!0,agent:N(o)};R.web(r,t,a,l=>{if(isConnError(l)&&(p+=1),e.lens&&isConnError(l)&&_&&p>=2&&!E&&!edgeGone(r,t)){E=!0,k(e).then(g=>{if(!edgeGone(r,t)){if(g==="ready"){d();return}if(g==="unsupported"&&Date.now()<m){setTimeout(d,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}U(e,r,t,e.target,l,g)}});return}if(isConnError(l)&&_&&Date.now()<m&&!edgeGone(r,t)){setTimeout(d,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}t.destroyed||U(e,r,t,o,l)})},"tryTcp");d()},"forward"),agentForTarget:N,destroyAgents:s(()=>{x.stop(),D.destroy(),q.destroy()},"destroyAgents"),closeProxy:s(()=>{try{R.close?.()}catch{}},"closeProxy")}}P(createProxy,"createProxy"),s(createProxy,"createProxy");
1
+ "use strict";var G=Object.defineProperty;var j=(t,c)=>G(t,"name",{value:c,configurable:!0});var K=Object.defineProperty,s=j((t,c)=>K(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isReplayableRequest=isReplayableRequest,exports.createProxy=createProxy;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),edge_1=require("./edge"),gateway_errors_1=require("../gateway-errors"),body_stall_1=require("../body-stall"),types_1=require("./types"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys");function isConnError(t){const c=t.code??"";return c==="ECONNREFUSED"||c==="ECONNRESET"||c==="ETIMEDOUT"||c==="EPIPE"||c==="EHOSTUNREACH"||c==="ENOENT"||/ECONNREFUSED|ECONNRESET|socket hang up|EPIPE/i.test(t.message)}j(isConnError,"isConnError"),s(isConnError,"isConnError");const IDEMPOTENT_METHODS=new Set(["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"]);function isReplayableRequest(t){if(!IDEMPOTENT_METHODS.has((t.method??"GET").toUpperCase())||t.headers["transfer-encoding"]!==void 0)return!1;const c=t.headers["content-length"];return!(c!==void 0&&String(c).trim()!=="0")}j(isReplayableRequest,"isReplayableRequest"),s(isReplayableRequest,"isReplayableRequest");function edgeGone(t,c){return c.headersSent||c.writableEnded||c.destroyed===!0||t.aborted===!0}j(edgeGone,"edgeGone"),s(edgeGone,"edgeGone");const HOP_BY_HOP=new Set(["connection","keep-alive","proxy-connection","proxy-authenticate","proxy-authorization","te","trailer","transfer-encoding","upgrade"]),UPSTREAM_CONNECTION_HEADERS=["connection","keep-alive","proxy-connection"],httpProxy=require("http-proxy");function declaredFirstByteMs(t){const c=Number(t);return Number.isFinite(c)&&c>0?c:types_1.POD_RESPONSE_TIMEOUT_MS}j(declaredFirstByteMs,"declaredFirstByteMs"),s(declaredFirstByteMs,"declaredFirstByteMs");function armFirstByteGuard(t){let c,$=!1,q=!1,w=(0,body_stall_1.requestBodyArriving)(t.req)?void 0:Date.now();const O=s(()=>{w??=Date.now()},"onBodyEnd");w===void 0&&t.req.once("end",O);const U=s(()=>(w===void 0&&!(0,body_stall_1.requestBodyArriving)(t.req)&&(w=Date.now()),w===void 0?0:Date.now()-w),"heldMs"),D=s(S=>{c=setTimeout(A,S),c.unref?.()},"arm"),A=s(()=>{if($||!t.pending())return;const S=U();if(t.ceilingMs<=0){if(w!==void 0&&S>=t.firstByteMs){t.onWedged(`no response within ${S}ms (wedged socket) \u2014 evicted + respawning`);return}D(w===void 0?t.firstByteMs:t.firstByteMs-S);return}(0,pod_probe_1.probePodAlive)(t.socketPath,void 0,t.serviceKey).then(x=>{if($||!t.pending())return;const T=U();if(!x){t.onWedged(`no response within ${T}ms and no answer on ${types_1.POD_ALIVE_PATH} (wedged socket) \u2014 evicted + respawning`);return}if(T>=t.ceilingMs){t.onCeiling(`alive on ${types_1.POD_ALIVE_PATH} but no response within ${T}ms (ceiling ${t.ceilingMs}ms) \u2014 giving up on this request, pod kept`,x);return}!q&&w!==void 0&&(q=!0,t.onSlow(T,x)),D(Math.max(t.firstByteMs,types_1.POD_ALIVE_PROBE_TIMEOUT_MS))})},"onDeadline");return D(t.firstByteMs),{clear:s(()=>{$=!0,c&&clearTimeout(c),t.req.removeListener("end",O)},"clear")}}j(armFirstByteGuard,"armFirstByteGuard"),s(armFirstByteGuard,"armFirstByteGuard");function createProxy(t,c,$){const{recordEdge:q,finishTrace:w,traceStep:O,emit:U}=c,D=s(async e=>{if(!e.lens||!t.lensSupervisor)return"unsupported";let r;const o=new Promise(p=>{r=setTimeout(()=>p("timeout"),types_1.LENS_REQUEST_RECOVERY_WAIT_MS),r.unref?.()}),g=t.lensSupervisor.recover(e.lens,"proxy-connection").then(p=>p?"ready":"failed").catch(()=>"failed"),h=await Promise.race([g,o]);r&&clearTimeout(r);const m=t.lensSupervisor.status(e.lens);return h==="failed"&&(!m||m.state==="ready")?"unsupported":h},"waitForManagedRecovery"),A=s((e,r,o,g,h,m)=>{if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}const p=e.lens?t.lensSupervisor?.status(e.lens):void 0,E=m==="timeout"||p?.state==="starting"||p?.state==="replacing",v=(0,gateway_errors_1.ensureRequestId)(r),d=e.lens?{project:e.project,reason:p?.reason??"connection-failure",generation:p?.generation??0,...E?{retryAfterMs:2e3}:{}}:void 0,n=E?503:502,a=E?"starting":"unavailable";console.error(`[gateway] ${new Date().toISOString()} upstream-error traceId=${v} project=${e.project} target=${String(g)} state=${p?.state??"unmanaged"} generation=${p?.generation??0}: ${h.message}`),U("warning",`upstream unavailable: ${e.project}`,`cluster-upstream:${e.project}`,{kind:"upstream-error",traceId:v,project:e.project,target:String(g),state:p?.state??"unmanaged",generation:p?.generation,error:h.message}),(0,edge_1.sendError)(o,r,n,a,E?`Upstream ${e.project} is restarting.`:`Upstream ${e.project} unavailable.`,d,E?{"retry-after":"2"}:void 0)},"sendTerminalUpstreamError"),S=(0,body_stall_1.createBodyStallGuard)(t.bodyStallMs??types_1.BODY_STALL_MS),x={keepAlive:!0,keepAliveMsecs:3e4,maxSockets:1024,maxFreeSockets:256},T=new http.Agent(x),I=new(require("node:https")).Agent(x),L=s(e=>typeof e=="string"&&e.startsWith("https:")?I:T,"agentForTarget"),C=httpProxy.createProxyServer({xfwd:!1,secure:!1,proxyTimeout:12e4,agent:T}),N=s((e,r,o)=>{const g=s(()=>{o()||e.destroyed||e.destroy()},"onEdgeClose");r.once("close",g),e.once("close",()=>r.removeListener("close",g))},"propagateEdgeCancel");C.on("proxyReq",(e,r,o)=>{for(const g of UPSTREAM_CONNECTION_HEADERS)e.removeHeader(g);e.setHeader("connection","keep-alive"),N(e,o,()=>o.writableEnded)}),C.on("proxyRes",(e,r,o)=>{const g=r.__lensmcpPod;g&&(e.headers["x-lensmcp-pod"]=g),delete e.headers["x-powered-by"],e.headers.server="LensMCP",o&&N(e,o,()=>o.writableEnded),o&&S.armResponse(o)});const H=s((e,r,o,g)=>{(0,edge_1.stampForwardingHeaders)(t,r),S.arm(r);const h={},m=r.method==="GET"||r.method==="HEAD"||r.method==="OPTIONS";for(const[i,f]of Object.entries(r.headers))f===void 0||i.startsWith(":")||HOP_BY_HOP.has(i)||m&&(i==="content-length"||i==="transfer-encoding")||(h[i]=f);let p=http,E;if(typeof e=="object")E={socketPath:e.socketPath,path:r.url,method:r.method,headers:h,agent:T};else{const i=new URL(e),f=i.protocol==="https:";p=f?require("node:https"):http,h.host=i.host,E={protocol:i.protocol,hostname:i.hostname,port:i.port||(f?443:80),path:r.url,method:r.method,headers:h,agent:L(e)}}const v=o;v.__lensmcpErrContained||(v.__lensmcpErrContained=!0,o.on("error",()=>{}));let d;const n=s(()=>{o.writableEnded||u.destroy()},"onReqGone"),a=s(()=>{d&&!d.destroyed&&d.destroy()},"onEdgeClose"),l=s(()=>{r.removeListener("error",n),r.removeListener("aborted",n),r.removeListener("close",n),o.removeListener("close",a)},"detach"),u=p.request(E,i=>{if(d=i,g.onResponse(),o.headersSent||o.writableEnded||o.destroyed){i.destroy();return}const f={};for(const[P,y]of Object.entries(i.headers))y===void 0||HOP_BY_HOP.has(P)||(f[P]=y);delete f["x-powered-by"],f.server="LensMCP";const R=r.__lensmcpPod;R&&(f["x-lensmcp-pod"]=R);try{o.writeHead(i.statusCode??502,f)}catch(P){console.error("[gateway] h2 relay: bad response header, resetting stream:",P.message),i.destroy(),o.destroyed||o.destroy(P);return}i.pipe(o),S.armResponse(o);const k=s(()=>{!o.writableEnded&&!o.destroyed&&o.destroy()},"abortEdge");i.once("error",k),i.once("close",k),o.on("close",a)});u.once("error",i=>g.onError(i)),u.once("close",l),r.on("error",n),r.on("aborted",n),r.on("close",n),m?u.end():r.pipe(u)},"pipeUpstream"),W=s(async(e,r,o)=>{const g=r.__lensmcpTrace;if(e.pool){const d=e.svc;d&&(d.lastUsed=Date.now(),d.inflight+=1,o.on("close",()=>{d.inflight-=1,d.lastUsed=Date.now()}));let n=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!n&&d&&(O(g,"cold-start",{project:e.project}),await $.ensureUp(d)&&(n=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),d&&n&&$.markUp(d,"observed"),d&&$.scaleUp(d),!n){const y=d?.lastErrors?.length?` Service errors: ${d.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${y} The watcher recovers and the next request retries automatically.`);return}const a=path.basename(n,".sock");r.__lensmcpPod=a,O(g,"pod-select",{pod:a,pods:e.pool.socks.length,strategy:"round-robin"});let l=!1;const u=s((y,b="unlink")=>{if(e.pool.socks=e.pool.socks.filter(_=>_!==n),e.pool.idx=-1,b==="unlink")try{fs.unlinkSync(n)}catch{}console.error(`[gateway] pod ${a} of ${e.project}: ${y}`)},"evictPod"),i=armFirstByteGuard({socketPath:n,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!l&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s(y=>{l=!0,u(y),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${a} of ${e.project} was unresponsive \u2014 evicted, retry.`)},"onWedged"),onCeiling:s(y=>{l=!0,u(y,"keep"),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${a} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs.`)},"onCeiling"),onSlow:s((y,b)=>{console.log(`[gateway] pod ${a} of ${e.project}: slow first byte, pod alive (${y}ms, ${b.inflight} in-flight, ${b.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>i.clear());const f=n,R=isReplayableRequest(r);let k=0;const P=s(()=>{k+=1,H({socketPath:f},r,o,{onResponse:s(()=>{i.clear(),l=!0},"onResponse"),onError:s(y=>{if(!l){if(edgeGone(r,o)){i.clear(),l=!0;return}(0,pod_probe_1.podConnectionVerdict)(f,y).then(b=>{if(!l){if(b==="alive"&&k<2&&R&&!edgeGone(r,o)){console.log(`[gateway] pod ${a} of ${e.project}: ${y.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),P();return}if(i.clear(),l=!0,b==="gone"){u(y.message),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${a} of ${e.project} unavailable \u2014 retry.`);return}console.error(`[gateway] pod ${a} of ${e.project}: ${y.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${a} of ${e.project} dropped the connection \u2014 retry.`)}})}},"onError")})},"sendToPod");P();return}const h=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let m=0,p=!1;const E=isReplayableRequest(r),v=s(()=>{if(edgeGone(r,o))return;const d=e.target;H(d,r,o,{onResponse:s(()=>{},"onResponse"),onError:s(n=>{if(isConnError(n)&&(m+=1),e.lens&&isConnError(n)&&E&&m>=2&&!p&&!edgeGone(r,o)){p=!0,D(e).then(a=>{if(!edgeGone(r,o)){if(a==="ready"){v();return}if(a==="unsupported"&&Date.now()<h){setTimeout(v,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}A(e,r,o,e.target,n,a)}});return}if(isConnError(n)&&E&&Date.now()<h&&!edgeGone(r,o)){setTimeout(v,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}if(o.headersSent||o.writableEnded||o.destroyed){o.destroyed||o.destroy();return}A(e,r,o,d,n)},"onError")})},"tryTcp");v()},"forwardNative");return{proxy:C,forward:s(async(e,r,o)=>{const g=Date.now(),h=r.__lensmcpTrace;if(o.on("close",()=>{q(e,Date.now()-g,o.statusCode??0,r.__lensmcpCaller),w(h,e.project,o.statusCode??0,{...r.__lensmcpPod?{pod:r.__lensmcpPod}:{},...r.__lensmcpCaller?{caller:r.__lensmcpCaller}:{}},e)}),e.prependPrefix&&!r.url?.startsWith(e.prependPrefix)&&(r.url=e.prependPrefix+(r.url??"/")),(0,edge_1.stampForwardingHeaders)(t,r),S.arm(r),(r.httpVersionMajor??1)>=2){await W(e,r,o);return}if(e.pool){const n=e.svc;n&&(n.lastUsed=Date.now(),n.inflight+=1,o.on("close",()=>{n.inflight-=1,n.lastUsed=Date.now()}));let a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs);if(!a&&n&&(O(h,"cold-start",{project:e.project}),await $.ensureUp(n)&&(a=(0,discovery_1.pickSock)(e.pool,t.scanTtlMs))),n&&a&&$.markUp(n,"observed"),n&&$.scaleUp(n),!a){const _=n?.lastErrors?.length?` Service errors: ${n.lastErrors.join("; ")}`:"";(0,edge_1.sendError)(o,r,503,"unavailable",`Service ${e.project} is not reachable (no pods in ${e.pool.dir}).${_} The watcher recovers and the next request retries automatically.`);return}const l=path.basename(a,".sock");r.__lensmcpPod=l,O(h,"pod-select",{pod:l,pods:e.pool.socks.length,strategy:"round-robin"});let u=!1;const i=s((_,M="unlink")=>{if(e.pool.socks=e.pool.socks.filter(B=>B!==a),e.pool.idx=-1,M==="unlink")try{fs.unlinkSync(a)}catch{}console.error(`[gateway] pod ${l} of ${e.project}: ${_}`)},"evictPod"),f=armFirstByteGuard({socketPath:a,serviceKey:t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.svc?.wsKey,e.project)],req:r,firstByteMs:declaredFirstByteMs(e.svc?.decl?.firstByteMs),ceilingMs:types_1.POD_SLOW_FIRST_BYTE_MS,pending:s(()=>!u&&!o.headersSent&&!o.writableEnded,"pending"),onWedged:s(_=>{u=!0,i(_),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${l} of ${e.project} was unresponsive \u2014 evicted, retry.`)},"onWedged"),onCeiling:s(_=>{u=!0,i(_,"keep"),(0,edge_1.sendError)(o,r,504,"unavailable",`Pod ${l} of ${e.project} did not answer in time \u2014 retry, or raise cluster.firstByteMs.`)},"onCeiling"),onSlow:s((_,M)=>{console.log(`[gateway] pod ${l} of ${e.project}: slow first byte, pod alive (${_}ms, ${M.inflight} in-flight, ${M.activeWork} background) \u2014 waiting, not evicting. Set cluster.firstByteMs on ${e.project} to size this route's budget.`)},"onSlow")});o.on("close",()=>f.clear());const R=Math.max(12e4,types_1.POD_SLOW_FIRST_BYTE_MS+6e4),k=a,P=isReplayableRequest(r);let y=0;const b=s(()=>{y+=1;const _=Date.now();C.web(r,o,{target:{socketPath:k},agent:T,proxyTimeout:R},M=>{if(!u){if(edgeGone(r,o)){f.clear(),u=!0;return}(0,pod_probe_1.podConnectionVerdict)(k,M).then(B=>{if(u)return;const F=Date.now()-_>=R-250;if(B==="alive"&&y<2&&P&&!F&&!edgeGone(r,o)){console.log(`[gateway] pod ${l} of ${e.project}: ${M.message} on one connection, the pod still accepts \u2014 sending the ${r.method} again on a fresh one`),b();return}if(f.clear(),u=!0,B==="gone"){i(M.message),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${l} of ${e.project} unavailable \u2014 retry.`);return}console.error(`[gateway] pod ${l} of ${e.project}: ${M.message} on one connection \u2014 the pod still accepts connections, so it stays in the pool`),(0,edge_1.sendError)(o,r,502,"unavailable",`Pod ${l} of ${e.project} dropped the connection \u2014 retry.`)})}})},"sendToPod");b();return}const m=Date.now()+types_1.UPSTREAM_HEAL_WINDOW_MS;let p=0,E=!1;const v=isReplayableRequest(r),d=s(()=>{if(edgeGone(r,o))return;const n=e.target,a={target:n,autoRewrite:typeof n=="string",changeOrigin:!0,agent:L(n)};C.web(r,o,a,l=>{if(isConnError(l)&&(p+=1),e.lens&&isConnError(l)&&v&&p>=2&&!E&&!edgeGone(r,o)){E=!0,D(e).then(u=>{if(!edgeGone(r,o)){if(u==="ready"){d();return}if(u==="unsupported"&&Date.now()<m){setTimeout(d,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}A(e,r,o,e.target,l,u)}});return}if(isConnError(l)&&v&&Date.now()<m&&!edgeGone(r,o)){setTimeout(d,types_1.UPSTREAM_HEAL_STEP_MS).unref?.();return}o.destroyed||A(e,r,o,n,l)})},"tryTcp");d()},"forward"),agentForTarget:L,destroyAgents:s(()=>{S.stop(),T.destroy(),I.destroy()},"destroyAgents"),closeProxy:s(()=>{try{C.close?.()}catch{}},"closeProxy")}}j(createProxy,"createProxy"),s(createProxy,"createProxy");
@@ -44,7 +44,7 @@ export interface SourceSetConfig {
44
44
  * `*.stories.*`, anything under `__tests__/`/`__mocks__/`) never count as a source-set change. No
45
45
  * running app can import them, so their arrival cannot make a dev server stale — yet every spec an
46
46
  * agent session wrote rolled BOTH lens vites and scaled six pods to zero (foodguard 2026-09-06:
47
- * `shared/contracts/src/haccp/__tests__/zz-r3probe.spec.ts`; issues/dev-gateway/01). `false` restores
47
+ * `shared/contracts/src/haccp/__tests__/zz-r3probe.spec.ts`; issues/closed/dev-gateway-01). `false` restores
48
48
  * the old count-everything behaviour.
49
49
  */
50
50
  ignoreTests: boolean;
@@ -0,0 +1,118 @@
1
+ import type { NxJsonConfiguration, ProjectGraph, Task, TaskGraph } from '@nx/devkit';
2
+ import type { ProjectGraphStores } from './project-graph-store';
3
+ /** The only executor the runner starts — LensMCP's own serve. Every other one goes through `nx run`. */
4
+ export declare const SERVE_RUNNER_EXECUTOR = "@lensmcp/cluster:serve";
5
+ /** The role in the runner's process name: `<workspace>-<service>-serve` (process-name.ts). */
6
+ export declare const SERVE_RUNNER_ROLE = "serve";
7
+ /** The argument that names the runner's workspace on its command line — how the stale-runner reaper
8
+ * tells one checkout's runner from another's ({@link isServeRunnerCommandOf}). Always the LAST argument. */
9
+ export declare const RUNNER_ROOT_FLAG = "--workspace-root=";
10
+ /** `LENSMCP_SERVE_RUNNER=0` (or `off`/`false`/`no`/`nx`) starts every service through `nx run`. */
11
+ export declare function serveRunnerEnabled(env?: NodeJS.ProcessEnv): boolean;
12
+ /** Where the runner entry is, and what node needs to run it. */
13
+ export interface RunnerScript {
14
+ path: string;
15
+ /** `--require @swc-node/register` when running from source (the same arrangement serve.impl makes for main.devserver). */
16
+ execArgv: string[];
17
+ }
18
+ /** The built `serve-runner.js` at the package root, or the `.ts` beside this source tree; `undefined` when neither exists. */
19
+ export declare function serveRunnerScript(baseDir?: string): RunnerScript | undefined;
20
+ /** The runner's arguments after the script: nothing reads them but `ps` — they make it readable and
21
+ * identifiable (`run <project>:<target>` is what the stale-runner reaper matches, the root comes last). */
22
+ export declare function serveRunnerArgs(target: string, childCount: number, root: string): string[];
23
+ /** Is this command line a serve runner's? */
24
+ export declare function isServeRunnerCommand(cmd: string): boolean;
25
+ /** Is this command line a serve runner started for the workspace at `root`? Exact: the root is the last
26
+ * argument, so `<root>-gate1` or `<root>/.claude/worktrees/x` never reads as `<root>`. */
27
+ export declare function isServeRunnerCommandOf(cmd: string, root: string): boolean;
28
+ /** The IPC message nx's client sends run-executor — the runner takes the same, plus which `run()` to call. */
29
+ export interface RunnerMessage {
30
+ /** Absolute path of the workspace nx's `src/command-line/run/run.js`. */
31
+ nxRunModule: string;
32
+ targetDescription: Task['target'];
33
+ overrides: Task['overrides'];
34
+ isVerbose: boolean;
35
+ taskGraph: TaskGraph;
36
+ }
37
+ export interface RunnerLaunch {
38
+ kind: 'runner';
39
+ taskId: string;
40
+ env: NodeJS.ProcessEnv;
41
+ message: RunnerMessage;
42
+ script: RunnerScript;
43
+ }
44
+ export interface NxLaunch {
45
+ kind: 'nx';
46
+ /** Why the runner was not used — logged with the spawn. */
47
+ reason: string;
48
+ }
49
+ export type ServeLaunchPlan = RunnerLaunch | NxLaunch;
50
+ export interface ServeLaunchRequest {
51
+ /** The gateway's project graphs, one per workspace root it serves: its own, and each registered workspace's
52
+ * (forwarded at registration, guest-graph.ts). Absent ⇒ the gateway was started without a graph. */
53
+ stores?: ProjectGraphStores;
54
+ root: string;
55
+ project: string;
56
+ target: string;
57
+ childCount: number;
58
+ /** The environment the gateway would hand `nx run` — what nx itself starts from. */
59
+ baseEnv: NodeJS.ProcessEnv;
60
+ /** The gateway's own environment, for the `LENSMCP_SERVE_RUNNER` switch. Default `process.env`. */
61
+ gatewayEnv?: NodeJS.ProcessEnv;
62
+ /** Default {@link serveRunnerScript}. */
63
+ script?: RunnerScript;
64
+ }
65
+ /** The nx functions the plan uses, loaded from the WORKSPACE's nx — the one its `nx run` would run. */
66
+ export interface NxHelpers {
67
+ version: string;
68
+ runModule: string;
69
+ createTaskGraph(projectGraph: ProjectGraph, extraTargetDependencies: Record<string, never>, projectNames: string[], targets: string[], configuration: string | undefined, overrides: Task['overrides']): TaskGraph;
70
+ getEnvFilesForTask(task: Task, graph: ProjectGraph): string[];
71
+ loadAndExpandDotEnvFile(files: string | string[], env: NodeJS.ProcessEnv, override?: boolean): unknown;
72
+ unloadDotEnvFile(file: string, env: NodeJS.ProcessEnv): void;
73
+ readNxJson(root: string): NxJsonConfiguration;
74
+ workspaceDataDirectoryForWorkspace(root: string): string;
75
+ }
76
+ /** The workspace nx's helpers, or why they cannot be loaded. Cached per root (a module load, not a computation). */
77
+ export declare function loadNxHelpers(root: string): NxHelpers | string;
78
+ /**
79
+ * The environment nx 22 hands the executor of `nx run <project>:<target>` started with `base`, step by
80
+ * step as nx takes it (one task, output piped, no TTY — how the gateway runs it):
81
+ *
82
+ * 1. `bin/nx.js`: `FORCE_COLOR=0` becomes `NO_COLOR=1`; the root `.local.env`, `.env.local`, `.env`
83
+ * are loaded and expanded — a variable already set wins, the first file wins.
84
+ * 2. The CLI marks itself (`NX_CLI_SET`), settles `NX_VERBOSE_LOGGING`, turns the TUI off and, unless
85
+ * `NX_LOAD_DOT_ENV_FILES=false`, sets it to `true`.
86
+ * 3. The task's own env (`getTaskSpecificEnv`): every variable whose value is still the one a root
87
+ * dotenv file gave is removed — including one the gateway passed down with that same value — and the
88
+ * task's files are loaded in nx's order (`getEnvFilesForTask`: project before workspace,
89
+ * `.env.<target>.<configuration>` before `.env.<configuration>` before `.env.<target>` before
90
+ * `.env`, each `.local` first), first one wins, a variable still set wins.
91
+ * 4. nx's task variables on top (`getEnvVariablesForTask`): `FORCE_COLOR` (`true` unless set),
92
+ * `NX_WORKSPACE_ROOT`, `NX_TASK_TARGET_PROJECT/TARGET/CONFIGURATION`, `LERNA_PACKAGE_NAME`,
93
+ * `NX_TUI=false`, `NX_SKIP_NX_CACHE=true` when the CLI's env asked to skip the cache
94
+ * (`NX_SKIP_NX_CACHE` or `NX_DISABLE_NX_CACHE`, `command-line-utils.js`) and removed otherwise; the
95
+ * output-capture variables removed.
96
+ * 5. The fork adds `NX_FORKED_TASK_EXECUTOR=true`.
97
+ *
98
+ * `NX_TASK_HASH` is removed, not computed (see the module doc). The dotenv primitives are nx's own.
99
+ * `isVerbose` is what nx's `--verbose` middleware settled in step 2 — the flag run-executor is handed.
100
+ */
101
+ export interface NxExecutorEnv {
102
+ env: NodeJS.ProcessEnv;
103
+ isVerbose: boolean;
104
+ }
105
+ export declare function nxExecutorEnv(nx: NxHelpers, root: string, base: NodeJS.ProcessEnv, task: Task, graph: ProjectGraph): NxExecutorEnv;
106
+ /**
107
+ * Runner or `nx run` for one spawn — and if `nx run`, why. The runner is used only when every one of these holds:
108
+ * - `LENSMCP_SERVE_RUNNER` is not off;
109
+ * - the gateway holds a project graph for THIS workspace — its own, or a registered one's that its
110
+ * registration forwarded or the gateway computed since (guest-graph.ts) — and no declaring file changed
111
+ * since it was read;
112
+ * - the target's executor is {@link SERVE_RUNNER_EXECUTOR}, and nothing about the target came from a
113
+ * plugin (its source maps name only `project.json`, `package.json` or `nx.json`);
114
+ * - nx's task graph for it is that one task — no `dependsOn` that would run first — and the task has
115
+ * no sync generator nx would run first;
116
+ * - nx keeps the graph cache the executor reads, and the workspace nx's helpers and the runner entry exist.
117
+ */
118
+ export declare function planServeLaunch(req: ServeLaunchRequest): ServeLaunchPlan;
@@ -0,0 +1,3 @@
1
+ "use strict";var T=Object.defineProperty;var u=(e,r)=>T(e,"name",{value:r,configurable:!0});var h=Object.defineProperty,i=u((e,r)=>h(e,"name",{value:r,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.RUNNER_ROOT_FLAG=exports.SERVE_RUNNER_ROLE=exports.SERVE_RUNNER_EXECUTOR=void 0,exports.serveRunnerEnabled=serveRunnerEnabled,exports.serveRunnerScript=serveRunnerScript,exports.serveRunnerArgs=serveRunnerArgs,exports.isServeRunnerCommand=isServeRunnerCommand,exports.isServeRunnerCommandOf=isServeRunnerCommandOf,exports.loadNxHelpers=loadNxHelpers,exports.nxExecutorEnv=nxExecutorEnv,exports.planServeLaunch=planServeLaunch;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),node_module_1=require("node:module"),path=tslib_1.__importStar(require("node:path"));exports.SERVE_RUNNER_EXECUTOR="@lensmcp/cluster:serve",exports.SERVE_RUNNER_ROLE="serve",exports.RUNNER_ROOT_FLAG="--workspace-root=";function serveRunnerEnabled(e=process.env){const r=(e.LENSMCP_SERVE_RUNNER??"").trim().toLowerCase();return!["0","off","false","no","nx"].includes(r)}u(serveRunnerEnabled,"serveRunnerEnabled"),i(serveRunnerEnabled,"serveRunnerEnabled");function serveRunnerScript(e=path.join(__dirname,"../../..")){const r=path.join(e,"serve-runner.js");if(fs.existsSync(r))return{path:r,execArgv:[]};const s=path.join(e,"serve-runner.ts");if(fs.existsSync(s))try{return{path:s,execArgv:["--require",require.resolve("@swc-node/register")]}}catch{return}}u(serveRunnerScript,"serveRunnerScript"),i(serveRunnerScript,"serveRunnerScript");function serveRunnerArgs(e,r,s){return["run",e,`--childCount=${r}`,`${exports.RUNNER_ROOT_FLAG}${s}`]}u(serveRunnerArgs,"serveRunnerArgs"),i(serveRunnerArgs,"serveRunnerArgs");function isServeRunnerCommand(e){return/\bserve-runner\.(js|ts)\b/.test(e)&&e.includes(` ${exports.RUNNER_ROOT_FLAG}`)}u(isServeRunnerCommand,"isServeRunnerCommand"),i(isServeRunnerCommand,"isServeRunnerCommand");function isServeRunnerCommandOf(e,r){return isServeRunnerCommand(e)&&e.trimEnd().endsWith(` ${exports.RUNNER_ROOT_FLAG}${r.replace(/\/+$/,"")}`)}u(isServeRunnerCommandOf,"isServeRunnerCommandOf"),i(isServeRunnerCommandOf,"isServeRunnerCommandOf");const helpersByRoot=new Map;function loadNxHelpers(e){const r=helpersByRoot.get(e);if(r)return r;let s;try{const o=(0,node_module_1.createRequire)(path.join(e,"package.json")),_=o("nx/package.json").version,t=o("nx/src/tasks-runner/task-env"),n=o("nx/src/tasks-runner/create-task-graph"),a=o("nx/src/config/nx-json"),p=o("nx/src/utils/cache-directory"),c=o.resolve("nx/src/command-line/run/run"),d=[["getEnvFilesForTask",t.getEnvFilesForTask],["loadAndExpandDotEnvFile",t.loadAndExpandDotEnvFile],["unloadDotEnvFile",t.unloadDotEnvFile],["createTaskGraph",n.createTaskGraph],["readNxJson",a.readNxJson],["workspaceDataDirectoryForWorkspace",p.workspaceDataDirectoryForWorkspace]].filter(([,R])=>typeof R!="function").map(([R])=>R);s=d.length>0?`nx ${_} has no ${d.join(", ")}`:{version:_,runModule:c,createTaskGraph:n.createTaskGraph,getEnvFilesForTask:t.getEnvFilesForTask,loadAndExpandDotEnvFile:t.loadAndExpandDotEnvFile,unloadDotEnvFile:t.unloadDotEnvFile,readNxJson:a.readNxJson,workspaceDataDirectoryForWorkspace:p.workspaceDataDirectoryForWorkspace}}catch(o){s=`cannot load the workspace's nx (${o.message.split(`
2
+ `)[0]})`}return helpersByRoot.set(e,s),s}u(loadNxHelpers,"loadNxHelpers"),i(loadNxHelpers,"loadNxHelpers");const ROOT_DOTENV_LOAD=[".local.env",".env.local",".env"],ROOT_DOTENV_UNLOAD=[".env",".local.env",".env.local"];function nxExecutorEnv(e,r,s,o,_){const t={...s};t.FORCE_COLOR==="0"&&(t.NO_COLOR="1",delete t.FORCE_COLOR),e.loadAndExpandDotEnvFile(ROOT_DOTENV_LOAD.map(a=>path.join(r,a)),t),t.NX_CLI_SET="true",t.NX_VERBOSE_LOGGING=String(t.NX_VERBOSE_LOGGING==="true"),t.NX_TUI="false",t.NX_LOAD_DOT_ENV_FILES!=="false"&&(t.NX_LOAD_DOT_ENV_FILES="true");const n={...t};for(const a of ROOT_DOTENV_UNLOAD)e.unloadDotEnvFile(path.join(r,a),n);t.NX_LOAD_DOT_ENV_FILES==="true"&&e.loadAndExpandDotEnvFile(e.getEnvFilesForTask(o,_).map(a=>path.join(r,a)),n),n.FORCE_COLOR=t.FORCE_COLOR===void 0?"true":t.FORCE_COLOR,n.NX_WORKSPACE_ROOT=r,n.NX_TASK_TARGET_PROJECT=o.target.project,n.NX_TASK_TARGET_TARGET=o.target.target,o.target.configuration?n.NX_TASK_TARGET_CONFIGURATION=o.target.configuration:delete n.NX_TASK_TARGET_CONFIGURATION,n.LERNA_PACKAGE_NAME=o.target.project,n.NX_TUI="false",o.target.target==="test"&&(n.NX_TERMINAL_CAPTURE_STDERR="true");for(const a of["NX_TASK_HASH","NX_SKIP_NX_CACHE","NX_TERMINAL_OUTPUT_PATH","NX_STREAM_OUTPUT","NX_PREFIX_OUTPUT","NX_SET_CLI"])delete n[a];return(t.NX_SKIP_NX_CACHE==="true"||t.NX_DISABLE_NX_CACHE==="true")&&(n.NX_SKIP_NX_CACHE="true"),n.NX_FORKED_TASK_EXECUTOR="true",{env:n,isVerbose:t.NX_VERBOSE_LOGGING==="true"}}u(nxExecutorEnv,"nxExecutorEnv"),i(nxExecutorEnv,"nxExecutorEnv");function inferredBy(e,r,s){const o=e.sourceMaps?.[r],_=new Set([path.posix.join(r,"project.json"),path.posix.join(r,"package.json"),"nx.json"]),t=`targets.${s}`;let n=!1;for(const[a,[p,c]]of Object.entries(o??{}))if(!(a!==t&&!a.startsWith(`${t}.`))&&(n=!0,!p||!_.has(p)))return`${c}${p?` (from ${p})`:""}`;return n?void 0:"a source the project graph does not record"}u(inferredBy,"inferredBy"),i(inferredBy,"inferredBy");function planServeLaunch(e){const r=i(E=>({kind:"nx",reason:E}),"nx");if(!serveRunnerEnabled(e.gatewayEnv??process.env))return r("LENSMCP_SERVE_RUNNER is off");if(!e.stores)return r("the gateway was started without an Nx project graph");const s=e.stores.get(e.root);if(!s)return r("a workspace registered into this gateway that it holds no project graph for");const o=s.current();if(!o)return s.refreshIfStale(),r(`no project graph${s.lastError()?` (${s.lastError()})`:""}`);const _=s.staleReason();if(_)return s.refreshIfStale(),r(_);const t=o.projectGraph.nodes[e.project],n=t?.data.targets?.[e.target];if(!t||!n)return r(`${e.project}:${e.target} is not in the project graph`);if(n.executor!==exports.SERVE_RUNNER_EXECUTOR)return r(`its executor is ${n.executor??"(none)"}, not ${exports.SERVE_RUNNER_EXECUTOR}`);if(!o.sourceMaps)return r("this nx reports no source maps, so the gateway cannot tell a declared target from an inferred one");const a=inferredBy(o,t.data.root,e.target);if(a)return r(`part of the target is inferred by ${a}`);const p=e.gatewayEnv??process.env;if(e.baseEnv.NX_CACHE_PROJECT_GRAPH==="false"||p.NX_CACHE_PROJECT_GRAPH==="false")return r("NX_CACHE_PROJECT_GRAPH=false \u2014 the executor reads nx's graph cache");const c=loadNxHelpers(e.root);if(typeof c=="string")return r(c);const d=e.script??serveRunnerScript();if(!d)return r("serve-runner.js is missing from @lensmcp/cluster");if(!fs.existsSync(path.join(c.workspaceDataDirectoryForWorkspace(e.root),"project-graph.json")))return r("nx has no project-graph cache on disk for the executor to read");const R={childCount:e.childCount,__overrides_unparsed__:[`--childCount=${e.childCount}`]};let N;try{N=c.createTaskGraph(o.projectGraph,{},[e.project],[e.target],void 0,R)}catch(E){return r(`nx could not build its task graph (${E.message.split(`
3
+ `)[0]})`)}const l=Object.values(N.tasks).find(E=>E.target.project===e.project&&E.target.target===e.target);if(!l)return r(`nx's task graph has no ${e.project}:${e.target}`);const f=Object.keys(N.tasks).filter(E=>E!==l.id);if(f.length>0)return r(`dependsOn: nx would run ${f.join(", ")} first`);const v=new Set(c.readNxJson(e.root).sync?.disabledTaskSyncGenerators??[]),O=(n.syncGenerators??[]).filter(E=>!v.has(E));if(O.length>0)return r(`sync generators nx would run first: ${O.join(", ")}`);l.startTime=Date.now();const{env:x,isVerbose:g}=nxExecutorEnv(c,e.root,e.baseEnv,l,o.projectGraph);return{kind:"runner",taskId:l.id,env:x,script:d,message:{nxRunModule:c.runModule,targetDescription:l.target,overrides:l.overrides,isVerbose:g,taskGraph:N}}}u(planServeLaunch,"planServeLaunch"),i(planServeLaunch,"planServeLaunch");
@@ -54,7 +54,7 @@ export declare function createClientErrorLogGate(windowMs?: number): (cause: str
54
54
  * The map is a per-workspace slot, and `onRegister` used to `set()` straight over it: a second
55
55
  * `gateway start` for an already-hosted workspace left the first registration's dashboard, vites and
56
56
  * capture children running with no handle able to name them, holding their ports for the life of the
57
- * daemon (issues/codex-security/50). Both the register and the unregister path go through here now, so
57
+ * daemon (issues/closed/codex-security-50). Both the register and the unregister path go through here now, so
58
58
  * "replace a registration" and "remove a registration" retire exactly the same set.
59
59
  *
60
60
  * The entry is deleted BEFORE the reaper runs, so a reaper that throws part-way cannot leave a dead
@@ -66,7 +66,7 @@ export declare function retirePreviousWorkspace(reapers: Map<string, () => void>
66
66
  /**
67
67
  * `maxSessionMemory: 512` / `maxConcurrentStreams: 512` (below, in the h2 server options) are DELIBERATE
68
68
  * and STAY — a full Vite reload streams tens of MB of transformed ESM over ONE h2 session and Node's 10 MB
69
- * default turns that into a burst of `ERR_HTTP2_PROTOCOL_ERROR` (issues/foodguard/14, commit `cc6aca4`).
69
+ * default turns that into a burst of `ERR_HTTP2_PROTOCOL_ERROR` (issues/closed/foodguard-14, commit `cc6aca4`).
70
70
  * What was missing is everything AROUND them. `maxSessionMemory` is a PER-SESSION accounting threshold, not
71
71
  * a process budget, and nothing bounded the aggregate: no cap on concurrent h2 sessions, no cap on streams
72
72
  * across sessions, and `GATEWAY_MAX_CONNECTIONS` defaults to `0` = unlimited, so the listener accepted
@@ -91,7 +91,7 @@ export declare const GATEWAY_MAX_CONNECTIONS_DEFAULT = 512;
91
91
  export declare const GATEWAY_MAX_H2_SESSIONS_DEFAULT = 64;
92
92
  /** Open h2 streams across ALL sessions. A full Vite reload is a few hundred; 4096 is ~8 saturated sessions
93
93
  * and is the only thing that bounds `sessions × maxConcurrentStreams` (= 32,768) proxied requests, each of
94
- * which also holds upstream state (issues/codex-security/10). */
94
+ * which also holds upstream state (issues/closed/codex-security-10). */
95
95
  export declare const GATEWAY_MAX_H2_STREAMS_DEFAULT = 4096;
96
96
  /** How long a session with ZERO open streams may sit before it is GOAWAY'd. Deliberately keyed on "no open
97
97
  * streams", never on socket silence: a silent-but-open stream is an SSE/MCP subscription or an idle HMR