@agen-ai/sandbox 0.0.0-stage → 0.4.4
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/LICENSE +21 -0
- package/README.md +141 -2
- package/dist/acp/index.d.ts +9 -0
- package/dist/acp/index.js +25 -0
- package/dist/backend/contracts.d.ts +41 -0
- package/dist/backend/contracts.js +0 -0
- package/dist/backend/index.d.ts +6 -0
- package/dist/backend/index.js +64 -0
- package/dist/backend/spill.d.ts +15 -0
- package/dist/backend/spill.js +0 -0
- package/dist/contracts.d.ts +37 -0
- package/dist/contracts.js +0 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.js +73 -0
- package/dist/files/bytes.d.ts +31 -0
- package/dist/files/bytes.js +134 -0
- package/dist/files/iterator.d.ts +13 -0
- package/dist/files/iterator.js +59 -0
- package/dist/files/metadata/index.d.ts +12 -0
- package/dist/files/metadata/index.js +193 -0
- package/dist/files/metadata/records.d.ts +21 -0
- package/dist/files/metadata/records.js +50 -0
- package/dist/files/metadata/sort.d.ts +13 -0
- package/dist/files/metadata/sort.js +63 -0
- package/dist/files/metadata/storage.d.ts +12 -0
- package/dist/files/metadata/storage.js +69 -0
- package/dist/files/metadata/types.d.ts +19 -0
- package/dist/files/metadata/types.js +0 -0
- package/dist/files/parse.d.ts +20 -0
- package/dist/files/parse.js +62 -0
- package/dist/files/pathSchemas.d.ts +5 -0
- package/dist/files/pathSchemas.js +16 -0
- package/dist/files/paths.d.ts +4 -0
- package/dist/files/paths.js +12 -0
- package/dist/files/quotas.d.ts +18 -0
- package/dist/files/quotas.js +37 -0
- package/dist/files/types.d.ts +81 -0
- package/dist/files/types.js +0 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +35 -0
- package/dist/internal/data.d.ts +6 -0
- package/dist/internal/data.js +68 -0
- package/dist/internal/operations.d.ts +4 -0
- package/dist/internal/operations.js +30 -0
- package/dist/internal/parse.d.ts +3 -0
- package/dist/internal/parse.js +12 -0
- package/dist/lifecycle/deadline.d.ts +3 -0
- package/dist/lifecycle/deadline.js +18 -0
- package/dist/lifecycle/journal.d.ts +14 -0
- package/dist/lifecycle/journal.js +51 -0
- package/dist/lifecycle/observation.d.ts +9 -0
- package/dist/lifecycle/observation.js +41 -0
- package/dist/lifecycle/owner.d.ts +38 -0
- package/dist/lifecycle/owner.js +288 -0
- package/dist/lifecycle/recovered.d.ts +16 -0
- package/dist/lifecycle/recovered.js +62 -0
- package/dist/lifecycle/recoveryIdentity.d.ts +3 -0
- package/dist/lifecycle/recoveryIdentity.js +18 -0
- package/dist/lifecycle/transfer.d.ts +10 -0
- package/dist/lifecycle/transfer.js +339 -0
- package/dist/node/directories/closing.d.ts +2 -0
- package/dist/node/directories/closing.js +19 -0
- package/dist/node/directories/export.d.ts +6 -0
- package/dist/node/directories/export.js +43 -0
- package/dist/node/directories/file.d.ts +26 -0
- package/dist/node/directories/file.js +104 -0
- package/dist/node/directories/import.d.ts +6 -0
- package/dist/node/directories/import.js +38 -0
- package/dist/node/directories/options.d.ts +4 -0
- package/dist/node/directories/options.js +10 -0
- package/dist/node/directories/ownership.d.ts +33 -0
- package/dist/node/directories/ownership.js +148 -0
- package/dist/node/directories/source.d.ts +25 -0
- package/dist/node/directories/source.js +143 -0
- package/dist/node/directories/stage.d.ts +43 -0
- package/dist/node/directories/stage.js +302 -0
- package/dist/node/directories/types.d.ts +23 -0
- package/dist/node/directories/types.js +17 -0
- package/dist/node/hostPath.d.ts +7 -0
- package/dist/node/hostPath.js +37 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +14 -0
- package/dist/node/spill.d.ts +14 -0
- package/dist/node/spill.js +97 -0
- package/dist/node/spillDirectory.d.ts +4 -0
- package/dist/node/spillDirectory.js +17 -0
- package/dist/node/spillFile.d.ts +24 -0
- package/dist/node/spillFile.js +96 -0
- package/dist/policy/limits.d.ts +31 -0
- package/dist/policy/limits.js +35 -0
- package/dist/policy/parse.d.ts +11 -0
- package/dist/policy/parse.js +47 -0
- package/dist/policy/schemas.d.ts +148 -0
- package/dist/policy/schemas.js +75 -0
- package/dist/policy/types.d.ts +105 -0
- package/dist/policy/types.js +0 -0
- package/dist/process/input.d.ts +23 -0
- package/dist/process/input.js +93 -0
- package/dist/process/output.d.ts +13 -0
- package/dist/process/output.js +43 -0
- package/dist/process/parse.d.ts +4 -0
- package/dist/process/parse.js +32 -0
- package/dist/process/streams.d.ts +9 -0
- package/dist/process/streams.js +61 -0
- package/dist/process/types.d.ts +38 -0
- package/dist/process/types.js +0 -0
- package/dist/recovery/parse.d.ts +4 -0
- package/dist/recovery/parse.js +67 -0
- package/dist/recovery/types.d.ts +59 -0
- package/dist/recovery/types.js +0 -0
- package/dist/withSandbox.d.ts +4 -0
- package/dist/withSandbox.js +39 -0
- package/package.json +76 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Trevor Nichols
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,142 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @agen-ai/sandbox
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
Optional, provider-neutral ownership for one sandbox and one native process. This package is an unpublished member of the coordinated `0.4.4` candidate. It supplies policy, streaming tree, process and recovery contracts, shared lifecycle admission and `withSandbox`. It does not select an engine, image, provider, credentials or application authority.
|
|
4
|
+
|
|
5
|
+
The ordinary ESM root supports Node >=22 and has no ACP dependency. Backend implementations use the explicit `@agen-ai/sandbox/backend` seam. The optional `@agen-ai/sandbox-docker` package supplies the local Docker implementation. Backend and native-provider qualification remain specific to their recorded environments and configurations.
|
|
6
|
+
|
|
7
|
+
## Choose the entrypoint
|
|
8
|
+
|
|
9
|
+
| Entrypoint | Use |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `@agen-ai/sandbox` | Plain contracts, parsers, cleanup errors and `withSandbox`; no Docker or ACP dependency. |
|
|
12
|
+
| `@agen-ai/sandbox/backend` | Implement a backend through the shared lifecycle and validation owner. |
|
|
13
|
+
| `@agen-ai/sandbox/node` | Explicit directory import/export and caller-selected metadata spill storage; no implicit host paths. |
|
|
14
|
+
| `@agen-ai/sandbox/acp` | Compose an explicitly started sandbox with the optional `@agen-ai/provider-acp/node` constructor. |
|
|
15
|
+
| `@agen-ai/sandbox-docker` | Select the separately installed local Docker backend. |
|
|
16
|
+
|
|
17
|
+
Use matching `0.4.4` candidate artifacts for every selected SDK package. Published `0.4.3` does not include the sandbox packages. The standalone candidate includes a complete [Docker walkthrough](../../examples/sandbox/README.md), its runnable [example](../../examples/sandbox/docker.ts), and the reusable [session lifecycle](../../examples/sandbox/session.ts). `withSandboxAcpSession` and `SandboxAcpSessionError` belong to that example source; they are not npm package exports.
|
|
18
|
+
|
|
19
|
+
## Ownership
|
|
20
|
+
|
|
21
|
+
`backend.reserve(request, recovery)` is synchronous and inert. Keep the returned reservation before starting it. Startup failure preserves that cleanup owner through `SandboxStartError.owner`. A reservation has one start attempt; cancellation never authorizes replay of a command. `stop()` fences admissions and releases the complete compute boundary while retaining files. `destroy()` releases compute and retained storage. Concurrent cleanup joins its entered work; failed or uncertain cleanup keeps the same retry owner.
|
|
22
|
+
|
|
23
|
+
After startup, stream regular files and directories with `importTree` before calling `reserveProcess`. Public methods accept ordinary typed path strings and validate them at entry; callers may also use the parsers for prevalidation. Driver ports receive branded paths, captured immutable metadata and once-only controlled bodies. Process reservation closes imports synchronously. The process owns binary Node `stdin`, `stdout`, and `stderr`; its `exit` promise reports only the leader. Exact compute removal may lose that receipt, represented as `{ kind: 'unavailable', reason: 'sandbox_stopped' }` without fabricating a code or signal. Leader exit and stream EOF do not establish stopped descendants. The process has `stopScope: 'sandbox'`. Use callback-scoped `exportTree` only after complete stop and stream settlement. The returned guest `workspace` is authoritative; it conveys no host filesystem path.
|
|
24
|
+
|
|
25
|
+
`withSandbox(backend, request, async sandbox => result, { recovery })` always attempts to destroy the reservation. Export any required files inside the callback, after `sandbox.stop()` succeeds and streams settle. If the callback and cleanup both fail, `SandboxRunError` preserves `primaryError`, `cleanupError` and `owner`; `retryCleanup()` uses that same owner. A `SandboxCleanupError` independently retains its exact stop/destroy operation and `retryCleanup()`. Successful later cleanup never erases the original failure.
|
|
26
|
+
|
|
27
|
+
## Explicit policy and finite values
|
|
28
|
+
|
|
29
|
+
Construct a request with `parseSandboxRequest({ policy: { resources, temporaryStorage, network, deadlines, transfers, streams } })`. CPU count, memory bytes and PID count are explicit positive values. `temporaryStorage.homeBytes` and `tempBytes` are positive safe-integer resource requests without a universal 256 MiB ceiling. The backend must enforce the requested resources. `network.kind` is `isolated` or `outbound`; outbound means broad egress, not an allowlist or host/LAN exclusion.
|
|
30
|
+
|
|
31
|
+
Optional `execution` defaults to `{ kind: 'until_stopped' }`. No execution or idle timer is installed. Applications can choose `{ kind: 'deadline', timeoutMs }` with any positive safe-integer duration, including days and durations beyond Node's single-timer range. The deadline begins at process startup entry and owns the whole workload, including descendants. `withSandbox` additionally applies a chosen finite budget to its callback and always destroys the environment after callback completion. Until-stopped ownership does not promise controller or infrastructure crash survival.
|
|
32
|
+
|
|
33
|
+
`deadlines` contains independent `startupMs` and `cleanupMs` observations, bounded by 60 seconds and 30 seconds. File transfers instead accept an optional `deadlineAt` and `signal` on each call; omission installs no whole-transfer timer. Observer expiry never proves that entered work ended. Both retired `deadlines.executionMs` and `deadlines.operationMs` reject; migrate their intended policies explicitly.
|
|
34
|
+
|
|
35
|
+
`SANDBOX_DEFAULT_TRANSFER_BUFFERS` selects 64 KiB chunks, 64 KiB metadata batches and merge fan-in eight. These govern working memory, not file count or workspace size. `SANDBOX_DEFAULT_STREAM_LIMITS` selects bounded stdin and combined stdout/stderr staging plus a high-water mark, with no cumulative traffic quota. Optional `streams.maxStdinBytes` and `maxOutputBytes` are caller-selected totals; zero is meaningful. `maxBufferedInputBytes` rejects excess queued writes even when a caller ignores backpressure. Buffered overflow fails and initiates owned stop instead of truncating data. Backend transport buffers are accounted separately; these settings do not claim a total heap limit.
|
|
36
|
+
|
|
37
|
+
Process recipes retain their independent representation bounds: an absolute guest executable, literal arguments, explicit environment and working directory; 256 KiB recipe JSON, 128 arguments/environment entries and 16 KiB per environment value. No shell interpolation or inherited environment is supplied. Unknown fields and unsupported authority fail admission.
|
|
38
|
+
|
|
39
|
+
## Stream a workspace
|
|
40
|
+
|
|
41
|
+
`importTree(entries, options?)` accepts a synchronous or asynchronous iterable of directories and files. Files declare `byteLength`, `executable` and `open({ signal })`, which returns a once-only byte source. Each admitted body must reach actual EOF with exactly its declared bytes. Short, excess, oversized or invalid chunks reject. `fileFromBytes(path, bytes, options?)` is a convenience for bytes already owned by the caller: it borrows bounded views without copying the whole file. Keep the backing bytes unchanged until transfer completion.
|
|
42
|
+
|
|
43
|
+
`exportTree(selection, consume, options?)` selects `{ kind: 'workspace' }`, `{ kind: 'files', paths }`, or `{ kind: 'subtree', path }`. Exact regular-file paths may be streamed; duplicate paths and regular-file ancestor conflicts reject through bounded spill validation. A subtree selects one canonical relative directory and its descendants, with no general multiple-root or overlap-coalescing API. A backend must export the selected root directory exactly once, including for an empty subtree, and cannot deliver an entry outside that root. Directory/file output paths remain relative to the workspace: selecting `results/reports` includes directory `results/reports` and file `results/reports/summary.txt`, without stripping the selected root. The root may arrive after descendants; all output remains provisional until the operation succeeds. Exact-file and subtree requests must target their selection without scanning unrelated workspace content.
|
|
44
|
+
|
|
45
|
+
The consumer receives immutable metadata and a once-only byte iterable. Consume every declared byte and await entered reads before returning; concurrent reads reject. Empty files and an exact final chunk require no extra EOF-only pull.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { fileFromBytes } from '@agen-ai/sandbox';
|
|
49
|
+
|
|
50
|
+
await sandbox.importTree([
|
|
51
|
+
{ kind: 'directory', path: 'inputs' },
|
|
52
|
+
fileFromBytes('inputs/task.txt', new TextEncoder().encode('task input')),
|
|
53
|
+
]);
|
|
54
|
+
// Reserve/run the process, close its SDK session, then stop the whole workload.
|
|
55
|
+
await sandbox.stop();
|
|
56
|
+
await sandbox.exportTree({ kind: 'files', paths: ['results/result.txt'] }, async (entry) => {
|
|
57
|
+
if (entry.kind === 'file') {
|
|
58
|
+
for await (const chunk of entry.bytes) await applicationOwnedSink.write(chunk);
|
|
59
|
+
}
|
|
60
|
+
}, { quotas: { maxTotalBytes: 20n * 1024n ** 3n } });
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
All callback bytes are **provisional until the returned export promise succeeds**. Later malformed archive data, duplicate metadata or cleanup failure can invalidate the operation. An arbitrary application sink cannot be rolled back by the SDK; publish results only after complete success. Borrowed chunks remain valid until the next pull; copy any bytes retained beyond it. Sources must yield chunks no larger than the selected buffer size rather than one giant buffer.
|
|
64
|
+
|
|
65
|
+
Optional per-transfer `quotas` include `maxFiles`, `maxFileBytes`, `maxTotalBytes`, `maxArchiveBytes` and `maxPaths`. Nonnegative safe integers or bigint amounts are admitted; omitted fields impose no SDK logical ceiling. Actual aggregate counters and successful summaries use bigint. Archive quotas include headers/padding, independently of payload bytes. JSON serializers must explicitly convert bigint values to decimal strings. Relative paths retain their 1,024-byte, 64-component and 255-byte-per-component representation limits.
|
|
66
|
+
|
|
67
|
+
`maxPaths` counts each requested exact file and one requested subtree root; an explicit whole-workspace selection supplies no path roots. A subtree with `maxPaths: 0` rejects before backend export. Its summary counts the selected root among directories and counts only admitted descendants. Coverage and duplicate checks remain part of full-operation validation, so a missing root, late duplicate or malformed descendant invalidates provisional callback output.
|
|
68
|
+
|
|
69
|
+
The neutral metadata ledger uses bounded sorted batches, fixed merge fan-in and external run descriptors; it does not retain the whole path set or one open file per entry. Backends supply an explicit spill-storage port. The Node implementation opens exclusive files under a caller-selected canonical directory and unlinks them before writing; cleanup closes the handles, and controller death releases them through the OS. Disk exhaustion is an owned failure. A crash during the short create/unlink gap can leave an empty file. No environment-derived temp path is selected.
|
|
70
|
+
|
|
71
|
+
Only one transfer is admitted at a time, before any caller iterator is entered. Imports permanently close at first process reservation; export requires complete stopped compute. A late import failure may follow native writes, so it fences execution and is never automatically replayed. Source, callback, HTTP/parser and spill work remain owned until actual settlement, even after abort/deadline observation. A noncooperative callback can keep cleanup pending; failed spill closure retains the same retry owner.
|
|
72
|
+
|
|
73
|
+
Logical transfer quotas do not enforce guest workspace disk use. `policy.workspaceDiskQuota` is optional at input and normalizes to `{ kind: 'not_requested' }`. An application requiring allocated-block enforcement can explicitly request `{ kind: 'required', bytes: 20 * 1024 ** 3, accounting: 'allocated_blocks' }`. `bytes` must be a positive safe integer. The current backend support is `workspaceDiskQuota: false`; such a requirement throws `SandboxUnsupportedError` before driver reservation or native acquisition. It never silently becomes an ordinary volume. Effective policy reports `{ kind: 'not_requested' }` for the admitted baseline. No positive quota capability or inode/logical-file-size guarantee is claimed. `backend.support`, requested policy and observed `sandbox.effective` remain separate facts. Allowlist and independent-process authority remain unsupported.
|
|
74
|
+
|
|
75
|
+
## Import and publish host directories
|
|
76
|
+
|
|
77
|
+
The explicit Node helpers keep filesystem authority in `@agen-ai/sandbox/node`:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { importSandboxDirectory, exportSandboxDirectory } from '@agen-ai/sandbox/node';
|
|
81
|
+
|
|
82
|
+
await importSandboxDirectory(sandbox, '/absolute/quiescent/source');
|
|
83
|
+
// Run and close the agent, then stop all compute before exporting.
|
|
84
|
+
await sandbox.stop();
|
|
85
|
+
const exported = await exportSandboxDirectory(
|
|
86
|
+
sandbox, { kind: 'workspace' }, '/absolute/new-output',
|
|
87
|
+
);
|
|
88
|
+
console.log(exported.directory); // /absolute/new-output/files
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Imports traverse incrementally and read one regular file through its owned descriptor. Symlinks and special files reject. Descriptor/path identity and size checks detect replacement, truncation and growth. The caller must keep the trusted source hierarchy stable and quiescent; same-length concurrent edits are not an immutable snapshot, and portable Node does not defend against a hostile same-user writer replacing ancestors. Import options extend the ordinary transfer options with `chunkBytes`, defaulting to 64 KiB; choose a matching smaller value if the sandbox uses a smaller transfer chunk buffer.
|
|
92
|
+
|
|
93
|
+
Exports require an initially absent destination. The helper creates that directory exclusively as a private output namespace, stages inside it, and publishes its `files` child only after complete export validation. It returns `{ directory, summary }`; use the returned directory for the exported tree. Existing destinations are preserved. Keep the new namespace and its parent exclusively owned and stable until completion or cleanup. Publication relies on that ownership because portable Node rename has no atomic no-overwrite flag. File aliases reject through exclusive creation, while disk-backed directory identities prevent distinct logical paths from silently merging on filesystems with aliases. No in-memory tree census is required. File creation selects owner read/write and, when requested, execute permission; a restrictive process umask that removes the requested executable bit fails final validation instead of publishing the wrong metadata.
|
|
94
|
+
|
|
95
|
+
On failure, the helper closes callback admissions, joins entered filesystem work and removes only its owned staging. It never destroys the caller's sandbox. `SandboxDirectoryCleanupError` retains failed local cleanup and an explicit `retryCleanup()`; preserve that live owner until it succeeds. If publication succeeded before private metadata cleanup failed, `publishedDirectory` identifies the committed result and retry preserves those files. Local cleanup does not prove hidden backend transfer work settled. A noncooperative entered body keeps local cleanup pending; an observation deadline is not a guarantee of immediate filesystem cleanup. Atomic publication does not establish power-loss durability.
|
|
96
|
+
|
|
97
|
+
The standalone [filesystem example](../../examples/sandbox/filesystem.ts) compiles and runs against an installed neutral sandbox/validation-only consumer with explicit filesystem permissions.
|
|
98
|
+
|
|
99
|
+
## Recovery
|
|
100
|
+
|
|
101
|
+
Choose `{ kind: 'in_process' }` or `{ kind: 'persisted', persist: async record => ... }` explicitly. Every checkpoint must finish persistence before the next resource effect. Failure retains cleanup ownership and fences progression. Stop and destruction must remain possible after persistence fails; cleanup does not depend on a later checkpoint succeeding.
|
|
102
|
+
|
|
103
|
+
`parseSandboxRecoveryRecord` accepts only `agenai.sandbox-recovery.v2`, bounded identity/digest fields and at most four unique resource roles: `compute`, `storage`, `export`, `network`. The mandatory `configurationDigest` binds immutable backend configuration separately from the request `policyDigest`. Both remain unchanged across checkpoints. Version 1 records are rejected before backend reconciliation; retain the original compatible controller for any still-owned older resources. Names are assigned before effects. Each resource is `planned`, `creating` with entered but unresolved acquisition, or `identified` with exact ID and a SHA-256 incarnation/configuration fingerprint. Authority starts `unverified`; read-only preflight must establish a verified backend identity before any resource effect. The strict fixed envelope is at most 64 KiB serialized UTF-8 and has no environment, arbitrary payload or runnable session restoration.
|
|
104
|
+
|
|
105
|
+
A parsed record is data, never mutation permission. An explicitly configured backend must validate its original namespace/backend identity, resource incarnation and ownership before reconciliation. Reconciliation returns matching or uncertain cleanup ownership, a conflict, or conclusive absence. It never returns a runnable session. An unreachable backend and a missing response do not prove absence; name or label matches alone do not authorize adoption. Recovery never automatically replays execution.
|
|
106
|
+
|
|
107
|
+
## Backend implementation seam
|
|
108
|
+
|
|
109
|
+
`createSandboxBackend(driver)` supplies shared admission, validation, deadlines and retained cleanup ownership around one finite driver. `driver.reserve(request)` assigns inert identities and an initial record. Its handle has `start({ signal, checkpoint })`, `stop()` and `destroy()`. The ready port supplies effective facts, a guest workspace, an explicit spill store, streaming imports/exports and one inert process reservation. Stop must terminate the complete workload and unblock all owned raw streams. This seam carries no scheduler, plugin registry, host facility or generic resource hook.
|
|
110
|
+
|
|
111
|
+
## Optional ACP integration
|
|
112
|
+
|
|
113
|
+
Install the optional `@agen-ai/provider-acp` peer explicitly and import `createSandboxAcpIntegration` from `@agen-ai/sandbox/acp`. The helper returns a frozen `{ executionBoundary, host }` for the existing `createNodeAcpProviderDriver` constructor; it does not create a provider or session. Ordinary sandbox entrypoints have no dependency on this integration.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { createSandboxAcpIntegration } from '@agen-ai/sandbox/acp';
|
|
117
|
+
import { createNodeAcpProviderDriver } from '@agen-ai/provider-acp/node';
|
|
118
|
+
|
|
119
|
+
// Start the sandbox and finish its file imports before creating the integration.
|
|
120
|
+
const integration = createSandboxAcpIntegration(sandbox);
|
|
121
|
+
const driver = createNodeAcpProviderDriver({
|
|
122
|
+
providerKey,
|
|
123
|
+
...integration,
|
|
124
|
+
launch: {
|
|
125
|
+
executable: '/opt/agent/bin/agent',
|
|
126
|
+
args: ['--acp'],
|
|
127
|
+
env: {},
|
|
128
|
+
cwd: sandbox.workspace,
|
|
129
|
+
shutdownTimeoutMs: 15_000,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The executable, arguments and environment must describe the caller's explicitly prepared guest image. Empty `env` supplies no caller variables or inherited host values; the Docker backend supplies its fixed HOME, TMPDIR and PATH. Its image admission requires exactly those environment values, when present, and rejects additional image environment metadata; see the [Docker image requirements](../sandbox-docker/README.md#prepare-the-guest-image). Add only explicit process environment values compatible with the chosen backend. Credentials passed this way are readable by guest code and the Docker controller. Use the exact returned `sandbox.workspace` as the guest working directory for both the launch recipe and SDK session opening. A guest path conveys no host filesystem authority. The existing Node constructor retains its POSIX host admission rules. Set the sandbox cleanup observation to 30 seconds with the launch shutdown setting of 15 seconds so the provider's outer execution cleanup observation is compatible.
|
|
135
|
+
|
|
136
|
+
Provider materialization eagerly initializes the one native connection and may reserve and start the process before the first SDK session opens. Keep its registry or materialized instance and dispose it even when no session opens. One SDK session may make multiple turns on that connection. A second connection, additional session or authentication reconnect fails before acquiring another process and cannot release or reuse the first connection. A failed connection attempt does not authorize replay. Use a fresh sandbox and integration for another native connection.
|
|
137
|
+
|
|
138
|
+
Connection release awaits `sandbox.stop()`, which stops the complete sandbox workload and joins its streams while retaining workspace storage. Export selected files after SDK close succeeds, then explicitly destroy the sandbox. Failed cleanup remains owned by the existing provider execution owner and sandbox cleanup owner; retain their errors and retry handles. A cleanup observation deadline does not establish physical shutdown.
|
|
139
|
+
|
|
140
|
+
The returned frozen `host` is empty. Host filesystem reads/writes, terminal creation, authentication terminals, URL/browser interactions, forms and external-tool facilities are disabled. The native agent's guest tools and ordinary portable permission handlers remain available inside their existing scope. Supplying different host callbacks, MCP servers or runtime host bindings separately grants that caller's explicit host authority; this helper cannot constrain arbitrary host code or move those effects into the guest. Native-provider feature mappings and qualification remain the provider's responsibility.
|
|
141
|
+
|
|
142
|
+
The optional sandbox packages and bridge do not change Agent Protocol V13 or add execution-input or structured-output support to a provider. The Docker backend's [qualification scope](../sandbox-docker/README.md#qualification-scope) records the selected real backend and Grok composition; other providers, account routes and fleet execution require their own evidence.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AcpHostFacilities } from '@agen-ai/provider-acp';
|
|
2
|
+
import type { NodeAcpExecutionBoundary } from '@agen-ai/provider-acp/node';
|
|
3
|
+
import type { Sandbox } from '../contracts.js';
|
|
4
|
+
/** One native connection, with host facilities disabled and workspace retention after release. */
|
|
5
|
+
export declare function createSandboxAcpIntegration(sandbox: Sandbox): Readonly<{
|
|
6
|
+
executionBoundary: NodeAcpExecutionBoundary;
|
|
7
|
+
host: AcpHostFacilities;
|
|
8
|
+
}>;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SandboxStateError } from "../errors.js";
|
|
2
|
+
function createSandboxAcpIntegration(sandbox) {
|
|
3
|
+
let reserved = false;
|
|
4
|
+
const executionBoundary = Object.freeze({
|
|
5
|
+
reserve(input) {
|
|
6
|
+
if (reserved) throw new SandboxStateError("Sandbox ACP integration admits one native connection.");
|
|
7
|
+
reserved = true;
|
|
8
|
+
const process = sandbox.reserveProcess({ executable: input.executable, args: input.args, env: input.env, cwd: input.cwd });
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
async start(context) {
|
|
11
|
+
const { stdin, stdout, stderr } = await process.start({ signal: context.signal });
|
|
12
|
+
return Object.freeze({ stdin, stdout, stderr });
|
|
13
|
+
},
|
|
14
|
+
async release() {
|
|
15
|
+
await sandbox.stop();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const host = Object.freeze({});
|
|
21
|
+
return Object.freeze({ executionBoundary, host });
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
createSandboxAcpIntegration
|
|
25
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { OperationOptions } from '../contracts.js';
|
|
2
|
+
import type { SandboxGuestPath, SandboxInputTreeEntry, SandboxInputTreeSelection, SandboxExportConsumer, SandboxTransferBudget, SandboxTransferSummary } from '../files/types.js';
|
|
3
|
+
import type { SandboxEffectivePolicy, SandboxId, SandboxProcessId, SandboxRequest, SandboxSupport, SandboxTransferBuffers } from '../policy/types.js';
|
|
4
|
+
import type { SandboxProcessSpec, SandboxProcessStreams } from '../process/types.js';
|
|
5
|
+
import type { SandboxReconciliation, SandboxRecoveryRecord } from '../recovery/types.js';
|
|
6
|
+
import type { SandboxDriverSpillStore } from './spill.js';
|
|
7
|
+
export interface SandboxDriverTransferOptions {
|
|
8
|
+
readonly signal: AbortSignal;
|
|
9
|
+
readonly quotas: SandboxTransferBudget;
|
|
10
|
+
readonly buffers: SandboxTransferBuffers;
|
|
11
|
+
}
|
|
12
|
+
export interface SandboxDriverStartOptions extends OperationOptions {
|
|
13
|
+
checkpoint(record: SandboxRecoveryRecord): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface SandboxDriverProcessReservation {
|
|
16
|
+
readonly id: SandboxProcessId;
|
|
17
|
+
start(options?: OperationOptions): Promise<SandboxProcessStreams>;
|
|
18
|
+
}
|
|
19
|
+
export interface SandboxDriverReady {
|
|
20
|
+
readonly effective: SandboxEffectivePolicy;
|
|
21
|
+
readonly workspace: SandboxGuestPath;
|
|
22
|
+
openSpill(options?: OperationOptions): Promise<SandboxDriverSpillStore>;
|
|
23
|
+
importTree(entries: AsyncIterable<SandboxInputTreeEntry>, options: SandboxDriverTransferOptions): Promise<SandboxTransferSummary>;
|
|
24
|
+
reserveProcess(spec: SandboxProcessSpec): SandboxDriverProcessReservation;
|
|
25
|
+
exportTree(selection: SandboxInputTreeSelection, consume: SandboxExportConsumer, options: SandboxDriverTransferOptions): Promise<SandboxTransferSummary>;
|
|
26
|
+
}
|
|
27
|
+
export interface SandboxDriverReservation {
|
|
28
|
+
readonly id: SandboxId;
|
|
29
|
+
readonly recoveryRecord: SandboxRecoveryRecord;
|
|
30
|
+
start(options: SandboxDriverStartOptions): Promise<SandboxDriverReady>;
|
|
31
|
+
/** Cleanup must remain possible after persistence has failed. */
|
|
32
|
+
stop(): Promise<void>;
|
|
33
|
+
destroy(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export interface SandboxBackendDriver {
|
|
36
|
+
readonly support: SandboxSupport;
|
|
37
|
+
/** Synchronous and inert: reserve identities without acquiring resources. */
|
|
38
|
+
reserve(request: SandboxRequest): SandboxDriverReservation;
|
|
39
|
+
reconcile(record: SandboxRecoveryRecord, options?: OperationOptions): Promise<SandboxReconciliation>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SandboxBackend } from '../contracts.js';
|
|
2
|
+
import type { SandboxBackendDriver } from './contracts.js';
|
|
3
|
+
export type { SandboxBackendDriver, SandboxDriverReservation, SandboxDriverReady, SandboxDriverProcessReservation, SandboxDriverStartOptions, SandboxDriverTransferOptions } from './contracts.js';
|
|
4
|
+
export type { SandboxDriverSpillStore, SandboxDriverSpillFile } from './spill.js';
|
|
5
|
+
export declare function createSandboxBackend(driver: SandboxBackendDriver): SandboxBackend;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SandboxOperationError, SandboxStateError } from "../errors.js";
|
|
2
|
+
import { parseOperationOptions } from "../internal/operations.js";
|
|
3
|
+
import { actualOperation, observe } from "../lifecycle/observation.js";
|
|
4
|
+
import { SandboxOwner } from "../lifecycle/owner.js";
|
|
5
|
+
import { RecoveredSandboxOwner } from "../lifecycle/recovered.js";
|
|
6
|
+
import { assertRecoveryContinuation } from "../lifecycle/recoveryIdentity.js";
|
|
7
|
+
import { SANDBOX_LIMITS } from "../policy/limits.js";
|
|
8
|
+
import { assertSandboxRequestSupported, parseSandboxId, parseSandboxRequest, parseSandboxSupport } from "../policy/parse.js";
|
|
9
|
+
import { parseSandboxRecoveryPolicy, parseSandboxRecoveryRecord } from "../recovery/parse.js";
|
|
10
|
+
function createSandboxBackend(driver) {
|
|
11
|
+
const support = parseSandboxSupport(driver.support);
|
|
12
|
+
const reservations = /* @__PURE__ */ new WeakSet();
|
|
13
|
+
const backend = {
|
|
14
|
+
support,
|
|
15
|
+
reserve(request, recovery) {
|
|
16
|
+
const parsed = parseSandboxRequest(request);
|
|
17
|
+
const recoveryPolicy = parseSandboxRecoveryPolicy(recovery);
|
|
18
|
+
assertSandboxRequestSupported(parsed, support);
|
|
19
|
+
const reserved = driver.reserve(parsed);
|
|
20
|
+
if (!reserved || typeof reserved !== "object" || "then" in reserved || reservations.has(reserved) || typeof reserved.start !== "function" || typeof reserved.stop !== "function" || typeof reserved.destroy !== "function") {
|
|
21
|
+
throw new SandboxStateError("A backend must return a fresh synchronous inert reservation with cleanup ownership.");
|
|
22
|
+
}
|
|
23
|
+
reservations.add(reserved);
|
|
24
|
+
const id = parseSandboxId(reserved.id);
|
|
25
|
+
const record = parseSandboxRecoveryRecord(reserved.recoveryRecord);
|
|
26
|
+
if (record.sandboxId !== id || record.backendId !== support.backendId || record.backendRevision !== support.backendRevision || record.phase !== "reserved" || record.authority.kind !== "unverified" || record.resources.some((resource) => resource.identity.kind !== "planned")) {
|
|
27
|
+
throw new SandboxStateError("The initial recovery record must describe this reserved backend ownership before resource effects.");
|
|
28
|
+
}
|
|
29
|
+
return new SandboxOwner(reserved, parsed, recoveryPolicy, record);
|
|
30
|
+
},
|
|
31
|
+
async reconcile(record, options) {
|
|
32
|
+
const parsed = parseSandboxRecoveryRecord(record);
|
|
33
|
+
const operation = parseOperationOptions(options);
|
|
34
|
+
if (parsed.backendId !== support.backendId || parsed.backendRevision !== support.backendRevision) {
|
|
35
|
+
return Object.freeze({ kind: "conflict", reason: "Recovery record belongs to another backend identity or revision." });
|
|
36
|
+
}
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
if (operation.signal?.aborted) throw new SandboxOperationError("cancelled", "Sandbox reconciliation was cancelled before entry.");
|
|
39
|
+
const actual = actualOperation(() => driver.reconcile(parsed, { signal: controller.signal }));
|
|
40
|
+
const result = await observe(actual, SANDBOX_LIMITS.startupMs, { ...operation, onInterrupt: (error) => controller.abort(error) });
|
|
41
|
+
if (result.kind === "matched" || result.kind === "uncertain") {
|
|
42
|
+
const owner = new RecoveredSandboxOwner(result.owner, parsed);
|
|
43
|
+
void owner.recoveryRecord;
|
|
44
|
+
return Object.freeze({ kind: result.kind, owner });
|
|
45
|
+
}
|
|
46
|
+
if (result.kind === "absent") {
|
|
47
|
+
const absent = parseSandboxRecoveryRecord(result.record);
|
|
48
|
+
assertRecoveryContinuation(parsed, absent, "reconciliation");
|
|
49
|
+
if (absent.resources.some((resource) => resource.identity.kind === "creating")) {
|
|
50
|
+
throw new SandboxStateError("An entered acquisition with unresolved identity cannot be conclusively absent.");
|
|
51
|
+
}
|
|
52
|
+
return Object.freeze({ kind: "absent", record: absent });
|
|
53
|
+
}
|
|
54
|
+
if (result.kind === "conflict" && typeof result.reason === "string" && result.reason.length > 0 && result.reason.length <= 1024) {
|
|
55
|
+
return Object.freeze({ kind: "conflict", reason: result.reason });
|
|
56
|
+
}
|
|
57
|
+
throw new SandboxStateError("Backend returned an invalid reconciliation result.");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
return Object.freeze(backend);
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
createSandboxBackend
|
|
64
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SandboxDriverSpillFile {
|
|
2
|
+
read(buffer: Uint8Array, offset: number, length: number, position: number): Promise<Readonly<{
|
|
3
|
+
bytesRead: number;
|
|
4
|
+
}>>;
|
|
5
|
+
write(buffer: Uint8Array, offset: number, length: number, position: number): Promise<Readonly<{
|
|
6
|
+
bytesWritten: number;
|
|
7
|
+
}>>;
|
|
8
|
+
truncate(size: number): Promise<void>;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface SandboxDriverSpillStore {
|
|
12
|
+
createFile(): Promise<SandboxDriverSpillFile>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=spill.d.ts.map
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SandboxGuestPath, SandboxTreeSource, SandboxTreeSelection, SandboxExportConsumer, SandboxTransferOptions, SandboxTransferSummary } from './files/types.js';
|
|
2
|
+
import type { SandboxEffectivePolicy, SandboxId, SandboxRequestInput, SandboxSupport } from './policy/types.js';
|
|
3
|
+
import type { SandboxProcessReservation, SandboxProcessInput } from './process/types.js';
|
|
4
|
+
import type { SandboxCleanupOwner, SandboxReconciliation, SandboxRecoveryPolicy, SandboxRecoveryRecord } from './recovery/types.js';
|
|
5
|
+
export interface OperationOptions {
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export type SandboxState = Readonly<{
|
|
9
|
+
kind: 'reserved' | 'starting' | 'ready' | 'stopping' | 'stopped' | 'destroying' | 'destroyed';
|
|
10
|
+
}> | Readonly<{
|
|
11
|
+
kind: 'failed' | 'uncertain';
|
|
12
|
+
operation: 'start' | 'import' | 'process' | 'export' | 'stop' | 'destroy';
|
|
13
|
+
}>;
|
|
14
|
+
export interface SandboxBackend {
|
|
15
|
+
readonly support: SandboxSupport;
|
|
16
|
+
reserve(request: SandboxRequestInput, recovery: SandboxRecoveryPolicy): SandboxReservation;
|
|
17
|
+
reconcile(record: SandboxRecoveryRecord, options?: OperationOptions): Promise<SandboxReconciliation>;
|
|
18
|
+
}
|
|
19
|
+
export interface SandboxReservation extends SandboxCleanupOwner {
|
|
20
|
+
readonly id: SandboxId;
|
|
21
|
+
readonly state: SandboxState;
|
|
22
|
+
start(options?: OperationOptions): Promise<Sandbox>;
|
|
23
|
+
}
|
|
24
|
+
export interface Sandbox extends SandboxCleanupOwner {
|
|
25
|
+
readonly id: SandboxId;
|
|
26
|
+
readonly state: SandboxState;
|
|
27
|
+
readonly effective: SandboxEffectivePolicy;
|
|
28
|
+
readonly workspace: SandboxGuestPath;
|
|
29
|
+
importTree(entries: SandboxTreeSource, options?: SandboxTransferOptions): Promise<SandboxTransferSummary>;
|
|
30
|
+
reserveProcess(spec: SandboxProcessInput): SandboxProcessReservation;
|
|
31
|
+
exportTree(selection: SandboxTreeSelection, consume: SandboxExportConsumer, options?: SandboxTransferOptions): Promise<SandboxTransferSummary>;
|
|
32
|
+
}
|
|
33
|
+
export interface SandboxRunOptions extends OperationOptions {
|
|
34
|
+
readonly recovery: SandboxRecoveryPolicy;
|
|
35
|
+
}
|
|
36
|
+
export type { SandboxRecoveryRecord };
|
|
37
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
File without changes
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ValidationIssue } from '@agen-ai/validation';
|
|
2
|
+
import type { SandboxCleanupOwner } from './recovery/types.js';
|
|
3
|
+
export type SandboxOperationErrorCode = 'cancelled' | 'deadline_exceeded' | 'output_limit' | 'input_limit' | 'stream_failure' | 'persistence_failure' | 'driver_failure';
|
|
4
|
+
export declare class SandboxOperationError extends Error {
|
|
5
|
+
readonly code: SandboxOperationErrorCode;
|
|
6
|
+
constructor(code: SandboxOperationErrorCode, message: string, options?: ErrorOptions);
|
|
7
|
+
}
|
|
8
|
+
export declare class SandboxValidationError extends Error {
|
|
9
|
+
readonly issues: readonly ValidationIssue[];
|
|
10
|
+
constructor(issues: readonly ValidationIssue[]);
|
|
11
|
+
}
|
|
12
|
+
export declare class SandboxUnsupportedError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class SandboxStateError extends Error {
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class SandboxCleanupError extends Error {
|
|
19
|
+
readonly owner: SandboxCleanupOwner;
|
|
20
|
+
readonly operation: 'stop' | 'destroy';
|
|
21
|
+
constructor(message: string, options: {
|
|
22
|
+
cause: unknown;
|
|
23
|
+
owner: SandboxCleanupOwner;
|
|
24
|
+
operation: 'stop' | 'destroy';
|
|
25
|
+
});
|
|
26
|
+
retryCleanup(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare class SandboxStartError extends Error {
|
|
29
|
+
readonly owner: SandboxCleanupOwner;
|
|
30
|
+
constructor(message: string, options: {
|
|
31
|
+
cause: unknown;
|
|
32
|
+
owner: SandboxCleanupOwner;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export declare class SandboxRunError extends Error {
|
|
36
|
+
readonly primaryError: unknown;
|
|
37
|
+
readonly cleanupError: unknown;
|
|
38
|
+
readonly owner: SandboxCleanupOwner;
|
|
39
|
+
constructor(options: {
|
|
40
|
+
primaryError: unknown;
|
|
41
|
+
cleanupError: unknown;
|
|
42
|
+
owner: SandboxCleanupOwner;
|
|
43
|
+
});
|
|
44
|
+
retryCleanup(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
class SandboxOperationError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = "SandboxOperationError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
class SandboxValidationError extends Error {
|
|
10
|
+
issues;
|
|
11
|
+
constructor(issues) {
|
|
12
|
+
super("Invalid sandbox input");
|
|
13
|
+
this.name = "SandboxValidationError";
|
|
14
|
+
this.issues = Object.freeze(issues.map((issue) => Object.freeze({ ...issue, path: Object.freeze([...issue.path]) })));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
class SandboxUnsupportedError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "SandboxUnsupportedError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class SandboxStateError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "SandboxStateError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class SandboxCleanupError extends Error {
|
|
30
|
+
owner;
|
|
31
|
+
operation;
|
|
32
|
+
constructor(message, options) {
|
|
33
|
+
super(message, { cause: options.cause });
|
|
34
|
+
this.name = "SandboxCleanupError";
|
|
35
|
+
this.owner = options.owner;
|
|
36
|
+
this.operation = options.operation;
|
|
37
|
+
}
|
|
38
|
+
retryCleanup() {
|
|
39
|
+
return this.owner[this.operation]();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
class SandboxStartError extends Error {
|
|
43
|
+
owner;
|
|
44
|
+
constructor(message, options) {
|
|
45
|
+
super(message, { cause: options.cause });
|
|
46
|
+
this.name = "SandboxStartError";
|
|
47
|
+
this.owner = options.owner;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
class SandboxRunError extends Error {
|
|
51
|
+
primaryError;
|
|
52
|
+
cleanupError;
|
|
53
|
+
owner;
|
|
54
|
+
constructor(options) {
|
|
55
|
+
super("Sandbox operation and cleanup both failed", { cause: options.primaryError });
|
|
56
|
+
this.name = "SandboxRunError";
|
|
57
|
+
this.primaryError = options.primaryError;
|
|
58
|
+
this.cleanupError = options.cleanupError;
|
|
59
|
+
this.owner = options.owner;
|
|
60
|
+
}
|
|
61
|
+
retryCleanup() {
|
|
62
|
+
return this.owner.destroy();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
SandboxCleanupError,
|
|
67
|
+
SandboxOperationError,
|
|
68
|
+
SandboxRunError,
|
|
69
|
+
SandboxStartError,
|
|
70
|
+
SandboxStateError,
|
|
71
|
+
SandboxUnsupportedError,
|
|
72
|
+
SandboxValidationError
|
|
73
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SandboxByteSource, SandboxTreeEntry } from './types.js';
|
|
2
|
+
export declare class TransferBody implements AsyncIterable<Uint8Array> {
|
|
3
|
+
private readonly chunkBytes;
|
|
4
|
+
private readonly requireEof;
|
|
5
|
+
private readonly signal;
|
|
6
|
+
private open;
|
|
7
|
+
private readonly interrupt;
|
|
8
|
+
private source;
|
|
9
|
+
private used;
|
|
10
|
+
private retired;
|
|
11
|
+
private remaining;
|
|
12
|
+
private eof;
|
|
13
|
+
private pending;
|
|
14
|
+
private failure;
|
|
15
|
+
constructor(byteLength: number, chunkBytes: number, requireEof: boolean, signal: AbortSignal, open: (() => SandboxByteSource) | undefined, interrupt: (error: unknown) => void);
|
|
16
|
+
get complete(): boolean;
|
|
17
|
+
[Symbol.asyncIterator](): AsyncIterator<Uint8Array>;
|
|
18
|
+
private rejectIterator;
|
|
19
|
+
private next;
|
|
20
|
+
assertComplete(): void;
|
|
21
|
+
rejectReuse(): never;
|
|
22
|
+
close(retry?: boolean): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** Borrows caller-owned bytes; keep them unchanged until the transfer settles. Yields bounded views. */
|
|
25
|
+
export declare function fileFromBytes(path: string, bytes: Uint8Array, options?: Readonly<{
|
|
26
|
+
executable?: boolean;
|
|
27
|
+
chunkBytes?: number;
|
|
28
|
+
}>): Extract<SandboxTreeEntry, {
|
|
29
|
+
kind: 'file';
|
|
30
|
+
}>;
|
|
31
|
+
//# sourceMappingURL=bytes.d.ts.map
|