@tangle-network/agent-provider-tangle 0.6.2 → 0.7.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 +16 -4
- package/dist/tangle-capabilities.d.ts +32 -14
- package/dist/tangle-capabilities.js +130 -61
- package/dist/tangle-environment-control.d.ts +4 -1
- package/dist/tangle-environment-control.js +11 -10
- package/dist/tangle-environment-dispatch.js +49 -19
- package/dist/tangle-environment-session.d.ts +12 -3
- package/dist/tangle-environment-session.js +213 -60
- package/dist/tangle-environment-validation.d.ts +1 -8
- package/dist/tangle-environment-validation.js +0 -25
- package/dist/tangle-environment-values.d.ts +11 -1
- package/dist/tangle-environment-values.js +48 -13
- package/dist/tangle-environment.js +30 -57
- package/dist/tangle-events.d.ts +8 -0
- package/dist/tangle-events.js +164 -4
- package/dist/tangle-prompt.d.ts +2 -1
- package/dist/tangle-prompt.js +20 -3
- package/dist/tangle-provider.js +15 -8
- package/dist/tangle-session-control.d.ts +6 -5
- package/dist/tangle-session-control.js +31 -16
- package/dist/tangle-types.d.ts +15 -7
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @tangle-network/agent-provider-tangle
|
|
2
2
|
|
|
3
|
-
Wraps `@tangle-network/sandbox`
|
|
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.21.1.
|
|
4
5
|
|
|
5
6
|
```ts
|
|
6
7
|
import { Sandbox } from '@tangle-network/sandbox'
|
|
@@ -13,14 +14,25 @@ const provider = createTangleProvider({
|
|
|
13
14
|
|
|
14
15
|
Detached dispatch returns the immutable Sandbox execution receipt in `controlRef`.
|
|
15
16
|
The adapter validates its complete capability document and omits optional environment methods whose capabilities are disabled.
|
|
16
|
-
Reconstruct an exact session with `environment.session(reference.id, { controlRef: reference.controlRef })`; replay cursors are exclusive at the agent interface
|
|
17
|
+
Reconstruct an exact session with `environment.session(reference.id, { controlRef: reference.controlRef })`; replay cursors are exclusive at both the agent interface and Sandbox session stream.
|
|
17
18
|
Result, replay, and cancel operations select that exact execution instead of whichever execution most recently changed the shared session.
|
|
19
|
+
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
|
+
The provider claims `retainedControl` only from probed facts.
|
|
22
|
+
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
|
+
The probe measures the linked SDK's method surface, not the connected service; service-side truth needs the sidecar capability endpoint and is a follow-up.
|
|
24
|
+
A client that cannot prove those facts gets no claim, so the runtime rejects retained dispatch before any sandbox is created.
|
|
25
|
+
Each concrete sandbox narrows the declared document independently against its own measured method surface, so a capable sandbox keeps retained control even when the provider-level claim failed closed.
|
|
26
|
+
|
|
27
|
+
Pass the SDK client itself when retained control matters.
|
|
28
|
+
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.
|
|
29
|
+
A wrapper must delegate the SDK methods instead of copying properties.
|
|
18
30
|
After `session.prompt()` admits another turn, that session object's `controlRef` advances only when Sandbox returns the requested execution ID; a mismatched receipt fails without advancing local state.
|
|
19
31
|
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.
|
|
20
32
|
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.
|
|
21
33
|
It also rejects `contextTransfer` and `nativeContinuation` inputs explicitly until those operations have native Sandbox support instead of silently dropping them.
|
|
22
|
-
The
|
|
23
|
-
|
|
34
|
+
The adapter never advertises `branching.checkpoint` or `branching.fork`.
|
|
35
|
+
Sandbox exposes `snapshot`, `listSnapshots`, `deleteSnapshot`, and `branch(count)` with different semantics; durable workspace branching stays unadvertised until the full `AgentWorkspaceBranching` contract — retry, lookup, conflict, and cleanup together — is implemented over that surface.
|
|
24
36
|
|
|
25
37
|
Pass `exactProcess: {}` only when the Sandbox deployment supports `agent: false` creates and reports `metadata.runtimeMode: "control"`.
|
|
26
38
|
The optional capability creates an ephemeral sandbox with an authenticated control service but no managed agent workload or agent credentials, explicit resources, exact blocked/domain egress, bounded binary file reads, shell-free launch, and recoverable process output plus terminal reason.
|
|
@@ -4,36 +4,54 @@ import type { SandboxClientLike, SandboxInstanceLike } from "./tangle-types.js";
|
|
|
4
4
|
* The full capability document this adapter supports when the Sandbox client
|
|
5
5
|
* implements every optional method.
|
|
6
6
|
*
|
|
7
|
-
* This is an upper bound, not a claim. `
|
|
8
|
-
* to what
|
|
9
|
-
* cannot back becomes an action the
|
|
7
|
+
* This is an upper bound, not a claim. `capabilitiesForClient()` and
|
|
8
|
+
* `capabilitiesForSandbox()` narrow it to what the deployment actually
|
|
9
|
+
* exposes, because a capability the client cannot back becomes an action the
|
|
10
|
+
* caller selects and finds missing.
|
|
10
11
|
*/
|
|
11
12
|
export declare function defaultTangleSandboxCapabilities(harness?: HarnessType): AgentEnvironmentCapabilities;
|
|
12
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Deployment facts that gate declared capabilities. Every fact defaults to
|
|
15
|
+
* false when it cannot be established; a false fact clears the matching
|
|
16
|
+
* declared capability.
|
|
17
|
+
*/
|
|
13
18
|
export interface SandboxCapabilitySupport {
|
|
19
|
+
/** The provider can rebuild an environment by id (`client.get`). */
|
|
20
|
+
reconstruct: boolean;
|
|
14
21
|
dispatchPrompt: boolean;
|
|
15
22
|
session: boolean;
|
|
16
23
|
read: boolean;
|
|
17
24
|
write: boolean;
|
|
18
25
|
exec: boolean;
|
|
19
|
-
checkpoint: boolean;
|
|
20
|
-
fork: boolean;
|
|
21
26
|
placement: boolean;
|
|
22
27
|
destroy: boolean;
|
|
28
|
+
cancelRun: boolean;
|
|
23
29
|
}
|
|
24
30
|
export declare function sandboxCapabilitySupport(box: SandboxInstanceLike, client: SandboxClientLike): SandboxCapabilitySupport;
|
|
25
31
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
32
|
+
* Establish client-stage facts before any sandbox exists. Two sources:
|
|
33
|
+
* the client's own members (get, describePlacement) and, for an SDK-backed
|
|
34
|
+
* client, the linked SDK surface via `linkedSdkProbeInstance`. Retained
|
|
35
|
+
* control fails closed: without a probe handle nothing proves `cancelRun`,
|
|
36
|
+
* so the provider must not claim it. Box-scoped workspace and streaming
|
|
37
|
+
* facts stay at the declared upper bound when no handle can be minted —
|
|
38
|
+
* each concrete sandbox re-narrows them in `capabilitiesForSandbox`.
|
|
31
39
|
*/
|
|
32
|
-
export declare function
|
|
40
|
+
export declare function clientCapabilitySupport(client: SandboxClientLike): SandboxCapabilitySupport;
|
|
33
41
|
/**
|
|
34
|
-
* Narrow a declared capability document to
|
|
42
|
+
* Narrow a declared capability document to established facts.
|
|
35
43
|
*
|
|
36
44
|
* Braid derives product actions from these flags, so an over-claimed flag is
|
|
37
|
-
* an offered action that throws at the moment the user selects it.
|
|
45
|
+
* an offered action that throws at the moment the user selects it. Retained
|
|
46
|
+
* control requires the complete fact set: exact dispatch, a session handle,
|
|
47
|
+
* canonical cancellation, and environment reconstruction by id.
|
|
38
48
|
*/
|
|
49
|
+
export declare function narrowedTangleCapabilities(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport): AgentEnvironmentCapabilities;
|
|
50
|
+
/**
|
|
51
|
+
* Narrow provider-level claims to facts the client can prove before any
|
|
52
|
+
* sandbox exists. `clientCapabilitySupport` documents which facts stay at
|
|
53
|
+
* the declared upper bound when the client offers no probe surface.
|
|
54
|
+
*/
|
|
55
|
+
export declare function capabilitiesForClient(declared: AgentEnvironmentCapabilities, client: SandboxClientLike): AgentEnvironmentCapabilities;
|
|
56
|
+
/** Narrow a declared capability document to what this Sandbox instance backs. */
|
|
39
57
|
export declare function capabilitiesForSandbox(declared: AgentEnvironmentCapabilities, support: SandboxCapabilitySupport): AgentEnvironmentCapabilities;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { harnessSystemPromptIntents } from "@tangle-network/agent-interface";
|
|
2
|
+
import { SandboxInstance } from "@tangle-network/sandbox";
|
|
2
3
|
/**
|
|
3
4
|
* The full capability document this adapter supports when the Sandbox client
|
|
4
5
|
* implements every optional method.
|
|
5
6
|
*
|
|
6
|
-
* This is an upper bound, not a claim. `
|
|
7
|
-
* to what
|
|
8
|
-
* cannot back becomes an action the
|
|
7
|
+
* This is an upper bound, not a claim. `capabilitiesForClient()` and
|
|
8
|
+
* `capabilitiesForSandbox()` narrow it to what the deployment actually
|
|
9
|
+
* exposes, because a capability the client cannot back becomes an action the
|
|
10
|
+
* caller selects and finds missing.
|
|
9
11
|
*/
|
|
10
12
|
export function defaultTangleSandboxCapabilities(harness) {
|
|
11
13
|
return {
|
|
@@ -31,10 +33,22 @@ export function defaultTangleSandboxCapabilities(harness) {
|
|
|
31
33
|
validation: true,
|
|
32
34
|
},
|
|
33
35
|
streaming: { live: true, replay: true, detach: true, turnIdempotency: true },
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
36
|
+
// Retained control is declared as intent here and stripped by narrowing
|
|
37
|
+
// wherever the facts cannot prove dispatchPrompt, session, cancelRun,
|
|
38
|
+
// and environment reconstruction by id. The four sub-flags are
|
|
39
|
+
// all-or-nothing by design: this adapter implements the identities
|
|
40
|
+
// together over one Sandbox surface, and the capability schema refuses
|
|
41
|
+
// a partial block, so they stand or fall on the same probed fact set.
|
|
42
|
+
sessions: { continue: true, list: false, messages: false },
|
|
43
|
+
retainedControl: {
|
|
44
|
+
exactRunIdentity: true,
|
|
45
|
+
resultIdentity: true,
|
|
46
|
+
eventIdentity: true,
|
|
47
|
+
cancellationIdempotency: true,
|
|
48
|
+
},
|
|
37
49
|
workspace: { read: true, write: true, exec: true, git: false, upload: true, download: true },
|
|
50
|
+
// Sandbox exposes snapshot/branch, not the checkpoint/fork contract, and
|
|
51
|
+
// durable branching needs retry, lookup, conflict, and cleanup together.
|
|
38
52
|
branching: { checkpoint: false, fork: false },
|
|
39
53
|
placement: true,
|
|
40
54
|
usage: false,
|
|
@@ -43,98 +57,153 @@ export function defaultTangleSandboxCapabilities(harness) {
|
|
|
43
57
|
confidential: false,
|
|
44
58
|
};
|
|
45
59
|
}
|
|
60
|
+
// One reserved id names both probe handles; neither ever reaches the service.
|
|
61
|
+
const CAPABILITY_PROBE_ID = "__tangle-capability-probe__";
|
|
46
62
|
export function sandboxCapabilitySupport(box, client) {
|
|
63
|
+
let session;
|
|
64
|
+
if (typeof box.session === "function") {
|
|
65
|
+
try {
|
|
66
|
+
// Sandbox session handles are lazy. Inspecting one does not call the
|
|
67
|
+
// service, and keeps retained-control claims tied to the actual handle.
|
|
68
|
+
session = box.session(CAPABILITY_PROBE_ID);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// A client that cannot produce a session handle cannot prove retained control.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
47
74
|
return {
|
|
75
|
+
reconstruct: typeof client.get === "function",
|
|
48
76
|
dispatchPrompt: typeof box.dispatchPrompt === "function",
|
|
49
77
|
session: typeof box.session === "function",
|
|
50
78
|
read: typeof box.read === "function",
|
|
51
79
|
write: typeof box.write === "function",
|
|
52
80
|
exec: typeof box.exec === "function",
|
|
53
|
-
checkpoint: typeof box.checkpoint === "function",
|
|
54
|
-
fork: typeof box.fork === "function",
|
|
55
81
|
placement: typeof client.describePlacement === "function",
|
|
56
82
|
destroy: typeof box.delete === "function",
|
|
83
|
+
cancelRun: typeof session?.cancelRun === "function",
|
|
57
84
|
};
|
|
58
85
|
}
|
|
59
86
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
87
|
+
* Mint a lazy instance handle from the sandbox SDK linked into this process.
|
|
88
|
+
* The handle measures the LINKED SDK's instance and session method surface —
|
|
89
|
+
* an adapter-capability fact, not deployment truth. It is valid exactly when
|
|
90
|
+
* the client is SDK-backed (carries the SDK `fetch` transport), because the
|
|
91
|
+
* sandboxes such a client returns are instances of these same classes.
|
|
92
|
+
* Deployment truth (what the connected service honors) needs the sidecar
|
|
93
|
+
* capability endpoint and is a follow-up. The handle and its probe session
|
|
94
|
+
* never leave the process: construction and `session(id)` are lazy in the
|
|
95
|
+
* SDK, so no request is sent and no billable resource is created.
|
|
65
96
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
function linkedSdkProbeInstance(client) {
|
|
98
|
+
if (typeof client.fetch !== "function")
|
|
99
|
+
return undefined;
|
|
100
|
+
try {
|
|
101
|
+
return new SandboxInstance(client, {
|
|
102
|
+
id: CAPABILITY_PROBE_ID,
|
|
103
|
+
status: "stopped",
|
|
104
|
+
createdAt: new Date(0),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Establish client-stage facts before any sandbox exists. Two sources:
|
|
113
|
+
* the client's own members (get, describePlacement) and, for an SDK-backed
|
|
114
|
+
* client, the linked SDK surface via `linkedSdkProbeInstance`. Retained
|
|
115
|
+
* control fails closed: without a probe handle nothing proves `cancelRun`,
|
|
116
|
+
* so the provider must not claim it. Box-scoped workspace and streaming
|
|
117
|
+
* facts stay at the declared upper bound when no handle can be minted —
|
|
118
|
+
* each concrete sandbox re-narrows them in `capabilitiesForSandbox`.
|
|
119
|
+
*/
|
|
120
|
+
export function clientCapabilitySupport(client) {
|
|
121
|
+
const probe = linkedSdkProbeInstance(client);
|
|
122
|
+
if (probe)
|
|
123
|
+
return sandboxCapabilitySupport(probe, client);
|
|
124
|
+
return {
|
|
125
|
+
reconstruct: typeof client.get === "function",
|
|
126
|
+
dispatchPrompt: true,
|
|
127
|
+
session: true,
|
|
128
|
+
read: true,
|
|
129
|
+
write: true,
|
|
130
|
+
exec: true,
|
|
131
|
+
placement: typeof client.describePlacement === "function",
|
|
132
|
+
destroy: true,
|
|
133
|
+
cancelRun: false,
|
|
87
134
|
};
|
|
88
|
-
delete narrowed.interactions;
|
|
89
|
-
delete narrowed.retainedControl;
|
|
90
|
-
delete narrowed.nativeContinuation;
|
|
91
|
-
return narrowed;
|
|
92
135
|
}
|
|
93
136
|
/**
|
|
94
|
-
* Narrow a declared capability document to
|
|
137
|
+
* Narrow a declared capability document to established facts.
|
|
95
138
|
*
|
|
96
139
|
* Braid derives product actions from these flags, so an over-claimed flag is
|
|
97
|
-
* an offered action that throws at the moment the user selects it.
|
|
140
|
+
* an offered action that throws at the moment the user selects it. Retained
|
|
141
|
+
* control requires the complete fact set: exact dispatch, a session handle,
|
|
142
|
+
* canonical cancellation, and environment reconstruction by id.
|
|
98
143
|
*/
|
|
99
|
-
export function
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
144
|
+
export function narrowedTangleCapabilities(declared, support) {
|
|
145
|
+
const supportsRetainedControl = declared.sessions.continue === true &&
|
|
146
|
+
declared.streaming.detach === true &&
|
|
147
|
+
declared.streaming.replay === true &&
|
|
148
|
+
declared.streaming.turnIdempotency === true &&
|
|
149
|
+
support.reconstruct &&
|
|
150
|
+
support.dispatchPrompt &&
|
|
151
|
+
support.session &&
|
|
152
|
+
support.cancelRun;
|
|
153
|
+
// A cleared fact forces false; a held fact passes the declared value
|
|
154
|
+
// through unchanged, so a malformed declaration still reaches the schema
|
|
155
|
+
// at the provider boundary instead of being laundered into a boolean.
|
|
156
|
+
const narrowed = {
|
|
157
|
+
...declared,
|
|
106
158
|
streaming: {
|
|
107
159
|
...declared.streaming,
|
|
108
|
-
detach: declared.streaming.detach
|
|
109
|
-
replay: declared.streaming.replay
|
|
110
|
-
turnIdempotency:
|
|
160
|
+
detach: support.dispatchPrompt ? declared.streaming.detach : false,
|
|
161
|
+
replay: support.session ? declared.streaming.replay : false,
|
|
162
|
+
turnIdempotency: support.session
|
|
163
|
+
? declared.streaming.turnIdempotency
|
|
164
|
+
: false,
|
|
111
165
|
},
|
|
112
166
|
sessions: {
|
|
113
167
|
...declared.sessions,
|
|
114
|
-
continue: false,
|
|
168
|
+
continue: supportsRetainedControl ? declared.sessions.continue : false,
|
|
115
169
|
list: false,
|
|
116
170
|
messages: false,
|
|
117
171
|
},
|
|
118
172
|
workspace: {
|
|
119
173
|
...declared.workspace,
|
|
120
|
-
read: declared.workspace.read
|
|
121
|
-
write: declared.workspace.write
|
|
122
|
-
exec: declared.workspace.exec
|
|
174
|
+
read: support.read ? declared.workspace.read : false,
|
|
175
|
+
write: support.write ? declared.workspace.write : false,
|
|
176
|
+
exec: support.exec ? declared.workspace.exec : false,
|
|
123
177
|
git: false,
|
|
124
|
-
upload: declared.workspace.upload
|
|
125
|
-
download: declared.workspace.download
|
|
178
|
+
upload: support.write ? declared.workspace.upload : false,
|
|
179
|
+
download: support.read ? declared.workspace.download : false,
|
|
126
180
|
},
|
|
127
181
|
branching: {
|
|
128
|
-
...
|
|
182
|
+
...declared.branching,
|
|
129
183
|
checkpoint: false,
|
|
130
184
|
fork: false,
|
|
131
|
-
...(declared.branching.retrySafe !== undefined
|
|
132
|
-
? { retrySafe: false }
|
|
133
|
-
: {}),
|
|
185
|
+
...(declared.branching.retrySafe !== undefined ? { retrySafe: false } : {}),
|
|
134
186
|
...(declared.branching.lookup !== undefined ? { lookup: false } : {}),
|
|
135
187
|
...(declared.branching.cleanup !== undefined ? { cleanup: false } : {}),
|
|
136
188
|
},
|
|
137
|
-
placement:
|
|
189
|
+
placement: support.placement ? declared.placement : false,
|
|
138
190
|
usage: false,
|
|
139
191
|
};
|
|
192
|
+
delete narrowed.interactions;
|
|
193
|
+
delete narrowed.nativeContinuation;
|
|
194
|
+
if (!supportsRetainedControl)
|
|
195
|
+
delete narrowed.retainedControl;
|
|
196
|
+
return narrowed;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Narrow provider-level claims to facts the client can prove before any
|
|
200
|
+
* sandbox exists. `clientCapabilitySupport` documents which facts stay at
|
|
201
|
+
* the declared upper bound when the client offers no probe surface.
|
|
202
|
+
*/
|
|
203
|
+
export function capabilitiesForClient(declared, client) {
|
|
204
|
+
return narrowedTangleCapabilities(declared, clientCapabilitySupport(client));
|
|
205
|
+
}
|
|
206
|
+
/** Narrow a declared capability document to what this Sandbox instance backs. */
|
|
207
|
+
export function capabilitiesForSandbox(declared, support) {
|
|
208
|
+
return narrowedTangleCapabilities(declared, support);
|
|
140
209
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { AgentSessionRef, AgentTurnInput } from "@tangle-network/agent-interface/environment-provider";
|
|
2
2
|
import type { SandboxInstanceLike, SandboxSessionLike } from "./tangle-types.js";
|
|
3
|
-
export declare function sessionPromptRequestDigest(input: AgentTurnInput, provider: string, environmentId: string, sessionId: string,
|
|
3
|
+
export declare function sessionPromptRequestDigest(input: AgentTurnInput, provider: string, environmentId: string, sessionId: string, options?: {
|
|
4
|
+
executionId?: string;
|
|
5
|
+
nonce?: string;
|
|
6
|
+
}): `sha256:${string}`;
|
|
4
7
|
export declare function hasReplayPayload(input: AgentTurnInput): boolean;
|
|
5
8
|
export declare function interruptAfterAbort(box: SandboxInstanceLike, reference: AgentSessionRef): Promise<void>;
|
|
6
9
|
export declare function interruptExecutionAfterAbort(source: SandboxInstanceLike | SandboxSessionLike, sessionId: string, executionId: string): Promise<void>;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { canonicalCandidateDigest } from "@tangle-network/agent-interface";
|
|
2
|
-
export function sessionPromptRequestDigest(input, provider, environmentId, sessionId,
|
|
2
|
+
export function sessionPromptRequestDigest(input, provider, environmentId, sessionId, options = {}) {
|
|
3
3
|
return canonicalCandidateDigest({
|
|
4
4
|
provider,
|
|
5
5
|
environmentId,
|
|
6
6
|
sessionId,
|
|
7
|
-
executionId
|
|
7
|
+
...(options.executionId === undefined
|
|
8
|
+
? {}
|
|
9
|
+
: { executionId: options.executionId }),
|
|
10
|
+
...(options.nonce === undefined ? {} : { nonce: options.nonce }),
|
|
8
11
|
...(input.turnId === undefined ? {} : { turnId: input.turnId }),
|
|
9
12
|
...(input.prompt === undefined ? {} : { prompt: input.prompt }),
|
|
10
13
|
...(input.parts === undefined ? {} : { parts: input.parts }),
|
|
@@ -25,17 +28,15 @@ export function hasReplayPayload(input) {
|
|
|
25
28
|
input.providerOptions !== undefined);
|
|
26
29
|
}
|
|
27
30
|
export async function interruptAfterAbort(box, reference) {
|
|
28
|
-
const
|
|
31
|
+
const metadata = reference.metadata;
|
|
32
|
+
// A duplicate dispatch receipt identifies work owned by another caller.
|
|
33
|
+
// Only a receipt that proves this call admitted new work may be interrupted.
|
|
34
|
+
if (metadata?.dispatched !== true || metadata.alreadyExisted === true)
|
|
35
|
+
return;
|
|
29
36
|
const executionId = reference.controlRef?.executionId;
|
|
30
37
|
if (!box.session || executionId === undefined)
|
|
31
38
|
return;
|
|
32
|
-
|
|
33
|
-
await box.session(sessionId)?.interrupt({ executionId });
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
// The original abort remains the caller-visible outcome; the provider has
|
|
37
|
-
// no stronger cleanup primitive when the late dispatch result is lost.
|
|
38
|
-
}
|
|
39
|
+
await interruptExecutionAfterAbort(box, reference.id, executionId);
|
|
39
40
|
}
|
|
40
41
|
export async function interruptExecutionAfterAbort(source, sessionId, executionId) {
|
|
41
42
|
try {
|
|
@@ -1,23 +1,61 @@
|
|
|
1
|
-
import { AgentTurnInputSchema } from "@tangle-network/agent-interface";
|
|
2
|
-
import {
|
|
3
|
-
import { retainedSessionControlRef, sessionRefFromSandboxDispatch } from "./tangle-session-control.js";
|
|
1
|
+
import { AgentExactRunControlRefSchema, AgentTurnInputSchema, } from "@tangle-network/agent-interface";
|
|
2
|
+
import { promptFromTurnInput, promptOptionsFromTurnInput, } from "./tangle-prompt.js";
|
|
3
|
+
import { retainedSessionControlRef, sessionPromptExecutionId, sessionPromptSessionId, sessionRefFromSandboxDispatch, } from "./tangle-session-control.js";
|
|
4
4
|
import { awaitWithSignal } from "./tangle-contract-safety.js";
|
|
5
5
|
import { interruptAfterAbort, sessionPromptRequestDigest, } from "./tangle-environment-control.js";
|
|
6
6
|
export function dispatchEnvironmentRun(box, provider, environmentId) {
|
|
7
7
|
return async (input) => {
|
|
8
8
|
AgentTurnInputSchema.parse(input);
|
|
9
9
|
input.signal?.throwIfAborted();
|
|
10
|
-
const
|
|
11
|
-
|
|
10
|
+
const callerControlRef = input.controlRef === undefined
|
|
11
|
+
? undefined
|
|
12
|
+
: AgentExactRunControlRefSchema.parse(input.controlRef);
|
|
13
|
+
if (callerControlRef !== undefined) {
|
|
14
|
+
if (callerControlRef.provider !== provider ||
|
|
15
|
+
callerControlRef.environmentId !== environmentId) {
|
|
16
|
+
throw new Error("Tangle control reference does not match this environment");
|
|
17
|
+
}
|
|
18
|
+
if (input.sessionId !== undefined &&
|
|
19
|
+
input.sessionId !== callerControlRef.sessionId) {
|
|
20
|
+
throw new Error("Tangle sessionId conflicts with the control reference");
|
|
21
|
+
}
|
|
22
|
+
if (input.executionId !== undefined &&
|
|
23
|
+
input.executionId !== callerControlRef.executionId) {
|
|
24
|
+
throw new Error("Tangle executionId conflicts with the control reference");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const expectedSessionId = input.sessionId ??
|
|
28
|
+
callerControlRef?.sessionId ??
|
|
29
|
+
sessionPromptSessionId(provider, environmentId, input.turnId);
|
|
30
|
+
if (expectedSessionId === undefined) {
|
|
31
|
+
throw new Error("Tangle detached dispatch requires an exact sessionId or turnId");
|
|
32
|
+
}
|
|
33
|
+
const exactInput = { ...input, sessionId: expectedSessionId };
|
|
34
|
+
const requestedExecutionId = input.executionId ?? callerControlRef?.executionId;
|
|
35
|
+
const baseRequestDigest = sessionPromptRequestDigest(exactInput, provider, environmentId, expectedSessionId);
|
|
36
|
+
const expectedExecutionId = requestedExecutionId ?? sessionPromptExecutionId(baseRequestDigest);
|
|
37
|
+
const requestDigest = sessionPromptRequestDigest(exactInput, provider, environmentId, expectedSessionId, { executionId: expectedExecutionId });
|
|
38
|
+
if (callerControlRef !== undefined) {
|
|
39
|
+
if (callerControlRef.requestDigest !== requestDigest) {
|
|
40
|
+
throw new Error("Tangle prompt request digest conflicts with the control reference");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const runControlRef = retainedSessionControlRef(expectedSessionId, expectedExecutionId, provider, environmentId, requestDigest, callerControlRef?.runId);
|
|
44
|
+
const dispatchInput = {
|
|
45
|
+
...exactInput,
|
|
46
|
+
executionId: expectedExecutionId,
|
|
47
|
+
controlRef: runControlRef,
|
|
48
|
+
};
|
|
49
|
+
const promise = box.dispatchPrompt?.(promptFromTurnInput(dispatchInput), promptOptionsFromTurnInput(dispatchInput, { provider, environmentId }));
|
|
12
50
|
let dispatched;
|
|
13
51
|
try {
|
|
14
52
|
dispatched = await awaitWithSignal(promise, input.signal);
|
|
15
53
|
}
|
|
16
54
|
catch (error) {
|
|
17
|
-
if (input.signal?.aborted && promise) {
|
|
55
|
+
if (input.signal?.aborted && input.detach !== true && promise) {
|
|
18
56
|
void promise
|
|
19
57
|
.then((late) => {
|
|
20
|
-
const lateRef = sessionRefFromSandboxDispatch(late, provider, environmentId,
|
|
58
|
+
const lateRef = sessionRefFromSandboxDispatch(late, provider, environmentId, expectedExecutionId, requestDigest, expectedSessionId, callerControlRef);
|
|
21
59
|
return interruptAfterAbort(box, lateRef);
|
|
22
60
|
})
|
|
23
61
|
.catch(() => undefined);
|
|
@@ -26,7 +64,7 @@ export function dispatchEnvironmentRun(box, provider, environmentId) {
|
|
|
26
64
|
}
|
|
27
65
|
let reference;
|
|
28
66
|
try {
|
|
29
|
-
reference = sessionRefFromSandboxDispatch(dispatched, provider, environmentId,
|
|
67
|
+
reference = sessionRefFromSandboxDispatch(dispatched, provider, environmentId, expectedExecutionId, requestDigest, expectedSessionId, callerControlRef);
|
|
30
68
|
}
|
|
31
69
|
catch (error) {
|
|
32
70
|
try {
|
|
@@ -41,20 +79,12 @@ export function dispatchEnvironmentRun(box, provider, environmentId) {
|
|
|
41
79
|
}
|
|
42
80
|
throw error;
|
|
43
81
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
? undefined
|
|
47
|
-
: sessionPromptRequestDigest(input, provider, environmentId, reference.id, executionId);
|
|
48
|
-
const boundReference = executionId === undefined || requestDigest === undefined
|
|
49
|
-
? reference
|
|
50
|
-
: {
|
|
51
|
-
...reference,
|
|
52
|
-
controlRef: retainedSessionControlRef(reference.id, executionId, provider, environmentId, requestDigest),
|
|
53
|
-
};
|
|
54
|
-
if (input.signal?.aborted) {
|
|
82
|
+
const boundReference = reference;
|
|
83
|
+
if (input.signal?.aborted && input.detach !== true) {
|
|
55
84
|
await interruptAfterAbort(box, boundReference);
|
|
56
85
|
input.signal.throwIfAborted();
|
|
57
86
|
}
|
|
87
|
+
input.signal?.throwIfAborted();
|
|
58
88
|
return boundReference;
|
|
59
89
|
};
|
|
60
90
|
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import type { AgentSession } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
-
import type { AgentRunControlRef } from "@tangle-network/agent-interface";
|
|
1
|
+
import type { AgentSession, AgentSessionRef, AgentTurnInput } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
+
import type { AgentExactRunControlRef, AgentRunControlRef } from "@tangle-network/agent-interface";
|
|
3
|
+
import type { SandboxEvent } from "@tangle-network/sandbox";
|
|
3
4
|
import type { SandboxSessionLike } from "./tangle-types.js";
|
|
4
|
-
|
|
5
|
+
type ExactExecutionEventStream = (options: {
|
|
6
|
+
sessionId: string;
|
|
7
|
+
executionId: string;
|
|
8
|
+
since?: string;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
controlRef?: AgentExactRunControlRef;
|
|
11
|
+
}) => AsyncIterable<SandboxEvent>;
|
|
12
|
+
export declare function sandboxSessionAsAgentSession(session: SandboxSessionLike, controlRef: AgentRunControlRef | undefined, provider: string, environmentId: string, dispatch?: (input: AgentTurnInput) => Promise<AgentSessionRef>, exactExecutionEvents?: ExactExecutionEventStream): AgentSession;
|
|
13
|
+
export {};
|