@mastra/platform-workspace 1.5.0-alpha.3 → 1.5.0-alpha.5

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @mastra/platform-workspace
2
2
 
3
- Mastra Platform workspace provider. Gives agents environment-scoped sandbox execution and bucket-backed filesystem access through the Mastra Platform workspace proxy.
3
+ Mastra Platform workspace provider. It gives agents environment-scoped sandbox execution and bucket-backed filesystem access through the Mastra Platform workspace proxy.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,23 +8,6 @@ Mastra Platform workspace provider. Gives agents environment-scoped sandbox exec
8
8
  npm install @mastra/platform-workspace
9
9
  ```
10
10
 
11
- ## Configuration
12
-
13
- All options can be passed to the constructor or read from environment variables:
14
-
15
- | Option | Env var | Required |
16
- | ----------------- | ------------------------------ | ---------------- |
17
- | `accessToken` | `MASTRA_PLATFORM_ACCESS_TOKEN` | Yes |
18
- | `projectId` | `MASTRA_PROJECT_ID` | Yes |
19
- | `environmentId` | `MASTRA_ENVIRONMENT_ID` | Yes (sandbox) |
20
- | `actingUserId` | — | No (sandbox) |
21
- | `sandboxProvider` | `SANDBOX_PROVIDER` | No (sandbox) |
22
- | `bucketName` | `MASTRA_PLATFORM_BUCKET_NAME` | Yes (filesystem) |
23
-
24
- The sandbox provider resolves from the explicit `sandboxProvider` option, then `SANDBOX_PROVIDER`, then defaults to `e2b`. Set either option to `railway` to use Railway sandboxes. The proxy URL defaults to `https://workspaces.mastra.ai` and can be overridden with the `MASTRA_WORKSPACE_PROXY_URL` env var (useful for staging).
25
-
26
- Requests to the proxy are authenticated with `Authorization: Bearer <accessToken>`. For sandbox requests authenticated with a project access token, set `actingUserId` to the stable opaque user subject from your authentication system. It is sent as `x-acting-user-id` for token partitioning and attribution; it is not an authorization claim.
27
-
28
11
  ## Usage
29
12
 
30
13
  ```typescript
@@ -33,11 +16,8 @@ import { Workspace } from '@mastra/core/workspace';
33
16
  import { PlatformFilesystem, PlatformSandbox } from '@mastra/platform-workspace';
34
17
 
35
18
  const workspace = new Workspace({
36
- filesystem: new PlatformFilesystem({
37
- // accessToken, projectId, bucketName all fall back to env
38
- }),
19
+ filesystem: new PlatformFilesystem({}),
39
20
  sandbox: new PlatformSandbox({
40
- // accessToken, projectId, environmentId all fall back to env
41
21
  idleTimeoutMinutes: 30,
42
22
  networkIsolation: 'ISOLATED',
43
23
  }),
@@ -50,98 +30,24 @@ const agent = new Agent({
50
30
  });
51
31
  ```
52
32
 
53
- ## Filesystem
54
-
55
- `PlatformFilesystem` implements the Mastra filesystem interface against a workspace bucket. Object keys are percent-encoded per segment, so filenames with `?`, `#`, `%`, `&`, `+`, or spaces are preserved end-to-end.
56
-
57
- ```typescript
58
- const fs = new PlatformFilesystem({ bucketName: 'reports' });
59
-
60
- await fs.writeFile('/analyses/repo.md', markdown);
61
- const content = await fs.readFile('/analyses/repo.md');
62
- const entries = await fs.readdir('/analyses');
63
- await fs.moveFile('/analyses/repo.md', '/analyses/repo-final.md');
64
- ```
65
-
66
- Pass `readOnly: true` to mount the bucket read-only. Mutating calls will throw `WorkspaceReadOnlyError`.
67
-
68
- ## Sandbox
69
-
70
- `PlatformSandbox` executes commands inside a provider-backed sandbox tied to a Platform environment. Sessions boot from the configured provider template or checkpoint.
71
-
72
- ```typescript
73
- const sandbox = new PlatformSandbox({ environmentId: 'env_abc' });
74
-
75
- const result = await sandbox.executeCommand('python', ['analyze.py'], {
76
- timeout: 30_000,
77
- env: { INPUT: 'repo' },
78
- });
79
-
80
- console.log(result.stdout);
81
- ```
82
-
83
- Pass an existing `sandboxId` to reattach to a live sandbox instead of creating a new one.
33
+ ## Documentation
84
34
 
85
- ### Reusable templates
35
+ Both providers authenticate through the workspace proxy with `MASTRA_PLATFORM_ACCESS_TOKEN` and `MASTRA_PROJECT_ID`. `PlatformSandbox` also requires `MASTRA_ENVIRONMENT_ID`; `PlatformFilesystem` requires `MASTRA_PLATFORM_BUCKET_NAME`. Constructor values override environment variables, and `MASTRA_WORKSPACE_PROXY_URL` can point requests at a non-production proxy.
86
36
 
87
- Use `Template()` to prebuild a public repository at an immutable commit. `PlatformSandbox` sends the serialized definition to Platform, which content-addresses it and starts or reuses the provider build. Sandbox creation doesn't wait for the build. Platform boots from a prior template in the same family with matching resources when available, otherwise from the provider default, while the requested template builds in the background:
37
+ `PlatformFilesystem` implements the Mastra filesystem interface against a Platform bucket. It supports reading, writing, listing, moving, and deleting files, preserves reserved characters in object names, and can be mounted with `readOnly: true` to reject mutations.
88
38
 
89
- ```typescript
90
- import { PlatformSandbox, Template } from '@mastra/platform-workspace';
91
-
92
- const commitSha = process.env.REPOSITORY_COMMIT_SHA!;
93
- const template = Template()
94
- .cpuCount(4)
95
- .memoryMB(8_192)
96
- .setWorkdir('/workspace/repo')
97
- .setEnvs({ BUILD_CONFIG_MARKER: 'template-v1' })
98
- .aptInstall(['git', 'jq'])
99
- .runCmd('git clone https://github.com/mastra-ai/mastra.git /workspace/repo')
100
- .runCmd(`git checkout ${commitSha}`)
101
- .runCmd('pnpm install --frozen-lockfile');
102
-
103
- const sandbox = new PlatformSandbox({
104
- environmentId: 'env_abc',
105
- sandboxProvider: 'e2b',
106
- template,
107
- });
108
- await sandbox.start();
109
- ```
110
-
111
- Platform serializes the builder and stores build state under a server-derived content hash within the selected environment and provider. Passing the same definition to another sandbox reuses that build. Call `await template.build(options)` to start or reuse the provider build without provisioning a sandbox; it returns `ready`, `pending`, or `failed`. For E2B templates, `cpuCount()` and `memoryMB()` set the resources inherited by sandboxes created from the exact build or a resource-matched stale build. They default to 2 CPUs and 1,024 MB. Effective resource values participate in the template identity, so changing either value creates a distinct template while explicit defaults reuse the omitted-default build. If a pending build falls back to the provider base, that sandbox may use provider-default resources; check `templatePending` to detect this case. Railway currently ignores these two methods because its sandbox template API doesn't expose matching resource settings.
112
-
113
- By default, operation arguments are serialized and sent to Platform. Use `setEnvs(values, { ephemeral: true })` for short-lived build credentials: these values are sent separately, excluded from content identity and persistence, unavailable at runtime, and take precedence over serialized values with the same key. Supply them on every build or fresh provision that may need to build. Railway's provider cache includes transient build variables, so rotating a value may trigger another provider build even though the Platform template ID stays stable.
114
-
115
- ## Errors
39
+ `PlatformSandbox` starts or reconnects to an environment-scoped provider sandbox and implements command execution, lifecycle, and networking operations. The provider defaults to E2B and can be changed to Railway through `sandboxProvider` or `SANDBOX_PROVIDER`. Pass an existing `sandboxId` to reattach to a live sandbox, and `actingUserId` to partition and attribute project-token requests to a stable application user.
116
40
 
117
- Failures from the proxy raise `PlatformApiError`. Structured `{ error: { message, type } }` payloads from the proxy are parsed into `.code` (machine kind) and `.proxyMessage` (human string); the raw response body stays available on `.body`:
118
-
119
- ```typescript
120
- import { PlatformApiError } from '@mastra/platform-workspace';
121
-
122
- try {
123
- await fs.readFile('/missing.txt');
124
- } catch (err) {
125
- if (err instanceof PlatformApiError) {
126
- if (err.code === 'not_found') {
127
- // handle missing file
128
- } else if (err.code === 'authentication_error') {
129
- // refresh token
130
- }
131
- console.error(err.status, err.code, err.proxyMessage, err.body);
132
- }
133
- }
134
- ```
41
+ The exported `Template()` builder creates reusable sandbox images from commands, packages, environment values, repository checkouts, CPU, memory, and working-directory settings. Platform derives a content identity from the serialized template so matching definitions can reuse previous builds. Ephemeral environment values are excluded from that identity and are not persisted into the runtime image.
135
42
 
136
- `code` / `proxyMessage` are `undefined` when the proxy returns a non-JSON body (e.g. an HTML 502 from a load balancer).
43
+ Proxy failures throw `PlatformApiError`, which includes the HTTP status, parsed machine-readable error code, proxy message, and raw response body. Use these fields to distinguish missing resources, authentication failures, and provider errors.
137
44
 
138
- Filesystem-specific errors (`FileNotFoundError`, `FileExistsError`, `WorkspaceReadOnlyError`) are re-exported from `@mastra/core`.
45
+ - [Mastra Platform workspaces](https://mastra.ai/docs/mastra-platform/workspaces)
139
46
 
140
- ### Sandbox exec errors
47
+ ## Changelog
141
48
 
142
- `PlatformSandbox.executeCommand` runs over the direct-exec data plane (a WebSocket straight to the Railway tcp-proxy) and can throw two typed errors on unrecoverable failure:
49
+ See the [package changelog](https://github.com/mastra-ai/mastra/blob/main/workspaces/platform-workspace/CHANGELOG.md) for version history and release notes.
143
50
 
144
- - `SandboxDestroyedError` — the platform returned 410 for `/exec-lease`, meaning the sandbox has been destroyed. The cached sandbox id and lease are cleared, so a reused `PlatformSandbox` instance will re-provision on the next call. Fleet-level code that owns a binding store should catch this, clear the stale sandbox id, and reprovision + replay.
145
- - `SandboxExecTransportError` — both the initial WebSocket attempt and the built-in retry closed without an `exit` frame against a live sandbox. Carries `{ opened, closeCode, closeReason, wsEndpoint }` diagnostics plus `sandboxId`, `command`, and `attempts` so upstream logs / alerts can distinguish "the Railway data plane is broken" from "your command failed".
51
+ ## Support
146
52
 
147
- `PlatformApiError` (with status 404 / 500 / 501 on `/exec-lease`) can also bubble up from `executeCommand` those are configuration or platform errors, not "reprovision me" signals, and are propagated as-is.
53
+ We have an [open community Discord](https://discord.gg/mastra-ai). Come and say hello and let us know if you have any questions or need any help getting things running.
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ let _mastra_core_workspace = require("@mastra/core/workspace");
28
28
  let e2b = require("e2b");
29
29
  let child_process = require("child_process");
30
30
  let util = require("util");
31
+ let crypto = require("crypto");
31
32
  //#region src/client.ts
32
33
  const DEFAULT_PROXY_URL = "https://workspaces.mastra.ai";
33
34
  /**
@@ -1964,6 +1965,31 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
1964
1965
  }
1965
1966
  };
1966
1967
  //#endregion
1968
+ //#region ../../packages/_internals/workspace/dist/index.js
1969
+ /**
1970
+ * Setup completion marker shared by repo templates and their consumers.
1971
+ *
1972
+ * A repo template writes this file beside the checkout as its last build
1973
+ * step, so it exists only in images where every setup command succeeded. Its
1974
+ * content is a digest of the setup commands the image ran, letting a sandbox
1975
+ * booted from the image tell whether the setup it is about to run already
1976
+ * happened. Relative to the template's build cwd, which is also the runtime
1977
+ * working directory the repo was cloned into.
1978
+ */
1979
+ const SETUP_MARKER_PATH = ".mastra-sandbox/setup";
1980
+ /** Blank entries never become build steps, so they never count toward the digest either. */
1981
+ function normalizeSetupCommands(setupCommand) {
1982
+ return (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== "");
1983
+ }
1984
+ /** The marker content for a setup command list: `sha256:<hex>` over the commands joined by newlines. */
1985
+ function setupMarkerContent(setupCommand) {
1986
+ return `sha256:${(0, crypto.createHash)("sha256").update(normalizeSetupCommands(setupCommand).join("\n")).digest("hex")}`;
1987
+ }
1988
+ /** Shell step that writes the marker relative to the cwd. `content` is a digest, so it is shell-safe. */
1989
+ function setupMarkerCommand(content) {
1990
+ return `mkdir -p "$(dirname "${SETUP_MARKER_PATH}")" && printf '%s' '${content}' > "${SETUP_MARKER_PATH}"`;
1991
+ }
1992
+ //#endregion
1967
1993
  //#region src/repo-template.ts
1968
1994
  const execFileAsync = (0, util.promisify)(child_process.execFile);
1969
1995
  const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
@@ -2046,6 +2072,7 @@ function createRepoTemplate(options) {
2046
2072
  if (workingDirectory) template = template.runCmd(`mkdir -p "${workingDirectory}"`).setWorkdir(workingDirectory);
2047
2073
  template = template.runCmd(`git ${auth}clone ${cloneUrl} "${repoDir}"`).runCmd(`git -C "${repoDir}" ${auth}fetch origin ${sha}`).runCmd(`git -C "${repoDir}" checkout ${sha}`);
2048
2074
  for (const command of setupCommands) template = template.runCmd(`cd "${repoDir}" && ${command}`);
2075
+ template = template.runCmd(setupMarkerCommand(setupMarkerContent(setupCommands)));
2049
2076
  return template.withFamily(family);
2050
2077
  };
2051
2078
  }