meshy-node 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import{c as s,r as t,j as a,b as n}from"./index-DHC5U3_j.js";/**
1
+ import{c as s,r as t,j as a,b as n}from"./index-n69fx3B-.js";/**
2
2
  * @license lucide-react v1.7.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Meshy Dashboard</title>
7
7
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>&#x1F578;</text></svg>" />
8
- <script type="module" crossorigin src="/assets/index-DHC5U3_j.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-DfiptIs_.css">
8
+ <script type="module" crossorigin src="/assets/index-n69fx3B-.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Ch6zIZ6Y.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/main.cjs CHANGED
@@ -34569,6 +34569,10 @@ var NODE_KIND_BY_LEGACY = {
34569
34569
  "node-workdir-tree": "node.workdir.tree",
34570
34570
  "node-workdir-branch-info": "node.workdir.branch-info",
34571
34571
  "node-workdir-branch-create": "node.workdir.branch-create",
34572
+ "node-terminal-session-start": "node.terminal.session.start",
34573
+ "node-terminal-session-list": "node.terminal.session.list",
34574
+ "node-terminal-session-get": "node.terminal.session.get",
34575
+ "node-terminal-session-stop": "node.terminal.session.stop",
34572
34576
  "node-sessions-list": "node.sessions.list",
34573
34577
  devtunnel: "node.transport.set",
34574
34578
  "node-agent-upgrade": "node.agent.upgrade",
@@ -43074,7 +43078,7 @@ var NativeSessionSummarySchema = external_exports.object({
43074
43078
  summary: external_exports.string(),
43075
43079
  updatedAt: external_exports.string().nullable()
43076
43080
  });
43077
- var NodeOperationStatusSchema = external_exports.enum(["queued", "running", "succeeded", "failed"]);
43081
+ var NodeTerminalSessionStatusSchema = external_exports.enum(["running", "exited", "failed", "stopped"]);
43078
43082
  var NodeListQuery = external_exports.object({
43079
43083
  status: external_exports.enum(["online", "busy", "offline"]).optional(),
43080
43084
  capability: external_exports.string().optional()
@@ -43108,7 +43112,7 @@ var NodeNativeSessionsQuery = external_exports.object({
43108
43112
  agent: external_exports.enum(["codex", "claudecode"]),
43109
43113
  limit: external_exports.coerce.number().int().min(1).max(100).default(50)
43110
43114
  });
43111
- var NodeTerminalExecuteBody = external_exports.object({
43115
+ var NodeTerminalSessionStartBody = external_exports.object({
43112
43116
  command: external_exports.string().trim().min(1),
43113
43117
  cwd: external_exports.string().trim().min(1).optional()
43114
43118
  });
@@ -43153,31 +43157,27 @@ var NodeNativeSessionsResponse = external_exports.object({
43153
43157
  agent: external_exports.enum(["codex", "claudecode"]),
43154
43158
  sessions: external_exports.array(NativeSessionSummarySchema)
43155
43159
  });
43156
- var NodeTerminalExecuteResult = external_exports.object({
43160
+ var NodeTerminalSessionResponse = external_exports.object({
43161
+ id: external_exports.string(),
43157
43162
  nodeId: external_exports.string(),
43158
43163
  cwd: external_exports.string(),
43159
43164
  command: external_exports.string(),
43165
+ pid: external_exports.number().int().nullable(),
43166
+ status: NodeTerminalSessionStatusSchema,
43160
43167
  exitCode: external_exports.number().int().nullable(),
43168
+ signal: external_exports.string().nullable(),
43161
43169
  stdout: external_exports.string(),
43162
43170
  stderr: external_exports.string(),
43171
+ startedAt: external_exports.number(),
43172
+ updatedAt: external_exports.number(),
43173
+ completedAt: external_exports.number().optional(),
43163
43174
  durationMs: external_exports.number().int().min(0),
43164
- timedOut: external_exports.boolean(),
43165
43175
  stdoutTruncated: external_exports.boolean(),
43166
43176
  stderrTruncated: external_exports.boolean()
43167
43177
  });
43168
- var NodeTerminalExecuteResponse = external_exports.object({
43169
- id: external_exports.string(),
43170
- kind: external_exports.literal("terminal.execute"),
43178
+ var NodeTerminalSessionListResponse = external_exports.object({
43171
43179
  nodeId: external_exports.string(),
43172
- requestedByNodeId: external_exports.string().optional(),
43173
- status: NodeOperationStatusSchema,
43174
- payload: NodeTerminalExecuteBody,
43175
- result: NodeTerminalExecuteResult.optional(),
43176
- error: external_exports.string().optional(),
43177
- createdAt: external_exports.number(),
43178
- updatedAt: external_exports.number(),
43179
- startedAt: external_exports.number().optional(),
43180
- completedAt: external_exports.number().optional()
43180
+ sessions: external_exports.array(NodeTerminalSessionResponse)
43181
43181
  });
43182
43182
  var UpdateNodeBody = external_exports.object({
43183
43183
  name: external_exports.string().min(1).optional(),
@@ -44503,8 +44503,8 @@ function computeParentPath(rootPath, currentPath, useAbsolute) {
44503
44503
  }
44504
44504
 
44505
44505
  // ../../packages/api/src/node/node-operation-service.ts
44506
- var fs16 = __toESM(require("fs"), 1);
44507
- var path18 = __toESM(require("path"), 1);
44506
+ var fs15 = __toESM(require("fs"), 1);
44507
+ var path17 = __toESM(require("path"), 1);
44508
44508
  var import_node_crypto7 = require("crypto");
44509
44509
 
44510
44510
  // ../../packages/api/src/node/agent-upgrade-service.ts
@@ -44790,95 +44790,6 @@ function upgradeRuntimeAgentForDeps(deps, agent) {
44790
44790
  return result;
44791
44791
  }
44792
44792
 
44793
- // ../../packages/api/src/node/node-terminal-service.ts
44794
- var import_node_child_process10 = require("child_process");
44795
- var fs15 = __toESM(require("fs"), 1);
44796
- var path17 = __toESM(require("path"), 1);
44797
- var DEFAULT_TIMEOUT_MS = 3e4;
44798
- var DEFAULT_OUTPUT_LIMIT_BYTES = 64 * 1024;
44799
- function isAbsolutePath2(value) {
44800
- return path17.isAbsolute(value) || /^[A-Za-z]:[\/]/.test(value);
44801
- }
44802
- function resolveCommandCwd(nodeId, rootPath, cwd) {
44803
- if (!rootPath) {
44804
- throw new MeshyError("VALIDATION_ERROR", `Node ${nodeId} does not expose a working directory`, 400);
44805
- }
44806
- const requestedCwd = cwd?.trim() || ".";
44807
- const resolved = isAbsolutePath2(requestedCwd) ? path17.resolve(requestedCwd) : path17.resolve(rootPath, requestedCwd);
44808
- if (!fs15.existsSync(resolved) || !fs15.statSync(resolved).isDirectory()) {
44809
- throw new MeshyError("VALIDATION_ERROR", `Working directory does not exist: ${resolved}`, 400);
44810
- }
44811
- return resolved;
44812
- }
44813
- function captureLimited(chunks, chunk, currentBytes, limitBytes) {
44814
- if (currentBytes >= limitBytes) {
44815
- return currentBytes + chunk.length;
44816
- }
44817
- const remaining = limitBytes - currentBytes;
44818
- chunks.push(chunk.length <= remaining ? chunk : chunk.subarray(0, remaining));
44819
- return currentBytes + chunk.length;
44820
- }
44821
- function toCapturedOutput(chunks, observedBytes, limitBytes) {
44822
- return {
44823
- text: Buffer.concat(chunks).toString("utf8"),
44824
- truncated: observedBytes > limitBytes
44825
- };
44826
- }
44827
- function executeLocalNodeTerminalCommand(nodeId, rootPath, command, options = {}) {
44828
- const normalizedCommand = command.trim();
44829
- if (!normalizedCommand) {
44830
- throw new MeshyError("VALIDATION_ERROR", "Command must not be empty", 400);
44831
- }
44832
- const cwd = resolveCommandCwd(nodeId, rootPath, options.cwd);
44833
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
44834
- const outputLimitBytes = options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES;
44835
- const startedAt = Date.now();
44836
- const stdoutChunks = [];
44837
- const stderrChunks = [];
44838
- let stdoutBytes = 0;
44839
- let stderrBytes = 0;
44840
- let timedOut = false;
44841
- return new Promise((resolve15, reject) => {
44842
- const child = (0, import_node_child_process10.spawn)(normalizedCommand, {
44843
- cwd,
44844
- shell: true,
44845
- windowsHide: true,
44846
- stdio: ["ignore", "pipe", "pipe"]
44847
- });
44848
- const timeout = setTimeout(() => {
44849
- timedOut = true;
44850
- child.kill("SIGTERM");
44851
- }, timeoutMs);
44852
- child.stdout?.on("data", (chunk) => {
44853
- stdoutBytes = captureLimited(stdoutChunks, chunk, stdoutBytes, outputLimitBytes);
44854
- });
44855
- child.stderr?.on("data", (chunk) => {
44856
- stderrBytes = captureLimited(stderrChunks, chunk, stderrBytes, outputLimitBytes);
44857
- });
44858
- child.on("error", (err) => {
44859
- clearTimeout(timeout);
44860
- reject(new MeshyError("VALIDATION_ERROR", err.message, 400));
44861
- });
44862
- child.on("close", (code) => {
44863
- clearTimeout(timeout);
44864
- const stdout = toCapturedOutput(stdoutChunks, stdoutBytes, outputLimitBytes);
44865
- const stderr = toCapturedOutput(stderrChunks, stderrBytes, outputLimitBytes);
44866
- resolve15({
44867
- nodeId,
44868
- cwd,
44869
- command: normalizedCommand,
44870
- exitCode: code,
44871
- stdout: stdout.text,
44872
- stderr: stderr.text,
44873
- durationMs: Date.now() - startedAt,
44874
- timedOut,
44875
- stdoutTruncated: stdout.truncated,
44876
- stderrTruncated: stderr.truncated
44877
- });
44878
- });
44879
- });
44880
- }
44881
-
44882
44793
  // ../../packages/api/src/node/node-operation-service.ts
44883
44794
  var NodeOperationFailure = class extends Error {
44884
44795
  constructor(message, result) {
@@ -44942,8 +44853,8 @@ var FileNodeOperationStore = class {
44942
44853
  load() {
44943
44854
  if (this.loaded) return;
44944
44855
  this.loaded = true;
44945
- if (!fs16.existsSync(this.filePath)) return;
44946
- const raw = JSON.parse(fs16.readFileSync(this.filePath, "utf-8"));
44856
+ if (!fs15.existsSync(this.filePath)) return;
44857
+ const raw = JSON.parse(fs15.readFileSync(this.filePath, "utf-8"));
44947
44858
  const entries = Array.isArray(raw) ? raw : Object.values(raw);
44948
44859
  for (const entry of entries) {
44949
44860
  if (isNodeOperation(entry)) {
@@ -44952,14 +44863,14 @@ var FileNodeOperationStore = class {
44952
44863
  }
44953
44864
  }
44954
44865
  persist() {
44955
- fs16.mkdirSync(this.storagePath, { recursive: true });
44866
+ fs15.mkdirSync(this.storagePath, { recursive: true });
44956
44867
  const body = JSON.stringify(Object.fromEntries(this.operations), null, 2);
44957
44868
  const tmpPath = `${this.filePath}.tmp`;
44958
- fs16.writeFileSync(tmpPath, body, "utf-8");
44959
- fs16.renameSync(tmpPath, this.filePath);
44869
+ fs15.writeFileSync(tmpPath, body, "utf-8");
44870
+ fs15.renameSync(tmpPath, this.filePath);
44960
44871
  }
44961
44872
  get filePath() {
44962
- return path18.join(this.storagePath, "node-operations.json");
44873
+ return path17.join(this.storagePath, "node-operations.json");
44963
44874
  }
44964
44875
  };
44965
44876
  var NodeOperationService = class {
@@ -44967,7 +44878,6 @@ var NodeOperationService = class {
44967
44878
  this.deps = deps;
44968
44879
  this.store = store;
44969
44880
  this.registerHandler("agent.upgrade", runAgentUpgradeOperation);
44970
- this.registerHandler("terminal.execute", runTerminalExecuteOperation);
44971
44881
  this.registerHandler("workdir.branch-create", runWorkDirBranchCreateOperation);
44972
44882
  }
44973
44883
  handlers = /* @__PURE__ */ new Map();
@@ -45113,15 +45023,6 @@ function runAgentUpgradeOperation(operation, deps) {
45113
45023
  }
45114
45024
  return result;
45115
45025
  }
45116
- async function runTerminalExecuteOperation(operation, deps) {
45117
- const self2 = deps.nodeRegistry.getSelf();
45118
- return executeLocalNodeTerminalCommand(
45119
- self2.id,
45120
- self2.workDir ?? deps.workDir,
45121
- readPayloadString(operation, "command"),
45122
- { cwd: readPayloadString(operation, "cwd") || void 0 }
45123
- );
45124
- }
45125
45026
  function runWorkDirBranchCreateOperation(operation, deps) {
45126
45027
  const self2 = deps.nodeRegistry.getSelf();
45127
45028
  return createLocalNodeWorkDirBranch(
@@ -45279,7 +45180,7 @@ async function sendNodeWorkDirBranchCreateOperation(req, res, nodeId) {
45279
45180
  }
45280
45181
 
45281
45182
  // ../../packages/api/src/tasks/task-route-utils.ts
45282
- var fs17 = __toESM(require("fs"), 1);
45183
+ var fs16 = __toESM(require("fs"), 1);
45283
45184
  var import_node_stream2 = require("stream");
45284
45185
  var import_promises5 = require("stream/promises");
45285
45186
 
@@ -45288,7 +45189,10 @@ var LEGACY_KIND_BY_NODE_MESSAGE = {
45288
45189
  "node.workdir.tree": "node-workdir-tree",
45289
45190
  "node.workdir.branch-info": "node-workdir-branch-info",
45290
45191
  "node.workdir.branch-create": "node-workdir-branch-create",
45291
- "node.terminal.execute": "node-terminal-execute",
45192
+ "node.terminal.session.start": "node-terminal-session-start",
45193
+ "node.terminal.session.list": "node-terminal-session-list",
45194
+ "node.terminal.session.get": "node-terminal-session-get",
45195
+ "node.terminal.session.stop": "node-terminal-session-stop",
45292
45196
  "node.sessions.list": "node-sessions-list",
45293
45197
  "node.transport.set": "devtunnel",
45294
45198
  "node.agent.upgrade": "node-agent-upgrade",
@@ -45304,9 +45208,13 @@ var LEGACY_KIND_BY_NODE_MESSAGE = {
45304
45208
  function canRequestNodeMessage(heartbeat) {
45305
45209
  return !!(heartbeat?.requestNodeMessage || heartbeat?.requestWorkerControl);
45306
45210
  }
45307
- function requestFallbackNodeMessage(heartbeat, nodeId, message) {
45308
- if (heartbeat.requestNodeMessage) return heartbeat.requestNodeMessage(nodeId, message);
45309
- if (heartbeat.requestWorkerControl) return heartbeat.requestWorkerControl(nodeId, toLegacyWorkerControl2(message));
45211
+ function requestFallbackNodeMessage(heartbeat, nodeId, message, timeoutMs) {
45212
+ if (heartbeat.requestNodeMessage) {
45213
+ return timeoutMs === void 0 ? heartbeat.requestNodeMessage(nodeId, message) : heartbeat.requestNodeMessage(nodeId, message, timeoutMs);
45214
+ }
45215
+ if (heartbeat.requestWorkerControl) {
45216
+ return timeoutMs === void 0 ? heartbeat.requestWorkerControl(nodeId, toLegacyWorkerControl2(message)) : heartbeat.requestWorkerControl(nodeId, toLegacyWorkerControl2(message), timeoutMs);
45217
+ }
45310
45218
  throw new Error("Node message fallback is not available");
45311
45219
  }
45312
45220
  function toLegacyWorkerControl2(message) {
@@ -45334,10 +45242,10 @@ function readLocalTaskLogs(engineRegistry, taskId, after, agent) {
45334
45242
  throw new MeshyError("VALIDATION_ERROR", `Engine not registered for agent: ${agent}`, 400);
45335
45243
  }
45336
45244
  const logPath = engine.getLogPath(taskId);
45337
- if (!fs17.existsSync(logPath)) {
45245
+ if (!fs16.existsSync(logPath)) {
45338
45246
  return { logs: [], total: 0 };
45339
45247
  }
45340
- const content = fs17.readFileSync(logPath, "utf-8");
45248
+ const content = fs16.readFileSync(logPath, "utf-8");
45341
45249
  const allLines = content.trim().split("\n").filter(Boolean);
45342
45250
  const logs = [];
45343
45251
  for (let i = after; i < allLines.length; i++) {
@@ -45567,6 +45475,193 @@ async function maybeProxyReadToLeader(req, res, options = {}) {
45567
45475
  }
45568
45476
  }
45569
45477
 
45478
+ // ../../packages/api/src/node/node-terminal-session-service.ts
45479
+ var import_node_child_process10 = require("child_process");
45480
+ var import_node_crypto8 = require("crypto");
45481
+
45482
+ // ../../packages/api/src/node/node-terminal-service.ts
45483
+ var fs17 = __toESM(require("fs"), 1);
45484
+ var path18 = __toESM(require("path"), 1);
45485
+ var DEFAULT_OUTPUT_LIMIT_BYTES = 64 * 1024;
45486
+ function isAbsolutePath2(value) {
45487
+ return path18.isAbsolute(value) || /^[A-Za-z]:[\/]/.test(value);
45488
+ }
45489
+ function resolveNodeTerminalCwd(nodeId, rootPath, cwd) {
45490
+ if (!rootPath) {
45491
+ throw new MeshyError("VALIDATION_ERROR", `Node ${nodeId} does not expose a working directory`, 400);
45492
+ }
45493
+ const requestedCwd = cwd?.trim() || ".";
45494
+ const resolved = isAbsolutePath2(requestedCwd) ? path18.resolve(requestedCwd) : path18.resolve(rootPath, requestedCwd);
45495
+ if (!fs17.existsSync(resolved) || !fs17.statSync(resolved).isDirectory()) {
45496
+ throw new MeshyError("VALIDATION_ERROR", `Working directory does not exist: ${resolved}`, 400);
45497
+ }
45498
+ return resolved;
45499
+ }
45500
+
45501
+ // ../../packages/api/src/node/node-terminal-session-service.ts
45502
+ var MAX_COMPLETED_SESSIONS = 10;
45503
+ function createOutputBuffer() {
45504
+ return { chunks: [], bytes: 0, observedBytes: 0 };
45505
+ }
45506
+ function appendTail(buffer, chunk, limitBytes) {
45507
+ buffer.observedBytes += chunk.length;
45508
+ const nextChunk = chunk.length > limitBytes ? chunk.subarray(chunk.length - limitBytes) : chunk;
45509
+ buffer.chunks.push(nextChunk);
45510
+ buffer.bytes += nextChunk.length;
45511
+ while (buffer.bytes > limitBytes && buffer.chunks.length > 0) {
45512
+ const first = buffer.chunks[0];
45513
+ if (!first) break;
45514
+ const overflow = buffer.bytes - limitBytes;
45515
+ if (first.length <= overflow) {
45516
+ buffer.chunks.shift();
45517
+ buffer.bytes -= first.length;
45518
+ continue;
45519
+ }
45520
+ buffer.chunks[0] = first.subarray(overflow);
45521
+ buffer.bytes -= overflow;
45522
+ }
45523
+ }
45524
+ function readBuffer(buffer, limitBytes) {
45525
+ return {
45526
+ text: Buffer.concat(buffer.chunks, buffer.bytes).toString("utf8"),
45527
+ truncated: buffer.observedBytes > limitBytes
45528
+ };
45529
+ }
45530
+ function cloneSession(session) {
45531
+ return { ...session };
45532
+ }
45533
+ function killSessionProcess(state3, signal) {
45534
+ const child = state3.process;
45535
+ if (!child) return;
45536
+ const pid = child.pid;
45537
+ if (process.platform !== "win32" && typeof pid === "number") {
45538
+ try {
45539
+ process.kill(-pid, signal);
45540
+ return;
45541
+ } catch {
45542
+ }
45543
+ }
45544
+ try {
45545
+ child.kill(signal);
45546
+ } catch {
45547
+ }
45548
+ }
45549
+ var NodeTerminalSessionService = class {
45550
+ sessions = /* @__PURE__ */ new Map();
45551
+ nextSequence = 0;
45552
+ start(nodeId, rootPath, command, options = {}) {
45553
+ const normalizedCommand = command.trim();
45554
+ if (!normalizedCommand) {
45555
+ throw new MeshyError("VALIDATION_ERROR", "Command must not be empty", 400);
45556
+ }
45557
+ const cwd = resolveNodeTerminalCwd(nodeId, rootPath, options.cwd);
45558
+ const startedAt = Date.now();
45559
+ const child = (0, import_node_child_process10.spawn)(normalizedCommand, {
45560
+ cwd,
45561
+ shell: true,
45562
+ windowsHide: true,
45563
+ stdio: ["ignore", "pipe", "pipe"],
45564
+ detached: process.platform !== "win32"
45565
+ });
45566
+ const session = {
45567
+ id: (0, import_node_crypto8.randomUUID)(),
45568
+ nodeId,
45569
+ cwd,
45570
+ command: normalizedCommand,
45571
+ pid: child.pid ?? null,
45572
+ status: "running",
45573
+ exitCode: null,
45574
+ signal: null,
45575
+ stdout: "",
45576
+ stderr: "",
45577
+ startedAt,
45578
+ updatedAt: startedAt,
45579
+ durationMs: 0,
45580
+ stdoutTruncated: false,
45581
+ stderrTruncated: false
45582
+ };
45583
+ const state3 = {
45584
+ session,
45585
+ process: child,
45586
+ stdout: createOutputBuffer(),
45587
+ stderr: createOutputBuffer(),
45588
+ sequence: this.nextSequence++
45589
+ };
45590
+ const outputLimitBytes = options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES;
45591
+ this.sessions.set(session.id, state3);
45592
+ child.stdout?.on("data", (chunk) => {
45593
+ appendTail(state3.stdout, chunk, outputLimitBytes);
45594
+ this.refreshOutput(state3, outputLimitBytes);
45595
+ });
45596
+ child.stderr?.on("data", (chunk) => {
45597
+ if (state3.session.status === "stopped") return;
45598
+ appendTail(state3.stderr, chunk, outputLimitBytes);
45599
+ this.refreshOutput(state3, outputLimitBytes);
45600
+ });
45601
+ child.on("error", (err) => {
45602
+ state3.session.status = "failed";
45603
+ state3.session.stderr = state3.session.stderr ? `${state3.session.stderr}
45604
+ ${err.message}` : err.message;
45605
+ this.complete(state3, null, null, outputLimitBytes);
45606
+ });
45607
+ child.on("close", (code, signal) => {
45608
+ if (state3.session.status !== "stopped") {
45609
+ state3.session.status = code === 0 ? "exited" : "failed";
45610
+ }
45611
+ this.complete(state3, code, signal, outputLimitBytes);
45612
+ });
45613
+ return cloneSession(session);
45614
+ }
45615
+ list() {
45616
+ return Array.from(this.sessions.values()).sort((a, b) => b.sequence - a.sequence).map((state3) => cloneSession(state3.session));
45617
+ }
45618
+ get(id) {
45619
+ const state3 = this.sessions.get(id);
45620
+ return state3 ? cloneSession(state3.session) : null;
45621
+ }
45622
+ stop(id) {
45623
+ const state3 = this.sessions.get(id);
45624
+ if (!state3) return null;
45625
+ if (state3.session.status === "running") {
45626
+ state3.session.status = "stopped";
45627
+ state3.session.updatedAt = Date.now();
45628
+ killSessionProcess(state3, "SIGTERM");
45629
+ setTimeout(() => {
45630
+ if (state3.session.status === "stopped" && !state3.session.completedAt) {
45631
+ killSessionProcess(state3, "SIGKILL");
45632
+ }
45633
+ }, 2e3).unref?.();
45634
+ }
45635
+ return cloneSession(state3.session);
45636
+ }
45637
+ refreshOutput(state3, outputLimitBytes) {
45638
+ const stdout = readBuffer(state3.stdout, outputLimitBytes);
45639
+ const stderr = readBuffer(state3.stderr, outputLimitBytes);
45640
+ state3.session.stdout = stdout.text;
45641
+ state3.session.stderr = stderr.text;
45642
+ state3.session.stdoutTruncated = stdout.truncated;
45643
+ state3.session.stderrTruncated = stderr.truncated;
45644
+ state3.session.updatedAt = Date.now();
45645
+ state3.session.durationMs = state3.session.updatedAt - state3.session.startedAt;
45646
+ }
45647
+ complete(state3, exitCode, signal, outputLimitBytes) {
45648
+ this.refreshOutput(state3, outputLimitBytes);
45649
+ state3.session.exitCode = exitCode;
45650
+ state3.session.signal = signal;
45651
+ state3.session.completedAt = Date.now();
45652
+ state3.session.updatedAt = state3.session.completedAt;
45653
+ state3.session.durationMs = state3.session.completedAt - state3.session.startedAt;
45654
+ state3.process = null;
45655
+ this.pruneCompletedSessions();
45656
+ }
45657
+ pruneCompletedSessions() {
45658
+ const completed = Array.from(this.sessions.values()).filter((state3) => state3.session.status !== "running").sort((a, b) => b.sequence - a.sequence);
45659
+ for (const state3 of completed.slice(MAX_COMPLETED_SESSIONS)) {
45660
+ this.sessions.delete(state3.session.id);
45661
+ }
45662
+ }
45663
+ };
45664
+
45570
45665
  // ../../packages/api/src/node/node-native-session-service.ts
45571
45666
  function getLocalNodeNativeSessions(nodeId, agent, limit) {
45572
45667
  return {
@@ -46651,6 +46746,10 @@ function payloadValue(request, key, fallback) {
46651
46746
  const value = request.payload[key];
46652
46747
  return value === void 0 ? fallback : value;
46653
46748
  }
46749
+ function getTerminalSessionService(deps) {
46750
+ deps.terminalSessionService ??= new NodeTerminalSessionService();
46751
+ return deps.terminalSessionService;
46752
+ }
46654
46753
  function maybeImportTaskSnapshot(deps, request) {
46655
46754
  if (!request.kind.startsWith("task.output.") && request.kind !== "task.preview.create" && request.kind !== "task.logs") return;
46656
46755
  const task = payloadValue(request, "task", null);
@@ -46728,12 +46827,12 @@ async function executeWorkerControlRequest(deps, request) {
46728
46827
  );
46729
46828
  break;
46730
46829
  }
46731
- case "node.terminal.execute": {
46830
+ case "node.terminal.session.start": {
46732
46831
  const self2 = deps.nodeRegistry.getSelf();
46733
46832
  response = jsonResponse(
46734
46833
  request.id,
46735
- 200,
46736
- await executeLocalNodeTerminalCommand(
46834
+ 202,
46835
+ getTerminalSessionService(deps).start(
46737
46836
  self2.id,
46738
46837
  self2.workDir ?? deps.workDir,
46739
46838
  payloadValue(request, "command", ""),
@@ -46742,6 +46841,28 @@ async function executeWorkerControlRequest(deps, request) {
46742
46841
  );
46743
46842
  break;
46744
46843
  }
46844
+ case "node.terminal.session.list": {
46845
+ const self2 = deps.nodeRegistry.getSelf();
46846
+ response = jsonResponse(request.id, 200, {
46847
+ nodeId: self2.id,
46848
+ sessions: getTerminalSessionService(deps).list()
46849
+ });
46850
+ break;
46851
+ }
46852
+ case "node.terminal.session.get": {
46853
+ const sessionId = payloadValue(request, "sessionId", "");
46854
+ const session = getTerminalSessionService(deps).get(sessionId);
46855
+ if (!session) throw new MeshyError("NODE_NOT_FOUND", `Terminal session ${sessionId} not found`, 404);
46856
+ response = jsonResponse(request.id, 200, session);
46857
+ break;
46858
+ }
46859
+ case "node.terminal.session.stop": {
46860
+ const sessionId = payloadValue(request, "sessionId", "");
46861
+ const session = getTerminalSessionService(deps).stop(sessionId);
46862
+ if (!session) throw new MeshyError("NODE_NOT_FOUND", `Terminal session ${sessionId} not found`, 404);
46863
+ response = jsonResponse(request.id, 200, session);
46864
+ break;
46865
+ }
46745
46866
  case "node.sessions.list": {
46746
46867
  const self2 = deps.nodeRegistry.getSelf();
46747
46868
  response = jsonResponse(
@@ -46990,28 +47111,82 @@ async function sendNodeAgentUpgrade(req, res, nodeId, agentParam) {
46990
47111
  }
46991
47112
 
46992
47113
  // ../../packages/api/src/routes/node-terminal.ts
46993
- async function sendNodeTerminalExecuteOperation(req, res, nodeId) {
46994
- const body = NodeTerminalExecuteBody.parse(req.body);
47114
+ var TERMINAL_SESSION_PROXY_TIMEOUT_MS = 1e4;
47115
+ function getTerminalSessionService2(deps) {
47116
+ deps.terminalSessionService ??= new NodeTerminalSessionService();
47117
+ return deps.terminalSessionService;
47118
+ }
47119
+ async function sendRemoteTerminalSessionRequest(req, res, nodeId, message) {
46995
47120
  const deps = req.app.locals.deps;
46996
- const { nodeRegistry } = deps;
46997
- const selfId = nodeRegistry.getSelf().id;
46998
- const node = nodeId === selfId ? nodeRegistry.getSelf() : nodeRegistry.getNode(nodeId);
47121
+ const node = deps.nodeRegistry.getNode(nodeId);
46999
47122
  if (!node) throw new MeshyError("NODE_NOT_FOUND", `Node ${nodeId} not found`, 404);
47000
- const operations = getNodeOperationService(deps);
47001
- const operation = operations.create("terminal.execute", nodeId, {
47002
- command: body.command,
47003
- cwd: body.cwd
47004
- });
47005
- if (nodeId === selfId) {
47006
- operations.runLocal(operation.id);
47123
+ const heartbeat = deps.heartbeat;
47124
+ const canPushToNode = heartbeat.canPushToNode?.(nodeId) ?? true;
47125
+ let response;
47126
+ if (!canPushToNode && canRequestNodeMessage(heartbeat)) {
47127
+ response = await requestFallbackNodeMessage(heartbeat, nodeId, message, TERMINAL_SESSION_PROXY_TIMEOUT_MS);
47007
47128
  } else {
47008
47129
  try {
47009
- await dispatchNodeOperation(deps, operation, node);
47130
+ response = await new NodeMessageClient({
47131
+ logger: deps.logger,
47132
+ timeoutMs: TERMINAL_SESSION_PROXY_TIMEOUT_MS
47133
+ }).request(node, message);
47010
47134
  } catch (err) {
47011
- operations.markFailed(operation.id, err instanceof Error ? err.message : String(err));
47135
+ if (canRequestNodeMessage(heartbeat)) {
47136
+ response = await requestFallbackNodeMessage(heartbeat, nodeId, message, TERMINAL_SESSION_PROXY_TIMEOUT_MS);
47137
+ } else {
47138
+ throw new MeshyError("NODE_OFFLINE", `Cannot reach node ${nodeId} for terminal session control`, 502, {
47139
+ error: err instanceof Error ? err.message : String(err)
47140
+ });
47141
+ }
47012
47142
  }
47013
47143
  }
47014
- res.status(202).json(operation);
47144
+ sendWorkerControlResponse(res, response);
47145
+ }
47146
+ async function sendNodeTerminalSessionStart(req, res, nodeId) {
47147
+ const body = NodeTerminalSessionStartBody.parse(req.body);
47148
+ const deps = req.app.locals.deps;
47149
+ const self2 = deps.nodeRegistry.getSelf();
47150
+ if (nodeId !== self2.id) {
47151
+ await sendRemoteTerminalSessionRequest(req, res, nodeId, createNodeMessage("node.terminal.session.start", {
47152
+ command: body.command,
47153
+ cwd: body.cwd
47154
+ }, { expectsResponse: true }));
47155
+ return;
47156
+ }
47157
+ const session = getTerminalSessionService2(deps).start(self2.id, self2.workDir ?? deps.workDir, body.command, { cwd: body.cwd });
47158
+ res.status(202).json(session);
47159
+ }
47160
+ async function sendNodeTerminalSessionList(req, res, nodeId) {
47161
+ const deps = req.app.locals.deps;
47162
+ const self2 = deps.nodeRegistry.getSelf();
47163
+ if (nodeId !== self2.id) {
47164
+ await sendRemoteTerminalSessionRequest(req, res, nodeId, createNodeMessage("node.terminal.session.list", {}, { expectsResponse: true }));
47165
+ return;
47166
+ }
47167
+ res.json({ nodeId: self2.id, sessions: getTerminalSessionService2(deps).list() });
47168
+ }
47169
+ async function sendNodeTerminalSessionGet(req, res, nodeId, sessionId) {
47170
+ const deps = req.app.locals.deps;
47171
+ const self2 = deps.nodeRegistry.getSelf();
47172
+ if (nodeId !== self2.id) {
47173
+ await sendRemoteTerminalSessionRequest(req, res, nodeId, createNodeMessage("node.terminal.session.get", { sessionId }, { expectsResponse: true }));
47174
+ return;
47175
+ }
47176
+ const session = getTerminalSessionService2(deps).get(sessionId);
47177
+ if (!session) throw new MeshyError("NODE_NOT_FOUND", `Terminal session ${sessionId} not found`, 404);
47178
+ res.json(session);
47179
+ }
47180
+ async function sendNodeTerminalSessionStop(req, res, nodeId, sessionId) {
47181
+ const deps = req.app.locals.deps;
47182
+ const self2 = deps.nodeRegistry.getSelf();
47183
+ if (nodeId !== self2.id) {
47184
+ await sendRemoteTerminalSessionRequest(req, res, nodeId, createNodeMessage("node.terminal.session.stop", { sessionId }, { expectsResponse: true }));
47185
+ return;
47186
+ }
47187
+ const session = getTerminalSessionService2(deps).stop(sessionId);
47188
+ if (!session) throw new MeshyError("NODE_NOT_FOUND", `Terminal session ${sessionId} not found`, 404);
47189
+ res.json(session);
47015
47190
  }
47016
47191
 
47017
47192
  // ../../packages/api/src/routes/nodes.ts
@@ -47339,9 +47514,17 @@ function createNodeRoutes() {
47339
47514
  }
47340
47515
  sendLocalNodeWorkDirBranchInfo(req, res, nodeId);
47341
47516
  }));
47342
- router.post("/:id/terminal/execute", asyncHandler3(async (req, res) => {
47343
- const nodeId = req.params.id;
47344
- await sendNodeTerminalExecuteOperation(req, res, nodeId);
47517
+ router.get("/:id/terminal/sessions", asyncHandler3(async (req, res) => {
47518
+ await sendNodeTerminalSessionList(req, res, req.params.id);
47519
+ }));
47520
+ router.post("/:id/terminal/sessions", asyncHandler3(async (req, res) => {
47521
+ await sendNodeTerminalSessionStart(req, res, req.params.id);
47522
+ }));
47523
+ router.get("/:id/terminal/sessions/:sessionId", asyncHandler3(async (req, res) => {
47524
+ await sendNodeTerminalSessionGet(req, res, req.params.id, req.params.sessionId);
47525
+ }));
47526
+ router.post("/:id/terminal/sessions/:sessionId/stop", asyncHandler3(async (req, res) => {
47527
+ await sendNodeTerminalSessionStop(req, res, req.params.id, req.params.sessionId);
47345
47528
  }));
47346
47529
  router.post("/:id/workdir/branch", asyncHandler3(async (req, res) => {
47347
47530
  const nodeId = req.params.id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meshy-node",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "Standalone Meshy node package with bundled runtime and dashboard assets.",
6
6
  "type": "commonjs",