@mastra/platform-workspace 1.8.2-alpha.0 → 1.9.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  }
17
17
  return to;
18
18
  };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
20
  value: mod,
21
21
  enumerable: true
22
22
  }) : target, mod));
@@ -168,8 +168,8 @@ function normalizePath(input) {
168
168
  normalized = path.default.posix.normalize(normalized);
169
169
  return normalized === "." ? "/" : normalized;
170
170
  }
171
- function keyFromPath(path$1) {
172
- const normalized = normalizePath(path$1);
171
+ function keyFromPath(path) {
172
+ const normalized = normalizePath(path);
173
173
  return normalized === "/" ? "" : normalized.slice(1);
174
174
  }
175
175
  /**
@@ -181,8 +181,8 @@ function keyFromPath(path$1) {
181
181
  function encodeKeyPath(key) {
182
182
  return key.split("/").map(encodeURIComponent).join("/");
183
183
  }
184
- function nameFromPath(path$2) {
185
- const normalized = normalizePath(path$2);
184
+ function nameFromPath(path) {
185
+ const normalized = normalizePath(path);
186
186
  if (normalized === "/") return "";
187
187
  return normalized.slice(normalized.lastIndexOf("/") + 1);
188
188
  }
@@ -231,32 +231,32 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
231
231
  generateId() {
232
232
  return `platform-fs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
233
233
  }
234
- async readFile(path$3, options) {
234
+ async readFile(path, options) {
235
235
  await this.ensureReady();
236
236
  let response;
237
237
  try {
238
- response = await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path$3))}`);
238
+ response = await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`);
239
239
  } catch (error) {
240
- if (isNotFound(error)) throw new _mastra_core_workspace.FileNotFoundError(path$3);
240
+ if (isNotFound(error)) throw new _mastra_core_workspace.FileNotFoundError(path);
241
241
  throw error;
242
242
  }
243
- const buffer$1 = buffer.Buffer.from(await response.arrayBuffer());
244
- return options?.encoding ? buffer$1.toString(options.encoding) : buffer$1;
243
+ const buffer$2 = buffer.Buffer.from(await response.arrayBuffer());
244
+ return options?.encoding ? buffer$2.toString(options.encoding) : buffer$2;
245
245
  }
246
- async writeFile(path$4, content, options) {
246
+ async writeFile(path, content, options) {
247
247
  await this.ensureReady();
248
248
  if (this.readOnly) throw new _mastra_core_workspace.WorkspaceReadOnlyError("writeFile");
249
249
  const headers = {};
250
250
  if (options?.mimeType) headers["content-type"] = options.mimeType;
251
251
  if (options?.overwrite === false) headers["if-none-match"] = "*";
252
252
  try {
253
- await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path$4))}`, {
253
+ await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
254
254
  method: "PUT",
255
255
  headers,
256
256
  body: contentToBody(content)
257
257
  });
258
258
  } catch (error) {
259
- if (typeof error === "object" && error !== null && "status" in error && error.status === 412) throw new _mastra_core_workspace.FileExistsError(path$4);
259
+ if (typeof error === "object" && error !== null && "status" in error && error.status === 412) throw new _mastra_core_workspace.FileExistsError(path);
260
260
  throw error;
261
261
  }
262
262
  }
@@ -270,21 +270,21 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
270
270
  * the same path can overwrite each other's writes ("last write wins").
271
271
  * Use `writeFile` with distinct keys for concurrent writers.
272
272
  */
273
- async appendFile(path$5, content) {
274
- const existing = await this.exists(path$5) ? await this.readFile(path$5) : buffer.Buffer.alloc(0);
275
- await this.writeFile(path$5, buffer.Buffer.concat([buffer.Buffer.isBuffer(existing) ? existing : buffer.Buffer.from(existing), buffer.Buffer.from(content)]));
273
+ async appendFile(path, content) {
274
+ const existing = await this.exists(path) ? await this.readFile(path) : buffer.Buffer.alloc(0);
275
+ await this.writeFile(path, buffer.Buffer.concat([buffer.Buffer.isBuffer(existing) ? existing : buffer.Buffer.from(existing), buffer.Buffer.from(content)]));
276
276
  }
277
- async deleteFile(path$6, options) {
277
+ async deleteFile(path, options) {
278
278
  await this.ensureReady();
279
279
  if (this.readOnly) throw new _mastra_core_workspace.WorkspaceReadOnlyError("deleteFile");
280
280
  try {
281
- await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path$6))}`, {
281
+ await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
282
282
  method: "DELETE",
283
283
  query: { recursive: options?.recursive }
284
284
  });
285
285
  } catch (error) {
286
286
  if (isNotFound(error) && options?.force) return;
287
- if (isNotFound(error)) throw new _mastra_core_workspace.FileNotFoundError(path$6);
287
+ if (isNotFound(error)) throw new _mastra_core_workspace.FileNotFoundError(path);
288
288
  throw error;
289
289
  }
290
290
  }
@@ -310,23 +310,23 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
310
310
  body: JSON.stringify({ destination: keyFromPath(dest) })
311
311
  });
312
312
  }
313
- async mkdir(path$7, _options) {
313
+ async mkdir(path, _options) {
314
314
  await this.ensureReady();
315
315
  if (this.readOnly) throw new _mastra_core_workspace.WorkspaceReadOnlyError("mkdir");
316
- await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path$7))}`, {
316
+ await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, {
317
317
  method: "POST",
318
318
  query: { op: "mkdir" }
319
319
  });
320
320
  }
321
- async rmdir(path$8, options) {
322
- await this.deleteFile(path$8.endsWith("/") ? path$8 : `${path$8}/`, {
321
+ async rmdir(path, options) {
322
+ await this.deleteFile(path.endsWith("/") ? path : `${path}/`, {
323
323
  recursive: true,
324
324
  force: options?.force
325
325
  });
326
326
  }
327
- async readdir(path$9, options) {
327
+ async readdir(path, options) {
328
328
  await this.ensureReady();
329
- const json = await this.listPrefix(keyFromPath(path$9), options?.recursive);
329
+ const json = await this.listPrefix(keyFromPath(path), options?.recursive);
330
330
  return [...(json.commonPrefixes ?? []).map((prefix) => ({
331
331
  name: nameFromPath(prefix.replace(/\/$/, "")),
332
332
  type: "directory"
@@ -334,7 +334,7 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
334
334
  name: nameFromPath(object.key),
335
335
  type: "file",
336
336
  size: object.size
337
- }))].filter((entry) => !options?.extension || entry.type === "directory" || matchesExtension(entry.name, options.extension));
337
+ }))].filter((entry) => !options?.extension || entry.type === "directory" || matchesExtension$1(entry.name, options.extension));
338
338
  }
339
339
  async listPrefix(prefix, recursive) {
340
340
  const normalized = prefix.replace(/\/$/, "");
@@ -343,18 +343,18 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
343
343
  prefix: normalized ? `${normalized}/` : void 0
344
344
  } })).json();
345
345
  }
346
- async exists(path$10) {
346
+ async exists(path) {
347
347
  try {
348
- await this.stat(path$10);
348
+ await this.stat(path);
349
349
  return true;
350
350
  } catch (error) {
351
351
  if (isNotFound(error) || error instanceof _mastra_core_workspace.FileNotFoundError) return false;
352
352
  throw error;
353
353
  }
354
354
  }
355
- async stat(path$11) {
355
+ async stat(path) {
356
356
  await this.ensureReady();
357
- const normalized = normalizePath(path$11);
357
+ const normalized = normalizePath(path);
358
358
  if (normalized === "/") return {
359
359
  name: "",
360
360
  path: "/",
@@ -365,13 +365,13 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
365
365
  };
366
366
  let response;
367
367
  try {
368
- response = await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path$11))}`, { method: "HEAD" });
368
+ response = await this._client.request(`/fs/${encodeURIComponent(this._bucketName)}/${encodeKeyPath(keyFromPath(path))}`, { method: "HEAD" });
369
369
  } catch (error) {
370
370
  if (!isNotFound(error)) throw error;
371
- const listing = await this.listPrefix(keyFromPath(path$11));
372
- if (!listing.contents?.length && !listing.commonPrefixes?.length) throw new _mastra_core_workspace.FileNotFoundError(path$11);
371
+ const listing = await this.listPrefix(keyFromPath(path));
372
+ if (!listing.contents?.length && !listing.commonPrefixes?.length) throw new _mastra_core_workspace.FileNotFoundError(path);
373
373
  return {
374
- name: nameFromPath(path$11),
374
+ name: nameFromPath(path),
375
375
  path: normalized,
376
376
  type: "directory",
377
377
  size: 0,
@@ -380,7 +380,7 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
380
380
  };
381
381
  }
382
382
  return {
383
- name: nameFromPath(path$11),
383
+ name: nameFromPath(path),
384
384
  path: normalized,
385
385
  type: normalized.endsWith("/") ? "directory" : "file",
386
386
  size: headerSize(response.headers),
@@ -389,8 +389,8 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
389
389
  mimeType: response.headers.get("content-type") ?? void 0
390
390
  };
391
391
  }
392
- realpath(path$12) {
393
- return Promise.resolve(normalizePath(path$12));
392
+ realpath(path) {
393
+ return Promise.resolve(normalizePath(path));
394
394
  }
395
395
  getInstructions(opts) {
396
396
  const defaultInstructions = `Platform filesystem backed by Mastra Platform bucket ${this._bucketName}. Use absolute workspace paths.`;
@@ -417,7 +417,7 @@ var PlatformFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
417
417
  };
418
418
  }
419
419
  };
420
- function matchesExtension(name, extension) {
420
+ function matchesExtension$1(name, extension) {
421
421
  return (Array.isArray(extension) ? extension : [extension]).some((ext) => name.endsWith(ext));
422
422
  }
423
423
  //#endregion
@@ -425,8 +425,8 @@ function matchesExtension(name, extension) {
425
425
  const SERIALIZE_TEMPLATE = Symbol("serializeTemplate");
426
426
  const GET_TEMPLATE_BUILD_ENVS = Symbol("getTemplateBuildEnvs");
427
427
  const MAX_OPERATIONS = 256;
428
- const MAX_SERIALIZED_BYTES = 256 * 1024;
429
- const MAX_STRING_LENGTH = 32 * 1024;
428
+ const MAX_SERIALIZED_BYTES = 262144;
429
+ const MAX_STRING_LENGTH = 32768;
430
430
  const MAX_COLLECTION_ITEMS = 512;
431
431
  const MAX_FAMILY_LENGTH = 200;
432
432
  var SerializableSandboxTemplateBuilder = class SerializableSandboxTemplateBuilder {
@@ -809,6 +809,14 @@ const execViaE2BLease = async (lease, options) => {
809
809
  };
810
810
  }
811
811
  };
812
+ /** Opens E2B's files API directly against the VM, authenticated by the exec lease. */
813
+ const e2bFilesForLease = (lease) => new e2b.Sandbox({
814
+ sandboxId: lease.sandboxId,
815
+ envdVersion: E2B_ENVD_VERSION,
816
+ envdAccessToken: lease.jwt,
817
+ sandboxUrl: lease.wsEndpoint,
818
+ validateApiKey: false
819
+ }).files;
812
820
  //#endregion
813
821
  //#region src/private-net-exec.ts
814
822
  /**
@@ -979,6 +987,499 @@ async function execViaPrivateNetwork(instanceUrl, options) {
979
987
  };
980
988
  }
981
989
  //#endregion
990
+ //#region ../../packages/_internals/workspace/dist/index.js
991
+ /** Directory, relative to the template's build cwd, that holds every marker below. */
992
+ const SETUP_MARKER_DIR = ".mastra-sandbox";
993
+ /**
994
+ * Setup completion marker shared by repo templates and their consumers.
995
+ *
996
+ * A repo template writes this file beside the checkout as its last build
997
+ * step, so it exists only in images where every setup command succeeded. Its
998
+ * content is a digest of the setup commands the image ran, letting a sandbox
999
+ * booted from the image tell whether the setup it is about to run already
1000
+ * happened. Relative to the template's build cwd, which is also the runtime
1001
+ * working directory the repo was cloned into.
1002
+ *
1003
+ * This is the single-repository marker. Templates built from a repository
1004
+ * list write one marker per repository instead ({@link repoSetupMarkerPath}).
1005
+ */
1006
+ const SETUP_MARKER_PATH = ".mastra-sandbox/setup";
1007
+ /**
1008
+ * Marker for the workspace-level setup of a multi-repository template: the
1009
+ * commands that ran at the working directory after every repository was set
1010
+ * up. Written even when there were no such commands, so a consumer can tell
1011
+ * "ran nothing" from "never built".
1012
+ */
1013
+ const WORKSPACE_SETUP_MARKER_PATH = ".mastra-sandbox/workspace-setup";
1014
+ /**
1015
+ * One line per repository directory whose setup failed while the build was
1016
+ * told to continue. Absent when nothing failed. Consumers check this before
1017
+ * trusting a per-repository marker: that marker means every step ran, not
1018
+ * that every step succeeded.
1019
+ */
1020
+ const SETUP_FAILED_MARKER_PATH = ".mastra-sandbox/setup-failed";
1021
+ /** Per-repository marker of a multi-repository template, keyed by the clone directory name. */
1022
+ function repoSetupMarkerPath(repoDir) {
1023
+ return `${SETUP_MARKER_DIR}/repos/${repoDir}`;
1024
+ }
1025
+ /** Blank entries never become build steps, so they never count toward the digest either. */
1026
+ function normalizeSetupCommands(setupCommand) {
1027
+ return (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== "");
1028
+ }
1029
+ /** The marker content for a setup command list: `sha256:<hex>` over the commands joined by newlines. */
1030
+ function setupMarkerContent(setupCommand) {
1031
+ return `sha256:${(0, crypto.createHash)("sha256").update(normalizeSetupCommands(setupCommand).join("\n")).digest("hex")}`;
1032
+ }
1033
+ /** Shell step that writes the marker relative to the cwd. `content` is a digest, so it is shell-safe. */
1034
+ function setupMarkerCommand(content, markerPath = SETUP_MARKER_PATH) {
1035
+ return `mkdir -p "$(dirname "${markerPath}")" && printf '%s' '${content}' > "${markerPath}"`;
1036
+ }
1037
+ /** A clone directory name is one path segment of word characters, dots and dashes, so it never needs quoting. */
1038
+ const REPO_DIR_PATTERN = /^[\w.-]+$/;
1039
+ /**
1040
+ * Shell step that runs one setup command inside a repository clone.
1041
+ *
1042
+ * Without the guard this is the plain `cd "<dir>" && <cmd>` step. With it,
1043
+ * the `cd` sits inside the parenthesized group so that the failure branch
1044
+ * runs back at the build cwd and the failure list lands at the workspace
1045
+ * level, never inside the repository.
1046
+ */
1047
+ function guardedSetupCommand({ repoDir, command, continueOnFailure }) {
1048
+ if (!REPO_DIR_PATTERN.test(repoDir)) throw new TypeError(`Repository directory "${repoDir}" must match ${REPO_DIR_PATTERN}.`);
1049
+ if (!continueOnFailure) return `cd "${repoDir}" && ${command}`;
1050
+ return `( cd "${repoDir}" && sh -c ${shellQuote$2(command)} ) || { mkdir -p "${SETUP_MARKER_DIR}" && grep -qxF -- '${repoDir}' "${SETUP_FAILED_MARKER_PATH}" 2>/dev/null || printf '%s\\n' '${repoDir}' >> "${SETUP_FAILED_MARKER_PATH}"; }`;
1051
+ }
1052
+ function shellQuote$2(value) {
1053
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1054
+ }
1055
+ function repoCloneCommand({ cloneUrl, destination, branch, tokenEnv }) {
1056
+ return `git ${tokenEnv ? `${gitAuthFlag$1(tokenEnv)} ` : ""}clone --depth=1 --single-branch ${branch ? `--branch ${shellQuote$1(branch)} ` : ""}${shellQuote$1(cloneUrl)} ${shellQuote$1(destination)}`;
1057
+ }
1058
+ /** Per-invocation auth header; `-c` config never reaches `.git/config`. */
1059
+ function gitAuthFlag$1(tokenEnv) {
1060
+ return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${tokenEnv}" | base64 -w0)"`;
1061
+ }
1062
+ function shellQuote$1(value) {
1063
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1064
+ }
1065
+ /**
1066
+ * Shell scripts for the file operations a sandbox can only do atomically (or
1067
+ * without moving file bytes over the network) by running a command inside the
1068
+ * VM. Each builder takes absolute paths and returns one POSIX `sh` script; the
1069
+ * caller runs it with `sh -c` and maps the exit codes below to typed errors.
1070
+ */
1071
+ /** Exit codes the scripts use for outcomes the caller turns into typed errors. Any other non-zero code is a plain failure. */
1072
+ const FILE_COMMAND_EXIT = {
1073
+ notFound: 20,
1074
+ isDirectory: 21,
1075
+ exists: 22,
1076
+ notDirectory: 23,
1077
+ destIsDirectory: 24
1078
+ };
1079
+ /** Single-quote a string for safe POSIX shell interpolation. */
1080
+ function shellQuote$3(value) {
1081
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1082
+ }
1083
+ const { notFound, isDirectory, exists, notDirectory, destIsDirectory } = FILE_COMMAND_EXIT;
1084
+ /** `[ -e ]` is false for a broken symlink, which still occupies the path. */
1085
+ const present = (variable) => `{ [ -e "$${variable}" ] || [ -L "$${variable}" ]; }`;
1086
+ /**
1087
+ * A sibling temp path for `path`, so a later `ln` / `mv` onto `path` stays on
1088
+ * one filesystem. Hidden, and unique per call.
1089
+ */
1090
+ function siblingTempPath(path$1) {
1091
+ const suffix = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
1092
+ return path.posix.join(path.posix.dirname(path$1), `.${path.posix.basename(path$1)}.mastra-tmp-${suffix}`);
1093
+ }
1094
+ /**
1095
+ * Hard-link `$tmp` to `$dest` and delete `$tmp`, failing with `exists` when
1096
+ * something is at `$dest`. `ln` onto a directory (or a link to one) would
1097
+ * create the link inside it and succeed, so that case is undone and reported
1098
+ * as taken too.
1099
+ */
1100
+ function linkTempIntoPlace() {
1101
+ return [
1102
+ `ln "$tmp" "$dest" 2>/dev/null || { rm -f "$tmp"; ${present("dest")} && exit ${exists}; exit 1; }`,
1103
+ `if [ -d "$dest" ]; then rm -f "$dest/$(basename "$tmp")" "$tmp"; exit ${exists}; fi`,
1104
+ `rm -f "$tmp"`
1105
+ ];
1106
+ }
1107
+ /**
1108
+ * Copy `src` to `dest` inside the sandbox, creating `dest`'s parent.
1109
+ *
1110
+ * A file source copies its content (a symlinked source is followed). A
1111
+ * directory source with `recursive` copies its contents into `dest`, keeping
1112
+ * symlinks inside the tree as links. With `overwrite: false` the destination
1113
+ * is claimed atomically: a directory with an exclusive `mkdir`, a file by
1114
+ * copying to a temp name and hard-linking it into place (`link(2)` fails when
1115
+ * the destination exists).
1116
+ *
1117
+ * Exits: `notFound` (no source), `isDirectory` (directory source without
1118
+ * `recursive`), `destIsDirectory` (a file onto an existing directory or a link
1119
+ * to one), `exists`.
1120
+ */
1121
+ function copyCommand(src, dest, options = {}) {
1122
+ const noClobber = options.overwrite === false;
1123
+ return [
1124
+ `src=${shellQuote$3(src)}`,
1125
+ `dest=${shellQuote$3(dest)}`,
1126
+ `[ -e "$src" ] || exit ${notFound}`,
1127
+ `mkdir -p "$(dirname "$dest")" || exit 1`,
1128
+ `if [ -d "$src" ]; then`,
1129
+ options.recursive ? ` :` : ` exit ${isDirectory}`,
1130
+ noClobber ? ` mkdir "$dest" 2>/dev/null || exit ${exists}` : ` mkdir -p "$dest" || exit 1`,
1131
+ ` cp -R -P "$src"/. "$dest"/ || exit 1`,
1132
+ ` exit 0`,
1133
+ `fi`,
1134
+ noClobber ? `${present("dest")} && exit ${exists}` : `if [ -d "$dest" ]; then exit ${destIsDirectory}; fi`,
1135
+ ...noClobber ? [
1136
+ `tmp=${shellQuote$3(siblingTempPath(dest))}`,
1137
+ `cp "$src" "$tmp" || { rm -f "$tmp"; exit 1; }`,
1138
+ ...linkTempIntoPlace()
1139
+ ] : [`cp "$src" "$dest" || exit 1`]
1140
+ ].join("\n");
1141
+ }
1142
+ /**
1143
+ * Move `src` to `dest` without replacing anything at `dest`, creating `dest`'s
1144
+ * parent. A skipped `mv -n` is detected by the source surviving, whatever
1145
+ * its exit code. If a directory appears at `dest` between the check and the
1146
+ * move, `mv` moves `src` into it (BSD `mv` has no `-T`), and that is reported
1147
+ * as success; the no-clobber rename itself is atomic.
1148
+ *
1149
+ * Exits: `notFound` (no source), `exists`.
1150
+ */
1151
+ function moveNoClobberCommand(src, dest) {
1152
+ return [
1153
+ `src=${shellQuote$3(src)}`,
1154
+ `dest=${shellQuote$3(dest)}`,
1155
+ `${present("src")} || exit ${notFound}`,
1156
+ `${present("dest")} && exit ${exists}`,
1157
+ `mkdir -p "$(dirname "$dest")" || exit 1`,
1158
+ `mv -n "$src" "$dest" 2>/dev/null || { ${present("src")} && ${present("dest")} && exit ${exists}; exit 1; }`,
1159
+ `${present("src")} && exit ${exists}`,
1160
+ `exit 0`
1161
+ ].join("\n");
1162
+ }
1163
+ /**
1164
+ * Append the bytes already written to `tmp` onto `dest`, then delete `tmp`.
1165
+ * The content travels as a file rather than a command argument, so it has no
1166
+ * size limit. Creates `dest` (and its parent) when missing.
1167
+ *
1168
+ * Exits: `isDirectory` (dest is a directory).
1169
+ */
1170
+ function appendFromCommand(tmp, dest) {
1171
+ return [
1172
+ `tmp=${shellQuote$3(tmp)}`,
1173
+ `dest=${shellQuote$3(dest)}`,
1174
+ `if [ -d "$dest" ]; then rm -f "$tmp"; exit ${isDirectory}; fi`,
1175
+ `mkdir -p "$(dirname "$dest")" && cat "$tmp" >> "$dest"`,
1176
+ `status=$?`,
1177
+ `rm -f "$tmp"`,
1178
+ `exit $status`
1179
+ ].join("\n");
1180
+ }
1181
+ /**
1182
+ * Publish the file already written to `tmp` at `dest` only if nothing is
1183
+ * there, then delete `tmp`. `ln` (`link(2)`) is the atomic exclusivity check;
1184
+ * `tmp` must be on the same filesystem, e.g. from {@link siblingTempPath}.
1185
+ *
1186
+ * Exits: `exists`, `notDirectory` (dest's parent is not a directory).
1187
+ */
1188
+ function linkNoClobberCommand(tmp, dest) {
1189
+ return [
1190
+ `tmp=${shellQuote$3(tmp)}`,
1191
+ `dest=${shellQuote$3(dest)}`,
1192
+ `if [ ! -d "$(dirname "$dest")" ]; then rm -f "$tmp"; exit ${notDirectory}; fi`,
1193
+ `if ${present("dest")}; then rm -f "$tmp"; exit ${exists}; fi`,
1194
+ ...linkTempIntoPlace()
1195
+ ].join("\n");
1196
+ }
1197
+ /** Default recursion depth for `readdir({ recursive: true })`, matching `LocalFilesystem`. */
1198
+ const DEFAULT_MAX_DEPTH = 100;
1199
+ const toAbsolute = (path$2) => path.posix.resolve("/", path$2);
1200
+ /** The entry's own type: a symlink is a link even when it points at a directory. */
1201
+ const isSymlink = (info) => info.symlinkTarget !== void 0;
1202
+ function toWriteData(content) {
1203
+ if (typeof content === "string") return content;
1204
+ return content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength);
1205
+ }
1206
+ function matchesExtension(name, extension) {
1207
+ if (!extension) return true;
1208
+ const ext = path.posix.extname(name);
1209
+ return (Array.isArray(extension) ? extension : [extension]).some((e) => e === ext || e === ext.slice(1));
1210
+ }
1211
+ /**
1212
+ * A sandbox's disk, read and written through E2B's envd files API, with the
1213
+ * operations the files API can't do in one call (copy, append, no-clobber
1214
+ * writes and moves) run as one shell command inside the VM so they are atomic
1215
+ * and file bytes never round-trip through the host. Paths are absolute inside
1216
+ * the sandbox; this class does not confine paths or symlinks to any root.
1217
+ *
1218
+ * Providers supply how to reach the files API and the shell, and how to read
1219
+ * their SDK's errors.
1220
+ *
1221
+ * If a provider's `runCommand` re-runs a command whose first run already
1222
+ * finished (e.g. a retry after a dropped connection), the second run sees the
1223
+ * first's result: a no-clobber copy, write or move reports `FileExistsError`
1224
+ * or `FileNotFoundError`, and an append fails, even though the work was done.
1225
+ */
1226
+ var FilesApiFilesystem = class extends _mastra_core_workspace.MastraFilesystem {
1227
+ async readFile(path, options) {
1228
+ const abs = toAbsolute(path);
1229
+ const bytes = await this.runFiles("readFile", async (files) => {
1230
+ try {
1231
+ return await files.read(abs, { format: "bytes" });
1232
+ } catch (error) {
1233
+ const kind = this.errorKind(error);
1234
+ if (kind === "not-found") throw new _mastra_core_workspace.FileNotFoundError(path);
1235
+ if (kind === "invalid-argument") throw new _mastra_core_workspace.IsDirectoryError(path);
1236
+ throw error;
1237
+ }
1238
+ });
1239
+ const buffer$1 = buffer.Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1240
+ return options?.encoding ? buffer$1.toString(options.encoding) : buffer$1;
1241
+ }
1242
+ async writeFile(path$3, content, options) {
1243
+ const abs = toAbsolute(path$3);
1244
+ if (options?.overwrite !== false) {
1245
+ await this.runFiles("writeFile", async (files) => {
1246
+ if (options?.recursive === false) await this.#assertParentDirectory(files, abs, path$3);
1247
+ await this.#write(files, abs, path$3, toWriteData(content));
1248
+ });
1249
+ return;
1250
+ }
1251
+ const tmp = siblingTempPath(abs);
1252
+ const upload = async (files) => {
1253
+ if (options.recursive === false) await this.#assertParentDirectory(files, abs, path$3);
1254
+ await this.#write(files, tmp, path$3, toWriteData(content));
1255
+ };
1256
+ await this.#publishTemp(tmp, "writeFile", upload, linkNoClobberCommand(tmp, abs), path$3, {
1257
+ exists: () => new _mastra_core_workspace.FileExistsError(path$3),
1258
+ notDirectory: () => new _mastra_core_workspace.NotDirectoryError(path.posix.dirname(path$3))
1259
+ });
1260
+ }
1261
+ async appendFile(path, content) {
1262
+ const abs = toAbsolute(path);
1263
+ const tmp = siblingTempPath(abs);
1264
+ const upload = (files) => this.#write(files, tmp, path, toWriteData(content));
1265
+ await this.#publishTemp(tmp, "appendFile", upload, appendFromCommand(tmp, abs), path, { isDirectory: () => new _mastra_core_workspace.IsDirectoryError(path) });
1266
+ }
1267
+ async deleteFile(path, options) {
1268
+ const abs = toAbsolute(path);
1269
+ await this.runFiles("deleteFile", async (files) => {
1270
+ const info = await this.#info(files, abs);
1271
+ if (!info) {
1272
+ if (options?.force) return;
1273
+ throw new _mastra_core_workspace.FileNotFoundError(path);
1274
+ }
1275
+ if (!isSymlink(info) && info.type === "dir") throw new _mastra_core_workspace.IsDirectoryError(path);
1276
+ await files.remove(abs);
1277
+ });
1278
+ }
1279
+ /**
1280
+ * One `cp` inside the VM. A recursive copy keeps symlinks inside the tree as
1281
+ * links; `overwrite: false` claims the destination atomically.
1282
+ */
1283
+ async copyFile(src, dest, options) {
1284
+ const command = copyCommand(toAbsolute(src), toAbsolute(dest), {
1285
+ recursive: options?.recursive,
1286
+ overwrite: options?.overwrite
1287
+ });
1288
+ await this.#command("copyFile", command, `${src} -> ${dest}`, {
1289
+ notFound: () => new _mastra_core_workspace.FileNotFoundError(src),
1290
+ isDirectory: () => new _mastra_core_workspace.IsDirectoryError(src),
1291
+ destIsDirectory: () => new _mastra_core_workspace.IsDirectoryError(dest),
1292
+ exists: () => new _mastra_core_workspace.FileExistsError(dest)
1293
+ });
1294
+ }
1295
+ async moveFile(src, dest, options) {
1296
+ const srcAbs = toAbsolute(src);
1297
+ const destAbs = toAbsolute(dest);
1298
+ if (options?.overwrite === false) {
1299
+ await this.#command("moveFile", moveNoClobberCommand(srcAbs, destAbs), `${src} -> ${dest}`, {
1300
+ notFound: () => new _mastra_core_workspace.FileNotFoundError(src),
1301
+ exists: () => new _mastra_core_workspace.FileExistsError(dest)
1302
+ });
1303
+ return;
1304
+ }
1305
+ await this.runFiles("moveFile", async (files) => {
1306
+ try {
1307
+ await files.rename(srcAbs, destAbs);
1308
+ } catch (error) {
1309
+ if (this.errorKind(error) === "not-found") throw new _mastra_core_workspace.FileNotFoundError(src);
1310
+ throw error;
1311
+ }
1312
+ });
1313
+ }
1314
+ async mkdir(path, options) {
1315
+ const abs = toAbsolute(path);
1316
+ await this.runFiles("mkdir", async (files) => {
1317
+ if (options?.recursive === false) await this.#assertParentDirectory(files, abs, path);
1318
+ try {
1319
+ await files.makeDir(abs);
1320
+ } catch (error) {
1321
+ if (this.errorKind(error) === "invalid-argument") throw new _mastra_core_workspace.FileExistsError(path);
1322
+ throw error;
1323
+ }
1324
+ });
1325
+ }
1326
+ async rmdir(path, options) {
1327
+ const abs = toAbsolute(path);
1328
+ await this.runFiles("rmdir", async (files) => {
1329
+ const info = await this.#info(files, abs);
1330
+ if (!info) {
1331
+ if (options?.force) return;
1332
+ throw new _mastra_core_workspace.DirectoryNotFoundError(path);
1333
+ }
1334
+ if (!options?.recursive) {
1335
+ if (isSymlink(info) || info.type !== "dir") throw new _mastra_core_workspace.NotDirectoryError(path);
1336
+ if ((await files.list(abs)).length > 0) throw new _mastra_core_workspace.DirectoryNotEmptyError(path);
1337
+ } else if (!isSymlink(info) && info.type !== "dir") throw new _mastra_core_workspace.NotDirectoryError(path);
1338
+ await files.remove(abs);
1339
+ });
1340
+ }
1341
+ async readdir(path$4, options) {
1342
+ const abs = toAbsolute(path$4);
1343
+ const depth = options?.recursive ? (options.maxDepth ?? DEFAULT_MAX_DEPTH) + 1 : 1;
1344
+ const entries = await this.runFiles("readdir", async (files) => {
1345
+ try {
1346
+ return await files.list(abs, { depth });
1347
+ } catch (error) {
1348
+ const kind = this.errorKind(error);
1349
+ if (kind === "not-found") throw new _mastra_core_workspace.DirectoryNotFoundError(path$4);
1350
+ if (kind === "invalid-argument") throw new _mastra_core_workspace.NotDirectoryError(path$4);
1351
+ throw error;
1352
+ }
1353
+ });
1354
+ const result = [];
1355
+ for (const entry of entries) {
1356
+ if (!entry.type) continue;
1357
+ const name = path.posix.relative(abs, entry.path);
1358
+ const type = entry.type === "dir" ? "directory" : "file";
1359
+ if (type === "file" && !matchesExtension(name, options?.extension)) continue;
1360
+ const link = isSymlink(entry);
1361
+ result.push({
1362
+ name,
1363
+ type,
1364
+ ...type === "file" && !link && { size: entry.size },
1365
+ ...link && {
1366
+ isSymlink: true,
1367
+ symlinkTarget: entry.symlinkTarget
1368
+ }
1369
+ });
1370
+ }
1371
+ return result;
1372
+ }
1373
+ async exists(path) {
1374
+ const abs = toAbsolute(path);
1375
+ return this.runFiles("exists", async (files) => Boolean((await this.#info(files, abs))?.type));
1376
+ }
1377
+ async stat(path$5) {
1378
+ const abs = toAbsolute(path$5);
1379
+ return this.runFiles("stat", async (files) => {
1380
+ let info = await this.#info(files, abs);
1381
+ if (info?.type && isSymlink(info)) info = await this.#info(files, path.posix.resolve(path.posix.dirname(abs), info.symlinkTarget));
1382
+ if (!info?.type) throw new _mastra_core_workspace.FileNotFoundError(path$5);
1383
+ const modifiedAt = info.modifiedTime ?? /* @__PURE__ */ new Date(0);
1384
+ return {
1385
+ name: path.posix.basename(abs),
1386
+ path: abs,
1387
+ type: info.type === "dir" ? "directory" : "file",
1388
+ size: info.type === "dir" ? 0 : info.size,
1389
+ createdAt: modifiedAt,
1390
+ modifiedAt
1391
+ };
1392
+ });
1393
+ }
1394
+ /** `getInfo` without following the entry itself, or `undefined` when nothing is there. */
1395
+ async #info(files, abs) {
1396
+ try {
1397
+ return await files.getInfo(abs);
1398
+ } catch (error) {
1399
+ if (this.errorKind(error) === "not-found") return void 0;
1400
+ throw error;
1401
+ }
1402
+ }
1403
+ async #assertParentDirectory(files, abs, path$6) {
1404
+ const parent = await this.#info(files, path.posix.dirname(abs));
1405
+ if (!parent?.type) throw new _mastra_core_workspace.DirectoryNotFoundError(path.posix.dirname(path$6));
1406
+ if (parent.type !== "dir") throw new _mastra_core_workspace.NotDirectoryError(path.posix.dirname(path$6));
1407
+ }
1408
+ async #write(files, abs, path, data) {
1409
+ try {
1410
+ await files.write(abs, data);
1411
+ } catch (error) {
1412
+ if (this.errorKind(error) === "invalid-argument") throw new _mastra_core_workspace.IsDirectoryError(path);
1413
+ throw error;
1414
+ }
1415
+ }
1416
+ /**
1417
+ * Upload the temp file `tmp`, then run the command that consumes it. The
1418
+ * script deletes it on every path it controls; if the upload or the command
1419
+ * fails part-way, delete it here so it doesn't linger in the user's tree.
1420
+ */
1421
+ async #publishTemp(tmp, operation, upload, script, subject, errors) {
1422
+ try {
1423
+ await this.runFiles(operation, upload);
1424
+ await this.#command(operation, script, subject, errors);
1425
+ } catch (error) {
1426
+ await this.runFiles(operation, (files) => files.remove(tmp)).catch(() => {});
1427
+ throw error;
1428
+ }
1429
+ }
1430
+ /** Run a file command and turn its known exit codes into typed errors. */
1431
+ async #command(operation, script, subject, errors) {
1432
+ const result = await this.runCommand(operation, script);
1433
+ if (result.exitCode === 0) return;
1434
+ for (const [outcome, code] of Object.entries(FILE_COMMAND_EXIT)) {
1435
+ const toError = errors[outcome];
1436
+ if (code === result.exitCode && toError) throw toError();
1437
+ }
1438
+ throw new Error(`${operation} ${subject} failed (exit ${result.exitCode}): ${result.stderr.trim()}`);
1439
+ }
1440
+ };
1441
+ //#endregion
1442
+ //#region src/sandbox-filesystem.ts
1443
+ /**
1444
+ * envd's edge answers a paused or missing sandbox with a 404 or 502 that the
1445
+ * SDK reports as `FileNotFoundError` or `TimeoutError`. Same message pattern as
1446
+ * `E2BSandbox`'s dead-sandbox check.
1447
+ */
1448
+ const DEAD_SANDBOX = /\b(?:paused\s+)?sandbox(?:\s+\S+)?\s+(?:was\s+)?not found\b/i;
1449
+ const isDeadSandbox = (error) => error instanceof e2b.SandboxNotFoundError || error instanceof Error && DEAD_SANDBOX.test(error.message);
1450
+ /** A failure that isn't a file-level answer from envd: the sandbox is gone, the call timed out, or it failed before envd replied. */
1451
+ const isBrokenSandboxError = (error) => isDeadSandbox(error) || error instanceof e2b.TimeoutError || !(error instanceof e2b.SandboxError);
1452
+ /**
1453
+ * The disk of a {@link PlatformSandbox}, read and written through E2B's files
1454
+ * API directly on the VM, authenticated by the same lease `executeCommand`
1455
+ * uses. Copies, appends and no-clobber writes and moves run as one shell
1456
+ * command in the VM, so they are atomic and file bytes don't round-trip
1457
+ * through the host. Paths are absolute inside the sandbox; this class does not
1458
+ * confine paths or symlinks to any root.
1459
+ */
1460
+ var PlatformSandboxFilesystem = class extends FilesApiFilesystem {
1461
+ id;
1462
+ name = "PlatformSandboxFilesystem";
1463
+ provider = "platform";
1464
+ status = "ready";
1465
+ #runner;
1466
+ constructor(runner) {
1467
+ super({ name: "PlatformSandboxFilesystem" });
1468
+ this.id = `${runner.id}-filesystem`;
1469
+ this.#runner = runner;
1470
+ }
1471
+ runFiles(operation, fn) {
1472
+ return this.#runner.withFiles(operation, fn);
1473
+ }
1474
+ runCommand(_operation, script) {
1475
+ return this.#runner.runCommand(script);
1476
+ }
1477
+ errorKind(error) {
1478
+ if (error instanceof e2b.NotFoundError && !isDeadSandbox(error)) return "not-found";
1479
+ if (error instanceof e2b.InvalidArgumentError) return "invalid-argument";
1480
+ }
1481
+ };
1482
+ //#endregion
982
1483
  //#region src/sandbox.ts
983
1484
  /**
984
1485
  * How long before a lease's stated `expiresAt` we should treat it as
@@ -1061,6 +1562,8 @@ var SandboxDestroyedError = class extends Error {
1061
1562
  this.attempts = diagnostics.attempts;
1062
1563
  }
1063
1564
  };
1565
+ /** Cap on one file command (a recursive copy can be large) when the sandbox sets no timeout. */
1566
+ const FILE_COMMAND_TIMEOUT_MS = 6e5;
1064
1567
  /**
1065
1568
  * Compose a shell command line from a `command` string and optional `args`.
1066
1569
  *
@@ -1076,9 +1579,9 @@ var SandboxDestroyedError = class extends Error {
1076
1579
  * sandbox.
1077
1580
  */
1078
1581
  function buildCommand(command, args) {
1079
- return args?.length ? `${command} ${args.map(shellQuote$2).join(" ")}` : command;
1582
+ return args?.length ? `${command} ${args.map(shellQuote).join(" ")}` : command;
1080
1583
  }
1081
- function shellQuote$2(arg) {
1584
+ function shellQuote(arg) {
1082
1585
  if (/^[a-zA-Z0-9._\-/=:@]+$/.test(arg)) return arg;
1083
1586
  return `'${arg.replace(/'/g, `'\\''`)}'`;
1084
1587
  }
@@ -1179,6 +1682,10 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
1179
1682
  _createdAt = null;
1180
1683
  _webSocketFactory;
1181
1684
  _e2bExecRunner;
1685
+ _e2bFilesFactory;
1686
+ /** Files handle for the current lease, rebuilt whenever the lease changes. */
1687
+ _e2bFiles = null;
1688
+ _filesystem;
1182
1689
  _privateNetFetch;
1183
1690
  /**
1184
1691
  * Registry that maps `sandboxId → instanceUrl` for the private-network
@@ -1268,6 +1775,7 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
1268
1775
  this._instructionsOverride = options.instructions;
1269
1776
  this._webSocketFactory = options.webSocketFactory;
1270
1777
  this._e2bExecRunner = options.e2bExecRunner ?? execViaE2BLease;
1778
+ this._e2bFilesFactory = options.e2bFilesFactory ?? e2bFilesForLease;
1271
1779
  this._privateNetFetch = options.privateNetFetch;
1272
1780
  this._addressRegistry = options.addressRegistry;
1273
1781
  }
@@ -1774,6 +2282,15 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
1774
2282
  * execution on the remote sandbox.
1775
2283
  */
1776
2284
  async executeCommand(command, args, options) {
2285
+ return this._executeCommand(command, args, options, { once: false });
2286
+ }
2287
+ /**
2288
+ * `executeCommand`, optionally sent at most once. With `once`, a transport
2289
+ * failure after the command may have started is thrown instead of retried
2290
+ * (on the lease or over the private network), so a script that changes files
2291
+ * never runs twice.
2292
+ */
2293
+ async _executeCommand(command, args, options, { once }) {
1777
2294
  await this.ensureRunning();
1778
2295
  if (!this._sandboxId) throw new _mastra_core_workspace.SandboxNotReadyError(this.id);
1779
2296
  const started = Date.now();
@@ -1788,7 +2305,7 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
1788
2305
  }
1789
2306
  } : options;
1790
2307
  await this._awaitTransportReady();
1791
- const instanceUrl = this._addressRegistry?.get(this._sandboxId);
2308
+ const instanceUrl = once ? void 0 : this._addressRegistry?.get(this._sandboxId);
1792
2309
  if (instanceUrl) {
1793
2310
  const privateNet = await this._tryExecViaPrivateNetwork(instanceUrl, fullCommand, effectiveTimeout, execCallOptions);
1794
2311
  if (privateNet) {
@@ -1804,7 +2321,7 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
1804
2321
  };
1805
2322
  }
1806
2323
  }
1807
- const result = await this._runDirectExec(fullCommand, effectiveTimeout, execCallOptions);
2324
+ const result = await this._runDirectExec(fullCommand, effectiveTimeout, execCallOptions, once ? 1 : 2);
1808
2325
  const exitCode = result.exitCode ?? 124;
1809
2326
  return {
1810
2327
  success: exitCode === 0,
@@ -1849,12 +2366,12 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
1849
2366
  const filtered = Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== void 0));
1850
2367
  return Object.keys(filtered).length > 0 ? filtered : void 0;
1851
2368
  }
1852
- async _runDirectExec(fullCommand, effectiveTimeout, options) {
2369
+ async _runDirectExec(fullCommand, effectiveTimeout, options, attempts = 2) {
1853
2370
  const filteredEnv = this._execEnv(options);
1854
2371
  let lastResult;
1855
2372
  let lastLease;
1856
2373
  let attemptsMade = 0;
1857
- for (let attempt = 0; attempt < 2; attempt++) {
2374
+ for (let attempt = 0; attempt < attempts; attempt++) {
1858
2375
  if (attempt > 0 && lastLease && this._lease === lastLease) this._lease = null;
1859
2376
  let lease;
1860
2377
  try {
@@ -1986,6 +2503,91 @@ var PlatformSandbox = class PlatformSandbox extends _mastra_core_workspace.Mastr
1986
2503
  if (this._leaseInFlight === inFlight) this._leaseInFlight = null;
1987
2504
  }
1988
2505
  }
2506
+ /**
2507
+ * The sandbox's disk through E2B's files API, called directly on the VM with
2508
+ * the same lease `executeCommand` uses. Paths are absolute inside the
2509
+ * sandbox and are not confined to any root. Absent on non-E2B sandboxes, so
2510
+ * callers fall back to `executeCommand`.
2511
+ */
2512
+ get filesystem() {
2513
+ if (this._client.sandboxProvider !== "e2b") return void 0;
2514
+ this._filesystem ??= new PlatformSandboxFilesystem({
2515
+ id: this.id,
2516
+ withFiles: (operation, fn) => this._withE2BFiles(operation, fn),
2517
+ runCommand: (script) => this._executeCommand("sh", ["-c", script], this._timeout === void 0 ? { timeout: FILE_COMMAND_TIMEOUT_MS } : {}, { once: true })
2518
+ });
2519
+ return this._filesystem;
2520
+ }
2521
+ /**
2522
+ * Run a filesystem operation against the VM through the files API. Each
2523
+ * files call that envd rejects for its token is retried once with a fresh
2524
+ * lease: the rejection proves that call did nothing, and the calls before it
2525
+ * already succeeded, so nothing is replayed. Any other failure is thrown
2526
+ * unchanged. A dead sandbox or a broken transport also drops the cached
2527
+ * lease, so the next operation re-mints it and learns about a destroyed
2528
+ * sandbox (410) the same way `executeCommand` does.
2529
+ */
2530
+ async _withE2BFiles(operation, fn) {
2531
+ await this.ensureRunning();
2532
+ if (!this._sandboxId) throw new _mastra_core_workspace.SandboxNotReadyError(this.id);
2533
+ let current = await this._filesHandle(operation, 1);
2534
+ const call = async (invoke) => {
2535
+ try {
2536
+ return await invoke(current.files);
2537
+ } catch (error) {
2538
+ if (error instanceof e2b.AuthenticationError) {
2539
+ if (this._lease === current.lease) this._lease = null;
2540
+ current = await this._filesHandle(operation, 2);
2541
+ try {
2542
+ return await invoke(current.files);
2543
+ } catch (retryError) {
2544
+ if (isBrokenSandboxError(retryError) && this._lease === current.lease) this._lease = null;
2545
+ throw retryError;
2546
+ }
2547
+ }
2548
+ if (isBrokenSandboxError(error) && this._lease === current.lease) this._lease = null;
2549
+ throw error;
2550
+ }
2551
+ };
2552
+ return fn({
2553
+ read: (path, opts) => call((files) => files.read(path, opts)),
2554
+ write: (path, data) => call((files) => files.write(path, data)),
2555
+ list: (path, opts) => call((files) => files.list(path, opts)),
2556
+ makeDir: (path) => call((files) => files.makeDir(path)),
2557
+ rename: (oldPath, newPath) => call((files) => files.rename(oldPath, newPath)),
2558
+ remove: (path) => call((files) => files.remove(path)),
2559
+ getInfo: (path) => call((files) => files.getInfo(path))
2560
+ });
2561
+ }
2562
+ /** The files handle for the current lease, rebuilt only when the lease changes. */
2563
+ async _filesHandle(operation, attempts) {
2564
+ const lease = await this._fileLease(operation, attempts);
2565
+ if (lease.provider !== "e2b") throw new Error(`PlatformSandbox.filesystem requires an E2B-backed sandbox; this sandbox's provider is "${lease.provider}"`);
2566
+ if (this._e2bFiles?.lease !== lease) this._e2bFiles = {
2567
+ lease,
2568
+ files: this._e2bFilesFactory(lease)
2569
+ };
2570
+ return this._e2bFiles;
2571
+ }
2572
+ /** `_ensureLease`, with a 410 handled the same way `_runDirectExec` handles it. */
2573
+ async _fileLease(operation, attempts) {
2574
+ try {
2575
+ return await this._ensureLease();
2576
+ } catch (error) {
2577
+ if (error instanceof PlatformApiError && error.status === 410) {
2578
+ this._lease = null;
2579
+ const priorSandboxId = this._sandboxId;
2580
+ this._sandboxId = void 0;
2581
+ this.status = "stopped";
2582
+ throw new SandboxDestroyedError(`Sandbox ${priorSandboxId ?? "(unknown)"} was destroyed; /exec-lease returned 410`, {
2583
+ ...priorSandboxId && { sandboxId: priorSandboxId },
2584
+ command: `filesystem.${operation}`,
2585
+ attempts
2586
+ });
2587
+ }
2588
+ throw error;
2589
+ }
2590
+ }
1989
2591
  async getInfo() {
1990
2592
  if (!this._sandboxId || this.status !== "running") return {
1991
2593
  id: this.id,
@@ -2038,82 +2640,6 @@ function templateHash(definition) {
2038
2640
  return (0, crypto.createHash)("sha256").update(JSON.stringify(content)).digest("hex").slice(0, 16);
2039
2641
  }
2040
2642
  //#endregion
2041
- //#region ../../packages/_internals/workspace/dist/index.js
2042
- /** Directory, relative to the template's build cwd, that holds every marker below. */
2043
- const SETUP_MARKER_DIR = ".mastra-sandbox";
2044
- /**
2045
- * Setup completion marker shared by repo templates and their consumers.
2046
- *
2047
- * A repo template writes this file beside the checkout as its last build
2048
- * step, so it exists only in images where every setup command succeeded. Its
2049
- * content is a digest of the setup commands the image ran, letting a sandbox
2050
- * booted from the image tell whether the setup it is about to run already
2051
- * happened. Relative to the template's build cwd, which is also the runtime
2052
- * working directory the repo was cloned into.
2053
- *
2054
- * This is the single-repository marker. Templates built from a repository
2055
- * list write one marker per repository instead ({@link repoSetupMarkerPath}).
2056
- */
2057
- const SETUP_MARKER_PATH = ".mastra-sandbox/setup";
2058
- /**
2059
- * Marker for the workspace-level setup of a multi-repository template: the
2060
- * commands that ran at the working directory after every repository was set
2061
- * up. Written even when there were no such commands, so a consumer can tell
2062
- * "ran nothing" from "never built".
2063
- */
2064
- const WORKSPACE_SETUP_MARKER_PATH = ".mastra-sandbox/workspace-setup";
2065
- /**
2066
- * One line per repository directory whose setup failed while the build was
2067
- * told to continue. Absent when nothing failed. Consumers check this before
2068
- * trusting a per-repository marker: that marker means every step ran, not
2069
- * that every step succeeded.
2070
- */
2071
- const SETUP_FAILED_MARKER_PATH = ".mastra-sandbox/setup-failed";
2072
- /** Per-repository marker of a multi-repository template, keyed by the clone directory name. */
2073
- function repoSetupMarkerPath(repoDir) {
2074
- return `${SETUP_MARKER_DIR}/repos/${repoDir}`;
2075
- }
2076
- /** Blank entries never become build steps, so they never count toward the digest either. */
2077
- function normalizeSetupCommands(setupCommand) {
2078
- return (setupCommand === void 0 ? [] : Array.isArray(setupCommand) ? setupCommand : [setupCommand]).filter((command) => command.trim() !== "");
2079
- }
2080
- /** The marker content for a setup command list: `sha256:<hex>` over the commands joined by newlines. */
2081
- function setupMarkerContent(setupCommand) {
2082
- return `sha256:${(0, crypto.createHash)("sha256").update(normalizeSetupCommands(setupCommand).join("\n")).digest("hex")}`;
2083
- }
2084
- /** Shell step that writes the marker relative to the cwd. `content` is a digest, so it is shell-safe. */
2085
- function setupMarkerCommand(content, markerPath = SETUP_MARKER_PATH) {
2086
- return `mkdir -p "$(dirname "${markerPath}")" && printf '%s' '${content}' > "${markerPath}"`;
2087
- }
2088
- /** A clone directory name is one path segment of word characters, dots and dashes, so it never needs quoting. */
2089
- const REPO_DIR_PATTERN = /^[\w.-]+$/;
2090
- /**
2091
- * Shell step that runs one setup command inside a repository clone.
2092
- *
2093
- * Without the guard this is the plain `cd "<dir>" && <cmd>` step. With it,
2094
- * the `cd` sits inside the parenthesized group so that the failure branch
2095
- * runs back at the build cwd and the failure list lands at the workspace
2096
- * level, never inside the repository.
2097
- */
2098
- function guardedSetupCommand({ repoDir, command, continueOnFailure }) {
2099
- if (!REPO_DIR_PATTERN.test(repoDir)) throw new TypeError(`Repository directory "${repoDir}" must match ${REPO_DIR_PATTERN}.`);
2100
- if (!continueOnFailure) return `cd "${repoDir}" && ${command}`;
2101
- return `( cd "${repoDir}" && sh -c ${shellQuote$1(command)} ) || { mkdir -p "${SETUP_MARKER_DIR}" && grep -qxF -- '${repoDir}' "${SETUP_FAILED_MARKER_PATH}" 2>/dev/null || printf '%s\\n' '${repoDir}' >> "${SETUP_FAILED_MARKER_PATH}"; }`;
2102
- }
2103
- function shellQuote$1(value) {
2104
- return `'${value.replace(/'/g, `'\\''`)}'`;
2105
- }
2106
- function repoCloneCommand({ cloneUrl, destination, branch, tokenEnv }) {
2107
- return `git ${tokenEnv ? `${gitAuthFlag$1(tokenEnv)} ` : ""}clone --depth=1 --single-branch ${branch ? `--branch ${shellQuote(branch)} ` : ""}${shellQuote(cloneUrl)} ${shellQuote(destination)}`;
2108
- }
2109
- /** Per-invocation auth header; `-c` config never reaches `.git/config`. */
2110
- function gitAuthFlag$1(tokenEnv) {
2111
- return `-c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$${tokenEnv}" | base64 -w0)"`;
2112
- }
2113
- function shellQuote(value) {
2114
- return `'${value.replace(/'/g, `'\\''`)}'`;
2115
- }
2116
- //#endregion
2117
2643
  //#region src/repo-template.ts
2118
2644
  const execFileAsync = (0, util.promisify)(child_process.execFile);
2119
2645
  const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
@@ -2392,7 +2918,7 @@ async function resolveDefaultBranchHead(cloneUrl, token, execute = execFileAsync
2392
2918
  "HEAD"
2393
2919
  ], {
2394
2920
  timeout: 1e4,
2395
- maxBuffer: 1024 * 1024,
2921
+ maxBuffer: 1048576,
2396
2922
  env
2397
2923
  });
2398
2924
  const sha = stdout.trim().split(/\s+/, 1)[0];
@@ -2700,6 +3226,7 @@ exports.PlatformClient = PlatformClient;
2700
3226
  exports.PlatformFactorySandbox = PlatformFactorySandbox;
2701
3227
  exports.PlatformFilesystem = PlatformFilesystem;
2702
3228
  exports.PlatformSandbox = PlatformSandbox;
3229
+ exports.PlatformSandboxFilesystem = PlatformSandboxFilesystem;
2703
3230
  exports.PrivateNetExecHttpError = PrivateNetExecHttpError;
2704
3231
  exports.SandboxDestroyedError = SandboxDestroyedError;
2705
3232
  exports.SandboxExecTransportError = SandboxExecTransportError;