@mastra/railway 0.4.1-alpha.0 → 0.4.1-alpha.1

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,18 @@
1
1
  # @mastra/railway
2
2
 
3
+ ## 0.4.1-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Improved Railway sandbox recovery and checkpoint management: ([#20739](https://github.com/mastra-ai/mastra/pull/20739))
8
+
9
+ - A configured `sandboxId` reconnects when the sandbox is running, or creates a replacement when it is missing or stopped.
10
+ - 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.
11
+ - `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.
12
+
13
+ - 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)]:
14
+ - @mastra/core@1.58.0-alpha.2
15
+
3
16
  ## 0.4.1-alpha.0
4
17
 
5
18
  ### 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,85 +312,6 @@ 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);
399
- }
400
315
  /**
401
316
  * Capture the sandbox's checkpoint on demand, outside the idle-timer schedule.
402
317
  *
@@ -447,74 +362,76 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
447
362
  checkpointName
448
363
  };
449
364
  }
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);
365
+ async _checkpointSandbox(sandbox) {
366
+ if (!this._checkpointName) return;
367
+ this.logger.debug(`${LOG_PREFIX} Capturing Railway sandbox checkpoint ${this._checkpointName} for: ${this.id}`);
368
+ if ((await railway.Sandbox.checkpoints(this._clientConfig())).some((checkpoint) => checkpoint.key === this._checkpointName)) await railway.Sandbox.deleteCheckpoint(this._checkpointName, this._clientConfig());
369
+ await sandbox.checkpoint(this._checkpointName);
370
+ }
371
+ _scheduleCheckpointRefresh() {
372
+ if (!this._checkpointName || !this._sandbox) return;
373
+ const idleTimeoutMinutes = this._idleTimeoutMinutes ?? this._sandbox.idleTimeoutMinutes;
374
+ if (!idleTimeoutMinutes) return;
375
+ this._cancelCheckpointRefresh();
376
+ const delayMs = Math.max(1e3, idleTimeoutMinutes * 6e4 - CHECKPOINT_REFRESH_MARGIN_MS);
377
+ this._checkpointRefreshTimer = setTimeout(() => {
378
+ this._checkpointRefreshTimer = null;
379
+ const sandbox = this._sandbox;
380
+ if (!sandbox || this._checkpointRefreshInFlight) return;
381
+ const refresh = this._checkpointSandbox(sandbox).finally(() => {
382
+ if (this._checkpointRefreshInFlight === refresh) this._checkpointRefreshInFlight = null;
383
+ });
384
+ this._checkpointRefreshInFlight = refresh;
385
+ refresh.catch((error) => {
386
+ this.logger.warn(`${LOG_PREFIX} Failed to refresh Railway sandbox checkpoint ${this._checkpointName}:`, error);
387
+ });
388
+ }, delayMs);
389
+ this._checkpointRefreshTimer.unref?.();
390
+ }
391
+ _cancelCheckpointRefresh() {
392
+ if (this._checkpointRefreshTimer) {
393
+ clearTimeout(this._checkpointRefreshTimer);
394
+ this._checkpointRefreshTimer = null;
489
395
  }
490
- return false;
491
396
  }
492
397
  /**
493
398
  * Stop the Railway sandbox.
494
399
  *
495
400
  * Railway sandboxes have no separate "stopped" state — they're either
496
- * running or destroyed — so stopping destroys the sandbox.
401
+ * running or destroyed — so stopping destroys the sandbox but we keep a checkpoint of the filesystem.
497
402
  */
498
403
  async stop() {
404
+ if (this._checkpointName) await this._flushCheckpointRefresh().catch((error) => {
405
+ this.logger.warn(`${LOG_PREFIX} Failed to checkpoint Railway sandbox ${this._sandbox?.id}:`, error);
406
+ });
499
407
  await this._teardown();
500
408
  }
501
409
  /**
502
- * Destroy the Railway sandbox and release its resources.
410
+ * Destroy the Railway sandbox and release its resources including the checkpoint.
503
411
  */
504
412
  async destroy() {
413
+ this._cancelCheckpointRefresh();
414
+ if (this._checkpointName) {
415
+ await this._checkpointRefreshInFlight;
416
+ await railway.Sandbox.deleteCheckpoint(this._checkpointName, this._clientConfig()).catch((error) => {
417
+ this.logger.warn(`${LOG_PREFIX} Failed to delete Railway checkpoint ${this._checkpointName}:`, error);
418
+ });
419
+ }
505
420
  await this._teardown();
506
421
  }
507
- async _teardown() {
508
- if (!this._sandbox) {
509
- this._cancelCheckpointRefresh();
422
+ async _flushCheckpointRefresh() {
423
+ this._cancelCheckpointRefresh();
424
+ if (this._checkpointRefreshInFlight) {
425
+ await this._checkpointRefreshInFlight;
510
426
  return;
511
427
  }
428
+ if (this._sandbox) await this._checkpointSandbox(this._sandbox);
429
+ }
430
+ async _teardown() {
431
+ this._cancelCheckpointRefresh();
432
+ if (!this._sandbox) return;
433
+ await this._checkpointRefreshInFlight;
512
434
  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
435
  this._sandbox = null;
519
436
  try {
520
437
  await sandbox.destroy();
@@ -523,15 +440,6 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
523
440
  }
524
441
  }
525
442
  /**
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
443
  * Fork this running sandbox into a new, independent `RailwaySandbox`.
536
444
  *
537
445
  * Clones the filesystem (a fresh boot, not live processes) into the same
@@ -542,7 +450,7 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
542
450
  * @throws {SandboxNotReadyError} If this sandbox has not been started.
543
451
  */
544
452
  async fork(options = {}) {
545
- const forked = await this.railway.fork({
453
+ await this.railway.fork({
546
454
  ...options.idleTimeoutMinutes !== void 0 && { idleTimeoutMinutes: options.idleTimeoutMinutes },
547
455
  ...options.networkIsolation !== void 0 && { networkIsolation: options.networkIsolation },
548
456
  ...options.env !== void 0 && { env: options.env }
@@ -551,7 +459,6 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
551
459
  ...options.id !== void 0 && { id: options.id },
552
460
  ...this._token !== void 0 && { token: this._token },
553
461
  ...this._environmentId !== void 0 && { environmentId: this._environmentId },
554
- sandboxId: forked.id,
555
462
  idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
556
463
  networkIsolation: options.networkIsolation ?? this._networkIsolation,
557
464
  env: options.env ?? this._env,
@@ -576,7 +483,6 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
576
483
  ...options.id !== void 0 && { id: options.id },
577
484
  ...this._token !== void 0 && { token: this._token },
578
485
  ...this._environmentId !== void 0 && { environmentId: this._environmentId },
579
- ...options.sandboxId !== void 0 && { sandboxId: options.sandboxId },
580
486
  ...(options.checkpointName ?? this._checkpointName) !== void 0 && { checkpointName: options.checkpointName ?? this._checkpointName },
581
487
  idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
582
488
  ...this._networkIsolation !== void 0 && { networkIsolation: this._networkIsolation },
@@ -633,28 +539,31 @@ var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraS
633
539
  * Execute a command in the sandbox and return the result.
634
540
  */
635
541
  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
- };
542
+ if (this._sandbox?.status !== "RUNNING") {
543
+ if (!this._checkpointName) throw new _mastra_core_workspace.SandboxNotReadyError(this.id);
544
+ this._sandbox = null;
545
+ await this.start();
546
+ }
547
+ const fullCommand = args.length > 0 ? `${command} ${args.map(shellQuote).join(" ")}` : command;
548
+ const timeout = options.timeout ?? this._timeout;
549
+ const env = options.env ? Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== void 0)) : void 0;
550
+ const startedAt = Date.now();
551
+ const result = await this.railway.exec(fullCommand, {
552
+ ...timeout !== void 0 && { timeoutSec: Math.ceil(timeout / 1e3) },
553
+ ...options.cwd !== void 0 && { cwd: options.cwd },
554
+ ...env !== void 0 && { env }
657
555
  });
556
+ const exitCode = result.exitCode ?? -1;
557
+ return {
558
+ success: exitCode === 0,
559
+ exitCode,
560
+ stdout: result.stdout,
561
+ stderr: result.stderr,
562
+ executionTimeMs: Date.now() - startedAt,
563
+ command,
564
+ args,
565
+ timedOut: result.timedOut
566
+ };
658
567
  }
659
568
  };
660
569
  //#endregion