meshy-node 0.0.5 → 0.0.7

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/main.cjs CHANGED
@@ -22771,9 +22771,11 @@ __export(main_exports, {
22771
22771
  DEFAULT_CONFIG: () => DEFAULT_CONFIG3,
22772
22772
  DEFAULT_NODE_NAME: () => DEFAULT_NODE_NAME,
22773
22773
  DEFAULT_NODE_PORT: () => DEFAULT_NODE_PORT,
22774
+ applyStartMetadata: () => applyStartMetadata,
22774
22775
  createDefaultConfig: () => createDefaultConfig,
22775
22776
  createRuntimeDefaultConfig: () => createRuntimeDefaultConfig,
22776
22777
  formatBanner: () => formatBanner,
22778
+ formatLoadedStartMetadata: () => formatLoadedStartMetadata,
22777
22779
  getDefaultNodeName: () => getDefaultNodeName,
22778
22780
  isDirectRunPath: () => isDirectRunPath,
22779
22781
  loadConfigFile: () => loadConfigFile,
@@ -22781,12 +22783,12 @@ __export(main_exports, {
22781
22783
  mergeConfig: () => mergeConfig,
22782
22784
  parseArgs: () => parseArgs,
22783
22785
  promptStartOptions: () => promptStartOptions,
22786
+ resolveRuntimeAuthMetadata: () => resolveRuntimeAuthMetadata,
22787
+ shouldCollectStartOptions: () => shouldCollectStartOptions,
22784
22788
  shouldPromptForStartOptions: () => shouldPromptForStartOptions
22785
22789
  });
22786
22790
  module.exports = __toCommonJS(main_exports);
22787
- var fs16 = __toESM(require("fs"), 1);
22788
22791
  var nodePath = __toESM(require("path"), 1);
22789
- var readline = __toESM(require("readline/promises"), 1);
22790
22792
 
22791
22793
  // ../../packages/core/src/logger.ts
22792
22794
  var fs = __toESM(require("fs"), 1);
@@ -22810,18 +22812,19 @@ function hourlyFolder(date) {
22810
22812
  var FileWriter = class {
22811
22813
  constructor(logDir, component, clock) {
22812
22814
  this.logDir = logDir;
22813
- this.component = component;
22814
22815
  this.clock = clock;
22816
+ this.fileName = component.replace(/[/\\]/g, "-");
22815
22817
  }
22816
22818
  currentFolder = "";
22817
22819
  currentStream = null;
22820
+ fileName;
22818
22821
  write(chunk) {
22819
22822
  const folder = hourlyFolder(this.clock());
22820
22823
  if (folder !== this.currentFolder) {
22821
22824
  this.currentStream?.end();
22822
22825
  const dir = path.join(this.logDir, folder);
22823
22826
  fs.mkdirSync(dir, { recursive: true });
22824
- const filePath = path.join(dir, `${this.component}.log`);
22827
+ const filePath = path.join(dir, `${this.fileName}.log`);
22825
22828
  this.currentStream = fs.createWriteStream(filePath, { flags: "a" });
22826
22829
  this.currentFolder = folder;
22827
22830
  }
@@ -22856,6 +22859,12 @@ var MeshyLogger = class _MeshyLogger {
22856
22859
  this.log("error", msg, data);
22857
22860
  }
22858
22861
  child(component) {
22862
+ if (this.state.logDir) {
22863
+ const fileWriter = new FileWriter(this.state.logDir, component, this.state.clock);
22864
+ const writer = this.state.consoleEnabled ? new MultiWriter([process.stdout, fileWriter]) : fileWriter;
22865
+ const childState = { ...this.state, writer };
22866
+ return new _MeshyLogger(childState, component);
22867
+ }
22859
22868
  return new _MeshyLogger(this.state, component);
22860
22869
  }
22861
22870
  log(level, msg, data) {
@@ -22912,7 +22921,9 @@ function createLogger(options = {}) {
22912
22921
  level: options.level ?? "info",
22913
22922
  nodeId: options.nodeId,
22914
22923
  clock,
22915
- writer
22924
+ writer,
22925
+ logDir: options.logDir,
22926
+ consoleEnabled: options.console
22916
22927
  };
22917
22928
  return new MeshyRootLogger(state, options.component);
22918
22929
  }
@@ -22996,10 +23007,14 @@ function getTaskInitialMessageContent(task) {
22996
23007
  return normalizeTaskUserMessageContent(task.description || task.title);
22997
23008
  }
22998
23009
 
23010
+ // ../../packages/core/src/azure-cli-auth.ts
23011
+ var import_node_child_process = require("child_process");
23012
+
22999
23013
  // ../../packages/core/src/node-metadata.ts
23000
23014
  var fs2 = __toESM(require("fs"), 1);
23001
23015
  var os = __toESM(require("os"), 1);
23002
23016
  var path2 = __toESM(require("path"), 1);
23017
+ var STARTUP_TRANSPORTS = /* @__PURE__ */ new Set(["direct", "devtunnel", "tailscale"]);
23003
23018
  function getDeviceNodeName() {
23004
23019
  return os.hostname();
23005
23020
  }
@@ -23026,8 +23041,59 @@ function writeNodeMetadata(storagePath, metadata) {
23026
23041
  fs2.mkdirSync(storagePath, { recursive: true });
23027
23042
  fs2.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + "\n", "utf-8");
23028
23043
  }
23044
+ function normalizeAllowedUsers(allowedUsers) {
23045
+ if (!Array.isArray(allowedUsers)) {
23046
+ return [];
23047
+ }
23048
+ const unique = /* @__PURE__ */ new Set();
23049
+ for (const user of allowedUsers) {
23050
+ if (typeof user !== "string") continue;
23051
+ const normalized = user.trim().toLowerCase();
23052
+ if (normalized.length > 0) {
23053
+ unique.add(normalized);
23054
+ }
23055
+ }
23056
+ return [...unique];
23057
+ }
23058
+ function normalizeAuthMetadata(auth) {
23059
+ return {
23060
+ enabled: auth?.enabled === true,
23061
+ allowSameTenant: auth?.allowSameTenant === true,
23062
+ allowedUsers: normalizeAllowedUsers(auth?.allowedUsers)
23063
+ };
23064
+ }
23065
+ function normalizePositiveInteger(value) {
23066
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
23067
+ return void 0;
23068
+ }
23069
+ return value;
23070
+ }
23071
+ function normalizeString(value) {
23072
+ if (typeof value !== "string") {
23073
+ return void 0;
23074
+ }
23075
+ const trimmed = value.trim();
23076
+ return trimmed.length > 0 ? trimmed : void 0;
23077
+ }
23078
+ function normalizeStartupTransport(value) {
23079
+ return typeof value === "string" && STARTUP_TRANSPORTS.has(value) ? value : void 0;
23080
+ }
23081
+ function normalizeStartupMetadata(startup) {
23082
+ return {
23083
+ port: normalizePositiveInteger(startup?.port),
23084
+ transport: normalizeStartupTransport(startup?.transport),
23085
+ join: normalizeString(startup?.join),
23086
+ workDir: normalizeString(startup?.workDir)
23087
+ };
23088
+ }
23089
+ function hasStartupValues(startup) {
23090
+ return startup.port !== void 0 || startup.transport !== void 0 || startup.join !== void 0 || startup.workDir !== void 0;
23091
+ }
23092
+ function resolvePersistedDefaultNodeName(storagePath) {
23093
+ return normalizeString(readNodeMetadata(storagePath).defaultNodeName);
23094
+ }
23029
23095
  function resolveDefaultNodeName(storagePath, deviceName = getDeviceNodeName()) {
23030
- const preferredName = readNodeMetadata(storagePath).defaultNodeName?.trim();
23096
+ const preferredName = resolvePersistedDefaultNodeName(storagePath);
23031
23097
  return preferredName && preferredName.length > 0 ? preferredName : deviceName;
23032
23098
  }
23033
23099
  function persistDefaultNodeName(storagePath, name, deviceName = getDeviceNodeName()) {
@@ -23040,6 +23106,46 @@ function persistDefaultNodeName(storagePath, name, deviceName = getDeviceNodeNam
23040
23106
  }
23041
23107
  writeNodeMetadata(storagePath, metadata);
23042
23108
  }
23109
+ function resolveNodeAuthMetadata(storagePath) {
23110
+ return normalizeAuthMetadata(readNodeMetadata(storagePath).auth);
23111
+ }
23112
+ function resolveNodeStartupMetadata(storagePath) {
23113
+ return normalizeStartupMetadata(readNodeMetadata(storagePath).startup);
23114
+ }
23115
+ function hasPersistedNodeStartupMetadata(storagePath) {
23116
+ return hasStartupValues(resolveNodeStartupMetadata(storagePath));
23117
+ }
23118
+ function persistNodeAuthMetadata(storagePath, auth) {
23119
+ const metadata = readNodeMetadata(storagePath);
23120
+ const normalized = normalizeAuthMetadata(auth);
23121
+ if (!normalized.enabled && !normalized.allowSameTenant && normalized.allowedUsers.length === 0) {
23122
+ delete metadata.auth;
23123
+ } else {
23124
+ metadata.auth = {
23125
+ enabled: normalized.enabled,
23126
+ allowSameTenant: normalized.allowSameTenant,
23127
+ allowedUsers: normalized.allowedUsers
23128
+ };
23129
+ }
23130
+ writeNodeMetadata(storagePath, metadata);
23131
+ }
23132
+ function persistNodeStartupMetadata(storagePath, startup) {
23133
+ const metadata = readNodeMetadata(storagePath);
23134
+ const normalized = normalizeStartupMetadata(startup);
23135
+ if (!hasStartupValues(normalized)) {
23136
+ delete metadata.startup;
23137
+ } else {
23138
+ metadata.startup = normalized;
23139
+ }
23140
+ writeNodeMetadata(storagePath, metadata);
23141
+ }
23142
+ function persistNodeStartupTransport(storagePath, transport) {
23143
+ const startup = resolveNodeStartupMetadata(storagePath);
23144
+ persistNodeStartupMetadata(storagePath, {
23145
+ ...startup,
23146
+ transport
23147
+ });
23148
+ }
23043
23149
  function resolvePersistedDevTunnelId(storagePath, kind) {
23044
23150
  return readNodeMetadata(storagePath).devTunnelIds?.[kind]?.trim() || void 0;
23045
23151
  }
@@ -23072,6 +23178,246 @@ function resolveOrCreateDevTunnelId(storagePath, nodeId, kind) {
23072
23178
  return generatedId;
23073
23179
  }
23074
23180
 
23181
+ // ../../packages/core/src/azure-cli-auth.ts
23182
+ var DEFAULT_AZ_RESOURCE = process.env.MESHY_AZ_TOKEN_RESOURCE?.trim() || "https://management.azure.com/";
23183
+ var TOKEN_REFRESH_SKEW_MS = 6e4;
23184
+ function summarizeClaims(claims) {
23185
+ return {
23186
+ tid: claims.tid,
23187
+ oid: claims.oid,
23188
+ preferred_username: claims.preferred_username,
23189
+ upn: claims.upn,
23190
+ email: claims.email,
23191
+ aud: claims.aud
23192
+ };
23193
+ }
23194
+ function decodeAzureCliJwtClaims(token) {
23195
+ const parts = token.split(".");
23196
+ if (parts.length < 2) {
23197
+ throw new Error("Token is not a JWT");
23198
+ }
23199
+ const payload = parts[1];
23200
+ if (!payload) {
23201
+ throw new Error("JWT payload is empty");
23202
+ }
23203
+ const normalized = payload.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payload.length / 4) * 4, "=");
23204
+ const parsed = JSON.parse(Buffer.from(normalized, "base64").toString("utf-8"));
23205
+ if (!parsed || typeof parsed !== "object") {
23206
+ throw new Error("JWT payload is not an object");
23207
+ }
23208
+ return parsed;
23209
+ }
23210
+ function normalizeIdentityValue(value) {
23211
+ return typeof value === "string" && value.trim().length > 0 ? value.trim().toLowerCase() : void 0;
23212
+ }
23213
+ function getIdentityValues(claims) {
23214
+ const values = /* @__PURE__ */ new Set();
23215
+ for (const candidate of [
23216
+ claims.oid,
23217
+ claims.preferred_username,
23218
+ claims.upn,
23219
+ claims.email,
23220
+ claims.unique_name,
23221
+ claims.sub
23222
+ ]) {
23223
+ const normalized = normalizeIdentityValue(candidate);
23224
+ if (normalized) {
23225
+ values.add(normalized);
23226
+ }
23227
+ }
23228
+ return values;
23229
+ }
23230
+ function parseExpiry(value) {
23231
+ if (typeof value === "number" && Number.isFinite(value)) {
23232
+ return value > 1e10 ? value : value * 1e3;
23233
+ }
23234
+ if (typeof value === "string") {
23235
+ const trimmed = value.trim();
23236
+ if (trimmed.length === 0) {
23237
+ throw new Error("expiresOn is empty");
23238
+ }
23239
+ if (/^\d+$/.test(trimmed)) {
23240
+ const numeric = Number(trimmed);
23241
+ return numeric > 1e10 ? numeric : numeric * 1e3;
23242
+ }
23243
+ const parsed = Date.parse(trimmed);
23244
+ if (!Number.isNaN(parsed)) {
23245
+ return parsed;
23246
+ }
23247
+ }
23248
+ throw new Error("Unable to parse Azure CLI token expiry");
23249
+ }
23250
+ function getAzureCliCommandInvocation(command, args, platform = process.platform) {
23251
+ if (platform !== "win32") {
23252
+ return { command, args };
23253
+ }
23254
+ return {
23255
+ command,
23256
+ args,
23257
+ shell: true
23258
+ };
23259
+ }
23260
+ function defaultCommandRunner(command, args) {
23261
+ const invocation = getAzureCliCommandInvocation(command, args);
23262
+ const result = (0, import_node_child_process.spawnSync)(invocation.command, invocation.args, {
23263
+ encoding: "utf-8",
23264
+ stdio: ["ignore", "pipe", "pipe"],
23265
+ shell: invocation.shell
23266
+ });
23267
+ if (result.error) {
23268
+ throw result.error;
23269
+ }
23270
+ if (result.status !== 0) {
23271
+ const message = result.stderr?.trim() || `Azure CLI command exited with code ${result.status}`;
23272
+ throw new Error(message);
23273
+ }
23274
+ return result.stdout;
23275
+ }
23276
+ var AzureCliNodeAuth = class {
23277
+ constructor(storagePath, options = {}) {
23278
+ this.storagePath = storagePath;
23279
+ this.runCommand = options.commandRunner ?? defaultCommandRunner;
23280
+ this.log = options.logger ?? { debug() {
23281
+ }, info() {
23282
+ }, warn() {
23283
+ }, error() {
23284
+ }, child() {
23285
+ return this;
23286
+ } };
23287
+ this.resource = options.resource?.trim() || DEFAULT_AZ_RESOURCE;
23288
+ this.settingsProvider = options.settingsProvider;
23289
+ }
23290
+ runCommand;
23291
+ log;
23292
+ resource;
23293
+ settingsProvider;
23294
+ cachedToken = null;
23295
+ getSettings() {
23296
+ return this.settingsProvider?.() ?? resolveNodeAuthMetadata(this.storagePath);
23297
+ }
23298
+ isEnabled() {
23299
+ return this.getSettings().enabled;
23300
+ }
23301
+ getAuthorizationHeaders() {
23302
+ if (!this.isEnabled()) {
23303
+ return {};
23304
+ }
23305
+ const token = this.getAccessToken();
23306
+ return {
23307
+ authorization: `Bearer ${token.accessToken}`
23308
+ };
23309
+ }
23310
+ validateAuthorizationHeader(header) {
23311
+ if (!this.isEnabled()) {
23312
+ return { ok: true, reason: "disabled" };
23313
+ }
23314
+ if (typeof header !== "string") {
23315
+ return { ok: false, reason: "missing" };
23316
+ }
23317
+ const token = header.startsWith("Bearer ") ? header.slice("Bearer ".length).trim() : "";
23318
+ if (!token) {
23319
+ return { ok: false, reason: "missing" };
23320
+ }
23321
+ let peerClaims;
23322
+ try {
23323
+ peerClaims = decodeAzureCliJwtClaims(token);
23324
+ } catch {
23325
+ return { ok: false, reason: "invalid" };
23326
+ }
23327
+ const local = this.getAccessToken();
23328
+ const settings = this.getSettings();
23329
+ if (isPeerIdentityAllowed(peerClaims, local.claims, settings)) {
23330
+ return {
23331
+ ok: true,
23332
+ reason: "match",
23333
+ localClaims: local.claims,
23334
+ peerClaims
23335
+ };
23336
+ }
23337
+ return {
23338
+ ok: false,
23339
+ reason: "mismatch",
23340
+ localClaims: local.claims,
23341
+ peerClaims
23342
+ };
23343
+ }
23344
+ getAccessToken() {
23345
+ if (this.cachedToken && this.cachedToken.expiresAtMs - TOKEN_REFRESH_SKEW_MS > Date.now()) {
23346
+ this.log.debug("using cached azure cli access token", {
23347
+ resource: this.resource,
23348
+ expiresAt: new Date(this.cachedToken.expiresAtMs).toISOString(),
23349
+ expiresInMs: this.cachedToken.expiresAtMs - Date.now(),
23350
+ claims: summarizeClaims(this.cachedToken.claims)
23351
+ });
23352
+ return this.cachedToken;
23353
+ }
23354
+ this.log.info("requesting azure cli access token", {
23355
+ resource: this.resource,
23356
+ cachedTokenPresent: this.cachedToken !== null
23357
+ });
23358
+ let raw;
23359
+ try {
23360
+ raw = this.runCommand("az", [
23361
+ "account",
23362
+ "get-access-token",
23363
+ "--resource",
23364
+ this.resource,
23365
+ "--output",
23366
+ "json"
23367
+ ]);
23368
+ } catch (error) {
23369
+ this.log.error("failed to execute az account get-access-token", {
23370
+ resource: this.resource,
23371
+ error: error instanceof Error ? error.message : String(error)
23372
+ });
23373
+ throw error;
23374
+ }
23375
+ const parsed = JSON.parse(raw);
23376
+ if (typeof parsed.accessToken !== "string" || parsed.accessToken.trim().length === 0) {
23377
+ this.log.error("azure cli token response did not include accessToken", {
23378
+ resource: this.resource
23379
+ });
23380
+ throw new Error("Azure CLI did not return accessToken");
23381
+ }
23382
+ const expiresAtMs = parseExpiry(parsed.expiresOn ?? parsed.expires_on);
23383
+ const claims = decodeAzureCliJwtClaims(parsed.accessToken);
23384
+ this.cachedToken = {
23385
+ accessToken: parsed.accessToken,
23386
+ expiresAtMs,
23387
+ claims
23388
+ };
23389
+ this.log.info("received azure cli access token", {
23390
+ resource: this.resource,
23391
+ expiresAt: new Date(expiresAtMs).toISOString(),
23392
+ expiresInMs: expiresAtMs - Date.now(),
23393
+ claims: summarizeClaims(claims)
23394
+ });
23395
+ return this.cachedToken;
23396
+ }
23397
+ };
23398
+ function isPeerIdentityAllowed(peerClaims, localClaims, settings) {
23399
+ const peerIdentities = getIdentityValues(peerClaims);
23400
+ const localIdentities = getIdentityValues(localClaims);
23401
+ for (const identity of peerIdentities) {
23402
+ if (localIdentities.has(identity)) {
23403
+ return true;
23404
+ }
23405
+ }
23406
+ for (const allowedUser of settings.allowedUsers) {
23407
+ if (peerIdentities.has(allowedUser)) {
23408
+ return true;
23409
+ }
23410
+ }
23411
+ if (settings.allowSameTenant) {
23412
+ const peerTenant = normalizeIdentityValue(peerClaims.tid);
23413
+ const localTenant = normalizeIdentityValue(localClaims.tid);
23414
+ if (peerTenant && localTenant && peerTenant === localTenant) {
23415
+ return true;
23416
+ }
23417
+ }
23418
+ return false;
23419
+ }
23420
+
23075
23421
  // ../../packages/core/src/file-store.ts
23076
23422
  var fs3 = __toESM(require("fs"), 1);
23077
23423
  var path3 = __toESM(require("path"), 1);
@@ -23516,6 +23862,7 @@ function parseNodeInfo(value) {
23516
23862
  lastHeartbeat: value.lastHeartbeat,
23517
23863
  transportType: typeof value.transportType === "string" ? value.transportType : void 0,
23518
23864
  devtunnelEndpoint: typeof value.devtunnelEndpoint === "string" ? value.devtunnelEndpoint : void 0,
23865
+ dashboardOrigin: typeof value.dashboardOrigin === "string" ? value.dashboardOrigin : void 0,
23519
23866
  previewOrigin: typeof value.previewOrigin === "string" ? value.previewOrigin : void 0
23520
23867
  };
23521
23868
  }
@@ -23595,6 +23942,38 @@ var EventBus = class {
23595
23942
  }
23596
23943
  };
23597
23944
 
23945
+ // ../../packages/core/src/request-auth.ts
23946
+ var requestAuthHeadersProvider = null;
23947
+ function normalizeHeaders(headers) {
23948
+ if (!headers) {
23949
+ return {};
23950
+ }
23951
+ if (headers instanceof Headers) {
23952
+ return Object.fromEntries(headers.entries());
23953
+ }
23954
+ if (Array.isArray(headers)) {
23955
+ return Object.fromEntries(headers);
23956
+ }
23957
+ return Object.fromEntries(
23958
+ Object.entries(headers).flatMap(
23959
+ ([key, value]) => value === void 0 ? [] : [[key, String(value)]]
23960
+ )
23961
+ );
23962
+ }
23963
+ function setRequestAuthHeadersProvider(provider) {
23964
+ requestAuthHeadersProvider = provider;
23965
+ }
23966
+ function applyRequestAuthHeaders(init = {}) {
23967
+ const headers = normalizeHeaders(init.headers);
23968
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === "authorization") && requestAuthHeadersProvider) {
23969
+ Object.assign(headers, requestAuthHeadersProvider());
23970
+ }
23971
+ return {
23972
+ ...init,
23973
+ headers
23974
+ };
23975
+ }
23976
+
23598
23977
  // ../../packages/core/src/node-endpoints.ts
23599
23978
  var DEFAULT_NODE_REQUEST_TIMEOUT_MS = 1500;
23600
23979
  function getNodePublicEndpoint(node) {
@@ -23623,10 +24002,10 @@ function createRequestSignal(signal, timeoutMs) {
23623
24002
  async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_NODE_REQUEST_TIMEOUT_MS) {
23624
24003
  const { cleanup, signal } = createRequestSignal(init?.signal, timeoutMs);
23625
24004
  try {
23626
- return await fetch(url, {
24005
+ return await fetch(url, applyRequestAuthHeaders({
23627
24006
  ...init,
23628
24007
  signal
23629
- });
24008
+ }));
23630
24009
  } finally {
23631
24010
  cleanup();
23632
24011
  }
@@ -23672,6 +24051,10 @@ var NodeRegistry = class {
23672
24051
  endpoint: info.endpoint
23673
24052
  });
23674
24053
  }
24054
+ upsertNode(info) {
24055
+ this.nodes.set(info.id, { ...info });
24056
+ this.store.upsertNode(info);
24057
+ }
23675
24058
  removeNode(id) {
23676
24059
  this.nodes.delete(id);
23677
24060
  this.store.removeNode(id);
@@ -23742,6 +24125,24 @@ var NodeRegistry = class {
23742
24125
  }
23743
24126
  }
23744
24127
  }
24128
+ updateDashboardOrigin(id, dashboardOrigin) {
24129
+ const node = this.nodes.get(id);
24130
+ if (node) {
24131
+ if (dashboardOrigin) {
24132
+ node.dashboardOrigin = dashboardOrigin;
24133
+ } else {
24134
+ delete node.dashboardOrigin;
24135
+ }
24136
+ this.store.upsertNode(node);
24137
+ }
24138
+ if (this.selfNode && this.selfNode.id === id) {
24139
+ if (dashboardOrigin) {
24140
+ this.selfNode.dashboardOrigin = dashboardOrigin;
24141
+ } else {
24142
+ delete this.selfNode.dashboardOrigin;
24143
+ }
24144
+ }
24145
+ }
23745
24146
  updatePreviewOrigin(id, previewOrigin) {
23746
24147
  const node = this.nodes.get(id);
23747
24148
  if (node) {
@@ -23996,11 +24397,11 @@ var LeaderElection = class {
23996
24397
  const announcePromises = onlineNodes.map(async (node) => {
23997
24398
  try {
23998
24399
  const endpoint = getNodePublicEndpoint(node);
23999
- await fetch(`${endpoint}/api/worker/announce`, {
24400
+ await fetch(`${endpoint}/api/worker/announce`, applyRequestAuthHeaders({
24000
24401
  method: "POST",
24001
24402
  headers: { "Content-Type": "application/json" },
24002
24403
  body: JSON.stringify({ leaderId, term })
24003
- });
24404
+ }));
24004
24405
  } catch {
24005
24406
  }
24006
24407
  });
@@ -24092,6 +24493,44 @@ var TaskEngine = class {
24092
24493
  const tasks = this.store.getAllTasks(filter);
24093
24494
  return { tasks, total: tasks.length };
24094
24495
  }
24496
+ importTasks(tasks) {
24497
+ let created = 0;
24498
+ let updated = 0;
24499
+ for (const task of tasks) {
24500
+ const existing = this.store.getTask(task.id);
24501
+ if (!existing) {
24502
+ this.store.createTask(task);
24503
+ created++;
24504
+ this.eventBus.emit("task.created", {
24505
+ taskId: task.id,
24506
+ title: task.title,
24507
+ priority: task.priority
24508
+ });
24509
+ this.eventBus.emit("task.status", {
24510
+ taskId: task.id,
24511
+ status: task.status,
24512
+ ...task.result ? { result: task.result } : {},
24513
+ ...task.error ? { error: task.error } : {}
24514
+ });
24515
+ continue;
24516
+ }
24517
+ if (existing.updatedAt > task.updatedAt) {
24518
+ continue;
24519
+ }
24520
+ const previousStatus = existing.status;
24521
+ this.store.updateTask(task.id, task);
24522
+ updated++;
24523
+ if (task.status !== previousStatus) {
24524
+ this.eventBus.emit("task.status", {
24525
+ taskId: task.id,
24526
+ status: task.status,
24527
+ ...task.result ? { result: task.result } : {},
24528
+ ...task.error ? { error: task.error } : {}
24529
+ });
24530
+ }
24531
+ }
24532
+ return { created, updated };
24533
+ }
24095
24534
  updateTask(id, updates) {
24096
24535
  const existing = this.store.getTask(id);
24097
24536
  if (!existing) return null;
@@ -24740,6 +25179,7 @@ var HeartbeatMonitor = class {
24740
25179
  this.nodeRegistry.updateHeartbeat(node.id, Date.now());
24741
25180
  this.nodeRegistry.updateLoad(node.id, result.load);
24742
25181
  this.applyReportedDevTunnelState(node.id, result.devtunnelEnabled, result.devtunnelEndpoint);
25182
+ this.nodeRegistry.updateDashboardOrigin(node.id, result.dashboardOrigin);
24743
25183
  this.nodeRegistry.updatePreviewOrigin(node.id, result.previewOrigin);
24744
25184
  if (result.workDirFolders) {
24745
25185
  this.nodeRegistry.updateFolders(node.id, result.workDirFolders);
@@ -24783,17 +25223,76 @@ var HeartbeatMonitor = class {
24783
25223
  load: 0,
24784
25224
  devtunnelEnabled: self.devtunnelEndpoint !== void 0,
24785
25225
  devtunnelEndpoint: self.devtunnelEndpoint,
25226
+ dashboardOrigin: self.dashboardOrigin,
24786
25227
  previewOrigin: self.previewOrigin,
24787
25228
  workDirFolders: self.workDir ? listWorkDirFolders(self.workDir) : void 0
24788
25229
  };
24789
25230
  }
24790
25231
  // ── Leader: handle keepalive from follower ──────────────────────────
24791
25232
  handleKeepalive(req) {
24792
- const { nodeId, status, load, devtunnelEnabled, devtunnelEndpoint, previewOrigin, workDirFolders } = req;
25233
+ const {
25234
+ nodeId,
25235
+ name,
25236
+ endpoint,
25237
+ status,
25238
+ load,
25239
+ capabilities,
25240
+ transportType,
25241
+ workDir,
25242
+ devtunnelEnabled,
25243
+ devtunnelEndpoint,
25244
+ dashboardOrigin,
25245
+ previewOrigin,
25246
+ workDirFolders
25247
+ } = req;
25248
+ const existingNode = this.nodeRegistry.getNode(nodeId);
25249
+ const normalizedName = name ?? existingNode?.name ?? nodeId;
25250
+ const normalizedEndpoint = endpoint ?? existingNode?.endpoint ?? devtunnelEndpoint ?? "";
25251
+ const normalizedCapabilities = capabilities ?? existingNode?.capabilities ?? [];
25252
+ const currentCapabilities = existingNode?.capabilities ?? [];
24793
25253
  this.log.debug("keepalive received", { nodeId, status });
24794
25254
  this.followerMissedMap.set(nodeId, 0);
24795
- this.nodeRegistry.updateHeartbeat(nodeId, Date.now());
24796
- const node = this.nodeRegistry.getNode(nodeId);
25255
+ let node = existingNode;
25256
+ const now = Date.now();
25257
+ if (!node) {
25258
+ this.nodeRegistry.addNode({
25259
+ id: nodeId,
25260
+ name: normalizedName,
25261
+ endpoint: normalizedEndpoint,
25262
+ role: "follower",
25263
+ status,
25264
+ capabilities: normalizedCapabilities,
25265
+ joinedAt: now,
25266
+ lastHeartbeat: now,
25267
+ ...transportType ? { transportType } : {},
25268
+ ...workDir ? { workDir } : {},
25269
+ ...devtunnelEnabled && devtunnelEndpoint ? { devtunnelEndpoint } : {},
25270
+ ...dashboardOrigin ? { dashboardOrigin } : {},
25271
+ ...previewOrigin ? { previewOrigin } : {},
25272
+ ...workDirFolders ? { workDirFolders } : {}
25273
+ });
25274
+ this.log.info("keepalive restored missing follower", { nodeId, endpoint: normalizedEndpoint });
25275
+ node = this.nodeRegistry.getNode(nodeId);
25276
+ }
25277
+ this.nodeRegistry.updateHeartbeat(nodeId, now);
25278
+ node = this.nodeRegistry.getNode(nodeId);
25279
+ if (node && normalizedEndpoint && node.endpoint !== normalizedEndpoint) {
25280
+ this.nodeRegistry.updateEndpoint(nodeId, normalizedEndpoint);
25281
+ node = this.nodeRegistry.getNode(nodeId);
25282
+ }
25283
+ if (node && (node.name !== normalizedName || node.transportType !== transportType || node.workDir !== workDir || currentCapabilities.length !== normalizedCapabilities.length || currentCapabilities.some((capability, index) => capability !== normalizedCapabilities[index]))) {
25284
+ this.nodeRegistry.upsertNode({
25285
+ ...node,
25286
+ name: normalizedName,
25287
+ endpoint: normalizedEndpoint || node.endpoint,
25288
+ status,
25289
+ lastHeartbeat: now,
25290
+ capabilities: normalizedCapabilities,
25291
+ ...transportType ? { transportType } : {},
25292
+ ...workDir ? { workDir } : {}
25293
+ });
25294
+ node = this.nodeRegistry.getNode(nodeId);
25295
+ }
24797
25296
  if (node && node.status === "offline") {
24798
25297
  this.nodeRegistry.updateStatus(nodeId, status);
24799
25298
  this.log.info("keepalive restored node", { nodeId, status });
@@ -24802,6 +25301,7 @@ var HeartbeatMonitor = class {
24802
25301
  }
24803
25302
  this.nodeRegistry.updateLoad(nodeId, load);
24804
25303
  this.applyReportedDevTunnelState(nodeId, devtunnelEnabled, devtunnelEndpoint);
25304
+ this.nodeRegistry.updateDashboardOrigin(nodeId, dashboardOrigin);
24805
25305
  this.nodeRegistry.updatePreviewOrigin(nodeId, previewOrigin);
24806
25306
  if (workDirFolders) {
24807
25307
  this.nodeRegistry.updateFolders(nodeId, workDirFolders);
@@ -24840,19 +25340,25 @@ var HeartbeatMonitor = class {
24840
25340
  const self = this.nodeRegistry.getSelf();
24841
25341
  const request = {
24842
25342
  nodeId: self.id,
25343
+ name: self.name,
25344
+ endpoint: self.endpoint,
24843
25345
  status: self.status,
24844
25346
  load: 0,
25347
+ capabilities: self.capabilities,
25348
+ transportType: self.transportType,
25349
+ workDir: self.workDir,
24845
25350
  devtunnelEnabled: self.devtunnelEndpoint !== void 0,
24846
25351
  devtunnelEndpoint: self.devtunnelEndpoint,
25352
+ dashboardOrigin: self.dashboardOrigin,
24847
25353
  previewOrigin: self.previewOrigin,
24848
25354
  workDirFolders: self.workDir ? listWorkDirFolders(self.workDir) : void 0
24849
25355
  };
24850
25356
  try {
24851
- const response = await fetch(`${leaderEndpoint}/api/worker/keepalive`, {
25357
+ const response = await fetch(`${leaderEndpoint}/api/worker/keepalive`, applyRequestAuthHeaders({
24852
25358
  method: "POST",
24853
25359
  headers: { "Content-Type": "application/json" },
24854
25360
  body: JSON.stringify(request)
24855
- });
25361
+ }));
24856
25362
  if (response.ok) {
24857
25363
  const result = await response.json();
24858
25364
  this.log.debug("keepalive ok", { nodes: result.clusterState.nodes.length });
@@ -25022,11 +25528,11 @@ var HeartbeatMonitor = class {
25022
25528
  }
25023
25529
  }
25024
25530
  try {
25025
- const res = await fetch(`${leaderEndpoint}/api/worker/control-response`, {
25531
+ const res = await fetch(`${leaderEndpoint}/api/worker/control-response`, applyRequestAuthHeaders({
25026
25532
  method: "POST",
25027
25533
  headers: { "Content-Type": "application/json" },
25028
25534
  body: JSON.stringify(response)
25029
- });
25535
+ }));
25030
25536
  if (!res.ok) {
25031
25537
  this.log.warn("leader rejected control response", {
25032
25538
  requestId: request.requestId,
@@ -25116,12 +25622,12 @@ var DataRouter = class {
25116
25622
  const controller = new AbortController();
25117
25623
  const timeoutId = setTimeout(() => controller.abort(), this.config.proxyTimeout);
25118
25624
  try {
25119
- const response = await fetch(targetUrl, {
25625
+ const response = await fetch(targetUrl, applyRequestAuthHeaders({
25120
25626
  method,
25121
25627
  headers: forwardHeaders,
25122
25628
  body,
25123
25629
  signal: controller.signal
25124
- });
25630
+ }));
25125
25631
  const responseHeaders = {};
25126
25632
  response.headers.forEach((value, key) => {
25127
25633
  responseHeaders[key] = value;
@@ -25315,11 +25821,11 @@ function forwardLeaderTaskPatch(log, nodeRegistry, taskId, data, options) {
25315
25821
  log.warn(options.missingLeaderMessage, metadata);
25316
25822
  return;
25317
25823
  }
25318
- fetch(`${leaderEndpoint}/api/tasks/${taskId}`, {
25824
+ fetch(`${leaderEndpoint}/api/tasks/${taskId}`, applyRequestAuthHeaders({
25319
25825
  method: "PATCH",
25320
25826
  headers: { "Content-Type": "application/json" },
25321
25827
  body: JSON.stringify(buildLeaderTaskPatch(data))
25322
- }).then((response) => {
25828
+ })).then((response) => {
25323
25829
  if (!response.ok) {
25324
25830
  log.warn(options.rejectedMessage, {
25325
25831
  ...metadata,
@@ -25366,11 +25872,11 @@ function forwardTaskOutputToLeader(log, nodeRegistry, taskId, event) {
25366
25872
  });
25367
25873
  return;
25368
25874
  }
25369
- fetch(`${leaderEndpoint}/api/worker/output`, {
25875
+ fetch(`${leaderEndpoint}/api/worker/output`, applyRequestAuthHeaders({
25370
25876
  method: "POST",
25371
25877
  headers: { "Content-Type": "application/json" },
25372
25878
  body: JSON.stringify({ taskId, event })
25373
- }).then((response) => {
25879
+ })).then((response) => {
25374
25880
  if (!response.ok) {
25375
25881
  log.warn("leader rejected forwarded output", {
25376
25882
  taskId,
@@ -25438,7 +25944,7 @@ function registerLeaderForwarding(eventBus, nodeRegistry, log, taskId, agent) {
25438
25944
  // ../../packages/core/src/engines/claude-code-engine.ts
25439
25945
  var fs7 = __toESM(require("fs"), 1);
25440
25946
  var path7 = __toESM(require("path"), 1);
25441
- var import_node_child_process = require("child_process");
25947
+ var import_node_child_process2 = require("child_process");
25442
25948
  function resolveClaudeInvocation() {
25443
25949
  if (process.platform !== "win32") {
25444
25950
  return { command: "claude", argsPrefix: [] };
@@ -25500,7 +26006,7 @@ var ClaudeCodeEngine = class extends ExecutionEngine {
25500
26006
  this.ensureLogDir();
25501
26007
  this.logger.info("Spawning claude CLI", { taskId: task.id, title: task.title, cwd });
25502
26008
  const invocation = resolveClaudeInvocation();
25503
- const proc = (0, import_node_child_process.spawn)(invocation.command, [...invocation.argsPrefix, ...args], {
26009
+ const proc = (0, import_node_child_process2.spawn)(invocation.command, [...invocation.argsPrefix, ...args], {
25504
26010
  cwd,
25505
26011
  stdio: ["pipe", "pipe", "pipe"]
25506
26012
  });
@@ -25610,7 +26116,7 @@ var ClaudeCodeEngine = class extends ExecutionEngine {
25610
26116
  "stream-json"
25611
26117
  ];
25612
26118
  const invocation = resolveClaudeInvocation();
25613
- const proc = (0, import_node_child_process.spawn)(invocation.command, [...invocation.argsPrefix, ...args], {
26119
+ const proc = (0, import_node_child_process2.spawn)(invocation.command, [...invocation.argsPrefix, ...args], {
25614
26120
  cwd: session.cwd,
25615
26121
  stdio: ["pipe", "pipe", "pipe"]
25616
26122
  });
@@ -25745,7 +26251,7 @@ var ClaudeCodeEngine = class extends ExecutionEngine {
25745
26251
  // ../../packages/core/src/engines/codex-engine.ts
25746
26252
  var fs8 = __toESM(require("fs"), 1);
25747
26253
  var path8 = __toESM(require("path"), 1);
25748
- var import_node_child_process2 = require("child_process");
26254
+ var import_node_child_process3 = require("child_process");
25749
26255
  function buildAssistantEvent(text, timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
25750
26256
  return {
25751
26257
  type: "assistant",
@@ -25804,7 +26310,7 @@ var CodexEngine = class extends ExecutionEngine {
25804
26310
  const args = this.buildArgs(prompt, imagePaths, { model: config.model });
25805
26311
  this.ensureLogDir();
25806
26312
  this.logger.info("Spawning codex CLI", { taskId: task.id, title: task.title, cwd });
25807
- const proc = (0, import_node_child_process2.spawn)("codex", args, {
26313
+ const proc = (0, import_node_child_process3.spawn)("codex", args, {
25808
26314
  cwd,
25809
26315
  stdio: ["ignore", "pipe", "pipe"]
25810
26316
  });
@@ -25831,7 +26337,7 @@ var CodexEngine = class extends ExecutionEngine {
25831
26337
  const { prompt, imagePaths } = this.prepareAssets(taskId, content);
25832
26338
  const args = this.buildArgs(prompt, imagePaths, { model, sessionId: session.sessionId });
25833
26339
  this.logger.info("Sending Codex follow-up message", { taskId, sessionId: session.sessionId });
25834
- const proc = (0, import_node_child_process2.spawn)("codex", args, {
26340
+ const proc = (0, import_node_child_process3.spawn)("codex", args, {
25835
26341
  cwd: session.cwd,
25836
26342
  stdio: ["ignore", "pipe", "pipe"]
25837
26343
  });
@@ -26191,16 +26697,17 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
26191
26697
  id: this.selfInfo.id,
26192
26698
  endpoint: this.selfInfo.endpoint,
26193
26699
  name: this.config.node.name,
26194
- capabilities: []
26700
+ capabilities: [],
26701
+ tasks: this.taskEngine.listTasks().tasks
26195
26702
  };
26196
26703
  if (this.selfInfo.devtunnelEndpoint) {
26197
26704
  joinBody.devtunnelEndpoint = this.selfInfo.devtunnelEndpoint;
26198
26705
  }
26199
- const response = await fetch(`${seedUrl}/api/cluster/join`, {
26706
+ const response = await fetch(`${seedUrl}/api/cluster/join`, applyRequestAuthHeaders({
26200
26707
  method: "POST",
26201
26708
  headers: { "Content-Type": "application/json" },
26202
26709
  body: JSON.stringify(joinBody)
26203
- });
26710
+ }));
26204
26711
  if (!response.ok) {
26205
26712
  const body = await response.json?.().catch(() => null);
26206
26713
  if (body?.error?.code === "NOT_LEADER") {
@@ -26212,13 +26719,36 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
26212
26719
  throw new Error(`Failed to join cluster via ${seedUrl}: ${response.status}`);
26213
26720
  }
26214
26721
  const data = await response.json();
26215
- for (const node of data.nodes) {
26216
- const existing = this.nodeRegistry.getNode(node.id);
26217
- if (!existing && node.id !== this.selfInfo.id) {
26218
- this.nodeRegistry.addNode(node);
26219
- }
26722
+ this.applyClusterState(data.nodes, data.leaderId, data.term);
26723
+ }
26724
+ async leaveCluster() {
26725
+ const leader = this.nodeRegistry.getLeader();
26726
+ const leaderEndpoint = this.nodeRegistry.getLeaderEndpoint();
26727
+ const self = this.nodeRegistry.getSelf();
26728
+ if (!leader || !leaderEndpoint || leader.id === self.id) {
26729
+ return;
26220
26730
  }
26221
- this.nodeRegistry.setLeader(data.leaderId, data.term);
26731
+ try {
26732
+ await fetch(`${leaderEndpoint}/api/cluster/leave`, applyRequestAuthHeaders({
26733
+ method: "POST",
26734
+ headers: { "Content-Type": "application/json" },
26735
+ body: JSON.stringify({ nodeId: self.id })
26736
+ }));
26737
+ } catch {
26738
+ }
26739
+ const refreshedSelf = {
26740
+ ...self,
26741
+ role: "follower",
26742
+ status: "online",
26743
+ lastHeartbeat: Date.now(),
26744
+ workDirFolders: listWorkDirFolders(this.getWorkDir())
26745
+ };
26746
+ this.selfInfo = refreshedSelf;
26747
+ this.nodeRegistry.setSelf(refreshedSelf);
26748
+ this.nodeRegistry.syncFromHeartbeat([refreshedSelf]);
26749
+ this.nodeRegistry.clearLeader();
26750
+ this.election.stepDown();
26751
+ await this.election.startElection();
26222
26752
  }
26223
26753
  async stop() {
26224
26754
  if (!this.started) return;
@@ -26228,11 +26758,11 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
26228
26758
  try {
26229
26759
  const leaderEndpoint = this.nodeRegistry.getLeaderEndpoint();
26230
26760
  if (leaderEndpoint) {
26231
- await fetch(`${leaderEndpoint}/api/cluster/leave`, {
26761
+ await fetch(`${leaderEndpoint}/api/cluster/leave`, applyRequestAuthHeaders({
26232
26762
  method: "POST",
26233
26763
  headers: { "Content-Type": "application/json" },
26234
26764
  body: JSON.stringify({ nodeId: this.selfInfo.id })
26235
- });
26765
+ }));
26236
26766
  }
26237
26767
  } catch {
26238
26768
  }
@@ -26313,11 +26843,11 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
26313
26843
  const leaderEndpoint = this.nodeRegistry.getLeaderEndpoint();
26314
26844
  if (leaderEndpoint && !this.nodeRegistry.isLeader()) {
26315
26845
  try {
26316
- await fetch(`${leaderEndpoint}/api/nodes/${this.selfInfo.id}`, {
26846
+ await fetch(`${leaderEndpoint}/api/nodes/${this.selfInfo.id}`, applyRequestAuthHeaders({
26317
26847
  method: "PATCH",
26318
26848
  headers: { "Content-Type": "application/json" },
26319
26849
  body: JSON.stringify({ endpoint: newEndpoint })
26320
- });
26850
+ }));
26321
26851
  } catch {
26322
26852
  }
26323
26853
  }
@@ -26381,6 +26911,22 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
26381
26911
  this.selfInfo.role = current.role;
26382
26912
  this.selfInfo.status = current.status;
26383
26913
  }
26914
+ applyClusterState(nodes, leaderId, term) {
26915
+ const remoteSelf = nodes.find((node) => node.id === this.selfInfo.id);
26916
+ const nextSelf = {
26917
+ ...this.selfInfo,
26918
+ ...remoteSelf ?? {},
26919
+ transportType: this.config.transport.type,
26920
+ workDir: this.selfInfo.workDir,
26921
+ workDirFolders: listWorkDirFolders(this.getWorkDir()),
26922
+ ...this.selfInfo.devtunnelEndpoint ? { devtunnelEndpoint: this.selfInfo.devtunnelEndpoint } : {},
26923
+ ...this.selfInfo.previewOrigin ? { previewOrigin: this.selfInfo.previewOrigin } : {}
26924
+ };
26925
+ this.selfInfo = nextSelf;
26926
+ this.nodeRegistry.setSelf(nextSelf);
26927
+ this.nodeRegistry.syncFromHeartbeat(nodes);
26928
+ this.election.handleLeaderAnnounce({ leaderId, term });
26929
+ }
26384
26930
  cloneTransportConfig(config) {
26385
26931
  return {
26386
26932
  type: config.type,
@@ -30609,14 +31155,36 @@ var NodeInfoSchema = external_exports.object({
30609
31155
  lastHeartbeat: external_exports.number(),
30610
31156
  transportType: external_exports.string().optional(),
30611
31157
  devtunnelEndpoint: external_exports.string().optional(),
31158
+ dashboardOrigin: external_exports.string().optional(),
30612
31159
  previewOrigin: external_exports.string().optional()
30613
31160
  });
31161
+ var JoinTaskSchema = external_exports.object({
31162
+ id: external_exports.string(),
31163
+ title: external_exports.string(),
31164
+ description: external_exports.string(),
31165
+ agent: external_exports.string(),
31166
+ project: external_exports.string().nullable(),
31167
+ effectiveProjectPath: external_exports.string().nullable(),
31168
+ payload: external_exports.record(external_exports.unknown()),
31169
+ status: external_exports.enum(["pending", "assigned", "running", "completed", "failed", "cancelled", "archived"]),
31170
+ priority: external_exports.enum(["low", "normal", "high", "critical"]),
31171
+ assignedTo: external_exports.string().nullable(),
31172
+ assignedNodeName: external_exports.string().nullable().optional(),
31173
+ createdBy: external_exports.string(),
31174
+ result: external_exports.record(external_exports.unknown()).nullable(),
31175
+ error: external_exports.string().nullable(),
31176
+ retryCount: external_exports.number(),
31177
+ maxRetries: external_exports.number(),
31178
+ createdAt: external_exports.number(),
31179
+ updatedAt: external_exports.number()
31180
+ });
30614
31181
  var JoinClusterBody = external_exports.object({
30615
31182
  id: external_exports.string().min(1),
30616
31183
  endpoint: external_exports.string().url(),
30617
31184
  name: external_exports.string().min(1),
30618
31185
  capabilities: external_exports.array(external_exports.string()).default([]),
30619
- devtunnelEndpoint: external_exports.string().url().optional()
31186
+ devtunnelEndpoint: external_exports.string().url().optional(),
31187
+ tasks: external_exports.array(JoinTaskSchema).default([])
30620
31188
  });
30621
31189
  var JoinClusterResponse = external_exports.object({
30622
31190
  clusterId: external_exports.string(),
@@ -30640,6 +31208,10 @@ var ElectionBody = external_exports.object({
30640
31208
  candidateId: external_exports.string(),
30641
31209
  term: external_exports.number().int().min(1)
30642
31210
  });
31211
+ var ConnectClusterBody = external_exports.object({
31212
+ leaderEndpoint: external_exports.string().url(),
31213
+ transport: external_exports.literal("devtunnel")
31214
+ });
30643
31215
 
30644
31216
  // ../../packages/api/src/schemas/nodes.ts
30645
31217
  var NodeInfoSchema2 = external_exports.object({
@@ -30653,6 +31225,7 @@ var NodeInfoSchema2 = external_exports.object({
30653
31225
  lastHeartbeat: external_exports.number(),
30654
31226
  transportType: external_exports.string().optional(),
30655
31227
  devtunnelEndpoint: external_exports.string().optional(),
31228
+ dashboardOrigin: external_exports.string().optional(),
30656
31229
  previewOrigin: external_exports.string().optional()
30657
31230
  });
30658
31231
  var NodeListQuery = external_exports.object({
@@ -30707,6 +31280,7 @@ var TaskListResponse = external_exports.object({
30707
31280
  priority: external_exports.enum(["low", "normal", "high", "critical"]),
30708
31281
  assignedTo: external_exports.string().nullable(),
30709
31282
  assignedNodeName: external_exports.string().nullable().optional(),
31283
+ assignedNodeAvailable: external_exports.boolean().optional(),
30710
31284
  createdBy: external_exports.string(),
30711
31285
  result: external_exports.record(external_exports.unknown()).nullable(),
30712
31286
  error: external_exports.string().nullable(),
@@ -30778,13 +31352,113 @@ var import_express7 = __toESM(require_express2(), 1);
30778
31352
 
30779
31353
  // ../../packages/api/src/middleware/auth.ts
30780
31354
  var SKIP_AUTH_PATHS = ["/api/system/health"];
31355
+ var TRUSTED_LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
31356
+ function normalizeHost(value) {
31357
+ if (!value) return void 0;
31358
+ const trimmed = value.trim().toLowerCase();
31359
+ if (!trimmed) return void 0;
31360
+ return trimmed.replace(/:\d+$/, "");
31361
+ }
31362
+ function collectHosts(candidates) {
31363
+ const unique = /* @__PURE__ */ new Set();
31364
+ for (const candidate of candidates) {
31365
+ if (!candidate) continue;
31366
+ for (const item of candidate.split(",")) {
31367
+ const normalized = normalizeHost(item);
31368
+ if (normalized) {
31369
+ unique.add(normalized);
31370
+ }
31371
+ }
31372
+ }
31373
+ return [...unique];
31374
+ }
31375
+ function getForwardedHosts(req) {
31376
+ const forwardedHosts = collectHosts([
31377
+ typeof req.headers["x-forwarded-host"] === "string" ? req.headers["x-forwarded-host"] : void 0
31378
+ ]);
31379
+ if (forwardedHosts.length > 0) {
31380
+ return forwardedHosts;
31381
+ }
31382
+ const forwardedHeader = typeof req.headers.forwarded === "string" ? req.headers.forwarded : void 0;
31383
+ if (!forwardedHeader) {
31384
+ return [];
31385
+ }
31386
+ const forwardedMatches = [...forwardedHeader.matchAll(/host=([^;,\s]+)/gi)];
31387
+ return collectHosts(forwardedMatches.map((match) => match[1]?.replace(/^"|"$/g, "")));
31388
+ }
31389
+ function getDirectHosts(req) {
31390
+ return collectHosts([
31391
+ req.hostname,
31392
+ typeof req.headers.host === "string" ? req.headers.host : void 0
31393
+ ]);
31394
+ }
31395
+ function getRequestHosts(req) {
31396
+ const forwardedHosts = getForwardedHosts(req);
31397
+ if (forwardedHosts.length > 0) {
31398
+ return forwardedHosts;
31399
+ }
31400
+ return getDirectHosts(req);
31401
+ }
31402
+ function isTrustedDashboardRequest(req, config) {
31403
+ const trustedHosts = new Set((config.getTrustedHosts?.() ?? []).map((host) => normalizeHost(host)).filter(Boolean));
31404
+ for (const host of getRequestHosts(req)) {
31405
+ if (TRUSTED_LOCAL_HOSTS.has(host) || trustedHosts.has(host)) {
31406
+ return true;
31407
+ }
31408
+ }
31409
+ return false;
31410
+ }
31411
+ function getAuthLogger(req) {
31412
+ const logger = req.app?.locals?.deps?.logger;
31413
+ if (logger && typeof logger.child === "function") {
31414
+ return logger.child("api/auth");
31415
+ }
31416
+ return {
31417
+ debug() {
31418
+ },
31419
+ warn() {
31420
+ }
31421
+ };
31422
+ }
30781
31423
  function createAuthMiddleware(config) {
30782
31424
  return (req, _res, next) => {
30783
- if (!config.apiKey) {
31425
+ const log = getAuthLogger(req);
31426
+ if (SKIP_AUTH_PATHS.includes(req.path)) {
30784
31427
  next();
30785
31428
  return;
30786
31429
  }
30787
- if (SKIP_AUTH_PATHS.includes(req.path)) {
31430
+ if (config.validateBearerToken) {
31431
+ if (isTrustedDashboardRequest(req, config)) {
31432
+ log.debug("skipping bearer auth for trusted dashboard request", {
31433
+ path: req.path,
31434
+ method: req.method,
31435
+ hosts: getRequestHosts(req)
31436
+ });
31437
+ next();
31438
+ return;
31439
+ }
31440
+ const result = config.validateBearerToken(req.headers.authorization);
31441
+ if (!result.ok) {
31442
+ log.warn("rejected bearer-authenticated request", {
31443
+ path: req.path,
31444
+ method: req.method,
31445
+ hosts: getRequestHosts(req),
31446
+ reason: result.reason
31447
+ });
31448
+ throw new MeshyError("VALIDATION_ERROR", "Invalid or missing bearer token", 401, {
31449
+ reason: result.reason
31450
+ });
31451
+ }
31452
+ log.debug("accepted bearer-authenticated request", {
31453
+ path: req.path,
31454
+ method: req.method,
31455
+ hosts: getRequestHosts(req),
31456
+ reason: result.reason
31457
+ });
31458
+ next();
31459
+ return;
31460
+ }
31461
+ if (!config.apiKey) {
30788
31462
  next();
30789
31463
  return;
30790
31464
  }
@@ -30885,13 +31559,32 @@ function createErrorMiddleware() {
30885
31559
 
30886
31560
  // ../../packages/api/src/routes/cluster.ts
30887
31561
  var import_express = __toESM(require_express2(), 1);
31562
+ var CONNECTIVITY_TIMEOUT_MS = 8e3;
31563
+ async function verifyHealth(endpoint) {
31564
+ const controller = new AbortController();
31565
+ const timer = setTimeout(() => controller.abort(), CONNECTIVITY_TIMEOUT_MS);
31566
+ try {
31567
+ const response = await fetch(`${endpoint}/api/system/health`, {
31568
+ signal: controller.signal
31569
+ });
31570
+ if (!response.ok) {
31571
+ throw new Error(`health check returned ${response.status}`);
31572
+ }
31573
+ } catch (err) {
31574
+ throw new Error(
31575
+ `Failed to verify connectivity for ${endpoint}: ${err instanceof Error ? err.message : String(err)}`
31576
+ );
31577
+ } finally {
31578
+ clearTimeout(timer);
31579
+ }
31580
+ }
30888
31581
  function asyncHandler(fn) {
30889
31582
  return (req, res, next) => fn(req, res, next).catch(next);
30890
31583
  }
30891
31584
  function createClusterRoutes() {
30892
31585
  const router = (0, import_express.Router)();
30893
31586
  router.post("/join", asyncHandler(async (req, res) => {
30894
- const { nodeRegistry, election } = req.app.locals.deps;
31587
+ const { nodeRegistry, election, taskEngine } = req.app.locals.deps;
30895
31588
  if (!election.isLeader()) {
30896
31589
  const leaderEndpoint = nodeRegistry.getLeaderEndpoint();
30897
31590
  res.status(421).json({
@@ -30922,6 +31615,7 @@ function createClusterRoutes() {
30922
31615
  ...body.devtunnelEndpoint && { devtunnelEndpoint: body.devtunnelEndpoint }
30923
31616
  };
30924
31617
  nodeRegistry.addNode(node);
31618
+ taskEngine.importTasks(body.tasks ?? []);
30925
31619
  const clusterState = nodeRegistry.getClusterState();
30926
31620
  res.status(201).json({
30927
31621
  clusterId: clusterState.clusterId ?? nanoid(),
@@ -30930,13 +31624,50 @@ function createClusterRoutes() {
30930
31624
  nodes: clusterState.nodes
30931
31625
  });
30932
31626
  }));
31627
+ router.post("/connect", asyncHandler(async (req, res) => {
31628
+ const {
31629
+ nodeRegistry,
31630
+ joinCurrentNodeToCluster
31631
+ } = req.app.locals.deps;
31632
+ const body = ConnectClusterBody.parse(req.body);
31633
+ if (!joinCurrentNodeToCluster) {
31634
+ throw new MeshyError("VALIDATION_ERROR", "Join flow is not available on this node", 501);
31635
+ }
31636
+ await verifyHealth(body.leaderEndpoint);
31637
+ await joinCurrentNodeToCluster(body.leaderEndpoint);
31638
+ const self = nodeRegistry.getSelf();
31639
+ const clusterState = nodeRegistry.getClusterState();
31640
+ res.json({
31641
+ ok: true,
31642
+ transport: body.transport,
31643
+ self,
31644
+ leaderId: clusterState.leaderId,
31645
+ term: clusterState.term,
31646
+ nodes: clusterState.nodes
31647
+ });
31648
+ }));
30933
31649
  router.post("/leave", asyncHandler(async (req, res) => {
30934
- const { nodeRegistry, eventBus } = req.app.locals.deps;
31650
+ const { nodeRegistry } = req.app.locals.deps;
30935
31651
  const { nodeId } = req.body;
30936
31652
  nodeRegistry.removeNode(nodeId);
30937
- eventBus.emit("node.left", { nodeId, reason: "left" });
30938
31653
  res.json({ ok: true });
30939
31654
  }));
31655
+ router.post("/disconnect", asyncHandler(async (req, res) => {
31656
+ const { leaveCurrentCluster, nodeRegistry } = req.app.locals.deps;
31657
+ if (!leaveCurrentCluster) {
31658
+ throw new MeshyError("VALIDATION_ERROR", "Leave flow is not available on this node", 501);
31659
+ }
31660
+ await leaveCurrentCluster();
31661
+ const self = nodeRegistry.getSelf();
31662
+ const clusterState = nodeRegistry.getClusterState();
31663
+ res.json({
31664
+ ok: true,
31665
+ self,
31666
+ leaderId: clusterState.leaderId,
31667
+ term: clusterState.term,
31668
+ nodes: clusterState.nodes
31669
+ });
31670
+ }));
30940
31671
  router.get("/state", asyncHandler(async (req, res) => {
30941
31672
  const { nodeRegistry, taskEngine } = req.app.locals.deps;
30942
31673
  const clusterState = nodeRegistry.getClusterState();
@@ -31419,10 +32150,10 @@ function readFileContent(root, relativePath) {
31419
32150
  }
31420
32151
 
31421
32152
  // ../../packages/api/src/output/git-diff.ts
31422
- var import_node_child_process3 = require("child_process");
32153
+ var import_node_child_process4 = require("child_process");
31423
32154
  function git(args, cwd) {
31424
32155
  try {
31425
- return (0, import_node_child_process3.execFileSync)("git", ["-C", cwd, ...args], {
32156
+ return (0, import_node_child_process4.execFileSync)("git", ["-C", cwd, ...args], {
31426
32157
  encoding: "utf-8",
31427
32158
  timeout: 1e4,
31428
32159
  stdio: ["pipe", "pipe", "pipe"]
@@ -31615,7 +32346,8 @@ async function executeWorkerControlRequest(deps, request) {
31615
32346
  return jsonResponse(request.requestId, 200, {
31616
32347
  ok: true,
31617
32348
  enabled: true,
31618
- devtunnelEndpoint
32349
+ devtunnelEndpoint,
32350
+ dashboardOrigin: deps.nodeRegistry.getSelf().dashboardOrigin
31619
32351
  });
31620
32352
  }
31621
32353
  if (!deps.disableDevTunnel) {
@@ -31624,7 +32356,8 @@ async function executeWorkerControlRequest(deps, request) {
31624
32356
  await deps.disableDevTunnel();
31625
32357
  return jsonResponse(request.requestId, 200, {
31626
32358
  ok: true,
31627
- enabled: false
32359
+ enabled: false,
32360
+ dashboardOrigin: deps.nodeRegistry.getSelf().dashboardOrigin
31628
32361
  });
31629
32362
  }
31630
32363
  case "task-cancel": {
@@ -31826,7 +32559,7 @@ function createNodeRoutes() {
31826
32559
  res.json(node);
31827
32560
  }));
31828
32561
  router.delete("/:id", asyncHandler2(async (req, res) => {
31829
- const { nodeRegistry, eventBus, election } = req.app.locals.deps;
32562
+ const { nodeRegistry, election } = req.app.locals.deps;
31830
32563
  if (!election.isLeader()) {
31831
32564
  throw new MeshyError("NOT_LEADER", "Only the leader node can delete nodes", 403);
31832
32565
  }
@@ -31835,7 +32568,6 @@ function createNodeRoutes() {
31835
32568
  throw new MeshyError("NODE_NOT_FOUND", `Node ${req.params.id} not found`, 404);
31836
32569
  }
31837
32570
  nodeRegistry.removeNode(req.params.id);
31838
- eventBus.emit("node.left", { nodeId: req.params.id, reason: "deleted" });
31839
32571
  res.json({ ok: true });
31840
32572
  }));
31841
32573
  router.post("/:id/devtunnel", asyncHandler2(async (req, res) => {
@@ -31859,10 +32591,19 @@ function createNodeRoutes() {
31859
32591
  }
31860
32592
  if (enabled) {
31861
32593
  const devtunnelEndpoint = await enableDevTunnel();
31862
- res.json({ ok: true, enabled: true, devtunnelEndpoint });
32594
+ res.json({
32595
+ ok: true,
32596
+ enabled: true,
32597
+ devtunnelEndpoint,
32598
+ dashboardOrigin: nodeRegistry.getSelf().dashboardOrigin
32599
+ });
31863
32600
  } else {
31864
32601
  await disableDevTunnel();
31865
- res.json({ ok: true, enabled: false });
32602
+ res.json({
32603
+ ok: true,
32604
+ enabled: false,
32605
+ dashboardOrigin: nodeRegistry.getSelf().dashboardOrigin
32606
+ });
31866
32607
  }
31867
32608
  } else {
31868
32609
  const node = nodeRegistry.getNode(targetId);
@@ -31901,6 +32642,7 @@ function createNodeRoutes() {
31901
32642
  if (result?.ok) {
31902
32643
  if (nodeRegistry.getNode(targetId)) {
31903
32644
  nodeRegistry.updateDevTunnelEndpoint(targetId, result.devtunnelEndpoint);
32645
+ nodeRegistry.updateDashboardOrigin(targetId, result.dashboardOrigin);
31904
32646
  }
31905
32647
  }
31906
32648
  sendWorkerControlResponse(res, controlResponse);
@@ -31916,6 +32658,7 @@ function createNodeRoutes() {
31916
32658
  const updatedNode = nodeRegistry.getNode(targetId);
31917
32659
  if (updatedNode) {
31918
32660
  nodeRegistry.updateDevTunnelEndpoint(targetId, result.devtunnelEndpoint);
32661
+ nodeRegistry.updateDashboardOrigin(targetId, result.dashboardOrigin);
31919
32662
  }
31920
32663
  res.json(result);
31921
32664
  }
@@ -32042,17 +32785,15 @@ var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned", "running"]
32042
32785
  function asyncHandler3(fn) {
32043
32786
  return (req, res, next) => fn(req, res, next).catch(next);
32044
32787
  }
32045
- function withAssignedNodeName(task, nodeRegistry) {
32046
- if (task.assignedNodeName || !task.assignedTo) {
32788
+ function withAssignedNodeMetadata(task, nodeRegistry) {
32789
+ if (!task.assignedTo) {
32047
32790
  return task;
32048
32791
  }
32049
32792
  const node = nodeRegistry.getNode(task.assignedTo);
32050
- if (!node?.name) {
32051
- return task;
32052
- }
32053
32793
  return {
32054
32794
  ...task,
32055
- assignedNodeName: node.name
32795
+ assignedNodeName: task.assignedNodeName ?? node?.name ?? null,
32796
+ assignedNodeAvailable: node?.status !== "offline" && Boolean(node)
32056
32797
  };
32057
32798
  }
32058
32799
  function createTaskRoutes() {
@@ -32071,10 +32812,10 @@ function createTaskRoutes() {
32071
32812
  });
32072
32813
  if (body.assignTo) {
32073
32814
  const assigned = taskEngine.assignTask(task.id, body.assignTo);
32074
- res.status(201).json(withAssignedNodeName(assigned, nodeRegistry));
32815
+ res.status(201).json(withAssignedNodeMetadata(assigned, nodeRegistry));
32075
32816
  return;
32076
32817
  }
32077
- res.status(201).json(withAssignedNodeName(task, nodeRegistry));
32818
+ res.status(201).json(withAssignedNodeMetadata(task, nodeRegistry));
32078
32819
  }));
32079
32820
  router.get("/", asyncHandler3(async (req, res) => {
32080
32821
  const { taskEngine, nodeRegistry } = req.app.locals.deps;
@@ -32087,7 +32828,7 @@ function createTaskRoutes() {
32087
32828
  if (query.offset) filter.offset = query.offset;
32088
32829
  const result = taskEngine.listTasks(filter);
32089
32830
  res.json({
32090
- tasks: result.tasks.map((task) => withAssignedNodeName(task, nodeRegistry)),
32831
+ tasks: result.tasks.map((task) => withAssignedNodeMetadata(task, nodeRegistry)),
32091
32832
  total: result.total
32092
32833
  });
32093
32834
  }));
@@ -32128,7 +32869,7 @@ function createTaskRoutes() {
32128
32869
  if (!task) {
32129
32870
  throw new MeshyError("TASK_NOT_FOUND", `Task ${req.params.id} not found`, 404);
32130
32871
  }
32131
- res.json(withAssignedNodeName(task, nodeRegistry));
32872
+ res.json(withAssignedNodeMetadata(task, nodeRegistry));
32132
32873
  }));
32133
32874
  router.patch("/:id", asyncHandler3(async (req, res) => {
32134
32875
  const { taskEngine, nodeRegistry, logger: rootLogger } = req.app.locals.deps;
@@ -32144,7 +32885,7 @@ function createTaskRoutes() {
32144
32885
  currentStatus: existing.status,
32145
32886
  incomingStatus: updates.status
32146
32887
  });
32147
- res.json(withAssignedNodeName(existing, nodeRegistry));
32888
+ res.json(withAssignedNodeMetadata(existing, nodeRegistry));
32148
32889
  return;
32149
32890
  }
32150
32891
  if (updates.status === "archived" && !ARCHIVABLE_STATUSES.has(existing.status)) {
@@ -32160,14 +32901,14 @@ function createTaskRoutes() {
32160
32901
  currentStatus: existing.status,
32161
32902
  incomingStatus: updates.status
32162
32903
  });
32163
- res.json(withAssignedNodeName(existing, nodeRegistry));
32904
+ res.json(withAssignedNodeMetadata(existing, nodeRegistry));
32164
32905
  return;
32165
32906
  }
32166
32907
  const task = taskEngine.updateTask(req.params.id, updates);
32167
32908
  if (!task) {
32168
32909
  throw new MeshyError("TASK_NOT_FOUND", `Task ${req.params.id} not found`, 404);
32169
32910
  }
32170
- res.json(withAssignedNodeName(task, nodeRegistry));
32911
+ res.json(withAssignedNodeMetadata(task, nodeRegistry));
32171
32912
  }));
32172
32913
  router.delete("/:id", asyncHandler3(async (req, res) => {
32173
32914
  const { taskEngine } = req.app.locals.deps;
@@ -32218,12 +32959,12 @@ function createTaskRoutes() {
32218
32959
  const { taskEngine, nodeRegistry } = req.app.locals.deps;
32219
32960
  const body = AssignTaskBody.parse(req.body);
32220
32961
  const task = taskEngine.assignTask(req.params.id, body.nodeId);
32221
- res.json(withAssignedNodeName(task, nodeRegistry));
32962
+ res.json(withAssignedNodeMetadata(task, nodeRegistry));
32222
32963
  }));
32223
32964
  router.post("/:id/retry", asyncHandler3(async (req, res) => {
32224
32965
  const { taskEngine, nodeRegistry } = req.app.locals.deps;
32225
32966
  const task = taskEngine.retryTask(req.params.id);
32226
- res.json(withAssignedNodeName(task, nodeRegistry));
32967
+ res.json(withAssignedNodeMetadata(task, nodeRegistry));
32227
32968
  }));
32228
32969
  router.get("/:id/logs", asyncHandler3(async (req, res) => {
32229
32970
  const { engineRegistry, taskEngine, nodeRegistry, logger: rootLogger } = req.app.locals.deps;
@@ -32308,26 +33049,18 @@ function createTaskRoutes() {
32308
33049
  }
32309
33050
  if (task.assignedTo && task.assignedTo !== nodeRegistry.getSelf()?.id) {
32310
33051
  const node = nodeRegistry.getNode(task.assignedTo);
32311
- const selfId = nodeRegistry.getSelf()?.id ?? null;
32312
33052
  if (!node || node.status === "offline") {
32313
- log.warn("assigned worker unavailable, resuming follow-up locally", {
33053
+ log.warn("assigned worker unavailable for follow-up", {
32314
33054
  taskId: task.id,
32315
- assignedTo: task.assignedTo,
32316
- nodeFound: Boolean(node),
32317
- nodeStatus: node?.status,
32318
- reassignedTo: selfId
32319
- });
32320
- startLocalTaskFollowUp({
32321
- taskEngine,
32322
- engineRegistry,
32323
- logger: log
32324
- }, task, body.content, selfId, {
32325
33055
  assignedTo: task.assignedTo,
32326
33056
  nodeFound: Boolean(node),
32327
33057
  nodeStatus: node?.status
32328
33058
  });
32329
- res.json({ ok: true, reassigned: true });
32330
- return;
33059
+ throw new MeshyError(
33060
+ "NODE_OFFLINE",
33061
+ `Task ${task.id} is unavailable because ${task.assignedNodeName ?? task.assignedTo} is no longer reachable`,
33062
+ 409
33063
+ );
32331
33064
  }
32332
33065
  taskEngine.updateTask(task.id, { status: "running" });
32333
33066
  let workerEndpoint = node.endpoint;
@@ -32858,13 +33591,19 @@ function createSystemRoutes() {
32858
33591
  res.json({ status: "ok", timestamp: Date.now() });
32859
33592
  }));
32860
33593
  router.get("/info", asyncHandler5(async (req, res) => {
32861
- const { nodeRegistry, getTransportType } = req.app.locals.deps;
33594
+ const { nodeRegistry, getTransportType, config, dashboardOrigin } = req.app.locals.deps;
32862
33595
  const self = nodeRegistry.getSelf();
32863
33596
  res.json({
32864
33597
  ...self,
32865
33598
  version: "0.1.0",
32866
33599
  uptime: process.uptime(),
32867
- transportType: getTransportType?.() ?? "direct"
33600
+ transportType: getTransportType?.() ?? "direct",
33601
+ dashboardOrigin: dashboardOrigin ?? self.dashboardOrigin,
33602
+ auth: config.authMetadata ?? {
33603
+ enabled: false,
33604
+ allowSameTenant: false,
33605
+ allowedUsers: []
33606
+ }
32868
33607
  });
32869
33608
  }));
32870
33609
  router.post("/transport", asyncHandler5(async (req, res) => {
@@ -32895,10 +33634,19 @@ function createSystemRoutes() {
32895
33634
  assertCanChangeNodeDevTunnel(taskEngine, nodeRegistry.getSelf().id);
32896
33635
  if (enabled) {
32897
33636
  const devtunnelEndpoint = await enableDevTunnel();
32898
- res.json({ ok: true, enabled: true, devtunnelEndpoint });
33637
+ res.json({
33638
+ ok: true,
33639
+ enabled: true,
33640
+ devtunnelEndpoint,
33641
+ dashboardOrigin: nodeRegistry.getSelf().dashboardOrigin
33642
+ });
32899
33643
  } else {
32900
33644
  await disableDevTunnel();
32901
- res.json({ ok: true, enabled: false });
33645
+ res.json({
33646
+ ok: true,
33647
+ enabled: false,
33648
+ dashboardOrigin: nodeRegistry.getSelf().dashboardOrigin
33649
+ });
32902
33650
  }
32903
33651
  }));
32904
33652
  router.get("/metrics", asyncHandler5(async (req, res) => {
@@ -33028,13 +33776,39 @@ function createServer2(deps) {
33028
33776
  if (typeof deps.heartbeat.setControlRequestHandler === "function") {
33029
33777
  deps.heartbeat.setControlRequestHandler((request) => executeWorkerControlRequest(deps, request));
33030
33778
  }
33779
+ const authConfig = {
33780
+ apiKey: deps.config.apiKey,
33781
+ validateBearerToken: deps.config.validateBearerToken,
33782
+ getTrustedHosts: () => {
33783
+ const trustedHosts = [];
33784
+ if (deps.dashboardOrigin) {
33785
+ try {
33786
+ trustedHosts.push(new URL(deps.dashboardOrigin).host);
33787
+ } catch {
33788
+ }
33789
+ }
33790
+ return trustedHosts;
33791
+ }
33792
+ };
33793
+ const isApiRequest = (req) => req.path === "/api" || req.path.startsWith("/api/");
33794
+ const canServeDashboard = (req) => !deps.config.validateBearerToken || isTrustedDashboardRequest(req, authConfig);
33031
33795
  const runtimeBaseDir = resolveRuntimeBaseDir();
33032
33796
  const staticDir = resolveStaticDir(runtimeBaseDir);
33033
33797
  if (staticDir) {
33034
- app.use(import_express7.default.static(staticDir));
33798
+ const staticMiddleware = import_express7.default.static(staticDir);
33799
+ app.use((req, res, next) => {
33800
+ if (isApiRequest(req)) {
33801
+ next();
33802
+ return;
33803
+ }
33804
+ if (!canServeDashboard(req)) {
33805
+ res.status(404).type("text/plain").send("Not Found");
33806
+ return;
33807
+ }
33808
+ staticMiddleware(req, res, next);
33809
+ });
33035
33810
  }
33036
33811
  app.use(import_express7.default.json({ limit: JSON_BODY_LIMIT }));
33037
- const authConfig = { apiKey: deps.config.apiKey };
33038
33812
  app.use(createAuthMiddleware(authConfig));
33039
33813
  app.use(createRoutingMiddleware(deps.dataRouter));
33040
33814
  const largeBodyParser = import_express7.default.json({ limit: JSON_BODY_LIMIT_LARGE });
@@ -33047,7 +33821,15 @@ function createServer2(deps) {
33047
33821
  if (staticDir) {
33048
33822
  const indexPath = path15.join(staticDir, "index.html");
33049
33823
  if (fs15.existsSync(indexPath)) {
33050
- app.get("*", (_req, res) => {
33824
+ app.get("*", (req, res, next) => {
33825
+ if (isApiRequest(req)) {
33826
+ next();
33827
+ return;
33828
+ }
33829
+ if (!canServeDashboard(req)) {
33830
+ res.status(404).type("text/plain").send("Not Found");
33831
+ return;
33832
+ }
33051
33833
  res.sendFile(indexPath);
33052
33834
  });
33053
33835
  }
@@ -33122,10 +33904,10 @@ var DirectTransport = class {
33122
33904
  };
33123
33905
 
33124
33906
  // ../../packages/transport/src/devtunnel.ts
33125
- var import_node_child_process4 = require("child_process");
33907
+ var import_node_child_process5 = require("child_process");
33126
33908
  function isInstalled(cmd) {
33127
33909
  try {
33128
- (0, import_node_child_process4.execSync)(process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`, { stdio: "pipe" });
33910
+ (0, import_node_child_process5.execSync)(process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`, { stdio: "pipe" });
33129
33911
  return true;
33130
33912
  } catch {
33131
33913
  return false;
@@ -33148,14 +33930,14 @@ var DevTunnelTransport = class {
33148
33930
  );
33149
33931
  }
33150
33932
  try {
33151
- (0, import_node_child_process4.execSync)("devtunnel user show", { stdio: "pipe" });
33933
+ (0, import_node_child_process5.execSync)("devtunnel user show", { stdio: "pipe" });
33152
33934
  } catch {
33153
33935
  throw new Error(
33154
33936
  "Not logged in to devtunnel. Run: devtunnel user login"
33155
33937
  );
33156
33938
  }
33157
33939
  const hostArgs = this.buildHostArgs(localPort);
33158
- const child = (0, import_node_child_process4.spawn)("devtunnel", hostArgs, {
33940
+ const child = (0, import_node_child_process5.spawn)("devtunnel", hostArgs, {
33159
33941
  stdio: ["pipe", "pipe", "pipe"]
33160
33942
  });
33161
33943
  this.process = child;
@@ -33260,7 +34042,7 @@ ${lines.join("")}`
33260
34042
  return void 0;
33261
34043
  }
33262
34044
  try {
33263
- (0, import_node_child_process4.execFileSync)("devtunnel", ["show", tunnelId], { stdio: "pipe" });
34045
+ (0, import_node_child_process5.execFileSync)("devtunnel", ["show", tunnelId], { stdio: "pipe" });
33264
34046
  return tunnelId;
33265
34047
  } catch {
33266
34048
  const createArgs = ["create", tunnelId];
@@ -33268,7 +34050,7 @@ ${lines.join("")}`
33268
34050
  createArgs.push("-a");
33269
34051
  }
33270
34052
  try {
33271
- (0, import_node_child_process4.execFileSync)("devtunnel", createArgs, { stdio: "pipe" });
34053
+ (0, import_node_child_process5.execFileSync)("devtunnel", createArgs, { stdio: "pipe" });
33272
34054
  return tunnelId;
33273
34055
  } catch (err) {
33274
34056
  throw new Error(
@@ -33283,7 +34065,7 @@ ${lines.join("")}`
33283
34065
  return;
33284
34066
  }
33285
34067
  try {
33286
- (0, import_node_child_process4.execFileSync)(
34068
+ (0, import_node_child_process5.execFileSync)(
33287
34069
  "devtunnel",
33288
34070
  ["port", "create", tunnelId, "-p", String(localPort), "--protocol", "http"],
33289
34071
  { stdio: "pipe" }
@@ -33299,7 +34081,7 @@ ${lines.join("")}`
33299
34081
  }
33300
34082
  listTunnelPorts(tunnelId) {
33301
34083
  try {
33302
- const output = (0, import_node_child_process4.execFileSync)("devtunnel", ["port", "list", tunnelId, "-j"], { stdio: "pipe" });
34084
+ const output = (0, import_node_child_process5.execFileSync)("devtunnel", ["port", "list", tunnelId, "-j"], { stdio: "pipe" });
33303
34085
  const parsed = JSON.parse(output.toString());
33304
34086
  const items = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.ports) ? parsed.ports : parsed && typeof parsed === "object" && Array.isArray(parsed.value) ? parsed.value : [];
33305
34087
  return items.map((item) => getPortNumber(item)).filter((value) => value !== void 0);
@@ -33311,7 +34093,7 @@ ${lines.join("")}`
33311
34093
  }
33312
34094
  hasTunnelPort(tunnelId, localPort) {
33313
34095
  try {
33314
- (0, import_node_child_process4.execFileSync)(
34096
+ (0, import_node_child_process5.execFileSync)(
33315
34097
  "devtunnel",
33316
34098
  ["port", "show", tunnelId, "-p", String(localPort), "-j"],
33317
34099
  { stdio: "pipe" }
@@ -33365,7 +34147,9 @@ function createTransport(config) {
33365
34147
  }
33366
34148
  }
33367
34149
 
33368
- // src/main.ts
34150
+ // src/startup.ts
34151
+ var fs16 = __toESM(require("fs"), 1);
34152
+ var readline = __toESM(require("readline/promises"), 1);
33369
34153
  function getDefaultNodeName() {
33370
34154
  return getDeviceNodeName();
33371
34155
  }
@@ -33381,9 +34165,10 @@ function createDefaultConfig(storagePath = resolveDefaultStoragePath(), nodeName
33381
34165
  };
33382
34166
  }
33383
34167
  var DEFAULT_CONFIG3 = createDefaultConfig();
33384
- function createRuntimeDefaultConfig(fileConfig) {
34168
+ function createRuntimeDefaultConfig(fileConfig, options = {}) {
33385
34169
  const storagePath = fileConfig.storage?.path ?? resolveDefaultStoragePath();
33386
- return createDefaultConfig(storagePath, resolveDefaultNodeName(storagePath));
34170
+ const nodeName = options.ignorePersistedName ? getDeviceNodeName() : resolveDefaultNodeName(storagePath);
34171
+ return createDefaultConfig(storagePath, nodeName);
33387
34172
  }
33388
34173
  function parseArgs(argv) {
33389
34174
  const result = {};
@@ -33421,6 +34206,12 @@ function parseArgs(argv) {
33421
34206
  result.config = argv[++i];
33422
34207
  }
33423
34208
  break;
34209
+ case "--reset":
34210
+ result.reset = true;
34211
+ break;
34212
+ case "--disable-auth":
34213
+ result.disableAuth = true;
34214
+ break;
33424
34215
  }
33425
34216
  }
33426
34217
  return result;
@@ -33531,6 +34322,67 @@ async function promptStartOptions(args, fileConfig, defaults, prompt) {
33531
34322
  await closePrompt?.();
33532
34323
  }
33533
34324
  }
34325
+ function applyStartMetadata(storagePath, args) {
34326
+ const startup = resolveNodeStartupMetadata(storagePath);
34327
+ const result = { ...args };
34328
+ const loaded = {};
34329
+ const persistedName = resolvePersistedDefaultNodeName(storagePath);
34330
+ if (persistedName && result.name === void 0) {
34331
+ loaded.name = persistedName;
34332
+ }
34333
+ if (startup.port !== void 0 && result.port === void 0) {
34334
+ result.port = startup.port;
34335
+ loaded.port = startup.port;
34336
+ }
34337
+ if (startup.transport && result.transport === void 0) {
34338
+ result.transport = startup.transport;
34339
+ loaded.transport = startup.transport;
34340
+ }
34341
+ if (startup.join && result.join === void 0) {
34342
+ result.join = startup.join;
34343
+ loaded.join = startup.join;
34344
+ }
34345
+ return {
34346
+ args: result,
34347
+ loaded: Object.keys(loaded).length > 0 ? loaded : void 0
34348
+ };
34349
+ }
34350
+ function shouldCollectStartOptions(args, storagePath) {
34351
+ return args.reset === true || !hasPersistedNodeStartupMetadata(storagePath);
34352
+ }
34353
+ function resolveRuntimeAuthMetadata(storagePath, disableAuth = false) {
34354
+ const persisted = resolveNodeAuthMetadata(storagePath);
34355
+ if (disableAuth) {
34356
+ return { ...persisted, enabled: false };
34357
+ }
34358
+ if (!persisted.enabled && !persisted.allowSameTenant && persisted.allowedUsers.length === 0) {
34359
+ return {
34360
+ enabled: true,
34361
+ allowSameTenant: false,
34362
+ allowedUsers: []
34363
+ };
34364
+ }
34365
+ return persisted;
34366
+ }
34367
+ function formatLoadedStartMetadata(info, authEnabled) {
34368
+ const lines = ["Loaded startup options from metadata:"];
34369
+ if (info.name) {
34370
+ lines.push(` Node: ${info.name}`);
34371
+ }
34372
+ if (info.port !== void 0) {
34373
+ lines.push(` Port: ${info.port}`);
34374
+ }
34375
+ if (info.transport) {
34376
+ lines.push(` Transport: ${info.transport}`);
34377
+ }
34378
+ if (info.join) {
34379
+ lines.push(` Join: ${info.join}`);
34380
+ }
34381
+ lines.push(` Auth: ${authEnabled ? "enabled" : "disabled"}`);
34382
+ return lines.join("\n");
34383
+ }
34384
+
34385
+ // src/main.ts
33534
34386
  function formatBanner(info) {
33535
34387
  const lines = [
33536
34388
  `Node: ${info.name}`,
@@ -33538,6 +34390,9 @@ function formatBanner(info) {
33538
34390
  `Endpoint: ${info.endpoint}`,
33539
34391
  `Dashboard: http://localhost:${info.port}`
33540
34392
  ];
34393
+ if (info.dashboardOrigin) {
34394
+ lines.push(`Dashboard Tunnel: ${info.dashboardOrigin}`);
34395
+ }
33541
34396
  const maxLen = Math.max(...lines.map((l) => l.length));
33542
34397
  const innerWidth = maxLen + 4;
33543
34398
  const top = ` \u256D${"\u2500".repeat(innerWidth)}\u256E`;
@@ -33559,16 +34414,48 @@ async function main() {
33559
34414
  const args = parseArgs(process.argv.slice(2));
33560
34415
  const configPath = args.config ?? "./config.json";
33561
34416
  const fileConfig = loadConfigFile(configPath);
33562
- const defaults = createRuntimeDefaultConfig(fileConfig);
33563
- const resolvedArgs = await promptStartOptions(args, fileConfig, defaults);
34417
+ const storagePath = fileConfig.storage?.path ?? resolveDefaultStoragePath();
34418
+ const hydratedArgs = args.reset ? { args: { ...args }, loaded: void 0 } : applyStartMetadata(storagePath, args);
34419
+ const defaults = createRuntimeDefaultConfig(fileConfig, {
34420
+ ignorePersistedName: args.reset === true
34421
+ });
34422
+ const resolvedArgs = shouldCollectStartOptions(args, storagePath) ? await promptStartOptions(
34423
+ args.reset ? args : hydratedArgs.args,
34424
+ fileConfig,
34425
+ defaults
34426
+ ) : hydratedArgs.args;
33564
34427
  const config = mergeConfig(defaults, fileConfig, resolvedArgs);
33565
34428
  persistDefaultNodeName(config.storage.path, config.node.name);
34429
+ persistNodeStartupMetadata(config.storage.path, {
34430
+ port: config.node.port,
34431
+ transport: config.transport.type,
34432
+ join: resolvedArgs.join
34433
+ });
34434
+ const authMetadata = resolveRuntimeAuthMetadata(config.storage.path, resolvedArgs.disableAuth === true);
34435
+ if (!resolvedArgs.disableAuth) {
34436
+ persistNodeAuthMetadata(config.storage.path, authMetadata);
34437
+ }
33566
34438
  const logDir = nodePath.join(config.storage.path, "logs");
33567
34439
  const logger = createLogger({
33568
34440
  component: "node",
33569
34441
  logDir,
33570
34442
  console: true
33571
34443
  });
34444
+ const nodeAuth = new AzureCliNodeAuth(config.storage.path, {
34445
+ logger: logger.child("azure-auth"),
34446
+ settingsProvider: () => authMetadata
34447
+ });
34448
+ if (hydratedArgs.loaded) {
34449
+ console.log("");
34450
+ console.log(formatLoadedStartMetadata(hydratedArgs.loaded, authMetadata.enabled));
34451
+ console.log("");
34452
+ }
34453
+ if (authMetadata.enabled) {
34454
+ nodeAuth.getAccessToken();
34455
+ setRequestAuthHeadersProvider(() => nodeAuth.getAuthorizationHeaders());
34456
+ } else {
34457
+ setRequestAuthHeadersProvider(null);
34458
+ }
33572
34459
  const meshyNode = new MeshyNode(config, {
33573
34460
  logger,
33574
34461
  transportFactory: createTransport
@@ -33578,9 +34465,24 @@ async function main() {
33578
34465
  const previewServer = new PreviewServer(previewSessionManager);
33579
34466
  const previewPort = config.node.port + 1;
33580
34467
  const actualPreviewPort = await previewServer.start(previewPort);
34468
+ let dashboardTransport = null;
34469
+ let dashboardOrigin;
33581
34470
  let previewTransport = null;
33582
34471
  let previewOrigin;
33583
34472
  let deps;
34473
+ function createDashboardTransportConfig() {
34474
+ return {
34475
+ type: "devtunnel",
34476
+ devtunnel: {
34477
+ id: resolveOrCreateDevTunnelId(
34478
+ config.storage.path,
34479
+ meshyNode.getNodeRegistry().getSelf().id,
34480
+ "dashboard"
34481
+ ),
34482
+ allowAnonymous: false
34483
+ }
34484
+ };
34485
+ }
33584
34486
  function createPreviewTransportConfig() {
33585
34487
  return {
33586
34488
  type: "devtunnel",
@@ -33594,11 +34496,92 @@ async function main() {
33594
34496
  }
33595
34497
  };
33596
34498
  }
34499
+ function setAdvertisedDashboardOrigin(origin) {
34500
+ dashboardOrigin = origin;
34501
+ deps.dashboardOrigin = origin;
34502
+ meshyNode.getNodeRegistry().updateDashboardOrigin(meshyNode.getNodeRegistry().getSelf().id, origin);
34503
+ }
33597
34504
  function setAdvertisedPreviewOrigin(origin) {
33598
34505
  previewOrigin = origin;
33599
34506
  deps.previewOrigin = origin;
33600
34507
  meshyNode.getNodeRegistry().updatePreviewOrigin(meshyNode.getNodeRegistry().getSelf().id, origin);
33601
34508
  }
34509
+ function shouldPublishDashboardTunnel() {
34510
+ return authMetadata.enabled && (meshyNode.getTransportType() === "devtunnel" || meshyNode.isDevTunnelEnabled());
34511
+ }
34512
+ async function stopDashboardTransport() {
34513
+ if (!dashboardTransport) {
34514
+ if (dashboardOrigin) {
34515
+ setAdvertisedDashboardOrigin(void 0);
34516
+ }
34517
+ return;
34518
+ }
34519
+ await dashboardTransport.stop().catch(() => void 0);
34520
+ dashboardTransport = null;
34521
+ setAdvertisedDashboardOrigin(void 0);
34522
+ meshyNode.getLogger().info("stopped dashboard tunnel", {
34523
+ reason: "dashboard transport no longer required",
34524
+ mainTransportType: meshyNode.getTransportType(),
34525
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
34526
+ });
34527
+ }
34528
+ async function restartDashboardTransport(transportConfig) {
34529
+ const previousTransport = dashboardTransport;
34530
+ const previousOrigin = dashboardOrigin;
34531
+ let nextTransport = null;
34532
+ try {
34533
+ nextTransport = createTransport(transportConfig);
34534
+ await nextTransport.start(config.node.port);
34535
+ const nextOrigin = await nextTransport.getEndpoint();
34536
+ dashboardTransport = nextTransport;
34537
+ setAdvertisedDashboardOrigin(nextOrigin);
34538
+ meshyNode.getLogger().info("started dashboard tunnel", {
34539
+ dashboardOrigin: nextOrigin,
34540
+ mainTransportType: meshyNode.getTransportType(),
34541
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
34542
+ });
34543
+ if (previousTransport) {
34544
+ await previousTransport.stop().catch(() => void 0);
34545
+ }
34546
+ } catch (err) {
34547
+ if (nextTransport) {
34548
+ await nextTransport.stop().catch(() => void 0);
34549
+ }
34550
+ dashboardTransport = previousTransport;
34551
+ setAdvertisedDashboardOrigin(previousOrigin);
34552
+ meshyNode.getLogger().warn("failed to start dashboard transport", {
34553
+ error: err instanceof Error ? err.message : String(err),
34554
+ port: config.node.port,
34555
+ transportType: transportConfig.type
34556
+ });
34557
+ }
34558
+ }
34559
+ async function syncDashboardTransport(reason) {
34560
+ if (!authMetadata.enabled) {
34561
+ await stopDashboardTransport();
34562
+ return;
34563
+ }
34564
+ if (!shouldPublishDashboardTunnel()) {
34565
+ meshyNode.getLogger().info("skipping dashboard tunnel", {
34566
+ reason,
34567
+ mainTransportType: meshyNode.getTransportType(),
34568
+ sidecarEnabled: meshyNode.isDevTunnelEnabled(),
34569
+ authEnabled: authMetadata.enabled
34570
+ });
34571
+ await stopDashboardTransport();
34572
+ return;
34573
+ }
34574
+ if (dashboardTransport && await dashboardTransport.isHealthy().catch(() => false)) {
34575
+ meshyNode.getLogger().debug("dashboard tunnel already active", {
34576
+ reason,
34577
+ dashboardOrigin,
34578
+ mainTransportType: meshyNode.getTransportType(),
34579
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
34580
+ });
34581
+ return;
34582
+ }
34583
+ await restartDashboardTransport(createDashboardTransportConfig());
34584
+ }
33602
34585
  async function restartPreviewTransport(transportConfig) {
33603
34586
  const previousTransport = previewTransport;
33604
34587
  const previousOrigin = previewOrigin;
@@ -33634,16 +34617,42 @@ async function main() {
33634
34617
  eventBus: meshyNode.getEventBus(),
33635
34618
  engineRegistry: meshyNode.getEngineRegistry(),
33636
34619
  logger: meshyNode.getLogger(),
33637
- config: { apiKey: config.cluster.apiKey },
34620
+ config: {
34621
+ apiKey: config.cluster.apiKey,
34622
+ authMetadata,
34623
+ validateBearerToken: authMetadata.enabled ? (header) => nodeAuth.validateAuthorizationHeader(header) : void 0
34624
+ },
33638
34625
  workDir: meshyNode.getWorkDir(),
33639
34626
  persistNodeNamePreference: (name) => persistDefaultNodeName(config.storage.path, name),
34627
+ joinCurrentNodeToCluster: async (leaderEndpoint) => {
34628
+ await meshyNode.joinCluster(leaderEndpoint);
34629
+ },
34630
+ leaveCurrentCluster: async () => {
34631
+ await meshyNode.leaveCluster();
34632
+ },
33640
34633
  switchTransport: async (type) => {
33641
- return meshyNode.switchTransport(type);
34634
+ const endpoint = await meshyNode.switchTransport(type);
34635
+ persistNodeStartupTransport(config.storage.path, type);
34636
+ await syncDashboardTransport(`switchTransport:${type}`);
34637
+ return endpoint;
33642
34638
  },
33643
34639
  getTransportType: () => meshyNode.getTransportType(),
33644
- enableDevTunnel: async () => meshyNode.enableDevTunnel(),
33645
- disableDevTunnel: async () => meshyNode.disableDevTunnel(),
34640
+ enableDevTunnel: async () => {
34641
+ const endpoint = await meshyNode.enableDevTunnel();
34642
+ persistNodeStartupTransport(config.storage.path, "devtunnel");
34643
+ await syncDashboardTransport("enableDevTunnel");
34644
+ return endpoint;
34645
+ },
34646
+ disableDevTunnel: async () => {
34647
+ await meshyNode.disableDevTunnel();
34648
+ persistNodeStartupTransport(
34649
+ config.storage.path,
34650
+ meshyNode.getTransportType()
34651
+ );
34652
+ await syncDashboardTransport("disableDevTunnel");
34653
+ },
33646
34654
  isDevTunnelEnabled: () => meshyNode.isDevTunnelEnabled(),
34655
+ dashboardOrigin,
33647
34656
  ensurePreviewOrigin: async () => {
33648
34657
  if (previewTransport && !await previewTransport.isHealthy()) {
33649
34658
  await previewTransport.stop().catch(() => void 0);
@@ -33659,7 +34668,16 @@ async function main() {
33659
34668
  };
33660
34669
  deps.previewSessionManager = previewSessionManager;
33661
34670
  deps.previewPort = actualPreviewPort;
34671
+ deps.dashboardOrigin = dashboardOrigin;
33662
34672
  deps.previewOrigin = previewOrigin;
34673
+ meshyNode.getLogger().info("configured node auth mode", {
34674
+ authEnabled: authMetadata.enabled,
34675
+ allowSameTenant: authMetadata.allowSameTenant,
34676
+ allowedUsersCount: authMetadata.allowedUsers.length,
34677
+ mainTransportType: meshyNode.getTransportType(),
34678
+ sidecarEnabled: meshyNode.isDevTunnelEnabled()
34679
+ });
34680
+ await syncDashboardTransport("startup");
33663
34681
  const previewHealthTimer = setInterval(() => {
33664
34682
  void (async () => {
33665
34683
  if (!previewTransport) return;
@@ -33673,6 +34691,19 @@ async function main() {
33673
34691
  });
33674
34692
  })();
33675
34693
  }, config.cluster.heartbeatInterval);
34694
+ const dashboardHealthTimer = setInterval(() => {
34695
+ void (async () => {
34696
+ if (!dashboardTransport) return;
34697
+ const healthy = await dashboardTransport.isHealthy().catch(() => false);
34698
+ if (healthy) return;
34699
+ await dashboardTransport.stop().catch(() => void 0);
34700
+ dashboardTransport = null;
34701
+ setAdvertisedDashboardOrigin(void 0);
34702
+ meshyNode.getLogger().warn("dashboard transport became unhealthy and was cleared", {
34703
+ port: config.node.port
34704
+ });
34705
+ })();
34706
+ }, config.cluster.heartbeatInterval);
33676
34707
  const app = createServer2(deps);
33677
34708
  const server = app.listen(config.node.port, () => {
33678
34709
  const self = meshyNode.getNodeRegistry().getSelf();
@@ -33683,7 +34714,8 @@ async function main() {
33683
34714
  role,
33684
34715
  term,
33685
34716
  endpoint: self.endpoint,
33686
- port: config.node.port
34717
+ port: config.node.port,
34718
+ dashboardOrigin
33687
34719
  });
33688
34720
  console.log("");
33689
34721
  console.log(banner);
@@ -33695,7 +34727,11 @@ async function main() {
33695
34727
  shuttingDown = true;
33696
34728
  console.log("\nShutting down...");
33697
34729
  try {
34730
+ clearInterval(dashboardHealthTimer);
33698
34731
  clearInterval(previewHealthTimer);
34732
+ if (dashboardTransport) {
34733
+ await dashboardTransport.stop();
34734
+ }
33699
34735
  if (previewTransport) {
33700
34736
  await previewTransport.stop();
33701
34737
  }
@@ -33724,9 +34760,11 @@ if (isDirectRun) {
33724
34760
  DEFAULT_CONFIG,
33725
34761
  DEFAULT_NODE_NAME,
33726
34762
  DEFAULT_NODE_PORT,
34763
+ applyStartMetadata,
33727
34764
  createDefaultConfig,
33728
34765
  createRuntimeDefaultConfig,
33729
34766
  formatBanner,
34767
+ formatLoadedStartMetadata,
33730
34768
  getDefaultNodeName,
33731
34769
  isDirectRunPath,
33732
34770
  loadConfigFile,
@@ -33734,6 +34772,8 @@ if (isDirectRun) {
33734
34772
  mergeConfig,
33735
34773
  parseArgs,
33736
34774
  promptStartOptions,
34775
+ resolveRuntimeAuthMetadata,
34776
+ shouldCollectStartOptions,
33737
34777
  shouldPromptForStartOptions
33738
34778
  });
33739
34779
  /*! Bundled license information: