@mastra/railway 0.4.0 → 0.4.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/dist/index.cjs +614 -646
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +605 -639
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +50 -20
- package/dist/sandbox/index.d.ts.map +1 -1
- package/package.json +9 -8
package/dist/index.cjs
CHANGED
|
@@ -1,656 +1,624 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _mastra_core_workspace = require("@mastra/core/workspace");
|
|
3
|
+
let railway = require("railway");
|
|
4
|
+
//#region src/utils/shell-quote.ts
|
|
5
|
+
/**
|
|
6
|
+
* Shell-quote a single argument for safe use in a command string.
|
|
7
|
+
*
|
|
8
|
+
* Arguments containing only safe characters are returned as-is.
|
|
9
|
+
* All others are wrapped in single quotes with embedded single quotes escaped.
|
|
10
|
+
*/
|
|
9
11
|
function shellQuote(arg) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
if (/^[a-zA-Z0-9._\-/@:=]+$/.test(arg)) return arg;
|
|
13
|
+
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
|
12
14
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/sandbox/process-manager.ts
|
|
17
|
+
/**
|
|
18
|
+
* Railway Process Manager
|
|
19
|
+
*
|
|
20
|
+
* Implements SandboxProcessManager for Railway sandboxes.
|
|
21
|
+
* Wraps the Railway SDK's `Sandbox.exec()` API.
|
|
22
|
+
*
|
|
23
|
+
* Railway's `exec(command, options)` accepts per-call `cwd` and `env` options
|
|
24
|
+
* (since SDK v3.3.1) and returns an `ExecHandle` that runs the command
|
|
25
|
+
* server-side, independently of the client. Each spawn() starts one exec.
|
|
26
|
+
* The handle streams output via `onStdout`/`onStderr` callbacks wired to
|
|
27
|
+
* `emitStdout`/`emitStderr`, exposes a durable `sessionName`, and can be
|
|
28
|
+
* terminated with `kill(signal)`.
|
|
29
|
+
*/
|
|
30
|
+
const LOG_PREFIX = "[RailwaySandbox]";
|
|
31
|
+
/**
|
|
32
|
+
* Wraps a Railway ExecHandle to conform to Mastra's ProcessHandle.
|
|
33
|
+
* Not exported — internal to this module.
|
|
34
|
+
*/
|
|
35
|
+
var RailwayProcessHandle = class extends _mastra_core_workspace.ProcessHandle {
|
|
36
|
+
pid;
|
|
37
|
+
_execHandle;
|
|
38
|
+
_startTime;
|
|
39
|
+
_exitCode;
|
|
40
|
+
_waitPromise = null;
|
|
41
|
+
_killed = false;
|
|
42
|
+
constructor(pid, execHandle, startTime, options) {
|
|
43
|
+
super(options);
|
|
44
|
+
this.pid = pid;
|
|
45
|
+
this._execHandle = execHandle;
|
|
46
|
+
this._startTime = startTime;
|
|
47
|
+
this._execHandle.then((result) => {
|
|
48
|
+
this._exitCode = result.exitCode ?? (this._killed ? 137 : -1);
|
|
49
|
+
}, () => {
|
|
50
|
+
if (this._exitCode === void 0) this._exitCode = 1;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
get exitCode() {
|
|
54
|
+
return this._exitCode;
|
|
55
|
+
}
|
|
56
|
+
async wait() {
|
|
57
|
+
if (!this._waitPromise) this._waitPromise = this._doWait();
|
|
58
|
+
return this._waitPromise;
|
|
59
|
+
}
|
|
60
|
+
async _doWait() {
|
|
61
|
+
try {
|
|
62
|
+
const result = await this._execHandle;
|
|
63
|
+
const exitCode = result.exitCode ?? (this._killed ? 137 : -1);
|
|
64
|
+
this._exitCode = exitCode;
|
|
65
|
+
if (result.stdout && !this.stdout) this.emitStdout(result.stdout);
|
|
66
|
+
if (result.stderr && !this.stderr) this.emitStderr(result.stderr);
|
|
67
|
+
return {
|
|
68
|
+
success: exitCode === 0,
|
|
69
|
+
exitCode,
|
|
70
|
+
stdout: this.stdout,
|
|
71
|
+
stderr: this.stderr,
|
|
72
|
+
executionTimeMs: Date.now() - this._startTime,
|
|
73
|
+
killed: this._killed,
|
|
74
|
+
timedOut: result.timedOut
|
|
75
|
+
};
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const exitCode = this._exitCode ?? 1;
|
|
78
|
+
this._exitCode = exitCode;
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
exitCode,
|
|
82
|
+
stdout: this.stdout,
|
|
83
|
+
stderr: this.stderr || (error instanceof Error ? error.message : String(error)),
|
|
84
|
+
executionTimeMs: Date.now() - this._startTime,
|
|
85
|
+
killed: this._killed
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async kill() {
|
|
90
|
+
if (this._exitCode !== void 0) return false;
|
|
91
|
+
this._killed = true;
|
|
92
|
+
try {
|
|
93
|
+
return await this._execHandle.kill("TERM");
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async sendStdin(_data) {
|
|
99
|
+
throw new Error(`${LOG_PREFIX} sending stdin is not supported by the Railway sandbox provider`);
|
|
100
|
+
}
|
|
87
101
|
};
|
|
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
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Railway implementation of SandboxProcessManager.
|
|
104
|
+
* Uses the Railway SDK's `Sandbox.exec()` with one exec per spawned process.
|
|
105
|
+
*/
|
|
106
|
+
var RailwayProcessManager = class extends _mastra_core_workspace.SandboxProcessManager {
|
|
107
|
+
_spawnCounter = 0;
|
|
108
|
+
constructor(opts = {}) {
|
|
109
|
+
super({ env: opts.env });
|
|
110
|
+
}
|
|
111
|
+
async spawn(command, options = {}) {
|
|
112
|
+
const railway = this.sandbox.railway;
|
|
113
|
+
const mergedEnv = {
|
|
114
|
+
...this.env,
|
|
115
|
+
...options.env
|
|
116
|
+
};
|
|
117
|
+
const env = Object.fromEntries(Object.entries(mergedEnv).filter((entry) => entry[1] !== void 0));
|
|
118
|
+
const pid = `railway-proc-${Date.now().toString(36)}-${(this._spawnCounter++).toString(36)}`;
|
|
119
|
+
let handle;
|
|
120
|
+
handle = new RailwayProcessHandle(pid, railway.exec(command, {
|
|
121
|
+
...options.timeout !== void 0 && { timeoutSec: Math.ceil(options.timeout / 1e3) },
|
|
122
|
+
...options.cwd !== void 0 && { cwd: options.cwd },
|
|
123
|
+
...Object.keys(env).length > 0 && { env },
|
|
124
|
+
onStdout: (chunk) => handle.emitStdout(chunk),
|
|
125
|
+
onStderr: (chunk) => handle.emitStderr(chunk)
|
|
126
|
+
}), Date.now(), options);
|
|
127
|
+
this._tracked.set(handle.pid, handle);
|
|
128
|
+
return handle;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* List tracked processes.
|
|
132
|
+
*
|
|
133
|
+
* Railway has no API to enumerate running exec sessions by sandbox, so this
|
|
134
|
+
* reports the processes this manager spawned.
|
|
135
|
+
*/
|
|
136
|
+
async list() {
|
|
137
|
+
return Array.from(this._tracked.values()).map((handle) => ({
|
|
138
|
+
pid: handle.pid,
|
|
139
|
+
command: handle.command,
|
|
140
|
+
running: handle.exitCode === void 0,
|
|
141
|
+
...handle.exitCode !== void 0 && { exitCode: handle.exitCode }
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
126
144
|
};
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
getInstructions() {
|
|
554
|
-
const defaultInstructions = this._buildDefaultInstructions();
|
|
555
|
-
if (typeof this._instructionsOverride === "string") {
|
|
556
|
-
return this._instructionsOverride;
|
|
557
|
-
}
|
|
558
|
-
if (typeof this._instructionsOverride === "function") {
|
|
559
|
-
return this._instructionsOverride({ defaultInstructions });
|
|
560
|
-
}
|
|
561
|
-
return defaultInstructions;
|
|
562
|
-
}
|
|
563
|
-
_buildDefaultInstructions() {
|
|
564
|
-
const parts = [];
|
|
565
|
-
parts.push("Railway cloud sandbox: an isolated Debian Linux VM with outbound internet access.");
|
|
566
|
-
if (this._networkIsolation === "PRIVATE") {
|
|
567
|
-
parts.push("Joined to the environment private network.");
|
|
568
|
-
}
|
|
569
|
-
if (this._timeout !== void 0) {
|
|
570
|
-
parts.push(`Default command timeout: ${Math.ceil(this._timeout / 1e3)}s.`);
|
|
571
|
-
} else {
|
|
572
|
-
parts.push("Commands run until they exit unless a timeout is set.");
|
|
573
|
-
}
|
|
574
|
-
if (this._idleTimeoutMinutes !== void 0) {
|
|
575
|
-
parts.push(`Idle timeout: ${this._idleTimeoutMinutes} minute(s).`);
|
|
576
|
-
}
|
|
577
|
-
return parts.join(" ");
|
|
578
|
-
}
|
|
579
|
-
// ---------------------------------------------------------------------------
|
|
580
|
-
// Command Execution
|
|
581
|
-
// ---------------------------------------------------------------------------
|
|
582
|
-
/**
|
|
583
|
-
* Execute a command in the sandbox and return the result.
|
|
584
|
-
*/
|
|
585
|
-
async executeCommand(command, args = [], options = {}) {
|
|
586
|
-
return this.withRestartRetry(async () => {
|
|
587
|
-
const fullCommand = args.length > 0 ? `${command} ${args.map(shellQuote).join(" ")}` : command;
|
|
588
|
-
const timeout = options.timeout ?? this._timeout;
|
|
589
|
-
const env = options.env ? Object.fromEntries(
|
|
590
|
-
Object.entries(options.env).filter((entry) => entry[1] !== void 0)
|
|
591
|
-
) : void 0;
|
|
592
|
-
const startedAt = Date.now();
|
|
593
|
-
const result = await this.railway.exec(fullCommand, {
|
|
594
|
-
...timeout !== void 0 && { timeoutSec: Math.ceil(timeout / 1e3) },
|
|
595
|
-
...options.cwd !== void 0 && { cwd: options.cwd },
|
|
596
|
-
...env !== void 0 && { env }
|
|
597
|
-
});
|
|
598
|
-
const exitCode = result.exitCode ?? -1;
|
|
599
|
-
return {
|
|
600
|
-
success: exitCode === 0,
|
|
601
|
-
exitCode,
|
|
602
|
-
stdout: result.stdout,
|
|
603
|
-
stderr: result.stderr,
|
|
604
|
-
executionTimeMs: Date.now() - startedAt,
|
|
605
|
-
command,
|
|
606
|
-
args,
|
|
607
|
-
timedOut: result.timedOut
|
|
608
|
-
};
|
|
609
|
-
});
|
|
610
|
-
}
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/sandbox/index.ts
|
|
147
|
+
/**
|
|
148
|
+
* Safety margin subtracted from the sandbox's idle timeout when scheduling the
|
|
149
|
+
* pre-reap checkpoint refresh. Sized to comfortably exceed Cloud Run cold-start
|
|
150
|
+
* / recycle windows so a scale event during the refresh doesn't cause the timer
|
|
151
|
+
* to lose the race with Railway's idle destroy. If a caller reduces the idle
|
|
152
|
+
* timeout below this margin, the refresh falls back to the 1-second floor and
|
|
153
|
+
* fires almost immediately after start — surfacing the misconfiguration rather
|
|
154
|
+
* than silently skipping the refresh.
|
|
155
|
+
*/
|
|
156
|
+
const CHECKPOINT_REFRESH_MARGIN_MS = 18e4;
|
|
157
|
+
/**
|
|
158
|
+
* Railway sandbox provider for Mastra workspaces.
|
|
159
|
+
*
|
|
160
|
+
* Features:
|
|
161
|
+
* - Ephemeral, isolated Linux VM via the Railway TypeScript SDK
|
|
162
|
+
* - Command execution with streaming output and timeouts
|
|
163
|
+
* - Configurable idle timeout and network isolation
|
|
164
|
+
* - Reattach to an existing sandbox by Railway ID
|
|
165
|
+
*
|
|
166
|
+
* @example Basic usage
|
|
167
|
+
* ```typescript
|
|
168
|
+
* import { Workspace } from '@mastra/core/workspace';
|
|
169
|
+
* import { RailwaySandbox } from '@mastra/railway';
|
|
170
|
+
*
|
|
171
|
+
* const sandbox = new RailwaySandbox({
|
|
172
|
+
* // token + environmentId read from RAILWAY_API_TOKEN / RAILWAY_ENVIRONMENT_ID
|
|
173
|
+
* idleTimeoutMinutes: 30,
|
|
174
|
+
* });
|
|
175
|
+
*
|
|
176
|
+
* const workspace = new Workspace({ sandbox });
|
|
177
|
+
* const result = await workspace.executeCode('console.log("Hello!")');
|
|
178
|
+
* ```
|
|
179
|
+
*
|
|
180
|
+
* @example Private networking
|
|
181
|
+
* ```typescript
|
|
182
|
+
* const sandbox = new RailwaySandbox({
|
|
183
|
+
* networkIsolation: 'PRIVATE',
|
|
184
|
+
* env: { NODE_ENV: 'production' },
|
|
185
|
+
* });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
var RailwaySandbox = class RailwaySandbox extends _mastra_core_workspace.MastraSandbox {
|
|
189
|
+
id;
|
|
190
|
+
name = "RailwaySandbox";
|
|
191
|
+
provider = "railway";
|
|
192
|
+
status = "pending";
|
|
193
|
+
_sandbox = null;
|
|
194
|
+
_createdAt = null;
|
|
195
|
+
_checkpointRefreshTimer = null;
|
|
196
|
+
_checkpointRefreshInFlight = null;
|
|
197
|
+
_sandboxId;
|
|
198
|
+
_startInFlight = null;
|
|
199
|
+
_token;
|
|
200
|
+
_environmentId;
|
|
201
|
+
_checkpointName;
|
|
202
|
+
_idleTimeoutMinutes;
|
|
203
|
+
_networkIsolation;
|
|
204
|
+
_env;
|
|
205
|
+
_timeout;
|
|
206
|
+
_instructionsOverride;
|
|
207
|
+
_templateOption;
|
|
208
|
+
constructor(options = {}) {
|
|
209
|
+
super({
|
|
210
|
+
...options,
|
|
211
|
+
name: "RailwaySandbox",
|
|
212
|
+
processes: new RailwayProcessManager({ env: options.env })
|
|
213
|
+
});
|
|
214
|
+
this.id = options.id ?? this.generateId();
|
|
215
|
+
this._token = options.token ?? process.env.RAILWAY_API_TOKEN;
|
|
216
|
+
this._environmentId = options.environmentId ?? process.env.RAILWAY_ENVIRONMENT_ID;
|
|
217
|
+
this._sandboxId = options.sandboxId;
|
|
218
|
+
this._checkpointName = options.checkpointName;
|
|
219
|
+
this._idleTimeoutMinutes = options.idleTimeoutMinutes;
|
|
220
|
+
this._networkIsolation = options.networkIsolation;
|
|
221
|
+
this._env = options.env ?? {};
|
|
222
|
+
this._timeout = options.timeout;
|
|
223
|
+
this._instructionsOverride = options.instructions;
|
|
224
|
+
this._templateOption = options.template;
|
|
225
|
+
}
|
|
226
|
+
generateId() {
|
|
227
|
+
return `railway-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get the underlying Railway Sandbox instance for direct SDK access.
|
|
231
|
+
*
|
|
232
|
+
* @throws {SandboxNotReadyError} If the sandbox has not been started.
|
|
233
|
+
*/
|
|
234
|
+
get railway() {
|
|
235
|
+
if (!this._sandbox) throw new _mastra_core_workspace.SandboxNotReadyError(this.id);
|
|
236
|
+
return this._sandbox;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Start the Railway sandbox.
|
|
240
|
+
*
|
|
241
|
+
* Reattaches to an existing sandbox when `sandboxId` is configured,
|
|
242
|
+
* otherwise provisions a new one. Resolves once the sandbox is RUNNING.
|
|
243
|
+
*/
|
|
244
|
+
async start() {
|
|
245
|
+
if (this._sandbox) return;
|
|
246
|
+
const clientConfig = this._clientConfig();
|
|
247
|
+
const createOptions = this._createOptions(clientConfig);
|
|
248
|
+
if (this._sandboxId) {
|
|
249
|
+
const sandboxId = this._sandboxId;
|
|
250
|
+
this._startInFlight ??= (async () => {
|
|
251
|
+
try {
|
|
252
|
+
this._sandbox = await this._reconnectSandbox(sandboxId, clientConfig);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (!(error instanceof railway.SandboxNotFoundError)) throw error;
|
|
255
|
+
this._sandbox = await this._createNewSandbox(createOptions);
|
|
256
|
+
}
|
|
257
|
+
})().finally(() => {
|
|
258
|
+
this._startInFlight = null;
|
|
259
|
+
});
|
|
260
|
+
} else this._startInFlight ??= (async () => {
|
|
261
|
+
this._sandbox = await this._createNewSandbox(createOptions);
|
|
262
|
+
})().finally(() => {
|
|
263
|
+
this._startInFlight = null;
|
|
264
|
+
});
|
|
265
|
+
await this._startInFlight;
|
|
266
|
+
if (!this._sandbox) throw new Error("Failed to start Railway sandbox");
|
|
267
|
+
const sandbox = this._sandbox;
|
|
268
|
+
this._sandboxId = sandbox.id;
|
|
269
|
+
this._createdAt = sandbox.createdAt ? new Date(sandbox.createdAt) : /* @__PURE__ */ new Date();
|
|
270
|
+
this.logger.debug(`${LOG_PREFIX} Railway sandbox ${sandbox.id} ready for logical ID: ${this.id}`);
|
|
271
|
+
this._scheduleCheckpointRefresh();
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Create a new Railway sandbox.
|
|
275
|
+
*/
|
|
276
|
+
async _createNewSandbox(createOptions) {
|
|
277
|
+
this.logger.debug(`${LOG_PREFIX} Creating Railway sandbox for: ${this.id}`);
|
|
278
|
+
try {
|
|
279
|
+
let checkpoinAlreadyExists = false;
|
|
280
|
+
if (this._checkpointName) checkpoinAlreadyExists = (await railway.Sandbox.checkpoints(this._clientConfig())).some((checkpoint) => checkpoint.key === this._checkpointName);
|
|
281
|
+
let sandbox;
|
|
282
|
+
if (checkpoinAlreadyExists) sandbox = await railway.Sandbox.create(this._checkpointName, createOptions);
|
|
283
|
+
else sandbox = await railway.Sandbox.create(createOptions);
|
|
284
|
+
return sandbox;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Reconnect to an existing Railway sandbox, creating a fresh one when
|
|
291
|
+
*/
|
|
292
|
+
async _reconnectSandbox(reconnectSandboxId, clientConfig) {
|
|
293
|
+
this.logger.debug(`${LOG_PREFIX} Reconnecting to Railway sandbox ${reconnectSandboxId}...`);
|
|
294
|
+
let connectedSandbox = await railway.Sandbox.connect(reconnectSandboxId, clientConfig);
|
|
295
|
+
if (connectedSandbox.status !== "RUNNING") throw new railway.SandboxNotFoundError({
|
|
296
|
+
id: reconnectSandboxId,
|
|
297
|
+
environmentId: clientConfig.environmentId ?? ""
|
|
298
|
+
});
|
|
299
|
+
return connectedSandbox;
|
|
300
|
+
}
|
|
301
|
+
_clientConfig() {
|
|
302
|
+
return {
|
|
303
|
+
...this._token !== void 0 && { token: this._token },
|
|
304
|
+
...this._environmentId !== void 0 && { environmentId: this._environmentId }
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
_createOptions(clientConfig) {
|
|
308
|
+
return {
|
|
309
|
+
...clientConfig,
|
|
310
|
+
...this._idleTimeoutMinutes !== void 0 && { idleTimeoutMinutes: this._idleTimeoutMinutes },
|
|
311
|
+
...this._networkIsolation !== void 0 && { networkIsolation: this._networkIsolation },
|
|
312
|
+
...Object.keys(this._env).length > 0 && { env: this._env }
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Capture the sandbox's checkpoint on demand, outside the idle-timer schedule.
|
|
317
|
+
*
|
|
318
|
+
* Intended for callers (e.g. a factory-side scheduler) that want to refresh
|
|
319
|
+
* the recovery checkpoint at semantic moments — turn end, session-idle,
|
|
320
|
+
* pre-teardown — rather than only just before Railway's idle destroy.
|
|
321
|
+
*
|
|
322
|
+
* Coalesces with any in-flight timer-driven refresh: concurrent callers join
|
|
323
|
+
* the same underlying `Sandbox.checkpoint` call and receive
|
|
324
|
+
* `{ status: 'coalesced', checkpointName }`. Both `captured` and `coalesced`
|
|
325
|
+
* carry the checkpoint name inline so callers can persist a session→
|
|
326
|
+
* checkpoint binding without a second, non-atomic read against the sandbox.
|
|
327
|
+
* Returns `{ status: 'skipped', reason }` when there's nothing to capture
|
|
328
|
+
* (no `checkpointName` configured, or the sandbox isn't running yet).
|
|
329
|
+
*
|
|
330
|
+
* On successful capture, restarts the idle-timer countdown so the next
|
|
331
|
+
* timer-driven refresh is scheduled from this capture.
|
|
332
|
+
*
|
|
333
|
+
* Never captures without a `checkpointName` and never mutates status — safe
|
|
334
|
+
* to invoke concurrently with `executeCommand`, `restart`, or `stop`.
|
|
335
|
+
*/
|
|
336
|
+
async captureCheckpoint() {
|
|
337
|
+
const checkpointName = this._checkpointName;
|
|
338
|
+
if (!checkpointName) return {
|
|
339
|
+
status: "skipped",
|
|
340
|
+
reason: "no-checkpoint-name-configured"
|
|
341
|
+
};
|
|
342
|
+
const sandbox = this._sandbox;
|
|
343
|
+
if (!sandbox) return {
|
|
344
|
+
status: "skipped",
|
|
345
|
+
reason: "sandbox-not-running"
|
|
346
|
+
};
|
|
347
|
+
if (this._checkpointRefreshInFlight) {
|
|
348
|
+
await this._checkpointRefreshInFlight;
|
|
349
|
+
return {
|
|
350
|
+
status: "coalesced",
|
|
351
|
+
checkpointName
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const capture = this._checkpointSandbox(sandbox).finally(() => {
|
|
355
|
+
if (this._checkpointRefreshInFlight === capture) this._checkpointRefreshInFlight = null;
|
|
356
|
+
});
|
|
357
|
+
this._checkpointRefreshInFlight = capture;
|
|
358
|
+
await capture;
|
|
359
|
+
this._scheduleCheckpointRefresh();
|
|
360
|
+
return {
|
|
361
|
+
status: "captured",
|
|
362
|
+
checkpointName
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
async _checkpointSandbox(sandbox) {
|
|
366
|
+
if (!this._checkpointName) return;
|
|
367
|
+
this.logger.debug(`${LOG_PREFIX} Capturing Railway sandbox checkpoint ${this._checkpointName} for: ${this.id}`);
|
|
368
|
+
if ((await railway.Sandbox.checkpoints(this._clientConfig())).some((checkpoint) => checkpoint.key === this._checkpointName)) await railway.Sandbox.deleteCheckpoint(this._checkpointName, this._clientConfig());
|
|
369
|
+
await sandbox.checkpoint(this._checkpointName);
|
|
370
|
+
}
|
|
371
|
+
_scheduleCheckpointRefresh() {
|
|
372
|
+
if (!this._checkpointName || !this._sandbox) return;
|
|
373
|
+
const idleTimeoutMinutes = this._idleTimeoutMinutes ?? this._sandbox.idleTimeoutMinutes;
|
|
374
|
+
if (!idleTimeoutMinutes) return;
|
|
375
|
+
this._cancelCheckpointRefresh();
|
|
376
|
+
const delayMs = Math.max(1e3, idleTimeoutMinutes * 6e4 - CHECKPOINT_REFRESH_MARGIN_MS);
|
|
377
|
+
this._checkpointRefreshTimer = setTimeout(() => {
|
|
378
|
+
this._checkpointRefreshTimer = null;
|
|
379
|
+
const sandbox = this._sandbox;
|
|
380
|
+
if (!sandbox || this._checkpointRefreshInFlight) return;
|
|
381
|
+
const refresh = this._checkpointSandbox(sandbox).finally(() => {
|
|
382
|
+
if (this._checkpointRefreshInFlight === refresh) this._checkpointRefreshInFlight = null;
|
|
383
|
+
});
|
|
384
|
+
this._checkpointRefreshInFlight = refresh;
|
|
385
|
+
refresh.catch((error) => {
|
|
386
|
+
this.logger.warn(`${LOG_PREFIX} Failed to refresh Railway sandbox checkpoint ${this._checkpointName}:`, error);
|
|
387
|
+
});
|
|
388
|
+
}, delayMs);
|
|
389
|
+
this._checkpointRefreshTimer.unref?.();
|
|
390
|
+
}
|
|
391
|
+
_cancelCheckpointRefresh() {
|
|
392
|
+
if (this._checkpointRefreshTimer) {
|
|
393
|
+
clearTimeout(this._checkpointRefreshTimer);
|
|
394
|
+
this._checkpointRefreshTimer = null;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Stop the Railway sandbox.
|
|
399
|
+
*
|
|
400
|
+
* Railway sandboxes have no separate "stopped" state — they're either
|
|
401
|
+
* running or destroyed — so stopping destroys the sandbox but we keep a checkpoint of the filesystem.
|
|
402
|
+
*/
|
|
403
|
+
async stop() {
|
|
404
|
+
if (this._checkpointName) await this._flushCheckpointRefresh().catch((error) => {
|
|
405
|
+
this.logger.warn(`${LOG_PREFIX} Failed to checkpoint Railway sandbox ${this._sandbox?.id}:`, error);
|
|
406
|
+
});
|
|
407
|
+
await this._teardown();
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Destroy the Railway sandbox and release its resources including the checkpoint.
|
|
411
|
+
*/
|
|
412
|
+
async destroy() {
|
|
413
|
+
this._cancelCheckpointRefresh();
|
|
414
|
+
if (this._checkpointName) {
|
|
415
|
+
await this._checkpointRefreshInFlight;
|
|
416
|
+
await railway.Sandbox.deleteCheckpoint(this._checkpointName, this._clientConfig()).catch((error) => {
|
|
417
|
+
this.logger.warn(`${LOG_PREFIX} Failed to delete Railway checkpoint ${this._checkpointName}:`, error);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
await this._teardown();
|
|
421
|
+
}
|
|
422
|
+
async _flushCheckpointRefresh() {
|
|
423
|
+
this._cancelCheckpointRefresh();
|
|
424
|
+
if (this._checkpointRefreshInFlight) {
|
|
425
|
+
await this._checkpointRefreshInFlight;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (this._sandbox) await this._checkpointSandbox(this._sandbox);
|
|
429
|
+
}
|
|
430
|
+
async _teardown() {
|
|
431
|
+
this._cancelCheckpointRefresh();
|
|
432
|
+
if (!this._sandbox) return;
|
|
433
|
+
await this._checkpointRefreshInFlight;
|
|
434
|
+
const sandbox = this._sandbox;
|
|
435
|
+
this._sandbox = null;
|
|
436
|
+
try {
|
|
437
|
+
await sandbox.destroy();
|
|
438
|
+
} catch (error) {
|
|
439
|
+
this.logger.warn(`${LOG_PREFIX} Failed to destroy Railway sandbox ${sandbox.id}:`, error);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Fork this running sandbox into a new, independent `RailwaySandbox`.
|
|
444
|
+
*
|
|
445
|
+
* Clones the filesystem (a fresh boot, not live processes) into the same
|
|
446
|
+
* environment. The returned sandbox is already started and reattached to the
|
|
447
|
+
* forked Railway sandbox; it inherits this sandbox's credentials and defaults
|
|
448
|
+
* unless overridden via `options`.
|
|
449
|
+
*
|
|
450
|
+
* @throws {SandboxNotReadyError} If this sandbox has not been started.
|
|
451
|
+
*/
|
|
452
|
+
async fork(options = {}) {
|
|
453
|
+
await this.railway.fork({
|
|
454
|
+
...options.idleTimeoutMinutes !== void 0 && { idleTimeoutMinutes: options.idleTimeoutMinutes },
|
|
455
|
+
...options.networkIsolation !== void 0 && { networkIsolation: options.networkIsolation },
|
|
456
|
+
...options.env !== void 0 && { env: options.env }
|
|
457
|
+
});
|
|
458
|
+
const child = new RailwaySandbox({
|
|
459
|
+
...options.id !== void 0 && { id: options.id },
|
|
460
|
+
...this._token !== void 0 && { token: this._token },
|
|
461
|
+
...this._environmentId !== void 0 && { environmentId: this._environmentId },
|
|
462
|
+
idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
|
|
463
|
+
networkIsolation: options.networkIsolation ?? this._networkIsolation,
|
|
464
|
+
env: options.env ?? this._env,
|
|
465
|
+
timeout: this._timeout
|
|
466
|
+
});
|
|
467
|
+
await child._start();
|
|
468
|
+
return child;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Construct a sibling `RailwaySandbox` that inherits this sandbox's
|
|
472
|
+
* credentials and defaults (token, environment, checkpoint, network
|
|
473
|
+
* isolation, timeout, template, instructions) with per-instance overrides.
|
|
474
|
+
*
|
|
475
|
+
* Unlike {@link fork}, `clone` performs no I/O and does not require this
|
|
476
|
+
* sandbox to be started — the returned sandbox is not started and provisions
|
|
477
|
+
* (or reattaches, when `sandboxId` is set) on its own `start()`. Use it when
|
|
478
|
+
* one configured sandbox acts as the template for a fleet of independent
|
|
479
|
+
* sandboxes (e.g. one per project).
|
|
480
|
+
*/
|
|
481
|
+
clone(options = {}) {
|
|
482
|
+
return new RailwaySandbox({
|
|
483
|
+
...options.id !== void 0 && { id: options.id },
|
|
484
|
+
...this._token !== void 0 && { token: this._token },
|
|
485
|
+
...this._environmentId !== void 0 && { environmentId: this._environmentId },
|
|
486
|
+
...(options.checkpointName ?? this._checkpointName) !== void 0 && { checkpointName: options.checkpointName ?? this._checkpointName },
|
|
487
|
+
idleTimeoutMinutes: options.idleTimeoutMinutes ?? this._idleTimeoutMinutes,
|
|
488
|
+
...this._networkIsolation !== void 0 && { networkIsolation: this._networkIsolation },
|
|
489
|
+
env: options.env ?? this._env,
|
|
490
|
+
...this._templateOption !== void 0 && { template: this._templateOption },
|
|
491
|
+
...this._timeout !== void 0 && { timeout: this._timeout },
|
|
492
|
+
...this._instructionsOverride !== void 0 && { instructions: this._instructionsOverride }
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Whether a Railway API token was resolved at construction (explicit option
|
|
497
|
+
* or the `RAILWAY_API_TOKEN` env fallback). Lets callers gate features on a
|
|
498
|
+
* usable configuration without provisioning a sandbox.
|
|
499
|
+
*/
|
|
500
|
+
get hasCredentials() {
|
|
501
|
+
return this._token !== void 0 && this._token !== "";
|
|
502
|
+
}
|
|
503
|
+
/** The configured idle teardown window in minutes, if any. */
|
|
504
|
+
get idleTimeoutMinutes() {
|
|
505
|
+
return this._idleTimeoutMinutes;
|
|
506
|
+
}
|
|
507
|
+
async getInfo() {
|
|
508
|
+
return {
|
|
509
|
+
id: this.id,
|
|
510
|
+
name: this.name,
|
|
511
|
+
provider: this.provider,
|
|
512
|
+
status: this.status,
|
|
513
|
+
createdAt: this._createdAt ?? /* @__PURE__ */ new Date(),
|
|
514
|
+
metadata: { ...this._sandbox && {
|
|
515
|
+
railwaySandboxId: this._sandbox.id,
|
|
516
|
+
environmentId: this._sandbox.environmentId,
|
|
517
|
+
region: this._sandbox.region,
|
|
518
|
+
networkIsolation: this._sandbox.networkIsolation,
|
|
519
|
+
...this._sandbox.idleTimeoutMinutes != null && { idleTimeoutMinutes: this._sandbox.idleTimeoutMinutes }
|
|
520
|
+
} }
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
getInstructions() {
|
|
524
|
+
const defaultInstructions = this._buildDefaultInstructions();
|
|
525
|
+
if (typeof this._instructionsOverride === "string") return this._instructionsOverride;
|
|
526
|
+
if (typeof this._instructionsOverride === "function") return this._instructionsOverride({ defaultInstructions });
|
|
527
|
+
return defaultInstructions;
|
|
528
|
+
}
|
|
529
|
+
_buildDefaultInstructions() {
|
|
530
|
+
const parts = [];
|
|
531
|
+
parts.push("Railway cloud sandbox: an isolated Debian Linux VM with outbound internet access.");
|
|
532
|
+
if (this._networkIsolation === "PRIVATE") parts.push("Joined to the environment private network.");
|
|
533
|
+
if (this._timeout !== void 0) parts.push(`Default command timeout: ${Math.ceil(this._timeout / 1e3)}s.`);
|
|
534
|
+
else parts.push("Commands run until they exit unless a timeout is set.");
|
|
535
|
+
if (this._idleTimeoutMinutes !== void 0) parts.push(`Idle timeout: ${this._idleTimeoutMinutes} minute(s).`);
|
|
536
|
+
return parts.join(" ");
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Execute a command in the sandbox and return the result.
|
|
540
|
+
*/
|
|
541
|
+
async executeCommand(command, args = [], options = {}) {
|
|
542
|
+
if (this._sandbox?.status !== "RUNNING") {
|
|
543
|
+
if (!this._checkpointName) throw new _mastra_core_workspace.SandboxNotReadyError(this.id);
|
|
544
|
+
this._sandbox = null;
|
|
545
|
+
await this.start();
|
|
546
|
+
}
|
|
547
|
+
const fullCommand = args.length > 0 ? `${command} ${args.map(shellQuote).join(" ")}` : command;
|
|
548
|
+
const timeout = options.timeout ?? this._timeout;
|
|
549
|
+
const env = options.env ? Object.fromEntries(Object.entries(options.env).filter((entry) => entry[1] !== void 0)) : void 0;
|
|
550
|
+
const startedAt = Date.now();
|
|
551
|
+
const result = await this.railway.exec(fullCommand, {
|
|
552
|
+
...timeout !== void 0 && { timeoutSec: Math.ceil(timeout / 1e3) },
|
|
553
|
+
...options.cwd !== void 0 && { cwd: options.cwd },
|
|
554
|
+
...env !== void 0 && { env }
|
|
555
|
+
});
|
|
556
|
+
const exitCode = result.exitCode ?? -1;
|
|
557
|
+
return {
|
|
558
|
+
success: exitCode === 0,
|
|
559
|
+
exitCode,
|
|
560
|
+
stdout: result.stdout,
|
|
561
|
+
stderr: result.stderr,
|
|
562
|
+
executionTimeMs: Date.now() - startedAt,
|
|
563
|
+
command,
|
|
564
|
+
args,
|
|
565
|
+
timedOut: result.timedOut
|
|
566
|
+
};
|
|
567
|
+
}
|
|
611
568
|
};
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region src/provider.ts
|
|
571
|
+
const railwaySandboxProvider = {
|
|
572
|
+
id: "railway",
|
|
573
|
+
name: "Railway Sandbox",
|
|
574
|
+
description: "Ephemeral, isolated Linux VM powered by Railway",
|
|
575
|
+
configSchema: {
|
|
576
|
+
type: "object",
|
|
577
|
+
properties: {
|
|
578
|
+
token: {
|
|
579
|
+
type: "string",
|
|
580
|
+
description: "Railway API token (falls back to RAILWAY_API_TOKEN)"
|
|
581
|
+
},
|
|
582
|
+
environmentId: {
|
|
583
|
+
type: "string",
|
|
584
|
+
description: "Railway environment ID (falls back to RAILWAY_ENVIRONMENT_ID)"
|
|
585
|
+
},
|
|
586
|
+
sandboxId: {
|
|
587
|
+
type: "string",
|
|
588
|
+
description: "Reattach to an existing Railway sandbox by ID"
|
|
589
|
+
},
|
|
590
|
+
idleTimeoutMinutes: {
|
|
591
|
+
type: "number",
|
|
592
|
+
description: "Minutes a sandbox can sit idle before Railway destroys it"
|
|
593
|
+
},
|
|
594
|
+
networkIsolation: {
|
|
595
|
+
type: "string",
|
|
596
|
+
description: "Network isolation mode",
|
|
597
|
+
enum: ["ISOLATED", "PRIVATE"],
|
|
598
|
+
default: "ISOLATED"
|
|
599
|
+
},
|
|
600
|
+
env: {
|
|
601
|
+
type: "object",
|
|
602
|
+
description: "Environment variables",
|
|
603
|
+
additionalProperties: { type: "string" }
|
|
604
|
+
},
|
|
605
|
+
timeout: {
|
|
606
|
+
type: "number",
|
|
607
|
+
description: "Default command timeout in ms"
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
createSandbox: (config) => new RailwaySandbox(config)
|
|
646
612
|
};
|
|
647
|
-
|
|
648
|
-
Object.defineProperty(exports, "SandboxFileNotFoundError", {
|
|
649
|
-
enumerable: true,
|
|
650
|
-
get: function () { return railway.SandboxFileNotFoundError; }
|
|
651
|
-
});
|
|
613
|
+
//#endregion
|
|
652
614
|
exports.RailwayProcessManager = RailwayProcessManager;
|
|
653
615
|
exports.RailwaySandbox = RailwaySandbox;
|
|
616
|
+
Object.defineProperty(exports, "SandboxFileNotFoundError", {
|
|
617
|
+
enumerable: true,
|
|
618
|
+
get: function() {
|
|
619
|
+
return railway.SandboxFileNotFoundError;
|
|
620
|
+
}
|
|
621
|
+
});
|
|
654
622
|
exports.railwaySandboxProvider = railwaySandboxProvider;
|
|
655
|
-
|
|
623
|
+
|
|
656
624
|
//# sourceMappingURL=index.cjs.map
|