@lensmcp/cluster 1.25.0 → 1.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/executors/gateway/runtime/control.d.ts +3 -0
- package/executors/gateway/runtime/control.js +2 -2
- package/executors/gateway/runtime/lifecycle.d.ts +11 -1
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/types.d.ts +26 -0
- package/main.devserver.js +10 -10
- package/package.json +4 -4
- package/service-build-events.d.ts +7 -0
- package/service-build-events.js +1 -1
- package/swap-mode.d.ts +23 -0
- package/swap-mode.js +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ServiceLayer } from './lifecycle';
|
|
1
2
|
import type { GatewayRuntime } from './types';
|
|
2
3
|
import type { WorkspaceFragment } from './route-registry';
|
|
3
4
|
/** What `/list` and `/status` say about a workspace that is a worktree instance; nothing for any other. */
|
|
@@ -14,6 +15,8 @@ export interface ControlServerDeps {
|
|
|
14
15
|
rt: GatewayRuntime;
|
|
15
16
|
/** Reap a fragment's spawned devservers when it unregisters (the svcLayer's `killSpawned`). */
|
|
16
17
|
killService: (svc: WorkspaceFragment['services'][number], reason: string) => void;
|
|
18
|
+
/** Restart one service (the svcLayer's `restartService`); `POST /restart` answers 501 without it. */
|
|
19
|
+
restartService?: ServiceLayer['restartService'];
|
|
17
20
|
/** Hooks so the daemon can host/tear-down a workspace's dashboard around the route merge (P3d).
|
|
18
21
|
* `onRegister` may be async (port probing) — the register handler AWAITS it before the rebuild. */
|
|
19
22
|
onRegister?: (fragment: WorkspaceFragment) => void | Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)}catch{}let
|
|
1
|
+
"use strict";var x=Object.defineProperty;var p=(s,r)=>x(s,"name",{value:r,configurable:!0});var O=Object.defineProperty,u=p((s,r)=>O(s,"name",{value:r,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.clusterVersion=void 0,exports.instanceSummary=instanceSummary,exports.controlSocketPath=controlSocketPath,exports.versionAtLoad=versionAtLoad,exports.readClusterVersion=readClusterVersion,exports.ensureOwnerOnlyDir=ensureOwnerOnlyDir,exports.startControlServer=startControlServer;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),guest_graph_1=require("./guest-graph"),guest_replay_1=require("./guest-replay"),registration_guards_1=require("./registration-guards"),scope_1=require("./scope"),service_keys_1=require("./service-keys"),workspace_log_1=require("./workspace-log"),workspace_middleware_1=require("./workspace-middleware");function instanceSummary(s){const r=s?.instance;return r?{instance:r.name,baseKey:r.baseKey,apex:r.apex,baseApex:r.baseApex,...r.frozen?{frozen:!0}:{}}:{}}p(instanceSummary,"instanceSummary"),u(instanceSummary,"instanceSummary");function controlSocketPath(){const s=process.env.LENSMCP_HOME||os.homedir();return path.join(s,".lensmcp","control.sock")}p(controlSocketPath,"controlSocketPath"),u(controlSocketPath,"controlSocketPath");const CONTROL_BODY_BYTES=1e6;class BodyTooLarge extends Error{static{p(this,"BodyTooLarge")}static{u(this,"BodyTooLarge")}constructor(r){super(`body too large (over ${r} bytes)`),this.limit=r}}exports.clusterVersion=versionAtLoad(()=>readClusterVersion(__dirname));function versionAtLoad(s){const r=s();return()=>r}p(versionAtLoad,"versionAtLoad"),u(versionAtLoad,"versionAtLoad");function readClusterVersion(s){let r=s;for(let y=0;y<6;y++){const w=path.join(r,"package.json");if(fs.existsSync(w))try{const g=JSON.parse(fs.readFileSync(w,"utf8"));if(g.name==="@lensmcp/cluster"&&typeof g.version=="string")return g.version}catch{}const i=path.dirname(r);if(i===r)break;r=i}}p(readClusterVersion,"readClusterVersion"),u(readClusterVersion,"readClusterVersion");function readJsonBody(s,r=CONTROL_BODY_BYTES){return new Promise((y,w)=>{const i=[];let g=0,k=!1;s.on("data",f=>{if(!k){if(g+=f.length,g>r){k=!0,i.length=0,w(new BodyTooLarge(r));return}i.push(f)}}),s.on("end",()=>{if(k)return;const f=Buffer.concat(i).toString("utf8");try{y({value:f?JSON.parse(f):{},bytes:g})}catch(S){w(S)}}),s.on("error",w)})}p(readJsonBody,"readJsonBody"),u(readJsonBody,"readJsonBody");function graphShare(s,r){return r-Buffer.byteLength(JSON.stringify({...s,projectGraph:null}))}p(graphShare,"graphShare"),u(graphShare,"graphShare");function graphVerdict(s){return s?s.refused===void 0?{forwarded:!0,accepted:!0}:{forwarded:!0,accepted:!1,reason:s.refused}:{forwarded:!1,accepted:!1}}p(graphVerdict,"graphVerdict"),u(graphVerdict,"graphVerdict");function ensureOwnerOnlyDir(s){try{fs.mkdirSync(s,{recursive:!0,mode:448})}catch{}try{const r=fs.lstatSync(s);return r.isDirectory()?((r.mode&63)!==0&&fs.chmodSync(s,448),!0):!1}catch{return!1}}p(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),u(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function startControlServer(s){const{rt:r}=s,y=controlSocketPath(),w=path.dirname(y);ensureOwnerOnlyDir(w)||console.warn(`[gateway] control home ${w} is not a usable owner-only directory \u2014 refusing to expose the control plane there.`);try{fs.unlinkSync(y)}catch{}const i=u((e,n,a)=>{e.writeHead(n,{"content-type":"application/json"}),e.end(JSON.stringify(a))},"send"),g=u(async(e,n)=>{if(!e.wsKey||!e.root)return{status:400,json:{error:"wsKey and root are required"}};const a=e.projects??{},t=(0,discovery_1.projectSourceRoots)(e.root,a,e.projectDependencies),o=(0,scope_1.readLensScope)(e.root),c=o.key===e.wsKey,d=c&&o.sockNs!==e.wsKey?o.sockNs:void 0,{routes:l,services:h,instance:m}=(0,discovery_1.discoverRoutes)(e.root,a,void 0,e.wsKey,t,d,c?o.instance:void 0),b=(0,workspace_middleware_1.findGatewayMiddleware)(e.root,a),$={wsKey:e.wsKey,...d?{sockNs:d}:{},root:e.root,routes:l,services:h,...m?{instance:m}:{},...b?{middleware:b}:{}},j=(0,registration_guards_1.refuseRegistration)(r.registry,$);if(j)return console.warn(`[gateway] refused to register '${e.wsKey}' from ${e.root}: ${j.error}`),{status:409,json:j};try{const v=(0,service_keys_1.adoptServiceKeys)(r,e.root,h,e.wsKey);v.added.length>0&&console.log(`[gateway] adopted east-west keys for ${e.wsKey}: ${v.added.join(", ")}`),v.regenerated.length>0&&console.warn(`[gateway] ${e.root} carried another workspace's east-west keys (a copied key file) \u2014 new keys for ${e.wsKey}: ${v.regenerated.join(", ")}`)}catch(v){console.error(`[gateway] could not adopt east-west keys for ${e.wsKey}: ${v.message}`)}try{(0,workspace_log_1.workspaceLogWriter)(e.root)?.(`[gateway] ${new Date().toISOString()} this workspace ('${e.wsKey}') is served by the daemon at ${r.root} (pid ${process.pid}); its gateway-level log is ${path.join(r.root,".lensmcp","gateway.log")}. Pod output for this workspace follows here. Attach live with \`lensmcp logs <service>\`.
|
|
2
|
+
`)}catch{}let K;if(r.graphStores){const v={root:e.root,daemonRoot:r.root,otherRoots:r.registry.keys().filter(_=>_!==e.wsKey).flatMap(_=>r.registry.get(_)?.root??[])};K=e.projectGraph===void 0?void 0:(0,guest_graph_1.admitForwardedGraph)(e.projectGraph,{...v,bytes:graphShare(e,n)}),K?.refused!==void 0&&console.warn(`[gateway] refused the project graph '${e.wsKey}' forwarded from ${e.root}: ${K.refused} \u2014 its services start through \`nx run\` until the gateway computes it`),(0,registration_guards_1.sameRoot)(e.root,r.root)||r.graphStores.set((0,guest_graph_1.createGuestGraphStore)({wsKey:e.wsKey,root:e.root,...K?{admission:K}:{},context:v}))}return r.registry.register($),await s.onRegister?.($),r.rebuildRoutes(),r.root&&!(0,registration_guards_1.sameRoot)(e.root,r.root)&&(0,guest_replay_1.recordGuest)({wsKey:e.wsKey,root:e.root,projects:a,...e.projectDependencies?{projectDependencies:e.projectDependencies}:{},daemonRoot:r.root,registeredAt:new Date().toISOString()}),{status:200,json:{ok:!0,wsKey:e.wsKey,routes:l.length,services:h.length,workspaces:r.registry.keys(),...r.graphStores?{projectGraph:graphVerdict(K)}:{}}}},"register"),k=u((e,n)=>{const a=r.registry.get(e);if(!a)return{error:`no workspace '${e}' is registered`,workspaces:r.registry.keys()};const t=a.services.find(o=>o.project===n);return t?{svc:t}:{error:`'${e}' has no service '${n}'`,services:a.services.map(o=>o.project)}},"findService"),f=u(e=>({wsKey:e.wsKey,service:e.project,state:e.state,...e.generation!==void 0?{generation:e.generation}:{},...e.restart?{restart:e.restart}:{}}),"restartView"),S=http.createServer((e,n)=>{(async()=>{try{const a=(e.url??"").split("?")[0];if(e.method==="GET"&&a==="/list")return i(n,200,{workspaces:r.registry.keys().map(t=>{const o=r.registry.get(t);return{key:t,root:o?.root,routes:o?.routes.length??0,services:o?.services.length??0,...instanceSummary(o)}})});if(e.method==="GET"&&a==="/status"){const t=(0,exports.clusterVersion)();return i(n,200,{daemon:{pid:process.pid,wsKey:r.wsKey,...t?{version:t}:{}},workspaces:r.registry.keys().map(o=>{const c=r.registry.get(o);return{key:o,root:c?.root,routes:c?.routes.length??0,services:c?.services.length??0,isDaemon:o===r.wsKey,...instanceSummary(c)}}),services:r.registry.allServices().map(o=>({project:o.project,wsKey:o.wsKey,host:o.decl.host,state:o.state,...o.generation!==void 0?{generation:o.generation}:{},...o.restart?{restart:o.restart}:{}}))})}if(e.method==="GET"&&a==="/instance-env"){const t=new URL(e.url??"/","http://control").searchParams.get("wsKey")??"",o=r.registry.get(t);return o?o.instance?i(n,200,{wsKey:t,root:o.root,...instanceSummary(o),env:o.instance.env}):i(n,404,{error:`'${t}' is not an instance`}):i(n,404,{error:`no workspace '${t}' is registered`})}if(a==="/restart"&&(e.method==="POST"||e.method==="GET")){const t=e.method==="GET"?Object.fromEntries(new URL(e.url??"/","http://control").searchParams):(await readJsonBody(e)).value??{},o=typeof t.wsKey=="string"?t.wsKey:"",c=typeof t.service=="string"?t.service:"";if(!o||!c)return i(n,400,{error:"wsKey and service are required"});const d=k(o,c);if("error"in d)return i(n,404,d);const l=d.svc;if(e.method==="GET")return i(n,200,f(l));if(!s.restartService)return i(n,501,{error:"this gateway cannot restart a service"});const h=typeof t.reason=="string"&&t.reason.trim()?t.reason.trim().slice(0,200):"asked for",m=s.restartService(l,{hard:t.hard===!0,reason:h});return"refused"in m?i(n,409,{error:m.refused,...f(l)}):i(n,202,{ok:!0,already:m.already,...f(l)})}if(e.method==="POST"&&a==="/register"){const{value:t,bytes:o}=await readJsonBody(e,CONTROL_BODY_BYTES+guest_graph_1.MAX_FORWARDED_GRAPH_BYTES),c=await g(t,o);return i(n,c.status,c.json)}if(e.method==="POST"&&a==="/unregister"){const t=(await readJsonBody(e)).value;if(!t.wsKey)return i(n,400,{error:"wsKey is required"});const o=(0,registration_guards_1.refuseUnregistration)(r.registry,{wsKey:t.wsKey,...t.root?{root:t.root}:{}},r.wsKey);if(o)return console.warn(`[gateway] refused to unregister '${t.wsKey}'${t.root?` for ${t.root}`:""}: ${o.error}`),i(n,409,o);const c=r.registry.unregister(t.wsKey);if(c){for(const d of c.services){const l=(0,service_keys_1.serviceKeyId)(d.wsKey??t.wsKey,d.project),h=r.serviceKeys[l];h&&(delete r.serviceKeys[l],r.keyToProject.delete(h),r.keyToWorkspace?.delete(h))}for(const d of c.services)s.killService(d,"workspace unregistered");r.graphStores?.delete(c.root),(0,guest_replay_1.forgetGuest)(t.wsKey),s.onUnregister?.(c)}return r.rebuildRoutes(),i(n,200,{ok:!0,removed:!!c,workspaces:r.registry.keys()})}i(n,404,{error:"not found"})}catch(a){if(a instanceof BodyTooLarge)return e.resume(),i(n,413,{error:a.message});i(n,500,{error:a.message})}})()});return S.listen(y,()=>{try{fs.chmodSync(y,384)}catch(e){console.warn(`[gateway] could not restrict ${y} to 0600:`,e.message)}}),{socketPath:y,async replay(){const{replay:e,gone:n}=(0,guest_replay_1.guestsToReplay)(r.root,(0,guest_replay_1.readGuests)()),a={replayed:[],refused:[],gone:n.map(t=>t.wsKey)};for(const t of n)(0,guest_replay_1.forgetGuest)(t.wsKey),console.log(`[gateway] not re-hosting '${t.wsKey}': ${t.root} is no longer a workspace \u2014 forgotten`);for(const t of e)if(!r.registry.has(t.wsKey))try{const o=await g({wsKey:t.wsKey,root:t.root,projects:t.projects,...t.projectDependencies?{projectDependencies:t.projectDependencies}:{}},0);o.status===200?(a.replayed.push(t.wsKey),console.log(`[gateway] re-hosted '${t.wsKey}' (${t.root}) \u2014 registered before this daemon restarted`)):(a.refused.push(t.wsKey),console.warn(`[gateway] could not re-host '${t.wsKey}' (${t.root}): ${JSON.stringify(o.json)} \u2014 \`lensmcp gateway start\` there`))}catch(o){a.refused.push(t.wsKey),console.warn(`[gateway] could not re-host '${t.wsKey}' (${t.root}): ${o.message}`)}return a},close:u(()=>{S.close();try{fs.unlinkSync(y)}catch{}},"close")}}p(startControlServer,"startControlServer"),u(startControlServer,"startControlServer");
|
|
@@ -7,7 +7,7 @@ import { spawn, type ChildProcess } from 'node:child_process';
|
|
|
7
7
|
import * as net from 'node:net';
|
|
8
8
|
import { probePodAlive } from './pod-probe';
|
|
9
9
|
import { type SharedBuildRelay } from './shared-build';
|
|
10
|
-
import type { GatewayRuntime, LensWatcherHealth, PodWatcherTestimony, PodWatcherVerdict, ServiceCtl, SockPool, SourceSetChange } from './types';
|
|
10
|
+
import type { GatewayRuntime, LensWatcherHealth, PodWatcherTestimony, PodWatcherVerdict, ServiceCtl, ServiceRestart, SockPool, SourceSetChange } from './types';
|
|
11
11
|
import type { Observability } from './observability';
|
|
12
12
|
/** Bounded, async, non-blocking walk of the workspace source tree → a signature of the file SET
|
|
13
13
|
* (`<count>:<xor-of-path-hashes>`). Skips node_modules/dist/build/dotdirs + `*-devserver` sock dirs + every
|
|
@@ -280,6 +280,16 @@ export interface ServiceLayer {
|
|
|
280
280
|
startSweeper(): NodeJS.Timeout;
|
|
281
281
|
/** Rolling recycle: standby generation first, retire the old once the new answers (issues/closed/dev-gateway-09). */
|
|
282
282
|
rollPod(svc: ServiceCtl, reason: string): Promise<boolean>;
|
|
283
|
+
/** A restart someone asked for ({@link ServiceRestart}): started at once, settled on `svc.restart.done`. */
|
|
284
|
+
restartService(svc: ServiceCtl, opts: {
|
|
285
|
+
hard?: boolean;
|
|
286
|
+
reason: string;
|
|
287
|
+
}): {
|
|
288
|
+
restart: ServiceRestart;
|
|
289
|
+
already: boolean;
|
|
290
|
+
} | {
|
|
291
|
+
refused: string;
|
|
292
|
+
};
|
|
283
293
|
/** Self-heal a WEDGED service (live parent, 0 live pods): recycle it, within the crash-loop budget. */
|
|
284
294
|
recycleWedged(svc: ServiceCtl): void;
|
|
285
295
|
/** Stop every tree a handoff replaced that is still draining — at gateway stop, where nothing waits behind it. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var qe=Object.defineProperty;var S=(t,c)=>qe(t,"name",{value:c,configurable:!0});var Ke=Object.defineProperty,s=S((t,c)=>Ke(t,"name",{value:c,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isLoopbackHostname=isLoopbackHostname,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isRunnerOf=isRunnerOf,exports.isTestSourcePath=isTestSourcePath,exports.sourceScanPass=sourceScanPass,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),pod_sock_1=require("../../../pod-sock"),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"),handoff_retire_1=require("../../../handoff-retire"),process_name_1=require("../../../process-name");function eastWestEnv(t,c){const a={},l=t.serviceKeys[(0,service_keys_1.serviceKeyId)(c.wsKey,c.project)];if(l&&(a.LENSMCP_SERVICE_KEY=l),t.https){const h=require("../../../basic-ssl").caCertPath();fs.existsSync(h)&&(a.NODE_EXTRA_CA_CERTS=h)}return a}S(eastWestEnv,"eastWestEnv"),s(eastWestEnv,"eastWestEnv");const sleep=s(t=>new Promise(c=>setTimeout(c,t)),"sleep"),SRC_EXCLUDE=new Set(["node_modules","dist","tmp","coverage","build","out","target"]),SRC_EXT=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".css",".scss",".sass",".less",".json",".html",".htm",".vue",".svelte",".mdx"]),SCAN_FILE_CAP=1e5;function fnv1a(t){let c=2166136261;for(let a=0;a<t.length;a+=1)c^=t.charCodeAt(a),c=Math.imul(c,16777619);return c>>>0}S(fnv1a,"fnv1a"),s(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:c}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(c.split("\0").filter(Boolean))}catch{return new Set}}S(gitIgnoredPaths,"gitIgnoredPaths"),s(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,c=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const a=path.join(t.pool.dir,"parent.sock");return c(a)?{socketPath:a}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}S(scaleTarget,"scaleTarget"),s(scaleTarget,"scaleTarget");function isLoopbackHostname(t){const c=t.toLowerCase().replace(/^\[(.*)\]$/,"$1").replace(/\.$/,"");return c==="localhost"||c.endsWith(".localhost")?!0:net.isIPv4(c)?c.split(".")[0]==="127":net.isIPv6(c)&&new URL(`http://[${c}]/`).hostname==="[::1]"}S(isLoopbackHostname,"isLoopbackHostname"),s(isLoopbackHostname,"isLoopbackHostname");function postScale(t,c,a){let l;if("socketPath"in t)l=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"),T=h?require("node:https"):http,v={method:"POST"};h&&isLoopbackHostname(new URL(t.url).hostname)&&(v.rejectUnauthorized=!1),a&&(v.lookup=a),l=T.request(`${t.url}/webpack/scale`,v,_=>_.resume())}return l.on("error",()=>{}),l.end(JSON.stringify({pods:c})),l}S(postScale,"postScale"),s(postScale,"postScale");function isOwnRunnerCommand(t,c){return(0,serve_launch_1.isServeRunnerCommand)(t)?(0,serve_launch_1.isServeRunnerCommandOf)(t,c):t.includes("/node_modules/nx/")?t.includes(`${c.replace(/\/+$/,"")}/node_modules/nx/`):!0}S(isOwnRunnerCommand,"isOwnRunnerCommand"),s(isOwnRunnerCommand,"isOwnRunnerCommand");function isRunnerOf(t,c,a){const l=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return!new RegExp(`(^|\\s)run ${l}(:|\\s|$)`).test(t)||!(0,serve_launch_1.isServeRunnerCommand)(t)&&!/\bnx(\.js)?\b/.test(t)?!1:isOwnRunnerCommand(t,a)}S(isRunnerOf,"isRunnerOf"),s(isRunnerOf,"isRunnerOf");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const c=t.split("/"),a=c[c.length-1]??"";if(TEST_FILE_RE.test(a))return!0;for(let l=0;l<c.length-1;l+=1)if(TEST_DIR_SEGMENTS.has(c[l]))return!0;return!1}S(isTestSourcePath,"isTestSourcePath"),s(isTestSourcePath,"isTestSourcePath");function sourceScanPass(t,c,a){const l=a?.ignoreTests??!0;let h;const T=new Map,v=`tree:${path.resolve(t)}`,_=s(g=>path.relative(t,g).split(path.sep).join("/"),"rel"),R=s(async(g,w)=>{const y=await(h??=gitIgnoredPaths(t)),x=[],F=new Set;let D=!1;const q=s(async(Q,E)=>{if(x.length>=SCAN_FILE_CAP){D=!0;return}let I;try{I=await fs.promises.readdir(Q,{withFileTypes:!0})}catch{return}E&&F.add(_(Q));for(const U of I){if(x.length>=SCAN_FILE_CAP){D=!0;return}const O=U.name,Z=path.join(Q,O);if(U.isDirectory()){if(!E||O.startsWith(".")||SRC_EXCLUDE.has(O)||c?.has(O)||O.endsWith("-devserver")||y.has(`${_(Z)}/`))continue;await q(Z,!0)}else if(U.isFile()){const ee=O.lastIndexOf(".");if(ee<=0||!SRC_EXT.has(O.slice(ee)))continue;const B=_(Z);if(y.has(B)||l&&isTestSourcePath(B))continue;x.push(B)}}},"walk");return await q(g,w),{paths:x,entered:F,truncated:D}},"walkDir"),b=s((g,w)=>{let y=T.get(g);return y||(y=w(),T.set(g,y)),y},"memo"),se=s(async()=>{const g=T.get(v);if(!g)return;const w=await g;return w.truncated?void 0:w},"wideWalk"),J=new Set,ie=s(()=>b(`files:${path.resolve(t)}`,async()=>{const g=await se();return g?{paths:g.paths.filter(w=>!w.includes("/")),entered:J,truncated:!1}:R(t,!1)}),"rootFiles"),ne=s(g=>b(`tree:${path.resolve(g)}`,async()=>{const w=await se(),y=_(g);if(w?.entered.has(y)){const x=`${y}/`;return{paths:y===""?w.paths:w.paths.filter(F=>F.startsWith(x)),entered:J,truncated:!1}}return R(g,!0)}),"subtree"),G=s(g=>{const w=new Set;let y=0,x=g.some(D=>D.truncated);const F=s(D=>w.has(D)?!0:w.size>=SCAN_FILE_CAP?(x=!0,!1):(w.add(D),y^=fnv1a(D),!0),"add");for(const D of g)if(!D.paths.every(F))break;return{signature:`${w.size}:${(y>>>0).toString(36)}`,paths:w,truncated:x}},"compose");return{async snapshot(g){if(!g||g.length===0)return G([await b(v,()=>R(t,!0))]);const w=[await ie()];for(const y of g)w.push(await ne(y));return G(w)}}}S(sourceScanPass,"sourceScanPass"),s(sourceScanPass,"sourceScanPass");async function sourceSetSnapshot(t,c,a,l){return sourceScanPass(t,a,l).snapshot(c)}S(sourceSetSnapshot,"sourceSetSnapshot"),s(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,c,a,l){return(await sourceSetSnapshot(t,c,a,l)).signature}S(sourceSetSignature,"sourceSetSignature"),s(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,c,a,l){if(t.paths.size===c.paths.size){let _=!0;for(const R of t.paths)if(!c.paths.has(R)){_=!1;break}if(_)return null}const h=[],T=[];let v=t.truncated||c.truncated;for(const _ of c.paths)t.paths.has(_)||(h.length+T.length<SOURCE_DIFF_PATH_CAP?h.push(_):v=!0);for(const _ of t.paths)c.paths.has(_)||(h.length+T.length<SOURCE_DIFF_PATH_CAP?T.push(_):v=!0);return{changedAt:a,added:h,removed:T,truncated:v,...l!==void 0?{windowMs:l}:{}}}S(diffSourceSets,"diffSourceSets"),s(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,c,a,l){const h=c&&c>0?t-c:a;return Math.max(h,a)+l}S(sourceChangeWindowMs,"sourceChangeWindowMs"),s(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,c,a,l,h){const T=s(R=>{const b=path.relative(t,R).split(path.sep).filter(Boolean);return b.length===0||b[0].startsWith("..")?"":b[0]},"bucketOf")(c);if(!T)return[];const v=new Set(a.filter(Boolean)),_=new Set([T]);for(const R of l)R.startsWith(".")||SRC_EXCLUDE.has(R)||h?.has(R)||R.endsWith("-devserver")||v.has(R)||_.add(R);return[..._].sort().map(R=>path.join(t,R))}S(sourceScopeDirs,"sourceScopeDirs"),s(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,c,a,l){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||c-t.lastUsed<l.settleMs||t.lastStaleRecycleAt!=null&&c-t.lastStaleRecycleAt<l.cooldownMs?null:a>0&&a-t.spawnAt>l.graceMs?"stale-source-set":l.maxAgeMs>0&&c-t.spawnAt>l.maxAgeMs?"max-age":null}S(podRecycleReason,"podRecycleReason"),s(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,c,a,l,h){return!(l-c<h.cooldownMs||a<=0||a-t<=h.graceMs||l-a<h.settleMs)}S(lensFrontendStale,"lensFrontendStale"),s(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,c,a,l=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&l.length>0){const h=c-a,T=new Set(t.recentEvents.filter(v=>v.at>=h).map(v=>path.resolve(v.path)));return l.every(v=>T.has(path.resolve(v)))?"heard":"missed"}return t.lastFsSetEventAt>=c-a?"heard":"missed"}S(lensWatcherVerdict,"lensWatcherVerdict"),s(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,c,a){if(!t)return"unknown";if(t.degraded)return"degraded";const l=c-a;return t.lastWatchRunAt>=l||t.lastBuildAt>=l?"heard":"silent"}S(podWatcherVerdict,"podWatcherVerdict"),s(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,c,a,l){const h=t.hasListened?l.graceMs:Math.max(l.graceMs,l.neverListenedGraceMs);return a-t.spawnedAt<h||a-c<l.cooldownMs||t.probeFails<Math.max(2,l.failureThreshold)?null:"wedged-no-listener"}S(lensChildWedged,"lensChildWedged"),s(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,c){return t.signal||t.code===null||t.code===0||t.ranMs>c.fastFailMs?!1:t.portHeld}S(isPortConflictExit,"isPortConflictExit"),s(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,c,a={}){const l=a.spawnProcess??node_child_process_1.spawn,h=a.probeAlive??pod_probe_1.probePodAlive,T=a.killTree??((e,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),v=a.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),_=s((e,o="SIGTERM")=>{v.detach(e),T(e,o)},"killTree"),R=a.handoffDrainMs??(0,handoff_retire_1.handoffDrainBoundMs)(),b=a.treeAlive??(e=>{if(!e.pid)return!1;try{return process.kill(-e.pid,0),!0}catch(o){return o.code==="EPERM"}}),se=a.retirePollMs??1e3,J=a.rollReadyMs??types_1.POD_ROLL_READY_MS,ie=a.rollPollMs??750,ne=a.maxConcurrentRolls??types_1.POD_MAX_CONCURRENT_ROLLS,G=ne>0?ne:Number.POSITIVE_INFINITY,g=a.maxConcurrentColdStarts??types_1.MAX_COLD_STARTS,w=g>0?g:Number.POSITIVE_INFINITY;let y=0;const x=[],F=s(()=>y<w?(y+=1,Promise.resolve()):new Promise(e=>{x.push(()=>{y+=1,e()})}),"acquireColdSlot"),D=s(()=>{y-=1,x.shift()?.()},"releaseColdSlot"),{emit:q}=c,Q=s(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),E=s((e,o,n,r,d)=>q(o,n,r,{wsKey:e.wsKey,...d},void 0,Q(e)),"emitSvc"),I=s(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),U=s((e,o)=>{let n;try{n=(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 d=0;for(const i of n.split(`
|
|
2
|
-
`)){const p=
|
|
3
|
-
`)){const j=(0,pod_output_1.classifyPodLine)(m),{clean:C,broken:N,healthy:k}=j,W=C;if(N){e.lastErrors.push(C),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const P=Date.now();(C!==e.lastErrorEmitSig||P-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=C,e.lastErrorEmitAt=P,E(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:C,fault:j.fault}))}k&&(e.buildFailed=!1);const M=(e.faults??=new pod_output_1.PodFaults).observe(j);if(M){e.lastErrorEmitSig=void 0;const P=M.open.length===0;E(e,"info",`${P?"service recovered":`${M.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:P?"service-recovered":"service-fault-closed",project:e.project,line:C,closed:M.closed,open:M.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(W)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),E(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||(V(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),U(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&X(e)},1500).unref?.())},250).unref?.())}},"sniff"),A=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,L=s($=>f=>{if(t.stopped())return;const m=(0,inspector_secret_1.redactInspectorUrls)(f.toString());u(m);const j=`[${e.project}] ${m}`;$.write(j),A?.(j)},"relay");return i.stdout?.on("data",L(process.stdout)),i.stderr?.on("data",L(process.stderr)),i.on("exit",($,f)=>{if(e.proc!==i||(e.proc=void 0,e.state="down",t.stopped()))return;const m=(0,exit_reason_1.describeExit)($,f);if(console.log(`[gateway] ${r} exited (${$??"no code"}${f?`, ${f}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${m?` \u2014 ${m}`:""}`),E(e,$===0?"info":"warning",`service down: ${e.project} (exit ${$})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:$,...f?{signal:f}:{}}),$===0||e.buildFailed)return;const j=Date.now();if(e.restarts=(e.restarts??[]).filter(N=>j-N<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){E(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(j);const C=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})`),E(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),X(e))},C)}),i},"launchServiceProcess"),X=s(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,n=Math.max(1,e.decl.minPods??1);a.reapForeign!==!1&&U(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${n})\u2026`),E(e,"info",`service starting: ${e.project} (${n} pod${n>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:n}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=B(e,e.generation,"active")},"spawnService"),Ee=s(async e=>{const o=e.pool.socks[0];if(!o)return;const n=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);n&&(e.activeWork=n.activeWork,n.watcher&&(e.watcher=n.watcher))},"refreshActiveWork"),_e=s(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),$e=s(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const n=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const d of e.pool.socks){const i=await(0,pod_probe_1.probePodAlive)(d,void 0,o);i&&(!i.keyFingerprint||i.keyFingerprint!==n)&&r.push({sock:d,pid:i.pid})}return r},"staleAdoptedSocks"),me=s((e,o)=>{const n=o.map(r=>r.sock);for(const{sock:r,pid:d}of o){const i=chooseStalePodKillTarget(d,_e(r),process.pid);if(i!==void 0)try{process.kill(i,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!n.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),le=s((e,o)=>{if(e.state==="up")return!1;e.state="up",e.wedgeRecycles=0;const n=e.pool.socks.length;return E(e,"info",`service up: ${e.project} (${n} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:n,adopted:o==="adopted"}),!0},"markUp"),ke=s(async e=>{if(I(e.pool)>0){const r=await $e(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.`),E(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&&V(e,"stale adoption (foreign east-west key)"),me(e,r)),e.pool.socks.length>0)return le(e,"adopted"),!0}if(t.stopped())return!1;let o=!1;const n=s(()=>e.state!=="starting"&&!e.proc,"notStarted");if(n())if(e.coldWait)await e.coldWait;else{y>=w&&(console.log(`[gateway] cold start queued: ${e.project}${e.wsKey&&e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${y} starting, ${x.length} ahead (LENSMCP_MAX_COLD_STARTS=${w}).`),E(e,"info",`cold start queued: ${e.project}`,`cluster-service:${e.project}`,{kind:"cold-start-queued",project:e.project,inFlight:y,ahead:x.length}));const r=F();if(e.coldWait=r,await r,e.coldWait=void 0,o=!0,t.stopped())return D(),!1;n()&&X(e)}try{return await je(e)}finally{o&&D()}},"ensureUp"),je=s(async e=>{const o=Date.now()+t.startupTimeoutMs,n=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>n)return!1;if(I(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`:""})`),E(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},"waitForPods"),Re=s(e=>{const o=e.decl.maxPods??e.decl.minPods??1,n=e.pool.socks.length,r=scaleTarget(e);!r||n===0||n>=o||e.inflight<=n||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(r,n+1,a.scaleLookup),console.log(`[gateway] autoscale ${e.project}: ${n} \u2192 ${n+1} pods (inflight ${e.inflight})`),E(e,"info",`autoscale: ${e.project} ${n} \u2192 ${n+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:n,to:n+1,inflight:e.inflight}))},"scaleUp"),V=s((e,o,n)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),n?.announce!==!1&&E(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{}(0,discovery_1.stopRoutingTo)(e.pool),_(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{ce(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ce=s(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const n=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${n}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(n)}catch{}try{fs.unlinkSync(`${n}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let H=0;const de=[],Ae=s(()=>H<G?(H+=1,Promise.resolve()):new Promise(e=>{de.push(()=>{H+=1,e()})}),"acquireRollSlot"),Te=s(()=>{H-=1,de.shift()?.()},"releaseRollSlot"),Ce=s(async(e,o,n)=>{I(e.pool);for(const r of e.pool.socks){const d=await h(r,void 0,n);if(d&&o(d.podGeneration))return!0}return!1},"anyPodAnswers"),pe=new Map,Me=s((e,o,n)=>{const r=R>0&&(0,handoff_retire_1.requestHandoffRetire)(e.pool.dir,n);if(_(o,"SIGTERM"),!r)return;const d=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,i=s(j=>C=>{if(t.stopped())return;const N=(0,inspector_secret_1.redactInspectorUrls)(C.toString()).replace(/^(?=.)/gm,`[${e.project} g${n} retiring] `);j.write(N),d?.(N)},"say"),p=i(process.stdout),u=i(process.stderr);o.stdout?.on("data",p),o.stderr?.on("data",u);let A=!1;const L=s(()=>{A||(A=!0,clearTimeout($),clearInterval(m),o.stdout?.removeListener("data",p),o.stderr?.removeListener("data",u),pe.delete(o),(0,handoff_retire_1.clearHandoffRetire)(e.pool.dir,n),ce(e),e.pool.scannedAt=0)},"end"),$=setTimeout(()=>{console.error(`[gateway] pod handoff: ${e.project} g${n} was still running ${R}ms after it was retired \u2014 killing it.`),T(o,"SIGKILL"),L()},R);$.unref?.();const f=s(()=>{if(!A){if(t.stopped()){L();return}b(o)||(console.log(`[gateway] pod handoff: ${e.project} g${n} finished its in-flight work and exited.`),L())}},"look"),m=setInterval(f,se);m.unref?.(),o.once("exit",f),pe.set(o,()=>{T(o,"SIGTERM"),L()})},"retireReplacedTree"),Pe=s(()=>{for(const e of[...pe.values()])e()},"stopRetiringTrees"),we=s((e,o)=>{if(e.rolling)return e.rolling;const n=e.proc;if(!n||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);H>=G&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${o} \u2014 ${H} roll(s) in flight, ${de.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${G}).`);const r=(async()=>{await Ae();try{return await xe(e,n,o)}finally{Te()}})().finally(()=>{e.rolling=void 0});return e.rolling=r,r},"rollPod"),xe=s(async(e,o,n)=>{const r=e.proc;if(r!==o||t.stopped())return!1;const d=e.generation??0,i=d+1,p=Date.now();e.lastStaleRecycleAt=p,console.log(`[gateway] pod handoff start: ${e.project} g${d}\u2192g${i} reason=${n} \u2014 standby spawning, old generation keeps serving.`),E(e,"info",`pod handoff starting: ${e.project} (${n})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:n,generation:i,fromGeneration:d});const u=B(e,i,"standby"),A=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],L=p+J;let $;for(;!t.stopped()&&Date.now()<L&&!(u.exitCode!==null||u.signalCode!==null);){I(e.pool);for(const f of e.pool.socks)if((await h(f,void 0,A))?.podGeneration===i){$=f;break}if($)break;await sleep(ie)}if(!$||t.stopped()){const f=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(J/1e3)}s`;try{u.stdout?.removeAllListeners("data"),u.stderr?.removeAllListeners("data")}catch{}(0,discovery_1.stopRoutingTo)(e.pool,C=>(0,pod_sock_1.podSockGeneration)(C)===i),_(u,"SIGTERM");const m=!t.stopped()&&e.proc===r&&r.exitCode===null&&r.signalCode===null&&await Ce(e,C=>C!==i,A),j=m?`old generation g${d} kept serving`:`and the old generation g${d} is NOT serving (${e.proc!==r||r.exitCode!==null||r.signalCode!==null?"its tree is gone":"none of its pods answers"}) \u2014 the service is down until it recovers`;return console.error(`[gateway] pod handoff failed: ${e.project} g${i}: ${f} \u2014 ${j}.`),E(e,m?"warning":"error",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:n,generation:i,error:f,durationMs:Date.now()-p,oldServing:m}),setTimeout(()=>ce(e),1200).unref?.(),!1}if(e.proc!==r)return(0,discovery_1.stopRoutingTo)(e.pool,f=>(0,pod_sock_1.podSockGeneration)(f)===i),_(u,"SIGTERM"),!1;e.proc=u,e.launcher=ee.get(u),e.generation=i,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(0,discovery_1.stopRoutingTo)(e.pool,f=>(0,pod_sock_1.podSockGeneration)(f)!==i&&path.join(e.pool.dir,f)!==$),Me(e,r,d),setTimeout(()=>{ce(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${i} answered on ${path.basename($)} in ${Date.now()-p}ms; retiring g${d}.`),E(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:n,generation:i,fromGeneration:d,durationMs:Date.now()-p}),!0},"runRoll"),Se=s(e=>{const o=Date.now();e.restarts=(e.restarts??[]).filter(r=>o-r<types_1.SERVICE_RESTART_WINDOW_MS);const n=(e.wedgeRecycles??0)+1;if(e.restarts.length>=types_1.SERVICE_MAX_RESTARTS||n>types_1.SERVICE_MAX_RESTARTS){const r=e.restarts.length>=types_1.SERVICE_MAX_RESTARTS?`wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min`:`recycled ${types_1.SERVICE_MAX_RESTARTS}\xD7 in a row and no pod came up`;E(e,"error",`service crash-looping: ${e.project} \u2014 ${r}, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project,recycles:n-1}),V(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.wedgeRecycles=n,e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; attempt ${n}/${types_1.SERVICE_MAX_RESTARTS} with no pod up since the first, ${e.restarts.length} in the last minute; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),E(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:n,inWindow:e.restarts.length}),V(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&X(e)},1500).unref?.()},"recycleWedged"),De={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 ue;const te=new Map,ve=new Map;let he=!1,fe=0,ae=0;const Le=s(()=>{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 n=new Set;for(const r of o){const d=e[r];if(!d)continue;const i=path.relative(t.root,d).split(path.sep).filter(Boolean)[0];i&&!i.startsWith("..")&&n.add(i)}return[...n]},"managedBuckets"),be=s((e,o,n,r)=>{const d=t.projectSourceRoots?.[e];if(d?.length)return d;const i=t.projectRoots?.[e];if(!i)return;const p=sourceScopeDirs(t.root,i,n,o,r);return p.length>0?p:void 0},"scopeFor"),Ie=s(()=>{if(he||Date.now()-fe<types_1.POD_STALE_SCAN_MS)return;he=!0,fe=Date.now();const e=fe;(async()=>{const o=(0,scope_1.readSourceSetOptions)(t.root),n=o.exclude,r={ignoreTests:o.ignoreTests},d=sourceScanPass(t.root,n,r),i=await d.snapshot(),p=Date.now(),u=sourceChangeWindowMs(p,ae,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),A=ue?diffSourceSets(ue,i,p,u):null;if(A&&(t.sourceSetChangedAt=p,t.sourceSetChange=A,q("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:A.added,removed:A.removed,truncated:A.truncated})),ue=i,!t.projectRoots){ae=e;return}let L;try{L=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(k=>k.isDirectory()).map(k=>k.name)}catch{return}const $=Le(),f=new Set(t.services.map(k=>k.project));for(const k of t.routes)k.lens&&f.add(k.lens.project);const m=new Map;for(const k of f){const W=be(k,L,$,n);if(!W)continue;const M=W.join("|"),P=m.get(M)??{dirs:W,projects:[]};P.projects.push(k),m.set(M,P)}const j=Date.now(),C=sourceChangeWindowMs(j,ae,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);ae=e;const N=s((k,W,M)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const P of k)t.sourceSetChangedFor.set(P,W),t.sourceSetChangesFor.set(P,M)},"stampScoped");for(const[k,{dirs:W,projects:M}]of m){const P=await d.snapshot(W),ye=te.get(k);te.set(k,P);const ge=new Map;for(const Y of M){const K=ve.get(Y);if(K===void 0||K===k)continue;const z=ge.get(K)??[];z.push(Y),ge.set(K,z)}for(const Y of M)ve.set(Y,k);for(const[Y,K]of ge){const z=te.get(Y),re=z?diffSourceSets(z,P,j,C):A;re&&(N(K,z?j:p,re),q("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:re.added,removed:re.removed,truncated:re.truncated}))}if(!ye)continue;const oe=diffSourceSets(ye,P,j,C);oe&&(N(M,j,oe),q("info",`source file set changed in ${M.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:M,added:oe.added,removed:oe.removed,truncated:oe.truncated}))}for(const k of[...te.keys()])m.has(k)||te.delete(k)})().catch(()=>{}).finally(()=>{he=!1})},"maybeScanSourceSet"),Oe=s((e,o)=>{const n=(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,n),d=s(i=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${i} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,E(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),d(`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"?d("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?d("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),Ne=s((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){we(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),E(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const n=!!e.decl.eager;V(e,`${o} \u2014 recycling stale pod`,{announce:!1}),n&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&X(e)},1500).unref?.()},"recyclePod"),We=s(()=>{const e=setInterval(()=>{Fe().catch(o=>console.error("[gateway] sweep error:",o))},t.sweepMs);return e.unref(),e},"startSweeper"),Fe=s(async()=>{{if(t.stopped())return;for(const e of t.graphStores?.all()??[])e.refreshIfStale();types_1.POD_RECYCLE_ENABLED&&Ie();for(const e of t.registry?t.registry.allServices():t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const o=path.resolve(e.root)===path.resolve(t.root),n=e.decl.idleKillSec;if(n&&e.inflight===0&&Date.now()-e.lastUsed>n*1e3){V(e,`idle ${n}s`),e.wedgedSince=void 0;continue}const r=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,d=I(e.pool)>0;if(d||r){if(e.wedgedSince=void 0,d&&le(e,"observed"),d&&!r&&types_1.POD_RECYCLE_ENABLED&&o){const i=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await Ee(e);const p=(e.staleVerifiedAt??0)>=i&&i>0,u=podRecycleReason(e,Date.now(),p?0:i,De);if(u==="stale-source-set"&&!Oe(e,i))continue;u&&Ne(e,u)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,Se(e))}}},"sweepOnce");return{scanNow:I,spawnService:X,ensureUp:ke,scaleUp:Re,killSpawned:V,markUp:le,startSweeper:We,rollPod:we,recycleWedged:Se,stopRetiringTrees:Pe,stopBuildHost:s(e=>e===void 0?v.stop():v.release(e),"stopBuildHost")}}S(createServiceLayer,"createServiceLayer"),s(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,c,a){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(c!==void 0&&c!==t)&&t!==a)return t}S(chooseStalePodKillTarget,"chooseStalePodKillTarget"),s(chooseStalePodKillTarget,"chooseStalePodKillTarget");
|
|
1
|
+
"use strict";var Ve=Object.defineProperty;var v=(t,a)=>Ve(t,"name",{value:a,configurable:!0});var Ge=Object.defineProperty,s=v((t,a)=>Ge(t,"name",{value:a,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.scaleTarget=scaleTarget,exports.isLoopbackHostname=isLoopbackHostname,exports.isOwnRunnerCommand=isOwnRunnerCommand,exports.isRunnerOf=isRunnerOf,exports.isTestSourcePath=isTestSourcePath,exports.sourceScanPass=sourceScanPass,exports.sourceSetSnapshot=sourceSetSnapshot,exports.sourceSetSignature=sourceSetSignature,exports.diffSourceSets=diffSourceSets,exports.sourceChangeWindowMs=sourceChangeWindowMs,exports.sourceScopeDirs=sourceScopeDirs,exports.podRecycleReason=podRecycleReason,exports.lensFrontendStale=lensFrontendStale,exports.lensWatcherVerdict=lensWatcherVerdict,exports.podWatcherVerdict=podWatcherVerdict,exports.lensChildWedged=lensChildWedged,exports.isPortConflictExit=isPortConflictExit,exports.createServiceLayer=createServiceLayer,exports.chooseStalePodKillTarget=chooseStalePodKillTarget;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),node_crypto_1=require("node:crypto"),fs=tslib_1.__importStar(require("node:fs")),http=tslib_1.__importStar(require("node:http")),net=tslib_1.__importStar(require("node:net")),path=tslib_1.__importStar(require("node:path")),node_util_1=require("node:util"),discovery_1=require("./discovery"),pod_sock_1=require("../../../pod-sock"),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"),handoff_retire_1=require("../../../handoff-retire"),process_name_1=require("../../../process-name");function eastWestEnv(t,a){const i={},d=t.serviceKeys[(0,service_keys_1.serviceKeyId)(a.wsKey,a.project)];if(d&&(i.LENSMCP_SERVICE_KEY=d),t.https){const f=require("../../../basic-ssl").caCertPath();fs.existsSync(f)&&(i.NODE_EXTRA_CA_CERTS=f)}return i}v(eastWestEnv,"eastWestEnv"),s(eastWestEnv,"eastWestEnv");const sleep=s(t=>new Promise(a=>setTimeout(a,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 a=2166136261;for(let i=0;i<t.length;i+=1)a^=t.charCodeAt(i),a=Math.imul(a,16777619);return a>>>0}v(fnv1a,"fnv1a"),s(fnv1a,"fnv1a");const execFileP=(0,node_util_1.promisify)(node_child_process_1.execFile);async function gitIgnoredPaths(t){try{const{stdout:a}=await execFileP("git",["ls-files","--others","--directory","--ignored","--exclude-standard","-z"],{cwd:t,maxBuffer:33554432,timeout:1e4,windowsHide:!0});return new Set(a.split("\0").filter(Boolean))}catch{return new Set}}v(gitIgnoredPaths,"gitIgnoredPaths"),s(gitIgnoredPaths,"gitIgnoredPaths");function scaleTarget(t,a=fs.existsSync){if(t.decl.adminUrl)return{url:t.decl.adminUrl};const i=path.join(t.pool.dir,"parent.sock");return a(i)?{socketPath:i}:t.decl.port?{url:`http://localhost:${t.decl.port}`}:void 0}v(scaleTarget,"scaleTarget"),s(scaleTarget,"scaleTarget");function isLoopbackHostname(t){const a=t.toLowerCase().replace(/^\[(.*)\]$/,"$1").replace(/\.$/,"");return a==="localhost"||a.endsWith(".localhost")?!0:net.isIPv4(a)?a.split(".")[0]==="127":net.isIPv6(a)&&new URL(`http://[${a}]/`).hostname==="[::1]"}v(isLoopbackHostname,"isLoopbackHostname"),s(isLoopbackHostname,"isLoopbackHostname");function postScale(t,a,i){let d;if("socketPath"in t)d=http.request({socketPath:t.socketPath,path:"/webpack/scale",method:"POST",headers:{"content-type":"application/json"}},f=>f.resume());else{const f=t.url.startsWith("https"),T=f?require("node:https"):http,y={method:"POST"};f&&isLoopbackHostname(new URL(t.url).hostname)&&(y.rejectUnauthorized=!1),i&&(y.lookup=i),d=T.request(`${t.url}/webpack/scale`,y,_=>_.resume())}return d.on("error",()=>{}),d.end(JSON.stringify({pods:a})),d}v(postScale,"postScale"),s(postScale,"postScale");function isOwnRunnerCommand(t,a){return(0,serve_launch_1.isServeRunnerCommand)(t)?(0,serve_launch_1.isServeRunnerCommandOf)(t,a):t.includes("/node_modules/nx/")?t.includes(`${a.replace(/\/+$/,"")}/node_modules/nx/`):!0}v(isOwnRunnerCommand,"isOwnRunnerCommand"),s(isOwnRunnerCommand,"isOwnRunnerCommand");function isRunnerOf(t,a,i){const d=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return!new RegExp(`(^|\\s)run ${d}(:|\\s|$)`).test(t)||!(0,serve_launch_1.isServeRunnerCommand)(t)&&!/\bnx(\.js)?\b/.test(t)?!1:isOwnRunnerCommand(t,i)}v(isRunnerOf,"isRunnerOf"),s(isRunnerOf,"isRunnerOf");const TEST_FILE_RE=/\.(spec|test|e2e|e2e-spec|stories)\.[cm]?[jt]sx?$/,TEST_DIR_SEGMENTS=new Set(["__tests__","__mocks__","__snapshots__"]);function isTestSourcePath(t){const a=t.split("/"),i=a[a.length-1]??"";if(TEST_FILE_RE.test(i))return!0;for(let d=0;d<a.length-1;d+=1)if(TEST_DIR_SEGMENTS.has(a[d]))return!0;return!1}v(isTestSourcePath,"isTestSourcePath"),s(isTestSourcePath,"isTestSourcePath");function sourceScanPass(t,a,i){const d=i?.ignoreTests??!0;let f;const T=new Map,y=`tree:${path.resolve(t)}`,_=s(g=>path.relative(t,g).split(path.sep).join("/"),"rel"),C=s(async(g,S)=>{const $=await(f??=gitIgnoredPaths(t)),L=[],I=new Set;let P=!1;const G=s(async(Q,w)=>{if(L.length>=SCAN_FILE_CAP){P=!0;return}let O;try{O=await fs.promises.readdir(Q,{withFileTypes:!0})}catch{return}w&&I.add(_(Q));for(const B of O){if(L.length>=SCAN_FILE_CAP){P=!0;return}const N=B.name,Z=path.join(Q,N);if(B.isDirectory()){if(!w||N.startsWith(".")||SRC_EXCLUDE.has(N)||a?.has(N)||N.endsWith("-devserver")||$.has(`${_(Z)}/`))continue;await G(Z,!0)}else if(B.isFile()){const ee=N.lastIndexOf(".");if(ee<=0||!SRC_EXT.has(N.slice(ee)))continue;const X=_(Z);if($.has(X)||d&&isTestSourcePath(X))continue;L.push(X)}}},"walk");return await G(g,S),{paths:L,entered:I,truncated:P}},"walkDir"),b=s((g,S)=>{let $=T.get(g);return $||($=S(),T.set(g,$)),$},"memo"),se=s(async()=>{const g=T.get(y);if(!g)return;const S=await g;return S.truncated?void 0:S},"wideWalk"),J=new Set,ne=s(()=>b(`files:${path.resolve(t)}`,async()=>{const g=await se();return g?{paths:g.paths.filter(S=>!S.includes("/")),entered:J,truncated:!1}:C(t,!1)}),"rootFiles"),ae=s(g=>b(`tree:${path.resolve(g)}`,async()=>{const S=await se(),$=_(g);if(S?.entered.has($)){const L=`${$}/`;return{paths:$===""?S.paths:S.paths.filter(I=>I.startsWith(L)),entered:J,truncated:!1}}return C(g,!0)}),"subtree"),U=s(g=>{const S=new Set;let $=0,L=g.some(P=>P.truncated);const I=s(P=>S.has(P)?!0:S.size>=SCAN_FILE_CAP?(L=!0,!1):(S.add(P),$^=fnv1a(P),!0),"add");for(const P of g)if(!P.paths.every(I))break;return{signature:`${S.size}:${($>>>0).toString(36)}`,paths:S,truncated:L}},"compose");return{async snapshot(g){if(!g||g.length===0)return U([await b(y,()=>C(t,!0))]);const S=[await ne()];for(const $ of g)S.push(await ae($));return U(S)}}}v(sourceScanPass,"sourceScanPass"),s(sourceScanPass,"sourceScanPass");async function sourceSetSnapshot(t,a,i,d){return sourceScanPass(t,i,d).snapshot(a)}v(sourceSetSnapshot,"sourceSetSnapshot"),s(sourceSetSnapshot,"sourceSetSnapshot");async function sourceSetSignature(t,a,i,d){return(await sourceSetSnapshot(t,a,i,d)).signature}v(sourceSetSignature,"sourceSetSignature"),s(sourceSetSignature,"sourceSetSignature");const SOURCE_DIFF_PATH_CAP=128;function diffSourceSets(t,a,i,d){if(t.paths.size===a.paths.size){let _=!0;for(const C of t.paths)if(!a.paths.has(C)){_=!1;break}if(_)return null}const f=[],T=[];let y=t.truncated||a.truncated;for(const _ of a.paths)t.paths.has(_)||(f.length+T.length<SOURCE_DIFF_PATH_CAP?f.push(_):y=!0);for(const _ of t.paths)a.paths.has(_)||(f.length+T.length<SOURCE_DIFF_PATH_CAP?T.push(_):y=!0);return{changedAt:i,added:f,removed:T,truncated:y,...d!==void 0?{windowMs:d}:{}}}v(diffSourceSets,"diffSourceSets"),s(diffSourceSets,"diffSourceSets");function sourceChangeWindowMs(t,a,i,d){const f=a&&a>0?t-a:i;return Math.max(f,i)+d}v(sourceChangeWindowMs,"sourceChangeWindowMs"),s(sourceChangeWindowMs,"sourceChangeWindowMs");function sourceScopeDirs(t,a,i,d,f){const T=s(C=>{const b=path.relative(t,C).split(path.sep).filter(Boolean);return b.length===0||b[0].startsWith("..")?"":b[0]},"bucketOf")(a);if(!T)return[];const y=new Set(i.filter(Boolean)),_=new Set([T]);for(const C of d)C.startsWith(".")||SRC_EXCLUDE.has(C)||f?.has(C)||C.endsWith("-devserver")||y.has(C)||_.add(C);return[..._].sort().map(C=>path.join(t,C))}v(sourceScopeDirs,"sourceScopeDirs"),s(sourceScopeDirs,"sourceScopeDirs");function podRecycleReason(t,a,i,d){return!t.proc||t.state!=="up"||t.spawnAt==null||t.inflight>0||(t.activeWork??0)>0||a-t.lastUsed<d.settleMs||t.lastStaleRecycleAt!=null&&a-t.lastStaleRecycleAt<d.cooldownMs?null:i>0&&i-t.spawnAt>d.graceMs?"stale-source-set":d.maxAgeMs>0&&a-t.spawnAt>d.maxAgeMs?"max-age":null}v(podRecycleReason,"podRecycleReason"),s(podRecycleReason,"podRecycleReason");function lensFrontendStale(t,a,i,d,f){return!(d-a<f.cooldownMs||i<=0||i-t<=f.graceMs||d-i<f.settleMs)}v(lensFrontendStale,"lensFrontendStale"),s(lensFrontendStale,"lensFrontendStale");function lensWatcherVerdict(t,a,i,d=[]){if(!t)return"unknown";if("schemaVersion"in t&&t.schemaVersion>=2&&d.length>0){const f=a-i,T=new Set(t.recentEvents.filter(y=>y.at>=f).map(y=>path.resolve(y.path)));return d.every(y=>T.has(path.resolve(y)))?"heard":"missed"}return t.lastFsSetEventAt>=a-i?"heard":"missed"}v(lensWatcherVerdict,"lensWatcherVerdict"),s(lensWatcherVerdict,"lensWatcherVerdict");function podWatcherVerdict(t,a,i){if(!t)return"unknown";if(t.degraded)return"degraded";const d=a-i;return t.lastWatchRunAt>=d||t.lastBuildAt>=d?"heard":"silent"}v(podWatcherVerdict,"podWatcherVerdict"),s(podWatcherVerdict,"podWatcherVerdict");function lensChildWedged(t,a,i,d){const f=t.hasListened?d.graceMs:Math.max(d.graceMs,d.neverListenedGraceMs);return i-t.spawnedAt<f||i-a<d.cooldownMs||t.probeFails<Math.max(2,d.failureThreshold)?null:"wedged-no-listener"}v(lensChildWedged,"lensChildWedged"),s(lensChildWedged,"lensChildWedged");function isPortConflictExit(t,a){return t.signal||t.code===null||t.code===0||t.ranMs>a.fastFailMs?!1:t.portHeld}v(isPortConflictExit,"isPortConflictExit"),s(isPortConflictExit,"isPortConflictExit");function createServiceLayer(t,a,i={}){const d=i.spawnProcess??node_child_process_1.spawn,f=i.probeAlive??pod_probe_1.probePodAlive,T=i.killTree??((e,o="SIGTERM")=>{try{process.kill(-e.pid,o)}catch{try{e.kill(o)}catch{}}}),y=i.sharedBuild??(0,shared_build_1.createSharedBuildRelay)(t),_=s((e,o="SIGTERM")=>{y.detach(e),T(e,o)},"killTree"),C=i.handoffDrainMs??(0,handoff_retire_1.handoffDrainBoundMs)(),b=i.treeAlive??(e=>{if(!e.pid)return!1;try{return process.kill(-e.pid,0),!0}catch(o){return o.code==="EPERM"}}),se=i.retirePollMs??1e3,J=i.rollReadyMs??types_1.POD_ROLL_READY_MS,ne=i.rollPollMs??750,ae=i.maxConcurrentRolls??types_1.POD_MAX_CONCURRENT_ROLLS,U=ae>0?ae:Number.POSITIVE_INFINITY,g=i.maxConcurrentColdStarts??types_1.MAX_COLD_STARTS,S=g>0?g:Number.POSITIVE_INFINITY;let $=0;const L=[],I=s(()=>$<S?($+=1,Promise.resolve()):new Promise(e=>{L.push(()=>{$+=1,e()})}),"acquireColdSlot"),P=s(()=>{$-=1,L.shift()?.()},"releaseColdSlot"),{emit:G}=a,Q=s(e=>e.wsKey===t.wsKey?void 0:path.join(e.root,".lensmcp","events.jsonl"),"serviceBus"),w=s((e,o,n,r,c)=>G(o,n,r,{wsKey:e.wsKey,...c},void 0,Q(e)),"emitSvc"),O=s(e=>(e.scannedAt=0,(0,discovery_1.pickSock)(e,t.scanTtlMs)?e.socks.length:0),"scanNow"),B=s((e,o)=>{let n;try{n=(0,node_child_process_1.execFileSync)("ps",["ax","-ww","-o","pid=,pgid=,command="],{encoding:"utf8",timeout:5e3})}catch{return 0}const r=e.proc?.pid;let c=0;for(const l of n.split(`
|
|
2
|
+
`)){const p=l.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);if(!p)continue;const u=Number(p[1]),R=Number(p[2]);if(isRunnerOf(p[3],o,e.root)&&!(u===process.pid||u===r)&&!(r!==void 0&&R===r)){try{process.kill(-R,"SIGKILL")}catch{try{process.kill(u,"SIGKILL")}catch{}}c+=1}}return c>0&&(console.log(`[gateway] ${o}: reaped ${c} stale runner tree(s) \u2014 a foreign generation of this service (holding nx's task lock, or its pool)`),w(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"),N=s((e,o)=>({...(0,pod_env_1.podBaseEnv)(process.env,t.root,e.root),...e.instanceEnv?{...e.instanceEnv,LENSMCP_SOCKET_ONLY:"1"}:{},...e.frozen?{LENSMCP_FREEZE:"1",LENSMCP_TYPECHECK:"off"}:{},NX_DAEMON:"false",NX_ISOLATE_PLUGINS:"false",...e.wsKey?{LENSMCP_WS_KEY:e.wsKey}:{},...e.sockNs?{LENSMCP_SOCK_NS:e.sockNs}:{},LENSMCP_EVENT_FILE:e.wsKey===t.wsKey?t.eventFile:path.join(e.root,".lensmcp","events.jsonl"),LENSMCP_GATEWAY_PID:String(process.pid),LENSMCP_POD_GENERATION:String(o),...eastWestEnv(t,e)}),"serviceEnv"),Z=s((e,o,n,r)=>{const c=N(e,o),l=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:c});if(p.kind==="runner"){const h=y.offeredTo(e),E=d((0,process_name_1.namedExecPath)((0,process_name_1.processName)(l,e.project,serve_launch_1.SERVE_RUNNER_ROLE)),[...p.script.execArgv,p.script.path,...(0,serve_launch_1.serveRunnerArgs)(n,r,e.root)],{cwd:e.root,detached:!0,env:h?{...p.env,[protocol_1.SHARED_BUILD_ENV]:"1"}:p.env,stdio:["ignore","pipe","pipe","ipc"]});return h&&y.attach(e,o,E),E.send?.(p.message,k=>{k&&console.error(`[gateway] ${n}: could not hand the task to its runner (${k.message})`)}),{proc:E,launcher:"runner"}}console.log(`[gateway] ${n}: starting through nx run \u2014 ${p.reason}`);const u=path.join(e.root,"node_modules","nx","dist","bin","nx.js"),R=(0,process_name_1.namedExecPath)((0,process_name_1.processName)(l,e.project,"nx")),[M,m]=fs.existsSync(u)?[R,[u,"run",n,`--childCount=${r}`]]:["yarn",["nx","run",n,`--childCount=${r}`]];return{proc:d(M,m,{cwd:e.root,detached:!0,env:c,stdio:["ignore","pipe","pipe"]}),launcher:"nx"}},"startServiceTree"),ee=new WeakMap,X=s((e,o,n)=>{const r=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,c=Math.max(1,e.decl.minPods??1),{proc:l,launcher:p}=Z(e,o,r,c);ee.set(l,p),n==="active"&&(e.launcher=p),n==="active"&&(e.lastErrors=[],e.buildFailed=!1,e.faults=new pod_output_1.PodFaults);const u=s(m=>{const h=m.toString();for(const E of h.split(`
|
|
3
|
+
`)){const k=(0,pod_output_1.classifyPodLine)(E),{clean:A,broken:F,healthy:j}=k,W=A;if(F){e.lastErrors.push(A),e.lastErrors.length>12&&e.lastErrors.shift(),e.buildFailed=!0;const D=Date.now();(A!==e.lastErrorEmitSig||D-(e.lastErrorEmitAt??0)>=types_1.SERVICE_ERROR_THROTTLE_MS)&&(e.lastErrorEmitSig=A,e.lastErrorEmitAt=D,w(e,"error",`service error: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-error",project:e.project,line:A,fault:k.fault}))}j&&(e.buildFailed=!1);const x=(e.faults??=new pod_output_1.PodFaults).observe(k);if(x){e.lastErrorEmitSig=void 0;const D=x.open.length===0;w(e,"info",`${D?"service recovered":`${x.closed.join(" + ")} recovered`}: ${e.project}`,`cluster-service:${e.project}`,{kind:D?"service-recovered":"service-fault-closed",project:e.project,line:A,closed:x.closed,open:x.open})}!e.lockBlockedAt&&/Waiting for .+ in another nx process/.test(W)&&(e.lockBlockedAt=Date.now(),console.log(`[gateway] ${r}: spawn blocked by a stale nx runner \u2014 breaking the lock + respawning`),w(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||(K(e,"parked on a foreign nx task lock \u2014 recycling",{announce:!1}),B(e,r),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&V(e)},1500).unref?.())},250).unref?.())}},"sniff"),R=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,M=s(m=>h=>{if(t.stopped())return;const E=(0,inspector_secret_1.redactInspectorUrls)(h.toString());u(E);const k=`[${e.project}] ${E}`;m.write(k),R?.(k)},"relay");return l.stdout?.on("data",M(process.stdout)),l.stderr?.on("data",M(process.stderr)),l.on("exit",(m,h)=>{if(e.proc!==l||(e.proc=void 0,e.state="down",t.stopped()))return;const E=(0,exit_reason_1.describeExit)(m,h);if(console.log(`[gateway] ${r} exited (${m??"no code"}${h?`, ${h}`:""}${e.inflight?`, ${e.inflight} request(s) in flight`:""})${E?` \u2014 ${E}`:""}`),w(e,m===0?"info":"warning",`service down: ${e.project} (exit ${m})`,`cluster-service:${e.project}`,{kind:"service-down",project:e.project,code:m,...h?{signal:h}:{}}),m===0||e.buildFailed)return;const k=Date.now();if(e.restarts=(e.restarts??[]).filter(F=>k-F<types_1.SERVICE_RESTART_WINDOW_MS),e.restarts.length>=types_1.SERVICE_MAX_RESTARTS){w(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 A=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})`),w(e,"info",`self-heal: respawning ${e.project}`,`cluster-service:${e.project}`,{kind:"service-respawn",project:e.project,attempt:e.restarts.length}),V(e))},A)}),l},"launchServiceProcess"),V=s(e=>{if(t.stopped())return;const o=`${e.project}:${e.decl.serveTarget??"serve-hmr"}`,n=Math.max(1,e.decl.minPods??1);i.reapForeign!==!1&&B(e,o),e.lockBlockedAt=void 0,console.log(`[gateway] starting ${o} (pods=${n})\u2026`),w(e,"info",`service starting: ${e.project} (${n} pod${n>1?"s":""})`,`cluster-service:${e.project}`,{kind:"service-starting",project:e.project,pods:n}),e.state="starting",e.spawnAt=Date.now(),e.generation=(e.generation??0)+1,e.proc=X(e,e.generation,"active")},"spawnService"),Ee=s(async e=>{const o=e.pool.socks[0];if(!o)return;const n=await(0,pod_probe_1.probePodAlive)(o,void 0,t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)]);n&&(e.activeWork=n.activeWork,n.watcher&&(e.watcher=n.watcher))},"refreshActiveWork"),ke=s(e=>{try{const o=Number(fs.readFileSync(`${e}.owner`,"utf8").trim());return Number.isInteger(o)&&o>1?o:void 0}catch{return}},"readOwnerSidecar"),je=s(async e=>{const o=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)];if(!o)return[];const n=(0,node_crypto_1.createHash)("sha256").update(o).digest("hex").slice(0,8),r=[];for(const c of e.pool.socks){const l=await(0,pod_probe_1.probePodAlive)(c,void 0,o);l&&(!l.keyFingerprint||l.keyFingerprint!==n)&&r.push({sock:c,pid:l.pid})}return r},"staleAdoptedSocks"),Re=s((e,o)=>{const n=o.map(r=>r.sock);for(const{sock:r,pid:c}of o){const l=chooseStalePodKillTarget(c,ke(r),process.pid);if(l!==void 0)try{process.kill(l,"SIGTERM")}catch{}try{fs.unlinkSync(r)}catch{}try{fs.unlinkSync(`${r}.owner`)}catch{}}e.pool.socks=e.pool.socks.filter(r=>!n.includes(r)),e.pool.idx=-1,e.pool.scannedAt=0},"evictStaleAdopted"),ce=s((e,o)=>{if(e.state==="up")return!1;e.state="up",e.wedgeRecycles=0;const n=e.pool.socks.length;return w(e,"info",`service up: ${e.project} (${n} pods, ${o})`,`cluster-service:${e.project}`,{kind:"service-up",project:e.project,pods:n,adopted:o==="adopted"}),!0},"markUp"),de=s(async e=>{if(O(e.pool)>0){const r=await je(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.`),w(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&&K(e,"stale adoption (foreign east-west key)"),Re(e,r)),e.pool.socks.length>0)return ce(e,"adopted"),!0}if(t.stopped())return!1;let o=!1;const n=s(()=>e.state!=="starting"&&!e.proc,"notStarted");if(n())if(e.coldWait)await e.coldWait;else{$>=S&&(console.log(`[gateway] cold start queued: ${e.project}${e.wsKey&&e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${$} starting, ${L.length} ahead (LENSMCP_MAX_COLD_STARTS=${S}).`),w(e,"info",`cold start queued: ${e.project}`,`cluster-service:${e.project}`,{kind:"cold-start-queued",project:e.project,inFlight:$,ahead:L.length}));const r=I();if(e.coldWait=r,await r,e.coldWait=void 0,o=!0,t.stopped())return P(),!1;n()&&V(e)}try{return await Ce(e)}finally{o&&P()}},"ensureUp"),Ce=s(async e=>{const o=Date.now()+t.startupTimeoutMs,n=Date.now()+12e3;for(;!t.stopped()&&Date.now()<o;){if(await sleep(Math.min(700,t.startupTimeoutMs/4)),e.buildFailed&&Date.now()>n)return!1;if(O(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`:""})`),w(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},"waitForPods"),Te=s(e=>{const o=e.decl.maxPods??e.decl.minPods??1,n=e.pool.socks.length,r=scaleTarget(e);!r||n===0||n>=o||e.inflight<=n||Date.now()-e.lastScaleAt<5e3||(e.lastScaleAt=Date.now(),postScale(r,n+1,i.scaleLookup),console.log(`[gateway] autoscale ${e.project}: ${n} \u2192 ${n+1} pods (inflight ${e.inflight})`),w(e,"info",`autoscale: ${e.project} ${n} \u2192 ${n+1} pods`,`cluster-service:${e.project}`,{kind:"autoscale",project:e.project,from:n,to:n+1,inflight:e.inflight}))},"scaleUp"),K=s((e,o,n)=>{if(!e.proc)return;console.log(`[gateway] ${e.project} ${o} \u2014 scaling to zero.`),n?.announce!==!1&&w(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{}(0,discovery_1.stopRoutingTo)(e.pool),_(r,"SIGTERM"),e.proc=void 0,e.state="down",setTimeout(()=>{ie(e),e.pool.socks=[],e.pool.scannedAt=0},1200).unref?.()},"killSpawned"),ie=s(e=>{try{for(const o of fs.readdirSync(e.pool.dir)){if(!o.endsWith(".sock"))continue;const n=path.join(e.pool.dir,o);try{const r=Number(fs.readFileSync(`${n}.owner`,"utf8").trim());if(Number.isFinite(r)&&r>0)try{process.kill(r,0);continue}catch{}}catch{}try{fs.unlinkSync(n)}catch{}try{fs.unlinkSync(`${n}.owner`)}catch{}}}catch{}},"sweepDeadOwnerSocks");let H=0;const pe=[],Ae=s(()=>H<U?(H+=1,Promise.resolve()):new Promise(e=>{pe.push(()=>{H+=1,e()})}),"acquireRollSlot"),Me=s(()=>{H-=1,pe.shift()?.()},"releaseRollSlot"),ye=s(async(e,o,n)=>{O(e.pool);for(const r of e.pool.socks){const c=await f(r,void 0,n);if(c&&o(c.podGeneration))return!0}return!1},"anyPodAnswers"),ue=new Map,xe=s((e,o,n)=>{const r=C>0&&(0,handoff_retire_1.requestHandoffRetire)(e.pool.dir,n);if(_(o,"SIGTERM"),!r)return;const c=e.wsKey&&e.wsKey!==t.wsKey&&e.root?(0,workspace_log_1.workspaceLogWriter)(e.root):void 0,l=s(k=>A=>{if(t.stopped())return;const F=(0,inspector_secret_1.redactInspectorUrls)(A.toString()).replace(/^(?=.)/gm,`[${e.project} g${n} retiring] `);k.write(F),c?.(F)},"say"),p=l(process.stdout),u=l(process.stderr);o.stdout?.on("data",p),o.stderr?.on("data",u);let R=!1;const M=s(()=>{R||(R=!0,clearTimeout(m),clearInterval(E),o.stdout?.removeListener("data",p),o.stderr?.removeListener("data",u),ue.delete(o),(0,handoff_retire_1.clearHandoffRetire)(e.pool.dir,n),ie(e),e.pool.scannedAt=0)},"end"),m=setTimeout(()=>{console.error(`[gateway] pod handoff: ${e.project} g${n} was still running ${C}ms after it was retired \u2014 killing it.`),T(o,"SIGKILL"),M()},C);m.unref?.();const h=s(()=>{if(!R){if(t.stopped()){M();return}b(o)||(console.log(`[gateway] pod handoff: ${e.project} g${n} finished its in-flight work and exited.`),M())}},"look"),E=setInterval(h,se);E.unref?.(),o.once("exit",h),ue.set(o,()=>{T(o,"SIGTERM"),M()})},"retireReplacedTree"),Pe=s(()=>{for(const e of[...ue.values()])e()},"stopRetiringTrees"),he=s((e,o)=>{if(e.rolling)return e.rolling;const n=e.proc;if(!n||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);H>=U&&console.log(`[gateway] pod handoff queued: ${e.project} reason=${o} \u2014 ${H} roll(s) in flight, ${pe.length} ahead (LENSMCP_POD_MAX_CONCURRENT_ROLLS=${U}).`);const r=(async()=>{await Ae();try{return await De(e,n,o)}finally{Me()}})().finally(()=>{e.rolling=void 0});return e.rolling=r,r},"rollPod"),De=s(async(e,o,n)=>{const r=e.proc;if(r!==o||t.stopped())return!1;const c=e.generation??0,l=c+1,p=Date.now();e.lastStaleRecycleAt=p,console.log(`[gateway] pod handoff start: ${e.project} g${c}\u2192g${l} reason=${n} \u2014 standby spawning, old generation keeps serving.`),w(e,"info",`pod handoff starting: ${e.project} (${n})`,`cluster-service:${e.project}`,{kind:"pod-handoff-start",project:e.project,reason:n,generation:l,fromGeneration:c});const u=X(e,l,"standby"),R=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],M=p+J;let m;for(;!t.stopped()&&Date.now()<M&&!(u.exitCode!==null||u.signalCode!==null);){O(e.pool);for(const h of e.pool.socks)if((await f(h,void 0,R))?.podGeneration===l){m=h;break}if(m)break;await sleep(ne)}if(!m||t.stopped()){const h=t.stopped()?"gateway stopping":u.exitCode!==null||u.signalCode!==null?`standby exited (${u.exitCode??u.signalCode})`:`standby not ready within ${Math.round(J/1e3)}s`;try{u.stdout?.removeAllListeners("data"),u.stderr?.removeAllListeners("data")}catch{}(0,discovery_1.stopRoutingTo)(e.pool,A=>(0,pod_sock_1.podSockGeneration)(A)===l),_(u,"SIGTERM");const E=!t.stopped()&&e.proc===r&&r.exitCode===null&&r.signalCode===null&&await ye(e,A=>A!==l,R),k=E?`old generation g${c} kept serving`:`and the old generation g${c} is NOT serving (${e.proc!==r||r.exitCode!==null||r.signalCode!==null?"its tree is gone":"none of its pods answers"}) \u2014 the service is down until it recovers`;return console.error(`[gateway] pod handoff failed: ${e.project} g${l}: ${h} \u2014 ${k}.`),e.rollFailure=`${h} \u2014 ${k}`,w(e,E?"warning":"error",`pod handoff failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-failed",project:e.project,reason:n,generation:l,error:h,durationMs:Date.now()-p,oldServing:E}),setTimeout(()=>ie(e),1200).unref?.(),!1}if(e.proc!==r)return(0,discovery_1.stopRoutingTo)(e.pool,h=>(0,pod_sock_1.podSockGeneration)(h)===l),_(u,"SIGTERM"),!1;e.proc=u,e.launcher=ee.get(u),e.generation=l,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(0,discovery_1.stopRoutingTo)(e.pool,h=>(0,pod_sock_1.podSockGeneration)(h)!==l&&path.join(e.pool.dir,h)!==m),xe(e,r,c),setTimeout(()=>{ie(e),e.pool.scannedAt=0},1200).unref?.(),console.log(`[gateway] pod handoff ready: ${e.project} g${l} answered on ${path.basename(m)} in ${Date.now()-p}ms; retiring g${c}.`),w(e,"info",`pod handoff complete: ${e.project}`,`cluster-service:${e.project}`,{kind:"pod-handoff-complete",project:e.project,reason:n,generation:l,fromGeneration:c,durationMs:Date.now()-p}),!0},"runRoll"),Le=s((e,o)=>{if(t.stopped())return{refused:"the gateway is stopping"};if(e.restart&&!e.restart.done)return{restart:e.restart,already:!0};if(!e.proc&&e.state!=="down")return{refused:`${e.project} is served by pods this gateway adopted from an earlier one \u2014 it has no tree of its own to replace. \`lensmcp gateway restart\` starts every service under this gateway.`};const n=e.proc?o.hard||e.launcher==="nx"||e.state!=="up"?"hard":"rolling":"start",r={how:n,reason:o.reason,startedAt:Date.now(),...e.proc&&e.generation!==void 0?{fromGeneration:e.generation}:{}};return e.restart=r,console.log(`[gateway] restart asked: ${e.project}${e.wsKey!==t.wsKey?` (${e.wsKey})`:""} \u2014 ${n} (${o.reason}).`),w(e,"info",`restart asked: ${e.project} (${n})`,`cluster-service:${e.project}`,{kind:"service-restart",phase:"started",project:e.project,how:n,reason:o.reason,...r.fromGeneration!==void 0?{fromGeneration:r.fromGeneration}:{}}),s(async()=>{if(n==="start")return await de(e)?{ok:!0}:{ok:!1,error:fe(e)};if(n==="rolling")return e.rolling&&await e.rolling,e.proc?(e.rollFailure=void 0,await he(e,o.reason)?{ok:!0}:{ok:!1,error:e.rollFailure??"the new tree did not take over"}):await de(e)?{ok:!0}:{ok:!1,error:fe(e)};if(K(e,`restart asked (${o.reason})`,{announce:!1}),await sleep(1500),t.stopped())return{ok:!1,error:"the gateway is stopping"};let c=!1;e.proc||(await I(),c=!0,!e.proc&&e.state!=="starting"&&V(e));try{const l=e.proc,p=e.generation,u=t.serviceKeys[(0,service_keys_1.serviceKeyId)(e.wsKey,e.project)],R=Date.now()+t.startupTimeoutMs;for(;!t.stopped()&&Date.now()<R;){if(!l||e.proc!==l||l.exitCode!==null||l.signalCode!==null)return{ok:!1,error:fe(e)};if(await ye(e,M=>M===p,u))return ce(e,"observed"),e.buildFailed=!1,{ok:!0};await sleep(ne)}return{ok:!1,error:t.stopped()?"the gateway is stopping":`no pod of the new tree answered within ${Math.round(t.startupTimeoutMs/1e3)}s`}}finally{c&&P()}},"run")().catch(c=>({ok:!1,error:c.message})).then(c=>{r.done={ok:c.ok,at:Date.now(),...e.generation!==void 0?{generation:e.generation}:{},...c.error?{error:c.error}:{}};const l=Math.round((r.done.at-r.startedAt)/100)/10;c.ok?console.log(`[gateway] restarted: ${e.project} \u2014 g${r.done.generation??"?"} serving after ${l}s (${n}).`):console.error(`[gateway] restart failed: ${e.project} \u2014 ${c.error} (${n}, after ${l}s).`),w(e,c.ok?"info":"error",c.ok?`restarted: ${e.project}`:`restart failed: ${e.project}`,`cluster-service:${e.project}`,{kind:"service-restart",phase:c.ok?"done":"failed",project:e.project,how:n,reason:o.reason,...r.fromGeneration!==void 0?{fromGeneration:r.fromGeneration}:{},...r.done.generation!==void 0?{generation:r.done.generation}:{},durationMs:r.done.at-r.startedAt,...c.error?{error:c.error}:{}})}),{restart:r,already:!1}},"restartService"),fe=s(e=>e.buildFailed?`its build failed${e.lastErrors?.length?`: ${e.lastErrors[e.lastErrors.length-1]}`:""}`:e.proc&&(e.proc.exitCode!==null||e.proc.signalCode!==null)?`its tree exited (${e.proc.exitCode??e.proc.signalCode})`:!e.proc&&e.state==="down"?"its tree exited before a pod answered":`no pod answered within ${Math.round(t.startupTimeoutMs/1e3)}s`,"startFailure"),$e=s(e=>{const o=Date.now();e.restarts=(e.restarts??[]).filter(r=>o-r<types_1.SERVICE_RESTART_WINDOW_MS);const n=(e.wedgeRecycles??0)+1;if(e.restarts.length>=types_1.SERVICE_MAX_RESTARTS||n>types_1.SERVICE_MAX_RESTARTS){const r=e.restarts.length>=types_1.SERVICE_MAX_RESTARTS?`wedged ${types_1.SERVICE_MAX_RESTARTS}+\xD7/min`:`recycled ${types_1.SERVICE_MAX_RESTARTS}\xD7 in a row and no pod came up`;w(e,"error",`service crash-looping: ${e.project} \u2014 ${r}, not auto-recycling (the next request retries)`,`cluster-service:${e.project}`,{kind:"service-crash-loop",project:e.project,recycles:n-1}),K(e,"wedged (crash-loop cap) \u2014 leaving down for the next request",{announce:!1});return}e.wedgeRecycles=n,e.restarts.push(o),console.log(`[gateway] self-heal: recycling WEDGED ${e.project} (live parent, 0 live pods; attempt ${n}/${types_1.SERVICE_MAX_RESTARTS} with no pod up since the first, ${e.restarts.length} in the last minute; ${e.inflight??0} request(s) in flight \u2014 jobs held by the dead pod keep their locks until they expire)`),w(e,"warning",`self-heal: recycling wedged ${e.project} (0 live pods)`,`cluster-service:${e.project}`,{kind:"service-recycle",project:e.project,attempt:n,inWindow:e.restarts.length}),K(e,"wedged: 0 live pods \u2014 recycling",{announce:!1}),setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&V(e)},1500).unref?.()},"recycleWedged"),be={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 ge;const te=new Map,_e=new Map;let we=!1,Se=0,le=0;const Ie=s(()=>{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 n=new Set;for(const r of o){const c=e[r];if(!c)continue;const l=path.relative(t.root,c).split(path.sep).filter(Boolean)[0];l&&!l.startsWith("..")&&n.add(l)}return[...n]},"managedBuckets"),Oe=s((e,o,n,r)=>{const c=t.projectSourceRoots?.[e];if(c?.length)return c;const l=t.projectRoots?.[e];if(!l)return;const p=sourceScopeDirs(t.root,l,n,o,r);return p.length>0?p:void 0},"scopeFor"),Ne=s(()=>{if(we||Date.now()-Se<types_1.POD_STALE_SCAN_MS)return;we=!0,Se=Date.now();const e=Se;(async()=>{const o=(0,scope_1.readSourceSetOptions)(t.root),n=o.exclude,r={ignoreTests:o.ignoreTests},c=sourceScanPass(t.root,n,r),l=await c.snapshot(),p=Date.now(),u=sourceChangeWindowMs(p,le,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS),R=ge?diffSourceSets(ge,l,p,u):null;if(R&&(t.sourceSetChangedAt=p,t.sourceSetChange=R,G("info","source file set changed \u2014 stale pods recycle when idle","cluster-gateway",{kind:"source-set-changed",added:R.added,removed:R.removed,truncated:R.truncated})),ge=l,!t.projectRoots){le=e;return}let M;try{M=(await fs.promises.readdir(t.root,{withFileTypes:!0})).filter(j=>j.isDirectory()).map(j=>j.name)}catch{return}const m=Ie(),h=new Set(t.services.map(j=>j.project));for(const j of t.routes)j.lens&&h.add(j.lens.project);const E=new Map;for(const j of h){const W=Oe(j,M,m,n);if(!W)continue;const x=W.join("|"),D=E.get(x)??{dirs:W,projects:[]};D.projects.push(j),E.set(x,D)}const k=Date.now(),A=sourceChangeWindowMs(k,le,types_1.POD_STALE_SCAN_MS,types_1.POD_RECYCLE_SETTLE_MS);le=e;const F=s((j,W,x)=>{t.sourceSetChangedFor??=new Map,t.sourceSetChangesFor??=new Map;for(const D of j)t.sourceSetChangedFor.set(D,W),t.sourceSetChangesFor.set(D,x)},"stampScoped");for(const[j,{dirs:W,projects:x}]of E){const D=await c.snapshot(W),me=te.get(j);te.set(j,D);const ve=new Map;for(const Y of x){const q=_e.get(Y);if(q===void 0||q===j)continue;const z=ve.get(q)??[];z.push(Y),ve.set(q,z)}for(const Y of x)_e.set(Y,j);for(const[Y,q]of ve){const z=te.get(Y),oe=z?diffSourceSets(z,D,k,A):R;oe&&(F(q,z?k:p,oe),G("info",`source file set changed \u2014 ${q.join(", ")}'s watch scope was re-keyed`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,rescoped:!0,projects:q,added:oe.added,removed:oe.removed,truncated:oe.truncated}))}if(!me)continue;const re=diffSourceSets(me,D,k,A);re&&(F(x,k,re),G("info",`source file set changed in ${x.join(", ")}'s scope`,"cluster-gateway",{kind:"source-set-changed",scoped:!0,projects:x,added:re.added,removed:re.removed,truncated:re.truncated}))}for(const j of[...te.keys()])E.has(j)||te.delete(j)})().catch(()=>{}).finally(()=>{we=!1})},"maybeScanSourceSet"),Fe=s((e,o)=>{const n=(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,n),c=s(l=>(e.staleKeptAt!==o&&(e.staleKeptAt=o,console.log(`[gateway] stale source set: pod ${e.project} ${l} \u2014 no recycle.`)),!1),"keep");return r==="heard"?(e.staleVerifiedAt=o,w(e,"info",`stale check: ${e.project} watcher confirmed \u2014 no recycle`,`cluster-service:${e.project}`,{kind:"pod-stale-verified",project:e.project,watcher:r}),c(`watcher CONFIRMED alive across the change (last build ${new Date(e.watcher?.lastBuildAt??0).toISOString()})`)):r==="degraded"?(console.log(`[gateway] stale source set: pod ${e.project} watcher DEGRADED (${e.watcher?.degraded}) \u2014 recycling.`),!0):r==="silent"?types_1.POD_WATCHER_SILENT==="keep"?c("watcher alive but quiet across the change \u2014 keeping it (LENSMCP_POD_WATCHER_SILENT=keep; a live watcher cannot miss an add it will need)"):!0:types_1.POD_WATCHER_UNVERIFIED==="keep"?c("unverifiable (no watcher testimony \u2014 older devserver bundle?) \u2014 keeping it (LENSMCP_POD_WATCHER_UNVERIFIED=keep)"):!0},"podStaleRecycleAllowed"),We=s((e,o)=>{if(e.lastStaleRecycleAt=Date.now(),types_1.POD_RECYCLE_MODE==="rolling"&&e.proc&&e.launcher!=="nx"){he(e,o);return}console.log(`[gateway] zombie recycle: ${e.project} (${o}) \u2014 stale fs view; fresh pod incoming`),w(e,"warning",`zombie recycle: ${e.project} (${o})`,`cluster-service:${e.project}`,{kind:"pod-recycle",project:e.project,reason:o});const n=!!e.decl.eager;K(e,`${o} \u2014 recycling stale pod`,{announce:!1}),n&&setTimeout(()=>{!t.stopped()&&!e.proc&&e.state==="down"&&V(e)},1500).unref?.()},"recyclePod"),Ke=s(()=>{const e=setInterval(()=>{qe().catch(o=>console.error("[gateway] sweep error:",o))},t.sweepMs);return e.unref(),e},"startSweeper"),qe=s(async()=>{{if(t.stopped())return;for(const e of t.graphStores?.all()??[])e.refreshIfStale();types_1.POD_RECYCLE_ENABLED&&Ne();for(const e of t.registry?t.registry.allServices():t.services){if(t.stopped()||!e.proc){e.wedgedSince=void 0;continue}const o=path.resolve(e.root)===path.resolve(t.root),n=e.decl.idleKillSec;if(n&&e.inflight===0&&Date.now()-e.lastUsed>n*1e3){K(e,`idle ${n}s`),e.wedgedSince=void 0;continue}const r=e.state==="starting"&&e.spawnAt!==void 0&&Date.now()-e.spawnAt<t.startupTimeoutMs,c=O(e.pool)>0;if(c||r){if(e.wedgedSince=void 0,c&&ce(e,"observed"),c&&!r&&types_1.POD_RECYCLE_ENABLED&&o){const l=t.sourceSetChangedFor?.get(e.project)??t.sourceSetChangedAt??0;await Ee(e);const p=(e.staleVerifiedAt??0)>=l&&l>0,u=podRecycleReason(e,Date.now(),p?0:l,be);if(u==="stale-source-set"&&!Fe(e,l))continue;u&&We(e,u)}continue}e.wedgedSince??=Date.now(),Date.now()-e.wedgedSince>types_1.WEDGE_RECYCLE_GRACE_MS&&(e.wedgedSince=void 0,$e(e))}}},"sweepOnce");return{scanNow:O,spawnService:V,ensureUp:de,scaleUp:Te,killSpawned:K,markUp:ce,startSweeper:Ke,rollPod:he,restartService:Le,recycleWedged:$e,stopRetiringTrees:Pe,stopBuildHost:s(e=>e===void 0?y.stop():y.release(e),"stopBuildHost")}}v(createServiceLayer,"createServiceLayer"),s(createServiceLayer,"createServiceLayer");function chooseStalePodKillTarget(t,a,i){if(!(t===void 0||!Number.isInteger(t)||t<=1)&&!(a!==void 0&&a!==t)&&t!==i)return t}v(chooseStalePodKillTarget,"chooseStalePodKillTarget"),s(chooseStalePodKillTarget,"chooseStalePodKillTarget");
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var Se=Object.defineProperty;var T=(s,r)=>Se(s,"name",{value:r,configurable:!0});var _e=Object.defineProperty,n=T((s,r)=>_e(s,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=exports.BENIGN_SOCKET_ERRNOS=void 0,exports.buildEdgeVerifierPool=buildEdgeVerifierPool,exports.classifyClientError=classifyClientError,exports.createClientErrorLogGate=createClientErrorLogGate,exports.retirePreviousWorkspace=retirePreviousWorkspace,exports.resolveMaxConnections=resolveMaxConnections,exports.h2BudgetFromEnv=h2BudgetFromEnv,exports.createH2Governor=createH2Governor,exports.startGateway=startGateway;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),scope_1=require("./scope"),route_registry_1=require("./route-registry"),control_1=require("./control"),workspace_middleware_1=require("./workspace-middleware"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),observability_1=require("./observability"),lifecycle_1=require("./lifecycle"),process_names_1=require("./process-names"),project_graph_store_1=require("./project-graph-store"),serve_launch_1=require("./serve-launch"),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),lens_children_1=require("./lens-children"),proxy_1=require("./proxy"),auth_1=require("./auth"),hooks_1=require("./hooks"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),jwks_verify_1=require("../jwks-verify"),types_1=require("./types");function insecureLocalFetch(s,r){return new Promise((d,h)=>{const a=(s.startsWith("https:")?require("node:https"):require("node:http")).request(s,{method:"GET",headers:r?.headers,rejectUnauthorized:!1},y=>{const _=[];y.on("data",S=>_.push(S)),y.on("end",()=>{const S=y.statusCode??0,p=Buffer.concat(_).toString("utf8");d({ok:S>=200&&S<300,status:S,json:n(async()=>JSON.parse(p),"json")})})});a.on("error",h),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(s){const r=process.env.LENSMCP_GW_JWKS_URL,d=process.env.LENSMCP_GW_JWT_ISS,h=new Map;let a;const y=n((_,S)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${_}${S}`);const p=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:_,...d?{issuer:d}:{},fetchImpl:insecureLocalFetch});return p.refresh().catch(o=>console.warn("[gateway] edge JWKS warm failed (will retry):",o.message)),p},"make");return{for(_){if(r)return a??=y(r,"");const S=_.split(":")[0].toLowerCase();let p;for(const c of s()){const i=c.host;if(typeof i!="string"||!/^auth\./.test(i)||i.startsWith("internal."))continue;const A=i.slice(5);(S===A||S.endsWith(`.${A}`))&&(!p||A.length>p.length)&&(p=A)}if(!p)return;let o=h.get(p);return o||(o=y(`https://auth.${p}/jwks.json`,` (auto-derived for apex ${p})`),h.set(p,o)),o},stop(){a?.stop();for(const _ of h.values())_.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(s,r){return s&&exports.BENIGN_SOCKET_ERRNOS.has(s)?{status:null,report:!1,cause:"client-abort"}:s==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:s==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:s==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:s?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:s??r??"unknown"}}T(classifyClientError,"classifyClientError"),n(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(s=1e4){const r=new Map;return d=>{const h=Date.now(),a=r.get(d)??0;return h-a<s?!1:(r.set(d,h),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(s){s.on("error",r=>{exports.BENIGN_SOCKET_ERRNOS.has(r.code??"")||console.warn(`[gateway] inbound socket error (${r.code??r.message}) \u2014 dropping this connection only`),s.destroy()})}T(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(s,r,d){const h=s.get(r);if(!h)return!1;s.delete(r),d==="re-registered"&&console.log(`[gateway] workspace '${r}' re-registered \u2014 retiring the children of its previous registration first.`);try{h()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${r}' (${d}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),n(retirePreviousWorkspace,"retirePreviousWorkspace"),exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=512,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=64,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=4096,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=3e5;function parseBudget(s,r){if(s===void 0||s.trim()==="")return r;const d=Number(s);return!Number.isFinite(d)||d<0?r:Math.floor(d)}T(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(s){return parseBudget(s,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(s=process.env){return{maxSessions:parseBudget(s.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(s.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(s.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(s,r){const{NGHTTP2_ENHANCE_YOUR_CALM:d,NGHTTP2_REFUSED_STREAM:h}=require("node:http2").constants,a=new Map;let y=0;const _=n(o=>{o.idle&&(clearTimeout(o.idle),o.idle=void 0)},"disarm"),S=n((o,c)=>{_(c),!(s.idleSessionMs<=0||c.open>0)&&(c.idle=setTimeout(()=>{o.destroyed||o.closed||(r("session-reclaimed",{idleMs:s.idleSessionMs,sessions:a.size}),a.delete(o),o.close())},s.idleSessionMs),c.idle.unref?.())},"arm"),p=n(()=>{for(const[o,c]of a)if(!(c.open>0||o.destroyed))return _(c),a.delete(o),r("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:s.maxSessions}),o.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(o){if(s.maxSessions>0&&a.size>=s.maxSessions&&!p()){r("session-refused",{sessions:a.size,maxSessions:s.maxSessions,streams:y});try{o.goaway(d)}catch{}try{o.close()}catch{}return!1}const c={open:0};return a.set(o,c),S(o,c),o.once("close",()=>{_(c),a.delete(o)}),!0},admitStream(o,c){const i=a.get(o);if(!i)return!0;if(s.maxStreams>0&&y>=s.maxStreams){r("stream-refused",{streams:y,maxStreams:s.maxStreams,sessions:a.size});try{c.close(h)}catch{}return!1}return y+=1,i.open+=1,_(i),c.once("close",()=>{y=Math.max(0,y-1),i.open=Math.max(0,i.open-1),i.open===0&&a.has(o)&&S(o,i)}),!0},dispose(){for(const o of a.values())_(o);a.clear()}}}T(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(s,r){const d=s.https!==!1,h=s.ports?.length?s.ports:[443],a=s.scanTtlMs??1500,y=s.startupTimeoutMs??12e4,_=s.sweepMs??1e4,S=s.trafficFlushMs??5e3;let p=!1;const o=process.env.LENSMCP_EVENT_FILE??path.join(r.root,".lensmcp","events.jsonl"),c=(0,scope_1.readLensScope)(r.root),i=c.key,A=c.sockNs!==i?c.sockNs:void 0,Y=r.projectsConfigurations?.projects??{},B=(0,discovery_1.projectSourceRoots)(r.root,Y,r.projectDependencies),{routes:H,services:N,instance:X}=(0,discovery_1.discoverRoutes)(r.root,Y,void 0,i,B,A,c.instance);if(H.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const $=new route_registry_1.RouteRegistry;$.register({wsKey:i,...A?{sockNs:A}:{},root:r.root,routes:H,services:N,...X?{instance:X}:{}});const R=[];let J=n(()=>{},"refreshCert");const V=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,$),J()},"rebuildRoutes");V();const{serviceKeys:te,keyToProject:oe,keyToWorkspace:ne}=(0,service_keys_1.loadServiceKeys)(r.root,N,i),ae=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...s.trustProxyHops!==void 0?{trustProxyHops:s.trustProxyHops}:{},...s.clientIpHeader?{clientIpHeader:s.clientIpHeader}:{},...s.clientIpTrustedProxies?{clientIpTrustedProxies:s.clientIpTrustedProxies}:{}}),m={root:r.root,wsKey:i,...A?{sockNs:A}:{},eventFile:o,routes:R,registry:$,rebuildRoutes:V,services:N,serviceKeys:te,keyToProject:oe,keyToWorkspace:ne,clientIp:ae,bodyStallMs:s.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:y,sweepMs:_,trafficFlushMs:S,https:d,projectRoots:Object.fromEntries(Object.entries(r.projectsConfigurations?.projects??{}).map(([e,t])=>[e,path.resolve(r.root,t.root)])),projectSourceRoots:B,...r.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(r.projectGraphStore)}:{},...s.sharedCompiler!==void 0?{sharedCompiler:s.sharedCompiler}:{},stopped:n(()=>p,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(N,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!r.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,s.sharedCompiler),label:i||path.basename(r.root),lens:{key:i,dashboard:s.dashboard!==!1,mcp:s.mcp!==!1&&H.some(e=>!!e.lens)}}));const w=(0,observability_1.createObservability)(o,{trafficFlushMs:S}),ie=createClientErrorLogGate(),x=h2BudgetFromEnv(),ce=createClientErrorLogGate(3e4),G=createH2Governor(x,(e,t)=>{ce(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(t)}). These are the FRONT DOOR's ceilings, not an upstream's \u2014 raise/disable with LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS / LENSMCP_MAX_CONNECTIONS (0 = unbounded).`),w.emit(e==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${e}`,`gateway-h2-budget:${e}`,{kind:"h2-aggregate-budget",event:e,project:"gateway",...t})}),v=(0,lifecycle_1.createServiceLayer)(m,w),k=(0,lens_children_1.createLensChildren)(m,w,s),j=new Map;let q;if(s.middleware){const e=require(path.resolve(r.root,s.middleware));q=e.default??e}const W=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:i,...q?{owner:q}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()});let O;try{O=(0,control_1.startControlServer)({rt:m,killService:n((e,t)=>v.killSpawned(e,t),"killService"),onRegister:n(async e=>{retirePreviousWorkspace(j,e.wsKey,"re-registered"),W.set(e.wsKey,e.root,e.middleware);const t=await k.hostWorkspaceDashboard(e),f=await k.hostWorkspaceApps(e);j.set(e.wsKey,()=>{try{t?.()}finally{f()}})},"onRegister"),onUnregister:n(e=>{retirePreviousWorkspace(j,e.wsKey,"unregistered"),W.drop(e.wsKey),v.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const b=(0,proxy_1.createProxy)(m,w,v),z=buildEdgeVerifierPool(()=>m.routes),Q=(0,auth_1.createEdgeAuth)(m,w,z),le=n(e=>{const t=e.route?.wsKey,f=W.for(t);if(f)try{const l=t?m.registry.get(t):void 0;f(e.req,{...t?{wsKey:t}:{},...l?{root:l.root}:{},env:l?.instance?.env??{}})}catch(l){w.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:l.message}),e.route&&w.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${l.message}`)}},"builtinMiddleware"),Z=s.hooks??{},ee=(0,hooks_1.createHooks)({...Z,afterAuth:[le,...Z.afterAuth??[]]}),se=(0,handler_1.createHandler)({rt:m,obs:w,auth:Q,proxy:b,hooks:ee}),re=(0,upgrade_1.createUpgrade)({rt:m,auth:Q,proxy:b,hooks:ee,obs:w}),ue=w.startEdgeFlusher(),de=v.startSweeper();await k.bootSingletonsAndApps();for(const e of N)e.decl.eager&&v.ensureUp(e);let P,D,L;if(d){const e=require("../../../basic-ssl"),t=(0,service_keys_1.gatewayCacheDir)(r.root);L=n(()=>e.getCertificateSync(t,"gateway",m.routes.map(f=>f.host).filter(f=>!!f)),"mintCert"),P=L(),D=e.caCertPath()}const F=[],I=[],U=s.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of h){const t=P?require("node:http2").createSecureServer({key:P,cert:P,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:s.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:s.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(l,E)=>se(l,E)):http.createServer(se);t.on("upgrade",re),t.on("error",l=>console.error(`[gateway] port ${e}: ${l.code}`)),t.on("connection",guardInboundSocket),t.on("secureConnection",guardInboundSocket),t.on("tlsClientError",(l,E)=>E.destroy()),t.on("clientError",(l,E)=>{const u=l,g=classifyClientError(u.code,u.message);if(g.report&&ie(g.cause)){const C=E.remoteAddress??"unknown",M=g.status===null?"dropped":String(g.status);console.warn(`[gateway] inbound H1 clientError from ${C}: ${g.cause} (${u.code??u.message}${u.bytesParsed!==void 0?`, bytesParsed=${u.bytesParsed}`:""}) \u2192 ${M}. This status is the GATEWAY's, not the upstream service's.`),w.emit("warning",`inbound clientError: ${g.cause} \u2192 ${M}`,`gateway-client-error:${g.cause}`,{kind:"gateway-client-error",cause:g.cause,code:u.code??null,status:g.status,peer:C,bytesParsed:u.bytesParsed??null})}g.status!==null&&E.writable&&E.end(`HTTP/1.1 ${g.status} ${CLIENT_ERROR_REASON[g.status]}\r
|
|
1
|
+
"use strict";var Se=Object.defineProperty;var T=(s,r)=>Se(s,"name",{value:r,configurable:!0});var _e=Object.defineProperty,n=T((s,r)=>_e(s,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=exports.BENIGN_SOCKET_ERRNOS=void 0,exports.buildEdgeVerifierPool=buildEdgeVerifierPool,exports.classifyClientError=classifyClientError,exports.createClientErrorLogGate=createClientErrorLogGate,exports.retirePreviousWorkspace=retirePreviousWorkspace,exports.resolveMaxConnections=resolveMaxConnections,exports.h2BudgetFromEnv=h2BudgetFromEnv,exports.createH2Governor=createH2Governor,exports.startGateway=startGateway;const tslib_1=require("tslib"),http=tslib_1.__importStar(require("node:http")),path=tslib_1.__importStar(require("node:path")),discovery_1=require("./discovery"),scope_1=require("./scope"),route_registry_1=require("./route-registry"),control_1=require("./control"),workspace_middleware_1=require("./workspace-middleware"),workspace_registry_1=require("./workspace-registry"),service_keys_1=require("./service-keys"),client_ip_1=require("../client-ip"),workspace_log_1=require("./workspace-log"),observability_1=require("./observability"),lifecycle_1=require("./lifecycle"),process_names_1=require("./process-names"),project_graph_store_1=require("./project-graph-store"),serve_launch_1=require("./serve-launch"),process_name_1=require("../../../process-name"),protocol_1=require("../../../shared-build/protocol"),lens_children_1=require("./lens-children"),proxy_1=require("./proxy"),auth_1=require("./auth"),hooks_1=require("./hooks"),handler_1=require("./handler"),upgrade_1=require("./upgrade"),jwks_verify_1=require("../jwks-verify"),types_1=require("./types");function insecureLocalFetch(s,r){return new Promise((d,h)=>{const a=(s.startsWith("https:")?require("node:https"):require("node:http")).request(s,{method:"GET",headers:r?.headers,rejectUnauthorized:!1},y=>{const _=[];y.on("data",S=>_.push(S)),y.on("end",()=>{const S=y.statusCode??0,p=Buffer.concat(_).toString("utf8");d({ok:S>=200&&S<300,status:S,json:n(async()=>JSON.parse(p),"json")})})});a.on("error",h),a.end()})}T(insecureLocalFetch,"insecureLocalFetch"),n(insecureLocalFetch,"insecureLocalFetch");function buildEdgeVerifierPool(s){const r=process.env.LENSMCP_GW_JWKS_URL,d=process.env.LENSMCP_GW_JWT_ISS,h=new Map;let a;const y=n((_,S)=>{console.log(`[gateway] edge JWT: verifying via JWKS ${_}${S}`);const p=(0,jwks_verify_1.createJwksVerifier)({jwksUrl:_,...d?{issuer:d}:{},fetchImpl:insecureLocalFetch});return p.refresh().catch(o=>console.warn("[gateway] edge JWKS warm failed (will retry):",o.message)),p},"make");return{for(_){if(r)return a??=y(r,"");const S=_.split(":")[0].toLowerCase();let p;for(const c of s()){const i=c.host;if(typeof i!="string"||!/^auth\./.test(i)||i.startsWith("internal."))continue;const A=i.slice(5);(S===A||S.endsWith(`.${A}`))&&(!p||A.length>p.length)&&(p=A)}if(!p)return;let o=h.get(p);return o||(o=y(`https://auth.${p}/jwks.json`,` (auto-derived for apex ${p})`),h.set(p,o)),o},stop(){a?.stop();for(const _ of h.values())_.stop()}}}T(buildEdgeVerifierPool,"buildEdgeVerifierPool"),n(buildEdgeVerifierPool,"buildEdgeVerifierPool"),exports.BENIGN_SOCKET_ERRNOS=new Set(["ECONNRESET","EPIPE","ETIMEDOUT","ECONNABORTED","ECANCELED"]);function classifyClientError(s,r){return s&&exports.BENIGN_SOCKET_ERRNOS.has(s)?{status:null,report:!1,cause:"client-abort"}:s==="HPE_INVALID_EOF_STATE"?{status:null,report:!1,cause:"truncated-at-eof"}:s==="ERR_HTTP_REQUEST_TIMEOUT"?{status:408,report:!0,cause:"request-timeout"}:s==="HPE_HEADER_OVERFLOW"?{status:431,report:!0,cause:"header-overflow"}:s?.startsWith("HPE_")?{status:400,report:!0,cause:"parse-error"}:{status:400,report:!0,cause:s??r??"unknown"}}T(classifyClientError,"classifyClientError"),n(classifyClientError,"classifyClientError");const CLIENT_ERROR_REASON={400:"Bad Request",408:"Request Timeout",431:"Request Header Fields Too Large"};function createClientErrorLogGate(s=1e4){const r=new Map;return d=>{const h=Date.now(),a=r.get(d)??0;return h-a<s?!1:(r.set(d,h),!0)}}T(createClientErrorLogGate,"createClientErrorLogGate"),n(createClientErrorLogGate,"createClientErrorLogGate");function guardInboundSocket(s){s.on("error",r=>{exports.BENIGN_SOCKET_ERRNOS.has(r.code??"")||console.warn(`[gateway] inbound socket error (${r.code??r.message}) \u2014 dropping this connection only`),s.destroy()})}T(guardInboundSocket,"guardInboundSocket"),n(guardInboundSocket,"guardInboundSocket");function retirePreviousWorkspace(s,r,d){const h=s.get(r);if(!h)return!1;s.delete(r),d==="re-registered"&&console.log(`[gateway] workspace '${r}' re-registered \u2014 retiring the children of its previous registration first.`);try{h()}catch(a){console.warn(`[gateway] could not fully retire the previous registration of '${r}' (${d}): ${a.message}`)}return!0}T(retirePreviousWorkspace,"retirePreviousWorkspace"),n(retirePreviousWorkspace,"retirePreviousWorkspace"),exports.GATEWAY_MAX_CONNECTIONS_DEFAULT=512,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT=64,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT=4096,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT=3e5;function parseBudget(s,r){if(s===void 0||s.trim()==="")return r;const d=Number(s);return!Number.isFinite(d)||d<0?r:Math.floor(d)}T(parseBudget,"parseBudget"),n(parseBudget,"parseBudget");function resolveMaxConnections(s){return parseBudget(s,exports.GATEWAY_MAX_CONNECTIONS_DEFAULT)}T(resolveMaxConnections,"resolveMaxConnections"),n(resolveMaxConnections,"resolveMaxConnections");function h2BudgetFromEnv(s=process.env){return{maxSessions:parseBudget(s.LENSMCP_MAX_H2_SESSIONS,exports.GATEWAY_MAX_H2_SESSIONS_DEFAULT),maxStreams:parseBudget(s.LENSMCP_MAX_H2_STREAMS,exports.GATEWAY_MAX_H2_STREAMS_DEFAULT),idleSessionMs:parseBudget(s.LENSMCP_H2_IDLE_MS,exports.GATEWAY_H2_IDLE_SESSION_MS_DEFAULT)}}T(h2BudgetFromEnv,"h2BudgetFromEnv"),n(h2BudgetFromEnv,"h2BudgetFromEnv");function createH2Governor(s,r){const{NGHTTP2_ENHANCE_YOUR_CALM:d,NGHTTP2_REFUSED_STREAM:h}=require("node:http2").constants,a=new Map;let y=0;const _=n(o=>{o.idle&&(clearTimeout(o.idle),o.idle=void 0)},"disarm"),S=n((o,c)=>{_(c),!(s.idleSessionMs<=0||c.open>0)&&(c.idle=setTimeout(()=>{o.destroyed||o.closed||(r("session-reclaimed",{idleMs:s.idleSessionMs,sessions:a.size}),a.delete(o),o.close())},s.idleSessionMs),c.idle.unref?.())},"arm"),p=n(()=>{for(const[o,c]of a)if(!(c.open>0||o.destroyed))return _(c),a.delete(o),r("session-reclaimed",{reason:"cap",sessions:a.size+1,maxSessions:s.maxSessions}),o.close(),!0;return!1},"reclaimOldestIdle");return{admitSession(o){if(s.maxSessions>0&&a.size>=s.maxSessions&&!p()){r("session-refused",{sessions:a.size,maxSessions:s.maxSessions,streams:y});try{o.goaway(d)}catch{}try{o.close()}catch{}return!1}const c={open:0};return a.set(o,c),S(o,c),o.once("close",()=>{_(c),a.delete(o)}),!0},admitStream(o,c){const i=a.get(o);if(!i)return!0;if(s.maxStreams>0&&y>=s.maxStreams){r("stream-refused",{streams:y,maxStreams:s.maxStreams,sessions:a.size});try{c.close(h)}catch{}return!1}return y+=1,i.open+=1,_(i),c.once("close",()=>{y=Math.max(0,y-1),i.open=Math.max(0,i.open-1),i.open===0&&a.has(o)&&S(o,i)}),!0},dispose(){for(const o of a.values())_(o);a.clear()}}}T(createH2Governor,"createH2Governor"),n(createH2Governor,"createH2Governor");async function startGateway(s,r){const d=s.https!==!1,h=s.ports?.length?s.ports:[443],a=s.scanTtlMs??1500,y=s.startupTimeoutMs??12e4,_=s.sweepMs??1e4,S=s.trafficFlushMs??5e3;let p=!1;const o=process.env.LENSMCP_EVENT_FILE??path.join(r.root,".lensmcp","events.jsonl"),c=(0,scope_1.readLensScope)(r.root),i=c.key,A=c.sockNs!==i?c.sockNs:void 0,Y=r.projectsConfigurations?.projects??{},B=(0,discovery_1.projectSourceRoots)(r.root,Y,r.projectDependencies),{routes:H,services:N,instance:X}=(0,discovery_1.discoverRoutes)(r.root,Y,void 0,i,B,A,c.instance);if(H.length===0)throw new Error("[gateway] no project declares a `cluster` field \u2014 nothing to route.");const $=new route_registry_1.RouteRegistry;$.register({wsKey:i,...A?{sockNs:A}:{},root:r.root,routes:H,services:N,...X?{instance:X}:{}});const R=[];let J=n(()=>{},"refreshCert");const V=n(()=>{(0,route_registry_1.rebuildRoutesInPlace)(R,$),J()},"rebuildRoutes");V();const{serviceKeys:te,keyToProject:oe,keyToWorkspace:ne}=(0,service_keys_1.loadServiceKeys)(r.root,N,i),ae=(0,client_ip_1.createClientIpResolver)({...(0,client_ip_1.clientIpOptionsFromEnv)(),...s.trustProxyHops!==void 0?{trustProxyHops:s.trustProxyHops}:{},...s.clientIpHeader?{clientIpHeader:s.clientIpHeader}:{},...s.clientIpTrustedProxies?{clientIpTrustedProxies:s.clientIpTrustedProxies}:{}}),m={root:r.root,wsKey:i,...A?{sockNs:A}:{},eventFile:o,routes:R,registry:$,rebuildRoutes:V,services:N,serviceKeys:te,keyToProject:oe,keyToWorkspace:ne,clientIp:ae,bodyStallMs:s.bodyStallMs??types_1.BODY_STALL_MS,scanTtlMs:a,startupTimeoutMs:y,sweepMs:_,trafficFlushMs:S,https:d,projectRoots:Object.fromEntries(Object.entries(r.projectsConfigurations?.projects??{}).map(([e,t])=>[e,path.resolve(r.root,t.root)])),projectSourceRoots:B,...r.projectGraphStore?{graphStores:(0,project_graph_store_1.createProjectGraphStores)(r.projectGraphStore)}:{},...s.sharedCompiler!==void 0?{sharedCompiler:s.sharedCompiler}:{},stopped:n(()=>p,"stopped")};(0,process_name_1.prelinkProcessNames)((0,process_names_1.gatewayProcessNames)(N,{runner:(0,serve_launch_1.serveRunnerEnabled)()&&!!r.projectGraphStore,sharedBuild:(0,protocol_1.sharedCompilerEnabled)(process.env,s.sharedCompiler),label:i||path.basename(r.root),lens:{key:i,dashboard:s.dashboard!==!1,mcp:s.mcp!==!1&&H.some(e=>!!e.lens)}}));const w=(0,observability_1.createObservability)(o,{trafficFlushMs:S}),ie=createClientErrorLogGate(),x=h2BudgetFromEnv(),ce=createClientErrorLogGate(3e4),G=createH2Governor(x,(e,t)=>{ce(e)&&console.warn(`[gateway] h2 aggregate budget: ${e} (${JSON.stringify(t)}). These are the FRONT DOOR's ceilings, not an upstream's \u2014 raise/disable with LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS / LENSMCP_MAX_CONNECTIONS (0 = unbounded).`),w.emit(e==="session-reclaimed"?"info":"warning",`h2 aggregate budget: ${e}`,`gateway-h2-budget:${e}`,{kind:"h2-aggregate-budget",event:e,project:"gateway",...t})}),M=(0,lifecycle_1.createServiceLayer)(m,w),k=(0,lens_children_1.createLensChildren)(m,w,s),j=new Map;let q;if(s.middleware){const e=require(path.resolve(r.root,s.middleware));q=e.default??e}const W=(0,workspace_middleware_1.createWorkspaceMiddleware)({ownerKey:i,...q?{owner:q}:{},policy:(0,workspace_middleware_1.guestMiddlewarePolicy)()});let O;try{O=(0,control_1.startControlServer)({rt:m,killService:n((e,t)=>M.killSpawned(e,t),"killService"),restartService:n((e,t)=>M.restartService(e,t),"restartService"),onRegister:n(async e=>{retirePreviousWorkspace(j,e.wsKey,"re-registered"),W.set(e.wsKey,e.root,e.middleware);const t=await k.hostWorkspaceDashboard(e),f=await k.hostWorkspaceApps(e);j.set(e.wsKey,()=>{try{t?.()}finally{f()}})},"onRegister"),onUnregister:n(e=>{retirePreviousWorkspace(j,e.wsKey,"unregistered"),W.drop(e.wsKey),M.stopBuildHost(e.root)},"onUnregister")})}catch(e){console.warn("[gateway] control plane failed to start (multi-workspace register disabled):",e.message)}const b=(0,proxy_1.createProxy)(m,w,M),z=buildEdgeVerifierPool(()=>m.routes),Q=(0,auth_1.createEdgeAuth)(m,w,z),le=n(e=>{const t=e.route?.wsKey,f=W.for(t);if(f)try{const l=t?m.registry.get(t):void 0;f(e.req,{...t?{wsKey:t}:{},...l?{root:l.root}:{},env:l?.instance?.env??{}})}catch(l){w.traceStep(e.trace,"auth",{mode:"middleware",ok:!1,reason:l.message}),e.route&&w.finishTrace(e.trace,e.route.project,401),e.sendError(401,"unauthorized",`Gateway middleware rejected the request: ${l.message}`)}},"builtinMiddleware"),Z=s.hooks??{},ee=(0,hooks_1.createHooks)({...Z,afterAuth:[le,...Z.afterAuth??[]]}),se=(0,handler_1.createHandler)({rt:m,obs:w,auth:Q,proxy:b,hooks:ee}),re=(0,upgrade_1.createUpgrade)({rt:m,auth:Q,proxy:b,hooks:ee,obs:w}),ue=w.startEdgeFlusher(),de=M.startSweeper();await k.bootSingletonsAndApps();for(const e of N)e.decl.eager&&M.ensureUp(e);let P,D,L;if(d){const e=require("../../../basic-ssl"),t=(0,service_keys_1.gatewayCacheDir)(r.root);L=n(()=>e.getCertificateSync(t,"gateway",m.routes.map(f=>f.host).filter(f=>!!f)),"mintCert"),P=L(),D=e.caCertPath()}const F=[],I=[],U=s.maxConnections??resolveMaxConnections(process.env.LENSMCP_MAX_CONNECTIONS);for(const e of h){const t=P?require("node:http2").createSecureServer({key:P,cert:P,allowHTTP1:!0,maxSessionMemory:512,settings:{maxConcurrentStreams:512},peerMaxConcurrentStreams:512,streamResetBurst:s.h2ResetBurst??types_1.GATEWAY_H2_RESET_BURST,streamResetRate:s.h2ResetRate??types_1.GATEWAY_H2_RESET_RATE},(l,E)=>se(l,E)):http.createServer(se);t.on("upgrade",re),t.on("error",l=>console.error(`[gateway] port ${e}: ${l.code}`)),t.on("connection",guardInboundSocket),t.on("secureConnection",guardInboundSocket),t.on("tlsClientError",(l,E)=>E.destroy()),t.on("clientError",(l,E)=>{const u=l,g=classifyClientError(u.code,u.message);if(g.report&&ie(g.cause)){const v=E.remoteAddress??"unknown",C=g.status===null?"dropped":String(g.status);console.warn(`[gateway] inbound H1 clientError from ${v}: ${g.cause} (${u.code??u.message}${u.bytesParsed!==void 0?`, bytesParsed=${u.bytesParsed}`:""}) \u2192 ${C}. This status is the GATEWAY's, not the upstream service's.`),w.emit("warning",`inbound clientError: ${g.cause} \u2192 ${C}`,`gateway-client-error:${g.cause}`,{kind:"gateway-client-error",cause:g.cause,code:u.code??null,status:g.status,peer:v,bytesParsed:u.bytesParsed??null})}g.status!==null&&E.writable&&E.end(`HTTP/1.1 ${g.status} ${CLIENT_ERROR_REASON[g.status]}\r
|
|
2
2
|
\r
|
|
3
|
-
`),E.destroy()}),t.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,t.headersTimeout=s.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,U>0&&(t.maxConnections=U);const f=s.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(f>0&&(t.connectionsCheckingInterval=f),P!=null){const l=t,{NGHTTP2_CANCEL:E}=require("node:http2").constants;l.on("session",u=>{if(!G.admitSession(u)){u.on("error",()=>{});return}let g=0,
|
|
3
|
+
`),E.destroy()}),t.keepAliveTimeout=types_1.GATEWAY_KEEPALIVE_TIMEOUT_MS,t.headersTimeout=s.headersTimeoutMs??types_1.GATEWAY_HEADERS_TIMEOUT_MS,U>0&&(t.maxConnections=U);const f=s.connectionsCheckingIntervalMs??types_1.GATEWAY_CONN_CHECK_MS;if(f>0&&(t.connectionsCheckingInterval=f),P!=null){const l=t,{NGHTTP2_CANCEL:E}=require("node:http2").constants;l.on("session",u=>{if(!G.admitSession(u)){u.on("error",()=>{});return}let g=0,v=0;u.on("stream",C=>{G.admitStream(u,C)&&C.once("close",()=>{u.destroyed?v+=1:C.rstCode===E&&(g+=1)})}),u.once("close",()=>{v!==0&&(console.warn(`[gateway] h2 session closed with ${v} stream(s) in flight after ${g} client reset(s) \u2014 every in-flight fetch on that connection failed at once (ERR_HTTP2_PROTOCOL_ERROR in the browser). Reset budget: LENSMCP_H2_RESET_BURST/LENSMCP_H2_RESET_RATE (issues/closed/foodguard-14).`),w.emit("warning",`h2 session lost with ${v} stream(s) in flight`,"gateway-h2-session-lost",{kind:"h2-session-lost",project:"gateway",inflight:v,clientResets:g}))}),u.on("error",C=>{const pe=C.code??C.message;console.warn(`[gateway] h2 session error (${pe}) \u2014 dropping this session only`),u.destroyed||u.destroy()})}),l.on("sessionError",u=>{console.warn(`[gateway] h2 sessionError: ${u.code??u.message}`)})}await new Promise(l=>{t.listen(e,()=>{const E=t.address()?.port??e;I.push(E),console.log(`[gateway] Listening on ${P?"https":"http"}://localhost:${E}`),l()})}),F.push(t)}L&&(J=n(()=>{const e=L();for(const t of F)t.setSecureContext?.({key:e,cert:e})},"refreshCert")),console.log("[gateway] Routes:");for(const e of R){const t=e.pool?`pods@${e.pool.dir}`:e.target;console.log(`[gateway] ${e.host??"(default)"} \u2192 ${t}${e.prependPrefix?` (+${e.prependPrefix})`:""}${e.internal?" [internal: no middleware]":""} [${e.project}]`)}D&&console.log(`[gateway] CA: ${D} \u2014 one-time setup via the trust executor.`);const K=n(e=>e>0?String(e):"unbounded","unbounded");return console.log(`[gateway] front-door budget: \u2264${K(U)} connections, \u2264${K(x.maxSessions)} h2 sessions (each \u2264512 MB session memory, \u2264512 streams), \u2264${K(x.maxStreams)} open streams in total, idle h2 session reclaimed after ${x.idleSessionMs>0?`${Math.round(x.idleSessionMs/1e3)}s`:"never"}. Override: LENSMCP_MAX_CONNECTIONS / LENSMCP_MAX_H2_SESSIONS / LENSMCP_MAX_H2_STREAMS / LENSMCP_H2_IDLE_MS (0 = unbounded).`),w.emit("info",`gateway up: ${R.length} routes on ${I.join("/")}`,"cluster-gateway",{kind:"gateway-up",project:"gateway",ports:I,routes:R.map(e=>({host:e.host??"(default)",project:e.project,mode:e.pool?"pods":"tcp",internal:!!e.internal,prependPrefix:e.prependPrefix}))}),O&&process.env.LENSMCP_GUEST_REPLAY!=="0"&&O.replay().catch(e=>console.warn("[gateway] guest replay failed:",e.message)),{ports:I,routes:R,services:N,stop:n(async()=>{if(!p){p=!0,clearInterval(ue),clearInterval(de),w.emit("info","gateway down","cluster-gateway",{kind:"gateway-down",project:"gateway"}),z.stop(),O?.close(),(0,workspace_registry_1.unregisterWorkspace)(i);for(const e of N)M.killSpawned(e,"gateway shutdown");M.stopRetiringTrees(),await M.stopBuildHost(),await k.reapAll(),re.closeTunnels(),await Promise.all(F.map(e=>new Promise(t=>{let f=!1;const l=n(()=>{f||(f=!0,t())},"done"),E=e;e.close(()=>l());try{E.closeIdleConnections?.()}catch{}setTimeout(()=>{try{E.closeAllConnections?.()}catch{}l()},types_1.GATEWAY_STOP_GRACE_MS).unref?.()}))),b.closeProxy(),b.destroyAgents(),G.dispose(),(0,workspace_log_1.closeWorkspaceLogs)()}},"stop"),...m.lensSupervisor?{lensSupervisor:m.lensSupervisor}:{},...m.graphStores?{graphStores:m.graphStores}:{}}}T(startGateway,"startGateway"),n(startGateway,"startGateway");
|
|
@@ -226,6 +226,28 @@ export interface LensFrontendSupervisor {
|
|
|
226
226
|
/** Ensure a replacement is running and resolve when its route is ready. */
|
|
227
227
|
recover(frontend: LensFrontend, reason: string): Promise<boolean>;
|
|
228
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* A restart someone asked for — an agent's `service.restart`, `lensmcp restart <service>` — of ONE service of one
|
|
231
|
+
* workspace or worktree. A fresh tree re-reads what the service reads when it starts: its config files, its env, its
|
|
232
|
+
* bundle; a hot swap re-reads only what the new bundle reads again.
|
|
233
|
+
*/
|
|
234
|
+
export interface ServiceRestart {
|
|
235
|
+
/** `rolling`: a standby tree first, the old one retired once the new one answers — nothing in between is refused.
|
|
236
|
+
* `hard`: stop the tree, then start it — a service run through `nx run` (no second tree can overlap it), one still
|
|
237
|
+
* starting, or asked for. `start`: it was down. */
|
|
238
|
+
how: 'rolling' | 'hard' | 'start';
|
|
239
|
+
reason: string;
|
|
240
|
+
startedAt: number;
|
|
241
|
+
/** The pod generation it replaces (none when it was down). */
|
|
242
|
+
fromGeneration?: number;
|
|
243
|
+
/** Set once it settled: a pod of `generation` answers (`ok`), or why not. */
|
|
244
|
+
done?: {
|
|
245
|
+
ok: boolean;
|
|
246
|
+
at: number;
|
|
247
|
+
generation?: number;
|
|
248
|
+
error?: string;
|
|
249
|
+
};
|
|
250
|
+
}
|
|
229
251
|
export interface ServiceCtl {
|
|
230
252
|
project: string;
|
|
231
253
|
decl: ClusterProjectDecl;
|
|
@@ -263,6 +285,10 @@ export interface ServiceCtl {
|
|
|
263
285
|
staleKeptAt?: number;
|
|
264
286
|
/** A rolling recycle in flight ({@link POD_RECYCLE_MODE}): the standby generation's promise. */
|
|
265
287
|
rolling?: Promise<boolean>;
|
|
288
|
+
/** Why the last rolling recycle failed (set by the roll, read by an asked-for restart). */
|
|
289
|
+
rollFailure?: string;
|
|
290
|
+
/** The latest restart someone ASKED for (control.ts `POST /restart`, lifecycle.ts `restartService`) — settled or not. */
|
|
291
|
+
restart?: ServiceRestart;
|
|
266
292
|
/** The pod GENERATION the current `proc` was spawned as (`LENSMCP_POD_GENERATION`, echoed in `/__lensmcp/alive`). */
|
|
267
293
|
generation?: number;
|
|
268
294
|
/** How the current `proc` was started: the gateway's serve runner, or `nx run` (serve-launch.ts). An `nx run`
|
package/main.devserver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var lt=Object.defineProperty;var h=(c,f)=>lt(c,"name",{value:f,configurable:!0});var at=Object.defineProperty,a=h((c,f)=>at(c,"name",{value:f,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.redactInspectorUrls=exports.inspectorPortOf=exports.DEBUG_FIXED_PORTS_ENV=exports.mayDriveThePool=exports.isOwnerOnlyTransport=exports.gatewayManaged=void 0,exports.tell=tell,exports.usesFixedInspectorPorts=usesFixedInspectorPorts,exports.inspectorPortFor=inspectorPortFor;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),node_child_process_1=require("node:child_process"),http=tslib_1.__importStar(require("node:http")),readline=tslib_1.__importStar(require("node:readline")),inspector=tslib_1.__importStar(require("node:inspector")),util=tslib_1.__importStar(require("node:util")),v8=tslib_1.__importStar(require("node:v8")),exit_reason_1=require("./exit-reason"),hot_swap_registries_1=require("./hot-swap-registries"),process_name_1=require("./process-name"),inspector_secret_1=require("./inspector-secret");Object.defineProperty(exports,"inspectorPortOf",{enumerable:!0,get:a(function(){return inspector_secret_1.inspectorPortOf},"get")}),Object.defineProperty(exports,"redactInspectorUrls",{enumerable:!0,get:a(function(){return inspector_secret_1.redactInspectorUrls},"get")});const devserver_transport_1=require("./devserver-transport");Object.defineProperty(exports,"gatewayManaged",{enumerable:!0,get:a(function(){return devserver_transport_1.gatewayManaged},"get")}),Object.defineProperty(exports,"isOwnerOnlyTransport",{enumerable:!0,get:a(function(){return devserver_transport_1.isOwnerOnlyTransport},"get")}),Object.defineProperty(exports,"mayDriveThePool",{enumerable:!0,get:a(function(){return devserver_transport_1.mayDriveThePool},"get")});const schema_first_instance_1=require("./schema-first-instance"),redis_disconnect_fix_1=require("./redis-disconnect-fix"),error_stack_release_1=require("./error-stack-release"),swap_recycle_1=require("./swap-recycle"),swap_mode_1=require("./swap-mode"),handoff_retire_1=require("./handoff-retire"),gc_notice_1=require("./gc-notice"),pod_sock_1=require("./pod-sock"),notice_generation_1=require("./notice-generation"),service_build_events_1=require("./service-build-events"),stdio_guard_1=require("./stdio-guard"),bundle_parent_1=require("./bundle-parent"),checkout_cache_1=require("./checkout-cache"),dev_devtool_1=require("./dev-devtool"),pod_memory_1=require("./pod-memory"),pod_memory_events_1=require("./pod-memory-events"),heap_snapshot_1=require("./heap-snapshot"),httpProxy=require("http-proxy"),{prettyFactory}=require("pino-pretty"),BUNDLE_PATH=process.env.BUNDLE_PATH?.trim()||"./main.js",SERVICE_NAME=process.env.SERVICE_NAME||"",SERVICE_PREFIX=process.env.SERVE_PREFIX?`/${process.env.SERVE_PREFIX}`:"",GATEWAY_MIDDLEWARE_PATH=process.env.GATEWAY_MIDDLEWARE||"",GATEWAY_CONFIG=(()=>{const c=process.env.GATEWAY_CONFIG;if(!c)return{};try{return JSON.parse(c)}catch{return{}}})();let gatewayMiddleware=null;if(GATEWAY_MIDDLEWARE_PATH)try{const c=require(GATEWAY_MIDDLEWARE_PATH);gatewayMiddleware=typeof c=="function"?c:typeof c.default=="function"?c.default:null,gatewayMiddleware||console.warn(`[gateway] ${GATEWAY_MIDDLEWARE_PATH} does not export a function, gateway disabled`)}catch(c){console.error(`[gateway] Failed to load middleware from ${GATEWAY_MIDDLEWARE_PATH}:`,c)}function applyGatewayMiddleware(c){if(gatewayMiddleware)try{gatewayMiddleware(c,GATEWAY_CONFIG)}catch(f){console.error("[gateway] Middleware error:",f)}}h(applyGatewayMiddleware,"applyGatewayMiddleware"),a(applyGatewayMiddleware,"applyGatewayMiddleware");const GATEWAY_ROUTES=(()=>{try{return JSON.parse(process.env.GATEWAY_ROUTES||"[]")}catch{return[]}})(),GATEWAY_HTTPS=process.env.GATEWAY_HTTPS==="1";function hostMatches(c,f){if(c.startsWith("*.")){const g=c.slice(1);return f.endsWith(g)||f===c.slice(2)}return f===c}h(hostMatches,"hostMatches"),a(hostMatches,"hostMatches");function matchGatewayRoute(c){if(GATEWAY_ROUTES.length===0)return;const f=(c.headers.host||"").split(":")[0],g=c.url||"/";for(const E of GATEWAY_ROUTES)if(!(E.host&&!hostMatches(E.host,f))&&!(E.prefix&&!g.startsWith(E.prefix)))return E}h(matchGatewayRoute,"matchGatewayRoute"),a(matchGatewayRoute,"matchGatewayRoute");const WORKER_NAMES=(()=>{try{return JSON.parse(process.env.WORKERS||"[]")}catch{return[]}})(),LENSMCP_WS_KEY=process.env.LENSMCP_WS_KEY||"",SOCK_NS=process.env.LENSMCP_SOCK_NS||LENSMCP_WS_KEY,SOCK_DIR=SOCK_NS?path.join(os.tmpdir(),SOCK_NS,`${SERVICE_NAME||"lensmcp-cluster"}-devserver`):path.join(os.tmpdir(),`${SERVICE_NAME||"lensmcp-cluster"}-devserver`),SOCKET_ONLY=process.env.LENSMCP_SOCKET_ONLY==="1",POD_GENERATION=(()=>{const c=Number(process.env.LENSMCP_POD_GENERATION);return Number.isFinite(c)&&c>1?Math.floor(c):1})();function childSockPath(c){return path.join(SOCK_DIR,(0,pod_sock_1.podSockName)(c,POD_GENERATION))}h(childSockPath,"childSockPath"),a(childSockPath,"childSockPath");function sockOwnerPath(c){return`${c}.owner`}h(sockOwnerPath,"sockOwnerPath"),a(sockOwnerPath,"sockOwnerPath");function sockRetiringPath(c){return`${c}.retiring`}h(sockRetiringPath,"sockRetiringPath"),a(sockRetiringPath,"sockRetiringPath");function tell(c,f){if(!c.connected||typeof c.send!="function")return!1;try{return c.send(f,()=>{})}catch{return!1}}h(tell,"tell"),a(tell,"tell");function refusePoolCommand(c,f){console.warn(`[devserver] refused ${c.method} ${c.url} on the public port \u2014 under a gateway the pool is driven on the control socket only.`),f.writeHead(404,{"content-type":"application/json"}),f.end(JSON.stringify({error:"not found"}))}h(refusePoolCommand,"refusePoolCommand"),a(refusePoolCommand,"refusePoolCommand"),exports.DEBUG_FIXED_PORTS_ENV="LENSMCP_DEBUG_FIXED_PORTS";function usesFixedInspectorPorts(c=process.env){const f=(c[exports.DEBUG_FIXED_PORTS_ENV]??"").trim().toLowerCase();return f==="1"||f==="true"||f==="yes"||f==="on"}h(usesFixedInspectorPorts,"usesFixedInspectorPorts"),a(usesFixedInspectorPorts,"usesFixedInspectorPorts");function inspectorPortFor(c,f,g=process.env){if(!usesFixedInspectorPorts(g))return 0;const E=Math.trunc(c)+Math.trunc(f);return Number.isInteger(E)&&E>1024&&E<=65535?E:0}h(inspectorPortFor,"inspectorPortFor"),a(inspectorPortFor,"inspectorPortFor");function ensureOwnerOnlyDir(c){try{fs.mkdirSync(c,{recursive:!0,mode:448})}catch{}try{const f=fs.lstatSync(c);f.isDirectory()&&(f.mode&63)!==0&&fs.chmodSync(c,448)}catch{}}h(ensureOwnerOnlyDir,"ensureOwnerOnlyDir"),a(ensureOwnerOnlyDir,"ensureOwnerOnlyDir");function claimSock(c,f=process.pid){ensureOwnerOnlyDir(path.dirname(c));try{const g=sockOwnerPath(c);try{fs.unlinkSync(g)}catch{}fs.writeFileSync(g,String(f),{flag:"wx",mode:384})}catch{}try{fs.unlinkSync(c)}catch{}try{fs.unlinkSync(sockRetiringPath(c))}catch{}}h(claimSock,"claimSock"),a(claimSock,"claimSock");function sockOwnedByAnother(c,f=process.pid){try{const g=fs.readFileSync(sockOwnerPath(c),"utf8").trim();return!!g&&g!==String(f)}catch{return!1}}h(sockOwnedByAnother,"sockOwnedByAnother"),a(sockOwnedByAnother,"sockOwnedByAnother");function cleanupSock(c,f=process.pid){if(c){try{const g=fs.readFileSync(sockOwnerPath(c),"utf8").trim();if(g&&g!==String(f))return}catch{}try{fs.unlinkSync(c)}catch{}try{fs.unlinkSync(sockOwnerPath(c))}catch{}try{fs.unlinkSync(sockRetiringPath(c))}catch{}(0,gc_notice_1.clearGcNotice)(c)}}h(cleanupSock,"cleanupSock"),a(cleanupSock,"cleanupSock");function postRetiringNotice(c,f){if(!(!c||!f)){try{const g=fs.readFileSync(sockOwnerPath(c),"utf8").trim();if(g&&g!==String(f))return}catch{}try{fs.writeFileSync(sockRetiringPath(c),String(f),{flag:"w",mode:384})}catch{}}}h(postRetiringNotice,"postRetiringNotice"),a(postRetiringNotice,"postRetiringNotice");function serviceKeyFingerprint(){const c=process.env.LENSMCP_SERVICE_KEY;if(!c)return;const{createHash:f}=require("node:crypto");return f("sha256").update(c).digest("hex").slice(0,8)}h(serviceKeyFingerprint,"serviceKeyFingerprint"),a(serviceKeyFingerprint,"serviceKeyFingerprint");function rootModuleOf(c){let f;for(const g of c.getModules?.()?.values()??[])typeof g?.metatype!="function"||typeof g.distance!="number"||!Number.isFinite(g.distance)||(!f||g.distance<f.distance)&&(f=g);return f?.metatype}h(rootModuleOf,"rootModuleOf"),a(rootModuleOf,"rootModuleOf");let lastImportedModule;function takeImportedModule(){const c=lastImportedModule;return lastImportedModule=void 0,c}h(takeImportedModule,"takeImportedModule"),a(takeImportedModule,"takeImportedModule");async function importFresh(spec){process.env.DEVSERVER_MODE="1";const requested=path.isAbsolute(spec)?spec:path.join(__dirname,spec);let resolved=requested;try{resolved=fs.realpathSync(requested)}catch{}eval(`(() => {
|
|
2
2
|
const key = ${JSON.stringify(resolved)};
|
|
3
3
|
const previous = require.cache[key];
|
|
4
4
|
delete require.cache[key]; // delete from CJS cache
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
const at = owner && Array.isArray(owner.children) ? owner.children.indexOf(previous) : -1;
|
|
10
10
|
if (at >= 0) owner.children.splice(at, 1);
|
|
11
11
|
}
|
|
12
|
-
})()`),(0,bundle_parent_1.routeBundleRequires)(requested,resolved);const exports=eval(`require(${JSON.stringify(resolved)})`);try{const mod=eval("require.cache")[resolved];lastImportedModule=mod&&typeof mod=="object"?mod:void 0}catch{}return exports}_(importFresh,"importFresh"),i(importFresh,"importFresh");function debounce(a,h){let g=null;return(...S)=>{g&&clearTimeout(g),g=setTimeout(()=>a(...S),h)}}if(_(debounce,"debounce"),i(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const a=i(g=>{if(!inspector.url())try{inspector.open(g,"127.0.0.1",!1)}catch(F){console.warn(`[child] inspector.open(${g}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,F.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const S=inspector.url();S&&tell(process,{type:"inspector-url",url:S})},"openInspector"),h=process.env.CHILD_DEBUG_PORT;if(h!==void 0&&h!==""){const g=Number(h);Number.isInteger(g)&&(g===0||g>1024)&&a(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,S=!1,F,re=!1;const Le=(0,hot_swap_registries_1.registryPruningEnabled)(),ce=i((t,n)=>{const l=(0,hot_swap_registries_1.describePrune)(t,n);l&&console.log(l)},"logPrune");let U={app:null,handler:i((t,n)=>{n.statusCode=503,n.end("starting")},"handler"),inflight:0};const le=(()=>{const t=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(t)&&t>=0?t:15*6e4})(),T=i(t=>{const n=globalThis.__lensmcpActiveWork;if(typeof n!="function")return{count:0};try{const l=n(t);return{count:Number(l?.count??0),...l?.oldestStartedAt?{oldestStartedAt:l.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),ve=i(t=>{const n=globalThis.__lensmcpStopWorkIntake;if(typeof n=="function")try{return n(t)}catch{return}},"stopWorkIntake"),de=i(t=>{const n=globalThis.__lensmcpWorkIntakeReport;if(!(t===void 0||typeof n!="function"))try{return n(t)??void 0}catch{return}},"workIntakeReport"),ye=(()=>{const t=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(t)&&t>=1?Math.floor(t):1})(),G=new Set,Z=(()=>{const t=Number(process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS);return process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==void 0&&process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==""&&Number.isFinite(t)&&t>=0?t:3e4})(),j=i(t=>{if(!t.abandoned?.size)return 0;const n=Date.now();for(const[l,p]of t.abandoned)(l.writableEnded||n-p>=Z)&&t.abandoned.delete(l);return t.abandoned.size},"abandonedRunning"),oe=i(t=>{let n=0;for(const l of t.open??[])l.headersSent||(n+=1);return{unanswered:n+j(t),streams:Math.max(0,t.inflight-n),work:T(t.workGen).count}},"owedBy"),ne=i((t,n,l=`${n}s`)=>`${t} ${t===1?n:l}`,"plural");async function $e(){if(Z<=0)return;const t=Date.now();let n=!1;for(;!(G.size<ye);){const l=G.values().next().value;if(!l||l.closed)break;const p=oe(l);if(p.unanswered===0&&p.work===0)break;const m=(l.drainingSince??t)+Z-Date.now();if(m<=0)break;n||(n=!0,console.log(`[child] swap held \u2014 the app replaced ${Math.round((Date.now()-(l.drainingSince??t))/100)/10}s ago is still `+[p.unanswered?`answering ${ne(p.unanswered,"request")}`:"",p.work?`running ${ne(p.work,"background job")}`:""].filter(Boolean).join(" and ")+`, and this pod keeps at most ${ye} replaced app(s) alive (LENSMCP_SWAP_MAX_DRAINING). The new build is applied when it is done \u2014 in ${Math.ceil(m/1e3)}s at the latest (LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS).`)),await new Promise(E=>{setTimeout(E,Math.min(100,m)).unref?.()})}n&&console.log(`[child] swap resumed after ${Date.now()-t}ms`)}_($e,"xe"),i($e,"holdForOwedWork");const R=(0,swap_recycle_1.swapRecyclePolicy)();let pe,se=0;const We=(()=>{const t=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(t)&&t>=0?t:3500})();let f=!1,b=!1;const v=(0,pod_memory_1.podMemoryPolicy)();let q=0;const O=new Set;let ue=!1;const H=i(()=>{if(!ue)return;let t=0;for(const n of O)n.deref()===void 0?O.delete(n):t+=1;return t},"liveContainers"),k=(0,pod_memory_1.createPodMemoryMonitor)({policy:v,state:i(()=>({gen:U.gen??0,swaps:se,...pe===void 0?{}:{baseline:pe},busy:S||re||M.size>0,lastBusyAt:Y}),"state"),send:i(t=>{tell(process,t)},"send"),containers:H,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:i(()=>we(),"onRetained"),gc:i(()=>(0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc),"gc")}),L=i(()=>((0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc)&&k.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),W=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,n=`${path.sep}node_modules${path.sep}`,l=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:i(p=>l.has(p)||p.startsWith(t)&&!p.slice(t.length).includes(n)&&!p.slice(t.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let me=!1;function be(){if(!(!W||me))try{const t=W();t.errors>0&&console.log(`[child] read the stack of ${t.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(t){console.error("[child] could not release library error frames:",t),me=!0}}_(be,"Ne"),i(be,"releaseLibraryErrorFrames");const M=new Map;let Y=Date.now();const he=i(()=>R.settleMs<=0||!S&&!re&&M.size===0&&Date.now()-Y>=R.settleMs,"settled");let K;function ie(t){if(!(!R.enabled||f||b)){if(R.settleMs<=0){ee(t);return}K&&clearTimeout(K),K=setTimeout(()=>{K=void 0,he()?ee(t):ie(t)},R.settleMs),K.unref?.()}}_(ie,"de"),i(ie,"recycleCheckWhenSettled");const Me=i(()=>{if(R.stuckCloseMs<=0)return 0;const t=Date.now();let n=0;for(const l of M.values())t-l>=R.stuckCloseMs&&(n+=1);return n},"stuckCloses");function ee(t){if(!R.enabled||f||b||!process.send)return;const n=v8.getHeapStatistics().heap_size_limit,l=i(m=>{const E=k.oldestRetained();return{swaps:se,usedBytes:m,limitBytes:n,stuckCloses:Me(),settled:he(),...pe===void 0?{}:{baselineBytes:pe},...E?{retained:{generation:E.gen,ageMs:Date.now()-E.closedAt,gcs:E.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(R,l(process.memoryUsage().heapUsed)))return;const p=(0,swap_recycle_1.recycleReason)(R,l(L()));p&&(f=!0,console.log(`[child] asking for a fresh pod ${t} \u2014 ${p}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB / _RETAINED_MS, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),tell(process,{type:"recycle-request",reason:p}))}_(ee,"oe"),i(ee,"considerRecycle");let fe;function we(){if(!R.enabled||R.retainedMs<=0||f||b)return;const t=k.oldestRetained();if(!t)return;const n=t.closedAt+R.retainedMs-Date.now();if(n<=0){ee("with a closed generation still in memory");return}fe||(fe=setTimeout(()=>{fe=void 0,k.measure("retention-check").catch(()=>{})},n),fe.unref?.())}_(we,"Oe"),i(we,"retainedRecycleCheck");function _e(t,n){const l={};n&&(l.module=n),t&&(l.app=t);try{const p=t?.app;if(p&&typeof p=="object"&&(l.nestApp=p,p.container&&typeof p.container=="object")){l.container=p.container;const m=rootModuleOf(p.container);m&&(l.rootModule=m)}}catch{}return l}_(_e,"we"),i(_e,"generationTargets");async function V(t,n){if(t.closed||(t.closed=!0,G.delete(t),n&&console.error(`[child] ${n}`),!t.app))return;M.set(t,Date.now());const l=R.enabled&&R.stuckCloseMs>0?setTimeout(()=>{M.has(t)&&(console.error(`[child] a previous app has been closing for ${Math.round(R.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),ee("with a previous app stuck in close()"))},R.stuckCloseMs):void 0;l?.unref?.();const p=t.app;try{await t.app.close()}catch(m){console.error("[child] error closing a previous app:",m)}finally{M.delete(t),Y=Date.now(),l&&clearTimeout(l),t.app=null,t.abandoned=void 0,t.handler=(m,E)=>{E.statusCode=503,E.end("closed")},t.gen!==void 0&&k.closed(t.gen,se,_e(p,t.module)),t.module=void 0}if(t.superseded){try{ce((0,hot_swap_registries_1.pruneRegistries)(t.superseded,"serve"),"after closing the previous app")}catch(m){console.error("[child] could not release the previous app's class registrations:",m)}t.superseded=void 0}ie("after closing the previous app")}_(V,"ne"),i(V,"closeGeneration");async function ke(t){const n=Date.now();t.drainingSince=n;const l=i(()=>({inflight:t.inflight+j(t),work:T(t.workGen).count}),"busy"),p=l();if(p.inflight>0||p.work>0){for(;G.size>=ye;){const I=G.values().next().value;if(!I)break;const P=oe(I),te=Math.round((Date.now()-(I.drainingSince??n))/1e3);if(P.unanswered===0&&P.streams===0&&P.work===0){await V(I);continue}await V(I,`closing a still-draining generation early \u2014 ${G.size} were pending and the cap is ${ye} (each holds a whole app: DI container, DB pool, queue workers). `+(P.unanswered>0||P.work>0?`It was replaced ${te}s ago and had still not answered ${ne(P.unanswered,"request")}`+(P.work?` or finished ${ne(P.work,"background job")}`:"")+` \u2014 those fail now (a queue job keeps its lock until the TTL lapses). Raise LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS (currently ${Z}ms) to wait longer for them. `:`It held only ${ne(P.streams,"open stream")}; their clients reconnect to the new app. `)+"Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.")}G.add(t)}for(;le>0&&Date.now()-n<le;){if(t.closed)return;const I=l();if(I.inflight===0&&I.work===0)break;await new Promise(P=>{setTimeout(P,250).unref?.()})}if(t.closed)return;const m=l(),E=de(t.workGen);m.inflight>0||m.work>0?console.error(`[child] swap drain expired after ${Date.now()-n}ms \u2014 closing the previous app with ${m.inflight} in-flight request(s) and ${m.work} background job(s) still running. `+(E?`Previous app still processing ${E.running} queue job(s); ${E.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${le}ms) if this is legitimate work.`):(Date.now()-n>250||E?.runningAtStop||E?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-n}ms \u2014 closing it`+(E?` (it finished ${E.finishedAfterStop} queue job(s) after the swap; ${E.takenAfterStop} were taken after the swap)`:"")),await V(t)}_(ke,"Le"),i(ke,"drainThenClose");async function J(){if(S)return re=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),g;S=!0,Y=Date.now(),await $e(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(N,z)=>{console.error(`[child] uncaughtException (${z}):`,N)}),process.on("unhandledRejection",N=>{const z=N instanceof Error?N.stack||N.message:String(N);z.includes("FronteggContextScope")||z.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",N)});let t;if(Le)try{t=(0,hot_swap_registries_1.captureRegistries)()}catch{t=void 0}const n=global.__lensmcpBeginWorkGeneration,l=typeof n=="function"?n():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(N){if(!(N instanceof SyntaxError))throw N;console.warn(`[child] bundle was unreadable mid-write (${N.message}) \u2014 retrying the swap once`),await new Promise(z=>setTimeout(z,400)),await importFresh(BUNDLE_PATH)}}catch(N){throw S=!1,Y=Date.now(),N}if(typeof global.createChildApp!="function")throw S=!1,Y=Date.now(),new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);let p;if(t)try{p=(0,hot_swap_registries_1.supersededSince)(t),ce((0,hot_swap_registries_1.pruneRegistries)(p,"build"),"at the swap")}catch(N){console.error("[child] could not release superseded class registrations:",N)}let m;try{m=await global.createChildApp()}catch(N){throw S=!1,Y=Date.now(),takeImportedModule(),N}const E=U;p?.size&&(E.app?E.superseded=p:ce((0,hot_swap_registries_1.pruneRegistries)(p,"serve"),"at the swap")),g=m;const I=takeImportedModule();U={app:m,handler:m.handler,inflight:0,gen:++q,...I?{module:I}:{},...l===void 0?{}:{workGen:l}};const P=_e(m,void 0).container;P&&(O.add(new WeakRef(P)),ue=!0);const te=E.app&&l!==void 0?ve(l):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(te?.workers?`; its ${te.workers} queue worker(s) stopped taking new jobs, previous app still processing ${te.runningAtStop} job(s)`:"")),be(),E.app&&(se+=1,ke(E).catch(N=>console.error("[child] background close error:",N)),setTimeout(()=>ee("30s after a hot swap"),3e4).unref?.(),k.swapped(E.gen,()=>E.closed===!0&&!M.has(E))),S=!1,Y=Date.now(),re?(re=!1,J()):m}_(J,"q"),i(J,"swapNow");const Fe=debounce(async()=>{try{console.log("[child] /webpack/reload"),await J()}catch(t){console.error("[child] reload failed:",t)}},250),B=http.createServer((t,n)=>{if(t.method==="POST"&&t.url==="/webpack/reload"){n.writeHead(200,{"content-type":"application/json"}),n.end(JSON.stringify({ok:!0})),Fe();return}if(t.method==="GET"&&t.url==="/__lensmcp/alive"){const p=t.headers["x-internal-token"],m=process.env.LENSMCP_SERVICE_KEY;if(m&&p!==m){n.writeHead(404,{"content-type":"text/plain"}),n.end("not found");return}const E=T();n.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),n.end(JSON.stringify({ok:!0,pid:process.pid,swapping:S,inflight:U.inflight,oldestInflightAt:U.oldestAt,activeWork:E.count,oldestWorkAt:E.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...F?{watcher:F}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const l=U;l.inflight+=1,(l.open??=new Set).add(n),l.oldestAt===void 0&&(l.oldestAt=Date.now()),n.on("close",()=>{l.inflight-=1,l.open?.delete(n),Z>0&&!l.closed&&!n.headersSent&&!n.writableEnded&&((l.abandoned??=new Map).set(n,Date.now()),l.abandoned.size>256&&j(l)),l.inflight===0&&(l.oldestAt=void 0),k.activity()}),l.handler(t,n)});B.keepAliveTimeout=0;const $=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock($),process.exit(0)});try{const t=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const n=(0,schema_first_instance_1.prewarmZodFor)(t);n?.created&&console.log(`[child] zod${n.version?` ${n.version}`:""}: built ${n.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const n=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(t);n?.patched&&console.log(`[child] ioredis${n.versions.length?` ${n.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await J(),pe=(await k.booted()).live,console.log(`[child] Using bundle: ${t}`),claimSock($),B.listen($,()=>{console.log(`[child] Listening on ${$}`),tell(process,{type:"ready",socketPath:$})})}catch(t){console.error("[child] Startup error:",t),tell(process,{type:"boot-error",error:String(t?.stack||t)}),process.exitCode=1;return}process.on("message",async t=>{if(!(!t||typeof t!="object")){if(t.type==="debug-open"){const n=Number(t.port);a(Number.isInteger(n)&&n>1024?n:0);return}if(t.type==="watcher"){const n=t.testimony;n&&typeof n=="object"&&(F=n);return}if(t.type==="heap-snapshot"){X(t);return}if(t.type==="recycle-declined"){f=!1;return}if(t.type==="retire"){ge();return}if(t.type==="reload"){if(b)return;try{const n=Date.now();await J(),tell(process,{type:"reloaded",socketPath:$,swap:{generation:q,swaps:se,swapMs:Date.now()-n,draining:G.size}})}catch(n){console.error("[child] reload error:",n),tell(process,{type:"reload-error",error:String(n?.stack||n)})}}}});let Ee=!1;async function X(t){const n=i(l=>{tell(process,{type:"heap-snapshot-result",id:t.id,...l})},"reply");if(Ee)return n({ok:!1,refused:"a snapshot of this pod is already being written"});Ee=!0;try{const l=L(),p=k.tracker.views().map(P=>P.gen),m=(0,heap_snapshot_1.heapSnapshotPolicy)();t.maxHeapBytes&&(m.maxHeapBytes=t.maxHeapBytes);const E=(0,heap_snapshot_1.heapSnapshotRefusal)(m,{live:l,...t.available?{available:t.available}:{}});if(E)return n({ok:!1,refused:E,live:l});console.log(`[child] writing a heap snapshot (${Math.round(l/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const I=await(0,heap_snapshot_1.writeHeapSnapshot)(t.file);n({ok:!0,file:t.file,bytes:I.bytes,ms:I.ms,live:l,gen:U.gen??0,closedAlive:p})}catch(l){n({ok:!1,error:l?.message??String(l)})}finally{Ee=!1}}_(X,"z"),i(X,"takeHeapSnapshot");async function ge(t="a fresh pod has taken over"){if(b)return;b=!0,k.stop(),console.log(`[child] retiring \u2014 ${t}; ${path.basename($,".sock")} (pid ${process.pid}) is finishing in-flight work, then exiting`);const n=ve(Number.POSITIVE_INFINITY);n?.workers&&console.log(`[child] its ${n.workers} queue worker(s) stopped taking new jobs, still processing ${n.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath($),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(p=>{setTimeout(p,We).unref?.()}),cleanupSock($),B.close();const l=Date.now();for(;Date.now()-l<le;){B.closeIdleConnections?.();let p=0;for(const m of G)p+=oe(m).unanswered;if(U.inflight+j(U)===0&&p===0&&T().count===0)break;await new Promise(m=>{setTimeout(m,250).unref?.()})}setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(p){console.error("[child] close error while retiring:",p)}process.exit(0)}_(ge,"_e"),i(ge,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const Ce=i(async t=>{console.log(`[child ${t}] shutting down\u2026`);const n=g?.close().catch(p=>console.error("[child] close error:",p)),l=new Promise(p=>B.close(()=>p()));cleanupSock($),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([n,l]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(t=>{process.on(t,()=>{if(t==="SIGTERM"&&!b&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){ge("this generation was handed over to a newer one");return}inspector?.close?.(),Ce(t)})})})()}else{let a=_(function(f){j.push(f),j.length>Z&&j.splice(0,j.length-Z),ye?.write((0,inspector_secret_1.redactInspectorUrls)(f.replace(G,"")));for(const b of oe)try{const v=b.plain?f.replace(G,""):f;b.res.write(b.redact?(0,inspector_secret_1.redactInspectorUrls)(v):v)}catch{oe.delete(b)}},"pe");i(a,"emitLogLine");const h=12,g=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",S=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),F=i(f=>({project:g,service:SERVICE_NAME||g,pod:f.id,pid:f.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),re=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],Le="\x1B[0m",ce=Math.max(1,Number(process.env.CHILD_COUNT||1)),U=LENSMCP_WS_KEY||path.basename(process.cwd()),le=i(f=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(U,SERVICE_NAME||"service",f)),"execPathFor");let T=(()=>{const f=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(f)&&f>1024?f:0})();const ve=20;T&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/closed/codex-security-06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${T}+slot, workers ${T}+${ve}+i). Any account on this host can connect to 127.0.0.1:${T} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const de=process.env.LENSMCP_LOG_FILE||"",ye=de?(()=>{try{fs.mkdirSync(path.dirname(de),{recursive:!0});const f=fs.createWriteStream(de,{flags:"a",mode:384});try{fs.chmodSync(de,384)}catch{}return f}catch(f){return console.warn(`[parent] cannot open log file ${de}:`,f.message),null}})():null,G=/\x1b\[[0-9;]*m/g,Z=2e3,j=[],oe=new Set,ne=(0,devserver_transport_1.gatewayManaged)();for(const f of["log","info","warn","error"]){const b=console[f].bind(console),v=f==="log"||f==="info"?process.stdout:process.stderr;console[f]=(...q)=>{b(...q.map(O=>typeof O=="string"?(0,inspector_secret_1.forStream)(v,ne,O):O)),a(q.map(O=>typeof O=="string"?O:util.inspect(O)).join(" ")+`
|
|
13
|
-
`)}}const
|
|
14
|
-
`+String(
|
|
15
|
-
`;L.write((0,inspector_secret_1.forStream)(L,ne,`${b}${W}`)),a(`${b}${W}`)},"write"),ue=i((H,k)=>{if(!H)return;const L=readline.createInterface({input:H});L.on("line",W=>O(k,W)),L.on("close",()=>{})},"attach");ue(f.stdout,"stdout"),ue(f.stderr,"stderr")},"wireProcLogging"),We=i(f=>se(f.proc,R(f.id),b=>f.tail.push(b)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const f=httpProxy.createProxyServer({});let b=Number(process.env.PORT)||0;b||(b=9090);const v=[],q=new Set;function O(){let e=0;for(;q.has(e);)e++;return q.add(e),e}_(O,"I"),i(O,"allocDebugSlot");let ue=1,H=0,k=!1,L=ce,W=[],me=0;const be=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),M=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),Y=i(e=>e.booting&&!M(e)&&Date.now()-e.spawnedAt<be,"stillBooting");function he(){return v.filter(e=>e.healthy&&typeof e.socketPath=="string")}_(he,"he"),i(he,"healthyChildren");let K,ie=0,Me;const ee={project:g,service:SERVICE_NAME||g,podGeneration:POD_GENERATION};function fe(e){const r=(0,service_build_events_1.buildAccountOf)(e.building);r&&r.startedAt!==ie&&(ie=r.startedAt,S.publish((0,service_build_events_1.buildServiceBuildEvent)(ee,"started",r,{root:process.cwd()})));const c=(0,service_build_events_1.buildAccountOf)(e.failed);c&&(ie=0,S.publish((0,service_build_events_1.buildServiceBuildEvent)(ee,"failed",c,{root:process.cwd()})))}_(fe,"fe"),i(fe,"recordBuildProgress");function we(e){if(!e||typeof e!="object")return;const r=e;if(r.schemaVersion===1){fe(r),"failed"in r&&delete r.failed,K=r;for(const c of v)tell(c.proc,{type:"watcher",testimony:r})}}_(we,"Oe"),i(we,"recordWatcherTestimony");function _e(e,r=256*1024){return new Promise(c=>{let u="",s=!1;e.on("data",o=>{s||(u+=o,u.length>r&&(s=!0))}),e.on("end",()=>{if(s||!u)return c(void 0);try{c(JSON.parse(u))}catch{c(void 0)}}),e.on("error",()=>c(void 0))})}_(_e,"we"),i(_e,"readJsonBody");function V(){const e=he();if(e.length===0)return null;const r=H%e.length;return H=(H+1)%e.length,e[r]}_(V,"ne"),i(V,"pickChild");function ke(e,r){const c=`Child#${e.id}`,u=SERVICE_NAME||g,s=Date.now();if(r.type==="pod-memory"){const o=r.report;if(!o||typeof o.live!="number")return;e.memory.latest=o,e.memory.history.push(o),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(c,o)}`),S.publish((0,pod_memory_events_1.buildPodMemoryEvent)(F(e),o))}else if(r.type==="pod-memory-retained"){const o=r.retained;if(!o||typeof o.gen!="number")return;e.memory.retained.set(o.gen,{...o,at:s}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(c,u,o)}`),S.publish((0,pod_memory_events_1.buildPodRetainedEvent)(F(e),o,s))}else{const o=r.released;if(!o||typeof o.gen!="number")return;e.memory.retained.delete(o.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(c,u,o)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(F(e),o,s))}}_(ke,"Le"),i(ke,"recordPodMemory");const J=new Map;let Fe=0;async function B(){const e=ue++,r=childSockPath(e);cleanupSock(r);const c=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&c.unshift("--require","@swc-node/register"),c.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const u=T?O():-1,s=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:r,...u>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(T,u))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:c,execPath:le("pod")}),o={id:e,proc:s,healthy:!1,lastError:null,debugSlot:u,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:me,memory:{history:[],retained:new Map}};return s.on("error",w=>console.error(`[parent] Child#${e}: ${w.message}`)),s.on("message",w=>{if(!w||typeof w!="object")return;const d=w;if(d.type==="ready")o.socketPath=String(d.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),K&&tell(s,{type:"watcher",testimony:K}),me!==o.bootReloadSeq&&tell(s,{type:"reload"});else if(d.type==="recycle-request")l(o,String(d.reason||"it has outgrown its bound"));else if(d.type==="inspector-url")o.inspectorUrl=String(d.url),console.log(`[parent] Child#${o.id} Debugger listening on ${d.url}`);else if(d.type==="reloaded"){if(X.has(o)){console.log(`[parent] Child#${o.id} finished a swap it began before it was retired \u2014 not serving it: a fresh pod has taken over`);return}o.socketPath=String(d.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`);const y=d.swap;y&&typeof y.generation=="number"&&typeof y.swaps=="number"&&S.publish((0,service_build_events_1.buildServiceSwapEvent)(F(o),{generation:y.generation,swaps:y.swaps,swapMs:typeof y.swapMs=="number"?y.swapMs:0,draining:typeof y.draining=="number"?y.draining:0},Me,{root:process.cwd()}))}else d.type==="reload-error"?(o.lastError=String(d.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):d.type==="boot-error"?(o.lastError=String(d.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1):d.type==="pod-memory"||d.type==="pod-memory-retained"||d.type==="pod-memory-released"?ke(o,d):d.type==="heap-snapshot-result"&&J.get(Number(d.id))?.(d)}),s.on("exit",(w,d)=>{o.booting=!1;for(const C of o.memory.retained.values()){const D={gen:C.gen,closedAt:C.closedAt,ageMs:Date.now()-C.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${o.id}`,SERVICE_NAME||g,D)}`),S.publish((0,pod_memory_events_1.buildPodReleasedEvent)(F(o),D,Date.now()))}o.memory.retained.clear(),o.memory.latest&&S.publish((0,pod_memory_events_1.buildPodExitedEvent)(F(o),{code:w,signal:d},Date.now())),o.debugSlot>=0&&(q.delete(o.debugSlot),o.debugSlot=-1);const y=d==="SIGINT"||d==="SIGTERM"||w===0;if(y?console.log(`[parent] Child#${o.id} exited cleanly (code=${w}, signal=${d??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${w}, signal=${d??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,w,d,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${w}, signal=${d??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!y&&!k){const C=v.indexOf(o);C>=0&&v.splice(C,1);const D=Date.now();if(W=W.filter(Q=>D-Q<3e4),W.length>=5){console.error(`[parent] crash-loop detected (${W.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}W.push(D),setTimeout(()=>{const Q=v.filter(Te=>Te.proc.killed===!1).length;!k&&Q<L&&(console.log(`[parent] respawning a crashed pod (${Q}/${L} live)`),B().catch(()=>{}))},1500).unref?.()}}),v.push(o),We(o),o}_(B,"K"),i(B,"spawnChild");async function $(e){L=Math.max(L,e);const r=v.filter(u=>u.proc.killed===!1),c=e-r.length;for(let u=0;u<c;u++)await B()}_($,"M"),i($,"ensurePoolSize");async function Ee(){const e=v.filter(r=>(!r.healthy||typeof r.socketPath!="string")&&!Y(r));await Promise.all(e.map(async r=>{try{r.proc.kill("SIGTERM")}catch{}cleanupSock(r.socketPath,r.proc.pid),r.socketPath=void 0;const c=v.indexOf(r);c>=0&&v.splice(c,1),await B()}))}_(Ee,"Ee"),i(Ee,"respawnUnhealthy");const X=new Set;let ge=!1,Ce=0;const t=3e4,n=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function l(e,r){const c=i(s=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${r}) \u2014 not now: ${s}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(k||!v.includes(e))return;if(ge)return c("another pod is already being replaced");const u=Date.now()-Ce;if(u<t)return c(`a pod was replaced ${Math.round(u/1e3)}s ago`);ge=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${r}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const s=await B(),o=Date.now()+be;for(;!s.healthy&&!s.lastError&&!M(s)&&Date.now()<o;)await new Promise(y=>{setTimeout(y,100).unref?.()});if(k)return;if(!s.healthy){const y=v.indexOf(s);y>=0&&v.splice(y,1);try{s.proc.kill("SIGTERM")}catch{}return c(`its successor Child#${s.id} did not come up (${s.lastError??"no answer in time"})`)}const w=v.indexOf(e);w>=0&&v.splice(w,1),e.healthy=!1,X.add(e),postRetiringNotice(e.socketPath,e.proc.pid);const d=setTimeout(()=>{if(!M(e)){console.error(`[parent] Child#${e.id} was still running ${n}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},n);if(d.unref?.(),e.proc.once("exit",()=>{clearTimeout(d),X.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}Ce=Date.now(),console.log(`[parent] Child#${s.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${s.proc.pid})`)}catch(s){c(`could not start a successor: ${s?.message??String(s)}`)}finally{ge=!1}}_(l,"l"),i(l,"recyclePod");const p=[];function m(e){const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),c=path.join(r,`${e}.js`);if(!fs.existsSync(c))return console.error(`[parent] Worker "${e}" bundle not found: ${c}`),null;const u=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&u.unshift("--require","@swc-node/register");try{u.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const s=inspectorPortFor(T||9229,ve+Math.max(0,WORKER_NAMES.indexOf(e)));u.push(T?`--inspect=127.0.0.1:${s}`:`--inspect-port=127.0.0.1:${s}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const o=(0,node_child_process_1.fork)(c,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:u,execPath:le(`w-${e}`)}),w={name:e,proc:o,debugPort:s};return p.push(w),o.on("error",d=>console.error(`[parent] Worker "${e}": ${d.message}`)),se(o,`\x1B[35m[worker:${e}] \x1B[0m`,d=>{const y=/Debugger listening on (wss?:\/\/\S+)/.exec(d);y&&(w.inspectorUrl=y[1])}),o.on("exit",(d,y)=>{y==="SIGINT"||y==="SIGTERM"||d===0||console.error(`[parent] Worker "${e}" crashed (code=${d}, signal=${y??"none"})`);const C=p.indexOf(w);C>=0&&p.splice(C,1)}),console.log(`[parent] Worker "${e}" started (pid=${o.pid})`),w}_(m,"g"),i(m,"spawnWorkerProcess");const E=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),I=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),P=new Set,te=i(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function N(e,r){if(P.delete(e),!te(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${r} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}_(N,"R"),i(N,"killDrainingWorker");function z(e){try{e.proc.kill("SIGTERM")}catch{}if(te(e))return;const r=[...P].filter(u=>u.name===e.name);for(;r.length>=I;)N(r.shift(),`was still running when a later reload replaced its successor (cap ${I})`);P.add(e);const c=setTimeout(()=>N(e,`was still running ${E}ms after SIGTERM`),E);c.unref?.(),e.proc.once("exit",()=>{clearTimeout(c),P.delete(e)})}_(z,"B"),i(z,"retireWorker");function qe(){for(const e of[...p])z(e);p.length=0;for(const e of WORKER_NAMES)m(e)}_(qe,"Ve"),i(qe,"restartWorkers");const Xe=debounce(async()=>{me+=1;const e=he();if(e.length>0){console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const r of e)tell(r.proc,{type:"reload"})}await Ee(),WORKER_NAMES.length>0&&qe()},200),Be=i((e,r)=>{const c=(0,notice_generation_1.noticeGeneration)(e.headers);return c===void 0||c===POD_GENERATION?!1:(e.resume(),r.writeHead(409,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!1,generation:POD_GENERATION,sentFor:c})),e.url==="/webpack/reload"&&console.log(`[parent] a build of generation g${c} reached generation g${POD_GENERATION} \u2014 not reloaded: it is not this generation's build`),!0)},"refusedForAnotherGeneration"),Pe=i(async(e,r)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);if(Be(e,r))return;r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0})),_e(e).then(s=>{const o=s;Me=(0,service_build_events_1.buildAccountOf)(o?.build),ie=0,we(o?.watcher)}),Xe();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);if(Be(e,r))return;const s=await _e(e);we(s),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,relayed:K!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);let s="";e.on("data",o=>{s+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(s||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await $(o),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const s=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(s.get("tail")??"100")||0,0),Z),w=s.get("follow")!=="0",d=s.get("plain")==="1",y=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);r.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const C=o===0?"":j.slice(-o).join(""),D=d?C.replace(G,""):C;if(r.write(y?(0,inspector_secret_1.redactInspectorUrls)(D):D),!w){r.end();return}const Q={res:r,plain:d,redact:y};oe.add(Q),e.on("close",()=>oe.delete(Q));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const s=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(s)&&s>1024&&(T=s),T||(T=9229);const o=v.filter(d=>d.proc.exitCode===null&&d.proc.signalCode===null);for(const d of o)d.debugSlot<0&&(d.debugSlot=O()),tell(d.proc,{type:"debug-open",port:inspectorPortFor(T,d.debugSlot)});for(const d of p)try{d.proc.kill("SIGUSR1")}catch{}const w=Date.now()+2e3;for(;Date.now()<w&&o.some(d=>!d.inspectorUrl);)await new Promise(d=>setTimeout(d,50));r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,basePort:T,fixedPorts:usesFixedInspectorPorts(),pods:o.map(d=>({id:d.id,inspectorUrl:d.inspectorUrl??null})),workers:p.map(d=>({name:d.name,port:d.debugPort||(0,inspector_secret_1.inspectorPortOf)(d.inspectorUrl),inspectorUrl:d.inspectorUrl??null}))},null,2)+`
|
|
16
|
-
`);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const
|
|
17
|
-
`);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const
|
|
18
|
-
`)},"reply"),o=new URL(e.url,"http://localhost").searchParams,
|
|
12
|
+
})()`),(0,bundle_parent_1.routeBundleRequires)(requested,resolved);const exports=eval(`require(${JSON.stringify(resolved)})`);try{const mod=eval("require.cache")[resolved];lastImportedModule=mod&&typeof mod=="object"?mod:void 0}catch{}return exports}h(importFresh,"importFresh"),a(importFresh,"importFresh");function debounce(c,f){let g=null;return(...E)=>{g&&clearTimeout(g),g=setTimeout(()=>c(...E),f)}}if(h(debounce,"debounce"),a(debounce,"debounce"),process.env.APP_RUNNER==="1"){(0,stdio_guard_1.guardBrokenStdio)();const c=a(g=>{if(!inspector.url())try{inspector.open(g,"127.0.0.1",!1)}catch(L){console.warn(`[child] inspector.open(${g}) failed (port taken by another devserver?) \u2014 falling back to a random port:`,L.message);try{inspector.open(0,"127.0.0.1",!1)}catch{}}const E=inspector.url();E&&tell(process,{type:"inspector-url",url:E})},"openInspector"),f=process.env.CHILD_DEBUG_PORT;if(f!==void 0&&f!==""){const g=Number(f);Number.isInteger(g)&&(g===0||g>1024)&&c(g)}try{require("@lensmcp/node-instrumentation/register")}catch{}(async()=>{let g=null,E=!1,L,Z=!1;const He=(0,hot_swap_registries_1.registryPruningEnabled)(),le=a((t,s)=>{const l=(0,hot_swap_registries_1.describePrune)(t,s);l&&console.log(l)},"logPrune");let B={app:null,handler:a((t,s)=>{s.statusCode=503,s.end("starting")},"handler"),inflight:0};const de=(()=>{const t=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(t)&&t>=0?t:15*6e4})(),T=a(t=>{const s=globalThis.__lensmcpActiveWork;if(typeof s!="function")return{count:0};try{const l=s(t);return{count:Number(l?.count??0),...l?.oldestStartedAt?{oldestStartedAt:l.oldestStartedAt}:{}}}catch{return{count:0}}},"activeWork"),ke=a(t=>{const s=globalThis.__lensmcpStopWorkIntake;if(typeof s=="function")try{return s(t)}catch{return}},"stopWorkIntake"),pe=a(t=>{const s=globalThis.__lensmcpWorkIntakeReport;if(!(t===void 0||typeof s!="function"))try{return s(t)??void 0}catch{return}},"workIntakeReport"),Ee=(()=>{const t=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(t)&&t>=1?Math.floor(t):1})(),W=new Set,z=(()=>{const t=Number(process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS);return process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==void 0&&process.env.LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS!==""&&Number.isFinite(t)&&t>=0?t:3e4})(),K=a(t=>{if(!t.abandoned?.size)return 0;const s=Date.now();for(const[l,u]of t.abandoned)(l.writableEnded||s-u>=z)&&t.abandoned.delete(l);return t.abandoned.size},"abandonedRunning"),ee=a(t=>{let s=0;for(const l of t.open??[])l.headersSent||(s+=1);return{unanswered:s+K(t),streams:Math.max(0,t.inflight-s),work:T(t.workGen).count}},"owedBy"),te=a((t,s,l=`${s}s`)=>`${t} ${t===1?s:l}`,"plural");async function De(){if(z<=0)return;const t=Date.now();let s=!1;for(;!(W.size<Ee);){const l=W.values().next().value;if(!l||l.closed)break;const u=ee(l);if(u.unanswered===0&&u.work===0)break;const y=(l.drainingSince??t)+z-Date.now();if(y<=0)break;s||(s=!0,console.log(`[child] swap held \u2014 the app replaced ${Math.round((Date.now()-(l.drainingSince??t))/100)/10}s ago is still `+[u.unanswered?`answering ${te(u.unanswered,"request")}`:"",u.work?`running ${te(u.work,"background job")}`:""].filter(Boolean).join(" and ")+`, and this pod keeps at most ${Ee} replaced app(s) alive (LENSMCP_SWAP_MAX_DRAINING). The new build is applied when it is done \u2014 in ${Math.ceil(y/1e3)}s at the latest (LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS).`)),await new Promise(w=>{setTimeout(w,Math.min(100,y)).unref?.()})}s&&console.log(`[child] swap resumed after ${Date.now()-t}ms`)}h(De,"Ge"),a(De,"holdForOwedWork");const R=(0,swap_recycle_1.swapRecyclePolicy)();let ue,re=0;const Ue=(()=>{const t=Number(process.env.LENSMCP_POD_RETIRE_NOTICE_MS);return Number.isFinite(t)&&t>=0?t:3500})();let _=!1,b=!1;const v=(0,pod_memory_1.podMemoryPolicy)();let j=0;const I=new Set;let he=!1;const q=a(()=>{if(!he)return;let t=0;for(const s of I)s.deref()===void 0?I.delete(s):t+=1;return t},"liveContainers"),k=(0,pod_memory_1.createPodMemoryMonitor)({policy:v,state:a(()=>({gen:B.gen??0,swaps:re,...ue===void 0?{}:{baseline:ue},busy:E||Z||V.size>0,lastBusyAt:M}),"state"),send:a(t=>{tell(process,t)},"send"),containers:q,counter:(0,pod_memory_1.createLiveObjectCounter)(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH)),onRetained:a(()=>Ne(),"onRetained"),gc:a(()=>(0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc),"gc")}),F=a(()=>((0,gc_notice_1.withGcNotice)($,pod_memory_1.fullGc)&&k.tracker.noteGc(),process.memoryUsage().heapUsed),"liveHeap"),H=(()=>{if(!(0,error_stack_release_1.errorFrameReleaseEnabled)())return null;try{const t=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH))+path.sep,s=`${path.sep}node_modules${path.sep}`,l=new Set(Object.keys(require.cache));return(0,error_stack_release_1.createErrorFrameReleaser)(require.cache,{skip:a(u=>l.has(u)||u.startsWith(t)&&!u.slice(t.length).includes(s)&&!u.slice(t.length).startsWith(`node_modules${path.sep}`),"skip")})}catch{return null}})();let fe=!1;function Pe(){if(!(!H||fe))try{const t=H();t.errors>0&&console.log(`[child] read the stack of ${t.errors} Error object(s) that libraries created while loading and keep for good \u2014 unread, each held the functions that were on its stack, this bundle's included`)}catch(t){console.error("[child] could not release library error frames:",t),fe=!0}}h(Pe,"Ie"),a(Pe,"releaseLibraryErrorFrames");const V=new Map;let M=Date.now();const xe=a(()=>R.settleMs<=0||!E&&!Z&&V.size===0&&Date.now()-M>=R.settleMs,"settled");let Y;function oe(t){if(!(!R.enabled||_||b)){if(R.settleMs<=0){J(t);return}Y&&clearTimeout(Y),Y=setTimeout(()=>{Y=void 0,xe()?J(t):oe(t)},R.settleMs),Y.unref?.()}}h(oe,"le"),a(oe,"recycleCheckWhenSettled");const Se=a(()=>{if(R.stuckCloseMs<=0)return 0;const t=Date.now();let s=0;for(const l of V.values())t-l>=R.stuckCloseMs&&(s+=1);return s},"stuckCloses");function J(t){if(!R.enabled||_||b||!process.send)return;const s=v8.getHeapStatistics().heap_size_limit,l=a(y=>{const w=k.oldestRetained();return{swaps:re,usedBytes:y,limitBytes:s,stuckCloses:Se(),settled:xe(),...ue===void 0?{}:{baselineBytes:ue},...w?{retained:{generation:w.gen,ageMs:Date.now()-w.closedAt,gcs:w.gcs}}:{}}},"state");if(!(0,swap_recycle_1.recycleReason)(R,l(process.memoryUsage().heapUsed)))return;const u=(0,swap_recycle_1.recycleReason)(R,l(F()));u&&(_=!0,console.log(`[child] asking for a fresh pod ${t} \u2014 ${u}. This pod keeps serving until its successor is ready. Size the bound with LENSMCP_SWAP_RECYCLE_HEAP_FACTOR / _HEAP_RATIO / _MIN_MB / _RETAINED_MS, or turn it off with LENSMCP_SWAP_RECYCLE=0.`),tell(process,{type:"recycle-request",reason:u}))}h(J,"X"),a(J,"considerRecycle");let _e;function Ne(){if(!R.enabled||R.retainedMs<=0||_||b)return;const t=k.oldestRetained();if(!t)return;const s=t.closedAt+R.retainedMs-Date.now();if(s<=0){J("with a closed generation still in memory");return}_e||(_e=setTimeout(()=>{_e=void 0,k.measure("retention-check").catch(()=>{})},s),_e.unref?.())}h(Ne,"We"),a(Ne,"retainedRecycleCheck");function ge(t,s){const l={};s&&(l.module=s),t&&(l.app=t);try{const u=t?.app;if(u&&typeof u=="object"&&(l.nestApp=u,u.container&&typeof u.container=="object")){l.container=u.container;const y=rootModuleOf(u.container);y&&(l.rootModule=y)}}catch{}return l}h(ge,"Pe"),a(ge,"generationTargets");async function ne(t,s){if(t.closed||(t.closed=!0,W.delete(t),s&&console.error(`[child] ${s}`),!t.app))return;V.set(t,Date.now());const l=R.enabled&&R.stuckCloseMs>0?setTimeout(()=>{V.has(t)&&(console.error(`[child] a previous app has been closing for ${Math.round(R.stuckCloseMs/1e3)}s and its close() has not returned \u2014 one of its shutdown hooks is waiting on something that does not end (a queue worker, a connection, a stream). It stays in memory until this pod is replaced.`),J("with a previous app stuck in close()"))},R.stuckCloseMs):void 0;l?.unref?.();const u=t.app;try{await t.app.close()}catch(y){console.error("[child] error closing a previous app:",y)}finally{V.delete(t),M=Date.now(),l&&clearTimeout(l),t.app=null,t.abandoned=void 0,t.handler=(y,w)=>{w.statusCode=503,w.end("closed")},t.gen!==void 0&&k.closed(t.gen,re,ge(u,t.module)),t.module=void 0}if(t.superseded){try{le((0,hot_swap_registries_1.pruneRegistries)(t.superseded,"serve"),"after closing the previous app")}catch(y){console.error("[child] could not release the previous app's class registrations:",y)}t.superseded=void 0}oe("after closing the previous app")}h(ne,"_e"),a(ne,"closeGeneration");async function se(t){const s=Date.now();t.drainingSince=s;const l=a(()=>({inflight:t.inflight+K(t),work:T(t.workGen).count}),"busy"),u=l();if(u.inflight>0||u.work>0){for(;W.size>=Ee;){const O=W.values().next().value;if(!O)break;const A=ee(O),Q=Math.round((Date.now()-(O.drainingSince??s))/1e3);if(A.unanswered===0&&A.streams===0&&A.work===0){await ne(O);continue}await ne(O,`closing a still-draining generation early \u2014 ${W.size} were pending and the cap is ${Ee} (each holds a whole app: DI container, DB pool, queue workers). `+(A.unanswered>0||A.work>0?`It was replaced ${Q}s ago and had still not answered ${te(A.unanswered,"request")}`+(A.work?` or finished ${te(A.work,"background job")}`:"")+` \u2014 those fail now (a queue job keeps its lock until the TTL lapses). Raise LENSMCP_SWAP_EARLY_CLOSE_GRACE_MS (currently ${z}ms) to wait longer for them. `:`It held only ${te(A.streams,"open stream")}; their clients reconnect to the new app. `)+"Raise LENSMCP_SWAP_MAX_DRAINING if this pod legitimately needs more.")}W.add(t)}for(;de>0&&Date.now()-s<de;){if(t.closed)return;const O=l();if(O.inflight===0&&O.work===0)break;await new Promise(A=>{setTimeout(A,250).unref?.()})}if(t.closed)return;const y=l(),w=pe(t.workGen);y.inflight>0||y.work>0?console.error(`[child] swap drain expired after ${Date.now()-s}ms \u2014 closing the previous app with ${y.inflight} in-flight request(s) and ${y.work} background job(s) still running. `+(w?`Previous app still processing ${w.running} queue job(s); ${w.takenAfterStop} were taken after the swap. `:"")+`A queue job killed here keeps its lock until the TTL lapses. Raise LENSMCP_SWAP_DRAIN_MS (currently ${de}ms) if this is legitimate work.`):(Date.now()-s>250||w?.runningAtStop||w?.takenAfterStop)&&console.log(`[child] previous app drained in ${Date.now()-s}ms \u2014 closing it`+(w?` (it finished ${w.finishedAfterStop} queue job(s) after the swap; ${w.takenAfterStop} were taken after the swap)`:"")),await ne(t)}h(se,"ge"),a(se,"drainThenClose");async function ie(){if(E)return Z=!0,console.log("[child] Reload deferred \u2014 will re-swap after current swap completes"),g;E=!0,M=Date.now(),await De(),process.removeAllListeners("uncaughtException"),process.removeAllListeners("unhandledRejection"),process.on("uncaughtException",(N,C)=>{console.error(`[child] uncaughtException (${C}):`,N)}),process.on("unhandledRejection",N=>{const C=N instanceof Error?N.stack||N.message:String(N);C.includes("FronteggContextScope")||C.includes("populateLoggerMetadata")||console.error("[child] unhandledRejection:",N)});let t;if(He)try{t=(0,hot_swap_registries_1.captureRegistries)()}catch{t=void 0}const s=global.__lensmcpBeginWorkGeneration,l=typeof s=="function"?s():void 0;try{try{await importFresh(BUNDLE_PATH)}catch(N){if(!(N instanceof SyntaxError))throw N;console.warn(`[child] bundle was unreadable mid-write (${N.message}) \u2014 retrying the swap once`),await new Promise(C=>setTimeout(C,400)),await importFresh(BUNDLE_PATH)}}catch(N){throw E=!1,M=Date.now(),N}if(typeof global.createChildApp!="function")throw E=!1,M=Date.now(),new Error(`Bundle '${BUNDLE_PATH}' does not export createChildApp() and the zero-touch bridge is not active. Either install @lensmcp/node-instrumentation (a plain NestFactory.create + app.listen main.ts then just works) or set global.createChildApp in main.ts when DEVSERVER_MODE === '1'.`);let u;if(t)try{u=(0,hot_swap_registries_1.supersededSince)(t),le((0,hot_swap_registries_1.pruneRegistries)(u,"build"),"at the swap")}catch(N){console.error("[child] could not release superseded class registrations:",N)}let y;try{y=await global.createChildApp()}catch(N){throw E=!1,M=Date.now(),takeImportedModule(),N}const w=B;u?.size&&(w.app?w.superseded=u:le((0,hot_swap_registries_1.pruneRegistries)(u,"serve"),"at the swap")),g=y;const O=takeImportedModule();B={app:y,handler:y.handler,inflight:0,gen:++j,...O?{module:O}:{},...l===void 0?{}:{workGen:l}};const A=ge(y,void 0).container;A&&(I.add(new WeakRef(A)),he=!0);const Q=w.app&&l!==void 0?ke(l):void 0;return console.log("[child] Swapped to fresh app (previous draining in background)"+(Q?.workers?`; its ${Q.workers} queue worker(s) stopped taking new jobs, previous app still processing ${Q.runningAtStop} job(s)`:"")),Pe(),w.app&&(re+=1,se(w).catch(N=>console.error("[child] background close error:",N)),setTimeout(()=>J("30s after a hot swap"),3e4).unref?.(),k.swapped(w.gen,()=>w.closed===!0&&!V.has(w))),E=!1,M=Date.now(),Z?(Z=!1,ie()):y}h(ie,"ye"),a(ie,"swapNow");const ve=debounce(async()=>{try{console.log("[child] /webpack/reload"),await ie()}catch(t){console.error("[child] reload failed:",t)}},250),ye=http.createServer((t,s)=>{if(t.method==="POST"&&t.url==="/webpack/reload"){s.writeHead(200,{"content-type":"application/json"}),s.end(JSON.stringify({ok:!0})),ve();return}if(t.method==="GET"&&t.url==="/__lensmcp/alive"){const u=t.headers["x-internal-token"],y=process.env.LENSMCP_SERVICE_KEY;if(y&&u!==y){s.writeHead(404,{"content-type":"text/plain"}),s.end("not found");return}const w=T();s.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),s.end(JSON.stringify({ok:!0,pid:process.pid,swapping:E,inflight:B.inflight,oldestInflightAt:B.oldestAt,activeWork:w.count,oldestWorkAt:w.oldestStartedAt,keyFingerprint:serviceKeyFingerprint(),...L?{watcher:L}:{},...Number.isFinite(Number(process.env.LENSMCP_POD_GENERATION))&&process.env.LENSMCP_POD_GENERATION?{podGeneration:Number(process.env.LENSMCP_POD_GENERATION)}:{}}));return}const l=B;l.inflight+=1,(l.open??=new Set).add(s),l.oldestAt===void 0&&(l.oldestAt=Date.now()),s.on("close",()=>{l.inflight-=1,l.open?.delete(s),z>0&&!l.closed&&!s.headersSent&&!s.writableEnded&&((l.abandoned??=new Map).set(s,Date.now()),l.abandoned.size>256&&K(l)),l.inflight===0&&(l.oldestAt=void 0),k.activity()}),l.handler(t,s)});ye.keepAliveTimeout=0;const $=process.env.CHILD_SOCK_PATH;process.on("disconnect",()=>{cleanupSock($),process.exit(0)});try{const t=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.join(__dirname,BUNDLE_PATH);if((0,schema_first_instance_1.schemaPrewarmEnabled)())try{const s=(0,schema_first_instance_1.prewarmZodFor)(t);s?.created&&console.log(`[child] zod${s.version?` ${s.version}`:""}: built ${s.created} throwaway schemas first, so none of the bundle's own is the first of its class`)}catch{}if((0,redis_disconnect_fix_1.redisDisconnectFixEnabled)())try{const s=(0,redis_disconnect_fix_1.finishStrandedDisconnectsFor)(t);s?.patched&&console.log(`[child] ioredis${s.versions.length?` ${s.versions.join(", ")}`:""}: a disconnect() between reconnect attempts now ends the client \u2014 it used to leave a queue worker's close() waiting for good`)}catch{}await ie(),ue=(await k.booted()).live,console.log(`[child] Using bundle: ${t}`),claimSock($),ye.listen($,()=>{console.log(`[child] Listening on ${$}`),tell(process,{type:"ready",socketPath:$})})}catch(t){console.error("[child] Startup error:",t),tell(process,{type:"boot-error",error:String(t?.stack||t)}),process.exitCode=1;return}process.on("message",async t=>{if(!(!t||typeof t!="object")){if(t.type==="debug-open"){const s=Number(t.port);c(Number.isInteger(s)&&s>1024?s:0);return}if(t.type==="watcher"){const s=t.testimony;s&&typeof s=="object"&&(L=s);return}if(t.type==="heap-snapshot"){Ae(t);return}if(t.type==="recycle-declined"){_=!1;return}if(t.type==="retire"){U();return}if(t.type==="reload"){if(b)return;try{const s=Date.now();await ie(),tell(process,{type:"reloaded",socketPath:$,swap:{generation:j,swaps:re,swapMs:Date.now()-s,draining:W.size}})}catch(s){console.error("[child] reload error:",s),tell(process,{type:"reload-error",error:String(s?.stack||s)})}}}});let we=!1;async function Ae(t){const s=a(l=>{tell(process,{type:"heap-snapshot-result",id:t.id,...l})},"reply");if(we)return s({ok:!1,refused:"a snapshot of this pod is already being written"});we=!0;try{const l=F(),u=k.tracker.views().map(A=>A.gen),y=(0,heap_snapshot_1.heapSnapshotPolicy)();t.maxHeapBytes&&(y.maxHeapBytes=t.maxHeapBytes);const w=(0,heap_snapshot_1.heapSnapshotRefusal)(y,{live:l,...t.available?{available:t.available}:{}});if(w)return s({ok:!1,refused:w,live:l});console.log(`[child] writing a heap snapshot (${Math.round(l/1048576)} MB live) \u2014 the pod pauses while V8 serialises it`);const O=await(0,heap_snapshot_1.writeHeapSnapshot)(t.file);s({ok:!0,file:t.file,bytes:O.bytes,ms:O.ms,live:l,gen:B.gen??0,closedAlive:u})}catch(l){s({ok:!1,error:l?.message??String(l)})}finally{we=!1}}h(Ae,"Ue"),a(Ae,"takeHeapSnapshot");async function U(t="a fresh pod has taken over"){if(b)return;b=!0,k.stop(),console.log(`[child] retiring \u2014 ${t}; ${path.basename($,".sock")} (pid ${process.pid}) is finishing in-flight work, then exiting`);const s=ke(Number.POSITIVE_INFINITY);s?.workers&&console.log(`[child] its ${s.workers} queue worker(s) stopped taking new jobs, still processing ${s.runningAtStop} job(s)`);try{fs.writeFileSync(sockRetiringPath($),String(process.pid),{flag:"w",mode:384})}catch{}await new Promise(u=>{setTimeout(u,Ue).unref?.()}),cleanupSock($),ye.close();const l=Date.now();for(;Date.now()-l<de;){ye.closeIdleConnections?.();let u=0;for(const y of W)u+=ee(y).unanswered;if(B.inflight+K(B)===0&&u===0&&T().count===0)break;await new Promise(y=>{setTimeout(y,250).unref?.()})}setTimeout(()=>process.exit(0),3e4).unref?.();try{await g?.close()}catch(u){console.error("[child] close error while retiring:",u)}process.exit(0)}h(U,"F"),a(U,"retire"),process.on("beforeExit",()=>{try{inspector?.close?.()}catch{}});const ae=a(async t=>{console.log(`[child ${t}] shutting down\u2026`);const s=g?.close().catch(u=>console.error("[child] close error:",u)),l=new Promise(u=>ye.close(()=>u()));cleanupSock($),setTimeout(()=>process.exit(0),700).unref(),await Promise.all([s,l]),process.exit(0)},"shutdown");["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(t=>{process.on(t,()=>{if(t==="SIGTERM"&&!b&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){U("this generation was handed over to a newer one");return}inspector?.close?.(),ae(t)})})})()}else{let c=h(function(_){K.push(_),K.length>z&&K.splice(0,K.length-z),Ee?.write((0,inspector_secret_1.redactInspectorUrls)(_.replace(W,"")));for(const b of ee)try{const v=b.plain?_.replace(W,""):_;b.res.write(b.redact?(0,inspector_secret_1.redactInspectorUrls)(v):v)}catch{ee.delete(b)}},"he");a(c,"emitLogLine");const f=12,g=process.env.LENSMCP_PROJECT||SERVICE_NAME||"service",E=(0,pod_memory_events_1.createPodMemoryPublisher)(process.env.LENSMCP_EVENT_FILE),L=a(_=>({project:g,service:SERVICE_NAME||g,pod:_.id,pid:_.proc.pid??0,podGeneration:POD_GENERATION}),"podIdentity"),Z=["\x1B[36m","\x1B[33m","\x1B[35m","\x1B[32m","\x1B[34m","\x1B[31m"],He="\x1B[0m",le=Math.max(1,Number(process.env.CHILD_COUNT||1)),B=LENSMCP_WS_KEY||path.basename(process.cwd()),de=a(_=>(0,process_name_1.namedExecPath)((0,process_name_1.processName)(B,SERVICE_NAME||"service",_)),"execPathFor");let T=(()=>{const _=Number(process.env.LENSMCP_DEBUG_PORT||"");return Number.isInteger(_)&&_>1024?_:0})();const ke=20;T&&(console.warn("[parent] DEBUGGER ENABLED \u2014 pods and workers open a Node inspector. Node's inspector has NO authentication: the ws:// URL's UUID is the whole credential, and any account on this host that obtains it can run code as you. Do not enable this on a shared machine. (issues/closed/codex-security-06)"),console.warn(usesFixedInspectorPorts()?`[parent] ${exports.DEBUG_FIXED_PORTS_ENV} is set \u2014 inspectors bind PREDICTABLE ports (${T}+slot, workers ${T}+${ke}+i). Any account on this host can connect to 127.0.0.1:${T} and read the session UUID from /json/list. Unset it to use ephemeral ports.`:`[parent] Inspectors bind an EPHEMERAL loopback port; attach via the printed "Debugger listening" URL, or \`lensmcp debug <service>\` (control socket). Set ${exports.DEBUG_FIXED_PORTS_ENV}=1 for fixed ports if your IDE needs a pre-configured attach port \u2014 any account on this host can then reach them.`));const pe=process.env.LENSMCP_LOG_FILE||"",Ee=pe?(()=>{try{fs.mkdirSync(path.dirname(pe),{recursive:!0});const _=fs.createWriteStream(pe,{flags:"a",mode:384});try{fs.chmodSync(pe,384)}catch{}return _}catch(_){return console.warn(`[parent] cannot open log file ${pe}:`,_.message),null}})():null,W=/\x1b\[[0-9;]*m/g,z=2e3,K=[],ee=new Set,te=(0,devserver_transport_1.gatewayManaged)();for(const _ of["log","info","warn","error"]){const b=console[_].bind(console),v=_==="log"||_==="info"?process.stdout:process.stderr;console[_]=(...j)=>{b(...j.map(I=>typeof I=="string"?(0,inspector_secret_1.forStream)(v,te,I):I)),c(j.map(I=>typeof I=="string"?I:util.inspect(I)).join(" ")+`
|
|
13
|
+
`)}}const De=a(_=>Z[(_-1)%Z.length],"colorFor"),R=a(_=>{if(le===1)return"";const b=`[child#${_}] `;return`${De(_)}${b}${He}`},"tagFor"),ue=a(_=>_.startsWith("{")&&_.endsWith("}"),"isJsonLog"),re=a((_,b,v)=>{const j=prettyFactory({sync:!0,colorize:!0,crlf:!0,messageKey:"message",errorLikeObjectKeys:["err","error"],errorProps:"type,message,stack",ignore:["logContext","context","hostname","req","res","err.driverError","module","cloudEnvironment","frontegg-application-id","frontegg-tenant-id","frontegg-trace-id","frontegg-vendor-id","host","service","version","err","error"].join(","),messageFormat:"{if module}[{module}] {end}{if context}[{context}] {end}{if logContext}[{logContext}] {end}{message}",customPrettifiers:{stack:a(q=>`
|
|
14
|
+
`+String(q),"stack")}}),I=a((q,k)=>{const F=q==="stdout"?process.stdout:process.stderr;try{v?.(k)}catch{}const H=ue(k)?j(k):`${k}
|
|
15
|
+
`;F.write((0,inspector_secret_1.forStream)(F,te,`${b}${H}`)),c(`${b}${H}`)},"write"),he=a((q,k)=>{if(!q)return;const F=readline.createInterface({input:q});F.on("line",H=>I(k,H)),F.on("close",()=>{})},"attach");he(_.stdout,"stdout"),he(_.stderr,"stderr")},"wireProcLogging"),Ue=a(_=>re(_.proc,R(_.id),b=>_.tail.push(b)),"wireChildLogging");(async()=>{ensureOwnerOnlyDir(SOCK_DIR);const _=httpProxy.createProxyServer({});let b=Number(process.env.PORT)||0;b||(b=9090);const v=[],j=new Set;function I(){let e=0;for(;j.has(e);)e++;return j.add(e),e}h(I,"$"),a(I,"allocDebugSlot");let he=1,q=0,k=!1,F=le,H=[],fe=0;const Pe=(0,swap_mode_1.swapMode)(),V=(()=>{const e=Number(process.env.LENSMCP_POD_BOOT_GRACE_MS);return Number.isFinite(e)&&e>=0?e:12e4})(),M=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"childExited"),xe=a(e=>e.booting&&!M(e)&&Date.now()-e.spawnedAt<V,"stillBooting");function Y(){return v.filter(e=>e.healthy&&typeof e.socketPath=="string")}h(Y,"K"),a(Y,"healthyChildren");let oe,Se=0,J;const _e={project:g,service:SERVICE_NAME||g,podGeneration:POD_GENERATION};function Ne(e){const r=(0,service_build_events_1.buildAccountOf)(e.building);r&&r.startedAt!==Se&&(Se=r.startedAt,E.publish((0,service_build_events_1.buildServiceBuildEvent)(_e,"started",r,{root:process.cwd()})));const n=(0,service_build_events_1.buildAccountOf)(e.failed);n&&(Se=0,E.publish((0,service_build_events_1.buildServiceBuildEvent)(_e,"failed",n,{root:process.cwd()})))}h(Ne,"We"),a(Ne,"recordBuildProgress");function ge(e){if(!e||typeof e!="object")return;const r=e;if(r.schemaVersion===1){Ne(r),"failed"in r&&delete r.failed,oe=r;for(const n of v)tell(n.proc,{type:"watcher",testimony:r})}}h(ge,"Pe"),a(ge,"recordWatcherTestimony");function ne(e,r=256*1024){return new Promise(n=>{let p="",i=!1;e.on("data",o=>{i||(p+=o,p.length>r&&(i=!0))}),e.on("end",()=>{if(i||!p)return n(void 0);try{n(JSON.parse(p))}catch{n(void 0)}}),e.on("error",()=>n(void 0))})}h(ne,"_e"),a(ne,"readJsonBody");function se(){const e=Y();if(e.length===0)return null;const r=q%e.length;return q=(q+1)%e.length,e[r]}h(se,"ge"),a(se,"pickChild");function ie(e,r){const n=`Child#${e.id}`,p=SERVICE_NAME||g,i=Date.now();if(r.type==="pod-memory"){const o=r.report;if(!o||typeof o.live!="number")return;e.memory.latest=o,e.memory.history.push(o),e.memory.history.length>12&&e.memory.history.splice(0,e.memory.history.length-12),console.log(`[parent] ${(0,pod_memory_1.describePodMemory)(n,o)}`),E.publish((0,pod_memory_events_1.buildPodMemoryEvent)(L(e),o))}else if(r.type==="pod-memory-retained"){const o=r.retained;if(!o||typeof o.gen!="number")return;e.memory.retained.set(o.gen,{...o,at:i}),console.warn(`[parent] ${(0,pod_memory_1.describeRetained)(n,p,o)}`),E.publish((0,pod_memory_events_1.buildPodRetainedEvent)(L(e),o,i))}else{const o=r.released;if(!o||typeof o.gen!="number")return;e.memory.retained.delete(o.gen),console.log(`[parent] ${(0,pod_memory_1.describeReleased)(n,p,o)}`),E.publish((0,pod_memory_events_1.buildPodReleasedEvent)(L(e),o,i))}}h(ie,"ye"),a(ie,"recordPodMemory");const ve=new Map;let ye=0;async function $(){const e=he++,r=childSockPath(e);cleanupSock(r);const n=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&n.unshift("--require","@swc-node/register"),n.push(...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const p=T?I():-1,i=(0,node_child_process_1.fork)(__filename,{env:{...process.env,APP_RUNNER:"1",CHILD_SOCK_PATH:r,...p>=0&&{CHILD_DEBUG_PORT:String(inspectorPortFor(T,p))}},stdio:["inherit","pipe","pipe","ipc"],execArgv:n,execPath:de("pod")}),o={id:e,proc:i,healthy:!1,lastError:null,debugSlot:p,tail:new exit_reason_1.TailBuffer,booting:!0,spawnedAt:Date.now(),bootReloadSeq:fe,memory:{history:[],retained:new Map}};return i.on("error",S=>console.error(`[parent] Child#${e}: ${S.message}`)),i.on("message",S=>{if(!S||typeof S!="object")return;const d=S;if(d.type==="ready")o.socketPath=String(d.socketPath),o.healthy=!0,o.booting=!1,console.log(`[parent] Child#${o.id} ready on socket ${o.socketPath}`),oe&&tell(i,{type:"watcher",testimony:oe}),fe!==o.bootReloadSeq&&(Pe==="process"?Q():tell(i,{type:"reload"}));else if(d.type==="recycle-request")w(o,String(d.reason||"it has outgrown its bound"));else if(d.type==="inspector-url")o.inspectorUrl=String(d.url),console.log(`[parent] Child#${o.id} Debugger listening on ${d.url}`);else if(d.type==="reloaded"){if(U.has(o)){console.log(`[parent] Child#${o.id} finished a swap it began before it was retired \u2014 not serving it: a fresh pod has taken over`);return}o.socketPath=String(d.socketPath),o.healthy=!0,console.log(`[parent] Child#${o.id} hot-swapped on socket ${o.socketPath}`);const m=d.swap;m&&typeof m.generation=="number"&&typeof m.swaps=="number"&&E.publish((0,service_build_events_1.buildServiceSwapEvent)(L(o),{generation:m.generation,swaps:m.swaps,swapMs:typeof m.swapMs=="number"?m.swapMs:0,draining:typeof m.draining=="number"?m.draining:0},J,{root:process.cwd()}))}else d.type==="reload-error"?(o.lastError=String(d.error||"unknown reload error"),console.error(`[parent] Child#${o.id} reload error: ${o.lastError}`),o.healthy=!1):d.type==="boot-error"?(o.lastError=String(d.error||"unknown boot error"),console.error(`[parent] Child#${o.id} boot error: ${o.lastError}`),o.healthy=!1,o.booting=!1):d.type==="pod-memory"||d.type==="pod-memory-retained"||d.type==="pod-memory-released"?ie(o,d):d.type==="heap-snapshot-result"&&ve.get(Number(d.id))?.(d)}),i.on("exit",(S,d)=>{o.booting=!1;for(const D of o.memory.retained.values()){const x={gen:D.gen,closedAt:D.closedAt,ageMs:Date.now()-D.closedAt,reason:"pod-exited"};console.log(`[parent] ${(0,pod_memory_1.describeReleased)(`Child#${o.id}`,SERVICE_NAME||g,x)}`),E.publish((0,pod_memory_events_1.buildPodReleasedEvent)(L(o),x,Date.now()))}o.memory.retained.clear(),o.memory.latest&&E.publish((0,pod_memory_events_1.buildPodExitedEvent)(L(o),{code:S,signal:d},Date.now())),o.debugSlot>=0&&(j.delete(o.debugSlot),o.debugSlot=-1);const m=d==="SIGINT"||d==="SIGTERM"||S===0;if(m?console.log(`[parent] Child#${o.id} exited cleanly (code=${S}, signal=${d??"none"})`):(console.error((0,exit_reason_1.crashReport)(`[parent] Child#${o.id} crashed (code=${S}, signal=${d??"none"}, pid=${o.proc.pid}, ${o.healthy?"was serving":"was not ready"})`,S,d,o.tail.snapshot())),o.lastError||(o.lastError=`Child exited abnormally (code=${S}, signal=${d??"none"})`)),o.healthy=!1,cleanupSock(o.socketPath,o.proc.pid),o.socketPath=void 0,!m&&!k){const D=v.indexOf(o);D>=0&&v.splice(D,1);const x=Date.now();if(H=H.filter(X=>x-X<3e4),H.length>=5){console.error(`[parent] crash-loop detected (${H.length} respawns/30s) \u2014 pausing auto-respawn; POST /webpack/reload to retry`);return}H.push(x),setTimeout(()=>{const X=v.filter(Ie=>Ie.proc.killed===!1).length;!k&&X<F&&(console.log(`[parent] respawning a crashed pod (${X}/${F} live)`),$().catch(()=>{}))},1500).unref?.()}}),v.push(o),Ue(o),o}h($,"M"),a($,"spawnChild");async function we(e){F=Math.max(F,e);const r=v.filter(p=>p.proc.killed===!1),n=e-r.length;for(let p=0;p<n;p++)await $()}h(we,"me"),a(we,"ensurePoolSize");async function Ae(){const e=v.filter(r=>(!r.healthy||typeof r.socketPath!="string")&&!xe(r));await Promise.all(e.map(async r=>{try{r.proc.kill("SIGTERM")}catch{}cleanupSock(r.socketPath,r.proc.pid),r.socketPath=void 0;const n=v.indexOf(r);n>=0&&v.splice(n,1),await $()}))}h(Ae,"Ue"),a(Ae,"respawnUnhealthy");const U=new Set;let ae=!1,t=0;const s=3e4,l=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return(Number.isFinite(e)&&e>=0?e:15*6e4)+6e4})();async function u(e){const r=Date.now(),n=await $(),p=Date.now()+V;for(;!n.healthy&&!n.lastError&&!M(n)&&Date.now()<p;)await new Promise(i=>{setTimeout(i,100).unref?.()});if(k)return{why:"shutting down"};if(!n.healthy){const i=v.indexOf(n);i>=0&&v.splice(i,1);try{n.proc.kill("SIGTERM")}catch{}return{why:`its successor Child#${n.id} did not come up (${n.lastError??"no answer in time"})`}}return y(e),{fresh:n,readyMs:Date.now()-r}}h(u,"_"),a(u,"replacePod");function y(e){const r=v.indexOf(e);if(r>=0&&v.splice(r,1),e.healthy=!1,M(e))return;U.add(e),postRetiringNotice(e.socketPath,e.proc.pid);const n=setTimeout(()=>{if(!M(e)){console.error(`[parent] Child#${e.id} was still running ${l}ms after it was retired \u2014 killing it`);try{e.proc.kill("SIGKILL")}catch{}}},l);if(n.unref?.(),e.proc.once("exit",()=>{clearTimeout(n),U.delete(e)}),!tell(e.proc,{type:"retire"}))try{e.proc.kill("SIGTERM")}catch{}}h(y,"g"),a(y,"retirePod");async function w(e,r){const n=a(i=>{console.log(`[parent] Child#${e.id} asked for a fresh pod (${r}) \u2014 not now: ${i}`),tell(e.proc,{type:"recycle-declined"})},"decline");if(k||!v.includes(e))return;if(ae)return n("another pod is already being replaced");const p=Date.now()-t;if(p<s)return n(`a pod was replaced ${Math.round(p/1e3)}s ago`);ae=!0,console.log(`[parent] Child#${e.id} asked for a fresh pod \u2014 ${r}. Starting one; Child#${e.id} keeps serving until it is ready.`);try{const i=await u(e);if("why"in i){i.why!=="shutting down"&&n(i.why);return}t=Date.now(),console.log(`[parent] Child#${i.fresh.id} took over from Child#${e.id} (pid ${e.proc.pid} \u2192 ${i.fresh.proc.pid})`)}catch(i){n(`could not start a successor: ${i?.message??String(i)}`)}finally{ae=!1}}h(w,"y"),a(w,"recyclePod");let O,A=!1;function Q(){if(O){A=!0;return}O=(async()=>{do{A=!1;const e=fe;for(;ae&&!k;)await new Promise(r=>{setTimeout(r,100).unref?.()});for(const r of Y().filter(n=>n.bootReloadSeq<e)){if(k)break;if(!(!v.includes(r)||!r.healthy)){ae=!0;try{N(),console.log(`[parent] A new build \u2014 starting a fresh pod for it; Child#${r.id} keeps serving the previous build until it is ready.`);const n=await u(r);if("why"in n){n.why!=="shutting down"&&console.error(`[parent] Child#${r.id} keeps serving the previous build \u2014 ${n.why}`);continue}console.log(`[parent] Child#${n.fresh.id} took over from Child#${r.id} with the new build (pid ${r.proc.pid} \u2192 ${n.fresh.proc.pid}, ready in ${(n.readyMs/1e3).toFixed(1)}s while Child#${r.id} served)`),E.publish((0,service_build_events_1.buildServiceSwapEvent)(L(n.fresh),{generation:1,swaps:0,swapMs:n.readyMs,draining:U.size,handoff:{fromPid:r.proc.pid??0}},J,{root:process.cwd()}))}catch(n){console.error(`[parent] Child#${r.id} keeps serving the previous build \u2014 could not start a successor: ${n?.message??String(n)}`)}finally{ae=!1}}}}while(A&&!k)})().finally(()=>{O=void 0})}h(Q,"q"),a(Q,"handoffForReload");function N(){const e=(0,swap_mode_1.maxRetiring)(),r=[...U].filter(n=>!M(n));for(;r.length>=e;){const n=r.shift();console.error(`[parent] Child#${n.id} (pid ${n.proc.pid}) was still finishing its work when a newer build replaced its successor too \u2014 stopping it (LENSMCP_SWAP_MAX_RETIRING=${e})`);try{n.proc.kill("SIGTERM")}catch{}}}h(N,"A"),a(N,"capRetiring");const C=[];function qe(e){const r=path.dirname(path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH)),n=path.join(r,`${e}.js`);if(!fs.existsSync(n))return console.error(`[parent] Worker "${e}" bundle not found: ${n}`),null;const p=(0,dev_devtool_1.sourceMapExecArgv)();__filename.endsWith(".ts")&&p.unshift("--require","@swc-node/register");try{p.push("--require",require.resolve("@lensmcp/node-instrumentation/register"))}catch{}const i=inspectorPortFor(T||9229,ke+Math.max(0,WORKER_NAMES.indexOf(e)));p.push(T?`--inspect=127.0.0.1:${i}`:`--inspect-port=127.0.0.1:${i}`,...(0,inspector_secret_1.inspectorPublishExecArgv)(usesFixedInspectorPorts()));const o=(0,node_child_process_1.fork)(n,{env:process.env,stdio:["inherit","pipe","pipe","ipc"],execArgv:p,execPath:de(`w-${e}`)}),S={name:e,proc:o,debugPort:i};return C.push(S),o.on("error",d=>console.error(`[parent] Worker "${e}": ${d.message}`)),re(o,`\x1B[35m[worker:${e}] \x1B[0m`,d=>{const m=/Debugger listening on (wss?:\/\/\S+)/.exec(d);m&&(S.inspectorUrl=m[1])}),o.on("exit",(d,m)=>{m==="SIGINT"||m==="SIGTERM"||d===0||console.error(`[parent] Worker "${e}" crashed (code=${d}, signal=${m??"none"})`);const D=C.indexOf(S);D>=0&&C.splice(D,1)}),console.log(`[parent] Worker "${e}" started (pid=${o.pid})`),S}h(qe,"Ye"),a(qe,"spawnWorkerProcess");const Ve=(()=>{const e=Number(process.env.LENSMCP_SWAP_DRAIN_MS);return Number.isFinite(e)&&e>=0?e:15*6e4})(),Je=(()=>{const e=Number(process.env.LENSMCP_SWAP_MAX_DRAINING);return Number.isFinite(e)&&e>=1?Math.floor(e):1})(),me=new Set,Be=a(e=>e.proc.exitCode!==null||e.proc.signalCode!==null,"workerExited");function Ge(e,r){if(me.delete(e),!Be(e)){console.error(`[parent] Worker "${e.name}" (pid=${e.proc.pid}) ${r} \u2014 killing it. A worker that returns from its SIGTERM handler but leaves a handle open (a Redis client handed to BullMQ is not closed by worker.close()) never exits on its own: close what it opened, or call process.exit().`);try{e.proc.kill("SIGKILL")}catch{}}}h(Ge,"He"),a(Ge,"killDrainingWorker");function je(e){try{e.proc.kill("SIGTERM")}catch{}if(Be(e))return;const r=[...me].filter(p=>p.name===e.name);for(;r.length>=Je;)Ge(r.shift(),`was still running when a later reload replaced its successor (cap ${Je})`);me.add(e);const n=setTimeout(()=>Ge(e,`was still running ${Ve}ms after SIGTERM`),Ve);n.unref?.(),e.proc.once("exit",()=>{clearTimeout(n),me.delete(e)})}h(je,"Je"),a(je,"retireWorker");function Xe(){for(const e of[...C])je(e);C.length=0;for(const e of WORKER_NAMES)qe(e)}h(Xe,"et"),a(Xe,"restartWorkers");const ot=debounce(async()=>{fe+=1;const e=Y();if(e.length>0)if(Pe==="process")Q();else{console.log(`[parent] Forwarding reload to ${e.length} child(ren)`);for(const r of e)tell(r.proc,{type:"reload"})}await Ae(),WORKER_NAMES.length>0&&Xe()},200),ze=a((e,r)=>{const n=(0,notice_generation_1.noticeGeneration)(e.headers);return n===void 0||n===POD_GENERATION?!1:(e.resume(),r.writeHead(409,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!1,generation:POD_GENERATION,sentFor:n})),e.url==="/webpack/reload"&&console.log(`[parent] a build of generation g${n} reached generation g${POD_GENERATION} \u2014 not reloaded: it is not this generation's build`),!0)},"refusedForAnotherGeneration"),Re=a(async(e,r)=>{if(e.method==="POST"&&e.url==="/webpack/reload"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);if(ze(e,r))return;r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0})),ne(e).then(i=>{const o=i;J=(0,service_build_events_1.buildAccountOf)(o?.build),Se=0,ge(o?.watcher)}),ot();return}if(e.method==="POST"&&e.url==="/webpack/watching"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);if(ze(e,r))return;const i=await ne(e);ge(i),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,relayed:oe!==void 0}));return}if(e.method==="POST"&&e.url==="/webpack/scale"){if(!(0,devserver_transport_1.mayDriveThePool)(e))return refusePoolCommand(e,r);let i="";e.on("data",o=>{i+=o}),e.on("end",async()=>{let o=0;try{o=Number(JSON.parse(i||"{}").pods)||0}catch{}o=Math.max(1,Math.min(o,16)),await we(o),r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,pods:o}))});return}if(e.method==="GET"&&e.url?.startsWith("/webpack/logs")){const i=new URL(e.url,"http://localhost").searchParams,o=Math.min(Math.max(Number(i.get("tail")??"100")||0,0),z),S=i.get("follow")!=="0",d=i.get("plain")==="1",m=!(0,devserver_transport_1.isOwnerOnlyTransport)(e);r.writeHead(200,{"content-type":"text/plain; charset=utf-8","cache-control":"no-cache"});const D=o===0?"":K.slice(-o).join(""),x=d?D.replace(W,""):D;if(r.write(m?(0,inspector_secret_1.redactInspectorUrls)(x):x),!S){r.end();return}const X={res:r,plain:d,redact:m};ee.add(X),e.on("close",()=>ee.delete(X));return}if(e.method==="POST"&&e.url?.startsWith("/webpack/debug")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const i=Number(new URL(e.url,"http://localhost").searchParams.get("port")||"");Number.isInteger(i)&&i>1024&&(T=i),T||(T=9229);const o=v.filter(d=>d.proc.exitCode===null&&d.proc.signalCode===null);for(const d of o)d.debugSlot<0&&(d.debugSlot=I()),tell(d.proc,{type:"debug-open",port:inspectorPortFor(T,d.debugSlot)});for(const d of C)try{d.proc.kill("SIGUSR1")}catch{}const S=Date.now()+2e3;for(;Date.now()<S&&o.some(d=>!d.inspectorUrl);)await new Promise(d=>setTimeout(d,50));r.writeHead(200,{"content-type":"application/json"}),r.end(JSON.stringify({ok:!0,basePort:T,fixedPorts:usesFixedInspectorPorts(),pods:o.map(d=>({id:d.id,inspectorUrl:d.inspectorUrl??null})),workers:C.map(d=>({name:d.name,port:d.debugPort||(0,inspector_secret_1.inspectorPortOf)(d.inspectorUrl),inspectorUrl:d.inspectorUrl??null}))},null,2)+`
|
|
16
|
+
`);return}if(e.method==="GET"&&e.url==="/webpack/memory"){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const i=path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),o=[...v,...U].map(S=>({id:S.id,pid:S.proc.pid??0,podGeneration:POD_GENERATION,healthy:S.healthy,retiring:U.has(S),latest:S.memory.latest??null,history:S.memory.history,retained:[...S.memory.retained.values()]}));r.writeHead(200,{"content-type":"application/json","cache-control":"no-store"}),r.end(JSON.stringify({ok:!0,service:SERVICE_NAME,project:g,bundle:{path:i},pods:o},null,2)+`
|
|
17
|
+
`);return}if(e.method==="POST"&&e.url?.startsWith("/webpack/heap")){if(!(0,devserver_transport_1.isOwnerOnlyTransport)(e)){r.writeHead(404,{"content-type":"application/json"}),r.end(JSON.stringify({error:"not found"}));return}const i=a((G,Ce)=>{r.writeHead(G,{"content-type":"application/json"}),r.end(JSON.stringify(Ce,null,2)+`
|
|
18
|
+
`)},"reply"),o=new URL(e.url,"http://localhost").searchParams,S=Number(o.get("pod")||""),d=o.has("maxMb")?Number(o.get("maxMb")):void 0;if(d!==void 0&&!(Number.isFinite(d)&&d>0)){i(400,{ok:!1,error:"maxMb takes a positive number of MB"});return}const m=Number.isInteger(S)&&S>0?v.find(G=>G.id===S):Y()[0];if(!m||M(m)){i(404,{ok:!1,error:S?`no pod #${S} \u2014 running: ${v.map(G=>`#${G.id}`).join(", ")||"none"}`:"no pod is serving"});return}if(ve.size>0){i(409,{ok:!1,refused:"a heap snapshot is already being written for this service"});return}const D=(0,heap_snapshot_1.heapSnapshotPolicy)(),x=(0,heap_snapshot_1.heapSnapshotDir)();let X;try{(0,heap_snapshot_1.ensureHeapDir)(x);const G=(0,heap_snapshot_1.pruneHeapSnapshots)(x,D);G.length&&console.log(`[parent] removed ${G.length} older heap snapshot(s) from ${x}`),X=path.join(x,(0,heap_snapshot_1.heapSnapshotFileName)(SERVICE_NAME||g,m.id,m.proc.pid??0,m.memory.latest?.gen??0,Date.now()))}catch(G){i(500,{ok:!1,error:`cannot prepare ${x}: ${G.message}`});return}const Ie=++ye,Fe=(0,heap_snapshot_1.availableSystemMemory)();let Me;const P=await new Promise(G=>{const Ce=setTimeout(()=>G(void 0),6e5);Ce.unref?.(),ve.set(Ie,ct=>{clearTimeout(Ce),G(ct)}),Me=a(()=>{clearTimeout(Ce),G(void 0)},"onExit"),m.proc.once("exit",Me);const it={type:"heap-snapshot",id:Ie,file:X,...Fe?{available:Fe}:{},...d!==void 0?{maxHeapBytes:Math.floor(d*1048576)}:{}};tell(m.proc,it)||Me()}).finally(()=>{ve.delete(Ie),Me&&m.proc.off("exit",Me)});if(!P){i(504,{ok:!1,pod:m.id,error:"the pod did not finish the snapshot (it exited, or took longer than 10 minutes)"});return}P.ok?console.log(`[parent] Child#${m.id} wrote a ${Math.round((P.bytes??0)/1048576)} MB heap snapshot in ${P.ms} ms \u2192 ${P.file}`):console.warn(`[parent] Child#${m.id} heap snapshot not taken: ${P.refused??P.error}`),i(P.ok?200:P.refused?409:500,{ok:P.ok,pod:m.id,pid:m.proc.pid??0,service:SERVICE_NAME,bundle:path.isAbsolute(BUNDLE_PATH)?BUNDLE_PATH:path.resolve(BUNDLE_PATH),...Fe?{available:Fe}:{},...P.file?{file:P.file,bytes:P.bytes,ms:P.ms}:{},...P.live!==void 0?{live:P.live}:{},...P.gen!==void 0?{gen:P.gen,closedAlive:P.closedAlive??[]}:{},...P.refused?{refused:P.refused}:{},...P.error?{error:P.error}:{}});return}const n=matchGatewayRoute(e);if(n?.prependPrefix&&!e.url?.startsWith(n.prependPrefix)&&(e.url=n.prependPrefix+(e.url??"/")),n?.target){applyGatewayMiddleware(e),_.web(e,r,{target:n.target,autoRewrite:typeof n.target=="string",changeOrigin:!0},i=>{console.error(`[parent] proxy error to ${n.target}:`,i),r.statusCode=502,r.setHeader("content-type","text/plain; charset=utf-8"),r.end(`Upstream error from ${n.target}.`)});return}if(SERVICE_PREFIX){if(!e.url?.startsWith(SERVICE_PREFIX)){r.writeHead(404,{"content-type":"text/plain; charset=utf-8"}),r.end(`Not found. This service is mounted at ${SERVICE_PREFIX}/`);return}e.url=e.url.slice(SERVICE_PREFIX.length)||"/"}applyGatewayMiddleware(e);const p=se();if(!p){r.statusCode=503,r.setHeader("content-type","text/plain; charset=utf-8");const i=v.map(o=>`#${o.id}: ${o.lastError??"no error recorded"}`).join(`
|
|
19
19
|
`);r.end(`No healthy children available. POST /webpack/reload to recover.
|
|
20
20
|
|
|
21
21
|
Last errors:
|
|
22
|
-
${
|
|
23
|
-
${
|
|
22
|
+
${i}`);return}_.web(e,r,{target:{socketPath:p.socketPath},headers:{"x-proxy-child-id":String(p.id)}},i=>{console.error(`[parent] proxy error to Child#${p.id}:`,i),p.healthy=!1,p.lastError=String(i?.stack||i),r.statusCode=502,r.setHeader("content-type","text/plain; charset=utf-8"),r.end(`Upstream error from Child#${p.id}. Try /webpack/reload.
|
|
23
|
+
${p.lastError}`)})},"requestHandler");let ce,Te,Ye;const $e=[];if(GATEWAY_HTTPS){const e=require("./basic-ssl"),r=GATEWAY_ROUTES.map(p=>p.host).filter(p=>!!p),n=(0,checkout_cache_1.checkoutCacheDir)(process.cwd(),"davnx-webpack");Te=e.getCertificateSync(n,SERVICE_NAME||"lensmcp.dev",r),Ye=e.caCertPath(),ce=require("node:https").createServer({key:Te,cert:Te},Re)}else ce=http.createServer(Re);const Qe=a((e,r,n)=>{const p=matchGatewayRoute(e);if(p?.prependPrefix&&!e.url?.startsWith(p.prependPrefix)&&(e.url=p.prependPrefix+(e.url??"/")),p?.target){_.ws(e,r,n,{target:p.target,changeOrigin:!0},()=>r.destroy());return}const i=se();if(!i){r.destroy();return}_.ws(e,r,n,{target:{socketPath:i.socketPath}},()=>r.destroy())},"upgradeHandler");ce.on("upgrade",Qe);const Ze=2e3;let et=!1;ce.on("error",e=>{if(e.code==="EADDRINUSE"){et||(et=!0,console.warn(`[parent] public port ${b} already in use \u2014 continuing on the unix-socket pool only (gateway routing is unaffected) and retrying the bind every ${Ze/1e3}s. Give this service a distinct cluster.port if this is not a rolling handoff.`)),setTimeout(()=>{!k&&!ce.listening&&ce.listen(b)},Ze).unref?.();return}console.error("[parent] public server error:",e)}),await we(le);for(const e of WORKER_NAMES)qe(e);SOCKET_ONLY?console.log(`[parent] socket-only: no TCP listener (port ${b} is not bound \u2014 the gateway reaches this service through its socket pool)`):ce.listen(b,()=>{const e=GATEWAY_HTTPS?"https":"http";console.log(`[parent] Listening on ${e}://localhost:${b}`),SERVICE_PREFIX&&console.log(`[parent] Service prefix: "${SERVICE_PREFIX}" (enforced \u2014 requests without it will get 404)`),gatewayMiddleware&&console.log(`[parent] Gateway middleware: ACTIVE (${GATEWAY_MIDDLEWARE_PATH})`),GATEWAY_ROUTES.length>0&&console.log(`[parent] Gateway routes: ${GATEWAY_ROUTES.map(r=>`${r.host??"*"}${r.prefix??"/"}\u2192${r.target??"children"}`).join(" ")}`),Ye&&(console.log("[parent] HTTPS dev CA (trust ONCE, then rotations/new hostnames stay green):"),console.log(`[parent] sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${Ye}`)),console.log(`POST ${e}://localhost:${b}/webpack/reload to trigger rolling swap/respawn`)});const Le=Number(process.env.INTERNAL_PORT||0);if(Le>0&&!SOCKET_ONLY){const e=a((n,p)=>{const i=se();if(!i){p.statusCode=503,p.setHeader("content-type","text/plain; charset=utf-8"),p.end("No healthy children available.");return}_.web(n,p,{target:{socketPath:i.socketPath}},o=>{i.healthy=!1,i.lastError=String(o?.stack||o),p.statusCode=502,p.end(`Upstream error from Child#${i.id}.`)})},"internalHandler"),r=http.createServer(e);r.on("upgrade",(n,p,i)=>{const o=se();if(!o){p.destroy();return}_.ws(n,p,i,{target:{socketPath:o.socketPath}},()=>p.destroy())}),r.on("error",n=>{console.warn(`[parent] internal port ${Le} unavailable (${n.code}).`)}),r.listen(Le,()=>{console.log(`[parent] Internal (service-to-service, no gateway middleware) on http://localhost:${Le}`)}),$e.push(r)}const nt=SOCKET_ONLY?[]:(()=>{try{return JSON.parse(process.env.GATEWAY_EXTRA_PORTS||"[]")}catch{return[]}})();for(const e of nt){if(!Number.isInteger(e)||e===Number(b))continue;const r=GATEWAY_HTTPS?require("node:https").createServer({key:Te,cert:Te},Re):http.createServer(Re);r.on("upgrade",Qe),r.on("error",n=>{console.warn(`[parent] extra port ${e} unavailable (${n.code}) \u2014 main port still up.`)}),r.listen(e,()=>{console.log(`[parent] Also listening on ${GATEWAY_HTTPS?"https":"http"}://localhost:${e}`)}),$e.push(r)}const be=path.join(SOCK_DIR,"parent.sock");claimSock(be),(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);const Oe=http.createServer(Re);Oe.on("error",e=>{console.warn(`[parent] control socket unavailable (${e.code}) \u2014 lensmcp logs/debug attach disabled.`)}),Oe.listen(be),$e.push(Oe);function tt(){k=!0;const e=[...v,...U];console.log(`[SIGTERM] [parent] generation g${POD_GENERATION} was handed over to a newer one \u2014 ${e.length} pod(s) finish their in-flight work, then it exits`);const r=sockOwnedByAnother(be);cleanupSock(be);for(const i of $e)if(!(i===Oe&&r))try{i.close()}catch{}try{ce.close()}catch{}for(const i of[...C])je(i);C.length=0;for(const i of v)if(!tell(i.proc,{type:"retire"}))try{i.proc.kill("SIGTERM")}catch{}const n=setTimeout(()=>{for(const i of e)if(!M(i)){console.error(`[parent] Child#${i.id} was still running ${l}ms after its generation was retired \u2014 killing it`);try{i.proc.kill("SIGKILL")}catch{}}},l);n.unref?.();const p=a(()=>{if(!(e.some(i=>!M(i))||[...me].some(i=>!Be(i)))){clearTimeout(n);for(const i of e)i.socketPath&&cleanupSock(i.socketPath,i.proc.pid);(0,handoff_retire_1.clearHandoffRetire)(SOCK_DIR,POD_GENERATION);try{fs.rmdirSync(SOCK_DIR)}catch{}console.log(`[parent] generation g${POD_GENERATION} retired`),process.exit(0)}},"finish");for(const i of e)i.proc.once("exit",p);for(const i of me)i.proc.once("exit",p);p()}h(tt,"ot"),a(tt,"retireGeneration");const rt=a(async e=>{k=!0,console.log(`[${e}] [parent] shutting down\u2026`);const r=sockOwnedByAnother(be);cleanupSock(be);for(const n of $e)if(!(n===Oe&&r))try{n.close()}catch{}for(const n of C)try{n.proc.kill("SIGTERM")}catch{}for(const n of[...me])Ge(n,"was still draining when its devserver shut down");for(const n of U)try{n.proc.kill("SIGTERM")}catch{}for(const n of v){try{n.proc.kill("SIGTERM")}catch{}n.socketPath&&cleanupSock(n.socketPath,n.proc.pid)}try{fs.rmdirSync(SOCK_DIR)}catch{}setTimeout(()=>process.exit(0),700).unref()},"shutdown");let Ke=0;const st=2e3;["SIGINT","SIGTERM","SIGHUP","SIGQUIT","SIGUSR2"].forEach(e=>process.on(e,()=>{if(e==="SIGTERM"){if(!k&&(0,handoff_retire_1.handoffRetireRequested)(SOCK_DIR,POD_GENERATION)){Ke=Date.now(),tt();return}if(Ke>0&&Date.now()-Ke<st)return}rt(e)}));const We=Number(process.env.LENSMCP_GATEWAY_PID??0);if(Number.isInteger(We)&&We>0){const e=setInterval(()=>{try{process.kill(We,0)}catch(r){if(r.code==="EPERM")return;clearInterval(e),console.error(`[parent] gateway (pid ${We}) is gone \u2014 shutting the pod down to avoid an orphan tree.`),rt("gateway-gone")}},5e3);e.unref()}})()}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/cluster",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.2",
|
|
4
4
|
"description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@lensmcp/core": "1.25.
|
|
61
|
-
"@lensmcp/node-instrumentation": "1.25.
|
|
62
|
-
"@lensmcp/nx-plugin": "1.25.
|
|
60
|
+
"@lensmcp/core": "1.25.2",
|
|
61
|
+
"@lensmcp/node-instrumentation": "1.25.2",
|
|
62
|
+
"@lensmcp/nx-plugin": "1.25.2",
|
|
63
63
|
"fastify": "^5.8.5",
|
|
64
64
|
"@fastify/reply-from": "^12.6.2",
|
|
65
65
|
"@typescript/native-preview": "^7.0.0-dev.20260707.2",
|
|
@@ -31,6 +31,13 @@ export interface SwapReport {
|
|
|
31
31
|
swapMs: number;
|
|
32
32
|
/** Replaced apps still finishing their work. */
|
|
33
33
|
draining: number;
|
|
34
|
+
/**
|
|
35
|
+
* Taken by a fresh pod (LENSMCP_SWAP_MODE=process, swap-mode.ts): `swapMs` is then how long the successor took to be
|
|
36
|
+
* ready — while the previous pod, `fromPid`, went on serving — not a time the service stood still.
|
|
37
|
+
*/
|
|
38
|
+
handoff?: {
|
|
39
|
+
fromPid: number;
|
|
40
|
+
};
|
|
34
41
|
}
|
|
35
42
|
/** Paths as a reader names them: relative to the workspace, forward slashes. One outside it stays as it is. */
|
|
36
43
|
export declare function workspaceRelative(files: readonly string[], root: string | undefined): string[];
|
package/service-build-events.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var l=Object.defineProperty;var c=(i,e)=>l(i,"name",{value:e,configurable:!0});var f=Object.defineProperty,o=c((i,e)=>f(i,"name",{value:e,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.workspaceRelative=workspaceRelative,exports.buildAccountOf=buildAccountOf,exports.buildServiceBuildEvent=buildServiceBuildEvent,exports.buildServiceSwapEvent=buildServiceSwapEvent;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),MAX_FILES=50;let idCounter=0;function makeId(){return idCounter=(idCounter+1)%65535,`${Date.now().toString(36)}${idCounter.toString(36)}${Math.random().toString(36).slice(2,8)}`}c(makeId,"makeId"),o(makeId,"makeId");function workspaceRelative(i,e){return i.slice(0,MAX_FILES).map(t=>{if(!e||!path.isAbsolute(t))return t;const d=path.relative(e,t);return d&&!d.startsWith("..")&&!path.isAbsolute(d)?d.split(path.sep).join("/"):t})}c(workspaceRelative,"workspaceRelative"),o(workspaceRelative,"workspaceRelative");function buildAccountOf(i){if(!i||typeof i!="object")return;const e=i;if(typeof e.startedAt!="number"||!Number.isFinite(e.startedAt)||e.startedAt<=0)return;const t=Array.isArray(e.changedFiles)?e.changedFiles.filter(d=>typeof d=="string"&&d.length>0&&d.length<1024).slice(0,MAX_FILES):[];return{startedAt:e.startedAt,...typeof e.endedAt=="number"&&Number.isFinite(e.endedAt)?{endedAt:e.endedAt}:{},changedFiles:t,...typeof e.moreChanged=="number"&&e.moreChanged>0?{moreChanged:Math.floor(e.moreChanged)}:{}}}c(buildAccountOf,"buildAccountOf"),o(buildAccountOf,"buildAccountOf");function envelope(i,e,t,d,s,r,a){const n="pending";return{id:a(),sessionId:n,timestamp:e,source:"gateway",category:"cluster",severity:t,context:{sessionId:n,projectName:i.project},fingerprint:`cluster:${i.project}:${d}`,title:s,raw:{kind:d,...r,project:i.project,service:i.service,podGeneration:i.podGeneration}}}c(envelope,"envelope"),o(envelope,"envelope");const filesText=o((i,e)=>{const t=i+e;return t===0?"no source change":`${t} file${t===1?"":"s"}`},"filesText"),seconds=o(i=>i<1e3?`${i}ms`:`${(i/1e3).toFixed(1)}s`,"seconds");function buildServiceBuildEvent(i,e,t,d={}){const s=workspaceRelative(t.changedFiles,d.root),r=t.moreChanged??0,a=d.at??(e==="failed"?t.endedAt??Date.now():t.startedAt),n=t.endedAt!==void 0?Math.max(0,t.endedAt-t.startedAt):void 0,p=e==="started"?`build started: ${i.project} \u2014 ${filesText(s.length,r)}`:`build failed: ${i.project} \u2014 ${filesText(s.length,r)}${n!==void 0?` \xB7 ${seconds(n)}`:""} \xB7 nothing was swapped, the pod serves the last good build`;return envelope(i,a,e==="failed"?"warning":"info","service-build",p,{phase:e,startedAt:t.startedAt,...t.endedAt!==void 0?{endedAt:t.endedAt,compileMs:n}:{},changedFiles:s,...r>0?{moreChanged:r}:{}},d.id??makeId)}c(buildServiceBuildEvent,"buildServiceBuildEvent"),o(buildServiceBuildEvent,"buildServiceBuildEvent");function buildServiceSwapEvent(i,e,t,d={}){const s=d.at??Date.now(),r=workspaceRelative(t?.changedFiles??[],d.root),a=t?.moreChanged??0,n=t?.endedAt!==void 0?Math.max(0,t.endedAt-t.startedAt):void 0,p=e.handoff?`swap: ${i.project} \u2014 a fresh pod on the new build`+(t?` \xB7 ${filesText(r.length,a)}`:"")+(n!==void 0?` \xB7 compiled in ${seconds(n)}`:"")+` \xB7 ready in ${seconds(e.swapMs)} while the previous pod served`:`swap: ${i.project} \u2014 generation ${e.generation} (${e.swaps} swap${e.swaps===1?"":"s"})`+(t?` \xB7 ${filesText(r.length,a)}`:"")+(n!==void 0?` \xB7 compiled in ${seconds(n)}`:"")+` \xB7 swapped in ${seconds(e.swapMs)}`;return envelope(i,s,"info","service-swap",p,{pod:i.pod,pid:i.pid,generation:e.generation,swaps:e.swaps,swappedAt:s,swapMs:e.swapMs,draining:e.draining,...e.handoff?{handoff:{fromPid:e.handoff.fromPid}}:{},...t?{compile:{startedAt:t.startedAt,...t.endedAt!==void 0?{endedAt:t.endedAt,ms:n}:{}},changedFiles:r,...a>0?{moreChanged:a}:{}}:{}},d.id??makeId)}c(buildServiceSwapEvent,"buildServiceSwapEvent"),o(buildServiceSwapEvent,"buildServiceSwapEvent");
|
package/swap-mode.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a pod takes a new build — `LENSMCP_SWAP_MODE`.
|
|
3
|
+
*
|
|
4
|
+
* process (the default) the parent starts a FRESH pod on the new bundle; the serving pod keeps serving, at full
|
|
5
|
+
* speed, until its successor is ready, then drains and exits — the make-before-break handoff `recyclePod`
|
|
6
|
+
* already did for a pod that outgrew its memory bound.
|
|
7
|
+
* in-process the serving pod re-evaluates the bundle and builds the new app itself (`swapNow`). Nothing new to start,
|
|
8
|
+
* so the new code serves a little sooner — but the pod stands still while it builds.
|
|
9
|
+
*
|
|
10
|
+
* Why the default changed (2026-10-19): FoodGuard's engine bundle is 35 MB. Evaluating it and building the app inside
|
|
11
|
+
* the serving pod held its event loop 2–17 s per swap, and everything waiting in that process timed out on wake — pg's
|
|
12
|
+
* pool waits («timeout exceeded when trying to connect», read as "Postgres ran out of connections") in 42 of the 53
|
|
13
|
+
* minutes they happened. A fresh process builds beside the serving one and freezes nothing that serves; it also
|
|
14
|
+
* carries no earlier generation, so the memory a swapped-out app leaves behind (swap-recycle.ts) never accumulates.
|
|
15
|
+
*/
|
|
16
|
+
export type SwapMode = 'process' | 'in-process';
|
|
17
|
+
export declare function swapMode(env?: Record<string, string | undefined>): SwapMode;
|
|
18
|
+
/**
|
|
19
|
+
* Pods a reload-handoff may leave finishing their work at once (`LENSMCP_SWAP_MAX_RETIRING`, default 2). Edits come
|
|
20
|
+
* faster than a long job ends; past the cap the oldest retiring pod is stopped, as the in-process swap closes the
|
|
21
|
+
* oldest draining app at its own cap.
|
|
22
|
+
*/
|
|
23
|
+
export declare function maxRetiring(env?: Record<string, string | undefined>): number;
|
package/swap-mode.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var n=Object.defineProperty;var t=(r,e)=>n(r,"name",{value:e,configurable:!0});var o=Object.defineProperty,s=t((r,e)=>o(r,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.swapMode=swapMode,exports.maxRetiring=maxRetiring;function swapMode(r=process.env){const e=(r.LENSMCP_SWAP_MODE??"").trim().toLowerCase();return e==="in-process"||e==="inprocess"||e==="hot"?"in-process":"process"}t(swapMode,"swapMode"),s(swapMode,"swapMode");function maxRetiring(r=process.env){const e=Number(r.LENSMCP_SWAP_MAX_RETIRING);return Number.isFinite(e)&&e>=1?Math.floor(e):2}t(maxRetiring,"maxRetiring"),s(maxRetiring,"maxRetiring");
|