@mastra/daytona 0.0.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/LICENSE.md +15 -0
- package/README.md +168 -0
- package/dist/index.cjs +545 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +542 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +224 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/process-manager.d.ts +34 -0
- package/dist/sandbox/process-manager.d.ts.map +1 -0
- package/dist/sandbox/types.d.ts +15 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/utils/compact.d.ts +5 -0
- package/dist/utils/compact.d.ts.map +1 -0
- package/dist/utils/shell-quote.d.ts +8 -0
- package/dist/utils/shell-quote.d.ts.map +1 -0
- package/package.json +68 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var sdk = require('@daytonaio/sdk');
|
|
4
|
+
var workspace = require('@mastra/core/workspace');
|
|
5
|
+
|
|
6
|
+
// src/sandbox/index.ts
|
|
7
|
+
|
|
8
|
+
// src/utils/compact.ts
|
|
9
|
+
function compact(obj) {
|
|
10
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/utils/shell-quote.ts
|
|
14
|
+
function shellQuote(arg) {
|
|
15
|
+
if (/^[a-zA-Z0-9._\-/@:=]+$/.test(arg)) return arg;
|
|
16
|
+
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/sandbox/process-manager.ts
|
|
20
|
+
var DaytonaProcessHandle = class extends workspace.ProcessHandle {
|
|
21
|
+
pid;
|
|
22
|
+
_sessionId;
|
|
23
|
+
_cmdId;
|
|
24
|
+
_sandbox;
|
|
25
|
+
_startTime;
|
|
26
|
+
_timeout;
|
|
27
|
+
_exitCode;
|
|
28
|
+
_waitPromise = null;
|
|
29
|
+
_streamingPromise = null;
|
|
30
|
+
_killed = false;
|
|
31
|
+
constructor(pid, sessionId, cmdId, sandbox, startTime, options) {
|
|
32
|
+
super(options);
|
|
33
|
+
this.pid = pid;
|
|
34
|
+
this._sessionId = sessionId;
|
|
35
|
+
this._cmdId = cmdId;
|
|
36
|
+
this._sandbox = sandbox;
|
|
37
|
+
this._startTime = startTime;
|
|
38
|
+
this._timeout = options?.timeout;
|
|
39
|
+
}
|
|
40
|
+
get exitCode() {
|
|
41
|
+
return this._exitCode;
|
|
42
|
+
}
|
|
43
|
+
/** @internal Set by the process manager after streaming starts. */
|
|
44
|
+
set streamingPromise(p) {
|
|
45
|
+
this._streamingPromise = p;
|
|
46
|
+
p.then(() => this._resolveExitCode()).catch(() => this._resolveExitCode());
|
|
47
|
+
}
|
|
48
|
+
/** Fetch the exit code from Daytona and set _exitCode. No-op if already set. */
|
|
49
|
+
async _resolveExitCode() {
|
|
50
|
+
if (this._exitCode !== void 0) return;
|
|
51
|
+
try {
|
|
52
|
+
const cmd = await this._sandbox.process.getSessionCommand(this._sessionId, this._cmdId);
|
|
53
|
+
this._exitCode = cmd.exitCode ?? 0;
|
|
54
|
+
} catch {
|
|
55
|
+
if (this._exitCode === void 0) {
|
|
56
|
+
this._exitCode = 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async wait() {
|
|
61
|
+
if (!this._waitPromise) {
|
|
62
|
+
this._waitPromise = this._doWait();
|
|
63
|
+
}
|
|
64
|
+
return this._waitPromise;
|
|
65
|
+
}
|
|
66
|
+
async _doWait() {
|
|
67
|
+
const streamDone = this._streamingPromise ?? Promise.resolve();
|
|
68
|
+
if (this._timeout) {
|
|
69
|
+
let timeoutId;
|
|
70
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
71
|
+
timeoutId = setTimeout(() => reject(new Error(`Command timed out after ${this._timeout}ms`)), this._timeout);
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
await Promise.race([streamDone, timeoutPromise]);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error instanceof Error && error.message.includes("timed out")) {
|
|
77
|
+
await this.kill();
|
|
78
|
+
this._exitCode = 124;
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
exitCode: 124,
|
|
82
|
+
stdout: this.stdout,
|
|
83
|
+
stderr: this.stderr || error.message,
|
|
84
|
+
executionTimeMs: Date.now() - this._startTime
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
} finally {
|
|
89
|
+
clearTimeout(timeoutId);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
await streamDone.catch(() => {
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (this._killed) {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
exitCode: this._exitCode ?? 137,
|
|
99
|
+
stdout: this.stdout,
|
|
100
|
+
stderr: this.stderr,
|
|
101
|
+
executionTimeMs: Date.now() - this._startTime
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
await this._resolveExitCode();
|
|
105
|
+
return {
|
|
106
|
+
success: this._exitCode === 0,
|
|
107
|
+
exitCode: this._exitCode ?? 1,
|
|
108
|
+
stdout: this.stdout,
|
|
109
|
+
stderr: this.stderr,
|
|
110
|
+
executionTimeMs: Date.now() - this._startTime
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async kill() {
|
|
114
|
+
if (this._exitCode !== void 0) return false;
|
|
115
|
+
this._killed = true;
|
|
116
|
+
this._exitCode = 137;
|
|
117
|
+
try {
|
|
118
|
+
await this._sandbox.process.deleteSession(this._sessionId);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
async sendStdin(data) {
|
|
124
|
+
if (this._exitCode !== void 0) {
|
|
125
|
+
throw new Error(`Process ${this.pid} has already exited with code ${this._exitCode}`);
|
|
126
|
+
}
|
|
127
|
+
await this._sandbox.process.sendSessionCommandInput(this._sessionId, this._cmdId, data);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
var DaytonaProcessManager = class extends workspace.SandboxProcessManager {
|
|
131
|
+
_nextPid = 1;
|
|
132
|
+
_defaultTimeout;
|
|
133
|
+
constructor(opts = {}) {
|
|
134
|
+
super({ env: opts.env });
|
|
135
|
+
this._defaultTimeout = opts.defaultTimeout;
|
|
136
|
+
}
|
|
137
|
+
async spawn(command, options = {}) {
|
|
138
|
+
const effectiveOptions = {
|
|
139
|
+
...options,
|
|
140
|
+
timeout: options.timeout ?? this._defaultTimeout
|
|
141
|
+
};
|
|
142
|
+
return this.sandbox.retryOnDead(async () => {
|
|
143
|
+
const sandbox = this.sandbox.instance;
|
|
144
|
+
const pid = this._nextPid++;
|
|
145
|
+
const mergedEnv = { ...this.env, ...effectiveOptions.env };
|
|
146
|
+
const envs = Object.fromEntries(
|
|
147
|
+
Object.entries(mergedEnv).filter((entry) => entry[1] !== void 0)
|
|
148
|
+
);
|
|
149
|
+
const sessionCommand = buildSpawnCommand(command, effectiveOptions.cwd, envs);
|
|
150
|
+
const sessionId = `mastra-proc-${Date.now().toString(36)}-${pid}`;
|
|
151
|
+
await sandbox.process.createSession(sessionId);
|
|
152
|
+
const { cmdId } = await sandbox.process.executeSessionCommand(sessionId, {
|
|
153
|
+
command: sessionCommand,
|
|
154
|
+
runAsync: true
|
|
155
|
+
});
|
|
156
|
+
const handle = new DaytonaProcessHandle(pid, sessionId, cmdId, sandbox, Date.now(), effectiveOptions);
|
|
157
|
+
const streamingPromise = sandbox.process.getSessionCommandLogs(
|
|
158
|
+
sessionId,
|
|
159
|
+
cmdId,
|
|
160
|
+
(chunk) => handle.emitStdout(chunk),
|
|
161
|
+
(chunk) => handle.emitStderr(chunk)
|
|
162
|
+
).catch(() => {
|
|
163
|
+
});
|
|
164
|
+
handle.streamingPromise = streamingPromise;
|
|
165
|
+
this._tracked.set(pid, handle);
|
|
166
|
+
return handle;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async list() {
|
|
170
|
+
const result = [];
|
|
171
|
+
for (const [pid, handle] of this._tracked) {
|
|
172
|
+
result.push({
|
|
173
|
+
pid,
|
|
174
|
+
command: handle.command,
|
|
175
|
+
running: handle.exitCode === void 0,
|
|
176
|
+
exitCode: handle.exitCode
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
async get(pid) {
|
|
182
|
+
return this._tracked.get(pid);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
function buildSpawnCommand(command, cwd, envs) {
|
|
186
|
+
const parts = [];
|
|
187
|
+
for (const [k, v] of Object.entries(envs)) {
|
|
188
|
+
parts.push(`export ${k}=${shellQuote(v)}`);
|
|
189
|
+
}
|
|
190
|
+
if (cwd) {
|
|
191
|
+
parts.push(`cd ${shellQuote(cwd)}`);
|
|
192
|
+
}
|
|
193
|
+
parts.push(`(${command})`);
|
|
194
|
+
return parts.join(" && ");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/sandbox/index.ts
|
|
198
|
+
var LOG_PREFIX = "[@mastra/daytona]";
|
|
199
|
+
var SANDBOX_DEAD_PATTERNS = [
|
|
200
|
+
/sandbox is not running/i,
|
|
201
|
+
/sandbox already destroyed/i,
|
|
202
|
+
/sandbox.*not found/i
|
|
203
|
+
];
|
|
204
|
+
var DaytonaSandbox = class extends workspace.MastraSandbox {
|
|
205
|
+
id;
|
|
206
|
+
name = "DaytonaSandbox";
|
|
207
|
+
provider = "daytona";
|
|
208
|
+
status = "pending";
|
|
209
|
+
_daytona = null;
|
|
210
|
+
_sandbox = null;
|
|
211
|
+
_createdAt = null;
|
|
212
|
+
_isRetrying = false;
|
|
213
|
+
_workingDir = null;
|
|
214
|
+
timeout;
|
|
215
|
+
language;
|
|
216
|
+
resources;
|
|
217
|
+
env;
|
|
218
|
+
labels;
|
|
219
|
+
snapshotId;
|
|
220
|
+
image;
|
|
221
|
+
ephemeral;
|
|
222
|
+
autoStopInterval;
|
|
223
|
+
autoArchiveInterval;
|
|
224
|
+
autoDeleteInterval;
|
|
225
|
+
volumeConfigs;
|
|
226
|
+
sandboxName;
|
|
227
|
+
sandboxUser;
|
|
228
|
+
sandboxPublic;
|
|
229
|
+
networkBlockAll;
|
|
230
|
+
networkAllowList;
|
|
231
|
+
connectionOpts;
|
|
232
|
+
constructor(options = {}) {
|
|
233
|
+
super({
|
|
234
|
+
...options,
|
|
235
|
+
name: "DaytonaSandbox",
|
|
236
|
+
processes: new DaytonaProcessManager({
|
|
237
|
+
env: options.env,
|
|
238
|
+
defaultTimeout: options.timeout ?? 3e5
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
this.id = options.id ?? this.generateId();
|
|
242
|
+
this.timeout = options.timeout ?? 3e5;
|
|
243
|
+
this.language = options.language ?? "typescript";
|
|
244
|
+
this.resources = options.resources;
|
|
245
|
+
this.env = options.env ?? {};
|
|
246
|
+
this.labels = options.labels ?? {};
|
|
247
|
+
this.snapshotId = options.snapshot;
|
|
248
|
+
this.image = options.image;
|
|
249
|
+
this.ephemeral = options.ephemeral ?? false;
|
|
250
|
+
this.autoStopInterval = options.autoStopInterval ?? 15;
|
|
251
|
+
this.autoArchiveInterval = options.autoArchiveInterval;
|
|
252
|
+
this.autoDeleteInterval = options.autoDeleteInterval;
|
|
253
|
+
this.volumeConfigs = options.volumes ?? [];
|
|
254
|
+
this.sandboxName = options.name ?? this.id;
|
|
255
|
+
this.sandboxUser = options.user;
|
|
256
|
+
this.sandboxPublic = options.public;
|
|
257
|
+
this.networkBlockAll = options.networkBlockAll;
|
|
258
|
+
this.networkAllowList = options.networkAllowList;
|
|
259
|
+
this.connectionOpts = {
|
|
260
|
+
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
261
|
+
...options.apiUrl !== void 0 && { apiUrl: options.apiUrl },
|
|
262
|
+
...options.target !== void 0 && { target: options.target }
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
generateId() {
|
|
266
|
+
return `daytona-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get the underlying Daytona Sandbox instance for direct access to Daytona APIs.
|
|
270
|
+
*
|
|
271
|
+
* Use this when you need to access Daytona features not exposed through the
|
|
272
|
+
* WorkspaceSandbox interface (e.g., filesystem API, git operations, LSP).
|
|
273
|
+
*
|
|
274
|
+
* @throws {SandboxNotReadyError} If the sandbox has not been started
|
|
275
|
+
*
|
|
276
|
+
* @example Direct file operations
|
|
277
|
+
* ```typescript
|
|
278
|
+
* const daytonaSandbox = sandbox.instance;
|
|
279
|
+
* await daytonaSandbox.fs.uploadFile(Buffer.from('Hello'), '/tmp/test.txt');
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
get instance() {
|
|
283
|
+
if (!this._sandbox) {
|
|
284
|
+
throw new workspace.SandboxNotReadyError(this.id);
|
|
285
|
+
}
|
|
286
|
+
return this._sandbox;
|
|
287
|
+
}
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Lifecycle
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
/**
|
|
292
|
+
* Start the Daytona sandbox.
|
|
293
|
+
* Reconnects to an existing sandbox with the same logical ID if one exists,
|
|
294
|
+
* otherwise creates a new sandbox instance.
|
|
295
|
+
*/
|
|
296
|
+
async start() {
|
|
297
|
+
if (this._sandbox) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!this._daytona) {
|
|
301
|
+
this._daytona = new sdk.Daytona(this.connectionOpts);
|
|
302
|
+
}
|
|
303
|
+
const existing = await this.findExistingSandbox();
|
|
304
|
+
if (existing) {
|
|
305
|
+
this._sandbox = existing;
|
|
306
|
+
this._createdAt = existing.createdAt ? new Date(existing.createdAt) : /* @__PURE__ */ new Date();
|
|
307
|
+
this.logger.debug(`${LOG_PREFIX} Reconnected to existing sandbox ${existing.id} for: ${this.id}`);
|
|
308
|
+
await this.detectWorkingDir();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
this.logger.debug(`${LOG_PREFIX} Creating sandbox for: ${this.id}`);
|
|
312
|
+
const baseParams = compact({
|
|
313
|
+
language: this.language,
|
|
314
|
+
envVars: this.env,
|
|
315
|
+
labels: { ...this.labels, "mastra-sandbox-id": this.id },
|
|
316
|
+
ephemeral: this.ephemeral,
|
|
317
|
+
autoStopInterval: this.autoStopInterval,
|
|
318
|
+
autoArchiveInterval: this.autoArchiveInterval,
|
|
319
|
+
autoDeleteInterval: this.autoDeleteInterval,
|
|
320
|
+
volumes: this.volumeConfigs.length > 0 ? this.volumeConfigs : void 0,
|
|
321
|
+
name: this.sandboxName,
|
|
322
|
+
user: this.sandboxUser,
|
|
323
|
+
public: this.sandboxPublic,
|
|
324
|
+
networkBlockAll: this.networkBlockAll,
|
|
325
|
+
networkAllowList: this.networkAllowList
|
|
326
|
+
});
|
|
327
|
+
if (this.resources && !this.image) {
|
|
328
|
+
this.logger.warn(
|
|
329
|
+
`${LOG_PREFIX} 'resources' option requires 'image' to take effect \u2014 falling back to snapshot-based creation without custom resources`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
const createParams = this.image && !this.snapshotId ? compact({
|
|
333
|
+
...baseParams,
|
|
334
|
+
image: this.image,
|
|
335
|
+
resources: this.resources
|
|
336
|
+
}) : compact({ ...baseParams, snapshot: this.snapshotId });
|
|
337
|
+
this._sandbox = await this._daytona.create(createParams);
|
|
338
|
+
this.logger.debug(`${LOG_PREFIX} Created sandbox ${this._sandbox.id} for logical ID: ${this.id}`);
|
|
339
|
+
this._createdAt = /* @__PURE__ */ new Date();
|
|
340
|
+
await this.detectWorkingDir();
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Stop the Daytona sandbox.
|
|
344
|
+
* Stops the sandbox instance and releases the reference.
|
|
345
|
+
*/
|
|
346
|
+
async stop() {
|
|
347
|
+
if (this._sandbox && this._daytona) {
|
|
348
|
+
try {
|
|
349
|
+
await this._daytona.stop(this._sandbox);
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
this._sandbox = null;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Destroy the Daytona sandbox and clean up all resources.
|
|
357
|
+
* Deletes the sandbox and clears all state.
|
|
358
|
+
*/
|
|
359
|
+
async destroy() {
|
|
360
|
+
if (this._sandbox && this._daytona) {
|
|
361
|
+
try {
|
|
362
|
+
await this._daytona.delete(this._sandbox);
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
} else if (!this._sandbox && this._daytona) {
|
|
366
|
+
try {
|
|
367
|
+
const orphan = await this._daytona.findOne({ labels: { "mastra-sandbox-id": this.id } });
|
|
368
|
+
if (orphan) {
|
|
369
|
+
await this._daytona.delete(orphan);
|
|
370
|
+
}
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
this._sandbox = null;
|
|
375
|
+
this._daytona = null;
|
|
376
|
+
this.mounts?.clear();
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Check if the sandbox is ready for operations.
|
|
380
|
+
*/
|
|
381
|
+
async isReady() {
|
|
382
|
+
return this.status === "running" && this._sandbox !== null;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get information about the current state of the sandbox.
|
|
386
|
+
*/
|
|
387
|
+
async getInfo() {
|
|
388
|
+
return {
|
|
389
|
+
id: this.id,
|
|
390
|
+
name: this.name,
|
|
391
|
+
provider: this.provider,
|
|
392
|
+
status: this.status,
|
|
393
|
+
createdAt: this._createdAt ?? /* @__PURE__ */ new Date(),
|
|
394
|
+
mounts: this.mounts ? Array.from(this.mounts.entries).map(([path, entry]) => ({
|
|
395
|
+
path,
|
|
396
|
+
filesystem: entry.filesystem?.provider ?? entry.config?.type ?? "unknown"
|
|
397
|
+
})) : [],
|
|
398
|
+
...this._sandbox && {
|
|
399
|
+
resources: {
|
|
400
|
+
cpuCores: this._sandbox.cpu,
|
|
401
|
+
memoryMB: this._sandbox.memory * 1024,
|
|
402
|
+
diskMB: this._sandbox.disk * 1024
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
metadata: {
|
|
406
|
+
language: this.language,
|
|
407
|
+
ephemeral: this.ephemeral,
|
|
408
|
+
...this.snapshotId && { snapshot: this.snapshotId },
|
|
409
|
+
...this.image && { image: this.image },
|
|
410
|
+
...this._sandbox && { target: this._sandbox.target }
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get instructions describing this Daytona sandbox.
|
|
416
|
+
* Used by agents to understand the execution environment.
|
|
417
|
+
*/
|
|
418
|
+
getInstructions() {
|
|
419
|
+
const parts = [];
|
|
420
|
+
parts.push(`Cloud sandbox with isolated execution (${this.language} runtime).`);
|
|
421
|
+
if (this._workingDir) {
|
|
422
|
+
parts.push(`Default working directory: ${this._workingDir}.`);
|
|
423
|
+
}
|
|
424
|
+
parts.push(`Command timeout: ${Math.ceil(this.timeout / 1e3)}s.`);
|
|
425
|
+
parts.push(`Running as user: ${this.sandboxUser ?? "daytona"}.`);
|
|
426
|
+
if (this.volumeConfigs.length > 0) {
|
|
427
|
+
parts.push(`${this.volumeConfigs.length} volume(s) attached.`);
|
|
428
|
+
}
|
|
429
|
+
if (this.networkBlockAll) {
|
|
430
|
+
parts.push(`Network access is blocked.`);
|
|
431
|
+
}
|
|
432
|
+
return parts.join(" ");
|
|
433
|
+
}
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
// Internal Helpers
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
/**
|
|
438
|
+
* Detect the actual working directory inside the sandbox via `pwd`.
|
|
439
|
+
* Stores the result for use in `getInstructions()`.
|
|
440
|
+
*/
|
|
441
|
+
async detectWorkingDir() {
|
|
442
|
+
if (!this._sandbox) return;
|
|
443
|
+
try {
|
|
444
|
+
const result = await this._sandbox.process.executeCommand("pwd");
|
|
445
|
+
const dir = result.result?.trim();
|
|
446
|
+
if (dir) {
|
|
447
|
+
this._workingDir = dir;
|
|
448
|
+
this.logger.debug(`${LOG_PREFIX} Detected working directory: ${dir}`);
|
|
449
|
+
}
|
|
450
|
+
} catch {
|
|
451
|
+
this.logger.debug(`${LOG_PREFIX} Could not detect working directory, will omit from instructions`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Try to find and reconnect to an existing Daytona sandbox with the same
|
|
456
|
+
* logical ID (via the mastra-sandbox-id label). Returns the sandbox if
|
|
457
|
+
* found and usable, or null if a fresh sandbox should be created.
|
|
458
|
+
*/
|
|
459
|
+
async findExistingSandbox() {
|
|
460
|
+
const DEAD_STATES = [
|
|
461
|
+
sdk.SandboxState.DESTROYED,
|
|
462
|
+
sdk.SandboxState.DESTROYING,
|
|
463
|
+
sdk.SandboxState.ERROR,
|
|
464
|
+
sdk.SandboxState.BUILD_FAILED
|
|
465
|
+
];
|
|
466
|
+
try {
|
|
467
|
+
const sandbox = await this._daytona.findOne({ labels: { "mastra-sandbox-id": this.id } });
|
|
468
|
+
const state = sandbox.state;
|
|
469
|
+
if (state && DEAD_STATES.includes(state)) {
|
|
470
|
+
this.logger.debug(
|
|
471
|
+
`${LOG_PREFIX} Existing sandbox ${sandbox.id} is dead (${state}), deleting and creating fresh`
|
|
472
|
+
);
|
|
473
|
+
try {
|
|
474
|
+
await this._daytona.delete(sandbox);
|
|
475
|
+
} catch {
|
|
476
|
+
}
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
if (state !== sdk.SandboxState.STARTED) {
|
|
480
|
+
this.logger.debug(`${LOG_PREFIX} Restarting sandbox ${sandbox.id} (state: ${state})`);
|
|
481
|
+
await this._daytona.start(sandbox);
|
|
482
|
+
}
|
|
483
|
+
return sandbox;
|
|
484
|
+
} catch {
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Check if an error indicates the sandbox is dead/gone.
|
|
490
|
+
* Uses DaytonaNotFoundError from the SDK when available,
|
|
491
|
+
* with string fallback for edge cases.
|
|
492
|
+
*
|
|
493
|
+
* String patterns observed in @daytonaio/sdk@0.143.0 error messages.
|
|
494
|
+
* Update if SDK error messages change in future versions.
|
|
495
|
+
*/
|
|
496
|
+
isSandboxDeadError(error) {
|
|
497
|
+
if (!error) return false;
|
|
498
|
+
if (error instanceof sdk.DaytonaNotFoundError) return true;
|
|
499
|
+
const errorStr = String(error);
|
|
500
|
+
return SANDBOX_DEAD_PATTERNS.some((pattern) => pattern.test(errorStr));
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Handle sandbox timeout by clearing the instance and resetting state.
|
|
504
|
+
*/
|
|
505
|
+
handleSandboxTimeout() {
|
|
506
|
+
this._sandbox = null;
|
|
507
|
+
if (this.mounts) {
|
|
508
|
+
for (const [path, entry] of this.mounts.entries) {
|
|
509
|
+
if (entry.state === "mounted" || entry.state === "mounting") {
|
|
510
|
+
this.mounts.set(path, { state: "pending" });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
this.status = "stopped";
|
|
515
|
+
}
|
|
516
|
+
// ---------------------------------------------------------------------------
|
|
517
|
+
// Retry on Dead
|
|
518
|
+
// ---------------------------------------------------------------------------
|
|
519
|
+
/**
|
|
520
|
+
* Execute a function, retrying once if the sandbox is found to be dead.
|
|
521
|
+
* Used by DaytonaProcessManager to handle stale sandboxes transparently.
|
|
522
|
+
*/
|
|
523
|
+
async retryOnDead(fn) {
|
|
524
|
+
try {
|
|
525
|
+
return await fn();
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if (this.isSandboxDeadError(error) && !this._isRetrying) {
|
|
528
|
+
this.handleSandboxTimeout();
|
|
529
|
+
this._isRetrying = true;
|
|
530
|
+
try {
|
|
531
|
+
await this.ensureRunning();
|
|
532
|
+
return await fn();
|
|
533
|
+
} finally {
|
|
534
|
+
this._isRetrying = false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
exports.DaytonaProcessManager = DaytonaProcessManager;
|
|
543
|
+
exports.DaytonaSandbox = DaytonaSandbox;
|
|
544
|
+
//# sourceMappingURL=index.cjs.map
|
|
545
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/compact.ts","../src/utils/shell-quote.ts","../src/sandbox/process-manager.ts","../src/sandbox/index.ts"],"names":["ProcessHandle","SandboxProcessManager","MastraSandbox","SandboxNotReadyError","Daytona","SandboxState","DaytonaNotFoundError"],"mappings":";;;;;;;;AAGO,SAAS,QAA0B,GAAA,EAAW;AACnD,EAAA,OAAO,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,CAAE,MAAA,CAAO,CAAC,GAAG,CAAC,CAAA,KAAM,CAAA,KAAM,MAAS,CAAC,CAAA;AAClF;;;ACCO,SAAS,WAAW,GAAA,EAAqB;AAE9C,EAAA,IAAI,wBAAA,CAAyB,IAAA,CAAK,GAAG,CAAA,EAAG,OAAO,GAAA;AAE/C,EAAA,OAAO,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,GAAI,GAAA;AAC5C;;;ACiBA,IAAM,oBAAA,GAAN,cAAmCA,uBAAA,CAAc;AAAA,EACtC,GAAA;AAAA,EAEQ,UAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EAET,SAAA;AAAA,EACA,YAAA,GAA8C,IAAA;AAAA,EAC9C,iBAAA,GAA0C,IAAA;AAAA,EAC1C,OAAA,GAAU,KAAA;AAAA,EAElB,YACE,GAAA,EACA,SAAA,EACA,KAAA,EACA,OAAA,EACA,WACA,OAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AACX,IAAA,IAAA,CAAK,UAAA,GAAa,SAAA;AAClB,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AACd,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAChB,IAAA,IAAA,CAAK,UAAA,GAAa,SAAA;AAClB,IAAA,IAAA,CAAK,WAAW,OAAA,EAAS,OAAA;AAAA,EAC3B;AAAA,EAEA,IAAI,QAAA,GAA+B;AACjC,IAAA,OAAO,IAAA,CAAK,SAAA;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,iBAAiB,CAAA,EAAkB;AACrC,IAAA,IAAA,CAAK,iBAAA,GAAoB,CAAA;AAGzB,IAAA,CAAA,CAAE,IAAA,CAAK,MAAM,IAAA,CAAK,gBAAA,EAAkB,EAAE,KAAA,CAAM,MAAM,IAAA,CAAK,gBAAA,EAAkB,CAAA;AAAA,EAC3E;AAAA;AAAA,EAGA,MAAc,gBAAA,GAAkC;AAC9C,IAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAClC,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,QAAA,CAAS,QAAQ,iBAAA,CAAkB,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,MAAM,CAAA;AACtF,MAAA,IAAA,CAAK,SAAA,GAAY,IAAI,QAAA,IAAY,CAAA;AAAA,IACnC,CAAA,CAAA,MAAQ;AACN,MAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,QAAA,IAAA,CAAK,SAAA,GAAY,CAAA;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,IAAA,GAA+B;AAEnC,IAAA,IAAI,CAAC,KAAK,YAAA,EAAc;AACtB,MAAA,IAAA,CAAK,YAAA,GAAe,KAAK,OAAA,EAAQ;AAAA,IACnC;AACA,IAAA,OAAO,IAAA,CAAK,YAAA;AAAA,EACd;AAAA,EAEA,MAAc,OAAA,GAAkC;AAE9C,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,iBAAA,IAAqB,OAAA,CAAQ,OAAA,EAAQ;AAE7D,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,IAAI,SAAA;AACJ,MAAA,MAAM,cAAA,GAAiB,IAAI,OAAA,CAAe,CAAC,GAAG,MAAA,KAAW;AACvD,QAAA,SAAA,GAAY,UAAA,CAAW,MAAM,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,IAAA,CAAK,QAAQ,CAAA,EAAA,CAAI,CAAC,CAAA,EAAG,IAAA,CAAK,QAAQ,CAAA;AAAA,MAC7G,CAAC,CAAA;AAED,MAAA,IAAI;AACF,QAAA,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,UAAA,EAAY,cAAc,CAAC,CAAA;AAAA,MACjD,SAAS,KAAA,EAAO;AAEd,QAAA,IAAI,iBAAiB,KAAA,IAAS,KAAA,CAAM,OAAA,CAAQ,QAAA,CAAS,WAAW,CAAA,EAAG;AACjE,UAAA,MAAM,KAAK,IAAA,EAAK;AAChB,UAAA,IAAA,CAAK,SAAA,GAAY,GAAA;AACjB,UAAA,OAAO;AAAA,YACL,OAAA,EAAS,KAAA;AAAA,YACT,QAAA,EAAU,GAAA;AAAA,YACV,QAAQ,IAAA,CAAK,MAAA;AAAA,YACb,MAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,KAAA,CAAM,OAAA;AAAA,YAC7B,eAAA,EAAiB,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK;AAAA,WACrC;AAAA,QACF;AACA,QAAA,MAAM,KAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,SAAS,CAAA;AAAA,MACxB;AAAA,IACF,CAAA,MAAO;AAEL,MAAA,MAAM,UAAA,CAAW,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,IACjC;AAGA,IAAA,IAAI,KAAK,OAAA,EAAS;AAChB,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,KAAA;AAAA,QACT,QAAA,EAAU,KAAK,SAAA,IAAa,GAAA;AAAA,QAC5B,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,eAAA,EAAiB,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK;AAAA,OACrC;AAAA,IACF;AAGA,IAAA,MAAM,KAAK,gBAAA,EAAiB;AAE5B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAK,SAAA,KAAc,CAAA;AAAA,MAC5B,QAAA,EAAU,KAAK,SAAA,IAAa,CAAA;AAAA,MAC5B,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,eAAA,EAAiB,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK;AAAA,KACrC;AAAA,EACF;AAAA,EAEA,MAAM,IAAA,GAAyB;AAC7B,IAAA,IAAI,IAAA,CAAK,SAAA,KAAc,MAAA,EAAW,OAAO,KAAA;AACzC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,IAAA,CAAK,SAAA,GAAY,GAAA;AACjB,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,aAAA,CAAc,KAAK,UAAU,CAAA;AAAA,IAC3D,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,UAAU,IAAA,EAA6B;AAC3C,IAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,MAAA,MAAM,IAAI,MAAM,CAAA,QAAA,EAAW,IAAA,CAAK,GAAG,CAAA,8BAAA,EAAiC,IAAA,CAAK,SAAS,CAAA,CAAE,CAAA;AAAA,IACtF;AACA,IAAA,MAAM,IAAA,CAAK,SAAS,OAAA,CAAQ,uBAAA,CAAwB,KAAK,UAAA,EAAY,IAAA,CAAK,QAAQ,IAAI,CAAA;AAAA,EACxF;AACF,CAAA;AAgBO,IAAM,qBAAA,GAAN,cAAoCC,+BAAA,CAAsC;AAAA,EACvE,QAAA,GAAW,CAAA;AAAA,EACF,eAAA;AAAA,EAEjB,WAAA,CAAY,IAAA,GAAqC,EAAC,EAAG;AACnD,IAAA,KAAA,CAAM,EAAE,GAAA,EAAK,IAAA,CAAK,GAAA,EAAK,CAAA;AACvB,IAAA,IAAA,CAAK,kBAAkB,IAAA,CAAK,cAAA;AAAA,EAC9B;AAAA,EAEA,MAAM,KAAA,CAAM,OAAA,EAAiB,OAAA,GAA+B,EAAC,EAA2B;AAEtF,IAAA,MAAM,gBAAA,GAAmB;AAAA,MACvB,GAAG,OAAA;AAAA,MACH,OAAA,EAAS,OAAA,CAAQ,OAAA,IAAW,IAAA,CAAK;AAAA,KACnC;AACA,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,WAAA,CAAY,YAAY;AAC1C,MAAA,MAAM,OAAA,GAAU,KAAK,OAAA,CAAQ,QAAA;AAC7B,MAAA,MAAM,MAAM,IAAA,CAAK,QAAA,EAAA;AAGjB,MAAA,MAAM,YAAY,EAAE,GAAG,KAAK,GAAA,EAAK,GAAG,iBAAiB,GAAA,EAAI;AACzD,MAAA,MAAM,OAAO,MAAA,CAAO,WAAA;AAAA,QAClB,MAAA,CAAO,OAAA,CAAQ,SAAS,CAAA,CAAE,MAAA,CAAO,CAAC,KAAA,KAAqC,KAAA,CAAM,CAAC,CAAA,KAAM,MAAS;AAAA,OAC/F;AAGA,MAAA,MAAM,cAAA,GAAiB,iBAAA,CAAkB,OAAA,EAAS,gBAAA,CAAiB,KAAK,IAAI,CAAA;AAG5E,MAAA,MAAM,SAAA,GAAY,eAAe,IAAA,CAAK,GAAA,GAAM,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AAE/D,MAAA,MAAM,OAAA,CAAQ,OAAA,CAAQ,aAAA,CAAc,SAAS,CAAA;AAE7C,MAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,OAAA,CAAQ,OAAA,CAAQ,sBAAsB,SAAA,EAAW;AAAA,QACvE,OAAA,EAAS,cAAA;AAAA,QACT,QAAA,EAAU;AAAA,OACX,CAAA;AAED,MAAA,MAAM,MAAA,GAAS,IAAI,oBAAA,CAAqB,GAAA,EAAK,SAAA,EAAW,OAAO,OAAA,EAAS,IAAA,CAAK,GAAA,EAAI,EAAG,gBAAgB,CAAA;AAGpG,MAAA,MAAM,gBAAA,GAAmB,QAAQ,OAAA,CAC9B,qBAAA;AAAA,QACC,SAAA;AAAA,QACA,KAAA;AAAA,QACA,CAAC,KAAA,KAAkB,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AAAA,QAC1C,CAAC,KAAA,KAAkB,MAAA,CAAO,UAAA,CAAW,KAAK;AAAA,OAC5C,CACC,MAAM,MAAM;AAAA,MAEb,CAAC,CAAA;AAEH,MAAA,MAAA,CAAO,gBAAA,GAAmB,gBAAA;AAE1B,MAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,GAAA,EAAK,MAAM,CAAA;AAC7B,MAAA,OAAO,MAAA;AAAA,IACT,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,IAAA,GAA+B;AACnC,IAAA,MAAM,SAAwB,EAAC;AAC/B,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,MAAM,CAAA,IAAK,KAAK,QAAA,EAAU;AACzC,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,GAAA;AAAA,QACA,SAAS,MAAA,CAAO,OAAA;AAAA,QAChB,OAAA,EAAS,OAAO,QAAA,KAAa,MAAA;AAAA,QAC7B,UAAU,MAAA,CAAO;AAAA,OAClB,CAAA;AAAA,IACH;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,GAAA,EAAiD;AACzD,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA;AAAA,EAC9B;AACF;AAgBA,SAAS,iBAAA,CAAkB,OAAA,EAAiB,GAAA,EAAyB,IAAA,EAAsC;AACzG,EAAA,MAAM,QAAkB,EAAC;AAEzB,EAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,EAAG;AACzC,IAAA,KAAA,CAAM,KAAK,CAAA,OAAA,EAAU,CAAC,IAAI,UAAA,CAAW,CAAC,CAAC,CAAA,CAAE,CAAA;AAAA,EAC3C;AAEA,EAAA,IAAI,GAAA,EAAK;AACP,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,GAAA,EAAM,UAAA,CAAW,GAAG,CAAC,CAAA,CAAE,CAAA;AAAA,EACpC;AAGA,EAAA,KAAA,CAAM,IAAA,CAAK,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,CAAG,CAAA;AAEzB,EAAA,OAAO,KAAA,CAAM,KAAK,MAAM,CAAA;AAC1B;;;ACzQA,IAAM,UAAA,GAAa,mBAAA;AAGnB,IAAM,qBAAA,GAAkC;AAAA,EACtC,yBAAA;AAAA,EACA,4BAAA;AAAA,EACA;AACF,CAAA;AAqHO,IAAM,cAAA,GAAN,cAA6BC,uBAAA,CAAc;AAAA,EACvC,EAAA;AAAA,EACA,IAAA,GAAO,gBAAA;AAAA,EACP,QAAA,GAAW,SAAA;AAAA,EAEpB,MAAA,GAAyB,SAAA;AAAA,EAEjB,QAAA,GAA2B,IAAA;AAAA,EAC3B,QAAA,GAA2B,IAAA;AAAA,EAC3B,UAAA,GAA0B,IAAA;AAAA,EAC1B,WAAA,GAAc,KAAA;AAAA,EACd,WAAA,GAA6B,IAAA;AAAA,EAEpB,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,gBAAA;AAAA,EACA,mBAAA;AAAA,EACA,kBAAA;AAAA,EACA,aAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,eAAA;AAAA,EACA,gBAAA;AAAA,EACA,cAAA;AAAA,EAEjB,WAAA,CAAY,OAAA,GAAiC,EAAC,EAAG;AAC/C,IAAA,KAAA,CAAM;AAAA,MACJ,GAAG,OAAA;AAAA,MACH,IAAA,EAAM,gBAAA;AAAA,MACN,SAAA,EAAW,IAAI,qBAAA,CAAsB;AAAA,QACnC,KAAK,OAAA,CAAQ,GAAA;AAAA,QACb,cAAA,EAAgB,QAAQ,OAAA,IAAW;AAAA,OACpC;AAAA,KACF,CAAA;AAED,IAAA,IAAA,CAAK,EAAA,GAAK,OAAA,CAAQ,EAAA,IAAM,IAAA,CAAK,UAAA,EAAW;AACxC,IAAA,IAAA,CAAK,OAAA,GAAU,QAAQ,OAAA,IAAW,GAAA;AAClC,IAAA,IAAA,CAAK,QAAA,GAAW,QAAQ,QAAA,IAAY,YAAA;AACpC,IAAA,IAAA,CAAK,YAAY,OAAA,CAAQ,SAAA;AACzB,IAAA,IAAA,CAAK,GAAA,GAAM,OAAA,CAAQ,GAAA,IAAO,EAAC;AAC3B,IAAA,IAAA,CAAK,MAAA,GAAS,OAAA,CAAQ,MAAA,IAAU,EAAC;AACjC,IAAA,IAAA,CAAK,aAAa,OAAA,CAAQ,QAAA;AAC1B,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACrB,IAAA,IAAA,CAAK,SAAA,GAAY,QAAQ,SAAA,IAAa,KAAA;AACtC,IAAA,IAAA,CAAK,gBAAA,GAAmB,QAAQ,gBAAA,IAAoB,EAAA;AACpD,IAAA,IAAA,CAAK,sBAAsB,OAAA,CAAQ,mBAAA;AACnC,IAAA,IAAA,CAAK,qBAAqB,OAAA,CAAQ,kBAAA;AAClC,IAAA,IAAA,CAAK,aAAA,GAAgB,OAAA,CAAQ,OAAA,IAAW,EAAC;AACzC,IAAA,IAAA,CAAK,WAAA,GAAc,OAAA,CAAQ,IAAA,IAAQ,IAAA,CAAK,EAAA;AACxC,IAAA,IAAA,CAAK,cAAc,OAAA,CAAQ,IAAA;AAC3B,IAAA,IAAA,CAAK,gBAAgB,OAAA,CAAQ,MAAA;AAC7B,IAAA,IAAA,CAAK,kBAAkB,OAAA,CAAQ,eAAA;AAC/B,IAAA,IAAA,CAAK,mBAAmB,OAAA,CAAQ,gBAAA;AAEhC,IAAA,IAAA,CAAK,cAAA,GAAiB;AAAA,MACpB,GAAI,OAAA,CAAQ,MAAA,KAAW,UAAa,EAAE,MAAA,EAAQ,QAAQ,MAAA,EAAO;AAAA,MAC7D,GAAI,OAAA,CAAQ,MAAA,KAAW,UAAa,EAAE,MAAA,EAAQ,QAAQ,MAAA,EAAO;AAAA,MAC7D,GAAI,OAAA,CAAQ,MAAA,KAAW,UAAa,EAAE,MAAA,EAAQ,QAAQ,MAAA;AAAO,KAC/D;AAAA,EACF;AAAA,EAEQ,UAAA,GAAqB;AAC3B,IAAA,OAAO,mBAAmB,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,GAAS,QAAA,CAAS,EAAE,EAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA;AAAA,EAC7F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,IAAI,QAAA,GAAoB;AACtB,IAAA,IAAI,CAAC,KAAK,QAAA,EAAU;AAClB,MAAA,MAAM,IAAIC,8BAAA,CAAqB,IAAA,CAAK,EAAE,CAAA;AAAA,IACxC;AACA,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,CAAC,KAAK,QAAA,EAAU;AAClB,MAAA,IAAA,CAAK,QAAA,GAAW,IAAIC,WAAA,CAAQ,IAAA,CAAK,cAAc,CAAA;AAAA,IACjD;AAGA,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,mBAAA,EAAoB;AAChD,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,MAAA,IAAA,CAAK,UAAA,GAAa,SAAS,SAAA,GAAY,IAAI,KAAK,QAAA,CAAS,SAAS,CAAA,mBAAI,IAAI,IAAA,EAAK;AAC/E,MAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,UAAU,CAAA,iCAAA,EAAoC,SAAS,EAAE,CAAA,MAAA,EAAS,IAAA,CAAK,EAAE,CAAA,CAAE,CAAA;AAChG,MAAA,MAAM,KAAK,gBAAA,EAAiB;AAC5B,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,OAAO,KAAA,CAAM,CAAA,EAAG,UAAU,CAAA,uBAAA,EAA0B,IAAA,CAAK,EAAE,CAAA,CAAE,CAAA;AAGlE,IAAA,MAAM,aAAa,OAAA,CAAQ;AAAA,MACzB,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,SAAS,IAAA,CAAK,GAAA;AAAA,MACd,QAAQ,EAAE,GAAG,KAAK,MAAA,EAAQ,mBAAA,EAAqB,KAAK,EAAA,EAAG;AAAA,MACvD,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,kBAAkB,IAAA,CAAK,gBAAA;AAAA,MACvB,qBAAqB,IAAA,CAAK,mBAAA;AAAA,MAC1B,oBAAoB,IAAA,CAAK,kBAAA;AAAA,MACzB,SAAS,IAAA,CAAK,aAAA,CAAc,MAAA,GAAS,CAAA,GAAI,KAAK,aAAA,GAAgB,MAAA;AAAA,MAC9D,MAAM,IAAA,CAAK,WAAA;AAAA,MACX,MAAM,IAAA,CAAK,WAAA;AAAA,MACX,QAAQ,IAAA,CAAK,aAAA;AAAA,MACb,iBAAiB,IAAA,CAAK,eAAA;AAAA,MACtB,kBAAkB,IAAA,CAAK;AAAA,KACxB,CAAA;AAID,IAAA,IAAI,IAAA,CAAK,SAAA,IAAa,CAAC,IAAA,CAAK,KAAA,EAAO;AACjC,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA;AAAA,QACV,GAAG,UAAU,CAAA,2HAAA;AAAA,OACf;AAAA,IACF;AAEA,IAAA,MAAM,eACJ,IAAA,CAAK,KAAA,IAAS,CAAC,IAAA,CAAK,aACf,OAAA,CAAQ;AAAA,MACP,GAAG,UAAA;AAAA,MACH,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,WAAW,IAAA,CAAK;AAAA,KACjB,IACA,OAAA,CAAQ,EAAE,GAAG,UAAA,EAAY,QAAA,EAAU,IAAA,CAAK,UAAA,EAAY,CAAA;AAG3D,IAAA,IAAA,CAAK,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,CAAS,OAAO,YAAY,CAAA;AAEvD,IAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,UAAU,CAAA,iBAAA,EAAoB,IAAA,CAAK,QAAA,CAAS,EAAE,CAAA,iBAAA,EAAoB,IAAA,CAAK,EAAE,CAAA,CAAE,CAAA;AAChG,IAAA,IAAA,CAAK,UAAA,uBAAiB,IAAA,EAAK;AAG3B,IAAA,MAAM,KAAK,gBAAA,EAAiB;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,EAAU;AAClC,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,IAAA,CAAK,QAAQ,CAAA;AAAA,MACxC,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAA,GAAyB;AAC7B,IAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,EAAU;AAClC,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAA;AAAA,MAC1C,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF,CAAA,MAAA,IAAW,CAAC,IAAA,CAAK,QAAA,IAAY,KAAK,QAAA,EAAU;AAI1C,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,EAAE,MAAA,EAAQ,EAAE,mBAAA,EAAqB,IAAA,CAAK,EAAA,EAAG,EAAG,CAAA;AACvF,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,MAAM,IAAA,CAAK,QAAA,CAAS,MAAA,CAAO,MAAM,CAAA;AAAA,QACnC;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAA,GAA4B;AAChC,IAAA,OAAO,IAAA,CAAK,MAAA,KAAW,SAAA,IAAa,IAAA,CAAK,QAAA,KAAa,IAAA;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAA,GAAgC;AACpC,IAAA,OAAO;AAAA,MACL,IAAI,IAAA,CAAK,EAAA;AAAA,MACT,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,SAAA,EAAW,IAAA,CAAK,UAAA,oBAAc,IAAI,IAAA,EAAK;AAAA,MACvC,MAAA,EAAQ,IAAA,CAAK,MAAA,GACT,KAAA,CAAM,KAAK,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA,CAAE,GAAA,CAAI,CAAC,CAAC,IAAA,EAAM,KAAK,CAAA,MAAO;AAAA,QACtD,IAAA;AAAA,QACA,YAAY,KAAA,CAAM,UAAA,EAAY,QAAA,IAAY,KAAA,CAAM,QAAQ,IAAA,IAAQ;AAAA,OAClE,CAAE,IACF,EAAC;AAAA,MACL,GAAI,KAAK,QAAA,IAAY;AAAA,QACnB,SAAA,EAAW;AAAA,UACT,QAAA,EAAU,KAAK,QAAA,CAAS,GAAA;AAAA,UACxB,QAAA,EAAU,IAAA,CAAK,QAAA,CAAS,MAAA,GAAS,IAAA;AAAA,UACjC,MAAA,EAAQ,IAAA,CAAK,QAAA,CAAS,IAAA,GAAO;AAAA;AAC/B,OACF;AAAA,MACA,QAAA,EAAU;AAAA,QACR,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,GAAI,IAAA,CAAK,UAAA,IAAc,EAAE,QAAA,EAAU,KAAK,UAAA,EAAW;AAAA,QACnD,GAAI,IAAA,CAAK,KAAA,IAAS,EAAE,KAAA,EAAO,KAAK,KAAA,EAAM;AAAA,QACtC,GAAI,IAAA,CAAK,QAAA,IAAY,EAAE,MAAA,EAAQ,IAAA,CAAK,SAAS,MAAA;AAAO;AACtD,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAA,GAA0B;AACxB,IAAA,MAAM,QAAkB,EAAC;AAEzB,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,uCAAA,EAA0C,IAAA,CAAK,QAAQ,CAAA,UAAA,CAAY,CAAA;AAE9E,IAAA,IAAI,KAAK,WAAA,EAAa;AACpB,MAAA,KAAA,CAAM,IAAA,CAAK,CAAA,2BAAA,EAA8B,IAAA,CAAK,WAAW,CAAA,CAAA,CAAG,CAAA;AAAA,IAC9D;AACA,IAAA,KAAA,CAAM,IAAA,CAAK,oBAAoB,IAAA,CAAK,IAAA,CAAK,KAAK,OAAA,GAAU,GAAI,CAAC,CAAA,EAAA,CAAI,CAAA;AAEjE,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,iBAAA,EAAoB,IAAA,CAAK,WAAA,IAAe,SAAS,CAAA,CAAA,CAAG,CAAA;AAE/D,IAAA,IAAI,IAAA,CAAK,aAAA,CAAc,MAAA,GAAS,CAAA,EAAG;AACjC,MAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,aAAA,CAAc,MAAM,CAAA,oBAAA,CAAsB,CAAA;AAAA,IAC/D;AAEA,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,KAAA,CAAM,KAAK,CAAA,0BAAA,CAA4B,CAAA;AAAA,IACzC;AAEA,IAAA,OAAO,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,gBAAA,GAAkC;AAC9C,IAAA,IAAI,CAAC,KAAK,QAAA,EAAU;AACpB,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,MAAM,IAAA,CAAK,QAAA,CAAS,OAAA,CAAQ,eAAe,KAAK,CAAA;AAC/D,MAAA,MAAM,GAAA,GAAM,MAAA,CAAO,MAAA,EAAQ,IAAA,EAAK;AAChC,MAAA,IAAI,GAAA,EAAK;AACP,QAAA,IAAA,CAAK,WAAA,GAAc,GAAA;AACnB,QAAA,IAAA,CAAK,OAAO,KAAA,CAAM,CAAA,EAAG,UAAU,CAAA,6BAAA,EAAgC,GAAG,CAAA,CAAE,CAAA;AAAA,MACtE;AAAA,IACF,CAAA,CAAA,MAAQ;AACN,MAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,UAAU,CAAA,gEAAA,CAAkE,CAAA;AAAA,IACnG;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,mBAAA,GAA+C;AAC3D,IAAA,MAAM,WAAA,GAA8B;AAAA,MAClCC,gBAAA,CAAa,SAAA;AAAA,MACbA,gBAAA,CAAa,UAAA;AAAA,MACbA,gBAAA,CAAa,KAAA;AAAA,MACbA,gBAAA,CAAa;AAAA,KACf;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,QAAA,CAAU,OAAA,CAAQ,EAAE,MAAA,EAAQ,EAAE,mBAAA,EAAqB,IAAA,CAAK,EAAA,EAAG,EAAG,CAAA;AACzF,MAAA,MAAM,QAAQ,OAAA,CAAQ,KAAA;AAEtB,MAAA,IAAI,KAAA,IAAS,WAAA,CAAY,QAAA,CAAS,KAAK,CAAA,EAAG;AACxC,QAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,UACV,GAAG,UAAU,CAAA,kBAAA,EAAqB,OAAA,CAAQ,EAAE,aAAa,KAAK,CAAA,8BAAA;AAAA,SAChE;AACA,QAAA,IAAI;AACF,UAAA,MAAM,IAAA,CAAK,QAAA,CAAU,MAAA,CAAO,OAAO,CAAA;AAAA,QACrC,CAAA,CAAA,MAAQ;AAAA,QAER;AACA,QAAA,OAAO,IAAA;AAAA,MACT;AAEA,MAAA,IAAI,KAAA,KAAUA,iBAAa,OAAA,EAAS;AAClC,QAAA,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,EAAG,UAAU,uBAAuB,OAAA,CAAQ,EAAE,CAAA,SAAA,EAAY,KAAK,CAAA,CAAA,CAAG,CAAA;AACpF,QAAA,MAAM,IAAA,CAAK,QAAA,CAAU,KAAA,CAAM,OAAO,CAAA;AAAA,MACpC;AAEA,MAAA,OAAO,OAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AAEN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,mBAAmB,KAAA,EAAyB;AAClD,IAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,IAAA,IAAI,KAAA,YAAiBC,0BAAsB,OAAO,IAAA;AAClD,IAAA,MAAM,QAAA,GAAW,OAAO,KAAK,CAAA;AAC7B,IAAA,OAAO,sBAAsB,IAAA,CAAK,CAAA,OAAA,KAAW,OAAA,CAAQ,IAAA,CAAK,QAAQ,CAAC,CAAA;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAA,GAA6B;AACnC,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAGhB,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,KAAA,MAAW,CAAC,IAAA,EAAM,KAAK,CAAA,IAAK,IAAA,CAAK,OAAO,OAAA,EAAS;AAC/C,QAAA,IAAI,KAAA,CAAM,KAAA,KAAU,SAAA,IAAa,KAAA,CAAM,UAAU,UAAA,EAAY;AAC3D,UAAA,IAAA,CAAK,OAAO,GAAA,CAAI,IAAA,EAAM,EAAE,KAAA,EAAO,WAAW,CAAA;AAAA,QAC5C;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,MAAA,GAAS,SAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,YAAe,EAAA,EAAkC;AACrD,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,EAAA,EAAG;AAAA,IAClB,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,KAAK,kBAAA,CAAmB,KAAK,CAAA,IAAK,CAAC,KAAK,WAAA,EAAa;AACvD,QAAA,IAAA,CAAK,oBAAA,EAAqB;AAC1B,QAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AACnB,QAAA,IAAI;AACF,UAAA,MAAM,KAAK,aAAA,EAAc;AACzB,UAAA,OAAO,MAAM,EAAA,EAAG;AAAA,QAClB,CAAA,SAAE;AACA,UAAA,IAAA,CAAK,WAAA,GAAc,KAAA;AAAA,QACrB;AAAA,MACF;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF","file":"index.cjs","sourcesContent":["/**\n * Returns a shallow copy of the object with all undefined values removed.\n */\nexport function compact<T extends object>(obj: T): T {\n return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as T;\n}\n","/**\n * Shell-quote a single argument for safe use in a command string.\n *\n * Arguments containing only safe characters are returned as-is.\n * All others are wrapped in single quotes with embedded single quotes escaped.\n */\nexport function shellQuote(arg: string): string {\n // Safe characters that don't need quoting\n if (/^[a-zA-Z0-9._\\-/@:=]+$/.test(arg)) return arg;\n // Wrap in single quotes, escaping any embedded single quotes\n return \"'\" + arg.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n","/**\n * Daytona Process Manager\n *\n * Implements SandboxProcessManager for Daytona cloud sandboxes.\n * Wraps the Daytona SDK's session API (createSession, executeSessionCommand,\n * getSessionCommandLogs, deleteSession) for background process management.\n *\n * Each spawn() creates a dedicated session with a single command.\n * The user command is wrapped in a subshell `(command)` so that:\n * - `exit N` exits the subshell, not the session shell\n * - Heredocs are contained within the subshell\n * - The session command finishes cleanly\n */\n\nimport type { Sandbox } from '@daytonaio/sdk';\nimport { ProcessHandle, SandboxProcessManager } from '@mastra/core/workspace';\nimport type { CommandResult, ProcessInfo, SpawnProcessOptions } from '@mastra/core/workspace';\nimport { shellQuote } from '../utils/shell-quote';\nimport type { DaytonaSandbox } from './index';\n\n// =============================================================================\n// Daytona Process Handle\n// =============================================================================\n\n/**\n * Wraps a Daytona session + command pair to conform to Mastra's ProcessHandle.\n * Not exported — internal to this module.\n */\nclass DaytonaProcessHandle extends ProcessHandle {\n readonly pid: number;\n\n private readonly _sessionId: string;\n private readonly _cmdId: string;\n private readonly _sandbox: Sandbox;\n private readonly _startTime: number;\n private readonly _timeout?: number;\n\n private _exitCode: number | undefined;\n private _waitPromise: Promise<CommandResult> | null = null;\n private _streamingPromise: Promise<void> | null = null;\n private _killed = false;\n\n constructor(\n pid: number,\n sessionId: string,\n cmdId: string,\n sandbox: Sandbox,\n startTime: number,\n options?: SpawnProcessOptions,\n ) {\n super(options);\n this.pid = pid;\n this._sessionId = sessionId;\n this._cmdId = cmdId;\n this._sandbox = sandbox;\n this._startTime = startTime;\n this._timeout = options?.timeout;\n }\n\n get exitCode(): number | undefined {\n return this._exitCode;\n }\n\n /** @internal Set by the process manager after streaming starts. */\n set streamingPromise(p: Promise<void>) {\n this._streamingPromise = p;\n\n // Auto-resolve exit code when streaming ends (so exitCode is available without wait())\n p.then(() => this._resolveExitCode()).catch(() => this._resolveExitCode());\n }\n\n /** Fetch the exit code from Daytona and set _exitCode. No-op if already set. */\n private async _resolveExitCode(): Promise<void> {\n if (this._exitCode !== undefined) return;\n try {\n const cmd = await this._sandbox.process.getSessionCommand(this._sessionId, this._cmdId);\n this._exitCode = cmd.exitCode ?? 0;\n } catch {\n if (this._exitCode === undefined) {\n this._exitCode = 1;\n }\n }\n }\n\n async wait(): Promise<CommandResult> {\n // Idempotent — cache the promise so repeated calls return the same result\n if (!this._waitPromise) {\n this._waitPromise = this._doWait();\n }\n return this._waitPromise;\n }\n\n private async _doWait(): Promise<CommandResult> {\n // Race streaming against timeout (if configured)\n const streamDone = this._streamingPromise ?? Promise.resolve();\n\n if (this._timeout) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(`Command timed out after ${this._timeout}ms`)), this._timeout);\n });\n\n try {\n await Promise.race([streamDone, timeoutPromise]);\n } catch (error) {\n // On timeout, kill the process and return partial output\n if (error instanceof Error && error.message.includes('timed out')) {\n await this.kill();\n this._exitCode = 124; // Standard timeout exit code\n return {\n success: false,\n exitCode: 124,\n stdout: this.stdout,\n stderr: this.stderr || error.message,\n executionTimeMs: Date.now() - this._startTime,\n };\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n } else {\n // No timeout — just wait for streaming to complete\n await streamDone.catch(() => {});\n }\n\n // If killed during wait, return with kill exit code\n if (this._killed) {\n return {\n success: false,\n exitCode: this._exitCode ?? 137,\n stdout: this.stdout,\n stderr: this.stderr,\n executionTimeMs: Date.now() - this._startTime,\n };\n }\n\n // Ensure exit code is resolved\n await this._resolveExitCode();\n\n return {\n success: this._exitCode === 0,\n exitCode: this._exitCode ?? 1,\n stdout: this.stdout,\n stderr: this.stderr,\n executionTimeMs: Date.now() - this._startTime,\n };\n }\n\n async kill(): Promise<boolean> {\n if (this._exitCode !== undefined) return false;\n this._killed = true;\n this._exitCode = 137; // SIGKILL\n try {\n await this._sandbox.process.deleteSession(this._sessionId);\n } catch {\n // Session may already be gone\n }\n return true;\n }\n\n async sendStdin(data: string): Promise<void> {\n if (this._exitCode !== undefined) {\n throw new Error(`Process ${this.pid} has already exited with code ${this._exitCode}`);\n }\n await this._sandbox.process.sendSessionCommandInput(this._sessionId, this._cmdId, data);\n }\n}\n\n// =============================================================================\n// Daytona Process Manager\n// =============================================================================\n\nexport interface DaytonaProcessManagerOptions {\n env?: Record<string, string | undefined>;\n /** Default timeout in milliseconds for commands that don't specify one. */\n defaultTimeout?: number;\n}\n\n/**\n * Daytona implementation of SandboxProcessManager.\n * Uses the Daytona SDK's session API with one session per spawned process.\n */\nexport class DaytonaProcessManager extends SandboxProcessManager<DaytonaSandbox> {\n private _nextPid = 1;\n private readonly _defaultTimeout?: number;\n\n constructor(opts: DaytonaProcessManagerOptions = {}) {\n super({ env: opts.env });\n this._defaultTimeout = opts.defaultTimeout;\n }\n\n async spawn(command: string, options: SpawnProcessOptions = {}): Promise<ProcessHandle> {\n // Apply default timeout if the caller didn't specify one\n const effectiveOptions = {\n ...options,\n timeout: options.timeout ?? this._defaultTimeout,\n };\n return this.sandbox.retryOnDead(async () => {\n const sandbox = this.sandbox.instance;\n const pid = this._nextPid++;\n\n // Merge default env with per-spawn env\n const mergedEnv = { ...this.env, ...effectiveOptions.env };\n const envs = Object.fromEntries(\n Object.entries(mergedEnv).filter((entry): entry is [string, string] => entry[1] !== undefined),\n );\n\n // Build command with baked-in env and cwd, wrapped in subshell\n const sessionCommand = buildSpawnCommand(command, effectiveOptions.cwd, envs);\n\n // Unique session ID per spawn\n const sessionId = `mastra-proc-${Date.now().toString(36)}-${pid}`;\n\n await sandbox.process.createSession(sessionId);\n\n const { cmdId } = await sandbox.process.executeSessionCommand(sessionId, {\n command: sessionCommand,\n runAsync: true,\n });\n\n const handle = new DaytonaProcessHandle(pid, sessionId, cmdId, sandbox, Date.now(), effectiveOptions);\n\n // Start streaming logs — route to handle's emitters\n const streamingPromise = sandbox.process\n .getSessionCommandLogs(\n sessionId,\n cmdId,\n (chunk: string) => handle.emitStdout(chunk),\n (chunk: string) => handle.emitStderr(chunk),\n )\n .catch(() => {\n // Stream ends when session is deleted (e.g., after kill) — swallow the error\n });\n\n handle.streamingPromise = streamingPromise;\n\n this._tracked.set(pid, handle);\n return handle;\n });\n }\n\n async list(): Promise<ProcessInfo[]> {\n const result: ProcessInfo[] = [];\n for (const [pid, handle] of this._tracked) {\n result.push({\n pid,\n command: handle.command,\n running: handle.exitCode === undefined,\n exitCode: handle.exitCode,\n });\n }\n return result;\n }\n\n async get(pid: number): Promise<ProcessHandle | undefined> {\n return this._tracked.get(pid);\n }\n}\n\n// =============================================================================\n// Command Building\n// =============================================================================\n\n/**\n * Build a shell command string that bakes in cwd and env vars.\n * Wraps the user command in a subshell `(command)` so that:\n * - `exit N` exits the subshell, not the session shell\n * - Heredocs work correctly within the subshell\n *\n * @example\n * buildSpawnCommand('npm test', '/app', { NODE_ENV: 'test' })\n * // → \"export NODE_ENV='test' && cd '/app' && (npm test)\"\n */\nfunction buildSpawnCommand(command: string, cwd: string | undefined, envs: Record<string, string>): string {\n const parts: string[] = [];\n\n for (const [k, v] of Object.entries(envs)) {\n parts.push(`export ${k}=${shellQuote(v)}`);\n }\n\n if (cwd) {\n parts.push(`cd ${shellQuote(cwd)}`);\n }\n\n // Wrap in subshell to isolate exit codes and heredocs\n parts.push(`(${command})`);\n\n return parts.join(' && ');\n}\n","/**\n * Daytona Sandbox Provider\n *\n * A Daytona sandbox implementation for Mastra workspaces.\n * Supports command execution, environment variables, resource configuration,\n * snapshots, and Daytona volumes.\n *\n * @see https://www.daytona.io/docs\n */\n\nimport { Daytona, DaytonaNotFoundError, SandboxState } from '@daytonaio/sdk';\nimport type {\n CreateSandboxFromImageParams,\n CreateSandboxFromSnapshotParams,\n Sandbox,\n VolumeMount,\n} from '@daytonaio/sdk';\nimport type { SandboxInfo, ProviderStatus, MastraSandboxOptions } from '@mastra/core/workspace';\nimport { MastraSandbox, SandboxNotReadyError } from '@mastra/core/workspace';\n\nimport { compact } from '../utils/compact';\nimport { DaytonaProcessManager } from './process-manager';\nimport type { DaytonaResources } from './types';\n\nconst LOG_PREFIX = '[@mastra/daytona]';\n\n/** Patterns indicating the sandbox is dead/gone (@daytonaio/sdk@0.143.0). */\nconst SANDBOX_DEAD_PATTERNS: RegExp[] = [\n /sandbox is not running/i,\n /sandbox already destroyed/i,\n /sandbox.*not found/i,\n];\n\n// =============================================================================\n// Daytona Sandbox Options\n// =============================================================================\n\n/**\n * Daytona sandbox provider configuration.\n */\nexport interface DaytonaSandboxOptions extends MastraSandboxOptions {\n /** Unique identifier for this sandbox instance */\n id?: string;\n /** API key for authentication. Falls back to DAYTONA_API_KEY env var. */\n apiKey?: string;\n /** API URL. Falls back to DAYTONA_API_URL env var or https://app.daytona.io/api. */\n apiUrl?: string;\n /** Target runner region. Falls back to DAYTONA_TARGET env var. */\n target?: string;\n /**\n * Default execution timeout in milliseconds.\n * @default 300_000 // 5 minutes\n */\n timeout?: number;\n /**\n * Sandbox runtime language.\n * @default 'typescript'\n */\n language?: 'typescript' | 'javascript' | 'python';\n /** Resource allocation for the sandbox */\n resources?: DaytonaResources;\n /** Environment variables to set in the sandbox */\n env?: Record<string, string>;\n /** Custom metadata labels */\n labels?: Record<string, string>;\n /** Pre-built snapshot ID to create sandbox from. Takes precedence over resources/image. */\n snapshot?: string;\n /**\n * Docker image to use for sandbox creation. When set, triggers image-based creation.\n * Can optionally be combined with `resources` for custom resource allocation.\n * Has no effect when `snapshot` is set.\n */\n image?: string;\n /**\n * Whether the sandbox should be ephemeral. If true, autoDeleteInterval will be set to 0\n * (delete immediately on stop).\n * @default false\n */\n ephemeral?: boolean;\n /**\n * Auto-stop interval in minutes (0 = disabled).\n * @default 15\n */\n autoStopInterval?: number;\n /**\n * Auto-archive interval in minutes (0 = maximum interval, which is 7 days).\n * @default 7 days\n */\n autoArchiveInterval?: number;\n /**\n * Daytona volumes to attach at creation.\n * Volumes are configured at sandbox creation time, not mounted dynamically.\n */\n volumes?: Array<VolumeMount>;\n /** Sandbox display name */\n name?: string;\n /** OS user to use for the sandbox */\n user?: string;\n /** Whether the sandbox port preview is public */\n public?: boolean;\n /**\n * Auto-delete interval in minutes (negative = disabled, 0 = delete immediately on stop).\n * @default disabled\n */\n autoDeleteInterval?: number;\n /** Whether to block all network access for the sandbox */\n networkBlockAll?: boolean;\n /** Comma-separated list of allowed CIDR network addresses for the sandbox */\n networkAllowList?: string;\n}\n\n// =============================================================================\n// Daytona Sandbox Implementation\n// =============================================================================\n\n/**\n * Daytona sandbox provider for Mastra workspaces.\n *\n * Features:\n * - Isolated cloud sandbox via Daytona SDK\n * - Multi-runtime support (TypeScript, JavaScript, Python)\n * - Resource configuration (CPU, memory, disk)\n * - Volume attachment at creation time\n * - Automatic sandbox timeout handling with retry\n *\n * @example Basic usage\n * ```typescript\n * import { Workspace } from '@mastra/core/workspace';\n * import { DaytonaSandbox } from '@mastra/daytona';\n *\n * const sandbox = new DaytonaSandbox({\n * timeout: 60000,\n * language: 'typescript',\n * });\n *\n * const workspace = new Workspace({ sandbox });\n * const result = await workspace.executeCode('console.log(\"Hello!\")');\n * ```\n *\n * @example With resources and volumes\n * ```typescript\n * const sandbox = new DaytonaSandbox({\n * resources: { cpu: 2, memory: 4, disk: 6 },\n * volumes: [{ volumeId: 'vol-123', mountPath: '/data' }],\n * env: { NODE_ENV: 'production' },\n * });\n * ```\n */\nexport class DaytonaSandbox extends MastraSandbox {\n readonly id: string;\n readonly name = 'DaytonaSandbox';\n readonly provider = 'daytona';\n\n status: ProviderStatus = 'pending';\n\n private _daytona: Daytona | null = null;\n private _sandbox: Sandbox | null = null;\n private _createdAt: Date | null = null;\n private _isRetrying = false;\n private _workingDir: string | null = null;\n\n private readonly timeout: number;\n private readonly language: 'typescript' | 'javascript' | 'python';\n private readonly resources?: DaytonaResources;\n private readonly env: Record<string, string>;\n private readonly labels: Record<string, string>;\n private readonly snapshotId?: string;\n private readonly image?: string;\n private readonly ephemeral: boolean;\n private readonly autoStopInterval?: number;\n private readonly autoArchiveInterval?: number;\n private readonly autoDeleteInterval?: number;\n private readonly volumeConfigs: Array<VolumeMount>;\n private readonly sandboxName?: string;\n private readonly sandboxUser?: string;\n private readonly sandboxPublic?: boolean;\n private readonly networkBlockAll?: boolean;\n private readonly networkAllowList?: string;\n private readonly connectionOpts: { apiKey?: string; apiUrl?: string; target?: string };\n\n constructor(options: DaytonaSandboxOptions = {}) {\n super({\n ...options,\n name: 'DaytonaSandbox',\n processes: new DaytonaProcessManager({\n env: options.env,\n defaultTimeout: options.timeout ?? 300_000,\n }),\n });\n\n this.id = options.id ?? this.generateId();\n this.timeout = options.timeout ?? 300_000;\n this.language = options.language ?? 'typescript';\n this.resources = options.resources;\n this.env = options.env ?? {};\n this.labels = options.labels ?? {};\n this.snapshotId = options.snapshot;\n this.image = options.image;\n this.ephemeral = options.ephemeral ?? false;\n this.autoStopInterval = options.autoStopInterval ?? 15;\n this.autoArchiveInterval = options.autoArchiveInterval;\n this.autoDeleteInterval = options.autoDeleteInterval;\n this.volumeConfigs = options.volumes ?? [];\n this.sandboxName = options.name ?? this.id;\n this.sandboxUser = options.user;\n this.sandboxPublic = options.public;\n this.networkBlockAll = options.networkBlockAll;\n this.networkAllowList = options.networkAllowList;\n\n this.connectionOpts = {\n ...(options.apiKey !== undefined && { apiKey: options.apiKey }),\n ...(options.apiUrl !== undefined && { apiUrl: options.apiUrl }),\n ...(options.target !== undefined && { target: options.target }),\n };\n }\n\n private generateId(): string {\n return `daytona-sandbox-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n }\n\n /**\n * Get the underlying Daytona Sandbox instance for direct access to Daytona APIs.\n *\n * Use this when you need to access Daytona features not exposed through the\n * WorkspaceSandbox interface (e.g., filesystem API, git operations, LSP).\n *\n * @throws {SandboxNotReadyError} If the sandbox has not been started\n *\n * @example Direct file operations\n * ```typescript\n * const daytonaSandbox = sandbox.instance;\n * await daytonaSandbox.fs.uploadFile(Buffer.from('Hello'), '/tmp/test.txt');\n * ```\n */\n get instance(): Sandbox {\n if (!this._sandbox) {\n throw new SandboxNotReadyError(this.id);\n }\n return this._sandbox;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /**\n * Start the Daytona sandbox.\n * Reconnects to an existing sandbox with the same logical ID if one exists,\n * otherwise creates a new sandbox instance.\n */\n async start(): Promise<void> {\n if (this._sandbox) {\n return;\n }\n\n // Create Daytona client if not exists\n if (!this._daytona) {\n this._daytona = new Daytona(this.connectionOpts);\n }\n\n // Try to reconnect to an existing sandbox with the same logical ID\n const existing = await this.findExistingSandbox();\n if (existing) {\n this._sandbox = existing;\n this._createdAt = existing.createdAt ? new Date(existing.createdAt) : new Date();\n this.logger.debug(`${LOG_PREFIX} Reconnected to existing sandbox ${existing.id} for: ${this.id}`);\n await this.detectWorkingDir();\n return;\n }\n\n this.logger.debug(`${LOG_PREFIX} Creating sandbox for: ${this.id}`);\n\n // Base params shared by both creation modes\n const baseParams = compact({\n language: this.language,\n envVars: this.env,\n labels: { ...this.labels, 'mastra-sandbox-id': this.id },\n ephemeral: this.ephemeral,\n autoStopInterval: this.autoStopInterval,\n autoArchiveInterval: this.autoArchiveInterval,\n autoDeleteInterval: this.autoDeleteInterval,\n volumes: this.volumeConfigs.length > 0 ? this.volumeConfigs : undefined,\n name: this.sandboxName,\n user: this.sandboxUser,\n public: this.sandboxPublic,\n networkBlockAll: this.networkBlockAll,\n networkAllowList: this.networkAllowList,\n });\n\n // Snapshot takes precedence. Image alone (with optional resources) triggers image-based creation.\n // Resources without image fall back to snapshot-based creation (resources are ignored).\n if (this.resources && !this.image) {\n this.logger.warn(\n `${LOG_PREFIX} 'resources' option requires 'image' to take effect — falling back to snapshot-based creation without custom resources`,\n );\n }\n\n const createParams: CreateSandboxFromSnapshotParams | CreateSandboxFromImageParams =\n this.image && !this.snapshotId\n ? (compact({\n ...baseParams,\n image: this.image,\n resources: this.resources,\n }) satisfies CreateSandboxFromImageParams)\n : (compact({ ...baseParams, snapshot: this.snapshotId }) satisfies CreateSandboxFromSnapshotParams);\n\n // Create sandbox\n this._sandbox = await this._daytona.create(createParams);\n\n this.logger.debug(`${LOG_PREFIX} Created sandbox ${this._sandbox.id} for logical ID: ${this.id}`);\n this._createdAt = new Date();\n\n // Detect the actual working directory (don't hardcode — custom images may differ)\n await this.detectWorkingDir();\n }\n\n /**\n * Stop the Daytona sandbox.\n * Stops the sandbox instance and releases the reference.\n */\n async stop(): Promise<void> {\n if (this._sandbox && this._daytona) {\n try {\n await this._daytona.stop(this._sandbox);\n } catch {\n // Best-effort stop; sandbox may already be stopped\n }\n }\n this._sandbox = null;\n }\n\n /**\n * Destroy the Daytona sandbox and clean up all resources.\n * Deletes the sandbox and clears all state.\n */\n async destroy(): Promise<void> {\n if (this._sandbox && this._daytona) {\n try {\n await this._daytona.delete(this._sandbox);\n } catch {\n // Ignore errors during cleanup\n }\n } else if (!this._sandbox && this._daytona) {\n // Orphan cleanup: _start() may have failed after the SDK created\n // a server-side sandbox (e.g. bad image → BUILD_FAILED).\n // Try to find and delete it so it doesn't leak.\n try {\n const orphan = await this._daytona.findOne({ labels: { 'mastra-sandbox-id': this.id } });\n if (orphan) {\n await this._daytona.delete(orphan);\n }\n } catch {\n // Best-effort — orphan may not exist or may already be gone\n }\n }\n\n this._sandbox = null;\n this._daytona = null;\n this.mounts?.clear();\n }\n\n /**\n * Check if the sandbox is ready for operations.\n */\n async isReady(): Promise<boolean> {\n return this.status === 'running' && this._sandbox !== null;\n }\n\n /**\n * Get information about the current state of the sandbox.\n */\n async getInfo(): Promise<SandboxInfo> {\n return {\n id: this.id,\n name: this.name,\n provider: this.provider,\n status: this.status,\n createdAt: this._createdAt ?? new Date(),\n mounts: this.mounts\n ? Array.from(this.mounts.entries).map(([path, entry]) => ({\n path,\n filesystem: entry.filesystem?.provider ?? entry.config?.type ?? 'unknown',\n }))\n : [],\n ...(this._sandbox && {\n resources: {\n cpuCores: this._sandbox.cpu,\n memoryMB: this._sandbox.memory * 1024,\n diskMB: this._sandbox.disk * 1024,\n },\n }),\n metadata: {\n language: this.language,\n ephemeral: this.ephemeral,\n ...(this.snapshotId && { snapshot: this.snapshotId }),\n ...(this.image && { image: this.image }),\n ...(this._sandbox && { target: this._sandbox.target }),\n },\n };\n }\n\n /**\n * Get instructions describing this Daytona sandbox.\n * Used by agents to understand the execution environment.\n */\n getInstructions(): string {\n const parts: string[] = [];\n\n parts.push(`Cloud sandbox with isolated execution (${this.language} runtime).`);\n\n if (this._workingDir) {\n parts.push(`Default working directory: ${this._workingDir}.`);\n }\n parts.push(`Command timeout: ${Math.ceil(this.timeout / 1000)}s.`);\n\n parts.push(`Running as user: ${this.sandboxUser ?? 'daytona'}.`);\n\n if (this.volumeConfigs.length > 0) {\n parts.push(`${this.volumeConfigs.length} volume(s) attached.`);\n }\n\n if (this.networkBlockAll) {\n parts.push(`Network access is blocked.`);\n }\n\n return parts.join(' ');\n }\n\n // ---------------------------------------------------------------------------\n // Internal Helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Detect the actual working directory inside the sandbox via `pwd`.\n * Stores the result for use in `getInstructions()`.\n */\n private async detectWorkingDir(): Promise<void> {\n if (!this._sandbox) return;\n try {\n const result = await this._sandbox.process.executeCommand('pwd');\n const dir = result.result?.trim();\n if (dir) {\n this._workingDir = dir;\n this.logger.debug(`${LOG_PREFIX} Detected working directory: ${dir}`);\n }\n } catch {\n this.logger.debug(`${LOG_PREFIX} Could not detect working directory, will omit from instructions`);\n }\n }\n\n /**\n * Try to find and reconnect to an existing Daytona sandbox with the same\n * logical ID (via the mastra-sandbox-id label). Returns the sandbox if\n * found and usable, or null if a fresh sandbox should be created.\n */\n private async findExistingSandbox(): Promise<Sandbox | null> {\n const DEAD_STATES: SandboxState[] = [\n SandboxState.DESTROYED,\n SandboxState.DESTROYING,\n SandboxState.ERROR,\n SandboxState.BUILD_FAILED,\n ];\n\n try {\n const sandbox = await this._daytona!.findOne({ labels: { 'mastra-sandbox-id': this.id } });\n const state = sandbox.state;\n\n if (state && DEAD_STATES.includes(state)) {\n this.logger.debug(\n `${LOG_PREFIX} Existing sandbox ${sandbox.id} is dead (${state}), deleting and creating fresh`,\n );\n try {\n await this._daytona!.delete(sandbox);\n } catch {\n // Best-effort cleanup of dead sandbox\n }\n return null;\n }\n\n if (state !== SandboxState.STARTED) {\n this.logger.debug(`${LOG_PREFIX} Restarting sandbox ${sandbox.id} (state: ${state})`);\n await this._daytona!.start(sandbox);\n }\n\n return sandbox;\n } catch {\n // Not found or any error — create a fresh sandbox\n return null;\n }\n }\n\n /**\n * Check if an error indicates the sandbox is dead/gone.\n * Uses DaytonaNotFoundError from the SDK when available,\n * with string fallback for edge cases.\n *\n * String patterns observed in @daytonaio/sdk@0.143.0 error messages.\n * Update if SDK error messages change in future versions.\n */\n private isSandboxDeadError(error: unknown): boolean {\n if (!error) return false;\n if (error instanceof DaytonaNotFoundError) return true;\n const errorStr = String(error);\n return SANDBOX_DEAD_PATTERNS.some(pattern => pattern.test(errorStr));\n }\n\n /**\n * Handle sandbox timeout by clearing the instance and resetting state.\n */\n private handleSandboxTimeout(): void {\n this._sandbox = null;\n\n // Reset mounted entries to pending so they get re-mounted on restart\n if (this.mounts) {\n for (const [path, entry] of this.mounts.entries) {\n if (entry.state === 'mounted' || entry.state === 'mounting') {\n this.mounts.set(path, { state: 'pending' });\n }\n }\n }\n\n this.status = 'stopped';\n }\n\n // ---------------------------------------------------------------------------\n // Retry on Dead\n // ---------------------------------------------------------------------------\n\n /**\n * Execute a function, retrying once if the sandbox is found to be dead.\n * Used by DaytonaProcessManager to handle stale sandboxes transparently.\n */\n async retryOnDead<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n if (this.isSandboxDeadError(error) && !this._isRetrying) {\n this.handleSandboxTimeout();\n this._isRetrying = true;\n try {\n await this.ensureRunning();\n return await fn();\n } finally {\n this._isRetrying = false;\n }\n }\n throw error;\n }\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|