@tangle-network/agent-provider-tangle 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -5
- package/dist/tangle-capabilities.d.ts +60 -22
- package/dist/tangle-capabilities.js +87 -37
- package/dist/tangle-deployment-capabilities.d.ts +83 -0
- package/dist/tangle-deployment-capabilities.js +103 -0
- package/dist/tangle-environment-session.d.ts +7 -1
- package/dist/tangle-environment-session.js +8 -2
- package/dist/tangle-environment.d.ts +20 -1
- package/dist/tangle-environment.js +33 -8
- package/dist/tangle-events.js +9 -1
- package/dist/tangle-prompt.js +20 -3
- package/dist/tangle-provider.js +2 -2
- package/dist/tangle-types.d.ts +49 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @tangle-network/agent-provider-tangle
|
|
2
2
|
|
|
3
3
|
Wraps `@tangle-network/sandbox` as an `AgentEnvironmentProvider`.
|
|
4
|
-
The peer range is `>=0.19.6 <1.0.0`; retained-run cancellation (`session.cancelRun`) first shipped in 0.19.6, and this package is developed and tested against 0.
|
|
4
|
+
The peer range is `>=0.19.6 <1.0.0`; retained-run cancellation (`session.cancelRun`) first shipped in 0.19.6, and this package is developed and tested against 0.22.0.
|
|
5
|
+
The floor stays at 0.19.6 although deployment capability discovery (`box.capabilities()`) needs 0.22.0: the adapter feature-detects that method, so a consumer on an older SDK keeps working and claims no retained control instead of failing to load.
|
|
5
6
|
|
|
6
7
|
```ts
|
|
7
8
|
import { Sandbox } from '@tangle-network/sandbox'
|
|
@@ -18,11 +19,49 @@ Reconstruct an exact session with `environment.session(reference.id, { controlRe
|
|
|
18
19
|
Result, replay, and cancel operations select that exact execution instead of whichever execution most recently changed the shared session.
|
|
19
20
|
Session status with an exact control reference reports a state only when the payload names that execution; a payload bound to a different or unnamed execution reports `unknown`.
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
## Two capability documents
|
|
23
|
+
|
|
24
|
+
Capabilities are derived in two stages, and the two stages answer different questions.
|
|
25
|
+
|
|
26
|
+
`provider.capabilities()` answers "what can this provider do against a deployment that backs it".
|
|
27
|
+
It runs before any sandbox exists, so it measures the adapter surface alone and states the adapter's ceiling for everything a deployment decides.
|
|
22
28
|
A lazy instance handle minted from the linked Sandbox SDK over the client's `fetch` transport must prove `dispatchPrompt`, `session`, and `cancelRun`, and the client must expose `get` for reconstruction; the probe sends no request and creates no resource.
|
|
23
|
-
|
|
24
|
-
A client that cannot prove those facts gets no claim, so the runtime rejects retained dispatch before any sandbox is created.
|
|
25
|
-
|
|
29
|
+
That handle measures the linked SDK's method surface, never the connected service.
|
|
30
|
+
A client that cannot prove those facts gets no retained-control claim, so the runtime rejects retained dispatch before any sandbox is created.
|
|
31
|
+
|
|
32
|
+
`environment.capabilities` answers "what can this environment do", and it is the document to read before offering an operation.
|
|
33
|
+
Composing an environment calls `box.capabilities()` once and derives every deployment-decided claim from that document.
|
|
34
|
+
The operations an environment exposes match its own document exactly: a claim the document does not carry has no method behind it.
|
|
35
|
+
One provider reaches deployments of different ages, which is why the environment carries its own document rather than inheriting the provider's.
|
|
36
|
+
|
|
37
|
+
Each deployment flag this adapter reads gates the claims it backs, and no flag is read that gates nothing:
|
|
38
|
+
|
|
39
|
+
| Deployment flag | Claims it gates |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `dispatch.runControlRef` | `streaming.detach`, `streaming.turnIdempotency`, `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
42
|
+
| `dispatch.executionIdOnAdmission` | `streaming.detach`, `streaming.turnIdempotency`, `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
43
|
+
| `cancel.canonicalRunCancellation` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
44
|
+
| `cancel.digestBound` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
45
|
+
| `cancel.idempotent` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
46
|
+
| `runs.eventReplay` | `streaming.replay`, `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
47
|
+
| `runs.executionScopedStatus` | `sessions.continue`, `retainedControl`, `session.cancelRun` |
|
|
48
|
+
|
|
49
|
+
Detached dispatch carries the caller's exact reference and refuses a receipt that does not name the execution back, so it needs both `dispatch` flags and a session handle to reach the run through.
|
|
50
|
+
`sessions.continue`, `retainedControl`, and `session.cancelRun` need every flag in the table, because the capability schema refuses a partial retained-control block and each identity rests on its own flag.
|
|
51
|
+
A claim takes its operation with it: `streaming.detach` gates `dispatch()`, and `session()` stands while any of `streaming.detach`, `streaming.replay`, or `sessions.continue` stands.
|
|
52
|
+
A missing flag means unknown, and unknown is never a claim.
|
|
53
|
+
|
|
54
|
+
Four inputs claim nothing at all: a Sandbox SDK older than 0.22.0, a sandbox that is not running, a `null` document (a deployment predating capability discovery, or one serving a newer schema this SDK cannot read), and a capability read that fails.
|
|
55
|
+
In each case the environment omits `dispatch` and `session`, so a caller never selects an action the deployment will reject.
|
|
56
|
+
A document that leaves a flag unset is not one of them.
|
|
57
|
+
It drops the claims that flag gates and keeps every claim its remaining flags back.
|
|
58
|
+
A document without `cancel.digestBound` still carries `streaming.detach` and `streaming.replay`, and its environment still exposes `dispatch` and `session`.
|
|
59
|
+
A failed read claims nothing rather than failing `create()`: discovery runs against a sandbox a cold provision has already paid for, and a transport failure is not evidence about the deployment.
|
|
60
|
+
The failure is reported on the warning channel.
|
|
61
|
+
|
|
62
|
+
The document is measured once, when the environment is composed.
|
|
63
|
+
A sandbox that is not yet running cannot answer, so an environment composed during provisioning claims nothing and keeps claiming nothing — the exposed operations and the document are composed together, and a caller may already hold either one.
|
|
64
|
+
Compose the environment again through `provider.get(id)` once the sandbox is running.
|
|
26
65
|
|
|
27
66
|
Pass the SDK client itself when retained control matters.
|
|
28
67
|
An object-spread wrapper (`{ ...client }`) drops class prototype methods, including `fetch`, so the provider treats the wrapper as a non-SDK client and claims no retained control.
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import type { AgentEnvironmentCapabilities, HarnessType } from "@tangle-network/agent-interface";
|
|
2
2
|
import type { SandboxClientLike, SandboxInstanceLike } from "./tangle-types.js";
|
|
3
|
+
import type { DeploymentCapabilitySupport } from "./tangle-deployment-capabilities.js";
|
|
3
4
|
/**
|
|
4
5
|
* The full capability document this adapter supports when the Sandbox client
|
|
5
6
|
* implements every optional method.
|
|
6
7
|
*
|
|
7
|
-
* This is an upper bound, not a claim. `capabilitiesForClient()`
|
|
8
|
-
* `capabilitiesForSandbox()`
|
|
9
|
-
*
|
|
10
|
-
* caller selects and finds missing.
|
|
8
|
+
* This is an upper bound, not a claim. `capabilitiesForClient()` narrows it
|
|
9
|
+
* to the adapter surface, and `capabilitiesForSandbox()` narrows it again to
|
|
10
|
+
* what the deployment behind one sandbox reports, because a capability
|
|
11
|
+
* nothing backs becomes an action the caller selects and finds missing.
|
|
11
12
|
*/
|
|
12
13
|
export declare function defaultTangleSandboxCapabilities(harness?: HarnessType): AgentEnvironmentCapabilities;
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* declared capability.
|
|
15
|
+
* Adapter-surface facts that gate declared capabilities: which methods this
|
|
16
|
+
* process can actually call. Every fact defaults to false when it cannot be
|
|
17
|
+
* established; a false fact clears the matching declared capability. These
|
|
18
|
+
* facts bound the claim from above — what the connected deployment honors is
|
|
19
|
+
* a separate fact, carried by `DeploymentCapabilitySupport`.
|
|
17
20
|
*/
|
|
18
21
|
export interface SandboxCapabilitySupport {
|
|
19
22
|
/** The provider can rebuild an environment by id (`client.get`). */
|
|
@@ -29,29 +32,64 @@ export interface SandboxCapabilitySupport {
|
|
|
29
32
|
}
|
|
30
33
|
export declare function sandboxCapabilitySupport(box: SandboxInstanceLike, client: SandboxClientLike): SandboxCapabilitySupport;
|
|
31
34
|
/**
|
|
32
|
-
* Establish client-stage facts before any sandbox exists. Two sources:
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* Establish client-stage facts before any sandbox exists. Two sources: the
|
|
36
|
+
* client's own members (get, describePlacement) and, for an SDK-backed client,
|
|
37
|
+
* the linked SDK surface via `linkedSdkProbeInstance`. These facts bound what
|
|
38
|
+
* the adapter can execute; the deployment that decides whether an execution is
|
|
39
|
+
* honored is unreachable at this stage. Box-scoped workspace facts stay at the
|
|
40
|
+
* declared upper bound when no handle can be minted, and each concrete sandbox
|
|
41
|
+
* re-measures them in `capabilitiesForSandbox`.
|
|
39
42
|
*/
|
|
40
43
|
export declare function clientCapabilitySupport(client: SandboxClientLike): SandboxCapabilitySupport;
|
|
44
|
+
/**
|
|
45
|
+
* Decide whether retained control may be claimed.
|
|
46
|
+
*
|
|
47
|
+
* Two independent fact sets must agree. The adapter surface must be able to
|
|
48
|
+
* execute it: exact dispatch, a session handle, canonical cancellation, and
|
|
49
|
+
* environment reconstruction by id. The connected deployment must honor it:
|
|
50
|
+
* exact dispatch, canonical cancellation, event replay, and execution-scoped
|
|
51
|
+
* status together. A deployment that leaves any of the four unreported refuses
|
|
52
|
+
* the claim even when every local method exists, because a method this process
|
|
53
|
+
* can call is not a run the service retains.
|
|
54
|
+
*/
|
|
55
|
+
export declare function tangleRetainedControlSupported(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport, deployment: DeploymentCapabilitySupport): boolean;
|
|
41
56
|
/**
|
|
42
57
|
* Narrow a declared capability document to established facts.
|
|
43
58
|
*
|
|
44
59
|
* Braid derives product actions from these flags, so an over-claimed flag is
|
|
45
|
-
* an offered action that throws at the moment the user selects it.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
60
|
+
* an offered action that throws at the moment the user selects it. Each flag
|
|
61
|
+
* takes the narrowest fact set it rests on. Detached dispatch carries the
|
|
62
|
+
* caller's exact `runControlRef` and refuses a receipt that does not echo the
|
|
63
|
+
* execution back, and it is only reachable through a session handle, so
|
|
64
|
+
* `streaming.detach` needs exact dispatch from the deployment plus both local
|
|
65
|
+
* methods. Cursor replay needs the deployment's own event replay, and turn
|
|
66
|
+
* idempotency needs the deployment to honor the exact reference that
|
|
67
|
+
* identifies a repeated turn.
|
|
48
68
|
*/
|
|
49
|
-
export declare function narrowedTangleCapabilities(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport): AgentEnvironmentCapabilities;
|
|
69
|
+
export declare function narrowedTangleCapabilities(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport, deployment: DeploymentCapabilitySupport): AgentEnvironmentCapabilities;
|
|
50
70
|
/**
|
|
51
71
|
* Narrow provider-level claims to facts the client can prove before any
|
|
52
|
-
* sandbox exists.
|
|
53
|
-
*
|
|
72
|
+
* sandbox exists.
|
|
73
|
+
*
|
|
74
|
+
* This document answers "what can this provider do against a deployment that
|
|
75
|
+
* backs it", which is the question a caller selects a provider on. No sandbox
|
|
76
|
+
* exists here, so the deployment input is the adapter's ceiling and this
|
|
77
|
+
* document is a bound, never a statement about one environment. Each concrete
|
|
78
|
+
* sandbox reads its own deployment in `capabilitiesForSandbox` and publishes
|
|
79
|
+
* the answer as `AgentEnvironment.capabilities`, which is the document a
|
|
80
|
+
* caller reads to decide which operation to offer against that environment.
|
|
54
81
|
*/
|
|
55
82
|
export declare function capabilitiesForClient(declared: AgentEnvironmentCapabilities, client: SandboxClientLike): AgentEnvironmentCapabilities;
|
|
56
|
-
/**
|
|
57
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Freeze a capability document before an environment publishes it.
|
|
85
|
+
*
|
|
86
|
+
* The document and the operations an environment exposes are decided together
|
|
87
|
+
* and must stay equal, so the copy a caller holds cannot be writable: a
|
|
88
|
+
* mutated flag would describe a surface this environment does not have.
|
|
89
|
+
*/
|
|
90
|
+
export declare function frozenCapabilityDocument<T>(document: T): T;
|
|
91
|
+
/**
|
|
92
|
+
* Narrow a declared capability document to what this Sandbox instance backs
|
|
93
|
+
* and what the deployment behind it reports.
|
|
94
|
+
*/
|
|
95
|
+
export declare function capabilitiesForSandbox(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport, deployment: DeploymentCapabilitySupport): AgentEnvironmentCapabilities;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { harnessSystemPromptIntents } from "@tangle-network/agent-interface";
|
|
2
2
|
import { SandboxInstance } from "@tangle-network/sandbox";
|
|
3
|
+
import { ADAPTER_CEILING_DEPLOYMENT, deploymentBacksRetainedControl, } from "./tangle-deployment-capabilities.js";
|
|
3
4
|
/**
|
|
4
5
|
* The full capability document this adapter supports when the Sandbox client
|
|
5
6
|
* implements every optional method.
|
|
6
7
|
*
|
|
7
|
-
* This is an upper bound, not a claim. `capabilitiesForClient()`
|
|
8
|
-
* `capabilitiesForSandbox()`
|
|
9
|
-
*
|
|
10
|
-
* caller selects and finds missing.
|
|
8
|
+
* This is an upper bound, not a claim. `capabilitiesForClient()` narrows it
|
|
9
|
+
* to the adapter surface, and `capabilitiesForSandbox()` narrows it again to
|
|
10
|
+
* what the deployment behind one sandbox reports, because a capability
|
|
11
|
+
* nothing backs becomes an action the caller selects and finds missing.
|
|
11
12
|
*/
|
|
12
13
|
export function defaultTangleSandboxCapabilities(harness) {
|
|
13
14
|
return {
|
|
@@ -35,10 +36,11 @@ export function defaultTangleSandboxCapabilities(harness) {
|
|
|
35
36
|
streaming: { live: true, replay: true, detach: true, turnIdempotency: true },
|
|
36
37
|
// Retained control is declared as intent here and stripped by narrowing
|
|
37
38
|
// wherever the facts cannot prove dispatchPrompt, session, cancelRun,
|
|
38
|
-
// and environment reconstruction by id
|
|
39
|
-
//
|
|
39
|
+
// and environment reconstruction by id, or wherever the deployment does
|
|
40
|
+
// not report the run-control and cancellation flags. The four sub-flags
|
|
41
|
+
// are all-or-nothing by design: this adapter implements the identities
|
|
40
42
|
// together over one Sandbox surface, and the capability schema refuses
|
|
41
|
-
// a partial block, so they stand or fall on the same
|
|
43
|
+
// a partial block, so they stand or fall on the same fact set.
|
|
42
44
|
sessions: { continue: true, list: false, messages: false },
|
|
43
45
|
retainedControl: {
|
|
44
46
|
exactRunIdentity: true,
|
|
@@ -86,13 +88,14 @@ export function sandboxCapabilitySupport(box, client) {
|
|
|
86
88
|
/**
|
|
87
89
|
* Mint a lazy instance handle from the sandbox SDK linked into this process.
|
|
88
90
|
* The handle measures the LINKED SDK's instance and session method surface —
|
|
89
|
-
* an adapter-
|
|
90
|
-
* the
|
|
91
|
+
* an adapter-surface fact and therefore an upper bound, never a claim that
|
|
92
|
+
* the connected service honors those methods. It is valid exactly when the
|
|
93
|
+
* client is SDK-backed (carries the SDK `fetch` transport), because the
|
|
91
94
|
* sandboxes such a client returns are instances of these same classes.
|
|
92
|
-
* Deployment truth
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
95
|
+
* Deployment truth arrives per-sandbox, from `box.capabilities()`, and can
|
|
96
|
+
* only narrow this bound. The handle and its probe session never leave the
|
|
97
|
+
* process: construction and `session(id)` are lazy in the SDK, so no request
|
|
98
|
+
* is sent and no billable resource is created.
|
|
96
99
|
*/
|
|
97
100
|
function linkedSdkProbeInstance(client) {
|
|
98
101
|
if (typeof client.fetch !== "function")
|
|
@@ -109,13 +112,13 @@ function linkedSdkProbeInstance(client) {
|
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
/**
|
|
112
|
-
* Establish client-stage facts before any sandbox exists. Two sources:
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
115
|
+
* Establish client-stage facts before any sandbox exists. Two sources: the
|
|
116
|
+
* client's own members (get, describePlacement) and, for an SDK-backed client,
|
|
117
|
+
* the linked SDK surface via `linkedSdkProbeInstance`. These facts bound what
|
|
118
|
+
* the adapter can execute; the deployment that decides whether an execution is
|
|
119
|
+
* honored is unreachable at this stage. Box-scoped workspace facts stay at the
|
|
120
|
+
* declared upper bound when no handle can be minted, and each concrete sandbox
|
|
121
|
+
* re-measures them in `capabilitiesForSandbox`.
|
|
119
122
|
*/
|
|
120
123
|
export function clientCapabilitySupport(client) {
|
|
121
124
|
const probe = linkedSdkProbeInstance(client);
|
|
@@ -134,22 +137,43 @@ export function clientCapabilitySupport(client) {
|
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
/**
|
|
137
|
-
*
|
|
140
|
+
* Decide whether retained control may be claimed.
|
|
138
141
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* canonical cancellation,
|
|
142
|
+
* Two independent fact sets must agree. The adapter surface must be able to
|
|
143
|
+
* execute it: exact dispatch, a session handle, canonical cancellation, and
|
|
144
|
+
* environment reconstruction by id. The connected deployment must honor it:
|
|
145
|
+
* exact dispatch, canonical cancellation, event replay, and execution-scoped
|
|
146
|
+
* status together. A deployment that leaves any of the four unreported refuses
|
|
147
|
+
* the claim even when every local method exists, because a method this process
|
|
148
|
+
* can call is not a run the service retains.
|
|
143
149
|
*/
|
|
144
|
-
export function
|
|
145
|
-
|
|
150
|
+
export function tangleRetainedControlSupported(declared, support, deployment) {
|
|
151
|
+
return (deploymentBacksRetainedControl(deployment) &&
|
|
152
|
+
declared.sessions.continue === true &&
|
|
146
153
|
declared.streaming.detach === true &&
|
|
147
154
|
declared.streaming.replay === true &&
|
|
148
155
|
declared.streaming.turnIdempotency === true &&
|
|
149
156
|
support.reconstruct &&
|
|
150
157
|
support.dispatchPrompt &&
|
|
151
158
|
support.session &&
|
|
152
|
-
support.cancelRun;
|
|
159
|
+
support.cancelRun);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Narrow a declared capability document to established facts.
|
|
163
|
+
*
|
|
164
|
+
* Braid derives product actions from these flags, so an over-claimed flag is
|
|
165
|
+
* an offered action that throws at the moment the user selects it. Each flag
|
|
166
|
+
* takes the narrowest fact set it rests on. Detached dispatch carries the
|
|
167
|
+
* caller's exact `runControlRef` and refuses a receipt that does not echo the
|
|
168
|
+
* execution back, and it is only reachable through a session handle, so
|
|
169
|
+
* `streaming.detach` needs exact dispatch from the deployment plus both local
|
|
170
|
+
* methods. Cursor replay needs the deployment's own event replay, and turn
|
|
171
|
+
* idempotency needs the deployment to honor the exact reference that
|
|
172
|
+
* identifies a repeated turn.
|
|
173
|
+
*/
|
|
174
|
+
export function narrowedTangleCapabilities(declared, support, deployment) {
|
|
175
|
+
const supportsRetainedControl = tangleRetainedControlSupported(declared, support, deployment);
|
|
176
|
+
const supportsDetach = support.dispatchPrompt && support.session && deployment.exactDispatch;
|
|
153
177
|
// A cleared fact forces false; a held fact passes the declared value
|
|
154
178
|
// through unchanged, so a malformed declaration still reaches the schema
|
|
155
179
|
// at the provider boundary instead of being laundered into a boolean.
|
|
@@ -157,9 +181,11 @@ export function narrowedTangleCapabilities(declared, support) {
|
|
|
157
181
|
...declared,
|
|
158
182
|
streaming: {
|
|
159
183
|
...declared.streaming,
|
|
160
|
-
detach:
|
|
161
|
-
replay: support.session
|
|
162
|
-
|
|
184
|
+
detach: supportsDetach ? declared.streaming.detach : false,
|
|
185
|
+
replay: support.session && deployment.eventReplay
|
|
186
|
+
? declared.streaming.replay
|
|
187
|
+
: false,
|
|
188
|
+
turnIdempotency: deployment.exactDispatch
|
|
163
189
|
? declared.streaming.turnIdempotency
|
|
164
190
|
: false,
|
|
165
191
|
},
|
|
@@ -197,13 +223,37 @@ export function narrowedTangleCapabilities(declared, support) {
|
|
|
197
223
|
}
|
|
198
224
|
/**
|
|
199
225
|
* Narrow provider-level claims to facts the client can prove before any
|
|
200
|
-
* sandbox exists.
|
|
201
|
-
*
|
|
226
|
+
* sandbox exists.
|
|
227
|
+
*
|
|
228
|
+
* This document answers "what can this provider do against a deployment that
|
|
229
|
+
* backs it", which is the question a caller selects a provider on. No sandbox
|
|
230
|
+
* exists here, so the deployment input is the adapter's ceiling and this
|
|
231
|
+
* document is a bound, never a statement about one environment. Each concrete
|
|
232
|
+
* sandbox reads its own deployment in `capabilitiesForSandbox` and publishes
|
|
233
|
+
* the answer as `AgentEnvironment.capabilities`, which is the document a
|
|
234
|
+
* caller reads to decide which operation to offer against that environment.
|
|
202
235
|
*/
|
|
203
236
|
export function capabilitiesForClient(declared, client) {
|
|
204
|
-
return narrowedTangleCapabilities(declared, clientCapabilitySupport(client));
|
|
237
|
+
return narrowedTangleCapabilities(declared, clientCapabilitySupport(client), ADAPTER_CEILING_DEPLOYMENT);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Freeze a capability document before an environment publishes it.
|
|
241
|
+
*
|
|
242
|
+
* The document and the operations an environment exposes are decided together
|
|
243
|
+
* and must stay equal, so the copy a caller holds cannot be writable: a
|
|
244
|
+
* mutated flag would describe a surface this environment does not have.
|
|
245
|
+
*/
|
|
246
|
+
export function frozenCapabilityDocument(document) {
|
|
247
|
+
if (document === null || typeof document !== "object")
|
|
248
|
+
return document;
|
|
249
|
+
for (const value of Object.values(document))
|
|
250
|
+
frozenCapabilityDocument(value);
|
|
251
|
+
return Object.freeze(document);
|
|
205
252
|
}
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Narrow a declared capability document to what this Sandbox instance backs
|
|
255
|
+
* and what the deployment behind it reports.
|
|
256
|
+
*/
|
|
257
|
+
export function capabilitiesForSandbox(declared, support, deployment) {
|
|
258
|
+
return narrowedTangleCapabilities(declared, support, deployment);
|
|
209
259
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { SandboxInstanceLike, SandboxRuntimeCapabilityDocument } from "./tangle-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* What the connected deployment reports about the run operations this adapter
|
|
4
|
+
* builds on top of a sandbox.
|
|
5
|
+
*
|
|
6
|
+
* Each fact is the conjunction of every document flag its operation needs, so
|
|
7
|
+
* a document that reports part of an operation reports none of it. A flag the
|
|
8
|
+
* document leaves unset is unknown, and unknown is never a claim: an absent,
|
|
9
|
+
* unreadable, or partial document leaves every fact false.
|
|
10
|
+
*/
|
|
11
|
+
export interface DeploymentCapabilitySupport {
|
|
12
|
+
/**
|
|
13
|
+
* Run requests carry the caller's exact `runControlRef`, and admission
|
|
14
|
+
* echoes the executionId. Detached dispatch needs both: it sends the
|
|
15
|
+
* reference and refuses a receipt that does not name the execution back.
|
|
16
|
+
*/
|
|
17
|
+
readonly exactDispatch: boolean;
|
|
18
|
+
/** Cancellation is canonical, digest-bound, and idempotent under replay. */
|
|
19
|
+
readonly canonicalCancellation: boolean;
|
|
20
|
+
/** Buffered run events replay by execution under stable event ids. */
|
|
21
|
+
readonly eventReplay: boolean;
|
|
22
|
+
/** Status and results select one execution, not the session's latest. */
|
|
23
|
+
readonly executionScopedStatus: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The deployment backs nothing.
|
|
27
|
+
*
|
|
28
|
+
* This is the client stage, where no sandbox exists to ask, and it is also
|
|
29
|
+
* every answer that fails to establish a fact: no capability method, a sandbox
|
|
30
|
+
* that cannot answer, a `null` document, a failed request, and a document that
|
|
31
|
+
* leaves a required flag unset.
|
|
32
|
+
*/
|
|
33
|
+
export declare const UNPROVEN_DEPLOYMENT: DeploymentCapabilitySupport;
|
|
34
|
+
/**
|
|
35
|
+
* The client stage's deployment input: this adapter's ceiling, not a fact.
|
|
36
|
+
*
|
|
37
|
+
* No sandbox exists before create, so no deployment can be asked, and the
|
|
38
|
+
* provider document answers a different question from the environment's — it
|
|
39
|
+
* states what this adapter offers against a deployment that backs it, which is
|
|
40
|
+
* what a caller selects a provider on. `AgentEnvironment.capabilities` carries
|
|
41
|
+
* the measured answer for one sandbox, and every operation an environment
|
|
42
|
+
* exposes follows that document, never this ceiling.
|
|
43
|
+
*
|
|
44
|
+
* The ceiling stays wide deliberately. A provider document that claimed
|
|
45
|
+
* nothing before create would refuse retained runs against every deployment,
|
|
46
|
+
* including the ones that back them, because a caller must read the provider
|
|
47
|
+
* document to decide whether to start one at all.
|
|
48
|
+
*/
|
|
49
|
+
export declare const ADAPTER_CEILING_DEPLOYMENT: DeploymentCapabilitySupport;
|
|
50
|
+
/**
|
|
51
|
+
* Read the deployment facts out of a capability document. Every flag this
|
|
52
|
+
* adapter acts on is read here; the shape carries no flag it does not act on.
|
|
53
|
+
*/
|
|
54
|
+
export declare function deploymentCapabilitySupport(document: SandboxRuntimeCapabilityDocument | null | undefined): DeploymentCapabilitySupport;
|
|
55
|
+
/**
|
|
56
|
+
* Establish the deployment facts for one sandbox through capability
|
|
57
|
+
* discovery. Every outcome but the caller's own abort resolves to a fact set.
|
|
58
|
+
*
|
|
59
|
+
* Three inputs answer without a request: a Sandbox SDK older than 0.22.0,
|
|
60
|
+
* which carries no `capabilities` method; a sandbox that is not running, whose
|
|
61
|
+
* capability route can only answer with a state error; and a `null` document,
|
|
62
|
+
* which is a deployment predating capability discovery or one serving a schema
|
|
63
|
+
* this SDK cannot read.
|
|
64
|
+
*
|
|
65
|
+
* A failed request resolves the same way. Discovery runs against a sandbox
|
|
66
|
+
* that a cold provision has just paid for, and a transport failure or a
|
|
67
|
+
* defective document is not evidence about the run operations: failing here
|
|
68
|
+
* would trade an unknown for the certain loss of that sandbox. The failure
|
|
69
|
+
* reaches the warning channel, and the environment then offers no operation
|
|
70
|
+
* the document did not prove.
|
|
71
|
+
*/
|
|
72
|
+
export declare function readDeploymentCapabilitySupport(box: SandboxInstanceLike, options?: {
|
|
73
|
+
signal?: AbortSignal;
|
|
74
|
+
}): Promise<DeploymentCapabilitySupport>;
|
|
75
|
+
/**
|
|
76
|
+
* Whether the deployment backs the complete retained-control identity set.
|
|
77
|
+
* The capability schema refuses a partial retained-control block, and each
|
|
78
|
+
* identity rests on its own deployment fact, so they stand together: exact
|
|
79
|
+
* dispatch for run identity, execution-scoped status for result identity,
|
|
80
|
+
* event replay for event identity, and canonical cancellation for
|
|
81
|
+
* cancellation idempotency.
|
|
82
|
+
*/
|
|
83
|
+
export declare function deploymentBacksRetainedControl(deployment: DeploymentCapabilitySupport): boolean;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { statusFromUnknown } from "./tangle-environment-values.js";
|
|
2
|
+
import { awaitWithSignal } from "./tangle-contract-safety.js";
|
|
3
|
+
/**
|
|
4
|
+
* The deployment backs nothing.
|
|
5
|
+
*
|
|
6
|
+
* This is the client stage, where no sandbox exists to ask, and it is also
|
|
7
|
+
* every answer that fails to establish a fact: no capability method, a sandbox
|
|
8
|
+
* that cannot answer, a `null` document, a failed request, and a document that
|
|
9
|
+
* leaves a required flag unset.
|
|
10
|
+
*/
|
|
11
|
+
export const UNPROVEN_DEPLOYMENT = {
|
|
12
|
+
exactDispatch: false,
|
|
13
|
+
canonicalCancellation: false,
|
|
14
|
+
eventReplay: false,
|
|
15
|
+
executionScopedStatus: false,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The client stage's deployment input: this adapter's ceiling, not a fact.
|
|
19
|
+
*
|
|
20
|
+
* No sandbox exists before create, so no deployment can be asked, and the
|
|
21
|
+
* provider document answers a different question from the environment's — it
|
|
22
|
+
* states what this adapter offers against a deployment that backs it, which is
|
|
23
|
+
* what a caller selects a provider on. `AgentEnvironment.capabilities` carries
|
|
24
|
+
* the measured answer for one sandbox, and every operation an environment
|
|
25
|
+
* exposes follows that document, never this ceiling.
|
|
26
|
+
*
|
|
27
|
+
* The ceiling stays wide deliberately. A provider document that claimed
|
|
28
|
+
* nothing before create would refuse retained runs against every deployment,
|
|
29
|
+
* including the ones that back them, because a caller must read the provider
|
|
30
|
+
* document to decide whether to start one at all.
|
|
31
|
+
*/
|
|
32
|
+
export const ADAPTER_CEILING_DEPLOYMENT = {
|
|
33
|
+
exactDispatch: true,
|
|
34
|
+
canonicalCancellation: true,
|
|
35
|
+
eventReplay: true,
|
|
36
|
+
executionScopedStatus: true,
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Read the deployment facts out of a capability document. Every flag this
|
|
40
|
+
* adapter acts on is read here; the shape carries no flag it does not act on.
|
|
41
|
+
*/
|
|
42
|
+
export function deploymentCapabilitySupport(document) {
|
|
43
|
+
if (!document || typeof document !== "object")
|
|
44
|
+
return UNPROVEN_DEPLOYMENT;
|
|
45
|
+
return {
|
|
46
|
+
exactDispatch: document.dispatch?.runControlRef === true &&
|
|
47
|
+
document.dispatch?.executionIdOnAdmission === true,
|
|
48
|
+
canonicalCancellation: document.cancel?.canonicalRunCancellation === true &&
|
|
49
|
+
document.cancel?.digestBound === true &&
|
|
50
|
+
document.cancel?.idempotent === true,
|
|
51
|
+
eventReplay: document.runs?.eventReplay === true,
|
|
52
|
+
executionScopedStatus: document.runs?.executionScopedStatus === true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Establish the deployment facts for one sandbox through capability
|
|
57
|
+
* discovery. Every outcome but the caller's own abort resolves to a fact set.
|
|
58
|
+
*
|
|
59
|
+
* Three inputs answer without a request: a Sandbox SDK older than 0.22.0,
|
|
60
|
+
* which carries no `capabilities` method; a sandbox that is not running, whose
|
|
61
|
+
* capability route can only answer with a state error; and a `null` document,
|
|
62
|
+
* which is a deployment predating capability discovery or one serving a schema
|
|
63
|
+
* this SDK cannot read.
|
|
64
|
+
*
|
|
65
|
+
* A failed request resolves the same way. Discovery runs against a sandbox
|
|
66
|
+
* that a cold provision has just paid for, and a transport failure or a
|
|
67
|
+
* defective document is not evidence about the run operations: failing here
|
|
68
|
+
* would trade an unknown for the certain loss of that sandbox. The failure
|
|
69
|
+
* reaches the warning channel, and the environment then offers no operation
|
|
70
|
+
* the document did not prove.
|
|
71
|
+
*/
|
|
72
|
+
export async function readDeploymentCapabilitySupport(box, options) {
|
|
73
|
+
if (typeof box.capabilities !== "function")
|
|
74
|
+
return UNPROVEN_DEPLOYMENT;
|
|
75
|
+
if (statusFromUnknown(box.status) !== "running")
|
|
76
|
+
return UNPROVEN_DEPLOYMENT;
|
|
77
|
+
options?.signal?.throwIfAborted();
|
|
78
|
+
let document;
|
|
79
|
+
try {
|
|
80
|
+
document = await awaitWithSignal(box.capabilities(), options?.signal);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
options?.signal?.throwIfAborted();
|
|
84
|
+
console.warn(`Tangle capability discovery failed for sandbox ${box.id}: the deployment backs nothing`, error);
|
|
85
|
+
return UNPROVEN_DEPLOYMENT;
|
|
86
|
+
}
|
|
87
|
+
options?.signal?.throwIfAborted();
|
|
88
|
+
return deploymentCapabilitySupport(document);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Whether the deployment backs the complete retained-control identity set.
|
|
92
|
+
* The capability schema refuses a partial retained-control block, and each
|
|
93
|
+
* identity rests on its own deployment fact, so they stand together: exact
|
|
94
|
+
* dispatch for run identity, execution-scoped status for result identity,
|
|
95
|
+
* event replay for event identity, and canonical cancellation for
|
|
96
|
+
* cancellation idempotency.
|
|
97
|
+
*/
|
|
98
|
+
export function deploymentBacksRetainedControl(deployment) {
|
|
99
|
+
return (deployment.exactDispatch &&
|
|
100
|
+
deployment.canonicalCancellation &&
|
|
101
|
+
deployment.eventReplay &&
|
|
102
|
+
deployment.executionScopedStatus);
|
|
103
|
+
}
|
|
@@ -9,5 +9,11 @@ type ExactExecutionEventStream = (options: {
|
|
|
9
9
|
signal?: AbortSignal;
|
|
10
10
|
controlRef?: AgentExactRunControlRef;
|
|
11
11
|
}) => AsyncIterable<SandboxEvent>;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param retainedControl Whether the environment's narrowed capability
|
|
14
|
+
* document grants retained control. Canonical cancellation is offered only
|
|
15
|
+
* under that grant: a `cancelRun` method the deployment does not honor is an
|
|
16
|
+
* action the caller selects and finds rejected on the wire.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sandboxSessionAsAgentSession(session: SandboxSessionLike, controlRef: AgentRunControlRef | undefined, provider: string, environmentId: string, dispatch: ((input: AgentTurnInput) => Promise<AgentSessionRef>) | undefined, exactExecutionEvents: ExactExecutionEventStream | undefined, retainedControl: boolean): AgentSession;
|
|
13
19
|
export {};
|
|
@@ -8,12 +8,18 @@ import { executionBoundSessionStatus, sessionStatusFromUnknown, } from "./tangle
|
|
|
8
8
|
import { awaitWithSignal, boundedIdentifier, } from "./tangle-contract-safety.js";
|
|
9
9
|
import { assertOptionKeys } from "./tangle-environment-validation.js";
|
|
10
10
|
import { hasReplayPayload, interruptExecutionAfterAbort, sessionPromptRequestDigest, } from "./tangle-environment-control.js";
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* @param retainedControl Whether the environment's narrowed capability
|
|
13
|
+
* document grants retained control. Canonical cancellation is offered only
|
|
14
|
+
* under that grant: a `cancelRun` method the deployment does not honor is an
|
|
15
|
+
* action the caller selects and finds rejected on the wire.
|
|
16
|
+
*/
|
|
17
|
+
export function sandboxSessionAsAgentSession(session, controlRef, provider, environmentId, dispatch, exactExecutionEvents, retainedControl) {
|
|
12
18
|
let activeControlRef = controlRef
|
|
13
19
|
? resolveRetainedSessionControlRef(controlRef, session.id, provider, environmentId)
|
|
14
20
|
: undefined;
|
|
15
21
|
let promptInFlight = false;
|
|
16
|
-
const cancelRunMethod = session.cancelRun;
|
|
22
|
+
const cancelRunMethod = retainedControl ? session.cancelRun : undefined;
|
|
17
23
|
const cancelRun = typeof cancelRunMethod === "function"
|
|
18
24
|
? async (request, options) => {
|
|
19
25
|
assertOptionKeys(options, ["signal"], "Tangle exact session cancellation");
|
|
@@ -1,3 +1,22 @@
|
|
|
1
1
|
import type { AgentEnvironment, AgentEnvironmentCapabilities } from "@tangle-network/agent-interface/environment-provider";
|
|
2
2
|
import type { SandboxClientLike, SandboxInstanceLike } from "./tangle-types.js";
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Compose one concrete sandbox into an environment.
|
|
5
|
+
*
|
|
6
|
+
* This is the only stage that can read deployment truth, so it does: one
|
|
7
|
+
* `GET /capabilities` against the sandbox decides retained control, and the
|
|
8
|
+
* environment then exposes exactly the operations both the adapter surface
|
|
9
|
+
* and the deployment back. A deployment that cannot disclose a readable
|
|
10
|
+
* document yields no retained-control surface at all. The environment
|
|
11
|
+
* publishes the resulting document on `capabilities`, so a caller reads the
|
|
12
|
+
* answer for this sandbox rather than the provider's pre-sandbox claim.
|
|
13
|
+
*
|
|
14
|
+
* The document is measured once, here. A sandbox that is not yet running
|
|
15
|
+
* cannot answer, so an environment composed during provisioning claims
|
|
16
|
+
* nothing and keeps claiming nothing: the exposed operations and the document
|
|
17
|
+
* are composed together and a caller may already hold either one. Compose the
|
|
18
|
+
* environment again through `provider.get(id)` once the sandbox is running.
|
|
19
|
+
*/
|
|
20
|
+
export declare function sandboxInstanceAsEnvironment(box: SandboxInstanceLike, providerName: string, client: SandboxClientLike, declaredCapabilities: AgentEnvironmentCapabilities, operation?: {
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}): Promise<AgentEnvironment>;
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
import { AgentTurnInputSchema } from "@tangle-network/agent-interface";
|
|
2
|
+
import { AgentEnvironmentCapabilitiesSchema } from "@tangle-network/agent-interface/environment-provider";
|
|
2
3
|
import { environmentEventFromSandboxEvent } from "./tangle-events.js";
|
|
3
4
|
import { executionIdFromTurnInput, promptFromTurnInput, promptOptionsFromTurnInput, } from "./tangle-prompt.js";
|
|
4
5
|
import { resolveRetainedSessionControlRef } from "./tangle-session-control.js";
|
|
5
6
|
import { placementInfoFromLoopPlacement, statusFromUnknown, } from "./tangle-environment-values.js";
|
|
6
7
|
import { execResultFromSandboxExecResult } from "./tangle-result-values.js";
|
|
7
|
-
import { capabilitiesForSandbox, sandboxCapabilitySupport } from "./tangle-capabilities.js";
|
|
8
|
+
import { capabilitiesForSandbox, frozenCapabilityDocument, sandboxCapabilitySupport, } from "./tangle-capabilities.js";
|
|
9
|
+
import { readDeploymentCapabilitySupport } from "./tangle-deployment-capabilities.js";
|
|
8
10
|
import { awaitWithSignal, assertBoundedJson, boundedIdentifier, boundedString, } from "./tangle-contract-safety.js";
|
|
9
11
|
import { assertExecOptions, assertOptionKeys, } from "./tangle-environment-validation.js";
|
|
10
12
|
import { interruptExecutionAfterAbort, } from "./tangle-environment-control.js";
|
|
11
13
|
import { dispatchEnvironmentRun } from "./tangle-environment-dispatch.js";
|
|
12
14
|
import { sandboxSessionAsAgentSession } from "./tangle-environment-session.js";
|
|
13
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Compose one concrete sandbox into an environment.
|
|
17
|
+
*
|
|
18
|
+
* This is the only stage that can read deployment truth, so it does: one
|
|
19
|
+
* `GET /capabilities` against the sandbox decides retained control, and the
|
|
20
|
+
* environment then exposes exactly the operations both the adapter surface
|
|
21
|
+
* and the deployment back. A deployment that cannot disclose a readable
|
|
22
|
+
* document yields no retained-control surface at all. The environment
|
|
23
|
+
* publishes the resulting document on `capabilities`, so a caller reads the
|
|
24
|
+
* answer for this sandbox rather than the provider's pre-sandbox claim.
|
|
25
|
+
*
|
|
26
|
+
* The document is measured once, here. A sandbox that is not yet running
|
|
27
|
+
* cannot answer, so an environment composed during provisioning claims
|
|
28
|
+
* nothing and keeps claiming nothing: the exposed operations and the document
|
|
29
|
+
* are composed together and a caller may already hold either one. Compose the
|
|
30
|
+
* environment again through `provider.get(id)` once the sandbox is running.
|
|
31
|
+
*/
|
|
32
|
+
export async function sandboxInstanceAsEnvironment(box, providerName, client, declaredCapabilities, operation) {
|
|
14
33
|
const environmentId = boundedIdentifier(box.id, "Tangle environment id");
|
|
15
34
|
boundedIdentifier(providerName, "Tangle provider name");
|
|
16
35
|
if (box.metadata !== undefined) {
|
|
@@ -20,7 +39,11 @@ export function sandboxInstanceAsEnvironment(box, providerName, client, declared
|
|
|
20
39
|
assertBoundedJson(box.metadata);
|
|
21
40
|
}
|
|
22
41
|
const support = sandboxCapabilitySupport(box, client);
|
|
23
|
-
const
|
|
42
|
+
const deployment = await readDeploymentCapabilitySupport(box, operation);
|
|
43
|
+
const capabilities = frozenCapabilityDocument(AgentEnvironmentCapabilitiesSchema.parse(capabilitiesForSandbox(declaredCapabilities, support, deployment)));
|
|
44
|
+
// The published document is the single source for what this environment
|
|
45
|
+
// offers, so the session surface reads its grant from there.
|
|
46
|
+
const retainedControl = capabilities.retainedControl !== undefined;
|
|
24
47
|
const dispatch = capabilities.streaming.detach && box.dispatchPrompt
|
|
25
48
|
? dispatchEnvironmentRun(box, providerName, environmentId)
|
|
26
49
|
: undefined;
|
|
@@ -37,6 +60,7 @@ export function sandboxInstanceAsEnvironment(box, providerName, client, declared
|
|
|
37
60
|
id: environmentId,
|
|
38
61
|
provider: providerName,
|
|
39
62
|
...(box.name ? { name: boundedString(box.name, "Tangle environment name") } : {}),
|
|
63
|
+
capabilities,
|
|
40
64
|
async status(options) {
|
|
41
65
|
assertOptionKeys(options, ["signal"], "Tangle environment status");
|
|
42
66
|
await awaitWithSignal(box.refresh?.(options), options?.signal);
|
|
@@ -103,11 +127,12 @@ export function sandboxInstanceAsEnvironment(box, providerName, client, declared
|
|
|
103
127
|
if (session.id !== id) {
|
|
104
128
|
throw new Error("sandbox session(id) returned an unrelated session");
|
|
105
129
|
}
|
|
106
|
-
const agentSession = sandboxSessionAsAgentSession(session, resolveRetainedSessionControlRef(options?.controlRef, id, providerName, environmentId), providerName, environmentId, dispatch, exactExecutionEvents);
|
|
107
|
-
// sessions.continue was granted from
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
130
|
+
const agentSession = sandboxSessionAsAgentSession(session, resolveRetainedSessionControlRef(options?.controlRef, id, providerName, environmentId), providerName, environmentId, dispatch, exactExecutionEvents, retainedControl);
|
|
131
|
+
// sessions.continue was granted from the probe session and the
|
|
132
|
+
// deployment document together; this backstop holds every
|
|
133
|
+
// concrete session to that grant, so a client whose sessions
|
|
134
|
+
// diverge from its probe surface fails loud here instead of
|
|
135
|
+
// failing at the first cancellation.
|
|
111
136
|
if (capabilities.sessions.continue &&
|
|
112
137
|
typeof agentSession.cancelRun !== "function") {
|
|
113
138
|
throw new Error("Tangle retained session support requires SandboxSession.cancelRun");
|
package/dist/tangle-events.js
CHANGED
|
@@ -72,7 +72,15 @@ export function environmentEventFromSandboxEvent(event, expected = {}) {
|
|
|
72
72
|
if (Object.prototype.hasOwnProperty.call(data, "contextTransferReceipt")) {
|
|
73
73
|
throw new Error("Tangle Sandbox emitted an unsolicited context transfer receipt");
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
// An exact run stream is already selected by the runtime execution id.
|
|
76
|
+
// On that stream, session.updated carries the harness-native session id
|
|
77
|
+
// (for example an OpenCode session), not the runtime session id. Keep that
|
|
78
|
+
// value as event content while lifecycle frames remain identity-checked.
|
|
79
|
+
const identity = sandboxEventIdentity(event);
|
|
80
|
+
const eventExecutionId = identity.executionId;
|
|
81
|
+
const eventSessionId = expected.streamBound === true && record.type === "session.updated"
|
|
82
|
+
? undefined
|
|
83
|
+
: identity.sessionId;
|
|
76
84
|
if (expected.executionId !== undefined &&
|
|
77
85
|
((eventExecutionId === undefined && expected.streamBound !== true) ||
|
|
78
86
|
(eventExecutionId !== undefined && eventExecutionId !== expected.executionId))) {
|
package/dist/tangle-prompt.js
CHANGED
|
@@ -58,15 +58,32 @@ export function promptOptionsFromTurnInput(input, target) {
|
|
|
58
58
|
...(input.detach !== undefined ? { detach: input.detach } : {}),
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
const SANDBOX_OPTIONAL_RESULT_FIELDS = new Set([
|
|
62
|
+
"executionId",
|
|
63
|
+
"response",
|
|
64
|
+
"error",
|
|
65
|
+
"errorCode",
|
|
66
|
+
"toolInvocations",
|
|
67
|
+
"approval",
|
|
68
|
+
"question",
|
|
69
|
+
"plan",
|
|
70
|
+
"traceId",
|
|
71
|
+
"usage",
|
|
72
|
+
"costUsd",
|
|
73
|
+
]);
|
|
61
74
|
export function validatedSandboxPromptResult(result) {
|
|
62
75
|
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
63
76
|
throw new Error("Tangle prompt returned no result object");
|
|
64
77
|
}
|
|
65
|
-
const
|
|
66
|
-
if (Object.hasOwn(
|
|
67
|
-
|
|
78
|
+
const source = result;
|
|
79
|
+
if (Object.hasOwn(source, "contextTransferReceipt") &&
|
|
80
|
+
source.contextTransferReceipt === undefined) {
|
|
68
81
|
throw new Error("Tangle prompt result returned a context receipt for a turn that requested no transfer");
|
|
69
82
|
}
|
|
83
|
+
// The Sandbox SDK materializes absent optional response fields as
|
|
84
|
+
// `undefined`. They were absent on the JSON wire and must stay absent in the
|
|
85
|
+
// provider-neutral result before the strict JSON check runs.
|
|
86
|
+
const record = Object.fromEntries(Object.entries(source).filter(([field, value]) => value !== undefined || !SANDBOX_OPTIONAL_RESULT_FIELDS.has(field)));
|
|
70
87
|
assertBoundedJson(record);
|
|
71
88
|
if (typeof record.success !== "boolean") {
|
|
72
89
|
throw new Error("Tangle prompt result omitted its success status");
|
package/dist/tangle-provider.js
CHANGED
|
@@ -83,7 +83,7 @@ export function createTangleProvider(options) {
|
|
|
83
83
|
}
|
|
84
84
|
try {
|
|
85
85
|
input.signal?.throwIfAborted();
|
|
86
|
-
const environment = sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities);
|
|
86
|
+
const environment = await sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities, input.signal ? { signal: input.signal } : undefined);
|
|
87
87
|
input.signal?.throwIfAborted();
|
|
88
88
|
return environment;
|
|
89
89
|
}
|
|
@@ -115,7 +115,7 @@ export function createTangleProvider(options) {
|
|
|
115
115
|
operation?.signal?.throwIfAborted();
|
|
116
116
|
if (!box || boundedIdentifier(box.id, "Tangle environment id") !== id)
|
|
117
117
|
return null;
|
|
118
|
-
return sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities);
|
|
118
|
+
return await sandboxInstanceAsEnvironment(box, providerName, options.client, declaredCapabilities, operation?.signal ? { signal: operation.signal } : undefined);
|
|
119
119
|
},
|
|
120
120
|
}
|
|
121
121
|
: {}),
|
package/dist/tangle-types.d.ts
CHANGED
|
@@ -31,6 +31,46 @@ export interface SandboxClientLike {
|
|
|
31
31
|
}): Promise<SandboxInstanceLike[]>;
|
|
32
32
|
describePlacement?(box: SandboxInstanceLike): unknown;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The `GET /capabilities` document as this adapter reads it: what the DEPLOYED
|
|
36
|
+
* sidecar image compiled in, not which methods the linked SDK class carries.
|
|
37
|
+
*
|
|
38
|
+
* Every capability flag this shape declares gates a claim the adapter makes,
|
|
39
|
+
* and the wire document's other flags are absent here because the adapter does
|
|
40
|
+
* not act on them yet. Every field is optional, and the document's own
|
|
41
|
+
* convention is that a missing flag means "unknown to that image", never
|
|
42
|
+
* false. The linked SDK parses a v1 wire body strictly, but this adapter reads
|
|
43
|
+
* any `SandboxInstanceLike`, so it never assumes a flag was validated: an
|
|
44
|
+
* absent field reaches the claim as unknown instead of being coerced. The
|
|
45
|
+
* SDK's `SandboxRuntimeCapabilities` is assignable to this shape;
|
|
46
|
+
* `deployment-capabilities.test.ts` pins that against the published type.
|
|
47
|
+
*/
|
|
48
|
+
export interface SandboxRuntimeCapabilityDocument {
|
|
49
|
+
schema?: number;
|
|
50
|
+
agentInterface?: string;
|
|
51
|
+
sidecarVersion?: string;
|
|
52
|
+
image?: string;
|
|
53
|
+
dispatch?: {
|
|
54
|
+
/** Run requests accept a caller-supplied exact `runControlRef`. */
|
|
55
|
+
runControlRef?: boolean;
|
|
56
|
+
/** Admission echoes the executionId the request named. */
|
|
57
|
+
executionIdOnAdmission?: boolean;
|
|
58
|
+
};
|
|
59
|
+
cancel?: {
|
|
60
|
+
/** Cancellation accepts the canonical digest-bound request. */
|
|
61
|
+
canonicalRunCancellation?: boolean;
|
|
62
|
+
/** Cancellation binds to the run's request digest. */
|
|
63
|
+
digestBound?: boolean;
|
|
64
|
+
/** Replaying an operation id returns the stored acknowledgement. */
|
|
65
|
+
idempotent?: boolean;
|
|
66
|
+
};
|
|
67
|
+
runs?: {
|
|
68
|
+
/** Status and results select one execution of a session. */
|
|
69
|
+
executionScopedStatus?: boolean;
|
|
70
|
+
/** Buffered run events replay by execution. */
|
|
71
|
+
eventReplay?: boolean;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
34
74
|
export interface SandboxProcessStatusLike {
|
|
35
75
|
pid: number;
|
|
36
76
|
running: boolean;
|
|
@@ -106,6 +146,15 @@ export interface SandboxInstanceLike {
|
|
|
106
146
|
}): Promise<unknown>;
|
|
107
147
|
};
|
|
108
148
|
process?: SandboxProcessManagerLike;
|
|
149
|
+
/**
|
|
150
|
+
* Capability discovery against the deployment behind this sandbox. Absent
|
|
151
|
+
* on a Sandbox SDK older than 0.22.0, which is why every call site feature-
|
|
152
|
+
* detects it: an older SDK cannot read deployment truth, so the adapter
|
|
153
|
+
* claims no retained control rather than trusting its own method surface.
|
|
154
|
+
* Resolves to null when the deployment cannot disclose a document this SDK
|
|
155
|
+
* reads; a malformed document throws.
|
|
156
|
+
*/
|
|
157
|
+
capabilities?(): Promise<SandboxRuntimeCapabilityDocument | null>;
|
|
109
158
|
refresh?(options?: {
|
|
110
159
|
signal?: AbortSignal;
|
|
111
160
|
}): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-tangle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"dist/tangle-exact-process-validation.js",
|
|
38
38
|
"dist/tangle-capabilities.d.ts",
|
|
39
39
|
"dist/tangle-capabilities.js",
|
|
40
|
+
"dist/tangle-deployment-capabilities.d.ts",
|
|
41
|
+
"dist/tangle-deployment-capabilities.js",
|
|
40
42
|
"dist/tangle-create-options.d.ts",
|
|
41
43
|
"dist/tangle-create-options.js",
|
|
42
44
|
"dist/tangle-environment-values.d.ts",
|
|
@@ -67,7 +69,7 @@
|
|
|
67
69
|
"LICENSE"
|
|
68
70
|
],
|
|
69
71
|
"dependencies": {
|
|
70
|
-
"@tangle-network/agent-interface": "0.
|
|
72
|
+
"@tangle-network/agent-interface": "0.50.0"
|
|
71
73
|
},
|
|
72
74
|
"peerDependencies": {
|
|
73
75
|
"@tangle-network/sandbox": ">=0.19.6 <1.0.0"
|
|
@@ -80,11 +82,11 @@
|
|
|
80
82
|
"devDependencies": {
|
|
81
83
|
"@tangle-network/agent-eval": "0.145.3",
|
|
82
84
|
"@tangle-network/agent-runtime": "0.132.13",
|
|
83
|
-
"@tangle-network/sandbox": "0.
|
|
85
|
+
"@tangle-network/sandbox": "0.22.0",
|
|
84
86
|
"@types/node": "25.6.0",
|
|
85
87
|
"typescript": "^6.0.3",
|
|
86
88
|
"vitest": "^4.1.5",
|
|
87
|
-
"@tangle-network/agent-provider-testkit": "0.
|
|
89
|
+
"@tangle-network/agent-provider-testkit": "0.7.0"
|
|
88
90
|
},
|
|
89
91
|
"scripts": {
|
|
90
92
|
"build": "tsc -p tsconfig.json",
|