@mastra/railway 0.4.1-alpha.0 → 0.5.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @mastra/railway
2
2
 
3
+ ## 0.5.0-alpha.2
4
+
5
+ ### Minor Changes
6
+
7
+ - Added `RailwaySandbox.snapshot()` to capture the configured recovery checkpoint. ([#21221](https://github.com/mastra-ai/mastra/pull/21221))
8
+
9
+ ```ts
10
+ await sandbox.snapshot();
11
+ ```
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [[`9571e3a`](https://github.com/mastra-ai/mastra/commit/9571e3a06ed2c5220196460bf82a2129255c3a8b), [`d6c56f9`](https://github.com/mastra-ai/mastra/commit/d6c56f951db3213330b98b0abafa9778c8770e58), [`9571e3a`](https://github.com/mastra-ai/mastra/commit/9571e3a06ed2c5220196460bf82a2129255c3a8b), [`acc3513`](https://github.com/mastra-ai/mastra/commit/acc3513b19f79bf0a7ec2998694580edca54086c), [`94e7ae9`](https://github.com/mastra-ai/mastra/commit/94e7ae970b37c888cd1244ef013292639a2fe6d1), [`6a667b4`](https://github.com/mastra-ai/mastra/commit/6a667b4b7cd6a93fe41fcdd357b08c5a8c09b9ab), [`2440e09`](https://github.com/mastra-ai/mastra/commit/2440e096ea6c2def1ccc1eb2d0f3f5b88c4af940), [`a59049b`](https://github.com/mastra-ai/mastra/commit/a59049b1652a13efff66ac826326b5ed9a550342)]:
16
+ - @mastra/core@1.58.0-alpha.13
17
+
18
+ ## 0.4.1-alpha.1
19
+
20
+ ### Patch Changes
21
+
22
+ - Improved Railway sandbox recovery and checkpoint management: ([#20739](https://github.com/mastra-ai/mastra/pull/20739))
23
+
24
+ - A configured `sandboxId` reconnects when the sandbox is running, or creates a replacement when it is missing or stopped.
25
+ - Use `captureCheckpoint()` to save a recovery point on demand. Saved checkpoints provide the baseline filesystem for new sandboxes; `stop()` captures one before teardown and `destroy()` removes it.
26
+ - `start()` no longer resolves `template`. The option is still accepted and copied by `clone()`, but has no effect: callers receive neither a custom base image nor an error.
27
+
28
+ - Updated dependencies [[`b4c89b4`](https://github.com/mastra-ai/mastra/commit/b4c89b4371b0c86da57403ad1a3b3ef0681f3128), [`e44e8f3`](https://github.com/mastra-ai/mastra/commit/e44e8f370b66c339ddcaba946d33da6d3c3f06cd), [`c967a5e`](https://github.com/mastra-ai/mastra/commit/c967a5eec150c5dc5418c4a4388982d1fb7ad27c), [`f53d5bd`](https://github.com/mastra-ai/mastra/commit/f53d5bd4885b29e4ac29a428a6044088ea8d6aa3), [`bda2235`](https://github.com/mastra-ai/mastra/commit/bda22353ee28f2df0eaea555f7cae1549f979c0b), [`a7eb4a1`](https://github.com/mastra-ai/mastra/commit/a7eb4a11450f6170274ed5141bffe821d4fdd5a6), [`2f9ef3f`](https://github.com/mastra-ai/mastra/commit/2f9ef3f4ca06fc2dcdd5088c26b7f4da6a016791), [`e7eefcb`](https://github.com/mastra-ai/mastra/commit/e7eefcb162cda7c493e8c3bf43050ead0efbcb2c), [`4d7aca2`](https://github.com/mastra-ai/mastra/commit/4d7aca2fe75f225c83d1502d63079568e6ec163f), [`c4ec889`](https://github.com/mastra-ai/mastra/commit/c4ec889561c0264c43f66d04d587bee4ce35e792), [`9be8878`](https://github.com/mastra-ai/mastra/commit/9be8878dcf0388e84fc4873e0eec27bd49b881a4)]:
29
+ - @mastra/core@1.58.0-alpha.2
30
+
3
31
  ## 0.4.1-alpha.0
4
32
 
5
33
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -194,9 +194,10 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
194
194
  _createdAt = null;
195
195
  _checkpointRefreshTimer = null;
196
196
  _checkpointRefreshInFlight = null;
197
+ _sandboxId;
198
+ _startInFlight = null;
197
199
  _token;
198
200
  _environmentId;
199
- _sandboxId;
200
201
  _checkpointName;
201
202
  _idleTimeoutMinutes;
202
203
  _networkIsolation;
@@ -242,67 +243,60 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
242
243
  */
243
244
  async start() {
244
245
  if (this._sandbox) return;
245
- await this._startRailwaySandbox({
246
- reconnectSandboxId: this._sandboxId,
247
- fallbackToCreate: false
246
+ const clientConfig = this._clientConfig();
247
+ const createOptions = this._createOptions(clientConfig);
248
+ if (this._sandboxId) {
249
+ const sandboxId = this._sandboxId;
250
+ this._startInFlight ??= (async () => {
251
+ try {
252
+ this._sandbox = await this._reconnectSandbox(sandboxId, clientConfig);
253
+ } catch (error) {
254
+ if (!(error instanceof railway.SandboxNotFoundError)) throw error;
255
+ this._sandbox = await this._createNewSandbox(createOptions);
256
+ }
257
+ })().finally(() => {
258
+ this._startInFlight = null;
259
+ });
260
+ } else this._startInFlight ??= (async () => {
261
+ this._sandbox = await this._createNewSandbox(createOptions);
262
+ })().finally(() => {
263
+ this._startInFlight = null;
248
264
  });
265
+ await this._startInFlight;
266
+ if (!this._sandbox) throw new Error("Failed to start Railway sandbox");
267
+ const sandbox = this._sandbox;
268
+ this._sandboxId = sandbox.id;
269
+ this._createdAt = sandbox.createdAt ? new Date(sandbox.createdAt) : /* @__PURE__ */ new Date();
270
+ this.logger.debug(`${LOG_PREFIX} Railway sandbox ${sandbox.id} ready for logical ID: ${this.id}`);
271
+ this._scheduleCheckpointRefresh();
249
272
  }
250
- async restart() {
251
- const reconnectSandboxId = this._sandbox?.id ?? this._sandboxId;
252
- this._cancelCheckpointRefresh();
253
- await this._checkpointRefreshInFlight?.catch((error) => {
254
- this.logger.warn(`${LOG_PREFIX} Failed to flush in-flight checkpoint before restart:`, error);
255
- });
256
- this._sandbox = null;
257
- this._createdAt = null;
258
- this.status = "starting";
273
+ /**
274
+ * Create a new Railway sandbox.
275
+ */
276
+ async _createNewSandbox(createOptions) {
277
+ this.logger.debug(`${LOG_PREFIX} Creating Railway sandbox for: ${this.id}`);
259
278
  try {
260
- await this._startRailwaySandbox({
261
- reconnectSandboxId,
262
- fallbackToCreate: true
263
- });
264
- this.status = "running";
279
+ let checkpoinAlreadyExists = false;
280
+ if (this._checkpointName) checkpoinAlreadyExists = (await railway.Sandbox.checkpoints(this._clientConfig())).some((checkpoint) => checkpoint.key === this._checkpointName);
281
+ let sandbox;
282
+ if (checkpoinAlreadyExists) sandbox = await railway.Sandbox.create(this._checkpointName, createOptions);
283
+ else sandbox = await railway.Sandbox.create(createOptions);
284
+ return sandbox;
265
285
  } catch (error) {
266
- this.status = "error";
267
286
  throw error;
268
287
  }
269
288
  }
270
- async withRestartRetry(operation) {
271
- await this.ensureRunning();
272
- try {
273
- return await operation();
274
- } catch (error) {
275
- if (!this.isSandboxUnavailableError(error)) throw error;
276
- await this.restart();
277
- return await operation();
278
- } finally {
279
- this._scheduleCheckpointRefresh();
280
- }
281
- }
282
- async _startRailwaySandbox({ reconnectSandboxId, fallbackToCreate }) {
283
- const clientConfig = this._clientConfig();
284
- const createOptions = this._createOptions(clientConfig);
285
- this._sandbox = reconnectSandboxId ? await this._reconnectSandbox(reconnectSandboxId, fallbackToCreate, clientConfig, createOptions) : await this._createNewSandbox(createOptions);
286
- this._createdAt = this._sandbox.createdAt ? new Date(this._sandbox.createdAt) : /* @__PURE__ */ new Date();
287
- this.logger.debug(`${LOG_PREFIX} Railway sandbox ${this._sandbox.id} ready for logical ID: ${this.id}`);
288
- this._scheduleCheckpointRefresh();
289
- }
290
289
  /**
291
290
  * Reconnect to an existing Railway sandbox, creating a fresh one when
292
- * `fallbackToCreate` is set and the sandbox is unavailable or not running.
293
291
  */
294
- async _reconnectSandbox(reconnectSandboxId, fallbackToCreate, clientConfig, createOptions) {
292
+ async _reconnectSandbox(reconnectSandboxId, clientConfig) {
295
293
  this.logger.debug(`${LOG_PREFIX} Reconnecting to Railway sandbox ${reconnectSandboxId}...`);
296
- let connectedSandbox;
297
- try {
298
- connectedSandbox = await railway.Sandbox.connect(reconnectSandboxId, clientConfig);
299
- } catch (error) {
300
- if (!fallbackToCreate || !this.isSandboxUnavailableError(error)) throw error;
301
- return this._createNewSandbox(createOptions);
302
- }
303
- if (connectedSandbox.status === "RUNNING") return connectedSandbox;
304
- if (!fallbackToCreate) throw new Error(`Railway sandbox ${reconnectSandboxId} is not running (status: ${connectedSandbox.status})`);
305
- return this._createNewSandbox(createOptions);
294
+ let connectedSandbox = await railway.Sandbox.connect(reconnectSandboxId, clientConfig);
295
+ if (connectedSandbox.status !== "RUNNING") throw new railway.SandboxNotFoundError({
296
+ id: reconnectSandboxId,
297
+ environmentId: clientConfig.environmentId ?? ""
298
+ });
299
+ return connectedSandbox;
306
300
  }
307
301
  _clientConfig() {
308
302
  return {
@@ -318,84 +312,9 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
318
312
  ...Object.keys(this._env).length > 0 && { env: this._env }
319
313
  };
320
314
  }
321
- async _createNewSandbox(createOptions) {
322
- const checkpointSandbox = await this._tryCreateFromCheckpoint(createOptions);
323
- if (checkpointSandbox) return checkpointSandbox;
324
- if (this._templateOption) {
325
- const template = this._resolveTemplate();
326
- this.logger.debug(`${LOG_PREFIX} Creating Railway sandbox from template for: ${this.id}`);
327
- const sandbox = await railway.Sandbox.create(template, createOptions);
328
- await this._checkpointSandbox(sandbox);
329
- return sandbox;
330
- }
331
- this.logger.debug(`${LOG_PREFIX} Creating Railway sandbox for: ${this.id}`);
332
- const sandbox = await railway.Sandbox.create(createOptions);
333
- await this._checkpointSandbox(sandbox);
334
- return sandbox;
335
- }
336
- async _tryCreateFromCheckpoint(createOptions) {
337
- if (!this._checkpointName) return;
338
- this.logger.debug(`${LOG_PREFIX} Creating Railway sandbox from checkpoint ${this._checkpointName} for: ${this.id}`);
339
- try {
340
- return await railway.Sandbox.create(this._checkpointName, createOptions);
341
- } catch (error) {
342
- if (!this.isCheckpointUnavailableError(error)) throw error;
343
- return;
344
- }
345
- }
346
- async _checkpointSandbox(sandbox) {
347
- if (!this._checkpointName) return;
348
- try {
349
- this.logger.debug(`${LOG_PREFIX} Capturing Railway sandbox checkpoint ${this._checkpointName} for: ${this.id}`);
350
- await sandbox.checkpoint(this._checkpointName);
351
- } catch (error) {
352
- if (!this.isCheckpointAlreadyExistsError(error)) throw error;
353
- await this._deleteCheckpointByName(this._checkpointName);
354
- await sandbox.checkpoint(this._checkpointName);
355
- }
356
- }
357
- async _deleteCheckpointByName(name) {
358
- try {
359
- const checkpoint = (await railway.Sandbox.checkpoints(this._clientConfig())).find((checkpoint) => checkpoint.key === name);
360
- if (!checkpoint) return;
361
- await railway.Sandbox.deleteCheckpoint(checkpoint.id, this._clientConfig());
362
- } catch (error) {
363
- if (!this.isCheckpointUnavailableError(error)) throw error;
364
- }
365
- }
366
- _scheduleCheckpointRefresh() {
367
- if (!this._checkpointName || !this._sandbox) return;
368
- const idleTimeoutMinutes = this._idleTimeoutMinutes ?? this._sandbox.idleTimeoutMinutes;
369
- if (!idleTimeoutMinutes) return;
370
- if (this._checkpointRefreshTimer) clearTimeout(this._checkpointRefreshTimer);
371
- const delayMs = Math.max(1e3, idleTimeoutMinutes * 6e4 - CHECKPOINT_REFRESH_MARGIN_MS);
372
- this._checkpointRefreshTimer = setTimeout(() => {
373
- this._checkpointRefreshTimer = null;
374
- const sandbox = this._sandbox;
375
- if (!sandbox) return;
376
- const refresh = this._checkpointSandbox(sandbox).finally(() => {
377
- if (this._checkpointRefreshInFlight === refresh) this._checkpointRefreshInFlight = null;
378
- });
379
- this._checkpointRefreshInFlight = refresh;
380
- this._checkpointRefreshInFlight.catch((error) => {
381
- this.logger.warn(`${LOG_PREFIX} Failed to refresh Railway sandbox checkpoint ${this._checkpointName}:`, error);
382
- });
383
- }, delayMs);
384
- this._checkpointRefreshTimer.unref?.();
385
- }
386
- _cancelCheckpointRefresh() {
387
- if (this._checkpointRefreshTimer) {
388
- clearTimeout(this._checkpointRefreshTimer);
389
- this._checkpointRefreshTimer = null;
390
- }
391
- }
392
- async _flushCheckpointRefresh() {
393
- this._cancelCheckpointRefresh();
394
- if (this._checkpointRefreshInFlight) {
395
- await this._checkpointRefreshInFlight;
396
- return;
397
- }
398
- if (this._sandbox) await this._checkpointSandbox(this._sandbox);
315
+ /** Persist the configured recovery checkpoint when available. */
316
+ async snapshot() {
317
+ await this.captureCheckpoint();
399
318
  }
400
319
  /**
401
320
  * Capture the sandbox's checkpoint on demand, outside the idle-timer schedule.
@@ -447,74 +366,76 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
447
366
  checkpointName
448
367
  };
449
368
  }
450
- isCheckpointUnavailableError(error) {
451
- if (!(error instanceof Error)) return false;
452
- const message = error.message.toLowerCase();
453
- return message.includes("checkpoint") && [
454
- "not found",
455
- "does not exist",
456
- "missing",
457
- "unknown",
458
- "no checkpoint"
459
- ].some((phrase) => message.includes(phrase));
460
- }
461
- isCheckpointAlreadyExistsError(error) {
462
- if (!(error instanceof Error)) return false;
463
- const message = error.message.toLowerCase();
464
- return message.includes("checkpoint") && ([
465
- "already exists",
466
- "must be unused",
467
- "unique"
468
- ].some((phrase) => message.includes(phrase)) || message.includes("name") && message.includes("used"));
469
- }
470
- isSandboxUnavailableError(error, seen = /* @__PURE__ */ new Set()) {
471
- if (error && typeof error === "object") {
472
- if (seen.has(error)) return false;
473
- seen.add(error);
474
- }
475
- if (error instanceof railway.SandboxNotFoundError || error instanceof railway.SandboxFailedError || error instanceof railway.SandboxTimeoutError && error.resource === "sandbox") return true;
476
- if (error && typeof error === "object") {
477
- const errorLike = error;
478
- const name = typeof errorLike.name === "string" ? errorLike.name : "";
479
- const message = typeof errorLike.message === "string" ? errorLike.message.toLowerCase() : "";
480
- if (name === "SandboxNotFoundError" || name === "SandboxFailedError" || name === "SandboxTimeoutError" && errorLike.resource === "sandbox") return true;
481
- if (message.includes("sandbox") && [
482
- "not found",
483
- "destroyed",
484
- "failed",
485
- "not running",
486
- "unavailable"
487
- ].some((phrase) => message.includes(phrase))) return true;
488
- if (errorLike.cause !== void 0) return this.isSandboxUnavailableError(errorLike.cause, seen);
369
+ async _checkpointSandbox(sandbox) {
370
+ if (!this._checkpointName) return;
371
+ this.logger.debug(`${LOG_PREFIX} Capturing Railway sandbox checkpoint ${this._checkpointName} for: ${this.id}`);
372
+ if ((await railway.Sandbox.checkpoints(this._clientConfig())).some((checkpoint) => checkpoint.key === this._checkpointName)) await railway.Sandbox.deleteCheckpoint(this._checkpointName, this._clientConfig());
373
+ await sandbox.checkpoint(this._checkpointName);
374
+ }
375
+ _scheduleCheckpointRefresh() {
376
+ if (!this._checkpointName || !this._sandbox) return;
377
+ const idleTimeoutMinutes = this._idleTimeoutMinutes ?? this._sandbox.idleTimeoutMinutes;
378
+ if (!idleTimeoutMinutes) return;
379
+ this._cancelCheckpointRefresh();
380
+ const delayMs = Math.max(1e3, idleTimeoutMinutes * 6e4 - CHECKPOINT_REFRESH_MARGIN_MS);
381
+ this._checkpointRefreshTimer = setTimeout(() => {
382
+ this._checkpointRefreshTimer = null;
383
+ const sandbox = this._sandbox;
384
+ if (!sandbox || this._checkpointRefreshInFlight) return;
385
+ const refresh = this._checkpointSandbox(sandbox).finally(() => {
386
+ if (this._checkpointRefreshInFlight === refresh) this._checkpointRefreshInFlight = null;
387
+ });
388
+ this._checkpointRefreshInFlight = refresh;
389
+ refresh.catch((error) => {
390
+ this.logger.warn(`${LOG_PREFIX} Failed to refresh Railway sandbox checkpoint ${this._checkpointName}:`, error);
391
+ });
392
+ }, delayMs);
393
+ this._checkpointRefreshTimer.unref?.();
394
+ }
395
+ _cancelCheckpointRefresh() {
396
+ if (this._checkpointRefreshTimer) {
397
+ clearTimeout(this._checkpointRefreshTimer);
398
+ this._checkpointRefreshTimer = null;
489
399
  }
490
- return false;
491
400
  }
492
401
  /**
493
402
  * Stop the Railway sandbox.
494
403
  *
495
404
  * Railway sandboxes have no separate "stopped" state — they're either
496
- * running or destroyed — so stopping destroys the sandbox.
405
+ * running or destroyed — so stopping destroys the sandbox but we keep a checkpoint of the filesystem.
497
406
  */
498
407
  async stop() {
408
+ if (this._checkpointName) await this._flushCheckpointRefresh().catch((error) => {
409
+ this.logger.warn(`${LOG_PREFIX} Failed to checkpoint Railway sandbox ${this._sandbox?.id}:`, error);
410
+ });
499
411
  await this._teardown();
500
412
  }
501
413
  /**
502
- * Destroy the Railway sandbox and release its resources.
414
+ * Destroy the Railway sandbox and release its resources including the checkpoint.
503
415
  */
504
416
  async destroy() {
417
+ this._cancelCheckpointRefresh();
418
+ if (this._checkpointName) {
419
+ await this._checkpointRefreshInFlight;
420
+ await railway.Sandbox.deleteCheckpoint(this._checkpointName, this._clientConfig()).catch((error) => {
421
+ this.logger.warn(`${LOG_PREFIX} Failed to delete Railway checkpoint ${this._checkpointName}:`, error);
422
+ });
423
+ }
505
424
  await this._teardown();
506
425
  }
507
- async _teardown() {
508
- if (!this._sandbox) {
509
- this._cancelCheckpointRefresh();
426
+ async _flushCheckpointRefresh() {
427
+ this._cancelCheckpointRefresh();
428
+ if (this._checkpointRefreshInFlight) {
429
+ await this._checkpointRefreshInFlight;
510
430
  return;
511
431
  }
432
+ if (this._sandbox) await this._checkpointSandbox(this._sandbox);
433
+ }
434
+ async _teardown() {
435
+ this._cancelCheckpointRefresh();
436
+ if (!this._sandbox) return;
437
+ await this._checkpointRefreshInFlight;
512
438
  const sandbox = this._sandbox;
513
- try {
514
- await this._flushCheckpointRefresh();
515
- } catch (error) {
516
- this.logger.warn(`${LOG_PREFIX} Failed to flush checkpoint before teardown:`, error);
517
- }
518
439
  this._sandbox = null;
519
440
  try {
520
441
  await sandbox.destroy();
@@ -523,15 +444,6 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
523
444
  }
524
445
  }
525
446
  /**
526
- * Resolve the configured template into a `SandboxTemplate` that Railway
527
- * builds during `Sandbox.create()`. Accepts either a pre-built
528
- * `SandboxTemplate` or a builder callback over `Sandbox.template()`.
529
- */
530
- _resolveTemplate() {
531
- const option = this._templateOption;
532
- return typeof option === "function" ? option(railway.Sandbox.template()) : option;
533
- }
534
- /**
535
447
  * Fork this running sandbox into a new, independent `RailwaySandbox`.
536
448
  *
537
449
  * Clones the filesystem (a fresh boot, not live processes) into the same
@@ -542,7 +454,7 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
542
454
  * @throws {SandboxNotReadyError} If this sandbox has not been started.
543
455
  */
544
456
  async fork(options = {}) {
545
- const forked = await this.railway.fork({
457
+ await this.railway.fork({
546
458
  ...options.idleTimeoutMinutes !== void 0 && { idleTimeoutMinutes: options.idleTimeoutMinutes },
547
459
  ...options.networkIsolation !== void 0 && { networkIsolation: options.networkIsolation },
548
460
  ...options.env !== void 0 && { env: options.env }
@@ -551,7 +463,6 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
551
463
  ...options.id !== void 0 && { id: options.id },
552
464
  ...this._token !== void 0 && { token: this._token },
553
465
  ...this._environmentId !== void 0 && { environmentId: this._environmentId },
554
- sandboxId: forked.id,
555
466
  idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
556
467
  networkIsolation: options.networkIsolation ?? this._networkIsolation,
557
468
  env: options.env ?? this._env,
@@ -576,7 +487,6 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
576
487
  ...options.id !== void 0 && { id: options.id },
577
488
  ...this._token !== void 0 && { token: this._token },
578
489
  ...this._environmentId !== void 0 && { environmentId: this._environmentId },
579
- ...options.sandboxId !== void 0 && { sandboxId: options.sandboxId },
580
490
  ...(options.checkpointName ?? this._checkpointName) !== void 0 && { checkpointName: options.checkpointName ?? this._checkpointName },
581
491
  idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
582
492
  ...this._networkIsolation !== void 0 && { networkIsolation: this._networkIsolation },
@@ -633,28 +543,31 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
633
543
  * Execute a command in the sandbox and return the result.
634
544
  */
635
545
  async executeCommand(command, args = [], options = {}) {
636
- return this.withRestartRetry(async () => {
637
- const fullCommand = args.length > 0 ? `${command} ${args.map(shellQuote).join(" ")}` : command;
638
- const timeout = options.timeout ?? this._timeout;
639
- const env = options.env ? Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== void 0)) : void 0;
640
- const startedAt = Date.now();
641
- const result = await this.railway.exec(fullCommand, {
642
- ...timeout !== void 0 && { timeoutSec: Math.ceil(timeout / 1e3) },
643
- ...options.cwd !== void 0 && { cwd: options.cwd },
644
- ...env !== void 0 && { env }
645
- });
646
- const exitCode = result.exitCode ?? -1;
647
- return {
648
- success: exitCode === 0,
649
- exitCode,
650
- stdout: result.stdout,
651
- stderr: result.stderr,
652
- executionTimeMs: Date.now() - startedAt,
653
- command,
654
- args,
655
- timedOut: result.timedOut
656
- };
546
+ if (this._sandbox?.status !== "RUNNING") {
547
+ if (!this._checkpointName) throw new _mastra_core_workspace.SandboxNotReadyError(this.id);
548
+ this._sandbox = null;
549
+ await this.start();
550
+ }
551
+ const fullCommand = args.length > 0 ? `${command} ${args.map(shellQuote).join(" ")}` : command;
552
+ const timeout = options.timeout ?? this._timeout;
553
+ const env = options.env ? Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== void 0)) : void 0;
554
+ const startedAt = Date.now();
555
+ const result = await this.railway.exec(fullCommand, {
556
+ ...timeout !== void 0 && { timeoutSec: Math.ceil(timeout / 1e3) },
557
+ ...options.cwd !== void 0 && { cwd: options.cwd },
558
+ ...env !== void 0 && { env }
657
559
  });
560
+ const exitCode = result.exitCode ?? -1;
561
+ return {
562
+ success: exitCode === 0,
563
+ exitCode,
564
+ stdout: result.stdout,
565
+ stderr: result.stderr,
566
+ executionTimeMs: Date.now() - startedAt,
567
+ command,
568
+ args,
569
+ timedOut: result.timedOut
570
+ };
658
571
  }
659
572
  };
660
573
  //#endregion