@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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Daytona, DaytonaNotFoundError, SandboxState } from '@daytonaio/sdk';
1
+ import { Daytona, SandboxState, DaytonaNotFoundError } from '@daytonaio/sdk';
2
2
  import { SandboxProcessManager, MastraSandbox, SandboxNotReadyError, ProcessHandle } from '@mastra/core/workspace';
3
3
  import { createHash } from 'crypto';
4
4
 
@@ -738,6 +738,29 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
738
738
  name = "DaytonaSandbox";
739
739
  provider = "daytona";
740
740
  // Non-optional (initialized by base class when mount() exists)
741
+ /**
742
+ * Networking capability: public HTTPS URLs for sandbox ports.
743
+ * Daytona exposes ports through preview links (`getPreviewLink(port)`) —
744
+ * if the port is closed it is opened automatically. Private sandboxes
745
+ * require the preview token; pass `public: true` for tokenless URLs
746
+ * (required for sandbox deploys).
747
+ *
748
+ * When not attached in this process, the sandbox is looked up by identity
749
+ * (`daytona.get()` does not start it), so other processes can resolve
750
+ * deployments without waking a stopped sandbox.
751
+ */
752
+ networking = {
753
+ getPortUrl: async (port) => {
754
+ try {
755
+ const sandbox = this._sandbox ?? await this.lookupDetachedSandbox();
756
+ if (!sandbox) return null;
757
+ const preview = await sandbox.getPreviewLink(port);
758
+ return preview?.url ?? null;
759
+ } catch {
760
+ return null;
761
+ }
762
+ }
763
+ };
741
764
  status = "pending";
742
765
  _daytona = null;
743
766
  _sandbox = null;
@@ -763,6 +786,7 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
763
786
  networkBlockAll;
764
787
  networkAllowList;
765
788
  connectionOpts;
789
+ _constructorOptions;
766
790
  constructor(options = {}) {
767
791
  super({
768
792
  ...options,
@@ -795,10 +819,34 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
795
819
  ...options.apiUrl !== void 0 && { apiUrl: options.apiUrl },
796
820
  ...options.target !== void 0 && { target: options.target }
797
821
  };
822
+ this._constructorOptions = { ...options };
798
823
  }
799
824
  generateId() {
800
825
  return `daytona-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
801
826
  }
827
+ /**
828
+ * Construct a sibling `DaytonaSandbox` that inherits this sandbox's
829
+ * configuration (credentials, snapshot/image, resources, network policy)
830
+ * with per-instance overrides.
831
+ *
832
+ * Performs no I/O — the sandbox clone provisions (or reconnects to an
833
+ * existing Daytona sandbox with the same logical `id`) on its own `start()`.
834
+ * Use it when one configured sandbox acts as the template for a fleet of
835
+ * independent sandboxes (e.g. one per project).
836
+ *
837
+ * `options.idleTimeoutMinutes` maps to Daytona's `autoStopInterval`
838
+ * (minutes); `options.sandboxId` is ignored because Daytona reconnects by
839
+ * logical `id`.
840
+ */
841
+ clone(options = {}) {
842
+ const { id: _id, name: _name, ...base } = this._constructorOptions;
843
+ return new _DaytonaSandbox({
844
+ ...base,
845
+ ...options.id !== void 0 && { id: options.id },
846
+ ...options.env !== void 0 && { env: options.env },
847
+ ...options.idleTimeoutMinutes !== void 0 && { autoStopInterval: options.idleTimeoutMinutes }
848
+ });
849
+ }
802
850
  /**
803
851
  * Get the underlying Daytona Sandbox instance for direct access to Daytona APIs.
804
852
  *
@@ -888,13 +936,23 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
888
936
  * Unmounts all filesystems, then stops the sandbox.
889
937
  */
890
938
  async stop() {
939
+ if (!this._sandbox) {
940
+ try {
941
+ const existing = await this.lookupDetachedSandbox();
942
+ if (existing && existing.state === SandboxState.STARTED) {
943
+ await this._daytona.stop(existing);
944
+ }
945
+ } catch {
946
+ }
947
+ return;
948
+ }
891
949
  for (const mountPath of [...this.mounts.entries.keys()]) {
892
950
  try {
893
951
  await this.unmount(mountPath);
894
952
  } catch {
895
953
  }
896
954
  }
897
- if (this._sandbox && this._daytona) {
955
+ if (this._daytona) {
898
956
  try {
899
957
  await this._daytona.stop(this._sandbox);
900
958
  } catch {
@@ -912,14 +970,13 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
912
970
  await this._daytona.delete(this._sandbox);
913
971
  } catch {
914
972
  }
915
- } else if (!this._sandbox && this._daytona) {
916
- const lookupKey = this._daytonaSandboxId ?? this.sandboxName;
917
- if (lookupKey) {
918
- try {
919
- const orphan = await this._daytona.get(lookupKey);
973
+ } else if (!this._sandbox) {
974
+ try {
975
+ const orphan = await this.lookupDetachedSandbox();
976
+ if (orphan) {
920
977
  await this._daytona.delete(orphan);
921
- } catch {
922
978
  }
979
+ } catch {
923
980
  }
924
981
  }
925
982
  this._sandbox = null;
@@ -998,6 +1055,18 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
998
1055
  const result = await handle.wait();
999
1056
  return { ...result, command, args };
1000
1057
  }
1058
+ /**
1059
+ * Bulk-write files into the sandbox filesystem via the SDK's native upload.
1060
+ */
1061
+ async writeFiles(files) {
1062
+ await this.ensureRunning();
1063
+ await this.daytona.fs.uploadFiles(
1064
+ files.map((file) => ({
1065
+ source: Buffer.isBuffer(file.content) ? file.content : Buffer.from(file.content),
1066
+ destination: file.path
1067
+ }))
1068
+ );
1069
+ }
1001
1070
  // ---------------------------------------------------------------------------
1002
1071
  // Mount Support
1003
1072
  // ---------------------------------------------------------------------------
@@ -1323,6 +1392,28 @@ var DaytonaSandbox = class _DaytonaSandbox extends MastraSandbox {
1323
1392
  this.logger.debug(`${LOG_PREFIX} Could not detect working directory, will omit from instructions`);
1324
1393
  }
1325
1394
  }
1395
+ /**
1396
+ * Look up the existing Daytona sandbox by identity WITHOUT starting it.
1397
+ * Used for detached (cross-process) networking/stop/destroy so those
1398
+ * operations never wake a stopped sandbox. Returns null when not found.
1399
+ */
1400
+ async lookupDetachedSandbox() {
1401
+ const lookupKey = this._daytonaSandboxId ?? this.sandboxName;
1402
+ if (!lookupKey) {
1403
+ return null;
1404
+ }
1405
+ if (!this._daytona) {
1406
+ this._daytona = new Daytona(this.connectionOpts);
1407
+ }
1408
+ try {
1409
+ return await this._daytona.get(lookupKey);
1410
+ } catch (error) {
1411
+ if (error instanceof DaytonaNotFoundError) {
1412
+ return null;
1413
+ }
1414
+ throw error;
1415
+ }
1416
+ }
1326
1417
  async findExistingSandbox() {
1327
1418
  const DEAD_STATES = [
1328
1419
  SandboxState.DESTROYED,