@agentproto/sandbox-e2b 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +30 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Sandbox } from 'e2b';
|
|
1
|
+
import { Sandbox, SandboxNotFoundError } from 'e2b';
|
|
2
|
+
import { SandboxBoxGoneError } from '@agentproto/sandbox';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @agentproto/sandbox-e2b v0.1.0-alpha
|
|
@@ -154,7 +155,13 @@ var e2bSandboxProvider = {
|
|
|
154
155
|
const config = readE2bConfig(spec);
|
|
155
156
|
const port = config.port ?? DEFAULT_PORT;
|
|
156
157
|
const workspace = config.workspace ?? DEFAULT_WORKSPACE;
|
|
157
|
-
|
|
158
|
+
let sandbox;
|
|
159
|
+
try {
|
|
160
|
+
sandbox = await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY });
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (err instanceof SandboxNotFoundError) throw new SandboxBoxGoneError(sandboxId, err);
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
158
165
|
await sandbox.setTimeout(config.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS);
|
|
159
166
|
try {
|
|
160
167
|
const host = sandbox.getHost(port);
|
|
@@ -164,6 +171,27 @@ var e2bSandboxProvider = {
|
|
|
164
171
|
await sandbox.kill().catch(() => void 0);
|
|
165
172
|
throw err;
|
|
166
173
|
}
|
|
174
|
+
},
|
|
175
|
+
/**
|
|
176
|
+
* Poll the provider control plane (`GET /sandboxes/:id` via the SDK's
|
|
177
|
+
* `Sandbox.getInfo`) — cheap (one authed HTTP call, no box contact),
|
|
178
|
+
* exactly the liveness signal a vanished box needs while the session
|
|
179
|
+
* descriptor and ledger still say paused/connected. `{alive:false}` on
|
|
180
|
+
* e2b's 404 ("Sandbox Not Found"); rethrows anything else so callers
|
|
181
|
+
* read "check failed", never "box died". NOTE (operator guidance): e2b
|
|
182
|
+
* also offers sandbox lifecycle webhooks, which would push death instead
|
|
183
|
+
* of forcing a poll — the SDK in-tree exposes no webhook/event API, so
|
|
184
|
+
* polling stays the implemented probe; switch to (or augment with) the
|
|
185
|
+
* webhook if/when the SDK surfaces one.
|
|
186
|
+
*/
|
|
187
|
+
async probe(sandboxId) {
|
|
188
|
+
try {
|
|
189
|
+
const info = await Sandbox.getInfo(sandboxId, { apiKey: process.env.E2B_API_KEY });
|
|
190
|
+
return { alive: true, ...info.state !== void 0 ? { state: String(info.state) } : {} };
|
|
191
|
+
} catch (err) {
|
|
192
|
+
if (err instanceof SandboxNotFoundError) return { alive: false };
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
167
195
|
}
|
|
168
196
|
};
|
|
169
197
|
function finishConnect(sandbox, host, opts, spec) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/template-versions.generated.ts","../src/provider.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * GENERATED by scripts/sync-templates.mjs from templates/workstation/versions.json.\n * DO NOT EDIT — edit versions.json and re-run the sync script instead.\n */\n\n/** Published stable e2b template id baked with the pins below. */\nexport const DEFAULT_TEMPLATE = \"53ybr99wdfgoebi9nee8\"\nexport const TEMPLATE_ALIASES = {\n stable: \"agentproto-workstation\",\n dev: \"agentproto-workstation-dev\",\n} as const\n/**\n * What each published image was PROVEN to contain at its last real e2b\n * build. A null field means unknown (e.g. out-of-band bake) — consumers\n * must assume the image does NOT match the declared pins.\n */\nexport const TEMPLATES = {\n stable: {\n id: \"53ybr99wdfgoebi9nee8\",\n alias: \"agentproto-workstation\",\n baked: {\n \"cli\": \"0.17.0\",\n \"adapters\": {\n \"@agentproto/adapter-hermes\": \"0.4.10\",\n \"@agentproto/adapter-mastra-agent\": \"0.6.0\",\n \"@agentproto/adapter-opencode\": \"1.1.10\"\n },\n \"builtAt\": \"2026-09-05T20:39:40Z\"\n },\n },\n dev: {\n id: \"tnqtmeims5q9ex7j9k06\",\n alias: \"agentproto-workstation-dev\",\n baked: {\n \"cli\": \"0.17.0\",\n \"adapters\": {\n \"@agentproto/adapter-hermes\": \"0.4.10\",\n \"@agentproto/adapter-mastra-agent\": \"0.6.0\",\n \"@agentproto/adapter-opencode\": \"1.1.10\"\n },\n \"builtAt\": \"2026-09-05T16:08:13Z\"\n },\n },\n} as const\nexport const BAKED_CLI_VERSION = \"0.17.0\"\nexport const BAKED_ADAPTERS = {\n \"@agentproto/adapter-hermes\": \"0.4.10\",\n \"@agentproto/adapter-mastra-agent\": \"0.6.0\",\n \"@agentproto/adapter-opencode\": \"1.1.10\",\n} as const\nexport const BAKED_RUNTIME = {\n \"opencode-ai\": \"1.18.28\",\n} as const\nexport const BAKED_BASE_IMAGE = \"e2bdev/code-interpreter:latest\"\n","/**\n * e2b `SandboxProvider` — boots the pre-built `agentproto-workstation`\n * template, ensures the agentproto daemon is listening on it, and exposes\n * the daemon's `/mcp` endpoint via `sandbox.getHost()` so\n * `createSandboxAgentSessionHost` (`@agentproto/sandbox`) can hand it\n * straight to `connectDaemonAgentSessionHost`.\n *\n * `getHost(port)` returns a publicly reachable hostname for that port\n * directly — e2b's per-sandbox auth token (`SandboxOpts.secure`) gates the\n * SDK's own control-plane (envd) traffic, not arbitrary listening ports the\n * sandbox process opens, so no extra auth header is threaded through here.\n * The daemon's OWN origin allowlist is a separate gate, though: it defaults\n * to `localhost:*`, which rejects a connection from this host process\n * against the sandbox's public `https://<getHost>` origin. `--allow-origin`\n * is passed the sandbox's own getHost origin to open that gate.\n *\n * The workstation template MAY already autostart the daemon; since that\n * can't be verified without a live template, this provider checks health\n * first and only issues the start command when the daemon isn't already\n * responding — correct either way. When it does start the daemon on a\n * non-proven template, it first updates the CLI (custom templates can lag\n * behind). The boot install is skipped by default ONLY when the template's\n * recorded `baked` block (templates/workstation/versions.json, via the\n * generated module) proves the image already carries the requested pin —\n * an unproven bake (`baked: null` fields, e.g. the current out-of-band\n * stable image) keeps the legacy install. An explicit `updateCliOnBoot`\n * overrides either way.\n */\n\nimport { Sandbox } from \"e2b\"\nimport type { BootedSandbox, SandboxBootOpts, SandboxProvider, SandboxSpec } from \"@agentproto/sandbox\"\nimport { DEFAULT_TEMPLATE, TEMPLATES } from \"./template-versions.generated.js\"\n\n/** Re-exported from the generated module (single source:\n * `templates/workstation/versions.json` via scripts/sync-templates.mjs). */\nexport { DEFAULT_TEMPLATE, TEMPLATES }\n\n/** `serve.ts`'s default port (`DEFAULT_MCP_URL` in `@agentproto/harness`). */\nconst DEFAULT_PORT = 18790\n/**\n * Default sandbox LIFETIME. e2b's own default is 5 minutes (`SandboxOpts.\n * timeoutMs @default 300_000`) — lethal for real agent work: boot (~30s) +\n * `npm i -g` (~90s) + a multi-minute agent turn crosses 5:00 mid-turn, e2b\n * reaps the VM, and the next request cold-boots it WITHOUT the daemon —\n * surfacing as the baffling `\"The sandbox is running but port is not open\"`\n * 502 (observed live killing a PR-review turn). 45 minutes covers any\n * plausible session; `config.timeoutMs` still overrides in either direction.\n */\nconst DEFAULT_SANDBOX_TIMEOUT_MS = 45 * 60_000\nconst DEFAULT_WORKSPACE = \"/home/user\"\nconst HEALTH_PROBE_TIMEOUT_MS = 3_000\nconst DAEMON_READY_TIMEOUT_MS = 30_000\nconst POLL_INTERVAL_MS = 500\nconst UPDATE_CLI_TIMEOUT_MS = 120_000\n\ninterface E2bSandboxConfig {\n /** Template name or id. Defaults to the `agentproto-workstation` template. */\n template?: string\n /** Port the agentproto daemon listens on inside the sandbox. Default 18790. */\n port?: number\n /** `--workspace` passed to `agentproto serve`. Default `/home/user`. */\n workspace?: string\n /** Sandbox lifetime cap, forwarded to `Sandbox.create`. */\n timeoutMs?: number\n /** How long to wait for the health probe before assuming the daemon isn't autostarted. */\n healthProbeTimeoutMs?: number\n /** How long to wait for the daemon to become healthy after starting it. */\n daemonReadyTimeoutMs?: number\n /** Delay between health-probe attempts. */\n pollIntervalMs?: number\n /** Run `npm i -g @agentproto/cli@...` before starting the daemon, so a\n * stale template bake doesn't pin callers to an old agentproto version.\n * Default: false when the configured template is the baked stable\n * workstation one AND the requested cliVersion matches its baked pin\n * (the bake already carries exactly that CLI) — true otherwise (custom\n * templates can lag behind). Only runs when this provider is the one\n * starting the daemon (skipped when the health probe finds it already\n * autostarted). */\n updateCliOnBoot?: boolean\n /** CLI version to install on boot, pinned instead of a floating `@latest`.\n * A broken `@agentproto/cli@latest` publish otherwise silently kills the\n * box on boot; pinning a known-good version (sourced from config, defaulting\n * to the repo's declared `cliVersion`) makes the boot install reproducible.\n * Produces `@agentproto/cli@<version>`; when unset, falls back to `@latest`. */\n cliVersion?: string\n /** Extra npm package specs installed globally ALONGSIDE the CLI update\n * (single `npm i -g` invocation). Load-bearing for adapters: the CLI\n * update replaces the global install, LOSING any template-baked\n * `@agentproto/adapter-*` packages (verified against a live template) —\n * so the adapter the caller intends to spawn must be (re)installed in the\n * same breath, e.g. `[\"@agentproto/adapter-claude-sdk@latest\"]` (plus\n * `\"@anthropic-ai/claude-code@latest\"` for the claude-code adapter's\n * underlying CLI). Ignored when `updateCliOnBoot` is false or the daemon\n * was already autostarted healthy. Declaring a non-empty list also\n * re-enables the boot install when `updateCliOnBoot` is unset — entries\n * are never silently dropped on the baked-template skip path. */\n installPackages?: string[]\n /** Timeout for the `npm i -g` update command. Default 120s. */\n updateCliTimeoutMs?: number\n /** Extra shell commands host-executed inside the box AFTER the boot `npm i -g`\n * and BEFORE `agentproto serve` hands control to the agent. Generic\n * provision-time hook — no policy baked in here; callers decide what to run\n * (e.g. installing a git hook). Each entry is passed verbatim to\n * `sandbox.commands.run`. Runs on EVERY boot/connect, even when the health\n * probe finds the daemon already autostarted — entries must be idempotent. */\n setupCommands?: string[]\n}\n\nfunction readE2bConfig(spec: SandboxSpec): E2bSandboxConfig {\n const config = (spec.config ?? {}) as Record<string, unknown>\n return {\n template: typeof config.template === \"string\" ? config.template : undefined,\n port: typeof config.port === \"number\" ? config.port : undefined,\n workspace: typeof config.workspace === \"string\" ? config.workspace : undefined,\n timeoutMs: typeof config.timeoutMs === \"number\" ? config.timeoutMs : undefined,\n healthProbeTimeoutMs:\n typeof config.healthProbeTimeoutMs === \"number\" ? config.healthProbeTimeoutMs : undefined,\n daemonReadyTimeoutMs:\n typeof config.daemonReadyTimeoutMs === \"number\" ? config.daemonReadyTimeoutMs : undefined,\n pollIntervalMs: typeof config.pollIntervalMs === \"number\" ? config.pollIntervalMs : undefined,\n updateCliOnBoot: typeof config.updateCliOnBoot === \"boolean\" ? config.updateCliOnBoot : undefined,\n cliVersion: typeof config.cliVersion === \"string\" ? config.cliVersion : undefined,\n installPackages: Array.isArray(config.installPackages)\n ? config.installPackages.filter((p): p is string => typeof p === \"string\" && p.length > 0)\n : undefined,\n updateCliTimeoutMs:\n typeof config.updateCliTimeoutMs === \"number\" ? config.updateCliTimeoutMs : undefined,\n setupCommands: Array.isArray(config.setupCommands)\n ? config.setupCommands.filter((c): c is string => typeof c === \"string\" && c.length > 0)\n : undefined,\n }\n}\n\n/**\n * Default the on-boot CLI update OFF only for a template whose recorded\n * `baked` block PROVES the image already carries the requested CLI pin —\n * the install would just re-download the same version (90-120s per cold\n * boot, npm registry dependency, and it replaces the template's baked\n * adapters). The gate is on the RECORDED bake, not the template id: an\n * out-of-band bake nobody has verified has `baked.cli: null` and keeps the\n * legacy default of true. A requested cliVersion that differs from the\n * recorded bake also installs. An explicit `updateCliOnBoot` always wins.\n */\nexport function resolveUpdateCli(\n config: Pick<E2bSandboxConfig, \"updateCliOnBoot\" | \"cliVersion\" | \"installPackages\">,\n template: string,\n templates: { [key: string]: { id: string | null; baked: { cli: string | null } } } = TEMPLATES,\n): boolean {\n if (config.updateCliOnBoot !== undefined) return config.updateCliOnBoot\n // Runtime escape hatch: a caller declaring installPackages wants an\n // install to happen — never silently drop them on the skip path.\n if ((config.installPackages?.length ?? 0) > 0) return true\n const baked = Object.values(templates).find(entry => entry.id !== null && entry.id === template)?.baked\n if (!baked || baked.cli === null) return true\n // Recorded bake matches the requested pin (an unset cliVersion means the\n // bake itself is the pin) → nothing to install.\n return config.cliVersion !== undefined && config.cliVersion !== baked.cli\n}\n\n/**\n * Probe health, and — if the daemon isn't already up — update the (possibly\n * stale) baked CLI and start it, then re-probe. Shared by `boot` (a fresh\n * template's autostart may not have fired yet) and `connect` (a resumed box\n * may wake with a dead daemon process — see PR3 risk \"stale daemon on\n * reconnect\"). Throws (after killing the sandbox) if the daemon never comes\n * up.\n */\nasync function ensureDaemonHealthy(\n sandbox: Sandbox,\n host: string,\n port: number,\n workspace: string,\n config: E2bSandboxConfig,\n env: Record<string, string>,\n updateCli: boolean,\n): Promise<void> {\n const healthUrl = `https://${host}/health`\n const healthProbeTimeoutMs = config.healthProbeTimeoutMs ?? HEALTH_PROBE_TIMEOUT_MS\n const daemonReadyTimeoutMs = config.daemonReadyTimeoutMs ?? DAEMON_READY_TIMEOUT_MS\n const pollIntervalMs = config.pollIntervalMs ?? POLL_INTERVAL_MS\n\n const alreadyUp = await probeHealth(healthUrl, healthProbeTimeoutMs, pollIntervalMs)\n\n if (!alreadyUp && updateCli) {\n // One `npm i -g` for the CLI update AND any caller-declared extras\n // (`installPackages` — typically the adapter about to be spawned). The\n // CLI update alone replaces the global install and LOSES template-baked\n // adapters, so the two must land in the same invocation. Extras are\n // single-quoted (with any quote chars stripped) since they arrive from\n // caller-authored spec config.\n const extras = (config.installPackages ?? [])\n .map(spec => ` '${spec.replace(/'/g, \"\")}'`)\n .join(\"\")\n const cli = `@agentproto/cli@${config.cliVersion ?? \"latest\"}`\n await sandbox.commands.run(`sudo npm i -g ${cli}${extras}`, {\n envs: env,\n timeoutMs: config.updateCliTimeoutMs ?? UPDATE_CLI_TIMEOUT_MS,\n })\n }\n // Caller-declared provision hooks — host-executed after the npm install and\n // before the agent gets control. Kept generic (no policy here); the ts layer\n // uses this to install a commit-msg hook that strips AI-attribution trailers.\n // Runs regardless of `alreadyUp`: the workstation template MAY autostart the\n // daemon (module doc above), and a caller-declared hook install must still\n // land on that path — callers rely on `setupCommands` entries being\n // idempotent so re-running them against an already-provisioned box is safe.\n for (const command of config.setupCommands ?? []) {\n await sandbox.commands.run(command, { envs: env })\n }\n\n if (alreadyUp) return\n\n // Start the daemon in the background and KEEP THE HANDLE. Its stdout/stderr\n // accumulate on the handle, and `probeHealthOrExit` races the health poll\n // against the process's own exit: if the daemon dies during boot (e.g. it\n // crashes loading a baked adapter on a broken image) we fail fast with its\n // captured output instead of blocking the caller for the whole readiness\n // window. See PR fix (2026-09-05): a boot that can't become healthy must\n // fail fast with the box's daemon output, never hang the HTTP/MCP call.\n const serve = await sandbox.commands.run(\n `agentproto serve --port ${port} --bind 0.0.0.0 --workspace ${workspace} --allow-origin https://${host}`,\n // timeoutMs: 0 disables e2b's per-command timeout — which DEFAULTS TO 60\n // SECONDS and applies to `background: true` commands too, silently\n // SIGKILLing the daemon one minute after boot. That was the root cause of\n // every long agent turn dying with \"sandbox is running but port is not\n // open\" (502) / `MCP error -32001` while short (<60s) probe turns passed\n // — verified live: the daemon's port went dead at exactly serve+60s with\n // the VM healthy and memory flat. The daemon must live as long as the\n // SANDBOX (lifetime cap above), not as long as a shell command.\n { background: true, envs: env, timeoutMs: 0 },\n )\n const outcome = await probeHealthOrExit(healthUrl, serve, daemonReadyTimeoutMs, pollIntervalMs)\n if (!outcome.healthy) {\n // Deliberately do NOT kill the box here — `boot`/`connect` own that\n // cleanup so EVERY failure path (a throwing npm install, a failing setup\n // hook, or this one) reclaims the VM through a single place and can never\n // leak a running, session-less box (observed live 2026-09-05: the daemon\n // gave up but the box kept running with no session record).\n const detail = boxDaemonOutput(serve)\n throw new Error(\n `@agentproto/sandbox-e2b: agentproto daemon did not become healthy at ${healthUrl} ` +\n `within ${daemonReadyTimeoutMs}ms (sandbox ${sandbox.sandboxId})` +\n `${outcome.exited ? \" — the box daemon process exited during boot\" : \"\"}.` +\n `${detail ? `\\n--- box daemon output ---\\n${detail}` : \"\"}`,\n )\n }\n}\n\n/** Wrap a healthy sandbox handle into the `BootedSandbox` shape common to\n * `boot` and `connect`. `pause()` always keeps the full memory snapshot\n * (`keepMemory: true`, the SDK default) — a filesystem-only pause would\n * cold-boot the box on resume, dropping the running agentproto daemon and\n * any open connections (PR3 risk \"e2b pause loses in-memory state\").\n *\n * `extraPorts` (from `spec.extraPorts`) are resolved eagerly at boot time\n * into the returned `ports` map (port → `https://<getHost(port)>`).\n * `expose(port)` works for any port — not just pre-declared ones — so\n * callers can also expose ports lazily after boot. */\nfunction toBootedSandbox(\n sandbox: Awaited<ReturnType<typeof Sandbox.create>>,\n host: string,\n token?: string,\n extraPorts?: number[],\n): BootedSandbox {\n const ports: Record<number, string> = {}\n for (const port of extraPorts ?? []) {\n ports[port] = `https://${sandbox.getHost(port)}`\n }\n return {\n mcpUrl: `https://${host}/mcp`,\n sandboxId: sandbox.sandboxId,\n ...(token ? { token } : {}),\n ...(Object.keys(ports).length > 0 ? { ports } : {}),\n expose(port: number): Promise<{ url: string }> {\n return Promise.resolve({ url: `https://${sandbox.getHost(port)}` })\n },\n async stop(): Promise<void> {\n await sandbox.kill()\n },\n async pause(): Promise<void> {\n await sandbox.pause({ keepMemory: true })\n },\n }\n}\n\n/** `e2bSandboxProvider.boot`/`.connect` — see module docs. */\nexport const e2bSandboxProvider: SandboxProvider = {\n async boot(spec: SandboxSpec, opts: SandboxBootOpts): Promise<BootedSandbox> {\n const config = readE2bConfig(spec)\n const template = config.template ?? DEFAULT_TEMPLATE\n const port = config.port ?? DEFAULT_PORT\n const workspace = config.workspace ?? DEFAULT_WORKSPACE\n\n const sandbox = await Sandbox.create(template, {\n apiKey: process.env.E2B_API_KEY,\n envs: opts.env,\n timeoutMs: config.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS,\n })\n\n // Once the VM exists, EVERY failure after this point must reclaim it —\n // a slow/failed `npm i -g`, a throwing setup hook, or a daemon that never\n // becomes healthy. Otherwise the caller's request (which may have already\n // timed out and walked away) leaves a running, session-less box behind\n // (observed live 2026-09-05, six leaked boxes). This single catch is the\n // only owner of that cleanup — `ensureDaemonHealthy` never kills.\n try {\n const host = sandbox.getHost(port)\n await ensureDaemonHealthy(sandbox, host, port, workspace, config, opts.env, resolveUpdateCli(config, template))\n return toBootedSandbox(sandbox, host, undefined, spec.extraPorts)\n } catch (err) {\n await sandbox.kill().catch(() => undefined)\n throw err\n }\n },\n\n async connect(sandboxId: string, spec: SandboxSpec, opts: SandboxBootOpts): Promise<BootedSandbox> {\n const config = readE2bConfig(spec)\n const port = config.port ?? DEFAULT_PORT\n const workspace = config.workspace ?? DEFAULT_WORKSPACE\n\n const sandbox = await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY })\n // A resumed box keeps its ORIGINAL lifetime deadline — re-arm it so the\n // reconnected session gets the same runway a fresh boot gets (same\n // 5-minute-default trap as `boot`, see DEFAULT_SANDBOX_TIMEOUT_MS).\n await sandbox.setTimeout(config.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS)\n\n // Same never-leak contract as `boot`: a reconnect whose daemon won't come\n // back healthy must reclaim the box rather than leave it running orphaned.\n try {\n const host = sandbox.getHost(port)\n // A resumed box keeps its original template — resolve against the\n // configured template the same way `boot` does.\n await ensureDaemonHealthy(sandbox, host, port, workspace, config, opts.env, resolveUpdateCli(config, config.template ?? DEFAULT_TEMPLATE))\n return finishConnect(sandbox, host, opts, spec)\n } catch (err) {\n await sandbox.kill().catch(() => undefined)\n throw err\n }\n },\n}\n\n/** Tail of `connect` — surface e2b's private-traffic token (when asked) and\n * wrap the healthy handle. Split out so the `connect` body's try/catch stays\n * a thin never-leak guard around the boot work. */\nfunction finishConnect(\n sandbox: Sandbox,\n host: string,\n opts: SandboxBootOpts,\n spec: SandboxSpec,\n): BootedSandbox {\n // `opts.expose === \"private\"` (only ever set by `attachSandbox`, see\n // @agentproto/runtime) surfaces e2b's own \"restricted public traffic\"\n // token, when the sandbox has one — it gates access to EVERY exposed\n // port, this one included, at e2b's edge (distinct from `SandboxOpts.\n // secure`, which only covers envd's own control-plane traffic — see\n // module docs). Best-effort: e2b's SDK only exposes the token as a\n // readonly property with no documented guarantee it's populated on a\n // reconnect for a sandbox that wasn't created with traffic restriction\n // enabled. Unverified against a live sandbox.\n const token = opts.expose === \"private\" ? sandbox.trafficAccessToken : undefined\n return toBootedSandbox(sandbox, host, token, spec.extraPorts)\n}\n\n/** Poll `url` until it responds OK, or return false once `timeoutMs` elapses. */\nasync function probeHealth(url: string, timeoutMs: number, pollIntervalMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs\n for (;;) {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(pollIntervalMs) })\n if (res.ok) return true\n } catch {\n // not up yet — fall through to the deadline check\n }\n if (Date.now() >= deadline) return false\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs))\n }\n}\n\n/** A background `commands.run` handle, narrowed to just what boot-readiness\n * needs. Kept structural (not `import type { CommandHandle }`) so the health\n * race stays trivially fakeable in unit tests and tolerant of a provider/SDK\n * that returns a plainer object. */\ninterface BackgroundCommand {\n /** Resolves when the process finishes; REJECTS (`CommandExitError`) on a\n * non-zero exit. Either way the process is gone. Absent ⇒ we can't observe\n * an early exit and fall back to polling the deadline. */\n wait?: () => Promise<unknown>\n /** `undefined` while the process is still running. */\n exitCode?: number\n stdout?: string\n stderr?: string\n}\n\n/**\n * Poll `url` until the daemon is healthy, the deadline elapses, OR the daemon\n * process (`handle`) exits — whichever comes first. Returns `{ healthy,\n * exited }`. `exited: true` means the serve process died during boot, so the\n * caller can fail FAST (with the daemon's captured output) instead of waiting\n * out the full readiness window — the difference between a crashing image\n * surfacing its stderr in ~1s and hanging the HTTP/MCP call for 30s+.\n */\nasync function probeHealthOrExit(\n url: string,\n handle: BackgroundCommand,\n timeoutMs: number,\n pollIntervalMs: number,\n): Promise<{ healthy: boolean; exited: boolean }> {\n const deadline = Date.now() + timeoutMs\n let exited = false\n // `wait()` rejects with CommandExitError on a non-zero exit and resolves on\n // a zero exit — either way the daemon is no longer running, so treat both\n // identically. Only observe the exit when the handle actually supports it;\n // a plain `{}` (some tests/providers) must NOT be read as an instant exit.\n if (typeof handle.wait === \"function\") {\n void handle.wait().then(\n () => {\n exited = true\n },\n () => {\n exited = true\n },\n )\n }\n for (;;) {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(pollIntervalMs) })\n if (res.ok) return { healthy: true, exited: false }\n } catch {\n // not up yet — fall through to the exit/deadline checks\n }\n if (exited || handle.exitCode !== undefined) return { healthy: false, exited: true }\n if (Date.now() >= deadline) return { healthy: false, exited: false }\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs))\n }\n}\n\n/** Best-effort tail of a background daemon handle's captured output, for\n * embedding in a boot-failure error so a broken image is diagnosable from the\n * caller's error alone. Empty when nothing was captured — a still-running but\n * unhealthy daemon buffers its stdout until exit, so this is populated mainly\n * when the process actually died. Bounded so a chatty crash can't bloat the\n * error. */\nfunction boxDaemonOutput(handle: BackgroundCommand): string {\n const joined = [handle.stderr ?? \"\", handle.stdout ?? \"\"]\n .map(s => s.trim())\n .filter(Boolean)\n .join(\"\\n\")\n const MAX = 2_000\n return joined.length > MAX ? `…${joined.slice(-MAX)}` : joined\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/template-versions.generated.ts","../src/provider.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * GENERATED by scripts/sync-templates.mjs from templates/workstation/versions.json.\n * DO NOT EDIT — edit versions.json and re-run the sync script instead.\n */\n\n/** Published stable e2b template id baked with the pins below. */\nexport const DEFAULT_TEMPLATE = \"53ybr99wdfgoebi9nee8\"\nexport const TEMPLATE_ALIASES = {\n stable: \"agentproto-workstation\",\n dev: \"agentproto-workstation-dev\",\n} as const\n/**\n * What each published image was PROVEN to contain at its last real e2b\n * build. A null field means unknown (e.g. out-of-band bake) — consumers\n * must assume the image does NOT match the declared pins.\n */\nexport const TEMPLATES = {\n stable: {\n id: \"53ybr99wdfgoebi9nee8\",\n alias: \"agentproto-workstation\",\n baked: {\n \"cli\": \"0.17.0\",\n \"adapters\": {\n \"@agentproto/adapter-hermes\": \"0.4.10\",\n \"@agentproto/adapter-mastra-agent\": \"0.6.0\",\n \"@agentproto/adapter-opencode\": \"1.1.10\"\n },\n \"builtAt\": \"2026-09-05T20:39:40Z\"\n },\n },\n dev: {\n id: \"tnqtmeims5q9ex7j9k06\",\n alias: \"agentproto-workstation-dev\",\n baked: {\n \"cli\": \"0.17.0\",\n \"adapters\": {\n \"@agentproto/adapter-hermes\": \"0.4.10\",\n \"@agentproto/adapter-mastra-agent\": \"0.6.0\",\n \"@agentproto/adapter-opencode\": \"1.1.10\"\n },\n \"builtAt\": \"2026-09-05T16:08:13Z\"\n },\n },\n} as const\nexport const BAKED_CLI_VERSION = \"0.17.0\"\nexport const BAKED_ADAPTERS = {\n \"@agentproto/adapter-hermes\": \"0.4.10\",\n \"@agentproto/adapter-mastra-agent\": \"0.6.0\",\n \"@agentproto/adapter-opencode\": \"1.1.10\",\n} as const\nexport const BAKED_RUNTIME = {\n \"opencode-ai\": \"1.18.28\",\n} as const\nexport const BAKED_BASE_IMAGE = \"e2bdev/code-interpreter:latest\"\n","/**\n * e2b `SandboxProvider` — boots the pre-built `agentproto-workstation`\n * template, ensures the agentproto daemon is listening on it, and exposes\n * the daemon's `/mcp` endpoint via `sandbox.getHost()` so\n * `createSandboxAgentSessionHost` (`@agentproto/sandbox`) can hand it\n * straight to `connectDaemonAgentSessionHost`.\n *\n * `getHost(port)` returns a publicly reachable hostname for that port\n * directly — e2b's per-sandbox auth token (`SandboxOpts.secure`) gates the\n * SDK's own control-plane (envd) traffic, not arbitrary listening ports the\n * sandbox process opens, so no extra auth header is threaded through here.\n * The daemon's OWN origin allowlist is a separate gate, though: it defaults\n * to `localhost:*`, which rejects a connection from this host process\n * against the sandbox's public `https://<getHost>` origin. `--allow-origin`\n * is passed the sandbox's own getHost origin to open that gate.\n *\n * The workstation template MAY already autostart the daemon; since that\n * can't be verified without a live template, this provider checks health\n * first and only issues the start command when the daemon isn't already\n * responding — correct either way. When it does start the daemon on a\n * non-proven template, it first updates the CLI (custom templates can lag\n * behind). The boot install is skipped by default ONLY when the template's\n * recorded `baked` block (templates/workstation/versions.json, via the\n * generated module) proves the image already carries the requested pin —\n * an unproven bake (`baked: null` fields, e.g. the current out-of-band\n * stable image) keeps the legacy install. An explicit `updateCliOnBoot`\n * overrides either way.\n */\n\nimport { Sandbox, SandboxNotFoundError } from \"e2b\"\nimport type { BootedSandbox, SandboxBootOpts, SandboxProvider, SandboxProbeResult, SandboxSpec } from \"@agentproto/sandbox\"\nimport { SandboxBoxGoneError } from \"@agentproto/sandbox\"\nimport { DEFAULT_TEMPLATE, TEMPLATES } from \"./template-versions.generated.js\"\n\n/** Re-exported from the generated module (single source:\n * `templates/workstation/versions.json` via scripts/sync-templates.mjs). */\nexport { DEFAULT_TEMPLATE, TEMPLATES }\n\n/** `serve.ts`'s default port (`DEFAULT_MCP_URL` in `@agentproto/harness`). */\nconst DEFAULT_PORT = 18790\n/**\n * Default sandbox LIFETIME. e2b's own default is 5 minutes (`SandboxOpts.\n * timeoutMs @default 300_000`) — lethal for real agent work: boot (~30s) +\n * `npm i -g` (~90s) + a multi-minute agent turn crosses 5:00 mid-turn, e2b\n * reaps the VM, and the next request cold-boots it WITHOUT the daemon —\n * surfacing as the baffling `\"The sandbox is running but port is not open\"`\n * 502 (observed live killing a PR-review turn). 45 minutes covers any\n * plausible session; `config.timeoutMs` still overrides in either direction.\n */\nconst DEFAULT_SANDBOX_TIMEOUT_MS = 45 * 60_000\nconst DEFAULT_WORKSPACE = \"/home/user\"\nconst HEALTH_PROBE_TIMEOUT_MS = 3_000\nconst DAEMON_READY_TIMEOUT_MS = 30_000\nconst POLL_INTERVAL_MS = 500\nconst UPDATE_CLI_TIMEOUT_MS = 120_000\n\ninterface E2bSandboxConfig {\n /** Template name or id. Defaults to the `agentproto-workstation` template. */\n template?: string\n /** Port the agentproto daemon listens on inside the sandbox. Default 18790. */\n port?: number\n /** `--workspace` passed to `agentproto serve`. Default `/home/user`. */\n workspace?: string\n /** Sandbox lifetime cap, forwarded to `Sandbox.create`. */\n timeoutMs?: number\n /** How long to wait for the health probe before assuming the daemon isn't autostarted. */\n healthProbeTimeoutMs?: number\n /** How long to wait for the daemon to become healthy after starting it. */\n daemonReadyTimeoutMs?: number\n /** Delay between health-probe attempts. */\n pollIntervalMs?: number\n /** Run `npm i -g @agentproto/cli@...` before starting the daemon, so a\n * stale template bake doesn't pin callers to an old agentproto version.\n * Default: false when the configured template is the baked stable\n * workstation one AND the requested cliVersion matches its baked pin\n * (the bake already carries exactly that CLI) — true otherwise (custom\n * templates can lag behind). Only runs when this provider is the one\n * starting the daemon (skipped when the health probe finds it already\n * autostarted). */\n updateCliOnBoot?: boolean\n /** CLI version to install on boot, pinned instead of a floating `@latest`.\n * A broken `@agentproto/cli@latest` publish otherwise silently kills the\n * box on boot; pinning a known-good version (sourced from config, defaulting\n * to the repo's declared `cliVersion`) makes the boot install reproducible.\n * Produces `@agentproto/cli@<version>`; when unset, falls back to `@latest`. */\n cliVersion?: string\n /** Extra npm package specs installed globally ALONGSIDE the CLI update\n * (single `npm i -g` invocation). Load-bearing for adapters: the CLI\n * update replaces the global install, LOSING any template-baked\n * `@agentproto/adapter-*` packages (verified against a live template) —\n * so the adapter the caller intends to spawn must be (re)installed in the\n * same breath, e.g. `[\"@agentproto/adapter-claude-sdk@latest\"]` (plus\n * `\"@anthropic-ai/claude-code@latest\"` for the claude-code adapter's\n * underlying CLI). Ignored when `updateCliOnBoot` is false or the daemon\n * was already autostarted healthy. Declaring a non-empty list also\n * re-enables the boot install when `updateCliOnBoot` is unset — entries\n * are never silently dropped on the baked-template skip path. */\n installPackages?: string[]\n /** Timeout for the `npm i -g` update command. Default 120s. */\n updateCliTimeoutMs?: number\n /** Extra shell commands host-executed inside the box AFTER the boot `npm i -g`\n * and BEFORE `agentproto serve` hands control to the agent. Generic\n * provision-time hook — no policy baked in here; callers decide what to run\n * (e.g. installing a git hook). Each entry is passed verbatim to\n * `sandbox.commands.run`. Runs on EVERY boot/connect, even when the health\n * probe finds the daemon already autostarted — entries must be idempotent. */\n setupCommands?: string[]\n}\n\nfunction readE2bConfig(spec: SandboxSpec): E2bSandboxConfig {\n const config = (spec.config ?? {}) as Record<string, unknown>\n return {\n template: typeof config.template === \"string\" ? config.template : undefined,\n port: typeof config.port === \"number\" ? config.port : undefined,\n workspace: typeof config.workspace === \"string\" ? config.workspace : undefined,\n timeoutMs: typeof config.timeoutMs === \"number\" ? config.timeoutMs : undefined,\n healthProbeTimeoutMs:\n typeof config.healthProbeTimeoutMs === \"number\" ? config.healthProbeTimeoutMs : undefined,\n daemonReadyTimeoutMs:\n typeof config.daemonReadyTimeoutMs === \"number\" ? config.daemonReadyTimeoutMs : undefined,\n pollIntervalMs: typeof config.pollIntervalMs === \"number\" ? config.pollIntervalMs : undefined,\n updateCliOnBoot: typeof config.updateCliOnBoot === \"boolean\" ? config.updateCliOnBoot : undefined,\n cliVersion: typeof config.cliVersion === \"string\" ? config.cliVersion : undefined,\n installPackages: Array.isArray(config.installPackages)\n ? config.installPackages.filter((p): p is string => typeof p === \"string\" && p.length > 0)\n : undefined,\n updateCliTimeoutMs:\n typeof config.updateCliTimeoutMs === \"number\" ? config.updateCliTimeoutMs : undefined,\n setupCommands: Array.isArray(config.setupCommands)\n ? config.setupCommands.filter((c): c is string => typeof c === \"string\" && c.length > 0)\n : undefined,\n }\n}\n\n/**\n * Default the on-boot CLI update OFF only for a template whose recorded\n * `baked` block PROVES the image already carries the requested CLI pin —\n * the install would just re-download the same version (90-120s per cold\n * boot, npm registry dependency, and it replaces the template's baked\n * adapters). The gate is on the RECORDED bake, not the template id: an\n * out-of-band bake nobody has verified has `baked.cli: null` and keeps the\n * legacy default of true. A requested cliVersion that differs from the\n * recorded bake also installs. An explicit `updateCliOnBoot` always wins.\n */\nexport function resolveUpdateCli(\n config: Pick<E2bSandboxConfig, \"updateCliOnBoot\" | \"cliVersion\" | \"installPackages\">,\n template: string,\n templates: { [key: string]: { id: string | null; baked: { cli: string | null } } } = TEMPLATES,\n): boolean {\n if (config.updateCliOnBoot !== undefined) return config.updateCliOnBoot\n // Runtime escape hatch: a caller declaring installPackages wants an\n // install to happen — never silently drop them on the skip path.\n if ((config.installPackages?.length ?? 0) > 0) return true\n const baked = Object.values(templates).find(entry => entry.id !== null && entry.id === template)?.baked\n if (!baked || baked.cli === null) return true\n // Recorded bake matches the requested pin (an unset cliVersion means the\n // bake itself is the pin) → nothing to install.\n return config.cliVersion !== undefined && config.cliVersion !== baked.cli\n}\n\n/**\n * Probe health, and — if the daemon isn't already up — update the (possibly\n * stale) baked CLI and start it, then re-probe. Shared by `boot` (a fresh\n * template's autostart may not have fired yet) and `connect` (a resumed box\n * may wake with a dead daemon process — see PR3 risk \"stale daemon on\n * reconnect\"). Throws (after killing the sandbox) if the daemon never comes\n * up.\n */\nasync function ensureDaemonHealthy(\n sandbox: Sandbox,\n host: string,\n port: number,\n workspace: string,\n config: E2bSandboxConfig,\n env: Record<string, string>,\n updateCli: boolean,\n): Promise<void> {\n const healthUrl = `https://${host}/health`\n const healthProbeTimeoutMs = config.healthProbeTimeoutMs ?? HEALTH_PROBE_TIMEOUT_MS\n const daemonReadyTimeoutMs = config.daemonReadyTimeoutMs ?? DAEMON_READY_TIMEOUT_MS\n const pollIntervalMs = config.pollIntervalMs ?? POLL_INTERVAL_MS\n\n const alreadyUp = await probeHealth(healthUrl, healthProbeTimeoutMs, pollIntervalMs)\n\n if (!alreadyUp && updateCli) {\n // One `npm i -g` for the CLI update AND any caller-declared extras\n // (`installPackages` — typically the adapter about to be spawned). The\n // CLI update alone replaces the global install and LOSES template-baked\n // adapters, so the two must land in the same invocation. Extras are\n // single-quoted (with any quote chars stripped) since they arrive from\n // caller-authored spec config.\n const extras = (config.installPackages ?? [])\n .map(spec => ` '${spec.replace(/'/g, \"\")}'`)\n .join(\"\")\n const cli = `@agentproto/cli@${config.cliVersion ?? \"latest\"}`\n await sandbox.commands.run(`sudo npm i -g ${cli}${extras}`, {\n envs: env,\n timeoutMs: config.updateCliTimeoutMs ?? UPDATE_CLI_TIMEOUT_MS,\n })\n }\n // Caller-declared provision hooks — host-executed after the npm install and\n // before the agent gets control. Kept generic (no policy here); the ts layer\n // uses this to install a commit-msg hook that strips AI-attribution trailers.\n // Runs regardless of `alreadyUp`: the workstation template MAY autostart the\n // daemon (module doc above), and a caller-declared hook install must still\n // land on that path — callers rely on `setupCommands` entries being\n // idempotent so re-running them against an already-provisioned box is safe.\n for (const command of config.setupCommands ?? []) {\n await sandbox.commands.run(command, { envs: env })\n }\n\n if (alreadyUp) return\n\n // Start the daemon in the background and KEEP THE HANDLE. Its stdout/stderr\n // accumulate on the handle, and `probeHealthOrExit` races the health poll\n // against the process's own exit: if the daemon dies during boot (e.g. it\n // crashes loading a baked adapter on a broken image) we fail fast with its\n // captured output instead of blocking the caller for the whole readiness\n // window. See PR fix (2026-09-05): a boot that can't become healthy must\n // fail fast with the box's daemon output, never hang the HTTP/MCP call.\n const serve = await sandbox.commands.run(\n `agentproto serve --port ${port} --bind 0.0.0.0 --workspace ${workspace} --allow-origin https://${host}`,\n // timeoutMs: 0 disables e2b's per-command timeout — which DEFAULTS TO 60\n // SECONDS and applies to `background: true` commands too, silently\n // SIGKILLing the daemon one minute after boot. That was the root cause of\n // every long agent turn dying with \"sandbox is running but port is not\n // open\" (502) / `MCP error -32001` while short (<60s) probe turns passed\n // — verified live: the daemon's port went dead at exactly serve+60s with\n // the VM healthy and memory flat. The daemon must live as long as the\n // SANDBOX (lifetime cap above), not as long as a shell command.\n { background: true, envs: env, timeoutMs: 0 },\n )\n const outcome = await probeHealthOrExit(healthUrl, serve, daemonReadyTimeoutMs, pollIntervalMs)\n if (!outcome.healthy) {\n // Deliberately do NOT kill the box here — `boot`/`connect` own that\n // cleanup so EVERY failure path (a throwing npm install, a failing setup\n // hook, or this one) reclaims the VM through a single place and can never\n // leak a running, session-less box (observed live 2026-09-05: the daemon\n // gave up but the box kept running with no session record).\n const detail = boxDaemonOutput(serve)\n throw new Error(\n `@agentproto/sandbox-e2b: agentproto daemon did not become healthy at ${healthUrl} ` +\n `within ${daemonReadyTimeoutMs}ms (sandbox ${sandbox.sandboxId})` +\n `${outcome.exited ? \" — the box daemon process exited during boot\" : \"\"}.` +\n `${detail ? `\\n--- box daemon output ---\\n${detail}` : \"\"}`,\n )\n }\n}\n\n/** Wrap a healthy sandbox handle into the `BootedSandbox` shape common to\n * `boot` and `connect`. `pause()` always keeps the full memory snapshot\n * (`keepMemory: true`, the SDK default) — a filesystem-only pause would\n * cold-boot the box on resume, dropping the running agentproto daemon and\n * any open connections (PR3 risk \"e2b pause loses in-memory state\").\n *\n * `extraPorts` (from `spec.extraPorts`) are resolved eagerly at boot time\n * into the returned `ports` map (port → `https://<getHost(port)>`).\n * `expose(port)` works for any port — not just pre-declared ones — so\n * callers can also expose ports lazily after boot. */\nfunction toBootedSandbox(\n sandbox: Awaited<ReturnType<typeof Sandbox.create>>,\n host: string,\n token?: string,\n extraPorts?: number[],\n): BootedSandbox {\n const ports: Record<number, string> = {}\n for (const port of extraPorts ?? []) {\n ports[port] = `https://${sandbox.getHost(port)}`\n }\n return {\n mcpUrl: `https://${host}/mcp`,\n sandboxId: sandbox.sandboxId,\n ...(token ? { token } : {}),\n ...(Object.keys(ports).length > 0 ? { ports } : {}),\n expose(port: number): Promise<{ url: string }> {\n return Promise.resolve({ url: `https://${sandbox.getHost(port)}` })\n },\n async stop(): Promise<void> {\n await sandbox.kill()\n },\n async pause(): Promise<void> {\n await sandbox.pause({ keepMemory: true })\n },\n }\n}\n\n/** `e2bSandboxProvider.boot`/`.connect` — see module docs. */\nexport const e2bSandboxProvider: SandboxProvider = {\n async boot(spec: SandboxSpec, opts: SandboxBootOpts): Promise<BootedSandbox> {\n const config = readE2bConfig(spec)\n const template = config.template ?? DEFAULT_TEMPLATE\n const port = config.port ?? DEFAULT_PORT\n const workspace = config.workspace ?? DEFAULT_WORKSPACE\n\n const sandbox = await Sandbox.create(template, {\n apiKey: process.env.E2B_API_KEY,\n envs: opts.env,\n timeoutMs: config.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS,\n })\n\n // Once the VM exists, EVERY failure after this point must reclaim it —\n // a slow/failed `npm i -g`, a throwing setup hook, or a daemon that never\n // becomes healthy. Otherwise the caller's request (which may have already\n // timed out and walked away) leaves a running, session-less box behind\n // (observed live 2026-09-05, six leaked boxes). This single catch is the\n // only owner of that cleanup — `ensureDaemonHealthy` never kills.\n try {\n const host = sandbox.getHost(port)\n await ensureDaemonHealthy(sandbox, host, port, workspace, config, opts.env, resolveUpdateCli(config, template))\n return toBootedSandbox(sandbox, host, undefined, spec.extraPorts)\n } catch (err) {\n await sandbox.kill().catch(() => undefined)\n throw err\n }\n },\n\n async connect(sandboxId: string, spec: SandboxSpec, opts: SandboxBootOpts): Promise<BootedSandbox> {\n const config = readE2bConfig(spec)\n const port = config.port ?? DEFAULT_PORT\n const workspace = config.workspace ?? DEFAULT_WORKSPACE\n\n let sandbox\n try {\n sandbox = await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY })\n } catch (err) {\n // A vanished box must surface as the portability sentinel, not a raw\n // SDK error — the reconnect path (session-spawn) and the ledger rely\n // on `isSandboxBoxGoneError` to flip the ledger row to \"gone\" instead\n // of leaving a phantom paused/connected entry advertising an app URL.\n if (err instanceof SandboxNotFoundError) throw new SandboxBoxGoneError(sandboxId, err)\n throw err\n }\n // A resumed box keeps its ORIGINAL lifetime deadline — re-arm it so the\n // reconnected session gets the same runway a fresh boot gets (same\n // 5-minute-default trap as `boot`, see DEFAULT_SANDBOX_TIMEOUT_MS).\n await sandbox.setTimeout(config.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS)\n\n // Same never-leak contract as `boot`: a reconnect whose daemon won't come\n // back healthy must reclaim the box rather than leave it running orphaned.\n try {\n const host = sandbox.getHost(port)\n // A resumed box keeps its original template — resolve against the\n // configured template the same way `boot` does.\n await ensureDaemonHealthy(sandbox, host, port, workspace, config, opts.env, resolveUpdateCli(config, config.template ?? DEFAULT_TEMPLATE))\n return finishConnect(sandbox, host, opts, spec)\n } catch (err) {\n await sandbox.kill().catch(() => undefined)\n throw err\n }\n },\n\n /**\n * Poll the provider control plane (`GET /sandboxes/:id` via the SDK's\n * `Sandbox.getInfo`) — cheap (one authed HTTP call, no box contact),\n * exactly the liveness signal a vanished box needs while the session\n * descriptor and ledger still say paused/connected. `{alive:false}` on\n * e2b's 404 (\"Sandbox Not Found\"); rethrows anything else so callers\n * read \"check failed\", never \"box died\". NOTE (operator guidance): e2b\n * also offers sandbox lifecycle webhooks, which would push death instead\n * of forcing a poll — the SDK in-tree exposes no webhook/event API, so\n * polling stays the implemented probe; switch to (or augment with) the\n * webhook if/when the SDK surfaces one.\n */\n async probe(sandboxId: string): Promise<SandboxProbeResult> {\n try {\n const info = await Sandbox.getInfo(sandboxId, { apiKey: process.env.E2B_API_KEY })\n return { alive: true, ...(info.state !== undefined ? { state: String(info.state) } : {}) }\n } catch (err) {\n if (err instanceof SandboxNotFoundError) return { alive: false }\n throw err\n }\n },\n}\n\n/** Tail of `connect` — surface e2b's private-traffic token (when asked) and\n * wrap the healthy handle. Split out so the `connect` body's try/catch stays\n * a thin never-leak guard around the boot work. */\nfunction finishConnect(\n sandbox: Sandbox,\n host: string,\n opts: SandboxBootOpts,\n spec: SandboxSpec,\n): BootedSandbox {\n // `opts.expose === \"private\"` (only ever set by `attachSandbox`, see\n // @agentproto/runtime) surfaces e2b's own \"restricted public traffic\"\n // token, when the sandbox has one — it gates access to EVERY exposed\n // port, this one included, at e2b's edge (distinct from `SandboxOpts.\n // secure`, which only covers envd's own control-plane traffic — see\n // module docs). Best-effort: e2b's SDK only exposes the token as a\n // readonly property with no documented guarantee it's populated on a\n // reconnect for a sandbox that wasn't created with traffic restriction\n // enabled. Unverified against a live sandbox.\n const token = opts.expose === \"private\" ? sandbox.trafficAccessToken : undefined\n return toBootedSandbox(sandbox, host, token, spec.extraPorts)\n}\n\n/** Poll `url` until it responds OK, or return false once `timeoutMs` elapses. */\nasync function probeHealth(url: string, timeoutMs: number, pollIntervalMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs\n for (;;) {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(pollIntervalMs) })\n if (res.ok) return true\n } catch {\n // not up yet — fall through to the deadline check\n }\n if (Date.now() >= deadline) return false\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs))\n }\n}\n\n/** A background `commands.run` handle, narrowed to just what boot-readiness\n * needs. Kept structural (not `import type { CommandHandle }`) so the health\n * race stays trivially fakeable in unit tests and tolerant of a provider/SDK\n * that returns a plainer object. */\ninterface BackgroundCommand {\n /** Resolves when the process finishes; REJECTS (`CommandExitError`) on a\n * non-zero exit. Either way the process is gone. Absent ⇒ we can't observe\n * an early exit and fall back to polling the deadline. */\n wait?: () => Promise<unknown>\n /** `undefined` while the process is still running. */\n exitCode?: number\n stdout?: string\n stderr?: string\n}\n\n/**\n * Poll `url` until the daemon is healthy, the deadline elapses, OR the daemon\n * process (`handle`) exits — whichever comes first. Returns `{ healthy,\n * exited }`. `exited: true` means the serve process died during boot, so the\n * caller can fail FAST (with the daemon's captured output) instead of waiting\n * out the full readiness window — the difference between a crashing image\n * surfacing its stderr in ~1s and hanging the HTTP/MCP call for 30s+.\n */\nasync function probeHealthOrExit(\n url: string,\n handle: BackgroundCommand,\n timeoutMs: number,\n pollIntervalMs: number,\n): Promise<{ healthy: boolean; exited: boolean }> {\n const deadline = Date.now() + timeoutMs\n let exited = false\n // `wait()` rejects with CommandExitError on a non-zero exit and resolves on\n // a zero exit — either way the daemon is no longer running, so treat both\n // identically. Only observe the exit when the handle actually supports it;\n // a plain `{}` (some tests/providers) must NOT be read as an instant exit.\n if (typeof handle.wait === \"function\") {\n void handle.wait().then(\n () => {\n exited = true\n },\n () => {\n exited = true\n },\n )\n }\n for (;;) {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(pollIntervalMs) })\n if (res.ok) return { healthy: true, exited: false }\n } catch {\n // not up yet — fall through to the exit/deadline checks\n }\n if (exited || handle.exitCode !== undefined) return { healthy: false, exited: true }\n if (Date.now() >= deadline) return { healthy: false, exited: false }\n await new Promise(resolve => setTimeout(resolve, pollIntervalMs))\n }\n}\n\n/** Best-effort tail of a background daemon handle's captured output, for\n * embedding in a boot-failure error so a broken image is diagnosable from the\n * caller's error alone. Empty when nothing was captured — a still-running but\n * unhealthy daemon buffers its stdout until exit, so this is populated mainly\n * when the process actually died. Bounded so a chatty crash can't bloat the\n * error. */\nfunction boxDaemonOutput(handle: BackgroundCommand): string {\n const joined = [handle.stderr ?? \"\", handle.stdout ?? \"\"]\n .map(s => s.trim())\n .filter(Boolean)\n .join(\"\\n\")\n const MAX = 2_000\n return joined.length > MAX ? `…${joined.slice(-MAX)}` : joined\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/sandbox-e2b",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "@agentproto/sandbox-e2b — e2b `SandboxProvider` for @agentproto/sandbox. Boots the pinned `agentproto-workstation` e2b template (baked @agentproto/cli 0.17.0), starts the agentproto daemon inside it, and exposes its MCP endpoint as a URL so any AgentSessionHost consumer (worktreeAgentWorkflow, worktree-agent CLI) can run a coding-agent step inside the sandbox.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"e2b": "^2.46.1",
|
|
45
|
-
"@agentproto/sandbox": "0.
|
|
45
|
+
"@agentproto/sandbox": "0.5.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^25.9.5",
|