@lensmcp/cluster 1.23.13 → 1.24.0

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 (105) hide show
  1. package/build-host.d.ts +1 -0
  2. package/build-host.js +1 -0
  3. package/create-webpack-dev.d.ts +74 -40
  4. package/create-webpack-dev.js +1 -1
  5. package/dev-devtool.d.ts +45 -0
  6. package/dev-devtool.js +1 -0
  7. package/devserver-notifier.d.ts +57 -0
  8. package/devserver-notifier.js +1 -0
  9. package/executors/gateway/body-stall.d.ts +13 -1
  10. package/executors/gateway/body-stall.js +1 -1
  11. package/executors/gateway/canonical-path.d.ts +1 -1
  12. package/executors/gateway/client-ip.d.ts +1 -1
  13. package/executors/gateway/gateway.impl.js +1 -1
  14. package/executors/gateway/jwt-claims.d.ts +1 -1
  15. package/executors/gateway/prod-config.d.ts +1 -1
  16. package/executors/gateway/prod-runtime/body-stall.d.ts +1 -1
  17. package/executors/gateway/rate-limit.d.ts +1 -1
  18. package/executors/gateway/runtime/auth.d.ts +1 -1
  19. package/executors/gateway/runtime/chooser.d.ts +2 -2
  20. package/executors/gateway/runtime/control.d.ts +3 -3
  21. package/executors/gateway/runtime/control.js +2 -2
  22. package/executors/gateway/runtime/dev-auth.d.ts +1 -1
  23. package/executors/gateway/runtime/edge.d.ts +3 -3
  24. package/executors/gateway/runtime/guest-graph.d.ts +113 -0
  25. package/executors/gateway/runtime/guest-graph.js +2 -0
  26. package/executors/gateway/runtime/lens-children.d.ts +11 -1
  27. package/executors/gateway/runtime/lens-children.js +1 -1
  28. package/executors/gateway/runtime/lifecycle.d.ts +54 -8
  29. package/executors/gateway/runtime/lifecycle.js +3 -3
  30. package/executors/gateway/runtime/process-names.d.ts +18 -0
  31. package/executors/gateway/runtime/process-names.js +1 -0
  32. package/executors/gateway/runtime/project-graph-store.d.ts +74 -0
  33. package/executors/gateway/runtime/project-graph-store.js +1 -0
  34. package/executors/gateway/runtime/proxy.d.ts +2 -2
  35. package/executors/gateway/runtime/proxy.js +1 -1
  36. package/executors/gateway/runtime/scope.d.ts +1 -1
  37. package/executors/gateway/runtime/serve-launch.d.ts +118 -0
  38. package/executors/gateway/runtime/serve-launch.js +3 -0
  39. package/executors/gateway/runtime/server.d.ts +3 -3
  40. package/executors/gateway/runtime/server.js +2 -2
  41. package/executors/gateway/runtime/shared-build.d.ts +65 -0
  42. package/executors/gateway/runtime/shared-build.js +4 -0
  43. package/executors/gateway/runtime/types.d.ts +35 -9
  44. package/executors/gateway/runtime/types.js +1 -1
  45. package/executors/gateway/runtime/workspace-log.js +1 -1
  46. package/executors/gateway/schema.d.ts +3 -0
  47. package/executors/gateway/schema.json +4 -0
  48. package/executors/serve/schema.d.ts +2 -0
  49. package/executors/serve/schema.json +4 -0
  50. package/executors/serve/serve.impl.d.ts +12 -6
  51. package/executors/serve/serve.impl.js +1 -1
  52. package/executors/trust/hostname.d.ts +1 -1
  53. package/exit-reason.d.ts +1 -1
  54. package/fork-ts-check-reporter.d.ts +12 -0
  55. package/fork-ts-check-reporter.js +1 -1
  56. package/graph-snapshot.d.ts +1 -0
  57. package/graph-snapshot.js +1 -0
  58. package/heap-snapshot.d.ts +66 -0
  59. package/heap-snapshot.js +1 -0
  60. package/hot-swap-registries.d.ts +1 -1
  61. package/inspector-secret.d.ts +39 -0
  62. package/inspector-secret.js +1 -0
  63. package/main.devserver.d.ts +33 -5
  64. package/main.devserver.js +11 -9
  65. package/package.json +4 -4
  66. package/perf/heavy-barrel-import.d.ts +135 -0
  67. package/perf/heavy-barrel-import.js +1 -0
  68. package/perf/perf-config.d.ts +29 -0
  69. package/perf/perf-config.js +1 -0
  70. package/perf/perf-rule-events.d.ts +71 -0
  71. package/perf/perf-rule-events.js +2 -0
  72. package/perf/perf-rules-analysis.d.ts +28 -0
  73. package/perf/perf-rules-analysis.js +1 -0
  74. package/perf/perf-rules-plugin.d.ts +71 -0
  75. package/perf/perf-rules-plugin.js +1 -0
  76. package/perf/perf-rules-worker.d.ts +1 -0
  77. package/perf/perf-rules-worker.js +1 -0
  78. package/perf/source-scan.d.ts +59 -0
  79. package/perf/source-scan.js +5 -0
  80. package/perf/workspace-libraries.d.ts +52 -0
  81. package/perf/workspace-libraries.js +2 -0
  82. package/pod-memory-events.d.ts +71 -0
  83. package/pod-memory-events.js +2 -0
  84. package/pod-memory.d.ts +258 -0
  85. package/pod-memory.js +1 -0
  86. package/process-name.d.ts +45 -0
  87. package/process-name.js +1 -1
  88. package/serve-runner.d.ts +2 -0
  89. package/serve-runner.js +1 -0
  90. package/shared-build/attach.d.ts +56 -0
  91. package/shared-build/attach.js +3 -0
  92. package/shared-build/host.d.ts +52 -0
  93. package/shared-build/host.js +4 -0
  94. package/shared-build/protocol.d.ts +118 -0
  95. package/shared-build/protocol.js +1 -0
  96. package/shared-build/shared-compilation.d.ts +91 -0
  97. package/shared-build/shared-compilation.js +3 -0
  98. package/shared-build/signature.d.ts +52 -0
  99. package/shared-build/signature.js +1 -0
  100. package/stdio-guard.d.ts +1 -1
  101. package/swap-recycle.d.ts +21 -0
  102. package/swap-recycle.js +1 -1
  103. package/tsgo-check-plugin.d.ts +1 -1
  104. package/workspace-paths.d.ts +76 -0
  105. package/workspace-paths.js +1 -0
@@ -0,0 +1,113 @@
1
+ import type { ProjectGraph, ProjectGraphDependency, ProjectGraphProjectNode } from '@nx/devkit';
2
+ import { type ComputedProjectGraph, type NxSourceMaps, type ProjectGraphSnapshot, type ProjectGraphStore } from './project-graph-store';
3
+ import type { RunnerScript } from './serve-launch';
4
+ /** The wire format's version. A graph in any other format is refused (and the gateway computes its own). */
5
+ export declare const FORWARDED_GRAPH_FORMAT = 1;
6
+ /** The most a forwarded graph may weigh, as JSON. FoodGuard's (148 projects, every target and its provenance)
7
+ * is ~1.5 MB in this form; its full nx graph with every npm package and source map is ~9 MB. */
8
+ export declare const MAX_FORWARDED_GRAPH_BYTES: number;
9
+ /** The most projects a forwarded graph may name. */
10
+ export declare const MAX_FORWARDED_PROJECTS = 20000;
11
+ /** A guest's graph is recomputed in a child process. After one fails, the sweeper waits this long before the next. */
12
+ export declare const GUEST_GRAPH_RETRY_MS = 60000;
13
+ /**
14
+ * A workspace's project graph as one gateway hands it to another — what `graph-snapshot.js` prints.
15
+ * - `projectGraph`: the project nodes as nx computed them (targets merged, tokens resolved) and the edges
16
+ * between them. npm packages are left out: nothing the gateway plans with reads them, and they are most of
17
+ * the graph's weight.
18
+ * - `sourceMaps`: nx's provenance, `targets.*` only, one key per distinct (file, plugin) per target
19
+ * ({@link compactSourceMaps}) — enough to tell a declared target from an inferred one.
20
+ * - `files`: every declaring file (workspace-relative) → its stat signature from BEFORE nx computed.
21
+ */
22
+ export interface ForwardedProjectGraph {
23
+ format: number;
24
+ /** The workspace root the graph describes (absolute). */
25
+ root: string;
26
+ /** When the computation started (ms epoch). */
27
+ startedAt: number;
28
+ projectGraph: ForwardedGraphBody;
29
+ sourceMaps?: NxSourceMaps;
30
+ files: Record<string, string>;
31
+ }
32
+ export interface ForwardedGraphBody {
33
+ nodes: Record<string, ProjectGraphProjectNode>;
34
+ dependencies: Record<string, ProjectGraphDependency[]>;
35
+ }
36
+ /** What the gateway decided about a forwarded graph: the snapshot it keeps, or why it keeps none. */
37
+ export type GraphAdmission = {
38
+ snapshot: ProjectGraphSnapshot;
39
+ refused?: undefined;
40
+ } | {
41
+ snapshot?: undefined;
42
+ refused: string;
43
+ };
44
+ export interface GraphAdmissionContext {
45
+ /** The root of the workspace that is registering — the only workspace whose graph it may forward. */
46
+ root: string;
47
+ /** The gateway's own workspace root: the gateway computes that graph itself, and no registration replaces it. */
48
+ daemonRoot: string;
49
+ /** Roots registered under ANOTHER key — not this registrant's either. */
50
+ otherRoots: readonly string[];
51
+ /** How many bytes the graph took on the wire. */
52
+ bytes: number;
53
+ }
54
+ /** Is `root` a directory that the user this gateway runs as owns? (Under `sudo`, the invoking user's counts.) */
55
+ export declare function ownedByThisUser(root: string): boolean;
56
+ /**
57
+ * Keep a forwarded graph — or say why not. In order: its size; its format; WHOSE workspace it describes (the
58
+ * registering one's own root, never the gateway's, never one registered under another key, and a directory this
59
+ * user owns); its shape; and its fingerprint, which must cover exactly the files that declare this graph.
60
+ */
61
+ export declare function admitForwardedGraph(input: unknown, ctx: GraphAdmissionContext): GraphAdmission;
62
+ /**
63
+ * nx's source maps cut to what the gateway reads ({@link ForwardedProjectGraph}): for each target of each project,
64
+ * the first key of every distinct (file, plugin) under `targets.<name>`. serve-launch's `inferredBy` asks whether
65
+ * ANY key under a target names a file other than a declaring one, and which comes first — this keeps both answers
66
+ * (the set of provenances under each target's prefix is unchanged, and so is the first non-declaring one in order).
67
+ */
68
+ export declare function compactSourceMaps(graph: ForwardedGraphBody, maps: NxSourceMaps): NxSourceMaps;
69
+ /** The graph's project nodes and the edges between them — the npm packages and the edges to them left out. */
70
+ export declare function projectsOnly(graph: ProjectGraph): ForwardedGraphBody;
71
+ /**
72
+ * The workspace at `root`'s graph in the forwarded form, computed in THIS process — which must be rooted at it
73
+ * (graph-snapshot.ts). The fingerprint follows the store's rule: a declaring file written after the computation
74
+ * began is recorded as `?`, a signature nothing matches, so the snapshot can only err towards stale.
75
+ */
76
+ export declare function snapshotWorkspaceGraph(root: string, compute?: () => Promise<ComputedProjectGraph>): Promise<ForwardedProjectGraph>;
77
+ /** The built `graph-snapshot.js` at the package root, or the `.ts` beside this source tree (serve-launch's `serveRunnerScript` rule). */
78
+ export declare function graphSnapshotScript(baseDir?: string): RunnerScript | undefined;
79
+ /**
80
+ * The environment a guest's graph is computed in: what a guest service's `nx run` is started with (the daemon
81
+ * workspace's dotenv keys scrubbed, pod-env.ts; no nx daemon; plugins in-process), minus the variables that
82
+ * describe the DAEMON's own nx task or point nx at its data directory.
83
+ */
84
+ export declare function guestGraphEnv(base: NodeJS.ProcessEnv, daemonRoot: string, guestRoot: string): NodeJS.ProcessEnv;
85
+ /** What one child computation printed: the graph as parsed JSON, and its size. */
86
+ export interface ChildGraphOutput {
87
+ graph: unknown;
88
+ bytes: number;
89
+ }
90
+ /**
91
+ * Run `graph-snapshot.js` in a child process rooted at `root` and read what it wrote — a file, not stdout, which
92
+ * nx and its plugins may print to. Nothing over {@link MAX_FORWARDED_GRAPH_BYTES} is read; the size is returned
93
+ * for the admission to refuse.
94
+ */
95
+ export declare function forwardedGraphInChild(root: string, env: NodeJS.ProcessEnv, script?: RunnerScript | undefined): Promise<ChildGraphOutput>;
96
+ export interface GuestGraphStoreParams {
97
+ /** The registered workspace's key — for the log lines. */
98
+ wsKey: string;
99
+ /** Its root. The store keeps it as the filesystem names it, like the admitted snapshot. */
100
+ root: string;
101
+ /** What the admission decided about the graph its registration forwarded; absent when it forwarded none. */
102
+ admission?: GraphAdmission;
103
+ /** The admission context for every later computation (its `bytes` is replaced by each output's). */
104
+ context: Omit<GraphAdmissionContext, 'bytes'>;
105
+ /** The guest's graph computation. Default: {@link forwardedGraphInChild} in {@link guestGraphEnv}. */
106
+ compute?: () => Promise<ChildGraphOutput>;
107
+ }
108
+ /**
109
+ * A registered workspace's store: seeded with the graph its registration forwarded (when admitted), and
110
+ * refreshed — when a declaring file changes, or when it has none — by a child computation that passes the same
111
+ * admission. A failed or refused computation is retried no sooner than {@link GUEST_GRAPH_RETRY_MS}.
112
+ */
113
+ export declare function createGuestGraphStore(params: GuestGraphStoreParams, daemonEnv?: NodeJS.ProcessEnv): ProjectGraphStore;
@@ -0,0 +1,2 @@
1
+ "use strict";var _=Object.defineProperty;var u=(e,r)=>_(e,"name",{value:r,configurable:!0});var l=Object.defineProperty,f=u((e,r)=>l(e,"name",{value:r,configurable:!0}),"f");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GUEST_GRAPH_RETRY_MS=exports.MAX_FORWARDED_PROJECTS=exports.MAX_FORWARDED_GRAPH_BYTES=exports.FORWARDED_GRAPH_FORMAT=void 0,exports.ownedByThisUser=ownedByThisUser,exports.admitForwardedGraph=admitForwardedGraph,exports.compactSourceMaps=compactSourceMaps,exports.projectsOnly=projectsOnly,exports.snapshotWorkspaceGraph=snapshotWorkspaceGraph,exports.graphSnapshotScript=graphSnapshotScript,exports.guestGraphEnv=guestGraphEnv,exports.forwardedGraphInChild=forwardedGraphInChild,exports.createGuestGraphStore=createGuestGraphStore;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),pod_env_1=require("./pod-env"),project_graph_store_1=require("./project-graph-store"),registration_guards_1=require("./registration-guards");exports.FORWARDED_GRAPH_FORMAT=1,exports.MAX_FORWARDED_GRAPH_BYTES=8*1024*1024,exports.MAX_FORWARDED_PROJECTS=2e4,exports.GUEST_GRAPH_RETRY_MS=6e4;const GUEST_GRAPH_TIMEOUT_MS=12e4,SIGNATURE=/^(?:-|\?|\d+(?:\.\d+)?:\d+:\d+)$/;function megabytes(e){return`${(e/(1024*1024)).toFixed(1)} MB`}u(megabytes,"megabytes"),f(megabytes,"megabytes");const isRecord=f(e=>!!e&&typeof e=="object"&&!Array.isArray(e),"isRecord");function insideWorkspace(e){return e===""||path.isAbsolute(e)||/^[a-zA-Z]:/.test(e)?!1:!path.posix.normalize(e.replace(/\\/g,"/")).split("/").includes("..")}u(insideWorkspace,"insideWorkspace"),f(insideWorkspace,"insideWorkspace");function ownedByThisUser(e){const r=fs.statSync(e,{throwIfNoEntry:!1});if(!r?.isDirectory())return!1;if(typeof process.getuid!="function")return!0;const t=process.getuid();return r.uid===t||t===0&&r.uid===Number(process.env.SUDO_UID)}u(ownedByThisUser,"ownedByThisUser"),f(ownedByThisUser,"ownedByThisUser");function relativeDeclaringFiles(e,r){return(0,project_graph_store_1.declaringFiles)(e,r).map(t=>path.relative(e,t).split(path.sep).join("/"))}u(relativeDeclaringFiles,"relativeDeclaringFiles"),f(relativeDeclaringFiles,"relativeDeclaringFiles");function shapeProblem(e){if(!isRecord(e)||!isRecord(e.nodes)||!isRecord(e.dependencies))return"it has no `nodes` and `dependencies`";const r=e.nodes,t=Object.keys(r);if(t.length>exports.MAX_FORWARDED_PROJECTS)return`it names ${t.length} projects (at most ${exports.MAX_FORWARDED_PROJECTS})`;for(const o of t){const s=r[o];if(!isRecord(s)||s.name!==o||typeof s.type!="string"||!isRecord(s.data))return`project '${o}' is not a project node`;const n=s.data.root;if(typeof n!="string"||!insideWorkspace(n))return`project '${o}' has a root outside the workspace (${String(n)})`;const p=s.data.targets;if(p!==void 0&&(!isRecord(p)||!Object.values(p).every(isRecord)))return`project '${o}' has malformed targets`}for(const[o,s]of Object.entries(e.dependencies)){if(!(o in r)||!Array.isArray(s))return`the dependencies of '${o}' are not a known project's`;for(const n of s)if(!isRecord(n)||n.source!==o||typeof n.target!="string"||!(n.target in r)||typeof n.type!="string")return`a dependency of '${o}' is malformed`}}u(shapeProblem,"shapeProblem"),f(shapeProblem,"shapeProblem");function sourceMapsProblem(e){if(e!==void 0){if(!isRecord(e))return"its source maps are not a map";for(const[r,t]of Object.entries(e)){if(!isRecord(t))return`the source map of ${r} is malformed`;for(const o of Object.values(t))if(!Array.isArray(o)||o.length!==2||o[0]!==null&&typeof o[0]!="string"||typeof o[1]!="string")return`the source map of ${r} is malformed`}}}u(sourceMapsProblem,"sourceMapsProblem"),f(sourceMapsProblem,"sourceMapsProblem");function admitForwardedGraph(e,r){const t=f(a=>({refused:a}),"refuse");if(r.bytes>exports.MAX_FORWARDED_GRAPH_BYTES)return t(`${megabytes(r.bytes)} is over the ${megabytes(exports.MAX_FORWARDED_GRAPH_BYTES)} limit`);if(!isRecord(e))return t("it is not a project graph");if(e.format!==exports.FORWARDED_GRAPH_FORMAT)return t(`its format is ${String(e.format)}, and this gateway reads ${exports.FORWARDED_GRAPH_FORMAT}`);const o=e.root;if(typeof o!="string"||!path.isAbsolute(o))return t("it does not say which workspace it describes");if(!(0,registration_guards_1.sameRoot)(o,r.root))return t(`it describes ${o}, not the registering workspace ${r.root}`);if((0,registration_guards_1.sameRoot)(r.root,r.daemonRoot))return t(`${r.root} is the gateway's own workspace \u2014 the gateway reads that graph itself`);if(r.otherRoots.some(a=>(0,registration_guards_1.sameRoot)(a,r.root)))return t(`${r.root} is registered under another key`);if(!ownedByThisUser(r.root))return t(`${r.root} is not a directory this user owns`);const s=e.startedAt;if(typeof s!="number"||!Number.isFinite(s))return t("it does not say when it was computed");const n=shapeProblem(e.projectGraph)??sourceMapsProblem(e.sourceMaps);if(n)return t(n);const p=e.projectGraph,i=e.files;if(!isRecord(i))return t("it carries no fingerprint of its declaring files");const c=(0,registration_guards_1.canonicalRoot)(r.root),h=relativeDeclaringFiles(c,p),d=Object.keys(i),g=h.filter(a=>!(a in i));return g.length>0||d.length!==h.length?t(`its fingerprint does not cover exactly the files that declare it${g.length>0?` (no ${g[0]})`:""}`):Object.values(i).every(a=>typeof a=="string"&&SIGNATURE.test(a))?{snapshot:{root:c,startedAt:s,files:new Map(h.map(a=>[path.join(c,a),i[a]])),projectGraph:{nodes:p.nodes,dependencies:p.dependencies,externalNodes:{}},...e.sourceMaps!==void 0?{sourceMaps:e.sourceMaps}:{}}}:t("its fingerprint is malformed")}u(admitForwardedGraph,"admitForwardedGraph"),f(admitForwardedGraph,"admitForwardedGraph");function compactSourceMaps(e,r){const t={};for(const o of Object.values(e.nodes)){const s=r[o.data.root];if(!s)continue;const n=new Set;for(const i of Object.keys(o.data.targets??{})){const c=`targets.${i}`,h=new Set;for(const[d,g]of Object.entries(s)){if(d!==c&&!d.startsWith(`${c}.`))continue;const a=JSON.stringify(g);h.has(a)||(h.add(a),n.add(d))}}const p={};for(const[i,c]of Object.entries(s))n.has(i)&&(p[i]=c);t[o.data.root]=p}return t}u(compactSourceMaps,"compactSourceMaps"),f(compactSourceMaps,"compactSourceMaps");function projectsOnly(e){const r={};for(const[t,o]of Object.entries(e.dependencies))t in e.nodes&&(r[t]=o.filter(s=>s.target in e.nodes));return{nodes:e.nodes,dependencies:r}}u(projectsOnly,"projectsOnly"),f(projectsOnly,"projectsOnly");async function snapshotWorkspaceGraph(e,r=()=>(0,project_graph_store_1.computeProjectGraphInProcess)(e)){const t=Date.now(),o=(0,project_graph_store_1.filesystemNow)(),s=await r(),n=projectsOnly(s.projectGraph),p={};for(const i of(0,project_graph_store_1.declaringFiles)(e,s.projectGraph)){const c=fs.statSync(i,{throwIfNoEntry:!1});p[path.relative(e,i).split(path.sep).join("/")]=c&&c.mtimeMs>o?"?":(0,project_graph_store_1.statSignature)(i)}return{format:exports.FORWARDED_GRAPH_FORMAT,root:e,startedAt:t,projectGraph:n,...s.sourceMaps?{sourceMaps:compactSourceMaps(n,s.sourceMaps)}:{},files:p}}u(snapshotWorkspaceGraph,"snapshotWorkspaceGraph"),f(snapshotWorkspaceGraph,"snapshotWorkspaceGraph");function graphSnapshotScript(e=path.join(__dirname,"../../..")){const r=path.join(e,"graph-snapshot.js");if(fs.existsSync(r))return{path:r,execArgv:[]};const t=path.join(e,"graph-snapshot.ts");if(fs.existsSync(t))try{return{path:t,execArgv:["--require",require.resolve("@swc-node/register")]}}catch{return}}u(graphSnapshotScript,"graphSnapshotScript"),f(graphSnapshotScript,"graphSnapshotScript");function guestGraphEnv(e,r,t){const o={...(0,pod_env_1.podBaseEnv)(e,r,t)};for(const s of Object.keys(o))(s.startsWith("NX_TASK_")||["NX_WORKSPACE_ROOT","NX_WORKSPACE_ROOT_PATH","NX_WORKSPACE_DATA_DIRECTORY","NX_PROJECT_GRAPH_CACHE_DIRECTORY"].includes(s))&&delete o[s];return o.NX_DAEMON="false",o.NX_ISOLATE_PLUGINS="false",o}u(guestGraphEnv,"guestGraphEnv"),f(guestGraphEnv,"guestGraphEnv");function forwardedGraphInChild(e,r,t=graphSnapshotScript()){if(!t)return Promise.reject(new Error("graph-snapshot.js is missing from @lensmcp/cluster"));const o=fs.mkdtempSync(path.join(os.tmpdir(),"lensmcp-graph-")),s=path.join(o,"graph.json");return new Promise((n,p)=>{let i="";const c=(0,node_child_process_1.spawn)(process.execPath,[...t.execArgv,t.path,s],{cwd:e,env:r,stdio:["ignore","ignore","pipe"]});c.stderr?.on("data",d=>{i.length<4096&&(i+=d.toString())});const h=setTimeout(()=>c.kill("SIGKILL"),GUEST_GRAPH_TIMEOUT_MS);h.unref(),c.on("error",d=>{clearTimeout(h),p(d)}),c.on("exit",(d,g)=>{if(clearTimeout(h),d!==0){const a=i.trim().split(`
2
+ `).pop();p(new Error(`the graph computation in ${e} exited ${g??d}${a?` \u2014 ${a}`:""}`));return}try{const a=fs.statSync(s).size;n({bytes:a,graph:a>exports.MAX_FORWARDED_GRAPH_BYTES?void 0:JSON.parse(fs.readFileSync(s,"utf8"))})}catch(a){p(a)}})}).finally(()=>fs.rmSync(o,{recursive:!0,force:!0}))}u(forwardedGraphInChild,"forwardedGraphInChild"),f(forwardedGraphInChild,"forwardedGraphInChild");function createGuestGraphStore(e,r=process.env){const{wsKey:t,admission:o,context:s}=e,n=(0,registration_guards_1.canonicalRoot)(e.root),p=e.compute??(()=>forwardedGraphInChild(n,guestGraphEnv(r,s.daemonRoot,n))),i=o===void 0?"its registration forwarded none":o.refused!==void 0?`the project graph its registration forwarded was refused: ${o.refused}`:void 0;return(0,project_graph_store_1.createProjectGraphStore)(n,async()=>{const{graph:c,bytes:h}=await p(),d=admitForwardedGraph(c,{...s,bytes:h});if(d.refused!==void 0)throw new Error(`the computed project graph was refused: ${d.refused}`);return{projectGraph:d.snapshot.projectGraph,...d.snapshot.sourceMaps?{sourceMaps:d.snapshot.sourceMaps}:{}}},{...o?.snapshot?{seed:o.snapshot}:{},...i?{error:i}:{},retryAfterFailureMs:exports.GUEST_GRAPH_RETRY_MS,onRefreshed:f(c=>console.log(`[gateway] project graph of '${t}' (${n}) refreshed \u2014 ${Object.keys(c.projectGraph.nodes).length} projects; its services start through the serve runner again`),"onRefreshed")})}u(createGuestGraphStore,"createGuestGraphStore"),f(createGuestGraphStore,"createGuestGraphStore");
@@ -49,6 +49,16 @@ export declare function viteArgPort(args: readonly string[]): number | undefined
49
49
  * taking them as arguments, because both are already settled by the time an 'exit' handler runs.
50
50
  */
51
51
  export declare function describeChildExit(child: Pick<ChildProcess, 'exitCode' | 'signalCode'>): string;
52
+ /**
53
+ * Signal a child ONLY if its spawn produced a process (issues/closed/test-infra-04).
54
+ *
55
+ * A `ChildProcess` whose spawn FAILED — its cwd is gone, its binary moved — has no pid, yet keeps its
56
+ * native handle until the `'error'` tick, and `kill()` on that handle reaches `kill(0, signal)`: the
57
+ * gateway's OWN process group (the gateway, the nx runner that launched it, the terminal job). Measured:
58
+ * a lens frontend retired in the same tick its spawn failed SIGTERMed the whole jest run, wrapper and all.
59
+ * Returns whether a signal was actually sent.
60
+ */
61
+ export declare function signalChild(child: ChildProcess, signal: NodeJS.Signals): boolean;
52
62
  export interface LensChildren {
53
63
  spawnManagedChild: SpawnChild;
54
64
  /** Boot the dashboard + MCP singletons and every `lens:true` app. Pushes the
@@ -76,7 +86,7 @@ export interface LensChildren {
76
86
  /**
77
87
  * PURE (given the probe): the first port at/after `desired` that is not claimed in-process, not `reserved`
78
88
  * (another lens app's DECLARED port — free at the OS level only because that app has rolled away from it;
79
- * stepping onto it made two apps trade ports for a day, issues/dev-gateway/05), and free at the OS level.
89
+ * stepping onto it made two apps trade ports for a day, issues/closed/dev-gateway-05), and free at the OS level.
80
90
  * Records the claim. Exported for the spec; `createLensChildren` wraps it with the log line.
81
91
  */
82
92
  export declare function claimPort(desired: number, claimed: Set<number>, isPortFree: (port: number) => Promise<boolean>, reserved?: ReadonlySet<number>): Promise<number>;
@@ -1 +1 @@
1
- "use strict";var Re=Object.defineProperty;var P=(r,E)=>Re(r,"name",{value:E,configurable:!0});var Pe=Object.defineProperty,d=P((r,E)=>Pe(r,"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.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(r,E){return new Promise(u=>{const _=net.createServer();_.unref(),_.once("error",p=>{const f=p.code??"";u(f==="EADDRINUSE"||f==="EACCES"?"in-use":"unavailable")}),_.listen(E===void 0?{port:r}:{port:r,host:E},()=>_.close(()=>u("free")))})}P(tryBind,"tryBind"),d(tryBind,"tryBind");async function probePortFree(r){for(const E of[void 0,"0.0.0.0","127.0.0.1","::1"])if(await tryBind(r,E)==="in-use")return!1;return!0}P(probePortFree,"probePortFree"),d(probePortFree,"probePortFree");function probeConnect(r,E,u){return new Promise(_=>{let p=!1;const f=net.createConnection({host:r,port:E});f.unref();const C=setTimeout(()=>v(!1),u);C.unref?.();function v(O){p||(p=!0,clearTimeout(C),f.destroy(),_(O))}P(v,"P"),d(v,"finish"),f.once("connect",()=>v(!0)),f.once("error",()=>v(!1))})}P(probeConnect,"probeConnect"),d(probeConnect,"probeConnect");async function probePortListening(r,E=1e3){return await probeConnect("127.0.0.1",r,E)?!0:probeConnect("::1",r,E)}P(probePortListening,"probePortListening"),d(probePortListening,"probePortListening");async function probeWatcherHealth(r,E=1500){try{const u=new AbortController,_=setTimeout(()=>u.abort(),E);_.unref?.();try{const p=await fetch(`http://127.0.0.1:${r}/__lensmcp/watcher-health`,{signal:u.signal});if(!p.ok)return null;const f=await p.json();if(typeof f?.lastFsSetEventAt!="number")return null;const C=Array.isArray(f.recentEvents)?f.recentEvents.filter(v=>v&&typeof v.path=="string"&&typeof v.at=="number"&&typeof v.sequence=="number"&&["add","addDir","unlink","unlinkDir"].includes(v.kind)):[];return{schemaVersion:f.schemaVersion===2?2:1,ready:f.ready!==!1,sequence:typeof f.sequence=="number"?f.sequence:0,lastFsSetEventAt:f.lastFsSetEventAt,fsSetEvents:typeof f.fsSetEvents=="number"?f.fsSetEvents:0,recentEvents:C}}finally{clearTimeout(_)}}catch{return null}}P(probeWatcherHealth,"probeWatcherHealth"),d(probeWatcherHealth,"probeWatcherHealth");const sleepUnref=d(r=>new Promise(E=>{setTimeout(E,r).unref?.()}),"sleepUnref");async function waitForPortFree(r,E,u={}){const _=u.isPortFree??probePortFree,p=Math.max(1,u.intervalMs??types_1.PORT_FREE_POLL_MS),f=Date.now()+Math.max(0,E);for(;;){if(await _(r))return!0;const C=f-Date.now();if(C<=0)return!1;await sleepUnref(Math.min(p,C))}}P(waitForPortFree,"waitForPortFree"),d(waitForPortFree,"waitForPortFree");function viteArgPort(r){const E=d(u=>Number.isInteger(u)&&u>0&&u<65536?u:void 0,"valid");for(let u=0;u<r.length;u+=1){const _=r[u];if(_==="--port")return E(Number(r[u+1]));if(_.startsWith("--port="))return E(Number(_.slice(7)))}}P(viteArgPort,"viteArgPort"),d(viteArgPort,"viteArgPort");function describeChildExit(r){return r.signalCode?`killed by ${r.signalCode}`:r.exitCode===null?"exit status unknown":`exit code ${r.exitCode}`}P(describeChildExit,"describeChildExit"),d(describeChildExit,"describeChildExit");async function claimPort(r,E,u,_){let p=r;for(;E.has(p)||_?.has(p)||!await u(p);)p+=1;return E.add(p),p}P(claimPort,"claimPort"),d(claimPort,"claimPort");function createLensChildren(r,E,u,_){const{emit:p}=E,f=_?.isPortFree??probePortFree,C=(u.ports?.length?u.ports:[443]).join(","),v=_?.probeWatcherHealth??probeWatcherHealth,O=[],K=new Map,T=new Map,F=new Map,Y=new Map,B=new Set;let D;const m=new Map,$=new Set,Q=new Set,q=new Set,N=new Set,L=new Map,j=new WeakMap,W=new Set,Z=d((e,s,n)=>{if(W.has(e))return;W.add(e);const t=setInterval(()=>{(async()=>{if(r.stopped()||$.has(e)){clearInterval(t),W.delete(e);return}await f(s)&&(clearInterval(t),W.delete(e),console.log(`[gateway] lens child ${e}: :${s} freed \u2014 respawning.`),n())})().catch(()=>{})},types_1.LENS_PARKED_PORT_REPROBE_MS);t.unref?.()},"parkUntilPortFree"),I=d(async(e,s,n)=>{const t=await claimPort(e,s,f,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,s,n)=>{const t=n?.LENSMCP_FRONTEND_KEY,o=Number(n?.LENSMCP_FRONTEND_GENERATION);if(!t||!Number.isFinite(o))return;const a=[...L.get(t)?.generations??[]].find(i=>i.generation===o);a&&(e.endsWith(":vite")?a.vite=s:e.endsWith(":browser-capture")&&(a.capture=s))},"attachRespawnedGenerationChild"),G=d(e=>{if(!(e.exitCode!==null||e.signalCode!==null)){Q.add(e);try{e.kill("SIGTERM")}catch{}setTimeout(()=>{if(e.exitCode===null&&e.signalCode===null)try{e.kill("SIGKILL")}catch{}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}},"retireChild"),de=d((e,s)=>{const n=m.get(e);!n||n===s||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??"?"}).`),G(n))},"retireLabelHolder"),k=d((e,s,n,t,o=!1,a,i,l)=>{const h=Date.now(),c=(0,node_child_process_1.spawn)(s,n,{cwd:i??r.root,env:{...(0,pod_env_1.podBaseEnv)(process.env,r.root,i??r.root),...t??{},LENSMCP_PARENT_PID:String(process.pid)},stdio:"inherit"});O.push(c),de(e,c),m.set(e,c),ce(e,c,t);const S=e==="vite"||e.endsWith(":vite"),M=typeof t?.LENSMCP_EVENT_FILE=="string"?t.LENSMCP_EVENT_FILE:void 0,y=M&&M!==r.eventFile?M:void 0,w=S?viteArgPort(n):l;S&&T.set(c,{key:t?.LENSMCP_FRONTEND_KEY??`${i??r.root}:${t?.LENSMCP_PROJECT??"app"}`,project:t?.LENSMCP_PROJECT??"app",workspaceRoot:i??r.root,spawnedAt:h,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 g=e==="lens-mcp"?"lens-mcp":e==="lens-dashboard"||e.startsWith("lens-dashboard:")?"lens-dashboard":void 0;return g&&w!==void 0&&(p("info",`lens child starting: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-starting",project:g,label:e,pid:c.pid,port:w},void 0,y),(async()=>{const b=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<b;){if(c.exitCode!==null||c.signalCode!==null||$.has(e))return;if(await probePortListening(w,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS))){p("info",`lens child up: ${g} (:${w})`,`cluster-lens-child:${g}`,{kind:"lens-child-up",project:g,label:e,pid:c.pid,port:w},void 0,y);return}await sleepUnref(150)}})().catch(()=>{})),c.on("exit",(b,H)=>{const se=O.indexOf(c);se>=0&&O.splice(se,1),m.get(e)===c&&m.delete(e);const A=T.get(c);if(T.delete(c),Q.delete(c))return;const ve=$.delete(e);if(r.stopped()||ve||(g?p("warning",`lens child exited: ${g}`,`cluster-lens-child:${g}`,{kind:"lens-child-exit",project:g,label:e,pid:c.pid,code:b,signal:H},void 0,y):A&&p("warning",`lens frontend exited: ${A.project}`,`cluster-lens-child:${A.project}`,{kind:"lens-child-exit",project:A.project,label:e,pid:c.pid,code:b,signal:H,generation:A.generation,port:A.port,ranMs:Date.now()-h},void 0,y),A&&Ee(c,A)))return;const ae=Date.now()-h,R=K.get(e)??{attempts:0,portConflicts:0};(async()=>{const Le=w!==void 0&&!await f(w),ie=(0,lifecycle_1.isPortConflictExit)({code:b,signal:H,ranMs:ae,portHeld:Le},{fastFailMs:types_1.CHILD_START_FAIL_MS});if(ie&&R.portConflicts<types_1.MAX_PORT_CONFLICT_RETRIES)R.portConflicts+=1,K.set(e,R),console.warn(`[gateway] lens child ${e} lost the :${w} bind race (EADDRINUSE, code=${b}); 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,K.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}),Z(e,w,()=>k(e,s,n,t,o,a,i,l));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,K.set(e,R),console.warn(`[gateway] lens child ${e} exited (code=${b} sig=${H}); auto-healing in ${Math.round(le/1e3)}s (attempt ${R.attempts}).`),await sleepUnref(le)}r.stopped()||$.has(e)||(w!==void 0&&!await waitForPortFree(w,types_1.PORT_FREE_WAIT_MS,{isPortFree:f,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.`),!(r.stopped()||$.has(e))&&k(e,s,n,t,o,a,i,l))})().catch(()=>{})}),c},"spawnManagedChild"),ee=d(e=>{const s=`${e.wsKey}:${e.project}`,n=L.get(s);if(n)return n;const t={key:s,...e,sourceDirs:e.frontend.sourceRoots?.length?e.frontend.sourceRoots:[e.projectRoot],generation:0,state:"starting",generations:new Set,disposed:!1};L.set(s,t);for(const o of e.routes)o.lens&&(o.lens.workspaceKey??=e.wsKey,j.set(o.lens,t));return e.frontend.workspaceKey??=e.wsKey,j.set(e.frontend,t),t},"registerFrontend"),pe=d(e=>{if(!types_1.LENS_WATCH_SCOPE_ENABLED)return{};const s=path.resolve(e.projectRoot),n=e.sourceDirs.map(t=>path.resolve(t)).filter(t=>t!==s);return n.length>0?{LENSMCP_WATCH_DIRS:n.join(path.delimiter)}:{}},"lensWatchScopeEnv"),te=d((e,s,n)=>{const t=++e.generation,o=`${e.key}:g${t}:`,a=`${o}vite`,i=`${o}browser-capture`;$.delete(a),$.delete(i);const l=d((S,M,y,w,g,b)=>k(S,M,y,w,g,b,e.workspaceRoot),"spawnInRoot"),h=(0,lens_frontend_1.spawnLensFrontend)({projectRoot:e.projectRoot,workspaceRoot:e.workspaceRoot,project:e.project,eventFile:e.eventFile,port:s,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")},l),c={ownerKey:e.key,generation:t,port:s,labelPrefix:o,vite:h?.vite??m.get(a),capture:m.get(i)};return e.generations.add(c),c},"spawnFrontendGeneration"),x=d((e,s)=>{if(!e)return;L.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)$.add(t),o&&G(o);s&&N.delete(e.port)},"terminateGeneration"),ue=d(async e=>{const s=Date.now()+types_1.LENS_REPLACEMENT_READY_MS;for(;!r.stopped()&&Date.now()<s;){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 v(e.port);if(!t||t.ready)return!0}await sleepUnref(150)}return!1},"waitForGenerationReady"),V=d(async e=>{const s=e?.vite;return!!(e&&s&&s.exitCode===null&&s.signalCode===null&&await probePortListening(e.port,Math.min(500,types_1.LENS_PROBE_TIMEOUT_MS)))},"generationIsServing"),U=d(e=>!e.disposed&&!r.stopped()&&L.get(e.key)===e,"frontendIsCurrent"),z=d((e,s)=>{if(!U(e))return Promise.resolve(!1);if(e.replacement)return e.replacement;const n=e.active;e.state=n?"replacing":"starting",e.reason=s,e.startedAt=Date.now();const t=(async()=>{let o,a;const i=Date.now();try{const l=new Set;for(const g of L.values())g!==e&&l.add(g.desiredPort);const h=n&&n.port===e.desiredPort?`g${n.generation}`:void 0,c=await I(e.desiredPort,N,{reserved:l,heldBy:h});if(a=c,!U(e))return N.delete(c),!1;o=te(e,c,"replacement"),e.candidate=o;const S=await V(n);console.log(`[gateway] lens handoff start: ${e.key} g${n?.generation??0}\u2192g${o.generation} :${n?.port??"down"}\u2192:${c} reason=${s}`),p("info",`lens handoff starting: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-start",wsKey:e.wsKey,project:e.project,reason:s,generation:o.generation,oldPort:n?.port,newPort:c,oldServing:S},void 0,e.eventFile===r.eventFile?void 0:e.eventFile);const M=await ue(o);if(!U(e))return x(o,!0),e.candidate=void 0,!1;if(!M){x(o,!0),e.candidate=void 0;const g=await V(n);return e.state=g?"ready":"failed",g&&(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:s,generation:o.generation,port:c,durationMs:Date.now()-i,oldKept:g},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}for(const g of e.routes)g.target=`http://localhost:${o.port}`,g.lens&&(g.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?T.get(o.vite):void 0;y&&(y.role="active",y.listening=!0);const w=(0,hmr_reload_1.broadcastFullReload)(r,e.key);if(console.log(`[gateway] lens handoff ready: ${e.key} g${o.generation} :${o.port} in ${Date.now()-i}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:s,generation:o.generation,oldPort:n?.port,newPort:o.port,durationMs:Date.now()-i,reloadedTabs:w},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),n){const g=`${n.labelPrefix}browser-capture`;$.add(g),n.capture&&G(n.capture),setTimeout(()=>x(n,!0),types_1.LENS_REPLACEMENT_DRAIN_MS).unref?.()}return!0}catch(l){x(o,!0),!o&&a!==void 0&&N.delete(a),e.candidate=void 0;const h=await V(n);e.state=h?"ready":"failed",h&&(e.reason=void 0,e.startedAt=void 0);const c=l.message;return console.error(`[gateway] lens handoff failed: ${e.key}: ${c}`),p("warning",`lens handoff failed: ${e.project}`,`cluster-lens-app:${e.project}`,{kind:"lens-handoff-failed",wsKey:e.wsKey,project:e.project,reason:s,generation:o?.generation??e.generation,port:o?.port??a,durationMs:Date.now()-i,oldKept:h,error:c},void 0,e.eventFile===r.eventFile?void 0:e.eventFile),!1}finally{e.replacement=void 0}})();return e.replacement=t,t},"rollFrontend"),oe=d((e,s)=>{e.active=te(e,s,"active"),e.state="starting",e.startedAt=Date.now()},"spawnInitialFrontend"),Ee=d((e,s)=>{const n=L.get(s.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${s.generation} :${s.port} (${describeChildExit(e)}); starting an alternate-port replacement.`),z(n,"child-exit")),!0)},"handleManagedViteExit");r.lensSupervisor=types_1.LENS_RESTART_MODE==="rolling"?{status(e){const s=j.get(e);return s?{state:s.state,generation:s.candidate?.generation??s.active?.generation??s.generation,reason:s.reason,startedAt:s.startedAt}:void 0},recover(e,s){const n=j.get(e);return n?z(n,s):Promise.resolve(!1)}}:void 0;let X=!1,ne=0;const fe=d(()=>{X||Date.now()-ne<types_1.POD_STALE_SCAN_MS||(X=!0,ne=Date.now(),(async()=>{for(const e of[...L.values()]){if(r.stopped())return;const s=Date.now(),n=(0,scope_1.readSourceSetOptions)(e.workspaceRoot),t=await(0,lifecycle_1.sourceSetSnapshot)(e.workspaceRoot,e.sourceDirs,n.exclude,{ignoreTests:n.ignoreTests});if(L.get(e.key)!==e)continue;const o=e.sourceSnapshot,a=e.sourceScanStartedAt;if(e.sourceSnapshot=t,e.sourceScanStartedAt=s,!o)continue;const i=Date.now(),l=(0,lifecycle_1.diffSourceSets)(o,t,i,(0,lifecycle_1.sourceChangeWindowMs)(i,a,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS));l&&(e.sourceChange=l,p("info",`source file set changed for ${e.key}`,`cluster-lens-app:${e.project}`,{kind:"source-set-changed",scoped:!0,wsKey:e.wsKey,project:e.project,added:l.added,removed:l.removed,truncated:l.truncated},void 0,e.eventFile===r.eventFile?void 0:e.eventFile))}})().catch(()=>{}).finally(()=>{X=!1}))},"scanFrontendSourceSets");let J=!1;const he=d(async(e,s)=>{if(!J){J=!0;try{for(const[n,t]of[...T]){if(r.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 l=L.get(t.key);l?.active?.vite===n&&(l.state="ready",l.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(!s)continue;t.probeFails+=1;const a=(0,lifecycle_1.lensChildWedged)({spawnedAt:t.spawnedAt,probeFails:t.probeFails,hasListened:t.listening},F.get(t.key)??0,Date.now(),e);if(!a)continue;const i=t.listening?"its listener VANISHED (was up, now refusing)":`it NEVER bound within the ${Math.round(o/1e3)}s boot allowance`;F.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 ${i} \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);try{n.kill("SIGKILL")}catch{}}}finally{J=!1}}},"probeLensChildren"),ge=d(()=>{if(D||!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};D=setInterval(()=>{if(r.stopped()||T.size===0||(re(),!types_1.POD_RECYCLE_ENABLED))return;fe();const s=Date.now();for(const[n,t]of T){const o=L.get(t.key),a=o?.sourceChange?.changedAt??0,i=F.get(t.key)??0;(0,lifecycle_1.lensFrontendStale)(t.spawnedAt,i,a,s,e)&&((Y.get(t.key)??0)>=a||B.has(t.key)||(B.add(t.key),(async()=>{try{const l=o?.sourceChange,h=l&&!l.truncated?[...l.added,...l.removed].map(y=>path.resolve(t.workspaceRoot,y)):[],c=types_1.LENS_WATCHER_PROBE_ENABLED&&t.port!==void 0?(0,lifecycle_1.lensWatcherVerdict)(await v(t.port),a,l?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,h):"unknown";if(c==="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(c==="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;F.set(t.key,Date.now());const S=L.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 ${c==="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:c,added:l?.added,removed:l?.removed},void 0,t.bus),await z(S,"stale-source-set");return}t.listening=!1,console.log(`[gateway] zombie recycle: lens app ${t.project} (stale-source-set) \u2014 watcher ${c==="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:c},void 0,t.bus);const M=(0,hmr_reload_1.broadcastFullReload)(r,t.key);M>0&&console.log(`[gateway] \u2192 told ${M} open tab(s) to reload (stale optimize-dep generation)`);try{n.kill("SIGTERM")}catch{}setTimeout(()=>{if(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.`);try{n.kill("SIGKILL")}catch{}}},types_1.LENS_RECYCLE_KILL_GRACE_MS).unref?.()}finally{B.delete(t.key)}})()))}},Math.min(types_1.POD_STALE_SCAN_MS,15e3)),D.unref?.()},"startLensSweeper"),we=d(async()=>{const e=[...new Map(r.routes.filter(t=>t.lens).map(t=>[t.project,t.lens])).values()],s=u.dashboard!==!1,n=u.mcp!==!1&&e.length>0;if(s){const t=(0,lens_frontend_1.findDashboardBundle)(r.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)(r.root),a=await I(u.dashboardPort??o.dashboardPort,q),i=u.lensHost??"lensmcp.local",l=o.basePath;r.registry.appendRoutes(r.wsKey,[{host:i,prefix:l,project:observability_1.LENS_DASHBOARD_PROJECT,target:`http://localhost:${a}`}]),r.rebuildRoutes(),(0,workspace_registry_1.registerWorkspace)({key:o.key,root:r.root,base:l,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://${i}${l}/)`),p("info",`lens dashboard up: ${i}${l} \u2192 :${a}`,"cluster-lens-dashboard",{kind:"lens-dashboard-up",host:i,port:a,base:l}),k("lens-dashboard",(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",o.key,"dashboard")),[t],{LENSMCP_EVENT_FILE:r.eventFile,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:l,LENSMCP_GATEWAY_PORTS:C},!1,void 0,void 0,a)}}if(n){const t=(0,lens_frontend_1.findMcpBundle)(r.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)(r.root),a=Number(process.env.LENSMCP_PORT??o.mcpHttpPort),i=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:r.eventFile,LENSMCP_PROCESS_ROLE:"mcp",LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:String(a),LENSMCP_WORKSPACE_KEY:o.key,LENSMCP_WORKSPACE_ROOT:r.root},!1,void 0,void 0,a)},"spawnMcp");await f(a)?i():(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,i))}}for(const t of e){if(t.started)continue;t.started=!0;const o=t.port,a=(r.registry.get(r.wsKey)?.routes??r.routes).filter(h=>h.lens?.project===t.project),i=await I(t.port,N);for(const h of a)h.lens&&(h.lens.port=i),h.target=`http://localhost:${i}`;t.port=i;const l=ee({wsKey:r.wsKey,project:t.project,workspaceRoot:r.root,projectRoot:t.projectRoot,eventFile:r.eventFile,routes:a,frontend:t,desiredPort:o});try{oe(l,i),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(h){N.delete(i),l.state="failed",console.error(`[gateway] lens app ${t.project}: ${h.message}`)}}ge()},"bootSingletonsAndApps"),_e=d(async e=>{if(u.dashboard===!1)return;const s=(0,lens_frontend_1.findDashboardBundle)(r.root);if(!s)return;const n=(0,scope_1.readLensScope)(e.root),t=u.lensHost??"lensmcp.local",o=n.basePath,a=await I(n.dashboardPort,q),i=`lens-dashboard:${e.wsKey}`,l=path.join(e.root,".lensmcp","events.jsonl");return $.delete(i),r.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(i,(0,process_name_1.namedExecPath)((0,process_name_1.processName)("lensmcp",e.wsKey,"dashboard")),[s],{LENSMCP_EVENT_FILE:l,LENSMCP_PROCESS_ROLE:"dashboard",LENSMCP_DASHBOARD_PORT:String(a),LENSMCP_DASHBOARD_BASE:o,LENSMCP_WS_KEY:e.wsKey,LENSMCP_GATEWAY_PORTS:C},!1,void 0,e.root,a),()=>{$.add(i),q.delete(a),(0,workspace_registry_1.unregisterWorkspace)(e.wsKey);const h=m.get(i);h&&G(h),m.delete(i)}},"hostWorkspaceDashboard"),Se=d(async e=>{const s=new Map;for(const o of e.routes){if(!o.lens)continue;const a=s.get(o.lens.project)??[];a.push(o),s.set(o.lens.project,a)}if(s.size===0)return()=>{};const n=path.join(e.root,".lensmcp","events.jsonl"),t=[];for(const[o,a]of s){const i=a[0].lens;if(i.started)continue;const l=await I(i.port,N),h=i.port;for(const S of a)S.lens.started=!0,S.lens.port=l,S.target=`http://localhost:${l}`;const c=ee({wsKey:e.wsKey,project:o,workspaceRoot:e.root,projectRoot:i.projectRoot,eventFile:n,routes:a,frontend:i,desiredPort:h});t.push(c),console.log(`[gateway] hosting lens app '${o}' of '${e.wsKey}' \u2192 vite :${l}`);try{oe(c,l),p("info",`lens app started: ${e.wsKey}/${o} (vite :${l})`,`cluster-lens-app:${e.wsKey}:${o}`,{kind:"lens-app-up",project:o,port:l},void 0,n)}catch(S){N.delete(l),c.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])x(a,!0);for(const a of o.routes)a.lens&&j.delete(a.lens);j.delete(o.frontend),F.delete(o.key),Y.delete(o.key),B.delete(o.key),L.delete(o.key)}}},"hostWorkspaceApps"),ye=d(async(e=2500)=>{D&&(clearInterval(D),D=void 0);const s=[...O];for(const o of s)try{o.kill("SIGTERM")}catch{}const n=d(o=>o.exitCode===null&&o.signalCode===null,"alive"),t=Date.now()+e;for(;Date.now()<t&&s.some(n);)await new Promise(o=>setTimeout(o,100));for(const o of s)if(n(o))try{o.kill("SIGKILL")}catch{}},"reapAll"),re=d(()=>he({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}}P(createLensChildren,"createLensChildren"),d(createLensChildren,"createLensChildren");
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");
@@ -4,7 +4,9 @@
4
4
  * pods' serverless lifecycle (eager/minPods/maxPods/idleKillSec).
5
5
  */
6
6
  import { spawn, type ChildProcess } from 'node:child_process';
7
+ import * as net from 'node:net';
7
8
  import { probePodAlive } from './pod-probe';
9
+ import { type SharedBuildRelay } from './shared-build';
8
10
  import type { GatewayRuntime, LensWatcherHealth, PodWatcherTestimony, PodWatcherVerdict, ServiceCtl, SockPool, SourceSetChange } from './types';
9
11
  import type { Observability } from './observability';
10
12
  /** Bounded, async, non-blocking walk of the workspace source tree → a signature of the file SET
@@ -21,7 +23,7 @@ import type { Observability } from './observability';
21
23
  *
22
24
  * It used to post to `localhost:<cluster.port>`. That port is the same in every checkout of a repo,
23
25
  * so with two of them up it reached whichever held it — and under a gateway the devserver no
24
- * longer takes pool commands on the port at all (issues/foodguard/35). The socket sits in this
26
+ * longer takes pool commands on the port at all (issues/closed/foodguard-35). The socket sits in this
25
27
  * service's own pool dir, so it cannot be anyone else's. The URL remains for a service that
26
28
  * declares an `adminUrl`, and for a devserver that has no control socket.
27
29
  */
@@ -30,17 +32,32 @@ export declare function scaleTarget(svc: Pick<ServiceCtl, 'pool' | 'decl'>, exis
30
32
  } | {
31
33
  url: string;
32
34
  } | undefined;
35
+ /** Where {@link scaleTarget} points a scale POST. */
36
+ export type ScaleTarget = NonNullable<ReturnType<typeof scaleTarget>>;
33
37
  /**
34
- * Is this nx runner command line one of OURS — a runner started from `root`?
38
+ * Is `hostname` (a URL's `hostname`, IPv6 brackets allowed) a LOOPBACK name or address: `localhost`,
39
+ * `*.localhost`, `127.0.0.0/8` or `::1`? Nothing else qualifies — not a private range, not a name that
40
+ * merely resolves to loopback today, not an IPv4-mapped form — so an unrecognised host keeps TLS on.
41
+ */
42
+ export declare function isLoopbackHostname(hostname: string): boolean;
43
+ /**
44
+ * Is this runner command line one of OURS — a runner started from `root`?
35
45
  *
36
46
  * It used to be `cmd.includes(root)`. A checkout at `<root>-gate1`, or a worktree nested at
37
47
  * `<root>/.claude/worktrees/x`, CONTAINS the main root as text — so every time the main workspace
38
48
  * respawned a service it SIGKILLed the same-named runner of the other checkout
39
- * (issues/foodguard/35). The runner's own nx entry is the identity: `<root>/node_modules/nx/…`.
40
- * A runner with no nx path in its argv (the yarn-shim fallback) cannot be told apart by path and is
41
- * bounded by the target match alone, as before.
49
+ * (issues/closed/foodguard-35). An nx runner's own nx entry is the identity: `<root>/node_modules/nx/…`;
50
+ * a serve runner names its root as its last argument (serve-launch.ts). An nx runner with no nx path in
51
+ * its argv (the yarn-shim fallback) cannot be told apart by path and is bounded by the target match
52
+ * alone, as before.
42
53
  */
43
54
  export declare function isOwnRunnerCommand(cmd: string, root: string): boolean;
55
+ /**
56
+ * Is this `ps` command line a runner of `target` (`<project>:<target>`) that belongs to `root` — the
57
+ * `nx run` client (direct nx entry or the yarn shim) or the gateway's serve runner? `run <target>` must
58
+ * stand as words, so `auth:serve-hmr` never matches a runner of `auth:serve-hmr2`.
59
+ */
60
+ export declare function isRunnerOf(cmd: string, target: string, root: string): boolean;
44
61
  export interface SourceSetSnapshot {
45
62
  signature: string;
46
63
  paths: Set<string>;
@@ -55,7 +72,7 @@ export interface SourceSetSnapshot {
55
72
  * wrote flipped the set — the three `pod-refresh` events that survived rotation on foodguard (2026-09-06)
56
73
  * were ALL specs (`shared/contracts/src/haccp/__tests__/zz-r3probe.spec.ts` rolled both lens vites;
57
74
  * `web/apps/dashboard/src/i18n/knowledge-admin-registry.spec.ts` rolled the dashboard), and the same
58
- * signal scaled all six backend pods to zero (issues/dev-gateway/01).
75
+ * signal scaled all six backend pods to zero (issues/closed/dev-gateway-01).
59
76
  */
60
77
  export declare function isTestSourcePath(relPath: string): boolean;
61
78
  export interface SourceSetScanOptions {
@@ -63,6 +80,26 @@ export interface SourceSetScanOptions {
63
80
  * `sourceSet.ignoreTests: false` restores count-everything. */
64
81
  ignoreTests?: boolean;
65
82
  }
83
+ /**
84
+ * ONE pass of source-set scanning over a workspace root (issues/closed/dev-gateway-16).
85
+ *
86
+ * The sweeper takes a workspace-wide snapshot and then one SCOPED snapshot per managed project. Taken
87
+ * independently, every one re-ran `git ls-files` over the whole repo and re-walked its directories — and
88
+ * with an nx graph each project's scope (its own root plus its dependency roots) is distinct, so "one scan
89
+ * per bucket" was one per PROJECT: N+1 full-repo git runs and N re-walks of the same shared libraries,
90
+ * every period, with nothing changed (foodguard: 13 of each per 30 s).
91
+ *
92
+ * A pass reads git's ignore set ONCE and walks each directory at most once, and a scope directory the
93
+ * workspace-wide walk already ENTERED is read straight out of that walk's file list — the same rules over
94
+ * the same entries, so the same answer. Only a scope dir the wide walk never entered (under an excluded or
95
+ * ignored name, a symlink, one created after the walk) is walked on its own. Make one per scan; it is a
96
+ * snapshot of a moment and must never be kept across scans.
97
+ */
98
+ export interface SourceScanPass {
99
+ /** Exactly what {@link sourceSetSnapshot} returns for these `scopeDirs`, without repeating this pass's work. */
100
+ snapshot(scopeDirs?: readonly string[]): Promise<SourceSetSnapshot>;
101
+ }
102
+ export declare function sourceScanPass(root: string, extraExclude?: ReadonlySet<string>, opts?: SourceSetScanOptions): SourceScanPass;
66
103
  export declare function sourceSetSnapshot(root: string, scopeDirs?: readonly string[], extraExclude?: ReadonlySet<string>, opts?: SourceSetScanOptions): Promise<SourceSetSnapshot>;
67
104
  export declare function sourceSetSignature(root: string, scopeDirs?: readonly string[], extraExclude?: ReadonlySet<string>, opts?: SourceSetScanOptions): Promise<string>;
68
105
  /** Exact, bounded evidence for a source-file-set transition. `windowMs` (see {@link SourceSetChange.windowMs})
@@ -241,8 +278,11 @@ export interface ServiceLayer {
241
278
  markUp(svc: ServiceCtl, how: 'adopted' | 'observed'): boolean;
242
279
  /** Start the idle sweeper; returns the interval (already `.unref()`'d). */
243
280
  startSweeper(): NodeJS.Timeout;
244
- /** Rolling recycle: standby generation first, retire the old once the new answers (issues/dev-gateway/09). */
281
+ /** Rolling recycle: standby generation first, retire the old once the new answers (issues/closed/dev-gateway-09). */
245
282
  rollPod(svc: ServiceCtl, reason: string): Promise<boolean>;
283
+ /** Stop the build host of the workspace at `root` — one that unregistered — or, with no root, every build host
284
+ * (shared-build.ts): after the service trees, at gateway stop. */
285
+ stopBuildHost(root?: string): Promise<void>;
246
286
  }
247
287
  /** Injectable seams for the process-level paths (unit tests stub them; real spawns are workspace-dependent). */
248
288
  export interface ServiceLayerDeps {
@@ -256,8 +296,14 @@ export interface ServiceLayerDeps {
256
296
  rollReadyMs?: number;
257
297
  /** Poll interval while waiting for the standby. Default 750ms. */
258
298
  rollPollMs?: number;
299
+ /** Rolls allowed in flight at once across every service. Default {@link POD_MAX_CONCURRENT_ROLLS}; `0` = no cap. */
300
+ maxConcurrentRolls?: number;
259
301
  /** Skip the nx foreign-runner reap before a spawn (tests). */
260
302
  reapForeign?: boolean;
303
+ /** DNS for an `adminUrl` scale POST. Default the OS resolver; tests point a non-loopback name at a local server. */
304
+ scaleLookup?: net.LookupFunction;
305
+ /** The shared compiler's relay. Default one for this runtime ({@link createSharedBuildRelay}). */
306
+ sharedBuild?: SharedBuildRelay;
261
307
  }
262
308
  export declare function createServiceLayer(rt: GatewayRuntime, obs: Observability, deps?: ServiceLayerDeps): ServiceLayer;
263
309
  /**
@@ -266,7 +312,7 @@ export declare function createServiceLayer(rt: GatewayRuntime, obs: Observabilit
266
312
  * The `.owner` sidecar used to be the whole answer, and it is an ordinary file at a predictable
267
313
  * `$TMPDIR/<workspace-key>/<service>-devserver/` path. Another local user could pre-create that
268
314
  * hierarchy, bind a fake socket, and write ANY pid into the sidecar — including the gateway's own —
269
- * and the gateway would signal it with the victim's authority (issues/codex-security/18).
315
+ * and the gateway would signal it with the victim's authority (issues/closed/codex-security-18).
270
316
  *
271
317
  * The pod's SELF-REPORTED pid is the better witness: it came from the process actually listening on
272
318
  * that socket, over that socket, in an exchange whose key fingerprint the caller has just verified.
@@ -1,3 +1,3 @@
1
- "use strict";var ge=Object.defineProperty;var w=(t,n)=>ge(t,"name",{value:n,configurable:!0});var fe=Object.defineProperty,i=w((t,n)=>fe(t,"name",{value:n,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isTestSourcePath=isTestSourcePath,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),scope_1=require("./scope"),pod_env_1=require("./pod-env"),pod_probe_1=require("./pod-probe"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),pod_output_1=require("./pod-output"),types_1=require("./types"),exit_reason_1=require("../../../exit-reason"),process_name_1=require("../../../process-name");function eastWestEnv(t,n){const a={},l=t.serviceKeys[(0,service_keys_1.serviceKeyId)(n.wsKey,n.project)];if(l&&(a.LENSMCP_SERVICE_KEY=l),t.https){const f=require("../../../basic-ssl").caCertPath();fs.existsSync(f)&&(a.NODE_EXTRA_CA_CERTS=f)}return a}w(eastWestEnv,"eastWestEnv"),i(eastWestEnv,"eastWestEnv");const sleep=i(t=>new Promise(n=>setTimeout(n,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let n=2166136261;for(let a=0;a<t.length;a+=1)n^=t.charCodeAt(a),n=Math.imul(n,16777619);return n>>>0}w(fnv1a,"fnv1a"),i(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:n}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(n.split("\0").filter(Boolean))}catch{return new Set}}w(gitIgnoredPaths,"gitIgnoredPaths"),i(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,n=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const a=path.join(t.pool.dir,"parent.sock");return n(a)?{socketPath:a}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}w(scaleTarget,"scaleTarget"),i(scaleTarget,"scaleTarget");function isOwnRunnerCommand(t,n){return t.includes("/node_modules/nx/")?t.includes(`${n.replace(/\/+$/,"")}/node_modules/nx/`):!0}w(isOwnRunnerCommand,"isOwnRunnerCommand"),i(isOwnRunnerCommand,"isOwnRunnerCommand");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const n=t.split("/"),a=n[n.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let l=0;l<n.length-1;l+=1)if(TEST_DIR_SEGMENTS.has(n[l]))return!0;return!1}w(isTestSourcePath,"isTestSourcePath"),i(isTestSourcePath,"isTestSourcePath");async function sourceSetSnapshot(t,n,a,l){const f=l?.ignoreTests??!0;let v=0,y=0,_=!1;const g=new Set,I=await gitIgnoredPaths(t),h=i(x=>path.relative(t,x).split(path.sep).join("/"),"rel"),C=i(async(x,X=!0)=>{if(v>=SCAN_FILE_CAP){_=!0;return}let D;try{D=await fs.promises.readdir(x,{withFileTypes:!0})}catch{return}for(const V of D){if(v>=SCAN_FILE_CAP){_=!0;return}const P=V.name,G=path.join(x,P);if(V.isDirectory()){if(!X||P.startsWith(".")||SRC_EXCLUDE.has(P)||a?.has(P)||P.endsWith("-devserver")||I.has(`${h(G)}/`))continue;await C(G)}else if(V.isFile()){const Y=P.lastIndexOf(".");if(Y<=0||!SRC_EXT.has(P.slice(Y)))continue;const b=h(G);if(I.has(b)||f&&isTestSourcePath(b)||g.has(b))continue;g.add(b),v+=1,y^=fnv1a(b)}}},"walk");if(n&&n.length>0){await C(t,!1);for(const x of n)await C(x)}else await C(t);return{signature:`${v}:${(y>>>0).toString(36)}`,paths:g,truncated:_}}w(sourceSetSnapshot,"sourceSetSnapshot"),i(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,n,a,l){return(await sourceSetSnapshot(t,n,a,l)).signature}w(sourceSetSignature,"sourceSetSignature"),i(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,n,a,l){if(t.paths.size===n.paths.size){let _=!0;for(const g of t.paths)if(!n.paths.has(g)){_=!1;break}if(_)return null}const f=[],v=[];let y=t.truncated||n.truncated;for(const _ of n.paths)t.paths.has(_)||(f.length+v.length<SOURCE_DIFF_PATH_CAP?f.push(_):y=!0);for(const _ of t.paths)n.paths.has(_)||(f.length+v.length<SOURCE_DIFF_PATH_CAP?v.push(_):y=!0);return{changedAt:a,added:f,removed:v,truncated:y,...l!==void 0?{windowMs:l}:{}}}w(diffSourceSets,"diffSourceSets"),i(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,n,a,l){const f=n&&n>0?t-n:a;return Math.max(f,a)+l}w(sourceChangeWindowMs,"sourceChangeWindowMs"),i(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,n,a,l,f){const v=i(g=>{const I=path.relative(t,g).split(path.sep).filter(Boolean);return I.length===0||I[0].startsWith("..")?"":I[0]},"bucketOf")(n);if(!v)return[];const y=new Set(a.filter(Boolean)),_=new Set([v]);for(const g of l)g.startsWith(".")||SRC_EXCLUDE.has(g)||f?.has(g)||g.endsWith("-devserver")||y.has(g)||_.add(g);return[..._].sort().map(g=>path.join(t,g))}w(sourceScopeDirs,"sourceScopeDirs"),i(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,n,a,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||n-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&n-t.lastStaleRecycleAt<l.cooldownMs?null:a>0&&a-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&n-t.spawnAt>l.maxAgeMs?"max-age":null}w(podRecycleReason,"podRecycleReason"),i(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,n,a,l,f){return!(l-n<f.cooldownMs||a<=0||a-t<=f.graceMs||l-a<f.settleMs)}w(lensFrontendStale,"lensFrontendStale"),i(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,n,a,l=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&l.length>0){const f=n-a,v=new Set(t.recentEvents.filter(y=>y.at>=f).map(y=>path.resolve(y.path)));return l.every(y=>v.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=n-a?"heard":"missed"}w(lensWatcherVerdict,"lensWatcherVerdict"),i(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,n,a){if(!t)return"unknown";if(t.degraded)return"degraded";const l=n-a;return t.lastWatchRunAt>=l||t.lastBuildAt>=l?"heard":"silent"}w(podWatcherVerdict,"podWatcherVerdict"),i(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,n,a,l){const f=t.hasListened?l.graceMs:Math.max(l.graceMs,l.neverListenedGraceMs);return a-t.spawnedAt<f||a-n<l.cooldownMs||t.probeFails<Math.max(2,l.failureThreshold)?null:"wedged-no-listener"}w(lensChildWedged,"lensChildWedged"),i(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,n){return t.signal||t.code===null||t.code===0||t.ranMs>n.fastFailMs?!1:t.portHeld}w(isPortConflictExit,"isPortConflictExit"),i(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,n,a={}){const l=a.spawnProcess??node_child_process_1.spawn,f=a.probeAlive??pod_probe_1.probePodAlive,v=a.killTree??((e,r="SIGTERM")=>{try{process.kill(-e.pid,r)}catch{try{e.kill(r)}catch{}}}),y=a.rollReadyMs??types_1.POD_ROLL_READY_MS,_=a.rollPollMs??750,{emit:g}=n,I=i(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),h=i((e,r,s,o,c)=>g(r,s,o,{wsKey:e.wsKey,...c},void 0,I(e)),"emitSvc"),C=i(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),x=i((e,r)=>{let s;try{s=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const o=e.proc?.pid;let c=0;for(const d of s.split(`
2
- `)){const u=d.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!u)continue;const m=Number(u[1]),L=Number(u[2]),k=u[3];if(k.includes(`run ${r}`)&&/\bnx(\.js)?\b/.test(k)&&isOwnRunnerCommand(k,e.root)&&!(m===process.pid||m===o)&&!(o!==void 0&&L===o)){try{process.kill(-L,"SIGKILL")}catch{try{process.kill(m,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${r}: reaped ${c} stale runner tree(s) \u2014 a foreign generation held the nx task lock`),h(e,"warning",`stale-runner reap: ${e.project} (${c} foreign tree(s))`,`cluster-service:${e.project}`,{kind:"stale-runner-reap",project:e.project,reaped:c})),c},"reapForeignRunners"),X=i((e,r,s)=>{const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),d=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),u=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(e.wsKey||path.basename(e.root),e.project,"nx")),[m,L]=fs.existsSync(d)?[u,[d,"run",o,`--childCount=${c}`]]:["yarn",["nx","run",o,`--childCount=${c}`]],k=l(m,L,{cwd:e.root,detached:!0,env:{...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(r),...eastWestEnv(t,e)},stdio:["ignore","pipe","pipe"]});s==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const R=i(S=>{const E=S.toString();for(const N of E.split(`
3
- `)){const p=(0,pod_output_1.classifyPodLine)(N),{clean:j,broken:$,healthy:T}=p,z=j;if($){e.lastErrors.push(j),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const M=Date.now();(j!==e.lastErrorEmitSig||M-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=j,e.lastErrorEmitAt=M,h(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:j,fault:p.fault}))}T&&(e.buildFailed=!1);const O=(e.faults??=new pod_output_1.PodFaults).observe(p);if(O){e.lastErrorEmitSig=void 0;const M=O.open.length===0;h(e,"info",`${M?"service recovered":`${O.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:M?"service-recovered":"service-fault-closed",project:e.project,line:j,closed:O.closed,open:O.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(z)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${o}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),h(e,"warning",`nx task lock: ${e.project} blocked by a stale runner \u2014 auto-breaking`,`cluster-service:${e.project}`,{kind:"nx-lock-break",project:e.project}),setTimeout(()=>{t.stopped()||!e.lockBlockedAt||(W(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),x(e,o),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.())},250).unref?.())}},"sniff"),A=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0;return k.stdout?.on("data",S=>{if(t.stopped())return;R(S);const E=`[${e.project}] ${S}`;process.stdout.write(E),A?.(E)}),k.stderr?.on("data",S=>{if(t.stopped())return;R(S);const E=`[${e.project}] ${S}`;process.stderr.write(E),A?.(E)}),k.on("exit",(S,E)=>{if(e.proc!==k||(e.proc=void 0,e.state="down",t.stopped()))return;const N=(0,exit_reason_1.describeExit)(S,E);if(console.log(`[gateway] ${o} exited (${S??"no code"}${E?`, ${E}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${N?` \u2014 ${N}`:""}`),h(e,S===0?"info":"warning",`service down: ${e.project} (exit ${S})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:S,...E?{signal:E}:{}}),S===0||e.buildFailed)return;const p=Date.now();if(e.restarts=(e.restarts??[]).filter($=>p-$<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 ${types_1.SERVICE_MAX_RESTARTS}+ restarts/min, not auto-respawning (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project});return}e.restarts.push(p);const j=Math.min(8e3,500*2**(e.restarts.length-1));setTimeout(()=>{t.stopped()||e.proc||e.state!=="down"||(console.log(`[gateway] self-heal: respawning ${e.project} (restart ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS})`),h(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),D(e))},j)}),k},"launchServiceProcess"),D=i(e=>{if(t.stopped())return;const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,s=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&x(e,r),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${r} (pods=${s})\u2026`),h(e,"info",`service starting: ${e.project} (${s} pod${s>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:s}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=X(e,e.generation,"active")},"spawnService"),V=i(async e=>{const r=e.pool.socks[0];if(!r)return;const s=await(0,pod_probe_1.probePodAlive)(r,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);s&&(e.activeWork=s.activeWork,s.watcher&&(e.watcher=s.watcher))},"refreshActiveWork"),P=i(e=>{try{const r=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(r)&&r>1?r:void 0}catch{return}},"readOwnerSidecar"),G=i(async e=>{const r=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!r)return[];const s=(0,node_crypto_1.createHash)("sha256").update(r).digest("hex").slice(0,8),o=[];for(const c of e.pool.socks){const d=await(0,pod_probe_1.probePodAlive)(c,void 0,r);d&&(!d.keyFingerprint||d.keyFingerprint!==s)&&o.push({sock:c,pid:d.pid})}return o},"staleAdoptedSocks"),Y=i((e,r)=>{const s=r.map(o=>o.sock);for(const{sock:o,pid:c}of r){const d=chooseStalePodKillTarget(c,P(o),process.pid);if(d!==void 0)try{process.kill(d,"SIGTERM")}catch{}try{fs.unlinkSync(o)}catch{}try{fs.unlinkSync(`${o}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(o=>!s.includes(o)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),b=i((e,r)=>{if(e.state==="up")return!1;e.state="up";const s=e.pool.socks.length;return h(e,"info",`service up: ${e.project} (${s} pods, ${r})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:s,adopted:r==="adopted"}),!0},"markUp"),re=i(async e=>{if(C(e.pool)>0){const o=await G(e);if(o.length>0&&(console.error(`[gateway] ${e.project}: ${o.length} adopted pod(s) hold a foreign east-west key \u2014 evicting instead of adopting.`),h(e,"warning",`stale adoption: ${e.project} (foreign east-west key)`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:"stale-adoption",pods:o.length}),e.proc&&W(e,"stale adoption (foreign east-west key)"),Y(e,o)),e.pool.socks.length>0)return b(e,"adopted"),!0}if(t.stopped())return!1;e.state!=="starting"&&!e.proc&&D(e);const r=Date.now()+t.startupTimeoutMs,s=Date.now()+12e3;for(;!t.stopped()&&Date.now()<r;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>s)return!1;if(C(e.pool)>0){e.state="up",e.buildFailed=!1;const o=e.spawnAt?Date.now()-e.spawnAt:void 0;return console.log(`[gateway] ${e.project} up (${e.pool.socks.length} pods${o?`, cold start ${o}ms`:""})`),h(e,"info",`cold start: ${e.project} (${e.pool.socks.length} pods, ${o??"?"}ms)`,`cluster-service:${e.project}`,{kind:"cold-start",project:e.project,pods:e.pool.socks.length,durationMs:o}),!0}if(e.state==="down")return!1}return!1},"ensureUp"),se=i(e=>{const r=e.decl.maxPods??e.decl.minPods??1,s=e.pool.socks.length,o=scaleTarget(e);if(!o||s===0||s>=r||e.inflight<=s||Date.now()-e.lastScaleAt<5e3)return;e.lastScaleAt=Date.now();let c;"socketPath"in o?c=http.request({socketPath:o.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},d=>d.resume()):c=(o.url.startsWith("https")?require("node:https"):http).request(`${o.url}/webpack/scale`,{method:"POST",rejectUnauthorized:!1},d=>d.resume()),c.on("error",()=>{}),c.end(JSON.stringify({pods:s+1})),console.log(`[gateway] autoscale ${e.project}: ${s} \u2192 ${s+1} pods (inflight ${e.inflight})`),h(e,"info",`autoscale: ${e.project} ${s} \u2192 ${s+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:s,to:s+1,inflight:e.inflight})},"scaleUp"),W=i((e,r,s)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${r} \u2014 scaling to zero.`),s?.announce!==!1&&h(e,"info",`idle kill: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"idle-kill",project:e.project,why:r});const o=e.proc;try{o.stdout?.removeAllListeners("data"),o.stderr?.removeAllListeners("data"),o.removeAllListeners("exit")}catch{}v(o,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{J(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),J=i(e=>{try{for(const r of fs.readdirSync(e.pool.dir)){if(!r.endsWith(".sock"))continue;const s=path.join(e.pool.dir,r);try{const o=Number(fs.readFileSync(`${s}.owner`,"utf8").trim());if(Number.isFinite(o)&&o>0)try{process.kill(o,0);continue}catch{}}catch{}try{fs.unlinkSync(s)}catch{}try{fs.unlinkSync(`${s}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks"),te=i((e,r)=>{if(e.rolling)return e.rolling;const s=e.proc;if(!s||t.stopped())return Promise.resolve(!1);const o=e.generation??0,c=o+1,d=Date.now();e.lastStaleRecycleAt=d,console.log(`[gateway] pod handoff start: ${e.project} g${o}\u2192g${c} reason=${r} \u2014 standby spawning, old generation keeps serving.`),h(e,"info",`pod handoff starting: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:r,generation:c,fromGeneration:o});const u=X(e,c,"standby"),m=(async()=>{const L=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],k=d+y;let R;for(;!t.stopped()&&Date.now()<k&&!(u.exitCode!==null||u.signalCode!==null);){C(e.pool);for(const A of e.pool.socks)if((await f(A,void 0,L))?.podGeneration===c){R=A;break}if(R)break;await sleep(_)}if(!R||t.stopped()){const A=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(y/1e3)}s`;try{u.stdout?.removeAllListeners("data"),u.stderr?.removeAllListeners("data")}catch{}return v(u,"SIGTERM"),console.error(`[gateway] pod handoff failed: ${e.project} g${c}: ${A} \u2014 old generation g${o} kept serving.`),h(e,"warning",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:r,generation:c,error:A,durationMs:Date.now()-d}),setTimeout(()=>J(e),1200).unref?.(),!1}if(e.proc!==s)return v(u,"SIGTERM"),!1;e.proc=u,e.generation=c,e.spawnAt=d,e.state="up",e.watcher=void 0,e.staleVerifiedAt=void 0,e.staleKeptAt=void 0,e.buildFailed=!1,e.lastErrors=[];try{s.stdout?.removeAllListeners("data"),s.stderr?.removeAllListeners("data"),s.removeAllListeners("exit")}catch{}return v(s,"SIGTERM"),setTimeout(()=>{J(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${c} answered on ${path.basename(R)} in ${Date.now()-d}ms; retiring g${o}.`),h(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:r,generation:c,fromGeneration:o,durationMs:Date.now()-d}),!0})().finally(()=>{e.rolling=void 0});return e.rolling=m,m},"rollPod"),ne=i(e=>{const r=Date.now();if(e.restarts=(e.restarts??[]).filter(s=>r-s<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){h(e,"error",`service crash-looping: ${e.project} \u2014 wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project}),W(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.restarts.push(r),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; ${e.restarts.length}/${types_1.SERVICE_MAX_RESTARTS}; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),h(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:e.restarts.length}),W(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recycleWedged"),ce={maxAgeMs:types_1.POD_MAX_AGE_MS,graceMs:types_1.POD_STALE_GRACE_MS,settleMs:types_1.POD_RECYCLE_SETTLE_MS,cooldownMs:types_1.POD_STALE_RECYCLE_COOLDOWN_MS};let Q;const B=new Map,oe=new Map;let Z=!1,ee=0,H=0;const ie=i(()=>{const e=t.projectRoots??{},r=new Set(t.services.map(o=>o.project));for(const o of t.routes)o.lens&&r.add(o.lens.project);const s=new Set;for(const o of r){const c=e[o];if(!c)continue;const d=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];d&&!d.startsWith("..")&&s.add(d)}return[...s]},"managedBuckets"),ae=i((e,r,s,o)=>{const c=t.projectSourceRoots?.[e];if(c?.length)return c;const d=t.projectRoots?.[e];if(!d)return;const u=sourceScopeDirs(t.root,d,s,r,o);return u.length>0?u:void 0},"scopeFor"),le=i(()=>{if(Z||Date.now()-ee<types_1.POD_STALE_SCAN_MS)return;Z=!0,ee=Date.now();const e=ee;(async()=>{const r=(0,scope_1.readSourceSetOptions)(t.root),s=r.exclude,o={ignoreTests:r.ignoreTests},c=await sourceSetSnapshot(t.root,void 0,s,o),d=Date.now(),u=sourceChangeWindowMs(d,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),m=Q?diffSourceSets(Q,c,d,u):null;if(m&&(t.sourceSetChangedAt=d,t.sourceSetChange=m,g("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:m.added,removed:m.removed,truncated:m.truncated})),Q=c,!t.projectRoots){H=e;return}let L;try{L=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(p=>p.isDirectory()).map(p=>p.name)}catch{return}const k=ie(),R=new Set(t.services.map(p=>p.project));for(const p of t.routes)p.lens&&R.add(p.lens.project);const A=new Map;for(const p of R){const j=ae(p,L,k,s);if(!j)continue;const $=j.join("|"),T=A.get($)??{dirs:j,projects:[]};T.projects.push(p),A.set($,T)}const S=Date.now(),E=sourceChangeWindowMs(S,H,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);H=e;const N=i((p,j,$)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const T of p)t.sourceSetChangedFor.set(T,j),t.sourceSetChangesFor.set(T,$)},"stampScoped");for(const[p,{dirs:j,projects:$}]of A){const T=await sourceSetSnapshot(t.root,j,s,o),z=B.get(p);B.set(p,T);const O=new Map;for(const K of $){const F=oe.get(K);if(F===void 0||F===p)continue;const q=O.get(F)??[];q.push(K),O.set(F,q)}for(const K of $)oe.set(K,p);for(const[K,F]of O){const q=B.get(K),U=q?diffSourceSets(q,T,S,E):m;U&&(N(F,q?S:d,U),g("info",`source file set changed \u2014 ${F.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:F,added:U.added,removed:U.removed,truncated:U.truncated}))}if(!z)continue;const M=diffSourceSets(z,T,S,E);M&&(N($,S,M),g("info",`source file set changed in ${$.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:$,added:M.added,removed:M.removed,truncated:M.truncated}))}for(const p of[...B.keys()])A.has(p)||B.delete(p)})().catch(()=>{}).finally(()=>{Z=!1})},"maybeScanSourceSet"),de=i((e,r)=>{const s=(t.sourceSetChangesFor?.get(e.project)??t.sourceSetChange)?.windowMs??types_1.POD_STALE_SCAN_MS+types_1.POD_RECYCLE_SETTLE_MS,o=podWatcherVerdict(e.watcher,r,s),c=i(d=>(e.staleKeptAt!==r&&(e.staleKeptAt=r,console.log(`[gateway] stale source set: pod ${e.project} ${d} \u2014 no recycle.`)),!1),"keep");return o==="heard"?(e.staleVerifiedAt=r,h(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:o}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):o==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):o==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),pe=i((e,r)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc){te(e,r);return}console.log(`[gateway] zombie recycle: ${e.project} (${r}) \u2014 stale fs view; fresh pod incoming`),h(e,"warning",`zombie recycle: ${e.project} (${r})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:r});const s=!!e.decl.eager;W(e,`${r} \u2014 recycling stale pod`,{announce:!1}),s&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&D(e)},1500).unref?.()},"recyclePod"),ue=i(()=>{const e=setInterval(()=>{he().catch(r=>console.error("[gateway] sweep error:",r))},t.sweepMs);return e.unref(),e},"startSweeper"),he=i(async()=>{{if(t.stopped())return;types_1.POD_RECYCLE_ENABLED&&le();for(const e of t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const r=e.decl.idleKillSec;if(r&&e.inflight===0&&Date.now()-e.lastUsed>r*1e3){W(e,`idle ${r}s`),e.wedgedSince=void 0;continue}const s=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,o=C(e.pool)>0;if(o||s){if(e.wedgedSince=void 0,o&&b(e,"observed"),o&&!s&&types_1.POD_RECYCLE_ENABLED){const c=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await V(e);const d=(e.staleVerifiedAt??0)>=c&&c>0,u=podRecycleReason(e,Date.now(),d?0:c,ce);if(u==="stale-source-set"&&!de(e,c))continue;u&&pe(e,u)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,ne(e))}}},"sweepOnce");return{scanNow:C,spawnService:D,ensureUp:re,scaleUp:se,killSpawned:W,markUp:b,startSweeper:ue,rollPod:te}}w(createServiceLayer,"createServiceLayer"),i(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,n,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(n!==void 0&&n!==t)&&t!==a)return t}w(chooseStalePodKillTarget,"chooseStalePodKillTarget"),i(chooseStalePodKillTarget,"chooseStalePodKillTarget");
1
+ "use strict";var Te=Object.defineProperty;var f=(t,n)=>Te(t,"name",{value:n,configurable:!0});var 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");