@mastra/daytona 0.8.1-alpha.0 → 0.9.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,68 @@
1
1
  # @mastra/daytona
2
2
 
3
+ ## 0.9.0-alpha.2
4
+
5
+ ### Minor Changes
6
+
7
+ - Added computer-use support to `DaytonaSandbox`. Workspaces backed by Daytona can now take screenshots, control the mouse and keyboard, inspect the display, and open a noVNC viewer through the standard computer tools. ([#21701](https://github.com/mastra-ai/mastra/pull/21701))
8
+
9
+ ```typescript
10
+ const sandbox = new DaytonaSandbox();
11
+ await sandbox.start();
12
+
13
+ await sandbox.computer.leftClick(100, 200);
14
+ const screenshot = await sandbox.computer.screenshot();
15
+ ```
16
+
17
+ Desktop services start lazily on the first computer operation. Set `computerUse: false` to disable the capability or `computerUse: { autoStart: false }` to manage those services directly.
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [[`48ef1f1`](https://github.com/mastra-ai/mastra/commit/48ef1f1d24eedafbb07f64e659a81b52b67b8bf6), [`63796ba`](https://github.com/mastra-ai/mastra/commit/63796ba0fda60253be17535e68f6bbbf1e6ffa09), [`3c19dce`](https://github.com/mastra-ai/mastra/commit/3c19dcef8e73062a80627a4927eae3ec11145afd)]:
22
+ - @mastra/core@1.62.0-alpha.12
23
+
24
+ ## 0.9.0-alpha.1
25
+
26
+ ### Minor Changes
27
+
28
+ - Added a `secrets` option to `DaytonaSandbox` for injecting Daytona Secrets into sandboxes. Map environment variable names to Daytona Secret names and the real value is substituted into HTTPS request headers at egress — the raw credential never enters the sandbox. ([#22322](https://github.com/mastra-ai/mastra/pull/22322))
29
+
30
+ ```typescript
31
+ const sandbox = new DaytonaSandbox({
32
+ secrets: {
33
+ GITHUB_TOKEN: 'github-token',
34
+ },
35
+ });
36
+ ```
37
+
38
+ Closes https://github.com/mastra-ai/mastra/issues/22314
39
+
40
+ ### Patch Changes
41
+
42
+ - Fixed a leak where every command left its process handle behind, by removing Daytona's own `executeCommand` in favour of the shared one, which releases them. ([#21984](https://github.com/mastra-ai/mastra/pull/21984))
43
+
44
+ Command results now match every other provider: `command` holds the full command string, and the separate `args` array is gone.
45
+
46
+ ```typescript
47
+ const result = await sandbox.executeCommand('echo', ['hello']);
48
+ // before: result.command === 'echo', result.args === ['hello']
49
+ // after: result.command === 'echo hello', result.args === undefined
50
+ ```
51
+
52
+ - Starting a sandbox now reports whether it created a fresh sandbox or reconnected to an existing one, so an `onStart` handler can run first-time setup only when it's actually needed: ([#21984](https://github.com/mastra-ai/mastra/pull/21984))
53
+
54
+ ```typescript
55
+ new E2BSandbox({
56
+ id: 'session-1',
57
+ onStart: async ({ outcome }) => {
58
+ if (outcome === 'created') await cloneRepo();
59
+ },
60
+ });
61
+ ```
62
+
63
+ - Updated dependencies [[`4ff3ee2`](https://github.com/mastra-ai/mastra/commit/4ff3ee2bff7ed07528b4817f8f49639031c72a4d), [`c24754c`](https://github.com/mastra-ai/mastra/commit/c24754c1fb6fe144e5051e536e98c8a18b0214ac), [`45dd6ee`](https://github.com/mastra-ai/mastra/commit/45dd6ee089bd7df0d0c98a10098e483fd388e04a), [`32d3583`](https://github.com/mastra-ai/mastra/commit/32d358332cb8ac2306b83b73cf3536e74dbd435e), [`aca2869`](https://github.com/mastra-ai/mastra/commit/aca2869b2031982f3c4a2f52525c9be7cf123ef8)]:
64
+ - @mastra/core@1.62.0-alpha.11
65
+
3
66
  ## 0.8.1-alpha.0
4
67
 
5
68
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -618,6 +618,8 @@ function validateMountPath(mountPath) {
618
618
  }
619
619
  /** Allowlist for marker filenames from ls output — e.g. "mount-abc123" */
620
620
  const SAFE_MARKER_NAME = /^mount-[a-z0-9]+$/;
621
+ /** Default port of the noVNC web viewer started by Daytona computer use. */
622
+ const DEFAULT_NOVNC_PORT = 6080;
621
623
  /** Patterns indicating the sandbox is dead/gone (@daytonaio/sdk@0.143.0). */
622
624
  const SANDBOX_DEAD_PATTERNS = [
623
625
  /sandbox is not running/i,
@@ -690,6 +692,9 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
690
692
  _createdAt = null;
691
693
  _workingDir = null;
692
694
  _isRetrying = false;
695
+ _computerUseStarted = null;
696
+ computerUseAutoStart;
697
+ noVncPort;
693
698
  timeout;
694
699
  language;
695
700
  resources;
@@ -708,6 +713,7 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
708
713
  networkBlockAll;
709
714
  networkAllowList;
710
715
  domainAllowList;
716
+ secrets;
711
717
  connectionOpts;
712
718
  _constructorOptions;
713
719
  constructor(options = {}) {
@@ -734,12 +740,17 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
734
740
  this.networkBlockAll = options.networkBlockAll;
735
741
  this.networkAllowList = options.networkAllowList;
736
742
  this.domainAllowList = options.domainAllowList;
743
+ this.secrets = options.secrets;
737
744
  this.connectionOpts = {
738
745
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
739
746
  ...options.apiUrl !== void 0 && { apiUrl: options.apiUrl },
740
747
  ...options.target !== void 0 && { target: options.target }
741
748
  };
742
749
  this._constructorOptions = { ...options };
750
+ const computerUseOption = options.computerUse ?? true;
751
+ this.computerUseAutoStart = typeof computerUseOption === "object" ? computerUseOption.autoStart ?? true : true;
752
+ this.noVncPort = typeof computerUseOption === "object" ? computerUseOption.noVncPort ?? DEFAULT_NOVNC_PORT : DEFAULT_NOVNC_PORT;
753
+ if (computerUseOption !== false) this.computer = this.createComputer();
743
754
  }
744
755
  generateId() {
745
756
  return `daytona-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -791,26 +802,35 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
791
802
  return this.daytona;
792
803
  }
793
804
  /**
794
- * Start the Daytona sandbox.
795
- * Reconnects to an existing sandbox with the same logical ID if one exists,
796
- * otherwise creates a new sandbox instance.
805
+ * Acquisition primitives (base-orchestrated start): the base derives
806
+ * `created` structurally from whether an existing sandbox was found, so
807
+ * reconnecting to one with the same logical ID reports `connected`.
808
+ * Lookup errors other than not-found propagate deliberately — creating a
809
+ * duplicate sandbox on a transient/auth error would be worse than failing.
797
810
  */
798
- async start() {
799
- if (this._sandbox) return;
811
+ async find() {
812
+ if (this._sandbox) return this._sandbox;
800
813
  if (!this._daytona) this._daytona = new _daytonaio_sdk.Daytona(this.connectionOpts);
801
- const existing = await this.findExistingSandbox();
802
- if (existing) {
803
- this._sandbox = existing;
804
- this._daytonaSandboxId = existing.id;
805
- this._createdAt = existing.createdAt ? new Date(existing.createdAt) : /* @__PURE__ */ new Date();
806
- this.logger.debug(`${LOG_PREFIX} Reconnected to existing sandbox ${existing.id} for: ${this.id}`);
807
- const expectedPaths = Array.from(this.mounts.entries.keys());
808
- this.logger.debug(`${LOG_PREFIX} Running mount reconciliation...`);
809
- await this.reconcileMounts(expectedPaths);
810
- this.logger.debug(`${LOG_PREFIX} Mount reconciliation complete`);
811
- await this.detectWorkingDir();
812
- return;
814
+ return await this.findExistingSandbox() ?? void 0;
815
+ }
816
+ async connect(existing) {
817
+ if (existing === this._sandbox) return;
818
+ if (existing.state !== _daytonaio_sdk.SandboxState.STARTED) {
819
+ this.logger.debug(`${LOG_PREFIX} Restarting sandbox ${existing.id} (state: ${existing.state})`);
820
+ await this.waitForStableStateAndStart(existing);
813
821
  }
822
+ this._sandbox = existing;
823
+ this._daytonaSandboxId = existing.id;
824
+ this._createdAt = existing.createdAt ? new Date(existing.createdAt) : /* @__PURE__ */ new Date();
825
+ this.logger.debug(`${LOG_PREFIX} Reconnected to existing sandbox ${existing.id} for: ${this.id}`);
826
+ const expectedPaths = Array.from(this.mounts.entries.keys());
827
+ this.logger.debug(`${LOG_PREFIX} Running mount reconciliation...`);
828
+ await this.reconcileMounts(expectedPaths);
829
+ this.logger.debug(`${LOG_PREFIX} Mount reconciliation complete`);
830
+ await this.detectWorkingDir();
831
+ }
832
+ async create() {
833
+ if (!this._daytona) this._daytona = new _daytonaio_sdk.Daytona(this.connectionOpts);
814
834
  this.logger.debug(`${LOG_PREFIX} Creating sandbox for: ${this.id}`);
815
835
  const baseParams = compact({
816
836
  language: this.language,
@@ -828,7 +848,8 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
828
848
  public: this.sandboxPublic,
829
849
  networkBlockAll: this.networkBlockAll,
830
850
  networkAllowList: this.networkAllowList,
831
- domainAllowList: this.domainAllowList
851
+ domainAllowList: this.domainAllowList,
852
+ secrets: this.secrets
832
853
  });
833
854
  if (this.resources && !this.image) this.logger.warn(`${LOG_PREFIX} 'resources' option requires 'image' to take effect — falling back to snapshot-based creation without custom resources`);
834
855
  const createParams = this.image && !this.snapshotId ? compact({
@@ -864,6 +885,7 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
864
885
  await this._daytona.stop(this._sandbox);
865
886
  } catch {}
866
887
  this._sandbox = null;
888
+ this._computerUseStarted = null;
867
889
  }
868
890
  /**
869
891
  * Destroy the Daytona sandbox and clean up all resources.
@@ -880,6 +902,7 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
880
902
  this._sandbox = null;
881
903
  this._daytonaSandboxId = void 0;
882
904
  this._daytona = null;
905
+ this._computerUseStarted = null;
883
906
  this.mounts?.clear();
884
907
  }
885
908
  /**
@@ -933,18 +956,6 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
933
956
  return parts.join(" ");
934
957
  }
935
958
  /**
936
- * Execute a command in the sandbox and return the result.
937
- */
938
- async executeCommand(command, args = [], options = {}) {
939
- await this.ensureRunning();
940
- const fullCommand = args.length > 0 ? `${command} ${args.map(shellQuote).join(" ")}` : command;
941
- return {
942
- ...await (await this.processes.spawn(fullCommand, options)).wait(),
943
- command,
944
- args
945
- };
946
- }
947
- /**
948
959
  * Bulk-write files into the sandbox filesystem via the SDK's native upload.
949
960
  */
950
961
  async writeFiles(files) {
@@ -955,6 +966,96 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
955
966
  })));
956
967
  }
957
968
  /**
969
+ * Ensure the Daytona computer use processes (Xvfb, xfce4, x11vnc, noVNC)
970
+ * are running. Memoized per attached sandbox; the memo is reset when the
971
+ * sandbox stops, dies, or is destroyed so a fresh sandbox restarts them.
972
+ */
973
+ async ensureComputerUseStarted() {
974
+ if (!this.computerUseAutoStart) return;
975
+ if (!this._computerUseStarted) this._computerUseStarted = this.daytona.computerUse.start().then(() => void 0).catch((error) => {
976
+ this._computerUseStarted = null;
977
+ throw error;
978
+ });
979
+ return this._computerUseStarted;
980
+ }
981
+ /**
982
+ * Build the {@link SandboxComputer} capability backed by Daytona's
983
+ * computer use API (`sandbox.computerUse`).
984
+ *
985
+ * Every operation ensures the sandbox is running and the desktop processes
986
+ * are started, and retries once if the sandbox died (mirroring command
987
+ * execution behavior).
988
+ */
989
+ createComputer() {
990
+ const run = async (fn) => {
991
+ await this.ensureRunning();
992
+ return this.retryOnDead(async () => {
993
+ await this.ensureComputerUseStarted();
994
+ return fn(this.daytona.computerUse);
995
+ });
996
+ };
997
+ return {
998
+ screenshot: async () => {
999
+ const response = await run((computerUse) => computerUse.screenshot.takeFullScreen());
1000
+ if (!response.screenshot) throw new Error(`${LOG_PREFIX} Daytona returned an empty screenshot response`);
1001
+ return {
1002
+ data: new Uint8Array(Buffer.from(response.screenshot, "base64")),
1003
+ mediaType: "image/png"
1004
+ };
1005
+ },
1006
+ leftClick: async (x, y) => {
1007
+ await run((computerUse) => computerUse.mouse.click(x, y, "left"));
1008
+ },
1009
+ rightClick: async (x, y) => {
1010
+ await run((computerUse) => computerUse.mouse.click(x, y, "right"));
1011
+ },
1012
+ doubleClick: async (x, y) => {
1013
+ await run((computerUse) => computerUse.mouse.click(x, y, "left", true));
1014
+ },
1015
+ moveMouse: async (x, y) => {
1016
+ await run((computerUse) => computerUse.mouse.move(x, y));
1017
+ },
1018
+ drag: async (from, to) => {
1019
+ await run((computerUse) => computerUse.mouse.drag(from.x, from.y, to.x, to.y));
1020
+ },
1021
+ scroll: async (direction, amount) => {
1022
+ await run(async (computerUse) => {
1023
+ const position = await computerUse.mouse.getPosition();
1024
+ return computerUse.mouse.scroll(position.x ?? 0, position.y ?? 0, direction, amount);
1025
+ });
1026
+ },
1027
+ type: async (text) => {
1028
+ await run((computerUse) => computerUse.keyboard.type(text));
1029
+ },
1030
+ press: async (key) => {
1031
+ await run((computerUse) => Array.isArray(key) ? computerUse.keyboard.hotkey(key.join("+")) : computerUse.keyboard.press(key));
1032
+ },
1033
+ getScreenSize: async () => {
1034
+ const info = await run((computerUse) => computerUse.display.getInfo());
1035
+ const display = info.displays?.find((d) => d.isActive) ?? info.displays?.[0];
1036
+ if (!display || display.width === void 0 || display.height === void 0) throw new Error(`${LOG_PREFIX} Daytona did not return display information`);
1037
+ return {
1038
+ width: display.width,
1039
+ height: display.height
1040
+ };
1041
+ },
1042
+ getCursorPosition: async () => {
1043
+ const position = await run((computerUse) => computerUse.mouse.getPosition());
1044
+ return {
1045
+ x: position.x ?? 0,
1046
+ y: position.y ?? 0
1047
+ };
1048
+ },
1049
+ streamUrl: async () => {
1050
+ try {
1051
+ return (await run(() => this.daytona.getPreviewLink(this.noVncPort)))?.url ?? null;
1052
+ } catch {
1053
+ return null;
1054
+ }
1055
+ }
1056
+ };
1057
+ }
1058
+ /**
958
1059
  * Mount a filesystem at a path in the sandbox.
959
1060
  * Uses FUSE tools (s3fs, gcsfuse) to mount cloud storage.
960
1061
  */
@@ -1315,10 +1416,6 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
1315
1416
  } catch {}
1316
1417
  return null;
1317
1418
  }
1318
- if (state !== _daytonaio_sdk.SandboxState.STARTED) {
1319
- this.logger.debug(`${LOG_PREFIX} Restarting sandbox ${sandbox.id} (state: ${state})`);
1320
- await this.waitForStableStateAndStart(sandbox);
1321
- }
1322
1419
  return sandbox;
1323
1420
  }
1324
1421
  /**
@@ -1393,6 +1490,7 @@ var DaytonaSandbox = class DaytonaSandbox extends _mastra_core_workspace.MastraS
1393
1490
  */
1394
1491
  handleSandboxTimeout() {
1395
1492
  this._sandbox = null;
1493
+ this._computerUseStarted = null;
1396
1494
  if (this.mounts) {
1397
1495
  for (const [path, entry] of this.mounts.entries) if (entry.state === "mounted" || entry.state === "mounting") this.mounts.set(path, { state: "pending" });
1398
1496
  }