@tanstack/ai-sandbox 0.3.4 → 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 +26 -0
- package/dist/esm/checkpoint-store.d.ts +147 -0
- package/dist/esm/checkpoint-store.js +267 -0
- package/dist/esm/checkpoint-store.js.map +1 -0
- package/dist/esm/contracts.d.ts +19 -0
- package/dist/esm/index.d.ts +11 -1
- package/dist/esm/index.js +12 -7
- package/dist/esm/memory-snapshot-types.d.ts +129 -0
- package/dist/esm/memory-snapshots.d.ts +6 -0
- package/dist/esm/memory-snapshots.js +490 -0
- package/dist/esm/memory-snapshots.js.map +1 -0
- package/dist/esm/middleware.d.ts +33 -1
- package/dist/esm/middleware.js +339 -94
- package/dist/esm/middleware.js.map +1 -1
- package/dist/esm/ngrok.d.ts +1 -1
- package/dist/esm/sandbox.d.ts +16 -0
- package/dist/esm/sandbox.js +62 -9
- package/dist/esm/sandbox.js.map +1 -1
- package/dist/esm/snapshot-operations.d.ts +65 -0
- package/dist/esm/snapshot-operations.js +317 -0
- package/dist/esm/snapshot-operations.js.map +1 -0
- package/dist/esm/snapshot-tools.d.ts +185 -0
- package/dist/esm/snapshot-tools.js +160 -0
- package/dist/esm/snapshot-tools.js.map +1 -0
- package/dist/esm/snapshots.d.ts +51 -0
- package/dist/esm/snapshots.js +350 -0
- package/dist/esm/snapshots.js.map +1 -0
- package/dist/esm/testkit/checkpoint-conformance.d.ts +2 -0
- package/dist/esm/testkit/checkpoint-conformance.js +453 -0
- package/dist/esm/testkit/checkpoint-conformance.js.map +1 -0
- package/dist/esm/testkit/checkpoint-fork-conformance.d.ts +18 -0
- package/dist/esm/testkit/checkpoint-fork-conformance.js +191 -0
- package/dist/esm/testkit/checkpoint-fork-conformance.js.map +1 -0
- package/dist/esm/testkit/conformance.d.ts +4 -0
- package/dist/esm/testkit/conformance.js +3 -1
- package/dist/esm/testkit/conformance.js.map +1 -1
- package/package.json +8 -3
- package/skills/ai-sandbox/SKILL.md +96 -8
- package/src/checkpoint-store.ts +652 -0
- package/src/contracts.ts +12 -0
- package/src/index.ts +56 -0
- package/src/memory-snapshot-types.ts +167 -0
- package/src/memory-snapshots.ts +936 -0
- package/src/middleware.ts +610 -160
- package/src/sandbox.ts +107 -6
- package/src/snapshot-operations.ts +540 -0
- package/src/snapshot-tools.ts +208 -0
- package/src/snapshots.ts +711 -0
- package/src/testkit/checkpoint-conformance.ts +472 -0
- package/src/testkit/checkpoint-fork-conformance.ts +299 -0
- package/src/testkit/conformance.ts +7 -0
package/README.md
CHANGED
|
@@ -137,6 +137,30 @@ lifecycle: {
|
|
|
137
137
|
}
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
+
### Portable snapshots
|
|
141
|
+
|
|
142
|
+
Use portable snapshots when a later run must rebuild completed files after the
|
|
143
|
+
provider sandbox is gone. Create one snapshots object, pass it to
|
|
144
|
+
`withPersistence` and `withSandbox`, and put `withPersistence` first.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { withPersistence } from '@tanstack/ai-persistence'
|
|
148
|
+
import { memorySandboxSnapshots, withSandbox } from '@tanstack/ai-sandbox'
|
|
149
|
+
|
|
150
|
+
const snapshots = await memorySandboxSnapshots({ sandbox, instances })
|
|
151
|
+
|
|
152
|
+
const middleware = [
|
|
153
|
+
withPersistence(snapshots.persistence),
|
|
154
|
+
withSandbox(sandbox, { instances, snapshots }),
|
|
155
|
+
]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
A successful terminal run saves regular files, empty directories, saved
|
|
159
|
+
conversation data, and thread artifacts. Restore runs only in a new private
|
|
160
|
+
sandbox. A live resumed sandbox keeps its current files. Read
|
|
161
|
+
[Keep Files After Reload](https://tanstack.com/ai/latest/docs/sandbox/portable-snapshots-configure)
|
|
162
|
+
for the full server setup.
|
|
163
|
+
|
|
140
164
|
### Secrets
|
|
141
165
|
|
|
142
166
|
Use `createSecrets()` so values stay behind opaque `SecretRef` tokens. They are never written to snapshots, the sandbox store, or event logs. The sandbox layer resolves them onto the live handle at create, resume, and snapshot restore:
|
|
@@ -178,6 +202,8 @@ Full guides on [tanstack.com/ai](https://tanstack.com/ai/latest/docs/sandbox/ove
|
|
|
178
202
|
- [Policy](https://tanstack.com/ai/latest/docs/sandbox/policy)
|
|
179
203
|
- [Tools](https://tanstack.com/ai/latest/docs/sandbox/tools) (host tool bridge)
|
|
180
204
|
- [Lifecycle & snapshots](https://tanstack.com/ai/latest/docs/sandbox/lifecycle)
|
|
205
|
+
- [Portable sandbox snapshots](https://tanstack.com/ai/latest/docs/sandbox/portable-snapshots)
|
|
206
|
+
- [Pick which files to keep](https://tanstack.com/ai/latest/docs/sandbox/portable-snapshots-files)
|
|
181
207
|
|
|
182
208
|
## Examples
|
|
183
209
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ModelMessage } from '@tanstack/ai';
|
|
2
|
+
export interface SandboxSnapshotFileEntry {
|
|
3
|
+
path: string;
|
|
4
|
+
kind: 'file';
|
|
5
|
+
blobKey: string;
|
|
6
|
+
size: number;
|
|
7
|
+
}
|
|
8
|
+
export interface SandboxSnapshotDirectoryEntry {
|
|
9
|
+
path: string;
|
|
10
|
+
kind: 'dir';
|
|
11
|
+
}
|
|
12
|
+
export type SandboxSnapshotEntry = SandboxSnapshotFileEntry | SandboxSnapshotDirectoryEntry;
|
|
13
|
+
export interface SandboxSnapshotArtifact {
|
|
14
|
+
artifactId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
size: number;
|
|
18
|
+
blobKey: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
}
|
|
21
|
+
export interface SandboxCheckpoint {
|
|
22
|
+
id: string;
|
|
23
|
+
threadId: string;
|
|
24
|
+
parentCheckpointId: string | null;
|
|
25
|
+
createdAt: number;
|
|
26
|
+
reason: 'automatic' | 'named' | 'fork-root';
|
|
27
|
+
label?: string;
|
|
28
|
+
sourceRunId?: string;
|
|
29
|
+
files: ReadonlyArray<SandboxSnapshotEntry>;
|
|
30
|
+
conversation: ReadonlyArray<ModelMessage>;
|
|
31
|
+
artifacts: ReadonlyArray<SandboxSnapshotArtifact>;
|
|
32
|
+
}
|
|
33
|
+
export interface SandboxCheckpointStore {
|
|
34
|
+
get: (id: string) => Promise<SandboxCheckpoint | null>;
|
|
35
|
+
list: (threadId: string) => Promise<Array<SandboxCheckpoint>>;
|
|
36
|
+
getHead: (threadId: string) => Promise<string | null>;
|
|
37
|
+
append: (input: {
|
|
38
|
+
checkpoint: SandboxCheckpoint;
|
|
39
|
+
expectedHeadId: string | null;
|
|
40
|
+
writer: SandboxCheckpointWriter;
|
|
41
|
+
}) => Promise<{
|
|
42
|
+
headId: string;
|
|
43
|
+
}>;
|
|
44
|
+
deleteHead: (input: {
|
|
45
|
+
threadId: string;
|
|
46
|
+
checkpointId: string;
|
|
47
|
+
writer: SandboxCheckpointWriter;
|
|
48
|
+
}) => Promise<void>;
|
|
49
|
+
acquireWriter: (threadId: string) => Promise<SandboxCheckpointWriterLease>;
|
|
50
|
+
listBlobReferences: () => Promise<Array<{
|
|
51
|
+
key: string;
|
|
52
|
+
references: number;
|
|
53
|
+
}>>;
|
|
54
|
+
/** Optional atomic fork capability. Stores without this method cannot fork. */
|
|
55
|
+
forkFromCheckpoint?: SandboxCheckpointForkCapability['forkFromCheckpoint'];
|
|
56
|
+
}
|
|
57
|
+
export interface SandboxCheckpointForkInput {
|
|
58
|
+
sourceThreadId: string;
|
|
59
|
+
sourceCheckpointId: string;
|
|
60
|
+
destinationThreadId: string;
|
|
61
|
+
destinationCheckpointId: string;
|
|
62
|
+
createdAt: number;
|
|
63
|
+
writer: SandboxCheckpointWriter;
|
|
64
|
+
}
|
|
65
|
+
export interface SandboxCheckpointForkCapability {
|
|
66
|
+
forkFromCheckpoint: (input: SandboxCheckpointForkInput) => Promise<{
|
|
67
|
+
checkpoint: SandboxCheckpoint;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
export type ForkCapableSandboxCheckpointStore = SandboxCheckpointStore & SandboxCheckpointForkCapability;
|
|
71
|
+
export declare function isForkCapableSandboxCheckpointStore(store: SandboxCheckpointStore): store is ForkCapableSandboxCheckpointStore;
|
|
72
|
+
export interface SandboxCheckpointWriter {
|
|
73
|
+
threadId: string;
|
|
74
|
+
ownerToken: string;
|
|
75
|
+
fence: number;
|
|
76
|
+
}
|
|
77
|
+
export interface SandboxCheckpointWriterLease extends SandboxCheckpointWriter {
|
|
78
|
+
expiresAt: number;
|
|
79
|
+
renewAfterMs: number;
|
|
80
|
+
renew: () => Promise<{
|
|
81
|
+
expiresAt: number;
|
|
82
|
+
}>;
|
|
83
|
+
release: () => Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
export interface SandboxCheckpointStoreOptions {
|
|
86
|
+
now?: () => number;
|
|
87
|
+
leaseDurationMs?: number;
|
|
88
|
+
renewAfterMs?: number;
|
|
89
|
+
}
|
|
90
|
+
export type SandboxCheckpointErrorCode = 'SANDBOX_SNAPSHOT_STALE_HEAD' | 'SANDBOX_SNAPSHOT_PARENT_MISMATCH' | 'SANDBOX_SNAPSHOT_DUPLICATE_ID' | 'SANDBOX_SNAPSHOT_NOT_HEAD' | 'SANDBOX_SNAPSHOT_WRITER_CONFLICT' | 'SANDBOX_SNAPSHOT_WRITER_LOST' | 'SANDBOX_SNAPSHOT_INVALID_ID' | 'SANDBOX_SNAPSHOT_INVALID_ENTRY' | 'SANDBOX_SNAPSHOT_CHECKPOINT_NOT_FOUND' | 'SANDBOX_SNAPSHOT_ATOMIC_FORK_REQUIRED' | 'SANDBOX_SNAPSHOT_FORK_SOURCE_NOT_FOUND' | 'SANDBOX_SNAPSHOT_FORK_SOURCE_THREAD_MISMATCH' | 'SANDBOX_SNAPSHOT_FORK_DESTINATION_NOT_EMPTY';
|
|
91
|
+
export declare class SandboxCheckpointError extends Error {
|
|
92
|
+
readonly code: SandboxCheckpointErrorCode;
|
|
93
|
+
constructor(code: SandboxCheckpointErrorCode, message: string);
|
|
94
|
+
}
|
|
95
|
+
export declare class SandboxCheckpointConflictError extends SandboxCheckpointError {
|
|
96
|
+
constructor(message: string);
|
|
97
|
+
}
|
|
98
|
+
export declare class SandboxCheckpointDuplicateIdError extends SandboxCheckpointError {
|
|
99
|
+
constructor(message: string);
|
|
100
|
+
}
|
|
101
|
+
export declare class SandboxCheckpointInvalidIdError extends SandboxCheckpointError {
|
|
102
|
+
constructor(message: string);
|
|
103
|
+
}
|
|
104
|
+
export declare class SandboxCheckpointInvalidEntryError extends SandboxCheckpointError {
|
|
105
|
+
constructor(message: string);
|
|
106
|
+
}
|
|
107
|
+
export declare class SandboxCheckpointParentMismatchError extends SandboxCheckpointError {
|
|
108
|
+
constructor(message: string);
|
|
109
|
+
}
|
|
110
|
+
export declare class SandboxCheckpointNotHeadError extends SandboxCheckpointError {
|
|
111
|
+
constructor(message: string);
|
|
112
|
+
}
|
|
113
|
+
export declare class SandboxCheckpointWriterConflictError extends SandboxCheckpointError {
|
|
114
|
+
constructor(message: string);
|
|
115
|
+
}
|
|
116
|
+
export declare class SandboxCheckpointWriterLostError extends SandboxCheckpointError {
|
|
117
|
+
constructor(message: string);
|
|
118
|
+
}
|
|
119
|
+
export declare function defineSandboxCheckpointStore(store: SandboxCheckpointStore): SandboxCheckpointStore;
|
|
120
|
+
export declare class InMemorySandboxCheckpointStore implements SandboxCheckpointStore {
|
|
121
|
+
private readonly state;
|
|
122
|
+
private readonly now;
|
|
123
|
+
private readonly leaseDurationMs;
|
|
124
|
+
private readonly renewAfterMs;
|
|
125
|
+
constructor(options?: SandboxCheckpointStoreOptions);
|
|
126
|
+
get(id: string): Promise<SandboxCheckpoint | null>;
|
|
127
|
+
list(threadId: string): Promise<Array<SandboxCheckpoint>>;
|
|
128
|
+
getHead(threadId: string): Promise<string | null>;
|
|
129
|
+
append(input: {
|
|
130
|
+
checkpoint: SandboxCheckpoint;
|
|
131
|
+
expectedHeadId: string | null;
|
|
132
|
+
writer: SandboxCheckpointWriter;
|
|
133
|
+
}): Promise<{
|
|
134
|
+
headId: string;
|
|
135
|
+
}>;
|
|
136
|
+
deleteHead(input: {
|
|
137
|
+
threadId: string;
|
|
138
|
+
checkpointId: string;
|
|
139
|
+
writer: SandboxCheckpointWriter;
|
|
140
|
+
}): Promise<void>;
|
|
141
|
+
acquireWriter(threadId: string): Promise<SandboxCheckpointWriterLease>;
|
|
142
|
+
private assertWriter;
|
|
143
|
+
listBlobReferences(): Promise<Array<{
|
|
144
|
+
key: string;
|
|
145
|
+
references: number;
|
|
146
|
+
}>>;
|
|
147
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
//#region src/checkpoint-store.ts
|
|
2
|
+
var utf8Encoder = new TextEncoder();
|
|
3
|
+
var compareUtf8Bytes = (left, right) => {
|
|
4
|
+
const leftBytes = utf8Encoder.encode(left);
|
|
5
|
+
const rightBytes = utf8Encoder.encode(right);
|
|
6
|
+
const length = Math.min(leftBytes.length, rightBytes.length);
|
|
7
|
+
for (let index = 0; index < length; index++) {
|
|
8
|
+
const leftByte = leftBytes[index];
|
|
9
|
+
const rightByte = rightBytes[index];
|
|
10
|
+
if (leftByte !== rightByte) return (leftByte ?? 0) - (rightByte ?? 0);
|
|
11
|
+
}
|
|
12
|
+
return leftBytes.length - rightBytes.length;
|
|
13
|
+
};
|
|
14
|
+
function isForkCapableSandboxCheckpointStore(store) {
|
|
15
|
+
return typeof store.forkFromCheckpoint === "function";
|
|
16
|
+
}
|
|
17
|
+
var SandboxCheckpointError = class extends Error {
|
|
18
|
+
code;
|
|
19
|
+
constructor(code, message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "SandboxCheckpointError";
|
|
22
|
+
this.code = code;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var SandboxCheckpointConflictError = class extends SandboxCheckpointError {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super("SANDBOX_SNAPSHOT_STALE_HEAD", message);
|
|
28
|
+
this.name = "SandboxCheckpointConflictError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var SandboxCheckpointDuplicateIdError = class extends SandboxCheckpointError {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super("SANDBOX_SNAPSHOT_DUPLICATE_ID", message);
|
|
34
|
+
this.name = "SandboxCheckpointDuplicateIdError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var SandboxCheckpointInvalidIdError = class extends SandboxCheckpointError {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super("SANDBOX_SNAPSHOT_INVALID_ID", message);
|
|
40
|
+
this.name = "SandboxCheckpointInvalidIdError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var SandboxCheckpointInvalidEntryError = class extends SandboxCheckpointError {
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super("SANDBOX_SNAPSHOT_INVALID_ENTRY", message);
|
|
46
|
+
this.name = "SandboxCheckpointInvalidEntryError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var SandboxCheckpointParentMismatchError = class extends SandboxCheckpointError {
|
|
50
|
+
constructor(message) {
|
|
51
|
+
super("SANDBOX_SNAPSHOT_PARENT_MISMATCH", message);
|
|
52
|
+
this.name = "SandboxCheckpointParentMismatchError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var SandboxCheckpointNotHeadError = class extends SandboxCheckpointError {
|
|
56
|
+
constructor(message) {
|
|
57
|
+
super("SANDBOX_SNAPSHOT_NOT_HEAD", message);
|
|
58
|
+
this.name = "SandboxCheckpointNotHeadError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var SandboxCheckpointWriterConflictError = class extends SandboxCheckpointError {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super("SANDBOX_SNAPSHOT_WRITER_CONFLICT", message);
|
|
64
|
+
this.name = "SandboxCheckpointWriterConflictError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var SandboxCheckpointWriterLostError = class extends SandboxCheckpointError {
|
|
68
|
+
constructor(message) {
|
|
69
|
+
super("SANDBOX_SNAPSHOT_WRITER_LOST", message);
|
|
70
|
+
this.name = "SandboxCheckpointWriterLostError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
function defineSandboxCheckpointStore(store) {
|
|
74
|
+
return store;
|
|
75
|
+
}
|
|
76
|
+
function copy(value) {
|
|
77
|
+
return structuredClone(value);
|
|
78
|
+
}
|
|
79
|
+
function blobKeys(checkpoint) {
|
|
80
|
+
const keys = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const entry of checkpoint.files) if (entry.kind === "file") keys.add(entry.blobKey);
|
|
82
|
+
for (const artifact of checkpoint.artifacts) keys.add(artifact.blobKey);
|
|
83
|
+
return keys;
|
|
84
|
+
}
|
|
85
|
+
function hasUnpairedSurrogate(value) {
|
|
86
|
+
for (let index = 0; index < value.length; index++) {
|
|
87
|
+
const code = value.charCodeAt(index);
|
|
88
|
+
if (code >= 55296 && code <= 56319) {
|
|
89
|
+
const next = value.charCodeAt(index + 1);
|
|
90
|
+
if (Number.isNaN(next) || next < 56320 || next > 57343) return true;
|
|
91
|
+
index++;
|
|
92
|
+
} else if (code >= 56320 && code <= 57343) return true;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
function assertValidIdentifier(value, label) {
|
|
97
|
+
if (typeof value !== "string" || value.length === 0 || hasUnpairedSurrogate(value)) throw new SandboxCheckpointInvalidIdError(`${label} must be a non-empty well-formed Unicode string`);
|
|
98
|
+
}
|
|
99
|
+
function hasOwn(value, key) {
|
|
100
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
101
|
+
}
|
|
102
|
+
function validateEntries(checkpoint) {
|
|
103
|
+
if (!Array.isArray(checkpoint.files)) throw new SandboxCheckpointInvalidEntryError("Checkpoint files must be an array");
|
|
104
|
+
const paths = /* @__PURE__ */ new Set();
|
|
105
|
+
const kinds = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const entry of checkpoint.files) {
|
|
107
|
+
if (entry === null || typeof entry !== "object") throw new SandboxCheckpointInvalidEntryError("Checkpoint entry must be an object");
|
|
108
|
+
const candidate = entry;
|
|
109
|
+
if (typeof candidate.path !== "string" || candidate.path.length === 0 || candidate.path.includes("\0") || candidate.path.startsWith("/") || candidate.path.startsWith("\\") || /^[A-Za-z]:([\\/]|$)/.test(candidate.path) || candidate.path.includes("\\") || candidate.path.split("/").some((part) => part.length === 0 || part === "." || part === "..")) throw new SandboxCheckpointInvalidEntryError("Checkpoint entry path must be a normalized workspace-relative path");
|
|
110
|
+
const path = candidate.path;
|
|
111
|
+
if (paths.has(path)) throw new SandboxCheckpointInvalidEntryError(`Checkpoint contains duplicate entry path '${path}'`);
|
|
112
|
+
for (let separator = path.indexOf("/"); separator !== -1; separator = path.indexOf("/", separator + 1)) {
|
|
113
|
+
const ancestor = path.slice(0, separator);
|
|
114
|
+
if (kinds.get(ancestor) === "file") throw new SandboxCheckpointInvalidEntryError(`Checkpoint entry '${path}' is beneath file '${ancestor}'`);
|
|
115
|
+
}
|
|
116
|
+
if (candidate.kind === "file" && Array.from(kinds.keys()).some((other) => other.startsWith(`${path}/`))) throw new SandboxCheckpointInvalidEntryError(`Checkpoint file '${path}' is an ancestor of another entry`);
|
|
117
|
+
paths.add(path);
|
|
118
|
+
if (candidate.kind === "file") {
|
|
119
|
+
if (typeof candidate.blobKey !== "string" || candidate.blobKey.length === 0 || hasUnpairedSurrogate(candidate.blobKey) || !/^sandbox-files\/sha256\/[0-9a-f]{64}$/.test(candidate.blobKey)) throw new SandboxCheckpointInvalidEntryError("File entries require a non-empty blobKey");
|
|
120
|
+
if (!hasOwn(candidate, "size") || typeof candidate.size !== "number" || !Number.isSafeInteger(candidate.size) || candidate.size < 0) throw new SandboxCheckpointInvalidEntryError("File entry size must be a non-negative safe integer");
|
|
121
|
+
} else if (candidate.kind === "dir") {
|
|
122
|
+
if (hasOwn(candidate, "blobKey") || hasOwn(candidate, "size")) throw new SandboxCheckpointInvalidEntryError("Directory entries cannot contain file fields");
|
|
123
|
+
} else throw new SandboxCheckpointInvalidEntryError("Checkpoint entry kind must be file or dir");
|
|
124
|
+
kinds.set(path, candidate.kind);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function validateArtifacts(checkpoint) {
|
|
128
|
+
if (!Array.isArray(checkpoint.artifacts)) throw new SandboxCheckpointInvalidEntryError("Checkpoint artifacts must be an array");
|
|
129
|
+
for (const artifact of checkpoint.artifacts) {
|
|
130
|
+
if (artifact === null || typeof artifact !== "object") throw new SandboxCheckpointInvalidEntryError("Checkpoint artifact must be an object");
|
|
131
|
+
const candidate = artifact;
|
|
132
|
+
if (typeof candidate.artifactId !== "string" || candidate.artifactId.length === 0 || hasUnpairedSurrogate(candidate.artifactId) || typeof candidate.name !== "string" || candidate.name.length === 0 || typeof candidate.mimeType !== "string" || candidate.mimeType.length === 0 || typeof candidate.blobKey !== "string" || candidate.blobKey.length === 0 || hasUnpairedSurrogate(candidate.blobKey) || !/^sandbox-artifacts\/sha256\/[0-9a-f]{64}$/.test(candidate.blobKey) || typeof candidate.size !== "number" || !Number.isSafeInteger(candidate.size) || candidate.size < 0 || typeof candidate.createdAt !== "number" || !Number.isFinite(candidate.createdAt)) throw new SandboxCheckpointInvalidEntryError("Checkpoint artifact has invalid fields");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
var InMemorySandboxCheckpointStore = class {
|
|
136
|
+
state;
|
|
137
|
+
now;
|
|
138
|
+
leaseDurationMs;
|
|
139
|
+
renewAfterMs;
|
|
140
|
+
constructor(options = {}) {
|
|
141
|
+
const state = {
|
|
142
|
+
checkpoints: /* @__PURE__ */ new Map(),
|
|
143
|
+
heads: /* @__PURE__ */ new Map(),
|
|
144
|
+
writers: /* @__PURE__ */ new Map(),
|
|
145
|
+
fences: /* @__PURE__ */ new Map(),
|
|
146
|
+
references: /* @__PURE__ */ new Map()
|
|
147
|
+
};
|
|
148
|
+
this.state = state;
|
|
149
|
+
this.now = options.now ?? (() => Date.now());
|
|
150
|
+
this.leaseDurationMs = options.leaseDurationMs ?? 12e4;
|
|
151
|
+
this.renewAfterMs = options.renewAfterMs ?? 45e3;
|
|
152
|
+
if (!Number.isFinite(this.leaseDurationMs) || this.leaseDurationMs <= 0) throw new Error("leaseDurationMs must be finite and positive");
|
|
153
|
+
if (!Number.isFinite(this.renewAfterMs) || this.renewAfterMs <= 0 || this.renewAfterMs >= this.leaseDurationMs) throw new Error("renewAfterMs must be finite, positive, and less than leaseDurationMs");
|
|
154
|
+
}
|
|
155
|
+
async get(id) {
|
|
156
|
+
assertValidIdentifier(id, "Checkpoint id");
|
|
157
|
+
const checkpoint = this.state.checkpoints.get(id);
|
|
158
|
+
return checkpoint ? copy(checkpoint) : null;
|
|
159
|
+
}
|
|
160
|
+
async list(threadId) {
|
|
161
|
+
assertValidIdentifier(threadId, "Thread id");
|
|
162
|
+
return Array.from(this.state.checkpoints.values()).filter((checkpoint) => checkpoint.threadId === threadId).sort((a, b) => a.createdAt - b.createdAt || compareUtf8Bytes(a.id, b.id)).map(copy);
|
|
163
|
+
}
|
|
164
|
+
async getHead(threadId) {
|
|
165
|
+
assertValidIdentifier(threadId, "Thread id");
|
|
166
|
+
return this.state.heads.get(threadId) ?? null;
|
|
167
|
+
}
|
|
168
|
+
async append(input) {
|
|
169
|
+
const checkpoint = copy(input.checkpoint);
|
|
170
|
+
const { expectedHeadId, writer } = input;
|
|
171
|
+
assertValidIdentifier(checkpoint.threadId, "Checkpoint thread id");
|
|
172
|
+
assertValidIdentifier(writer.threadId, "Writer thread id");
|
|
173
|
+
assertValidIdentifier(checkpoint.id, "Checkpoint id");
|
|
174
|
+
if (expectedHeadId !== null) assertValidIdentifier(expectedHeadId, "Expected head id");
|
|
175
|
+
if (checkpoint.parentCheckpointId != null) assertValidIdentifier(checkpoint.parentCheckpointId, "Parent checkpoint id");
|
|
176
|
+
if (writer.threadId !== checkpoint.threadId) throw new SandboxCheckpointWriterLostError("Checkpoint writer thread does not match checkpoint thread");
|
|
177
|
+
if (typeof checkpoint.id !== "string" || checkpoint.id.length === 0) throw new SandboxCheckpointInvalidIdError("Checkpoint id must be non-empty");
|
|
178
|
+
if (hasUnpairedSurrogate(checkpoint.id)) throw new SandboxCheckpointInvalidIdError("Checkpoint id must contain valid Unicode");
|
|
179
|
+
if (hasUnpairedSurrogate(checkpoint.threadId)) throw new SandboxCheckpointInvalidIdError("Checkpoint thread id must contain valid Unicode");
|
|
180
|
+
if (typeof checkpoint.createdAt !== "number" || !Number.isFinite(checkpoint.createdAt)) throw new SandboxCheckpointInvalidEntryError("Checkpoint createdAt must be a finite number");
|
|
181
|
+
if (expectedHeadId === "") throw new SandboxCheckpointInvalidIdError("Expected head id must be null or non-empty");
|
|
182
|
+
const parentCheckpointId = checkpoint.parentCheckpointId ?? null;
|
|
183
|
+
if (parentCheckpointId === "") throw new SandboxCheckpointInvalidIdError("Parent checkpoint id must be null or non-empty");
|
|
184
|
+
if (expectedHeadId !== null && hasUnpairedSurrogate(expectedHeadId)) throw new SandboxCheckpointInvalidIdError("Expected head id must contain valid Unicode");
|
|
185
|
+
if (parentCheckpointId !== null && hasUnpairedSurrogate(parentCheckpointId)) throw new SandboxCheckpointInvalidIdError("Parent checkpoint id must contain valid Unicode");
|
|
186
|
+
validateEntries(checkpoint);
|
|
187
|
+
validateArtifacts(checkpoint);
|
|
188
|
+
this.assertWriter(writer, checkpoint.threadId);
|
|
189
|
+
if (this.state.checkpoints.has(checkpoint.id)) throw new SandboxCheckpointDuplicateIdError(`Checkpoint '${checkpoint.id}' already exists`);
|
|
190
|
+
const actualHeadId = this.state.heads.get(checkpoint.threadId) ?? null;
|
|
191
|
+
if (actualHeadId !== expectedHeadId) throw new SandboxCheckpointConflictError(`Expected head '${expectedHeadId}', but thread '${checkpoint.threadId}' is at '${actualHeadId}'`);
|
|
192
|
+
if (parentCheckpointId !== expectedHeadId) throw new SandboxCheckpointParentMismatchError(`Checkpoint '${checkpoint.id}' parent does not match expected head`);
|
|
193
|
+
const stored = {
|
|
194
|
+
...checkpoint,
|
|
195
|
+
parentCheckpointId
|
|
196
|
+
};
|
|
197
|
+
const keys = blobKeys(stored);
|
|
198
|
+
this.state.checkpoints.set(stored.id, stored);
|
|
199
|
+
this.state.heads.set(stored.threadId, stored.id);
|
|
200
|
+
for (const key of keys) this.state.references.set(key, (this.state.references.get(key) ?? 0) + 1);
|
|
201
|
+
return { headId: stored.id };
|
|
202
|
+
}
|
|
203
|
+
async deleteHead(input) {
|
|
204
|
+
const { threadId, checkpointId, writer } = input;
|
|
205
|
+
assertValidIdentifier(threadId, "Thread id");
|
|
206
|
+
assertValidIdentifier(checkpointId, "Checkpoint id");
|
|
207
|
+
assertValidIdentifier(writer.threadId, "Writer thread id");
|
|
208
|
+
if (writer.threadId !== threadId) throw new SandboxCheckpointWriterLostError("Checkpoint writer thread does not match operation thread");
|
|
209
|
+
this.assertWriter(writer, threadId);
|
|
210
|
+
if ((this.state.heads.get(threadId) ?? null) !== checkpointId) throw new SandboxCheckpointNotHeadError(`Checkpoint '${checkpointId}' is not the current head of thread '${threadId}'`);
|
|
211
|
+
const checkpoint = this.state.checkpoints.get(checkpointId);
|
|
212
|
+
if (!checkpoint) throw new SandboxCheckpointNotHeadError(`Checkpoint '${checkpointId}' does not exist`);
|
|
213
|
+
this.state.checkpoints.delete(checkpointId);
|
|
214
|
+
if (checkpoint.parentCheckpointId) this.state.heads.set(threadId, checkpoint.parentCheckpointId);
|
|
215
|
+
else this.state.heads.delete(threadId);
|
|
216
|
+
for (const key of blobKeys(checkpoint)) {
|
|
217
|
+
const references = (this.state.references.get(key) ?? 0) - 1;
|
|
218
|
+
if (references > 0) this.state.references.set(key, references);
|
|
219
|
+
else this.state.references.delete(key);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async acquireWriter(threadId) {
|
|
223
|
+
assertValidIdentifier(threadId, "Thread id");
|
|
224
|
+
const current = this.state.writers.get(threadId);
|
|
225
|
+
if (current && current.expiresAt > this.now()) throw new SandboxCheckpointWriterConflictError(`Thread '${threadId}' already has an active checkpoint writer`);
|
|
226
|
+
const fence = (this.state.fences.get(threadId) ?? 0) + 1;
|
|
227
|
+
this.state.fences.set(threadId, fence);
|
|
228
|
+
const ownerToken = globalThis.crypto.randomUUID();
|
|
229
|
+
const lease = {
|
|
230
|
+
threadId,
|
|
231
|
+
ownerToken,
|
|
232
|
+
fence,
|
|
233
|
+
expiresAt: this.now() + this.leaseDurationMs
|
|
234
|
+
};
|
|
235
|
+
this.state.writers.set(threadId, lease);
|
|
236
|
+
return {
|
|
237
|
+
...lease,
|
|
238
|
+
get expiresAt() {
|
|
239
|
+
return lease.expiresAt;
|
|
240
|
+
},
|
|
241
|
+
renewAfterMs: this.renewAfterMs,
|
|
242
|
+
renew: async () => {
|
|
243
|
+
this.assertWriter(lease, threadId);
|
|
244
|
+
lease.expiresAt = this.now() + this.leaseDurationMs;
|
|
245
|
+
return { expiresAt: lease.expiresAt };
|
|
246
|
+
},
|
|
247
|
+
release: async () => {
|
|
248
|
+
const currentLease = this.state.writers.get(threadId);
|
|
249
|
+
if (currentLease?.ownerToken === ownerToken && currentLease.fence === fence) this.state.writers.delete(threadId);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
assertWriter(writer, threadId) {
|
|
254
|
+
const current = this.state.writers.get(threadId);
|
|
255
|
+
if (!current || current.ownerToken !== writer.ownerToken || current.fence !== writer.fence || current.expiresAt <= this.now()) throw new SandboxCheckpointWriterLostError(`Checkpoint writer lease for thread '${threadId}' is no longer current`);
|
|
256
|
+
}
|
|
257
|
+
async listBlobReferences() {
|
|
258
|
+
return Array.from(this.state.references.entries()).sort(([a], [b]) => compareUtf8Bytes(a, b)).map(([key, references]) => ({
|
|
259
|
+
key,
|
|
260
|
+
references
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
//#endregion
|
|
265
|
+
export { InMemorySandboxCheckpointStore, SandboxCheckpointConflictError, SandboxCheckpointDuplicateIdError, SandboxCheckpointError, SandboxCheckpointInvalidEntryError, SandboxCheckpointInvalidIdError, SandboxCheckpointNotHeadError, SandboxCheckpointParentMismatchError, SandboxCheckpointWriterConflictError, SandboxCheckpointWriterLostError, defineSandboxCheckpointStore, isForkCapableSandboxCheckpointStore };
|
|
266
|
+
|
|
267
|
+
//# sourceMappingURL=checkpoint-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoint-store.js","names":[],"sources":["../../src/checkpoint-store.ts"],"sourcesContent":["import type { ModelMessage } from '@tanstack/ai'\n\n// Compare strings by their UTF-8 bytes so ordering does not depend on locale.\nconst utf8Encoder = new TextEncoder()\n\nconst compareUtf8Bytes = (left: string, right: string): number => {\n const leftBytes = utf8Encoder.encode(left)\n const rightBytes = utf8Encoder.encode(right)\n const length = Math.min(leftBytes.length, rightBytes.length)\n for (let index = 0; index < length; index++) {\n const leftByte = leftBytes[index]\n const rightByte = rightBytes[index]\n if (leftByte !== rightByte) {\n return (leftByte ?? 0) - (rightByte ?? 0)\n }\n }\n return leftBytes.length - rightBytes.length\n}\n\nexport interface SandboxSnapshotFileEntry {\n path: string\n kind: 'file'\n blobKey: string\n size: number\n}\n\nexport interface SandboxSnapshotDirectoryEntry {\n path: string\n kind: 'dir'\n}\n\nexport type SandboxSnapshotEntry =\n | SandboxSnapshotFileEntry\n | SandboxSnapshotDirectoryEntry\n\nexport interface SandboxSnapshotArtifact {\n artifactId: string\n name: string\n mimeType: string\n size: number\n blobKey: string\n createdAt: number\n}\n\nexport interface SandboxCheckpoint {\n id: string\n threadId: string\n parentCheckpointId: string | null\n createdAt: number\n reason: 'automatic' | 'named' | 'fork-root'\n label?: string\n sourceRunId?: string\n files: ReadonlyArray<SandboxSnapshotEntry>\n conversation: ReadonlyArray<ModelMessage>\n artifacts: ReadonlyArray<SandboxSnapshotArtifact>\n}\n\nexport interface SandboxCheckpointStore {\n get: (id: string) => Promise<SandboxCheckpoint | null>\n list: (threadId: string) => Promise<Array<SandboxCheckpoint>>\n getHead: (threadId: string) => Promise<string | null>\n append: (input: {\n checkpoint: SandboxCheckpoint\n expectedHeadId: string | null\n writer: SandboxCheckpointWriter\n }) => Promise<{ headId: string }>\n deleteHead: (input: {\n threadId: string\n checkpointId: string\n writer: SandboxCheckpointWriter\n }) => Promise<void>\n acquireWriter: (threadId: string) => Promise<SandboxCheckpointWriterLease>\n listBlobReferences: () => Promise<Array<{ key: string; references: number }>>\n /** Optional atomic fork capability. Stores without this method cannot fork. */\n forkFromCheckpoint?: SandboxCheckpointForkCapability['forkFromCheckpoint']\n}\n\nexport interface SandboxCheckpointForkInput {\n sourceThreadId: string\n sourceCheckpointId: string\n destinationThreadId: string\n destinationCheckpointId: string\n createdAt: number\n writer: SandboxCheckpointWriter\n}\n\nexport interface SandboxCheckpointForkCapability {\n forkFromCheckpoint: (input: SandboxCheckpointForkInput) => Promise<{\n checkpoint: SandboxCheckpoint\n }>\n}\n\nexport type ForkCapableSandboxCheckpointStore = SandboxCheckpointStore &\n SandboxCheckpointForkCapability\n\nexport function isForkCapableSandboxCheckpointStore(\n store: SandboxCheckpointStore,\n): store is ForkCapableSandboxCheckpointStore {\n return typeof store.forkFromCheckpoint === 'function'\n}\n\nexport interface SandboxCheckpointWriter {\n threadId: string\n ownerToken: string\n fence: number\n}\n\nexport interface SandboxCheckpointWriterLease extends SandboxCheckpointWriter {\n expiresAt: number\n renewAfterMs: number\n renew: () => Promise<{ expiresAt: number }>\n release: () => Promise<void>\n}\n\nexport interface SandboxCheckpointStoreOptions {\n now?: () => number\n leaseDurationMs?: number\n renewAfterMs?: number\n}\n\ninterface InMemoryCheckpointState {\n checkpoints: Map<string, SandboxCheckpoint>\n heads: Map<string, string>\n writers: Map<string, { ownerToken: string; fence: number; expiresAt: number }>\n fences: Map<string, number>\n references: Map<string, number>\n}\n\nexport type SandboxCheckpointErrorCode =\n | 'SANDBOX_SNAPSHOT_STALE_HEAD'\n | 'SANDBOX_SNAPSHOT_PARENT_MISMATCH'\n | 'SANDBOX_SNAPSHOT_DUPLICATE_ID'\n | 'SANDBOX_SNAPSHOT_NOT_HEAD'\n | 'SANDBOX_SNAPSHOT_WRITER_CONFLICT'\n | 'SANDBOX_SNAPSHOT_WRITER_LOST'\n | 'SANDBOX_SNAPSHOT_INVALID_ID'\n | 'SANDBOX_SNAPSHOT_INVALID_ENTRY'\n | 'SANDBOX_SNAPSHOT_CHECKPOINT_NOT_FOUND'\n | 'SANDBOX_SNAPSHOT_ATOMIC_FORK_REQUIRED'\n | 'SANDBOX_SNAPSHOT_FORK_SOURCE_NOT_FOUND'\n | 'SANDBOX_SNAPSHOT_FORK_SOURCE_THREAD_MISMATCH'\n | 'SANDBOX_SNAPSHOT_FORK_DESTINATION_NOT_EMPTY'\n\nexport class SandboxCheckpointError extends Error {\n readonly code: SandboxCheckpointErrorCode\n\n constructor(code: SandboxCheckpointErrorCode, message: string) {\n super(message)\n this.name = 'SandboxCheckpointError'\n this.code = code\n }\n}\n\nexport class SandboxCheckpointConflictError extends SandboxCheckpointError {\n constructor(message: string) {\n super('SANDBOX_SNAPSHOT_STALE_HEAD', message)\n this.name = 'SandboxCheckpointConflictError'\n }\n}\n\nexport class SandboxCheckpointDuplicateIdError extends SandboxCheckpointError {\n constructor(message: string) {\n super('SANDBOX_SNAPSHOT_DUPLICATE_ID', message)\n this.name = 'SandboxCheckpointDuplicateIdError'\n }\n}\n\nexport class SandboxCheckpointInvalidIdError extends SandboxCheckpointError {\n constructor(message: string) {\n super('SANDBOX_SNAPSHOT_INVALID_ID', message)\n this.name = 'SandboxCheckpointInvalidIdError'\n }\n}\n\nexport class SandboxCheckpointInvalidEntryError extends SandboxCheckpointError {\n constructor(message: string) {\n super('SANDBOX_SNAPSHOT_INVALID_ENTRY', message)\n this.name = 'SandboxCheckpointInvalidEntryError'\n }\n}\n\nexport class SandboxCheckpointParentMismatchError extends SandboxCheckpointError {\n constructor(message: string) {\n super('SANDBOX_SNAPSHOT_PARENT_MISMATCH', message)\n this.name = 'SandboxCheckpointParentMismatchError'\n }\n}\n\nexport class SandboxCheckpointNotHeadError extends SandboxCheckpointError {\n constructor(message: string) {\n super('SANDBOX_SNAPSHOT_NOT_HEAD', message)\n this.name = 'SandboxCheckpointNotHeadError'\n }\n}\n\nexport class SandboxCheckpointWriterConflictError extends SandboxCheckpointError {\n constructor(message: string) {\n super('SANDBOX_SNAPSHOT_WRITER_CONFLICT', message)\n this.name = 'SandboxCheckpointWriterConflictError'\n }\n}\n\nexport class SandboxCheckpointWriterLostError extends SandboxCheckpointError {\n constructor(message: string) {\n super('SANDBOX_SNAPSHOT_WRITER_LOST', message)\n this.name = 'SandboxCheckpointWriterLostError'\n }\n}\n\nexport function defineSandboxCheckpointStore(\n store: SandboxCheckpointStore,\n): SandboxCheckpointStore {\n return store\n}\n\nfunction copy<T>(value: T): T {\n return structuredClone(value)\n}\n\nfunction blobKeys(checkpoint: SandboxCheckpoint): Set<string> {\n const keys = new Set<string>()\n for (const entry of checkpoint.files) {\n if (entry.kind === 'file') keys.add(entry.blobKey)\n }\n for (const artifact of checkpoint.artifacts) keys.add(artifact.blobKey)\n return keys\n}\n\nfunction hasUnpairedSurrogate(value: string): boolean {\n for (let index = 0; index < value.length; index++) {\n const code = value.charCodeAt(index)\n if (code >= 0xd800 && code <= 0xdbff) {\n const next = value.charCodeAt(index + 1)\n if (Number.isNaN(next) || next < 0xdc00 || next > 0xdfff) return true\n index++\n } else if (code >= 0xdc00 && code <= 0xdfff) {\n return true\n }\n }\n return false\n}\n\nfunction assertValidIdentifier(\n value: unknown,\n label: string,\n): asserts value is string {\n if (\n typeof value !== 'string' ||\n value.length === 0 ||\n hasUnpairedSurrogate(value)\n ) {\n throw new SandboxCheckpointInvalidIdError(\n `${label} must be a non-empty well-formed Unicode string`,\n )\n }\n}\n\nfunction hasOwn(value: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction validateEntries(checkpoint: SandboxCheckpoint): void {\n if (!Array.isArray(checkpoint.files)) {\n throw new SandboxCheckpointInvalidEntryError(\n 'Checkpoint files must be an array',\n )\n }\n const paths = new Set<string>()\n const kinds = new Map<string, 'file' | 'dir'>()\n for (const entry of checkpoint.files as ReadonlyArray<unknown>) {\n if (entry === null || typeof entry !== 'object') {\n throw new SandboxCheckpointInvalidEntryError(\n 'Checkpoint entry must be an object',\n )\n }\n const candidate = entry as Record<string, unknown>\n if (\n typeof candidate.path !== 'string' ||\n candidate.path.length === 0 ||\n candidate.path.includes('\\0') ||\n candidate.path.startsWith('/') ||\n candidate.path.startsWith('\\\\') ||\n /^[A-Za-z]:([\\\\/]|$)/.test(candidate.path) ||\n candidate.path.includes('\\\\') ||\n candidate.path\n .split('/')\n .some((part) => part.length === 0 || part === '.' || part === '..')\n ) {\n throw new SandboxCheckpointInvalidEntryError(\n 'Checkpoint entry path must be a normalized workspace-relative path',\n )\n }\n const path = candidate.path\n if (paths.has(path)) {\n throw new SandboxCheckpointInvalidEntryError(\n `Checkpoint contains duplicate entry path '${path}'`,\n )\n }\n for (\n let separator = path.indexOf('/');\n separator !== -1;\n separator = path.indexOf('/', separator + 1)\n ) {\n const ancestor = path.slice(0, separator)\n if (kinds.get(ancestor) === 'file') {\n throw new SandboxCheckpointInvalidEntryError(\n `Checkpoint entry '${path}' is beneath file '${ancestor}'`,\n )\n }\n }\n if (\n candidate.kind === 'file' &&\n Array.from(kinds.keys()).some((other) => other.startsWith(`${path}/`))\n ) {\n throw new SandboxCheckpointInvalidEntryError(\n `Checkpoint file '${path}' is an ancestor of another entry`,\n )\n }\n paths.add(path)\n if (candidate.kind === 'file') {\n if (\n typeof candidate.blobKey !== 'string' ||\n candidate.blobKey.length === 0 ||\n hasUnpairedSurrogate(candidate.blobKey) ||\n !/^sandbox-files\\/sha256\\/[0-9a-f]{64}$/.test(candidate.blobKey)\n ) {\n throw new SandboxCheckpointInvalidEntryError(\n 'File entries require a non-empty blobKey',\n )\n }\n if (\n !hasOwn(candidate, 'size') ||\n typeof candidate.size !== 'number' ||\n !Number.isSafeInteger(candidate.size) ||\n candidate.size < 0\n ) {\n throw new SandboxCheckpointInvalidEntryError(\n 'File entry size must be a non-negative safe integer',\n )\n }\n } else if (candidate.kind === 'dir') {\n if (hasOwn(candidate, 'blobKey') || hasOwn(candidate, 'size')) {\n throw new SandboxCheckpointInvalidEntryError(\n 'Directory entries cannot contain file fields',\n )\n }\n } else {\n throw new SandboxCheckpointInvalidEntryError(\n 'Checkpoint entry kind must be file or dir',\n )\n }\n kinds.set(path, candidate.kind)\n }\n}\n\nfunction validateArtifacts(checkpoint: SandboxCheckpoint): void {\n if (!Array.isArray(checkpoint.artifacts)) {\n throw new SandboxCheckpointInvalidEntryError(\n 'Checkpoint artifacts must be an array',\n )\n }\n for (const artifact of checkpoint.artifacts as ReadonlyArray<unknown>) {\n if (artifact === null || typeof artifact !== 'object') {\n throw new SandboxCheckpointInvalidEntryError(\n 'Checkpoint artifact must be an object',\n )\n }\n const candidate = artifact as Record<string, unknown>\n if (\n typeof candidate.artifactId !== 'string' ||\n candidate.artifactId.length === 0 ||\n hasUnpairedSurrogate(candidate.artifactId) ||\n typeof candidate.name !== 'string' ||\n candidate.name.length === 0 ||\n typeof candidate.mimeType !== 'string' ||\n candidate.mimeType.length === 0 ||\n typeof candidate.blobKey !== 'string' ||\n candidate.blobKey.length === 0 ||\n hasUnpairedSurrogate(candidate.blobKey) ||\n !/^sandbox-artifacts\\/sha256\\/[0-9a-f]{64}$/.test(candidate.blobKey) ||\n typeof candidate.size !== 'number' ||\n !Number.isSafeInteger(candidate.size) ||\n candidate.size < 0 ||\n typeof candidate.createdAt !== 'number' ||\n !Number.isFinite(candidate.createdAt)\n ) {\n throw new SandboxCheckpointInvalidEntryError(\n 'Checkpoint artifact has invalid fields',\n )\n }\n }\n}\n\nexport class InMemorySandboxCheckpointStore implements SandboxCheckpointStore {\n private readonly state: InMemoryCheckpointState\n private readonly now: () => number\n private readonly leaseDurationMs: number\n private readonly renewAfterMs: number\n\n constructor(options: SandboxCheckpointStoreOptions = {}) {\n const state: InMemoryCheckpointState = {\n checkpoints: new Map(),\n heads: new Map(),\n writers: new Map(),\n fences: new Map(),\n references: new Map(),\n }\n this.state = state\n this.now = options.now ?? (() => Date.now())\n this.leaseDurationMs = options.leaseDurationMs ?? 120_000\n this.renewAfterMs = options.renewAfterMs ?? 45_000\n if (!Number.isFinite(this.leaseDurationMs) || this.leaseDurationMs <= 0) {\n throw new Error('leaseDurationMs must be finite and positive')\n }\n if (\n !Number.isFinite(this.renewAfterMs) ||\n this.renewAfterMs <= 0 ||\n this.renewAfterMs >= this.leaseDurationMs\n ) {\n throw new Error(\n 'renewAfterMs must be finite, positive, and less than leaseDurationMs',\n )\n }\n }\n\n async get(id: string): Promise<SandboxCheckpoint | null> {\n assertValidIdentifier(id, 'Checkpoint id')\n const checkpoint = this.state.checkpoints.get(id)\n return checkpoint ? copy(checkpoint) : null\n }\n\n async list(threadId: string): Promise<Array<SandboxCheckpoint>> {\n assertValidIdentifier(threadId, 'Thread id')\n return Array.from(this.state.checkpoints.values())\n .filter((checkpoint) => checkpoint.threadId === threadId)\n .sort((a, b) => a.createdAt - b.createdAt || compareUtf8Bytes(a.id, b.id))\n .map(copy)\n }\n\n async getHead(threadId: string): Promise<string | null> {\n assertValidIdentifier(threadId, 'Thread id')\n return this.state.heads.get(threadId) ?? null\n }\n\n async append(input: {\n checkpoint: SandboxCheckpoint\n expectedHeadId: string | null\n writer: SandboxCheckpointWriter\n }): Promise<{ headId: string }> {\n const checkpoint = copy(input.checkpoint)\n const { expectedHeadId, writer } = input\n assertValidIdentifier(checkpoint.threadId, 'Checkpoint thread id')\n assertValidIdentifier(writer.threadId, 'Writer thread id')\n assertValidIdentifier(checkpoint.id, 'Checkpoint id')\n if (expectedHeadId !== null) {\n assertValidIdentifier(expectedHeadId, 'Expected head id')\n }\n if (checkpoint.parentCheckpointId != null) {\n assertValidIdentifier(\n checkpoint.parentCheckpointId,\n 'Parent checkpoint id',\n )\n }\n if (writer.threadId !== checkpoint.threadId) {\n throw new SandboxCheckpointWriterLostError(\n 'Checkpoint writer thread does not match checkpoint thread',\n )\n }\n if (typeof checkpoint.id !== 'string' || checkpoint.id.length === 0) {\n throw new SandboxCheckpointInvalidIdError(\n 'Checkpoint id must be non-empty',\n )\n }\n if (hasUnpairedSurrogate(checkpoint.id)) {\n throw new SandboxCheckpointInvalidIdError(\n 'Checkpoint id must contain valid Unicode',\n )\n }\n if (hasUnpairedSurrogate(checkpoint.threadId)) {\n throw new SandboxCheckpointInvalidIdError(\n 'Checkpoint thread id must contain valid Unicode',\n )\n }\n if (\n typeof checkpoint.createdAt !== 'number' ||\n !Number.isFinite(checkpoint.createdAt)\n ) {\n throw new SandboxCheckpointInvalidEntryError(\n 'Checkpoint createdAt must be a finite number',\n )\n }\n if (expectedHeadId === '') {\n throw new SandboxCheckpointInvalidIdError(\n 'Expected head id must be null or non-empty',\n )\n }\n const parentCheckpointId = checkpoint.parentCheckpointId ?? null\n if (parentCheckpointId === '') {\n throw new SandboxCheckpointInvalidIdError(\n 'Parent checkpoint id must be null or non-empty',\n )\n }\n if (expectedHeadId !== null && hasUnpairedSurrogate(expectedHeadId)) {\n throw new SandboxCheckpointInvalidIdError(\n 'Expected head id must contain valid Unicode',\n )\n }\n if (\n parentCheckpointId !== null &&\n hasUnpairedSurrogate(parentCheckpointId)\n ) {\n throw new SandboxCheckpointInvalidIdError(\n 'Parent checkpoint id must contain valid Unicode',\n )\n }\n validateEntries(checkpoint)\n validateArtifacts(checkpoint)\n\n // The caller can re-enter append while its checkpoint is staged. Recheck\n // live writer and CAS state immediately before publishing.\n this.assertWriter(writer, checkpoint.threadId)\n if (this.state.checkpoints.has(checkpoint.id)) {\n throw new SandboxCheckpointDuplicateIdError(\n `Checkpoint '${checkpoint.id}' already exists`,\n )\n }\n const actualHeadId = this.state.heads.get(checkpoint.threadId) ?? null\n if (actualHeadId !== expectedHeadId) {\n throw new SandboxCheckpointConflictError(\n `Expected head '${expectedHeadId}', but thread '${checkpoint.threadId}' is at '${actualHeadId}'`,\n )\n }\n if (parentCheckpointId !== expectedHeadId) {\n throw new SandboxCheckpointParentMismatchError(\n `Checkpoint '${checkpoint.id}' parent does not match expected head`,\n )\n }\n const stored = { ...checkpoint, parentCheckpointId }\n const keys = blobKeys(stored)\n this.state.checkpoints.set(stored.id, stored)\n this.state.heads.set(stored.threadId, stored.id)\n for (const key of keys) {\n this.state.references.set(key, (this.state.references.get(key) ?? 0) + 1)\n }\n return { headId: stored.id }\n }\n\n async deleteHead(input: {\n threadId: string\n checkpointId: string\n writer: SandboxCheckpointWriter\n }): Promise<void> {\n const { threadId, checkpointId, writer } = input\n assertValidIdentifier(threadId, 'Thread id')\n assertValidIdentifier(checkpointId, 'Checkpoint id')\n assertValidIdentifier(writer.threadId, 'Writer thread id')\n if (writer.threadId !== threadId) {\n throw new SandboxCheckpointWriterLostError(\n 'Checkpoint writer thread does not match operation thread',\n )\n }\n this.assertWriter(writer, threadId)\n const headId = this.state.heads.get(threadId) ?? null\n if (headId !== checkpointId) {\n throw new SandboxCheckpointNotHeadError(\n `Checkpoint '${checkpointId}' is not the current head of thread '${threadId}'`,\n )\n }\n const checkpoint = this.state.checkpoints.get(checkpointId)\n if (!checkpoint) {\n throw new SandboxCheckpointNotHeadError(\n `Checkpoint '${checkpointId}' does not exist`,\n )\n }\n this.state.checkpoints.delete(checkpointId)\n if (checkpoint.parentCheckpointId) {\n this.state.heads.set(threadId, checkpoint.parentCheckpointId)\n } else {\n this.state.heads.delete(threadId)\n }\n for (const key of blobKeys(checkpoint)) {\n const references = (this.state.references.get(key) ?? 0) - 1\n if (references > 0) this.state.references.set(key, references)\n else this.state.references.delete(key)\n }\n }\n\n async acquireWriter(threadId: string): Promise<SandboxCheckpointWriterLease> {\n assertValidIdentifier(threadId, 'Thread id')\n const current = this.state.writers.get(threadId)\n if (current && current.expiresAt > this.now()) {\n throw new SandboxCheckpointWriterConflictError(\n `Thread '${threadId}' already has an active checkpoint writer`,\n )\n }\n const fence = (this.state.fences.get(threadId) ?? 0) + 1\n this.state.fences.set(threadId, fence)\n const ownerToken = globalThis.crypto.randomUUID()\n const lease = {\n threadId,\n ownerToken,\n fence,\n expiresAt: this.now() + this.leaseDurationMs,\n }\n this.state.writers.set(threadId, lease)\n return {\n ...lease,\n get expiresAt() {\n return lease.expiresAt\n },\n renewAfterMs: this.renewAfterMs,\n renew: async () => {\n this.assertWriter(lease, threadId)\n lease.expiresAt = this.now() + this.leaseDurationMs\n return { expiresAt: lease.expiresAt }\n },\n release: async () => {\n const currentLease = this.state.writers.get(threadId)\n if (\n currentLease?.ownerToken === ownerToken &&\n currentLease.fence === fence\n )\n this.state.writers.delete(threadId)\n },\n }\n }\n\n private assertWriter(\n writer: SandboxCheckpointWriter,\n threadId: string,\n ): void {\n const current = this.state.writers.get(threadId)\n if (\n !current ||\n current.ownerToken !== writer.ownerToken ||\n current.fence !== writer.fence ||\n current.expiresAt <= this.now()\n ) {\n throw new SandboxCheckpointWriterLostError(\n `Checkpoint writer lease for thread '${threadId}' is no longer current`,\n )\n }\n }\n\n async listBlobReferences(): Promise<\n Array<{ key: string; references: number }>\n > {\n return Array.from(this.state.references.entries())\n .sort(([a], [b]) => compareUtf8Bytes(a, b))\n .map(([key, references]) => ({ key, references }))\n }\n}\n"],"mappings":";AAGA,IAAM,cAAc,IAAI,YAAY;AAEpC,IAAM,oBAAoB,MAAc,UAA0B;CAChE,MAAM,YAAY,YAAY,OAAO,IAAI;CACzC,MAAM,aAAa,YAAY,OAAO,KAAK;CAC3C,MAAM,SAAS,KAAK,IAAI,UAAU,QAAQ,WAAW,MAAM;CAC3D,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,SAAS;EAC3C,MAAM,WAAW,UAAU;EAC3B,MAAM,YAAY,WAAW;EAC7B,IAAI,aAAa,WACf,QAAQ,YAAY,MAAM,aAAa;CAE3C;CACA,OAAO,UAAU,SAAS,WAAW;AACvC;AA8EA,SAAgB,oCACd,OAC4C;CAC5C,OAAO,OAAO,MAAM,uBAAuB;AAC7C;AA4CA,IAAa,yBAAb,cAA4C,MAAM;CAChD;CAEA,YAAY,MAAkC,SAAiB;EAC7D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAEA,IAAa,iCAAb,cAAoD,uBAAuB;CACzE,YAAY,SAAiB;EAC3B,MAAM,+BAA+B,OAAO;EAC5C,KAAK,OAAO;CACd;AACF;AAEA,IAAa,oCAAb,cAAuD,uBAAuB;CAC5E,YAAY,SAAiB;EAC3B,MAAM,iCAAiC,OAAO;EAC9C,KAAK,OAAO;CACd;AACF;AAEA,IAAa,kCAAb,cAAqD,uBAAuB;CAC1E,YAAY,SAAiB;EAC3B,MAAM,+BAA+B,OAAO;EAC5C,KAAK,OAAO;CACd;AACF;AAEA,IAAa,qCAAb,cAAwD,uBAAuB;CAC7E,YAAY,SAAiB;EAC3B,MAAM,kCAAkC,OAAO;EAC/C,KAAK,OAAO;CACd;AACF;AAEA,IAAa,uCAAb,cAA0D,uBAAuB;CAC/E,YAAY,SAAiB;EAC3B,MAAM,oCAAoC,OAAO;EACjD,KAAK,OAAO;CACd;AACF;AAEA,IAAa,gCAAb,cAAmD,uBAAuB;CACxE,YAAY,SAAiB;EAC3B,MAAM,6BAA6B,OAAO;EAC1C,KAAK,OAAO;CACd;AACF;AAEA,IAAa,uCAAb,cAA0D,uBAAuB;CAC/E,YAAY,SAAiB;EAC3B,MAAM,oCAAoC,OAAO;EACjD,KAAK,OAAO;CACd;AACF;AAEA,IAAa,mCAAb,cAAsD,uBAAuB;CAC3E,YAAY,SAAiB;EAC3B,MAAM,gCAAgC,OAAO;EAC7C,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,6BACd,OACwB;CACxB,OAAO;AACT;AAEA,SAAS,KAAQ,OAAa;CAC5B,OAAO,gBAAgB,KAAK;AAC9B;AAEA,SAAS,SAAS,YAA4C;CAC5D,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,WAAW,OAC7B,IAAI,MAAM,SAAS,QAAQ,KAAK,IAAI,MAAM,OAAO;CAEnD,KAAK,MAAM,YAAY,WAAW,WAAW,KAAK,IAAI,SAAS,OAAO;CACtE,OAAO;AACT;AAEA,SAAS,qBAAqB,OAAwB;CACpD,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;EACjD,MAAM,OAAO,MAAM,WAAW,KAAK;EACnC,IAAI,QAAQ,SAAU,QAAQ,OAAQ;GACpC,MAAM,OAAO,MAAM,WAAW,QAAQ,CAAC;GACvC,IAAI,OAAO,MAAM,IAAI,KAAK,OAAO,SAAU,OAAO,OAAQ,OAAO;GACjE;EACF,OAAO,IAAI,QAAQ,SAAU,QAAQ,OACnC,OAAO;CAEX;CACA,OAAO;AACT;AAEA,SAAS,sBACP,OACA,OACyB;CACzB,IACE,OAAO,UAAU,YACjB,MAAM,WAAW,KACjB,qBAAqB,KAAK,GAE1B,MAAM,IAAI,gCACR,GAAG,MAAM,gDACX;AAEJ;AAEA,SAAS,OAAO,OAAe,KAAsB;CACnD,OAAO,OAAO,UAAU,eAAe,KAAK,OAAO,GAAG;AACxD;AAEA,SAAS,gBAAgB,YAAqC;CAC5D,IAAI,CAAC,MAAM,QAAQ,WAAW,KAAK,GACjC,MAAM,IAAI,mCACR,mCACF;CAEF,MAAM,wBAAQ,IAAI,IAAY;CAC9B,MAAM,wBAAQ,IAAI,IAA4B;CAC9C,KAAK,MAAM,SAAS,WAAW,OAAiC;EAC9D,IAAI,UAAU,QAAQ,OAAO,UAAU,UACrC,MAAM,IAAI,mCACR,oCACF;EAEF,MAAM,YAAY;EAClB,IACE,OAAO,UAAU,SAAS,YAC1B,UAAU,KAAK,WAAW,KAC1B,UAAU,KAAK,SAAS,IAAI,KAC5B,UAAU,KAAK,WAAW,GAAG,KAC7B,UAAU,KAAK,WAAW,IAAI,KAC9B,sBAAsB,KAAK,UAAU,IAAI,KACzC,UAAU,KAAK,SAAS,IAAI,KAC5B,UAAU,KACP,MAAM,GAAG,CAAC,CACV,MAAM,SAAS,KAAK,WAAW,KAAK,SAAS,OAAO,SAAS,IAAI,GAEpE,MAAM,IAAI,mCACR,oEACF;EAEF,MAAM,OAAO,UAAU;EACvB,IAAI,MAAM,IAAI,IAAI,GAChB,MAAM,IAAI,mCACR,6CAA6C,KAAK,EACpD;EAEF,KACE,IAAI,YAAY,KAAK,QAAQ,GAAG,GAChC,cAAc,IACd,YAAY,KAAK,QAAQ,KAAK,YAAY,CAAC,GAC3C;GACA,MAAM,WAAW,KAAK,MAAM,GAAG,SAAS;GACxC,IAAI,MAAM,IAAI,QAAQ,MAAM,QAC1B,MAAM,IAAI,mCACR,qBAAqB,KAAK,qBAAqB,SAAS,EAC1D;EAEJ;EACA,IACE,UAAU,SAAS,UACnB,MAAM,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM,UAAU,MAAM,WAAW,GAAG,KAAK,EAAE,CAAC,GAErE,MAAM,IAAI,mCACR,oBAAoB,KAAK,kCAC3B;EAEF,MAAM,IAAI,IAAI;EACd,IAAI,UAAU,SAAS,QAAQ;GAC7B,IACE,OAAO,UAAU,YAAY,YAC7B,UAAU,QAAQ,WAAW,KAC7B,qBAAqB,UAAU,OAAO,KACtC,CAAC,wCAAwC,KAAK,UAAU,OAAO,GAE/D,MAAM,IAAI,mCACR,0CACF;GAEF,IACE,CAAC,OAAO,WAAW,MAAM,KACzB,OAAO,UAAU,SAAS,YAC1B,CAAC,OAAO,cAAc,UAAU,IAAI,KACpC,UAAU,OAAO,GAEjB,MAAM,IAAI,mCACR,qDACF;EAEJ,OAAO,IAAI,UAAU,SAAS,OACxB;OAAA,OAAO,WAAW,SAAS,KAAK,OAAO,WAAW,MAAM,GAC1D,MAAM,IAAI,mCACR,8CACF;EAAA,OAGF,MAAM,IAAI,mCACR,2CACF;EAEF,MAAM,IAAI,MAAM,UAAU,IAAI;CAChC;AACF;AAEA,SAAS,kBAAkB,YAAqC;CAC9D,IAAI,CAAC,MAAM,QAAQ,WAAW,SAAS,GACrC,MAAM,IAAI,mCACR,uCACF;CAEF,KAAK,MAAM,YAAY,WAAW,WAAqC;EACrE,IAAI,aAAa,QAAQ,OAAO,aAAa,UAC3C,MAAM,IAAI,mCACR,uCACF;EAEF,MAAM,YAAY;EAClB,IACE,OAAO,UAAU,eAAe,YAChC,UAAU,WAAW,WAAW,KAChC,qBAAqB,UAAU,UAAU,KACzC,OAAO,UAAU,SAAS,YAC1B,UAAU,KAAK,WAAW,KAC1B,OAAO,UAAU,aAAa,YAC9B,UAAU,SAAS,WAAW,KAC9B,OAAO,UAAU,YAAY,YAC7B,UAAU,QAAQ,WAAW,KAC7B,qBAAqB,UAAU,OAAO,KACtC,CAAC,4CAA4C,KAAK,UAAU,OAAO,KACnE,OAAO,UAAU,SAAS,YAC1B,CAAC,OAAO,cAAc,UAAU,IAAI,KACpC,UAAU,OAAO,KACjB,OAAO,UAAU,cAAc,YAC/B,CAAC,OAAO,SAAS,UAAU,SAAS,GAEpC,MAAM,IAAI,mCACR,wCACF;CAEJ;AACF;AAEA,IAAa,iCAAb,MAA8E;CAC5E;CACA;CACA;CACA;CAEA,YAAY,UAAyC,CAAC,GAAG;EACvD,MAAM,QAAiC;GACrC,6BAAa,IAAI,IAAI;GACrB,uBAAO,IAAI,IAAI;GACf,yBAAS,IAAI,IAAI;GACjB,wBAAQ,IAAI,IAAI;GAChB,4BAAY,IAAI,IAAI;EACtB;EACA,KAAK,QAAQ;EACb,KAAK,MAAM,QAAQ,cAAc,KAAK,IAAI;EAC1C,KAAK,kBAAkB,QAAQ,mBAAmB;EAClD,KAAK,eAAe,QAAQ,gBAAgB;EAC5C,IAAI,CAAC,OAAO,SAAS,KAAK,eAAe,KAAK,KAAK,mBAAmB,GACpE,MAAM,IAAI,MAAM,6CAA6C;EAE/D,IACE,CAAC,OAAO,SAAS,KAAK,YAAY,KAClC,KAAK,gBAAgB,KACrB,KAAK,gBAAgB,KAAK,iBAE1B,MAAM,IAAI,MACR,sEACF;CAEJ;CAEA,MAAM,IAAI,IAA+C;EACvD,sBAAsB,IAAI,eAAe;EACzC,MAAM,aAAa,KAAK,MAAM,YAAY,IAAI,EAAE;EAChD,OAAO,aAAa,KAAK,UAAU,IAAI;CACzC;CAEA,MAAM,KAAK,UAAqD;EAC9D,sBAAsB,UAAU,WAAW;EAC3C,OAAO,MAAM,KAAK,KAAK,MAAM,YAAY,OAAO,CAAC,CAAC,CAC/C,QAAQ,eAAe,WAAW,aAAa,QAAQ,CAAC,CACxD,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,aAAa,iBAAiB,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CACzE,IAAI,IAAI;CACb;CAEA,MAAM,QAAQ,UAA0C;EACtD,sBAAsB,UAAU,WAAW;EAC3C,OAAO,KAAK,MAAM,MAAM,IAAI,QAAQ,KAAK;CAC3C;CAEA,MAAM,OAAO,OAImB;EAC9B,MAAM,aAAa,KAAK,MAAM,UAAU;EACxC,MAAM,EAAE,gBAAgB,WAAW;EACnC,sBAAsB,WAAW,UAAU,sBAAsB;EACjE,sBAAsB,OAAO,UAAU,kBAAkB;EACzD,sBAAsB,WAAW,IAAI,eAAe;EACpD,IAAI,mBAAmB,MACrB,sBAAsB,gBAAgB,kBAAkB;EAE1D,IAAI,WAAW,sBAAsB,MACnC,sBACE,WAAW,oBACX,sBACF;EAEF,IAAI,OAAO,aAAa,WAAW,UACjC,MAAM,IAAI,iCACR,2DACF;EAEF,IAAI,OAAO,WAAW,OAAO,YAAY,WAAW,GAAG,WAAW,GAChE,MAAM,IAAI,gCACR,iCACF;EAEF,IAAI,qBAAqB,WAAW,EAAE,GACpC,MAAM,IAAI,gCACR,0CACF;EAEF,IAAI,qBAAqB,WAAW,QAAQ,GAC1C,MAAM,IAAI,gCACR,iDACF;EAEF,IACE,OAAO,WAAW,cAAc,YAChC,CAAC,OAAO,SAAS,WAAW,SAAS,GAErC,MAAM,IAAI,mCACR,8CACF;EAEF,IAAI,mBAAmB,IACrB,MAAM,IAAI,gCACR,4CACF;EAEF,MAAM,qBAAqB,WAAW,sBAAsB;EAC5D,IAAI,uBAAuB,IACzB,MAAM,IAAI,gCACR,gDACF;EAEF,IAAI,mBAAmB,QAAQ,qBAAqB,cAAc,GAChE,MAAM,IAAI,gCACR,6CACF;EAEF,IACE,uBAAuB,QACvB,qBAAqB,kBAAkB,GAEvC,MAAM,IAAI,gCACR,iDACF;EAEF,gBAAgB,UAAU;EAC1B,kBAAkB,UAAU;EAI5B,KAAK,aAAa,QAAQ,WAAW,QAAQ;EAC7C,IAAI,KAAK,MAAM,YAAY,IAAI,WAAW,EAAE,GAC1C,MAAM,IAAI,kCACR,eAAe,WAAW,GAAG,iBAC/B;EAEF,MAAM,eAAe,KAAK,MAAM,MAAM,IAAI,WAAW,QAAQ,KAAK;EAClE,IAAI,iBAAiB,gBACnB,MAAM,IAAI,+BACR,kBAAkB,eAAe,iBAAiB,WAAW,SAAS,WAAW,aAAa,EAChG;EAEF,IAAI,uBAAuB,gBACzB,MAAM,IAAI,qCACR,eAAe,WAAW,GAAG,sCAC/B;EAEF,MAAM,SAAS;GAAE,GAAG;GAAY;EAAmB;EACnD,MAAM,OAAO,SAAS,MAAM;EAC5B,KAAK,MAAM,YAAY,IAAI,OAAO,IAAI,MAAM;EAC5C,KAAK,MAAM,MAAM,IAAI,OAAO,UAAU,OAAO,EAAE;EAC/C,KAAK,MAAM,OAAO,MAChB,KAAK,MAAM,WAAW,IAAI,MAAM,KAAK,MAAM,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;EAE1E,OAAO,EAAE,QAAQ,OAAO,GAAG;CAC7B;CAEA,MAAM,WAAW,OAIC;EAChB,MAAM,EAAE,UAAU,cAAc,WAAW;EAC3C,sBAAsB,UAAU,WAAW;EAC3C,sBAAsB,cAAc,eAAe;EACnD,sBAAsB,OAAO,UAAU,kBAAkB;EACzD,IAAI,OAAO,aAAa,UACtB,MAAM,IAAI,iCACR,0DACF;EAEF,KAAK,aAAa,QAAQ,QAAQ;EAElC,KADe,KAAK,MAAM,MAAM,IAAI,QAAQ,KAAK,UAClC,cACb,MAAM,IAAI,8BACR,eAAe,aAAa,uCAAuC,SAAS,EAC9E;EAEF,MAAM,aAAa,KAAK,MAAM,YAAY,IAAI,YAAY;EAC1D,IAAI,CAAC,YACH,MAAM,IAAI,8BACR,eAAe,aAAa,iBAC9B;EAEF,KAAK,MAAM,YAAY,OAAO,YAAY;EAC1C,IAAI,WAAW,oBACb,KAAK,MAAM,MAAM,IAAI,UAAU,WAAW,kBAAkB;OAE5D,KAAK,MAAM,MAAM,OAAO,QAAQ;EAElC,KAAK,MAAM,OAAO,SAAS,UAAU,GAAG;GACtC,MAAM,cAAc,KAAK,MAAM,WAAW,IAAI,GAAG,KAAK,KAAK;GAC3D,IAAI,aAAa,GAAG,KAAK,MAAM,WAAW,IAAI,KAAK,UAAU;QACxD,KAAK,MAAM,WAAW,OAAO,GAAG;EACvC;CACF;CAEA,MAAM,cAAc,UAAyD;EAC3E,sBAAsB,UAAU,WAAW;EAC3C,MAAM,UAAU,KAAK,MAAM,QAAQ,IAAI,QAAQ;EAC/C,IAAI,WAAW,QAAQ,YAAY,KAAK,IAAI,GAC1C,MAAM,IAAI,qCACR,WAAW,SAAS,0CACtB;EAEF,MAAM,SAAS,KAAK,MAAM,OAAO,IAAI,QAAQ,KAAK,KAAK;EACvD,KAAK,MAAM,OAAO,IAAI,UAAU,KAAK;EACrC,MAAM,aAAa,WAAW,OAAO,WAAW;EAChD,MAAM,QAAQ;GACZ;GACA;GACA;GACA,WAAW,KAAK,IAAI,IAAI,KAAK;EAC/B;EACA,KAAK,MAAM,QAAQ,IAAI,UAAU,KAAK;EACtC,OAAO;GACL,GAAG;GACH,IAAI,YAAY;IACd,OAAO,MAAM;GACf;GACA,cAAc,KAAK;GACnB,OAAO,YAAY;IACjB,KAAK,aAAa,OAAO,QAAQ;IACjC,MAAM,YAAY,KAAK,IAAI,IAAI,KAAK;IACpC,OAAO,EAAE,WAAW,MAAM,UAAU;GACtC;GACA,SAAS,YAAY;IACnB,MAAM,eAAe,KAAK,MAAM,QAAQ,IAAI,QAAQ;IACpD,IACE,cAAc,eAAe,cAC7B,aAAa,UAAU,OAEvB,KAAK,MAAM,QAAQ,OAAO,QAAQ;GACtC;EACF;CACF;CAEA,aACE,QACA,UACM;EACN,MAAM,UAAU,KAAK,MAAM,QAAQ,IAAI,QAAQ;EAC/C,IACE,CAAC,WACD,QAAQ,eAAe,OAAO,cAC9B,QAAQ,UAAU,OAAO,SACzB,QAAQ,aAAa,KAAK,IAAI,GAE9B,MAAM,IAAI,iCACR,uCAAuC,SAAS,uBAClD;CAEJ;CAEA,MAAM,qBAEJ;EACA,OAAO,MAAM,KAAK,KAAK,MAAM,WAAW,QAAQ,CAAC,CAAC,CAC/C,MAAM,CAAC,IAAI,CAAC,OAAO,iBAAiB,GAAG,CAAC,CAAC,CAAC,CAC1C,KAAK,CAAC,KAAK,iBAAiB;GAAE;GAAK;EAAW,EAAE;CACrD;AACF"}
|
package/dist/esm/contracts.d.ts
CHANGED
|
@@ -98,6 +98,11 @@ export interface SandboxFs {
|
|
|
98
98
|
remove: (path: string) => Promise<void>;
|
|
99
99
|
rename: (from: string, to: string) => Promise<void>;
|
|
100
100
|
exists: (path: string) => Promise<boolean>;
|
|
101
|
+
/**
|
|
102
|
+
* Optional metadata lookup. Implementations must not follow symlinks.
|
|
103
|
+
* Returns undefined only for a confirmed missing path. All other errors reject.
|
|
104
|
+
*/
|
|
105
|
+
lstat?: (path: string) => Promise<SandboxFsStat | undefined>;
|
|
101
106
|
/** Optional — present only when `capabilities.fs` providers advertise watch. */
|
|
102
107
|
watch?: (path: string, onEvent: (event: {
|
|
103
108
|
type: string;
|
|
@@ -106,6 +111,20 @@ export interface SandboxFs {
|
|
|
106
111
|
stop: () => Promise<void>;
|
|
107
112
|
}>;
|
|
108
113
|
}
|
|
114
|
+
export type SandboxFsStat = {
|
|
115
|
+
type: 'file';
|
|
116
|
+
mode: number;
|
|
117
|
+
size: number;
|
|
118
|
+
} | {
|
|
119
|
+
type: 'dir';
|
|
120
|
+
mode: number;
|
|
121
|
+
} | {
|
|
122
|
+
type: 'symlink';
|
|
123
|
+
mode: number;
|
|
124
|
+
} | {
|
|
125
|
+
type: 'other';
|
|
126
|
+
mode: number;
|
|
127
|
+
};
|
|
109
128
|
/**
|
|
110
129
|
* Uniform git surface. Implementations either delegate to the provider's
|
|
111
130
|
* native git (when advertised) or desugar to `process.exec("git …")`, so the
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export { SandboxCapability, SandboxPolicyCapability, ToolBridgeProvisionerCapability, getSandbox, provideSandbox, getSandboxPolicy, provideSandboxPolicy, getToolBridgeProvisioner, provideToolBridgeProvisioner, } from './capabilities.js';
|
|
2
2
|
export { SandboxInstanceStoreCapability, getSandboxInstanceStore, provideSandboxInstanceStore, InMemorySandboxInstanceStore, defineSandboxInstanceStore, } from './instance-store.js';
|
|
3
3
|
export type { SandboxInstanceStore, SandboxInstanceRecord, } from './instance-store.js';
|
|
4
|
+
export { SandboxCheckpointError, SandboxCheckpointConflictError, SandboxCheckpointDuplicateIdError, SandboxCheckpointInvalidIdError, SandboxCheckpointInvalidEntryError, SandboxCheckpointParentMismatchError, SandboxCheckpointNotHeadError, SandboxCheckpointWriterConflictError, SandboxCheckpointWriterLostError, isForkCapableSandboxCheckpointStore, InMemorySandboxCheckpointStore, defineSandboxCheckpointStore, } from './checkpoint-store.js';
|
|
5
|
+
export type { SandboxCheckpoint, SandboxCheckpointStore, SandboxSnapshotEntry, SandboxSnapshotFileEntry, SandboxSnapshotDirectoryEntry, SandboxSnapshotArtifact, SandboxCheckpointErrorCode, SandboxCheckpointWriter, SandboxCheckpointWriterLease, SandboxCheckpointStoreOptions, SandboxCheckpointForkInput, SandboxCheckpointForkCapability, ForkCapableSandboxCheckpointStore, } from './checkpoint-store.js';
|
|
6
|
+
export { SandboxSnapshotError, defaultSandboxSnapshotPolicy } from './snapshots.js';
|
|
7
|
+
export type { SandboxSnapshotErrorCode, SandboxSnapshotPolicy, } from './snapshots.js';
|
|
8
|
+
export { memorySandboxSnapshots } from './memory-snapshots.js';
|
|
9
|
+
export type { MemorySandboxSnapshots, MemorySandboxSnapshotsOptions, } from './memory-snapshots.js';
|
|
10
|
+
export { createSandboxSnapshots } from './snapshot-operations.js';
|
|
11
|
+
export type { CreateSandboxSnapshotsInput, ForkSandboxSnapshotInput, ReadSandboxSnapshotArtifactInput, SandboxSnapshots, SaveSandboxSnapshotInput, SnapshotPersistence, } from './snapshot-operations.js';
|
|
12
|
+
export { createSnapshotTools } from './snapshot-tools.js';
|
|
13
|
+
export type { CreateSnapshotToolsOptions } from './snapshot-tools.js';
|
|
4
14
|
export { ProjectionCapability, getWorkspaceProjection, provideWorkspaceProjection, } from './projection.js';
|
|
5
15
|
export type { WorkspaceProjection } from './projection.js';
|
|
6
16
|
export { withSandbox } from './middleware.js';
|
|
@@ -14,7 +24,7 @@ export { createSecrets, bearer, isSecretRef, resolveSecret, resolveBearer, resol
|
|
|
14
24
|
export type { SecretRef, Secrets, BearerRef } from './secrets.js';
|
|
15
25
|
export { defineSandboxPolicy, evaluateCommand, commandAliases } from './policy.js';
|
|
16
26
|
export type { SandboxPolicy, PolicyDecision, CommandRules, CapabilityRules, } from './policy.js';
|
|
17
|
-
export type { SandboxProvider, SandboxHandle, SandboxCapabilities, SandboxFs, SandboxGit, SandboxProcess, SandboxPorts, SandboxEnv, SandboxChannel, SpawnHandle, ExecResult, ProcessOptions, SnapshotRef, SandboxCreateInput, SandboxResumeInput, SandboxRestoreInput, SandboxDestroyInput, } from './contracts.js';
|
|
27
|
+
export type { SandboxProvider, SandboxHandle, SandboxCapabilities, SandboxFs, SandboxFsStat, SandboxGit, SandboxProcess, SandboxPorts, SandboxEnv, SandboxChannel, SpawnHandle, ExecResult, ProcessOptions, SnapshotRef, SandboxCreateInput, SandboxResumeInput, SandboxRestoreInput, SandboxDestroyInput, } from './contracts.js';
|
|
18
28
|
export { bootstrapWorkspace, detectPackageManager, DEFAULT_WORKSPACE_ROOT, } from './bootstrap.js';
|
|
19
29
|
export { resolveHarnessCwd } from './harness-cwd.js';
|
|
20
30
|
export type { BootstrapResult } from './bootstrap.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { SandboxCapability, SandboxPolicyCapability, ToolBridgeProvisionerCapability, getSandbox, getSandboxPolicy, getToolBridgeProvisioner, provideSandbox, provideSandboxPolicy, provideToolBridgeProvisioner } from "./capabilities.js";
|
|
2
2
|
import { InMemorySandboxInstanceStore, SandboxInstanceStoreCapability, defineSandboxInstanceStore, getSandboxInstanceStore, provideSandboxInstanceStore } from "./instance-store.js";
|
|
3
|
+
import { InMemorySandboxCheckpointStore, SandboxCheckpointConflictError, SandboxCheckpointDuplicateIdError, SandboxCheckpointError, SandboxCheckpointInvalidEntryError, SandboxCheckpointInvalidIdError, SandboxCheckpointNotHeadError, SandboxCheckpointParentMismatchError, SandboxCheckpointWriterConflictError, SandboxCheckpointWriterLostError, defineSandboxCheckpointStore, isForkCapableSandboxCheckpointStore } from "./checkpoint-store.js";
|
|
4
|
+
import { SandboxSnapshotError, defaultSandboxSnapshotPolicy } from "./snapshots.js";
|
|
5
|
+
import { bearer, createSecrets, isSecretRef, resolveAllSecrets, resolveBearer, resolveSecret } from "./secrets.js";
|
|
6
|
+
import { computeSandboxKey, computeWorkspaceHash } from "./key.js";
|
|
7
|
+
import { resolveHarnessCwd } from "./harness-cwd.js";
|
|
8
|
+
import { discoverSkillDirs, formatWorkspaceScriptsSection, mergeAgentsContent, resolveGitSkillDir, writeAgentsFile } from "./agents-file.js";
|
|
9
|
+
import { DEFAULT_WORKSPACE_ROOT, bootstrapWorkspace, detectPackageManager } from "./bootstrap.js";
|
|
10
|
+
import { defineSandbox } from "./sandbox.js";
|
|
11
|
+
import { createSandboxSnapshots } from "./snapshot-operations.js";
|
|
12
|
+
import { memorySandboxSnapshots } from "./memory-snapshots.js";
|
|
13
|
+
import { createSnapshotTools } from "./snapshot-tools.js";
|
|
3
14
|
import { ProjectionCapability, getWorkspaceProjection, provideWorkspaceProjection } from "./projection.js";
|
|
4
15
|
import { DEFAULT_JOURNAL_DIR, EXIT_SENTINEL_KEY, EXIT_SENTINEL_NONCE_KEY, decodeJournalRunId, encodeRunId, exitSentinelLine, journalExistsCommand, journalExitProbeCommand, journalFollowCommand, journalListCommand, journalMtimeListCommand, journalPaths, journalReadCommand, journaledCommand, parseExitSentinel, parseJournalExit, parseJournalMtimeListing } from "./journal.js";
|
|
5
16
|
import { chunkFingerprint, chunkFingerprintIgnoringThreadId, chunkThreadId, createRunScopedIdGen } from "./chunk-identity.js";
|
|
6
17
|
import { DEFAULT_MAX_OUT_OF_BAND_SKIP, JournalReplayDivergedError, JournalReplayThreadIdMismatchError, alignToStoredLog, isBridgeCustomChunk } from "./align.js";
|
|
7
18
|
import { DurableAttachNotSupportedError, DurableRunIdRequiredError, DurableThreadIdRequiredError, SandboxDurabilityCapability, alignedIfAttaching, getSandboxDurability, journalOptionsFor, provideSandboxDurability, resolveDurableRunId, resolveDurableThreadId } from "./durability.js";
|
|
8
|
-
import { computeSandboxKey, computeWorkspaceHash } from "./key.js";
|
|
9
|
-
import { bearer, createSecrets, isSecretRef, resolveAllSecrets, resolveBearer, resolveSecret } from "./secrets.js";
|
|
10
19
|
import { isSandboxToolCall } from "./tool-history.js";
|
|
11
|
-
import { resolveHarnessCwd } from "./harness-cwd.js";
|
|
12
|
-
import { discoverSkillDirs, formatWorkspaceScriptsSection, mergeAgentsContent, resolveGitSkillDir, writeAgentsFile } from "./agents-file.js";
|
|
13
|
-
import { DEFAULT_WORKSPACE_ROOT, bootstrapWorkspace, detectPackageManager } from "./bootstrap.js";
|
|
14
20
|
import { diffSnapshots, watchWorkspace } from "./watch.js";
|
|
15
21
|
import { withSandbox } from "./middleware.js";
|
|
16
|
-
import { defineSandbox } from "./sandbox.js";
|
|
17
22
|
import { agentSkill, defineWorkspace, fileSkill, gitSkill, gitSource, githubRepo, localSource, mcpSkill } from "./workspace.js";
|
|
18
23
|
import { commandAliases, defineSandboxPolicy, evaluateCommand } from "./policy.js";
|
|
19
24
|
import { createExecBackedGit } from "./git-exec.js";
|
|
@@ -32,4 +37,4 @@ import { createBridgeEventChannel, mergeChunkStreams } from "./bridge-events.js"
|
|
|
32
37
|
import { executeHostTool, httpRemoteToolExecutor, isToolExecRequest, remoteToolStubs, toolDescriptors } from "./remote-tools.js";
|
|
33
38
|
import { APPROVAL_REQUESTED_EVENT, approvalId, buildApprovalRequestedEvent, resolveApproval } from "./approvals.js";
|
|
34
39
|
import { MissingSandboxError, UnsupportedCapabilityError } from "./errors.js";
|
|
35
|
-
export { APPROVAL_REQUESTED_EVENT, BRIDGED_MCP_SERVER_NAME, DEFAULT_ATTACH_JOURNAL_WAIT_MS, DEFAULT_ATTACH_PROBE_INTERVAL_MS, DEFAULT_EXIT_PROBE_BYTES, DEFAULT_FENCE_QUIET_MS, DEFAULT_JOURNAL_DIR, DEFAULT_JOURNAL_POLL_MS, DEFAULT_MAX_DELETES, DEFAULT_MAX_OUT_OF_BAND_SKIP, DEFAULT_MAX_RUNS, DEFAULT_ORPHAN_TTL_MS, DEFAULT_RUN_BUDGET_MS, DEFAULT_WORKSPACE_ROOT, DurableAttachNotSupportedError, DurableRunIdRequiredError, DurableThreadIdRequiredError, EXIT_SENTINEL_KEY, EXIT_SENTINEL_NONCE_KEY, InMemorySandboxInstanceStore, JournalAttachUnavailableError, JournalReplayDivergedError, JournalReplayThreadIdMismatchError, MissingSandboxError, ProjectionCapability, RunClaimLostError, RunClaimNotAcquiredError, RunController, RunDriverPipeOutsideClaimError, SandboxCapability, SandboxDurabilityCapability, SandboxInstanceStoreCapability, SandboxPolicyCapability, SandboxReclaimFailedError, ToolBridgeProvisionerCapability, UnsupportedCapabilityError, agentSkill, alignToStoredLog, alignedIfAttaching, approvalId, awaitAttachableJournal, bearer, bootstrapWorkspace, buildApprovalRequestedEvent, chunkFingerprint, chunkFingerprintIgnoringThreadId, chunkThreadId, commandAliases, computeSandboxKey, computeWorkspaceHash, createBridgeEventChannel, createExecBackedGit, createRunScopedIdGen, createSecrets, createToolBridgeCore, decodeBase64Stream, decodeJournalRunId, defineSandbox, defineSandboxInstanceStore, defineSandboxPolicy, defineWorkspace, detectPackageManager, diffSnapshots, discoverSkillDirs, encodeRunId, evaluateCommand, executeHostTool, exitSentinelLine, fileSkill, formatWorkspaceScriptsSection, getSandbox, getSandboxDurability, getSandboxInstanceStore, getSandboxPolicy, getToolBridgeProvisioner, getWorkspaceProjection, gitSkill, gitSource, githubRepo, handleBridgeJsonRpc, hostForSandbox, httpRemoteToolExecutor, isBridgeCustomChunk, isSandboxToolCall, isSecretRef, isToolExecRequest, journalExistsCommand, journalExitProbeCommand, journalFollowCommand, journalListCommand, journalMtimeListCommand, journalOptionsFor, journalPaths, journalReadCommand, journalReadStrategy, journaledCommand, localSource, mcpSkill, mergeAgentsContent, mergeChunkStreams, nodeHttpBridgeProvisioner, parseExitSentinel, parseJournalExit, parseJournalMtimeListing, pipeToRunLog, probeRunExit, provideSandbox, provideSandboxDurability, provideSandboxInstanceStore, provideSandboxPolicy, provideToolBridgeProvisioner, provideWorkspaceProjection, pruneJournals, readJournal, readJournalNdjson, reapDetachedRuns, reclaimSandbox, remoteToolStubs, resolveAllSecrets, resolveApproval, resolveBearer, resolveDurableRunId, resolveDurableThreadId, resolveGitSkillDir, resolveHarnessCwd, resolveSecret, sandboxReclaimer, sandboxRunDriver, spawnNdjson, startHostToolBridge, startJournaledAgent, timingSafeBearerEqual, toJournalLines, toLines, toolDescriptors, watchWorkspace, withSandbox, writeAgentsFile };
|
|
40
|
+
export { APPROVAL_REQUESTED_EVENT, BRIDGED_MCP_SERVER_NAME, DEFAULT_ATTACH_JOURNAL_WAIT_MS, DEFAULT_ATTACH_PROBE_INTERVAL_MS, DEFAULT_EXIT_PROBE_BYTES, DEFAULT_FENCE_QUIET_MS, DEFAULT_JOURNAL_DIR, DEFAULT_JOURNAL_POLL_MS, DEFAULT_MAX_DELETES, DEFAULT_MAX_OUT_OF_BAND_SKIP, DEFAULT_MAX_RUNS, DEFAULT_ORPHAN_TTL_MS, DEFAULT_RUN_BUDGET_MS, DEFAULT_WORKSPACE_ROOT, DurableAttachNotSupportedError, DurableRunIdRequiredError, DurableThreadIdRequiredError, EXIT_SENTINEL_KEY, EXIT_SENTINEL_NONCE_KEY, InMemorySandboxCheckpointStore, InMemorySandboxInstanceStore, JournalAttachUnavailableError, JournalReplayDivergedError, JournalReplayThreadIdMismatchError, MissingSandboxError, ProjectionCapability, RunClaimLostError, RunClaimNotAcquiredError, RunController, RunDriverPipeOutsideClaimError, SandboxCapability, SandboxCheckpointConflictError, SandboxCheckpointDuplicateIdError, SandboxCheckpointError, SandboxCheckpointInvalidEntryError, SandboxCheckpointInvalidIdError, SandboxCheckpointNotHeadError, SandboxCheckpointParentMismatchError, SandboxCheckpointWriterConflictError, SandboxCheckpointWriterLostError, SandboxDurabilityCapability, SandboxInstanceStoreCapability, SandboxPolicyCapability, SandboxReclaimFailedError, SandboxSnapshotError, ToolBridgeProvisionerCapability, UnsupportedCapabilityError, agentSkill, alignToStoredLog, alignedIfAttaching, approvalId, awaitAttachableJournal, bearer, bootstrapWorkspace, buildApprovalRequestedEvent, chunkFingerprint, chunkFingerprintIgnoringThreadId, chunkThreadId, commandAliases, computeSandboxKey, computeWorkspaceHash, createBridgeEventChannel, createExecBackedGit, createRunScopedIdGen, createSandboxSnapshots, createSecrets, createSnapshotTools, createToolBridgeCore, decodeBase64Stream, decodeJournalRunId, defaultSandboxSnapshotPolicy, defineSandbox, defineSandboxCheckpointStore, defineSandboxInstanceStore, defineSandboxPolicy, defineWorkspace, detectPackageManager, diffSnapshots, discoverSkillDirs, encodeRunId, evaluateCommand, executeHostTool, exitSentinelLine, fileSkill, formatWorkspaceScriptsSection, getSandbox, getSandboxDurability, getSandboxInstanceStore, getSandboxPolicy, getToolBridgeProvisioner, getWorkspaceProjection, gitSkill, gitSource, githubRepo, handleBridgeJsonRpc, hostForSandbox, httpRemoteToolExecutor, isBridgeCustomChunk, isForkCapableSandboxCheckpointStore, isSandboxToolCall, isSecretRef, isToolExecRequest, journalExistsCommand, journalExitProbeCommand, journalFollowCommand, journalListCommand, journalMtimeListCommand, journalOptionsFor, journalPaths, journalReadCommand, journalReadStrategy, journaledCommand, localSource, mcpSkill, memorySandboxSnapshots, mergeAgentsContent, mergeChunkStreams, nodeHttpBridgeProvisioner, parseExitSentinel, parseJournalExit, parseJournalMtimeListing, pipeToRunLog, probeRunExit, provideSandbox, provideSandboxDurability, provideSandboxInstanceStore, provideSandboxPolicy, provideToolBridgeProvisioner, provideWorkspaceProjection, pruneJournals, readJournal, readJournalNdjson, reapDetachedRuns, reclaimSandbox, remoteToolStubs, resolveAllSecrets, resolveApproval, resolveBearer, resolveDurableRunId, resolveDurableThreadId, resolveGitSkillDir, resolveHarnessCwd, resolveSecret, sandboxReclaimer, sandboxRunDriver, spawnNdjson, startHostToolBridge, startJournaledAgent, timingSafeBearerEqual, toJournalLines, toLines, toolDescriptors, watchWorkspace, withSandbox, writeAgentsFile };
|