@lensmcp/cluster 1.23.9 → 1.23.11

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.
@@ -22,12 +22,22 @@ export interface ControlServer {
22
22
  * still serving last month's code: the process was started before the install and keeps its
23
23
  * loaded bundle forever (same failure mode as the embedded MCP server, measured at 2+ days).
24
24
  *
25
- * Walks up from this file to the nearest package.json NAMED `@lensmcp/cluster` — the
26
- * published layout (`<pkg>/executors/gateway/runtime/control.js`) and the source tree
27
- * (`libs/cluster/src/...`) both resolve their own manifest; an un-prepared dist layout
28
- * resolves nothing and reports nothing — absent means "cannot vouch", never a guess.
25
+ * Read ONCE, when this module loads — which is when the daemon starts. It used to be read per
26
+ * request, and an install rewrites that manifest in place: so the daemon it was meant to expose
27
+ * answered with the NEW version the moment the files landed, and `lensmcp upgrade` concluded
28
+ * "nothing to do" beside a process still running the old bundle (foodguard, 2026-10-06 —
29
+ * issues/foodguard/33). What is on disk now is not what was loaded then.
29
30
  */
30
- export declare function clusterVersion(): string | undefined;
31
+ export declare const clusterVersion: () => string | undefined;
32
+ /** Take the answer now; give that same answer for the life of the process. */
33
+ export declare function versionAtLoad(read: () => string | undefined): () => string | undefined;
34
+ /**
35
+ * Walks up from `from` to the nearest package.json NAMED `@lensmcp/cluster` — the published
36
+ * layout (`<pkg>/executors/gateway/runtime/control.js`) and the source tree (`libs/cluster/src/...`)
37
+ * both resolve their own manifest; an un-prepared dist layout resolves nothing and reports
38
+ * nothing — absent means "cannot vouch", never a guess.
39
+ */
40
+ export declare function readClusterVersion(from: string): string | undefined;
31
41
  /**
32
42
  * Make `dir` exist and be readable/writable by its owner ONLY.
33
43
  *
@@ -1,2 +1,2 @@
1
- "use strict";var f=Object.defineProperty;var p=(t,e)=>f(t,"name",{value:e,configurable:!0});var h=Object.defineProperty,u=p((t,e)=>h(t,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.controlSocketPath=controlSocketPath,exports.clusterVersion=clusterVersion,exports.ensureOwnerOnlyDir=ensureOwnerOnlyDir,exports.startControlServer=startControlServer;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log");function controlSocketPath(){const t=process.env.LENSMCP_HOME||os.homedir();return path.join(t,".lensmcp","control.sock")}p(controlSocketPath,"controlSocketPath"),u(controlSocketPath,"controlSocketPath");function clusterVersion(){let t=__dirname;for(let e=0;e<6;e++){const n=path.join(t,"package.json");if(fs.existsSync(n))try{const s=JSON.parse(fs.readFileSync(n,"utf8"));if(s.name==="@lensmcp/cluster"&&typeof s.version=="string")return s.version}catch{}const c=path.dirname(t);if(c===t)break;t=c}}p(clusterVersion,"clusterVersion"),u(clusterVersion,"clusterVersion");function readJsonBody(t){return new Promise((e,n)=>{let c="";t.on("data",s=>{c+=s,c.length>1e6&&n(new Error("body too large"))}),t.on("end",()=>{try{e(c?JSON.parse(c):{})}catch(s){n(s)}}),t.on("error",n)})}p(readJsonBody,"readJsonBody"),u(readJsonBody,"readJsonBody");function ensureOwnerOnlyDir(t){try{fs.mkdirSync(t,{recursive:!0,mode:448})}catch{}try{const e=fs.lstatSync(t);return e.isDirectory()?((e.mode&63)!==0&&fs.chmodSync(t,448),!0):!1}catch{return!1}}p(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),u(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function startControlServer(t){const{rt:e}=t,n=controlSocketPath(),c=path.dirname(n);ensureOwnerOnlyDir(c)||console.warn(`[gateway] control home ${c} is not a usable owner-only directory \u2014 refusing to expose the control plane there.`);try{fs.unlinkSync(n)}catch{}const s=u((i,a,l)=>{i.writeHead(a,{"content-type":"application/json"}),i.end(JSON.stringify(l))},"send"),v=http.createServer((i,a)=>{(async()=>{try{const l=(i.url??"").split("?")[0];if(i.method==="GET"&&l==="/list")return s(a,200,{workspaces:e.registry.keys().map(r=>{const o=e.registry.get(r);return{key:r,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0}})});if(i.method==="GET"&&l==="/status"){const r=clusterVersion();return s(a,200,{daemon:{pid:process.pid,wsKey:e.wsKey,...r?{version:r}:{}},workspaces:e.registry.keys().map(o=>{const y=e.registry.get(o);return{key:o,root:y?.root,routes:y?.routes.length??0,services:y?.services.length??0,isDaemon:o===e.wsKey}}),services:e.registry.allServices().map(o=>({project:o.project,wsKey:o.wsKey,host:o.decl.host,state:o.state}))})}if(i.method==="POST"&&l==="/register"){const r=await readJsonBody(i);if(!r.wsKey||!r.root)return s(a,400,{error:"wsKey and root are required"});const o=r.projects??{},y=(0,discovery_1.projectSourceRoots)(r.root,o,r.projectDependencies),{routes:w,services:d}=(0,discovery_1.discoverRoutes)(r.root,o,void 0,r.wsKey,y),k={wsKey:r.wsKey,root:r.root,routes:w,services:d};try{const g=(0,service_keys_1.adoptServiceKeys)(e,r.root,d,r.wsKey);g.added.length>0&&console.log(`[gateway] adopted east-west keys for ${r.wsKey}: ${g.added.join(", ")}`)}catch(g){console.error(`[gateway] could not adopt east-west keys for ${r.wsKey}: ${g.message}`)}try{(0,workspace_log_1.workspaceLogWriter)(r.root)?.(`[gateway] ${new Date().toISOString()} this workspace ('${r.wsKey}') is served by the daemon at ${e.root} (pid ${process.pid}); its gateway-level log is ${path.join(e.root,".lensmcp","gateway.log")}. Pod output for this workspace follows here. Attach live with \`lensmcp logs <service>\`.
2
- `)}catch{}return e.registry.register(k),await t.onRegister?.(k),e.rebuildRoutes(),s(a,200,{ok:!0,wsKey:r.wsKey,routes:w.length,services:d.length,workspaces:e.registry.keys()})}if(i.method==="POST"&&l==="/unregister"){const r=await readJsonBody(i);if(!r.wsKey)return s(a,400,{error:"wsKey is required"});const o=e.registry.unregister(r.wsKey);if(o){for(const y of o.services){const w=(0,service_keys_1.serviceKeyId)(y.wsKey??r.wsKey,y.project),d=e.serviceKeys[w];d&&(delete e.serviceKeys[w],e.keyToProject.delete(d),e.keyToWorkspace?.delete(d))}for(const y of o.services)t.killService(y,"workspace unregistered");t.onUnregister?.(o)}return e.rebuildRoutes(),s(a,200,{ok:!0,removed:!!o,workspaces:e.registry.keys()})}s(a,404,{error:"not found"})}catch(l){s(a,500,{error:l.message})}})()});return v.listen(n,()=>{try{fs.chmodSync(n,384)}catch(i){console.warn(`[gateway] could not restrict ${n} to 0600:`,i.message)}}),{socketPath:n,close:u(()=>{v.close();try{fs.unlinkSync(n)}catch{}},"close")}}p(startControlServer,"startControlServer"),u(startControlServer,"startControlServer");
1
+ "use strict";var k=Object.defineProperty;var u=(t,e)=>k(t,"name",{value:e,configurable:!0});var h=Object.defineProperty,d=u((t,e)=>h(t,"name",{value:e,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.clusterVersion=void 0,exports.controlSocketPath=controlSocketPath,exports.versionAtLoad=versionAtLoad,exports.readClusterVersion=readClusterVersion,exports.ensureOwnerOnlyDir=ensureOwnerOnlyDir,exports.startControlServer=startControlServer;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log");function controlSocketPath(){const t=process.env.LENSMCP_HOME||os.homedir();return path.join(t,".lensmcp","control.sock")}u(controlSocketPath,"controlSocketPath"),d(controlSocketPath,"controlSocketPath"),exports.clusterVersion=versionAtLoad(()=>readClusterVersion(__dirname));function versionAtLoad(t){const e=t();return()=>e}u(versionAtLoad,"versionAtLoad"),d(versionAtLoad,"versionAtLoad");function readClusterVersion(t){let e=t;for(let n=0;n<6;n++){const c=path.join(e,"package.json");if(fs.existsSync(c))try{const w=JSON.parse(fs.readFileSync(c,"utf8"));if(w.name==="@lensmcp/cluster"&&typeof w.version=="string")return w.version}catch{}const s=path.dirname(e);if(s===e)break;e=s}}u(readClusterVersion,"readClusterVersion"),d(readClusterVersion,"readClusterVersion");function readJsonBody(t){return new Promise((e,n)=>{let c="";t.on("data",s=>{c+=s,c.length>1e6&&n(new Error("body too large"))}),t.on("end",()=>{try{e(c?JSON.parse(c):{})}catch(s){n(s)}}),t.on("error",n)})}u(readJsonBody,"readJsonBody"),d(readJsonBody,"readJsonBody");function ensureOwnerOnlyDir(t){try{fs.mkdirSync(t,{recursive:!0,mode:448})}catch{}try{const e=fs.lstatSync(t);return e.isDirectory()?((e.mode&63)!==0&&fs.chmodSync(t,448),!0):!1}catch{return!1}}u(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),d(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function startControlServer(t){const{rt:e}=t,n=controlSocketPath(),c=path.dirname(n);ensureOwnerOnlyDir(c)||console.warn(`[gateway] control home ${c} is not a usable owner-only directory \u2014 refusing to expose the control plane there.`);try{fs.unlinkSync(n)}catch{}const s=d((i,a,l)=>{i.writeHead(a,{"content-type":"application/json"}),i.end(JSON.stringify(l))},"send"),w=http.createServer((i,a)=>{(async()=>{try{const l=(i.url??"").split("?")[0];if(i.method==="GET"&&l==="/list")return s(a,200,{workspaces:e.registry.keys().map(r=>{const o=e.registry.get(r);return{key:r,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0}})});if(i.method==="GET"&&l==="/status"){const r=(0,exports.clusterVersion)();return s(a,200,{daemon:{pid:process.pid,wsKey:e.wsKey,...r?{version:r}:{}},workspaces:e.registry.keys().map(o=>{const y=e.registry.get(o);return{key:o,root:y?.root,routes:y?.routes.length??0,services:y?.services.length??0,isDaemon:o===e.wsKey}}),services:e.registry.allServices().map(o=>({project:o.project,wsKey:o.wsKey,host:o.decl.host,state:o.state}))})}if(i.method==="POST"&&l==="/register"){const r=await readJsonBody(i);if(!r.wsKey||!r.root)return s(a,400,{error:"wsKey and root are required"});const o=r.projects??{},y=(0,discovery_1.projectSourceRoots)(r.root,o,r.projectDependencies),{routes:g,services:p}=(0,discovery_1.discoverRoutes)(r.root,o,void 0,r.wsKey,y),f={wsKey:r.wsKey,root:r.root,routes:g,services:p};try{const v=(0,service_keys_1.adoptServiceKeys)(e,r.root,p,r.wsKey);v.added.length>0&&console.log(`[gateway] adopted east-west keys for ${r.wsKey}: ${v.added.join(", ")}`)}catch(v){console.error(`[gateway] could not adopt east-west keys for ${r.wsKey}: ${v.message}`)}try{(0,workspace_log_1.workspaceLogWriter)(r.root)?.(`[gateway] ${new Date().toISOString()} this workspace ('${r.wsKey}') is served by the daemon at ${e.root} (pid ${process.pid}); its gateway-level log is ${path.join(e.root,".lensmcp","gateway.log")}. Pod output for this workspace follows here. Attach live with \`lensmcp logs <service>\`.
2
+ `)}catch{}return e.registry.register(f),await t.onRegister?.(f),e.rebuildRoutes(),s(a,200,{ok:!0,wsKey:r.wsKey,routes:g.length,services:p.length,workspaces:e.registry.keys()})}if(i.method==="POST"&&l==="/unregister"){const r=await readJsonBody(i);if(!r.wsKey)return s(a,400,{error:"wsKey is required"});const o=e.registry.unregister(r.wsKey);if(o){for(const y of o.services){const g=(0,service_keys_1.serviceKeyId)(y.wsKey??r.wsKey,y.project),p=e.serviceKeys[g];p&&(delete e.serviceKeys[g],e.keyToProject.delete(p),e.keyToWorkspace?.delete(p))}for(const y of o.services)t.killService(y,"workspace unregistered");t.onUnregister?.(o)}return e.rebuildRoutes(),s(a,200,{ok:!0,removed:!!o,workspaces:e.registry.keys()})}s(a,404,{error:"not found"})}catch(l){s(a,500,{error:l.message})}})()});return w.listen(n,()=>{try{fs.chmodSync(n,384)}catch(i){console.warn(`[gateway] could not restrict ${n} to 0600:`,i.message)}}),{socketPath:n,close:d(()=>{w.close();try{fs.unlinkSync(n)}catch{}},"close")}}u(startControlServer,"startControlServer"),d(startControlServer,"startControlServer");
@@ -1,3 +1,3 @@
1
- "use strict";var ge=Object.defineProperty;var v=(t,n)=>ge(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=v((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isTestSourcePath=isTestSourcePath,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),pod_output_1=require("./pod-output"),types_1=require("./types"),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}v(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let a=0;a<t.length;a+=1)n^=t.charCodeAt(a),n=Math.imul(n,16777619);return n>>>0}v(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}v(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const n=t.split("/"),a=n[n.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let l=0;l<n.length-1;l+=1)if(TEST_DIR_SEGMENTS.has(n[l]))return!0;return!1}v(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,l){const f=l?.ignoreTests??!0;let S=0,y=0,E=!1;const g=new Set,L=await gitIgnoredPaths(t),h=i(R=>path.relative(t,R).split(path.sep).join("/"),"rel"),T=i(async(R,X=!0)=>{if(S>=SCAN_FILE_CAP){E=!0;return}let D;try{D=await fs.promises.readdir(R,{withFileTypes:!0})}catch{return}for(const q of D){if(S>=SCAN_FILE_CAP){E=!0;return}const x=q.name,V=path.join(R,x);if(q.isDirectory()){if(!X||x.startsWith(".")||SRC_EXCLUDE.has(x)||a?.has(x)||x.endsWith("-devserver")||L.has(`${h(V)}/`))continue;await T(V)}else if(q.isFile()){const Y=x.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(x.slice(Y)))continue;const P=h(V);if(L.has(P)||f&&isTestSourcePath(P)||g.has(P))continue;g.add(P),S+=1,y^=fnv1a(P)}}},"walk");if(n&&n.length>0){await T(t,!1);for(const R of n)await T(R)}else await T(t);return{signature:`${S}:${(y>>>0).toString(36)}`,paths:g,truncated:E}}v(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,l){return(await sourceSetSnapshot(t,n,a,l)).signature}v(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,l){if(t.paths.size===n.paths.size){let E=!0;for(const g of t.paths)if(!n.paths.has(g)){E=!1;break}if(E)return null}const f=[],S=[];let y=t.truncated||n.truncated;for(const E of n.paths)t.paths.has(E)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?f.push(E):y=!0);for(const E of t.paths)n.paths.has(E)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?S.push(E):y=!0);return{changedAt:a,added:f,removed:S,truncated:y,...l!==void 0?{windowMs:l}:{}}}v(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}v(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,f){const S=i(g=>{const L=path.relative(t,g).split(path.sep).filter(Boolean);return L.length===0||L[0].startsWith("..")?"":L[0]},"bucketOf")(n);if(!S)return[];const y=new Set(a.filter(Boolean)),E=new Set([S]);for(const g of l)g.startsWith(".")||SRC_EXCLUDE.has(g)||f?.has(g)||g.endsWith("-devserver")||y.has(g)||E.add(g);return[...E].sort().map(g=>path.join(t,g))}v(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,a,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<l.cooldownMs?null:a>0&&a-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&n-t.spawnAt>l.maxAgeMs?"max-age":null}v(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,a,l,f){return!(l-n<f.cooldownMs||a<=0||a-t<=f.graceMs||l-a<f.settleMs)}v(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,a,l=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&l.length>0){const f=n-a,S=new Set(t.recentEvents.filter(y=>y.at>=f).map(y=>path.resolve(y.path)));return l.every(y=>S.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}v(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,a){if(!t)return"unknown";if(t.degraded)return"degraded";const l=n-a;return t.lastWatchRunAt>=l||t.lastBuildAt>=l?"heard":"silent"}v(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,a,l){const 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"}v(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}v(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,a={}){const l=a.spawnProcess??node_child_process_1.spawn,f=a.probeAlive??pod_probe_1.probePodAlive,S=a.killTree??((e,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),y=a.rollReadyMs??types_1.POD_ROLL_READY_MS,E=a.rollPollMs??750,{emit:g}=n,L=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,o,s,r,c)=>g(o,s,r,{wsKey:e.wsKey,...c},void 0,L(e)),"emitSvc"),T=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),R=i((e,o)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const r=e.proc?.pid;let c=0;for(const d of s.split(`
2
- `)){const p=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const $=Number(p[1]),b=Number(p[2]),m=p[3];if(m.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test(m)&&!(m.includes("/node_modules/nx/")&&!m.includes(e.root))&&!($===process.pid||$===r)&&!(r!==void 0&&b===r)){try{process.kill(-b,"SIGKILL")}catch{try{process.kill($,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${o}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),h(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),X=i((e,o,s)=>{const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),d=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),p=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(e.wsKey||path.basename(e.root),e.project,"nx")),[$,b]=fs.existsSync(d)?[p,[d,"run",r,`--childCount=${c}`]]:["yarn",["nx","run",r,`--childCount=${c}`]],m=l($,b,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(o),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const C=i(w=>{const _=w.toString();for(const O of _.split(`
3
- `)){const u=(0,pod_output_1.classifyPodLine)(O),{clean:j,broken:k,healthy:M}=u,z=j;if(k){e.lastErrors.push(j),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const W=Date.now();(j!==e.lastErrorEmitSig||W-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=j,e.lastErrorEmitAt=W,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:j}))}M&&(e.buildFailed=!1),(e.faults??=new pod_output_1.PodFaults).observe(u)&&(e.lastErrorEmitSig=void 0,h(e,"info",`service recovered: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-recovered",project:e.project,line:j})),!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(z)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),h(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(F(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),R(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.())},250).unref?.())}},"sniff"),A=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return m.stdout?.on("data",w=>{if(t.stopped())return;C(w);const _=`[${e.project}] ${w}`;process.stdout.write(_),A?.(_)}),m.stderr?.on("data",w=>{if(t.stopped())return;C(w);const _=`[${e.project}] ${w}`;process.stderr.write(_),A?.(_)}),m.on("exit",(w,_)=>{if(e.proc!==m||(e.proc=void 0,e.state="down",t.stopped()))return;const O=(0,exit_reason_1.describeExit)(w,_);if(console.log(`[gateway] ${r} exited (${w??"no code"}${_?`, ${_}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${O?` \u2014 ${O}`:""}`),h(e,w===0?"info":"warning",`service down: ${e.project} (exit ${w})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:w,..._?{signal:_}:{}}),w===0||e.buildFailed)return;const u=Date.now();if(e.restarts=(e.restarts??[]).filter(k=>u-k<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(u);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)}),m},"launchServiceProcess"),D=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&R(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${s})\u2026`),h(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=X(e,e.generation,"active")},"spawnService"),q=i(async e=>{const o=e.pool.socks[0];if(!o)return;const s=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),x=i(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),V=i(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const s=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,o);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&r.push({sock:c,pid:d.pid})}return r},"staleAdoptedSocks"),Y=i((e,o)=>{const s=o.map(r=>r.sock);for(const{sock:r,pid:c}of o){const d=chooseStalePodKillTarget(c,x(r),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!s.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),P=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return h(e,"info",`service up: ${e.project} (${s} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:o==="adopted"}),!0},"markUp"),re=i(async e=>{if(T(e.pool)>0){const r=await V(e);if(r.length>0&&(console.error(`[gateway] ${e.project}: ${r.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),h(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:r.length}),e.proc&&F(e,"stale adoption (foreign east-west key)"),Y(e,r)),e.pool.socks.length>0)return P(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&D(e);const o=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(T(e.pool)>0){e.state="up",e.buildFailed=!1;const r=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${r?`, cold start ${r}ms`:""})`),h(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${r??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:r}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const o=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,r=e.decl.adminUrl??(e.decl.port?`http://localhost:${e.decl.port}`:void 0);if(!r||s===0||s>=o||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();const c=(r.startsWith("https")?require("node:https"):http).request(`${r}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume());c.on("error",()=>{}),c.end(JSON.stringify({pods:s+1})),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),h(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight})},"scaleUp"),F=i((e,o,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),s?.announce!==!1&&h(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const r=e.proc;try{r.stdout?.removeAllListeners("data"),r.stderr?.removeAllListeners("data"),r.removeAllListeners("exit")}catch{}S(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{J(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),J=i(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const s=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),te=i((e,o)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const r=e.generation??0,c=r+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${r}\u2192g${c} reason=${o} \u2014 standby spawning, old generation keeps serving.`),h(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:c,fromGeneration:r});const p=X(e,c,"standby"),$=(async()=>{const b=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],m=d+y;let C;for(;!t.stopped()&&Date.now()<m&&!(p.exitCode!==null||p.signalCode!==null);){T(e.pool);for(const A of e.pool.socks)if((await f(A,void 0,b))?.podGeneration===c){C=A;break}if(C)break;await sleep(E)}if(!C||t.stopped()){const A=t.stopped()?"gateway stopping":p.exitCode!==null||p.signalCode!==null?`standby exited (${p.exitCode??p.signalCode})`:`standby not ready within ${Math.round(y/1e3)}s`;try{p.stdout?.removeAllListeners("data"),p.stderr?.removeAllListeners("data")}catch{}return S(p,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${A} \u2014 old generation g${r} kept serving.`),h(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:o,generation:c,error:A,durationMs:Date.now()-d}),setTimeout(()=>J(e),1200).unref?.(),!1}if(e.proc!==s)return S(p,"SIGTERM"),!1;e.proc=p,e.generation=c,e.spawnAt=d,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}return S(s,"SIGTERM"),setTimeout(()=>{J(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(C)} in ${Date.now()-d}ms; retiring g${r}.`),h(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:c,fromGeneration:r,durationMs:Date.now()-d}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=$,$},"rollPod"),ne=i(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>o-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),F(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS}; ${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}),F(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 G=new Map,oe=new Map;let Z=!1,ee=0,H=0;const ie=i(()=>{const e=t.projectRoots??{},o=new Set(t.services.map(r=>r.project));for(const r of t.routes)r.lens&&o.add(r.lens.project);const s=new Set;for(const r of o){const c=e[r];if(!c)continue;const d=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&s.add(d)}return[...s]},"managedBuckets"),ae=i((e,o,s,r)=>{const c=t.projectSourceRoots?.[e];if(c?.length)return c;const d=t.projectRoots?.[e];if(!d)return;const p=sourceScopeDirs(t.root,d,s,o,r);return p.length>0?p:void 0},"scopeFor"),le=i(()=>{if(Z||Date.now()-ee<types_1.POD_STALE_SCAN_MS)return;Z=!0,ee=Date.now();const e=ee;(async()=>{const o=(0,scope_1.readSourceSetOptions)(t.root),s=o.exclude,r={ignoreTests:o.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,r),d=Date.now(),p=sourceChangeWindowMs(d,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),$=Q?diffSourceSets(Q,c,d,p):null;if($&&(t.sourceSetChangedAt=d,t.sourceSetChange=$,g("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:$.added,removed:$.removed,truncated:$.truncated})),Q=c,!t.projectRoots){H=e;return}let b;try{b=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(u=>u.isDirectory()).map(u=>u.name)}catch{return}const m=ie(),C=new Set(t.services.map(u=>u.project));for(const u of t.routes)u.lens&&C.add(u.lens.project);const A=new Map;for(const u of C){const j=ae(u,b,m,s);if(!j)continue;const k=j.join("|"),M=A.get(k)??{dirs:j,projects:[]};M.projects.push(u),A.set(k,M)}const w=Date.now(),_=sourceChangeWindowMs(w,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);H=e;const O=i((u,j,k)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const M of u)t.sourceSetChangedFor.set(M,j),t.sourceSetChangesFor.set(M,k)},"stampScoped");for(const[u,{dirs:j,projects:k}]of A){const M=await sourceSetSnapshot(t.root,j,s,r),z=G.get(u);G.set(u,M);const W=new Map;for(const N of k){const I=oe.get(N);if(I===void 0||I===u)continue;const K=W.get(I)??[];K.push(N),W.set(I,K)}for(const N of k)oe.set(N,u);for(const[N,I]of W){const K=G.get(N),U=K?diffSourceSets(K,M,w,_):$;U&&(O(I,K?w:d,U),g("info",`source file set changed \u2014 ${I.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:I,added:U.added,removed:U.removed,truncated:U.truncated}))}if(!z)continue;const B=diffSourceSets(z,M,w,_);B&&(O(k,w,B),g("info",`source file set changed in ${k.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:k,added:B.added,removed:B.removed,truncated:B.truncated}))}for(const u of[...G.keys()])A.has(u)||G.delete(u)})().catch(()=>{}).finally(()=>{Z=!1})},"maybeScanSourceSet"),de=i((e,o)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,r=podWatcherVerdict(e.watcher,o,s),c=i(d=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,h(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):r==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):r==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),pe=i((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){te(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),h(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const s=!!e.decl.eager;F(e,`${o} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{he().catch(o=>console.error("[gateway] sweep error:",o))},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 o=e.decl.idleKillSec;if(o&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){F(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,r=T(e.pool)>0;if(r||s){if(e.wedgedSince=void 0,r&&P(e,"observed"),r&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await q(e);const d=(e.staleVerifiedAt??0)>=c&&c>0,p=podRecycleReason(e,Date.now(),d?0:c,ce);if(p==="stale-source-set"&&!de(e,c))continue;p&&pe(e,p)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ne(e))}}},"sweepOnce");return{scanNow:T,spawnService:D,ensureUp:re,scaleUp:se,killSpawned:F,markUp:P,startSweeper:ue,rollPod:te}}v(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==a)return t}v(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
1
+ "use strict";var ge=Object.defineProperty;var v=(t,n)=>ge(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=v((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.isTestSourcePath=isTestSourcePath,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),pod_output_1=require("./pod-output"),types_1=require("./types"),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}v(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let a=0;a<t.length;a+=1)n^=t.charCodeAt(a),n=Math.imul(n,16777619);return n>>>0}v(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}v(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const n=t.split("/"),a=n[n.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let l=0;l<n.length-1;l+=1)if(TEST_DIR_SEGMENTS.has(n[l]))return!0;return!1}v(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,l){const f=l?.ignoreTests??!0;let S=0,y=0,E=!1;const g=new Set,O=await gitIgnoredPaths(t),h=i(D=>path.relative(t,D).split(path.sep).join("/"),"rel"),C=i(async(D,X=!0)=>{if(S>=SCAN_FILE_CAP){E=!0;return}let x;try{x=await fs.promises.readdir(D,{withFileTypes:!0})}catch{return}for(const V of x){if(S>=SCAN_FILE_CAP){E=!0;return}const P=V.name,G=path.join(D,P);if(V.isDirectory()){if(!X||P.startsWith(".")||SRC_EXCLUDE.has(P)||a?.has(P)||P.endsWith("-devserver")||O.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(O.has(b)||f&&isTestSourcePath(b)||g.has(b))continue;g.add(b),S+=1,y^=fnv1a(b)}}},"walk");if(n&&n.length>0){await C(t,!1);for(const D of n)await C(D)}else await C(t);return{signature:`${S}:${(y>>>0).toString(36)}`,paths:g,truncated:E}}v(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,l){return(await sourceSetSnapshot(t,n,a,l)).signature}v(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,l){if(t.paths.size===n.paths.size){let E=!0;for(const g of t.paths)if(!n.paths.has(g)){E=!1;break}if(E)return null}const f=[],S=[];let y=t.truncated||n.truncated;for(const E of n.paths)t.paths.has(E)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?f.push(E):y=!0);for(const E of t.paths)n.paths.has(E)||(f.length+S.length<SOURCE_DIFF_PATH_CAP?S.push(E):y=!0);return{changedAt:a,added:f,removed:S,truncated:y,...l!==void 0?{windowMs:l}:{}}}v(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}v(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,f){const S=i(g=>{const O=path.relative(t,g).split(path.sep).filter(Boolean);return O.length===0||O[0].startsWith("..")?"":O[0]},"bucketOf")(n);if(!S)return[];const y=new Set(a.filter(Boolean)),E=new Set([S]);for(const g of l)g.startsWith(".")||SRC_EXCLUDE.has(g)||f?.has(g)||g.endsWith("-devserver")||y.has(g)||E.add(g);return[...E].sort().map(g=>path.join(t,g))}v(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,a,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<l.cooldownMs?null:a>0&&a-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&n-t.spawnAt>l.maxAgeMs?"max-age":null}v(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,a,l,f){return!(l-n<f.cooldownMs||a<=0||a-t<=f.graceMs||l-a<f.settleMs)}v(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,a,l=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&l.length>0){const f=n-a,S=new Set(t.recentEvents.filter(y=>y.at>=f).map(y=>path.resolve(y.path)));return l.every(y=>S.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}v(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,a){if(!t)return"unknown";if(t.degraded)return"degraded";const l=n-a;return t.lastWatchRunAt>=l||t.lastBuildAt>=l?"heard":"silent"}v(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,a,l){const 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"}v(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}v(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,a={}){const l=a.spawnProcess??node_child_process_1.spawn,f=a.probeAlive??pod_probe_1.probePodAlive,S=a.killTree??((e,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),y=a.rollReadyMs??types_1.POD_ROLL_READY_MS,E=a.rollPollMs??750,{emit:g}=n,O=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,o,s,r,c)=>g(o,s,r,{wsKey:e.wsKey,...c},void 0,O(e)),"emitSvc"),C=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),D=i((e,o)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const r=e.proc?.pid;let c=0;for(const d of s.split(`
2
+ `)){const u=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!u)continue;const $=Number(u[1]),L=Number(u[2]),m=u[3];if(m.includes(`run ${o}`)&&/\bnx(\.js)?\b/.test(m)&&!(m.includes("/node_modules/nx/")&&!m.includes(e.root))&&!($===process.pid||$===r)&&!(r!==void 0&&L===r)){try{process.kill(-L,"SIGKILL")}catch{try{process.kill($,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${o}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),h(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),X=i((e,o,s)=>{const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),d=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),u=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(e.wsKey||path.basename(e.root),e.project,"nx")),[$,L]=fs.existsSync(d)?[u,[d,"run",r,`--childCount=${c}`]]:["yarn",["nx","run",r,`--childCount=${c}`]],m=l($,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(o),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const R=i(w=>{const _=w.toString();for(const N of _.split(`
3
+ `)){const p=(0,pod_output_1.classifyPodLine)(N),{clean:j,broken:k,healthy:M}=p,z=j;if(k){e.lastErrors.push(j),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const T=Date.now();(j!==e.lastErrorEmitSig||T-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=j,e.lastErrorEmitAt=T,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:j,fault:p.fault}))}M&&(e.buildFailed=!1);const I=(e.faults??=new pod_output_1.PodFaults).observe(p);if(I){e.lastErrorEmitSig=void 0;const T=I.open.length===0;h(e,"info",`${T?"service recovered":`${I.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:T?"service-recovered":"service-fault-closed",project:e.project,line:j,closed:I.closed,open:I.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(z)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),h(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(W(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),D(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&x(e)},1500).unref?.())},250).unref?.())}},"sniff"),A=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return m.stdout?.on("data",w=>{if(t.stopped())return;R(w);const _=`[${e.project}] ${w}`;process.stdout.write(_),A?.(_)}),m.stderr?.on("data",w=>{if(t.stopped())return;R(w);const _=`[${e.project}] ${w}`;process.stderr.write(_),A?.(_)}),m.on("exit",(w,_)=>{if(e.proc!==m||(e.proc=void 0,e.state="down",t.stopped()))return;const N=(0,exit_reason_1.describeExit)(w,_);if(console.log(`[gateway] ${r} exited (${w??"no code"}${_?`, ${_}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${N?` \u2014 ${N}`:""}`),h(e,w===0?"info":"warning",`service down: ${e.project} (exit ${w})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:w,..._?{signal:_}:{}}),w===0||e.buildFailed)return;const p=Date.now();if(e.restarts=(e.restarts??[]).filter(k=>p-k<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}),x(e))},j)}),m},"launchServiceProcess"),x=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&D(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${s})\u2026`),h(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=X(e,e.generation,"active")},"spawnService"),V=i(async e=>{const o=e.pool.socks[0];if(!o)return;const s=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),P=i(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),G=i(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const s=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,o);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&r.push({sock:c,pid:d.pid})}return r},"staleAdoptedSocks"),Y=i((e,o)=>{const s=o.map(r=>r.sock);for(const{sock:r,pid:c}of o){const d=chooseStalePodKillTarget(c,P(r),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!s.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),b=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return h(e,"info",`service up: ${e.project} (${s} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:o==="adopted"}),!0},"markUp"),re=i(async e=>{if(C(e.pool)>0){const r=await G(e);if(r.length>0&&(console.error(`[gateway] ${e.project}: ${r.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),h(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:r.length}),e.proc&&W(e,"stale adoption (foreign east-west key)"),Y(e,r)),e.pool.socks.length>0)return b(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&x(e);const o=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(C(e.pool)>0){e.state="up",e.buildFailed=!1;const r=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${r?`, cold start ${r}ms`:""})`),h(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${r??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:r}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const o=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,r=e.decl.adminUrl??(e.decl.port?`http://localhost:${e.decl.port}`:void 0);if(!r||s===0||s>=o||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();const c=(r.startsWith("https")?require("node:https"):http).request(`${r}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume());c.on("error",()=>{}),c.end(JSON.stringify({pods:s+1})),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),h(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight})},"scaleUp"),W=i((e,o,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),s?.announce!==!1&&h(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const r=e.proc;try{r.stdout?.removeAllListeners("data"),r.stderr?.removeAllListeners("data"),r.removeAllListeners("exit")}catch{}S(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{J(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),J=i(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const s=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),te=i((e,o)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const r=e.generation??0,c=r+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${r}\u2192g${c} reason=${o} \u2014 standby spawning, old generation keeps serving.`),h(e,"info",`pod handoff starting: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:o,generation:c,fromGeneration:r});const u=X(e,c,"standby"),$=(async()=>{const L=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],m=d+y;let R;for(;!t.stopped()&&Date.now()<m&&!(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(E)}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 S(u,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${A} \u2014 old generation g${r} kept serving.`),h(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:o,generation:c,error:A,durationMs:Date.now()-d}),setTimeout(()=>J(e),1200).unref?.(),!1}if(e.proc!==s)return S(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 S(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${r}.`),h(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:o,generation:c,fromGeneration:r,durationMs:Date.now()-d}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=$,$},"rollPod"),ne=i(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>o-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),W(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS}; ${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"&&x(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??{},o=new Set(t.services.map(r=>r.project));for(const r of t.routes)r.lens&&o.add(r.lens.project);const s=new Set;for(const r of o){const c=e[r];if(!c)continue;const d=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&s.add(d)}return[...s]},"managedBuckets"),ae=i((e,o,s,r)=>{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,o,r);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 o=(0,scope_1.readSourceSetOptions)(t.root),s=o.exclude,r={ignoreTests:o.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,r),d=Date.now(),u=sourceChangeWindowMs(d,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),$=Q?diffSourceSets(Q,c,d,u):null;if($&&(t.sourceSetChangedAt=d,t.sourceSetChange=$,g("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:$.added,removed:$.removed,truncated:$.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 m=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,m,s);if(!j)continue;const k=j.join("|"),M=A.get(k)??{dirs:j,projects:[]};M.projects.push(p),A.set(k,M)}const w=Date.now(),_=sourceChangeWindowMs(w,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);H=e;const N=i((p,j,k)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const M of p)t.sourceSetChangedFor.set(M,j),t.sourceSetChangesFor.set(M,k)},"stampScoped");for(const[p,{dirs:j,projects:k}]of A){const M=await sourceSetSnapshot(t.root,j,s,r),z=B.get(p);B.set(p,M);const I=new Map;for(const K of k){const F=oe.get(K);if(F===void 0||F===p)continue;const q=I.get(F)??[];q.push(K),I.set(F,q)}for(const K of k)oe.set(K,p);for(const[K,F]of I){const q=B.get(K),U=q?diffSourceSets(q,M,w,_):$;U&&(N(F,q?w: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 T=diffSourceSets(z,M,w,_);T&&(N(k,w,T),g("info",`source file set changed in ${k.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:k,added:T.added,removed:T.removed,truncated:T.truncated}))}for(const p of[...B.keys()])A.has(p)||B.delete(p)})().catch(()=>{}).finally(()=>{Z=!1})},"maybeScanSourceSet"),de=i((e,o)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,r=podWatcherVerdict(e.watcher,o,s),c=i(d=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,h(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):r==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):r==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),pe=i((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){te(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),h(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const s=!!e.decl.eager;W(e,`${o} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&x(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{he().catch(o=>console.error("[gateway] sweep error:",o))},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 o=e.decl.idleKillSec;if(o&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){W(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,r=C(e.pool)>0;if(r||s){if(e.wedgedSince=void 0,r&&b(e,"observed"),r&&!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:x,ensureUp:re,scaleUp:se,killSpawned:W,markUp:b,startSweeper:ue,rollPod:te}}v(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==a)return t}v(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
@@ -44,6 +44,11 @@ export interface PodLine {
44
44
  closes: readonly PodFault[];
45
45
  }
46
46
  export declare function classifyPodLine(raw: string): PodLine;
47
+ /** What one line took back: the faults it closed, and the ones still open after it. */
48
+ export interface PodFaultsClosed {
49
+ closed: PodFault[];
50
+ open: PodFault[];
51
+ }
47
52
  /**
48
53
  * The faults a pod has open, as REPORTED to the lens.
49
54
  *
@@ -53,6 +58,10 @@ export declare function classifyPodLine(raw: string): PodLine;
53
58
  */
54
59
  export declare class PodFaults {
55
60
  private readonly open;
56
- /** Fold one line. True when it closed the LAST open fault — the edge worth announcing. */
57
- observe(line: PodLine): boolean;
61
+ /**
62
+ * Fold one line. Answers only when the line CLOSED something — the edge worth announcing —
63
+ * with what is still open, so a bundle that recovers under a standing type error is not left
64
+ * reading "failed to build" until the types are fixed too.
65
+ */
66
+ observe(line: PodLine): PodFaultsClosed | undefined;
58
67
  }
@@ -1 +1 @@
1
- "use strict";var l=Object.defineProperty;var n=(s,e)=>l(s,"name",{value:e,configurable:!0});var d=Object.defineProperty,r=n((s,e)=>d(s,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PodFaults=void 0,exports.classifyPodLine=classifyPodLine;const ANSI_SGR=/\x1b\[[0-9;]*m/g,OPENS=[["build",/ERROR in |Module not found/],["types",/error TS\d+|Found [1-9]\d* errors?/],["process",/Unhandled 'error' event|EADDRINUSE/]],CLOSES=[["build",/compiled successfully/],["types",/No typescript errors found|Found 0 errors|\[tsgo\] .+: types clean/],["process",/Child#\d+ (ready|hot-swapped)/]];function classifyPodLine(s){const e=s.replace(ANSI_SGR,"").trim(),t=OPENS.find(([,o])=>o.test(e))?.[0],i=t?[]:CLOSES.filter(([,o])=>o.test(e)).map(([o])=>o);return{clean:e,broken:t!==void 0,healthy:i.length>0,...t?{fault:t}:{},closes:i}}n(classifyPodLine,"classifyPodLine"),r(classifyPodLine,"classifyPodLine");class PodFaults{static{n(this,"PodFaults")}static{r(this,"PodFaults")}constructor(){this.open=new Set}observe(e){if(e.fault)return this.open.add(e.fault),!1;if(this.open.size===0)return!1;for(const t of e.closes)this.open.delete(t);return this.open.size===0}}exports.PodFaults=PodFaults;
1
+ "use strict";var i=Object.defineProperty;var l=(s,e)=>i(s,"name",{value:e,configurable:!0});var d=Object.defineProperty,r=l((s,e)=>d(s,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PodFaults=void 0,exports.classifyPodLine=classifyPodLine;const ANSI_SGR=/\x1b\[[0-9;]*m/g,OPENS=[["build",/ERROR in |Module not found/],["types",/error TS\d+|Found [1-9]\d* errors?/],["process",/Unhandled 'error' event|EADDRINUSE/]],CLOSES=[["build",/compiled successfully/],["types",/No typescript errors found|Found 0 errors|\[tsgo\] .+: types clean/],["process",/Child#\d+ (ready|hot-swapped)/]];function classifyPodLine(s){const e=s.replace(ANSI_SGR,"").trim(),t=OPENS.find(([,o])=>o.test(e))?.[0],n=t?[]:CLOSES.filter(([,o])=>o.test(e)).map(([o])=>o);return{clean:e,broken:t!==void 0,healthy:n.length>0,...t?{fault:t}:{},closes:n}}l(classifyPodLine,"classifyPodLine"),r(classifyPodLine,"classifyPodLine");class PodFaults{static{l(this,"PodFaults")}static{r(this,"PodFaults")}constructor(){this.open=new Set}observe(e){if(e.fault){this.open.add(e.fault);return}const t=e.closes.filter(n=>this.open.delete(n));return t.length?{closed:t,open:[...this.open]}:void 0}}exports.PodFaults=PodFaults;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.23.9",
3
+ "version": "1.23.11",
4
4
  "description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
5
5
  "main": "./index.js",
6
6
  "types": "./index.d.ts",
@@ -57,9 +57,9 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@lensmcp/core": "1.23.9",
61
- "@lensmcp/node-instrumentation": "1.23.9",
62
- "@lensmcp/nx-plugin": "1.23.9",
60
+ "@lensmcp/core": "1.23.11",
61
+ "@lensmcp/node-instrumentation": "1.23.11",
62
+ "@lensmcp/nx-plugin": "1.23.11",
63
63
  "fastify": "^5.8.5",
64
64
  "@fastify/reply-from": "^12.6.2",
65
65
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",