@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
296 lines (194 loc) • 8.94 kB
Markdown
# SandboxProcessManager
**Added in:** `@mastra/core@1.7.0`
Abstract base class for managing background processes in sandboxes. Provides methods to spawn processes, list them, get handles by PID, and kill them.
[`BlaxelSandbox`](https://mastra.ai/reference/workspace/blaxel-sandbox), [`DaytonaSandbox`](https://mastra.ai/reference/workspace/daytona-sandbox), [`E2BSandbox`](https://mastra.ai/reference/workspace/e2b-sandbox), [`ModalSandbox`](https://mastra.ai/reference/workspace/modal-sandbox), and [`LocalSandbox`](https://mastra.ai/reference/workspace/local-sandbox) all include built-in process managers. You don't need to instantiate this class directly unless you're building a custom sandbox provider.
## Usage example
Access the process manager through the sandbox's `processes` property:
```typescript
import { LocalSandbox } from '@mastra/core/workspace'
const sandbox = new LocalSandbox({ workingDirectory: './workspace' })
await sandbox.start()
// Spawn a background process
const handle = await sandbox.processes.spawn('node server.js', {
env: { PORT: '3000' },
onStdout: data => console.log(data),
})
// List all tracked processes
const procs = await sandbox.processes.list()
// Get a handle by PID
const proc = await sandbox.processes.get(handle.pid)
// Kill a process
await sandbox.processes.kill(handle.pid)
```
## Methods
### `spawn(command, options?)`
Spawn a background process. Returns a `ProcessHandle` immediately without waiting for the process to finish.
```typescript
const handle = await sandbox.processes.spawn('npm run dev', {
cwd: '/app',
env: { NODE_ENV: 'development' },
onStdout: data => console.log(data),
})
```
**Parameters:**
**command** (`string`): The command to run. Interpreted by the shell.
**options** (`SpawnProcessOptions`): Optional settings for the spawned process.
**options.timeout** (`number`): Timeout in milliseconds. Kills the process if exceeded.
**options.env** (`NodeJS.ProcessEnv`): Environment variables for the process.
**options.cwd** (`string`): Working directory for the process.
**options.onStdout** (`(data: string) => void`): Callback for stdout chunks. Called as data arrives.
**options.onStderr** (`(data: string) => void`): Callback for stderr chunks. Called as data arrives.
**options.abortSignal** (`AbortSignal`): Signal to abort the process. When aborted, the process is killed.
**Returns:** `Promise<ProcessHandle>`
### `list()`
List all tracked processes. Returns info about each process including PID, running state, and exit code.
```typescript
const procs = await sandbox.processes.list()
for (const proc of procs) {
console.log(proc.pid, proc.running, proc.exitCode)
}
```
**Returns:** `Promise<ProcessInfo[]>`
### `get(pid)`
Get a handle to a process by PID. Returns `undefined` if the process isn't found or has already been dismissed.
```typescript
const handle = await sandbox.processes.get(1234)
if (handle) {
console.log(handle.stdout)
await handle.kill()
}
```
**Returns:** `Promise<ProcessHandle | undefined>`
### `kill(pid)`
Kill a process by PID. Waits for the process to terminate before returning. Returns `true` if the process was killed, `false` if it wasn't found.
```typescript
const killed = await sandbox.processes.kill(handle.pid)
```
**Returns:** `Promise<boolean>`
## `ProcessInfo`
Information about a tracked process, returned by `list()`.
**pid** (`number`): Process ID.
**command** (`string`): The command that was executed.
**running** (`boolean`): Whether the process is still running.
**exitCode** (`number`): Exit code if the process has finished.
***
## `ProcessHandle`
Handle to a spawned background process. Provides methods to read output, send stdin, wait for completion, and kill the process.
You don't create `ProcessHandle` instances directly — they're returned by `spawn()` and `get()`.
### Usage example
```typescript
const handle = await sandbox.processes.spawn('npm run dev', {
onStdout: data => console.log(data),
})
// Read accumulated output
console.log(handle.pid)
console.log(handle.stdout)
console.log(handle.stderr)
console.log(handle.exitCode) // undefined while running
// Wait for completion
const result = await handle.wait()
// Send stdin
await handle.sendStdin('input data\n')
// Kill the process
await handle.kill()
```
### Properties
**pid** (`number`): Process ID.
**stdout** (`string`): Accumulated stdout output so far.
**stderr** (`string`): Accumulated stderr output so far.
**exitCode** (`number | undefined`): Exit code. undefined while the process is still running.
**command** (`string | undefined`): The command that was spawned. Set automatically by the process manager.
**reader** (`Readable`): Readable stream of stdout. Useful for protocols like LSP or JSON-RPC that communicate over stdio.
**writer** (`Writable`): Writable stream to stdin. Useful for protocols like LSP or JSON-RPC that communicate over stdio.
### Methods
#### `wait(options?)`
Wait for the process to exit and return the result. Optionally pass `onStdout`/`onStderr` callbacks to stream output while waiting. Callbacks are automatically removed when `wait()` resolves.
```typescript
// Simple wait
const result = await handle.wait()
console.log(result.success, result.exitCode, result.stdout)
// Wait with streaming
const result = await handle.wait({
onStdout: data => process.stdout.write(data),
onStderr: data => process.stderr.write(data),
})
```
**Parameters:**
**options** (`WaitOptions`): Optional settings for waiting.
**options.onStdout** (`(data: string) => void`): Callback for stdout chunks while waiting.
**options.onStderr** (`(data: string) => void`): Callback for stderr chunks while waiting.
**Returns:** `Promise<CommandResult>`
The `CommandResult` object contains:
**success** (`boolean`): true if exit code is 0.
**exitCode** (`number`): Numeric exit code.
**stdout** (`string`): Full stdout output.
**stderr** (`string`): Full stderr output.
**executionTimeMs** (`number`): Execution time in milliseconds.
**timedOut** (`boolean`): true if the process was killed due to timeout.
**killed** (`boolean`): true if the process was killed by a signal.
#### `kill()`
Kill the process. Returns `true` if the process was killed, `false` if it had already exited.
```typescript
const killed = await handle.kill()
```
**Returns:** `Promise<boolean>`
#### `sendStdin(data)`
Send data to the process's stdin. Throws if the process has already exited or stdin isn't available.
```typescript
await handle.sendStdin('console.log("hello")\n')
```
**Returns:** `Promise<void>`
## Stream interop
`ProcessHandle` exposes `reader` and `writer` properties for integration with Node.js stream-based protocols like LSP or JSON-RPC:
```typescript
import {
createMessageConnection,
StreamMessageReader,
StreamMessageWriter,
} from 'vscode-jsonrpc/node'
const handle = await sandbox.processes.spawn('typescript-language-server --stdio')
const connection = createMessageConnection(
new StreamMessageReader(handle.reader),
new StreamMessageWriter(handle.writer),
)
connection.listen()
```
## Building a custom process manager
To build a process manager for a custom sandbox provider, extend `SandboxProcessManager` and implement `spawn()` and `list()`. The base class automatically wraps your methods with `ensureRunning()` so the sandbox starts before any process operation.
```typescript
import { SandboxProcessManager, ProcessHandle } from '@mastra/core/workspace'
import type { ProcessInfo, SpawnProcessOptions } from '@mastra/core/workspace'
class MyProcessManager extends SandboxProcessManager<MySandbox> {
async spawn(command: string, options: SpawnProcessOptions = {}): Promise<ProcessHandle> {
// Your spawn implementation
const handle = new MyProcessHandle(/* ... */)
this._tracked.set(handle.pid, handle)
return handle
}
async list(): Promise<ProcessInfo[]> {
return Array.from(this._tracked.values()).map(handle => ({
pid: handle.pid,
running: handle.exitCode === undefined,
exitCode: handle.exitCode,
}))
}
}
```
Pass the process manager to your sandbox via the `processes` option in `MastraSandbox`:
```typescript
class MySandbox extends MastraSandbox {
constructor() {
super({
name: 'MySandbox',
processes: new MyProcessManager(),
})
}
}
```
When a process manager is provided, `MastraSandbox` automatically creates a default `executeCommand` implementation that uses `spawn()` + `wait()`, so you don't need to implement both.
## Related
- [Sandbox](https://mastra.ai/docs/workspace/sandbox)
- [WorkspaceSandbox interface](https://mastra.ai/reference/workspace/sandbox)
- [LocalSandbox reference](https://mastra.ai/reference/workspace/local-sandbox)
- [E2BSandbox reference](https://mastra.ai/reference/workspace/e2b-sandbox)
- [DaytonaSandbox reference](https://mastra.ai/reference/workspace/daytona-sandbox)