@tangle-network/agent-provider-tangle 0.13.2 → 0.14.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 +92 -4
- package/dist/exact-process.js +5 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/tangle-capabilities.d.ts +14 -3
- package/dist/tangle-capabilities.js +46 -19
- package/dist/tangle-contract-safety.d.ts +2 -0
- package/dist/tangle-contract-safety.js +2 -0
- package/dist/tangle-environment.d.ts +2 -0
- package/dist/tangle-environment.js +18 -3
- package/dist/tangle-observation.js +1 -1
- package/dist/tangle-prompt.js +1 -0
- package/dist/tangle-provider.js +51 -7
- package/dist/tangle-types.d.ts +115 -4
- package/dist/tangle-workspace-branching.d.ts +24 -0
- package/dist/tangle-workspace-branching.js +1287 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# @tangle-network/agent-provider-tangle
|
|
2
2
|
|
|
3
3
|
Wraps `@tangle-network/sandbox` as an `AgentEnvironmentProvider`.
|
|
4
|
-
The peer range is `>=0.
|
|
5
|
-
The floor is 0.
|
|
4
|
+
The peer range is `>=0.33.1 <1.0.0`, and this package is developed and tested against 0.33.1.
|
|
5
|
+
The floor is 0.33.1 because workspace branching uses the keyed snapshot, fork,
|
|
6
|
+
lookup, and cleanup operations added to that SDK.
|
|
6
7
|
The provider fails closed when the configured backend or its catalog entry cannot be read.
|
|
7
8
|
Newer SDKs may also provide `getBackend()` as a lookup over the same catalog.
|
|
8
9
|
|
|
@@ -104,8 +105,95 @@ After `session.prompt()` admits another turn, that session object's `controlRef`
|
|
|
104
105
|
Sandbox keeps execution identifiers optional for older or unproven service paths, so this adapter fails closed when a dispatch or prompt does not return one and never falls back to latest-session state.
|
|
105
106
|
Sessions reconstructed without a control reference may start a new prompt, but result lookup, cancellation, and cursor replay fail before calling Sandbox because those operations could otherwise select the newest unrelated execution.
|
|
106
107
|
It also rejects `contextTransfer` and `nativeContinuation` inputs explicitly until those operations have native Sandbox support instead of silently dropping them.
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
|
|
109
|
+
### Workspace branching
|
|
110
|
+
|
|
111
|
+
`environment.workspaceBranching` is the single operation surface for creating
|
|
112
|
+
and recovering a checkpoint, forking one managed child, and cleaning both
|
|
113
|
+
resources in dependency order.
|
|
114
|
+
The adapter advertises `branching.checkpoint`, `branching.fork`, `retrySafe`,
|
|
115
|
+
`lookup`, and `cleanup` only when the linked SDK exposes the complete managed
|
|
116
|
+
surface: keyed `snapshot` and `fork`, operation lookup, inventory recovery, and
|
|
117
|
+
explicit deletion outcomes.
|
|
118
|
+
An incomplete SDK surface clears every branching flag and omits
|
|
119
|
+
`environment.workspaceBranching`.
|
|
120
|
+
|
|
121
|
+
Every operation validates the canonical agent-interface request digest before
|
|
122
|
+
calling Sandbox.
|
|
123
|
+
Retries with the same key replay the original resource, while changed material
|
|
124
|
+
returns a conflict containing the original interface digest.
|
|
125
|
+
The provider stores a bounded request marker in snapshot tags and child
|
|
126
|
+
metadata so a fresh process can recover the exact interface digest, but the
|
|
127
|
+
marker only names a candidate: the Sandbox operation ledger still has to report
|
|
128
|
+
a settled success before the adapter returns the resource.
|
|
129
|
+
Checkpoint deletion reports `in_use` with every verified child that still
|
|
130
|
+
references it; delete the child first, then retry checkpoint deletion.
|
|
131
|
+
The adapter never treats an SDK response without an explicit idempotency or
|
|
132
|
+
deletion outcome as success.
|
|
133
|
+
|
|
134
|
+
After a provider process restart, `provider.workspaceBranching.forEnvironment()`
|
|
135
|
+
returns a fresh source-scoped handle for lookup and cleanup, or `null` when it
|
|
136
|
+
cannot prove the complete operation surface.
|
|
137
|
+
|
|
138
|
+
Recovery reads the account inventory through Sandbox offset pages of at most
|
|
139
|
+
1,000 sandboxes and continues until a short page proves the inventory is
|
|
140
|
+
complete.
|
|
141
|
+
The adapter also reads checkpoints from the previous marker format while writing only the current bounded format.
|
|
142
|
+
Malformed, repeated, failed, or over-bound pages return `unknown` and do not
|
|
143
|
+
mutate a resource.
|
|
144
|
+
Sandbox exposes snapshots only through the live source instance.
|
|
145
|
+
If that source was deleted, this provider cannot recover its checkpoints or
|
|
146
|
+
forked children through the public SDK.
|
|
147
|
+
Clean branch resources before deleting the source or use a platform reaper.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import {
|
|
151
|
+
workspaceCheckpointRequestDigest,
|
|
152
|
+
workspaceForkRequestDigest,
|
|
153
|
+
} from '@tangle-network/agent-interface'
|
|
154
|
+
|
|
155
|
+
const checkpoint = await environment.workspaceBranching?.checkpoint({
|
|
156
|
+
source: exactRun,
|
|
157
|
+
idempotencyKey: 'checkpoint-before-analysis',
|
|
158
|
+
requestDigest: workspaceCheckpointRequestDigest({ source: exactRun }),
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if (checkpoint?.status === 'created' || checkpoint?.status === 'replayed') {
|
|
162
|
+
const fork = await environment.workspaceBranching?.fork({
|
|
163
|
+
checkpoint: checkpoint.checkpoint,
|
|
164
|
+
placement: { kind: 'sandbox', sandboxId: 'analysis-worker' },
|
|
165
|
+
idempotencyKey: 'analysis-worker',
|
|
166
|
+
requestDigest: workspaceForkRequestDigest({
|
|
167
|
+
checkpoint: checkpoint.checkpoint,
|
|
168
|
+
placement: { kind: 'sandbox', sandboxId: 'analysis-worker' },
|
|
169
|
+
}),
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Confidential forks
|
|
175
|
+
|
|
176
|
+
Sandbox returns raw TEE evidence, not a verified claim.
|
|
177
|
+
Pass `confidentialAttestationVerifier` to `createTangleProvider` to connect a
|
|
178
|
+
trusted provider-key and measurement verifier.
|
|
179
|
+
The callback receives the raw report, the expected environment binding, and
|
|
180
|
+
the canonical request-bound attestation material.
|
|
181
|
+
It returns a provider key id, signature, and optional normalized measurement
|
|
182
|
+
only after verification succeeds.
|
|
183
|
+
Returning `null`, throwing, a mismatched measurement, or a copied quote leaves
|
|
184
|
+
the result unverified while preserving `confidentialRequested: true`.
|
|
185
|
+
The same unverified result occurs when no trusted verifier is configured.
|
|
186
|
+
The adapter does not trust the requested nonce, child metadata, or any legacy
|
|
187
|
+
`confidential: true` assertion as proof.
|
|
188
|
+
`verifyTangleQuote` below represents a verifier supplied by the caller.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const provider = createTangleProvider({
|
|
192
|
+
client: new Sandbox({ apiKey: process.env.TANGLE_API_KEY }),
|
|
193
|
+
confidentialAttestationVerifier: async ({ report, attestation }) =>
|
|
194
|
+
(await verifyTangleQuote({ report, attestation })) ?? null,
|
|
195
|
+
})
|
|
196
|
+
```
|
|
109
197
|
|
|
110
198
|
## Environment observation
|
|
111
199
|
|
package/dist/exact-process.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { assertBoundedJson, attachCleanupHandle, awaitWithSignal, boundedIdentifier, boundedString, exactProcessRequestDigest, isBoundedJson, MAX_LIST_RESULTS, } from "./tangle-contract-safety.js";
|
|
1
|
+
import { assertBoundedJson, attachCleanupHandle, awaitWithSignal, boundedIdentifier, boundedString, exactProcessRequestDigest, isBoundedJson, MAX_LIST_RESULTS, SANDBOX_LIST_PAGE_SIZE, } from "./tangle-contract-safety.js";
|
|
2
2
|
import { sandboxInstanceAsExactProcessEnvironment } from "./tangle-exact-process-environment.js";
|
|
3
3
|
import { assertExactProcessSandbox, assertSupportedProviderOptions, assertUnreservedMetadata, EXACT_PROCESS_METADATA_KEY, isExactProcessRequestConflict, isExactProcessSandbox, metadataMatches, assertSignalOptions, } from "./tangle-exact-process-validation.js";
|
|
4
4
|
const IMMUTABLE_TANGLE_IMAGE = /^(?:sha256:[a-f0-9]{64}|\S+@sha256:[a-f0-9]{64})$/i;
|
|
5
|
-
const LIST_PAGE_SIZE = 1_000;
|
|
6
5
|
export function createTangleExactProcessProvider(input) {
|
|
7
6
|
const { client, options, providerName } = input;
|
|
8
7
|
boundedIdentifier(providerName, "Tangle exact process provider");
|
|
@@ -98,7 +97,7 @@ export function createTangleExactProcessProvider(input) {
|
|
|
98
97
|
assertSupportedProviderOptions(query?.providerOptions);
|
|
99
98
|
assertExactProcessListQuery(query);
|
|
100
99
|
const matches = [];
|
|
101
|
-
for (let offset = 0;; offset +=
|
|
100
|
+
for (let offset = 0;; offset += SANDBOX_LIST_PAGE_SIZE) {
|
|
102
101
|
if (offset > MAX_LIST_RESULTS) {
|
|
103
102
|
throw new Error("Tangle exact process list exceeded its page bound");
|
|
104
103
|
}
|
|
@@ -107,11 +106,11 @@ export function createTangleExactProcessProvider(input) {
|
|
|
107
106
|
...(options.teamId
|
|
108
107
|
? { scope: `team:${options.teamId}` }
|
|
109
108
|
: { scope: "personal" }),
|
|
110
|
-
limit:
|
|
109
|
+
limit: SANDBOX_LIST_PAGE_SIZE,
|
|
111
110
|
offset,
|
|
112
111
|
...(signal ? { signal } : {}),
|
|
113
112
|
}), signal);
|
|
114
|
-
if (!Array.isArray(page) || page.length >
|
|
113
|
+
if (!Array.isArray(page) || page.length > SANDBOX_LIST_PAGE_SIZE) {
|
|
115
114
|
throw new Error("Tangle exact process list returned an invalid page size");
|
|
116
115
|
}
|
|
117
116
|
if (offset + page.length > MAX_LIST_RESULTS) {
|
|
@@ -132,7 +131,7 @@ export function createTangleExactProcessProvider(input) {
|
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
}
|
|
135
|
-
if (page.length <
|
|
134
|
+
if (page.length < SANDBOX_LIST_PAGE_SIZE)
|
|
136
135
|
return matches;
|
|
137
136
|
}
|
|
138
137
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,6 @@ export type { TangleExactProcessOptions } from "./tangle-types.js";
|
|
|
2
2
|
export * from "./tangle-types.js";
|
|
3
3
|
export { createTangleProvider } from "./tangle-provider.js";
|
|
4
4
|
export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
|
|
5
|
+
export { createTangleWorkspaceBranching, supportsWorkspaceBranching, } from "./tangle-workspace-branching.js";
|
|
6
|
+
export type { TangleWorkspaceBranchingOptions } from "./tangle-workspace-branching.js";
|
|
5
7
|
export { safeEndpointFromConnection } from "./tangle-observation.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from "./tangle-types.js";
|
|
2
2
|
export { createTangleProvider } from "./tangle-provider.js";
|
|
3
3
|
export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
|
|
4
|
+
export { createTangleWorkspaceBranching, supportsWorkspaceBranching, } from "./tangle-workspace-branching.js";
|
|
4
5
|
export { safeEndpointFromConnection } from "./tangle-observation.js";
|
|
@@ -4,6 +4,7 @@ import type { SandboxClientLike, SandboxInstanceLike } from "./tangle-types.js";
|
|
|
4
4
|
import type { DeploymentCapabilitySupport } from "./tangle-deployment-capabilities.js";
|
|
5
5
|
import type { ResourceProfile } from "@tangle-network/agent-interface";
|
|
6
6
|
import { type ObservationSurfaceSupport } from "./tangle-observation.js";
|
|
7
|
+
import type { TangleConfidentialAttestationVerifier } from "./tangle-types.js";
|
|
7
8
|
/**
|
|
8
9
|
* The full capability document this adapter supports when the Sandbox client
|
|
9
10
|
* implements every optional method.
|
|
@@ -47,6 +48,10 @@ export interface SandboxCapabilitySupport {
|
|
|
47
48
|
interactiveTerminal: boolean;
|
|
48
49
|
/** The SDK can drive the existing native TUI and read its terminal metadata. */
|
|
49
50
|
interactiveAgent: boolean;
|
|
51
|
+
/** Snapshot/fork methods and inventory recovery are all present. */
|
|
52
|
+
workspaceBranching: boolean;
|
|
53
|
+
/** The SDK can request raw TEE evidence for this sandbox. */
|
|
54
|
+
confidentialAttestation: boolean;
|
|
50
55
|
}
|
|
51
56
|
export declare function sandboxCapabilitySupport(box: SandboxInstanceLike, client: SandboxClientLike, requestedResources?: ResourceProfile): SandboxCapabilitySupport;
|
|
52
57
|
/**
|
|
@@ -99,7 +104,9 @@ export declare function tangleInteractiveAgentSupported(declared: AgentEnvironme
|
|
|
99
104
|
* idempotency needs the deployment to honor the exact reference that
|
|
100
105
|
* identifies a repeated turn.
|
|
101
106
|
*/
|
|
102
|
-
export declare function narrowedTangleCapabilities(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport, deployment: DeploymentCapabilitySupport
|
|
107
|
+
export declare function narrowedTangleCapabilities(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport, deployment: DeploymentCapabilitySupport, options?: {
|
|
108
|
+
confidentialAttestationVerifier?: TangleConfidentialAttestationVerifier;
|
|
109
|
+
}): AgentEnvironmentCapabilities;
|
|
103
110
|
/**
|
|
104
111
|
* Narrow provider-level claims to facts the client can prove before any
|
|
105
112
|
* sandbox exists.
|
|
@@ -112,7 +119,9 @@ export declare function narrowedTangleCapabilities(declared: AgentEnvironmentCap
|
|
|
112
119
|
* the answer as `AgentEnvironment.capabilities`, which is the document a
|
|
113
120
|
* caller reads to decide which operation to offer against that environment.
|
|
114
121
|
*/
|
|
115
|
-
export declare function capabilitiesForClient(declared: AgentEnvironmentCapabilities, client: SandboxClientLike
|
|
122
|
+
export declare function capabilitiesForClient(declared: AgentEnvironmentCapabilities, client: SandboxClientLike, options?: {
|
|
123
|
+
confidentialAttestationVerifier?: TangleConfidentialAttestationVerifier;
|
|
124
|
+
}): AgentEnvironmentCapabilities;
|
|
116
125
|
/**
|
|
117
126
|
* Freeze a capability document before an environment publishes it.
|
|
118
127
|
*
|
|
@@ -125,4 +134,6 @@ export declare function frozenCapabilityDocument<T>(document: T): T;
|
|
|
125
134
|
* Narrow a declared capability document to what this Sandbox instance backs
|
|
126
135
|
* and what the deployment behind it reports.
|
|
127
136
|
*/
|
|
128
|
-
export declare function capabilitiesForSandbox(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport, deployment: DeploymentCapabilitySupport
|
|
137
|
+
export declare function capabilitiesForSandbox(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport, deployment: DeploymentCapabilitySupport, options?: {
|
|
138
|
+
confidentialAttestationVerifier?: TangleConfidentialAttestationVerifier;
|
|
139
|
+
}): AgentEnvironmentCapabilities;
|
|
@@ -4,6 +4,7 @@ import { ADAPTER_CEILING_DEPLOYMENT, deploymentBacksInteractiveAgent, deployment
|
|
|
4
4
|
import { clientObservationSurfaceSupport, observationSurfaceSupport, } from "./tangle-observation.js";
|
|
5
5
|
import { sandboxBacksInteractiveTerminal } from "./tangle-terminal.js";
|
|
6
6
|
import { sandboxBacksInteractiveAgent } from "./tangle-interactive.js";
|
|
7
|
+
import { supportsWorkspaceBranching } from "./tangle-workspace-branching.js";
|
|
7
8
|
/**
|
|
8
9
|
* The full capability document this adapter supports when the Sandbox client
|
|
9
10
|
* implements every optional method.
|
|
@@ -72,14 +73,20 @@ export function defaultTangleSandboxCapabilities(harness) {
|
|
|
72
73
|
cancellationIdempotency: true,
|
|
73
74
|
},
|
|
74
75
|
workspace: { read: true, write: true, exec: true, git: false, upload: true, download: true },
|
|
75
|
-
// Sandbox
|
|
76
|
-
//
|
|
77
|
-
branching: {
|
|
76
|
+
// The complete Sandbox SDK surface backs this contract. Narrowing below
|
|
77
|
+
// removes every flag when any recovery or cleanup method is absent.
|
|
78
|
+
branching: {
|
|
79
|
+
checkpoint: true,
|
|
80
|
+
fork: true,
|
|
81
|
+
retrySafe: true,
|
|
82
|
+
lookup: true,
|
|
83
|
+
cleanup: true,
|
|
84
|
+
},
|
|
78
85
|
placement: true,
|
|
79
86
|
usage: false,
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
confidential:
|
|
87
|
+
// This is intent only. Narrowing requires both raw TEE evidence and the
|
|
88
|
+
// caller's external provider-key verifier before the flag survives.
|
|
89
|
+
confidential: true,
|
|
83
90
|
// Observation surfaces are declared as intent and narrowed per sandbox to
|
|
84
91
|
// the sources that can put a value on each one.
|
|
85
92
|
observation: {
|
|
@@ -171,6 +178,8 @@ export function sandboxCapabilitySupport(box, client, requestedResources) {
|
|
|
171
178
|
observation: observationSurfaceSupport(box, client, requestedResources),
|
|
172
179
|
interactiveTerminal: sandboxBacksInteractiveTerminal(box),
|
|
173
180
|
interactiveAgent: sandboxBacksInteractiveAgent(box),
|
|
181
|
+
workspaceBranching: supportsWorkspaceBranching(box, client),
|
|
182
|
+
confidentialAttestation: typeof box.getTeeAttestation === "function",
|
|
174
183
|
};
|
|
175
184
|
}
|
|
176
185
|
/**
|
|
@@ -231,6 +240,8 @@ export function clientCapabilitySupport(client) {
|
|
|
231
240
|
observation,
|
|
232
241
|
interactiveTerminal: true,
|
|
233
242
|
interactiveAgent: false,
|
|
243
|
+
workspaceBranching: false,
|
|
244
|
+
confidentialAttestation: false,
|
|
234
245
|
};
|
|
235
246
|
}
|
|
236
247
|
/**
|
|
@@ -292,7 +303,14 @@ export function tangleInteractiveAgentSupported(declared, support, deployment) {
|
|
|
292
303
|
* idempotency needs the deployment to honor the exact reference that
|
|
293
304
|
* identifies a repeated turn.
|
|
294
305
|
*/
|
|
295
|
-
export function narrowedTangleCapabilities(declared, support, deployment) {
|
|
306
|
+
export function narrowedTangleCapabilities(declared, support, deployment, options) {
|
|
307
|
+
// JavaScript callers can omit required fields before schema validation.
|
|
308
|
+
// Missing branching intent must disable the surface, not throw while it is
|
|
309
|
+
// being narrowed.
|
|
310
|
+
const declaredBranching = declared.branching ?? {
|
|
311
|
+
checkpoint: false,
|
|
312
|
+
fork: false,
|
|
313
|
+
};
|
|
296
314
|
const supportsRetainedControl = tangleRetainedControlSupported(declared, support, deployment);
|
|
297
315
|
const supportsDetach = support.dispatchPrompt && support.session && deployment.exactDispatch;
|
|
298
316
|
// A cleared fact forces false; a held fact passes the declared value
|
|
@@ -325,14 +343,18 @@ export function narrowedTangleCapabilities(declared, support, deployment) {
|
|
|
325
343
|
upload: support.write ? declared.workspace.upload : false,
|
|
326
344
|
download: support.read ? declared.workspace.download : false,
|
|
327
345
|
},
|
|
328
|
-
branching
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
346
|
+
// An incomplete Sandbox surface clears every branching flag together. A
|
|
347
|
+
// partial claim would let a caller start an operation it cannot recover.
|
|
348
|
+
branching: support.workspaceBranching
|
|
349
|
+
? { ...declaredBranching }
|
|
350
|
+
: {
|
|
351
|
+
...declaredBranching,
|
|
352
|
+
checkpoint: false,
|
|
353
|
+
fork: false,
|
|
354
|
+
...(declaredBranching.retrySafe !== undefined ? { retrySafe: false } : {}),
|
|
355
|
+
...(declaredBranching.lookup !== undefined ? { lookup: false } : {}),
|
|
356
|
+
...(declaredBranching.cleanup !== undefined ? { cleanup: false } : {}),
|
|
357
|
+
},
|
|
336
358
|
placement: support.placement ? declared.placement : false,
|
|
337
359
|
usage: false,
|
|
338
360
|
...(declared.observation === undefined
|
|
@@ -358,6 +380,11 @@ export function narrowedTangleCapabilities(declared, support, deployment) {
|
|
|
358
380
|
delete narrowed.nativeContinuation;
|
|
359
381
|
if (!supportsRetainedControl)
|
|
360
382
|
delete narrowed.retainedControl;
|
|
383
|
+
if (narrowed.confidential &&
|
|
384
|
+
(!support.confidentialAttestation ||
|
|
385
|
+
typeof options?.confidentialAttestationVerifier !== "function")) {
|
|
386
|
+
narrowed.confidential = false;
|
|
387
|
+
}
|
|
361
388
|
return narrowed;
|
|
362
389
|
}
|
|
363
390
|
/**
|
|
@@ -417,8 +444,8 @@ function narrowedInteractiveAgent(declared, supported) {
|
|
|
417
444
|
* the answer as `AgentEnvironment.capabilities`, which is the document a
|
|
418
445
|
* caller reads to decide which operation to offer against that environment.
|
|
419
446
|
*/
|
|
420
|
-
export function capabilitiesForClient(declared, client) {
|
|
421
|
-
return narrowedTangleCapabilities(declared, clientCapabilitySupport(client), ADAPTER_CEILING_DEPLOYMENT);
|
|
447
|
+
export function capabilitiesForClient(declared, client, options) {
|
|
448
|
+
return narrowedTangleCapabilities(declared, clientCapabilitySupport(client), ADAPTER_CEILING_DEPLOYMENT, options);
|
|
422
449
|
}
|
|
423
450
|
/**
|
|
424
451
|
* Freeze a capability document before an environment publishes it.
|
|
@@ -434,6 +461,6 @@ export function frozenCapabilityDocument(document) {
|
|
|
434
461
|
* Narrow a declared capability document to what this Sandbox instance backs
|
|
435
462
|
* and what the deployment behind it reports.
|
|
436
463
|
*/
|
|
437
|
-
export function capabilitiesForSandbox(declared, support, deployment) {
|
|
438
|
-
return narrowedTangleCapabilities(declared, support, deployment);
|
|
464
|
+
export function capabilitiesForSandbox(declared, support, deployment, options) {
|
|
465
|
+
return narrowedTangleCapabilities(declared, support, deployment, options);
|
|
439
466
|
}
|
|
@@ -2,6 +2,8 @@ import type { CreateAgentExactProcessEnvironmentInput } from "@tangle-network/ag
|
|
|
2
2
|
import type { TangleExactProcessOptions } from "./tangle-types.js";
|
|
3
3
|
export declare const MAX_EXACT_FILE_BYTES: number;
|
|
4
4
|
export declare const MAX_LIST_RESULTS = 100000;
|
|
5
|
+
/** Sandbox caps list responses at 1,000 resources. */
|
|
6
|
+
export declare const SANDBOX_LIST_PAGE_SIZE = 1000;
|
|
5
7
|
export declare const MAX_IDENTIFIER_LENGTH = 512;
|
|
6
8
|
export declare const MAX_STRING_LENGTH = 16384;
|
|
7
9
|
export declare const MAX_ARRAY_LENGTH = 1024;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { canonicalCandidateDigest } from "@tangle-network/agent-interface";
|
|
2
2
|
export const MAX_EXACT_FILE_BYTES = 64 * 1024 * 1024;
|
|
3
3
|
export const MAX_LIST_RESULTS = 100_000;
|
|
4
|
+
/** Sandbox caps list responses at 1,000 resources. */
|
|
5
|
+
export const SANDBOX_LIST_PAGE_SIZE = 1_000;
|
|
4
6
|
export const MAX_IDENTIFIER_LENGTH = 512;
|
|
5
7
|
export const MAX_STRING_LENGTH = 16_384;
|
|
6
8
|
export const MAX_ARRAY_LENGTH = 1_024;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentEnvironment, AgentEnvironmentCapabilities, ResourceProfile } from "@tangle-network/agent-interface/environment-provider";
|
|
2
2
|
import type { SandboxClientLike, SandboxInstanceLike } from "./tangle-types.js";
|
|
3
|
+
import type { TangleConfidentialAttestationVerifier } from "./tangle-types.js";
|
|
3
4
|
/**
|
|
4
5
|
* Compose one concrete sandbox into an environment.
|
|
5
6
|
*
|
|
@@ -25,4 +26,5 @@ export declare function sandboxInstanceAsEnvironment(box: SandboxInstanceLike, p
|
|
|
25
26
|
signal?: AbortSignal;
|
|
26
27
|
}, request?: {
|
|
27
28
|
resources?: ResourceProfile;
|
|
29
|
+
confidentialAttestationVerifier?: TangleConfidentialAttestationVerifier;
|
|
28
30
|
}): Promise<AgentEnvironment>;
|
|
@@ -17,6 +17,7 @@ import { createExecutionUsageLog } from "./tangle-usage-log.js";
|
|
|
17
17
|
import { observeTangleEnvironment } from "./tangle-observation.js";
|
|
18
18
|
import { createTangleTerminalRegistry } from "./tangle-terminal.js";
|
|
19
19
|
import { createTangleInteractiveAgentRegistry } from "./tangle-interactive.js";
|
|
20
|
+
import { confidentialVerifierOption, createTangleWorkspaceBranching, } from "./tangle-workspace-branching.js";
|
|
20
21
|
/**
|
|
21
22
|
* Compose one concrete sandbox into an environment.
|
|
22
23
|
*
|
|
@@ -49,7 +50,18 @@ export async function sandboxInstanceAsEnvironment(box, providerName, client, de
|
|
|
49
50
|
}
|
|
50
51
|
const support = sandboxCapabilitySupport(box, client, request?.resources);
|
|
51
52
|
const deployment = await readDeploymentCapabilitySupport(box, operation);
|
|
52
|
-
const capabilities = frozenCapabilityDocument(AgentEnvironmentCapabilitiesSchema.parse(capabilitiesForSandbox(declaredCapabilities, support, deployment)));
|
|
53
|
+
const capabilities = frozenCapabilityDocument(AgentEnvironmentCapabilitiesSchema.parse(capabilitiesForSandbox(declaredCapabilities, support, deployment, confidentialVerifierOption(request?.confidentialAttestationVerifier))));
|
|
54
|
+
const workspaceBranching = capabilities.branching.checkpoint &&
|
|
55
|
+
capabilities.branching.fork &&
|
|
56
|
+
capabilities.branching.lookup === true &&
|
|
57
|
+
capabilities.branching.cleanup === true
|
|
58
|
+
? createTangleWorkspaceBranching({
|
|
59
|
+
box,
|
|
60
|
+
client,
|
|
61
|
+
provider: providerName,
|
|
62
|
+
...confidentialVerifierOption(request?.confidentialAttestationVerifier),
|
|
63
|
+
})
|
|
64
|
+
: undefined;
|
|
53
65
|
// The published document is the single source for what this environment
|
|
54
66
|
// offers, so the session surface reads its grant from there.
|
|
55
67
|
const retainedControl = capabilities.retainedControl !== undefined;
|
|
@@ -84,9 +96,12 @@ export async function sandboxInstanceAsEnvironment(box, providerName, client, de
|
|
|
84
96
|
...(box.name ? { name: boundedString(box.name, "Tangle environment name") } : {}),
|
|
85
97
|
...(box.metadata ? { metadata: snapshotMetadata(box.metadata) } : {}),
|
|
86
98
|
capabilities,
|
|
99
|
+
...(workspaceBranching === undefined
|
|
100
|
+
? {}
|
|
101
|
+
: { workspaceBranching }),
|
|
87
102
|
async status(options) {
|
|
88
103
|
assertOptionKeys(options, ["signal"], "Tangle environment status");
|
|
89
|
-
await awaitWithSignal(box.refresh?.(options), options?.signal);
|
|
104
|
+
await awaitWithSignal(box.refresh?.(options?.signal), options?.signal);
|
|
90
105
|
return statusFromUnknown(box.status);
|
|
91
106
|
},
|
|
92
107
|
async *stream(input) {
|
|
@@ -284,7 +299,7 @@ export async function sandboxInstanceAsEnvironment(box, providerName, client, de
|
|
|
284
299
|
async refresh(options) {
|
|
285
300
|
assertOptionKeys(options, ["signal"], "Tangle refresh");
|
|
286
301
|
options?.signal?.throwIfAborted();
|
|
287
|
-
await awaitWithSignal(box.refresh?.(options), options?.signal);
|
|
302
|
+
await awaitWithSignal(box.refresh?.(options?.signal), options?.signal);
|
|
288
303
|
options?.signal?.throwIfAborted();
|
|
289
304
|
},
|
|
290
305
|
...(support.destroy
|
|
@@ -167,7 +167,7 @@ async function refreshBeforeObservation(box, options) {
|
|
|
167
167
|
return "the Sandbox client cannot refresh this environment";
|
|
168
168
|
}
|
|
169
169
|
try {
|
|
170
|
-
await awaitWithSignal(box.refresh(options), options?.signal);
|
|
170
|
+
await awaitWithSignal(box.refresh(options?.signal), options?.signal);
|
|
171
171
|
return undefined;
|
|
172
172
|
}
|
|
173
173
|
catch (error) {
|
package/dist/tangle-prompt.js
CHANGED
package/dist/tangle-provider.js
CHANGED
|
@@ -2,10 +2,11 @@ import { AgentEnvironmentCapabilitiesSchema, createAgentEnvironmentWithIdempoten
|
|
|
2
2
|
import { createTangleExactProcessProvider, } from "./exact-process.js";
|
|
3
3
|
import { capabilitiesForClient, defaultTangleSandboxCapabilities, narrowTangleCapabilitiesToBackend, } from "./tangle-capabilities.js";
|
|
4
4
|
import { sandboxInstanceAsEnvironment } from "./tangle-environment.js";
|
|
5
|
+
import { confidentialVerifierOption, createTangleWorkspaceBranching, } from "./tangle-workspace-branching.js";
|
|
5
6
|
import { assertCreateInputShape, assertMappedCreateOptions, assertMappedSecretNames, assertNoInlineSecretValues, sandboxOptionsFromCreateInput } from "./tangle-create-options.js";
|
|
6
7
|
import { statusFromUnknown } from "./tangle-environment-values.js";
|
|
7
8
|
import { requestedResourceProfile } from "./tangle-resources.js";
|
|
8
|
-
import { assertBoundedJson, attachCleanupHandle, awaitWithSignal, boundedIdentifier, boundedString, MAX_LIST_RESULTS, } from "./tangle-contract-safety.js";
|
|
9
|
+
import { assertBoundedJson, attachCleanupHandle, awaitWithSignal, boundedIdentifier, boundedString, MAX_LIST_RESULTS, SANDBOX_LIST_PAGE_SIZE, } from "./tangle-contract-safety.js";
|
|
9
10
|
export function createTangleProvider(options) {
|
|
10
11
|
const providerName = options.name ?? "tangle-sandbox";
|
|
11
12
|
boundedIdentifier(providerName, "Tangle provider name");
|
|
@@ -38,7 +39,7 @@ export function createTangleProvider(options) {
|
|
|
38
39
|
};
|
|
39
40
|
// Provider-boundary document: client-stage facts only. It also validates
|
|
40
41
|
// the configured document, so create() and get() call it before any effect.
|
|
41
|
-
const narrowedProviderCapabilities = (declared) => AgentEnvironmentCapabilitiesSchema.parse(capabilitiesForClient(declared, options.client));
|
|
42
|
+
const narrowedProviderCapabilities = (declared) => AgentEnvironmentCapabilitiesSchema.parse(capabilitiesForClient(declared, options.client, confidentialVerifierOption(options.confidentialAttestationVerifier)));
|
|
42
43
|
const resolveCapabilities = async () => narrowedProviderCapabilities(await resolveDeclaredCapabilities());
|
|
43
44
|
const createRecords = new Map();
|
|
44
45
|
const createEnvironment = async (input) => {
|
|
@@ -89,7 +90,12 @@ export function createTangleProvider(options) {
|
|
|
89
90
|
try {
|
|
90
91
|
input.signal?.throwIfAborted();
|
|
91
92
|
const requestedResources = requestedResourceProfile(input.resources);
|
|
92
|
-
const environment = await sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities, input.signal ? { signal: input.signal } : undefined,
|
|
93
|
+
const environment = await sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities, input.signal ? { signal: input.signal } : undefined, {
|
|
94
|
+
...(requestedResources === undefined
|
|
95
|
+
? {}
|
|
96
|
+
: { resources: requestedResources }),
|
|
97
|
+
...confidentialVerifierOption(options.confidentialAttestationVerifier),
|
|
98
|
+
});
|
|
93
99
|
input.signal?.throwIfAborted();
|
|
94
100
|
return environment;
|
|
95
101
|
}
|
|
@@ -109,9 +115,30 @@ export function createTangleProvider(options) {
|
|
|
109
115
|
throw error;
|
|
110
116
|
}
|
|
111
117
|
};
|
|
118
|
+
const workspaceBranching = options.client.get
|
|
119
|
+
? {
|
|
120
|
+
async forEnvironment(sourceEnvironmentId, operation) {
|
|
121
|
+
assertProviderOperationOptions(operation, "Tangle workspaceBranching.forEnvironment");
|
|
122
|
+
const environmentId = boundedIdentifier(sourceEnvironmentId, "Tangle workspace branching source environment id");
|
|
123
|
+
operation?.signal?.throwIfAborted();
|
|
124
|
+
const box = await awaitWithSignal(options.client.get?.(environmentId, operation), operation?.signal);
|
|
125
|
+
operation?.signal?.throwIfAborted();
|
|
126
|
+
if (!box || box.id !== environmentId)
|
|
127
|
+
return null;
|
|
128
|
+
boundedIdentifier(box.id, "Tangle workspace branching environment id");
|
|
129
|
+
return (createTangleWorkspaceBranching({
|
|
130
|
+
box,
|
|
131
|
+
client: options.client,
|
|
132
|
+
provider: providerName,
|
|
133
|
+
...confidentialVerifierOption(options.confidentialAttestationVerifier),
|
|
134
|
+
}) ?? null);
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
: undefined;
|
|
112
138
|
return {
|
|
113
139
|
name: providerName,
|
|
114
140
|
...(exactProcess ? { exactProcess } : {}),
|
|
141
|
+
...(workspaceBranching === undefined ? {} : { workspaceBranching }),
|
|
115
142
|
capabilities: resolveCapabilities,
|
|
116
143
|
...(options.validateProfile ? { validateProfile: options.validateProfile } : {}),
|
|
117
144
|
create(input) {
|
|
@@ -129,7 +156,7 @@ export function createTangleProvider(options) {
|
|
|
129
156
|
operation?.signal?.throwIfAborted();
|
|
130
157
|
if (!box || boundedIdentifier(box.id, "Tangle environment id") !== id)
|
|
131
158
|
return null;
|
|
132
|
-
return await sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities, operation?.signal ? { signal: operation.signal } : undefined);
|
|
159
|
+
return await sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities, operation?.signal ? { signal: operation.signal } : undefined, confidentialVerifierOption(options.confidentialAttestationVerifier));
|
|
133
160
|
},
|
|
134
161
|
}
|
|
135
162
|
: {}),
|
|
@@ -156,9 +183,26 @@ export function createTangleProvider(options) {
|
|
|
156
183
|
}
|
|
157
184
|
assertBoundedJson(query.metadata);
|
|
158
185
|
}
|
|
159
|
-
const boxes =
|
|
160
|
-
|
|
161
|
-
|
|
186
|
+
const boxes = [];
|
|
187
|
+
for (let offset = 0;; offset += SANDBOX_LIST_PAGE_SIZE) {
|
|
188
|
+
if (offset > MAX_LIST_RESULTS) {
|
|
189
|
+
throw new Error("Tangle environment list exceeded its page bound");
|
|
190
|
+
}
|
|
191
|
+
operation?.signal?.throwIfAborted();
|
|
192
|
+
const page = await awaitWithSignal(options.client.list?.({
|
|
193
|
+
limit: SANDBOX_LIST_PAGE_SIZE,
|
|
194
|
+
offset,
|
|
195
|
+
...(operation?.signal ? { signal: operation.signal } : {}),
|
|
196
|
+
}), operation?.signal);
|
|
197
|
+
if (!Array.isArray(page) || page.length > SANDBOX_LIST_PAGE_SIZE) {
|
|
198
|
+
throw new Error("Tangle environment list returned an invalid page size");
|
|
199
|
+
}
|
|
200
|
+
if (boxes.length + page.length > MAX_LIST_RESULTS) {
|
|
201
|
+
throw new Error("Tangle environment list exceeded its result bound");
|
|
202
|
+
}
|
|
203
|
+
boxes.push(...page);
|
|
204
|
+
if (page.length < SANDBOX_LIST_PAGE_SIZE)
|
|
205
|
+
break;
|
|
162
206
|
}
|
|
163
207
|
const summaries = (boxes ?? []).filter((box) => {
|
|
164
208
|
boundedIdentifier(box.id, "Tangle environment id");
|