@lensmcp/cluster 1.24.1 → 1.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +13 -1
  2. package/checkout-cache.d.ts +5 -0
  3. package/checkout-cache.js +1 -0
  4. package/create-webpack-dev.d.ts +1 -1
  5. package/create-webpack-dev.js +1 -1
  6. package/devserver-notifier.d.ts +1 -1
  7. package/devserver-notifier.js +1 -1
  8. package/devserver-transport.d.ts +41 -0
  9. package/devserver-transport.js +1 -0
  10. package/executors/gateway/manifest.js +1 -1
  11. package/executors/gateway/runtime/auth.d.ts +7 -0
  12. package/executors/gateway/runtime/auth.js +1 -1
  13. package/executors/gateway/runtime/chooser.d.ts +2 -1
  14. package/executors/gateway/runtime/chooser.js +23 -10
  15. package/executors/gateway/runtime/control.d.ts +14 -0
  16. package/executors/gateway/runtime/control.js +2 -2
  17. package/executors/gateway/runtime/discovery.d.ts +15 -2
  18. package/executors/gateway/runtime/discovery.js +1 -1
  19. package/executors/gateway/runtime/edge.d.ts +7 -0
  20. package/executors/gateway/runtime/edge.js +1 -1
  21. package/executors/gateway/runtime/guest-replay.d.ts +22 -0
  22. package/executors/gateway/runtime/guest-replay.js +2 -0
  23. package/executors/gateway/runtime/instance.d.ts +104 -0
  24. package/executors/gateway/runtime/instance.js +2 -0
  25. package/executors/gateway/runtime/lens-children.js +1 -1
  26. package/executors/gateway/runtime/lifecycle.d.ts +2 -0
  27. package/executors/gateway/runtime/lifecycle.js +3 -3
  28. package/executors/gateway/runtime/pod-probe.d.ts +17 -0
  29. package/executors/gateway/runtime/pod-probe.js +1 -1
  30. package/executors/gateway/runtime/proxy.d.ts +14 -0
  31. package/executors/gateway/runtime/proxy.js +1 -1
  32. package/executors/gateway/runtime/registration-guards.js +1 -1
  33. package/executors/gateway/runtime/route-registry.d.ts +7 -0
  34. package/executors/gateway/runtime/scope.d.ts +18 -0
  35. package/executors/gateway/runtime/scope.js +1 -1
  36. package/executors/gateway/runtime/server.js +2 -2
  37. package/executors/gateway/runtime/service-keys.d.ts +12 -3
  38. package/executors/gateway/runtime/service-keys.js +1 -1
  39. package/executors/gateway/runtime/shared-build.d.ts +1 -1
  40. package/executors/gateway/runtime/shared-build.js +3 -3
  41. package/executors/gateway/runtime/types.d.ts +47 -0
  42. package/executors/gateway/runtime/types.js +1 -1
  43. package/executors/gateway/runtime/upgrade.d.ts +13 -8
  44. package/executors/gateway/runtime/upgrade.js +1 -1
  45. package/executors/gateway/runtime/workspace-middleware.d.ts +38 -0
  46. package/executors/gateway/runtime/workspace-middleware.js +1 -0
  47. package/executors/gateway/runtime/workspace-registry.d.ts +7 -0
  48. package/executors/serve/serve.impl.js +1 -1
  49. package/executors/trust/trust.impl.js +3 -3
  50. package/inspector-secret.d.ts +6 -0
  51. package/inspector-secret.js +1 -1
  52. package/main.devserver.d.ts +4 -38
  53. package/main.devserver.js +10 -10
  54. package/package.json +4 -4
  55. package/shared-build/host.js +2 -2
@@ -0,0 +1,104 @@
1
+ /** What `.lensmcp/config.json` → `instance` declares. */
2
+ export interface InstanceDecl {
3
+ /** The instance's name: one DNS label of at most 20 characters (`gate1`). */
4
+ name: string;
5
+ /** The key of the workspace it is an instance of (`foodguard`); the instance's own key is `<baseKey>-<name>`. */
6
+ baseKey: string;
7
+ /** The base apex to rewrite under, when the longest common suffix of the declared hosts is not it. */
8
+ apex?: string;
9
+ /** The main checkout's root, when known — recorded for `lensmcp instance list`, never routed on. */
10
+ mainRoot?: string;
11
+ /**
12
+ * FROZEN: serve the commit as it is — each service compiles once and runs with no file watcher and no type checker,
13
+ * its Vite watches nothing, and nothing it is sent hot-swaps it. For a worktree used as a fixed test target; an
14
+ * agent's live worktree leaves it off. Set by `lensmcp instance start --frozen`.
15
+ */
16
+ frozen?: boolean;
17
+ }
18
+ /** One DNS label, at most 20 characters: lowercase letters, digits and inner hyphens. */
19
+ export declare const INSTANCE_NAME_PATTERN: RegExp;
20
+ /** The `instance` block of a parsed config, or `undefined` when there is none or it is malformed (then the checkout
21
+ * is served as an ordinary workspace — a half-written block must never rewrite hosts into something unroutable). */
22
+ export declare function parseInstanceDecl(raw: unknown): InstanceDecl | undefined;
23
+ /** The instance key: `<baseKey>-<name>`. */
24
+ export declare function instanceKey(baseKey: string, name: string): string;
25
+ /**
26
+ * The instance's pod socket folder: `i-` and six hex digits of the key. Eight characters, where the key itself
27
+ * would not fit — a unix socket path has 103 usable characters on macOS, and FoodGuard's longest pod socket took
28
+ * 101 of them under `foodguard` alone. In lock-step with the CLI's `instanceSockNs`.
29
+ */
30
+ export declare function instanceSockNs(key: string): string;
31
+ /**
32
+ * The longest common dotted suffix of `hosts` that is a whole host name (two labels or more): the apex the instance
33
+ * name goes in front of. `['app.foodguard.localhost', 'internal.api.foodguard.localhost']` → `foodguard.localhost`.
34
+ * Wildcards count by their suffix; one distinct host is its own apex. Undefined when the hosts share no such suffix
35
+ * (then `instance.apex` must name it).
36
+ */
37
+ export declare function commonApex(hosts: readonly string[]): string | undefined;
38
+ /**
39
+ * Rewrite one declared host for instance `name` under `apex`: the name goes in front of the apex.
40
+ *
41
+ * app.foodguard.localhost → app.gate1.foodguard.localhost
42
+ * internal.api.foodguard.localhost → internal.api.gate1.foodguard.localhost
43
+ * foodguard.localhost → gate1.foodguard.localhost
44
+ * *.foodguard.localhost → *.gate1.foodguard.localhost
45
+ *
46
+ * `undefined` for a host outside the apex: there is no rule that names it for the instance, and serving it as
47
+ * declared would shadow (or be refused beside) the main checkout's.
48
+ */
49
+ export declare function rewriteHost(host: string, apex: string, name: string): string | undefined;
50
+ /** What discovery hands the env builder for one project: its public host and, for a pod service, its internal one. */
51
+ export interface ProjectHosts {
52
+ host?: string;
53
+ internalHost?: string;
54
+ }
55
+ /** The instance a discovered workspace is, once its apex is known — carried on the fragment and its controllers. */
56
+ export interface InstanceRouting {
57
+ name: string;
58
+ baseKey: string;
59
+ /** The apex the declared hosts live under (`foodguard.localhost`). */
60
+ baseApex: string;
61
+ /** The instance's own apex (`gate1.foodguard.localhost`). */
62
+ apex: string;
63
+ /** Every variable the gateway injects into this instance's pods and Vite children (built-ins + `instance.env`). */
64
+ env: Readonly<Record<string, string>>;
65
+ /** {@link InstanceDecl.frozen}. */
66
+ frozen?: boolean;
67
+ }
68
+ /** `knowledge-api` → `KNOWLEDGE_API`: the env-name form of a project name. */
69
+ export declare function envSuffix(project: string): string;
70
+ /**
71
+ * The built-in variables of an instance — the addresses a test or a service needs, and nothing else:
72
+ *
73
+ * LENSMCP_INSTANCE gate1
74
+ * LENSMCP_APEX gate1.foodguard.localhost
75
+ * LENSMCP_BASE_APEX foodguard.localhost
76
+ * LENSMCP_HOST_<PROJECT> app.gate1.foodguard.localhost (each project with a host)
77
+ * LENSMCP_INTERNAL_HOST_<PROJ> internal.api.gate1.foodguard.localhost (each pod service with an internal route)
78
+ * LENSMCP_HOSTS_JSON {"<project>":{"host":…,"internalHost":…},…}
79
+ */
80
+ export declare function instanceBuiltinEnv(inst: {
81
+ name: string;
82
+ apex: string;
83
+ baseApex: string;
84
+ }, hosts: Readonly<Record<string, ProjectHosts>>): Record<string, string>;
85
+ /** `<root>/.lensmcp/instance.env` — the instance's own variables, layered over the built-ins. */
86
+ export declare function instanceEnvFile(root: string): string;
87
+ /** `<root>/.lensmcp/instance.resources.env` — written by `lensmcp instance start` from the project's declared
88
+ * resources (its database URL, its Redis db, …): under `instance.env`, over the built-ins. */
89
+ export declare function instanceResourcesEnvFile(root: string): string;
90
+ /**
91
+ * Parse an `instance.env` (dotenv): `KEY=value` lines, `#` comments, an optional `export `, values optionally in
92
+ * single or double quotes. `${VAR}` in an unquoted or double-quoted value expands over `scope` (the built-ins)
93
+ * and the keys defined above it in the file; an unknown name expands to the empty string. Single quotes are
94
+ * literal. Malformed lines are skipped and named in `skipped` — never thrown, a typo must not take the instance down.
95
+ */
96
+ export declare function parseInstanceEnv(text: string, scope: Readonly<Record<string, string>>): {
97
+ env: Record<string, string>;
98
+ skipped: number[];
99
+ };
100
+ /**
101
+ * The built-ins, then `instance.resources.env` (the lens's, from the declared resources), then `instance.env` (the
102
+ * user's) over them — each file expanding over everything below it. A missing file is the common case.
103
+ */
104
+ export declare function instanceEnv(root: string, builtins: Readonly<Record<string, string>>): Record<string, string>;
@@ -0,0 +1,2 @@
1
+ "use strict";var x=Object.defineProperty;var a=(t,e)=>x(t,"name",{value:e,configurable:!0});var h=Object.defineProperty,c=a((t,e)=>h(t,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.INSTANCE_NAME_PATTERN=void 0,exports.parseInstanceDecl=parseInstanceDecl,exports.instanceKey=instanceKey,exports.instanceSockNs=instanceSockNs,exports.commonApex=commonApex,exports.rewriteHost=rewriteHost,exports.envSuffix=envSuffix,exports.instanceBuiltinEnv=instanceBuiltinEnv,exports.instanceEnvFile=instanceEnvFile,exports.instanceResourcesEnvFile=instanceResourcesEnvFile,exports.parseInstanceEnv=parseInstanceEnv,exports.instanceEnv=instanceEnv;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));exports.INSTANCE_NAME_PATTERN=/^[a-z0-9](?:[a-z0-9-]{0,18}[a-z0-9])?$/;const HOST_PATTERN=/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/;function parseInstanceDecl(t){if(!t||typeof t!="object")return;const e=t;if(typeof e.name!="string"||!exports.INSTANCE_NAME_PATTERN.test(e.name)||typeof e.baseKey!="string"||e.baseKey.length===0)return;const n=typeof e.apex=="string"&&HOST_PATTERN.test(e.apex.toLowerCase())?e.apex.toLowerCase():void 0;return{name:e.name,baseKey:e.baseKey,...n?{apex:n}:{},...typeof e.mainRoot=="string"&&e.mainRoot.length>0?{mainRoot:e.mainRoot}:{},...e.frozen===!0?{frozen:!0}:{}}}a(parseInstanceDecl,"parseInstanceDecl"),c(parseInstanceDecl,"parseInstanceDecl");function instanceKey(t,e){return`${t}-${e}`}a(instanceKey,"instanceKey"),c(instanceKey,"instanceKey");function instanceSockNs(t){let e=2166136261;for(let n=0;n<t.length;n++)e^=t.charCodeAt(n),e=Math.imul(e,16777619);return`i-${(e>>>0&16777215).toString(16).padStart(6,"0")}`}a(instanceSockNs,"instanceSockNs"),c(instanceSockNs,"instanceSockNs");function commonApex(t){const e=t.map(s=>s.toLowerCase().replace(/^\*\./,"")).filter(s=>s.length>0).map(s=>s.split(".").reverse());if(e.length===0)return;const n=[];for(let s=0;;s++){const i=e[0]?.[s];if(i===void 0||!e.every(r=>r[s]===i))break;n.push(i)}return n.length>=2?n.reverse().join("."):void 0}a(commonApex,"commonApex"),c(commonApex,"commonApex");function rewriteHost(t,e,n){const s=t.toLowerCase();if(s.startsWith("*.")){const i=rewriteHost(s.slice(2),e,n);return i===void 0?void 0:`*.${i}`}if(s===e)return`${n}.${e}`;if(s.endsWith(`.${e}`))return`${s.slice(0,-e.length)}${n}.${e}`}a(rewriteHost,"rewriteHost"),c(rewriteHost,"rewriteHost");function envSuffix(t){return t.toUpperCase().replace(/[^A-Z0-9]+/g,"_").replace(/^_+|_+$/g,"")}a(envSuffix,"envSuffix"),c(envSuffix,"envSuffix");function instanceBuiltinEnv(t,e){const n={LENSMCP_INSTANCE:t.name,LENSMCP_APEX:t.apex,LENSMCP_BASE_APEX:t.baseApex},s={};for(const[i,r]of Object.entries(e).sort(([o],[l])=>o.localeCompare(l))){const o=envSuffix(i);o&&(r.host&&(n[`LENSMCP_HOST_${o}`]=r.host),r.internalHost&&(n[`LENSMCP_INTERNAL_HOST_${o}`]=r.internalHost),(r.host||r.internalHost)&&(s[i]={...r.host?{host:r.host}:{},...r.internalHost?{internalHost:r.internalHost}:{}}))}return n.LENSMCP_HOSTS_JSON=JSON.stringify(s),n}a(instanceBuiltinEnv,"instanceBuiltinEnv"),c(instanceBuiltinEnv,"instanceBuiltinEnv");function instanceEnvFile(t){return path.join(t,".lensmcp","instance.env")}a(instanceEnvFile,"instanceEnvFile"),c(instanceEnvFile,"instanceEnvFile");function instanceResourcesEnvFile(t){return path.join(t,".lensmcp","instance.resources.env")}a(instanceResourcesEnvFile,"instanceResourcesEnvFile"),c(instanceResourcesEnvFile,"instanceResourcesEnvFile");function parseInstanceEnv(t,e){const n={},s=[],i=c(o=>n[o]??e[o]??"","lookup"),r=c(o=>o.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(l,u)=>i(u)),"expand");return t.split(/\r?\n/).forEach((o,l)=>{const u=o.trim();if(u===""||u.startsWith("#"))return;const v=/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(u);if(!v){s.push(l+1);return}const[,_,p]=v;let E;if(p.startsWith("'")){const f=p.indexOf("'",1);if(f<0){s.push(l+1);return}E=p.slice(1,f)}else if(p.startsWith('"')){const f=p.indexOf('"',1);if(f<0){s.push(l+1);return}E=r(p.slice(1,f).replace(/\\n/g,`
2
+ `))}else E=r(p.replace(/\s+#.*$/,"").trim());n[_]=E}),{env:n,skipped:s}}a(parseInstanceEnv,"parseInstanceEnv"),c(parseInstanceEnv,"parseInstanceEnv");function instanceEnv(t,e){let n={...e};for(const s of[instanceResourcesEnvFile(t),instanceEnvFile(t)]){let i;try{i=fs.readFileSync(s,"utf8")}catch{continue}const{env:r,skipped:o}=parseInstanceEnv(i,n);o.length>0&&console.warn(`[gateway] ${s}: skipped malformed line(s) ${o.join(", ")}`),n={...n,...r}}return{...n,...e}}a(instanceEnv,"instanceEnv"),c(instanceEnv,"instanceEnv");
@@ -1 +1 @@
1
- "use strict";var $e=Object.defineProperty;var v=(s,E)=>$e(s,"name",{value:E,configurable:!0});var Pe=Object.defineProperty,d=v((s,E)=>Pe(s,"name",{value:E,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.describeChildExit=describeChildExit,exports.signalChild=signalChild,exports.claimPort=claimPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),process_name_1=require("../../../process-name"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(s,E){return new Promise(f=>{const _=net.createServer();_.unref(),_.once("error",p=>{const g=p.code??"";f(g==="EADDRINUSE"||g==="EACCES"?"in-use":"unavailable")}),_.listen(E===void 0?{port:s}:{port:s,host:E},()=>_.close(()=>f("free")))})}v(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(s){for(const E of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(s,E)==="in-use")return!1;return!0}v(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(s,E,f){return new Promise(_=>{let p=!1;const g=net.createConnection({host:s,port:E});g.unref();const M=setTimeout(()=>L(!1),f);M.unref?.();function L(N){p||(p=!0,clearTimeout(M),g.destroy(),_(N))}v(L,"R"),d(L,"finish"),g.once("connect",()=>L(!0)),g.once("error",()=>L(!1))})}v(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(s,E=1e3){return await probeConnect("127.0.0.1",s,E)?!0:probeConnect("::1",s,E)}v(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(s,E=1500){try{const f=new AbortController,_=setTimeout(()=>f.abort(),E);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${s}/__lensmcp/watcher-health`,{signal:f.signal});if(!p.ok)return null;const g=await p.json();if(typeof g?.lastFsSetEventAt!="number")return null;const M=Array.isArray(g.recentEvents)?g.recentEvents.filter(L=>L&&typeof L.path=="string"&&typeof L.at=="number"&&typeof L.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(L.kind)):[];return{schemaVersion:g.schemaVersion===2?2:1,ready:g.ready!==!1,sequence:typeof g.sequence=="number"?g.sequence:0,lastFsSetEventAt:g.lastFsSetEventAt,fsSetEvents:typeof g.fsSetEvents=="number"?g.fsSetEvents:0,recentEvents:M}}finally{clearTimeout(_)}}catch{return null}}v(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(s=>new Promise(E=>{setTimeout(E,s).unref?.()}),"sleepUnref");async function waitForPortFree(s,E,f={}){const _=f.isPortFree??probePortFree,p=Math.max(1,f.intervalMs??types_1.PORT_FREE_POLL_MS),g=Date.now()+Math.max(0,E);for(;;){if(await _(s))return!0;const M=g-Date.now();if(M<=0)return!1;await sleepUnref(Math.min(p,M))}}v(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(s){const E=d(f=>Number.isInteger(f)&&f>0&&f<65536?f:void 0,"valid");for(let f=0;f<s.length;f+=1){const _=s[f];if(_==="--port")return E(Number(s[f+1]));if(_.startsWith("--port="))return E(Number(_.slice(7)))}}v(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(s){return s.signalCode?`killed by ${s.signalCode}`:s.exitCode===null?"exit status unknown":`exit code ${s.exitCode}`}v(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");function signalChild(s,E){if(s.pid===void 0)return!1;try{return s.kill(E)}catch{return!1}}v(signalChild,"signalChild"),d(signalChild,"signalChild");async function claimPort(s,E,f,_){let p=s;for(;E.has(p)||_?.has(p)||!await f(p);)p+=1;return E.add(p),p}v(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(s,E,f,_){const{emit:p}=E,g=_?.isPortFree??probePortFree,M=(f.ports?.length?f.ports:[443]).join(","),L=_?.probeWatcherHealth??probeWatcherHealth,N=[],B=new Map,A=new Map,I=new Map,Y=new Map,W=new Set;let j;const m=new Map,R=new Set,q=new Set,V=new Set,T=new Set,$=new Map,F=new WeakMap,G=new Set,Z=d((e,r,n)=>{if(G.has(e))return;G.add(e);const t=setInterval(()=>{(async()=>{if(s.stopped()||R.has(e)){clearInterval(t),G.delete(e);return}await g(r)&&(clearInterval(t),G.delete(e),console.log(`[gateway] lens child ${e}: :${r} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),x=d(async(e,r,n)=>{const t=await claimPort(e,r,g,n?.reserved);return t!==e&&(n?.heldBy?console.log(`[gateway] handoff: :${e} is held by ${n.heldBy} \u2014 replacement on :${t}.`):console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${t} instead.`)),t},"claimFreePort"),ce=d((e,r,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...$.get(t)?.generations??[]].find(l=>l.generation===o);a&&(e.endsWith(":vite")?a.vite=r:e.endsWith(":browser-capture")&&(a.capture=r))},"attachRespawnedGenerationChild"),H=d(e=>{e.exitCode!==null||e.signalCode!==null||e.pid===void 0||(q.add(e),signalChild(e,"SIGTERM"),setTimeout(()=>{e.exitCode===null&&e.signalCode===null&&signalChild(e,"SIGKILL")},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.())},"retireChild"),de=d((e,r)=>{const n=m.get(e);!n||n===r||n.exitCode!==null||n.signalCode!==null||(console.warn(`[gateway] lens child ${e}: a replacement took the label \u2014 retiring the previous child (pid ${n.pid??"?"}).`),H(n))},"retireLabelHolder"),k=d((e,r,n,t,o=!1,a,l,c)=>{const u=Date.now(),i=(0,node_child_process_1.spawn)(r,n,{cwd:l??s.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,s.root,l??s.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});N.push(i),de(e,i),m.set(e,i),ce(e,i,t);const S=e==="vite"||e.endsWith(":vite"),b=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=b&&b!==s.eventFile?b:void 0,w=S?viteArgPort(n):c;S&&A.set(i,{key:t?.LENSMCP_FRONTEND_KEY??`${l??s.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:l??s.root,spawnedAt:u,port:w,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...y?{bus:y}:{}});const h=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return h&&w!==void 0&&(p("info",`lens child starting: ${h} (:${w})`,`cluster-lens-child:${h}`,{kind:"lens-child-starting",project:h,label:e,pid:i.pid,port:w},void 0,y),(async()=>{const C=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<C;){if(i.exitCode!==null||i.signalCode!==null||R.has(e))return;if(await probePortListening(w,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${h} (:${w})`,`cluster-lens-child:${h}`,{kind:"lens-child-up",project:h,label:e,pid:i.pid,port:w},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),i.on("error",C=>{if(i.pid!==void 0){console.warn(`[gateway] lens child ${e}: ${C.message}`);return}const D=N.indexOf(i);D>=0&&N.splice(D,1),m.get(e)===i&&m.delete(e),A.delete(i),q.delete(i),console.error(`[gateway] lens child ${e} failed to spawn (${C.message}) \u2014 not started, not retried.`)}),i.on("exit",(C,D)=>{const se=N.indexOf(i);se>=0&&N.splice(se,1),m.get(e)===i&&m.delete(e);const O=A.get(i);if(A.delete(i),q.delete(i))return;const ve=R.delete(e);if(s.stopped()||ve||(h?p("warning",`lens child exited: ${h}`,`cluster-lens-child:${h}`,{kind:"lens-child-exit",project:h,label:e,pid:i.pid,code:C,signal:D},void 0,y):O&&p("warning",`lens frontend exited: ${O.project}`,`cluster-lens-child:${O.project}`,{kind:"lens-child-exit",project:O.project,label:e,pid:i.pid,code:C,signal:D,generation:O.generation,port:O.port,ranMs:Date.now()-u},void 0,y),O&&fe(i,O)))return;const ae=Date.now()-u,P=B.get(e)??{attempts:0,portConflicts:0};(async()=>{const Le=w!==void 0&&!await g(w),ie=(0,lifecycle_1.isPortConflictExit)({code:C,signal:D,ranMs:ae,portHeld:Le},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ie&&P.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)P.portConflicts+=1,B.set(e,P),console.warn(`[gateway] lens child ${e} lost the :${w} bind race (EADDRINUSE, code=${C}); waiting for the port, then retrying (port-retry ${P.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(ie){P.portConflicts=0,B.set(e,P),console.warn(`[gateway] lens child ${e}: :${w} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${w} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:w}),Z(e,w,()=>k(e,r,n,t,o,a,l,c));return}else{P.portConflicts=0,ae>=HEAL_HEALTHY_MS&&(P.attempts=0);const le=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**P.attempts);P.attempts+=1,B.set(e,P),console.warn(`[gateway] lens child ${e} exited (code=${C} sig=${D}); auto-healing in ${Math.round(le/1e3)}s (attempt ${P.attempts}).`),await sleepUnref(le)}s.stopped()||R.has(e)||(w!==void 0&&!await waitForPortFree(w,types_1.PORT_FREE_WAIT_MS,{isPortFree:g,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${w} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(s.stopped()||R.has(e))&&k(e,r,n,t,o,a,l,c))})().catch(()=>{})}),i},"spawnManagedChild"),ee=d(e=>{const r=`${e.wsKey}:${e.project}`,n=$.get(r);if(n)return n;const t={key:r,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};$.set(r,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,F.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,F.set(e.frontend,t),t},"registerFrontend"),pe=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const r=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==r);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),te=d((e,r,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,l=`${o}browser-capture`;R.delete(a),R.delete(l);const c=d((S,b,y,w,h,C)=>k(S,b,y,w,h,C,e.workspaceRoot),"spawnInRoot"),u=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:r,chrome:!0,headless:!0,viteEnv:{LENSMCP_PROJECT:e.project,LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:n,...pe(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:o,log:d(S=>console.log(S),"log")},c),i={ownerKey:e.key,generation:t,port:r,labelPrefix:o,vite:u?.vite??m.get(a),capture:m.get(l)};return e.generations.add(i),i},"spawnFrontendGeneration"),K=d((e,r)=>{if(!e)return;$.get(e.ownerKey)?.generations.delete(e);const n=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,o]of n)R.add(t),o&&H(o);r&&T.delete(e.port)},"terminateGeneration"),ue=d(async e=>{const r=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<r;){const n=e.vite;if(!n||n.exitCode!==null||n.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await L(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),U=d(async e=>{const r=e?.vite;return!!(e&&r&&r.exitCode===null&&r.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),z=d(e=>!e.disposed&&!s.stopped()&&$.get(e.key)===e,"frontendIsCurrent"),X=d((e,r)=>{if(!z(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=r,e.startedAt=Date.now();const t=(async()=>{let o,a;const l=Date.now();try{const c=new Set;for(const h of $.values())h!==e&&c.add(h.desiredPort);const u=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,i=await x(e.desiredPort,T,{reserved:c,heldBy:u});if(a=i,!z(e))return T.delete(i),!1;o=te(e,i,"replacement"),e.candidate=o;const S=await U(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${i} reason=${r}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:i,oldServing:S},void 0,e.eventFile===s.eventFile?void 0:e.eventFile);const b=await ue(o);if(!z(e))return K(o,!0),e.candidate=void 0,!1;if(!b){K(o,!0),e.candidate=void 0;const h=await U(n);return e.state=h?"ready":"failed",h&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${o.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,port:i,durationMs:Date.now()-l,oldKept:h},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}for(const h of e.routes)h.target=`http://localhost:${o.port}`,h.lens&&(h.lens.port=o.port);e.frontend.port=o.port,e.active=o,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const y=o.vite?A.get(o.vite):void 0;y&&(y.role="active",y.listening=!0);const w=(0,hmr_reload_1.broadcastFullReload)(s,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${o.generation} :${o.port} in ${Date.now()-l}ms; draining g${n?.generation??0}${w>0?`; reloading ${w} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:r,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-l,reloadedTabs:w},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),n){const h=`${n.labelPrefix}browser-capture`;R.add(h),n.capture&&H(n.capture),setTimeout(()=>K(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(c){K(o,!0),!o&&a!==void 0&&T.delete(a),e.candidate=void 0;const u=await U(n);e.state=u?"ready":"failed",u&&(e.reason=void 0,e.startedAt=void 0);const i=c.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${i}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-l,oldKept:u,error:i},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),oe=d((e,r)=>{e.active=te(e,r,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),fe=d((e,r)=>{const n=$.get(r.key);return!n||types_1.LENS_RESTART_MODE==="in-place"?!1:(n.candidate?.vite===e||n.active?.vite!==e||(n.state="starting",n.reason="child-exit",n.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${n.key} g${r.generation} :${r.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),X(n,"child-exit")),!0)},"handleManagedViteExit");s.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const r=F.get(e);return r?{state:r.state,generation:r.candidate?.generation??r.active?.generation??r.generation,reason:r.reason,startedAt:r.startedAt}:void 0},recover(e,r){const n=F.get(e);return n?X(n,r):Promise.resolve(!1)}}:void 0;let J=!1,ne=0;const Ee=d(()=>{J||Date.now()-ne<types_1.POD_STALE_SCAN_MS||(J=!0,ne=Date.now(),(async()=>{const e=new Map;for(const r of[...$.values()]){if(s.stopped())return;let n=e.get(r.workspaceRoot);if(!n){const i=(0,scope_1.readSourceSetOptions)(r.workspaceRoot);n={pass:(0,lifecycle_1.sourceScanPass)(r.workspaceRoot,i.exclude,{ignoreTests:i.ignoreTests}),startedAt:Date.now()},e.set(r.workspaceRoot,n)}const t=n.startedAt,o=await n.pass.snapshot(r.sourceDirs);if($.get(r.key)!==r)continue;const a=r.sourceSnapshot,l=r.sourceScanStartedAt;if(r.sourceSnapshot=o,r.sourceScanStartedAt=t,!a)continue;const c=Date.now(),u=(0,lifecycle_1.diffSourceSets)(a,o,c,(0,lifecycle_1.sourceChangeWindowMs)(c,l,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));u&&(r.sourceChange=u,p("info",`source file set changed for ${r.key}`,`cluster-lens-app:${r.project}`,{kind:"source-set-changed",scoped:!0,wsKey:r.wsKey,project:r.project,added:u.added,removed:u.removed,truncated:u.truncated},void 0,r.eventFile===s.eventFile?void 0:r.eventFile))}})().catch(()=>{}).finally(()=>{J=!1}))},"scanFrontendSourceSets");let Q=!1;const ge=d(async(e,r)=>{if(!Q){Q=!0;try{for(const[n,t]of[...A]){if(s.stopped())return;if(t.port===void 0||n.exitCode!==null||n.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const c=$.get(t.key);c?.active?.vite===n&&(c.state="ready",c.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const o=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<o){t.probeFails=0;continue}if(!r)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},I.get(t.key)??0,Date.now(),e);if(!a)continue;const l=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(o/1e3)}s boot allowance`;I.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${l} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus),signalChild(n,"SIGKILL")}}finally{Q=!1}}},"probeLensChildren"),he=d(()=>{if(j||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};j=setInterval(()=>{if(s.stopped()||A.size===0||(re(),!types_1.POD_RECYCLE_ENABLED))return;Ee();const r=Date.now();for(const[n,t]of A){const o=$.get(t.key),a=o?.sourceChange?.changedAt??0,l=I.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,l,a,r,e)&&((Y.get(t.key)??0)>=a||W.has(t.key)||(W.add(t.key),(async()=>{try{const c=o?.sourceChange,u=c&&!c.truncated?[...c.added,...c.removed].map(y=>path.resolve(t.workspaceRoot,y)):[],i=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await L(t.port),a,c?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,u):"unknown";if(i==="heard"){Y.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(i==="unknown"&&types_1.LENS_WATCHER_UNVERIFIED==="keep"){console.log(`[gateway] stale source set: lens app ${t.project} unverifiable (no watcher testimony) \u2014 keeping it (LENSMCP_LENS_WATCHER_UNVERIFIED=keep).`);return}if(n.exitCode!==null||n.signalCode!==null)return;I.set(t.key,Date.now());const S=$.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&S){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${i==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:S.wsKey,project:t.project,reason:"stale-source-set",watcher:i,added:c?.added,removed:c?.removed},void 0,t.bus),await X(S,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${i==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:i},void 0,t.bus);const b=(0,hmr_reload_1.broadcastFullReload)(s,t.key);b>0&&console.log(`[gateway] \u2192 told ${b} open tab(s) to reload (stale optimize-dep generation)`),signalChild(n,"SIGTERM"),setTimeout(()=>{n.exitCode===null&&n.signalCode===null&&(console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`),signalChild(n,"SIGKILL"))},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{W.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),j.unref?.()},"startLensSweeper"),we=d(async()=>{const e=[...new Map(s.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],r=f.dashboard!==!1,n=f.mcp!==!1&&e.length>0;if(r){const t=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const o=(0,scope_1.readLensScope)(s.root),a=await x(f.dashboardPort??o.dashboardPort,V),l=f.lensHost??"lensmcp.local",c=o.basePath;s.registry.appendRoutes(s.wsKey,[{host:l,prefix:c,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),s.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:s.root,base:c,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${l}${c}/)`),p("info",`lens dashboard up: ${l}${c} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:l,port:a,base:c}),k("lens-dashboard",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"dashboard")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:c,LENSMCP_GATEWAY_PORTS:M},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(s.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const o=(0,scope_1.readLensScope)(s.root),a=Number(process.env.LENSMCP_PORT??o.mcpHttpPort),l=d(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),k("lens-mcp",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"mcp")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_WORKSPACE_ROOT:s.root},!1,void 0,void 0,a)},"spawnMcp");await g(a)?l():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),Z("lens-mcp",a,l))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(s.registry.get(s.wsKey)?.routes??s.routes).filter(u=>u.lens?.project===t.project),l=await x(t.port,T);for(const u of a)u.lens&&(u.lens.port=l),u.target=`http://localhost:${l}`;t.port=l;const c=ee({wsKey:s.wsKey,project:t.project,workspaceRoot:s.root,projectRoot:t.projectRoot,eventFile:s.eventFile,routes:a,frontend:t,desiredPort:o});try{oe(c,l),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(u){T.delete(l),c.state="failed",console.error(`[gateway] lens app ${t.project}: ${u.message}`)}}he()},"bootSingletonsAndApps"),_e=d(async e=>{if(f.dashboard===!1)return;const r=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!r)return;const n=(0,scope_1.readLensScope)(e.root),t=f.lensHost??"lensmcp.local",o=n.basePath,a=await x(n.dashboardPort,V),l=`lens-dashboard:${e.wsKey}`,c=path.join(e.root,".lensmcp","events.jsonl");return R.delete(l),s.registry.appendRoutes(e.wsKey,[{host:t,prefix:o,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:o,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${o}/)`),p("info",`lens dashboard up: ${t}${o} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:o}),k(l,(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),[r],{LENSMCP_EVENT_FILE:c,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey,LENSMCP_GATEWAY_PORTS:M},!1,void 0,e.root,a),()=>{R.add(l),V.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const u=m.get(l);u&&H(u),m.delete(l)}},"hostWorkspaceDashboard"),Se=d(async e=>{const r=new Map;for(const o of e.routes){if(!o.lens)continue;const a=r.get(o.lens.project)??[];a.push(o),r.set(o.lens.project,a)}if(r.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of r){const l=a[0].lens;if(l.started)continue;const c=await x(l.port,T),u=l.port;for(const S of a)S.lens.started=!0,S.lens.port=c,S.target=`http://localhost:${c}`;const i=ee({wsKey:e.wsKey,project:o,workspaceRoot:e.root,projectRoot:l.projectRoot,eventFile:n,routes:a,frontend:l,desiredPort:u});t.push(i),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${c}`);try{oe(i,c),p("info",`lens app started: ${e.wsKey}/${o} (vite :${c})`,`cluster-lens-app:${e.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:c},void 0,n)}catch(S){T.delete(c),i.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${o}: ${S.message}`)}}return()=>{for(const o of t){o.disposed=!0;for(const a of[...o.generations])K(a,!0);for(const a of o.routes)a.lens&&F.delete(a.lens);F.delete(o.frontend),I.delete(o.key),Y.delete(o.key),W.delete(o.key),$.delete(o.key)}}},"hostWorkspaceApps"),ye=d(async(e=2500)=>{j&&(clearInterval(j),j=void 0);const r=[...N];for(const o of r)signalChild(o,"SIGTERM");const n=d(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&r.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of r)n(o)&&signalChild(o,"SIGKILL")},"reapAll"),re=d(()=>ge({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:k,bootSingletonsAndApps:we,hostWorkspaceDashboard:_e,hostWorkspaceApps:Se,probeLensFrontends:re,reapAll:ye}}v(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
1
+ "use strict";var Ce=Object.defineProperty;var v=(s,E)=>Ce(s,"name",{value:E,configurable:!0});var Pe=Object.defineProperty,d=v((s,E)=>Pe(s,"name",{value:E,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.probePortFree=probePortFree,exports.probePortListening=probePortListening,exports.probeWatcherHealth=probeWatcherHealth,exports.waitForPortFree=waitForPortFree,exports.viteArgPort=viteArgPort,exports.describeChildExit=describeChildExit,exports.signalChild=signalChild,exports.claimPort=claimPort,exports.createLensChildren=createLensChildren;const tslib_1=require("tslib"),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_child_process_1=require("node:child_process"),lens_frontend_1=require("@lensmcp/nx-plugin/lens-frontend"),hmr_reload_1=require("./hmr-reload"),process_name_1=require("../../../process-name"),pod_env_1=require("./pod-env"),scope_1=require("./scope"),workspace_registry_1=require("./workspace-registry"),lifecycle_1=require("./lifecycle"),types_1=require("./types"),observability_1=require("./observability"),HEAL_BASE_MS=1500,HEAL_MAX_MS=3e4,HEAL_HEALTHY_MS=2e4;function tryBind(s,E){return new Promise(f=>{const _=net.createServer();_.unref(),_.once("error",p=>{const g=p.code??"";f(g==="EADDRINUSE"||g==="EACCES"?"in-use":"unavailable")}),_.listen(E===void 0?{port:s}:{port:s,host:E},()=>_.close(()=>f("free")))})}v(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(s){for(const E of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(s,E)==="in-use")return!1;return!0}v(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(s,E,f){return new Promise(_=>{let p=!1;const g=net.createConnection({host:s,port:E});g.unref();const M=setTimeout(()=>L(!1),f);M.unref?.();function L(b){p||(p=!0,clearTimeout(M),g.destroy(),_(b))}v(L,"R"),d(L,"finish"),g.once("connect",()=>L(!0)),g.once("error",()=>L(!1))})}v(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(s,E=1e3){return await probeConnect("127.0.0.1",s,E)?!0:probeConnect("::1",s,E)}v(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(s,E=1500){try{const f=new AbortController,_=setTimeout(()=>f.abort(),E);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${s}/__lensmcp/watcher-health`,{signal:f.signal});if(!p.ok)return null;const g=await p.json();if(typeof g?.lastFsSetEventAt!="number")return null;const M=Array.isArray(g.recentEvents)?g.recentEvents.filter(L=>L&&typeof L.path=="string"&&typeof L.at=="number"&&typeof L.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(L.kind)):[];return{schemaVersion:g.schemaVersion===2?2:1,ready:g.ready!==!1,sequence:typeof g.sequence=="number"?g.sequence:0,lastFsSetEventAt:g.lastFsSetEventAt,fsSetEvents:typeof g.fsSetEvents=="number"?g.fsSetEvents:0,recentEvents:M}}finally{clearTimeout(_)}}catch{return null}}v(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(s=>new Promise(E=>{setTimeout(E,s).unref?.()}),"sleepUnref");async function waitForPortFree(s,E,f={}){const _=f.isPortFree??probePortFree,p=Math.max(1,f.intervalMs??types_1.PORT_FREE_POLL_MS),g=Date.now()+Math.max(0,E);for(;;){if(await _(s))return!0;const M=g-Date.now();if(M<=0)return!1;await sleepUnref(Math.min(p,M))}}v(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(s){const E=d(f=>Number.isInteger(f)&&f>0&&f<65536?f:void 0,"valid");for(let f=0;f<s.length;f+=1){const _=s[f];if(_==="--port")return E(Number(s[f+1]));if(_.startsWith("--port="))return E(Number(_.slice(7)))}}v(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(s){return s.signalCode?`killed by ${s.signalCode}`:s.exitCode===null?"exit status unknown":`exit code ${s.exitCode}`}v(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");function signalChild(s,E){if(s.pid===void 0)return!1;try{return s.kill(E)}catch{return!1}}v(signalChild,"signalChild"),d(signalChild,"signalChild");async function claimPort(s,E,f,_){let p=s;for(;E.has(p)||_?.has(p)||!await f(p);)p+=1;return E.add(p),p}v(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(s,E,f,_){const{emit:p}=E,g=_?.isPortFree??probePortFree,M=(f.ports?.length?f.ports:[443]).join(","),L=_?.probeWatcherHealth??probeWatcherHealth,b=[],B=new Map,A=new Map,I=new Map,Y=new Map,W=new Set;let j;const m=new Map,$=new Set,q=new Set,V=new Set,T=new Set,C=new Map,F=new WeakMap,G=new Set,Q=d((e,r,o)=>{if(G.has(e))return;G.add(e);const t=setInterval(()=>{(async()=>{if(s.stopped()||$.has(e)){clearInterval(t),G.delete(e);return}await g(r)&&(clearInterval(t),G.delete(e),console.log(`[gateway] lens child ${e}: :${r} freed \u2014 respawning.`),o())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),x=d(async(e,r,o)=>{const t=await claimPort(e,r,g,o?.reserved);return t!==e&&(o?.heldBy?console.log(`[gateway] handoff: :${e} is held by ${o.heldBy} \u2014 replacement on :${t}.`):console.warn(`[gateway] port ${e} is unavailable (another process holds it \u2014 an orphan from a crashed gateway?) \u2014 using ${t} instead.`)),t},"claimFreePort"),ce=d((e,r,o)=>{const t=o?.LENSMCP_FRONTEND_KEY,n=Number(o?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(n))return;const a=[...C.get(t)?.generations??[]].find(l=>l.generation===n);a&&(e.endsWith(":vite")?a.vite=r:e.endsWith(":browser-capture")&&(a.capture=r))},"attachRespawnedGenerationChild"),H=d(e=>{e.exitCode!==null||e.signalCode!==null||e.pid===void 0||(q.add(e),signalChild(e,"SIGTERM"),setTimeout(()=>{e.exitCode===null&&e.signalCode===null&&signalChild(e,"SIGKILL")},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.())},"retireChild"),de=d((e,r)=>{const o=m.get(e);!o||o===r||o.exitCode!==null||o.signalCode!==null||(console.warn(`[gateway] lens child ${e}: a replacement took the label \u2014 retiring the previous child (pid ${o.pid??"?"}).`),H(o))},"retireLabelHolder"),k=d((e,r,o,t,n=!1,a,l,c)=>{const u=Date.now(),i=(0,node_child_process_1.spawn)(r,o,{cwd:l??s.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,s.root,l??s.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});b.push(i),de(e,i),m.set(e,i),ce(e,i,t);const S=e==="vite"||e.endsWith(":vite"),N=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=N&&N!==s.eventFile?N:void 0,w=S?viteArgPort(o):c;S&&A.set(i,{key:t?.LENSMCP_FRONTEND_KEY??`${l??s.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:l??s.root,spawnedAt:u,port:w,probeFails:0,listening:!1,generation:Number(t?.LENSMCP_FRONTEND_GENERATION??0),role:t?.LENSMCP_FRONTEND_ROLE==="replacement"?"replacement":"active",...y?{bus:y}:{}});const h=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return h&&w!==void 0&&(p("info",`lens child starting: ${h} (:${w})`,`cluster-lens-child:${h}`,{kind:"lens-child-starting",project:h,label:e,pid:i.pid,port:w},void 0,y),(async()=>{const P=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<P;){if(i.exitCode!==null||i.signalCode!==null||$.has(e))return;if(await probePortListening(w,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${h} (:${w})`,`cluster-lens-child:${h}`,{kind:"lens-child-up",project:h,label:e,pid:i.pid,port:w},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),i.on("error",P=>{if(i.pid!==void 0){console.warn(`[gateway] lens child ${e}: ${P.message}`);return}const D=b.indexOf(i);D>=0&&b.splice(D,1),m.get(e)===i&&m.delete(e),A.delete(i),q.delete(i),console.error(`[gateway] lens child ${e} failed to spawn (${P.message}) \u2014 not started, not retried.`)}),i.on("exit",(P,D)=>{const se=b.indexOf(i);se>=0&&b.splice(se,1),m.get(e)===i&&m.delete(e);const O=A.get(i);if(A.delete(i),q.delete(i))return;const ve=$.delete(e);if(s.stopped()||ve||(h?p("warning",`lens child exited: ${h}`,`cluster-lens-child:${h}`,{kind:"lens-child-exit",project:h,label:e,pid:i.pid,code:P,signal:D},void 0,y):O&&p("warning",`lens frontend exited: ${O.project}`,`cluster-lens-child:${O.project}`,{kind:"lens-child-exit",project:O.project,label:e,pid:i.pid,code:P,signal:D,generation:O.generation,port:O.port,ranMs:Date.now()-u},void 0,y),O&&fe(i,O)))return;const ae=Date.now()-u,R=B.get(e)??{attempts:0,portConflicts:0};(async()=>{const Le=w!==void 0&&!await g(w),ie=(0,lifecycle_1.isPortConflictExit)({code:P,signal:D,ranMs:ae,portHeld:Le},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ie&&R.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)R.portConflicts+=1,B.set(e,R),console.warn(`[gateway] lens child ${e} lost the :${w} bind race (EADDRINUSE, code=${P}); waiting for the port, then retrying (port-retry ${R.portConflicts}/${types_1.MAX_PORT_CONFLICT_RETRIES}; crash budget untouched).`);else if(ie){R.portConflicts=0,B.set(e,R),console.warn(`[gateway] lens child ${e}: :${w} persistently held after ${types_1.MAX_PORT_CONFLICT_RETRIES} bind races \u2014 another server owns it (an external/shared daemon?). Parking; re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s, will respawn if it frees.`),p("warning",`lens child parked: ${e} (:${w} held externally)`,`cluster-lens-child:${e}`,{kind:"lens-child-parked",label:e,port:w}),Q(e,w,()=>k(e,r,o,t,n,a,l,c));return}else{R.portConflicts=0,ae>=HEAL_HEALTHY_MS&&(R.attempts=0);const le=Math.min(HEAL_MAX_MS,HEAL_BASE_MS*2**R.attempts);R.attempts+=1,B.set(e,R),console.warn(`[gateway] lens child ${e} exited (code=${P} sig=${D}); auto-healing in ${Math.round(le/1e3)}s (attempt ${R.attempts}).`),await sleepUnref(le)}s.stopped()||$.has(e)||(w!==void 0&&!await waitForPortFree(w,types_1.PORT_FREE_WAIT_MS,{isPortFree:g,intervalMs:types_1.PORT_FREE_POLL_MS})&&console.warn(`[gateway] lens child ${e}: :${w} still held after ${Math.round(types_1.PORT_FREE_WAIT_MS/1e3)}s \u2014 spawning anyway.`),!(s.stopped()||$.has(e))&&k(e,r,o,t,n,a,l,c))})().catch(()=>{})}),i},"spawnManagedChild"),ee=d(e=>{const r=`${e.wsKey}:${e.project}`,o=C.get(r);if(o)return o;const t={key:r,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};C.set(r,t);for(const n of e.routes)n.lens&&(n.lens.workspaceKey??=e.wsKey,F.set(n.lens,t));return e.frontend.workspaceKey??=e.wsKey,F.set(e.frontend,t),t},"registerFrontend"),pe=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const r=path.resolve(e.projectRoot),o=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==r);return o.length>0?{LENSMCP_WATCH_DIRS:o.join(path.delimiter)}:{}},"lensWatchScopeEnv"),te=d((e,r,o)=>{const t=++e.generation,n=`${e.key}:g${t}:`,a=`${n}vite`,l=`${n}browser-capture`;$.delete(a),$.delete(l);const c=d((S,N,y,w,h,P)=>k(S,N,y,w,h,P,e.workspaceRoot),"spawnInRoot"),u=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:r,chrome:!0,headless:!0,viteEnv:{...e.frontend.env??{},...e.frontend.frozen?{LENSMCP_FREEZE:"1"}:{},LENSMCP_PROJECT:e.project,LENSMCP_FRONTEND_KEY:e.key,LENSMCP_FRONTEND_GENERATION:String(t),LENSMCP_FRONTEND_ROLE:o,...pe(e),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false"},labelPrefix:n,log:d(S=>console.log(S),"log")},c),i={ownerKey:e.key,generation:t,port:r,labelPrefix:n,vite:u?.vite??m.get(a),capture:m.get(l)};return e.generations.add(i),i},"spawnFrontendGeneration"),K=d((e,r)=>{if(!e)return;C.get(e.ownerKey)?.generations.delete(e);const o=[[`${e.labelPrefix}browser-capture`,e.capture],[`${e.labelPrefix}vite`,e.vite]];for(const[t,n]of o)$.add(t),n&&H(n);r&&T.delete(e.port)},"terminateGeneration"),ue=d(async e=>{const r=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!s.stopped()&&Date.now()<r;){const o=e.vite;if(!o||o.exitCode!==null||o.signalCode!==null)return!1;if(await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){const t=await L(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),U=d(async e=>{const r=e?.vite;return!!(e&&r&&r.exitCode===null&&r.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),z=d(e=>!e.disposed&&!s.stopped()&&C.get(e.key)===e,"frontendIsCurrent"),X=d((e,r)=>{if(!z(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const o=e.active;e.state=o?"replacing":"starting",e.reason=r,e.startedAt=Date.now();const t=(async()=>{let n,a;const l=Date.now();try{const c=new Set;for(const h of C.values())h!==e&&c.add(h.desiredPort);const u=o&&o.port===e.desiredPort?`g${o.generation}`:void 0,i=await x(e.desiredPort,T,{reserved:c,heldBy:u});if(a=i,!z(e))return T.delete(i),!1;n=te(e,i,"replacement"),e.candidate=n;const S=await U(o);console.log(`[gateway] lens handoff start: ${e.key} g${o?.generation??0}\u2192g${n.generation} :${o?.port??"down"}\u2192:${i} reason=${r}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:r,generation:n.generation,oldPort:o?.port,newPort:i,oldServing:S},void 0,e.eventFile===s.eventFile?void 0:e.eventFile);const N=await ue(n);if(!z(e))return K(n,!0),e.candidate=void 0,!1;if(!N){K(n,!0),e.candidate=void 0;const h=await U(o);return e.state=h?"ready":"failed",h&&(e.reason=void 0,e.startedAt=void 0),console.error(`[gateway] lens handoff failed: ${e.key} g${n.generation} did not become ready within ${types_1.LENS_REPLACEMENT_READY_MS}ms; ${e.state==="ready"?"old generation kept serving":"no active generation"}.`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:n.generation,port:i,durationMs:Date.now()-l,oldKept:h},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}for(const h of e.routes)h.target=`http://localhost:${n.port}`,h.lens&&(h.lens.port=n.port);e.frontend.port=n.port,e.active=n,e.candidate=void 0,e.state="ready",e.reason=void 0,e.startedAt=void 0;const y=n.vite?A.get(n.vite):void 0;y&&(y.role="active",y.listening=!0);const w=(0,hmr_reload_1.broadcastFullReload)(s,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${n.generation} :${n.port} in ${Date.now()-l}ms; draining g${o?.generation??0}${w>0?`; reloading ${w} tab(s)`:""}.`),p("info",`lens handoff complete: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-complete",wsKey:e.wsKey,project:e.project,reason:r,generation:n.generation,oldPort:o?.port,newPort:n.port,durationMs:Date.now()-l,reloadedTabs:w},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),o){const h=`${o.labelPrefix}browser-capture`;$.add(h),o.capture&&H(o.capture),setTimeout(()=>K(o,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(c){K(n,!0),!n&&a!==void 0&&T.delete(a),e.candidate=void 0;const u=await U(o);e.state=u?"ready":"failed",u&&(e.reason=void 0,e.startedAt=void 0);const i=c.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${i}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:r,generation:n?.generation??e.generation,port:n?.port??a,durationMs:Date.now()-l,oldKept:u,error:i},void 0,e.eventFile===s.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),ne=d((e,r)=>{e.active=te(e,r,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),fe=d((e,r)=>{const o=C.get(r.key);return!o||types_1.LENS_RESTART_MODE==="in-place"?!1:(o.candidate?.vite===e||o.active?.vite!==e||(o.state="starting",o.reason="child-exit",o.startedAt=Date.now(),console.warn(`[gateway] lens frontend exited: ${o.key} g${r.generation} :${r.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),X(o,"child-exit")),!0)},"handleManagedViteExit");s.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const r=F.get(e);return r?{state:r.state,generation:r.candidate?.generation??r.active?.generation??r.generation,reason:r.reason,startedAt:r.startedAt}:void 0},recover(e,r){const o=F.get(e);return o?X(o,r):Promise.resolve(!1)}}:void 0;let J=!1,oe=0;const Ee=d(()=>{J||Date.now()-oe<types_1.POD_STALE_SCAN_MS||(J=!0,oe=Date.now(),(async()=>{const e=new Map;for(const r of[...C.values()]){if(s.stopped())return;let o=e.get(r.workspaceRoot);if(!o){const i=(0,scope_1.readSourceSetOptions)(r.workspaceRoot);o={pass:(0,lifecycle_1.sourceScanPass)(r.workspaceRoot,i.exclude,{ignoreTests:i.ignoreTests}),startedAt:Date.now()},e.set(r.workspaceRoot,o)}const t=o.startedAt,n=await o.pass.snapshot(r.sourceDirs);if(C.get(r.key)!==r)continue;const a=r.sourceSnapshot,l=r.sourceScanStartedAt;if(r.sourceSnapshot=n,r.sourceScanStartedAt=t,!a)continue;const c=Date.now(),u=(0,lifecycle_1.diffSourceSets)(a,n,c,(0,lifecycle_1.sourceChangeWindowMs)(c,l,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));u&&(r.sourceChange=u,p("info",`source file set changed for ${r.key}`,`cluster-lens-app:${r.project}`,{kind:"source-set-changed",scoped:!0,wsKey:r.wsKey,project:r.project,added:u.added,removed:u.removed,truncated:u.truncated},void 0,r.eventFile===s.eventFile?void 0:r.eventFile))}})().catch(()=>{}).finally(()=>{J=!1}))},"scanFrontendSourceSets");let Z=!1;const ge=d(async(e,r)=>{if(!Z){Z=!0;try{for(const[o,t]of[...A]){if(s.stopped())return;if(t.port===void 0||o.exitCode!==null||o.signalCode!==null)continue;if(await probePortListening(t.port,types_1.LENS_PROBE_TIMEOUT_MS)){if(t.probeFails=0,!t.listening){t.listening=!0;const c=C.get(t.key);c?.active?.vite===o&&(c.state="ready",c.reason=void 0),p("info",`lens app up: ${t.project} (vite :${t.port} listening)`,`cluster-lens-app:${t.project}`,{kind:"service-up",project:t.project,port:t.port},void 0,t.bus)}continue}const n=t.listening?e.graceMs:Math.max(e.graceMs,e.neverListenedGraceMs);if(Date.now()-t.spawnedAt<n){t.probeFails=0;continue}if(!r)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},I.get(t.key)??0,Date.now(),e);if(!a)continue;const l=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(n/1e3)}s boot allowance`;I.set(t.key,Date.now()),t.probeFails=0,t.listening=!1,console.error(`[gateway] wedge recycle: lens app ${t.project} is ALIVE but NOT LISTENING on :${t.port} after ${Math.max(2,e.failureThreshold)} probes \u2014 ${l} \u2014 SIGKILL; a fresh vite is coming.`),p("warning",`wedge recycle: ${t.project} (${a})`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:a,port:t.port},void 0,t.bus),signalChild(o,"SIGKILL")}}finally{Z=!1}}},"probeLensChildren"),he=d(()=>{if(j||!types_1.POD_RECYCLE_ENABLED&&!types_1.LENS_PROBE_ENABLED)return;const e={graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};j=setInterval(()=>{if(s.stopped()||A.size===0||(re(),!types_1.POD_RECYCLE_ENABLED))return;Ee();const r=Date.now();for(const[o,t]of A){const n=C.get(t.key);if(n?.frontend.frozen)continue;const a=n?.sourceChange?.changedAt??0,l=I.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,l,a,r,e)&&((Y.get(t.key)??0)>=a||W.has(t.key)||(W.add(t.key),(async()=>{try{const c=n?.sourceChange,u=c&&!c.truncated?[...c.added,...c.removed].map(y=>path.resolve(t.workspaceRoot,y)):[],i=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await L(t.port),a,c?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,u):"unknown";if(i==="heard"){Y.set(t.key,a),console.log(`[gateway] stale source set: lens app ${t.project} watcher CONFIRMED the change \u2014 no recycle, HMR intact.`),p("info",`stale check: ${t.project} watcher confirmed \u2014 no recycle`,`cluster-lens-app:${t.project}`,{kind:"pod-stale-verified",project:t.project},void 0,t.bus);return}if(i==="unknown"&&types_1.LENS_WATCHER_UNVERIFIED==="keep"){console.log(`[gateway] stale source set: lens app ${t.project} unverifiable (no watcher testimony) \u2014 keeping it (LENSMCP_LENS_WATCHER_UNVERIFIED=keep).`);return}if(o.exitCode!==null||o.signalCode!==null)return;I.set(t.key,Date.now());const S=C.get(t.key);if(types_1.LENS_RESTART_MODE==="rolling"&&S){console.log(`[gateway] stale refresh: lens app ${t.key} (stale-source-set) \u2014 watcher ${i==="missed"?"missed the exact path change":"unverifiable"}; preparing a standby without dropping traffic.`),p("warning",`stale refresh: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-refresh",wsKey:S.wsKey,project:t.project,reason:"stale-source-set",watcher:i,added:c?.added,removed:c?.removed},void 0,t.bus),await X(S,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${i==="missed"?"SILENT across the change":"unverifiable"}; restarting it`),p("warning",`zombie recycle: ${t.project} (stale-source-set)`,`cluster-lens-app:${t.project}`,{kind:"pod-recycle",project:t.project,reason:"stale-source-set",watcher:i},void 0,t.bus);const N=(0,hmr_reload_1.broadcastFullReload)(s,t.key);N>0&&console.log(`[gateway] \u2192 told ${N} open tab(s) to reload (stale optimize-dep generation)`),signalChild(o,"SIGTERM"),setTimeout(()=>{o.exitCode===null&&o.signalCode===null&&(console.log(`[gateway] zombie recycle: lens app ${t.project} ignored SIGTERM for ${Math.round(types_1.LENS_RECYCLE_KILL_GRACE_MS/1e3)}s \u2014 escalating to SIGKILL.`),signalChild(o,"SIGKILL"))},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{W.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),j.unref?.()},"startLensSweeper"),we=d(async()=>{const e=[...new Map(s.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],r=f.dashboard!==!1,o=f.mcp!==!1&&e.length>0;if(r){const t=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!t)console.warn("[gateway] lens dashboard bundle not found \u2014 skipping. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/dashboard.js`), or `yarn nx build @lensmcp/lensmcp-mcp` in-repo.");else{const n=(0,scope_1.readLensScope)(s.root),a=await x(f.dashboardPort??n.dashboardPort,V),l=f.lensHost??"lensmcp.local",c=n.basePath;s.registry.appendRoutes(s.wsKey,[{host:l,prefix:c,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),s.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:n.key,root:s.root,base:c,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString()}),console.log(`[gateway] starting lens dashboard \u2192 http://localhost:${a} (routed at https://${l}${c}/)`),p("info",`lens dashboard up: ${l}${c} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:l,port:a,base:c}),k("lens-dashboard",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",n.key,"dashboard")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:c,LENSMCP_GATEWAY_PORTS:M},!1,void 0,void 0,a)}}if(o){const t=(0,lens_frontend_1.findMcpBundle)(s.root);if(!t)console.warn("[gateway] MCP server bundle not found \u2014 skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");else{const n=(0,scope_1.readLensScope)(s.root),a=Number(process.env.LENSMCP_PORT??n.mcpHttpPort),l=d(()=>{console.log(`[gateway] starting lens MCP server on 127.0.0.1:${a} (${t})`),k("lens-mcp",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",n.key,"mcp")),[t],{LENSMCP_EVENT_FILE:s.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:n.key,LENSMCP_WORKSPACE_ROOT:s.root},!1,void 0,void 0,a)},"spawnMcp");await g(a)?l():(console.warn(`[gateway] lens MCP port :${a} is already served \u2014 an external lensmcp server (a shared daemon?) owns this workspace's MCP; not spawning a second. Re-probing every ${Math.round(types_1.LENS_PARKED_PORT_REPROBE_MS/1e3)}s; will take over if it frees.`),p("info",`lens MCP served externally on :${a} \u2014 gateway parked its own`,"cluster-lens-child:lens-mcp",{kind:"lens-child-up",project:"lens-mcp",label:"lens-mcp",port:a,external:!0}),Q("lens-mcp",a,l))}}for(const t of e){if(t.started)continue;t.started=!0;const n=t.port,a=(s.registry.get(s.wsKey)?.routes??s.routes).filter(u=>u.lens?.project===t.project),l=await x(t.port,T);for(const u of a)u.lens&&(u.lens.port=l),u.target=`http://localhost:${l}`;t.port=l;const c=ee({wsKey:s.wsKey,project:t.project,workspaceRoot:s.root,projectRoot:t.projectRoot,eventFile:s.eventFile,routes:a,frontend:t,desiredPort:n});try{ne(c,l),p("info",`lens app started: ${t.project} (vite :${t.port})`,`cluster-lens-app:${t.project}`,{kind:"lens-app-up",project:t.project,port:t.port})}catch(u){T.delete(l),c.state="failed",console.error(`[gateway] lens app ${t.project}: ${u.message}`)}}he()},"bootSingletonsAndApps"),_e=d(async e=>{if(f.dashboard===!1)return;const r=(0,lens_frontend_1.findDashboardBundle)(s.root);if(!r)return;const o=(0,scope_1.readLensScope)(e.root),t=f.lensHost??"lensmcp.local",n=o.basePath,a=await x(o.dashboardPort,V),l=`lens-dashboard:${e.wsKey}`,c=path.join(e.root,".lensmcp","events.jsonl");return $.delete(l),s.registry.appendRoutes(e.wsKey,[{host:t,prefix:n,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),(0,workspace_registry_1.registerWorkspace)({key:e.wsKey,root:e.root,base:n,pid:process.pid,procStamp:(0,workspace_registry_1.processStamp)(process.pid),updatedAt:new Date().toISOString(),...e.instance?{instance:{name:e.instance.name,baseKey:e.instance.baseKey,apex:e.instance.apex}}:{}}),console.log(`[gateway] hosting dashboard for workspace '${e.wsKey}' \u2192 http://localhost:${a} (routed at https://${t}${n}/)`),p("info",`lens dashboard up: ${t}${n} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:t,port:a,base:n}),k(l,(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),[r],{LENSMCP_EVENT_FILE:c,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:n,LENSMCP_WS_KEY:e.wsKey,...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},LENSMCP_GATEWAY_PORTS:M},!1,void 0,e.root,a),()=>{$.add(l),V.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const u=m.get(l);u&&H(u),m.delete(l)}},"hostWorkspaceDashboard"),Se=d(async e=>{const r=new Map;for(const n of e.routes){if(!n.lens)continue;const a=r.get(n.lens.project)??[];a.push(n),r.set(n.lens.project,a)}if(r.size===0)return()=>{};const o=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[n,a]of r){const l=a[0].lens;if(l.started)continue;const c=await x(l.port,T),u=l.port;for(const S of a)S.lens.started=!0,S.lens.port=c,S.target=`http://localhost:${c}`;const i=ee({wsKey:e.wsKey,project:n,workspaceRoot:e.root,projectRoot:l.projectRoot,eventFile:o,routes:a,frontend:l,desiredPort:u});t.push(i),console.log(`[gateway] hosting lens app '${n}' of '${e.wsKey}' \u2192 vite :${c}`);try{ne(i,c),p("info",`lens app started: ${e.wsKey}/${n} (vite :${c})`,`cluster-lens-app:${e.wsKey}:${n}`,{kind:"lens-app-up",project:n,port:c},void 0,o)}catch(S){T.delete(c),i.state="failed",console.error(`[gateway] lens app ${e.wsKey}/${n}: ${S.message}`)}}return()=>{for(const n of t){n.disposed=!0;for(const a of[...n.generations])K(a,!0);for(const a of n.routes)a.lens&&F.delete(a.lens);F.delete(n.frontend),I.delete(n.key),Y.delete(n.key),W.delete(n.key),C.delete(n.key)}}},"hostWorkspaceApps"),ye=d(async(e=2500)=>{j&&(clearInterval(j),j=void 0);const r=[...b];for(const n of r)signalChild(n,"SIGTERM");const o=d(n=>n.exitCode===null&&n.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&r.some(o);)await new Promise(n=>setTimeout(n,100));for(const n of r)o(n)&&signalChild(n,"SIGKILL")},"reapAll"),re=d(()=>ge({graceMs:types_1.LENS_PROBE_GRACE_MS,failureThreshold:types_1.LENS_PROBE_FAILS,cooldownMs:types_1.LENS_PROBE_COOLDOWN_MS,neverListenedGraceMs:types_1.LENS_PROBE_BOOT_GRACE_MS},types_1.LENS_PROBE_ENABLED),"probeLensFrontends");return{spawnManagedChild:k,bootSingletonsAndApps:we,hostWorkspaceDashboard:_e,hostWorkspaceApps:Se,probeLensFrontends:re,reapAll:ye}}v(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
@@ -300,6 +300,8 @@ export interface ServiceLayerDeps {
300
300
  rollPollMs?: number;
301
301
  /** Rolls allowed in flight at once across every service. Default {@link POD_MAX_CONCURRENT_ROLLS}; `0` = no cap. */
302
302
  maxConcurrentRolls?: number;
303
+ /** Cold starts allowed at once across every service. Default {@link MAX_COLD_STARTS}; `0` = no cap. */
304
+ maxConcurrentColdStarts?: number;
303
305
  /** Skip the nx foreign-runner reap before a spawn (tests). */
304
306
  reapForeign?: boolean;
305
307
  /** DNS for an `adminUrl` scale POST. Default the OS resolver; tests point a non-loopback name at a local server. */
@@ -1,3 +1,3 @@
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");
1
+ "use strict";var Oe=Object.defineProperty;var w=(t,c)=>Oe(t,"name",{value:c,configurable:!0});var De=Object.defineProperty,s=w((t,c)=>De(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isLoopbackHostname=isLoopbackHostname,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isRunnerOf=isRunnerOf,exports.isTestSourcePath=isTestSourcePath,exports.sourceScanPass=sourceScanPass,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.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,c){const a={},i=t.serviceKeys[(0,service_keys_1.serviceKeyId)(c.wsKey,c.project)];if(i&&(a.LENSMCP_SERVICE_KEY=i),t.https){const h=require("../../../basic-ssl").caCertPath();fs.existsSync(h)&&(a.NODE_EXTRA_CA_CERTS=h)}return a}w(eastWestEnv,"eastWestEnv"),s(eastWestEnv,"eastWestEnv");const sleep=s(t=>new Promise(c=>setTimeout(c,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let c=2166136261;for(let a=0;a<t.length;a+=1)c^=t.charCodeAt(a),c=Math.imul(c,16777619);return c>>>0}w(fnv1a,"fnv1a"),s(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:c}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(c.split("\0").filter(Boolean))}catch{return new Set}}w(gitIgnoredPaths,"gitIgnoredPaths"),s(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,c=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const a=path.join(t.pool.dir,"parent.sock");return c(a)?{socketPath:a}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}w(scaleTarget,"scaleTarget"),s(scaleTarget,"scaleTarget");function isLoopbackHostname(t){const c=t.toLowerCase().replace(/^\[(.*)\]$/,"$1").replace(/\.$/,"");return c==="localhost"||c.endsWith(".localhost")?!0:net.isIPv4(c)?c.split(".")[0]==="127":net.isIPv6(c)&&new URL(`http://[${c}]/`).hostname==="[::1]"}w(isLoopbackHostname,"isLoopbackHostname"),s(isLoopbackHostname,"isLoopbackHostname");function postScale(t,c,a){let i;if("socketPath"in t)i=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,S={method:"POST"};h&&isLoopbackHostname(new URL(t.url).hostname)&&(S.rejectUnauthorized=!1),a&&(S.lookup=a),i=A.request(`${t.url}/webpack/scale`,S,v=>v.resume())}return i.on("error",()=>{}),i.end(JSON.stringify({pods:c})),i}w(postScale,"postScale"),s(postScale,"postScale");function isOwnRunnerCommand(t,c){return(0,serve_launch_1.isServeRunnerCommand)(t)?(0,serve_launch_1.isServeRunnerCommandOf)(t,c):t.includes("/node_modules/nx/")?t.includes(`${c.replace(/\/+$/,"")}/node_modules/nx/`):!0}w(isOwnRunnerCommand,"isOwnRunnerCommand"),s(isOwnRunnerCommand,"isOwnRunnerCommand");function isRunnerOf(t,c,a){const i=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return!new RegExp(`(^|\\s)run ${i}(:|\\s|$)`).test(t)||!(0,serve_launch_1.isServeRunnerCommand)(t)&&!/\bnx(\.js)?\b/.test(t)?!1:isOwnRunnerCommand(t,a)}w(isRunnerOf,"isRunnerOf"),s(isRunnerOf,"isRunnerOf");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const c=t.split("/"),a=c[c.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let i=0;i<c.length-1;i+=1)if(TEST_DIR_SEGMENTS.has(c[i]))return!0;return!1}w(isTestSourcePath,"isTestSourcePath"),s(isTestSourcePath,"isTestSourcePath");function sourceScanPass(t,c,a){const i=a?.ignoreTests??!0;let h;const A=new Map,S=`tree:${path.resolve(t)}`,v=s(g=>path.relative(t,g).split(path.sep).join("/"),"rel"),k=s(async(g,_)=>{const j=await(h??=gitIgnoredPaths(t)),P=[],F=new Set;let p=!1;const I=s(async(G,re)=>{if(P.length>=SCAN_FILE_CAP){p=!0;return}let se;try{se=await fs.promises.readdir(G,{withFileTypes:!0})}catch{return}re&&F.add(v(G));for(const U of se){if(P.length>=SCAN_FILE_CAP){p=!0;return}const L=U.name,b=path.join(G,L);if(U.isDirectory()){if(!re||L.startsWith(".")||SRC_EXCLUDE.has(L)||c?.has(L)||L.endsWith("-devserver")||j.has(`${v(b)}/`))continue;await I(b,!0)}else if(U.isFile()){const ne=L.lastIndexOf(".");if(ne<=0||!SRC_EXT.has(L.slice(ne)))continue;const Q=v(b);if(j.has(Q)||i&&isTestSourcePath(Q))continue;P.push(Q)}}},"walk");return await I(g,_),{paths:P,entered:F,truncated:p}},"walkDir"),O=s((g,_)=>{let j=A.get(g);return j||(j=_(),A.set(g,j)),j},"memo"),z=s(async()=>{const g=A.get(S);if(!g)return;const _=await g;return _.truncated?void 0:_},"wideWalk"),V=new Set,oe=s(()=>O(`files:${path.resolve(t)}`,async()=>{const g=await z();return g?{paths:g.paths.filter(_=>!_.includes("/")),entered:V,truncated:!1}:k(t,!1)}),"rootFiles"),J=s(g=>O(`tree:${path.resolve(g)}`,async()=>{const _=await z(),j=v(g);if(_?.entered.has(j)){const P=`${j}/`;return{paths:j===""?_.paths:_.paths.filter(F=>F.startsWith(P)),entered:V,truncated:!1}}return k(g,!0)}),"subtree"),D=s(g=>{const _=new Set;let j=0,P=g.some(p=>p.truncated);const F=s(p=>_.has(p)?!0:_.size>=SCAN_FILE_CAP?(P=!0,!1):(_.add(p),j^=fnv1a(p),!0),"add");for(const p of g)if(!p.paths.every(F))break;return{signature:`${_.size}:${(j>>>0).toString(36)}`,paths:_,truncated:P}},"compose");return{async snapshot(g){if(!g||g.length===0)return D([await O(S,()=>k(t,!0))]);const _=[await oe()];for(const j of g)_.push(await J(j));return D(_)}}}w(sourceScanPass,"sourceScanPass"),s(sourceScanPass,"sourceScanPass");async function sourceSetSnapshot(t,c,a,i){return sourceScanPass(t,a,i).snapshot(c)}w(sourceSetSnapshot,"sourceSetSnapshot"),s(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,c,a,i){return(await sourceSetSnapshot(t,c,a,i)).signature}w(sourceSetSignature,"sourceSetSignature"),s(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,c,a,i){if(t.paths.size===c.paths.size){let v=!0;for(const k of t.paths)if(!c.paths.has(k)){v=!1;break}if(v)return null}const h=[],A=[];let S=t.truncated||c.truncated;for(const v of c.paths)t.paths.has(v)||(h.length+A.length<SOURCE_DIFF_PATH_CAP?h.push(v):S=!0);for(const v of t.paths)c.paths.has(v)||(h.length+A.length<SOURCE_DIFF_PATH_CAP?A.push(v):S=!0);return{changedAt:a,added:h,removed:A,truncated:S,...i!==void 0?{windowMs:i}:{}}}w(diffSourceSets,"diffSourceSets"),s(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,c,a,i){const h=c&&c>0?t-c:a;return Math.max(h,a)+i}w(sourceChangeWindowMs,"sourceChangeWindowMs"),s(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,c,a,i,h){const A=s(k=>{const O=path.relative(t,k).split(path.sep).filter(Boolean);return O.length===0||O[0].startsWith("..")?"":O[0]},"bucketOf")(c);if(!A)return[];const S=new Set(a.filter(Boolean)),v=new Set([A]);for(const k of i)k.startsWith(".")||SRC_EXCLUDE.has(k)||h?.has(k)||k.endsWith("-devserver")||S.has(k)||v.add(k);return[...v].sort().map(k=>path.join(t,k))}w(sourceScopeDirs,"sourceScopeDirs"),s(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,c,a,i){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||c-t.lastUsed<i.settleMs||t.lastStaleRecycleAt!=null&&c-t.lastStaleRecycleAt<i.cooldownMs?null:a>0&&a-t.spawnAt>i.graceMs?"stale-source-set":i.maxAgeMs>0&&c-t.spawnAt>i.maxAgeMs?"max-age":null}w(podRecycleReason,"podRecycleReason"),s(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,c,a,i,h){return!(i-c<h.cooldownMs||a<=0||a-t<=h.graceMs||i-a<h.settleMs)}w(lensFrontendStale,"lensFrontendStale"),s(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,c,a,i=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&i.length>0){const h=c-a,A=new Set(t.recentEvents.filter(S=>S.at>=h).map(S=>path.resolve(S.path)));return i.every(S=>A.has(path.resolve(S)))?"heard":"missed"}return t.lastFsSetEventAt>=c-a?"heard":"missed"}w(lensWatcherVerdict,"lensWatcherVerdict"),s(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,c,a){if(!t)return"unknown";if(t.degraded)return"degraded";const i=c-a;return t.lastWatchRunAt>=i||t.lastBuildAt>=i?"heard":"silent"}w(podWatcherVerdict,"podWatcherVerdict"),s(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,c,a,i){const h=t.hasListened?i.graceMs:Math.max(i.graceMs,i.neverListenedGraceMs);return a-t.spawnedAt<h||a-c<i.cooldownMs||t.probeFails<Math.max(2,i.failureThreshold)?null:"wedged-no-listener"}w(lensChildWedged,"lensChildWedged"),s(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,c){return t.signal||t.code===null||t.code===0||t.ranMs>c.fastFailMs?!1:t.portHeld}w(isPortConflictExit,"isPortConflictExit"),s(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,c,a={}){const i=a.spawnProcess??node_child_process_1.spawn,h=a.probeAlive??pod_probe_1.probePodAlive,A=a.killTree??((e,n="SIGTERM")=>{try{process.kill(-e.pid,n)}catch{try{e.kill(n)}catch{}}}),S=a.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),v=s((e,n="SIGTERM")=>{S.detach(e),A(e,n)},"killTree"),k=a.rollReadyMs??types_1.POD_ROLL_READY_MS,O=a.rollPollMs??750,z=a.maxConcurrentRolls??types_1.POD_MAX_CONCURRENT_ROLLS,V=z>0?z:Number.POSITIVE_INFINITY,oe=a.maxConcurrentColdStarts??types_1.MAX_COLD_STARTS,J=oe>0?oe:Number.POSITIVE_INFINITY;let D=0;const g=[],_=s(()=>D<J?(D+=1,Promise.resolve()):new Promise(e=>{g.push(()=>{D+=1,e()})}),"acquireColdSlot"),j=s(()=>{D-=1,g.shift()?.()},"releaseColdSlot"),{emit:P}=c,F=s(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),p=s((e,n,r,o,d)=>P(n,r,o,{wsKey:e.wsKey,...d},void 0,F(e)),"emitSvc"),I=s(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),G=s((e,n)=>{let r;try{r=(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 d=0;for(const l of r.split(`
2
+ `)){const u=l.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!u)continue;const f=Number(u[1]),C=Number(u[2]);if(isRunnerOf(u[3],n,e.root)&&!(f===process.pid||f===o)&&!(o!==void 0&&C===o)){try{process.kill(-C,"SIGKILL")}catch{try{process.kill(f,"SIGKILL")}catch{}}d+=1}}return d>0&&(console.log(`[gateway] ${n}: reaped ${d} stale runner tree(s) \u2014 a foreign generation of this service (holding nx's task lock, or its pool)`),p(e,"warning",`stale-runner reap: ${e.project} (${d} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:d})),d},"reapForeignRunners"),re=s((e,n)=>({...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),...e.instanceEnv?{...e.instanceEnv,LENSMCP_SOCKET_ONLY:"1"}:{},...e.frozen?{LENSMCP_FREEZE:"1",LENSMCP_TYPECHECK:"off"}:{},NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(n),...eastWestEnv(t,e)}),"serviceEnv"),se=s((e,n,r,o)=>{const d=re(e,n),l=e.wsKey||path.basename(e.root),u=(0,serve_launch_1.planServeLaunch)({stores:t.graphStores,root:e.root,project:e.project,target:e.decl.serveTarget??"serve-hmr",childCount:o,baseEnv:d});if(u.kind==="runner"){const E=S.offeredTo(e),m=i((0,process_name_1.namedExecPath)((0,process_name_1.processName)(l,e.project,serve_launch_1.SERVE_RUNNER_ROLE)),[...u.script.execArgv,u.script.path,...(0,serve_launch_1.serveRunnerArgs)(r,o,e.root)],{cwd:e.root,detached:!0,env:E?{...u.env,[protocol_1.SHARED_BUILD_ENV]:"1"}:u.env,stdio:["ignore","pipe","pipe","ipc"]});return E&&S.attach(e,n,m),m.send?.(u.message,$=>{$&&console.error(`[gateway] ${r}: could not hand the task to its runner (${$.message})`)}),{proc:m,launcher:"runner"}}console.log(`[gateway] ${r}: starting through nx run \u2014 ${u.reason}`);const f=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),C=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(l,e.project,"nx")),[N,R]=fs.existsSync(f)?[C,[f,"run",r,`--childCount=${o}`]]:["yarn",["nx","run",r,`--childCount=${o}`]];return{proc:i(N,R,{cwd:e.root,detached:!0,env:d,stdio:["ignore","pipe","pipe"]}),launcher:"nx"}},"startServiceTree"),U=new WeakMap,L=s((e,n,r)=>{const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,d=Math.max(1,e.decl.minPods??1),{proc:l,launcher:u}=se(e,n,o,d);U.set(l,u),r==="active"&&(e.launcher=u),r==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const f=s(R=>{const E=R.toString();for(const m of E.split(`
3
+ `)){const $=(0,pod_output_1.classifyPodLine)(m),{clean:x,broken:X,healthy:y}=$,W=x;if(X){e.lastErrors.push(x),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const M=Date.now();(x!==e.lastErrorEmitSig||M-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=x,e.lastErrorEmitAt=M,p(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:x,fault:$.fault}))}y&&(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;p(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:x,closed:T.closed,open:T.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(W)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${o}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),p(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||(q(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),G(e,o),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&b(e)},1500).unref?.())},250).unref?.())}},"sniff"),C=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,N=s(R=>E=>{if(t.stopped())return;const m=(0,inspector_secret_1.redactInspectorUrls)(E.toString());f(m);const $=`[${e.project}] ${m}`;R.write($),C?.($)},"relay");return l.stdout?.on("data",N(process.stdout)),l.stderr?.on("data",N(process.stderr)),l.on("exit",(R,E)=>{if(e.proc!==l||(e.proc=void 0,e.state="down",t.stopped()))return;const m=(0,exit_reason_1.describeExit)(R,E);if(console.log(`[gateway] ${o} exited (${R??"no code"}${E?`, ${E}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${m?` \u2014 ${m}`:""}`),p(e,R===0?"info":"warning",`service down: ${e.project} (exit ${R})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:R,...E?{signal:E}:{}}),R===0||e.buildFailed)return;const $=Date.now();if(e.restarts=(e.restarts??[]).filter(X=>$-X<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){p(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 x=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),p(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),b(e))},x)}),l},"launchServiceProcess"),b=s(e=>{if(t.stopped())return;const n=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,r=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&G(e,n),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${n} (pods=${r})\u2026`),p(e,"info",`service starting: ${e.project} (${r} pod${r>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:r}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=L(e,e.generation,"active")},"spawnService"),ne=s(async e=>{const n=e.pool.socks[0];if(!n)return;const r=await(0,pod_probe_1.probePodAlive)(n,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);r&&(e.activeWork=r.activeWork,r.watcher&&(e.watcher=r.watcher))},"refreshActiveWork"),Q=s(e=>{try{const n=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(n)&&n>1?n:void 0}catch{return}},"readOwnerSidecar"),ve=s(async e=>{const n=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!n)return[];const r=(0,node_crypto_1.createHash)("sha256").update(n).digest("hex").slice(0,8),o=[];for(const d of e.pool.socks){const l=await(0,pod_probe_1.probePodAlive)(d,void 0,n);l&&(!l.keyFingerprint||l.keyFingerprint!==r)&&o.push({sock:d,pid:l.pid})}return o},"staleAdoptedSocks"),_e=s((e,n)=>{const r=n.map(o=>o.sock);for(const{sock:o,pid:d}of n){const l=chooseStalePodKillTarget(d,Q(o),process.pid);if(l!==void 0)try{process.kill(l,"SIGTERM")}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(o=>!r.includes(o)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),ae=s((e,n)=>{if(e.state==="up")return!1;e.state="up",e.wedgeRecycles=0;const r=e.pool.socks.length;return p(e,"info",`service up: ${e.project} (${r} pods, ${n})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:r,adopted:n==="adopted"}),!0},"markUp"),ye=s(async e=>{if(I(e.pool)>0){const o=await ve(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.`),p(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&&q(e,"stale adoption (foreign east-west key)"),_e(e,o)),e.pool.socks.length>0)return ae(e,"adopted"),!0}if(t.stopped())return!1;let n=!1;const r=s(()=>e.state!=="starting"&&!e.proc,"notStarted");if(r())if(e.coldWait)await e.coldWait;else{D>=J&&(console.log(`[gateway] cold start queued: ${e.project}${e.wsKey&&e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${D} starting, ${g.length} ahead (LENSMCP_MAX_COLD_STARTS=${J}).`),p(e,"info",`cold start queued: ${e.project}`,`cluster-service:${e.project}`,{kind:"cold-start-queued",project:e.project,inFlight:D,ahead:g.length}));const o=_();if(e.coldWait=o,await o,e.coldWait=void 0,n=!0,t.stopped())return j(),!1;r()&&b(e)}try{return await Ee(e)}finally{n&&j()}},"ensureUp"),Ee=s(async e=>{const n=Date.now()+t.startupTimeoutMs,r=Date.now()+12e3;for(;!t.stopped()&&Date.now()<n;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>r)return!1;if(I(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`:""})`),p(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},"waitForPods"),me=s(e=>{const n=e.decl.maxPods??e.decl.minPods??1,r=e.pool.socks.length,o=scaleTarget(e);!o||r===0||r>=n||e.inflight<=r||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(o,r+1,a.scaleLookup),console.log(`[gateway] autoscale ${e.project}: ${r} \u2192 ${r+1} pods (inflight ${e.inflight})`),p(e,"info",`autoscale: ${e.project} ${r} \u2192 ${r+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:r,to:r+1,inflight:e.inflight}))},"scaleUp"),q=s((e,n,r)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${n} \u2014 scaling to zero.`),r?.announce!==!1&&p(e,"info",`idle kill: ${e.project} (${n})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:n});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(()=>{ie(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ie=s(e=>{try{for(const n of fs.readdirSync(e.pool.dir)){if(!n.endsWith(".sock"))continue;const r=path.join(e.pool.dir,n);try{const o=Number(fs.readFileSync(`${r}.owner`,"utf8").trim());if(Number.isFinite(o)&&o>0)try{process.kill(o,0);continue}catch{}}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let B=0;const le=[],$e=s(()=>B<V?(B+=1,Promise.resolve()):new Promise(e=>{le.push(()=>{B+=1,e()})}),"acquireRollSlot"),ke=s(()=>{B-=1,le.shift()?.()},"releaseRollSlot"),je=s(async(e,n,r)=>{I(e.pool);for(const o of e.pool.socks){const d=await h(o,void 0,r);if(d&&n(d.podGeneration))return!0}return!1},"anyPodAnswers"),ge=s((e,n)=>{if(e.rolling)return e.rolling;const r=e.proc;if(!r||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=${n}).`),Promise.resolve(!1);B>=V&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${n} \u2014 ${B} roll(s) in flight, ${le.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${V}).`);const o=(async()=>{await $e();try{return await Re(e,r,n)}finally{ke()}})().finally(()=>{e.rolling=void 0});return e.rolling=o,o},"rollPod"),Re=s(async(e,n,r)=>{const o=e.proc;if(o!==n||t.stopped())return!1;const d=e.generation??0,l=d+1,u=Date.now();e.lastStaleRecycleAt=u,console.log(`[gateway] pod handoff start: ${e.project} g${d}\u2192g${l} reason=${r} \u2014 standby spawning, old generation keeps serving.`),p(e,"info",`pod handoff starting: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:r,generation:l,fromGeneration:d});const f=L(e,l,"standby"),C=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],N=u+k;let R;for(;!t.stopped()&&Date.now()<N&&!(f.exitCode!==null||f.signalCode!==null);){I(e.pool);for(const E of e.pool.socks)if((await h(E,void 0,C))?.podGeneration===l){R=E;break}if(R)break;await sleep(O)}if(!R||t.stopped()){const E=t.stopped()?"gateway stopping":f.exitCode!==null||f.signalCode!==null?`standby exited (${f.exitCode??f.signalCode})`:`standby not ready within ${Math.round(k/1e3)}s`;try{f.stdout?.removeAllListeners("data"),f.stderr?.removeAllListeners("data")}catch{}v(f,"SIGTERM");const m=!t.stopped()&&e.proc===o&&o.exitCode===null&&o.signalCode===null&&await je(e,x=>x!==l,C),$=m?`old generation g${d} kept serving`:`and the old generation g${d} 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${l}: ${E} \u2014 ${$}.`),p(e,m?"warning":"error",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:r,generation:l,error:E,durationMs:Date.now()-u,oldServing:m}),setTimeout(()=>ie(e),1200).unref?.(),!1}if(e.proc!==o)return v(f,"SIGTERM"),!1;e.proc=f,e.launcher=U.get(f),e.generation=l,e.spawnAt=u,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 v(o,"SIGTERM"),setTimeout(()=>{ie(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${l} answered on ${path.basename(R)} in ${Date.now()-u}ms; retiring g${d}.`),p(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:r,generation:l,fromGeneration:d,durationMs:Date.now()-u}),!0},"runRoll"),fe=s(e=>{const n=Date.now();e.restarts=(e.restarts??[]).filter(o=>n-o<types_1.SERVICE_RESTART_WINDOW_MS);const r=(e.wedgeRecycles??0)+1;if(e.restarts.length>=types_1.SERVICE_MAX_RESTARTS||r>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`;p(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:r-1}),q(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.wedgeRecycles=r,e.restarts.push(n),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; attempt ${r}/${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)`),p(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:r,inWindow:e.restarts.length}),q(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&b(e)},1500).unref?.()},"recycleWedged"),Ae={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 de;const Z=new Map,we=new Map;let pe=!1,ue=0,ce=0;const Ce=s(()=>{const e=t.projectRoots??{},n=new Set(t.services.map(o=>o.project));for(const o of t.routes)o.lens&&n.add(o.lens.project);const r=new Set;for(const o of n){const d=e[o];if(!d)continue;const l=path.relative(t.root,d).split(path.sep).filter(Boolean)[0];l&&!l.startsWith("..")&&r.add(l)}return[...r]},"managedBuckets"),Te=s((e,n,r,o)=>{const d=t.projectSourceRoots?.[e];if(d?.length)return d;const l=t.projectRoots?.[e];if(!l)return;const u=sourceScopeDirs(t.root,l,r,n,o);return u.length>0?u:void 0},"scopeFor"),Me=s(()=>{if(pe||Date.now()-ue<types_1.POD_STALE_SCAN_MS)return;pe=!0,ue=Date.now();const e=ue;(async()=>{const n=(0,scope_1.readSourceSetOptions)(t.root),r=n.exclude,o={ignoreTests:n.ignoreTests},d=sourceScanPass(t.root,r,o),l=await d.snapshot(),u=Date.now(),f=sourceChangeWindowMs(u,ce,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),C=de?diffSourceSets(de,l,u,f):null;if(C&&(t.sourceSetChangedAt=u,t.sourceSetChange=C,P("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})),de=l,!t.projectRoots){ce=e;return}let N;try{N=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(y=>y.isDirectory()).map(y=>y.name)}catch{return}const R=Ce(),E=new Set(t.services.map(y=>y.project));for(const y of t.routes)y.lens&&E.add(y.lens.project);const m=new Map;for(const y of E){const W=Te(y,N,R,r);if(!W)continue;const T=W.join("|"),M=m.get(T)??{dirs:W,projects:[]};M.projects.push(y),m.set(T,M)}const $=Date.now(),x=sourceChangeWindowMs($,ce,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);ce=e;const X=s((y,W,T)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const M of y)t.sourceSetChangedFor.set(M,W),t.sourceSetChangesFor.set(M,T)},"stampScoped");for(const[y,{dirs:W,projects:T}]of m){const M=await d.snapshot(W),Se=Z.get(y);Z.set(y,M);const he=new Map;for(const H of T){const K=we.get(H);if(K===void 0||K===y)continue;const Y=he.get(K)??[];Y.push(H),he.set(K,Y)}for(const H of T)we.set(H,y);for(const[H,K]of he){const Y=Z.get(H),te=Y?diffSourceSets(Y,M,$,x):C;te&&(X(K,Y?$:u,te),P("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:te.added,removed:te.removed,truncated:te.truncated}))}if(!Se)continue;const ee=diffSourceSets(Se,M,$,x);ee&&(X(T,$,ee),P("info",`source file set changed in ${T.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:T,added:ee.added,removed:ee.removed,truncated:ee.truncated}))}for(const y of[...Z.keys()])m.has(y)||Z.delete(y)})().catch(()=>{}).finally(()=>{pe=!1})},"maybeScanSourceSet"),Pe=s((e,n)=>{const r=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,o=podWatcherVerdict(e.watcher,n,r),d=s(l=>(e.staleKeptAt!==n&&(e.staleKeptAt=n,console.log(`[gateway] stale source set: pod ${e.project} ${l} \u2014 no recycle.`)),!1),"keep");return o==="heard"?(e.staleVerifiedAt=n,p(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:o}),d(`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"?d("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"?d("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),xe=s((e,n)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){ge(e,n);return}console.log(`[gateway] zombie recycle: ${e.project} (${n}) \u2014 stale fs view; fresh pod incoming`),p(e,"warning",`zombie recycle: ${e.project} (${n})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:n});const r=!!e.decl.eager;q(e,`${n} \u2014 recycling stale pod`,{announce:!1}),r&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&b(e)},1500).unref?.()},"recyclePod"),Le=s(()=>{const e=setInterval(()=>{be().catch(n=>console.error("[gateway] sweep error:",n))},t.sweepMs);return e.unref(),e},"startSweeper"),be=s(async()=>{{if(t.stopped())return;for(const e of t.graphStores?.all()??[])e.refreshIfStale();types_1.POD_RECYCLE_ENABLED&&Me();for(const e of t.registry?t.registry.allServices():t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const n=path.resolve(e.root)===path.resolve(t.root),r=e.decl.idleKillSec;if(r&&e.inflight===0&&Date.now()-e.lastUsed>r*1e3){q(e,`idle ${r}s`),e.wedgedSince=void 0;continue}const o=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,d=I(e.pool)>0;if(d||o){if(e.wedgedSince=void 0,d&&ae(e,"observed"),d&&!o&&types_1.POD_RECYCLE_ENABLED&&n){const l=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await ne(e);const u=(e.staleVerifiedAt??0)>=l&&l>0,f=podRecycleReason(e,Date.now(),u?0:l,Ae);if(f==="stale-source-set"&&!Pe(e,l))continue;f&&xe(e,f)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,fe(e))}}},"sweepOnce");return{scanNow:I,spawnService:b,ensureUp:ye,scaleUp:me,killSpawned:q,markUp:ae,startSweeper:Le,rollPod:ge,recycleWedged:fe,stopBuildHost:s(e=>e===void 0?S.stop():S.release(e),"stopBuildHost")}}w(createServiceLayer,"createServiceLayer"),s(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,c,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(c!==void 0&&c!==t)&&t!==a)return t}w(chooseStalePodKillTarget,"chooseStalePodKillTarget"),s(chooseStalePodKillTarget,"chooseStalePodKillTarget");
@@ -35,6 +35,23 @@ export declare function probePodAlive(socketPath: string, timeoutMs?: number,
35
35
  /** The target service's east-west key. The pod refuses the route without it, so that a
36
36
  * client coming through the gateway cannot read its diagnostics. */
37
37
  serviceKey?: string): Promise<PodAliveness | undefined>;
38
+ /**
39
+ * Why a liveness probe found no evidence — the distinction a single `undefined` hid (issues/closed/foodguard-54):
40
+ *
41
+ * - `silent` — the socket took the connection and nothing answered in time. A dead event loop looks like this,
42
+ * and so does a LIVE one in the middle of a long synchronous stretch (a large parse, a GC pause on a
43
+ * big heap): the kernel accepts into the listen backlog either way. Only waiting tells them apart.
44
+ * - `gone` — nothing listens behind the file any more (`ECONNREFUSED`, `ENOENT`, a reset): the process is gone.
45
+ * - `no-route` — something answered, but not our liveness answer (an older pod with no such route, a refused key,
46
+ * another shape). No evidence either way; the caller keeps its evict-on-deadline behaviour.
47
+ */
48
+ export type PodProbeFailure = 'silent' | 'gone' | 'no-route';
49
+ export interface PodProbeVerdict {
50
+ alive?: PodAliveness;
51
+ failure?: PodProbeFailure;
52
+ }
53
+ /** {@link probePodAlive}, saying WHY when there is no answer. Never throws, never hangs past `timeoutMs`. */
54
+ export declare function probePodAliveVerdict(socketPath: string, timeoutMs?: number, serviceKey?: string): Promise<PodProbeVerdict>;
38
55
  /**
39
56
  * What one failed pod connection says about the POD.
40
57
  *
@@ -1 +1 @@
1
- "use strict";var h=Object.defineProperty;var p=(r,e)=>h(r,"name",{value:e,configurable:!0});var f=Object.defineProperty,u=p((r,e)=>f(r,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.parsePodWatcherTestimony=parsePodWatcherTestimony,exports.probePodAlive=probePodAlive,exports.podConnectionVerdict=podConnectionVerdict,exports.podSocketAccepts=podSocketAccepts;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),net=tslib_1.__importStar(require("node:net")),types_1=require("./types");function parsePodWatcherTestimony(r){if(!r||typeof r!="object")return;const e=r;if(e.schemaVersion!==1)return;const c=u(n=>typeof n=="number"&&Number.isFinite(n)?n:0,"num"),o=e.degraded;return{schemaVersion:1,lastWatchRunAt:c(e.lastWatchRunAt),lastBuildAt:c(e.lastBuildAt),buildsSinceSpawn:c(e.buildsSinceSpawn),degraded:typeof o=="string"&&o?o:o===!0?"degraded":!1,recentPaths:Array.isArray(e.recentPaths)?e.recentPaths.filter(n=>typeof n=="string").slice(-32):[]}}p(parsePodWatcherTestimony,"parsePodWatcherTestimony"),u(parsePodWatcherTestimony,"parsePodWatcherTestimony");async function probePodAlive(r,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS,c){return new Promise(o=>{let n=!1;const i=u(s=>{n||(n=!0,o(s))},"finish");let a;try{a=http.request({socketPath:r,path:types_1.POD_ALIVE_PATH,method:"GET",timeout:e,agent:!1,...c?{headers:{"x-internal-token":c}}:{}},s=>{if(s.statusCode!==200){s.resume(),i(void 0);return}let d="";s.setEncoding("utf8"),s.on("data",t=>{d+=t}),s.on("end",()=>{try{const t=JSON.parse(d);i(t.ok===!0?{inflight:Number(t.inflight??0),activeWork:Number(t.activeWork??0),swapping:t.swapping===!0,...typeof t.pid=="number"?{pid:t.pid}:{},...typeof t.keyFingerprint=="string"?{keyFingerprint:t.keyFingerprint}:{},...(()=>{const l=parsePodWatcherTestimony(t.watcher);return l?{watcher:l}:{}})(),...typeof t.podGeneration=="number"&&Number.isFinite(t.podGeneration)?{podGeneration:t.podGeneration}:{}}:void 0)}catch{i(void 0)}})})}catch{i(void 0);return}a.on("timeout",()=>{a.destroy(),i(void 0)}),a.on("error",()=>i(void 0)),a.end()})}p(probePodAlive,"probePodAlive"),u(probePodAlive,"probePodAlive");async function podConnectionVerdict(r,e,c=types_1.POD_ALIVE_PROBE_TIMEOUT_MS){const o=e.code??"";return o==="ECONNREFUSED"||o==="ENOENT"?"gone":await podSocketAccepts(r,c)?"alive":"gone"}p(podConnectionVerdict,"podConnectionVerdict"),u(podConnectionVerdict,"podConnectionVerdict");function podSocketAccepts(r,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS){return new Promise(c=>{let o=!1,n;const i=u(s=>{o||(o=!0,clearTimeout(a),n?.destroy(),c(s))},"finish"),a=setTimeout(()=>i(!1),e);a.unref?.();try{n=net.connect({path:r}),n.once("connect",()=>i(!0)),n.once("error",()=>i(!1))}catch{i(!1)}})}p(podSocketAccepts,"podSocketAccepts"),u(podSocketAccepts,"podSocketAccepts");
1
+ "use strict";var f=Object.defineProperty;var p=(t,e)=>f(t,"name",{value:e,configurable:!0});var l=Object.defineProperty,u=p((t,e)=>l(t,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.parsePodWatcherTestimony=parsePodWatcherTestimony,exports.probePodAlive=probePodAlive,exports.probePodAliveVerdict=probePodAliveVerdict,exports.podConnectionVerdict=podConnectionVerdict,exports.podSocketAccepts=podSocketAccepts;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),net=tslib_1.__importStar(require("node:net")),types_1=require("./types");function parsePodWatcherTestimony(t){if(!t||typeof t!="object")return;const e=t;if(e.schemaVersion!==1)return;const i=u(n=>typeof n=="number"&&Number.isFinite(n)?n:0,"num"),c=e.degraded;return{schemaVersion:1,lastWatchRunAt:i(e.lastWatchRunAt),lastBuildAt:i(e.lastBuildAt),buildsSinceSpawn:i(e.buildsSinceSpawn),degraded:typeof c=="string"&&c?c:c===!0?"degraded":!1,recentPaths:Array.isArray(e.recentPaths)?e.recentPaths.filter(n=>typeof n=="string").slice(-32):[]}}p(parsePodWatcherTestimony,"parsePodWatcherTestimony"),u(parsePodWatcherTestimony,"parsePodWatcherTestimony");async function probePodAlive(t,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS,i){return(await probePodAliveVerdict(t,e,i)).alive}p(probePodAlive,"probePodAlive"),u(probePodAlive,"probePodAlive");function probePodAliveVerdict(t,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS,i){return new Promise(c=>{let n=!1,s="no-route";const d=u(o=>{n||(n=!0,c(o?{alive:o}:{failure:s}))},"finish");let a;try{a=http.request({socketPath:t,path:types_1.POD_ALIVE_PATH,method:"GET",timeout:e,agent:!1,...i?{headers:{"x-internal-token":i}}:{}},o=>{if(o.statusCode!==200){o.resume(),d(void 0);return}let E="";o.setEncoding("utf8"),o.on("data",r=>{E+=r}),o.on("end",()=>{try{const r=JSON.parse(E);d(r.ok===!0?{inflight:Number(r.inflight??0),activeWork:Number(r.activeWork??0),swapping:r.swapping===!0,...typeof r.pid=="number"?{pid:r.pid}:{},...typeof r.keyFingerprint=="string"?{keyFingerprint:r.keyFingerprint}:{},...(()=>{const P=parsePodWatcherTestimony(r.watcher);return P?{watcher:P}:{}})(),...typeof r.podGeneration=="number"&&Number.isFinite(r.podGeneration)?{podGeneration:r.podGeneration}:{}}:void 0)}catch{d(void 0)}})})}catch{s="gone",d(void 0);return}a.on("timeout",()=>{s="silent",a.destroy(),d(void 0)}),a.on("error",o=>{s!=="silent"&&(s=o.code==="ECONNREFUSED"||o.code==="ENOENT"||o.code==="ECONNRESET"||o.code==="EPIPE"?"gone":"silent"),d(void 0)}),a.end()})}p(probePodAliveVerdict,"probePodAliveVerdict"),u(probePodAliveVerdict,"probePodAliveVerdict");async function podConnectionVerdict(t,e,i=types_1.POD_ALIVE_PROBE_TIMEOUT_MS){const c=e.code??"";return c==="ECONNREFUSED"||c==="ENOENT"?"gone":await podSocketAccepts(t,i)?"alive":"gone"}p(podConnectionVerdict,"podConnectionVerdict"),u(podConnectionVerdict,"podConnectionVerdict");function podSocketAccepts(t,e=types_1.POD_ALIVE_PROBE_TIMEOUT_MS){return new Promise(i=>{let c=!1,n;const s=u(a=>{c||(c=!0,clearTimeout(d),n?.destroy(),i(a))},"finish"),d=setTimeout(()=>s(!1),e);d.unref?.();try{n=net.connect({path:t}),n.once("connect",()=>s(!0)),n.once("error",()=>s(!1))}catch{s(!1)}})}p(podSocketAccepts,"podSocketAccepts"),u(podSocketAccepts,"podSocketAccepts");
@@ -1,6 +1,7 @@
1
1
  import * as http from 'node:http';
2
2
  import type * as stream from 'node:stream';
3
3
  import type { GatewayRuntime, Route } from './types';
4
+ import { type PodProbeFailure } from './pod-probe';
4
5
  import type { Observability } from './observability';
5
6
  import type { ServiceLayer } from './lifecycle';
6
7
  /**
@@ -44,4 +45,17 @@ export interface ProxyLayer {
44
45
  destroyAgents(): void;
45
46
  closeProxy(): void;
46
47
  }
48
+ /** What the guard knew when it condemned a pod — the incident's evidence (issues/closed/foodguard-54). */
49
+ export interface WedgeDetail {
50
+ /** How long the pod had held the whole request with nothing written back. */
51
+ heldMs: number;
52
+ /** How long its liveness route stayed silent before the verdict (0 when the verdict needed no confirming). */
53
+ silentMs: number;
54
+ /** Liveness probes that found no answer. */
55
+ probes: number;
56
+ /** Why the last probe found none — `deadline` when no probe is configured (`LENSMCP_POD_SLOW_FIRST_BYTE_MS=0`). */
57
+ failure: PodProbeFailure | 'deadline';
58
+ }
59
+ /** What the client reads in the 502 — what happened, and what it means for the call (issues/closed/foodguard-54). */
60
+ export declare function wedgedMessage(podName: string, project: string, d: WedgeDetail): string;
47
61
  export declare function createProxy(rt: GatewayRuntime, obs: Observability, svcLayer: ServiceLayer): ProxyLayer;