@mastra/e2b 0.10.0-alpha.3 → 0.10.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -649,6 +649,7 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
649
649
  network;
650
650
  lifecycle;
651
651
  connectionOpts;
652
+ _preferredSandboxId;
652
653
  _instructionsOverride;
653
654
  _constructorOptions;
654
655
  /** Resolved template ID after building (if needed) */
@@ -673,6 +674,7 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
673
674
  ...options.apiKey && { apiKey: options.apiKey },
674
675
  ...options.accessToken && { accessToken: options.accessToken }
675
676
  };
677
+ this._preferredSandboxId = options.sandboxId;
676
678
  this._instructionsOverride = options.instructions;
677
679
  this._constructorOptions = { ...options };
678
680
  this._templatePreparePromise = this.resolveTemplate().catch((err) => {
@@ -691,13 +693,16 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
691
693
  * independent sandboxes (e.g. one per project).
692
694
  *
693
695
  * `options.idleTimeoutMinutes` maps to the E2B sandbox `timeout` (ms);
694
- * `options.sandboxId` is ignored because E2B reconnects by logical `id`.
696
+ * `options.sandboxId` reattaches the clone to that exact E2B sandbox on
697
+ * `start()`. The parent's own preferred provider sandbox ID is never
698
+ * inherited — physical identity is per-instance.
695
699
  */
696
700
  clone(options = {}) {
697
- const { id: _id, ...base } = this._constructorOptions;
701
+ const { id: _id, sandboxId: _sandboxId, ...base } = this._constructorOptions;
698
702
  return new E2BSandbox({
699
703
  ...base,
700
704
  ...options.id !== void 0 && { id: options.id },
705
+ ...options.sandboxId !== void 0 && { sandboxId: options.sandboxId },
701
706
  ...options.env !== void 0 && { env: options.env },
702
707
  ...options.idleTimeoutMinutes !== void 0 && { timeout: options.idleTimeoutMinutes * 6e4 }
703
708
  });
@@ -729,25 +734,43 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
729
734
  return this._sandbox;
730
735
  }
731
736
  /**
732
- * Start the E2B sandbox.
733
- * Handles template preparation, existing sandbox reconnection, and new sandbox creation.
737
+ * The E2B provider sandbox ID resolved after connect or create.
734
738
  *
735
- * Status management and mount processing are handled by the base class.
739
+ * Persist this to reattach deterministically later via the `sandboxId`
740
+ * option (or `clone({ sandboxId })`). Undefined until the sandbox has been
741
+ * started (attached) in this process.
736
742
  */
737
- async start() {
738
- if (this._sandbox) return;
739
- const [existingSandbox, templateId] = await Promise.all([this.findExistingSandbox(), this._templatePreparePromise || this.resolveTemplate()]);
740
- if (existingSandbox) {
741
- this._sandbox = existingSandbox;
742
- this._createdAt = /* @__PURE__ */ new Date();
743
- this.logger.debug(`${LOG_PREFIX} Reconnected to existing sandbox for: ${this.id}`);
744
- const expectedPaths = Array.from(this.mounts.entries.keys());
745
- this.logger.debug(`${LOG_PREFIX} Running mount reconciliation...`);
746
- await this.reconcileMounts(expectedPaths);
747
- this.logger.debug(`${LOG_PREFIX} Mount reconciliation complete`);
748
- return;
749
- }
750
- let resolvedTemplateId = templateId;
743
+ get sandboxId() {
744
+ return this._sandbox?.sandboxId;
745
+ }
746
+ /**
747
+ * Acquisition primitives (base-orchestrated start): the base derives
748
+ * `outcome: 'created'` only when a brand-new sandbox VM was created;
749
+ * reconnecting (including resuming a paused sandbox) is `outcome: 'connected'`.
750
+ *
751
+ * `find` returns an already-connected E2B handle: `Sandbox.connect`
752
+ * resumes paused sandboxes, and its failures are deliberately swallowed
753
+ * (unusable handle → create fresh) — that forgiveness is this provider's
754
+ * policy, so it lives here rather than in `connect`. The exception is the
755
+ * `sandboxId` reattach inside {@link acquireExistingSandbox}, which is
756
+ * fail-closed: only a "sandbox gone" error falls through to discovery.
757
+ */
758
+ async find() {
759
+ if (this._sandbox) return this._sandbox;
760
+ return await this.acquireExistingSandbox() ?? void 0;
761
+ }
762
+ async connect(existingSandbox) {
763
+ if (existingSandbox === this._sandbox) return;
764
+ this._sandbox = existingSandbox;
765
+ this._createdAt = /* @__PURE__ */ new Date();
766
+ this.logger.debug(`${LOG_PREFIX} Reconnected to existing sandbox for: ${this.id}`);
767
+ const expectedPaths = Array.from(this.mounts.entries.keys());
768
+ this.logger.debug(`${LOG_PREFIX} Running mount reconciliation...`);
769
+ await this.reconcileMounts(expectedPaths);
770
+ this.logger.debug(`${LOG_PREFIX} Mount reconciliation complete`);
771
+ }
772
+ async create() {
773
+ let resolvedTemplateId = await (this._templatePreparePromise || this.resolveTemplate());
751
774
  if (!resolvedTemplateId) {
752
775
  this.logger.debug(`${LOG_PREFIX} Template preparation failed earlier, retrying...`);
753
776
  resolvedTemplateId = await this.resolveTemplate();
@@ -767,7 +790,7 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
767
790
  } catch (createError) {
768
791
  const errorStr = String(createError);
769
792
  if (errorStr.includes("404") && errorStr.includes("not found") && !this.templateSpec) {
770
- this.logger.debug(`${LOG_PREFIX} Template not found, rebuilding: ${templateId}`);
793
+ this.logger.debug(`${LOG_PREFIX} Template not found, rebuilding: ${resolvedTemplateId}`);
771
794
  this._resolvedTemplateId = void 0;
772
795
  const rebuiltTemplateId = await this.buildDefaultTemplate();
773
796
  this.logger.debug(`${LOG_PREFIX} Retrying sandbox creation with rebuilt template: ${rebuiltTemplateId}`);
@@ -848,7 +871,10 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
848
871
  path,
849
872
  filesystem: entry.filesystem?.provider ?? entry.config?.type ?? "unknown"
850
873
  })),
851
- metadata: { ...this.metadata }
874
+ metadata: {
875
+ ...this.metadata,
876
+ ...this._sandbox && { sandboxId: this._sandbox.sandboxId }
877
+ }
852
878
  };
853
879
  }
854
880
  /**
@@ -1126,6 +1152,52 @@ var E2BSandbox = class E2BSandbox extends MastraSandbox {
1126
1152
  return null;
1127
1153
  }
1128
1154
  /**
1155
+ * Acquire an existing sandbox: try the preferred provider sandbox ID first
1156
+ * (deterministic reattach), then fall back to logical-id metadata discovery.
1157
+ */
1158
+ async acquireExistingSandbox() {
1159
+ if (this._preferredSandboxId) {
1160
+ const preferred = await this.connectToPreferredSandbox(this._preferredSandboxId);
1161
+ if (preferred) return preferred;
1162
+ }
1163
+ return this.findExistingSandbox();
1164
+ }
1165
+ /**
1166
+ * Deterministically reattach to a sandbox by its E2B provider ID.
1167
+ *
1168
+ * Fail-closed: only a typed "sandbox gone" error (not found / killed /
1169
+ * not running) returns null so the caller can fall through to logical-id
1170
+ * discovery or creation. Any other error (auth, quota, rate limit,
1171
+ * timeout, network) propagates so a duplicate sandbox is never created.
1172
+ *
1173
+ * Ownership is validated before connecting: a sandbox tagged with a
1174
+ * different `mastra-sandbox-id` is refused (without resuming it).
1175
+ * Sandboxes without the tag (created outside Mastra) are attachable.
1176
+ */
1177
+ async connectToPreferredSandbox(preferredSandboxId) {
1178
+ let info;
1179
+ try {
1180
+ info = await Sandbox.getInfo(preferredSandboxId, this.connectionOpts);
1181
+ } catch (e) {
1182
+ if (this.isSandboxDeadError(e)) {
1183
+ this.logger.debug(`${LOG_PREFIX} Preferred sandbox ${preferredSandboxId} is gone, falling back to logical-id discovery:`, e);
1184
+ return null;
1185
+ }
1186
+ throw e;
1187
+ }
1188
+ const owner = info.metadata?.["mastra-sandbox-id"];
1189
+ if (owner !== void 0 && owner !== this.id) throw new Error(`${LOG_PREFIX} Provider sandbox ${preferredSandboxId} belongs to logical sandbox id "${owner}", refusing to attach it to "${this.id}"`);
1190
+ try {
1191
+ return await Sandbox.connect(preferredSandboxId, this.connectionOpts);
1192
+ } catch (e) {
1193
+ if (this.isSandboxDeadError(e)) {
1194
+ this.logger.debug(`${LOG_PREFIX} Preferred sandbox ${preferredSandboxId} vanished before connect, falling back:`, e);
1195
+ return null;
1196
+ }
1197
+ throw e;
1198
+ }
1199
+ }
1200
+ /**
1129
1201
  * Find an existing sandbox with matching mastra-sandbox-id metadata.
1130
1202
  * Returns the connected sandbox if found, null otherwise.
1131
1203
  * Connecting to a paused sandbox resumes it.