@mastra/platform-workspace 0.3.0-alpha.1 → 1.0.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,88 @@
1
1
  # @mastra/platform
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - Removed support for using `MASTRA_PLATFORM_SECRET_KEY` to authenticate workspace providers. Use the platform-injected `MASTRA_PLATFORM_ACCESS_TOKEN` or pass `accessToken` explicitly instead. ([#20695](https://github.com/mastra-ai/mastra/pull/20695))
8
+
9
+ **Before:** Set `MASTRA_PLATFORM_SECRET_KEY`.
10
+
11
+ **After:** Use the platform-injected `MASTRA_PLATFORM_ACCESS_TOKEN`. For local development, set `MASTRA_PLATFORM_ACCESS_TOKEN` to an organization API token, or pass it explicitly:
12
+
13
+ ```typescript
14
+ import { PlatformSandbox } from '@mastra/platform-workspace';
15
+
16
+ const sandbox = new PlatformSandbox({
17
+ accessToken: 'sk_your-api-token',
18
+ projectId: 'project_abc',
19
+ environmentId: 'environment_abc',
20
+ });
21
+ ```
22
+
23
+ ### Minor Changes
24
+
25
+ - `PlatformSandbox.executeCommand` now retries a dropped connection once and continues using direct execution for later commands. Previously a single connection hiccup permanently downgraded the sandbox to a slower fallback route for the rest of its lifetime. ([#20482](https://github.com/mastra-ai/mastra/pull/20482))
26
+
27
+ Execution failures now surface directly:
28
+
29
+ - A destroyed sandbox throws the new `SandboxDestroyedError`. The cached sandbox is cleared, so the next call provisions a fresh one.
30
+ - Two connection failures in a row against a live sandbox throw the new `SandboxExecTransportError`, which carries `sandboxId`, `command`, `attempts`, `opened`, `closeCode`, `closeReason`, and `wsEndpoint` for diagnostics.
31
+ - Other platform errors previously masked by the fallback now bubble out as `PlatformApiError`.
32
+
33
+ ```ts
34
+ import { SandboxDestroyedError, SandboxExecTransportError } from '@mastra/platform-workspace';
35
+
36
+ try {
37
+ await sandbox.executeCommand('pytest');
38
+ } catch (err) {
39
+ if (err instanceof SandboxDestroyedError) {
40
+ // Reprovision and retry.
41
+ } else if (err instanceof SandboxExecTransportError) {
42
+ // Connection failed twice; sandbox is still alive.
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### Patch Changes
48
+
49
+ - Fixed `PlatformSandbox.clone()` silently ignoring `checkpointName`. Clones created with `clone({ checkpointName })` now reuse a matching captured checkpoint on `start()` instead of always provisioning a fresh sandbox, so repeated boots of the same session start much faster. ([#20477](https://github.com/mastra-ai/mastra/pull/20477))
50
+
51
+ ```ts
52
+ const child = template.clone({ checkpointName: 'mastra-recovery-session-42' });
53
+ await child.start(); // Reuses the captured checkpoint when one is available.
54
+ ```
55
+
56
+ An explicit `id` still takes precedence over `checkpointName` when both are passed.
57
+
58
+ - Updated dependencies [[`4844167`](https://github.com/mastra-ai/mastra/commit/4844167cff2d5ec5004e94edd34970833040fa3f), [`c5e56ff`](https://github.com/mastra-ai/mastra/commit/c5e56ff3bcabdf062708f2d48744fec304df6792), [`594f7b2`](https://github.com/mastra-ai/mastra/commit/594f7b28f5263fb9982fd50d95c471fb971ea984), [`7f4e26d`](https://github.com/mastra-ai/mastra/commit/7f4e26dd57bd9b23c278ea21235ab823a3810a6c), [`311f943`](https://github.com/mastra-ai/mastra/commit/311f943bee60e8fdf5c84499ea50e884276c936c), [`322daa6`](https://github.com/mastra-ai/mastra/commit/322daa6d90552909204044790d850958f6745fed), [`db4e6ff`](https://github.com/mastra-ai/mastra/commit/db4e6ff744503112eb64deeaf6c2b54bf26a54c7), [`5faf93f`](https://github.com/mastra-ai/mastra/commit/5faf93f03e19daea394b9e2a923f2e4f833407f2), [`82201f7`](https://github.com/mastra-ai/mastra/commit/82201f75fae8e050a8de2df08b74875ee74c6b83), [`cadaa13`](https://github.com/mastra-ai/mastra/commit/cadaa1372e1077c8e85eb64c5499ba8803caa323), [`0c89896`](https://github.com/mastra-ai/mastra/commit/0c8989673fb7d106837098398131e570c6023b68), [`6d19a65`](https://github.com/mastra-ai/mastra/commit/6d19a6517f5da3911023d446b7e2d5dad8adb1cb), [`23b4238`](https://github.com/mastra-ai/mastra/commit/23b423844ad0bcf2a502a68dd62866d6160f9f6d), [`80ad891`](https://github.com/mastra-ai/mastra/commit/80ad891f8cd10379aa5b5af7510c763783b2ab56), [`fb18da5`](https://github.com/mastra-ai/mastra/commit/fb18da56fc35689ae370621a8f10b5b0d8606e20), [`fb18da5`](https://github.com/mastra-ai/mastra/commit/fb18da56fc35689ae370621a8f10b5b0d8606e20), [`e320a76`](https://github.com/mastra-ai/mastra/commit/e320a763feaf65c6be3cebecf746defcbde161b3), [`03b4918`](https://github.com/mastra-ai/mastra/commit/03b4918c80d188ce375334c393e131c6e94bd7eb), [`14ef73a`](https://github.com/mastra-ai/mastra/commit/14ef73a4bbd73e7808414816eb0628ce1d80b5d7), [`b582f7f`](https://github.com/mastra-ai/mastra/commit/b582f7fa2f9c1f87d19efc63d344fbe5dda2608c), [`0a6598b`](https://github.com/mastra-ai/mastra/commit/0a6598bde80bde008986ad6616bed9632b9294cb), [`06000d7`](https://github.com/mastra-ai/mastra/commit/06000d73712911572e913b8a83339270296d0a22), [`1d677d5`](https://github.com/mastra-ai/mastra/commit/1d677d5f99d7db403f7828585e8c25f299f72628), [`9e1dad8`](https://github.com/mastra-ai/mastra/commit/9e1dad8f7b1cab2bb7ade90e5b7561f24577b88a), [`2f43145`](https://github.com/mastra-ai/mastra/commit/2f4314504c03cbba280414ac81ba3197448ee6b0), [`4e35a56`](https://github.com/mastra-ai/mastra/commit/4e35a56cdf8d74a5ff6d5eda01f2c1deaf6cc7be), [`d94b8e1`](https://github.com/mastra-ai/mastra/commit/d94b8e1cee67416d518a8c30099040061bef6a1c), [`93e28ec`](https://github.com/mastra-ai/mastra/commit/93e28ecce9031c02397e0ae8406593e5c7a95883), [`729dab4`](https://github.com/mastra-ai/mastra/commit/729dab408faccfaef0cbb048e5a4338f9172847e), [`484003d`](https://github.com/mastra-ai/mastra/commit/484003d33ff59330c86b19863e4a38732d7e4155), [`3de0188`](https://github.com/mastra-ai/mastra/commit/3de0188bfaf9a9c09c95fe322b53838cf52c70b6), [`34d34d8`](https://github.com/mastra-ai/mastra/commit/34d34d8c811df512fef4dd5459f79b7821be1866), [`b582f7f`](https://github.com/mastra-ai/mastra/commit/b582f7fa2f9c1f87d19efc63d344fbe5dda2608c), [`933d291`](https://github.com/mastra-ai/mastra/commit/933d291146b789c19442ad206f94da3e4be90c64), [`a1cb98d`](https://github.com/mastra-ai/mastra/commit/a1cb98d11990b560b98482292a1f34aa1a2d9092), [`598ad82`](https://github.com/mastra-ai/mastra/commit/598ad82d41c41389a686338a1d0e50b7400e1938), [`1fd6aad`](https://github.com/mastra-ai/mastra/commit/1fd6aad1ea4a9d32f65efa832307c35e981a4c0a)]:
59
+ - @mastra/core@1.56.0
60
+
61
+ ## 1.0.0-alpha.2
62
+
63
+ ### Major Changes
64
+
65
+ - Removed support for using `MASTRA_PLATFORM_SECRET_KEY` to authenticate workspace providers. Use the platform-injected `MASTRA_PLATFORM_ACCESS_TOKEN` or pass `accessToken` explicitly instead. ([#20695](https://github.com/mastra-ai/mastra/pull/20695))
66
+
67
+ **Before:** Set `MASTRA_PLATFORM_SECRET_KEY`.
68
+
69
+ **After:** Use the platform-injected `MASTRA_PLATFORM_ACCESS_TOKEN`. For local development, set `MASTRA_PLATFORM_ACCESS_TOKEN` to an organization API token, or pass it explicitly:
70
+
71
+ ```typescript
72
+ import { PlatformSandbox } from '@mastra/platform-workspace';
73
+
74
+ const sandbox = new PlatformSandbox({
75
+ accessToken: 'sk_your-api-token',
76
+ projectId: 'project_abc',
77
+ environmentId: 'environment_abc',
78
+ });
79
+ ```
80
+
81
+ ### Patch Changes
82
+
83
+ - Updated dependencies [[`d94b8e1`](https://github.com/mastra-ai/mastra/commit/d94b8e1cee67416d518a8c30099040061bef6a1c)]:
84
+ - @mastra/core@1.56.0-alpha.7
85
+
3
86
  ## 0.3.0-alpha.1
4
87
 
5
88
  ### Minor Changes
package/README.md CHANGED
@@ -12,14 +12,12 @@ npm install @mastra/platform-workspace
12
12
 
13
13
  All options can be passed to the constructor or read from environment variables:
14
14
 
15
- | Option | Env var | Required |
16
- | --------------- | ----------------------------- | ---------------- |
17
- | `accessToken` | `MASTRA_PLATFORM_SECRET_KEY` | Yes |
18
- | `projectId` | `MASTRA_PROJECT_ID` | Yes |
19
- | `environmentId` | `MASTRA_ENVIRONMENT_ID` | Yes (sandbox) |
20
- | `bucketName` | `MASTRA_PLATFORM_BUCKET_NAME` | Yes (filesystem) |
21
-
22
- `MASTRA_PLATFORM_ACCESS_TOKEN` is still read as a deprecated fallback for `accessToken`.
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
+ | `bucketName` | `MASTRA_PLATFORM_BUCKET_NAME` | Yes (filesystem) |
23
21
 
24
22
  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
23
 
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;CAC/D;AAWD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,qBAAqB;;;;;EAapE;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC;CACd;AAkBD,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2HAA2H;IAC3H,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,yGAAyG;IACzG,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE9B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAUzC;AAED,qBAAa,cAAc;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;gBAEjB,OAAO,EAAE,qBAAqB;IAQpC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,QAAQ,CAAC;CAoBrF"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;CAC/D;AAWD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,qBAAqB;;;;;EAOpE;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAC;CACd;AAkBD,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2HAA2H;IAC3H,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,yGAAyG;IACzG,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE9B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAUzC;AAED,qBAAa,cAAc;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;gBAEjB,OAAO,EAAE,qBAAqB;IAQpC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,QAAQ,CAAC;CAoBrF"}
package/dist/index.cjs CHANGED
@@ -39,7 +39,7 @@ function requireOption(value, name) {
39
39
  }
40
40
  function resolvePlatformOptions(options) {
41
41
  return {
42
- accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_SECRET_KEY ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, "accessToken"),
42
+ accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, "accessToken"),
43
43
  projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, "projectId"),
44
44
  proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/$/, ""),
45
45
  fetch: options.fetch ?? fetch
@@ -952,7 +952,7 @@ const platformSandboxProvider = {
952
952
  properties: {
953
953
  accessToken: {
954
954
  type: "string",
955
- description: "Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)"
955
+ description: "Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)"
956
956
  },
957
957
  projectId: {
958
958
  type: "string",
@@ -998,7 +998,7 @@ const platformFilesystemProvider = {
998
998
  properties: {
999
999
  accessToken: {
1000
1000
  type: "string",
1001
- description: "Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)"
1001
+ description: "Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)"
1002
1002
  },
1003
1003
  projectId: {
1004
1004
  type: "string",
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["nodePath","path","Buffer","MastraFilesystem","FileNotFoundError","buffer","WorkspaceReadOnlyError","FileExistsError","ProcessHandle","SandboxProcessManager","MastraSandbox","SandboxNotReadyError"],"sources":["../src/client.ts","../src/filesystem.ts","../src/direct-exec.ts","../src/sandbox.ts","../src/provider.ts"],"sourcesContent":["export interface PlatformClientOptions {\n accessToken?: string;\n projectId?: string;\n fetch?: typeof fetch;\n}\n\nexport interface PlatformRequestOptions extends RequestInit {\n query?: Record<string, string | number | boolean | undefined>;\n}\n\nconst DEFAULT_PROXY_URL = 'https://workspaces.mastra.ai';\n\n/**\n * Default per-request timeout for calls to the workspace proxy. Applied only\n * when the caller doesn't already pass an `AbortSignal`. Long-running routes\n * (e.g. `POST /sandbox/:id/exec`) pass their own longer signal.\n */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 60_000;\n\nexport function requireOption(value: string | undefined, name: string): string {\n if (!value) throw new Error(`${name} is required`);\n return value;\n}\n\nexport function resolvePlatformOptions(options: PlatformClientOptions) {\n return {\n accessToken: requireOption(\n options.accessToken ??\n process.env.MASTRA_PLATFORM_SECRET_KEY ??\n // Deprecated alias — prefer MASTRA_PLATFORM_SECRET_KEY.\n process.env.MASTRA_PLATFORM_ACCESS_TOKEN,\n 'accessToken',\n ),\n projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, 'projectId'),\n proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\\/$/, ''),\n fetch: options.fetch ?? fetch,\n };\n}\n\n/**\n * Structured error shape returned by the workspace proxy. All routes emit\n * `{ error: { message, type } }` on failure — see servers/workspace-proxy in\n * the Platform repo. Kept as a wire-level type so callers can switch on\n * `error.code` without re-parsing `error.body`.\n */\nexport interface PlatformProxyError {\n message: string;\n /** Machine-readable error kind, e.g. `not_found`, `invalid_request`, `authentication_error`. */\n type: string;\n}\n\nfunction parseProxyError(body: string): PlatformProxyError | undefined {\n if (!body) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const err = (parsed as { error?: unknown }).error;\n if (typeof err !== 'object' || err === null) return undefined;\n const { message, type } = err as { message?: unknown; type?: unknown };\n if (typeof message !== 'string' || typeof type !== 'string') return undefined;\n return { message, type };\n}\n\nexport class PlatformApiError extends Error {\n readonly status: number;\n readonly body: string;\n /** Machine-readable proxy error kind (e.g. `not_found`), when the response body matches `{ error: { message, type } }`. */\n readonly code: string | undefined;\n /** Human-readable proxy error message, when the response body matches `{ error: { message, type } }`. */\n readonly proxyMessage: string | undefined;\n\n constructor(status: number, body: string) {\n const parsed = parseProxyError(body);\n const summary = parsed ? `${parsed.type}: ${parsed.message}` : body;\n super(`Platform proxy request failed with ${status}${summary ? `: ${summary}` : ''}`);\n this.name = 'PlatformApiError';\n this.status = status;\n this.body = body;\n this.code = parsed?.type;\n this.proxyMessage = parsed?.message;\n }\n}\n\nexport class PlatformClient {\n readonly accessToken: string;\n readonly projectId: string;\n readonly proxyUrl: string;\n readonly fetch: typeof fetch;\n\n constructor(options: PlatformClientOptions) {\n const resolved = resolvePlatformOptions(options);\n this.accessToken = resolved.accessToken;\n this.projectId = resolved.projectId;\n this.proxyUrl = resolved.proxyUrl;\n this.fetch = resolved.fetch;\n }\n\n async request(path: string, options: PlatformRequestOptions = {}): Promise<Response> {\n const url = new URL(`${this.proxyUrl}/v1/projects/${encodeURIComponent(this.projectId)}${path}`);\n for (const [key, value] of Object.entries(options.query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n\n const headers = new Headers(options.headers);\n headers.set('authorization', `Bearer ${this.accessToken}`);\n\n // Strip our helper-only field so the underlying fetch sees a valid RequestInit.\n const { query: _query, ...fetchOptions } = options;\n // Apply a default timeout only when the caller didn't already supply an\n // AbortSignal — long-running routes (exec) provide their own longer signal.\n const signal = fetchOptions.signal ?? AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS);\n const response = await this.fetch(url, { ...fetchOptions, headers, signal });\n if (!response.ok) {\n throw new PlatformApiError(response.status, await response.text());\n }\n return response;\n }\n}\n","import { Buffer } from 'node:buffer';\nimport nodePath from 'node:path';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type {\n CopyOptions,\n FileContent,\n FileEntry,\n FileStat,\n FilesystemIcon,\n FilesystemInfo,\n InstructionsOption,\n ListOptions,\n MastraFilesystemOptions,\n ProviderStatus,\n ReadOptions,\n RemoveOptions,\n WriteOptions,\n} from '@mastra/core/workspace';\nimport { FileExistsError, FileNotFoundError, MastraFilesystem, WorkspaceReadOnlyError } from '@mastra/core/workspace';\nimport type { PlatformClientOptions } from './client.js';\nimport { PlatformClient } from './client.js';\n\ninterface ProxyListResponse {\n contents?: Array<{ key?: string; size?: number; lastModified?: string }>;\n commonPrefixes?: string[];\n}\n\nexport interface PlatformFilesystemOptions extends PlatformClientOptions, MastraFilesystemOptions {\n id?: string;\n bucketName?: string;\n readOnly?: boolean;\n displayName?: string;\n icon?: FilesystemIcon;\n description?: string;\n instructions?: InstructionsOption;\n}\n\nfunction normalizePath(input: string): string {\n if (!input || input === '.') return '/';\n let normalized = input.startsWith('/') ? input : `/${input}`;\n normalized = nodePath.posix.normalize(normalized);\n return normalized === '.' ? '/' : normalized;\n}\n\nfunction keyFromPath(path: string): string {\n const normalized = normalizePath(path);\n return normalized === '/' ? '' : normalized.slice(1);\n}\n\n/**\n * Encode each `/`-delimited segment of an object key with `encodeURIComponent`\n * so reserved URL characters (`?`, `#`, `%`, `&`, `+`, spaces, etc.) are\n * treated as part of the key instead of URL syntax. Kept segment-aware so\n * `/` continues to act as a path separator on the wire.\n */\nfunction encodeKeyPath(key: string): string {\n return key.split('/').map(encodeURIComponent).join('/');\n}\n\nfunction nameFromPath(path: string): string {\n const normalized = normalizePath(path);\n if (normalized === '/') return '';\n return normalized.slice(normalized.lastIndexOf('/') + 1);\n}\n\nfunction contentToBody(content: FileContent): string | Buffer {\n if (typeof content === 'string') return content;\n return Buffer.from(content);\n}\n\nfunction headerDate(headers: Headers, name: string): Date {\n const value = headers.get(name);\n return value ? new Date(value) : new Date(0);\n}\n\nfunction headerSize(headers: Headers): number {\n const value = headers.get('content-length');\n return value ? Number(value) : 0;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'status' in error && error.status === 404;\n}\n\nexport class PlatformFilesystem extends MastraFilesystem {\n readonly id: string;\n readonly name = 'PlatformFilesystem';\n readonly provider = 'platform';\n readonly readOnly?: boolean;\n readonly displayName?: string;\n readonly icon: FilesystemIcon;\n readonly description?: string;\n status: ProviderStatus = 'pending';\n\n private readonly _client: PlatformClient;\n private readonly _bucketName: string;\n private readonly _instructionsOverride?: InstructionsOption;\n\n constructor(options: PlatformFilesystemOptions = {}) {\n super({ ...options, name: 'PlatformFilesystem' });\n this.id = options.id ?? this.generateId();\n this._bucketName = options.bucketName ?? process.env.MASTRA_PLATFORM_BUCKET_NAME ?? '';\n if (!this._bucketName) throw new Error('bucketName is required');\n this.readOnly = options.readOnly;\n this.displayName = options.displayName;\n this.icon = options.icon ?? 'cloud';\n this.description = options.description;\n this._instructionsOverride = options.instructions;\n this._client = new PlatformClient(options);\n }\n\n private generateId(): string {\n return `platform-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n await this.ensureReady();\n let response: Response;\n try {\n response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,\n );\n } catch (error) {\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n return options?.encoding ? buffer.toString(options.encoding) : buffer;\n }\n\n async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('writeFile');\n const headers: Record<string, string> = {};\n if (options?.mimeType) headers['content-type'] = options.mimeType;\n if (options?.overwrite === false) headers['if-none-match'] = '*';\n try {\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'PUT',\n headers,\n body: contentToBody(content),\n });\n } catch (error) {\n if (typeof error === 'object' && error !== null && 'status' in error && error.status === 412) {\n throw new FileExistsError(path);\n }\n throw error;\n }\n }\n\n /**\n * Append bytes to a file.\n *\n * **Not atomic.** Object storage behind the workspace proxy has no native\n * append or compare-and-swap primitive, so this implementation is a\n * read-modify-write: it reads the current contents, concatenates the new\n * bytes, and PUTs the whole object back. Concurrent `appendFile` calls to\n * the same path can overwrite each other's writes (\"last write wins\").\n * Use `writeFile` with distinct keys for concurrent writers.\n */\n async appendFile(path: string, content: FileContent): Promise<void> {\n const existing = (await this.exists(path)) ? await this.readFile(path) : Buffer.alloc(0);\n await this.writeFile(\n path,\n Buffer.concat([Buffer.isBuffer(existing) ? existing : Buffer.from(existing), Buffer.from(content)]),\n );\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('deleteFile');\n try {\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'DELETE',\n query: { recursive: options?.recursive },\n });\n } catch (error) {\n if (isNotFound(error) && options?.force) return;\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n }\n\n async copyFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('copyFile');\n // The workspace proxy's `?op=copy` route always overwrites the destination;\n // there's no conditional wire field to prevent it. Reject the option\n // explicitly instead of silently overwriting when the caller asked us not to.\n if (options?.overwrite === false) {\n throw new Error('PlatformFilesystem.copyFile does not support overwrite: false — the proxy always overwrites.');\n }\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {\n method: 'POST',\n query: { op: 'copy' },\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ destination: keyFromPath(dest) }),\n });\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('moveFile');\n // Same rationale as copyFile: `?op=rename` always overwrites.\n if (options?.overwrite === false) {\n throw new Error('PlatformFilesystem.moveFile does not support overwrite: false — the proxy always overwrites.');\n }\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {\n method: 'POST',\n query: { op: 'rename' },\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ destination: keyFromPath(dest) }),\n });\n }\n\n async mkdir(path: string, _options?: { recursive?: boolean }): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('mkdir');\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'POST',\n query: { op: 'mkdir' },\n });\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n await this.deleteFile(path.endsWith('/') ? path : `${path}/`, { recursive: true, force: options?.force });\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n await this.ensureReady();\n const prefix = keyFromPath(path);\n const response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(prefix)}`,\n {\n query: {\n delimiter: options?.recursive ? undefined : '/',\n prefix: prefix ? `${prefix.replace(/\\/$/, '')}/` : undefined,\n },\n },\n );\n const json = (await response.json()) as ProxyListResponse;\n return [\n ...(json.commonPrefixes ?? []).map(prefix => ({\n name: nameFromPath(prefix.replace(/\\/$/, '')),\n type: 'directory' as const,\n })),\n ...(json.contents ?? [])\n .filter(object => object.key && !object.key.endsWith('/'))\n .map(object => ({\n name: nameFromPath(object.key!),\n type: 'file' as const,\n size: object.size,\n })),\n ].filter(\n entry => !options?.extension || entry.type === 'directory' || matchesExtension(entry.name, options.extension),\n );\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await this.stat(path);\n return true;\n } catch (error) {\n if (isNotFound(error) || error instanceof FileNotFoundError) return false;\n throw error;\n }\n }\n\n async stat(path: string): Promise<FileStat> {\n await this.ensureReady();\n const normalized = normalizePath(path);\n if (normalized === '/') {\n return { name: '', path: '/', type: 'directory', size: 0, createdAt: new Date(0), modifiedAt: new Date(0) };\n }\n let response: Response;\n try {\n response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,\n {\n method: 'HEAD',\n },\n );\n } catch (error) {\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n return {\n name: nameFromPath(path),\n path: normalized,\n type: normalized.endsWith('/') ? 'directory' : 'file',\n size: headerSize(response.headers),\n createdAt: headerDate(response.headers, 'last-modified'),\n modifiedAt: headerDate(response.headers, 'last-modified'),\n mimeType: response.headers.get('content-type') ?? undefined,\n };\n }\n\n realpath(path: string): Promise<string> {\n return Promise.resolve(normalizePath(path));\n }\n\n getInstructions(opts?: { requestContext?: RequestContext }): string {\n const defaultInstructions = `Platform filesystem backed by Mastra Platform bucket ${this._bucketName}. Use absolute workspace paths.`;\n if (typeof this._instructionsOverride === 'function') {\n return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });\n }\n if (typeof this._instructionsOverride === 'string') return this._instructionsOverride;\n return defaultInstructions;\n }\n\n getInfo(): FilesystemInfo<{ bucketName: string; displayName?: string; description?: string }> {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n readOnly: this.readOnly,\n icon: this.icon,\n metadata: {\n bucketName: this._bucketName,\n ...(this.displayName && { displayName: this.displayName }),\n ...(this.description && { description: this.description }),\n },\n };\n }\n}\n\nfunction matchesExtension(name: string, extension: string | string[]): boolean {\n const extensions = Array.isArray(extension) ? extension : [extension];\n return extensions.some(ext => name.endsWith(ext));\n}\n","/**\n * Direct exec client — opens Railway's tcp-proxy exec WebSocket directly using\n * a short-lived JWT minted by the workspace proxy's exec-lease endpoint. This\n * removes the platform data plane from the exec stdout/stderr path entirely\n * (see `docs/factory/direct-sandbox-connection.md` in the Platform repo),\n * cutting payload-scaled Cloud Run egress and RTT for commands like\n * `pnpm install` that stream tens of MB of output.\n *\n * The frame protocol below mirrors `connectExecWs()` in `railway@3.5.5`\n * (`workspaces/railway/node_modules/railway/dist/index.js`). The `railway`\n * SDK's version is pinned on both sides (platform + here); a version bump\n * signals the protocol may have drifted and this module must be revisited.\n */\n\n/** Byte-0 tag on binary WS frames for stdout output. */\nconst STDOUT_FRAME = 1;\n/** Byte-0 tag on binary WS frames for stderr output. */\nconst STDERR_FRAME = 3;\n/**\n * Upper bound on how long we'll wait for the WebSocket to open when the\n * caller didn't supply a `timeoutMs`. Guards against a stalled TLS/WS\n * handshake leaving the promise unresolved forever. Not applied once the\n * socket has opened — a caller with no timeout has opted in to unbounded\n * command runtime, just not to unbounded connection setup.\n */\nconst HANDSHAKE_DEADLINE_MS = 30_000;\n\n/**\n * Minimal WebSocket surface this module depends on. Matches both the browser\n * `WebSocket` global and Node 22+'s built-in `WebSocket`. Extracted so tests\n * can inject a fake without pulling in `ws` or jsdom.\n */\nexport interface DirectExecWebSocket {\n binaryType: 'blob' | 'arraybuffer';\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: { code: number; reason: string }) => void) | null;\n onerror: ((event: unknown) => void) | null;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n}\n\n/**\n * Factory that opens a WebSocket to `endpoint` with the given subprotocols.\n * Defaults to the global `WebSocket` when omitted, which works on Node 22+\n * (the package's minimum) and in the browser. Tests inject a fake here.\n */\nexport type DirectExecWebSocketFactory = (endpoint: string, subprotocols: string[]) => DirectExecWebSocket;\n\n/** Lease payload returned by `POST /v1/projects/:projectId/sandbox/:sandboxId/exec-lease`. */\nexport interface ExecLease {\n jwt: string;\n wsEndpoint: string;\n subprotocol: string;\n /** ISO-8601 UTC. Null when the provider issues a JWT without an `exp` claim. */\n expiresAt: string | null;\n}\n\n/** Inputs to a direct exec invocation. Mirrors the shape of the `/exec` route body. */\nexport interface DirectExecOptions {\n command: string;\n cwd?: string;\n env?: Record<string, string>;\n /**\n * Wall-clock cap for the exec. When elapsed, we close the socket and\n * return `{timedOut: true, exitCode: 124}` matching the semantics of the\n * proxy's `/exec` route.\n */\n timeoutMs?: number;\n onStdout?: (chunk: string) => void;\n onStderr?: (chunk: string) => void;\n /** Injected for tests. Defaults to `globalThis.WebSocket`. */\n webSocketFactory?: DirectExecWebSocketFactory;\n}\n\n/**\n * Result of a direct exec. Shape matches the workspace-proxy `/exec` response\n * so the caller (`PlatformSandbox.executeCommand`) can hand it back with no\n * translation.\n *\n * `exitCode` is `null` when the socket closed without an `exit` frame AND the\n * exec did not time out (rare — usually a mid-stream network drop). Callers\n * currently coerce `null` to `1` upstream; kept nullable here to preserve the\n * distinction for future observability.\n */\nexport interface DirectExecResult {\n exitCode: number | null;\n stdout: string;\n stderr: string;\n truncated: boolean;\n timedOut: boolean;\n /**\n * WebSocket close metadata. Populated on any close (normal or transport\n * failure). `opened` distinguishes handshake failures (never opened) from\n * mid-stream drops. Callers use this for diagnostic logging; not part of\n * the CommandResult contract.\n */\n closeCode?: number;\n closeReason?: string;\n opened?: boolean;\n}\n\nconst DEFAULT_WS_FACTORY: DirectExecWebSocketFactory = (endpoint, subprotocols) => {\n const WS = (globalThis as { WebSocket?: unknown }).WebSocket as\n | (new (url: string, protocols: string[]) => DirectExecWebSocket)\n | undefined;\n if (!WS) {\n throw new Error(\n 'Direct exec requires a WebSocket implementation. Node 22+ provides one globally; on older runtimes, pass webSocketFactory explicitly.',\n );\n }\n return new WS(endpoint, subprotocols);\n};\n\n/**\n * Open the provider exec WebSocket using `lease`, run `command`, and resolve\n * with the accumulated stdout/stderr + exit code. See the module docstring\n * for the wire protocol reference.\n *\n * The client sends `stdin_close` immediately after `init_exec`, matching the\n * SDK's own one-shot exec behavior — we never stream stdin from the caller.\n */\nexport function execViaLease(lease: ExecLease, options: DirectExecOptions): Promise<DirectExecResult> {\n const factory = options.webSocketFactory ?? DEFAULT_WS_FACTORY;\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n\n return new Promise<DirectExecResult>(resolve => {\n let stdout = '';\n let stderr = '';\n let exitCode: number | null = null;\n let timedOut = false;\n let settled = false;\n let opened = false;\n let closeCode: number | undefined;\n let closeReason: string | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let handshakeTimer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = () => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n if (handshakeTimer) clearTimeout(handshakeTimer);\n // Flush any bytes still buffered in the decoders. A stream:true decode\n // holds trailing partial multi-byte sequences until the next chunk, so\n // without a flush the final char(s) of a UTF-8 stream can be dropped.\n const stdoutTail = stdoutDecoder.decode();\n if (stdoutTail) {\n stdout += stdoutTail;\n options.onStdout?.(stdoutTail);\n }\n const stderrTail = stderrDecoder.decode();\n if (stderrTail) {\n stderr += stderrTail;\n options.onStderr?.(stderrTail);\n }\n try {\n socket.close(1000, '');\n } catch {\n /* already closed */\n }\n resolve({\n exitCode,\n stdout,\n stderr,\n truncated: false,\n timedOut,\n ...(closeCode !== undefined && { closeCode }),\n ...(closeReason !== undefined && { closeReason }),\n opened,\n });\n };\n\n // Arm the timeout BEFORE we open the socket so a stalled handshake can't\n // leave the promise pending. Callers with a positive `timeoutMs` get the\n // wall-clock cap they asked for; callers without one still get a\n // connect-only deadline that clears once the socket opens.\n if (options.timeoutMs !== undefined && options.timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n // 124 matches the proxy's `/exec` semantics (coreutils `timeout`\n // exit code) so callers that switch on exitCode see the same value.\n if (exitCode === null) exitCode = 124;\n settle();\n }, options.timeoutMs);\n } else {\n handshakeTimer = setTimeout(() => {\n // Never opened → treat as a transport failure. Leave exitCode=null\n // so the caller can distinguish this from a normal exit; do not\n // set timedOut (that flag is reserved for the wall-clock case).\n if (!opened) settle();\n }, HANDSHAKE_DEADLINE_MS);\n }\n\n const socket = factory(lease.wsEndpoint, [lease.subprotocol, lease.jwt]);\n socket.binaryType = 'arraybuffer';\n\n socket.onopen = () => {\n opened = true;\n if (handshakeTimer) {\n clearTimeout(handshakeTimer);\n handshakeTimer = undefined;\n }\n const data: Record<string, unknown> = { command: options.command };\n if (options.cwd) data.cwd = options.cwd;\n if (options.env && Object.keys(options.env).length > 0) data.env = options.env;\n socket.send(JSON.stringify({ type: 'init_exec', data }));\n // We never stream stdin for one-shot exec; the SDK does this too, and\n // omitting it can leave the exec hanging waiting on EOF.\n socket.send(JSON.stringify({ type: 'stdin_close' }));\n };\n\n socket.onmessage = event => {\n const { data } = event;\n if (data instanceof ArrayBuffer) {\n handleBinaryFrame(data);\n } else if (typeof data === 'string') {\n handleTextFrame(data);\n }\n };\n\n socket.onclose = event => {\n closeCode = event.code;\n closeReason = event.reason;\n if (!opened) {\n // Never opened — surface as a failure via exitCode=null,\n // truncated=false, timedOut=false so the caller can distinguish\n // it from a normal exit-0 by inspecting `exitCode === null`.\n settle();\n return;\n }\n // Preserve any info captured before close; if the server sent an\n // `exit` frame this is a no-op because settle() already ran.\n settle();\n };\n\n socket.onerror = () => {\n if (settled) return;\n if (!opened) {\n settle();\n }\n // If we're mid-stream and the socket errors, wait for onclose to fire\n // so we settle with whatever output we did receive.\n };\n\n function handleBinaryFrame(buffer: ArrayBuffer) {\n const view = new Uint8Array(buffer);\n if (view.length <= 1) return;\n if (view[0] === STDOUT_FRAME) {\n const chunk = stdoutDecoder.decode(view.subarray(1), { stream: true });\n stdout += chunk;\n options.onStdout?.(chunk);\n } else if (view[0] === STDERR_FRAME) {\n const chunk = stderrDecoder.decode(view.subarray(1), { stream: true });\n stderr += chunk;\n options.onStderr?.(chunk);\n }\n }\n\n function handleTextFrame(text: string) {\n let frame: { type?: string; data?: { exit_code?: number } };\n try {\n frame = JSON.parse(text) as { type?: string; data?: { exit_code?: number } };\n } catch {\n return;\n }\n if (frame.type === 'exit') {\n exitCode = frame.data?.exit_code ?? 0;\n settle();\n }\n // `durable_session` frames are intentionally ignored — we don't reattach\n // or expose session names on the one-shot exec path.\n }\n });\n}\n","import type { RequestContext } from '@mastra/core/di';\nimport type {\n CommandResult,\n ExecuteCommandOptions,\n InstructionsOption,\n MastraSandboxOptions,\n ProcessInfo,\n ProviderStatus,\n SandboxCloneOptions,\n SandboxInfo,\n SpawnProcessOptions,\n} from '@mastra/core/workspace';\nimport { MastraSandbox, ProcessHandle, SandboxNotReadyError, SandboxProcessManager } from '@mastra/core/workspace';\nimport type { PlatformClientOptions } from './client.js';\nimport { PlatformApiError, PlatformClient } from './client.js';\nimport type { DirectExecWebSocketFactory, ExecLease } from './direct-exec.js';\nimport { execViaLease } from './direct-exec.js';\n\nexport type PlatformSandboxNetworkIsolation = 'ISOLATED' | 'PRIVATE';\n\nexport interface PlatformSandboxOptions extends Omit<MastraSandboxOptions, 'processes'>, PlatformClientOptions {\n id?: string;\n environmentId?: string;\n sandboxId?: string;\n idleTimeoutMinutes?: number;\n networkIsolation?: PlatformSandboxNetworkIsolation;\n env?: Record<string, string>;\n timeout?: number;\n instructions?: InstructionsOption;\n /**\n * Injected WebSocket factory used by the direct-exec code path. Defaults to\n * the global `WebSocket` (available on Node 22+, this package's minimum) and\n * only exists so tests can drive the exec state machine deterministically\n * without a real network socket.\n */\n webSocketFactory?: DirectExecWebSocketFactory;\n}\n\ninterface ExecLeaseResponse {\n provider: string;\n sandboxId: string;\n providerResourceId: string;\n jwt: string;\n wsEndpoint: string;\n subprotocol: string;\n expiresAt: string | null;\n}\n\n/**\n * How long before a lease's stated `expiresAt` we should treat it as\n * expired. Avoids a race where the JWT is valid at cache-hit time but the\n * server rejects it by the time the WebSocket handshake completes.\n */\nconst LEASE_REFRESH_MARGIN_MS = 60_000;\n\ninterface CreateSandboxResponse {\n id: string;\n providerResourceId?: string | null;\n status?: string;\n createdAt?: string;\n destroyedAt?: string | null;\n}\n\n/** Max attempts for `POST /sandbox` when the proxy returns transient 5xx errors. */\nconst CREATE_MAX_ATTEMPTS = 3;\n/** Base delay between create retries; multiplied by the attempt number. */\nconst CREATE_RETRY_BASE_DELAY_MS = 2_000;\n\n/**\n * Diagnostic error thrown when the direct-exec WebSocket transport fails\n * twice in a row (opening handshake refused or socket closed mid-stream\n * without an `exit` frame). Distinguishes \"the sandbox transport is broken\"\n * from \"your command failed\" so callers can decide whether to retry at a\n * higher level (e.g. reprovision the sandbox) or surface the error.\n *\n * `opened` is `true` when the WebSocket completed its handshake at least\n * once before closing; `false` when Railway refused the upgrade outright.\n */\nexport class SandboxExecTransportError extends Error {\n readonly sandboxId: string | undefined;\n readonly command: string;\n readonly attempts: number;\n readonly opened: boolean;\n readonly closeCode: number | undefined;\n readonly closeReason: string | undefined;\n readonly wsEndpoint: string;\n\n constructor(\n message: string,\n diagnostics: {\n sandboxId?: string;\n command: string;\n attempts: number;\n opened: boolean;\n closeCode?: number;\n closeReason?: string;\n wsEndpoint: string;\n },\n ) {\n super(message);\n this.name = 'SandboxExecTransportError';\n this.sandboxId = diagnostics.sandboxId;\n this.command = diagnostics.command;\n this.attempts = diagnostics.attempts;\n this.opened = diagnostics.opened;\n this.closeCode = diagnostics.closeCode;\n this.closeReason = diagnostics.closeReason;\n this.wsEndpoint = diagnostics.wsEndpoint;\n }\n}\n\n/**\n * Thrown when `/exec-lease` returns 410 Gone — the sandbox has been destroyed\n * (Railway destroy, quota reclamation, etc.). The client cannot recover from\n * this on its own because it does not own the binding store; only the fleet\n * layer can clear the stale sandbox id and provision a fresh one. Callers\n * (typically `SandboxFleet`) must catch this and reprovision-and-replay.\n *\n * When this is thrown the cached `_lease` and `_sandboxId` on the sandbox\n * instance are cleared, so the next `ensureRunning()` on a reused instance\n * will re-provision cleanly.\n */\nexport class SandboxDestroyedError extends Error {\n readonly sandboxId: string | undefined;\n readonly command: string;\n readonly attempts: number;\n\n constructor(message: string, diagnostics: { sandboxId?: string; command: string; attempts: number }) {\n super(message);\n this.name = 'SandboxDestroyedError';\n this.sandboxId = diagnostics.sandboxId;\n this.command = diagnostics.command;\n this.attempts = diagnostics.attempts;\n }\n}\n\n/**\n * Compose a shell command line from a `command` string and optional `args`.\n *\n * IMPORTANT: `command` is treated as a **shell string** and passed to the\n * remote shell verbatim so callers can use pipes, redirects, and chaining\n * (`ls -la | grep foo`). This matches the contract of {@link MastraSandbox}\n * and the local sandbox implementation. `args` are always shell-quoted so\n * they cannot inject syntax.\n *\n * Callers MUST NOT pass untrusted input as `command`. Untrusted values must\n * be passed via `args`, where they are safely quoted. Passing untrusted\n * input as `command` allows arbitrary shell syntax execution on the remote\n * sandbox.\n */\nfunction buildCommand(command: string, args?: string[]): string {\n return args?.length ? `${command} ${args.map(shellQuote).join(' ')}` : command;\n}\n\nfunction shellQuote(arg: string): string {\n if (/^[a-zA-Z0-9._\\-/=:@]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, `'\\\\''`)}'`;\n}\n\nclass PlatformProcessHandle extends ProcessHandle {\n readonly pid: string;\n private readonly resultPromise: Promise<CommandResult>;\n private exitCodeValue: number | undefined;\n\n constructor(pid: string, resultPromise: Promise<CommandResult>, options?: SpawnProcessOptions) {\n super(options);\n this.pid = pid;\n this.resultPromise = resultPromise.then(result => {\n this.exitCodeValue = result.exitCode;\n if (result.stdout) this.emitStdout(result.stdout);\n if (result.stderr) this.emitStderr(result.stderr);\n return result;\n });\n }\n\n get exitCode(): number | undefined {\n return this.exitCodeValue;\n }\n\n async wait(): Promise<CommandResult> {\n return this.resultPromise;\n }\n\n async kill(): Promise<boolean> {\n // The workspace proxy has no cancel-exec endpoint; each `executeCommand`\n // is a synchronous round-trip that has already completed (or timed out)\n // by the time a handle exists to kill. Making this explicit avoids\n // callers silently believing they cancelled a still-running process.\n throw new Error('Platform sandbox command execution does not support killing individual processes');\n }\n\n async sendStdin(): Promise<void> {\n throw new Error('Platform sandbox command execution does not support stdin');\n }\n}\n\nclass PlatformProcessManager extends SandboxProcessManager<PlatformSandbox> {\n private spawnCounter = 0;\n\n /**\n * Spawn a process on the remote sandbox.\n *\n * `command` is interpreted as a shell string by the remote shell, matching\n * the {@link MastraSandbox} contract. See {@link PlatformSandbox.executeCommand}\n * for the untrusted-input caveat: never pass untrusted values as `command`.\n */\n async spawn(command: string, options: SpawnProcessOptions = {}): Promise<ProcessHandle> {\n const pid = `platform-proc-${Date.now().toString(36)}-${(this.spawnCounter++).toString(36)}`;\n const resultPromise = this.sandbox.executeCommand(command, undefined, options);\n const handle = new PlatformProcessHandle(pid, resultPromise, options);\n this._tracked.set(handle.pid, handle);\n return handle;\n }\n\n async list(): Promise<ProcessInfo[]> {\n return Array.from(this._tracked.values()).map(handle => ({\n pid: handle.pid,\n command: handle.command,\n running: handle.exitCode === undefined,\n ...(handle.exitCode !== undefined && { exitCode: handle.exitCode }),\n }));\n }\n}\n\nexport class PlatformSandbox extends MastraSandbox {\n readonly id: string;\n readonly name = 'PlatformSandbox';\n readonly provider = 'platform';\n status: ProviderStatus = 'pending';\n declare readonly processes: PlatformProcessManager;\n\n private readonly _client: PlatformClient;\n private readonly _environmentId: string;\n private _sandboxId?: string;\n private readonly _idleTimeoutMinutes?: number;\n private readonly _networkIsolation?: PlatformSandboxNetworkIsolation;\n private readonly _env: Record<string, string>;\n private readonly _timeout?: number;\n private readonly _instructionsOverride?: InstructionsOption;\n private _createdAt: Date | null = null;\n private readonly _webSocketFactory?: DirectExecWebSocketFactory;\n /**\n * Cached exec lease for this sandbox. `null` before the first exec and\n * after {@link destroy}. Refreshed when `expiresAt - LEASE_REFRESH_MARGIN_MS < now`\n * (see {@link _ensureLease}); a lease without a disclosed `expiresAt`\n * is refreshed on every call.\n */\n private _lease: (ExecLease & { expiresAtMs: number | null }) | null = null;\n /**\n * In-flight mint request; concurrent `_ensureLease` callers on a cold or\n * near-expiry cache all await this single promise so we don't burn N\n * `POST /exec-lease` round-trips when the sandbox is doing N parallel execs.\n * Cleared (regardless of success or failure) when the request settles.\n */\n private _leaseInFlight: Promise<ExecLease & { expiresAtMs: number | null }> | null = null;\n\n constructor(options: PlatformSandboxOptions = {}) {\n super({ ...options, name: 'PlatformSandbox', processes: new PlatformProcessManager() });\n this.id = options.id ?? this.generateId();\n this._client = new PlatformClient(options);\n this._environmentId = options.environmentId ?? process.env.MASTRA_ENVIRONMENT_ID ?? '';\n if (!this._environmentId && !options.sandboxId) throw new Error('environmentId is required');\n this._sandboxId = options.sandboxId;\n this._idleTimeoutMinutes = options.idleTimeoutMinutes;\n this._networkIsolation = options.networkIsolation;\n this._env = options.env ?? {};\n this._timeout = options.timeout;\n this._instructionsOverride = options.instructions;\n this._webSocketFactory = options.webSocketFactory;\n }\n\n private generateId(): string {\n return `platform-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n /**\n * Construct a sibling {@link PlatformSandbox} that inherits this sandbox's\n * credentials and defaults (access token, project, environment, network\n * isolation, timeout, instructions, env, idle timeout) with per-instance\n * overrides from `options`.\n *\n * Performs no I/O and does not require this sandbox to be started — the\n * returned sandbox is not started and provisions (or reattaches, when\n * `sandboxId` is set) on its own `start()`. Use it when one configured\n * sandbox acts as the template for a fleet of independent sandboxes\n * (e.g. one per project).\n */\n clone(options: SandboxCloneOptions = {}): PlatformSandbox {\n // The proxy hashes `body.id` on POST /sandbox to look up a prior\n // checkpoint. A stable `checkpointName` is only useful if it round-trips\n // to `body.id`, so route it through the sandbox id when the caller\n // didn't pick one explicitly. Without this, every clone gets a random\n // id and no boot ever hits its captured checkpoint (see\n // issue-platform-sandbox-clone-drops-checkpoint-name.md).\n const id = options.id ?? options.checkpointName;\n return new PlatformSandbox({\n ...(id !== undefined && { id }),\n accessToken: this._client.accessToken,\n projectId: this._client.projectId,\n fetch: this._client.fetch,\n environmentId: this._environmentId,\n ...(options.sandboxId !== undefined && { sandboxId: options.sandboxId }),\n idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,\n ...(this._networkIsolation !== undefined && { networkIsolation: this._networkIsolation }),\n env: options.env ?? this._env,\n ...(this._timeout !== undefined && { timeout: this._timeout }),\n ...(this._instructionsOverride !== undefined && { instructions: this._instructionsOverride }),\n ...(this._webSocketFactory !== undefined && { webSocketFactory: this._webSocketFactory }),\n });\n }\n\n async start(): Promise<void> {\n if (this._sandboxId) {\n try {\n const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);\n const json = (await response.json()) as CreateSandboxResponse;\n // A destroyed record (idle GC, manual delete) is not reattachable —\n // treat it like a missing sandbox so we fall through to a fresh\n // provision instead of pointing exec at a dead resource.\n if (!json.destroyedAt) {\n this._createdAt = json.createdAt ? new Date(json.createdAt) : new Date();\n return;\n }\n this._sandboxId = undefined;\n } catch (error) {\n if (!(error instanceof PlatformApiError) || error.status !== 404) throw error;\n this._sandboxId = undefined;\n }\n }\n\n if (!this._environmentId) throw new Error('environmentId is required');\n\n const body = JSON.stringify({\n // Sent so the platform can associate the provisioned resource with a\n // caller-stable identifier (used for opt-in checkpoint recovery). The\n // platform treats it as an advisory key: unknown values fall through\n // to a fresh sandbox, matching pre-existing behavior.\n id: this.id,\n environmentId: this._environmentId,\n idleTimeoutMinutes: this._idleTimeoutMinutes,\n networkIsolation: this._networkIsolation,\n env: this._env,\n });\n // Provisioning is observed to fail intermittently with proxy 500s while\n // the provider is under load. A create either succeeds (201) or fails\n // without allocating a caller-visible resource, so retrying transient\n // 5xx responses with a short backoff is safe and keeps a single flaky\n // window from killing the caller's whole workflow.\n let response: Response | undefined;\n for (let attempt = 1; ; attempt++) {\n try {\n response = await this._client.request('/sandbox', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body,\n });\n break;\n } catch (error) {\n const transient = error instanceof PlatformApiError && error.status >= 500;\n if (!transient || attempt >= CREATE_MAX_ATTEMPTS) throw error;\n await new Promise(resolve => setTimeout(resolve, CREATE_RETRY_BASE_DELAY_MS * attempt));\n }\n }\n const json = (await response.json()) as CreateSandboxResponse;\n this._sandboxId = json.id;\n this._createdAt = json.createdAt ? new Date(json.createdAt) : new Date();\n }\n\n async stop(): Promise<void> {\n await this.destroy();\n }\n\n async destroy(): Promise<void> {\n if (!this._sandboxId) return;\n await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`, { method: 'DELETE' });\n // Clear local state so a subsequent start() creates a fresh remote sandbox\n // instead of taking the reattach branch and pointing exec at a deleted resource.\n this._sandboxId = undefined;\n this._createdAt = null;\n // Drop the exec lease with the sandbox — the JWT is tied to the provider\n // instance id and would be rejected against a fresh one.\n this._lease = null;\n }\n\n /**\n * Execute a command on the remote sandbox.\n *\n * `command` is a **shell string**: it is concatenated verbatim into the\n * command line sent to the remote shell, which lets callers use pipes,\n * redirects, and chaining (`ls -la | grep foo`). This matches the contract\n * of {@link MastraSandbox} and the local sandbox implementation.\n *\n * `args`, when provided, are always shell-quoted so they cannot inject\n * additional shell syntax.\n *\n * Security: callers MUST NOT pass untrusted input as `command`. If any part\n * of the invocation is derived from an untrusted source, pass it through\n * `args` (which is safely quoted) or shell-quote it yourself before\n * inclusion. Untrusted `command` values allow arbitrary shell syntax\n * execution on the remote sandbox.\n */\n async executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<CommandResult> {\n await this.ensureRunning();\n if (!this._sandboxId) throw new SandboxNotReadyError(this.id);\n\n const started = Date.now();\n const fullCommand = buildCommand(command, args);\n // Nullish check so an explicit `timeout: 0` still overrides the instance\n // default. `_runDirectExec` omits `timeoutMs` from the exec payload when\n // the value is 0, which disables the client-side timer entirely.\n const effectiveTimeout = options?.timeout ?? this._timeout;\n\n // Direct-exec (WebSocket straight to Railway's tcp-proxy) is the only\n // data plane. `_runDirectExec` handles single-shot transport retry and\n // throws typed errors on unrecoverable failure: `SandboxDestroyedError`\n // when `/exec-lease` returns 410 (fleet must reprovision),\n // `SandboxExecTransportError` when the WebSocket transport fails twice\n // against a live sandbox, `PlatformApiError` for other `/exec-lease`\n // errors (404/500/501). See ./direct-exec.ts and\n // `docs/factory/direct-sandbox-connection.md` in the Platform repo.\n const result = await this._runDirectExec(fullCommand, effectiveTimeout, options);\n // `_runDirectExec` throws on transport failure (see its jsdoc), so a\n // `null` exitCode here can only mean `timedOut: true` — the sandbox\n // never got to send an exit frame because we cut the command short.\n // Use 124 for that (the conventional timeout exit code). We are NOT\n // coercing transport-failure nulls to fake exit codes — those throw.\n const exitCode = result.exitCode ?? 124;\n return {\n success: exitCode === 0,\n exitCode,\n stdout: result.stdout,\n stderr: result.stderr,\n timedOut: result.timedOut,\n command: fullCommand,\n executionTimeMs: Date.now() - started,\n };\n }\n\n /**\n * Run a single exec against the direct-exec transport, with one in-flight\n * retry on WebSocket transport failure (socket closed without an `exit`\n * frame and the exec did not time out). The retry mints a fresh lease\n * — the failure could be a stale JWT — and reopens a new WebSocket.\n *\n * Error taxonomy:\n * - **410 on `/exec-lease`** (either attempt) → the sandbox is gone.\n * Nulls the cached `_lease` and `_sandboxId` and throws\n * {@link SandboxDestroyedError}. Callers (typically `SandboxFleet`) must\n * catch this, clear the stale binding, and reprovision + replay.\n * - **Persistent transport failure** (both WS attempts close without an\n * `exit` frame against a live sandbox) → {@link SandboxExecTransportError}\n * with WebSocket close diagnostics.\n * - **Other `PlatformApiError`s** (404/500/501) propagate directly.\n * - **Real command result** (exit code from Railway's exit frame, or\n * `timedOut: true`) returns normally.\n *\n * Returns a result with a real `exitCode` OR `timedOut: true`. Never\n * returns `{ exitCode: null, timedOut: false }` — that case throws.\n */\n private async _runDirectExec(\n fullCommand: string,\n effectiveTimeout: number | undefined,\n options: ExecuteCommandOptions | undefined,\n ): Promise<{ exitCode: number | null; stdout: string; stderr: string; timedOut: boolean }> {\n // Filter undefined values out of the env overlay so we match the\n // Record<string, string> shape execViaLease expects. `ExecuteCommandOptions.env`\n // is NodeJS.ProcessEnv (string | undefined).\n const filteredEnv = options?.env\n ? Object.fromEntries(\n Object.entries(options.env).filter((entry): entry is [string, string] => entry[1] !== undefined),\n )\n : undefined;\n\n let lastResult: Awaited<ReturnType<typeof execViaLease>> | undefined;\n let lastLease: (ExecLease & { expiresAtMs: number | null }) | undefined;\n let attemptsMade = 0;\n // Two attempts: initial + one retry. On the second attempt we drop the\n // cached lease so we don't reuse a JWT that may itself be the cause of\n // the transport failure — but only if the cache still holds the same\n // lease we just failed against. A concurrent exec sharing this instance\n // may have already cached a fresh, unrelated lease in between, and we\n // must not discard that.\n for (let attempt = 0; attempt < 2; attempt++) {\n if (attempt > 0 && lastLease && this._lease === lastLease) this._lease = null;\n let lease: ExecLease & { expiresAtMs: number | null };\n try {\n lease = await this._ensureLease();\n } catch (error) {\n // 410 → sandbox has been destroyed. Clear all cached state so a\n // reused instance re-provisions cleanly, then hand off to the fleet\n // layer via a typed error. Other PlatformApiErrors (404/500/501)\n // propagate as-is — those are configuration or platform errors, not\n // a \"reprovision me\" signal.\n if (error instanceof PlatformApiError && error.status === 410) {\n this._lease = null;\n const priorSandboxId = this._sandboxId;\n this._sandboxId = undefined;\n throw new SandboxDestroyedError(\n `Sandbox ${priorSandboxId ?? '(unknown)'} was destroyed; /exec-lease returned 410`,\n {\n ...(priorSandboxId && { sandboxId: priorSandboxId }),\n command: fullCommand,\n attempts: attempt + 1,\n },\n );\n }\n throw error;\n }\n lastLease = lease;\n attemptsMade = attempt + 1;\n const result = await execViaLease(lease, {\n command: fullCommand,\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(filteredEnv !== undefined && { env: filteredEnv }),\n ...(effectiveTimeout != null && effectiveTimeout > 0 && { timeoutMs: effectiveTimeout }),\n ...(this._webSocketFactory && { webSocketFactory: this._webSocketFactory }),\n });\n lastResult = result;\n // `null` exitCode with `timedOut: false` means the socket closed\n // without an exit frame — a transport failure (handshake stalled,\n // mid-stream drop, expired token). Any other outcome (real exit code\n // or timed-out) is a valid result and we return it.\n if (result.exitCode !== null || result.timedOut) return result;\n }\n\n // Both attempts failed at the transport layer against a live sandbox.\n // Surface a loud, typed error with close diagnostics so callers can\n // distinguish \"your command failed\" from \"the sandbox transport is\n // broken.\"\n const result = lastResult!;\n const lease = lastLease!;\n // The lease from the failed second attempt is still cached; drop it so\n // the next `executeCommand` doesn't waste its first attempt on the same\n // implicated JWT before minting fresh. Identity-check first so a\n // concurrent exec that has already cached a fresh, unrelated lease\n // isn't collateral-damaged.\n if (this._lease === lease) this._lease = null;\n throw new SandboxExecTransportError(\n `Direct-exec transport failed for sandbox ${this._sandboxId ?? '(unknown)'} after ${attemptsMade} attempt(s)` +\n (result.closeCode !== undefined\n ? ` (close ${result.closeCode}${result.closeReason ? ` ${result.closeReason}` : ''})`\n : ''),\n {\n ...(this._sandboxId && { sandboxId: this._sandboxId }),\n command: fullCommand,\n attempts: attemptsMade,\n opened: result.opened ?? false,\n ...(result.closeCode !== undefined && { closeCode: result.closeCode }),\n ...(result.closeReason !== undefined && { closeReason: result.closeReason }),\n wsEndpoint: lease.wsEndpoint,\n },\n );\n }\n\n /**\n * Return a cached exec lease, minting a fresh one when the cache is empty\n * or the JWT is within {@link LEASE_REFRESH_MARGIN_MS} of `expiresAt`.\n *\n * Callers are expected to be on the \"sandbox is running\" path; we don't\n * re-check `_sandboxId` here because `executeCommand` already gated on it.\n */\n private async _ensureLease(): Promise<ExecLease & { expiresAtMs: number | null }> {\n const now = Date.now();\n // Cache hit only when we know the expiry AND we're comfortably before it.\n // A null `expiresAtMs` means the provider didn't disclose a TTL — treat\n // that as \"refresh every call\" rather than \"cache forever\", so a token\n // that turns out to be short-lived can't wedge the sandbox until restart.\n if (this._lease && this._lease.expiresAtMs !== null && this._lease.expiresAtMs - LEASE_REFRESH_MARGIN_MS > now) {\n return this._lease;\n }\n // Coalesce concurrent mints on a cold/expired cache.\n if (this._leaseInFlight) return this._leaseInFlight;\n if (!this._sandboxId) throw new SandboxNotReadyError(this.id);\n const sandboxId = this._sandboxId;\n const inFlight = (async () => {\n const response = await this._client.request(`/sandbox/${encodeURIComponent(sandboxId)}/exec-lease`, {\n method: 'POST',\n });\n const json = (await response.json()) as ExecLeaseResponse;\n const expiresAtMs = json.expiresAt ? Date.parse(json.expiresAt) : null;\n const lease = {\n jwt: json.jwt,\n wsEndpoint: json.wsEndpoint,\n subprotocol: json.subprotocol,\n expiresAt: json.expiresAt,\n // Guard against `Date.parse` returning NaN for malformed values by\n // treating them as \"no expiry known\", which forces a mint every call\n // rather than silently caching a broken lease forever.\n expiresAtMs: expiresAtMs !== null && !Number.isNaN(expiresAtMs) ? expiresAtMs : null,\n };\n this._lease = lease;\n return lease;\n })();\n this._leaseInFlight = inFlight;\n try {\n return await inFlight;\n } finally {\n // Clear on both success and failure so a failed mint doesn't wedge\n // future callers into awaiting the same rejected promise forever.\n if (this._leaseInFlight === inFlight) this._leaseInFlight = null;\n }\n }\n\n async getInfo(): Promise<SandboxInfo> {\n if (!this._sandboxId) {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this._createdAt ?? new Date(),\n };\n }\n const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);\n const json = (await response.json()) as CreateSandboxResponse;\n return {\n id: json.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: json.createdAt ? new Date(json.createdAt) : (this._createdAt ?? new Date()),\n metadata: {\n // The platform assigns its own sandbox id on create (the advisory id\n // sent in the POST body is not honored). Expose it so callers that\n // persist a reattach id (e.g. the Factory sandbox fleet, which reads\n // `metadata.sandboxId`) store the id the proxy actually recognizes\n // instead of the locally generated construction id.\n sandboxId: json.id,\n providerResourceId: json.providerResourceId ?? undefined,\n platformStatus: json.status,\n },\n };\n }\n\n getInstructions(opts?: { requestContext?: RequestContext }): string {\n const defaultInstructions = `Platform sandbox${this._sandboxId ? ` ${this._sandboxId}` : ''}. Execute commands with the sandbox command APIs.`;\n if (typeof this._instructionsOverride === 'function') {\n return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });\n }\n if (typeof this._instructionsOverride === 'string') return this._instructionsOverride;\n return defaultInstructions;\n }\n}\n","import type { FilesystemProvider, SandboxProvider } from '@mastra/core/editor';\nimport type { PlatformFilesystemOptions } from './filesystem.js';\nimport { PlatformFilesystem } from './filesystem.js';\nimport type { PlatformSandboxOptions } from './sandbox.js';\nimport { PlatformSandbox } from './sandbox.js';\n\nexport const platformSandboxProvider: SandboxProvider<PlatformSandboxOptions> = {\n id: 'platform',\n name: 'Mastra Platform Sandbox',\n description: 'Environment-scoped sandbox execution through Mastra Platform workspace proxy',\n configSchema: {\n type: 'object',\n properties: {\n accessToken: {\n type: 'string',\n description: 'Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)',\n },\n projectId: { type: 'string', description: 'Platform project ID (falls back to MASTRA_PROJECT_ID)' },\n environmentId: { type: 'string', description: 'Platform environment ID (falls back to MASTRA_ENVIRONMENT_ID)' },\n sandboxId: { type: 'string', description: 'Reattach to an existing Platform sandbox by ID' },\n idleTimeoutMinutes: { type: 'number', description: 'Minutes before the sandbox can be destroyed while idle' },\n networkIsolation: {\n type: 'string',\n description: 'Network isolation mode',\n enum: ['ISOLATED', 'PRIVATE'],\n default: 'ISOLATED',\n },\n env: { type: 'object', description: 'Environment variables', additionalProperties: { type: 'string' } },\n timeout: { type: 'number', description: 'Default command timeout in ms' },\n },\n },\n createSandbox: config => new PlatformSandbox(config),\n};\n\nexport const platformFilesystemProvider: FilesystemProvider<PlatformFilesystemOptions> = {\n id: 'platform',\n name: 'Mastra Platform Filesystem',\n description: 'Bucket-backed filesystem access through Mastra Platform workspace proxy',\n configSchema: {\n type: 'object',\n properties: {\n accessToken: {\n type: 'string',\n description: 'Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)',\n },\n projectId: { type: 'string', description: 'Platform project ID (falls back to MASTRA_PROJECT_ID)' },\n bucketName: {\n type: 'string',\n description: 'Platform workspace bucket name (falls back to MASTRA_PLATFORM_BUCKET_NAME)',\n },\n readOnly: { type: 'boolean', description: 'Mount as read-only', default: false },\n },\n },\n createFilesystem: config => new PlatformFilesystem(config),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,MAAM,oBAAoB;;;;;;AAO1B,MAAM,6BAA6B;AAEnC,SAAgB,cAAc,OAA2B,MAAsB;CAC7E,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,GAAG,KAAK,aAAa;CACjD,OAAO;AACT;AAEA,SAAgB,uBAAuB,SAAgC;CACrE,OAAO;EACL,aAAa,cACX,QAAQ,eACN,QAAQ,IAAI,8BAEZ,QAAQ,IAAI,8BACd,aACF;EACA,WAAW,cAAc,QAAQ,aAAa,QAAQ,IAAI,mBAAmB,WAAW;EACxF,WAAW,QAAQ,IAAI,8BAA8B,kBAAA,CAAmB,QAAQ,OAAO,EAAE;EACzF,OAAO,QAAQ,SAAS;CAC1B;AACF;AAcA,SAAS,gBAAgB,MAA8C;CACrE,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,IAAI;CAC1B,QAAQ;EACN;CACF;CACA,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM,OAAO,KAAA;CAC1D,MAAM,MAAO,OAA+B;CAC5C,IAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM,OAAO,KAAA;CACpD,MAAM,EAAE,SAAS,SAAS;CAC1B,IAAI,OAAO,YAAY,YAAY,OAAO,SAAS,UAAU,OAAO,KAAA;CACpE,OAAO;EAAE;EAAS;CAAK;AACzB;AAEA,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CACA;;CAEA;;CAEA;CAEA,YAAY,QAAgB,MAAc;EACxC,MAAM,SAAS,gBAAgB,IAAI;EACnC,MAAM,UAAU,SAAS,GAAG,OAAO,KAAK,IAAI,OAAO,YAAY;EAC/D,MAAM,sCAAsC,SAAS,UAAU,KAAK,YAAY,IAAI;EACpF,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,OAAO;EACZ,KAAK,OAAO,QAAQ;EACpB,KAAK,eAAe,QAAQ;CAC9B;AACF;AAEA,IAAa,iBAAb,MAA4B;CAC1B;CACA;CACA;CACA;CAEA,YAAY,SAAgC;EAC1C,MAAM,WAAW,uBAAuB,OAAO;EAC/C,KAAK,cAAc,SAAS;EAC5B,KAAK,YAAY,SAAS;EAC1B,KAAK,WAAW,SAAS;EACzB,KAAK,QAAQ,SAAS;CACxB;CAEA,MAAM,QAAQ,MAAc,UAAkC,CAAC,GAAsB;EACnF,MAAM,MAAM,IAAI,IAAI,GAAG,KAAK,SAAS,eAAe,mBAAmB,KAAK,SAAS,IAAI,MAAM;EAC/F,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,SAAS,CAAC,CAAC,GAC3D,IAAI,UAAU,KAAA,GAAW,IAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;EAGlE,MAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;EAC3C,QAAQ,IAAI,iBAAiB,UAAU,KAAK,aAAa;EAGzD,MAAM,EAAE,OAAO,QAAQ,GAAG,iBAAiB;EAG3C,MAAM,SAAS,aAAa,UAAU,YAAY,QAAQ,0BAA0B;EACpF,MAAM,WAAW,MAAM,KAAK,MAAM,KAAK;GAAE,GAAG;GAAc;GAAS;EAAO,CAAC;EAC3E,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,iBAAiB,SAAS,QAAQ,MAAM,SAAS,KAAK,CAAC;EAEnE,OAAO;CACT;AACF;;;ACpFA,SAAS,cAAc,OAAuB;CAC5C,IAAI,CAAC,SAAS,UAAU,KAAK,OAAO;CACpC,IAAI,aAAa,MAAM,WAAW,GAAG,IAAI,QAAQ,IAAI;CACrD,aAAaA,KAAAA,QAAS,MAAM,UAAU,UAAU;CAChD,OAAO,eAAe,MAAM,MAAM;AACpC;AAEA,SAAS,YAAY,QAAsB;CACzC,MAAM,aAAa,cAAcC,MAAI;CACrC,OAAO,eAAe,MAAM,KAAK,WAAW,MAAM,CAAC;AACrD;;;;;;;AAQA,SAAS,cAAc,KAAqB;CAC1C,OAAO,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC,KAAK,GAAG;AACxD;AAEA,SAAS,aAAa,QAAsB;CAC1C,MAAM,aAAa,cAAcA,MAAI;CACrC,IAAI,eAAe,KAAK,OAAO;CAC/B,OAAO,WAAW,MAAM,WAAW,YAAY,GAAG,IAAI,CAAC;AACzD;AAEA,SAAS,cAAc,SAAuC;CAC5D,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,OAAOC,OAAAA,OAAO,KAAK,OAAO;AAC5B;AAEA,SAAS,WAAW,SAAkB,MAAoB;CACxD,MAAM,QAAQ,QAAQ,IAAI,IAAI;CAC9B,OAAO,QAAQ,IAAI,KAAK,KAAK,oBAAI,IAAI,KAAK,CAAC;AAC7C;AAEA,SAAS,WAAW,SAA0B;CAC5C,MAAM,QAAQ,QAAQ,IAAI,gBAAgB;CAC1C,OAAO,QAAQ,OAAO,KAAK,IAAI;AACjC;AAEA,SAAS,WAAW,OAAyB;CAC3C,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,YAAY,SAAS,MAAM,WAAW;AAC9F;AAEA,IAAa,qBAAb,cAAwCC,uBAAAA,iBAAiB;CACvD;CACA,OAAgB;CAChB,WAAoB;CACpB;CACA;CACA;CACA;CACA,SAAyB;CAEzB;CACA;CACA;CAEA,YAAY,UAAqC,CAAC,GAAG;EACnD,MAAM;GAAE,GAAG;GAAS,MAAM;EAAqB,CAAC;EAChD,KAAK,KAAK,QAAQ,MAAM,KAAK,WAAW;EACxC,KAAK,cAAc,QAAQ,cAAc,QAAQ,IAAI,+BAA+B;EACpF,IAAI,CAAC,KAAK,aAAa,MAAM,IAAI,MAAM,wBAAwB;EAC/D,KAAK,WAAW,QAAQ;EACxB,KAAK,cAAc,QAAQ;EAC3B,KAAK,OAAO,QAAQ,QAAQ;EAC5B,KAAK,cAAc,QAAQ;EAC3B,KAAK,wBAAwB,QAAQ;EACrC,KAAK,UAAU,IAAI,eAAe,OAAO;CAC3C;CAEA,aAA6B;EAC3B,OAAO,eAAe,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;CACxF;CAEA,MAAM,SAAS,QAAc,SAAiD;EAC5E,MAAM,KAAK,YAAY;EACvB,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,KAAK,QAAQ,QAC5B,OAAO,mBAAmB,KAAK,WAAW,EAAE,GAAG,cAAc,YAAYF,MAAI,CAAC,GAChF;EACF,SAAS,OAAO;GACd,IAAI,WAAW,KAAK,GAAG,MAAM,IAAIG,uBAAAA,kBAAkBH,MAAI;GACvD,MAAM;EACR;EACA,MAAMI,WAASH,OAAAA,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC;EACvD,OAAO,SAAS,WAAWG,SAAO,SAAS,QAAQ,QAAQ,IAAIA;CACjE;CAEA,MAAM,UAAU,QAAc,SAAsB,SAAuC;EACzF,MAAM,KAAK,YAAY;EACvB,IAAI,KAAK,UAAU,MAAM,IAAIC,uBAAAA,uBAAuB,WAAW;EAC/D,MAAM,UAAkC,CAAC;EACzC,IAAI,SAAS,UAAU,QAAQ,kBAAkB,QAAQ;EACzD,IAAI,SAAS,cAAc,OAAO,QAAQ,mBAAmB;EAC7D,IAAI;GACF,MAAM,KAAK,QAAQ,QAAQ,OAAO,mBAAmB,KAAK,WAAW,EAAE,GAAG,cAAc,YAAYL,MAAI,CAAC,KAAK;IAC5G,QAAQ;IACR;IACA,MAAM,cAAc,OAAO;GAC7B,CAAC;EACH,SAAS,OAAO;GACd,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,YAAY,SAAS,MAAM,WAAW,KACvF,MAAM,IAAIM,uBAAAA,gBAAgBN,MAAI;GAEhC,MAAM;EACR;CACF;;;;;;;;;;;CAYA,MAAM,WAAW,QAAc,SAAqC;EAClE,MAAM,WAAY,MAAM,KAAK,OAAOA,MAAI,IAAK,MAAM,KAAK,SAASA,MAAI,IAAIC,OAAAA,OAAO,MAAM,CAAC;EACvF,MAAM,KAAK,UACTD,QACAC,OAAAA,OAAO,OAAO,CAACA,OAAAA,OAAO,SAAS,QAAQ,IAAI,WAAWA,OAAAA,OAAO,KAAK,QAAQ,GAAGA,OAAAA,OAAO,KAAK,OAAO,CAAC,CAAC,CACpG;CACF;CAEA,MAAM,WAAW,QAAc,SAAwC;EACrE,MAAM,KAAK,YAAY;EACvB,IAAI,KAAK,UAAU,MAAM,IAAII,uBAAAA,uBAAuB,YAAY;EAChE,IAAI;GACF,MAAM,KAAK,QAAQ,QAAQ,OAAO,mBAAmB,KAAK,WAAW,EAAE,GAAG,cAAc,YAAYL,MAAI,CAAC,KAAK;IAC5G,QAAQ;IACR,OAAO,EAAE,WAAW,SAAS,UAAU;GACzC,CAAC;EACH,SAAS,OAAO;GACd,IAAI,WAAW,KAAK,KAAK,SAAS,OAAO;GACzC,IAAI,WAAW,KAAK,GAAG,MAAM,IAAIG,uBAAAA,kBAAkBH,MAAI;GACvD,MAAM;EACR;CACF;CAEA,MAAM,SAAS,KAAa,MAAc,SAAsC;EAC9E,MAAM,KAAK,YAAY;EACvB,IAAI,KAAK,UAAU,MAAM,IAAIK,uBAAAA,uBAAuB,UAAU;EAI9D,IAAI,SAAS,cAAc,OACzB,MAAM,IAAI,MAAM,8FAA8F;EAEhH,MAAM,KAAK,QAAQ,QAAQ,OAAO,mBAAmB,KAAK,WAAW,EAAE,GAAG,cAAc,YAAY,GAAG,CAAC,KAAK;GAC3G,QAAQ;GACR,OAAO,EAAE,IAAI,OAAO;GACpB,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,EAAE,aAAa,YAAY,IAAI,EAAE,CAAC;EACzD,CAAC;CACH;CAEA,MAAM,SAAS,KAAa,MAAc,SAAsC;EAC9E,MAAM,KAAK,YAAY;EACvB,IAAI,KAAK,UAAU,MAAM,IAAIA,uBAAAA,uBAAuB,UAAU;EAE9D,IAAI,SAAS,cAAc,OACzB,MAAM,IAAI,MAAM,8FAA8F;EAEhH,MAAM,KAAK,QAAQ,QAAQ,OAAO,mBAAmB,KAAK,WAAW,EAAE,GAAG,cAAc,YAAY,GAAG,CAAC,KAAK;GAC3G,QAAQ;GACR,OAAO,EAAE,IAAI,SAAS;GACtB,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,EAAE,aAAa,YAAY,IAAI,EAAE,CAAC;EACzD,CAAC;CACH;CAEA,MAAM,MAAM,QAAc,UAAmD;EAC3E,MAAM,KAAK,YAAY;EACvB,IAAI,KAAK,UAAU,MAAM,IAAIA,uBAAAA,uBAAuB,OAAO;EAC3D,MAAM,KAAK,QAAQ,QAAQ,OAAO,mBAAmB,KAAK,WAAW,EAAE,GAAG,cAAc,YAAYL,MAAI,CAAC,KAAK;GAC5G,QAAQ;GACR,OAAO,EAAE,IAAI,QAAQ;EACvB,CAAC;CACH;CAEA,MAAM,MAAM,QAAc,SAAwC;EAChE,MAAM,KAAK,WAAWA,OAAK,SAAS,GAAG,IAAIA,SAAO,GAAGA,OAAK,IAAI;GAAE,WAAW;GAAM,OAAO,SAAS;EAAM,CAAC;CAC1G;CAEA,MAAM,QAAQ,QAAc,SAA6C;EACvE,MAAM,KAAK,YAAY;EACvB,MAAM,SAAS,YAAYA,MAAI;EAU/B,MAAM,OAAQ,OAAM,MATG,KAAK,QAAQ,QAClC,OAAO,mBAAmB,KAAK,WAAW,EAAE,GAAG,cAAc,MAAM,KACnE,EACE,OAAO;GACL,WAAW,SAAS,YAAY,KAAA,IAAY;GAC5C,QAAQ,SAAS,GAAG,OAAO,QAAQ,OAAO,EAAE,EAAE,KAAK,KAAA;EACrD,EACF,CACF,EAAA,CAC6B,KAAK;EAClC,OAAO,CACL,IAAI,KAAK,kBAAkB,CAAC,EAAA,CAAG,KAAI,YAAW;GAC5C,MAAM,aAAa,OAAO,QAAQ,OAAO,EAAE,CAAC;GAC5C,MAAM;EACR,EAAE,GACF,IAAI,KAAK,YAAY,CAAC,EAAA,CACnB,QAAO,WAAU,OAAO,OAAO,CAAC,OAAO,IAAI,SAAS,GAAG,CAAC,CAAC,CACzD,KAAI,YAAW;GACd,MAAM,aAAa,OAAO,GAAI;GAC9B,MAAM;GACN,MAAM,OAAO;EACf,EAAE,CACN,CAAC,CAAC,QACA,UAAS,CAAC,SAAS,aAAa,MAAM,SAAS,eAAe,iBAAiB,MAAM,MAAM,QAAQ,SAAS,CAC9G;CACF;CAEA,MAAM,OAAO,SAAgC;EAC3C,IAAI;GACF,MAAM,KAAK,KAAKA,OAAI;GACpB,OAAO;EACT,SAAS,OAAO;GACd,IAAI,WAAW,KAAK,KAAK,iBAAiBG,uBAAAA,mBAAmB,OAAO;GACpE,MAAM;EACR;CACF;CAEA,MAAM,KAAK,SAAiC;EAC1C,MAAM,KAAK,YAAY;EACvB,MAAM,aAAa,cAAcH,OAAI;EACrC,IAAI,eAAe,KACjB,OAAO;GAAE,MAAM;GAAI,MAAM;GAAK,MAAM;GAAa,MAAM;GAAG,2BAAW,IAAI,KAAK,CAAC;GAAG,4BAAY,IAAI,KAAK,CAAC;EAAE;EAE5G,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,KAAK,QAAQ,QAC5B,OAAO,mBAAmB,KAAK,WAAW,EAAE,GAAG,cAAc,YAAYA,OAAI,CAAC,KAC9E,EACE,QAAQ,OACV,CACF;EACF,SAAS,OAAO;GACd,IAAI,WAAW,KAAK,GAAG,MAAM,IAAIG,uBAAAA,kBAAkBH,OAAI;GACvD,MAAM;EACR;EACA,OAAO;GACL,MAAM,aAAaA,OAAI;GACvB,MAAM;GACN,MAAM,WAAW,SAAS,GAAG,IAAI,cAAc;GAC/C,MAAM,WAAW,SAAS,OAAO;GACjC,WAAW,WAAW,SAAS,SAAS,eAAe;GACvD,YAAY,WAAW,SAAS,SAAS,eAAe;GACxD,UAAU,SAAS,QAAQ,IAAI,cAAc,KAAK,KAAA;EACpD;CACF;CAEA,SAAS,SAA+B;EACtC,OAAO,QAAQ,QAAQ,cAAcA,OAAI,CAAC;CAC5C;CAEA,gBAAgB,MAAoD;EAClE,MAAM,sBAAsB,wDAAwD,KAAK,YAAY;EACrG,IAAI,OAAO,KAAK,0BAA0B,YACxC,OAAO,KAAK,sBAAsB;GAAE;GAAqB,gBAAgB,MAAM;EAAe,CAAC;EAEjG,IAAI,OAAO,KAAK,0BAA0B,UAAU,OAAO,KAAK;EAChE,OAAO;CACT;CAEA,UAA8F;EAC5F,OAAO;GACL,IAAI,KAAK;GACT,MAAM,KAAK;GACX,UAAU,KAAK;GACf,QAAQ,KAAK;GACb,UAAU,KAAK;GACf,MAAM,KAAK;GACX,UAAU;IACR,YAAY,KAAK;IACjB,GAAI,KAAK,eAAe,EAAE,aAAa,KAAK,YAAY;IACxD,GAAI,KAAK,eAAe,EAAE,aAAa,KAAK,YAAY;GAC1D;EACF;CACF;AACF;AAEA,SAAS,iBAAiB,MAAc,WAAuC;CAE7E,QADmB,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS,EAAA,CAClD,MAAK,QAAO,KAAK,SAAS,GAAG,CAAC;AAClD;;;;;;;;;;;;;;;;;AC3TA,MAAM,eAAe;;AAErB,MAAM,eAAe;;;;;;;;AAQrB,MAAM,wBAAwB;AA6E9B,MAAM,sBAAkD,UAAU,iBAAiB;CACjF,MAAM,KAAM,WAAuC;CAGnD,IAAI,CAAC,IACH,MAAM,IAAI,MACR,uIACF;CAEF,OAAO,IAAI,GAAG,UAAU,YAAY;AACtC;;;;;;;;;AAUA,SAAgB,aAAa,OAAkB,SAAuD;CACpG,MAAM,UAAU,QAAQ,oBAAoB;CAC5C,MAAM,gBAAgB,IAAI,YAAY;CACtC,MAAM,gBAAgB,IAAI,YAAY;CAEtC,OAAO,IAAI,SAA0B,YAAW;EAC9C,IAAI,SAAS;EACb,IAAI,SAAS;EACb,IAAI,WAA0B;EAC9B,IAAI,WAAW;EACf,IAAI,UAAU;EACd,IAAI,SAAS;EACb,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EAEJ,MAAM,eAAe;GACnB,IAAI,SAAS;GACb,UAAU;GACV,IAAI,OAAO,aAAa,KAAK;GAC7B,IAAI,gBAAgB,aAAa,cAAc;GAI/C,MAAM,aAAa,cAAc,OAAO;GACxC,IAAI,YAAY;IACd,UAAU;IACV,QAAQ,WAAW,UAAU;GAC/B;GACA,MAAM,aAAa,cAAc,OAAO;GACxC,IAAI,YAAY;IACd,UAAU;IACV,QAAQ,WAAW,UAAU;GAC/B;GACA,IAAI;IACF,OAAO,MAAM,KAAM,EAAE;GACvB,QAAQ,CAER;GACA,QAAQ;IACN;IACA;IACA;IACA,WAAW;IACX;IACA,GAAI,cAAc,KAAA,KAAa,EAAE,UAAU;IAC3C,GAAI,gBAAgB,KAAA,KAAa,EAAE,YAAY;IAC/C;GACF,CAAC;EACH;EAMA,IAAI,QAAQ,cAAc,KAAA,KAAa,QAAQ,YAAY,GACzD,QAAQ,iBAAiB;GACvB,WAAW;GAGX,IAAI,aAAa,MAAM,WAAW;GAClC,OAAO;EACT,GAAG,QAAQ,SAAS;OAEpB,iBAAiB,iBAAiB;GAIhC,IAAI,CAAC,QAAQ,OAAO;EACtB,GAAG,qBAAqB;EAG1B,MAAM,SAAS,QAAQ,MAAM,YAAY,CAAC,MAAM,aAAa,MAAM,GAAG,CAAC;EACvE,OAAO,aAAa;EAEpB,OAAO,eAAe;GACpB,SAAS;GACT,IAAI,gBAAgB;IAClB,aAAa,cAAc;IAC3B,iBAAiB,KAAA;GACnB;GACA,MAAM,OAAgC,EAAE,SAAS,QAAQ,QAAQ;GACjE,IAAI,QAAQ,KAAK,KAAK,MAAM,QAAQ;GACpC,IAAI,QAAQ,OAAO,OAAO,KAAK,QAAQ,GAAG,CAAC,CAAC,SAAS,GAAG,KAAK,MAAM,QAAQ;GAC3E,OAAO,KAAK,KAAK,UAAU;IAAE,MAAM;IAAa;GAAK,CAAC,CAAC;GAGvD,OAAO,KAAK,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC,CAAC;EACrD;EAEA,OAAO,aAAY,UAAS;GAC1B,MAAM,EAAE,SAAS;GACjB,IAAI,gBAAgB,aAClB,kBAAkB,IAAI;QACjB,IAAI,OAAO,SAAS,UACzB,gBAAgB,IAAI;EAExB;EAEA,OAAO,WAAU,UAAS;GACxB,YAAY,MAAM;GAClB,cAAc,MAAM;GACpB,IAAI,CAAC,QAAQ;IAIX,OAAO;IACP;GACF;GAGA,OAAO;EACT;EAEA,OAAO,gBAAgB;GACrB,IAAI,SAAS;GACb,IAAI,CAAC,QACH,OAAO;EAIX;EAEA,SAAS,kBAAkB,QAAqB;GAC9C,MAAM,OAAO,IAAI,WAAW,MAAM;GAClC,IAAI,KAAK,UAAU,GAAG;GACtB,IAAI,KAAK,OAAO,cAAc;IAC5B,MAAM,QAAQ,cAAc,OAAO,KAAK,SAAS,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;IACrE,UAAU;IACV,QAAQ,WAAW,KAAK;GAC1B,OAAO,IAAI,KAAK,OAAO,cAAc;IACnC,MAAM,QAAQ,cAAc,OAAO,KAAK,SAAS,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;IACrE,UAAU;IACV,QAAQ,WAAW,KAAK;GAC1B;EACF;EAEA,SAAS,gBAAgB,MAAc;GACrC,IAAI;GACJ,IAAI;IACF,QAAQ,KAAK,MAAM,IAAI;GACzB,QAAQ;IACN;GACF;GACA,IAAI,MAAM,SAAS,QAAQ;IACzB,WAAW,MAAM,MAAM,aAAa;IACpC,OAAO;GACT;EAGF;CACF,CAAC;AACH;;;;;;;;AC9NA,MAAM,0BAA0B;;AAWhC,MAAM,sBAAsB;;AAE5B,MAAM,6BAA6B;;;;;;;;;;;AAYnC,IAAa,4BAAb,cAA+C,MAAM;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YACE,SACA,aASA;EACA,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,YAAY,YAAY;EAC7B,KAAK,UAAU,YAAY;EAC3B,KAAK,WAAW,YAAY;EAC5B,KAAK,SAAS,YAAY;EAC1B,KAAK,YAAY,YAAY;EAC7B,KAAK,cAAc,YAAY;EAC/B,KAAK,aAAa,YAAY;CAChC;AACF;;;;;;;;;;;;AAaA,IAAa,wBAAb,cAA2C,MAAM;CAC/C;CACA;CACA;CAEA,YAAY,SAAiB,aAAwE;EACnG,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,YAAY,YAAY;EAC7B,KAAK,UAAU,YAAY;EAC3B,KAAK,WAAW,YAAY;CAC9B;AACF;;;;;;;;;;;;;;;AAgBA,SAAS,aAAa,SAAiB,MAAyB;CAC9D,OAAO,MAAM,SAAS,GAAG,QAAQ,GAAG,KAAK,IAAI,UAAU,CAAC,CAAC,KAAK,GAAG,MAAM;AACzE;AAEA,SAAS,WAAW,KAAqB;CACvC,IAAI,yBAAyB,KAAK,GAAG,GAAG,OAAO;CAC/C,OAAO,IAAI,IAAI,QAAQ,MAAM,OAAO,EAAE;AACxC;AAEA,IAAM,wBAAN,cAAoCO,uBAAAA,cAAc;CAChD;CACA;CACA;CAEA,YAAY,KAAa,eAAuC,SAA+B;EAC7F,MAAM,OAAO;EACb,KAAK,MAAM;EACX,KAAK,gBAAgB,cAAc,MAAK,WAAU;GAChD,KAAK,gBAAgB,OAAO;GAC5B,IAAI,OAAO,QAAQ,KAAK,WAAW,OAAO,MAAM;GAChD,IAAI,OAAO,QAAQ,KAAK,WAAW,OAAO,MAAM;GAChD,OAAO;EACT,CAAC;CACH;CAEA,IAAI,WAA+B;EACjC,OAAO,KAAK;CACd;CAEA,MAAM,OAA+B;EACnC,OAAO,KAAK;CACd;CAEA,MAAM,OAAyB;EAK7B,MAAM,IAAI,MAAM,kFAAkF;CACpG;CAEA,MAAM,YAA2B;EAC/B,MAAM,IAAI,MAAM,2DAA2D;CAC7E;AACF;AAEA,IAAM,yBAAN,cAAqCC,uBAAAA,sBAAuC;CAC1E,eAAuB;;;;;;;;CASvB,MAAM,MAAM,SAAiB,UAA+B,CAAC,GAA2B;EAGtF,MAAM,SAAS,IAAI,sBAAsB,iBAFZ,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,IAAI,KAAK,eAAA,CAAgB,SAAS,EAAE,KACnE,KAAK,QAAQ,eAAe,SAAS,KAAA,GAAW,OACxB,GAAe,OAAO;EACpE,KAAK,SAAS,IAAI,OAAO,KAAK,MAAM;EACpC,OAAO;CACT;CAEA,MAAM,OAA+B;EACnC,OAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,CAAC,CAAC,KAAI,YAAW;GACvD,KAAK,OAAO;GACZ,SAAS,OAAO;GAChB,SAAS,OAAO,aAAa,KAAA;GAC7B,GAAI,OAAO,aAAa,KAAA,KAAa,EAAE,UAAU,OAAO,SAAS;EACnE,EAAE;CACJ;AACF;AAEA,IAAa,kBAAb,MAAa,wBAAwBC,uBAAAA,cAAc;CACjD;CACA,OAAgB;CAChB,WAAoB;CACpB,SAAyB;CAGzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,aAAkC;CAClC;;;;;;;CAOA,SAAsE;;;;;;;CAOtE,iBAAqF;CAErF,YAAY,UAAkC,CAAC,GAAG;EAChD,MAAM;GAAE,GAAG;GAAS,MAAM;GAAmB,WAAW,IAAI,uBAAuB;EAAE,CAAC;EACtF,KAAK,KAAK,QAAQ,MAAM,KAAK,WAAW;EACxC,KAAK,UAAU,IAAI,eAAe,OAAO;EACzC,KAAK,iBAAiB,QAAQ,iBAAiB,QAAQ,IAAI,yBAAyB;EACpF,IAAI,CAAC,KAAK,kBAAkB,CAAC,QAAQ,WAAW,MAAM,IAAI,MAAM,2BAA2B;EAC3F,KAAK,aAAa,QAAQ;EAC1B,KAAK,sBAAsB,QAAQ;EACnC,KAAK,oBAAoB,QAAQ;EACjC,KAAK,OAAO,QAAQ,OAAO,CAAC;EAC5B,KAAK,WAAW,QAAQ;EACxB,KAAK,wBAAwB,QAAQ;EACrC,KAAK,oBAAoB,QAAQ;CACnC;CAEA,aAA6B;EAC3B,OAAO,oBAAoB,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;CAC7F;;;;;;;;;;;;;CAcA,MAAM,UAA+B,CAAC,GAAoB;EAOxD,MAAM,KAAK,QAAQ,MAAM,QAAQ;EACjC,OAAO,IAAI,gBAAgB;GACzB,GAAI,OAAO,KAAA,KAAa,EAAE,GAAG;GAC7B,aAAa,KAAK,QAAQ;GAC1B,WAAW,KAAK,QAAQ;GACxB,OAAO,KAAK,QAAQ;GACpB,eAAe,KAAK;GACpB,GAAI,QAAQ,cAAc,KAAA,KAAa,EAAE,WAAW,QAAQ,UAAU;GACtE,oBAAoB,QAAQ,sBAAsB,KAAK;GACvD,GAAI,KAAK,sBAAsB,KAAA,KAAa,EAAE,kBAAkB,KAAK,kBAAkB;GACvF,KAAK,QAAQ,OAAO,KAAK;GACzB,GAAI,KAAK,aAAa,KAAA,KAAa,EAAE,SAAS,KAAK,SAAS;GAC5D,GAAI,KAAK,0BAA0B,KAAA,KAAa,EAAE,cAAc,KAAK,sBAAsB;GAC3F,GAAI,KAAK,sBAAsB,KAAA,KAAa,EAAE,kBAAkB,KAAK,kBAAkB;EACzF,CAAC;CACH;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAK,YACP,IAAI;GAEF,MAAM,OAAQ,OAAM,MADG,KAAK,QAAQ,QAAQ,YAAY,mBAAmB,KAAK,UAAU,GAAG,EAAA,CAChE,KAAK;GAIlC,IAAI,CAAC,KAAK,aAAa;IACrB,KAAK,aAAa,KAAK,YAAY,IAAI,KAAK,KAAK,SAAS,oBAAI,IAAI,KAAK;IACvE;GACF;GACA,KAAK,aAAa,KAAA;EACpB,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,qBAAqB,MAAM,WAAW,KAAK,MAAM;GACxE,KAAK,aAAa,KAAA;EACpB;EAGF,IAAI,CAAC,KAAK,gBAAgB,MAAM,IAAI,MAAM,2BAA2B;EAErE,MAAM,OAAO,KAAK,UAAU;GAK1B,IAAI,KAAK;GACT,eAAe,KAAK;GACpB,oBAAoB,KAAK;GACzB,kBAAkB,KAAK;GACvB,KAAK,KAAK;EACZ,CAAC;EAMD,IAAI;EACJ,KAAK,IAAI,UAAU,IAAK,WACtB,IAAI;GACF,WAAW,MAAM,KAAK,QAAQ,QAAQ,YAAY;IAChD,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C;GACF,CAAC;GACD;EACF,SAAS,OAAO;GAEd,IAAI,EADc,iBAAiB,oBAAoB,MAAM,UAAU,QACrD,WAAW,qBAAqB,MAAM;GACxD,MAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,6BAA6B,OAAO,CAAC;EACxF;EAEF,MAAM,OAAQ,MAAM,SAAS,KAAK;EAClC,KAAK,aAAa,KAAK;EACvB,KAAK,aAAa,KAAK,YAAY,IAAI,KAAK,KAAK,SAAS,oBAAI,IAAI,KAAK;CACzE;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,QAAQ;CACrB;CAEA,MAAM,UAAyB;EAC7B,IAAI,CAAC,KAAK,YAAY;EACtB,MAAM,KAAK,QAAQ,QAAQ,YAAY,mBAAmB,KAAK,UAAU,KAAK,EAAE,QAAQ,SAAS,CAAC;EAGlG,KAAK,aAAa,KAAA;EAClB,KAAK,aAAa;EAGlB,KAAK,SAAS;CAChB;;;;;;;;;;;;;;;;;;CAmBA,MAAM,eAAe,SAAiB,MAAiB,SAAyD;EAC9G,MAAM,KAAK,cAAc;EACzB,IAAI,CAAC,KAAK,YAAY,MAAM,IAAIC,uBAAAA,qBAAqB,KAAK,EAAE;EAE5D,MAAM,UAAU,KAAK,IAAI;EACzB,MAAM,cAAc,aAAa,SAAS,IAAI;EAI9C,MAAM,mBAAmB,SAAS,WAAW,KAAK;EAUlD,MAAM,SAAS,MAAM,KAAK,eAAe,aAAa,kBAAkB,OAAO;EAM/E,MAAM,WAAW,OAAO,YAAY;EACpC,OAAO;GACL,SAAS,aAAa;GACtB;GACA,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,UAAU,OAAO;GACjB,SAAS;GACT,iBAAiB,KAAK,IAAI,IAAI;EAChC;CACF;;;;;;;;;;;;;;;;;;;;;;CAuBA,MAAc,eACZ,aACA,kBACA,SACyF;EAIzF,MAAM,cAAc,SAAS,MACzB,OAAO,YACL,OAAO,QAAQ,QAAQ,GAAG,CAAC,CAAC,QAAQ,UAAqC,MAAM,OAAO,KAAA,CAAS,CACjG,IACA,KAAA;EAEJ,IAAI;EACJ,IAAI;EACJ,IAAI,eAAe;EAOnB,KAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WAAW;GAC5C,IAAI,UAAU,KAAK,aAAa,KAAK,WAAW,WAAW,KAAK,SAAS;GACzE,IAAI;GACJ,IAAI;IACF,QAAQ,MAAM,KAAK,aAAa;GAClC,SAAS,OAAO;IAMd,IAAI,iBAAiB,oBAAoB,MAAM,WAAW,KAAK;KAC7D,KAAK,SAAS;KACd,MAAM,iBAAiB,KAAK;KAC5B,KAAK,aAAa,KAAA;KAClB,MAAM,IAAI,sBACR,WAAW,kBAAkB,YAAY,2CACzC;MACE,GAAI,kBAAkB,EAAE,WAAW,eAAe;MAClD,SAAS;MACT,UAAU,UAAU;KACtB,CACF;IACF;IACA,MAAM;GACR;GACA,YAAY;GACZ,eAAe,UAAU;GACzB,MAAM,SAAS,MAAM,aAAa,OAAO;IACvC,SAAS;IACT,GAAI,SAAS,QAAQ,KAAA,KAAa,EAAE,KAAK,QAAQ,IAAI;IACrD,GAAI,gBAAgB,KAAA,KAAa,EAAE,KAAK,YAAY;IACpD,GAAI,oBAAoB,QAAQ,mBAAmB,KAAK,EAAE,WAAW,iBAAiB;IACtF,GAAI,KAAK,qBAAqB,EAAE,kBAAkB,KAAK,kBAAkB;GAC3E,CAAC;GACD,aAAa;GAKb,IAAI,OAAO,aAAa,QAAQ,OAAO,UAAU,OAAO;EAC1D;EAMA,MAAM,SAAS;EACf,MAAM,QAAQ;EAMd,IAAI,KAAK,WAAW,OAAO,KAAK,SAAS;EACzC,MAAM,IAAI,0BACR,4CAA4C,KAAK,cAAc,YAAY,SAAS,aAAa,gBAC9F,OAAO,cAAc,KAAA,IAClB,WAAW,OAAO,YAAY,OAAO,cAAc,IAAI,OAAO,gBAAgB,GAAG,KACjF,KACN;GACE,GAAI,KAAK,cAAc,EAAE,WAAW,KAAK,WAAW;GACpD,SAAS;GACT,UAAU;GACV,QAAQ,OAAO,UAAU;GACzB,GAAI,OAAO,cAAc,KAAA,KAAa,EAAE,WAAW,OAAO,UAAU;GACpE,GAAI,OAAO,gBAAgB,KAAA,KAAa,EAAE,aAAa,OAAO,YAAY;GAC1E,YAAY,MAAM;EACpB,CACF;CACF;;;;;;;;CASA,MAAc,eAAoE;EAChF,MAAM,MAAM,KAAK,IAAI;EAKrB,IAAI,KAAK,UAAU,KAAK,OAAO,gBAAgB,QAAQ,KAAK,OAAO,cAAc,0BAA0B,KACzG,OAAO,KAAK;EAGd,IAAI,KAAK,gBAAgB,OAAO,KAAK;EACrC,IAAI,CAAC,KAAK,YAAY,MAAM,IAAIA,uBAAAA,qBAAqB,KAAK,EAAE;EAC5D,MAAM,YAAY,KAAK;EACvB,MAAM,YAAY,YAAY;GAI5B,MAAM,OAAQ,OAAM,MAHG,KAAK,QAAQ,QAAQ,YAAY,mBAAmB,SAAS,EAAE,cAAc,EAClG,QAAQ,OACV,CAAC,EAAA,CAC4B,KAAK;GAClC,MAAM,cAAc,KAAK,YAAY,KAAK,MAAM,KAAK,SAAS,IAAI;GAClE,MAAM,QAAQ;IACZ,KAAK,KAAK;IACV,YAAY,KAAK;IACjB,aAAa,KAAK;IAClB,WAAW,KAAK;IAIhB,aAAa,gBAAgB,QAAQ,CAAC,OAAO,MAAM,WAAW,IAAI,cAAc;GAClF;GACA,KAAK,SAAS;GACd,OAAO;EACT,EAAA,CAAG;EACH,KAAK,iBAAiB;EACtB,IAAI;GACF,OAAO,MAAM;EACf,UAAU;GAGR,IAAI,KAAK,mBAAmB,UAAU,KAAK,iBAAiB;EAC9D;CACF;CAEA,MAAM,UAAgC;EACpC,IAAI,CAAC,KAAK,YACR,OAAO;GACL,IAAI,KAAK;GACT,MAAM,KAAK;GACX,UAAU,KAAK;GACf,QAAQ,KAAK;GACb,WAAW,KAAK,8BAAc,IAAI,KAAK;EACzC;EAGF,MAAM,OAAQ,OAAM,MADG,KAAK,QAAQ,QAAQ,YAAY,mBAAmB,KAAK,UAAU,GAAG,EAAA,CAChE,KAAK;EAClC,OAAO;GACL,IAAI,KAAK;GACT,MAAM,KAAK;GACX,UAAU,KAAK;GACf,QAAQ,KAAK;GACb,WAAW,KAAK,YAAY,IAAI,KAAK,KAAK,SAAS,IAAK,KAAK,8BAAc,IAAI,KAAK;GACpF,UAAU;IAMR,WAAW,KAAK;IAChB,oBAAoB,KAAK,sBAAsB,KAAA;IAC/C,gBAAgB,KAAK;GACvB;EACF;CACF;CAEA,gBAAgB,MAAoD;EAClE,MAAM,sBAAsB,mBAAmB,KAAK,aAAa,IAAI,KAAK,eAAe,GAAG;EAC5F,IAAI,OAAO,KAAK,0BAA0B,YACxC,OAAO,KAAK,sBAAsB;GAAE;GAAqB,gBAAgB,MAAM;EAAe,CAAC;EAEjG,IAAI,OAAO,KAAK,0BAA0B,UAAU,OAAO,KAAK;EAChE,OAAO;CACT;AACF;;;AC5nBA,MAAa,0BAAmE;CAC9E,IAAI;CACJ,MAAM;CACN,aAAa;CACb,cAAc;EACZ,MAAM;EACN,YAAY;GACV,aAAa;IACX,MAAM;IACN,aAAa;GACf;GACA,WAAW;IAAE,MAAM;IAAU,aAAa;GAAwD;GAClG,eAAe;IAAE,MAAM;IAAU,aAAa;GAAgE;GAC9G,WAAW;IAAE,MAAM;IAAU,aAAa;GAAiD;GAC3F,oBAAoB;IAAE,MAAM;IAAU,aAAa;GAAyD;GAC5G,kBAAkB;IAChB,MAAM;IACN,aAAa;IACb,MAAM,CAAC,YAAY,SAAS;IAC5B,SAAS;GACX;GACA,KAAK;IAAE,MAAM;IAAU,aAAa;IAAyB,sBAAsB,EAAE,MAAM,SAAS;GAAE;GACtG,SAAS;IAAE,MAAM;IAAU,aAAa;GAAgC;EAC1E;CACF;CACA,gBAAe,WAAU,IAAI,gBAAgB,MAAM;AACrD;AAEA,MAAa,6BAA4E;CACvF,IAAI;CACJ,MAAM;CACN,aAAa;CACb,cAAc;EACZ,MAAM;EACN,YAAY;GACV,aAAa;IACX,MAAM;IACN,aAAa;GACf;GACA,WAAW;IAAE,MAAM;IAAU,aAAa;GAAwD;GAClG,YAAY;IACV,MAAM;IACN,aAAa;GACf;GACA,UAAU;IAAE,MAAM;IAAW,aAAa;IAAsB,SAAS;GAAM;EACjF;CACF;CACA,mBAAkB,WAAU,IAAI,mBAAmB,MAAM;AAC3D"}
1
+ {"version":3,"file":"index.cjs","names":["nodePath","path","Buffer","MastraFilesystem","FileNotFoundError","buffer","WorkspaceReadOnlyError","FileExistsError","ProcessHandle","SandboxProcessManager","MastraSandbox","SandboxNotReadyError"],"sources":["../src/client.ts","../src/filesystem.ts","../src/direct-exec.ts","../src/sandbox.ts","../src/provider.ts"],"sourcesContent":["export interface PlatformClientOptions {\n accessToken?: string;\n projectId?: string;\n fetch?: typeof fetch;\n}\n\nexport interface PlatformRequestOptions extends RequestInit {\n query?: Record<string, string | number | boolean | undefined>;\n}\n\nconst DEFAULT_PROXY_URL = 'https://workspaces.mastra.ai';\n\n/**\n * Default per-request timeout for calls to the workspace proxy. Applied only\n * when the caller doesn't already pass an `AbortSignal`. Long-running routes\n * (e.g. `POST /sandbox/:id/exec`) pass their own longer signal.\n */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 60_000;\n\nexport function requireOption(value: string | undefined, name: string): string {\n if (!value) throw new Error(`${name} is required`);\n return value;\n}\n\nexport function resolvePlatformOptions(options: PlatformClientOptions) {\n return {\n accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, 'accessToken'),\n projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, 'projectId'),\n proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\\/$/, ''),\n fetch: options.fetch ?? fetch,\n };\n}\n\n/**\n * Structured error shape returned by the workspace proxy. All routes emit\n * `{ error: { message, type } }` on failure — see servers/workspace-proxy in\n * the Platform repo. Kept as a wire-level type so callers can switch on\n * `error.code` without re-parsing `error.body`.\n */\nexport interface PlatformProxyError {\n message: string;\n /** Machine-readable error kind, e.g. `not_found`, `invalid_request`, `authentication_error`. */\n type: string;\n}\n\nfunction parseProxyError(body: string): PlatformProxyError | undefined {\n if (!body) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const err = (parsed as { error?: unknown }).error;\n if (typeof err !== 'object' || err === null) return undefined;\n const { message, type } = err as { message?: unknown; type?: unknown };\n if (typeof message !== 'string' || typeof type !== 'string') return undefined;\n return { message, type };\n}\n\nexport class PlatformApiError extends Error {\n readonly status: number;\n readonly body: string;\n /** Machine-readable proxy error kind (e.g. `not_found`), when the response body matches `{ error: { message, type } }`. */\n readonly code: string | undefined;\n /** Human-readable proxy error message, when the response body matches `{ error: { message, type } }`. */\n readonly proxyMessage: string | undefined;\n\n constructor(status: number, body: string) {\n const parsed = parseProxyError(body);\n const summary = parsed ? `${parsed.type}: ${parsed.message}` : body;\n super(`Platform proxy request failed with ${status}${summary ? `: ${summary}` : ''}`);\n this.name = 'PlatformApiError';\n this.status = status;\n this.body = body;\n this.code = parsed?.type;\n this.proxyMessage = parsed?.message;\n }\n}\n\nexport class PlatformClient {\n readonly accessToken: string;\n readonly projectId: string;\n readonly proxyUrl: string;\n readonly fetch: typeof fetch;\n\n constructor(options: PlatformClientOptions) {\n const resolved = resolvePlatformOptions(options);\n this.accessToken = resolved.accessToken;\n this.projectId = resolved.projectId;\n this.proxyUrl = resolved.proxyUrl;\n this.fetch = resolved.fetch;\n }\n\n async request(path: string, options: PlatformRequestOptions = {}): Promise<Response> {\n const url = new URL(`${this.proxyUrl}/v1/projects/${encodeURIComponent(this.projectId)}${path}`);\n for (const [key, value] of Object.entries(options.query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n\n const headers = new Headers(options.headers);\n headers.set('authorization', `Bearer ${this.accessToken}`);\n\n // Strip our helper-only field so the underlying fetch sees a valid RequestInit.\n const { query: _query, ...fetchOptions } = options;\n // Apply a default timeout only when the caller didn't already supply an\n // AbortSignal — long-running routes (exec) provide their own longer signal.\n const signal = fetchOptions.signal ?? AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS);\n const response = await this.fetch(url, { ...fetchOptions, headers, signal });\n if (!response.ok) {\n throw new PlatformApiError(response.status, await response.text());\n }\n return response;\n }\n}\n","import { Buffer } from 'node:buffer';\nimport nodePath from 'node:path';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type {\n CopyOptions,\n FileContent,\n FileEntry,\n FileStat,\n FilesystemIcon,\n FilesystemInfo,\n InstructionsOption,\n ListOptions,\n MastraFilesystemOptions,\n ProviderStatus,\n ReadOptions,\n RemoveOptions,\n WriteOptions,\n} from '@mastra/core/workspace';\nimport { FileExistsError, FileNotFoundError, MastraFilesystem, WorkspaceReadOnlyError } from '@mastra/core/workspace';\nimport type { PlatformClientOptions } from './client.js';\nimport { PlatformClient } from './client.js';\n\ninterface ProxyListResponse {\n contents?: Array<{ key?: string; size?: number; lastModified?: string }>;\n commonPrefixes?: string[];\n}\n\nexport interface PlatformFilesystemOptions extends PlatformClientOptions, MastraFilesystemOptions {\n id?: string;\n bucketName?: string;\n readOnly?: boolean;\n displayName?: string;\n icon?: FilesystemIcon;\n description?: string;\n instructions?: InstructionsOption;\n}\n\nfunction normalizePath(input: string): string {\n if (!input || input === '.') return '/';\n let normalized = input.startsWith('/') ? input : `/${input}`;\n normalized = nodePath.posix.normalize(normalized);\n return normalized === '.' ? '/' : normalized;\n}\n\nfunction keyFromPath(path: string): string {\n const normalized = normalizePath(path);\n return normalized === '/' ? '' : normalized.slice(1);\n}\n\n/**\n * Encode each `/`-delimited segment of an object key with `encodeURIComponent`\n * so reserved URL characters (`?`, `#`, `%`, `&`, `+`, spaces, etc.) are\n * treated as part of the key instead of URL syntax. Kept segment-aware so\n * `/` continues to act as a path separator on the wire.\n */\nfunction encodeKeyPath(key: string): string {\n return key.split('/').map(encodeURIComponent).join('/');\n}\n\nfunction nameFromPath(path: string): string {\n const normalized = normalizePath(path);\n if (normalized === '/') return '';\n return normalized.slice(normalized.lastIndexOf('/') + 1);\n}\n\nfunction contentToBody(content: FileContent): string | Buffer {\n if (typeof content === 'string') return content;\n return Buffer.from(content);\n}\n\nfunction headerDate(headers: Headers, name: string): Date {\n const value = headers.get(name);\n return value ? new Date(value) : new Date(0);\n}\n\nfunction headerSize(headers: Headers): number {\n const value = headers.get('content-length');\n return value ? Number(value) : 0;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'status' in error && error.status === 404;\n}\n\nexport class PlatformFilesystem extends MastraFilesystem {\n readonly id: string;\n readonly name = 'PlatformFilesystem';\n readonly provider = 'platform';\n readonly readOnly?: boolean;\n readonly displayName?: string;\n readonly icon: FilesystemIcon;\n readonly description?: string;\n status: ProviderStatus = 'pending';\n\n private readonly _client: PlatformClient;\n private readonly _bucketName: string;\n private readonly _instructionsOverride?: InstructionsOption;\n\n constructor(options: PlatformFilesystemOptions = {}) {\n super({ ...options, name: 'PlatformFilesystem' });\n this.id = options.id ?? this.generateId();\n this._bucketName = options.bucketName ?? process.env.MASTRA_PLATFORM_BUCKET_NAME ?? '';\n if (!this._bucketName) throw new Error('bucketName is required');\n this.readOnly = options.readOnly;\n this.displayName = options.displayName;\n this.icon = options.icon ?? 'cloud';\n this.description = options.description;\n this._instructionsOverride = options.instructions;\n this._client = new PlatformClient(options);\n }\n\n private generateId(): string {\n return `platform-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n await this.ensureReady();\n let response: Response;\n try {\n response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,\n );\n } catch (error) {\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n return options?.encoding ? buffer.toString(options.encoding) : buffer;\n }\n\n async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('writeFile');\n const headers: Record<string, string> = {};\n if (options?.mimeType) headers['content-type'] = options.mimeType;\n if (options?.overwrite === false) headers['if-none-match'] = '*';\n try {\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'PUT',\n headers,\n body: contentToBody(content),\n });\n } catch (error) {\n if (typeof error === 'object' && error !== null && 'status' in error && error.status === 412) {\n throw new FileExistsError(path);\n }\n throw error;\n }\n }\n\n /**\n * Append bytes to a file.\n *\n * **Not atomic.** Object storage behind the workspace proxy has no native\n * append or compare-and-swap primitive, so this implementation is a\n * read-modify-write: it reads the current contents, concatenates the new\n * bytes, and PUTs the whole object back. Concurrent `appendFile` calls to\n * the same path can overwrite each other's writes (\"last write wins\").\n * Use `writeFile` with distinct keys for concurrent writers.\n */\n async appendFile(path: string, content: FileContent): Promise<void> {\n const existing = (await this.exists(path)) ? await this.readFile(path) : Buffer.alloc(0);\n await this.writeFile(\n path,\n Buffer.concat([Buffer.isBuffer(existing) ? existing : Buffer.from(existing), Buffer.from(content)]),\n );\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('deleteFile');\n try {\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'DELETE',\n query: { recursive: options?.recursive },\n });\n } catch (error) {\n if (isNotFound(error) && options?.force) return;\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n }\n\n async copyFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('copyFile');\n // The workspace proxy's `?op=copy` route always overwrites the destination;\n // there's no conditional wire field to prevent it. Reject the option\n // explicitly instead of silently overwriting when the caller asked us not to.\n if (options?.overwrite === false) {\n throw new Error('PlatformFilesystem.copyFile does not support overwrite: false — the proxy always overwrites.');\n }\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {\n method: 'POST',\n query: { op: 'copy' },\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ destination: keyFromPath(dest) }),\n });\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('moveFile');\n // Same rationale as copyFile: `?op=rename` always overwrites.\n if (options?.overwrite === false) {\n throw new Error('PlatformFilesystem.moveFile does not support overwrite: false — the proxy always overwrites.');\n }\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {\n method: 'POST',\n query: { op: 'rename' },\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ destination: keyFromPath(dest) }),\n });\n }\n\n async mkdir(path: string, _options?: { recursive?: boolean }): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('mkdir');\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'POST',\n query: { op: 'mkdir' },\n });\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n await this.deleteFile(path.endsWith('/') ? path : `${path}/`, { recursive: true, force: options?.force });\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n await this.ensureReady();\n const prefix = keyFromPath(path);\n const response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(prefix)}`,\n {\n query: {\n delimiter: options?.recursive ? undefined : '/',\n prefix: prefix ? `${prefix.replace(/\\/$/, '')}/` : undefined,\n },\n },\n );\n const json = (await response.json()) as ProxyListResponse;\n return [\n ...(json.commonPrefixes ?? []).map(prefix => ({\n name: nameFromPath(prefix.replace(/\\/$/, '')),\n type: 'directory' as const,\n })),\n ...(json.contents ?? [])\n .filter(object => object.key && !object.key.endsWith('/'))\n .map(object => ({\n name: nameFromPath(object.key!),\n type: 'file' as const,\n size: object.size,\n })),\n ].filter(\n entry => !options?.extension || entry.type === 'directory' || matchesExtension(entry.name, options.extension),\n );\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await this.stat(path);\n return true;\n } catch (error) {\n if (isNotFound(error) || error instanceof FileNotFoundError) return false;\n throw error;\n }\n }\n\n async stat(path: string): Promise<FileStat> {\n await this.ensureReady();\n const normalized = normalizePath(path);\n if (normalized === '/') {\n return { name: '', path: '/', type: 'directory', size: 0, createdAt: new Date(0), modifiedAt: new Date(0) };\n }\n let response: Response;\n try {\n response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,\n {\n method: 'HEAD',\n },\n );\n } catch (error) {\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n return {\n name: nameFromPath(path),\n path: normalized,\n type: normalized.endsWith('/') ? 'directory' : 'file',\n size: headerSize(response.headers),\n createdAt: headerDate(response.headers, 'last-modified'),\n modifiedAt: headerDate(response.headers, 'last-modified'),\n mimeType: response.headers.get('content-type') ?? undefined,\n };\n }\n\n realpath(path: string): Promise<string> {\n return Promise.resolve(normalizePath(path));\n }\n\n getInstructions(opts?: { requestContext?: RequestContext }): string {\n const defaultInstructions = `Platform filesystem backed by Mastra Platform bucket ${this._bucketName}. Use absolute workspace paths.`;\n if (typeof this._instructionsOverride === 'function') {\n return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });\n }\n if (typeof this._instructionsOverride === 'string') return this._instructionsOverride;\n return defaultInstructions;\n }\n\n getInfo(): FilesystemInfo<{ bucketName: string; displayName?: string; description?: string }> {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n readOnly: this.readOnly,\n icon: this.icon,\n metadata: {\n bucketName: this._bucketName,\n ...(this.displayName && { displayName: this.displayName }),\n ...(this.description && { description: this.description }),\n },\n };\n }\n}\n\nfunction matchesExtension(name: string, extension: string | string[]): boolean {\n const extensions = Array.isArray(extension) ? extension : [extension];\n return extensions.some(ext => name.endsWith(ext));\n}\n","/**\n * Direct exec client — opens Railway's tcp-proxy exec WebSocket directly using\n * a short-lived JWT minted by the workspace proxy's exec-lease endpoint. This\n * removes the platform data plane from the exec stdout/stderr path entirely\n * (see `docs/factory/direct-sandbox-connection.md` in the Platform repo),\n * cutting payload-scaled Cloud Run egress and RTT for commands like\n * `pnpm install` that stream tens of MB of output.\n *\n * The frame protocol below mirrors `connectExecWs()` in `railway@3.5.5`\n * (`workspaces/railway/node_modules/railway/dist/index.js`). The `railway`\n * SDK's version is pinned on both sides (platform + here); a version bump\n * signals the protocol may have drifted and this module must be revisited.\n */\n\n/** Byte-0 tag on binary WS frames for stdout output. */\nconst STDOUT_FRAME = 1;\n/** Byte-0 tag on binary WS frames for stderr output. */\nconst STDERR_FRAME = 3;\n/**\n * Upper bound on how long we'll wait for the WebSocket to open when the\n * caller didn't supply a `timeoutMs`. Guards against a stalled TLS/WS\n * handshake leaving the promise unresolved forever. Not applied once the\n * socket has opened — a caller with no timeout has opted in to unbounded\n * command runtime, just not to unbounded connection setup.\n */\nconst HANDSHAKE_DEADLINE_MS = 30_000;\n\n/**\n * Minimal WebSocket surface this module depends on. Matches both the browser\n * `WebSocket` global and Node 22+'s built-in `WebSocket`. Extracted so tests\n * can inject a fake without pulling in `ws` or jsdom.\n */\nexport interface DirectExecWebSocket {\n binaryType: 'blob' | 'arraybuffer';\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: { code: number; reason: string }) => void) | null;\n onerror: ((event: unknown) => void) | null;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n}\n\n/**\n * Factory that opens a WebSocket to `endpoint` with the given subprotocols.\n * Defaults to the global `WebSocket` when omitted, which works on Node 22+\n * (the package's minimum) and in the browser. Tests inject a fake here.\n */\nexport type DirectExecWebSocketFactory = (endpoint: string, subprotocols: string[]) => DirectExecWebSocket;\n\n/** Lease payload returned by `POST /v1/projects/:projectId/sandbox/:sandboxId/exec-lease`. */\nexport interface ExecLease {\n jwt: string;\n wsEndpoint: string;\n subprotocol: string;\n /** ISO-8601 UTC. Null when the provider issues a JWT without an `exp` claim. */\n expiresAt: string | null;\n}\n\n/** Inputs to a direct exec invocation. Mirrors the shape of the `/exec` route body. */\nexport interface DirectExecOptions {\n command: string;\n cwd?: string;\n env?: Record<string, string>;\n /**\n * Wall-clock cap for the exec. When elapsed, we close the socket and\n * return `{timedOut: true, exitCode: 124}` matching the semantics of the\n * proxy's `/exec` route.\n */\n timeoutMs?: number;\n onStdout?: (chunk: string) => void;\n onStderr?: (chunk: string) => void;\n /** Injected for tests. Defaults to `globalThis.WebSocket`. */\n webSocketFactory?: DirectExecWebSocketFactory;\n}\n\n/**\n * Result of a direct exec. Shape matches the workspace-proxy `/exec` response\n * so the caller (`PlatformSandbox.executeCommand`) can hand it back with no\n * translation.\n *\n * `exitCode` is `null` when the socket closed without an `exit` frame AND the\n * exec did not time out (rare — usually a mid-stream network drop). Callers\n * currently coerce `null` to `1` upstream; kept nullable here to preserve the\n * distinction for future observability.\n */\nexport interface DirectExecResult {\n exitCode: number | null;\n stdout: string;\n stderr: string;\n truncated: boolean;\n timedOut: boolean;\n /**\n * WebSocket close metadata. Populated on any close (normal or transport\n * failure). `opened` distinguishes handshake failures (never opened) from\n * mid-stream drops. Callers use this for diagnostic logging; not part of\n * the CommandResult contract.\n */\n closeCode?: number;\n closeReason?: string;\n opened?: boolean;\n}\n\nconst DEFAULT_WS_FACTORY: DirectExecWebSocketFactory = (endpoint, subprotocols) => {\n const WS = (globalThis as { WebSocket?: unknown }).WebSocket as\n | (new (url: string, protocols: string[]) => DirectExecWebSocket)\n | undefined;\n if (!WS) {\n throw new Error(\n 'Direct exec requires a WebSocket implementation. Node 22+ provides one globally; on older runtimes, pass webSocketFactory explicitly.',\n );\n }\n return new WS(endpoint, subprotocols);\n};\n\n/**\n * Open the provider exec WebSocket using `lease`, run `command`, and resolve\n * with the accumulated stdout/stderr + exit code. See the module docstring\n * for the wire protocol reference.\n *\n * The client sends `stdin_close` immediately after `init_exec`, matching the\n * SDK's own one-shot exec behavior — we never stream stdin from the caller.\n */\nexport function execViaLease(lease: ExecLease, options: DirectExecOptions): Promise<DirectExecResult> {\n const factory = options.webSocketFactory ?? DEFAULT_WS_FACTORY;\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n\n return new Promise<DirectExecResult>(resolve => {\n let stdout = '';\n let stderr = '';\n let exitCode: number | null = null;\n let timedOut = false;\n let settled = false;\n let opened = false;\n let closeCode: number | undefined;\n let closeReason: string | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let handshakeTimer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = () => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n if (handshakeTimer) clearTimeout(handshakeTimer);\n // Flush any bytes still buffered in the decoders. A stream:true decode\n // holds trailing partial multi-byte sequences until the next chunk, so\n // without a flush the final char(s) of a UTF-8 stream can be dropped.\n const stdoutTail = stdoutDecoder.decode();\n if (stdoutTail) {\n stdout += stdoutTail;\n options.onStdout?.(stdoutTail);\n }\n const stderrTail = stderrDecoder.decode();\n if (stderrTail) {\n stderr += stderrTail;\n options.onStderr?.(stderrTail);\n }\n try {\n socket.close(1000, '');\n } catch {\n /* already closed */\n }\n resolve({\n exitCode,\n stdout,\n stderr,\n truncated: false,\n timedOut,\n ...(closeCode !== undefined && { closeCode }),\n ...(closeReason !== undefined && { closeReason }),\n opened,\n });\n };\n\n // Arm the timeout BEFORE we open the socket so a stalled handshake can't\n // leave the promise pending. Callers with a positive `timeoutMs` get the\n // wall-clock cap they asked for; callers without one still get a\n // connect-only deadline that clears once the socket opens.\n if (options.timeoutMs !== undefined && options.timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n // 124 matches the proxy's `/exec` semantics (coreutils `timeout`\n // exit code) so callers that switch on exitCode see the same value.\n if (exitCode === null) exitCode = 124;\n settle();\n }, options.timeoutMs);\n } else {\n handshakeTimer = setTimeout(() => {\n // Never opened → treat as a transport failure. Leave exitCode=null\n // so the caller can distinguish this from a normal exit; do not\n // set timedOut (that flag is reserved for the wall-clock case).\n if (!opened) settle();\n }, HANDSHAKE_DEADLINE_MS);\n }\n\n const socket = factory(lease.wsEndpoint, [lease.subprotocol, lease.jwt]);\n socket.binaryType = 'arraybuffer';\n\n socket.onopen = () => {\n opened = true;\n if (handshakeTimer) {\n clearTimeout(handshakeTimer);\n handshakeTimer = undefined;\n }\n const data: Record<string, unknown> = { command: options.command };\n if (options.cwd) data.cwd = options.cwd;\n if (options.env && Object.keys(options.env).length > 0) data.env = options.env;\n socket.send(JSON.stringify({ type: 'init_exec', data }));\n // We never stream stdin for one-shot exec; the SDK does this too, and\n // omitting it can leave the exec hanging waiting on EOF.\n socket.send(JSON.stringify({ type: 'stdin_close' }));\n };\n\n socket.onmessage = event => {\n const { data } = event;\n if (data instanceof ArrayBuffer) {\n handleBinaryFrame(data);\n } else if (typeof data === 'string') {\n handleTextFrame(data);\n }\n };\n\n socket.onclose = event => {\n closeCode = event.code;\n closeReason = event.reason;\n if (!opened) {\n // Never opened — surface as a failure via exitCode=null,\n // truncated=false, timedOut=false so the caller can distinguish\n // it from a normal exit-0 by inspecting `exitCode === null`.\n settle();\n return;\n }\n // Preserve any info captured before close; if the server sent an\n // `exit` frame this is a no-op because settle() already ran.\n settle();\n };\n\n socket.onerror = () => {\n if (settled) return;\n if (!opened) {\n settle();\n }\n // If we're mid-stream and the socket errors, wait for onclose to fire\n // so we settle with whatever output we did receive.\n };\n\n function handleBinaryFrame(buffer: ArrayBuffer) {\n const view = new Uint8Array(buffer);\n if (view.length <= 1) return;\n if (view[0] === STDOUT_FRAME) {\n const chunk = stdoutDecoder.decode(view.subarray(1), { stream: true });\n stdout += chunk;\n options.onStdout?.(chunk);\n } else if (view[0] === STDERR_FRAME) {\n const chunk = stderrDecoder.decode(view.subarray(1), { stream: true });\n stderr += chunk;\n options.onStderr?.(chunk);\n }\n }\n\n function handleTextFrame(text: string) {\n let frame: { type?: string; data?: { exit_code?: number } };\n try {\n frame = JSON.parse(text) as { type?: string; data?: { exit_code?: number } };\n } catch {\n return;\n }\n if (frame.type === 'exit') {\n exitCode = frame.data?.exit_code ?? 0;\n settle();\n }\n // `durable_session` frames are intentionally ignored — we don't reattach\n // or expose session names on the one-shot exec path.\n }\n });\n}\n","import type { RequestContext } from '@mastra/core/di';\nimport type {\n CommandResult,\n ExecuteCommandOptions,\n InstructionsOption,\n MastraSandboxOptions,\n ProcessInfo,\n ProviderStatus,\n SandboxCloneOptions,\n SandboxInfo,\n SpawnProcessOptions,\n} from '@mastra/core/workspace';\nimport { MastraSandbox, ProcessHandle, SandboxNotReadyError, SandboxProcessManager } from '@mastra/core/workspace';\nimport type { PlatformClientOptions } from './client.js';\nimport { PlatformApiError, PlatformClient } from './client.js';\nimport type { DirectExecWebSocketFactory, ExecLease } from './direct-exec.js';\nimport { execViaLease } from './direct-exec.js';\n\nexport type PlatformSandboxNetworkIsolation = 'ISOLATED' | 'PRIVATE';\n\nexport interface PlatformSandboxOptions extends Omit<MastraSandboxOptions, 'processes'>, PlatformClientOptions {\n id?: string;\n environmentId?: string;\n sandboxId?: string;\n idleTimeoutMinutes?: number;\n networkIsolation?: PlatformSandboxNetworkIsolation;\n env?: Record<string, string>;\n timeout?: number;\n instructions?: InstructionsOption;\n /**\n * Injected WebSocket factory used by the direct-exec code path. Defaults to\n * the global `WebSocket` (available on Node 22+, this package's minimum) and\n * only exists so tests can drive the exec state machine deterministically\n * without a real network socket.\n */\n webSocketFactory?: DirectExecWebSocketFactory;\n}\n\ninterface ExecLeaseResponse {\n provider: string;\n sandboxId: string;\n providerResourceId: string;\n jwt: string;\n wsEndpoint: string;\n subprotocol: string;\n expiresAt: string | null;\n}\n\n/**\n * How long before a lease's stated `expiresAt` we should treat it as\n * expired. Avoids a race where the JWT is valid at cache-hit time but the\n * server rejects it by the time the WebSocket handshake completes.\n */\nconst LEASE_REFRESH_MARGIN_MS = 60_000;\n\ninterface CreateSandboxResponse {\n id: string;\n providerResourceId?: string | null;\n status?: string;\n createdAt?: string;\n destroyedAt?: string | null;\n}\n\n/** Max attempts for `POST /sandbox` when the proxy returns transient 5xx errors. */\nconst CREATE_MAX_ATTEMPTS = 3;\n/** Base delay between create retries; multiplied by the attempt number. */\nconst CREATE_RETRY_BASE_DELAY_MS = 2_000;\n\n/**\n * Diagnostic error thrown when the direct-exec WebSocket transport fails\n * twice in a row (opening handshake refused or socket closed mid-stream\n * without an `exit` frame). Distinguishes \"the sandbox transport is broken\"\n * from \"your command failed\" so callers can decide whether to retry at a\n * higher level (e.g. reprovision the sandbox) or surface the error.\n *\n * `opened` is `true` when the WebSocket completed its handshake at least\n * once before closing; `false` when Railway refused the upgrade outright.\n */\nexport class SandboxExecTransportError extends Error {\n readonly sandboxId: string | undefined;\n readonly command: string;\n readonly attempts: number;\n readonly opened: boolean;\n readonly closeCode: number | undefined;\n readonly closeReason: string | undefined;\n readonly wsEndpoint: string;\n\n constructor(\n message: string,\n diagnostics: {\n sandboxId?: string;\n command: string;\n attempts: number;\n opened: boolean;\n closeCode?: number;\n closeReason?: string;\n wsEndpoint: string;\n },\n ) {\n super(message);\n this.name = 'SandboxExecTransportError';\n this.sandboxId = diagnostics.sandboxId;\n this.command = diagnostics.command;\n this.attempts = diagnostics.attempts;\n this.opened = diagnostics.opened;\n this.closeCode = diagnostics.closeCode;\n this.closeReason = diagnostics.closeReason;\n this.wsEndpoint = diagnostics.wsEndpoint;\n }\n}\n\n/**\n * Thrown when `/exec-lease` returns 410 Gone — the sandbox has been destroyed\n * (Railway destroy, quota reclamation, etc.). The client cannot recover from\n * this on its own because it does not own the binding store; only the fleet\n * layer can clear the stale sandbox id and provision a fresh one. Callers\n * (typically `SandboxFleet`) must catch this and reprovision-and-replay.\n *\n * When this is thrown the cached `_lease` and `_sandboxId` on the sandbox\n * instance are cleared, so the next `ensureRunning()` on a reused instance\n * will re-provision cleanly.\n */\nexport class SandboxDestroyedError extends Error {\n readonly sandboxId: string | undefined;\n readonly command: string;\n readonly attempts: number;\n\n constructor(message: string, diagnostics: { sandboxId?: string; command: string; attempts: number }) {\n super(message);\n this.name = 'SandboxDestroyedError';\n this.sandboxId = diagnostics.sandboxId;\n this.command = diagnostics.command;\n this.attempts = diagnostics.attempts;\n }\n}\n\n/**\n * Compose a shell command line from a `command` string and optional `args`.\n *\n * IMPORTANT: `command` is treated as a **shell string** and passed to the\n * remote shell verbatim so callers can use pipes, redirects, and chaining\n * (`ls -la | grep foo`). This matches the contract of {@link MastraSandbox}\n * and the local sandbox implementation. `args` are always shell-quoted so\n * they cannot inject syntax.\n *\n * Callers MUST NOT pass untrusted input as `command`. Untrusted values must\n * be passed via `args`, where they are safely quoted. Passing untrusted\n * input as `command` allows arbitrary shell syntax execution on the remote\n * sandbox.\n */\nfunction buildCommand(command: string, args?: string[]): string {\n return args?.length ? `${command} ${args.map(shellQuote).join(' ')}` : command;\n}\n\nfunction shellQuote(arg: string): string {\n if (/^[a-zA-Z0-9._\\-/=:@]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, `'\\\\''`)}'`;\n}\n\nclass PlatformProcessHandle extends ProcessHandle {\n readonly pid: string;\n private readonly resultPromise: Promise<CommandResult>;\n private exitCodeValue: number | undefined;\n\n constructor(pid: string, resultPromise: Promise<CommandResult>, options?: SpawnProcessOptions) {\n super(options);\n this.pid = pid;\n this.resultPromise = resultPromise.then(result => {\n this.exitCodeValue = result.exitCode;\n if (result.stdout) this.emitStdout(result.stdout);\n if (result.stderr) this.emitStderr(result.stderr);\n return result;\n });\n }\n\n get exitCode(): number | undefined {\n return this.exitCodeValue;\n }\n\n async wait(): Promise<CommandResult> {\n return this.resultPromise;\n }\n\n async kill(): Promise<boolean> {\n // The workspace proxy has no cancel-exec endpoint; each `executeCommand`\n // is a synchronous round-trip that has already completed (or timed out)\n // by the time a handle exists to kill. Making this explicit avoids\n // callers silently believing they cancelled a still-running process.\n throw new Error('Platform sandbox command execution does not support killing individual processes');\n }\n\n async sendStdin(): Promise<void> {\n throw new Error('Platform sandbox command execution does not support stdin');\n }\n}\n\nclass PlatformProcessManager extends SandboxProcessManager<PlatformSandbox> {\n private spawnCounter = 0;\n\n /**\n * Spawn a process on the remote sandbox.\n *\n * `command` is interpreted as a shell string by the remote shell, matching\n * the {@link MastraSandbox} contract. See {@link PlatformSandbox.executeCommand}\n * for the untrusted-input caveat: never pass untrusted values as `command`.\n */\n async spawn(command: string, options: SpawnProcessOptions = {}): Promise<ProcessHandle> {\n const pid = `platform-proc-${Date.now().toString(36)}-${(this.spawnCounter++).toString(36)}`;\n const resultPromise = this.sandbox.executeCommand(command, undefined, options);\n const handle = new PlatformProcessHandle(pid, resultPromise, options);\n this._tracked.set(handle.pid, handle);\n return handle;\n }\n\n async list(): Promise<ProcessInfo[]> {\n return Array.from(this._tracked.values()).map(handle => ({\n pid: handle.pid,\n command: handle.command,\n running: handle.exitCode === undefined,\n ...(handle.exitCode !== undefined && { exitCode: handle.exitCode }),\n }));\n }\n}\n\nexport class PlatformSandbox extends MastraSandbox {\n readonly id: string;\n readonly name = 'PlatformSandbox';\n readonly provider = 'platform';\n status: ProviderStatus = 'pending';\n declare readonly processes: PlatformProcessManager;\n\n private readonly _client: PlatformClient;\n private readonly _environmentId: string;\n private _sandboxId?: string;\n private readonly _idleTimeoutMinutes?: number;\n private readonly _networkIsolation?: PlatformSandboxNetworkIsolation;\n private readonly _env: Record<string, string>;\n private readonly _timeout?: number;\n private readonly _instructionsOverride?: InstructionsOption;\n private _createdAt: Date | null = null;\n private readonly _webSocketFactory?: DirectExecWebSocketFactory;\n /**\n * Cached exec lease for this sandbox. `null` before the first exec and\n * after {@link destroy}. Refreshed when `expiresAt - LEASE_REFRESH_MARGIN_MS < now`\n * (see {@link _ensureLease}); a lease without a disclosed `expiresAt`\n * is refreshed on every call.\n */\n private _lease: (ExecLease & { expiresAtMs: number | null }) | null = null;\n /**\n * In-flight mint request; concurrent `_ensureLease` callers on a cold or\n * near-expiry cache all await this single promise so we don't burn N\n * `POST /exec-lease` round-trips when the sandbox is doing N parallel execs.\n * Cleared (regardless of success or failure) when the request settles.\n */\n private _leaseInFlight: Promise<ExecLease & { expiresAtMs: number | null }> | null = null;\n\n constructor(options: PlatformSandboxOptions = {}) {\n super({ ...options, name: 'PlatformSandbox', processes: new PlatformProcessManager() });\n this.id = options.id ?? this.generateId();\n this._client = new PlatformClient(options);\n this._environmentId = options.environmentId ?? process.env.MASTRA_ENVIRONMENT_ID ?? '';\n if (!this._environmentId && !options.sandboxId) throw new Error('environmentId is required');\n this._sandboxId = options.sandboxId;\n this._idleTimeoutMinutes = options.idleTimeoutMinutes;\n this._networkIsolation = options.networkIsolation;\n this._env = options.env ?? {};\n this._timeout = options.timeout;\n this._instructionsOverride = options.instructions;\n this._webSocketFactory = options.webSocketFactory;\n }\n\n private generateId(): string {\n return `platform-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n /**\n * Construct a sibling {@link PlatformSandbox} that inherits this sandbox's\n * credentials and defaults (access token, project, environment, network\n * isolation, timeout, instructions, env, idle timeout) with per-instance\n * overrides from `options`.\n *\n * Performs no I/O and does not require this sandbox to be started — the\n * returned sandbox is not started and provisions (or reattaches, when\n * `sandboxId` is set) on its own `start()`. Use it when one configured\n * sandbox acts as the template for a fleet of independent sandboxes\n * (e.g. one per project).\n */\n clone(options: SandboxCloneOptions = {}): PlatformSandbox {\n // The proxy hashes `body.id` on POST /sandbox to look up a prior\n // checkpoint. A stable `checkpointName` is only useful if it round-trips\n // to `body.id`, so route it through the sandbox id when the caller\n // didn't pick one explicitly. Without this, every clone gets a random\n // id and no boot ever hits its captured checkpoint (see\n // issue-platform-sandbox-clone-drops-checkpoint-name.md).\n const id = options.id ?? options.checkpointName;\n return new PlatformSandbox({\n ...(id !== undefined && { id }),\n accessToken: this._client.accessToken,\n projectId: this._client.projectId,\n fetch: this._client.fetch,\n environmentId: this._environmentId,\n ...(options.sandboxId !== undefined && { sandboxId: options.sandboxId }),\n idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,\n ...(this._networkIsolation !== undefined && { networkIsolation: this._networkIsolation }),\n env: options.env ?? this._env,\n ...(this._timeout !== undefined && { timeout: this._timeout }),\n ...(this._instructionsOverride !== undefined && { instructions: this._instructionsOverride }),\n ...(this._webSocketFactory !== undefined && { webSocketFactory: this._webSocketFactory }),\n });\n }\n\n async start(): Promise<void> {\n if (this._sandboxId) {\n try {\n const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);\n const json = (await response.json()) as CreateSandboxResponse;\n // A destroyed record (idle GC, manual delete) is not reattachable —\n // treat it like a missing sandbox so we fall through to a fresh\n // provision instead of pointing exec at a dead resource.\n if (!json.destroyedAt) {\n this._createdAt = json.createdAt ? new Date(json.createdAt) : new Date();\n return;\n }\n this._sandboxId = undefined;\n } catch (error) {\n if (!(error instanceof PlatformApiError) || error.status !== 404) throw error;\n this._sandboxId = undefined;\n }\n }\n\n if (!this._environmentId) throw new Error('environmentId is required');\n\n const body = JSON.stringify({\n // Sent so the platform can associate the provisioned resource with a\n // caller-stable identifier (used for opt-in checkpoint recovery). The\n // platform treats it as an advisory key: unknown values fall through\n // to a fresh sandbox, matching pre-existing behavior.\n id: this.id,\n environmentId: this._environmentId,\n idleTimeoutMinutes: this._idleTimeoutMinutes,\n networkIsolation: this._networkIsolation,\n env: this._env,\n });\n // Provisioning is observed to fail intermittently with proxy 500s while\n // the provider is under load. A create either succeeds (201) or fails\n // without allocating a caller-visible resource, so retrying transient\n // 5xx responses with a short backoff is safe and keeps a single flaky\n // window from killing the caller's whole workflow.\n let response: Response | undefined;\n for (let attempt = 1; ; attempt++) {\n try {\n response = await this._client.request('/sandbox', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body,\n });\n break;\n } catch (error) {\n const transient = error instanceof PlatformApiError && error.status >= 500;\n if (!transient || attempt >= CREATE_MAX_ATTEMPTS) throw error;\n await new Promise(resolve => setTimeout(resolve, CREATE_RETRY_BASE_DELAY_MS * attempt));\n }\n }\n const json = (await response.json()) as CreateSandboxResponse;\n this._sandboxId = json.id;\n this._createdAt = json.createdAt ? new Date(json.createdAt) : new Date();\n }\n\n async stop(): Promise<void> {\n await this.destroy();\n }\n\n async destroy(): Promise<void> {\n if (!this._sandboxId) return;\n await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`, { method: 'DELETE' });\n // Clear local state so a subsequent start() creates a fresh remote sandbox\n // instead of taking the reattach branch and pointing exec at a deleted resource.\n this._sandboxId = undefined;\n this._createdAt = null;\n // Drop the exec lease with the sandbox — the JWT is tied to the provider\n // instance id and would be rejected against a fresh one.\n this._lease = null;\n }\n\n /**\n * Execute a command on the remote sandbox.\n *\n * `command` is a **shell string**: it is concatenated verbatim into the\n * command line sent to the remote shell, which lets callers use pipes,\n * redirects, and chaining (`ls -la | grep foo`). This matches the contract\n * of {@link MastraSandbox} and the local sandbox implementation.\n *\n * `args`, when provided, are always shell-quoted so they cannot inject\n * additional shell syntax.\n *\n * Security: callers MUST NOT pass untrusted input as `command`. If any part\n * of the invocation is derived from an untrusted source, pass it through\n * `args` (which is safely quoted) or shell-quote it yourself before\n * inclusion. Untrusted `command` values allow arbitrary shell syntax\n * execution on the remote sandbox.\n */\n async executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<CommandResult> {\n await this.ensureRunning();\n if (!this._sandboxId) throw new SandboxNotReadyError(this.id);\n\n const started = Date.now();\n const fullCommand = buildCommand(command, args);\n // Nullish check so an explicit `timeout: 0` still overrides the instance\n // default. `_runDirectExec` omits `timeoutMs` from the exec payload when\n // the value is 0, which disables the client-side timer entirely.\n const effectiveTimeout = options?.timeout ?? this._timeout;\n\n // Direct-exec (WebSocket straight to Railway's tcp-proxy) is the only\n // data plane. `_runDirectExec` handles single-shot transport retry and\n // throws typed errors on unrecoverable failure: `SandboxDestroyedError`\n // when `/exec-lease` returns 410 (fleet must reprovision),\n // `SandboxExecTransportError` when the WebSocket transport fails twice\n // against a live sandbox, `PlatformApiError` for other `/exec-lease`\n // errors (404/500/501). See ./direct-exec.ts and\n // `docs/factory/direct-sandbox-connection.md` in the Platform repo.\n const result = await this._runDirectExec(fullCommand, effectiveTimeout, options);\n // `_runDirectExec` throws on transport failure (see its jsdoc), so a\n // `null` exitCode here can only mean `timedOut: true` — the sandbox\n // never got to send an exit frame because we cut the command short.\n // Use 124 for that (the conventional timeout exit code). We are NOT\n // coercing transport-failure nulls to fake exit codes — those throw.\n const exitCode = result.exitCode ?? 124;\n return {\n success: exitCode === 0,\n exitCode,\n stdout: result.stdout,\n stderr: result.stderr,\n timedOut: result.timedOut,\n command: fullCommand,\n executionTimeMs: Date.now() - started,\n };\n }\n\n /**\n * Run a single exec against the direct-exec transport, with one in-flight\n * retry on WebSocket transport failure (socket closed without an `exit`\n * frame and the exec did not time out). The retry mints a fresh lease\n * — the failure could be a stale JWT — and reopens a new WebSocket.\n *\n * Error taxonomy:\n * - **410 on `/exec-lease`** (either attempt) → the sandbox is gone.\n * Nulls the cached `_lease` and `_sandboxId` and throws\n * {@link SandboxDestroyedError}. Callers (typically `SandboxFleet`) must\n * catch this, clear the stale binding, and reprovision + replay.\n * - **Persistent transport failure** (both WS attempts close without an\n * `exit` frame against a live sandbox) → {@link SandboxExecTransportError}\n * with WebSocket close diagnostics.\n * - **Other `PlatformApiError`s** (404/500/501) propagate directly.\n * - **Real command result** (exit code from Railway's exit frame, or\n * `timedOut: true`) returns normally.\n *\n * Returns a result with a real `exitCode` OR `timedOut: true`. Never\n * returns `{ exitCode: null, timedOut: false }` — that case throws.\n */\n private async _runDirectExec(\n fullCommand: string,\n effectiveTimeout: number | undefined,\n options: ExecuteCommandOptions | undefined,\n ): Promise<{ exitCode: number | null; stdout: string; stderr: string; timedOut: boolean }> {\n // Filter undefined values out of the env overlay so we match the\n // Record<string, string> shape execViaLease expects. `ExecuteCommandOptions.env`\n // is NodeJS.ProcessEnv (string | undefined).\n const filteredEnv = options?.env\n ? Object.fromEntries(\n Object.entries(options.env).filter((entry): entry is [string, string] => entry[1] !== undefined),\n )\n : undefined;\n\n let lastResult: Awaited<ReturnType<typeof execViaLease>> | undefined;\n let lastLease: (ExecLease & { expiresAtMs: number | null }) | undefined;\n let attemptsMade = 0;\n // Two attempts: initial + one retry. On the second attempt we drop the\n // cached lease so we don't reuse a JWT that may itself be the cause of\n // the transport failure — but only if the cache still holds the same\n // lease we just failed against. A concurrent exec sharing this instance\n // may have already cached a fresh, unrelated lease in between, and we\n // must not discard that.\n for (let attempt = 0; attempt < 2; attempt++) {\n if (attempt > 0 && lastLease && this._lease === lastLease) this._lease = null;\n let lease: ExecLease & { expiresAtMs: number | null };\n try {\n lease = await this._ensureLease();\n } catch (error) {\n // 410 → sandbox has been destroyed. Clear all cached state so a\n // reused instance re-provisions cleanly, then hand off to the fleet\n // layer via a typed error. Other PlatformApiErrors (404/500/501)\n // propagate as-is — those are configuration or platform errors, not\n // a \"reprovision me\" signal.\n if (error instanceof PlatformApiError && error.status === 410) {\n this._lease = null;\n const priorSandboxId = this._sandboxId;\n this._sandboxId = undefined;\n throw new SandboxDestroyedError(\n `Sandbox ${priorSandboxId ?? '(unknown)'} was destroyed; /exec-lease returned 410`,\n {\n ...(priorSandboxId && { sandboxId: priorSandboxId }),\n command: fullCommand,\n attempts: attempt + 1,\n },\n );\n }\n throw error;\n }\n lastLease = lease;\n attemptsMade = attempt + 1;\n const result = await execViaLease(lease, {\n command: fullCommand,\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(filteredEnv !== undefined && { env: filteredEnv }),\n ...(effectiveTimeout != null && effectiveTimeout > 0 && { timeoutMs: effectiveTimeout }),\n ...(this._webSocketFactory && { webSocketFactory: this._webSocketFactory }),\n });\n lastResult = result;\n // `null` exitCode with `timedOut: false` means the socket closed\n // without an exit frame — a transport failure (handshake stalled,\n // mid-stream drop, expired token). Any other outcome (real exit code\n // or timed-out) is a valid result and we return it.\n if (result.exitCode !== null || result.timedOut) return result;\n }\n\n // Both attempts failed at the transport layer against a live sandbox.\n // Surface a loud, typed error with close diagnostics so callers can\n // distinguish \"your command failed\" from \"the sandbox transport is\n // broken.\"\n const result = lastResult!;\n const lease = lastLease!;\n // The lease from the failed second attempt is still cached; drop it so\n // the next `executeCommand` doesn't waste its first attempt on the same\n // implicated JWT before minting fresh. Identity-check first so a\n // concurrent exec that has already cached a fresh, unrelated lease\n // isn't collateral-damaged.\n if (this._lease === lease) this._lease = null;\n throw new SandboxExecTransportError(\n `Direct-exec transport failed for sandbox ${this._sandboxId ?? '(unknown)'} after ${attemptsMade} attempt(s)` +\n (result.closeCode !== undefined\n ? ` (close ${result.closeCode}${result.closeReason ? ` ${result.closeReason}` : ''})`\n : ''),\n {\n ...(this._sandboxId && { sandboxId: this._sandboxId }),\n command: fullCommand,\n attempts: attemptsMade,\n opened: result.opened ?? false,\n ...(result.closeCode !== undefined && { closeCode: result.closeCode }),\n ...(result.closeReason !== undefined && { closeReason: result.closeReason }),\n wsEndpoint: lease.wsEndpoint,\n },\n );\n }\n\n /**\n * Return a cached exec lease, minting a fresh one when the cache is empty\n * or the JWT is within {@link LEASE_REFRESH_MARGIN_MS} of `expiresAt`.\n *\n * Callers are expected to be on the \"sandbox is running\" path; we don't\n * re-check `_sandboxId` here because `executeCommand` already gated on it.\n */\n private async _ensureLease(): Promise<ExecLease & { expiresAtMs: number | null }> {\n const now = Date.now();\n // Cache hit only when we know the expiry AND we're comfortably before it.\n // A null `expiresAtMs` means the provider didn't disclose a TTL — treat\n // that as \"refresh every call\" rather than \"cache forever\", so a token\n // that turns out to be short-lived can't wedge the sandbox until restart.\n if (this._lease && this._lease.expiresAtMs !== null && this._lease.expiresAtMs - LEASE_REFRESH_MARGIN_MS > now) {\n return this._lease;\n }\n // Coalesce concurrent mints on a cold/expired cache.\n if (this._leaseInFlight) return this._leaseInFlight;\n if (!this._sandboxId) throw new SandboxNotReadyError(this.id);\n const sandboxId = this._sandboxId;\n const inFlight = (async () => {\n const response = await this._client.request(`/sandbox/${encodeURIComponent(sandboxId)}/exec-lease`, {\n method: 'POST',\n });\n const json = (await response.json()) as ExecLeaseResponse;\n const expiresAtMs = json.expiresAt ? Date.parse(json.expiresAt) : null;\n const lease = {\n jwt: json.jwt,\n wsEndpoint: json.wsEndpoint,\n subprotocol: json.subprotocol,\n expiresAt: json.expiresAt,\n // Guard against `Date.parse` returning NaN for malformed values by\n // treating them as \"no expiry known\", which forces a mint every call\n // rather than silently caching a broken lease forever.\n expiresAtMs: expiresAtMs !== null && !Number.isNaN(expiresAtMs) ? expiresAtMs : null,\n };\n this._lease = lease;\n return lease;\n })();\n this._leaseInFlight = inFlight;\n try {\n return await inFlight;\n } finally {\n // Clear on both success and failure so a failed mint doesn't wedge\n // future callers into awaiting the same rejected promise forever.\n if (this._leaseInFlight === inFlight) this._leaseInFlight = null;\n }\n }\n\n async getInfo(): Promise<SandboxInfo> {\n if (!this._sandboxId) {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this._createdAt ?? new Date(),\n };\n }\n const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);\n const json = (await response.json()) as CreateSandboxResponse;\n return {\n id: json.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: json.createdAt ? new Date(json.createdAt) : (this._createdAt ?? new Date()),\n metadata: {\n // The platform assigns its own sandbox id on create (the advisory id\n // sent in the POST body is not honored). Expose it so callers that\n // persist a reattach id (e.g. the Factory sandbox fleet, which reads\n // `metadata.sandboxId`) store the id the proxy actually recognizes\n // instead of the locally generated construction id.\n sandboxId: json.id,\n providerResourceId: json.providerResourceId ?? undefined,\n platformStatus: json.status,\n },\n };\n }\n\n getInstructions(opts?: { requestContext?: RequestContext }): string {\n const defaultInstructions = `Platform sandbox${this._sandboxId ? ` ${this._sandboxId}` : ''}. Execute commands with the sandbox command APIs.`;\n if (typeof this._instructionsOverride === 'function') {\n return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });\n }\n if (typeof this._instructionsOverride === 'string') return this._instructionsOverride;\n return defaultInstructions;\n }\n}\n","import type { FilesystemProvider, SandboxProvider } from '@mastra/core/editor';\nimport type { PlatformFilesystemOptions } from './filesystem.js';\nimport { PlatformFilesystem } from './filesystem.js';\nimport type { PlatformSandboxOptions } from './sandbox.js';\nimport { PlatformSandbox } from './sandbox.js';\n\nexport const platformSandboxProvider: SandboxProvider<PlatformSandboxOptions> = {\n id: 'platform',\n name: 'Mastra Platform Sandbox',\n description: 'Environment-scoped sandbox execution through Mastra Platform workspace proxy',\n configSchema: {\n type: 'object',\n properties: {\n accessToken: {\n type: 'string',\n description: 'Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)',\n },\n projectId: { type: 'string', description: 'Platform project ID (falls back to MASTRA_PROJECT_ID)' },\n environmentId: { type: 'string', description: 'Platform environment ID (falls back to MASTRA_ENVIRONMENT_ID)' },\n sandboxId: { type: 'string', description: 'Reattach to an existing Platform sandbox by ID' },\n idleTimeoutMinutes: { type: 'number', description: 'Minutes before the sandbox can be destroyed while idle' },\n networkIsolation: {\n type: 'string',\n description: 'Network isolation mode',\n enum: ['ISOLATED', 'PRIVATE'],\n default: 'ISOLATED',\n },\n env: { type: 'object', description: 'Environment variables', additionalProperties: { type: 'string' } },\n timeout: { type: 'number', description: 'Default command timeout in ms' },\n },\n },\n createSandbox: config => new PlatformSandbox(config),\n};\n\nexport const platformFilesystemProvider: FilesystemProvider<PlatformFilesystemOptions> = {\n id: 'platform',\n name: 'Mastra Platform Filesystem',\n description: 'Bucket-backed filesystem access through Mastra Platform workspace proxy',\n configSchema: {\n type: 'object',\n properties: {\n accessToken: {\n type: 'string',\n description: 'Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)',\n },\n projectId: { type: 'string', description: 'Platform project ID (falls back to MASTRA_PROJECT_ID)' },\n bucketName: {\n type: 'string',\n description: 'Platform workspace bucket name (falls back to MASTRA_PLATFORM_BUCKET_NAME)',\n },\n readOnly: { type: 'boolean', description: 'Mount as read-only', default: false },\n },\n },\n createFilesystem: config => new PlatformFilesystem(config),\n};\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ function requireOption(value, name) {
15
15
  }
16
16
  function resolvePlatformOptions(options) {
17
17
  return {
18
- accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_SECRET_KEY ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, "accessToken"),
18
+ accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, "accessToken"),
19
19
  projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, "projectId"),
20
20
  proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\/$/, ""),
21
21
  fetch: options.fetch ?? fetch
@@ -928,7 +928,7 @@ const platformSandboxProvider = {
928
928
  properties: {
929
929
  accessToken: {
930
930
  type: "string",
931
- description: "Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)"
931
+ description: "Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)"
932
932
  },
933
933
  projectId: {
934
934
  type: "string",
@@ -974,7 +974,7 @@ const platformFilesystemProvider = {
974
974
  properties: {
975
975
  accessToken: {
976
976
  type: "string",
977
- description: "Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)"
977
+ description: "Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)"
978
978
  },
979
979
  projectId: {
980
980
  type: "string",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/client.ts","../src/filesystem.ts","../src/direct-exec.ts","../src/sandbox.ts","../src/provider.ts"],"sourcesContent":["export interface PlatformClientOptions {\n accessToken?: string;\n projectId?: string;\n fetch?: typeof fetch;\n}\n\nexport interface PlatformRequestOptions extends RequestInit {\n query?: Record<string, string | number | boolean | undefined>;\n}\n\nconst DEFAULT_PROXY_URL = 'https://workspaces.mastra.ai';\n\n/**\n * Default per-request timeout for calls to the workspace proxy. Applied only\n * when the caller doesn't already pass an `AbortSignal`. Long-running routes\n * (e.g. `POST /sandbox/:id/exec`) pass their own longer signal.\n */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 60_000;\n\nexport function requireOption(value: string | undefined, name: string): string {\n if (!value) throw new Error(`${name} is required`);\n return value;\n}\n\nexport function resolvePlatformOptions(options: PlatformClientOptions) {\n return {\n accessToken: requireOption(\n options.accessToken ??\n process.env.MASTRA_PLATFORM_SECRET_KEY ??\n // Deprecated alias — prefer MASTRA_PLATFORM_SECRET_KEY.\n process.env.MASTRA_PLATFORM_ACCESS_TOKEN,\n 'accessToken',\n ),\n projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, 'projectId'),\n proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\\/$/, ''),\n fetch: options.fetch ?? fetch,\n };\n}\n\n/**\n * Structured error shape returned by the workspace proxy. All routes emit\n * `{ error: { message, type } }` on failure — see servers/workspace-proxy in\n * the Platform repo. Kept as a wire-level type so callers can switch on\n * `error.code` without re-parsing `error.body`.\n */\nexport interface PlatformProxyError {\n message: string;\n /** Machine-readable error kind, e.g. `not_found`, `invalid_request`, `authentication_error`. */\n type: string;\n}\n\nfunction parseProxyError(body: string): PlatformProxyError | undefined {\n if (!body) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const err = (parsed as { error?: unknown }).error;\n if (typeof err !== 'object' || err === null) return undefined;\n const { message, type } = err as { message?: unknown; type?: unknown };\n if (typeof message !== 'string' || typeof type !== 'string') return undefined;\n return { message, type };\n}\n\nexport class PlatformApiError extends Error {\n readonly status: number;\n readonly body: string;\n /** Machine-readable proxy error kind (e.g. `not_found`), when the response body matches `{ error: { message, type } }`. */\n readonly code: string | undefined;\n /** Human-readable proxy error message, when the response body matches `{ error: { message, type } }`. */\n readonly proxyMessage: string | undefined;\n\n constructor(status: number, body: string) {\n const parsed = parseProxyError(body);\n const summary = parsed ? `${parsed.type}: ${parsed.message}` : body;\n super(`Platform proxy request failed with ${status}${summary ? `: ${summary}` : ''}`);\n this.name = 'PlatformApiError';\n this.status = status;\n this.body = body;\n this.code = parsed?.type;\n this.proxyMessage = parsed?.message;\n }\n}\n\nexport class PlatformClient {\n readonly accessToken: string;\n readonly projectId: string;\n readonly proxyUrl: string;\n readonly fetch: typeof fetch;\n\n constructor(options: PlatformClientOptions) {\n const resolved = resolvePlatformOptions(options);\n this.accessToken = resolved.accessToken;\n this.projectId = resolved.projectId;\n this.proxyUrl = resolved.proxyUrl;\n this.fetch = resolved.fetch;\n }\n\n async request(path: string, options: PlatformRequestOptions = {}): Promise<Response> {\n const url = new URL(`${this.proxyUrl}/v1/projects/${encodeURIComponent(this.projectId)}${path}`);\n for (const [key, value] of Object.entries(options.query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n\n const headers = new Headers(options.headers);\n headers.set('authorization', `Bearer ${this.accessToken}`);\n\n // Strip our helper-only field so the underlying fetch sees a valid RequestInit.\n const { query: _query, ...fetchOptions } = options;\n // Apply a default timeout only when the caller didn't already supply an\n // AbortSignal — long-running routes (exec) provide their own longer signal.\n const signal = fetchOptions.signal ?? AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS);\n const response = await this.fetch(url, { ...fetchOptions, headers, signal });\n if (!response.ok) {\n throw new PlatformApiError(response.status, await response.text());\n }\n return response;\n }\n}\n","import { Buffer } from 'node:buffer';\nimport nodePath from 'node:path';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type {\n CopyOptions,\n FileContent,\n FileEntry,\n FileStat,\n FilesystemIcon,\n FilesystemInfo,\n InstructionsOption,\n ListOptions,\n MastraFilesystemOptions,\n ProviderStatus,\n ReadOptions,\n RemoveOptions,\n WriteOptions,\n} from '@mastra/core/workspace';\nimport { FileExistsError, FileNotFoundError, MastraFilesystem, WorkspaceReadOnlyError } from '@mastra/core/workspace';\nimport type { PlatformClientOptions } from './client.js';\nimport { PlatformClient } from './client.js';\n\ninterface ProxyListResponse {\n contents?: Array<{ key?: string; size?: number; lastModified?: string }>;\n commonPrefixes?: string[];\n}\n\nexport interface PlatformFilesystemOptions extends PlatformClientOptions, MastraFilesystemOptions {\n id?: string;\n bucketName?: string;\n readOnly?: boolean;\n displayName?: string;\n icon?: FilesystemIcon;\n description?: string;\n instructions?: InstructionsOption;\n}\n\nfunction normalizePath(input: string): string {\n if (!input || input === '.') return '/';\n let normalized = input.startsWith('/') ? input : `/${input}`;\n normalized = nodePath.posix.normalize(normalized);\n return normalized === '.' ? '/' : normalized;\n}\n\nfunction keyFromPath(path: string): string {\n const normalized = normalizePath(path);\n return normalized === '/' ? '' : normalized.slice(1);\n}\n\n/**\n * Encode each `/`-delimited segment of an object key with `encodeURIComponent`\n * so reserved URL characters (`?`, `#`, `%`, `&`, `+`, spaces, etc.) are\n * treated as part of the key instead of URL syntax. Kept segment-aware so\n * `/` continues to act as a path separator on the wire.\n */\nfunction encodeKeyPath(key: string): string {\n return key.split('/').map(encodeURIComponent).join('/');\n}\n\nfunction nameFromPath(path: string): string {\n const normalized = normalizePath(path);\n if (normalized === '/') return '';\n return normalized.slice(normalized.lastIndexOf('/') + 1);\n}\n\nfunction contentToBody(content: FileContent): string | Buffer {\n if (typeof content === 'string') return content;\n return Buffer.from(content);\n}\n\nfunction headerDate(headers: Headers, name: string): Date {\n const value = headers.get(name);\n return value ? new Date(value) : new Date(0);\n}\n\nfunction headerSize(headers: Headers): number {\n const value = headers.get('content-length');\n return value ? Number(value) : 0;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'status' in error && error.status === 404;\n}\n\nexport class PlatformFilesystem extends MastraFilesystem {\n readonly id: string;\n readonly name = 'PlatformFilesystem';\n readonly provider = 'platform';\n readonly readOnly?: boolean;\n readonly displayName?: string;\n readonly icon: FilesystemIcon;\n readonly description?: string;\n status: ProviderStatus = 'pending';\n\n private readonly _client: PlatformClient;\n private readonly _bucketName: string;\n private readonly _instructionsOverride?: InstructionsOption;\n\n constructor(options: PlatformFilesystemOptions = {}) {\n super({ ...options, name: 'PlatformFilesystem' });\n this.id = options.id ?? this.generateId();\n this._bucketName = options.bucketName ?? process.env.MASTRA_PLATFORM_BUCKET_NAME ?? '';\n if (!this._bucketName) throw new Error('bucketName is required');\n this.readOnly = options.readOnly;\n this.displayName = options.displayName;\n this.icon = options.icon ?? 'cloud';\n this.description = options.description;\n this._instructionsOverride = options.instructions;\n this._client = new PlatformClient(options);\n }\n\n private generateId(): string {\n return `platform-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n await this.ensureReady();\n let response: Response;\n try {\n response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,\n );\n } catch (error) {\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n return options?.encoding ? buffer.toString(options.encoding) : buffer;\n }\n\n async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('writeFile');\n const headers: Record<string, string> = {};\n if (options?.mimeType) headers['content-type'] = options.mimeType;\n if (options?.overwrite === false) headers['if-none-match'] = '*';\n try {\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'PUT',\n headers,\n body: contentToBody(content),\n });\n } catch (error) {\n if (typeof error === 'object' && error !== null && 'status' in error && error.status === 412) {\n throw new FileExistsError(path);\n }\n throw error;\n }\n }\n\n /**\n * Append bytes to a file.\n *\n * **Not atomic.** Object storage behind the workspace proxy has no native\n * append or compare-and-swap primitive, so this implementation is a\n * read-modify-write: it reads the current contents, concatenates the new\n * bytes, and PUTs the whole object back. Concurrent `appendFile` calls to\n * the same path can overwrite each other's writes (\"last write wins\").\n * Use `writeFile` with distinct keys for concurrent writers.\n */\n async appendFile(path: string, content: FileContent): Promise<void> {\n const existing = (await this.exists(path)) ? await this.readFile(path) : Buffer.alloc(0);\n await this.writeFile(\n path,\n Buffer.concat([Buffer.isBuffer(existing) ? existing : Buffer.from(existing), Buffer.from(content)]),\n );\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('deleteFile');\n try {\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'DELETE',\n query: { recursive: options?.recursive },\n });\n } catch (error) {\n if (isNotFound(error) && options?.force) return;\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n }\n\n async copyFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('copyFile');\n // The workspace proxy's `?op=copy` route always overwrites the destination;\n // there's no conditional wire field to prevent it. Reject the option\n // explicitly instead of silently overwriting when the caller asked us not to.\n if (options?.overwrite === false) {\n throw new Error('PlatformFilesystem.copyFile does not support overwrite: false — the proxy always overwrites.');\n }\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {\n method: 'POST',\n query: { op: 'copy' },\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ destination: keyFromPath(dest) }),\n });\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('moveFile');\n // Same rationale as copyFile: `?op=rename` always overwrites.\n if (options?.overwrite === false) {\n throw new Error('PlatformFilesystem.moveFile does not support overwrite: false — the proxy always overwrites.');\n }\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {\n method: 'POST',\n query: { op: 'rename' },\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ destination: keyFromPath(dest) }),\n });\n }\n\n async mkdir(path: string, _options?: { recursive?: boolean }): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('mkdir');\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'POST',\n query: { op: 'mkdir' },\n });\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n await this.deleteFile(path.endsWith('/') ? path : `${path}/`, { recursive: true, force: options?.force });\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n await this.ensureReady();\n const prefix = keyFromPath(path);\n const response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(prefix)}`,\n {\n query: {\n delimiter: options?.recursive ? undefined : '/',\n prefix: prefix ? `${prefix.replace(/\\/$/, '')}/` : undefined,\n },\n },\n );\n const json = (await response.json()) as ProxyListResponse;\n return [\n ...(json.commonPrefixes ?? []).map(prefix => ({\n name: nameFromPath(prefix.replace(/\\/$/, '')),\n type: 'directory' as const,\n })),\n ...(json.contents ?? [])\n .filter(object => object.key && !object.key.endsWith('/'))\n .map(object => ({\n name: nameFromPath(object.key!),\n type: 'file' as const,\n size: object.size,\n })),\n ].filter(\n entry => !options?.extension || entry.type === 'directory' || matchesExtension(entry.name, options.extension),\n );\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await this.stat(path);\n return true;\n } catch (error) {\n if (isNotFound(error) || error instanceof FileNotFoundError) return false;\n throw error;\n }\n }\n\n async stat(path: string): Promise<FileStat> {\n await this.ensureReady();\n const normalized = normalizePath(path);\n if (normalized === '/') {\n return { name: '', path: '/', type: 'directory', size: 0, createdAt: new Date(0), modifiedAt: new Date(0) };\n }\n let response: Response;\n try {\n response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,\n {\n method: 'HEAD',\n },\n );\n } catch (error) {\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n return {\n name: nameFromPath(path),\n path: normalized,\n type: normalized.endsWith('/') ? 'directory' : 'file',\n size: headerSize(response.headers),\n createdAt: headerDate(response.headers, 'last-modified'),\n modifiedAt: headerDate(response.headers, 'last-modified'),\n mimeType: response.headers.get('content-type') ?? undefined,\n };\n }\n\n realpath(path: string): Promise<string> {\n return Promise.resolve(normalizePath(path));\n }\n\n getInstructions(opts?: { requestContext?: RequestContext }): string {\n const defaultInstructions = `Platform filesystem backed by Mastra Platform bucket ${this._bucketName}. Use absolute workspace paths.`;\n if (typeof this._instructionsOverride === 'function') {\n return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });\n }\n if (typeof this._instructionsOverride === 'string') return this._instructionsOverride;\n return defaultInstructions;\n }\n\n getInfo(): FilesystemInfo<{ bucketName: string; displayName?: string; description?: string }> {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n readOnly: this.readOnly,\n icon: this.icon,\n metadata: {\n bucketName: this._bucketName,\n ...(this.displayName && { displayName: this.displayName }),\n ...(this.description && { description: this.description }),\n },\n };\n }\n}\n\nfunction matchesExtension(name: string, extension: string | string[]): boolean {\n const extensions = Array.isArray(extension) ? extension : [extension];\n return extensions.some(ext => name.endsWith(ext));\n}\n","/**\n * Direct exec client — opens Railway's tcp-proxy exec WebSocket directly using\n * a short-lived JWT minted by the workspace proxy's exec-lease endpoint. This\n * removes the platform data plane from the exec stdout/stderr path entirely\n * (see `docs/factory/direct-sandbox-connection.md` in the Platform repo),\n * cutting payload-scaled Cloud Run egress and RTT for commands like\n * `pnpm install` that stream tens of MB of output.\n *\n * The frame protocol below mirrors `connectExecWs()` in `railway@3.5.5`\n * (`workspaces/railway/node_modules/railway/dist/index.js`). The `railway`\n * SDK's version is pinned on both sides (platform + here); a version bump\n * signals the protocol may have drifted and this module must be revisited.\n */\n\n/** Byte-0 tag on binary WS frames for stdout output. */\nconst STDOUT_FRAME = 1;\n/** Byte-0 tag on binary WS frames for stderr output. */\nconst STDERR_FRAME = 3;\n/**\n * Upper bound on how long we'll wait for the WebSocket to open when the\n * caller didn't supply a `timeoutMs`. Guards against a stalled TLS/WS\n * handshake leaving the promise unresolved forever. Not applied once the\n * socket has opened — a caller with no timeout has opted in to unbounded\n * command runtime, just not to unbounded connection setup.\n */\nconst HANDSHAKE_DEADLINE_MS = 30_000;\n\n/**\n * Minimal WebSocket surface this module depends on. Matches both the browser\n * `WebSocket` global and Node 22+'s built-in `WebSocket`. Extracted so tests\n * can inject a fake without pulling in `ws` or jsdom.\n */\nexport interface DirectExecWebSocket {\n binaryType: 'blob' | 'arraybuffer';\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: { code: number; reason: string }) => void) | null;\n onerror: ((event: unknown) => void) | null;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n}\n\n/**\n * Factory that opens a WebSocket to `endpoint` with the given subprotocols.\n * Defaults to the global `WebSocket` when omitted, which works on Node 22+\n * (the package's minimum) and in the browser. Tests inject a fake here.\n */\nexport type DirectExecWebSocketFactory = (endpoint: string, subprotocols: string[]) => DirectExecWebSocket;\n\n/** Lease payload returned by `POST /v1/projects/:projectId/sandbox/:sandboxId/exec-lease`. */\nexport interface ExecLease {\n jwt: string;\n wsEndpoint: string;\n subprotocol: string;\n /** ISO-8601 UTC. Null when the provider issues a JWT without an `exp` claim. */\n expiresAt: string | null;\n}\n\n/** Inputs to a direct exec invocation. Mirrors the shape of the `/exec` route body. */\nexport interface DirectExecOptions {\n command: string;\n cwd?: string;\n env?: Record<string, string>;\n /**\n * Wall-clock cap for the exec. When elapsed, we close the socket and\n * return `{timedOut: true, exitCode: 124}` matching the semantics of the\n * proxy's `/exec` route.\n */\n timeoutMs?: number;\n onStdout?: (chunk: string) => void;\n onStderr?: (chunk: string) => void;\n /** Injected for tests. Defaults to `globalThis.WebSocket`. */\n webSocketFactory?: DirectExecWebSocketFactory;\n}\n\n/**\n * Result of a direct exec. Shape matches the workspace-proxy `/exec` response\n * so the caller (`PlatformSandbox.executeCommand`) can hand it back with no\n * translation.\n *\n * `exitCode` is `null` when the socket closed without an `exit` frame AND the\n * exec did not time out (rare — usually a mid-stream network drop). Callers\n * currently coerce `null` to `1` upstream; kept nullable here to preserve the\n * distinction for future observability.\n */\nexport interface DirectExecResult {\n exitCode: number | null;\n stdout: string;\n stderr: string;\n truncated: boolean;\n timedOut: boolean;\n /**\n * WebSocket close metadata. Populated on any close (normal or transport\n * failure). `opened` distinguishes handshake failures (never opened) from\n * mid-stream drops. Callers use this for diagnostic logging; not part of\n * the CommandResult contract.\n */\n closeCode?: number;\n closeReason?: string;\n opened?: boolean;\n}\n\nconst DEFAULT_WS_FACTORY: DirectExecWebSocketFactory = (endpoint, subprotocols) => {\n const WS = (globalThis as { WebSocket?: unknown }).WebSocket as\n | (new (url: string, protocols: string[]) => DirectExecWebSocket)\n | undefined;\n if (!WS) {\n throw new Error(\n 'Direct exec requires a WebSocket implementation. Node 22+ provides one globally; on older runtimes, pass webSocketFactory explicitly.',\n );\n }\n return new WS(endpoint, subprotocols);\n};\n\n/**\n * Open the provider exec WebSocket using `lease`, run `command`, and resolve\n * with the accumulated stdout/stderr + exit code. See the module docstring\n * for the wire protocol reference.\n *\n * The client sends `stdin_close` immediately after `init_exec`, matching the\n * SDK's own one-shot exec behavior — we never stream stdin from the caller.\n */\nexport function execViaLease(lease: ExecLease, options: DirectExecOptions): Promise<DirectExecResult> {\n const factory = options.webSocketFactory ?? DEFAULT_WS_FACTORY;\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n\n return new Promise<DirectExecResult>(resolve => {\n let stdout = '';\n let stderr = '';\n let exitCode: number | null = null;\n let timedOut = false;\n let settled = false;\n let opened = false;\n let closeCode: number | undefined;\n let closeReason: string | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let handshakeTimer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = () => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n if (handshakeTimer) clearTimeout(handshakeTimer);\n // Flush any bytes still buffered in the decoders. A stream:true decode\n // holds trailing partial multi-byte sequences until the next chunk, so\n // without a flush the final char(s) of a UTF-8 stream can be dropped.\n const stdoutTail = stdoutDecoder.decode();\n if (stdoutTail) {\n stdout += stdoutTail;\n options.onStdout?.(stdoutTail);\n }\n const stderrTail = stderrDecoder.decode();\n if (stderrTail) {\n stderr += stderrTail;\n options.onStderr?.(stderrTail);\n }\n try {\n socket.close(1000, '');\n } catch {\n /* already closed */\n }\n resolve({\n exitCode,\n stdout,\n stderr,\n truncated: false,\n timedOut,\n ...(closeCode !== undefined && { closeCode }),\n ...(closeReason !== undefined && { closeReason }),\n opened,\n });\n };\n\n // Arm the timeout BEFORE we open the socket so a stalled handshake can't\n // leave the promise pending. Callers with a positive `timeoutMs` get the\n // wall-clock cap they asked for; callers without one still get a\n // connect-only deadline that clears once the socket opens.\n if (options.timeoutMs !== undefined && options.timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n // 124 matches the proxy's `/exec` semantics (coreutils `timeout`\n // exit code) so callers that switch on exitCode see the same value.\n if (exitCode === null) exitCode = 124;\n settle();\n }, options.timeoutMs);\n } else {\n handshakeTimer = setTimeout(() => {\n // Never opened → treat as a transport failure. Leave exitCode=null\n // so the caller can distinguish this from a normal exit; do not\n // set timedOut (that flag is reserved for the wall-clock case).\n if (!opened) settle();\n }, HANDSHAKE_DEADLINE_MS);\n }\n\n const socket = factory(lease.wsEndpoint, [lease.subprotocol, lease.jwt]);\n socket.binaryType = 'arraybuffer';\n\n socket.onopen = () => {\n opened = true;\n if (handshakeTimer) {\n clearTimeout(handshakeTimer);\n handshakeTimer = undefined;\n }\n const data: Record<string, unknown> = { command: options.command };\n if (options.cwd) data.cwd = options.cwd;\n if (options.env && Object.keys(options.env).length > 0) data.env = options.env;\n socket.send(JSON.stringify({ type: 'init_exec', data }));\n // We never stream stdin for one-shot exec; the SDK does this too, and\n // omitting it can leave the exec hanging waiting on EOF.\n socket.send(JSON.stringify({ type: 'stdin_close' }));\n };\n\n socket.onmessage = event => {\n const { data } = event;\n if (data instanceof ArrayBuffer) {\n handleBinaryFrame(data);\n } else if (typeof data === 'string') {\n handleTextFrame(data);\n }\n };\n\n socket.onclose = event => {\n closeCode = event.code;\n closeReason = event.reason;\n if (!opened) {\n // Never opened — surface as a failure via exitCode=null,\n // truncated=false, timedOut=false so the caller can distinguish\n // it from a normal exit-0 by inspecting `exitCode === null`.\n settle();\n return;\n }\n // Preserve any info captured before close; if the server sent an\n // `exit` frame this is a no-op because settle() already ran.\n settle();\n };\n\n socket.onerror = () => {\n if (settled) return;\n if (!opened) {\n settle();\n }\n // If we're mid-stream and the socket errors, wait for onclose to fire\n // so we settle with whatever output we did receive.\n };\n\n function handleBinaryFrame(buffer: ArrayBuffer) {\n const view = new Uint8Array(buffer);\n if (view.length <= 1) return;\n if (view[0] === STDOUT_FRAME) {\n const chunk = stdoutDecoder.decode(view.subarray(1), { stream: true });\n stdout += chunk;\n options.onStdout?.(chunk);\n } else if (view[0] === STDERR_FRAME) {\n const chunk = stderrDecoder.decode(view.subarray(1), { stream: true });\n stderr += chunk;\n options.onStderr?.(chunk);\n }\n }\n\n function handleTextFrame(text: string) {\n let frame: { type?: string; data?: { exit_code?: number } };\n try {\n frame = JSON.parse(text) as { type?: string; data?: { exit_code?: number } };\n } catch {\n return;\n }\n if (frame.type === 'exit') {\n exitCode = frame.data?.exit_code ?? 0;\n settle();\n }\n // `durable_session` frames are intentionally ignored — we don't reattach\n // or expose session names on the one-shot exec path.\n }\n });\n}\n","import type { RequestContext } from '@mastra/core/di';\nimport type {\n CommandResult,\n ExecuteCommandOptions,\n InstructionsOption,\n MastraSandboxOptions,\n ProcessInfo,\n ProviderStatus,\n SandboxCloneOptions,\n SandboxInfo,\n SpawnProcessOptions,\n} from '@mastra/core/workspace';\nimport { MastraSandbox, ProcessHandle, SandboxNotReadyError, SandboxProcessManager } from '@mastra/core/workspace';\nimport type { PlatformClientOptions } from './client.js';\nimport { PlatformApiError, PlatformClient } from './client.js';\nimport type { DirectExecWebSocketFactory, ExecLease } from './direct-exec.js';\nimport { execViaLease } from './direct-exec.js';\n\nexport type PlatformSandboxNetworkIsolation = 'ISOLATED' | 'PRIVATE';\n\nexport interface PlatformSandboxOptions extends Omit<MastraSandboxOptions, 'processes'>, PlatformClientOptions {\n id?: string;\n environmentId?: string;\n sandboxId?: string;\n idleTimeoutMinutes?: number;\n networkIsolation?: PlatformSandboxNetworkIsolation;\n env?: Record<string, string>;\n timeout?: number;\n instructions?: InstructionsOption;\n /**\n * Injected WebSocket factory used by the direct-exec code path. Defaults to\n * the global `WebSocket` (available on Node 22+, this package's minimum) and\n * only exists so tests can drive the exec state machine deterministically\n * without a real network socket.\n */\n webSocketFactory?: DirectExecWebSocketFactory;\n}\n\ninterface ExecLeaseResponse {\n provider: string;\n sandboxId: string;\n providerResourceId: string;\n jwt: string;\n wsEndpoint: string;\n subprotocol: string;\n expiresAt: string | null;\n}\n\n/**\n * How long before a lease's stated `expiresAt` we should treat it as\n * expired. Avoids a race where the JWT is valid at cache-hit time but the\n * server rejects it by the time the WebSocket handshake completes.\n */\nconst LEASE_REFRESH_MARGIN_MS = 60_000;\n\ninterface CreateSandboxResponse {\n id: string;\n providerResourceId?: string | null;\n status?: string;\n createdAt?: string;\n destroyedAt?: string | null;\n}\n\n/** Max attempts for `POST /sandbox` when the proxy returns transient 5xx errors. */\nconst CREATE_MAX_ATTEMPTS = 3;\n/** Base delay between create retries; multiplied by the attempt number. */\nconst CREATE_RETRY_BASE_DELAY_MS = 2_000;\n\n/**\n * Diagnostic error thrown when the direct-exec WebSocket transport fails\n * twice in a row (opening handshake refused or socket closed mid-stream\n * without an `exit` frame). Distinguishes \"the sandbox transport is broken\"\n * from \"your command failed\" so callers can decide whether to retry at a\n * higher level (e.g. reprovision the sandbox) or surface the error.\n *\n * `opened` is `true` when the WebSocket completed its handshake at least\n * once before closing; `false` when Railway refused the upgrade outright.\n */\nexport class SandboxExecTransportError extends Error {\n readonly sandboxId: string | undefined;\n readonly command: string;\n readonly attempts: number;\n readonly opened: boolean;\n readonly closeCode: number | undefined;\n readonly closeReason: string | undefined;\n readonly wsEndpoint: string;\n\n constructor(\n message: string,\n diagnostics: {\n sandboxId?: string;\n command: string;\n attempts: number;\n opened: boolean;\n closeCode?: number;\n closeReason?: string;\n wsEndpoint: string;\n },\n ) {\n super(message);\n this.name = 'SandboxExecTransportError';\n this.sandboxId = diagnostics.sandboxId;\n this.command = diagnostics.command;\n this.attempts = diagnostics.attempts;\n this.opened = diagnostics.opened;\n this.closeCode = diagnostics.closeCode;\n this.closeReason = diagnostics.closeReason;\n this.wsEndpoint = diagnostics.wsEndpoint;\n }\n}\n\n/**\n * Thrown when `/exec-lease` returns 410 Gone — the sandbox has been destroyed\n * (Railway destroy, quota reclamation, etc.). The client cannot recover from\n * this on its own because it does not own the binding store; only the fleet\n * layer can clear the stale sandbox id and provision a fresh one. Callers\n * (typically `SandboxFleet`) must catch this and reprovision-and-replay.\n *\n * When this is thrown the cached `_lease` and `_sandboxId` on the sandbox\n * instance are cleared, so the next `ensureRunning()` on a reused instance\n * will re-provision cleanly.\n */\nexport class SandboxDestroyedError extends Error {\n readonly sandboxId: string | undefined;\n readonly command: string;\n readonly attempts: number;\n\n constructor(message: string, diagnostics: { sandboxId?: string; command: string; attempts: number }) {\n super(message);\n this.name = 'SandboxDestroyedError';\n this.sandboxId = diagnostics.sandboxId;\n this.command = diagnostics.command;\n this.attempts = diagnostics.attempts;\n }\n}\n\n/**\n * Compose a shell command line from a `command` string and optional `args`.\n *\n * IMPORTANT: `command` is treated as a **shell string** and passed to the\n * remote shell verbatim so callers can use pipes, redirects, and chaining\n * (`ls -la | grep foo`). This matches the contract of {@link MastraSandbox}\n * and the local sandbox implementation. `args` are always shell-quoted so\n * they cannot inject syntax.\n *\n * Callers MUST NOT pass untrusted input as `command`. Untrusted values must\n * be passed via `args`, where they are safely quoted. Passing untrusted\n * input as `command` allows arbitrary shell syntax execution on the remote\n * sandbox.\n */\nfunction buildCommand(command: string, args?: string[]): string {\n return args?.length ? `${command} ${args.map(shellQuote).join(' ')}` : command;\n}\n\nfunction shellQuote(arg: string): string {\n if (/^[a-zA-Z0-9._\\-/=:@]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, `'\\\\''`)}'`;\n}\n\nclass PlatformProcessHandle extends ProcessHandle {\n readonly pid: string;\n private readonly resultPromise: Promise<CommandResult>;\n private exitCodeValue: number | undefined;\n\n constructor(pid: string, resultPromise: Promise<CommandResult>, options?: SpawnProcessOptions) {\n super(options);\n this.pid = pid;\n this.resultPromise = resultPromise.then(result => {\n this.exitCodeValue = result.exitCode;\n if (result.stdout) this.emitStdout(result.stdout);\n if (result.stderr) this.emitStderr(result.stderr);\n return result;\n });\n }\n\n get exitCode(): number | undefined {\n return this.exitCodeValue;\n }\n\n async wait(): Promise<CommandResult> {\n return this.resultPromise;\n }\n\n async kill(): Promise<boolean> {\n // The workspace proxy has no cancel-exec endpoint; each `executeCommand`\n // is a synchronous round-trip that has already completed (or timed out)\n // by the time a handle exists to kill. Making this explicit avoids\n // callers silently believing they cancelled a still-running process.\n throw new Error('Platform sandbox command execution does not support killing individual processes');\n }\n\n async sendStdin(): Promise<void> {\n throw new Error('Platform sandbox command execution does not support stdin');\n }\n}\n\nclass PlatformProcessManager extends SandboxProcessManager<PlatformSandbox> {\n private spawnCounter = 0;\n\n /**\n * Spawn a process on the remote sandbox.\n *\n * `command` is interpreted as a shell string by the remote shell, matching\n * the {@link MastraSandbox} contract. See {@link PlatformSandbox.executeCommand}\n * for the untrusted-input caveat: never pass untrusted values as `command`.\n */\n async spawn(command: string, options: SpawnProcessOptions = {}): Promise<ProcessHandle> {\n const pid = `platform-proc-${Date.now().toString(36)}-${(this.spawnCounter++).toString(36)}`;\n const resultPromise = this.sandbox.executeCommand(command, undefined, options);\n const handle = new PlatformProcessHandle(pid, resultPromise, options);\n this._tracked.set(handle.pid, handle);\n return handle;\n }\n\n async list(): Promise<ProcessInfo[]> {\n return Array.from(this._tracked.values()).map(handle => ({\n pid: handle.pid,\n command: handle.command,\n running: handle.exitCode === undefined,\n ...(handle.exitCode !== undefined && { exitCode: handle.exitCode }),\n }));\n }\n}\n\nexport class PlatformSandbox extends MastraSandbox {\n readonly id: string;\n readonly name = 'PlatformSandbox';\n readonly provider = 'platform';\n status: ProviderStatus = 'pending';\n declare readonly processes: PlatformProcessManager;\n\n private readonly _client: PlatformClient;\n private readonly _environmentId: string;\n private _sandboxId?: string;\n private readonly _idleTimeoutMinutes?: number;\n private readonly _networkIsolation?: PlatformSandboxNetworkIsolation;\n private readonly _env: Record<string, string>;\n private readonly _timeout?: number;\n private readonly _instructionsOverride?: InstructionsOption;\n private _createdAt: Date | null = null;\n private readonly _webSocketFactory?: DirectExecWebSocketFactory;\n /**\n * Cached exec lease for this sandbox. `null` before the first exec and\n * after {@link destroy}. Refreshed when `expiresAt - LEASE_REFRESH_MARGIN_MS < now`\n * (see {@link _ensureLease}); a lease without a disclosed `expiresAt`\n * is refreshed on every call.\n */\n private _lease: (ExecLease & { expiresAtMs: number | null }) | null = null;\n /**\n * In-flight mint request; concurrent `_ensureLease` callers on a cold or\n * near-expiry cache all await this single promise so we don't burn N\n * `POST /exec-lease` round-trips when the sandbox is doing N parallel execs.\n * Cleared (regardless of success or failure) when the request settles.\n */\n private _leaseInFlight: Promise<ExecLease & { expiresAtMs: number | null }> | null = null;\n\n constructor(options: PlatformSandboxOptions = {}) {\n super({ ...options, name: 'PlatformSandbox', processes: new PlatformProcessManager() });\n this.id = options.id ?? this.generateId();\n this._client = new PlatformClient(options);\n this._environmentId = options.environmentId ?? process.env.MASTRA_ENVIRONMENT_ID ?? '';\n if (!this._environmentId && !options.sandboxId) throw new Error('environmentId is required');\n this._sandboxId = options.sandboxId;\n this._idleTimeoutMinutes = options.idleTimeoutMinutes;\n this._networkIsolation = options.networkIsolation;\n this._env = options.env ?? {};\n this._timeout = options.timeout;\n this._instructionsOverride = options.instructions;\n this._webSocketFactory = options.webSocketFactory;\n }\n\n private generateId(): string {\n return `platform-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n /**\n * Construct a sibling {@link PlatformSandbox} that inherits this sandbox's\n * credentials and defaults (access token, project, environment, network\n * isolation, timeout, instructions, env, idle timeout) with per-instance\n * overrides from `options`.\n *\n * Performs no I/O and does not require this sandbox to be started — the\n * returned sandbox is not started and provisions (or reattaches, when\n * `sandboxId` is set) on its own `start()`. Use it when one configured\n * sandbox acts as the template for a fleet of independent sandboxes\n * (e.g. one per project).\n */\n clone(options: SandboxCloneOptions = {}): PlatformSandbox {\n // The proxy hashes `body.id` on POST /sandbox to look up a prior\n // checkpoint. A stable `checkpointName` is only useful if it round-trips\n // to `body.id`, so route it through the sandbox id when the caller\n // didn't pick one explicitly. Without this, every clone gets a random\n // id and no boot ever hits its captured checkpoint (see\n // issue-platform-sandbox-clone-drops-checkpoint-name.md).\n const id = options.id ?? options.checkpointName;\n return new PlatformSandbox({\n ...(id !== undefined && { id }),\n accessToken: this._client.accessToken,\n projectId: this._client.projectId,\n fetch: this._client.fetch,\n environmentId: this._environmentId,\n ...(options.sandboxId !== undefined && { sandboxId: options.sandboxId }),\n idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,\n ...(this._networkIsolation !== undefined && { networkIsolation: this._networkIsolation }),\n env: options.env ?? this._env,\n ...(this._timeout !== undefined && { timeout: this._timeout }),\n ...(this._instructionsOverride !== undefined && { instructions: this._instructionsOverride }),\n ...(this._webSocketFactory !== undefined && { webSocketFactory: this._webSocketFactory }),\n });\n }\n\n async start(): Promise<void> {\n if (this._sandboxId) {\n try {\n const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);\n const json = (await response.json()) as CreateSandboxResponse;\n // A destroyed record (idle GC, manual delete) is not reattachable —\n // treat it like a missing sandbox so we fall through to a fresh\n // provision instead of pointing exec at a dead resource.\n if (!json.destroyedAt) {\n this._createdAt = json.createdAt ? new Date(json.createdAt) : new Date();\n return;\n }\n this._sandboxId = undefined;\n } catch (error) {\n if (!(error instanceof PlatformApiError) || error.status !== 404) throw error;\n this._sandboxId = undefined;\n }\n }\n\n if (!this._environmentId) throw new Error('environmentId is required');\n\n const body = JSON.stringify({\n // Sent so the platform can associate the provisioned resource with a\n // caller-stable identifier (used for opt-in checkpoint recovery). The\n // platform treats it as an advisory key: unknown values fall through\n // to a fresh sandbox, matching pre-existing behavior.\n id: this.id,\n environmentId: this._environmentId,\n idleTimeoutMinutes: this._idleTimeoutMinutes,\n networkIsolation: this._networkIsolation,\n env: this._env,\n });\n // Provisioning is observed to fail intermittently with proxy 500s while\n // the provider is under load. A create either succeeds (201) or fails\n // without allocating a caller-visible resource, so retrying transient\n // 5xx responses with a short backoff is safe and keeps a single flaky\n // window from killing the caller's whole workflow.\n let response: Response | undefined;\n for (let attempt = 1; ; attempt++) {\n try {\n response = await this._client.request('/sandbox', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body,\n });\n break;\n } catch (error) {\n const transient = error instanceof PlatformApiError && error.status >= 500;\n if (!transient || attempt >= CREATE_MAX_ATTEMPTS) throw error;\n await new Promise(resolve => setTimeout(resolve, CREATE_RETRY_BASE_DELAY_MS * attempt));\n }\n }\n const json = (await response.json()) as CreateSandboxResponse;\n this._sandboxId = json.id;\n this._createdAt = json.createdAt ? new Date(json.createdAt) : new Date();\n }\n\n async stop(): Promise<void> {\n await this.destroy();\n }\n\n async destroy(): Promise<void> {\n if (!this._sandboxId) return;\n await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`, { method: 'DELETE' });\n // Clear local state so a subsequent start() creates a fresh remote sandbox\n // instead of taking the reattach branch and pointing exec at a deleted resource.\n this._sandboxId = undefined;\n this._createdAt = null;\n // Drop the exec lease with the sandbox — the JWT is tied to the provider\n // instance id and would be rejected against a fresh one.\n this._lease = null;\n }\n\n /**\n * Execute a command on the remote sandbox.\n *\n * `command` is a **shell string**: it is concatenated verbatim into the\n * command line sent to the remote shell, which lets callers use pipes,\n * redirects, and chaining (`ls -la | grep foo`). This matches the contract\n * of {@link MastraSandbox} and the local sandbox implementation.\n *\n * `args`, when provided, are always shell-quoted so they cannot inject\n * additional shell syntax.\n *\n * Security: callers MUST NOT pass untrusted input as `command`. If any part\n * of the invocation is derived from an untrusted source, pass it through\n * `args` (which is safely quoted) or shell-quote it yourself before\n * inclusion. Untrusted `command` values allow arbitrary shell syntax\n * execution on the remote sandbox.\n */\n async executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<CommandResult> {\n await this.ensureRunning();\n if (!this._sandboxId) throw new SandboxNotReadyError(this.id);\n\n const started = Date.now();\n const fullCommand = buildCommand(command, args);\n // Nullish check so an explicit `timeout: 0` still overrides the instance\n // default. `_runDirectExec` omits `timeoutMs` from the exec payload when\n // the value is 0, which disables the client-side timer entirely.\n const effectiveTimeout = options?.timeout ?? this._timeout;\n\n // Direct-exec (WebSocket straight to Railway's tcp-proxy) is the only\n // data plane. `_runDirectExec` handles single-shot transport retry and\n // throws typed errors on unrecoverable failure: `SandboxDestroyedError`\n // when `/exec-lease` returns 410 (fleet must reprovision),\n // `SandboxExecTransportError` when the WebSocket transport fails twice\n // against a live sandbox, `PlatformApiError` for other `/exec-lease`\n // errors (404/500/501). See ./direct-exec.ts and\n // `docs/factory/direct-sandbox-connection.md` in the Platform repo.\n const result = await this._runDirectExec(fullCommand, effectiveTimeout, options);\n // `_runDirectExec` throws on transport failure (see its jsdoc), so a\n // `null` exitCode here can only mean `timedOut: true` — the sandbox\n // never got to send an exit frame because we cut the command short.\n // Use 124 for that (the conventional timeout exit code). We are NOT\n // coercing transport-failure nulls to fake exit codes — those throw.\n const exitCode = result.exitCode ?? 124;\n return {\n success: exitCode === 0,\n exitCode,\n stdout: result.stdout,\n stderr: result.stderr,\n timedOut: result.timedOut,\n command: fullCommand,\n executionTimeMs: Date.now() - started,\n };\n }\n\n /**\n * Run a single exec against the direct-exec transport, with one in-flight\n * retry on WebSocket transport failure (socket closed without an `exit`\n * frame and the exec did not time out). The retry mints a fresh lease\n * — the failure could be a stale JWT — and reopens a new WebSocket.\n *\n * Error taxonomy:\n * - **410 on `/exec-lease`** (either attempt) → the sandbox is gone.\n * Nulls the cached `_lease` and `_sandboxId` and throws\n * {@link SandboxDestroyedError}. Callers (typically `SandboxFleet`) must\n * catch this, clear the stale binding, and reprovision + replay.\n * - **Persistent transport failure** (both WS attempts close without an\n * `exit` frame against a live sandbox) → {@link SandboxExecTransportError}\n * with WebSocket close diagnostics.\n * - **Other `PlatformApiError`s** (404/500/501) propagate directly.\n * - **Real command result** (exit code from Railway's exit frame, or\n * `timedOut: true`) returns normally.\n *\n * Returns a result with a real `exitCode` OR `timedOut: true`. Never\n * returns `{ exitCode: null, timedOut: false }` — that case throws.\n */\n private async _runDirectExec(\n fullCommand: string,\n effectiveTimeout: number | undefined,\n options: ExecuteCommandOptions | undefined,\n ): Promise<{ exitCode: number | null; stdout: string; stderr: string; timedOut: boolean }> {\n // Filter undefined values out of the env overlay so we match the\n // Record<string, string> shape execViaLease expects. `ExecuteCommandOptions.env`\n // is NodeJS.ProcessEnv (string | undefined).\n const filteredEnv = options?.env\n ? Object.fromEntries(\n Object.entries(options.env).filter((entry): entry is [string, string] => entry[1] !== undefined),\n )\n : undefined;\n\n let lastResult: Awaited<ReturnType<typeof execViaLease>> | undefined;\n let lastLease: (ExecLease & { expiresAtMs: number | null }) | undefined;\n let attemptsMade = 0;\n // Two attempts: initial + one retry. On the second attempt we drop the\n // cached lease so we don't reuse a JWT that may itself be the cause of\n // the transport failure — but only if the cache still holds the same\n // lease we just failed against. A concurrent exec sharing this instance\n // may have already cached a fresh, unrelated lease in between, and we\n // must not discard that.\n for (let attempt = 0; attempt < 2; attempt++) {\n if (attempt > 0 && lastLease && this._lease === lastLease) this._lease = null;\n let lease: ExecLease & { expiresAtMs: number | null };\n try {\n lease = await this._ensureLease();\n } catch (error) {\n // 410 → sandbox has been destroyed. Clear all cached state so a\n // reused instance re-provisions cleanly, then hand off to the fleet\n // layer via a typed error. Other PlatformApiErrors (404/500/501)\n // propagate as-is — those are configuration or platform errors, not\n // a \"reprovision me\" signal.\n if (error instanceof PlatformApiError && error.status === 410) {\n this._lease = null;\n const priorSandboxId = this._sandboxId;\n this._sandboxId = undefined;\n throw new SandboxDestroyedError(\n `Sandbox ${priorSandboxId ?? '(unknown)'} was destroyed; /exec-lease returned 410`,\n {\n ...(priorSandboxId && { sandboxId: priorSandboxId }),\n command: fullCommand,\n attempts: attempt + 1,\n },\n );\n }\n throw error;\n }\n lastLease = lease;\n attemptsMade = attempt + 1;\n const result = await execViaLease(lease, {\n command: fullCommand,\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(filteredEnv !== undefined && { env: filteredEnv }),\n ...(effectiveTimeout != null && effectiveTimeout > 0 && { timeoutMs: effectiveTimeout }),\n ...(this._webSocketFactory && { webSocketFactory: this._webSocketFactory }),\n });\n lastResult = result;\n // `null` exitCode with `timedOut: false` means the socket closed\n // without an exit frame — a transport failure (handshake stalled,\n // mid-stream drop, expired token). Any other outcome (real exit code\n // or timed-out) is a valid result and we return it.\n if (result.exitCode !== null || result.timedOut) return result;\n }\n\n // Both attempts failed at the transport layer against a live sandbox.\n // Surface a loud, typed error with close diagnostics so callers can\n // distinguish \"your command failed\" from \"the sandbox transport is\n // broken.\"\n const result = lastResult!;\n const lease = lastLease!;\n // The lease from the failed second attempt is still cached; drop it so\n // the next `executeCommand` doesn't waste its first attempt on the same\n // implicated JWT before minting fresh. Identity-check first so a\n // concurrent exec that has already cached a fresh, unrelated lease\n // isn't collateral-damaged.\n if (this._lease === lease) this._lease = null;\n throw new SandboxExecTransportError(\n `Direct-exec transport failed for sandbox ${this._sandboxId ?? '(unknown)'} after ${attemptsMade} attempt(s)` +\n (result.closeCode !== undefined\n ? ` (close ${result.closeCode}${result.closeReason ? ` ${result.closeReason}` : ''})`\n : ''),\n {\n ...(this._sandboxId && { sandboxId: this._sandboxId }),\n command: fullCommand,\n attempts: attemptsMade,\n opened: result.opened ?? false,\n ...(result.closeCode !== undefined && { closeCode: result.closeCode }),\n ...(result.closeReason !== undefined && { closeReason: result.closeReason }),\n wsEndpoint: lease.wsEndpoint,\n },\n );\n }\n\n /**\n * Return a cached exec lease, minting a fresh one when the cache is empty\n * or the JWT is within {@link LEASE_REFRESH_MARGIN_MS} of `expiresAt`.\n *\n * Callers are expected to be on the \"sandbox is running\" path; we don't\n * re-check `_sandboxId` here because `executeCommand` already gated on it.\n */\n private async _ensureLease(): Promise<ExecLease & { expiresAtMs: number | null }> {\n const now = Date.now();\n // Cache hit only when we know the expiry AND we're comfortably before it.\n // A null `expiresAtMs` means the provider didn't disclose a TTL — treat\n // that as \"refresh every call\" rather than \"cache forever\", so a token\n // that turns out to be short-lived can't wedge the sandbox until restart.\n if (this._lease && this._lease.expiresAtMs !== null && this._lease.expiresAtMs - LEASE_REFRESH_MARGIN_MS > now) {\n return this._lease;\n }\n // Coalesce concurrent mints on a cold/expired cache.\n if (this._leaseInFlight) return this._leaseInFlight;\n if (!this._sandboxId) throw new SandboxNotReadyError(this.id);\n const sandboxId = this._sandboxId;\n const inFlight = (async () => {\n const response = await this._client.request(`/sandbox/${encodeURIComponent(sandboxId)}/exec-lease`, {\n method: 'POST',\n });\n const json = (await response.json()) as ExecLeaseResponse;\n const expiresAtMs = json.expiresAt ? Date.parse(json.expiresAt) : null;\n const lease = {\n jwt: json.jwt,\n wsEndpoint: json.wsEndpoint,\n subprotocol: json.subprotocol,\n expiresAt: json.expiresAt,\n // Guard against `Date.parse` returning NaN for malformed values by\n // treating them as \"no expiry known\", which forces a mint every call\n // rather than silently caching a broken lease forever.\n expiresAtMs: expiresAtMs !== null && !Number.isNaN(expiresAtMs) ? expiresAtMs : null,\n };\n this._lease = lease;\n return lease;\n })();\n this._leaseInFlight = inFlight;\n try {\n return await inFlight;\n } finally {\n // Clear on both success and failure so a failed mint doesn't wedge\n // future callers into awaiting the same rejected promise forever.\n if (this._leaseInFlight === inFlight) this._leaseInFlight = null;\n }\n }\n\n async getInfo(): Promise<SandboxInfo> {\n if (!this._sandboxId) {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this._createdAt ?? new Date(),\n };\n }\n const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);\n const json = (await response.json()) as CreateSandboxResponse;\n return {\n id: json.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: json.createdAt ? new Date(json.createdAt) : (this._createdAt ?? new Date()),\n metadata: {\n // The platform assigns its own sandbox id on create (the advisory id\n // sent in the POST body is not honored). Expose it so callers that\n // persist a reattach id (e.g. the Factory sandbox fleet, which reads\n // `metadata.sandboxId`) store the id the proxy actually recognizes\n // instead of the locally generated construction id.\n sandboxId: json.id,\n providerResourceId: json.providerResourceId ?? undefined,\n platformStatus: json.status,\n },\n };\n }\n\n getInstructions(opts?: { requestContext?: RequestContext }): string {\n const defaultInstructions = `Platform sandbox${this._sandboxId ? ` ${this._sandboxId}` : ''}. Execute commands with the sandbox command APIs.`;\n if (typeof this._instructionsOverride === 'function') {\n return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });\n }\n if (typeof this._instructionsOverride === 'string') return this._instructionsOverride;\n return defaultInstructions;\n }\n}\n","import type { FilesystemProvider, SandboxProvider } from '@mastra/core/editor';\nimport type { PlatformFilesystemOptions } from './filesystem.js';\nimport { PlatformFilesystem } from './filesystem.js';\nimport type { PlatformSandboxOptions } from './sandbox.js';\nimport { PlatformSandbox } from './sandbox.js';\n\nexport const platformSandboxProvider: SandboxProvider<PlatformSandboxOptions> = {\n id: 'platform',\n name: 'Mastra Platform Sandbox',\n description: 'Environment-scoped sandbox execution through Mastra Platform workspace proxy',\n configSchema: {\n type: 'object',\n properties: {\n accessToken: {\n type: 'string',\n description: 'Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)',\n },\n projectId: { type: 'string', description: 'Platform project ID (falls back to MASTRA_PROJECT_ID)' },\n environmentId: { type: 'string', description: 'Platform environment ID (falls back to MASTRA_ENVIRONMENT_ID)' },\n sandboxId: { type: 'string', description: 'Reattach to an existing Platform sandbox by ID' },\n idleTimeoutMinutes: { type: 'number', description: 'Minutes before the sandbox can be destroyed while idle' },\n networkIsolation: {\n type: 'string',\n description: 'Network isolation mode',\n enum: ['ISOLATED', 'PRIVATE'],\n default: 'ISOLATED',\n },\n env: { type: 'object', description: 'Environment variables', additionalProperties: { type: 'string' } },\n timeout: { type: 'number', description: 'Default command timeout in ms' },\n },\n },\n createSandbox: config => new PlatformSandbox(config),\n};\n\nexport const platformFilesystemProvider: FilesystemProvider<PlatformFilesystemOptions> = {\n id: 'platform',\n name: 'Mastra Platform Filesystem',\n description: 'Bucket-backed filesystem access through Mastra Platform workspace proxy',\n configSchema: {\n type: 'object',\n properties: {\n accessToken: {\n type: 'string',\n description: 'Mastra Platform secret key (falls back to MASTRA_PLATFORM_SECRET_KEY)',\n },\n projectId: { type: 'string', description: 'Platform project ID (falls back to MASTRA_PROJECT_ID)' },\n bucketName: {\n type: 'string',\n description: 'Platform workspace bucket name (falls back to MASTRA_PLATFORM_BUCKET_NAME)',\n },\n readOnly: { type: 'boolean', description: 'Mount as read-only', default: false },\n },\n },\n createFilesystem: config => new PlatformFilesystem(config),\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/client.ts","../src/filesystem.ts","../src/direct-exec.ts","../src/sandbox.ts","../src/provider.ts"],"sourcesContent":["export interface PlatformClientOptions {\n accessToken?: string;\n projectId?: string;\n fetch?: typeof fetch;\n}\n\nexport interface PlatformRequestOptions extends RequestInit {\n query?: Record<string, string | number | boolean | undefined>;\n}\n\nconst DEFAULT_PROXY_URL = 'https://workspaces.mastra.ai';\n\n/**\n * Default per-request timeout for calls to the workspace proxy. Applied only\n * when the caller doesn't already pass an `AbortSignal`. Long-running routes\n * (e.g. `POST /sandbox/:id/exec`) pass their own longer signal.\n */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 60_000;\n\nexport function requireOption(value: string | undefined, name: string): string {\n if (!value) throw new Error(`${name} is required`);\n return value;\n}\n\nexport function resolvePlatformOptions(options: PlatformClientOptions) {\n return {\n accessToken: requireOption(options.accessToken ?? process.env.MASTRA_PLATFORM_ACCESS_TOKEN, 'accessToken'),\n projectId: requireOption(options.projectId ?? process.env.MASTRA_PROJECT_ID, 'projectId'),\n proxyUrl: (process.env.MASTRA_WORKSPACE_PROXY_URL ?? DEFAULT_PROXY_URL).replace(/\\/$/, ''),\n fetch: options.fetch ?? fetch,\n };\n}\n\n/**\n * Structured error shape returned by the workspace proxy. All routes emit\n * `{ error: { message, type } }` on failure — see servers/workspace-proxy in\n * the Platform repo. Kept as a wire-level type so callers can switch on\n * `error.code` without re-parsing `error.body`.\n */\nexport interface PlatformProxyError {\n message: string;\n /** Machine-readable error kind, e.g. `not_found`, `invalid_request`, `authentication_error`. */\n type: string;\n}\n\nfunction parseProxyError(body: string): PlatformProxyError | undefined {\n if (!body) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const err = (parsed as { error?: unknown }).error;\n if (typeof err !== 'object' || err === null) return undefined;\n const { message, type } = err as { message?: unknown; type?: unknown };\n if (typeof message !== 'string' || typeof type !== 'string') return undefined;\n return { message, type };\n}\n\nexport class PlatformApiError extends Error {\n readonly status: number;\n readonly body: string;\n /** Machine-readable proxy error kind (e.g. `not_found`), when the response body matches `{ error: { message, type } }`. */\n readonly code: string | undefined;\n /** Human-readable proxy error message, when the response body matches `{ error: { message, type } }`. */\n readonly proxyMessage: string | undefined;\n\n constructor(status: number, body: string) {\n const parsed = parseProxyError(body);\n const summary = parsed ? `${parsed.type}: ${parsed.message}` : body;\n super(`Platform proxy request failed with ${status}${summary ? `: ${summary}` : ''}`);\n this.name = 'PlatformApiError';\n this.status = status;\n this.body = body;\n this.code = parsed?.type;\n this.proxyMessage = parsed?.message;\n }\n}\n\nexport class PlatformClient {\n readonly accessToken: string;\n readonly projectId: string;\n readonly proxyUrl: string;\n readonly fetch: typeof fetch;\n\n constructor(options: PlatformClientOptions) {\n const resolved = resolvePlatformOptions(options);\n this.accessToken = resolved.accessToken;\n this.projectId = resolved.projectId;\n this.proxyUrl = resolved.proxyUrl;\n this.fetch = resolved.fetch;\n }\n\n async request(path: string, options: PlatformRequestOptions = {}): Promise<Response> {\n const url = new URL(`${this.proxyUrl}/v1/projects/${encodeURIComponent(this.projectId)}${path}`);\n for (const [key, value] of Object.entries(options.query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n\n const headers = new Headers(options.headers);\n headers.set('authorization', `Bearer ${this.accessToken}`);\n\n // Strip our helper-only field so the underlying fetch sees a valid RequestInit.\n const { query: _query, ...fetchOptions } = options;\n // Apply a default timeout only when the caller didn't already supply an\n // AbortSignal — long-running routes (exec) provide their own longer signal.\n const signal = fetchOptions.signal ?? AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS);\n const response = await this.fetch(url, { ...fetchOptions, headers, signal });\n if (!response.ok) {\n throw new PlatformApiError(response.status, await response.text());\n }\n return response;\n }\n}\n","import { Buffer } from 'node:buffer';\nimport nodePath from 'node:path';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type {\n CopyOptions,\n FileContent,\n FileEntry,\n FileStat,\n FilesystemIcon,\n FilesystemInfo,\n InstructionsOption,\n ListOptions,\n MastraFilesystemOptions,\n ProviderStatus,\n ReadOptions,\n RemoveOptions,\n WriteOptions,\n} from '@mastra/core/workspace';\nimport { FileExistsError, FileNotFoundError, MastraFilesystem, WorkspaceReadOnlyError } from '@mastra/core/workspace';\nimport type { PlatformClientOptions } from './client.js';\nimport { PlatformClient } from './client.js';\n\ninterface ProxyListResponse {\n contents?: Array<{ key?: string; size?: number; lastModified?: string }>;\n commonPrefixes?: string[];\n}\n\nexport interface PlatformFilesystemOptions extends PlatformClientOptions, MastraFilesystemOptions {\n id?: string;\n bucketName?: string;\n readOnly?: boolean;\n displayName?: string;\n icon?: FilesystemIcon;\n description?: string;\n instructions?: InstructionsOption;\n}\n\nfunction normalizePath(input: string): string {\n if (!input || input === '.') return '/';\n let normalized = input.startsWith('/') ? input : `/${input}`;\n normalized = nodePath.posix.normalize(normalized);\n return normalized === '.' ? '/' : normalized;\n}\n\nfunction keyFromPath(path: string): string {\n const normalized = normalizePath(path);\n return normalized === '/' ? '' : normalized.slice(1);\n}\n\n/**\n * Encode each `/`-delimited segment of an object key with `encodeURIComponent`\n * so reserved URL characters (`?`, `#`, `%`, `&`, `+`, spaces, etc.) are\n * treated as part of the key instead of URL syntax. Kept segment-aware so\n * `/` continues to act as a path separator on the wire.\n */\nfunction encodeKeyPath(key: string): string {\n return key.split('/').map(encodeURIComponent).join('/');\n}\n\nfunction nameFromPath(path: string): string {\n const normalized = normalizePath(path);\n if (normalized === '/') return '';\n return normalized.slice(normalized.lastIndexOf('/') + 1);\n}\n\nfunction contentToBody(content: FileContent): string | Buffer {\n if (typeof content === 'string') return content;\n return Buffer.from(content);\n}\n\nfunction headerDate(headers: Headers, name: string): Date {\n const value = headers.get(name);\n return value ? new Date(value) : new Date(0);\n}\n\nfunction headerSize(headers: Headers): number {\n const value = headers.get('content-length');\n return value ? Number(value) : 0;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'status' in error && error.status === 404;\n}\n\nexport class PlatformFilesystem extends MastraFilesystem {\n readonly id: string;\n readonly name = 'PlatformFilesystem';\n readonly provider = 'platform';\n readonly readOnly?: boolean;\n readonly displayName?: string;\n readonly icon: FilesystemIcon;\n readonly description?: string;\n status: ProviderStatus = 'pending';\n\n private readonly _client: PlatformClient;\n private readonly _bucketName: string;\n private readonly _instructionsOverride?: InstructionsOption;\n\n constructor(options: PlatformFilesystemOptions = {}) {\n super({ ...options, name: 'PlatformFilesystem' });\n this.id = options.id ?? this.generateId();\n this._bucketName = options.bucketName ?? process.env.MASTRA_PLATFORM_BUCKET_NAME ?? '';\n if (!this._bucketName) throw new Error('bucketName is required');\n this.readOnly = options.readOnly;\n this.displayName = options.displayName;\n this.icon = options.icon ?? 'cloud';\n this.description = options.description;\n this._instructionsOverride = options.instructions;\n this._client = new PlatformClient(options);\n }\n\n private generateId(): string {\n return `platform-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n async readFile(path: string, options?: ReadOptions): Promise<string | Buffer> {\n await this.ensureReady();\n let response: Response;\n try {\n response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,\n );\n } catch (error) {\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n return options?.encoding ? buffer.toString(options.encoding) : buffer;\n }\n\n async writeFile(path: string, content: FileContent, options?: WriteOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('writeFile');\n const headers: Record<string, string> = {};\n if (options?.mimeType) headers['content-type'] = options.mimeType;\n if (options?.overwrite === false) headers['if-none-match'] = '*';\n try {\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'PUT',\n headers,\n body: contentToBody(content),\n });\n } catch (error) {\n if (typeof error === 'object' && error !== null && 'status' in error && error.status === 412) {\n throw new FileExistsError(path);\n }\n throw error;\n }\n }\n\n /**\n * Append bytes to a file.\n *\n * **Not atomic.** Object storage behind the workspace proxy has no native\n * append or compare-and-swap primitive, so this implementation is a\n * read-modify-write: it reads the current contents, concatenates the new\n * bytes, and PUTs the whole object back. Concurrent `appendFile` calls to\n * the same path can overwrite each other's writes (\"last write wins\").\n * Use `writeFile` with distinct keys for concurrent writers.\n */\n async appendFile(path: string, content: FileContent): Promise<void> {\n const existing = (await this.exists(path)) ? await this.readFile(path) : Buffer.alloc(0);\n await this.writeFile(\n path,\n Buffer.concat([Buffer.isBuffer(existing) ? existing : Buffer.from(existing), Buffer.from(content)]),\n );\n }\n\n async deleteFile(path: string, options?: RemoveOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('deleteFile');\n try {\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'DELETE',\n query: { recursive: options?.recursive },\n });\n } catch (error) {\n if (isNotFound(error) && options?.force) return;\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n }\n\n async copyFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('copyFile');\n // The workspace proxy's `?op=copy` route always overwrites the destination;\n // there's no conditional wire field to prevent it. Reject the option\n // explicitly instead of silently overwriting when the caller asked us not to.\n if (options?.overwrite === false) {\n throw new Error('PlatformFilesystem.copyFile does not support overwrite: false — the proxy always overwrites.');\n }\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {\n method: 'POST',\n query: { op: 'copy' },\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ destination: keyFromPath(dest) }),\n });\n }\n\n async moveFile(src: string, dest: string, options?: CopyOptions): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('moveFile');\n // Same rationale as copyFile: `?op=rename` always overwrites.\n if (options?.overwrite === false) {\n throw new Error('PlatformFilesystem.moveFile does not support overwrite: false — the proxy always overwrites.');\n }\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(src))}`, {\n method: 'POST',\n query: { op: 'rename' },\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ destination: keyFromPath(dest) }),\n });\n }\n\n async mkdir(path: string, _options?: { recursive?: boolean }): Promise<void> {\n await this.ensureReady();\n if (this.readOnly) throw new WorkspaceReadOnlyError('mkdir');\n await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {\n method: 'POST',\n query: { op: 'mkdir' },\n });\n }\n\n async rmdir(path: string, options?: RemoveOptions): Promise<void> {\n await this.deleteFile(path.endsWith('/') ? path : `${path}/`, { recursive: true, force: options?.force });\n }\n\n async readdir(path: string, options?: ListOptions): Promise<FileEntry[]> {\n await this.ensureReady();\n const prefix = keyFromPath(path);\n const response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(prefix)}`,\n {\n query: {\n delimiter: options?.recursive ? undefined : '/',\n prefix: prefix ? `${prefix.replace(/\\/$/, '')}/` : undefined,\n },\n },\n );\n const json = (await response.json()) as ProxyListResponse;\n return [\n ...(json.commonPrefixes ?? []).map(prefix => ({\n name: nameFromPath(prefix.replace(/\\/$/, '')),\n type: 'directory' as const,\n })),\n ...(json.contents ?? [])\n .filter(object => object.key && !object.key.endsWith('/'))\n .map(object => ({\n name: nameFromPath(object.key!),\n type: 'file' as const,\n size: object.size,\n })),\n ].filter(\n entry => !options?.extension || entry.type === 'directory' || matchesExtension(entry.name, options.extension),\n );\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await this.stat(path);\n return true;\n } catch (error) {\n if (isNotFound(error) || error instanceof FileNotFoundError) return false;\n throw error;\n }\n }\n\n async stat(path: string): Promise<FileStat> {\n await this.ensureReady();\n const normalized = normalizePath(path);\n if (normalized === '/') {\n return { name: '', path: '/', type: 'directory', size: 0, createdAt: new Date(0), modifiedAt: new Date(0) };\n }\n let response: Response;\n try {\n response = await this._client.request(\n `/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`,\n {\n method: 'HEAD',\n },\n );\n } catch (error) {\n if (isNotFound(error)) throw new FileNotFoundError(path);\n throw error;\n }\n return {\n name: nameFromPath(path),\n path: normalized,\n type: normalized.endsWith('/') ? 'directory' : 'file',\n size: headerSize(response.headers),\n createdAt: headerDate(response.headers, 'last-modified'),\n modifiedAt: headerDate(response.headers, 'last-modified'),\n mimeType: response.headers.get('content-type') ?? undefined,\n };\n }\n\n realpath(path: string): Promise<string> {\n return Promise.resolve(normalizePath(path));\n }\n\n getInstructions(opts?: { requestContext?: RequestContext }): string {\n const defaultInstructions = `Platform filesystem backed by Mastra Platform bucket ${this._bucketName}. Use absolute workspace paths.`;\n if (typeof this._instructionsOverride === 'function') {\n return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });\n }\n if (typeof this._instructionsOverride === 'string') return this._instructionsOverride;\n return defaultInstructions;\n }\n\n getInfo(): FilesystemInfo<{ bucketName: string; displayName?: string; description?: string }> {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n readOnly: this.readOnly,\n icon: this.icon,\n metadata: {\n bucketName: this._bucketName,\n ...(this.displayName && { displayName: this.displayName }),\n ...(this.description && { description: this.description }),\n },\n };\n }\n}\n\nfunction matchesExtension(name: string, extension: string | string[]): boolean {\n const extensions = Array.isArray(extension) ? extension : [extension];\n return extensions.some(ext => name.endsWith(ext));\n}\n","/**\n * Direct exec client — opens Railway's tcp-proxy exec WebSocket directly using\n * a short-lived JWT minted by the workspace proxy's exec-lease endpoint. This\n * removes the platform data plane from the exec stdout/stderr path entirely\n * (see `docs/factory/direct-sandbox-connection.md` in the Platform repo),\n * cutting payload-scaled Cloud Run egress and RTT for commands like\n * `pnpm install` that stream tens of MB of output.\n *\n * The frame protocol below mirrors `connectExecWs()` in `railway@3.5.5`\n * (`workspaces/railway/node_modules/railway/dist/index.js`). The `railway`\n * SDK's version is pinned on both sides (platform + here); a version bump\n * signals the protocol may have drifted and this module must be revisited.\n */\n\n/** Byte-0 tag on binary WS frames for stdout output. */\nconst STDOUT_FRAME = 1;\n/** Byte-0 tag on binary WS frames for stderr output. */\nconst STDERR_FRAME = 3;\n/**\n * Upper bound on how long we'll wait for the WebSocket to open when the\n * caller didn't supply a `timeoutMs`. Guards against a stalled TLS/WS\n * handshake leaving the promise unresolved forever. Not applied once the\n * socket has opened — a caller with no timeout has opted in to unbounded\n * command runtime, just not to unbounded connection setup.\n */\nconst HANDSHAKE_DEADLINE_MS = 30_000;\n\n/**\n * Minimal WebSocket surface this module depends on. Matches both the browser\n * `WebSocket` global and Node 22+'s built-in `WebSocket`. Extracted so tests\n * can inject a fake without pulling in `ws` or jsdom.\n */\nexport interface DirectExecWebSocket {\n binaryType: 'blob' | 'arraybuffer';\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: { code: number; reason: string }) => void) | null;\n onerror: ((event: unknown) => void) | null;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n}\n\n/**\n * Factory that opens a WebSocket to `endpoint` with the given subprotocols.\n * Defaults to the global `WebSocket` when omitted, which works on Node 22+\n * (the package's minimum) and in the browser. Tests inject a fake here.\n */\nexport type DirectExecWebSocketFactory = (endpoint: string, subprotocols: string[]) => DirectExecWebSocket;\n\n/** Lease payload returned by `POST /v1/projects/:projectId/sandbox/:sandboxId/exec-lease`. */\nexport interface ExecLease {\n jwt: string;\n wsEndpoint: string;\n subprotocol: string;\n /** ISO-8601 UTC. Null when the provider issues a JWT without an `exp` claim. */\n expiresAt: string | null;\n}\n\n/** Inputs to a direct exec invocation. Mirrors the shape of the `/exec` route body. */\nexport interface DirectExecOptions {\n command: string;\n cwd?: string;\n env?: Record<string, string>;\n /**\n * Wall-clock cap for the exec. When elapsed, we close the socket and\n * return `{timedOut: true, exitCode: 124}` matching the semantics of the\n * proxy's `/exec` route.\n */\n timeoutMs?: number;\n onStdout?: (chunk: string) => void;\n onStderr?: (chunk: string) => void;\n /** Injected for tests. Defaults to `globalThis.WebSocket`. */\n webSocketFactory?: DirectExecWebSocketFactory;\n}\n\n/**\n * Result of a direct exec. Shape matches the workspace-proxy `/exec` response\n * so the caller (`PlatformSandbox.executeCommand`) can hand it back with no\n * translation.\n *\n * `exitCode` is `null` when the socket closed without an `exit` frame AND the\n * exec did not time out (rare — usually a mid-stream network drop). Callers\n * currently coerce `null` to `1` upstream; kept nullable here to preserve the\n * distinction for future observability.\n */\nexport interface DirectExecResult {\n exitCode: number | null;\n stdout: string;\n stderr: string;\n truncated: boolean;\n timedOut: boolean;\n /**\n * WebSocket close metadata. Populated on any close (normal or transport\n * failure). `opened` distinguishes handshake failures (never opened) from\n * mid-stream drops. Callers use this for diagnostic logging; not part of\n * the CommandResult contract.\n */\n closeCode?: number;\n closeReason?: string;\n opened?: boolean;\n}\n\nconst DEFAULT_WS_FACTORY: DirectExecWebSocketFactory = (endpoint, subprotocols) => {\n const WS = (globalThis as { WebSocket?: unknown }).WebSocket as\n | (new (url: string, protocols: string[]) => DirectExecWebSocket)\n | undefined;\n if (!WS) {\n throw new Error(\n 'Direct exec requires a WebSocket implementation. Node 22+ provides one globally; on older runtimes, pass webSocketFactory explicitly.',\n );\n }\n return new WS(endpoint, subprotocols);\n};\n\n/**\n * Open the provider exec WebSocket using `lease`, run `command`, and resolve\n * with the accumulated stdout/stderr + exit code. See the module docstring\n * for the wire protocol reference.\n *\n * The client sends `stdin_close` immediately after `init_exec`, matching the\n * SDK's own one-shot exec behavior — we never stream stdin from the caller.\n */\nexport function execViaLease(lease: ExecLease, options: DirectExecOptions): Promise<DirectExecResult> {\n const factory = options.webSocketFactory ?? DEFAULT_WS_FACTORY;\n const stdoutDecoder = new TextDecoder();\n const stderrDecoder = new TextDecoder();\n\n return new Promise<DirectExecResult>(resolve => {\n let stdout = '';\n let stderr = '';\n let exitCode: number | null = null;\n let timedOut = false;\n let settled = false;\n let opened = false;\n let closeCode: number | undefined;\n let closeReason: string | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let handshakeTimer: ReturnType<typeof setTimeout> | undefined;\n\n const settle = () => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n if (handshakeTimer) clearTimeout(handshakeTimer);\n // Flush any bytes still buffered in the decoders. A stream:true decode\n // holds trailing partial multi-byte sequences until the next chunk, so\n // without a flush the final char(s) of a UTF-8 stream can be dropped.\n const stdoutTail = stdoutDecoder.decode();\n if (stdoutTail) {\n stdout += stdoutTail;\n options.onStdout?.(stdoutTail);\n }\n const stderrTail = stderrDecoder.decode();\n if (stderrTail) {\n stderr += stderrTail;\n options.onStderr?.(stderrTail);\n }\n try {\n socket.close(1000, '');\n } catch {\n /* already closed */\n }\n resolve({\n exitCode,\n stdout,\n stderr,\n truncated: false,\n timedOut,\n ...(closeCode !== undefined && { closeCode }),\n ...(closeReason !== undefined && { closeReason }),\n opened,\n });\n };\n\n // Arm the timeout BEFORE we open the socket so a stalled handshake can't\n // leave the promise pending. Callers with a positive `timeoutMs` get the\n // wall-clock cap they asked for; callers without one still get a\n // connect-only deadline that clears once the socket opens.\n if (options.timeoutMs !== undefined && options.timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n // 124 matches the proxy's `/exec` semantics (coreutils `timeout`\n // exit code) so callers that switch on exitCode see the same value.\n if (exitCode === null) exitCode = 124;\n settle();\n }, options.timeoutMs);\n } else {\n handshakeTimer = setTimeout(() => {\n // Never opened → treat as a transport failure. Leave exitCode=null\n // so the caller can distinguish this from a normal exit; do not\n // set timedOut (that flag is reserved for the wall-clock case).\n if (!opened) settle();\n }, HANDSHAKE_DEADLINE_MS);\n }\n\n const socket = factory(lease.wsEndpoint, [lease.subprotocol, lease.jwt]);\n socket.binaryType = 'arraybuffer';\n\n socket.onopen = () => {\n opened = true;\n if (handshakeTimer) {\n clearTimeout(handshakeTimer);\n handshakeTimer = undefined;\n }\n const data: Record<string, unknown> = { command: options.command };\n if (options.cwd) data.cwd = options.cwd;\n if (options.env && Object.keys(options.env).length > 0) data.env = options.env;\n socket.send(JSON.stringify({ type: 'init_exec', data }));\n // We never stream stdin for one-shot exec; the SDK does this too, and\n // omitting it can leave the exec hanging waiting on EOF.\n socket.send(JSON.stringify({ type: 'stdin_close' }));\n };\n\n socket.onmessage = event => {\n const { data } = event;\n if (data instanceof ArrayBuffer) {\n handleBinaryFrame(data);\n } else if (typeof data === 'string') {\n handleTextFrame(data);\n }\n };\n\n socket.onclose = event => {\n closeCode = event.code;\n closeReason = event.reason;\n if (!opened) {\n // Never opened — surface as a failure via exitCode=null,\n // truncated=false, timedOut=false so the caller can distinguish\n // it from a normal exit-0 by inspecting `exitCode === null`.\n settle();\n return;\n }\n // Preserve any info captured before close; if the server sent an\n // `exit` frame this is a no-op because settle() already ran.\n settle();\n };\n\n socket.onerror = () => {\n if (settled) return;\n if (!opened) {\n settle();\n }\n // If we're mid-stream and the socket errors, wait for onclose to fire\n // so we settle with whatever output we did receive.\n };\n\n function handleBinaryFrame(buffer: ArrayBuffer) {\n const view = new Uint8Array(buffer);\n if (view.length <= 1) return;\n if (view[0] === STDOUT_FRAME) {\n const chunk = stdoutDecoder.decode(view.subarray(1), { stream: true });\n stdout += chunk;\n options.onStdout?.(chunk);\n } else if (view[0] === STDERR_FRAME) {\n const chunk = stderrDecoder.decode(view.subarray(1), { stream: true });\n stderr += chunk;\n options.onStderr?.(chunk);\n }\n }\n\n function handleTextFrame(text: string) {\n let frame: { type?: string; data?: { exit_code?: number } };\n try {\n frame = JSON.parse(text) as { type?: string; data?: { exit_code?: number } };\n } catch {\n return;\n }\n if (frame.type === 'exit') {\n exitCode = frame.data?.exit_code ?? 0;\n settle();\n }\n // `durable_session` frames are intentionally ignored — we don't reattach\n // or expose session names on the one-shot exec path.\n }\n });\n}\n","import type { RequestContext } from '@mastra/core/di';\nimport type {\n CommandResult,\n ExecuteCommandOptions,\n InstructionsOption,\n MastraSandboxOptions,\n ProcessInfo,\n ProviderStatus,\n SandboxCloneOptions,\n SandboxInfo,\n SpawnProcessOptions,\n} from '@mastra/core/workspace';\nimport { MastraSandbox, ProcessHandle, SandboxNotReadyError, SandboxProcessManager } from '@mastra/core/workspace';\nimport type { PlatformClientOptions } from './client.js';\nimport { PlatformApiError, PlatformClient } from './client.js';\nimport type { DirectExecWebSocketFactory, ExecLease } from './direct-exec.js';\nimport { execViaLease } from './direct-exec.js';\n\nexport type PlatformSandboxNetworkIsolation = 'ISOLATED' | 'PRIVATE';\n\nexport interface PlatformSandboxOptions extends Omit<MastraSandboxOptions, 'processes'>, PlatformClientOptions {\n id?: string;\n environmentId?: string;\n sandboxId?: string;\n idleTimeoutMinutes?: number;\n networkIsolation?: PlatformSandboxNetworkIsolation;\n env?: Record<string, string>;\n timeout?: number;\n instructions?: InstructionsOption;\n /**\n * Injected WebSocket factory used by the direct-exec code path. Defaults to\n * the global `WebSocket` (available on Node 22+, this package's minimum) and\n * only exists so tests can drive the exec state machine deterministically\n * without a real network socket.\n */\n webSocketFactory?: DirectExecWebSocketFactory;\n}\n\ninterface ExecLeaseResponse {\n provider: string;\n sandboxId: string;\n providerResourceId: string;\n jwt: string;\n wsEndpoint: string;\n subprotocol: string;\n expiresAt: string | null;\n}\n\n/**\n * How long before a lease's stated `expiresAt` we should treat it as\n * expired. Avoids a race where the JWT is valid at cache-hit time but the\n * server rejects it by the time the WebSocket handshake completes.\n */\nconst LEASE_REFRESH_MARGIN_MS = 60_000;\n\ninterface CreateSandboxResponse {\n id: string;\n providerResourceId?: string | null;\n status?: string;\n createdAt?: string;\n destroyedAt?: string | null;\n}\n\n/** Max attempts for `POST /sandbox` when the proxy returns transient 5xx errors. */\nconst CREATE_MAX_ATTEMPTS = 3;\n/** Base delay between create retries; multiplied by the attempt number. */\nconst CREATE_RETRY_BASE_DELAY_MS = 2_000;\n\n/**\n * Diagnostic error thrown when the direct-exec WebSocket transport fails\n * twice in a row (opening handshake refused or socket closed mid-stream\n * without an `exit` frame). Distinguishes \"the sandbox transport is broken\"\n * from \"your command failed\" so callers can decide whether to retry at a\n * higher level (e.g. reprovision the sandbox) or surface the error.\n *\n * `opened` is `true` when the WebSocket completed its handshake at least\n * once before closing; `false` when Railway refused the upgrade outright.\n */\nexport class SandboxExecTransportError extends Error {\n readonly sandboxId: string | undefined;\n readonly command: string;\n readonly attempts: number;\n readonly opened: boolean;\n readonly closeCode: number | undefined;\n readonly closeReason: string | undefined;\n readonly wsEndpoint: string;\n\n constructor(\n message: string,\n diagnostics: {\n sandboxId?: string;\n command: string;\n attempts: number;\n opened: boolean;\n closeCode?: number;\n closeReason?: string;\n wsEndpoint: string;\n },\n ) {\n super(message);\n this.name = 'SandboxExecTransportError';\n this.sandboxId = diagnostics.sandboxId;\n this.command = diagnostics.command;\n this.attempts = diagnostics.attempts;\n this.opened = diagnostics.opened;\n this.closeCode = diagnostics.closeCode;\n this.closeReason = diagnostics.closeReason;\n this.wsEndpoint = diagnostics.wsEndpoint;\n }\n}\n\n/**\n * Thrown when `/exec-lease` returns 410 Gone — the sandbox has been destroyed\n * (Railway destroy, quota reclamation, etc.). The client cannot recover from\n * this on its own because it does not own the binding store; only the fleet\n * layer can clear the stale sandbox id and provision a fresh one. Callers\n * (typically `SandboxFleet`) must catch this and reprovision-and-replay.\n *\n * When this is thrown the cached `_lease` and `_sandboxId` on the sandbox\n * instance are cleared, so the next `ensureRunning()` on a reused instance\n * will re-provision cleanly.\n */\nexport class SandboxDestroyedError extends Error {\n readonly sandboxId: string | undefined;\n readonly command: string;\n readonly attempts: number;\n\n constructor(message: string, diagnostics: { sandboxId?: string; command: string; attempts: number }) {\n super(message);\n this.name = 'SandboxDestroyedError';\n this.sandboxId = diagnostics.sandboxId;\n this.command = diagnostics.command;\n this.attempts = diagnostics.attempts;\n }\n}\n\n/**\n * Compose a shell command line from a `command` string and optional `args`.\n *\n * IMPORTANT: `command` is treated as a **shell string** and passed to the\n * remote shell verbatim so callers can use pipes, redirects, and chaining\n * (`ls -la | grep foo`). This matches the contract of {@link MastraSandbox}\n * and the local sandbox implementation. `args` are always shell-quoted so\n * they cannot inject syntax.\n *\n * Callers MUST NOT pass untrusted input as `command`. Untrusted values must\n * be passed via `args`, where they are safely quoted. Passing untrusted\n * input as `command` allows arbitrary shell syntax execution on the remote\n * sandbox.\n */\nfunction buildCommand(command: string, args?: string[]): string {\n return args?.length ? `${command} ${args.map(shellQuote).join(' ')}` : command;\n}\n\nfunction shellQuote(arg: string): string {\n if (/^[a-zA-Z0-9._\\-/=:@]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, `'\\\\''`)}'`;\n}\n\nclass PlatformProcessHandle extends ProcessHandle {\n readonly pid: string;\n private readonly resultPromise: Promise<CommandResult>;\n private exitCodeValue: number | undefined;\n\n constructor(pid: string, resultPromise: Promise<CommandResult>, options?: SpawnProcessOptions) {\n super(options);\n this.pid = pid;\n this.resultPromise = resultPromise.then(result => {\n this.exitCodeValue = result.exitCode;\n if (result.stdout) this.emitStdout(result.stdout);\n if (result.stderr) this.emitStderr(result.stderr);\n return result;\n });\n }\n\n get exitCode(): number | undefined {\n return this.exitCodeValue;\n }\n\n async wait(): Promise<CommandResult> {\n return this.resultPromise;\n }\n\n async kill(): Promise<boolean> {\n // The workspace proxy has no cancel-exec endpoint; each `executeCommand`\n // is a synchronous round-trip that has already completed (or timed out)\n // by the time a handle exists to kill. Making this explicit avoids\n // callers silently believing they cancelled a still-running process.\n throw new Error('Platform sandbox command execution does not support killing individual processes');\n }\n\n async sendStdin(): Promise<void> {\n throw new Error('Platform sandbox command execution does not support stdin');\n }\n}\n\nclass PlatformProcessManager extends SandboxProcessManager<PlatformSandbox> {\n private spawnCounter = 0;\n\n /**\n * Spawn a process on the remote sandbox.\n *\n * `command` is interpreted as a shell string by the remote shell, matching\n * the {@link MastraSandbox} contract. See {@link PlatformSandbox.executeCommand}\n * for the untrusted-input caveat: never pass untrusted values as `command`.\n */\n async spawn(command: string, options: SpawnProcessOptions = {}): Promise<ProcessHandle> {\n const pid = `platform-proc-${Date.now().toString(36)}-${(this.spawnCounter++).toString(36)}`;\n const resultPromise = this.sandbox.executeCommand(command, undefined, options);\n const handle = new PlatformProcessHandle(pid, resultPromise, options);\n this._tracked.set(handle.pid, handle);\n return handle;\n }\n\n async list(): Promise<ProcessInfo[]> {\n return Array.from(this._tracked.values()).map(handle => ({\n pid: handle.pid,\n command: handle.command,\n running: handle.exitCode === undefined,\n ...(handle.exitCode !== undefined && { exitCode: handle.exitCode }),\n }));\n }\n}\n\nexport class PlatformSandbox extends MastraSandbox {\n readonly id: string;\n readonly name = 'PlatformSandbox';\n readonly provider = 'platform';\n status: ProviderStatus = 'pending';\n declare readonly processes: PlatformProcessManager;\n\n private readonly _client: PlatformClient;\n private readonly _environmentId: string;\n private _sandboxId?: string;\n private readonly _idleTimeoutMinutes?: number;\n private readonly _networkIsolation?: PlatformSandboxNetworkIsolation;\n private readonly _env: Record<string, string>;\n private readonly _timeout?: number;\n private readonly _instructionsOverride?: InstructionsOption;\n private _createdAt: Date | null = null;\n private readonly _webSocketFactory?: DirectExecWebSocketFactory;\n /**\n * Cached exec lease for this sandbox. `null` before the first exec and\n * after {@link destroy}. Refreshed when `expiresAt - LEASE_REFRESH_MARGIN_MS < now`\n * (see {@link _ensureLease}); a lease without a disclosed `expiresAt`\n * is refreshed on every call.\n */\n private _lease: (ExecLease & { expiresAtMs: number | null }) | null = null;\n /**\n * In-flight mint request; concurrent `_ensureLease` callers on a cold or\n * near-expiry cache all await this single promise so we don't burn N\n * `POST /exec-lease` round-trips when the sandbox is doing N parallel execs.\n * Cleared (regardless of success or failure) when the request settles.\n */\n private _leaseInFlight: Promise<ExecLease & { expiresAtMs: number | null }> | null = null;\n\n constructor(options: PlatformSandboxOptions = {}) {\n super({ ...options, name: 'PlatformSandbox', processes: new PlatformProcessManager() });\n this.id = options.id ?? this.generateId();\n this._client = new PlatformClient(options);\n this._environmentId = options.environmentId ?? process.env.MASTRA_ENVIRONMENT_ID ?? '';\n if (!this._environmentId && !options.sandboxId) throw new Error('environmentId is required');\n this._sandboxId = options.sandboxId;\n this._idleTimeoutMinutes = options.idleTimeoutMinutes;\n this._networkIsolation = options.networkIsolation;\n this._env = options.env ?? {};\n this._timeout = options.timeout;\n this._instructionsOverride = options.instructions;\n this._webSocketFactory = options.webSocketFactory;\n }\n\n private generateId(): string {\n return `platform-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n /**\n * Construct a sibling {@link PlatformSandbox} that inherits this sandbox's\n * credentials and defaults (access token, project, environment, network\n * isolation, timeout, instructions, env, idle timeout) with per-instance\n * overrides from `options`.\n *\n * Performs no I/O and does not require this sandbox to be started — the\n * returned sandbox is not started and provisions (or reattaches, when\n * `sandboxId` is set) on its own `start()`. Use it when one configured\n * sandbox acts as the template for a fleet of independent sandboxes\n * (e.g. one per project).\n */\n clone(options: SandboxCloneOptions = {}): PlatformSandbox {\n // The proxy hashes `body.id` on POST /sandbox to look up a prior\n // checkpoint. A stable `checkpointName` is only useful if it round-trips\n // to `body.id`, so route it through the sandbox id when the caller\n // didn't pick one explicitly. Without this, every clone gets a random\n // id and no boot ever hits its captured checkpoint (see\n // issue-platform-sandbox-clone-drops-checkpoint-name.md).\n const id = options.id ?? options.checkpointName;\n return new PlatformSandbox({\n ...(id !== undefined && { id }),\n accessToken: this._client.accessToken,\n projectId: this._client.projectId,\n fetch: this._client.fetch,\n environmentId: this._environmentId,\n ...(options.sandboxId !== undefined && { sandboxId: options.sandboxId }),\n idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,\n ...(this._networkIsolation !== undefined && { networkIsolation: this._networkIsolation }),\n env: options.env ?? this._env,\n ...(this._timeout !== undefined && { timeout: this._timeout }),\n ...(this._instructionsOverride !== undefined && { instructions: this._instructionsOverride }),\n ...(this._webSocketFactory !== undefined && { webSocketFactory: this._webSocketFactory }),\n });\n }\n\n async start(): Promise<void> {\n if (this._sandboxId) {\n try {\n const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);\n const json = (await response.json()) as CreateSandboxResponse;\n // A destroyed record (idle GC, manual delete) is not reattachable —\n // treat it like a missing sandbox so we fall through to a fresh\n // provision instead of pointing exec at a dead resource.\n if (!json.destroyedAt) {\n this._createdAt = json.createdAt ? new Date(json.createdAt) : new Date();\n return;\n }\n this._sandboxId = undefined;\n } catch (error) {\n if (!(error instanceof PlatformApiError) || error.status !== 404) throw error;\n this._sandboxId = undefined;\n }\n }\n\n if (!this._environmentId) throw new Error('environmentId is required');\n\n const body = JSON.stringify({\n // Sent so the platform can associate the provisioned resource with a\n // caller-stable identifier (used for opt-in checkpoint recovery). The\n // platform treats it as an advisory key: unknown values fall through\n // to a fresh sandbox, matching pre-existing behavior.\n id: this.id,\n environmentId: this._environmentId,\n idleTimeoutMinutes: this._idleTimeoutMinutes,\n networkIsolation: this._networkIsolation,\n env: this._env,\n });\n // Provisioning is observed to fail intermittently with proxy 500s while\n // the provider is under load. A create either succeeds (201) or fails\n // without allocating a caller-visible resource, so retrying transient\n // 5xx responses with a short backoff is safe and keeps a single flaky\n // window from killing the caller's whole workflow.\n let response: Response | undefined;\n for (let attempt = 1; ; attempt++) {\n try {\n response = await this._client.request('/sandbox', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body,\n });\n break;\n } catch (error) {\n const transient = error instanceof PlatformApiError && error.status >= 500;\n if (!transient || attempt >= CREATE_MAX_ATTEMPTS) throw error;\n await new Promise(resolve => setTimeout(resolve, CREATE_RETRY_BASE_DELAY_MS * attempt));\n }\n }\n const json = (await response.json()) as CreateSandboxResponse;\n this._sandboxId = json.id;\n this._createdAt = json.createdAt ? new Date(json.createdAt) : new Date();\n }\n\n async stop(): Promise<void> {\n await this.destroy();\n }\n\n async destroy(): Promise<void> {\n if (!this._sandboxId) return;\n await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`, { method: 'DELETE' });\n // Clear local state so a subsequent start() creates a fresh remote sandbox\n // instead of taking the reattach branch and pointing exec at a deleted resource.\n this._sandboxId = undefined;\n this._createdAt = null;\n // Drop the exec lease with the sandbox — the JWT is tied to the provider\n // instance id and would be rejected against a fresh one.\n this._lease = null;\n }\n\n /**\n * Execute a command on the remote sandbox.\n *\n * `command` is a **shell string**: it is concatenated verbatim into the\n * command line sent to the remote shell, which lets callers use pipes,\n * redirects, and chaining (`ls -la | grep foo`). This matches the contract\n * of {@link MastraSandbox} and the local sandbox implementation.\n *\n * `args`, when provided, are always shell-quoted so they cannot inject\n * additional shell syntax.\n *\n * Security: callers MUST NOT pass untrusted input as `command`. If any part\n * of the invocation is derived from an untrusted source, pass it through\n * `args` (which is safely quoted) or shell-quote it yourself before\n * inclusion. Untrusted `command` values allow arbitrary shell syntax\n * execution on the remote sandbox.\n */\n async executeCommand(command: string, args?: string[], options?: ExecuteCommandOptions): Promise<CommandResult> {\n await this.ensureRunning();\n if (!this._sandboxId) throw new SandboxNotReadyError(this.id);\n\n const started = Date.now();\n const fullCommand = buildCommand(command, args);\n // Nullish check so an explicit `timeout: 0` still overrides the instance\n // default. `_runDirectExec` omits `timeoutMs` from the exec payload when\n // the value is 0, which disables the client-side timer entirely.\n const effectiveTimeout = options?.timeout ?? this._timeout;\n\n // Direct-exec (WebSocket straight to Railway's tcp-proxy) is the only\n // data plane. `_runDirectExec` handles single-shot transport retry and\n // throws typed errors on unrecoverable failure: `SandboxDestroyedError`\n // when `/exec-lease` returns 410 (fleet must reprovision),\n // `SandboxExecTransportError` when the WebSocket transport fails twice\n // against a live sandbox, `PlatformApiError` for other `/exec-lease`\n // errors (404/500/501). See ./direct-exec.ts and\n // `docs/factory/direct-sandbox-connection.md` in the Platform repo.\n const result = await this._runDirectExec(fullCommand, effectiveTimeout, options);\n // `_runDirectExec` throws on transport failure (see its jsdoc), so a\n // `null` exitCode here can only mean `timedOut: true` — the sandbox\n // never got to send an exit frame because we cut the command short.\n // Use 124 for that (the conventional timeout exit code). We are NOT\n // coercing transport-failure nulls to fake exit codes — those throw.\n const exitCode = result.exitCode ?? 124;\n return {\n success: exitCode === 0,\n exitCode,\n stdout: result.stdout,\n stderr: result.stderr,\n timedOut: result.timedOut,\n command: fullCommand,\n executionTimeMs: Date.now() - started,\n };\n }\n\n /**\n * Run a single exec against the direct-exec transport, with one in-flight\n * retry on WebSocket transport failure (socket closed without an `exit`\n * frame and the exec did not time out). The retry mints a fresh lease\n * — the failure could be a stale JWT — and reopens a new WebSocket.\n *\n * Error taxonomy:\n * - **410 on `/exec-lease`** (either attempt) → the sandbox is gone.\n * Nulls the cached `_lease` and `_sandboxId` and throws\n * {@link SandboxDestroyedError}. Callers (typically `SandboxFleet`) must\n * catch this, clear the stale binding, and reprovision + replay.\n * - **Persistent transport failure** (both WS attempts close without an\n * `exit` frame against a live sandbox) → {@link SandboxExecTransportError}\n * with WebSocket close diagnostics.\n * - **Other `PlatformApiError`s** (404/500/501) propagate directly.\n * - **Real command result** (exit code from Railway's exit frame, or\n * `timedOut: true`) returns normally.\n *\n * Returns a result with a real `exitCode` OR `timedOut: true`. Never\n * returns `{ exitCode: null, timedOut: false }` — that case throws.\n */\n private async _runDirectExec(\n fullCommand: string,\n effectiveTimeout: number | undefined,\n options: ExecuteCommandOptions | undefined,\n ): Promise<{ exitCode: number | null; stdout: string; stderr: string; timedOut: boolean }> {\n // Filter undefined values out of the env overlay so we match the\n // Record<string, string> shape execViaLease expects. `ExecuteCommandOptions.env`\n // is NodeJS.ProcessEnv (string | undefined).\n const filteredEnv = options?.env\n ? Object.fromEntries(\n Object.entries(options.env).filter((entry): entry is [string, string] => entry[1] !== undefined),\n )\n : undefined;\n\n let lastResult: Awaited<ReturnType<typeof execViaLease>> | undefined;\n let lastLease: (ExecLease & { expiresAtMs: number | null }) | undefined;\n let attemptsMade = 0;\n // Two attempts: initial + one retry. On the second attempt we drop the\n // cached lease so we don't reuse a JWT that may itself be the cause of\n // the transport failure — but only if the cache still holds the same\n // lease we just failed against. A concurrent exec sharing this instance\n // may have already cached a fresh, unrelated lease in between, and we\n // must not discard that.\n for (let attempt = 0; attempt < 2; attempt++) {\n if (attempt > 0 && lastLease && this._lease === lastLease) this._lease = null;\n let lease: ExecLease & { expiresAtMs: number | null };\n try {\n lease = await this._ensureLease();\n } catch (error) {\n // 410 → sandbox has been destroyed. Clear all cached state so a\n // reused instance re-provisions cleanly, then hand off to the fleet\n // layer via a typed error. Other PlatformApiErrors (404/500/501)\n // propagate as-is — those are configuration or platform errors, not\n // a \"reprovision me\" signal.\n if (error instanceof PlatformApiError && error.status === 410) {\n this._lease = null;\n const priorSandboxId = this._sandboxId;\n this._sandboxId = undefined;\n throw new SandboxDestroyedError(\n `Sandbox ${priorSandboxId ?? '(unknown)'} was destroyed; /exec-lease returned 410`,\n {\n ...(priorSandboxId && { sandboxId: priorSandboxId }),\n command: fullCommand,\n attempts: attempt + 1,\n },\n );\n }\n throw error;\n }\n lastLease = lease;\n attemptsMade = attempt + 1;\n const result = await execViaLease(lease, {\n command: fullCommand,\n ...(options?.cwd !== undefined && { cwd: options.cwd }),\n ...(filteredEnv !== undefined && { env: filteredEnv }),\n ...(effectiveTimeout != null && effectiveTimeout > 0 && { timeoutMs: effectiveTimeout }),\n ...(this._webSocketFactory && { webSocketFactory: this._webSocketFactory }),\n });\n lastResult = result;\n // `null` exitCode with `timedOut: false` means the socket closed\n // without an exit frame — a transport failure (handshake stalled,\n // mid-stream drop, expired token). Any other outcome (real exit code\n // or timed-out) is a valid result and we return it.\n if (result.exitCode !== null || result.timedOut) return result;\n }\n\n // Both attempts failed at the transport layer against a live sandbox.\n // Surface a loud, typed error with close diagnostics so callers can\n // distinguish \"your command failed\" from \"the sandbox transport is\n // broken.\"\n const result = lastResult!;\n const lease = lastLease!;\n // The lease from the failed second attempt is still cached; drop it so\n // the next `executeCommand` doesn't waste its first attempt on the same\n // implicated JWT before minting fresh. Identity-check first so a\n // concurrent exec that has already cached a fresh, unrelated lease\n // isn't collateral-damaged.\n if (this._lease === lease) this._lease = null;\n throw new SandboxExecTransportError(\n `Direct-exec transport failed for sandbox ${this._sandboxId ?? '(unknown)'} after ${attemptsMade} attempt(s)` +\n (result.closeCode !== undefined\n ? ` (close ${result.closeCode}${result.closeReason ? ` ${result.closeReason}` : ''})`\n : ''),\n {\n ...(this._sandboxId && { sandboxId: this._sandboxId }),\n command: fullCommand,\n attempts: attemptsMade,\n opened: result.opened ?? false,\n ...(result.closeCode !== undefined && { closeCode: result.closeCode }),\n ...(result.closeReason !== undefined && { closeReason: result.closeReason }),\n wsEndpoint: lease.wsEndpoint,\n },\n );\n }\n\n /**\n * Return a cached exec lease, minting a fresh one when the cache is empty\n * or the JWT is within {@link LEASE_REFRESH_MARGIN_MS} of `expiresAt`.\n *\n * Callers are expected to be on the \"sandbox is running\" path; we don't\n * re-check `_sandboxId` here because `executeCommand` already gated on it.\n */\n private async _ensureLease(): Promise<ExecLease & { expiresAtMs: number | null }> {\n const now = Date.now();\n // Cache hit only when we know the expiry AND we're comfortably before it.\n // A null `expiresAtMs` means the provider didn't disclose a TTL — treat\n // that as \"refresh every call\" rather than \"cache forever\", so a token\n // that turns out to be short-lived can't wedge the sandbox until restart.\n if (this._lease && this._lease.expiresAtMs !== null && this._lease.expiresAtMs - LEASE_REFRESH_MARGIN_MS > now) {\n return this._lease;\n }\n // Coalesce concurrent mints on a cold/expired cache.\n if (this._leaseInFlight) return this._leaseInFlight;\n if (!this._sandboxId) throw new SandboxNotReadyError(this.id);\n const sandboxId = this._sandboxId;\n const inFlight = (async () => {\n const response = await this._client.request(`/sandbox/${encodeURIComponent(sandboxId)}/exec-lease`, {\n method: 'POST',\n });\n const json = (await response.json()) as ExecLeaseResponse;\n const expiresAtMs = json.expiresAt ? Date.parse(json.expiresAt) : null;\n const lease = {\n jwt: json.jwt,\n wsEndpoint: json.wsEndpoint,\n subprotocol: json.subprotocol,\n expiresAt: json.expiresAt,\n // Guard against `Date.parse` returning NaN for malformed values by\n // treating them as \"no expiry known\", which forces a mint every call\n // rather than silently caching a broken lease forever.\n expiresAtMs: expiresAtMs !== null && !Number.isNaN(expiresAtMs) ? expiresAtMs : null,\n };\n this._lease = lease;\n return lease;\n })();\n this._leaseInFlight = inFlight;\n try {\n return await inFlight;\n } finally {\n // Clear on both success and failure so a failed mint doesn't wedge\n // future callers into awaiting the same rejected promise forever.\n if (this._leaseInFlight === inFlight) this._leaseInFlight = null;\n }\n }\n\n async getInfo(): Promise<SandboxInfo> {\n if (!this._sandboxId) {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this._createdAt ?? new Date(),\n };\n }\n const response = await this._client.request(`/sandbox/${encodeURIComponent(this._sandboxId)}`);\n const json = (await response.json()) as CreateSandboxResponse;\n return {\n id: json.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: json.createdAt ? new Date(json.createdAt) : (this._createdAt ?? new Date()),\n metadata: {\n // The platform assigns its own sandbox id on create (the advisory id\n // sent in the POST body is not honored). Expose it so callers that\n // persist a reattach id (e.g. the Factory sandbox fleet, which reads\n // `metadata.sandboxId`) store the id the proxy actually recognizes\n // instead of the locally generated construction id.\n sandboxId: json.id,\n providerResourceId: json.providerResourceId ?? undefined,\n platformStatus: json.status,\n },\n };\n }\n\n getInstructions(opts?: { requestContext?: RequestContext }): string {\n const defaultInstructions = `Platform sandbox${this._sandboxId ? ` ${this._sandboxId}` : ''}. Execute commands with the sandbox command APIs.`;\n if (typeof this._instructionsOverride === 'function') {\n return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });\n }\n if (typeof this._instructionsOverride === 'string') return this._instructionsOverride;\n return defaultInstructions;\n }\n}\n","import type { FilesystemProvider, SandboxProvider } from '@mastra/core/editor';\nimport type { PlatformFilesystemOptions } from './filesystem.js';\nimport { PlatformFilesystem } from './filesystem.js';\nimport type { PlatformSandboxOptions } from './sandbox.js';\nimport { PlatformSandbox } from './sandbox.js';\n\nexport const platformSandboxProvider: SandboxProvider<PlatformSandboxOptions> = {\n id: 'platform',\n name: 'Mastra Platform Sandbox',\n description: 'Environment-scoped sandbox execution through Mastra Platform workspace proxy',\n configSchema: {\n type: 'object',\n properties: {\n accessToken: {\n type: 'string',\n description: 'Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)',\n },\n projectId: { type: 'string', description: 'Platform project ID (falls back to MASTRA_PROJECT_ID)' },\n environmentId: { type: 'string', description: 'Platform environment ID (falls back to MASTRA_ENVIRONMENT_ID)' },\n sandboxId: { type: 'string', description: 'Reattach to an existing Platform sandbox by ID' },\n idleTimeoutMinutes: { type: 'number', description: 'Minutes before the sandbox can be destroyed while idle' },\n networkIsolation: {\n type: 'string',\n description: 'Network isolation mode',\n enum: ['ISOLATED', 'PRIVATE'],\n default: 'ISOLATED',\n },\n env: { type: 'object', description: 'Environment variables', additionalProperties: { type: 'string' } },\n timeout: { type: 'number', description: 'Default command timeout in ms' },\n },\n },\n createSandbox: config => new PlatformSandbox(config),\n};\n\nexport const platformFilesystemProvider: FilesystemProvider<PlatformFilesystemOptions> = {\n id: 'platform',\n name: 'Mastra Platform Filesystem',\n description: 'Bucket-backed filesystem access through Mastra Platform workspace proxy',\n configSchema: {\n type: 'object',\n properties: {\n accessToken: {\n type: 'string',\n description: 'Mastra Platform access token (falls back to MASTRA_PLATFORM_ACCESS_TOKEN)',\n },\n projectId: { type: 'string', description: 'Platform project ID (falls back to MASTRA_PROJECT_ID)' },\n bucketName: {\n type: 'string',\n description: 'Platform workspace bucket name (falls back to MASTRA_PLATFORM_BUCKET_NAME)',\n },\n readOnly: { type: 'boolean', description: 'Mount as read-only', default: false },\n },\n },\n createFilesystem: config => new PlatformFilesystem(config),\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/platform-workspace",
3
- "version": "0.3.0-alpha.1",
3
+ "version": "1.0.0",
4
4
  "description": "Mastra Platform workspace sandbox and filesystem providers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,9 +27,9 @@
27
27
  "tsdown": "0.22.9",
28
28
  "typescript": "^6.0.3",
29
29
  "vitest": "4.1.10",
30
- "@internal/lint": "0.0.119",
31
- "@internal/types-builder": "0.0.94",
32
- "@mastra/core": "1.56.0-alpha.3"
30
+ "@internal/lint": "0.0.120",
31
+ "@internal/types-builder": "0.0.95",
32
+ "@mastra/core": "1.56.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@mastra/core": ">=1.4.0-0 <2.0.0-0"