@tangle-network/agent-provider-tangle 0.14.4 → 1.0.1
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/dist/tangle-capabilities.js +9 -1
- package/dist/tangle-create-options.d.ts +4 -4
- package/dist/tangle-create-options.js +17 -44
- package/dist/tangle-environment-session.js +0 -1
- package/dist/tangle-environment.js +0 -1
- package/dist/tangle-interaction-response.d.ts +0 -1
- package/dist/tangle-interaction-response.js +9 -7
- package/dist/tangle-prompt.d.ts +1 -1
- package/dist/tangle-prompt.js +2 -0
- package/dist/tangle-provider.js +3 -3
- package/dist/tangle-types.d.ts +2 -0
- package/dist/tangle-workspace-branching.js +92 -8
- package/package.json +6 -6
|
@@ -72,7 +72,15 @@ export function defaultTangleSandboxCapabilities(harness) {
|
|
|
72
72
|
eventIdentity: true,
|
|
73
73
|
cancellationIdempotency: true,
|
|
74
74
|
},
|
|
75
|
-
workspace: {
|
|
75
|
+
workspace: {
|
|
76
|
+
read: true,
|
|
77
|
+
write: true,
|
|
78
|
+
exec: true,
|
|
79
|
+
git: false,
|
|
80
|
+
upload: true,
|
|
81
|
+
download: true,
|
|
82
|
+
cwdBases: { repository: true, host: false },
|
|
83
|
+
},
|
|
76
84
|
// The complete Sandbox SDK surface backs this contract. Narrowing below
|
|
77
85
|
// removes every flag when any recovery or cleanup method is absent.
|
|
78
86
|
branching: {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { BackendType, CreateSandboxOptions } from "@tangle-network/sandbox";
|
|
2
|
-
import type { CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
|
|
3
|
-
export declare function sandboxOptionsFromCreateInput(input: CreateAgentEnvironmentInput, defaultBackend: BackendType): CreateSandboxOptions;
|
|
2
|
+
import type { CreateAgentEnvironmentInput, WorkspaceRequest } from "@tangle-network/agent-interface/environment-provider";
|
|
3
|
+
export declare function sandboxOptionsFromCreateInput(input: CreateAgentEnvironmentInput, defaultBackend: BackendType, parsedWorkspace?: WorkspaceRequest): CreateSandboxOptions;
|
|
4
4
|
/** Reject value-bearing secret maps before any custom mapper can drop them. */
|
|
5
|
-
export declare function assertNoInlineSecretValues(input: CreateAgentEnvironmentInput): void;
|
|
5
|
+
export declare function assertNoInlineSecretValues(input: CreateAgentEnvironmentInput, parsedWorkspace?: WorkspaceRequest): void;
|
|
6
6
|
/** A custom mapper must not smuggle a value map into the Sandbox request. */
|
|
7
7
|
export declare function assertMappedSecretNames(options: CreateSandboxOptions): void;
|
|
8
|
-
export declare function assertCreateInputShape(input: CreateAgentEnvironmentInput):
|
|
8
|
+
export declare function assertCreateInputShape(input: CreateAgentEnvironmentInput, parsedWorkspace?: WorkspaceRequest): WorkspaceRequest | undefined;
|
|
9
9
|
export declare function assertMappedCreateOptions(options: CreateSandboxOptions): void;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { WorkspaceRequestSchema, workspaceCwdPathForBase, } from "@tangle-network/agent-interface/environment-provider";
|
|
1
2
|
import { assertBoundedJson, boundedIdentifier, boundedString, MAX_ARRAY_LENGTH, MAX_MAP_ENTRIES, } from "./tangle-contract-safety.js";
|
|
2
3
|
import { sandboxResourcesFromResourceRequest } from "./tangle-resources.js";
|
|
3
|
-
export function sandboxOptionsFromCreateInput(input, defaultBackend) {
|
|
4
|
-
assertCreateInputShape(input);
|
|
5
|
-
assertNoInlineSecretValues(input);
|
|
4
|
+
export function sandboxOptionsFromCreateInput(input, defaultBackend, parsedWorkspace) {
|
|
5
|
+
const workspace = assertCreateInputShape(input, parsedWorkspace) ?? {};
|
|
6
|
+
assertNoInlineSecretValues(input, workspace);
|
|
6
7
|
if (input.providerOptions && Object.keys(input.providerOptions).length > 0) {
|
|
7
8
|
throw new Error("Tangle create providerOptions are not supported");
|
|
8
9
|
}
|
|
@@ -19,23 +20,10 @@ export function sandboxOptionsFromCreateInput(input, defaultBackend) {
|
|
|
19
20
|
boundedIdentifier(input.backend, "Tangle backend");
|
|
20
21
|
if (input.env !== undefined)
|
|
21
22
|
assertStringRecord(input.env, "Tangle");
|
|
22
|
-
const workspace = input.workspace ?? {};
|
|
23
23
|
if (workspace.providerOptions && Object.keys(workspace.providerOptions).length > 0) {
|
|
24
24
|
throw new Error("Tangle workspace providerOptions are not supported");
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
assertBoundedRecord(workspace.providerOptions, "Tangle workspace providerOptions");
|
|
28
|
-
if (workspace.environment !== undefined) {
|
|
29
|
-
boundedIdentifier(workspace.environment, "Tangle workspace environment");
|
|
30
|
-
}
|
|
31
|
-
if (workspace.image !== undefined)
|
|
32
|
-
boundedString(workspace.image, "Tangle workspace image");
|
|
33
|
-
if (workspace.cwd !== undefined)
|
|
34
|
-
boundedString(workspace.cwd, "Tangle workspace cwd");
|
|
35
|
-
if (workspace.repoUrl !== undefined)
|
|
36
|
-
boundedString(workspace.repoUrl, "Tangle repository URL");
|
|
37
|
-
if (workspace.gitRef !== undefined)
|
|
38
|
-
boundedIdentifier(workspace.gitRef, "Tangle git ref");
|
|
26
|
+
const workspaceCwd = workspaceCwdPathForBase(workspace.cwd, "repository", "Tangle");
|
|
39
27
|
if (input.resources?.providerOptions && Object.keys(input.resources.providerOptions).length > 0) {
|
|
40
28
|
throw new Error("Tangle resource providerOptions are not supported");
|
|
41
29
|
}
|
|
@@ -44,24 +32,16 @@ export function sandboxOptionsFromCreateInput(input, defaultBackend) {
|
|
|
44
32
|
if (input.idempotencyKey !== undefined) {
|
|
45
33
|
boundedIdentifier(input.idempotencyKey, "Tangle idempotency key");
|
|
46
34
|
}
|
|
47
|
-
if (input.workspace?.cwd === "")
|
|
48
|
-
throw new Error("Tangle workspace cwd cannot be empty");
|
|
49
|
-
if (workspace.image === "")
|
|
50
|
-
throw new Error("Tangle workspace image cannot be empty");
|
|
51
|
-
if (workspace.repoUrl === "")
|
|
52
|
-
throw new Error("Tangle repository URL cannot be empty");
|
|
53
35
|
const resources = sandboxResourcesFromResourceRequest(input.resources);
|
|
54
|
-
if (workspace.environment !== undefined && workspace.image !== undefined) {
|
|
55
|
-
throw new Error("Tangle workspace cannot specify both environment and image");
|
|
56
|
-
}
|
|
57
36
|
// Sandbox injects secrets by name from its own store. Accepting a name/value
|
|
58
37
|
// record and dropping it would create an environment with no credentials and
|
|
59
38
|
// no error, surfacing later as an unexplained tool failure.
|
|
60
39
|
const environment = workspace.image ?? workspace.environment;
|
|
61
40
|
const base = {};
|
|
62
|
-
|
|
41
|
+
const mapped = {
|
|
63
42
|
...base,
|
|
64
43
|
...(environment !== undefined ? { environment } : {}),
|
|
44
|
+
...(workspaceCwd === undefined ? {} : { cwd: workspaceCwd }),
|
|
65
45
|
...(workspace.repoUrl
|
|
66
46
|
? {
|
|
67
47
|
git: {
|
|
@@ -82,9 +62,10 @@ export function sandboxOptionsFromCreateInput(input, defaultBackend) {
|
|
|
82
62
|
profile: inlineAgentProfile(input.profile),
|
|
83
63
|
},
|
|
84
64
|
};
|
|
65
|
+
return mapped;
|
|
85
66
|
}
|
|
86
67
|
/** Reject value-bearing secret maps before any custom mapper can drop them. */
|
|
87
|
-
export function assertNoInlineSecretValues(input) {
|
|
68
|
+
export function assertNoInlineSecretValues(input, parsedWorkspace) {
|
|
88
69
|
if (input.providerOptions !== undefined) {
|
|
89
70
|
if (!input.providerOptions || typeof input.providerOptions !== "object" || Array.isArray(input.providerOptions)) {
|
|
90
71
|
throw new Error("Tangle create providerOptions must be a JSON object");
|
|
@@ -95,11 +76,8 @@ export function assertNoInlineSecretValues(input) {
|
|
|
95
76
|
}
|
|
96
77
|
}
|
|
97
78
|
if (input.workspace?.providerOptions !== undefined) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
assertBoundedJson(input.workspace.providerOptions);
|
|
102
|
-
if (Object.keys(input.workspace.providerOptions).length > 0) {
|
|
79
|
+
const workspace = parsedWorkspace ?? WorkspaceRequestSchema.parse(input.workspace);
|
|
80
|
+
if (workspace.providerOptions && Object.keys(workspace.providerOptions).length > 0) {
|
|
103
81
|
throw new Error("Tangle workspace providerOptions are not supported");
|
|
104
82
|
}
|
|
105
83
|
}
|
|
@@ -136,7 +114,7 @@ export function assertMappedSecretNames(options) {
|
|
|
136
114
|
boundedIdentifier(secret, "Tangle mapped secret name");
|
|
137
115
|
}
|
|
138
116
|
}
|
|
139
|
-
export function assertCreateInputShape(input) {
|
|
117
|
+
export function assertCreateInputShape(input, parsedWorkspace) {
|
|
140
118
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
141
119
|
throw new Error("Tangle create input must be an object");
|
|
142
120
|
}
|
|
@@ -166,16 +144,6 @@ export function assertCreateInputShape(input) {
|
|
|
166
144
|
}
|
|
167
145
|
assertBoundedJson(input.profile);
|
|
168
146
|
}
|
|
169
|
-
if (input.workspace !== undefined) {
|
|
170
|
-
if (!input.workspace || typeof input.workspace !== "object" || Array.isArray(input.workspace)) {
|
|
171
|
-
throw new Error("Tangle workspace must be an object");
|
|
172
|
-
}
|
|
173
|
-
const workspaceKeys = new Set(Object.keys(input.workspace));
|
|
174
|
-
for (const key of ["environment", "image", "repoUrl", "gitRef", "cwd", "providerOptions"])
|
|
175
|
-
workspaceKeys.delete(key);
|
|
176
|
-
if (workspaceKeys.size > 0)
|
|
177
|
-
throw new Error("Tangle workspace contains unsupported fields");
|
|
178
|
-
}
|
|
179
147
|
if (input.resources !== undefined) {
|
|
180
148
|
if (!input.resources || typeof input.resources !== "object" || Array.isArray(input.resources)) {
|
|
181
149
|
throw new Error("Tangle resources must be an object");
|
|
@@ -186,6 +154,11 @@ export function assertCreateInputShape(input) {
|
|
|
186
154
|
if (resourceKeys.size > 0)
|
|
187
155
|
throw new Error("Tangle resources contain unsupported fields");
|
|
188
156
|
}
|
|
157
|
+
if (input.workspace === undefined)
|
|
158
|
+
return undefined;
|
|
159
|
+
const workspace = parsedWorkspace ?? WorkspaceRequestSchema.parse(input.workspace);
|
|
160
|
+
workspaceCwdPathForBase(workspace.cwd, "repository", "Tangle");
|
|
161
|
+
return workspace;
|
|
189
162
|
}
|
|
190
163
|
export function assertMappedCreateOptions(options) {
|
|
191
164
|
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
@@ -20,20 +20,22 @@ function acknowledge(command, status, extras = {}) {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* The coordinates this environment can answer for
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* The coordinates this environment can answer for before the command reaches
|
|
24
|
+
* the deployment. Tangle hosts a backend adapter inside the sandbox, so the
|
|
25
|
+
* interaction binding's provider names that inner adapter (for example,
|
|
26
|
+
* `opencode`) rather than this outer provider. The deployment compares that
|
|
27
|
+
* provider with its durable interaction record. Environment and session are
|
|
28
|
+
* still checked here because the Sandbox SDK rejects those coordinates before
|
|
29
|
+
* it can return an acknowledgement.
|
|
27
30
|
*/
|
|
28
31
|
function foreignBinding(command, options) {
|
|
29
32
|
const binding = command.binding;
|
|
30
|
-
if (binding.
|
|
31
|
-
binding.environmentId === options.environmentId &&
|
|
33
|
+
if (binding.environmentId === options.environmentId &&
|
|
32
34
|
binding.sessionId === options.sessionId) {
|
|
33
35
|
return undefined;
|
|
34
36
|
}
|
|
35
37
|
return acknowledge(command, "binding_mismatch", {
|
|
36
|
-
message: `response command binding names another
|
|
38
|
+
message: `response command binding names another environment or session than ${options.environmentId}/${options.sessionId}`,
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
/**
|
package/dist/tangle-prompt.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare function promptOptionsFromTurnInput(input: AgentTurnInput, target
|
|
|
8
8
|
environmentId: string;
|
|
9
9
|
sessionId?: string;
|
|
10
10
|
}): PromptOptions;
|
|
11
|
-
type SandboxRunStatus = "success" | "failed" | "blocked_on_approval" | "awaiting_question" | "awaiting_plan_decision";
|
|
11
|
+
type SandboxRunStatus = "success" | "failed" | "blocked_on_approval" | "awaiting_question" | "awaiting_interaction" | "awaiting_plan_decision";
|
|
12
12
|
type ValidatedSandboxPromptResult = Record<string, unknown> & {
|
|
13
13
|
success: boolean;
|
|
14
14
|
status: SandboxRunStatus;
|
package/dist/tangle-prompt.js
CHANGED
|
@@ -97,6 +97,7 @@ export function validatedSandboxPromptResult(result) {
|
|
|
97
97
|
"failed",
|
|
98
98
|
"blocked_on_approval",
|
|
99
99
|
"awaiting_question",
|
|
100
|
+
"awaiting_interaction",
|
|
100
101
|
"awaiting_plan_decision",
|
|
101
102
|
]);
|
|
102
103
|
if (typeof record.status !== "string" ||
|
|
@@ -134,6 +135,7 @@ export function validatedSandboxPromptResult(result) {
|
|
|
134
135
|
const AWAITING_STATUSES = new Set([
|
|
135
136
|
"blocked_on_approval",
|
|
136
137
|
"awaiting_question",
|
|
138
|
+
"awaiting_interaction",
|
|
137
139
|
"awaiting_plan_decision",
|
|
138
140
|
]);
|
|
139
141
|
export function agentTurnResultFromPromptRecord(record, options = {}) {
|
package/dist/tangle-provider.js
CHANGED
|
@@ -43,9 +43,9 @@ export function createTangleProvider(options) {
|
|
|
43
43
|
const resolveCapabilities = async () => narrowedProviderCapabilities(await resolveDeclaredCapabilities());
|
|
44
44
|
const createRecords = new Map();
|
|
45
45
|
const createEnvironment = async (input) => {
|
|
46
|
-
assertCreateInputShape(input);
|
|
46
|
+
const parsedWorkspace = assertCreateInputShape(input);
|
|
47
47
|
input.signal?.throwIfAborted();
|
|
48
|
-
assertNoInlineSecretValues(input);
|
|
48
|
+
assertNoInlineSecretValues(input, parsedWorkspace);
|
|
49
49
|
if (input.providerOptions && Object.keys(input.providerOptions).length > 0) {
|
|
50
50
|
throw new Error("Tangle create providerOptions are not supported");
|
|
51
51
|
}
|
|
@@ -55,7 +55,7 @@ export function createTangleProvider(options) {
|
|
|
55
55
|
const declaredCapabilities = await resolveDeclaredCapabilities();
|
|
56
56
|
narrowedProviderCapabilities(declaredCapabilities);
|
|
57
57
|
const createOptions = options.mapCreateInput?.(input) ??
|
|
58
|
-
sandboxOptionsFromCreateInput(input, options.defaultBackend ?? "opencode");
|
|
58
|
+
sandboxOptionsFromCreateInput(input, options.defaultBackend ?? "opencode", parsedWorkspace);
|
|
59
59
|
assertMappedCreateOptions(createOptions);
|
|
60
60
|
if (input.idempotencyKey !== undefined &&
|
|
61
61
|
createOptions.idempotencyKey !== input.idempotencyKey) {
|
package/dist/tangle-types.d.ts
CHANGED
|
@@ -202,6 +202,8 @@ export interface SandboxWorkspaceOperationLookupLike {
|
|
|
202
202
|
outcome: "found" | "not_found" | "conflict" | "unknown";
|
|
203
203
|
kind: "checkpoint" | "fork";
|
|
204
204
|
state?: "pending" | "succeeded" | "failed";
|
|
205
|
+
/** The durable operation result, when the service records one. */
|
|
206
|
+
result?: Record<string, unknown>;
|
|
205
207
|
}
|
|
206
208
|
/** Fan-out acknowledgement returned by SandboxInstance.fork(). */
|
|
207
209
|
export interface SandboxForkAcknowledgementLike {
|
|
@@ -116,7 +116,7 @@ export function createTangleWorkspaceBranching(options) {
|
|
|
116
116
|
message: "Sandbox fork child identity is incomplete",
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
|
-
const environment = await environmentFromChild(recovered.marker.request, child, provider, child.createdAt, options.confidentialAttestationVerifier, signal);
|
|
119
|
+
const environment = await environmentFromChild(recovered.marker.request, child, provider, recovered.createdAt ?? child.createdAt, options.confidentialAttestationVerifier, signal);
|
|
120
120
|
if (!environment) {
|
|
121
121
|
return {
|
|
122
122
|
state: "undecided",
|
|
@@ -699,6 +699,24 @@ function validForkResult(result) {
|
|
|
699
699
|
result.materializedCount === result.children.length &&
|
|
700
700
|
typeof result.complete === "boolean");
|
|
701
701
|
}
|
|
702
|
+
function validOperationRecord(value) {
|
|
703
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
704
|
+
}
|
|
705
|
+
function validOperationDate(value) {
|
|
706
|
+
return ((typeof value === "string" || value instanceof Date) && validDate(value));
|
|
707
|
+
}
|
|
708
|
+
function validSnapshotOperationResult(value) {
|
|
709
|
+
return (validOperationRecord(value) &&
|
|
710
|
+
safeIdentifier(value.snapshotId) !== undefined &&
|
|
711
|
+
validOperationDate(value.createdAt));
|
|
712
|
+
}
|
|
713
|
+
function validForkOperationChildResult(value) {
|
|
714
|
+
return (validOperationRecord(value) &&
|
|
715
|
+
safeIdentifier(value.sandboxId ?? value.id) !== undefined &&
|
|
716
|
+
(value.createdAt === undefined ||
|
|
717
|
+
value.createdAt === null ||
|
|
718
|
+
validOperationDate(value.createdAt)));
|
|
719
|
+
}
|
|
702
720
|
function checkpointMarkerTags(request) {
|
|
703
721
|
const marker = {
|
|
704
722
|
version: 1,
|
|
@@ -755,22 +773,30 @@ function forkMarkerMetadata(request) {
|
|
|
755
773
|
* A marker only names a candidate resource. Nothing is returned to a caller
|
|
756
774
|
* until the ledger reports the operation succeeded.
|
|
757
775
|
*/
|
|
758
|
-
async function
|
|
776
|
+
async function checkpointOperationLookup(box, marker, signal) {
|
|
759
777
|
const lookup = await awaitWithSignal(box.getSnapshotOperation?.(marker.idempotencyKey, {
|
|
760
778
|
tags: marker.legacy
|
|
761
779
|
? legacyCheckpointMarkerTags(marker.request)
|
|
762
780
|
: checkpointMarkerTags(marker.request),
|
|
763
781
|
}), signal);
|
|
782
|
+
return lookup;
|
|
783
|
+
}
|
|
784
|
+
async function checkpointOperationSucceeded(box, marker, signal) {
|
|
785
|
+
const lookup = await checkpointOperationLookup(box, marker, signal);
|
|
764
786
|
return (lookup?.outcome === "found" &&
|
|
765
787
|
lookup.kind === "checkpoint" &&
|
|
766
788
|
lookup.state === "succeeded");
|
|
767
789
|
}
|
|
768
790
|
/** The fork equivalent of {@link checkpointOperationSucceeded}. */
|
|
769
|
-
async function
|
|
791
|
+
async function forkOperationLookup(box, marker, signal) {
|
|
770
792
|
const lookup = await awaitWithSignal(box.getForkOperation?.(marker.idempotencyKey, {
|
|
771
793
|
count: 1,
|
|
772
794
|
metadata: forkMarkerMetadata(marker.request),
|
|
773
795
|
}), signal);
|
|
796
|
+
return lookup;
|
|
797
|
+
}
|
|
798
|
+
async function forkOperationSucceeded(box, marker, signal) {
|
|
799
|
+
const lookup = await forkOperationLookup(box, marker, signal);
|
|
774
800
|
return (lookup?.outcome === "found" &&
|
|
775
801
|
lookup.kind === "fork" &&
|
|
776
802
|
lookup.state === "succeeded");
|
|
@@ -818,8 +844,14 @@ async function findCheckpointByKey(box, key, signal) {
|
|
|
818
844
|
if (!marker)
|
|
819
845
|
continue;
|
|
820
846
|
try {
|
|
821
|
-
|
|
822
|
-
|
|
847
|
+
const lookup = await checkpointOperationLookup(box, marker, signal);
|
|
848
|
+
if (lookup?.outcome === "found" &&
|
|
849
|
+
lookup.kind === "checkpoint" &&
|
|
850
|
+
lookup.state === "succeeded") {
|
|
851
|
+
const authoritative = snapshotFromOperationResult(snapshot, lookup);
|
|
852
|
+
if (authoritative === undefined)
|
|
853
|
+
return undefined;
|
|
854
|
+
return { snapshot: authoritative, marker };
|
|
823
855
|
}
|
|
824
856
|
unresolved = true;
|
|
825
857
|
}
|
|
@@ -830,6 +862,23 @@ async function findCheckpointByKey(box, key, signal) {
|
|
|
830
862
|
}
|
|
831
863
|
return unresolved ? undefined : null;
|
|
832
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* Prefer the durable operation result over inventory metadata.
|
|
867
|
+
*
|
|
868
|
+
* Snapshot inventory and the operation ledger can expose different creation
|
|
869
|
+
* timestamps. The ledger result is the acknowledgement returned by the
|
|
870
|
+
* idempotent operation, so recovery must rebuild the exact checkpoint ref
|
|
871
|
+
* from it when the service provides that result.
|
|
872
|
+
*/
|
|
873
|
+
function snapshotFromOperationResult(snapshot, lookup) {
|
|
874
|
+
if (lookup.result === undefined)
|
|
875
|
+
return snapshot;
|
|
876
|
+
if (!validSnapshotOperationResult(lookup.result) ||
|
|
877
|
+
lookup.result.snapshotId !== snapshot.snapshotId) {
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
return { ...snapshot, createdAt: lookup.result.createdAt };
|
|
881
|
+
}
|
|
833
882
|
/**
|
|
834
883
|
* Confirm that one snapshot id is a settled checkpoint this provider created.
|
|
835
884
|
*
|
|
@@ -877,8 +926,15 @@ async function findForkByKey(client, box, provider, key, signal) {
|
|
|
877
926
|
let unresolved = false;
|
|
878
927
|
for (const candidate of candidates) {
|
|
879
928
|
try {
|
|
880
|
-
|
|
881
|
-
|
|
929
|
+
const lookup = await forkOperationLookup(box, candidate.marker, signal);
|
|
930
|
+
if (lookup?.outcome === "found" &&
|
|
931
|
+
lookup.kind === "fork" &&
|
|
932
|
+
lookup.state === "succeeded") {
|
|
933
|
+
const authoritative = childFromOperationResult(candidate.child, lookup);
|
|
934
|
+
if (authoritative === undefined)
|
|
935
|
+
return undefined;
|
|
936
|
+
return { ...authoritative, marker: candidate.marker };
|
|
937
|
+
}
|
|
882
938
|
unresolved = true;
|
|
883
939
|
}
|
|
884
940
|
catch {
|
|
@@ -888,6 +944,34 @@ async function findForkByKey(client, box, provider, key, signal) {
|
|
|
888
944
|
}
|
|
889
945
|
return unresolved ? undefined : null;
|
|
890
946
|
}
|
|
947
|
+
/**
|
|
948
|
+
* Prefer the durable fork result over account-inventory metadata.
|
|
949
|
+
*
|
|
950
|
+
* Fork inventory can report a child timestamp from a later registry read. The
|
|
951
|
+
* operation ledger stores the original child acknowledgement, which is the
|
|
952
|
+
* stable value required to replay one exact fork reference after a restart.
|
|
953
|
+
* Some Sandbox responses omit that timestamp, so the validated inventory
|
|
954
|
+
* record supplies it only when the operation result does not.
|
|
955
|
+
*/
|
|
956
|
+
function childFromOperationResult(child, lookup) {
|
|
957
|
+
if (lookup.result === undefined) {
|
|
958
|
+
return { child, createdAt: child.createdAt };
|
|
959
|
+
}
|
|
960
|
+
const result = lookup.result;
|
|
961
|
+
if (!validOperationRecord(result))
|
|
962
|
+
return undefined;
|
|
963
|
+
const children = result.children;
|
|
964
|
+
if (!Array.isArray(children))
|
|
965
|
+
return undefined;
|
|
966
|
+
const operationChild = children.find((candidate) => validForkOperationChildResult(candidate) &&
|
|
967
|
+
(candidate.sandboxId ?? candidate.id) === child.id);
|
|
968
|
+
if (!operationChild)
|
|
969
|
+
return undefined;
|
|
970
|
+
const createdAt = operationChild.createdAt ?? child.createdAt;
|
|
971
|
+
if (!validOperationDate(createdAt))
|
|
972
|
+
return undefined;
|
|
973
|
+
return { child, createdAt };
|
|
974
|
+
}
|
|
891
975
|
async function findForkChildById(client, box, provider, id, signal) {
|
|
892
976
|
try {
|
|
893
977
|
if (typeof client.get !== "function")
|
|
@@ -1229,7 +1313,7 @@ async function forkConflictFromRemote(client, box, provider, request, verifier,
|
|
|
1229
1313
|
const child = await completeForkChild(client, recovered.child, signal);
|
|
1230
1314
|
if (!child)
|
|
1231
1315
|
return undefined;
|
|
1232
|
-
const environment = await environmentFromChild(recovered.marker.request, child, provider, child.createdAt, verifier, signal);
|
|
1316
|
+
const environment = await environmentFromChild(recovered.marker.request, child, provider, recovered.createdAt ?? child.createdAt, verifier, signal);
|
|
1233
1317
|
return environment === undefined
|
|
1234
1318
|
? undefined
|
|
1235
1319
|
: forkSuccess(request, environment, "replayed");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-tangle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -89,10 +89,10 @@
|
|
|
89
89
|
"LICENSE"
|
|
90
90
|
],
|
|
91
91
|
"dependencies": {
|
|
92
|
-
"@tangle-network/agent-interface": "^
|
|
92
|
+
"@tangle-network/agent-interface": "^2.0.0"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
|
-
"@tangle-network/sandbox": ">=0.34.
|
|
95
|
+
"@tangle-network/sandbox": ">=0.34.6 <1.0.0"
|
|
96
96
|
},
|
|
97
97
|
"peerDependenciesMeta": {
|
|
98
98
|
"@tangle-network/sandbox": {
|
|
@@ -101,12 +101,12 @@
|
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
103
|
"@tangle-network/agent-eval": "0.170.0",
|
|
104
|
-
"@tangle-network/agent-runtime": "0.
|
|
105
|
-
"@tangle-network/sandbox": "0.34.
|
|
104
|
+
"@tangle-network/agent-runtime": "0.184.0",
|
|
105
|
+
"@tangle-network/sandbox": "0.34.6",
|
|
106
106
|
"@types/node": "26.4.0",
|
|
107
107
|
"typescript": "7.0.2",
|
|
108
108
|
"vitest": "4.1.11",
|
|
109
|
-
"@tangle-network/agent-provider-testkit": "0.8.
|
|
109
|
+
"@tangle-network/agent-provider-testkit": "0.8.6"
|
|
110
110
|
},
|
|
111
111
|
"scripts": {
|
|
112
112
|
"build": "tsc -p tsconfig.json",
|