the universal sandbox runtime for agents and humans.
pocketenv.io
sandbox
openclaw
agent
claude-code
vercel-sandbox
deno-sandbox
cloudflare-sandbox
atproto
sprites
daytona
1import type { Context, Session, TerminalSocket } from "context";
2import { eq, or } from "drizzle-orm";
3import schema from "schema";
4import { consola } from "consola";
5import { Sandbox } from "e2b";
6import decrypt from "lib/decrypt";
7import type { Message } from "pty/pty-tunnel/messages";
8
9export async function createTerminalSession(
10 ctx: Context,
11 id: string,
12 key = id,
13): Promise<Session> {
14 const [record] = await ctx.db
15 .select()
16 .from(schema.sandboxes)
17 .leftJoin(schema.e2bAuth, eq(schema.e2bAuth.sandboxId, schema.sandboxes.id))
18 .where(or(eq(schema.sandboxes.id, id), eq(schema.sandboxes.sandboxId, id)))
19 .execute();
20
21 if (!record?.e2b_auth) {
22 consola.error("E2B auth not found for sandbox", { id });
23 throw new Error("E2B auth not found for sandbox " + id);
24 }
25
26 if (!record.sandboxes.sandboxId) {
27 consola.error("Sandbox ID not found for sandbox", { id });
28 throw new Error("Sandbox ID not found for sandbox " + id);
29 }
30
31 const sandbox = await Sandbox.connect(record.sandboxes.sandboxId, {
32 apiKey: decrypt(record.e2b_auth.apiKey),
33 });
34
35 // pid is set after pty.create() resolves; sendMessage closes over it.
36 let pid = 0;
37
38 const socket: TerminalSocket = {
39 sendMessage(msg: Message) {
40 if (msg.type === "message") {
41 sandbox.pty
42 .sendInput(pid, new TextEncoder().encode(msg.message))
43 .catch((err) => consola.error("E2B PTY sendInput error:", err));
44 } else if (msg.type === "resize") {
45 sandbox.pty
46 .resize(pid, { cols: msg.cols, rows: msg.rows })
47 .catch((err) => consola.error("E2B PTY resize error:", err));
48 }
49 },
50 };
51
52 const session: Session = { socket, clients: new Set(), wsClients: new Set() };
53
54 const terminal = await sandbox.pty.create({
55 cols: process.stdout.columns ?? 80,
56 rows: process.stdout.rows ?? 24,
57 onData: (data) => {
58 const text = Buffer.from(data).toString("utf-8");
59 for (const res of session.clients) {
60 res.write("event: output\n");
61 res.write(`data: ${JSON.stringify({ data: text })}\n\n`);
62 }
63 for (const ws of session.wsClients) {
64 if (ws.readyState === ws.OPEN) ws.send(text);
65 }
66 },
67 });
68
69 pid = terminal.pid;
70 consola.info("E2B PTY session created", { id, pid });
71
72 ctx.sessions.set(key, session);
73 return session;
74}