@mastra/apple-container 0.3.0 → 0.4.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/dist/index.cjs CHANGED
@@ -1,948 +1,864 @@
1
- 'use strict';
2
-
3
- var child_process = require('child_process');
4
- var crypto = require('crypto');
5
- var string_decoder = require('string_decoder');
6
- var workspace = require('@mastra/core/workspace');
7
-
8
- // src/sandbox/index.ts
9
- var DEFAULT_COMMAND_TIMEOUT_MS = 3e5;
10
- var DEFAULT_IMAGE = "node:22-slim";
11
- var DEFAULT_COMMAND = ["sleep", "infinity"];
12
- var DEFAULT_WORKING_DIR = "/workspace";
13
- var APPLE_CONTAINER_CLI_GRACE_TIMEOUT_MS = 1e4;
14
- var APPLE_CONTAINER_READY_TIMEOUT_MS = 1e4;
15
- var APPLE_CONTAINER_READY_EXEC_TIMEOUT_MS = 5e3;
16
- var APPLE_CONTAINER_TIMEOUT_EXIT_CODE = 124;
17
- var APPLE_CONTAINER_TIMEOUT_MARKER = "__MASTRA_APPLE_CONTAINER_TIMEOUT__";
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let child_process = require("child_process");
3
+ let crypto = require("crypto");
4
+ let string_decoder = require("string_decoder");
5
+ let _mastra_core_workspace = require("@mastra/core/workspace");
6
+ //#region src/sandbox/index.ts
7
+ /**
8
+ * Apple container CLI sandbox provider.
9
+ *
10
+ * This provider maps Mastra's WorkspaceSandbox command execution contract to
11
+ * Apple's `container` CLI. It starts a long-lived OCI Linux container and uses
12
+ * `container exec` for commands.
13
+ *
14
+ * @see https://github.com/apple/container
15
+ */
16
+ const DEFAULT_COMMAND_TIMEOUT_MS = 3e5;
17
+ const DEFAULT_IMAGE = "node:22-slim";
18
+ const DEFAULT_COMMAND = ["sleep", "infinity"];
19
+ const DEFAULT_WORKING_DIR = "/workspace";
20
+ const APPLE_CONTAINER_CLI_GRACE_TIMEOUT_MS = 1e4;
21
+ const APPLE_CONTAINER_READY_TIMEOUT_MS = 1e4;
22
+ const APPLE_CONTAINER_READY_EXEC_TIMEOUT_MS = 5e3;
23
+ const APPLE_CONTAINER_TIMEOUT_EXIT_CODE = 124;
24
+ const APPLE_CONTAINER_TIMEOUT_MARKER = "__MASTRA_APPLE_CONTAINER_TIMEOUT__";
18
25
  var DefaultAppleContainerCommandRunner = class {
19
- constructor(binary = "container") {
20
- this.binary = binary;
21
- }
22
- binary;
23
- run(args, options = {}) {
24
- return runAppleContainerCli(this.binary, args, options);
25
- }
26
+ binary;
27
+ constructor(binary = "container") {
28
+ this.binary = binary;
29
+ }
30
+ run(args, options = {}) {
31
+ return runAppleContainerCli(this.binary, args, options);
32
+ }
26
33
  };
27
- var AppleContainerSandbox = class _AppleContainerSandbox extends workspace.MastraSandbox {
28
- id;
29
- name = "AppleContainerSandbox";
30
- provider = "apple-container";
31
- status = "pending";
32
- _containerName;
33
- _configuredName;
34
- _image;
35
- _command;
36
- _env;
37
- _volumes;
38
- _mounts;
39
- _network;
40
- _publishedPorts;
41
- _publishedSockets;
42
- _cpus;
43
- _memory;
44
- _platform;
45
- _arch;
46
- _os;
47
- _rosetta;
48
- _readonlyRootfs;
49
- _ssh;
50
- _init;
51
- _virtualization;
52
- _capAdd;
53
- _capDrop;
54
- _tmpfs;
55
- _dns;
56
- _dnsSearch;
57
- _noDns;
58
- _userLabels;
59
- _labels;
60
- _configHash;
61
- _workingDir;
62
- _timeout;
63
- _deleteOnDestroy;
64
- _runner;
65
- _instructionsOverride;
66
- _createdAt;
67
- _constructorOptions;
68
- _containerId;
69
- constructor(options = {}) {
70
- super({
71
- ...options,
72
- name: "AppleContainerSandbox"
73
- });
74
- this.id = options.id ?? generateId();
75
- this._configuredName = options.name;
76
- this._containerName = sanitizeContainerName(options.name ?? this.id);
77
- this._image = options.image ?? DEFAULT_IMAGE;
78
- this._command = options.command ?? DEFAULT_COMMAND;
79
- this._env = options.env ?? {};
80
- this._volumes = options.volumes ?? {};
81
- this._mounts = options.mounts ?? [];
82
- this._network = options.network;
83
- this._publishedPorts = options.publishedPorts ?? [];
84
- this._publishedSockets = options.publishedSockets ?? [];
85
- this._cpus = options.cpus;
86
- this._memory = options.memory;
87
- this._platform = options.platform;
88
- this._arch = options.arch;
89
- this._os = options.os;
90
- this._rosetta = options.rosetta ?? false;
91
- this._readonlyRootfs = options.readonlyRootfs ?? false;
92
- this._ssh = options.ssh ?? false;
93
- this._init = options.init ?? true;
94
- this._virtualization = options.virtualization ?? false;
95
- this._capAdd = options.capAdd ?? [];
96
- this._capDrop = options.capDrop ?? [];
97
- this._tmpfs = options.tmpfs ?? [];
98
- validateTmpfsPaths(this._tmpfs);
99
- this._dns = options.dns ?? [];
100
- this._dnsSearch = options.dnsSearch ?? [];
101
- this._noDns = options.noDns ?? false;
102
- this._workingDir = options.workingDir ?? DEFAULT_WORKING_DIR;
103
- this._userLabels = options.labels ?? {};
104
- this._configHash = hashConfig(this._runtimeConfigForHash());
105
- this._labels = {
106
- ...this._userLabels,
107
- "mastra.sandbox": "true",
108
- "mastra.sandbox.id": this.id,
109
- "mastra.sandbox.config-hash": this._configHash
110
- };
111
- this._timeout = options.timeout ?? DEFAULT_COMMAND_TIMEOUT_MS;
112
- this._deleteOnDestroy = options.deleteOnDestroy ?? true;
113
- this._runner = options.runner ?? new DefaultAppleContainerCommandRunner(options.containerBinary);
114
- this._instructionsOverride = options.instructions;
115
- this._createdAt = /* @__PURE__ */ new Date();
116
- this._constructorOptions = { ...options };
117
- }
118
- /**
119
- * Construct a sibling `AppleContainerSandbox` that inherits this sandbox's
120
- * configuration (image, resources, network, security options, labels) with
121
- * per-instance overrides.
122
- *
123
- * Performs no I/O — the sandbox clone provisions (or reconnects to an
124
- * existing container labelled with the same logical `id`) on its own
125
- * `start()`. Use it when one configured sandbox acts as the template for a
126
- * fleet of independent sandboxes (e.g. one per project).
127
- *
128
- * `options.idleTimeoutMinutes` is ignored (Apple containers have no
129
- * provider-side idle teardown; `timeout` here is a command timeout), and
130
- * `options.sandboxId` is ignored because reconnection is by logical `id`.
131
- */
132
- clone(options = {}) {
133
- const { id: _id, name: _name, ...base } = this._constructorOptions;
134
- return new _AppleContainerSandbox({
135
- ...base,
136
- ...options.id !== void 0 && { id: options.id },
137
- ...options.env !== void 0 && { env: options.env }
138
- });
139
- }
140
- get containerId() {
141
- return this._containerId ?? this._containerName;
142
- }
143
- async start() {
144
- await this._runPlainLifecycle("starting", "running", () => this._startContainer());
145
- }
146
- async stop() {
147
- await this._runPlainLifecycle("stopping", "stopped", () => this._stopContainer());
148
- }
149
- async destroy() {
150
- await this._runPlainLifecycle("destroying", "destroyed", () => this._destroyContainer());
151
- }
152
- async _startContainer() {
153
- const existing = await this._inspectContainer();
154
- if (existing) {
155
- this._assertMastraOwned(existing);
156
- this._assertCompatibleConfig(existing);
157
- this._containerId = existing.configuration?.id ?? this._containerName;
158
- if (!isRunning(existing)) {
159
- const result2 = await this._runCli(["start", this.containerId]);
160
- this._assertSuccess(result2, `start Apple container ${this.containerId}`);
161
- await this._waitUntilContainerReady(`start Apple container ${this.containerId}`);
162
- }
163
- return;
164
- }
165
- const env = envFlags(this._env);
166
- const result = await this._runCli(this._buildRunArgs(env.args), { env: env.env });
167
- this._assertSuccess(result, `create Apple container ${this._containerName}`);
168
- this._containerId = this._containerName;
169
- try {
170
- await this._waitUntilContainerReady(`create Apple container ${this._containerName}`);
171
- } catch (error) {
172
- if (this._deleteOnDestroy) {
173
- await this._deleteContainerIgnoringMissing();
174
- }
175
- throw error;
176
- }
177
- }
178
- async _stopContainer() {
179
- const existing = await this._inspectContainer();
180
- if (!existing) {
181
- return;
182
- }
183
- this._assertMastraOwned(existing);
184
- if (!isRunning(existing)) {
185
- return;
186
- }
187
- const result = await this._runCli(["stop", this.containerId]);
188
- if (!result.success && !isMissingContainerMessage(result.stderr)) {
189
- this._assertSuccess(result, `stop Apple container ${this.containerId}`);
190
- }
191
- }
192
- async _destroyContainer() {
193
- if (!this._deleteOnDestroy) {
194
- await this._stopContainer();
195
- return;
196
- }
197
- const existing = await this._inspectContainer();
198
- if (!existing) {
199
- return;
200
- }
201
- this._assertMastraOwned(existing);
202
- await this._deleteContainerIgnoringMissing();
203
- }
204
- async executeCommand(command, args = [], options = {}) {
205
- await this.ensureRunning();
206
- const commandTimeout = options.timeout ?? this._timeout;
207
- const hasCommandTimeout = Number.isFinite(commandTimeout) && commandTimeout > 0;
208
- const fullCommand = buildShellCommand(command, args);
209
- const shellCommand = hasCommandTimeout ? buildTimeoutShellCommand(fullCommand, commandTimeout) : fullCommand;
210
- const env = envFlags({ ...this._env, ...options.env });
211
- const cliArgs = [
212
- "exec",
213
- ...env.args,
214
- "--workdir",
215
- options.cwd ?? this._workingDir,
216
- this.containerId,
217
- "sh",
218
- "-lc",
219
- shellCommand
220
- ];
221
- const result = await this._runner.run(cliArgs, {
222
- timeout: hasCommandTimeout ? commandTimeout + APPLE_CONTAINER_CLI_GRACE_TIMEOUT_MS : void 0,
223
- env: env.env,
224
- abortSignal: options.abortSignal,
225
- onStdout: options.onStdout,
226
- onStderr: options.onStderr,
227
- maxRetainedBytes: options.maxRetainedBytes
228
- });
229
- const timedOut = result.exitCode === APPLE_CONTAINER_TIMEOUT_EXIT_CODE && result.stderr.includes(APPLE_CONTAINER_TIMEOUT_MARKER);
230
- const stderr = timedOut ? stripTimeoutMarker(result.stderr) : result.stderr;
231
- return {
232
- ...result,
233
- stderr,
234
- ...timedOut && { timedOut: true, killed: true },
235
- command: fullCommand,
236
- args
237
- };
238
- }
239
- async getInfo() {
240
- const inspect = this._containerId ? await this._inspectContainer() : void 0;
241
- const resources = inspect?.configuration?.resources;
242
- return {
243
- id: this.id,
244
- name: this.name,
245
- provider: this.provider,
246
- status: this.status,
247
- createdAt: this._createdAt,
248
- resources: resources ? {
249
- cpuCores: resources.cpus,
250
- memoryMB: resources.memoryInBytes ? Math.round(resources.memoryInBytes / 1024 / 1024) : void 0
251
- } : void 0,
252
- metadata: {
253
- ...this._serializableConfig()
254
- }
255
- };
256
- }
257
- getInstructions(opts) {
258
- const defaultInstructions = this._buildDefaultInstructions();
259
- if (typeof this._instructionsOverride === "string") {
260
- return this._instructionsOverride;
261
- }
262
- if (typeof this._instructionsOverride === "function") {
263
- return this._instructionsOverride({ defaultInstructions, requestContext: opts?.requestContext });
264
- }
265
- return defaultInstructions;
266
- }
267
- _buildDefaultInstructions() {
268
- const parts = [
269
- `Apple container sandbox: commands run inside a local OCI Linux container from image ${this._image}.`,
270
- `Working directory: ${this._workingDir}.`
271
- ];
272
- const volumeCount = Object.keys(this._volumes).length + this._mounts.length;
273
- if (volumeCount > 0) {
274
- parts.push(`${volumeCount} host mount(s) are configured.`);
275
- }
276
- if (this._timeout > 0) {
277
- parts.push(`Default command timeout: ${Math.ceil(this._timeout / 1e3)}s.`);
278
- }
279
- return parts.join(" ");
280
- }
281
- async _runPlainLifecycle(activeStatus, completeStatus, operation) {
282
- const managedByBaseWrapper = this.status === activeStatus;
283
- if (!managedByBaseWrapper) {
284
- this.status = activeStatus;
285
- }
286
- try {
287
- await operation();
288
- if (!managedByBaseWrapper) {
289
- this.status = completeStatus;
290
- }
291
- } catch (error) {
292
- if (!managedByBaseWrapper) {
293
- this.status = "error";
294
- }
295
- throw error;
296
- }
297
- }
298
- async _inspectContainer() {
299
- const result = await this._runCli(["inspect", this.containerId]);
300
- if (!result.success) {
301
- if (isMissingContainerMessage(result.stderr)) return void 0;
302
- this._assertSuccess(result, `inspect Apple container ${this.containerId}`);
303
- return void 0;
304
- }
305
- try {
306
- const parsed = JSON.parse(result.stdout);
307
- return Array.isArray(parsed) ? parsed[0] : parsed;
308
- } catch (error) {
309
- throw new workspace.SandboxExecutionError(
310
- `Failed to parse Apple container inspect output for ${this.containerId}: ${error instanceof Error ? error.message : String(error)}`,
311
- 1,
312
- result.stdout,
313
- result.stderr
314
- );
315
- }
316
- }
317
- _buildRunArgs(envArgs) {
318
- const args = ["run", "-d", "--name", this._containerName, "--workdir", this._workingDir];
319
- args.push(...envArgs);
320
- for (const [hostPath, containerPath] of Object.entries(this._volumes)) {
321
- args.push("--volume", `${hostPath}:${containerPath}`);
322
- }
323
- for (const mount of this._mounts) args.push("--mount", mount);
324
- for (const [key, value] of Object.entries(this._labels)) args.push("--label", `${key}=${value}`);
325
- for (const port of this._publishedPorts) args.push("--publish", port);
326
- for (const socket of this._publishedSockets) args.push("--publish-socket", socket);
327
- for (const cap of this._capAdd) args.push("--cap-add", cap);
328
- for (const cap of this._capDrop) args.push("--cap-drop", cap);
329
- for (const tmpfs of this._tmpfs) args.push("--tmpfs", tmpfs);
330
- for (const dns of this._dns) args.push("--dns", dns);
331
- for (const domain of this._dnsSearch) args.push("--dns-search", domain);
332
- if (this._network) args.push("--network", this._network);
333
- if (this._cpus !== void 0) args.push("--cpus", String(this._cpus));
334
- if (this._memory !== void 0) args.push("--memory", this._memory);
335
- if (this._platform) args.push("--platform", this._platform);
336
- if (this._arch) args.push("--arch", this._arch);
337
- if (this._os) args.push("--os", this._os);
338
- if (this._rosetta) args.push("--rosetta");
339
- if (this._readonlyRootfs) args.push("--read-only");
340
- if (this._ssh) args.push("--ssh");
341
- if (this._init) args.push("--init");
342
- if (this._virtualization) args.push("--virtualization");
343
- if (this._noDns) args.push("--no-dns");
344
- args.push(this._image, ...this._command);
345
- return args;
346
- }
347
- async _waitUntilContainerReady(action) {
348
- const deadline = Date.now() + APPLE_CONTAINER_READY_TIMEOUT_MS;
349
- let lastResult;
350
- while (Date.now() < deadline) {
351
- const inspect = await this._inspectContainer();
352
- if (!inspect) {
353
- throw new workspace.SandboxExecutionError(
354
- `${action} failed because Apple container ${this.containerId} disappeared`,
355
- 1,
356
- "",
357
- ""
358
- );
359
- }
360
- this._assertMastraOwned(inspect);
361
- this._assertCompatibleConfig(inspect);
362
- if (!isRunning(inspect)) {
363
- const state = getContainerState(inspect) ?? "not running";
364
- throw new workspace.SandboxExecutionError(
365
- `${action} failed because Apple container ${this.containerId} is ${state}`,
366
- 1,
367
- "",
368
- ""
369
- );
370
- }
371
- lastResult = await this._runCli(["exec", this.containerId, "sh", "-lc", "true"], {
372
- timeout: APPLE_CONTAINER_READY_EXEC_TIMEOUT_MS
373
- });
374
- if (lastResult.success) return;
375
- if (!isMissingContainerMessage(lastResult.stderr) && !/not running|not yet running/i.test(lastResult.stderr)) {
376
- break;
377
- }
378
- await delay(100);
379
- }
380
- throw new workspace.SandboxExecutionError(
381
- `${action} failed because Apple container ${this.containerId} did not become ready for exec`,
382
- lastResult?.exitCode ?? 1,
383
- lastResult?.stdout ?? "",
384
- lastResult?.stderr ?? ""
385
- );
386
- }
387
- async _deleteContainerIgnoringMissing() {
388
- const result = await this._runCli(["delete", "--force", this.containerId]);
389
- if (!result.success && !isMissingContainerMessage(result.stderr)) {
390
- this._assertSuccess(result, `delete Apple container ${this.containerId}`);
391
- }
392
- }
393
- _runtimeConfigForHash() {
394
- return {
395
- image: this._image,
396
- command: this._command,
397
- env: this._env,
398
- volumes: this._volumes,
399
- mounts: this._mounts,
400
- network: this._network,
401
- publishedPorts: this._publishedPorts,
402
- publishedSockets: this._publishedSockets,
403
- cpus: this._cpus,
404
- memory: this._memory,
405
- platform: this._platform,
406
- arch: this._arch,
407
- os: this._os,
408
- rosetta: this._rosetta,
409
- readonlyRootfs: this._readonlyRootfs,
410
- ssh: this._ssh,
411
- init: this._init,
412
- virtualization: this._virtualization,
413
- capAdd: this._capAdd,
414
- capDrop: this._capDrop,
415
- tmpfs: this._tmpfs,
416
- dns: this._dns,
417
- dnsSearch: this._dnsSearch,
418
- noDns: this._noDns,
419
- labels: this._userLabels,
420
- workingDir: this._workingDir
421
- };
422
- }
423
- _serializableConfig() {
424
- return compactConfig({
425
- id: this.id,
426
- name: this._configuredName,
427
- image: this._image,
428
- command: this._command,
429
- env: this._env,
430
- volumes: this._volumes,
431
- mounts: this._mounts,
432
- network: this._network,
433
- publishedPorts: this._publishedPorts,
434
- publishedSockets: this._publishedSockets,
435
- cpus: this._cpus,
436
- memory: this._memory,
437
- platform: this._platform,
438
- arch: this._arch,
439
- os: this._os,
440
- rosetta: this._rosetta,
441
- readonlyRootfs: this._readonlyRootfs,
442
- ssh: this._ssh,
443
- init: this._init,
444
- virtualization: this._virtualization,
445
- capAdd: this._capAdd,
446
- capDrop: this._capDrop,
447
- tmpfs: this._tmpfs,
448
- dns: this._dns,
449
- dnsSearch: this._dnsSearch,
450
- noDns: this._noDns,
451
- labels: this._userLabels,
452
- workingDir: this._workingDir,
453
- timeout: this._timeout,
454
- deleteOnDestroy: this._deleteOnDestroy
455
- });
456
- }
457
- _assertSuccess(result, action) {
458
- if (result.success) return;
459
- throw new workspace.SandboxExecutionError(
460
- `${action} failed with exit code ${result.exitCode}: ${result.stderr}`,
461
- result.exitCode,
462
- result.stdout,
463
- result.stderr
464
- );
465
- }
466
- _assertMastraOwned(inspect) {
467
- if (isMastraOwned(inspect, this.id)) return;
468
- throw new workspace.SandboxExecutionError(
469
- `Refusing to manage Apple container ${this.containerId} because it is not labeled as Mastra sandbox ${this.id}`,
470
- 1,
471
- "",
472
- ""
473
- );
474
- }
475
- _assertCompatibleConfig(inspect) {
476
- const existingHash = inspect.configuration?.labels?.["mastra.sandbox.config-hash"];
477
- if (!existingHash || existingHash === this._configHash) return;
478
- throw new workspace.SandboxExecutionError(
479
- `Refusing to manage Apple container ${this.containerId} because its immutable configuration does not match sandbox ${this.id}`,
480
- 1,
481
- "",
482
- ""
483
- );
484
- }
485
- _runCli(args, options = {}) {
486
- return this._runner.run(args, {
487
- timeout: this._timeout,
488
- ...options
489
- });
490
- }
34
+ var AppleContainerSandbox = class AppleContainerSandbox extends _mastra_core_workspace.MastraSandbox {
35
+ id;
36
+ name = "AppleContainerSandbox";
37
+ provider = "apple-container";
38
+ status = "pending";
39
+ _containerName;
40
+ _configuredName;
41
+ _image;
42
+ _command;
43
+ _env;
44
+ _volumes;
45
+ _mounts;
46
+ _network;
47
+ _publishedPorts;
48
+ _publishedSockets;
49
+ _cpus;
50
+ _memory;
51
+ _platform;
52
+ _arch;
53
+ _os;
54
+ _rosetta;
55
+ _readonlyRootfs;
56
+ _ssh;
57
+ _init;
58
+ _virtualization;
59
+ _capAdd;
60
+ _capDrop;
61
+ _tmpfs;
62
+ _dns;
63
+ _dnsSearch;
64
+ _noDns;
65
+ _userLabels;
66
+ _labels;
67
+ _configHash;
68
+ _workingDir;
69
+ _timeout;
70
+ _deleteOnDestroy;
71
+ _runner;
72
+ _instructionsOverride;
73
+ _createdAt;
74
+ _constructorOptions;
75
+ _containerId;
76
+ constructor(options = {}) {
77
+ super({
78
+ ...options,
79
+ name: "AppleContainerSandbox"
80
+ });
81
+ this.id = options.id ?? generateId();
82
+ this._configuredName = options.name;
83
+ this._containerName = sanitizeContainerName(options.name ?? this.id);
84
+ this._image = options.image ?? DEFAULT_IMAGE;
85
+ this._command = options.command ?? DEFAULT_COMMAND;
86
+ this._env = options.env ?? {};
87
+ this._volumes = options.volumes ?? {};
88
+ this._mounts = options.mounts ?? [];
89
+ this._network = options.network;
90
+ this._publishedPorts = options.publishedPorts ?? [];
91
+ this._publishedSockets = options.publishedSockets ?? [];
92
+ this._cpus = options.cpus;
93
+ this._memory = options.memory;
94
+ this._platform = options.platform;
95
+ this._arch = options.arch;
96
+ this._os = options.os;
97
+ this._rosetta = options.rosetta ?? false;
98
+ this._readonlyRootfs = options.readonlyRootfs ?? false;
99
+ this._ssh = options.ssh ?? false;
100
+ this._init = options.init ?? true;
101
+ this._virtualization = options.virtualization ?? false;
102
+ this._capAdd = options.capAdd ?? [];
103
+ this._capDrop = options.capDrop ?? [];
104
+ this._tmpfs = options.tmpfs ?? [];
105
+ validateTmpfsPaths(this._tmpfs);
106
+ this._dns = options.dns ?? [];
107
+ this._dnsSearch = options.dnsSearch ?? [];
108
+ this._noDns = options.noDns ?? false;
109
+ this._workingDir = options.workingDir ?? DEFAULT_WORKING_DIR;
110
+ this._userLabels = options.labels ?? {};
111
+ this._configHash = hashConfig(this._runtimeConfigForHash());
112
+ this._labels = {
113
+ ...this._userLabels,
114
+ "mastra.sandbox": "true",
115
+ "mastra.sandbox.id": this.id,
116
+ "mastra.sandbox.config-hash": this._configHash
117
+ };
118
+ this._timeout = options.timeout ?? DEFAULT_COMMAND_TIMEOUT_MS;
119
+ this._deleteOnDestroy = options.deleteOnDestroy ?? true;
120
+ this._runner = options.runner ?? new DefaultAppleContainerCommandRunner(options.containerBinary);
121
+ this._instructionsOverride = options.instructions;
122
+ this._createdAt = /* @__PURE__ */ new Date();
123
+ this._constructorOptions = { ...options };
124
+ }
125
+ /**
126
+ * Construct a sibling `AppleContainerSandbox` that inherits this sandbox's
127
+ * configuration (image, resources, network, security options, labels) with
128
+ * per-instance overrides.
129
+ *
130
+ * Performs no I/O — the sandbox clone provisions (or reconnects to an
131
+ * existing container labelled with the same logical `id`) on its own
132
+ * `start()`. Use it when one configured sandbox acts as the template for a
133
+ * fleet of independent sandboxes (e.g. one per project).
134
+ *
135
+ * `options.idleTimeoutMinutes` is ignored (Apple containers have no
136
+ * provider-side idle teardown; `timeout` here is a command timeout), and
137
+ * `options.sandboxId` is ignored because reconnection is by logical `id`.
138
+ */
139
+ clone(options = {}) {
140
+ const { id: _id, name: _name, ...base } = this._constructorOptions;
141
+ return new AppleContainerSandbox({
142
+ ...base,
143
+ ...options.id !== void 0 && { id: options.id },
144
+ ...options.env !== void 0 && { env: options.env }
145
+ });
146
+ }
147
+ get containerId() {
148
+ return this._containerId ?? this._containerName;
149
+ }
150
+ async start() {
151
+ await this._runPlainLifecycle("starting", "running", () => this._startContainer());
152
+ }
153
+ async stop() {
154
+ await this._runPlainLifecycle("stopping", "stopped", () => this._stopContainer());
155
+ }
156
+ async destroy() {
157
+ await this._runPlainLifecycle("destroying", "destroyed", () => this._destroyContainer());
158
+ }
159
+ async _startContainer() {
160
+ const existing = await this._inspectContainer();
161
+ if (existing) {
162
+ this._assertMastraOwned(existing);
163
+ this._assertCompatibleConfig(existing);
164
+ this._containerId = existing.configuration?.id ?? this._containerName;
165
+ if (!isRunning(existing)) {
166
+ const result = await this._runCli(["start", this.containerId]);
167
+ this._assertSuccess(result, `start Apple container ${this.containerId}`);
168
+ await this._waitUntilContainerReady(`start Apple container ${this.containerId}`);
169
+ }
170
+ return;
171
+ }
172
+ const env = envFlags(this._env);
173
+ const result = await this._runCli(this._buildRunArgs(env.args), { env: env.env });
174
+ this._assertSuccess(result, `create Apple container ${this._containerName}`);
175
+ this._containerId = this._containerName;
176
+ try {
177
+ await this._waitUntilContainerReady(`create Apple container ${this._containerName}`);
178
+ } catch (error) {
179
+ if (this._deleteOnDestroy) await this._deleteContainerIgnoringMissing();
180
+ throw error;
181
+ }
182
+ }
183
+ async _stopContainer() {
184
+ const existing = await this._inspectContainer();
185
+ if (!existing) return;
186
+ this._assertMastraOwned(existing);
187
+ if (!isRunning(existing)) return;
188
+ const result = await this._runCli(["stop", this.containerId]);
189
+ if (!result.success && !isMissingContainerMessage(result.stderr)) this._assertSuccess(result, `stop Apple container ${this.containerId}`);
190
+ }
191
+ async _destroyContainer() {
192
+ if (!this._deleteOnDestroy) {
193
+ await this._stopContainer();
194
+ return;
195
+ }
196
+ const existing = await this._inspectContainer();
197
+ if (!existing) return;
198
+ this._assertMastraOwned(existing);
199
+ await this._deleteContainerIgnoringMissing();
200
+ }
201
+ async executeCommand(command, args = [], options = {}) {
202
+ await this.ensureRunning();
203
+ const commandTimeout = options.timeout ?? this._timeout;
204
+ const hasCommandTimeout = Number.isFinite(commandTimeout) && commandTimeout > 0;
205
+ const fullCommand = buildShellCommand(command, args);
206
+ const shellCommand = hasCommandTimeout ? buildTimeoutShellCommand(fullCommand, commandTimeout) : fullCommand;
207
+ const env = envFlags({
208
+ ...this._env,
209
+ ...options.env
210
+ });
211
+ const cliArgs = [
212
+ "exec",
213
+ ...env.args,
214
+ "--workdir",
215
+ options.cwd ?? this._workingDir,
216
+ this.containerId,
217
+ "sh",
218
+ "-lc",
219
+ shellCommand
220
+ ];
221
+ const result = await this._runner.run(cliArgs, {
222
+ timeout: hasCommandTimeout ? commandTimeout + APPLE_CONTAINER_CLI_GRACE_TIMEOUT_MS : void 0,
223
+ env: env.env,
224
+ abortSignal: options.abortSignal,
225
+ onStdout: options.onStdout,
226
+ onStderr: options.onStderr,
227
+ maxRetainedBytes: options.maxRetainedBytes
228
+ });
229
+ const timedOut = result.exitCode === APPLE_CONTAINER_TIMEOUT_EXIT_CODE && result.stderr.includes(APPLE_CONTAINER_TIMEOUT_MARKER);
230
+ const stderr = timedOut ? stripTimeoutMarker(result.stderr) : result.stderr;
231
+ return {
232
+ ...result,
233
+ stderr,
234
+ ...timedOut && {
235
+ timedOut: true,
236
+ killed: true
237
+ },
238
+ command: fullCommand,
239
+ args
240
+ };
241
+ }
242
+ async getInfo() {
243
+ const resources = (this._containerId ? await this._inspectContainer() : void 0)?.configuration?.resources;
244
+ return {
245
+ id: this.id,
246
+ name: this.name,
247
+ provider: this.provider,
248
+ status: this.status,
249
+ createdAt: this._createdAt,
250
+ resources: resources ? {
251
+ cpuCores: resources.cpus,
252
+ memoryMB: resources.memoryInBytes ? Math.round(resources.memoryInBytes / 1024 / 1024) : void 0
253
+ } : void 0,
254
+ metadata: { ...this._serializableConfig() }
255
+ };
256
+ }
257
+ getInstructions(opts) {
258
+ const defaultInstructions = this._buildDefaultInstructions();
259
+ if (typeof this._instructionsOverride === "string") return this._instructionsOverride;
260
+ if (typeof this._instructionsOverride === "function") return this._instructionsOverride({
261
+ defaultInstructions,
262
+ requestContext: opts?.requestContext
263
+ });
264
+ return defaultInstructions;
265
+ }
266
+ _buildDefaultInstructions() {
267
+ const parts = [`Apple container sandbox: commands run inside a local OCI Linux container from image ${this._image}.`, `Working directory: ${this._workingDir}.`];
268
+ const volumeCount = Object.keys(this._volumes).length + this._mounts.length;
269
+ if (volumeCount > 0) parts.push(`${volumeCount} host mount(s) are configured.`);
270
+ if (this._timeout > 0) parts.push(`Default command timeout: ${Math.ceil(this._timeout / 1e3)}s.`);
271
+ return parts.join(" ");
272
+ }
273
+ async _runPlainLifecycle(activeStatus, completeStatus, operation) {
274
+ const managedByBaseWrapper = this.status === activeStatus;
275
+ if (!managedByBaseWrapper) this.status = activeStatus;
276
+ try {
277
+ await operation();
278
+ if (!managedByBaseWrapper) this.status = completeStatus;
279
+ } catch (error) {
280
+ if (!managedByBaseWrapper) this.status = "error";
281
+ throw error;
282
+ }
283
+ }
284
+ async _inspectContainer() {
285
+ const result = await this._runCli(["inspect", this.containerId]);
286
+ if (!result.success) {
287
+ if (isMissingContainerMessage(result.stderr)) return void 0;
288
+ this._assertSuccess(result, `inspect Apple container ${this.containerId}`);
289
+ return;
290
+ }
291
+ try {
292
+ const parsed = JSON.parse(result.stdout);
293
+ return Array.isArray(parsed) ? parsed[0] : parsed;
294
+ } catch (error) {
295
+ throw new _mastra_core_workspace.SandboxExecutionError(`Failed to parse Apple container inspect output for ${this.containerId}: ${error instanceof Error ? error.message : String(error)}`, 1, result.stdout, result.stderr);
296
+ }
297
+ }
298
+ _buildRunArgs(envArgs) {
299
+ const args = [
300
+ "run",
301
+ "-d",
302
+ "--name",
303
+ this._containerName,
304
+ "--workdir",
305
+ this._workingDir
306
+ ];
307
+ args.push(...envArgs);
308
+ for (const [hostPath, containerPath] of Object.entries(this._volumes)) args.push("--volume", `${hostPath}:${containerPath}`);
309
+ for (const mount of this._mounts) args.push("--mount", mount);
310
+ for (const [key, value] of Object.entries(this._labels)) args.push("--label", `${key}=${value}`);
311
+ for (const port of this._publishedPorts) args.push("--publish", port);
312
+ for (const socket of this._publishedSockets) args.push("--publish-socket", socket);
313
+ for (const cap of this._capAdd) args.push("--cap-add", cap);
314
+ for (const cap of this._capDrop) args.push("--cap-drop", cap);
315
+ for (const tmpfs of this._tmpfs) args.push("--tmpfs", tmpfs);
316
+ for (const dns of this._dns) args.push("--dns", dns);
317
+ for (const domain of this._dnsSearch) args.push("--dns-search", domain);
318
+ if (this._network) args.push("--network", this._network);
319
+ if (this._cpus !== void 0) args.push("--cpus", String(this._cpus));
320
+ if (this._memory !== void 0) args.push("--memory", this._memory);
321
+ if (this._platform) args.push("--platform", this._platform);
322
+ if (this._arch) args.push("--arch", this._arch);
323
+ if (this._os) args.push("--os", this._os);
324
+ if (this._rosetta) args.push("--rosetta");
325
+ if (this._readonlyRootfs) args.push("--read-only");
326
+ if (this._ssh) args.push("--ssh");
327
+ if (this._init) args.push("--init");
328
+ if (this._virtualization) args.push("--virtualization");
329
+ if (this._noDns) args.push("--no-dns");
330
+ args.push(this._image, ...this._command);
331
+ return args;
332
+ }
333
+ async _waitUntilContainerReady(action) {
334
+ const deadline = Date.now() + APPLE_CONTAINER_READY_TIMEOUT_MS;
335
+ let lastResult;
336
+ while (Date.now() < deadline) {
337
+ const inspect = await this._inspectContainer();
338
+ if (!inspect) throw new _mastra_core_workspace.SandboxExecutionError(`${action} failed because Apple container ${this.containerId} disappeared`, 1, "", "");
339
+ this._assertMastraOwned(inspect);
340
+ this._assertCompatibleConfig(inspect);
341
+ if (!isRunning(inspect)) {
342
+ const state = getContainerState(inspect) ?? "not running";
343
+ throw new _mastra_core_workspace.SandboxExecutionError(`${action} failed because Apple container ${this.containerId} is ${state}`, 1, "", "");
344
+ }
345
+ lastResult = await this._runCli([
346
+ "exec",
347
+ this.containerId,
348
+ "sh",
349
+ "-lc",
350
+ "true"
351
+ ], { timeout: APPLE_CONTAINER_READY_EXEC_TIMEOUT_MS });
352
+ if (lastResult.success) return;
353
+ if (!isMissingContainerMessage(lastResult.stderr) && !/not running|not yet running/i.test(lastResult.stderr)) break;
354
+ await delay(100);
355
+ }
356
+ throw new _mastra_core_workspace.SandboxExecutionError(`${action} failed because Apple container ${this.containerId} did not become ready for exec`, lastResult?.exitCode ?? 1, lastResult?.stdout ?? "", lastResult?.stderr ?? "");
357
+ }
358
+ async _deleteContainerIgnoringMissing() {
359
+ const result = await this._runCli([
360
+ "delete",
361
+ "--force",
362
+ this.containerId
363
+ ]);
364
+ if (!result.success && !isMissingContainerMessage(result.stderr)) this._assertSuccess(result, `delete Apple container ${this.containerId}`);
365
+ }
366
+ _runtimeConfigForHash() {
367
+ return {
368
+ image: this._image,
369
+ command: this._command,
370
+ env: this._env,
371
+ volumes: this._volumes,
372
+ mounts: this._mounts,
373
+ network: this._network,
374
+ publishedPorts: this._publishedPorts,
375
+ publishedSockets: this._publishedSockets,
376
+ cpus: this._cpus,
377
+ memory: this._memory,
378
+ platform: this._platform,
379
+ arch: this._arch,
380
+ os: this._os,
381
+ rosetta: this._rosetta,
382
+ readonlyRootfs: this._readonlyRootfs,
383
+ ssh: this._ssh,
384
+ init: this._init,
385
+ virtualization: this._virtualization,
386
+ capAdd: this._capAdd,
387
+ capDrop: this._capDrop,
388
+ tmpfs: this._tmpfs,
389
+ dns: this._dns,
390
+ dnsSearch: this._dnsSearch,
391
+ noDns: this._noDns,
392
+ labels: this._userLabels,
393
+ workingDir: this._workingDir
394
+ };
395
+ }
396
+ _serializableConfig() {
397
+ return compactConfig({
398
+ id: this.id,
399
+ name: this._configuredName,
400
+ image: this._image,
401
+ command: this._command,
402
+ env: this._env,
403
+ volumes: this._volumes,
404
+ mounts: this._mounts,
405
+ network: this._network,
406
+ publishedPorts: this._publishedPorts,
407
+ publishedSockets: this._publishedSockets,
408
+ cpus: this._cpus,
409
+ memory: this._memory,
410
+ platform: this._platform,
411
+ arch: this._arch,
412
+ os: this._os,
413
+ rosetta: this._rosetta,
414
+ readonlyRootfs: this._readonlyRootfs,
415
+ ssh: this._ssh,
416
+ init: this._init,
417
+ virtualization: this._virtualization,
418
+ capAdd: this._capAdd,
419
+ capDrop: this._capDrop,
420
+ tmpfs: this._tmpfs,
421
+ dns: this._dns,
422
+ dnsSearch: this._dnsSearch,
423
+ noDns: this._noDns,
424
+ labels: this._userLabels,
425
+ workingDir: this._workingDir,
426
+ timeout: this._timeout,
427
+ deleteOnDestroy: this._deleteOnDestroy
428
+ });
429
+ }
430
+ _assertSuccess(result, action) {
431
+ if (result.success) return;
432
+ throw new _mastra_core_workspace.SandboxExecutionError(`${action} failed with exit code ${result.exitCode}: ${result.stderr}`, result.exitCode, result.stdout, result.stderr);
433
+ }
434
+ _assertMastraOwned(inspect) {
435
+ if (isMastraOwned(inspect, this.id)) return;
436
+ throw new _mastra_core_workspace.SandboxExecutionError(`Refusing to manage Apple container ${this.containerId} because it is not labeled as Mastra sandbox ${this.id}`, 1, "", "");
437
+ }
438
+ _assertCompatibleConfig(inspect) {
439
+ const existingHash = inspect.configuration?.labels?.["mastra.sandbox.config-hash"];
440
+ if (!existingHash || existingHash === this._configHash) return;
441
+ throw new _mastra_core_workspace.SandboxExecutionError(`Refusing to manage Apple container ${this.containerId} because its immutable configuration does not match sandbox ${this.id}`, 1, "", "");
442
+ }
443
+ _runCli(args, options = {}) {
444
+ return this._runner.run(args, {
445
+ timeout: this._timeout,
446
+ ...options
447
+ });
448
+ }
491
449
  };
492
450
  function runAppleContainerCli(binary, args, options = {}) {
493
- const handle = new AppleContainerCliProcess(binary, args, options);
494
- return handle.wait();
451
+ return new AppleContainerCliProcess(binary, args, options).wait();
495
452
  }
496
- var AppleContainerCliProcess = class extends workspace.ProcessHandle {
497
- pid;
498
- exitCode;
499
- child;
500
- waitPromise;
501
- startedAt = Date.now();
502
- killed = false;
503
- timedOut = false;
504
- forceKillTimeout;
505
- constructor(binary, args, options = {}) {
506
- super({
507
- maxRetainedBytes: options.maxRetainedBytes,
508
- onStdout: options.onStdout,
509
- onStderr: options.onStderr
510
- });
511
- this.child = child_process.spawn(binary, args, {
512
- stdio: ["ignore", "pipe", "pipe"],
513
- env: options.env ? { ...process.env, ...options.env } : process.env
514
- });
515
- this.pid = this.child.pid ? String(this.child.pid) : `${binary}:${args.join(" ")}`;
516
- let settled = false;
517
- const stdoutDecoder = new string_decoder.StringDecoder();
518
- const stderrDecoder = new string_decoder.StringDecoder();
519
- let stdoutDecoderEnded = false;
520
- let stderrDecoderEnded = false;
521
- let timeout;
522
- const onAbort = () => {
523
- void this.kill();
524
- };
525
- const flushStdout = () => {
526
- if (stdoutDecoderEnded) return;
527
- stdoutDecoderEnded = true;
528
- const data = stdoutDecoder.end();
529
- if (data) this.emitStdout(data);
530
- };
531
- const flushStderr = () => {
532
- if (stderrDecoderEnded) return;
533
- stderrDecoderEnded = true;
534
- const data = stderrDecoder.end();
535
- if (data) this.emitStderr(data);
536
- };
537
- const finish = (exitCode) => {
538
- this.exitCode = exitCode;
539
- return {
540
- success: exitCode === 0,
541
- exitCode,
542
- stdout: this.stdout,
543
- stderr: this.stderr,
544
- executionTimeMs: Date.now() - this.startedAt,
545
- killed: this.killed,
546
- timedOut: this.timedOut
547
- };
548
- };
549
- const cleanup = () => {
550
- if (timeout) clearTimeout(timeout);
551
- if (this.forceKillTimeout) clearTimeout(this.forceKillTimeout);
552
- options.abortSignal?.removeEventListener("abort", onAbort);
553
- };
554
- this.waitPromise = new Promise((resolve, reject) => {
555
- const settle = (callback) => {
556
- if (settled) return;
557
- settled = true;
558
- cleanup();
559
- callback();
560
- };
561
- this.child.stdout.on("data", (chunk) => {
562
- const data = stdoutDecoder.write(chunk);
563
- if (data) this.emitStdout(data);
564
- });
565
- this.child.stderr.on("data", (chunk) => {
566
- const data = stderrDecoder.write(chunk);
567
- if (data) this.emitStderr(data);
568
- });
569
- this.child.stdout.on("end", flushStdout);
570
- this.child.stderr.on("end", flushStderr);
571
- this.child.on("error", (error) => {
572
- settle(() => {
573
- flushStdout();
574
- flushStderr();
575
- reject(
576
- error instanceof Error && "code" in error && error.code === "ENOENT" ? new workspace.SandboxExecutionError(`Apple container CLI not found: ${binary}`, 127, this.stdout, error.message) : error
577
- );
578
- });
579
- });
580
- this.child.on("close", (code) => {
581
- settle(() => {
582
- flushStdout();
583
- flushStderr();
584
- resolve(finish(code ?? (this.killed ? 137 : 1)));
585
- });
586
- });
587
- });
588
- timeout = options.timeout && options.timeout > 0 ? setTimeout(() => {
589
- this.timedOut = true;
590
- void this.kill();
591
- }, options.timeout) : void 0;
592
- if (options.abortSignal) {
593
- if (options.abortSignal.aborted) {
594
- void this.kill();
595
- } else {
596
- options.abortSignal.addEventListener("abort", onAbort, { once: true });
597
- }
598
- }
599
- }
600
- async wait() {
601
- return this.waitPromise;
602
- }
603
- async kill() {
604
- if (this.exitCode !== void 0 || this.child.killed) return false;
605
- this.killed = true;
606
- this.child.kill("SIGTERM");
607
- this.forceKillTimeout = setTimeout(() => {
608
- if (this.exitCode === void 0 && this.child.exitCode === null && this.child.signalCode === null) {
609
- this.child.kill("SIGKILL");
610
- }
611
- }, 1e3);
612
- this.forceKillTimeout.unref();
613
- return true;
614
- }
615
- async sendStdin() {
616
- throw new Error("Apple container CLI runner does not support stdin");
617
- }
453
+ var AppleContainerCliProcess = class extends _mastra_core_workspace.ProcessHandle {
454
+ pid;
455
+ exitCode;
456
+ child;
457
+ waitPromise;
458
+ startedAt = Date.now();
459
+ killed = false;
460
+ timedOut = false;
461
+ forceKillTimeout;
462
+ constructor(binary, args, options = {}) {
463
+ super({
464
+ maxRetainedBytes: options.maxRetainedBytes,
465
+ onStdout: options.onStdout,
466
+ onStderr: options.onStderr
467
+ });
468
+ this.child = (0, child_process.spawn)(binary, args, {
469
+ stdio: [
470
+ "ignore",
471
+ "pipe",
472
+ "pipe"
473
+ ],
474
+ env: options.env ? {
475
+ ...process.env,
476
+ ...options.env
477
+ } : process.env
478
+ });
479
+ this.pid = this.child.pid ? String(this.child.pid) : `${binary}:${args.join(" ")}`;
480
+ let settled = false;
481
+ const stdoutDecoder = new string_decoder.StringDecoder();
482
+ const stderrDecoder = new string_decoder.StringDecoder();
483
+ let stdoutDecoderEnded = false;
484
+ let stderrDecoderEnded = false;
485
+ let timeout;
486
+ const onAbort = () => {
487
+ this.kill();
488
+ };
489
+ const flushStdout = () => {
490
+ if (stdoutDecoderEnded) return;
491
+ stdoutDecoderEnded = true;
492
+ const data = stdoutDecoder.end();
493
+ if (data) this.emitStdout(data);
494
+ };
495
+ const flushStderr = () => {
496
+ if (stderrDecoderEnded) return;
497
+ stderrDecoderEnded = true;
498
+ const data = stderrDecoder.end();
499
+ if (data) this.emitStderr(data);
500
+ };
501
+ const finish = (exitCode) => {
502
+ this.exitCode = exitCode;
503
+ return {
504
+ success: exitCode === 0,
505
+ exitCode,
506
+ stdout: this.stdout,
507
+ stderr: this.stderr,
508
+ executionTimeMs: Date.now() - this.startedAt,
509
+ killed: this.killed,
510
+ timedOut: this.timedOut
511
+ };
512
+ };
513
+ const cleanup = () => {
514
+ if (timeout) clearTimeout(timeout);
515
+ if (this.forceKillTimeout) clearTimeout(this.forceKillTimeout);
516
+ options.abortSignal?.removeEventListener("abort", onAbort);
517
+ };
518
+ this.waitPromise = new Promise((resolve, reject) => {
519
+ const settle = (callback) => {
520
+ if (settled) return;
521
+ settled = true;
522
+ cleanup();
523
+ callback();
524
+ };
525
+ this.child.stdout.on("data", (chunk) => {
526
+ const data = stdoutDecoder.write(chunk);
527
+ if (data) this.emitStdout(data);
528
+ });
529
+ this.child.stderr.on("data", (chunk) => {
530
+ const data = stderrDecoder.write(chunk);
531
+ if (data) this.emitStderr(data);
532
+ });
533
+ this.child.stdout.on("end", flushStdout);
534
+ this.child.stderr.on("end", flushStderr);
535
+ this.child.on("error", (error) => {
536
+ settle(() => {
537
+ flushStdout();
538
+ flushStderr();
539
+ reject(error instanceof Error && "code" in error && error.code === "ENOENT" ? new _mastra_core_workspace.SandboxExecutionError(`Apple container CLI not found: ${binary}`, 127, this.stdout, error.message) : error);
540
+ });
541
+ });
542
+ this.child.on("close", (code) => {
543
+ settle(() => {
544
+ flushStdout();
545
+ flushStderr();
546
+ resolve(finish(code ?? (this.killed ? 137 : 1)));
547
+ });
548
+ });
549
+ });
550
+ timeout = options.timeout && options.timeout > 0 ? setTimeout(() => {
551
+ this.timedOut = true;
552
+ this.kill();
553
+ }, options.timeout) : void 0;
554
+ if (options.abortSignal) if (options.abortSignal.aborted) this.kill();
555
+ else options.abortSignal.addEventListener("abort", onAbort, { once: true });
556
+ }
557
+ async wait() {
558
+ return this.waitPromise;
559
+ }
560
+ async kill() {
561
+ if (this.exitCode !== void 0 || this.child.killed) return false;
562
+ this.killed = true;
563
+ this.child.kill("SIGTERM");
564
+ this.forceKillTimeout = setTimeout(() => {
565
+ if (this.exitCode === void 0 && this.child.exitCode === null && this.child.signalCode === null) this.child.kill("SIGKILL");
566
+ }, 1e3);
567
+ this.forceKillTimeout.unref();
568
+ return true;
569
+ }
570
+ async sendStdin() {
571
+ throw new Error("Apple container CLI runner does not support stdin");
572
+ }
573
+ async closeStdin() {
574
+ throw new _mastra_core_workspace.UnsupportedStdinCloseError("Apple container CLI runner does not support closing stdin");
575
+ }
618
576
  };
619
577
  function generateId() {
620
- return `apple-container-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
578
+ return `apple-container-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
621
579
  }
622
580
  function sanitizeContainerName(name) {
623
- const sanitized = name.replace(/[^a-zA-Z0-9_.-]/g, "-");
624
- return /^[a-zA-Z0-9]/.test(sanitized) ? sanitized : `c-${sanitized}`;
581
+ const sanitized = name.replace(/[^a-zA-Z0-9_.-]/g, "-");
582
+ return /^[a-zA-Z0-9]/.test(sanitized) ? sanitized : `c-${sanitized}`;
625
583
  }
626
584
  function buildShellCommand(command, args) {
627
- return args.length > 0 ? [command, ...args].map(shellQuote).join(" ") : command;
585
+ return args.length > 0 ? [command, ...args].map(shellQuote).join(" ") : command;
628
586
  }
629
587
  function buildTimeoutShellCommand(command, timeoutMs) {
630
- const timeoutSeconds = formatTimeoutSeconds(timeoutMs);
631
- const innerScript = [
632
- `sh -lc ${shellQuote(command)} & child=$!`,
633
- `trap 'kill -TERM "$child" 2>/dev/null; wait "$child" 2>/dev/null; exit ${APPLE_CONTAINER_TIMEOUT_EXIT_CODE}' TERM INT`,
634
- 'wait "$child"; code=$?',
635
- "trap - TERM INT",
636
- 'printf "%s" "$code" > "$MASTRA_TIMEOUT_RESULT_FILE"',
637
- 'exit "$code"'
638
- ].join("; ");
639
- return [
640
- 'result_file="/tmp/.mastra-apple-container-exit-$$"',
641
- 'rm -f "$result_file"',
642
- `MASTRA_TIMEOUT_RESULT_FILE="$result_file" timeout ${timeoutSeconds}s sh -lc ${shellQuote(innerScript)}`,
643
- "timeout_code=$?",
644
- 'if [ -f "$result_file" ]; then code="$(cat "$result_file")"; rm -f "$result_file"; exit "$code"; fi',
645
- 'rm -f "$result_file"',
646
- `case "$timeout_code" in 124|137|143) printf '%s\\n' ${shellQuote(APPLE_CONTAINER_TIMEOUT_MARKER)} >&2; exit ${APPLE_CONTAINER_TIMEOUT_EXIT_CODE};; *) exit "$timeout_code";; esac`
647
- ].join("; ");
588
+ return [
589
+ "result_file=\"/tmp/.mastra-apple-container-exit-$$\"",
590
+ "rm -f \"$result_file\"",
591
+ `MASTRA_TIMEOUT_RESULT_FILE="$result_file" timeout ${formatTimeoutSeconds(timeoutMs)}s sh -lc ${shellQuote([
592
+ `sh -lc ${shellQuote(command)} & child=$!`,
593
+ `trap 'kill -TERM "$child" 2>/dev/null; wait "$child" 2>/dev/null; exit ${APPLE_CONTAINER_TIMEOUT_EXIT_CODE}' TERM INT`,
594
+ "wait \"$child\"; code=$?",
595
+ "trap - TERM INT",
596
+ "printf \"%s\" \"$code\" > \"$MASTRA_TIMEOUT_RESULT_FILE\"",
597
+ "exit \"$code\""
598
+ ].join("; "))}`,
599
+ "timeout_code=$?",
600
+ "if [ -f \"$result_file\" ]; then code=\"$(cat \"$result_file\")\"; rm -f \"$result_file\"; exit \"$code\"; fi",
601
+ "rm -f \"$result_file\"",
602
+ `case "$timeout_code" in 124|137|143) printf '%s\\n' ${shellQuote(APPLE_CONTAINER_TIMEOUT_MARKER)} >&2; exit ${APPLE_CONTAINER_TIMEOUT_EXIT_CODE};; *) exit "$timeout_code";; esac`
603
+ ].join("; ");
648
604
  }
649
605
  function formatTimeoutSeconds(timeoutMs) {
650
- return Math.max(timeoutMs / 1e3, 1e-3).toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
606
+ return Math.max(timeoutMs / 1e3, .001).toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
651
607
  }
652
608
  function shellQuote(arg) {
653
- if (/^[a-zA-Z0-9._\-\/=:@]+$/.test(arg)) return arg;
654
- return `'${arg.replace(/'/g, "'\\''")}'`;
609
+ if (/^[a-zA-Z0-9._\-\/=:@]+$/.test(arg)) return arg;
610
+ return `'${arg.replace(/'/g, "'\\''")}'`;
655
611
  }
656
612
  function stripTimeoutMarker(stderr) {
657
- return stderr.split("\n").filter((line) => line.trim() !== APPLE_CONTAINER_TIMEOUT_MARKER).join("\n").replace(/^\n+|\n+$/g, "");
613
+ return stderr.split("\n").filter((line) => line.trim() !== APPLE_CONTAINER_TIMEOUT_MARKER).join("\n").replace(/^\n+|\n+$/g, "");
658
614
  }
659
615
  function envFlags(env) {
660
- const args = [];
661
- const childEnv = {};
662
- for (const [key, value] of Object.entries(env)) {
663
- if (value === void 0) continue;
664
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
665
- throw new Error(`Invalid environment variable name for Apple container command: ${key}`);
666
- }
667
- args.push("--env", key);
668
- childEnv[key] = value;
669
- }
670
- return { args, env: childEnv };
616
+ const args = [];
617
+ const childEnv = {};
618
+ for (const [key, value] of Object.entries(env)) {
619
+ if (value === void 0) continue;
620
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`Invalid environment variable name for Apple container command: ${key}`);
621
+ args.push("--env", key);
622
+ childEnv[key] = value;
623
+ }
624
+ return {
625
+ args,
626
+ env: childEnv
627
+ };
671
628
  }
672
629
  function isRunning(inspect) {
673
- return getContainerState(inspect) === "running";
630
+ return getContainerState(inspect) === "running";
674
631
  }
675
632
  function getContainerState(inspect) {
676
- return typeof inspect.status === "string" ? inspect.status : inspect.status?.state;
633
+ return typeof inspect.status === "string" ? inspect.status : inspect.status?.state;
677
634
  }
678
635
  function isMastraOwned(inspect, sandboxId) {
679
- const labels = inspect.configuration?.labels;
680
- return labels?.["mastra.sandbox"] === "true" && labels["mastra.sandbox.id"] === sandboxId;
636
+ const labels = inspect.configuration?.labels;
637
+ return labels?.["mastra.sandbox"] === "true" && labels["mastra.sandbox.id"] === sandboxId;
681
638
  }
682
639
  function isMissingContainerMessage(message) {
683
- return /not found|no such|does not exist|unknown container/i.test(message);
640
+ return /not found|no such|does not exist|unknown container/i.test(message);
684
641
  }
685
642
  function validateTmpfsPaths(tmpfs) {
686
- for (const entry of tmpfs) {
687
- if (!entry.startsWith("/") || /[:,]/.test(entry)) {
688
- throw new Error(
689
- `Invalid Apple container tmpfs path "${entry}". Apple container --tmpfs accepts container paths only, for example "/tmp".`
690
- );
691
- }
692
- }
643
+ for (const entry of tmpfs) if (!entry.startsWith("/") || /[:,]/.test(entry)) throw new Error(`Invalid Apple container tmpfs path "${entry}". Apple container --tmpfs accepts container paths only, for example "/tmp".`);
693
644
  }
694
645
  function compactConfig(config) {
695
- const result = {};
696
- for (const [key, value] of Object.entries(config)) {
697
- if (value === void 0) continue;
698
- if (Array.isArray(value) && value.length === 0) continue;
699
- if (isPlainRecord(value) && Object.keys(value).length === 0) continue;
700
- result[key] = value;
701
- }
702
- return result;
646
+ const result = {};
647
+ for (const [key, value] of Object.entries(config)) {
648
+ if (value === void 0) continue;
649
+ if (Array.isArray(value) && value.length === 0) continue;
650
+ if (isPlainRecord(value) && Object.keys(value).length === 0) continue;
651
+ result[key] = value;
652
+ }
653
+ return result;
703
654
  }
704
655
  function hashConfig(config) {
705
- return crypto.createHash("sha256").update(stableStringify(compactConfig(config))).digest("hex").slice(0, 16);
656
+ return (0, crypto.createHash)("sha256").update(stableStringify(compactConfig(config))).digest("hex").slice(0, 16);
706
657
  }
707
658
  function stableStringify(value) {
708
- if (Array.isArray(value)) {
709
- return `[${value.map(stableStringify).join(",")}]`;
710
- }
711
- if (isPlainRecord(value)) {
712
- return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
713
- }
714
- return JSON.stringify(value);
659
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
660
+ if (isPlainRecord(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
661
+ return JSON.stringify(value);
715
662
  }
716
663
  function isPlainRecord(value) {
717
- return typeof value === "object" && value !== null && !Array.isArray(value);
664
+ return typeof value === "object" && value !== null && !Array.isArray(value);
718
665
  }
719
666
  function delay(ms) {
720
- return new Promise((resolve) => setTimeout(resolve, ms));
667
+ return new Promise((resolve) => setTimeout(resolve, ms));
721
668
  }
722
-
723
- // src/provider.ts
724
- var appleContainerSandboxProvider = {
725
- id: "apple-container",
726
- name: "Apple Container Sandbox",
727
- description: "Local OCI Linux container sandbox powered by Apple container",
728
- configSchema: {
729
- type: "object",
730
- additionalProperties: false,
731
- properties: {
732
- id: {
733
- type: "string",
734
- description: "Stable sandbox ID used for reconnecting to the same Apple container."
735
- },
736
- image: {
737
- type: "string",
738
- description: "OCI image to use",
739
- default: "node:22-slim"
740
- },
741
- name: {
742
- type: "string",
743
- description: "Apple container name. Defaults to the sandbox ID."
744
- },
745
- command: {
746
- type: "array",
747
- description: "Container init command. Must keep the container alive for exec-based command execution.",
748
- items: { type: "string" }
749
- },
750
- env: {
751
- type: "object",
752
- description: "Environment variables",
753
- additionalProperties: { type: "string" }
754
- },
755
- volumes: {
756
- type: "object",
757
- description: "Host-to-container bind mounts (host path -> container path)",
758
- additionalProperties: { type: "string" }
759
- },
760
- mounts: {
761
- type: "array",
762
- description: "Raw Apple container --mount specs",
763
- items: { type: "string" }
764
- },
765
- network: {
766
- type: "string",
767
- description: "Apple container network attachment spec"
768
- },
769
- publishedPorts: {
770
- type: "array",
771
- description: "Port publish specs",
772
- items: { type: "string" }
773
- },
774
- publishedSockets: {
775
- type: "array",
776
- description: "Socket publish specs",
777
- items: { type: "string" }
778
- },
779
- cpus: {
780
- anyOf: [{ type: "number" }, { type: "string" }],
781
- description: "Number of CPUs to allocate"
782
- },
783
- memory: {
784
- type: "string",
785
- description: "Memory allocation, for example 1G"
786
- },
787
- platform: {
788
- type: "string",
789
- description: "OCI platform, for example linux/arm64"
790
- },
791
- arch: {
792
- type: "string",
793
- description: "Image architecture for multi-arch images"
794
- },
795
- os: {
796
- type: "string",
797
- description: "Operating system for multi-platform images"
798
- },
799
- rosetta: {
800
- type: "boolean",
801
- description: "Enable Rosetta in the container",
802
- default: false
803
- },
804
- readonlyRootfs: {
805
- type: "boolean",
806
- description: "Mount the container root filesystem as read-only",
807
- default: false
808
- },
809
- ssh: {
810
- type: "boolean",
811
- description: "Forward the host SSH agent socket",
812
- default: false
813
- },
814
- init: {
815
- type: "boolean",
816
- description: "Enable Apple's init process in the container",
817
- default: true
818
- },
819
- virtualization: {
820
- type: "boolean",
821
- description: "Expose virtualization capabilities to the container",
822
- default: false
823
- },
824
- capAdd: {
825
- type: "array",
826
- description: "Linux capabilities to add",
827
- items: { type: "string" }
828
- },
829
- capDrop: {
830
- type: "array",
831
- description: "Linux capabilities to drop",
832
- items: { type: "string" }
833
- },
834
- tmpfs: {
835
- type: "array",
836
- description: "tmpfs destination paths",
837
- items: { type: "string" }
838
- },
839
- dns: {
840
- type: "array",
841
- description: "DNS nameserver IPs",
842
- items: { type: "string" }
843
- },
844
- dnsSearch: {
845
- type: "array",
846
- description: "DNS search domains",
847
- items: { type: "string" }
848
- },
849
- noDns: {
850
- type: "boolean",
851
- description: "Do not configure DNS in the container",
852
- default: false
853
- },
854
- labels: {
855
- type: "object",
856
- description: "Container labels",
857
- additionalProperties: { type: "string" }
858
- },
859
- workingDir: {
860
- type: "string",
861
- description: "Working directory inside the container",
862
- default: "/workspace"
863
- },
864
- timeout: {
865
- type: "number",
866
- description: "Default command timeout in milliseconds",
867
- default: 3e5
868
- },
869
- deleteOnDestroy: {
870
- type: "boolean",
871
- description: "Delete the Apple container on destroy",
872
- default: true
873
- }
874
- }
875
- },
876
- createSandbox: (config) => {
877
- const {
878
- id,
879
- image,
880
- name,
881
- command,
882
- env,
883
- volumes,
884
- mounts,
885
- network,
886
- publishedPorts,
887
- publishedSockets,
888
- cpus,
889
- memory,
890
- platform,
891
- arch,
892
- os,
893
- rosetta,
894
- readonlyRootfs,
895
- ssh,
896
- init,
897
- virtualization,
898
- capAdd,
899
- capDrop,
900
- tmpfs,
901
- dns,
902
- dnsSearch,
903
- noDns,
904
- labels,
905
- workingDir,
906
- timeout,
907
- deleteOnDestroy
908
- } = config;
909
- return new AppleContainerSandbox({
910
- id,
911
- image,
912
- name,
913
- command,
914
- env,
915
- volumes,
916
- mounts,
917
- network,
918
- publishedPorts,
919
- publishedSockets,
920
- cpus,
921
- memory,
922
- platform,
923
- arch,
924
- os,
925
- rosetta,
926
- readonlyRootfs,
927
- ssh,
928
- init,
929
- virtualization,
930
- capAdd,
931
- capDrop,
932
- tmpfs,
933
- dns,
934
- dnsSearch,
935
- noDns,
936
- labels,
937
- workingDir,
938
- timeout,
939
- deleteOnDestroy
940
- });
941
- }
669
+ //#endregion
670
+ //#region src/provider.ts
671
+ const appleContainerSandboxProvider = {
672
+ id: "apple-container",
673
+ name: "Apple Container Sandbox",
674
+ description: "Local OCI Linux container sandbox powered by Apple container",
675
+ configSchema: {
676
+ type: "object",
677
+ additionalProperties: false,
678
+ properties: {
679
+ id: {
680
+ type: "string",
681
+ description: "Stable sandbox ID used for reconnecting to the same Apple container."
682
+ },
683
+ image: {
684
+ type: "string",
685
+ description: "OCI image to use",
686
+ default: "node:22-slim"
687
+ },
688
+ name: {
689
+ type: "string",
690
+ description: "Apple container name. Defaults to the sandbox ID."
691
+ },
692
+ command: {
693
+ type: "array",
694
+ description: "Container init command. Must keep the container alive for exec-based command execution.",
695
+ items: { type: "string" }
696
+ },
697
+ env: {
698
+ type: "object",
699
+ description: "Environment variables",
700
+ additionalProperties: { type: "string" }
701
+ },
702
+ volumes: {
703
+ type: "object",
704
+ description: "Host-to-container bind mounts (host path -> container path)",
705
+ additionalProperties: { type: "string" }
706
+ },
707
+ mounts: {
708
+ type: "array",
709
+ description: "Raw Apple container --mount specs",
710
+ items: { type: "string" }
711
+ },
712
+ network: {
713
+ type: "string",
714
+ description: "Apple container network attachment spec"
715
+ },
716
+ publishedPorts: {
717
+ type: "array",
718
+ description: "Port publish specs",
719
+ items: { type: "string" }
720
+ },
721
+ publishedSockets: {
722
+ type: "array",
723
+ description: "Socket publish specs",
724
+ items: { type: "string" }
725
+ },
726
+ cpus: {
727
+ anyOf: [{ type: "number" }, { type: "string" }],
728
+ description: "Number of CPUs to allocate"
729
+ },
730
+ memory: {
731
+ type: "string",
732
+ description: "Memory allocation, for example 1G"
733
+ },
734
+ platform: {
735
+ type: "string",
736
+ description: "OCI platform, for example linux/arm64"
737
+ },
738
+ arch: {
739
+ type: "string",
740
+ description: "Image architecture for multi-arch images"
741
+ },
742
+ os: {
743
+ type: "string",
744
+ description: "Operating system for multi-platform images"
745
+ },
746
+ rosetta: {
747
+ type: "boolean",
748
+ description: "Enable Rosetta in the container",
749
+ default: false
750
+ },
751
+ readonlyRootfs: {
752
+ type: "boolean",
753
+ description: "Mount the container root filesystem as read-only",
754
+ default: false
755
+ },
756
+ ssh: {
757
+ type: "boolean",
758
+ description: "Forward the host SSH agent socket",
759
+ default: false
760
+ },
761
+ init: {
762
+ type: "boolean",
763
+ description: "Enable Apple's init process in the container",
764
+ default: true
765
+ },
766
+ virtualization: {
767
+ type: "boolean",
768
+ description: "Expose virtualization capabilities to the container",
769
+ default: false
770
+ },
771
+ capAdd: {
772
+ type: "array",
773
+ description: "Linux capabilities to add",
774
+ items: { type: "string" }
775
+ },
776
+ capDrop: {
777
+ type: "array",
778
+ description: "Linux capabilities to drop",
779
+ items: { type: "string" }
780
+ },
781
+ tmpfs: {
782
+ type: "array",
783
+ description: "tmpfs destination paths",
784
+ items: { type: "string" }
785
+ },
786
+ dns: {
787
+ type: "array",
788
+ description: "DNS nameserver IPs",
789
+ items: { type: "string" }
790
+ },
791
+ dnsSearch: {
792
+ type: "array",
793
+ description: "DNS search domains",
794
+ items: { type: "string" }
795
+ },
796
+ noDns: {
797
+ type: "boolean",
798
+ description: "Do not configure DNS in the container",
799
+ default: false
800
+ },
801
+ labels: {
802
+ type: "object",
803
+ description: "Container labels",
804
+ additionalProperties: { type: "string" }
805
+ },
806
+ workingDir: {
807
+ type: "string",
808
+ description: "Working directory inside the container",
809
+ default: "/workspace"
810
+ },
811
+ timeout: {
812
+ type: "number",
813
+ description: "Default command timeout in milliseconds",
814
+ default: 3e5
815
+ },
816
+ deleteOnDestroy: {
817
+ type: "boolean",
818
+ description: "Delete the Apple container on destroy",
819
+ default: true
820
+ }
821
+ }
822
+ },
823
+ createSandbox: (config) => {
824
+ const { id, image, name, command, env, volumes, mounts, network, publishedPorts, publishedSockets, cpus, memory, platform, arch, os, rosetta, readonlyRootfs, ssh, init, virtualization, capAdd, capDrop, tmpfs, dns, dnsSearch, noDns, labels, workingDir, timeout, deleteOnDestroy } = config;
825
+ return new AppleContainerSandbox({
826
+ id,
827
+ image,
828
+ name,
829
+ command,
830
+ env,
831
+ volumes,
832
+ mounts,
833
+ network,
834
+ publishedPorts,
835
+ publishedSockets,
836
+ cpus,
837
+ memory,
838
+ platform,
839
+ arch,
840
+ os,
841
+ rosetta,
842
+ readonlyRootfs,
843
+ ssh,
844
+ init,
845
+ virtualization,
846
+ capAdd,
847
+ capDrop,
848
+ tmpfs,
849
+ dns,
850
+ dnsSearch,
851
+ noDns,
852
+ labels,
853
+ workingDir,
854
+ timeout,
855
+ deleteOnDestroy
856
+ });
857
+ }
942
858
  };
943
-
859
+ //#endregion
944
860
  exports.AppleContainerSandbox = AppleContainerSandbox;
945
861
  exports.DefaultAppleContainerCommandRunner = DefaultAppleContainerCommandRunner;
946
862
  exports.appleContainerSandboxProvider = appleContainerSandboxProvider;
947
- //# sourceMappingURL=index.cjs.map
863
+
948
864
  //# sourceMappingURL=index.cjs.map