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