Add AgentOS daemon control plane and maintenance tooling
This commit is contained in:
370
.agents/skills/sandbox-agent/SKILL.md
Normal file
370
.agents/skills/sandbox-agent/SKILL.md
Normal file
@@ -0,0 +1,370 @@
|
||||
---
|
||||
name: "sandbox-agent"
|
||||
description: "Deploy, configure, and integrate Sandbox Agent - a universal API for orchestrating AI coding agents (Claude Code, Codex, OpenCode, Amp) in sandboxed environments. Use when setting up sandbox-agent server locally or in cloud sandboxes (E2B, Daytona, Docker), creating and managing agent sessions via SDK or API, streaming agent events and handling human-in-the-loop interactions, building chat UIs for coding agents, or understanding the universal schema for agent responses."
|
||||
---
|
||||
|
||||
# Sandbox Agent
|
||||
|
||||
Sandbox Agent provides a universal API for orchestrating AI coding agents in sandboxed environments.
|
||||
|
||||
## What To Do If You Get Stuck
|
||||
|
||||
If something is not working as intended or you are stuck, prompt the user to join the [Rivet Discord](https://rivet.dev/discord) or file an issue on [GitHub](https://github.com/rivet-dev/sandbox-agent/issues) to report an issue and get help.
|
||||
|
||||
Provide the user with a pre-generated report with:
|
||||
|
||||
- Symptoms
|
||||
- If this is happening in local dev, deployed, or both
|
||||
- The error you're seeing
|
||||
- Relevant source code related to this
|
||||
- What you've tried to solve it
|
||||
- Environment
|
||||
- Sandbox Agent version
|
||||
- SDK in use (TypeScript, Python, or HTTP)
|
||||
- Agent in use (Claude Code, Codex, OpenCode, Amp, Mock)
|
||||
- Sandbox provider (E2B, Daytona, Docker, local)
|
||||
|
||||
## Quickstart
|
||||
|
||||
### Install skill (optional)
|
||||
|
||||
#### npx
|
||||
|
||||
```bash
|
||||
npx skills add rivet-dev/skills -s sandbox-agent
|
||||
```
|
||||
|
||||
#### bunx
|
||||
|
||||
```bash
|
||||
bunx skills add rivet-dev/skills -s sandbox-agent
|
||||
```
|
||||
|
||||
### Set environment variables
|
||||
|
||||
Each coding agent requires API keys to connect to their respective LLM providers.
|
||||
|
||||
#### Local shell
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
```
|
||||
|
||||
#### E2B
|
||||
|
||||
```typescript
|
||||
import { Sandbox } from "@e2b/code-interpreter";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sandbox = await Sandbox.create({ envs });
|
||||
```
|
||||
|
||||
#### Daytona
|
||||
|
||||
```typescript
|
||||
import { Daytona } from "@daytonaio/sdk";
|
||||
|
||||
const envVars: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const daytona = new Daytona();
|
||||
const sandbox = await daytona.create({
|
||||
snapshot: "sandbox-agent-ready",
|
||||
envVars,
|
||||
});
|
||||
```
|
||||
|
||||
#### Docker
|
||||
|
||||
```bash
|
||||
docker run -p 2468:2468 \
|
||||
-e ANTHROPIC_API_KEY="sk-ant-..." \
|
||||
-e OPENAI_API_KEY="sk-..." \
|
||||
rivetdev/sandbox-agent:0.4.2-full \
|
||||
server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### Extracting API keys from current machine
|
||||
|
||||
Use `sandbox-agent credentials extract-env --export` to extract your existing API keys (Anthropic, OpenAI, etc.) from local Claude Code or Codex config files.
|
||||
|
||||
#### Testing without API keys
|
||||
|
||||
Use the `mock` agent for SDK and integration testing without provider credentials.
|
||||
|
||||
#### Multi-tenant and per-user billing
|
||||
|
||||
For per-tenant token tracking, budget enforcement, or usage-based billing, see [LLM Credentials](/llm-credentials) for gateway options like OpenRouter, LiteLLM, and Portkey.
|
||||
|
||||
### Run the server
|
||||
|
||||
#### curl
|
||||
|
||||
Install and run the binary directly.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### npx
|
||||
|
||||
Run without installing globally.
|
||||
|
||||
```bash
|
||||
npx @sandbox-agent/cli@0.4.x server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### bunx
|
||||
|
||||
Run without installing globally.
|
||||
|
||||
```bash
|
||||
bunx @sandbox-agent/cli@0.4.x server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### npm i -g
|
||||
|
||||
Install globally, then run.
|
||||
|
||||
```bash
|
||||
npm install -g @sandbox-agent/cli@0.4.x
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### bun add -g
|
||||
|
||||
Install globally, then run.
|
||||
|
||||
```bash
|
||||
bun add -g @sandbox-agent/cli@0.4.x
|
||||
# Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()).
|
||||
bun pm -g trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-linux-arm64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### Node.js (local)
|
||||
|
||||
For local development, use `SandboxAgent.start()` to spawn and manage the server as a subprocess.
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.start();
|
||||
```
|
||||
|
||||
#### Bun (local)
|
||||
|
||||
For local development, use `SandboxAgent.start()` to spawn and manage the server as a subprocess.
|
||||
|
||||
```bash
|
||||
bun add sandbox-agent@0.4.x
|
||||
# Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()).
|
||||
bun pm trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-linux-arm64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.start();
|
||||
```
|
||||
|
||||
#### Build from source
|
||||
|
||||
If you're running from source instead of the installed CLI.
|
||||
|
||||
```bash
|
||||
cargo run -p sandbox-agent -- server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
Binding to `0.0.0.0` allows the server to accept connections from any network interface, which is required when running inside a sandbox where clients connect remotely.
|
||||
|
||||
#### Configuring token
|
||||
|
||||
Tokens are usually not required. Most sandbox providers (E2B, Daytona, etc.) already secure networking at the infrastructure layer.
|
||||
|
||||
If you expose the server publicly, use `--token "$SANDBOX_TOKEN"` to require authentication:
|
||||
|
||||
```bash
|
||||
sandbox-agent server --token "$SANDBOX_TOKEN" --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
Then pass the token when connecting:
|
||||
|
||||
#### TypeScript
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://your-server:2468",
|
||||
token: process.env.SANDBOX_TOKEN,
|
||||
});
|
||||
```
|
||||
|
||||
#### curl
|
||||
|
||||
```bash
|
||||
curl "http://your-server:2468/v1/health" \
|
||||
-H "Authorization: Bearer $SANDBOX_TOKEN"
|
||||
```
|
||||
|
||||
#### CLI
|
||||
|
||||
```bash
|
||||
sandbox-agent --token "$SANDBOX_TOKEN" api agents list \
|
||||
--endpoint http://your-server:2468
|
||||
```
|
||||
|
||||
#### CORS
|
||||
|
||||
If you're calling the server from a browser, see the [CORS configuration guide](/cors).
|
||||
|
||||
### Install agents (optional)
|
||||
|
||||
To preinstall agents:
|
||||
|
||||
```bash
|
||||
sandbox-agent install-agent --all
|
||||
```
|
||||
|
||||
If agents are not installed up front, they are lazily installed when creating a session.
|
||||
|
||||
### Install desktop dependencies (optional, Linux only)
|
||||
|
||||
If you want to use `/v1/desktop/*`, install the desktop runtime packages first:
|
||||
|
||||
```bash
|
||||
sandbox-agent install desktop --yes
|
||||
```
|
||||
|
||||
Then use `GET /v1/desktop/status` or `sdk.getDesktopStatus()` to verify the runtime is ready before calling desktop screenshot or input APIs.
|
||||
|
||||
### Create a session
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: {
|
||||
cwd: "/",
|
||||
mcpServers: [],
|
||||
},
|
||||
});
|
||||
|
||||
console.log(session.id);
|
||||
```
|
||||
|
||||
### Send a message
|
||||
|
||||
```typescript
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
### Read events
|
||||
|
||||
```typescript
|
||||
const off = session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const page = await sdk.getEvents({
|
||||
sessionId: session.id,
|
||||
limit: 50,
|
||||
});
|
||||
|
||||
console.log(page.items.length);
|
||||
off();
|
||||
```
|
||||
|
||||
### Test with Inspector
|
||||
|
||||
Open the Inspector UI at `/ui/` on your server (for example, `http://localhost:2468/ui/`) to inspect sessions and events in a GUI.
|
||||
|
||||

|
||||
|
||||
## Next steps
|
||||
|
||||
- [Session Persistence](/session-persistence) — Configure in-memory, Rivet Actor state, IndexedDB, SQLite, and Postgres persistence.
|
||||
|
||||
- [Deploy to a Sandbox](/deploy/local) — Deploy your agent to E2B, Daytona, Docker, Vercel, or Cloudflare.
|
||||
|
||||
- [SDK Overview](/sdk-overview) — Use the latest TypeScript SDK API.
|
||||
|
||||
## Reference Map
|
||||
|
||||
### Agents
|
||||
|
||||
- [Amp](references/agents/amp.md)
|
||||
- [Claude](references/agents/claude.md)
|
||||
- [Codex](references/agents/codex.md)
|
||||
- [Cursor](references/agents/cursor.md)
|
||||
- [OpenCode](references/agents/opencode.md)
|
||||
- [Pi](references/agents/pi.md)
|
||||
|
||||
### AI
|
||||
|
||||
- [llms.txt](references/ai/llms-txt.md)
|
||||
- [skill.md](references/ai/skill.md)
|
||||
|
||||
### Deploy
|
||||
|
||||
- [Agent Computer](references/deploy/agentcomputer.md)
|
||||
- [BoxLite](references/deploy/boxlite.md)
|
||||
- [Cloudflare](references/deploy/cloudflare.md)
|
||||
- [ComputeSDK](references/deploy/computesdk.md)
|
||||
- [Daytona](references/deploy/daytona.md)
|
||||
- [Docker](references/deploy/docker.md)
|
||||
- [E2B](references/deploy/e2b.md)
|
||||
- [Local](references/deploy/local.md)
|
||||
- [Modal](references/deploy/modal.md)
|
||||
- [Vercel](references/deploy/vercel.md)
|
||||
|
||||
### General
|
||||
|
||||
- [Agent Sessions](references/agent-sessions.md)
|
||||
- [Architecture](references/architecture.md)
|
||||
- [Attachments](references/attachments.md)
|
||||
- [CLI Reference](references/cli.md)
|
||||
- [Common Software](references/common-software.md)
|
||||
- [Computer Use](references/computer-use.md)
|
||||
- [CORS Configuration](references/cors.md)
|
||||
- [Custom Tools](references/custom-tools.md)
|
||||
- [Daemon](references/daemon.md)
|
||||
- [File System](references/file-system.md)
|
||||
- [Inspector](references/inspector.md)
|
||||
- [LLM Credentials](references/llm-credentials.md)
|
||||
- [Manage Sessions](references/manage-sessions.md)
|
||||
- [MCP](references/mcp-config.md)
|
||||
- [Multiplayer](references/multiplayer.md)
|
||||
- [Observability](references/observability.md)
|
||||
- [OpenCode Compatibility](references/opencode-compatibility.md)
|
||||
- [Orchestration Architecture](references/orchestration-architecture.md)
|
||||
- [Persisting Sessions](references/session-persistence.md)
|
||||
- [Processes](references/processes.md)
|
||||
- [Quickstart](references/quickstart.md)
|
||||
- [React Components](references/react-components.md)
|
||||
- [SDK Overview](references/sdk-overview.md)
|
||||
- [Security](references/security.md)
|
||||
- [Session Restoration](references/session-restoration.md)
|
||||
- [Skills](references/skills-config.md)
|
||||
- [Telemetry](references/telemetry.md)
|
||||
- [Troubleshooting](references/troubleshooting.md)
|
||||
263
.agents/skills/sandbox-agent/references/agent-sessions.md
Normal file
263
.agents/skills/sandbox-agent/references/agent-sessions.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Agent Sessions
|
||||
|
||||
> Source: `docs/agent-sessions.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/agent-sessions
|
||||
> Description: Create sessions, prompt agents, and inspect event history.
|
||||
|
||||
---
|
||||
Sessions are the unit of interaction with an agent. Create one session per task, send prompts, and consume event history.
|
||||
|
||||
For SDK-based flows, sessions can be restored after runtime/session loss when persistence is enabled.
|
||||
See [Session Restoration](/session-restoration).
|
||||
|
||||
## Create a session
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "codex",
|
||||
cwd: "/",
|
||||
});
|
||||
|
||||
console.log(session.id, session.agentSessionId);
|
||||
```
|
||||
|
||||
## Send a prompt
|
||||
|
||||
```ts
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository structure." },
|
||||
]);
|
||||
|
||||
console.log(response.stopReason);
|
||||
```
|
||||
|
||||
## Subscribe to live events
|
||||
|
||||
```ts
|
||||
const unsubscribe = session.onEvent((event) => {
|
||||
console.log(event.eventIndex, event.sender, event.payload);
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Explain the main entrypoints." },
|
||||
]);
|
||||
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
### Event types
|
||||
|
||||
Each event's `payload` contains a session update. The `sessionUpdate` field identifies the type.
|
||||
|
||||
#### agent_message_chunk
|
||||
|
||||
Streamed text or content from the agent's response.
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "agent_message_chunk",
|
||||
"content": { "type": "text", "text": "Here's how the repository is structured..." }
|
||||
}
|
||||
```
|
||||
|
||||
#### agent_thought_chunk
|
||||
|
||||
Internal reasoning from the agent (chain-of-thought / extended thinking).
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "agent_thought_chunk",
|
||||
"content": { "type": "text", "text": "I should start by looking at the project structure..." }
|
||||
}
|
||||
```
|
||||
|
||||
#### user_message_chunk
|
||||
|
||||
Echo of the user's prompt being processed.
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "user_message_chunk",
|
||||
"content": { "type": "text", "text": "Summarize the repository structure." }
|
||||
}
|
||||
```
|
||||
|
||||
#### tool_call
|
||||
|
||||
The agent invoked a tool (file edit, terminal command, etc.).
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "tool_call",
|
||||
"toolCallId": "tc_abc123",
|
||||
"title": "Read file",
|
||||
"status": "in_progress",
|
||||
"rawInput": { "path": "/src/index.ts" }
|
||||
}
|
||||
```
|
||||
|
||||
#### tool_call_update
|
||||
|
||||
Progress or result update for an in-progress tool call.
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "tool_call_update",
|
||||
"toolCallId": "tc_abc123",
|
||||
"status": "completed",
|
||||
"content": [{ "type": "text", "text": "import express from 'express';\n..." }]
|
||||
}
|
||||
```
|
||||
|
||||
#### plan
|
||||
|
||||
The agent's execution plan for the current task.
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "plan",
|
||||
"entries": [
|
||||
{ "content": "Read the project structure", "status": "completed" },
|
||||
{ "content": "Identify main entrypoints", "status": "in_progress" },
|
||||
{ "content": "Write summary", "status": "pending" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### usage_update
|
||||
|
||||
Token usage metrics for the current turn.
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "usage_update"
|
||||
}
|
||||
```
|
||||
|
||||
#### session_info_update
|
||||
|
||||
Session metadata changed (e.g. agent-generated title).
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionUpdate": "session_info_update",
|
||||
"title": "Repository structure analysis"
|
||||
}
|
||||
```
|
||||
|
||||
## Fetch persisted event history
|
||||
|
||||
```ts
|
||||
const page = await sdk.getEvents({
|
||||
sessionId: session.id,
|
||||
limit: 50,
|
||||
});
|
||||
|
||||
for (const event of page.items) {
|
||||
console.log(event.id, event.createdAt, event.sender);
|
||||
}
|
||||
```
|
||||
|
||||
## List and load sessions
|
||||
|
||||
```ts
|
||||
const sessions = await sdk.listSessions({ limit: 20 });
|
||||
|
||||
for (const item of sessions.items) {
|
||||
console.log(item.id, item.agent, item.createdAt);
|
||||
}
|
||||
|
||||
if (sessions.items.length > 0) {
|
||||
const loaded = await sdk.resumeSession(sessions.items[0]!.id);
|
||||
await loaded.prompt([{ type: "text", text: "Continue." }]);
|
||||
}
|
||||
```
|
||||
|
||||
## Configure model, mode, and thought level
|
||||
|
||||
Set the model, mode, or thought level on a session at creation time or after:
|
||||
|
||||
```ts
|
||||
// At creation time
|
||||
const session = await sdk.createSession({
|
||||
agent: "codex",
|
||||
model: "gpt-5.3-codex",
|
||||
mode: "auto",
|
||||
thoughtLevel: "high",
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// After creation
|
||||
await session.setModel("gpt-5.2-codex");
|
||||
await session.setMode("full-access");
|
||||
await session.setThoughtLevel("medium");
|
||||
```
|
||||
|
||||
Query available modes:
|
||||
|
||||
```ts
|
||||
const modes = await session.getModes();
|
||||
console.log(modes?.currentModeId, modes?.availableModes);
|
||||
```
|
||||
|
||||
### Advanced config options
|
||||
|
||||
For config options beyond model, mode, and thought level, use `getConfigOptions` to discover what the agent supports and `setConfigOption` to set any option by ID:
|
||||
|
||||
```ts
|
||||
const options = await session.getConfigOptions();
|
||||
for (const opt of options) {
|
||||
console.log(opt.id, opt.category, opt.type);
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
await session.setConfigOption("some-agent-option", "value");
|
||||
```
|
||||
|
||||
## Handle permission requests
|
||||
|
||||
For agents that request tool-use permissions, register a permission listener and reply with `once`, `always`, or `reject`:
|
||||
|
||||
```ts
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
mode: "default",
|
||||
});
|
||||
|
||||
session.onPermissionRequest((request) => {
|
||||
console.log(request.toolCall.title, request.availableReplies);
|
||||
void session.respondPermission(request.id, "once");
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Create ./permission-example.txt with the text hello." },
|
||||
]);
|
||||
```
|
||||
|
||||
### Auto-approving permissions
|
||||
|
||||
To auto-approve all permission requests, respond with `"once"` or `"always"` in your listener:
|
||||
|
||||
```ts
|
||||
session.onPermissionRequest((request) => {
|
||||
void session.respondPermission(request.id, "always");
|
||||
});
|
||||
```
|
||||
|
||||
See `examples/permissions/src/index.ts` for a complete permissions example that works with Claude and Codex.
|
||||
|
||||
Some agents like Claude allow configuring permission behavior through modes (e.g. `bypassPermissions`, `acceptEdits`). We recommend leaving the mode as `default` and handling permission decisions explicitly in `onPermissionRequest` instead.
|
||||
|
||||
## Destroy a session
|
||||
|
||||
```ts
|
||||
await sdk.destroySession(session.id);
|
||||
```
|
||||
22
.agents/skills/sandbox-agent/references/agents/amp.md
Normal file
22
.agents/skills/sandbox-agent/references/agents/amp.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Amp
|
||||
|
||||
> Source: `docs/agents/amp.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/agents/amp
|
||||
> Description: Use Amp as a sandbox agent.
|
||||
|
||||
---
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "amp",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `amp-default` |
|
||||
| **Modes** | `default`, `bypass` |
|
||||
| **Thought levels** | Unsupported |
|
||||
49
.agents/skills/sandbox-agent/references/agents/claude.md
Normal file
49
.agents/skills/sandbox-agent/references/agents/claude.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Claude
|
||||
|
||||
> Source: `docs/agents/claude.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/agents/claude
|
||||
> Description: Use Claude Code as a sandbox agent.
|
||||
|
||||
---
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "claude",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `default`, `sonnet`, `opus`, `haiku` |
|
||||
| **Modes** | `default`, `acceptEdits`, `plan`, `dontAsk`, `bypassPermissions` |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
## Configuring effort level
|
||||
|
||||
Claude does not support changing effort level after a session starts. Configure it in the filesystem before creating the session.
|
||||
|
||||
```ts
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const cwd = "/path/to/workspace";
|
||||
await mkdir(path.join(cwd, ".claude"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(cwd, ".claude", "settings.json"),
|
||||
JSON.stringify({ effortLevel: "high" }, null, 2),
|
||||
);
|
||||
|
||||
const session = await client.createSession({
|
||||
agent: "claude",
|
||||
cwd,
|
||||
});
|
||||
```
|
||||
|
||||
#### Supported settings file locations (highest precedence last)
|
||||
|
||||
1. `~/.claude/settings.json`
|
||||
2. `<session cwd>/.claude/settings.json`
|
||||
3. `<session cwd>/.claude/settings.local.json`
|
||||
22
.agents/skills/sandbox-agent/references/agents/codex.md
Normal file
22
.agents/skills/sandbox-agent/references/agents/codex.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Codex
|
||||
|
||||
> Source: `docs/agents/codex.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/agents/codex
|
||||
> Description: Use OpenAI Codex as a sandbox agent.
|
||||
|
||||
---
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "codex",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `gpt-5.3-codex` (default), `gpt-5.3-codex-spark`, `gpt-5.2-codex`, `gpt-5.1-codex-max`, `gpt-5.2`, `gpt-5.1-codex-mini` |
|
||||
| **Modes** | `read-only` (default), `auto`, `full-access` |
|
||||
| **Thought levels** | `low`, `medium`, `high` (default), `xhigh` |
|
||||
34
.agents/skills/sandbox-agent/references/agents/cursor.md
Normal file
34
.agents/skills/sandbox-agent/references/agents/cursor.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Cursor
|
||||
|
||||
> Source: `docs/agents/cursor.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/agents/cursor
|
||||
> Description: Use Cursor as a sandbox agent.
|
||||
|
||||
---
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "cursor",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | See below |
|
||||
| **Modes** | Unsupported |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
#### All models
|
||||
|
||||
| Group | Models |
|
||||
|-------|--------|
|
||||
| **Auto** | `auto` |
|
||||
| **Composer** | `composer-1.5`, `composer-1` |
|
||||
| **GPT-5.3 Codex** | `gpt-5.3-codex`, `gpt-5.3-codex-low`, `gpt-5.3-codex-high`, `gpt-5.3-codex-xhigh`, `gpt-5.3-codex-fast`, `gpt-5.3-codex-low-fast`, `gpt-5.3-codex-high-fast`, `gpt-5.3-codex-xhigh-fast` |
|
||||
| **GPT-5.2** | `gpt-5.2`, `gpt-5.2-high`, `gpt-5.2-codex`, `gpt-5.2-codex-low`, `gpt-5.2-codex-high`, `gpt-5.2-codex-xhigh`, `gpt-5.2-codex-fast`, `gpt-5.2-codex-low-fast`, `gpt-5.2-codex-high-fast`, `gpt-5.2-codex-xhigh-fast` |
|
||||
| **GPT-5.1** | `gpt-5.1-high`, `gpt-5.1-codex-max`, `gpt-5.1-codex-max-high` |
|
||||
| **Claude** | `opus-4.6-thinking` (default), `opus-4.6`, `opus-4.5`, `opus-4.5-thinking`, `sonnet-4.5`, `sonnet-4.5-thinking` |
|
||||
| **Other** | `gemini-3-pro`, `gemini-3-flash`, `grok` |
|
||||
31
.agents/skills/sandbox-agent/references/agents/opencode.md
Normal file
31
.agents/skills/sandbox-agent/references/agents/opencode.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# OpenCode
|
||||
|
||||
> Source: `docs/agents/opencode.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/agents/opencode
|
||||
> Description: Use OpenCode as a sandbox agent.
|
||||
|
||||
---
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "opencode",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | See below |
|
||||
| **Modes** | `build` (default), `plan` |
|
||||
| **Thought levels** | Unsupported |
|
||||
|
||||
#### All models
|
||||
|
||||
| Provider | Models |
|
||||
|----------|--------|
|
||||
| **Anthropic** | `anthropic/claude-3-5-haiku-20241022`, `anthropic/claude-3-5-haiku-latest`, `anthropic/claude-3-5-sonnet-20240620`, `anthropic/claude-3-5-sonnet-20241022`, `anthropic/claude-3-7-sonnet-20250219`, `anthropic/claude-3-7-sonnet-latest`, `anthropic/claude-3-haiku-20240307`, `anthropic/claude-3-opus-20240229`, `anthropic/claude-3-sonnet-20240229`, `anthropic/claude-haiku-4-5`, `anthropic/claude-haiku-4-5-20251001`, `anthropic/claude-opus-4-0`, `anthropic/claude-opus-4-1`, `anthropic/claude-opus-4-1-20250805`, `anthropic/claude-opus-4-20250514`, `anthropic/claude-opus-4-5`, `anthropic/claude-opus-4-5-20251101`, `anthropic/claude-opus-4-6`, `anthropic/claude-sonnet-4-0`, `anthropic/claude-sonnet-4-20250514`, `anthropic/claude-sonnet-4-5`, `anthropic/claude-sonnet-4-5-20250929` |
|
||||
| **OpenAI** | `openai/gpt-5.1-codex`, `openai/gpt-5.1-codex-max`, `openai/gpt-5.1-codex-mini`, `openai/gpt-5.2`, `openai/gpt-5.2-codex`, `openai/gpt-5.3-codex` |
|
||||
| **Cerebras** | `cerebras/gpt-oss-120b`, `cerebras/qwen-3-235b-a22b-instruct-2507`, `cerebras/zai-glm-4.7` |
|
||||
| **OpenCode Zen** | `opencode/big-pickle`, `opencode/claude-3-5-haiku`, `opencode/claude-haiku-4-5`, `opencode/claude-opus-4-1`, `opencode/claude-opus-4-5`, `opencode/claude-opus-4-6`, `opencode/claude-sonnet-4`, `opencode/claude-sonnet-4-5`, `opencode/gemini-3-flash`, `opencode/gemini-3-pro` (default), `opencode/glm-4.6`, `opencode/glm-4.7`, `opencode/gpt-5`, `opencode/gpt-5-codex`, `opencode/gpt-5-nano`, `opencode/gpt-5.1`, `opencode/gpt-5.1-codex`, `opencode/gpt-5.1-codex-max`, `opencode/gpt-5.1-codex-mini`, `opencode/gpt-5.2`, `opencode/gpt-5.2-codex`, `opencode/kimi-k2`, `opencode/kimi-k2-thinking`, `opencode/kimi-k2.5`, `opencode/kimi-k2.5-free`, `opencode/minimax-m2.1`, `opencode/minimax-m2.1-free`, `opencode/trinity-large-preview-free` |
|
||||
22
.agents/skills/sandbox-agent/references/agents/pi.md
Normal file
22
.agents/skills/sandbox-agent/references/agents/pi.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Pi
|
||||
|
||||
> Source: `docs/agents/pi.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/agents/pi
|
||||
> Description: Use Pi as a sandbox agent.
|
||||
|
||||
---
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
const session = await client.createSession({
|
||||
agent: "pi",
|
||||
});
|
||||
```
|
||||
|
||||
## Capabilities
|
||||
|
||||
| Category | Values |
|
||||
|----------|--------|
|
||||
| **Models** | `default` |
|
||||
| **Modes** | Unsupported |
|
||||
| **Thought levels** | Unsupported |
|
||||
17
.agents/skills/sandbox-agent/references/ai/llms-txt.md
Normal file
17
.agents/skills/sandbox-agent/references/ai/llms-txt.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# llms.txt
|
||||
|
||||
> Source: `docs/ai/llms-txt.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/ai/llms-txt
|
||||
> Description: LLM-friendly documentation manifests.
|
||||
|
||||
---
|
||||
Mintlify publishes `llms.txt` and `llms-full.txt` for this documentation site.
|
||||
|
||||
Access them at:
|
||||
|
||||
```
|
||||
https://sandboxagent.dev/docs/llms.txt
|
||||
https://sandboxagent.dev/docs/llms-full.txt
|
||||
```
|
||||
|
||||
If you run a reverse proxy in front of the docs, forward `/llms.txt` and `/llms-full.txt` to Mintlify.
|
||||
31
.agents/skills/sandbox-agent/references/ai/skill.md
Normal file
31
.agents/skills/sandbox-agent/references/ai/skill.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# skill.md
|
||||
|
||||
> Source: `docs/ai/skill.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/ai/skill
|
||||
> Description: Agent skill manifest for this documentation.
|
||||
|
||||
---
|
||||
Mintlify hosts a `skill.md` file for this documentation site.
|
||||
|
||||
Access it at:
|
||||
|
||||
```
|
||||
https://sandboxagent.dev/docs/skill.md
|
||||
```
|
||||
|
||||
To add it to an agent using the Skills CLI:
|
||||
|
||||
#### npx
|
||||
|
||||
```bash
|
||||
npx skills add rivet-dev/skills -s sandbox-agent
|
||||
```
|
||||
|
||||
#### bunx
|
||||
|
||||
```bash
|
||||
bunx skills add rivet-dev/skills -s sandbox-agent
|
||||
```
|
||||
|
||||
If you run a reverse proxy in front of the docs, make sure `/skill.md` and `/.well-known/skills/*`
|
||||
are forwarded to Mintlify.
|
||||
65
.agents/skills/sandbox-agent/references/architecture.md
Normal file
65
.agents/skills/sandbox-agent/references/architecture.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Architecture
|
||||
|
||||
> Source: `docs/architecture.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/architecture
|
||||
> Description: How the Sandbox Agent server, SDK, and agent processes fit together.
|
||||
|
||||
---
|
||||
Sandbox Agent is a lightweight HTTP server that runs **inside** a sandbox. It:
|
||||
|
||||
- **Agent management**: Installs, spawns, and stops coding agent processes
|
||||
- **Sessions**: Routes prompts to agents and streams events back in real time
|
||||
- **Sandbox APIs**: Filesystem, process, and terminal access for the sandbox environment
|
||||
|
||||
## Components
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CLIENT["Your App"]
|
||||
|
||||
subgraph SANDBOX["Sandbox"]
|
||||
direction TB
|
||||
SERVER["Sandbox Agent Server"]
|
||||
AGENT["Agent Process<br/>(Claude, Codex, etc.)"]
|
||||
SERVER --> AGENT
|
||||
end
|
||||
|
||||
CLIENT -->|"SDK (HTTP)"| SERVER
|
||||
```
|
||||
|
||||
- **Your app**: Uses the `sandbox-agent` TypeScript SDK to talk to the server over HTTP.
|
||||
- **Sandbox**: An isolated runtime (local process, Docker, E2B, Daytona, Vercel, Cloudflare).
|
||||
- **Sandbox Agent server**: A single binary inside the sandbox that manages agent lifecycles, routes prompts, streams events, and exposes filesystem/process/terminal APIs.
|
||||
- **Agent process**: A coding agent (Claude Code, Codex, etc.) spawned by the server. Each session maps to one agent process.
|
||||
|
||||
## What `SandboxAgent.start()` does
|
||||
|
||||
1. **Provision**: The provider creates a sandbox (starts a container, creates a VM, etc.)
|
||||
2. **Install**: The Sandbox Agent binary is installed inside the sandbox
|
||||
3. **Boot**: The server starts listening on an HTTP port
|
||||
4. **Health check**: The SDK waits for `/v1/health` to respond
|
||||
5. **Ready**: The SDK returns a connected client
|
||||
|
||||
For the `local` provider, provisioning is a no-op and the server runs as a local subprocess.
|
||||
|
||||
### Server recovery
|
||||
|
||||
If the server process stops, the SDK automatically calls the provider's `ensureServer()` after 3 consecutive health-check failures. Most built-in providers implement this. Custom providers can add `ensureServer(sandboxId)` to their `SandboxProvider` object.
|
||||
|
||||
## Server HTTP API
|
||||
|
||||
See the [HTTP API reference](/api-reference) for the full list of server endpoints.
|
||||
|
||||
## Agent installation
|
||||
|
||||
Agents are installed lazily on first use. To avoid the cold-start delay, pre-install them:
|
||||
|
||||
```bash
|
||||
sandbox-agent install-agent --all
|
||||
```
|
||||
|
||||
The `rivetdev/sandbox-agent:0.4.2-full` Docker image ships with all agents pre-installed.
|
||||
|
||||
## Production-ready agent orchestration
|
||||
|
||||
For production deployments, see [Orchestration Architecture](/orchestration-architecture) for recommended topology, backend requirements, and session persistence patterns.
|
||||
57
.agents/skills/sandbox-agent/references/attachments.md
Normal file
57
.agents/skills/sandbox-agent/references/attachments.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Attachments
|
||||
|
||||
> Source: `docs/attachments.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/attachments
|
||||
> Description: Upload files into the sandbox and reference them in prompts.
|
||||
|
||||
---
|
||||
Use the filesystem API to upload files, then include file references in prompt content.
|
||||
|
||||
### Upload a file
|
||||
|
||||
```ts TypeScript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import fs from "node:fs";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const buffer = await fs.promises.readFile("./data.csv");
|
||||
|
||||
const upload = await sdk.writeFsFile(
|
||||
{ path: "./uploads/data.csv" },
|
||||
buffer,
|
||||
);
|
||||
|
||||
console.log(upload.path);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=./uploads/data.csv" \
|
||||
--data-binary @./data.csv
|
||||
```
|
||||
|
||||
The upload response returns the absolute path.
|
||||
|
||||
### Reference the file in a prompt
|
||||
|
||||
```ts TypeScript
|
||||
const session = await sdk.createSession({ agent: "mock" });
|
||||
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Please analyze the attached CSV." },
|
||||
{
|
||||
type: "resource_link",
|
||||
name: "data.csv",
|
||||
uri: "file:///home/sandbox/uploads/data.csv",
|
||||
mimeType: "text/csv",
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Use absolute file URIs in `resource_link` blocks.
|
||||
- If `mimeType` is omitted, the agent/runtime may infer a default.
|
||||
- Support for non-text resources depends on each agent's prompt capabilities.
|
||||
299
.agents/skills/sandbox-agent/references/cli.md
Normal file
299
.agents/skills/sandbox-agent/references/cli.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# CLI Reference
|
||||
|
||||
> Source: `docs/cli.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/cli
|
||||
> Description: CLI reference for sandbox-agent.
|
||||
|
||||
---
|
||||
Global flags (available on all commands):
|
||||
|
||||
- `-t, --token `: require/use bearer auth
|
||||
- `-n, --no-token`: disable auth
|
||||
|
||||
## server
|
||||
|
||||
Run the HTTP server.
|
||||
|
||||
```bash
|
||||
sandbox-agent server [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-H, --host ` | `127.0.0.1` | Host to bind |
|
||||
| `-p, --port ` | `2468` | Port to bind |
|
||||
| `-O, --cors-allow-origin ` | - | Allowed CORS origin (repeatable) |
|
||||
| `-M, --cors-allow-method ` | all | Allowed CORS method (repeatable) |
|
||||
| `-A, --cors-allow-header ` | all | Allowed CORS header (repeatable) |
|
||||
| `-C, --cors-allow-credentials` | false | Enable CORS credentials |
|
||||
| `--no-telemetry` | false | Disable anonymous telemetry |
|
||||
|
||||
```bash
|
||||
sandbox-agent server --port 3000
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Server logs are redirected to files by default.
|
||||
- Set `SANDBOX_AGENT_LOG_STDOUT=1` to force stdout/stderr logging.
|
||||
- Use `SANDBOX_AGENT_LOG_DIR` to override log directory.
|
||||
|
||||
## install
|
||||
|
||||
Install first-party runtime dependencies.
|
||||
|
||||
### install desktop
|
||||
|
||||
Install the Linux desktop runtime packages required by `/v1/desktop/*`.
|
||||
|
||||
```bash
|
||||
sandbox-agent install desktop [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--yes` | Skip the confirmation prompt |
|
||||
| `--print-only` | Print the package-manager command without executing it |
|
||||
| `--package-manager <apt\|dnf\|apk>` | Override package-manager detection |
|
||||
| `--no-fonts` | Skip the default DejaVu font package |
|
||||
|
||||
```bash
|
||||
sandbox-agent install desktop --yes
|
||||
sandbox-agent install desktop --print-only
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Supported on Linux only.
|
||||
- The command detects `apt`, `dnf`, or `apk`.
|
||||
- If the host is not already running as root, the command requires `sudo`.
|
||||
|
||||
## install-agent
|
||||
|
||||
Install or reinstall a single agent, or every supported agent with `--all`.
|
||||
|
||||
```bash
|
||||
sandbox-agent install-agent [<AGENT>] [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--all` | Install every supported agent |
|
||||
| `-r, --reinstall` | Force reinstall |
|
||||
| `--agent-version ` | Override agent package version (conflicts with `--all`) |
|
||||
| `--agent-process-version ` | Override agent process version (conflicts with `--all`) |
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
sandbox-agent install-agent claude --reinstall
|
||||
sandbox-agent install-agent --all
|
||||
```
|
||||
|
||||
### Custom Pi implementation path
|
||||
|
||||
If you use a forked/custom `pi` binary with `pi-acp`, you can override what executable gets launched.
|
||||
|
||||
#### Option 1: explicit command override (recommended)
|
||||
|
||||
Set `PI_ACP_PI_COMMAND` in the environment where `sandbox-agent` runs:
|
||||
|
||||
```bash
|
||||
PI_ACP_PI_COMMAND=/absolute/path/to/your/pi-fork sandbox-agent server
|
||||
```
|
||||
|
||||
This is forwarded to `pi-acp`, which uses it instead of looking up `pi` on `PATH`.
|
||||
|
||||
#### Option 2: PATH override
|
||||
|
||||
Put your custom `pi` first on `PATH` before starting `sandbox-agent`:
|
||||
|
||||
```bash
|
||||
export PATH="/path/to/custom-pi-dir:$PATH"
|
||||
sandbox-agent server
|
||||
```
|
||||
|
||||
#### Option 3: symlink override
|
||||
|
||||
Point `pi` to your custom binary via symlink in a directory that is early on `PATH`:
|
||||
|
||||
```bash
|
||||
ln -sf /absolute/path/to/your/pi-fork /usr/local/bin/pi
|
||||
```
|
||||
|
||||
Then start `sandbox-agent` normally.
|
||||
|
||||
## opencode (experimental)
|
||||
|
||||
Start/reuse daemon and run `opencode attach` against `/opencode`.
|
||||
|
||||
```bash
|
||||
sandbox-agent opencode [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-H, --host ` | `127.0.0.1` | Daemon host |
|
||||
| `-p, --port ` | `2468` | Daemon port |
|
||||
| `--session-title ` | - | Reserved option (currently no-op) |
|
||||
| `--yolo` | false | OpenCode attach mode flag |
|
||||
|
||||
```bash
|
||||
sandbox-agent opencode
|
||||
```
|
||||
|
||||
## daemon
|
||||
|
||||
Manage the background daemon.
|
||||
|
||||
### daemon start
|
||||
|
||||
```bash
|
||||
sandbox-agent daemon start [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-H, --host ` | `127.0.0.1` | Host |
|
||||
| `-p, --port ` | `2468` | Port |
|
||||
| `--upgrade` | false | Use ensure-running + upgrade behavior |
|
||||
|
||||
```bash
|
||||
sandbox-agent daemon start
|
||||
sandbox-agent daemon start --upgrade
|
||||
```
|
||||
|
||||
### daemon stop
|
||||
|
||||
```bash
|
||||
sandbox-agent daemon stop [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-H, --host ` | `127.0.0.1` | Host |
|
||||
| `-p, --port ` | `2468` | Port |
|
||||
|
||||
### daemon status
|
||||
|
||||
```bash
|
||||
sandbox-agent daemon status [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-H, --host ` | `127.0.0.1` | Host |
|
||||
| `-p, --port ` | `2468` | Port |
|
||||
|
||||
## credentials
|
||||
|
||||
### credentials extract
|
||||
|
||||
```bash
|
||||
sandbox-agent credentials extract [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-a, --agent ` | Filter by `claude`, `codex`, `opencode`, or `amp` |
|
||||
| `-p, --provider ` | Filter by provider |
|
||||
| `-d, --home-dir ` | Override home dir |
|
||||
| `--no-oauth` | Skip OAuth sources |
|
||||
| `-r, --reveal` | Show full credential values |
|
||||
|
||||
```bash
|
||||
sandbox-agent credentials extract --agent claude --reveal
|
||||
```
|
||||
|
||||
### credentials extract-env
|
||||
|
||||
```bash
|
||||
sandbox-agent credentials extract-env [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-e, --export` | Prefix output with `export` |
|
||||
| `-d, --home-dir ` | Override home dir |
|
||||
| `--no-oauth` | Skip OAuth sources |
|
||||
|
||||
```bash
|
||||
eval "$(sandbox-agent credentials extract-env --export)"
|
||||
```
|
||||
|
||||
## api
|
||||
|
||||
API subcommands for scripting.
|
||||
|
||||
Shared option:
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-e, --endpoint ` | `http://127.0.0.1:2468` | Target server |
|
||||
|
||||
### api agents
|
||||
|
||||
```bash
|
||||
sandbox-agent api agents list [--endpoint <URL>]
|
||||
sandbox-agent api agents report [--endpoint <URL>]
|
||||
sandbox-agent api agents install <AGENT> [--reinstall] [--endpoint <URL>]
|
||||
```
|
||||
|
||||
#### api agents list
|
||||
|
||||
List all agents and their install status.
|
||||
|
||||
```bash
|
||||
sandbox-agent api agents list
|
||||
```
|
||||
|
||||
#### api agents report
|
||||
|
||||
Emit a JSON report of available models, modes, and thought levels for every agent, grouped by category.
|
||||
|
||||
```bash
|
||||
sandbox-agent api agents report --endpoint http://127.0.0.1:2468 | jq .
|
||||
```
|
||||
|
||||
Example output:
|
||||
|
||||
```json
|
||||
{
|
||||
"generatedAtMs": 1740000000000,
|
||||
"endpoint": "http://127.0.0.1:2468",
|
||||
"agents": [
|
||||
{
|
||||
"id": "claude",
|
||||
"installed": true,
|
||||
"models": {
|
||||
"currentValue": "default",
|
||||
"values": [
|
||||
{ "value": "default", "name": "Default" },
|
||||
{ "value": "sonnet", "name": "Sonnet" },
|
||||
{ "value": "opus", "name": "Opus" },
|
||||
{ "value": "haiku", "name": "Haiku" }
|
||||
]
|
||||
},
|
||||
"modes": {
|
||||
"currentValue": "default",
|
||||
"values": [
|
||||
{ "value": "default", "name": "Default" },
|
||||
{ "value": "acceptEdits", "name": "Accept Edits" },
|
||||
{ "value": "plan", "name": "Plan" },
|
||||
{ "value": "dontAsk", "name": "Don't Ask" },
|
||||
{ "value": "bypassPermissions", "name": "Bypass Permissions" }
|
||||
]
|
||||
},
|
||||
"thoughtLevels": { "values": [] }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
See individual agent pages (e.g. [Claude](/agents/claude), [Codex](/agents/codex)) for supported models, modes, and thought levels.
|
||||
|
||||
#### api agents install
|
||||
|
||||
```bash
|
||||
sandbox-agent api agents install codex --reinstall
|
||||
```
|
||||
508
.agents/skills/sandbox-agent/references/common-software.md
Normal file
508
.agents/skills/sandbox-agent/references/common-software.md
Normal file
@@ -0,0 +1,508 @@
|
||||
# Common Software
|
||||
|
||||
> Source: `docs/common-software.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/common-software
|
||||
> Description: Install browsers, languages, databases, and other tools inside the sandbox.
|
||||
|
||||
---
|
||||
The sandbox runs a Debian/Ubuntu base image. You can install software with `apt-get` via the [Process API](/processes) or by customizing your Docker image. This page covers commonly needed packages and how to install them.
|
||||
|
||||
## Browsers
|
||||
|
||||
### Chromium
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "chromium", "chromium-sandbox"],
|
||||
});
|
||||
|
||||
// Launch headless
|
||||
await sdk.runProcess({
|
||||
command: "chromium",
|
||||
args: ["--headless", "--no-sandbox", "--disable-gpu", "https://example.com"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","chromium","chromium-sandbox"]}'
|
||||
```
|
||||
|
||||
Use `--no-sandbox` when running Chromium inside a container. The container itself provides isolation.
|
||||
|
||||
### Firefox
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "firefox-esr"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","firefox-esr"]}'
|
||||
```
|
||||
|
||||
### Playwright browsers
|
||||
|
||||
Playwright bundles its own browser binaries. Install the Playwright CLI and let it download browsers for you.
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "npx",
|
||||
args: ["playwright", "install", "--with-deps", "chromium"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"npx","args":["playwright","install","--with-deps","chromium"]}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Languages and runtimes
|
||||
|
||||
### Node.js
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "nodejs", "npm"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","nodejs","npm"]}'
|
||||
```
|
||||
|
||||
For a specific version, use [nvm](https://github.com/nvm-sh/nvm):
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "bash",
|
||||
args: ["-c", "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash && . ~/.nvm/nvm.sh && nvm install 22"],
|
||||
});
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
Python 3 is typically pre-installed. To add pip and common packages:
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "python3", "python3-pip", "python3-venv"],
|
||||
});
|
||||
|
||||
await sdk.runProcess({
|
||||
command: "pip3",
|
||||
args: ["install", "numpy", "pandas", "matplotlib"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","python3","python3-pip","python3-venv"]}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"pip3","args":["install","numpy","pandas","matplotlib"]}'
|
||||
```
|
||||
|
||||
### Go
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "bash",
|
||||
args: ["-c", "curl -fsSL https://go.dev/dl/go1.23.6.linux-amd64.tar.gz | tar -C /usr/local -xz"],
|
||||
});
|
||||
|
||||
// Add to PATH for subsequent commands
|
||||
await sdk.runProcess({
|
||||
command: "bash",
|
||||
args: ["-c", "export PATH=$PATH:/usr/local/go/bin && go version"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"bash","args":["-c","curl -fsSL https://go.dev/dl/go1.23.6.linux-amd64.tar.gz | tar -C /usr/local -xz"]}'
|
||||
```
|
||||
|
||||
### Rust
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "bash",
|
||||
args: ["-c", "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"bash","args":["-c","curl --proto =https --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"]}'
|
||||
```
|
||||
|
||||
### Java (OpenJDK)
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "default-jdk"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","default-jdk"]}'
|
||||
```
|
||||
|
||||
### Ruby
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "ruby-full"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","ruby-full"]}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Databases
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "postgresql", "postgresql-client"],
|
||||
});
|
||||
|
||||
// Start the service
|
||||
const proc = await sdk.createProcess({
|
||||
command: "bash",
|
||||
args: ["-c", "su - postgres -c 'pg_ctlcluster 15 main start'"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","postgresql","postgresql-client"]}'
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "sqlite3"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","sqlite3"]}'
|
||||
```
|
||||
|
||||
### Redis
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "redis-server"],
|
||||
});
|
||||
|
||||
const proc = await sdk.createProcess({
|
||||
command: "redis-server",
|
||||
args: ["--daemonize", "no"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","redis-server"]}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"redis-server","args":["--daemonize","no"]}'
|
||||
```
|
||||
|
||||
### MySQL / MariaDB
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "mariadb-server", "mariadb-client"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","mariadb-server","mariadb-client"]}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build tools
|
||||
|
||||
### Essential build toolchain
|
||||
|
||||
Most compiled software needs the standard build toolchain:
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "build-essential", "cmake", "pkg-config"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","build-essential","cmake","pkg-config"]}'
|
||||
```
|
||||
|
||||
This installs `gcc`, `g++`, `make`, `cmake`, and related tools.
|
||||
|
||||
---
|
||||
|
||||
## Desktop applications
|
||||
|
||||
These require the [Computer Use](/computer-use) desktop to be started first.
|
||||
|
||||
### LibreOffice
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "libreoffice"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","libreoffice"]}'
|
||||
```
|
||||
|
||||
### GIMP
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "gimp"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","gimp"]}'
|
||||
```
|
||||
|
||||
### VLC
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "vlc"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","vlc"]}'
|
||||
```
|
||||
|
||||
### VS Code (code-server)
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "bash",
|
||||
args: ["-c", "curl -fsSL https://code-server.dev/install.sh | sh"],
|
||||
});
|
||||
|
||||
const proc = await sdk.createProcess({
|
||||
command: "code-server",
|
||||
args: ["--bind-addr", "0.0.0.0:8080", "--auth", "none"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"bash","args":["-c","curl -fsSL https://code-server.dev/install.sh | sh"]}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"code-server","args":["--bind-addr","0.0.0.0:8080","--auth","none"]}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI tools
|
||||
|
||||
### Git
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "git"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","git"]}'
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "bash",
|
||||
args: ["-c", "curl -fsSL https://get.docker.com | sh"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"bash","args":["-c","curl -fsSL https://get.docker.com | sh"]}'
|
||||
```
|
||||
|
||||
### jq
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "jq"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","jq"]}'
|
||||
```
|
||||
|
||||
### tmux
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "tmux"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","tmux"]}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Media and graphics
|
||||
|
||||
### FFmpeg
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "ffmpeg"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","ffmpeg"]}'
|
||||
```
|
||||
|
||||
### ImageMagick
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "imagemagick"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","imagemagick"]}'
|
||||
```
|
||||
|
||||
### Poppler (PDF utilities)
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "poppler-utils"],
|
||||
});
|
||||
|
||||
// Convert PDF to images
|
||||
await sdk.runProcess({
|
||||
command: "pdftoppm",
|
||||
args: ["-png", "document.pdf", "output"],
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","poppler-utils"]}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-installing in a Docker image
|
||||
|
||||
For production use, install software in your Dockerfile instead of at runtime. This avoids repeated downloads and makes startup faster.
|
||||
|
||||
```dockerfile
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
chromium \
|
||||
firefox-esr \
|
||||
nodejs npm \
|
||||
python3 python3-pip \
|
||||
git curl wget \
|
||||
build-essential \
|
||||
sqlite3 \
|
||||
ffmpeg \
|
||||
imagemagick \
|
||||
jq \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pip3 install numpy pandas matplotlib
|
||||
```
|
||||
|
||||
See [Docker deployment](/deploy/docker) for how to use custom images with Sandbox Agent.
|
||||
809
.agents/skills/sandbox-agent/references/computer-use.md
Normal file
809
.agents/skills/sandbox-agent/references/computer-use.md
Normal file
@@ -0,0 +1,809 @@
|
||||
# Computer Use
|
||||
|
||||
> Source: `docs/computer-use.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/computer-use
|
||||
> Description: Control a virtual desktop inside the sandbox with mouse, keyboard, screenshots, recordings, and live streaming.
|
||||
|
||||
---
|
||||
Sandbox Agent provides a managed virtual desktop (Xvfb + openbox) that you can control programmatically. This is useful for browser automation, GUI testing, and AI computer-use workflows.
|
||||
|
||||
## Start and stop
|
||||
|
||||
```ts TypeScript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const status = await sdk.startDesktop({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
dpi: 96,
|
||||
});
|
||||
|
||||
console.log(status.state); // "active"
|
||||
console.log(status.display); // ":99"
|
||||
|
||||
// When done
|
||||
await sdk.stopDesktop();
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/start" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"width":1920,"height":1080,"dpi":96}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/stop"
|
||||
```
|
||||
|
||||
All fields in the start request are optional. Defaults are 1440x900 at 96 DPI.
|
||||
|
||||
### Start request options
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `width` | number | 1440 | Desktop width in pixels |
|
||||
| `height` | number | 900 | Desktop height in pixels |
|
||||
| `dpi` | number | 96 | Display DPI |
|
||||
| `displayNum` | number | 99 | Starting X display number. The runtime probes from this number upward to find an available display. |
|
||||
| `stateDir` | string | (auto) | Desktop state directory for home, logs, recordings |
|
||||
| `streamVideoCodec` | string | `"vp8"` | WebRTC video codec (`vp8`, `vp9`, `h264`) |
|
||||
| `streamAudioCodec` | string | `"opus"` | WebRTC audio codec (`opus`, `g722`) |
|
||||
| `streamFrameRate` | number | 30 | Streaming frame rate (1-60) |
|
||||
| `webrtcPortRange` | string | `"59050-59070"` | UDP port range for WebRTC media |
|
||||
| `recordingFps` | number | 30 | Default recording FPS when not specified in `startDesktopRecording` (1-60) |
|
||||
|
||||
The streaming and recording options configure defaults for the desktop session. They take effect when streaming or recording is started later.
|
||||
|
||||
```ts TypeScript
|
||||
const status = await sdk.startDesktop({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
streamVideoCodec: "h264",
|
||||
streamFrameRate: 60,
|
||||
webrtcPortRange: "59100-59120",
|
||||
recordingFps: 15,
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/start" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"streamVideoCodec": "h264",
|
||||
"streamFrameRate": 60,
|
||||
"webrtcPortRange": "59100-59120",
|
||||
"recordingFps": 15
|
||||
}'
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
```ts TypeScript
|
||||
const status = await sdk.getDesktopStatus();
|
||||
console.log(status.state); // "inactive" | "active" | "failed" | ...
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/desktop/status"
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
Capture the full desktop or a specific region. Optionally include the cursor position.
|
||||
|
||||
```ts TypeScript
|
||||
// Full screenshot (PNG by default)
|
||||
const png = await sdk.takeDesktopScreenshot();
|
||||
|
||||
// JPEG at 70% quality, half scale
|
||||
const jpeg = await sdk.takeDesktopScreenshot({
|
||||
format: "jpeg",
|
||||
quality: 70,
|
||||
scale: 0.5,
|
||||
});
|
||||
|
||||
// Include cursor overlay
|
||||
const withCursor = await sdk.takeDesktopScreenshot({
|
||||
showCursor: true,
|
||||
});
|
||||
|
||||
// Region screenshot
|
||||
const region = await sdk.takeDesktopRegionScreenshot({
|
||||
x: 100,
|
||||
y: 100,
|
||||
width: 400,
|
||||
height: 300,
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/desktop/screenshot" --output screenshot.png
|
||||
|
||||
curl "http://127.0.0.1:2468/v1/desktop/screenshot?format=jpeg&quality=70&scale=0.5" \
|
||||
--output screenshot.jpg
|
||||
|
||||
# Include cursor overlay
|
||||
curl "http://127.0.0.1:2468/v1/desktop/screenshot?show_cursor=true" \
|
||||
--output with_cursor.png
|
||||
|
||||
curl "http://127.0.0.1:2468/v1/desktop/screenshot/region?x=100&y=100&width=400&height=300" \
|
||||
--output region.png
|
||||
```
|
||||
|
||||
### Screenshot options
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `format` | string | `"png"` | Output format: `png`, `jpeg`, or `webp` |
|
||||
| `quality` | number | 85 | Compression quality (1-100, JPEG/WebP only) |
|
||||
| `scale` | number | 1.0 | Scale factor (0.1-1.0) |
|
||||
| `showCursor` | boolean | `false` | Composite a crosshair at the cursor position |
|
||||
|
||||
When `showCursor` is enabled, the cursor position is captured at the moment of the screenshot and a red crosshair is drawn at that location. This is useful for AI agents that need to see where the cursor is in the screenshot.
|
||||
|
||||
## Mouse
|
||||
|
||||
```ts TypeScript
|
||||
// Get current position
|
||||
const pos = await sdk.getDesktopMousePosition();
|
||||
console.log(pos.x, pos.y);
|
||||
|
||||
// Move
|
||||
await sdk.moveDesktopMouse({ x: 500, y: 300 });
|
||||
|
||||
// Click (left by default)
|
||||
await sdk.clickDesktop({ x: 500, y: 300 });
|
||||
|
||||
// Right click
|
||||
await sdk.clickDesktop({ x: 500, y: 300, button: "right" });
|
||||
|
||||
// Double click
|
||||
await sdk.clickDesktop({ x: 500, y: 300, clickCount: 2 });
|
||||
|
||||
// Drag
|
||||
await sdk.dragDesktopMouse({
|
||||
startX: 100, startY: 100,
|
||||
endX: 400, endY: 400,
|
||||
});
|
||||
|
||||
// Scroll
|
||||
await sdk.scrollDesktop({ x: 500, y: 300, deltaY: -3 });
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/desktop/mouse/position"
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/mouse/click" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"x":500,"y":300}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/mouse/drag" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"startX":100,"startY":100,"endX":400,"endY":400}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/mouse/scroll" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"x":500,"y":300,"deltaY":-3}'
|
||||
```
|
||||
|
||||
## Keyboard
|
||||
|
||||
```ts TypeScript
|
||||
// Type text
|
||||
await sdk.typeDesktopText({ text: "Hello, world!" });
|
||||
|
||||
// Press a key with modifiers
|
||||
await sdk.pressDesktopKey({
|
||||
key: "c",
|
||||
modifiers: { ctrl: true },
|
||||
});
|
||||
|
||||
// Low-level key down/up
|
||||
await sdk.keyDownDesktop({ key: "Shift_L" });
|
||||
await sdk.keyUpDesktop({ key: "Shift_L" });
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/keyboard/type" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"text":"Hello, world!"}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/keyboard/press" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key":"c","modifiers":{"ctrl":true}}'
|
||||
```
|
||||
|
||||
## Clipboard
|
||||
|
||||
Read and write the X11 clipboard programmatically.
|
||||
|
||||
```ts TypeScript
|
||||
// Read clipboard
|
||||
const clipboard = await sdk.getDesktopClipboard();
|
||||
console.log(clipboard.text);
|
||||
|
||||
// Read primary selection (mouse-selected text)
|
||||
const primary = await sdk.getDesktopClipboard({ selection: "primary" });
|
||||
|
||||
// Write to clipboard
|
||||
await sdk.setDesktopClipboard({ text: "Pasted via API" });
|
||||
|
||||
// Write to both clipboard and primary selection
|
||||
await sdk.setDesktopClipboard({
|
||||
text: "Synced text",
|
||||
selection: "both",
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/desktop/clipboard"
|
||||
|
||||
curl "http://127.0.0.1:2468/v1/desktop/clipboard?selection=primary"
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/clipboard" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"text":"Pasted via API"}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/clipboard" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"text":"Synced text","selection":"both"}'
|
||||
```
|
||||
|
||||
The `selection` parameter controls which X11 selection to read or write:
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `clipboard` (default) | The standard clipboard (Ctrl+C / Ctrl+V) |
|
||||
| `primary` | The primary selection (text selected with the mouse) |
|
||||
| `both` | Write to both clipboard and primary selection (write only) |
|
||||
|
||||
## Display and windows
|
||||
|
||||
```ts TypeScript
|
||||
const display = await sdk.getDesktopDisplayInfo();
|
||||
console.log(display.resolution); // { width: 1920, height: 1080, dpi: 96 }
|
||||
|
||||
const { windows } = await sdk.listDesktopWindows();
|
||||
for (const win of windows) {
|
||||
console.log(win.title, win.x, win.y, win.width, win.height);
|
||||
}
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/desktop/display/info"
|
||||
|
||||
curl "http://127.0.0.1:2468/v1/desktop/windows"
|
||||
```
|
||||
|
||||
The windows endpoint filters out noise automatically: window manager internals (Openbox), windows with empty titles, and tiny helper windows (under 120x80) are excluded. The currently active/focused window is always included regardless of filters.
|
||||
|
||||
### Focused window
|
||||
|
||||
Get the currently focused window without listing all windows.
|
||||
|
||||
```ts TypeScript
|
||||
const focused = await sdk.getDesktopFocusedWindow();
|
||||
console.log(focused.title, focused.id);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/desktop/windows/focused"
|
||||
```
|
||||
|
||||
Returns 404 if no window currently has focus.
|
||||
|
||||
### Window management
|
||||
|
||||
Focus, move, and resize windows by their X11 window ID.
|
||||
|
||||
```ts TypeScript
|
||||
const { windows } = await sdk.listDesktopWindows();
|
||||
const win = windows[0];
|
||||
|
||||
// Bring window to foreground
|
||||
await sdk.focusDesktopWindow(win.id);
|
||||
|
||||
// Move window
|
||||
await sdk.moveDesktopWindow(win.id, { x: 100, y: 50 });
|
||||
|
||||
// Resize window
|
||||
await sdk.resizeDesktopWindow(win.id, { width: 1280, height: 720 });
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
# Focus a window
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/windows/12345/focus"
|
||||
|
||||
# Move a window
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/windows/12345/move" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"x":100,"y":50}'
|
||||
|
||||
# Resize a window
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/windows/12345/resize" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"width":1280,"height":720}'
|
||||
```
|
||||
|
||||
All three endpoints return the updated window info so you can verify the operation took effect. The window manager may adjust the requested position or size.
|
||||
|
||||
## App launching
|
||||
|
||||
Launch applications or open files/URLs on the desktop without needing to shell out.
|
||||
|
||||
```ts TypeScript
|
||||
// Launch an app by name
|
||||
const result = await sdk.launchDesktopApp({
|
||||
app: "firefox",
|
||||
args: ["--private"],
|
||||
});
|
||||
console.log(result.processId); // "proc_7"
|
||||
|
||||
// Launch and wait for the window to appear
|
||||
const withWindow = await sdk.launchDesktopApp({
|
||||
app: "xterm",
|
||||
wait: true,
|
||||
});
|
||||
console.log(withWindow.windowId); // "12345" or null if timed out
|
||||
|
||||
// Open a URL with the default handler
|
||||
const opened = await sdk.openDesktopTarget({
|
||||
target: "https://example.com",
|
||||
});
|
||||
console.log(opened.processId);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/launch" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"app":"firefox","args":["--private"]}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/launch" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"app":"xterm","wait":true}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/open" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"target":"https://example.com"}'
|
||||
```
|
||||
|
||||
The returned `processId` can be used with the [Process API](/processes) to read logs (`GET /v1/processes/{id}/logs`) or stop the application (`POST /v1/processes/{id}/stop`).
|
||||
|
||||
When `wait` is `true`, the API polls for up to 5 seconds for a window to appear. If the window appears, its ID is returned in `windowId`. If it times out, `windowId` is `null` but the process is still running.
|
||||
|
||||
**Launch/Open vs the Process API:** Both `launch` and `open` are convenience wrappers around the [Process API](/processes). They create managed processes (with `owner: "desktop"`) that you can inspect, log, and stop through the same Process endpoints. The difference is that `launch` validates the binary exists in PATH first and can optionally wait for a window to appear, while `open` delegates to the system default handler (`xdg-open`). Use the Process API directly when you need full control over command, environment, working directory, or restart policies.
|
||||
|
||||
## Recording
|
||||
|
||||
Record the desktop to MP4.
|
||||
|
||||
```ts TypeScript
|
||||
const recording = await sdk.startDesktopRecording({ fps: 30 });
|
||||
console.log(recording.id);
|
||||
|
||||
// ... do things ...
|
||||
|
||||
const stopped = await sdk.stopDesktopRecording();
|
||||
|
||||
// List all recordings
|
||||
const { recordings } = await sdk.listDesktopRecordings();
|
||||
|
||||
// Download
|
||||
const mp4 = await sdk.downloadDesktopRecording(recording.id);
|
||||
|
||||
// Clean up
|
||||
await sdk.deleteDesktopRecording(recording.id);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/recording/start" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"fps":30}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/recording/stop"
|
||||
|
||||
curl "http://127.0.0.1:2468/v1/desktop/recordings"
|
||||
|
||||
curl "http://127.0.0.1:2468/v1/desktop/recordings/rec_1/download" --output recording.mp4
|
||||
|
||||
curl -X DELETE "http://127.0.0.1:2468/v1/desktop/recordings/rec_1"
|
||||
```
|
||||
|
||||
## Desktop processes
|
||||
|
||||
The desktop runtime manages several background processes (Xvfb, openbox, neko, ffmpeg). These are all registered with the general [Process API](/processes) under the `desktop` owner, so you can inspect logs, check status, and troubleshoot using the same tools you use for any other managed process.
|
||||
|
||||
```ts TypeScript
|
||||
// List all processes, including desktop-owned ones
|
||||
const { processes } = await sdk.listProcesses();
|
||||
|
||||
const desktopProcs = processes.filter((p) => p.owner === "desktop");
|
||||
for (const p of desktopProcs) {
|
||||
console.log(p.id, p.command, p.status);
|
||||
}
|
||||
|
||||
// Read logs from a specific desktop process
|
||||
const logs = await sdk.getProcessLogs(desktopProcs[0].id, { tail: 50 });
|
||||
for (const entry of logs.entries) {
|
||||
console.log(entry.stream, atob(entry.data));
|
||||
}
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
# List all processes (desktop processes have owner: "desktop")
|
||||
curl "http://127.0.0.1:2468/v1/processes"
|
||||
|
||||
# Get logs from a specific desktop process
|
||||
curl "http://127.0.0.1:2468/v1/processes/proc_1/logs?tail=50"
|
||||
```
|
||||
|
||||
The desktop status endpoint also includes a summary of running processes:
|
||||
|
||||
```ts TypeScript
|
||||
const status = await sdk.getDesktopStatus();
|
||||
for (const proc of status.processes) {
|
||||
console.log(proc.name, proc.pid, proc.running);
|
||||
}
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/desktop/status"
|
||||
# Response includes: processes: [{ name: "Xvfb", pid: 123, running: true }, ...]
|
||||
```
|
||||
|
||||
| Process | Role | Restart policy |
|
||||
|---------|------|---------------|
|
||||
| Xvfb | Virtual X11 framebuffer | Auto-restart while desktop is active |
|
||||
| openbox | Window manager | Auto-restart while desktop is active |
|
||||
| neko | WebRTC streaming server (started by `startDesktopStream`) | No auto-restart |
|
||||
| ffmpeg | Screen recorder (started by `startDesktopRecording`) | No auto-restart |
|
||||
|
||||
## Live streaming
|
||||
|
||||
Start a WebRTC stream for real-time desktop viewing in a browser.
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.startDesktopStream();
|
||||
|
||||
// Check stream status
|
||||
const status = await sdk.getDesktopStreamStatus();
|
||||
console.log(status.active); // true
|
||||
console.log(status.processId); // "proc_5"
|
||||
|
||||
// Connect via the React DesktopViewer component or
|
||||
// use the WebSocket signaling endpoint directly
|
||||
// at ws://127.0.0.1:2468/v1/desktop/stream/signaling
|
||||
|
||||
await sdk.stopDesktopStream();
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/stream/start"
|
||||
|
||||
# Check stream status
|
||||
curl "http://127.0.0.1:2468/v1/desktop/stream/status"
|
||||
|
||||
# Connect to ws://127.0.0.1:2468/v1/desktop/stream/signaling for WebRTC signaling
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/desktop/stream/stop"
|
||||
```
|
||||
|
||||
For a drop-in React component, see [React Components](/react-components).
|
||||
|
||||
## API reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `POST` | `/v1/desktop/start` | Start the desktop runtime |
|
||||
| `POST` | `/v1/desktop/stop` | Stop the desktop runtime |
|
||||
| `GET` | `/v1/desktop/status` | Get desktop runtime status |
|
||||
| `GET` | `/v1/desktop/screenshot` | Capture full desktop screenshot |
|
||||
| `GET` | `/v1/desktop/screenshot/region` | Capture a region screenshot |
|
||||
| `GET` | `/v1/desktop/mouse/position` | Get current mouse position |
|
||||
| `POST` | `/v1/desktop/mouse/move` | Move the mouse |
|
||||
| `POST` | `/v1/desktop/mouse/click` | Click the mouse |
|
||||
| `POST` | `/v1/desktop/mouse/down` | Press mouse button down |
|
||||
| `POST` | `/v1/desktop/mouse/up` | Release mouse button |
|
||||
| `POST` | `/v1/desktop/mouse/drag` | Drag from one point to another |
|
||||
| `POST` | `/v1/desktop/mouse/scroll` | Scroll at a position |
|
||||
| `POST` | `/v1/desktop/keyboard/type` | Type text |
|
||||
| `POST` | `/v1/desktop/keyboard/press` | Press a key with optional modifiers |
|
||||
| `POST` | `/v1/desktop/keyboard/down` | Press a key down (hold) |
|
||||
| `POST` | `/v1/desktop/keyboard/up` | Release a key |
|
||||
| `GET` | `/v1/desktop/display/info` | Get display info |
|
||||
| `GET` | `/v1/desktop/windows` | List visible windows |
|
||||
| `GET` | `/v1/desktop/windows/focused` | Get focused window info |
|
||||
| `POST` | `/v1/desktop/windows/{id}/focus` | Focus a window |
|
||||
| `POST` | `/v1/desktop/windows/{id}/move` | Move a window |
|
||||
| `POST` | `/v1/desktop/windows/{id}/resize` | Resize a window |
|
||||
| `GET` | `/v1/desktop/clipboard` | Read clipboard contents |
|
||||
| `POST` | `/v1/desktop/clipboard` | Write to clipboard |
|
||||
| `POST` | `/v1/desktop/launch` | Launch an application |
|
||||
| `POST` | `/v1/desktop/open` | Open a file or URL |
|
||||
| `POST` | `/v1/desktop/recording/start` | Start recording |
|
||||
| `POST` | `/v1/desktop/recording/stop` | Stop recording |
|
||||
| `GET` | `/v1/desktop/recordings` | List recordings |
|
||||
| `GET` | `/v1/desktop/recordings/{id}` | Get recording metadata |
|
||||
| `GET` | `/v1/desktop/recordings/{id}/download` | Download recording |
|
||||
| `DELETE` | `/v1/desktop/recordings/{id}` | Delete recording |
|
||||
| `POST` | `/v1/desktop/stream/start` | Start WebRTC streaming |
|
||||
| `POST` | `/v1/desktop/stream/stop` | Stop WebRTC streaming |
|
||||
| `GET` | `/v1/desktop/stream/status` | Get stream status |
|
||||
| `GET` | `/v1/desktop/stream/signaling` | WebSocket for WebRTC signaling |
|
||||
|
||||
### TypeScript SDK methods
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `startDesktop(request?)` | `DesktopStatusResponse` | Start the desktop |
|
||||
| `stopDesktop()` | `DesktopStatusResponse` | Stop the desktop |
|
||||
| `getDesktopStatus()` | `DesktopStatusResponse` | Get desktop status |
|
||||
| `takeDesktopScreenshot(query?)` | `Uint8Array` | Capture screenshot |
|
||||
| `takeDesktopRegionScreenshot(query)` | `Uint8Array` | Capture region screenshot |
|
||||
| `getDesktopMousePosition()` | `DesktopMousePositionResponse` | Get mouse position |
|
||||
| `moveDesktopMouse(request)` | `DesktopMousePositionResponse` | Move mouse |
|
||||
| `clickDesktop(request)` | `DesktopMousePositionResponse` | Click mouse |
|
||||
| `mouseDownDesktop(request)` | `DesktopMousePositionResponse` | Mouse button down |
|
||||
| `mouseUpDesktop(request)` | `DesktopMousePositionResponse` | Mouse button up |
|
||||
| `dragDesktopMouse(request)` | `DesktopMousePositionResponse` | Drag mouse |
|
||||
| `scrollDesktop(request)` | `DesktopMousePositionResponse` | Scroll |
|
||||
| `typeDesktopText(request)` | `DesktopActionResponse` | Type text |
|
||||
| `pressDesktopKey(request)` | `DesktopActionResponse` | Press key |
|
||||
| `keyDownDesktop(request)` | `DesktopActionResponse` | Key down |
|
||||
| `keyUpDesktop(request)` | `DesktopActionResponse` | Key up |
|
||||
| `getDesktopDisplayInfo()` | `DesktopDisplayInfoResponse` | Get display info |
|
||||
| `listDesktopWindows()` | `DesktopWindowListResponse` | List windows |
|
||||
| `getDesktopFocusedWindow()` | `DesktopWindowInfo` | Get focused window |
|
||||
| `focusDesktopWindow(id)` | `DesktopWindowInfo` | Focus a window |
|
||||
| `moveDesktopWindow(id, request)` | `DesktopWindowInfo` | Move a window |
|
||||
| `resizeDesktopWindow(id, request)` | `DesktopWindowInfo` | Resize a window |
|
||||
| `getDesktopClipboard(query?)` | `DesktopClipboardResponse` | Read clipboard |
|
||||
| `setDesktopClipboard(request)` | `DesktopActionResponse` | Write clipboard |
|
||||
| `launchDesktopApp(request)` | `DesktopLaunchResponse` | Launch an app |
|
||||
| `openDesktopTarget(request)` | `DesktopOpenResponse` | Open file/URL |
|
||||
| `startDesktopRecording(request?)` | `DesktopRecordingInfo` | Start recording |
|
||||
| `stopDesktopRecording()` | `DesktopRecordingInfo` | Stop recording |
|
||||
| `listDesktopRecordings()` | `DesktopRecordingListResponse` | List recordings |
|
||||
| `getDesktopRecording(id)` | `DesktopRecordingInfo` | Get recording |
|
||||
| `downloadDesktopRecording(id)` | `Uint8Array` | Download recording |
|
||||
| `deleteDesktopRecording(id)` | `void` | Delete recording |
|
||||
| `startDesktopStream()` | `DesktopStreamStatusResponse` | Start streaming |
|
||||
| `stopDesktopStream()` | `DesktopStreamStatusResponse` | Stop streaming |
|
||||
| `getDesktopStreamStatus()` | `DesktopStreamStatusResponse` | Stream status |
|
||||
|
||||
## Customizing the desktop environment
|
||||
|
||||
The desktop runs inside the sandbox filesystem, so you can customize it using the [File System](/file-system) API before or after starting the desktop. The desktop HOME directory is located at `~/.local/state/sandbox-agent/desktop/home` (or `$XDG_STATE_HOME/sandbox-agent/desktop/home` if `XDG_STATE_HOME` is set).
|
||||
|
||||
All configuration files below are written to paths relative to this HOME directory.
|
||||
|
||||
### Window manager (openbox)
|
||||
|
||||
The desktop uses [openbox](http://openbox.org/) as its window manager. You can customize its behavior, theme, and keyboard shortcuts by writing an `rc.xml` config file.
|
||||
|
||||
```ts TypeScript
|
||||
const openboxConfig = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openbox_config xmlns="http://openbox.org/3.4/rc">
|
||||
<theme>
|
||||
<name>Clearlooks</name>
|
||||
<titleLayout>NLIMC</titleLayout>
|
||||
<font place="ActiveWindow"><name>DejaVu Sans</name><size>10</size></font>
|
||||
</theme>
|
||||
<desktops><number>1</number></desktops>
|
||||
<keyboard>
|
||||
<keybind key="A-F4"><action name="Close"/></keybind>
|
||||
<keybind key="A-Tab"><action name="NextWindow"/></keybind>
|
||||
</keyboard>
|
||||
</openbox_config>`;
|
||||
|
||||
await sdk.mkdirFs({ path: "~/.local/state/sandbox-agent/desktop/home/.config/openbox" });
|
||||
await sdk.writeFsFile(
|
||||
{ path: "~/.local/state/sandbox-agent/desktop/home/.config/openbox/rc.xml" },
|
||||
openboxConfig,
|
||||
);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/fs/mkdir?path=~/.local/state/sandbox-agent/desktop/home/.config/openbox"
|
||||
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=~/.local/state/sandbox-agent/desktop/home/.config/openbox/rc.xml" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @rc.xml
|
||||
```
|
||||
|
||||
### Autostart programs
|
||||
|
||||
Openbox runs scripts in `~/.config/openbox/autostart` on startup. Use this to launch applications, set the background, or configure the environment.
|
||||
|
||||
```ts TypeScript
|
||||
const autostart = `#!/bin/sh
|
||||
# Set a solid background color
|
||||
xsetroot -solid "#1e1e2e" &
|
||||
|
||||
# Launch a terminal
|
||||
xterm -geometry 120x40+50+50 &
|
||||
|
||||
# Launch a browser
|
||||
firefox --no-remote &
|
||||
`;
|
||||
|
||||
await sdk.mkdirFs({ path: "~/.local/state/sandbox-agent/desktop/home/.config/openbox" });
|
||||
await sdk.writeFsFile(
|
||||
{ path: "~/.local/state/sandbox-agent/desktop/home/.config/openbox/autostart" },
|
||||
autostart,
|
||||
);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/fs/mkdir?path=~/.local/state/sandbox-agent/desktop/home/.config/openbox"
|
||||
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=~/.local/state/sandbox-agent/desktop/home/.config/openbox/autostart" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @autostart.sh
|
||||
```
|
||||
|
||||
The autostart script runs when openbox starts, which happens during `startDesktop()`. Write the autostart file before calling `startDesktop()` for it to take effect.
|
||||
|
||||
### Background
|
||||
|
||||
There is no wallpaper set by default (the background is the X root window default). You can set it using `xsetroot` in the autostart script (as shown above), or use `feh` if you need an image:
|
||||
|
||||
```ts TypeScript
|
||||
// Upload a wallpaper image
|
||||
import fs from "node:fs";
|
||||
|
||||
const wallpaper = await fs.promises.readFile("./wallpaper.png");
|
||||
await sdk.writeFsFile(
|
||||
{ path: "~/.local/state/sandbox-agent/desktop/home/wallpaper.png" },
|
||||
wallpaper,
|
||||
);
|
||||
|
||||
// Set the autostart to apply it
|
||||
const autostart = `#!/bin/sh
|
||||
feh --bg-fill ~/wallpaper.png &
|
||||
`;
|
||||
|
||||
await sdk.mkdirFs({ path: "~/.local/state/sandbox-agent/desktop/home/.config/openbox" });
|
||||
await sdk.writeFsFile(
|
||||
{ path: "~/.local/state/sandbox-agent/desktop/home/.config/openbox/autostart" },
|
||||
autostart,
|
||||
);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=~/.local/state/sandbox-agent/desktop/home/wallpaper.png" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @wallpaper.png
|
||||
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=~/.local/state/sandbox-agent/desktop/home/.config/openbox/autostart" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @autostart.sh
|
||||
```
|
||||
|
||||
`feh` is not installed by default. Install it via the [Process API](/processes) before starting the desktop: `await sdk.runProcess({ command: "apt-get", args: ["install", "-y", "feh"] })`.
|
||||
|
||||
### Fonts
|
||||
|
||||
Only `fonts-dejavu-core` is installed by default. To add more fonts, install them with your system package manager or copy font files into the sandbox:
|
||||
|
||||
```ts TypeScript
|
||||
// Install a font package
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "fonts-noto", "fonts-liberation"],
|
||||
});
|
||||
|
||||
// Or copy a custom font file
|
||||
import fs from "node:fs";
|
||||
|
||||
const font = await fs.promises.readFile("./CustomFont.ttf");
|
||||
await sdk.mkdirFs({ path: "~/.local/state/sandbox-agent/desktop/home/.local/share/fonts" });
|
||||
await sdk.writeFsFile(
|
||||
{ path: "~/.local/state/sandbox-agent/desktop/home/.local/share/fonts/CustomFont.ttf" },
|
||||
font,
|
||||
);
|
||||
|
||||
// Rebuild the font cache
|
||||
await sdk.runProcess({ command: "fc-cache", args: ["-fv"] });
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","fonts-noto","fonts-liberation"]}'
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/fs/mkdir?path=~/.local/state/sandbox-agent/desktop/home/.local/share/fonts"
|
||||
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=~/.local/state/sandbox-agent/desktop/home/.local/share/fonts/CustomFont.ttf" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @CustomFont.ttf
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"fc-cache","args":["-fv"]}'
|
||||
```
|
||||
|
||||
### Cursor theme
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.runProcess({
|
||||
command: "apt-get",
|
||||
args: ["install", "-y", "dmz-cursor-theme"],
|
||||
});
|
||||
|
||||
const xresources = `Xcursor.theme: DMZ-White\nXcursor.size: 24\n`;
|
||||
await sdk.writeFsFile(
|
||||
{ path: "~/.local/state/sandbox-agent/desktop/home/.Xresources" },
|
||||
xresources,
|
||||
);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"apt-get","args":["install","-y","dmz-cursor-theme"]}'
|
||||
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=~/.local/state/sandbox-agent/desktop/home/.Xresources" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary 'Xcursor.theme: DMZ-White\nXcursor.size: 24'
|
||||
```
|
||||
|
||||
Run `xrdb -merge ~/.Xresources` (via the autostart or process API) after writing the file for changes to take effect.
|
||||
|
||||
### Shell and terminal
|
||||
|
||||
No terminal emulator or shell is launched by default. Add one to the openbox autostart:
|
||||
|
||||
```sh
|
||||
# In ~/.config/openbox/autostart
|
||||
xterm -geometry 120x40+50+50 &
|
||||
```
|
||||
|
||||
To use a different shell, set the `SHELL` environment variable in your Dockerfile or install your preferred shell and configure the terminal to use it.
|
||||
|
||||
### GTK theme
|
||||
|
||||
Applications using GTK will pick up settings from `~/.config/gtk-3.0/settings.ini`:
|
||||
|
||||
```ts TypeScript
|
||||
const gtkSettings = `[Settings]
|
||||
gtk-theme-name=Adwaita
|
||||
gtk-icon-theme-name=Adwaita
|
||||
gtk-font-name=DejaVu Sans 10
|
||||
gtk-cursor-theme-name=DMZ-White
|
||||
gtk-cursor-theme-size=24
|
||||
`;
|
||||
|
||||
await sdk.mkdirFs({ path: "~/.local/state/sandbox-agent/desktop/home/.config/gtk-3.0" });
|
||||
await sdk.writeFsFile(
|
||||
{ path: "~/.local/state/sandbox-agent/desktop/home/.config/gtk-3.0/settings.ini" },
|
||||
gtkSettings,
|
||||
);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/fs/mkdir?path=~/.local/state/sandbox-agent/desktop/home/.config/gtk-3.0"
|
||||
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=~/.local/state/sandbox-agent/desktop/home/.config/gtk-3.0/settings.ini" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @settings.ini
|
||||
```
|
||||
|
||||
### Summary of configuration paths
|
||||
|
||||
All paths are relative to the desktop HOME directory (`~/.local/state/sandbox-agent/desktop/home`).
|
||||
|
||||
| What | Path | Notes |
|
||||
|------|------|-------|
|
||||
| Openbox config | `.config/openbox/rc.xml` | Window manager theme, keybindings, behavior |
|
||||
| Autostart | `.config/openbox/autostart` | Shell script run on desktop start |
|
||||
| Custom fonts | `.local/share/fonts/` | TTF/OTF files, run `fc-cache -fv` after |
|
||||
| Cursor theme | `.Xresources` | Requires `xrdb -merge` to apply |
|
||||
| GTK 3 settings | `.config/gtk-3.0/settings.ini` | Theme, icons, fonts for GTK apps |
|
||||
| Wallpaper | Any path, referenced from autostart | Requires `feh` or similar tool |
|
||||
52
.agents/skills/sandbox-agent/references/cors.md
Normal file
52
.agents/skills/sandbox-agent/references/cors.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# CORS Configuration
|
||||
|
||||
> Source: `docs/cors.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/cors
|
||||
> Description: Configure CORS for browser-based applications.
|
||||
|
||||
---
|
||||
When calling the Sandbox Agent server from a browser, CORS (Cross-Origin Resource Sharing) controls which origins can make requests.
|
||||
|
||||
## Default Behavior
|
||||
|
||||
By default, no CORS origins are allowed. You must explicitly specify origins for browser-based applications:
|
||||
|
||||
```bash
|
||||
sandbox-agent server \
|
||||
--cors-allow-origin "http://localhost:5173"
|
||||
```
|
||||
|
||||
The built-in Inspector UI at `/ui/` is served from the same origin as the server, so it does not require CORS configuration.
|
||||
|
||||
## Options
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--cors-allow-origin` | Origins to allow |
|
||||
| `--cors-allow-method` | HTTP methods to allow (defaults to all if not specified) |
|
||||
| `--cors-allow-header` | Headers to allow (defaults to all if not specified) |
|
||||
| `--cors-allow-credentials` | Allow credentials (cookies, authorization headers) |
|
||||
|
||||
## Multiple Origins
|
||||
|
||||
Specify the flag multiple times to allow multiple origins:
|
||||
|
||||
```bash
|
||||
sandbox-agent server \
|
||||
--cors-allow-origin "http://localhost:5173" \
|
||||
--cors-allow-origin "http://localhost:3000"
|
||||
```
|
||||
|
||||
## Restricting Methods and Headers
|
||||
|
||||
By default, all methods and headers are allowed. To restrict them:
|
||||
|
||||
```bash
|
||||
sandbox-agent server \
|
||||
--cors-allow-origin "https://your-app.com" \
|
||||
--cors-allow-method "GET" \
|
||||
--cors-allow-method "POST" \
|
||||
--cors-allow-header "Authorization" \
|
||||
--cors-allow-header "Content-Type" \
|
||||
--cors-allow-credentials
|
||||
```
|
||||
149
.agents/skills/sandbox-agent/references/custom-tools.md
Normal file
149
.agents/skills/sandbox-agent/references/custom-tools.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Custom Tools
|
||||
|
||||
> Source: `docs/custom-tools.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/custom-tools
|
||||
> Description: Give agents custom tools inside the sandbox using MCP servers or skills.
|
||||
|
||||
---
|
||||
There are two common patterns for sandbox-local custom tooling:
|
||||
|
||||
| | MCP Server | Skill |
|
||||
|---|---|---|
|
||||
| **How it works** | Agent connects to an MCP server (`mcpServers`) | Agent follows `SKILL.md` instructions and runs scripts |
|
||||
| **Best for** | Typed tool calls and structured protocols | Lightweight task-specific guidance |
|
||||
| **Requires** | MCP server process (stdio/http/sse) | Script + `SKILL.md` |
|
||||
|
||||
## Option A: MCP server (stdio)
|
||||
|
||||
### Write and bundle your MCP server
|
||||
|
||||
```ts src/mcp-server.ts
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
|
||||
const server = new McpServer({ name: "rand", version: "1.0.0" });
|
||||
|
||||
server.tool(
|
||||
"random_number",
|
||||
"Generate a random integer between min and max",
|
||||
{
|
||||
min: z.number(),
|
||||
max: z.number(),
|
||||
},
|
||||
async ({ min, max }) => ({
|
||||
content: [{ type: "text", text: String(Math.floor(Math.random() * (max - min + 1)) + min) }],
|
||||
}),
|
||||
);
|
||||
|
||||
await server.connect(new StdioServerTransport());
|
||||
```
|
||||
|
||||
```bash
|
||||
npx esbuild src/mcp-server.ts --bundle --format=cjs --platform=node --target=node18 --outfile=dist/mcp-server.cjs
|
||||
```
|
||||
|
||||
### Upload it into the sandbox
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import fs from "node:fs";
|
||||
|
||||
const sdk = await SandboxAgent.connect({ baseUrl: "http://127.0.0.1:2468" });
|
||||
const content = await fs.promises.readFile("./dist/mcp-server.cjs");
|
||||
|
||||
await sdk.writeFsFile({ path: "/opt/mcp/custom-tools/mcp-server.cjs" }, content);
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=/opt/mcp/custom-tools/mcp-server.cjs" \
|
||||
--data-binary @./dist/mcp-server.cjs
|
||||
```
|
||||
|
||||
### Register MCP config and create a session
|
||||
|
||||
```ts
|
||||
await sdk.setMcpConfig(
|
||||
{
|
||||
directory: "/workspace",
|
||||
mcpName: "customTools",
|
||||
},
|
||||
{
|
||||
type: "local",
|
||||
command: "node",
|
||||
args: ["/opt/mcp/custom-tools/mcp-server.cjs"],
|
||||
},
|
||||
);
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Use the random_number tool with min=1 and max=10." },
|
||||
]);
|
||||
```
|
||||
|
||||
## Option B: Skills
|
||||
|
||||
### Write script + skill file
|
||||
|
||||
```ts src/random-number.ts
|
||||
const min = Number(process.argv[2]);
|
||||
const max = Number(process.argv[3]);
|
||||
|
||||
if (Number.isNaN(min) || Number.isNaN(max)) {
|
||||
console.error("Usage: random-number <min> <max>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(Math.floor(Math.random() * (max - min + 1)) + min);
|
||||
```
|
||||
|
||||
````md SKILL.md
|
||||
---
|
||||
name: random-number
|
||||
description: Generate a random integer between min and max.
|
||||
---
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node /opt/skills/random-number/random-number.cjs <min> <max>
|
||||
```
|
||||
````
|
||||
|
||||
```bash
|
||||
npx esbuild src/random-number.ts --bundle --format=cjs --platform=node --target=node18 --outfile=dist/random-number.cjs
|
||||
```
|
||||
|
||||
### Upload files
|
||||
|
||||
```ts
|
||||
import fs from "node:fs";
|
||||
|
||||
const script = await fs.promises.readFile("./dist/random-number.cjs");
|
||||
await sdk.writeFsFile({ path: "/opt/skills/random-number/random-number.cjs" }, script);
|
||||
|
||||
const skill = await fs.promises.readFile("./SKILL.md");
|
||||
await sdk.writeFsFile({ path: "/opt/skills/random-number/SKILL.md" }, skill);
|
||||
```
|
||||
|
||||
### Use in a session
|
||||
|
||||
```ts
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Use the random-number skill to pick a number from 1 to 100." },
|
||||
]);
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The sandbox runtime must include Node.js (or your chosen runtime).
|
||||
- For persistent skill-source wiring by directory, see [Skills](/skills-config).
|
||||
71
.agents/skills/sandbox-agent/references/daemon.md
Normal file
71
.agents/skills/sandbox-agent/references/daemon.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Daemon
|
||||
|
||||
> Source: `docs/daemon.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/daemon
|
||||
> Description: Background daemon lifecycle and management.
|
||||
|
||||
---
|
||||
The sandbox-agent daemon is a background server process. Commands like `sandbox-agent opencode` and `gigacode` can ensure it is running.
|
||||
|
||||
## How it works
|
||||
|
||||
1. A daemon-aware command checks for a healthy daemon at host/port.
|
||||
2. If missing, it starts one in the background and records PID/version files.
|
||||
3. Subsequent checks can compare build/version and restart when required.
|
||||
|
||||
## Auto-upgrade behavior
|
||||
|
||||
- `sandbox-agent opencode` and `gigacode` use ensure-running behavior with upgrade checks.
|
||||
- `sandbox-agent daemon start` uses direct start by default.
|
||||
- `sandbox-agent daemon start --upgrade` uses ensure-running behavior (including version check/restart).
|
||||
|
||||
## Managing the daemon
|
||||
|
||||
### Start
|
||||
|
||||
```bash
|
||||
sandbox-agent daemon start [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-H, --host ` | `127.0.0.1` | Host |
|
||||
| `-p, --port ` | `2468` | Port |
|
||||
| `--upgrade` | false | Use ensure-running + upgrade behavior |
|
||||
|
||||
```bash
|
||||
sandbox-agent daemon start
|
||||
sandbox-agent daemon start --upgrade
|
||||
```
|
||||
|
||||
### Stop
|
||||
|
||||
```bash
|
||||
sandbox-agent daemon stop [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-H, --host ` | `127.0.0.1` | Host |
|
||||
| `-p, --port ` | `2468` | Port |
|
||||
|
||||
### Status
|
||||
|
||||
```bash
|
||||
sandbox-agent daemon status [OPTIONS]
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `-H, --host ` | `127.0.0.1` | Host |
|
||||
| `-p, --port ` | `2468` | Port |
|
||||
|
||||
## Files
|
||||
|
||||
Daemon state is stored under the sandbox-agent data directory (for example `~/.local/share/sandbox-agent/daemon/`):
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `daemon-{host}-{port}.pid` | PID of running daemon |
|
||||
| `daemon-{host}-{port}.version` | Build/version marker |
|
||||
| `daemon-{host}-{port}.log` | Daemon stdout/stderr log |
|
||||
@@ -0,0 +1,62 @@
|
||||
# Agent Computer
|
||||
|
||||
> Source: `docs/deploy/agentcomputer.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/agentcomputer
|
||||
> Description: Run Sandbox Agent on Agent Computer managed workers.
|
||||
|
||||
---
|
||||
## Prerequisites
|
||||
|
||||
- `COMPUTER_API_KEY` or `AGENTCOMPUTER_API_KEY`
|
||||
- An Agent Computer managed-worker image, or default machine source, that already has your coding agent authenticated
|
||||
|
||||
The platform image already includes Sandbox Agent, Claude Code, and Codex. Agent credentials still live inside the machine, so make sure your image or workspace bootstrap has already handled agent login.
|
||||
|
||||
## TypeScript example
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { agentcomputer } from "sandbox-agent/agentcomputer";
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: agentcomputer(),
|
||||
});
|
||||
|
||||
try {
|
||||
const health = await sdk.getHealth();
|
||||
console.log(health.status);
|
||||
console.log(sdk.inspectorUrl);
|
||||
|
||||
const agents = await sdk.listAgents();
|
||||
console.log(agents.agents.map((agent) => agent.id));
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `agentcomputer` provider creates a managed-worker computer, waits until browser access is ready, and routes SDK requests through the machine's authenticated web host. `sdk.inspectorUrl` is already a browser-ready URL, so it opens the built-in Inspector directly.
|
||||
|
||||
By default, the provider creates:
|
||||
|
||||
- `runtimeFamily: "managed-worker"`
|
||||
- `usePlatformDefault: true`
|
||||
|
||||
Pass `create` when you want to override the handle, workspace name, or other machine-create settings:
|
||||
|
||||
```typescript
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: agentcomputer({
|
||||
create: {
|
||||
handle: "sandbox-agent-demo",
|
||||
workspaceName: "sandbox-agent-demo",
|
||||
usePlatformDefault: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Set `apiUrl` when you are targeting a self-hosted or local Agent Computer API instead of `https://api.computer.agentcomputer.ai`.
|
||||
69
.agents/skills/sandbox-agent/references/deploy/boxlite.md
Normal file
69
.agents/skills/sandbox-agent/references/deploy/boxlite.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# BoxLite
|
||||
|
||||
> Source: `docs/deploy/boxlite.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/boxlite
|
||||
> Description: Run Sandbox Agent inside a BoxLite micro-VM.
|
||||
|
||||
---
|
||||
BoxLite is a local-first micro-VM sandbox — no cloud account needed.
|
||||
See [BoxLite docs](https://docs.boxlite.ai) for platform requirements (KVM on Linux, Apple Silicon on macOS).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `@boxlite-ai/boxlite` installed (requires KVM or Apple Hypervisor)
|
||||
- Docker (to build the base image)
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
||||
|
||||
## Base image
|
||||
|
||||
Build a Docker image with Sandbox Agent pre-installed, then export it as an OCI layout
|
||||
that BoxLite can load directly (BoxLite has its own image store separate from Docker):
|
||||
|
||||
```dockerfile
|
||||
FROM node:22-bookworm-slim
|
||||
RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh
|
||||
RUN sandbox-agent install-agent claude
|
||||
RUN sandbox-agent install-agent codex
|
||||
```
|
||||
|
||||
```bash
|
||||
docker build -t sandbox-agent-boxlite .
|
||||
mkdir -p oci-image
|
||||
docker save sandbox-agent-boxlite | tar -xf - -C oci-image
|
||||
```
|
||||
|
||||
## TypeScript example
|
||||
|
||||
```typescript
|
||||
import { SimpleBox } from "@boxlite-ai/boxlite";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const env: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) env.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const box = new SimpleBox({
|
||||
rootfsPath: "./oci-image",
|
||||
env,
|
||||
ports: [{ hostPort: 3000, guestPort: 3000 }],
|
||||
diskSizeGb: 4,
|
||||
});
|
||||
|
||||
await box.exec("sh", "-c",
|
||||
"nohup sandbox-agent server --no-token --host 0.0.0.0 --port 3000 >/tmp/sandbox-agent.log 2>&1 &"
|
||||
);
|
||||
|
||||
const baseUrl = "http://localhost:3000";
|
||||
const sdk = await SandboxAgent.connect({ baseUrl });
|
||||
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const off = session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
await session.prompt([{ type: "text", text: "Summarize this repository" }]);
|
||||
off();
|
||||
|
||||
await box.stop();
|
||||
```
|
||||
188
.agents/skills/sandbox-agent/references/deploy/cloudflare.md
Normal file
188
.agents/skills/sandbox-agent/references/deploy/cloudflare.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Cloudflare
|
||||
|
||||
> Source: `docs/deploy/cloudflare.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/cloudflare
|
||||
> Description: Deploy Sandbox Agent inside a Cloudflare Sandbox.
|
||||
|
||||
---
|
||||
## Prerequisites
|
||||
|
||||
- Cloudflare account with Workers paid plan
|
||||
- Docker for local `wrangler dev`
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
||||
|
||||
Cloudflare Sandbox SDK is beta. See [Sandbox SDK docs](https://developers.cloudflare.com/sandbox/).
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
npm create cloudflare@latest -- my-sandbox --template=cloudflare/sandbox-sdk/examples/minimal
|
||||
cd my-sandbox
|
||||
```
|
||||
|
||||
## Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM cloudflare/sandbox:0.7.0
|
||||
|
||||
RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh
|
||||
RUN sandbox-agent install-agent claude && sandbox-agent install-agent codex
|
||||
|
||||
EXPOSE 8000
|
||||
```
|
||||
|
||||
## TypeScript example (with provider)
|
||||
|
||||
For standalone scripts, use the `cloudflare` provider:
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x @cloudflare/sandbox
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { cloudflare } from "sandbox-agent/cloudflare";
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: cloudflare(),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "codex" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `cloudflare` provider uses `containerFetch` under the hood, automatically stripping `AbortSignal` to avoid dropped streaming updates.
|
||||
|
||||
## TypeScript example (Durable Objects)
|
||||
|
||||
For Workers with Durable Objects, use `SandboxAgent.connect(...)` with a custom `fetch` backed by `sandbox.containerFetch(...)`:
|
||||
|
||||
```typescript
|
||||
import { getSandbox, type Sandbox } from "@cloudflare/sandbox";
|
||||
import { Hono } from "hono";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
export { Sandbox } from "@cloudflare/sandbox";
|
||||
|
||||
type Bindings = {
|
||||
Sandbox: DurableObjectNamespace<Sandbox>;
|
||||
ASSETS: Fetcher;
|
||||
ANTHROPIC_API_KEY?: string;
|
||||
OPENAI_API_KEY?: string;
|
||||
};
|
||||
|
||||
const app = new Hono<{ Bindings: Bindings }>();
|
||||
const PORT = 8000;
|
||||
|
||||
async function isServerRunning(sandbox: Sandbox): Promise<boolean> {
|
||||
try {
|
||||
const result = await sandbox.exec(`curl -sf http://localhost:${PORT}/v1/health`);
|
||||
return result.success;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getReadySandbox(name: string, env: Bindings): Promise<Sandbox> {
|
||||
const sandbox = getSandbox(env.Sandbox, name);
|
||||
if (!(await isServerRunning(sandbox))) {
|
||||
const envVars: Record<string, string> = {};
|
||||
if (env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = env.ANTHROPIC_API_KEY;
|
||||
if (env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = env.OPENAI_API_KEY;
|
||||
await sandbox.setEnvVars(envVars);
|
||||
await sandbox.startProcess(`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`);
|
||||
}
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
app.post("/sandbox/:name/prompt", async (c) => {
|
||||
const sandbox = await getReadySandbox(c.req.param("name"), c.env);
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
fetch: (input, init) =>
|
||||
sandbox.containerFetch(
|
||||
input as Request | string | URL,
|
||||
{
|
||||
...(init ?? {}),
|
||||
// Avoid passing AbortSignal through containerFetch; it can drop streamed session updates.
|
||||
signal: undefined,
|
||||
},
|
||||
PORT,
|
||||
),
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({ agent: "codex" });
|
||||
const response = await session.prompt([{ type: "text", text: "Summarize this repository" }]);
|
||||
await sdk.destroySession(session.id);
|
||||
await sdk.dispose();
|
||||
|
||||
return c.json(response);
|
||||
});
|
||||
|
||||
app.all("/sandbox/:name/proxy/*", async (c) => {
|
||||
const sandbox = await getReadySandbox(c.req.param("name"), c.env);
|
||||
const wildcard = c.req.param("*");
|
||||
const path = wildcard ? `/${wildcard}` : "/";
|
||||
const query = new URL(c.req.raw.url).search;
|
||||
|
||||
return sandbox.containerFetch(new Request(`http://localhost${path}${query}`, c.req.raw), PORT);
|
||||
});
|
||||
|
||||
app.all("*", (c) => c.env.ASSETS.fetch(c.req.raw));
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
This keeps all Sandbox Agent calls inside the Cloudflare sandbox routing path and does not require a `baseUrl`.
|
||||
|
||||
## Troubleshooting streaming updates
|
||||
|
||||
If you only receive:
|
||||
- the outbound prompt request
|
||||
- the final `{ stopReason: "end_turn" }` response
|
||||
|
||||
then the streamed update channel dropped. In Cloudflare sandbox paths, this is typically caused by forwarding `AbortSignal` from SDK fetch init into `containerFetch(...)`.
|
||||
|
||||
Fix:
|
||||
|
||||
```ts
|
||||
const sdk = await SandboxAgent.connect({
|
||||
fetch: (input, init) =>
|
||||
sandbox.containerFetch(
|
||||
input as Request | string | URL,
|
||||
{
|
||||
...(init ?? {}),
|
||||
// Avoid passing AbortSignal through containerFetch; it can drop streamed session updates.
|
||||
signal: undefined,
|
||||
},
|
||||
PORT,
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
This keeps prompt completion behavior the same, but restores streamed text/tool updates.
|
||||
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Test health:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8787/sandbox/demo/proxy/v1/health
|
||||
```
|
||||
|
||||
## Production deployment
|
||||
|
||||
```bash
|
||||
wrangler deploy
|
||||
```
|
||||
83
.agents/skills/sandbox-agent/references/deploy/computesdk.md
Normal file
83
.agents/skills/sandbox-agent/references/deploy/computesdk.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# ComputeSDK
|
||||
|
||||
> Source: `docs/deploy/computesdk.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/computesdk
|
||||
> Description: Deploy Sandbox Agent using ComputeSDK's provider-agnostic sandbox API.
|
||||
|
||||
---
|
||||
[ComputeSDK](https://computesdk.com) provides a unified interface for managing sandboxes across multiple providers. Write once, deploy anywhere by changing environment variables.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `COMPUTESDK_API_KEY` from [console.computesdk.com](https://console.computesdk.com)
|
||||
- Provider API key (one of: `E2B_API_KEY`, `DAYTONA_API_KEY`, `VERCEL_TOKEN`, `MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET`, `BLAXEL_API_KEY`, `CSB_API_KEY`)
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
||||
|
||||
## TypeScript example
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x computesdk
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { computesdk } from "sandbox-agent/computesdk";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: computesdk({
|
||||
create: {
|
||||
envs,
|
||||
image: process.env.COMPUTESDK_IMAGE,
|
||||
templateId: process.env.COMPUTESDK_TEMPLATE_ID,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `computesdk` provider handles sandbox creation, Sandbox Agent installation, agent setup, and server startup automatically. ComputeSDK routes to your configured provider behind the scenes.
|
||||
The `create` option now forwards the full ComputeSDK sandbox-create payload, including provider-specific fields such as `image` and `templateId` when the selected provider supports them.
|
||||
|
||||
Before calling `SandboxAgent.start()`, configure ComputeSDK with your provider:
|
||||
|
||||
```typescript
|
||||
import { compute } from "computesdk";
|
||||
|
||||
compute.setConfig({
|
||||
provider: "e2b", // or auto-detect via detectProvider()
|
||||
computesdkApiKey: process.env.COMPUTESDK_API_KEY,
|
||||
});
|
||||
```
|
||||
|
||||
## Supported providers
|
||||
|
||||
ComputeSDK auto-detects your provider from environment variables:
|
||||
|
||||
| Provider | Environment Variables |
|
||||
|----------|----------------------|
|
||||
| E2B | `E2B_API_KEY` |
|
||||
| Daytona | `DAYTONA_API_KEY` |
|
||||
| Vercel | `VERCEL_TOKEN` or `VERCEL_OIDC_TOKEN` |
|
||||
| Modal | `MODAL_TOKEN_ID` + `MODAL_TOKEN_SECRET` |
|
||||
| Blaxel | `BLAXEL_API_KEY` |
|
||||
| CodeSandbox | `CSB_API_KEY` |
|
||||
|
||||
## Notes
|
||||
|
||||
- **Provider resolution**: Set `COMPUTESDK_PROVIDER` to force a specific provider, or let ComputeSDK auto-detect from API keys.
|
||||
- `sandbox.runCommand(..., { background: true })` keeps the server running while your app continues.
|
||||
- `sandbox.getUrl({ port })` returns a public URL for the sandbox port.
|
||||
- Always destroy the sandbox when done to avoid leaking resources.
|
||||
70
.agents/skills/sandbox-agent/references/deploy/daytona.md
Normal file
70
.agents/skills/sandbox-agent/references/deploy/daytona.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Daytona
|
||||
|
||||
> Source: `docs/deploy/daytona.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/daytona
|
||||
> Description: Run Sandbox Agent in a Daytona workspace.
|
||||
|
||||
---
|
||||
Daytona Tier 3+ is required for access to common model provider endpoints.
|
||||
See [Daytona network limits](https://www.daytona.io/docs/en/network-limits/).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `DAYTONA_API_KEY`
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
||||
|
||||
## TypeScript example
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x @daytonaio/sdk
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { daytona } from "sandbox-agent/daytona";
|
||||
|
||||
const envVars: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: daytona({
|
||||
create: { envVars },
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `daytona` provider uses the `rivetdev/sandbox-agent:0.4.2-full` image by default and starts the server automatically.
|
||||
|
||||
## Using snapshots for faster startup
|
||||
|
||||
```typescript
|
||||
import { Daytona, Image } from "@daytonaio/sdk";
|
||||
|
||||
const daytona = new Daytona();
|
||||
const SNAPSHOT = "sandbox-agent-ready";
|
||||
|
||||
const hasSnapshot = await daytona.snapshot.get(SNAPSHOT).then(() => true, () => false);
|
||||
|
||||
if (!hasSnapshot) {
|
||||
await daytona.snapshot.create({
|
||||
name: SNAPSHOT,
|
||||
image: Image.base("ubuntu:22.04").runCommands(
|
||||
"apt-get update && apt-get install -y curl ca-certificates",
|
||||
"curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh",
|
||||
"sandbox-agent install-agent claude",
|
||||
"sandbox-agent install-agent codex",
|
||||
),
|
||||
});
|
||||
}
|
||||
```
|
||||
108
.agents/skills/sandbox-agent/references/deploy/docker.md
Normal file
108
.agents/skills/sandbox-agent/references/deploy/docker.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Docker
|
||||
|
||||
> Source: `docs/deploy/docker.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/docker
|
||||
> Description: Build and run Sandbox Agent in a Docker container.
|
||||
|
||||
---
|
||||
Docker is not recommended for production isolation of untrusted workloads. Use dedicated sandbox providers (E2B, Daytona, etc.) for stronger isolation.
|
||||
|
||||
## Quick start
|
||||
|
||||
Run the published full image with all supported agents pre-installed:
|
||||
|
||||
```bash
|
||||
docker run --rm -p 3000:3000 \
|
||||
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
||||
-e OPENAI_API_KEY="$OPENAI_API_KEY" \
|
||||
rivetdev/sandbox-agent:0.4.2-full \
|
||||
server --no-token --host 0.0.0.0 --port 3000
|
||||
```
|
||||
|
||||
The `0.4.2-full` tag pins the exact version. The moving `full` tag is also published for contributors who want the latest full image.
|
||||
|
||||
If you also want the desktop API inside the container, install desktop dependencies before starting the server:
|
||||
|
||||
```bash
|
||||
docker run --rm -p 3000:3000 \
|
||||
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
||||
-e OPENAI_API_KEY="$OPENAI_API_KEY" \
|
||||
node:22-bookworm-slim sh -c "\
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y curl ca-certificates bash libstdc++6 && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh && \
|
||||
sandbox-agent install desktop --yes && \
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 3000"
|
||||
```
|
||||
|
||||
In a Dockerfile:
|
||||
|
||||
```dockerfile
|
||||
RUN sandbox-agent install desktop --yes
|
||||
```
|
||||
|
||||
## TypeScript with dockerode
|
||||
|
||||
```typescript
|
||||
import Docker from "dockerode";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const docker = new Docker();
|
||||
const PORT = 3000;
|
||||
|
||||
const container = await docker.createContainer({
|
||||
Image: "rivetdev/sandbox-agent:0.4.2-full",
|
||||
Cmd: ["server", "--no-token", "--host", "0.0.0.0", "--port", `${PORT}`],
|
||||
Env: [
|
||||
`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`,
|
||||
`OPENAI_API_KEY=${process.env.OPENAI_API_KEY}`,
|
||||
`CODEX_API_KEY=${process.env.CODEX_API_KEY}`,
|
||||
].filter(Boolean),
|
||||
ExposedPorts: { [`${PORT}/tcp`]: {} },
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: { [`${PORT}/tcp`]: [{ HostPort: `${PORT}` }] },
|
||||
},
|
||||
});
|
||||
|
||||
await container.start();
|
||||
|
||||
const baseUrl = `http://127.0.0.1:${PORT}`;
|
||||
const sdk = await SandboxAgent.connect({ baseUrl });
|
||||
|
||||
const session = await sdk.createSession({ agent: "codex" });
|
||||
await session.prompt([{ type: "text", text: "Summarize this repository." }]);
|
||||
```
|
||||
|
||||
## Building a custom image with everything preinstalled
|
||||
|
||||
If you need to extend your own base image, install Sandbox Agent and preinstall every supported agent in one step:
|
||||
|
||||
```dockerfile
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
bash ca-certificates curl git && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh && \
|
||||
sandbox-agent install-agent --all
|
||||
|
||||
RUN useradd -m -s /bin/bash sandbox
|
||||
USER sandbox
|
||||
WORKDIR /home/sandbox
|
||||
|
||||
EXPOSE 2468
|
||||
ENTRYPOINT ["sandbox-agent"]
|
||||
CMD ["server", "--host", "0.0.0.0", "--port", "2468"]
|
||||
```
|
||||
|
||||
## Building from source
|
||||
|
||||
```bash
|
||||
docker build -f docker/release/linux-x86_64.Dockerfile -t sandbox-agent-build .
|
||||
docker run --rm -v "$PWD/artifacts:/artifacts" sandbox-agent-build
|
||||
```
|
||||
|
||||
Binary output: `./artifacts/sandbox-agent-x86_64-unknown-linux-musl`.
|
||||
54
.agents/skills/sandbox-agent/references/deploy/e2b.md
Normal file
54
.agents/skills/sandbox-agent/references/deploy/e2b.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# E2B
|
||||
|
||||
> Source: `docs/deploy/e2b.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/e2b
|
||||
> Description: Deploy Sandbox Agent inside an E2B sandbox.
|
||||
|
||||
---
|
||||
## Prerequisites
|
||||
|
||||
- `E2B_API_KEY`
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
||||
|
||||
## TypeScript example
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x @e2b/code-interpreter
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { e2b } from "sandbox-agent/e2b";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
const template = process.env.E2B_TEMPLATE;
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: e2b({
|
||||
template,
|
||||
create: { envs },
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `e2b` provider handles sandbox creation, Sandbox Agent installation, agent setup, and server startup automatically. Sandboxes pause by default instead of being deleted, and reconnecting with the same `sandboxId` resumes them automatically.
|
||||
|
||||
Pass `template` when you want to start from a custom E2B template alias or template ID. E2B base-image selection happens when you build the template, then `sandbox-agent/e2b` uses that template at sandbox creation time.
|
||||
|
||||
## Faster cold starts
|
||||
|
||||
For faster startup, create a custom E2B template with Sandbox Agent and target agents pre-installed.
|
||||
Build System 2.0 also lets you choose the template's base image in code.
|
||||
See [E2B Custom Templates](https://e2b.dev/docs/sandbox-template) and [E2B Base Images](https://e2b.dev/docs/template/base-image).
|
||||
71
.agents/skills/sandbox-agent/references/deploy/local.md
Normal file
71
.agents/skills/sandbox-agent/references/deploy/local.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Local
|
||||
|
||||
> Source: `docs/deploy/local.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/local
|
||||
> Description: Run Sandbox Agent locally for development.
|
||||
|
||||
---
|
||||
For local development, run Sandbox Agent directly on your machine.
|
||||
|
||||
## With the CLI
|
||||
|
||||
```bash
|
||||
# Install
|
||||
curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh
|
||||
|
||||
# Run
|
||||
sandbox-agent server --no-token --host 127.0.0.1 --port 2468
|
||||
```
|
||||
|
||||
Or with npm/Bun:
|
||||
|
||||
#### npx
|
||||
|
||||
```bash
|
||||
npx @sandbox-agent/cli@0.4.x server --no-token --host 127.0.0.1 --port 2468
|
||||
```
|
||||
|
||||
#### bunx
|
||||
|
||||
```bash
|
||||
bunx @sandbox-agent/cli@0.4.x server --no-token --host 127.0.0.1 --port 2468
|
||||
```
|
||||
|
||||
## With the TypeScript SDK
|
||||
|
||||
The SDK can spawn and manage the server as a subprocess using the `local` provider:
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { local } from "sandbox-agent/local";
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: local(),
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository." },
|
||||
]);
|
||||
|
||||
await sdk.destroySandbox();
|
||||
```
|
||||
|
||||
This starts the server on an available local port and connects automatically.
|
||||
|
||||
Pass options to customize the local provider:
|
||||
|
||||
```typescript
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: local({
|
||||
port: 3000,
|
||||
log: "inherit",
|
||||
env: {
|
||||
ANTHROPIC_API_KEY: process.env.MY_ANTHROPIC_KEY,
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
57
.agents/skills/sandbox-agent/references/deploy/modal.md
Normal file
57
.agents/skills/sandbox-agent/references/deploy/modal.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Modal
|
||||
|
||||
> Source: `docs/deploy/modal.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/modal
|
||||
> Description: Deploy Sandbox Agent inside a Modal sandbox.
|
||||
|
||||
---
|
||||
## Prerequisites
|
||||
|
||||
- `MODAL_TOKEN_ID` and `MODAL_TOKEN_SECRET` from [modal.com/settings](https://modal.com/settings)
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
||||
|
||||
## TypeScript example
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x modal
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { modal } from "sandbox-agent/modal";
|
||||
|
||||
const secrets: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) secrets.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) secrets.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
const baseImage = process.env.MODAL_BASE_IMAGE ?? "node:22-slim";
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: modal({
|
||||
image: baseImage,
|
||||
create: { secrets },
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `modal` provider handles app creation, image building, sandbox provisioning, agent installation, server startup, and tunnel networking automatically.
|
||||
Set `image` to change the base Docker image before Sandbox Agent and its agent binaries are layered on top. You can also pass a prebuilt Modal `Image` object.
|
||||
|
||||
## Faster cold starts
|
||||
|
||||
Modal caches image layers, so the Dockerfile commands that install `curl` and `sandbox-agent` only run on the first build. Subsequent sandbox creates reuse the cached image.
|
||||
|
||||
## Notes
|
||||
|
||||
- Modal sandboxes use [gVisor](https://gvisor.dev/) for strong isolation.
|
||||
- Ports are exposed via encrypted tunnels (`encryptedPorts`). The provider uses `sb.tunnels()` to get the public HTTPS URL.
|
||||
- Environment variables (API keys) are passed as Modal [Secrets](https://modal.com/docs/guide/secrets) for security.
|
||||
52
.agents/skills/sandbox-agent/references/deploy/vercel.md
Normal file
52
.agents/skills/sandbox-agent/references/deploy/vercel.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Vercel
|
||||
|
||||
> Source: `docs/deploy/vercel.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/deploy/vercel
|
||||
> Description: Deploy Sandbox Agent inside a Vercel Sandbox.
|
||||
|
||||
---
|
||||
## Prerequisites
|
||||
|
||||
- `VERCEL_OIDC_TOKEN` or `VERCEL_ACCESS_TOKEN`
|
||||
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`
|
||||
|
||||
## TypeScript example
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x @vercel/sandbox
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { vercel } from "sandbox-agent/vercel";
|
||||
|
||||
const env: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) env.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) env.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: vercel({
|
||||
create: {
|
||||
runtime: "node24",
|
||||
env,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
const session = await sdk.createSession({ agent: "claude" });
|
||||
const response = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository" },
|
||||
]);
|
||||
console.log(response.stopReason);
|
||||
} finally {
|
||||
await sdk.destroySandbox();
|
||||
}
|
||||
```
|
||||
|
||||
The `vercel` provider handles sandbox creation, Sandbox Agent installation, agent setup, and server startup automatically.
|
||||
|
||||
## Authentication
|
||||
|
||||
Vercel Sandboxes support OIDC token auth (recommended) and access-token auth.
|
||||
See [Vercel Sandbox docs](https://vercel.com/docs/functions/sandbox).
|
||||
144
.agents/skills/sandbox-agent/references/file-system.md
Normal file
144
.agents/skills/sandbox-agent/references/file-system.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# File System
|
||||
|
||||
> Source: `docs/file-system.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/file-system
|
||||
> Description: Read, write, and manage files inside the sandbox.
|
||||
|
||||
---
|
||||
The filesystem API lets you list, read, write, move, and delete files inside the sandbox, plus upload tar archives in batch.
|
||||
|
||||
## Path resolution
|
||||
|
||||
- Absolute paths are used as-is.
|
||||
- Relative paths resolve from the server process working directory.
|
||||
- Requests that attempt to escape allowed roots are rejected by the server.
|
||||
|
||||
## List entries
|
||||
|
||||
```ts TypeScript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const entries = await sdk.listFsEntries({
|
||||
path: "./workspace",
|
||||
});
|
||||
|
||||
console.log(entries);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X GET "http://127.0.0.1:2468/v1/fs/entries?path=./workspace"
|
||||
```
|
||||
|
||||
## Read and write files
|
||||
|
||||
`PUT /v1/fs/file` writes raw bytes. `GET /v1/fs/file` returns raw bytes.
|
||||
|
||||
```ts TypeScript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
await sdk.writeFsFile({ path: "./notes.txt" }, "hello");
|
||||
|
||||
const bytes = await sdk.readFsFile({ path: "./notes.txt" });
|
||||
const text = new TextDecoder().decode(bytes);
|
||||
|
||||
console.log(text);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=./notes.txt" \
|
||||
--data-binary "hello"
|
||||
|
||||
curl -X GET "http://127.0.0.1:2468/v1/fs/file?path=./notes.txt" \
|
||||
--output ./notes.txt
|
||||
```
|
||||
|
||||
## Create directories
|
||||
|
||||
```ts TypeScript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
await sdk.mkdirFs({ path: "./data" });
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/fs/mkdir?path=./data"
|
||||
```
|
||||
|
||||
## Move, delete, and stat
|
||||
|
||||
```ts TypeScript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
await sdk.moveFs({
|
||||
from: "./notes.txt",
|
||||
to: "./notes-old.txt",
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
const stat = await sdk.statFs({ path: "./notes-old.txt" });
|
||||
await sdk.deleteFsEntry({ path: "./notes-old.txt" });
|
||||
|
||||
console.log(stat);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/fs/move" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"from":"./notes.txt","to":"./notes-old.txt","overwrite":true}'
|
||||
|
||||
curl -X GET "http://127.0.0.1:2468/v1/fs/stat?path=./notes-old.txt"
|
||||
|
||||
curl -X DELETE "http://127.0.0.1:2468/v1/fs/entry?path=./notes-old.txt"
|
||||
```
|
||||
|
||||
## Batch upload (tar)
|
||||
|
||||
Batch upload accepts `application/x-tar` and extracts into the destination directory.
|
||||
|
||||
```ts TypeScript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import tar from "tar";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const archivePath = path.join(process.cwd(), "skills.tar");
|
||||
await tar.c({
|
||||
cwd: "./skills",
|
||||
file: archivePath,
|
||||
}, ["."]);
|
||||
|
||||
const tarBuffer = await fs.promises.readFile(archivePath);
|
||||
const result = await sdk.uploadFsBatch(tarBuffer, {
|
||||
path: "./skills",
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
tar -cf skills.tar -C ./skills .
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/fs/upload-batch?path=./skills" \
|
||||
-H "Content-Type: application/x-tar" \
|
||||
--data-binary @skills.tar
|
||||
```
|
||||
66
.agents/skills/sandbox-agent/references/inspector.md
Normal file
66
.agents/skills/sandbox-agent/references/inspector.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Inspector
|
||||
|
||||
> Source: `docs/inspector.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/inspector
|
||||
> Description: Debug and inspect agent sessions with the Inspector UI.
|
||||
|
||||
---
|
||||
The Inspector is a web UI for inspecting Sandbox Agent sessions. Use it to view events, inspect payloads, and troubleshoot behavior.
|
||||
|
||||

|
||||
|
||||
## Open the Inspector
|
||||
|
||||
The Inspector is served at `/ui/` on your Sandbox Agent server.
|
||||
For example, if your server runs at `http://localhost:2468`, open `http://localhost:2468/ui/`.
|
||||
|
||||
You can also generate a pre-filled Inspector URL from the SDK:
|
||||
|
||||
```typescript
|
||||
import { buildInspectorUrl } from "sandbox-agent";
|
||||
|
||||
const url = buildInspectorUrl({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
console.log(url);
|
||||
// http://127.0.0.1:2468/ui/
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Session list
|
||||
- Event stream view
|
||||
- Event JSON inspector
|
||||
- Prompt testing
|
||||
- Request/response debugging
|
||||
- Interactive permission prompts (approve, always-allow, or reject tool-use requests)
|
||||
- Desktop panel for status, remediation, start/stop, and screenshot refresh
|
||||
- Process management (create, stop, kill, delete, view logs)
|
||||
- Interactive PTY terminal for tty processes
|
||||
- One-shot command execution
|
||||
|
||||
## When to use
|
||||
|
||||
- Development: validate session behavior quickly
|
||||
- Debugging: inspect raw event payloads
|
||||
- Integration work: compare UI behavior with SDK/API calls
|
||||
|
||||
## Process terminal
|
||||
|
||||
The Inspector includes an embedded Ghostty-based terminal for interactive tty
|
||||
processes. The UI uses the SDK's high-level `connectProcessTerminal(...)`
|
||||
wrapper via the shared `@sandbox-agent/react` `ProcessTerminal` component.
|
||||
|
||||
## Desktop panel
|
||||
|
||||
The `Desktop` panel shows the current desktop runtime state, missing dependencies,
|
||||
the suggested install command, last error details, process/log paths, and the
|
||||
latest captured screenshot.
|
||||
|
||||
Use it to:
|
||||
|
||||
- Check whether desktop dependencies are installed
|
||||
- Start or stop the managed desktop runtime
|
||||
- Refresh desktop status
|
||||
- Capture a fresh screenshot on demand
|
||||
241
.agents/skills/sandbox-agent/references/llm-credentials.md
Normal file
241
.agents/skills/sandbox-agent/references/llm-credentials.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# LLM Credentials
|
||||
|
||||
> Source: `docs/llm-credentials.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/llm-credentials
|
||||
> Description: Strategies for providing LLM provider credentials to agents.
|
||||
|
||||
---
|
||||
Sandbox Agent needs LLM provider credentials (Anthropic, OpenAI, etc.) to run agent sessions.
|
||||
|
||||
## Configuration
|
||||
|
||||
Pass credentials via `spawn.env` when starting a sandbox. Each call to `SandboxAgent.start()` can use different credentials:
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
spawn: {
|
||||
env: {
|
||||
ANTHROPIC_API_KEY: "sk-ant-...",
|
||||
OPENAI_API_KEY: "sk-...",
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Each agent requires credentials from a specific provider. Sandbox Agent checks environment variables (including those passed via `spawn.env`) and host config files:
|
||||
|
||||
| Agent | Provider | Environment variables | Config files |
|
||||
|-------|----------|----------------------|--------------|
|
||||
| Claude Code | Anthropic | `ANTHROPIC_API_KEY`, `CLAUDE_API_KEY` | `~/.claude.json`, `~/.claude/.credentials.json` |
|
||||
| Amp | Anthropic | `ANTHROPIC_API_KEY`, `CLAUDE_API_KEY` | `~/.amp/config.json` |
|
||||
| Codex | OpenAI | `OPENAI_API_KEY`, `CODEX_API_KEY` | `~/.codex/auth.json` |
|
||||
| OpenCode | Anthropic or OpenAI | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` | `~/.local/share/opencode/auth.json` |
|
||||
| Mock | None | - | - |
|
||||
|
||||
## Credential strategies
|
||||
|
||||
LLM credentials are passed into the sandbox as environment variables. The agent and everything inside the sandbox has access to the token, so it's important to choose the right strategy for how you provision and scope these credentials.
|
||||
|
||||
| Strategy | Who pays | Cost attribution | Best for |
|
||||
|----------|----------|-----------------|----------|
|
||||
| **Per-tenant gateway** (recommended) | Your organization, billed back per tenant | Per-tenant keys with budgets | Multi-tenant SaaS, usage-based billing |
|
||||
| **Bring your own key** | Each user (usage-based) | Per-user by default | Dev environments, internal tools |
|
||||
| **Shared API key** | Your organization | None (single bill) | Single-tenant apps, internal platforms |
|
||||
| **Personal subscription** | Each user (existing subscription) | Per-user by default | Local dev, internal tools where users have Claude or Codex subscriptions |
|
||||
|
||||
### Per-tenant gateway (recommended)
|
||||
|
||||
Route LLM traffic through a gateway that mints per-tenant API keys, each with its own spend tracking and budget limits.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
B[Your Backend] -->|tenant key| S[Sandbox]
|
||||
S -->|LLM requests| G[Gateway]
|
||||
G -->|scoped key| P[LLM Provider]
|
||||
```
|
||||
|
||||
Your backend issues a scoped key per tenant, then passes it to the sandbox. This is the typical pattern when using sandbox providers (E2B, Daytona, Docker).
|
||||
|
||||
```typescript expandable
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
async function createTenantSandbox(tenantId: string) {
|
||||
// Issue a scoped key for this tenant via OpenRouter
|
||||
const res = await fetch("https://openrouter.ai/api/v1/keys", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.OPENROUTER_PROVISIONING_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: `tenant-${tenantId}`,
|
||||
limit: 50,
|
||||
limitResetType: "monthly",
|
||||
}),
|
||||
});
|
||||
const { key } = await res.json();
|
||||
|
||||
// Start a sandbox with the tenant's scoped key
|
||||
const sdk = await SandboxAgent.start({
|
||||
spawn: {
|
||||
env: {
|
||||
OPENAI_API_KEY: key, // OpenRouter uses OpenAI-compatible endpoints
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: { cwd: "/workspace" },
|
||||
});
|
||||
|
||||
return { sdk, session };
|
||||
}
|
||||
```
|
||||
|
||||
#### Security
|
||||
|
||||
Recommended for multi-tenant applications. Each tenant gets a scoped key with its own budget, so exfiltration only exposes that tenant's allowance.
|
||||
|
||||
#### Use cases
|
||||
|
||||
- **Multi-tenant SaaS**: per-tenant spend tracking and budget limits
|
||||
- **Production apps**: exposed to end users who need isolated credentials
|
||||
- **Usage-based billing**: each tenant pays for their own consumption
|
||||
|
||||
#### Choosing a gateway
|
||||
|
||||
#### OpenRouter provisioned keys
|
||||
|
||||
Managed service, zero infrastructure. [OpenRouter](https://openrouter.ai/docs/features/provisioning-api-keys) provides per-tenant API keys with spend tracking and budget limits via their Provisioning API. Pass the tenant key to Sandbox Agent as `OPENAI_API_KEY` (OpenRouter uses OpenAI-compatible endpoints).
|
||||
|
||||
```bash
|
||||
# Create a key for a tenant with a $50/month budget
|
||||
curl https://openrouter.ai/api/v1/keys \
|
||||
-H "Authorization: Bearer $PROVISIONING_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "tenant-acme",
|
||||
"limit": 50,
|
||||
"limitResetType": "monthly"
|
||||
}'
|
||||
```
|
||||
|
||||
Easiest to set up but not open-source. See [OpenRouter pricing](https://openrouter.ai/docs/framework/pricing) for details.
|
||||
|
||||
#### LiteLLM proxy
|
||||
|
||||
Self-hosted, open-source (MIT). [LiteLLM](https://github.com/BerriAI/litellm) is an OpenAI-compatible proxy with hierarchical budgets (org, team, user, key), virtual keys, and spend tracking. Requires Python + PostgreSQL.
|
||||
|
||||
```bash
|
||||
# Create a team (tenant) with a $500 budget
|
||||
curl http://litellm:4000/team/new \
|
||||
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"team_alias": "tenant-acme",
|
||||
"max_budget": 500
|
||||
}'
|
||||
|
||||
# Generate a key for that team
|
||||
curl http://litellm:4000/key/generate \
|
||||
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"team_id": "team-abc123",
|
||||
"max_budget": 100
|
||||
}'
|
||||
```
|
||||
|
||||
Full control with no vendor lock-in. Organization-level features require an enterprise license.
|
||||
|
||||
#### Portkey gateway
|
||||
|
||||
Self-hosted, open-source (Apache 2.0). [Portkey](https://github.com/Portkey-AI/gateway) is a lightweight OpenAI-compatible gateway supporting 200+ providers. Single binary, no database required. Create virtual keys with per-tenant budget limits and pass them to Sandbox Agent.
|
||||
|
||||
Lightest operational footprint of the self-hosted options. Observability and analytics require the managed platform or your own tooling.
|
||||
|
||||
To bill tenants for LLM usage, use [Stripe token billing](https://docs.stripe.com/billing/token-billing) (integrates natively with OpenRouter) or query your gateway's spend API and feed usage into your billing system.
|
||||
|
||||
### Bring your own key
|
||||
|
||||
Each user provides their own API key. Users are billed directly by the LLM provider with no additional infrastructure needed.
|
||||
|
||||
Pass the user's key via `spawn.env`:
|
||||
|
||||
```typescript
|
||||
const sdk = await SandboxAgent.start({
|
||||
spawn: {
|
||||
env: {
|
||||
ANTHROPIC_API_KEY: userProvidedKey,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Security
|
||||
|
||||
API keys are typically long-lived. The key is visible to the agent and anything running inside the sandbox, so exfiltration is possible. This is usually acceptable for developer-facing tools where the user owns the key.
|
||||
|
||||
#### Use cases
|
||||
|
||||
- **Developer tools**: each user manages their own API key
|
||||
- **Internal platforms**: users already have LLM provider accounts
|
||||
- **Per-user billing**: no extra infrastructure needed
|
||||
|
||||
### Shared credentials
|
||||
|
||||
A single organization-wide API key is used for all sessions. All token usage appears on one bill with no per-user or per-tenant cost attribution.
|
||||
|
||||
```typescript
|
||||
const sdk = await SandboxAgent.start({
|
||||
spawn: {
|
||||
env: {
|
||||
ANTHROPIC_API_KEY: process.env.ORG_ANTHROPIC_KEY!,
|
||||
OPENAI_API_KEY: process.env.ORG_OPENAI_KEY!,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
If you need to track or limit spend per tenant, use a per-tenant gateway instead.
|
||||
|
||||
#### Security
|
||||
|
||||
Not recommended for anything other than internal tooling. A single exfiltrated key exposes your organization's entire LLM budget. If you need org-paid credentials for external users, use a per-tenant gateway with scoped keys instead.
|
||||
|
||||
#### Use cases
|
||||
|
||||
- **Single-tenant apps**: small number of users, one bill
|
||||
- **Prototyping**: cost attribution not needed yet
|
||||
- **Simplicity over security**: acceptable when exfiltration risk is low
|
||||
|
||||
### Personal subscription
|
||||
|
||||
If the user is signed into Claude Code or Codex on the host machine, Sandbox Agent automatically picks up their OAuth tokens. No configuration is needed.
|
||||
|
||||
#### Remote sandboxes
|
||||
|
||||
Extract credentials locally and pass them to a remote sandbox via `spawn.env`:
|
||||
|
||||
```bash
|
||||
$ sandbox-agent credentials extract-env
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
CLAUDE_API_KEY=sk-ant-...
|
||||
OPENAI_API_KEY=sk-...
|
||||
CODEX_API_KEY=sk-...
|
||||
```
|
||||
|
||||
Use `-e` to prefix with `export` for shell sourcing.
|
||||
|
||||
#### Security
|
||||
|
||||
Personal subscriptions use OAuth tokens with a limited lifespan. These are the same credentials used when running an agent normally on the host. If a token is exfiltrated from the sandbox, the exposure window is short.
|
||||
|
||||
#### Use cases
|
||||
|
||||
- **Local development**: users are already signed into Claude Code or Codex
|
||||
- **Internal tools**: every user has their own subscription
|
||||
- **Prototyping**: no key management needed
|
||||
260
.agents/skills/sandbox-agent/references/manage-sessions.md
Normal file
260
.agents/skills/sandbox-agent/references/manage-sessions.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# Manage Sessions
|
||||
|
||||
> Source: `docs/manage-sessions.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/manage-sessions
|
||||
> Description: Persist and replay agent transcripts across connections.
|
||||
|
||||
---
|
||||
Sandbox Agent stores sessions in memory only. When the server restarts or the sandbox is destroyed, all session data is lost. It's your responsibility to persist events to your own database.
|
||||
|
||||
## Recommended approach
|
||||
|
||||
1. Store events to your database as they arrive
|
||||
2. On reconnect, get the last event's `sequence` and pass it as `offset`
|
||||
3. The API returns events where `sequence > offset`
|
||||
|
||||
This prevents duplicate writes and lets you recover from disconnects.
|
||||
|
||||
## Receiving Events
|
||||
|
||||
Two ways to receive events: streaming (recommended) or polling.
|
||||
|
||||
### Streaming
|
||||
|
||||
Use streaming for real-time events with automatic reconnection support.
|
||||
|
||||
```typescript
|
||||
import { SandboxAgentClient } from "sandbox-agent";
|
||||
|
||||
const client = new SandboxAgentClient({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
agent: "mock",
|
||||
});
|
||||
|
||||
// Get offset from last stored event (0 returns all events)
|
||||
const lastEvent = await db.getLastEvent("my-session");
|
||||
const offset = lastEvent?.sequence ?? 0;
|
||||
|
||||
// Stream from where you left off
|
||||
for await (const event of client.streamEvents("my-session", { offset })) {
|
||||
await db.insertEvent("my-session", event);
|
||||
}
|
||||
```
|
||||
|
||||
### Polling
|
||||
|
||||
If you can't use streaming, poll the events endpoint:
|
||||
|
||||
```typescript
|
||||
const lastEvent = await db.getLastEvent("my-session");
|
||||
let offset = lastEvent?.sequence ?? 0;
|
||||
|
||||
while (true) {
|
||||
const { events } = await client.getEvents("my-session", {
|
||||
offset,
|
||||
limit: 100
|
||||
});
|
||||
|
||||
for (const event of events) {
|
||||
await db.insertEvent("my-session", event);
|
||||
offset = event.sequence;
|
||||
}
|
||||
|
||||
await sleep(1000);
|
||||
}
|
||||
```
|
||||
|
||||
## Database options
|
||||
|
||||
Choose where to persist events based on your requirements. For most use cases, we recommend Rivet Actors.
|
||||
|
||||
| | Durable | Real-time | Multiplayer | Scaling | Throughput | Complexity |
|
||||
|---------|:-------:|:---------:|:-----------:|---------|------------|------------|
|
||||
| Rivet Actors | ✓ | ✓ | ✓ | Auto-sharded, one actor per session | Millions of concurrent sessions | Zero infrastructure |
|
||||
| PostgreSQL | ✓ | | | Manual sharding | Connection pool limited | Connection pools, migrations |
|
||||
| Redis | | ✓ | | Redis Cluster | High, in-memory | Memory management, Sentinel for failover |
|
||||
|
||||
### Rivet Actors
|
||||
|
||||
For production workloads, [Rivet Actors](https://rivet.gg) provide a managed solution for:
|
||||
|
||||
- **Persistent state**: Events survive crashes and restarts
|
||||
- **Real-time streaming**: Built-in WebSocket support for clients
|
||||
- **Horizontal scaling**: Run thousands of concurrent sessions
|
||||
- **Observability**: Built-in logging and metrics
|
||||
|
||||
#### Actor
|
||||
|
||||
```typescript
|
||||
import { actor } from "rivetkit";
|
||||
import { Daytona } from "@daytonaio/sdk";
|
||||
import { SandboxAgent, SandboxAgentClient, AgentEvent } from "sandbox-agent";
|
||||
|
||||
interface CodingSessionState {
|
||||
sandboxId: string;
|
||||
baseUrl: string;
|
||||
sessionId: string;
|
||||
events: AgentEvent[];
|
||||
}
|
||||
|
||||
interface CodingSessionVars {
|
||||
client: SandboxAgentClient;
|
||||
}
|
||||
|
||||
const daytona = new Daytona();
|
||||
|
||||
const codingSession = actor({
|
||||
createState: async (): Promise<CodingSessionState> => {
|
||||
const sandbox = await daytona.create({
|
||||
snapshot: "sandbox-agent-ready",
|
||||
envVars: {
|
||||
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
||||
},
|
||||
autoStopInterval: 0,
|
||||
});
|
||||
|
||||
await sandbox.process.executeCommand(
|
||||
"nohup sandbox-agent server --no-token --host 0.0.0.0 --port 3000 &"
|
||||
);
|
||||
|
||||
const baseUrl = (await sandbox.getSignedPreviewUrl(3000)).url;
|
||||
const sessionId = crypto.randomUUID();
|
||||
|
||||
return {
|
||||
sandboxId: sandbox.id,
|
||||
baseUrl,
|
||||
sessionId,
|
||||
events: [],
|
||||
};
|
||||
},
|
||||
|
||||
createVars: async (c): Promise<CodingSessionVars> => {
|
||||
const client = new SandboxAgentClient({
|
||||
baseUrl: c.state.baseUrl,
|
||||
agent: "mock",
|
||||
});
|
||||
await client.createSession(c.state.sessionId, { agent: "claude" });
|
||||
return { client };
|
||||
},
|
||||
|
||||
onDestroy: async (c) => {
|
||||
const sandbox = await daytona.get(c.state.sandboxId);
|
||||
await sandbox.delete();
|
||||
},
|
||||
|
||||
run: async (c) => {
|
||||
for await (const event of c.vars.client.streamEvents(c.state.sessionId)) {
|
||||
c.state.events.push(event);
|
||||
c.broadcast("agentEvent", event);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
postMessage: async (c, message: string) => {
|
||||
await c.vars.client.postMessage(c.state.sessionId, message);
|
||||
},
|
||||
|
||||
getTranscript: (c) => c.state.events,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Client
|
||||
|
||||
```typescript TypeScript
|
||||
import { createClient } from "rivetkit/client";
|
||||
|
||||
const client = createClient();
|
||||
const session = client.codingSession.getOrCreate(["my-session"]);
|
||||
|
||||
const conn = session.connect();
|
||||
conn.on("agentEvent", (event) => {
|
||||
console.log(event.type, event.data);
|
||||
});
|
||||
|
||||
await conn.postMessage("Create a new React component for user profiles");
|
||||
|
||||
const transcript = await conn.getTranscript();
|
||||
```
|
||||
|
||||
```typescript React
|
||||
import { createRivetKit } from "@rivetkit/react";
|
||||
|
||||
const { useActor } = createRivetKit();
|
||||
|
||||
function CodingSession() {
|
||||
const [messages, setMessages] = useState<AgentEvent[]>([]);
|
||||
const session = useActor({ name: "codingSession", key: ["my-session"] });
|
||||
|
||||
session.useEvent("agentEvent", (event) => {
|
||||
setMessages((prev) => [...prev, event]);
|
||||
});
|
||||
|
||||
const sendPrompt = async (prompt: string) => {
|
||||
await session.connection?.postMessage(prompt);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{messages.map((msg, i) => (
|
||||
<div key={i}>{JSON.stringify(msg)}</div>
|
||||
))}
|
||||
<button onClick={() => sendPrompt("Build a login page")}>
|
||||
Send Prompt
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```sql
|
||||
CREATE TABLE agent_events (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
native_session_id TEXT,
|
||||
sequence INTEGER NOT NULL,
|
||||
time TIMESTAMPTZ NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
synthetic BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
data JSONB NOT NULL,
|
||||
UNIQUE(session_id, sequence)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_events_session ON agent_events(session_id, sequence);
|
||||
```
|
||||
|
||||
### Redis
|
||||
|
||||
```typescript
|
||||
// Append event to list
|
||||
await redis.rpush(`session:${sessionId}`, JSON.stringify(event));
|
||||
|
||||
// Get events from offset
|
||||
const events = await redis.lrange(`session:${sessionId}`, offset, -1);
|
||||
```
|
||||
|
||||
## Handling disconnects
|
||||
|
||||
The event stream may disconnect due to network issues. Handle reconnection gracefully:
|
||||
|
||||
```typescript
|
||||
async function streamWithRetry(sessionId: string) {
|
||||
while (true) {
|
||||
try {
|
||||
const lastEvent = await db.getLastEvent(sessionId);
|
||||
const offset = lastEvent?.sequence ?? 0;
|
||||
|
||||
for await (const event of client.streamEvents(sessionId, { offset })) {
|
||||
await db.insertEvent(sessionId, event);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Stream disconnected, reconnecting...", error);
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
82
.agents/skills/sandbox-agent/references/mcp-config.md
Normal file
82
.agents/skills/sandbox-agent/references/mcp-config.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# MCP
|
||||
|
||||
> Source: `docs/mcp-config.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/mcp-config
|
||||
> Description: Configure MCP servers for agent sessions.
|
||||
|
||||
---
|
||||
MCP (Model Context Protocol) servers extend agents with tools and external context.
|
||||
|
||||
## Configuring MCP servers
|
||||
|
||||
The HTTP config endpoints let you store/retrieve MCP server configs by directory + name.
|
||||
|
||||
```ts
|
||||
// Create MCP config
|
||||
await sdk.setMcpConfig(
|
||||
{
|
||||
directory: "/workspace",
|
||||
mcpName: "github",
|
||||
},
|
||||
{
|
||||
type: "remote",
|
||||
url: "https://example.com/mcp",
|
||||
},
|
||||
);
|
||||
|
||||
// Create a session using the configured MCP servers
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Use available MCP servers to help with this task." },
|
||||
]);
|
||||
|
||||
// List MCP configs
|
||||
const config = await sdk.getMcpConfig({
|
||||
directory: "/workspace",
|
||||
mcpName: "github",
|
||||
});
|
||||
|
||||
console.log(config.type);
|
||||
|
||||
// Delete MCP config
|
||||
await sdk.deleteMcpConfig({
|
||||
directory: "/workspace",
|
||||
mcpName: "github",
|
||||
});
|
||||
```
|
||||
|
||||
## Config fields
|
||||
|
||||
### Local server
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `type` | `local` |
|
||||
| `command` | executable path |
|
||||
| `args` | array of CLI args |
|
||||
| `env` | environment variable map |
|
||||
| `cwd` | working directory |
|
||||
| `enabled` | enable/disable server |
|
||||
| `timeoutMs` | timeout override |
|
||||
|
||||
### Remote server
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `type` | `remote` |
|
||||
| `url` | MCP server URL |
|
||||
| `transport` | `http` or `sse` |
|
||||
| `headers` | static headers map |
|
||||
| `bearerTokenEnvVar` | env var name to inject in auth header |
|
||||
| `envHeaders` | header name to env var map |
|
||||
| `oauth` | optional OAuth config object |
|
||||
| `enabled` | enable/disable server |
|
||||
| `timeoutMs` | timeout override |
|
||||
|
||||
## Custom MCP servers
|
||||
|
||||
To bundle and upload your own MCP server into the sandbox, see [Custom Tools](/custom-tools).
|
||||
144
.agents/skills/sandbox-agent/references/multiplayer.md
Normal file
144
.agents/skills/sandbox-agent/references/multiplayer.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Multiplayer
|
||||
|
||||
> Source: `docs/multiplayer.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/multiplayer
|
||||
> Description: Use Rivet Actors to coordinate shared sessions.
|
||||
|
||||
---
|
||||
For multiplayer orchestration, use [Rivet Actors](https://rivet.dev/docs/actors).
|
||||
|
||||
Recommended model:
|
||||
|
||||
- One actor per collaborative workspace/thread.
|
||||
- The actor owns Sandbox Agent session lifecycle and persistence.
|
||||
- Clients connect to the actor and receive realtime broadcasts.
|
||||
|
||||
Use [actor keys](https://rivet.dev/docs/actors/keys) to map each workspace to one actor, [events](https://rivet.dev/docs/actors/events) for realtime updates, and [lifecycle hooks](https://rivet.dev/docs/actors/lifecycle) for cleanup.
|
||||
|
||||
## Example
|
||||
|
||||
```ts Actor (server)
|
||||
import { actor, setup } from "rivetkit";
|
||||
import { SandboxAgent, type SessionPersistDriver, type SessionRecord, type SessionEvent, type ListPageRequest, type ListPage, type ListEventsRequest } from "sandbox-agent";
|
||||
|
||||
interface RivetPersistData { sessions: Record<string, SessionRecord>; events: Record<string, SessionEvent[]>; }
|
||||
type RivetPersistState = { _sandboxAgentPersist: RivetPersistData };
|
||||
|
||||
class RivetSessionPersistDriver implements SessionPersistDriver {
|
||||
private readonly stateKey: string;
|
||||
private readonly ctx: { state: Record<string, unknown> };
|
||||
constructor(ctx: { state: Record<string, unknown> }, options: { stateKey?: string } = {}) {
|
||||
this.ctx = ctx;
|
||||
this.stateKey = options.stateKey ?? "_sandboxAgentPersist";
|
||||
if (!this.ctx.state[this.stateKey]) {
|
||||
this.ctx.state[this.stateKey] = { sessions: {}, events: {} };
|
||||
}
|
||||
}
|
||||
private get data(): RivetPersistData { return this.ctx.state[this.stateKey] as RivetPersistData; }
|
||||
async getSession(id: string) { const s = this.data.sessions[id]; return s ? { ...s } : undefined; }
|
||||
async listSessions(request: ListPageRequest = {}): Promise<ListPage<SessionRecord>> {
|
||||
const sorted = Object.values(this.data.sessions).sort((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id));
|
||||
const offset = Number(request.cursor ?? 0);
|
||||
const limit = request.limit ?? 100;
|
||||
const slice = sorted.slice(offset, offset + limit);
|
||||
return { items: slice, nextCursor: offset + slice.length < sorted.length ? String(offset + slice.length) : undefined };
|
||||
}
|
||||
async updateSession(session: SessionRecord) { this.data.sessions[session.id] = { ...session }; if (!this.data.events[session.id]) this.data.events[session.id] = []; }
|
||||
async listEvents(request: ListEventsRequest): Promise<ListPage<SessionEvent>> {
|
||||
const all = [...(this.data.events[request.sessionId] ?? [])].sort((a, b) => a.eventIndex - b.eventIndex || a.id.localeCompare(b.id));
|
||||
const offset = Number(request.cursor ?? 0);
|
||||
const limit = request.limit ?? 100;
|
||||
const slice = all.slice(offset, offset + limit);
|
||||
return { items: slice, nextCursor: offset + slice.length < all.length ? String(offset + slice.length) : undefined };
|
||||
}
|
||||
async insertEvent(sessionId: string, event: SessionEvent) { const events = this.data.events[sessionId] ?? []; events.push({ ...event, payload: JSON.parse(JSON.stringify(event.payload)) }); this.data.events[sessionId] = events; }
|
||||
}
|
||||
|
||||
type WorkspaceState = RivetPersistState & {
|
||||
sandboxId: string;
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
export const workspace = actor({
|
||||
createState: async () => {
|
||||
return {
|
||||
sandboxId: "sbx_123",
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
} satisfies Partial<WorkspaceState>;
|
||||
},
|
||||
|
||||
createVars: async (c) => {
|
||||
const persist = new RivetSessionPersistDriver(c);
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: c.state.baseUrl,
|
||||
persist,
|
||||
});
|
||||
|
||||
const session = await sdk.resumeOrCreateSession({ id: "default", agent: "codex" });
|
||||
|
||||
const unsubscribe = session.onEvent((event) => {
|
||||
c.broadcast("session.event", event);
|
||||
});
|
||||
|
||||
return { sdk, session, unsubscribe };
|
||||
},
|
||||
|
||||
actions: {
|
||||
getSessionInfo: (c) => ({
|
||||
workspaceId: c.key[0],
|
||||
sandboxId: c.state.sandboxId,
|
||||
}),
|
||||
|
||||
prompt: async (c, input: { userId: string; text: string }) => {
|
||||
c.broadcast("chat.user", {
|
||||
userId: input.userId,
|
||||
text: input.text,
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
|
||||
await c.vars.session.prompt([{ type: "text", text: input.text }]);
|
||||
},
|
||||
},
|
||||
|
||||
onSleep: async (c) => {
|
||||
c.vars.unsubscribe?.();
|
||||
await c.vars.sdk.dispose();
|
||||
},
|
||||
});
|
||||
|
||||
export const registry = setup({
|
||||
use: { workspace },
|
||||
});
|
||||
```
|
||||
|
||||
```ts Client (browser)
|
||||
import { createClient } from "rivetkit/client";
|
||||
import type { registry } from "./actors";
|
||||
|
||||
const client = createClient<typeof registry>({
|
||||
endpoint: process.env.NEXT_PUBLIC_RIVET_ENDPOINT!,
|
||||
});
|
||||
|
||||
const workspaceId = "workspace-42";
|
||||
const room = client.workspace.getOrCreate([workspaceId]);
|
||||
const conn = room.connect();
|
||||
|
||||
conn.on("chat.user", (event) => {
|
||||
console.log("user message", event);
|
||||
});
|
||||
|
||||
conn.on("session.event", (event) => {
|
||||
console.log("sandbox event", event);
|
||||
});
|
||||
|
||||
await conn.prompt({
|
||||
userId: "user-123",
|
||||
text: "Propose a refactor plan for auth middleware.",
|
||||
});
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep sandbox calls actor-only. Browser clients should not call Sandbox Agent directly.
|
||||
- Copy the Rivet persist driver from the example above into your project so session history persists in actor state.
|
||||
- For client connection patterns, see [Rivet JavaScript client](https://rivet.dev/docs/clients/javascript).
|
||||
65
.agents/skills/sandbox-agent/references/observability.md
Normal file
65
.agents/skills/sandbox-agent/references/observability.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Observability
|
||||
|
||||
> Source: `docs/observability.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/observability
|
||||
> Description: Track session activity with OpenTelemetry.
|
||||
|
||||
---
|
||||
Use OpenTelemetry to instrument session traffic, then ship telemetry to your collector/backend.
|
||||
|
||||
## Common collectors and backends
|
||||
|
||||
- [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/)
|
||||
- [Jaeger](https://www.jaegertracing.io/)
|
||||
- [Grafana Tempo](https://grafana.com/oss/tempo/)
|
||||
- [Honeycomb](https://www.honeycomb.io/)
|
||||
- [Datadog APM](https://docs.datadoghq.com/tracing/)
|
||||
|
||||
## Example: trace a prompt round-trip
|
||||
|
||||
Wrap `session.prompt()` in a span to measure the full round-trip, then log individual events as span events.
|
||||
|
||||
Assumes your OTEL provider/exporter is already configured.
|
||||
|
||||
```ts
|
||||
import { trace } from "@opentelemetry/api";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const tracer = trace.getTracer("my-app/sandbox-agent");
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: process.env.SANDBOX_URL!,
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({ agent: "mock" });
|
||||
|
||||
// Log each event as an OTEL span event on the active span
|
||||
const unsubscribe = session.onEvent((event) => {
|
||||
const activeSpan = trace.getActiveSpan();
|
||||
if (!activeSpan) return;
|
||||
|
||||
activeSpan.addEvent("session.event", {
|
||||
"sandbox.sender": event.sender,
|
||||
"sandbox.event_index": event.eventIndex,
|
||||
});
|
||||
});
|
||||
|
||||
// The span covers the full prompt round-trip
|
||||
await tracer.startActiveSpan("sandbox_agent.prompt", async (span) => {
|
||||
span.setAttribute("sandbox.session_id", session.id);
|
||||
|
||||
try {
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository." },
|
||||
]);
|
||||
span.setAttribute("sandbox.stop_reason", result.stopReason);
|
||||
} catch (error) {
|
||||
span.recordException(error as Error);
|
||||
throw error;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
});
|
||||
|
||||
unsubscribe();
|
||||
```
|
||||
@@ -0,0 +1,123 @@
|
||||
# OpenCode Compatibility
|
||||
|
||||
> Source: `docs/opencode-compatibility.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/opencode-compatibility
|
||||
> Description: Connect OpenCode clients, SDKs, and web UI to Sandbox Agent.
|
||||
|
||||
---
|
||||
**Experimental**: OpenCode SDK/UI compatibility may change.
|
||||
|
||||
Sandbox Agent exposes an OpenCode-compatible API at `/opencode`.
|
||||
|
||||
## Why use OpenCode clients with Sandbox Agent?
|
||||
|
||||
- OpenCode CLI (`opencode attach`)
|
||||
- OpenCode web UI
|
||||
- OpenCode TypeScript SDK (`@opencode-ai/sdk`)
|
||||
|
||||
## Quick start
|
||||
|
||||
### OpenCode CLI / TUI
|
||||
|
||||
```bash
|
||||
sandbox-agent opencode --port 2468 --no-token
|
||||
```
|
||||
|
||||
Or start server + attach manually:
|
||||
|
||||
```bash
|
||||
sandbox-agent server --no-token --host 127.0.0.1 --port 2468
|
||||
opencode attach http://localhost:2468/opencode
|
||||
```
|
||||
|
||||
With authentication enabled:
|
||||
|
||||
```bash
|
||||
sandbox-agent server --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 2468
|
||||
opencode attach http://localhost:2468/opencode --password "$SANDBOX_TOKEN"
|
||||
```
|
||||
|
||||
### OpenCode web UI
|
||||
|
||||
### Start Sandbox Agent with CORS
|
||||
|
||||
```bash
|
||||
sandbox-agent server --no-token --host 127.0.0.1 --port 2468 --cors-allow-origin http://127.0.0.1:5173
|
||||
```
|
||||
|
||||
### Run OpenCode web app
|
||||
|
||||
```bash
|
||||
git clone https://github.com/anomalyco/opencode
|
||||
cd opencode/packages/app
|
||||
export VITE_OPENCODE_SERVER_HOST=127.0.0.1
|
||||
export VITE_OPENCODE_SERVER_PORT=2468
|
||||
bun install
|
||||
bun run dev -- --host 127.0.0.1 --port 5173
|
||||
```
|
||||
|
||||
### Open UI
|
||||
|
||||
Visit `http://127.0.0.1:5173/`.
|
||||
|
||||
### OpenCode SDK
|
||||
|
||||
```typescript
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk";
|
||||
|
||||
const client = createOpencodeClient({
|
||||
baseUrl: "http://localhost:2468/opencode",
|
||||
});
|
||||
|
||||
const session = await client.session.create();
|
||||
|
||||
await client.session.promptAsync({
|
||||
path: { id: session.data.id },
|
||||
body: {
|
||||
parts: [{ type: "text", text: "Hello, write a hello world script" }],
|
||||
},
|
||||
});
|
||||
|
||||
const events = await client.event.subscribe({});
|
||||
for await (const event of events.stream) {
|
||||
console.log(event);
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- API base path: `/opencode`
|
||||
- If server auth is enabled, pass bearer auth (or `--password` in OpenCode CLI)
|
||||
- For browser UIs, configure CORS with `--cors-allow-origin`
|
||||
- Provider selector currently exposes compatible providers (`mock`, `amp`, `claude`, `codex`)
|
||||
- Provider/model metadata for compatibility endpoints is normalized and may differ from native OpenCode grouping
|
||||
- Optional proxy: set `OPENCODE_COMPAT_PROXY_URL` to forward selected endpoints to native OpenCode
|
||||
|
||||
## Endpoint coverage
|
||||
|
||||
#### Endpoint Status Table
|
||||
|
||||
| Endpoint | Status | Notes |
|
||||
|---|---|---|
|
||||
| `GET /event` | ✓ | Session/message updates (SSE) |
|
||||
| `GET /global/event` | ✓ | GlobalEvent-wrapped stream |
|
||||
| `GET /session` | ✓ | Session list |
|
||||
| `POST /session` | ✓ | Create session |
|
||||
| `GET /session/{id}` | ✓ | Session details |
|
||||
| `POST /session/{id}/message` | ✓ | Send message |
|
||||
| `GET /session/{id}/message` | ✓ | Session messages |
|
||||
| `GET /permission` | ✓ | Pending permissions |
|
||||
| `POST /permission/{id}/reply` | ✓ | Permission reply |
|
||||
| `GET /question` | ✓ | Pending questions |
|
||||
| `POST /question/{id}/reply` | ✓ | Question reply |
|
||||
| `GET /provider` | ✓ | Provider metadata |
|
||||
| `GET /command` | ↔ | Proxied when `OPENCODE_COMPAT_PROXY_URL` is set; otherwise stub |
|
||||
| `GET /config` | ↔ | Proxied when set; otherwise stub |
|
||||
| `PATCH /config` | ↔ | Proxied when set; otherwise local compatibility behavior |
|
||||
| `GET /global/config` | ↔ | Proxied when set; otherwise stub |
|
||||
| `PATCH /global/config` | ↔ | Proxied when set; otherwise local compatibility behavior |
|
||||
| `/tui/*` | ↔ | Proxied when set; otherwise local compatibility behavior |
|
||||
| `GET /agent` | − | Agent list |
|
||||
| *other endpoints* | − | Empty/stub responses |
|
||||
|
||||
✓ Functional ↔ Proxied optional − Stubbed
|
||||
@@ -0,0 +1,44 @@
|
||||
# Orchestration Architecture
|
||||
|
||||
> Source: `docs/orchestration-architecture.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/orchestration-architecture
|
||||
> Description: Production topology, backend requirements, and session persistence.
|
||||
|
||||
---
|
||||
This page covers production topology and backend requirements. Read [Architecture](/architecture) first for an overview of how the server, SDK, and agent processes fit together.
|
||||
|
||||
## Suggested Topology
|
||||
|
||||
Run the SDK on your backend, then call it from your frontend.
|
||||
|
||||
This extra hop is recommended because it keeps auth/token logic on the backend and makes persistence simpler.
|
||||
|
||||
```mermaid placement="top-right"
|
||||
flowchart LR
|
||||
BROWSER["Browser"]
|
||||
subgraph BACKEND["Your backend"]
|
||||
direction TB
|
||||
SDK["Sandbox Agent SDK"]
|
||||
end
|
||||
subgraph SANDBOX_SIMPLE["Sandbox"]
|
||||
SERVER_SIMPLE["Sandbox Agent server"]
|
||||
end
|
||||
|
||||
BROWSER --> BACKEND
|
||||
BACKEND --> SDK --> SERVER_SIMPLE
|
||||
```
|
||||
|
||||
### Backend requirements
|
||||
|
||||
Your backend layer needs to handle:
|
||||
|
||||
- **Long-running connections**: prompts can take minutes.
|
||||
- **Session affinity**: follow-up messages must reach the same session.
|
||||
- **State between requests**: session metadata and event history must persist across requests.
|
||||
- **Graceful recovery**: sessions should resume after backend restarts.
|
||||
|
||||
We recommend [Rivet](https://rivet.dev) over serverless because actors natively support the long-lived connections, session routing, and state persistence that agent workloads require.
|
||||
|
||||
## Session persistence
|
||||
|
||||
For storage driver options and replay behavior, see [Persisting Sessions](/session-persistence).
|
||||
242
.agents/skills/sandbox-agent/references/processes.md
Normal file
242
.agents/skills/sandbox-agent/references/processes.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# Processes
|
||||
|
||||
> Source: `docs/processes.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/processes
|
||||
> Description: Run commands and manage long-lived processes inside the sandbox.
|
||||
|
||||
---
|
||||
The process API supports:
|
||||
|
||||
- **One-shot execution** — run a command to completion and capture stdout, stderr, and exit code
|
||||
- **Managed processes** — spawn, list, stop, kill, and delete long-lived processes
|
||||
- **Log streaming** — fetch buffered logs or follow live output
|
||||
- **Terminals** — full PTY support with bidirectional WebSocket I/O
|
||||
- **Configurable limits** — control concurrency, timeouts, and buffer sizes per runtime
|
||||
|
||||
## Run a command
|
||||
|
||||
Execute a command to completion and get its output.
|
||||
|
||||
```ts TypeScript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const result = await sdk.runProcess({
|
||||
command: "ls",
|
||||
args: ["-la", "/workspace"],
|
||||
});
|
||||
|
||||
console.log(result.exitCode); // 0
|
||||
console.log(result.stdout);
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"ls","args":["-la","/workspace"]}'
|
||||
```
|
||||
|
||||
You can set a timeout and cap output size:
|
||||
|
||||
```ts TypeScript
|
||||
const result = await sdk.runProcess({
|
||||
command: "make",
|
||||
args: ["build"],
|
||||
timeoutMs: 60000,
|
||||
maxOutputBytes: 1048576,
|
||||
});
|
||||
|
||||
if (result.timedOut) {
|
||||
console.log("Build timed out");
|
||||
}
|
||||
if (result.stdoutTruncated) {
|
||||
console.log("Output was truncated");
|
||||
}
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/run" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"make","args":["build"],"timeoutMs":60000,"maxOutputBytes":1048576}'
|
||||
```
|
||||
|
||||
## Managed processes
|
||||
|
||||
Create a long-lived process that you can interact with, monitor, and stop later.
|
||||
|
||||
### Create
|
||||
|
||||
```ts TypeScript
|
||||
const proc = await sdk.createProcess({
|
||||
command: "node",
|
||||
args: ["server.js"],
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
console.log(proc.id, proc.pid); // proc_1, 12345
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command":"node","args":["server.js"],"cwd":"/workspace"}'
|
||||
```
|
||||
|
||||
### List and get
|
||||
|
||||
```ts TypeScript
|
||||
const { processes } = await sdk.listProcesses();
|
||||
|
||||
for (const p of processes) {
|
||||
console.log(p.id, p.command, p.status);
|
||||
}
|
||||
|
||||
const proc = await sdk.getProcess("proc_1");
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/processes"
|
||||
|
||||
curl "http://127.0.0.1:2468/v1/processes/proc_1"
|
||||
```
|
||||
|
||||
### Stop, kill, and delete
|
||||
|
||||
```ts TypeScript
|
||||
// SIGTERM with optional wait
|
||||
await sdk.stopProcess("proc_1", { waitMs: 5000 });
|
||||
|
||||
// SIGKILL
|
||||
await sdk.killProcess("proc_1", { waitMs: 1000 });
|
||||
|
||||
// Remove exited process record
|
||||
await sdk.deleteProcess("proc_1");
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/proc_1/stop?waitMs=5000"
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/proc_1/kill?waitMs=1000"
|
||||
|
||||
curl -X DELETE "http://127.0.0.1:2468/v1/processes/proc_1"
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
### Fetch buffered logs
|
||||
|
||||
```ts TypeScript
|
||||
const logs = await sdk.getProcessLogs("proc_1", {
|
||||
tail: 50,
|
||||
stream: "combined",
|
||||
});
|
||||
|
||||
for (const entry of logs.entries) {
|
||||
console.log(entry.stream, atob(entry.data));
|
||||
}
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/processes/proc_1/logs?tail=50&stream=combined"
|
||||
```
|
||||
|
||||
### Follow logs
|
||||
|
||||
Stream log entries in real time. The subscription replays buffered entries first, then streams new output as it arrives.
|
||||
|
||||
```ts TypeScript
|
||||
const sub = await sdk.followProcessLogs("proc_1", (entry) => {
|
||||
console.log(entry.stream, atob(entry.data));
|
||||
});
|
||||
|
||||
// Later, stop following
|
||||
sub.close();
|
||||
await sub.closed;
|
||||
```
|
||||
|
||||
## Terminals
|
||||
|
||||
Create a process with `tty: true` to allocate a pseudo-terminal, then connect via WebSocket for full bidirectional I/O.
|
||||
|
||||
```ts TypeScript
|
||||
const proc = await sdk.createProcess({
|
||||
command: "bash",
|
||||
tty: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Write input
|
||||
|
||||
```ts TypeScript
|
||||
await sdk.sendProcessInput("proc_1", {
|
||||
data: "echo hello\n",
|
||||
encoding: "utf8",
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/proc_1/input" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data":"echo hello\n","encoding":"utf8"}'
|
||||
```
|
||||
|
||||
### Connect to a terminal
|
||||
|
||||
Use `ProcessTerminalSession` unless you need direct frame access.
|
||||
|
||||
```ts TypeScript
|
||||
const terminal = sdk.connectProcessTerminal("proc_1");
|
||||
|
||||
terminal.onReady(() => {
|
||||
terminal.resize({ cols: 120, rows: 40 });
|
||||
terminal.sendInput("ls\n");
|
||||
});
|
||||
|
||||
terminal.onData((bytes) => {
|
||||
process.stdout.write(new TextDecoder().decode(bytes));
|
||||
});
|
||||
|
||||
terminal.onExit((status) => {
|
||||
console.log("exit:", status.exitCode);
|
||||
});
|
||||
|
||||
terminal.onError((error) => {
|
||||
console.error(error instanceof Error ? error.message : error.message);
|
||||
});
|
||||
|
||||
terminal.onClose(() => {
|
||||
console.log("terminal closed");
|
||||
});
|
||||
```
|
||||
|
||||
Since the browser WebSocket API cannot send custom headers, the endpoint accepts an `access_token` query parameter for authentication. The SDK handles this automatically.
|
||||
|
||||
### Browser terminal emulators
|
||||
|
||||
The terminal session works with any browser terminal emulator like ghostty-web or xterm.js. For a drop-in React terminal, see [React Components](/react-components).
|
||||
|
||||
## Configuration
|
||||
|
||||
Adjust runtime limits like max concurrent processes, timeouts, and buffer sizes.
|
||||
|
||||
```ts TypeScript
|
||||
const config = await sdk.getProcessConfig();
|
||||
console.log(config);
|
||||
|
||||
await sdk.setProcessConfig({
|
||||
...config,
|
||||
maxConcurrentProcesses: 32,
|
||||
defaultRunTimeoutMs: 60000,
|
||||
});
|
||||
```
|
||||
|
||||
```bash cURL
|
||||
curl "http://127.0.0.1:2468/v1/processes/config"
|
||||
|
||||
curl -X POST "http://127.0.0.1:2468/v1/processes/config" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"maxConcurrentProcesses":32,"defaultRunTimeoutMs":60000,"maxRunTimeoutMs":300000,"maxOutputBytes":1048576,"maxLogBytesPerProcess":10485760,"maxInputBytesPerRequest":65536}'
|
||||
```
|
||||
289
.agents/skills/sandbox-agent/references/quickstart.md
Normal file
289
.agents/skills/sandbox-agent/references/quickstart.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Quickstart
|
||||
|
||||
> Source: `docs/quickstart.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/quickstart
|
||||
> Description: Start the server and send your first message.
|
||||
|
||||
---
|
||||
### Install skill (optional)
|
||||
|
||||
#### npx
|
||||
|
||||
```bash
|
||||
npx skills add rivet-dev/skills -s sandbox-agent
|
||||
```
|
||||
|
||||
#### bunx
|
||||
|
||||
```bash
|
||||
bunx skills add rivet-dev/skills -s sandbox-agent
|
||||
```
|
||||
|
||||
### Set environment variables
|
||||
|
||||
Each coding agent requires API keys to connect to their respective LLM providers.
|
||||
|
||||
#### Local shell
|
||||
|
||||
```bash
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
```
|
||||
|
||||
#### E2B
|
||||
|
||||
```typescript
|
||||
import { Sandbox } from "@e2b/code-interpreter";
|
||||
|
||||
const envs: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const sandbox = await Sandbox.create({ envs });
|
||||
```
|
||||
|
||||
#### Daytona
|
||||
|
||||
```typescript
|
||||
import { Daytona } from "@daytonaio/sdk";
|
||||
|
||||
const envVars: Record<string, string> = {};
|
||||
if (process.env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
|
||||
if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
|
||||
const daytona = new Daytona();
|
||||
const sandbox = await daytona.create({
|
||||
snapshot: "sandbox-agent-ready",
|
||||
envVars,
|
||||
});
|
||||
```
|
||||
|
||||
#### Docker
|
||||
|
||||
```bash
|
||||
docker run -p 2468:2468 \
|
||||
-e ANTHROPIC_API_KEY="sk-ant-..." \
|
||||
-e OPENAI_API_KEY="sk-..." \
|
||||
rivetdev/sandbox-agent:0.4.2-full \
|
||||
server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### Extracting API keys from current machine
|
||||
|
||||
Use `sandbox-agent credentials extract-env --export` to extract your existing API keys (Anthropic, OpenAI, etc.) from local Claude Code or Codex config files.
|
||||
|
||||
#### Testing without API keys
|
||||
|
||||
Use the `mock` agent for SDK and integration testing without provider credentials.
|
||||
|
||||
#### Multi-tenant and per-user billing
|
||||
|
||||
For per-tenant token tracking, budget enforcement, or usage-based billing, see [LLM Credentials](/llm-credentials) for gateway options like OpenRouter, LiteLLM, and Portkey.
|
||||
|
||||
### Run the server
|
||||
|
||||
#### curl
|
||||
|
||||
Install and run the binary directly.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### npx
|
||||
|
||||
Run without installing globally.
|
||||
|
||||
```bash
|
||||
npx @sandbox-agent/cli@0.4.x server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### bunx
|
||||
|
||||
Run without installing globally.
|
||||
|
||||
```bash
|
||||
bunx @sandbox-agent/cli@0.4.x server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### npm i -g
|
||||
|
||||
Install globally, then run.
|
||||
|
||||
```bash
|
||||
npm install -g @sandbox-agent/cli@0.4.x
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### bun add -g
|
||||
|
||||
Install globally, then run.
|
||||
|
||||
```bash
|
||||
bun add -g @sandbox-agent/cli@0.4.x
|
||||
# Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()).
|
||||
bun pm -g trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-linux-arm64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64
|
||||
sandbox-agent server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
#### Node.js (local)
|
||||
|
||||
For local development, use `SandboxAgent.start()` to spawn and manage the server as a subprocess.
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.start();
|
||||
```
|
||||
|
||||
#### Bun (local)
|
||||
|
||||
For local development, use `SandboxAgent.start()` to spawn and manage the server as a subprocess.
|
||||
|
||||
```bash
|
||||
bun add sandbox-agent@0.4.x
|
||||
# Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()).
|
||||
bun pm trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-linux-arm64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.start();
|
||||
```
|
||||
|
||||
#### Build from source
|
||||
|
||||
If you're running from source instead of the installed CLI.
|
||||
|
||||
```bash
|
||||
cargo run -p sandbox-agent -- server --no-token --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
Binding to `0.0.0.0` allows the server to accept connections from any network interface, which is required when running inside a sandbox where clients connect remotely.
|
||||
|
||||
#### Configuring token
|
||||
|
||||
Tokens are usually not required. Most sandbox providers (E2B, Daytona, etc.) already secure networking at the infrastructure layer.
|
||||
|
||||
If you expose the server publicly, use `--token "$SANDBOX_TOKEN"` to require authentication:
|
||||
|
||||
```bash
|
||||
sandbox-agent server --token "$SANDBOX_TOKEN" --host 0.0.0.0 --port 2468
|
||||
```
|
||||
|
||||
Then pass the token when connecting:
|
||||
|
||||
#### TypeScript
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://your-server:2468",
|
||||
token: process.env.SANDBOX_TOKEN,
|
||||
});
|
||||
```
|
||||
|
||||
#### curl
|
||||
|
||||
```bash
|
||||
curl "http://your-server:2468/v1/health" \
|
||||
-H "Authorization: Bearer $SANDBOX_TOKEN"
|
||||
```
|
||||
|
||||
#### CLI
|
||||
|
||||
```bash
|
||||
sandbox-agent --token "$SANDBOX_TOKEN" api agents list \
|
||||
--endpoint http://your-server:2468
|
||||
```
|
||||
|
||||
#### CORS
|
||||
|
||||
If you're calling the server from a browser, see the [CORS configuration guide](/cors).
|
||||
|
||||
### Install agents (optional)
|
||||
|
||||
To preinstall agents:
|
||||
|
||||
```bash
|
||||
sandbox-agent install-agent --all
|
||||
```
|
||||
|
||||
If agents are not installed up front, they are lazily installed when creating a session.
|
||||
|
||||
### Install desktop dependencies (optional, Linux only)
|
||||
|
||||
If you want to use `/v1/desktop/*`, install the desktop runtime packages first:
|
||||
|
||||
```bash
|
||||
sandbox-agent install desktop --yes
|
||||
```
|
||||
|
||||
Then use `GET /v1/desktop/status` or `sdk.getDesktopStatus()` to verify the runtime is ready before calling desktop screenshot or input APIs.
|
||||
|
||||
### Create a session
|
||||
|
||||
```typescript
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
sessionInit: {
|
||||
cwd: "/",
|
||||
mcpServers: [],
|
||||
},
|
||||
});
|
||||
|
||||
console.log(session.id);
|
||||
```
|
||||
|
||||
### Send a message
|
||||
|
||||
```typescript
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: "Summarize the repository and suggest next steps." },
|
||||
]);
|
||||
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
### Read events
|
||||
|
||||
```typescript
|
||||
const off = session.onEvent((event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const page = await sdk.getEvents({
|
||||
sessionId: session.id,
|
||||
limit: 50,
|
||||
});
|
||||
|
||||
console.log(page.items.length);
|
||||
off();
|
||||
```
|
||||
|
||||
### Test with Inspector
|
||||
|
||||
Open the Inspector UI at `/ui/` on your server (for example, `http://localhost:2468/ui/`) to inspect sessions and events in a GUI.
|
||||
|
||||

|
||||
|
||||
## Next steps
|
||||
|
||||
- [Session Persistence](/session-persistence) — Configure in-memory, Rivet Actor state, IndexedDB, SQLite, and Postgres persistence.
|
||||
|
||||
- [Deploy to a Sandbox](/deploy/local) — Deploy your agent to E2B, Daytona, Docker, Vercel, or Cloudflare.
|
||||
|
||||
- [SDK Overview](/sdk-overview) — Use the latest TypeScript SDK API.
|
||||
246
.agents/skills/sandbox-agent/references/react-components.md
Normal file
246
.agents/skills/sandbox-agent/references/react-components.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# React Components
|
||||
|
||||
> Source: `docs/react-components.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/react-components
|
||||
> Description: Drop-in React components for Sandbox Agent frontends.
|
||||
|
||||
---
|
||||
`@sandbox-agent/react` exposes small React components built on top of the `sandbox-agent` SDK.
|
||||
|
||||
Current exports:
|
||||
|
||||
- `AgentConversation` for a combined transcript + composer surface
|
||||
- `ProcessTerminal` for attaching to a running tty process
|
||||
- `AgentTranscript` for rendering session/message timelines without bundling any styles
|
||||
- `ChatComposer` for a reusable prompt input/send surface
|
||||
- `useTranscriptVirtualizer` for wiring large transcript lists to a scroll container
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @sandbox-agent/react@0.4.x
|
||||
```
|
||||
|
||||
## Full example
|
||||
|
||||
This example connects to a running Sandbox Agent server, starts a tty shell, renders `ProcessTerminal`, and cleans up the process when the component unmounts.
|
||||
|
||||
```tsx TerminalPane.tsx expandable highlight={5,32-36,71}
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { ProcessTerminal } from "@sandbox-agent/react";
|
||||
|
||||
export default function TerminalPane() {
|
||||
const [client, setClient] = useState<SandboxAgent | null>(null);
|
||||
const [processId, setProcessId] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
let sdk: SandboxAgent | null = null;
|
||||
let createdProcessId: string | null = null;
|
||||
|
||||
const cleanup = async () => {
|
||||
if (!sdk || !createdProcessId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await sdk.killProcess(createdProcessId, { waitMs: 1_000 }).catch(() => {});
|
||||
await sdk.deleteProcess(createdProcessId).catch(() => {});
|
||||
};
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
const process = await sdk.createProcess({
|
||||
command: "sh",
|
||||
interactive: true,
|
||||
tty: true,
|
||||
});
|
||||
|
||||
if (cancelled) {
|
||||
createdProcessId = process.id;
|
||||
await cleanup();
|
||||
await sdk.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
createdProcessId = process.id;
|
||||
setClient(sdk);
|
||||
setProcessId(process.id);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Failed to start terminal.";
|
||||
setError(message);
|
||||
}
|
||||
};
|
||||
|
||||
void start();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
void cleanup();
|
||||
void sdk?.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <div>{error}</div>;
|
||||
}
|
||||
|
||||
if (!client || !processId) {
|
||||
return <div>Starting terminal...</div>;
|
||||
}
|
||||
|
||||
return <ProcessTerminal client={client} processId={processId} height={480} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Component
|
||||
|
||||
`ProcessTerminal` attaches to a running tty process.
|
||||
|
||||
- `client`: a `SandboxAgent` client
|
||||
- `processId`: the process to attach to
|
||||
- `height`, `style`, `terminalStyle`: optional layout overrides
|
||||
- `onExit`, `onError`: optional lifecycle callbacks
|
||||
|
||||
See [Processes](/processes) for the lower-level terminal APIs.
|
||||
|
||||
## Headless transcript
|
||||
|
||||
`AgentTranscript` is intentionally unstyled. It follows the common headless React pattern used by libraries like Radix, Headless UI, and React Aria: behavior lives in the component, while styling stays in your app through `className`, slot-level `classNames`, and `data-*` state attributes on the rendered DOM.
|
||||
|
||||
```tsx TranscriptPane.tsx
|
||||
import {
|
||||
AgentTranscript,
|
||||
type AgentTranscriptClassNames,
|
||||
type TranscriptEntry,
|
||||
} from "@sandbox-agent/react";
|
||||
|
||||
const transcriptClasses: Partial<AgentTranscriptClassNames> = {
|
||||
root: "transcript",
|
||||
message: "transcript-message",
|
||||
messageContent: "transcript-message-content",
|
||||
toolGroupContainer: "transcript-tools",
|
||||
toolGroupHeader: "transcript-tools-header",
|
||||
toolItem: "transcript-tool-item",
|
||||
toolItemHeader: "transcript-tool-item-header",
|
||||
toolItemBody: "transcript-tool-item-body",
|
||||
divider: "transcript-divider",
|
||||
dividerText: "transcript-divider-text",
|
||||
error: "transcript-error",
|
||||
};
|
||||
|
||||
export function TranscriptPane({ entries }: { entries: TranscriptEntry[] }) {
|
||||
return (
|
||||
<AgentTranscript
|
||||
entries={entries}
|
||||
classNames={transcriptClasses}
|
||||
renderMessageText={(entry) => <div>{entry.text}</div>}
|
||||
renderInlinePendingIndicator={() => <span>...</span>}
|
||||
renderToolGroupIcon={() => <span>Events</span>}
|
||||
renderChevron={(expanded) => <span>{expanded ? "Hide" : "Show"}</span>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
.transcript {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.transcript [data-slot="message"][data-variant="user"] .transcript-message-content {
|
||||
background: #161616;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.transcript [data-slot="message"][data-variant="assistant"] .transcript-message-content {
|
||||
background: #f4f4f0;
|
||||
color: #161616;
|
||||
}
|
||||
|
||||
.transcript [data-slot="tool-item"][data-failed="true"] {
|
||||
border-color: #d33;
|
||||
}
|
||||
|
||||
.transcript [data-slot="tool-item-header"][data-expanded="true"] {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
```
|
||||
|
||||
`AgentTranscript` accepts `TranscriptEntry[]`, which matches the Inspector timeline shape:
|
||||
|
||||
- `message` entries render user/assistant text
|
||||
- `tool` entries render expandable tool input/output sections
|
||||
- `reasoning` entries render expandable reasoning blocks
|
||||
- `meta` entries render status rows or expandable metadata details
|
||||
|
||||
Useful props:
|
||||
|
||||
- `className`: root class hook
|
||||
- `classNames`: slot-level class hooks for styling from outside the package
|
||||
- `scrollRef` + `virtualize`: opt into TanStack Virtual against an external scroll container
|
||||
- `renderMessageText`: custom text or markdown renderer
|
||||
- `renderToolItemIcon`, `renderToolGroupIcon`, `renderChevron`, `renderEventLinkContent`: presentation overrides
|
||||
- `renderInlinePendingIndicator`, `renderThinkingState`: loading/thinking UI overrides
|
||||
- `isDividerEntry`, `canOpenEvent`, `getToolGroupSummary`: behavior overrides for grouping and labels
|
||||
|
||||
## Transcript virtualization hook
|
||||
|
||||
`useTranscriptVirtualizer` exposes the same TanStack Virtual behavior used by `AgentTranscript` when `virtualize` is enabled.
|
||||
|
||||
- Pass the grouped transcript rows you want to virtualize
|
||||
- Pass a `scrollRef` that points at the actual scrollable element
|
||||
- Use it when you need transcript-aware virtualization outside the stock `AgentTranscript` renderer
|
||||
|
||||
## Composer and conversation
|
||||
|
||||
`ChatComposer` is the headless message input. `AgentConversation` composes `AgentTranscript` and `ChatComposer` so apps can reuse the transcript/composer pairing without pulling in Inspector session chrome.
|
||||
|
||||
```tsx ConversationPane.tsx
|
||||
import { AgentConversation, type TranscriptEntry } from "@sandbox-agent/react";
|
||||
|
||||
export function ConversationPane({
|
||||
entries,
|
||||
message,
|
||||
onMessageChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
entries: TranscriptEntry[];
|
||||
message: string;
|
||||
onMessageChange: (value: string) => void;
|
||||
onSubmit: () => void;
|
||||
}) {
|
||||
return (
|
||||
<AgentConversation
|
||||
entries={entries}
|
||||
emptyState={<div>Start the conversation.</div>}
|
||||
transcriptProps={{
|
||||
renderMessageText: (entry) => <div>{entry.text}</div>,
|
||||
}}
|
||||
composerProps={{
|
||||
message,
|
||||
onMessageChange,
|
||||
onSubmit,
|
||||
placeholder: "Send a message...",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Useful `ChatComposer` props:
|
||||
|
||||
- `className` and `classNames` for external styling
|
||||
- `inputRef` to manage focus or autoresize from the consumer
|
||||
- `textareaProps` for lower-level textarea behavior
|
||||
- `allowEmptySubmit` when the submit action is valid without draft text, such as a stop button
|
||||
|
||||
Use `transcriptProps` and `composerProps` when you want the shared composition but still need custom rendering or behavior. Use `transcriptClassNames` and `composerClassNames` when you want styling hooks for each subcomponent.
|
||||
279
.agents/skills/sandbox-agent/references/sdk-overview.md
Normal file
279
.agents/skills/sandbox-agent/references/sdk-overview.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# SDK Overview
|
||||
|
||||
> Source: `docs/sdk-overview.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/sdk-overview
|
||||
> Description: Use the TypeScript SDK to manage Sandbox Agent sessions and APIs.
|
||||
|
||||
---
|
||||
The TypeScript SDK is centered on `sandbox-agent` and its `SandboxAgent` class.
|
||||
|
||||
## Install
|
||||
|
||||
#### npm
|
||||
|
||||
```bash
|
||||
npm install sandbox-agent@0.4.x
|
||||
```
|
||||
|
||||
#### bun
|
||||
|
||||
```bash
|
||||
bun add sandbox-agent@0.4.x
|
||||
# Allow Bun to run postinstall scripts for native binaries (required for SandboxAgent.start()).
|
||||
bun pm trust @sandbox-agent/cli-linux-x64 @sandbox-agent/cli-linux-arm64 @sandbox-agent/cli-darwin-arm64 @sandbox-agent/cli-darwin-x64 @sandbox-agent/cli-win32-x64
|
||||
```
|
||||
|
||||
## Optional React components
|
||||
|
||||
```bash
|
||||
npm install @sandbox-agent/react@0.4.x
|
||||
```
|
||||
|
||||
## Create a client
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
```
|
||||
|
||||
`SandboxAgent.connect(...)` now waits for `/v1/health` by default before other SDK requests proceed. To disable that gate, pass `waitForHealth: false`. To keep the default gate but fail after a bounded wait, pass `waitForHealth: { timeoutMs: 120_000 }`. To cancel the startup wait early, pass `signal: abortController.signal`.
|
||||
|
||||
With a custom fetch handler (for example, proxying requests inside Workers):
|
||||
|
||||
```ts
|
||||
const sdk = await SandboxAgent.connect({
|
||||
fetch: (input, init) => customFetch(input, init),
|
||||
});
|
||||
```
|
||||
|
||||
With an abort signal for the startup health gate:
|
||||
|
||||
```ts
|
||||
const controller = new AbortController();
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
controller.abort();
|
||||
```
|
||||
|
||||
With persistence (see [Persisting Sessions](/session-persistence) for driver options):
|
||||
|
||||
```ts
|
||||
import { SandboxAgent, InMemorySessionPersistDriver } from "sandbox-agent";
|
||||
|
||||
const persist = new InMemorySessionPersistDriver();
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
persist,
|
||||
});
|
||||
```
|
||||
|
||||
Local spawn with a sandbox provider:
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { local } from "sandbox-agent/local";
|
||||
|
||||
const sdk = await SandboxAgent.start({
|
||||
sandbox: local(),
|
||||
});
|
||||
|
||||
// sdk.sandboxId — prefixed provider ID (e.g. "local/127.0.0.1:2468")
|
||||
|
||||
await sdk.destroySandbox(); // provider-defined cleanup + disposes client
|
||||
```
|
||||
|
||||
`SandboxAgent.start(...)` requires a `sandbox` provider. Built-in providers:
|
||||
|
||||
| Import | Provider |
|
||||
|--------|----------|
|
||||
| `sandbox-agent/local` | Local subprocess |
|
||||
| `sandbox-agent/docker` | Docker container |
|
||||
| `sandbox-agent/e2b` | E2B sandbox |
|
||||
| `sandbox-agent/daytona` | Daytona workspace |
|
||||
| `sandbox-agent/agentcomputer` | Agent Computer managed worker |
|
||||
| `sandbox-agent/vercel` | Vercel Sandbox |
|
||||
| `sandbox-agent/cloudflare` | Cloudflare Sandbox |
|
||||
|
||||
Use `sdk.dispose()` to disconnect without changing sandbox state, `sdk.pauseSandbox()` for graceful suspension when supported, or `sdk.killSandbox()` for permanent deletion.
|
||||
|
||||
## Session flow
|
||||
|
||||
```ts
|
||||
const session = await sdk.createSession({
|
||||
agent: "mock",
|
||||
cwd: "/",
|
||||
});
|
||||
|
||||
const prompt = await session.prompt([
|
||||
{ type: "text", text: "Summarize this repository." },
|
||||
]);
|
||||
|
||||
console.log(prompt.stopReason);
|
||||
```
|
||||
|
||||
Load and destroy:
|
||||
|
||||
```ts
|
||||
const restored = await sdk.resumeSession(session.id);
|
||||
await restored.prompt([{ type: "text", text: "Continue from previous context." }]);
|
||||
|
||||
await sdk.destroySession(restored.id);
|
||||
```
|
||||
|
||||
## Session configuration
|
||||
|
||||
Set model, mode, or thought level at creation or on an existing session:
|
||||
|
||||
```ts
|
||||
const session = await sdk.createSession({
|
||||
agent: "codex",
|
||||
model: "gpt-5.3-codex",
|
||||
});
|
||||
|
||||
await session.setModel("gpt-5.2-codex");
|
||||
await session.setMode("auto");
|
||||
|
||||
const options = await session.getConfigOptions();
|
||||
const modes = await session.getModes();
|
||||
```
|
||||
|
||||
Handle permission requests from agents that ask before executing tools:
|
||||
|
||||
```ts
|
||||
const claude = await sdk.createSession({
|
||||
agent: "claude",
|
||||
mode: "default",
|
||||
});
|
||||
|
||||
claude.onPermissionRequest((request) => {
|
||||
void claude.respondPermission(request.id, "once");
|
||||
});
|
||||
```
|
||||
|
||||
See [Agent Sessions](/agent-sessions) for full details on config options and error handling.
|
||||
|
||||
## Events
|
||||
|
||||
Subscribe to live events:
|
||||
|
||||
```ts
|
||||
const unsubscribe = session.onEvent((event) => {
|
||||
console.log(event.eventIndex, event.sender, event.payload);
|
||||
});
|
||||
|
||||
await session.prompt([{ type: "text", text: "Give me a short summary." }]);
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
Fetch persisted events:
|
||||
|
||||
```ts
|
||||
const page = await sdk.getEvents({
|
||||
sessionId: session.id,
|
||||
limit: 100,
|
||||
});
|
||||
|
||||
console.log(page.items.length);
|
||||
```
|
||||
|
||||
## Control-plane and HTTP helpers
|
||||
|
||||
```ts
|
||||
const health = await sdk.getHealth();
|
||||
const agents = await sdk.listAgents();
|
||||
await sdk.installAgent("codex", { reinstall: true });
|
||||
|
||||
const entries = await sdk.listFsEntries({ path: "." });
|
||||
const writeResult = await sdk.writeFsFile({ path: "./hello.txt" }, "hello");
|
||||
|
||||
console.log(health.status, agents.agents.length, entries.length, writeResult.path);
|
||||
```
|
||||
|
||||
## Desktop API
|
||||
|
||||
The SDK also wraps the desktop host/runtime HTTP API.
|
||||
|
||||
Install desktop dependencies first on Linux hosts:
|
||||
|
||||
```bash
|
||||
sandbox-agent install desktop --yes
|
||||
```
|
||||
|
||||
Then query status, surface remediation if needed, and start the runtime:
|
||||
|
||||
```ts
|
||||
const status = await sdk.getDesktopStatus();
|
||||
|
||||
if (status.state === "install_required") {
|
||||
console.log(status.installCommand);
|
||||
}
|
||||
|
||||
const started = await sdk.startDesktop({
|
||||
width: 1440,
|
||||
height: 900,
|
||||
dpi: 96,
|
||||
});
|
||||
|
||||
const screenshot = await sdk.takeDesktopScreenshot();
|
||||
const displayInfo = await sdk.getDesktopDisplayInfo();
|
||||
|
||||
await sdk.moveDesktopMouse({ x: 400, y: 300 });
|
||||
await sdk.clickDesktop({ x: 400, y: 300, button: "left", clickCount: 1 });
|
||||
await sdk.typeDesktopText({ text: "hello world", delayMs: 10 });
|
||||
await sdk.pressDesktopKey({ key: "ctrl+l" });
|
||||
|
||||
await sdk.stopDesktop();
|
||||
```
|
||||
|
||||
Screenshot helpers return `Uint8Array` PNG bytes. The SDK does not attempt to install OS packages remotely; callers should surface `missingDependencies` and `installCommand` from `getDesktopStatus()`.
|
||||
|
||||
## Error handling
|
||||
|
||||
```ts
|
||||
import { SandboxAgentError } from "sandbox-agent";
|
||||
|
||||
try {
|
||||
await sdk.listAgents();
|
||||
} catch (error) {
|
||||
if (error instanceof SandboxAgentError) {
|
||||
console.error(error.status, error.problem);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Inspector URL
|
||||
|
||||
When you call `SandboxAgent.start(...)`, prefer `sdk.inspectorUrl`. Some hosted providers return a browser-ready Inspector URL there.
|
||||
|
||||
```ts
|
||||
import { buildInspectorUrl } from "sandbox-agent";
|
||||
|
||||
const url = buildInspectorUrl({
|
||||
baseUrl: "https://your-sandbox-agent.example.com",
|
||||
headers: { "X-Custom-Header": "value" },
|
||||
});
|
||||
|
||||
console.log(url);
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- `baseUrl` (required unless `fetch` is provided): Sandbox Agent server URL
|
||||
- `token` (optional): Bearer token for authenticated servers
|
||||
- `headers` (optional): Additional request headers
|
||||
- `fetch` (optional): Custom fetch implementation used by SDK HTTP and session calls
|
||||
- `skipHealthCheck` (optional): set `true` to skip the startup `/v1/health` wait
|
||||
- `waitForHealth` (optional, defaults to enabled): waits for `/v1/health` before HTTP helpers and session setup proceed; pass `false` to disable or `{ timeoutMs }` to bound the wait
|
||||
- `signal` (optional): aborts the startup `/v1/health` wait used by `connect()`
|
||||
|
||||
## LLM credentials
|
||||
|
||||
Sandbox Agent supports personal API keys, shared organization keys, and per-tenant gateway keys with budget enforcement. See [LLM Credentials](/llm-credentials) for setup details.
|
||||
187
.agents/skills/sandbox-agent/references/security.md
Normal file
187
.agents/skills/sandbox-agent/references/security.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Security
|
||||
|
||||
> Source: `docs/security.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/security
|
||||
> Description: Backend-first auth and access control patterns.
|
||||
|
||||
---
|
||||
As covered in [Orchestration Architecture](/orchestration-architecture), run the Sandbox Agent client on your backend, not in the browser.
|
||||
|
||||
This keeps sandbox credentials private and gives you one place for authz, rate limiting, and audit logging.
|
||||
|
||||
## Auth model
|
||||
|
||||
Implement auth however it fits your stack (sessions, JWT, API keys, etc.), but enforce it before any sandbox-bound request.
|
||||
|
||||
Minimum checks:
|
||||
|
||||
- Authenticate the caller.
|
||||
- Authorize access to the target workspace/sandbox/session.
|
||||
- Apply request rate limits and request logging.
|
||||
|
||||
## Examples
|
||||
|
||||
### Rivet
|
||||
|
||||
```ts Actor (server)
|
||||
import { UserError, actor } from "rivetkit";
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
type ConnParams = {
|
||||
accessToken: string;
|
||||
};
|
||||
|
||||
type WorkspaceClaims = {
|
||||
sub: string;
|
||||
workspaceId: string;
|
||||
role: "owner" | "member" | "viewer";
|
||||
};
|
||||
|
||||
async function verifyWorkspaceToken(
|
||||
token: string,
|
||||
workspaceId: string,
|
||||
): Promise<WorkspaceClaims | null> {
|
||||
// Validate JWT/session token here, then enforce workspace scope.
|
||||
// Return null when invalid/expired/not a member.
|
||||
if (!token) return null;
|
||||
return { sub: "user_123", workspaceId, role: "member" };
|
||||
}
|
||||
|
||||
export const workspace = actor({
|
||||
state: {
|
||||
events: [] as Array<{ userId: string; prompt: string; createdAt: number }>,
|
||||
},
|
||||
|
||||
onBeforeConnect: async (c, params: ConnParams) => {
|
||||
const claims = await verifyWorkspaceToken(params.accessToken, c.key[0]);
|
||||
if (!claims) {
|
||||
throw new UserError("Forbidden", { code: "forbidden" });
|
||||
}
|
||||
},
|
||||
|
||||
createConnState: async (c, params: ConnParams) => {
|
||||
const claims = await verifyWorkspaceToken(params.accessToken, c.key[0]);
|
||||
if (!claims) {
|
||||
throw new UserError("Forbidden", { code: "forbidden" });
|
||||
}
|
||||
|
||||
return {
|
||||
userId: claims.sub,
|
||||
role: claims.role,
|
||||
workspaceId: claims.workspaceId,
|
||||
};
|
||||
},
|
||||
|
||||
actions: {
|
||||
submitPrompt: async (c, prompt: string) => {
|
||||
if (!c.conn) {
|
||||
throw new UserError("Connection required", { code: "connection_required" });
|
||||
}
|
||||
|
||||
if (c.conn.state.role === "viewer") {
|
||||
throw new UserError("Insufficient permissions", { code: "forbidden" });
|
||||
}
|
||||
|
||||
// Connect to Sandbox Agent from the actor (server-side only).
|
||||
// Sandbox credentials never reach the client.
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: process.env.SANDBOX_URL!,
|
||||
token: process.env.SANDBOX_TOKEN,
|
||||
});
|
||||
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
session.onEvent((event) => {
|
||||
c.broadcast("session.event", {
|
||||
userId: c.conn!.state.userId,
|
||||
eventIndex: event.eventIndex,
|
||||
sender: event.sender,
|
||||
payload: event.payload,
|
||||
});
|
||||
});
|
||||
|
||||
const result = await session.prompt([
|
||||
{ type: "text", text: prompt },
|
||||
]);
|
||||
|
||||
c.state.events.push({
|
||||
userId: c.conn.state.userId,
|
||||
prompt,
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
|
||||
return { stopReason: result.stopReason };
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts Client (browser)
|
||||
import { createClient } from "rivetkit/client";
|
||||
import type { registry } from "./actors";
|
||||
|
||||
const client = createClient<typeof registry>({
|
||||
endpoint: process.env.NEXT_PUBLIC_RIVET_ENDPOINT!,
|
||||
});
|
||||
|
||||
const handle = client.workspace.getOrCreate(["ws_123"], {
|
||||
params: { accessToken: userJwt },
|
||||
});
|
||||
|
||||
const conn = handle.connect();
|
||||
|
||||
conn.on("session.event", (event) => {
|
||||
console.log(event.sender, event.payload);
|
||||
});
|
||||
|
||||
const result = await conn.submitPrompt("Plan a refactor for auth middleware.");
|
||||
console.log(result.stopReason);
|
||||
```
|
||||
|
||||
Use [onBeforeConnect](https://rivet.dev/docs/actors/authentication), [connection params](https://rivet.dev/docs/actors/connections), and [actor keys](https://rivet.dev/docs/actors/keys) together so each actor enforces auth per workspace.
|
||||
|
||||
### Hono
|
||||
|
||||
```ts
|
||||
import { Hono } from "hono";
|
||||
import { bearerAuth } from "hono/bearer-auth";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.use("/sandbox/*", bearerAuth({ token: process.env.APP_API_TOKEN! }));
|
||||
|
||||
app.all("/sandbox/*", async (c) => {
|
||||
const incoming = new URL(c.req.url);
|
||||
const upstreamUrl = new URL(process.env.SANDBOX_URL!);
|
||||
upstreamUrl.pathname = incoming.pathname.replace(/^\/sandbox/, "/v1");
|
||||
upstreamUrl.search = incoming.search;
|
||||
|
||||
const headers = new Headers();
|
||||
headers.set("authorization", `Bearer ${process.env.SANDBOX_TOKEN ?? ""}`);
|
||||
|
||||
const accept = c.req.header("accept");
|
||||
if (accept) headers.set("accept", accept);
|
||||
|
||||
const contentType = c.req.header("content-type");
|
||||
if (contentType) headers.set("content-type", contentType);
|
||||
|
||||
const body =
|
||||
c.req.method === "POST" || c.req.method === "PUT" || c.req.method === "PATCH"
|
||||
? await c.req.text()
|
||||
: undefined;
|
||||
|
||||
const upstream = await fetch(upstreamUrl, {
|
||||
method: c.req.method,
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
return new Response(upstream.body, {
|
||||
status: upstream.status,
|
||||
headers: upstream.headers,
|
||||
});
|
||||
});
|
||||
```
|
||||
122
.agents/skills/sandbox-agent/references/session-persistence.md
Normal file
122
.agents/skills/sandbox-agent/references/session-persistence.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Persisting Sessions
|
||||
|
||||
> Source: `docs/session-persistence.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/session-persistence
|
||||
> Description: Choose and configure session persistence for the TypeScript SDK.
|
||||
|
||||
---
|
||||
The TypeScript SDK uses a `SessionPersistDriver` to store session records and event history.
|
||||
If you do not provide one, the SDK uses in-memory storage.
|
||||
With persistence enabled, sessions can be restored after runtime/session loss. See [Session Restoration](/session-restoration).
|
||||
|
||||
Each driver stores:
|
||||
|
||||
- `SessionRecord` (`id`, `agent`, `agentSessionId`, `lastConnectionId`, `createdAt`, optional `destroyedAt`, optional `sandboxId`, optional `sessionInit`, optional `configOptions`, optional `modes`)
|
||||
- `SessionEvent` (`id`, `eventIndex`, `sessionId`, `connectionId`, `sender`, `payload`, `createdAt`)
|
||||
|
||||
## Persistence drivers
|
||||
|
||||
### Rivet
|
||||
|
||||
Recommended for sandbox orchestration with actor state. See [Multiplayer](/multiplayer) for a full Rivet actor example with persistence in actor state.
|
||||
|
||||
### IndexedDB (browser)
|
||||
|
||||
Best for browser apps that should survive reloads. See the [Inspector source](https://github.com/rivet-dev/sandbox-agent/tree/main/frontend/packages/inspector/src/persist-indexeddb.ts) for a complete IndexedDB driver you can copy into your project.
|
||||
|
||||
### In-memory (built-in)
|
||||
|
||||
Best for local dev and ephemeral workloads. No extra dependencies required.
|
||||
|
||||
```ts
|
||||
import { InMemorySessionPersistDriver, SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const persist = new InMemorySessionPersistDriver({
|
||||
maxSessions: 1024,
|
||||
maxEventsPerSession: 500,
|
||||
});
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
persist,
|
||||
});
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
Best for local/server Node apps that need durable storage without a DB server.
|
||||
|
||||
```bash
|
||||
npm install better-sqlite3
|
||||
```
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { SQLiteSessionPersistDriver } from "./persist.ts";
|
||||
|
||||
const persist = new SQLiteSessionPersistDriver({
|
||||
filename: "./sandbox-agent.db",
|
||||
});
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
persist,
|
||||
});
|
||||
```
|
||||
|
||||
See the [full SQLite example](https://github.com/rivet-dev/sandbox-agent/tree/main/examples/persist-sqlite) for the complete driver implementation you can copy into your project.
|
||||
|
||||
### Postgres
|
||||
|
||||
Use when you already run Postgres and want shared relational storage.
|
||||
|
||||
```bash
|
||||
npm install pg
|
||||
```
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
import { PostgresSessionPersistDriver } from "./persist.ts";
|
||||
|
||||
const persist = new PostgresSessionPersistDriver({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
schema: "public",
|
||||
});
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
persist,
|
||||
});
|
||||
```
|
||||
|
||||
See the [full Postgres example](https://github.com/rivet-dev/sandbox-agent/tree/main/examples/persist-postgres) for the complete driver implementation you can copy into your project.
|
||||
|
||||
### Custom driver
|
||||
|
||||
Implement `SessionPersistDriver` for custom backends.
|
||||
|
||||
```ts
|
||||
import type { SessionPersistDriver } from "sandbox-agent";
|
||||
|
||||
class MyDriver implements SessionPersistDriver {
|
||||
async getSession(id) { return undefined; }
|
||||
async listSessions(request) { return { items: [] }; }
|
||||
async updateSession(session) {}
|
||||
async listEvents(request) { return { items: [] }; }
|
||||
async insertEvent(sessionId, event) {}
|
||||
}
|
||||
```
|
||||
|
||||
## Replay controls
|
||||
|
||||
`SandboxAgent.connect(...)` supports:
|
||||
|
||||
- `replayMaxEvents` (default `50`)
|
||||
- `replayMaxChars` (default `12000`)
|
||||
|
||||
These cap replay size when restoring sessions.
|
||||
|
||||
## Related docs
|
||||
|
||||
- [SDK Overview](/sdk-overview)
|
||||
- [Session Restoration](/session-restoration)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Session Restoration
|
||||
|
||||
> Source: `docs/session-restoration.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/session-restoration
|
||||
> Description: How the TypeScript SDK restores sessions after connection/runtime loss.
|
||||
|
||||
---
|
||||
Sandbox Agent automatically restores stale sessions when live session state is no longer available.
|
||||
|
||||
This is driven by the configured `SessionPersistDriver` (`inMemory`, IndexedDB, SQLite, Postgres, or custom).
|
||||
|
||||
## How Auto-Restore Works
|
||||
|
||||
When you call `session.prompt(...)` (or `resumeSession(...)`) and the saved session points to a stale connection, the SDK:
|
||||
|
||||
1. Recreates a fresh session for the same local session id.
|
||||
2. Rebinds the local session to the new runtime session id.
|
||||
3. Replays recent persisted events into the next prompt as context.
|
||||
|
||||
This happens automatically; you do not need to manually rebuild the session.
|
||||
|
||||
## Replay Limits
|
||||
|
||||
Replay payload size is capped by:
|
||||
|
||||
- `replayMaxEvents` (default `50`)
|
||||
- `replayMaxChars` (default `12000`)
|
||||
|
||||
These controls limit prompt growth during restore while preserving recent context.
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [SDK Overview](/sdk-overview)
|
||||
- [Persisting Sessions](/session-persistence)
|
||||
- [Agent Sessions](/agent-sessions)
|
||||
79
.agents/skills/sandbox-agent/references/skills-config.md
Normal file
79
.agents/skills/sandbox-agent/references/skills-config.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Skills
|
||||
|
||||
> Source: `docs/skills-config.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/skills-config
|
||||
> Description: Configure skill sources for agent sessions.
|
||||
|
||||
---
|
||||
Skills are local instruction bundles stored in `SKILL.md` files.
|
||||
|
||||
## Configuring skills
|
||||
|
||||
Use `setSkillsConfig` / `getSkillsConfig` / `deleteSkillsConfig` to manage skill source config by directory + skill name.
|
||||
|
||||
```ts
|
||||
import { SandboxAgent } from "sandbox-agent";
|
||||
|
||||
const sdk = await SandboxAgent.connect({
|
||||
baseUrl: "http://127.0.0.1:2468",
|
||||
});
|
||||
|
||||
// Add a skill
|
||||
await sdk.setSkillsConfig(
|
||||
{
|
||||
directory: "/workspace",
|
||||
skillName: "default",
|
||||
},
|
||||
{
|
||||
sources: [
|
||||
{ type: "github", source: "rivet-dev/skills", skills: ["sandbox-agent"] },
|
||||
{ type: "local", source: "/workspace/my-custom-skill" },
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
// Create a session using the configured skills
|
||||
const session = await sdk.createSession({
|
||||
agent: "claude",
|
||||
cwd: "/workspace",
|
||||
});
|
||||
|
||||
await session.prompt([
|
||||
{ type: "text", text: "Use available skills to help with this task." },
|
||||
]);
|
||||
|
||||
// List skills
|
||||
const config = await sdk.getSkillsConfig({
|
||||
directory: "/workspace",
|
||||
skillName: "default",
|
||||
});
|
||||
|
||||
console.log(config.sources.length);
|
||||
|
||||
// Delete skill
|
||||
await sdk.deleteSkillsConfig({
|
||||
directory: "/workspace",
|
||||
skillName: "default",
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
## Skill sources
|
||||
|
||||
Each `skills.sources` entry describes where to find skills.
|
||||
|
||||
| Type | `source` value | Example |
|
||||
|------|---------------|---------|
|
||||
| `github` | `owner/repo` | `"rivet-dev/skills"` |
|
||||
| `local` | filesystem path | `"/workspace/my-skill"` |
|
||||
| `git` | git clone URL | `"https://git.example.com/skills.git"` |
|
||||
|
||||
Optional fields:
|
||||
|
||||
- `skills`: subset of skill directory names to include
|
||||
- `ref`: branch/tag/commit (for `github` and `git`)
|
||||
- `subpath`: subdirectory within repo to scan
|
||||
|
||||
## Custom skills
|
||||
|
||||
To write, upload, and configure your own skills inside the sandbox, see [Custom Tools](/custom-tools).
|
||||
27
.agents/skills/sandbox-agent/references/telemetry.md
Normal file
27
.agents/skills/sandbox-agent/references/telemetry.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Telemetry
|
||||
|
||||
> Source: `docs/telemetry.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/telemetry
|
||||
> Description: Anonymous telemetry collected by sandbox-agent.
|
||||
|
||||
---
|
||||
sandbox-agent sends a small, anonymous telemetry payload on startup and then every 5 minutes to help us understand usage and improve reliability.
|
||||
|
||||
## What gets sent
|
||||
|
||||
- Sandbox Agent version.
|
||||
- OS name, architecture, and OS family.
|
||||
- Detected sandbox provider (for example: Docker, E2B, Vercel Sandboxes).
|
||||
|
||||
Each sandbox gets a random anonymous ID stored on disk so usage can be counted without identifying users.
|
||||
The last successful send time is also stored on disk, and heartbeats are rate-limited to at most one every 5 minutes.
|
||||
|
||||
## Opting out
|
||||
|
||||
Telemetry is enabled by default in release builds.
|
||||
|
||||
Disable it with:
|
||||
|
||||
```bash
|
||||
sandbox-agent server --no-telemetry
|
||||
```
|
||||
58
.agents/skills/sandbox-agent/references/troubleshooting.md
Normal file
58
.agents/skills/sandbox-agent/references/troubleshooting.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Troubleshooting
|
||||
|
||||
> Source: `docs/troubleshooting.mdx`
|
||||
> Canonical URL: https://sandboxagent.dev/docs/troubleshooting
|
||||
> Description: Common issues and solutions when running sandbox-agent
|
||||
|
||||
---
|
||||
## "Agent Process Exited" immediately after sending a message
|
||||
|
||||
This typically means the agent (Claude, Codex) crashed on startup. Common causes:
|
||||
|
||||
### 1. Network restrictions
|
||||
|
||||
The sandbox cannot reach the AI provider's API (`api.anthropic.com` or `api.openai.com`). Test connectivity with:
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 https://api.anthropic.com/v1/messages
|
||||
```
|
||||
|
||||
A `000` or timeout means the network is blocked. See [Daytona Network Restrictions](#daytona-network-restrictions) below.
|
||||
|
||||
### 2. Missing API key
|
||||
|
||||
Ensure `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` is set in the sandbox environment, not just locally.
|
||||
|
||||
### 3. Agent binary not found
|
||||
|
||||
Verify the agent is installed:
|
||||
|
||||
```bash
|
||||
ls -la ~/.local/share/sandbox-agent/bin/
|
||||
```
|
||||
|
||||
## Daytona Network Restrictions
|
||||
|
||||
Daytona sandboxes have tier-based network access:
|
||||
|
||||
| Tier | Network Access |
|
||||
|------|----------------|
|
||||
| Tier 1 & 2 | Restricted. **Cannot be overridden.** AI provider APIs blocked by default. |
|
||||
| Tier 3 & 4 | Full internet access. Custom allowlists supported. |
|
||||
|
||||
If you're on Tier 1/2 and agents fail immediately, you have two options:
|
||||
|
||||
1. **Upgrade to Tier 3+** for full network access
|
||||
2. **Contact Daytona support** to whitelist `api.anthropic.com` and `api.openai.com` for your organization
|
||||
|
||||
The `networkAllowList` parameter only works on Tier 3+:
|
||||
|
||||
```typescript
|
||||
await daytona.create({
|
||||
snapshot: "my-snapshot",
|
||||
envVars: { ANTHROPIC_API_KEY: "..." },
|
||||
networkAllowList: "api.anthropic.com,api.openai.com", // Tier 3+ only
|
||||
});
|
||||
```
|
||||
|
||||
See [Daytona Network Limits documentation](https://www.daytona.io/docs/en/network-limits/) for details.
|
||||
Reference in New Issue
Block a user