@agentproto/sandbox 0.2.6 → 0.4.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/README.md +39 -0
- package/dist/chunk-VGEBUSSP.mjs +47 -0
- package/dist/chunk-VGEBUSSP.mjs.map +1 -0
- package/dist/index.d.ts +61 -12
- package/dist/index.mjs +20 -3
- package/dist/index.mjs.map +1 -1
- package/dist/manifest/index.d.ts +2 -2
- package/dist/manifest/index.mjs +2 -2
- package/dist/{schema-PGQIdcQ3.d.ts → schema-h9uR_HeR.d.ts} +34 -2
- package/package.json +8 -8
- package/dist/chunk-VSYQ4BUI.mjs +0 -47
- package/dist/chunk-VSYQ4BUI.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -44,6 +44,45 @@ try {
|
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
## Adapter auto-install on boot
|
|
48
|
+
|
|
49
|
+
A box's boot-time `npm i -g @agentproto/cli` replaces the global install and
|
|
50
|
+
loses the template-baked `@agentproto/adapter-*` packages. Providers that
|
|
51
|
+
accept `config.installPackages` (e2b, box) install those entries in the SAME
|
|
52
|
+
npm invocation as the CLI update; when spawning through the runtime
|
|
53
|
+
(`spawnAgentSession`), the adapter about to be launched — plus
|
|
54
|
+
`@anthropic-ai/claude-code` for the `claude-code` adapter — is injected there
|
|
55
|
+
automatically (a caller-declared pin always wins, nothing is injected for
|
|
56
|
+
non-sandbox spawns).
|
|
57
|
+
|
|
58
|
+
For harnesses BEYOND the spawned adapter, declare semantic slugs instead of
|
|
59
|
+
raw npm specs: `config.installAdapters: ["hermes", "claude-code"]`. Each slug
|
|
60
|
+
expands to `@agentproto/adapter-<slug>@latest` (plus that adapter's declared
|
|
61
|
+
boot extras, e.g. `@anthropic-ai/claude-code`) and merges into
|
|
62
|
+
`config.installPackages` with dedupe — a caller's explicit pin for the same
|
|
63
|
+
package always wins. An unknown slug still expands (the box's npm install is
|
|
64
|
+
the authority); an absent field changes nothing.
|
|
65
|
+
|
|
66
|
+
## Billing-auth auto-passthrough (opt-in)
|
|
67
|
+
|
|
68
|
+
Set `env.autoPassthrough: true` on a sandbox spec and the runtime adds the
|
|
69
|
+
spawn's RESOLVED billing-credential env-var NAME (e.g. `ANTHROPIC_API_KEY`) to
|
|
70
|
+
`env.passthrough` before the box boots — so a fresh box inherits host auth
|
|
71
|
+
without the caller naming vars. Only the NAME is injected; the value travels
|
|
72
|
+
via the normal passthrough mechanism (host secrets broker → box env) and is
|
|
73
|
+
never read by the flag. Billing credential only (no `GITHUB_TOKEN`); the
|
|
74
|
+
caller's explicit `env.passthrough` entries are kept and deduped. When no
|
|
75
|
+
credential resolved — or the host cannot resolve the var — nothing is
|
|
76
|
+
injected and the spawn proceeds: the flag is a convenience, not a contract.
|
|
77
|
+
|
|
78
|
+
## Lifecycle: pause is the default teardown
|
|
79
|
+
|
|
80
|
+
Closing a session PAUSES its box by default (`pause({ keepMemory: true })`)
|
|
81
|
+
rather than killing it — any closed box stays reattachable via
|
|
82
|
+
`sandbox.reuse` / `agentproto sandbox attach`. A paused box still dies at its
|
|
83
|
+
own `timeoutMs` (45 min by default), so paused boxes don't accumulate
|
|
84
|
+
indefinitely; declare `lifecycle.destroy_on` for a hard kill on close.
|
|
85
|
+
|
|
47
86
|
## License
|
|
48
87
|
|
|
49
88
|
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createDoctype } from '@agentproto/define-doctype';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @agentproto/sandbox v0.1.0-alpha
|
|
6
|
+
* AIP-36 SANDBOX.md `defineSandbox` reference implementation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
var sandboxFrontmatterSchema = z.object({ "schema": z.literal("sandbox/v1").describe("Standalone-only. Identifies the doctype + version. Absent when the block is inlined.").optional(), "id": z.string().regex(new RegExp("^@[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*$")).describe("Standalone-only. Globally addressable id `@<owner-slug>/<sandbox-slug>`.").optional(), "version": z.string().regex(new RegExp("^\\d+\\.\\d+\\.\\d+(?:[-+][\\w.\\-]+)?$")).describe("Standalone-only. Spec version of THIS file.").optional(), "provider": z.string().min(1).describe("Backend kind. Day-1 enumerated set: local | mastra-e2b | mastra-modal | mastra-daytona | mastra-blaxel | node-permission. Hosts MAY register additional ids."), "config": z.object({ "installAdapters": z.array(z.string().min(1)).describe(`Semantic harness slugs to pre-install in the box at boot (e.g. ["hermes", "claude-code"]). Each expands to @agentproto/adapter-<slug>@latest plus that adapter's declared boot extras; a config.installPackages pin for the same package always wins. Additive \u2014 absent means no extra install.`).optional() }).catchall(z.any()).describe("Provider-specific connection fields. Shape varies per provider (see AIP-36 \xA7Provider config shapes)."), "limits": z.object({ "timeout_ms": z.number().int().gte(1).optional(), "memory_mb": z.number().int().gte(1).optional(), "cpu_ms": z.number().int().gte(1).optional() }).strict().describe("Resource caps per command.").optional(), "env": z.object({ "auth": z.any().optional(), "passthrough": z.array(z.string()).describe("Static host env-var names to forward into the sandbox.").default([]), "autoPassthrough": z.boolean().describe("Opt-in: forward the spawn's resolved billing-credential env-var NAME (e.g. ANTHROPIC_API_KEY) into passthrough so a fresh box inherits host auth without the caller naming vars. Only the name is injected \u2014 the value travels via the normal passthrough mechanism.").optional() }).strict().optional(), "network": z.object({ "egress": z.array(z.string()).describe("Hostnames the sandbox MAY reach. Empty / missing = no egress.").default([]) }).strict().optional(), "mounts": z.array(z.any()).describe("Filesystems mounted inside the sandbox at declared paths. Maps to Mastra Workspace.mounts.").default([]), "identity": z.any().describe("AIP-23 identity-ref \u2014 owner of the sandbox processes.").optional(), "policy": z.any().describe("AIP-38 POLICY block \u2014 access grants on sandbox actions.").optional(), "lifecycle": z.object({ "pause_after_idle": z.string().min(1).describe("AIP-37 event name (e.g. `idle-600` for 10 min). Provider-supported only (modal, daytona).").optional(), "destroy_on": z.string().min(1).describe("AIP-37 event name (e.g. `workspace-close`).").optional() }).strict().optional(), "read_only": z.boolean().describe("Reject command execution at the sandbox layer. Read-only sandbox calls fail with `sandbox_read_only`.").default(false), "extraPorts": z.array(z.number().int().gte(1)).describe("App ports to expose at boot time. Resolved into BootedSandbox.ports (port \u2192 public URL) by providers that support port exposure.").optional(), "metadata": z.record(z.string(), z.any()).describe("Free-form, namespaced. Adapter hints under `metadata.<adapter>.*`.").optional() }).strict().describe("Validates the YAML frontmatter portion of an AIP-36 SANDBOX.md manifest, OR the inline form embedded in any other manifest's `sandbox:` block. Compute-only \u2014 durable filesystem backings live in AIP-35 STORAGE.md.");
|
|
10
|
+
var defineSandboxInner = createDoctype({
|
|
11
|
+
aip: 36,
|
|
12
|
+
name: "sandbox",
|
|
13
|
+
readDescription: false,
|
|
14
|
+
// AIP-36 makes `id` standalone-only — inline sandbox blocks fall
|
|
15
|
+
// back to `provider` for the cross-AIP id-pattern check (mirrors
|
|
16
|
+
// AIP-43 § Identity).
|
|
17
|
+
readIdentity: (def) => {
|
|
18
|
+
if (typeof def.id === "string" && def.id.length > 0) return def.id;
|
|
19
|
+
return def.provider;
|
|
20
|
+
},
|
|
21
|
+
validate(def) {
|
|
22
|
+
const { factory: _factory, capabilities: _capabilities, ...manifest } = def;
|
|
23
|
+
const result = sandboxFrontmatterSchema.safeParse(manifest);
|
|
24
|
+
if (!result.success) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`defineSandbox (AIP-36): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
build(def) {
|
|
31
|
+
const { factory, capabilities, ...manifest } = def;
|
|
32
|
+
return {
|
|
33
|
+
...manifest,
|
|
34
|
+
...factory !== void 0 ? { factory } : {},
|
|
35
|
+
...capabilities !== void 0 ? { capabilities: Object.freeze({ ...capabilities }) } : {}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
function defineSandbox(definition) {
|
|
40
|
+
return defineSandboxInner(
|
|
41
|
+
definition
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { defineSandbox, sandboxFrontmatterSchema };
|
|
46
|
+
//# sourceMappingURL=chunk-VGEBUSSP.mjs.map
|
|
47
|
+
//# sourceMappingURL=chunk-VGEBUSSP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts","../src/define-sandbox.ts"],"names":[],"mappings":"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","file":"chunk-VGEBUSSP.mjs","sourcesContent":["/**\n * AIP-36 SANDBOX.md frontmatter zod schema.\n *\n * Generated from `resources/aip-36/draft/SANDBOX.schema.json` via\n * json-schema-to-zod. Imported by both `define-sandbox.ts` (TS path\n * validation) and `manifest/index.ts` (.md path validation) so every\n * field-level constraint runs in both authoring paths from a single\n * source of truth — re-run scaffold-aip to refresh after spec changes.\n *\n * Cross-field rules (if/then/allOf in JSON Schema) don't translate\n * cleanly and live in `define-sandbox.ts`'s `validate(def)` instead.\n */\n\nimport { z } from \"zod\"\n\nexport const sandboxFrontmatterSchema = z.object({ \"schema\": z.literal(\"sandbox/v1\").describe(\"Standalone-only. Identifies the doctype + version. Absent when the block is inlined.\").optional(), \"id\": z.string().regex(new RegExp(\"^@[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*$\")).describe(\"Standalone-only. Globally addressable id `@<owner-slug>/<sandbox-slug>`.\").optional(), \"version\": z.string().regex(new RegExp(\"^\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:[-+][\\\\w.\\\\-]+)?$\")).describe(\"Standalone-only. Spec version of THIS file.\").optional(), \"provider\": z.string().min(1).describe(\"Backend kind. Day-1 enumerated set: local | mastra-e2b | mastra-modal | mastra-daytona | mastra-blaxel | node-permission. Hosts MAY register additional ids.\"), \"config\": z.object({ \"installAdapters\": z.array(z.string().min(1)).describe(\"Semantic harness slugs to pre-install in the box at boot (e.g. [\\\"hermes\\\", \\\"claude-code\\\"]). Each expands to @agentproto/adapter-<slug>@latest plus that adapter's declared boot extras; a config.installPackages pin for the same package always wins. Additive — absent means no extra install.\").optional() }).catchall(z.any()).describe(\"Provider-specific connection fields. Shape varies per provider (see AIP-36 §Provider config shapes).\"), \"limits\": z.object({ \"timeout_ms\": z.number().int().gte(1).optional(), \"memory_mb\": z.number().int().gte(1).optional(), \"cpu_ms\": z.number().int().gte(1).optional() }).strict().describe(\"Resource caps per command.\").optional(), \"env\": z.object({ \"auth\": z.any().optional(), \"passthrough\": z.array(z.string()).describe(\"Static host env-var names to forward into the sandbox.\").default([] as never), \"autoPassthrough\": z.boolean().describe(\"Opt-in: forward the spawn's resolved billing-credential env-var NAME (e.g. ANTHROPIC_API_KEY) into passthrough so a fresh box inherits host auth without the caller naming vars. Only the name is injected — the value travels via the normal passthrough mechanism.\").optional() }).strict().optional(), \"network\": z.object({ \"egress\": z.array(z.string()).describe(\"Hostnames the sandbox MAY reach. Empty / missing = no egress.\").default([] as never) }).strict().optional(), \"mounts\": z.array(z.any()).describe(\"Filesystems mounted inside the sandbox at declared paths. Maps to Mastra Workspace.mounts.\").default([] as never), \"identity\": z.any().describe(\"AIP-23 identity-ref — owner of the sandbox processes.\").optional(), \"policy\": z.any().describe(\"AIP-38 POLICY block — access grants on sandbox actions.\").optional(), \"lifecycle\": z.object({ \"pause_after_idle\": z.string().min(1).describe(\"AIP-37 event name (e.g. `idle-600` for 10 min). Provider-supported only (modal, daytona).\").optional(), \"destroy_on\": z.string().min(1).describe(\"AIP-37 event name (e.g. `workspace-close`).\").optional() }).strict().optional(), \"read_only\": z.boolean().describe(\"Reject command execution at the sandbox layer. Read-only sandbox calls fail with `sandbox_read_only`.\").default(false), \"extraPorts\": z.array(z.number().int().gte(1)).describe(\"App ports to expose at boot time. Resolved into BootedSandbox.ports (port → public URL) by providers that support port exposure.\").optional(), \"metadata\": z.record(z.string(), z.any()).describe(\"Free-form, namespaced. Adapter hints under `metadata.<adapter>.*`.\").optional() }).strict().describe(\"Validates the YAML frontmatter portion of an AIP-36 SANDBOX.md manifest, OR the inline form embedded in any other manifest's `sandbox:` block. Compute-only — durable filesystem backings live in AIP-35 STORAGE.md.\")\n\nexport type SandboxFrontmatter = z.infer<typeof sandboxFrontmatterSchema>\n","import { createDoctype } from \"@agentproto/define-doctype\"\nimport { sandboxFrontmatterSchema } from \"./schema.js\"\nimport type {\n SandboxRuntimeHandle,\n SandboxRuntimeInput,\n} from \"./types.js\"\n\n/**\n * AIP-36 reference implementation of `defineSandbox`.\n *\n * Built on `createDoctype` so the cross-AIP invariants (id pattern,\n * description length, top-level freeze, \"defineSandbox (AIP-36): …\"\n * error prefix) run uniformly with every other AIP defineX.\n *\n * Field-level validation runs the schema-derived zod from\n * `./schema.ts` against the manifest portion of the input. The\n * AIP-43 runtime slots (`factory`, `capabilities`) are HOST-OPAQUE\n * TS-runtime metadata stripped before validation and re-attached in\n * `build` — same pattern as `@agentproto/storage`'s `defineStorage`.\n *\n * Generic params:\n * TFactory — host-typed factory (e.g. `(input) => WorkspaceSandbox`\n * for the Guilde host). Defaults to `unknown`.\n * TCapabilities — opaque metadata the registry queries on. Per\n * AIP-43 § Capability metadata namespace.\n */\nconst defineSandboxInner = createDoctype<\n SandboxRuntimeInput,\n SandboxRuntimeHandle\n>({\n aip: 36,\n name: \"sandbox\",\n readDescription: false,\n // AIP-36 makes `id` standalone-only — inline sandbox blocks fall\n // back to `provider` for the cross-AIP id-pattern check (mirrors\n // AIP-43 § Identity).\n readIdentity: def => {\n if (typeof def.id === \"string\" && def.id.length > 0) return def.id\n return def.provider\n },\n validate(def) {\n const { factory: _factory, capabilities: _capabilities, ...manifest } = def\n const result = sandboxFrontmatterSchema.safeParse(manifest)\n if (!result.success) {\n throw new Error(\n `defineSandbox (AIP-36): ${result.error.issues\n .map(i => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \")}`,\n )\n }\n // TODO: spec-36-specific cross-field rules (if/then/allOf in\n // the JSON Schema) — those don't translate to zod cleanly and\n // belong here. See @agentproto/operator's autonomy=gated rule.\n },\n build(def) {\n const { factory, capabilities, ...manifest } = def\n return {\n ...manifest,\n ...(factory !== undefined ? { factory } : {}),\n ...(capabilities !== undefined\n ? { capabilities: Object.freeze({ ...capabilities }) }\n : {}),\n } as SandboxRuntimeHandle\n },\n})\n\n/**\n * Type-aware wrapper preserving `TFactory` / `TCapabilities` generics\n * across the call.\n */\nexport function defineSandbox<\n TFactory = unknown,\n TCapabilities extends Record<string, unknown> = Record<string, unknown>,\n>(\n definition: SandboxRuntimeInput<TFactory, TCapabilities>,\n): SandboxRuntimeHandle<TFactory, TCapabilities> {\n return defineSandboxInner(\n definition as SandboxRuntimeInput,\n ) as SandboxRuntimeHandle<TFactory, TCapabilities>\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SandboxRuntimeInput, a as SandboxRuntimeHandle, b as SandboxHandle } from './schema-
|
|
2
|
-
export { c as SandboxDefinition, s as SandboxSpecSchema } from './schema-
|
|
1
|
+
import { S as SandboxRuntimeInput, a as SandboxRuntimeHandle, b as SandboxHandle } from './schema-h9uR_HeR.js';
|
|
2
|
+
export { c as SandboxDefinition, s as SandboxSpecSchema } from './schema-h9uR_HeR.js';
|
|
3
3
|
import { SecretResolver } from '@agentproto/secrets/exposure';
|
|
4
4
|
import { DaemonAgentSessionHost } from '@agentproto/worktree';
|
|
5
5
|
import 'zod';
|
|
@@ -26,6 +26,15 @@ declare function defineSandbox<TFactory = unknown, TCapabilities extends Record<
|
|
|
26
26
|
|
|
27
27
|
/** AIP-36 sandbox manifest handle — provider id, config, env passthrough, limits. */
|
|
28
28
|
type SandboxSpec = SandboxHandle;
|
|
29
|
+
/**
|
|
30
|
+
* Thrown when a caller requests port exposure on a `BootedSandbox` whose
|
|
31
|
+
* provider does not support it — i.e. the sandbox handle has no `expose()`
|
|
32
|
+
* method. Callers should check for `expose` before calling it, or catch
|
|
33
|
+
* this error and fall back gracefully.
|
|
34
|
+
*/
|
|
35
|
+
declare class SandboxPortExposureUnsupportedError extends Error {
|
|
36
|
+
constructor(message?: string);
|
|
37
|
+
}
|
|
29
38
|
/** What a `SandboxProvider` hands back once the box is up and reachable. */
|
|
30
39
|
interface BootedSandbox {
|
|
31
40
|
/** The booted agentproto daemon's MCP endpoint, reachable from this process. */
|
|
@@ -50,6 +59,26 @@ interface BootedSandbox {
|
|
|
50
59
|
* token-only provider that omits this is treated by `buildMcpConfigSnippet`
|
|
51
60
|
* as `Authorization: Bearer <token>`. */
|
|
52
61
|
authHeaders?: Record<string, string>;
|
|
62
|
+
/**
|
|
63
|
+
* Expose an app port on the sandbox and return its public URL. E2B returns
|
|
64
|
+
* `https://<port>-<sandboxId>.e2b.app`. Loopback bind is enough inside the
|
|
65
|
+
* VM — the provider's edge handles the forwarding.
|
|
66
|
+
*
|
|
67
|
+
* Optional: providers that cannot expose arbitrary ports omit this method.
|
|
68
|
+
* Callers should check for presence before calling, or catch
|
|
69
|
+
* `SandboxPortExposureUnsupportedError` when using `exposePort()`.
|
|
70
|
+
*/
|
|
71
|
+
expose?(port: number): Promise<{
|
|
72
|
+
url: string;
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Ports resolved at boot time from `SandboxSpec.extraPorts` — a map of
|
|
76
|
+
* port number to public URL. Only present when the spec declared
|
|
77
|
+
* `extraPorts` AND the provider supports exposure. Callers that need a
|
|
78
|
+
* port URL at runtime should use `expose()` directly when this map is
|
|
79
|
+
* absent or doesn't include the target port.
|
|
80
|
+
*/
|
|
81
|
+
ports?: Record<number, string>;
|
|
53
82
|
/** Tear down the sandbox. */
|
|
54
83
|
stop(): Promise<void>;
|
|
55
84
|
/** Pause the sandbox instead of killing it — keeps it reconnectable via
|
|
@@ -58,6 +87,13 @@ interface BootedSandbox {
|
|
|
58
87
|
* that want to pause fall back to `stop()` when it's absent. */
|
|
59
88
|
pause?(): Promise<void>;
|
|
60
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Expose a port on a booted sandbox. Throws `SandboxPortExposureUnsupportedError`
|
|
92
|
+
* when the provider's sandbox handle has no `expose()` method.
|
|
93
|
+
*/
|
|
94
|
+
declare function exposePort(booted: BootedSandbox, port: number): Promise<{
|
|
95
|
+
url: string;
|
|
96
|
+
}>;
|
|
61
97
|
/** Env resolved from secrets, handed to `provider.boot`. */
|
|
62
98
|
interface SandboxBootOpts {
|
|
63
99
|
env: Record<string, string>;
|
|
@@ -112,9 +148,20 @@ interface CreateSandboxAgentSessionHostOpts {
|
|
|
112
148
|
sandboxId?: string;
|
|
113
149
|
}
|
|
114
150
|
type SandboxAgentSessionHost = DaemonAgentSessionHost & {
|
|
151
|
+
/** The booted sandbox daemon's MCP endpoint (`BootedSandbox.mcpUrl`) —
|
|
152
|
+
* surfaced so a caller can drive the box's OTHER daemon tools (app_install,
|
|
153
|
+
* command_execute, …) the same way the session host drives agent_start. */
|
|
154
|
+
mcpUrl: string;
|
|
115
155
|
/** Provider-assigned sandbox id (`BootedSandbox.sandboxId`) — surfaced so a
|
|
116
156
|
* caller can record it (there's no local PID for a sandboxed session). */
|
|
117
157
|
sandboxId: string;
|
|
158
|
+
/** Ports resolved at boot from `SandboxSpec.extraPorts` — forwarded from
|
|
159
|
+
* `BootedSandbox.ports` so the runtime can record them on the session
|
|
160
|
+
* descriptor without reaching into the booted handle after the fact. */
|
|
161
|
+
ports?: Record<number, string>;
|
|
162
|
+
/** Expose an app port and return its public URL — forwarded from
|
|
163
|
+
* `BootedSandbox.expose`. Absent when the provider doesn't support it. */
|
|
164
|
+
expose?: BootedSandbox["expose"];
|
|
118
165
|
/** Close the daemon connection AND tear down the sandbox. */
|
|
119
166
|
stop(): Promise<void>;
|
|
120
167
|
/** Close the daemon connection and PAUSE the sandbox instead of killing
|
|
@@ -140,9 +187,10 @@ declare function createSandboxAgentSessionHost(opts: CreateSandboxAgentSessionHo
|
|
|
140
187
|
*/
|
|
141
188
|
|
|
142
189
|
interface SandboxLifecyclePolicy {
|
|
143
|
-
/** What session close should do to the box:
|
|
144
|
-
*
|
|
145
|
-
*
|
|
190
|
+
/** What session close should do to the box: pause it (keeps it
|
|
191
|
+
* reconnectable via `SandboxProvider.connect`) or kill it (ephemeral).
|
|
192
|
+
* Pause is the default: absent any explicit lifecycle declaration the
|
|
193
|
+
* box is paused on close, and dies at its own `timeoutMs` anyway. */
|
|
146
194
|
teardown: "kill" | "pause";
|
|
147
195
|
/** Idle window in milliseconds, parsed from the AIP-37 `idle-<seconds>`
|
|
148
196
|
* event name. Undefined when the spec doesn't declare
|
|
@@ -150,12 +198,13 @@ interface SandboxLifecyclePolicy {
|
|
|
150
198
|
pauseAfterIdleMs?: number;
|
|
151
199
|
}
|
|
152
200
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* `destroy_on` always
|
|
158
|
-
*
|
|
201
|
+
* Pause is the default teardown: absent `destroy_on`, `pause_after_idle`
|
|
202
|
+
* AND `reuse`, a closed box is paused (`SandboxProvider.connect`-able)
|
|
203
|
+
* rather than killed — it still dies at its own `timeoutMs`, so pausing
|
|
204
|
+
* never accumulates boxes indefinitely. The explicit declarations stay
|
|
205
|
+
* authoritative: an `destroy_on` always kills (the spec states outright
|
|
206
|
+
* the box must not survive session close), and `pause_after_idle` /
|
|
207
|
+
* `reuse` pause (which the default now agrees with).
|
|
159
208
|
*/
|
|
160
209
|
declare function resolveLifecyclePolicy(spec: SandboxHandle, reuse: boolean): SandboxLifecyclePolicy;
|
|
161
210
|
|
|
@@ -173,4 +222,4 @@ declare function resolveLifecyclePolicy(spec: SandboxHandle, reuse: boolean): Sa
|
|
|
173
222
|
declare const SPEC_NAME: "agentsandbox/v1";
|
|
174
223
|
declare const SPEC_VERSION: "1.0.0-alpha";
|
|
175
224
|
|
|
176
|
-
export { type BootedSandbox, type CreateSandboxAgentSessionHostOpts, SPEC_NAME, SPEC_VERSION, type SandboxAgentSessionHost, type SandboxBootOpts, SandboxHandle, type SandboxLifecyclePolicy, type SandboxProvider, SandboxRuntimeHandle, SandboxRuntimeInput, type SandboxSecretsConfig, type SandboxSpec, createSandboxAgentSessionHost, defineSandbox, resolveLifecyclePolicy };
|
|
225
|
+
export { type BootedSandbox, type CreateSandboxAgentSessionHostOpts, SPEC_NAME, SPEC_VERSION, type SandboxAgentSessionHost, type SandboxBootOpts, SandboxHandle, type SandboxLifecyclePolicy, SandboxPortExposureUnsupportedError, type SandboxProvider, SandboxRuntimeHandle, SandboxRuntimeInput, type SandboxSecretsConfig, type SandboxSpec, createSandboxAgentSessionHost, defineSandbox, exposePort, resolveLifecyclePolicy };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { sandboxFrontmatterSchema as SandboxSpecSchema, defineSandbox } from './chunk-
|
|
1
|
+
export { sandboxFrontmatterSchema as SandboxSpecSchema, defineSandbox } from './chunk-VGEBUSSP.mjs';
|
|
2
2
|
import { assertSafeSecretValue } from '@agentproto/secrets/exposure';
|
|
3
3
|
import { connectDaemonAgentSessionHost } from '@agentproto/worktree';
|
|
4
4
|
|
|
@@ -6,6 +6,20 @@ import { connectDaemonAgentSessionHost } from '@agentproto/worktree';
|
|
|
6
6
|
* @agentproto/sandbox v0.1.0-alpha
|
|
7
7
|
* AIP-36 SANDBOX.md `defineSandbox` reference implementation.
|
|
8
8
|
*/
|
|
9
|
+
var SandboxPortExposureUnsupportedError = class extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message ?? "This sandbox provider does not support port exposure.");
|
|
12
|
+
this.name = "SandboxPortExposureUnsupportedError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
async function exposePort(booted, port) {
|
|
16
|
+
if (!booted.expose) {
|
|
17
|
+
throw new SandboxPortExposureUnsupportedError(
|
|
18
|
+
`sandbox "${booted.sandboxId}" does not support port exposure \u2014 the provider has no expose() implementation.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return booted.expose(port);
|
|
22
|
+
}
|
|
9
23
|
async function createSandboxAgentSessionHost(opts) {
|
|
10
24
|
const env = await resolveSandboxSecretsEnv(opts.secrets);
|
|
11
25
|
let booted;
|
|
@@ -28,7 +42,10 @@ async function createSandboxAgentSessionHost(opts) {
|
|
|
28
42
|
}
|
|
29
43
|
return {
|
|
30
44
|
...host,
|
|
45
|
+
mcpUrl: booted.mcpUrl,
|
|
31
46
|
sandboxId: booted.sandboxId,
|
|
47
|
+
...booted.ports ? { ports: booted.ports } : {},
|
|
48
|
+
...booted.expose ? { expose: booted.expose.bind(booted) } : {},
|
|
32
49
|
async stop() {
|
|
33
50
|
await host.close();
|
|
34
51
|
await booted.stop();
|
|
@@ -63,7 +80,7 @@ var IDLE_EVENT_PATTERN = /^idle-(\d+)$/;
|
|
|
63
80
|
function resolveLifecyclePolicy(spec, reuse) {
|
|
64
81
|
if (spec.lifecycle?.destroy_on) return { teardown: "kill" };
|
|
65
82
|
const pauseAfterIdleMs = parseIdleAfterMs(spec.lifecycle?.pause_after_idle);
|
|
66
|
-
const teardown =
|
|
83
|
+
const teardown = "pause";
|
|
67
84
|
return { teardown, ...pauseAfterIdleMs !== void 0 ? { pauseAfterIdleMs } : {} };
|
|
68
85
|
}
|
|
69
86
|
function parseIdleAfterMs(event) {
|
|
@@ -77,6 +94,6 @@ function parseIdleAfterMs(event) {
|
|
|
77
94
|
var SPEC_NAME = "agentsandbox/v1";
|
|
78
95
|
var SPEC_VERSION = "1.0.0-alpha";
|
|
79
96
|
|
|
80
|
-
export { SPEC_NAME, SPEC_VERSION, createSandboxAgentSessionHost, resolveLifecyclePolicy };
|
|
97
|
+
export { SPEC_NAME, SPEC_VERSION, SandboxPortExposureUnsupportedError, createSandboxAgentSessionHost, exposePort, resolveLifecyclePolicy };
|
|
81
98
|
//# sourceMappingURL=index.mjs.map
|
|
82
99
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agent-session-host.ts","../src/lifecycle.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * AIP-36 sandbox-backed `AgentSessionHost`.\n *\n * The seam an `AgentStep` binds against (`AgentSessionHost`,\n * `@agentproto/workflow-runtime`) is already satisfiable by a *remote*\n * daemon via `connectDaemonAgentSessionHost` (`@agentproto/worktree`) —\n * it just needs a reachable MCP URL. So running a coding-agent step\n * inside a sandbox is: boot a provider-specific box that exposes an\n * agentproto daemon's MCP endpoint as a URL, then hand that URL to the\n * daemon host unchanged. No new session-host implementation, no\n * bespoke spawn/prompt plumbing — this module only wires secrets → env\n * → `provider.boot` → `connectDaemonAgentSessionHost`.\n */\n\nimport { assertSafeSecretValue, type SecretResolver } from \"@agentproto/secrets/exposure\"\nimport { connectDaemonAgentSessionHost, type DaemonAgentSessionHost } from \"@agentproto/worktree\"\nimport type { SandboxHandle } from \"./types.js\"\n\n/** AIP-36 sandbox manifest handle — provider id, config, env passthrough, limits. */\nexport type SandboxSpec = SandboxHandle\n\n/** What a `SandboxProvider` hands back once the box is up and reachable. */\nexport interface BootedSandbox {\n /** The booted agentproto daemon's MCP endpoint, reachable from this process. */\n mcpUrl: string\n /** Provider-assigned sandbox id, for logging / lookup. */\n sandboxId: string\n /** Opaque secret gating `mcpUrl`, present when `opts.expose === \"private\"`\n * was honoured (see `SandboxBootOpts.expose`). Absent for the default\n * public-exposure path (boot-and-drive) and for providers/paths that\n * can't gate the port at all — a caller that needs a gated URL (e.g.\n * `attachSandbox`) MUST treat a missing token as \"not gated\", not as\n * \"no auth needed\". The token is the raw secret; how a client must\n * PRESENT it (bearer header, cookie, …) is provider-specific — see\n * `authHeaders`. */\n token?: string\n /** Exact HTTP header(s) a client must send to authenticate against the\n * gated `mcpUrl` — the provider's own answer to \"how do I present the\n * token\". Box, for instance, gates its private hostname with a\n * `Cookie: _port_auth=<token>` (verified live: bearer/query are ignored,\n * the port edge only honours the cookie), so it returns that here rather\n * than leaving the caller to guess a scheme. Present iff `token` is; a\n * token-only provider that omits this is treated by `buildMcpConfigSnippet`\n * as `Authorization: Bearer <token>`. */\n authHeaders?: Record<string, string>\n /** Tear down the sandbox. */\n stop(): Promise<void>\n /** Pause the sandbox instead of killing it — keeps it reconnectable via\n * `SandboxProvider.connect(sandboxId, ...)` later. Optional: providers\n * that can't pause (or don't support reconnect at all) omit it; callers\n * that want to pause fall back to `stop()` when it's absent. */\n pause?(): Promise<void>\n}\n\n/** Env resolved from secrets, handed to `provider.boot`. */\nexport interface SandboxBootOpts {\n env: Record<string, string>\n /**\n * How the provider should expose the daemon's port. `\"public\"` (the\n * default when omitted) is boot-and-drive's ephemeral, provider-owned,\n * ungated URL. `\"private\"` asks the provider for a PERSISTENT,\n * token-gated URL instead — set by `attachSandbox`, which produces a\n * durable connection descriptor and must never emit an ungated one.\n * Providers that don't support gating simply ignore this and omit\n * `BootedSandbox.token`; the caller is responsible for treating that as\n * a failure when it needed a gated URL.\n */\n expose?: \"public\" | \"private\"\n /**\n * Keep the sandbox awake indefinitely for the always-on rendezvous model\n * — set by `attachSandbox` when its own `keepAlive` opt is true. A\n * provider that supports an explicit no-auto-stop/no-expiry assertion\n * (e.g. Box's `ttlSeconds: null`) should (re-)apply it as part of\n * `connect()`, defensively, even if the sandbox already defaults to it.\n * Providers with no such concept simply ignore this.\n */\n keepAlive?: boolean\n}\n\n/**\n * Backend-agnostic sandbox lifecycle. Concrete implementations (e2b, modal,\n * daytona, blaxel, …) live in their own packages so this one stays free of\n * vendor SDK dependencies — see `@agentproto/sandbox-e2b`.\n */\nexport interface SandboxProvider {\n boot(spec: SandboxSpec, opts: SandboxBootOpts): Promise<BootedSandbox>\n /** Reconnect to an already-booted (possibly paused) sandbox instead of\n * booting a fresh one — the reuse path (`agent_start.sandbox.reuse`).\n * Optional: providers that can't reconnect (e.g. the `local` passthrough,\n * which tears down its temp workspace on `stop()`) omit it; the runtime\n * errors clearly when reuse is requested against such a provider. */\n connect?(sandboxId: string, spec: SandboxSpec, opts: SandboxBootOpts): Promise<BootedSandbox>\n}\n\n/** Which secrets to resolve into the sandbox's env, and how. */\nexport interface SandboxSecretsConfig {\n /** Secret slugs to resolve (e.g. `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`). */\n slugs: readonly string[]\n /** Resolves a slug to its value. Defaults to reading `process.env[slug]`. */\n resolver?: SecretResolver\n}\n\nexport interface CreateSandboxAgentSessionHostOpts {\n provider: SandboxProvider\n spec: SandboxSpec\n secrets: SandboxSecretsConfig\n /** Reconnect to this existing sandbox id instead of booting a fresh box —\n * requires `provider.connect`; throws a clear error otherwise. */\n sandboxId?: string\n}\n\nexport type SandboxAgentSessionHost = DaemonAgentSessionHost & {\n /** Provider-assigned sandbox id (`BootedSandbox.sandboxId`) — surfaced so a\n * caller can record it (there's no local PID for a sandboxed session). */\n sandboxId: string\n /** Close the daemon connection AND tear down the sandbox. */\n stop(): Promise<void>\n /** Close the daemon connection and PAUSE the sandbox instead of killing\n * it — only present when the booted sandbox supports `pause()`. */\n pause?(): Promise<void>\n}\n\n/**\n * Resolve `secrets` into an env map, boot (or, when `opts.sandboxId` is set,\n * reconnect to) the sandbox with it, then connect the #202 daemon host to\n * the sandbox's exposed MCP URL. `stop()` closes the daemon connection\n * before tearing down the sandbox (never leaks the box on a client-side\n * error); `pause()` does the same but pauses rather than kills.\n */\nexport async function createSandboxAgentSessionHost(\n opts: CreateSandboxAgentSessionHostOpts,\n): Promise<SandboxAgentSessionHost> {\n const env = await resolveSandboxSecretsEnv(opts.secrets)\n let booted: BootedSandbox\n if (opts.sandboxId !== undefined) {\n if (!opts.provider.connect) {\n throw new Error(\n `createSandboxAgentSessionHost: reuse requested for sandbox \"${opts.sandboxId}\", ` +\n \"but this provider has no connect() — it can only boot fresh sandboxes.\",\n )\n }\n booted = await opts.provider.connect(opts.sandboxId, opts.spec, { env })\n } else {\n booted = await opts.provider.boot(opts.spec, { env })\n }\n let host: DaemonAgentSessionHost\n try {\n host = await connectDaemonAgentSessionHost({ url: booted.mcpUrl })\n } catch (err) {\n await booted.stop()\n throw err\n }\n return {\n ...host,\n sandboxId: booted.sandboxId,\n async stop(): Promise<void> {\n await host.close()\n await booted.stop()\n },\n ...(booted.pause\n ? {\n async pause(): Promise<void> {\n await host.close()\n await booted.pause!()\n },\n }\n : {}),\n }\n}\n\nconst defaultProcessEnvResolver: SecretResolver = name => process.env[name] ?? null\n\n/** Resolve every configured slug, failing loudly (no silent gaps in the sandbox env). */\nasync function resolveSandboxSecretsEnv(\n config: SandboxSecretsConfig,\n): Promise<Record<string, string>> {\n const resolver = config.resolver ?? defaultProcessEnvResolver\n const env: Record<string, string> = {}\n for (const slug of config.slugs) {\n const value = await resolver(slug)\n if (value === null || value === undefined) {\n throw new Error(\n `createSandboxAgentSessionHost: missing secret \"${slug}\" — set it in the ` +\n \"host process's environment, or pass a resolver that can supply it.\",\n )\n }\n assertSafeSecretValue(slug, value)\n env[slug] = value\n }\n return env\n}\n","/**\n * AIP-36 `lifecycle` policy resolution — maps a `SandboxHandle`'s\n * `lifecycle.pause_after_idle` / `lifecycle.destroy_on` (plus whether this\n * boot is a request to reconnect to an existing box) to a concrete\n * teardown decision. Pure and host-agnostic: the actual pause-vs-kill call\n * happens in `@agentproto/runtime`'s sandbox proxy, which just reads this\n * policy back off.\n */\n\nimport type { SandboxHandle } from \"./types.js\"\n\nexport interface SandboxLifecyclePolicy {\n /** What session close should do to the box: kill it (ephemeral, the\n * default) or pause it (keeps it reconnectable via `SandboxProvider.\n * connect`). */\n teardown: \"kill\" | \"pause\"\n /** Idle window in milliseconds, parsed from the AIP-37 `idle-<seconds>`\n * event name. Undefined when the spec doesn't declare\n * `lifecycle.pause_after_idle`. */\n pauseAfterIdleMs?: number\n}\n\nconst IDLE_EVENT_PATTERN = /^idle-(\\d+)$/\n\n/**\n * `reuse` is true when this spawn asked to reconnect to an existing\n * sandbox id (`agent_start.sandbox.reuse`) — such a box defaults to\n * \"pause\" on close even absent an explicit `lifecycle` block, since\n * killing it would defeat the point of having reconnected. An explicit\n * `destroy_on` always wins over both `reuse` and `pause_after_idle`: the\n * spec is stating outright that this box must not survive session close.\n */\nexport function resolveLifecyclePolicy(spec: SandboxHandle, reuse: boolean): SandboxLifecyclePolicy {\n if (spec.lifecycle?.destroy_on) return { teardown: \"kill\" }\n\n const pauseAfterIdleMs = parseIdleAfterMs(spec.lifecycle?.pause_after_idle)\n const teardown: \"kill\" | \"pause\" = reuse || pauseAfterIdleMs !== undefined ? \"pause\" : \"kill\"\n return { teardown, ...(pauseAfterIdleMs !== undefined ? { pauseAfterIdleMs } : {}) }\n}\n\nfunction parseIdleAfterMs(event: string | undefined): number | undefined {\n if (!event) return undefined\n const match = IDLE_EVENT_PATTERN.exec(event)\n if (!match) return undefined\n return Number(match[1]) * 1000\n}\n","/**\n * @agentproto/sandbox — AIP-36 SANDBOX.md `defineSandbox` reference impl.\n *\n * A composable schema block defining the `sandbox` field — provider, config, command env, network egress, resource limits — for any manifest that names a compute environment for agent-issued shell commands. Sibling primitive to STORAGE.md (AIP-35); inline or ref, mirroring AIP-17 RUNNER and AIP-19 SECRETS.\n *\n * Spec: https://agentproto.sh/docs/aip-36\n *\n * Authoring paths:\n * - TS: `defineSandbox({...})` → `SandboxHandle`\n * - MD: `parseSandboxManifest(src) → sandboxFromManifest({...})` → `SandboxHandle`\n */\n\nexport const SPEC_NAME = \"agentsandbox/v1\" as const\nexport const SPEC_VERSION = \"1.0.0-alpha\" as const\n\nexport { defineSandbox } from \"./define-sandbox.js\"\nexport type {\n SandboxDefinition,\n SandboxHandle,\n SandboxRuntimeInput,\n SandboxRuntimeHandle,\n} from \"./types.js\"\n\n/** The AIP-36 frontmatter zod schema, under the name consumers that accept\n * an inline `SandboxSpec` (e.g. `@agentproto/runtime`'s `agent_start.sandbox`)\n * validate against. Same schema `define-sandbox.ts`/`manifest/index.ts` use. */\nexport { sandboxFrontmatterSchema as SandboxSpecSchema } from \"./schema.js\"\n\nexport {\n createSandboxAgentSessionHost,\n type SandboxSpec,\n type BootedSandbox,\n type SandboxBootOpts,\n type SandboxProvider,\n type SandboxSecretsConfig,\n type CreateSandboxAgentSessionHostOpts,\n type SandboxAgentSessionHost,\n} from \"./agent-session-host.js\"\n\nexport { resolveLifecyclePolicy, type SandboxLifecyclePolicy } from \"./lifecycle.js\"\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/agent-session-host.ts","../src/lifecycle.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * AIP-36 sandbox-backed `AgentSessionHost`.\n *\n * The seam an `AgentStep` binds against (`AgentSessionHost`,\n * `@agentproto/workflow-runtime`) is already satisfiable by a *remote*\n * daemon via `connectDaemonAgentSessionHost` (`@agentproto/worktree`) —\n * it just needs a reachable MCP URL. So running a coding-agent step\n * inside a sandbox is: boot a provider-specific box that exposes an\n * agentproto daemon's MCP endpoint as a URL, then hand that URL to the\n * daemon host unchanged. No new session-host implementation, no\n * bespoke spawn/prompt plumbing — this module only wires secrets → env\n * → `provider.boot` → `connectDaemonAgentSessionHost`.\n */\n\nimport { assertSafeSecretValue, type SecretResolver } from \"@agentproto/secrets/exposure\"\nimport { connectDaemonAgentSessionHost, type DaemonAgentSessionHost } from \"@agentproto/worktree\"\nimport type { SandboxHandle } from \"./types.js\"\n\n/** AIP-36 sandbox manifest handle — provider id, config, env passthrough, limits. */\nexport type SandboxSpec = SandboxHandle\n\n/**\n * Thrown when a caller requests port exposure on a `BootedSandbox` whose\n * provider does not support it — i.e. the sandbox handle has no `expose()`\n * method. Callers should check for `expose` before calling it, or catch\n * this error and fall back gracefully.\n */\nexport class SandboxPortExposureUnsupportedError extends Error {\n constructor(message?: string) {\n super(message ?? \"This sandbox provider does not support port exposure.\")\n this.name = \"SandboxPortExposureUnsupportedError\"\n }\n}\n\n/** What a `SandboxProvider` hands back once the box is up and reachable. */\nexport interface BootedSandbox {\n /** The booted agentproto daemon's MCP endpoint, reachable from this process. */\n mcpUrl: string\n /** Provider-assigned sandbox id, for logging / lookup. */\n sandboxId: string\n /** Opaque secret gating `mcpUrl`, present when `opts.expose === \"private\"`\n * was honoured (see `SandboxBootOpts.expose`). Absent for the default\n * public-exposure path (boot-and-drive) and for providers/paths that\n * can't gate the port at all — a caller that needs a gated URL (e.g.\n * `attachSandbox`) MUST treat a missing token as \"not gated\", not as\n * \"no auth needed\". The token is the raw secret; how a client must\n * PRESENT it (bearer header, cookie, …) is provider-specific — see\n * `authHeaders`. */\n token?: string\n /** Exact HTTP header(s) a client must send to authenticate against the\n * gated `mcpUrl` — the provider's own answer to \"how do I present the\n * token\". Box, for instance, gates its private hostname with a\n * `Cookie: _port_auth=<token>` (verified live: bearer/query are ignored,\n * the port edge only honours the cookie), so it returns that here rather\n * than leaving the caller to guess a scheme. Present iff `token` is; a\n * token-only provider that omits this is treated by `buildMcpConfigSnippet`\n * as `Authorization: Bearer <token>`. */\n authHeaders?: Record<string, string>\n /**\n * Expose an app port on the sandbox and return its public URL. E2B returns\n * `https://<port>-<sandboxId>.e2b.app`. Loopback bind is enough inside the\n * VM — the provider's edge handles the forwarding.\n *\n * Optional: providers that cannot expose arbitrary ports omit this method.\n * Callers should check for presence before calling, or catch\n * `SandboxPortExposureUnsupportedError` when using `exposePort()`.\n */\n expose?(port: number): Promise<{ url: string }>\n /**\n * Ports resolved at boot time from `SandboxSpec.extraPorts` — a map of\n * port number to public URL. Only present when the spec declared\n * `extraPorts` AND the provider supports exposure. Callers that need a\n * port URL at runtime should use `expose()` directly when this map is\n * absent or doesn't include the target port.\n */\n ports?: Record<number, string>\n /** Tear down the sandbox. */\n stop(): Promise<void>\n /** Pause the sandbox instead of killing it — keeps it reconnectable via\n * `SandboxProvider.connect(sandboxId, ...)` later. Optional: providers\n * that can't pause (or don't support reconnect at all) omit it; callers\n * that want to pause fall back to `stop()` when it's absent. */\n pause?(): Promise<void>\n}\n\n/**\n * Expose a port on a booted sandbox. Throws `SandboxPortExposureUnsupportedError`\n * when the provider's sandbox handle has no `expose()` method.\n */\nexport async function exposePort(booted: BootedSandbox, port: number): Promise<{ url: string }> {\n if (!booted.expose) {\n throw new SandboxPortExposureUnsupportedError(\n `sandbox \"${booted.sandboxId}\" does not support port exposure — ` +\n \"the provider has no expose() implementation.\",\n )\n }\n return booted.expose(port)\n}\n\n/** Env resolved from secrets, handed to `provider.boot`. */\nexport interface SandboxBootOpts {\n env: Record<string, string>\n /**\n * How the provider should expose the daemon's port. `\"public\"` (the\n * default when omitted) is boot-and-drive's ephemeral, provider-owned,\n * ungated URL. `\"private\"` asks the provider for a PERSISTENT,\n * token-gated URL instead — set by `attachSandbox`, which produces a\n * durable connection descriptor and must never emit an ungated one.\n * Providers that don't support gating simply ignore this and omit\n * `BootedSandbox.token`; the caller is responsible for treating that as\n * a failure when it needed a gated URL.\n */\n expose?: \"public\" | \"private\"\n /**\n * Keep the sandbox awake indefinitely for the always-on rendezvous model\n * — set by `attachSandbox` when its own `keepAlive` opt is true. A\n * provider that supports an explicit no-auto-stop/no-expiry assertion\n * (e.g. Box's `ttlSeconds: null`) should (re-)apply it as part of\n * `connect()`, defensively, even if the sandbox already defaults to it.\n * Providers with no such concept simply ignore this.\n */\n keepAlive?: boolean\n}\n\n/**\n * Backend-agnostic sandbox lifecycle. Concrete implementations (e2b, modal,\n * daytona, blaxel, …) live in their own packages so this one stays free of\n * vendor SDK dependencies — see `@agentproto/sandbox-e2b`.\n */\nexport interface SandboxProvider {\n boot(spec: SandboxSpec, opts: SandboxBootOpts): Promise<BootedSandbox>\n /** Reconnect to an already-booted (possibly paused) sandbox instead of\n * booting a fresh one — the reuse path (`agent_start.sandbox.reuse`).\n * Optional: providers that can't reconnect (e.g. the `local` passthrough,\n * which tears down its temp workspace on `stop()`) omit it; the runtime\n * errors clearly when reuse is requested against such a provider. */\n connect?(sandboxId: string, spec: SandboxSpec, opts: SandboxBootOpts): Promise<BootedSandbox>\n}\n\n/** Which secrets to resolve into the sandbox's env, and how. */\nexport interface SandboxSecretsConfig {\n /** Secret slugs to resolve (e.g. `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`). */\n slugs: readonly string[]\n /** Resolves a slug to its value. Defaults to reading `process.env[slug]`. */\n resolver?: SecretResolver\n}\n\nexport interface CreateSandboxAgentSessionHostOpts {\n provider: SandboxProvider\n spec: SandboxSpec\n secrets: SandboxSecretsConfig\n /** Reconnect to this existing sandbox id instead of booting a fresh box —\n * requires `provider.connect`; throws a clear error otherwise. */\n sandboxId?: string\n}\n\nexport type SandboxAgentSessionHost = DaemonAgentSessionHost & {\n /** The booted sandbox daemon's MCP endpoint (`BootedSandbox.mcpUrl`) —\n * surfaced so a caller can drive the box's OTHER daemon tools (app_install,\n * command_execute, …) the same way the session host drives agent_start. */\n mcpUrl: string\n /** Provider-assigned sandbox id (`BootedSandbox.sandboxId`) — surfaced so a\n * caller can record it (there's no local PID for a sandboxed session). */\n sandboxId: string\n /** Ports resolved at boot from `SandboxSpec.extraPorts` — forwarded from\n * `BootedSandbox.ports` so the runtime can record them on the session\n * descriptor without reaching into the booted handle after the fact. */\n ports?: Record<number, string>\n /** Expose an app port and return its public URL — forwarded from\n * `BootedSandbox.expose`. Absent when the provider doesn't support it. */\n expose?: BootedSandbox[\"expose\"]\n /** Close the daemon connection AND tear down the sandbox. */\n stop(): Promise<void>\n /** Close the daemon connection and PAUSE the sandbox instead of killing\n * it — only present when the booted sandbox supports `pause()`. */\n pause?(): Promise<void>\n}\n\n/**\n * Resolve `secrets` into an env map, boot (or, when `opts.sandboxId` is set,\n * reconnect to) the sandbox with it, then connect the #202 daemon host to\n * the sandbox's exposed MCP URL. `stop()` closes the daemon connection\n * before tearing down the sandbox (never leaks the box on a client-side\n * error); `pause()` does the same but pauses rather than kills.\n */\nexport async function createSandboxAgentSessionHost(\n opts: CreateSandboxAgentSessionHostOpts,\n): Promise<SandboxAgentSessionHost> {\n const env = await resolveSandboxSecretsEnv(opts.secrets)\n let booted: BootedSandbox\n if (opts.sandboxId !== undefined) {\n if (!opts.provider.connect) {\n throw new Error(\n `createSandboxAgentSessionHost: reuse requested for sandbox \"${opts.sandboxId}\", ` +\n \"but this provider has no connect() — it can only boot fresh sandboxes.\",\n )\n }\n booted = await opts.provider.connect(opts.sandboxId, opts.spec, { env })\n } else {\n booted = await opts.provider.boot(opts.spec, { env })\n }\n let host: DaemonAgentSessionHost\n try {\n host = await connectDaemonAgentSessionHost({ url: booted.mcpUrl })\n } catch (err) {\n await booted.stop()\n throw err\n }\n return {\n ...host,\n mcpUrl: booted.mcpUrl,\n sandboxId: booted.sandboxId,\n ...(booted.ports ? { ports: booted.ports } : {}),\n ...(booted.expose ? { expose: booted.expose.bind(booted) } : {}),\n async stop(): Promise<void> {\n await host.close()\n await booted.stop()\n },\n ...(booted.pause\n ? {\n async pause(): Promise<void> {\n await host.close()\n await booted.pause!()\n },\n }\n : {}),\n }\n}\n\nconst defaultProcessEnvResolver: SecretResolver = name => process.env[name] ?? null\n\n/** Resolve every configured slug, failing loudly (no silent gaps in the sandbox env). */\nasync function resolveSandboxSecretsEnv(\n config: SandboxSecretsConfig,\n): Promise<Record<string, string>> {\n const resolver = config.resolver ?? defaultProcessEnvResolver\n const env: Record<string, string> = {}\n for (const slug of config.slugs) {\n const value = await resolver(slug)\n if (value === null || value === undefined) {\n throw new Error(\n `createSandboxAgentSessionHost: missing secret \"${slug}\" — set it in the ` +\n \"host process's environment, or pass a resolver that can supply it.\",\n )\n }\n assertSafeSecretValue(slug, value)\n env[slug] = value\n }\n return env\n}\n","/**\n * AIP-36 `lifecycle` policy resolution — maps a `SandboxHandle`'s\n * `lifecycle.pause_after_idle` / `lifecycle.destroy_on` (plus whether this\n * boot is a request to reconnect to an existing box) to a concrete\n * teardown decision. Pure and host-agnostic: the actual pause-vs-kill call\n * happens in `@agentproto/runtime`'s sandbox proxy, which just reads this\n * policy back off.\n */\n\nimport type { SandboxHandle } from \"./types.js\"\n\nexport interface SandboxLifecyclePolicy {\n /** What session close should do to the box: pause it (keeps it\n * reconnectable via `SandboxProvider.connect`) or kill it (ephemeral).\n * Pause is the default: absent any explicit lifecycle declaration the\n * box is paused on close, and dies at its own `timeoutMs` anyway. */\n teardown: \"kill\" | \"pause\"\n /** Idle window in milliseconds, parsed from the AIP-37 `idle-<seconds>`\n * event name. Undefined when the spec doesn't declare\n * `lifecycle.pause_after_idle`. */\n pauseAfterIdleMs?: number\n}\n\nconst IDLE_EVENT_PATTERN = /^idle-(\\d+)$/\n\n/**\n * Pause is the default teardown: absent `destroy_on`, `pause_after_idle`\n * AND `reuse`, a closed box is paused (`SandboxProvider.connect`-able)\n * rather than killed — it still dies at its own `timeoutMs`, so pausing\n * never accumulates boxes indefinitely. The explicit declarations stay\n * authoritative: an `destroy_on` always kills (the spec states outright\n * the box must not survive session close), and `pause_after_idle` /\n * `reuse` pause (which the default now agrees with).\n */\nexport function resolveLifecyclePolicy(spec: SandboxHandle, reuse: boolean): SandboxLifecyclePolicy {\n if (spec.lifecycle?.destroy_on) return { teardown: \"kill\" }\n\n const pauseAfterIdleMs = parseIdleAfterMs(spec.lifecycle?.pause_after_idle)\n const teardown: \"kill\" | \"pause\" = \"pause\"\n return { teardown, ...(pauseAfterIdleMs !== undefined ? { pauseAfterIdleMs } : {}) }\n}\n\nfunction parseIdleAfterMs(event: string | undefined): number | undefined {\n if (!event) return undefined\n const match = IDLE_EVENT_PATTERN.exec(event)\n if (!match) return undefined\n return Number(match[1]) * 1000\n}\n","/**\n * @agentproto/sandbox — AIP-36 SANDBOX.md `defineSandbox` reference impl.\n *\n * A composable schema block defining the `sandbox` field — provider, config, command env, network egress, resource limits — for any manifest that names a compute environment for agent-issued shell commands. Sibling primitive to STORAGE.md (AIP-35); inline or ref, mirroring AIP-17 RUNNER and AIP-19 SECRETS.\n *\n * Spec: https://agentproto.sh/docs/aip-36\n *\n * Authoring paths:\n * - TS: `defineSandbox({...})` → `SandboxHandle`\n * - MD: `parseSandboxManifest(src) → sandboxFromManifest({...})` → `SandboxHandle`\n */\n\nexport const SPEC_NAME = \"agentsandbox/v1\" as const\nexport const SPEC_VERSION = \"1.0.0-alpha\" as const\n\nexport { defineSandbox } from \"./define-sandbox.js\"\nexport type {\n SandboxDefinition,\n SandboxHandle,\n SandboxRuntimeInput,\n SandboxRuntimeHandle,\n} from \"./types.js\"\n\n/** The AIP-36 frontmatter zod schema, under the name consumers that accept\n * an inline `SandboxSpec` (e.g. `@agentproto/runtime`'s `agent_start.sandbox`)\n * validate against. Same schema `define-sandbox.ts`/`manifest/index.ts` use. */\nexport { sandboxFrontmatterSchema as SandboxSpecSchema } from \"./schema.js\"\n\nexport {\n createSandboxAgentSessionHost,\n exposePort,\n SandboxPortExposureUnsupportedError,\n type SandboxSpec,\n type BootedSandbox,\n type SandboxBootOpts,\n type SandboxProvider,\n type SandboxSecretsConfig,\n type CreateSandboxAgentSessionHostOpts,\n type SandboxAgentSessionHost,\n} from \"./agent-session-host.js\"\n\nexport { resolveLifecyclePolicy, type SandboxLifecyclePolicy } from \"./lifecycle.js\"\n"]}
|
package/dist/manifest/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { d as SandboxFrontmatter, b as SandboxHandle } from '../schema-
|
|
2
|
-
export { s as sandboxFrontmatterSchema } from '../schema-
|
|
1
|
+
import { d as SandboxFrontmatter, b as SandboxHandle } from '../schema-h9uR_HeR.js';
|
|
2
|
+
export { s as sandboxFrontmatterSchema } from '../schema-h9uR_HeR.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/manifest/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sandboxFrontmatterSchema, defineSandbox } from '../chunk-
|
|
2
|
-
export { sandboxFrontmatterSchema } from '../chunk-
|
|
1
|
+
import { sandboxFrontmatterSchema, defineSandbox } from '../chunk-VGEBUSSP.mjs';
|
|
2
|
+
export { sandboxFrontmatterSchema } from '../chunk-VGEBUSSP.mjs';
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -48,7 +48,16 @@ interface SandboxDefinition {
|
|
|
48
48
|
/**
|
|
49
49
|
* Provider-specific connection fields. Shape varies per provider (see AIP-36 §Provider config shapes).
|
|
50
50
|
*/
|
|
51
|
-
config: {
|
|
51
|
+
config: {
|
|
52
|
+
/**
|
|
53
|
+
* Semantic harness slugs to pre-install in the box at boot (e.g. `["hermes", "claude-code"]`).
|
|
54
|
+
* Each expands to `@agentproto/adapter-<slug>@latest` plus that adapter's declared boot extras;
|
|
55
|
+
* a `config.installPackages` pin for the same package always wins. Additive — absent means no
|
|
56
|
+
* extra install.
|
|
57
|
+
*/
|
|
58
|
+
installAdapters?: string[];
|
|
59
|
+
[k: string]: unknown;
|
|
60
|
+
};
|
|
52
61
|
/**
|
|
53
62
|
* Resource caps per command.
|
|
54
63
|
*/
|
|
@@ -63,6 +72,17 @@ interface SandboxDefinition {
|
|
|
63
72
|
* Static host env-var names to forward into the sandbox.
|
|
64
73
|
*/
|
|
65
74
|
passthrough?: string[];
|
|
75
|
+
/**
|
|
76
|
+
* Opt-in: forward the spawn's RESOLVED billing-credential env-var NAME
|
|
77
|
+
* (e.g. `ANTHROPIC_API_KEY`) into `passthrough` so a fresh box inherits
|
|
78
|
+
* host auth without the caller naming vars. Only the name is injected —
|
|
79
|
+
* the value travels via the normal passthrough mechanism (host secrets
|
|
80
|
+
* broker → box env) and is never read by this flag. Billing credential
|
|
81
|
+
* only: other vars (GITHUB_TOKEN, …) stay the job of an explicit
|
|
82
|
+
* `passthrough`. A convenience, not a contract: when no credential
|
|
83
|
+
* resolved, nothing is injected and the spawn proceeds.
|
|
84
|
+
*/
|
|
85
|
+
autoPassthrough?: boolean;
|
|
66
86
|
};
|
|
67
87
|
network?: {
|
|
68
88
|
/**
|
|
@@ -92,6 +112,13 @@ interface SandboxDefinition {
|
|
|
92
112
|
* Reject command execution at the sandbox layer. Read-only sandbox calls fail with `sandbox_read_only`.
|
|
93
113
|
*/
|
|
94
114
|
read_only?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* App ports to expose at boot time. Resolved into `BootedSandbox.ports` (port → public URL)
|
|
117
|
+
* by providers that support port exposure (e.g. e2b via `getHost(port)`). Providers that do
|
|
118
|
+
* not support exposure ignore this field — callers that need a URL must check `ports` on the
|
|
119
|
+
* booted handle and call `expose()` explicitly when the field is absent.
|
|
120
|
+
*/
|
|
121
|
+
extraPorts?: number[];
|
|
95
122
|
/**
|
|
96
123
|
* Free-form, namespaced. Adapter hints under `metadata.<adapter>.*`.
|
|
97
124
|
*/
|
|
@@ -317,7 +344,9 @@ declare const sandboxFrontmatterSchema: z.ZodObject<{
|
|
|
317
344
|
id: z.ZodOptional<z.ZodString>;
|
|
318
345
|
version: z.ZodOptional<z.ZodString>;
|
|
319
346
|
provider: z.ZodString;
|
|
320
|
-
config: z.
|
|
347
|
+
config: z.ZodObject<{
|
|
348
|
+
installAdapters: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
349
|
+
}, z.core.$catchall<z.ZodAny>>;
|
|
321
350
|
limits: z.ZodOptional<z.ZodObject<{
|
|
322
351
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
323
352
|
memory_mb: z.ZodOptional<z.ZodNumber>;
|
|
@@ -326,17 +355,20 @@ declare const sandboxFrontmatterSchema: z.ZodObject<{
|
|
|
326
355
|
env: z.ZodOptional<z.ZodObject<{
|
|
327
356
|
auth: z.ZodOptional<z.ZodAny>;
|
|
328
357
|
passthrough: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
358
|
+
autoPassthrough: z.ZodOptional<z.ZodBoolean>;
|
|
329
359
|
}, z.core.$strict>>;
|
|
330
360
|
network: z.ZodOptional<z.ZodObject<{
|
|
331
361
|
egress: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
332
362
|
}, z.core.$strict>>;
|
|
333
363
|
mounts: z.ZodDefault<z.ZodArray<z.ZodAny>>;
|
|
334
364
|
identity: z.ZodOptional<z.ZodAny>;
|
|
365
|
+
policy: z.ZodOptional<z.ZodAny>;
|
|
335
366
|
lifecycle: z.ZodOptional<z.ZodObject<{
|
|
336
367
|
pause_after_idle: z.ZodOptional<z.ZodString>;
|
|
337
368
|
destroy_on: z.ZodOptional<z.ZodString>;
|
|
338
369
|
}, z.core.$strict>>;
|
|
339
370
|
read_only: z.ZodDefault<z.ZodBoolean>;
|
|
371
|
+
extraPorts: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
340
372
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
341
373
|
}, z.core.$strict>;
|
|
342
374
|
type SandboxFrontmatter = z.infer<typeof sandboxFrontmatterSchema>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/sandbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "@agentproto/sandbox — AIP-36 SANDBOX.md reference implementation. A composable schema block defining the `sandbox` field — provider, config, command env, network egress, resource limits — for any manifest that names a compute environment for agent-issued shell commands. Sibling primitive to STORAGE.md (AIP-35); inline or ref, mirroring AIP-17 RUNNER and AIP-19 SECRETS. Also ships createSandboxAgentSessionHost — the provider-agnostic seam that runs an AgentStep's coding-agent turn inside a booted sandbox by pointing the existing daemon-backed AgentSessionHost at its MCP URL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -48,17 +48,17 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"gray-matter": "^4.0.3",
|
|
50
50
|
"zod": "^4.5.4",
|
|
51
|
-
"@agentproto/
|
|
52
|
-
"@agentproto/
|
|
53
|
-
"@agentproto/
|
|
54
|
-
"@agentproto/worktree": "0.
|
|
51
|
+
"@agentproto/secrets": "0.2.5",
|
|
52
|
+
"@agentproto/workflow-runtime": "0.11.0",
|
|
53
|
+
"@agentproto/define-doctype": "0.1.2",
|
|
54
|
+
"@agentproto/worktree": "0.6.2"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@types/node": "^25.
|
|
57
|
+
"@types/node": "^25.9.5",
|
|
58
58
|
"tsup": "^8.5.1",
|
|
59
59
|
"typescript": "^5.9.3",
|
|
60
|
-
"vitest": "^3.2.
|
|
61
|
-
"@agentproto/tooling": "0.1.0
|
|
60
|
+
"vitest": "^3.2.7",
|
|
61
|
+
"@agentproto/tooling": "0.1.0"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"dev": "tsup --watch",
|
package/dist/chunk-VSYQ4BUI.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { createDoctype } from '@agentproto/define-doctype';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @agentproto/sandbox v0.1.0-alpha
|
|
6
|
-
* AIP-36 SANDBOX.md `defineSandbox` reference implementation.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
var sandboxFrontmatterSchema = z.object({ "schema": z.literal("sandbox/v1").describe("Standalone-only. Identifies the doctype + version. Absent when the block is inlined.").optional(), "id": z.string().regex(new RegExp("^@[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*$")).describe("Standalone-only. Globally addressable id `@<owner-slug>/<sandbox-slug>`.").optional(), "version": z.string().regex(new RegExp("^\\d+\\.\\d+\\.\\d+(?:[-+][\\w.\\-]+)?$")).describe("Standalone-only. Spec version of THIS file.").optional(), "provider": z.string().min(1).describe("Backend kind. Day-1 enumerated set: local | mastra-e2b | mastra-modal | mastra-daytona | mastra-blaxel | node-permission. Hosts MAY register additional ids."), "config": z.record(z.string(), z.any()).describe("Provider-specific connection fields. Shape varies per provider (see AIP-36 \xA7Provider config shapes)."), "limits": z.object({ "timeout_ms": z.number().int().gte(1).optional(), "memory_mb": z.number().int().gte(1).optional(), "cpu_ms": z.number().int().gte(1).optional() }).strict().describe("Resource caps per command.").optional(), "env": z.object({ "auth": z.any().optional(), "passthrough": z.array(z.string()).describe("Static host env-var names to forward into the sandbox.").default([]) }).strict().optional(), "network": z.object({ "egress": z.array(z.string()).describe("Hostnames the sandbox MAY reach. Empty / missing = no egress.").default([]) }).strict().optional(), "mounts": z.array(z.any()).describe("Filesystems mounted inside the sandbox at declared paths. Maps to Mastra Workspace.mounts.").default([]), "identity": z.any().describe("AIP-23 identity-ref \u2014 owner of the sandbox processes.").optional(), "lifecycle": z.object({ "pause_after_idle": z.string().min(1).describe("AIP-37 event name (e.g. `idle-600` for 10 min). Provider-supported only (modal, daytona).").optional(), "destroy_on": z.string().min(1).describe("AIP-37 event name (e.g. `workspace-close`).").optional() }).strict().optional(), "read_only": z.boolean().describe("Reject command execution at the sandbox layer. Read-only sandbox calls fail with `sandbox_read_only`.").default(false), "metadata": z.record(z.string(), z.any()).describe("Free-form, namespaced. Adapter hints under `metadata.<adapter>.*`.").optional() }).strict().describe("Validates the YAML frontmatter portion of an AIP-36 SANDBOX.md manifest, OR the inline form embedded in any other manifest's `sandbox:` block. Compute-only \u2014 durable filesystem backings live in AIP-35 STORAGE.md.");
|
|
10
|
-
var defineSandboxInner = createDoctype({
|
|
11
|
-
aip: 36,
|
|
12
|
-
name: "sandbox",
|
|
13
|
-
readDescription: false,
|
|
14
|
-
// AIP-36 makes `id` standalone-only — inline sandbox blocks fall
|
|
15
|
-
// back to `provider` for the cross-AIP id-pattern check (mirrors
|
|
16
|
-
// AIP-43 § Identity).
|
|
17
|
-
readIdentity: (def) => {
|
|
18
|
-
if (typeof def.id === "string" && def.id.length > 0) return def.id;
|
|
19
|
-
return def.provider;
|
|
20
|
-
},
|
|
21
|
-
validate(def) {
|
|
22
|
-
const { factory: _factory, capabilities: _capabilities, ...manifest } = def;
|
|
23
|
-
const result = sandboxFrontmatterSchema.safeParse(manifest);
|
|
24
|
-
if (!result.success) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
`defineSandbox (AIP-36): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
build(def) {
|
|
31
|
-
const { factory, capabilities, ...manifest } = def;
|
|
32
|
-
return {
|
|
33
|
-
...manifest,
|
|
34
|
-
...factory !== void 0 ? { factory } : {},
|
|
35
|
-
...capabilities !== void 0 ? { capabilities: Object.freeze({ ...capabilities }) } : {}
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
function defineSandbox(definition) {
|
|
40
|
-
return defineSandboxInner(
|
|
41
|
-
definition
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export { defineSandbox, sandboxFrontmatterSchema };
|
|
46
|
-
//# sourceMappingURL=chunk-VSYQ4BUI.mjs.map
|
|
47
|
-
//# sourceMappingURL=chunk-VSYQ4BUI.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schema.ts","../src/define-sandbox.ts"],"names":[],"mappings":"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","file":"chunk-VSYQ4BUI.mjs","sourcesContent":["/**\n * AIP-36 SANDBOX.md frontmatter zod schema.\n *\n * Generated from `resources/aip-36/draft/SANDBOX.schema.json` via\n * json-schema-to-zod. Imported by both `define-sandbox.ts` (TS path\n * validation) and `manifest/index.ts` (.md path validation) so every\n * field-level constraint runs in both authoring paths from a single\n * source of truth — re-run scaffold-aip to refresh after spec changes.\n *\n * Cross-field rules (if/then/allOf in JSON Schema) don't translate\n * cleanly and live in `define-sandbox.ts`'s `validate(def)` instead.\n */\n\nimport { z } from \"zod\"\n\nexport const sandboxFrontmatterSchema = z.object({ \"schema\": z.literal(\"sandbox/v1\").describe(\"Standalone-only. Identifies the doctype + version. Absent when the block is inlined.\").optional(), \"id\": z.string().regex(new RegExp(\"^@[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*$\")).describe(\"Standalone-only. Globally addressable id `@<owner-slug>/<sandbox-slug>`.\").optional(), \"version\": z.string().regex(new RegExp(\"^\\\\d+\\\\.\\\\d+\\\\.\\\\d+(?:[-+][\\\\w.\\\\-]+)?$\")).describe(\"Standalone-only. Spec version of THIS file.\").optional(), \"provider\": z.string().min(1).describe(\"Backend kind. Day-1 enumerated set: local | mastra-e2b | mastra-modal | mastra-daytona | mastra-blaxel | node-permission. Hosts MAY register additional ids.\"), \"config\": z.record(z.string(), z.any()).describe(\"Provider-specific connection fields. Shape varies per provider (see AIP-36 §Provider config shapes).\"), \"limits\": z.object({ \"timeout_ms\": z.number().int().gte(1).optional(), \"memory_mb\": z.number().int().gte(1).optional(), \"cpu_ms\": z.number().int().gte(1).optional() }).strict().describe(\"Resource caps per command.\").optional(), \"env\": z.object({ \"auth\": z.any().optional(), \"passthrough\": z.array(z.string()).describe(\"Static host env-var names to forward into the sandbox.\").default([] as never) }).strict().optional(), \"network\": z.object({ \"egress\": z.array(z.string()).describe(\"Hostnames the sandbox MAY reach. Empty / missing = no egress.\").default([] as never) }).strict().optional(), \"mounts\": z.array(z.any()).describe(\"Filesystems mounted inside the sandbox at declared paths. Maps to Mastra Workspace.mounts.\").default([] as never), \"identity\": z.any().describe(\"AIP-23 identity-ref — owner of the sandbox processes.\").optional(), \"lifecycle\": z.object({ \"pause_after_idle\": z.string().min(1).describe(\"AIP-37 event name (e.g. `idle-600` for 10 min). Provider-supported only (modal, daytona).\").optional(), \"destroy_on\": z.string().min(1).describe(\"AIP-37 event name (e.g. `workspace-close`).\").optional() }).strict().optional(), \"read_only\": z.boolean().describe(\"Reject command execution at the sandbox layer. Read-only sandbox calls fail with `sandbox_read_only`.\").default(false), \"metadata\": z.record(z.string(), z.any()).describe(\"Free-form, namespaced. Adapter hints under `metadata.<adapter>.*`.\").optional() }).strict().describe(\"Validates the YAML frontmatter portion of an AIP-36 SANDBOX.md manifest, OR the inline form embedded in any other manifest's `sandbox:` block. Compute-only — durable filesystem backings live in AIP-35 STORAGE.md.\")\n\nexport type SandboxFrontmatter = z.infer<typeof sandboxFrontmatterSchema>\n","import { createDoctype } from \"@agentproto/define-doctype\"\nimport { sandboxFrontmatterSchema } from \"./schema.js\"\nimport type {\n SandboxRuntimeHandle,\n SandboxRuntimeInput,\n} from \"./types.js\"\n\n/**\n * AIP-36 reference implementation of `defineSandbox`.\n *\n * Built on `createDoctype` so the cross-AIP invariants (id pattern,\n * description length, top-level freeze, \"defineSandbox (AIP-36): …\"\n * error prefix) run uniformly with every other AIP defineX.\n *\n * Field-level validation runs the schema-derived zod from\n * `./schema.ts` against the manifest portion of the input. The\n * AIP-43 runtime slots (`factory`, `capabilities`) are HOST-OPAQUE\n * TS-runtime metadata stripped before validation and re-attached in\n * `build` — same pattern as `@agentproto/storage`'s `defineStorage`.\n *\n * Generic params:\n * TFactory — host-typed factory (e.g. `(input) => WorkspaceSandbox`\n * for the Guilde host). Defaults to `unknown`.\n * TCapabilities — opaque metadata the registry queries on. Per\n * AIP-43 § Capability metadata namespace.\n */\nconst defineSandboxInner = createDoctype<\n SandboxRuntimeInput,\n SandboxRuntimeHandle\n>({\n aip: 36,\n name: \"sandbox\",\n readDescription: false,\n // AIP-36 makes `id` standalone-only — inline sandbox blocks fall\n // back to `provider` for the cross-AIP id-pattern check (mirrors\n // AIP-43 § Identity).\n readIdentity: def => {\n if (typeof def.id === \"string\" && def.id.length > 0) return def.id\n return def.provider\n },\n validate(def) {\n const { factory: _factory, capabilities: _capabilities, ...manifest } = def\n const result = sandboxFrontmatterSchema.safeParse(manifest)\n if (!result.success) {\n throw new Error(\n `defineSandbox (AIP-36): ${result.error.issues\n .map(i => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \")}`,\n )\n }\n // TODO: spec-36-specific cross-field rules (if/then/allOf in\n // the JSON Schema) — those don't translate to zod cleanly and\n // belong here. See @agentproto/operator's autonomy=gated rule.\n },\n build(def) {\n const { factory, capabilities, ...manifest } = def\n return {\n ...manifest,\n ...(factory !== undefined ? { factory } : {}),\n ...(capabilities !== undefined\n ? { capabilities: Object.freeze({ ...capabilities }) }\n : {}),\n } as SandboxRuntimeHandle\n },\n})\n\n/**\n * Type-aware wrapper preserving `TFactory` / `TCapabilities` generics\n * across the call.\n */\nexport function defineSandbox<\n TFactory = unknown,\n TCapabilities extends Record<string, unknown> = Record<string, unknown>,\n>(\n definition: SandboxRuntimeInput<TFactory, TCapabilities>,\n): SandboxRuntimeHandle<TFactory, TCapabilities> {\n return defineSandboxInner(\n definition as SandboxRuntimeInput,\n ) as SandboxRuntimeHandle<TFactory, TCapabilities>\n}\n"]}
|