@agent-compose/sdk 0.6.0 → 0.8.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.
Files changed (126) hide show
  1. package/README.md +66 -39
  2. package/dist/agent/__tests__/runtime-json-schema.test.d.ts +10 -0
  3. package/dist/agent/agent-context.d.ts +21 -1
  4. package/dist/agent/agent-loop.d.ts +24 -1
  5. package/dist/client.d.ts +338 -534
  6. package/dist/directives.d.ts +112 -0
  7. package/dist/display.d.ts +242 -0
  8. package/dist/errors.d.ts +24 -1
  9. package/dist/index.d.ts +34 -13
  10. package/dist/index.js +2984 -861
  11. package/dist/pause/wrappers.d.ts +31 -9
  12. package/dist/processors/ask-human.d.ts +30 -0
  13. package/dist/processors/ask-human.test.d.ts +1 -0
  14. package/dist/processors/index.d.ts +1 -0
  15. package/dist/runtimes/_acp-client.d.ts +46 -1
  16. package/dist/runtimes/_cli-agent.d.ts +58 -4
  17. package/dist/runtimes/_jsonl-guard.d.ts +103 -0
  18. package/dist/runtimes/amp.d.ts +2 -2
  19. package/dist/runtimes/claude-code.d.ts +59 -0
  20. package/dist/runtimes/claude-code.test.d.ts +14 -0
  21. package/dist/runtimes/claude.d.ts +16 -0
  22. package/dist/runtimes/claude.test.d.ts +8 -0
  23. package/dist/runtimes/codex.d.ts +9 -3
  24. package/dist/runtimes/cursor.d.ts +9 -0
  25. package/dist/runtimes/droid.d.ts +9 -0
  26. package/dist/runtimes/jsonl-guard.test.d.ts +19 -0
  27. package/dist/runtimes/openai-desktop.js +2922 -861
  28. package/dist/runtimes/opencode.d.ts +25 -0
  29. package/dist/runtimes/vercel.js +22 -1
  30. package/dist/sandbox/devbox.d.ts +42 -0
  31. package/dist/sandbox/exec-stream.d.ts +14 -0
  32. package/dist/sandbox/network-policy.d.ts +100 -0
  33. package/dist/sandbox/provider-def.d.ts +79 -0
  34. package/dist/sandbox/providers/desktop.d.ts +10 -0
  35. package/dist/sandbox/providers/e2b.d.ts +17 -0
  36. package/dist/sandbox/providers/local.d.ts +11 -0
  37. package/dist/sandbox/providers/vercel.d.ts +18 -0
  38. package/dist/sandbox/registry.d.ts +45 -0
  39. package/dist/sandbox/sizes.d.ts +68 -0
  40. package/dist/sandbox.d.ts +24 -299
  41. package/dist/step-invocation/__tests__/foreground-recovery.test.d.ts +1 -0
  42. package/dist/step-invocation/invoker.d.ts +24 -1
  43. package/dist/step-invocation/protocol.d.ts +13 -0
  44. package/dist/types/api-compliance.d.ts +71 -0
  45. package/dist/types/api-conversations.d.ts +492 -0
  46. package/dist/types/api-factory.d.ts +309 -0
  47. package/dist/types/api-projects.d.ts +131 -0
  48. package/dist/types/api-runs.d.ts +377 -0
  49. package/dist/types/api-scopes.d.ts +102 -0
  50. package/dist/types/conversation-stream.d.ts +191 -0
  51. package/dist/types/execution-context.d.ts +12 -2
  52. package/dist/types/protocol.d.ts +30 -1
  53. package/dist/types/sandbox-environment.d.ts +8 -5
  54. package/dist/types/sandbox.d.ts +79 -0
  55. package/dist/types/workflow-metadata.d.ts +33 -8
  56. package/dist/types/workflow-plan.d.ts +10 -0
  57. package/dist/types/workflow.d.ts +18 -193
  58. package/dist/utils/bundler.d.ts +12 -1
  59. package/dist/utils/errors.d.ts +9 -1
  60. package/dist/workflow-steps/index.d.ts +1 -1
  61. package/dist/workflow-steps/observability.d.ts +8 -1
  62. package/dist/workflow-steps/runner.d.ts +3 -3
  63. package/dist/workflow-steps/step.d.ts +15 -1
  64. package/dist/workflow-steps/types.d.ts +19 -5
  65. package/dist/workflow-steps/workflow.d.ts +22 -1
  66. package/dist/workflows/engine.d.ts +3 -2
  67. package/dist/workflows/invoke-child.d.ts +2 -2
  68. package/package.json +1 -1
  69. package/src/agent/agent-context.ts +206 -16
  70. package/src/agent/agent-loop.ts +40 -4
  71. package/src/agent/run-agent.ts +9 -1
  72. package/src/client.ts +909 -621
  73. package/src/directives.ts +184 -0
  74. package/src/display.ts +788 -0
  75. package/src/errors.ts +39 -0
  76. package/src/index.ts +117 -10
  77. package/src/pause/wrappers.ts +44 -9
  78. package/src/processors/ask-human.ts +136 -0
  79. package/src/processors/index.ts +5 -0
  80. package/src/runtimes/_acp-client.ts +72 -3
  81. package/src/runtimes/_cli-agent.ts +171 -38
  82. package/src/runtimes/_jsonl-guard.ts +219 -0
  83. package/src/runtimes/claude-code.ts +246 -0
  84. package/src/runtimes/claude.ts +32 -2
  85. package/src/runtimes/codex.ts +55 -3
  86. package/src/runtimes/cursor.ts +59 -0
  87. package/src/runtimes/droid.ts +63 -0
  88. package/src/runtimes/openai-desktop.ts +59 -14
  89. package/src/runtimes/opencode.ts +61 -0
  90. package/src/sandbox/devbox.ts +48 -0
  91. package/src/sandbox/exec-stream.ts +48 -0
  92. package/src/sandbox/network-policy.ts +181 -0
  93. package/src/sandbox/provider-def.ts +94 -0
  94. package/src/sandbox/providers/desktop.ts +57 -0
  95. package/src/sandbox/providers/e2b.ts +354 -0
  96. package/src/sandbox/providers/local.ts +106 -0
  97. package/src/sandbox/providers/vercel.ts +331 -0
  98. package/src/sandbox/registry.ts +198 -0
  99. package/src/sandbox/sizes.ts +95 -0
  100. package/src/sandbox.ts +59 -1263
  101. package/src/step-invocation/invoker.ts +319 -34
  102. package/src/step-invocation/protocol.ts +19 -0
  103. package/src/types/api-compliance.ts +79 -0
  104. package/src/types/api-conversations.ts +522 -0
  105. package/src/types/api-factory.ts +336 -0
  106. package/src/types/api-projects.ts +140 -0
  107. package/src/types/api-runs.ts +412 -0
  108. package/src/types/api-scopes.ts +102 -0
  109. package/src/types/conversation-stream.ts +231 -0
  110. package/src/types/execution-context.ts +10 -2
  111. package/src/types/protocol.ts +33 -0
  112. package/src/types/sandbox-environment.ts +28 -9
  113. package/src/types/sandbox.ts +78 -0
  114. package/src/types/workflow-metadata.ts +35 -8
  115. package/src/types/workflow-plan.ts +11 -0
  116. package/src/types/workflow.ts +25 -280
  117. package/src/utils/bundler.ts +32 -5
  118. package/src/utils/errors.ts +34 -2
  119. package/src/workflow-steps/index.ts +1 -0
  120. package/src/workflow-steps/observability.ts +19 -8
  121. package/src/workflow-steps/runner.ts +4 -4
  122. package/src/workflow-steps/step.ts +49 -1
  123. package/src/workflow-steps/types.ts +20 -5
  124. package/src/workflow-steps/workflow.ts +22 -1
  125. package/src/workflows/engine.ts +3 -2
  126. package/src/workflows/invoke-child.ts +2 -2
@@ -0,0 +1,25 @@
1
+ /**
2
+ * OpenCode CLI runtime — drives sst's `opencode` agentic CLI inside the sandbox.
3
+ * OpenCode speaks ACP natively (`opencode acp`, protocolVersion 1 — verified
4
+ * live on E2B), so the runner drives it over ACP; the JSONL members below are
5
+ * only the version-mismatch fallback (vestigial for a v1 agent).
6
+ *
7
+ * Auth + model via OpenRouter: set OPENROUTER_API_KEY (a factory/workflow
8
+ * secret) and use a model id like `openrouter/z-ai/glm-5.2`. The runtime
9
+ * installs the `opencode-ai` CLI on demand; pair with
10
+ * `snapshots: { bootFrom: "reuse" }` to install once and boot from the capture.
11
+ *
12
+ * Verified live on E2B (2026-06-30): `npm i -g opencode-ai` (v1.17.12);
13
+ * `opencode run --model openrouter/z-ai/glm-5.2` drove a GLM-5.2 turn through
14
+ * OpenRouter; `opencode acp` answered the ACP `initialize` handshake with
15
+ * protocolVersion 1.
16
+ */
17
+ import { type CliAgentSpec } from "./_cli-agent.js";
18
+ export declare const opencodeSpec: CliAgentSpec;
19
+ export interface OpencodeRuntimeConfig {
20
+ /** OpenRouter-prefixed model id (default `openrouter/z-ai/glm-5.2`). */
21
+ model?: string;
22
+ }
23
+ export declare function createOpencodeRuntime(config?: OpencodeRuntimeConfig): import("../index.js").AgentRuntime<import("../sandbox.js").SandboxProvider>;
24
+ declare const _default: import("../index.js").AgentRuntime<import("../sandbox.js").SandboxProvider>;
25
+ export default _default;
@@ -717,7 +717,28 @@ async function corePause(req, coord, kind = "custom") {
717
717
 
718
718
  // src/utils/errors.ts
719
719
  function formatError(err) {
720
- return err instanceof Error ? err.message : String(err);
720
+ if (!(err instanceof Error)) {
721
+ if (typeof err === "object" && err !== null) {
722
+ const msg = err.message;
723
+ if (typeof msg === "string" && msg.length > 0)
724
+ return msg;
725
+ try {
726
+ return JSON.stringify(err) ?? String(err);
727
+ } catch {
728
+ return String(err);
729
+ }
730
+ }
731
+ return String(err);
732
+ }
733
+ const parts = [err.message];
734
+ const seen = new Set([err]);
735
+ let cause = err.cause;
736
+ while (cause != null && !seen.has(cause)) {
737
+ seen.add(cause);
738
+ parts.push(cause instanceof Error ? cause.message : String(cause));
739
+ cause = cause instanceof Error ? cause.cause : undefined;
740
+ }
741
+ return parts.join(": ");
721
742
  }
722
743
 
723
744
  // src/runtimes/vercel.ts
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The devbox — ADR-0038's "Computer": one persistent E2B **desktop** machine per
3
+ * member, paused by default, resumed on attach (~100ms).
4
+ *
5
+ * This module is the SINGLE SOURCE for the machine's template identity: the
6
+ * recipe lives in `infra/e2b-template/devbox.ts`, `infra/e2b-template/build.ts`
7
+ * bakes it under the alias below, and the server imports the alias from here to
8
+ * provision a machine (exactly how `e2bBaseTemplate` reaches the E2B provider —
9
+ * a stable ALIAS, never a snapshot id, so the same string resolves in whichever
10
+ * E2B account a deployment uses).
11
+ *
12
+ * Interactive only. Workflow RUNS never execute on a devbox (ADR-0038 §2.6):
13
+ * runs boot the clean per-size `agent-compose-base-<size>` / `agent-env-<size>`
14
+ * templates so reproducibility never inherits hand-configured drift.
15
+ */
16
+ /** Stable E2B template ALIAS for the per-member desktop machine. Baked by
17
+ * `infra/e2b-template/build.ts` from `infra/e2b-template/devbox.ts`; CI rebakes
18
+ * it on any master push that touches the recipe (.github/workflows/sandbox-images.yml).
19
+ * Boot it with `@e2b/desktop`'s `Sandbox.create(E2B_DEVBOX_TEMPLATE, …)`. */
20
+ export declare const E2B_DEVBOX_TEMPLATE = "agent-compose-devbox";
21
+ /** Machine spec the devbox alias is stamped with. E2B sizing is TEMPLATE-baked
22
+ * (no create-time cpu/mem knob), so this is the machine every member gets.
23
+ *
24
+ * 8 vCPU / 8192 MB matches E2B's stock `desktop` template — the exact shape the
25
+ * ADR-0038 spike measured (pause 456ms / resume 99ms, RAM + desktop + dockerd
26
+ * preserved). 8192 MB is also the E2B account's hard memory ceiling
27
+ * (`Template.build` 400s above it), so this is the largest machine we can bake
28
+ * today; raising it needs a plan change first. */
29
+ export declare const E2B_DEVBOX_SPEC: {
30
+ readonly cpuCount: number;
31
+ readonly memoryMB: number;
32
+ };
33
+ /** Recipe version of the baked devbox image. The alias is stable across
34
+ * rebuilds (it re-points at the new build), so the alias alone can't tell you
35
+ * WHICH recipe a machine was provisioned from — the server stamps
36
+ * `dev_machines.template` with `e2bDevboxTemplateRef()` for that lineage.
37
+ * BUMP whenever `infra/e2b-template/devbox.ts` changes what it bakes. */
38
+ export declare const E2B_DEVBOX_RECIPE_VERSION = "1";
39
+ /** Versioned, human-readable ref for the image a machine was provisioned from
40
+ * (`agent-compose-devbox@1`) — what ADR-0038 §6's `dev_machines.template`
41
+ * stores. NOT a boot target: boot from `E2B_DEVBOX_TEMPLATE`. */
42
+ export declare function e2bDevboxTemplateRef(): string;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * SSE exec-stream parser — used by the agent sandbox broker clients in
3
+ * runner.ts to consume a streamed command execution.
4
+ */
5
+ import type { SandboxCommandResult } from "../types/sandbox.js";
6
+ export interface ParseSseExecStreamOptions {
7
+ onStdout?: (data: string) => void;
8
+ onStderr?: (data: string) => void;
9
+ }
10
+ /**
11
+ * Parse an SSE exec stream from a ReadableStream.
12
+ * Used by the agent sandbox broker in runner.ts.
13
+ */
14
+ export declare function parseSseExecStream(body: ReadableStream<Uint8Array>, opts?: ParseSseExecStreamOptions): Promise<SandboxCommandResult>;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Sandbox egress network policy — ONE shape for every provider, plus the
3
+ * translators into each provider's native enforcement config
4
+ * (`toVercelNetworkPolicy` / `toE2bNetwork`) and the dot-segment path guards.
5
+ */
6
+ import type { SandboxNetworkOpts as E2bNetworkOpts } from "e2b";
7
+ import type { ConnectorRequestRules } from "../types/workflow-metadata.js";
8
+ import type { NetworkPolicy as VercelNetworkPolicy } from "@vercel/sandbox";
9
+ /**
10
+ * Network policy for outbound HTTPS requests — ONE shape for every provider;
11
+ * only the enforcement point differs. When a sandbox makes a request matching
12
+ * a domain in `allow`, the egress layer injects the specified headers before
13
+ * forwarding — credentials never exist inside the VM. Both providers enforce
14
+ * this natively at the platform edge from the resolved policy passed at
15
+ * create: Vercel via its firewall (`requestRules` translate to native `match`
16
+ * rules), E2B via its native network firewall (`toE2bNetwork` →
17
+ * `allowOut`/`denyOut`/`rules`). See `toVercelNetworkPolicy` / `toE2bNetwork`.
18
+ */
19
+ export interface SandboxNetworkHeaderTransform {
20
+ headers?: Record<string, string>;
21
+ }
22
+ export interface SandboxNetworkAllowRule {
23
+ transform?: SandboxNetworkHeaderTransform[];
24
+ /** Tier-2 request gate (method/path) — the transform only applies when the
25
+ * request matches. Honoured on Vercel (translated to native `match` rules).
26
+ * NOT honoured on E2B: its native rules carry only a `transform`, no
27
+ * method/path matcher, so a brokered credential rides ALL requests to an
28
+ * allowed host there (see `toE2bNetwork`). Present on connector-auth rules;
29
+ * see `ConnectorRequestRules`. */
30
+ requestRules?: ConnectorRequestRules;
31
+ }
32
+ export interface SandboxNetworkSubnetPolicy {
33
+ allow?: string[];
34
+ deny?: string[];
35
+ }
36
+ export type SandboxNetworkPolicy = "allow-all" | "deny-all" | {
37
+ allow?: string[] | Record<string, SandboxNetworkAllowRule[]>;
38
+ subnets?: SandboxNetworkSubnetPolicy;
39
+ };
40
+ /** Matches a path containing a dot-dot segment — literal (`/../`) or
41
+ * percent-encoded (`%2e%2e`, `%2f` boundaries). `DOT_SEGMENT_PATH_RE2` is
42
+ * the RE2 form handed to Vercel's matcher: wrapped in `.*` so it behaves
43
+ * identically under search and full-match semantics (RE2 has no lookaround,
44
+ * so the guard is a positive match on the traversal pattern, used as a
45
+ * credential-stripping first rule). `DOT_SEGMENT_RE` is the local JS twin
46
+ * used to validate DECLARED prefixes. */
47
+ export declare const DOT_SEGMENT_PATH_RE2 = ".*(?:^|/|%2[fF])(?:\\.|%2[eE]){2}(?:/|%2[fF]|$).*";
48
+ /**
49
+ * Translate our policy shape into Vercel's native `NetworkPolicy`.
50
+ *
51
+ * The shapes are identical except for Tier-2: our `requestRules`
52
+ * (`{ methods, pathPrefixes }`) become Vercel `match` rules
53
+ * (`{ method, path: { startsWith } }`) — one rule per path prefix, since a
54
+ * Vercel matcher carries a single path.
55
+ *
56
+ * Dot-segment defense: prefix matching is raw — `/repos/acme/../../user`
57
+ * starts with `/repos/acme/` but the origin normalizes it to `/user`, riding
58
+ * the credential outside its declared scope. We don't get to assume the
59
+ * enforcer RFC-3986-normalizes before matching, so every domain with a path
60
+ * gate gets a transform-free FIRST rule matching dot-segment paths
61
+ * (first-match-wins ⇒ such requests go out credential-free), and declared
62
+ * prefixes themselves are validated (must start with "/", no dot-segments).
63
+ *
64
+ * Accepted divergence from iron-proxy (E2B): off-gate requests to an allowed
65
+ * domain pass WITHOUT the transform (Vercel terminates TLS only for
66
+ * transform-bearing domains and applies first-match-wins) — iron-proxy
67
+ * REFUSES them instead. Reachability differs; the property that matters is
68
+ * preserved on both substrates: the brokered credential rides only declared
69
+ * method/path combinations. Pinned by vercel-network-policy.test.ts.
70
+ */
71
+ export declare function toVercelNetworkPolicy(policy: SandboxNetworkPolicy): VercelNetworkPolicy;
72
+ /**
73
+ * Translate our policy shape into E2B's native `network` config
74
+ * (`SandboxNetworkOpts`) — the E2B analogue of `toVercelNetworkPolicy`.
75
+ *
76
+ * - `allowOut`: the set of allowed egress targets — every host named in
77
+ * `allow` PLUS every CIDR in `subnets.allow`. The Archil raw-TCP
78
+ * data-plane CIDRs ride here as FIRST-CLASS allow entries (E2B has no
79
+ * root-exemption to lean on, unlike the old in-VM iptables model).
80
+ * - `denyOut`: the all-traffic sentinel (`0.0.0.0/0`) so the policy is
81
+ * DEFAULT-DENY — only `allowOut` targets pass. E2B applies allow before
82
+ * deny, so a host in both lists is allowed.
83
+ * - `rules`: per-host header injection. For each host carrying transform(s),
84
+ * emit one rule per transform with `{ transform: { headers } }`. A host
85
+ * with a rule MUST also be in `allowOut` (registering a rule does not
86
+ * grant egress on its own — E2B's API requires the host in `allowOut`).
87
+ *
88
+ * BEHAVIOURAL DIVERGENCE FROM iron-proxy / Vercel — path/method gating is NOT
89
+ * available on E2B native rules. An `SandboxNetworkRule` carries only a
90
+ * `transform` (header injection); there is no method/path matcher. So our
91
+ * Tier-2 `requestRules` (`{ methods, pathPrefixes }`) cannot be enforced here:
92
+ * a brokered credential rides EVERY request to an allowed host on E2B, whereas
93
+ * Vercel (native `match`) and the old iron-proxy gated it to the declared
94
+ * method+path. The host allowlist still confines WHICH hosts the credential
95
+ * can reach; it just can't narrow to a method/path within an allowed host.
96
+ *
97
+ * String policies map straight through: "allow-all" → no restriction (omit
98
+ * `allowOut`/`denyOut`), "deny-all" → block all egress.
99
+ */
100
+ export declare function toE2bNetwork(policy: SandboxNetworkPolicy): E2bNetworkOpts;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The contract a sandbox provider implements (`SandboxProviderDef`), the
3
+ * create-time options every provider receives, and the fleet tag that scopes
4
+ * a deployment's sandboxes on the provider side. Providers implement this;
5
+ * the registry consumes it.
6
+ */
7
+ import type { SandboxProvider } from "../types/sandbox.js";
8
+ import type { SandboxNetworkPolicy } from "./network-policy.js";
9
+ import type { SandboxSize } from "./sizes.js";
10
+ export declare const AGENT_COMPOSE_TAG: string;
11
+ export interface SandboxCreateOpts {
12
+ envs: Record<string, string>;
13
+ metadata: Record<string, string>;
14
+ timeoutMs: number;
15
+ /** Provider-specific template/snapshot identifier. E2B: template id or
16
+ * snapshot id (omit → E2B's default base). Vercel: snapshot id (omit → node24). */
17
+ template?: string;
18
+ /** Machine size. Vercel maps it to `resources.vcpus`; E2B ignores it
19
+ * (size is template-defined). Omit → `DEFAULT_SANDBOX_SIZE`. */
20
+ size?: SandboxSize;
21
+ /** Outbound request policy with header transforms — ONE shape for every
22
+ * provider; only the enforcement point differs. Both providers enforce +
23
+ * inject natively at create from this value: Vercel via its firewall
24
+ * (`toVercelNetworkPolicy`), E2B via its native network firewall
25
+ * (`toE2bNetwork` → `network: { allowOut, denyOut, rules }`). */
26
+ networkPolicy?: SandboxNetworkPolicy;
27
+ }
28
+ /**
29
+ * What the provider reports as "currently alive" — the single input to orphan
30
+ * reconciliation. `metadata` is best-effort: E2B populates it from sandbox
31
+ * labels, Vercel from sandbox tags (max 5, stamped at create). Callers
32
+ * that need runId correlation cross-reference `sandboxId` against their own
33
+ * state (server: `workflow_runs.sandbox_id` + `run_agent_sandboxes.provider_sandbox_id`).
34
+ */
35
+ export interface OwnedSandbox {
36
+ sandboxId: string;
37
+ createdAt: Date;
38
+ metadata: Record<string, string>;
39
+ }
40
+ export interface SandboxProviderDef {
41
+ requiredEnv: Record<string, string>;
42
+ create: (opts: SandboxCreateOpts, env: Record<string, string>) => Promise<SandboxProvider>;
43
+ reconnect?: (sandboxId: string) => Promise<SandboxProvider>;
44
+ killAll?: (env: Record<string, string>) => Promise<void>;
45
+ /**
46
+ * Returns the number of sandboxes currently running against this provider.
47
+ * Used for quota observability — account-wide, not per-instance.
48
+ *
49
+ * Both providers scope by our AGENT_COMPOSE_TAG (E2B metadata labels,
50
+ * Vercel tags) so each fleet only reports its own sandboxes; DD aggregates
51
+ * across instances with `sum by {provider}`.
52
+ */
53
+ getActiveCount?: (env: Record<string, string>) => Promise<number>;
54
+ /**
55
+ * Provider's view of what's currently alive for our account/fleet.
56
+ * The reconciliation SoT — a sandbox missing from this list IS dead,
57
+ * regardless of what our DB says. Implemented by listing the provider's
58
+ * running sandboxes; E2B filters by metadata tag, Vercel by the `executor`
59
+ * sandbox tag.
60
+ */
61
+ listOwned?: (env: Record<string, string>) => Promise<OwnedSandbox[]>;
62
+ /**
63
+ * Delete a snapshot by id. Called from the customer-initiated
64
+ * `DELETE /workflows/:runId/snapshot` route. Best-effort — the DB is
65
+ * source of truth; an orphan on the provider side is benign and cleaned
66
+ * up eventually by the provider's own retention.
67
+ */
68
+ deleteSnapshot?: (snapshotId: string, env: Record<string, string>) => Promise<void>;
69
+ /**
70
+ * Does this snapshot id resolve on the provider for this account? A cheap
71
+ * metadata lookup — NEVER provisions a sandbox. Used at server boot to
72
+ * validate that the platform base snapshots the default templates boot from
73
+ * actually exist for this deployment (catching e.g. a dev-account snapshot
74
+ * baked into prod). Returns true if it resolves, false if the provider
75
+ * reports it does not exist. Transport/credential errors PROPAGATE — an
76
+ * indeterminate result must not be reported as "missing".
77
+ */
78
+ snapshotExists?: (snapshotId: string, env: Record<string, string>) => Promise<boolean>;
79
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * E2B Desktop sandbox provider — the base E2B wrapper plus mouse/keyboard/
3
+ * screenshot control, and the "e2b-desktop" registry entry. Registry-only:
4
+ * never a workflow's runtime (see `workflow-metadata.ts`).
5
+ */
6
+ import { Sandbox as Desktop } from "@e2b/desktop";
7
+ import type { DesktopSandboxProvider } from "../../types/sandbox.js";
8
+ import type { SandboxProviderDef } from "../provider-def.js";
9
+ export declare function makeDesktopSandboxProvider(sb: Desktop): DesktopSandboxProvider;
10
+ export declare const e2bDesktopProviderDef: SandboxProviderDef;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * E2B sandbox provider — the generic E2B command/file wrapper
3
+ * (`makeSandboxProvider`, shared with the desktop provider), the E2B-specific
4
+ * provider with background launch + native VM-suspend, and the "e2b"
5
+ * registry entry.
6
+ */
7
+ import { Sandbox } from "e2b";
8
+ import type { Sandbox as Desktop } from "@e2b/desktop";
9
+ import type { SandboxProvider } from "../../types/sandbox.js";
10
+ import type { SandboxProviderDef } from "../provider-def.js";
11
+ export declare function makeSandboxProvider(sb: Sandbox | Desktop): SandboxProvider;
12
+ /** E2B caps a sandbox's lifetime per plan (Hobby 1h, Pro 24h) and 400s when the
13
+ * create timeout exceeds it — and our `AC_SANDBOX_DEADLINE` (4.5h) tops Hobby.
14
+ * Clamp the E2B create timeout to this max (raise on Pro via `E2B_MAX_SANDBOX_MS`).
15
+ * Read at call time so the workflow bundle stays env-free. */
16
+ export declare function e2bMaxSandboxMs(): number;
17
+ export declare const e2bProviderDef: SandboxProviderDef;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Local provider (runner's own VM).
3
+ */
4
+ import type { SandboxProvider } from "../../types/sandbox.js";
5
+ /** Minimal SandboxProvider that targets the current process's host VM.
6
+ * `commands.run` → `child_process.spawn`; `files.write` → `fs.writeFile`;
7
+ * `kill` is a no-op because the caller IS the VM. Used by
8
+ * `defineSandboxEnvironment` so setup recipes read like imperative
9
+ * provisioning scripts. Uses `node:child_process` — works under both Node
10
+ * (the runner executes `node /tmp/runner.bundle.js`) and Bun. */
11
+ export declare function makeLocalSandboxProvider(): SandboxProvider;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Vercel sandbox provider (@vercel/sandbox 2.x) — tag building, the provider
3
+ * wrapper, and the "vercel" registry entry.
4
+ *
5
+ * CRITICAL: every VALUE import of @vercel/sandbox in this file is dynamic
6
+ * (`await import(...)`) so the package stays out of runner.bundle.js — the
7
+ * runner never calls these paths. Top-level imports are type-only (erased).
8
+ */
9
+ import type { SandboxProviderDef } from "../provider-def.js";
10
+ /** Vercel caps sandbox tags at 5. Build the tag set with the fleet `executor`
11
+ * tag always present and always winning over caller metadata — `listOwned` /
12
+ * orphan reconciliation scope on it, so a clobbered or dropped executor tag
13
+ * makes the sandbox invisible to cleanup. When metadata overflows the cap,
14
+ * keep executor + the lexicographically-first 4 metadata keys, so what gets
15
+ * dropped is deterministic rather than dependent on object insertion order. */
16
+ export declare const VERCEL_MAX_TAGS = 5;
17
+ export declare function buildVercelTags(metadata: Record<string, string>): Record<string, string>;
18
+ export declare const vercelProviderDef: SandboxProviderDef;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Sandbox provider registry — creates and manages isolated execution
3
+ * environments across every registered provider, and owns the ONE sandbox
4
+ * retry policy. Providers: "vercel" (Vercel Sandbox), "e2b" (E2B),
5
+ * "e2b-desktop" (E2B Desktop). Each provider validates its required env vars
6
+ * at creation time.
7
+ */
8
+ import type { SandboxProvider } from "../types/sandbox.js";
9
+ import type { SandboxCreateOpts, SandboxProviderDef, OwnedSandbox } from "./provider-def.js";
10
+ export type SandboxProviderName = "vercel" | "e2b" | "e2b-desktop";
11
+ export declare const SANDBOX_PROVIDERS: Record<string, SandboxProviderDef>;
12
+ /** Provision a sandbox for the named provider. */
13
+ export declare function createSandbox(provider: SandboxProviderName, opts: SandboxCreateOpts): Promise<SandboxProvider>;
14
+ /** Reconnect to an existing sandbox by provider + provider-native sandbox ID. */
15
+ export declare function reconnectSandbox(provider: SandboxProviderName, sandboxId: string): Promise<SandboxProvider>;
16
+ /** Delete a snapshot by id on the named provider. No live sandbox needed. */
17
+ export declare function deleteSandboxSnapshot(provider: SandboxProviderName, snapshotId: string): Promise<void>;
18
+ /**
19
+ * Does `snapshotId` resolve on the named provider for this account? A cheap
20
+ * metadata lookup — never provisions a sandbox. `true` = resolves, `false` =
21
+ * provider says it does not exist. Transport/credential errors propagate (an
22
+ * indeterminate result is not "missing"). Used at server boot to validate the
23
+ * platform base snapshots the default templates boot from.
24
+ */
25
+ export declare function snapshotResolves(provider: SandboxProviderName, snapshotId: string): Promise<boolean>;
26
+ /**
27
+ * Current active-sandbox count per configured provider, for quota gauges.
28
+ * Skips providers whose required env isn't set or which don't implement
29
+ * `getActiveCount`. Errors are surfaced per-provider so one flaky provider
30
+ * doesn't silence the rest.
31
+ */
32
+ export type SandboxQuotaResult = Partial<Record<SandboxProviderName, number | Error>>;
33
+ export declare function getSandboxQuotas(): Promise<SandboxQuotaResult>;
34
+ /**
35
+ * List every alive sandbox owned by this fleet, across all configured
36
+ * providers — the orphan-reconciler SoT. Providers without `listOwned`
37
+ * or missing env are skipped. Errors propagate per-provider so one flaky
38
+ * provider doesn't silence the rest.
39
+ */
40
+ export type OwnedSandboxResult = Partial<Record<SandboxProviderName, OwnedSandbox[] | Error>>;
41
+ export declare function listOwnedSandboxes(): Promise<OwnedSandboxResult>;
42
+ /** Kill a sandbox by provider + native ID. Used by the orphan reconciler. */
43
+ export declare function killSandboxById(provider: SandboxProviderName, sandboxId: string): Promise<void>;
44
+ /** Kill all sandboxes across all registered providers. Call at startup to clean up after crashes. */
45
+ export declare function killAllSandboxes(onError?: (provider: SandboxProviderName, err: unknown) => void): Promise<void>;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Sandbox machine sizes + the E2B template aliases derived from them.
3
+ *
4
+ * A coarse hardware knob that maps to provider machine specs at create time:
5
+ * Vercel honours it natively via `resources.vcpus`; E2B sizing is baked into
6
+ * the template, so on E2B a size resolves to a pre-built per-size template.
7
+ */
8
+ /** Sandbox hardware SKU. Named for the actual machine spec (vCPU + RAM) rather
9
+ * than abstract t-shirt sizes. Memory is always 2048 MB per vCPU:
10
+ * 2vcpu-4gb = 2 vCPU / 4 GiB (Vercel's own default machine)
11
+ * 4vcpu-8gb = 4 vCPU / 8 GiB
12
+ * 8vcpu-16gb = 8 vCPU / 16 GiB (per-sandbox ceiling on STANDARD accounts —
13
+ * probed live: 16 & 32 vCPU 400 on dev)
14
+ * 32vcpu-64gb = 32 vCPU / 64 GiB (ENTERPRISE ONLY — standard accounts reject >8 vCPU) */
15
+ export type SandboxSize = "2vcpu-4gb" | "4vcpu-8gb" | "8vcpu-16gb" | "32vcpu-64gb";
16
+ /** SKU → Vercel vCPU count (RAM follows at 2048 MB/vCPU). */
17
+ export declare const SANDBOX_VCPUS: Record<SandboxSize, number>;
18
+ /** SDK fallback size when neither the caller nor the deployment specifies one.
19
+ * Deliberately conservative — the OPERATIONAL default is the server's
20
+ * `SANDBOX_DEFAULT_SIZE` env var (now also `2vcpu-4gb`). Keeping the default
21
+ * small matters because Vercel rate-limits creation by vCPUs-per-window
22
+ * (`api-sandboxes-vcpus-creation`); a large default 429s bursty/simultaneous
23
+ * creates. Workloads that need more RAM/CPU declare `resources.size` on the
24
+ * workflow rather than inflating the default for everyone. */
25
+ export declare const DEFAULT_SANDBOX_SIZE: SandboxSize;
26
+ /** The E2B sizes we pre-build a template for. E2B sizing is template-baked
27
+ * (no per-create cpu/mem knob), so honouring `resources.size` on E2B means
28
+ * ONE pre-built template per size. `32vcpu-64gb` is absent (E2B has no
29
+ * >8-vCPU equivalent). `8vcpu-16gb` is also absent: it needs 16 GiB RAM, but
30
+ * the E2B account caps memory at 8 GiB (`Template.build` 400s with
31
+ * "Memory can't be higher than 8192 MiB"). Add it back here (and rebuild the
32
+ * templates) only once the account's memory limit is raised. The register/
33
+ * invoke guards reject an unsupported E2B size before it can reach here. */
34
+ export declare const E2B_TEMPLATE_SIZES: readonly SandboxSize[];
35
+ /** Is `size` one E2B can be built/booted at? `32vcpu-64gb` (no >8-vCPU E2B
36
+ * equivalent) and `8vcpu-16gb` (exceeds the account's 8 GiB memory cap) are
37
+ * not — the guards lean on this so the "no E2B equivalent" decision lives in
38
+ * exactly one place. */
39
+ export declare function isE2bSupportedSize(size: SandboxSize): boolean;
40
+ /** Machine spec for a SandboxSize, in the shape `Template.build` wants. RAM is
41
+ * always 2048 MB/vCPU, matching the size name + Vercel parity
42
+ * (`SANDBOX_VCPUS` × 2048). Used by `infra/e2b-template/build.ts` to stamp the
43
+ * per-size base + agent-env templates. */
44
+ export declare function e2bMachineSpec(size: SandboxSize): {
45
+ cpuCount: number;
46
+ memoryMB: number;
47
+ };
48
+ /** Stable E2B template ALIAS for the platform base at a given size
49
+ * (`agent-compose-base-<size>`). Aliases — not snapshot ids — so the refs are
50
+ * multi-account-clean: the same string resolves in any E2B account that built
51
+ * the templates. Built by `infra/e2b-template/build.ts`; the E2B provider
52
+ * boots this when a run on E2B declares no explicit `bootFrom`/template. */
53
+ export declare function e2bBaseTemplate(size: SandboxSize): string;
54
+ /** Stable E2B template ALIAS for the agent runtime (base + claude binary) at a
55
+ * given size (`agent-env-<size>`). The agent default templates boot from this
56
+ * via `bootFrom: { snapshotId: e2bAgentEnvTemplate(size) }`. Multi-account-clean
57
+ * for the same reason as `e2bBaseTemplate`. */
58
+ export declare function e2bAgentEnvTemplate(size: SandboxSize): string;
59
+ /** Is `id` one of the platform-managed E2B template aliases — a
60
+ * `agent-compose-base-<size>` or `agent-env-<size>` for a supported size?
61
+ *
62
+ * These are stable, platform-built, multi-account-clean strings (NOT tenant
63
+ * captures), so any workflow may boot from them: the same alias resolves to
64
+ * the same platform base in every account, and there is no tenant data behind
65
+ * it to leak. The dispatch snapshot gate uses this to vouch a platform E2B
66
+ * boot alias without it having to be a team-owned capture or a published
67
+ * template's bootFrom. */
68
+ export declare function isPlatformE2bTemplateAlias(id: string): boolean;