meshy-node 0.4.4 → 0.4.6

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
@@ -6629,8 +6629,8 @@ var require_node2 = __commonJS({
6629
6629
  }
6630
6630
  break;
6631
6631
  case "FILE":
6632
- var fs22 = require("fs");
6633
- stream2 = new fs22.SyncWriteStream(fd2, { autoClose: false });
6632
+ var fs23 = require("fs");
6633
+ stream2 = new fs23.SyncWriteStream(fd2, { autoClose: false });
6634
6634
  stream2._type = "fs";
6635
6635
  break;
6636
6636
  case "PIPE":
@@ -23749,8 +23749,8 @@ var require_view = __commonJS({
23749
23749
  "use strict";
23750
23750
  var debug = require_src2()("express:view");
23751
23751
  var path23 = require("path");
23752
- var fs22 = require("fs");
23753
- var dirname7 = path23.dirname;
23752
+ var fs23 = require("fs");
23753
+ var dirname8 = path23.dirname;
23754
23754
  var basename5 = path23.basename;
23755
23755
  var extname3 = path23.extname;
23756
23756
  var join17 = path23.join;
@@ -23789,7 +23789,7 @@ var require_view = __commonJS({
23789
23789
  for (var i = 0; i < roots.length && !path24; i++) {
23790
23790
  var root = roots[i];
23791
23791
  var loc = resolve15(root, name2);
23792
- var dir = dirname7(loc);
23792
+ var dir = dirname8(loc);
23793
23793
  var file = basename5(loc);
23794
23794
  path24 = this.resolve(dir, file);
23795
23795
  }
@@ -23815,7 +23815,7 @@ var require_view = __commonJS({
23815
23815
  function tryStat(path24) {
23816
23816
  debug('stat "%s"', path24);
23817
23817
  try {
23818
- return fs22.statSync(path24);
23818
+ return fs23.statSync(path24);
23819
23819
  } catch (e) {
23820
23820
  return void 0;
23821
23821
  }
@@ -24113,7 +24113,7 @@ var require_mime = __commonJS({
24113
24113
  "../../node_modules/.pnpm/mime@1.6.0/node_modules/mime/mime.js"(exports2, module2) {
24114
24114
  "use strict";
24115
24115
  var path23 = require("path");
24116
- var fs22 = require("fs");
24116
+ var fs23 = require("fs");
24117
24117
  function Mime() {
24118
24118
  this.types = /* @__PURE__ */ Object.create(null);
24119
24119
  this.extensions = /* @__PURE__ */ Object.create(null);
@@ -24134,7 +24134,7 @@ var require_mime = __commonJS({
24134
24134
  };
24135
24135
  Mime.prototype.load = function(file) {
24136
24136
  this._loading = file;
24137
- var map = {}, content = fs22.readFileSync(file, "ascii"), lines = content.split(/[\r\n]+/);
24137
+ var map = {}, content = fs23.readFileSync(file, "ascii"), lines = content.split(/[\r\n]+/);
24138
24138
  lines.forEach(function(line) {
24139
24139
  var fields = line.replace(/\s*#.*|^\s*|\s*$/g, "").split(/\s+/);
24140
24140
  map[fields.shift()] = fields;
@@ -24256,7 +24256,7 @@ var require_send = __commonJS({
24256
24256
  var escapeHtml2 = require_escape_html();
24257
24257
  var etag = require_etag();
24258
24258
  var fresh = require_fresh();
24259
- var fs22 = require("fs");
24259
+ var fs23 = require("fs");
24260
24260
  var mime = require_mime();
24261
24261
  var ms = require_ms();
24262
24262
  var onFinished = require_on_finished();
@@ -24589,7 +24589,7 @@ var require_send = __commonJS({
24589
24589
  var i = 0;
24590
24590
  var self2 = this;
24591
24591
  debug('stat "%s"', path24);
24592
- fs22.stat(path24, function onstat(err, stat) {
24592
+ fs23.stat(path24, function onstat(err, stat) {
24593
24593
  if (err && err.code === "ENOENT" && !extname3(path24) && path24[path24.length - 1] !== sep4) {
24594
24594
  return next(err);
24595
24595
  }
@@ -24604,7 +24604,7 @@ var require_send = __commonJS({
24604
24604
  }
24605
24605
  var p = path24 + "." + self2._extensions[i++];
24606
24606
  debug('stat "%s"', p);
24607
- fs22.stat(p, function(err2, stat) {
24607
+ fs23.stat(p, function(err2, stat) {
24608
24608
  if (err2) return next(err2);
24609
24609
  if (stat.isDirectory()) return next();
24610
24610
  self2.emit("file", p, stat);
@@ -24622,7 +24622,7 @@ var require_send = __commonJS({
24622
24622
  }
24623
24623
  var p = join17(path24, self2._index[i]);
24624
24624
  debug('stat "%s"', p);
24625
- fs22.stat(p, function(err2, stat) {
24625
+ fs23.stat(p, function(err2, stat) {
24626
24626
  if (err2) return next(err2);
24627
24627
  if (stat.isDirectory()) return next();
24628
24628
  self2.emit("file", p, stat);
@@ -24634,7 +24634,7 @@ var require_send = __commonJS({
24634
24634
  SendStream.prototype.stream = function stream(path24, options) {
24635
24635
  var self2 = this;
24636
24636
  var res = this.res;
24637
- var stream2 = fs22.createReadStream(path24, options);
24637
+ var stream2 = fs23.createReadStream(path24, options);
24638
24638
  this.emit("stream", stream2);
24639
24639
  stream2.pipe(res);
24640
24640
  function cleanup() {
@@ -31894,11 +31894,11 @@ var processUtils = {
31894
31894
  // ../../node_modules/.pnpm/@azure+identity@4.13.1/node_modules/@azure/identity/dist/esm/credentials/azurePowerShellCredential.js
31895
31895
  var logger18 = credentialLogger("AzurePowerShellCredential");
31896
31896
  var isWindows = process.platform === "win32";
31897
- function formatCommand(commandName) {
31897
+ function formatCommand(commandName2) {
31898
31898
  if (isWindows) {
31899
- return `${commandName}.exe`;
31899
+ return `${commandName2}.exe`;
31900
31900
  } else {
31901
- return commandName;
31901
+ return commandName2;
31902
31902
  }
31903
31903
  }
31904
31904
  async function runCommands(commands, timeout) {
@@ -33863,6 +33863,49 @@ var LeaderElection = class {
33863
33863
  var import_node_crypto5 = require("crypto");
33864
33864
  var fs4 = __toESM(require("fs"), 1);
33865
33865
  var path4 = __toESM(require("path"), 1);
33866
+
33867
+ // ../../packages/core/src/tasks/task-join-snapshot.ts
33868
+ var JOIN_TASK_SNAPSHOT_PAYLOAD_KEY = "__meshyJoinSnapshot";
33869
+ var MAX_JOIN_TASK_ERROR_LENGTH = 2e3;
33870
+ function truncate(value, maxLength) {
33871
+ if (value === null || value.length <= maxLength) {
33872
+ return value;
33873
+ }
33874
+ return `${value.slice(0, maxLength)}...`;
33875
+ }
33876
+ function shouldIncludeTaskInJoinSnapshot(task, selfId) {
33877
+ return task.assignedTo === null || task.assignedTo === selfId;
33878
+ }
33879
+ function createJoinTaskSnapshot(task, ownerNodeId) {
33880
+ return {
33881
+ ...task,
33882
+ description: "",
33883
+ payload: {
33884
+ [JOIN_TASK_SNAPSHOT_PAYLOAD_KEY]: {
33885
+ ownerNodeId,
33886
+ version: 1
33887
+ }
33888
+ },
33889
+ result: null,
33890
+ error: truncate(task.error, MAX_JOIN_TASK_ERROR_LENGTH)
33891
+ };
33892
+ }
33893
+ function buildJoinTaskSnapshots(tasks, ownerNodeId) {
33894
+ return tasks.filter((task) => shouldIncludeTaskInJoinSnapshot(task, ownerNodeId)).map((task) => createJoinTaskSnapshot(task, ownerNodeId));
33895
+ }
33896
+ function getJoinTaskSnapshotOwnerId(task) {
33897
+ const metadata = task.payload[JOIN_TASK_SNAPSHOT_PAYLOAD_KEY];
33898
+ if (typeof metadata !== "object" || metadata === null) {
33899
+ return null;
33900
+ }
33901
+ const ownerNodeId = metadata.ownerNodeId;
33902
+ return typeof ownerNodeId === "string" && ownerNodeId.length > 0 ? ownerNodeId : null;
33903
+ }
33904
+ function isJoinTaskSnapshot(task) {
33905
+ return getJoinTaskSnapshotOwnerId(task) !== null;
33906
+ }
33907
+
33908
+ // ../../packages/core/src/tasks/task-engine.ts
33866
33909
  function nodeSupportsAgent(node, agent) {
33867
33910
  return !node.supportedAgents || node.supportedAgents.length === 0 || node.supportedAgents.includes(agent);
33868
33911
  }
@@ -34148,7 +34191,9 @@ var TaskEngine = class {
34148
34191
  if (now - task.updatedAt < backoff) continue;
34149
34192
  }
34150
34193
  const previousNodeId = this.previousAssignments.get(task.id);
34151
- const capableNodes = availableNodes.filter((node) => nodeSupportsAgent(node, task.agent));
34194
+ const snapshotOwnerId = getJoinTaskSnapshotOwnerId(task);
34195
+ const candidateNodes = snapshotOwnerId ? availableNodes.filter((node) => node.id === snapshotOwnerId) : availableNodes;
34196
+ const capableNodes = candidateNodes.filter((node) => nodeSupportsAgent(node, task.agent));
34152
34197
  if (capableNodes.length === 0) continue;
34153
34198
  const targetNode = this.pickLeastBusyNode(capableNodes, nodeLoad, previousNodeId);
34154
34199
  if (!targetNode) continue;
@@ -36160,16 +36205,17 @@ var RestLeaderTaskReporter = class {
36160
36205
  var import_node_child_process2 = require("child_process");
36161
36206
  async function startWorkerTaskExecution(options) {
36162
36207
  const { taskEngine, engineRegistry, nodeRegistry, eventBus, logger: logger27, workDir, leaderReporter } = options;
36163
- const task = { ...options.task };
36164
- const existing = taskEngine.getTask(task.id);
36208
+ const incomingTask = { ...options.task };
36209
+ const existing = taskEngine.getTask(incomingTask.id);
36165
36210
  if (existing && hasExecutionAlreadyStarted(existing.status)) {
36166
36211
  logger27.warn("skipping duplicate task.execute for already-started task", {
36167
- taskId: task.id,
36212
+ taskId: incomingTask.id,
36168
36213
  status: existing.status,
36169
- agent: task.agent
36214
+ agent: incomingTask.agent
36170
36215
  });
36171
36216
  return existing;
36172
36217
  }
36218
+ const task = getExecutionTask(incomingTask, existing);
36173
36219
  task.effectiveProjectPath = resolveTaskWorkDir(task.project, workDir);
36174
36220
  task.branch = getCurrentGitBranch(task.effectiveProjectPath) ?? task.branch ?? null;
36175
36221
  await taskEngine.executeTask(task);
@@ -36219,6 +36265,17 @@ async function startWorkerTaskExecution(options) {
36219
36265
  }
36220
36266
  return task;
36221
36267
  }
36268
+ function getExecutionTask(incomingTask, existing) {
36269
+ if (!existing || !isJoinTaskSnapshot(incomingTask) || isJoinTaskSnapshot(existing)) {
36270
+ return incomingTask;
36271
+ }
36272
+ return {
36273
+ ...existing,
36274
+ status: incomingTask.status,
36275
+ assignedTo: incomingTask.assignedTo,
36276
+ assignedNodeName: incomingTask.assignedNodeName
36277
+ };
36278
+ }
36222
36279
  function hasExecutionAlreadyStarted(status) {
36223
36280
  return status === "running" || status === "completed" || status === "failed" || status === "cancelled" || status === "archived";
36224
36281
  }
@@ -37780,8 +37837,8 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
37780
37837
  devtunnelEndpoint
37781
37838
  });
37782
37839
  }
37783
- const tasks = this.taskEngine.listTasks().tasks;
37784
37840
  const self2 = this.nodeRegistry.getSelf();
37841
+ const tasks = buildJoinTaskSnapshots(this.taskEngine.listTasks().tasks, self2.id);
37785
37842
  const joinBody = {
37786
37843
  id: self2.id,
37787
37844
  endpoint: self2.endpoint,
@@ -37872,6 +37929,7 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
37872
37929
  leaderEndpoint,
37873
37930
  selfId: self2.id
37874
37931
  });
37932
+ persistNodeStartupJoin(this.config.storage.path, void 0);
37875
37933
  return;
37876
37934
  }
37877
37935
  if (leader && leaderEndpoint) {
@@ -37934,7 +37992,7 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
37934
37992
  this.election.stop();
37935
37993
  try {
37936
37994
  const leaderEndpoint = this.nodeRegistry.getLeaderEndpoint();
37937
- if (leaderEndpoint) {
37995
+ if (leaderEndpoint && !this.nodeRegistry.isLeader()) {
37938
37996
  this.logger.info("sending shutdown cluster leave request", {
37939
37997
  leaderEndpoint,
37940
37998
  nodeId: this.selfInfo.id
@@ -38835,7 +38893,7 @@ function isDirectRunPath(entryPath) {
38835
38893
  }
38836
38894
 
38837
38895
  // src/bootstrap/start-node.ts
38838
- var nodePath2 = __toESM(require("path"), 1);
38896
+ var nodePath3 = __toESM(require("path"), 1);
38839
38897
 
38840
38898
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
38841
38899
  var external_exports = {};
@@ -42951,6 +43009,15 @@ var SystemAgentInfoSchema = external_exports.object({
42951
43009
  detail: external_exports.string().nullable().optional()
42952
43010
  }).optional()
42953
43011
  });
43012
+ var SystemRuntimeUpdateInfoSchema = external_exports.object({
43013
+ packageName: external_exports.string(),
43014
+ currentVersion: external_exports.string().nullable(),
43015
+ latestVersion: external_exports.string().nullable(),
43016
+ updateAvailable: external_exports.boolean(),
43017
+ checkedAt: external_exports.string(),
43018
+ restartMode: external_exports.enum(["package", "development"]),
43019
+ detail: external_exports.string().nullable().optional()
43020
+ });
42954
43021
  var NodeSettingsSnapshotSchema = external_exports.object({
42955
43022
  collectedAt: external_exports.number(),
42956
43023
  version: external_exports.string(),
@@ -42959,6 +43026,7 @@ var NodeSettingsSnapshotSchema = external_exports.object({
42959
43026
  auth: SystemAuthInfoSchema.optional(),
42960
43027
  os: SystemOsInfoSchema.optional(),
42961
43028
  runtime: SystemRuntimeInfoSchema.optional(),
43029
+ runtimeUpdate: SystemRuntimeUpdateInfoSchema.optional(),
42962
43030
  agents: external_exports.array(SystemAgentInfoSchema).optional(),
42963
43031
  startupRequirements: external_exports.object({
42964
43032
  lastCheckedAt: external_exports.string().optional(),
@@ -43020,6 +43088,7 @@ var JoinTaskSchema = external_exports.object({
43020
43088
  agent: external_exports.string(),
43021
43089
  project: external_exports.string().nullable(),
43022
43090
  effectiveProjectPath: external_exports.string().nullable(),
43091
+ branch: external_exports.string().nullable().optional(),
43023
43092
  conversationKind: external_exports.enum(["meshyChat", "nativeSession"]).optional(),
43024
43093
  payload: external_exports.record(external_exports.unknown()),
43025
43094
  status: external_exports.enum(["pending", "assigned", "running", "completed", "failed", "cancelled", "archived"]),
@@ -43806,10 +43875,19 @@ function createClusterRoutes() {
43806
43875
  });
43807
43876
  }));
43808
43877
  router.post("/leave", asyncHandler(async (req, res) => {
43809
- const { nodeRegistry, logger: rootLogger } = req.app.locals.deps;
43878
+ const { nodeRegistry, election, leaveCurrentCluster, logger: rootLogger } = req.app.locals.deps;
43810
43879
  const log2 = rootLogger.child("cluster/leave");
43811
43880
  const { nodeId } = req.body;
43812
43881
  log2.info("received cluster leave request", { nodeId });
43882
+ if (!election.isLeader()) {
43883
+ if (!leaveCurrentCluster) {
43884
+ throw new MeshyError("VALIDATION_ERROR", "Leave flow is not available on this node", 501);
43885
+ }
43886
+ log2.info("handling cluster leave request as local follower detach", { requestedNodeId: nodeId });
43887
+ await leaveCurrentCluster();
43888
+ res.json({ ok: true });
43889
+ return;
43890
+ }
43813
43891
  nodeRegistry.removeNode(nodeId);
43814
43892
  res.json({ ok: true });
43815
43893
  }));
@@ -44755,6 +44833,7 @@ function buildNodeSettingsSnapshot(options) {
44755
44833
  storagePath: options.storagePath ?? null,
44756
44834
  localDashboardOrigin: options.localDashboardOrigin ?? null
44757
44835
  },
44836
+ runtimeUpdate: options.runtimeUpdate,
44758
44837
  agents,
44759
44838
  startupRequirements: {
44760
44839
  lastCheckedAt: runtimeMetadata?.startupRequirementsLastCheckedAt,
@@ -44838,8 +44917,61 @@ function upgradeRuntimeAgentForDeps(deps, agent) {
44838
44917
  return result;
44839
44918
  }
44840
44919
 
44920
+ // ../../packages/api/src/node/runtime-restart-request.ts
44921
+ function parseRuntimeRestartRequest(value) {
44922
+ const body = isRecord4(value) ? value : {};
44923
+ const startArgs = parseRuntimeRestartStartArgs(body.startArgs);
44924
+ return {
44925
+ reason: body.reason === "update" ? "update" : "restart",
44926
+ ...startArgs && { startArgs }
44927
+ };
44928
+ }
44929
+ function parseRuntimeRestartStartArgs(value) {
44930
+ if (value === void 0) return void 0;
44931
+ if (!isRecord4(value)) {
44932
+ throw new MeshyError("VALIDATION_ERROR", "Runtime restart startArgs must be an object", 400);
44933
+ }
44934
+ const startArgs = {};
44935
+ if (value.port !== void 0) {
44936
+ const port = value.port;
44937
+ if (typeof port !== "number" || !Number.isInteger(port) || port <= 0) {
44938
+ throw new MeshyError("VALIDATION_ERROR", "Runtime restart port must be a positive integer", 400);
44939
+ }
44940
+ startArgs.port = port;
44941
+ }
44942
+ for (const key of ["name", "join", "workDir", "config"]) {
44943
+ const parsed = parseOptionalString(value[key], key);
44944
+ if (parsed !== void 0) startArgs[key] = parsed;
44945
+ }
44946
+ if (value.transport !== void 0) {
44947
+ if (value.transport !== "direct" && value.transport !== "devtunnel" && value.transport !== "tailscale") {
44948
+ throw new MeshyError("VALIDATION_ERROR", "Runtime restart transport must be direct, devtunnel, or tailscale", 400);
44949
+ }
44950
+ startArgs.transport = value.transport;
44951
+ }
44952
+ if (value.disableAuth !== void 0) {
44953
+ if (typeof value.disableAuth !== "boolean") {
44954
+ throw new MeshyError("VALIDATION_ERROR", "Runtime restart disableAuth must be a boolean", 400);
44955
+ }
44956
+ startArgs.disableAuth = value.disableAuth;
44957
+ }
44958
+ return Object.keys(startArgs).length > 0 ? startArgs : void 0;
44959
+ }
44960
+ function parseOptionalString(value, key) {
44961
+ if (value === void 0) return void 0;
44962
+ if (typeof value !== "string") {
44963
+ throw new MeshyError("VALIDATION_ERROR", `Runtime restart ${key} must be a string`, 400);
44964
+ }
44965
+ const trimmed = value.trim();
44966
+ return trimmed || void 0;
44967
+ }
44968
+ function isRecord4(value) {
44969
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
44970
+ }
44971
+
44841
44972
  // ../../packages/api/src/node/node-operation-service.ts
44842
44973
  var LEADER_REPORT_RETRY_MS = 5e3;
44974
+ var OPERATION_COMMIT_EFFECT = /* @__PURE__ */ Symbol("operationCommitEffect");
44843
44975
  var NodeOperationFailure = class extends Error {
44844
44976
  constructor(message, result) {
44845
44977
  super(message);
@@ -44873,6 +45005,7 @@ var NodeOperationService = class {
44873
45005
  this.deps = deps;
44874
45006
  this.store = store;
44875
45007
  this.registerHandler("agent.upgrade", runAgentUpgradeOperation);
45008
+ this.registerHandler("runtime.restart", runRuntimeRestartOperation);
44876
45009
  this.registerHandler("workdir.branch-create", runWorkDirBranchCreateOperation);
44877
45010
  }
44878
45011
  handlers = /* @__PURE__ */ new Map();
@@ -44943,8 +45076,14 @@ var NodeOperationService = class {
44943
45076
  try {
44944
45077
  const handler = this.handlers.get(running.kind);
44945
45078
  if (!handler) throw new Error(`No node operation handler registered for kind: ${running.kind}`);
44946
- const result = await handler(running, this.deps);
44947
- this.update(id, { status: "succeeded", result, completedAt: Date.now() });
45079
+ const handled = readOperationCommitEffect(await handler(running, this.deps));
45080
+ const saved = this.update(id, { status: "succeeded", result: handled.result, completedAt: Date.now() }, {
45081
+ reportToLeader: handled.afterCommit ? false : true
45082
+ });
45083
+ if (handled.afterCommit) {
45084
+ if (saved) await this.reportToLeader(saved);
45085
+ await handled.afterCommit();
45086
+ }
44948
45087
  } catch (err) {
44949
45088
  this.update(id, {
44950
45089
  status: "failed",
@@ -44954,12 +45093,12 @@ var NodeOperationService = class {
44954
45093
  });
44955
45094
  }
44956
45095
  }
44957
- update(id, updates) {
45096
+ update(id, updates, options = {}) {
44958
45097
  const saved = this.store.update(id, { ...updates, updatedAt: Date.now() });
44959
45098
  if (!saved) return null;
44960
45099
  this.applyOperationSideEffects(saved);
44961
45100
  this.deps.eventBus.emit("node.operation.updated", { operation: saved });
44962
- void this.reportToLeader(saved);
45101
+ if (options.reportToLeader !== false) void this.reportToLeader(saved);
44963
45102
  return saved;
44964
45103
  }
44965
45104
  applyOperationSideEffects(operation) {
@@ -45040,6 +45179,24 @@ function runAgentUpgradeOperation(operation, deps) {
45040
45179
  }
45041
45180
  return result;
45042
45181
  }
45182
+ function runRuntimeRestartOperation(operation, deps) {
45183
+ if (!deps.restartRuntime) throw new Error("Runtime restart is not available for this node");
45184
+ const reason = readPayloadString(operation, "reason") === "update" ? "update" : "restart";
45185
+ const startArgs = readPayloadStartArgs(operation);
45186
+ return deps.restartRuntime({ reason, ...startArgs && { startArgs }, deferShutdown: true }).then(
45187
+ ({ completeShutdown, ...result }) => operationCommitEffect(result, completeShutdown)
45188
+ );
45189
+ }
45190
+ function operationCommitEffect(result, afterCommit) {
45191
+ return { [OPERATION_COMMIT_EFFECT]: true, result, afterCommit };
45192
+ }
45193
+ function readOperationCommitEffect(value) {
45194
+ if (value && typeof value === "object" && value[OPERATION_COMMIT_EFFECT]) {
45195
+ const effect = value;
45196
+ return { result: effect.result, afterCommit: effect.afterCommit };
45197
+ }
45198
+ return { result: value };
45199
+ }
45043
45200
  function runWorkDirBranchCreateOperation(operation, deps) {
45044
45201
  const self2 = deps.nodeRegistry.getSelf();
45045
45202
  return createLocalNodeWorkDirBranch(
@@ -45073,6 +45230,11 @@ function readPayloadBoolean(operation, key, fallback) {
45073
45230
  const value = payload[key];
45074
45231
  return typeof value === "boolean" ? value : fallback;
45075
45232
  }
45233
+ function readPayloadStartArgs(operation) {
45234
+ const payload = operation.payload;
45235
+ if (!payload || typeof payload !== "object") return void 0;
45236
+ return parseRuntimeRestartStartArgs(payload.startArgs);
45237
+ }
45076
45238
  function readSettingsSnapshot(value) {
45077
45239
  if (!value || typeof value !== "object") return void 0;
45078
45240
  const snapshot = value.settingsSnapshot;
@@ -45240,7 +45402,7 @@ function toLegacyWorkerControl2(message) {
45240
45402
  }
45241
45403
 
45242
45404
  // ../../packages/api/src/tasks/task-route-utils.ts
45243
- function isRecord4(value) {
45405
+ function isRecord5(value) {
45244
45406
  return typeof value === "object" && value !== null;
45245
45407
  }
45246
45408
  function restoreTaskState(taskEngine, task) {
@@ -45307,7 +45469,7 @@ function normalizeStructuredValue(value) {
45307
45469
  if (Array.isArray(value)) {
45308
45470
  return value.map((entry) => normalizeStructuredValue(entry));
45309
45471
  }
45310
- if (!isRecord4(value)) {
45472
+ if (!isRecord5(value)) {
45311
45473
  return value;
45312
45474
  }
45313
45475
  return Object.fromEntries(
@@ -45316,20 +45478,20 @@ function normalizeStructuredValue(value) {
45316
45478
  }
45317
45479
  function getTranscriptEventSignature(event) {
45318
45480
  if (event.type === "user") {
45319
- const signature = getTaskUserMessageSignature(isRecord4(event.message) ? event.message.content : event.message) ?? getTaskUserMessageSignature(event.content);
45481
+ const signature = getTaskUserMessageSignature(isRecord5(event.message) ? event.message.content : event.message) ?? getTaskUserMessageSignature(event.content);
45320
45482
  return signature ? `user:${signature}` : null;
45321
45483
  }
45322
45484
  if (event.type === "assistant") {
45323
45485
  if (typeof event.message === "string") {
45324
45486
  return `assistant:${JSON.stringify(event.message)}`;
45325
45487
  }
45326
- if (isRecord4(event.message) && "content" in event.message) {
45488
+ if (isRecord5(event.message) && "content" in event.message) {
45327
45489
  return `assistant:${JSON.stringify(normalizeStructuredValue(event.message.content))}`;
45328
45490
  }
45329
45491
  if (Array.isArray(event.content)) {
45330
45492
  return `assistant:${JSON.stringify(normalizeStructuredValue(event.content))}`;
45331
45493
  }
45332
- if (isRecord4(event.message)) {
45494
+ if (isRecord5(event.message)) {
45333
45495
  return `assistant:${JSON.stringify(normalizeStructuredValue(event.message))}`;
45334
45496
  }
45335
45497
  }
@@ -47125,6 +47287,34 @@ async function sendNodeAgentUpgrade(req, res, nodeId, agentParam) {
47125
47287
  res.status(202).json(operation);
47126
47288
  }
47127
47289
 
47290
+ // ../../packages/api/src/routes/node-runtime.ts
47291
+ async function sendNodeRuntimeRestart(req, res, nodeId) {
47292
+ const deps = req.app.locals.deps;
47293
+ const self2 = deps.nodeRegistry.getSelf();
47294
+ const isSelf = nodeId === self2.id;
47295
+ if (!isSelf && !deps.election.isLeader()) {
47296
+ throw new MeshyError("NOT_LEADER", "Only the leader can restart other nodes", 403);
47297
+ }
47298
+ const node = isSelf ? self2 : deps.nodeRegistry.getNode(nodeId);
47299
+ if (!node) {
47300
+ throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
47301
+ }
47302
+ const restartRequest = parseRuntimeRestartRequest(req.body);
47303
+ const operations = getNodeOperationService(deps);
47304
+ const operation = operations.create("runtime.restart", nodeId, { ...restartRequest });
47305
+ if (isSelf) {
47306
+ operations.runLocal(operation.id);
47307
+ } else {
47308
+ try {
47309
+ await dispatchNodeOperation(deps, operation, node);
47310
+ } catch (err) {
47311
+ operations.markFailed(operation.id, err instanceof Error ? err.message : String(err));
47312
+ throw new MeshyError("NODE_OFFLINE", `Cannot reach node ${nodeId} to restart runtime`, 502);
47313
+ }
47314
+ }
47315
+ res.status(202).json(operation);
47316
+ }
47317
+
47128
47318
  // ../../packages/api/src/routes/node-terminal.ts
47129
47319
  var TERMINAL_SESSION_PROXY_TIMEOUT_MS = 1e4;
47130
47320
  function getTerminalSessionService2(deps) {
@@ -47548,6 +47738,9 @@ function createNodeRoutes() {
47548
47738
  router.post("/:id/agents/:agent/upgrade", asyncHandler3(async (req, res) => {
47549
47739
  await sendNodeAgentUpgrade(req, res, req.params.id, req.params.agent);
47550
47740
  }));
47741
+ router.post("/:id/runtime/restart", asyncHandler3(async (req, res) => {
47742
+ await sendNodeRuntimeRestart(req, res, req.params.id);
47743
+ }));
47551
47744
  router.patch("/:id", asyncHandler3(async (req, res) => {
47552
47745
  const { nodeRegistry, persistNodeNamePreference } = req.app.locals.deps;
47553
47746
  const updates = UpdateNodeBody.parse(req.body);
@@ -48029,8 +48222,10 @@ async function sendTaskLogsResponse(req, res, taskId) {
48029
48222
  if (!task && !proxyRequest.isProxy && await maybeProxyReadToLeader(req, res)) return;
48030
48223
  const selfId = nodeRegistry.getSelf()?.id;
48031
48224
  const isLeader = nodeRegistry.isLeader();
48225
+ const snapshotOwnerId = task ? getJoinTaskSnapshotOwnerId(task) : null;
48226
+ const proxyNodeId = task?.assignedTo ?? snapshotOwnerId;
48032
48227
  const isFollowerRemoteTask = !isLeader && !!(task?.assignedTo && task.assignedTo !== selfId);
48033
- const needsProxy = isLeader && !!(task?.assignedTo && task.assignedTo !== selfId);
48228
+ const needsProxy = isLeader && !!(proxyNodeId && proxyNodeId !== selfId);
48034
48229
  if (proxyRequest.isProxy) {
48035
48230
  log2.info("received proxied task logs request", {
48036
48231
  taskId,
@@ -48048,6 +48243,7 @@ async function sendTaskLogsResponse(req, res, taskId) {
48048
48243
  log2.debug("log request", {
48049
48244
  taskId,
48050
48245
  assignedTo: task?.assignedTo,
48246
+ snapshotOwnerId,
48051
48247
  selfId,
48052
48248
  isLeader,
48053
48249
  needsProxy
@@ -48071,20 +48267,21 @@ async function sendTaskLogsResponse(req, res, taskId) {
48071
48267
  selfId
48072
48268
  });
48073
48269
  }
48074
- const assignedTo = task.assignedTo;
48075
- if (needsProxy && assignedTo) {
48076
- const node = nodeRegistry.getNode(assignedTo);
48270
+ if (needsProxy && proxyNodeId) {
48271
+ const node = nodeRegistry.getNode(proxyNodeId);
48077
48272
  log2.debug("proxy target", { nodeId: node?.id, endpoint: node?.endpoint, devtunnel: node?.devtunnelEndpoint, status: node?.status });
48078
48273
  if (node) {
48079
- const seededLogs = await seedTaskSnapshotOnWorker(task, [], node, log2).catch((err) => {
48080
- log2.warn("failed to seed task snapshot before task logs proxy", {
48081
- taskId,
48082
- assignedTo,
48083
- ...describeProxyError3(err)
48274
+ if (task.assignedTo === proxyNodeId && !snapshotOwnerId) {
48275
+ const seededLogs = await seedTaskSnapshotOnWorker(task, [], node, log2).catch((err) => {
48276
+ log2.warn("failed to seed task snapshot before task logs proxy", {
48277
+ taskId,
48278
+ assignedTo: proxyNodeId,
48279
+ ...describeProxyError3(err)
48280
+ });
48281
+ return [];
48084
48282
  });
48085
- return [];
48086
- });
48087
- if (seededLogs.length > 0) recordLocalTaskLogs(engineRegistry, task, seededLogs);
48283
+ if (seededLogs.length > 0) recordLocalTaskLogs(engineRegistry, task, seededLogs);
48284
+ }
48088
48285
  const proxyPath = `/api/tasks/${taskId}/logs?after=${after}`;
48089
48286
  try {
48090
48287
  const { endpoint, response: proxyRes } = await fetchNodeWithFallback(
@@ -48098,7 +48295,7 @@ async function sendTaskLogsResponse(req, res, taskId) {
48098
48295
  const proxyUrl = `${endpoint}${proxyPath}`;
48099
48296
  log2.info("proxying task logs request", {
48100
48297
  taskId,
48101
- assignedTo,
48298
+ assignedTo: proxyNodeId,
48102
48299
  endpoint,
48103
48300
  proxyPath,
48104
48301
  url: proxyUrl
@@ -48111,11 +48308,11 @@ async function sendTaskLogsResponse(req, res, taskId) {
48111
48308
  } catch (err) {
48112
48309
  log2.warn("task logs proxy failed; falling back to keepalive control", {
48113
48310
  taskId,
48114
- assignedTo,
48311
+ assignedTo: proxyNodeId,
48115
48312
  timeoutMs: TASK_LOG_PROXY_TIMEOUT_MS,
48116
48313
  ...describeProxyError3(err)
48117
48314
  });
48118
- const fallback = await requestTaskLogsOverKeepalive(heartbeat, assignedTo, task, after);
48315
+ const fallback = await requestTaskLogsOverKeepalive(heartbeat, proxyNodeId, task, after);
48119
48316
  if (fallback) {
48120
48317
  sendWorkerControlResponse(res, fallback);
48121
48318
  return;
@@ -48506,6 +48703,8 @@ var TERMINAL_STATUSES3 = /* @__PURE__ */ new Set(["completed", "failed", "cancel
48506
48703
  var ARCHIVABLE_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "archived"]);
48507
48704
  var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned", "running"]);
48508
48705
  var TASK_DELETE_NOTIFY_TIMEOUT_MS = 1500;
48706
+ var TASK_DETAIL_PROXY_TIMEOUT_MS = 1e4;
48707
+ var TASK_DETAIL_PROXY_HEADER = "x-meshy-task-detail-proxy";
48509
48708
  function shouldGenerateTitle(task) {
48510
48709
  return task.payload.titleSource === "derived";
48511
48710
  }
@@ -48616,6 +48815,62 @@ function withShareMetadata(task, taskEngine, shareOrigin) {
48616
48815
  function toTaskResponse(task, nodeRegistry, taskEngine, shareOrigin) {
48617
48816
  return withShareMetadata(withAssignedNodeMetadata(task, nodeRegistry), taskEngine, shareOrigin);
48618
48817
  }
48818
+ async function proxyJoinSnapshotTaskDetail(deps) {
48819
+ if (deps.req.get(TASK_DETAIL_PROXY_HEADER) === "1") {
48820
+ return null;
48821
+ }
48822
+ const ownerNodeId = getJoinTaskSnapshotOwnerId(deps.task);
48823
+ const selfId = deps.nodeRegistry.getSelf()?.id;
48824
+ if (!ownerNodeId || ownerNodeId === selfId) {
48825
+ return null;
48826
+ }
48827
+ const owner = deps.nodeRegistry.getNode(ownerNodeId);
48828
+ const log2 = deps.logger.child("tasks/detail");
48829
+ if (!owner) {
48830
+ log2.warn("join snapshot owner is unavailable for task detail proxy", {
48831
+ taskId: deps.task.id,
48832
+ ownerNodeId
48833
+ });
48834
+ return null;
48835
+ }
48836
+ const proxyPath = `/api/tasks/${encodeURIComponent(deps.task.id)}`;
48837
+ try {
48838
+ const { endpoint, response } = await fetchNodeWithFallback(
48839
+ owner,
48840
+ proxyPath,
48841
+ { method: "GET", headers: { [TASK_DETAIL_PROXY_HEADER]: "1" } },
48842
+ TASK_DETAIL_PROXY_TIMEOUT_MS,
48843
+ void 0,
48844
+ { preferPublicEndpoint: true }
48845
+ );
48846
+ if (!response.ok) {
48847
+ log2.warn("join snapshot task detail proxy returned non-ok response", {
48848
+ taskId: deps.task.id,
48849
+ ownerNodeId,
48850
+ endpoint,
48851
+ statusCode: response.status
48852
+ });
48853
+ return null;
48854
+ }
48855
+ const fullTask = await response.json();
48856
+ if (fullTask.id !== deps.task.id) {
48857
+ log2.warn("join snapshot task detail proxy returned a different task id", {
48858
+ taskId: deps.task.id,
48859
+ ownerNodeId,
48860
+ returnedTaskId: fullTask.id
48861
+ });
48862
+ return null;
48863
+ }
48864
+ return fullTask;
48865
+ } catch (err) {
48866
+ log2.warn("join snapshot task detail proxy failed", {
48867
+ taskId: deps.task.id,
48868
+ ownerNodeId,
48869
+ error: err instanceof Error ? err.message : String(err)
48870
+ });
48871
+ return null;
48872
+ }
48873
+ }
48619
48874
  function buildShareUrl(origin, shareId) {
48620
48875
  return `${origin.replace(/\/+$/, "")}/shared/tasks/${encodeURIComponent(shareId)}`;
48621
48876
  }
@@ -48739,14 +48994,15 @@ function createTaskRoutes() {
48739
48994
  res.json({ results });
48740
48995
  }));
48741
48996
  router.get("/:id", asyncHandler6(async (req, res) => {
48742
- const { taskEngine, nodeRegistry, shareOrigin } = req.app.locals.deps;
48997
+ const { taskEngine, nodeRegistry, shareOrigin, logger: logger27 } = req.app.locals.deps;
48743
48998
  const taskId = req.params.id;
48744
48999
  const task = taskEngine.getTask(taskId);
48745
49000
  if (!task) {
48746
49001
  if (await maybeProxyReadToLeader(req, res)) return;
48747
49002
  throw new MeshyError("TASK_NOT_FOUND", `Task ${taskId} not found`, 404);
48748
49003
  }
48749
- res.json(toTaskResponse(task, nodeRegistry, taskEngine, shareOrigin));
49004
+ const fullTask = await proxyJoinSnapshotTaskDetail({ task, req, logger: logger27, nodeRegistry });
49005
+ res.json(toTaskResponse(fullTask ?? task, nodeRegistry, taskEngine, shareOrigin));
48750
49006
  }));
48751
49007
  router.post("/:id/share", asyncHandler6(async (req, res) => {
48752
49008
  const { taskEngine, ensureShareTunnel } = req.app.locals.deps;
@@ -49378,7 +49634,8 @@ function createSystemRoutes() {
49378
49634
  localDashboardOrigin,
49379
49635
  storagePath,
49380
49636
  inspectRuntimeTools: inspectTools,
49381
- runtimeMetadata
49637
+ runtimeMetadata,
49638
+ runtimeUpdate
49382
49639
  } = req.app.locals.deps;
49383
49640
  const self2 = nodeRegistry.getSelf();
49384
49641
  const auth = config.authMetadata ?? {
@@ -49391,6 +49648,7 @@ function createSystemRoutes() {
49391
49648
  inspectRuntimeTools: inspectTools,
49392
49649
  localDashboardOrigin,
49393
49650
  runtimeMetadata,
49651
+ runtimeUpdate,
49394
49652
  storagePath,
49395
49653
  workDir: self2.workDir
49396
49654
  });
@@ -49406,6 +49664,7 @@ function createSystemRoutes() {
49406
49664
  auth: settingsSnapshot.auth,
49407
49665
  os: settingsSnapshot.os,
49408
49666
  runtime: settingsSnapshot.runtime,
49667
+ runtimeUpdate: settingsSnapshot.runtimeUpdate,
49409
49668
  agents: settingsSnapshot.agents,
49410
49669
  startupRequirements: settingsSnapshot.startupRequirements,
49411
49670
  components: settingsSnapshot.components,
@@ -49413,6 +49672,23 @@ function createSystemRoutes() {
49413
49672
  packages: settingsSnapshot.packages
49414
49673
  });
49415
49674
  }));
49675
+ router.post("/runtime/restart", asyncHandler9(async (req, res) => {
49676
+ const { restartRuntime } = req.app.locals.deps;
49677
+ if (!restartRuntime) {
49678
+ throw new MeshyError("VALIDATION_ERROR", "Runtime restart is not available for this node", 501);
49679
+ }
49680
+ const restartRequest = parseRuntimeRestartRequest(req.body);
49681
+ const result = await restartRuntime(restartRequest);
49682
+ res.status(202).json(result);
49683
+ }));
49684
+ router.post("/runtime/stop", asyncHandler9(async (req, res) => {
49685
+ const { stopRuntime } = req.app.locals.deps;
49686
+ if (!stopRuntime) {
49687
+ throw new MeshyError("VALIDATION_ERROR", "Runtime stop is not available for this node", 501);
49688
+ }
49689
+ const result = await stopRuntime();
49690
+ res.status(202).json(result);
49691
+ }));
49416
49692
  router.post("/transport", asyncHandler9(async (req, res) => {
49417
49693
  const { switchTransport } = req.app.locals.deps;
49418
49694
  if (!switchTransport) {
@@ -49581,6 +49857,9 @@ function createNodeOperationRoutes() {
49581
49857
  var JSON_BODY_LIMIT = "1mb";
49582
49858
  var JSON_BODY_LIMIT_LARGE = "25mb";
49583
49859
  var TRUSTED_LOCAL_HOSTS2 = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
49860
+ function usesLargeJsonBodyLimit(pathname) {
49861
+ return pathname === "/api/cluster/join" || pathname === "/api/node" || pathname.startsWith("/api/node/") || pathname === "/api/tasks" || pathname.startsWith("/api/tasks/") || pathname === "/api/worker" || pathname.startsWith("/api/worker/");
49862
+ }
49584
49863
  function normalizeHost2(value) {
49585
49864
  const trimmed = value?.trim().toLowerCase();
49586
49865
  return trimmed ? trimmed.replace(/:\d+$/, "") : void 0;
@@ -49707,7 +49986,12 @@ function createServer2(deps) {
49707
49986
  staticMiddleware(req, res, next);
49708
49987
  });
49709
49988
  }
49710
- app.use(import_express14.default.json({ limit: JSON_BODY_LIMIT }));
49989
+ const defaultJsonParser = import_express14.default.json({ limit: JSON_BODY_LIMIT });
49990
+ const largeJsonParser = import_express14.default.json({ limit: JSON_BODY_LIMIT_LARGE });
49991
+ app.use((req, res, next) => {
49992
+ const parser = usesLargeJsonBodyLimit(req.path) ? largeJsonParser : defaultJsonParser;
49993
+ parser(req, res, next);
49994
+ });
49711
49995
  app.use(createAuthMiddleware(authConfig));
49712
49996
  app.use((req, res, next) => {
49713
49997
  void (async () => {
@@ -49733,14 +50017,13 @@ function createServer2(deps) {
49733
50017
  taskEngine: deps.taskEngine,
49734
50018
  logger: deps.logger
49735
50019
  }));
49736
- const largeBodyParser = import_express14.default.json({ limit: JSON_BODY_LIMIT_LARGE });
49737
50020
  app.use("/api/cluster", createClusterRoutes());
49738
50021
  app.use("/api/cluster-control", createClusterControlRoutes());
49739
- app.use("/api/node", largeBodyParser, createNodeMessageRoutes());
50022
+ app.use("/api/node", createNodeMessageRoutes());
49740
50023
  app.use("/api/nodes", createNodeRoutes());
49741
- app.use("/api/tasks", largeBodyParser, createTaskRoutes());
50024
+ app.use("/api/tasks", createTaskRoutes());
49742
50025
  app.use("/api/shared", createSharedRoutes());
49743
- app.use("/api/worker", largeBodyParser, createWorkerRoutes());
50026
+ app.use("/api/worker", createWorkerRoutes());
49744
50027
  app.use("/api/system", createSystemRoutes());
49745
50028
  app.use("/api/node-operations", createNodeOperationRoutes());
49746
50029
  app.use("/api/events", createEventRoutes());
@@ -50780,6 +51063,346 @@ function buildRuntimeMetadata(storagePath) {
50780
51063
  };
50781
51064
  }
50782
51065
 
51066
+ // src/bootstrap/runtime-restart.ts
51067
+ var fs22 = __toESM(require("fs"), 1);
51068
+ var nodePath2 = __toESM(require("path"), 1);
51069
+ var import_node_child_process14 = require("child_process");
51070
+ function resolveRuntimeRestartStartArgs(hydratedArgs, restartArgs) {
51071
+ return restartArgs ? { ...restartArgs } : hydratedArgs;
51072
+ }
51073
+ var runtimeUpdateCache = /* @__PURE__ */ new Map();
51074
+ var RESTARTER_SOURCE = String.raw`
51075
+ const { spawn } = require('node:child_process');
51076
+ const http = require('node:http');
51077
+
51078
+ const plan = JSON.parse(process.env.MESHY_RESTART_PLAN || '{}');
51079
+ const parentPid = Number(process.env.MESHY_RESTART_PARENT_PID || '0');
51080
+
51081
+ function log(message) {
51082
+ process.stdout.write('[' + new Date().toISOString() + '] ' + message + '\n');
51083
+ }
51084
+
51085
+ function sleep(ms) {
51086
+ return new Promise(resolve => setTimeout(resolve, ms));
51087
+ }
51088
+
51089
+ function isProcessAlive(pid) {
51090
+ if (!pid) return false;
51091
+ try {
51092
+ process.kill(pid, 0);
51093
+ return true;
51094
+ } catch {
51095
+ return false;
51096
+ }
51097
+ }
51098
+
51099
+ function requestHealth(url) {
51100
+ return new Promise(resolve => {
51101
+ const req = http.get(url, res => {
51102
+ res.resume();
51103
+ res.on('end', () => resolve(res.statusCode >= 200 && res.statusCode < 500));
51104
+ });
51105
+ req.setTimeout(1000, () => {
51106
+ req.destroy();
51107
+ resolve(false);
51108
+ });
51109
+ req.on('error', () => resolve(false));
51110
+ });
51111
+ }
51112
+
51113
+ async function waitForParentExit() {
51114
+ const deadline = Date.now() + 30000;
51115
+ while (isProcessAlive(parentPid) && Date.now() < deadline) {
51116
+ await sleep(250);
51117
+ }
51118
+ }
51119
+
51120
+ async function waitForEndpointDown() {
51121
+ if (!plan.healthUrl) return;
51122
+ const deadline = Date.now() + 30000;
51123
+ while (Date.now() < deadline) {
51124
+ if (!await requestHealth(plan.healthUrl)) return;
51125
+ await sleep(250);
51126
+ }
51127
+ }
51128
+
51129
+ (async () => {
51130
+ log('waiting to restart ' + (plan.displayCommand || plan.command));
51131
+ await waitForParentExit();
51132
+ await waitForEndpointDown();
51133
+ log('starting ' + (plan.displayCommand || plan.command));
51134
+ const child = spawn(plan.command, plan.args || [], {
51135
+ cwd: plan.cwd,
51136
+ env: { ...process.env, ...(plan.env || {}), MESHY_RESTART_CHILD: '1' },
51137
+ detached: true,
51138
+ stdio: ['ignore', 'inherit', 'inherit'],
51139
+ windowsHide: true,
51140
+ });
51141
+ child.on('error', error => {
51142
+ log('failed to launch restart command: ' + (error && error.message ? error.message : String(error)));
51143
+ process.exitCode = 1;
51144
+ });
51145
+ child.unref();
51146
+ await sleep(500);
51147
+ })().catch(error => {
51148
+ log('restart helper failed: ' + (error && error.message ? error.message : String(error)));
51149
+ process.exit(1);
51150
+ });
51151
+ `;
51152
+ function formatLocalDate3(date) {
51153
+ const year = date.getFullYear();
51154
+ const month = String(date.getMonth() + 1).padStart(2, "0");
51155
+ const day = String(date.getDate()).padStart(2, "0");
51156
+ return `${year}-${month}-${day}`;
51157
+ }
51158
+ function normalizeOutput2(output) {
51159
+ if (!output) return null;
51160
+ const firstLine = output.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
51161
+ return firstLine ?? null;
51162
+ }
51163
+ function normalizePackageVersion2(output) {
51164
+ const normalized = normalizeOutput2(output);
51165
+ if (!normalized) return null;
51166
+ try {
51167
+ const parsed = JSON.parse(normalized);
51168
+ return typeof parsed === "string" ? parsed.trim() || null : normalized;
51169
+ } catch {
51170
+ return normalized;
51171
+ }
51172
+ }
51173
+ function compareSemver2(left, right) {
51174
+ const leftParts = left.split(/[.-]/).map((part) => Number.parseInt(part, 10));
51175
+ const rightParts = right.split(/[.-]/).map((part) => Number.parseInt(part, 10));
51176
+ for (let index = 0; index < 3; index += 1) {
51177
+ const delta = (leftParts[index] || 0) - (rightParts[index] || 0);
51178
+ if (delta !== 0) return delta;
51179
+ }
51180
+ return 0;
51181
+ }
51182
+ function runRuntimePackageCommand(command, args) {
51183
+ const result = (0, import_node_child_process14.spawnSync)(command, args, {
51184
+ encoding: "utf-8",
51185
+ windowsHide: true,
51186
+ timeout: 2500
51187
+ });
51188
+ return {
51189
+ status: result.status,
51190
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
51191
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
51192
+ error: result.error?.message ?? null
51193
+ };
51194
+ }
51195
+ function commandName(base, platform2) {
51196
+ return platform2 === "win32" ? `${base}.cmd` : base;
51197
+ }
51198
+ function pathForPlatform(platform2) {
51199
+ return platform2 === "win32" ? nodePath2.win32 : nodePath2;
51200
+ }
51201
+ function resolveDevelopmentRepoRoot(cwd, env, platform2) {
51202
+ if (env.MESHY_STORAGE_BASE_DIR) return env.MESHY_STORAGE_BASE_DIR;
51203
+ const pathApi = pathForPlatform(platform2);
51204
+ const packageDir = pathApi.basename(cwd);
51205
+ const packageParent = pathApi.basename(pathApi.dirname(cwd));
51206
+ return packageDir === "node" && packageParent === "apps" ? pathApi.dirname(pathApi.dirname(cwd)) : cwd;
51207
+ }
51208
+ function windowsShellQuote(value) {
51209
+ return /[\s&()^|<>"]/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
51210
+ }
51211
+ function createSpawnableCommand(command, args, platform2) {
51212
+ const displayCommand = [command, ...args].map(shellQuote).join(" ");
51213
+ if (platform2 !== "win32") return { command, args, displayCommand };
51214
+ return {
51215
+ command: "cmd.exe",
51216
+ args: ["/d", "/s", "/c", [command, ...args].map(windowsShellQuote).join(" ")],
51217
+ displayCommand
51218
+ };
51219
+ }
51220
+ function readProcessCommand(pid, platform2) {
51221
+ if (platform2 === "win32") return null;
51222
+ const result = (0, import_node_child_process14.spawnSync)("ps", ["-p", String(pid), "-o", "args="], {
51223
+ encoding: "utf-8",
51224
+ windowsHide: true,
51225
+ timeout: 1e3
51226
+ });
51227
+ return result.status === 0 && typeof result.stdout === "string" ? result.stdout.trim() || null : null;
51228
+ }
51229
+ function isNodeWatchCommand(command) {
51230
+ return /(?:^|[/\\])node(?:\.exe)?(?:\s|$)/.test(command) && /(?:^|\s)--watch(?:=|\s|$)/.test(command);
51231
+ }
51232
+ function terminateRuntimeWatchParent(options) {
51233
+ if (options.mode !== "development") return { terminated: false, reason: "not-development" };
51234
+ const parentPid = options.parentPid ?? process.ppid;
51235
+ if (!Number.isInteger(parentPid) || parentPid <= 1 || parentPid === process.pid) {
51236
+ return { terminated: false, reason: "invalid-parent" };
51237
+ }
51238
+ const commandReader = options.commandReader ?? ((pid) => readProcessCommand(pid, options.platform ?? process.platform));
51239
+ const parentCommand = commandReader(parentPid);
51240
+ const parentIsWatch = parentCommand !== null ? isNodeWatchCommand(parentCommand) : options.env?.WATCH_REPORT_DEPENDENCIES === "1";
51241
+ if (!parentIsWatch) return { terminated: false, reason: "parent-not-watch" };
51242
+ try {
51243
+ const kill = options.kill ?? process.kill;
51244
+ kill(parentPid, "SIGINT");
51245
+ return { terminated: true, pid: parentPid };
51246
+ } catch {
51247
+ return { terminated: false, reason: "kill-failed" };
51248
+ }
51249
+ }
51250
+ function compactEnv(env) {
51251
+ const result = {};
51252
+ for (const [key, value] of Object.entries(env)) {
51253
+ if (typeof value === "string") result[key] = value;
51254
+ }
51255
+ return result;
51256
+ }
51257
+ function shellQuote(value) {
51258
+ return /\s/.test(value) ? JSON.stringify(value) : value;
51259
+ }
51260
+ function formatStartArgs(args) {
51261
+ const result = ["start"];
51262
+ if (args.port !== void 0) result.push("--port", String(args.port));
51263
+ if (args.name) result.push("--name", args.name);
51264
+ if (args.join) result.push("--join", args.join);
51265
+ if (args.transport) result.push("--transport", args.transport);
51266
+ if (args.workDir) result.push("--work-dir", args.workDir);
51267
+ if (args.config) result.push("--config", args.config);
51268
+ if (args.disableAuth) result.push("--disable-auth");
51269
+ return result;
51270
+ }
51271
+ function detectRuntimeRestartMode(env = process.env, entryPath = process.argv[1]) {
51272
+ if (env.MESHY_RESTART_MODE === "package" || env.MESHY_RESTART_MODE === "development") {
51273
+ return env.MESHY_RESTART_MODE;
51274
+ }
51275
+ if (env.MESHY_STORAGE_MODE === "cwd" && env.MESHY_STORAGE_BASE_DIR) {
51276
+ return "development";
51277
+ }
51278
+ return typeof entryPath === "string" && /src[/\\]main\.ts$/.test(entryPath) ? "development" : "package";
51279
+ }
51280
+ function createRuntimeRestartLaunchPlan(options) {
51281
+ const platform2 = options.platform ?? process.platform;
51282
+ const startArgs = formatStartArgs(options.startArgs);
51283
+ const env = compactEnv(options.env);
51284
+ const logPath = pathForPlatform(platform2).join(options.storagePath, "logs", "restart.log");
51285
+ const healthUrl = new URL("/api/system/health", options.localDashboardOrigin).toString();
51286
+ if (options.mode === "development") {
51287
+ const repoRoot2 = resolveDevelopmentRepoRoot(options.cwd, options.env, platform2);
51288
+ const launch2 = createSpawnableCommand(commandName("pnpm", platform2), ["dev:node", "--", ...startArgs], platform2);
51289
+ return {
51290
+ mode: "development",
51291
+ command: launch2.command,
51292
+ args: launch2.args,
51293
+ cwd: repoRoot2,
51294
+ env,
51295
+ displayCommand: launch2.displayCommand,
51296
+ logPath,
51297
+ healthUrl
51298
+ };
51299
+ }
51300
+ const launch = createSpawnableCommand(commandName("npx", platform2), ["-y", "meshy-node@latest", ...startArgs], platform2);
51301
+ return {
51302
+ mode: "package",
51303
+ command: launch.command,
51304
+ args: launch.args,
51305
+ cwd: options.cwd,
51306
+ env,
51307
+ displayCommand: launch.displayCommand,
51308
+ logPath,
51309
+ healthUrl
51310
+ };
51311
+ }
51312
+ function scheduleRuntimeRestart(plan, options = {}) {
51313
+ fs22.mkdirSync(nodePath2.dirname(plan.logPath), { recursive: true });
51314
+ const logFd = fs22.openSync(plan.logPath, "a");
51315
+ const spawnImpl = options.spawnImpl ?? import_node_child_process14.spawn;
51316
+ const env = {
51317
+ ...process.env,
51318
+ MESHY_RESTART_PARENT_PID: String(options.parentPid ?? process.pid),
51319
+ MESHY_RESTART_PLAN: JSON.stringify(plan)
51320
+ };
51321
+ const child = spawnImpl(process.execPath, ["-e", RESTARTER_SOURCE], {
51322
+ detached: true,
51323
+ env,
51324
+ stdio: ["ignore", logFd, logFd],
51325
+ windowsHide: true
51326
+ });
51327
+ child.unref();
51328
+ }
51329
+ function createRuntimeUpdateSnapshot(options) {
51330
+ const now = options.now ?? /* @__PURE__ */ new Date();
51331
+ const checkedAt = now.toISOString();
51332
+ const currentVersion = options.runtimeMetadata?.packageVersion ?? null;
51333
+ const packageName = "meshy-node";
51334
+ if (options.mode === "development") {
51335
+ return {
51336
+ packageName,
51337
+ currentVersion,
51338
+ latestVersion: null,
51339
+ updateAvailable: false,
51340
+ checkedAt,
51341
+ restartMode: "development",
51342
+ detail: "Development runtime restarts from the local workspace."
51343
+ };
51344
+ }
51345
+ const cache = options.updateCache ?? runtimeUpdateCache;
51346
+ const checkedOn = formatLocalDate3(now);
51347
+ const cached = cache.get(packageName);
51348
+ let latestVersion = cached?.latestVersion ?? null;
51349
+ let detail = cached?.detail ?? null;
51350
+ let updateCheckedAt = cached?.checkedAt ?? checkedAt;
51351
+ if (cached?.checkedOn !== checkedOn) {
51352
+ const runner = options.commandRunner ?? runRuntimePackageCommand;
51353
+ const result = runner("npm", ["view", `${packageName}@latest`, "version", "--json"]);
51354
+ latestVersion = result.status === 0 ? normalizePackageVersion2(result.stdout) : null;
51355
+ detail = result.status === 0 ? null : normalizeOutput2(result.stderr) ?? result.error ?? "Runtime update check failed";
51356
+ updateCheckedAt = checkedAt;
51357
+ cache.set(packageName, { checkedOn, checkedAt, latestVersion, detail });
51358
+ }
51359
+ return {
51360
+ packageName,
51361
+ currentVersion,
51362
+ latestVersion,
51363
+ updateAvailable: Boolean(currentVersion && latestVersion && compareSemver2(latestVersion, currentVersion) > 0),
51364
+ checkedAt: updateCheckedAt,
51365
+ restartMode: "package",
51366
+ detail
51367
+ };
51368
+ }
51369
+
51370
+ // src/bootstrap/settings-snapshot.ts
51371
+ function createSettingsSnapshotProvider(options) {
51372
+ let cachedSnapshot = null;
51373
+ let cachedSnapshotAt = 0;
51374
+ function buildSnapshot() {
51375
+ cachedSnapshot = buildNodeSettingsSnapshot({
51376
+ auth: options.authMetadata,
51377
+ localDashboardOrigin: options.localDashboardOrigin,
51378
+ runtimeMetadata: options.runtimeMetadata,
51379
+ runtimeUpdate: createRuntimeUpdateSnapshot({
51380
+ mode: options.runtimeRestartMode,
51381
+ runtimeMetadata: options.runtimeMetadata
51382
+ }),
51383
+ storagePath: options.storagePath,
51384
+ workDir: options.workDir
51385
+ });
51386
+ cachedSnapshotAt = Date.now();
51387
+ return structuredClone(cachedSnapshot);
51388
+ }
51389
+ function getSettingsSnapshot() {
51390
+ const now = Date.now();
51391
+ if (cachedSnapshot && now - cachedSnapshotAt < 3e4) {
51392
+ return structuredClone(cachedSnapshot);
51393
+ }
51394
+ return buildSnapshot();
51395
+ }
51396
+ return {
51397
+ getSettingsSnapshot,
51398
+ refreshSettingsSnapshot: () => {
51399
+ cachedSnapshot = null;
51400
+ cachedSnapshotAt = 0;
51401
+ return buildSnapshot();
51402
+ }
51403
+ };
51404
+ }
51405
+
50783
51406
  // src/bootstrap/tunnel-health.ts
50784
51407
  var DEFAULT_TUNNEL_REACHABILITY_INTERVAL_MS = 6e4;
50785
51408
  var DEFAULT_TUNNEL_REACHABILITY_TIMEOUT_MS = 5e3;
@@ -50857,36 +51480,6 @@ async function fetchWithTimeout2(fetchImpl, url, timeoutMs) {
50857
51480
  }
50858
51481
 
50859
51482
  // src/bootstrap/start-node.ts
50860
- function createSettingsSnapshotProvider(options) {
50861
- let cachedSnapshot = null;
50862
- let cachedSnapshotAt = 0;
50863
- function buildSnapshot() {
50864
- cachedSnapshot = buildNodeSettingsSnapshot({
50865
- auth: options.authMetadata,
50866
- localDashboardOrigin: options.localDashboardOrigin,
50867
- runtimeMetadata: options.runtimeMetadata,
50868
- storagePath: options.storagePath,
50869
- workDir: options.workDir
50870
- });
50871
- cachedSnapshotAt = Date.now();
50872
- return structuredClone(cachedSnapshot);
50873
- }
50874
- function getSettingsSnapshot() {
50875
- const now = Date.now();
50876
- if (cachedSnapshot && now - cachedSnapshotAt < 3e4) {
50877
- return structuredClone(cachedSnapshot);
50878
- }
50879
- return buildSnapshot();
50880
- }
50881
- return {
50882
- getSettingsSnapshot,
50883
- refreshSettingsSnapshot: () => {
50884
- cachedSnapshot = null;
50885
- cachedSnapshotAt = 0;
50886
- return buildSnapshot();
50887
- }
50888
- };
50889
- }
50890
51483
  async function resolveStartConfig(args) {
50891
51484
  const configPath = args.config ?? "./config.json";
50892
51485
  const fileConfig = loadConfigFile(configPath);
@@ -50916,7 +51509,7 @@ async function resolveStartConfig(args) {
50916
51509
  ignorePersistedName: args.reset === true
50917
51510
  });
50918
51511
  const config = mergeConfig(defaults, scopedFileConfig, resolvedArgs);
50919
- setStartupFatalLogPath(nodePath2.join(config.storage.path, "logs", "startup-fatal.log"));
51512
+ setStartupFatalLogPath(nodePath3.join(config.storage.path, "logs", "startup-fatal.log"));
50920
51513
  persistDefaultNodeName(storageRoot, config.node.name);
50921
51514
  persistDefaultNodeName(config.storage.path, config.node.name);
50922
51515
  persistNodeStartupMetadata(config.storage.path, {
@@ -51271,15 +51864,20 @@ function createTunnelManager(options) {
51271
51864
  }
51272
51865
  function registerShutdownHandlers(options) {
51273
51866
  let shuttingDown = false;
51274
- async function shutdown() {
51867
+ function stopDevelopmentWatchParent(runtimeMode) {
51868
+ if (runtimeMode) terminateRuntimeWatchParent({ mode: runtimeMode, env: process.env });
51869
+ }
51870
+ async function shutdown(reason = "shutdown", runtimeMode) {
51275
51871
  if (shuttingDown) {
51276
51872
  terminalWriter.line("Force exit.");
51873
+ stopDevelopmentWatchParent(runtimeMode);
51277
51874
  process.exit(130);
51278
51875
  }
51279
51876
  shuttingDown = true;
51280
- terminalWriter.line("\nShutting down...");
51877
+ terminalWriter.line(reason === "restart" ? "\nRestarting..." : "\nShutting down...");
51281
51878
  const forceExitTimer = setTimeout(() => {
51282
51879
  terminalWriter.line("Force exit.");
51880
+ stopDevelopmentWatchParent(runtimeMode);
51283
51881
  process.exit(0);
51284
51882
  }, 1e4);
51285
51883
  forceExitTimer.unref?.();
@@ -51289,15 +51887,21 @@ function registerShutdownHandlers(options) {
51289
51887
  await options.tunnelManager.stop();
51290
51888
  await options.meshyNode.stop();
51291
51889
  clearTimeout(forceExitTimer);
51292
- terminalWriter.line("Goodbye!");
51890
+ terminalWriter.line(reason === "restart" ? "Restart handoff complete." : "Goodbye!");
51891
+ stopDevelopmentWatchParent(runtimeMode);
51293
51892
  process.exit(0);
51294
51893
  } catch (err) {
51295
51894
  terminalWriter.error("Error during shutdown:", err);
51296
51895
  process.exit(1);
51297
51896
  }
51298
51897
  }
51299
- process.on("SIGINT", shutdown);
51300
- process.on("SIGTERM", shutdown);
51898
+ process.on("SIGINT", () => void shutdown("shutdown"));
51899
+ process.on("SIGTERM", () => void shutdown("shutdown"));
51900
+ return {
51901
+ requestShutdown: (reason = "shutdown", runtimeMode) => {
51902
+ void shutdown(reason, runtimeMode);
51903
+ }
51904
+ };
51301
51905
  }
51302
51906
  async function startNode(args) {
51303
51907
  const { authMetadata, config, hydratedArgs, runtimeMetadata, storageRoot } = await resolveStartConfig(args);
@@ -51309,7 +51913,7 @@ async function startNode(args) {
51309
51913
  const localDashboardOrigin = `http://localhost:${config.node.port}`;
51310
51914
  const logger27 = createLogger({
51311
51915
  component: "node",
51312
- logDir: nodePath2.join(config.storage.path, "logs"),
51916
+ logDir: nodePath3.join(config.storage.path, "logs"),
51313
51917
  console: true
51314
51918
  });
51315
51919
  const nodeAuth = new AzureCliNodeAuth(config.storage.path, {
@@ -51331,8 +51935,9 @@ async function startNode(args) {
51331
51935
  authMetadata,
51332
51936
  localDashboardOrigin,
51333
51937
  runtimeMetadata,
51938
+ runtimeRestartMode: detectRuntimeRestartMode(),
51334
51939
  storagePath: config.storage.path,
51335
- workDir: nodePath2.resolve(config.node.workDir ?? process.cwd())
51940
+ workDir: nodePath3.resolve(config.node.workDir ?? process.cwd())
51336
51941
  });
51337
51942
  const { getSettingsSnapshot, refreshSettingsSnapshot } = settingsSnapshotProvider;
51338
51943
  const meshyNode = new MeshyNode(config, {
@@ -51348,6 +51953,7 @@ async function startNode(args) {
51348
51953
  const previewSessionManager = new PreviewSessionManager();
51349
51954
  const previewProxyManager = new PreviewProxyManager();
51350
51955
  let deps;
51956
+ let requestShutdownForRestart;
51351
51957
  const tunnelManager = createTunnelManager({
51352
51958
  authMetadata,
51353
51959
  config,
@@ -51424,7 +52030,39 @@ async function startNode(args) {
51424
52030
  isDevTunnelEnabled: () => meshyNode.isDevTunnelEnabled(),
51425
52031
  localDashboardOrigin,
51426
52032
  upgradeRuntimeAgent,
52033
+ restartRuntime: async (request) => {
52034
+ const mode = detectRuntimeRestartMode();
52035
+ const plan = createRuntimeRestartLaunchPlan({
52036
+ mode,
52037
+ cwd: process.cwd(),
52038
+ env: process.env,
52039
+ localDashboardOrigin,
52040
+ startArgs: resolveRuntimeRestartStartArgs(hydratedArgs.args, request.startArgs),
52041
+ storagePath: config.storage.path
52042
+ });
52043
+ scheduleRuntimeRestart(plan, { parentPid: process.pid });
52044
+ const completeShutdown = () => setTimeout(() => requestShutdownForRestart?.("restart", mode), 250).unref?.();
52045
+ if (!request.deferShutdown) completeShutdown();
52046
+ return {
52047
+ ok: true,
52048
+ mode: plan.mode,
52049
+ command: plan.displayCommand,
52050
+ logPath: plan.logPath,
52051
+ pid: process.pid,
52052
+ completeShutdown
52053
+ };
52054
+ },
52055
+ stopRuntime: async () => {
52056
+ const pid = process.pid;
52057
+ const mode = detectRuntimeRestartMode();
52058
+ setTimeout(() => requestShutdownForRestart?.("shutdown", mode), 250).unref?.();
52059
+ return { ok: true, pid };
52060
+ },
51427
52061
  refreshSettingsSnapshot,
52062
+ runtimeUpdate: createRuntimeUpdateSnapshot({
52063
+ mode: detectRuntimeRestartMode(),
52064
+ runtimeMetadata
52065
+ }),
51428
52066
  dashboardOrigin: tunnelManager.getDashboardOrigin(),
51429
52067
  shareOrigin: tunnelManager.getShareOrigin(),
51430
52068
  ensureShareTunnel: () => tunnelManager.ensureShareTunnel(),
@@ -51456,11 +52094,12 @@ async function startNode(args) {
51456
52094
  terminalWriter.line(banner);
51457
52095
  terminalWriter.line("");
51458
52096
  });
51459
- registerShutdownHandlers({
52097
+ const shutdownHandle = registerShutdownHandlers({
51460
52098
  server,
51461
52099
  meshyNode,
51462
52100
  tunnelManager
51463
52101
  });
52102
+ requestShutdownForRestart = shutdownHandle.requestShutdown;
51464
52103
  }
51465
52104
 
51466
52105
  // src/main.ts