@lensmcp/cluster 1.24.0 → 1.24.2

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 (56) hide show
  1. package/README.md +13 -1
  2. package/bundle-parent.d.ts +8 -0
  3. package/bundle-parent.js +1 -0
  4. package/checkout-cache.d.ts +5 -0
  5. package/checkout-cache.js +1 -0
  6. package/create-webpack-dev.d.ts +1 -1
  7. package/create-webpack-dev.js +1 -1
  8. package/devserver-notifier.d.ts +1 -1
  9. package/devserver-notifier.js +1 -1
  10. package/devserver-transport.d.ts +41 -0
  11. package/devserver-transport.js +1 -0
  12. package/executors/gateway/manifest.js +1 -1
  13. package/executors/gateway/runtime/auth.d.ts +7 -0
  14. package/executors/gateway/runtime/auth.js +1 -1
  15. package/executors/gateway/runtime/chooser.d.ts +2 -1
  16. package/executors/gateway/runtime/chooser.js +23 -10
  17. package/executors/gateway/runtime/control.d.ts +14 -0
  18. package/executors/gateway/runtime/control.js +2 -2
  19. package/executors/gateway/runtime/discovery.d.ts +15 -2
  20. package/executors/gateway/runtime/discovery.js +1 -1
  21. package/executors/gateway/runtime/edge.d.ts +7 -0
  22. package/executors/gateway/runtime/edge.js +1 -1
  23. package/executors/gateway/runtime/guest-replay.d.ts +22 -0
  24. package/executors/gateway/runtime/guest-replay.js +2 -0
  25. package/executors/gateway/runtime/instance.d.ts +104 -0
  26. package/executors/gateway/runtime/instance.js +2 -0
  27. package/executors/gateway/runtime/lens-children.js +1 -1
  28. package/executors/gateway/runtime/lifecycle.d.ts +4 -0
  29. package/executors/gateway/runtime/lifecycle.js +3 -3
  30. package/executors/gateway/runtime/registration-guards.js +1 -1
  31. package/executors/gateway/runtime/route-registry.d.ts +7 -0
  32. package/executors/gateway/runtime/scope.d.ts +18 -0
  33. package/executors/gateway/runtime/scope.js +1 -1
  34. package/executors/gateway/runtime/server.js +2 -2
  35. package/executors/gateway/runtime/service-keys.d.ts +12 -3
  36. package/executors/gateway/runtime/service-keys.js +1 -1
  37. package/executors/gateway/runtime/shared-build.d.ts +1 -1
  38. package/executors/gateway/runtime/shared-build.js +3 -3
  39. package/executors/gateway/runtime/types.d.ts +42 -0
  40. package/executors/gateway/runtime/types.js +1 -1
  41. package/executors/gateway/runtime/upgrade.d.ts +5 -7
  42. package/executors/gateway/runtime/upgrade.js +1 -1
  43. package/executors/gateway/runtime/workspace-middleware.d.ts +38 -0
  44. package/executors/gateway/runtime/workspace-middleware.js +1 -0
  45. package/executors/gateway/runtime/workspace-registry.d.ts +7 -0
  46. package/executors/serve/serve.impl.js +1 -1
  47. package/executors/trust/trust.impl.js +3 -3
  48. package/heap-snapshot.js +1 -1
  49. package/inspector-secret.d.ts +6 -0
  50. package/inspector-secret.js +1 -1
  51. package/main.devserver.d.ts +4 -38
  52. package/main.devserver.js +10 -10
  53. package/package.json +4 -4
  54. package/pod-memory.d.ts +6 -0
  55. package/pod-memory.js +1 -1
  56. package/shared-build/host.js +2 -2
@@ -0,0 +1,22 @@
1
+ export interface GuestRecord {
2
+ wsKey: string;
3
+ root: string;
4
+ projects: Record<string, {
5
+ root: string;
6
+ }>;
7
+ projectDependencies?: Record<string, string[]>;
8
+ /** The root of the daemon it was registered with. */
9
+ daemonRoot: string;
10
+ registeredAt: string;
11
+ }
12
+ export declare function guestsFile(): string;
13
+ export declare function readGuests(file?: string): GuestRecord[];
14
+ /** Record (or replace) a registration. Best-effort: a daemon that cannot write its home still serves the guest. */
15
+ export declare function recordGuest(entry: GuestRecord, file?: string): void;
16
+ /** Forget a registration (an unregister, or a root that is gone). */
17
+ export declare function forgetGuest(wsKey: string, file?: string): void;
18
+ /** What a daemon serving `daemonRoot` replays: its own guests whose folder is still a workspace — not itself. */
19
+ export declare function guestsToReplay(daemonRoot: string, list: readonly GuestRecord[]): {
20
+ replay: GuestRecord[];
21
+ gone: GuestRecord[];
22
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";var a=Object.defineProperty;var n=(r,t)=>a(r,"name",{value:t,configurable:!0});var c=Object.defineProperty,o=n((r,t)=>c(r,"name",{value:t,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.guestsFile=guestsFile,exports.readGuests=readGuests,exports.recordGuest=recordGuest,exports.forgetGuest=forgetGuest,exports.guestsToReplay=guestsToReplay;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path"));function guestsFile(){return path.join(process.env.LENSMCP_HOME||os.homedir(),".lensmcp","guests.json")}n(guestsFile,"guestsFile"),o(guestsFile,"guestsFile");function readGuests(r=guestsFile()){try{const t=JSON.parse(fs.readFileSync(r,"utf8"));return Array.isArray(t)?t.filter(e=>!!e&&typeof e=="object"&&typeof e.wsKey=="string"&&typeof e.root=="string"&&typeof e.daemonRoot=="string"&&!!e.projects&&typeof e.projects=="object"):[]}catch{return[]}}n(readGuests,"readGuests"),o(readGuests,"readGuests");function writeGuests(r,t){fs.mkdirSync(path.dirname(t),{recursive:!0,mode:448});const e=`${t}.${process.pid}.tmp`;fs.writeFileSync(e,JSON.stringify(r,null,2)+`
2
+ `,{mode:384}),fs.renameSync(e,t)}n(writeGuests,"writeGuests"),o(writeGuests,"writeGuests");function recordGuest(r,t=guestsFile()){try{writeGuests([...readGuests(t).filter(e=>e.wsKey!==r.wsKey),r],t)}catch(e){console.warn(`[gateway] could not record '${r.wsKey}' for replay (${e.message}) \u2014 a daemon restart will not bring it back`)}}n(recordGuest,"recordGuest"),o(recordGuest,"recordGuest");function forgetGuest(r,t=guestsFile()){try{const e=readGuests(t),s=e.filter(i=>i.wsKey!==r);s.length!==e.length&&writeGuests(s,t)}catch{}}n(forgetGuest,"forgetGuest"),o(forgetGuest,"forgetGuest");const same=o((r,t)=>{const e=o(s=>{try{return fs.realpathSync(s)}catch{return path.resolve(s)}},"real");return e(r)===e(t)},"same");function guestsToReplay(r,t){const e=[],s=[];for(const i of t)!same(i.daemonRoot,r)||same(i.root,r)||(fs.existsSync(path.join(i.root,"package.json"))?e:s).push(i);return{replay:e,gone:s}}n(guestsToReplay,"guestsToReplay"),o(guestsToReplay,"guestsToReplay");
@@ -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");
@@ -280,6 +280,8 @@ export interface ServiceLayer {
280
280
  startSweeper(): NodeJS.Timeout;
281
281
  /** Rolling recycle: standby generation first, retire the old once the new answers (issues/closed/dev-gateway-09). */
282
282
  rollPod(svc: ServiceCtl, reason: string): Promise<boolean>;
283
+ /** Self-heal a WEDGED service (live parent, 0 live pods): recycle it, within the crash-loop budget. */
284
+ recycleWedged(svc: ServiceCtl): void;
283
285
  /** Stop the build host of the workspace at `root` — one that unregistered — or, with no root, every build host
284
286
  * (shared-build.ts): after the service trees, at gateway stop. */
285
287
  stopBuildHost(root?: string): Promise<void>;
@@ -298,6 +300,8 @@ export interface ServiceLayerDeps {
298
300
  rollPollMs?: number;
299
301
  /** Rolls allowed in flight at once across every service. Default {@link POD_MAX_CONCURRENT_ROLLS}; `0` = no cap. */
300
302
  maxConcurrentRolls?: number;
303
+ /** Cold starts allowed at once across every service. Default {@link MAX_COLD_STARTS}; `0` = no cap. */
304
+ maxConcurrentColdStarts?: number;
301
305
  /** Skip the nx foreign-runner reap before a spawn (tests). */
302
306
  reapForeign?: boolean;
303
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 Re=Object.defineProperty,i=f((t,n)=>Re(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"),$=i(async(u,w)=>{const j=await(h??=gitIgnoredPaths(t)),P=[],I=new Set;let x=!1;const N=i(async(H,te)=>{if(P.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(P.length>=SCAN_FILE_CAP){x=!0;return}const D=z.name,J=path.join(H,D);if(z.isDirectory()){if(!te||D.startsWith(".")||SRC_EXCLUDE.has(D)||n?.has(D)||D.endsWith("-devserver")||j.has(`${S(J)}/`))continue;await N(J,!0)}else if(z.isFile()){const re=D.lastIndexOf(".");if(re<=0||!SRC_EXT.has(D.slice(re)))continue;const b=S(J);if(j.has(b)||a&&isTestSourcePath(b))continue;P.push(b)}}},"walk");return await N(u,w),{paths:P,entered:I,truncated:x}},"walkDir"),O=i((u,w)=>{let j=A.get(u);return j||(j=w(),A.set(u,j)),j},"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}:$(t,!1)}),"rootFiles"),ne=i(u=>O(`tree:${path.resolve(u)}`,async()=>{const w=await Y(),j=S(u);if(w?.entered.has(j)){const P=`${j}/`;return{paths:j===""?w.paths:w.paths.filter(I=>I.startsWith(P)),entered:q,truncated:!1}}return $(u,!0)}),"subtree"),v=i(u=>{const w=new Set;let j=0,P=u.some(x=>x.truncated);const I=i(x=>w.has(x)?!0:w.size>=SCAN_FILE_CAP?(P=!0,!1):(w.add(x),j^=fnv1a(x),!0),"add");for(const x of u)if(!x.paths.every(I))break;return{signature:`${w.size}:${(j>>>0).toString(36)}`,paths:w,truncated:P}},"compose");return{async snapshot(u){if(!u||u.length===0)return v([await O(g,()=>$(t,!0))]);const w=[await V()];for(const j of u)w.push(await ne(j));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 $ of t.paths)if(!n.paths.has($)){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($=>{const O=path.relative(t,$).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 $ of a)$.startsWith(".")||SRC_EXCLUDE.has($)||h?.has($)||$.endsWith("-devserver")||g.has($)||S.add($);return[...S].sort().map($=>path.join(t,$))}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,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),g=c.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),S=i((e,o="SIGTERM")=>{g.detach(e),A(e,o)},"killTree"),$=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,o,s,r,l)=>V(o,s,r,{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,o)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const r=e.proc?.pid;let l=0;for(const d of s.split(`
2
- `)){const p=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const _=Number(p[1]),C=Number(p[2]);if(isRunnerOf(p[3],o,e.root)&&!(_===process.pid||_===r)&&!(r!==void 0&&C===r)){try{process.kill(-C,"SIGKILL")}catch{try{process.kill(_,"SIGKILL")}catch{}}l+=1}}return l>0&&(console.log(`[gateway] ${o}: 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"),j=i((e,o)=>({...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(o),...eastWestEnv(t,e)}),"serviceEnv"),P=i((e,o,s,r)=>{const l=j(e,o),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:r,baseEnv:l});if(p.kind==="runner"){const m=g.offeredTo(e),R=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,r,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,o,R),R.send?.(p.message,k=>{k&&console.error(`[gateway] ${s}: could not hand the task to its runner (${k.message})`)}),{proc:R,launcher:"runner"}}console.log(`[gateway] ${s}: starting through nx run \u2014 ${p.reason}`);const _=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,E]=fs.existsSync(_)?[C,[_,"run",s,`--childCount=${r}`]]:["yarn",["nx","run",s,`--childCount=${r}`]];return{proc:a(W,E,{cwd:e.root,detached:!0,env:l,stdio:["ignore","pipe","pipe"]}),launcher:"nx"}},"startServiceTree"),I=new WeakMap,x=i((e,o,s)=>{const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,l=Math.max(1,e.decl.minPods??1),{proc:d,launcher:p}=P(e,o,r,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 _=i(E=>{const m=E.toString();for(const R of m.split(`
3
- `)){const k=(0,pod_output_1.classifyPodLine)(R),{clean:L,broken:U,healthy:y}=k,F=L;if(U){e.lastErrors.push(L),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const M=Date.now();(L!==e.lastErrorEmitSig||M-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=L,e.lastErrorEmitAt=M,v(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:L,fault:k.fault}))}y&&(e.buildFailed=!1);const T=(e.faults??=new pod_output_1.PodFaults).observe(k);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:L,closed:T.closed,open:T.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(F)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: 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||(b(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),w(e,r),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(E=>m=>{if(t.stopped())return;const R=(0,inspector_secret_1.redactInspectorUrls)(m.toString());_(R);const k=`[${e.project}] ${R}`;E.write(k),C?.(k)},"relay");return d.stdout?.on("data",W(process.stdout)),d.stderr?.on("data",W(process.stderr)),d.on("exit",(E,m)=>{if(e.proc!==d||(e.proc=void 0,e.state="down",t.stopped()))return;const R=(0,exit_reason_1.describeExit)(E,m);if(console.log(`[gateway] ${r} exited (${E??"no code"}${m?`, ${m}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${R?` \u2014 ${R}`:""}`),v(e,E===0?"info":"warning",`service down: ${e.project} (exit ${E})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:E,...m?{signal:m}:{}}),E===0||e.buildFailed)return;const k=Date.now();if(e.restarts=(e.restarts??[]).filter(U=>k-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(k);const L=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))},L)}),d},"launchServiceProcess"),N=i(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);c.reapForeign!==!1&&w(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (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 o=e.pool.socks[0];if(!o)return;const s=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),te=i(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),oe=i(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const s=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const l of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(l,void 0,o);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&r.push({sock:l,pid:d.pid})}return r},"staleAdoptedSocks"),z=i((e,o)=>{const s=o.map(r=>r.sock);for(const{sock:r,pid:l}of o){const d=chooseStalePodKillTarget(l,te(r),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!s.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),D=i((e,o)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return v(e,"info",`service up: ${e.project} (${s} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:o==="adopted"}),!0},"markUp"),J=i(async e=>{if(u(e.pool)>0){const r=await oe(e);if(r.length>0&&(console.error(`[gateway] ${e.project}: ${r.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),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:r.length}),e.proc&&b(e,"stale adoption (foreign east-west key)"),z(e,r)),e.pool.socks.length>0)return D(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&N(e);const o=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(u(e.pool)>0){e.state="up",e.buildFailed=!1;const r=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${r?`, cold start ${r}ms`:""})`),v(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${r??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:r}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),re=i(e=>{const o=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,r=scaleTarget(e);!r||s===0||s>=o||e.inflight<=s||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(r,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"),b=i((e,o,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),s?.announce!==!1&&v(e,"info",`idle kill: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:o});const r=e.proc;try{r.stdout?.removeAllListeners("data"),r.stderr?.removeAllListeners("data"),r.removeAllListeners("exit")}catch{}S(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{ce(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ce=i(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const s=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let G=0;const ae=[],ge=i(()=>G<q?(G+=1,Promise.resolve()):new Promise(e=>{ae.push(()=>{G+=1,e()})}),"acquireRollSlot"),we=i(()=>{G-=1,ae.shift()?.()},"releaseRollSlot"),ue=i((e,o)=>{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=${o}).`),Promise.resolve(!1);G>=q&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${o} \u2014 ${G} roll(s) in flight, ${ae.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${q}).`);const r=(async()=>{await ge();try{return await Se(e,s,o)}finally{we()}})().finally(()=>{e.rolling=void 0});return e.rolling=r,r},"rollPod"),Se=i(async(e,o,s)=>{const r=e.proc;if(r!==o||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 _=x(e,d,"standby"),C=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],W=p+$;let E;for(;!t.stopped()&&Date.now()<W&&!(_.exitCode!==null||_.signalCode!==null);){u(e.pool);for(const m of e.pool.socks)if((await h(m,void 0,C))?.podGeneration===d){E=m;break}if(E)break;await sleep(O)}if(!E||t.stopped()){const m=t.stopped()?"gateway stopping":_.exitCode!==null||_.signalCode!==null?`standby exited (${_.exitCode??_.signalCode})`:`standby not ready within ${Math.round($/1e3)}s`;try{_.stdout?.removeAllListeners("data"),_.stderr?.removeAllListeners("data")}catch{}return S(_,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${d}: ${m} \u2014 old generation g${l} kept serving.`),v(e,"warning",`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}),setTimeout(()=>ce(e),1200).unref?.(),!1}if(e.proc!==r)return S(_,"SIGTERM"),!1;e.proc=_,e.launcher=I.get(_),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{r.stdout?.removeAllListeners("data"),r.stderr?.removeAllListeners("data"),r.removeAllListeners("exit")}catch{}return S(r,"SIGTERM"),setTimeout(()=>{ce(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${d} answered on ${path.basename(E)} 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"),ve=i(e=>{const o=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>o-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){v(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),b(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS}; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),v(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),b(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,he=new Map;let le=!1,de=0,se=0;const ye=i(()=>{const e=t.projectRoots??{},o=new Set(t.services.map(r=>r.project));for(const r of t.routes)r.lens&&o.add(r.lens.project);const s=new Set;for(const r of o){const l=e[r];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"),me=i((e,o,s,r)=>{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,o,r);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 o=(0,scope_1.readSourceSetOptions)(t.root),s=o.exclude,r={ignoreTests:o.ignoreTests},l=sourceScanPass(t.root,s,r),d=await l.snapshot(),p=Date.now(),_=sourceChangeWindowMs(p,se,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),C=ie?diffSourceSets(ie,d,p,_):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(y=>y.isDirectory()).map(y=>y.name)}catch{return}const E=ye(),m=new Set(t.services.map(y=>y.project));for(const y of t.routes)y.lens&&m.add(y.lens.project);const R=new Map;for(const y of m){const F=me(y,W,E,s);if(!F)continue;const T=F.join("|"),M=R.get(T)??{dirs:F,projects:[]};M.projects.push(y),R.set(T,M)}const k=Date.now(),L=sourceChangeWindowMs(k,se,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);se=e;const U=i((y,F,T)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const M of y)t.sourceSetChangedFor.set(M,F),t.sourceSetChangesFor.set(M,T)},"stampScoped");for(const[y,{dirs:F,projects:T}]of R){const M=await l.snapshot(F),fe=Q.get(y);Q.set(y,M);const pe=new Map;for(const B of T){const K=he.get(B);if(K===void 0||K===y)continue;const X=pe.get(K)??[];X.push(B),pe.set(K,X)}for(const B of T)he.set(B,y);for(const[B,K]of pe){const X=Q.get(B),ee=X?diffSourceSets(X,M,k,L):C;ee&&(U(K,X?k: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(!fe)continue;const Z=diffSourceSets(fe,M,k,L);Z&&(U(T,k,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 y of[...Q.keys()])R.has(y)||Q.delete(y)})().catch(()=>{}).finally(()=>{le=!1})},"maybeScanSourceSet"),Ee=i((e,o)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,r=podWatcherVerdict(e.watcher,o,s),l=i(d=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,v(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),l(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):r==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):r==="silent"?types_1.POD_WATCHER_SILENT==="keep"?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"),je=i((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){ue(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),v(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const s=!!e.decl.eager;b(e,`${o} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&N(e)},1500).unref?.()},"recyclePod"),ke=i(()=>{const e=setInterval(()=>{Ae().catch(o=>console.error("[gateway] sweep error:",o))},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 o=e.decl.idleKillSec;if(o&&e.inflight===0&&Date.now()-e.lastUsed>o*1e3){b(e,`idle ${o}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,r=u(e.pool)>0;if(r||s){if(e.wedgedSince=void 0,r&&D(e,"observed"),r&&!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"&&!Ee(e,l))continue;p&&je(e,p)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ve(e))}}},"sweepOnce");return{scanNow:u,spawnService:N,ensureUp:J,scaleUp:re,killSpawned:b,markUp:D,startSweeper:ke,rollPod:ue,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");
@@ -1 +1 @@
1
- "use strict";var l=Object.defineProperty;var n=(e,t)=>l(e,"name",{value:t,configurable:!0});var u=Object.defineProperty,r=n((e,t)=>u(e,"name",{value:t,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.sameRoot=void 0,exports.canonicalRoot=canonicalRoot,exports.hostConflicts=hostConflicts,exports.refuseRegistration=refuseRegistration,exports.refuseUnregistration=refuseUnregistration;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));function canonicalRoot(e){try{return fs.realpathSync(e)}catch{return path.resolve(e)}}n(canonicalRoot,"canonicalRoot"),r(canonicalRoot,"canonicalRoot");const sameRoot=r((e,t)=>canonicalRoot(e)===canonicalRoot(t),"sameRoot");exports.sameRoot=sameRoot;function claim(e){return e.host?`${e.host.toLowerCase()} ${e.prefix??""}`:e.apex?`* ${e.apex.toLowerCase()}`:void 0}n(claim,"claim"),r(claim,"claim");function hostConflicts(e,t){const s=[],o=new Set;for(const i of e.keys()){if(i===t.wsKey)continue;const a=e.get(i);if(!a)continue;const f=new Set(a.routes.map(claim));for(const c of t.routes){const h=claim(c);!h||!f.has(h)||o.has(h)||(o.add(h),s.push({host:c.host??`(default for ${c.apex})`,...c.prefix?{prefix:c.prefix}:{},heldBy:i,heldByRoot:a.root}))}}return s}n(hostConflicts,"hostConflicts"),r(hostConflicts,"hostConflicts");const describeConflict=r(e=>`${e.host}${e.prefix??""} (served by '${e.heldBy}')`,"describeConflict");function refuseRegistration(e,t){const s=e.get(t.wsKey);if(s&&!(0,exports.sameRoot)(s.root,t.root))return{error:"key-in-use",heldByRoot:s.root,message:`'${t.wsKey}' is already served from ${s.root}. ${t.root} is another checkout that derives the same key, and two checkouts cannot share a key or hosts behind one gateway: registering this one would replace the other's routes. Give this checkout its own \`key\` in ${path.join(t.root,".lensmcp","config.json")} and its own hosts, or run \`lensmcp gateway stop\` in ${s.root} first.`};const o=hostConflicts(e,t);if(o.length>0){const i=o.slice(0,6).map(describeConflict).join(", "),a=o.length>6?`, and ${o.length-6} more`:"";return{error:"host-conflict",conflicts:o,message:`'${t.wsKey}' declares ${o.length===1?"a host":`${o.length} hosts`} another workspace already serves: ${i}${a}. The workspace registered first would win every request to ${o.length===1?"it":"them"}, so this registration is refused. Two workspaces behind one gateway need different hosts.`}}}n(refuseRegistration,"refuseRegistration"),r(refuseRegistration,"refuseRegistration");function refuseUnregistration(e,t,s){const o=e.get(t.wsKey);if(o&&t.root&&!(0,exports.sameRoot)(o.root,t.root))return{error:"not-your-registration",heldByRoot:o.root,message:`'${t.wsKey}' is served from ${o.root}, not from ${t.root} \u2014 nothing of this checkout is registered, so nothing was removed.`};if(o&&s!==void 0&&t.wsKey===s)return{error:"daemon-workspace",heldByRoot:o.root,message:`'${t.wsKey}' is the workspace that owns the gateway \u2014 unregistering it would leave :443 serving without it. Stop the gateway instead: \`lensmcp gateway stop\` in ${o.root}.`}}n(refuseUnregistration,"refuseUnregistration"),r(refuseUnregistration,"refuseUnregistration");
1
+ "use strict";var d=Object.defineProperty;var n=(e,o)=>d(e,"name",{value:o,configurable:!0});var u=Object.defineProperty,r=n((e,o)=>u(e,"name",{value:o,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.sameRoot=void 0,exports.canonicalRoot=canonicalRoot,exports.hostConflicts=hostConflicts,exports.refuseRegistration=refuseRegistration,exports.refuseUnregistration=refuseUnregistration;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));function canonicalRoot(e){try{return fs.realpathSync(e)}catch{return path.resolve(e)}}n(canonicalRoot,"canonicalRoot"),r(canonicalRoot,"canonicalRoot");const sameRoot=r((e,o)=>canonicalRoot(e)===canonicalRoot(o),"sameRoot");exports.sameRoot=sameRoot;function claim(e){return e.host?`${e.host.toLowerCase()} ${e.prefix??""}`:e.apex?`* ${e.apex.toLowerCase()}`:void 0}n(claim,"claim"),r(claim,"claim");function hostConflicts(e,o){const s=[],t=new Set;for(const i of e.keys()){if(i===o.wsKey)continue;const a=e.get(i);if(!a)continue;const f=new Set(a.routes.map(claim));for(const c of o.routes){const h=claim(c);!h||!f.has(h)||t.has(h)||(t.add(h),s.push({host:c.host??`(default for ${c.apex})`,...c.prefix?{prefix:c.prefix}:{},heldBy:i,heldByRoot:a.root}))}}return s}n(hostConflicts,"hostConflicts"),r(hostConflicts,"hostConflicts");const describeConflict=r(e=>`${e.host}${e.prefix??""} (served by '${e.heldBy}')`,"describeConflict");function refuseRegistration(e,o){const s=e.get(o.wsKey);if(s&&!(0,exports.sameRoot)(s.root,o.root))return{error:"key-in-use",heldByRoot:s.root,message:`'${o.wsKey}' is already served from ${s.root}. ${o.root} is another checkout that derives the same key, and two checkouts cannot share a key or hosts behind one gateway: registering this one would replace the other's routes. To serve it beside the other under hosts of its own, run \`lensmcp instance start\` in ${o.root}; otherwise run \`lensmcp gateway stop\` in ${s.root} first.`};const t=hostConflicts(e,o);if(t.length>0){const i=t.slice(0,6).map(describeConflict).join(", "),a=t.length>6?`, and ${t.length-6} more`:"";return{error:"host-conflict",conflicts:t,message:`'${o.wsKey}' declares ${t.length===1?"a host":`${t.length} hosts`} another workspace already serves: ${i}${a}. The workspace registered first would win every request to ${t.length===1?"it":"them"}, so this registration is refused. Two workspaces behind one gateway need different hosts \u2014 for a second checkout of a workspace, \`lensmcp instance start\` in it serves it under hosts named after it.`}}}n(refuseRegistration,"refuseRegistration"),r(refuseRegistration,"refuseRegistration");function refuseUnregistration(e,o,s){const t=e.get(o.wsKey);if(t&&o.root&&!(0,exports.sameRoot)(t.root,o.root))return{error:"not-your-registration",heldByRoot:t.root,message:`'${o.wsKey}' is served from ${t.root}, not from ${o.root} \u2014 nothing of this checkout is registered, so nothing was removed.`};if(t&&s!==void 0&&o.wsKey===s)return{error:"daemon-workspace",heldByRoot:t.root,message:`'${o.wsKey}' is the workspace that owns the gateway \u2014 unregistering it would leave :443 serving without it. Stop the gateway instead: \`lensmcp gateway stop\` in ${t.root}.`}}n(refuseUnregistration,"refuseUnregistration"),r(refuseUnregistration,"refuseUnregistration");
@@ -1,7 +1,10 @@
1
+ import type { InstanceRouting } from './instance';
1
2
  import type { Route, ServiceCtl } from './types';
2
3
  export interface WorkspaceFragment {
3
4
  /** The workspace key (`readLensScope(root).key`). */
4
5
  wsKey: string;
6
+ /** Where its pods' sockets live under `$TMPDIR` when that is not `wsKey` (`readLensScope(root).sockNs`). */
7
+ sockNs?: string;
5
8
  /** Absolute workspace root — the fragment's provenance (for the chooser + reaping). */
6
9
  root: string;
7
10
  /** Every route this workspace contributes (host + path mounts, internal.*, its default catch-all, and —
@@ -9,6 +12,10 @@ export interface WorkspaceFragment {
9
12
  routes: Route[];
10
13
  /** The pod-service controllers this workspace owns (scale-from-zero lifecycle is per-service). */
11
14
  services: ServiceCtl[];
15
+ /** Set when this workspace is a worktree INSTANCE of another (instance.ts): its name, apexes and variables. */
16
+ instance?: InstanceRouting;
17
+ /** Its own gateway target's `middleware` (relative to `root`), run on its routes (workspace-middleware.ts). */
18
+ middleware?: string;
12
19
  }
13
20
  /** A live map of `wsKey → fragment`; `merged()` is the flat table the pipeline matches against. */
14
21
  export declare class RouteRegistry {
@@ -1,3 +1,4 @@
1
+ import { type InstanceDecl } from './instance';
1
2
  /** The base domain for the lens dashboard host — the longest common dotted
2
3
  * suffix of the declared cluster hosts (e.g. ['api.tetros.ai.local',
3
4
  * 'tetros.ai.local'] → 'tetros.ai.local'). Falls back to the first host, then
@@ -16,7 +17,24 @@ export interface LensScope {
16
17
  */
17
18
  mcpHttpPort: number;
18
19
  basePath: string;
20
+ /**
21
+ * The folder under `$TMPDIR` its pods' sockets live in (`$TMPDIR/<sockNs>/<service>-devserver/`). The key,
22
+ * unless `.lensmcp/config.json` pins a shorter `sockNs`: a unix socket path has 103 usable characters on
23
+ * macOS, and with a 49-character `$TMPDIR` FoodGuard's longest pod socket already took 101 under the key
24
+ * `foodguard`. A worktree instance (`foodguard-<name>`) cannot fit under its key, so it gets a short one.
25
+ */
26
+ sockNs: string;
27
+ /**
28
+ * Set when this checkout is a worktree INSTANCE of another workspace (`.lensmcp/config.json` → `instance`, written
29
+ * by `lensmcp instance start`): its hosts are rewritten under its name (instance.ts). Only honoured when the
30
+ * config's `key` is the instance key `<baseKey>-<name>` — a half-edited file is served as an ordinary workspace.
31
+ */
32
+ instance?: InstanceDecl;
19
33
  }
34
+ /** A `sockNs` the lens accepts: one short path segment. Anything else falls back to the key. */
35
+ export declare const SOCK_NS_PATTERN: RegExp;
36
+ /** The socket namespace a config pins, or `key` when it pins none (or one that is not a single short segment). */
37
+ export declare function sockNamespace(key: string, pinned: unknown): string;
20
38
  /** Per-workspace lens scope (key + ports + mount path). Prefers the
21
39
  * CLI-written `.lensmcp/config.json` (so the gateway agrees with `lensmcp gateway`
22
40
  * / `lensmcp dashboard` / the MCP shim); falls back to deriving from the workspace. */
@@ -1 +1 @@
1
- "use strict";var u=Object.defineProperty;var s=(e,r)=>u(e,"name",{value:r,configurable:!0});var a=Object.defineProperty,o=s((e,r)=>a(e,"name",{value:r,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.baseDomainOf=baseDomainOf,exports.readLensScope=readLensScope,exports.deriveMcpHttpPort=deriveMcpHttpPort,exports.readSourceSetExclude=readSourceSetExclude,exports.readSourceSetOptions=readSourceSetOptions,exports.lensKeyFrom=lensKeyFrom,exports.lensSlug=lensSlug;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));function baseDomainOf(e){const r=e.filter(n=>!!n&&!n.startsWith("*."));if(r.length===0)return"local";const t=r.reduce((n,i)=>i.length<n.length?i:n);if(r.every(n=>n===t||n.endsWith("."+t)))return t;const c=t.split(".");return c.slice(-Math.min(c.length,3)).join(".")}s(baseDomainOf,"baseDomainOf"),o(baseDomainOf,"baseDomainOf");const BASE_PORTS={dashboard:4321,mcpHttp:4500};function hashToRange(e,r){let t=2166136261;for(let c=0;c<e.length;c++)t^=e.charCodeAt(c),t=Math.imul(t,16777619);return Math.abs(t|0)%r}s(hashToRange,"hashToRange"),o(hashToRange,"hashToRange");function readLensScope(e){try{const t=JSON.parse(fs.readFileSync(path.join(e,".lensmcp","config.json"),"utf8"));if(t&&typeof t.key=="string")return{key:t.key,dashboardPort:t.ports?.dashboard??BASE_PORTS.dashboard,mcpHttpPort:t.ports?.mcpHttp??deriveMcpHttpPort(t.key),basePath:t.dashboardBasePath??"/"+t.key}}catch{}const r=lensKeyFrom(e);return{key:r,dashboardPort:BASE_PORTS.dashboard,mcpHttpPort:deriveMcpHttpPort(r),basePath:"/"+r}}s(readLensScope,"readLensScope"),o(readLensScope,"readLensScope");function deriveMcpHttpPort(e){return BASE_PORTS.mcpHttp+hashToRange(e,200)}s(deriveMcpHttpPort,"deriveMcpHttpPort"),o(deriveMcpHttpPort,"deriveMcpHttpPort");function readSourceSetExclude(e){return readSourceSetOptions(e).exclude}s(readSourceSetExclude,"readSourceSetExclude"),o(readSourceSetExclude,"readSourceSetExclude");function readSourceSetOptions(e){const r={exclude:new Set,ignoreTests:!0};try{const t=JSON.parse(fs.readFileSync(path.join(e,".lensmcp","config.json"),"utf8")),c=t?.sourceSet?.exclude;Array.isArray(c)&&(r.exclude=new Set(c.filter(n=>typeof n=="string"&&n.length>0&&!n.includes("/")))),t?.sourceSet?.ignoreTests===!1&&(r.ignoreTests=!1)}catch{}return r}s(readSourceSetOptions,"readSourceSetOptions"),o(readSourceSetOptions,"readSourceSetOptions");function lensKeyFrom(e){try{const r=JSON.parse(fs.readFileSync(path.join(e,"package.json"),"utf8"));if(r?.name)return lensSlug(r.name)}catch{}return lensSlug(path.basename(e))}s(lensKeyFrom,"lensKeyFrom"),o(lensKeyFrom,"lensKeyFrom");function lensSlug(e){return e.toLowerCase().replace(/^@[^/]+\//,"").replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"")||"workspace"}s(lensSlug,"lensSlug"),o(lensSlug,"lensSlug");
1
+ "use strict";var p=Object.defineProperty;var c=(r,t)=>p(r,"name",{value:t,configurable:!0});var a=Object.defineProperty,o=c((r,t)=>a(r,"name",{value:t,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.SOCK_NS_PATTERN=void 0,exports.baseDomainOf=baseDomainOf,exports.sockNamespace=sockNamespace,exports.readLensScope=readLensScope,exports.deriveMcpHttpPort=deriveMcpHttpPort,exports.readSourceSetExclude=readSourceSetExclude,exports.readSourceSetOptions=readSourceSetOptions,exports.lensKeyFrom=lensKeyFrom,exports.lensSlug=lensSlug;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),instance_1=require("./instance");function baseDomainOf(r){const t=r.filter(n=>!!n&&!n.startsWith("*."));if(t.length===0)return"local";const e=t.reduce((n,i)=>i.length<n.length?i:n);if(t.every(n=>n===e||n.endsWith("."+e)))return e;const s=e.split(".");return s.slice(-Math.min(s.length,3)).join(".")}c(baseDomainOf,"baseDomainOf"),o(baseDomainOf,"baseDomainOf");const BASE_PORTS={dashboard:4321,mcpHttp:4500};function hashToRange(r,t){let e=2166136261;for(let s=0;s<r.length;s++)e^=r.charCodeAt(s),e=Math.imul(e,16777619);return Math.abs(e|0)%t}c(hashToRange,"hashToRange"),o(hashToRange,"hashToRange"),exports.SOCK_NS_PATTERN=/^[a-z0-9][a-z0-9-]{0,31}$/;function sockNamespace(r,t){return typeof t=="string"&&exports.SOCK_NS_PATTERN.test(t)?t:r}c(sockNamespace,"sockNamespace"),o(sockNamespace,"sockNamespace");function readLensScope(r){try{const e=JSON.parse(fs.readFileSync(path.join(r,".lensmcp","config.json"),"utf8"));if(e&&typeof e.key=="string"){const s=(0,instance_1.parseInstanceDecl)(e.instance),n=s&&(0,instance_1.instanceKey)(s.baseKey,s.name)===e.key?s:void 0;return{key:e.key,dashboardPort:e.ports?.dashboard??BASE_PORTS.dashboard,mcpHttpPort:e.ports?.mcpHttp??deriveMcpHttpPort(e.key),basePath:e.dashboardBasePath??"/"+e.key,sockNs:sockNamespace(n?(0,instance_1.instanceSockNs)(e.key):e.key,e.sockNs),...n?{instance:n}:{}}}}catch{}const t=lensKeyFrom(r);return{key:t,dashboardPort:BASE_PORTS.dashboard,mcpHttpPort:deriveMcpHttpPort(t),basePath:"/"+t,sockNs:t}}c(readLensScope,"readLensScope"),o(readLensScope,"readLensScope");function deriveMcpHttpPort(r){return BASE_PORTS.mcpHttp+hashToRange(r,200)}c(deriveMcpHttpPort,"deriveMcpHttpPort"),o(deriveMcpHttpPort,"deriveMcpHttpPort");function readSourceSetExclude(r){return readSourceSetOptions(r).exclude}c(readSourceSetExclude,"readSourceSetExclude"),o(readSourceSetExclude,"readSourceSetExclude");function readSourceSetOptions(r){const t={exclude:new Set,ignoreTests:!0};try{const e=JSON.parse(fs.readFileSync(path.join(r,".lensmcp","config.json"),"utf8")),s=e?.sourceSet?.exclude;Array.isArray(s)&&(t.exclude=new Set(s.filter(n=>typeof n=="string"&&n.length>0&&!n.includes("/")))),e?.sourceSet?.ignoreTests===!1&&(t.ignoreTests=!1)}catch{}return t}c(readSourceSetOptions,"readSourceSetOptions"),o(readSourceSetOptions,"readSourceSetOptions");function lensKeyFrom(r){try{const t=JSON.parse(fs.readFileSync(path.join(r,"package.json"),"utf8"));if(t?.name)return lensSlug(t.name)}catch{}return lensSlug(path.basename(r))}c(lensKeyFrom,"lensKeyFrom"),o(lensKeyFrom,"lensKeyFrom");function lensSlug(r){return r.toLowerCase().replace(/^@[^/]+\//,"").replace(/[^a-z0-9]+/g,"-").replace(/(^-|-$)/g,"")||"workspace"}c(lensSlug,"lensSlug"),o(lensSlug,"lensSlug");