the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Hopx terminal provider and integrate it

+102
+95
apps/api/src/pty/hopx/index.ts
··· 1 + import type { Context, Session, TerminalSocket } from "context"; 2 + import { eq, or } from "drizzle-orm"; 3 + import schema from "schema"; 4 + import { consola } from "consola"; 5 + import { Sandbox } from "@hopx-ai/sdk"; 6 + import decrypt from "lib/decrypt"; 7 + import chalk from "chalk"; 8 + import type { Message } from "pty/pty-tunnel/messages"; 9 + 10 + export async function createTerminalSession( 11 + ctx: Context, 12 + id: string, 13 + key = id, 14 + ): Promise<Session> { 15 + const [record] = await ctx.db 16 + .select() 17 + .from(schema.sandboxes) 18 + .leftJoin( 19 + schema.hopxAuth, 20 + eq(schema.hopxAuth.sandboxId, schema.sandboxes.id), 21 + ) 22 + .where(or(eq(schema.sandboxes.id, id), eq(schema.sandboxes.sandboxId, id))) 23 + .execute(); 24 + 25 + if (!record?.hopx_auth) { 26 + consola.error("Hopx auth not found for sandbox", { id }); 27 + throw new Error("Hopx auth not found for sandbox " + id); 28 + } 29 + 30 + if (!record.sandboxes.sandboxId) { 31 + consola.error("Sandbox ID not found for sandbox", { id }); 32 + throw new Error("Sandbox ID not found for sandbox " + id); 33 + } 34 + 35 + const sandboxId = record.sandboxes.sandboxId; 36 + const apiKey = decrypt(record.hopx_auth.apiKey); 37 + 38 + consola.info("Hopx: connecting to sandbox", chalk.greenBright(sandboxId)); 39 + const sandbox = await Sandbox.connect(sandboxId, apiKey); 40 + consola.info("Hopx: sandbox connected", chalk.greenBright(sandboxId)); 41 + 42 + consola.info("Hopx: connecting terminal", chalk.greenBright(sandboxId)); 43 + const ws = await sandbox.terminal.connect(); 44 + consola.info("Hopx: terminal connected", chalk.greenBright(sandboxId)); 45 + 46 + sandbox.terminal.resize( 47 + ws, 48 + process.stdout.columns ?? 80, 49 + process.stdout.rows ?? 24, 50 + ); 51 + 52 + const socket: TerminalSocket = { 53 + sendMessage(msg: Message) { 54 + if (msg.type === "message") { 55 + sandbox.terminal.sendInput(ws, msg.message); 56 + } else if (msg.type === "resize") { 57 + sandbox.terminal.resize(ws, msg.cols, msg.rows); 58 + } 59 + }, 60 + }; 61 + 62 + const session: Session = { socket, clients: new Set(), wsClients: new Set() }; 63 + 64 + (async () => { 65 + try { 66 + for await (const message of sandbox.terminal.output(ws)) { 67 + if (message.type === "output") { 68 + const text = message.data; 69 + for (const res of session.clients) { 70 + res.write("event: output\n"); 71 + res.write(`data: ${JSON.stringify({ data: text })}\n\n`); 72 + } 73 + for (const client of session.wsClients) { 74 + if (client.readyState === client.OPEN) client.send(text); 75 + } 76 + } else if (message.type === "exit") { 77 + break; 78 + } 79 + } 80 + } catch (err) { 81 + consola.error("Hopx terminal output error:", err); 82 + } finally { 83 + ctx.sessions.delete(key); 84 + for (const client of session.wsClients) { 85 + if (client.readyState === client.OPEN) client.close(1000, "exit"); 86 + } 87 + session.clients.clear(); 88 + session.wsClients.clear(); 89 + } 90 + })(); 91 + 92 + consola.info("Hopx: terminal session ready", chalk.greenBright(id)); 93 + ctx.sessions.set(key, session); 94 + return session; 95 + }
+7
apps/api/src/pty/index.ts
··· 10 10 import * as modal from "./modal"; 11 11 import * as e2b from "./e2b"; 12 12 import * as runloop from "./runloop"; 13 + import * as hopx from "./hopx"; 13 14 import { WebSocketServer, type WebSocket } from "ws"; 14 15 import type { IncomingMessage } from "http"; 15 16 ··· 57 58 modalAuth: schema.modalAuth.id, 58 59 e2bAuth: schema.e2bAuth.id, 59 60 runloopAuth: schema.runloopAuth.id, 61 + hopxAuth: schema.hopxAuth.id, 60 62 }) 61 63 .from(schema.sandboxes) 62 64 .leftJoin( ··· 68 70 schema.runloopAuth, 69 71 eq(schema.runloopAuth.sandboxId, schema.sandboxes.id), 70 72 ) 73 + .leftJoin( 74 + schema.hopxAuth, 75 + eq(schema.hopxAuth.sandboxId, schema.sandboxes.id), 76 + ) 71 77 .where(or(eq(schema.sandboxes.id, id), eq(schema.sandboxes.sandboxId, id))) 72 78 .execute(); 73 79 74 80 if (record?.modalAuth) return modal.createTerminalSession(ctx, id, key); 75 81 if (record?.e2bAuth) return e2b.createTerminalSession(ctx, id, key); 76 82 if (record?.runloopAuth) return runloop.createTerminalSession(ctx, id, key); 83 + if (record?.hopxAuth) return hopx.createTerminalSession(ctx, id, key); 77 84 return vercel.createTerminalSession(ctx, id, key); 78 85 } 79 86