@mastra/daytona 0.5.2 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,80 @@
1
1
  # @mastra/daytona
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Added networking and bulk file upload support to `DaytonaSandbox`: ([#19577](https://github.com/mastra-ai/mastra/pull/19577))
8
+
9
+ - **Public port URLs**: `sandbox.networking.getPortUrl(port)` returns the preview URL for a port, enabling preview URLs and deploys with the new `@mastra/deployer-sandbox` package. Pass `public: true` for tokenless URLs.
10
+ - **Bulk file upload**: `sandbox.writeFiles(files)` uploads multiple files in one call via the SDK's native `fs.uploadFiles()`.
11
+ - **Detached lifecycle**: `getPortUrl()`, `stop()`, and `destroy()` now work from a fresh process by looking up the existing sandbox by its `id`, without starting a stopped sandbox first.
12
+
13
+ ```typescript
14
+ import { DaytonaSandbox } from '@mastra/daytona';
15
+
16
+ const sandbox = new DaytonaSandbox({ id: 'my-preview', public: true });
17
+ await sandbox.start();
18
+ const url = await sandbox.networking.getPortUrl(4111);
19
+ ```
20
+
21
+ - Added `clone()` support to the sandbox providers. `clone()` constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides for `id` and `env`, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). ([#19616](https://github.com/mastra-ai/mastra/pull/19616))
22
+
23
+ ```ts
24
+ const template = new E2BSandbox({ apiKey, template: 'base' });
25
+
26
+ const projectSandbox = template.clone({
27
+ id: 'mc-project-42',
28
+ env: { GITHUB_TOKEN: token },
29
+ idleTimeoutMinutes: 30,
30
+ });
31
+ await projectSandbox.start();
32
+ ```
33
+
34
+ `idleTimeoutMinutes` is a best-effort hint that maps to each provider's native lifetime knob (Railway `idleTimeoutMinutes`, E2B/Modal/Vercel timeout in milliseconds, Daytona `autoStopInterval`, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
35
+
36
+ ### Patch Changes
37
+
38
+ - Updated dependencies [[`ec857fc`](https://github.com/mastra-ai/mastra/commit/ec857fc79c264b53b38e16478c789b7177f2ad59), [`d7385ad`](https://github.com/mastra-ai/mastra/commit/d7385ad9e88f9e4f33d15c0ec0bfebedde0cbc2e), [`41a5392`](https://github.com/mastra-ai/mastra/commit/41a5392d9f6c5e18d6b227f0fc0ddf49c50774e9), [`3d6e539`](https://github.com/mastra-ai/mastra/commit/3d6e539272eb2ea0407034605ee1906b3be06b39), [`1426af2`](https://github.com/mastra-ai/mastra/commit/1426af24975879c000d13ac75673f630fcc970c1), [`a40adeb`](https://github.com/mastra-ai/mastra/commit/a40adeb222b961a56a58af56a106106525721b74), [`8a0d145`](https://github.com/mastra-ai/mastra/commit/8a0d145aadbdf7278665aceaaec364b35dd9bd94), [`bd2f1d2`](https://github.com/mastra-ai/mastra/commit/bd2f1d274d05e60e2366f005ea0d94d5cea0d5ff), [`e1f2fae`](https://github.com/mastra-ai/mastra/commit/e1f2faebaf048c3d4c2e2c01d293767c195d5794), [`63aa799`](https://github.com/mastra-ai/mastra/commit/63aa799c6b44eacc7806cda6846b7c5bbee06b37), [`b7e79c3`](https://github.com/mastra-ai/mastra/commit/b7e79c3c02ac5cd415db34ba0975ceafc1464333), [`675fbff`](https://github.com/mastra-ai/mastra/commit/675fbff84d3274391b33e852f76083c38a5514e5), [`da009e1`](https://github.com/mastra-ai/mastra/commit/da009e1aacd89ed94b8d1b2af09c9d4fe7c4db49), [`3b77e77`](https://github.com/mastra-ai/mastra/commit/3b77e7704936522e4769d29de1b5ea6901f302bd), [`c7d30cd`](https://github.com/mastra-ai/mastra/commit/c7d30cd86009c407df91105591f03cd6e3d2854d), [`21a0eb8`](https://github.com/mastra-ai/mastra/commit/21a0eb86746ba0b703acea360d4f84c6a5a493f2), [`8b20926`](https://github.com/mastra-ai/mastra/commit/8b20926cd59e2ba3d66458e062fa0e6e2ada3e68), [`975295d`](https://github.com/mastra-ai/mastra/commit/975295d418552f0d46a59edfef4c3ee555f9930a), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`6b1bf3b`](https://github.com/mastra-ai/mastra/commit/6b1bf3b9494bd51aa8f654c68c9355d6046fa2a1), [`35c2181`](https://github.com/mastra-ai/mastra/commit/35c2181e6a50e47c90ba36260db7c9723d54696f), [`0a2c22c`](https://github.com/mastra-ai/mastra/commit/0a2c22c902604439ec490319e14c17f331e0c84c), [`4cfdd64`](https://github.com/mastra-ai/mastra/commit/4cfdd645794feaea0c4ea711e70ecdfbef0c5b8e), [`b75d749`](https://github.com/mastra-ai/mastra/commit/b75d749621ff5d17e86bcb4ee809d301fb4f7cf3), [`821648b`](https://github.com/mastra-ai/mastra/commit/821648bf2871ef840100c7bacbecf676010bd12a), [`de86fd7`](https://github.com/mastra-ai/mastra/commit/de86fd7119f0438381d1a642e3d258143c0b9c29), [`2745031`](https://github.com/mastra-ai/mastra/commit/2745031d1d4a4978f037092da371428c32e2842a), [`b4b7ea8`](https://github.com/mastra-ai/mastra/commit/b4b7ea8733f033fc441ea47ed03f6afb17ec2248), [`3a8024c`](https://github.com/mastra-ai/mastra/commit/3a8024ce615f8aa89479c0d71fe61d10bb0040be), [`35865a5`](https://github.com/mastra-ai/mastra/commit/35865a53e194aa9634d6a70a97010e7a6b9d58b1), [`74faf8b`](https://github.com/mastra-ai/mastra/commit/74faf8bd9c1018f2492653c06b1e25fc8300e9e6), [`ef03fbc`](https://github.com/mastra-ai/mastra/commit/ef03fbcc556bcbc04c9b3d06fab88771ecaa043c), [`675fbff`](https://github.com/mastra-ai/mastra/commit/675fbff84d3274391b33e852f76083c38a5514e5), [`70687f7`](https://github.com/mastra-ai/mastra/commit/70687f7e495a322a02070b4a67cb0c77a5ca91ec), [`1fadac4`](https://github.com/mastra-ai/mastra/commit/1fadac44537caeefe81f9f775ae2f2f3d94e9069), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`76b7181`](https://github.com/mastra-ai/mastra/commit/76b71810366e6d90b9d3973149d1c7ba3659ffb9), [`792ec9a`](https://github.com/mastra-ai/mastra/commit/792ec9a0869bab8274cf5e0ed2840738737a1607), [`712b864`](https://github.com/mastra-ai/mastra/commit/712b864aa1ed12b14c54390ec17b69de163c37f7), [`85e4fb5`](https://github.com/mastra-ai/mastra/commit/85e4fb50087a81c74df3a762f53b56373db0b912), [`0c0e8d7`](https://github.com/mastra-ai/mastra/commit/0c0e8d7becd4d1445c656b78d5d845f606c1ff9d), [`a7bbe77`](https://github.com/mastra-ai/mastra/commit/a7bbe773577f60bc4761b534ef7ec6b476332dad), [`72e437c`](https://github.com/mastra-ai/mastra/commit/72e437c515942c80b9def5b026e0bdee61b469d9), [`8f7a5de`](https://github.com/mastra-ai/mastra/commit/8f7a5dedc246cdc938bb65516703cf9b27b03756), [`a7bbe77`](https://github.com/mastra-ai/mastra/commit/a7bbe773577f60bc4761b534ef7ec6b476332dad), [`11f6cd9`](https://github.com/mastra-ai/mastra/commit/11f6cd96fe42582403416608beb212cc1a2cc79e), [`ef03c0c`](https://github.com/mastra-ai/mastra/commit/ef03c0cfc62367a458e4cc56462e2148b35681c5), [`4fb4d88`](https://github.com/mastra-ai/mastra/commit/4fb4d881bc107acee13890ad4d78661016c510ed), [`4e68363`](https://github.com/mastra-ai/mastra/commit/4e683634f94ebd062d26a3bb6093a8dfc7263d37), [`c328769`](https://github.com/mastra-ai/mastra/commit/c3287698ff8ef98dba86d415faa566fa3e5f4d56), [`9f7c67a`](https://github.com/mastra-ai/mastra/commit/9f7c67abeeb52c41c51a9b5edee60b62afe7cd8d), [`3b65e68`](https://github.com/mastra-ai/mastra/commit/3b65e68d7f1c771c7a70eea42d83fefdd28cad88), [`4eba27a`](https://github.com/mastra-ai/mastra/commit/4eba27adcf60f991df0e62f94b3e75b4e67f3b4b), [`c701be3`](https://github.com/mastra-ai/mastra/commit/c701be32d7d9aa94a66da8c6cc38dcac6856f464), [`db650ce`](https://github.com/mastra-ai/mastra/commit/db650ce490348914e85b93651d83acdf8f2a4c31), [`232fcbc`](https://github.com/mastra-ai/mastra/commit/232fcbc14fce625dd672ba043329c0b732c62be2), [`6354eeb`](https://github.com/mastra-ai/mastra/commit/6354eeb32efa9f5f68f51dda394e90e2ee76f1fb), [`a8799bb`](https://github.com/mastra-ai/mastra/commit/a8799bb8e44f4a60d01e4e2acd3448ff80bf14f8), [`3d6e539`](https://github.com/mastra-ai/mastra/commit/3d6e539272eb2ea0407034605ee1906b3be06b39), [`e3868e2`](https://github.com/mastra-ai/mastra/commit/e3868e22babfffd0133771669ca724501c2dd58e), [`9251370`](https://github.com/mastra-ai/mastra/commit/9251370ad413af464aa22d7566338bec5613e8de), [`3491666`](https://github.com/mastra-ai/mastra/commit/34916663c4fdd43b48c21f4ab2d5fb6dcccc94f9), [`c0bec73`](https://github.com/mastra-ai/mastra/commit/c0bec732c93d1a22ae5e51ed66cf8cacca8bd6a6)]:
39
+ - @mastra/core@1.52.0
40
+
41
+ ## 0.6.0-alpha.0
42
+
43
+ ### Minor Changes
44
+
45
+ - Added networking and bulk file upload support to `DaytonaSandbox`: ([#19577](https://github.com/mastra-ai/mastra/pull/19577))
46
+ - **Public port URLs**: `sandbox.networking.getPortUrl(port)` returns the preview URL for a port, enabling preview URLs and deploys with the new `@mastra/deployer-sandbox` package. Pass `public: true` for tokenless URLs.
47
+ - **Bulk file upload**: `sandbox.writeFiles(files)` uploads multiple files in one call via the SDK's native `fs.uploadFiles()`.
48
+ - **Detached lifecycle**: `getPortUrl()`, `stop()`, and `destroy()` now work from a fresh process by looking up the existing sandbox by its `id`, without starting a stopped sandbox first.
49
+
50
+ ```typescript
51
+ import { DaytonaSandbox } from '@mastra/daytona';
52
+
53
+ const sandbox = new DaytonaSandbox({ id: 'my-preview', public: true });
54
+ await sandbox.start();
55
+ const url = await sandbox.networking.getPortUrl(4111);
56
+ ```
57
+
58
+ - Added `clone()` support to the sandbox providers. `clone()` constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides for `id` and `env`, so one configured sandbox can act as a template for a fleet of sandbox clones (for example, one per project). ([#19616](https://github.com/mastra-ai/mastra/pull/19616))
59
+
60
+ ```ts
61
+ const template = new E2BSandbox({ apiKey, template: 'base' });
62
+
63
+ const projectSandbox = template.clone({
64
+ id: 'mc-project-42',
65
+ env: { GITHUB_TOKEN: token },
66
+ idleTimeoutMinutes: 30,
67
+ });
68
+ await projectSandbox.start();
69
+ ```
70
+
71
+ `idleTimeoutMinutes` is a best-effort hint that maps to each provider's native lifetime knob (Railway `idleTimeoutMinutes`, E2B/Modal/Vercel timeout in milliseconds, Daytona `autoStopInterval`, Blaxel TTL duration). Docker and Apple Container ignore it since they have no provider-side idle teardown.
72
+
73
+ ### Patch Changes
74
+
75
+ - Updated dependencies [[`ec857fc`](https://github.com/mastra-ai/mastra/commit/ec857fc79c264b53b38e16478c789b7177f2ad59), [`e1f2fae`](https://github.com/mastra-ai/mastra/commit/e1f2faebaf048c3d4c2e2c01d293767c195d5794), [`63aa799`](https://github.com/mastra-ai/mastra/commit/63aa799c6b44eacc7806cda6846b7c5bbee06b37), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`76b7181`](https://github.com/mastra-ai/mastra/commit/76b71810366e6d90b9d3973149d1c7ba3659ffb9), [`0c0e8d7`](https://github.com/mastra-ai/mastra/commit/0c0e8d7becd4d1445c656b78d5d845f606c1ff9d), [`9f7c67a`](https://github.com/mastra-ai/mastra/commit/9f7c67abeeb52c41c51a9b5edee60b62afe7cd8d), [`3b65e68`](https://github.com/mastra-ai/mastra/commit/3b65e68d7f1c771c7a70eea42d83fefdd28cad88), [`e3868e2`](https://github.com/mastra-ai/mastra/commit/e3868e22babfffd0133771669ca724501c2dd58e)]:
76
+ - @mastra/core@1.52.0-alpha.5
77
+
3
78
  ## 0.5.2
4
79
 
5
80
  ### Patch Changes
package/README.md CHANGED
@@ -356,12 +356,8 @@ Then use the snapshot name in your sandbox config:
356
356
  ```typescript
357
357
  const workspace = new Workspace({
358
358
  mounts: {
359
- '/s3-data': new S3Filesystem({
360
- /* ... */
361
- }),
362
- '/gcs-data': new GCSFilesystem({
363
- /* ... */
364
- }),
359
+ '/s3-data': new S3Filesystem({/* ... */}),
360
+ '/gcs-data': new GCSFilesystem({/* ... */}),
365
361
  },
366
362
  sandbox: new DaytonaSandbox({ snapshot: 'cloud-fs-mounting' }),
367
363
  });
package/dist/index.cjs CHANGED
@@ -740,6 +740,29 @@ var DaytonaSandbox = class _DaytonaSandbox extends workspace.MastraSandbox {
740
740
  name = "DaytonaSandbox";
741
741
  provider = "daytona";
742
742
  // Non-optional (initialized by base class when mount() exists)
743
+ /**
744
+ * Networking capability: public HTTPS URLs for sandbox ports.
745
+ * Daytona exposes ports through preview links (`getPreviewLink(port)`) —
746
+ * if the port is closed it is opened automatically. Private sandboxes
747
+ * require the preview token; pass `public: true` for tokenless URLs
748
+ * (required for sandbox deploys).
749
+ *
750
+ * When not attached in this process, the sandbox is looked up by identity
751
+ * (`daytona.get()` does not start it), so other processes can resolve
752
+ * deployments without waking a stopped sandbox.
753
+ */
754
+ networking = {
755
+ getPortUrl: async (port) => {
756
+ try {
757
+ const sandbox = this._sandbox ?? await this.lookupDetachedSandbox();
758
+ if (!sandbox) return null;
759
+ const preview = await sandbox.getPreviewLink(port);
760
+ return preview?.url ?? null;
761
+ } catch {
762
+ return null;
763
+ }
764
+ }
765
+ };
743
766
  status = "pending";
744
767
  _daytona = null;
745
768
  _sandbox = null;
@@ -765,6 +788,7 @@ var DaytonaSandbox = class _DaytonaSandbox extends workspace.MastraSandbox {
765
788
  networkBlockAll;
766
789
  networkAllowList;
767
790
  connectionOpts;
791
+ _constructorOptions;
768
792
  constructor(options = {}) {
769
793
  super({
770
794
  ...options,
@@ -797,10 +821,34 @@ var DaytonaSandbox = class _DaytonaSandbox extends workspace.MastraSandbox {
797
821
  ...options.apiUrl !== void 0 && { apiUrl: options.apiUrl },
798
822
  ...options.target !== void 0 && { target: options.target }
799
823
  };
824
+ this._constructorOptions = { ...options };
800
825
  }
801
826
  generateId() {
802
827
  return `daytona-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
803
828
  }
829
+ /**
830
+ * Construct a sibling `DaytonaSandbox` that inherits this sandbox's
831
+ * configuration (credentials, snapshot/image, resources, network policy)
832
+ * with per-instance overrides.
833
+ *
834
+ * Performs no I/O — the sandbox clone provisions (or reconnects to an
835
+ * existing Daytona sandbox with the same logical `id`) on its own `start()`.
836
+ * Use it when one configured sandbox acts as the template for a fleet of
837
+ * independent sandboxes (e.g. one per project).
838
+ *
839
+ * `options.idleTimeoutMinutes` maps to Daytona's `autoStopInterval`
840
+ * (minutes); `options.sandboxId` is ignored because Daytona reconnects by
841
+ * logical `id`.
842
+ */
843
+ clone(options = {}) {
844
+ const { id: _id, name: _name, ...base } = this._constructorOptions;
845
+ return new _DaytonaSandbox({
846
+ ...base,
847
+ ...options.id !== void 0 && { id: options.id },
848
+ ...options.env !== void 0 && { env: options.env },
849
+ ...options.idleTimeoutMinutes !== void 0 && { autoStopInterval: options.idleTimeoutMinutes }
850
+ });
851
+ }
804
852
  /**
805
853
  * Get the underlying Daytona Sandbox instance for direct access to Daytona APIs.
806
854
  *
@@ -890,13 +938,23 @@ var DaytonaSandbox = class _DaytonaSandbox extends workspace.MastraSandbox {
890
938
  * Unmounts all filesystems, then stops the sandbox.
891
939
  */
892
940
  async stop() {
941
+ if (!this._sandbox) {
942
+ try {
943
+ const existing = await this.lookupDetachedSandbox();
944
+ if (existing && existing.state === sdk.SandboxState.STARTED) {
945
+ await this._daytona.stop(existing);
946
+ }
947
+ } catch {
948
+ }
949
+ return;
950
+ }
893
951
  for (const mountPath of [...this.mounts.entries.keys()]) {
894
952
  try {
895
953
  await this.unmount(mountPath);
896
954
  } catch {
897
955
  }
898
956
  }
899
- if (this._sandbox && this._daytona) {
957
+ if (this._daytona) {
900
958
  try {
901
959
  await this._daytona.stop(this._sandbox);
902
960
  } catch {
@@ -914,14 +972,13 @@ var DaytonaSandbox = class _DaytonaSandbox extends workspace.MastraSandbox {
914
972
  await this._daytona.delete(this._sandbox);
915
973
  } catch {
916
974
  }
917
- } else if (!this._sandbox && this._daytona) {
918
- const lookupKey = this._daytonaSandboxId ?? this.sandboxName;
919
- if (lookupKey) {
920
- try {
921
- const orphan = await this._daytona.get(lookupKey);
975
+ } else if (!this._sandbox) {
976
+ try {
977
+ const orphan = await this.lookupDetachedSandbox();
978
+ if (orphan) {
922
979
  await this._daytona.delete(orphan);
923
- } catch {
924
980
  }
981
+ } catch {
925
982
  }
926
983
  }
927
984
  this._sandbox = null;
@@ -1000,6 +1057,18 @@ var DaytonaSandbox = class _DaytonaSandbox extends workspace.MastraSandbox {
1000
1057
  const result = await handle.wait();
1001
1058
  return { ...result, command, args };
1002
1059
  }
1060
+ /**
1061
+ * Bulk-write files into the sandbox filesystem via the SDK's native upload.
1062
+ */
1063
+ async writeFiles(files) {
1064
+ await this.ensureRunning();
1065
+ await this.daytona.fs.uploadFiles(
1066
+ files.map((file) => ({
1067
+ source: Buffer.isBuffer(file.content) ? file.content : Buffer.from(file.content),
1068
+ destination: file.path
1069
+ }))
1070
+ );
1071
+ }
1003
1072
  // ---------------------------------------------------------------------------
1004
1073
  // Mount Support
1005
1074
  // ---------------------------------------------------------------------------
@@ -1325,6 +1394,28 @@ var DaytonaSandbox = class _DaytonaSandbox extends workspace.MastraSandbox {
1325
1394
  this.logger.debug(`${LOG_PREFIX} Could not detect working directory, will omit from instructions`);
1326
1395
  }
1327
1396
  }
1397
+ /**
1398
+ * Look up the existing Daytona sandbox by identity WITHOUT starting it.
1399
+ * Used for detached (cross-process) networking/stop/destroy so those
1400
+ * operations never wake a stopped sandbox. Returns null when not found.
1401
+ */
1402
+ async lookupDetachedSandbox() {
1403
+ const lookupKey = this._daytonaSandboxId ?? this.sandboxName;
1404
+ if (!lookupKey) {
1405
+ return null;
1406
+ }
1407
+ if (!this._daytona) {
1408
+ this._daytona = new sdk.Daytona(this.connectionOpts);
1409
+ }
1410
+ try {
1411
+ return await this._daytona.get(lookupKey);
1412
+ } catch (error) {
1413
+ if (error instanceof sdk.DaytonaNotFoundError) {
1414
+ return null;
1415
+ }
1416
+ throw error;
1417
+ }
1418
+ }
1328
1419
  async findExistingSandbox() {
1329
1420
  const DEAD_STATES = [
1330
1421
  sdk.SandboxState.DESTROYED,