// ExeSandbox: a Mastra workspace sandbox that is an exe.dev VM of its own. // From https://exe.dev/deploy/mastra/exe-sandbox.ts // // It reaches exe.dev's API through an exe-dev integration, which holds the // API key off the VM: https://.int.exe.xyz (EXE_API, default // https://mastra-vms.int.exe.xyz). Off exe.dev, pass token instead. // The same id on the same machine always means the same VM, so a sandbox per // thread keeps its files, packages, and services until you delete it. import { createHash } from 'node:crypto'; import { hostname } from 'node:os'; import exedev, { quote, type VM } from '@exedev/sdk'; import { MastraSandbox, type CommandResult, type ExecuteCommandOptions, type SandboxInfo, } from '@mastra/core/workspace'; export interface ExeSandboxOptions { /** Logical id, such as a thread id. The VM's name is derived from it. */ id: string; /** exe-dev integration address. Default: EXE_API, or https://mastra-vms.int.exe.xyz. */ api?: string; /** An exe.dev API key, for use off exe.dev instead of an integration. */ token?: string; /** VM name prefix. Default: mastra. */ prefix?: string; /** Where commands run. Default: /home/exedev/work. */ workingDirectory?: string; /** Default command timeout, in milliseconds. */ timeout?: number; env?: Record; } export class ExeSandbox extends MastraSandbox { readonly id: string; readonly name = 'ExeSandbox'; readonly provider = 'exe.dev'; status: MastraSandbox['status'] = 'pending'; readonly vmName: string; readonly networking = { getPortUrl: async (port: number) => `https://${this.vmName}.exe.xyz:${port}/`, }; private exe; private vm?: VM; private timeout?: number; private createdAt = new Date(); constructor(opts: ExeSandboxOptions) { super({ name: 'ExeSandbox', env: opts.env, workingDirectory: opts.workingDirectory ?? '/home/exedev/work' }); this.id = opts.id; const hash = createHash('sha256').update(`${hostname()}\0${opts.id}`).digest('hex').slice(0, 12); this.vmName = `${opts.prefix ?? 'mastra'}-${hash}`; this.timeout = opts.timeout; const api = opts.api ?? process.env.EXE_API ?? 'https://mastra-vms.int.exe.xyz'; this.exe = opts.token ? exedev({ token: opts.token }) : exedev({ token: 'integration', baseURL: api }); } protected async find() { return (await this.exe.ls(this.vmName)).find((vm) => vm.name === this.vmName); } protected async connect(vm: VM) { this.vm = vm; } protected async create() { this.vm = await this.exe.new({ name: this.vmName, comment: `Mastra sandbox ${this.id}`.slice(0, 200), 'no-email': true }); } async executeCommand(command: string, args: string[] = [], options: ExecuteCommandOptions = {}): Promise { await this.ensureRunning(); const started = Date.now(); const env = { ...this.getEnv(), ...options.env }; const exports = Object.entries(env) .filter(([k, v]) => v !== undefined && /^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) .map(([k, v]) => `export ${k}=${quote(String(v))}; `) .join(''); const cwd = options.cwd ?? this.workingDirectory!; const line = args.length ? `${command} ${args.map(quote).join(' ')}` : command; const timeout = options.timeout ?? this.timeout; // The command goes on a line of its own, so a trailing & backgrounds only it. const body = `mkdir -p ${quote(cwd)} && cd ${quote(cwd)} || exit 1\n${exports}\n${line}`; const script = timeout ? `timeout -k 5 ${Math.ceil(timeout / 1000)} bash -c ${quote(body)}` : body; const r = await this.vm!.ssh(script, { onStdout: options.onStdout, onStderr: options.onStderr, signal: options.abortSignal, }); return { success: r.exitCode === 0, exitCode: r.exitCode, stdout: r.stdout, stderr: r.stderr, executionTimeMs: Date.now() - started, timedOut: Boolean(timeout) && r.exitCode === 124, command, args, }; } /** Deletes the VM and everything on it. */ async destroy() { const vm = this.vm ?? (await this.find()); this.vm = undefined; if (vm) await vm.rm(); } getInstructions() { return [ `Your commands run on ${this.vmName}, an exe.dev VM of your own: Ubuntu 24.04 with sudo, Docker, git, Python, uv, and Go.`, `Commands start in ${this.workingDirectory}, and files, packages, and services stay between turns.`, 'Run anything that must keep running, such as a web server, under systemd:', 'sudo systemd-run --unit=NAME --working-directory=DIR COMMAND, then check it with systemctl status NAME.', `A server on 0.0.0.0, on a port from 3000 to 9999, is at https://${this.vmName}.exe.xyz:PORT/, private to the VM's owner.`, ].join(' '); } getInfo(): SandboxInfo { return { id: this.id, name: this.name, provider: this.provider, status: this.status, createdAt: this.createdAt, metadata: { vm: this.vmName }, }; } }