@lensmcp/cluster 1.17.2 → 1.17.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"create-webpack-dev.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/create-webpack-dev.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAQtF,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,qBAAqB,CAAC,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA2CD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgKtF"}
1
+ {"version":3,"file":"create-webpack-dev.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/create-webpack-dev.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAatF,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,qBAAqB,CAAC,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA2CD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAyKtF"}
@@ -49,12 +49,22 @@ function createDevWebpackConfig(options) {
49
49
  // Type-checking mode (LENSMCP_TYPECHECK): default = the resident fork-ts-checker pair (async,
50
50
  // feeds the lens `error TS` service-error signal); 'off' = none (fastest, quietest — the signal
51
51
  // is traded away); 'tsgo' = zero-resident cold re-checks via @typescript/native-preview (same
52
- // signal, no idle memory — see tsgo-check-plugin.ts). tsgo falls back to fork-ts-checker when
53
- // the binary isn't installed in the workspace, so the flag can ship ahead of the devDep.
52
+ // signal, no idle memory — see tsgo-check-plugin.ts).
53
+ //
54
+ // tsgo still falls back to fork-ts-checker when no binary resolves — degrading gracefully beats
55
+ // failing a dev server — but that fallback is now an ANOMALY, not the expected "the flag shipped
56
+ // ahead of the devDep" state: @lensmcp/cluster DEPENDS on the compiler. So it also reports itself
57
+ // onto the lens bus. fork-ts-checker publishes to the CONSOLE only, so without this the
58
+ // `typecheck://` resources would sit at `producer: 'none'` with nothing naming the reason —
59
+ // the precise "looks healthy because nobody is watching" state this producer exists to end.
54
60
  const typecheckMode = process.env.LENSMCP_TYPECHECK;
55
61
  const tsgoBin = typecheckMode === 'tsgo' ? (0, tsgo_check_plugin_1.resolveTsgoBin)(workspaceRoot, process.env.LENSMCP_TSGO_BIN) : null;
56
62
  if (typecheckMode === 'tsgo' && !tsgoBin) {
57
- console.warn(`[serve] ${options.appName}: LENSMCP_TYPECHECK=tsgo but no tsgo binary (yarn add -D @typescript/native-preview) falling back to fork-ts-checker`);
63
+ // No trailing period: `summariseProducers` renders this INSIDE a sentence it terminates itself.
64
+ const detail = `${tsgo_check_plugin_1.TSGO_MISSING_CAUSE}. Fell back to fork-ts-checker, which reports to the console only — ` +
65
+ `so typecheck:// has no producer for this project until it is fixed`;
66
+ console.warn(`[serve] ${options.appName}: LENSMCP_TYPECHECK=tsgo but ${detail}.`);
67
+ (0, tsgo_check_plugin_1.reportTsgoUnavailable)({ appName: options.appName, tsConfig, workspaceRoot, detail });
58
68
  }
59
69
  const useTsgo = !!tsgoBin;
60
70
  // Build combined allowlist: orgScopes + bundlePackages + user-provided
@@ -13,7 +13,7 @@
13
13
  export { hostMatches } from './manifest';
14
14
  export { startGateway } from './runtime/server';
15
15
  export { verifyDevJwt, stampIdentity } from './runtime/dev-auth';
16
- export { baseDomainOf, readLensScope } from './runtime/scope';
16
+ export { baseDomainOf, deriveMcpHttpPort, readLensScope, type LensScope } from './runtime/scope';
17
17
  export { pickSock, discoverRoutes } from './runtime/discovery';
18
18
  export type { GatewayContext, ClusterProjectDecl, SockPool, Route, LensFrontend, ServiceCtl, GatewayRuntimeOptions, GatewayHandle, GatewayHooks, GatewayPhase, HookHandler, HookContext, } from './runtime/types';
19
19
  //# sourceMappingURL=gateway.lib.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.lib.d.ts","sourceRoot":"","sources":["../../../../../libs/cluster/src/executors/gateway/gateway.lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE/D,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,QAAQ,EACR,KAAK,EACL,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,aAAa,EAEb,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,GACZ,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"gateway.lib.d.ts","sourceRoot":"","sources":["../../../../../libs/cluster/src/executors/gateway/gateway.lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE/D,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,QAAQ,EACR,KAAK,EACL,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,aAAa,EAEb,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,GACZ,MAAM,iBAAiB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.discoverRoutes = exports.pickSock = exports.readLensScope = exports.baseDomainOf = exports.stampIdentity = exports.verifyDevJwt = exports.startGateway = exports.hostMatches = void 0;
3
+ exports.discoverRoutes = exports.pickSock = exports.readLensScope = exports.deriveMcpHttpPort = exports.baseDomainOf = exports.stampIdentity = exports.verifyDevJwt = exports.startGateway = exports.hostMatches = void 0;
4
4
  /**
5
5
  * Public facade for the dev gateway runtime.
6
6
  *
@@ -22,6 +22,7 @@ Object.defineProperty(exports, "verifyDevJwt", { enumerable: true, get: function
22
22
  Object.defineProperty(exports, "stampIdentity", { enumerable: true, get: function () { return dev_auth_1.stampIdentity; } });
23
23
  var scope_1 = require("./runtime/scope");
24
24
  Object.defineProperty(exports, "baseDomainOf", { enumerable: true, get: function () { return scope_1.baseDomainOf; } });
25
+ Object.defineProperty(exports, "deriveMcpHttpPort", { enumerable: true, get: function () { return scope_1.deriveMcpHttpPort; } });
25
26
  Object.defineProperty(exports, "readLensScope", { enumerable: true, get: function () { return scope_1.readLensScope; } });
26
27
  var discovery_1 = require("./runtime/discovery");
27
28
  Object.defineProperty(exports, "pickSock", { enumerable: true, get: function () { return discovery_1.pickSock; } });
@@ -1 +1 @@
1
- {"version":3,"file":"lens-children.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lens-children.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,kCAAkC,CAAC;AAI1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAuB,MAAM,SAAS,CAAC;AAiB1F,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA0B7E,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAalE;AAyBD;;;;;;mDAMmD;AACnD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAG1F;AAMD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAClF,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;iFAGiF;AACjF,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAQvE;AAED,MAAM,WAAW,YAAY;IAC3B,iBAAiB,EAAE,UAAU,CAAC;IAC9B;;gFAE4E;IAC5E,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC;;6GAEyG;IACzG,sBAAsB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;IACvF;;yGAEqG;IACrG,iBAAiB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IACpE;gHAC4G;IAC5G,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,qBAAqB,EAE9B,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GACzD,YAAY,CAmbd"}
1
+ {"version":3,"file":"lens-children.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lens-children.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,kCAAkC,CAAC;AAI1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAuB,MAAM,SAAS,CAAC;AAiB1F,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AA0B7E,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAalE;AAyBD;;;;;;mDAMmD;AACnD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAG1F;AAMD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAClF,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;iFAGiF;AACjF,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAQvE;AAED,MAAM,WAAW,YAAY;IAC3B,iBAAiB,EAAE,UAAU,CAAC;IAC9B;;gFAE4E;IAC5E,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC;;6GAEyG;IACzG,sBAAsB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;IACvF;;yGAEqG;IACrG,iBAAiB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IACpE;gHAC4G;IAC5G,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,qBAAqB,EAE9B,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GACzD,YAAY,CA4bd"}
@@ -399,11 +399,20 @@ deps) {
399
399
  console.warn("[gateway] MCP server bundle not found — skipping (lens apps will still publish to the bus; an external `lensmcp mcp` can read it). Install: `npm i -D lensmcp`.");
400
400
  }
401
401
  else {
402
- console.log(`[gateway] starting lens MCP server (${mcpBundle})`);
402
+ // The workspace's OWN port, not a shared default. This used to be a
403
+ // hardcoded 3000 for EVERY workspace: with two gateways (or one daemon
404
+ // hosting two workspaces) whichever bound first served the other's
405
+ // sessions too — one project's agent reading another project's events.
406
+ // The port is also the contract the MCP shim resolves from
407
+ // `.lensmcp/config.json`, so it must not be stepped up on collision:
408
+ // a shim pointed at 4543 cannot find a server that quietly moved to 4544.
409
+ const scope = (0, scope_1.readLensScope)(rt.root);
410
+ const mcpPort = Number(process.env['LENSMCP_PORT'] ?? scope.mcpHttpPort);
411
+ console.log(`[gateway] starting lens MCP server on 127.0.0.1:${mcpPort} (${mcpBundle})`);
403
412
  spawnManagedChild('lens-mcp', process.execPath, [mcpBundle], {
404
413
  LENSMCP_EVENT_FILE: rt.eventFile,
405
414
  LENSMCP_TRANSPORT: process.env['LENSMCP_TRANSPORT'] ?? 'http',
406
- LENSMCP_PORT: process.env['LENSMCP_PORT'] ?? '3000',
415
+ LENSMCP_PORT: String(mcpPort),
407
416
  });
408
417
  }
409
418
  }
@@ -3,14 +3,26 @@
3
3
  * 'tetros.ai.local'] → 'tetros.ai.local'). Falls back to the first host, then
4
4
  * to 'lens.local' if no host is declared. */
5
5
  export declare function baseDomainOf(hosts: string[]): string;
6
- /** Per-workspace lens scope (key + dashboard port + mount path). Prefers the
7
- * CLI-written `.lensmcp/config.json` (so the gateway agrees with `lensmcp gateway`
8
- * / `lensmcp dashboard`); falls back to deriving from the workspace. */
9
- export declare function readLensScope(root: string): {
6
+ export interface LensScope {
10
7
  key: string;
11
8
  dashboardPort: number;
9
+ /**
10
+ * The MCP server's HTTP port for THIS workspace.
11
+ *
12
+ * This is the ISOLATION BOUNDARY for the shared MCP server. One daemon hosts
13
+ * several workspaces; if their MCP children shared a port, a session in one
14
+ * workspace would read the other's observability data. It used to be the
15
+ * hardcoded `3000` for every workspace — see `lens-children.ts`.
16
+ */
17
+ mcpHttpPort: number;
12
18
  basePath: string;
13
- };
19
+ }
20
+ /** Per-workspace lens scope (key + ports + mount path). Prefers the
21
+ * CLI-written `.lensmcp/config.json` (so the gateway agrees with `lensmcp gateway`
22
+ * / `lensmcp dashboard` / the MCP shim); falls back to deriving from the workspace. */
23
+ export declare function readLensScope(root: string): LensScope;
24
+ /** The workspace's MCP HTTP port, derived from its key. In lock-step with the CLI. */
25
+ export declare function deriveMcpHttpPort(key: string): number;
14
26
  /**
15
27
  * Per-workspace extra SOURCE-SET excludes (`.lensmcp/config.json` →
16
28
  * `sourceSet.exclude: string[]`) — top-level dir names the staleness scanner
@@ -1 +1 @@
1
- {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/scope.ts"],"names":[],"mappings":"AAOA;;;8CAG8C;AAC9C,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAUpD;AAED;;yEAEyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAmBpG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAa9D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ9C"}
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/scope.ts"],"names":[],"mappings":"AAOA;;;8CAG8C;AAC9C,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAUpD;AAeD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;wFAEwF;AACxF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CA2BrD;AAED,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAa9D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQ9C"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.baseDomainOf = baseDomainOf;
4
4
  exports.readLensScope = readLensScope;
5
+ exports.deriveMcpHttpPort = deriveMcpHttpPort;
5
6
  exports.readSourceSetExclude = readSourceSetExclude;
6
7
  exports.lensKeyFrom = lensKeyFrom;
7
8
  exports.lensSlug = lensSlug;
@@ -29,16 +30,30 @@ function baseDomainOf(hosts) {
29
30
  const parts = shortest.split('.');
30
31
  return parts.slice(-Math.min(parts.length, 3)).join('.');
31
32
  }
32
- /** Per-workspace lens scope (key + dashboard port + mount path). Prefers the
33
+ /** Lens-infra port bases must equal `BASE_PORTS` in the CLI's workspace-scope. */
34
+ const BASE_PORTS = { dashboard: 4321, mcpHttp: 4500 };
35
+ /** FNV-1a → 0..(range-1). Same derivation as the CLI's `hashToRange`. */
36
+ function hashToRange(s, range) {
37
+ let h = 0x811c9dc5;
38
+ for (let i = 0; i < s.length; i++) {
39
+ h ^= s.charCodeAt(i);
40
+ h = Math.imul(h, 0x01000193);
41
+ }
42
+ return Math.abs(h | 0) % range;
43
+ }
44
+ /** Per-workspace lens scope (key + ports + mount path). Prefers the
33
45
  * CLI-written `.lensmcp/config.json` (so the gateway agrees with `lensmcp gateway`
34
- * / `lensmcp dashboard`); falls back to deriving from the workspace. */
46
+ * / `lensmcp dashboard` / the MCP shim); falls back to deriving from the workspace. */
35
47
  function readLensScope(root) {
36
48
  try {
37
49
  const cfg = JSON.parse(fs.readFileSync(path.join(root, '.lensmcp', 'config.json'), 'utf8'));
38
50
  if (cfg && typeof cfg.key === 'string') {
39
51
  return {
40
52
  key: cfg.key,
41
- dashboardPort: cfg.ports?.dashboard ?? 4321,
53
+ dashboardPort: cfg.ports?.dashboard ?? BASE_PORTS.dashboard,
54
+ // Derive rather than fall back to a constant: a constant would put two
55
+ // config-less workspaces on ONE port, which is the cross-feed bug.
56
+ mcpHttpPort: cfg.ports?.mcpHttp ?? deriveMcpHttpPort(cfg.key),
42
57
  basePath: cfg.dashboardBasePath ?? '/' + cfg.key,
43
58
  };
44
59
  }
@@ -47,7 +62,16 @@ function readLensScope(root) {
47
62
  /* no config yet — derive below */
48
63
  }
49
64
  const key = lensKeyFrom(root);
50
- return { key, dashboardPort: 4321, basePath: '/' + key };
65
+ return {
66
+ key,
67
+ dashboardPort: BASE_PORTS.dashboard,
68
+ mcpHttpPort: deriveMcpHttpPort(key),
69
+ basePath: '/' + key,
70
+ };
71
+ }
72
+ /** The workspace's MCP HTTP port, derived from its key. In lock-step with the CLI. */
73
+ function deriveMcpHttpPort(key) {
74
+ return BASE_PORTS.mcpHttp + hashToRange(key, 200);
51
75
  }
52
76
  /**
53
77
  * Per-workspace extra SOURCE-SET excludes (`.lensmcp/config.json` →
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/cluster",
3
- "version": "1.17.2",
3
+ "version": "1.17.4",
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",
@@ -65,8 +65,9 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@lensmcp/node-instrumentation": "1.17.2",
69
- "@lensmcp/nx-plugin": "1.17.2",
68
+ "@lensmcp/node-instrumentation": "1.17.4",
69
+ "@lensmcp/nx-plugin": "1.17.4",
70
+ "@typescript/native-preview": "^7.0.0-dev.20260707.2",
70
71
  "fork-ts-checker-webpack-plugin": "^9.0.0",
71
72
  "glob": "^11.0.0",
72
73
  "http-proxy": "^1.18.0",
@@ -21,8 +21,15 @@
21
21
  * `module: preserve` + `emitDecoratorMetadata` otherwise flags TS1272 on decorated signatures —
22
22
  * an emit-safety rule that doesn't apply here (this checker never emits; webpack owns emit).
23
23
  * Parity was verified per adopting workspace (0-error parity with tsc on all spot-checked apps);
24
- * tsgo is a PREVIEW compiler, which is why this mode is opt-in and falls back to fork-ts-checker
25
- * when the binary is absent.
24
+ * tsgo is a PREVIEW compiler, which is why this MODE is opt-in.
25
+ *
26
+ * OWNERSHIP: `@lensmcp/cluster` DEPENDS on `@typescript/native-preview` — this is our feature, so we
27
+ * ship the compiler that powers it rather than asking every consumer to remember a devDep. That was
28
+ * the old contract, and it made the feature (and, once this plugin became the `typecheck://`
29
+ * producer, the whole typecheck health signal) die quietly in any workspace that forgot. Resolution
30
+ * still PREFERS a workspace's own copy (a workspace pinning a tsgo version keeps winning), and a
31
+ * missing binary is now an ANOMALY: it degrades gracefully — never crashing the dev server — but it
32
+ * reports itself onto the lens bus instead of only warning into a log nobody reads.
26
33
  */
27
34
  import { spawn } from 'node:child_process';
28
35
  export interface TsgoCheckPluginOptions {
@@ -31,7 +38,7 @@ export interface TsgoCheckPluginOptions {
31
38
  tsConfig: string;
32
39
  workspaceRoot: string;
33
40
  debounceMs?: number;
34
- /** Binary override (LENSMCP_TSGO_BIN); default: <workspaceRoot>/node_modules/.bin/tsgo. */
41
+ /** Binary override (LENSMCP_TSGO_BIN); otherwise resolved by {@link resolveTsgoBin}. */
35
42
  binPath?: string;
36
43
  /** Injectable for tests. */
37
44
  spawnImpl?: typeof spawn;
@@ -44,8 +51,70 @@ export interface TsgoCheckPluginOptions {
44
51
  /** Set false to skip publishing to the lens bus (console output is unchanged). */
45
52
  publishEvents?: boolean;
46
53
  }
47
- /** The workspace's tsgo binary, or null when not installed (caller falls back to fork-ts-checker). */
48
- export declare function resolveTsgoBin(workspaceRoot: string, override?: string): string | null;
54
+ /** The npm package that ships the tsgo compiler a real `dependency` of `@lensmcp/cluster`. */
55
+ export declare const TSGO_PACKAGE = "@typescript/native-preview";
56
+ /** Seams for {@link resolveTsgoBin}, so every branch of the precedence is testable without an install. */
57
+ export interface TsgoResolutionDeps {
58
+ /** Locate OUR `@typescript/native-preview`. Default: {@link resolveTsgoPackageDir}. */
59
+ ownPackageDir?: () => string | null;
60
+ /** Probe a candidate. Default: exists && is a file && is executable. */
61
+ isExecutable?: (candidate: string) => boolean;
62
+ /** Default: `process.platform`. */
63
+ platform?: NodeJS.Platform;
64
+ }
65
+ /**
66
+ * Where OUR copy of {@link TSGO_PACKAGE} lives, or null when it isn't installed.
67
+ *
68
+ * `require.resolve` walks up from THIS module, so it finds the copy installed for
69
+ * `@lensmcp/cluster` whether the consumer's installer HOISTED it
70
+ * (`<root>/node_modules/@typescript/native-preview`) or NESTED it
71
+ * (`<root>/node_modules/@lensmcp/cluster/node_modules/@typescript/native-preview`, the shape you
72
+ * get on a version conflict) or used pnpm's virtual store. A hand-written `../../node_modules/…`
73
+ * relative guess gets exactly one of those right.
74
+ */
75
+ export declare function resolveTsgoPackageDir(): string | null;
76
+ /**
77
+ * The tsgo binary to run, or null when none is usable.
78
+ *
79
+ * Precedence — first usable wins:
80
+ * 1. `override` (LENSMCP_TSGO_BIN). Deliberately NOT a fallthrough: an override pointing at a
81
+ * missing/non-executable file is an operator mistake to SURFACE, not to paper over by silently
82
+ * running a different compiler than the one that was asked for.
83
+ * 2. The consuming workspace's `node_modules/.bin/tsgo` — a workspace that pins its own tsgo
84
+ * version still beats the one we ship.
85
+ * 3. OURS, via {@link resolveTsgoPackageDir}. `@lensmcp/cluster` depends on {@link TSGO_PACKAGE},
86
+ * so this is the branch that normally answers, and the reason the feature no longer depends on
87
+ * every consumer independently remembering a devDep.
88
+ */
89
+ export declare function resolveTsgoBin(workspaceRoot: string, override?: string, deps?: TsgoResolutionDeps): string | null;
90
+ /**
91
+ * Why tsgo could not run, and what to do — the CAUSE half, shared by both sites that can hit it.
92
+ *
93
+ * Phrased for the new ownership: `@lensmcp/cluster` DEPENDS on the compiler, so "not installed" is
94
+ * no longer an expected opt-out state, it is a broken install (or an unsupported platform, or a bad
95
+ * override). Callers append the CONSEQUENCE, which differs by site.
96
+ */
97
+ export declare const TSGO_MISSING_CAUSE: string;
98
+ /**
99
+ * Report — onto the LENS BUS, not just the console — that tsgo cannot run.
100
+ *
101
+ * A `console.warn` is invisible to anything reading `typecheck://current`, which is precisely the
102
+ * failure this whole area exists to eliminate: a resource that looks healthy because nobody is
103
+ * watching. Published as a `toolError` run, so the reducer records the producer as reporting with
104
+ * `lastRunStatus: 'error'` — `status` degrades to `unknown` (never a false `clean`) and `detail`
105
+ * names the cause and the fix.
106
+ *
107
+ * Strictly passive: fully swallowed, never blocks or fails the host build.
108
+ */
109
+ export declare function reportTsgoUnavailable(args: {
110
+ appName: string;
111
+ tsConfig: string;
112
+ workspaceRoot: string;
113
+ /** Full sentence: {@link TSGO_MISSING_CAUSE} plus this site's consequence. */
114
+ detail: string;
115
+ /** Override the lens bus file. Injectable for tests. */
116
+ eventFile?: string;
117
+ }): void;
49
118
  /**
50
119
  * Write the shim tsconfig (absolute `extends`; see the header) and return its path. It lives under
51
120
  * the WORKSPACE's git-ignored `tmp/` — NOT os.tmpdir(): `types: ['node']` (and every other @types
@@ -1 +1 @@
1
- {"version":3,"file":"tsgo-check-plugin.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/tsgo-check-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAK9D,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGtF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CA0BlG;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,EAAE,sBAAsB;IAMxC,KAAK,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAO1B,sGAAsG;IACtG,QAAQ,IAAI,IAAI;IAOhB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,GAAG;CA4DZ"}
1
+ {"version":3,"file":"tsgo-check-plugin.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/tsgo-check-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAK9D,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,gGAAgG;AAChG,eAAO,MAAM,YAAY,+BAA+B,CAAC;AAEzD,0GAA0G;AAC1G,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACpC,wEAAwE;IACxE,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B;AAmCD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CA6BrD;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,GAAE,kBAAuB,GAC5B,MAAM,GAAG,IAAI,CAcf;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,QAG2C,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAeP;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CA0BlG;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,EAAE,sBAAsB;IAMxC,KAAK,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAO1B,sGAAsG;IACtG,QAAQ,IAAI,IAAI;IAOhB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,GAAG;CA8DZ"}
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TsgoCheckPlugin = void 0;
3
+ exports.TsgoCheckPlugin = exports.TSGO_MISSING_CAUSE = exports.TSGO_PACKAGE = void 0;
4
+ exports.resolveTsgoPackageDir = resolveTsgoPackageDir;
4
5
  exports.resolveTsgoBin = resolveTsgoBin;
6
+ exports.reportTsgoUnavailable = reportTsgoUnavailable;
5
7
  exports.writeShimTsconfig = writeShimTsconfig;
6
8
  const tslib_1 = require("tslib");
7
9
  /**
@@ -27,17 +29,191 @@ const tslib_1 = require("tslib");
27
29
  * `module: preserve` + `emitDecoratorMetadata` otherwise flags TS1272 on decorated signatures —
28
30
  * an emit-safety rule that doesn't apply here (this checker never emits; webpack owns emit).
29
31
  * Parity was verified per adopting workspace (0-error parity with tsc on all spot-checked apps);
30
- * tsgo is a PREVIEW compiler, which is why this mode is opt-in and falls back to fork-ts-checker
31
- * when the binary is absent.
32
+ * tsgo is a PREVIEW compiler, which is why this MODE is opt-in.
33
+ *
34
+ * OWNERSHIP: `@lensmcp/cluster` DEPENDS on `@typescript/native-preview` — this is our feature, so we
35
+ * ship the compiler that powers it rather than asking every consumer to remember a devDep. That was
36
+ * the old contract, and it made the feature (and, once this plugin became the `typecheck://`
37
+ * producer, the whole typecheck health signal) die quietly in any workspace that forgot. Resolution
38
+ * still PREFERS a workspace's own copy (a workspace pinning a tsgo version keeps winning), and a
39
+ * missing binary is now an ANOMALY: it degrades gracefully — never crashing the dev server — but it
40
+ * reports itself onto the lens bus instead of only warning into a log nobody reads.
32
41
  */
33
42
  const node_child_process_1 = require("node:child_process");
34
43
  const fs = tslib_1.__importStar(require("node:fs"));
35
44
  const path = tslib_1.__importStar(require("node:path"));
36
45
  const typecheck_bus_1 = require("./typecheck-bus");
37
- /** The workspace's tsgo binary, or null when not installed (caller falls back to fork-ts-checker). */
38
- function resolveTsgoBin(workspaceRoot, override) {
39
- const candidate = override || path.join(workspaceRoot, 'node_modules', '.bin', 'tsgo');
40
- return fs.existsSync(candidate) ? candidate : null;
46
+ /** The npm package that ships the tsgo compiler a real `dependency` of `@lensmcp/cluster`. */
47
+ exports.TSGO_PACKAGE = '@typescript/native-preview';
48
+ /**
49
+ * The spawnable shim names inside a `node_modules/.bin`.
50
+ *
51
+ * On win32 the only shims `child_process.spawn` can execute directly are `tsgo.cmd` / `tsgo.exe`;
52
+ * the EXTENSIONLESS `tsgo` npm writes beside them is a POSIX shell script for git-bash, and
53
+ * spawning it without a shell fails. So win32 never considers the bare name.
54
+ */
55
+ function shimNames(platform) {
56
+ return platform === 'win32' ? ['tsgo.cmd', 'tsgo.exe'] : ['tsgo'];
57
+ }
58
+ /**
59
+ * Existence is not enough — a directory, a dangling symlink, or a non-`+x` file all "exist" and
60
+ * then fail at spawn time as an opaque EACCES/ENOENT mid-serve. Probe once, here.
61
+ * `statSync` follows symlinks on purpose (`.bin/tsgo` IS one).
62
+ */
63
+ function isExecutableFile(candidate, platform) {
64
+ try {
65
+ if (!fs.statSync(candidate).isFile())
66
+ return false;
67
+ }
68
+ catch {
69
+ return false; // missing, or a dangling symlink
70
+ }
71
+ // X_OK is meaningless on win32 (it reports every readable file executable), so the extension
72
+ // check in `shimNames` is the real gate there.
73
+ if (platform === 'win32')
74
+ return true;
75
+ try {
76
+ fs.accessSync(candidate, fs.constants.X_OK);
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ /**
84
+ * Where OUR copy of {@link TSGO_PACKAGE} lives, or null when it isn't installed.
85
+ *
86
+ * `require.resolve` walks up from THIS module, so it finds the copy installed for
87
+ * `@lensmcp/cluster` whether the consumer's installer HOISTED it
88
+ * (`<root>/node_modules/@typescript/native-preview`) or NESTED it
89
+ * (`<root>/node_modules/@lensmcp/cluster/node_modules/@typescript/native-preview`, the shape you
90
+ * get on a version conflict) or used pnpm's virtual store. A hand-written `../../node_modules/…`
91
+ * relative guess gets exactly one of those right.
92
+ */
93
+ function resolveTsgoPackageDir() {
94
+ try {
95
+ // The package maps `"./package.json"` in its `exports`, which is the cheap, exact answer.
96
+ return path.dirname(require.resolve(`${exports.TSGO_PACKAGE}/package.json`));
97
+ }
98
+ catch {
99
+ /* an `exports` map without ./package.json — fall through to the entry-point walk */
100
+ }
101
+ try {
102
+ let dir = path.dirname(require.resolve(exports.TSGO_PACKAGE));
103
+ // The entry may sit a few levels below the package root (`<pkg>/lib/version.cjs`), so walk up
104
+ // to the manifest that actually NAMES the package rather than assuming a depth.
105
+ for (let i = 0; i < 6; i++) {
106
+ const manifest = path.join(dir, 'package.json');
107
+ if (fs.existsSync(manifest)) {
108
+ try {
109
+ const { name } = JSON.parse(fs.readFileSync(manifest, 'utf8'));
110
+ if (name === exports.TSGO_PACKAGE)
111
+ return dir;
112
+ }
113
+ catch {
114
+ /* unreadable manifest — keep walking */
115
+ }
116
+ }
117
+ const parent = path.dirname(dir);
118
+ if (parent === dir)
119
+ break;
120
+ dir = parent;
121
+ }
122
+ }
123
+ catch {
124
+ /* not installed */
125
+ }
126
+ return null;
127
+ }
128
+ /** `bin.tsgo` (or a string `bin`) from a package dir, as an absolute path. */
129
+ function packageDeclaredBin(pkgDir) {
130
+ try {
131
+ const { bin } = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
132
+ const rel = typeof bin === 'string' ? bin : bin?.['tsgo'];
133
+ return rel ? path.join(pkgDir, rel) : null;
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ /** Candidate binaries for an installed {@link TSGO_PACKAGE}, best first. */
140
+ function ownBinCandidates(pkgDir, platform) {
141
+ // `<…>/node_modules/@typescript/native-preview` ⇒ `<…>/node_modules/.bin` — two levels up holds
142
+ // for a hoisted install, a nested one, and pnpm's store alike (a scoped package is always
143
+ // `<node_modules>/<scope>/<name>`).
144
+ const binDir = path.resolve(pkgDir, '..', '..', '.bin');
145
+ const shims = shimNames(platform).map((n) => path.join(binDir, n));
146
+ // The package's OWN `bin/tsgo` is present whenever the package is, regardless of whether the
147
+ // installer wrote shims — but it is a `#!/usr/bin/env node` script, so it is directly spawnable
148
+ // on POSIX only. On win32 the `.cmd`/`.exe` shims are the only usable entry.
149
+ const declared = platform === 'win32' ? null : packageDeclaredBin(pkgDir);
150
+ return declared ? [declared, ...shims] : shims;
151
+ }
152
+ /**
153
+ * The tsgo binary to run, or null when none is usable.
154
+ *
155
+ * Precedence — first usable wins:
156
+ * 1. `override` (LENSMCP_TSGO_BIN). Deliberately NOT a fallthrough: an override pointing at a
157
+ * missing/non-executable file is an operator mistake to SURFACE, not to paper over by silently
158
+ * running a different compiler than the one that was asked for.
159
+ * 2. The consuming workspace's `node_modules/.bin/tsgo` — a workspace that pins its own tsgo
160
+ * version still beats the one we ship.
161
+ * 3. OURS, via {@link resolveTsgoPackageDir}. `@lensmcp/cluster` depends on {@link TSGO_PACKAGE},
162
+ * so this is the branch that normally answers, and the reason the feature no longer depends on
163
+ * every consumer independently remembering a devDep.
164
+ */
165
+ function resolveTsgoBin(workspaceRoot, override, deps = {}) {
166
+ const platform = deps.platform ?? process.platform;
167
+ const isExecutable = deps.isExecutable ?? ((c) => isExecutableFile(c, platform));
168
+ if (override)
169
+ return isExecutable(override) ? override : null;
170
+ for (const name of shimNames(platform)) {
171
+ const candidate = path.join(workspaceRoot, 'node_modules', '.bin', name);
172
+ if (isExecutable(candidate))
173
+ return candidate;
174
+ }
175
+ const pkgDir = (deps.ownPackageDir ?? resolveTsgoPackageDir)();
176
+ if (!pkgDir)
177
+ return null;
178
+ return ownBinCandidates(pkgDir, platform).find(isExecutable) ?? null;
179
+ }
180
+ /**
181
+ * Why tsgo could not run, and what to do — the CAUSE half, shared by both sites that can hit it.
182
+ *
183
+ * Phrased for the new ownership: `@lensmcp/cluster` DEPENDS on the compiler, so "not installed" is
184
+ * no longer an expected opt-out state, it is a broken install (or an unsupported platform, or a bad
185
+ * override). Callers append the CONSEQUENCE, which differs by site.
186
+ */
187
+ exports.TSGO_MISSING_CAUSE = `tsgo binary not found — @lensmcp/cluster depends on ${exports.TSGO_PACKAGE}, so an absent binary means an ` +
188
+ `incomplete install (reinstall node_modules), a platform with no ${exports.TSGO_PACKAGE} build, or a ` +
189
+ `LENSMCP_TSGO_BIN override pointing at a missing or non-executable file`;
190
+ /**
191
+ * Report — onto the LENS BUS, not just the console — that tsgo cannot run.
192
+ *
193
+ * A `console.warn` is invisible to anything reading `typecheck://current`, which is precisely the
194
+ * failure this whole area exists to eliminate: a resource that looks healthy because nobody is
195
+ * watching. Published as a `toolError` run, so the reducer records the producer as reporting with
196
+ * `lastRunStatus: 'error'` — `status` degrades to `unknown` (never a false `clean`) and `detail`
197
+ * names the cause and the fix.
198
+ *
199
+ * Strictly passive: fully swallowed, never blocks or fails the host build.
200
+ */
201
+ function reportTsgoUnavailable(args) {
202
+ try {
203
+ (0, typecheck_bus_1.publishTypecheckRun)({
204
+ workspaceRoot: args.workspaceRoot,
205
+ project: args.appName,
206
+ tool: 'tsgo',
207
+ tsConfig: args.tsConfig,
208
+ output: '',
209
+ durationMs: 0,
210
+ toolError: args.detail,
211
+ ...(args.eventFile ? { eventFile: args.eventFile } : {}),
212
+ });
213
+ }
214
+ catch {
215
+ /* never let instrumentation disturb a build */
216
+ }
41
217
  }
42
218
  /**
43
219
  * Write the shim tsconfig (absolute `extends`; see the header) and return its path. It lives under
@@ -120,12 +296,14 @@ class TsgoCheckPlugin {
120
296
  if (!bin) {
121
297
  // The binary vanished after config time (e.g. a reinstall mid-serve) — warn once, go quiet.
122
298
  this.disabled = true;
123
- console.warn(`[tsgo] ${this.opts.appName}: tsgo binary not found type checking disabled for this session`);
299
+ // No trailing period: `summariseProducers` renders this INSIDE a sentence it terminates itself.
300
+ const detail = `${exports.TSGO_MISSING_CAUSE}. Type checking is disabled for the rest of this session`;
301
+ console.warn(`[tsgo] ${this.opts.appName}: ${detail}.`);
124
302
  // A checker that stopped checking must SAY so: without this the resource
125
303
  // would keep serving the last (possibly clean) verdict forever, which is
126
304
  // exactly the "looks healthy, nobody is watching" state this work exists
127
305
  // to make impossible.
128
- this.publish({ output: '', durationMs: 0, toolError: `tsgo binary not found — type checking disabled` });
306
+ this.publish({ output: '', durationMs: 0, toolError: detail });
129
307
  return;
130
308
  }
131
309
  if (this.inflight) {